how to change exist text position in pdf by itext - java

sorry,you may not understand my problems,because i am not good at english.
i want to add some labels at the top and bottom of pdf,but the label position can set minus.if i set a minus,i should make heigh larger to set label.
i got help from How to resize existing pdf page size
to change my pdf pagesize.then i encountered another problem,when i set lly as a
minus, my text was truncated,then i want to add bottom length to top, but i do not know how to move the text up to make the text in center.
key codes
float newTop = rectangle.getTop();
if (printSet.getHeaderMargins() < 0) {
newTop += height2Offset(PrintSet.defaultMargins - printSet.getHeaderMargins());
headMargins = height2Offset(PrintSet.defaultMargins);
}
if (printSet.getFooterMargins() < 0) {
newTop += height2Offset(PrintSet.defaultMargins - printSet.getFooterMargins());
footMargins = height2Offset(PrintSet.defaultMargins);
}
float[] newBoxValues = new float[] {
rectangle.getLeft(),
rectangle.getBottom(),
rectangle.getRight(),
newTop
};
PdfArray newBox = new PdfArray(newBoxValues);
PdfDictionary pageDict = reader.getPageN(page + 1);
pageDict.put(PdfName.CROPBOX, newBox);
pageDict.put(PdfName.MEDIABOX, newBox);

Currently, you are defining a new page size like this:
float[] newBoxValues = new float[] {
rectangle.getLeft(),
rectangle.getBottom(),
rectangle.getRight(),
newTop
};
This creates a bigger rectangle, but that rectangle only expands the page towards the top.
I think you should create the new rectangle like this:
float[] newBoxValues = new float[] {
rectangle.getLeft(),
rectangle.getBottom() - extramarginBottom,
rectangle.getRight(),
rectangle.getTop() + extramarginTop
};
I can't help you define the value of extramarginBottom and extramarginTop because I'm not sure what your height2Offset() method is supposed to do, nor what PrintSet.defaultMargins, printSet.getHeaderMargins(), and printSet.getFooterMargins() are about.
Basically, extramarginTop is the height to be added at the top, whereas extramarginBottom is the height to be added at the bottom:

Related

How to apply round corner border to table (single page / multipage)?

I want to apply round corner border to a table. This table is dynamic. That means it can grow up to multiple pages or can accommodate in single page.
If table comes in single page, then outermost corner of all four corner cells should be drawn as rounded.
If table grows up to multiple pages (say 3 pages), then outermost corner of all four corner cells should be drawn as rounded for all 3 pages.
Here is the approach which I am using to implement the above scenario.
public void createPdf(String dest) throws FileNotFoundException {
PdfWriter writer = new PdfWriter(DEST);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4, false);
Table table = new Table(3);
for (int i=0; i < 100; i++) {
for (int j=0; j < 3; j++) {
table.addCell(new Cell().add(new Paragraph("Cell content")));
}
}
table.setNextRenderer(new TableBorderRenderer(table));
document.add(table);
document.close();
}
TableBorderRenderer.java
public class TableBorderRenderer extends TableRenderer {
public TableBorderRenderer(Table modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new TableBorderRenderer((Table) modelElement);
}
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(0.5f).setStrokeColor(new DeviceRgb(255,0,0)).roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
}
Now the code is working fine as it is supposed to be, but there is an issue with the rendering. when i draw the white border on top of table border it does not overlap it completely. Also the outer red border is drawn slightly outside the expected area. In simple words, white rectangle and red rectangle are not coinciding with each other. So there is some gap coming between the outer border and cell borders.
I am attaching the generated output from above code. To notice the issue you might need to zoom the PDF a little bit.
I have some doubts regarding the same:
I am using a top canvas to get the expected solution. But is there any approach in which I can modify the table border directly while rendering? I tried
drawContext.getCanvas()
.saveState()
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
But in this approach border is overlapped by the cell borders (cell borders are needed as well). If I am missing something to prevent this issue, guide me.
Thanks.
The getOccupiedAreaBBox method gives you the outer bounding box of the border area. Borders, however, have thickness on their own, and when you draw lines in PDF by default you should pass the center of the line to the drawing operation, while you are passing the outer bbox coordinates, hence the small margin and imprecise overlap.
To fix the issue, you need to add half of the border line width to all of the edges of your rectangle:
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
All in all, the following customized code:
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,0,0))
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
Yields the following visual result:

