Calculate correct width of a text - java

I need to read a plan exported by AutoCAD to PDF and place some markers with text on it with PDFBox.
Everything works fine, except the calculation of the width of the text, which is written next to the markers.
I skimmed through the whole PDF specification and read in detail the parts, which deal with the graphic and the text, but to no avail. As far as I understand, the glyph coordinate space is set up in a 1/1000 of the user coordinate space. Hence the width need to be scale up by 1000, but it's still a fraction of the real width.
This is what I am doing to position the text:
float textWidth = font.getStringWidth(marker.id) * 0.043f;
contentStream.beginText();
contentStream.setTextScaling(1, 1, 0, 0);
contentStream.moveTextPositionByAmount(
marker.endX + marker.getXTextOffset(textWidth, fontPadding),
marker.endY + marker.getYTextOffset(fontSize, fontPadding));
contentStream.drawString(marker.id);
contentStream.endText();
The * 0.043f works as an approximation for one document, but fails for the next.
Do I need to reset any other transformation matrix except the text matrix?
EDIT: A full idea example project is on github with tests and example pdfs: https://github.com/ascheucher/pdf-stamp-prototype
Thanks for your help!

Unfortunately the question and comments merely include (by running the sample project) the actual result for two source documents and the description
The annotating text should be center aligned on the top and bottom marker, aligned to the left on the right marker and aligned to the right on the left marker. The alignment is not working for me, as the font.getSTringWidth( .. ) returns only a fraction of what it seems to be. And the discrepance seems to be different in both PDFs.
but not a concrete sample discrepancy to repair.
There are several issues in the code, though, which may lead to such observations (and other ones, too!). Fixing them should be done first; this may already resolve the issues observed by the OP.
Which box to take
The code of the OP derives several values from the media box:
PDRectangle pageSize = page.findMediaBox();
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
float lineWidth = Math.max(pageWidth, pageHeight) / 1000;
float markerRadius = lineWidth * 10;
float fontSize = Math.min(pageWidth, pageHeight) / 20;
float fontPadding = Math.max(pageWidth, pageHeight) / 100;
These seem to be chosen to be optically pleasing in relation to the page size. But the media box is not, in general, the final displayed or printed page size, the crop box is. Thus, it should be
PDRectangle pageSize = page.findCropBox();
(Actually the trim box, the intended dimensions of the finished page after trimming, might even be more apropos; the trim box defaults to the crop box. For details read here.)
This is not relevant for the given sample documents as they do not contain explicit crop box definitions, so the crop box defaults to the media box. It might be relevant for other documents, though, e.g. those the OP could not include.
Which PDPageContentStream constructor to use
The code of the OP adds a content stream to the page at hand using this constructor:
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);
This constructor appends (first true) and compresses (second true) but unfortunately it continues in the graphics state left behind by the pre-existing content.
Details of the graphics state of importance for the observations at hand:
Transformation matrix - it may have been changed to scale (or rotate, skew, move ...) any new content added
Character spacing - it may have been changed to put any new characters added nearer to or farther from each other
Word spacing - it may have been changed to put any new words added nearer to or farther from each other
Horizontal scaling - it may have been changed to scale any new characters added
Text rise - it may have been changed to displace any new characters added vertically
Thus, a constructor should be chosen which also resets the graphics state:
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);
The third true tells PDFBox to reset the graphics state, i.e. to surround the former content with a save-state/restore-state operator pair.
This is relevant for the given sample documents, at least the transformation matrix is changed.
Setting and using the CalRGB color space
The OP's code sets the stroking and non-stroking color spaces to a calibrated color space:
contentStream.setStrokingColorSpace(new PDCalRGB());
contentStream.setNonStrokingColorSpace(new PDCalRGB());
Unfortunately new PDCalRGB() does not create a valid CalRGB color space object, its required WhitePoint value is missing. Thus, before selecting a calibrated color space, initialize it properly.
Thereafter the OP's code sets the colors using
contentStream.setStrokingColor(marker.color.r, marker.color.g, marker.color.b);
contentStream.setNonStrokingColor(marker.color.r, marker.color.g, marker.color.b);
These (int, int, int) overloads unfortunately use the RG and rg operators implicitly selecting the DeviceRGB color space. To not overwrite the current color space, use the (float[]) overloads with normalized (0..1) values instead.
While this is not relevant for the observed issue, it causes error messages by PDF viewers.
Calculating the width of a drawn string
The OP's code calculates the width of a drawn string using
float textWidth = font.getStringWidth(marker.id) * 0.043f;
and the OP is surprised
The * 0.043f works as an approximation for one document, but fails for the next.
There are two factors building this "magic" number:
As the OP has remarked the glyph coordinate space is set up in a 1/1000 of the user coordinate space and that number is in glyph space, thus a factor of 0.001.
As the OP has ignored he wants the width for the string using the font size he selected. But the font object has no knowledge of the current font size and returns the width for a font size of 1. As the OP selects the font size dynamically as Math.min(pageWidth, pageHeight) / 20, this factor varies. In case of the two given sample documents about 42 but probably totally different in other documents.
Positioning text
The OP's code positions the text like this starting from identity text matrices:
contentStream.moveTextPositionByAmount(
marker.endX + marker.getXTextOffset(textWidth, fontPadding),
marker.endY + marker.getYTextOffset(fontSize, fontPadding));
using methods getXTextOffset and getYTextOffset:
public float getXTextOffset(float textWidth, float fontPadding) {
if (getLocation() == Location.TOP)
return (textWidth / 2 + fontPadding) * -1;
else if (getLocation() == Location.BOTTOM)
return (textWidth / 2 + fontPadding) * -1;
else if (getLocation() == Location.RIGHT)
return 0 + fontPadding;
else
return (textWidth + fontPadding) * -1;
}
public float getYTextOffset(float fontSize, float fontPadding) {
if (getLocation() == Location.TOP)
return 0 + fontPadding;
else if (getLocation() == Location.BOTTOM)
return (fontSize + fontPadding) * -1f;
else
return fontSize / 2 * -1;
}
In case of getXTextOffset I doubt that adding fontPadding for Location.TOP and Location.BOTTOM makes sense, especially in the light of the OP's desire
The annotating text should be center aligned on the top and bottom marker
For the text to be centered it should not be shifted off-center.
The case of getYTextOffset is more difficult. The OP's code is built upon two misunderstandings: It assumes
that the text position selected by moveTextPositionByAmount is the lower left, and
that the font size is the character height.
Actually the text position is positioned on the base line, the glyph origin of the next drawn glyph will be positioned there, e.g.
Thus, the y positioned either has to be corrected to take the descent into account (for centering on the whole glyph height) or only use the ascent (for centering on the above-baseline glyph height).
And a font size does not denote the actual character height but is arranged so that the nominal height of tightly spaced lines of text is 1 unit for font size 1. "Tightly spaced" implies that some small amount of additional inter-line space is contained in the font size.
In essence for centering vertically one has to decide what to center on, whole height or above-baseline height, first letter only, whole label, or all font glyphs. PDFBox does not readily supply the necessary information for all cases but methods like PDFont.getFontBoundingBox() should help.

