123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- /*
- * $Id$
- * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
- * For details on use and redistribution please refer to the
- * LICENSE file included with these sources.
- */
-
- package org.apache.fop.layoutmgr;
-
-
- import org.apache.fop.fo.FObj;
- import org.apache.fop.area.Area;
- import org.apache.fop.area.LineArea;
- import org.apache.fop.area.MinOptMax;
- import org.apache.fop.area.inline.InlineArea;
- import org.apache.fop.fo.properties.VerticalAlign;
-
- import org.apache.fop.area.inline.Word;
- import org.apache.fop.area.inline.Space;
- import org.apache.fop.area.inline.Character;
-
- import java.util.ListIterator;
- import java.util.List;
- import java.util.ArrayList;
- import java.util.Iterator;
-
- /**
- * LayoutManager for lines. It builds one or more lines containing
- * inline areas generated by its sub layout managers.
- *
- * The line layout manager does the following things:
- * receives a list of inline creating layout managers
- * adds the inline areas retrieved from the child layout managers
- * finds the best line break position
- * adds complete line to parent
- * stores the starting position for each line in case of recreation
- * if ipd not changed but line contains resolved values (eg. page number), redoes from that line
- * when freeing memory, release all layout managers and inline areas before current position
- * As each child layout manager is used it gets the start, end and normal references for id area, footnotes, floats, links, colour-back properties
- * first line properties are set and used by the child when retrieving the inline area(s)
- *
- * Hyphenation is handled by asking the child to split the words then this
- * adds the hyph char. If redone then exra char ignored.
- *
- * How do we handle Unicode BIDI?
- */
- public class LineLayoutManager extends AbstractBPLayoutManager {
- private LineInfo currentLine = null;
- private boolean bFirstLine = true;
- private MinOptMax totalIPD;
- // the following values must be set by the block
- // these are the dominant basline and lineheight values
- private int lineHeight;
- private int lead;
- private int follow;
-
- List lmList;
- List lines = new ArrayList();
-
- private LayoutPos bestPos = null;
- private MinOptMax bestIPD = null;
-
- static class LineInfo {
- LayoutPos startPos;
- LineArea area;
- boolean hasResolved = false;
- boolean noJustify = false;
- // footnotes, floats?
- }
-
- public LineLayoutManager(FObj fobjBlock, List lms, int lh, int l, int f) {
- super(fobjBlock);
- lmList = lms;
- lineHeight = lh;
- lead = l;
- follow = f;
- }
-
- public int getContentIPD() {
- return parentLM.getContentIPD();
- }
-
- /**
- * Call child layout managers to generate content as long as they
- * generate inline areas. If a block-level generating LM is found,
- * finish any line being filled and return to the parent LM.
- */
- public boolean generateAreas() {
- // if a side float is added and the line contains content
- // where the ipd depends on the line width then restart
- // the line with the adjusted length
-
- while (curPos.lmIndex < lmList.size()) {
- LeafNodeLayoutManager curLM =
- (LeafNodeLayoutManager) lmList.get(curPos.lmIndex);
- curLM.setParentLM(this);
-
- LeafNodeLayoutManager nextLM = null;
- if (curPos.lmIndex + 1 < lmList.size()) {
- nextLM = (LeafNodeLayoutManager) lmList.get(
- curPos.lmIndex + 1);
- while (nextLM.size() == 0) {
- lmList.remove(curPos.lmIndex + 1);
- if (curPos.lmIndex + 1 == lmList.size()) {
- nextLM = null;
- break;
- }
- nextLM = (LeafNodeLayoutManager) lmList.get(
- curPos.lmIndex + 1);
-
- }
- }
- if (nextLM != null) {
- nextLM.setParentLM(this);
- }
- if (curLM.resolved()) {
- currentLine.hasResolved = true;
- }
- while (curPos.subIndex < curLM.size()) {
- InlineArea ia = curLM.get(curPos.subIndex);
- InlineArea next = null;
- if (curPos.subIndex + 1 < curLM.size()) {
- next = curLM.get(curPos.subIndex + 1);
- } else if (curPos.lmIndex + 1 < lmList.size()) {
- if (nextLM != null) {
- next = nextLM.get(0);
- }
- }
- if (currentLine != null && !currentLine.noJustify &&
- (curPos.subIndex + 1 == curLM.size() &&
- curPos.lmIndex + 1 == lmList.size())) {
- currentLine.noJustify = true;
- }
- if (addChild(ia, next)) {
- if (flush()) {
- return true;
- }
- }
- // flush final line in same context as other lines
- // handle last line concepts
- if (curPos.subIndex + 1 == curLM.size() &&
- curPos.lmIndex + 1 == lmList.size()) {
- if (flush()) {
- return true;
- }
- if (curPos.subIndex + 1 == curLM.size() &&
- curPos.lmIndex + 1 == lmList.size()) {
- return false;
- }
-
- }
- curPos.subIndex++;
- }
- curPos.lmIndex++;
- curPos.subIndex = 0;
- }
- return false;
- }
-
- /**
- * Align and position curLine and add it to parentContainer.
- * Set curLine to null.
- */
- public boolean flush() {
- if (currentLine != null) {
- // Adjust spacing as necessary
- adjustSpacing();
- verticalAlign(currentLine.area);
-
- boolean res = parentLM.addChild(currentLine.area);
-
- lines.add(currentLine);
- currentLine = null;
- bestPos = null;
- bestIPD = null;
-
- return res;
- }
- return false;
- }
-
- /**
- * Do the ipd adjustment for stretch areas etc.
- * Consecutive spaces need to be collapsed if possible.
- * should this be on the line area so it can finish resolved areas?
- */
- private void adjustSpacing() {
- List inlineAreas = currentLine.area.getInlineAreas();
-
- // group text elements to split at hyphen if available
- // remove collapsable spaces at start or end on line
-
- // backtrack to best position
- while (true) {
- if (curPos.lmIndex == bestPos.lmIndex &&
- curPos.subIndex == bestPos.subIndex) {
- break;
- }
-
- InlineArea inline =
- (InlineArea) inlineAreas.get(inlineAreas.size() - 1);
- MinOptMax ipd = inline.getAllocationIPD();
- totalIPD.subtract(ipd);
-
- inlineAreas.remove(inlineAreas.size() - 1);
- currentLine.noJustify = false;
-
- curPos.subIndex--;
- if (curPos.subIndex == -1) {
- curPos.lmIndex--;
- LeafNodeLayoutManager curLM =
- (LeafNodeLayoutManager) lmList.get( curPos.lmIndex);
- curPos.subIndex = curLM.size() - 1;
- }
- }
-
-
- // for justify also stretch spaces to fill
- // stretch to best match
- float percentAdjust = 0;
- boolean maxSide = false;
- int realWidth = bestIPD.opt;
- if (bestIPD.opt > parentLM.getContentIPD()) {
- if (bestIPD.opt - parentLM.getContentIPD() <
- (bestIPD.max - bestIPD.opt)) {
- percentAdjust = (bestIPD.opt - parentLM.getContentIPD()) /
- (float)(bestIPD.max - bestIPD.opt);
- realWidth = parentLM.getContentIPD();
- } else {
- percentAdjust = 1;
- realWidth = bestIPD.max;
- }
- maxSide = true;
- } else {
- if (parentLM.getContentIPD() - bestIPD.opt <
- bestIPD.opt - bestIPD.min) {
- percentAdjust = (parentLM.getContentIPD() - bestIPD.opt) /
- (float)(bestIPD.opt - bestIPD.min);
- realWidth = parentLM.getContentIPD();
- } else {
- percentAdjust = 1;
- realWidth = bestIPD.min;
- }
- }
- if (percentAdjust > 0) {
- for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
- InlineArea inline = (InlineArea) iter.next();
- int width;
- MinOptMax iipd = inline.getAllocationIPD();
- if (!maxSide) {
- width = iipd.opt +
- (int)((iipd.max - iipd.opt) * percentAdjust);
- } else {
- width = iipd.opt -
- (int)((iipd.opt - iipd.min) * percentAdjust);
- }
- inline.setWidth(width);
- }
- }
-
- // don't justify lines ending with U+000A or last line
- if (/*justify && */!currentLine.noJustify &&
- realWidth != parentLM.getContentIPD()) {
- ArrayList spaces = new ArrayList();
- for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
- InlineArea inline = (InlineArea) iter.next();
- if (inline instanceof Space /* && !((Space)inline).fixed*/) {
- spaces.add(inline);
- }
- }
- for (Iterator iter = spaces.iterator(); iter.hasNext();) {
- Space space = (Space) iter.next();
- space.setWidth(space.getWidth() +
- (parentLM.getContentIPD() - realWidth) /
- spaces.size());
- }
- }
-
- }
-
- protected void verticalAlign(LineArea lineArea) {
- int maxHeight = lineHeight;
- List inlineAreas = lineArea.getInlineAreas();
-
- // get smallest possible offset to before edge
- // this depends on the height of no and middle alignments
- int before = lead;
- int after = follow;
- int halfLeading = (lineHeight - lead - follow) / 2;
- before += halfLeading;
- for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
- InlineArea inline = (InlineArea) iter.next();
- LayoutInfo info = inline.info;
- int al;
- int ld = inline.getHeight();
- if (info != null) {
- al = info.alignment;
- ld = info.lead;
- } else {
- al = VerticalAlign.BASELINE;
- }
- if (al == VerticalAlign.BASELINE) {
- if (ld > before) {
- before = ld;
- }
- if (inline.getHeight() > before) {
- before = inline.getHeight();
- }
- } else if (al == VerticalAlign.MIDDLE) {
- if (inline.getHeight() / 2 + lead / 2 > before) {
- before = inline.getHeight() / 2 + lead / 2;
- }
- if (inline.getHeight() / 2 - lead / 2 > after) {
- after = inline.getHeight() / 2 - lead / 2;
- }
- } else if (al == VerticalAlign.TOP) {
- } else if (al == VerticalAlign.BOTTOM) {
- }
- }
-
- // then align all before, no and middle alignment
- for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
- InlineArea inline = (InlineArea) iter.next();
- LayoutInfo info = inline.info;
- int al;
- int ld = inline.getHeight();
- boolean bloffset = false;
- if (info != null) {
- al = info.alignment;
- ld = info.lead;
- bloffset = info.blOffset;
- } else {
- al = VerticalAlign.BASELINE;
- }
- if (al == VerticalAlign.BASELINE) {
- // the offset position for text is the baseline
- if (bloffset) {
- inline.setOffset(before);
- } else {
- inline.setOffset(before - ld);
- }
- if (inline.getHeight() - ld > after) {
- after = inline.getHeight() - ld;
- }
- } else if (al == VerticalAlign.MIDDLE) {
- inline.setOffset(before - inline.getHeight() / 2 -
- lead / 2);
- } else if (al == VerticalAlign.TOP) {
- inline.setOffset(0);
- if (inline.getHeight() - before > after) {
- after = inline.getHeight() - before;
- }
- } else if (al == VerticalAlign.BOTTOM) {
- if (inline.getHeight() - before > after) {
- after = inline.getHeight() - before;
- }
- }
- }
-
- // after alignment depends on maximum height of before
- // and middle alignments
- for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
- InlineArea inline = (InlineArea) iter.next();
- LayoutInfo info = inline.info;
- int al;
- if (info != null) {
- al = info.alignment;
- } else {
- al = VerticalAlign.BASELINE;
- }
- if (al == VerticalAlign.BASELINE) {
- } else if (al == VerticalAlign.MIDDLE) {
- } else if (al == VerticalAlign.TOP) {
- } else if (al == VerticalAlign.BOTTOM) {
- inline.setOffset(before + after - inline.getHeight());
- }
- }
- if (before + after > maxHeight) {
- lineArea.setHeight(before + after);
- } else {
- lineArea.setHeight(maxHeight);
- }
- }
-
- /**
- * Return current lineArea or generate a new one if necessary.
- */
- public Area getParentArea(Area childArea) {
- if (currentLine.area == null) {
- createLine();
- }
- return currentLine.area;
- }
-
- protected void createLine() {
- currentLine = new LineInfo();
- currentLine.startPos = curPos;
- currentLine.area = new LineArea();
- /* Set line IPD from parentArea
- * This accounts for indents. What about first line indent?
- * Should we set an "isFirst" flag on the lineArea to signal
- * that to the parent (Block) LM? That's where indent property
- * information will be managed.
- */
- Area parent = parentLM.getParentArea(currentLine.area);
- // currentLine.area.setContentIPD(parent.getContentIPD());
- // totalIPD = new MinOptMax();
- // OR???
- totalIPD = new MinOptMax();
- this.bFirstLine = false;
- }
-
- /**
- * Called by child LayoutManager when it has filled one of its areas.
- * See if the area will fit in the current container.
- * If so, add it.
- * This should also handle floats if childArea is an anchor.
- * @param childArea the area to add: should be an InlineArea subclass!
- */
- public boolean addChild(InlineArea inlineArea, InlineArea nextArea) {
- if (currentLine == null) {
- createLine();
- }
-
- // add side floats first
-
- int pIPD = parentLM.getContentIPD();
-
- currentLine.area.addInlineArea(inlineArea);
- totalIPD.add(inlineArea.getAllocationIPD());
-
- LayoutInfo info = inlineArea.info;
- if (info == null) {
- info = new LayoutInfo();
- }
- LayoutInfo ninfo;
- if (nextArea != null && nextArea.info != null) {
- ninfo = nextArea.info;
- } else {
- ninfo = new LayoutInfo();
- }
-
- // the best pos cannot be before the first area
- if (bestPos == null || bestIPD == null) {
- bestPos = new LayoutPos();
- bestPos.lmIndex = curPos.lmIndex;
- bestPos.subIndex = curPos.subIndex;
- MinOptMax imop = inlineArea.getAllocationIPD();
- bestIPD = new MinOptMax(imop.min, imop.opt, imop.max);
- } else {
-
- // bestPos changed only when it can break
- // before/after a space or other atomic inlines
- // check keep-with on this and next
- // since chars are optimized as words we cannot assume a
- // word is complete and therefore hyphenate or break after
- // side floats effect the available ipd but do not add to line
-
- if (!ninfo.keepPrev && !info.keepNext &&
- !(info.isText && ninfo.isText)) {
- if (Math.abs(bestIPD.opt - pIPD) >
- Math.abs(totalIPD.opt - pIPD) &&
- (totalIPD.min <= pIPD)) {
- bestPos.lmIndex = curPos.lmIndex;
- bestPos.subIndex = curPos.subIndex;
- bestIPD = new MinOptMax(totalIPD.min, totalIPD.opt,
- totalIPD.max);
- }
- }
- }
-
- // Forced line break after this area (ex. ends with LF in nowrap)
- if (info.breakAfter) {
- currentLine.noJustify = true;
- return true;
- }
-
- if (totalIPD.min > pIPD) {
- return true;
- }
-
- return false;
- }
-
- public boolean addChild(Area childArea) {
- return false;
- }
- }
|