Skip to content

Commit 8e67812

Browse files
committed
Support 'patternTransform' attribute for SVG pattern element
DEVSIX-4811
1 parent ad6b1d5 commit 8e67812

22 files changed

+173
-26
lines changed

svg/src/main/java/com/itextpdf/svg/SvgConstants.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -654,19 +654,6 @@ public static final class Attributes extends CommonAttributeConstants {
654654
*/
655655
public static final String ORIENT = "orient";
656656

657-
658-
/**
659-
* Attribute defining the coordinate system for attributes x, y, width , and height in pattern.
660-
*/
661-
// TODO: DEVSIX-3923 remove normalization (.toLowerCase)
662-
public static final String PATTERN_UNITS = "patternUnits".toLowerCase();
663-
664-
/**
665-
* Attribute defining the coordinate system for the pattern content.
666-
*/
667-
// TODO: DEVSIX-3923 remove normalization (.toLowerCase)
668-
public static final String PATTERN_CONTENT_UNITS = "patternContentUnits".toLowerCase();
669-
670657
/**
671658
* Close Path Operator.
672659
*/
@@ -780,6 +767,24 @@ public static final class Attributes extends CommonAttributeConstants {
780767
*/
781768
public static final String PATH_DATA_REL_QUAD_CURVE_TO = "q";
782769

770+
/**
771+
* Attribute defining the coordinate system for the pattern content.
772+
*/
773+
// TODO: DEVSIX-3923 remove normalization (.toLowerCase)
774+
public static final String PATTERN_CONTENT_UNITS = "patternContentUnits".toLowerCase();
775+
776+
/**
777+
* Attribute defining list of transform definitions for the pattern element.
778+
*/
779+
// TODO: DEVSIX-3923 remove normalization (.toLowerCase)
780+
public static final String PATTERN_TRANSFORM = "patternTransform".toLowerCase();
781+
782+
/**
783+
* Attribute defining the coordinate system for attributes x, y, width , and height in pattern.
784+
*/
785+
// TODO: DEVSIX-3923 remove normalization (.toLowerCase)
786+
public static final String PATTERN_UNITS = "patternUnits".toLowerCase();
787+
783788
/**
784789
* Attribute defining the points of a polyline or polygon.
785790
*/

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

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ This file is part of the iText (R) project.
5151
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
5252
import com.itextpdf.kernel.pdf.canvas.PdfPatternCanvas;
5353
import com.itextpdf.kernel.pdf.colorspace.PdfPattern;
54+
import com.itextpdf.svg.SvgConstants;
5455
import com.itextpdf.svg.SvgConstants.Attributes;
5556
import com.itextpdf.svg.SvgConstants.Values;
5657
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
5758
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
5859
import com.itextpdf.svg.renderers.ISvgPaintServer;
5960
import com.itextpdf.svg.renderers.SvgDrawContext;
6061
import com.itextpdf.svg.utils.SvgCoordinateUtils;
62+
import com.itextpdf.svg.utils.TransformUtils;
6163

6264
import org.slf4j.Logger;
6365
import org.slf4j.LoggerFactory;
@@ -112,12 +114,16 @@ private PdfPattern.Tiling createTilingPattern(SvgDrawContext context,
112114
return null;
113115
}
114116

115-
// transform user space to target pattern rectangle origin and scale
116-
final AffineTransform patternAffineTransform = context.getCurrentCanvasTransform();
117+
// we have to consider transforming an element that use pattern in corresponding with SVG logic
118+
final AffineTransform patternMatrixTransform = context.getCurrentCanvasTransform();
119+
120+
patternMatrixTransform.concatenate(getPatternTransform());
121+
117122
if (isObjectBoundingBoxInPatternUnits) {
118-
patternAffineTransform.concatenate(getTransformToUserSpaceOnUse(objectBoundingBox));
123+
patternMatrixTransform.concatenate(getTransformToUserSpaceOnUse(objectBoundingBox));
119124
}
120-
patternAffineTransform.translate(originalPatternRectangle.getX(), originalPatternRectangle.getY());
125+
126+
patternMatrixTransform.translate(originalPatternRectangle.getX(), originalPatternRectangle.getY());
121127

122128
final float[] viewBoxValues = getViewBoxValues();
123129
Rectangle bbox;
@@ -134,7 +140,7 @@ private PdfPattern.Tiling createTilingPattern(SvgDrawContext context,
134140
scaleX = CONVERT_COEFF / objectBoundingBox.getWidth();
135141
scaleY = CONVERT_COEFF / objectBoundingBox.getHeight();
136142
}
137-
patternAffineTransform.scale(scaleX, scaleY);
143+
patternMatrixTransform.scale(scaleX, scaleY);
138144
xStep /= scaleX;
139145
yStep /= scaleY;
140146
}
@@ -149,30 +155,30 @@ private PdfPattern.Tiling createTilingPattern(SvgDrawContext context,
149155
if (isObjectBoundingBoxInPatternUnits) {
150156
double scaleX = CONVERT_COEFF / objectBoundingBox.getWidth();
151157
double scaleY = CONVERT_COEFF / objectBoundingBox.getHeight();
152-
patternAffineTransform.scale(scaleX, scaleY);
158+
patternMatrixTransform.scale(scaleX, scaleY);
153159
xStep /= scaleX;
154160
yStep /= scaleY;
155161
}
156162

157163
Rectangle viewBox = new Rectangle(viewBoxValues[0], viewBoxValues[1], viewBoxValues[2], viewBoxValues[3]);
158164
Rectangle appliedViewBox = calculateAppliedViewBox(viewBox, xStep, yStep);
159165

160-
patternAffineTransform.translate(appliedViewBox.getX(), appliedViewBox.getY());
166+
patternMatrixTransform.translate(appliedViewBox.getX(), appliedViewBox.getY());
161167

162168
double scaleX = (double) appliedViewBox.getWidth() / (double) viewBox.getWidth();
163169
double scaleY = (double) appliedViewBox.getHeight() / (double) viewBox.getHeight();
164-
patternAffineTransform.scale(scaleX, scaleY);
170+
patternMatrixTransform.scale(scaleX, scaleY);
165171
xStep /= scaleX;
166172
yStep /= scaleY;
167173

168-
patternAffineTransform.translate(-viewBox.getX(), -viewBox.getY());
174+
patternMatrixTransform.translate(-viewBox.getX(), -viewBox.getY());
169175

170176
double bboxXOriginal = viewBox.getX() - appliedViewBox.getX() / scaleX;
171177
double bboxYOriginal = viewBox.getY() - appliedViewBox.getY() / scaleY;
172178
bbox = new Rectangle((float) bboxXOriginal, (float) bboxYOriginal, (float) xStep, (float) yStep);
173179
}
174180

175-
return createColoredTilingPatternInstance(patternAffineTransform, bbox, xStep, yStep);
181+
return createColoredTilingPatternInstance(patternMatrixTransform, bbox, xStep, yStep);
176182
}
177183

