Align text to the right in a TextLayout, using Java Graphics2D API - java

So, I'm using the code in Java tutorial to draw a piece of text, but I don't know how to align text to the right margin.
I just included attstring.addAttribute(TextAttribute.RUN_DIRECTION, TextAttribute.RUN_DIRECTION_RTL); in the code for that case but it doesn't work.
protected float drawParagraph (Graphics2D g2, String text, float width, float x, float y, Boolean dir){
AttributedString attstring = new AttributedString(text);
attstring.addAttribute(TextAttribute.FONT, font);
if (dir == TextAttribute.RUN_DIRECTION_RTL){
attstring.addAttribute(TextAttribute.RUN_DIRECTION, TextAttribute.RUN_DIRECTION_RTL);
}
AttributedCharacterIterator paragraph = attstring.getIterator();
int paragraphStart = paragraph.getBeginIndex();
int paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2.getFontRenderContext();
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
// Set break width to width of Component.
float breakWidth = width;
float drawPosY = y;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = (float) (layout.isLeftToRight()
? x : breakWidth - layout.getAdvance());
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
return drawPosY;
}
Give a hand please, I'm lost ^^

The error was in the calculation of drawPosX. The working formula is drawPosX = (float) x + breakWidth - layout.getAdvance();
I ended up doing a little fix to support center alignment and here is the code:
public abstract class MyClass extends JPanel implements Printable{
[...]
public static enum Alignment {RIGHT, LEFT, CENTER};
[...]
/**
* Draw paragraph.
* Pinta un parrafo segun las localizaciones pasadas como parametros.
*
* #param g2 Drawing graphic.
* #param text String to draw.
* #param width Paragraph's desired width.
* #param x Start paragraph's X-Position.
* #param y Start paragraph's Y-Position.
* #param dir Paragraph's alignment.
* #return Next line Y-position to write to.
*/
protected float drawParagraph (Graphics2D g2, String text, float width, float x, float y, Alignment alignment){
AttributedString attstring = new AttributedString(text);
attstring.addAttribute(TextAttribute.FONT, font);
AttributedCharacterIterator paragraph = attstring.getIterator();
int paragraphStart = paragraph.getBeginIndex();
int paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2.getFontRenderContext();
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
// Set break width to width of Component.
float breakWidth = width;
float drawPosY = y;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position.
float drawPosX;
switch (alignment){
case RIGHT:
drawPosX = (float) x + breakWidth - layout.getAdvance();
break;
case CENTER:
drawPosX = (float) x + (breakWidth - layout.getAdvance())/2;
break;
default:
drawPosX = (float) x;
}
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
return drawPosY;
}
}

Related

How do you find out the height and width of a PFont string in Processing or Java?

