graph from html looking distorted when converting to pdf using itext - java

I am trying to convert html to pdf using itext Api but the issue is that my html has an element as well. so when I see the generated pdf the graph i.e in element looks distorted. my code in paint method is as below
#Override
public void paint(RenderingContext renderingContext, ITextOutputDevice outputDevice, BlockBox blockBox) {
PdfContentByte cb = outputDevice.getWriter().getDirectContent();
float width = (float) (cssWidth / outputDevice.getDotsPerPoint());
float height =(float) (cssHeight / outputDevice.getDotsPerPoint());
System.out.println("width :"+width+" height:"+height+" outputDevice.getDotsPerPoint(): "+outputDevice.getDotsPerPoint());
PdfTemplate template = cb.createTemplate(width, height);
Graphics2D g2d = template.createGraphics(width, height);
PrintTranscoder prm = new PrintTranscoder();
TranscoderInput ti = new TranscoderInput(svg);
prm.transcode(ti, null);
PageFormat pg = new PageFormat();
Paper pp = new Paper();
pp.setSize(width, height);
pp.setImageableArea(0, 0, width, height);
pg.setPaper(pp);
prm.print(g2d,pg, 0);
g2d.dispose();
PageBox page = renderingContext.getPage();
float x = blockBox.getAbsX() + page.getMarginBorderPadding(renderingContext, CalculatedStyle.LEFT);
float y = (page.getBottom() - (blockBox.getAbsY() + cssHeight)) + page.getMarginBorderPadding(renderingContext, CalculatedStyle.BOTTOM);
System.out.println("x " + x + " y" + y + " outputDevice.getDotsPerPoint() " + outputDevice.getDotsPerPoint());
x /= outputDevice.getDotsPerPoint();
y /= outputDevice.getDotsPerPoint();
cb.addTemplate(template, x, y);
System.out.println("Done with addition " + x + " " + y);
}
Now the changes that I observed is when I change float width = 400 and height = 620 then I see my graph coming but the top section disappears.
Can someone help me out with this issue. Also I think there must be some formulae for height and width so that the graph wont distort.
Thanks
The html that I am trying to convert is like this http://tinypic.com/r/e0o4xz/8 and the pdf I am getting is like http://i60.tinypic.com/14sz6kp.jpg So as you can see the pdf I am getting has missing x axis and the last bar of the chart is also missing. So the code part is like
float width = (float) (cssWidth / outputDevice.getDotsPerPoint());
float height = (float) (cssHeight / outputDevice.getDotsPerPoint());
So when I comment change the cssWidth=22000 and cssHeight=12000 then the chart comes with axis and the last bar is also coming thats in red but the alignment is distorted.

Related

How to add a text above a previous added image and center it above this image with pdfbox?

