diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2011-08-08 17:34:46 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2011-08-08 17:34:46 +0000 |
commit | fb02159c7df3ad79eda435be9ae1f940953d9fd8 (patch) | |
tree | d5128eaade6e1df5d8e38e575ee3f1752c0c350e /test | |
parent | 8e894b822fd834209663744877fb6d648630abfc (diff) | |
download | xmlgraphics-fop-fb02159c7df3ad79eda435be9ae1f940953d9fd8.tar.gz xmlgraphics-fop-fb02159c7df3ad79eda435be9ae1f940953d9fd8.zip |
Bugzilla #51596: A composite glyph can be remapped more than once in a TTF subset, resulting in garbled character
Test and clean-up provided by Mehdi Houshmand
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1155024 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test')
-rw-r--r-- | test/java/org/apache/fop/StandardTestSuite.java | 2 | ||||
-rw-r--r-- | test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java | 190 |
2 files changed, 192 insertions, 0 deletions
diff --git a/test/java/org/apache/fop/StandardTestSuite.java b/test/java/org/apache/fop/StandardTestSuite.java index 01e3a365e..a2e6d7524 100644 --- a/test/java/org/apache/fop/StandardTestSuite.java +++ b/test/java/org/apache/fop/StandardTestSuite.java @@ -25,6 +25,7 @@ import junit.framework.TestSuite; import org.apache.fop.area.ViewportTestSuite; import org.apache.fop.afp.parser.MODCAParserTestCase; import org.apache.fop.fonts.DejaVuLGCSerifTest; +import org.apache.fop.fonts.truetype.GlyfTableTestCase; import org.apache.fop.image.loader.batik.ImageLoaderTestCase; import org.apache.fop.image.loader.batik.ImagePreloaderTestCase; import org.apache.fop.intermediate.IFMimickingTestCase; @@ -61,6 +62,7 @@ public class StandardTestSuite { suite.addTest(new TestSuite(MODCAParserTestCase.class)); suite.addTest(org.apache.fop.render.afp.AFPTestSuite.suite()); suite.addTest(PSTestSuite.suite()); + suite.addTest(new TestSuite(GlyfTableTestCase.class)); suite.addTest(RichTextFormatTestSuite.suite()); suite.addTest(new TestSuite(ImageLoaderTestCase.class)); suite.addTest(new TestSuite(ImagePreloaderTestCase.class)); diff --git a/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java new file mode 100644 index 000000000..89527a775 --- /dev/null +++ b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java @@ -0,0 +1,190 @@ +/* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +/** + * Tests {@link GlyfTable}. + */ +public class GlyfTableTestCase extends TestCase { + + private final static class DirData { + + final long offset; + final long length; + + DirData(long offset, long length) { + this.offset = offset; + this.length = length; + } + } + + private FontFileReader subsetReader; + + private long[] glyphOffsets; + + private FontFileReader originalFontReader; + + @Override + public void setUp() throws IOException { + originalFontReader = new FontFileReader("test/resources/fonts/DejaVuLGCSerif.ttf"); + } + + /** + * Tests that composed glyphs are included in the glyph subset if a composite glyph is used. + * + * @throws IOException if an I/O error occurs + */ + public void testPopulateGlyphsWithComposites() throws IOException { + // Glyph 408 -> U+01D8 "uni01D8" this is a composite glyph. + int[] composedIndices = setupTest(408); + + int[] expected = new int[composedIndices.length]; + expected[1] = 6; + expected[5] = 2; + expected[6] = 4; + + assertArrayEquals(expected, composedIndices); + } + + /** + * Tests that no glyphs are added if there are no composite glyphs the subset. + * + * @throws IOException if an I/O error occurs + */ + public void testPopulateNoCompositeGlyphs() throws IOException { + int[] composedIndices = setupTest(36, 37, 38); // "A", "B", "C" + int[] expected = new int[composedIndices.length]; + + // There should be NO composite glyphs + assertArrayEquals(expected, composedIndices); + } + + /** + * Tests that glyphs aren't remapped twice if the glyph before a composite glyph has 0-length. + * + * @throws IOException if an I/O error occurs + */ + public void testGlyphsNotRemappedTwice() throws IOException { + int composedGlyph = 12; + // The order of these glyph indices, must NOT be changed! (see javadoc above) + int[] composedIndices = setupTest(1, 2, 3, 16, 2014, 4, 7, 8, 13, 2015, composedGlyph); + + // There are 2 composed glyphs within the subset + int[] expected = new int[composedIndices.length]; + expected[10] = composedGlyph; + + assertArrayEquals(expected, composedIndices); + } + + /** + * Tests that the correct glyph is included in the subset, when a composite glyph composed of a + * composite glyph is used. + * + * @throws IOException if an I/O error occurs + */ + public void testSingleRecursionStep() throws IOException { + // Glyph 2077 -> U+283F "uni283F" this is composed of a composite glyph (recursive). + int[] composedIndices = setupTest(2077); + + int[] expected = new int[composedIndices.length]; + expected[1] = 2; + + assertArrayEquals(expected, composedIndices); + } + + private int[] setupTest(int... glyphIndices) throws IOException { + Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>(); + int index = 0; + glyphs.put(0, index++); // Glyph 0 (.notdef) must ALWAYS be in the subset + + for (int glyphIndex : glyphIndices) { + glyphs.put(glyphIndex, index++); + } + setupSubsetReader(glyphs); + readLoca(); + + return retrieveIndicesOfComposedGlyphs(); + } + + private void setupSubsetReader(Map<Integer, Integer> glyphs) throws IOException { + TTFSubSetFile fontFile = new TTFSubSetFile(); + byte[] subsetFont = fontFile.readFont(originalFontReader, "Deja", glyphs); + InputStream intputStream = new ByteArrayInputStream(subsetFont); + subsetReader = new FontFileReader(intputStream); + } + + private void readLoca() throws IOException { + DirData loca = getTableData("loca"); + int numberOfGlyphs = (int) (loca.length - 4) / 4; + glyphOffsets = new long[numberOfGlyphs]; + subsetReader.seekSet(loca.offset); + + for (int i = 0; i < numberOfGlyphs; i++) { + glyphOffsets[i] = subsetReader.readTTFULong(); + } + } + + private int[] retrieveIndicesOfComposedGlyphs() throws IOException { + DirData glyf = getTableData("glyf"); + int[] composedGlyphIndices = new int[glyphOffsets.length]; + + for (int i = 0; i < glyphOffsets.length; i++) { + long glyphOffset = glyphOffsets[i]; + if (i != glyphOffsets.length - 1 && glyphOffset == glyphOffsets[i + 1]) { + continue; + } + subsetReader.seekSet(glyf.offset + glyphOffset); + short numberOfContours = subsetReader.readTTFShort(); + if (numberOfContours < 0) { + subsetReader.skip(8); + subsetReader.readTTFUShort(); // flags + int glyphIndex = subsetReader.readTTFUShort(); + composedGlyphIndices[i] = glyphIndex; + } + } + return composedGlyphIndices; + } + + private DirData getTableData(String tableName) throws IOException { + subsetReader.seekSet(0); + subsetReader.skip(12); + String name; + do { + name = subsetReader.readTTFString(4); + subsetReader.skip(4 * 3); + } while (!name.equals(tableName)); + + subsetReader.skip(-8); // We've found the table, go back to get the data we skipped over + return new DirData(subsetReader.readTTFLong(), subsetReader.readTTFLong()); + } + + private void assertArrayEquals(int[] expected, int[] actual) { + assertTrue(Arrays.equals(expected, actual)); + } +} |