Java tutorial
/* * 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: MultiByteFont.java 1357883 2012-07-05 20:29:53Z gadams $ */ package org.apache.fop.fonts; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.Positionable; import org.apache.fop.complexscripts.fonts.Substitutable; import org.apache.fop.complexscripts.util.GlyphSequence; 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 // CSOK: ConstantNameCheck = LogFactory.getLog(MultiByteFont.class); private String ttcName = null; private String encoding = "Identity-H"; private int defaultWidth = 0; private CIDFontType cidType = CIDFontType.CIDTYPE2; private CIDSubset subset = new CIDSubset(); /* 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; /** * Default constructor */ public MultiByteFont() { subset.setupFirstGlyph(); setFontType(FontType.TYPE0); } /** {@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; } /** * 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 !(getEmbedFileName() == null && getEmbedResourceName() == null); } public boolean isSubsetEmbedded() { return true; } /** {@inheritDoc} */ @Override public CIDSubset getCIDSubset() { return this.subset; } /** {@inheritDoc} */ @Override public String getEncodingName() { return encoding; } /** {@inheritDoc} */ public int getWidth(int i, int size) { if (isEmbeddable()) { int glyphIndex = subset.getGlyphIndexForSubsetIndex(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; } /** * 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 private int findGlyphIndex(int c) { int idx = c; int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT; for (int i = 0; (i < cmap.length) && retIdx == 0; i++) { if (cmap[i].getUnicodeStart() <= idx && cmap[i].getUnicodeEnd() >= idx) { retIdx = cmap[i].getGlyphStartIndex() + idx - cmap[i].getUnicodeStart(); } } 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. */ private synchronized void addPrivateUseMapping(int pu, int gi) { assert findGlyphIndex(pu) == SingleByteEncoding.NOT_FOUND_CODE_POINT; CMapSegment[] oldCmap = cmap; int cmapLength = oldCmap.length; CMapSegment[] newCmap = new CMapSegment[cmapLength + 1]; System.arraycopy(oldCmap, 0, newCmap, 0, cmapLength); newCmap[cmapLength] = new CMapSegment(pu, pu, gi); cmap = newCmap; } /** * 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 * @returns 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 (int i = 0, n = cmap.length; i < n; i++) { CMapSegment segment = cmap[i]; 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); } /** {@inheritDoc} */ @Override public char mapChar(char c) { notifyMapOperation(); int glyphIndex = findGlyphIndex(c); if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) { warnMissingGlyph(c); glyphIndex = findGlyphIndex(Typeface.NOT_FOUND); } if (isEmbeddable()) { glyphIndex = subset.mapSubsetChar(glyphIndex, c); } return (char) glyphIndex; } /** {@inheritDoc} */ @Override public boolean hasChar(char c) { return (findGlyphIndex(c) != 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; } /** * Returns a Map of used Glyphs. * @return Map Map of used Glyphs */ public Map<Integer, Integer> getUsedGlyphs() { return subset.getSubsetGlyphs(); } /** @return an array of the chars used */ public char[] getCharsUsed() { if (!isEmbeddable()) { return null; } return subset.getSubsetChars(); } /** * 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 cs, String script, String language) { if (gsub != null) { GlyphSequence igs = mapCharsToGlyphs(cs); GlyphSequence ogs = gsub.substitute(igs, script, language); CharSequence ocs = mapGlyphsToChars(ogs); return ocs; } else { return cs; } } /** {@inheritDoc} */ public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language) { if (gdef != null) { GlyphSequence igs = mapCharsToGlyphs(cs); GlyphSequence ogs = gdef.reorderCombiningMarks(igs, gpa, script, language); CharSequence ocs = mapGlyphsToChars(ogs); return ocs; } else { return cs; } } /** {@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); 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 i = 0, n = adjustments.length; i < n; i++) { int[] gpa = adjustments[i]; 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) { 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); } cb.flip(); gb.flip(); return new GlyphSequence(cb, gb, null); } /** * 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(); CharBuffer cb = CharBuffer.allocate(ng); int ccMissing = Typeface.NOT_FOUND; 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; cb.put((char) sh); cb.put((char) sl); } else { cb.put((char) cc); } } cb.flip(); return (CharSequence) cb; } }