My aim is to add a image to a pdf and write a text above this image. I have centered the image and the text should be center above the image with a little margin to the image.
Currently the image will be added and centered but the text is not centered.
Here my current code. The interesting part is where the method drawTitleAtTop will be called. Here i have added the height of the newly added image to the y postion plus a margin of 3. The x coordinate I calculate depending on the incoming text but there is some miscalculation. Any advice?
private static void addScaledImage(ImageData imgData, PDDocument pdDocument, Dimension thePdfDimension) {
ImageHelper helper = Scalr::resize;
byte[] scaledImage = ImageUtils.resizeImageKeepAspectRatio(helper, imgData.getImageBinary(), thePdfDimension.width);
PDRectangle rectangle = pdDocument.getPage(0).getMediaBox();
PDPage page = new PDPage(rectangle);
pdDocument.addPage(page);
PDImageXObject pdImage = null;
try {
pdImage = PDImageXObject.createFromByteArray(pdDocument, scaledImage, null);
LOG.debug("size of scaled image is x: {0} y {1}", pdImage.getWidth(), pdImage.getHeight());
int xForImage = (thePdfDimension.width - pdImage.getWidth()) / 2 ;
int yForImage = (thePdfDimension.height - pdImage.getHeight()) / 2;
LOG.debug("new x {0} new y {1}", xForImage, yForImage);
try (PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, AppendMode.APPEND, true, true)) {
if (StringUtils.isNotBlank(imgData.getTitle())) {
yForImage = xForImage - 20;
contentStream.drawImage(pdImage, xForImage, yForImage, pdImage.getWidth(), pdImage.getHeight());
drawTitelAtTop(imgData, page, xForImage , yForImage + pdImage.getHeight() + 3, contentStream);
} else {
contentStream.drawImage(pdImage, xForImage, yForImage, pdImage.getWidth(), pdImage.getHeight());
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void drawTitelAtTop(ImageData imgData, PDPage page, int x, int y, PDPageContentStream contentStream) throws IOException {
PDFont font = PDType1Font.COURIER;
int fontSize = FONT_SIZE_FOR_TITLE;
float titleWidth = font.getStringWidth(imgData.getTitle()) / 1000 * fontSize;
LOG.debug("title width is " + titleWidth);
contentStream.setFont(font, fontSize);
contentStream.beginText();
float tx = ((x - titleWidth) / 2) + x;
//float tx = x;
//float ty = page.getMediaBox().getHeight() - marginTop + (marginTop / 4);
float ty = y;
LOG.debug("title offset x {0} y {1}", tx, ty);
contentStream.newLineAtOffset(tx,
ty);
contentStream.showText(imgData.getTitle());
contentStream.endText();
}

Rotate text using pdfbox

I'm trying to rotate text using pdfbox by I couldn't achieve it. I tried to set the texMatrix but my text is not rotating as intended.
Does someone have an idea of how I could turn at 90 degrees my text?
This is my code :
contentStream.beginText();
float tx = titleWidth / 2;
float ty = titleHeight / 2;
contentStream.setTextMatrix(Matrix.getTranslateInstance(tx, ty));
contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(90),tx,ty));
contentStream.setTextMatrix(Matrix.getTranslateInstance(-tx, -ty));
contentStream.newLineAtOffset(xPos, yPos);
contentStream.setFont(font, fontSize);
contentStream.showText("Tets");
contentStream.endText();
Thank You
Here's a solution that draws three pages, one with text unrotated, one with text rotated but keeping the coordinates as if planning landscape printing, and one that is what you wanted (rotated around the center of the text). My solution is close to that, it rotates around the bottom of the center of the text.
public static void main(String[] args) throws IOException
{
PDDocument doc = new PDDocument();
PDPage page1 = new PDPage();
doc.addPage(page1);
PDPage page2 = new PDPage();
doc.addPage(page2);
PDPage page3 = new PDPage();
doc.addPage(page3);
PDFont font = PDType1Font.HELVETICA;
float fontSize = 20;
int xPos = 100;
int yPos = 400;
float titleWidth = font.getStringWidth("Tets") / 1000;
float titleHeight = fontSize;
float tx = titleWidth / 2;
float ty = titleHeight / 2;
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page1))
{
contentStream.beginText();
contentStream.newLineAtOffset(xPos, yPos);
contentStream.setFont(font, fontSize);
contentStream.showText("Tets");
contentStream.endText();
}
// classic case of rotated page
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page2))
{
contentStream.beginText();
Matrix matrix = Matrix.getRotateInstance(Math.toRadians(90), 0, 0);
matrix.translate(0, -page2.getMediaBox().getWidth());
contentStream.setTextMatrix(matrix);
contentStream.newLineAtOffset(xPos, yPos);
contentStream.setFont(font, fontSize);
contentStream.showText("Tets");
contentStream.endText();
}
// rotation around text
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page3))
{
contentStream.beginText();
Matrix matrix = Matrix.getRotateInstance(Math.toRadians(90), 0, 0);
matrix.translate(0, -page3.getMediaBox().getWidth());
contentStream.setTextMatrix(matrix);
contentStream.newLineAtOffset(yPos - titleWidth / 2 - fontSize, page3.getMediaBox().getWidth() - xPos - titleWidth / 2 - fontSize);
contentStream.setFont(font, fontSize);
contentStream.showText("Tets");
contentStream.endText();
}
doc.save("saved.pdf");
doc.close();
}
This example rotates around the left baseline of the text and uses the matrix translation to position the text at the specific point.
The showText() is always positioned at 0,0, which is the position before the rotation. The matrix translation then positions the text after the rotation.
If you want another rotation point of your text relocation the text rotation position in the contentStream.newLineAtOffset(0, 0)-line
float angle = 35;
double radians = Math.toRadians(angle);
for (int x : new int[] {50,85,125, 200})
for (int y : new int[] {40, 95, 160, 300}) {
contentStream.beginText();
// Notice the post rotation position
Matrix matrix = Matrix.getRotateInstance(radians,x,y);
contentStream.setTextMatrix(matrix);
// Notice the pre rotation position
contentStream.newLineAtOffset(0, 0);
contentStream.showText(".(" + x + "," + y + ")");
contentStream.endText();
}
To get the height and the width of the text you want to rotate use font.getBoundingBox().getHeight()/1000*fontSize and font.getStringWidth(text)/1000*fontSize.

