/* * Copyright 1999-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id: Block.java,v 1.14 2004/04/02 13:50:52 cbowditch Exp $ */ package org.apache.fop.fo.flow; // Java import java.util.List; // XML import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXParseException; // FOP import org.apache.fop.datatypes.ColorType; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.CharIterator; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.PropertySets; import org.apache.fop.fo.RecursiveCharIterator; import org.apache.fop.fo.properties.CommonAccessibility; import org.apache.fop.fo.properties.CommonAural; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonFont; import org.apache.fop.fo.properties.CommonHyphenation; import org.apache.fop.fo.properties.CommonMarginBlock; import org.apache.fop.fo.properties.CommonRelativePosition; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.SpaceProperty; import org.apache.fop.layoutmgr.BlockLayoutManager; import org.apache.fop.fo.Constants; import org.apache.fop.util.CharUtilities; /* Modified by Mark Lillywhite mark-fop@inomial.com. The changes here are based on memory profiling and do not change functionality. Essentially, the Block object had a pointer to a BlockArea object that it created. The BlockArea was not referenced after the Block was finished except to determine the size of the BlockArea, however a reference to the BlockArea was maintained and this caused a lot of GC problems, and was a major reason for FOP memory leaks. So, the reference to BlockArea was made local, the required information is now stored (instead of a reference to the complex BlockArea object) and it appears that there are a lot of changes in this file, in fact there are only a few sematic changes; mostly I just got rid of "this." from blockArea since BlockArea is now local. */ /** * Class modelling the fo:block object. */ public class Block extends FObjMixed { // used for FO validation private boolean blockOrInlineItemFound = false; private boolean initialPropertySetFound = false; // The value of properties relevant for fo:block. private CommonAccessibility commonAccessibility; private CommonAural commonAural; private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonFont commonFont; private CommonHyphenation commonHyphenation; private CommonMarginBlock commonMarginBlock; private CommonRelativePosition commonRelativePosition; private int breakAfter; private int breakBefore; private ColorType color; // private ToBeImplementedProperty textDepth; // private ToBeImplementedProperty textAltitude; // private ToBeImplementedProperty hyphenationKeep; // private ToBeImplementedProperty hyphenationLadderCount; private String id; // private ToBeImplementedProperty intrusionDisplace; private KeepProperty keepTogether; private KeepProperty keepWithNext; private KeepProperty keepWithPrevious; // private ToBeImplementedProperty lastLineEndIndent; private int linefeedTreatment; private SpaceProperty lineHeight; // private ToBeImplementedProperty lineHeightShiftAdjustment; // private ToBeImplementedProperty lineStackingStrategy; private Numeric orphans; private int whiteSpaceTreatment; private int span; private int textAlign; private int textAlignLast; private Length textIndent; // private ToBeImplementedProperty visibility; private int whiteSpaceCollapse; private Numeric widows; private int wrapOption; // End of property values private int align; private int alignLast; private int _lineHeight; private int startIndent; private int endIndent; private int spaceBefore; private int spaceAfter; private int _textIndent; private int _keepWithNext; private ColorType backgroundColor; private int blockWidows; private int blockOrphans; private int wsTreatment; //ENUMERATION private int lfTreatment; //ENUMERATION private boolean bWScollapse; //true if white-space-collapse=true // this may be helpful on other FOs too private boolean anythingLaidOut = false; /** * Index of first inline-type FO seen in a sequence. * Used during FO tree building to do white-space handling. */ private FONode firstInlineChild = null; /** * @param parent FONode that is the parent of this object * */ public Block(FONode parent) { super(parent); } /** * @see org.apache.fop.fo.FObj#bind(PropertyList) */ public void bind(PropertyList pList) throws SAXParseException { commonAccessibility = pList.getAccessibilityProps(); commonAural = pList.getAuralProps(); commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); commonFont = pList.getFontProps(); commonHyphenation = pList.getHyphenationProps(); commonMarginBlock = pList.getMarginBlockProps(); commonRelativePosition = pList.getRelativePositionProps(); breakAfter = pList.get(PR_BREAK_AFTER).getEnum(); breakBefore = pList.get(PR_BREAK_BEFORE).getEnum(); color = pList.get(PR_COLOR).getColorType(); // textDepth = pList.get(PR_TEXT_DEPTH); // textAltitude = pList.get(PR_TEXT_ALTITUDE); // hyphenationKeep = pList.get(PR_HYPHENATION_KEEP); // hyphenationLadderCount = pList.get(PR_HYPHENATION_LADDER_COUNT); id = pList.get(PR_ID).getString(); // intrusionDisplace = pList.get(PR_INTRUSION_DISPLACE); keepTogether = pList.get(PR_KEEP_TOGETHER).getKeep(); keepWithNext = pList.get(PR_KEEP_WITH_NEXT).getKeep(); keepWithPrevious = pList.get(PR_KEEP_WITH_PREVIOUS).getKeep(); // lastLineEndIndent = pList.get(PR_LAST_LINE_END_INDENT); linefeedTreatment = pList.get(PR_LINEFEED_TREATMENT).getEnum(); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); // lineHeightShiftAdjustment = pList.get(PR_LINE_HEIGHT_SHIFT_ADJUSTMENT); // lineStackingStrategy = pList.get(PR_LINE_STACKING_STRATEGY); orphans = pList.get(PR_ORPHANS).getNumeric(); whiteSpaceTreatment = pList.get(PR_WHITE_SPACE_TREATMENT).getEnum(); span = pList.get(PR_SPAN).getEnum(); textAlign = pList.get(PR_TEXT_ALIGN).getEnum(); textAlignLast = pList.get(PR_TEXT_ALIGN_LAST).getEnum(); textIndent = pList.get(PR_TEXT_INDENT).getLength(); // visibility = pList.get(PR_VISIBILITY); whiteSpaceCollapse = pList.get(PR_WHITE_SPACE_COLLAPSE).getEnum(); widows = pList.get(PR_WIDOWS).getNumeric(); wrapOption = pList.get(PR_WRAP_OPTION).getEnum(); } /** * @see org.apache.fop.fo.FONode#startOfNode */ protected void startOfNode() throws SAXParseException { checkId(id); getFOEventHandler().startBlock(this); } /** * @see org.apache.fop.fo.FONode#endOfNode */ protected void endOfNode() throws SAXParseException { handleWhiteSpace(); getFOEventHandler().endBlock(this); } /** * Return the Common Margin Properties-Block. */ public CommonMarginBlock getCommonMarginBlock() { return commonMarginBlock; } /** * Return the Common Border, Padding, and Background Properties. */ public CommonBorderPaddingBackground getCommonBorderPaddingBackground() { return commonBorderPaddingBackground; } /** * Return the Common Font Properties. */ public CommonFont getCommonFont() { return commonFont; } /** * Return the Common Hyphenation Properties. */ public CommonHyphenation getCommonHyphenation() { return commonHyphenation; } /** * Return the "break-after" property. */ public int getBreakAfter() { return breakAfter; } /** * Return the "break-before" property. */ public int getBreakBefore() { return breakBefore; } /** * Return the "color" property. */ public ColorType getColor() { return color; } /** * Return the "id" property. */ public String getId() { return id; } /** * Return the "line-height" property. */ public SpaceProperty getLineHeight() { return lineHeight; } /** * Return the "span" property. */ public int getSpan() { return this.span; } /** * Return the "text-align" property. */ public int getTextAlign() { return textAlign; } /** * Return the "text-align-last" property. */ public int getTextAlignLast() { return textAlignLast; } /** * Return the "text-indent" property. */ public Length getTextIndent() { return textIndent; } /** * @see org.apache.fop.fo.FObj#addProperties */ protected void addProperties(Attributes attlist) throws SAXParseException { super.addProperties(attlist); this.span = getPropEnum(PR_SPAN); this.wsTreatment = getPropEnum(PR_WHITE_SPACE_TREATMENT); this.bWScollapse = (getPropEnum(PR_WHITE_SPACE_COLLAPSE) == Constants.TRUE); this.lfTreatment = getPropEnum(PR_LINEFEED_TREATMENT); this.align = getPropEnum(PR_TEXT_ALIGN); this.alignLast = getPropEnum(PR_TEXT_ALIGN_LAST); this.breakAfter = getPropEnum(PR_BREAK_AFTER); this._lineHeight = getPropLength(PR_LINE_HEIGHT); this.startIndent = getPropLength(PR_START_INDENT); this.endIndent = getPropLength(PR_END_INDENT); this.spaceBefore = getPropLength(PR_SPACE_BEFORE | CP_OPTIMUM); this.spaceAfter = getPropLength(PR_SPACE_AFTER | CP_OPTIMUM); this._textIndent = getPropLength(PR_TEXT_INDENT); this._keepWithNext = getPropEnum(PR_KEEP_WITH_NEXT); this.blockWidows = this.propertyList.get(PR_WIDOWS).getNumber().intValue(); this.blockOrphans = this.propertyList.get(PR_ORPHANS).getNumber().intValue(); getFOEventHandler().startBlock(this); } /** * @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String) * XSL Content Model: marker* initial-property-set? (#PCDATA|%inline;|%block;)* * Additionally: "An fo:bidi-override that is a descendant of an fo:leader * or of the fo:inline child of an fo:footnote may not have block-level * children, unless it has a nearer ancestor that is an * fo:inline-container." */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws SAXParseException { if (nsURI == FO_URI && localName.equals("marker")) { if (blockOrInlineItemFound || initialPropertySetFound) { nodesOutOfOrderError(loc, "fo:marker", "initial-property-set? (#PCDATA|%inline;|%block;)"); } } else if (nsURI == FO_URI && localName.equals("initial-property-set")) { if (initialPropertySetFound) { tooManyNodesError(loc, "fo:initial-property-set"); } else if (blockOrInlineItemFound) { nodesOutOfOrderError(loc, "fo:initial-property-set", "(#PCDATA|%inline;|%block;)"); } else { initialPropertySetFound = true; } } else if (isBlockOrInlineItem(nsURI, localName)) { blockOrInlineItemFound = true; } else { invalidChildError(loc, nsURI, localName); } } /** * @see org.apache.fop.fo.FONode#addChildNode(FONode) */ public void addChildNode(FONode child) throws SAXParseException { // Handle whitespace based on values of properties // Handle a sequence of inline-producing child nodes in // one pass if (child instanceof FOText || PropertySets.generatesInlineAreas(child.getNameId())) { if (firstInlineChild == null) { firstInlineChild = child; } // lastInlineChild = childNodes.size(); } else { // Handle whitespace in preceeding inline areas if any handleWhiteSpace(); } super.addChildNode(child); } private void handleWhiteSpace() { //getLogger().debug("fo:block: handleWhiteSpace"); if (firstInlineChild != null) { boolean bInWS = false; boolean bPrevWasLF = false; /* bSeenNonWSYet is an indicator used for trimming all leading whitespace for the first inline child of the block */ boolean bSeenNonWSYet = false; RecursiveCharIterator charIter = new RecursiveCharIterator(this, firstInlineChild); LFchecker lfCheck = new LFchecker(charIter); while (charIter.hasNext()) { char currentChar = charIter.nextChar(); switch (CharUtilities.classOf(currentChar)) { case CharUtilities.XMLWHITESPACE: /* Some kind of whitespace character, except linefeed. */ boolean bIgnore = false; switch (wsTreatment) { case Constants.IGNORE: bIgnore = true; break; case Constants.IGNORE_IF_BEFORE_LINEFEED: bIgnore = lfCheck.nextIsLF(); break; case Constants.IGNORE_IF_SURROUNDING_LINEFEED: bIgnore = (bPrevWasLF || lfCheck.nextIsLF()); break; case Constants.IGNORE_IF_AFTER_LINEFEED: bIgnore = bPrevWasLF; break; case Constants.PRESERVE: // nothing to do now, replacement takes place later break; } // Handle ignore and replacement if (bIgnore) { charIter.remove(); } else if (bWScollapse) { if (bInWS || (lfTreatment == Constants.PRESERVE && (bPrevWasLF || lfCheck.nextIsLF()))) { charIter.remove(); } else { // this is to retain a single space between words bInWS = true; // remove the space if no word in block // encountered yet if (!bSeenNonWSYet) { charIter.remove(); } else { if (currentChar != '\u0020') { charIter.replaceChar('\u0020'); } } } } else { // !bWScollapse if (currentChar != '\u0020') { charIter.replaceChar('\u0020'); } } break; case CharUtilities.LINEFEED: /* A linefeed */ lfCheck.reset(); bPrevWasLF = true; // for following whitespace switch (lfTreatment) { case Constants.IGNORE: charIter.remove(); break; case Constants.TREAT_AS_SPACE: if (bInWS) { // only if bWScollapse=true charIter.remove(); } else { if (bWScollapse) { bInWS = true; // remove the linefeed if no word in block // encountered yet if (!bSeenNonWSYet) { charIter.remove(); } } charIter.replaceChar('\u0020'); } break; case Constants.TREAT_AS_ZERO_WIDTH_SPACE: charIter.replaceChar('\u200b'); // Fall through: this isn't XML whitespace case Constants.PRESERVE: bInWS = false; break; } break; case CharUtilities.EOT: // A "boundary" objects such as non-character inline // or nested block object was encountered. // If any whitespace run in progress, finish it. // FALL THROUGH case CharUtilities.UCWHITESPACE: // Non XML-whitespace case CharUtilities.NONWHITESPACE: /* Any other character */ bInWS = bPrevWasLF = false; bSeenNonWSYet = true; lfCheck.reset(); break; } } firstInlineChild = null; } } private static class LFchecker { private boolean bNextIsLF = false; private RecursiveCharIterator charIter; LFchecker(RecursiveCharIterator charIter) { this.charIter = charIter; } boolean nextIsLF() { if (bNextIsLF == false) { CharIterator lfIter = charIter.mark(); while (lfIter.hasNext()) { char c = lfIter.nextChar(); if (c == '\n') { bNextIsLF = true; break; } else if (CharUtilities.classOf(c) != CharUtilities.XMLWHITESPACE) { break; } } } return bNextIsLF; } void reset() { bNextIsLF = false; } } /** * @see org.apache.fop.fo.FONode#addLayoutManager(List) */ public void addLayoutManager(List list) { BlockLayoutManager blm = new BlockLayoutManager(this); list.add(blm); } /** * @see org.apache.fop.fo.FONode#getName() */ public String getName() { return "fo:block"; } /** * @see org.apache.fop.fo.FObj#getNameId() */ public int getNameId() { return FO_BLOCK; } }