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