1 18 19 package org.apache.batik.svggen.font; 20 21 import java.io.FileOutputStream ; 22 import java.io.PrintStream ; 23 24 import org.apache.batik.svggen.font.table.CmapFormat; 25 import org.apache.batik.svggen.font.table.Feature; 26 import org.apache.batik.svggen.font.table.FeatureTags; 27 import org.apache.batik.svggen.font.table.GsubTable; 28 import org.apache.batik.svggen.font.table.KernSubtable; 29 import org.apache.batik.svggen.font.table.KernTable; 30 import org.apache.batik.svggen.font.table.KerningPair; 31 import org.apache.batik.svggen.font.table.LangSys; 32 import org.apache.batik.svggen.font.table.PostTable; 33 import org.apache.batik.svggen.font.table.Script; 34 import org.apache.batik.svggen.font.table.ScriptTags; 35 import org.apache.batik.svggen.font.table.SingleSubst; 36 import org.apache.batik.svggen.font.table.Table; 37 import org.apache.batik.util.SVGConstants; 38 import org.apache.batik.util.XMLConstants; 39 40 46 public class SVGFont implements XMLConstants, SVGConstants, ScriptTags, FeatureTags { 47 static final String EOL; 48 49 static final String PROPERTY_LINE_SEPARATOR = "line.separator"; 50 static final String PROPERTY_LINE_SEPARATOR_DEFAULT = "\n"; 51 52 static final int DEFAULT_FIRST = 32; 53 static final int DEFAULT_LAST = 128; 54 55 static { 56 String temp; 57 try { 58 temp = System.getProperty (PROPERTY_LINE_SEPARATOR, 59 PROPERTY_LINE_SEPARATOR_DEFAULT); 60 } catch (SecurityException e) { 61 temp = PROPERTY_LINE_SEPARATOR_DEFAULT; 62 } 63 EOL = temp; 64 } 65 66 static private String QUOT_EOL = XML_CHAR_QUOT + EOL; 67 68 71 static private String CONFIG_USAGE = 72 "SVGFont.config.usage"; 73 74 79 static private String CONFIG_SVG_BEGIN = 80 "SVGFont.config.svg.begin"; 81 82 86 static private String CONFIG_SVG_TEST_CARD_START = 87 "SVGFont.config.svg.test.card.start"; 88 89 93 static private String CONFIG_SVG_TEST_CARD_END = 94 "SVGFont.config.svg.test.card.end"; 95 96 protected static String encodeEntities(String s) { 97 StringBuffer sb = new StringBuffer (); 98 for (int i = 0; i < s.length(); i++) { 99 if (s.charAt(i) == XML_CHAR_LT) { 100 sb.append(XML_ENTITY_LT); 101 } else if (s.charAt(i) == XML_CHAR_GT) { 102 sb.append(XML_ENTITY_GT); 103 } else if (s.charAt(i) == XML_CHAR_AMP) { 104 sb.append(XML_ENTITY_AMP); 105 } else if (s.charAt(i) == XML_CHAR_APOS) { 106 sb.append(XML_ENTITY_APOS); 107 } else if(s.charAt(i) == XML_CHAR_QUOT) { 108 sb.append(XML_ENTITY_QUOT); 109 } else { 110 sb.append(s.charAt(i)); 111 } 112 } 113 return sb.toString(); 114 } 115 116 protected static String getContourAsSVGPathData(Glyph glyph, int startIndex, int count) { 117 118 if (glyph.getPoint(startIndex).endOfContour) { 120 return ""; 121 } 122 123 StringBuffer sb = new StringBuffer (); 124 int offset = 0; 125 126 while (offset < count) { 127 Point point = glyph.getPoint(startIndex + offset%count); 128 Point point_plus1 = glyph.getPoint(startIndex + (offset+1)%count); 129 Point point_plus2 = glyph.getPoint(startIndex + (offset+2)%count); 130 131 if (offset == 0) { 132 sb.append(PATH_MOVE) 133 .append(String.valueOf(point.x)) 134 .append(XML_SPACE) 135 .append(String.valueOf(point.y)); 136 } 137 138 if (point.onCurve && point_plus1.onCurve) { 139 if (point_plus1.x == point.x) { sb.append(PATH_VERTICAL_LINE_TO) 141 .append(String.valueOf(point_plus1.y)); 142 } else if (point_plus1.y == point.y) { sb.append(PATH_HORIZONTAL_LINE_TO) 144 .append(String.valueOf(point_plus1.x)); 145 } else { 146 sb.append(PATH_LINE_TO) 147 .append(String.valueOf(point_plus1.x)) 148 .append(XML_SPACE) 149 .append(String.valueOf(point_plus1.y)); 150 } 151 offset++; 152 } else if (point.onCurve && !point_plus1.onCurve && point_plus2.onCurve) { 153 sb.append(PATH_QUAD_TO) 155 .append(String.valueOf(point_plus1.x)) 156 .append(XML_SPACE) 157 .append(String.valueOf(point_plus1.y)) 158 .append(XML_SPACE) 159 .append(String.valueOf(point_plus2.x)) 160 .append(XML_SPACE) 161 .append(String.valueOf(point_plus2.y)); 162 offset+=2; 163 } else if (point.onCurve && !point_plus1.onCurve && !point_plus2.onCurve) { 164 sb.append(PATH_QUAD_TO) 166 .append(String.valueOf(point_plus1.x)) 167 .append(XML_SPACE) 168 .append(String.valueOf(point_plus1.y)) 169 .append(XML_SPACE) 170 .append(String.valueOf(midValue(point_plus1.x, point_plus2.x))) 171 .append(XML_SPACE) 172 .append(String.valueOf(midValue(point_plus1.y, point_plus2.y))); 173 offset+=2; 174 } else if (!point.onCurve && !point_plus1.onCurve) { 175 sb.append(PATH_SMOOTH_QUAD_TO) 177 .append(String.valueOf(midValue(point.x, point_plus1.x))) 178 .append(XML_SPACE) 179 .append(String.valueOf(midValue(point.y, point_plus1.y))); 180 offset++; 181 } else if (!point.onCurve && point_plus1.onCurve) { 182 sb.append(PATH_SMOOTH_QUAD_TO) 183 .append(String.valueOf(point_plus1.x)) 184 .append(XML_SPACE) 185 .append(String.valueOf(point_plus1.y)); 186 offset++; 187 } else { 188 System.out.println("drawGlyph case not catered for!!"); 189 break; 190 } 191 } 192 sb.append(PATH_CLOSE); 193 194 return sb.toString(); 195 } 196 197 protected static String getSVGFontFaceElement(Font font) { 198 StringBuffer sb = new StringBuffer (); 199 String fontFamily = font.getNameTable().getRecord(Table.nameFontFamilyName); 200 short unitsPerEm = font.getHeadTable().getUnitsPerEm(); 201 String panose = font.getOS2Table().getPanose().toString(); 202 short ascent = font.getHheaTable().getAscender(); 203 short descent = font.getHheaTable().getDescender(); 204 int baseline = 0; 206 243 sb.append(XML_OPEN_TAG_START).append(SVG_FONT_FACE_TAG).append(EOL) 244 .append(XML_TAB).append(SVG_FONT_FAMILY_ATTRIBUTE).append(XML_EQUAL_QUOT).append(fontFamily).append(QUOT_EOL) 245 .append(XML_TAB).append(SVG_UNITS_PER_EM_ATTRIBUTE).append(XML_EQUAL_QUOT).append(unitsPerEm).append(QUOT_EOL) 247 .append(XML_TAB).append(SVG_PANOSE_1_ATTRIBUTE).append(XML_EQUAL_QUOT).append(panose).append(QUOT_EOL) 249 .append(XML_TAB).append(SVG_ASCENT_ATTRIBUTE).append(XML_EQUAL_QUOT).append(ascent).append(QUOT_EOL) 251 .append(XML_TAB).append(SVG_DESCENT_ATTRIBUTE).append(XML_EQUAL_QUOT).append(descent).append(QUOT_EOL) 253 .append(XML_TAB).append(SVG_ALPHABETIC_ATTRIBUTE).append(XML_EQUAL_QUOT).append(baseline).append(XML_CHAR_QUOT) 255 .append(XML_OPEN_TAG_END_NO_CHILDREN).append(EOL); 256 258 return sb.toString(); 259 } 260 261 271 protected static void writeFontAsSVGFragment(PrintStream ps, Font font, String id, int first, int last, boolean autoRange, boolean forceAscii) 272 throws Exception { 273 int horiz_advance_x = font.getOS2Table().getAvgCharWidth(); 277 278 ps.print(XML_OPEN_TAG_START); 279 ps.print(SVG_FONT_TAG); 280 ps.print(XML_SPACE); 281 if (id != null) { 283 ps.print(SVG_ID_ATTRIBUTE); 284 ps.print(XML_EQUAL_QUOT); 285 ps.print(id); 287 ps.print(XML_CHAR_QUOT); 288 ps.print(XML_SPACE); 289 } 291 292 ps.print(SVG_HORIZ_ADV_X_ATTRIBUTE); 293 ps.print(XML_EQUAL_QUOT); 294 ps.print(horiz_advance_x); 296 ps.print(XML_CHAR_QUOT); 297 ps.print(XML_OPEN_TAG_END_CHILDREN); 298 300 ps.print(getSVGFontFaceElement(font)); 301 302 CmapFormat cmapFmt = null; 304 if (forceAscii) { 305 cmapFmt = font.getCmapTable().getCmapFormat( 307 Table.platformMacintosh, 308 Table.encodingRoman ); 309 } else { 310 cmapFmt = font.getCmapTable().getCmapFormat( 312 Table.platformMicrosoft, 313 Table.encodingUGL ); 314 if (cmapFmt == null) { 315 cmapFmt = font.getCmapTable().getCmapFormat( 317 Table.platformMicrosoft, 318 Table.encodingUndefined ); 319 } 320 } 321 if (cmapFmt == null) { 322 throw new Exception ("Cannot find a suitable cmap table"); 323 } 324 325 GsubTable gsub = (GsubTable) font.getTable(Table.GSUB); 329 SingleSubst initialSubst = null; 330 SingleSubst medialSubst = null; 331 SingleSubst terminalSubst = null; 332 if (gsub != null) { 333 Script s = gsub.getScriptList().findScript(SCRIPT_TAG_ARAB); 334 if (s != null) { 335 LangSys ls = s.getDefaultLangSys(); 336 if (ls != null) { 337 Feature init = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_INIT); 338 Feature medi = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_MEDI); 339 Feature fina = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_FINA); 340 341 initialSubst = (SingleSubst) 342 gsub.getLookupList().getLookup(init, 0).getSubtable(0); 343 medialSubst = (SingleSubst) 344 gsub.getLookupList().getLookup(medi, 0).getSubtable(0); 345 terminalSubst = (SingleSubst) 346 gsub.getLookupList().getLookup(fina, 0).getSubtable(0); 347 } 348 } 349 } 350 351 ps.println(getGlyphAsSVG(font, font.getGlyph(0), 0, horiz_advance_x, 353 initialSubst, medialSubst, terminalSubst, "")); 354 355 try { 356 if (first == -1) { 357 if (!autoRange) first = DEFAULT_FIRST; 358 else first = cmapFmt.getFirst(); 359 } 360 if (last == -1) { 361 if (!autoRange) last = DEFAULT_LAST; 362 else last = cmapFmt.getLast(); 363 } 364 365 for (int i = first; i <= last; i++) { 367 int glyphIndex = cmapFmt.mapCharCode(i); 368 372 if (glyphIndex > 0) { 373 ps.println(getGlyphAsSVG( 374 font, 375 font.getGlyph(glyphIndex), 376 glyphIndex, 377 horiz_advance_x, 378 initialSubst, medialSubst, terminalSubst, 379 (32 <= i && i <= 127) ? 380 encodeEntities("" + (char) i) : 381 XML_CHAR_REF_PREFIX + Integer.toHexString(i) + XML_CHAR_REF_SUFFIX)); 382 } 383 384 } 385 386 KernTable kern = (KernTable) font.getTable(Table.kern); 388 if (kern != null) { 389 KernSubtable kst = kern.getSubtable(0); 390 PostTable post = (PostTable) font.getTable(Table.post); 391 for (int i = 0; i < kst.getKerningPairCount(); i++) { 392 ps.println(getKerningPairAsSVG(kst.getKerningPair(i), post)); 393 } 394 } 395 } catch (Exception e) { 396 System.err.println(e.getMessage()); 397 } 398 399 ps.print(XML_CLOSE_TAG_START); 400 ps.print(SVG_FONT_TAG); 401 ps.println(XML_CLOSE_TAG_END); 402 } 404 405 protected static String getGlyphAsSVG( 406 Font font, 407 Glyph glyph, 408 int glyphIndex, 409 int defaultHorizAdvanceX, 410 String attrib, 411 String code) { 412 413 StringBuffer sb = new StringBuffer (); 414 int firstIndex = 0; 415 int count = 0; 416 int i; 417 int horiz_advance_x; 418 419 horiz_advance_x = font.getHmtxTable().getAdvanceWidth(glyphIndex); 420 421 if (glyphIndex == 0) { 422 sb.append(XML_OPEN_TAG_START); 423 sb.append(SVG_MISSING_GLYPH_TAG); 424 } else { 426 427 sb.append(XML_OPEN_TAG_START) 429 .append(SVG_GLYPH_TAG).append(XML_SPACE).append(SVG_UNICODE_ATTRIBUTE) 430 .append(XML_EQUAL_QUOT).append(code).append(XML_CHAR_QUOT); 431 433 sb.append(XML_SPACE).append(SVG_GLYPH_NAME_ATTRIBUTE).append(XML_EQUAL_QUOT) 435 .append(font.getPostTable().getGlyphName(glyphIndex)) 437 .append(XML_CHAR_QUOT); 439 } 440 if (horiz_advance_x != defaultHorizAdvanceX) { 441 sb.append(XML_SPACE).append(SVG_HORIZ_ADV_X_ATTRIBUTE).append(XML_EQUAL_QUOT) 442 .append(horiz_advance_x).append(XML_CHAR_QUOT); 443 } 445 446 if (attrib != null) { 447 sb.append(attrib); 448 } 449 450 if (glyph != null) { 451 sb.append(XML_SPACE).append(SVG_D_ATTRIBUTE).append(XML_EQUAL_QUOT); 453 for (i = 0; i < glyph.getPointCount(); i++) { 454 count++; 455 if (glyph.getPoint(i).endOfContour) { 456 sb.append(getContourAsSVGPathData(glyph, firstIndex, count)); 457 firstIndex = i + 1; 458 count = 0; 459 } 460 } 461 sb.append(XML_CHAR_QUOT); 463 } 464 465 sb.append(XML_OPEN_TAG_END_NO_CHILDREN); 466 468 chopUpStringBuffer(sb); 470 471 return sb.toString(); 472 } 473 474 protected static String getGlyphAsSVG( 475 Font font, 476 Glyph glyph, 477 int glyphIndex, 478 int defaultHorizAdvanceX, 479 SingleSubst arabInitSubst, 480 SingleSubst arabMediSubst, 481 SingleSubst arabTermSubst, 482 String code) { 483 484 StringBuffer sb = new StringBuffer (); 485 boolean substituted = false; 486 487 int arabInitGlyphIndex = glyphIndex; 489 int arabMediGlyphIndex = glyphIndex; 490 int arabTermGlyphIndex = glyphIndex; 491 if (arabInitSubst != null) { 492 arabInitGlyphIndex = arabInitSubst.substitute(glyphIndex); 493 } 494 if (arabMediSubst != null) { 495 arabMediGlyphIndex = arabMediSubst.substitute(glyphIndex); 496 } 497 if (arabTermSubst != null) { 498 arabTermGlyphIndex = arabTermSubst.substitute(glyphIndex); 499 } 500 501 if (arabInitGlyphIndex != glyphIndex) { 502 sb.append(getGlyphAsSVG( 503 font, 504 font.getGlyph(arabInitGlyphIndex), 505 arabInitGlyphIndex, 506 defaultHorizAdvanceX, 507 (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + 509 SVG_INITIAL_VALUE + XML_CHAR_QUOT), 510 code)); 511 sb.append(EOL); 513 substituted = true; 514 } 515 516 if (arabMediGlyphIndex != glyphIndex) { 517 sb.append(getGlyphAsSVG( 518 font, 519 font.getGlyph(arabMediGlyphIndex), 520 arabMediGlyphIndex, 521 defaultHorizAdvanceX, 522 (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + 524 SVG_MEDIAL_VALUE + XML_CHAR_QUOT), 525 code)); 526 sb.append(EOL); 528 substituted = true; 529 } 530 531 if (arabTermGlyphIndex != glyphIndex) { 532 sb.append(getGlyphAsSVG( 533 font, 534 font.getGlyph(arabTermGlyphIndex), 535 arabTermGlyphIndex, 536 defaultHorizAdvanceX, 537 (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + 539 SVG_TERMINAL_VALUE + XML_CHAR_QUOT), 540 code)); 541 sb.append(EOL); 543 substituted = true; 544 } 545 546 if (substituted) { 547 sb.append(getGlyphAsSVG( 548 font, 549 glyph, 550 glyphIndex, 551 defaultHorizAdvanceX, 552 (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + 554 SVG_ISOLATED_VALUE + XML_CHAR_QUOT), 555 code)); 556 } else { 557 sb.append(getGlyphAsSVG( 558 font, 559 glyph, 560 glyphIndex, 561 defaultHorizAdvanceX, 562 null, 563 code)); 564 } 565 566 return sb.toString(); 567 } 568 569 protected static String getKerningPairAsSVG(KerningPair kp, PostTable post) { 570 StringBuffer sb = new StringBuffer (); 571 sb.append(XML_OPEN_TAG_START).append(SVG_HKERN_TAG).append(XML_SPACE); 573 sb.append(SVG_G1_ATTRIBUTE).append(XML_EQUAL_QUOT); 574 575 sb.append(post.getGlyphName(kp.getLeft())); 576 sb.append(XML_CHAR_QUOT).append(XML_SPACE).append(SVG_G2_ATTRIBUTE).append(XML_EQUAL_QUOT); 578 579 sb.append(post.getGlyphName(kp.getRight())); 580 sb.append(XML_CHAR_QUOT).append(XML_SPACE).append(SVG_K_ATTRIBUTE).append(XML_EQUAL_QUOT); 582 583 sb.append(-kp.getValue()); 585 sb.append(XML_CHAR_QUOT).append(XML_OPEN_TAG_END_NO_CHILDREN); 587 588 return sb.toString(); 589 } 590 621 protected static void writeSvgBegin(PrintStream ps) { 622 ps.println(Messages.formatMessage(CONFIG_SVG_BEGIN, 627 new Object []{SVG_PUBLIC_ID, SVG_SYSTEM_ID})); 628 629 } 630 631 protected static void writeSvgDefsBegin(PrintStream ps) { 632 ps.println(XML_OPEN_TAG_START + SVG_DEFS_TAG + XML_OPEN_TAG_END_CHILDREN); 634 } 635 636 protected static void writeSvgDefsEnd(PrintStream ps) { 637 ps.println(XML_CLOSE_TAG_START + SVG_DEFS_TAG + XML_CLOSE_TAG_END); 639 } 640 641 protected static void writeSvgEnd(PrintStream ps) { 642 ps.println(XML_CLOSE_TAG_START + SVG_SVG_TAG + XML_CLOSE_TAG_END); 644 } 645 646 protected static void writeSvgTestCard(PrintStream ps, String fontFamily) { 647 ps.println(Messages.formatMessage(CONFIG_SVG_TEST_CARD_START, null)); 648 ps.println(fontFamily); 649 ps.println(Messages.formatMessage(CONFIG_SVG_TEST_CARD_END, null)); 650 651 660 } 661 662 public static final char ARG_KEY_START_CHAR = '-'; 663 public static final String ARG_KEY_CHAR_RANGE_LOW = "-l"; 664 public static final String ARG_KEY_CHAR_RANGE_HIGH = "-h"; 665 public static final String ARG_KEY_ID = "-id"; 666 public static final String ARG_KEY_ASCII = "-ascii"; 667 public static final String ARG_KEY_TESTCARD = "-testcard"; 668 public static final String ARG_KEY_AUTO_RANGE = "-autorange"; 669 public static final String ARG_KEY_OUTPUT_PATH = "-o"; 670 671 675 public static void main(String [] args) { 676 try { 677 String path = parseArgs(args, null); 678 String low = parseArgs(args, ARG_KEY_CHAR_RANGE_LOW); 679 String high = parseArgs(args, ARG_KEY_CHAR_RANGE_HIGH); 680 String id = parseArgs(args, ARG_KEY_ID); 681 String ascii = parseArgs(args, ARG_KEY_ASCII); 682 String testCard = parseArgs(args, ARG_KEY_TESTCARD); 683 String outPath = parseArgs(args, ARG_KEY_OUTPUT_PATH); 684 String autoRange = parseArgs(args, ARG_KEY_AUTO_RANGE); 685 PrintStream ps = null; 686 FileOutputStream fos = null; 687 688 if (outPath != null) { 690 fos = new FileOutputStream (outPath); 692 ps = new PrintStream (fos); 693 } else { 694 ps = System.out; 696 } 697 698 if (path != null) { 700 Font font = Font.create(path); 701 702 writeSvgBegin(ps); 704 writeSvgDefsBegin(ps); 705 writeFontAsSVGFragment( 706 ps, 707 font, 708 id, 709 (low != null ? Integer.parseInt(low) : -1), 710 (high != null ? Integer.parseInt(high) : -1), 711 (autoRange != null), 712 (ascii != null)); 713 writeSvgDefsEnd(ps); 714 if (testCard != null) { 715 String fontFamily = font.getNameTable().getRecord(Table.nameFontFamilyName); 716 writeSvgTestCard(ps, fontFamily); 717 } 718 writeSvgEnd(ps); 719 720 if (fos != null) { 722 fos.close(); 723 } 724 } else { 725 usage(); 726 } 727 } catch (Exception e) { 728 e.printStackTrace(); 729 System.err.println(e.getMessage()); 730 usage(); 731 } 732 } 733 734 private static void chopUpStringBuffer(StringBuffer sb) { 735 if (sb.length() < 256) { 736 return; 737 } else { 738 for (int i = 240; i < sb.length(); i++) { 741 if (sb.charAt(i) == ' ') { 742 sb.setCharAt(i, '\n'); 743 i += 240; 744 } 745 } 746 } 747 } 748 749 private static int midValue(int a, int b) { 750 return a + (b - a)/2; 751 } 752 753 764 765 private static String parseArgs(String [] args, String name) { 766 for (int i = 0; i < args.length; i++) { 767 if (name == null) { 768 if (args[i].charAt(0) != ARG_KEY_START_CHAR) { 769 return args[i]; 770 } 771 } else if (name.equalsIgnoreCase(args[i])) { 772 if ((i < args.length - 1) && (args[i+1].charAt(0) != ARG_KEY_START_CHAR)) { 773 return args[i+1]; 774 } else { 775 return args[i]; 776 } 777 } 778 } 779 return null; 780 } 781 782 private static void usage() { 783 System.err.println(Messages.formatMessage(CONFIG_USAGE, null)); 784 } 785 } 786 | Popular Tags |