Java tutorial
/** * 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; } }