001/* 002 * $Id: TrueTypeFontSubSet.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.util.ArrayList; 048import java.util.Arrays; 049import java.util.HashMap; 050import java.util.HashSet; 051 052import com.itextpdf.text.DocumentException; 053import com.itextpdf.text.ExceptionConverter; 054import com.itextpdf.text.error_messages.MessageLocalization; 055 056/** Subsets a True Type font by removing the unneeded glyphs from 057 * the font. 058 * 059 * @author Paulo Soares 060 */ 061class TrueTypeFontSubSet { 062 static final String tableNamesSimple[] = {"cvt ", "fpgm", "glyf", "head", 063 "hhea", "hmtx", "loca", "maxp", "prep"}; 064 static final String tableNamesCmap[] = {"cmap", "cvt ", "fpgm", "glyf", "head", 065 "hhea", "hmtx", "loca", "maxp", "prep"}; 066 static final String tableNamesExtra[] = {"OS/2", "cmap", "cvt ", "fpgm", "glyf", "head", 067 "hhea", "hmtx", "loca", "maxp", "name, prep"}; 068 static final int entrySelectors[] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4}; 069 static final int TABLE_CHECKSUM = 0; 070 static final int TABLE_OFFSET = 1; 071 static final int TABLE_LENGTH = 2; 072 static final int HEAD_LOCA_FORMAT_OFFSET = 51; 073 074 static final int ARG_1_AND_2_ARE_WORDS = 1; 075 static final int WE_HAVE_A_SCALE = 8; 076 static final int MORE_COMPONENTS = 32; 077 static final int WE_HAVE_AN_X_AND_Y_SCALE = 64; 078 static final int WE_HAVE_A_TWO_BY_TWO = 128; 079 080 081 /** Contains the location of the several tables. The key is the name of 082 * the table and the value is an <CODE>int[3]</CODE> where position 0 083 * is the checksum, position 1 is the offset from the start of the file 084 * and position 2 is the length of the table. 085 */ 086 protected HashMap<String, int[]> tableDirectory; 087 /** The file in use. 088 */ 089 protected RandomAccessFileOrArray rf; 090 /** The file name. 091 */ 092 protected String fileName; 093 protected boolean includeCmap; 094 protected boolean includeExtras; 095 protected boolean locaShortTable; 096 protected int locaTable[]; 097 protected HashSet<Integer> glyphsUsed; 098 protected ArrayList<Integer> glyphsInList; 099 protected int tableGlyphOffset; 100 protected int newLocaTable[]; 101 protected byte newLocaTableOut[]; 102 protected byte newGlyfTable[]; 103 protected int glyfTableRealSize; 104 protected int locaTableRealSize; 105 protected byte outFont[]; 106 protected int fontPtr; 107 protected int directoryOffset; 108 109 /** Creates a new TrueTypeFontSubSet 110 * @param directoryOffset The offset from the start of the file to the table directory 111 * @param fileName the file name of the font 112 * @param glyphsUsed the glyphs used 113 * @param includeCmap <CODE>true</CODE> if the table cmap is to be included in the generated font 114 */ 115 TrueTypeFontSubSet(String fileName, RandomAccessFileOrArray rf, HashSet<Integer> glyphsUsed, int directoryOffset, boolean includeCmap, boolean includeExtras) { 116 this.fileName = fileName; 117 this.rf = rf; 118 this.glyphsUsed = glyphsUsed; 119 this.includeCmap = includeCmap; 120 this.includeExtras = includeExtras; 121 this.directoryOffset = directoryOffset; 122 glyphsInList = new ArrayList<Integer>(glyphsUsed); 123 } 124 125 /** Does the actual work of subsetting the font. 126 * @throws IOException on error 127 * @throws DocumentException on error 128 * @return the subset font 129 */ 130 byte[] process() throws IOException, DocumentException { 131 try { 132 rf.reOpen(); 133 createTableDirectory(); 134 readLoca(); 135 flatGlyphs(); 136 createNewGlyphTables(); 137 locaTobytes(); 138 assembleFont(); 139 return outFont; 140 } 141 finally { 142 try { 143 rf.close(); 144 } 145 catch (Exception e) { 146 // empty on purpose 147 } 148 } 149 } 150 151 protected void assembleFont() throws IOException { 152 int tableLocation[]; 153 int fullFontSize = 0; 154 String tableNames[]; 155 if (includeExtras) 156 tableNames = tableNamesExtra; 157 else { 158 if (includeCmap) 159 tableNames = tableNamesCmap; 160 else 161 tableNames = tableNamesSimple; 162 } 163 int tablesUsed = 2; 164 int len = 0; 165 for (int k = 0; k < tableNames.length; ++k) { 166 String name = tableNames[k]; 167 if (name.equals("glyf") || name.equals("loca")) 168 continue; 169 tableLocation = tableDirectory.get(name); 170 if (tableLocation == null) 171 continue; 172 ++tablesUsed; 173 fullFontSize += tableLocation[TABLE_LENGTH] + 3 & ~3; 174 } 175 fullFontSize += newLocaTableOut.length; 176 fullFontSize += newGlyfTable.length; 177 int ref = 16 * tablesUsed + 12; 178 fullFontSize += ref; 179 outFont = new byte[fullFontSize]; 180 fontPtr = 0; 181 writeFontInt(0x00010000); 182 writeFontShort(tablesUsed); 183 int selector = entrySelectors[tablesUsed]; 184 writeFontShort((1 << selector) * 16); 185 writeFontShort(selector); 186 writeFontShort((tablesUsed - (1 << selector)) * 16); 187 for (int k = 0; k < tableNames.length; ++k) { 188 String name = tableNames[k]; 189 tableLocation = tableDirectory.get(name); 190 if (tableLocation == null) 191 continue; 192 writeFontString(name); 193 if (name.equals("glyf")) { 194 writeFontInt(calculateChecksum(newGlyfTable)); 195 len = glyfTableRealSize; 196 } 197 else if (name.equals("loca")) { 198 writeFontInt(calculateChecksum(newLocaTableOut)); 199 len = locaTableRealSize; 200 } 201 else { 202 writeFontInt(tableLocation[TABLE_CHECKSUM]); 203 len = tableLocation[TABLE_LENGTH]; 204 } 205 writeFontInt(ref); 206 writeFontInt(len); 207 ref += len + 3 & ~3; 208 } 209 for (int k = 0; k < tableNames.length; ++k) { 210 String name = tableNames[k]; 211 tableLocation = tableDirectory.get(name); 212 if (tableLocation == null) 213 continue; 214 if (name.equals("glyf")) { 215 System.arraycopy(newGlyfTable, 0, outFont, fontPtr, newGlyfTable.length); 216 fontPtr += newGlyfTable.length; 217 newGlyfTable = null; 218 } 219 else if (name.equals("loca")) { 220 System.arraycopy(newLocaTableOut, 0, outFont, fontPtr, newLocaTableOut.length); 221 fontPtr += newLocaTableOut.length; 222 newLocaTableOut = null; 223 } 224 else { 225 rf.seek(tableLocation[TABLE_OFFSET]); 226 rf.readFully(outFont, fontPtr, tableLocation[TABLE_LENGTH]); 227 fontPtr += tableLocation[TABLE_LENGTH] + 3 & ~3; 228 } 229 } 230 } 231 232 protected void createTableDirectory() throws IOException, DocumentException { 233 tableDirectory = new HashMap<String, int[]>(); 234 rf.seek(directoryOffset); 235 int id = rf.readInt(); 236 if (id != 0x00010000) 237 throw new DocumentException(MessageLocalization.getComposedMessage("1.is.not.a.true.type.file", fileName)); 238 int num_tables = rf.readUnsignedShort(); 239 rf.skipBytes(6); 240 for (int k = 0; k < num_tables; ++k) { 241 String tag = readStandardString(4); 242 int tableLocation[] = new int[3]; 243 tableLocation[TABLE_CHECKSUM] = rf.readInt(); 244 tableLocation[TABLE_OFFSET] = rf.readInt(); 245 tableLocation[TABLE_LENGTH] = rf.readInt(); 246 tableDirectory.put(tag, tableLocation); 247 } 248 } 249 250 protected void readLoca() throws IOException, DocumentException { 251 int tableLocation[]; 252 tableLocation = tableDirectory.get("head"); 253 if (tableLocation == null) 254 throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "head", fileName)); 255 rf.seek(tableLocation[TABLE_OFFSET] + HEAD_LOCA_FORMAT_OFFSET); 256 locaShortTable = rf.readUnsignedShort() == 0; 257 tableLocation = tableDirectory.get("loca"); 258 if (tableLocation == null) 259 throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "loca", fileName)); 260 rf.seek(tableLocation[TABLE_OFFSET]); 261 if (locaShortTable) { 262 int entries = tableLocation[TABLE_LENGTH] / 2; 263 locaTable = new int[entries]; 264 for (int k = 0; k < entries; ++k) 265 locaTable[k] = rf.readUnsignedShort() * 2; 266 } 267 else { 268 int entries = tableLocation[TABLE_LENGTH] / 4; 269 locaTable = new int[entries]; 270 for (int k = 0; k < entries; ++k) 271 locaTable[k] = rf.readInt(); 272 } 273 } 274 275 protected void createNewGlyphTables() throws IOException { 276 newLocaTable = new int[locaTable.length]; 277 int activeGlyphs[] = new int[glyphsInList.size()]; 278 for (int k = 0; k < activeGlyphs.length; ++k) 279 activeGlyphs[k] = glyphsInList.get(k).intValue(); 280 Arrays.sort(activeGlyphs); 281 int glyfSize = 0; 282 for (int k = 0; k < activeGlyphs.length; ++k) { 283 int glyph = activeGlyphs[k]; 284 glyfSize += locaTable[glyph + 1] - locaTable[glyph]; 285 } 286 glyfTableRealSize = glyfSize; 287 glyfSize = glyfSize + 3 & ~3; 288 newGlyfTable = new byte[glyfSize]; 289 int glyfPtr = 0; 290 int listGlyf = 0; 291 for (int k = 0; k < newLocaTable.length; ++k) { 292 newLocaTable[k] = glyfPtr; 293 if (listGlyf < activeGlyphs.length && activeGlyphs[listGlyf] == k) { 294 ++listGlyf; 295 newLocaTable[k] = glyfPtr; 296 int start = locaTable[k]; 297 int len = locaTable[k + 1] - start; 298 if (len > 0) { 299 rf.seek(tableGlyphOffset + start); 300 rf.readFully(newGlyfTable, glyfPtr, len); 301 glyfPtr += len; 302 } 303 } 304 } 305 } 306 307 protected void locaTobytes() { 308 if (locaShortTable) 309 locaTableRealSize = newLocaTable.length * 2; 310 else 311 locaTableRealSize = newLocaTable.length * 4; 312 newLocaTableOut = new byte[locaTableRealSize + 3 & ~3]; 313 outFont = newLocaTableOut; 314 fontPtr = 0; 315 for (int k = 0; k < newLocaTable.length; ++k) { 316 if (locaShortTable) 317 writeFontShort(newLocaTable[k] / 2); 318 else 319 writeFontInt(newLocaTable[k]); 320 } 321 322 } 323 324 protected void flatGlyphs() throws IOException, DocumentException { 325 int tableLocation[]; 326 tableLocation = tableDirectory.get("glyf"); 327 if (tableLocation == null) 328 throw new DocumentException(MessageLocalization.getComposedMessage("table.1.does.not.exist.in.2", "glyf", fileName)); 329 Integer glyph0 = Integer.valueOf(0); 330 if (!glyphsUsed.contains(glyph0)) { 331 glyphsUsed.add(glyph0); 332 glyphsInList.add(glyph0); 333 } 334 tableGlyphOffset = tableLocation[TABLE_OFFSET]; 335 for (int k = 0; k < glyphsInList.size(); ++k) { 336 int glyph = glyphsInList.get(k).intValue(); 337 checkGlyphComposite(glyph); 338 } 339 } 340 341 protected void checkGlyphComposite(int glyph) throws IOException { 342 int start = locaTable[glyph]; 343 if (start == locaTable[glyph + 1]) // no contour 344 return; 345 rf.seek(tableGlyphOffset + start); 346 int numContours = rf.readShort(); 347 if (numContours >= 0) 348 return; 349 rf.skipBytes(8); 350 for(;;) { 351 int flags = rf.readUnsignedShort(); 352 Integer cGlyph = Integer.valueOf(rf.readUnsignedShort()); 353 if (!glyphsUsed.contains(cGlyph)) { 354 glyphsUsed.add(cGlyph); 355 glyphsInList.add(cGlyph); 356 } 357 if ((flags & MORE_COMPONENTS) == 0) 358 return; 359 int skip; 360 if ((flags & ARG_1_AND_2_ARE_WORDS) != 0) 361 skip = 4; 362 else 363 skip = 2; 364 if ((flags & WE_HAVE_A_SCALE) != 0) 365 skip += 2; 366 else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) 367 skip += 4; 368 if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) 369 skip += 8; 370 rf.skipBytes(skip); 371 } 372 } 373 374 /** Reads a <CODE>String</CODE> from the font file as bytes using the Cp1252 375 * encoding. 376 * @param length the length of bytes to read 377 * @return the <CODE>String</CODE> read 378 * @throws IOException the font file could not be read 379 */ 380 protected String readStandardString(int length) throws IOException { 381 byte buf[] = new byte[length]; 382 rf.readFully(buf); 383 try { 384 return new String(buf, BaseFont.WINANSI); 385 } 386 catch (Exception e) { 387 throw new ExceptionConverter(e); 388 } 389 } 390 391 protected void writeFontShort(int n) { 392 outFont[fontPtr++] = (byte)(n >> 8); 393 outFont[fontPtr++] = (byte)n; 394 } 395 396 protected void writeFontInt(int n) { 397 outFont[fontPtr++] = (byte)(n >> 24); 398 outFont[fontPtr++] = (byte)(n >> 16); 399 outFont[fontPtr++] = (byte)(n >> 8); 400 outFont[fontPtr++] = (byte)n; 401 } 402 403 protected void writeFontString(String s) { 404 byte b[] = PdfEncodings.convertToBytes(s, BaseFont.WINANSI); 405 System.arraycopy(b, 0, outFont, fontPtr, b.length); 406 fontPtr += b.length; 407 } 408 409 protected int calculateChecksum(byte b[]) { 410 int len = b.length / 4; 411 int v0 = 0; 412 int v1 = 0; 413 int v2 = 0; 414 int v3 = 0; 415 int ptr = 0; 416 for (int k = 0; k < len; ++k) { 417 v3 += b[ptr++] & 0xff; 418 v2 += b[ptr++] & 0xff; 419 v1 += b[ptr++] & 0xff; 420 v0 += b[ptr++] & 0xff; 421 } 422 return v0 + (v1 << 8) + (v2 << 16) + (v3 << 24); 423 } 424}