Related

Java Advanced Imaging: How to get the ImageLayout from a huge image?

I have a couple of huge images which can't be loaded into the memory in whole. I know that the images are tiled and all the methods in the class ImageReader give me plausible non zero return values for
getTileGridXOffset(int),
getTileGridYOffset(int),
getTileWidth(int) and
getTileHeight(int).
My problem now is that I want to read one tile only to avoid having to load the entire image into memory using the ImageReader.readtTile(int, int, int) method. But how do I determine what the valid values for the tile coordinates are?
There is the method getNumXTiles() and getNumYTiles() in the interface RenderedImage but all attempts to create a rendered image from the source results into a out of memory/java heap space error.
The tile coordinates can theoretically be anything and I tried readtTile(0, -1, -1) which also works for a few images I tested.
I also tried to reach the metadata for those images but I didn't find any useful information regarding the image layout.
Is there anyone who can tell me how to get the values for the tile coordinates without having to read the entire image into memory? Is there another way which does not require an instance of ImageLayout?
Thank you very much for your assistance.
First of all, you should check that the ImageReader in question supports tiling for the given image, using the isImageTiled(imageIndex). If it doesn't, you can't expect useful values from the other methods.
Then if it does, all tiles for a given image must be equal in size (but the last tile in each column/the last row may be truncated). This is also the case for all tiled file formats that I know of (ie. TIFF). So, using this knowledge, the number of tiles in both dimensions can be calculated:
// Calculate number of x tiles/y tiles:
int cols = (int) Math.ceil(reader.getWidth(imageIndex) / (double) reader.getTileWidth(imageIndex));
int rows = (int) Math.ceil(reader.getHeight(imageIndex) / (double) reader.getTileHeight(imageIndex));
You can then, loop over the tile indexes (the first tile is always 0,0):
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
BufferedImage tile = reader.readTile(imageIndex, col, row);
// ...do more processing...
}
}
Or, if you only want to get a single tile, you obviously don't need the double for loops. :-)
Note: For ImageReaders/images that don't support tiling, the getTileWidth and getTileHeight methods will just return the same as getWidthand getHeight, respectively.
Also, the readTile API docs says:
If the arguments are out of range, an IllegalArgumentException is thrown. If the image is not tiled, the values 0, 0 will return the entire image; any other values will cause an IllegalArgumentException to be thrown.
This means your example, readtTile(0, -1, -1) should always throw an IllegalArgumentException regardless of the tiling... I suspect some implementations may disregard the tile coordinates completely, and give you the entire image anyway.
PS: The RenderedImage interface could in theory help you. But it would require a special implementation in the ImageReader. In most cases you will just get a normal BufferedImage (which implements RenderedImage), and is a single (1x1) tile.

