1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* $Id: BlockLayoutManager.java 1835810 2018-07-13 10:29:57Z ssteiner $ */ 19 20 package org.apache.fop.layoutmgr; 21 22 import java.util.LinkedList; 23 import java.util.List; 24 import java.util.ListIterator; 25 import java.util.Stack; 26 27 import org.apache.commons.logging.Log; 28 import org.apache.commons.logging.LogFactory; 29 30 import org.apache.fop.area.Area; 31 import org.apache.fop.area.Block; 32 import org.apache.fop.area.LineArea; 33 import org.apache.fop.datatypes.Length; 34 import org.apache.fop.fo.FONode; 35 import org.apache.fop.fo.properties.CommonBorderPaddingBackground; 36 import org.apache.fop.fo.properties.KeepProperty; 37 import org.apache.fop.fonts.Font; 38 import org.apache.fop.fonts.FontInfo; 39 import org.apache.fop.fonts.FontTriplet; 40 import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager; 41 import org.apache.fop.layoutmgr.inline.LineLayoutManager; 42 import org.apache.fop.traits.MinOptMax; 43 import org.apache.fop.traits.SpaceVal; 44 45 /** 46 * LayoutManager for a block FO. 47 */ 48 public class BlockLayoutManager extends SpacedBorderedPaddedBlockLayoutManager 49 implements BreakOpportunity { 50 51 /** logging instance */ 52 private static Log log = LogFactory.getLog(BlockLayoutManager.class); 53 54 private Block curBlockArea; 55 56 /** Iterator over the child layout managers. */ 57 protected ListIterator<LayoutManager> proxyLMiter; 58 59 private int lead = 12000; 60 private Length lineHeight; 61 private int follow = 2000; 62 //private int middleShift = 0; 63 64 /** 65 * Creates a new BlockLayoutManager. 66 * @param inBlock the block FO object to create the layout manager for. 67 */ BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock)68 public BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock) { 69 super(inBlock); 70 proxyLMiter = new ProxyLMiter(); 71 } 72 73 /** {@inheritDoc} */ 74 @Override initialize()75 public void initialize() { 76 super.initialize(); 77 org.apache.fop.fo.flow.Block fo = getBlockFO(); 78 FontInfo fi = fo.getFOEventHandler().getFontInfo(); 79 FontTriplet[] fontkeys = fo.getCommonFont().getFontState(fi); 80 Font initFont = fi.getFontInstance(fontkeys[0], 81 getBlockFO().getCommonFont().fontSize.getValue(this)); 82 lead = initFont.getAscender(); 83 follow = -initFont.getDescender(); 84 //middleShift = -fs.getXHeight() / 2; 85 lineHeight = fo.getLineHeight().getOptimum(this).getLength(); 86 startIndent = fo.getCommonMarginBlock().startIndent.getValue(this); 87 endIndent = fo.getCommonMarginBlock().endIndent.getValue(this); 88 foSpaceBefore = new SpaceVal(fo.getCommonMarginBlock().spaceBefore, this).getSpace(); 89 foSpaceAfter = new SpaceVal(fo.getCommonMarginBlock().spaceAfter, this).getSpace(); 90 // use optimum space values 91 adjustedSpaceBefore = fo.getCommonMarginBlock().spaceBefore.getSpace() 92 .getOptimum(this).getLength().getValue(this); 93 adjustedSpaceAfter = fo.getCommonMarginBlock().spaceAfter.getSpace() 94 .getOptimum(this).getLength().getValue(this); 95 } 96 97 @Override getCommonBorderPaddingBackground()98 protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { 99 return getBlockFO().getCommonBorderPaddingBackground(); 100 } 101 102 /** {@inheritDoc} */ 103 @Override getNextKnuthElements(LayoutContext context, int alignment)104 public List getNextKnuthElements(LayoutContext context, int alignment) { 105 return getNextKnuthElements(context, alignment, null, null, null); 106 } 107 108 /** {@inheritDoc} */ 109 @Override getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM)110 public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, 111 Position restartPosition, LayoutManager restartAtLM) { 112 resetSpaces(); 113 return super.getNextKnuthElements( 114 context, alignment, lmStack, restartPosition, restartAtLM); 115 } 116 117 /** 118 * Overridden to take into account that the childLM may be the block's 119 * {@link LineLayoutManager}. 120 * {@inheritDoc} 121 */ 122 @Override getNextChildElements(LayoutManager childLM, LayoutContext context, LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM)123 protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context, 124 LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, 125 LayoutManager restartAtLM) { 126 127 childLC.copyPendingMarksFrom(context); 128 129 if (childLM instanceof LineLayoutManager) { 130 childLC.setRefIPD(getContentAreaIPD()); 131 } else { 132 // nop; will have been properly set by makeChildLayoutContext() 133 } 134 135 if (childLM == this.childLMs.get(0)) { 136 childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); 137 //Handled already by the parent (break collapsing, see above) 138 } 139 140 if (lmStack == null) { 141 return childLM.getNextKnuthElements(childLC, alignment); 142 } else { 143 if (childLM instanceof LineLayoutManager) { 144 assert (restartPosition instanceof LeafPosition); 145 return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment, 146 (LeafPosition) restartPosition); 147 } else { 148 return childLM.getNextKnuthElements(childLC, alignment, 149 lmStack, restartPosition, restartAtLM); 150 } 151 } 152 } 153 resetSpaces()154 private void resetSpaces() { 155 this.discardBorderBefore = false; 156 this.discardBorderAfter = false; 157 this.discardPaddingBefore = false; 158 this.discardPaddingAfter = false; 159 this.effSpaceBefore = null; 160 this.effSpaceAfter = null; 161 } 162 163 /** 164 * Proxy iterator for Block LM. 165 * This iterator creates and holds the complete list 166 * of child LMs. 167 * It uses fobjIter as its base iterator. 168 * Block LM's createNextChildLMs uses this iterator 169 * as its base iterator. 170 */ 171 protected class ProxyLMiter extends LMiter { 172 173 /** 174 * Constructs a proxy iterator for Block LM. 175 */ ProxyLMiter()176 public ProxyLMiter() { 177 super(BlockLayoutManager.this); 178 listLMs = new java.util.ArrayList<LayoutManager>(10); 179 } 180 181 /** 182 * @return true if there are more child lms 183 */ hasNext()184 public boolean hasNext() { 185 return (curPos < listLMs.size()) || createNextChildLMs(curPos); 186 } 187 188 /** 189 * @param pos ... 190 * @return true if new child lms were added 191 */ createNextChildLMs(int pos)192 protected boolean createNextChildLMs(int pos) { 193 List<LayoutManager> newLMs = createChildLMs(pos + 1 - listLMs.size()); 194 if (newLMs != null) { 195 listLMs.addAll(newLMs); 196 } 197 return pos < listLMs.size(); 198 } 199 } 200 201 /** {@inheritDoc} */ 202 @Override createNextChildLMs(int pos)203 public boolean createNextChildLMs(int pos) { 204 205 while (proxyLMiter.hasNext()) { 206 LayoutManager lm = proxyLMiter.next(); 207 if (lm instanceof InlineLevelLayoutManager) { 208 LineLayoutManager lineLM = createLineManager(lm); 209 addChildLM(lineLM); 210 } else { 211 addChildLM(lm); 212 } 213 if (pos < childLMs.size()) { 214 return true; 215 } 216 } 217 return false; 218 } 219 220 /** 221 * Create a new LineLM, and collect all consecutive 222 * inline generating LMs as its child LMs. 223 * @param firstlm First LM in new LineLM 224 * @return the newly created LineLM 225 */ createLineManager(LayoutManager firstlm)226 private LineLayoutManager createLineManager(LayoutManager firstlm) { 227 LineLayoutManager llm; 228 llm = new LineLayoutManager(getBlockFO(), lineHeight, lead, follow); 229 List<LayoutManager> inlines = new java.util.ArrayList<LayoutManager>(); 230 inlines.add(firstlm); 231 while (proxyLMiter.hasNext()) { 232 LayoutManager lm = proxyLMiter.next(); 233 if (lm instanceof InlineLevelLayoutManager) { 234 inlines.add(lm); 235 } else { 236 proxyLMiter.previous(); 237 break; 238 } 239 } 240 llm.addChildLMs(inlines); 241 return llm; 242 } 243 244 /** {@inheritDoc} */ 245 @Override getKeepTogetherProperty()246 public KeepProperty getKeepTogetherProperty() { 247 return getBlockFO().getKeepTogether(); 248 } 249 250 /** {@inheritDoc} */ 251 @Override getKeepWithPreviousProperty()252 public KeepProperty getKeepWithPreviousProperty() { 253 return getBlockFO().getKeepWithPrevious(); 254 } 255 256 /** {@inheritDoc} */ 257 @Override getKeepWithNextProperty()258 public KeepProperty getKeepWithNextProperty() { 259 return getBlockFO().getKeepWithNext(); 260 } 261 262 /** {@inheritDoc} */ 263 @Override addAreas(PositionIterator parentIter, LayoutContext layoutContext)264 public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { 265 getParentArea(null); 266 267 // if this will create the first block area in a page 268 // and display-align is after or center, add space before 269 if (layoutContext.getSpaceBefore() > 0) { 270 addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore())); 271 } 272 273 LayoutManager childLM; 274 LayoutManager lastLM = null; 275 LayoutContext lc = LayoutContext.offspringOf(layoutContext); 276 lc.setSpaceAdjust(layoutContext.getSpaceAdjust()); 277 // set space after in the LayoutContext for children 278 if (layoutContext.getSpaceAfter() > 0) { 279 lc.setSpaceAfter(layoutContext.getSpaceAfter()); 280 } 281 PositionIterator childPosIter; 282 283 // "unwrap" the NonLeafPositions stored in parentIter 284 // and put them in a new list; 285 LinkedList<Position> positionList = new LinkedList<Position>(); 286 Position pos; 287 Position firstPos = null; 288 Position lastPos = null; 289 while (parentIter.hasNext()) { 290 pos = parentIter.next(); 291 //log.trace("pos = " + pos.getClass().getName() + "; " + pos); 292 if (pos.getIndex() >= 0) { 293 if (firstPos == null) { 294 firstPos = pos; 295 } 296 lastPos = pos; 297 } 298 Position innerPosition = pos; 299 if (pos instanceof NonLeafPosition) { 300 //Not all elements are wrapped 301 innerPosition = pos.getPosition(); 302 } 303 304 if (innerPosition != null 305 && (innerPosition.getLM() != this 306 || innerPosition instanceof MappingPosition)) { 307 // innerPosition was created by another LM 308 positionList.add(innerPosition); 309 lastLM = innerPosition.getLM(); 310 } 311 } 312 313 addId(); 314 315 registerMarkers(true, isFirst(firstPos), isLast(lastPos)); 316 317 // the Positions in positionList were inside the elements 318 // created by the LineLM 319 childPosIter = new PositionIterator(positionList.listIterator()); 320 321 while ((childLM = childPosIter.getNextChildLM()) != null) { 322 // set last area flag 323 lc.setFlags(LayoutContext.LAST_AREA, 324 (layoutContext.isLastArea() && childLM == lastLM)); 325 lc.setStackLimitBP(layoutContext.getStackLimitBP()); 326 // Add the line areas to Area 327 childLM.addAreas(childPosIter, lc); 328 } 329 330 registerMarkers(false, isFirst(firstPos), isLast(lastPos)); 331 332 TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(), 333 effSpaceBefore, effSpaceAfter); 334 TraitSetter.setVisibility(curBlockArea, getBlockFO().getVisibility()); 335 flush(); 336 337 curBlockArea = null; 338 resetSpaces(); 339 340 //Notify end of block layout manager to the PSLM 341 checkEndOfLayout(lastPos); 342 } 343 344 /** 345 * Return an Area which can contain the passed childArea. The childArea 346 * may not yet have any content, but it has essential traits set. 347 * In general, if the LayoutManager already has an Area it simply returns 348 * it. Otherwise, it makes a new Area of the appropriate class. 349 * It gets a parent area for its area by calling its parent LM. 350 * Finally, based on the dimensions of the parent area, it initializes 351 * its own area. This includes setting the content IPD and the maximum 352 * BPD. 353 * @param childArea area to get the parent area for 354 * @return the parent area 355 */ 356 @Override getParentArea(Area childArea)357 public Area getParentArea(Area childArea) { 358 if (curBlockArea == null) { 359 curBlockArea = new Block(); 360 curBlockArea.setChangeBarList(getChangeBarList()); 361 362 curBlockArea.setIPD(super.getContentAreaIPD()); 363 364 curBlockArea.setBidiLevel(getBlockFO().getBidiLevelRecursive()); 365 366 TraitSetter.addBreaks(curBlockArea, 367 getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter()); 368 369 // Must get dimensions from parent area 370 //Don't optimize this line away. It can have ugly side-effects. 371 /*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea); 372 373 // set traits 374 TraitSetter.setProducerID(curBlockArea, getBlockFO().getId()); 375 TraitSetter.addBorders(curBlockArea, 376 getBlockFO().getCommonBorderPaddingBackground(), 377 discardBorderBefore, discardBorderAfter, false, false, this); 378 TraitSetter.addPadding(curBlockArea, 379 getBlockFO().getCommonBorderPaddingBackground(), 380 discardPaddingBefore, discardPaddingAfter, false, false, this); 381 TraitSetter.addMargins(curBlockArea, 382 getBlockFO().getCommonBorderPaddingBackground(), 383 startIndent, endIndent, 384 this); 385 TraitSetter.setLayer(curBlockArea, getBlockFO().getLayer()); 386 387 curBlockArea.setLocale(getBlockFO().getCommonHyphenation().getLocale()); 388 curBlockArea.setLocation(FONode.getLocatorString(getBlockFO().getLocator())); 389 setCurrentArea(curBlockArea); // ??? for generic operations 390 } 391 return curBlockArea; 392 } 393 394 /** {@inheritDoc} */ 395 @Override addChildArea(Area childArea)396 public void addChildArea(Area childArea) { 397 if (curBlockArea != null) { 398 if (childArea instanceof LineArea) { 399 curBlockArea.addLineArea((LineArea) childArea); 400 } else { 401 curBlockArea.addBlock((Block) childArea); 402 } 403 } 404 } 405 406 /** 407 * Force current area to be added to parent area. 408 * {@inheritDoc} 409 */ 410 @Override flush()411 protected void flush() { 412 if (curBlockArea != null) { 413 TraitSetter.addBackground(curBlockArea, 414 getBlockFO().getCommonBorderPaddingBackground(), 415 this); 416 super.flush(); 417 } 418 } 419 420 /** 421 * convenience method that returns the Block node 422 * @return the block node 423 */ getBlockFO()424 protected org.apache.fop.fo.flow.Block getBlockFO() { 425 return (org.apache.fop.fo.flow.Block) fobj; 426 } 427 428 // --------- Property Resolution related functions --------- // 429 430 /** 431 * Returns the IPD of the content area 432 * @return the IPD of the content area 433 */ 434 @Override getContentAreaIPD()435 public int getContentAreaIPD() { 436 if (curBlockArea != null) { 437 return curBlockArea.getIPD(); 438 } 439 return super.getContentAreaIPD(); 440 } 441 442 443 /** 444 * Returns the BPD of the content area 445 * @return the BPD of the content area 446 */ 447 @Override getContentAreaBPD()448 public int getContentAreaBPD() { 449 if (curBlockArea != null) { 450 return curBlockArea.getBPD(); 451 } 452 return -1; 453 } 454 455 /** {@inheritDoc} */ 456 @Override getGeneratesBlockArea()457 public boolean getGeneratesBlockArea() { 458 return true; 459 } 460 461 /** {@inheritDoc} */ 462 @Override isRestartable()463 public boolean isRestartable() { 464 return true; 465 } 466 467 } 468