Skip to content

Commit c623d68

Browse files
SnipxiText-CI
authored andcommitted
SVG: improve support for path painting operators
Implement relative LineTo, MoveTo and CurveTo operators. Implement (x1 y1 x2 y2 x y)+ parameters for CurveTo, (x y)+ parameters for LineTo. Previously only one subset of parameters was supported. Deprecate several interface methods as supporting them imposes restrictions on the implementation. Reimplement several path painting operators in a more object-oriented way: move logic to class definitions DEVSIX-2442
1 parent b69be93 commit c623d68

23 files changed

+245
-474
lines changed

svg/src/main/java/com/itextpdf/svg/SvgConstants.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,15 +634,22 @@ public static final class Attributes extends CommonAttributeConstants {
634634
* Relative vertical LineTo Path operator.
635635
*/
636636
public static final String PATH_DATA_REL_LINE_TO_V = "v";
637+
637638
/**
638639
* Relative LineTo Path Operator.
639640
*/
640641
public static final String PATH_DATA_REL_LINE_TO = "l";
642+
641643
/**
642644
* MoveTo Path Operator.
643645
*/
644646
public static final String PATH_DATA_MOVE_TO = "M";
645647

648+
/**
649+
* Relative MoveTo Path Operator.
650+
*/
651+
public static final String PATH_DATA_REL_MOVE_TO = "m";
652+
646653
/**
647654
* Shorthand/smooth quadratic Bézier curveto.
648655
*/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.itextpdf.svg.exceptions;
2+
3+
public class SvgExceptionMessageConstant {
4+
5+
public static final String COORDINATE_ARRAY_LENGTH_MUST_BY_DIVISIBLE_BY_CURRENT_COORDINATES_ARRAY_LENGTH = "Array of current coordinates must have length that is divisible by the length of the array with current coordinates";
6+
public static final String CURVE_TO_EXPECTS_FOLLOWING_PARAMETERS_GOT_0 = "(x1 y1 x2 y2 x y)+ parameters are expected for curves. Got: {0}";
7+
public static final String QUADRATIC_CURVE_TO_EXPECTS_FOLLOWING_PARAMETERS_GOT_0 = "(x1 y1 x y)+ parameters are expected for quadratic curves. Got: {0}";
8+
public static final String MOVE_TO_EXPECTS_FOLLOWING_PARAMETERS_GOT_0 = "(x y)+ parameters are expected for moveTo operator. Got: {0}";
9+
public static final String LINE_TO_EXPECTS_FOLLOWING_PARAMETERS_GOT_0 = "(x y)+ parameters are expected for lineTo operator. Got: {0}";
10+
11+
}

svg/src/main/java/com/itextpdf/svg/renderers/impl/PathSvgNodeRenderer.java

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ This file is part of the iText (R) project.
4545
import com.itextpdf.io.util.MessageFormatUtil;
4646
import com.itextpdf.kernel.geom.Point;
4747
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
48-
import com.itextpdf.styledxmlparser.css.util.CssUtils;
4948
import com.itextpdf.svg.SvgConstants;
5049
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
5150
import com.itextpdf.svg.exceptions.SvgProcessingException;
@@ -55,10 +54,8 @@ This file is part of the iText (R) project.
5554
import com.itextpdf.svg.renderers.path.SvgPathShapeFactory;
5655
import com.itextpdf.svg.renderers.path.impl.ClosePath;
5756
import com.itextpdf.svg.renderers.path.impl.CurveTo;
58-
import com.itextpdf.svg.renderers.path.impl.HorizontalLineTo;
5957
import com.itextpdf.svg.renderers.path.impl.MoveTo;
6058
import com.itextpdf.svg.renderers.path.impl.SmoothSCurveTo;
61-
import com.itextpdf.svg.renderers.path.impl.VerticalLineTo;
6259
import com.itextpdf.svg.utils.SvgCssUtils;
6360
import com.itextpdf.svg.utils.SvgRegexUtils;
6461
import org.slf4j.Logger;
@@ -68,7 +65,6 @@ This file is part of the iText (R) project.
6865
import java.util.Arrays;
6966
import java.util.Collection;
7067
import java.util.List;
71-
import java.util.Map;
7268
import java.util.regex.Pattern;
7369

7470
/**
@@ -145,55 +141,32 @@ private String[] getShapeCoordinates(IPathShape shape, IPathShape previousShape,
145141
if (shape instanceof SmoothSCurveTo) {
146142
String[] startingControlPoint = new String[2];
147143
if (previousShape != null) {
148-
Map<String, String> coordinates = previousShape.getCoordinates();
149-
144+
Point previousEndPoint = previousShape.getEndingPoint();
150145
//if the previous command was a C or S use its last control point
151146
if (((previousShape instanceof CurveTo))) {
152-
float reflectedX = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X2)));
153-
float reflectedy = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y2)));
147+
Point lastControlPoint = ((CurveTo) previousShape).getLastControlPoint();
148+
float reflectedX = (float) (2 * previousEndPoint.getX() - lastControlPoint.getX());
149+
float reflectedY = (float) (2 * previousEndPoint.getY() - lastControlPoint.getY());
154150

155151
startingControlPoint[0] = SvgCssUtils.convertFloatToString(reflectedX);
156-
startingControlPoint[1] = SvgCssUtils.convertFloatToString(reflectedy);
152+
startingControlPoint[1] = SvgCssUtils.convertFloatToString(reflectedY);
157153
} else {
158-
startingControlPoint[0] = coordinates.get(SvgConstants.Attributes.X);
159-
startingControlPoint[1] = coordinates.get(SvgConstants.Attributes.Y);
154+
startingControlPoint[0] = SvgCssUtils.convertDoubleToString(previousEndPoint.getX());
155+
startingControlPoint[1] = SvgCssUtils.convertDoubleToString(previousEndPoint.getY());
160156
}
161157
} else {
162158
// TODO RND-951
163159
startingControlPoint[0] = pathProperties[1];
164160
startingControlPoint[1] = pathProperties[2];
165161
}
166162
shapeCoordinates = concatenate(startingControlPoint, operatorArgs);
167-
} else if (shape instanceof VerticalLineTo) {
168-
String currentX = SvgCssUtils.convertDoubleToString(currentPoint.x);
169-
String currentY = SvgCssUtils.convertDoubleToString(currentPoint.y);
170-
String[] yValues = concatenate(new String[]{currentY}, shape.isRelative() ? makeRelativeOperatorsAbsolute(operatorArgs, currentPoint.y) : operatorArgs);
171-
shapeCoordinates = concatenate(new String[]{currentX}, yValues);
172-
173-
} else if (shape instanceof HorizontalLineTo) {
174-
String currentX = SvgCssUtils.convertDoubleToString(currentPoint.x);
175-
String currentY = SvgCssUtils.convertDoubleToString(currentPoint.y);
176-
String[] xValues = concatenate(new String[]{currentX}, shape.isRelative() ? makeRelativeOperatorsAbsolute(operatorArgs, currentPoint.x) : operatorArgs);
177-
shapeCoordinates = concatenate(new String[]{currentY}, xValues);
178163
}
179164
if (shapeCoordinates == null) {
180165
shapeCoordinates = operatorArgs;
181166
}
182167
return shapeCoordinates;
183168
}
184169

185-
private String[] makeRelativeOperatorsAbsolute(String[] relativeOperators, double currentCoordinate) {
186-
String[] absoluteOperators = new String[relativeOperators.length];
187-
188-
for (int i = 0; i < relativeOperators.length; i++) {
189-
double relativeDouble = Double.parseDouble(relativeOperators[i]);
190-
relativeDouble += currentCoordinate;
191-
absoluteOperators[i] = SvgCssUtils.convertDoubleToString(relativeDouble);
192-
}
193-
194-
return absoluteOperators;
195-
}
196-
197170
/**
198171
* Processes an individual pathing operator and all of its arguments, converting into one or more
199172
* {@link IPathShape} objects.
@@ -213,21 +186,23 @@ private List<IPathShape> processPathOperator(String[] pathProperties, IPathShape
213186
IPathShape pathShape = SvgPathShapeFactory.createPathShape(pathProperties[0]);
214187
String[] shapeCoordinates = getShapeCoordinates(pathShape, previousShape, pathProperties);
215188
if (pathShape instanceof ClosePath) {
216-
pathShape = zOperator;
217-
if (pathShape == null) {
189+
if (previousShape != null) {
190+
pathShape = zOperator;
191+
} else {
218192
throw new SvgProcessingException(SvgLogMessageConstant.INVALID_CLOSEPATH_OPERATOR_USE);
219193
}
220194
} else if (pathShape instanceof MoveTo) {
221-
zOperator = new ClosePath();
195+
zOperator = new ClosePath(pathShape.isRelative());
222196
if (shapeCoordinates != null && shapeCoordinates.length != MOVETOARGUMENTNR) {
223197
LOGGER.warn(MessageFormatUtil.format(SvgLogMessageConstant.PATH_WRONG_NUMBER_OF_ARGUMENTS, pathProperties[0], shapeCoordinates.length, MOVETOARGUMENTNR, MOVETOARGUMENTNR));
224198
}
225-
zOperator.setCoordinates(shapeCoordinates);
199+
zOperator.setCoordinates(shapeCoordinates, currentPoint);
226200
}
227201

228202
if (pathShape != null) {
229203
if (shapeCoordinates != null) {
230-
pathShape.setCoordinates(shapeCoordinates);
204+
// Cast will be removed when the method is introduced in the interface
205+
pathShape.setCoordinates(shapeCoordinates, currentPoint);
231206
}
232207
currentPoint = pathShape.getEndingPoint(); // unsupported operators are ignored.
233208
shapes.add(pathShape);

svg/src/main/java/com/itextpdf/svg/renderers/path/IPathShape.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ This file is part of the iText (R) project.
4545
import com.itextpdf.kernel.geom.Point;
4646
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
4747

48-
import java.util.Map;
49-
5048
/**
5149
* Interface for IPathShape, which draws the Path-data's d element instructions.
5250
*/
@@ -59,28 +57,17 @@ public interface IPathShape {
5957
void draw(PdfCanvas canvas);
6058

6159
/**
62-
* Sets the map of attributes that this path instruction needs.
63-
*
64-
* @param properties maps key names to values.
65-
*/
66-
void setProperties(Map<String, String> properties);
67-
68-
/**
60+
* This method sets the coordinates for the path painting operator and does internal
61+
* preprocessing, if necessary
6962
* @param coordinates an array containing point values for path coordinates
70-
* This method Mapps point attributes to their respective values
71-
*/
72-
void setCoordinates(String[] coordinates);
73-
74-
/**
75-
* Returns the coordinates associated with this Shape.
76-
*
77-
* @return the coordinates associated with this Shape
63+
* @param startPoint the ending point of the previous operator, or, in broader terms,
64+
* the point that the coordinates should be absolutized against, for relative operators
7865
*/
79-
Map<String, String> getCoordinates();
66+
void setCoordinates(String[] coordinates, Point startPoint);
8067

8168
/**
8269
* Gets the ending point on the canvas after the path shape has been drawn
83-
* via the {@link IPathShape#draw(PdfCanvas)} method.
70+
* via the {@link IPathShape#draw(PdfCanvas)} method, in SVG space coordinates.
8471
*
8572
* @return The {@link Point} representing the final point in the drawn path.
8673
* If the point does not exist or does not change {@code null} may be returned.

svg/src/main/java/com/itextpdf/svg/renderers/path/impl/AbstractPathShape.java

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.svg.renderers.path.impl;
4444

45+
import com.itextpdf.kernel.geom.Point;
4546
import com.itextpdf.styledxmlparser.css.util.CssUtils;
46-
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
47-
import com.itextpdf.svg.exceptions.SvgProcessingException;
4847
import com.itextpdf.svg.renderers.path.IPathShape;
4948

5049
import java.util.Map;
@@ -64,55 +63,13 @@ public abstract class AbstractPathShape implements IPathShape {
6463
*/
6564
protected boolean relative;
6665

67-
/**
68-
* Get a coordinate based on a key value.
69-
*
70-
* @param attributes map containing the attributes of the shape
71-
* @param key key of the coordinate
72-
* @return coordinate associated with the key
73-
*/
74-
public float getCoordinate(Map<String, String> attributes, String key) {
75-
return CssUtils.parseAbsoluteLength(getCoordinateAttributeStringSafe(attributes, key));
76-
}
77-
78-
/**
79-
* Get the coordinate based on a key value in the SVG unit-space.
80-
*
81-
* @param attributes map containing the attributes of the shape
82-
* @param key key of the coordinate
83-
* @return coordinate in SVG units associated with the key
84-
*/
85-
public float getSvgCoordinate(Map<String, String> attributes, String key) {
86-
return Float.valueOf(getCoordinateAttributeStringSafe(attributes, key));
87-
}
88-
89-
@Override
90-
public void setProperties(Map<String, String> properties) {
91-
this.properties = properties;
92-
}
93-
94-
@Override
95-
public Map<String, String> getCoordinates() {
96-
return properties;
97-
}
98-
9966
@Override
10067
public boolean isRelative() {
10168
return this.relative;
10269
}
10370

104-
private static String getCoordinateAttributeStringSafe(Map<String, String> attributes, String key) {
105-
String value;
106-
107-
if (attributes == null) {
108-
throw new SvgProcessingException(SvgLogMessageConstant.ATTRIBUTES_NULL);
109-
}
110-
111-
value = attributes.get(key);
112-
113-
if (value == null || value.isEmpty()) {
114-
throw new SvgProcessingException(SvgLogMessageConstant.COORDINATE_VALUE_ABSENT);
115-
}
116-
return value;
71+
protected Point createPoint(String coordX, String coordY) {
72+
return new Point((float)CssUtils.parseFloat(coordX), (float)CssUtils.parseFloat(coordY));
11773
}
74+
11875
}

svg/src/main/java/com/itextpdf/svg/renderers/path/impl/ClosePath.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,17 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.svg.renderers.path.impl;
4444

45-
import com.itextpdf.kernel.geom.Point;
46-
import com.itextpdf.svg.SvgConstants;
47-
4845
/***
4946
* Implements closePath(Z) attribute of SVG's path element
5047
* */
5148
public class ClosePath extends LineTo {
5249

53-
@Override
54-
public Point getEndingPoint() {
55-
float x = getSvgCoordinate(properties, SvgConstants.Attributes.X);
56-
float y = getSvgCoordinate(properties, SvgConstants.Attributes.Y);
57-
return new Point(x,y);
50+
public ClosePath() {
51+
this(false);
52+
}
53+
54+
public ClosePath(boolean relative) {
55+
super(relative);
5856
}
5957

6058
}

svg/src/main/java/com/itextpdf/svg/renderers/path/impl/CurveTo.java

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,48 +42,75 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.svg.renderers.path.impl;
4444

45+
import com.itextpdf.io.util.MessageFormatUtil;
4546
import com.itextpdf.kernel.geom.Point;
4647
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
47-
import com.itextpdf.svg.SvgConstants;
48+
import com.itextpdf.styledxmlparser.css.util.CssUtils;
49+
import com.itextpdf.svg.exceptions.SvgExceptionMessageConstant;
50+
import com.itextpdf.svg.utils.SvgCoordinateUtils;
4851

49-
import java.util.HashMap;
50-
import java.util.Map;
52+
import java.util.Arrays;
5153

5254
/***
5355
* Implements curveTo(L) attribute of SVG's path element
5456
* */
5557
public class CurveTo extends AbstractPathShape {
5658

59+
// Original coordinates from path instruction, according to the (x1 y1 x2 y2 x y)+ spec
60+
private String[][] coordinates;
61+
62+
public CurveTo() {
63+
this(false);
64+
}
65+
66+
public CurveTo(boolean relative) {
67+
this.relative = relative;
68+
}
69+
5770
@Override
5871
public void draw(PdfCanvas canvas) {
59-
canvas.curveTo(
60-
getCoordinate(properties, SvgConstants.Attributes.X1),
61-
getCoordinate(properties, SvgConstants.Attributes.Y1),
62-
getCoordinate(properties, SvgConstants.Attributes.X2),
63-
getCoordinate(properties, SvgConstants.Attributes.Y2),
64-
getCoordinate(properties, SvgConstants.Attributes.X),
65-
getCoordinate(properties, SvgConstants.Attributes.Y)
66-
);
72+
for (int i = 0; i < coordinates.length; i++) {
73+
float x1 = CssUtils.parseAbsoluteLength(coordinates[i][0]);
74+
float y1 = CssUtils.parseAbsoluteLength(coordinates[i][1]);
75+
float x2 = CssUtils.parseAbsoluteLength(coordinates[i][2]);
76+
float y2 = CssUtils.parseAbsoluteLength(coordinates[i][3]);
77+
float x = CssUtils.parseAbsoluteLength(coordinates[i][4]);
78+
float y = CssUtils.parseAbsoluteLength(coordinates[i][5]);
79+
canvas.curveTo(x1, y1, x2, y2, x, y);
80+
}
6781
}
6882

6983
@Override
70-
public void setCoordinates(String[] coordinates) {
71-
72-
Map<String, String> map = new HashMap<String, String>();
73-
map.put("x1", coordinates.length > 0 && !coordinates[0].isEmpty() ? coordinates[0] : "0");
74-
map.put("y1", coordinates.length > 1 && !coordinates[1].isEmpty() ? coordinates[1] : "0");
75-
map.put("x2", coordinates.length > 2 && !coordinates[2].isEmpty() ? coordinates[2] : "0");
76-
map.put("y2", coordinates.length > 3 && !coordinates[3].isEmpty() ? coordinates[3] : "0");
77-
map.put("x", coordinates.length > 4 && !coordinates[4].isEmpty() ? coordinates[4] : "0");
78-
map.put("y", coordinates.length > 5 && !coordinates[5].isEmpty() ? coordinates[5] : "0");
79-
setProperties(map);
84+
public void setCoordinates(String[] coordinates, Point startPoint) {
85+
if (coordinates.length == 0 || coordinates.length % 6 != 0) {
86+
throw new IllegalArgumentException(MessageFormatUtil.format(SvgExceptionMessageConstant.CURVE_TO_EXPECTS_FOLLOWING_PARAMETERS_GOT_0, Arrays.toString(coordinates)));
87+
}
88+
this.coordinates = new String[coordinates.length / 6][];
89+
double[] initialPoint = new double[] {startPoint.getX(), startPoint.getY()};
90+
for (int i = 0; i < coordinates.length; i += 6) {
91+
String[] curCoordinates = new String[]{coordinates[i], coordinates[i + 1], coordinates[i + 2],
92+
coordinates[i + 3], coordinates[i + 4], coordinates[i + 5]};
93+
if (isRelative()) {
94+
curCoordinates = SvgCoordinateUtils.makeRelativeOperatorCoordinatesAbsolute(curCoordinates, initialPoint);
95+
initialPoint[0] = (float)CssUtils.parseFloat(curCoordinates[4]);
96+
initialPoint[1] = (float)CssUtils.parseFloat(curCoordinates[5]);
97+
}
98+
this.coordinates[i / 6] = curCoordinates;
99+
}
100+
}
101+
102+
/**
103+
* Returns coordinates of the last control point (the one closer to the ending point)
104+
* in the series of Bezier curves (possibly, one curve), in SVG space coordinates
105+
* @return coordinates of the last control points in SVG space coordinates
106+
*/
107+
public Point getLastControlPoint() {
108+
return createPoint(coordinates[coordinates.length - 1][2], coordinates[coordinates.length - 1][3]);
80109
}
81110

82111
@Override
83112
public Point getEndingPoint() {
84-
float x = getSvgCoordinate(properties, SvgConstants.Attributes.X);
85-
float y = getSvgCoordinate(properties, SvgConstants.Attributes.Y);
86-
return new Point(x,y);
113+
return createPoint(coordinates[coordinates.length - 1][4], coordinates[coordinates.length - 1][5]);
87114
}
88115

89116
}

0 commit comments

Comments
 (0)