@@ -42,6 +42,7 @@ This file is part of the iText (R) project.
42
42
*/
43
43
package com .itextpdf .svg .renderers .impl ;
44
44
45
+ import com .itextpdf .kernel .geom .Point ;
45
46
import com .itextpdf .kernel .pdf .canvas .PdfCanvas ;
46
47
import com .itextpdf .styledxmlparser .css .util .CssUtils ;
47
48
import com .itextpdf .svg .SvgConstants ;
@@ -51,10 +52,12 @@ This file is part of the iText (R) project.
51
52
import com .itextpdf .svg .renderers .SvgDrawContext ;
52
53
import com .itextpdf .svg .renderers .path .SvgPathShapeFactory ;
53
54
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 ;
56
56
import com .itextpdf .svg .renderers .path .impl .MoveTo ;
57
+ import com .itextpdf .svg .renderers .path .impl .CurveTo ;
57
58
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 ;
58
61
import com .itextpdf .svg .utils .SvgCssUtils ;
59
62
60
63
import java .util .ArrayList ;
@@ -87,7 +90,18 @@ public class PathSvgNodeRenderer extends AbstractSvgNodeRenderer {
87
90
*/
88
91
private final String SPLIT_REGEX = "(?=[\\ p{L}])" ;
89
92
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 ;
91
105
92
106
@ Override
93
107
public void doDraw (SvgDrawContext context ) {
@@ -98,60 +112,126 @@ public void doDraw(SvgDrawContext context) {
98
112
}
99
113
}
100
114
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
+ */
101
226
private Collection <IPathShape > getShapes () {
102
227
Collection <String > parsedResults = parsePropertiesAndStyles ();
103
228
List <IPathShape > shapes = new ArrayList <>();
104
229
105
230
for (String parsedResult : parsedResults ) {
106
- //split att to {M , 100, 100}
107
231
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 );
155
235
}
156
236
return shapes ;
157
237
}
@@ -191,11 +271,5 @@ private Collection<String> parsePropertiesAndStyles() {
191
271
return resultList ;
192
272
}
193
273
194
- @ Override
195
- public ISvgNodeRenderer createDeepCopy () {
196
- PathSvgNodeRenderer copy = new PathSvgNodeRenderer ();
197
- deepCopyAttributesAndStyles (copy );
198
- return copy ;
199
- }
200
274
201
275
}
0 commit comments