@@ -65,7 +65,7 @@ public class PathSvgNodeRenderer : AbstractSvgNodeRenderer {
65
65
/// <summary>
66
66
/// The regular expression to find invalid operators in the <a href="https://www.w3.org/TR/SVG/paths.html#PathData">PathData attribute of the <path> element</a>
67
67
/// <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
69
69
/// </summary>
70
70
private const String INVALID_OPERATOR_REGEX = "(?:(?![mzlhvcsqtae])\\ p{L})" ;
71
71
@@ -80,9 +80,16 @@ public class PathSvgNodeRenderer : AbstractSvgNodeRenderer {
80
80
/// is called before the use of this expression in
81
81
/// <see cref="ParsePathOperations()"/>
82
82
/// 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
84
90
/// </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
+ ) ;
86
93
87
94
/// <summary>
88
95
/// The
@@ -149,7 +156,7 @@ private String[] GetShapeCoordinates(IPathShape shape, IPathShape previousShape,
149
156
String [ ] startingControlPoint = new String [ 2 ] ;
150
157
if ( previousShape != null ) {
151
158
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
153
160
if ( previousShape is IControlPointCurve ) {
154
161
Point lastControlPoint = ( ( IControlPointCurve ) previousShape ) . GetLastControlPoint ( ) ;
155
162
float reflectedX = ( float ) ( 2 * previousEndPoint . GetX ( ) - lastControlPoint . GetX ( ) ) ;
@@ -319,9 +326,8 @@ internal virtual ICollection<String> ParsePathOperations() {
319
326
throw new SvgProcessingException ( SvgLogMessageConstant . INVALID_PATH_D_ATTRIBUTE_OPERATORS ) . SetMessageParams
320
327
( attributes ) ;
321
328
}
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 ) {
325
331
String instTrim = inst . Trim ( ) ;
326
332
if ( ! String . IsNullOrEmpty ( instTrim ) ) {
327
333
char instruction = instTrim [ 0 ] ;
@@ -334,38 +340,51 @@ internal virtual ICollection<String> ParsePathOperations() {
334
340
return result ;
335
341
}
336
342
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>
338
344
internal virtual String SeparateDecimalPoints ( String input ) {
339
345
//If a space or minus sign is found reset
340
346
//If a another point is found, add an extra space on before the point
341
347
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 ;
344
352
for ( int i = 0 ; i < input . Length ; i ++ ) {
345
353
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 ( " " ) ;
349
365
}
350
- //If a point is found, mark and continue
351
366
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 ;
359
368
}
360
369
else {
361
- if ( c == '-' ) {
362
- // If a minus is found, add an extra space
363
- res . Append ( " " ) ;
370
+ if ( c == 'e' ) {
371
+ exponentSignMagnitude = true ;
364
372
}
365
373
}
366
374
res . Append ( c ) ;
367
375
}
368
376
return res . ToString ( ) ;
369
377
}
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
+ }
370
389
}
371
390
}
0 commit comments