Skip to content

Commit f3311bf

Browse files
SnipxiText-CI
authored andcommitted
SVG: Support floating numbers with exponent notation in path painting instructions
DEVSIX-2343 Autoported commit. Original commit hash: [3adb38da4]
1 parent c14dcc0 commit f3311bf

File tree

6 files changed

+101
-27
lines changed

6 files changed

+101
-27
lines changed

itext.tests/itext.styledxmlparser.tests/itext/styledxmlparser/css/util/CssUtilTest.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,22 @@ public virtual void ParseAbsoluteLengthFrom10pt() {
103103
NUnit.Framework.Assert.AreEqual(expected, actual, 0);
104104
}
105105

106+
[NUnit.Framework.Test]
107+
public virtual void ParseAboluteLengthExponential01() {
108+
String value = "1e2pt";
109+
float actual = CssUtils.ParseAbsoluteLength(value);
110+
float expected = 1e2f;
111+
NUnit.Framework.Assert.AreEqual(expected, actual, 0);
112+
}
113+
114+
[NUnit.Framework.Test]
115+
public virtual void ParseAboluteLengthExponential02() {
116+
String value = "1e2px";
117+
float actual = CssUtils.ParseAbsoluteLength(value);
118+
float expected = 1e2f * 0.75f;
119+
NUnit.Framework.Assert.AreEqual(expected, actual, 0);
120+
}
121+
106122
[NUnit.Framework.Test]
107123
[LogMessage(iText.StyledXmlParser.LogMessageConstant.UNKNOWN_ABSOLUTE_METRIC_LENGTH_PARSED, Count = 1)]
108124
public virtual void ParseAbsoluteLengthFromUnknownType() {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace iText.Svg.Renderers.Impl {
4+
public class PathOperatorSplitTest {
5+
[NUnit.Framework.Test]
6+
public virtual void TestNumbersContainingExponent01() {
7+
String path = "M10,9.999999999999972C203.33333333333334,9.999999999999972,396.6666666666667,1.4210854715202004e-14,590,1.4210854715202004e-14L590,41.666666666666686C396.6666666666667,41.666666666666686,203.33333333333334,51.66666666666664,10,51.66666666666664Z";
8+
String[] operators = new String[] { "M10,9.999999999999972", "C203.33333333333334,9.999999999999972,396.6666666666667,1.4210854715202004e-14,590,1.4210854715202004e-14"
9+
, "L590,41.666666666666686", "C396.6666666666667,41.666666666666686,203.33333333333334,51.66666666666664,10,51.66666666666664"
10+
, "Z" };
11+
TestSplitting(path, operators);
12+
}
13+
14+
private void TestSplitting(String originalStr, String[] expectedSplitting) {
15+
String[] result = PathSvgNodeRenderer.SplitPathStringIntoOperators(originalStr);
16+
NUnit.Framework.Assert.AreEqual(expectedSplitting, result);
17+
}
18+
}
19+
}

itext.tests/itext.svg.tests/itext/svg/renderers/impl/PathParsingTest.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,5 +201,14 @@ public virtual void NegativeAfterPositiveTest() {
201201
String actual = path.SeparateDecimalPoints(input);
202202
NUnit.Framework.Assert.AreEqual(expected, actual);
203203
}
204+
205+
[NUnit.Framework.Test]
206+
public virtual void ExponentInNumberTest01() {
207+
PathSvgNodeRenderer path = new PathSvgNodeRenderer();
208+
String input = "C 268.88888888888886 67.97916666666663e+10 331.1111111111111 -2.842170943040401e-14 393.3333333333333 -2.842170943040401e-14";
209+
String expected = "C 268.88888888888886 67.97916666666663e+10 331.1111111111111 -2.842170943040401e-14 393.3333333333333 -2.842170943040401e-14";
210+
String actual = path.SeparateDecimalPoints(input);
211+
NUnit.Framework.Assert.AreEqual(expected, actual);
212+
}
204213
}
205214
}

