| /* |
| * 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.Arrays; |
| import java.util.List; |
| import java.util.Vector; |
| |
| import org.apache.fop.area.inline.Anchor; |
| import org.apache.fop.area.inline.InlineArea; |
| import org.apache.fop.area.inline.InlineBlockParent; |
| import org.apache.fop.area.inline.InlineParent; |
| import org.apache.fop.area.inline.InlineViewport; |
| import org.apache.fop.area.inline.Leader; |
| import org.apache.fop.area.inline.Space; |
| import org.apache.fop.area.inline.SpaceArea; |
| import org.apache.fop.area.inline.UnresolvedPageNumber; |
| import org.apache.fop.area.inline.WordArea; |
| import org.apache.fop.util.CharUtilities; |
| |
| /** |
| * The <code>InlineRun</code> class is a utility class, the instances of which are used |
| * to capture a sequence of reordering levels associated with an inline area. |
| * |
| * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> |
| */ |
| public class InlineRun { |
| private InlineArea inline; |
| private int[] levels; |
| private int minLevel; |
| private int maxLevel; |
| private int reversals; |
| /** |
| * Primary constructor. |
| * @param inline which generated this inline run |
| * @param levels levels array |
| */ |
| public InlineRun(InlineArea inline, int[] levels) { |
| assert inline != null; |
| assert levels != null; |
| this.inline = inline; |
| this.levels = levels; |
| setMinMax(levels); |
| } |
| /** |
| * Alternate constructor. |
| * @param inline which generated this inline run |
| * @param level for each index |
| * @param count of indices |
| */ |
| public InlineRun(InlineArea inline, int level, int count) { |
| this (inline, makeLevels(level, count)); |
| } |
| /** |
| * Obtain inline area that generated this inline run. |
| * @return inline area that generated this inline run. |
| */ |
| public InlineArea getInline() { |
| return inline; |
| } |
| /** |
| * Obtain minimum bidi level for this run. |
| * @return minimum bidi level |
| */ |
| public int getMinLevel() { |
| return minLevel; |
| } |
| /** |
| * Obtain maximum bidi level for this run. |
| * @return maximum bidi level |
| */ |
| public int getMaxLevel() { |
| return maxLevel; |
| } |
| private void setMinMax(int[] levels) { |
| int mn = Integer.MAX_VALUE; |
| int mx = Integer.MIN_VALUE; |
| if ((levels != null) && (levels.length > 0)) { |
| for (int l : levels) { |
| if (l < mn) { |
| mn = l; |
| } |
| if (l > mx) { |
| mx = l; |
| } |
| } |
| } else { |
| mn = mx = -1; |
| } |
| this.minLevel = mn; |
| this.maxLevel = mx; |
| } |
| /** |
| * Determine if this run has homogenous (same valued) bidi levels. |
| * @return true if homogenous |
| */ |
| public boolean isHomogenous() { |
| return minLevel == maxLevel; |
| } |
| /** |
| * Split this inline run into homogenous runs. |
| * @return list of new runs |
| */ |
| public List split() { |
| List runs = new Vector(); |
| for (int i = 0, n = levels.length; i < n; ) { |
| int l = levels [ i ]; |
| int s = i; |
| int e = s; |
| while (e < n) { |
| if (levels [ e ] != l) { |
| break; |
| } else { |
| e++; |
| } |
| } |
| if (s < e) { |
| runs.add(new InlineRun(inline, l, e - s)); |
| } |
| i = e; |
| } |
| assert runs.size() < 2 : "heterogeneous inlines not yet supported!!"; |
| return runs; |
| } |
| /** |
| * Update a min/max array to correspond with this run's min/max values. |
| * @param mm reference to min/max array |
| */ |
| public void updateMinMax(int[] mm) { |
| if (minLevel < mm[0]) { |
| mm[0] = minLevel; |
| } |
| if (maxLevel > mm[1]) { |
| mm[1] = maxLevel; |
| } |
| } |
| /** |
| * Determine if run needs mirroring. |
| * @return true if run is homogenous and (positive) odd (i.e., right to left) |
| */ |
| public boolean maybeNeedsMirroring() { |
| return (minLevel == maxLevel) && (minLevel > 0) && ((minLevel & 1) != 0); |
| } |
| /** |
| * Reverse run (by incrementing reversal count, not actually reversing). |
| */ |
| public void reverse() { |
| reversals++; |
| } |
| /** |
| * Reverse inline area if it is a word area and it requires |
| * reversal. |
| * @param mirror if true then also mirror characters |
| */ |
| public void maybeReverseWord(boolean mirror) { |
| if (inline instanceof WordArea) { |
| WordArea w = (WordArea) inline; |
| // if not already reversed, then reverse now |
| if (!w.isReversed()) { |
| if ((reversals & 1) != 0) { |
| w.reverse(mirror); |
| } else if (mirror && maybeNeedsMirroring()) { |
| w.mirror(); |
| } |
| } |
| } |
| } |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof InlineRun) { |
| InlineRun ir = (InlineRun) o; |
| if (ir.inline != inline) { |
| return false; |
| } else if (ir.minLevel != minLevel) { |
| return false; |
| } else if (ir.maxLevel != maxLevel) { |
| return false; |
| } else if ((ir.levels != null) && (levels != null)) { |
| if (ir.levels.length != levels.length) { |
| return false; |
| } else { |
| for (int i = 0, n = levels.length; i < n; i++) { |
| if (ir.levels[i] != levels[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } else { |
| return (ir.levels == null) && (levels == null); |
| } |
| } else { |
| return false; |
| } |
| } |
| @Override |
| public int hashCode() { |
| int l = (inline != null) ? inline.hashCode() : 0; |
| l = (l ^ minLevel) + (l << 19); |
| l = (l ^ maxLevel) + (l << 11); |
| return l; |
| } |
| @Override |
| public String toString() { |
| StringBuffer sb = new StringBuffer("RR: { type = \'"); |
| char c; |
| String content = null; |
| if (inline instanceof WordArea) { |
| c = 'W'; |
| content = ((WordArea) inline) .getWord(); |
| } else if (inline instanceof SpaceArea) { |
| c = 'S'; |
| content = ((SpaceArea) inline) .getSpace(); |
| } else if (inline instanceof Anchor) { |
| c = 'A'; |
| } else if (inline instanceof Leader) { |
| c = 'L'; |
| } else if (inline instanceof Space) { |
| c = 'S'; |
| } else if (inline instanceof UnresolvedPageNumber) { |
| c = '#'; |
| content = ((UnresolvedPageNumber) inline) .getText(); |
| } else if (inline instanceof InlineBlockParent) { |
| c = 'B'; |
| } else if (inline instanceof InlineViewport) { |
| c = 'V'; |
| } else if (inline instanceof InlineParent) { |
| c = 'I'; |
| } else { |
| c = '?'; |
| } |
| sb.append(c); |
| sb.append("\', levels = \'"); |
| sb.append(generateLevels(levels)); |
| sb.append("\', min = "); |
| sb.append(minLevel); |
| sb.append(", max = "); |
| sb.append(maxLevel); |
| sb.append(", reversals = "); |
| sb.append(reversals); |
| sb.append(", content = <"); |
| sb.append(CharUtilities.toNCRefs(content)); |
| sb.append("> }"); |
| return sb.toString(); |
| } |
| private String generateLevels(int[] levels) { |
| StringBuffer lb = new StringBuffer(); |
| int maxLevel = -1; |
| int numLevels = levels.length; |
| for (int l : levels) { |
| if (l > maxLevel) { |
| maxLevel = l; |
| } |
| } |
| if (maxLevel < 0) { |
| // leave level buffer empty |
| } else if (maxLevel < 10) { |
| // use string of decimal digits |
| for (int level : levels) { |
| lb.append((char) ('0' + level)); |
| } |
| } else { |
| // use comma separated list |
| boolean first = true; |
| for (int level : levels) { |
| if (first) { |
| first = false; |
| } else { |
| lb.append(','); |
| } |
| lb.append(level); |
| } |
| } |
| return lb.toString(); |
| } |
| private static int[] makeLevels(int level, int count) { |
| int[] levels = new int [ count > 0 ? count : 1 ]; |
| Arrays.fill(levels, level); |
| return levels; |
| } |
| } |
| |