KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > lowagie > text > pdf > CJKFont


1 /*
2  * $Id: CJKFont.java 2382 2006-09-15 23:37:38Z xlv $
3  * $Name$
4  *
5  * Copyright 2000, 2001, 2002 by Paulo Soares.
6  *
7  * The contents of this file are subject to the Mozilla Public License Version 1.1
8  * (the "License"); you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the License.
14  *
15  * The Original Code is 'iText, a free JAVA-PDF library'.
16  *
17  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
18  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
19  * All Rights Reserved.
20  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
21  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
22  *
23  * Contributor(s): all the names of the contributors are added in the source code
24  * where applicable.
25  *
26  * Alternatively, the contents of this file may be used under the terms of the
27  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
28  * provisions of LGPL are applicable instead of those above. If you wish to
29  * allow use of your version of this file only under the terms of the LGPL
30  * License and not to allow others to use your version of this file under
31  * the MPL, indicate your decision by deleting the provisions above and
32  * replace them with the notice and other provisions required by the LGPL.
33  * If you do not delete the provisions above, a recipient may use your version
34  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
35  *
36  * This library is free software; you can redistribute it and/or modify it
37  * under the terms of the MPL as stated above or under the terms of the GNU
38  * Library General Public License as published by the Free Software Foundation;
39  * either version 2 of the License, or any later version.
40  *
41  * This library is distributed in the hope that it will be useful, but WITHOUT
42  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
43  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
44  * details.
45  *
46  * If you didn't download this code from the following link, you should check if
47  * you aren't using an obsolete version:
48  * http://www.lowagie.com/iText/
49  */

50
51 package com.lowagie.text.pdf;
52
53 import java.io.IOException JavaDoc;
54 import java.io.InputStream JavaDoc;
55 import java.util.Enumeration JavaDoc;
56 import java.util.HashMap JavaDoc;
57 import java.util.Hashtable JavaDoc;
58 import java.util.Properties JavaDoc;
59 import java.util.StringTokenizer JavaDoc;
60
61 import com.lowagie.text.DocumentException;
62
63 /**
64  * Creates a CJK font compatible with the fonts in the Adobe Asian font Pack.
65  *
66  * @author Paulo Soares (psoares@consiste.pt)
67  */

68
69 class CJKFont extends BaseFont {
70     /** The encoding used in the PDF document for CJK fonts
71      */

72     static final String JavaDoc CJK_ENCODING = "UnicodeBigUnmarked";
73     private static final int FIRST = 0;
74     private static final int BRACKET = 1;
75     private static final int SERIAL = 2;
76     private static final int V1Y = 880;
77         
78     static Properties JavaDoc cjkFonts = new Properties JavaDoc();
79     static Properties JavaDoc cjkEncodings = new Properties JavaDoc();
80     static Hashtable JavaDoc allCMaps = new Hashtable JavaDoc();
81     static Hashtable JavaDoc allFonts = new Hashtable JavaDoc();
82     private static boolean propertiesLoaded = false;
83     
84     /** The font name */
85     private String JavaDoc fontName;
86     /** The style modifier */
87     private String JavaDoc style = "";
88     /** The CMap name associated with this font */
89     private String JavaDoc CMap;
90     
91     private boolean cidDirect = false;
92     
93     private char[] translationMap;
94     private IntHashtable vMetrics;
95     private IntHashtable hMetrics;
96     private HashMap JavaDoc fontDesc;
97     private boolean vertical = false;
98     
99     private static void loadProperties() {
100         if (propertiesLoaded)
101             return;
102         synchronized (allFonts) {
103             if (propertiesLoaded)
104                 return;
105             try {
106                 InputStream JavaDoc is = getResourceStream(RESOURCE_PATH + "cjkfonts.properties");
107                 cjkFonts.load(is);
108                 is.close();
109                 is = getResourceStream(RESOURCE_PATH + "cjkencodings.properties");
110                 cjkEncodings.load(is);
111                 is.close();
112             }
113             catch (Exception JavaDoc e) {
114                 cjkFonts = new Properties JavaDoc();
115                 cjkEncodings = new Properties JavaDoc();
116             }
117             propertiesLoaded = true;
118         }
119     }
120     
121     /** Creates a CJK font.
122      * @param fontName the name of the font
123      * @param enc the encoding of the font
124      * @param emb always <CODE>false</CODE>. CJK font and not embedded
125      * @throws DocumentException on error
126      */

127     CJKFont(String JavaDoc fontName, String JavaDoc enc, boolean emb) throws DocumentException {
128         loadProperties();
129         fontType = FONT_TYPE_CJK;
130         String JavaDoc nameBase = getBaseName(fontName);
131         if (!isCJKFont(nameBase, enc))
132             throw new DocumentException("Font '" + fontName + "' with '" + enc + "' encoding is not a CJK font.");
133         if (nameBase.length() < fontName.length()) {
134             style = fontName.substring(nameBase.length());
135             fontName = nameBase;
136         }
137         this.fontName = fontName;
138         encoding = CJK_ENCODING;
139         vertical = enc.endsWith("V");
140         CMap = enc;
141         if (enc.startsWith("Identity-")) {
142             cidDirect = true;
143             String JavaDoc s = cjkFonts.getProperty(fontName);
144             s = s.substring(0, s.indexOf('_'));
145             char c[] = (char[])allCMaps.get(s);
146             if (c == null) {
147                 c = readCMap(s);
148                 if (c == null)
149                     throw new DocumentException("The cmap " + s + " does not exist as a resource.");
150                 c[CID_NEWLINE] = '\n';
151                 allCMaps.put(s, c);
152             }
153             translationMap = c;
154         }
155         else {
156             char c[] = (char[])allCMaps.get(enc);
157             if (c == null) {
158                 String JavaDoc s = cjkEncodings.getProperty(enc);
159                 if (s == null)
160                     throw new DocumentException("The resource cjkencodings.properties does not contain the encoding " + enc);
161                 StringTokenizer JavaDoc tk = new StringTokenizer JavaDoc(s);
162                 String JavaDoc nt = tk.nextToken();
163                 c = (char[])allCMaps.get(nt);
164                 if (c == null) {
165                     c = readCMap(nt);
166                     allCMaps.put(nt, c);
167                 }
168                 if (tk.hasMoreTokens()) {
169                     String JavaDoc nt2 = tk.nextToken();
170                     char m2[] = readCMap(nt2);
171                     for (int k = 0; k < 0x10000; ++k) {
172                         if (m2[k] == 0)
173                             m2[k] = c[k];
174                     }
175                     allCMaps.put(enc, m2);
176                     c = m2;
177                 }
178             }
179             translationMap = c;
180         }
181         fontDesc = (HashMap JavaDoc)allFonts.get(fontName);
182         if (fontDesc == null) {
183             fontDesc = readFontProperties(fontName);
184             allFonts.put(fontName, fontDesc);
185         }
186         hMetrics = (IntHashtable)fontDesc.get("W");
187         vMetrics = (IntHashtable)fontDesc.get("W2");
188     }
189     
190     /** Checks if its a valid CJK font.
191      * @param fontName the font name
192      * @param enc the encoding
193      * @return <CODE>true</CODE> if it is CJK font
194      */

195     public static boolean isCJKFont(String JavaDoc fontName, String JavaDoc enc) {
196         loadProperties();
197         String JavaDoc encodings = cjkFonts.getProperty(fontName);
198         return (encodings != null && (enc.equals("Identity-H") || enc.equals("Identity-V") || encodings.indexOf("_" + enc + "_") >= 0));
199     }
200         
201     public int getWidth(String JavaDoc text) {
202         int total = 0;
203         for (int k = 0; k < text.length(); ++k) {
204             int c = text.charAt(k);
205             if (!cidDirect)
206                 c = translationMap[c];
207             int v;
208             if (vertical)
209                 v = vMetrics.get(c);
210             else
211                 v = hMetrics.get(c);
212             if (v > 0)
213                 total += v;
214             else
215                 total += 1000;
216         }
217         return total;
218     }
219     
220     int getRawWidth(int c, String JavaDoc name) {
221         return 0;
222     }
223   
224     public int getKerning(char char1, char char2) {
225         return 0;
226     }
227
228     private PdfDictionary getFontDescriptor() {
229         PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
230         dic.put(PdfName.ASCENT, new PdfLiteral((String JavaDoc)fontDesc.get("Ascent")));
231         dic.put(PdfName.CAPHEIGHT, new PdfLiteral((String JavaDoc)fontDesc.get("CapHeight")));
232         dic.put(PdfName.DESCENT, new PdfLiteral((String JavaDoc)fontDesc.get("Descent")));
233         dic.put(PdfName.FLAGS, new PdfLiteral((String JavaDoc)fontDesc.get("Flags")));
234         dic.put(PdfName.FONTBBOX, new PdfLiteral((String JavaDoc)fontDesc.get("FontBBox")));
235         dic.put(PdfName.FONTNAME, new PdfName(fontName + style));
236         dic.put(PdfName.ITALICANGLE, new PdfLiteral((String JavaDoc)fontDesc.get("ItalicAngle")));
237         dic.put(PdfName.STEMV, new PdfLiteral((String JavaDoc)fontDesc.get("StemV")));
238         PdfDictionary pdic = new PdfDictionary();
239         pdic.put(PdfName.PANOSE, new PdfString((String JavaDoc)fontDesc.get("Panose"), null));
240         dic.put(PdfName.STYLE, pdic);
241         return dic;
242     }
243     
244     private PdfDictionary getCIDFont(PdfIndirectReference fontDescriptor, IntHashtable cjkTag) {
245         PdfDictionary dic = new PdfDictionary(PdfName.FONT);
246         dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0);
247         dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
248         dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
249         int keys[] = cjkTag.toOrderedKeys();
250         String JavaDoc w = convertToHCIDMetrics(keys, hMetrics);
251         if (w != null)
252             dic.put(PdfName.W, new PdfLiteral(w));
253         if (vertical) {
254             w = convertToVCIDMetrics(keys, vMetrics, hMetrics);
255             if (w != null)
256                 dic.put(PdfName.W2, new PdfLiteral(w));
257         }
258         else
259             dic.put(PdfName.DW, new PdfNumber(1000));
260         PdfDictionary cdic = new PdfDictionary();
261         cdic.put(PdfName.REGISTRY, new PdfString((String JavaDoc)fontDesc.get("Registry"), null));
262         cdic.put(PdfName.ORDERING, new PdfString((String JavaDoc)fontDesc.get("Ordering"), null));
263         cdic.put(PdfName.SUPPLEMENT, new PdfLiteral((String JavaDoc)fontDesc.get("Supplement")));
264         dic.put(PdfName.CIDSYSTEMINFO, cdic);
265         return dic;
266     }
267     
268     private PdfDictionary getFontBaseType(PdfIndirectReference CIDFont) {
269         PdfDictionary dic = new PdfDictionary(PdfName.FONT);
270         dic.put(PdfName.SUBTYPE, PdfName.TYPE0);
271         String JavaDoc name = fontName;
272         if (style.length() > 0)
273             name += "-" + style.substring(1);
274         name += "-" + CMap;
275         dic.put(PdfName.BASEFONT, new PdfName(name));
276         dic.put(PdfName.ENCODING, new PdfName(CMap));
277         dic.put(PdfName.DESCENDANTFONTS, new PdfArray(CIDFont));
278         return dic;
279     }
280     
281     void writeFont(PdfWriter writer, PdfIndirectReference ref, Object JavaDoc params[]) throws DocumentException, IOException JavaDoc {
282         IntHashtable cjkTag = (IntHashtable)params[0];
283         PdfIndirectReference ind_font = null;
284         PdfObject pobj = null;
285         PdfIndirectObject obj = null;
286         pobj = getFontDescriptor();
287         if (pobj != null){
288             obj = writer.addToBody(pobj);
289             ind_font = obj.getIndirectReference();
290         }
291         pobj = getCIDFont(ind_font, cjkTag);
292         if (pobj != null){
293             obj = writer.addToBody(pobj);
294             ind_font = obj.getIndirectReference();
295         }
296         pobj = getFontBaseType(ind_font);
297         writer.addToBody(pobj, ref);
298     }
299     
300     private float getDescNumber(String JavaDoc name) {
301         return Integer.parseInt((String JavaDoc)fontDesc.get(name));
302     }
303     
304     private float getBBox(int idx) {
305         String JavaDoc s = (String JavaDoc)fontDesc.get("FontBBox");
306         StringTokenizer JavaDoc tk = new StringTokenizer JavaDoc(s, " []\r\n\t\f");
307         String JavaDoc ret = tk.nextToken();
308         for (int k = 0; k < idx; ++k)
309             ret = tk.nextToken();
310         return Integer.parseInt(ret);
311     }
312     
313     /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
314      * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>
315      * and <CODE>ITALICANGLE</CODE>.
316      * @param key the parameter to be extracted
317      * @param fontSize the font size in points
318      * @return the parameter in points
319      */

320     public float getFontDescriptor(int key, float fontSize) {
321         switch (key) {
322             case AWT_ASCENT:
323             case ASCENT:
324                 return getDescNumber("Ascent") * fontSize / 1000;
325             case CAPHEIGHT:
326                 return getDescNumber("CapHeight") * fontSize / 1000;
327             case AWT_DESCENT:
328             case DESCENT:
329                 return getDescNumber("Descent") * fontSize / 1000;
330             case ITALICANGLE:
331                 return getDescNumber("ItalicAngle");
332             case BBOXLLX:
333                 return fontSize * getBBox(0) / 1000;
334             case BBOXLLY:
335                 return fontSize * getBBox(1) / 1000;
336             case BBOXURX:
337                 return fontSize * getBBox(2) / 1000;
338             case BBOXURY:
339                 return fontSize * getBBox(3) / 1000;
340             case AWT_LEADING:
341                 return 0;
342             case AWT_MAXADVANCE:
343                 return fontSize * (getBBox(2) - getBBox(0)) / 1000;
344         }
345         return 0;
346     }
347     
348     public String JavaDoc getPostscriptFontName() {
349         return fontName;
350     }
351     
352     /** Gets the full name of the font. If it is a True Type font
353      * each array element will have {Platform ID, Platform Encoding ID,
354      * Language ID, font name}. The interpretation of this values can be
355      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
356      * For the other fonts the array has a single element with {"", "", "",
357      * font name}.
358      * @return the full name of the font
359      */