How do you find out the height and width of a PFont string in Processing or Java?
The best thing you can do when you have a question like this is to read through the Processing reference.
Specifically you're probably looking for the textWidth(), textAscent(), and textDescent() functions.
size(400, 400);
textSize(36);
String str = "Hello world";
float x = 100;
float y = 100;
float strWidth = textWidth(str);
float strAscent = textAscent();
float strDescent = textDescent();
float strHeight = strAscent + strDescent;
rect(x, y - strAscent, strWidth, strHeight);
fill(0);
text(str, x, y);
Using the inbuilt functions textWidth(), textAscent(), and textDescent() are an easy way to get a good approximate result for the height and width of a string, but they are not exact.
Why?
textAscent() returns text height above the line based on the letter 'd'
textDescent() returns text height below the line based on the letter 'p'.
textWidth() includes glyph whitespace (aka padding; ideally we want to ignore this for the first and last characters)
textAscent() + textDescent() therefore measures the maximum height of a string in a given font and font size, and not the height of a specific string. In other words, if your text doesn't include both 'd' and 'p' characters, then using these methods to determine text height will overestimate the result (as we see in Kevin's screenshot).
Getting the exact height
We can use this approach to get an exact result for height:
Get a vector representation of each character
Iterate over the vector's vertices, finding:
The vertex with highest Y position
The vertex with lowest Y position
Subtract the highest Y position from the lowest Y position to determine the exact string height
Code Example
Note you'll need to explicitly create a PFont for this.
String string = "Hello world";
PFont font = createFont("Arial", 96, true); // arial, size 96
textFont(font);
float minY = Float.MAX_VALUE;
float maxY = Float.NEGATIVE_INFINITY;
for (Character c : string.toCharArray()) {
PShape character = font.getShape(c); // create character vector
for (int i = 0; i < character.getVertexCount(); i++) {
minY = min(character.getVertex(i).y, minY);
maxY = max(character.getVertex(i).y, maxY);
}
}
final float textHeight = maxY - minY;
Result
(Note we're still using textWidth() for width here)
text(string, mouseX, mouseY);
rect(mouseX, mouseY, textWidth("Hello world"), -textHeight);
Getting the exact width
Code Example
String string = "Hello world";
PFont font = createFont("Arial", 96, true); // arial, size 96
textFont(font);
float textWidth = textWidth(string); // call Processing method
float whitespace = (font.width(string.charAt(string.length() - 1)) * font.getSize()
- font.getGlyph(string.charAt(string.length() - 1)).width) / 2;
textWidth -= whitespace; // subtract whitespace of last character
whitespace = (font.width(string.charAt(0)) * font.getSize() - font.getGlyph(string.charAt(0)).width) / 2;
textWidth -= whitespace; // subtract whitespace of first character
Result
(Putting the two together...)
text(string, mouseX, mouseY);
rect(mouseX + whitespace, mouseY, textWidth, -textHeight);
Y-Axis Alignment
A rectangle drawn around "Hello world" happens to be aligned because none of the glyphs descend below the baseline.
With a string like ##'pdXW\, both # and p descend below the baseline such that the rectangle, although it is the correct height, is out of alignment with the string on the y-axis, as below:
A programmatic way to determine the y-offset would be to find the Y-coordinate of the lowest (although remember Processing's y-axis extends downwards so we're actually looking for the highest value) vertex . Fortunately, this was calculated as part of finding the exact height.
We can simply use the maxY value that was calculated there to offset the text bounding box.
Result
text(string, mouseX, mouseY);
rect(mouseX + whitespace, mouseY + maxY, textWidth, -textHeight);

Rotate watermark text at 45 degree angle across the center Apache PDFBox

I want to add a text to the PDF using PDFBox API and rotate it by 45 Degree and place it at the center of the page, The text is dynamic and should be placed in the center always, I got everything else to work except centering piece, I'll appreciate any help.
I have this code:
Point2D.Float pageCenter = getCenter(page);
float stringWidth = getStringWidth(watermarkText, font, fontSize);
float textX = pageCenter.x - stringWidth / 2F + center.x;
System.out.println(textX);
float textY = pageCenter.y + center.y;
//System.out.println("Inside cross"+textX+", "+textY);
fontSize = 110.0f;
cs.transform(Matrix.getRotateInstance(Math.toRadians(45), textX, textY));
cs.moveTo(0, 0);
cs.lineTo(125, 0);
r0.setNonStrokingAlphaConstant(0.20f);
This is the result i want:
Output PDF
What I do is to first rotate based on the calculated angle. In this "rotated world" I do a horizontal offset so that the text is in the middle, and also move the text vertically a bit lower, so that it is in the "vertical" middle of an imagined diagonal line (horizontal in the "rotated world").
try (PDDocument doc = new PDDocument())
{
PDPage page = new PDPage();
doc.addPage(page);
PDFont font = PDType1Font.HELVETICA_BOLD;
try (PDPageContentStream cs =
new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true))
// use this long constructor when working on existing PDFs
{
float fontHeight = 110;
String text = "Watermark";
float width = page.getMediaBox().getWidth();
float height = page.getMediaBox().getHeight();
int rotation = page.getRotation();
switch (rotation)
{
case 90:
width = page.getMediaBox().getHeight();
height = page.getMediaBox().getWidth();
cs.transform(Matrix.getRotateInstance(Math.toRadians(90), height, 0));
break;
case 180:
cs.transform(Matrix.getRotateInstance(Math.toRadians(180), width, height));
break;
case 270:
width = page.getMediaBox().getHeight();
height = page.getMediaBox().getWidth();
cs.transform(Matrix.getRotateInstance(Math.toRadians(270), 0, width));
break;
default:
break;
}
float stringWidth = font.getStringWidth(text) / 1000 * fontHeight;
float diagonalLength = (float) Math.sqrt(width * width + height * height);
float angle = (float) Math.atan2(height, width);
float x = (diagonalLength - stringWidth) / 2; // "horizontal" position in rotated world
float y = -fontHeight / 4; // 4 is a trial-and-error thing, this lowers the text a bit
cs.transform(Matrix.getRotateInstance(angle, 0, 0));
cs.setFont(font, fontHeight);
//cs.setRenderingMode(RenderingMode.STROKE); // for "hollow" effect
PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
gs.setNonStrokingAlphaConstant(0.2f);
gs.setStrokingAlphaConstant(0.2f);
gs.setBlendMode(BlendMode.MULTIPLY);
cs.setGraphicsStateParameters(gs);
// some API weirdness here. When int, range is 0..255.
// when float, this would be 0..1f
cs.setNonStrokingColor(255, 0, 0);
cs.setStrokingColor(255, 0, 0);
cs.beginText();
cs.newLineAtOffset(x, y);
cs.showText(text);
cs.endText();
}
doc.save("watermarked.pdf");
}
Note that I've set both stroking and non stroking (= fill). This is useful for people who want to try the (disabled) "hollow" appearance, that one uses stroking only. The default mode is fill, i.e. non-stroking.

How much height does a newLine() occupy in apache pdfBox?

I want to keep track of the y-coordinate when generating pdf.
This is how I am currently doing it.
PDRectangle mediabox = page.findMediaBox();
float margin = 15;
float y = mediabox.getUpperRightY() - margin;
float fontSize = 10f;
PDType1Font font = PDType1Font.HELVETICA;
contentStream.showText("Hello");
y = y - fontSize; //decrease y-coordinate
contentStream.newLine(); //go to new line
contentStream.showText("World!");
y = y - fontSize; //decrease y-coordinate
What is the height of new line so that I can precisely keep track of the y-coordinate?
I need something like this.
contentStream.showText("Hello");
y = y - fontSize; //decrease y-coordinate
contentStream.newLine(); //go to new line
y = y - newLineSize; <---- require the height of new line.
contentStream.showText("World!");
y = y - fontSize; //decrease y-coordinate
Thank you.
The operator created by newLine() starts a new line taking the start of the current line and subtracting the leading from the y coordinate, a value you can set using setLeading.

iText- ColumnText set text fit size in Rectangle

I want to add text into a rectangle(x,y,w,h). Text should be fitted size of rectangle (mean it has a maximum size but it still contains in rectangle).
I tried to measure the text size base on BaseFont.getWidthPoint() The problem is the final text size can't fit the rect. It looks like this:
Here is my try:
PdfContentByte cb = writer.getDirectContent();
cb.saveState();
ColumnText ct = new ColumnText(writer.getDirectContent());
Font font = new Font(BaseFont.createFont());
int rectWidth = 80;
float maxFontSize = getMaxFontSize(BaseFont.createFont(), "text", rectWidth );
font.setSize(maxFontSize);
ct.setText(new Phrase("test", font));
ct.setSimpleColumn(10, 10, rectWidth , 70);
ct.go();
// draw the rect
cb.setColorStroke(BaseColor.BLUE);
cb.rectangle(10, 10, rectWidth , 70);
cb.stroke();
cb.restoreState();
// get max font size base on rect width
private static float getMaxFontSize(BaseFont bf, String text, int width){
float measureWidth = 1;
float fontSize = 0.1f;
float oldSize = 0.1f;
while(measureWidth < width){
measureWidth = bf.getWidthPoint(text, fontSize);
oldSize = fontSize;
fontSize += 0.1f;
}
return oldSize;
}
Could you please tell me where I am wrong?
Another problem, I want to measure for both width and height, which text completely contains in rectangle and has the maximum font size. Is there any way to do this?
Update: here is the complete source code that worked for me:
private static float getMaxFontSize(BaseFont bf, String text, int width, int height){
// avoid infinite loop when text is empty
if(TextUtils.isEmpty(text)){
return 0.0f;
}
float fontSize = 0.1f;
while(bf.getWidthPoint(text, fontSize) < width){
fontSize += 0.1f;
}
float maxHeight = measureHeight(bf, text, fontSize);
while(maxHeight > height){
fontSize -= 0.1f;
maxHeight = measureHeight(bf, text, fontSize);
};
return fontSize;
}
public static float measureHeight(BaseFont baseFont, String text, float fontSize)
{
float ascend = baseFont.getAscentPoint(text, fontSize);
float descend = baseFont.getDescentPoint(text, fontSize);
return ascend - descend;
}
The main issue
The main issue is that you use the wrong arguments in ct.setSimpleColumn:
ct.setSimpleColumn(10, 10, rectWidth , 70);
In contrast to the later cb.rectangle call
cb.rectangle(10, 10, rectWidth , 70);
which has arguments float x, float y, float w, float h (w and h being width and height) the method ct.setSimpleColumn has arguments float llx, float lly, float urx, float ury (ll being lower left and ur being upper right). Thus your ct.setSimpleColumn should look like this:
ct.setSimpleColumn(10, 10, 10 + rectWidth, 10 + 70);
A side issue
In addition to the main issue your result font size is 0.1 too large; essentially this is an error already pointed out by #David.
Your main loop in your getMaxFontSize method is this:
while(measureWidth < width){
measureWidth = bf.getWidthPoint(text, fontSize);
oldSize = fontSize;
fontSize += 0.1f;
}
This essentially results in oldSize (which eventually is returned) being the first font size which does not fit. You could fix this by instead using
while(bf.getWidthPoint(text, fontSize) < width){
oldSize = fontSize;
fontSize += 0.1f;
}
Even better would be an approach only calculating the string width once, not using a loop at all, e.g.
private static float getMaxFontSize(BaseFont bf, String text, int width)
{
int textWidth = bf.getWidth(text);
return (1000 * width) / textWidth;
}
(This method uses integer arithmetic. If you insist on an exact fit, switch to float or double arithmetic.)
There are two bugs here, both in
while(measureWidth < width){
measureWidth = bf.getWidthPoint(text, fontSize++);
}
You're staying in the loop until measureWidth >= width - in other words, by the time you escape from the while loop, measureWidth is already too big for the rectangle.
You're doing fontSize++, which means that after you've used fontSize to calculate measureWidth, you're increasing it. When you do get round to returning it, it's one more than the value you just tested. So the return value from the method will be one more than the last value that you tested (which, due to point 1., was already too big).
I've spent quite a while to implement such functionality using binary search method to find rectangle fitting font size. And today i stumbled upon an interesting method in iText...
It is quite a new method in iText ColumnText;
public float fitText(Font font, String text, Rectangle rect, float maxFontSize, int runDirection)
//Fits the text to some rectangle adjusting the font size as needed.
In my version of iText it is static.
See details: http://developers.itextpdf.com/reference/com.itextpdf.text.pdf.ColumnText

