Skip to content

Commit cc280cf

Browse files
committed
Add implementation for Path operators v and h. RND-1108
1 parent f146182 commit cc280cf

22 files changed

+174
-28
lines changed

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ This file is part of the iText (R) project.
5050
import com.itextpdf.svg.exceptions.SvgProcessingException;
5151
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
5252
import com.itextpdf.svg.renderers.SvgDrawContext;
53-
import com.itextpdf.svg.renderers.path.SvgPathShapeFactory;
5453
import com.itextpdf.svg.renderers.path.IPathShape;
54+
import com.itextpdf.svg.renderers.path.SvgPathShapeFactory;
5555
import com.itextpdf.svg.renderers.path.impl.ClosePath;
56-
import com.itextpdf.svg.renderers.path.impl.MoveTo;
5756
import com.itextpdf.svg.renderers.path.impl.CurveTo;
58-
import com.itextpdf.svg.renderers.path.impl.SmoothSCurveTo;
5957
import com.itextpdf.svg.renderers.path.impl.HorizontalLineTo;
58+
import com.itextpdf.svg.renderers.path.impl.MoveTo;
59+
import com.itextpdf.svg.renderers.path.impl.SmoothSCurveTo;
6060
import com.itextpdf.svg.renderers.path.impl.VerticalLineTo;
6161
import com.itextpdf.svg.utils.SvgCssUtils;
6262

@@ -107,6 +107,7 @@ public class PathSvgNodeRenderer extends AbstractSvgNodeRenderer {
107107
public void doDraw(SvgDrawContext context) {
108108
PdfCanvas canvas = context.getCurrentCanvas();
109109
canvas.writeLiteral("% path\n");
110+
currentPoint = new Point(0,0);
110111
for (IPathShape item : getShapes()) {
111112
item.draw(canvas);
112113
}
@@ -139,7 +140,7 @@ private String[] getShapeCoordinates(IPathShape shape, IPathShape previousShape,
139140
Map<String, String> coordinates = previousShape.getCoordinates();
140141

141142
//if the previous command was a C or S use its last control point
142-
if (((previousShape instanceof CurveTo) || (previousShape instanceof SmoothSCurveTo))) {
143+
if (((previousShape instanceof CurveTo))) {
143144
float reflectedX = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.X2)));
144145
float reflectedy = (float) (2 * CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y)) - CssUtils.parseFloat(coordinates.get(SvgConstants.Attributes.Y2)));
145146

@@ -158,13 +159,13 @@ private String[] getShapeCoordinates(IPathShape shape, IPathShape previousShape,
158159
} else if (shape instanceof VerticalLineTo) {
159160
String currentX = String.valueOf(currentPoint.x);
160161
String currentY = String.valueOf(currentPoint.y);
161-
String[] yValues = concatenate(new String[]{currentY}, operatorArgs);
162+
String[] yValues = concatenate(new String[] { currentY }, shape.isRelative() ? makeRelativeOperatorsAbsolute(operatorArgs, currentPoint.y) : operatorArgs);
162163
shapeCoordinates = concatenate(new String[]{currentX}, yValues);
163164

164165
} else if (shape instanceof HorizontalLineTo) {
165166
String currentX = String.valueOf(currentPoint.x);
166167
String currentY = String.valueOf(currentPoint.y);
167-
String[] xValues = concatenate(new String[]{currentX}, operatorArgs);
168+
String[] xValues = concatenate(new String[]{currentX}, shape.isRelative() ? makeRelativeOperatorsAbsolute(operatorArgs, currentPoint.x) : operatorArgs);
168169
shapeCoordinates = concatenate(new String[]{currentY}, xValues);
169170
}
170171
if (shapeCoordinates == null) {
@@ -173,6 +174,19 @@ private String[] getShapeCoordinates(IPathShape shape, IPathShape previousShape,
173174
return shapeCoordinates;
174175
}
175176

177+
private String[] makeRelativeOperatorsAbsolute(String[] relativeOperators, double currentCoordinate) {
178+
String[] absoluteOperators = new String[relativeOperators.length];
179+
180+
for (int i = 0; i < relativeOperators.length; i++) {
181+
double relativeDouble = Double.parseDouble(relativeOperators[i]);
182+
relativeDouble += currentCoordinate;
183+
absoluteOperators[i] = String.valueOf(relativeDouble);
184+
}
185+
186+
return absoluteOperators;
187+
}
188+
189+
176190

177191
/**
178192
* Processes an individual pathing operator and all of its arguments, converting into one or more
@@ -202,15 +216,13 @@ private List<IPathShape> processPathOperator(String[] pathProperties, IPathShape
202216
zOperator.setCoordinates(shapeCoordinates);
203217
}
204218

205-
Point endingPoint = null;
206219
if (pathShape != null) {
207220
if (shapeCoordinates != null) {
208221
pathShape.setCoordinates(shapeCoordinates);
209222
}
210-
endingPoint = pathShape.getEndingPoint();
223+
currentPoint = pathShape.getEndingPoint(); // unsupported operators are ignored.
211224
shapes.add(pathShape);
212225
}
213-
currentPoint = endingPoint;
214226
return shapes;
215227
}
216228

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.kernel.geom.Point;
4646
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
47-
import com.itextpdf.svg.renderers.SvgDrawContext;
4847

4948
import java.util.Map;
5049

@@ -72,6 +71,11 @@ public interface IPathShape {
7271
*/
7372
void setCoordinates(String[] coordinates);
7473

74+
/**
75+
* Returns the coordinates associated with this Shape.
76+
*
77+
* @return the coordinates associated with this Shape
78+
*/
7579
Map<String, String> getCoordinates();
7680

7781
/**
@@ -82,4 +86,12 @@ public interface IPathShape {
8286
* If the point does not exist or does not change {@code null} may be returned.
8387
*/
8488
Point getEndingPoint();
89+
90+
/**
91+
* Returns true when this shape is a relative operator. False if it is an absolute operator.
92+
*
93+
* @return true if relative, false if absolute
94+
*/
95+
boolean isRelative();
96+
8597
}

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

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

