diff --git a/svg/src/main/java/com/itextpdf/svg/css/SvgStrokeParameterConverter.java b/svg/src/main/java/com/itextpdf/svg/css/SvgStrokeParameterConverter.java new file mode 100644 index 0000000000..57fc1d05e5 --- /dev/null +++ b/svg/src/main/java/com/itextpdf/svg/css/SvgStrokeParameterConverter.java @@ -0,0 +1,94 @@ +package com.itextpdf.svg.css; + +import com.itextpdf.styledxmlparser.css.util.CssDimensionParsingUtils; +import com.itextpdf.styledxmlparser.css.util.CssTypesValidationUtils; +import com.itextpdf.svg.SvgConstants; +import com.itextpdf.svg.logs.SvgLogMessageConstant; +import com.itextpdf.svg.utils.SvgCssUtils; + +import java.util.Arrays; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class converts stroke-related SVG parameters and attributes into those from PDF specification + */ +public class SvgStrokeParameterConverter { + + private SvgStrokeParameterConverter() { + } + + private static Logger LOGGER = LoggerFactory.getLogger(SvgStrokeParameterConverter.class); + + public static PdfLineDashParameters convertStrokeDashArray(String strokeDashArray) { + if (!SvgConstants.Values.NONE.equalsIgnoreCase(strokeDashArray)) { + List dashArray = SvgCssUtils.splitValueList(strokeDashArray); + + for (String dashArrayItem : dashArray) { + if (CssTypesValidationUtils.isPercentageValue(dashArrayItem)) { + LOGGER.error(SvgLogMessageConstant.PERCENTAGE_VALUES_IN_STROKE_DASHARRAY_ARE_NOT_SUPPORTED); + return null; + } + } + + if (dashArray.size() > 0) { + if (dashArray.size() % 2 == 1) { + // If an odd number of values is provided, then the list of values is repeated to yield an even + // number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2. + dashArray.addAll(dashArray); + } + float[] dashArrayFloat = new float[dashArray.size()]; + for (int i = 0; i < dashArray.size(); i++) { + dashArrayFloat[i] = CssDimensionParsingUtils.parseAbsoluteLength(dashArray.get(i)); + } + return new PdfLineDashParameters(dashArrayFloat, 0); + } + } + return null; + } + + public static class PdfLineDashParameters { + private float[] lengths; + private float phase; + + public PdfLineDashParameters(float[] lengths, float phase) { + this.lengths = lengths; + this.phase = phase; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PdfLineDashParameters that = (PdfLineDashParameters) o; + + if (Float.compare(that.phase, phase) != 0) { + return false; + } + return Arrays.equals(lengths, that.lengths); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(lengths); + result = 31 * result + (phase != +0.0f ? Float.floatToIntBits(phase) : 0); + return result; + } + + + public float[] getLengths() { + return lengths; + } + + public float getPhase() { + return phase; + } + } + +} diff --git a/svg/src/main/java/com/itextpdf/svg/logs/SvgLogMessageConstant.java b/svg/src/main/java/com/itextpdf/svg/logs/SvgLogMessageConstant.java index c8da4d187e..c56d1ca1ad 100644 --- a/svg/src/main/java/com/itextpdf/svg/logs/SvgLogMessageConstant.java +++ b/svg/src/main/java/com/itextpdf/svg/logs/SvgLogMessageConstant.java @@ -81,6 +81,9 @@ public final class SvgLogMessageConstant { public static final String PATTERN_WIDTH_OR_HEIGHT_IS_NEGATIVE = "Pattern width or height is negative value. This pattern will not be rendered."; + public static final String PERCENTAGE_VALUES_IN_STROKE_DASHARRAY_ARE_NOT_SUPPORTED = + "Percentage values in 'stroke-dasharray' attribute are not supported. Attribute will be ignored completely"; + public static final String MISSING_WIDTH = "Top Svg tag has no defined width attribute and viewbox width is not present, so browser default of 300px " + "is used"; diff --git a/svg/src/main/java/com/itextpdf/svg/renderers/impl/AbstractSvgNodeRenderer.java b/svg/src/main/java/com/itextpdf/svg/renderers/impl/AbstractSvgNodeRenderer.java index 746454defe..09426ffa1b 100644 --- a/svg/src/main/java/com/itextpdf/svg/renderers/impl/AbstractSvgNodeRenderer.java +++ b/svg/src/main/java/com/itextpdf/svg/renderers/impl/AbstractSvgNodeRenderer.java @@ -45,7 +45,6 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.colors.Color; import com.itextpdf.kernel.colors.ColorConstants; import com.itextpdf.kernel.colors.DeviceRgb; -import com.itextpdf.kernel.colors.WebColors; import com.itextpdf.kernel.geom.AffineTransform; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; @@ -62,6 +61,8 @@ This file is part of the iText (R) project. import com.itextpdf.styledxmlparser.css.validate.CssDeclarationValidationMaster; import com.itextpdf.svg.MarkerVertexType; import com.itextpdf.svg.SvgConstants; +import com.itextpdf.svg.SvgConstants.Attributes; +import com.itextpdf.svg.css.SvgStrokeParameterConverter; import com.itextpdf.svg.css.impl.SvgNodeRendererInheritanceResolver; import com.itextpdf.svg.renderers.IMarkerCapable; import com.itextpdf.svg.renderers.ISvgNodeRenderer; @@ -320,9 +321,9 @@ void preDraw(SvgDrawContext context) { PdfExtGState opacityGraphicsState = new PdfExtGState(); if (!partOfClipPath) { - float generalOpacity = getOpacity(); // fill { + float generalOpacity = getOpacity(); String fillRawValue = getAttributeOrDefault(SvgConstants.Attributes.FILL, "black"); this.doFill = !SvgConstants.Values.NONE.equalsIgnoreCase(fillRawValue); @@ -349,47 +350,9 @@ void preDraw(SvgDrawContext context) { currentCanvas.setFillColor(fillColor); } } - // stroke - { - String strokeRawValue = getAttributeOrDefault(SvgConstants.Attributes.STROKE, - SvgConstants.Values.NONE); - - if (!SvgConstants.Values.NONE.equalsIgnoreCase(strokeRawValue)) { - String strokeWidthRawValue = getAttribute(SvgConstants.Attributes.STROKE_WIDTH); - - // 1 px = 0,75 pt - float strokeWidth = 0.75f; - - if (strokeWidthRawValue != null) { - strokeWidth = CssDimensionParsingUtils.parseAbsoluteLength(strokeWidthRawValue); - } - float strokeOpacity = getOpacityByAttributeName(SvgConstants.Attributes.STROKE_OPACITY, - generalOpacity); - - Color strokeColor = null; - TransparentColor transparentColor = getColorFromAttributeValue( - context, strokeRawValue, (float) ((double) strokeWidth / 2.0), strokeOpacity); - if (transparentColor != null) { - strokeColor = transparentColor.getColor(); - strokeOpacity = transparentColor.getOpacity(); - } + applyStrokeProperties(context, currentCanvas, opacityGraphicsState); - if (!CssUtils.compareFloats(strokeOpacity, 1f)) { - opacityGraphicsState.setStrokeOpacity(strokeOpacity); - } - - // as default value for stroke is 'none' we should not set - // it in case when value obtaining fails - if (strokeColor != null) { - currentCanvas.setStrokeColor(strokeColor); - } - - currentCanvas.setLineWidth(strokeWidth); - - doStroke = true; - } - } // opacity { if (!opacityGraphicsState.getPdfObject().isEmpty()) { @@ -507,4 +470,52 @@ private float getOpacity() { return result; } + + private void applyStrokeProperties(SvgDrawContext context, PdfCanvas currentCanvas, PdfExtGState opacityGraphicsState) { + String strokeRawValue = getAttributeOrDefault(SvgConstants.Attributes.STROKE, + SvgConstants.Values.NONE); + if (!SvgConstants.Values.NONE.equalsIgnoreCase(strokeRawValue)) { + String strokeWidthRawValue = getAttribute(SvgConstants.Attributes.STROKE_WIDTH); + + // 1 px = 0,75 pt + float strokeWidth = 0.75f; + + if (strokeWidthRawValue != null) { + strokeWidth = CssDimensionParsingUtils.parseAbsoluteLength(strokeWidthRawValue); + } + + float generalOpacity = getOpacity(); + float strokeOpacity = getOpacityByAttributeName(SvgConstants.Attributes.STROKE_OPACITY, + generalOpacity); + + Color strokeColor = null; + TransparentColor transparentColor = getColorFromAttributeValue( + context, strokeRawValue, (float) ((double) strokeWidth / 2.0), strokeOpacity); + if (transparentColor != null) { + strokeColor = transparentColor.getColor(); + strokeOpacity = transparentColor.getOpacity(); + } + + if (!CssUtils.compareFloats(strokeOpacity, 1f)) { + opacityGraphicsState.setStrokeOpacity(strokeOpacity); + } + + String strokeDashArrayRawValue = getAttribute(Attributes.STROKE_DASHARRAY); + SvgStrokeParameterConverter.PdfLineDashParameters lineDashParameters = + SvgStrokeParameterConverter.convertStrokeDashArray(strokeDashArrayRawValue); + if (lineDashParameters != null) { + currentCanvas.setLineDash(lineDashParameters.getLengths(), lineDashParameters.getPhase()); + } + + // as default value for stroke is 'none' we should not set + // it in case when value obtaining fails + if (strokeColor != null) { + currentCanvas.setStrokeColor(strokeColor); + } + + currentCanvas.setLineWidth(strokeWidth); + + doStroke = true; + } + } } diff --git a/svg/src/test/java/com/itextpdf/svg/css/SvgStrokeParameterConverterUnitTest.java b/svg/src/test/java/com/itextpdf/svg/css/SvgStrokeParameterConverterUnitTest.java new file mode 100644 index 0000000000..c39b963a55 --- /dev/null +++ b/svg/src/test/java/com/itextpdf/svg/css/SvgStrokeParameterConverterUnitTest.java @@ -0,0 +1,36 @@ +package com.itextpdf.svg.css; + +import com.itextpdf.svg.css.SvgStrokeParameterConverter.PdfLineDashParameters; +import com.itextpdf.svg.logs.SvgLogMessageConstant; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.LogMessage; +import com.itextpdf.test.annotations.LogMessages; + +import org.junit.Assert; +import org.junit.Test; + +public class SvgStrokeParameterConverterUnitTest extends ExtendedITextTest { + + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = + SvgLogMessageConstant.PERCENTAGE_VALUES_IN_STROKE_DASHARRAY_ARE_NOT_SUPPORTED)}) + public void testStrokeDashArrayPercentsAreNotSupported() { + Assert.assertNull(SvgStrokeParameterConverter.convertStrokeDashArray("5,3%")); + } + + @Test + public void testStrokeDashArrayOddNumberOfValues() { + PdfLineDashParameters result = SvgStrokeParameterConverter.convertStrokeDashArray("5pt"); + Assert.assertNotNull(result); + Assert.assertEquals(0, result.getPhase(), 0); + Assert.assertArrayEquals(new float[] {5, 5}, result.getLengths(), 1e-5f); + } + + @Test + public void testEmptyStrokeDashArray() { + PdfLineDashParameters result = SvgStrokeParameterConverter.convertStrokeDashArray(""); + Assert.assertNull(result); + } + +} diff --git a/svg/src/test/java/com/itextpdf/svg/renderers/StrokeTest.java b/svg/src/test/java/com/itextpdf/svg/renderers/StrokeTest.java index 5e16f9d900..aa699aae59 100644 --- a/svg/src/test/java/com/itextpdf/svg/renderers/StrokeTest.java +++ b/svg/src/test/java/com/itextpdf/svg/renderers/StrokeTest.java @@ -80,6 +80,11 @@ public void noLineStrokeWidthTest() throws IOException, InterruptedException { convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "noLineStrokeWidth"); } + @Test + public void strokeWithDashesTest() throws IOException, InterruptedException { + convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "strokeWithDashes"); + } + @Test //TODO: update cmp-file after DEVSIX-2258 public void advancedStrokeTest() throws IOException, InterruptedException { diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromFileTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromFileTest.pdf index a8a1031784..5fa8bb839d 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromFileTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromFileTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromStringTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromStringTest.pdf index 5e8414ca45..0efc88f2e7 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromStringTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgBarChartFromStringTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromFileTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromFileTest.pdf index 509fc38472..e302cb8227 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromFileTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromFileTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromStringTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromStringTest.pdf index 7a4db32aea..ca6fa935c3 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromStringTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgLineChartFromStringTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromFileTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromFileTest.pdf index d9cdbed7ee..a66611da85 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromFileTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromFileTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromStringTest.pdf b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromStringTest.pdf index c347ee51fa..b43ecb453a 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromStringTest.pdf and b/svg/src/test/resources/com/itextpdf/svg/JFreeSvgTest/cmp_usingJFreeSvgXYChartFromStringTest.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeAdvanced.pdf b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeAdvanced.pdf index c383394778..304c8828e3 100644 Binary files a/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeAdvanced.pdf and b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeAdvanced.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeWithDashes.pdf b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeWithDashes.pdf new file mode 100644 index 0000000000..5b8530e1a2 Binary files /dev/null and b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/cmp_strokeWithDashes.pdf differ diff --git a/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/strokeWithDashes.svg b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/strokeWithDashes.svg new file mode 100644 index 0000000000..babad53183 --- /dev/null +++ b/svg/src/test/resources/com/itextpdf/svg/renderers/impl/StrokeTest/strokeWithDashes.svg @@ -0,0 +1,3 @@ + + +