org.apache.fop.fonts.MultiByteFont.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.fop.fonts.MultiByteFont.java

Source

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

}