Skip to content

Commit

Permalink
#79 - Tables implementation for PDF/UA with testcase.
Browse files Browse the repository at this point in the history
  • Loading branch information
danfickle committed Jan 27, 2019
1 parent 3477d3f commit 6cc4f40
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ public static void main(String... args) throws Exception {
run("image-over-two-pages");
run("running");
run("lists");
run("tables");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<html>
<head>
<title>Simple PDF/UA Table Testcase</title>
<meta name="description" content="A simple table example"/>
<style>
@page {
size: 200px 200px;
margin: 0;
}
body {
margin: 0;
width: 200px;
}
</style>
</head>
<body style="font-family: 'TestFont'; font-size: 14px;">
<h1>A simple table example</h1>

<h2>Table without header or footer</h2>
<table>
<tr><td>One</td><td>Two</td></tr>
<tr><td>Three</td><td>Four</td></tr>
</table>

<h2>Table with everything</h2>
<table>
<caption>Simple table example with fake data</caption>

<thead>
<tr><th>Col One</th><th>Col Two</th></tr>
</thead>

<tbody>
<tr><td>One</td><td>Two</td></tr>
<tr><td>Three</td><td>Four</td></tr>
</tbody>

<tfoot>
<tr><td>Footer1</td><td>Footer2</td></tr>
<tr><td>Footer3</td><td>Footer4</td></tr>
</tfoot>
</table>

<h2>Using colspan</h2>

<table>
<tr><td colspan="2">Two columns</td></tr>
<tr><td>One</td><td>Two</td></tr>
</table>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.Revisions;
import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.StandardStructureTypes;
import org.apache.xmpbox.type.AbstractStructuredType;
import org.w3c.dom.Document;

import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.extend.StructureType;
import com.openhtmltopdf.newtable.TableCellBox;
import com.openhtmltopdf.render.BlockBox;
import com.openhtmltopdf.render.Box;
import com.openhtmltopdf.render.InlineLayoutBox;
Expand Down Expand Up @@ -63,7 +66,10 @@ private static Map<String, Supplier<AbstractStructualElement>> createTagSupplier
suppliers.put("ol", ListStructualElement::new);
suppliers.put("li", ListItemStructualElement::new);

// TODO: Tables.
suppliers.put("table", TableStructualElement::new);
suppliers.put("tr", TableRowStructualElement::new);
suppliers.put("td", TableCellStructualElement::new);
suppliers.put("th", TableHeaderStructualElement::new);

return suppliers;
}
Expand Down Expand Up @@ -126,8 +132,8 @@ void addChild(AbstractTreeItem child) {
}

