001: /*
002: * $Id: CJKFont.java 2864 2007-07-03 15:13:38Z psoares33 $
003: * $Name$
004: *
005: * Copyright 2000, 2001, 2002 by 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.io.InputStream;
055: import java.util.Enumeration;
056: import java.util.HashMap;
057: import java.util.Hashtable;
058: import java.util.Properties;
059: import java.util.StringTokenizer;
060:
061: import com.lowagie.text.DocumentException;
062:
063: /**
064: * Creates a CJK font compatible with the fonts in the Adobe Asian font Pack.
065: *
066: * @author Paulo Soares (psoares@consiste.pt)
067: */
068:
069: class CJKFont extends BaseFont {
070: /** The encoding used in the PDF document for CJK fonts
071: */
072: static final String CJK_ENCODING = "UnicodeBigUnmarked";
073: private static final int FIRST = 0;
074: private static final int BRACKET = 1;
075: private static final int SERIAL = 2;
076: private static final int V1Y = 880;
077:
078: static Properties cjkFonts = new Properties();
079: static Properties cjkEncodings = new Properties();
080: static Hashtable allCMaps = new Hashtable();
081: static Hashtable allFonts = new Hashtable();
082: private static boolean propertiesLoaded = false;
083:
084: /** The font name */
085: private String fontName;
086: /** The style modifier */
087: private String style = "";
088: /** The CMap name associated with this font */
089: private String CMap;
090:
091: private boolean cidDirect = false;
092:
093: private char[] translationMap;
094: private IntHashtable vMetrics;
095: private IntHashtable hMetrics;
096: private HashMap fontDesc;
097: private boolean vertical = false;
098:
099: private static void loadProperties() {
100: if (propertiesLoaded)
101: return;
102: synchronized (allFonts) {
103: if (propertiesLoaded)
104: return;
105: try {
106: InputStream is = getResourceStream(RESOURCE_PATH
107: + "cjkfonts.properties");
108: cjkFonts.load(is);
109: is.close();
110: is = getResourceStream(RESOURCE_PATH
111: + "cjkencodings.properties");
112: cjkEncodings.load(is);
113: is.close();
114: } catch (Exception e) {
115: cjkFonts = new Properties();
116: cjkEncodings = new Properties();
117: }
118: propertiesLoaded = true;
119: }
120: }
121:
122: /** Creates a CJK font.
123: * @param fontName the name of the font
124: * @param enc the encoding of the font
125: * @param emb always <CODE>false</CODE>. CJK font and not embedded
126: * @throws DocumentException on error
127: */
128: CJKFont(String fontName, String enc, boolean emb)
129: throws DocumentException {
130: loadProperties();
131: fontType = FONT_TYPE_CJK;
132: String nameBase = getBaseName(fontName);
133: if (!isCJKFont(nameBase, enc))
134: throw new DocumentException("Font '" + fontName
135: + "' with '" + enc
136: + "' encoding is not a CJK font.");
137: if (nameBase.length() < fontName.length()) {
138: style = fontName.substring(nameBase.length());
139: fontName = nameBase;
140: }
141: this .fontName = fontName;
142: encoding = CJK_ENCODING;
143: vertical = enc.endsWith("V");
144: CMap = enc;
145: if (enc.startsWith("Identity-")) {
146: cidDirect = true;
147: String s = cjkFonts.getProperty(fontName);
148: s = s.substring(0, s.indexOf('_'));
149: char c[] = (char[]) allCMaps.get(s);
150: if (c == null) {
151: c = readCMap(s);
152: if (c == null)
153: throw new DocumentException("The cmap " + s
154: + " does not exist as a resource.");
155: c[CID_NEWLINE] = '\n';
156: allCMaps.put(s, c);
157: }
158: translationMap = c;
159: } else {
160: char c[] = (char[]) allCMaps.get(enc);
161: if (c == null) {
162: String s = cjkEncodings.getProperty(enc);
163: if (s == null)
164: throw new DocumentException(
165: "The resource cjkencodings.properties does not contain the encoding "
166: + enc);
167: StringTokenizer tk = new StringTokenizer(s);
168: String nt = tk.nextToken();
169: c = (char[]) allCMaps.get(nt);
170: if (c == null) {
171: c = readCMap(nt);
172: allCMaps.put(nt, c);
173: }
174: if (tk.hasMoreTokens()) {
175: String nt2 = tk.nextToken();
176: char m2[] = readCMap(nt2);
177: for (int k = 0; k < 0x10000; ++k) {
178: if (m2[k] == 0)
179: m2[k] = c[k];
180: }
181: allCMaps.put(enc, m2);
182: c = m2;
183: }
184: }
185: translationMap = c;
186: }
187: fontDesc = (HashMap) allFonts.get(fontName);
188: if (fontDesc == null) {
189: fontDesc = readFontProperties(fontName);
190: allFonts.put(fontName, fontDesc);
191: }
192: hMetrics = (IntHashtable) fontDesc.get("W");
193: vMetrics = (IntHashtable) fontDesc.get("W2");
194: }
195:
196: /** Checks if its a valid CJK font.
197: * @param fontName the font name
198: * @param enc the encoding
199: * @return <CODE>true</CODE> if it is CJK font
200: */
201: public static boolean isCJKFont(String fontName, String enc) {
202: loadProperties();
203: String encodings = cjkFonts.getProperty(fontName);
204: return (encodings != null && (enc.equals("Identity-H")
205: || enc.equals("Identity-V") || encodings.indexOf("_"
206: + enc + "_") >= 0));
207: }
208:
209: /**
210: * Gets the width of a <CODE>char</CODE> in normalized 1000 units.
211: * @param char1 the unicode <CODE>char</CODE> to get the width of
212: * @return the width in normalized 1000 units
213: */
214: public int getWidth(char char1) {
215: int c = (int) char1;
216: if (!cidDirect)
217: c = translationMap[c];
218: int v;
219: if (vertical)
220: v = vMetrics.get(c);
221: else
222: v = hMetrics.get(c);
223: if (v > 0)
224: return v;
225: else
226: return 1000;
227: }
228:
229: public int getWidth(String text) {
230: int total = 0;
231: for (int k = 0; k < text.length(); ++k) {
232: int c = text.charAt(k);
233: if (!cidDirect)
234: c = translationMap[c];
235: int v;
236: if (vertical)
237: v = vMetrics.get(c);
238: else
239: v = hMetrics.get(c);
240: if (v > 0)
241: total += v;
242: else
243: total += 1000;
244: }
245: return total;
246: }
247:
248: int getRawWidth(int c, String name) {
249: return 0;
250: }
251:
252: public int getKerning(char char1, char char2) {
253: return 0;
254: }
255:
256: private PdfDictionary getFontDescriptor() {
257: PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
258: dic.put(PdfName.ASCENT, new PdfLiteral((String) fontDesc
259: .get("Ascent")));
260: dic.put(PdfName.CAPHEIGHT, new PdfLiteral((String) fontDesc
261: .get("CapHeight")));
262: dic.put(PdfName.DESCENT, new PdfLiteral((String) fontDesc
263: .get("Descent")));
264: dic.put(PdfName.FLAGS, new PdfLiteral((String) fontDesc
265: .get("Flags")));
266: dic.put(PdfName.FONTBBOX, new PdfLiteral((String) fontDesc
267: .get("FontBBox")));
268: dic.put(PdfName.FONTNAME, new PdfName(fontName + style));
269: dic.put(PdfName.ITALICANGLE, new PdfLiteral((String) fontDesc
270: .get("ItalicAngle")));
271: dic.put(PdfName.STEMV, new PdfLiteral((String) fontDesc
272: .get("StemV")));
273: PdfDictionary pdic = new PdfDictionary();
274: pdic.put(PdfName.PANOSE, new PdfString((String) fontDesc
275: .get("Panose"), null));
276: dic.put(PdfName.STYLE, pdic);
277: return dic;
278: }
279:
280: private PdfDictionary getCIDFont(
281: PdfIndirectReference fontDescriptor, IntHashtable cjkTag) {
282: PdfDictionary dic = new PdfDictionary(PdfName.FONT);
283: dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0);
284: dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
285: dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
286: int keys[] = cjkTag.toOrderedKeys();
287: String w = convertToHCIDMetrics(keys, hMetrics);
288: if (w != null)
289: dic.put(PdfName.W, new PdfLiteral(w));
290: if (vertical) {
291: w = convertToVCIDMetrics(keys, vMetrics, hMetrics);
292: if (w != null)
293: dic.put(PdfName.W2, new PdfLiteral(w));
294: } else
295: dic.put(PdfName.DW, new PdfNumber(1000));
296: PdfDictionary cdic = new PdfDictionary();
297: cdic.put(PdfName.REGISTRY, new PdfString((String) fontDesc
298: .get("Registry"), null));
299: cdic.put(PdfName.ORDERING, new PdfString((String) fontDesc
300: .get("Ordering"), null));
301: cdic.put(PdfName.SUPPLEMENT, new PdfLiteral((String) fontDesc
302: .get("Supplement")));
303: dic.put(PdfName.CIDSYSTEMINFO, cdic);
304: return dic;
305: }
306:
307: private PdfDictionary getFontBaseType(PdfIndirectReference CIDFont) {
308: PdfDictionary dic = new PdfDictionary(PdfName.FONT);
309: dic.put(PdfName.SUBTYPE, PdfName.TYPE0);
310: String name = fontName;
311: if (style.length() > 0)
312: name += "-" + style.substring(1);
313: name += "-" + CMap;
314: dic.put(PdfName.BASEFONT, new PdfName(name));
315: dic.put(PdfName.ENCODING, new PdfName(CMap));
316: dic.put(PdfName.DESCENDANTFONTS, new PdfArray(CIDFont));
317: return dic;
318: }
319:
320: void writeFont(PdfWriter writer, PdfIndirectReference ref,
321: Object params[]) throws DocumentException, IOException {
322: IntHashtable cjkTag = (IntHashtable) params[0];
323: PdfIndirectReference ind_font = null;
324: PdfObject pobj = null;
325: PdfIndirectObject obj = null;
326: pobj = getFontDescriptor();
327: if (pobj != null) {
328: obj = writer.addToBody(pobj);
329: ind_font = obj.getIndirectReference();
330: }
331: pobj = getCIDFont(ind_font, cjkTag);
332: if (pobj != null) {
333: obj = writer.addToBody(pobj);
334: ind_font = obj.getIndirectReference();
335: }
336: pobj = getFontBaseType(ind_font);
337: writer.addToBody(pobj, ref);
338: }
339:
340: private float getDescNumber(String name) {
341: return Integer.parseInt((String) fontDesc.get(name));
342: }
343:
344: private float getBBox(int idx) {
345: String s = (String) fontDesc.get("FontBBox");
346: StringTokenizer tk = new StringTokenizer(s, " []\r\n\t\f");
347: String ret = tk.nextToken();
348: for (int k = 0; k < idx; ++k)
349: ret = tk.nextToken();
350: return Integer.parseInt(ret);
351: }
352:
353: /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
354: * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>
355: * and <CODE>ITALICANGLE</CODE>.
356: * @param key the parameter to be extracted
357: * @param fontSize the font size in points
358: * @return the parameter in points
359: */
360: public float getFontDescriptor(int key, float fontSize) {
361: switch (key) {
362: case AWT_ASCENT:
363: case ASCENT:
364: return getDescNumber("Ascent") * fontSize / 1000;
365: case CAPHEIGHT:
366: return getDescNumber("CapHeight") * fontSize / 1000;
367: case AWT_DESCENT:
368: case DESCENT:
369: return getDescNumber("Descent") * fontSize / 1000;
370: case ITALICANGLE:
371: return getDescNumber("ItalicAngle");
372: case BBOXLLX:
373: return fontSize * getBBox(0) / 1000;
374: case BBOXLLY:
375: return fontSize * getBBox(1) / 1000;
376: case BBOXURX:
377: return fontSize * getBBox(2) / 1000;
378: case BBOXURY:
379: return fontSize * getBBox(3) / 1000;
380: case AWT_LEADING:
381: return 0;
382: case AWT_MAXADVANCE:
383: return fontSize * (getBBox(2) - getBBox(0)) / 1000;
384: }
385: return 0;
386: }
387:
388: public String getPostscriptFontName() {
389: return fontName;
390: }
391:
392: /** Gets the full name of the font. If it is a True Type font
393: * each array element will have {Platform ID, Platform Encoding ID,
394: * Language ID, font name}. The interpretation of this values can be
395: * found in the Open Type specification, chapter 2, in the 'name' table.<br>
396: * For the other fonts the array has a single element with {"", "", "",
397: * font name}.
398: * @return the full name of the font
399: */
400: public String[][] getFullFontName() {
401: return new String[][] { { "", "", "", fontName } };
402: }
403:
404: /** Gets the family name of the font. If it is a True Type font
405: * each array element will have {Platform ID, Platform Encoding ID,
406: * Language ID, font name}. The interpretation of this values can be
407: * found in the Open Type specification, chapter 2, in the 'name' table.<br>
408: * For the other fonts the array has a single element with {"", "", "",
409: * font name}.
410: * @return the family name of the font
411: */
412: public String[][] getFamilyFontName() {
413: return getFullFontName();
414: }
415:
416: static char[] readCMap(String name) {
417: try {
418: name = name + ".cmap";
419: InputStream is = getResourceStream(RESOURCE_PATH + name);
420: char c[] = new char[0x10000];
421: for (int k = 0; k < 0x10000; ++k)
422: c[k] = (char) ((is.read() << 8) + is.read());
423: return c;
424: } catch (Exception e) {
425: // empty on purpose
426: }
427: return null;
428: }
429:
430: static IntHashtable createMetric(String s) {
431: IntHashtable h = new IntHashtable();
432: StringTokenizer tk = new StringTokenizer(s);
433: while (tk.hasMoreTokens()) {
434: int n1 = Integer.parseInt(tk.nextToken());
435: h.put(n1, Integer.parseInt(tk.nextToken()));
436: }
437: return h;
438: }
439:
440: static String convertToHCIDMetrics(int keys[], IntHashtable h) {
441: if (keys.length == 0)
442: return null;
443: int lastCid = 0;
444: int lastValue = 0;
445: int start;
446: for (start = 0; start < keys.length; ++start) {
447: lastCid = keys[start];
448: lastValue = h.get(lastCid);
449: if (lastValue != 0) {
450: ++start;
451: break;
452: }
453: }
454: if (lastValue == 0)
455: return null;
456: StringBuffer buf = new StringBuffer();
457: buf.append('[');
458: buf.append(lastCid);
459: int state = FIRST;
460: for (int k = start; k < keys.length; ++k) {
461: int cid = keys[k];
462: int value = h.get(cid);
463: if (value == 0)
464: continue;
465: switch (state) {
466: case FIRST: {
467: if (cid == lastCid + 1 && value == lastValue) {
468: state = SERIAL;
469: } else if (cid == lastCid + 1) {
470: state = BRACKET;
471: buf.append('[').append(lastValue);
472: } else {
473: buf.append('[').append(lastValue).append(']')
474: .append(cid);
475: }
476: break;
477: }
478: case BRACKET: {
479: if (cid == lastCid + 1 && value == lastValue) {
480: state = SERIAL;
481: buf.append(']').append(lastCid);
482: } else if (cid == lastCid + 1) {
483: buf.append(' ').append(lastValue);
484: } else {
485: state = FIRST;
486: buf.append(' ').append(lastValue).append(']')
487: .append(cid);
488: }
489: break;
490: }
491: case SERIAL: {
492: if (cid != lastCid + 1 || value != lastValue) {
493: buf.append(' ').append(lastCid).append(' ').append(
494: lastValue).append(' ').append(cid);
495: state = FIRST;
496: }
497: break;
498: }
499: }
500: lastValue = value;
501: lastCid = cid;
502: }
503: switch (state) {
504: case FIRST: {
505: buf.append('[').append(lastValue).append("]]");
506: break;
507: }
508: case BRACKET: {
509: buf.append(' ').append(lastValue).append("]]");
510: break;
511: }
512: case SERIAL: {
513: buf.append(' ').append(lastCid).append(' ').append(
514: lastValue).append(']');
515: break;
516: }
517: }
518: return buf.toString();
519: }
520:
521: static String convertToVCIDMetrics(int keys[], IntHashtable v,
522: IntHashtable h) {
523: if (keys.length == 0)
524: return null;
525: int lastCid = 0;
526: int lastValue = 0;
527: int lastHValue = 0;
528: int start;
529: for (start = 0; start < keys.length; ++start) {
530: lastCid = keys[start];
531: lastValue = v.get(lastCid);
532: if (lastValue != 0) {
533: ++start;
534: break;
535: } else
536: lastHValue = h.get(lastCid);
537: }
538: if (lastValue == 0)
539: return null;
540: if (lastHValue == 0)
541: lastHValue = 1000;
542: StringBuffer buf = new StringBuffer();
543: buf.append('[');
544: buf.append(lastCid);
545: int state = FIRST;
546: for (int k = start; k < keys.length; ++k) {
547: int cid = keys[k];
548: int value = v.get(cid);
549: if (value == 0)
550: continue;
551: int hValue = h.get(lastCid);
552: if (hValue == 0)
553: hValue = 1000;
554: switch (state) {
555: case FIRST: {
556: if (cid == lastCid + 1 && value == lastValue
557: && hValue == lastHValue) {
558: state = SERIAL;
559: } else {
560: buf.append(' ').append(lastCid).append(' ').append(
561: -lastValue).append(' ').append(
562: lastHValue / 2).append(' ').append(V1Y)
563: .append(' ').append(cid);
564: }
565: break;
566: }
567: case SERIAL: {
568: if (cid != lastCid + 1 || value != lastValue
569: || hValue != lastHValue) {
570: buf.append(' ').append(lastCid).append(' ').append(
571: -lastValue).append(' ').append(
572: lastHValue / 2).append(' ').append(V1Y)
573: .append(' ').append(cid);
574: state = FIRST;
575: }
576: break;
577: }
578: }
579: lastValue = value;
580: lastCid = cid;
581: lastHValue = hValue;
582: }
583: buf.append(' ').append(lastCid).append(' ').append(-lastValue)
584: .append(' ').append(lastHValue / 2).append(' ').append(
585: V1Y).append(" ]");
586: return buf.toString();
587: }
588:
589: static HashMap readFontProperties(String name) {
590: try {
591: name += ".properties";
592: InputStream is = getResourceStream(RESOURCE_PATH + name);
593: Properties p = new Properties();
594: p.load(is);
595: is.close();
596: IntHashtable W = createMetric(p.getProperty("W"));
597: p.remove("W");
598: IntHashtable W2 = createMetric(p.getProperty("W2"));
599: p.remove("W2");
600: HashMap map = new HashMap();
601: for (Enumeration e = p.keys(); e.hasMoreElements();) {
602: Object obj = e.nextElement();
603: map.put(obj, p.getProperty((String) obj));
604: }
605: map.put("W", W);
606: map.put("W2", W2);
607: return map;
608: } catch (Exception e) {
609: // empty on purpose
610: }
611: return null;
612: }
613:
614: public char getUnicodeEquivalent(char c) {
615: if (cidDirect)
616: return translationMap[c];
617: return c;
618: }
619:
620: public char getCidCode(char c) {
621: if (cidDirect)
622: return c;
623: return translationMap[c];
624: }
625:
626: /** Checks if the font has any kerning pairs.
627: * @return always <CODE>false</CODE>
628: */
629: public boolean hasKernPairs() {
630: return false;
631: }
632:
633: /**
634: * Checks if a character exists in this font.
635: * @param c the character to check
636: * @return <CODE>true</CODE> if the character has a glyph,
637: * <CODE>false</CODE> otherwise
638: */
639: public boolean charExists(char c) {
640: return translationMap[c] != 0;
641: }
642:
643: /**
644: * Sets the character advance.
645: * @param c the character
646: * @param advance the character advance normalized to 1000 units
647: * @return <CODE>true</CODE> if the advance was set,
648: * <CODE>false</CODE> otherwise. Will always return <CODE>false</CODE>
649: */
650: public boolean setCharAdvance(char c, int advance) {
651: return false;
652: }
653:
654: /**
655: * Sets the font name that will appear in the pdf font dictionary.
656: * Use with care as it can easily make a font unreadable if not embedded.
657: * @param name the new font name
658: */
659: public void setPostscriptFontName(String name) {
660: fontName = name;
661: }
662:
663: public boolean setKerning(char char1, char char2, int kern) {
664: return false;
665: }
666:
667: public int[] getCharBBox(char c) {
668: return null;
669: }
670:
671: protected int[] getRawCharBBox(int c, String name) {
672: return null;
673: }
674: }
|