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