123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- /*
- * 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.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.TreeSet;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.xmlgraphics.fonts.Glyphs;
-
- import org.apache.fop.apps.io.InternalResourceResolver;
- import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
- import org.apache.fop.util.CharUtilities;
-
- /**
- * Generic SingleByte font
- */
- public class SingleByteFont extends CustomFont {
-
- /** logger */
- private static Log log = LogFactory.getLog(SingleByteFont.class);
-
- protected SingleByteEncoding mapping;
- private boolean useNativeEncoding;
-
- protected int[] width;
-
- private Rectangle[] boundingBoxes;
-
- private Map<Character, UnencodedCharacter> unencodedCharacters;
- private List<SimpleSingleByteEncoding> additionalEncodings;
- private Map<Character, Character> alternativeCodes;
-
- private PostScriptVersion ttPostScriptVersion;
-
- private int usedGlyphsCount;
- private LinkedHashMap<Integer, String> usedGlyphNames;
- private Map<Integer, Integer> usedGlyphs;
- private Map<Integer, Character> usedCharsIndex;
- private Map<Character, Integer> charGIDMappings;
-
- public SingleByteFont(InternalResourceResolver resourceResolver) {
- super(resourceResolver);
- setEncoding(CodePointMapping.WIN_ANSI_ENCODING);
- }
-
- public SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
- this(resourceResolver);
- setEmbeddingMode(embeddingMode);
- if (embeddingMode != EmbeddingMode.FULL) {
- usedGlyphNames = new LinkedHashMap<Integer, String>();
- usedGlyphs = new HashMap<Integer, Integer>();
- usedCharsIndex = new HashMap<Integer, Character>();
- charGIDMappings = new HashMap<Character, Integer>();
-
- // The zeroth value is reserved for .notdef
- usedGlyphs.put(0, 0);
- usedGlyphsCount++;
- }
- }
-
- /** {@inheritDoc} */
- public boolean isEmbeddable() {
- return (!(getEmbedFileURI() == null
- && getEmbedResourceName() == null));
- }
-
- /** {@inheritDoc} */
- public boolean isSubsetEmbedded() {
- return getEmbeddingMode() != EmbeddingMode.FULL;
- }
-
- /** {@inheritDoc} */
- public String getEncodingName() {
- return this.mapping.getName();
- }
-
- /**
- * Returns the code point mapping (encoding) of this font.
- * @return the code point mapping
- */
- public SingleByteEncoding getEncoding() {
- return this.mapping;
- }
-
- /** {@inheritDoc} */
- public int getWidth(int i, int size) {
- if (i < 256) {
- int idx = i - getFirstChar();
- if (idx >= 0 && idx < width.length) {
- return size * width[idx];
- }
- } else if (this.additionalEncodings != null) {
- int encodingIndex = (i / 256) - 1;
- SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex);
- int codePoint = i % 256;
- NamedCharacter nc = encoding.getCharacterForIndex(codePoint);
- UnencodedCharacter uc
- = this.unencodedCharacters.get(nc.getSingleUnicodeValue());
- return size * uc.getWidth();
- }
- return 0;
- }
-
- /** {@inheritDoc} */
- public int[] getWidths() {
- int[] arr = new int[width.length];
- System.arraycopy(width, 0, arr, 0, width.length);
- return arr;
- }
-
- public Rectangle getBoundingBox(int glyphIndex, int size) {
- Rectangle bbox = null;
- if (glyphIndex < 256) {
- int idx = glyphIndex - getFirstChar();
- if (idx >= 0 && idx < boundingBoxes.length) {
- bbox = boundingBoxes[idx];
- }
- } else if (this.additionalEncodings != null) {
- int encodingIndex = (glyphIndex / 256) - 1;
- SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex);
- int codePoint = glyphIndex % 256;
- NamedCharacter nc = encoding.getCharacterForIndex(codePoint);
- UnencodedCharacter uc = this.unencodedCharacters.get(nc.getSingleUnicodeValue());
- bbox = uc.getBBox();
- }
- return bbox == null ? null : new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
- }
-
- /**
- * Lookup a character using its alternative names. If found, cache it so we
- * can speed up lookups.
- * @param c the character
- * @return the suggested alternative character present in the font
- */
- private char findAlternative(char c) {
- char d;
- if (alternativeCodes == null) {
- alternativeCodes = new java.util.HashMap<Character, Character>();
- } else {
- Character alternative = alternativeCodes.get(c);
- if (alternative != null) {
- return alternative;
- }
- }
- String charName = Glyphs.charToGlyphName(c);
- String[] charNameAlternatives = Glyphs.getCharNameAlternativesFor(charName);
- if (charNameAlternatives != null && charNameAlternatives.length > 0) {
- for (int i = 0; i < charNameAlternatives.length; i++) {
- if (log.isDebugEnabled()) {
- log.debug("Checking alternative for char " + c + " (charname="
- + charName + "): " + charNameAlternatives[i]);
- }
- String s = Glyphs.getUnicodeSequenceForGlyphName(charNameAlternatives[i]);
- if (s != null) {
- d = lookupChar(s.charAt(0));
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- alternativeCodes.put(c, d);
- return d;
- }
- }
- }
- }
-
- return SingleByteEncoding.NOT_FOUND_CODE_POINT;
- }
-
- private char lookupChar(char c) {
- char d = mapping.mapChar(c);
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- return d;
- }
-
- // Check unencoded characters which are available in the font by
- // character name
- d = mapUnencodedChar(c);
- return d;
- }
-
- private boolean isSubset() {
- return getEmbeddingMode() == EmbeddingMode.SUBSET;
- }
-
- /** {@inheritDoc} */
- @Override
- public char mapChar(char c) {
- notifyMapOperation();
- char d = lookupChar(c);
- if (d == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- // Check for alternative
- d = findAlternative(c);
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- return d;
- } else {
- this.warnMissingGlyph(c);
- return Typeface.NOT_FOUND;
- }
- }
- if (isEmbeddable() && isSubset()) {
- mapChar(d, c);
- }
- return d;
- }
-
- private int mapChar(int glyphIndex, char unicode) {
- // Reencode to a new subset font or get the reencoded value
- // IOW, accumulate the accessed characters and build a character map for them
- Integer subsetCharSelector = usedGlyphs.get(glyphIndex);
- if (subsetCharSelector == null) {
- int selector = usedGlyphsCount;
- usedGlyphs.put(glyphIndex, selector);
- usedCharsIndex.put(selector, unicode);
- charGIDMappings.put(unicode, glyphIndex);
- usedGlyphsCount++;
- return selector;
- } else {
- return subsetCharSelector;
- }
- }
-
- private char getUnicode(int index) {
- Character mapValue = usedCharsIndex.get(index);
- if (mapValue != null) {
- return mapValue.charValue();
- } else {
- return CharUtilities.NOT_A_CHARACTER;
- }
- }
-
- private char mapUnencodedChar(char ch) {
- if (this.unencodedCharacters != null) {
- 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;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean hasChar(char c) {
- char d = mapping.mapChar(c);
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- return true;
- }
- //Check unencoded characters which are available in the font by character name
- d = mapUnencodedChar(c);
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- return true;
- }
- // Check if an alternative exists
- d = findAlternative(c);
- if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
- return true;
- }
- return false;
- }
-
- /* ---- single byte font specific setters --- */
-
- /**
- * Updates the mapping variable based on the encoding.
- * @param encoding the name of the encoding
- */
- protected void updateMapping(String encoding) {
- try {
- this.mapping = CodePointMapping.getMapping(encoding);
- } catch (UnsupportedOperationException e) {
- log.error("Font '" + super.getFontName() + "': " + e.getMessage());
- }
- }
-
- /**
- * Sets the encoding of the font.
- * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding")
- */
- public void setEncoding(String encoding) {
- updateMapping(encoding);
- }
-
- /**
- * Sets the encoding of the font.
- * @param encoding the encoding information
- */
- public void setEncoding(CodePointMapping encoding) {
- this.mapping = encoding;
- }
-
- /**
- * Controls whether the font is configured to use its native encoding or if it
- * may need to be re-encoded for the target format.
- * @param value true indicates that the configured encoding is the font's native encoding
- */
- public void setUseNativeEncoding(boolean value) {
- this.useNativeEncoding = value;
- }
-
- /**
- * Indicates whether this font is configured to use its native encoding. This
- * method is used to determine whether the font needs to be re-encoded.
- * @return true if the font uses its native encoding.
- */
- public boolean isUsingNativeEncoding() {
- return this.useNativeEncoding;
- }
-
- /**
- * Sets a width for a character.
- * @param index index of the character
- * @param w the width of the character
- */
- public void setWidth(int index, int w) {
- if (this.width == null) {
- this.width = new int[getLastChar() - getFirstChar() + 1];
- }
- this.width[index - getFirstChar()] = w;
- }
-
- public void setBoundingBox(int index, Rectangle bbox) {
- if (this.boundingBoxes == null) {
- this.boundingBoxes = new Rectangle[getLastChar() - getFirstChar() + 1];
- }
- this.boundingBoxes[index - getFirstChar()] = bbox;
- }
-
- /**
- * 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, UnencodedCharacter>();
- }
- if (ch.hasSingleUnicodeValue()) {
- UnencodedCharacter uc = new UnencodedCharacter(ch, width, bbox);
- this.unencodedCharacters.put(ch.getSingleUnicodeValue(), uc);
- } else {
- //Cannot deal with unicode sequences, so ignore this character
- }
- }
-
- /**
- * Makes all unencoded characters available through additional encodings. This method
- * is used in cases where the fonts need to be encoded in the target format before
- * all text of the document is processed (for example in PostScript when resource optimization
- * is disabled).
- */
- public void encodeAllUnencodedCharacters() {
- if (this.unencodedCharacters != null) {
- Set<Character> sortedKeys = new TreeSet<Character>(this.unencodedCharacters.keySet());
- for (Character ch : sortedKeys) {
- char mapped = mapChar(ch.charValue());
- assert mapped != Typeface.NOT_FOUND;
- }
- }
- }
-
- /**
- * 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");
- }
- }
-
- /**
- * Returns an array with the widths for an additional encoding.
- * @param index the index of the additional encoding
- * @return the width array
- */
- public int[] getAdditionalWidths(int index) {
- SimpleSingleByteEncoding enc = getAdditionalEncoding(index);
- int[] arr = new int[enc.getLastChar() - enc.getFirstChar() + 1];
- for (int i = 0, c = arr.length; i < c; i++) {
- NamedCharacter nc = enc.getCharacterForIndex(enc.getFirstChar() + i);
- UnencodedCharacter uc = this.unencodedCharacters.get(
- nc.getSingleUnicodeValue());
- arr[i] = uc.getWidth();
- }
- return arr;
- }
-
- private static final class UnencodedCharacter {
-
- private final NamedCharacter character;
- private final int width;
- private final Rectangle bbox;
-
- public UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox) {
- this.character = character;
- this.width = width;
- this.bbox = bbox;
- }
-
- public NamedCharacter getCharacter() {
- return this.character;
- }
-
- public int getWidth() {
- return this.width;
- }
-
- public Rectangle getBBox() {
- return bbox;
- }
-
- /** {@inheritDoc} */
- @Override
- public String toString() {
- return getCharacter().toString();
- }
- }
-
- /**
- * Sets the version of the PostScript table stored in the TrueType font represented by
- * this instance.
- *
- * @param version version of the <q>post</q> table
- */
- public void setTrueTypePostScriptVersion(PostScriptVersion version) {
- ttPostScriptVersion = version;
- }
-
- /**
- * Returns the version of the PostScript table stored in the TrueType font represented by
- * this instance.
- *
- * @return the version of the <q>post</q> table
- */
- public PostScriptVersion getTrueTypePostScriptVersion() {
- assert getFontType() == FontType.TRUETYPE;
- return ttPostScriptVersion;
- }
-
- /**
- * Returns a Map of used Glyphs.
- * @return Map Map of used Glyphs
- */
- public Map<Integer, Integer> getUsedGlyphs() {
- return Collections.unmodifiableMap(usedGlyphs);
- }
-
- public char getUnicodeFromSelector(int selector) {
- return getUnicode(selector);
- }
-
- public int getGIDFromChar(char ch) {
- return charGIDMappings.get(ch);
- }
-
- public char getUnicodeFromGID(int glyphIndex) {
- int selector = usedGlyphs.get(glyphIndex);
- return usedCharsIndex.get(selector);
- }
-
- public void mapUsedGlyphName(int gid, String value) {
- usedGlyphNames.put(gid, value);
- }
-
- public Map<Integer, String> getUsedGlyphNames() {
- return usedGlyphNames;
- }
-
- public String getGlyphName(int idx) {
- if (idx < mapping.getCharNameMap().length) {
- return mapping.getCharNameMap()[idx];
- } else {
- int selector = usedGlyphs.get(idx);
- char theChar = usedCharsIndex.get(selector);
- return unencodedCharacters.get(theChar).getCharacter().getName();
- }
- }
- }
|