fix linespacing in table cells, add max height to table rendering

This commit is contained in:
Jörg Prante 2023-08-25 15:25:33 +02:00
parent b3167d16ce
commit 26d8f2756a
11 changed files with 211 additions and 13 deletions

View file

@ -1,5 +1,5 @@
group = org.xbib.graphics group = org.xbib.graphics
name = graphics name = graphics
version = 4.5.3 version = 4.5.4
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL

View file

@ -21,6 +21,8 @@ public class TableElement implements Element, Drawable, Dividable {
private Position absolutePosition; private Position absolutePosition;
private Position endPosition;
public TableElement() { public TableElement() {
this.table = Table.builder(); this.table = Table.builder();
} }
@ -75,6 +77,10 @@ public class TableElement implements Element, Drawable, Dividable {
table.verticalAlignment(verticalAlignment); table.verticalAlignment(verticalAlignment);
} }
public void maximumHeight(float maximumHeight) {
table.maximumHeight(maximumHeight);
}
@Override @Override
public float getWidth() { public float getWidth() {
return table.build().getWidth(); return table.build().getWidth();
@ -102,17 +108,27 @@ public class TableElement implements Element, Drawable, Dividable {
this.absolutePosition = absolutePosition; this.absolutePosition = absolutePosition;
} }
public void setEndPosition(Position endPosition) {
this.endPosition = endPosition;
}
public Position getEndPosition() {
return endPosition;
}
@Override @Override
public void draw(PDDocument pdDocument, PDPageContentStream contentStream, public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
Position upperLeft, Transform transform, DrawListener drawListener) throws IOException { Position upperLeft, Transform transform, DrawListener drawListener) throws IOException {
TableRenderer tableRenderer = TableRenderer.builder() TableRenderer.Builder tableRenderer = TableRenderer.builder()
.table(table.build()) .table(table.build())
.document(pdDocument) .document(pdDocument)
.contentStream(contentStream) .contentStream(contentStream)
.startX(upperLeft.getX()) .startX(upperLeft.getX())
.startY(upperLeft.getY()) .startY(upperLeft.getY());
.build(); if (endPosition != null) {
tableRenderer.draw(); tableRenderer.endY(endPosition.getY());
}
tableRenderer.build().draw();
if (drawListener != null) { if (drawListener != null) {
drawListener.drawn(this, upperLeft, getWidth(), getHeight()); drawListener.drawn(this, upperLeft, getWidth(), getHeight());
} }

View file

@ -64,8 +64,10 @@ public class CellCommand implements Command {
} }
cell.colSpan(settings.getAsInt("colspan", 1)); cell.colSpan(settings.getAsInt("colspan", 1));
cell.rowSpan(settings.getAsInt("rowspan", 1)); cell.rowSpan(settings.getAsInt("rowspan", 1));
cell.lineSpacing(settings.getAsFloat("linespacing", 1.0f));
state.getElements().peek().add(cell.build()); state.getElements().peek().add(cell.build());
} else if (settings.containsSetting("markup")) { } else if (settings.containsSetting("markup")) {
float linespacing = settings.getAsFloat("linespacing", 1.0f);
ParagraphCell.Builder cell = ParagraphCell.builder(); ParagraphCell.Builder cell = ParagraphCell.builder();
cell.colSpan(settings.getAsInt("colspan", 1)); cell.colSpan(settings.getAsInt("colspan", 1));
cell.rowSpan(settings.getAsInt("rowspan", 1)); cell.rowSpan(settings.getAsInt("rowspan", 1));
@ -79,16 +81,14 @@ public class CellCommand implements Command {
if (settings.containsSetting("alignment")) { if (settings.containsSetting("alignment")) {
paragraph.setAlignment(Alignment.valueOf(settings.get("alignment", "left").toUpperCase(Locale.ROOT))); paragraph.setAlignment(Alignment.valueOf(settings.get("alignment", "left").toUpperCase(Locale.ROOT)));
} }
paragraph.setLineSpacing(linespacing);
cell.paragraph(paragraph); cell.paragraph(paragraph);
String value = settings.get("markup"); String value = settings.get("markup");
float size = settings.getAsFloat("fontsize", 11.0f); float size = settings.getAsFloat("fontsize", 11.0f);
float lineSpacing = settings.getAsFloat("linespacing", -1f);
Document document = state.getDocument(); Document document = state.getDocument();
Font font = document.getFont(settings.get("font", "helvetica")); Font font = document.getFont(settings.get("font", "helvetica"));
cell.add(new Markup().setValue(value).setFont(font).setFontSize(size)); cell.add(new Markup().setValue(value).setFont(font).setFontSize(size));
if (lineSpacing >= 0f) { cell.lineSpacing(linespacing);
cell.lineSpacing(lineSpacing);
}
state.getElements().peek().add(cell.build()); state.getElements().peek().add(cell.build());
} }
} }

View file

