Skip to content

Commit 13ec451

Browse files
committed
Implement V and H operators for Paths. Also refactored PathSvgNodeRenderer. RND-909 RND-1104
1 parent 1959365 commit 13ec451

37 files changed

+533
-97
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,9 @@ public static final class Attributes extends CommonAttributeConstants {
536536
public static final String RY = "ry";
537537

538538
/**
539-
* Colse Path Operator.
539+
* Close Path Operator.
540540
*/
541-
public static final String PATH_DATA_CLOSE_PATH = "z";
541+
public static final String PATH_DATA_CLOSE_PATH = "Z";
542542

543543
/**
544544
* CurveTo Path Operator.

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

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

45+
import com.itextpdf.kernel.geom.Point;
4546
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
4647
import com.itextpdf.styledxmlparser.css.util.CssUtils;
4748
import com.itextpdf.svg.SvgConstants;
@@ -51,10 +52,12 @@ This file is part of the iText (R) project.
5152
import com.itextpdf.svg.renderers.SvgDrawContext;
5253
import com.itextpdf.svg.renderers.path.SvgPathShapeFactory;
5354
import com.itextpdf.svg.renderers.path.IPathShape;
54-
import com.itextpdf.svg.renderers.path.impl.CurveTo;
55-
import com.itextpdf.svg.renderers.path.impl.LineTo;
55+
import com.itextpdf.svg.renderers.path.impl.ClosePath;
5656
import com.itextpdf.svg.renderers.path.impl.MoveTo;
57+
import com.itextpdf.svg.renderers.path.impl.CurveTo;
5758
import com.itextpdf.svg.renderers.path.impl.SmoothSCurveTo;
59+
import com.itextpdf.svg.renderers.path.impl.HorizontalLineTo;
60+
import com.itextpdf.svg.renderers.path.impl.VerticalLineTo;
5861
import com.itextpdf.svg.utils.SvgCssUtils;
5962

6063
import java.util.ArrayList;
@@ -87,7 +90,18 @@ public class PathSvgNodeRenderer extends AbstractSvgNodeRenderer {
8790
*/
8891
private final String SPLIT_REGEX = "(?=[\\p{L}])";
8992

90-
private LineTo zOperator = null;
93+
94+
/**
95+
* The {@link Point} representing the current point in the path to be used for relative pathing operations.
96+
* The original value is {@code null}, and must be set via a {@link MoveTo} operation before it may be referenced.
97+
*/
98+
private Point currentPoint = null;
99+
100+
/**
101+
* The {@link ClosePath} shape keeping track of the initial point set by a {@link MoveTo} operation.
102+
* The original value is {@code null}, and must be set via a {@link MoveTo} operation before it may drawn.
103+
*/
104+
private ClosePath zOperator = null;
91105

92106
@Override
93107
public void doDraw(SvgDrawContext context) {
@@ -98,60 +112,126 @@ public void doDraw(SvgDrawContext context) {
98112
}
99113
}
100114

115+
@Override
116+
public ISvgNodeRenderer createDeepCopy() {
117+
PathSvgNodeRenderer copy = new PathSvgNodeRenderer();
118+
deepCopyAttributesAndStyles(copy);
119+
return copy;
120+
}
121+
122+
/**
123+
* Gets the coordinates that shall be passed to {@link IPathShape#setCoordinates(String[])} for the current shape.
124+
*
125+
* @param shape The current shape.
126+
* @param previousShape The previous shape which can affect the coordinates of the current shape.
127+
* @param pathProperties The operator and all arguments as a {@link String[]}
128+
* @return a {@link String[]} of coordinates that shall be passed to {@link IPathShape#setCoordinates(String[])}
129+
*/
130+
private String[] getShapeCoordinates(IPathShape shape, IPathShape previousShape, String[] pathProperties) {
131+
if (shape instanceof ClosePath) {
132+
return null;
133+
}
134+
String[] shapeCoordinates = null;
135+
String[] operatorArgs = Arrays.copyOfRange(pathProperties, 1, pathProperties.length);
136+
if (shape instanceof SmoothSCurveTo) {
137+
String[] startingControlPoint = new String[2];
138+
if (previousShape != null) {
139+
Map<String, String> coordinates = previousShape.getCoordinates();
140+
141+
//if the previous command was a C or S use its last control point
142+
if (((previousShape instanceof CurveTo) || (previousShape instanceof SmoothSCurveTo))) {
143+
float reflectedX = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X2)));
144+
float reflectedy = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y2)));
145+
146+
startingControlPoint[0] = SvgCssUtils.convertFloatToString(reflectedX);
147+
startingControlPoint[1] = SvgCssUtils.convertFloatToString(reflectedy);
148+
} else {
149+
startingControlPoint[0] = coordinates.get(SvgConstants.Attributes.X);
150+
startingControlPoint[1] = coordinates.get(SvgConstants.Attributes.Y);
151+
}
152+
} else {
153+
// TODO RND-951
154+
startingControlPoint[0] = pathProperties[1];
155+
startingControlPoint[1] = pathProperties[2];
156+
}
157+
shapeCoordinates = concatenate(startingControlPoint, operatorArgs);
158+
} else if (shape instanceof VerticalLineTo) {
159+
String currentX = String.valueOf(currentPoint.x);
160+
String currentY = String.valueOf(currentPoint.y);
161+
String[] yValues = concatenate(new String[]{currentY}, operatorArgs);
162+
shapeCoordinates = concatenate(new String[]{currentX}, yValues);
163+
164+
} else if (shape instanceof HorizontalLineTo) {
165+
String currentX = String.valueOf(currentPoint.x);
166+
String currentY = String.valueOf(currentPoint.y);
167+
String[] xValues = concatenate(new String[]{currentX}, operatorArgs);
168+
shapeCoordinates = concatenate(new String[]{currentY}, xValues);
169+
}
170+
if (shapeCoordinates == null) {
171+
shapeCoordinates = operatorArgs;
172+
}
173+
return shapeCoordinates;
174+
}
175+
176+
177+
/**
178+
* Processes an individual pathing operator and all of its arguments, converting into one or more
179+
* {@link IPathShape} objects.
180+
*
181+
* @param pathProperties The property operator and all arguments as a {@link String[]}
182+
* @param previousShape The previous shape which can affect the positioning of the current shape. If no previous
183+
* shape exists {@code null} is passed.
184+
* @return a {@link List} of each {@link IPathShape} that should be drawn to represent the operator.
185+
*/
186+
private List<IPathShape> processPathOperator(String[] pathProperties, IPathShape previousShape) {
187+
List<IPathShape> shapes = new ArrayList<>();
188+
if (pathProperties.length == 0 || pathProperties[0].equals(SEPARATOR)) {
189+
return shapes;
190+
}
191+
//Implements (absolute) command value only
192+
//TODO implement relative values e. C(absolute), c(relative)
193+
IPathShape pathShape = SvgPathShapeFactory.createPathShape(pathProperties[0]);
194+
String[] shapeCoordinates = getShapeCoordinates(pathShape, previousShape, pathProperties);
195+
if (pathShape instanceof ClosePath) {
196+
pathShape = zOperator;
197+
if (pathShape == null) {
198+
throw new SvgProcessingException(SvgLogMessageConstant.INVALID_CLOSEPATH_OPERATOR_USE);
199+
}
200+
} else if (pathShape instanceof MoveTo) {
201+
zOperator = new ClosePath();
202+
zOperator.setCoordinates(shapeCoordinates);
203+
}
204+
205+
Point endingPoint = null;
206+
if (pathShape != null) {
207+
if (shapeCoordinates != null) {
208+
pathShape.setCoordinates(shapeCoordinates);
209+
}
210+
endingPoint = pathShape.getEndingPoint();
211+
shapes.add(pathShape);
212+
}
213+
currentPoint = endingPoint;
214+
return shapes;
215+
}
216+
217+
218+
/**
219+
* Processes the {@link SvgConstants.Attributes.D} {@link PathSvgNodeRenderer#attributesAndStyles} and converts them
220+
* into one or more {@link IPathShape} objects to be drawn on the canvas.
221+
* <p>
222+
* Each individual operator is passed to {@link PathSvgNodeRenderer#processPathOperator(String[], IPathShape)} to be processed individually.
223+
*
224+
* @return a {@link Collection} of each {@link IPathShape} that should be drawn to represent the path.
225+
*/
101226
private Collection<IPathShape> getShapes() {
102227
Collection<String> parsedResults = parsePropertiesAndStyles();
103228
List<IPathShape> shapes = new ArrayList<>();
104229

105230
for (String parsedResult : parsedResults) {
106-
//split att to {M , 100, 100}
107231
String[] pathProperties = parsedResult.split(SPACE_CHAR);
108-
if (pathProperties.length > 0 && !pathProperties[0].equals(SEPARATOR)) {
109-
if (pathProperties[0].equalsIgnoreCase(SvgConstants.Attributes.PATH_DATA_CLOSE_PATH)) {
110-
if (zOperator != null) {
111-
shapes.add(zOperator);
112-
} else {
113-
throw new SvgProcessingException(SvgLogMessageConstant.INVALID_CLOSEPATH_OPERATOR_USE);
114-
}
115-
} else {
116-
String[] startingControlPoint = new String[2];
117-
118-
//Implements (absolute) command value only
119-
//TODO implement relative values e. C(absolute), c(relative)
120-
IPathShape pathShape = SvgPathShapeFactory.createPathShape(pathProperties[0].toUpperCase());
121-
if (pathShape instanceof MoveTo) {
122-
zOperator = new LineTo();
123-
zOperator.setCoordinates(Arrays.copyOfRange(pathProperties, 1, pathProperties.length));
124-
}
125-
if (pathShape instanceof SmoothSCurveTo) {
126-
IPathShape previousCommand = !shapes.isEmpty() ? shapes.get(shapes.size() - 1) : null;
127-
if (previousCommand != null) {
128-
Map<String, String> coordinates = previousCommand.getCoordinates();
129-
130-
/*if the previous command was a C or S use its last control point*/
131-
if (((previousCommand instanceof CurveTo) || (previousCommand instanceof SmoothSCurveTo))) {
132-
float reflectedX = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X2)));
133-
float reflectedy = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y2)));
134-
135-
startingControlPoint[0] = SvgCssUtils.convertFloatToString(reflectedX);
136-
startingControlPoint[1] = SvgCssUtils.convertFloatToString(reflectedy);
137-
} else {
138-
startingControlPoint[0] = coordinates.get(SvgConstants.Attributes.X);
139-
startingControlPoint[1] = coordinates.get(SvgConstants.Attributes.Y);
140-
}
141-
} else {
142-
// TODO RND-951
143-
startingControlPoint[0] = pathProperties[1];
144-
startingControlPoint[1] = pathProperties[2];
145-
}
146-
String[] properties = concatenate(startingControlPoint, Arrays.copyOfRange(pathProperties, 1, pathProperties.length));
147-
pathShape.setCoordinates(properties);
148-
shapes.add(pathShape);
149-
} else if (pathShape != null) {
150-
pathShape.setCoordinates(Arrays.copyOfRange(pathProperties, 1, pathProperties.length));
151-
shapes.add(pathShape);
152-
}
153-
}
154-
}
232+
IPathShape previousShape = shapes.size() == 0 ? null : shapes.get(shapes.size() - 1);
233+
List<IPathShape> operatorShapes = processPathOperator(pathProperties, previousShape);
234+
shapes.addAll(operatorShapes);
155235
}
156236
return shapes;
157237
}
@@ -191,11 +271,5 @@ private Collection<String> parsePropertiesAndStyles() {
191271
return resultList;
192272
}
193273

