Skip to content

Commit 396d5d6

Browse files
author
Samuel Huylebroeck
committed
Path parsing updates
Add warning for move to operation with incorrect nr of operands Move regex execution to util class for autoport Handle decimal points in path data DEVSIX-2043 DEVSIX-2157 DEVSIX-2158 DEVSIX-2159
1 parent ce581c0 commit 396d5d6

20 files changed

+288
-8
lines changed

svg/src/main/java/com/itextpdf/svg/exceptions/SvgLogMessageConstant.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,6 @@ public final class SvgLogMessageConstant {
8383
public static final String FONT_PROVIDER_CONTAINS_ZERO_FONTS = "Font Provider contains zero fonts. At least one font shall be present";
8484
/** The Constant UNABLE_TO_RETRIEVE_FONT. */
8585
public static final String UNABLE_TO_RETRIEVE_FONT = "Unable to retrieve font:\n {0}";
86+
public static final String PATH_WRONG_NUMBER_OF_ARGUMENTS = "Path operator {0} has received {1} arguments, but expects between {2} and {3} arguments. \n Resulting SVG will be incorrect.";
8687

8788
}

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

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

45+
46+
import com.itextpdf.io.util.MessageFormatUtil;
4547
import com.itextpdf.kernel.geom.Point;
4648
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
4749
import com.itextpdf.styledxmlparser.css.util.CssUtils;
@@ -59,12 +61,16 @@ This file is part of the iText (R) project.
5961
import com.itextpdf.svg.renderers.path.impl.SmoothSCurveTo;
6062
import com.itextpdf.svg.renderers.path.impl.VerticalLineTo;
6163
import com.itextpdf.svg.utils.SvgCssUtils;
64+
import com.itextpdf.svg.utils.SvgRegexUtils;
65+
import org.slf4j.Logger;
66+
import org.slf4j.LoggerFactory;
6267

6368
import java.util.ArrayList;
6469
import java.util.Arrays;
6570
import java.util.Collection;
6671
import java.util.List;
6772
import java.util.Map;
73+
import java.util.regex.Pattern;
6874

6975
/**
7076
* {@link ISvgNodeRenderer} implementation for the <path> tag.
@@ -74,13 +80,16 @@ public class PathSvgNodeRenderer extends AbstractSvgNodeRenderer {
7480
private static final String SEPARATOR = "";
7581
private static final String SPACE_CHAR = " ";
7682

83+
private static final Logger LOGGER = LoggerFactory.getLogger(PathSvgNodeRenderer.class);
84+
private static final int MOVETOARGUMENTNR = 2;
85+
7786
/**
7887
* The regular expression to find invalid operators in the <a href="https://www.w3.org/TR/SVG/paths.html#PathData">PathData attribute of the &ltpath&gt element</a>
7988
* <p>
80-
* Any two consecutive letters are an invalid operator.
89+
* Find any occurence of a letter that is not an operator
8190
*/
82-
private final String INVALID_OPERATOR_REGEX = "(\\p{L}{2,})";
83-
91+
private static final String INVALID_OPERATOR_REGEX = "(?:(?![mzlhvcsqtae])\\p{L})";
92+
private static Pattern invalidRegexPattern = Pattern.compile(INVALID_OPERATOR_REGEX, Pattern.CASE_INSENSITIVE);;
8493

