blob: f8f67f953812b6f706e9e2d3c3dbecc151d9c0c6 [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.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 i = 0, n = levels.length; i < n; i++) {
int l = levels [ i ];
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 odd (i.e., right to left)
*/
public boolean maybeNeedsMirroring() {
return (minLevel == maxLevel) && ((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 i = 0; i < numLevels; i++) {
int l = levels [ i ];
if (l > maxLevel) {
maxLevel = l;
}
}
if (maxLevel < 0) {
// leave level buffer empty
} else if (maxLevel < 10) {
// use string of decimal digits
for (int i = 0; i < numLevels; i++) {
lb.append((char) ('0' + levels [ i ]));
}
} else {
// use comma separated list
boolean first = true;
for (int i = 0; i < numLevels; i++) {
if (first) {
first = false;
} else {
lb.append(',');
}
lb.append(levels [ i ]);
}
}
return lb.toString();
}
private static int[] makeLevels(int level, int count) {
int[] levels = new int [ count ];
Arrays.fill(levels, level);
return levels;
}
}