@ -25,6 +25,12 @@ public class TableCommand implements Command {
if (settings.containsSetting("x") && settings.containsSetting("y")) { if (settings.containsSetting("x") && settings.containsSetting("y")) {
tableElement.setAbsolutePosition(new Position(mmToPt(settings.getAsFloat("x", 0f)), mmToPt(settings.getAsFloat("y", 0f)))); tableElement.setAbsolutePosition(new Position(mmToPt(settings.getAsFloat("x", 0f)), mmToPt(settings.getAsFloat("y", 0f))));
} }
if (settings.containsSetting("endx") && settings.containsSetting("endy")) {
tableElement.setEndPosition(new Position(mmToPt(settings.getAsFloat("endx", 0f)), mmToPt(settings.getAsFloat("endy", 0f))));
}
if (settings.containsSetting("maxheight")) {
tableElement.maximumHeight(mmToPt(settings.getAsFloat("maxheight", 0f)));
}
if (settings.containsSetting("padding")) { if (settings.containsSetting("padding")) {
String padding = settings.get("padding", "0 0 0 0"); String padding = settings.get("padding", "0 0 0 0");
String[] paddings = padding.split("\\s+"); String[] paddings = padding.split("\\s+");

View file

@ -42,6 +42,8 @@ public class Table {
private float width; private float width;
private float maximumHeight;
public Table(List<Row> rows, List<Column> columns, Set<Point> rowSpanCells) { public Table(List<Row> rows, List<Column> columns, Set<Point> rowSpanCells) {
this.rows = rows; this.rows = rows;
this.columns = columns; this.columns = columns;
@ -64,6 +66,14 @@ public class Table {
return width; return width;
} }
public void setMaximumHeight(float maximumHeight) {
this.maximumHeight = maximumHeight;
}
public float getMaximumHeight() {
return maximumHeight;
}
public static BorderStyleInterface getDefaultBorderStyle() { public static BorderStyleInterface getDefaultBorderStyle() {
return DEFAULT_BORDER_STYLE; return DEFAULT_BORDER_STYLE;
} }
@ -142,6 +152,8 @@ public class Table {
private float width; private float width;
private float maximumHeight;
private Builder() { private Builder() {
parameters.setFont(DEFAULT_FONT); parameters.setFont(DEFAULT_FONT);
parameters.setFontSize(DEFAULT_FONT_SIZE); parameters.setFontSize(DEFAULT_FONT_SIZE);
@ -280,6 +292,11 @@ public class Table {
return this; return this;
} }
public Builder maximumHeight(float maximumHeight) {
this.maximumHeight = maximumHeight;
return this;
}
public Table build() { public Table build() {
if (getNumberOfRegularCells() != getNumberOfSpannedCells()) { if (getNumberOfRegularCells() != getNumberOfSpannedCells()) {
throw new TableSetupException("Number of table cells does not match with table setup. " + throw new TableSetupException("Number of table cells does not match with table setup. " +
@ -288,6 +305,7 @@ public class Table {
Table table = new Table(rows, columns, rowSpanCells); Table table = new Table(rows, columns, rowSpanCells);
table.setSettings(parameters); table.setSettings(parameters);
table.setWidth(width); table.setWidth(width);
table.setMaximumHeight(maximumHeight);
table.setNumberOfColumns(numberOfColumns); table.setNumberOfColumns(numberOfColumns);
setupConnectionsBetweenElementsFor(table); setupConnectionsBetweenElementsFor(table);
correctHeightOfCellsDueToRowSpanningIfNecessaryFor(table); correctHeightOfCellsDueToRowSpanningIfNecessaryFor(table);

View file

@ -14,6 +14,8 @@ import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND;
@ -152,12 +154,15 @@ public class TableRenderer {
protected void drawWithFunction(PageData pageData, Point2D.Float startingPoint, BiConsumer<Renderer, TableRenderContext> consumer) { protected void drawWithFunction(PageData pageData, Point2D.Float startingPoint, BiConsumer<Renderer, TableRenderContext> consumer) {
float y = startingPoint.y; float y = startingPoint.y;
float miny = startingPoint.y - table.getMaximumHeight();
for (int rowIndex = pageData.firstRowOnPage; rowIndex < pageData.firstRowOnNextPage; rowIndex++) { for (int rowIndex = pageData.firstRowOnPage; rowIndex < pageData.firstRowOnNextPage; rowIndex++) {
final Row row = table.getRows().get(rowIndex); final Row row = table.getRows().get(rowIndex);
y -= row.getHeight(); y -= row.getHeight();
if (table.getMaximumHeight() == 0.0f || y > miny) {
drawRow(new Point2D.Float(startingPoint.x, y), row, rowIndex, consumer); drawRow(new Point2D.Float(startingPoint.x, y), row, rowIndex, consumer);
} }
} }
}
protected void drawRow(Point2D.Float start, Row row, int rowIndex, BiConsumer<Renderer, TableRenderContext> consumer) { protected void drawRow(Point2D.Float start, Row row, int rowIndex, BiConsumer<Renderer, TableRenderContext> consumer) {
float x = start.x; float x = start.x;

View file

@ -37,6 +37,8 @@ public class TextCell extends AbstractTextCell {
private int rowSpan; private int rowSpan;
private float lineSpacing = 1.0f;
private Builder() { private Builder() {
parameters = new Parameters(); parameters = new Parameters();
} }
@ -178,10 +180,16 @@ public class TextCell extends AbstractTextCell {
return this; return this;
} }
public Builder lineSpacing(float lineSpacing) {
this.lineSpacing = lineSpacing;
return this;
}
public TextCell build() { public TextCell build() {
TextCell cell = new TextCell(); TextCell cell = new TextCell();
cell.setParameters(parameters); cell.setParameters(parameters);
cell.setText(text); cell.setText(text);
cell.setLineSpacing(lineSpacing);
if (colSpan > 0) { if (colSpan > 0) {
cell.setColSpan(colSpan); cell.setColSpan(colSpan);
} }

View file

@ -17,8 +17,7 @@ public class DrawableCellRenderer extends AbstractCellRenderer<DrawableCell> {
@Override @Override
public void renderContent(TableRenderContext tableRenderContext) { public void renderContent(TableRenderContext tableRenderContext) {
Drawable drawable = cell.getDrawable(); Drawable drawable = cell.getDrawable();
if (drawable instanceof WidthRespecting) { if (drawable instanceof WidthRespecting widthRespecting) {
WidthRespecting widthRespecting = (WidthRespecting) drawable;
widthRespecting.setMaxWidth(cell.getWidth()); widthRespecting.setMaxWidth(cell.getWidth());
} }
float x = tableRenderContext.getStartingPoint().x + cell.getPaddingLeft(); float x = tableRenderContext.getStartingPoint().x + cell.getPaddingLeft();

View file

@ -244,7 +244,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
* @return a copy of this text flow where all leading {@link NewLine}s are removed. * @return a copy of this text flow where all leading {@link NewLine}s are removed.
*/ */
public TextFlow removeLeadingEmptyLines() { public TextFlow removeLeadingEmptyLines() {
if (text.size() == 0 || !(text.get(0) instanceof NewLine)) { if (text.isEmpty() || !(text.get(0) instanceof NewLine)) {
return this; return this;
} }
TextFlow result = createInstance(); TextFlow result = createInstance();

View file

@ -0,0 +1,23 @@
package org.xbib.graphics.pdfbox.layout.test;
import org.junit.jupiter.api.Test;
import org.xbib.graphics.pdfbox.layout.element.Document;
import org.xbib.graphics.pdfbox.layout.element.scripting.Engine;
import org.xbib.settings.Settings;
import java.io.FileOutputStream;
public class ScriptingTableTest {
@Test
public void testTable() throws Exception {
Settings settings = Settings.settingsBuilder()
.loadFromResource("json", getClass().getResourceAsStream("scriptingtable.json"))
.build();
try (Engine engine = new Engine()) {
engine.execute(settings);
Document document = engine.getState().getDocument();
document.render().save(new FileOutputStream("build/scriptingtable.pdf")).close();
}
}
}

View file

@ -0,0 +1,123 @@
{
"type": "document",
"margin": "0 0 0 0",
"author": "Jörg Prante",
"font": [
"helvetica",
"notosans"
],
"elements": [
{
"type": "table",
"padding": "10 10 10 10",
"columnwidths": "50 50 50",
"elements": [
{
"type": "row",
"elements": [
{
"type": "cell",
"text": "Cell A"
},
{
"type": "cell",
"text": "Cell B"
},
{
"type": "cell",
"text": "Cell C"
}
]
},
{
"type": "row",
"elements": [
{
"type": "cell",
"text": "Cell D"
},
{
"type": "cell",
"text": "Cell E"
},
{
"type": "cell",
"text": "Cell F"
}
]
}
]
},
{
"type": "table",
"columnwidths": "60 40 20",
"maxheight": 25,
"elements": [
{
"type": "row",
"elements": [
{
"type": "cell",
"text": "This is very long text for cell 1 This is very long text for cell 1 This is very long text for cell 1",
"linespacing": 0.5
},
{
"type": "cell",
"text": "Cell 2"
},
{
"type": "cell",
"text": "Cell 3"
}
]
},
{
"type": "row",
"elements": [
{
"type": "cell",
"text": "Cell 4",
"padding": "5 5 5 5",
"borderwidth": 0.5
},
{
"type": "cell",
"text": "Cell 5",
"padding": "5 5 5 5",
"borderwidth": 0.5
},
{
"type": "cell",
"text": "Cell 6",
"padding": "5 5 5 5",
"borderwidth": 0.5
}
]
},
{
"type": "row",
"elements": [
{
"type": "cell",
"text": "Cell 7",
"padding": "5 5 5 5",
"borderwidth": 0.5
},
{
"type": "cell",
"text": "Cell 8",
"padding": "5 5 5 5",
"borderwidth": 0.5
},
{
"type": "cell",
"text": "Cell 9",
"padding": "5 5 5 5",
"borderwidth": 0.5
}
]
}
]
}
]
}