diff --git a/fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java b/fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java index cbaace8a2e9..766a09f92ed 100644 --- a/fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java +++ b/fop-core/src/main/java/org/apache/fop/fonts/MultiByteFont.java @@ -77,8 +77,11 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl private int firstUnmapped; private int lastUnmapped; - /** Contains the character bounding boxes for all characters in the font */ - protected Rectangle[] boundingBoxes; + /** Contains the character bounding boxes for all characters in the font, + * packed as x, y, width, height per glyph. A Rectangle per glyph costs 36 + * bytes (32 for the object, 4 for the array slot) where these cost 16, and + * the array is retained for as long as the font is loaded. */ + protected int[] boundingBoxes; private boolean isOTFFile; @@ -210,8 +213,9 @@ public int[] getWidths() { public Rectangle getBoundingBox(int glyphIndex, int size) { int index = isEmbeddable() ? cidSet.getOriginalGlyphIndex(glyphIndex) : glyphIndex; - Rectangle bbox = boundingBoxes[index]; - return new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size); + int i = index * 4; + return new Rectangle(boundingBoxes[i] * size, boundingBoxes[i + 1] * size, + boundingBoxes[i + 2] * size, boundingBoxes[i + 3] * size); } /** @@ -451,6 +455,24 @@ public void setWidthArray(int[] wds) { * @param boundingBoxes array of bounding boxes. */ public void setBBoxArray(Rectangle[] boundingBoxes) { + int[] packed = new int[boundingBoxes.length * 4]; + for (int i = 0; i < boundingBoxes.length; i++) { + Rectangle bbox = boundingBoxes[i]; + packed[i * 4] = bbox.x; + packed[i * 4 + 1] = bbox.y; + packed[i * 4 + 2] = bbox.width; + packed[i * 4 + 3] = bbox.height; + } + this.boundingBoxes = packed; + } + + /** + * Sets the bounding boxes array, packed as x, y, width, height per glyph. + * Preferred over setBBoxArray(Rectangle[]), which allocates a Rectangle per + * glyph merely to pass the values in. + * @param boundingBoxes packed bounding boxes, 4 ints per glyph. + */ + public void setBBoxArray(int[] boundingBoxes) { this.boundingBoxes = boundingBoxes; } diff --git a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OFFontLoader.java b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OFFontLoader.java index 895b798f713..c66ba29d2c5 100644 --- a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OFFontLoader.java +++ b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OFFontLoader.java @@ -193,7 +193,7 @@ private void buildFont(OpenFont otf, String ttcFontName) { multiFont.setCIDType(CIDFontType.CIDTYPE2); } multiFont.setWidthArray(otf.getWidths()); - multiFont.setBBoxArray(otf.getBoundingBoxes()); + multiFont.setBBoxArray(otf.getBoundingBoxesPacked()); } else { singleFont.setFontType(FontType.TRUETYPE); singleFont.setEncoding(otf.getCharSetName()); diff --git a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java index 95113f40763..84bde41ffda 100644 --- a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java +++ b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OpenFont.java @@ -1128,6 +1128,23 @@ public int[] getWidths() { return wx; } + /** + * The bounding boxes of all glyphs, packed as x, y, width, height per glyph + * (so 4 ints each). The same values as getBoundingBoxes(), without a + * Rectangle per glyph. + */ + public int[] getBoundingBoxesPacked() { + int[] packed = new int[mtxTab.length * 4]; + for (int i = 0; i < mtxTab.length; i++) { + int[] boundingBox = mtxTab[i].getBoundingBox(); + packed[i * 4] = convertTTFUnit2PDFUnit(boundingBox[0]); + packed[i * 4 + 1] = convertTTFUnit2PDFUnit(boundingBox[1]); + packed[i * 4 + 2] = convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]); + packed[i * 4 + 3] = convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1]); + } + return packed; + } + public Rectangle[] getBoundingBoxes() { Rectangle[] boundingBoxes = new Rectangle[mtxTab.length]; for (int i = 0; i < boundingBoxes.length; i++) { diff --git a/fop-core/src/test/java/org/apache/fop/fonts/MultiByteFontTestCase.java b/fop-core/src/test/java/org/apache/fop/fonts/MultiByteFontTestCase.java new file mode 100644 index 00000000000..01c00f22be5 --- /dev/null +++ b/fop-core/src/test/java/org/apache/fop/fonts/MultiByteFontTestCase.java @@ -0,0 +1,63 @@ +/* + * 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 org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * Tests for MultiByteFont's glyph bounding boxes, which are stored packed as + * ints rather than as a Rectangle per glyph. + */ +public class MultiByteFontTestCase { + + /** Not embeddable (there is no embed URI), so bounding boxes are indexed by glyph index. */ + private MultiByteFont font() { + return new MultiByteFont(null, EmbeddingMode.AUTO); + } + + @Test + public void testPackedBBoxArray() { + MultiByteFont font = font(); + font.setBBoxArray(new int[] {1, 2, 3, 4, 5, 6, 7, 8}); + + assertEquals(new Rectangle(1, 2, 3, 4), font.getBoundingBox(0, 1)); + assertEquals(new Rectangle(5, 6, 7, 8), font.getBoundingBox(1, 1)); + } + + @Test + public void testRectangleBBoxArray() { + MultiByteFont font = font(); + font.setBBoxArray(new Rectangle[] {new Rectangle(1, 2, 3, 4), new Rectangle(5, 6, 7, 8)}); + + assertEquals(new Rectangle(1, 2, 3, 4), font.getBoundingBox(0, 1)); + assertEquals(new Rectangle(5, 6, 7, 8), font.getBoundingBox(1, 1)); + } + + @Test + public void testBoundingBoxIsScaledBySize() { + MultiByteFont font = font(); + font.setBBoxArray(new int[] {-1, 2, 3, 4}); + + assertEquals(new Rectangle(-10, 20, 30, 40), font.getBoundingBox(0, 10)); + } +} diff --git a/fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java b/fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java index 24b3b396dc5..5c48f8adbb4 100644 --- a/fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java @@ -19,6 +19,7 @@ package org.apache.fop.fonts.truetype; +import java.awt.Rectangle; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -577,6 +578,24 @@ public void testBBox() { assertEquals(dejavuTTFFile.getBBox(2330).length, 4); } + /** + * Test getBoundingBoxesPacked() - it must carry the same values as + * getBoundingBoxes(), which it exists to avoid allocating. + */ + @Test + public void testGetBoundingBoxesPacked() { + Rectangle[] boundingBoxes = dejavuTTFFile.getBoundingBoxes(); + int[] packed = dejavuTTFFile.getBoundingBoxesPacked(); + assertEquals(boundingBoxes.length * 4, packed.length); + for (int i = 0; i < boundingBoxes.length; i++) { + Rectangle boundingBox = boundingBoxes[i]; + assertEquals(boundingBox.x, packed[i * 4]); + assertEquals(boundingBox.y, packed[i * 4 + 1]); + assertEquals(boundingBox.width, packed[i * 4 + 2]); + assertEquals(boundingBox.height, packed[i * 4 + 3]); + } + } + @Test public void testReservedIndex() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream();