Reputation: 13
I have to take some reports in html to pdf using itextpdf. Mi client has printers using 4x11 inches therefore the reports must have this size.
The problem is he wants that reports that usually takes a page and a half in 4x11 fits in one single page of 4x11. As if we were scaling an image.
Following an itext7 tutorial about how to use pdfHtml to create single page content I created the reports in an 4xcontent height page size.
The question itself is if there is anyway to scale the content so it fits in the 4x11 inches regardless of the content height
Here is an example of the html code to convert into pdf
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="generator"
content="HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net"/>
<title>#Title</title>
<style type="text/css">
html, body {
padding: 0;
margin: 0;
}
@media print {
@page {
margin: 2px;
}
}
body {
font-family: Verdana, sans-serif;
font-size: 10px;
}
</style>
</head>
<body>
<div class="divTicketWrapper">
<div class="headerLogo"></div>
<div class="headerInfo">
<div style="font-size:18px; font-weight:NUMBERd;">COMPANY, LLC</div>
<div>COMPANY NAME</div>
<div>US, 123344</div>
<div>EMERGENCY CONTACT: 12345667</div>
</div>
<!-- LOAD INFORMATION -->
<div class="sectionHeader" width="100%">
<h3 style="font-size:10px;">INFORMATION</h3>
</div>
<table width="100%">
<tr width="100%">
<td class="divLabel">Product Type:</td>
<td class="divField" colspan="3">product 1</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">NUMBER #:</td>
<td class="divLeftColumn divField">1234556</td>
<td class="divRightColumn divLabel">Trucked By:</td>
<td class="divRightColumn divField">COMPANY, LLC</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Ticket #:</td>
<td class="divLeftColumn divField">123243355</td>
<td class="divRightColumn divLabel">Accepted Date/Time:</td>
<td class="divRightColumn divField">04/28/2000 22:07</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Split Ticket # w/ #:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">Confirmation #:</td>
<td class="divRightColumn divField">1233223423</td>
</tr>
<!-- DROP OFF TICKET NUMBER -->
<tr width="100%">
<td class="divLeftColumn divLabel">Receipt #:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">DO Receipt #:</td>
<td class="divRightColumn divField"></td>
</tr>
</table>
<div class="sectionHeader" width="100%">
<h3 style="font-size:10px;">INFORMATION</h3>
</div>
<table width="100%">
<tr width="100%">
<td class="divLabel">INFORMATION:</td>
<td class="divField" colspan="3">COMPANY LLC</td>
</tr>
<tr width="100%">
<td class="divLabel">Name:</td>
<td class="divField" colspan="3">COMPANY A 2,3,4</td>
</tr>
<tr width="100%">
<td class="divLabel">Other name:</td>
<td class="divField" colspan="3">COMPANY</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">SOMETHING #:</td>
<td class="divLeftColumn divField">COMPANY</td>
<td class="divRightColumn divLabel">Arrival Date & Time::</td>
<td class="divRightColumn divField">04/28/2000 22:39</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">SOMETHING #:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">Load Time:</td>
<td class="divRightColumn divField">00:08</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Legal Description:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">Wait Time:</td>
<td class="divRightColumn divField">00:00</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Latitude:</td>
<td class="divLeftColumn divField">1212324343</td>
<td class="divRightColumn divLabel">SOMETHING Date & Time:</td>
<td class="divRightColumn divField">04/28/2000 22:47</td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Longitude:</td>
<td class="divLeftColumn divField">123322344</td>
<td class="divRightColumn divLabel">Loaded Miles:</td>
<td class="divRightColumn divField">100</td>
</tr>
<tr width="100%">
<td class="divLabel">County, State::</td>
<td class="divField" colspan="3">COMPANY</td>
</tr>
<tr width="100%">
<td class="divLabel">Wait Time Notes:</td>
<td class="divField" colspan="3"></td>
</tr>
<tr width="100%">
<td class="divLabel">Reject Notes:</td>
<td class="divField" colspan="3"></td>
</tr>
<tr width="100%">
<td class="divLabel">Other Notes:</td>
<td class="divField" colspan="3">SOMETHING In Production;</td>
</tr>
</table>
<div class="sectionHeader" width="100%">
<h3 style="font-size:10px;">PICK UP</h3>
</div>
<table width="100%">
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">ACCEPT</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap"></td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY Type</td>
<td class="divLeftColumn divField divFieldWrap">COMPANY</td>
<td class="divRightColumn divLabel">BS&W(%)</td>
<td class="divRightColumn divField divFieldWrap">0.1</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">COMPANY</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0.0</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0.01</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">53.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">49.9</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">194.0</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">COMPANY</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">190.53</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">04/28/2000
22:39
</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">190.72</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">121313</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">04/28/2000
22:47
</td>
</tr>
</table>
<!-- / SOMETHING INFORMATION -->
<div class="sectionHeader" width="100%">
<h3 style="font-size:10px;">PICK UP</h3>
</div>
<table width="100%">
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">ACCEPT</td>
<td class="divRightColumn divLabel">Reject Reason</td>
<td class="divRightColumn divField divFieldWrap"></td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">COMPANY</td>
<td class="divRightColumn divLabel">BS&W(%)</td>
<td class="divRightColumn divField divFieldWrap">0.1</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">COMPANY</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0.0</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0.01</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">90.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">53.0</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">49.9</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">194.0</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">COMPANY</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">190.53</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">04/28/2000
22:39
</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">190.72</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">108712121215</td>
</tr>
<tr>
<td class="divLeftColumn divLabel">COMPANY</td>
<td class="divLeftColumn divField divFieldWrap">0 ft 0 in 0
in<br/>
<br/>
(0.0 in)
</td>
<td class="divRightColumn divLabel">COMPANY</td>
<td class="divRightColumn divField divFieldWrap">04/28/2000
22:47
</td>
</tr>
</table>
<!-- SOMETHING INFORMATION -->
<div class="sectionHeader" width="100%">
<h3 style="font-size:10px;">COMPANY</h3>
</div>
<table width="100%">
<tr width="100%">
<td class="divLabel">COMPANY:</td>
<td class="divField" colspan="3"></td>
</tr>
<tr width="100%">
<td class="divLabel">COMPANY:</td>
<td class="divField" colspan="3"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">Arrival Date & Time:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY #:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">COMPANY:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Latitude:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">Wait Time:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">Longitude:</td>
<td class="divLeftColumn divField"></td>
<td class="divRightColumn divLabel">SOMETHING Date & Time:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLabel">COMPANY</td>
<td class="divField" colspan="3">COMPANY</td>
</tr>
<tr width="100%">
<td class="divLabel">Wait Time Notes:</td>
<td class="divField" colspan="3"></td>
</tr>
<tr width="100%">
<td class="divLabel">Other Notes:</td>
<td class="divField" colspan="3"></td>
</tr>
</table>
<!-- / SOMETHING INFORMATION -->
<!-- SOMETHING SOMETHING & SOMETHING -->
<div class="">
<table width="100%" style="border-collapse: collapse;">
<tr width="100%">
<td class="divLeftColumn divLabel divLabelBackGround">
COMPANY
</td>
<td class="divRightColumn divLabel divLabelBackGround">
COMPANY
</td>
</tr>
</table>
<table width="100%">
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY #:</td>
<td class="divLeftColumn divField">100</td>
<td class="divRightColumn divLabel">COMPANY #:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY #:</td>
<td class="divLeftColumn divField">1015</td>
<td class="divRightColumn divLabel">COMPANY #:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY:</td>
<td class="divLeftColumn divField">COMPANY</td>
<td class="divRightColumn divLabel">COMPANY:</td>
<td class="divRightColumn divField"></td>
</tr>
<tr width="100%">
<td class="divLeftColumn divLabel">COMPANY:</td>
<td class="divLeftColumn divField">102</td>
<td class="divRightColumn divLabel">COMPANY:</td>
<td class="divRightColumn divField"></td>
</tr>
</table>
</div>
<p>hola</p>
<table width="100%" style="border-collapse: collapse;">
<tr width="100%">
<td style="text-align:center;"></td>
</tr>
</table>
</div>
</body>
</html>
Now using this java code I get the PDF report in twoo 4x11 inches pages
String path = "src/main/resources/html/";
String pdFName = String.valueOf(UUID.randomUUID());
PdfWriter pdfWriter = new PdfWriter(new FileOutputStream(path + pdFName + ".pdf"));
PdfDocument pdf = new PdfDocument(pdfWriter);
pdf.setDefaultPageSize(new PageSize(new Rectangle(288, 792)));//4x11 inches
Document document = HtmlConverter.convertToDocument(new FileInputStream(path + "out.html"), pdf, null);
document.close();
And using this java code I get the hole report in one single Pdf 4x15 inches page
ConverterProperties properties = new ConverterProperties();
PdfWriter pdfWriter = new PdfWriter(new FileOutputStream(path + pdFName + ".pdf"));
PdfDocument pdf = new PdfDocument(pdfWriter);
pdf.setDefaultPageSize(new PageSize(new Rectangle(288, 14400)));//4x full PDF height
Document document = HtmlConverter.convertToDocument(new FileInputStream(path + "out.html"), pdf, properties);
EndPosition endPosition = new EndPosition();
LineSeparator separator = new LineSeparator(endPosition);
document.add(separator);
document.getRenderer().close();
PdfPage page = pdf.getPage(1);
float y = endPosition.getY() - 36;
page.setMediaBox(new Rectangle(0, y, 288, 14400 - y));
document.close();
private static class EndPosition implements ILineDrawer {
protected float y;
public float getY() {
return y;
}
@Override
public void draw(PdfCanvas pdfCanvas, Rectangle rectangle) {
this.y = rectangle.getY();
}
@Override
public float getLineWidth() {
return 0;
}
@Override
public void setLineWidth(float v) {
}
@Override
public Color getColor() {
return null;
}
@Override
public void setColor(Color color) {
}
}
But I still need the hole page to fit 4x11 inches regardles of the height of the content. When I print the 4x15 inches page I can specify the printer to fit the content into 4x11 inches and the printer shrinks it... but I need to this using code not the printer.
Upvotes: 1
Views: 4533
Reputation: 12312
Of course simply resizing 4x15 page into 4x11 one is possible with transformation matrix in the content stream of a page, but that will skew all the content, which is likely undesirable.
Instead, what we can do is first convert the HTML into elements instead of converting to the document directly and then scale the font size with various scale factors and emulate layouting elements on the page to see at which scale factor all the elements will fit into one page completely.
Please note that the approach and code I am describing relies on some assumptions made about input data, e.g. that the HTML does not use advanced CSS layouts like floats or absolute positioning as well as that it does not contain images. For more complex inputs some additional modifications or complete rewrite of the code might be required, so please test it carefully and use it at your own risk.
To test out different font sizes, binary search is our best friend and allows a giant speed up.
First off, a helper function to scale font size of the element recursively:
private void scaleFontSizeRecursively(IElement element, float scale) {
if (element.hasOwnProperty(Property.FONT_SIZE)) {
UnitValue fontSize = element.getOwnProperty(Property.FONT_SIZE);
fontSize.setValue(fontSize.getValue() * scale);
}
if (element instanceof com.itextpdf.layout.element.AbstractElement) {
for (Object child : ((AbstractElement) element).getChildren()) {
scaleFontSizeRecursively((IElement) child, scale);
}
}
}
Now the meaty part of the code. In the binary search body we scale all the font sizes of the elements and emulate complete document layout to see if one page was enough. Then we do some clean ups 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 pdf = new PdfDocument(pdfWriter);
pdf.setDefaultPageSize(new PageSize(new Rectangle(72 * 4, 72 * 11)));
// Important to set last boolean parameter to false so that added elements are not drawn immediately
Document document = new Document(pdf, pdf.getDefaultPageSize(), false);
List<IElement> elements = HtmlConverter.convertToElements(html);
float lFontSizeScale = 0.001f;
float rFontSize = 1;
// Initially adding all the elements into the document. They will be document's children from now on
for (IElement element : elements) {
if (element instanceof IBlockElement) {
document.add((IBlockElement) element);
} else {
throw new IllegalStateException("Unexpected situation that needs to be additionally handled in code");
}
}
for (int i = 0; i < 20; i++) {
float midFontSize = (lFontSizeScale + rFontSize) / 2;
for (IElement element : elements) {
scaleFontSizeRecursively(element, midFontSize);
}
// Relayouting all the elements with the new font size
document.relayout();
// See if one page was enough
if (document.getPdfDocument().getNumberOfPages() == 1) {
lFontSizeScale = midFontSize;
} else {
rFontSize = midFontSize;
}
// Roll back our font size scaling
for (IElement element : elements) {
scaleFontSizeRecursively(element, 1 / midFontSize);
}
// Remove extra created pages
while (pdf.getNumberOfPages() > 1) {
pdf.removePage(2);
}
}
// Finally setting the font scale that was the match for us and layouting again
for (IElement element : elements) {
scaleFontSizeRecursively(element, lFontSizeScale);
}
document.relayout();
System.out.println("Resultant scale: " + lFontSizeScale);
document.close();
Running the code produces a valid document with one page of 4x11 inches and all the content is present there. Console output:
Resultant scale: 0.6297823
As you can see, we needed to scale down at ~63%
Upvotes: 1