Strikethrough in cell using itext in Android/Java

I have two numbers one above the other, but the first one must have an Strikethrough, I'm using a table and cell to put both numbers in the table, is there a way to make what I need?
Create a font with the style STRIKETHRU.
Font f = new Font(Font.FontFamily.HELVETICA, 12, Font.STRIKETHRU);
I am adding an extra answer for the sake of completeness.
Please take a look at the SimpleTable6 example:
In the first row, we strike through a number using a STRIKETHRU font as explained by Paulo:
Font font = new Font(FontFamily.HELVETICA, 12f, Font.STRIKETHRU);
table.addCell(new Phrase("0123456789", font));
In this case, iText has made a couple of decisions for you: where do I put the line? How thick is the line?
If you want to make these decisions yourself, you can use the setUnderline() method:
chunk1.setUnderline(1.5f, -1);
table.addCell(new Phrase(chunk1));
Chunk chunk2 = new Chunk("0123456789");
chunk2.setUnderline(1.5f, 3.5f);
table.addCell(new Phrase(chunk2));
If you pass a negative value for the y-offset parameter, the Chunk will be underlined (see first column). You can also use this method to strike through text by passing a positive y-offset.
As you can see, we also defined the thickness of the line (1.5f). There is another setUnderline() method that also allows you to pass the following parameters:
color - the color of the line or null to follow the text color
thickness - the absolute thickness of the line
thicknessMul - the thickness multiplication factor with the font size
yPosition - the absolute y position relative to the baseline
yPositionMul - the position multiplication factor with the font size
cap - the end line cap. Allowed values are PdfContentByte.LINE_CAP_BUTT, PdfContentByte.LINE_CAP_ROUND and PdfContentByte.LINE_CAP_PROJECTING_SQUARE
See http://api.itextpdf.com/itext/com/itextpdf/text/Chunk.html

Collisions between rectangles

