All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.odftoolkit.odfdom.converter.pdf.internal.stylable.StylableParagraph Maven / Gradle / Ivy

/**
 * Copyright (C) 2011-2012 The XDocReport Team 
 *
 * 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 org.odftoolkit.odfdom.converter.pdf.internal.stylable;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import org.odftoolkit.odfdom.converter.core.utils.ODFUtils;
import org.odftoolkit.odfdom.converter.pdf.internal.styles.Style;
import org.odftoolkit.odfdom.converter.pdf.internal.styles.StyleBreak;
import org.odftoolkit.odfdom.converter.pdf.internal.styles.StyleLineHeight;
import org.odftoolkit.odfdom.converter.pdf.internal.styles.StyleParagraphProperties;
import org.odftoolkit.odfdom.converter.pdf.internal.styles.StyleTextProperties;

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.xdocreport.itext.extension.ExtendedParagraph;

/**
 * fixes for paragraph pdf conversion by Leszek Piotrowicz 
 */
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 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 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 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 fontMap = new LinkedHashMap();
        Map countMap = new LinkedHashMap();
        Font mostUsedFont = null;
        int mostUsedCount = -1;
        ArrayList 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy