blob: c511dbd707b3a4f13e2158c0e2d3833eb02d8fc4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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$ */
package org.apache.fop.complexscripts.bidi;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import org.apache.fop.area.Area;
import org.apache.fop.area.LinkResolver;
import org.apache.fop.area.inline.BasicLinkArea;
import org.apache.fop.area.inline.FilledArea;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.SpaceArea;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.UnresolvedPageNumber;
// CSOFF: LineLengthCheck
/**
* <p>The <code>UnflattenProcessor</code> class is used to reconstruct (by unflattening) a line
* area's internal area hierarachy after leaf inline area reordering is completed.</p>
*
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
*/
class UnflattenProcessor {
private List<InlineArea> il; // list of flattened inline areas being unflattened
private List<InlineArea> ilNew; // list of unflattened inline areas being constructed
private int iaLevelLast; // last (previous) level of current inline area (if applicable) or -1
private TextArea tcOrig; // original text area container
private TextArea tcNew; // new text area container being constructed
private Stack<InlineParent> icOrig; // stack of original inline parent containers
private Stack<InlineParent> icNew; // stack of new inline parent containers being constructed
UnflattenProcessor(List<InlineArea> inlines) {
this.il = inlines;
this.ilNew = new ArrayList<InlineArea>();
this.iaLevelLast = -1;
this.icOrig = new Stack<InlineParent>();
this.icNew = new Stack<InlineParent>();
}
List unflatten() {
if (il != null) {
for (Iterator<InlineArea> it = il.iterator(); it.hasNext(); ) {
process(it.next());
}
}
finishAll();
return ilNew;
}
private void process(InlineArea ia) {
process(findInlineContainers(ia), findTextContainer(ia), ia);
}
private void process(List<InlineParent> ich, TextArea tc, InlineArea ia) {
if ((tcNew == null) || (tc != tcNew)) {
maybeFinishTextContainer(tc, ia);
maybeFinishInlineContainers(ich, tc, ia);
update(ich, tc, ia);
} else {
// skip inline area whose text container is the current new text container,
// which occurs in the context of the inline runs produced by a filled area
}
}
private boolean shouldFinishTextContainer(TextArea tc, InlineArea ia) {
if ((tcOrig != null) && (tc != tcOrig)) {
return true;
} else {
return (iaLevelLast != -1) && (ia.getBidiLevel() != iaLevelLast);
}
}
private void finishTextContainer() {
finishTextContainer(null, null);
}
private void finishTextContainer(TextArea tc, InlineArea ia) {
if (tcNew != null) {
updateIPD(tcNew);
if (!icNew.empty()) {
icNew.peek().addChildArea(tcNew);
} else {
ilNew.add(tcNew);
}
}
tcNew = null;
}
private void maybeFinishTextContainer(TextArea tc, InlineArea ia) {
if (shouldFinishTextContainer(tc, ia)) {
finishTextContainer(tc, ia);
}
}
private boolean shouldFinishInlineContainer(List<InlineParent> ich, TextArea tc, InlineArea ia) {
if ((ich == null) || ich.isEmpty()) {
return !icOrig.empty();
} else {
if (!icOrig.empty()) {
InlineParent ic = ich.get(0);
InlineParent ic0 = icOrig.peek();
return (ic != ic0) && !isInlineParentOf(ic, ic0);
} else {
return false;
}
}
}
private void finishInlineContainer() {
finishInlineContainer(null, null, null);
}
private void finishInlineContainer(List<InlineParent> ich, TextArea tc, InlineArea ia) {
if ((ich != null) && !ich.isEmpty()) { // finish non-matching inner inline container(s)
for (Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) {
InlineParent ic = it.next();
InlineParent ic0 = icOrig.empty() ? null : icOrig.peek();
if (ic0 == null) {
assert icNew.empty();
} else if (ic != ic0) {
assert !icNew.empty();
InlineParent icO0 = icOrig.pop();
InlineParent icN0 = icNew.pop();
assert icO0 != null;
assert icN0 != null;
if (icNew.empty()) {
ilNew.add(icN0);
} else {
icNew.peek().addChildArea(icN0);
}
if (!icOrig.empty() && (icOrig.peek() == ic)) {
break;
}
} else {
break;
}
}
} else { // finish all inline containers
while (!icNew.empty()) {
InlineParent icO0 = icOrig.pop();
InlineParent icN0 = icNew.pop();
assert icO0 != null;
assert icN0 != null;
if (icNew.empty()) {
ilNew.add(icN0);
} else {
icNew.peek().addChildArea(icN0);
}
}
}
}
private void maybeFinishInlineContainers(List<InlineParent> ich, TextArea tc, InlineArea ia) {
if (shouldFinishInlineContainer(ich, tc, ia)) {
finishInlineContainer(ich, tc, ia);
}
}
private void finishAll() {
finishTextContainer();
finishInlineContainer();
}
private void update(List<InlineParent> ich, TextArea tc, InlineArea ia) {
if (!alreadyUnflattened(ia)) {
if ((ich != null) && !ich.isEmpty()) {
pushInlineContainers(ich);
}
if (tc != null) {
pushTextContainer(tc, ia);
} else {
pushNonTextInline(ia);
}
iaLevelLast = ia.getBidiLevel();
tcOrig = tc;
} else if (tcNew != null) {
finishTextContainer();
tcOrig = null;
} else {
tcOrig = null;
}
}
private boolean alreadyUnflattened(InlineArea ia) {
for (Iterator<InlineArea> it = ilNew.iterator(); it.hasNext(); ) {
if (ia.isAncestorOrSelf(it.next())) {
return true;
}
}
return false;
}
private void pushInlineContainers(List<InlineParent> ich) {
LinkedList<InlineParent> icl = new LinkedList<InlineParent>();
for (Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) {
InlineParent ic = it.next();
if (icOrig.search(ic) >= 0) {
break;
} else {
icl.addFirst(ic);
}
}
for (Iterator<InlineParent> it = icl.iterator(); it.hasNext(); ) {
InlineParent ic = it.next();
icOrig.push(ic);
icNew.push(generateInlineContainer(ic));
}
}
private void pushTextContainer(TextArea tc, InlineArea ia) {
if (tc instanceof UnresolvedPageNumber) {
tcNew = tc;
} else {
if (tcNew == null) {
tcNew = generateTextContainer(tc);
}
tcNew.addChildArea(ia);
}
}
private void pushNonTextInline(InlineArea ia) {
if (icNew.empty()) {
ilNew.add(ia);
} else {
icNew.peek().addChildArea(ia);
}
}
private InlineParent generateInlineContainer(InlineParent i) {
if (i instanceof BasicLinkArea) {
return generateBasicLinkArea((BasicLinkArea) i);
} else if (i instanceof FilledArea) {
return generateFilledArea((FilledArea) i);
} else {
return generateInlineContainer0(i);
}
}
private InlineParent generateBasicLinkArea(BasicLinkArea l) {
BasicLinkArea lc = new BasicLinkArea();
if (l != null) {
initializeInlineContainer(lc, l);
initializeLinkArea(lc, l);
}
return lc;
}
private void initializeLinkArea(BasicLinkArea lc, BasicLinkArea l) {
assert lc != null;
assert l != null;
LinkResolver r = l.getResolver();
if (r != null) {
String[] idrefs = r.getIDRefs();
if (idrefs.length > 0) {
String idref = idrefs[0];
LinkResolver lr = new LinkResolver(idref, lc);
lc.setResolver(lr);
r.addDependent(lr);
}
}
}
private InlineParent generateFilledArea(FilledArea f) {
FilledArea fc = new FilledArea();
if (f != null) {
initializeInlineContainer(fc, f);
initializeFilledArea(fc, f);
}
return fc;
}
private void initializeFilledArea(FilledArea fc, FilledArea f) {
assert fc != null;
assert f != null;
fc.setIPD(f.getIPD());
fc.setUnitWidth(f.getUnitWidth());
fc.setAdjustingInfo(f.getAdjustingInfo());
}
private InlineParent generateInlineContainer0(InlineParent i) {
InlineParent ic = new InlineParent();
if (i != null) {
initializeInlineContainer(ic, i);
}
return ic;
}
private void initializeInlineContainer(InlineParent ic, InlineParent i) {
assert ic != null;
assert i != null;
ic.setTraits(i.getTraits());
ic.setBPD(i.getBPD());
ic.setBlockProgressionOffset(i.getBlockProgressionOffset());
}
private TextArea generateTextContainer(TextArea t) {
TextArea tc = new TextArea();
if (t != null) {
tc.setTraits(t.getTraits());
tc.setBPD(t.getBPD());
tc.setBlockProgressionOffset(t.getBlockProgressionOffset());
tc.setBaselineOffset(t.getBaselineOffset());
tc.setTextWordSpaceAdjust(t.getTextWordSpaceAdjust());
tc.setTextLetterSpaceAdjust(t.getTextLetterSpaceAdjust());
}
return tc;
}
private void updateIPD(TextArea tc) {
int numAdjustable = 0;
for (Iterator it = tc.getChildAreas().iterator(); it.hasNext(); ) {
InlineArea ia = (InlineArea) it.next();
if (ia instanceof SpaceArea) {
SpaceArea sa = (SpaceArea) ia;
if (sa.isAdjustable()) {
numAdjustable++;
}
}
}
if (numAdjustable > 0) {
tc.setIPD(tc.getIPD() + (numAdjustable * tc.getTextWordSpaceAdjust()));
}
}
private TextArea findTextContainer(InlineArea ia) {
assert ia != null;
TextArea t = null;
while (t == null) {
if (ia instanceof TextArea) {
t = (TextArea) ia;
} else {
Area p = ia.getParentArea();
if (p instanceof InlineArea) {
ia = (InlineArea) p;
} else {
break;
}
}
}
return t;
}
private List<InlineParent> findInlineContainers(InlineArea ia) {
assert ia != null;
List<InlineParent> ich = new ArrayList<InlineParent>();
Area a = ia.getParentArea();
while (a != null) {
if (a instanceof InlineArea) {
if ((a instanceof InlineParent) && !(a instanceof TextArea)) {
ich.add((InlineParent) a);
}
a = ((InlineArea) a) .getParentArea();
} else {
a = null;
}
}
return ich;
}
private boolean isInlineParentOf(InlineParent ic0, InlineParent ic1) {
assert ic0 != null;
return ic0.getParentArea() == ic1;
}
}