blob: 3e752c46ccf654f312938556e32d91d624d77e1d [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.fonts;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
// CSOFF: LineLengthCheck
// CSOFF: NoWhitespaceAfterCheck
/**
* The <code>GlyphProcessingState</code> implements a common, base state object used during glyph substitution
* and positioning processing.
* @author Glenn Adams
*/
public class GlyphProcessingState {
/** governing glyph definition table */
protected GlyphDefinitionTable gdef;
/** governing script */
protected String script;
/** governing language */
protected String language;
/** governing feature */
protected String feature;
/** current input glyph sequence */
protected GlyphSequence igs;
/** current index in input sequence */
protected int index;
/** last (maximum) index of input sequence (exclusive) */
protected int indexLast;
/** consumed, updated after each successful subtable application */
protected int consumed;
/** lookup flags */
protected int flags;
/** class match set */
protected int classMatchSet;
/** script specific context tester or null */
protected ScriptContextTester sct;
/** glyph context tester or null */
protected GlyphContextTester gct;
/** ignore base glyph tester */
protected GlyphTester ignoreBase;
/** ignore ligature glyph tester */
protected GlyphTester ignoreLigature;
/** ignore mark glyph tester */
protected GlyphTester ignoreMark;
/** default ignore glyph tester */
protected GlyphTester ignoreDefault;
/**
* Construct glyph processing state.
* @param gs input glyph sequence
* @param script script identifier
* @param language language identifier
* @param feature feature identifier
* @param sct script context tester (or null)
*/
protected GlyphProcessingState ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
this.script = script;
this.language = language;
this.feature = feature;
this.igs = gs;
this.indexLast = gs.getGlyphCount();
this.sct = sct;
this.gct = ( sct != null ) ? sct.getTester ( feature ) : null;
this.ignoreBase = new GlyphTester() { public boolean test(int gi) { return isBase(gi); } };
this.ignoreLigature = new GlyphTester() { public boolean test(int gi) { return isLigature(gi); } };
this.ignoreMark = new GlyphTester() { public boolean test(int gi) { return isMark(gi); } };
}
/**
* Construct glyph processing state using an existing state object using shallow copy
* except as follows: input glyph sequence is copied deep except for its characters array.
* @param s existing processing state to copy from
*/
protected GlyphProcessingState ( GlyphProcessingState s ) {
this ( new GlyphSequence ( s.igs ), s.script, s.language, s.feature, s.sct );
setPosition ( s.index );
}
/**
* Set governing glyph definition table.
* @param gdef glyph definition table (or null, to unset)
*/
public void setGDEF ( GlyphDefinitionTable gdef ) {
if ( this.gdef == null ) {
this.gdef = gdef;
} else if ( gdef == null ) {
this.gdef = null;
}
}
/**
* Obtain governing glyph definition table.
* @return glyph definition table (or null, to not set)
*/
public GlyphDefinitionTable getGDEF() {
return gdef;
}
/**
* Set governing lookup flags
* @param flags lookup flags (or zero, to unset)
*/
public void setFlags ( int flags ) {
if ( this.flags == 0 ) {
this.flags = flags;
} else if ( flags == 0 ) {
this.flags = 0;
}
}
/**
* Obtain governing lookup flags.
* @return lookup flags (zero may indicate unset or no flags)
*/
public int getFlags() {
return flags;
}
/**
* Obtain governing class match set.
* @param gi glyph index that may be used to determine which match set applies
* @return class match set (zero may indicate unset or no set)
*/
public int getClassMatchSet ( int gi ) {
return 0;
}
/**
* Set default ignore tester.
* @param ignoreDefault glyph tester (or null, to unset)
*/
public void setIgnoreDefault ( GlyphTester ignoreDefault ) {
if ( this.ignoreDefault == null ) {
this.ignoreDefault = ignoreDefault;
} else if ( ignoreDefault == null ) {
this.ignoreDefault = null;
}
}
/**
* Obtain governing default ignores tester.
* @return default ignores tester
*/
public GlyphTester getIgnoreDefault() {
return ignoreDefault;
}
/**
* Update glyph subtable specific state. Each time a
* different glyph subtable is to be applied, it is used
* to update this state prior to application, after which
* this state is to be reset.
* @param st glyph subtable to use for update
*/
public void updateSubtableState ( GlyphSubtable st ) {
setGDEF ( st.getGDEF() );
setFlags ( st.getFlags() );
setIgnoreDefault ( getIgnoreTester ( flags ) );
}
/**
* Reset glyph subtable specific state.
*/
public void resetSubtableState() {
setGDEF ( null );
setFlags ( 0 );
setIgnoreDefault ( null );
}
/**
* Obtain current position index in input glyph sequence.
* @return current index
*/
public int getPosition() {
return index;
}
/**
* Set (seek to) position index in input glyph sequence.
* @param index to seek to
* @throws IndexOutOfBoundsException if index is less than zero
* or exceeds last valid position
*/
public void setPosition ( int index ) throws IndexOutOfBoundsException {
if ( ( index >= 0 ) && ( index <= indexLast ) ) {
this.index = index;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Obtain last valid position index in input glyph sequence.
* @return current last index
*/
public int getLastPosition() {
return indexLast;
}
/**
* Determine if at least one glyph remains in
* input sequence.
* @return true if one or more glyph remains
*/
public boolean hasNext() {
return hasNext ( 1 );
}
/**
* Determine if at least <code>count</code> glyphs remain in
* input sequence.
* @param count of glyphs to test
* @return true if at least <code>count</code> glyphs are available
*/
public boolean hasNext ( int count ) {
return ( index + count ) <= indexLast;
}
/**
* Update the current position index based upon previously consumed
* glyphs, i.e., add the consuemd count to the current position index.
* If no glyphs were previously consumed, then forces exactly one
* glyph to be consumed.
* @return the new (updated) position index
*/
public int next() {
if ( index < indexLast ) {
// force consumption of at least one input glyph
if ( consumed == 0 ) {
consumed = 1;
}
index += consumed; consumed = 0;
if ( index > indexLast ) {
index = indexLast;
}
}
return index;
}
/**
* Determine if at least one backtrack (previous) glyph is present
* in input sequence.
* @return true if one or more glyph remains
*/
public boolean hasPrev() {
return hasPrev ( 1 );
}
/**
* Determine if at least <code>count</code> backtrack (previous) glyphs
* are present in input sequence.
* @param count of glyphs to test
* @return true if at least <code>count</code> glyphs are available
*/
public boolean hasPrev ( int count ) {
return ( index - count ) >= 0;
}
/**
* Update the current position index based upon previously consumed
* glyphs, i.e., subtract the consuemd count from the current position index.
* If no glyphs were previously consumed, then forces exactly one
* glyph to be consumed. This method is used to traverse an input
* glyph sequence in reverse order.
* @return the new (updated) position index
*/
public int prev() {
if ( index > 0 ) {
// force consumption of at least one input glyph
if ( consumed == 0 ) {
consumed = 1;
}
index -= consumed; consumed = 0;
if ( index < 0 ) {
index = 0;
}
}
return index;
}
/**
* Record the consumption of <code>count</code> glyphs such that
* this consumption never exceeds the number of glyphs in the input glyph
* sequence.
* @param count of glyphs to consume
* @return newly adjusted consumption count
* @throws IndexOutOfBoundsException if count would cause consumption
* to exceed count of glyphs in input glyph sequence
*/
public int consume ( int count ) throws IndexOutOfBoundsException {
if ( ( consumed + count ) <= indexLast ) {
consumed += count;
return consumed;
} else {
throw new IndexOutOfBoundsException();
}
}
/**
* Determine if any consumption has occurred.
* @return true if consumption count is greater than zero
*/
public boolean didConsume() {
return consumed > 0;
}
/**
* Obtain reference to input glyph sequence, which must not be modified.
* @return input glyph sequence
*/
public GlyphSequence getInput() {
return igs;
}
/**
* Obtain glyph at specified offset from current position.
* @param offset from current position
* @return glyph at specified offset from current position
* @throws IndexOutOfBoundsException if no glyph available at offset
*/
public int getGlyph ( int offset ) throws IndexOutOfBoundsException {
int i = index + offset;
if ( ( i >= 0 ) && ( i < indexLast ) ) {
return igs.getGlyph ( i );
} else {
throw new IndexOutOfBoundsException ( "attempting index at " + i );
}
}
/**
* Obtain glyph at current position.
* @return glyph at current position
* @throws IndexOutOfBoundsException if no glyph available
*/
public int getGlyph() throws IndexOutOfBoundsException {
return getGlyph ( 0 );
}
/**
* Set (replace) glyph at specified offset from current position.
* @param offset from current position
* @param glyph to set at specified offset from current position
* @throws IndexOutOfBoundsException if specified offset is not valid position
*/
public void setGlyph ( int offset, int glyph ) throws IndexOutOfBoundsException {
int i = index + offset;
if ( ( i >= 0 ) && ( i < indexLast ) ) {
igs.setGlyph ( i, glyph );
} else {
throw new IndexOutOfBoundsException ( "attempting index at " + i );
}
}
/**
* Obtain character association of glyph at specified offset from current position.
* @param offset from current position
* @return character association of glyph at current position
* @throws IndexOutOfBoundsException if offset results in an invalid index into input glyph sequence
*/
public GlyphSequence.CharAssociation getAssociation ( int offset ) throws IndexOutOfBoundsException {
int i = index + offset;
if ( ( i >= 0 ) && ( i < indexLast ) ) {
return igs.getAssociation ( i );
} else {
throw new IndexOutOfBoundsException ( "attempting index at " + i );
}
}
/**
* Obtain character association of glyph at current position.
* @return character association of glyph at current position
* @throws IndexOutOfBoundsException if no glyph available
*/
public GlyphSequence.CharAssociation getAssociation() throws IndexOutOfBoundsException {
return getAssociation ( 0 );
}
/**
* Obtain <code>count</code> glyphs starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
* and going in reverse towards beginning of input glyph sequence.
* @param offset from current position
* @param count number of glyphs to obtain
* @param reverseOrder true if to obtain in reverse order
* @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
* @param glyphs array to use to fetch glyphs
* @param counts int[2] array to receive fetched glyph counts, where counts[0] will
* receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
* ignored
* @return array of glyphs
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
if ( count < 0 ) {
count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
}
int start = index + offset;
if ( start < 0 ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + start );
} else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
} else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
}
if ( glyphs == null ) {
glyphs = new int [ count ];
} else if ( glyphs.length != count ) {
throw new IllegalArgumentException ( "glyphs array is non-null, but its length (" + glyphs.length + "), is not equal to count (" + count + ")" );
}
if ( ! reverseOrder ) {
return getGlyphsForward ( start, count, ignoreTester, glyphs, counts );
} else {
return getGlyphsReverse ( start, count, ignoreTester, glyphs, counts );
}
}
private int[] getGlyphsForward ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
if ( k < count ) {
glyphs [ k++ ] = gi; counted++;
} else {
break;
}
} else {
ignored++;
}
}
}
if ( ( counts != null ) && ( counts.length > 1 ) ) {
counts[0] = counted;
counts[1] = ignored;
}
return glyphs;
}
private int[] getGlyphsReverse ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
for ( int i = start, k = 0; i >= 0; i-- ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
if ( k < count ) {
glyphs [ k++ ] = gi; counted++;
} else {
break;
}
} else {
ignored++;
}
}
}
if ( ( counts != null ) && ( counts.length > 1 ) ) {
counts[0] = counted;
counts[1] = ignored;
}
return glyphs;
}
/**
* Obtain <code>count</code> glyphs starting at specified offset from current position. If
* offset is negative, then glyphs are returned in reverse order starting at specified offset
* and going in reverse towards beginning of input glyph sequence.
* @param offset from current position
* @param count number of glyphs to obtain
* @param glyphs array to use to fetch glyphs
* @param counts int[2] array to receive fetched glyph counts, where counts[0] will
* receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
* ignored
* @return array of glyphs
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getGlyphs ( int offset, int count, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
return getGlyphs ( offset, count, offset < 0, ignoreDefault, glyphs, counts );
}
/**
* Obtain all glyphs starting from current position to end of input glyph sequence.
* @return array of available glyphs
* @throws IndexOutOfBoundsException if no glyph available
*/
public int[] getGlyphs() throws IndexOutOfBoundsException {
return getGlyphs ( 0, indexLast - index, false, null, null, null );
}
/**
* Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
* and going in reverse towards beginning of input glyph sequence.
* @param offset from current position
* @param count number of glyphs to obtain
* @param reverseOrder true if to obtain in reverse order
* @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
* @param glyphs array to use to fetch glyphs
* @param counts int[2] array to receive fetched glyph counts, where counts[0] will
* receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
* ignored
* @return array of glyphs
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getIgnoredGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
return getGlyphs ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), glyphs, counts );
}
/**
* Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If <code>offset</code> is
* negative, then fetch in reverse order.
* @param offset from current position
* @param count number of glyphs to obtain
* @return array of glyphs
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getIgnoredGlyphs ( int offset, int count ) throws IndexOutOfBoundsException {
return getIgnoredGlyphs ( offset, count, offset < 0, ignoreDefault, null, null );
}
/**
* Determine number of glyphs available starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then search backwards in input glyph sequence.
* @param offset from current position
* @param reverseOrder true if to obtain in reverse order
* @param ignoreTester glyph tester to use to determine which glyphs to count (or null, in which case none are ignored)
* @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getGlyphsAvailable ( int offset, boolean reverseOrder, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
int start = index + offset;
if ( ( start < 0 ) || ( start > indexLast ) ) {
return new int[] { 0, 0 };
} else if ( ! reverseOrder ) {
return getGlyphsAvailableForward ( start, ignoreTester );
} else {
return getGlyphsAvailableReverse ( start, ignoreTester );
}
}
private int[] getGlyphsAvailableForward ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
if ( ignoreTester == null ) {
counted = indexLast - start;
} else {
for ( int i = start, n = indexLast; i < n; i++ ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ignoreTester.test ( gi ) ) {
ignored++;
} else {
counted++;
}
}
}
}
return new int[] { counted, ignored };
}
private int[] getGlyphsAvailableReverse ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
if ( ignoreTester == null ) {
counted = start + 1;
} else {
for ( int i = start; i >= 0; i-- ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ignoreTester.test ( gi ) ) {
ignored++;
} else {
counted++;
}
}
}
}
return new int[] { counted, ignored };
}
/**
* Determine number of glyphs available starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then search backwards in input glyph sequence. Uses the
* default ignores tester.
* @param offset from current position
* @param reverseOrder true if to obtain in reverse order
* @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getGlyphsAvailable ( int offset, boolean reverseOrder ) throws IndexOutOfBoundsException {
return getGlyphsAvailable ( offset, reverseOrder, ignoreDefault );
}
/**
* Determine number of glyphs available starting at specified offset from current position. If
* offset is negative, then search backwards in input glyph sequence. Uses the
* default ignores tester.
* @param offset from current position
* @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int[] getGlyphsAvailable ( int offset ) throws IndexOutOfBoundsException {
return getGlyphsAvailable ( offset, offset < 0 );
}
/**
* Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then associations are returned in reverse order starting at specified offset
* and going in reverse towards beginning of input glyph sequence.
* @param offset from current position
* @param count number of associations to obtain
* @param reverseOrder true if to obtain in reverse order
* @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
* @param associations array to use to fetch associations
* @param counts int[2] array to receive fetched association counts, where counts[0] will
* receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
* associations were ignored
* @return array of associations
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
throws IndexOutOfBoundsException {
if ( count < 0 ) {
count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
}
int start = index + offset;
if ( start < 0 ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + start );
} else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
} else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
}
if ( associations == null ) {
associations = new GlyphSequence.CharAssociation [ count ];
} else if ( associations.length != count ) {
throw new IllegalArgumentException ( "associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")" );
}
if ( ! reverseOrder ) {
return getAssociationsForward ( start, count, ignoreTester, associations, counts );
} else {
return getAssociationsReverse ( start, count, ignoreTester, associations, counts );
}
}
private GlyphSequence.CharAssociation[] getAssociationsForward ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
if ( k < count ) {
associations [ k++ ] = getAssociation ( i - index ); counted++;
} else {
break;
}
} else {
ignored++;
}
}
}
if ( ( counts != null ) && ( counts.length > 1 ) ) {
counts[0] = counted;
counts[1] = ignored;
}
return associations;
}
private GlyphSequence.CharAssociation[] getAssociationsReverse ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
throws IndexOutOfBoundsException {
int counted = 0;
int ignored = 0;
for ( int i = start, k = 0; i >= 0; i-- ) {
int gi = getGlyph ( i - index );
if ( gi == 65535 ) {
ignored++;
} else {
if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
if ( k < count ) {
associations [ k++ ] = getAssociation ( i - index ); counted++;
} else {
break;
}
} else {
ignored++;
}
}
}
if ( ( counts != null ) && ( counts.length > 1 ) ) {
counts[0] = counted;
counts[1] = ignored;
}
return associations;
}
/**
* Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
* offset is negative, then search backwards in input glyph sequence. Uses the
* default ignores tester.
* @param offset from current position
* @param count number of associations to obtain
* @return array of associations
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
return getAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
}
/**
* Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
* <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
* and going in reverse towards beginning of input glyph sequence.
* @param offset from current position
* @param count number of character associations to obtain
* @param reverseOrder true if to obtain in reverse order
* @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
* @param associations array to use to fetch associations
* @param counts int[2] array to receive fetched association counts, where counts[0] will
* receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
* associations were ignored
* @return array of associations
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
throws IndexOutOfBoundsException {
return getAssociations ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), associations, counts );
}
/**
* Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
* offset is negative, then search backwards in input glyph sequence. Uses the
* default ignores tester.
* @param offset from current position
* @param count number of character associations to obtain
* @return array of associations
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
return getIgnoredAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
}
/**
* Replace subsequence of input glyph sequence starting at specified offset from current position and of
* length <code>count</code> glyphs with a subsequence of the sequence <code>gs</code> starting from the specified
* offset <code>gsOffset</code> of length <code>gsCount</code> glyphs.
* @param offset from current position
* @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
* @param gs glyph sequence from which to obtain replacement glyphs
* @param gsOffset offset of first glyph in replacement sequence
* @param gsCount count of glyphs in replacement sequence starting at <code>gsOffset</code>
* @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public boolean replaceInput ( int offset, int count, GlyphSequence gs, int gsOffset, int gsCount ) throws IndexOutOfBoundsException {
int nig = ( igs != null ) ? igs.getGlyphCount() : 0;
int position = getPosition() + offset;
if ( position < 0 ) {
position = 0;
} else if ( position > nig ) {
position = nig;
}
if ( ( count < 0 ) || ( ( position + count ) > nig ) ) {
count = nig - position;
}
int nrg = ( gs != null ) ? gs.getGlyphCount() : 0;
if ( gsOffset < 0 ) {
gsOffset = 0;
} else if ( gsOffset > nrg ) {
gsOffset = nrg;
}
if ( ( gsCount < 0 ) || ( ( gsOffset + gsCount ) > nrg ) ) {
gsCount = nrg - gsOffset;
}
int ng = nig + gsCount - count;
IntBuffer gb = IntBuffer.allocate ( ng );
List al = new ArrayList ( ng );
for ( int i = 0, n = position; i < n; i++ ) {
gb.put ( igs.getGlyph ( i ) );
al.add ( igs.getAssociation ( i ) );
}
for ( int i = gsOffset, n = gsOffset + gsCount; i < n; i++ ) {
gb.put ( gs.getGlyph ( i ) );
al.add ( gs.getAssociation ( i ) );
}
for ( int i = position + count, n = nig; i < n; i++ ) {
gb.put ( igs.getGlyph ( i ) );
al.add ( igs.getAssociation ( i ) );
}
gb.flip();
if ( igs.compareGlyphs ( gb ) != 0 ) {
this.igs = new GlyphSequence ( igs.getCharacters(), gb, al );
this.indexLast = gb.limit();
return true;
} else {
return false;
}
}
/**
* Replace subsequence of input glyph sequence starting at specified offset from current position and of
* length <code>count</code> glyphs with all glyphs in the replacement sequence <code>gs</code>.
* @param offset from current position
* @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
* @param gs glyph sequence from which to obtain replacement glyphs
* @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public boolean replaceInput ( int offset, int count, GlyphSequence gs ) throws IndexOutOfBoundsException {
return replaceInput ( offset, count, gs, 0, gs.getGlyphCount() );
}
/**
* Erase glyphs in input glyph sequence starting at specified offset from current position, where each glyph
* in the specified <code>glyphs</code> array is matched, one at a time, and when a (forward searching) match is found
* in the input glyph sequence, the matching glyph is replaced with the glyph index 65535.
* @param offset from current position
* @param glyphs array of glyphs to erase
* @return the number of glyphs erased, which may be less than the number of specified glyphs
* @throws IndexOutOfBoundsException if offset or count results in an
* invalid index into input glyph sequence
*/
public int erase ( int offset, int[] glyphs ) throws IndexOutOfBoundsException {
int start = index + offset;
if ( ( start < 0 ) || ( start > indexLast ) ) {
throw new IndexOutOfBoundsException ( "will attempt index at " + start );
} else {
int erased = 0;
for ( int i = start - index, n = indexLast - start; i < n; i++ ) {
int gi = getGlyph ( i );
if ( gi == glyphs [ erased ] ) {
setGlyph ( i, 65535 );
erased++;
}
}
return erased;
}
}
/**
* Determine if is possible that the current input sequence satisfies a script specific
* context testing predicate. If no predicate applies, then application is always possible.
* @return true if no script specific context tester applies or if a specified tester returns
* true for the current input sequence context
*/
public boolean maybeApplicable() {
if ( gct == null ) {
return true;
} else {
return gct.test ( script, language, feature, igs, index );
}
}
/**
* Apply default application semantices; namely, consume one glyph.
*/
public void applyDefault() {
consumed += 1;
}
/**
* Determine if specified glyph is a base glyph according to the governing
* glyph definition table.
* @param gi glyph index to test
* @return true if glyph definition table records glyph as a base glyph; otherwise, false
*/
public boolean isBase ( int gi ) {
if ( gdef != null ) {
return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_BASE );
} else {
return false;
}
}
/**
* Determine if specified glyph is a ligature glyph according to the governing
* glyph definition table.
* @param gi glyph index to test
* @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
*/
public boolean isLigature ( int gi ) {
if ( gdef != null ) {
return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_LIGATURE );
} else {
return false;
}
}
/**
* Determine if specified glyph is a mark glyph according to the governing
* glyph definition table.
* @param gi glyph index to test
* @return true if glyph definition table records glyph as a mark glyph; otherwise, false
*/
public boolean isMark ( int gi ) {
if ( gdef != null ) {
return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_MARK );
} else {
return false;
}
}
/**
* Obtain an ignored glyph tester that corresponds to the specified lookup flags.
* @param flags lookup flags
* @return a glyph tester
*/
public GlyphTester getIgnoreTester ( int flags ) {
if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
if ( ( flags & (GlyphSubtable.LF_IGNORE_LIGATURE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
return ignoreBase;
} else {
return getCombinedIgnoreTester ( flags );
}
}
if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
return ignoreLigature;
} else {
return getCombinedIgnoreTester ( flags );
}
}
if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_LIGATURE) ) == 0 ) {
return ignoreMark;
} else {
return getCombinedIgnoreTester ( flags );
}
}
return null;
}
/**
* Obtain an ignored glyph tester that corresponds to the specified multiple (combined) lookup flags.
* @param flags lookup flags
* @return a glyph tester
*/
public GlyphTester getCombinedIgnoreTester ( int flags ) {
GlyphTester[] gta = new GlyphTester [ 3 ];
int ngt = 0;
if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
gta [ ngt++ ] = ignoreBase;
}
if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
gta [ ngt++ ] = ignoreLigature;
}
if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
gta [ ngt++ ] = ignoreMark;
}
return getCombinedOrTester ( gta, ngt );
}
/**
* Obtain an combined OR glyph tester.
* @param gta an array of glyph testers
* @param ngt number of glyph testers present in specified array
* @return a combined OR glyph tester
*/
public GlyphTester getCombinedOrTester ( GlyphTester[] gta, int ngt ) {
if ( ngt > 0 ) {
return new CombinedOrGlyphTester ( gta, ngt );
} else {
return null;
}
}
/**
* Obtain an combined AND glyph tester.
* @param gta an array of glyph testers
* @param ngt number of glyph testers present in specified array
* @return a combined AND glyph tester
*/
public GlyphTester getCombinedAndTester ( GlyphTester[] gta, int ngt ) {
if ( ngt > 0 ) {
return new CombinedAndGlyphTester ( gta, ngt );
} else {
return null;
}
}
/** combined OR glyph tester */
private static class CombinedOrGlyphTester implements GlyphTester {
private GlyphTester[] gta;
private int ngt;
CombinedOrGlyphTester ( GlyphTester[] gta, int ngt ) {
this.gta = gta;
this.ngt = ngt;
}
/** {@inheritDoc} */
public boolean test ( int gi ) {
for ( int i = 0, n = ngt; i < n; i++ ) {
GlyphTester gt = gta [ i ];
if ( gt != null ) {
if ( gt.test ( gi ) ) {
return true;
}
}
}
return false;
}
}
/** combined AND glyph tester */
private static class CombinedAndGlyphTester implements GlyphTester {
private GlyphTester[] gta;
private int ngt;
CombinedAndGlyphTester ( GlyphTester[] gta, int ngt ) {
this.gta = gta;
this.ngt = ngt;
}
/** {@inheritDoc} */
public boolean test ( int gi ) {
for ( int i = 0, n = ngt; i < n; i++ ) {
GlyphTester gt = gta [ i ];
if ( gt != null ) {
if ( ! gt.test ( gi ) ) {
return false;
}
}
}
return true;
}
}
/** NOT glyph tester */
private static class NotGlyphTester implements GlyphTester {
private GlyphTester gt;
NotGlyphTester ( GlyphTester gt ) {
this.gt = gt;
}
/** {@inheritDoc} */
public boolean test ( int gi ) {
if ( gt != null ) {
if ( gt.test ( gi ) ) {
return false;
}
}
return true;
}
}
}