You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TTFSubSetFile.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fonts.truetype;
  19. import java.io.IOException;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Set;
  24. /**
  25. * Reads a TrueType file and generates a subset
  26. * that can be used to embed a TrueType CID font.
  27. * TrueType tables needed for embedded CID fonts are:
  28. * "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm".
  29. * The TrueType spec can be found at the Microsoft
  30. * Typography site: http://www.microsoft.com/truetype/
  31. */
  32. public class TTFSubSetFile extends TTFFile {
  33. private byte[] output = null;
  34. private int realSize = 0;
  35. private int currentPos = 0;
  36. /*
  37. * Offsets in name table to be filled out by table.
  38. * The offsets are to the checkSum field
  39. */
  40. private int cvtDirOffset = 0;
  41. private int fpgmDirOffset = 0;
  42. private int glyfDirOffset = 0;
  43. private int headDirOffset = 0;
  44. private int hheaDirOffset = 0;
  45. private int hmtxDirOffset = 0;
  46. private int locaDirOffset = 0;
  47. private int maxpDirOffset = 0;
  48. private int prepDirOffset = 0;
  49. private int checkSumAdjustmentOffset = 0;
  50. private int locaOffset = 0;
  51. /**
  52. * Initalize the output array
  53. */
  54. private void init(int size) {
  55. output = new byte[size];
  56. realSize = 0;
  57. currentPos = 0;
  58. // createDirectory()
  59. }
  60. private int determineTableCount() {
  61. int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp
  62. if (isCFF()) {
  63. throw new UnsupportedOperationException(
  64. "OpenType fonts with CFF glyphs are not supported");
  65. } else {
  66. numTables += 2; //1 req'd table: glyf,loca
  67. if (hasCvt()) {
  68. numTables++;
  69. }
  70. if (hasFpgm()) {
  71. numTables++;
  72. }
  73. if (hasPrep()) {
  74. numTables++;
  75. }
  76. }
  77. return numTables;
  78. }
  79. /**
  80. * Create the directory table
  81. */
  82. private void createDirectory() {
  83. int numTables = determineTableCount();
  84. // Create the TrueType header
  85. writeByte((byte)0);
  86. writeByte((byte)1);
  87. writeByte((byte)0);
  88. writeByte((byte)0);
  89. realSize += 4;
  90. writeUShort(numTables);
  91. realSize += 2;
  92. // Create searchRange, entrySelector and rangeShift
  93. int maxPow = maxPow2(numTables);
  94. int searchRange = maxPow * 16;
  95. writeUShort(searchRange);
  96. realSize += 2;
  97. writeUShort(maxPow);
  98. realSize += 2;
  99. writeUShort((numTables * 16) - searchRange);
  100. realSize += 2;
  101. // Create space for the table entries
  102. if (hasCvt()) {
  103. writeString("cvt ");
  104. cvtDirOffset = currentPos;
  105. currentPos += 12;
  106. realSize += 16;
  107. }
  108. if (hasFpgm()) {
  109. writeString("fpgm");
  110. fpgmDirOffset = currentPos;
  111. currentPos += 12;
  112. realSize += 16;
  113. }
  114. writeString("glyf");
  115. glyfDirOffset = currentPos;
  116. currentPos += 12;
  117. realSize += 16;
  118. writeString("head");
  119. headDirOffset = currentPos;
  120. currentPos += 12;
  121. realSize += 16;
  122. writeString("hhea");
  123. hheaDirOffset = currentPos;
  124. currentPos += 12;
  125. realSize += 16;
  126. writeString("hmtx");
  127. hmtxDirOffset = currentPos;
  128. currentPos += 12;
  129. realSize += 16;
  130. writeString("loca");
  131. locaDirOffset = currentPos;
  132. currentPos += 12;
  133. realSize += 16;
  134. writeString("maxp");
  135. maxpDirOffset = currentPos;
  136. currentPos += 12;
  137. realSize += 16;
  138. if (hasPrep()) {
  139. writeString("prep");
  140. prepDirOffset = currentPos;
  141. currentPos += 12;
  142. realSize += 16;
  143. }
  144. }
  145. /**
  146. * Copy the cvt table as is from original font to subset font
  147. */
  148. private boolean createCvt(FontFileReader in) throws IOException {
  149. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt ");
  150. if (entry != null) {
  151. pad4();
  152. seekTab(in, "cvt ", 0);
  153. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  154. 0, output, currentPos, (int)entry.getLength());
  155. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  156. writeULong(cvtDirOffset, checksum);
  157. writeULong(cvtDirOffset + 4, currentPos);
  158. writeULong(cvtDirOffset + 8, (int)entry.getLength());
  159. currentPos += (int)entry.getLength();
  160. realSize += (int)entry.getLength();
  161. return true;
  162. } else {
  163. return false;
  164. //throw new IOException("Can't find cvt table");
  165. }
  166. }
  167. private boolean hasCvt() {
  168. return dirTabs.containsKey("cvt ");
  169. }
  170. private boolean hasFpgm() {
  171. return dirTabs.containsKey("fpgm");
  172. }
  173. private boolean hasPrep() {
  174. return dirTabs.containsKey("prep");
  175. }
  176. /**
  177. * Copy the fpgm table as is from original font to subset font
  178. */
  179. private boolean createFpgm(FontFileReader in) throws IOException {
  180. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm");
  181. if (entry != null) {
  182. pad4();
  183. seekTab(in, "fpgm", 0);
  184. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  185. 0, output, currentPos, (int)entry.getLength());
  186. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  187. writeULong(fpgmDirOffset, checksum);
  188. writeULong(fpgmDirOffset + 4, currentPos);
  189. writeULong(fpgmDirOffset + 8, (int)entry.getLength());
  190. currentPos += (int)entry.getLength();
  191. realSize += (int)entry.getLength();
  192. return true;
  193. } else {
  194. return false;
  195. }
  196. }
  197. /**
  198. * Create an empty loca table without updating checksum
  199. */
  200. private void createLoca(int size) throws IOException {
  201. pad4();
  202. locaOffset = currentPos;
  203. writeULong(locaDirOffset + 4, currentPos);
  204. writeULong(locaDirOffset + 8, size * 4 + 4);
  205. currentPos += size * 4 + 4;
  206. realSize += size * 4 + 4;
  207. }
  208. /**
  209. * Copy the maxp table as is from original font to subset font
  210. * and set num glyphs to size
  211. */
  212. private void createMaxp(FontFileReader in, int size) throws IOException {
  213. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp");
  214. if (entry != null) {
  215. pad4();
  216. seekTab(in, "maxp", 0);
  217. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  218. 0, output, currentPos, (int)entry.getLength());
  219. writeUShort(currentPos + 4, size);
  220. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  221. writeULong(maxpDirOffset, checksum);
  222. writeULong(maxpDirOffset + 4, currentPos);
  223. writeULong(maxpDirOffset + 8, (int)entry.getLength());
  224. currentPos += (int)entry.getLength();
  225. realSize += (int)entry.getLength();
  226. } else {
  227. throw new IOException("Can't find maxp table");
  228. }
  229. }
  230. /**
  231. * Copy the prep table as is from original font to subset font
  232. */
  233. private boolean createPrep(FontFileReader in) throws IOException {
  234. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep");
  235. if (entry != null) {
  236. pad4();
  237. seekTab(in, "prep", 0);
  238. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  239. 0, output, currentPos, (int)entry.getLength());
  240. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  241. writeULong(prepDirOffset, checksum);
  242. writeULong(prepDirOffset + 4, currentPos);
  243. writeULong(prepDirOffset + 8, (int)entry.getLength());
  244. currentPos += (int)entry.getLength();
  245. realSize += (int)entry.getLength();
  246. return true;
  247. } else {
  248. return false;
  249. }
  250. }
  251. /**
  252. * Copy the hhea table as is from original font to subset font
  253. * and fill in size of hmtx table
  254. */
  255. private void createHhea(FontFileReader in, int size) throws IOException {
  256. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea");
  257. if (entry != null) {
  258. pad4();
  259. seekTab(in, "hhea", 0);
  260. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  261. 0, output, currentPos, (int)entry.getLength());
  262. writeUShort((int)entry.getLength() + currentPos - 2, size);
  263. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  264. writeULong(hheaDirOffset, checksum);
  265. writeULong(hheaDirOffset + 4, currentPos);
  266. writeULong(hheaDirOffset + 8, (int)entry.getLength());
  267. currentPos += (int)entry.getLength();
  268. realSize += (int)entry.getLength();
  269. } else {
  270. throw new IOException("Can't find hhea table");
  271. }
  272. }
  273. /**
  274. * Copy the head table as is from original font to subset font
  275. * and set indexToLocaFormat to long and set
  276. * checkSumAdjustment to 0, store offset to checkSumAdjustment
  277. * in checkSumAdjustmentOffset
  278. */
  279. private void createHead(FontFileReader in) throws IOException {
  280. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head");
  281. if (entry != null) {
  282. pad4();
  283. seekTab(in, "head", 0);
  284. System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
  285. 0, output, currentPos, (int)entry.getLength());
  286. checkSumAdjustmentOffset = currentPos + 8;
  287. output[currentPos + 8] = 0; // Set checkSumAdjustment to 0
  288. output[currentPos + 9] = 0;
  289. output[currentPos + 10] = 0;
  290. output[currentPos + 11] = 0;
  291. output[currentPos + 50] = 0; // long locaformat
  292. output[currentPos + 51] = 1; // long locaformat
  293. int checksum = getCheckSum(currentPos, (int)entry.getLength());
  294. writeULong(headDirOffset, checksum);
  295. writeULong(headDirOffset + 4, currentPos);
  296. writeULong(headDirOffset + 8, (int)entry.getLength());
  297. currentPos += (int)entry.getLength();
  298. realSize += (int)entry.getLength();
  299. } else {
  300. throw new IOException("Can't find head table");
  301. }
  302. }
  303. /**
  304. * Create the glyf table and fill in loca table
  305. */
  306. private void createGlyf(FontFileReader in,
  307. Map glyphs) throws IOException {
  308. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
  309. int size = 0;
  310. int start = 0;
  311. int endOffset = 0; // Store this as the last loca
  312. if (entry != null) {
  313. pad4();
  314. start = currentPos;
  315. /* Loca table must be in order by glyph index, so build
  316. * an array first and then write the glyph info and
  317. * location offset.
  318. */
  319. int[] origIndexes = new int[glyphs.size()];
  320. Iterator e = glyphs.keySet().iterator();
  321. while (e.hasNext()) {
  322. Integer origIndex = (Integer)e.next();
  323. Integer subsetIndex = (Integer)glyphs.get(origIndex);
  324. origIndexes[subsetIndex.intValue()] = origIndex.intValue();
  325. }
  326. for (int i = 0; i < origIndexes.length; i++) {
  327. int glyphLength = 0;
  328. int nextOffset = 0;
  329. int origGlyphIndex = origIndexes[i];
  330. if (origGlyphIndex >= (mtxTab.length - 1)) {
  331. nextOffset = (int)lastLoca;
  332. } else {
  333. nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset();
  334. }
  335. glyphLength = nextOffset - (int)mtxTab[origGlyphIndex].getOffset();
  336. // Copy glyph
  337. System.arraycopy(
  338. in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(),
  339. glyphLength), 0,
  340. output, currentPos,
  341. glyphLength);
  342. // Update loca table
  343. writeULong(locaOffset + i * 4, currentPos - start);
  344. if ((currentPos - start + glyphLength) > endOffset) {
  345. endOffset = (currentPos - start + glyphLength);
  346. }
  347. currentPos += glyphLength;
  348. realSize += glyphLength;
  349. }
  350. size = currentPos - start;
  351. int checksum = getCheckSum(start, size);
  352. writeULong(glyfDirOffset, checksum);
  353. writeULong(glyfDirOffset + 4, start);
  354. writeULong(glyfDirOffset + 8, size);
  355. currentPos += 12;
  356. realSize += 12;
  357. // Update loca checksum and last loca index
  358. writeULong(locaOffset + glyphs.size() * 4, endOffset);
  359. checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4);
  360. writeULong(locaDirOffset, checksum);
  361. } else {
  362. throw new IOException("Can't find glyf table");
  363. }
  364. }
  365. /**
  366. * Create the hmtx table by copying metrics from original
  367. * font to subset font. The glyphs Map contains an
  368. * Integer key and Integer value that maps the original
  369. * metric (key) to the subset metric (value)
  370. */
  371. private void createHmtx(FontFileReader in,
  372. Map glyphs) throws IOException {
  373. TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx");
  374. int longHorMetricSize = glyphs.size() * 2;
  375. int leftSideBearingSize = glyphs.size() * 2;
  376. int hmtxSize = longHorMetricSize + leftSideBearingSize;
  377. if (entry != null) {
  378. pad4();
  379. //int offset = (int)entry.offset;
  380. Iterator e = glyphs.keySet().iterator();
  381. while (e.hasNext()) {
  382. Integer origIndex = (Integer)e.next();
  383. Integer subsetIndex = (Integer)glyphs.get(origIndex);
  384. writeUShort(currentPos + subsetIndex.intValue() * 4,
  385. mtxTab[origIndex.intValue()].getWx());
  386. writeUShort(currentPos + subsetIndex.intValue() * 4 + 2,
  387. mtxTab[origIndex.intValue()].getLsb());
  388. }
  389. int checksum = getCheckSum(currentPos, hmtxSize);
  390. writeULong(hmtxDirOffset, checksum);
  391. writeULong(hmtxDirOffset + 4, currentPos);
  392. writeULong(hmtxDirOffset + 8, hmtxSize);
  393. currentPos += hmtxSize;
  394. realSize += hmtxSize;
  395. } else {
  396. throw new IOException("Can't find hmtx table");
  397. }
  398. }
  399. /**
  400. * Returns a List containing the glyph itself plus all glyphs
  401. * that this composite glyph uses
  402. */
  403. private List<Integer> getIncludedGlyphs(FontFileReader in, int glyphOffset,
  404. Integer glyphIdx) throws IOException {
  405. List<Integer> ret = new java.util.ArrayList<Integer>();
  406. ret.add(glyphIdx);
  407. int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + 10;
  408. Integer compositeIdx = null;
  409. int flags = 0;
  410. boolean moreComposites = true;
  411. while (moreComposites) {
  412. flags = in.readTTFUShort(offset);
  413. compositeIdx = Integer.valueOf(in.readTTFUShort(offset + 2));
  414. ret.add(compositeIdx);
  415. offset += 4;
  416. if ((flags & 1) > 0) {
  417. // ARG_1_AND_ARG_2_ARE_WORDS
  418. offset += 4;
  419. } else {
  420. offset += 2;
  421. }
  422. if ((flags & 8) > 0) {
  423. offset += 2; // WE_HAVE_A_SCALE
  424. } else if ((flags & 64) > 0) {
  425. offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE
  426. } else if ((flags & 128) > 0) {
  427. offset += 8; // WE_HAVE_A_TWO_BY_TWO
  428. }
  429. if ((flags & 32) > 0) {
  430. moreComposites = true;
  431. } else {
  432. moreComposites = false;
  433. }
  434. }
  435. return ret;
  436. }
  437. /**
  438. * We need to remember which composites were already remapped because the value to be
  439. * remapped is being read from the TTF file and being replaced right there. Doing this
  440. * twice would create a bad map the second time.
  441. */
  442. private Set<Long> remappedComposites = null;
  443. /**
  444. * Rewrite all compositepointers in glyphindex glyphIdx
  445. */
  446. private void remapComposite(FontFileReader in, Map<Integer, Integer> glyphs,
  447. long glyphOffset,
  448. Integer glyphIdx) throws IOException {
  449. if (remappedComposites == null) {
  450. remappedComposites = new java.util.HashSet<Long>();
  451. }
  452. TTFMtxEntry mtxEntry = mtxTab[glyphIdx.intValue()];
  453. long offset = glyphOffset + mtxEntry.getOffset() + 10;
  454. //Avoid duplicate remapping (see javadoc for remappedComposites above)
  455. if (!remappedComposites.contains(offset)) {
  456. remappedComposites.add(offset);
  457. innerRemapComposite(in, glyphs, offset);
  458. }
  459. }
  460. private void innerRemapComposite(FontFileReader in, Map<Integer, Integer> glyphs, long offset)
  461. throws IOException {
  462. Integer compositeIdx = null;
  463. int flags = 0;
  464. boolean moreComposites = true;
  465. while (moreComposites) {
  466. flags = in.readTTFUShort(offset);
  467. compositeIdx = Integer.valueOf(in.readTTFUShort(offset + 2));
  468. Integer newIdx = glyphs.get(compositeIdx);
  469. if (newIdx == null) {
  470. // This errormessage would look much better
  471. // if the fontname was printed to
  472. //log.error("An embedded font "
  473. // + "contains bad glyph data. "
  474. // + "Characters might not display "
  475. // + "correctly.");
  476. moreComposites = false;
  477. continue;
  478. }
  479. in.writeTTFUShort((int)(offset + 2), newIdx.intValue());
  480. offset += 4;
  481. if ((flags & 1) > 0) {
  482. // ARG_1_AND_ARG_2_ARE_WORDS
  483. offset += 4;
  484. } else {
  485. offset += 2;
  486. }
  487. if ((flags & 8) > 0) {
  488. offset += 2; // WE_HAVE_A_SCALE
  489. } else if ((flags & 64) > 0) {
  490. offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE
  491. } else if ((flags & 128) > 0) {
  492. offset += 8; // WE_HAVE_A_TWO_BY_TWO
  493. }
  494. if ((flags & 32) > 0) {
  495. moreComposites = true;
  496. } else {
  497. moreComposites = false;
  498. }
  499. }
  500. }
  501. /**
  502. * Scan all the original glyphs for composite glyphs and add those glyphs
  503. * to the glyphmapping also rewrite the composite glyph pointers to the new
  504. * mapping
  505. */
  506. private void scanGlyphs(FontFileReader in,
  507. Map<Integer, Integer> glyphs) throws IOException {
  508. TTFDirTabEntry glyfTable = (TTFDirTabEntry)dirTabs.get("glyf");
  509. Map<Integer, Integer> newComposites = null;
  510. Set<Integer> allComposites = new java.util.HashSet<Integer>();
  511. int newIndex = glyphs.size();
  512. if (glyfTable != null) {
  513. while (newComposites == null || newComposites.size() > 0) {
  514. // Inefficient to iterate through all glyphs
  515. newComposites = new java.util.HashMap<Integer, Integer>();
  516. for (int origIndex : glyphs.keySet()) {
  517. short numberOfContours = in.readTTFShort(glyfTable.getOffset()
  518. + mtxTab[origIndex].getOffset());
  519. if (numberOfContours < 0) {
  520. // origIndex is a composite glyph
  521. allComposites.add(origIndex);
  522. List<Integer> composites
  523. = getIncludedGlyphs(in, (int)glyfTable.getOffset(),
  524. origIndex);
  525. if (log.isTraceEnabled()) {
  526. log.trace("Glyph " + origIndex
  527. + " is a composite glyph using the following glyphs: "
  528. + composites);
  529. }
  530. // Iterate through all composites pointed to
  531. // by this composite and check if they exists
  532. // in the glyphs map, add them if not.
  533. for (int cIdx : composites) {
  534. if (glyphs.get(cIdx) == null
  535. && newComposites.get(cIdx) == null) {
  536. newComposites.put(cIdx, newIndex);
  537. newIndex++;
  538. }
  539. }
  540. }
  541. }
  542. // Add composites to glyphs
  543. for (int im : newComposites.keySet()) {
  544. glyphs.put(im, newComposites.get(im));
  545. }
  546. }
  547. // Iterate through all composites to remap their composite index
  548. for (int glyphIdx : allComposites) {
  549. remapComposite(in, glyphs, glyfTable.getOffset(), glyphIdx);
  550. }
  551. } else {
  552. throw new IOException("Can't find glyf table");
  553. }
  554. }
  555. /**
  556. * Returns a subset of the original font.
  557. *
  558. * @param in FontFileReader to read from
  559. * @param name Name to be checked for in the font file
  560. * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
  561. * new index as (Integer) value)
  562. * @return A subset of the original font
  563. * @throws IOException in case of an I/O problem
  564. */
  565. public byte[] readFont(FontFileReader in, String name,
  566. Map<Integer, Integer> glyphs) throws IOException {
  567. //Check if TrueType collection, and that the name exists in the collection
  568. if (!checkTTC(in, name)) {
  569. throw new IOException("Failed to read font");
  570. }
  571. //Copy the Map as we're going to modify it
  572. Map<Integer, Integer> subsetGlyphs = new java.util.HashMap<Integer, Integer>(glyphs);
  573. output = new byte[in.getFileSize()];
  574. readDirTabs(in);
  575. readFontHeader(in);
  576. getNumGlyphs(in);
  577. readHorizontalHeader(in);
  578. readHorizontalMetrics(in);
  579. readIndexToLocation(in);
  580. scanGlyphs(in, subsetGlyphs);
  581. createDirectory(); // Create the TrueType header and directory
  582. createHead(in);
  583. createHhea(in, subsetGlyphs.size()); // Create the hhea table
  584. createHmtx(in, subsetGlyphs); // Create hmtx table
  585. createMaxp(in, subsetGlyphs.size()); // copy the maxp table
  586. boolean optionalTableFound;
  587. optionalTableFound = createCvt(in); // copy the cvt table
  588. if (!optionalTableFound) {
  589. // cvt is optional (used in TrueType fonts only)
  590. log.debug("TrueType: ctv table not present. Skipped.");
  591. }
  592. optionalTableFound = createFpgm(in); // copy fpgm table
  593. if (!optionalTableFound) {
  594. // fpgm is optional (used in TrueType fonts only)
  595. log.debug("TrueType: fpgm table not present. Skipped.");
  596. }
  597. optionalTableFound = createPrep(in); // copy prep table
  598. if (!optionalTableFound) {
  599. // prep is optional (used in TrueType fonts only)
  600. log.debug("TrueType: prep table not present. Skipped.");
  601. }
  602. createLoca(subsetGlyphs.size()); // create empty loca table
  603. createGlyf(in, subsetGlyphs); //create glyf table and update loca table
  604. pad4();
  605. createCheckSumAdjustment();
  606. byte[] ret = new byte[realSize];
  607. System.arraycopy(output, 0, ret, 0, realSize);
  608. return ret;
  609. }
  610. /**
  611. * writes a ISO-8859-1 string at the currentPosition
  612. * updates currentPosition but not realSize
  613. * @return number of bytes written
  614. */
  615. private int writeString(String str) {
  616. int length = 0;
  617. try {
  618. byte[] buf = str.getBytes("ISO-8859-1");
  619. System.arraycopy(buf, 0, output, currentPos, buf.length);
  620. length = buf.length;
  621. currentPos += length;
  622. } catch (java.io.UnsupportedEncodingException e) {
  623. // This should never happen!
  624. }
  625. return length;
  626. }
  627. /**
  628. * Appends a byte to the output array,
  629. * updates currentPost but not realSize
  630. */
  631. private void writeByte(byte b) {
  632. output[currentPos++] = b;
  633. }
  634. /**
  635. * Appends a USHORT to the output array,
  636. * updates currentPost but not realSize
  637. */
  638. private void writeUShort(int s) {
  639. byte b1 = (byte)((s >> 8) & 0xff);
  640. byte b2 = (byte)(s & 0xff);
  641. writeByte(b1);
  642. writeByte(b2);
  643. }
  644. /**
  645. * Appends a USHORT to the output array,
  646. * at the given position without changing currentPos
  647. */
  648. private void writeUShort(int pos, int s) {
  649. byte b1 = (byte)((s >> 8) & 0xff);
  650. byte b2 = (byte)(s & 0xff);
  651. output[pos] = b1;
  652. output[pos + 1] = b2;
  653. }
  654. /**
  655. * Appends a ULONG to the output array,
  656. * updates currentPos but not realSize
  657. */
  658. private void writeULong(int s) {
  659. byte b1 = (byte)((s >> 24) & 0xff);
  660. byte b2 = (byte)((s >> 16) & 0xff);
  661. byte b3 = (byte)((s >> 8) & 0xff);
  662. byte b4 = (byte)(s & 0xff);
  663. writeByte(b1);
  664. writeByte(b2);
  665. writeByte(b3);
  666. writeByte(b4);
  667. }
  668. /**
  669. * Appends a ULONG to the output array,
  670. * at the given position without changing currentPos
  671. */
  672. private void writeULong(int pos, int s) {
  673. byte b1 = (byte)((s >> 24) & 0xff);
  674. byte b2 = (byte)((s >> 16) & 0xff);
  675. byte b3 = (byte)((s >> 8) & 0xff);
  676. byte b4 = (byte)(s & 0xff);
  677. output[pos] = b1;
  678. output[pos + 1] = b2;
  679. output[pos + 2] = b3;
  680. output[pos + 3] = b4;
  681. }
  682. /**
  683. * Read a signed short value at given position
  684. */
  685. private short readShort(int pos) {
  686. int ret = readUShort(pos);
  687. return (short)ret;
  688. }
  689. /**
  690. * Read a unsigned short value at given position
  691. */
  692. private int readUShort(int pos) {
  693. int ret = output[pos];
  694. if (ret < 0) {
  695. ret += 256;
  696. }
  697. ret = ret << 8;
  698. if (output[pos + 1] < 0) {
  699. ret |= output[pos + 1] + 256;
  700. } else {
  701. ret |= output[pos + 1];
  702. }
  703. return ret;
  704. }
  705. /**
  706. * Create a padding in the fontfile to align
  707. * on a 4-byte boundary
  708. */
  709. private void pad4() {
  710. int padSize = currentPos % 4;
  711. for (int i = 0; i < padSize; i++) {
  712. output[currentPos++] = 0;
  713. realSize++;
  714. }
  715. }
  716. /**
  717. * Returns the maximum power of 2 <= max
  718. */
  719. private int maxPow2(int max) {
  720. int i = 0;
  721. while (Math.pow(2, i) < max) {
  722. i++;
  723. }
  724. return (i - 1);
  725. }
  726. private int log2(int num) {
  727. return (int)(Math.log(num) / Math.log(2));
  728. }
  729. private int getCheckSum(int start, int size) {
  730. return (int)getLongCheckSum(start, size);
  731. }
  732. private long getLongCheckSum(int start, int size) {
  733. // All the tables here are aligned on four byte boundaries
  734. // Add remainder to size if it's not a multiple of 4
  735. int remainder = size % 4;
  736. if (remainder != 0) {
  737. size += remainder;
  738. }
  739. long sum = 0;
  740. for (int i = 0; i < size; i += 4) {
  741. int l = (output[start + i] << 24);
  742. l += (output[start + i + 1] << 16);
  743. l += (output[start + i + 2] << 16);
  744. l += (output[start + i + 3] << 16);
  745. sum += l;
  746. if (sum > 0xffffffff) {
  747. sum = sum - 0xffffffff;
  748. }
  749. }
  750. return sum;
  751. }
  752. private void createCheckSumAdjustment() {
  753. long sum = getLongCheckSum(0, realSize);
  754. int checksum = (int)(0xb1b0afba - sum);
  755. writeULong(checkSumAdjustmentOffset, checksum);
  756. }
  757. }