360     public String JavaDoc[][] getFullFontName() {
361         return new String JavaDoc[][]{{"", "", "", fontName}};
362     }
363     
364     /** Gets the family name of the font. If it is a True Type font
365      * each array element will have {Platform ID, Platform Encoding ID,
366      * Language ID, font name}. The interpretation of this values can be
367      * found in the Open Type specification, chapter 2, in the 'name' table.<br>
368      * For the other fonts the array has a single element with {"", "", "",
369      * font name}.
370      * @return the family name of the font
371      */

372     public String JavaDoc[][] getFamilyFontName() {
373         return getFullFontName();
374     }
375     
376     static char[] readCMap(String JavaDoc name) {
377         try {
378             name = name + ".cmap";
379             InputStream JavaDoc is = getResourceStream(RESOURCE_PATH + name);
380             char c[] = new char[0x10000];
381             for (int k = 0; k < 0x10000; ++k)
382                 c[k] = (char)((is.read() << 8) + is.read());
383             return c;
384         }
385         catch (Exception JavaDoc e) {
386             // empty on purpose
387
}
388         return null;
389     }
390     
391     static IntHashtable createMetric(String JavaDoc s) {
392         IntHashtable h = new IntHashtable();
393         StringTokenizer JavaDoc tk = new StringTokenizer JavaDoc(s);
394         while (tk.hasMoreTokens()) {
395             int n1 = Integer.parseInt(tk.nextToken());
396             h.put(n1, Integer.parseInt(tk.nextToken()));
397         }
398         return h;
399     }
400     
401     static String JavaDoc convertToHCIDMetrics(int keys[], IntHashtable h) {
402         if (keys.length == 0)
403             return null;
404         int lastCid = 0;
405         int lastValue = 0;
406         int start;
407         for (start = 0; start < keys.length; ++start) {
408             lastCid = keys[start];
409             lastValue = h.get(lastCid);
410             if (lastValue != 0) {
411                 ++start;
412                 break;
413             }
414         }
415         if (lastValue == 0)
416             return null;
417         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
418         buf.append('[');
419         buf.append(lastCid);
420         int state = FIRST;
421         for (int k = start; k < keys.length; ++k) {
422             int cid = keys[k];
423             int value = h.get(cid);
424             if (value == 0)
425                 continue;
426             switch (state) {
427                 case FIRST: {
428                     if (cid == lastCid + 1 && value == lastValue) {
429                         state = SERIAL;
430                     }
431                     else if (cid == lastCid + 1) {
432                         state = BRACKET;
433                         buf.append('[').append(lastValue);
434                     }
435                     else {
436                         buf.append('[').append(lastValue).append(']').append(cid);
437                     }
438                     break;
439                 }
440                 case BRACKET: {
441                     if (cid == lastCid + 1 && value == lastValue) {
442                         state = SERIAL;
443                         buf.append(']').append(lastCid);
444                     }
445                     else if (cid == lastCid + 1) {
446                         buf.append(' ').append(lastValue);
447                     }
448                     else {
449                         state = FIRST;
450                         buf.append(' ').append(lastValue).append(']').append(cid);
451                     }
452                     break;
453                 }
454                 case SERIAL: {
455                     if (cid != lastCid + 1 || value != lastValue) {
456                         buf.append(' ').append(lastCid).append(' ').append(lastValue).append(' ').append(cid);
457                         state = FIRST;
458                     }
459                     break;
460                 }
461             }
462             lastValue = value;
463             lastCid = cid;
464         }
465         switch (state) {
466             case FIRST: {
467                 buf.append('[').append(lastValue).append("]]");
468                 break;
469             }
470             case BRACKET: {
471                 buf.append(' ').append(lastValue).append("]]");
472                 break;
473             }
474             case SERIAL: {
475                 buf.append(' ').append(lastCid).append(' ').append(lastValue).append(']');
476                 break;
477             }
478         }
479         return buf.toString();
480     }
481     
482     static String JavaDoc convertToVCIDMetrics(int keys[], IntHashtable v, IntHashtable h) {
483         if (keys.length == 0)
484             return null;
485         int lastCid = 0;
486         int lastValue = 0;
487         int lastHValue = 0;
488         int start;
489         for (start = 0; start < keys.length; ++start) {
490             lastCid = keys[start];
491             lastValue = v.get(lastCid);
492             if (lastValue != 0) {
493                 ++start;
494                 break;
495             }
496             else
497                 lastHValue = h.get(lastCid);
498         }
499         if (lastValue == 0)
500             return null;
501         if (lastHValue == 0)
502             lastHValue = 1000;
503         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
504         buf.append('[');
505         buf.append(lastCid);
506         int state = FIRST;
507         for (int k = start; k < keys.length; ++k) {
508             int cid = keys[k];
509             int value = v.get(cid);
510             if (value == 0)
511                 continue;
512             int hValue = h.get(lastCid);
513             if (hValue == 0)
514                 hValue = 1000;
515             switch (state) {
516                 case FIRST: {
517                     if (cid == lastCid + 1 && value == lastValue && hValue == lastHValue) {
518                         state = SERIAL;
519                     }
520                     else {
521                         buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid);
522                     }
523                     break;
524                 }
525                 case SERIAL: {
526                     if (cid != lastCid + 1 || value != lastValue || hValue != lastHValue) {
527                         buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid);
528                         state = FIRST;
529                     }
530                     break;
531                 }
532             }
533             lastValue = value;
534             lastCid = cid;
535             lastHValue = hValue;
536         }
537         buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(" ]");
538         return buf.toString();
539     }
540     
541     static HashMap JavaDoc readFontProperties(String JavaDoc name) {
542         try {
543             name += ".properties";
544             InputStream JavaDoc is = getResourceStream(RESOURCE_PATH + name);
545             Properties JavaDoc p = new Properties JavaDoc();
546             p.load(is);
547             is.close();
548             IntHashtable W = createMetric(p.getProperty("W"));
549             p.remove("W");
550             IntHashtable W2 = createMetric(p.getProperty("W2"));
551             p.remove("W2");
552             HashMap JavaDoc map = new HashMap JavaDoc();
553             for (Enumeration JavaDoc e = p.keys(); e.hasMoreElements();) {
554                 Object JavaDoc obj = e.nextElement();
555                 map.put(obj, p.getProperty((String JavaDoc)obj));
556             }
557             map.put("W", W);
558             map.put("W2", W2);
559             return map;
560         }
561         catch (Exception JavaDoc e) {
562             // empty on purpose
563
}
564         return null;
565     }
566
567     public char getUnicodeEquivalent(char c) {
568         if (cidDirect)
569             return translationMap[c];
570         return c;
571     }
572     
573     public char getCidCode(char c) {
574         if (cidDirect)
575             return c;
576         return translationMap[c];
577     }
578     
579     /** Checks if the font has any kerning pairs.
580      * @return always <CODE>false</CODE>
581      */