private static class ListItemStructualElement extends AbstractStructualElement {
ListLabelStructualElement label;
ListBodyStructualElement body;
final ListLabelStructualElement label;
final ListBodyStructualElement body;

ListItemStructualElement() {
this.body = new ListBodyStructualElement();
Expand All @@ -148,11 +154,18 @@ void addChild(AbstractTreeItem child) {
}
}

private static class ListLabelStructualElement extends GenericStructualElement {
private static class ListLabelStructualElement extends AbstractStructualElement {
final List<AbstractTreeItem> children = new ArrayList<>(1);

@Override
String getPdfTag() {
return StandardStructureTypes.LBL;
}

@Override
void addChild(AbstractTreeItem child) {
this.children.add(child);
}
}

private static class ListBodyStructualElement extends GenericStructualElement {
Expand All @@ -162,6 +175,67 @@ String getPdfTag() {
}
}

private static class TableStructualElement extends AbstractStructualElement {
final TableHeadStructualElement thead = new TableHeadStructualElement();
final List<TableBodyStructualElement> tbodies = new ArrayList<>(1);
final TableFootStructualElement tfoot = new TableFootStructualElement();

@Override
void addChild(AbstractTreeItem child) {
this.tbodies.add((TableBodyStructualElement) child);
}

@Override
String getPdfTag() {
return StandardStructureTypes.TABLE;
}
}

private static class TableHeadStructualElement extends GenericStructualElement {
@Override
String getPdfTag() {
return StandardStructureTypes.T_HEAD;
}
}

private static class TableBodyStructualElement extends GenericStructualElement {
@Override
String getPdfTag() {
return StandardStructureTypes.T_BODY;
}
}

private static class TableFootStructualElement extends GenericStructualElement {
@Override
String getPdfTag() {
return StandardStructureTypes.T_FOOT;
}
}

private static class TableRowStructualElement extends GenericStructualElement {
@Override
String getPdfTag() {
return StandardStructureTypes.TR;
}
}

private static class TableHeaderStructualElement extends GenericStructualElement {
@Override
String getPdfTag() {
return StandardStructureTypes.TH;
}
}

private static class TableCellStructualElement extends GenericStructualElement {
int rowspan = 1;
int colspan = 1;

@Override
String getPdfTag() {
return StandardStructureTypes.TD;
}
}

private static class FigureStructualElement extends AbstractStructualElement {
String alternateText;
PDRectangle boundingBox;
Expand Down Expand Up @@ -299,6 +373,10 @@ private static String chooseTag(Box box) {
return StandardStructureTypes.SPAN;
}

private void finishTreeItems(List<? extends AbstractTreeItem> children, AbstractStructualElement parent) {
children.forEach(itm -> finishTreeItem(itm, parent));
}

private void finishTreeItem(AbstractTreeItem item, AbstractStructualElement parent) {
if (item instanceof GenericContentItem) {
// A content item (text or replaced image), we need to add it to its parent structual item.
Expand Down Expand Up @@ -354,7 +432,7 @@ private void finishTreeItem(AbstractTreeItem item, AbstractStructualElement pare

createPdfStrucureElement(parent, child);

child.listItems.forEach(itm -> finishTreeItem(itm, child));
finishTreeItems(child.listItems, child);

} else if (item instanceof ListItemStructualElement) {
ListItemStructualElement child = (ListItemStructualElement) item;
Expand All @@ -374,15 +452,73 @@ private void finishTreeItem(AbstractTreeItem item, AbstractStructualElement pare

createPdfStrucureElement(parent, child);

child.children.forEach(itm -> finishTreeItem(itm, child));
finishTreeItems(child.children, child);

} else if (item instanceof ListBodyStructualElement) {
ListBodyStructualElement child = (ListBodyStructualElement) item;

createPdfStrucureElement(parent, child);

child.children.forEach(itm -> finishTreeItem(itm, child));
finishTreeItems(child.children, child);

} else if (item instanceof TableStructualElement) {
TableStructualElement child = (TableStructualElement) item;

createPdfStrucureElement(parent, child);

finishTreeItem(child.thead, child);
finishTreeItems(child.tbodies, child);
finishTreeItem(child.tfoot, child);
} else if (item instanceof TableHeadStructualElement) {
TableHeadStructualElement child = (TableHeadStructualElement) item;
createPdfStrucureElement(parent, child);
finishTreeItems(child.children, child);
} else if (item instanceof TableBodyStructualElement) {
TableBodyStructualElement child = (TableBodyStructualElement) item;
createPdfStrucureElement(parent, child);
finishTreeItems(child.children, child);
} else if (item instanceof TableFootStructualElement) {
TableFootStructualElement child = (TableFootStructualElement) item;
createPdfStrucureElement(parent, child);
finishTreeItems(child.children, child);
} else if (item instanceof TableRowStructualElement) {
TableRowStructualElement child = (TableRowStructualElement) item;
createPdfStrucureElement(parent, child);
finishTreeItems(child.children, child);
} else if (item instanceof TableHeaderStructualElement) {
TableHeaderStructualElement child = (TableHeaderStructualElement) item;
createPdfStrucureElement(parent, child);
finishTreeItems(child.children, child);
} else if (item instanceof TableCellStructualElement) {
TableCellStructualElement child = (TableCellStructualElement) item;
createPdfStrucureElement(parent, child);

COSDictionary colspan = null;
COSDictionary rowspan = null;

if (child.colspan != 1) {
colspan = new COSDictionary();
colspan.setInt(COSName.getPDFName("ColSpan"), child.colspan);
colspan.setItem(COSName.O, COSName.getPDFName("Table"));
}
if (child.rowspan != 1) {
rowspan = new COSDictionary();
rowspan.setInt(COSName.getPDFName("RowSpan"), child.rowspan);
rowspan.setItem(COSName.O, COSName.getPDFName("Table"));
}

if (colspan != null || rowspan != null) {
Revisions<PDAttributeObject> attributes = new Revisions<>();
if (colspan != null) {
attributes.addObject(PDAttributeObject.create(colspan), 0);
}
if (rowspan != null) {
attributes.addObject(PDAttributeObject.create(rowspan), 0);
}
child.elem.setAttributes(attributes);
}

finishTreeItems(child.children, child);
} else if (item instanceof GenericStructualElement) {
// A structual element such as Div, Sect, p, etc
// which contains other structual elements or content items (text).
Expand All @@ -396,12 +532,12 @@ private void finishTreeItem(AbstractTreeItem item, AbstractStructualElement pare
if (child.box instanceof LineBox &&
!child.box.hasNonTextContent(_ctx)) {
// We skip line boxes in the tree.
child.children.forEach(itm -> finishTreeItem(itm, parent));
finishTreeItems(child.children, parent);
} else {
createPdfStrucureElement(parent, child);

// Recursively, depth first, process the structual tree.
child.children.forEach(itm -> finishTreeItem(itm, child));
finishTreeItems(child.children, child);
}
}
}
Expand Down Expand Up @@ -466,13 +602,39 @@ private AbstractStructualElement createStructureItem(StructureType type, Box box
}
}

if (child == null &&
box.getParent() != null &&
box.getParent().getAccessibilityObject() instanceof TableStructualElement) {

TableStructualElement table = (TableStructualElement) box.getParent().getAccessibilityObject();

if (box.getStyle().isIdent(CSSName.DISPLAY, IdentValue.TABLE_HEADER_GROUP)) {
child = table.thead;
} else if (box.getStyle().isIdent(CSSName.DISPLAY, IdentValue.TABLE_ROW_GROUP)) {
child = new TableBodyStructualElement();
} else if (box.getStyle().isIdent(CSSName.DISPLAY, IdentValue.TABLE_FOOTER_GROUP)) {
child = table.tfoot;
}
}


if (child == null) {
child = new GenericStructualElement();
}

child.page = _page;
child.box = box;

if (child instanceof TableCellStructualElement &&
box instanceof TableCellBox) {

TableCellBox cell = (TableCellBox) box;

((TableCellStructualElement) child).colspan = cell.getStyle().getColSpan();
((TableCellStructualElement) child).rowspan = cell.getStyle().getRowSpan();
}


return child;
}

Expand All @@ -485,7 +647,13 @@ private void setupStructureElement(AbstractStructualElement child, Box box) {

private void ensureParent(Box box, AbstractTreeItem child) {
if (child.parent == null) {
if (box.getParent() != null) {
if (child instanceof TableHeadStructualElement ||
child instanceof TableFootStructualElement) {
child.parent = (TableStructualElement) box.getParent().getAccessibilityObject();
} else if (child instanceof TableBodyStructualElement) {
child.parent = (TableStructualElement) box.getParent().getAccessibilityObject();
((TableStructualElement) child.parent).tbodies.add((TableBodyStructualElement) child);
} else if (box.getParent() != null) {
AbstractStructualElement parent = (AbstractStructualElement) box.getParent().getAccessibilityObject();
parent.addChild(child);
child.parent = parent;
Expand Down

0 comments on commit 6cc4f40

Please sign in to comment.