1
49
50 package com.lowagie.text.pdf;
51
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.util.Enumeration;
55 import java.util.HashMap;
56 import java.util.Hashtable;
57 import java.util.Properties;
58 import java.util.StringTokenizer;
59
60 import com.lowagie.text.DocumentException;
61
62
67
68 class CJKFont extends BaseFont {
69
71 static final String CJK_ENCODING = "UnicodeBigUnmarked";
72 private static final int FIRST = 0;
73 private static final int BRACKET = 1;
74 private static final int SERIAL = 2;
75 private static final int V1Y = 880;
76
77 static Properties cjkFonts = new Properties();
78 static Properties cjkEncodings = new Properties();
79 static Hashtable allCMaps = new Hashtable();
80 static Hashtable allFonts = new Hashtable();
81 private static boolean propertiesLoaded = false;
82
83
84 private String fontName;
85
86 private String style = "";
87
88 private String CMap;
89
90 private boolean cidDirect = false;
91
92 private char[] translationMap;
93 private IntHashtable vMetrics;
94 private IntHashtable hMetrics;
95 private HashMap fontDesc;
96 private boolean vertical = false;
97
98 private static void loadProperties() {
99 if (propertiesLoaded)
100 return;
101 synchronized (allFonts) {
102 if (propertiesLoaded)
103 return;
104 try {
105 InputStream is = getResourceStream(RESOURCE_PATH + "cjkfonts.properties");
106 cjkFonts.load(is);
107 is.close();
108 is = getResourceStream(RESOURCE_PATH + "cjkencodings.properties");
109 cjkEncodings.load(is);
110 is.close();
111 }
112 catch (Exception e) {
113 cjkFonts = new Properties();
114 cjkEncodings = new Properties();
115 }
116 propertiesLoaded = true;
117 }
118 }
119
120
126 CJKFont(String fontName, String enc, boolean emb) throws DocumentException {
127 loadProperties();
128 fontType = FONT_TYPE_CJK;
129 String nameBase = getBaseName(fontName);
130 if (!isCJKFont(nameBase, enc))
131 throw new DocumentException("Font '" + fontName + "' with '" + enc + "' encoding is not a CJK font.");
132 if (nameBase.length() < fontName.length()) {
133 style = fontName.substring(nameBase.length());
134 fontName = nameBase;
135 }
136 this.fontName = fontName;
137 encoding = CJK_ENCODING;
138 vertical = enc.endsWith("V");
139 CMap = enc;
140 if (enc.startsWith("Identity-")) {
141 cidDirect = true;
142 String s = cjkFonts.getProperty(fontName);
143 s = s.substring(0, s.indexOf('_'));
144 char c[] = (char[])allCMaps.get(s);
145 if (c == null) {
146 c = readCMap(s);
147 if (c == null)
148 throw new DocumentException("The cmap " + s + " does not exist as a resource.");
149 c[CID_NEWLINE] = '\n';
150 allCMaps.put(s, c);
151 }
152 translationMap = c;
153 }
154 else {
155 char c[] = (char[])allCMaps.get(enc);
156 if (c == null) {
157 String s = cjkEncodings.getProperty(enc);
158 if (s == null)
159 throw new DocumentException("The resource cjkencodings.properties does not contain the encoding " + enc);
160 StringTokenizer tk = new StringTokenizer(s);
161 String nt = tk.nextToken();
162 c = (char[])allCMaps.get(nt);
163 if (c == null) {
164 c = readCMap(nt);
165 allCMaps.put(nt, c);
166 }
167 if (tk.hasMoreTokens()) {
168 String nt2 = tk.nextToken();
169 char m2[] = readCMap(nt2);
170 for (int k = 0; k < 0x10000; ++k) {
171 if (m2[k] == 0)
172 m2[k] = c[k];
173 }
174 allCMaps.put(enc, m2);
175 c = m2;
176 }
177 }
178 translationMap = c;
179 }
180 fontDesc = (HashMap)allFonts.get(fontName);
181 if (fontDesc == null) {
182 fontDesc = readFontProperties(fontName);
183 allFonts.put(fontName, fontDesc);
184 }
185 hMetrics = (IntHashtable)fontDesc.get("W");
186 vMetrics = (IntHashtable)fontDesc.get("W2");
187 }
188
189
194 public static boolean isCJKFont(String fontName, String enc) {
195 loadProperties();
196 String encodings = cjkFonts.getProperty(fontName);
197 return (encodings != null && (enc.equals("Identity-H") || enc.equals("Identity-V") || encodings.indexOf("_" + enc + "_") >= 0));
198 }
199
200
205 public int getWidth(int char1) {
206 int c = char1;
207 if (!cidDirect)
208 c = translationMap[c];
209 int v;
210 if (vertical)
211 v = vMetrics.get(c);
212 else
213 v = hMetrics.get(c);
214 if (v > 0)
215 return v;
216 else
217 return 1000;
218 }
219
220 public int getWidth(String text) {
221 int total = 0;
222 for (int k = 0; k < text.length(); ++k) {
223 int c = text.charAt(k);
224 if (!cidDirect)
225 c = translationMap[c];
226 int v;
227 if (vertical)
228 v = vMetrics.get(c);
229 else
230 v = hMetrics.get(c);
231 if (v > 0)
232 total += v;
233 else
234 total += 1000;
235 }
236 return total;
237 }
238
239 int getRawWidth(int c, String name) {
240 return 0;
241 }
242
243 public int getKerning(int char1, int char2) {
244 return 0;
245 }
246
247 private PdfDictionary getFontDescriptor() {
248 PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
249 dic.put(PdfName.ASCENT, new PdfLiteral((String)fontDesc.get("Ascent")));
250 dic.put(PdfName.CAPHEIGHT, new PdfLiteral((String)fontDesc.get("CapHeight")));
251 dic.put(PdfName.DESCENT, new PdfLiteral((String)fontDesc.get("Descent")));
252 dic.put(PdfName.FLAGS, new PdfLiteral((String)fontDesc.get("Flags")));
253 dic.put(PdfName.FONTBBOX, new PdfLiteral((String)fontDesc.get("FontBBox")));
254 dic.put(PdfName.FONTNAME, new PdfName(fontName + style));
255 dic.put(PdfName.ITALICANGLE, new PdfLiteral((String)fontDesc.get("ItalicAngle")));
256 dic.put(PdfName.STEMV, new PdfLiteral((String)fontDesc.get("StemV")));
257 PdfDictionary pdic = new PdfDictionary();
258 pdic.put(PdfName.PANOSE, new PdfString((String)fontDesc.get("Panose"), null));
259 dic.put(PdfName.STYLE, pdic);
260 return dic;
261 }
262
263 private PdfDictionary getCIDFont(PdfIndirectReference fontDescriptor, IntHashtable cjkTag) {
264 PdfDictionary dic = new PdfDictionary(PdfName.FONT);
265 dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0);
266 dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
267 dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
268 int keys[] = cjkTag.toOrderedKeys();
269 String w = convertToHCIDMetrics(keys, hMetrics);
270 if (w != null)
271 dic.put(PdfName.W, new PdfLiteral(w));
272 if (vertical) {
273 w = convertToVCIDMetrics(keys, vMetrics, hMetrics);
274 if (w != null)
275 dic.put(PdfName.W2, new PdfLiteral(w));
276 }
277 else
278 dic.put(PdfName.DW, new PdfNumber(1000));
279 PdfDictionary cdic = new PdfDictionary();
280 cdic.put(PdfName.REGISTRY, new PdfString((String)fontDesc.get("Registry"), null));
281 cdic.put(PdfName.ORDERING, new PdfString((String)fontDesc.get("Ordering"), null));
282 cdic.put(PdfName.SUPPLEMENT, new PdfLiteral((String)fontDesc.get("Supplement")));
283 dic.put(PdfName.CIDSYSTEMINFO, cdic);
284 return dic;
285 }
286
287 private PdfDictionary getFontBaseType(PdfIndirectReference CIDFont) {
288 PdfDictionary dic = new PdfDictionary(PdfName.FONT);
289 dic.put(PdfName.SUBTYPE, PdfName.TYPE0);
290 String name = fontName;
291 if (style.length() > 0)
292 name += "-" + style.substring(1);
293 name += "-" + CMap;
294 dic.put(PdfName.BASEFONT, new PdfName(name));
295 dic.put(PdfName.ENCODING, new PdfName(CMap));
296 dic.put(PdfName.DESCENDANTFONTS, new PdfArray(CIDFont));
297 return dic;
298 }
299
300 void writeFont(PdfWriter writer, PdfIndirectReference ref, Object params[]) throws DocumentException, IOException {
301 IntHashtable cjkTag = (IntHashtable)params[0];
302 PdfIndirectReference ind_font = null;
303 PdfObject pobj = null;
304 PdfIndirectObject obj = null;
305 pobj = getFontDescriptor();
306 if (pobj != null){
307 obj = writer.addToBody(pobj);
308 ind_font = obj.getIndirectReference();
309 }
310 pobj = getCIDFont(ind_font, cjkTag);
311 if (pobj != null){
312 obj = writer.addToBody(pobj);
313 ind_font = obj.getIndirectReference();
314 }
315 pobj = getFontBaseType(ind_font);
316 writer.addToBody(pobj, ref);
317 }
318
319
325 public PdfStream getFullFontStream() {
326 return null;
327 }
328
329 private float getDescNumber(String name) {
330 return Integer.parseInt((String)fontDesc.get(name));
331 }
332
333 private float getBBox(int idx) {
334 String s = (String)fontDesc.get("FontBBox");
335 StringTokenizer tk = new StringTokenizer(s, " []\r\n\t\f");
336 String ret = tk.nextToken();
337 for (int k = 0; k < idx; ++k)
338 ret = tk.nextToken();
339 return Integer.parseInt(ret);
340 }
341
342
349 public float getFontDescriptor(int key, float fontSize) {
350 switch (key) {
351 case AWT_ASCENT:
352 case ASCENT:
353 return getDescNumber("Ascent") * fontSize / 1000;
354 case CAPHEIGHT:
355 return getDescNumber("CapHeight") * fontSize / 1000;
356 case AWT_DESCENT:
357 case DESCENT:
358 return getDescNumber("Descent") * fontSize / 1000;
359 case ITALICANGLE:
360 return getDescNumber("ItalicAngle");
361 case BBOXLLX:
362 return fontSize * getBBox(0) / 1000;
363 case BBOXLLY:
364 return fontSize * getBBox(1) / 1000;
365 case BBOXURX:
366 return fontSize * getBBox(2) / 1000;
367 case BBOXURY:
368 return fontSize * getBBox(3) / 1000;
369 case AWT_LEADING:
370 return 0;
371 case AWT_MAXADVANCE:
372 return fontSize * (getBBox(2) - getBBox(0)) / 1000;
373 }
374 return 0;
375 }
376
377 public String getPostscriptFontName() {
378 return fontName;
379 }
380
381
389 public String[][] getFullFontName() {
390 return new String[][]{{"", "", "", fontName}};
391 }
392
393
401 public String[][] getAllNameEntries() {
402 return new String[][]{{"4", "", "", "", fontName}};
403 }
404
405
413 public String[][] getFamilyFontName() {
414 return getFullFontName();
415 }
416
417 static char[] readCMap(String name) {
418 try {
419 name = name + ".cmap";
420 InputStream is = getResourceStream(RESOURCE_PATH + name);
421 char c[] = new char[0x10000];
422 for (int k = 0; k < 0x10000; ++k)
423 c[k] = (char)((is.read() << 8) + is.read());
424 is.close();
425 return c;
426 }
427 catch (Exception e) {
428
429 }
430 return null;
431 }
432
433 static IntHashtable createMetric(String s) {
434 IntHashtable h = new IntHashtable();
435 StringTokenizer tk = new StringTokenizer(s);
436 while (tk.hasMoreTokens()) {
437 int n1 = Integer.parseInt(tk.nextToken());
438 h.put(n1, Integer.parseInt(tk.nextToken()));
439 }
440 return h;
441 }
442
443 static String convertToHCIDMetrics(int keys[], IntHashtable h) {
444 if (keys.length == 0)
445 return null;
446 int lastCid = 0;
447 int lastValue = 0;
448 int start;
449 for (start = 0; start < keys.length; ++start) {
450 lastCid = keys[start];
451 lastValue = h.get(lastCid);
452 if (lastValue != 0) {
453 ++start;
454 break;
455 }
456 }
457 if (lastValue == 0)
458 return null;
459 StringBuffer buf = new StringBuffer();
460 buf.append('[');
461 buf.append(lastCid);
462 int state = FIRST;
463 for (int k = start; k < keys.length; ++k) {
464 int cid = keys[k];
465 int value = h.get(cid);
466 if (value == 0)
467 continue;
468 switch (state) {
469 case FIRST: {
470 if (cid == lastCid + 1 && value == lastValue) {
471 state = SERIAL;
472 }
473 else if (cid == lastCid + 1) {
474 state = BRACKET;
475 buf.append('[').append(lastValue);
476 }
477 else {
478 buf.append('[').append(lastValue).append(']').append(cid);
479 }
480 break;
481 }
482 case BRACKET: {
483 if (cid == lastCid + 1 && value == lastValue) {
484 state = SERIAL;
485 buf.append(']').append(lastCid);
486 }
487 else if (cid == lastCid + 1) {
488 buf.append(' ').append(lastValue);
489 }
490 else {
491 state = FIRST;
492 buf.append(' ').append(lastValue).append(']').append(cid);
493 }
494 break;
495 }
496 case SERIAL: {
497 if (cid != lastCid + 1 || value != lastValue) {
498 buf.append(' ').append(lastCid).append(' ').append(lastValue).append(' ').append(cid);
499 state = FIRST;
500 }
501 break;
502 }
503 }
504 lastValue = value;
505 lastCid = cid;
506 }
507 switch (state) {
508 case FIRST: {
509 buf.append('[').append(lastValue).append("]]");
510 break;
511 }
512 case BRACKET: {
513 buf.append(' ').append(lastValue).append("]]");
514 break;
515 }
516 case SERIAL: {
517 buf.append(' ').append(lastCid).append(' ').append(lastValue).append(']');
518 break;
519 }
520 }
521 return buf.toString();
522 }
523
524 static String convertToVCIDMetrics(int keys[], IntHashtable v, IntHashtable h) {
525 if (keys.length == 0)
526 return null;
527 int lastCid = 0;
528 int lastValue = 0;
529 int lastHValue = 0;
530 int start;
531 for (start = 0; start < keys.length; ++start) {
532 lastCid = keys[start];
533 lastValue = v.get(lastCid);
534 if (lastValue != 0) {
535 ++start;
536 break;
537 }
538 else
539 lastHValue = h.get(lastCid);
540 }
541 if (lastValue == 0)
542 return null;
543 if (lastHValue == 0)
544 lastHValue = 1000;
545 StringBuffer buf = new StringBuffer();
546 buf.append('[');
547 buf.append(lastCid);
548 int state = FIRST;
549 for (int k = start; k < keys.length; ++k) {
550 int cid = keys[k];
551 int value = v.get(cid);
552 if (value == 0)
553 continue;
554 int hValue = h.get(lastCid);
555 if (hValue == 0)
556 hValue = 1000;
557 switch (state) {
558 case FIRST: {
559 if (cid == lastCid + 1 && value == lastValue && hValue == lastHValue) {
560 state = SERIAL;
561 }
562 else {
563 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid);
564 }
565 break;
566 }
567 case SERIAL: {
568 if (cid != lastCid + 1 || value != lastValue || hValue != lastHValue) {
569 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid);
570 state = FIRST;
571 }
572 break;
573 }
574 }
575 lastValue = value;
576 lastCid = cid;
577 lastHValue = hValue;
578 }
579 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(" ]");
580 return buf.toString();
581 }
582
583 static HashMap readFontProperties(String name) {
584 try {
585 name += ".properties";
586 InputStream is = getResourceStream(RESOURCE_PATH + name);
587 Properties p = new Properties();
588 p.load(is);
589 is.close();
590 IntHashtable W = createMetric(p.getProperty("W"));
591 p.remove("W");
592 IntHashtable W2 = createMetric(p.getProperty("W2"));
593 p.remove("W2");
594 HashMap map = new HashMap();
595 for (Enumeration e = p.keys(); e.hasMoreElements();) {
596 Object obj = e.nextElement();
597 map.put(obj, p.getProperty((String)obj));
598 }
599 map.put("W", W);
600 map.put("W2", W2);
601 return map;
602 }
603 catch (Exception e) {
604
605 }
606 return null;
607 }
608
609 public int getUnicodeEquivalent(int c) {
610 if (cidDirect)
611 return translationMap[c];
612 return c;
613 }
614
615 public int getCidCode(int c) {
616 if (cidDirect)
617 return c;
618 return translationMap[c];
619 }
620
621
624 public boolean hasKernPairs() {
625 return false;
626 }
627
628
634 public boolean charExists(int c) {
635 return translationMap[c] != 0;
636 }
637
638
645 public boolean setCharAdvance(int c, int advance) {
646 return false;
647 }
648
649
654 public void setPostscriptFontName(String name) {
655 fontName = name;
656 }
657
658 public boolean setKerning(int char1, int char2, int kern) {
659 return false;
660 }
661
662 public int[] getCharBBox(int c) {
663 return null;
664 }
665
666 protected int[] getRawCharBBox(int c, String name) {
667 return null;
668 }
669 }