From a string array to a diagram of blocks

I have made a program which takes the results of some SPARQL queries through Jena and saves them in a 2-dimensional string (i.e., 2-dimensional array of Strings). I want to take the values of the first column only and design a diagram of blocks where every block contains every value of the first column and links them successively with each other.
From what I have read, JGraph seems to be pretty helpful for this, but I downloaded it and tried to do it but I failed.
How could I do this with JGraph, or are there other ways?
Here's a method I put together that will draw a rectangle, fill it with a color, and put a String at the center of the rectangle.
/**
* <p>This method will draw a rectangle and place the text in the center
* of the rectangle.</p>
* #param g - Graphics instance from a JPanel paintComponent method
* #param r - Rectangle (origin and dimension) of the rectangle.
* #param c - Fill color of the rectangle.
* #param s - String to place at the center of the rectangle.
*/
public void drawBox(Graphics g, Rectangle r, Color c, String s) {
g.setColor(c);
g.fillRect(r.x, r.y, r.width, r.height);
Graphics2D g2d = (Graphics2D) g;
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout = new TextLayout(s, g.getFont(), frc);
Rectangle2D bounds = layout.getBounds();
int width = (int) Math.round(bounds.getWidth());
int height = (int) Math.round(bounds.getHeight());
int x = r.x + (r.width - width) / 2;
int y = r.y + height + (r.height - height) / 2;
g.setColor(Color.BLACK);
layout.draw(g2d, (float) x, (float) y);
}
You'll have to figure out where you want the rectangles and how to connect them with skinny rectangles.

Categories