Skip to content

Commit 189827b

Browse files
BezrukovMKate Ivanova
authored andcommitted
Fix rounded border logic in layout
DEVSIX-2025
1 parent 4177fb4 commit 189827b

File tree

13 files changed

+185
-97
lines changed

13 files changed

+185
-97
lines changed

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

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,17 +1036,27 @@ public PdfCanvas curveFromTo(double x1, double y1, double x3, double y3) {
10361036
*/
10371037
public PdfCanvas arc(double x1, double y1, double x2, double y2,
10381038
double startAng, double extent) {
1039-
List<double[]> ar = bezierArc(x1, y1, x2, y2, startAng, extent);
1040-
if (ar.isEmpty())
1041-
return this;
1042-
double[] pt = ar.get(0);
1043-
moveTo(pt[0], pt[1]);
1044-
for (int i = 0; i < ar.size(); ++i) {
1045-
pt = ar.get(i);
1046-
curveTo(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]);
1047-
}
1039+
return drawArc(x1, y1, x2, y2, startAng, extent, false);
1040+
}
10481041

1049-
return this;
1042+
/**
1043+
* Draws a partial ellipse with the preceding line to the start of the arc to prevent path
1044+
* broking. The target arc is inscribed within the rectangle x1,y1,x2,y2, starting
1045+
* at startAng degrees and covering extent degrees. Angles start with 0 to the right (+x)
1046+
* and increase counter-clockwise.
1047+
*
1048+
* @param x1 a corner of the enclosing rectangle
1049+
* @param y1 a corner of the enclosing rectangle
1050+
* @param x2 a corner of the enclosing rectangle
1051+
* @param y2 a corner of the enclosing rectangle
1052+
* @param startAng starting angle in degrees
1053+
* @param extent angle extent in degrees
1054+
*
1055+
* @return the current canvas
1056+
*/
1057+
public PdfCanvas arcContinuous(double x1, double y1, double x2, double y2,
1058+
double startAng, double extent) {
1059+
return drawArc(x1, y1, x2, y2, startAng, extent, true);
10501060
}
10511061

10521062
/**
@@ -2788,6 +2798,26 @@ private void applyRotation(PdfPage page) {
27882798
}
27892799
}
27902800

2801+
private PdfCanvas drawArc(double x1, double y1, double x2, double y2,
2802+
double startAng, double extent, boolean continuous) {
2803+
List<double[]> ar = bezierArc(x1, y1, x2, y2, startAng, extent);
2804+
if (ar.isEmpty()) {
2805+
return this;
2806+
}
2807+
2808+
double[] pt = ar.get(0);
2809+
if (continuous) {
2810+
lineTo(pt[0], pt[1]);
2811+
} else {
2812+
moveTo(pt[0], pt[1]);
2813+
}
2814+
for (int index = 0; index < ar.size(); ++index) {
2815+
pt = ar.get(index);
2816+
curveTo(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]);
2817+
}
2818+
return this;
2819+
}
2820+
27912821
private static PdfStream getPageStream(PdfPage page) {
27922822
PdfStream stream = page.getLastContentStream();
27932823
return stream == null || stream.getOutputStream() == null || stream.containsKey(PdfName.Filter) ? page.newContentStreamAfter() : stream;

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.kernel.pdf.canvas;
4444

45-
import com.itextpdf.io.LogMessageConstant;
4645
import com.itextpdf.io.font.constants.StandardFonts;
4746
import com.itextpdf.io.image.ImageData;
4847
import com.itextpdf.io.image.ImageDataFactory;
@@ -51,37 +50,32 @@ This file is part of the iText (R) project.
5150
import com.itextpdf.io.util.StreamUtil;
5251
import com.itextpdf.io.util.UrlUtil;
5352
import com.itextpdf.kernel.PdfException;
53+
import com.itextpdf.kernel.colors.ColorConstants;
5454
import com.itextpdf.kernel.font.PdfFontFactory;
55-
import com.itextpdf.kernel.pdf.CompressionConstants;
5655
import com.itextpdf.kernel.pdf.PdfDictionary;
5756
import com.itextpdf.kernel.pdf.PdfDocument;
5857
import com.itextpdf.kernel.pdf.PdfName;
5958
import com.itextpdf.kernel.pdf.PdfNumber;
6059
import com.itextpdf.kernel.pdf.PdfObject;
6160
import com.itextpdf.kernel.pdf.PdfPage;
6261
import com.itextpdf.kernel.pdf.PdfReader;
63-
import com.itextpdf.kernel.pdf.PdfVersion;
6462
import com.itextpdf.kernel.pdf.PdfWriter;
6563
import com.itextpdf.kernel.pdf.WriterProperties;
6664
import com.itextpdf.kernel.pdf.canvas.wmf.WmfImageData;
6765
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
68-
import com.itextpdf.kernel.pdf.xobject.PdfImageXObject;
6966
import com.itextpdf.kernel.utils.CompareTool;
7067
import com.itextpdf.test.ExtendedITextTest;
71-
import com.itextpdf.test.annotations.LogMessage;
72-
import com.itextpdf.test.annotations.LogMessages;
7368
import com.itextpdf.test.annotations.type.IntegrationTest;
69+
7470
import java.awt.Toolkit;
7571
import java.io.FileInputStream;
76-
import java.io.FileNotFoundException;
7772
import java.io.IOException;
7873
import java.io.InputStream;
7974
import java.util.ArrayList;
8075
import java.util.HashMap;
8176
import java.util.List;
8277
import org.junit.Assert;
8378
import org.junit.BeforeClass;
84-
import org.junit.Ignore;
8579
import org.junit.Test;
8680
import org.junit.experimental.categories.Category;
8781

@@ -106,7 +100,7 @@ public static void beforeClass() {
106100
}
107101

108102
@Test
109-
public void createSimpleCanvas() throws IOException, FileNotFoundException {
103+
public void createSimpleCanvas() throws IOException {
110104

111105
final String author = "Alexander Chingarev";
112106
final String creator = "iText 6";
@@ -135,6 +129,38 @@ public void createSimpleCanvas() throws IOException, FileNotFoundException {
135129
reader.close();
136130
}
137131

132+
@Test
133+
public void canvasDrawArcsTest() throws IOException, InterruptedException {
134+
String fileName = "canvasDrawArcsTest.pdf";
135+
String output = destinationFolder + fileName;
136+
String cmp = sourceFolder + "cmp_" + fileName;
137+
138+
try (PdfDocument doc = new PdfDocument(new PdfWriter(output))) {
139+
PdfPage page = doc.addNewPage();
140+
PdfCanvas canvas = new PdfCanvas(page);
141+
142+
canvas.setLineWidth(5);
143+
144+
canvas.setStrokeColor(ColorConstants.BLUE);
145+
canvas.moveTo(10, 300);
146+
canvas.lineTo(50, 300);
147+
canvas.arc(100, 550, 200, 600, 90, -135);
148+
canvas.closePath();
149+
canvas.stroke();
150+
151+
canvas.setStrokeColor(ColorConstants.RED);
152+
canvas.moveTo(210, 300);
153+
canvas.lineTo(250, 300);
154+
canvas.arcContinuous(300, 550, 400, 600, 90, -135);
155+
canvas.closePath();
156+
canvas.stroke();
157+
158+
canvas.release();
159+
}
160+
161+
Assert.assertNull(new CompareTool().compareByContent(output, cmp, destinationFolder, "diff_"));
162+
}
163+
138164
@Test
139165
public void createSimpleCanvasWithDrawing() throws IOException {
140166

layout/src/main/java/com/itextpdf/layout/borders/Border.java

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ public abstract class Border {
6464
*/
6565
public static final Border NO_BORDER = null;
6666

