| /* |
| * 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.truetype; |
| |
| import java.io.IOException; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| /** |
| * This "glyf" table in a TrueType font file contains information that describes the glyphs. This |
| * class is responsible for creating a subset of the "glyf" table given a set of glyph indices. |
| */ |
| public class GlyfTable { |
| |
| private final OFMtxEntry[] mtxTab; |
| |
| private final long tableOffset; |
| |
| private final Set<Long> remappedComposites; |
| |
| protected final Map<Integer, Integer> subset; |
| |
| private final FontFileReader in; |
| |
| /** All the composite glyphs that appear in the subset. */ |
| private Set<Integer> compositeGlyphs = new TreeSet<Integer>(); |
| |
| /** All the glyphs that are composed, but do not appear in the subset. */ |
| protected Set<Integer> composedGlyphs = new TreeSet<Integer>(); |
| |
| public GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry, |
| Map<Integer, Integer> glyphs) throws IOException { |
| mtxTab = metrics; |
| tableOffset = dirTableEntry.getOffset(); |
| remappedComposites = new HashSet<Long>(); |
| this.subset = glyphs; |
| this.in = in; |
| } |
| |
| private static enum GlyfFlags { |
| |
| ARG_1_AND_2_ARE_WORDS(4, 2), |
| ARGS_ARE_XY_VALUES, |
| ROUND_XY_TO_GRID, |
| WE_HAVE_A_SCALE(2), |
| RESERVED, |
| MORE_COMPONENTS, |
| WE_HAVE_AN_X_AND_Y_SCALE(4), |
| WE_HAVE_A_TWO_BY_TWO(8), |
| WE_HAVE_INSTRUCTIONS, |
| USE_MY_METRICS, |
| OVERLAP_COMPOUND, |
| SCALED_COMPONENT_OFFSET, |
| UNSCALED_COMPONENT_OFFSET; |
| |
| private final int bitMask; |
| private final int argsCountIfSet; |
| private final int argsCountIfNotSet; |
| |
| private GlyfFlags(int argsCountIfSet, int argsCountIfNotSet) { |
| this.bitMask = 1 << this.ordinal(); |
| this.argsCountIfSet = argsCountIfSet; |
| this.argsCountIfNotSet = argsCountIfNotSet; |
| } |
| |
| private GlyfFlags(int argsCountIfSet) { |
| this(argsCountIfSet, 0); |
| } |
| |
| private GlyfFlags() { |
| this(0, 0); |
| } |
| |
| /** |
| * Calculates, from the given flags, the offset to the next glyph index. |
| * |
| * @param flags the glyph data flags |
| * @return offset to the next glyph if any, or 0 |
| */ |
| static int getOffsetToNextComposedGlyf(int flags) { |
| int offset = 0; |
| for (GlyfFlags flag : GlyfFlags.values()) { |
| offset += (flags & flag.bitMask) > 0 ? flag.argsCountIfSet : flag.argsCountIfNotSet; |
| } |
| return offset; |
| } |
| |
| /** |
| * Checks the given flags to see if there is another composed glyph. |
| * |
| * @param flags the glyph data flags |
| * @return true if there is another composed glyph, otherwise false. |
| */ |
| static boolean hasMoreComposites(int flags) { |
| return (flags & MORE_COMPONENTS.bitMask) > 0; |
| } |
| } |
| |
| /** |
| * Populates the map of subset glyphs with all the glyphs that compose the glyphs in the subset. |
| * This also re-maps the indices of composed glyphs to their new index in the subset font. |
| * |
| * @throws IOException an I/O error |
| */ |
| protected void populateGlyphsWithComposites() throws IOException { |
| for (int indexInOriginal : subset.keySet()) { |
| scanGlyphsRecursively(indexInOriginal); |
| } |
| |
| addAllComposedGlyphsToSubset(); |
| |
| for (int compositeGlyph : compositeGlyphs) { |
| long offset = tableOffset + mtxTab[compositeGlyph].getOffset() + 10; |
| if (!remappedComposites.contains(offset)) { |
| remapComposite(offset); |
| } |
| } |
| } |
| |
| /** |
| * Scans each glyph for any composed glyphs. This populates <code>compositeGlyphs</code> with |
| * all the composite glyphs being used in the subset. This also populates <code>newGlyphs</code> |
| * with any new glyphs that are composed and do not appear in the subset of glyphs. |
| * |
| * For example the double quote mark (") is often composed of two apostrophes ('), if an |
| * apostrophe doesn't appear in the glyphs in the subset, it will be included and will be added |
| * to newGlyphs. |
| * |
| * @param indexInOriginal the index of the glyph to test from the original font |
| * @throws IOException an I/O error |
| */ |
| private void scanGlyphsRecursively(int indexInOriginal) throws IOException { |
| if (!subset.containsKey(indexInOriginal)) { |
| composedGlyphs.add(indexInOriginal); |
| } |
| |
| if (isComposite(indexInOriginal)) { |
| compositeGlyphs.add(indexInOriginal); |
| Set<Integer> composedGlyphs = retrieveComposedGlyphs(indexInOriginal); |
| for (Integer composedGlyph : composedGlyphs) { |
| scanGlyphsRecursively(composedGlyph); |
| } |
| } |
| } |
| |
| /** |
| * Adds to the subset, all the glyphs that are composed by a glyph, but do not appear themselves |
| * in the subset. |
| */ |
| protected void addAllComposedGlyphsToSubset() { |
| int newIndex = subset.size(); |
| for (int composedGlyph : composedGlyphs) { |
| subset.put(composedGlyph, newIndex++); |
| } |
| } |
| |
| /** |
| * Re-maps the index of composed glyphs in the original font to the index of the same glyph in |
| * the subset font. |
| * |
| * @param glyphOffset the offset of the composite glyph |
| * @throws IOException an I/O error |
| */ |
| private void remapComposite(long glyphOffset) throws IOException { |
| long currentGlyphOffset = glyphOffset; |
| |
| remappedComposites.add(currentGlyphOffset); |
| |
| int flags = 0; |
| do { |
| flags = in.readTTFUShort(currentGlyphOffset); |
| int glyphIndex = in.readTTFUShort(currentGlyphOffset + 2); |
| Integer indexInSubset = subset.get(glyphIndex); |
| assert indexInSubset != null; |
| /* |
| * TODO: this should not be done here!! We're writing to the stream we're reading from, |
| * this is asking for trouble! What should happen is when the glyph data is copied from |
| * subset, the remapping should be done there. So the original stream is left untouched. |
| */ |
| in.writeTTFUShort(currentGlyphOffset + 2, indexInSubset); |
| |
| currentGlyphOffset += 4 + GlyfFlags.getOffsetToNextComposedGlyf(flags); |
| } while (GlyfFlags.hasMoreComposites(flags)); |
| } |
| |
| public boolean isComposite(int indexInOriginal) throws IOException { |
| int numberOfContours = in.readTTFShort(tableOffset + mtxTab[indexInOriginal].getOffset()); |
| return numberOfContours < 0; |
| } |
| |
| /** |
| * Reads a composite glyph at a given index and retrieves all the glyph indices of contingent |
| * composed glyphs. |
| * |
| * @param indexInOriginal the glyph index of the composite glyph |
| * @return the set of glyph indices this glyph composes |
| * @throws IOException an I/O error |
| */ |
| public Set<Integer> retrieveComposedGlyphs(int indexInOriginal) |
| throws IOException { |
| Set<Integer> composedGlyphs = new HashSet<Integer>(); |
| long offset = tableOffset + mtxTab[indexInOriginal].getOffset() + 10; |
| int flags = 0; |
| do { |
| flags = in.readTTFUShort(offset); |
| composedGlyphs.add(in.readTTFUShort(offset + 2)); |
| offset += 4 + GlyfFlags.getOffsetToNextComposedGlyf(flags); |
| } while (GlyfFlags.hasMoreComposites(flags)); |
| |
| return composedGlyphs; |
| } |
| } |