| /* |
| * 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.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: LineLengthCheck |
| |
| /** |
| * <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); |
| |
| 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); |
| } |
| // 1. collect delimited text ranges |
| List ranges = ps.collectDelimitedTextRanges(new Stack()); |
| if (log.isDebugEnabled()) { |
| dumpRanges("BD: RESOLVE: RANGES:", ranges); |
| } |
| // 2. prune empty ranges |
| ranges = pruneEmptyRanges(ranges); |
| if (log.isDebugEnabled()) { |
| dumpRanges("BD: RESOLVE: PRUNED RANGES:", ranges); |
| } |
| // 3. resolve inline directionaly of unpruned ranges |
| 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 (Object range : ranges) { |
| DelimitedTextRange r = (DelimitedTextRange) range; |
| r.resolve(); |
| if (log.isDebugEnabled()) { |
| log.debug(r); |
| } |
| } |
| } |
| |
| private static List collectRuns(List inlines, List runs) { |
| for (Object inline : inlines) { |
| InlineArea ia = (InlineArea) inline; |
| runs = ia.collectInlineRuns(runs); |
| } |
| return runs; |
| } |
| |
| private static List splitRuns(List runs) { |
| List runsNew = new Vector(); |
| for (Object run : runs) { |
| InlineRun ir = (InlineRun) run; |
| 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 (Object run : runs) { |
| InlineRun ir = (InlineRun) run; |
| 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 (Object run : runs) { |
| InlineRun ir = (InlineRun) run; |
| 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 (Object run : runs) { |
| InlineRun ir = (InlineRun) run; |
| 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 (Object run : runs) { |
| InlineRun ir = (InlineRun) run; |
| log.debug(ir); |
| } |
| } |
| private static void dumpRanges(String header, List ranges) { |
| log.debug(header); |
| for (Object range : ranges) { |
| DelimitedTextRange r = (DelimitedTextRange) range; |
| log.debug(r); |
| } |
| } |
| private static List pruneEmptyRanges(List ranges) { |
| Vector rv = new Vector(); |
| for (Object range : ranges) { |
| DelimitedTextRange r = (DelimitedTextRange) range; |
| if (!r.isEmpty()) { |
| rv.add(r); |
| } |
| } |
| return rv; |
| } |
| |
| } |