8594
/**
8695
* The regular expression to split the <a href="https://www.w3.org/TR/SVG/paths.html#PathData">PathData attribute of the &ltpath&gt element</a>
@@ -186,8 +195,6 @@ private String[] makeRelativeOperatorsAbsolute(String[] relativeOperators, doubl
186195
return absoluteOperators;
187196
}
188197

189-
190-
191198
/**
192199
* Processes an individual pathing operator and all of its arguments, converting into one or more
193200
* {@link IPathShape} objects.
@@ -213,6 +220,9 @@ private List<IPathShape> processPathOperator(String[] pathProperties, IPathShape
213220
}
214221
} else if (pathShape instanceof MoveTo) {
215222
zOperator = new ClosePath();
223+
if (shapeCoordinates != null && shapeCoordinates.length != MOVETOARGUMENTNR) {
224+
LOGGER.warn(MessageFormatUtil.format(SvgLogMessageConstant.PATH_WRONG_NUMBER_OF_ARGUMENTS, pathProperties[0], shapeCoordinates.length, MOVETOARGUMENTNR, MOVETOARGUMENTNR));
225+
}
216226
zOperator.setCoordinates(shapeCoordinates);
217227
}
218228

@@ -226,7 +236,6 @@ private List<IPathShape> processPathOperator(String[] pathProperties, IPathShape
226236
return shapes;
227237
}
228238

229-
230239
/**
231240
* Processes the {@link SvgConstants.Attributes.D} {@link PathSvgNodeRenderer#attributesAndStyles} and converts them
232241
* into one or more {@link IPathShape} objects to be drawn on the canvas.
@@ -255,8 +264,8 @@ private static String[] concatenate(String[] first, String[] second) {
255264
return arr;
256265
}
257266

258-
private boolean containsInvalidAttributes(String attributes) {
259-
return attributes.split(INVALID_OPERATOR_REGEX).length > 1;
267+
boolean containsInvalidAttributes(String attributes) {
268+
return SvgRegexUtils.ContainsAtLeastOneMatch(invalidRegexPattern,attributes);
260269
}
261270

262271
private Collection<String> parsePropertiesAndStyles() {
@@ -272,6 +281,8 @@ private Collection<String> parsePropertiesAndStyles() {
272281
String instTrim = inst.trim();
273282
String instruction = instTrim.charAt(0) + SPACE_CHAR;
274283
String temp = instruction + instTrim.replace(instTrim.charAt(0) + SEPARATOR, SEPARATOR).replace(",", SPACE_CHAR).trim();
284+
//Do a run-through for decimal point separation
285+
temp = separateDecimalPoints(temp);
275286
result.append(SPACE_CHAR);
276287
result.append(temp);
277288
}
@@ -283,5 +294,38 @@ private Collection<String> parsePropertiesAndStyles() {
283294
return resultList;
284295
}
285296

297+
/**
298+
* Iterate over the input string and to seperate
299+
* @param input
300+
* @return
301+
*/
302+
String separateDecimalPoints(String input){
303+
//If a space or minus sign is found reset
304+
//If a another point is found, add an extra space on before the point
305+
String res="";
306+
//Iterate over string
307+
boolean decimalPointEncountered = false;
308+
for (int i = 0; i < input.length(); i++) {
309+
char c = input.charAt(i);
310+
//If it's a whitespace or minus sign and a point was previously found, reset
311+
if(decimalPointEncountered && (c=='-' || Character.isWhitespace(c))){
312+
decimalPointEncountered = false;
313+
}
314+
//If a point is found, mark and continue
315+
if(c =='.'){
316+
//If it's the second point, add extra space
317+
if(decimalPointEncountered){
318+
res+=" ";
319+
}else{
320+
decimalPointEncountered=true;
321+
}
322+
}
323+
res+=c;
324+
}
325+
326+
327+
return res;
328+
}
329+
286330

287331
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.itextpdf.svg.utils;
2+
3+
import java.util.regex.Matcher;
4+
import java.util.regex.Pattern;
5+
6+
public class SvgRegexUtils {
7+
8+
public static boolean ContainsAtLeastOneMatch(Pattern regexPattern, String stringToExamine){
9+
Matcher matcher = regexPattern.matcher(stringToExamine);
10+
return matcher.find();
11+
}
12+
}

svg/src/test/java/com/itextpdf/svg/renderers/FillTest.java

Lines changed: 2 additions & 0 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;
4444

45+
import com.itextpdf.svg.exceptions.SvgProcessingException;
4546
import com.itextpdf.test.ITextTest;
4647
import com.itextpdf.test.annotations.type.IntegrationTest;
4748