194-
@Override
195-
public ISvgNodeRenderer createDeepCopy() {
196-
PathSvgNodeRenderer copy = new PathSvgNodeRenderer();
197-
deepCopyAttributesAndStyles(copy);
198-
return copy;
199-
}
200274

201275
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,31 +42,44 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.svg.renderers.path;
4444

45+
import com.itextpdf.kernel.geom.Point;
4546
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
4647
import com.itextpdf.svg.renderers.SvgDrawContext;
4748

4849
import java.util.Map;
50+
4951
/**
5052
* Interface for IPathShape, which draws the Path-data's d element instructions.
5153
*/
5254
public interface IPathShape {
5355
/**
5456
* Draws this instruction to a canvas object.
57+
*
5558
* @param canvas to which this instruction is drawn
5659
*/
5760
void draw(PdfCanvas canvas);
5861

5962
/**
6063
* Sets the map of attributes that this path instruction needs.
64+
*
6165
* @param properties maps key names to values.
6266
*/
6367
void setProperties(Map<String, String> properties);
6468

6569
/**
6670
* @param coordinates an array containing point values for path coordinates
67-
* This method Mapps point attributes to their respective values
71+
* This method Mapps point attributes to their respective values
6872
*/
6973
void setCoordinates(String[] coordinates);
74+
7075
Map<String, String> getCoordinates();
7176

77+
/**
78+
* Gets the ending point on the canvas after the path shape has been drawn
79+
* via the {@link IPathShape#draw(PdfCanvas)} method.
80+
*
81+
* @return The {@link Point} representing the final point in the drawn path.
82+
* If the point does not exist or does not change {@code null} may be returned.
83+
*/
84+
Point getEndingPoint();
7285
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public abstract class AbstractPathShape implements IPathShape {
5959
*/
6060
protected Map<String, String> properties;
6161

62+
6263
/**
6364
* Get a coordinate based on a key value.
6465
*
@@ -67,6 +68,19 @@ public abstract class AbstractPathShape implements IPathShape {
6768
* @return coordinate associated with the key
6869
*/
6970
public float getCoordinate(Map<String, String> attributes, String key) {
71+
float svgCoordinate = getSvgCoordinate(attributes, key);
72+
return CssUtils.parseAbsoluteLength(String.valueOf(svgCoordinate));
73+
}
74+
75+
/**
76+
* Get the coordinate based on a key value in the SVG unit-space.
77+
*
78+
* @param attributes map containing the attributes of the shape
79+
* @param key key of the coordinate
80+
* @return coordinate in SVG units associated with the key
81+
*/
82+
public float getSvgCoordinate(Map<String, String> attributes, String key) {
83+
7084
String value;
7185

7286
if (attributes == null) {
@@ -76,7 +90,7 @@ public float getCoordinate(Map<String, String> attributes, String key) {
7690
value = attributes.get(key);
7791

7892
if (value != null && !value.isEmpty()) {
79-
return CssUtils.parseAbsoluteLength(value);
93+
return Float.valueOf(value);
8094
} else {
8195
throw new SvgProcessingException(SvgLogMessageConstant.COORDINATE_VALUE_ABSENT);
8296
}
@@ -91,4 +105,5 @@ public void setProperties(Map<String, String> properties) {
91105
public Map<String, String> getCoordinates() {
92106
return properties;
93107
}
108+
94109
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.itextpdf.svg.renderers.path.impl;
2+
3+
import com.itextpdf.kernel.geom.Point;
4+
import com.itextpdf.svg.SvgConstants;
5+
6+
/***
7+
* Implements closePath(Z) attribute of SVG's path element
8+
* */
9+
public class ClosePath extends LineTo {
10+
11+
@Override
12+
public Point getEndingPoint() {
13+
float x = getSvgCoordinate(properties, SvgConstants.Attributes.X);
14+
float y = getSvgCoordinate(properties, SvgConstants.Attributes.Y);
15+
return new Point(x,y);
16+
}
17+
18+
}

0 commit comments

Comments
 (0)