001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.font.truetype;
031:
032: import java.io.ByteArrayOutputStream;
033: import java.io.IOException;
034: import java.util.ArrayList;
035: import java.util.Arrays;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Map;
040: import java.util.Set;
041:
042: import de.intarsys.font.FontStyle;
043: import de.intarsys.font.IFont;
044: import de.intarsys.tools.locator.ILocator;
045: import de.intarsys.tools.randomaccess.IRandomAccess;
046: import de.intarsys.tools.stream.StreamTools;
047: import de.intarsys.tools.string.StringTools;
048:
049: /**
050: * This class represents a true type font. Currently only single font files are
051: * supported. The bytes defining the font are read completely and stored for
052: * later use. This class is under construction, it's use is for reading true
053: * types and make some operations on it rather than creating from the scratch.
054: */
055: public class TTFont implements IFont {
056: public static final byte[] TABLE_POST = "post".getBytes();//$NON-NLS-1$
057:
058: public static final byte[] TABLE_PREP = "prep".getBytes(); //$NON-NLS-1$
059:
060: public static final byte[] TABLE_LOCA = "loca".getBytes(); //$NON-NLS-1$
061:
062: public static final byte[] TABLE_GLYF = "glyf".getBytes(); //$NON-NLS-1$
063:
064: public static final byte[] TABLE_FGPM = "fpgm".getBytes(); //$NON-NLS-1$
065:
066: public static final byte[] TABLE_CVT = "cvt ".getBytes(); //$NON-NLS-1$
067:
068: public static final byte[] TABLE_OS2 = "OS/2".getBytes(); //$NON-NLS-1$
069:
070: public static final byte[] TABLE_NAME = "name".getBytes(); //$NON-NLS-1$
071:
072: public static final byte[] TABLE_MAXP = "maxp".getBytes(); //$NON-NLS-1$
073:
074: public static final byte[] TABLE_HMTX = "hmtx".getBytes(); //$NON-NLS-1$
075:
076: public static final byte[] TABLE_HHEA = "hhea".getBytes(); //$NON-NLS-1$
077:
078: public static final byte[] TABLE_HEAD = "head".getBytes(); //$NON-NLS-1$
079:
080: public static final byte[] TABLE_CMAP = "cmap".getBytes(); //$NON-NLS-1$
081:
082: public static final int ARG_1_AND_2_ARE_WORDS = 1;
083:
084: public static final int WE_HAVE_A_SCALE = 8;
085:
086: public static final int MORE_COMPONENTS = 32;
087:
088: public static final int WE_HAVE_AN_X_AND_Y_SCALE = 64;
089:
090: public static final int WE_HAVE_A_TWO_BY_TWO = 128;
091:
092: public static byte[][] SubsetTables = { TABLE_CMAP, TABLE_HEAD,
093: TABLE_HHEA, TABLE_HMTX, TABLE_MAXP, TABLE_NAME, TABLE_OS2,
094: TABLE_CVT, TABLE_FGPM, TABLE_GLYF, TABLE_LOCA, TABLE_PREP };
095:
096: /** objectified versions of the "cmap" tables and subtables */
097: private Map cmaps;
098:
099: private ILocator locator;
100:
101: // Font names in Postscript notation
102: private String fontFamilyName = null;
103:
104: private String psName = null;
105:
106: /** "objectified" version of the "head" table */
107: private TTFontHeader fontHeader;
108:
109: /** objectified version of the "hhea" table */
110: private TTHorizontalHeader horizontalHeader;
111:
112: /** objectified version of the "os/2" table */
113: private TTMetrics metrics;
114:
115: /** objectified version of the "naming" table */
116: private TTNaming naming;
117:
118: /** objectified versions of the "post" table */
119: private TTPostScriptInformation postScriptInformation;
120:
121: /** objectified version of the "hmtx" table */
122: private int[] glyphWidths;
123:
124: /** the parsed table directory information */
125: private TTTable[] tables;
126:
127: /** The style of this font */
128: private FontStyle fontStyle = FontStyle.REGULAR;
129:
130: /**
131: * Create an empty true type font.
132: */
133: protected TTFont() {
134: super ();
135: }
136:
137: public static TTFont createFromLocator(ILocator locator)
138: throws IOException {
139: TTFont result = new TTFont();
140: result.setLocator(locator);
141: result.initializeFromLocator();
142: return result;
143: }
144:
145: protected void initializeFromLocator() throws IOException {
146: IRandomAccess random = null;
147: try {
148: random = getLocator().getRandomAccess();
149: TTFontParser parser = new TTFontParser();
150: parser.parseTables(this );
151: try {
152: setFontName(this );
153: } catch (TrueTypeException e) {
154: throw new IOException(e.getMessage());
155: }
156:
157: } finally {
158: StreamTools.close(random);
159: }
160: }
161:
162: protected void setFontName(TTFont font) throws TrueTypeException {
163: TTNaming tempNaming = font.getNaming();
164: if (tempNaming != null) {
165: font.setFontFamilyName(tempNaming
166: .getValue(ITTNamingIDs.FontFamilyName));
167: String styleName = tempNaming
168: .getValue(ITTNamingIDs.FontSubfamilyName);
169: font.setFontStyle(FontStyle.getFontStyle(styleName));
170: font.setPsName(tempNaming.getValue(ITTNamingIDs.PSName));
171: if (font.getPsName() == null) {
172: // Here we should use operating system specific information
173: // which can't be done by Java. So we use our own
174: // implementation and hope we it works.
175: // todo postscript name
176: font.setPsName(font.getFontFamilyName() + "-" //$NON-NLS-1$
177: + font.getFontStyle().getLabel());
178: }
179: }
180: }
181:
182: public Map getCMapsAt(int platformID, int platformSpecificID)
183: throws TrueTypeException {
184: String key = StringTools.EMPTY + platformID
185: + ":" + platformSpecificID; //$NON-NLS-1$
186: Object result = getCMaps().get(key);
187:
188: if (result instanceof TTTable) {
189: // not yet parsed
190: TTFontParser parser = new TTFontParser();
191:
192: try {
193: Map submap = parser
194: .parseTable_cmap_subtable((TTTable) result);
195: cmaps.put(key, submap);
196: result = submap;
197: } catch (IOException e) {
198: throw new TrueTypeException(e.getMessage());
199: }
200: }
201:
202: return (Map) result;
203: }
204:
205: public String getFontFamilyName() {
206: return fontFamilyName;
207: }
208:
209: public TTFontHeader getFontHeader() throws TrueTypeException {
210: if (fontHeader == null) {
211: TTFontParser parser = new TTFontParser();
212:
213: try {
214: fontHeader = parser
215: .parseTable_head(getTable(TABLE_HEAD));
216: } catch (IOException e) {
217: throw new TrueTypeException(e.getMessage());
218: }
219: }
220:
221: return fontHeader;
222: }
223:
224: /*
225: * (non-Javadoc)
226: *
227: * @see de.intarsys.font.IFont#getFontName()
228: */
229: public String getFontName() {
230: return getPsName();
231: }
232:
233: public FontStyle getFontStyle() {
234: return fontStyle;
235: }
236:
237: public int getGlyphWidth(int codePoint) throws TrueTypeException {
238: if (codePoint < getGlyphWidths().length) {
239: return getGlyphWidths()[codePoint];
240: }
241: return getGlyphWidths()[getGlyphWidths().length - 1];
242: }
243:
244: public TTHorizontalHeader getHorizontalHeader()
245: throws TrueTypeException {
246: if (horizontalHeader == null) {
247: TTFontParser parser = new TTFontParser();
248:
249: try {
250: horizontalHeader = parser
251: .parseTable_hhea(getTable(TABLE_HHEA));
252: } catch (IOException e) {
253: throw new TrueTypeException(e.getMessage());
254: }
255: }
256:
257: return horizontalHeader;
258: }
259:
260: public TTMetrics getMetrics() throws TrueTypeException {
261: if (metrics == null) {
262: TTFontParser parser = new TTFontParser();
263:
264: try {
265: metrics = parser.parseTable_os2(getTable(TABLE_OS2));
266: } catch (IOException e) {
267: throw new TrueTypeException(e.getMessage());
268: }
269: }
270:
271: return metrics;
272: }
273:
274: public TTNaming getNaming() throws TrueTypeException {
275: if (naming == null) {
276: TTFontParser parser = new TTFontParser();
277:
278: try {
279: TTTable table = getTable(TABLE_NAME);
280: if (table != null) {
281: naming = parser.parseTable_name(table);
282: }
283: } catch (IOException e) {
284: throw new TrueTypeException(e.getMessage());
285: }
286: }
287: return naming;
288: }
289:
290: public TTPostScriptInformation getPostScriptInformation()
291: throws TrueTypeException {
292: if (postScriptInformation == null) {
293: TTFontParser parser = new TTFontParser();
294:
295: try {
296: postScriptInformation = parser
297: .parseTable_post(getTable(TABLE_POST));
298: } catch (IOException e) {
299: throw new TrueTypeException(e.getMessage());
300: }
301: }
302:
303: return postScriptInformation;
304: }
305:
306: public void setPsName(String string) {
307: psName = string;
308: }
309:
310: public String getPsName() {
311: return psName;
312: }
313:
314: public boolean isSymbolFont() {
315: try {
316: return (getCMapsAt(3, 0) != null);
317: } catch (TrueTypeException e) {
318: // TODO
319: }
320: return false;
321: }
322:
323: public TTTable getTable(byte[] name) {
324: for (int i = 0; i < getTables().length; i++) {
325: TTTable current = tables[i];
326:
327: if (Arrays.equals(current.getName(), name)) {
328: return current;
329: }
330: }
331:
332: return null;
333: }
334:
335: public TTTable[] getTables() {
336: return tables;
337: }
338:
339: protected TTFont copySubset() {
340: TTFont resultFont = new TTFont();
341: List newTables = new ArrayList();
342:
343: for (int i = 0; i < SubsetTables.length; i++) {
344: TTTable table = getTable(SubsetTables[i]);
345:
346: if (table != null) {
347: newTables.add(table);
348: }
349: }
350:
351: resultFont.setTables((TTTable[]) newTables
352: .toArray(new TTTable[0]));
353:
354: return resultFont;
355: }
356:
357: public TTFont createSubset(Set glyphs) throws IOException,
358: TrueTypeException {
359: TTTable loca = getTable(TABLE_LOCA);
360: int[] locations = new TTFontParser().parseTable_loca(loca,
361: getFontHeader().isShortLocationFormat());
362: TTTable glyf = getTable(TABLE_GLYF);
363: IRandomAccess glyfRandom = glyf.getRandomAccess();
364: try {
365: //
366: TTFont result = copySubset();
367: Set compositeGlyphs = result.addCompositeGlyphs(glyfRandom,
368: locations, glyphs);
369: result.createGlyphTable(loca, glyf, glyfRandom, locations,
370: compositeGlyphs);
371: return result;
372: } finally {
373: StreamTools.close(glyfRandom);
374: }
375: }
376:
377: public Map getCMaps() throws TrueTypeException {
378: if (cmaps == null) {
379: TTFontParser parser = new TTFontParser();
380:
381: try {
382: cmaps = parser.parseTable_cmap(getTable(TABLE_CMAP));
383: } catch (IOException e) {
384: throw new TrueTypeException(e.getMessage());
385: }
386: }
387:
388: return cmaps;
389: }
390:
391: protected void setFontFamilyName(String string) {
392: fontFamilyName = string;
393: }
394:
395: protected void setFontStyle(FontStyle fontStyle) {
396: this .fontStyle = fontStyle;
397: }
398:
399: protected int[] getGlyphWidths() throws TrueTypeException {
400: if (glyphWidths == null) {
401: TTFontParser parser = new TTFontParser();
402:
403: try {
404: glyphWidths = parser.parseTable_hmtx(
405: getTable(TABLE_HMTX), getHorizontalHeader()
406: .getNumberOfHMetrics());
407: } catch (IOException e) {
408: throw new TrueTypeException(e.getMessage());
409: }
410: }
411:
412: return glyphWidths;
413: }
414:
415: protected void setTables(TTTable[] tables) {
416: this .tables = tables;
417: }
418:
419: protected Set addCompositeGlyphs(IRandomAccess glyfRandom,
420: int[] locations, Set glyphs) throws IOException,
421: TrueTypeException {
422: glyphs.add(new Integer(0));
423: Set allGlyphs = new HashSet();
424: allGlyphs.addAll(glyphs);
425: for (Iterator i = glyphs.iterator(); i.hasNext();) {
426: int codePoint = ((Integer) i.next()).intValue();
427: addCompositeGlyphs(glyfRandom, locations, allGlyphs,
428: codePoint);
429: }
430: return allGlyphs;
431: }
432:
433: protected void addCompositeGlyphs(IRandomAccess random,
434: int[] locations, Set glyphs, int codePoint)
435: throws IOException, TrueTypeException {
436: if (locations[codePoint] == locations[codePoint + 1]) {
437: return;
438: }
439: random.seek(locations[codePoint]);
440: TTFontParser parser = new TTFontParser();
441: int numContours = parser.readShort(random);
442: if (numContours >= 0) {
443: return;
444: }
445: random.seekBy(8);
446:
447: for (;;) {
448: int flags = parser.readUShort(random);
449: int codePointRef = parser.readUShort(random);
450: glyphs.add(new Integer(codePointRef));
451:
452: if ((flags & MORE_COMPONENTS) == 0) {
453: return;
454: }
455:
456: int skip;
457:
458: if ((flags & ARG_1_AND_2_ARE_WORDS) != 0) {
459: skip = 4;
460: } else {
461: skip = 2;
462: }
463:
464: if ((flags & WE_HAVE_A_SCALE) != 0) {
465: skip += 2;
466: } else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) {
467: skip += 4;
468: }
469:
470: if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) {
471: skip += 8;
472: }
473: random.seekBy(skip);
474: }
475: }
476:
477: protected void createGlyphTable(TTTable loca, TTTable glyf,
478: IRandomAccess glyfRandom, int[] oldLocations, Set glyphs)
479: throws IOException, TrueTypeException {
480: int newLength = 0;
481:
482: for (Iterator i = glyphs.iterator(); i.hasNext();) {
483: int codePoint = ((Integer) i.next()).intValue();
484:
485: if ((codePoint + 1) >= oldLocations.length) {
486: continue;
487: }
488:
489: newLength += (oldLocations[codePoint + 1] - oldLocations[codePoint]);
490: }
491:
492: newLength = (newLength + 3) & (~3);
493:
494: int[] newLocations = new int[oldLocations.length];
495: byte[] newGlyfData = new byte[newLength];
496: int ptr = 0;
497:
498: for (int i = 0; i < oldLocations.length; i++) {
499: newLocations[i] = ptr;
500:
501: if (glyphs.contains(new Integer(i))) {
502: int glyfstart = oldLocations[i];
503: int glyflength = oldLocations[i + 1] - glyfstart;
504:
505: if (glyflength > 0) {
506: glyfRandom.seek(glyfstart);
507: glyfRandom.read(newGlyfData, ptr, glyflength);
508: ptr += glyflength;
509: }
510: }
511: }
512:
513: ByteArrayOutputStream bos = new ByteArrayOutputStream();
514: TTFontSerializer serializer = new TTFontSerializer(bos);
515: serializer.write_loca(newLocations, getFontHeader()
516: .isShortLocationFormat());
517: loca.setBytes(bos.toByteArray());
518: glyf.setBytes(newGlyfData);
519: }
520:
521: protected void putCMaps(int platformID, int platformSpecificID,
522: Map value) {
523: String key = StringTools.EMPTY + platformID
524: + ":" + platformSpecificID; //$NON-NLS-1$
525: cmaps.put(key, value);
526: }
527:
528: public ILocator getLocator() {
529: return locator;
530: }
531:
532: protected void setLocator(ILocator locator) {
533: this.locator = locator;
534: }
535:
536: }
|