@@ -127,6 +128,7 @@ public void opacityFillTest() throws IOException, InterruptedException {
127128
/* This test should fail when RND-1108 is resolved*/
128129
@Test
129130
public void eofillUnsuportedAtributeTest() throws IOException, InterruptedException {
131+
junitExpectedException.expect(SvgProcessingException.class);
130132
convertAndCompareVisually(SOURCE_FOLDER, DESTINATION_FOLDER, "eofillUnsuportedAtributeTest");
131133
}
132134

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2018 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.svg.renderers.impl;
44+
45+
46+
import com.itextpdf.svg.exceptions.SvgProcessingException;
47+
import com.itextpdf.svg.renderers.SvgIntegrationTest;
48+
import com.itextpdf.test.ITextTest;
49+
import com.itextpdf.test.annotations.type.IntegrationTest;
50+
import org.junit.Assert;
51+
import org.junit.BeforeClass;
52+
import org.junit.Rule;
53+
import org.junit.Test;
54+
import org.junit.experimental.categories.Category;
55+
import org.junit.rules.ExpectedException;
56+
57+
import java.io.IOException;
58+
59+
@Category(IntegrationTest.class)
60+
public class PathParsingIntegrationTest extends SvgIntegrationTest {
61+
62+
public static final String sourceFolder = "./src/test/resources/com/itextpdf/svg/renderers/impl/PathParsingIntegrationTest/";
63+
public static final String destinationFolder = "./target/test/com/itextpdf/svg/renderers/impl/PathParsingIntegrationTest/";
64+
65+
@Rule
66+
public ExpectedException junitExpectedException = ExpectedException.none();
67+
68+
@BeforeClass
69+
public static void beforeClass() {
70+
ITextTest.createDestinationFolder(destinationFolder);
71+
}
72+
73+
@Test
74+
public void normalTest() throws IOException, InterruptedException {
75+
convertAndCompareVisually(sourceFolder, destinationFolder, "normal");
76+
}
77+
78+
@Test
79+
public void mixTest() throws IOException, InterruptedException {
80+
convertAndCompareVisually(sourceFolder, destinationFolder, "mix");
81+
}
82+
83+
@Test
84+
public void noWhitespace() throws IOException, InterruptedException {
85+
convertAndCompareVisually(sourceFolder, destinationFolder, "noWhitespace");
86+
}
87+
88+
@Test
89+
public void zOperator() throws IOException, InterruptedException {
90+
convertAndCompareVisually(sourceFolder, destinationFolder, "zOperator");
91+
}
92+
93+
@Test
94+
public void missingOperandArgument() throws IOException, InterruptedException {
95+
convertAndCompareVisually(sourceFolder, destinationFolder, "missingOperandArgument");
96+
}
97+
98+
@Test
99+
public void decimalPointHandlingTest() throws IOException, InterruptedException {
100+
convertAndCompareVisually(sourceFolder, destinationFolder, "decimalPointHandling");
101+
}
102+
103+
@Test
104+
public void invalidOperatorTest() throws IOException, InterruptedException {
105+
junitExpectedException.expect(SvgProcessingException.class);
106+
convertAndCompareVisually(sourceFolder, destinationFolder, "invalidOperator");
107+
}
108+
109+
@Test
110+
public void invalidOperatorCSensTest() throws IOException, InterruptedException {
111+
junitExpectedException.expect(SvgProcessingException.class);
112+
convertAndCompareVisually(sourceFolder, destinationFolder, "invalidOperatorCSens");
113+
}
114+
115+
@Test
116+
public void decimalPointParsingTest(){
117+
PathSvgNodeRenderer path = new PathSvgNodeRenderer();
118+
String input = "2.35.96";
119+
120+
String expected = "2.35 .96";
121+
String actual = path.separateDecimalPoints(input);
122+
Assert.assertEquals(expected,actual);
123+
}
124+
125+
@Test
126+
public void decimalPointParsingSpaceTest(){
127+
PathSvgNodeRenderer path = new PathSvgNodeRenderer();
128+
String input = "2.35.96 3.25 .25";
129+
130+
String expected = "2.35 .96 3.25 .25";
131+
String actual = path.separateDecimalPoints(input);
132+
Assert.assertEquals(expected,actual);
133+
}
134+
135+
@Test
136+
public void decimalPointParsingTabTest(){
137+
PathSvgNodeRenderer path = new PathSvgNodeRenderer();
138+
String input = "2.35.96 3.25\t.25";
139+
140+
String expected = "2.35 .96 3.25\t.25";
141+
String actual = path.separateDecimalPoints(input);
142+
Assert.assertEquals(expected,actual);
143+
}
144+
@Test
145+
public void decimalPointParsingMinusTest(){
146+
PathSvgNodeRenderer path = new PathSvgNodeRenderer();
147+
String input = "2.35.96 3.25-.25";
148+
149+
String expected = "2.35 .96 3.25-.25";
150+
String actual = path.separateDecimalPoints(input);
151+
Assert.assertEquals(expected,actual);
152+
}
153+
154+
}
155+

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ public void multipleRelativeVerticalLineToTest() throws IOException, Interrupted
410410

411411
@Test
412412
public void eofillUnsuportedPathTest() throws IOException, InterruptedException {
413+
junitExpectedException.expect(SvgProcessingException.class);
413414
convertAndCompareVisually(sourceFolder, destinationFolder, "eofillUnsuportedPathTest");
414415
}
415416

0 commit comments

Comments
 (0)