Skip to content

Commit 26e9ecc

Browse files
ar3emiText-CI
authored andcommitted
Add initial support of SVG pattern element
Support SVG pattern element in the case when userSpaceOnUse value is set for 'patternContentUnits' and 'patternUnits' attributes DEVSIX-4737
1 parent 86e293a commit 26e9ecc

File tree

53 files changed

+630
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+630
-141
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,19 @@ 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+
657670
/**
658671
* Close Path Operator.
659672
*/
@@ -965,13 +978,21 @@ public static final class Values {
965978

966979
/**
967980
* Value representing the gradient units relation "objectBoundingBox".
981+
*
982+
* @deprecated it will be removed in the 7.2 update.
983+
* Use {@link SvgConstants.Values#OBJECT_BOUNDING_BOX} instead.
968984
*/
969-
public static final String GRADIENT_UNITS_OBJECT_BOUNDING_BOX = "objectBoundingBox";
985+
@Deprecated
986+
public static final String GRADIENT_UNITS_OBJECT_BOUNDING_BOX = Values.OBJECT_BOUNDING_BOX;
970987

971988
/**
972989
* Value representing the gradient units relation "userSpaceOnUse".
990+
*
991+
* @deprecated it will be removed in the 7.2 update.
992+
* Use {@link SvgConstants.Values#USER_SPACE_ON_USE} instead.
973993
*/
974-
public static final String GRADIENT_UNITS_USER_SPACE_ON_USE = "userSpaceOnUse";
994+
@Deprecated
995+
public static final String GRADIENT_UNITS_USER_SPACE_ON_USE = Values.USER_SPACE_ON_USE;
975996

976997
/**
977998
* Value representing the meet for preserve aspect ratio calculations.
@@ -983,6 +1004,11 @@ public static final class Values {
9831004
*/
9841005
public static final String NONE = "none";
9851006

1007+
/**
1008+
* Value representing the units relation "objectBoundingBox".
1009+
*/
1010+
public static final String OBJECT_BOUNDING_BOX = "objectBoundingBox";
1011+
9861012
/**
9871013
* The value representing slice for the preserve aspect ratio calculations;
9881014
*/
@@ -1029,6 +1055,11 @@ public static final class Values {
10291055
*/
10301056
public static final String STROKEWIDTH = "strokeWidth";
10311057

1058+
/**
1059+
* Value representing the units relation "userSpaceOnUse".
1060+
*/
1061+
public static final String USER_SPACE_ON_USE = "userSpaceOnUse";
1062+
10321063
/**
10331064
* Value representing how to align when scaling.
10341065
*/

svg/src/main/java/com/itextpdf/svg/css/impl/SvgStyleResolver.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ public class SvgStyleResolver implements ICssResolver {
105105
public static final Set<IStyleInheritance> INHERITANCE_RULES = Collections.unmodifiableSet(new HashSet<>(
106106
Arrays.asList((IStyleInheritance) new CssInheritance(), (IStyleInheritance) new SvgAttributeInheritance())));
107107

108+
private static final String[] ELEMENTS_INHERITING_PARENT_STYLES = new String[] {Tags.MARKER, Tags.LINEAR_GRADIENT,
109+
Tags.PATTERN};
110+
108111
private static final float DEFAULT_FONT_SIZE = CssDimensionParsingUtils.parseAbsoluteFontSize(
109112
CssDefaults.getDefaultValue(SvgConstants.Attributes.FONT_SIZE));
113+
110114
private static final Logger LOGGER = LoggerFactory.getLogger(SvgStyleResolver.class);
111115

112116
private CssStyleSheet css;
@@ -274,11 +278,11 @@ public Map<String, String> resolveNativeStyles(INode node, AbstractCssContext cs
274278
}
275279

276280
private static boolean onlyNativeStylesShouldBeResolved(IElementNode element) {
277-
if (Tags.MARKER.equals(element.name()) || SvgStyleResolver.isElementNested(element, Tags.MARKER)) {
278-
return false;
279-
}
280-
if (Tags.LINEAR_GRADIENT.equals(element.name())) {
281-
return false;
281+
for (final String elementInheritingParentStyles : ELEMENTS_INHERITING_PARENT_STYLES) {
282+
if (elementInheritingParentStyles.equals(element.name())
283+
|| SvgStyleResolver.isElementNested(element, elementInheritingParentStyles)) {
284+
return false;
285+
}
282286
}
283287
return SvgStyleResolver.isElementNested(element, Tags.DEFS);
284288
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public final class SvgLogMessageConstant {
8080
public static final String NAMED_OBJECT_NULL = "A named object can't be null.";
8181
public static final String NONINVERTIBLE_TRANSFORMATION_MATRIX_USED_IN_CLIP_PATH = "Non-invertible transformation matrix was used in a clipping path context. Clipped elements may show undefined behavior.";
8282
public static final String NOROOT = "No root found";
83+
public static final String PATTERN_INVALID_PATTERN_UNITS_LOG = "Could not recognize patternUnits value {0}";
84+
public static final String PATTERN_INVALID_PATTERN_CONTENT_UNITS_LOG = "Could not recognize patternContentUnits value {0}";
8385
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.";
8486
public static final String PARAMETER_CANNOT_BE_NULL = "Parameters for this method cannot be null.";
8587
public static final String POINTS_ATTRIBUTE_INVALID_LIST = "Points attribute {0} on polyline tag does not contain a valid set of points";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2020 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;
44+
45+
import com.itextpdf.kernel.colors.Color;
46+
import com.itextpdf.kernel.geom.Rectangle;
47+
48+
/**
49+
* Interface for working with paint servers. These are the elements that are referenced from the fill or stroke of an
50+
* object.
51+
*/
52+
public interface ISvgPaintServer extends INoDrawSvgNodeRenderer {
53+
/**
54+
* Creates the {@link Color} that represents the corresponding paint server for specified object box.
55+
*
56+
* @param context the current svg draw context
57+
* @param objectBoundingBox the coloring object bounding box without any adjustments
58+
* (additional stroke width or others)
59+
* @param objectBoundingBoxMargin the objectBoundingBoxMargin of the object bounding box
60+
* to be colored (for example - the part of stroke width
61+
* that exceeds the object bounding box, i.e. the half of stroke
62+
* width value)
63+
* @param parentOpacity current parent opacity modifier
64+
* @return the created color
65+
*/
66+
Color createColor(SvgDrawContext context, Rectangle objectBoundingBox, float objectBoundingBoxMargin,
67+
float parentOpacity);
68+
}

svg/src/main/java/com/itextpdf/svg/renderers/SvgDrawContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public class SvgDrawContext {
7070
private final Deque<PdfCanvas> canvases = new LinkedList<>();
7171
private final Deque<Rectangle> viewports = new LinkedList<>();
7272
private final Stack<String> useIds = new Stack<>();
73+
private final Stack<String> patternIds = new Stack<>();
7374
private final ResourceResolver resourceResolver;
7475
private final FontProvider fontProvider;
7576
private FontSet tempFonts;
@@ -376,4 +377,27 @@ public SvgCssContext getCssContext() {
376377
public void setCssContext(SvgCssContext cssContext) {
377378
this.cssContext = cssContext;
378379
}
380+
381+
/**
382+
* Add pattern id to stack. Check if the id is already in the stack.
383+
* If it is, then return {@code false} and not add, if it is not - add and return {@code true}.
384+
*
385+
* @param patternId pattern id
386+
* @return {@code true} if pattern id was not on the stack and was pushed; {@code false} if it is on the stack
387+
*/
388+
public boolean pushPatternId(String patternId) {
389+
if (this.patternIds.contains(patternId)) {
390+
return false;
391+
} else {
392+
this.patternIds.push(patternId);
393+
return true;
394+
}
395+
}
396+
397+
/**
398+
* Pops the last template id from the stack.
399+
*/
400+
public void popPatternId() {
401+
this.patternIds.pop();
402+
}
379403
}

svg/src/main/java/com/itextpdf/svg/renderers/factories/DefaultSvgNodeRendererMapper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,17 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.svg.SvgConstants;
4646
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
47+
import com.itextpdf.svg.renderers.impl.CircleSvgNodeRenderer;
4748
import com.itextpdf.svg.renderers.impl.ClipPathSvgNodeRenderer;
4849
import com.itextpdf.svg.renderers.impl.DefsSvgNodeRenderer;
49-
import com.itextpdf.svg.renderers.impl.GroupSvgNodeRenderer;
50-
import com.itextpdf.svg.renderers.impl.CircleSvgNodeRenderer;
5150
import com.itextpdf.svg.renderers.impl.EllipseSvgNodeRenderer;
51+
import com.itextpdf.svg.renderers.impl.GroupSvgNodeRenderer;
5252
import com.itextpdf.svg.renderers.impl.ImageSvgNodeRenderer;
5353
import com.itextpdf.svg.renderers.impl.LineSvgNodeRenderer;
5454
import com.itextpdf.svg.renderers.impl.LinearGradientSvgNodeRenderer;
5555
import com.itextpdf.svg.renderers.impl.MarkerSvgNodeRenderer;
5656
import com.itextpdf.svg.renderers.impl.PathSvgNodeRenderer;
57+
import com.itextpdf.svg.renderers.impl.PatternSvgNodeRenderer;
5758
import com.itextpdf.svg.renderers.impl.PolygonSvgNodeRenderer;
5859
import com.itextpdf.svg.renderers.impl.PolylineSvgNodeRenderer;
5960
import com.itextpdf.svg.renderers.impl.RectangleSvgNodeRenderer;
@@ -97,6 +98,7 @@ public Map<String, Class<? extends ISvgNodeRenderer>> getMapping() {
9798
result.put(SvgConstants.Tags.LINE, LineSvgNodeRenderer.class);
9899
result.put(SvgConstants.Tags.LINEAR_GRADIENT, LinearGradientSvgNodeRenderer.class);
99100
result.put(SvgConstants.Tags.MARKER, MarkerSvgNodeRenderer.class);
101+
result.put(SvgConstants.Tags.PATTERN, PatternSvgNodeRenderer.class);
100102
result.put(SvgConstants.Tags.PATH, PathSvgNodeRenderer.class);
101103
result.put(SvgConstants.Tags.POLYGON, PolygonSvgNodeRenderer.class);
102104
result.put(SvgConstants.Tags.POLYLINE, PolylineSvgNodeRenderer.class);
@@ -167,8 +169,6 @@ public Collection<String> getIgnoredTags() {
167169
ignored.add(SvgConstants.Tags.METADATA);
168170
ignored.add(SvgConstants.Tags.MISSING_GLYPH);
169171

170-
ignored.add(SvgConstants.Tags.PATTERN);
171-
172172
ignored.add(SvgConstants.Tags.RADIAL_GRADIENT);
173173

174174
ignored.add(SvgConstants.Tags.STYLE);

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

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,50 +23,32 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.svg.renderers.impl;
2424

2525
import com.itextpdf.io.util.MessageFormatUtil;
26-
import com.itextpdf.kernel.colors.Color;
27-
import com.itextpdf.kernel.geom.AffineTransform;
28-
import com.itextpdf.kernel.geom.Rectangle;
2926
import com.itextpdf.kernel.colors.gradients.GradientSpreadMethod;
27+
import com.itextpdf.kernel.geom.AffineTransform;
3028
import com.itextpdf.svg.SvgConstants.Attributes;
3129
import com.itextpdf.svg.SvgConstants.Values;
3230
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
3331
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
32+
import com.itextpdf.svg.renderers.ISvgPaintServer;
3433
import com.itextpdf.svg.renderers.SvgDrawContext;
3534
import com.itextpdf.svg.utils.TransformUtils;
3635

37-
import org.slf4j.LoggerFactory;
38-
3936
import java.util.ArrayList;
4037
import java.util.List;
38+
import org.slf4j.LoggerFactory;
4139

4240
/**
4341
* {@link ISvgNodeRenderer} abstract implementation for gradient tags
4442
* (&lt;linearGradient&gt;, &lt;radialGradient&gt;).
4543
*/
46-
public abstract class AbstractGradientSvgNodeRenderer extends NoDrawOperationSvgNodeRenderer {
44+
public abstract class AbstractGradientSvgNodeRenderer extends NoDrawOperationSvgNodeRenderer implements
45+
ISvgPaintServer {
4746

4847
@Override
4948
protected void doDraw(SvgDrawContext context) {
5049
throw new UnsupportedOperationException(SvgLogMessageConstant.DRAW_NO_DRAW);
5150
}
5251

53-
/**
54-
* Creates the {@link Color} that represents the corresponding gradient for specified object box
55-
*
56-
* @param context the current svg draw context
57-
* @param objectBoundingBox the coloring object bounding box without any adjustments
58-
* (additional stroke width or others)
59-
* @param objectBoundingBoxMargin the objectBoundingBoxMargin of the object bounding box
60-
* to be colored (for example - the part of stroke width
61-
* that exceeds the object bounding box, i.e. the half of stroke
62-
* width value)
63-
* @param parentOpacity current parent opacity modifier
64-
* @return the created color
65-
*/
66-
public abstract Color createColor(SvgDrawContext context, Rectangle objectBoundingBox,
67-
float objectBoundingBoxMargin,
68-
float parentOpacity);
69-
7052
/**
7153
* Checks whether the gradient units values are on user space on use or object bounding box
7254
*
@@ -75,9 +57,9 @@ public abstract Color createColor(SvgDrawContext context, Rectangle objectBoundi
7557
*/
7658
protected boolean isObjectBoundingBoxUnits() {
7759
String gradientUnits = getAttribute(Attributes.GRADIENT_UNITS);
78-
if (Values.GRADIENT_UNITS_USER_SPACE_ON_USE.equals(gradientUnits)) {
60+
if (Values.USER_SPACE_ON_USE.equals(gradientUnits)) {
7961
return false;
80-
} else if (gradientUnits != null && !Values.GRADIENT_UNITS_OBJECT_BOUNDING_BOX.equals(gradientUnits)) {
62+
} else if (gradientUnits != null && !Values.OBJECT_BOUNDING_BOX.equals(gradientUnits)) {
8163
LoggerFactory.getLogger(this.getClass()).warn(MessageFormatUtil.format(
8264
SvgLogMessageConstant.GRADIENT_INVALID_GRADIENT_UNITS_LOG, gradientUnits));
8365
}

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ This file is part of the iText (R) project.
5353
import com.itextpdf.layout.property.UnitValue;
5454
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer;
5555
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer.Token;
56-
import com.itextpdf.styledxmlparser.css.util.CssTypesValidationUtils;
5756
import com.itextpdf.styledxmlparser.css.util.CssDimensionParsingUtils;
57+
import com.itextpdf.styledxmlparser.css.util.CssTypesValidationUtils;
5858
import com.itextpdf.styledxmlparser.css.util.CssUtils;
5959
import com.itextpdf.svg.MarkerVertexType;
6060
import com.itextpdf.svg.SvgConstants;
6161
import com.itextpdf.svg.css.impl.SvgNodeRendererInheritanceResolver;
6262
import com.itextpdf.svg.renderers.IMarkerCapable;
6363
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
64+
import com.itextpdf.svg.renderers.ISvgPaintServer;
6465
import com.itextpdf.svg.renderers.SvgDrawContext;
6566
import com.itextpdf.svg.utils.TransformUtils;
6667

@@ -345,7 +346,6 @@ void preDraw(SvgDrawContext context) {
345346
this.doFill = !SvgConstants.Values.NONE.equalsIgnoreCase(fillRawValue);
346347

347348
if (doFill && canElementFill()) {
348-
349349
float fillOpacity = getOpacityByAttributeName(
350350
SvgConstants.Attributes.FILL_OPACITY, generalOpacity);
351351

@@ -388,7 +388,7 @@ void preDraw(SvgDrawContext context) {
388388

389389
Color strokeColor = null;
390390
TransparentColor transparentColor = getColorFromAttributeValue(
391-
context, strokeRawValue, strokeWidth / 2, strokeOpacity);
391+
context, strokeRawValue, (float) ((double) strokeWidth / 2.0), strokeOpacity);
392392
if (transparentColor != null) {
393393
strokeColor = transparentColor.getColor();
394394
strokeOpacity = transparentColor.getOpacity();
@@ -457,14 +457,9 @@ private TransparentColor getColorFromAttributeValue(SvgDrawContext context, Stri
457457
Color resolvedColor = null;
458458
float resolvedOpacity = 1;
459459
final String normalizedName = tokenValue.substring(5, tokenValue.length() - 1).trim();
460-
final ISvgNodeRenderer template = context.getNamedObject(normalizedName);
461-
// Clone template
462-
final ISvgNodeRenderer colorRenderer = template == null ? null : template.createDeepCopy();
463-
// Resolve parent inheritance
464-
SvgNodeRendererInheritanceResolver.applyInheritanceToSubTree(this, colorRenderer, context.getCssContext());
465-
466-
if (colorRenderer instanceof AbstractGradientSvgNodeRenderer) {
467-
resolvedColor = ((AbstractGradientSvgNodeRenderer) colorRenderer).createColor(
460+
final ISvgNodeRenderer colorRenderer = context.getNamedObject(normalizedName);
461+
if (colorRenderer instanceof ISvgPaintServer) {
462+
resolvedColor = ((ISvgPaintServer) colorRenderer).createColor(
468463
context, getObjectBoundingBox(context), objectBoundingBoxMargin, parentOpacity);
469464
}
470465
if (resolvedColor != null) {

0 commit comments

Comments
 (0)