Make string centered in rectangle

I have coordinates of rectangle to draw and I want to centre some text inside this rectangle.
int x, y, width, height;
String str = "This is a text";
x = 15;
y = 15;
width = 20;
heights = 30;
g.drawRect(x, y, width, height);
g.drawString(str, x + width/2, y + height/2);
If you want to center the text then you need to know the length of the text so you know its width relative to the width of the rectangle. This is done by getting the FontMetrics instance from the Graphics object.
So the basic code would be:
FontMetrics fm = g.getFontMetrics();
int stringWidth = fm.getStringWidth(...);
int xDiff = (width - stringWidth) / 2;
g.drawString(str, x + xDiff, ...);
Of course you will also need to center based on the height.
Doc says...
public abstract void drawString(String theString,
int x,
int y)
Renders the text of the specified iterator applying its attributes in accordance with the specification of the TextAttribute class.
The baseline of the leftmost character is at position (x, y) in this graphics context's coordinate system.
So... you are drawing your string starting at the center point of the rect but not the string.
https://docs.oracle.com/javase/7/docs/api/java/awt/FontMetrics.html - get the font metrics from the gc and figure out how wide the string is. Then subtract half of that from the start x. Do something similar for y (remember that height doesn't start at base.)

Draw text directly under a specified bitmap

p.setColor(Color.parseColor("#D32F2F"));
RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
c.drawRect(background, p);
icon = getBitmapFromVectorDrawable(MainActivity.this, R.drawable.ic_clear);
RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBot
c.drawBitmap(icon, null, icon_dest, p); //[1]
p.setTextSize(50);
p.setColor(Color.WHITE);
c.drawText("Cancel",*/Insert x and y here*/,p);
So far I have that.
I'm trying to drawText directly under the bitmap I drawed. ([1]). However I'm not sure how to get the coordinates of the bitmap and adjust it so that it's centered below it.
The above image is what I'm trying to achieve. Contents of the app has been censored.
In conclusion, how do I get the coordinates for the text that I'm trying to get?
I'm using onChildDraw method of the ItemTouchHelper for a RecyclerView.
You can use paint.getTextBounds() to get the bounds of the text that you are about to draw on the canvas. Once you get the bounds of the text, you can calculate the position to draw the text based on the icon's position.
Edit:
This aligns the text below the icon at the X position same as the icon:
float textX = icon_dest.left;
float textY = icon_dest.bottom + some_padding;
canvas.drawText("Cancel", textX, textY, textPaint);
This aligns the center of the icon and the center of the text in a same vertical line:
Rect textBounds = new Rect();
textPaint.getTextBounds("Cancel", 0, length, textBounds);
float iconCenterX = icon_dest.left + (icon_dest.width() / 2);
float textHalfWidth = (textBounds.right - textBounds.left) / 2;
float textX = iconCenterX - textHalfWidth;
float textY = icon_dest.bottom + somePadding
canvas.drawText("Cancel", textX, textY, textPaint);

Drawing an image using sub-pixel level accuracy using Graphics2D

