/* * 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; } }