582     public boolean hasKernPairs() {
583         return false;
584     }
585     
586     /**
587      * Checks if a character exists in this font.
588      * @param c the character to check
589      * @return <CODE>true</CODE> if the character has a glyph,
590      * <CODE>false</CODE> otherwise
591      */

592     public boolean charExists(char c) {
593         return translationMap[c] != 0;
594     }
595     
596     /**
597      * Sets the character advance.
598      * @param c the character
599      * @param advance the character advance normalized to 1000 units
600      * @return <CODE>true</CODE> if the advance was set,
601      * <CODE>false</CODE> otherwise. Will always return <CODE>false</CODE>
602      */

603     public boolean setCharAdvance(char c, int advance) {
604         return false;
605     }
606     
607     /**
608      * Sets the font name that will appear in the pdf font dictionary.
609      * Use with care as it can easily make a font unreadable if not embedded.
610      * @param name the new font name
611      */

612     public void setPostscriptFontName(String JavaDoc name) {
613         fontName = name;
614     }
615     
616     public boolean setKerning(char char1, char char2, int kern) {
617         return false;
618     }
619     
620     public int[] getCharBBox(char c) {
621         return null;
622     }
623     
624     protected int[] getRawCharBBox(int c, String JavaDoc name) {
625         return null;
626     }
627 }
Popular Tags