| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.complexscripts.util; |
| |
| import java.nio.IntBuffer; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| // CSOFF: LineLengthCheck |
| |
| /** |
| * <p>A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes, |
| * and a sequence of character associations, where, for each glyph in the sequence of glyph |
| * codes, there is a corresponding character association. Character associations server to |
| * relate the glyph codes in a glyph sequence to the specific characters in an original |
| * character code sequence with which the glyph codes are associated.</p> |
| * |
| * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> |
| */ |
| public class GlyphSequence implements Cloneable { |
| |
| /** default character buffer capacity in case new character buffer is created */ |
| private static final int DEFAULT_CHARS_CAPACITY = 8; |
| |
| /** character buffer */ |
| private IntBuffer characters; |
| /** glyph buffer */ |
| private IntBuffer glyphs; |
| /** association list */ |
| private List associations; |
| /** predications flag */ |
| private boolean predications; |
| |
| protected GlyphSequence unprocessedGS; |
| |
| /** |
| * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced |
| * character and glyph buffers and associations. If characters is null, then |
| * an empty character buffer is created. If glyphs is null, then a glyph buffer |
| * is created whose capacity is that of the character buffer. If associations is |
| * null, then identity associations are created. |
| * @param characters a (possibly null) buffer of associated (originating) characters |
| * @param glyphs a (possibly null) buffer of glyphs |
| * @param associations a (possibly null) array of glyph to character associations |
| * @param predications true if predications are enabled |
| */ |
| public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations, boolean predications) { |
| if (characters == null) { |
| characters = IntBuffer.allocate(DEFAULT_CHARS_CAPACITY); |
| } |
| if (glyphs == null) { |
| glyphs = IntBuffer.allocate(characters.capacity()); |
| } |
| if (associations == null) { |
| associations = makeIdentityAssociations(characters.limit(), glyphs.limit()); |
| } |
| this.characters = characters; |
| this.glyphs = glyphs; |
| this.associations = associations; |
| this.predications = predications; |
| unprocessedGS = this; |
| } |
| |
| /** |
| * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced |
| * character and glyph buffers and associations. If characters is null, then |
| * an empty character buffer is created. If glyphs is null, then a glyph buffer |
| * is created whose capacity is that of the character buffer. If associations is |
| * null, then identity associations are created. |
| * @param characters a (possibly null) buffer of associated (originating) characters |
| * @param glyphs a (possibly null) buffer of glyphs |
| * @param associations a (possibly null) array of glyph to character associations |
| */ |
| public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations) { |
| this (characters, glyphs, associations, false); |
| } |
| |
| /** |
| * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares |
| * the character array of the existing sequence (but not the buffer object), and creates new copies |
| * of glyphs buffer and association list. |
| * @param gs an existing glyph sequence |
| */ |
| public GlyphSequence(GlyphSequence gs) { |
| this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications); |
| this.unprocessedGS = gs.unprocessedGS; |
| } |
| |
| /** |
| * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares |
| * the character array of the existing sequence (but not the buffer object), but uses the specified |
| * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified |
| * of glyphs buffer and association list. |
| * backtrack, input, and lookahead association arrays to populate the associations. |
| * @param gs an existing glyph sequence |
| * @param bga backtrack glyph array |
| * @param iga input glyph array |
| * @param lga lookahead glyph array |
| * @param bal backtrack association list |
| * @param ial input association list |
| * @param lal lookahead association list |
| */ |
| public GlyphSequence(GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal) { |
| this (gs.characters.duplicate(), concatGlyphs(bga, iga, lga), concatAssociations(bal, ial, lal), gs.predications); |
| } |
| |
| /** |
| * Obtain reference to underlying character buffer. |
| * @return character buffer reference |
| */ |
| public IntBuffer getCharacters() { |
| return characters; |
| } |
| |
| /** |
| * Obtain array of characters. If <code>copy</code> is true, then |
| * a newly instantiated array is returned, otherwise a reference to |
| * the underlying buffer's array is returned. N.B. in case a reference |
| * to the undelying buffer's array is returned, the length |
| * of the array is not necessarily the number of characters in array. |
| * To determine the number of characters, use {@link #getCharacterCount}. |
| * @param copy true if to return a newly instantiated array of characters |
| * @return array of characters |
| */ |
| public int[] getCharacterArray(boolean copy) { |
| if (copy) { |
| return toArray(characters); |
| } else { |
| return characters.array(); |
| } |
| } |
| |
| /** |
| * Obtain the number of characters in character array, where |
| * each character constitutes a unicode scalar value. |
| * NB: Supplementary characters (non-BMP code points) count as 1 |
| * character, not as two UTF-16 code units. |
| * @return number of characters available in character array |
| */ |
| public int getCharacterCount() { |
| return characters.limit(); |
| } |
| |
| /** |
| * Obtain the number of characters in character array, where |
| * each character constitutes a UTF-16 character. This means |
| * that every non-BMP character is counted as 2 characters. |
| * @return number of chars (UTF-16 code units) available in |
| * character array |
| */ |
| public int getUTF16CharacterCount() { |
| int count = 0; |
| for (int ch : characters.array()) { |
| count += Character.charCount(ch); |
| } |
| return count; |
| } |
| |
| /** |
| * Obtain glyph id at specified index. |
| * @param index to obtain glyph |
| * @return the glyph identifier of glyph at specified index |
| * @throws IndexOutOfBoundsException if index is less than zero |
| * or exceeds last valid position |
| */ |
| public int getGlyph(int index) throws IndexOutOfBoundsException { |
| return glyphs.get(index); |
| } |
| |
| public int getUnprocessedGlyph(int index) throws IndexOutOfBoundsException { |
| return unprocessedGS.getGlyph(index); |
| } |
| |
| public void setUnprocessedGS(GlyphSequence glyphSequence) { |
| unprocessedGS = glyphSequence; |
| } |
| |
| /** |
| * Set glyph id at specified index. |
| * @param index to set glyph |
| * @param gi glyph index |
| * @throws IndexOutOfBoundsException if index is greater or equal to |
| * the limit of the underlying glyph buffer |
| */ |
| public void setGlyph(int index, int gi) throws IndexOutOfBoundsException { |
| if (gi > 65535) { |
| gi = 65535; |
| } |
| glyphs.put(index, gi); |
| } |
| |
| /** |
| * Obtain reference to underlying glyph buffer. |
| * @return glyph buffer reference |
| */ |
| public IntBuffer getGlyphs() { |
| return glyphs; |
| } |
| |
| /** |
| * Obtain count glyphs starting at offset. If <code>count</code> is |
| * negative, then it is treated as if the number of available glyphs |
| * were specified. |
| * @param offset into glyph sequence |
| * @param count of glyphs to obtain starting at offset, or negative, |
| * indicating all avaialble glyphs starting at offset |
| * @return glyph array |
| */ |
| public int[] getGlyphs(int offset, int count) { |
| int ng = getGlyphCount(); |
| if (offset < 0) { |
| offset = 0; |
| } else if (offset > ng) { |
| offset = ng; |
| } |
| if (count < 0) { |
| count = ng - offset; |
| } |
| int[] ga = new int [ count ]; |
| for (int i = offset, n = offset + count, k = 0; i < n; i++) { |
| if (k < ga.length) { |
| ga [ k++ ] = glyphs.get(i); |
| } |
| } |
| return ga; |
| } |
| |
| /** |
| * Obtain array of glyphs. If <code>copy</code> is true, then |
| * a newly instantiated array is returned, otherwise a reference to |
| * the underlying buffer's array is returned. N.B. in case a reference |
| * to the undelying buffer's array is returned, the length |
| * of the array is not necessarily the number of glyphs in array. |
| * To determine the number of glyphs, use {@link #getGlyphCount}. |
| * @param copy true if to return a newly instantiated array of glyphs |
| * @return array of glyphs |
| */ |
| public int[] getGlyphArray(boolean copy) { |
| if (copy) { |
| return toArray(glyphs); |
| } else { |
| return glyphs.array(); |
| } |
| } |
| |
| /** |
| * Obtain the number of glyphs in glyphs array, where |
| * each glyph constitutes a font specific glyph index. |
| * @return number of glyphs available in character array |
| */ |
| public int getGlyphCount() { |
| return glyphs.limit(); |
| } |
| |
| /** |
| * Obtain association at specified index. |
| * @param index into associations array |
| * @return glyph to character associations at specified index |
| * @throws IndexOutOfBoundsException if index is less than zero |
| * or exceeds last valid position |
| */ |
| public CharAssociation getAssociation(int index) throws IndexOutOfBoundsException { |
| return (CharAssociation) associations.get(index); |
| } |
| |
| /** |
| * Obtain reference to underlying associations list. |
| * @return associations list |
| */ |
| public List getAssociations() { |
| return associations; |
| } |
| |
| /** |
| * Obtain count associations starting at offset. |
| * @param offset into glyph sequence |
| * @param count of associations to obtain starting at offset, or negative, |
| * indicating all avaialble associations starting at offset |
| * @return associations |
| */ |
| public CharAssociation[] getAssociations(int offset, int count) { |
| int ng = getGlyphCount(); |
| if (offset < 0) { |
| offset = 0; |
| } else if (offset > ng) { |
| offset = ng; |
| } |
| if (count < 0) { |
| count = ng - offset; |
| } |
| CharAssociation[] aa = new CharAssociation [ count ]; |
| for (int i = offset, n = offset + count, k = 0; i < n; i++) { |
| if (k < aa.length) { |
| aa [ k++ ] = (CharAssociation) associations.get(i); |
| } |
| } |
| return aa; |
| } |
| |
| /** |
| * Enable or disable predications. |
| * @param enable true if predications are to be enabled; otherwise false to disable |
| */ |
| public void setPredications(boolean enable) { |
| this.predications = enable; |
| } |
| |
| /** |
| * Obtain predications state. |
| * @return true if predications are enabled |
| */ |
| public boolean getPredications() { |
| return this.predications; |
| } |
| |
| /** |
| * Set predication <KEY,VALUE> at glyph sequence OFFSET. |
| * @param offset offset (index) into glyph sequence |
| * @param key predication key |
| * @param value predication value |
| */ |
| public void setPredication(int offset, String key, Object value) { |
| if (predications) { |
| CharAssociation[] aa = getAssociations(offset, 1); |
| CharAssociation ca = aa[0]; |
| ca.setPredication(key, value); |
| } |
| } |
| |
| /** |
| * Get predication KEY at glyph sequence OFFSET. |
| * @param offset offset (index) into glyph sequence |
| * @param key predication key |
| * @return predication KEY at OFFSET or null if none exists |
| */ |
| public Object getPredication(int offset, String key) { |
| if (predications) { |
| CharAssociation[] aa = getAssociations(offset, 1); |
| CharAssociation ca = aa[0]; |
| return ca.getPredication(key); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Compare glyphs. |
| * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared |
| * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's |
| * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer |
| */ |
| public int compareGlyphs(IntBuffer gb) { |
| int ng = getGlyphCount(); |
| for (int i = 0, n = gb.limit(); i < n; i++) { |
| if (i < ng) { |
| int g1 = glyphs.get(i); |
| int g2 = gb.get(i); |
| if (g1 > g2) { |
| return 1; |
| } else if (g1 < g2) { |
| return -1; |
| } |
| } else { |
| return -1; // this gb is a proper prefix of specified gb |
| } |
| } |
| return 0; // same lengths with no difference |
| } |
| |
| /** {@inheritDoc} */ |
| public Object clone() { |
| try { |
| GlyphSequence gs = (GlyphSequence) super.clone(); |
| gs.characters = copyBuffer(characters); |
| gs.glyphs = copyBuffer(glyphs); |
| gs.associations = copyAssociations(associations); |
| return gs; |
| } catch (CloneNotSupportedException e) { |
| return null; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.append('{'); |
| sb.append("chars = ["); |
| sb.append(characters); |
| sb.append("], glyphs = ["); |
| sb.append(glyphs); |
| sb.append("], associations = ["); |
| sb.append(associations); |
| sb.append("]"); |
| sb.append('}'); |
| return sb.toString(); |
| } |
| |
| /** |
| * Determine if two arrays of glyphs are identical. |
| * @param ga1 first glyph array |
| * @param ga2 second glyph array |
| * @return true if arrays are botth null or both non-null and have identical elements |
| */ |
| public static boolean sameGlyphs(int[] ga1, int[] ga2) { |
| if (ga1 == ga2) { |
| return true; |
| } else if ((ga1 == null) || (ga2 == null)) { |
| return false; |
| } else if (ga1.length != ga2.length) { |
| return false; |
| } else { |
| for (int i = 0, n = ga1.length; i < n; i++) { |
| if (ga1[i] != ga2[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Concatenante glyph arrays. |
| * @param bga backtrack glyph array |
| * @param iga input glyph array |
| * @param lga lookahead glyph array |
| * @return new integer buffer containing concatenated glyphs |
| */ |
| public static IntBuffer concatGlyphs(int[] bga, int[] iga, int[] lga) { |
| int ng = 0; |
| if (bga != null) { |
| ng += bga.length; |
| } |
| if (iga != null) { |
| ng += iga.length; |
| } |
| if (lga != null) { |
| ng += lga.length; |
| } |
| IntBuffer gb = IntBuffer.allocate(ng); |
| if (bga != null) { |
| gb.put(bga); |
| } |
| if (iga != null) { |
| gb.put(iga); |
| } |
| if (lga != null) { |
| gb.put(lga); |
| } |
| gb.flip(); |
| return gb; |
| } |
| |
| /** |
| * Concatenante association arrays. |
| * @param baa backtrack association array |
| * @param iaa input association array |
| * @param laa lookahead association array |
| * @return new list containing concatenated associations |
| */ |
| public static List concatAssociations(CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa) { |
| int na = 0; |
| if (baa != null) { |
| na += baa.length; |
| } |
| if (iaa != null) { |
| na += iaa.length; |
| } |
| if (laa != null) { |
| na += laa.length; |
| } |
| if (na > 0) { |
| List gl = new ArrayList(na); |
| if (baa != null) { |
| Collections.addAll(gl, baa); |
| } |
| if (iaa != null) { |
| Collections.addAll(gl, iaa); |
| } |
| if (laa != null) { |
| Collections.addAll(gl, laa); |
| } |
| return gl; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Join (concatenate) glyph sequences. |
| * @param gs original glyph sequence from which to reuse character array reference |
| * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated |
| * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA |
| */ |
| public static GlyphSequence join(GlyphSequence gs, GlyphSequence[] sa) { |
| assert sa != null; |
| int tg = 0; |
| int ta = 0; |
| for (GlyphSequence s : sa) { |
| IntBuffer ga = s.getGlyphs(); |
| assert ga != null; |
| int ng = ga.limit(); |
| List al = s.getAssociations(); |
| assert al != null; |
| int na = al.size(); |
| assert na == ng; |
| tg += ng; |
| ta += na; |
| } |
| IntBuffer uga = IntBuffer.allocate(tg); |
| ArrayList ual = new ArrayList(ta); |
| for (GlyphSequence s : sa) { |
| uga.put(s.getGlyphs()); |
| ual.addAll(s.getAssociations()); |
| } |
| return new GlyphSequence(gs.getCharacters(), uga, ual, gs.getPredications()); |
| } |
| |
| /** |
| * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET. |
| * @param gs input sequence |
| * @param source index of sub-sequence to reorder |
| * @param count length of sub-sequence to reorder |
| * @param target index to which source sub-sequence is to be moved |
| * @return reordered sequence (or original if no reordering performed) |
| */ |
| public static GlyphSequence reorder(GlyphSequence gs, int source, int count, int target) { |
| if (source != target) { |
| int ng = gs.getGlyphCount(); |
| int[] ga = gs.getGlyphArray(false); |
| int[] nga = new int [ ng ]; |
| CharAssociation[] aa = gs.getAssociations(0, ng); |
| CharAssociation[] naa = new CharAssociation [ ng ]; |
| if (source < target) { |
| int t = 0; |
| for (int s = 0, e = source; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = source + count, e = target; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = source, e = source + count; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = target, e = ng; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| } else { |
| int t = 0; |
| for (int s = 0, e = target; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = source, e = source + count; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = target, e = source; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| for (int s = source + count, e = ng; s < e; s++, t++) { |
| nga[t] = ga[s]; |
| naa[t] = aa[s]; |
| } |
| } |
| return new GlyphSequence(gs, null, nga, null, null, naa, null); |
| } else { |
| return gs; |
| } |
| } |
| |
| private static int[] toArray(IntBuffer ib) { |
| if (ib != null) { |
| int n = ib.limit(); |
| int[] ia = new int[n]; |
| ib.get(ia, 0, n); |
| return ia; |
| } else { |
| return new int[0]; |
| } |
| } |
| |
| private static List makeIdentityAssociations(int numChars, int numGlyphs) { |
| int nc = numChars; |
| int ng = numGlyphs; |
| List av = new ArrayList(ng); |
| for (int i = 0, n = ng; i < n; i++) { |
| int k = (i > nc) ? nc : i; |
| av.add(new CharAssociation(i, (k == nc) ? 0 : 1)); |
| } |
| return av; |
| } |
| |
| private static IntBuffer copyBuffer(IntBuffer ib) { |
| if (ib != null) { |
| int[] ia = new int [ ib.capacity() ]; |
| int p = ib.position(); |
| int l = ib.limit(); |
| System.arraycopy(ib.array(), 0, ia, 0, ia.length); |
| return IntBuffer.wrap(ia, p, l - p); |
| } else { |
| return null; |
| } |
| } |
| |
| private static List copyAssociations(List ca) { |
| if (ca != null) { |
| return new ArrayList(ca); |
| } else { |
| return ca; |
| } |
| } |
| |
| } |