Skip to content

Commit 2cb9f37

Browse files
author
Dmitry Radchuk
committed
Improve <object> and <img> svg support
DEVSIX-8829
1 parent f304486 commit 2cb9f37

24 files changed

+523
-45
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/xobject/PdfFormXObject.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,14 @@ public PdfFormXObject put(PdfName key, PdfObject value) {
343343
return this;
344344
}
345345

346+
/**
347+
* If the form xobject is relative sized. This information
348+
* is used during xobject layout to resolve it's relative size.
349+
*
350+
* @return {@code true} if the xobject is relative sized, {@code false} otherwise
351+
*/
352+
public boolean isRelativeSized() {
353+
return false;
354+
}
355+
346356
}

layout/src/main/java/com/itextpdf/layout/renderer/ImageRenderer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,8 @@ private void calculateImageDimensions(Rectangle layoutBox, AffineTransform t, Pd
494494
Float verticalScaling = this.getPropertyAsFloat(Property.VERTICAL_SCALING, 1f);
495495

496496

497-
if (xObject instanceof PdfFormXObject && width != imageWidth) {
497+
if (xObject instanceof PdfFormXObject
498+
&& (Float.compare((float) width, imageWidth) != 0 || Float.compare((float) height, imageHeight) != 0)) {
498499
horizontalScaling *= width / imageWidth;
499500
verticalScaling *= height / imageHeight;
500501
}

styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/resolver/resource/ResourceResolver.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.commons.utils.Base64;
2626
import com.itextpdf.commons.utils.MessageFormatUtil;
2727
import com.itextpdf.io.image.ImageDataFactory;
28+
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
2829
import com.itextpdf.kernel.pdf.xobject.PdfImageXObject;
2930
import com.itextpdf.kernel.pdf.xobject.PdfXObject;
3031
import com.itextpdf.styledxmlparser.logs.StyledXmlParserLogMessageConstant;
@@ -267,7 +268,10 @@ protected PdfXObject tryResolveUrlImageSource(String uri) {
267268
PdfXObject imageXObject = imageCache.getImage(imageResolvedSrc);
268269
if (imageXObject == null) {
269270
imageXObject = createImageByUrl(url);
270-
if (imageXObject != null) {
271+
//relative sized xObject can't be cached because it's internal state depends on the context
272+
boolean isAbsoluteSized = imageXObject != null && !(imageXObject instanceof PdfFormXObject
273+
&& ((PdfFormXObject)imageXObject).isRelativeSized());
274+
if (isAbsoluteSized) {
271275
imageCache.putImage(imageResolvedSrc, imageXObject);
272276
}
273277
}

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

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.svg.renderers;
2424

2525
import com.itextpdf.kernel.geom.Rectangle;
26+
import com.itextpdf.kernel.pdf.PdfArray;
27+
import com.itextpdf.kernel.pdf.PdfNumber;
2628
import com.itextpdf.layout.layout.LayoutContext;
2729
import com.itextpdf.layout.layout.LayoutResult;
30+
import com.itextpdf.layout.properties.Property;
31+
import com.itextpdf.layout.properties.UnitValue;
2832
import com.itextpdf.layout.renderer.DrawContext;
2933
import com.itextpdf.layout.renderer.ImageRenderer;
3034
import com.itextpdf.svg.SvgConstants;
@@ -50,10 +54,35 @@ public SvgImageRenderer(SvgImage image) {
5054
@Override
5155
public LayoutResult layout(LayoutContext layoutContext) {
5256
SvgImage svgImage = (SvgImage) modelElement;
57+
Rectangle layoutBox = layoutContext.getArea().getBBox();
5358
if (svgImage.getSvgImageXObject().isRelativeSized()) {
54-
calculateRelativeSizedSvgSize(svgImage, layoutContext.getArea().getBBox());
59+
calculateRelativeSizedSvgSize(svgImage, layoutBox);
60+
} else if (svgImage.getSvgImageXObject().isCreatedByObject()
61+
&& !Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT))) {
62+
63+
NullableArea retrievedArea = new NullableArea(retrieveWidth(layoutBox.getWidth()), retrieveHeight());
64+
PdfArray bbox = svgImage.getSvgImageXObject().getBBox();
65+
if (retrievedArea.width != null && retrievedArea.height != null) {
66+
bbox.set(2, new PdfNumber((double) retrievedArea.width));
67+
bbox.set(3, new PdfNumber((double) retrievedArea.height));
68+
imageWidth = (float) retrievedArea.width;
69+
imageHeight = (float) retrievedArea.height;
70+
} else if (retrievedArea.width != null) {
71+
Area bboxArea = new Area(((PdfNumber)bbox.get(2)).floatValue(), ((PdfNumber)bbox.get(3)).floatValue());
72+
double verticalScaling = (double) retrievedArea.width / bboxArea.width;
73+
bbox.set(2, new PdfNumber((double) retrievedArea.width));
74+
bbox.set(3, new PdfNumber(bboxArea.height * verticalScaling));
75+
imageWidth = (float) retrievedArea.width;
76+
imageHeight = imageHeight * (float) verticalScaling;
77+
} else if (retrievedArea.height != null) {
78+
Area bboxArea = new Area(((PdfNumber)bbox.get(2)).floatValue(), ((PdfNumber)bbox.get(3)).floatValue());
79+
double horizontalScaling = (double) retrievedArea.height / bboxArea.height;
80+
bbox.set(2, new PdfNumber(bboxArea.width * horizontalScaling));
81+
bbox.set(3, new PdfNumber((double) retrievedArea.height));
82+
imageWidth = imageWidth * (float) horizontalScaling;
83+
imageHeight = (float) retrievedArea.height;
84+
}
5585
}
56-
5786
return super.layout(layoutContext);
5887
}
5988

@@ -78,35 +107,114 @@ private void calculateRelativeSizedSvgSize(SvgImage svgImage, Rectangle layoutBo
78107
aspectRatio = viewBoxValues[2] / viewBoxValues[3];
79108
}
80109

81-
Float retrievedAreaWidth = retrieveWidth(layoutBox.getWidth());
82-
Float retrievedAreaHeight = retrieveHeight();
110+
NullableArea retrievedArea = new NullableArea(retrieveWidth(layoutBox.getWidth()), retrieveHeight());
111+
boolean preserveAspectRatioNone
112+
= Values.NONE.equals(svgRootRenderer.getAttribute(Attributes.PRESERVE_ASPECT_RATIO));
113+
Area area = new Area();
83114

84-
float areaWidth = retrievedAreaWidth == null ?
115+
area.width = retrievedArea.width == null ?
85116
(aspectRatio == null ? Values.DEFAULT_VIEWPORT_WIDTH : layoutBox.getWidth())
86-
: (float) retrievedAreaWidth;
87-
float areaHeight = retrievedAreaHeight == null ? Values.DEFAULT_VIEWPORT_HEIGHT : (float) retrievedAreaHeight;
88-
89-
float finalWidth;
90-
float finalHeight;
91-
92-
if (aspectRatio != null && (retrievedAreaHeight == null || retrievedAreaWidth == null)) {
93-
if (retrievedAreaWidth == null && retrievedAreaHeight != null) {
94-
finalHeight = areaHeight;
95-
finalWidth = (float) (finalHeight * aspectRatio);
96-
} else {
97-
finalWidth = areaWidth;
98-
finalHeight = (float) (finalWidth / aspectRatio);
99-
}
117+
: (float) retrievedArea.width;
118+
area.height = retrievedArea.height == null ?
119+
(aspectRatio == null ? Values.DEFAULT_VIEWPORT_HEIGHT : layoutBox.getHeight())
120+
: (float) retrievedArea.height;
121+
122+
UnitValue elementWidth = svgImageXObject.getElementWidth();
123+
UnitValue elementHeight = svgImageXObject.getElementHeight();
124+
125+
//For aspect ratio none we're using the default viewport instead of layoutBox to behave like a browser
126+
//But this only for <img>, for all other cases using layoutBox as a fallback
127+
Area finalArea = new Area();
128+
if (preserveAspectRatioNone && svgImageXObject.isCreatedByImg()) {
129+
finalArea.width = retrievedArea.width == null ? Values.DEFAULT_VIEWPORT_WIDTH : (float) retrievedArea.width;
130+
finalArea.height = retrievedArea.height == null ? Values.DEFAULT_VIEWPORT_HEIGHT : (float) retrievedArea.height;
100131
} else {
101-
finalWidth = areaWidth;
102-
finalHeight = areaHeight;
132+
finalArea = initMissingMetricsAndApplyAspectRatio(aspectRatio, retrievedArea,
133+
area, elementWidth, elementHeight);
103134
}
104135

105-
svgRootRenderer.setAttribute(Attributes.WIDTH, null);
106-
svgRootRenderer.setAttribute(Attributes.HEIGHT, null);
136+
if (svgImageXObject.isCreatedByImg() && viewBoxValues == null) {
137+
if (this.<UnitValue>getProperty(Property.WIDTH) == null) {
138+
this.setProperty(Property.WIDTH, UnitValue.createPointValue(finalArea.width));
139+
}
140+
if (retrieveHeight() == null) {
141+
this.setProperty(Property.HEIGHT, UnitValue.createPointValue(finalArea.height));
142+
}
143+
svgImageXObject.updateBBox(finalArea.width, finalArea.height);
144+
} else {
145+
svgRootRenderer.setAttribute(Attributes.WIDTH, null);
146+
svgRootRenderer.setAttribute(Attributes.HEIGHT, null);
147+
svgImageXObject.updateBBox(finalArea.width, finalArea.height);
148+
}
107149

108-
svgImageXObject.updateBBox(finalWidth, finalHeight);
109150
imageWidth = svgImage.getImageWidth();
110151
imageHeight = svgImage.getImageHeight();
111152
}
153+
154+
private Area initMissingMetricsAndApplyAspectRatio(Float aspectRatio, NullableArea retrievedArea,
155+
Area area, UnitValue xObjectWidth, UnitValue xObjectHeight) {
156+
Area finalArea = new Area();
157+
if (!tryToApplyAspectRatio(retrievedArea, area, finalArea, aspectRatio)) {
158+
if (xObjectWidth != null && xObjectWidth.isPointValue() && retrievedArea.width == null) {
159+
area.width = xObjectWidth.getValue();
160+
retrievedArea.width = area.width;
161+
this.setProperty(Property.WIDTH, UnitValue.createPointValue(area.width));
162+
}
163+
if (xObjectHeight != null && xObjectHeight.isPointValue() && retrievedArea.height == null) {
164+
area.height = xObjectHeight.getValue();
165+
retrievedArea.height = area.height;
166+
this.setProperty(Property.HEIGHT, UnitValue.createPointValue(area.height));
167+
}
168+
boolean isAspectRatioApplied = tryToApplyAspectRatio(retrievedArea, area, area, aspectRatio);
169+
if (!isAspectRatioApplied && aspectRatio != null && retrievedArea.height == null) {
170+
// retrievedArea.width is also null here
171+
area.height = (float) (area.width / aspectRatio);
172+
}
173+
finalArea.width = area.width;
174+
finalArea.height = area.height;
175+
}
176+
return finalArea;
177+
}
178+
179+
private static boolean tryToApplyAspectRatio(NullableArea retrievedArea, Area inputArea, Area resultArea,
180+
Float aspectRatio) {
181+
182+
if (aspectRatio == null) {
183+
return false;
184+
}
185+
if (retrievedArea.width == null && retrievedArea.height != null) {
186+
resultArea.height = inputArea.height;
187+
resultArea.width = (float) (inputArea.height * (float) aspectRatio);
188+
return true;
189+
} else if (retrievedArea.width != null && retrievedArea.height == null) {
190+
resultArea.width = inputArea.width;
191+
resultArea.height = (float) (inputArea.width / (float) aspectRatio);
192+
return true;
193+
}
194+
return false;
195+
}
196+
197+
private static class NullableArea {
198+
public Float width;
199+
public Float height;
200+
201+
public NullableArea(Float width, Float height) {
202+
this.width = width;
203+
this.height = height;
204+
}
205+
}
206+
207+
private static class Area {
208+
public float width;
209+
public float height;
210+
211+
public Area() {
212+
width = 0.0f;
213+
height = 0.0f;
214+
}
215+
public Area(float width, float height) {
216+
this.width = width;
217+
this.height = height;
218+
}
219+
}
112220
}

svg/src/main/java/com/itextpdf/svg/xobject/SvgImageXObject.java

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class SvgImageXObject extends PdfFormXObject {
4545
private final ISvgProcessorResult result;
4646
private final ResourceResolver resourceResolver;
4747
private boolean isGenerated = false;
48+
private boolean isCreatedByImg = false;
49+
private boolean isCreatedByObject = false;
4850

4951
private float em;
5052
private SvgDrawContext svgDrawContext;
@@ -83,30 +85,53 @@ public SvgImageXObject(ISvgProcessorResult result, SvgDrawContext svgContext, fl
8385
}
8486

8587
/**
86-
* If the SVG image is relative sized. This information
87-
* is used during image layouting to resolve it's relative size.
88+
* Set if SVG image is created from HTML img tag context
8889
*
89-
* @return {@code true} if the SVG image is relative sized, {@code false} otherwise
90+
* @param isCreatedByImg true if object is created from HTML img tag, false otherwise
91+
*/
92+
public void setIsCreatedByImg(boolean isCreatedByImg) {
93+
this.isCreatedByImg = isCreatedByImg;
94+
}
95+
96+
/**
97+
* Check if SVG image is created from HTML img tag context
9098
*
91-
* @see #updateBBox(Float, Float)
92-
* @see #SvgImageXObject(ISvgProcessorResult, SvgDrawContext, float, PdfDocument)
99+
* @return true if object is created from HTML img tag, false otherwise
93100
*/
94-
public boolean isRelativeSized() {
95-
return isRelativeSized;
101+
public boolean isCreatedByImg() {
102+
return isCreatedByImg;
96103
}
97104

98105
/**
99-
* Sets if the SVG image is relative sized. This information
106+
* Set if SVG image is created from HTML object tag context
107+
*
108+
* @param isCreatedByObject true if object is created from HTML object tag, false otherwise
109+
*/
110+
public void setIsCreatedByObject(boolean isCreatedByObject) {
111+
this.isCreatedByObject = isCreatedByObject;
112+
}
113+
114+
/**
115+
* Check if SVG image is created from HTML object tag context
116+
*
117+
* @return true if object is created from HTML object tag, false otherwise
118+
*/
119+
public boolean isCreatedByObject() {
120+
return isCreatedByObject;
121+
}
122+
123+
/**
124+
* If the SVG image is relative sized. This information
100125
* is used during image layouting to resolve it's relative size.
101126
*
102-
* @param relativeSized {@code true} if the SVG image is relative sized, {@code false} otherwise
127+
* @return {@code true} if the SVG image is relative sized, {@code false} otherwise
103128
*
104-
* @see #updateBBox(Float, Float)
129+
* @see #updateBBox(float, float)
105130
* @see #SvgImageXObject(ISvgProcessorResult, SvgDrawContext, float, PdfDocument)
106131
*/
107-
public void setRelativeSized(boolean relativeSized) {
108-
// TODO DEVSIX-8829 remove/deprecate this method after ticket will be done
109-
isRelativeSized = relativeSized;
132+
@Override
133+
public boolean isRelativeSized() {
134+
return isRelativeSized;
110135
}
111136

112137
/**
@@ -159,11 +184,8 @@ public void generate(PdfDocument document) {
159184
* @param areaWidth the area width where SVG image will be drawn
160185
* @param areaHeight the area height where SVG image will be drawn
161186
*/
162-
public void updateBBox(Float areaWidth, Float areaHeight) {
163-
// TODO DEVSIX-8829 change parameters to float, not Float
164-
if (areaWidth != null && areaHeight != null) {
165-
svgDrawContext.setCustomViewport(new Rectangle((float) areaWidth, (float) areaHeight));
166-
}
187+
public void updateBBox(float areaWidth, float areaHeight) {
188+
svgDrawContext.setCustomViewport(new Rectangle(areaWidth, areaHeight));
167189
Rectangle bbox = SvgCssUtils.extractWidthAndHeight(result.getRootRenderer(), em, svgDrawContext);
168190
setBBox(new PdfArray(bbox));
169191
}

0 commit comments

Comments
 (0)