From 5fbcb59106846093d22ab24e74737730bd3d2987 Mon Sep 17 00:00:00 2001 From: Carlos Amengual Date: Mon, 2 Sep 2024 17:09:49 +0200 Subject: [PATCH] Use the smallest gamut that fits all the colors, via the new `ColorContext` interface --- .../echosvg/ext/awt/color/ColorContext.java | 36 +++++ .../ext/awt/color/StandardColorSpaces.java | 116 ++++++++------ .../awt}/color/profiles/A98RGBCompat-v4.icc | Bin .../ext/awt/color/profiles/D65_XYZ.icc | Bin 0 -> 968 bytes .../ext/awt}/color/profiles/Display P3.icc | Bin .../ext/awt}/color/profiles/ITU-R_BT2020.icc | Bin .../ext/awt}/color/profiles/NOTICE.txt | 4 +- .../awt}/color/profiles/WideGamutPhoto-v4.icc | Bin .../carte/echosvg/bridge/BridgeContext.java | 9 +- .../sf/carte/echosvg/bridge/PaintServer.java | 19 +-- .../echosvg/bridge/SVGTextElementBridge.java | 20 +-- .../svg12/SVGFlowRootElementBridge.java | 4 +- .../carte/echosvg/bridge/PaintServerTest.java | 145 +++++++++++++++--- .../engine/value/AbstractColorManager.java | 59 ++++--- .../svg/EchoSVGFlowTextElementBridge.java | 4 +- .../carte/echosvg/gvt/FillShapePainter.java | 62 ++------ .../carte/echosvg/gvt/PaintShapePainter.java | 123 +++++++++++++++ .../carte/echosvg/gvt/StrokeShapePainter.java | 47 ++---- .../echosvg/gvt/font/AWTGVTGlyphVector.java | 4 +- .../io/sf/carte/echosvg/gvt/font/Glyph.java | 8 +- .../carte/echosvg/gvt/text/TextPaintInfo.java | 94 +++++++++++- .../test/svg/SamplesSpecRenderingTest.java | 1 - .../test/svg/StyleBypassRenderingTest.java | 5 + .../tests/spec/color/colorProfiles-ref.svg | 87 +++++++++++ samples/tests/spec/color/colorProfiles.svg | 26 ++-- samples/tests/spec/scripting/domSVGColor.svg | 4 +- .../rendering/ContextFontDecoration.png | Bin 5931 -> 5924 bytes .../svggen/rendering/FontDecoration.png | Bin 5923 -> 5917 bytes .../tests/spec/color/colorProfiles.png | Bin 39593 -> 46259 bytes .../tests/spec/scripting/domSVGColor.png | Bin 35278 -> 46465 bytes .../tests/spec2/foreign/mermaid-mindmap.png | Bin 48095 -> 48079 bytes .../tests/spec2/foreign/mermaid-timeline.png | Bin 25306 -> 25286 bytes .../tests/spec2/styling/mermaid-color4.png | Bin 16725 -> 16704 bytes 33 files changed, 663 insertions(+), 214 deletions(-) create mode 100644 echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/ColorContext.java rename echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/CSSColorSpaces.java => echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/StandardColorSpaces.java (65%) rename {echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge => echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt}/color/profiles/A98RGBCompat-v4.icc (100%) create mode 100644 echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/D65_XYZ.icc rename {echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge => echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt}/color/profiles/Display P3.icc (100%) rename {echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge => echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt}/color/profiles/ITU-R_BT2020.icc (100%) rename {echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge => echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt}/color/profiles/NOTICE.txt (70%) rename {echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge => echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt}/color/profiles/WideGamutPhoto-v4.icc (100%) create mode 100644 echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/PaintShapePainter.java create mode 100644 samples/tests/spec/color/colorProfiles-ref.svg diff --git a/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/ColorContext.java b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/ColorContext.java new file mode 100644 index 000000000..3ef376f11 --- /dev/null +++ b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/ColorContext.java @@ -0,0 +1,36 @@ +/* + + See the NOTICE file distributed with this work for additional + information regarding copyright ownership. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package io.sf.carte.echosvg.ext.awt.color; + +import java.awt.color.ColorSpace; + +/** + * AWT color context. + */ +public interface ColorContext { + + /** + * The target color space. + * + * @return the color space, or {@code null} if the color space is the default + * sRGB. + */ + ColorSpace getColorSpace(); + +} diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/CSSColorSpaces.java b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/StandardColorSpaces.java similarity index 65% rename from echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/CSSColorSpaces.java rename to echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/StandardColorSpaces.java index 8a2e51380..7bd0c0f9a 100644 --- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/CSSColorSpaces.java +++ b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/color/StandardColorSpaces.java @@ -16,7 +16,7 @@ limitations under the License. */ -package io.sf.carte.echosvg.bridge; +package io.sf.carte.echosvg.ext.awt.color; import java.awt.Color; import java.awt.color.ColorSpace; @@ -28,7 +28,7 @@ /** * Predefined ICC color spaces. */ -class CSSColorSpaces { +public class StandardColorSpaces { private static ICC_ColorSpace a98rgb; @@ -38,81 +38,99 @@ class CSSColorSpaces { private static ICC_ColorSpace rec2020; - CSSColorSpaces() { + private static ICC_ColorSpace xyzD65; + + StandardColorSpaces() { super(); } + /** + * Get the A98 RGB color space. + * + * @return the instance of the color space. + */ public static ICC_ColorSpace getA98RGB() { - ICC_Profile prof; if (a98rgb == null) { - try (InputStream is = CSSColorSpaces.class.getResourceAsStream( - "color/profiles/A98RGBCompat-v4.icc")) { - prof = ICC_Profile.getInstance(is); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - a98rgb = new ICC_ColorSpace(prof); + a98rgb = loadColorSpace("profiles/A98RGBCompat-v4.icc"); } return a98rgb; } + /** + * Get the ProPhoto RGB color space. + * + * @return the instance of the color space. + */ public static ICC_ColorSpace getProphotoRGB() { - ICC_Profile prof; if (prophotoRGB == null) { - try (InputStream is = CSSColorSpaces.class.getResourceAsStream( - "color/profiles/WideGamutPhoto-v4.icc")) { - prof = ICC_Profile.getInstance(is); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - prophotoRGB = new ICC_ColorSpace(prof); + prophotoRGB = loadColorSpace("profiles/WideGamutPhoto-v4.icc"); } return prophotoRGB; } + /** + * Get the Display P3 color space. + * + * @return the instance of the color space. + */ public static ICC_ColorSpace getDisplayP3() { - ICC_Profile prof; if (displayP3 == null) { - try (InputStream is = CSSColorSpaces.class.getResourceAsStream( - "color/profiles/Display P3.icc")) { - prof = ICC_Profile.getInstance(is); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - displayP3 = new ICC_ColorSpace(prof); + displayP3 = loadColorSpace("profiles/Display P3.icc"); } return displayP3; } + /** + * Get the ITU recommendation bt.2020 color space. + * + * @return the instance of the color space. + */ public static ICC_ColorSpace getRec2020() { - ICC_Profile prof; if (rec2020 == null) { - try (InputStream is = CSSColorSpaces.class.getResourceAsStream( - "color/profiles/ITU-R_BT2020.icc")) { - prof = ICC_Profile.getInstance(is); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - rec2020 = new ICC_ColorSpace(prof); + rec2020 = loadColorSpace("profiles/ITU-R_BT2020.icc"); } return rec2020; } /** - * Merge the two color spaces. + * Get the XYZ color space with a D65 white. + * + * @return the instance of the color space. + */ + public static ICC_ColorSpace getXYZ_D65() { + if (xyzD65 == null) { + xyzD65 = loadColorSpace("profiles/D65_XYZ.icc"); + } + return xyzD65; + } + + private static ICC_ColorSpace loadColorSpace(String resource) { + ICC_Profile prof; + try (InputStream is = StandardColorSpaces.class.getResourceAsStream(resource)) { + prof = ICC_Profile.getInstance(is); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return new ICC_ColorSpace(prof); + } + + /** + * Do an approximate merge of the two color spaces. + * + * The merged color space must enclose the first space, and should be the + * smallest merge that is able to represent the given color. That color is + * intended to be represented by the second color space (although currently may + * not be expressed in that space). * * @param colorSpace1 the first color space, or {@code null}. * @param colorSpace2 the second color space. Cannot be equal to colorSpace1 * (check that before calling). - * @param color the color that needs to be represented by the merged - * space. - * @return the recommended merged color space, or {@code null} if sRGB is recommended. + * @param color a color that needs to be represented by the merged space. + * @return the recommended merged color space, or {@code null} if sRGB is + * recommended. */ - static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2, Color color) { + public static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2, Color color) { // For a reasoning, you may want to look at // https://upload.wikimedia.org/wikipedia/commons/b/b3/CIE1931xy_gamut_comparison_of_sRGB_P3_Rec2020.svg if (colorSpace1 == null) { @@ -146,7 +164,7 @@ static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2 } /** - * Determine which color space is the most adequate to represent the given + * Determine which RGB color space is the most adequate to represent the given * color, starting with sRGB and then giving precedence to the supplied color * space over others. * @@ -154,7 +172,7 @@ static ColorSpace mergeColorSpace(ColorSpace colorSpace1, ColorSpace colorSpace2 * @param colorSpace the suggested color space. * @return the recommended color space, or {@code null} if it is sRGB. */ - static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) { + public static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) { // Verify whether xyz is in sRGB gamut, otherwise check P3, A98, rec2020, prophoto. ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); if (isInGamut(xyz, cs)) { @@ -195,8 +213,8 @@ static ColorSpace containerRGBSpace(Color xyz, ColorSpace colorSpace) { * This method is only approximate. *

* - * @param color the color to check. - * @param space the color space. Cannot be {@code null}. + * @param color the color to check (cannot be {@code null}). + * @param space the color space (cannot be {@code null}). * @return {@code false} if the color may not be representable in the given * color space. */ @@ -207,7 +225,7 @@ private static boolean isInGamut(Color color, ColorSpace space) { for (float comp : comps) { if (comp <= min || comp >= max) { - // Symptom that the color is at the edge of the gamut + // Symptom that the color is outside or at the edge of the gamut return false; } } diff --git a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/A98RGBCompat-v4.icc b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/A98RGBCompat-v4.icc similarity index 100% rename from echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/A98RGBCompat-v4.icc rename to echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/A98RGBCompat-v4.icc diff --git a/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/D65_XYZ.icc b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/D65_XYZ.icc new file mode 100644 index 0000000000000000000000000000000000000000..8f200f32f28c932a5b0b6a333e846b72b15ef1b5 GIT binary patch literal 968 zcmb`GKTq306u@5`MOiYS3Yc#|2vxX(1R}8^97MaJG^zpVhKko*jc>LV}Y{_q*r!|D6GfzdhgM+A=`jQ?j|X!rB{~ zto#?sFaaN62|k)u7{F+eE^WLPMj|}_U#GV}`siDx{+e^l=Anh|8mPS8!QjYe$m5+> z(?I@(T(SGMjeLdt!%1|sHt9Gy9XIL=0L>+NpPM1_0rFI5Cs4?zB$uh}dBQnc#jPId zT%x?P8*z-K-5`G-D6%bXA&*;uB*qOhR?kG8;t}#}nm-c2Sfjp}^e>QpSGd%s_gyp_ z@)*bersMej7~mqZBDtGI$>s9t%c{ZZ^Go%*Ue)JWgLloyRg5NV_^uyG_BAjq&U6-{ zEOYQqXO<^G=?-A{6MJ?0B*P%ZmpWcPiidP(afn<>L<;fYNo!@8+ZWwznXOb literal 0 HcmV?d00001 diff --git a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/Display P3.icc b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/Display P3.icc similarity index 100% rename from echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/Display P3.icc rename to echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/Display P3.icc diff --git a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/ITU-R_BT2020.icc b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/ITU-R_BT2020.icc similarity index 100% rename from echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/ITU-R_BT2020.icc rename to echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/ITU-R_BT2020.icc diff --git a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/NOTICE.txt b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/NOTICE.txt similarity index 70% rename from echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/NOTICE.txt rename to echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/NOTICE.txt index 6b3bee480..e00ee86f8 100644 --- a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/NOTICE.txt +++ b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/NOTICE.txt @@ -4,5 +4,5 @@ file. The Display P3 profile was made by Apple and was obtained from color.org (ICC). -The profile for the ITU-R Recommendation BT.2020 was also obtained from -color.org (ICC). +The profiles for the ITU-R Recommendation BT.2020 and XYZ-D65 were also obtained +from color.org (ICC). diff --git a/echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/WideGamutPhoto-v4.icc b/echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/WideGamutPhoto-v4.icc similarity index 100% rename from echosvg-bridge/src/main/resources/io/sf/carte/echosvg/bridge/color/profiles/WideGamutPhoto-v4.icc rename to echosvg-awt-util/src/main/resources/io/sf/carte/echosvg/ext/awt/color/profiles/WideGamutPhoto-v4.icc diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/BridgeContext.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/BridgeContext.java index 546cec4bc..4ffd38da1 100644 --- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/BridgeContext.java +++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/BridgeContext.java @@ -70,6 +70,8 @@ import io.sf.carte.echosvg.dom.events.NodeEventTarget; import io.sf.carte.echosvg.dom.svg.SVGContext; import io.sf.carte.echosvg.dom.xbl.XBLManager; +import io.sf.carte.echosvg.ext.awt.color.StandardColorSpaces; +import io.sf.carte.echosvg.ext.awt.color.ColorContext; import io.sf.carte.echosvg.gvt.CompositeGraphicsNode; import io.sf.carte.echosvg.gvt.GraphicsNode; import io.sf.carte.echosvg.script.Interpreter; @@ -92,7 +94,7 @@ * @author For later modifications, see Git history. * @version $Id$ */ -public class BridgeContext implements ErrorConstants, CSSContext, Closeable { +public class BridgeContext implements ErrorConstants, CSSContext, ColorContext, Closeable { /** * The document is bridge context is dedicated to. @@ -410,16 +412,19 @@ public CSSEngine getCSSEngineForElement(Element e) { */ public void updateColorSpace(Color color, ColorSpace csRGB) { if (colorSpace != csRGB) { - colorSpace = CSSColorSpaces.mergeColorSpace(colorSpace, csRGB, color); + colorSpace = StandardColorSpaces.mergeColorSpace(colorSpace, csRGB, color); } } + // ColorContext ////////////////////////////////////////////////////////// + /** * Get the recommended color space for this context. * * @return the recommended color space, or {@code null} if the color space is * the default sRGB. */ + @Override public ColorSpace getColorSpace() { return colorSpace; } diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/PaintServer.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/PaintServer.java index 3a79cdac2..8482d810c 100644 --- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/PaintServer.java +++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/PaintServer.java @@ -43,6 +43,7 @@ import io.sf.carte.echosvg.css.engine.value.svg.ICCColor; import io.sf.carte.echosvg.css.engine.value.svg12.DeviceColor; import io.sf.carte.echosvg.css.engine.value.svg12.ICCNamedColor; +import io.sf.carte.echosvg.ext.awt.color.StandardColorSpaces; import io.sf.carte.echosvg.gvt.CompositeShapePainter; import io.sf.carte.echosvg.gvt.FillShapePainter; import io.sf.carte.echosvg.gvt.GraphicsNode; @@ -172,7 +173,7 @@ public static ShapePainter convertFillAndStroke(Element e, ShapeNode node, Bridg return null; Paint fillPaint = convertFillPaint(e, node, ctx); - FillShapePainter fp = new FillShapePainter(shape); + FillShapePainter fp = new FillShapePainter(shape, ctx); fp.setPaint(fillPaint); Stroke stroke = convertStroke(e); @@ -180,7 +181,7 @@ public static ShapePainter convertFillAndStroke(Element e, ShapeNode node, Bridg return fp; Paint strokePaint = convertStrokePaint(e, node, ctx); - StrokeShapePainter sp = new StrokeShapePainter(shape); + StrokeShapePainter sp = new StrokeShapePainter(shape, ctx); sp.setStroke(stroke); sp.setPaint(strokePaint); @@ -200,7 +201,7 @@ public static ShapePainter convertStrokePainter(Element e, ShapeNode node, Bridg return null; Paint strokePaint = convertStrokePaint(e, node, ctx); - StrokeShapePainter sp = new StrokeShapePainter(shape); + StrokeShapePainter sp = new StrokeShapePainter(shape, ctx); sp.setStroke(stroke); sp.setPaint(strokePaint); return sp; @@ -577,22 +578,22 @@ public static Color convertColor(RGBColorValue c, float opacity) { public static Color convertColor(ColorFunction c, float opacity, BridgeContext ctx) { switch (c.getCSSColorSpace()) { case ColorValue.CS_DISPLAY_P3: - ICC_ColorSpace space = CSSColorSpaces.getDisplayP3(); + ICC_ColorSpace space = StandardColorSpaces.getDisplayP3(); Color color = convert3Color(space, c, opacity); ctx.updateColorSpace(color, space); return color; case ColorValue.CS_A98_RGB: - space = CSSColorSpaces.getA98RGB(); + space = StandardColorSpaces.getA98RGB(); color = convert3Color(space, c, opacity); ctx.updateColorSpace(color, space); return color; case ColorValue.CS_PROPHOTO_RGB: - space = CSSColorSpaces.getProphotoRGB(); + space = StandardColorSpaces.getProphotoRGB(); color = convert3Color(space, c, opacity); ctx.updateColorSpace(color, space); return color; case ColorValue.CS_REC2020: - space = CSSColorSpaces.getRec2020(); + space = StandardColorSpaces.getRec2020(); color = convert3Color(space, c, opacity); ctx.updateColorSpace(color, space); return color; @@ -610,7 +611,7 @@ public static Color convertColor(ColorFunction c, float opacity, BridgeContext c float a = resolveAlphaComponent(c.getAlpha()); cs = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); color = new Color(cs, xyzd50, a * opacity); - cs = CSSColorSpaces.containerRGBSpace(color, ctx.getColorSpace()); + cs = StandardColorSpaces.containerRGBSpace(color, ctx.getColorSpace()); if (cs != null) { ctx.updateColorSpace(color, cs); } @@ -618,7 +619,7 @@ public static Color convertColor(ColorFunction c, float opacity, BridgeContext c case ColorValue.CS_XYZ_D50: cs = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ); color = convert3Color(cs, c, opacity); - cs = CSSColorSpaces.containerRGBSpace(color, ctx.getColorSpace()); + cs = StandardColorSpaces.containerRGBSpace(color, ctx.getColorSpace()); if (cs != null) { ctx.updateColorSpace(color, cs); } diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGTextElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGTextElementBridge.java index ee9cd9aa7..c4cbd8486 100644 --- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGTextElementBridge.java +++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGTextElementBridge.java @@ -610,7 +610,7 @@ protected void computeLaidoutText(BridgeContext ctx, Element e, GraphicsNode nod // Now get the real paint into - this needs to // wait until the text node is laidout so we can get // objectBoundingBox info. - TextPaintInfo pi = new TextPaintInfo(); + TextPaintInfo pi = new TextPaintInfo(ctx); setBaseTextPaintInfo(pi, e, node, ctx); // This get's Overline/underline info. setDecorationTextPaintInfo(pi, e); @@ -759,7 +759,7 @@ protected void rebuildACI() { TextPaintInfo pi, oldPI; if (cssProceedElement == e) { - pi = new TextPaintInfo(); + pi = new TextPaintInfo(ctx); setBaseTextPaintInfo(pi, e, node, ctx); setDecorationTextPaintInfo(pi, e); oldPI = elemTPI.get(e); @@ -1455,10 +1455,10 @@ protected Map getAttributeMap(BridgeContext ctx, Element elem // Add null TPI objects to the text (after we set it on the // Text we will swap in the correct values. - TextPaintInfo pi = new TextPaintInfo(); + TextPaintInfo pi = new TextPaintInfo(ctx); // Set some basic props so we can get bounds info for complex paints. pi.visible = true; - pi.fillPaint = Color.black; + pi.setFillPaint(Color.black); result.put(PAINT_INFO, pi); elemTPI.put(element, pi); @@ -1744,10 +1744,10 @@ public void setDecorationTextPaintInfo(TextPaintInfo pi, Element element) { switch (s.charAt(0)) { case 'u': if (pi.fillPaint != null) { - pi.underlinePaint = pi.fillPaint; + pi.underlinePaint = pi.getFillPaint(); } if (pi.strokePaint != null) { - pi.underlineStrokePaint = pi.strokePaint; + pi.underlineStrokePaint = pi.getStrokePaint(); } if (pi.strokeStroke != null) { pi.underlineStroke = pi.strokeStroke; @@ -1755,10 +1755,10 @@ public void setDecorationTextPaintInfo(TextPaintInfo pi, Element element) { break; case 'o': if (pi.fillPaint != null) { - pi.overlinePaint = pi.fillPaint; + pi.overlinePaint = pi.getFillPaint(); } if (pi.strokePaint != null) { - pi.overlineStrokePaint = pi.strokePaint; + pi.overlineStrokePaint = pi.getStrokePaint(); } if (pi.strokeStroke != null) { pi.overlineStroke = pi.strokeStroke; @@ -1766,10 +1766,10 @@ public void setDecorationTextPaintInfo(TextPaintInfo pi, Element element) { break; case 'l': if (pi.fillPaint != null) { - pi.strikethroughPaint = pi.fillPaint; + pi.strikethroughPaint = pi.getFillPaint(); } if (pi.strokePaint != null) { - pi.strikethroughStrokePaint = pi.strokePaint; + pi.strikethroughStrokePaint = pi.getStrokePaint(); } if (pi.strokeStroke != null) { pi.strikethroughStroke = pi.strokeStroke; diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/svg12/SVGFlowRootElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/svg12/SVGFlowRootElementBridge.java index 0dd195ea4..ee8b02643 100644 --- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/svg12/SVGFlowRootElementBridge.java +++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/svg12/SVGFlowRootElementBridge.java @@ -468,10 +468,10 @@ protected AttributedString getFlowDiv(BridgeContext ctx, Element element) { } protected AttributedString gatherFlowPara(BridgeContext ctx, Element div) { - TextPaintInfo divTPI = new TextPaintInfo(); + TextPaintInfo divTPI = new TextPaintInfo(ctx); // Set some basic props so we can get bounds info for complex paints. divTPI.visible = true; - divTPI.fillPaint = Color.black; + divTPI.setFillPaint(Color.black); elemTPI.put(div, divTPI); AttributedStringBuffer asb = new AttributedStringBuffer(); diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PaintServerTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PaintServerTest.java index d0fa3f49e..30de55976 100644 --- a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PaintServerTest.java +++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PaintServerTest.java @@ -26,6 +26,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Paint; +import java.awt.color.ColorSpace; import java.awt.geom.Dimension2D; import org.junit.jupiter.api.BeforeEach; @@ -44,6 +45,7 @@ import io.sf.carte.echosvg.css.engine.CSSStylableElement; import io.sf.carte.echosvg.css.engine.value.ColorValue; import io.sf.carte.echosvg.css.engine.value.Value; +import io.sf.carte.echosvg.ext.awt.color.StandardColorSpaces; import io.sf.carte.echosvg.gvt.GraphicsNode; import io.sf.carte.echosvg.util.CSSConstants; import io.sf.carte.echosvg.util.SVGConstants; @@ -86,6 +88,24 @@ public void testConvertColorFunction() { assertEquals(255, color.getAlpha()); assertNull(context.getColorSpace()); + + assertEquals("#a628b9", toHexString(color.getRGBComponents(null))); + } + + private static String toHexString(float[] comp) { + String s = '#' + hexComp(comp[0]) + hexComp(comp[1]) + hexComp(comp[2]); + if (comp[3] != 1f) { + s += hexComp(comp[3]); + } + return s; + } + + private static String hexComp(float f) { + String s = Integer.toHexString(Math.round(f * 255f)); + if (s.length() == 1) { + s = '0' + s; + } + return s; } @Test @@ -100,7 +120,7 @@ public void testConvertColorFunctionP3() { assertEquals(.7f, comp[2]); assertEquals(255, color.getAlpha()); - assertSame(CSSColorSpaces.getDisplayP3(), context.getColorSpace()); + assertSame(StandardColorSpaces.getDisplayP3(), context.getColorSpace()); } @Test @@ -108,6 +128,8 @@ public void testConvertColorFunctionLinearSRGB() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(srgb-linear 0.14 0.002 0.064)", ColorValue.CS_SRGB_LINEAR); assertNotNull(color); + assertSame(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), color.getColorSpace()); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.14f, comp[0]); @@ -116,6 +138,27 @@ public void testConvertColorFunctionLinearSRGB() { assertEquals(255, color.getAlpha()); assertNull(context.getColorSpace()); + + assertEquals("#690748", toHexString(color.getRGBComponents(null))); + } + + @Test + public void testConvertColorFunctionXYZD50_RGB() { + Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, + "color(xyz-d50 0.3636655 0.6739725 0.0938054)", ColorValue.CS_XYZ_D50); + assertNotNull(color); + assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), color.getColorSpace()); + + float[] comp = new float[3]; + color.getColorComponents(comp); + assertEquals(0.3636655f, comp[0], 1e-7f); + assertEquals(0.6739725f, comp[1], 1e-7f); + assertEquals(0.0938054f, comp[2], 1e-7f); + assertEquals(255, color.getAlpha()); + + assertNull(context.getColorSpace()); + + assertEquals("#0bf80c", toHexString(color.getRGBComponents(null))); } @Test @@ -123,6 +166,7 @@ public void testConvertColorFunctionXYZD50_P3() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz-d50 0.07 0.08 0.19 / 0.969)", ColorValue.CS_XYZ_D50); assertNotNull(color); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.07f, comp[0], 1e-5f); @@ -130,7 +174,7 @@ public void testConvertColorFunctionXYZD50_P3() { assertEquals(.19f, comp[2], 1e-5f); assertEquals(247, color.getAlpha()); - assertSame(CSSColorSpaces.getDisplayP3(), context.getColorSpace()); + assertSame(StandardColorSpaces.getDisplayP3(), context.getColorSpace()); } @Test @@ -138,6 +182,7 @@ public void testConvertColorFunctionXYZD50_A98() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz-d50 0.07 0.08 0.23 / 0.969)", ColorValue.CS_XYZ_D50); assertNotNull(color); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.07f, comp[0], 1e-5f); @@ -145,7 +190,7 @@ public void testConvertColorFunctionXYZD50_A98() { assertEquals(.23f, comp[2], 1e-5f); assertEquals(247, color.getAlpha()); - assertSame(CSSColorSpaces.getA98RGB(), context.getColorSpace()); + assertSame(StandardColorSpaces.getA98RGB(), context.getColorSpace()); } @Test @@ -153,6 +198,7 @@ public void testConvertColorFunctionXYZD50_REC() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz-d50 0.07 0.08 0.28)", ColorValue.CS_XYZ_D50); assertNotNull(color); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.07f, comp[0], 1e-5f); @@ -160,7 +206,7 @@ public void testConvertColorFunctionXYZD50_REC() { assertEquals(.28f, comp[2], 1e-5f); assertEquals(255, color.getAlpha()); - assertSame(CSSColorSpaces.getRec2020(), context.getColorSpace()); + assertSame(StandardColorSpaces.getRec2020(), context.getColorSpace()); } @Test @@ -168,6 +214,7 @@ public void testConvertColorFunctionXYZD50_ProPhoto() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz-d50 0.2 0.08 0.3 / 0.969)", ColorValue.CS_XYZ_D50); assertNotNull(color); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.2f, comp[0], 1e-5f); @@ -175,7 +222,7 @@ public void testConvertColorFunctionXYZD50_ProPhoto() { assertEquals(.3f, comp[2], 1e-5f); assertEquals(247, color.getAlpha()); - assertSame(CSSColorSpaces.getProphotoRGB(), context.getColorSpace()); + assertSame(StandardColorSpaces.getProphotoRGB(), context.getColorSpace()); } @Test @@ -183,6 +230,8 @@ public void testConvertColorFunctionXYZD65() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz-d65 0.065 0.033 0.06 / 0.2)", ColorValue.CS_XYZ_D65); assertNotNull(color); + assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), color.getColorSpace()); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.06586f, comp[0], 1e-5f); // D50 @@ -198,6 +247,8 @@ public void testConvertColorFunctionXYZ() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz 0.065 0.033 0.06 / 0.2)", ColorValue.CS_XYZ); assertNotNull(color); + assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), color.getColorSpace()); + float[] comp = new float[3]; color.getColorComponents(comp); assertEquals(.06586f, comp[0], 1e-5f); // D50 @@ -206,20 +257,56 @@ public void testConvertColorFunctionXYZ() { assertEquals(51, color.getAlpha()); assertNull(context.getColorSpace()); + + assertEquals("#65054533", toHexString(color.getRGBComponents(null))); + } + + @Test + public void testConvertColorFunctionXYZ_2() { + Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "color(xyz 0.33801785 0.67248034 0.1155108)", + ColorValue.CS_XYZ); + assertNotNull(color); + assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), color.getColorSpace()); + + float[] comp = color.getColorComponents(null); + assertEquals(0.3637795f, comp[0], 5e-5f); // D50 + assertEquals(0.6740978f, comp[1], 1e-5f); // D50 + assertEquals(0.09387444f, comp[2], 3e-5f); // D50 + assertEquals(255, color.getAlpha()); + + assertNull(context.getColorSpace()); + + assertEquals("#0bf80c", toHexString(color.getRGBComponents(null))); } @Test public void testConvertLab() { Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "lab(20.6% 48.8 -13.8 / 0.2)", null); assertNotNull(color); + float[] comp = new float[3]; color.getColorComponents(comp); - assertEquals(0.06798f, comp[0], 1e-5f); // D50 - assertEquals(0.03141f, comp[1], 1e-5f); // D50 - assertEquals(0.04692f, comp[2], 1e-5f); // D50 + assertEquals(0.269976f, comp[0], 1e-5f); // Rec 2020 + assertEquals(0.026763f, comp[1], 1e-5f); // Rec 2020 + assertEquals(0.2079f, comp[2], 1e-4f); // Rec 2020 assertEquals(51, color.getAlpha()); - assertSame(CSSColorSpaces.getRec2020(), context.getColorSpace()); + assertSame(StandardColorSpaces.getRec2020(), context.getColorSpace()); + } + + @Test + public void testConvertLabSRGB() { + Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "lab(83% 21.5 20.08)", null); + assertNotNull(color); + + float[] comp = new float[3]; + color.getColorComponents(comp); + assertEquals(0.999577f, comp[0], 1e-5f); // R + assertEquals(0.750328f, comp[1], 1e-5f); // G + assertEquals(0.667233f, comp[2], 1e-5f); // B + assertEquals(255, color.getAlpha()); + + assertNull(context.getColorSpace()); } @Test @@ -228,12 +315,28 @@ public void testConvertLCh() { assertNotNull(color); float[] comp = new float[3]; color.getColorComponents(comp); - assertEquals(0.06798f, comp[0], 1e-5f); // D50 - assertEquals(0.03141f, comp[1], 1e-5f); // D50 - assertEquals(0.046909f, comp[2], 1e-5f); // D50 + assertEquals(0.269976f, comp[0], 1e-5f); // Rec 2020 + assertEquals(0.026763f, comp[1], 1e-5f); // Rec 2020 + assertEquals(0.2079f, comp[2], 1e-4f); // Rec 2020 assertEquals(51, color.getAlpha()); - assertSame(CSSColorSpaces.getRec2020(), context.getColorSpace()); + assertSame(StandardColorSpaces.getRec2020(), context.getColorSpace()); + } + + @Test + public void testConvertLCh_99Pcnt() { + Color color = convertPaint(CSSConstants.CSS_FILL_PROPERTY, "lch(99% 110 60)", null); + assertNotNull(color); + assertSame(StandardColorSpaces.getProphotoRGB(), color.getColorSpace()); + + float[] comp = new float[3]; + color.getColorComponents(comp); + assertEquals(1f, comp[0], 1e-3f); // Prophoto + assertEquals(0.975576f, comp[1], 1e-3f); // Prophoto + assertEquals(0.9142f, comp[2], 1e-3f); // Prophoto + assertEquals(255, color.getAlpha()); + + assertSame(StandardColorSpaces.getProphotoRGB(), context.getColorSpace()); } @Test @@ -242,12 +345,12 @@ public void testConvertOkLab() { assertNotNull(color); float[] comp = new float[3]; color.getColorComponents(comp); - assertEquals(0.068184f, comp[0], 1e-5f); // D50 - assertEquals(0.031465f, comp[1], 1e-5f); // D50 - assertEquals(0.04694f, comp[2], 1e-5f); // D50 + assertEquals(0.27054f, comp[0], 1e-5f); // Rec 2020 + assertEquals(0.02655f, comp[1], 1e-5f); // Rec 2020 + assertEquals(0.208f, comp[2], 1e-3f); // Rec 2020 assertEquals(204, color.getAlpha()); - assertSame(CSSColorSpaces.getRec2020(), context.getColorSpace()); + assertSame(StandardColorSpaces.getRec2020(), context.getColorSpace()); } @Test @@ -256,12 +359,12 @@ public void testConvertOkLCh() { assertNotNull(color); float[] comp = new float[3]; color.getColorComponents(comp); - assertEquals(0.0681926f, comp[0], 1e-5f); // D50 - assertEquals(0.031464f, comp[1], 1e-5f); // D50 - assertEquals(0.04694f, comp[2], 1e-5f); // D50 + assertEquals(0.27056f, comp[0], 1e-5f); // Rec 2020 + assertEquals(0.026513f, comp[1], 1e-5f); // Rec 2020 + assertEquals(0.208f, comp[2], 1e-3f); // Rec 2020 assertEquals(204, color.getAlpha()); - assertSame(CSSColorSpaces.getRec2020(), context.getColorSpace()); + assertSame(StandardColorSpaces.getRec2020(), context.getColorSpace()); } @Test diff --git a/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java b/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java index 11a755b95..588c030ae 100644 --- a/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java +++ b/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java @@ -24,12 +24,14 @@ import org.w3c.css.om.unit.CSSUnit; import org.w3c.dom.DOMException; +import io.sf.carte.doc.style.css.CSSColor; import io.sf.carte.doc.style.css.CSSTypedValue; import io.sf.carte.doc.style.css.CSSValue.CssType; import io.sf.carte.doc.style.css.nsac.CSSParseException; import io.sf.carte.doc.style.css.nsac.LexicalUnit; import io.sf.carte.doc.style.css.nsac.LexicalUnit.LexicalType; import io.sf.carte.doc.style.css.parser.CSSParser; +import io.sf.carte.doc.style.css.property.NumberValue; import io.sf.carte.doc.style.css.property.StyleValue; import io.sf.carte.doc.style.css.property.ValueFactory; import io.sf.carte.echosvg.css.dom.CSSValue.Type; @@ -140,24 +142,33 @@ public Value createValue(LexicalUnit lunit, CSSEngine engine) throws DOMExceptio case OKLCHCOLOR: case COLOR_MIX: { ValueFactory vf = new ValueFactory(); - String xyzSerialization; + String colorSerialization; try { StyleValue css4jValue = vf.createCSSValue(lunit); if (css4jValue.getCssValueType() != CssType.TYPED) { throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); } - xyzSerialization = ((io.sf.carte.doc.style.css.CSSColorValue) css4jValue).getColor() - .toColorSpace(io.sf.carte.doc.style.css.ColorSpace.xyz_d50).toString(); + CSSColor color = ((io.sf.carte.doc.style.css.CSSColorValue) css4jValue).getColor(); + if (color.isInGamut(io.sf.carte.doc.style.css.ColorSpace.srgb_linear)) { + color = color.toColorSpace(io.sf.carte.doc.style.css.ColorSpace.srgb); + setComponentsMaximumFractionDigits(color, 6); + colorSerialization = color.toString(); + // Now parse the result + lunit = reparseColor(colorSerialization); + return createRGBColor(lunit); + } else if (color.isInGamut(io.sf.carte.doc.style.css.ColorSpace.rec2020)) { + color = color.toColorSpace(io.sf.carte.doc.style.css.ColorSpace.rec2020); + } else { + // Prophoto is the largest supported RGB space + color = color.toColorSpace(io.sf.carte.doc.style.css.ColorSpace.prophoto_rgb); + } + setComponentsMaximumFractionDigits(color, 6); + colorSerialization = color.toString(); } catch (DOMException e) { throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); } - // Now re-parse the result - CSSParser parser = new CSSParser(); - try { - lunit = parser.parsePropertyValue(new StringReader(xyzSerialization)); - } catch (CSSParseException | IOException e) { - throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); - } + // Now parse the result + lunit = reparseColor(colorSerialization); } case COLOR_FUNCTION: return createColorFunction(lunit); @@ -170,17 +181,14 @@ public Value createValue(LexicalUnit lunit, CSSEngine engine) throws DOMExceptio if (css4jValue.getCssValueType() != CssType.TYPED) { throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); } - rgbSerialization = ((CSSTypedValue) css4jValue).toRGBColor().toString(); + CSSColor rgb = ((CSSTypedValue) css4jValue).toRGBColor(); + setComponentsMaximumFractionDigits(rgb, 6); + rgbSerialization = rgb.toString(); } catch (DOMException e) { throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); } - // Now re-parse the result - CSSParser parser = new CSSParser(); - try { - lunit = parser.parsePropertyValue(new StringReader(rgbSerialization)); - } catch (CSSParseException | IOException e) { - throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType()); - } + // Now parse the result + lunit = reparseColor(rgbSerialization); } case RGBCOLOR: return createRGBColor(lunit); @@ -190,6 +198,21 @@ public Value createValue(LexicalUnit lunit, CSSEngine engine) throws DOMExceptio } } + private static void setComponentsMaximumFractionDigits(CSSColor color, int maxFractionDigits) { + ((NumberValue) color.item(1)).setMaximumFractionDigits(maxFractionDigits); + ((NumberValue) color.item(2)).setMaximumFractionDigits(maxFractionDigits); + ((NumberValue) color.item(3)).setMaximumFractionDigits(maxFractionDigits); + } + + private LexicalUnit reparseColor(String colorSerialization) throws DOMException { + CSSParser parser = new CSSParser(); + try { + return parser.parsePropertyValue(new StringReader(colorSerialization)); + } catch (CSSParseException | IOException e) { + throw createMalformedLexicalUnitDOMException(); + } + } + /** * Implements * {@link ValueManager#computeValue(CSSStylableElement,String,CSSEngine,int,StyleMap,Value)}. diff --git a/echosvg-extension/src/main/java/io/sf/carte/echosvg/extension/svg/EchoSVGFlowTextElementBridge.java b/echosvg-extension/src/main/java/io/sf/carte/echosvg/extension/svg/EchoSVGFlowTextElementBridge.java index c5ea7821a..a32f51a8b 100644 --- a/echosvg-extension/src/main/java/io/sf/carte/echosvg/extension/svg/EchoSVGFlowTextElementBridge.java +++ b/echosvg-extension/src/main/java/io/sf/carte/echosvg/extension/svg/EchoSVGFlowTextElementBridge.java @@ -295,10 +295,10 @@ protected AttributedString getFlowDiv(BridgeContext ctx, Element element) { } protected AttributedString gatherFlowPara(BridgeContext ctx, Element div) { - TextPaintInfo divTPI = new TextPaintInfo(); + TextPaintInfo divTPI = new TextPaintInfo(ctx); // Set some basic props so we can get bounds info for complex paints. divTPI.visible = true; - divTPI.fillPaint = Color.black; + divTPI.setFillPaint(Color.black); elemTPI.put(div, divTPI); AttributedStringBuffer asb = new AttributedStringBuffer(); diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/FillShapePainter.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/FillShapePainter.java index 642436dc9..0c4d7124b 100644 --- a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/FillShapePainter.java +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/FillShapePainter.java @@ -24,52 +24,44 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import io.sf.carte.echosvg.ext.awt.color.ColorContext; + /** * A shape painter that can be used to fill a shape. * - * @author Thierry Kormann - * @author For later modifications, see Git history. + *

+ * Original author: Thierry Kormann. + * For later modifications, see Git history. + *

* @version $Id$ */ -public class FillShapePainter implements ShapePainter { - - /** - * The Shape to be painted. - */ - protected Shape shape; - - /** - * The paint attribute used to fill the shape. - */ - protected Paint paint; +public class FillShapePainter extends PaintShapePainter { /** * Constructs a new FillShapePainter that can be used to fill a * Shape. * * @param shape Shape to be painted by this painter Should not be null. + * @param ctx the color context. Cannot be {@code null}. */ - public FillShapePainter(Shape shape) { - if (shape == null) - throw new IllegalArgumentException("Shape can not be null!"); - - this.shape = shape; + public FillShapePainter(Shape shape, ColorContext ctx) { + super(shape, ctx); } /** * Sets the paint used to fill a shape. * - * @param newPaint the paint object used to fill the shape + * @param newPaint the paint object used to fill the shape. */ public void setPaint(Paint newPaint) { - this.paint = newPaint; + super.setPaint(newPaint); } /** - * Gets the paint used to draw the outline of the shape. + * Gets the paint used to fill the shape. */ public Paint getPaint() { - return paint; + return super.getPaint(); } /** @@ -80,7 +72,7 @@ public Paint getPaint() { @Override public void paint(Graphics2D g2d) { if (paint != null) { - g2d.setPaint(paint); + g2d.setPaint(getPaint()); g2d.fill(shape); } } @@ -147,28 +139,4 @@ public boolean inSensitiveArea(Point2D pt) { return shape.contains(pt); } - /** - * Sets the Shape this shape painter is associated with. - * - * @param shape new shape this painter should be associated with. Should not be - * null. - */ - @Override - public void setShape(Shape shape) { - if (shape == null) { - throw new IllegalArgumentException(); - } - this.shape = shape; - } - - /** - * Gets the Shape this shape painter is associated with. - * - * @return shape associated with this Painter. - */ - @Override - public Shape getShape() { - return shape; - } - } diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/PaintShapePainter.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/PaintShapePainter.java new file mode 100644 index 000000000..1ace011de --- /dev/null +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/PaintShapePainter.java @@ -0,0 +1,123 @@ +/* + + See the NOTICE file distributed with this work for additional + information regarding copyright ownership. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ +package io.sf.carte.echosvg.gvt; + +import java.awt.Color; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.color.ColorSpace; + +import io.sf.carte.echosvg.ext.awt.color.ColorContext; + +/** + * A shape painter that has a paint. + * + * @version $Id$ + */ +abstract class PaintShapePainter implements ShapePainter { + + /** + * The Shape to be painted. + */ + protected Shape shape; + + /** + * The paint attribute used to fill or stroke the shape. + */ + protected Paint paint; + + /** + * The color context. + */ + private ColorContext context; + + /** + * Constructs a new PaintShapePainter that can be used to color a + * Shape. + * + * @param shape Shape to be painted by this painter Should not be null. + * @param ctx the color context. Cannot be {@code null}. + */ + protected PaintShapePainter(Shape shape, ColorContext ctx) { + if (shape == null) + throw new IllegalArgumentException("Shape can not be null!"); + if (ctx == null) + throw new IllegalArgumentException("Color context can not be null!"); + + this.shape = shape; + this.context = ctx; + } + + /** + * Sets the paint used to fill or stroke a shape. + * + * @param newPaint the paint object used to fill the shape + */ + public void setPaint(Paint newPaint) { + this.paint = newPaint; + } + + /** + * Gets the paint used to fill or draw the outline of the shape. + */ + public Paint getPaint() { + if (paint instanceof Color) { + Color color = (Color) paint; + ColorSpace space = color.getColorSpace(); + ColorSpace targetSpace = context.getColorSpace(); + if (targetSpace == null) { + // sRGB + if (!space.isCS_sRGB()) { + float[] comp = color.getRGBComponents(null); + color = new Color(comp[0], comp[1], comp[2], comp[3]); + } + } else { + color = new Color(targetSpace, color.getColorComponents(targetSpace, null), + color.getAlpha() / 255f); + } + return color; + } + return paint; + } + + /** + * Sets the Shape this shape painter is associated with. + * + * @param shape new shape this painter should be associated with. Should not be + * null. + */ + @Override + public void setShape(Shape shape) { + if (shape == null) { + throw new IllegalArgumentException(); + } + this.shape = shape; + } + + /** + * Gets the Shape this shape painter is associated with. + * + * @return shape associated with this Painter. + */ + @Override + public Shape getShape() { + return shape; + } + +} diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/StrokeShapePainter.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/StrokeShapePainter.java index 36561938d..d17c91fb9 100644 --- a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/StrokeShapePainter.java +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/StrokeShapePainter.java @@ -25,19 +25,18 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import io.sf.carte.echosvg.ext.awt.color.ColorContext; + /** * A shape painter that can be used to draw the outline of a shape. * - * @author Thierry Kormann - * @author For later modifications, see Git history. + *

+ * Original author: Thierry Kormann. + * For later modifications, see Git history. + *

* @version $Id$ */ -public class StrokeShapePainter implements ShapePainter { - - /** - * Shape painted by this painter. - */ - protected Shape shape; +public class StrokeShapePainter extends PaintShapePainter { /** * Stroked version of the shape. @@ -49,22 +48,15 @@ public class StrokeShapePainter implements ShapePainter { */ protected Stroke stroke; - /** - * The paint attribute used to draw the outline of the shape. - */ - protected Paint paint; - /** * Constructs a new ShapePainter that can be used to draw the * outline of a Shape. * * @param shape shape to be painted by this painter. Should not be null. + * @param ctx the color context. Cannot be {@code null}. */ - public StrokeShapePainter(Shape shape) { - if (shape == null) { - throw new IllegalArgumentException(); - } - this.shape = shape; + public StrokeShapePainter(Shape shape, ColorContext ctx) { + super(shape, ctx); } /** @@ -97,7 +89,7 @@ public void setPaint(Paint newPaint) { * Gets the paint used to draw the outline of the shape. */ public Paint getPaint() { - return paint; + return super.getPaint(); } /** @@ -108,7 +100,7 @@ public Paint getPaint() { @Override public void paint(Graphics2D g2d) { if (stroke != null && paint != null) { - g2d.setPaint(paint); + g2d.setPaint(getPaint()); g2d.setStroke(stroke); g2d.draw(shape); } @@ -198,21 +190,8 @@ public boolean inSensitiveArea(Point2D pt) { */ @Override public void setShape(Shape shape) { - if (shape == null) { - throw new IllegalArgumentException(); - } - this.shape = shape; + super.setShape(shape); this.strokedShape = null; } - /** - * Gets the Shape this shape painter is associated with. - * - * @return shape associated with this painter. - */ - @Override - public Shape getShape() { - return shape; - } - } diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/AWTGVTGlyphVector.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/AWTGVTGlyphVector.java index baec99bbf..82ad4de4d 100644 --- a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/AWTGVTGlyphVector.java +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/AWTGVTGlyphVector.java @@ -793,9 +793,9 @@ public void draw(Graphics2D graphics2D, AttributedCharacterIterator aci) { if (!tpi.visible) return; - Paint fillPaint = tpi.fillPaint; + Paint fillPaint = tpi.getFillPaint(); Stroke stroke = tpi.strokeStroke; - Paint strokePaint = tpi.strokePaint; + Paint strokePaint = tpi.getStrokePaint(); if ((fillPaint == null) && ((strokePaint == null) || (stroke == null))) return; diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/Glyph.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/Glyph.java index 2c055be5e..22d7c28c5 100644 --- a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/Glyph.java +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/font/Glyph.java @@ -19,6 +19,7 @@ package io.sf.carte.echosvg.gvt.font; import java.awt.Graphics2D; +import java.awt.Paint; import java.awt.Shape; import java.awt.font.GlyphMetrics; import java.awt.geom.AffineTransform; @@ -359,15 +360,16 @@ public void draw(Graphics2D graphics2D) { // paint the dShape first if ((dShape != null) && (tpi != null)) { Shape tShape = tr.createTransformedShape(dShape); - if (tpi.fillPaint != null) { - graphics2D.setPaint(tpi.fillPaint); + Paint fillPaint = tpi.getFillPaint(); + if (fillPaint != null) { + graphics2D.setPaint(fillPaint); graphics2D.fill(tShape); } // check if we need to draw the outline of this glyph if (tpi.strokeStroke != null && tpi.strokePaint != null) { graphics2D.setStroke(tpi.strokeStroke); - graphics2D.setPaint(tpi.strokePaint); + graphics2D.setPaint(tpi.getStrokePaint()); graphics2D.draw(tShape); } } diff --git a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/text/TextPaintInfo.java b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/text/TextPaintInfo.java index d9c906d9f..d7487c09d 100644 --- a/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/text/TextPaintInfo.java +++ b/echosvg-gvt/src/main/java/io/sf/carte/echosvg/gvt/text/TextPaintInfo.java @@ -18,9 +18,13 @@ */ package io.sf.carte.echosvg.gvt.text; +import java.awt.Color; import java.awt.Composite; import java.awt.Paint; import java.awt.Stroke; +import java.awt.color.ColorSpace; + +import io.sf.carte.echosvg.ext.awt.color.ColorContext; /** * One line Class Desc @@ -53,7 +57,13 @@ public class TextPaintInfo { public int startChar, endChar; - public TextPaintInfo() { + /** + * The color context. + */ + private ColorContext context; + + public TextPaintInfo(ColorContext context) { + this.context = context; } public TextPaintInfo(TextPaintInfo pi) { @@ -80,6 +90,7 @@ public void set(TextPaintInfo pi) { this.strikethroughStroke = null; this.visible = false; + this.context = null; } else { this.fillPaint = pi.fillPaint; this.strokePaint = pi.strokePaint; @@ -99,7 +110,88 @@ public void set(TextPaintInfo pi) { this.strikethroughStroke = pi.strikethroughStroke; this.visible = pi.visible; + this.context = pi.context; + } + } + + public Paint getFillPaint() { + if (fillPaint instanceof Color) { + Color color = (Color) fillPaint; + ColorSpace space = color.getColorSpace(); + ColorSpace targetSpace = context.getColorSpace(); + if (targetSpace == null) { + // sRGB + if (!space.isCS_sRGB()) { + float[] comp = color.getRGBComponents(null); + color = new Color(comp[0], comp[1], comp[2], comp[3]); + } + } else { + color = new Color(targetSpace, color.getColorComponents(targetSpace, null), + color.getAlpha() / 255f); + } + return color; } + return fillPaint; + } + + public void setFillPaint(Paint fillPaint) { + this.fillPaint = fillPaint; + } + + public Paint getStrokePaint() { + return strokePaint; + } + + public void setStrokePaint(Paint strokePaint) { + this.strokePaint = strokePaint; + } + + public Paint getUnderlinePaint() { + return underlinePaint; + } + + public void setUnderlinePaint(Paint underlinePaint) { + this.underlinePaint = underlinePaint; + } + + public Paint getUnderlineStrokePaint() { + return underlineStrokePaint; + } + + public void setUnderlineStrokePaint(Paint underlineStrokePaint) { + this.underlineStrokePaint = underlineStrokePaint; + } + + public Paint getOverlinePaint() { + return overlinePaint; + } + + public void setOverlinePaint(Paint overlinePaint) { + this.overlinePaint = overlinePaint; + } + + public Paint getOverlineStrokePaint() { + return overlineStrokePaint; + } + + public void setOverlineStrokePaint(Paint overlineStrokePaint) { + this.overlineStrokePaint = overlineStrokePaint; + } + + public Paint getStrikethroughPaint() { + return strikethroughPaint; + } + + public void setStrikethroughPaint(Paint strikethroughPaint) { + this.strikethroughPaint = strikethroughPaint; + } + + public Paint getStrikethroughStrokePaint() { + return strikethroughStrokePaint; + } + + public void setStrikethroughStrokePaint(Paint strikethroughStrokePaint) { + this.strikethroughStrokePaint = strikethroughStrokePaint; } public static boolean equivilent(TextPaintInfo tpi1, TextPaintInfo tpi2) { diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java index 4d7d9a236..53ef9c2a4 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java @@ -59,7 +59,6 @@ public void testColorProfile() throws TranscoderException, IOException { test("samples/tests/spec/color/colorProfile.svg"); } - @Disabled @Test public void testColorProfiles() throws TranscoderException, IOException { testNV("samples/tests/spec/color/colorProfiles.svg"); diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java index 54bc0eba8..1b03a9f29 100644 --- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java +++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java @@ -279,6 +279,11 @@ public void testColorProfile() throws TranscoderException, IOException { test("samples/tests/spec/color/colorProfile.svg"); } + @Test + public void testColorProfiles() throws TranscoderException, IOException { + testNV("samples/tests/spec/color/colorProfiles.svg"); + } + @Test public void testColors() throws TranscoderException, IOException { test("samples/tests/spec/color/colors.svg"); diff --git a/samples/tests/spec/color/colorProfiles-ref.svg b/samples/tests/spec/color/colorProfiles-ref.svg new file mode 100644 index 000000000..13c905041 --- /dev/null +++ b/samples/tests/spec/color/colorProfiles-ref.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + SVG + HTML + XHTML + SVGZ + + diff --git a/samples/tests/spec/color/colorProfiles.svg b/samples/tests/spec/color/colorProfiles.svg index 6f1e80dc9..54697969b 100644 --- a/samples/tests/spec/color/colorProfiles.svg +++ b/samples/tests/spec/color/colorProfiles.svg @@ -1,5 +1,5 @@ - +