123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872 |
- /*
- * 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.awt.Rectangle;
- import java.io.InputStream;
- import java.nio.Buffer;
- import java.nio.CharBuffer;
- import java.nio.IntBuffer;
- import java.util.ArrayList;
- import java.util.BitSet;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.apps.io.InternalResourceResolver;
- import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
- import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
- import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
- import org.apache.fop.complexscripts.fonts.GlyphTable;
- import org.apache.fop.complexscripts.fonts.Positionable;
- import org.apache.fop.complexscripts.fonts.Substitutable;
- import org.apache.fop.complexscripts.util.CharAssociation;
- import org.apache.fop.complexscripts.util.CharNormalize;
- import org.apache.fop.complexscripts.util.GlyphSequence;
- import org.apache.fop.fonts.truetype.SVGGlyphData;
- import org.apache.fop.util.CharUtilities;
-
- /**
- * Generic MultiByte (CID) font
- */
- public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
-
- /** logging instance */
- private static final Log log
- = LogFactory.getLog(MultiByteFont.class);
-
- private String ttcName;
- private String encoding = "Identity-H";
-
- private int defaultWidth;
- private CIDFontType cidType = CIDFontType.CIDTYPE2;
-
- protected final CIDSet cidSet;
-
- /* advanced typographic support */
- private GlyphDefinitionTable gdef;
- private GlyphSubstitutionTable gsub;
- private GlyphPositioningTable gpos;
-
- /* dynamic private use (character) mappings */
- private int numMapped;
- private int numUnmapped;
- private int nextPrivateUse = 0xE000;
- private int firstPrivate;
- private int lastPrivate;
- private int firstUnmapped;
- private int lastUnmapped;
-
- /** Contains the character bounding boxes for all characters in the font */
- protected Rectangle[] boundingBoxes;
-
- private boolean isOTFFile;
-
- // since for most users the most likely glyphs are in the first cmap segments we store their mapping.
- private static final int NUM_MOST_LIKELY_GLYPHS = 256;
- private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS];
-
- //A map to store each used glyph from the CID set against the glyph name.
- private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>();
-
- /**
- * Default constructor
- */
- public MultiByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
- super(resourceResolver);
- setFontType(FontType.TYPE0);
- setEmbeddingMode(embeddingMode);
- if (embeddingMode != EmbeddingMode.FULL) {
- cidSet = new CIDSubset(this);
- } else {
- cidSet = new CIDFull(this);
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public int getDefaultWidth() {
- return defaultWidth;
- }
-
- /** {@inheritDoc} */
- @Override
- public String getRegistry() {
- return "Adobe";
- }
-
- /** {@inheritDoc} */
- @Override
- public String getOrdering() {
- return "UCS";
- }
-
- /** {@inheritDoc} */
- @Override
- public int getSupplement() {
- return 0;
- }
-
- /** {@inheritDoc} */
- @Override
- public CIDFontType getCIDType() {
- return cidType;
- }
-
- public void setIsOTFFile(boolean isOTFFile) {
- this.isOTFFile = isOTFFile;
- }
-
- public boolean isOTFFile() {
- return this.isOTFFile;
- }
-
- /**
- * Sets the CIDType.
- * @param cidType The cidType to set
- */
- public void setCIDType(CIDFontType cidType) {
- this.cidType = cidType;
- }
-
- /** {@inheritDoc} */
- @Override
- public String getEmbedFontName() {
- if (isEmbeddable()) {
- return FontUtil.stripWhiteSpace(super.getFontName());
- } else {
- return super.getFontName();
- }
- }
-
- /** {@inheritDoc} */
- public boolean isEmbeddable() {
- return !(getEmbedFileURI() == null && getEmbedResourceName() == null);
- }
-
- public boolean isSubsetEmbedded() {
- if (getEmbeddingMode() == EmbeddingMode.FULL) {
- return false;
- }
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- public CIDSet getCIDSet() {
- return this.cidSet;
- }
-
- public void mapUsedGlyphName(int gid, String value) {
- usedGlyphNames.put(gid, value);
- }
-
- public LinkedHashMap<Integer, String> getUsedGlyphNames() {
- return usedGlyphNames;
- }
-
- /** {@inheritDoc} */
- @Override
- public String getEncodingName() {
- return encoding;
- }
-
- /** {@inheritDoc} */
- public int getWidth(int i, int size) {
- if (isEmbeddable()) {
- int glyphIndex = cidSet.getOriginalGlyphIndex(i);
- return size * width[glyphIndex];
- } else {
- return size * width[i];
- }
- }
-
- /** {@inheritDoc} */
- public int[] getWidths() {
- int[] arr = new int[width.length];
- System.arraycopy(width, 0, arr, 0, width.length);
- return arr;
- }
-
- public Rectangle getBoundingBox(int glyphIndex, int size) {
- int index = isEmbeddable() ? cidSet.getOriginalGlyphIndex(glyphIndex) : glyphIndex;
- Rectangle bbox = boundingBoxes[index];
- return new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
- }
-
- /**
- * Returns the glyph index for a Unicode character. The method returns 0 if there's no
- * such glyph in the character map.
- * @param c the Unicode character index
- * @return the glyph index (or 0 if the glyph is not available)
- */
- // [TBD] - needs optimization, i.e., change from linear search to binary search
- public int findGlyphIndex(int c) {
- int idx = c;
- int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;
-
- // for most users the most likely glyphs are in the first cmap segments (meaning the one with
- // the lowest unicode start values)
- if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) {
- return mostLikelyGlyphs[idx];
- }
- for (CMapSegment i : cmap) {
- if (retIdx == 0
- && i.getUnicodeStart() <= idx
- && i.getUnicodeEnd() >= idx) {
- retIdx = i.getGlyphStartIndex()
- + idx
- - i.getUnicodeStart();
- if (idx < NUM_MOST_LIKELY_GLYPHS) {
- mostLikelyGlyphs[idx] = retIdx;
- }
- if (retIdx != 0) {
- break;
- }
- }
- }
- return retIdx;
- }
-
- /**
- * Add a private use mapping {PU,GI} to the existing character map.
- * N.B. Does not insert in order, merely appends to end of existing map.
- */
- protected synchronized void addPrivateUseMapping(int pu, int gi) {
- assert findGlyphIndex(pu) == SingleByteEncoding.NOT_FOUND_CODE_POINT;
- cmap.add(new CMapSegment(pu, pu, gi));
- }
-
- /**
- * Given a glyph index, create a new private use mapping, augmenting the bfentries
- * table. This is needed to accommodate the presence of an (output) glyph index in a
- * complex script glyph substitution that does not correspond to a character in the
- * font's CMAP. The creation of such private use mappings is deferred until an
- * attempt is actually made to perform the reverse lookup from the glyph index. This
- * is necessary in order to avoid exhausting the private use space on fonts containing
- * many such non-mapped glyph indices, if these mappings had been created statically
- * at font load time.
- * @param gi glyph index
- * @returns unicode scalar value
- */
- private int createPrivateUseMapping(int gi) {
- while ((nextPrivateUse < 0xF900)
- && (findGlyphIndex(nextPrivateUse) != SingleByteEncoding.NOT_FOUND_CODE_POINT)) {
- nextPrivateUse++;
- }
- if (nextPrivateUse < 0xF900) {
- int pu = nextPrivateUse;
- addPrivateUseMapping(pu, gi);
- if (firstPrivate == 0) {
- firstPrivate = pu;
- }
- lastPrivate = pu;
- numMapped++;
- if (log.isDebugEnabled()) {
- log.debug("Create private use mapping from "
- + CharUtilities.format(pu)
- + " to glyph index " + gi
- + " in font '" + getFullName() + "'");
- }
- return pu;
- } else {
- if (firstUnmapped == 0) {
- firstUnmapped = gi;
- }
- lastUnmapped = gi;
- numUnmapped++;
- log.warn("Exhausted private use area: unable to map "
- + numUnmapped + " glyphs in glyph index range ["
- + firstUnmapped + "," + lastUnmapped
- + "] (inclusive) of font '" + getFullName() + "'");
- return 0;
- }
- }
-
- /**
- * Returns the Unicode scalar value that corresponds to the glyph index. If more than
- * one correspondence exists, then the first one is returned (ordered by bfentries[]).
- * @param gi glyph index
- * @return unicode scalar value
- */
- // [TBD] - needs optimization, i.e., change from linear search to binary search
- private int findCharacterFromGlyphIndex(int gi, boolean augment) {
- int cc = 0;
- for (CMapSegment segment : cmap) {
- int s = segment.getGlyphStartIndex();
- int e = s + (segment.getUnicodeEnd() - segment.getUnicodeStart());
- if ((gi >= s) && (gi <= e)) {
- cc = segment.getUnicodeStart() + (gi - s);
- break;
- }
- }
- if ((cc == 0) && augment) {
- cc = createPrivateUseMapping(gi);
- }
- return cc;
- }
-
- private int findCharacterFromGlyphIndex(int gi) {
- return findCharacterFromGlyphIndex(gi, true);
- }
-
- protected BitSet getGlyphIndices() {
- BitSet bitset = new BitSet();
- bitset.set(0);
- bitset.set(1);
- bitset.set(2);
- for (CMapSegment i : cmap) {
- int start = i.getUnicodeStart();
- int end = i.getUnicodeEnd();
- int glyphIndex = i.getGlyphStartIndex();
- while (start++ < end + 1) {
- bitset.set(glyphIndex++);
- }
- }
- return bitset;
- }
-
- protected char[] getChars() {
- // the width array is set when the font is built
- char[] chars = new char[width.length];
- for (CMapSegment i : cmap) {
- int start = i.getUnicodeStart();
- int end = i.getUnicodeEnd();
- int glyphIndex = i.getGlyphStartIndex();
- while (start < end + 1) {
- chars[glyphIndex++] = (char) start++;
- }
- }
- return chars;
- }
-
- /** {@inheritDoc} */
- @Override
- public char mapChar(char c) {
- notifyMapOperation();
- int glyphIndex = findGlyphIndex(c);
- if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- warnMissingGlyph(c);
- if (!isOTFFile) {
- glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
- }
- }
- if (isEmbeddable()) {
- glyphIndex = cidSet.mapChar(glyphIndex, c);
- }
- if (isCID() && glyphIndex > 256) {
- mapUnencodedChar(c);
- }
- return (char) glyphIndex;
- }
-
- /** {@inheritDoc} */
- @Override
- public int mapCodePoint(int cp) {
- notifyMapOperation();
- int glyphIndex = findGlyphIndex(cp);
- if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
-
- for (char ch : Character.toChars(cp)) {
- //TODO better handling for non BMP
- warnMissingGlyph(ch);
- }
-
- if (!isOTFFile) {
- glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
- }
- }
- if (isEmbeddable()) {
- glyphIndex = cidSet.mapCodePoint(glyphIndex, cp);
- }
- return (char) glyphIndex;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean hasChar(char c) {
- return hasCodePoint(c);
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean hasCodePoint(int cp) {
- return (findGlyphIndex(cp) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
- }
-
- /**
- * Sets the defaultWidth.
- * @param defaultWidth The defaultWidth to set
- */
- public void setDefaultWidth(int defaultWidth) {
- this.defaultWidth = defaultWidth;
- }
-
- /**
- * Returns the TrueType Collection Name.
- * @return the TrueType Collection Name
- */
- public String getTTCName() {
- return ttcName;
- }
-
- /**
- * Sets the the TrueType Collection Name.
- * @param ttcName the TrueType Collection Name
- */
- public void setTTCName(String ttcName) {
- this.ttcName = ttcName;
- }
-
- /**
- * Sets the width array.
- * @param wds array of widths.
- */
- public void setWidthArray(int[] wds) {
- this.width = wds;
- }
-
- /**
- * Sets the bounding boxes array.
- * @param boundingBoxes array of bounding boxes.
- */
- public void setBBoxArray(Rectangle[] boundingBoxes) {
- this.boundingBoxes = boundingBoxes;
- }
-
- /**
- * Returns a Map of used Glyphs.
- * @return Map Map of used Glyphs
- */
- public Map<Integer, Integer> getUsedGlyphs() {
- return cidSet.getGlyphs();
- }
-
- /**
- * Returns the character from it's original glyph index in the font
- * @param glyphIndex The original index of the character
- * @return The character
- */
- public char getUnicodeFromGID(int glyphIndex) {
- return cidSet.getUnicodeFromGID(glyphIndex);
- }
-
- /**
- * Gets the original glyph index in the font from a character.
- * @param ch The character
- * @return The glyph index in the font
- */
- public int getGIDFromChar(char ch) {
- return cidSet.getGIDFromChar(ch);
- }
-
- /**
- * Establishes the glyph definition table.
- * @param gdef the glyph definition table to be used by this font
- */
- public void setGDEF(GlyphDefinitionTable gdef) {
- if ((this.gdef == null) || (gdef == null)) {
- this.gdef = gdef;
- } else {
- throw new IllegalStateException("font already associated with GDEF table");
- }
- }
-
- /**
- * Obtain glyph definition table.
- * @return glyph definition table or null if none is associated with font
- */
- public GlyphDefinitionTable getGDEF() {
- return gdef;
- }
-
- /**
- * Establishes the glyph substitution table.
- * @param gsub the glyph substitution table to be used by this font
- */
- public void setGSUB(GlyphSubstitutionTable gsub) {
- if ((this.gsub == null) || (gsub == null)) {
- this.gsub = gsub;
- } else {
- throw new IllegalStateException("font already associated with GSUB table");
- }
- }
-
- /**
- * Obtain glyph substitution table.
- * @return glyph substitution table or null if none is associated with font
- */
- public GlyphSubstitutionTable getGSUB() {
- return gsub;
- }
-
- /**
- * Establishes the glyph positioning table.
- * @param gpos the glyph positioning table to be used by this font
- */
- public void setGPOS(GlyphPositioningTable gpos) {
- if ((this.gpos == null) || (gpos == null)) {
- this.gpos = gpos;
- } else {
- throw new IllegalStateException("font already associated with GPOS table");
- }
- }
-
- /**
- * Obtain glyph positioning table.
- * @return glyph positioning table or null if none is associated with font
- */
- public GlyphPositioningTable getGPOS() {
- return gpos;
- }
-
- /** {@inheritDoc} */
- public boolean performsSubstitution() {
- return gsub != null;
- }
-
- /** {@inheritDoc} */
- public CharSequence performSubstitution(CharSequence charSequence, String script, String language,
- List associations, boolean retainControls) {
- if (gsub != null) {
- charSequence = gsub.preProcess(charSequence, script, this, associations);
- GlyphSequence glyphSequence = charSequenceToGlyphSequence(charSequence, associations);
- GlyphSequence glyphSequenceSubstituted = gsub.substitute(glyphSequence, script, language);
- if (associations != null) {
- associations.clear();
- associations.addAll(glyphSequenceSubstituted.getAssociations());
- }
- if (!retainControls) {
- glyphSequenceSubstituted = elideControls(glyphSequenceSubstituted);
- }
- // may not contains all the characters that were in charSequence.
- // see: #createPrivateUseMapping(int gi)
- return mapGlyphsToChars(glyphSequenceSubstituted);
- } else {
- return charSequence;
- }
- }
-
- public GlyphSequence charSequenceToGlyphSequence(CharSequence charSequence, List associations) {
- CharSequence normalizedCharSequence = normalize(charSequence, associations);
- return mapCharsToGlyphs(normalizedCharSequence, associations);
- }
-
- /** {@inheritDoc} */
- public CharSequence reorderCombiningMarks(
- CharSequence cs, int[][] gpa, String script, String language, List associations) {
- if (gdef != null) {
- GlyphSequence igs = mapCharsToGlyphs(cs, associations);
- GlyphSequence ogs = gdef.reorderCombiningMarks(igs, getUnscaledWidths(igs), gpa, script, language);
- if (associations != null) {
- associations.clear();
- associations.addAll(ogs.getAssociations());
- }
- CharSequence ocs = mapGlyphsToChars(ogs);
- return ocs;
- } else {
- return cs;
- }
- }
-
- protected int[] getUnscaledWidths(GlyphSequence gs) {
- int[] widths = new int[gs.getGlyphCount()];
- for (int i = 0, n = widths.length; i < n; ++i) {
- if (i < width.length) {
- widths[i] = width[i];
- }
- }
- return widths;
- }
-
- /** {@inheritDoc} */
- public boolean performsPositioning() {
- return gpos != null;
- }
-
- /** {@inheritDoc} */
- public int[][]
- performPositioning(CharSequence cs, String script, String language, int fontSize) {
- if (gpos != null) {
- GlyphSequence gs = mapCharsToGlyphs(cs, null);
- int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
- if (gpos.position(gs, script, language, fontSize, this.width, adjustments)) {
- return scaleAdjustments(adjustments, fontSize);
- } else {
- return null;
- }
- } else {
- return null;
- }
- }
-
- /** {@inheritDoc} */
- public int[][] performPositioning(CharSequence cs, String script, String language) {
- throw new UnsupportedOperationException();
- }
-
-
- private int[][] scaleAdjustments(int[][] adjustments, int fontSize) {
- if (adjustments != null) {
- for (int[] gpa : adjustments) {
- for (int k = 0; k < 4; k++) {
- gpa[k] = (gpa[k] * fontSize) / 1000;
- }
- }
- return adjustments;
- } else {
- return null;
- }
- }
-
- /**
- * Map sequence CS, comprising a sequence of UTF-16 encoded Unicode Code Points, to
- * an output character sequence GS, comprising a sequence of Glyph Indices. N.B. Unlike
- * mapChar(), this method does not make use of embedded subset encodings.
- * @param cs a CharSequence containing UTF-16 encoded Unicode characters
- * @returns a CharSequence containing glyph indices
- */
- private GlyphSequence mapCharsToGlyphs(CharSequence cs, List associations) {
- IntBuffer cb = IntBuffer.allocate(cs.length());
- IntBuffer gb = IntBuffer.allocate(cs.length());
- int gi;
- int giMissing = findGlyphIndex(Typeface.NOT_FOUND);
- for (int i = 0, n = cs.length(); i < n; i++) {
- int cc = cs.charAt(i);
- if ((cc >= 0xD800) && (cc < 0xDC00)) {
- if ((i + 1) < n) {
- int sh = cc;
- int sl = cs.charAt(++i);
- if ((sl >= 0xDC00) && (sl < 0xE000)) {
- cc = 0x10000 + ((sh - 0xD800) << 10) + ((sl - 0xDC00) << 0);
- } else {
- throw new IllegalArgumentException(
- "ill-formed UTF-16 sequence, "
- + "contains isolated high surrogate at index " + i);
- }
- } else {
- throw new IllegalArgumentException(
- "ill-formed UTF-16 sequence, "
- + "contains isolated high surrogate at end of sequence");
- }
- } else if ((cc >= 0xDC00) && (cc < 0xE000)) {
- throw new IllegalArgumentException(
- "ill-formed UTF-16 sequence, "
- + "contains isolated low surrogate at index " + i);
- }
- notifyMapOperation();
- gi = findGlyphIndex(cc);
- if (gi == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- warnMissingGlyph((char) cc);
- gi = giMissing;
- }
- cb.put(cc);
- gb.put(gi);
- }
- Buffer cbBase = cb;
- cbBase.flip();
- Buffer gbBase = gb;
- gbBase.flip();
- if ((associations != null) && (associations.size() == cs.length())) {
- associations = new java.util.ArrayList(associations);
- } else {
- associations = null;
- }
- return new GlyphSequence(cb, gb, associations);
- }
-
- /**
- * Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
- * comprising a sequence of UTF-16 encoded Unicode Code Points.
- * @param gs a GlyphSequence containing glyph indices
- * @returns a CharSequence containing UTF-16 encoded Unicode characters
- */
- private CharSequence mapGlyphsToChars(GlyphSequence gs) {
- int ng = gs.getGlyphCount();
- int ccMissing = Typeface.NOT_FOUND;
- List<Character> chars = new ArrayList<Character>(gs.getUTF16CharacterCount());
-
- for (int i = 0, n = ng; i < n; i++) {
- int gi = gs.getGlyph(i);
- int cc = findCharacterFromGlyphIndex(gi);
- if ((cc == 0) || (cc > 0x10FFFF)) {
- cc = ccMissing;
- log.warn("Unable to map glyph index " + gi
- + " to Unicode scalar in font '"
- + getFullName() + "', substituting missing character '"
- + (char) cc + "'");
- }
- if (cc > 0x00FFFF) {
- int sh;
- int sl;
- cc -= 0x10000;
- sh = ((cc >> 10) & 0x3FF) + 0xD800;
- sl = ((cc >> 0) & 0x3FF) + 0xDC00;
- chars.add((char) sh);
- chars.add((char) sl);
- } else {
- chars.add((char) cc);
- }
- }
-
- CharBuffer cb = CharBuffer.allocate(chars.size());
-
- for (char c : chars) {
- cb.put(c);
- }
-
- Buffer cbBase = cb;
- cbBase.flip();
- return cb;
- }
-
- private CharSequence normalize(CharSequence cs, List associations) {
- return hasDecomposable(cs) ? decompose(cs, associations) : cs;
- }
-
- private boolean hasDecomposable(CharSequence cs) {
- for (int i = 0, n = cs.length(); i < n; i++) {
- int cc = cs.charAt(i);
- if (CharNormalize.isDecomposable(cc)) {
- return true;
- }
- }
- return false;
- }
-
- private CharSequence decompose(CharSequence cs, List associations) {
- StringBuffer sb = new StringBuffer(cs.length());
- int[] daBuffer = new int[CharNormalize.maximumDecompositionLength()];
- for (int i = 0, n = cs.length(); i < n; i++) {
- int cc = cs.charAt(i);
- int[] da = CharNormalize.decompose(cc, daBuffer);
- for (int aDa : da) {
- if (aDa > 0) {
- sb.append((char) aDa);
- } else {
- break;
- }
- }
- }
- return sb;
- }
-
- /**
- * Removes the glyphs associated with elidable control characters.
- * All the characters in an association must be elidable in order
- * to remove the corresponding glyph.
- *
- * @param gs GlyphSequence that may contains the elidable glyphs
- * @return GlyphSequence without the elidable glyphs
- */
- private static GlyphSequence elideControls(GlyphSequence gs) {
- if (hasElidableControl(gs)) {
- int[] ca = gs.getCharacterArray(false);
- IntBuffer ngb = IntBuffer.allocate(gs.getGlyphCount());
- List nal = new java.util.ArrayList(gs.getGlyphCount());
- for (int i = 0, n = gs.getGlyphCount(); i < n; ++i) {
- CharAssociation a = gs.getAssociation(i);
- int s = a.getStart();
- int e = a.getEnd();
- while (s < e) {
- int ch = ca [ s ];
- if (!isElidableControl(ch)) {
- break;
- } else {
- ++s;
- }
- }
- // If there is at least one non-elidable character in the char
- // sequence then the glyph/association is kept.
- if (s != e) {
- ngb.put(gs.getGlyph(i));
- nal.add(a);
- }
- }
- Buffer ngbBase = ngb;
- ngbBase.flip();
- return new GlyphSequence(gs.getCharacters(), ngb, nal, gs.getPredications());
- } else {
- return gs;
- }
- }
-
- private static boolean hasElidableControl(GlyphSequence gs) {
- int[] ca = gs.getCharacterArray(false);
- for (int ch : ca) {
- if (isElidableControl(ch)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isElidableControl(int ch) {
- if (ch < 0x0020) {
- return true;
- } else if ((ch >= 0x80) && (ch < 0x00A0)) {
- return true;
- } else if ((ch >= 0x2000) && (ch <= 0x206F)) {
- if ((ch >= 0x200B) && (ch <= 0x200F)) {
- return true;
- } else if ((ch >= 0x2028) && (ch <= 0x202E)) {
- return true;
- } else if (ch >= 0x2066) {
- return true;
- } else {
- return ch == 0x2060;
- }
- } else {
- return false;
- }
- }
-
- @Override
- public boolean hasFeature(int tableType, String script, String language, String feature) {
- GlyphTable table;
- if (tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) {
- table = getGSUB();
- } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) {
- table = getGPOS();
- } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) {
- table = getGDEF();
- } else {
- table = null;
- }
- return (table != null) && table.hasFeature(script, language, feature);
- }
-
- public Map<Integer, Integer> getWidthsMap() {
- return null;
- }
-
- public InputStream getCmapStream() {
- return null;
- }
-
- public SVGGlyphData getSVG(int c) {
- int gid = findGlyphIndex(c);
- return svgs.get(gid);
- }
- }
|