public Document nextTableRow(String atts) { // peek at current cell - stay with it IF // + it's the first cell in the row // + the current cursor points at the first child (a block) // + the block pointed by cursor is empty Element cell = peek("table-cell", "nextTableRow() is not applicable outside enclosing table"); if (cell.getPreviousSibling() == null && cursor == cell.getFirstChild() && !cursor.hasChildNodes()) { attributes((Element) cell.getParentNode(), atts); return this; } // pop to table pop("table", "nextTableRow() is not applicable outside enclosing table"); Element table = cursor; // last child is already table-body? if (table.getLastChild().getNodeName().equals("table-body")) { cursor = (Element) table.getLastChild(); } else { push("table-body"); } // add row push("table-row", atts); // add cell push("table-cell", "border=" + table.getAttribute("border")); push("block"); // done return this; }
/** Add a list item */ public Document nextListItem(String format) { // <list-block> // <list-item> // <list-item-label end-indent="label-end()"><block>•</block></list-item-label> // <list-item-body start-indent="body-start()"> // <block/> // </list-item-body> // </list-item> // check containing list-block Element list = peek("list-block", "nextListItem() is not applicable outside list block"); // a list with only one item containing an empty block? if (list.getChildNodes().getLength() == 1 && cursor.getFirstChild() == null && cursor.getPreviousSibling() == null && cursor.getParentNode().getLocalName().equals("list-item-body")) { // delete list-item and start over list.removeChild(list.getFirstChild()); } // continue with list cursor = list; // find out what 'bullet' to use String label = attribute("genj:label", format); if (label != null) { // check provisional-distance-between-starts - we assume a certain 'em' per label character String dist = list.getAttribute("provisional-distance-between-starts"); if (dist.endsWith("em")) { float len = label.length() * 0.6F; if (Float.parseFloat(dist.substring(0, dist.length() - 2)) < len) list.setAttribute("provisional-distance-between-starts", len + "em"); } } else { label = "\u2219"; // • /u2219 works in JEditPane, • \u2022 doesn't } // add new item push("list-item"); push("list-item-label", "end-indent=label-end()"); push("block"); text(label, ""); pop(); pop(); push("list-item-body", "start-indent=body-start()"); push("block"); return this; }
/** Add a link */ public Document addLink(String text, String id) { // <basic-link>text</basic-link> push("basic-link", "internal-destination=" + id); text(text, ""); pop(); // done return this; }
/** End a list */ public Document endList() { // * // <list-block> pop("list-block", "endList() is not applicable outside list-block").pop(); push("block", ""); return this; }
public Document nextTableCell(String atts) { // peek at current cell - stay with it IF // + it's the first cell in the row // + the current cursor points at the first child (a block) // + the block pointed by cursor is empty Element cell = peek("table-cell", "nextTableCell() is not applicable outside enclosing table"); if (cell.getPreviousSibling() == null && cursor == cell.getFirstChild() && !cursor.hasChildNodes()) { attributes(cell, atts); // add empty content to block so another call to nextTableCell() willl actually move forward push("inline", "").pop(); return this; } // peek at row Element row = peek("table-row", "nextTableCell() is not applicable outside enclosing table row"); int cells = row.getElementsByTagName("table-cell").getLength(); // peek at table - add new row if we have all columns already Element table = peek("table", "nextTableCell() is not applicable outside enclosing table"); int cols = table.getElementsByTagName("table-column").getLength(); if (cols > 0 && cells == cols) return nextTableRow(); // pop to row pop("table-row", "nextTableCell() is not applicable outside enclosing table row"); // 20060215 wanted to use border=inherit here but that would require table-row // and table-body to have border=inherit as well. table-body can't have a // border property in a table with border-collapse=separate (which is the only // model FOP supports). // So we're simply doing our own 'inherit' here :) Alternative would be to do // us border=from-nearest-specified-value() on each cell or border=inherit // on the table-columns and then border=from-table-column() on the cells. // add now push("table-cell", "border=" + table.getAttribute("border") + "," + atts); push("block"); // done return this; }
/** Add Table of content */ private Document toc() { // anything to do? if (toc.isEmpty()) return this; Element old = cursor; // pop back to flow pop("flow", "can't create TOC without enclosing flow"); // add block for toc AS FIRST child push("block", "", cursor.getFirstChild()); // <block> // Table of Contents // <block> // Title 1<leader/><page-number-citation/> // </block> // ... // </block> // add toc header push("block", formatSectionLarger); text(RESOURCES.getString("toc"), ""); pop(); // add toc entries for (Iterator it = toc.iterator(); it.hasNext(); ) { push( "block", "start-indent=1cm,end-indent=1cm,text-indent=0cm,text-align-last=justify,text-align=justify"); TOCEntry entry = (TOCEntry) it.next(); addLink(entry.text, entry.id); push("leader", "leader-pattern=dots").pop(); push("page-number-citation", "ref-id=" + entry.id).pop(); pop(); } // done cursor = old; return this; }
/** Start a list */ public Document startList(String format) { // <list-block> pop(); push( "list-block", "provisional-distance-between-starts=0.6em, provisional-label-separation=0pt," + format); nextListItem(); return this; }
/** Add text element */ private Document text(String text, String atts) { // ignore empties if (text.length() == 0) return this; // create a text node for it Node txt = doc.createTextNode(text); if (atts.length() > 0) { push("inline", atts); cursor.appendChild(txt); pop(); } else { cursor.appendChild(txt); } return this; }
public Document startTable(String format) { // patch format - FOP only supports table-layout=fixed format = "table-layout=fixed," + format; // <table> // <table-header> // <table-row> // <table-cell> // <block> // ... // </table-header> // <table-body> // <table-row> // <table-cell> // <block> // ... push("table", format); Element table = cursor; // mark as cvs if applicable - non fo namespace attributes won't be picked up by push() and // attributes() if ("true".equals(attribute("genj:csv", format))) { containsCSV = true; cursor.setAttributeNS(NS_GENJ, "genj:csv", "true"); String prefix = attribute("genj:csvprefix", format); if (prefix != null) cursor.setAttributeNS(NS_GENJ, "genj:csvprefix", prefix); } // head/body & row if (format.indexOf("genj:header=true") >= 0) { push("table-header"); push("table-row", "color=#ffffff,background-color=#c0c0c0,font-weight=bold"); } else { push("table-body"); push("table-row"); } // cell and done push("table-cell", "border=" + table.getAttribute("border")); push("block"); return this; }
/** Add a column to the table (this is not necessary) */ public Document addTableColumn(String atts) { // <table> // <table-column/> Element save = cursor; // find the enclosing table pop("table", "addTableColumn() is not applicable outside enclosing table"); // find last table definition Node before = cursor.getFirstChild(); while (before != null && before.getNodeName().equals("table-column")) before = before.getNextSibling(); push("table-column", atts, before); // done for now cursor = save; return this; }
/** * Add image file reference to the document * * @param file the file pointing to the image * @param atts fo attributes for the image */ public Document addImage(File file, String atts) { // anything we care about? if (file == null || !file.exists()) return this; // check dimension - let's not make this bigger than 1x1 inch Dimension2D dim = new ImageSniffer(file).getDimensionInInches(); if (dim == null) return this; if (dim.getWidth() > dim.getHeight()) { if (dim.getWidth() > 1) atts = "width=1in,content-width=scale-to-fit," + atts; // can be overriden } else { if (dim.getHeight() > 1) atts = "height=1in,content-height=scale-to-fit," + atts; // can be overriden } // <fo:external-graphic src="file"/> push("external-graphic", "src=" + file.getAbsolutePath() + "," + atts); // remember file in case a formatter wants to resolve file location later List elements = (List) file2elements.get(file); if (elements == null) { elements = new ArrayList(3); file2elements.put(file, elements); } elements.add(cursor); // back to enclosing block pop(); // add opportunity to line break addText(" "); // done return this; }
/** Add indexes */ private Document indexes() { // loop over indexes for (Iterator indexes = index2primary2secondary2elements.keySet().iterator(); indexes.hasNext(); ) { String index = (String) indexes.next(); Map primary2secondary2elements = (Map) index2primary2secondary2elements.get(index); // add section nextPage(); startSection(index); push("block", "start-indent=1cm"); // loop over primaries for (Iterator primaries = primary2secondary2elements.keySet().iterator(); primaries.hasNext(); ) { String primary = (String) primaries.next(); Map secondary2elements = (Map) primary2secondary2elements.get(primary); // add block and primary push("block", ""); text(primary + " ", ""); // loop over secondaries for (Iterator secondaries = secondary2elements.keySet().iterator(); secondaries.hasNext(); ) { String secondary = (String) secondaries.next(); List elements = (List) secondary2elements.get(secondary); if (secondary.length() > 0) { push("block", "start-indent=2cm"); // start-indent? text(secondary + " ", ""); } // loop over elements for (int e = 0; e < elements.size(); e++) { if (e > 0) text(", ", ""); Element element = (Element) elements.get(e); String id = element.getAttribute("id"); push("basic-link", "internal-destination=" + id); push("page-number-citation", "ref-id=" + id); cursor.setAttributeNS(NS_GENJ, "genj:citation", Integer.toString(e + 1)); pop(); pop(); } if (secondary.length() > 0) pop(); // next } // next pop(); } // next pop(); } // done return this; }
/** Force a page break */ public Document nextPage() { pop(); push("block", "page-break-before=always"); return this; }
/** Constructor */ public Document(String title) { // remember title this.title = title; // section size range setSectionSizes(FONT_MEDIUM, FONT_XX_LARGE); // create a dom document try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); doc = dbf.newDocumentBuilder().newDocument(); } catch (Throwable t) { throw new RuntimeException(t); } // boilerplate // <root> // <layout-master-set> // <simple-page-master> // <region-body/> // <region-end/> // </simple-page-master> // </layout-master-set> // <page-sequence> // <title> // <flow> // <block/> // </flow> // </page-sequence> cursor = (Element) doc.appendChild(doc.createElementNS(NS_XSLFO, "root")); cursor.setAttribute("xmlns", NS_XSLFO); cursor.setAttribute("xmlns:genj", NS_GENJ); // FOP crashes when a title element is present so we use an extension to pass it to our fo2html // stylesheet // @see http://issues.apache.org/bugzilla/show_bug.cgi?id=38710 cursor.setAttributeNS(NS_GENJ, "genj:title", title); push("layout-master-set"); // Tip: see also http://www.dpawson.co.uk/xsl/sect3/N8565.html for a minimal page master. push( "simple-page-master", "master-name=master,margin-top=1cm,margin-bottom=1cm,margin-left=1cm,margin-right=1cm"); push("region-body", "margin-bottom=1cm").pop(); push("region-after", "extent=0.8cm").pop(); pop().pop().push("page-sequence", "master-reference=master"); /* Paul Grosso offers this suggestion for left-center-right header formatting at http://www.dpawson.co.uk/xsl/sect3/headers.html#d13432e123: <fo:static-content flow-name="xsl-region-before"> <!-- header-width is the width of the full header in picas --> <xsl:variable name="header-width" select="36"/> <xsl:variable name="header-field-width"> <xsl:value-of select="$header-width * 0.3333"/><xsl:text>pc</xsl:text> </xsl:variable> <fo:list-block font-size="8pt" provisional-label-separation="0pt"> <xsl:attribute name="provisional-distance-between-starts"> <xsl:value-of select="$header-field-width"/> </xsl:attribute> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block text-align="left"> <xsl:text>The left header field</xsl:text> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:list-block provisional-label-separation="0pt"> <xsl:attribute name="provisional-distance-between-starts"> <xsl:value-of select="$header-field-width"/> </xsl:attribute> <fo:list-item> <fo:list-item-label end-indent="label-end()"> <fo:block text-align="center"> <fo:page-number/> </fo:block> </fo:list-item-label> <fo:list-item-body start-indent="body-start()"> <fo:block text-align="right"> <xsl:text>The right header field</xsl:text> </fo:block> </fo:list-item-body> </fo:list-item> </fo:list-block> </fo:list-item-body> </fo:list-item> </fo:list-block> </fo:static-content> */ push("static-content", "flow-name=xsl-region-after"); push("block", "text-align=center"); // text("p. ", ""); // todo bk better w/o text, to avoid language-dependency, but with title push("page-number").pop(); pop(); // </block> pop(); // </static-content> // don't use title - see above // push("title").text(getTitle(), "").pop(); push("flow", "flow-name=xsl-region-body"); push("block"); // done - cursor points to first block }