178184
private Rectangle calculateAppliedViewBox(Rectangle viewBox, double xStep, double yStep) {
@@ -308,4 +314,12 @@ private static boolean isViewBoxInvalid(float[] viewBoxValues) {
308314
return false;
309315
}
310316
}
317+
318+
private AffineTransform getPatternTransform() {
319+
String patternTransform = getAttribute(SvgConstants.Attributes.PATTERN_TRANSFORM);
320+
if (patternTransform != null && !patternTransform.isEmpty()) {
321+
return TransformUtils.parseTransform(patternTransform);
322+
}
323+
return new AffineTransform();
324+
}
311325
}

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,23 +218,60 @@ public void viewBoxAndAbsoluteCoordinatesTest() throws IOException, InterruptedE
218218
}
219219

220220
@Test
221-
//TODO DEVSIX-4811 support 'patternTransform' attribute for SVG pattern element
222221
public void patternTransformSimpleTest() throws IOException, InterruptedException {
223222
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformSimple");
224223
}
225224

226225
@Test
227-
//TODO DEVSIX-4811 support 'patternTransform' attribute for SVG pattern element
228226
public void patternTransformUnitsObjectBoundingBoxTest() throws IOException, InterruptedException {
229227
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformUnitsObjectBoundingBox");
230228
}
231229

232230
@Test
233-
//TODO DEVSIX-4811 support 'patternTransform' attribute for SVG pattern element
234231
public void patternTransformUnitsUserSpaceOnUseTest() throws IOException, InterruptedException {
235232
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformUnitsUserSpaceOnUse");
236233
}
237234

235+
@Test
236+
public void patternTransformObjBoundingBoxTest() throws IOException, InterruptedException {
237+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformObjBoundingBox");
238+
}
239+
240+
@Test
241+
public void patternTransformUserSpaceOnUseTest() throws IOException, InterruptedException {
242+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformUserSpaceOnUse");
243+
}
244+
245+
@Test
246+
public void patternTransformMixed1Test() throws IOException, InterruptedException {
247+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformMixed1");
248+
}
249+
250+
@Test
251+
public void patternTransformMixed2Test() throws IOException, InterruptedException {
252+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformMixed2");
253+
}
254+
255+
@Test
256+
public void patternTransformViewBoxUsrSpaceOnUseTest() throws IOException, InterruptedException {
257+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformViewBoxUsrSpaceOnUse");
258+
}
259+
260+
@Test
261+
public void patternTransformViewBoxObjBoundBoxTest() throws IOException, InterruptedException {
262+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformViewBoxObjBoundBox");
263+
}
264+
265+
@Test
266+
public void patternTransformElementTransformTest() throws IOException, InterruptedException {
267+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformElementTransform", PageSize.A8);
268+
}
269+
270+
@Test
271+
public void patternTransformTranslateTest() throws IOException, InterruptedException {
272+
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "patternTransformTranslate");
273+
}
274+
238275
@Test
239276
public void preserveAspectRatioXMaxYMidMeetTest() throws IOException, InterruptedException {
240277
convertAndCompare(SOURCE_FOLDER, DESTINATION_FOLDER, "preserveAspectRatioXMaxYMidMeet");

0 commit comments

Comments
 (0)