Skip to content

Commit 2cd532d

Browse files
committed
SVG: support text-decoration attribute
DEVSIX-2270
1 parent b4fc4fb commit 2cd532d

File tree

41 files changed

+432
-51
lines changed

Some content is hidden

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

41 files changed

+432
-51
lines changed

layout/src/main/java/com/itextpdf/layout/ElementPropertyContainer.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -722,36 +722,53 @@ public T setUnderline(Color color, float thickness, float thicknessMul, float yP
722722
}
723723

724724
/**
725-
* Sets an horizontal line that can be an underline or a strikethrough.
725+
* Sets horizontal line that can be an underline or a strikethrough.
726726
* Actually, the line can be anywhere vertically due to position parameter.
727727
* Multiple call to this method will produce multiple lines.
728+
*
728729
* <p>
729730
* The thickness of the line will be {@code thickness + thicknessMul * fontSize}.
730731
* The position of the line will be {@code baseLine + yPosition + yPositionMul * fontSize}.
731732
*
732733
* @param color the color of the line or <CODE>null</CODE> to follow the
733734
* text color
734-
* @param opacity the opacity of the line; a float between 0 and 1, where 1 stands for fully opaque color and 0 - for fully transparent
735+
* @param opacity the opacity of the line; a float between 0 and 1, where 1 stands for fully opaque color and
736+
* 0 - for fully transparent
735737
* @param thickness the absolute thickness of the line
736738
* @param thicknessMul the thickness multiplication factor with the font size
737739
* @param yPosition the absolute y position relative to the baseline
738740
* @param yPositionMul the position multiplication factor with the font size
739741
* @param lineCapStyle the end line cap style. Allowed values are enumerated in
740742
* {@link com.itextpdf.kernel.pdf.canvas.PdfCanvasConstants.LineCapStyle}
743+
*
744+
* @return this element
745+
*/
746+
public T setUnderline(Color color, float opacity, float thickness, float thicknessMul, float yPosition,
747+
float yPositionMul, int lineCapStyle) {
748+
return setUnderline(new Underline(color, opacity, thickness, thicknessMul, yPosition,
749+
yPositionMul, lineCapStyle));
750+
}
751+
752+
/**
753+
* Sets horizontal line that can be an underline, overline or a strikethrough.
754+
* Actually, the line can be anywhere vertically due to position parameter.
755+
* Multiple call to this method will produce multiple lines.
756+
*
757+
* @param underline {@link Underline} to set
758+
*
741759
* @return this element
742760
*/
743-
public T setUnderline(Color color, float opacity, float thickness, float thicknessMul, float yPosition, float yPositionMul, int lineCapStyle) {
744-
Underline newUnderline = new Underline(color, opacity, thickness, thicknessMul, yPosition, yPositionMul, lineCapStyle);
761+
public T setUnderline(Underline underline) {
745762
Object currentProperty = this.<Object>getProperty(Property.UNDERLINE);
746763
if (currentProperty instanceof List) {
747-
((List) currentProperty).add(newUnderline);
764+
((List) currentProperty).add(underline);
748765
} else if (currentProperty instanceof Underline) {
749766
List<Underline> mergedUnderlines = new ArrayList<>();
750767
mergedUnderlines.add((Underline) currentProperty);
751-
mergedUnderlines.add(newUnderline);
768+
mergedUnderlines.add(underline);
752769
setProperty(Property.UNDERLINE, mergedUnderlines);
753770
} else {
754-
setProperty(Property.UNDERLINE, newUnderline);
771+
setProperty(Property.UNDERLINE, underline);
755772
}
756773
return (T) (Object) this;
757774
}

layout/src/main/java/com/itextpdf/layout/properties/Underline.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ This file is part of the iText (R) project.
2929

3030
/**
3131
* A POJO that describes the underline of a layout element.
32-
*
32+
*
33+
* <p>
3334
* This class is to be used as a property for an element or renderer,
34-
* as the value for {@link com.itextpdf.layout.properties.Property#UNDERLINE}
35+
* as the value for {@link com.itextpdf.layout.properties.Property#UNDERLINE}.
3536
*/
3637
public class Underline {
3738
protected TransparentColor transparentColor;
@@ -41,6 +42,9 @@ public class Underline {
4142
protected float yPositionMul;
4243
protected int lineCapStyle = PdfCanvasConstants.LineCapStyle.BUTT;
4344

45+
private TransparentColor strokeColor;
46+
private float strokeWidth = 0f;
47+
4448
/**
4549
* Creates an Underline. Both the thickness and vertical positioning under
4650
* the text element's base line can be set to a fixed value, or a variable
@@ -137,4 +141,46 @@ public float getYPositionMul() {
137141
public int getLineCapStyle() {
138142
return lineCapStyle;
139143
}
144+
145+
/**
146+
* Gets the color of the underline stroke.
147+
*
148+
* @return {@link TransparentColor} stroke color
149+
*/
150+
public TransparentColor getStrokeColor() {
151+
return strokeColor;
152+
}
153+
154+
/**
155+
* Sets the stroke color of the underline.
156+
*
157+
* @param strokeColor {@link TransparentColor} stroke color
158+
*
159+
* @return this {@link Underline} instance
160+
*/
161+
public Underline setStrokeColor(TransparentColor strokeColor) {
162+
this.strokeColor = strokeColor;
163+
return this;
164+
}
165+
166+
/**
167+
* Gets the thickness of the underline stroke.
168+
*
169+
* @return float value of the stroke width
170+
*/
171+
public float getStrokeWidth() {
172+
return strokeWidth;
173+
}
174+
175+
/**
176+
* Sets the thickness of the underline stroke.
177+
*
178+
* @param strokeWidth float value of the stroke width
179+
*
180+
* @return this {@link Underline} instance
181+
*/
182+
public Underline setStrokeWidth(float strokeWidth) {
183+
this.strokeWidth = strokeWidth;
184+
return this;
185+
}
140186
}

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

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,27 +1489,57 @@ protected TextRenderer[] split(int initialOverflowTextPos) {
14891489
return new TextRenderer[]{splitRenderer, overflowRenderer};
14901490
}
14911491

1492-
protected void drawSingleUnderline(Underline underline, TransparentColor fontStrokeColor, PdfCanvas canvas, float fontSize, float italicAngleTan) {
1493-
TransparentColor underlineColor = underline.getColor() != null ? new TransparentColor(underline.getColor(), underline.getOpacity()) : fontStrokeColor;
1494-
canvas.saveState();
1492+
protected void drawSingleUnderline(Underline underline, TransparentColor fontColor, PdfCanvas canvas,
1493+
float fontSize, float italicAngleTan) {
1494+
TransparentColor underlineFillColor = underline.getColor() != null ?
1495+
new TransparentColor(underline.getColor(), underline.getOpacity()) : null;
1496+
TransparentColor underlineStrokeColor = underline.getStrokeColor();
1497+
1498+
boolean doStroke = underlineStrokeColor != null;
1499+
RenderingMode mode = this.<RenderingMode>getProperty(Property.RENDERING_MODE);
1500+
// In SVG mode we should always use underline color, it is not related to the font color of the current text,
1501+
// but to the font color of the text element where text-decoration has been declared. In case of none value
1502+
// for fill and stroke in SVG mode, underline shouldn't be drawn at all.
1503+
if (underlineFillColor == null && !doStroke) {
1504+
if (RenderingMode.SVG_MODE == mode) {
1505+
return;
1506+
}
1507+
underlineFillColor = fontColor;
1508+
}
1509+
boolean doFill = underlineFillColor != null;
14951510

1496-
if (underlineColor != null) {
1497-
canvas.setStrokeColor(underlineColor.getColor());
1498-
underlineColor.applyStrokeTransparency(canvas);
1511+
canvas.saveState();
1512+
if (doFill) {
1513+
canvas.setFillColor(underlineFillColor.getColor());
1514+
underlineFillColor.applyFillTransparency(canvas);
1515+
}
1516+
if (doStroke) {
1517+
canvas.setStrokeColor(underlineStrokeColor.getColor());
1518+
underlineStrokeColor.applyStrokeTransparency(canvas);
14991519
}
15001520
canvas.setLineCapStyle(underline.getLineCapStyle());
15011521
float underlineThickness = underline.getThickness(fontSize);
15021522
if (underlineThickness != 0) {
1503-
canvas.setLineWidth(underlineThickness);
1523+
if (doStroke) {
1524+
canvas.setLineWidth(underline.getStrokeWidth());
1525+
}
15041526
float yLine = getYLine();
15051527
float underlineYPosition = underline.getYPosition(fontSize) + yLine;
15061528
float italicWidthSubstraction = .5f * fontSize * italicAngleTan;
15071529
Rectangle innerAreaBbox = getInnerAreaBBox();
1508-
canvas.moveTo(innerAreaBbox.getX(), underlineYPosition).
1509-
lineTo(innerAreaBbox.getX() + innerAreaBbox.getWidth() - italicWidthSubstraction, underlineYPosition).
1510-
stroke();
1530+
Rectangle underlineBBox = new Rectangle(innerAreaBbox.getX(), underlineYPosition - underlineThickness / 2,
1531+
innerAreaBbox.getWidth() - italicWidthSubstraction, underlineThickness);
1532+
canvas.rectangle(underlineBBox);
1533+
if (doFill && doStroke) {
1534+
canvas.fillStroke();
1535+
} else if (doStroke) {
1536+
canvas.stroke();
1537+
} else {
1538+
// In layout/html we should use default color in case underline and fontColor are null
1539+
// and still draw underline.
1540+
canvas.fill();
1541+
}
15111542
}
1512-
15131543
canvas.restoreState();
15141544
}
15151545

layout/src/test/java/com/itextpdf/layout/TextWritingTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ This file is part of the iText (R) project.
3939
import com.itextpdf.layout.properties.FloatPropertyValue;
4040
import com.itextpdf.layout.properties.OverflowPropertyValue;
4141
import com.itextpdf.layout.properties.Property;
42+
import com.itextpdf.layout.properties.TransparentColor;
43+
import com.itextpdf.layout.properties.Underline;
4244
import com.itextpdf.test.ExtendedITextTest;
4345

4446
import java.io.IOException;
@@ -340,6 +342,47 @@ public void underlineTest() throws IOException, InterruptedException {
340342
Assertions.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
341343
}
342344

345+
@Test
346+
public void strokedUnderlineTest() throws IOException, InterruptedException {
347+
String outFileName = destinationFolder + "strokedUnderline.pdf";
348+
String cmpFileName = sourceFolder + "cmp_strokedUnderline.pdf";
349+
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
350+
Document document = new Document(pdfDocument)) {
351+
352+
Paragraph p = new Paragraph("Yellow text with pink stroked underline.")
353+
.setFontSize(50).setFontColor(ColorConstants.YELLOW);
354+
Underline underline = new Underline(null, 0, 0.1f, 0, -0.1f, PdfCanvasConstants.LineCapStyle.BUTT)
355+
.setStrokeWidth(2).setStrokeColor(new TransparentColor(ColorConstants.PINK, 0.5f));
356+
p.setUnderline(underline);
357+
358+
Paragraph p2 = new Paragraph("Text with line-through and default underline.")
359+
.setFontSize(50).setStrokeWidth(1).setFontColor(ColorConstants.DARK_GRAY)
360+
.setStrokeColor(ColorConstants.GREEN);
361+
Underline underline2 = new Underline(ColorConstants.DARK_GRAY, 0, 0.1f, 0, 0.3f,
362+
PdfCanvasConstants.LineCapStyle.BUTT)
363+
.setStrokeWidth(1).setStrokeColor(new TransparentColor(ColorConstants.GREEN));
364+
p2.setUnderline(underline2);
365+
p2.setUnderline();
366+
367+
Paragraph p3 = new Paragraph("Text with transparent font color and default overline.").setFontSize(50)
368+
.setFontColor(new TransparentColor(ColorConstants.BLUE, 0));
369+
Underline underline3 = new Underline(null, 0, 0.1f, 0, 0.9f, PdfCanvasConstants.LineCapStyle.BUTT);
370+
p3.setUnderline(underline3);
371+
p3.setBackgroundColor(ColorConstants.PINK);
372+
373+
Paragraph p4 = new Paragraph("Text with null font color and default overline.").setFontSize(50)
374+
.setFontColor((TransparentColor) null);
375+
p4.setUnderline(underline3);
376+
377+
document.add(p);
378+
document.add(p2);
379+
document.add(p3);
380+
document.add(p4);
381+
}
382+
383+
Assertions.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
384+
}
385+
343386
@Test
344387
public void lineThroughTest() throws IOException, InterruptedException {
345388
//TODO: update after DEVSIX-2623 fix

0 commit comments

Comments
 (0)