Skip to content

Commit 2027c66

Browse files
committed
Customize saving state, so that q and Q operators are not drawn inside text blocks
Changes were cherry picked from 7.2 develop branch (see DEVSIX-5427 ticket) DEVSIX-6938
1 parent 70cdd0f commit 2027c66

File tree

757 files changed

+241
-44
lines changed

Some content is hidden

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

757 files changed

+241
-44
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class SvgDrawContext {
7878

7979
private AffineTransform lastTextTransform = new AffineTransform();
8080
private float[] textMove = new float[]{0.0f, 0.0f};
81+
private float[] previousElementTextMove;
8182

8283
/**
8384
* Create an instance of the context that is used to store information when converting SVG.
@@ -400,4 +401,12 @@ public boolean pushPatternId(String patternId) {
400401
public void popPatternId() {
401402
this.patternIds.pop();
402403
}
404+
405+
public void setPreviousElementTextMove(float[] previousElementTextMove) {
406+
this.previousElementTextMove = previousElementTextMove;
407+
}
408+
409+
public float[] getPreviousElementTextMove() {
410+
return previousElementTextMove;
411+
}
403412
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ void postDraw(SvgDrawContext context) {
284284
currentCanvas.clip();
285285
}
286286
currentCanvas.endPath();
287-
} else {
287+
} else if (!(this instanceof ISvgTextNodeRenderer)) {
288288
if (doFill && canElementFill()) {
289289
String fillRuleRawValue = getAttribute(SvgConstants.Attributes.FILL_RULE);
290290

@@ -303,10 +303,9 @@ void postDraw(SvgDrawContext context) {
303303
}
304304
} else if (doStroke) {
305305
currentCanvas.stroke();
306-
} else if (!TextSvgBranchRenderer.class.isInstance(this)) {
306+
} else {
307307
currentCanvas.endPath();
308308
}
309-
310309
}
311310
// Marker drawing
312311
if (this instanceof IMarkerCapable) {
@@ -404,6 +403,7 @@ void preDraw(SvgDrawContext context) {
404403
}
405404

406405
currentCanvas.setLineWidth(strokeWidth);
406+
407407
doStroke = true;
408408
}
409409
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ protected void doDraw(SvgDrawContext context) {
125125
if (this.attributesAndStyles != null && this.attributesAndStyles.containsKey(SvgConstants.Attributes.TEXT_CONTENT)) {
126126
PdfCanvas currentCanvas = context.getCurrentCanvas();
127127
//TODO(DEVSIX-2507): Support for glyph by glyph handling of x, y and rotate
128-
currentCanvas.moveText(context.getTextMove()[0], context.getTextMove()[1]);
128+
if (context.getPreviousElementTextMove() == null) {
129+
currentCanvas.moveText(context.getTextMove()[0], context.getTextMove()[1]);
130+
} else {
131+
currentCanvas.moveText(context.getPreviousElementTextMove()[0],
132+
context.getPreviousElementTextMove()[1]);
133+
}
129134
currentCanvas.showText(this.attributesAndStyles.get(SvgConstants.Attributes.TEXT_CONTENT));
130135
}
131136
}

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

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,13 @@ public TextSvgBranchRenderer() {
104104
@Override
105105
public ISvgNodeRenderer createDeepCopy() {
106106
TextSvgBranchRenderer copy = new TextSvgBranchRenderer();
107+
fillCopy(copy);
108+
return copy;
109+
}
110+
111+
void fillCopy(TextSvgBranchRenderer copy) {
107112
deepCopyAttributesAndStyles(copy);
108113
deepCopyChildren(copy);
109-
return copy;
110114
}
111115

112116
public final void addChild(ISvgTextNodeRenderer child) {
@@ -205,34 +209,19 @@ protected Rectangle getObjectBoundingBox(SvgDrawContext context) {
205209
protected void doDraw(SvgDrawContext context) {
206210
if (getChildren().size() > 0) { // if branch has no children, don't do anything
207211
PdfCanvas currentCanvas = context.getCurrentCanvas();
208-
if (performRootTransformations) {
209-
currentCanvas.beginText();
210-
// Current transformation matrix results in the character glyphs being mirrored, correct with inverse tf
211-
AffineTransform rootTf;
212-
if (this.containsAbsolutePositionChange()) {
213-
rootTf = getTextTransform(this.getAbsolutePositionChanges(), context);
214-
} else {
215-
rootTf = new AffineTransform(TEXTFLIP);
216-
}
217-
currentCanvas.setTextMatrix(rootTf);
218-
// Reset context of text move
219-
context.resetTextMove();
220-
// Apply relative move
221-
if (this.containsRelativeMove()) {
222-
float[] rootMove = this.getRelativeTranslation();
223-
context.addTextMove(rootMove[0], -rootMove[1]); //-y to account for the text-matrix transform we do in the text root to account for the coordinates
224-
}
225-
// Handle white-spaces
226-
if (!whiteSpaceProcessed) {
227-
SvgTextUtil.processWhiteSpace(this, true);
228-
}
229-
}
230-
applyTextRenderingMode(currentCanvas);
231-
212+
context.resetTextMove();
213+
context.setLastTextTransform(null);
232214
if (this.attributesAndStyles != null) {
233-
resolveFont(context);
234-
currentCanvas.setFontAndSize(font, getCurrentFontSize());
235215
for (ISvgTextNodeRenderer c : children) {
216+
currentCanvas.saveState();
217+
currentCanvas.beginText();
218+
219+
performRootTransformations(currentCanvas, context);
220+
221+
applyTextRenderingMode(currentCanvas);
222+
resolveFont(context);
223+
currentCanvas.setFontAndSize(font, getCurrentFontSize());
224+
236225
final float childLength = c.getTextContentLength(getCurrentFontSize(), font);
237226
if (c.containsAbsolutePositionChange()) {
238227
// TODO: DEVSIX-2507 support rotate and other attributes
@@ -244,6 +233,9 @@ protected void doDraw(SvgDrawContext context) {
244233
currentCanvas.setTextMatrix(newTransform);
245234
// Absolute position changes requires resetting the current text move in the context
246235
context.resetTextMove();
236+
} else if (c instanceof TextLeafSvgNodeRenderer &&
237+
!context.getLastTextTransform().isIdentity()) {
238+
currentCanvas.setTextMatrix(context.getLastTextTransform());
247239
}
248240

249241
// Handle Text-Anchor declarations
@@ -256,24 +248,41 @@ protected void doDraw(SvgDrawContext context) {
256248
float[] childMove = c.getRelativeTranslation();
257249
context.addTextMove(childMove[0], -childMove[1]); //-y to account for the text-matrix transform we do in the text root to account for the coordinates
258250
}
259-
currentCanvas.saveState();
251+
260252
c.draw(context);
261253

262254
context.addTextMove(childLength, 0);
263-
currentCanvas.restoreState();
264-
// Restore transformation matrix
265-
if (!context.getLastTextTransform().isIdentity()) {
266-
currentCanvas.setTextMatrix(context.getLastTextTransform());
267-
}
268255

269-
}
270-
if (performRootTransformations) {
256+
context.setPreviousElementTextMove(null);
257+
271258
currentCanvas.endText();
259+
currentCanvas.restoreState();
272260
}
273261
}
274262
}
275263
}
276264

265+
void performRootTransformations(PdfCanvas currentCanvas, SvgDrawContext context) {
266+
// Current transformation matrix results in the character glyphs being mirrored, correct with inverse tf
267+
AffineTransform rootTf;
268+
if (this.containsAbsolutePositionChange()) {
269+
rootTf = getTextTransform(this.getAbsolutePositionChanges(), context);
270+
} else {
271+
rootTf = new AffineTransform(TEXTFLIP);
272+
}
273+
currentCanvas.setTextMatrix(rootTf);
274+
// Apply relative move
275+
if (this.containsRelativeMove()) {
276+
float[] rootMove = this.getRelativeTranslation();
277+
//-y to account for the text-matrix transform we do in the text root to account for the coordinates
278+
context.addTextMove(rootMove[0], -rootMove[1]);
279+
}
280+
// Handle white-spaces
281+
if (!whiteSpaceProcessed) {
282+
SvgTextUtil.processWhiteSpace(this, true);
283+
}
284+
}
285+
277286
private void resolveTextMove() {
278287
if (this.attributesAndStyles != null) {
279288
String xRawValue = this.attributesAndStyles.get(SvgConstants.Attributes.DX);
@@ -370,7 +379,7 @@ private static float[] getPositionsFromString(String rawValuesString) {
370379
return result;
371380
}
372381

373-
private static AffineTransform getTextTransform(float[][] absolutePositions, SvgDrawContext context) {
382+
static AffineTransform getTextTransform(float[][] absolutePositions, SvgDrawContext context) {
374383
AffineTransform tf = new AffineTransform();
375384
// If x is not specified, but y is, we need to correct for preceding text.
376385
if (absolutePositions[0] == null && absolutePositions[1] != null) {
@@ -386,7 +395,7 @@ private static AffineTransform getTextTransform(float[][] absolutePositions, Svg
386395
return tf;
387396
}
388397

389-
private void applyTextRenderingMode(PdfCanvas currentCanvas) {
398+
void applyTextRenderingMode(PdfCanvas currentCanvas) {
390399
// Fill only is the default for text operation in PDF
391400
if (doStroke && doFill) {
392401
currentCanvas.setTextRenderingMode(PdfCanvasConstants.TextRenderingMode.FILL_STROKE); //Default for SVG
@@ -407,7 +416,7 @@ private void deepCopyChildren(TextSvgBranchRenderer deepCopy) {
407416
}
408417
}
409418

410-
private float getTextAnchorAlignmentCorrection(float childContentLength) {
419+
float getTextAnchorAlignmentCorrection(float childContentLength) {
411420
// Resolve text anchor
412421
// TODO DEVSIX-2631 properly resolve text-anchor by taking entire line into account, not only children of the current TextSvgBranchRenderer
413422
float textAnchorXCorrection = 0.0f;

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,20 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.svg.renderers.impl;
4444

45+
import com.itextpdf.kernel.geom.AffineTransform;
46+
import com.itextpdf.kernel.geom.Matrix;
4547
import com.itextpdf.kernel.geom.Rectangle;
48+
import com.itextpdf.kernel.pdf.canvas.CanvasGraphicsState;
49+
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
50+
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
51+
import com.itextpdf.styledxmlparser.css.util.CssUtils;
52+
import com.itextpdf.svg.renderers.ISvgNodeRenderer;
4653
import com.itextpdf.svg.renderers.SvgDrawContext;
4754

4855
public class TextSvgTSpanBranchRenderer extends TextSvgBranchRenderer {
4956

57+
private static final float EPS = 0.0001f;
58+
5059
public TextSvgTSpanBranchRenderer() {
5160
this.performRootTransformations = false;
5261
}
@@ -59,4 +68,102 @@ protected Rectangle getObjectBoundingBox(SvgDrawContext context) {
5968
return null;
6069
}
6170
}
71+
72+
@Override
73+
public ISvgNodeRenderer createDeepCopy() {
74+
TextSvgBranchRenderer copy = new TextSvgTSpanBranchRenderer();
75+
fillCopy(copy);
76+
return copy;
77+
}
78+
79+
@Override
80+
protected void doDraw(SvgDrawContext context) {
81+
if (getChildren().size() > 0) { // if branch has no children, don't do anything
82+
PdfCanvas currentCanvas = context.getCurrentCanvas();
83+
if (this.attributesAndStyles != null) {
84+
for (ISvgTextNodeRenderer c : getChildren()) {
85+
86+
applyTextRenderingMode(currentCanvas);
87+
resolveFont(context);
88+
currentCanvas.setFontAndSize(getFont(), getCurrentFontSize());
89+
90+
final float childLength = c.getTextContentLength(getCurrentFontSize(), getFont());
91+
if (c.containsAbsolutePositionChange()) {
92+
// TODO: DEVSIX-2507 support rotate and other attributes
93+
float[][] absolutePositions = c.getAbsolutePositionChanges();
94+
AffineTransform newTransform = getTextTransform(absolutePositions, context);
95+
// Overwrite the last transformation stored in the context
96+
context.setLastTextTransform(newTransform);
97+
// Apply transformation
98+
currentCanvas.setTextMatrix(newTransform);
99+
// Absolute position changes requires resetting the current text move in the context
100+
context.resetTextMove();
101+
context.setPreviousElementTextMove(null);
102+
}
103+
104+
// Handle Text-Anchor declarations
105+
float textAnchorCorrection = getTextAnchorAlignmentCorrection(childLength);
106+
if (!CssUtils.compareFloats(0f, textAnchorCorrection)) {
107+
context.addTextMove(textAnchorCorrection, 0);
108+
}
109+
// Move needs to happen before the saving of the state in order for it to cascade beyond
110+
if (c.containsRelativeMove()) {
111+
float[] childMove = c.getRelativeTranslation();
112+
//-y to account for the text-matrix transform we do in the text root to account
113+
// for the coordinates
114+
context.addTextMove(childMove[0], -childMove[1]);
115+
context.setPreviousElementTextMove(
116+
new float[] {context.getPreviousElementTextMove()[0] + childMove[0],
117+
context.getPreviousElementTextMove()[1] - childMove[1]});
118+
}
119+
120+
CanvasGraphicsState savedState = new CanvasGraphicsState(currentCanvas.getGraphicsState());
121+
c.draw(context);
122+
applyGSDifference(currentCanvas, savedState);
123+
context.addTextMove(childLength, 0);
124+
125+
if (!floatsAreEqual(childLength, 0)) {
126+
context.setPreviousElementTextMove(new float[]{childLength, 0});
127+
}
128+
}
129+
}
130+
}
131+
}
132+
133+
// This method is used to follow q/Q store/restore approach. If some graphics characteristics
134+
// have been updated while processing this renderer's children, they are restored.
135+
void applyGSDifference(PdfCanvas currentCanvas, CanvasGraphicsState savedGs) {
136+
CanvasGraphicsState newGs = currentCanvas.getGraphicsState();
137+
if (!floatsAreEqual(savedGs.getCharSpacing(), newGs.getCharSpacing())) {
138+
currentCanvas.setCharacterSpacing(savedGs.getCharSpacing());
139+
}
140+
if (savedGs.getFillColor() != newGs.getFillColor()) {
141+
currentCanvas.setFillColor(savedGs.getFillColor());
142+
}
143+
if (savedGs.getFont() != newGs.getFont() || !floatsAreEqual(savedGs.getFontSize(), newGs.getFontSize())) {
144+
currentCanvas.setFontAndSize(savedGs.getFont(), savedGs.getFontSize());
145+
}
146+
if (!floatsAreEqual(savedGs.getLineWidth(), newGs.getLineWidth())) {
147+
currentCanvas.setLineWidth(savedGs.getLineWidth());
148+
}
149+
if (savedGs.getStrokeColor() != newGs.getStrokeColor()) {
150+
currentCanvas.setStrokeColor(savedGs.getStrokeColor());
151+
}
152+
if (savedGs.getTextRenderingMode() != newGs.getTextRenderingMode()) {
153+
currentCanvas.setTextRenderingMode(savedGs.getTextRenderingMode());
154+
}
155+
156+
// Only the next extended options are set in svg
157+
if (!floatsAreEqual(savedGs.getFillOpacity(), newGs.getFillOpacity())
158+
|| !floatsAreEqual(savedGs.getStrokeOpacity(), newGs.getStrokeOpacity())) {
159+
PdfExtGState extGState = new PdfExtGState();
160+
extGState.setFillOpacity(savedGs.getFillOpacity());
161+
extGState.setStrokeOpacity(savedGs.getStrokeOpacity());
162+
currentCanvas.setExtGState(extGState);
163+
}
164+
}
165+
166+
private static boolean floatsAreEqual(float first, float second) {
167+
return Math.abs(first - second) < EPS;
168+
}
62169
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,19 @@ public void tspanInheritTextFontSizeTest() throws IOException, InterruptedExcept
229229
public void tspanInheritAncestorsTspanFontSizeTest() throws IOException, InterruptedException {
230230
convertAndCompareSinglePage(SOURCE_FOLDER, DESTINATION_FOLDER, "tspanInheritAncestorsTspanFontSize");
231231
}
232+
233+
@Test
234+
public void tspanNestedWithOffsets() throws IOException, InterruptedException {
235+
convertAndCompareSinglePage(SOURCE_FOLDER, DESTINATION_FOLDER, "tspanNestedWithOffsets");
236+
}
237+
238+
@Test
239+
public void tspanNestedRelativeOffsets() throws IOException, InterruptedException {
240+
convertAndCompareSinglePage(SOURCE_FOLDER, DESTINATION_FOLDER, "tspanNestedRelativeOffsets");
241+
}
242+
243+
@Test
244+
public void simpleNestedTspanTest() throws IOException, InterruptedException {
245+
convertAndCompareSinglePage(SOURCE_FOLDER, DESTINATION_FOLDER, "simpleNestedTspan");
246+
}
232247
}

0 commit comments

Comments
 (0)