blob: 285d754bec50924a4d98ca4234d777e9dd1b53a2 [file] [log] [blame]
/*
* 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.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.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();
/**
* A map from Unicode indices to glyph indices. No assumption
* about ordering is made below. If lookup is changed to a binary
* search (from the current linear search), then addPrivateUseMapping()
* needs to be changed to perform ordered inserts.
*/
private BFEntry[] bfentries = null;
/* 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} */
public int getDefaultWidth() {
return defaultWidth;
}
/** {@inheritDoc} */
public String getRegistry() {
return "Adobe";
}
/** {@inheritDoc} */
public String getOrdering() {
return "UCS";
}
/** {@inheritDoc} */
public int getSupplement() {
return 0;
}
/** {@inheritDoc} */
public CIDFontType getCIDType() {
return cidType;
}
/**
* Sets the CIDType.
* @param cidType The cidType to set
*/
public void setCIDType(CIDFontType cidType) {
this.cidType = cidType;
}
/** {@inheritDoc} */
public String getEmbedFontName() {
if (isEmbeddable()) {
return FontUtil.stripWhiteSpace(super.getFontName());
} else {
return super.getFontName();
}
}
/** {@inheritDoc} */
public boolean isEmbeddable() {
return !(getEmbedFileName() == null && getEmbedResourceName() == null);
}
/** {@inheritDoc} */
public boolean isSubsetEmbedded() {
return true;
}
/** {@inheritDoc} */
public CIDSubset getCIDSubset() {
return this.subset;
}
/** {@inheritDoc} */
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 < bfentries.length) && retIdx == 0; i++) {
if (bfentries[i].getUnicodeStart() <= idx
&& bfentries[i].getUnicodeEnd() >= idx) {
retIdx = bfentries[i].getGlyphStartIndex()
+ idx
- bfentries[i].getUnicodeStart();
}
}
return retIdx;
}
/**
* Add a private use mapping {PU,GI} to the existing BFENTRIES 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;
BFEntry[] bfeOld = bfentries;
int bfeCnt = bfeOld.length;
BFEntry[] bfeNew = new BFEntry [ bfeCnt + 1 ];
System.arraycopy ( bfeOld, 0, bfeNew, 0, bfeCnt );
bfeNew [ bfeCnt ] = new BFEntry ( pu, pu, gi );
bfentries = bfeNew;
}
/**
* 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 = bfentries.length; i < n; i++ ) {
BFEntry be = bfentries [ i ];
int s = be.getGlyphStartIndex();
int e = s + ( be.getUnicodeEnd() - be.getUnicodeStart() );
if ( ( gi >= s ) && ( gi <= e ) ) {
cc = be.getUnicodeStart() + ( gi - s );
break;
}
}
if ( ( cc == 0 ) && augment ) {
cc = createPrivateUseMapping ( gi );
}
return cc;
}
private int findCharacterFromGlyphIndex ( int gi ) {
return findCharacterFromGlyphIndex ( gi, true );
}
/** {@inheritDoc} */
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} */
public boolean hasChar(char c) {
return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
}
/**
* Sets the array of BFEntry instances which constitutes the Unicode to glyph index map for
* a font. ("BF" means "base font")
* @param entries the Unicode to glyph index map
*/
public void setBFEntries(BFEntry[] entries) {
this.bfentries = entries;
}
/**
* 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, 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, 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;
}
}