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