I need to rotate a link rectangle using Java iText.
The original link rectangle appears in red. The rotated link rectangle appears in green.
My code:
PdfReader reader = new PdfReader( "input/blank.pdf" );
PdfStamper stamper = new PdfStamper( reader, new FileOutputStream( "output/blank_stamped.pdf" ) );
Rectangle linkLocation = new Rectangle( 100, 700, 100 + 200, 700 + 25 );
PdfName highlight = PdfAnnotation.HIGHLIGHT_INVERT;
PdfAnnotation linkRed = PdfAnnotation.createLink( stamper.getWriter(), linkLocation, highlight, "red" );
PdfAnnotation linkGreen = PdfAnnotation.createLink( stamper.getWriter(), linkLocation, highlight, "green" );
BaseColor baseColorRed = new BaseColor(255,0,0);
BaseColor baseColorGreen = new BaseColor(0,255,0);
linkRed.setColor(baseColorRed);
linkGreen.setColor(baseColorGreen);
double angleDegrees = 10;
double angleRadians = Math.PI*angleDegrees/180;
stamper.addAnnotation(linkRed, 1);
linkGreen.applyCTM(AffineTransform.getRotateInstance(angleRadians));
stamper.addAnnotation(linkGreen, 1);
stamper.close();
But this code does not rotate the recangle.
Please take a look at the following screen shot:
I have added 5 annotations to a simple Hello World file.
The first two are link annotations. Their position is defined by the rectangles linkLocation1 and linkLocation2:
Rectangle linkLocation1 = new Rectangle(30, 770, 120, 800);
PdfAnnotation link1 = PdfAnnotation.createLink(stamper.getWriter(),
linkLocation1, PdfAnnotation.HIGHLIGHT_INVERT, action);
link1.setColor(BaseColor.RED);
stamper.addAnnotation(link1, 1);
Rectangle linkLocation2 = new Rectangle(30, 670, 60, 760);
PdfAnnotation link2 = PdfAnnotation.createLink(stamper.getWriter(),
linkLocation2, PdfAnnotation.HIGHLIGHT_INVERT, action);
link2.setColor(BaseColor.GREEN);
stamper.addAnnotation(link2, 1);
The green rectangle looks like a rotated version of the red rectangle, but that's not really true: we just defined the "clickable" area that way. I don't understand why you'd want to get this effect by introducing a rotation. Why? Because a rotation always needs a rotating point. Suppose that you would introduce a rotation, what would be your rotation point? The (0, 0) coordinate? That would lead to strange results, wouldn't it?
Introducing a rotation for does make sense for some types of annotations though. In my example, I introduced three stamp annotations:
Rectangle linkLocation3 = new Rectangle(150, 770, 240, 800);
PdfAnnotation stamp1 = PdfAnnotation.createStamp(stamper.getWriter(), linkLocation3, "Landscape", "Confidential");
stamper.addAnnotation(stamp1, 1);
Rectangle linkLocation4 = new Rectangle(150, 670, 240, 760);
PdfAnnotation stamp2 = PdfAnnotation.createStamp(stamper.getWriter(), linkLocation4, "Portrait", "Confidential");
stamp2.setRotate(90);
stamper.addAnnotation(stamp2, 1);
Rectangle linkLocation5 = new Rectangle(250, 670, 340, 760);
PdfAnnotation stamp3 = PdfAnnotation.createStamp(stamper.getWriter(), linkLocation5, "Portrait", "Confidential");
stamp3.setRotate(45);
stamper.addAnnotation(stamp3, 1);
In this case, I introduce a rotation angle using the setRotate() method. This rotates the CONFIDENTIAL stamp inside the rectangle we defined. As you can see, this makes sense because the annotation does have actual content: the rotation has an impact on the way you read the word CONFIDENTIAL. In the case of the clickable area of the link annotation, there is no such content to be rotated.
If this doesn't answer your question, please rephrase your question because I don't think anyone can answer it in its current state.
Update
Please take a look at ISO-32000-1 aka the PDF specification. You'll discover that a rectangle is defined using 4 values: the x and y coordinate of the lower-left corner of the rectangle and the x and y coordinate of the upper-right corner of the rectangle. These are the two starting points of the horizontal and vertical sides. You want a rectangle that has sides that aren't horizontal/vertical. Obviously that isn't possible as you'd need the coordinates of 4 corner points to achieve that (8 values, not 4). You can achieve this using a polygon defined by QuadPoints.
See ITextShape Clickable Polygon or path
Related
I currently have an application that onClick will draw a green bounding rectangle around the battery and the blue strip of paper. I would also like to have the button onClick draw a line from the battery to the strip of paper(as shown in second picture below). Currently I am able to get the all the x and y values of the rectangles, thus knowing that I need to draw a line from 534,1261 to 788,1261 and have the line labeled with the x difference as shown in picture.
For drawing lines and text You can use code like that:
Point firstPoint = new Point(100, 200);
Point secondPoint = new Point(100, 400);
Point middlePoint = new Point(firstPoint.x,
firstPoint.y + 0.5 * (secondPoint.y - firstPoint.y));
Scalar lineColor = new Scalar(255, 0, 0, 255);
int lineWidth = 3;
Scalar textColor = new Scalar(255, 0, 0, 255);
Imgproc.line(sourceMat, firstPoint, secondPoint, lineColor, lineWidth);
Imgproc.putText(sourceMat, " Text" , middlePoint,
Core.FONT_HERSHEY_PLAIN, 1.5 , textColor);
Where sourceMat - Mat with image.
And for determining of line "height" in cm (approximately) You should use "height" of battery rectangle :
lineHeightCm = 4.46 / heightOfBatteryRectangleInPixels * lineHeightInPixels;
where 4.46 - "height" of AAA battery in cm.
So I have this program to test the possibility of an object to slide down in a ramp given its friction, object mass and ramp angle. However I need to animate the box if the force is positive. Just a simple animation moving the box from that point to the end of the ramp. But I can't. Please help
private void drawTransform(Graphics g, double modifier) {
// redtowhite = new GradientPaint(0,0,color.RED,100, 0,color.WHITE);
Rectangle rect = new Rectangle(130,350, 350, 15);
Rectangle box = new Rectangle((int) (rect.getX()+300), 300, 50, 50);
AffineTransform at = new AffineTransform();
at.rotate(-Math.toRadians(modifier), rect.getX(), rect.getY() + rect.height);
// Transform the shape and draw it to screen
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.DARK_GRAY);
// g2d.fillRect(0, 0, 350, 600);
g2d.fill(at.createTransformedShape(rect));
g2d.draw(at.createTransformedShape(box));
}
Screenshot:
If all you want to do is move the box, this can be done by simply updating it's X position. You should be able to manipulate the rectangle's X position directly using something like "box.x++". Alternatively you could create a variable and reference that to provide the initial X co-ordinate, then updating that variable will "move" the box. One issue is this will only move the box along the X axis, hence you will also need some kind of constant downward force acting as gravity. This is easy to achieve, just minus the box's Y position value when it is not colliding with the ground, or your ramp.
Another approach is velocity based movement using vectors, however you mentioned that the animation should be simple. If you do want a smoother animation velocity based movement will provide this but you will need to perform a little research first.
The documentation for the clearRect method of GraphicsContext states that it uses the current clip, but this isn't currently working for me. Consider:
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.rect(0,0,100,100); //Set the current path to a rectangle
context.stroke(); //Highlights where the current path is
context.clip(); //Intersect current clip with rectangle
context.fillOval(80, 80, 40, 40); //This correctly draws the oval clipped
context.clearRect(0,0,100,100); //This does nothing at all
The above code sets the clip mask correctly, as evidenced by the fact that fillOval works correctly, however clearRect does nothing (although it works normally without the context.clip()). Why is this?
(Note that I specifically need the clip mask to be working, as later I plan on setting it to specific shapes to erase in non-rectangular shapes.)
-- Edit --
To be clear, clearRect does literally nothing, not even erase the oval. I realise that it won't erase the stroked rectangle but that's not what I'm concerned about.
-- Edit 2 --
Updating to the latest JDK has partially fixed the issue. The above code now works correctly. However, using a non-rectangular clip mask still has the same problem. e.g.
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.arc(50, 50, 40, 40, 0, 360); // Make a circular clip mask
context.closePath();
context.clip();
context.fillRect(0, 0, 200, 200); //Draw a circle clipped correctly, shows clip mask is working
context.clearRect(0, 0, 200, 200); //Does nothing
I realise I could use save and restore to get a rectangular clip mask back, and then clearRect would work. However I want to be able to erase in non-rectangular shapes.
Full code for reproducing this is (created by making a new JavaFX project in eclipse and adding the above lines):
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
Canvas canvas = new Canvas(500, 500);
root.getChildren().add(canvas);
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.arc(50, 50, 40, 40, 0, 360);
context.closePath();
context.clip();
context.fillRect(0, 0, 200, 200);
context.clearRect(0, 0, 200, 200);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This should show a blank screen, but the circle is not being cleared.
As per Edit 2, this behaviour seems to be a bug. In summary, clearRect has no effect when a non-rectangular clip is set. (If it isn't working for you even with a rectangular clip, update to the latest JDK.) I've filed a bug report for this.
While i agree that this should be fixed, but am facing the fact that it just is not, i have come up with a solution that meets my specific needs at least.
It is a method that clears an area based on an SVGPath using the GraphicsContext.
private void clearPath(GraphicsContext gc, SVGPath path) {
int xstart = (int) path.getLayoutX();
int xend = (int) (xstart + path.getLayoutBounds().getMaxX());
int ystart = (int) path.getLayoutY();
int yend = (int) (ystart + path.getLayoutBounds().getMaxY());
PixelWriter pw = gc.getPixelWriter();
for (int x = xstart; x <= xend; x++) {
for (int y = ystart; y <= yend; y++) {
if(path.contains(new Point2D(x, y))) {
pw.setColor(x, y, Color.TRANSPARENT);
}
}
}
}
The code works just fine on Windows 7 with JavaFX 8u40.
What you should do is to provide a MCVE. Nobody can possibly guess what else you've done with the GraphicsContext. There may be a save and restore missing and what not. By the way, your code lacks a closePath().
Answer before your edit:
The problem you are facing is the way JavaFX draws the lines. Check out the documentation of the Node class:
At the device pixel level, integer coordinates map onto the corners
and cracks between the pixels and the centers of the pixels appear at
the midpoints between integer pixel locations. Because all coordinate
values are specified with floating point numbers, coordinates can
precisely point to these corners (when the floating point values have
exact integer values) or to any location on the pixel. For example, a
coordinate of (0.5, 0.5) would point to the center of the upper left
pixel on the Stage. Similarly, a rectangle at (0, 0) with dimensions
of 10 by 10 would span from the upper left corner of the upper left
pixel on the Stage to the lower right corner of the 10th pixel on the
10th scanline. The pixel center of the last pixel inside that
rectangle would be at the coordinates (9.5, 9.5).
That's why you have a thin line on the right and at the bottom. The lines aren't crisp.
I suggest you move the rectangle to the center and also use the fill method to make this more visible for you.
Your code without clearRect:
Your code with clearRect:
I am trying to draw a rectangle around multiline text in iText.
The user will be able to enter some lines of text. The font size of the text might be different and it can be formatted (bold, underlined...).
I use this code to draw the text:
ColumnText ct = new ColumnText(cb);
Phrase phrase = new Phrase("Some String\nOther string etc...\n test");
ct.setSimpleColumn(myText......);
ct.addElement(phrase);
ct.go();
I know how to draw a rectangle, but I am not able to draw a rectangle outlining this text.
It sounds as if you are missing only a single piece of the puzzle to meet your requirement. That piece is called getYLine().
Please take a look at the DrawRectangleAroundText example. This example draws the same paragraph twice. The first time, it adds a rectangle that probably looks like the solution you already have. The second time, it adds a rectangle the way you want it to look:
The first time, we add the text like this:
ColumnText ct = new ColumnText(cb);
ct.setSimpleColumn(120f, 500f, 250f, 780f);
Paragraph p = new Paragraph("This is a long paragraph that doesn't"
+ "fit the width we defined for the simple column of the"
+ "ColumnText object, so it will be distributed over several"
+ "lines (and we don't know in advance how many).");
ct.addElement(p);
ct.go();
You define your column using the coordinates:
llx = 120;
lly = 500;
urx = 250;
ury = 780;
This is a rectangle with lower left corner (120, 500), a width of 130 and a height of 380. Hence you draw a rectangle like this:
cb.rectangle(120, 500, 130, 280);
cb.stroke();
Unfortunately, that rectangle is too big.
Now let's add the text once more at slightly different coordinates:
ct = new ColumnText(cb);
ct.setSimpleColumn(300f, 500f, 430f, 780f);
ct.addElement(p);
ct.go();
Instead of using (300, 500) as lower left corner for the rectangle, we ask the ct object for its current Y position using the getYLine() method:
float endPos = ct.getYLine() - 5;
As you can see, I subtract 5 user units, otherwise the bottom line of my rectangle will coincide with the baseline of the final line of text and that doesn't look very nice. Now I can use the endPos value to draw my rectangle like this:
cb.rectangle(300, endPos, 130, 780 - endPos);
cb.stroke();
I try to draw text inside rectangle which fit rectangle size, like my previous question, I want text align center in rectangle.
The problem is display text has wrong Y coordinate, look like this one:
And here is my code:
PdfContentByte cb = writer.getDirectContent();
Rectangle rect = new Rectangle(100, 150, 100 + 120, 150 + 50);
cb.saveState();
ColumnText ct = new ColumnText(writer.getDirectContent());
Font font = new Font(BaseFont.createFont());
float maxFontSize;
// try to get max font size that fit in rectangle
font.setSize(maxFontSize);
ct.setText(new Phrase("test", font));
ct.setSimpleColumn(rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop());
ct.go();
// draw the rect
cb.setColorStroke(BaseColor.BLUE);
cb.rectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight());
cb.stroke();
cb.restoreState();
I even draw text like this:
cb.saveState();
cb.beginText();
cb.moveText(rect.getLeft(), rect.getBottom());
cb.setFontAndSize(BaseFont.createFont(), maxSize);
cb.showText("test");
cb.endText();
cb.setColorStroke(BaseColor.BLUE);
cb.rectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight());
cb.stroke();
And got the result:
So I wonder how can itext render text base on the coordinates? Because I use the same rectangle frame for text and rectangle bound.
I'm not sure if I understand your question correctly. I'm assuming you want to fit some text into a rectangle vertically, but I don't understand how you calculate the font size, and I don't see you setting the leading anywhere (which you can avoid by using ColumnText.showAligned()).
I've created an example named FitTextInRectangle which results in the PDF chunk_in_rectangle.pdf. Due to rounding factors (we're working with float values), the word test slightly exceeds the rectangle, but the code shows how to calculate a font size that makes the text fit more or less inside the rectangle.
In your code samples, the baseline is defined by the leading when using ColumnText (and the leading is wrong) or the bottom coordinate of the rectangle when using showText() (and you forgot to take into account value of the descender).