/*
* 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: EmptyForIteratorPadCheck
// CSOFF: LineLengthCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: SimplifyBooleanReturnCheck
/**
* <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 if ( ( iaLevelLast != -1 ) && ( ia.getBidiLevel() != iaLevelLast ) ) {
return true;
} else {
return false;
}
}
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() );
}
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;
}
}