blob: 2b659f512daa6f8f3be933a51dd2a84c6053c8d5 [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.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fop.apps.io.InternalResourceResolver;
/**
* Abstract base class for custom fonts loaded from files, for example.
*/
public abstract class CustomFont extends Typeface
implements FontDescriptor, MutableFont {
/** Fallback thickness for underline and strikeout when not provided by the font. */
private static final int DEFAULT_LINE_THICKNESS = 50;
private URI fontFileURI;
private String fontName;
private String fullName;
private Set<String> familyNames;
private String fontSubName;
private URI embedFileURI;
private String embedResourceName;
private final InternalResourceResolver resourceResolver;
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO;
private int capHeight;
private int xHeight;
private int ascender;
private int descender;
private int[] fontBBox = {0, 0, 0, 0};
private int flags = 4;
private int weight; //0 means unknown weight
private int stemV;
private int italicAngle;
private int missingWidth;
private FontType fontType = FontType.TYPE1;
private int firstChar;
private int lastChar = 255;
private int underlinePosition;
private int underlineThickness;
private int strikeoutPosition;
private int strikeoutThickness;
private Map<Integer, Map<Integer, Integer>> kerning;
private boolean useKerning = true;
/** the character map, mapping Unicode ranges to glyph indices. */
protected List<CMapSegment> cmap = new ArrayList<CMapSegment>();
private boolean useAdvanced = true;
private boolean simulateStyle;
protected List<SimpleSingleByteEncoding> additionalEncodings;
protected Map<Character, SingleByteFont.UnencodedCharacter> unencodedCharacters;
/**
* @param resourceResolver the URI resource resolver for controlling file access
*/
public CustomFont(InternalResourceResolver resourceResolver) {
this.resourceResolver = resourceResolver;
}
/** {@inheritDoc} */
public URI getFontURI() {
return fontFileURI;
}
/** {@inheritDoc} */
public String getFontName() {
return fontName;
}
/** {@inheritDoc} */
public String getEmbedFontName() {
return getFontName();
}
/** {@inheritDoc} */
public String getFullName() {
return fullName;
}
/**
* Returns the font family names.
* @return the font family names (a Set of Strings)
*/
public Set<String> getFamilyNames() {
return Collections.unmodifiableSet(this.familyNames);
}
/**
* Returns the font family name stripped of whitespace.
* @return the stripped font family
* @see FontUtil#stripWhiteSpace(String)
*/
public String getStrippedFontName() {
return FontUtil.stripWhiteSpace(getFontName());
}
/**
* Returns font's subfamily name.
* @return the font's subfamily name
*/
public String getFontSubName() {
return fontSubName;
}
/**
* Returns an URI representing an embeddable font file.
*
* @return URI to an embeddable font file or null if not available.
*/
public URI getEmbedFileURI() {
return embedFileURI;
}
/**
* Returns the embedding mode for this font.
* @return embedding mode
*/
public EmbeddingMode getEmbeddingMode() {
return embeddingMode;
}
/**
* Returns an {@link InputStream} representing an embeddable font file.
*
* @return {@link InputStream} for an embeddable font file
* @throws IOException if embedFileName is not null but Source is not found
*/
public InputStream getInputStream() throws IOException {
return resourceResolver.getResource(embedFileURI);
}
/**
* Returns the lookup name to an embeddable font file available as a
* resource.
* (todo) Remove this method, this should be done using a resource: URI.
* @return the lookup name
*/
public String getEmbedResourceName() {
return embedResourceName;
}
/**
* {@inheritDoc}
*/
public int getAscender() {
return ascender;
}
/**
* {@inheritDoc}
*/
public int getDescender() {
return descender;
}
/**
* {@inheritDoc}
*/
public int getCapHeight() {
return capHeight;
}
/**
* {@inheritDoc}
*/
public int getAscender(int size) {
return size * ascender;
}
/**
* {@inheritDoc}
*/
public int getDescender(int size) {
return size * descender;
}
/**
* {@inheritDoc}
*/
public int getCapHeight(int size) {
return size * capHeight;
}
/**
* {@inheritDoc}
*/
public int getXHeight(int size) {
return size * xHeight;
}
/**
* {@inheritDoc}
*/
public int[] getFontBBox() {
return fontBBox;
}
/** {@inheritDoc} */
public int getFlags() {
return flags;
}
/** {@inheritDoc} */
public boolean isSymbolicFont() {
return ((getFlags() & 4) != 0) || "ZapfDingbatsEncoding".equals(getEncodingName());
//Note: The check for ZapfDingbats is necessary as the PFM does not reliably indicate
//if a font is symbolic.
}
/**
* Returns the font weight (100, 200...800, 900). This value may be different from the
* one that was actually used to register the font.
* @return the font weight (or 0 if the font weight is unknown)
*/
public int getWeight() {
return this.weight;
}
/**
* {@inheritDoc}
*/
public int getStemV() {
return stemV;
}
/**
* {@inheritDoc}
*/
public int getItalicAngle() {
return italicAngle;
}
/**
* Returns the width to be used when no width is available.
* @return a character width
*/
public int getMissingWidth() {
return missingWidth;
}
/**
* {@inheritDoc}
*/
public FontType getFontType() {
return fontType;
}
/**
* Returns the index of the first character defined in this font.
* @return the index of the first character
*/
public int getFirstChar() {
return firstChar;
}
/**
* Returns the index of the last character defined in this font.
* @return the index of the last character
*/
public int getLastChar() {
return lastChar;
}
/**
* Used to determine if kerning is enabled.
* @return True if kerning is enabled.
*/
public boolean isKerningEnabled() {
return useKerning;
}
/**
* {@inheritDoc}
*/
public final boolean hasKerningInfo() {
return (isKerningEnabled() && (kerning != null) && !kerning.isEmpty());
}
/**
* {@inheritDoc}
*/
public final Map<Integer, Map<Integer, Integer>> getKerningInfo() {
if (hasKerningInfo()) {
return kerning;
} else {
return Collections.emptyMap();
}
}
/**
* Used to determine if advanced typographic features are enabled.
* By default, this is false, but may be overridden by subclasses.
* @return true if enabled.
*/
public boolean isAdvancedEnabled() {
return useAdvanced;
}
/* ---- MutableFont interface ---- */
/** {@inheritDoc} */
public void setFontURI(URI uri) {
this.fontFileURI = uri;
}
/** {@inheritDoc} */
public void setFontName(String name) {
this.fontName = name;
}
/** {@inheritDoc} */
public void setFullName(String name) {
this.fullName = name;
}
/** {@inheritDoc} */
public void setFamilyNames(Set<String> names) {
this.familyNames = new HashSet<String>(names);
}
/**
* Sets the font's subfamily name.
* @param subFamilyName the subfamily name of the font
*/
public void setFontSubFamilyName(String subFamilyName) {
this.fontSubName = subFamilyName;
}
/**
* {@inheritDoc}
*/
public void setEmbedURI(URI path) {
this.embedFileURI = path;
}
/**
* {@inheritDoc}
*/
public void setEmbedResourceName(String name) {
this.embedResourceName = name;
}
/**
* {@inheritDoc}
*/
public void setEmbeddingMode(EmbeddingMode embeddingMode) {
this.embeddingMode = embeddingMode;
}
/**
* {@inheritDoc}
*/
public void setCapHeight(int capHeight) {
this.capHeight = capHeight;
}
/**
* Returns the XHeight value of the font.
* @param xHeight the XHeight value
*/
public void setXHeight(int xHeight) {
this.xHeight = xHeight;
}
/**
* {@inheritDoc}
*/
public void setAscender(int ascender) {
this.ascender = ascender;
}
/**
* {@inheritDoc}
*/
public void setDescender(int descender) {
this.descender = descender;
}
/**
* {@inheritDoc}
*/
public void setFontBBox(int[] bbox) {
this.fontBBox = bbox;
}
/**
* {@inheritDoc}
*/
public void setFlags(int flags) {
this.flags = flags;
}
/**
* Sets the font weight. Valid values are 100, 200...800, 900.
* @param weight the font weight
*/
public void setWeight(int weight) {
weight = (weight / 100) * 100;
weight = Math.max(100, weight);
weight = Math.min(900, weight);
this.weight = weight;
}
/**
* {@inheritDoc}
*/
public void setStemV(int stemV) {
this.stemV = stemV;
}
/**
* {@inheritDoc}
*/
public void setItalicAngle(int italicAngle) {
this.italicAngle = italicAngle;
}
/**
* {@inheritDoc}
*/
public void setMissingWidth(int width) {
this.missingWidth = width;
}
/**
* {@inheritDoc}
*/
public void setFontType(FontType fontType) {
this.fontType = fontType;
}
/**
* {@inheritDoc}
*/
public void setFirstChar(int index) {
this.firstChar = index;
}
/**
* {@inheritDoc}
*/
public void setLastChar(int index) {
this.lastChar = index;
}
/**
* {@inheritDoc}
*/
public void setKerningEnabled(boolean enabled) {
this.useKerning = enabled;
}
/**
* {@inheritDoc}
*/
public void setAdvancedEnabled(boolean enabled) {
this.useAdvanced = enabled;
}
/**
* {@inheritDoc}
*/
public void setSimulateStyle(boolean enabled) {
this.simulateStyle = enabled;
}
public boolean getSimulateStyle() {
return this.simulateStyle;
}
/** {@inheritDoc} */
public void putKerningEntry(Integer key, Map<Integer, Integer> value) {
if (kerning == null) {
kerning = new HashMap<Integer, Map<Integer, Integer>>();
}
this.kerning.put(key, value);
}
/**
* Replaces the existing kerning map with a new one.
* @param kerningMap the kerning map (the integers are
* character codes)
*/
public void replaceKerningMap(Map<Integer, Map<Integer, Integer>> kerningMap) {
if (kerningMap == null) {
this.kerning = Collections.emptyMap();
} else {
this.kerning = kerningMap;
}
}
/**
* Sets the character map for this font. It maps all available Unicode characters
* to their glyph indices inside the font.
* @param cmap the character map
*/
public void setCMap(CMapSegment[] cmap) {
this.cmap.clear();
Collections.addAll(this.cmap, cmap);
}
/**
* Returns the character map for this font. It maps all available Unicode characters
* to their glyph indices inside the font.
* @return the character map
*/
public CMapSegment[] getCMap() {
return cmap.toArray(new CMapSegment[cmap.size()]);
}
public int getUnderlinePosition(int size) {
return (underlinePosition == 0)
? getDescender(size) / 2
: size * underlinePosition;
}
public void setUnderlinePosition(int underlinePosition) {
this.underlinePosition = underlinePosition;
}
public int getUnderlineThickness(int size) {
return size * ((underlineThickness == 0) ? DEFAULT_LINE_THICKNESS : underlineThickness);
}
public void setUnderlineThickness(int underlineThickness) {
this.underlineThickness = underlineThickness;
}
public int getStrikeoutPosition(int size) {
return (strikeoutPosition == 0)
? getXHeight(size) / 2
: size * strikeoutPosition;
}
public void setStrikeoutPosition(int strikeoutPosition) {
this.strikeoutPosition = strikeoutPosition;
}
public int getStrikeoutThickness(int size) {
return (strikeoutThickness == 0) ? getUnderlineThickness(size) : size * strikeoutThickness;
}
public void setStrikeoutThickness(int strikeoutThickness) {
this.strikeoutThickness = strikeoutThickness;
}
/**
* Returns a Map of used Glyphs.
* @return Map Map of used Glyphs
*/
public abstract Map<Integer, Integer> getUsedGlyphs();
/**
* Returns the character from it's original glyph index in the font
* @param glyphIndex The original index of the character
* @return The character
*/
public abstract char getUnicodeFromGID(int glyphIndex);
/**
* Indicates whether the encoding has additional encodings besides the primary encoding.
* @return true if there are additional encodings.
*/
public boolean hasAdditionalEncodings() {
return (this.additionalEncodings != null) && (this.additionalEncodings.size() > 0);
}
/**
* Returns the number of additional encodings this single-byte font maintains.
* @return the number of additional encodings
*/
public int getAdditionalEncodingCount() {
if (hasAdditionalEncodings()) {
return this.additionalEncodings.size();
} else {
return 0;
}
}
/**
* Returns an additional encoding.
* @param index the index of the additional encoding
* @return the additional encoding
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
public SimpleSingleByteEncoding getAdditionalEncoding(int index)
throws IndexOutOfBoundsException {
if (hasAdditionalEncodings()) {
return this.additionalEncodings.get(index);
} else {
throw new IndexOutOfBoundsException("No additional encodings available");
}
}
/**
* Adds an unencoded character (one that is not supported by the primary encoding).
* @param ch the named character
* @param width the width of the character
*/
public void addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox) {
if (this.unencodedCharacters == null) {
this.unencodedCharacters = new HashMap<Character, SingleByteFont.UnencodedCharacter>();
}
if (ch.hasSingleUnicodeValue()) {
SingleByteFont.UnencodedCharacter uc = new SingleByteFont.UnencodedCharacter(ch, width, bbox);
this.unencodedCharacters.put(ch.getSingleUnicodeValue(), uc);
} else {
//Cannot deal with unicode sequences, so ignore this character
}
}
/**
* Adds a character to additional encodings
* @param ch character to map
*/
protected char mapUnencodedChar(char ch) {
if (this.unencodedCharacters != null) {
SingleByteFont.UnencodedCharacter unencoded = this.unencodedCharacters.get(ch);
if (unencoded != null) {
if (this.additionalEncodings == null) {
this.additionalEncodings = new ArrayList<SimpleSingleByteEncoding>();
}
SimpleSingleByteEncoding encoding = null;
char mappedStart = 0;
int additionalsCount = this.additionalEncodings.size();
for (int i = 0; i < additionalsCount; i++) {
mappedStart += 256;
encoding = getAdditionalEncoding(i);
char alt = encoding.mapChar(ch);
if (alt != 0) {
return (char)(mappedStart + alt);
}
}
if (encoding != null && encoding.isFull()) {
encoding = null;
}
if (encoding == null) {
encoding = new SimpleSingleByteEncoding(
getFontName() + "EncodingSupp" + (additionalsCount + 1));
this.additionalEncodings.add(encoding);
mappedStart += 256;
}
return (char)(mappedStart + encoding.addCharacter(unencoded.getCharacter()));
}
}
return 0;
}
}