| /* |
| * 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.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.ResolvedPageNumber; |
| 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 (InlineArea anIl : il) { |
| process(anIl); |
| } |
| } |
| 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 (InlineParent ic : ich) { |
| 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 (InlineArea anIlNew : ilNew) { |
| if (ia.isAncestorOrSelf(anIlNew)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| private void pushInlineContainers(List<InlineParent> ich) { |
| LinkedList<InlineParent> icl = new LinkedList<InlineParent>(); |
| for (InlineParent ic : ich) { |
| if (icOrig.search(ic) >= 0) { |
| break; |
| } else { |
| icl.addFirst(ic); |
| } |
| } |
| for (InlineParent ic : icl) { |
| icOrig.push(ic); |
| icNew.push(generateInlineContainer(ic)); |
| } |
| } |
| private void pushTextContainer(TextArea tc, InlineArea ia) { |
| if (tc instanceof ResolvedPageNumber) { |
| tcNew = tc; |
| } else 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 (InlineArea ia : tc.getChildAreas()) { |
| 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; |
| } |
| } |