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 @@
+
+
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 @@
-