iText Core: Fixing incorrect height calculation
Background:
The BBox, or the Bounding Box, is the smallest rectangle that can enclose all the content on the page. The values represented by the bounding box area should be accurate as they can be used to calculate the size of the enclosed content.
However, in older versions of iText the returned values of a BBox (height and width) were slightly misrepresentative of the actual values represented in the PDF page. For instance, if one calculated a BBox of a paragraph and used its values to fit the paragraph into a cell with a fixed height, the paragraph would not fit in the cell and would therefore get clipped.
As of version 7.2.4, iText Core will now accurately calculate the BBox dimensions and can consistently return the actual values of any page content.
Example Code:
The following code snippet creates a table cell, adds it to the table, and then reads the area occupied by the cell by returning its BBox. The height/width values of the returned BBox are then applied to the same cell.
Java
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.element.Text;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.properties.TextAlignment;
import java.io.FileNotFoundException;
public class HeightCalculationTest {
public static void main(String[] args) throws FileNotFoundException {
PdfDocument pdfDoc = new PdfDocument(new PdfWriter("test.pdf"));
Document doc = new Document(pdfDoc, new PageSize(700, 700));
// This is how table looks like if no height property is set
addTable(doc, 504, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", -1);
// Here we set value from pre layout as height. We expect that this table shall be equal to the previous one
addTable(doc, 360, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 0);
// Here we set 100 as height. We expect that this will be enough, and all text will be placed
addTable(doc, 216, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 100);
// Here we set 102 as height. We expect that this will be enough, and all text will be placed
addTable(doc, 72, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 102);
doc.close();
}
public static void addTable(Document doc, int y, String text, int heightParam) {
float width = 585f;
float fontSize = 32f;
Table table = new Table(1);
table.setWidth(width);
table.setFixedLayout();
Paragraph ph = new Paragraph();
Text txt = new Text(text);
txt.setFontSize(fontSize);
ph.add(txt);
ph.setFixedLeading(fontSize);
Cell cell = new Cell();
cell.setPaddingTop(0f);
cell.setPaddingBottom(0f);
cell.add(ph);
cell.setBackgroundColor(ColorConstants.LIGHT_GRAY);
cell.setBorder(null);
table.addCell(cell);
// Retrieve the dimensions of the cell we just added
LayoutResult result = table.createRendererSubTree()
.setParent(doc.getRenderer())
.layout(
new LayoutContext(
new LayoutArea(
1,
new Rectangle(0, 0, width, 10000.0F)))
);
String heightStr = "Natural";
if (heightParam == 0) {
float rowHeight = result.getOccupiedArea().getBBox().getHeight();
cell.setHeight(rowHeight);
heightStr = "Calculated " + rowHeight;
} else if (heightParam > 0) {
cell.setHeight(heightParam);
heightStr = "Explicit " + heightParam;
}
table.setFixedPosition((float) 36, (float) y, width);
doc.add(table);
Table t2 = new Table(1);
t2.setWidth(width);
t2.setFixedLayout();
Cell c2 = new Cell();
c2.setTextAlignment(TextAlignment.CENTER);
c2.setWidth(width);
c2.setBorder(Border.NO_BORDER);
c2.add(new Paragraph("Row Height: " + heightStr));
t2.addCell(c2);
t2.setFixedPosition((float) 36, (float) y - 18, width);
doc.add(t2);
}
}
C#
using iText.Kernel.Colors;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Layout;
using iText.Layout.Properties;
namespace SUP_36259
{
public class HeightCalculationTest
{
public static void Main(string[] args)
{
using (var pdfDoc = new PdfDocument(new PdfWriter("test.pdf")))
using (var doc = new Document(pdfDoc, new PageSize(700, 700)))
{
// This is how table looks like if no height property is set
AddTable(doc, 504, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", -1);
// Here we set value from pre layout as height. We expect that this table shall be equal to the previous one
AddTable(doc, 360, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 0);
// Here we set 100 as height. We expect that this will be enough, and all text will be placed
AddTable(doc, 216, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 100);
// Here we set 102 as height. We expect that this will be enough, and all text will be placed
AddTable(doc, 72, "RETIREMENT PLANNING: BECAUSE YOU CAN’T BE A FINANCIAL PLANNER FOREVER.", 102);
}
}
public static void AddTable(Document doc, int y, string text, int heightParam)
{
const float width = 585f;
const float fontSize = 32f;
var table = new Table(1);
table.SetWidth(width);
table.SetFixedLayout();
var ph = new Paragraph();
var txt = new Text(text);
txt.SetFontSize(fontSize);
ph.Add(txt);
ph.SetFixedLeading(fontSize);
var cell = new Cell();
cell.SetPaddingTop(0f);
cell.SetPaddingBottom(0f);
cell.Add(ph);
cell.SetBackgroundColor(ColorConstants.LIGHT_GRAY);
cell.SetBorder(null);
table.AddCell(cell);
// Retrieve the dimensions of the cell we just added
var result = table.CreateRendererSubTree()
.SetParent(doc.GetRenderer())
.Layout(new LayoutContext(
new LayoutArea(
1,
new Rectangle(0, 0, width, 10000.0F))));
var heightStr = "Natural";
if (heightParam == 0)
{
var rowHeight = result.GetOccupiedArea().GetBBox().GetHeight();
cell.SetHeight(rowHeight);
heightStr = "Calculated " + rowHeight;
}
else if (heightParam > 0)
{
cell.SetHeight(heightParam);
heightStr = "Explicit " + heightParam;
}
table.SetFixedPosition((float)36, (float)y, width);
doc.Add(table);
var t2 = new Table(1);
t2.SetWidth(width);
t2.SetFixedLayout();
var c2 = new Cell();
c2.SetTextAlignment(TextAlignment.CENTER);
c2.SetWidth(width);
c2.SetBorder(Border.NO_BORDER);
c2.Add(new Paragraph("Row Height: " + heightStr));
t2.AddCell(c2);
t2.SetFixedPosition((float)36, (float)y - 18, width);
doc.Add(t2);
}
}
}
In older versions of iText Core, the text inside the cell gets clipped, proving that the BBox dimensions are miscalculated.
As of iText Core version 7.2.4, the text fits into the cell.