I have been trying to solve this for a few hours, and the internet is pretty unfruitful on the subject.
I need help detecting and solving collisions between rectangles, and not just detecting, but note I mentioned solving as well.
These are two boxes, with x/y width/heights. I simply need to detect when they are overlapping, and push one of the boxes out of the other one smoothly.
Also, note that one box is stationary - and the other is moving.
Does anyone have anything on this (or can give me an example?) I'd really appreciate it.
I need the boxes to be able to rest on top of each other as well.
Thank you!
I'm not sure what the context here is (Are these boxes moving or stationary? Are you looking for a physically accurate resolution, or simply a geometrically correct one?), but it seems like you could accomplish this in the following way:
1) Determine if there is a box collision
2) Determine the intersection of the two boxes, which would produce a third box. The width and height of the box is your penetration depth.
3) move the center of one of the boxes by the penetration depth, (x - width, y - height).
This should cause the boxes to become disjoint.
FYI: Intersection of two boxes can be computed by taking the max of the mins and the mins of the maxes from both boxes.
Here is some code from my engine for box intersection:
bool Bounds::IntersectsBounds(const Bounds &other) const
{
return !(min.x > other.max.x || max.x < other.min.x
|| min.y > other.max.y || max.y < other.min.y);
}
bool Bounds::Intersection(const Bounds &other, Bounds &outBounds) const
{
if (!this->IntersectsBounds(other)) {
return false;
}
outBounds.min.x = std::max(min.x, other.min.x);
outBounds.min.y = std::max(min.y, other.min.y);
outBounds.max.x = std::min(max.x, other.max.x);
outBounds.max.y = std::min(max.y, other.max.y);
return true;
}
In this case, the "outBounds" variable is the intersection of the two boxes (which in this case is your penetration depth). You can use the width/height of this box to perform your collision resolution.
Yeah! This is a pretty common problem! You may want to check out the gamedev portion of the stack exchange network!
Detection
bool collide(float x1,float y1,float sx1,float sy1, float x2, float y2, float sx2, float sy2){
if (x1+sx1 <= x2)
return false;
if (x2+sx2 <= x1)
return false;
if (y1+sy1 <= y2)
return false;
if (y2+sy2 <= y1)
return false;
return true;
}
Resolution
As far as an answer, this depends on the type of application you are going for. Is it a sidescroller, top-down, tile based? The answer depends on the response to this question. I'll assume something dynamic like a sidescroller or top-down action game.
The code is not difficult, but the implementation can be. If you have few objects moving on the screen you can use a similar system to mine, which goes something like the following:
Get a list of objects you are currently colliding with, in order of distance from the current object.
Iterate through the objects, and resolve collisions using the following method
Check if the object has some special collision type (teleporter, etc) by sending that object a message, and checking on the return value (a teleporter will take care of the collision resolution)
check if the previous bottom position of our current object (A) was above the top side of the object in question(B), if so that means you have had a bottom collision. Resolve by setting the y position of A to the y position of B minus the height of A
(IF THE PREVIOUS FAILED) check if the previous right side of A was to the left of the left side of B, if so that means you have had a right side collision. Resolve by setting the x position of A to B's position minus A's width
(IF THE PREVIOUS FAILED) check if the previous left side of A was to the right of the right side of B, if so that means you have had a left side collision. Resolve by setting the x position of A to B's x position plus B's width
(IF THE PREVIOUS FAILED) check if the previous top side of A was below the bottom side of B, if so you have had a top side collision. Resolve by setting the y position of A to the y position of B plus B's height
Whew. It is important that you have the objects sorted according to distance, it will catch on edges if you check collisions with an object that is farther away!
I hope that makes sense!
Edit: Apparently doesn't work in Android.
https://stackoverflow.com/a/15515114/3492994
Using the available classes from the 2D Graphics API.
Rectangle r1 = new Rectangle(100, 100, 100, 100);
Line2D l1 = new Line2D.Float(0, 200, 200, 0);
System.out.println("l1.intsects(r1) = " + l1.intersects(r1));
What this doesn't tell you, is where...

java print job cutting off the edge of the page

An application uses a jEditorPane to display html pages, which also has the ability to print said html page. We construct the MediaPrintableArea for the printerJob attributeSet like so:
float mediaWidth = mediaSize.getX(Size2DSyntax.MM);
float mediaHeight = mediaSize.getY(Size2DSyntax.MM);
float imageableX = 18;
float imageableY = 25;
float imageableWidth = (mediaWidth - (2 * imageableX));
float imageableHeight = (mediaHeight - (2 * imageableY));
MediaPrintableArea imageableArea = new MediaPrintableArea(imageableX, imageableY, imageableWidth, imageableHeight, Size2DSyntax.MM);
So we control the printable area of the page. However, when the moons align and a single line is just the right length, the end of the last character in the line is being cut off.
EX: if a line ends with the word "to", there will only be the left-most half of the 'o' visible on the printed page. I would expect if this were to run off the edge of the printable are, "to" would wrap to the next line, but its not.
Is there some other method of defining the printable area besides using the MediaPrintableArea? Is there anything that can be causing the words to not wrap or how java calculates the placement of the words?
We've also tested several other printers and printed from browsers where we can print beyond there our java print job is cutting off, so I don't think hardware problems should be considered.
You're probably rendering the JEditorPane starting from (0, 0) instead of from PageFormat.getImageableX(), PageFormat.getImageableY(). See http://java.sun.com/developer/onlineTraining/Programming/JDCBook/advprint.html for more information.

How to change line thickness in iText?

I'm drawing images to pdf using Java framework iText. I need to draw lines of specified width. There is a method setLineWidth(float width) in class PdfContentByte that should change it. However no matter what value I pass as its parameter the lines drawn are always extra thin.
There is following line in javadoc of setLineWidth:
The line width specifies the thickness of the line used to stroke a path and is measured in user space units.
I don't know what is "space unit". Everything else in iText seems to be measured in point(around 1/72 inch). I cant find any reference to what are those "space units" and how to change them.
code:
to.setLineWidth(thickness);
to.moveTo(x, y);
to.lineTo(x + 100, y + 100);
Variable to contains instance of PdfContentByte.
Solved. There was no stroke method call after lineTo call. That's why it used another line width set just before stoke method was called.
Correct code look like this:
to.setLineWidth(thickness);
to.moveTo(x, y);
to.lineTo(x + 100, y + 100);
to.stroke();

Categories

Resources