itext7 - Maximum font size without clipping content

EDIT in bold
I have a table where several cells contain Paragraphs with some arbitrarily long text. The table width is determined via useAllAvailableWidth, and I am also invoking setAutoLayout.
I am using the Renderer framework to set the Paragraph's font size to the largest possible value without clipping off any content.
In particular I wish to achieve similar results to iText Maximum Font Size, however that questions was written for itext5. I am using itext7.
I have read this sample, and I have come up with the following (partial) solution thanks to a previous answer:
class FontSizeRenderer(val content: Paragraph) : ParagraphRenderer(content) {
override fun getNextRenderer() = FontSizeRenderer(content)
override fun layout(layoutContext: LayoutContext?): LayoutResult {
val currentFontSize = content.getProperty<UnitValue>(Property.FONT_SIZE).value
return layoutBinarySearch(layoutContext, 1f, currentFontSize, 20)
}
private tailrec fun layoutBinarySearch(layoutContext: LayoutContext?, minFontSize: Float, maxFontSize: Float, iterationThreshold: Int): LayoutResult {
val currentLayout = super.layout(layoutContext)
if (iterationThreshold <= 0) {
return currentLayout
}
val currentFontSize = content.getProperty<UnitValue>(Property.FONT_SIZE).value
return if (currentLayout.status == LayoutResult.FULL) {
val increment = (currentFontSize + maxFontSize) / 2
content.setFontSize(increment)
layoutBinarySearch(layoutContext, currentFontSize, maxFontSize, iterationThreshold - 1)
} else {
val decrement = (minFontSize + currentFontSize) / 2
content.setFontSize(decrement)
layoutBinarySearch(layoutContext, minFontSize, currentFontSize, iterationThreshold - 1)
}
}
}
When using this renderer in a fully-fledged table, it "kinda works" in a sense that it starts to recurse but it stops too early.
The expected output string in the bottom cell on the first page is Scramble: R' U' F R F2 D2 R' B2 U2 R F2 R' B2 R' B F U' L2 B' R' B' U' R F2 R' U' F. The full code sample can be inspected (and downloaded) at this repository, under webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf, files FmcSolutionSheet.kt and util/FooRenderer.kt.
How can I adjust my Renderer to prevent the Cell from overflowing?
Having a layout element you can create a renderer subtree out of it and try to lay it out in a given area. Then depending on whether the element fit completely in the area or there was not enough space you can increase or decrease your font size and try again.
To test out different font sizes, binary search is our best friend and allows a giant speed up.
First off, a helper function to set the font size of the element recursively:
private void setFontSizeRecursively(IElement element, float size) {
element.setProperty(Property.FONT_SIZE, UnitValue.createPointValue(size));
if (element instanceof com.itextpdf.layout.element.AbstractElement) {
for (Object child : ((AbstractElement) element).getChildren()) {
setFontSizeRecursively((IElement) child, size);
}
}
}
Now the meaty part of the code. In the binary search body we scale all the font sizes of the elements and emulate layout to see if our root element (table) fits into the given area. Then we shift the left or right search boundary and try again until we converge. You can tweak the number of iterations (set at 20 in the code) to your own taste for the accuracy/speed trade-off. You can also tweak left and right bounds of the binary search if you can make such judgment depending on the expected input. Finally, we set the font size scale we converged at and do the final layout.
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
Document document = new Document(pdfDocument);
Table table = new Table(UnitValue.createPercentArray(new float[] {20, 40, 40}));
for (int i = 0; i < 9; i++) {
table.addCell(new Cell().add(new Paragraph("Hello random text")));
}
float lFontSize = 1f;
float rFontSize = 100f;
float desiredWidth = 300;
float desiredHeight = 400;
table.setWidth(UnitValue.createPointValue(desiredWidth));
for (int i = 0; i < 20; i++) {
float midFontSize = (lFontSize + rFontSize) / 2;
setFontSizeRecursively(table, midFontSize);
IRenderer tableRenderer = table.createRendererSubTree().setParent(document.getRenderer());
LayoutResult result = tableRenderer.layout(new LayoutContext(new LayoutArea(1,
new Rectangle(0, 0, desiredWidth, desiredHeight)))); // You can tweak desired size to fit the table in
if (result.getStatus() == LayoutResult.FULL) {
lFontSize = midFontSize;
} else {
rFontSize = midFontSize;
}
}
setFontSizeRecursively(table, lFontSize);
document.add(table);
document.close();

