Java tutorial
/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2006 - 2017 Hitachi Vantara.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.layout.process.text; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes; import org.pentaho.reporting.engine.classic.core.layout.model.PageGrid; import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphPoolBox; import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode; import org.pentaho.reporting.engine.classic.core.layout.model.RenderableComplexText; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData; import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility; import org.pentaho.reporting.engine.classic.core.layout.process.AbstractMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.IterateSimpleStructureProcessStep; import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisNodeContext; import org.pentaho.reporting.engine.classic.core.style.StyleSheet; import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys; import org.pentaho.reporting.engine.classic.core.style.TextWrap; import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility; import org.pentaho.reporting.libraries.base.util.ArgumentNullException; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.text.AttributedCharacterIterator; import java.util.ArrayList; public class ComplexTextMinorAxisLayoutStep extends IterateSimpleStructureProcessStep implements TextMinorAxisLayoutStep { private static final Log logger = LogFactory.getLog(ComplexTextMinorAxisLayoutStep.class); private final boolean strictCompatibility; private final OutputProcessorMetaData metaData; private final ResourceManager resourceManager; private MinorAxisNodeContext nodeContext; public ComplexTextMinorAxisLayoutStep(final OutputProcessorMetaData metaData, final ResourceManager resourceManager) { this.resourceManager = resourceManager; ArgumentNullException.validate("metaData", metaData); this.metaData = metaData; this.strictCompatibility = getMetaData().isFeatureSupported(OutputProcessorFeature.STRICT_COMPATIBILITY); } public void process(final ParagraphRenderBox box, final MinorAxisNodeContext nodeContext, final PageGrid pageGrid) { this.nodeContext = nodeContext; processParagraphChildsComplex(box); } public MinorAxisNodeContext getNodeContext() { return nodeContext; } public OutputProcessorMetaData getMetaData() { return metaData; } protected void processParagraphChildsComplex(final ParagraphRenderBox box) { // Clear the paragraph to throw away previously layouted nodes. This leaves the // paragraph's pool (where your original text is stored) untouched. box.clearLayout(); if (box.isComplexParagraph()) { final RenderBox lineBoxContainer = box.getLineboxContainer(); final StyleSheet layoutContext = box.getStyleSheet(); RenderNode paragraphContainer = lineBoxContainer.getFirstChild(); while (paragraphContainer != null) { if (paragraphContainer.getNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX) { throw new IllegalStateException("Expected ParagraphPoolBox elements."); } final ParagraphPoolBox paragraph = (ParagraphPoolBox) paragraphContainer; addGeneratedComplexTextLines(box, paragraph, layoutContext); paragraphContainer = paragraphContainer.getNext(); } } else { final ParagraphPoolBox lineBoxContainer = (ParagraphPoolBox) box.getEffectiveLineboxContainer(); final StyleSheet layoutContext = box.getStyleSheet(); addGeneratedComplexTextLines(box, lineBoxContainer, layoutContext); } } private FontRenderContext createFontRenderContext(final StyleSheet layoutContext) { final boolean antiAliasing = RenderUtility.isFontSmooth(layoutContext, getMetaData()); return new FontRenderContext(null, antiAliasing, true); } private void addGeneratedComplexTextLines(final ParagraphRenderBox box, final ParagraphPoolBox lineBoxContainer, final StyleSheet layoutContext) { updateNodeContextWidth(box); // Determine if anti-aliasing is required or not if (TextWrap.NONE.equals(box.getStyleSheet().getStyleProperty(TextStyleKeys.TEXT_WRAP))) { addCompleteLine(box, lineBoxContainer, layoutContext); return; } // Create a LineBreakMeasurer to break down that string into lines. final RichTextSpec richText = RichTextSpecProducer.compute(lineBoxContainer, metaData, resourceManager); final LineBreakIterator lineIterator = createLineBreakIterator(box, layoutContext, richText); ArrayList<RenderableComplexText> lines = new ArrayList<RenderableComplexText>(); ParagraphFontMetricsImpl metrics = new ParagraphFontMetricsImpl(); while (lineIterator.hasNext()) { final LineBreakIteratorState state = lineIterator.next(); final RenderableComplexText text = richText.create(lineBoxContainer, state.getStart(), state.getEnd()); text.setTextLayout(state.getTextLayout()); metrics.update(state.getTextLayout()); // and finally add the line to the paragraph lines.add(text); } final double height = metrics.getLineHeight(); for (RenderableComplexText text : lines) { final RenderBox line = generateLine(box, lineBoxContainer, text, height, metrics); box.addGeneratedChild(line); } } private LineBreakIterator createLineBreakIterator(final ParagraphRenderBox box, final StyleSheet layoutContext, final RichTextSpec richText) { final AttributedCharacterIterator ci = richText.createAttributedCharacterIterator(); final FontRenderContext fontRenderContext = createFontRenderContext(layoutContext); final boolean breakOnWordBoundary = strictCompatibility || layoutContext.getBooleanStyleProperty(TextStyleKeys.WORDBREAK); if (breakOnWordBoundary) { return new WordBreakingLineIterator(box, fontRenderContext, ci, richText.getText()); } else { return new LineBreakIterator(box, fontRenderContext, ci); } } private void addCompleteLine(final ParagraphRenderBox box, final ParagraphPoolBox lineBoxContainer, final StyleSheet layoutContext) { RichTextSpec richText = RichTextSpecProducer.compute(lineBoxContainer, metaData, resourceManager); final FontRenderContext fontRenderContext = createFontRenderContext(layoutContext); final TextLayout textLayout = new TextLayout(richText.createAttributedCharacterIterator(), fontRenderContext); double height = textLayout.getAscent() + textLayout.getDescent() + textLayout.getLeading(); final RenderableComplexText text = richText.create(lineBoxContainer); text.setTextLayout(textLayout); ParagraphFontMetricsImpl metrics = new ParagraphFontMetricsImpl(); metrics.update(textLayout); final RenderBox line = generateLine(box, lineBoxContainer, text, height, metrics); // and finally add the line to the paragraph getNodeContext().updateX2(line.getCachedX2()); box.addGeneratedChild(line); } private RenderBox generateLine(final ParagraphRenderBox paragraph, final ParagraphPoolBox lineBoxContainer, final RenderableComplexText text, final double height, final ParagraphFontMetricsImpl metrics) { // derive a new RenderableComplexText object representing the line, that holds on to the TextLayout class. TextLayout textLayout = text.getTextLayout(); // Store the height and width, so that the other parts of the layouter have access to the information // text.setCachedHeight(); text.setCachedHeight(Math.max(StrictGeomUtility.toInternalValue(height), lineBoxContainer.getLineHeight())); text.setCachedWidth(StrictGeomUtility.toInternalValue(textLayout.getAdvance())); text.setParagraphFontMetrics(metrics); MinorAxisNodeContext nodeContext = getNodeContext(); final long alignmentX = RenderUtility.computeHorizontalAlignment(paragraph.getTextAlignment(), nodeContext.getContentAreaWidth(), StrictGeomUtility.toInternalValue(textLayout.getAdvance())); text.setCachedX(alignmentX + nodeContext.getX()); // Create a shallow copy of the paragraph-pool to act as a line container. final RenderBox line = (RenderBox) paragraph.getPool().deriveFrozen(false); line.addGeneratedChild(text); // Align the line inside the paragraph. (Adjust the cachedX position depending on whether the line is left, // centred or right aligned) line.setCachedX(alignmentX + nodeContext.getX()); line.setCachedWidth(nodeContext.getContentAreaWidth()); return line; } private void updateNodeContextWidth(final ParagraphRenderBox paragraph) { MinorAxisNodeContext nodeContext = getNodeContext(); final long lineEnd; final boolean overflowX = paragraph.getStaticBoxLayoutProperties().isOverflowX(); if (overflowX) { lineEnd = nodeContext.getX1() + AbstractMinorAxisLayoutStep.OVERFLOW_DUMMY_WIDTH; } else { lineEnd = nodeContext.getX2(); } long firstLineIndent = 0; // todo long lineStart = Math.min(lineEnd, nodeContext.getX1() + firstLineIndent); if (lineEnd - lineStart <= 0) { final long minimumChunkWidth = paragraph.getPool().getMinimumChunkWidth(); nodeContext.updateX2(lineStart + minimumChunkWidth); logger.warn("Auto-Corrected zero-width first-line on paragraph - " + paragraph.getName()); } else { if (overflowX == false) { nodeContext.updateX2(lineEnd); } } } }