/** * Copyright (C) 2011-2015 The XDocReport Team <xdocreport@googlegroups.com> * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package fr.opensagres.odfdom.converter.pdf.internal.stylable; import java.io.OutputStream; import java.util.List; import org.odftoolkit.odfdom.dom.style.OdfStyleFamily; import com.itextpdf.text.DocumentException; import com.itextpdf.text.Element; import com.itextpdf.text.Image; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.ColumnText; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import fr.opensagres.odfdom.converter.core.ODFConverterException; import fr.opensagres.odfdom.converter.pdf.internal.StyleEngineForIText; import fr.opensagres.odfdom.converter.pdf.internal.styles.Style; import fr.opensagres.odfdom.converter.pdf.internal.styles.StylePageLayoutProperties; import fr.opensagres.xdocreport.itext.extension.ExtendedDocument; import fr.opensagres.xdocreport.itext.extension.IMasterPage; import fr.opensagres.xdocreport.itext.extension.IPdfAWriterConfiguration; import fr.opensagres.xdocreport.itext.extension.IPdfWriterConfiguration; import fr.opensagres.xdocreport.itext.extension.PageOrientation; import fr.opensagres.xdocreport.itext.extension.font.FontGroup; /** * fixes for pdf conversion by Leszek Piotrowicz <leszekp@safe-mail.net> */ public class StylableDocument extends ExtendedDocument implements IStylableContainer, IBoundsLimitContainer, IBreakHandlingContainer, IStylableFactory { private final StyleEngineForIText styleEngine; private Style lastStyleApplied = null; private StylableMasterPage activeMasterPage; private boolean masterPageJustChanged; private boolean documentEmpty = true; private PdfPTable layoutTable; private ColumnText text; private int colIdx; public StylableDocument( OutputStream out, IPdfWriterConfiguration configuration, StyleEngineForIText styleEngine ) throws DocumentException { super( out, configuration ); this.styleEngine = styleEngine; } public StylableDocument( OutputStream out, IPdfAWriterConfiguration configuration, StyleEngineForIText styleEngine ) throws DocumentException { super( out, configuration ); this.styleEngine = styleEngine; } // // IStylableFactory implementation // public StylableAnchor createAnchor( IStylableContainer parent ) { return new StylableAnchor( this, parent ); } public StylableChunk createChunk( IStylableContainer parent, String textContent, FontGroup fontGroup ) { return new StylableChunk( this, parent, textContent, fontGroup ); } public StylableDocumentSection createDocumentSection( IStylableContainer parent, boolean inHeaderFooter ) { return new StylableDocumentSection( this, parent, inHeaderFooter ); } public StylableHeaderFooter createHeaderFooter( boolean header ) { return new StylableHeaderFooter( this, header ); } public StylableHeading createHeading( IStylableContainer parent, List<Integer> headingNumbering ) { return new StylableHeading( this, parent, headingNumbering ); } public StylableImage createImage( IStylableContainer parent, Image image, Float x, Float y, Float width, Float height ) { return new StylableImage( this, parent, image, x, y, width, height ); } public StylableList createList( IStylableContainer parent, int listLevel ) { return new StylableList( this, parent, listLevel ); } public StylableListItem createListItem( IStylableContainer parent ) { return new StylableListItem( this, parent ); } public StylableParagraph createParagraph( IStylableContainer parent ) { return new StylableParagraph( this, parent ); } public StylablePhrase createPhrase( IStylableContainer parent ) { return new StylablePhrase( this, parent ); } public StylableTab createTab( IStylableContainer parent, boolean inTableOfContent ) { return new StylableTab( this, parent, inTableOfContent ); } public StylableTable createTable( IStylableContainer parent, int numColumns ) { return new StylableTable( this, parent, numColumns ); } public StylableTableCell createTableCell( IStylableContainer parent ) { return new StylableTableCell( this, parent ); } // // master page handling // @Override public void setActiveMasterPage( IMasterPage masterPage ) { // flush pending content flushTable(); // activate master page in three steps Style style = getStyleMasterPage( (StylableMasterPage) masterPage ); if ( style != null ) { // step 1 - apply styles like page dimensions and orientation this.applyStyles( style ); } // step 2 - set header/footer if any, it needs page dimensions from step 1 super.setActiveMasterPage( masterPage ); if ( activeMasterPage != null ) { // set a flag used by addElement/pageBreak masterPageJustChanged = true; } activeMasterPage = (StylableMasterPage) masterPage; // step 3 - initialize column layout, it needs page dimensions which may be lowered by header/footer in step 2 layoutTable = StylableDocumentSection.createLayoutTable( getPageWidth(), getAdjustedPageHeight(), style ); text = StylableDocumentSection.createColumnText(); setColIdx( 0 ); } private Style setNextActiveMasterPageIfNecessary() { // called on page break // return new page style if changed if ( activeMasterPage != null ) { String nextMasterPageStyleName = activeMasterPage.getNextStyleName(); if ( nextMasterPageStyleName != null && nextMasterPageStyleName.length() > 0 ) { StylableMasterPage nextMasterPage = getMasterPage( nextMasterPageStyleName ); if ( nextMasterPage != null ) { // activate next master page Style style = getStyleMasterPage( nextMasterPage ); if ( style != null ) { // step 1 - apply styles like page dimensions and orientation this.applyStyles( style ); } // step 2 - set header/footer if any, it needs page dimensions from step 1 super.setActiveMasterPage( nextMasterPage ); // activeMasterPage = nextMasterPage; return style; } } } return null; } @Override public StylableMasterPage getActiveMasterPage() { return activeMasterPage; } public Style getStyleMasterPage( StylableMasterPage masterPage ) { Style style = styleEngine.getStyle( OdfStyleFamily.List.getName(), masterPage.getPageLayoutName(), null ); return style; } @Override public StylableMasterPage getMasterPage( String masterPageName ) { return (StylableMasterPage) super.getMasterPage( masterPageName ); } @Override public StylableMasterPage getDefaultMasterPage() { return (StylableMasterPage) super.getDefaultMasterPage(); } // // IStylableContainer implementation // public void applyStyles( Style style ) { this.lastStyleApplied = style; StylePageLayoutProperties pageLayoutProperties = style.getPageLayoutProperties(); if ( pageLayoutProperties != null ) { // width/height Float width = pageLayoutProperties.getWidth(); Float height = pageLayoutProperties.getHeight(); if ( width != null && height != null ) { Rectangle pageSize = new Rectangle( width, height ); super.setPageSize( pageSize ); } // margin if ( pageLayoutProperties.getMarginTop() != null ) { originMarginTop = pageLayoutProperties.getMarginTop(); } if ( pageLayoutProperties.getMarginBottom() != null ) { originMarginBottom = pageLayoutProperties.getMarginBottom(); } if ( pageLayoutProperties.getMarginLeft() != null ) { originMarginLeft = pageLayoutProperties.getMarginLeft(); } if ( pageLayoutProperties.getMarginRight() != null ) { originMarginRight = pageLayoutProperties.getMarginRight(); } super.setMargins( originMarginLeft, originMarginRight, originMarginTop, originMarginBottom ); // orientation PageOrientation orientation = pageLayoutProperties.getOrientation(); if ( orientation != null ) { super.setOrientation( orientation ); } } } public Style getLastStyleApplied() { return lastStyleApplied; } public IStylableContainer getParent() { return null; } public Element getElement() { return null; } // // this amazing and awesome algorithm // which lay out content in columns on page // was written by Leszek Piotrowicz <leszekp@safe-mail.net> // @Override public void addElement( Element element ) { if ( !super.isOpen() ) { super.open(); } if ( masterPageJustChanged ) { // master page was changed but there was no explicit page break pageBreak(); } text.addElement( element ); StylableDocumentSection.getCell( layoutTable, colIdx ).getColumn().addElement( element ); simulateText(); documentEmpty = false; } public void columnBreak() { if ( colIdx + 1 < layoutTable.getNumberOfColumns() ) { setColIdx( colIdx + 1 ); simulateText(); } else { pageBreak(); } } public void pageBreak() { if ( documentEmpty ) { // no element was added - ignore page break } else if ( masterPageJustChanged ) { // we are just after master page change // move to a new page but do not initialize column layout // because it is already done masterPageJustChanged = false; super.newPage(); } else { // flush pending content flushTable(); // check if master page change necessary Style nextStyle = setNextActiveMasterPageIfNecessary(); // document new page super.newPage(); // initialize column layout for new page if ( nextStyle == null ) { // ordinary page break layoutTable = StylableDocumentSection.cloneAndClearTable( layoutTable, false ); } else { // page break with new master page activation // style changed so recreate table layoutTable = StylableDocumentSection.createLayoutTable( getPageWidth(), getAdjustedPageHeight(), nextStyle ); } setColIdx( 0 ); simulateText(); } } @Override public boolean newPage() { throw new ODFConverterException( "internal error - do not call newPage directly" ); } @Override public void close() { flushTable(); super.close(); } public float getWidthLimit() { PdfPCell cell = StylableDocumentSection.getCell( layoutTable, colIdx ); return cell.getRight() - cell.getPaddingRight() - cell.getLeft() - cell.getPaddingLeft(); } public float getHeightLimit() { // yLine is negative return StylableDocumentSection.getCell( layoutTable, colIdx ).getFixedHeight() + text.getYLine(); } public float getPageWidth() { return right() - left(); } private float getAdjustedPageHeight() { // subtract small value from height, otherwise table breaks to new page return top() - bottom() - 0.001f; } private void setColIdx( int idx ) { colIdx = idx; PdfPCell cell = StylableDocumentSection.getCell( layoutTable, colIdx ); text.setSimpleColumn( cell.getLeft() + cell.getPaddingLeft(), -getAdjustedPageHeight(), cell.getRight() - cell.getPaddingRight(), 0.0f ); cell.setColumn( ColumnText.duplicate( text ) ); } private void simulateText() { int res = 0; try { res = text.go( true ); } catch ( DocumentException e ) { throw new ODFConverterException( e ); } if ( ColumnText.hasMoreText( res ) ) { // text does not fit into current column // split it to a new column columnBreak(); } } private void flushTable() { if ( layoutTable != null ) { // force calculate height because it may be zero // and nothing will be flushed layoutTable.calculateHeights(); try { super.add( layoutTable ); } catch ( DocumentException e ) { throw new ODFConverterException( e ); } } } }