fr.opensagres.odfdom.converter.pdf.internal.stylable.StylableParagraph.java Source code

Java tutorial

Introduction

Here is the source code for fr.opensagres.odfdom.converter.pdf.internal.stylable.StylableParagraph.java

Source

/**
 * 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.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import com.lowagie.text.Chunk;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.BaseFont;

import fr.opensagres.odfdom.converter.core.utils.ODFUtils;
import fr.opensagres.odfdom.converter.pdf.internal.styles.Style;
import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleBreak;
import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleLineHeight;
import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleParagraphProperties;
import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleTextProperties;
import fr.opensagres.xdocreport.itext.extension.ExtendedParagraph;

/**
 * fixes for paragraph pdf conversion by Leszek Piotrowicz <leszekp@safe-mail.net>
 */
public class StylableParagraph extends ExtendedParagraph implements IStylableContainer {
    private static final long serialVersionUID = 664309269352903329L;

    public static final float DEFAULT_LINE_HEIGHT = 1.0f;

    private final StylableDocument ownerDocument;

    private IStylableContainer parent;

    private Style lastStyleApplied = null;

    private boolean elementPostProcessed = false;

    public StylableParagraph(StylableDocument ownerDocument, IStylableContainer parent) {
        super();
        this.ownerDocument = ownerDocument;
        this.parent = parent;
        super.setMultipliedLeading(DEFAULT_LINE_HEIGHT);
    }

    public StylableParagraph(StylableDocument ownerDocument, Paragraph title, IStylableContainer parent) {
        super(title);
        this.ownerDocument = ownerDocument;
        this.parent = parent;
        super.setMultipliedLeading(DEFAULT_LINE_HEIGHT);
    }

    public void applyStyles(Style style) {
        this.lastStyleApplied = style;

        StyleTextProperties textProperties = style.getTextProperties();
        if (textProperties != null) {
            // font
            Font font = textProperties.getFont();
            if (font != null) {
                super.setFont(font);
            }
        }

        StyleParagraphProperties paragraphProperties = style.getParagraphProperties();
        if (paragraphProperties != null) {
            // break-before
            StyleBreak breakBefore = paragraphProperties.getBreakBefore();
            if (breakBefore != null) {
                handleBreak(breakBefore);
            }

            // alignment
            int alignment = paragraphProperties.getAlignment();
            if (alignment != Element.ALIGN_UNDEFINED) {
                super.setAlignment(alignment);
            }

            // first line indentation
            Boolean autoTextIndent = paragraphProperties.getAutoTextIndent();
            if (Boolean.TRUE.equals(autoTextIndent)) {
                float fontSize = font != null ? font.getCalculatedSize() : Font.DEFAULTSIZE;
                super.setFirstLineIndent(1.3f * fontSize);
            } else {
                Float textIndent = paragraphProperties.getTextIndent();
                if (textIndent != null) {
                    // text indent can be negative. 
                    // See https://code.google.com/p/xdocreport/issues/detail?id=366 
                    super.setFirstLineIndent(textIndent);
                }
            }

            // line height
            StyleLineHeight lineHeight = paragraphProperties.getLineHeight();
            if (lineHeight != null && lineHeight.getLineHeight() != null) {
                if (lineHeight.isLineHeightProportional()) {
                    super.setMultipliedLeading(lineHeight.getLineHeight());
                } else {
                    super.setLeading(lineHeight.getLineHeight());
                }
            }

            // keep together on the same page
            Boolean keepTogether = paragraphProperties.getKeepTogether();
            if (keepTogether != null) {
                super.setKeepTogether(keepTogether);
            }
        }
    }

    private void handleBreak(StyleBreak styleBreak) {
        if (styleBreak.isColumnBreak() || styleBreak.isPageBreak()) {
            IBreakHandlingContainer b = StylableDocumentSection.getIBreakHandlingContainer(parent);
            if (b != null) {
                if (styleBreak.isColumnBreak()) {
                    b.columnBreak();
                } else if (styleBreak.isPageBreak()) {
                    b.pageBreak();
                }
            }
        }
    }

    public Style getLastStyleApplied() {
        return lastStyleApplied;
    }

    public IStylableContainer getParent() {
        return parent;
    }

    public StylableDocument getOwnerDocument() {
        return ownerDocument;
    }

    public static Chunk createAdjustedChunk(String content, Font font, float lineHeight,
            boolean lineHeightProportional) {
        // adjust chunk attributes like text rise
        // use StylableParagraph mechanism
        StylableParagraph p = new StylableParagraph(null, null);
        p.setFont(font);
        if (lineHeightProportional) {
            p.setMultipliedLeading(lineHeight);
        } else {
            p.setLeading(lineHeight);
        }
        p.addElement(new Chunk(content, font));
        p.getElement(); // post-processing here
        return (Chunk) p.getChunks().get(0);
    }

