Skip to content

Commit 0bedf08

Browse files
committed
Support stroke dash pattern in layout for text elements and underline
DEVSIX-8776
1 parent 97c3ae4 commit 0bedf08

File tree

24 files changed

+264
-52
lines changed

24 files changed

+264
-52
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,7 +1513,11 @@ public PdfCanvas setMiterLimit(float miterLimit) {
15131513
* @return current canvas.
15141514
*/
15151515
public PdfCanvas setLineDash(float phase) {
1516-
currentGs.setDashPattern(getDashPatternArray(phase));
1516+
PdfArray dashPattern = getDashPatternArray(phase);
1517+
if (dashPattern == null) {
1518+
return this;
1519+
}
1520+
currentGs.setDashPattern(dashPattern);
15171521
contentStream.getOutputStream().writeByte('[').writeByte(']').writeSpace()
15181522
.writeFloat(phase).writeSpace()
15191523
.writeBytes(d);
@@ -1533,7 +1537,11 @@ public PdfCanvas setLineDash(float phase) {
15331537
* @return current canvas.
15341538
*/
15351539
public PdfCanvas setLineDash(float unitsOn, float phase) {
1536-
currentGs.setDashPattern(getDashPatternArray(new float[]{unitsOn}, phase));
1540+
PdfArray dashPattern = getDashPatternArray(new float[]{unitsOn}, phase);
1541+
if (dashPattern == null) {
1542+
return this;
1543+
}
1544+
currentGs.setDashPattern(dashPattern);
15371545
contentStream.getOutputStream().writeByte('[').writeFloat(unitsOn).writeByte(']').writeSpace()
15381546
.writeFloat(phase).writeSpace()
15391547
.writeBytes(d);
@@ -1555,7 +1563,11 @@ public PdfCanvas setLineDash(float unitsOn, float phase) {
15551563
* @return current canvas.
15561564
*/
15571565
public PdfCanvas setLineDash(float unitsOn, float unitsOff, float phase) {
1558-
currentGs.setDashPattern(getDashPatternArray(new float[]{unitsOn, unitsOff}, phase));
1566+
PdfArray dashPattern = getDashPatternArray(new float[]{unitsOn, unitsOff}, phase);
1567+
if (dashPattern == null) {
1568+
return this;
1569+
}
1570+
currentGs.setDashPattern(dashPattern);
15591571
contentStream.getOutputStream().writeByte('[').writeFloat(unitsOn).writeSpace()
15601572
.writeFloat(unitsOff).writeByte(']').writeSpace()
15611573
.writeFloat(phase).writeSpace()
@@ -1576,7 +1588,11 @@ public PdfCanvas setLineDash(float unitsOn, float unitsOff, float phase) {
15761588
* @return current canvas.
15771589
*/
15781590
public PdfCanvas setLineDash(float[] array, float phase) {
1579-
currentGs.setDashPattern(getDashPatternArray(array, phase));
1591+
PdfArray dashPattern = getDashPatternArray(array, phase);
1592+
if (dashPattern == null) {
1593+
return this;
1594+
}
1595+
currentGs.setDashPattern(dashPattern);
15801596
PdfOutputStream out = contentStream.getOutputStream();
15811597
out.writeByte('[');
15821598
for (int iter = 0; iter < array.length; iter++) {
@@ -2497,9 +2513,19 @@ private PdfArray getDashPatternArray(float[] dashArray, float phase) {
24972513
PdfArray dashPatternArray = new PdfArray();
24982514
PdfArray dArray = new PdfArray();
24992515
if (dashArray != null) {
2516+
float sum = 0;
25002517
for (float fl : dashArray) {
2518+
if (fl < 0) {
2519+
// Negative values are not allowed.
2520+
return null;
2521+
}
2522+
sum += fl;
25012523
dArray.add(new PdfNumber(fl));
25022524
}
2525+
if (sum < 1e-6) {
2526+
// All 0 values are not allowed.
2527+
return null;
2528+
}
25032529
}
25042530
dashPatternArray.add(dArray);
25052531
dashPatternArray.add(new PdfNumber(phase));

kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/PdfCanvasTest.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,8 +1831,6 @@ public void graphicStateFontNullTest() throws IOException {
18311831
}
18321832
}
18331833

1834-
1835-
18361834
@Test
18371835
public void glyphlineActualTextTest() throws IOException {
18381836
String outFileName = DESTINATION_FOLDER + "glyphlineActualText.pdf";
@@ -1860,6 +1858,30 @@ public void glyphlineActualTextTest() throws IOException {
18601858
}
18611859
}
18621860

1861+
@Test
1862+
public void lineDashTest() throws IOException, InterruptedException {
1863+
String outPdf = DESTINATION_FOLDER + "lineDash.pdf";
1864+
String cmpPdf = SOURCE_FOLDER + "cmp_lineDash.pdf";
1865+
1866+
try (PdfDocument pdfDocument = new PdfDocument(CompareTool.createTestPdfWriter(outPdf))) {
1867+
PdfCanvas canvas = new PdfCanvas(pdfDocument.addNewPage());
1868+
canvas.saveState()
1869+
.setTextRenderingMode(PdfCanvasConstants.TextRenderingMode.FILL_STROKE)
1870+
.setStrokeColor(ColorConstants.BLUE)
1871+
.setLineWidth(2)
1872+
.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 30);
1873+
canvas.setLineDash(3);
1874+
canvas.beginText().moveText(180, 250).showText("phase 3").endText();
1875+
canvas.setLineDash(new float[]{0, 0, 0}, 2);
1876+
canvas.beginText().moveText(180, 350).showText("dashArray [0, 0, 0]").endText();
1877+
canvas.setLineDash(new float[]{5, -5}, 1);
1878+
canvas.beginText().moveText(180, 450).showText("dashArray [5, -5]").endText();
1879+
canvas.setLineDash(5, -10);
1880+
canvas.beginText().moveText(180, 550).showText("phase -10").endText().restoreState();
1881+
}
1882+
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER, "diff_"));
1883+
}
1884+
18631885
private void createStandardDocument(PdfWriter writer, int pageCount, ContentProvider contentProvider) throws IOException {
18641886
PdfDocument pdfDoc = new PdfDocument(writer);
18651887
pdfDoc.getDocumentInfo().setAuthor(AUTHOR).

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,30 @@ public T setStrokeColor(Color strokeColor) {
621621
return (T) (Object) this;
622622
}
623623

624+
/**
625+
* Sets the stroke dash pattern for the current text. Dash pattern is an array of the form [ dashArray dashPhase ],
626+
* where {@code dashArray} is a float array that specifies the length of the alternating dashes and gaps,
627+
* {@code dashPhase} is a float that specifies the distance into the dash pattern to start the dash.
628+
*
629+
* @param dashArray float array that specifies the length of the alternating dashes and gaps,
630+
* use {@code null} for solid line
631+
* @param dashPhase float that specifies the distance into the dash pattern to start the dash,
632+
* use 0 in case offset isn't needed
633+
*
634+
* @return this element
635+
*/
636+
public T setDashPattern(float[] dashArray, float dashPhase) {
637+
List<Float> dashPattern = new ArrayList<>();
638+
if (dashArray != null) {
639+
for (float fl : dashArray) {
640+
dashPattern.add(fl);
641+
}
642+
}
643+
dashPattern.add(dashPhase);
644+
setProperty(Property.STROKE_DASH_PATTERN, dashPattern);
645+
return (T) (Object) this;
646+
}
647+
624648
/**
625649
* Gets the stroke width for the current element.
626650
* The stroke width is the width of the outlines or edges of a shape.

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ public final class Property {
194194
public static final int SPACING_RATIO = 61;
195195
public static final int SPLIT_CHARACTERS = 62;
196196
public static final int STROKE_COLOR = 63;
197+
/**
198+
* STROKE_DASH_PATTERN property specifies dash pattern for the text stroke and stores the {@link java.util.List}
199+
* as float array of the form [ dashArray dashPhase ], where {@code dashArray} is a float array that specifies
200+
* the length of the alternating dashes and gaps, {@code dashPhase} is a float that specifies the distance into
201+
* the dash pattern to start the dash.
202+
*/
203+
public static final int STROKE_DASH_PATTERN = 156;
197204
public static final int STROKE_WIDTH = 64;
198205
public static final int SKEW = 65;
199206
public static final int TABLE_LAYOUT = 93;
@@ -235,7 +242,7 @@ public final class Property {
235242
* related to textual operations. Indicates whether or not this type of property is inheritable.
236243
*/
237244
private static final boolean[] INHERITED_PROPERTIES;
238-
private static final int MAX_INHERITED_PROPERTY_ID = 155;
245+
private static final int MAX_INHERITED_PROPERTY_ID = 156;
239246

240247
static {
241248
INHERITED_PROPERTIES = new boolean[MAX_INHERITED_PROPERTY_ID + 1];
@@ -266,6 +273,7 @@ public final class Property {
266273
INHERITED_PROPERTIES[Property.SPACING_RATIO] = true;
267274
INHERITED_PROPERTIES[Property.SPLIT_CHARACTERS] = true;
268275
INHERITED_PROPERTIES[Property.STROKE_COLOR] = true;
276+
INHERITED_PROPERTIES[Property.STROKE_DASH_PATTERN] = true;
269277
INHERITED_PROPERTIES[Property.STROKE_WIDTH] = true;
270278
INHERITED_PROPERTIES[Property.TEXT_ALIGNMENT] = true;
271279
INHERITED_PROPERTIES[Property.TEXT_ANCHOR] = true;

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class Underline {
4444

4545
private TransparentColor strokeColor;
4646
private float strokeWidth = 0f;
47+
private float[] dashArray = null;
48+
private float dashPhase = 0f;
4749

4850
/**
4951
* Creates an Underline. Both the thickness and vertical positioning under
@@ -183,4 +185,60 @@ public Underline setStrokeWidth(float strokeWidth) {
183185
this.strokeWidth = strokeWidth;
184186
return this;
185187
}
188+
189+
/**
190+
* Gets dash array part of the dash pattern to be used when paths are stroked. Default value is solid line.
191+
*
192+
* <p>
193+
* The line dash pattern is expressed as an array of the form [ dashArray dashPhase ],
194+
* where dashArray is itself an array and dashPhase is an integer.
195+
*
196+
* <p>
197+
* An empty dash array (first element in the array) and zero phase (second element in the array)
198+
* can be used to restore the dash pattern to a solid line.
199+
*
200+
* @return float dash array
201+
*/
202+
public float[] getDashArray() {
203+
return dashArray;
204+
}
205+
206+
/**
207+
* Gets dash phase part of the dash pattern to be used when paths are stroked. Default value is solid line.
208+
*
209+
* <p>
210+
* The line dash pattern is expressed as an array of the form [ dashArray dashPhase ],
211+
* where dashArray is itself an array and dashPhase is an integer.
212+
*
213+
* <p>
214+
* An empty dash array (first element in the array) and zero phase (second element in the array)
215+
* can be used to restore the dash pattern to a solid line.
216+
*
217+
* @return float dash array
218+
*/
219+
public float getDashPhase() {
220+
return dashPhase;
221+
}
222+
223+
/**
224+
* Sets a description of the dash pattern to be used when paths are stroked. Default value is solid line.
225+
*
226+
* <p>
227+
* The line dash pattern is expressed as an array of the form [ dashArray dashPhase ],
228+
* where dashArray is itself an array and dashPhase is a number.
229+
*
230+
* <p>
231+
* An empty dash array (first element in the array) and zero phase (second element in the array)
232+
* can be used to restore the dash pattern to a solid line.
233+
*
234+
* @param dashArray dash array
235+
* @param dashPhase dash phase value
236+
*
237+
* @return this same {@link Underline} instance
238+
*/
239+
public Underline setDashPattern(float[] dashArray, float dashPhase) {
240+
this.dashArray = dashArray;
241+
this.dashPhase = dashPhase;
242+
return this;
243+
}
186244
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,17 @@ public void draw(DrawContext drawContext) {
915915
if (textRenderingMode != PdfCanvasConstants.TextRenderingMode.FILL) {
916916
canvas.setTextRenderingMode((int) textRenderingMode);
917917
}
918-
if (textRenderingMode == PdfCanvasConstants.TextRenderingMode.STROKE || textRenderingMode == PdfCanvasConstants.TextRenderingMode.FILL_STROKE) {
918+
if (textRenderingMode == PdfCanvasConstants.TextRenderingMode.STROKE ||
919+
textRenderingMode == PdfCanvasConstants.TextRenderingMode.FILL_STROKE) {
920+
List<Float> strokeDashPattern = this.<List<Float>>getProperty(Property.STROKE_DASH_PATTERN);
921+
if (strokeDashPattern != null && !strokeDashPattern.isEmpty()) {
922+
float[] dashArray = new float[strokeDashPattern.size() - 1];
923+
for (int i = 0; i < strokeDashPattern.size() - 1; ++i) {
924+
dashArray[i] = strokeDashPattern.get(i);
925+
}
926+
float dashPhase = strokeDashPattern.get(strokeDashPattern.size() - 1);
927+
canvas.setLineDash(dashArray, dashPhase);
928+
}
919929
if (strokeWidth == null) {
920930
strokeWidth = this.getPropertyAsFloat(Property.STROKE_WIDTH);
921931
}
@@ -1516,6 +1526,10 @@ protected void drawSingleUnderline(Underline underline, TransparentColor fontCol
15161526
if (doStroke) {
15171527
canvas.setStrokeColor(underlineStrokeColor.getColor());
15181528
underlineStrokeColor.applyStrokeTransparency(canvas);
1529+
float[] strokeDashArray = underline.getDashArray();
1530+
if (strokeDashArray != null) {
1531+
canvas.setLineDash(strokeDashArray, underline.getDashPhase());
1532+
}
15191533
}
15201534
canvas.setLineCapStyle(underline.getLineCapStyle());
15211535
float underlineThickness = underline.getThickness(fontSize);

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ public void textRenderingModeTest01() throws IOException, InterruptedException {
117117
setFontSize(20);
118118
document.add(new Paragraph(text3));
119119

120+
Text text4 = new Text("This is a stroke with dashes text").
121+
setTextRenderingMode(PdfCanvasConstants.TextRenderingMode.FILL_STROKE).
122+
setStrokeColor(ColorConstants.BLUE).
123+
setStrokeWidth(0.5f).
124+
setFontColor(ColorConstants.PINK).
125+
setFontSize(20);
126+
text4.setDashPattern(new float[]{0.5f, 1f}, 0f);
127+
document.add(new Paragraph(text4));
128+
120129
document.close();
121130

122131
Assertions.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
@@ -349,10 +358,11 @@ public void strokedUnderlineTest() throws IOException, InterruptedException {
349358
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
350359
Document document = new Document(pdfDocument)) {
351360

352-
Paragraph p = new Paragraph("Yellow text with pink stroked underline.")
353-
.setFontSize(50).setFontColor(ColorConstants.YELLOW);
361+
Paragraph p = new Paragraph("Yellow text with pink stroked dashed underline.")
362+
.setFontSize(45).setFontColor(ColorConstants.YELLOW);
354363
Underline underline = new Underline(null, 0, 0.1f, 0, -0.1f, PdfCanvasConstants.LineCapStyle.BUTT)
355-
.setStrokeWidth(2).setStrokeColor(new TransparentColor(ColorConstants.PINK, 0.5f));
364+
.setStrokeWidth(2).setStrokeColor(new TransparentColor(ColorConstants.PINK, 0.5f))
365+
.setDashPattern(new float[]{5, 5, 10, 5}, 5);
356366
p.setUnderline(underline);
357367

358368
Paragraph p2 = new Paragraph("Text with line-through and default underline.")
@@ -364,7 +374,7 @@ public void strokedUnderlineTest() throws IOException, InterruptedException {
364374
p2.setUnderline(underline2);
365375
p2.setUnderline();
366376

367-
Paragraph p3 = new Paragraph("Text with transparent font color and default overline.").setFontSize(50)
377+
Paragraph p3 = new Paragraph("Text with transparent color and default overline.").setFontSize(50)
368378
.setFontColor(new TransparentColor(ColorConstants.BLUE, 0));
369379
Underline underline3 = new Underline(null, 0, 0.1f, 0, 0.9f, PdfCanvasConstants.LineCapStyle.BUTT);
370380
p3.setUnderline(underline3);

0 commit comments

Comments
 (0)