itext/itext.styledxmlparser/itext/styledxmlparser/css/util/CssUtils.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,8 @@ private static int DeterminePositionBetweenValueAndUnit(String @string) {
424424
}
425425
int pos = 0;
426426
while (pos < @string.Length) {
427-
if (@string[pos] == '+' || @string[pos] == '-' || @string[pos] == '.' || @string[pos] >= '0' && @string[pos
428-
] <= '9') {
427+
if (@string[pos] == '+' || @string[pos] == '-' || @string[pos] == '.' || IsDigit(@string[pos]) || IsExponentNotation
428+
(@string, pos)) {
429429
pos++;
430430
}
431431
else {
@@ -630,5 +630,16 @@ private static bool AddRange(RangeBuilder builder, String left, String right) {
630630
builder.AddRange(l, r);
631631
return true;
632632
}
633+
634+
private static bool IsDigit(char ch) {
635+
return ch >= '0' && ch <= '9';
636+
}
637+
638+
private static bool IsExponentNotation(String s, int index) {
639+
return index < s.Length && s[index] == 'e' && (index + 1 < s.Length && IsDigit(s[index + 1]) || index + 2
640+
< s.Length && (s[index + 1] == '-' || s[index + 1] == '+') && IsDigit(s[index + 2]));
641+
}
642+
// e.g. 12e5
643+
// e.g. 12e-5, 12e+5
633644
}
634645
}

itext/itext.svg/itext/svg/renderers/impl/PathSvgNodeRenderer.cs

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class PathSvgNodeRenderer : AbstractSvgNodeRenderer {
6565
/// <summary>
6666
/// 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>
6767
/// <p>
68-
/// Find any occurence of a letter that is not an operator
68+
/// Find any occurrence of a letter that is not an operator
6969
/// </summary>
7070
private const String INVALID_OPERATOR_REGEX = "(?:(?![mzlhvcsqtae])\\p{L})";
7171

@@ -80,9 +80,16 @@ public class PathSvgNodeRenderer : AbstractSvgNodeRenderer {
8080
/// is called before the use of this expression in
8181
/// <see cref="ParsePathOperations()"/>
8282
/// the attribute to be split is valid.
83-
/// The regex splits at each letter.
83+
/// SVG defines 6 types of path commands, for a total of 20 commands:
84+
/// MoveTo: M, m
85+
/// LineTo: L, l, H, h, V, v
86+
/// Cubic Bezier Curve: C, c, S, s
87+
/// Quadratic Bezier Curve: Q, q, T, t
88+
/// Elliptical Arc Curve: A, a
89+
/// ClosePath: Z, z
8490
/// </summary>
85-
private const String SPLIT_REGEX = "(?=[\\p{L}])";
91+
private static readonly Regex SPLIT_PATTERN = iText.IO.Util.StringUtil.RegexCompile("(?=[mlhvcsqtaz])", System.Text.RegularExpressions.RegexOptions.IgnoreCase
92+
);
8693

8794
/// <summary>
8895
/// The
@@ -149,7 +156,7 @@ private String[] GetShapeCoordinates(IPathShape shape, IPathShape previousShape,
149156
String[] startingControlPoint = new String[2];
150157
if (previousShape != null) {
151158
Point previousEndPoint = previousShape.GetEndingPoint();
152-
//if the previous command was a Bézier curve, use its last control point
159+
//if the previous command was a Bezier curve, use its last control point
153160
if (previousShape is IControlPointCurve) {
154161
Point lastControlPoint = ((IControlPointCurve)previousShape).GetLastControlPoint();
155162
float reflectedX = (float)(2 * previousEndPoint.GetX() - lastControlPoint.GetX());
@@ -319,9 +326,8 @@ internal virtual ICollection<String> ParsePathOperations() {
319326
throw new SvgProcessingException(SvgLogMessageConstant.INVALID_PATH_D_ATTRIBUTE_OPERATORS).SetMessageParams
320327
(attributes);
321328
}
322-
String[] coordinates = iText.IO.Util.StringUtil.Split(attributes, SPLIT_REGEX);
323-
//gets an array attributesAr of {M 100 100, L 300 100, L200, 300, z}
324-
foreach (String inst in coordinates) {
329+
String[] operators = SplitPathStringIntoOperators(attributes);
330+
foreach (String inst in operators) {
325331
String instTrim = inst.Trim();
326332
if (!String.IsNullOrEmpty(instTrim)) {
327333
char instruction = instTrim[0];
@@ -334,38 +340,51 @@ internal virtual ICollection<String> ParsePathOperations() {
334340
return result;
335341
}
336342

337-
/// <summary>Iterate over the input string and to seperate</summary>
343+
/// <summary>Iterate over the input string and separate numbers from each other with space chars</summary>
338344
internal virtual String SeparateDecimalPoints(String input) {
339345
//If a space or minus sign is found reset
340346
//If a another point is found, add an extra space on before the point
341347
StringBuilder res = new StringBuilder();
342-
//Iterate over string
343-
bool decimalPointEncountered = false;
348+
// We are now among the digits to the right of the decimal point
349+
bool fractionalPartAfterDecimalPoint = false;
350+
// We are now among the exponent magnitude part
351+
bool exponentSignMagnitude = false;
344352
for (int i = 0; i < input.Length; i++) {
345353
char c = input[i];
346-
//If it's a whitespace or a minus sign and a point was previously found, reset the decimal point flag
347-
if (decimalPointEncountered && (c == '-' || iText.IO.Util.TextUtil.IsWhiteSpace(c))) {
348-
decimalPointEncountered = false;
354+
// Resetting flags
355+
if (c == '-' || iText.IO.Util.TextUtil.IsWhiteSpace(c)) {
356+
fractionalPartAfterDecimalPoint = false;
357+
}
358+
if (iText.IO.Util.TextUtil.IsWhiteSpace(c)) {
359+
exponentSignMagnitude = false;
360+
}
361+
// Add extra space before the next number starting from '.', or before the next number starting with '-'
362+
if (EndsWithNonWhitespace(res) && (c == '.' && fractionalPartAfterDecimalPoint || c == '-' && !exponentSignMagnitude
363+
)) {
364+
res.Append(" ");
349365
}
350-
//If a point is found, mark and continue
351366
if (c == '.') {
352-
//If it's the second point, add an extra space
353-
if (decimalPointEncountered) {
354-
res.Append(" ");
355-
}
356-
else {
357-
decimalPointEncountered = true;
358-
}
367+
fractionalPartAfterDecimalPoint = true;
359368
}
360369
else {
361-
if (c == '-') {
362-
// If a minus is found, add an extra space
363-
res.Append(" ");
370+
if (c == 'e') {
371+
exponentSignMagnitude = true;
364372
}
365373
}
366374
res.Append(c);
367375
}
368376
return res.ToString();
369377
}
378+
379+
/// <summary>Gets an array of strings representing operators with their arguments, e.g.</summary>
380+
/// <remarks>Gets an array of strings representing operators with their arguments, e.g. {"M 100 100", "L 300 100", "L200, 300", "z"}
381+
/// </remarks>
382+
internal static String[] SplitPathStringIntoOperators(String path) {
383+
return iText.IO.Util.StringUtil.Split(SPLIT_PATTERN, path);
384+
}
385+
386+
private static bool EndsWithNonWhitespace(StringBuilder sb) {
387+
return sb.Length > 0 && !iText.IO.Util.TextUtil.IsWhiteSpace(sb[sb.Length - 1]);
388+
}
370389
}
371390
}

port-hash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6bf0c6b50cd94df0cdeb2b1a9c4af9fca67007d9
1+
3adb38da4488df5dad06a8f2d0cc91fe569c0e29

0 commit comments

Comments
 (0)