001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: InlineLayoutManager.java 453310 2006-10-05 18:44:15Z spepping $ */
019:
020: package org.apache.fop.layoutmgr.inline;
021:
022: import java.util.ListIterator;
023: import java.util.LinkedList;
024: import java.util.List;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.fop.area.Area;
029: import org.apache.fop.area.inline.InlineArea;
030: import org.apache.fop.area.inline.InlineBlockParent;
031: import org.apache.fop.area.inline.InlineParent;
032: import org.apache.fop.datatypes.Length;
033: import org.apache.fop.fo.flow.Inline;
034: import org.apache.fop.fo.flow.InlineLevel;
035: import org.apache.fop.fo.flow.Leader;
036: import org.apache.fop.fo.pagination.Title;
037: import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
038: import org.apache.fop.fo.properties.CommonMarginInline;
039: import org.apache.fop.fo.properties.SpaceProperty;
040: import org.apache.fop.fonts.Font;
041: import org.apache.fop.layoutmgr.BlockKnuthSequence;
042: import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
043: import org.apache.fop.layoutmgr.BreakElement;
044: import org.apache.fop.layoutmgr.KnuthBox;
045: import org.apache.fop.layoutmgr.KnuthSequence;
046: import org.apache.fop.layoutmgr.LayoutContext;
047: import org.apache.fop.layoutmgr.NonLeafPosition;
048: import org.apache.fop.layoutmgr.SpaceSpecifier;
049: import org.apache.fop.layoutmgr.TraitSetter;
050: import org.apache.fop.layoutmgr.LayoutManager;
051: import org.apache.fop.layoutmgr.Position;
052: import org.apache.fop.layoutmgr.PositionIterator;
053: import org.apache.fop.traits.MinOptMax;
054: import org.apache.fop.traits.SpaceVal;
055:
056: /**
057: * LayoutManager for objects which stack children in the inline direction,
058: * such as Inline or Line
059: */
060: public class InlineLayoutManager extends InlineStackingLayoutManager {
061:
062: /**
063: * logging instance
064: */
065: private static Log log = LogFactory
066: .getLog(InlineLayoutManager.class);
067:
068: private InlineLevel fobj;
069:
070: private CommonMarginInline inlineProps = null;
071: private CommonBorderPaddingBackground borderProps = null;
072:
073: private boolean areaCreated = false;
074: private LayoutManager lastChildLM = null; // Set when return last breakposs;
075:
076: private Position auxiliaryPosition;
077:
078: private Font font;
079:
080: /** The alignment adjust property */
081: protected Length alignmentAdjust;
082: /** The alignment baseline property */
083: protected int alignmentBaseline = EN_BASELINE;
084: /** The baseline shift property */
085: protected Length baselineShift;
086: /** The dominant baseline property */
087: protected int dominantBaseline;
088: /** The line height property */
089: protected SpaceProperty lineHeight;
090:
091: private AlignmentContext alignmentContext = null;
092:
093: /**
094: * Create an inline layout manager.
095: * This is used for fo's that create areas that
096: * contain inline areas.
097: *
098: * @param node the formatting object that creates the area
099: */
100: // The node should be FObjMixed
101: public InlineLayoutManager(InlineLevel node) {
102: super (node);
103: fobj = node;
104: }
105:
106: private Inline getInlineFO() {
107: return (Inline) fobj;
108: }
109:
110: /** @see LayoutManager#initialize */
111: public void initialize() {
112: int padding = 0;
113: font = fobj.getCommonFont().getFontState(
114: fobj.getFOEventHandler().getFontInfo(), this );
115: lineHeight = fobj.getLineHeight();
116: borderProps = fobj.getCommonBorderPaddingBackground();
117: inlineProps = fobj.getCommonMarginInline();
118:
119: if (fobj instanceof Inline) {
120: alignmentAdjust = ((Inline) fobj).getAlignmentAdjust();
121: alignmentBaseline = ((Inline) fobj).getAlignmentBaseline();
122: baselineShift = ((Inline) fobj).getBaselineShift();
123: dominantBaseline = ((Inline) fobj).getDominantBaseline();
124: } else if (fobj instanceof Leader) {
125: alignmentAdjust = ((Leader) fobj).getAlignmentAdjust();
126: alignmentBaseline = ((Leader) fobj).getAlignmentBaseline();
127: baselineShift = ((Leader) fobj).getBaselineShift();
128: dominantBaseline = ((Leader) fobj).getDominantBaseline();
129: }
130: if (borderProps != null) {
131: padding = borderProps.getPadding(
132: CommonBorderPaddingBackground.BEFORE, false, this );
133: padding += borderProps.getBorderWidth(
134: CommonBorderPaddingBackground.BEFORE, false);
135: padding += borderProps.getPadding(
136: CommonBorderPaddingBackground.AFTER, false, this );
137: padding += borderProps.getBorderWidth(
138: CommonBorderPaddingBackground.AFTER, false);
139: }
140: extraBPD = new MinOptMax(padding);
141:
142: }
143:
144: /** @see InlineStackingLayoutManager#getExtraIPD(boolean, boolean) */
145: protected MinOptMax getExtraIPD(boolean isNotFirst,
146: boolean isNotLast) {
147: int borderAndPadding = 0;
148: if (borderProps != null) {
149: borderAndPadding = borderProps.getPadding(
150: CommonBorderPaddingBackground.START, isNotFirst,
151: this );
152: borderAndPadding += borderProps.getBorderWidth(
153: CommonBorderPaddingBackground.START, isNotFirst);
154: borderAndPadding += borderProps.getPadding(
155: CommonBorderPaddingBackground.END, isNotLast, this );
156: borderAndPadding += borderProps.getBorderWidth(
157: CommonBorderPaddingBackground.END, isNotLast);
158: }
159: return new MinOptMax(borderAndPadding);
160: }
161:
162: /** @see InlineStackingLayoutManager#hasLeadingFence(boolean) */
163: protected boolean hasLeadingFence(boolean isNotFirst) {
164: return borderProps != null
165: && (borderProps.getPadding(
166: CommonBorderPaddingBackground.START,
167: isNotFirst, this ) > 0 || borderProps
168: .getBorderWidth(
169: CommonBorderPaddingBackground.START,
170: isNotFirst) > 0);
171: }
172:
173: /** @see InlineStackingLayoutManager#hasTrailingFence(boolean) */
174: protected boolean hasTrailingFence(boolean isNotLast) {
175: return borderProps != null
176: && (borderProps.getPadding(
177: CommonBorderPaddingBackground.END, isNotLast,
178: this ) > 0 || borderProps.getBorderWidth(
179: CommonBorderPaddingBackground.END, isNotLast) > 0);
180: }
181:
182: /** @see InlineStackingLayoutManager#getSpaceStart */
183: protected SpaceProperty getSpaceStart() {
184: return inlineProps != null ? inlineProps.spaceStart : null;
185: }
186:
187: /** @see InlineStackingLayoutManager#getSpaceEnd */
188: protected SpaceProperty getSpaceEnd() {
189: return inlineProps != null ? inlineProps.spaceEnd : null;
190: }
191:
192: /** @see org.apache.fop.layoutmgr.inline.InlineLayoutManager#createArea(boolean) */
193: protected InlineArea createArea(boolean hasInlineParent) {
194: InlineArea area;
195: if (hasInlineParent) {
196: area = new InlineParent();
197: area.setOffset(0);
198: } else {
199: area = new InlineBlockParent();
200: }
201: if (fobj instanceof Inline) {
202: TraitSetter.setProducerID(area, getInlineFO().getId());
203: }
204: return area;
205: }
206:
207: /**
208: * @see org.apache.fop.layoutmgr.inline.InlineStackingLayoutManager#setTraits(boolean, boolean)
209: */
210: protected void setTraits(boolean isNotFirst, boolean isNotLast) {
211: if (borderProps != null) {
212: // Add border and padding to current area and set flags (FIRST, LAST ...)
213: TraitSetter.setBorderPaddingTraits(getCurrentArea(),
214: borderProps, isNotFirst, isNotLast, this );
215: TraitSetter.addBackground(getCurrentArea(), borderProps,
216: this );
217: }
218: }
219:
220: /**
221: * @return true if this element must be kept together
222: */
223: // TODO Use the keep-together property on Inline as well
224: public boolean mustKeepTogether() {
225: return mustKeepTogether(this .getParent());
226: }
227:
228: private boolean mustKeepTogether(LayoutManager lm) {
229: if (lm instanceof BlockLevelLayoutManager) {
230: return ((BlockLevelLayoutManager) lm).mustKeepTogether();
231: } else if (lm instanceof InlineLayoutManager) {
232: return ((InlineLayoutManager) lm).mustKeepTogether();
233: } else {
234: return mustKeepTogether(lm.getParent());
235: }
236: }
237:
238: /** @see org.apache.fop.layoutmgr.LayoutManager */
239: public LinkedList getNextKnuthElements(LayoutContext context,
240: int alignment) {
241: LayoutManager curLM;
242:
243: // the list returned by child LM
244: LinkedList returnedList;
245:
246: // the list which will be returned to the parent LM
247: LinkedList returnList = new LinkedList();
248: KnuthSequence lastSequence = null;
249:
250: SpaceSpecifier leadingSpace = context.getLeadingSpace();
251:
252: if (fobj instanceof Title) {
253: alignmentContext = new AlignmentContext(font, lineHeight
254: .getOptimum(this ).getLength().getValue(this ),
255: context.getWritingMode());
256:
257: } else {
258: alignmentContext = new AlignmentContext(font, lineHeight
259: .getOptimum(this ).getLength().getValue(this ),
260: alignmentAdjust, alignmentBaseline, baselineShift,
261: dominantBaseline, context.getAlignmentContext());
262: }
263:
264: childLC = new LayoutContext(context);
265: childLC.setAlignmentContext(alignmentContext);
266:
267: if (context.startsNewArea()) {
268: // First call to this LM in new parent "area", but this may
269: // not be the first area created by this inline
270: if (getSpaceStart() != null) {
271: context.getLeadingSpace().addSpace(
272: new SpaceVal(getSpaceStart(), this ));
273: }
274:
275: // Check for "fence"
276: if (hasLeadingFence(!context.isFirstArea())) {
277: // Reset leading space sequence for child areas
278: leadingSpace = new SpaceSpecifier(false);
279: }
280: // Reset state variables
281: clearPrevIPD(); // Clear stored prev content dimensions
282: }
283:
284: StringBuffer trace = new StringBuffer("InlineLM:");
285:
286: // We'll add the border to the first inline sequence created.
287: // This flag makes sure we do it only once.
288: boolean borderAdded = false;
289:
290: if (borderProps != null) {
291: childLC.setLineStartBorderAndPaddingWidth(context
292: .getLineStartBorderAndPaddingWidth()
293: + borderProps.getPaddingStart(true, this )
294: + borderProps.getBorderStartWidth(true));
295: childLC.setLineEndBorderAndPaddingWidth(context
296: .getLineEndBorderAndPaddingWidth()
297: + borderProps.getPaddingEnd(true, this )
298: + borderProps.getBorderEndWidth(true));
299: }
300:
301: while ((curLM = (LayoutManager) getChildLM()) != null) {
302: if (!(curLM instanceof InlineLevelLayoutManager)) {
303: // A block LM
304: // Leave room for start/end border and padding
305: if (borderProps != null) {
306: childLC
307: .setRefIPD(childLC.getRefIPD()
308: - borderProps.getPaddingStart(
309: lastChildLM != null, this )
310: - borderProps
311: .getBorderStartWidth(lastChildLM != null)
312: - borderProps.getPaddingEnd(
313: hasNextChildLM(), this )
314: - borderProps
315: .getBorderEndWidth(hasNextChildLM()));
316: }
317: }
318: // get KnuthElements from curLM
319: returnedList = curLM.getNextKnuthElements(childLC,
320: alignment);
321: if (returnList.size() == 0
322: && childLC.isKeepWithPreviousPending()) {
323: childLC
324: .setFlags(
325: LayoutContext.KEEP_WITH_PREVIOUS_PENDING,
326: false);
327: }
328: if (returnedList == null) {
329: // curLM returned null because it finished;
330: // just iterate once more to see if there is another child
331: continue;
332: }
333: if (returnedList.size() == 0) {
334: continue;
335: }
336: if (curLM instanceof InlineLevelLayoutManager) {
337: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING,
338: false);
339: // "wrap" the Position stored in each element of returnedList
340: ListIterator seqIter = returnedList.listIterator();
341: while (seqIter.hasNext()) {
342: KnuthSequence sequence = (KnuthSequence) seqIter
343: .next();
344: sequence.wrapPositions(this );
345: }
346: if (lastSequence != null
347: && lastSequence
348: .appendSequenceOrClose((KnuthSequence) returnedList
349: .get(0))) {
350: returnedList.remove(0);
351: }
352: // add border and padding to the first complete sequence of this LM
353: if (!borderAdded && returnedList.size() != 0) {
354: addKnuthElementsForBorderPaddingStart((KnuthSequence) returnedList
355: .get(0));
356: borderAdded = true;
357: }
358: returnList.addAll(returnedList);
359: } else { // A block LM
360: BlockKnuthSequence sequence = new BlockKnuthSequence(
361: returnedList);
362: sequence.wrapPositions(this );
363: boolean appended = false;
364: if (lastSequence != null) {
365: if (lastSequence.canAppendSequence(sequence)) {
366: BreakElement bk = new BreakElement(
367: new Position(this ), 0, context);
368: boolean keepTogether = (mustKeepTogether()
369: || context.isKeepWithNextPending() || childLC
370: .isKeepWithPreviousPending());
371: appended = lastSequence.appendSequenceOrClose(
372: sequence, keepTogether, bk);
373: } else {
374: lastSequence.endSequence();
375: }
376: }
377: if (!appended) {
378: // add border and padding to the first complete sequence of this LM
379: if (!borderAdded) {
380: addKnuthElementsForBorderPaddingStart(sequence);
381: borderAdded = true;
382: }
383: returnList.add(sequence);
384: }
385: // propagate and clear
386: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING,
387: childLC.isKeepWithNextPending());
388: childLC.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING,
389: false);
390: childLC
391: .setFlags(
392: LayoutContext.KEEP_WITH_PREVIOUS_PENDING,
393: false);
394: }
395: lastSequence = (KnuthSequence) returnList.getLast();
396: lastChildLM = curLM;
397: }
398:
399: if (lastSequence != null) {
400: addKnuthElementsForBorderPaddingEnd(lastSequence);
401: }
402:
403: setFinished(true);
404: log.trace(trace);
405: return returnList.size() == 0 ? null : returnList;
406: }
407:
408: /**
409: * Generate and add areas to parent area.
410: * Set size of each area. This should only create and return one
411: * inline area for any inline parent area.
412: *
413: * @param parentIter Iterator over Position information returned
414: * by this LayoutManager.
415: * @param context layout context.
416: */
417: public void addAreas(PositionIterator parentIter,
418: LayoutContext context) {
419:
420: Position lastPos = null;
421:
422: addId();
423:
424: setChildContext(new LayoutContext(context)); // Store current value
425:
426: // If this LM has fence, make a new leading space specifier.
427: if (hasLeadingFence(areaCreated)) {
428: getContext().setLeadingSpace(new SpaceSpecifier(false));
429: getContext().setFlags(LayoutContext.RESOLVE_LEADING_SPACE,
430: true);
431: } else {
432: getContext().setFlags(LayoutContext.RESOLVE_LEADING_SPACE,
433: false);
434: }
435:
436: if (getSpaceStart() != null) {
437: context.getLeadingSpace().addSpace(
438: new SpaceVal(getSpaceStart(), this ));
439: }
440:
441: // "Unwrap" the NonLeafPositions stored in parentIter and put
442: // them in a new list. Set lastLM to be the LayoutManager
443: // which created the last Position: if the LAST_AREA flag is
444: // set in the layout context, it must be also set in the
445: // layout context given to lastLM, but must be cleared in the
446: // layout context given to the other LMs.
447: LinkedList positionList = new LinkedList();
448: NonLeafPosition pos = null;
449: LayoutManager lastLM = null; // last child LM in this iterator
450: while (parentIter.hasNext()) {
451: pos = (NonLeafPosition) parentIter.next();
452: if (pos != null && pos.getPosition() != null) {
453: positionList.add(pos.getPosition());
454: lastLM = pos.getPosition().getLM();
455: lastPos = pos;
456: }
457: }
458: /*if (pos != null) {
459: lastLM = pos.getPosition().getLM();
460: }*/
461:
462: InlineArea parent = createArea(lastLM == null
463: || lastLM instanceof InlineLevelLayoutManager);
464: parent.setBPD(alignmentContext.getHeight());
465: if (parent instanceof InlineParent) {
466: parent.setOffset(alignmentContext.getOffset());
467: } else if (parent instanceof InlineBlockParent) {
468: // All inline elements are positioned by the renderers relative to
469: // the before edge of their content rectangle
470: if (borderProps != null) {
471: parent.setOffset(borderProps.getPaddingBefore(false,
472: this )
473: + borderProps.getBorderBeforeWidth(false));
474: }
475: }
476: setCurrentArea(parent);
477:
478: StackingIter childPosIter = new StackingIter(positionList
479: .listIterator());
480:
481: LayoutManager prevLM = null;
482: LayoutManager childLM;
483: while ((childLM = childPosIter.getNextChildLM()) != null) {
484: getContext().setFlags(LayoutContext.LAST_AREA,
485: context.isLastArea() && childLM == lastLM);
486: childLM.addAreas(childPosIter, getContext());
487: getContext().setLeadingSpace(
488: getContext().getTrailingSpace());
489: getContext().setFlags(LayoutContext.RESOLVE_LEADING_SPACE,
490: true);
491: prevLM = childLM;
492: }
493:
494: /* If this LM has a trailing fence, resolve trailing space
495: * specs from descendants. Otherwise, propagate any trailing
496: * space specs to the parent LM via the layout context. If
497: * the last child LM called returns LAST_AREA in the layout
498: * context and it is the last child LM for this LM, then this
499: * must be the last area for the current LM too.
500: */
501: boolean isLast = (getContext().isLastArea() && prevLM == lastChildLM);
502: if (hasTrailingFence(isLast)) {
503: addSpace(getCurrentArea(), getContext().getTrailingSpace()
504: .resolve(false), getContext().getSpaceAdjust());
505: context.setTrailingSpace(new SpaceSpecifier(false));
506: } else {
507: // Propagate trailing space-spec sequence to parent LM in context.
508: context.setTrailingSpace(getContext().getTrailingSpace());
509: }
510: // Add own trailing space to parent context (or set on area?)
511: if (context.getTrailingSpace() != null && getSpaceEnd() != null) {
512: context.getTrailingSpace().addSpace(
513: new SpaceVal(getSpaceEnd(), this ));
514: }
515:
516: // Not sure if lastPos can legally be null or if that masks a different problem.
517: // But it seems to fix bug 38053.
518: setTraits(areaCreated, lastPos == null || !isLast(lastPos));
519: parentLM.addChildArea(getCurrentArea());
520:
521: context.setFlags(LayoutContext.LAST_AREA, isLast);
522: areaCreated = true;
523: }
524:
525: /** @see LayoutManager#addChildArea(Area) */
526: public void addChildArea(Area childArea) {
527: Area parent = getCurrentArea();
528: if (getContext().resolveLeadingSpace()) {
529: addSpace(parent, getContext().getLeadingSpace().resolve(
530: false), getContext().getSpaceAdjust());
531: }
532: parent.addChildArea(childArea);
533: }
534:
535: /** @see LayoutManager#getChangedKnuthElements(List, int) */
536: public LinkedList getChangedKnuthElements(List oldList,
537: int alignment) {
538: LinkedList returnedList = new LinkedList();
539: addKnuthElementsForBorderPaddingStart(returnedList);
540: returnedList.addAll(super .getChangedKnuthElements(oldList,
541: alignment));
542: addKnuthElementsForBorderPaddingEnd(returnedList);
543: return returnedList;
544: }
545:
546: /**
547: * Creates Knuth elements for start border padding and adds them to the return list.
548: * @param returnList return list to add the additional elements to
549: */
550: protected void addKnuthElementsForBorderPaddingStart(List returnList) {
551: //Border and Padding (start)
552: CommonBorderPaddingBackground borderAndPadding = fobj
553: .getCommonBorderPaddingBackground();
554: if (borderAndPadding != null) {
555: int ipStart = borderAndPadding.getBorderStartWidth(false)
556: + borderAndPadding.getPaddingStart(false, this );
557: if (ipStart > 0) {
558: returnList.add(0, new KnuthBox(ipStart,
559: getAuxiliaryPosition(), true));
560: }
561: }
562: }
563:
564: /**
565: * Creates Knuth elements for end border padding and adds them to the return list.
566: * @param returnList return list to add the additional elements to
567: */
568: protected void addKnuthElementsForBorderPaddingEnd(List returnList) {
569: //Border and Padding (after)
570: CommonBorderPaddingBackground borderAndPadding = fobj
571: .getCommonBorderPaddingBackground();
572: if (borderAndPadding != null) {
573: int ipEnd = borderAndPadding.getBorderEndWidth(false)
574: + borderAndPadding.getPaddingEnd(false, this );
575: if (ipEnd > 0) {
576: returnList.add(new KnuthBox(ipEnd,
577: getAuxiliaryPosition(), true));
578: }
579: }
580: }
581:
582: /** @return a cached auxiliary Position instance used for things like spaces. */
583: protected Position getAuxiliaryPosition() {
584: //if (this.auxiliaryPosition == null) {
585: //this.auxiliaryPosition = new NonLeafPosition(this, new LeafPosition(this, -1));
586: this .auxiliaryPosition = new NonLeafPosition(this , null);
587: //}
588: return this .auxiliaryPosition;
589: }
590:
591: /** @see org.apache.fop.layoutmgr.inline.LeafNodeLayoutManager#addId() */
592: protected void addId() {
593: if (fobj instanceof Inline) {
594: getPSLM().addIDToPage(getInlineFO().getId());
595: }
596: }
597:
598: }
|