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