itext7 Java Create PdfExplicitDestination for titles in existing pdf

I am using PdfExplicitDestination as a page number, for titles by reading the existing pdf content from the page,
but I need to point the focus on specific text content while click on the bookmark.
for (int page = 1; page <= pdf.getNumberOfPages(); page++) {
ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
String currentText = PdfTextExtractor.getTextFromPage(pdf.getPage(page), strategy);
if (currentText.contains("title")) {
k.addDestination(PdfExplicitDestination.createXYZ(pdf.getPage(page), pdf.getPage(page).getPageSize().getLeft(), pdf.getPage(page).getPageSize().getTop(), 0));
//System.out.println(currentText);
}
}
I need to find the position of the title in the pdf page to set "float top" value.
PdfExplicitDestination.createXYZ(pageNum, left, top, zoom)
Can any one please help to get it from the existing content in the pdf.
This task can be approached in a number of ways. One of the way is to go over page content in "stripes" (rectangles with small height), and only consider content from such a small rectangle at a time. If you find a text piece in such rectangle then you know that somewhere between upper and lower bound of Y position given by the rectangle coordinates lies the desired text content. You can e.g. create the destination to point to the topmost coordinate in that case - it might be a bit above the desired text but the difference will be small depending on the rectangle height you select.
The following code snipped contains example implementation of the presented idea. There are two parameters - windowHeight which must be tall enough to fit a piece of content you are looking for, but the smaller this variable is the better accuracy you get in the result. Parameter step defines how many such rectangles of height windowHeight we will try on each page. The smaller the parameter is the better accuracy you get, but bigger parameter values optimize performance. Up to a specific use case to tweak those trade-offs.
final float windowHeight = 30;
final float step = 10;
for (int page = 1; page <= pdf.getNumberOfPages(); page++) {
Rectangle pageSize = pdf.getPage(page).getPageSize();
for (float upperPoint = pageSize.getHeight(); upperPoint > 0; upperPoint -= step) {
IEventFilter filter = new TextRegionEventFilter(new Rectangle(0, upperPoint - windowHeight, pageSize.getWidth(), windowHeight));
LocationTextExtractionStrategy strategy = new LocationTextExtractionStrategy();
FilteredTextEventListener listener = new FilteredTextEventListener(strategy, filter);
new PdfCanvasProcessor(listener).processPageContent(pdf.getPage(page));
if (strategy.getResultantText().contains("title")) {
float top = upperPoint; // This is the topmost point of the rectangle
break; // Break here not to capture same text twice
}
}
}

Transform cartesian pixel-data-array to lat/lon pixel-data-array