62+
/**
63+
* Whether this is a relative operator or not.
64+
*/
65+
protected boolean relative;
6266

6367
/**
6468
* Get a coordinate based on a key value.
@@ -106,4 +110,8 @@ public Map<String, String> getCoordinates() {
106110
return properties;
107111
}
108112

113+
@Override
114+
public boolean isRelative() {
115+
return this.relative;
116+
}
109117
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ This file is part of the iText (R) project.
4949
* Implements lineTo(H) attribute of SVG's path element
5050
* */
5151
public class HorizontalLineTo extends OneDimensionalLineTo {
52+
53+
/**
54+
* Creates an absolute Horizontal LineTo.
55+
*/
56+
public HorizontalLineTo() {
57+
this(false);
58+
}
59+
60+
/**
61+
* Creates a Horizontal LineTo. Set argument to true to create a relative HorizontalLineTo.
62+
*
63+
* @param relative whether this is a relative HorizontalLineTo or not
64+
*/
65+
public HorizontalLineTo(boolean relative) {
66+
super.relative = relative;
67+
}
68+
69+
5270
@Override
5371
public void draw(PdfCanvas canvas) {
5472
float minX = getCoordinate(properties, MINIMUM_CHANGING_DIMENSION_VALUE);

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ This file is part of the iText (R) project.
5454
* */
5555
public class LineTo extends AbstractPathShape {
5656

57+
public LineTo() {
58+
this(false);
59+
}
60+
61+
public LineTo(boolean relative) {
62+
super.relative = relative;
63+
}
64+
65+
5766
@Override
5867
public void draw(PdfCanvas canvas) {
5968
canvas.lineTo(getCoordinate(properties, SvgConstants.Attributes.X), getCoordinate(properties, SvgConstants.Attributes.Y));
@@ -62,8 +71,8 @@ public void draw(PdfCanvas canvas) {
6271
@Override
6372
public void setCoordinates(String[] coordinates) {
6473
Map<String, String> map = new HashMap<String, String>();
65-
map.put(SvgConstants.Attributes.X, coordinates.length > 0 && !coordinates[0].isEmpty() ? coordinates[0] : "0");
66-
map.put(SvgConstants.Attributes.Y, coordinates.length > 1 && !coordinates[1].isEmpty() ? coordinates[1] : "0");
74+
map.put(SvgConstants.Attributes.X, coordinates.length > 0 && ! coordinates[0].isEmpty() ? coordinates[0] : "0");
75+
map.put(SvgConstants.Attributes.Y, coordinates.length > 1 && ! coordinates[1].isEmpty() ? coordinates[1] : "0");
6776
setProperties(map);
6877
}
6978

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,29 +58,29 @@ public abstract class OneDimensionalLineTo extends AbstractPathShape {
5858
* For vertical lines the x value will not change,
5959
* for horizontal lines y value will not change.
6060
*/
61-
protected final String CURRENT_NONCHANGING_DIMENSION_VALUE = "CURRENT_NONCHANGING_DIMENSION_VALUE";
61+
protected static final String CURRENT_NONCHANGING_DIMENSION_VALUE = "CURRENT_NONCHANGING_DIMENSION_VALUE";
6262

6363
/**
6464
* The minimum x or y value in the dimension that will change.
6565
* For vertical lines this will be the y value of the bottom-most point.
6666
* For horizontal lines this will be the x value of the left-most point.
6767
*/
68-
protected final String MINIMUM_CHANGING_DIMENSION_VALUE = "MINIMUM_CHANGING_DIMENSION_VALUE";
68+
protected static final String MINIMUM_CHANGING_DIMENSION_VALUE = "MINIMUM_CHANGING_DIMENSION_VALUE";
6969

7070
/**
7171
* The maximum x or y value in the dimension that will change.
7272
* For vertical lines this will be the y value of the top-most point.
7373
* For horizontal lines this will be the x value of the righ-most point.
7474
*/
7575

76-
protected final String MAXIMUM_CHANGING_DIMENSION_VALUE = "MAXIMUM_CHANGING_DIMENSION_VALUE";
76+
protected static final String MAXIMUM_CHANGING_DIMENSION_VALUE = "MAXIMUM_CHANGING_DIMENSION_VALUE";
7777

7878
/**
7979
* The final x or y value in the dimension that will change.
8080
* For vertical lines this will be the y value of the last point.
8181
* For horizontal lines this will be the x value of the last point.
8282
*/
83-
protected final String ENDING_CHANGING_DIMENSION_VALUE = "ENDING_CHANGING_DIMENSION_VALUE";
83+
protected static final String ENDING_CHANGING_DIMENSION_VALUE = "ENDING_CHANGING_DIMENSION_VALUE";
8484

8585

8686
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ public Map<String, IPathShape> getMapping() {
6161
Map<String, IPathShape> result = new HashMap<>();
6262
result.put(SvgConstants.Attributes.PATH_DATA_LINE_TO, new LineTo());
6363
result.put(SvgConstants.Attributes.PATH_DATA_LINE_TO_V, new VerticalLineTo());
64+
result.put(SvgConstants.Attributes.PATH_DATA_REL_LINE_TO_V, new VerticalLineTo(true));
6465
result.put(SvgConstants.Attributes.PATH_DATA_LINE_TO_H, new HorizontalLineTo());
66+
result.put(SvgConstants.Attributes.PATH_DATA_REL_LINE_TO_H, new HorizontalLineTo(true));
6567
result.put(SvgConstants.Attributes.PATH_DATA_CLOSE_PATH, new ClosePath());
6668
result.put(SvgConstants.Attributes.PATH_DATA_CLOSE_PATH.toLowerCase(), new ClosePath());
6769
result.put(SvgConstants.Attributes.PATH_DATA_MOVE_TO, new MoveTo());

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ This file is part of the iText (R) project.
4949
* Implements lineTo(V) attribute of SVG's path element
5050
* */
5151
public class VerticalLineTo extends OneDimensionalLineTo {
52+
53+
/**
54+
* Creates an absolute Vertical LineTo.
55+
*/
56+
public VerticalLineTo() {
57+
this(false);
58+
}
59+
60+
/**
61+
* Creates a Vertical LineTo. Set argument to true to create a relative VerticalLineTo.
62+
*
63+
* @param relative whether this is a relative VerticalLineTo or not
64+
*/
65+
public VerticalLineTo(boolean relative) {
66+
super.relative = relative;
67+
}
68+
69+
5270
@Override
5371
public void draw(PdfCanvas canvas) {
5472
float minY = getCoordinate(properties, MINIMUM_CHANGING_DIMENSION_VALUE);

svg/src/test/java/com/itextpdf/svg/renderers/impl/PathSvgNodeRendererTest.java

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ This file is part of the iText (R) project.
4848
import com.itextpdf.kernel.utils.CompareTool;
4949
import com.itextpdf.styledxmlparser.node.IElementNode;
5050
import com.itextpdf.styledxmlparser.node.impl.jsoup.JsoupXmlParser;
51-
import com.itextpdf.svg.exceptions.SvgProcessingException;
5251
import com.itextpdf.svg.SvgConstants;
52+
import com.itextpdf.svg.exceptions.SvgProcessingException;
5353
import com.itextpdf.svg.processors.impl.DefaultSvgProcessor;
5454
import com.itextpdf.svg.renderers.IBranchSvgNodeRenderer;
5555
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
@@ -71,7 +71,7 @@ This file is part of the iText (R) project.
7171
import org.junit.experimental.categories.Category;
7272
import org.junit.rules.ExpectedException;
7373

74-
@Category(IntegrationTest.class)
74+
@Category( IntegrationTest.class )
7575
public class PathSvgNodeRendererTest extends SvgIntegrationTest {
7676

7777
public static final String sourceFolder = "./src/test/resources/com/itextpdf/svg/renderers/impl/PathSvgNodeRendererTest/";
@@ -106,7 +106,7 @@ public void pathNodeRendererMoveToTest() throws IOException, InterruptedExceptio
106106

107107
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
108108

109-
if (result != null && !result.contains("No visual differences")) {
109+
if (result != null && ! result.contains("No visual differences")) {
110110
Assert.fail(result);
111111
}
112112
}
@@ -132,7 +132,7 @@ public void pathNodeRendererMoveToTest1() throws IOException, InterruptedExcepti
132132

133133
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
134134

135-
if (result != null && !result.contains("No visual differences")) {
135+
if (result != null && ! result.contains("No visual differences")) {
136136
Assert.fail(result);
137137
}
138138
}
@@ -157,7 +157,7 @@ public void pathNodeRendererCurveToTest() throws IOException, InterruptedExcepti
157157
doc.close();
158158
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
159159

160-
if (result != null && !result.contains("No visual differences")) {
160+
if (result != null && ! result.contains("No visual differences")) {
161161
Assert.fail(result);
162162
}
163163
}
@@ -182,7 +182,7 @@ public void pathNodeRendererCurveToTest1() throws IOException, InterruptedExcept
182182

183183
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
184184

185-
if (result != null && !result.contains("No visual differences")) {
185+
if (result != null && ! result.contains("No visual differences")) {
186186
Assert.fail(result);
187187
}
188188
}
@@ -206,7 +206,7 @@ public void pathNodeRendererQCurveToCurveToTest() throws IOException, Interrupte
206206
doc.close();
207207
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
208208

209-
if (result != null && !result.contains("No visual differences")) {
209+
if (result != null && ! result.contains("No visual differences")) {
210210
Assert.fail(result);
211211
}
212212
}
@@ -230,7 +230,7 @@ public void pathNodeRendererQCurveToCurveToTest1() throws IOException, Interrupt
230230
doc.close();
231231
String result = new CompareTool().compareByContent(destinationFolder + filename, sourceFolder + "cmp_" + filename, destinationFolder, "diff_");
232232

233-
if (result != null && !result.contains("No visual differences")) {
233+
if (result != null && ! result.contains("No visual differences")) {
234234
Assert.fail(result);
235235
}
236236
}
@@ -304,11 +304,11 @@ public void pathNodeRendererCurveComplexTest() throws IOException, InterruptedEx
304304
}
305305

306306
@Test
307-
public void deepCopyTest(){
307+
public void deepCopyTest() {
308308
PathSvgNodeRenderer expected = new PathSvgNodeRenderer();
309-
expected.setAttribute(SvgConstants.Attributes.FILL,"blue");
310-
ISvgNodeRenderer actual =expected.createDeepCopy();
311-
Assert.assertEquals(expected,actual);
309+
expected.setAttribute(SvgConstants.Attributes.FILL, "blue");
310+
ISvgNodeRenderer actual = expected.createDeepCopy();
311+
Assert.assertEquals(expected, actual);
312312
}
313313

314314
@Test
@@ -377,9 +377,40 @@ public void pathHOperatorSimpleTest01() throws IOException, InterruptedException
377377
public void pathHandVOperatorTest01() throws IOException, InterruptedException {
378378
convertAndCompareVisually(sourceFolder, destinationFolder, "pathHandVOperatorTest01");
379379
}
380+
380381
@Test
381382
public void curveToContinuePathingTest() throws IOException, InterruptedException {
382383
convertAndCompareVisually(sourceFolder, destinationFolder, "curveToContinuePathingTest");
383384
}
384385

386+
@Test
387+
public void relativeHorizontalLineToTest() throws IOException, InterruptedException {
388+
convertAndCompareVisually(sourceFolder, destinationFolder, "relativeHorizontalLineTo");
389+
}
390+
391+
@Test
392+
public void relativeVerticalLineToTest() throws IOException, InterruptedException {
393+
convertAndCompareVisually(sourceFolder, destinationFolder, "relativeVerticalLineTo");
394+
}
395+
396+
@Test
397+
public void combinedRelativeVerticalLineToAndRelativeHorizontalLineToTest() throws IOException, InterruptedException {
398+
convertAndCompareVisually(sourceFolder, destinationFolder, "combinedRelativeVerticalLineToAndRelativeHorizontalLineTo");
399+
}
400+
401+
@Test
402+
public void multipleRelativeHorizontalLineToTest() throws IOException, InterruptedException {
403+
convertAndCompareVisually(sourceFolder, destinationFolder, "multipleRelativeHorizontalLineTo");
404+
}
405+
406+
@Test
407+
public void multipleRelativeVerticalLineToTest() throws IOException, InterruptedException {
408+
convertAndCompareVisually(sourceFolder, destinationFolder, "multipleRelativeVerticalLineTo");
409+
}
410+
411+
@Test
412+
public void eofillUnsuportedPathTest() throws IOException, InterruptedException {
413+
convertAndCompareVisually(sourceFolder, destinationFolder, "eofillUnsuportedPathTest");
414+
}
415+
385416
}

0 commit comments

Comments
 (0)