Skip to content

Commit 91d4fb8

Browse files
ar3emBezrukovM
authored andcommitted
Add calculating path and text bounding box in SVG
DEVSIX-4018
1 parent e791447 commit 91d4fb8

File tree

119 files changed

+1801
-184
lines changed

Some content is hidden

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

119 files changed

+1801
-184
lines changed

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

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,27 +1051,26 @@ public IRenderer getNextRenderer() {
10511051
return new TextRenderer((Text) modelElement);
10521052
}
10531053

1054-
List<int[]> getReversedRanges() {
1055-
return reversedRanges;
1056-
}
1057-
1058-
List<int[]> initReversedRanges() {
1059-
if (reversedRanges == null) {
1060-
reversedRanges = new ArrayList<>();
1061-
}
1062-
return reversedRanges;
1063-
}
1064-
1065-
TextRenderer removeReversedRanges() {
1066-
reversedRanges = null;
1067-
return this;
1068-
}
1069-
1070-
static float[] calculateAscenderDescender(PdfFont font) {
1054+
/**
1055+
* Get ascender and descender from font metrics.
1056+
* If these values are obtained from typo metrics they are normalized with a scale coefficient.
1057+
*
1058+
* @param font from which metrics will be extracted
1059+
* @return array in which the first element is an ascender and the second is a descender
1060+
*/
1061+
public static float[] calculateAscenderDescender(PdfFont font) {
10711062
return calculateAscenderDescender(font, RenderingMode.DEFAULT_LAYOUT_MODE);
10721063
}
10731064

1074-
static float[] calculateAscenderDescender(PdfFont font, RenderingMode mode) {
1065+
/**
1066+
* Get ascender and descender from font metrics.
1067+
* In RenderingMode.DEFAULT_LAYOUT_MODE if these values are obtained from typo metrics they are normalized with a scale coefficient.
1068+
*
1069+
* @param font from which metrics will be extracted
1070+
* @param mode mode in which metrics will be obtained. Impact on the use of scale coefficient
1071+
* @return array in which the first element is an ascender and the second is a descender
1072+
*/
1073+
public static float[] calculateAscenderDescender(PdfFont font, RenderingMode mode) {
10751074
FontMetrics fontMetrics = font.getFontProgram().getFontMetrics();
10761075
float ascender;
10771076
float descender;
@@ -1080,14 +1079,31 @@ static float[] calculateAscenderDescender(PdfFont font, RenderingMode mode) {
10801079
usedTypoAscenderScaleCoeff = 1;
10811080
}
10821081
if (fontMetrics.getWinAscender() == 0 || fontMetrics.getWinDescender() == 0 ||
1083-
fontMetrics.getTypoAscender() == fontMetrics.getWinAscender() && fontMetrics.getTypoDescender() == fontMetrics.getWinDescender()) {
1082+
fontMetrics.getTypoAscender() == fontMetrics.getWinAscender()
1083+
&& fontMetrics.getTypoDescender() == fontMetrics.getWinDescender()) {
10841084
ascender = fontMetrics.getTypoAscender() * usedTypoAscenderScaleCoeff;
10851085
descender = fontMetrics.getTypoDescender() * usedTypoAscenderScaleCoeff;
10861086
} else {
10871087
ascender = fontMetrics.getWinAscender();
10881088
descender = fontMetrics.getWinDescender();
10891089
}
1090-
return new float[]{ascender, descender};
1090+
return new float[] {ascender, descender};
1091+
}
1092+
1093+
List<int[]> getReversedRanges() {
1094+
return reversedRanges;
1095+
}
1096+
1097+
List<int[]> initReversedRanges() {
1098+
if (reversedRanges == null) {
1099+
reversedRanges = new ArrayList<>();
1100+
}
1101+
return reversedRanges;
1102+
}
1103+
1104+
TextRenderer removeReversedRanges() {
1105+
reversedRanges = null;
1106+
return this;
10911107
}
10921108

10931109
private TextRenderer[] splitIgnoreFirstNewLine(int currentTextPos) {

styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/util/CssUtils.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ public static float parseRelativeValue(final String relativeValue, final float b
379379
* @return the unit value
380380
*/
381381
public static UnitValue parseLengthValueToPt(final String value, final float emValue, final float remValue) {
382+
// TODO (DEVSIX-3596) Add support of 'lh' 'ch' units and viewport-relative units
382383
if (isMetricValue(value) || isNumericValue(value)) {
383384
return new UnitValue(UnitValue.POINT, parseAbsoluteLength(value));
384385
} else if (value != null && value.endsWith(CommonCssConstants.PERCENTAGE)) {
@@ -770,6 +771,46 @@ public static Range parseUnicodeRange(String unicodeRange) {
770771
return builder.create();
771772
}
772773

774+
/**
775+
* Convert given point value to a pixel value. 1 px is 0.75 pts.
776+
*
777+
* @param pts float value to be converted to pixels
778+
* @return float converted value pts/0.75f
779+
*/
780+
public static float convertPtsToPx(float pts) {
781+
return pts / 0.75f;
782+
}
783+
784+
/**
785+
* Convert given point value to a pixel value. 1 px is 0.75 pts.
786+
*
787+
* @param pts double value to be converted to pixels
788+
* @return double converted value pts/0.75
789+
*/
790+
public static double convertPtsToPx(double pts) {
791+
return pts / 0.75;
792+
}
793+
794+
/**
795+
* Convert given point value to a point value. 1 px is 0.75 pts.
796+
*
797+
* @param px float value to be converted to pixels
798+
* @return float converted value px*0.75
799+
*/
800+
public static float convertPxToPts(float px) {
801+
return px * 0.75f;
802+
}
803+
804+
/**
805+
* Convert given point value to a point value. 1 px is 0.75 pts.
806+
*
807+
* @param px double value to be converted to pixels
808+
* @return double converted value px*0.75
809+
*/
810+
public static double convertPxToPts(double px) {
811+
return px * 0.75;
812+
}
813+
773814
private static boolean addRange(RangeBuilder builder, String range) {
774815
range = range.trim();
775816
if (range.matches("[uU]\\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})?")) {

svg/src/main/java/com/itextpdf/svg/converter/SvgConverter.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ This file is part of the iText (R) project.
7171
import com.itextpdf.svg.renderers.SvgDrawContext;
7272
import com.itextpdf.svg.renderers.impl.PdfRootSvgNodeRenderer;
7373
import com.itextpdf.svg.utils.SvgCssUtils;
74-
import org.slf4j.Logger;
75-
import org.slf4j.LoggerFactory;
7674

7775
import java.io.File;
7876
import java.io.FileInputStream;
@@ -81,6 +79,8 @@ This file is part of the iText (R) project.
8179
import java.io.InputStream;
8280
import java.io.OutputStream;
8381
import java.util.List;
82+
import org.slf4j.Logger;
83+
import org.slf4j.LoggerFactory;
8484

8585
/**
8686
* This is the main container class for static methods that do high-level
@@ -573,7 +573,7 @@ public static void createPdf(InputStream svgStream, OutputStream pdfDest, ISvgCo
573573
resourceResolver = new ResourceResolver(baseUri);
574574
}
575575
SvgDrawContext drawContext =
576-
new SvgDrawContext(resourceResolver, processorResult.getFontProvider());
576+
new SvgDrawContext(resourceResolver, processorResult.getFontProvider(), processorResult.getRootRenderer());
577577

578578
drawContext.addNamedObjects(processorResult.getNamedObjects());
579579
//Add temp fonts
@@ -680,9 +680,11 @@ public static PdfFormXObject convertToXObject(InputStream stream, PdfDocument do
680680
}
681681

682682
//Private converter for unification
683-
private static PdfFormXObject convertToXObject(ISvgProcessorResult processorResult, PdfDocument document, ISvgConverterProperties props) {
683+
private static PdfFormXObject convertToXObject(ISvgProcessorResult processorResult, PdfDocument document,
684+
ISvgConverterProperties props) {
684685
ResourceResolver resourceResolver = getResourceResolver(processorResult, props);
685-
SvgDrawContext drawContext = new SvgDrawContext(resourceResolver, processorResult.getFontProvider());
686+
SvgDrawContext drawContext = new SvgDrawContext(resourceResolver, processorResult.getFontProvider(),
687+
processorResult.getRootRenderer());
686688
drawContext.setTempFonts(processorResult.getTempFonts());
687689
drawContext.addNamedObjects(processorResult.getNamedObjects());
688690
return convertToXObject(processorResult.getRootRenderer(), document, drawContext);

svg/src/main/java/com/itextpdf/svg/processors/impl/DefaultSvgProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ This file is part of the iText (R) project.
4747
import com.itextpdf.styledxmlparser.node.INode;
4848
import com.itextpdf.styledxmlparser.node.ITextNode;
4949
import com.itextpdf.svg.SvgConstants;
50+
import com.itextpdf.svg.SvgConstants.Tags;
5051
import com.itextpdf.svg.css.SvgCssContext;
5152
import com.itextpdf.svg.css.impl.SvgStyleResolver;
5253
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
@@ -239,7 +240,8 @@ && onlyNativeStylesShouldBeResolved(element)) {
239240
}
240241

241242
private static boolean onlyNativeStylesShouldBeResolved(IElementNode element) {
242-
return !SvgConstants.Tags.MARKER.equals(element.name())
243+
return !Tags.LINEAR_GRADIENT.equals(element.name())
244+
&& !SvgConstants.Tags.MARKER.equals(element.name())
243245
&& isElementNested(element, SvgConstants.Tags.DEFS)
244246
&& !isElementNested(element, SvgConstants.Tags.MARKER);
245247
}

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ This file is part of the iText (R) project.
4848
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
4949
import com.itextpdf.layout.font.FontProvider;
5050
import com.itextpdf.layout.font.FontSet;
51+
import com.itextpdf.styledxmlparser.css.CommonCssConstants;
52+
import com.itextpdf.styledxmlparser.css.resolve.CssDefaults;
53+
import com.itextpdf.styledxmlparser.css.util.CssUtils;
5154
import com.itextpdf.styledxmlparser.resolver.font.BasicFontProvider;
5255
import com.itextpdf.styledxmlparser.resolver.resource.ResourceResolver;
5356
import com.itextpdf.svg.exceptions.SvgLogMessageConstant;
5457
import com.itextpdf.svg.exceptions.SvgProcessingException;
58+
import com.itextpdf.svg.renderers.impl.AbstractSvgNodeRenderer;
5559

5660
import java.util.Deque;
5761
import java.util.HashMap;
@@ -72,15 +76,44 @@ public class SvgDrawContext {
7276
private ResourceResolver resourceResolver;
7377
private FontProvider fontProvider;
7478
private FontSet tempFonts;
79+
// value of root element font-size in points
80+
private float remValue;
7581

7682
private AffineTransform lastTextTransform = new AffineTransform();
7783
private float textMove[] = new float[]{0.0f, 0.0f};
7884

85+
/**
86+
* Create an instance of the context that is used to store information when converting SVG.
87+
*
88+
* @param resourceResolver instance of {@link ResourceResolver}
89+
* @param fontProvider instance of {@link FontProvider}
90+
*/
7991
public SvgDrawContext(ResourceResolver resourceResolver, FontProvider fontProvider) {
80-
if (resourceResolver == null) resourceResolver = new ResourceResolver("");
92+
this (resourceResolver, fontProvider, null);
93+
}
94+
95+
/**
96+
* Create an instance of the context that is used to store information when converting SVG.
97+
*
98+
* @param resourceResolver instance of {@link ResourceResolver}
99+
* @param fontProvider instance of {@link FontProvider}
100+
* @param svgRootRenderer svg element that is root for current file
101+
*/
102+
public SvgDrawContext(ResourceResolver resourceResolver, FontProvider fontProvider, ISvgNodeRenderer svgRootRenderer) {
103+
if (resourceResolver == null) {
104+
resourceResolver = new ResourceResolver("");
105+
}
81106
this.resourceResolver = resourceResolver;
82-
if (fontProvider == null) fontProvider = new BasicFontProvider();
107+
if (fontProvider == null) {
108+
fontProvider = new BasicFontProvider();
109+
}
83110
this.fontProvider = fontProvider;
111+
if (svgRootRenderer instanceof AbstractSvgNodeRenderer) {
112+
remValue = ((AbstractSvgNodeRenderer) svgRootRenderer).getCurrentFontSize();
113+
} else {
114+
// default font-size value
115+
remValue = CssUtils.parseAbsoluteFontSize(CssDefaults.getDefaultValue(CommonCssConstants.FONT_SIZE));
116+
}
84117
}
85118

86119
/**
@@ -320,4 +353,13 @@ public AffineTransform getCurrentCanvasTransform() {
320353
}
321354
return new AffineTransform();
322355
}
356+
357+
/**
358+
* Return the value of root svg element font-size
359+
*
360+
* @return rem value
361+
*/
362+
public float getRemValue() {
363+
return remValue;
364+
}
323365
}

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

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ This file is part of the iText (R) project.
5050
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
5151
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
5252
import com.itextpdf.layout.property.TransparentColor;
53+
import com.itextpdf.layout.property.UnitValue;
5354
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer;
5455
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer.Token;
5556
import com.itextpdf.styledxmlparser.css.parse.CssDeclarationValueTokenizer.TokenType;
@@ -60,6 +61,7 @@ This file is part of the iText (R) project.
6061
import com.itextpdf.svg.renderers.IMarkerCapable;
6162
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
6263
import com.itextpdf.svg.renderers.SvgDrawContext;
64+
import com.itextpdf.svg.utils.SvgCssUtils;
6365
import com.itextpdf.svg.utils.SvgTextUtil;
6466
import com.itextpdf.svg.utils.TransformUtils;
6567

@@ -196,6 +198,17 @@ public boolean canConstructViewPort() {
196198
return false;
197199
}
198200

201+
202+
/**
203+
* Return font-size of the current element
204+
*
205+
* @return absolute value of font-size
206+
*/
207+
public float getCurrentFontSize() {
208+
// TODO DEVSIX-4140 check work of this method with relative unit
209+
return CssUtils.parseAbsoluteFontSize(getAttribute(SvgConstants.Attributes.FONT_SIZE));
210+
}
211+
199212
/**
200213
* Make a deep copy of the styles and attributes of this renderer
201214
* Helper method for deep copying logic
@@ -217,12 +230,15 @@ protected void deepCopyAttributesAndStyles(ISvgNodeRenderer deepCopy) {
217230
*/
218231
protected abstract void doDraw(SvgDrawContext context);
219232

233+
220234
/**
221235
* Evaluate the current object bounding box
222236
*
223237
* @return the {@link Rectangle} representing the current object's bounding box
224238
*/
225-
protected Rectangle getObjectBoundingBox() {
239+
@Deprecated
240+
// TODO DEVSIX-3814 move to ISvgNodeRenderer in 7.2
241+
protected Rectangle getObjectBoundingBox(SvgDrawContext context) {
226242
return null;
227243
}
228244

@@ -403,6 +419,30 @@ void preDraw(SvgDrawContext context) {
403419
}
404420
}
405421

422+
/**
423+
* Parse absolute length.
424+
* @param length {@link String} for parsing
425+
* @param percentRelativeValue the value on which percent length is based on
426+
* @param defaultValue default value if length is not recognized
427+
* @param context current {@link SvgDrawContext}
428+
* @return absolute value in points
429+
*/
430+
protected float parseAbsoluteLength(String length, float percentRelativeValue, float defaultValue,
431+
SvgDrawContext context) {
432+
if (CssUtils.isPercentageValue(length)) {
433+
return CssUtils.parseRelativeValue(length, percentRelativeValue);
434+
} else {
435+
float em = getCurrentFontSize();
436+
float rem = context.getRemValue();
437+
UnitValue unitValue = CssUtils.parseLengthValueToPt(length, em, rem);
438+
if (unitValue != null && unitValue.isPointValue()) {
439+
return unitValue.getValue();
440+
} else {
441+
return defaultValue;
442+
}
443+
}
444+
}
445+
406446
private TransparentColor getColorFromAttributeValue(SvgDrawContext context, String rawColorValue,
407447
float objectBoundingBoxMargin, float parentOpacity) {
408448
if (rawColorValue == null) {
@@ -421,7 +461,7 @@ private TransparentColor getColorFromAttributeValue(SvgDrawContext context, Stri
421461
ISvgNodeRenderer colorRenderer = context.getNamedObject(normalizedName);
422462
if (colorRenderer instanceof AbstractGradientSvgNodeRenderer) {
423463
resolvedColor = ((AbstractGradientSvgNodeRenderer) colorRenderer).createColor(
424-
context, getObjectBoundingBox(), objectBoundingBoxMargin, parentOpacity);
464+
context, getObjectBoundingBox(context), objectBoundingBoxMargin, parentOpacity);
425465
}
426466
if (resolvedColor != null) {
427467
return new TransparentColor(resolvedColor, resolvedOpacity);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ protected void doDraw(SvgDrawContext context) {
7070
}
7171

7272
@Override
73-
protected Rectangle getObjectBoundingBox() {
73+
protected Rectangle getObjectBoundingBox(SvgDrawContext context) {
7474
if (setParameters()) {
7575
return new Rectangle(cx - rx, cy - ry, rx + rx, ry + ry);
7676
} else {
77-
return super.getObjectBoundingBox();
77+
return super.getObjectBoundingBox(context);
7878
}
7979
}
8080

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.itextpdf.svg.renderers.impl;
2+
3+
import com.itextpdf.kernel.geom.Point;
4+
import com.itextpdf.svg.renderers.SvgDrawContext;
5+
import com.itextpdf.svg.utils.TextRectangle;
6+
7+
8+
/**
9+
* An interface containing a method to simplify working with SVG text elements.
10+
* Must be removed in update 7.3 as the methods of this interface will be moved to {@link ISvgTextNodeRenderer}
11+
*/
12+
@Deprecated
13+
public interface ISvgTextNodeHelper {
14+
/**
15+
* Return the bounding rectangle of the text element.
16+
*
17+
* @param context current {@link SvgDrawContext}
18+
* @param basePoint end point of previous text element
19+
* @return created instance of {@link TextRectangle}
20+
*/
21+
// TODO DEVSIX-3814 This method should be moved to ISvgTextNodeRenderer in 7.2 and this class should be removed
22+
TextRectangle getTextRectangle(SvgDrawContext context, Point basePoint);
23+
}

0 commit comments

Comments
 (0)