So, my problem is that I need to print the content of my tableview, but I have so many items in it, that it only prints the first 23 of them.
I found a few solutions here already, unfortunately they didn't help much.
This is my print method:
#FXML
private void printIt() {
Printer printer = Printer.getDefaultPrinter();
PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.LANDSCAPE, Printer.MarginType.DEFAULT);
double scaleX = pageLayout.getPrintableWidth() / logBookTable.getBoundsInParent().getWidth();
double scaleY = pageLayout.getPrintableHeight() / logBookTable.getBoundsInParent().getHeight();
logBookTable.getTransforms().add(new Scale(scaleX, scaleY));
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
boolean successPrintDialog = job.showPrintDialog(dialogStage);
if(successPrintDialog){
boolean success = job.printPage(pageLayout,logBookTable);
if (success) {
job.endJob();
}
}
}
}
I looked through a lot of posts before I was able to come up with an answer. The key is to extend the height of the tableview to show without the need to have a scroll on the right of the screen. (There are ways of doing this without skewing your programs layout. I did not address that part in this questions. There are very good answer on how to accomplish this task.) Once you have extend the height of the tableview to the show all of it's rows without the need for a scroll bar. Then just print one page at a time starting from height of zero going to a negative height of one page.(In this case: close to 11 inches down. Borders play a role in this.) The next page should pick up where the last page ended and print down to almost 11 more inches. (from about -11 inches to about -22 inches.) This pattern continues until the whole height of the tableview has been traversed.
Printer printer = Printer.getDefaultPrinter(); //get the default printer
javafx.print.PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT); //create a pagelayout. I used Paper.NA_LETTER for a standard 8.5 x 11 in page.
PrinterJob job = PrinterJob.createPrinterJob();//create a printer job
if(job.showPrintDialog(taMain.getScene().getWindow()))// this is very useful it allows you to save the file as a pdf instead using all of your printer's paper. A dialog box pops up, allowing you to change the "name" option from your default printer to Adobe pdf.
{
double pagePrintableWidth = pageLayout.getPrintableWidth(); //this should be 8.5 inches for this page layout.
double pagePrintableHeight = pageLayout.getPrintableHeight();// this should be 11 inches for this page layout.
tblvMain.prefHeightProperty().bind(Bindings.size(tblvMain.getItems()).multiply(35));// If your cells' rows are variable size you add the .multiply and play with the input value until your output is close to what you want. If your cells' rows are the same height, I think you can use .multiply(1). This changes the height of your tableView to show all rows in the table.
tblvMain.minHeightProperty().bind(tblvMain.prefHeightProperty());//You can probably play with this to see if it's really needed. Comment it out to find out.
tblvMain.maxHeightProperty().bind(tblvMain.prefHeightProperty());//You can probably play with this to see if it' really needed. Comment it out to find out.
double scaleX = pagePrintableWidth / tblvMain.getBoundsInParent().getWidth();//scaling down so that the printing width fits within the paper's width bound.
double scaleY = scaleX; //scaling the height using the same scale as the width. This allows the writing and the images to maintain their scale, or not look skewed.
double localScale = scaleX; //not really needed since everything is scaled down at the same ratio. scaleX is used thoughout the program to scale the print out.
double numberOfPages = Math.ceil((tblvMain.getPrefHeight() * localScale) / pagePrintableHeight);//used to figure out the number of pages that will be printed.
//System.out.println("pref Height: " + tblvMain.getPrefHeight());
//System.out.println("number of pages: " + numberOfPages);
tblvMain.getTransforms().add(new Scale(scaleX, (scaleY)));//scales the printing. Allowing the width to say within the papers width, and scales the height to do away with skewed letters and images.
tblvMain.getTransforms().add(new Translate(0, 0));// starts the first print at the top left corner of the image that needs to be printed
//Since the height of what needs to be printed is longer than the paper's heights we use gridTransfrom to only select the part to be printed for a given page.
Translate gridTransform = new Translate();
tblvMain.getTransforms().add(gridTransform);
//now we loop though the image that needs to be printed and we only print a subimage of the full image.
//for example: In the first loop we only pint the printable image from the top down to the height of a standard piece of paper. Then we print starting from were the last printed page ended down to the height of the next page. This happens until all of the pages are printed.
// first page prints from 0 height to -11 inches height, Second page prints from -11 inches height to -22 inches height, etc.
for(int i = 0; i < numberOfPages; i++)
{
gridTransform.setY(-i * (pagePrintableHeight / localScale));
job.printPage(pageLayout, tblvMain);
}
job.endJob();//finally end the printing job.
Found a solution myself
1) create for loop
2)create new table and insert items, which are not being printed
3) print
Printer printer = Printer.getDefaultPrinter();
Printer printer = Printer.getDefaultPrinter();PrinterJob printerJob = PrinterJob.createPrinterJob();
PageLayout pageLayout = printer.createPageLayout(Paper.A4, PageOrientation.LANDSCAPE, Printer.MarginType.HARDWARE_MINIMUM);
printerJob.getJobSettings().setPageLayout(pageLayout);
Stage stage = (Stage) anchorPane.getScene().getWindow();
boolean openPrintDialog = printerJob.showPrintDialog(stage);
if(openPrintDialog){
tableView.setScaleX(0.8);
tableView.setScaleY(0.8);
tableView.setTranslateX(-70);
tableView.setTranslateY(-50);
ObservableList<List<SimpleStringProperty>> allPrintItems = tableView.getItems();
ObservableList <List<SimpleStringProperty>> pageList = FXCollections.observableArrayList();
boolean printing = false;
for(int i=0; i<allPrintItems.size(); i++) {
List<SimpleStringProperty> oneRow = allPrintItems.get(i);
pageList.add(oneRow);
if(i!=0 && (i%24==0 || i == (allPrintItems.size()-1))){
tableView.setItems(pageList);
printing = printerJob.printPage(tableView);
pageList.clear();
}
}
tableView.setItems(allPrintItems);
if(printing)printerJob.endJob();
tableView.setScaleX(1.0);
tableView.setScaleY(1.0);
tableView.setTranslateX(0);
tableView.setTranslateY(0);
Related
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
}
}
}
I am working on a project where as part of statements I need to attach arbitrary PDF files. These PDF files need to be marked by a title and page numbering, in the top-right corner of the PDF file. This is a legal requirement as these attachments are referred to by their title and total number of pages from the statements.
I (naively) hacked together some code that appears to be working on PDF files with pages in the Portrait orientation (at least the PDF files I tested with). However when I use this code on pages in a Landscape orientation, the title and numbering isn't visible.
The code:
PdfContentByte canvas = pdfStamper.getOverContent( pageNr );
Phrase phrase = new Phrase( sb.toString( ), new Font( FontFamily.HELVETICA, 9f ) ); // sb holds title + page numbering
float width = ColumnText.getWidth( phrase );
ColumnText.showTextAligned ( // draw text top-right
canvas,
Element.ALIGN_LEFT,
phrase,
canvas.getPdfDocument( ).right( ) - width, //x
canvas.getPdfDocument( ).top( ) + 9, //y
0 //rotation
);
Examples:
Portrait where it appears to work:
Landscape where it doesn't work:
Questions:
Where did I go wrong?
Is it possible to write such a piece of code that does it right for all possible page orientations?
If so, how?
You are adding the content, but you are adding it at the wrong place. See PageSize of PDF always the same between landscape and portrait with itextpdf
Let's assume that you are working with an A4 page using portrait orientation. That pages measures 595 by 842 user units. 595 is the width; 842 is the height.
Now let's switch to landscape. This can be done in two different ways:
define a width of 595 and a height of 842, and a rotation of 90 degrees.
define a width of 842 and a height of 595.
Which way is used to define the landscape orientation will have an impact on the value of the right() and top() method. I am pretty sure that you are adding the header to the landscape pages, but you are adding them outside the visible area of the page.
For those interested, I ended up doing it as follows. This works for both Portrait and Landscape orientations. This uses the PdfReader.getPageSizeWithRotation method to get the proper page size.
private String pageText(int pageNr, int pageTotal) {
return ""; // generate string to display top-right of PDF here
}
private void addDocumentObjects(int pageNr, PdfReader pdfReader, PdfStamper pdfStamper) {
final float pageMargin = 25f;
final float textSize = 9f;
final float lineMargin = 5f;
Phrase phrase = new Phrase (
pageText(pageNr, pdfReader.getNumberOfPages()),
new Font(FontFamily.HELVETICA, textSize)
);
final float phraseWidth = ColumnText.getWidth(phrase);
PdfContentByte canvas = pdfStamper.getOverContent(pageNr);
com.itextpdf.text.Rectangle pageRectangle = pdfReader.getPageSizeWithRotation(pageNr);
// draw white background rectangle before adding text + line
canvas.setColorFill(BaseColor.WHITE);
canvas.rectangle (
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin), //y
phraseWidth, // width
textSize + lineMargin //height
);
canvas.fill();
// draw text top right
canvas.setColorFill(BaseColor.BLACK);
ColumnText.showTextAligned (
canvas, //canvas
Element.ALIGN_LEFT, //alignment
phrase, //phrase
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin), //y
0 //rotation
);
// draw line under text
canvas.setColorStroke(BaseColor.BLACK);
canvas.setLineWidth(1);
canvas.moveTo (
pageRectangle.getRight(pageMargin) - phraseWidth, //x
pageRectangle.getTop(pageMargin) - lineMargin //y
);
canvas.lineTo (
pageRectangle.getRight(pageMargin), //x
pageRectangle.getTop(pageMargin) - lineMargin //y
);
canvas.stroke();
}
Is it possible to display custom text centered between 2 points on the graph?
I've got MPAndroidChart setup to display a step function type graph (representing hours spent doing a specific task) with horizontal and vertical lines only. What I would like to be able to do is show a label over the horizontal sections indicating the size of the section (aka the time spent calculated by taking the difference between the x values). Is there a way to do this? I've been look into modifying the library but I can't seem to figure out where would be the correct place to do so.
My best guess would be some changes in BarLineChartBase onDraw() method or maybe in the LineChartRenderer drawLinear() method.
Here is what I am able to produce:
Here is an example of what I am trying to produce:
Figured it out! Just add a new method drawTime() to the LineChart class at the end of onDraw() right after drawDescription(). Since each horizontal line is described by 2 Entry points I simply loop through 2 entries at a time for my single data set and calculate the difference:
protected void drawTime(Canvas c)
{
Paint timePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
timePaint.setTextSize(Utils.convertDpToPixel(16));
timePaint.setColor(Color.BLUE);
timePaint.setTextAlign(Paint.Align.CENTER);
MPPointD position;
LineData data = this.getLineData();
ILineDataSet dataSet = data.getDataSetByIndex(0);
for (int i = 1; i < dataSet.getEntryCount(); i+=2)
{
Entry e1 = dataSet.getEntryForIndex(i-1);
Entry e2 = dataSet.getEntryForIndex(i);
float time = e2.getX() - e1.getX();
position = getPixelForValues(e1.getX() + time/2, e1.getY() - 0.05f, YAxis.AxisDependency.LEFT);
c.drawText(String.valueOf(time), (float)position.x, (float)position.y, timePaint);
}
}
The resulting graph looks like this
I'm facing a problem while trying to generate a PdfPTable and calculate its height before adding it to a document. The method calculateHeights of PdfPTable returned the height a lot greater than the height of a page (while the table is about 1/4 of page's height), so I wrote a method to calculate the height:
protected Float getVerticalSize() throws DocumentException, ParseException, IOException {
float overallHeight=0.0f;
for(PdfPRow curRow : this.getPdfObject().getRows()) {
float maxHeight = 0.0f;
for(PdfPCell curCell : curRow.getCells()) {
if(curCell.getHeight()>maxHeight) maxHeight=curCell.getHeight();
}
overallHeight+=maxHeight;
}
return overallHeight;
}
where getPdfObject method returns a PdfPTable object.
Using debugger I've discovered that lly and ury coordinate difference (and thus the height) of cell's rectangle is much bigger than it looks after adding a table to a document (for example, one cell is 20 and the other is 38 height while they look like the same on a page). There is nothing in the cell except a paragraph with a chunk in it:
Font f = getFont();
if (f != null) {
int[] color = getTextColor();
if(color != null) f.setColor(color[0],color[1],color[2]);
ch = new Chunk(celltext, f);
par = new Paragraph(ch);
}
cell = new PdfPCell(par);
cell.setHorizontalAlignment(getHorizontalTextAlignment());
cell.setVerticalAlignment(getVerticalTextAlignment());
A table then has a cell added and setWidthPercentage attribute set to a some float.
What am I doing wrong? Why does cell's proportions are different from those I see after generating PDF? Maybe I'm calculating the height wrong? Isn't it the height of a cell on a PDF page should strictly be the difference between lly and ury coordinates
Sorry I haven't shown the exact code, because the PDF is being generated of XML using lots of intermediate steps and objects and it is not very useful "as is" I guess...
Thanks in advance!
The height of table added to a page where the available width is 400 is different from the height of a table added to a page where the available width is 1000. There is no way you can measure the height correctly until the width is defined.
Defining the width can be done by adding the table to the document. Once the table is rendered, the total height is known.
If you want to know the height in advance, you need to define the width in advance. For instance by using:
table.setTotalWidth(400);
table.setLockedWidth(true);
This is explained in the TableHeight example. In table_height.pdf, you see that iText returns a height of 0 before adding a table and a height of 48 after adding the table. iText initially returns 0 because there is no way to determine the actual height.
We then take the same table and we define a total width of 50 (which is much smaller than the original 80% of the available width on the page). Now when we calculate the height of the table with the same contents, iText returns 192 instead of 48. When you look at the table on the page, the cause of the difference in height is obvious.
Inorder to get dynamic table height we should set and lock width of table.
Here, 595 is A4 size paper width.
table.setTotalWidth(595);
table.setLockedWidth(true);
There is a method in PDFBox's font class, PDFont, named getFontHeight which sounds simple enough. However I don't quite understand the documentation and what the parameters stand for.
getFontHeight
This will get the font width for a character.
Parameters:
c - The character code to get the width for.
offset - The offset into the array. length
The length of the data.
Returns: The width is in 1000 unit of text space, ie 333 or 777
Is this method the right one to use to get the height of a character in PDFBox and if so how? Is it some kind of relationship between font height and font size I can use instead?
I believe the answer marked right requires some additional clarification. There are no "error" per font for getHeight() and hence I believe it is not a good practice manually guessing the coefficient for each new font.
Guess it could be nice for your purposes simply use CapHeight instead of Height.
float height = ( font.getFontDescriptor().getCapHeight()) / 1000 * fontSize;
That will return the value similar to what you are trying to get by correcting the Height with 0.865 for Helvetica. But it will be universal for any font.
PDFBox docs do not explain too much what is it. But you can look at the image in the wikipedia Cap_height article to understand better how it is working and choose the parameter fit to your particular task.
https://en.wikipedia.org/wiki/Cap_height
EDIT: Cap height was what I was looking for. See the accepted answer.
After digging through the source of PDFBox I found that this should do the trick of calculating the font height.
int fontSize = 14;
PDFont font = PDType1Font.HELVETICA;
font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize
The method isn't perfect though. If you draw a rectangle with the height 200 and a Y with the font size 200 you get the font height 231.2 calculated with the above method even though it actually is printed smaller then the rectangle.
Every font has a different error but with helvetica it is close to 13.5 precent too much independently of font size. Therefore, to get the right font height for helvetica this works...
font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize * 0.865
Maybe use this?
http://pdfbox.apache.org/apidocs/org/apache/pdfbox/util/TextPosition.html
Seems like a wrap-around util for text. I haven't looked in the source if it accounts for font error though.
this is a working method for splitting the text and finding the height
public float heightForWidth(float width) throws IOException {
float height = 0;
String[] split = getTxt().split("(?<=\\W)");
int[] possibleWrapPoints = new int[split.length];
possibleWrapPoints[0] = split[0].length();
for (int i = 1; i < split.length; i++) {
possibleWrapPoints[i] = possibleWrapPoints[i - 1] + split[i].length();
}
float leading = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
int start = 0;
int end = 0;
for (int i : possibleWrapPoints) {
float w = font.getStringWidth(getTxt().substring(start, i)) / 1000 * fontSize;
if (start < end && w > width) {
height += leading;
start = end;
}
end = i;
}
height += leading;
return height + 3;
}
For imported True Type Fonts the total height of the font is
(org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getDescent() + org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getAscent() + org.apache.pdfbox.pdmodel.font.PDFont.getFontDescriptor().getLeading()) * point size * org.apache.pdfbox.pdmodel.font.PDFont.getFontMatrix().getValue(0, 0)
You will find that font.getFontDescriptor().getFontBoundingBox().getHeight() is 20% larger than the above value as it includes a 20% leading on the above value, but if you take the top value and remove 20%, the font will be right next too each other