1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: MultiByteFont.java 1885366 2021-01-11 15:00:20Z ssteiner $ */
19 
20 package org.apache.fop.fonts;
21 
22 import java.awt.Rectangle;
23 import java.io.InputStream;
24 import java.nio.CharBuffer;
25 import java.nio.IntBuffer;
26 import java.util.ArrayList;
27 import java.util.BitSet;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 
35 import org.apache.fop.apps.io.InternalResourceResolver;
36 import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
37 import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
38 import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
39 import org.apache.fop.complexscripts.fonts.GlyphTable;
40 import org.apache.fop.complexscripts.fonts.Positionable;
41 import org.apache.fop.complexscripts.fonts.Substitutable;
42 import org.apache.fop.complexscripts.util.CharAssociation;
43 import org.apache.fop.complexscripts.util.CharNormalize;
44 import org.apache.fop.complexscripts.util.GlyphSequence;
45 import org.apache.fop.fonts.truetype.SVGGlyphData;
46 import org.apache.fop.util.CharUtilities;
47 
48 /**
49  * Generic MultiByte (CID) font
50  */
51 public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
52 
53     /** logging instance */
54     private static final Log log
55         = LogFactory.getLog(MultiByteFont.class);
56 
57     private String ttcName;
58     private String encoding = "Identity-H";
59 
60     private int defaultWidth;
61     private CIDFontType cidType = CIDFontType.CIDTYPE2;
62 
63     protected final CIDSet cidSet;
64 
65     /* advanced typographic support */
66     private GlyphDefinitionTable gdef;
67     private GlyphSubstitutionTable gsub;
68     private GlyphPositioningTable gpos;
69 
70     /* dynamic private use (character) mappings */
71     private int numMapped;
72     private int numUnmapped;
73     private int nextPrivateUse = 0xE000;
74     private int firstPrivate;
75     private int lastPrivate;
76     private int firstUnmapped;
77     private int lastUnmapped;
78 
79     /** Contains the character bounding boxes for all characters in the font */
80     protected Rectangle[] boundingBoxes;
81 
82     private boolean isOTFFile;
83 
84     // since for most users the most likely glyphs are in the first cmap segments we store their mapping.
85     private static final int NUM_MOST_LIKELY_GLYPHS = 256;
86     private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS];
87 
88     //A map to store each used glyph from the CID set against the glyph name.
89     private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>();
90 
91     /**
92      * Default constructor
93      */
MultiByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode)94     public MultiByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
95         super(resourceResolver);
96         setFontType(FontType.TYPE0);
97         setEmbeddingMode(embeddingMode);
98         if (embeddingMode != EmbeddingMode.FULL) {
99             cidSet = new CIDSubset(this);
100         } else {
101             cidSet = new CIDFull(this);
102         }
103     }
104 
105     /** {@inheritDoc} */
106     @Override
getDefaultWidth()107     public int getDefaultWidth() {
108         return defaultWidth;
109     }
110 
111     /** {@inheritDoc} */
112     @Override
getRegistry()113     public String getRegistry() {
114         return "Adobe";
115     }
116 
117     /** {@inheritDoc} */
118     @Override
getOrdering()119     public String getOrdering() {
120         return "UCS";
121     }
122 
123     /** {@inheritDoc} */
124     @Override
getSupplement()125     public int getSupplement() {
126         return 0;
127     }
128 
129     /** {@inheritDoc} */
130     @Override
getCIDType()131     public CIDFontType getCIDType() {
132         return cidType;
133     }
134 
setIsOTFFile(boolean isOTFFile)135     public void setIsOTFFile(boolean isOTFFile) {
136         this.isOTFFile = isOTFFile;
137     }
138 
isOTFFile()139     public boolean isOTFFile() {
140         return this.isOTFFile;
141     }
142 
143     /**
144      * Sets the CIDType.
145      * @param cidType The cidType to set
146      */
setCIDType(CIDFontType cidType)147     public void setCIDType(CIDFontType cidType) {
148         this.cidType = cidType;
149     }
150 
151     /** {@inheritDoc} */
152     @Override
getEmbedFontName()153     public String getEmbedFontName() {
154         if (isEmbeddable()) {
155             return FontUtil.stripWhiteSpace(super.getFontName());
156         } else {
157             return super.getFontName();
158         }
159     }
160 
161     /** {@inheritDoc} */
isEmbeddable()162     public boolean isEmbeddable() {
163         return !(getEmbedFileURI() == null && getEmbedResourceName() == null);
164     }
165 
isSubsetEmbedded()166     public boolean isSubsetEmbedded() {
167         if (getEmbeddingMode() == EmbeddingMode.FULL) {
168             return false;
169         }
170         return true;
171     }
172 
173     /** {@inheritDoc} */
174     @Override
getCIDSet()175     public CIDSet getCIDSet() {
176         return this.cidSet;
177     }
178 
mapUsedGlyphName(int gid, String value)179     public void mapUsedGlyphName(int gid, String value) {
180         usedGlyphNames.put(gid, value);
181     }
182 
getUsedGlyphNames()183     public LinkedHashMap<Integer, String> getUsedGlyphNames() {
184         return usedGlyphNames;
185     }
186 
187     /** {@inheritDoc} */
188     @Override
getEncodingName()189     public String getEncodingName() {
190         return encoding;
191     }
192 
193     /** {@inheritDoc} */
getWidth(int i, int size)194     public int getWidth(int i, int size) {
195         if (isEmbeddable()) {
196             int glyphIndex = cidSet.getOriginalGlyphIndex(i);
197             return size * width[glyphIndex];
198         } else {
199             return size * width[i];
200         }
201     }
202 
203     /** {@inheritDoc} */
getWidths()204     public int[] getWidths() {
205         int[] arr = new int[width.length];
206         System.arraycopy(width, 0, arr, 0, width.length);
207         return arr;
208     }
209 
getBoundingBox(int glyphIndex, int size)210     public Rectangle getBoundingBox(int glyphIndex, int size) {
211         int index = isEmbeddable() ? cidSet.getOriginalGlyphIndex(glyphIndex) : glyphIndex;
212         Rectangle bbox = boundingBoxes[index];
213         return new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
214     }
215 
216     /**
217      * Returns the glyph index for a Unicode character. The method returns 0 if there's no
218      * such glyph in the character map.
219      * @param c the Unicode character index
220      * @return the glyph index (or 0 if the glyph is not available)
221      */
222     // [TBD] - needs optimization, i.e., change from linear search to binary search
findGlyphIndex(int c)223     public int findGlyphIndex(int c) {
224         int idx = c;
225         int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;
226 
227         // for most users the most likely glyphs are in the first cmap segments (meaning the one with
228         // the lowest unicode start values)
229         if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) {
230             return mostLikelyGlyphs[idx];
231         }
232         for (CMapSegment i : cmap) {
233             if (retIdx == 0
234                     && i.getUnicodeStart() <= idx
235                     && i.getUnicodeEnd() >= idx) {
236                 retIdx = i.getGlyphStartIndex()
237                     + idx
238                     - i.getUnicodeStart();
239                 if (idx < NUM_MOST_LIKELY_GLYPHS) {
240                     mostLikelyGlyphs[idx] = retIdx;
241                 }
242                 if (retIdx != 0) {
243                     break;
244                 }
245             }
246         }
247         return retIdx;
248     }
249 
250     /**
251      * Add a private use mapping {PU,GI} to the existing character map.
252      * N.B. Does not insert in order, merely appends to end of existing map.
253      */
addPrivateUseMapping(int pu, int gi)254     protected synchronized void addPrivateUseMapping(int pu, int gi) {
255         assert findGlyphIndex(pu) == SingleByteEncoding.NOT_FOUND_CODE_POINT;
256         cmap.add(new CMapSegment(pu, pu, gi));
257     }
258 
259     /**
260      * Given a glyph index, create a new private use mapping, augmenting the bfentries
261      * table. This is needed to accommodate the presence of an (output) glyph index in a
262      * complex script glyph substitution that does not correspond to a character in the
263      * font's CMAP.  The creation of such private use mappings is deferred until an
264      * attempt is actually made to perform the reverse lookup from the glyph index. This
265      * is necessary in order to avoid exhausting the private use space on fonts containing
266      * many such non-mapped glyph indices, if these mappings had been created statically
267      * at font load time.
268      * @param gi glyph index
269      * @returns unicode scalar value
270      */
createPrivateUseMapping(int gi)271     private int createPrivateUseMapping(int gi) {
272         while ((nextPrivateUse < 0xF900)
273                 && (findGlyphIndex(nextPrivateUse) != SingleByteEncoding.NOT_FOUND_CODE_POINT)) {
274             nextPrivateUse++;
275         }
276         if (nextPrivateUse < 0xF900) {
277             int pu = nextPrivateUse;
278             addPrivateUseMapping(pu, gi);
279             if (firstPrivate == 0) {
280                 firstPrivate = pu;
281             }
282             lastPrivate = pu;
283             numMapped++;
284             if (log.isDebugEnabled()) {
285                 log.debug("Create private use mapping from "
286                             + CharUtilities.format(pu)
287                             + " to glyph index " + gi
288                             + " in font '" + getFullName() + "'");
289             }
290             return pu;
291         } else {
292             if (firstUnmapped == 0) {
293                 firstUnmapped = gi;
294             }
295             lastUnmapped = gi;
296             numUnmapped++;
297             log.warn("Exhausted private use area: unable to map "
298                        + numUnmapped + " glyphs in glyph index range ["
299                        + firstUnmapped + "," + lastUnmapped
300                        + "] (inclusive) of font '" + getFullName() + "'");
301             return 0;
302         }
303     }
304 
305     /**
306      * Returns the Unicode scalar value that corresponds to the glyph index. If more than
307      * one correspondence exists, then the first one is returned (ordered by bfentries[]).
308      * @param gi glyph index
309      * @return unicode scalar value
310      */
311     // [TBD] - needs optimization, i.e., change from linear search to binary search
findCharacterFromGlyphIndex(int gi, boolean augment)312     private int findCharacterFromGlyphIndex(int gi, boolean augment) {
313         int cc = 0;
314         for (CMapSegment segment : cmap) {
315             int s = segment.getGlyphStartIndex();
316             int e = s + (segment.getUnicodeEnd() - segment.getUnicodeStart());
317             if ((gi >= s) && (gi <= e)) {
318                 cc = segment.getUnicodeStart() + (gi - s);
319                 break;
320             }
321         }
322         if ((cc == 0) && augment) {
323             cc = createPrivateUseMapping(gi);
324         }
325         return cc;
326     }
327 
findCharacterFromGlyphIndex(int gi)328     private int findCharacterFromGlyphIndex(int gi) {
329         return findCharacterFromGlyphIndex(gi, true);
330     }
331 
getGlyphIndices()332     protected BitSet getGlyphIndices() {
333         BitSet bitset = new BitSet();
334         bitset.set(0);
335         bitset.set(1);
336         bitset.set(2);
337         for (CMapSegment i : cmap) {
338             int start = i.getUnicodeStart();
339             int end = i.getUnicodeEnd();
340             int glyphIndex = i.getGlyphStartIndex();
341             while (start++ < end + 1) {
342                 bitset.set(glyphIndex++);
343             }
344         }
345         return bitset;
346     }
347 
getChars()348     protected char[] getChars() {
349         // the width array is set when the font is built
350         char[] chars = new char[width.length];
351         for (CMapSegment i : cmap) {
352             int start = i.getUnicodeStart();
353             int end = i.getUnicodeEnd();
354             int glyphIndex = i.getGlyphStartIndex();
355             while (start < end + 1) {
356                 chars[glyphIndex++] = (char) start++;
357             }
358         }
359         return chars;
360     }
361 
362     /** {@inheritDoc} */
363     @Override
mapChar(char c)364     public char mapChar(char c) {
365         notifyMapOperation();
366         int glyphIndex = findGlyphIndex(c);
367         if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
368             warnMissingGlyph(c);
369             if (!isOTFFile) {
370                 glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
371             }
372         }
373         if (isEmbeddable()) {
374             glyphIndex = cidSet.mapChar(glyphIndex, c);
375         }
376         if (isCID() && glyphIndex > 256) {
377             mapUnencodedChar(c);
378         }
379         return (char) glyphIndex;
380     }
381 
382     /** {@inheritDoc} */
383     @Override
mapCodePoint(int cp)384     public int mapCodePoint(int cp) {
385         notifyMapOperation();
386         int glyphIndex = findGlyphIndex(cp);
387         if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
388 
389             for (char ch : Character.toChars(cp)) {
390                 //TODO better handling for non BMP
391                 warnMissingGlyph(ch);
392             }
393 
394             if (!isOTFFile) {
395                 glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
396             }
397         }
398         if (isEmbeddable()) {
399             glyphIndex = cidSet.mapCodePoint(glyphIndex, cp);
400         }
401         return (char) glyphIndex;
402     }
403 
404     /** {@inheritDoc} */
405     @Override
hasChar(char c)406     public boolean hasChar(char c) {
407         return hasCodePoint(c);
408     }
409 
410     /** {@inheritDoc} */
411     @Override
hasCodePoint(int cp)412     public boolean hasCodePoint(int cp) {
413         return (findGlyphIndex(cp) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
414     }
415 
416     /**
417      * Sets the defaultWidth.
418      * @param defaultWidth The defaultWidth to set
419      */
setDefaultWidth(int defaultWidth)420     public void setDefaultWidth(int defaultWidth) {
421         this.defaultWidth = defaultWidth;
422     }
423 
424     /**
425      * Returns the TrueType Collection Name.
426      * @return the TrueType Collection Name
427      */
getTTCName()428     public String getTTCName() {
429         return ttcName;
430     }
431 
432     /**
433      * Sets the the TrueType Collection Name.
434      * @param ttcName the TrueType Collection Name
435      */
setTTCName(String ttcName)436     public void setTTCName(String ttcName) {
437         this.ttcName = ttcName;
438     }
439 
440     /**
441      * Sets the width array.
442      * @param wds array of widths.
443      */
setWidthArray(int[] wds)444     public void setWidthArray(int[] wds) {
445         this.width = wds;
446     }
447 
448     /**
449      * Sets the bounding boxes array.
450      * @param boundingBoxes array of bounding boxes.
451      */
setBBoxArray(Rectangle[] boundingBoxes)452     public void setBBoxArray(Rectangle[] boundingBoxes) {
453         this.boundingBoxes = boundingBoxes;
454     }
455 
456     /**
457      * Returns a Map of used Glyphs.
458      * @return Map Map of used Glyphs
459      */
getUsedGlyphs()460     public Map<Integer, Integer> getUsedGlyphs() {
461         return cidSet.getGlyphs();
462     }
463 
464     /**
465      * Returns the character from it's original glyph index in the font
466      * @param glyphIndex The original index of the character
467      * @return The character
468      */
getUnicodeFromGID(int glyphIndex)469     public char getUnicodeFromGID(int glyphIndex) {
470         return cidSet.getUnicodeFromGID(glyphIndex);
471     }
472 
473     /**
474      * Gets the original glyph index in the font from a character.
475      * @param ch The character
476      * @return The glyph index in the font
477      */
getGIDFromChar(char ch)478     public int getGIDFromChar(char ch) {
479         return cidSet.getGIDFromChar(ch);
480     }
481 
482     /**
483      * Establishes the glyph definition table.
484      * @param gdef the glyph definition table to be used by this font
485      */
setGDEF(GlyphDefinitionTable gdef)486     public void setGDEF(GlyphDefinitionTable gdef) {
487         if ((this.gdef == null) || (gdef == null)) {
488             this.gdef = gdef;
489         } else {
490             throw new IllegalStateException("font already associated with GDEF table");
491         }
492     }
493 
494     /**
495      * Obtain glyph definition table.
496      * @return glyph definition table or null if none is associated with font
497      */
getGDEF()498     public GlyphDefinitionTable getGDEF() {
499         return gdef;
500     }
501 
502     /**
503      * Establishes the glyph substitution table.
504      * @param gsub the glyph substitution table to be used by this font
505      */
setGSUB(GlyphSubstitutionTable gsub)506     public void setGSUB(GlyphSubstitutionTable gsub) {
507         if ((this.gsub == null) || (gsub == null)) {
508             this.gsub = gsub;
509         } else {
510             throw new IllegalStateException("font already associated with GSUB table");
511         }
512     }
513 
514     /**
515      * Obtain glyph substitution table.
516      * @return glyph substitution table or null if none is associated with font
517      */
getGSUB()518     public GlyphSubstitutionTable getGSUB() {
519         return gsub;
520     }
521 
522     /**
523      * Establishes the glyph positioning table.
524      * @param gpos the glyph positioning table to be used by this font
525      */
setGPOS(GlyphPositioningTable gpos)526     public void setGPOS(GlyphPositioningTable gpos) {
527         if ((this.gpos == null) || (gpos == null)) {
528             this.gpos = gpos;
529         } else {
530             throw new IllegalStateException("font already associated with GPOS table");
531         }
532     }
533 
534     /**
535      * Obtain glyph positioning table.
536      * @return glyph positioning table or null if none is associated with font
537      */
getGPOS()538     public GlyphPositioningTable getGPOS() {
539         return gpos;
540     }
541 
542     /** {@inheritDoc} */
performsSubstitution()543     public boolean performsSubstitution() {
544         return gsub != null;
545     }
546 
547     /** {@inheritDoc} */
performSubstitution(CharSequence charSequence, String script, String language, List associations, boolean retainControls)548     public CharSequence performSubstitution(CharSequence charSequence, String script, String language,
549                                             List associations, boolean retainControls) {
550         if (gsub != null) {
551             charSequence = gsub.preProcess(charSequence, script, this, associations);
552             GlyphSequence glyphSequence = charSequenceToGlyphSequence(charSequence, associations);
553             GlyphSequence glyphSequenceSubstituted = gsub.substitute(glyphSequence, script, language);
554             if (associations != null) {
555                 associations.clear();
556                 associations.addAll(glyphSequenceSubstituted.getAssociations());
557             }
558             if (!retainControls) {
559                 glyphSequenceSubstituted = elideControls(glyphSequenceSubstituted);
560             }
561             // may not contains all the characters that were in charSequence.
562             // see: #createPrivateUseMapping(int gi)
563             return mapGlyphsToChars(glyphSequenceSubstituted);
564         } else {
565             return charSequence;
566         }
567     }
568 
charSequenceToGlyphSequence(CharSequence charSequence, List associations)569     public GlyphSequence charSequenceToGlyphSequence(CharSequence charSequence, List associations) {
570         CharSequence normalizedCharSequence = normalize(charSequence, associations);
571         return mapCharsToGlyphs(normalizedCharSequence, associations);
572     }
573 
574     /** {@inheritDoc} */
reorderCombiningMarks( CharSequence cs, int[][] gpa, String script, String language, List associations)575     public CharSequence reorderCombiningMarks(
576         CharSequence cs, int[][] gpa, String script, String language, List associations) {
577         if (gdef != null) {
578             GlyphSequence igs = mapCharsToGlyphs(cs, associations);
579             GlyphSequence ogs = gdef.reorderCombiningMarks(igs, getUnscaledWidths(igs), gpa, script, language);
580             if (associations != null) {
581                 associations.clear();
582                 associations.addAll(ogs.getAssociations());
583             }
584             CharSequence ocs = mapGlyphsToChars(ogs);
585             return ocs;
586         } else {
587             return cs;
588         }
589     }
590 
getUnscaledWidths(GlyphSequence gs)591     protected int[] getUnscaledWidths(GlyphSequence gs) {
592         int[] widths = new int[gs.getGlyphCount()];
593         for (int i = 0, n = widths.length; i < n; ++i) {
594             if (i < width.length) {
595                 widths[i] = width[i];
596             }
597         }
598         return widths;
599     }
600 
601     /** {@inheritDoc} */
performsPositioning()602     public boolean performsPositioning() {
603         return gpos != null;
604     }
605 
606     /** {@inheritDoc} */
607     public int[][]
performPositioning(CharSequence cs, String script, String language, int fontSize)608         performPositioning(CharSequence cs, String script, String language, int fontSize) {
609         if (gpos != null) {
610             GlyphSequence gs = mapCharsToGlyphs(cs, null);
611             int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
612             if (gpos.position(gs, script, language, fontSize, this.width, adjustments)) {
613                 return scaleAdjustments(adjustments, fontSize);
614             } else {
615                 return null;
616             }
617         } else {
618             return null;
619         }
620     }
621 
622     /** {@inheritDoc} */
performPositioning(CharSequence cs, String script, String language)623     public int[][] performPositioning(CharSequence cs, String script, String language) {
624         throw new UnsupportedOperationException();
625     }
626 
627 
scaleAdjustments(int[][] adjustments, int fontSize)628     private int[][] scaleAdjustments(int[][] adjustments, int fontSize) {
629         if (adjustments != null) {
630             for (int[] gpa : adjustments) {
631                 for (int k = 0; k < 4; k++) {
632                     gpa[k] = (gpa[k] * fontSize) / 1000;
633                 }
634             }
635             return adjustments;
636         } else {
637             return null;
638         }
639     }
640 
641     /**
642      * Map sequence CS, comprising a sequence of UTF-16 encoded Unicode Code Points, to
643      * an output character sequence GS, comprising a sequence of Glyph Indices. N.B. Unlike
644      * mapChar(), this method does not make use of embedded subset encodings.
645      * @param cs a CharSequence containing UTF-16 encoded Unicode characters
646      * @returns a CharSequence containing glyph indices
647      */
mapCharsToGlyphs(CharSequence cs, List associations)648     private GlyphSequence mapCharsToGlyphs(CharSequence cs, List associations) {
649         IntBuffer cb = IntBuffer.allocate(cs.length());
650         IntBuffer gb = IntBuffer.allocate(cs.length());
651         int gi;
652         int giMissing = findGlyphIndex(Typeface.NOT_FOUND);
653         for (int i = 0, n = cs.length(); i < n; i++) {
654             int cc = cs.charAt(i);
655             if ((cc >= 0xD800) && (cc < 0xDC00)) {
656                 if ((i + 1) < n) {
657                     int sh = cc;
658                     int sl = cs.charAt(++i);
659                     if ((sl >= 0xDC00) && (sl < 0xE000)) {
660                         cc = 0x10000 + ((sh - 0xD800) << 10) + ((sl - 0xDC00) << 0);
661                     } else {
662                         throw new IllegalArgumentException(
663                             "ill-formed UTF-16 sequence, "
664                                + "contains isolated high surrogate at index " + i);
665                     }
666                 } else {
667                     throw new IllegalArgumentException(
668                         "ill-formed UTF-16 sequence, "
669                           + "contains isolated high surrogate at end of sequence");
670                 }
671             } else if ((cc >= 0xDC00) && (cc < 0xE000)) {
672                 throw new IllegalArgumentException(
673                     "ill-formed UTF-16 sequence, "
674                       + "contains isolated low surrogate at index " + i);
675             }
676             notifyMapOperation();
677             gi = findGlyphIndex(cc);
678             if (gi == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
679                 warnMissingGlyph((char) cc);
680                 gi = giMissing;
681             }
682             cb.put(cc);
683             gb.put(gi);
684         }
685         cb.flip();
686         gb.flip();
687         if ((associations != null) && (associations.size() == cs.length())) {
688             associations = new java.util.ArrayList(associations);
689         } else {
690             associations = null;
691         }
692         return new GlyphSequence(cb, gb, associations);
693     }
694 
695     /**
696      * Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
697      * comprising a sequence of UTF-16 encoded Unicode Code Points.
698      * @param gs a GlyphSequence containing glyph indices
699      * @returns a CharSequence containing UTF-16 encoded Unicode characters
700      */
mapGlyphsToChars(GlyphSequence gs)701     private CharSequence mapGlyphsToChars(GlyphSequence gs) {
702         int ng = gs.getGlyphCount();
703         int ccMissing = Typeface.NOT_FOUND;
704         List<Character> chars = new ArrayList<Character>(gs.getUTF16CharacterCount());
705 
706         for (int i = 0, n = ng; i < n; i++) {
707             int gi = gs.getGlyph(i);
708             int cc = findCharacterFromGlyphIndex(gi);
709             if ((cc == 0) || (cc > 0x10FFFF)) {
710                 cc = ccMissing;
711                 log.warn("Unable to map glyph index " + gi
712                          + " to Unicode scalar in font '"
713                          + getFullName() + "', substituting missing character '"
714                          + (char) cc + "'");
715             }
716             if (cc > 0x00FFFF) {
717                 int sh;
718                 int sl;
719                 cc -= 0x10000;
720                 sh = ((cc >> 10) & 0x3FF) + 0xD800;
721                 sl = ((cc >>  0) & 0x3FF) + 0xDC00;
722                 chars.add((char) sh);
723                 chars.add((char) sl);
724             } else {
725                 chars.add((char) cc);
726             }
727         }
728 
729         CharBuffer cb = CharBuffer.allocate(chars.size());
730 
731         for (char c : chars) {
732             cb.put(c);
733         }
734 
735         cb.flip();
736         return cb;
737     }
738 
normalize(CharSequence cs, List associations)739     private CharSequence normalize(CharSequence cs, List associations) {
740         return hasDecomposable(cs) ? decompose(cs, associations) : cs;
741     }
742 
hasDecomposable(CharSequence cs)743     private boolean hasDecomposable(CharSequence cs) {
744         for (int i = 0, n = cs.length(); i < n; i++) {
745             int cc = cs.charAt(i);
746             if (CharNormalize.isDecomposable(cc)) {
747                 return true;
748             }
749         }
750         return false;
751     }
752 
decompose(CharSequence cs, List associations)753     private CharSequence decompose(CharSequence cs, List associations) {
754         StringBuffer sb = new StringBuffer(cs.length());
755         int[] daBuffer = new int[CharNormalize.maximumDecompositionLength()];
756         for (int i = 0, n = cs.length(); i < n; i++) {
757             int cc = cs.charAt(i);
758             int[] da = CharNormalize.decompose(cc, daBuffer);
759             for (int aDa : da) {
760                 if (aDa > 0) {
761                     sb.append((char) aDa);
762                 } else {
763                     break;
764                 }
765             }
766         }
767         return sb;
768     }
769 
770     /**
771      * Removes the glyphs associated with elidable control characters.
772      * All the characters in an association must be elidable in order
773      * to remove the corresponding glyph.
774      *
775      * @param gs GlyphSequence that may contains the elidable glyphs
776      * @return GlyphSequence without the elidable glyphs
777      */
elideControls(GlyphSequence gs)778     private static GlyphSequence elideControls(GlyphSequence gs) {
779         if (hasElidableControl(gs)) {
780             int[] ca = gs.getCharacterArray(false);
781             IntBuffer ngb = IntBuffer.allocate(gs.getGlyphCount());
782             List nal = new java.util.ArrayList(gs.getGlyphCount());
783             for (int i = 0, n = gs.getGlyphCount(); i < n; ++i) {
784                 CharAssociation a = gs.getAssociation(i);
785                 int s = a.getStart();
786                 int e = a.getEnd();
787                 while (s < e) {
788                     int ch = ca [ s ];
789                     if (!isElidableControl(ch)) {
790                         break;
791                     } else {
792                         ++s;
793                     }
794                 }
795                 // If there is at least one non-elidable character in the char
796                 // sequence then the glyph/association is kept.
797                 if (s != e) {
798                     ngb.put(gs.getGlyph(i));
799                     nal.add(a);
800                 }
801             }
802             ngb.flip();
803             return new GlyphSequence(gs.getCharacters(), ngb, nal, gs.getPredications());
804         } else {
805             return gs;
806         }
807     }
808 
hasElidableControl(GlyphSequence gs)809     private static boolean hasElidableControl(GlyphSequence gs) {
810         int[] ca = gs.getCharacterArray(false);
811         for (int ch : ca) {
812             if (isElidableControl(ch)) {
813                 return true;
814             }
815         }
816         return false;
817     }
818 
isElidableControl(int ch)819     private static boolean isElidableControl(int ch) {
820         if (ch < 0x0020) {
821             return true;
822         } else if ((ch >= 0x80) && (ch < 0x00A0)) {
823             return true;
824         } else if ((ch >= 0x2000) && (ch <= 0x206F)) {
825             if ((ch >= 0x200B) && (ch <= 0x200F)) {
826                 return true;
827             } else if ((ch >= 0x2028) && (ch <= 0x202E)) {
828                 return true;
829             } else if (ch >= 0x2066) {
830                 return true;
831             } else {
832                 return ch == 0x2060;
833             }
834         } else {
835             return false;
836         }
837     }
838 
839     @Override
hasFeature(int tableType, String script, String language, String feature)840     public boolean hasFeature(int tableType, String script, String language, String feature) {
841         GlyphTable table;
842         if (tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) {
843             table = getGSUB();
844         } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) {
845             table = getGPOS();
846         } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) {
847             table = getGDEF();
848         } else {
849             table = null;
850         }
851         return (table != null) && table.hasFeature(script, language, feature);
852     }
853 
getWidthsMap()854     public Map<Integer, Integer> getWidthsMap() {
855         return null;
856     }
857 
getCmapStream()858     public InputStream getCmapStream() {
859         return null;
860     }
861 
getSVG(char c)862     public SVGGlyphData getSVG(char c) {
863         int gid = findGlyphIndex(c);
864         return svgs.get(gid);
865     }
866 }
867 
868