001/* 002 * $Id: CJKFont.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.io.InputStream; 048import java.util.Enumeration; 049import java.util.HashMap; 050import java.util.Hashtable; 051import java.util.Properties; 052import java.util.StringTokenizer; 053 054import com.itextpdf.text.DocumentException; 055import com.itextpdf.text.error_messages.MessageLocalization; 056 057/** 058 * Creates a CJK font compatible with the fonts in the Adobe Asian font Pack. 059 * 060 * @author Paulo Soares 061 */ 062 063class CJKFont extends BaseFont { 064 /** The encoding used in the PDF document for CJK fonts 065 */ 066 static final String CJK_ENCODING = "UnicodeBigUnmarked"; 067 private static final int FIRST = 0; 068 private static final int BRACKET = 1; 069 private static final int SERIAL = 2; 070 private static final int V1Y = 880; 071 072 static Properties cjkFonts = new Properties(); 073 static Properties cjkEncodings = new Properties(); 074 static Hashtable<String, char[]> allCMaps = new Hashtable<String, char[]>(); 075 static Hashtable<String, HashMap<String, Object>> allFonts = new Hashtable<String, HashMap<String, Object>>(); 076 private static boolean propertiesLoaded = false; 077 078 /** The font name */ 079 private String fontName; 080 /** The style modifier */ 081 private String style = ""; 082 /** The CMap name associated with this font */ 083 private String CMap; 084 085 private boolean cidDirect = false; 086 087 private char[] translationMap; 088 private IntHashtable vMetrics; 089 private IntHashtable hMetrics; 090 private HashMap<String, Object> fontDesc; 091 private boolean vertical = false; 092 093 private static void loadProperties() { 094 if (propertiesLoaded) 095 return; 096 synchronized (allFonts) { 097 if (propertiesLoaded) 098 return; 099 try { 100 InputStream is = getResourceStream(RESOURCE_PATH + "cjkfonts.properties"); 101 cjkFonts.load(is); 102 is.close(); 103 is = getResourceStream(RESOURCE_PATH + "cjkencodings.properties"); 104 cjkEncodings.load(is); 105 is.close(); 106 } 107 catch (Exception e) { 108 cjkFonts = new Properties(); 109 cjkEncodings = new Properties(); 110 } 111 propertiesLoaded = true; 112 } 113 } 114 115 /** Creates a CJK font. 116 * @param fontName the name of the font 117 * @param enc the encoding of the font 118 * @param emb always <CODE>false</CODE>. CJK font and not embedded 119 * @throws DocumentException on error 120 */ 121 CJKFont(String fontName, String enc, boolean emb) throws DocumentException { 122 loadProperties(); 123 fontType = FONT_TYPE_CJK; 124 String nameBase = getBaseName(fontName); 125 if (!isCJKFont(nameBase, enc)) 126 throw new DocumentException(MessageLocalization.getComposedMessage("font.1.with.2.encoding.is.not.a.cjk.font", fontName, enc)); 127 if (nameBase.length() < fontName.length()) { 128 style = fontName.substring(nameBase.length()); 129 fontName = nameBase; 130 } 131 this.fontName = fontName; 132 encoding = CJK_ENCODING; 133 vertical = enc.endsWith("V"); 134 CMap = enc; 135 if (enc.startsWith("Identity-")) { 136 cidDirect = true; 137 String s = cjkFonts.getProperty(fontName); 138 s = s.substring(0, s.indexOf('_')); 139 char c[] = allCMaps.get(s); 140 if (c == null) { 141 c = readCMap(s); 142 if (c == null) 143 throw new DocumentException(MessageLocalization.getComposedMessage("the.cmap.1.does.not.exist.as.a.resource", s)); 144 c[CID_NEWLINE] = '\n'; 145 allCMaps.put(s, c); 146 } 147 translationMap = c; 148 } 149 else { 150 char c[] = allCMaps.get(enc); 151 if (c == null) { 152 String s = cjkEncodings.getProperty(enc); 153 if (s == null) 154 throw new DocumentException(MessageLocalization.getComposedMessage("the.resource.cjkencodings.properties.does.not.contain.the.encoding.1", enc)); 155 StringTokenizer tk = new StringTokenizer(s); 156 String nt = tk.nextToken(); 157 c = allCMaps.get(nt); 158 if (c == null) { 159 c = readCMap(nt); 160 allCMaps.put(nt, c); 161 } 162 if (tk.hasMoreTokens()) { 163 String nt2 = tk.nextToken(); 164 char m2[] = readCMap(nt2); 165 for (int k = 0; k < 0x10000; ++k) { 166 if (m2[k] == 0) 167 m2[k] = c[k]; 168 } 169 allCMaps.put(enc, m2); 170 c = m2; 171 } 172 } 173 translationMap = c; 174 } 175 fontDesc = allFonts.get(fontName); 176 if (fontDesc == null) { 177 fontDesc = readFontProperties(fontName); 178 allFonts.put(fontName, fontDesc); 179 } 180 hMetrics = (IntHashtable)fontDesc.get("W"); 181 vMetrics = (IntHashtable)fontDesc.get("W2"); 182 } 183 184 /** Checks if its a valid CJK font. 185 * @param fontName the font name 186 * @param enc the encoding 187 * @return <CODE>true</CODE> if it is CJK font 188 */ 189 public static boolean isCJKFont(String fontName, String enc) { 190 loadProperties(); 191 String encodings = cjkFonts.getProperty(fontName); 192 return encodings != null && (enc.equals("Identity-H") || enc.equals("Identity-V") || encodings.indexOf("_" + enc + "_") >= 0); 193 } 194 195 /** 196 * Gets the width of a <CODE>char</CODE> in normalized 1000 units. 197 * @param char1 the unicode <CODE>char</CODE> to get the width of 198 * @return the width in normalized 1000 units 199 */ 200 @Override 201 public int getWidth(int char1) { 202 int c = char1; 203 if (!cidDirect) 204 c = translationMap[c]; 205 int v; 206 if (vertical) 207 v = vMetrics.get(c); 208 else 209 v = hMetrics.get(c); 210 if (v > 0) 211 return v; 212 else 213 return 1000; 214 } 215 216 @Override 217 public int getWidth(String text) { 218 int total = 0; 219 for (int k = 0; k < text.length(); ++k) { 220 int c = text.charAt(k); 221 if (!cidDirect) 222 c = translationMap[c]; 223 int v; 224 if (vertical) 225 v = vMetrics.get(c); 226 else 227 v = hMetrics.get(c); 228 if (v > 0) 229 total += v; 230 else 231 total += 1000; 232 } 233 return total; 234 } 235 236 @Override 237 int getRawWidth(int c, String name) { 238 return 0; 239 } 240 241 @Override 242 public int getKerning(int char1, int char2) { 243 return 0; 244 } 245 246 private PdfDictionary getFontDescriptor() { 247 PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR); 248 dic.put(PdfName.ASCENT, new PdfLiteral((String)fontDesc.get("Ascent"))); 249 dic.put(PdfName.CAPHEIGHT, new PdfLiteral((String)fontDesc.get("CapHeight"))); 250 dic.put(PdfName.DESCENT, new PdfLiteral((String)fontDesc.get("Descent"))); 251 dic.put(PdfName.FLAGS, new PdfLiteral((String)fontDesc.get("Flags"))); 252 dic.put(PdfName.FONTBBOX, new PdfLiteral((String)fontDesc.get("FontBBox"))); 253 dic.put(PdfName.FONTNAME, new PdfName(fontName + style)); 254 dic.put(PdfName.ITALICANGLE, new PdfLiteral((String)fontDesc.get("ItalicAngle"))); 255 dic.put(PdfName.STEMV, new PdfLiteral((String)fontDesc.get("StemV"))); 256 PdfDictionary pdic = new PdfDictionary(); 257 pdic.put(PdfName.PANOSE, new PdfString((String)fontDesc.get("Panose"), null)); 258 dic.put(PdfName.STYLE, pdic); 259 return dic; 260 } 261 262 private PdfDictionary getCIDFont(PdfIndirectReference fontDescriptor, IntHashtable cjkTag) { 263 PdfDictionary dic = new PdfDictionary(PdfName.FONT); 264 dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0); 265 dic.put(PdfName.BASEFONT, new PdfName(fontName + style)); 266 dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor); 267 int keys[] = cjkTag.toOrderedKeys(); 268 String w = convertToHCIDMetrics(keys, hMetrics); 269 if (w != null) 270 dic.put(PdfName.W, new PdfLiteral(w)); 271 if (vertical) { 272 w = convertToVCIDMetrics(keys, vMetrics, hMetrics); 273 if (w != null) 274 dic.put(PdfName.W2, new PdfLiteral(w)); 275 } 276 else 277 dic.put(PdfName.DW, new PdfNumber(1000)); 278 PdfDictionary cdic = new PdfDictionary(); 279 cdic.put(PdfName.REGISTRY, new PdfString((String)fontDesc.get("Registry"), null)); 280 cdic.put(PdfName.ORDERING, new PdfString((String)fontDesc.get("Ordering"), null)); 281 cdic.put(PdfName.SUPPLEMENT, new PdfLiteral((String)fontDesc.get("Supplement"))); 282 dic.put(PdfName.CIDSYSTEMINFO, cdic); 283 return dic; 284 } 285 286 private PdfDictionary getFontBaseType(PdfIndirectReference CIDFont) { 287 PdfDictionary dic = new PdfDictionary(PdfName.FONT); 288 dic.put(PdfName.SUBTYPE, PdfName.TYPE0); 289 String name = fontName; 290 if (style.length() > 0) 291 name += "-" + style.substring(1); 292 name += "-" + CMap; 293 dic.put(PdfName.BASEFONT, new PdfName(name)); 294 dic.put(PdfName.ENCODING, new PdfName(CMap)); 295 dic.put(PdfName.DESCENDANTFONTS, new PdfArray(CIDFont)); 296 return dic; 297 } 298 299 @Override 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 /** 320 * You can't get the FontStream of a CJK font (CJK fonts are never embedded), 321 * so this method always returns null. 322 * @return null 323 * @since 2.1.3 324 */ 325 @Override 326 public PdfStream getFullFontStream() { 327 return null; 328 } 329 330 private float getDescNumber(String name) { 331 return Integer.parseInt((String)fontDesc.get(name)); 332 } 333 334 private float getBBox(int idx) { 335 String s = (String)fontDesc.get("FontBBox"); 336 StringTokenizer tk = new StringTokenizer(s, " []\r\n\t\f"); 337 String ret = tk.nextToken(); 338 for (int k = 0; k < idx; ++k) 339 ret = tk.nextToken(); 340 return Integer.parseInt(ret); 341 } 342 343 /** Gets the font parameter identified by <CODE>key</CODE>. Valid values 344 * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE> 345 * and <CODE>ITALICANGLE</CODE>. 346 * @param key the parameter to be extracted 347 * @param fontSize the font size in points 348 * @return the parameter in points 349 */ 350 @Override 351 public float getFontDescriptor(int key, float fontSize) { 352 switch (key) { 353 case AWT_ASCENT: 354 case ASCENT: 355 return getDescNumber("Ascent") * fontSize / 1000; 356 case CAPHEIGHT: 357 return getDescNumber("CapHeight") * fontSize / 1000; 358 case AWT_DESCENT: 359 case DESCENT: 360 return getDescNumber("Descent") * fontSize / 1000; 361 case ITALICANGLE: 362 return getDescNumber("ItalicAngle"); 363 case BBOXLLX: 364 return fontSize * getBBox(0) / 1000; 365 case BBOXLLY: 366 return fontSize * getBBox(1) / 1000; 367 case BBOXURX: 368 return fontSize * getBBox(2) / 1000; 369 case BBOXURY: 370 return fontSize * getBBox(3) / 1000; 371 case AWT_LEADING: 372 return 0; 373 case AWT_MAXADVANCE: 374 return fontSize * (getBBox(2) - getBBox(0)) / 1000; 375 } 376 return 0; 377 } 378 379 @Override 380 public String getPostscriptFontName() { 381 return fontName; 382 } 383 384 /** Gets the full name of the font. If it is a True Type font 385 * each array element will have {Platform ID, Platform Encoding ID, 386 * Language ID, font name}. The interpretation of this values can be 387 * found in the Open Type specification, chapter 2, in the 'name' table.<br> 388 * For the other fonts the array has a single element with {"", "", "", 389 * font name}. 390 * @return the full name of the font 391 */ 392 @Override 393 public String[][] getFullFontName() { 394 return new String[][]{{"", "", "", fontName}}; 395 } 396 397 /** Gets all the entries of the names-table. If it is a True Type font 398 * each array element will have {Name ID, Platform ID, Platform Encoding ID, 399 * Language ID, font name}. The interpretation of this values can be 400 * found in the Open Type specification, chapter 2, in the 'name' table.<br> 401 * For the other fonts the array has a single element with {"4", "", "", "", 402 * font name}. 403 * @return the full name of the font 404 */ 405 @Override 406 public String[][] getAllNameEntries() { 407 return new String[][]{{"4", "", "", "", fontName}}; 408 } 409 410 /** Gets the family name of the font. If it is a True Type font 411 * each array element will have {Platform ID, Platform Encoding ID, 412 * Language ID, font name}. The interpretation of this values can be 413 * found in the Open Type specification, chapter 2, in the 'name' table.<br> 414 * For the other fonts the array has a single element with {"", "", "", 415 * font name}. 416 * @return the family name of the font 417 */ 418 @Override 419 public String[][] getFamilyFontName() { 420 return getFullFontName(); 421 } 422 423 static char[] readCMap(String name) { 424 try { 425 name = name + ".cmap"; 426 InputStream is = getResourceStream(RESOURCE_PATH + name); 427 char c[] = new char[0x10000]; 428 for (int k = 0; k < 0x10000; ++k) 429 c[k] = (char)((is.read() << 8) + is.read()); 430 is.close(); 431 return c; 432 } 433 catch (Exception e) { 434 // empty on purpose 435 } 436 return null; 437 } 438 439 static IntHashtable createMetric(String s) { 440 IntHashtable h = new IntHashtable(); 441 StringTokenizer tk = new StringTokenizer(s); 442 while (tk.hasMoreTokens()) { 443 int n1 = Integer.parseInt(tk.nextToken()); 444 h.put(n1, Integer.parseInt(tk.nextToken())); 445 } 446 return h; 447 } 448 449 static String convertToHCIDMetrics(int keys[], IntHashtable h) { 450 if (keys.length == 0) 451 return null; 452 int lastCid = 0; 453 int lastValue = 0; 454 int start; 455 for (start = 0; start < keys.length; ++start) { 456 lastCid = keys[start]; 457 lastValue = h.get(lastCid); 458 if (lastValue != 0) { 459 ++start; 460 break; 461 } 462 } 463 if (lastValue == 0) 464 return null; 465 StringBuffer buf = new StringBuffer(); 466 buf.append('['); 467 buf.append(lastCid); 468 int state = FIRST; 469 for (int k = start; k < keys.length; ++k) { 470 int cid = keys[k]; 471 int value = h.get(cid); 472 if (value == 0) 473 continue; 474 switch (state) { 475 case FIRST: { 476 if (cid == lastCid + 1 && value == lastValue) { 477 state = SERIAL; 478 } 479 else if (cid == lastCid + 1) { 480 state = BRACKET; 481 buf.append('[').append(lastValue); 482 } 483 else { 484 buf.append('[').append(lastValue).append(']').append(cid); 485 } 486 break; 487 } 488 case BRACKET: { 489 if (cid == lastCid + 1 && value == lastValue) { 490 state = SERIAL; 491 buf.append(']').append(lastCid); 492 } 493 else if (cid == lastCid + 1) { 494 buf.append(' ').append(lastValue); 495 } 496 else { 497 state = FIRST; 498 buf.append(' ').append(lastValue).append(']').append(cid); 499 } 500 break; 501 } 502 case SERIAL: { 503 if (cid != lastCid + 1 || value != lastValue) { 504 buf.append(' ').append(lastCid).append(' ').append(lastValue).append(' ').append(cid); 505 state = FIRST; 506 } 507 break; 508 } 509 } 510 lastValue = value; 511 lastCid = cid; 512 } 513 switch (state) { 514 case FIRST: { 515 buf.append('[').append(lastValue).append("]]"); 516 break; 517 } 518 case BRACKET: { 519 buf.append(' ').append(lastValue).append("]]"); 520 break; 521 } 522 case SERIAL: { 523 buf.append(' ').append(lastCid).append(' ').append(lastValue).append(']'); 524 break; 525 } 526 } 527 return buf.toString(); 528 } 529 530 static String convertToVCIDMetrics(int keys[], IntHashtable v, IntHashtable h) { 531 if (keys.length == 0) 532 return null; 533 int lastCid = 0; 534 int lastValue = 0; 535 int lastHValue = 0; 536 int start; 537 for (start = 0; start < keys.length; ++start) { 538 lastCid = keys[start]; 539 lastValue = v.get(lastCid); 540 if (lastValue != 0) { 541 ++start; 542 break; 543 } 544 else 545 lastHValue = h.get(lastCid); 546 } 547 if (lastValue == 0) 548 return null; 549 if (lastHValue == 0) 550 lastHValue = 1000; 551 StringBuffer buf = new StringBuffer(); 552 buf.append('['); 553 buf.append(lastCid); 554 int state = FIRST; 555 for (int k = start; k < keys.length; ++k) { 556 int cid = keys[k]; 557 int value = v.get(cid); 558 if (value == 0) 559 continue; 560 int hValue = h.get(lastCid); 561 if (hValue == 0) 562 hValue = 1000; 563 switch (state) { 564 case FIRST: { 565 if (cid == lastCid + 1 && value == lastValue && hValue == lastHValue) { 566 state = SERIAL; 567 } 568 else { 569 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid); 570 } 571 break; 572 } 573 case SERIAL: { 574 if (cid != lastCid + 1 || value != lastValue || hValue != lastHValue) { 575 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(' ').append(cid); 576 state = FIRST; 577 } 578 break; 579 } 580 } 581 lastValue = value; 582 lastCid = cid; 583 lastHValue = hValue; 584 } 585 buf.append(' ').append(lastCid).append(' ').append(-lastValue).append(' ').append(lastHValue / 2).append(' ').append(V1Y).append(" ]"); 586 return buf.toString(); 587 } 588 589 static HashMap<String, Object> 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<String, Object> map = new HashMap<String, Object>(); 601 for (Enumeration<Object> e = p.keys(); e.hasMoreElements();) { 602 Object obj = e.nextElement(); 603 map.put((String)obj, p.getProperty((String)obj)); 604 } 605 map.put("W", W); 606 map.put("W2", W2); 607 return map; 608 } 609 catch (Exception e) { 610 // empty on purpose 611 } 612 return null; 613 } 614 615 @Override 616 public int getUnicodeEquivalent(int c) { 617 if (cidDirect) 618 return translationMap[c]; 619 return c; 620 } 621 622 @Override 623 public int getCidCode(int c) { 624 if (cidDirect) 625 return c; 626 return translationMap[c]; 627 } 628 629 /** Checks if the font has any kerning pairs. 630 * @return always <CODE>false</CODE> 631 */ 632 @Override 633 public boolean hasKernPairs() { 634 return false; 635 } 636 637 /** 638 * Checks if a character exists in this font. 639 * @param c the character to check 640 * @return <CODE>true</CODE> if the character has a glyph, 641 * <CODE>false</CODE> otherwise 642 */ 643 @Override 644 public boolean charExists(int c) { 645 return translationMap[c] != 0; 646 } 647 648 /** 649 * Sets the character advance. 650 * @param c the character 651 * @param advance the character advance normalized to 1000 units 652 * @return <CODE>true</CODE> if the advance was set, 653 * <CODE>false</CODE> otherwise. Will always return <CODE>false</CODE> 654 */ 655 @Override 656 public boolean setCharAdvance(int c, int advance) { 657 return false; 658 } 659 660 /** 661 * Sets the font name that will appear in the pdf font dictionary. 662 * Use with care as it can easily make a font unreadable if not embedded. 663 * @param name the new font name 664 */ 665 @Override 666 public void setPostscriptFontName(String name) { 667 fontName = name; 668 } 669 670 @Override 671 public boolean setKerning(int char1, int char2, int kern) { 672 return false; 673 } 674 675 @Override 676 public int[] getCharBBox(int c) { 677 return null; 678 } 679 680 @Override 681 protected int[] getRawCharBBox(int c, String name) { 682 return null; 683 } 684}