blob: 55a44e681942870b885074a9d2a4deac088e097b [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.area.inline;
import java.util.Arrays;
import java.util.List;
import org.apache.fop.complexscripts.bidi.InlineRun;
import org.apache.fop.complexscripts.util.CharMirror;
/**
* A string of characters without spaces
*/
public class WordArea extends InlineArea {
private static final long serialVersionUID = 6444644662158970942L;
/** The text for this word area */
protected String word;
/** An array of width for adjusting the individual letters (optional) */
protected int[] letterAdjust;
/**
* An array of resolved bidirectional levels corresponding to each character
* in word (optional)
*/
protected int[] levels;
/**
* An array of glyph positioning adjustments to apply to each glyph 'char' in word (optional)
*/
protected int[][] gposAdjustments;
/**
* A flag indicating whether the content of word is reversed in relation to
* its original logical order.
*/
protected boolean reversed;
private boolean nextIsSpace;
/**
* Create a word area
* @param blockProgressionOffset the offset for this area
* @param level the bidirectional embedding level (or -1 if not defined) for word as a group
* @param word the word string
* @param letterAdjust the letter adjust array (may be null)
* @param levels array of per-character (glyph) bidirectional levels,
* in case word area is heterogenously leveled
* @param gposAdjustments array of general position adjustments or null if none apply
* @param reversed true if word is known to be reversed at construction time
*/
public WordArea(int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
int[][] gposAdjustments, boolean reversed, boolean nextIsSpace) {
super(blockProgressionOffset, level);
int length = (word != null) ? word.length() : 0;
this.word = word;
this.letterAdjust = maybeAdjustLength(letterAdjust, length);
this.levels = maybePopulateLevels(levels, level, length);
this.gposAdjustments = maybeAdjustLength(gposAdjustments, length);
this.reversed = reversed;
this.nextIsSpace = nextIsSpace;
}
public WordArea(int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
int[][] gposAdjustments, boolean reversed) {
this(blockProgressionOffset, level, word, letterAdjust, levels, gposAdjustments, reversed, false);
}
/**
* Create a word area
* @param blockProgressionOffset the offset for this area
* @param level the bidirectional embedding level (or -1 if not defined) for word as a group
* @param word the word string
* @param letterAdjust the letter adjust array (may be null)
* @param levels array of per-character (glyph) bidirectional levels,
* in case word area is heterogenously leveled
* @param gposAdjustments array of general position adjustments or null if none apply
*/
public WordArea(
int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
int[][] gposAdjustments) {
this (blockProgressionOffset, level, word, letterAdjust, levels, gposAdjustments, false);
}
/** @return Returns the word. */
public String getWord() {
return word;
}
/** @return the array of letter adjust widths */
public int[] getLetterAdjustArray() {
return this.letterAdjust;
}
/**
* Obtain per-character (glyph) bidi levels.
* @return a (possibly empty) array of levels or null (if none resolved)
*/
public int[] getBidiLevels() {
return levels;
}
/**
* <p>Obtain per-character (glyph) bidi levels over a specified subsequence.</p>
* <p>If word has been reversed, then the subsequence is over the reversed word.</p>
* @param start starting (inclusive) index of subsequence
* @param end ending (exclusive) index of subsequence
* @return a (possibly null) array of per-character (glyph) levels over the specified
* sequence
*/
public int[] getBidiLevels(int start, int end) {
assert start <= end;
if (this.levels != null) {
int n = end - start;
int[] levels = new int [ n ];
System.arraycopy(this.levels, start + 0, levels, 0, n);
return levels;
} else {
return null;
}
}
/**
* <p>Obtain per-character (glyph) level at a specified index position.</p>
* <p>If word has been reversed, then the position is relative to the reversed word.</p>
* @param position the index of the (possibly reversed) character from which to obtain the
* level
* @return a resolved bidirectional level or, if not specified, then -1
*/
public int bidiLevelAt(int position) {
if (position > word.length()) {
throw new IndexOutOfBoundsException();
} else if (levels != null) {
return levels [ position ];
} else {
return -1;
}
}
@Override
public List collectInlineRuns(List runs) {
assert runs != null;
InlineRun r;
int[] levels = getBidiLevels();
if ((levels != null) && (levels.length > 0)) {
r = new InlineRun(this, levels);
} else {
r = new InlineRun(this, getBidiLevel(), word.length());
}
runs.add(r);
return runs;
}
/**
* Obtain per-character (glyph) position adjustments.
* @return a (possibly empty) array of adjustments, each having four elements, or null
* if no adjustments apply
*/
public int[][] getGlyphPositionAdjustments() {
return gposAdjustments;
}
/**
* <p>Obtain per-character (glyph) position adjustments at a specified index position.</p>
* <p>If word has been reversed, then the position is relative to the reversed word.</p>
* @param position the index of the (possibly reversed) character from which to obtain the
* level
* @return an array of adjustments or null if none applies
*/
public int[] glyphPositionAdjustmentsAt(int position) {
if (position > word.length()) {
throw new IndexOutOfBoundsException();
} else if (gposAdjustments != null) {
return gposAdjustments [ position ];
} else {
return null;
}
}
/**
* <p>Reverse characters and corresponding per-character levels and glyph position
* adjustments.</p>
* @param mirror if true, then perform mirroring if mirrorred characters
*/
public void reverse(boolean mirror) {
if (word.length() > 0) {
word = ((new StringBuffer(word)) .reverse()) .toString();
if (levels != null) {
reverse(levels);
}
if (gposAdjustments != null) {
reverse(gposAdjustments);
}
reversed = !reversed;
if (mirror) {
word = CharMirror.mirror(word);
}
}
}
/**
* <p>Perform mirroring on mirrorable characters.</p>
*/
public void mirror() {
if (word.length() > 0) {
word = CharMirror.mirror(word);
}
}
/**
* <p>Determined if word has been reversed (in relation to original logical order).</p>
* <p>If a word is reversed, then both its characters (glyphs) and corresponding per-character
* levels are in reverse order.</p>
* <p>Note: this information is used in order to process non-spacing marks during rendering as
* well as provide hints for caret direction.</p>
* @return true if word is reversed
*/
public boolean isReversed() {
return reversed;
}
public boolean isNextIsSpace() {
return nextIsSpace;
}
/*
* If int[] array is not of specified length, then create
* a new copy of the first length entries.
*/
private static int[] maybeAdjustLength(int[] ia, int length) {
if (ia != null) {
if (ia.length == length) {
return ia;
} else {
int[] iaNew = new int [ length ];
for (int i = 0, n = ia.length; i < n; i++) {
if (i < length) {
iaNew [ i ] = ia [ i ];
} else {
break;
}
}
return iaNew;
}
} else {
return ia;
}
}
/*
* If int[][] matrix is not of specified length, then create
* a new shallow copy of the first length entries.
*/
private static int[][] maybeAdjustLength(int[][] im, int length) {
if (im != null) {
if (im.length == length) {
return im;
} else {
int[][] imNew = new int [ length ][];
for (int i = 0, n = im.length; i < n; i++) {
if (i < length) {
imNew [ i ] = im [ i ];
} else {
break;
}
}
return imNew;
}
} else {
return im;
}
}
private static int[] maybePopulateLevels(int[] levels, int level, int count) {
if ((levels == null) && (level >= 0)) {
levels = new int[count];
Arrays.fill(levels, level);
}
return maybeAdjustLength(levels, count);
}
private static void reverse(int[] a) {
for (int i = 0, n = a.length, m = n / 2; i < m; i++) {
int k = n - i - 1;
int t = a [ k ];
a [ k ] = a [ i ];
a [ i ] = t;
}
}
private static void reverse(int[][] aa) {
for (int i = 0, n = aa.length, m = n / 2; i < m; i++) {
int k = n - i - 1;
int[] t = aa [ k ];
aa [ k ] = aa [ i ];
aa [ i ] = t;
}
}
}