blob: 97a9f0b5284298a4c6fa823f62cd6c9d02b706cc [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.List;
import java.util.Stack;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.fo.pagination.PageSequence;
// CSOFF: EmptyForIteratorPadCheck
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: SimplifyBooleanReturnCheck
/**
* <p>A utility class for performing bidirectional resolution processing.</p>
*
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
*/
public final class BidiResolver {
/**
* logging instance
*/
private static final Log log = LogFactory.getLog(BidiResolver.class); // CSOK: ConstantNameCheck
private BidiResolver() {
}
/**
* Resolve inline directionality.
* @param ps a page sequence FO instance
*/
public static void resolveInlineDirectionality ( PageSequence ps ) {
if (log.isDebugEnabled()) {
log.debug ( "BD: RESOLVE: " + ps );
}
List ranges = pruneEmptyRanges ( ps.collectDelimitedTextRanges ( new Stack() ) );
resolveInlineDirectionality ( ranges );
}
/**
* Reorder line area.
* @param la a line area instance
*/
public static void reorder ( LineArea la ) {
// 1. collect inline levels
List runs = collectRuns ( la.getInlineAreas(), new Vector() );
if (log.isDebugEnabled()) {
dumpRuns ( "BD: REORDER: INPUT:", runs );
}
// 2. split heterogeneous inlines
runs = splitRuns ( runs );
if (log.isDebugEnabled()) {
dumpRuns ( "BD: REORDER: SPLIT INLINES:", runs );
}
// 3. determine minimum and maximum levels
int[] mm = computeMinMaxLevel ( runs, null );
if (log.isDebugEnabled()) {
log.debug( "BD: REORDER: { min = " + mm[0] + ", max = " + mm[1] + "}" );
}
// 4. reorder from maximum level to minimum odd level
int mn = mm[0];
int mx = mm[1];
if ( mx > 0 ) {
for ( int l1 = mx, l2 = ( ( mn & 1 ) == 0 ) ? ( mn + 1 ) : mn; l1 >= l2; l1-- ) {
runs = reorderRuns ( runs, l1 );
}
}
if (log.isDebugEnabled()) {
dumpRuns ( "BD: REORDER: REORDERED RUNS:", runs );
}
// 5. reverse word consituents (characters and glyphs) while mirroring
boolean mirror = true;
reverseWords ( runs, mirror );
if (log.isDebugEnabled()) {
dumpRuns ( "BD: REORDER: REORDERED WORDS:", runs );
}
// 6. replace line area's inline areas with reordered runs' inline areas
replaceInlines ( la, replicateSplitWords ( runs ) );
}
private static void resolveInlineDirectionality ( List ranges ) {
for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
DelimitedTextRange r = (DelimitedTextRange) it.next();
r.resolve();
if (log.isDebugEnabled()) {
log.debug ( r );
}
}
}
private static List collectRuns ( List inlines, List runs ) {
for ( Iterator it = inlines.iterator(); it.hasNext(); ) {
InlineArea ia = (InlineArea) it.next();
runs = ia.collectInlineRuns ( runs );
}
return runs;
}
private static List splitRuns ( List runs ) {
List runsNew = new Vector();
for ( Iterator it = runs.iterator(); it.hasNext(); ) {
InlineRun ir = (InlineRun) it.next();
if ( ir.isHomogenous() ) {
runsNew.add ( ir );
} else {
runsNew.addAll ( ir.split() );
}
}
if ( ! runsNew.equals ( runs ) ) {
runs = runsNew;
}
return runs;
}
private static int[] computeMinMaxLevel ( List runs, int[] mm ) {
if ( mm == null ) {
mm = new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE};
}
for ( Iterator it = runs.iterator(); it.hasNext(); ) {
InlineRun ir = (InlineRun) it.next();
ir.updateMinMax ( mm );
}
return mm;
}
private static List reorderRuns ( List runs, int level ) {
assert level >= 0;
List runsNew = new Vector();
for ( int i = 0, n = runs.size(); i < n; i++ ) {
InlineRun iri = (InlineRun) runs.get(i);
if ( iri.getMinLevel() < level ) {
runsNew.add ( iri );
} else {
int s = i;
int e = s;
while ( e < n ) {
InlineRun ire = (InlineRun) runs.get(e);
if ( ire.getMinLevel() < level ) {
break;
} else {
e++;
}
}
if ( s < e ) {
runsNew.addAll ( reverseRuns ( runs, s, e ) );
}
i = e - 1;
}
}
if ( ! runsNew.equals ( runs ) ) {
runs = runsNew;
}
return runs;
}
private static List reverseRuns ( List runs, int s, int e ) {
int n = e - s;
Vector runsNew = new Vector ( n );
if ( n > 0 ) {
for ( int i = 0; i < n; i++ ) {
int k = ( n - i - 1 );
InlineRun ir = (InlineRun) runs.get(s + k);
ir.reverse();
runsNew.add ( ir );
}
}
return runsNew;
}
private static void reverseWords ( List runs, boolean mirror ) {
for ( Iterator it = runs.iterator(); it.hasNext(); ) {
InlineRun ir = (InlineRun) it.next();
ir.maybeReverseWord ( mirror );
}
}
private static List replicateSplitWords ( List runs ) {
// [TBD] for each run which inline word area appears multiple times in
// runs, replicate that word
return runs;
}
private static void replaceInlines ( LineArea la, List runs ) {
List<InlineArea> inlines = new ArrayList<InlineArea>();
for ( Iterator it = runs.iterator(); it.hasNext(); ) {
InlineRun ir = (InlineRun) it.next();
inlines.add ( ir.getInline() );
}
la.setInlineAreas ( unflattenInlines ( inlines ) );
}
private static List unflattenInlines ( List<InlineArea> inlines ) {
return new UnflattenProcessor ( inlines ) .unflatten();
}
private static void dumpRuns ( String header, List runs ) {
log.debug ( header );
for ( Iterator it = runs.iterator(); it.hasNext(); ) {
InlineRun ir = (InlineRun) it.next();
log.debug ( ir );
}
}
private static List pruneEmptyRanges ( Stack ranges ) {
Vector rv = new Vector();
for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
DelimitedTextRange r = (DelimitedTextRange) it.next();
if ( ! r.isEmpty() ) {
rv.add ( r );
}
}
return rv;
}
}