    @Override
    public Element getElement() {
        if (!elementPostProcessed) {
            elementPostProcessed = true;
            postProcessEmptyParagraph();
            postProcessBookmarks();
            postProcessLineHeightAndBaseline();
        }
        return super.getElement();
    }

    @SuppressWarnings("unchecked")
    private void postProcessEmptyParagraph() {
        // add space if this paragraph is empty
        // otherwise its height will be zero
        boolean empty = true;
        ArrayList<Chunk> chunks = getChunks();
        for (Chunk chunk : chunks) {
            if (chunk.getImage() == null && chunk.getContent() != null && chunk.getContent().length() > 0) {
                empty = false;
                break;
            }
        }
        if (empty) {
            super.add(new Chunk(ODFUtils.TAB_STR));
        }
    }

    @SuppressWarnings("unchecked")
    private void postProcessBookmarks() {
        // add space if last chunk is a bookmark
        // otherwise the bookmark will disappear from pdf
        ArrayList<Chunk> chunks = getChunks();
        if (chunks.size() > 0) {
            Chunk lastChunk = chunks.get(chunks.size() - 1);
            String localDestination = null;
            if (lastChunk.getAttributes() != null) {
                localDestination = (String) lastChunk.getAttributes().get(Chunk.LOCALDESTINATION);
            }
            if (localDestination != null) {
                super.add(new Chunk(ODFUtils.NON_BREAKING_SPACE_STR));
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void postProcessLineHeightAndBaseline() {
        // adjust line height and baseline
        Font font = getMostOftenUsedFont();
        if (font == null || font.getBaseFont() == null) {
            font = this.font;
        }
        if (font != null && font.getBaseFont() != null) {
            // iText and open office computes proportional line height differently
            // [iText] line height = coefficient * font size
            // [open office] line height = coefficient * (font ascender + font descender + font extra margin)
            // we have to increase paragraph line height to generate pdf similar to open office document
            // this algorithm may be inaccurate if fonts with different multipliers are used in this paragraph
            float size = font.getSize();
            float ascender = font.getBaseFont().getFontDescriptor(BaseFont.AWT_ASCENT, size);
            float descender = -font.getBaseFont().getFontDescriptor(BaseFont.AWT_DESCENT, size); // negative value
            float margin = font.getBaseFont().getFontDescriptor(BaseFont.AWT_LEADING, size);
            float multiplier = (ascender + descender + margin) / size;
            if (multipliedLeading > 0.0f) {
                setMultipliedLeading(getMultipliedLeading() * multiplier);
            }

            // iText seems to output text with baseline lower than open office
            // we raise all paragraph text by some amount
            // again this may be inaccurate if fonts with different size are used in this paragraph
            float itextdescender = -font.getBaseFont().getFontDescriptor(BaseFont.DESCENT, size); // negative
            float textRise = itextdescender + getTotalLeading() - font.getSize() * multiplier;
            ArrayList<Chunk> chunks = getChunks();
            for (Chunk chunk : chunks) {
                Font f = chunk.getFont();
                if (f != null) {
                    // have to raise underline and strikethru as well
                    float s = f.getSize();
                    if (f.isUnderlined()) {
                        f.setStyle(f.getStyle() & ~Font.UNDERLINE);
                        chunk.setUnderline(s * 1 / 17, s * -1 / 7 + textRise);
                    }
                    if (f.isStrikethru()) {
                        f.setStyle(f.getStyle() & ~Font.STRIKETHRU);
                        chunk.setUnderline(s * 1 / 17, s * 1 / 4 + textRise);
                    }
                }
                chunk.setTextRise(chunk.getTextRise() + textRise);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Font getMostOftenUsedFont() {
        // determine font most often used in this paragraph
        // font with the highest count of non-whitespace characters
        // is considered to be most often used
        Map<String, Font> fontMap = new LinkedHashMap<String, Font>();
        Map<String, Integer> countMap = new LinkedHashMap<String, Integer>();
        Font mostUsedFont = null;
        int mostUsedCount = -1;
        ArrayList<Chunk> chunks = getChunks();
        for (Chunk chunk : chunks) {
            Font font = chunk.getFont();
            int count = 0;
            String text = chunk.getContent();
            if (text != null) {
                // count non-whitespace characters in a chunk
                for (int i = 0; i < text.length(); i++) {
                    char ch = text.charAt(i);
                    if (!Character.isWhitespace(ch)) {
                        count++;
                    }
                }
            }
            if (font != null) {
                // update font and its count
                String fontKey = font.getFamilyname() + "_" + (int) font.getSize();
                Font fontTmp = fontMap.get(fontKey);
                if (fontTmp == null) {
                    fontMap.put(fontKey, font);
                }
                Integer countTmp = countMap.get(fontKey);
                int totalCount = countTmp == null ? count : countTmp + count;
                countMap.put(fontKey, totalCount);
                // update most used font
                if (totalCount > mostUsedCount) {
                    mostUsedCount = totalCount;
                    mostUsedFont = font;
                }
            }
        }
        return mostUsedFont;
    }
}