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