I am currently attempting to draw images on the screen at a regular rate like in a video game.
Unfortunately, because of the rate at which the image is moving, some frames are identical because the image has not yet moved a full pixel.
Is there a way to provide float values to Graphics2D for on-screen position to draw the image, rather than int values?
Initially here is what I had done:
BufferedImage srcImage = sprite.getImage ( );
Position imagePosition = ... ; //Defined elsewhere
g.drawImage ( srcImage, (int) imagePosition.getX(), (int) imagePosition.getY() );
This of course thresholds, so the picture doesn't move between pixels, but skips from one to the next.
The next method was to set the paint color to a texture instead and draw at a specified position. Unfortunately, this produced incorrect results that showed tiling rather than correct antialiasing.
g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
BufferedImage srcImage = sprite.getImage ( );
g.setPaint ( new TexturePaint ( srcImage, new Rectangle2D.Float ( 0, 0, srcImage.getWidth ( ), srcImage.getHeight ( ) ) ) );
AffineTransform xform = new AffineTransform ( );
xform.setToIdentity ( );
xform.translate ( onScreenPos.getX ( ), onScreenPos.getY ( ) );
g.transform ( xform );
g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());
What should I do to achieve the desired effect of subpixel rendering of an Image in Java?
You can use a BufferedImage and AffineTransform, draw to the buffered image, then draw the buffered image to the component in the paint event.
/* overrides the paint method */
#Override
public void paint(Graphics g) {
/* clear scene buffer */
g2d.clearRect(0, 0, (int)width, (int)height);
/* draw ball image to the memory image with transformed x/y double values */
AffineTransform t = new AffineTransform();
t.translate(ball.x, ball.y); // x/y set here, ball.x/y = double, ie: 10.33
t.scale(1, 1); // scale = 1
g2d.drawImage(image, t, null);
// draw the scene (double percision image) to the ui component
g.drawImage(scene, 0, 0, this);
}
Check my full example here: http://pastebin.com/hSAkYWqM
You can composite the image yourself using sub-pixel accuracy, but it's more work on your part. Simple bilinear interpolation should work well enough for a game. Below is psuedo-C++ code for doing it.
Normally, to draw a sprite at location (a,b), you'd do something like this:
for (x = a; x < a + sprite.width; x++)
{
for (y = b; y < b + sprite.height; y++)
{
*dstPixel = alphaBlend (*dstPixel, *spritePixel);
dstPixel++;
spritePixel++;
}
dstPixel += destLineDiff; // Move to start of next destination line
spritePixel += spriteLineDiff; // Move to start of next sprite line
}
To do sub-pixel rendering, you do the same loop, but account for the sub-pixel offset like so:
float xOffset = a - floor (a);
float yOffset = b - floor (b);
for (x = floor(a), spriteX = 0; x < floor(a) + sprite.width + 1; x++, spriteX++)
{
for (y = floor(b), spriteY = 0; y < floor (b) + sprite.height + 1; y++, spriteY++)
{
spriteInterp = bilinearInterp (sprite, spriteX + xOffset, spriteY + yOffset);
*dstPixel = alphaBlend (*dstPixel, spriteInterp);
dstPixel++;
spritePixel++;
}
dstPixel += destLineDiff; // Move to start of next destination line
spritePixel += spriteLineDiff; // Move to start of next sprite line
}
The bilinearInterp() function would look something like this:
Pixel bilinearInterp (Sprite* sprite, float x, float y)
{
// Interpolate the upper row of pixels
Pixel* topPtr = sprite->dataPtr + ((floor (y) + 1) * sprite->rowBytes) + floor(x) * sizeof (Pixel);
Pixel* bottomPtr = sprite->dataPtr + (floor (y) * sprite->rowBytes) + floor (x) * sizeof (Pixel);
float xOffset = x - floor (x);
float yOffset = y - floor (y);
Pixel top = *topPtr + ((*(topPtr + 1) - *topPtr) * xOffset;
Pixel bottom = *bottomPtr + ((*(bottomPtr + 1) - *bottomPtr) * xOffset;
return bottom + (top - bottom) * yOffset;
}
This should use no additional memory, but will take additional time to render.
I successfully solved my problem after doing something like lawrencealan proposed.
Originally, I had the following code, where g is transformed to a 16:9 coordinate system before the method is called:
private void drawStar(Graphics2D g, Star s) {
double radius = s.getRadius();
double x = s.getX() - radius;
double y = s.getY() - radius;
double width = radius*2;
double height = radius*2;
try {
BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
g.drawImage(image, (int)x, (int)y, (int)width, (int)height, this);
} catch (IOException ex) {
Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
}
}
However, as noted by the questioner Kaushik Shankar, turning the double positions into integers makes the image "jump" around, and turning the double dimensions into integers makes it scale "jumpy" (why the hell does g.drawImage not accept doubles?!). What I found working for me was the following:
private void drawStar(Graphics2D g, Star s) {
AffineTransform originalTransform = g.getTransform();
double radius = s.getRadius();
double x = s.getX() - radius;
double y = s.getY() - radius;
double width = radius*2;
double height = radius*2;
try {
BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
g.translate(x, y);
g.scale(width/image.getWidth(), height/image.getHeight());
g.drawImage(image, 0, 0, this);
} catch (IOException ex) {
Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
}
g.setTransform(originalTransform);
}
Seems like a stupid way of doing it though.
Change the resolution of your image accordingly, there's no such thing as a bitmap with sub-pixel coordinates, so basically what you can do is create an in memory image larger than what you want rendered to the screen, but allows you "sub-pixel" accuracy.
When you draw to the larger image in memory, you copy and resample that into the smaller render visible to the end user.
For example: a 100x100 image and it's 50x50 resized / resampled counterpart:
See: http://en.wikipedia.org/wiki/Resampling_%28bitmap%29

Categories

Resources