1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
/*
* 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.render.pcl.fonts;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID;
import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;
public class PCLTTFFontReaderTestCase {
private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class);
private PCLByteWriterUtil byteWriter;
private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";
@Before
public void setUp() {
byteWriter = new PCLByteWriterUtil();
}
@Test
public void verifyFontAData() throws Exception {
CustomFont sbFont = mock(CustomFont.class);
when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_A)));
when(customFont.getRealFont()).thenReturn(sbFont);
SingleByteFont font = mock(SingleByteFont.class);
when(font.getGIDFromChar('h')).thenReturn(104);
when(font.getGIDFromChar('e')).thenReturn(101);
when(font.getGIDFromChar('l')).thenReturn(108);
when(font.getGIDFromChar('o')).thenReturn(111);
PCLTTFFontReader reader = new MockPCLTTFFontReader(customFont, byteWriter);
reader.setFont(font);
verifyFontData(reader);
validateOffsets(reader);
validateFontSegments(reader);
}
/**
* Compares the input font data against a sample of the data read and calculated by the reader. The assertions are
* made against data taken from the TrueType Font Analyzer tool.
* @param reader The reader
*/
private void verifyFontData(PCLTTFFontReader reader) {
assertEquals(reader.getCellWidth(), 5015); // Bounding box X2 - X1
assertEquals(reader.getCellHeight(), 3254); // Bounding box Y2 - Y1
assertEquals(reader.getCapHeight(), 0); // OS2Table.capHeight
assertEquals(reader.getFontName(), "DejaVu LGC Serif"); // Full name read by TTFFont object
assertEquals(reader.getFirstCode(), 32); // Always 32 for bound font
assertEquals(reader.getLastCode(), 255); // Always 255 for bound font
// Values that require conversion tables (See PCLTTFFontReader.java)
assertEquals(reader.getStrokeWeight(), 0); // Weight Class 400 (regular) should be equivalent 0
assertEquals(reader.getSerifStyle(), 128); // Serif Style 0 should equal 0
assertEquals(reader.getWidthType(), 0); // Width Class 5 (regular) should be equivalent 0
}
private void validateOffsets(PCLTTFFontReader reader) throws IOException {
// Offsets are stored with their character ID with the array [offset, length]
Map<Integer, int[]> offsets = reader.getCharacterOffsets();
// Test data
int[] charC = {27644, 144}; // Char index = 99
int[] charDollar = {16044, 264}; // Char index = 36
int[] charOne = {17808, 176}; // Char index = 49
int[] charUpperD = {21236, 148}; // Char index = 68
int[] charUpperJ = {22140, 176}; // Char index = 74
assertArrayEquals(offsets.get(99), charC);
assertArrayEquals(offsets.get(36), charDollar);
assertArrayEquals(offsets.get(49), charOne);
assertArrayEquals(offsets.get(68), charUpperD);
assertArrayEquals(offsets.get(74), charUpperJ);
}
/**
* Verifies the font segment data copied originally from the TrueType font. Data was verified using TrueType Font
* Analyzer and PCLParaphernalia tool.
* @param reader The reader
* @throws IOException
*/
private void validateFontSegments(PCLTTFFontReader reader) throws IOException {
HashMap<Character, Integer> mappedChars = new HashMap<Character, Integer>();
mappedChars.put('H', 1);
mappedChars.put('e', 1);
mappedChars.put('l', 1);
mappedChars.put('o', 1);
List<PCLFontSegment> segments = reader.getFontSegments(mappedChars);
assertEquals(segments.size(), 5);
for (PCLFontSegment segment : segments) {
if (segment.getIdentifier() == SegmentID.PA) {
// Panose
assertEquals(segment.getData().length, 10);
byte[] panose = {2, 6, 6, 3, 5, 6, 5, 2, 2, 4};
assertArrayEquals(segment.getData(), panose);
} else if (segment.getIdentifier() == SegmentID.GT) {
verifyGlobalTrueTypeData(segment, mappedChars.size());
} else if (segment.getIdentifier() == SegmentID.NULL) {
// Terminating segment
assertEquals(segment.getData().length, 0);
}
}
}
private void verifyGlobalTrueTypeData(PCLFontSegment segment, int mappedCharsSize)
throws IOException {
byte[] ttfData = segment.getData();
int currentPos = 0;
//Version
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 1);
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 0);
//Number of tables
int numTables = readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]});
assertEquals(numTables, 8);
//Search range
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 128);
//Entry Selector
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 3);
//Range shift
assertEquals(readInt(new byte[]{ttfData[currentPos++], ttfData[currentPos++]}), 0);
String[] validTags = {"head", "hhea", "hmtx", "maxp", "gdir"};
int matches = 0;
for (int i = 0; i < numTables; i++) {
String tag = readTag(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
if (Arrays.asList(validTags).contains(tag)) {
matches++;
}
if (tag.equals("hmtx")) {
currentPos += 4;
int offset = readLong(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
int length = readLong(new byte[]{ttfData[currentPos++], ttfData[currentPos++],
ttfData[currentPos++], ttfData[currentPos++]});
verifyHmtx(ttfData, offset, length, mappedCharsSize);
} else {
currentPos += 12;
}
}
assertEquals(matches, 5);
}
private void verifyHmtx(byte[] ttfData, int offset, int length, int mappedCharsSize)
throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(ttfData);
byte[] subsetHmtx = new byte[length];
bais.skip(offset);
bais.read(subsetHmtx);
assertEquals(subsetHmtx.length, (mappedCharsSize + 32) * 4);
}
private int readInt(byte[] bytes) {
return ((0xFF & bytes[0]) << 8) | (0xFF & bytes[1]);
}
private int readLong(byte[] bytes) {
return ((0xFF & bytes[0]) << 24) | ((0xFF & bytes[1]) << 16) | ((0xFF & bytes[2]) << 8)
| (0xFF & bytes[3]);
}
private String readTag(byte[] tag) {
return new String(tag);
}
}
|