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: FlowLayoutManager.java 1761021 2016-09-16 11:40: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.BlockParent; 32 import org.apache.fop.fo.pagination.Flow; 33 import org.apache.fop.util.ListUtil; 34 35 /** 36 * LayoutManager for an fo:flow object. 37 * Its parent LM is the PageSequenceLayoutManager. 38 * This LM is responsible for getting columns of the appropriate size 39 * and filling them with block-level areas generated by its children. 40 * TODO Reintroduce emergency counter (generate error to avoid endless loop) 41 */ 42 public class FlowLayoutManager extends BlockStackingLayoutManager { 43 44 /** 45 * logging instance 46 */ 47 private static Log log = LogFactory.getLog(FlowLayoutManager.class); 48 49 /** Array of areas currently being filled stored by area class */ 50 private final BlockParent[] currentAreas = new BlockParent[Area.CLASS_MAX]; 51 52 private boolean handlingFloat; 53 54 /** 55 * This is the top level layout manager. 56 * It is created by the PageSequence FO. 57 * @param pslm parent PageSequenceLayoutManager object 58 * @param node Flow object 59 */ FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node)60 public FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node) { 61 super(node); 62 setGeneratesBlockArea(true); 63 setParent(pslm); 64 } 65 66 /** {@inheritDoc} */ 67 @Override getNextKnuthElements(LayoutContext context, int alignment)68 public List getNextKnuthElements(LayoutContext context, int alignment) { 69 return getNextKnuthElements(context, alignment, null, null); 70 } 71 72 /** 73 * Get a sequence of KnuthElements representing the content 74 * of the node assigned to the LM. 75 * @param context the LayoutContext used to store layout information 76 * @param alignment the desired text alignment 77 * @param restartPosition {@link Position} to restart from 78 * @param restartLM {@link LayoutManager} to restart from 79 * @return the list of KnuthElements 80 * @see LayoutManager#getNextKnuthElements(LayoutContext,int) 81 */ getNextKnuthElements(LayoutContext context, int alignment, Position restartPosition, LayoutManager restartLM)82 List getNextKnuthElements(LayoutContext context, int alignment, 83 Position restartPosition, LayoutManager restartLM) { 84 85 List<ListElement> elements = new LinkedList<ListElement>(); 86 87 boolean isRestart = (restartPosition != null); 88 // always reset in case of restart (exception: see below) 89 boolean doReset = isRestart; 90 LayoutManager currentChildLM; 91 Stack<LayoutManager> lmStack = new Stack<LayoutManager>(); 92 if (isRestart) { 93 currentChildLM = restartPosition.getLM(); 94 if (currentChildLM == null) { 95 throw new IllegalStateException("Cannot find layout manager to restart from"); 96 } 97 if (restartLM != null && restartLM.getParent() == this) { 98 currentChildLM = restartLM; 99 } else { 100 while (currentChildLM.getParent() != this) { 101 lmStack.push(currentChildLM); 102 currentChildLM = currentChildLM.getParent(); 103 } 104 doReset = false; 105 } 106 setCurrentChildLM(currentChildLM); 107 } else { 108 currentChildLM = getChildLM(); 109 } 110 111 while (currentChildLM != null) { 112 if (!isRestart || doReset) { 113 if (doReset) { 114 currentChildLM.reset(); // TODO won't work with forced breaks 115 } 116 if (addChildElements(elements, currentChildLM, context, alignment, 117 null, null, null) != null) { 118 return elements; 119 } 120 } else { 121 if (addChildElements(elements, currentChildLM, context, alignment, lmStack, 122 restartPosition, restartLM) != null) { 123 return elements; 124 } 125 // restarted; force reset as of next child 126 doReset = true; 127 } 128 currentChildLM = getChildLM(); 129 } 130 131 SpaceResolver.resolveElementList(elements); 132 setFinished(true); 133 134 assert !elements.isEmpty(); 135 return elements; 136 } 137 addChildElements(List<ListElement> elements, LayoutManager childLM, LayoutContext context, int alignment, Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM)138 private List<ListElement> addChildElements(List<ListElement> elements, 139 LayoutManager childLM, LayoutContext context, int alignment, 140 Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM) { 141 if (handleSpanChange(childLM, context)) { 142 SpaceResolver.resolveElementList(elements); 143 return elements; 144 } 145 146 LayoutContext childLC = makeChildLayoutContext(context); 147 List<ListElement> childElements 148 = getNextChildElements(childLM, context, childLC, alignment, lmStack, 149 position, restartAtLM); 150 if (elements.isEmpty()) { 151 context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); 152 } 153 if (!elements.isEmpty() 154 && !ElementListUtils.startsWithForcedBreak(childElements)) { 155 addInBetweenBreak(elements, context, childLC); 156 } 157 context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); 158 159 elements.addAll(childElements); 160 161 if (ElementListUtils.endsWithForcedBreak(elements)) { 162 // a descendant of this flow has break-before or break-after 163 if (childLM.isFinished() && !hasNextChildLM()) { 164 setFinished(true); 165 } 166 SpaceResolver.resolveElementList(elements); 167 return elements; 168 } 169 return null; 170 } 171 handleSpanChange(LayoutManager childLM, LayoutContext context)172 private boolean handleSpanChange(LayoutManager childLM, LayoutContext context) { 173 int span = EN_NONE; 174 int disableColumnBalancing = EN_FALSE; 175 if (childLM instanceof BlockLayoutManager) { 176 span = ((BlockLayoutManager)childLM).getBlockFO().getSpan(); 177 disableColumnBalancing = ((BlockLayoutManager) childLM).getBlockFO() 178 .getDisableColumnBalancing(); 179 } else if (childLM instanceof BlockContainerLayoutManager) { 180 span = ((BlockContainerLayoutManager)childLM).getBlockContainerFO().getSpan(); 181 disableColumnBalancing = ((BlockContainerLayoutManager) childLM).getBlockContainerFO() 182 .getDisableColumnBalancing(); 183 } 184 185 int currentSpan = context.getCurrentSpan(); 186 if (currentSpan != span) { 187 if (span == EN_ALL) { 188 context.setDisableColumnBalancing(disableColumnBalancing); 189 } 190 log.debug("span change from " + currentSpan + " to " + span); 191 context.signalSpanChange(span); 192 return true; 193 } else { 194 return false; 195 } 196 } 197 198 /** 199 * Overridden to take into account the current page-master's 200 * writing-mode 201 * {@inheritDoc} 202 */ 203 @Override makeChildLayoutContext(LayoutContext context)204 protected LayoutContext makeChildLayoutContext(LayoutContext context) { 205 LayoutContext childLC = LayoutContext.newInstance(); 206 childLC.setStackLimitBP(context.getStackLimitBP()); 207 childLC.setRefIPD(context.getRefIPD()); 208 childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode()); 209 return childLC; 210 } 211 212 /** 213 * Overridden to wrap the child positions before returning the list 214 * {@inheritDoc} 215 */ 216 @Override getNextChildElements(LayoutManager childLM, LayoutContext context, LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack, Position restartPosition, LayoutManager restartLM)217 protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context, 218 LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack, 219 Position restartPosition, LayoutManager restartLM) { 220 221 List<ListElement> childElements; 222 if (lmStack == null) { 223 childElements = childLM.getNextKnuthElements(childLC, alignment); 224 } else { 225 childElements = childLM.getNextKnuthElements(childLC, alignment, 226 lmStack, restartPosition, restartLM); 227 } 228 assert !childElements.isEmpty(); 229 230 // "wrap" the Position inside each element 231 List tempList = childElements; 232 childElements = new LinkedList<ListElement>(); 233 wrapPositionElements(tempList, childElements); 234 return childElements; 235 } 236 237 /** {@inheritDoc} */ 238 @Override negotiateBPDAdjustment(int adj, KnuthElement lastElement)239 public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { 240 log.debug(" FLM.negotiateBPDAdjustment> " + adj); 241 242 Position lastPosition = lastElement.getPosition(); 243 if (lastPosition instanceof NonLeafPosition) { 244 // this element was not created by this FlowLM 245 NonLeafPosition savedPos = (NonLeafPosition) lastPosition; 246 lastElement.setPosition(savedPos.getPosition()); 247 int returnValue = ((BlockLevelLayoutManager)lastElement.getLayoutManager()) 248 .negotiateBPDAdjustment(adj, lastElement); 249 lastElement.setPosition(savedPos); 250 log.debug(" FLM.negotiateBPDAdjustment> result " + returnValue); 251 return returnValue; 252 } else { 253 return 0; 254 } 255 } 256 257 /** {@inheritDoc} */ 258 @Override discardSpace(KnuthGlue spaceGlue)259 public void discardSpace(KnuthGlue spaceGlue) { 260 log.debug(" FLM.discardSpace> "); 261 262 Position gluePosition = spaceGlue.getPosition(); 263 if (gluePosition instanceof NonLeafPosition) { 264 // this element was not created by this FlowLM 265 NonLeafPosition savedPos = (NonLeafPosition) gluePosition; 266 spaceGlue.setPosition(savedPos.getPosition()); 267 ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue); 268 spaceGlue.setPosition(savedPos); 269 } 270 } 271 272 /** {@inheritDoc} */ 273 @Override getKeepTogether()274 public Keep getKeepTogether() { 275 return Keep.KEEP_AUTO; 276 } 277 278 /** {@inheritDoc} */ 279 @Override getKeepWithNext()280 public Keep getKeepWithNext() { 281 return Keep.KEEP_AUTO; 282 } 283 284 /** {@inheritDoc} */ 285 @Override getKeepWithPrevious()286 public Keep getKeepWithPrevious() { 287 return Keep.KEEP_AUTO; 288 } 289 290 /** {@inheritDoc} */ 291 @Override getChangedKnuthElements(List oldList, int alignment)292 public List<KnuthElement> getChangedKnuthElements(List oldList, int alignment) { 293 ListIterator<KnuthElement> oldListIterator = oldList.listIterator(); 294 KnuthElement returnedElement; 295 List<KnuthElement> returnedList = new LinkedList<KnuthElement>(); 296 List<KnuthElement> returnList = new LinkedList<KnuthElement>(); 297 KnuthElement prevElement = null; 298 KnuthElement currElement = null; 299 int fromIndex = 0; 300 301 // "unwrap" the Positions stored in the elements 302 KnuthElement oldElement; 303 while (oldListIterator.hasNext()) { 304 oldElement = oldListIterator.next(); 305 if (oldElement.getPosition() instanceof NonLeafPosition) { 306 // oldElement was created by a descendant of this FlowLM 307 oldElement.setPosition((oldElement.getPosition()).getPosition()); 308 } else { 309 // thisElement was created by this FlowLM, remove it 310 oldListIterator.remove(); 311 } 312 } 313 // reset the iterator 314 oldListIterator = oldList.listIterator(); 315 316 317 while (oldListIterator.hasNext()) { 318 currElement = oldListIterator.next(); 319 if (prevElement != null 320 && prevElement.getLayoutManager() != currElement.getLayoutManager()) { 321 // prevElement is the last element generated by the same LM 322 BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager) 323 prevElement.getLayoutManager(); 324 BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) 325 currElement.getLayoutManager(); 326 returnedList.addAll(prevLM.getChangedKnuthElements( 327 oldList.subList(fromIndex, oldListIterator.previousIndex()), alignment)); 328 fromIndex = oldListIterator.previousIndex(); 329 330 // there is another block after this one 331 if (prevLM.mustKeepWithNext() 332 || currLM.mustKeepWithPrevious()) { 333 // add an infinite penalty to forbid a break between blocks 334 returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, 335 new Position(this), false)); 336 } else if (!ListUtil.getLast(returnedList).isGlue()) { 337 // add a null penalty to allow a break between blocks 338 returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); 339 } 340 } 341 prevElement = currElement; 342 } 343 if (currElement != null) { 344 BlockLevelLayoutManager currLM = (BlockLevelLayoutManager) 345 currElement.getLayoutManager(); 346 returnedList.addAll(currLM.getChangedKnuthElements( 347 oldList.subList(fromIndex, oldList.size()), alignment)); 348 } 349 350 // "wrap" the Position stored in each element of returnedList 351 // and add elements to returnList 352 for (KnuthElement aReturnedList : returnedList) { 353 returnedElement = aReturnedList; 354 if (returnedElement.getLayoutManager() != this) { 355 returnedElement.setPosition( 356 new NonLeafPosition(this, returnedElement.getPosition())); 357 } 358 returnList.add(returnedElement); 359 } 360 361 return returnList; 362 } 363 364 /** {@inheritDoc} */ 365 @Override addAreas(PositionIterator parentIter, LayoutContext layoutContext)366 public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { 367 AreaAdditionUtil.addAreas(this, parentIter, layoutContext); 368 flush(); 369 } 370 371 /** 372 * Add child area to a the correct container, depending on its 373 * area class. A Flow can fill at most one area container of any class 374 * at any one time. The actual work is done by BlockStackingLM. 375 * 376 * @param childArea the area to add 377 */ 378 @Override addChildArea(Area childArea)379 public void addChildArea(Area childArea) { 380 if (childArea instanceof BlockParent && handlingFloat()) { 381 BlockParent bp = (BlockParent) childArea; 382 bp.setXOffset(getPSLM().getStartIntrusionAdjustment()); 383 } 384 getParentArea(childArea); 385 addChildToArea(childArea, 386 this.currentAreas[childArea.getAreaClass()]); 387 } 388 389 /** {@inheritDoc} */ 390 @Override getParentArea(Area childArea)391 public Area getParentArea(Area childArea) { 392 BlockParent parentArea = null; 393 int aclass = childArea.getAreaClass(); 394 395 if (aclass == Area.CLASS_NORMAL || aclass == Area.CLASS_SIDE_FLOAT) { 396 parentArea = getCurrentPV().getCurrentFlow(); 397 } else if (aclass == Area.CLASS_BEFORE_FLOAT) { 398 parentArea = getCurrentPV().getBodyRegion().getBeforeFloat(); 399 } else if (aclass == Area.CLASS_FOOTNOTE) { 400 parentArea = getCurrentPV().getBodyRegion().getFootnote(); 401 } else { 402 throw new IllegalStateException("(internal error) Invalid " 403 + "area class (" + aclass + ") requested."); 404 } 405 406 this.currentAreas[aclass] = parentArea; 407 setCurrentArea(parentArea); 408 return parentArea; 409 } 410 411 /** 412 * Returns the IPD of the content area 413 * @return the IPD of the content area 414 */ 415 @Override getContentAreaIPD()416 public int getContentAreaIPD() { 417 int flowIPD = getPSLM().getCurrentColumnWidth(); 418 return flowIPD; 419 } 420 421 /** 422 * Returns the BPD of the content area 423 * @return the BPD of the content area 424 */ 425 @Override getContentAreaBPD()426 public int getContentAreaBPD() { 427 return getCurrentPV().getBodyRegion().getBPD(); 428 } 429 430 /** {@inheritDoc} */ 431 @Override isRestartable()432 public boolean isRestartable() { 433 return true; 434 } 435 handleFloatOn()436 public void handleFloatOn() { 437 handlingFloat = true; 438 } 439 handleFloatOff()440 public void handleFloatOff() { 441 handlingFloat = false; 442 } 443 handlingFloat()444 public boolean handlingFloat() { 445 return handlingFloat; 446 } 447 } 448 449