001: /*
002: * $Id: TrueTypeFontUnicode.java 2864 2007-07-03 15:13:38Z psoares33 $
003: * $Name$
004: *
005: * Copyright 2001, 2002 Paulo Soares
006: *
007: * The contents of this file are subject to the Mozilla Public License Version 1.1
008: * (the "License"); you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the License.
014: *
015: * The Original Code is 'iText, a free JAVA-PDF library'.
016: *
017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
019: * All Rights Reserved.
020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
022: *
023: * Contributor(s): all the names of the contributors are added in the source code
024: * where applicable.
025: *
026: * Alternatively, the contents of this file may be used under the terms of the
027: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
028: * provisions of LGPL are applicable instead of those above. If you wish to
029: * allow use of your version of this file only under the terms of the LGPL
030: * License and not to allow others to use your version of this file under
031: * the MPL, indicate your decision by deleting the provisions above and
032: * replace them with the notice and other provisions required by the LGPL.
033: * If you do not delete the provisions above, a recipient may use your version
034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
035: *
036: * This library is free software; you can redistribute it and/or modify it
037: * under the terms of the MPL as stated above or under the terms of the GNU
038: * Library General Public License as published by the Free Software Foundation;
039: * either version 2 of the License, or any later version.
040: *
041: * This library is distributed in the hope that it will be useful, but WITHOUT
042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
044: * details.
045: *
046: * If you didn't download this code from the following link, you should check if
047: * you aren't using an obsolete version:
048: * http://www.lowagie.com/iText/
049: */
050:
051: package com.lowagie.text.pdf;
052:
053: import java.io.IOException;
054: import java.util.Arrays;
055: import java.util.Comparator;
056: import java.util.HashMap;
057:
058: import com.lowagie.text.DocumentException;
059:
060: /** Represents a True Type font with Unicode encoding. All the character
061: * in the font can be used directly by using the encoding Identity-H or
062: * Identity-V. This is the only way to represent some character sets such
063: * as Thai.
064: * @author Paulo Soares (psoares@consiste.pt)
065: */
066: class TrueTypeFontUnicode extends TrueTypeFont implements Comparator {
067:
068: /** <CODE>true</CODE> if the encoding is vertical.
069: */
070: boolean vertical = false;
071:
072: /** Creates a new TrueType font addressed by Unicode characters. The font
073: * will always be embedded.
074: * @param ttFile the location of the font on file. The file must end in '.ttf'.
075: * The modifiers after the name are ignored.
076: * @param enc the encoding to be applied to this font
077: * @param emb true if the font is to be embedded in the PDF
078: * @param ttfAfm the font as a <CODE>byte</CODE> array
079: * @throws DocumentException the font is invalid
080: * @throws IOException the font file could not be read
081: */
082: TrueTypeFontUnicode(String ttFile, String enc, boolean emb,
083: byte ttfAfm[]) throws DocumentException, IOException {
084: String nameBase = getBaseName(ttFile);
085: String ttcName = getTTCName(nameBase);
086: if (nameBase.length() < ttFile.length()) {
087: style = ttFile.substring(nameBase.length());
088: }
089: encoding = enc;
090: embedded = emb;
091: fileName = ttcName;
092: ttcIndex = "";
093: if (ttcName.length() < nameBase.length())
094: ttcIndex = nameBase.substring(ttcName.length() + 1);
095: fontType = FONT_TYPE_TTUNI;
096: if ((fileName.toLowerCase().endsWith(".ttf")
097: || fileName.toLowerCase().endsWith(".otf") || fileName
098: .toLowerCase().endsWith(".ttc"))
099: && ((enc.equals(IDENTITY_H) || enc.equals(IDENTITY_V)) && emb)) {
100: process(ttfAfm);
101: if (os_2.fsType == 2)
102: throw new DocumentException(
103: fileName
104: + style
105: + " cannot be embedded due to licensing restrictions.");
106: // Sivan
107: if ((cmap31 == null && !fontSpecific)
108: || (cmap10 == null && fontSpecific))
109: directTextToByte = true;
110: //throw new DocumentException(fileName + " " + style + " does not contain an usable cmap.");
111: if (fontSpecific) {
112: fontSpecific = false;
113: String tempEncoding = encoding;
114: encoding = "";
115: createEncoding();
116: encoding = tempEncoding;
117: fontSpecific = true;
118: }
119: } else
120: throw new DocumentException(fileName + " " + style
121: + " is not a TTF font file.");
122: vertical = enc.endsWith("V");
123: }
124:
125: /**
126: * Gets the width of a <CODE>char</CODE> in normalized 1000 units.
127: * @param char1 the unicode <CODE>char</CODE> to get the width of
128: * @return the width in normalized 1000 units
129: */
130: public int getWidth(char char1) {
131: if (vertical)
132: return 1000;
133: if (fontSpecific) {
134: if ((char1 & 0xff00) == 0 || (char1 & 0xff00) == 0xf000)
135: return getRawWidth(char1 & 0xff, null);
136: else
137: return 0;
138: } else {
139: return getRawWidth(char1, encoding);
140: }
141: }
142:
143: /**
144: * Gets the width of a <CODE>String</CODE> in normalized 1000 units.
145: * @param text the <CODE>String</CODE> to get the witdth of
146: * @return the width in normalized 1000 units
147: */
148: public int getWidth(String text) {
149: if (vertical)
150: return text.length() * 1000;
151: int total = 0;
152: if (fontSpecific) {
153: char cc[] = text.toCharArray();
154: int len = cc.length;
155: for (int k = 0; k < len; ++k) {
156: char c = cc[k];
157: if ((c & 0xff00) == 0 || (c & 0xff00) == 0xf000)
158: total += getRawWidth(c & 0xff, null);
159: }
160: } else {
161: int len = text.length();
162: for (int k = 0; k < len; ++k)
163: total += getRawWidth(text.charAt(k), encoding);
164: }
165: return total;
166: }
167:
168: /** Creates a ToUnicode CMap to allow copy and paste from Acrobat.
169: * @param metrics metrics[0] contains the glyph index and metrics[2]
170: * contains the Unicode code
171: * @throws DocumentException on error
172: * @return the stream representing this CMap or <CODE>null</CODE>
173: */
174: private PdfStream getToUnicode(Object metrics[]) {
175: if (metrics.length == 0)
176: return null;
177: StringBuffer buf = new StringBuffer(
178: "/CIDInit /ProcSet findresource begin\n"
179: + "12 dict begin\n" + "begincmap\n"
180: + "/CIDSystemInfo\n" + "<< /Registry (Adobe)\n"
181: + "/Ordering (UCS)\n" + "/Supplement 0\n"
182: + ">> def\n"
183: + "/CMapName /Adobe-Identity-UCS def\n"
184: + "/CMapType 2 def\n"
185: + "1 begincodespacerange\n" + "<0000><FFFF>\n"
186: + "endcodespacerange\n");
187: int size = 0;
188: for (int k = 0; k < metrics.length; ++k) {
189: if (size == 0) {
190: if (k != 0) {
191: buf.append("endbfrange\n");
192: }
193: size = Math.min(100, metrics.length - k);
194: buf.append(size).append(" beginbfrange\n");
195: }
196: --size;
197: int metric[] = (int[]) metrics[k];
198: String fromTo = toHex(metric[0]);
199: buf.append(fromTo).append(fromTo).append(toHex(metric[2]))
200: .append('\n');
201: }
202: buf.append("endbfrange\n" + "endcmap\n"
203: + "CMapName currentdict /CMap defineresource pop\n"
204: + "end end\n");
205: String s = buf.toString();
206: PdfStream stream = new PdfStream(PdfEncodings.convertToBytes(s,
207: null));
208: stream.flateCompress();
209: return stream;
210: }
211:
212: /** Gets an hex string in the format "<HHHH>".
213: * @param n the number
214: * @return the hex string
215: */
216: static String toHex(int n) {
217: String s = Integer.toHexString(n);
218: return "<0000".substring(0, 5 - s.length()) + s + ">";
219: }
220:
221: /** Generates the CIDFontTyte2 dictionary.
222: * @param fontDescriptor the indirect reference to the font descriptor
223: * @param subsetPrefix the subset prefix
224: * @param metrics the horizontal width metrics
225: * @return a stream
226: */
227: private PdfDictionary getCIDFontType2(
228: PdfIndirectReference fontDescriptor, String subsetPrefix,
229: Object metrics[]) {
230: PdfDictionary dic = new PdfDictionary(PdfName.FONT);
231: // sivan; cff
232: if (cff) {
233: dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0);
234: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix
235: + fontName + "-" + encoding));
236: } else {
237: dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE2);
238: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix
239: + fontName));
240: }
241: dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
242: if (!cff)
243: dic.put(PdfName.CIDTOGIDMAP, PdfName.IDENTITY);
244: PdfDictionary cdic = new PdfDictionary();
245: cdic.put(PdfName.REGISTRY, new PdfString("Adobe"));
246: cdic.put(PdfName.ORDERING, new PdfString("Identity"));
247: cdic.put(PdfName.SUPPLEMENT, new PdfNumber(0));
248: dic.put(PdfName.CIDSYSTEMINFO, cdic);
249: if (!vertical) {
250: dic.put(PdfName.DW, new PdfNumber(1000));
251: StringBuffer buf = new StringBuffer("[");
252: int lastNumber = -10;
253: boolean firstTime = true;
254: for (int k = 0; k < metrics.length; ++k) {
255: int metric[] = (int[]) metrics[k];
256: if (metric[1] == 1000)
257: continue;
258: int m = metric[0];
259: if (m == lastNumber + 1) {
260: buf.append(' ').append(metric[1]);
261: } else {
262: if (!firstTime) {
263: buf.append(']');
264: }
265: firstTime = false;
266: buf.append(m).append('[').append(metric[1]);
267: }
268: lastNumber = m;
269: }
270: if (buf.length() > 1) {
271: buf.append("]]");
272: dic.put(PdfName.W, new PdfLiteral(buf.toString()));
273: }
274: }
275: return dic;
276: }
277:
278: /** Generates the font dictionary.
279: * @param descendant the descendant dictionary
280: * @param subsetPrefix the subset prefix
281: * @param toUnicode the ToUnicode stream
282: * @return the stream
283: */
284: private PdfDictionary getFontBaseType(
285: PdfIndirectReference descendant, String subsetPrefix,
286: PdfIndirectReference toUnicode) {
287: PdfDictionary dic = new PdfDictionary(PdfName.FONT);
288:
289: dic.put(PdfName.SUBTYPE, PdfName.TYPE0);
290: // The PDF Reference manual advises to add -encoding to CID font names
291: if (cff)
292: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix
293: + fontName + "-" + encoding));
294: //dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix+fontName));
295: else
296: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix
297: + fontName));
298: //dic.put(PdfName.BASEFONT, new PdfName(fontName));
299: dic.put(PdfName.ENCODING, new PdfName(encoding));
300: dic.put(PdfName.DESCENDANTFONTS, new PdfArray(descendant));
301: if (toUnicode != null)
302: dic.put(PdfName.TOUNICODE, toUnicode);
303: return dic;
304: }
305:
306: /** The method used to sort the metrics array.
307: * @param o1 the first element
308: * @param o2 the second element
309: * @return the comparisation
310: */
311: public int compare(Object o1, Object o2) {
312: int m1 = ((int[]) o1)[0];
313: int m2 = ((int[]) o2)[0];
314: if (m1 < m2)
315: return -1;
316: if (m1 == m2)
317: return 0;
318: return 1;
319: }
320:
321: /** Outputs to the writer the font dictionaries and streams.
322: * @param writer the writer for this document
323: * @param ref the font indirect reference
324: * @param params several parameters that depend on the font type
325: * @throws IOException on error
326: * @throws DocumentException error in generating the object
327: */
328: void writeFont(PdfWriter writer, PdfIndirectReference ref,
329: Object params[]) throws DocumentException, IOException {
330: HashMap longTag = (HashMap) params[0];
331: addRangeUni(longTag, true, subset);
332: Object metrics[] = longTag.values().toArray();
333: Arrays.sort(metrics, this );
334: PdfIndirectReference ind_font = null;
335: PdfObject pobj = null;
336: PdfIndirectObject obj = null;
337: // sivan: cff
338: if (cff) {
339: RandomAccessFileOrArray rf2 = new RandomAccessFileOrArray(
340: rf);
341: byte b[] = new byte[cffLength];
342: try {
343: rf2.reOpen();
344: rf2.seek(cffOffset);
345: rf2.readFully(b);
346: } finally {
347: try {
348: rf2.close();
349: } catch (Exception e) {
350: // empty on purpose
351: }
352: }
353: if (subset || subsetRanges != null) {
354: CFFFontSubset cff = new CFFFontSubset(
355: new RandomAccessFileOrArray(b), longTag);
356: b = cff.Process((cff.getNames())[0]);
357: }
358: pobj = new StreamFont(b, "CIDFontType0C");
359: obj = writer.addToBody(pobj);
360: ind_font = obj.getIndirectReference();
361: } else {
362: byte[] b;
363: if (subset || directoryOffset != 0) {
364: TrueTypeFontSubSet sb = new TrueTypeFontSubSet(
365: fileName, new RandomAccessFileOrArray(rf),
366: longTag, directoryOffset, false, false);
367: b = sb.process();
368: } else {
369: b = getFullFont();
370: }
371: int lengths[] = new int[] { b.length };
372: pobj = new StreamFont(b, lengths);
373: obj = writer.addToBody(pobj);
374: ind_font = obj.getIndirectReference();
375: }
376: String subsetPrefix = "";
377: if (subset)
378: subsetPrefix = createSubsetPrefix();
379: PdfDictionary dic = getFontDescriptor(ind_font, subsetPrefix);
380: obj = writer.addToBody(dic);
381: ind_font = obj.getIndirectReference();
382:
383: pobj = getCIDFontType2(ind_font, subsetPrefix, metrics);
384: obj = writer.addToBody(pobj);
385: ind_font = obj.getIndirectReference();
386:
387: pobj = getToUnicode(metrics);
388: PdfIndirectReference toUnicodeRef = null;
389:
390: if (pobj != null) {
391: obj = writer.addToBody(pobj);
392: toUnicodeRef = obj.getIndirectReference();
393: }
394:
395: pobj = getFontBaseType(ind_font, subsetPrefix, toUnicodeRef);
396: writer.addToBody(pobj, ref);
397: }
398:
399: /** A forbidden operation. Will throw a null pointer exception.
400: * @param text the text
401: * @return always <CODE>null</CODE>
402: */
403: byte[] convertToBytes(String text) {
404: return null;
405: }
406:
407: byte[] convertToBytes(char char1) {
408: return null;
409: }
410:
411: /**
412: * Checks if a character exists in this font.
413: * @param c the character to check
414: * @return <CODE>true</CODE> if the character has a glyph,
415: * <CODE>false</CODE> otherwise
416: */
417: public boolean charExists(char c) {
418: HashMap map = null;
419: if (fontSpecific)
420: map = cmap10;
421: else
422: map = cmap31;
423: if (map == null)
424: return false;
425: if (fontSpecific) {
426: if ((c & 0xff00) == 0 || (c & 0xff00) == 0xf000)
427: return map.get(new Integer(c & 0xff)) != null;
428: else
429: return false;
430: } else
431: return map.get(new Integer(c)) != null;
432: }
433:
434: /**
435: * Sets the character advance.
436: * @param c the character
437: * @param advance the character advance normalized to 1000 units
438: * @return <CODE>true</CODE> if the advance was set,
439: * <CODE>false</CODE> otherwise
440: */
441: public boolean setCharAdvance(char c, int advance) {
442: HashMap map = null;
443: if (fontSpecific)
444: map = cmap10;
445: else
446: map = cmap31;
447: if (map == null)
448: return false;
449: int m[] = null;
450: if (fontSpecific) {
451: if ((c & 0xff00) == 0 || (c & 0xff00) == 0xf000)
452: m = (int[]) map.get(new Integer(c & 0xff));
453: else
454: return false;
455: } else
456: m = (int[]) map.get(new Integer(c));
457: if (m == null)
458: return false;
459: else
460: m[1] = advance;
461: return true;
462: }
463:
464: public int[] getCharBBox(char c) {
465: if (bboxes == null)
466: return null;
467: HashMap map = null;
468: if (fontSpecific)
469: map = cmap10;
470: else
471: map = cmap31;
472: if (map == null)
473: return null;
474: int m[] = null;
475: if (fontSpecific) {
476: if ((c & 0xff00) == 0 || (c & 0xff00) == 0xf000)
477: m = (int[]) map.get(new Integer(c & 0xff));
478: else
479: return null;
480: } else
481: m = (int[]) map.get(new Integer(c));
482: if (m == null)
483: return null;
484: return bboxes[m[0]];
485: }
486: }
|