67-
/**
68-
* Value used by discontinuous borders during the drawing process
69-
*/
70-
private static final float CURV = 0.447f;
7167
/**
7268
* The solid border.
7369
*
@@ -129,6 +125,13 @@ public abstract class Border {
129125
*/
130126
public static final int DASHED_FIXED = 9;
131127

128+
private static final int ARC_RIGHT_DEGREE = 0;
129+
private static final int ARC_TOP_DEGREE = 90;
130+
private static final int ARC_LEFT_DEGREE = 180;
131+
private static final int ARC_BOTTOM_DEGREE = 270;
132+
133+
private static final int ARC_QUARTER_CLOCKWISE_EXTENT = -90;
134+
132135
/**
133136
* The color of the border.
134137
*
@@ -487,34 +490,34 @@ protected float getDotsGap(double distance, float initialGap) {
487490
* @param borderWidthAfter defines width of the border that is after the current one
488491
*/
489492
protected void drawDiscontinuousBorders(PdfCanvas canvas, Rectangle boundingRectangle, float[] horizontalRadii, float[] verticalRadii, Side defaultSide, float borderWidthBefore, float borderWidthAfter) {
490-
float x1 = boundingRectangle.getX();
491-
float y1 = boundingRectangle.getY();
492-
float x2 = boundingRectangle.getRight();
493-
float y2 = boundingRectangle.getTop();
493+
double x1 = boundingRectangle.getX();
494+
double y1 = boundingRectangle.getY();
495+
double x2 = boundingRectangle.getRight();
496+
double y2 = boundingRectangle.getTop();
494497

495-
float horizontalRadius1 = horizontalRadii[0];
496-
float horizontalRadius2 = horizontalRadii[1];
498+
final double horizontalRadius1 = horizontalRadii[0];
499+
final double horizontalRadius2 = horizontalRadii[1];
497500

498-
float verticalRadius1 = verticalRadii[0];
499-
float verticalRadius2 = verticalRadii[1];
501+
final double verticalRadius1 = verticalRadii[0];
502+
final double verticalRadius2 = verticalRadii[1];
500503

501504
// Points (x0, y0) and (x3, y3) are used to produce Bezier curve
502-
float x0 = boundingRectangle.getX();
503-
float y0 = boundingRectangle.getY();
504-
float x3 = boundingRectangle.getRight();
505-
float y3 = boundingRectangle.getTop();
505+
double x0 = boundingRectangle.getX();
506+
double y0 = boundingRectangle.getY();
507+
double x3 = boundingRectangle.getRight();
508+
double y3 = boundingRectangle.getTop();
506509

507-
float innerRadiusBefore;
508-
float innerRadiusFirst;
509-
float innerRadiusSecond;
510-
float innerRadiusAfter;
510+
double innerRadiusBefore;
511+
double innerRadiusFirst;
512+
double innerRadiusSecond;
513+
double innerRadiusAfter;
511514

512-
float widthHalf = width / 2;
515+
final double widthHalf = width / 2.0;
513516

514517
Point clipPoint1;
515518
Point clipPoint2;
516519
Point clipPoint;
517-
Border.Side borderSide = getBorderSide(x1, y1, x2, y2, defaultSide);
520+
final Border.Side borderSide = getBorderSide((float) x1, (float) y1, (float) x2, (float) y2, defaultSide);
518521
switch (borderSide) {
519522
case TOP:
520523

@@ -547,9 +550,12 @@ protected void drawDiscontinuousBorders(PdfCanvas canvas, Rectangle boundingRect
547550
y2 += widthHalf;
548551

549552
canvas
550-
.moveTo(x0, y0).curveTo(x0, y0 + innerRadiusFirst * CURV, x1 - innerRadiusBefore * CURV, y1, x1, y1)
551-
.lineTo(x2, y2)
552-
.curveTo(x2 + innerRadiusAfter * CURV, y2, x3, y3 + innerRadiusSecond * CURV, x3, y3);
553+
.arc(x0, y0 - innerRadiusFirst,
554+
x1 + innerRadiusBefore, y1,
555+
ARC_LEFT_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT)
556+
.arcContinuous(x2 - innerRadiusAfter, y2,
557+
x3, y3 - innerRadiusSecond,
558+
ARC_TOP_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT);
553559
break;
554560
case RIGHT:
555561
innerRadiusBefore = Math.max(0, verticalRadius1 - borderWidthBefore);
@@ -580,10 +586,12 @@ protected void drawDiscontinuousBorders(PdfCanvas canvas, Rectangle boundingRect
580586
y2 += innerRadiusAfter;
581587

582588
canvas
583-
.moveTo(x0, y0).curveTo(x0 + innerRadiusFirst * CURV, y0, x1, y1 + innerRadiusBefore * CURV, x1, y1)
584-
.lineTo(x2, y2)
585-
.curveTo(x2, y2 - innerRadiusAfter * CURV, x3 + innerRadiusSecond * CURV, y3, x3, y3);
586-
589+
.arc(x0 - innerRadiusFirst, y0,
590+
x1, y1 - innerRadiusBefore,
591+
ARC_TOP_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT)
592+
.arcContinuous(x2, y2 + innerRadiusAfter,
593+
x3 - innerRadiusSecond, y3,
594+
ARC_RIGHT_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT);
587595
break;
588596
case BOTTOM:
589597
innerRadiusBefore = Math.max(0, horizontalRadius1 - borderWidthBefore);
@@ -614,10 +622,12 @@ protected void drawDiscontinuousBorders(PdfCanvas canvas, Rectangle boundingRect
614622
y2 -= widthHalf;
615623

616624
canvas
617-
.moveTo(x0, y0).curveTo(x0, y0 - innerRadiusFirst * CURV, x1 + innerRadiusBefore * CURV, y1, x1, y1)
618-
.lineTo(x2, y2)
619-
.curveTo(x2 - innerRadiusAfter * CURV, y2, x3, y3 - innerRadiusSecond * CURV, x3, y3);
620-
625+
.arc(x0, y0 + innerRadiusFirst,
626+
x1 - innerRadiusBefore, y1,
627+
ARC_RIGHT_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT)
628+
.arcContinuous(x2 + innerRadiusAfter, y2,
629+
x3, y3 + innerRadiusSecond,
630+
ARC_BOTTOM_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT);
621631
break;
622632
case LEFT:
623633
innerRadiusBefore = Math.max(0, verticalRadius1 - borderWidthBefore);
@@ -648,9 +658,12 @@ protected void drawDiscontinuousBorders(PdfCanvas canvas, Rectangle boundingRect
648658
y2 -= innerRadiusAfter;
649659

650660
canvas
651-
.moveTo(x0, y0).curveTo(x0 - innerRadiusFirst * CURV, y0, x1, y1 - innerRadiusBefore * CURV, x1, y1)
652-
.lineTo(x2, y2)
653-
.curveTo(x2, y2 + innerRadiusAfter * CURV, x3 - innerRadiusSecond * CURV, y3, x3, y3);
661+
.arc(x0 + innerRadiusFirst, y0,
662+
x1, y1 + innerRadiusBefore,
663+
ARC_BOTTOM_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT)
664+
.arcContinuous(x2, y2 - innerRadiusAfter,
665+
x3 + innerRadiusSecond, y3,
666+
ARC_LEFT_DEGREE, ARC_QUARTER_CLOCKWISE_EXTENT);
654667
break;
655668
default:
656669
break;

0 commit comments

Comments
 (0)