I have an image (basically, I get raw image data as 1024x1024 pixels) and the position in lat/lon of the center pixel of the image.
Each pixel represents the same fixed pixel scale in meters (e.g. 30m per pixel).
Now, I would like to draw the image onto a map which uses the coordinate reference system "EPSG:4326" (WGS84).
When I draw it by defining just corners in lat/lon of the image, depending on a "image size in pixel * pixel scale" calculation and converting the distances from the center point to lat/lon coordinates of each corner, I suppose, the image is not correctly drawn onto the map.
By the term "not correctly drawn" I mean, that the image seems to be shifted and also the contents of the image are not at the map location, where I expected them to be.
I suppose this is the case because I "mix" a pixel scaled image and a "EPSG:4326" coordinate reference system.
Now, with the information I have given, can I transform the whole pixel matrix from fixed pixel scale base to a new pixel matrix in the "EPSG:4326" coordinate reference system, using Geotools?
Of course, the transformation must be dependant on the center position in lat/lon, that I have been given, and on the pixel scale.
I wonder if using something like this would point me into the correct direction:
MathTransform transform = CRS.findMathTransform(DefaultGeocentricCRS.CARTESIAN, DefaultGeographicCRS.WGS84, true);
DirectPosition2D srcDirectPosition2D = new DirectPosition2D(DefaultGeocentricCRS.CARTESIAN, degreeLat.getDegree(), degreeLon.getDegree());
DirectPosition2D destDirectPosition2D = new DirectPosition2D();
transform.transform(srcDirectPosition2D, destDirectPosition2D);
double transX = destDirectPosition2D.x;
double transY = destDirectPosition2D.y;
int kmPerPixel = mapImage.getWidth / 1024; // It is known to me that my map is 1024x1024km ...
double x = zeroPointX + ((transX * 0.001) * kmPerPixel);
double y = zeroPointY + (((transX * -1) * 0.001) * kmPerPixel);
(got this code from another SO thread and already modified it a little bit, but still wonder if this is the correct starting point for my problem.)
I only suppose that my original image coordinate reference system is of the type DefaultGeocentricCRS.CARTESIAN. Can someone confirm this?
And from here on, is this the correct start to use Geotools for this kind of problem solving, or am I on the complete wrong path?
Additionally, I would like to add that this would be used in a quiet dynamic system. So my image update would be about 10Hz and the transormations have to be performed accordingly often.
Again, is this initial thought of mine leading to a solution, or do you have other solutions for solving my problem?
Thank you very much,
Kiamur
This is not as simple as it might sound. You are essentially trying to define an area on a sphere (ellipsoid technically) using a flat square. As such there is no "correct" way to do it, so you will always end up with some distortion. Without knowing exactly where your image came from there is no way to answer this exactly but the following code provides you with 3 different possible answers:
The first two make use of GeoTools' GeodeticCalculator to calculate the corner points using bearings and distances. These are the blue "square" and the green "square" above. The blue is calculating the corners directly while the green calculates the edges and infers the corners from the intersections (that's why it is squarer).
final int width = 1024, height = 1024;
GeometryFactory gf = new GeometryFactory();
Point centre = gf.createPoint(new Coordinate(0,51));
WKTWriter writer = new WKTWriter();
//direct method
GeodeticCalculator calc = new GeodeticCalculator(DefaultGeographicCRS.WGS84);
calc.setStartingGeographicPoint(centre.getX(), centre.getY());
double height2 = height/2.0;
double width2 = width/2.0;
double dist = Math.sqrt(height2*height2+width2 *width2);
double bearing = 45.0;
Coordinate[] corners = new Coordinate[5];
for (int i=0;i<4;i++) {
calc.setDirection(bearing, dist*1000.0 );
Point2D corner = calc.getDestinationGeographicPoint();
corners[i] = new Coordinate(corner.getX(),corner.getY());
bearing+=90.0;
}
corners[4] = corners[0];
Polygon bbox = gf.createPolygon(corners);
System.out.println(writer.write(bbox));
double[] edges = new double[4];
bearing = 0;
for(int i=0;i<4;i++) {
calc.setDirection(bearing, height2*1000.0 );
Point2D corner = calc.getDestinationGeographicPoint();
if(i%2 ==0) {
edges[i] = corner.getY();
}else {
edges[i] = corner.getX();
}
bearing+=90.0;
}
corners[0] = new Coordinate( edges[1],edges[0]);
corners[1] = new Coordinate( edges[1],edges[2]);
corners[2] = new Coordinate( edges[3],edges[2]);
corners[3] = new Coordinate( edges[3],edges[0]);
corners[4] = corners[0];
bbox = gf.createPolygon(corners);
System.out.println(writer.write(bbox));
Another way to do this is to transform the centre point into a projection that is "flatter" and use simple addition to calculate the corners and then reverse the transformation. To do this we can use the AUTO projection defined by the OGC WMS Specification to generate an Orthographic projection centred on our point, this gives the red "square" which is very similar to the blue one.
String code = "AUTO:42003," + centre.getX() + "," + centre.getY();
// System.out.println(code);
CoordinateReferenceSystem auto = CRS.decode(code);
// System.out.println(auto);
MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84,
auto);
MathTransform rtransform = CRS.findMathTransform(auto,DefaultGeographicCRS.WGS84);
Point g = (Point)JTS.transform(centre, transform);
width2 *=1000.0;
height2 *= 1000.0;
corners[0] = new Coordinate(g.getX()-width2,g.getY()-height2);
corners[1] = new Coordinate(g.getX()+width2,g.getY()-height2);
corners[2] = new Coordinate(g.getX()+width2,g.getY()+height2);
corners[3] = new Coordinate(g.getX()-width2,g.getY()+height2);
corners[4] = corners[0];
bbox = gf.createPolygon(corners);
bbox = (Polygon)JTS.transform(bbox, rtransform);
System.out.println(writer.write(bbox));
Which solution to use is a matter of taste, and depends on where your image came from but I suspect that either the red or the blue will be best. If you need to do this at 10Hz then you will need to test them for speed, but I suspect that transforming the images will be the bottle neck.
Once you have your bounding box setup to your satisfaction you can convert you (unreferenced) image to a georeferenced coverage using:
GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
GridCoverage2D gc = factory.create("name", image, new ReferencedEnvelope(bbox.getEnvelopeInternal(),DefaultGeographicCRS.WGS84));
String fileName = "myImage.tif";
AbstractGridFormat format = GridFormatFinder.findFormat(fileName);
File out = new File(fileName);
GridCoverageWriter writer = format.getWriter(out);
try {
writer.write(gc, null);
writer.dispose();
} catch (IllegalArgumentException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

SWT tree multiple icons

I am trying to add multiple icons for the swt tree by appending more images into one long image and then adding it for each tree node. The problem is that the dashed line is elongated more and more as the width of the image increases(see the picture). I tried to add paint listeners but I am having some repainting problem, so the paint listeners don't work for me.
If anyone has any suggestions, please help.
switch (event.type) {
case SWT.MeasureItem: {
final Object value = ((TreeItem) event.item).getData();
final BrowserNode node = getBrowserNode(value);
Image image = getCombinedImage(node.getImage1(),node.getImage2(),node.getImage3(),node.getImage4());
Rectangle rect = image.getBounds();
event.width += rect.width;// rect.width*2;
event.height = Math.max(event.height, rect.height + 2);
break;
}
case SWT.PaintItem: {
BrowserNode node = getBrowserNode(((TreeItem) event.item).getData());
Image image = getCombinedImage(node.getImage1(),node.getImage2(),node.getImage3(),node.getImage4());
Rectangle rect = image.getBounds();
int offset = Math.max(0, (event.height - rect.height) / 2);
event.gc.copyArea(event.x, event.y, event.width, event.height, event.x + image.getImageData().width-30, event.y + offset, false);
event.gc.fillRectangle(event.x, event.y, image.getImageData().width, event.height);
event.gc.drawImage(image, event.x, event.y + offset);
break;
}
}
The excessive indentation of tree nodes is specific to Windows, other platforms do not behave this way.
This issue is discussed in this bug report:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=185004
The suggested workaround is to draw the tree items yourself.

Categories

Resources