| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.batik.svggen.font; |
| |
| import java.io.FileOutputStream; |
| import java.io.PrintStream; |
| |
| import org.apache.batik.svggen.font.table.CmapFormat; |
| import org.apache.batik.svggen.font.table.Feature; |
| import org.apache.batik.svggen.font.table.FeatureTags; |
| import org.apache.batik.svggen.font.table.GsubTable; |
| import org.apache.batik.svggen.font.table.KernSubtable; |
| import org.apache.batik.svggen.font.table.KernTable; |
| import org.apache.batik.svggen.font.table.KerningPair; |
| import org.apache.batik.svggen.font.table.LangSys; |
| import org.apache.batik.svggen.font.table.PostTable; |
| import org.apache.batik.svggen.font.table.Script; |
| import org.apache.batik.svggen.font.table.ScriptTags; |
| import org.apache.batik.svggen.font.table.SingleSubst; |
| import org.apache.batik.svggen.font.table.Table; |
| import org.apache.batik.util.SVGConstants; |
| import org.apache.batik.util.XMLConstants; |
| |
| /** |
| * Converts a TrueType font to an SVG embedded font. |
| * |
| * @version $Id$ |
| * @author <a href="mailto:david@steadystate.co.uk">David Schweinsberg</a> |
| */ |
| public class SVGFont implements XMLConstants, SVGConstants, ScriptTags, FeatureTags { |
| static final String EOL; |
| |
| static final String PROPERTY_LINE_SEPARATOR = "line.separator"; |
| static final String PROPERTY_LINE_SEPARATOR_DEFAULT = "\n"; |
| |
| static final int DEFAULT_FIRST = 32; |
| static final int DEFAULT_LAST = 128; |
| |
| static { |
| String temp; |
| try { |
| temp = System.getProperty (PROPERTY_LINE_SEPARATOR, |
| PROPERTY_LINE_SEPARATOR_DEFAULT); |
| } catch (SecurityException e) { |
| temp = PROPERTY_LINE_SEPARATOR_DEFAULT; |
| } |
| EOL = temp; |
| } |
| |
| private static String QUOT_EOL = XML_CHAR_QUOT + EOL; |
| |
| /** |
| * Defines the application arguments. |
| */ |
| private static String CONFIG_USAGE = |
| "SVGFont.config.usage"; |
| |
| /** |
| * Defines the start of the generated SVG document |
| * {0} SVG public ID |
| * {1} SVG system ID |
| */ |
| private static String CONFIG_SVG_BEGIN = |
| "SVGFont.config.svg.begin"; |
| |
| /** |
| * Defines the SVG start fragment that exercise the generated |
| * Font. |
| */ |
| private static String CONFIG_SVG_TEST_CARD_START = |
| "SVGFont.config.svg.test.card.start"; |
| |
| /** |
| * Defines the end of the SVG fragment that exercise the generated |
| * Font. |
| */ |
| private static String CONFIG_SVG_TEST_CARD_END = |
| "SVGFont.config.svg.test.card.end"; |
| |
| protected static String encodeEntities(String s) { |
| StringBuffer sb = new StringBuffer(); |
| for (int i = 0; i < s.length(); i++) { |
| if (s.charAt(i) == XML_CHAR_LT) { |
| sb.append(XML_ENTITY_LT); |
| } else if (s.charAt(i) == XML_CHAR_GT) { |
| sb.append(XML_ENTITY_GT); |
| } else if (s.charAt(i) == XML_CHAR_AMP) { |
| sb.append(XML_ENTITY_AMP); |
| } else if (s.charAt(i) == XML_CHAR_APOS) { |
| sb.append(XML_ENTITY_APOS); |
| } else if(s.charAt(i) == XML_CHAR_QUOT) { |
| sb.append(XML_ENTITY_QUOT); |
| } else { |
| sb.append(s.charAt(i)); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| protected static String getContourAsSVGPathData(Glyph glyph, int startIndex, int count) { |
| |
| // If this is a single point on it's own, we can't do anything with it |
| if (glyph.getPoint(startIndex).endOfContour) { |
| return ""; |
| } |
| |
| StringBuffer sb = new StringBuffer(); |
| int offset = 0; |
| |
| while (offset < count) { |
| Point point = glyph.getPoint(startIndex + offset%count); |
| Point point_plus1 = glyph.getPoint(startIndex + (offset+1)%count); |
| Point point_plus2 = glyph.getPoint(startIndex + (offset+2)%count); |
| |
| if (offset == 0) { |
| sb.append(PATH_MOVE) |
| .append(String.valueOf(point.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point.y)); |
| } |
| |
| if (point.onCurve && point_plus1.onCurve) { |
| if (point_plus1.x == point.x) { // This is a vertical line |
| sb.append(PATH_VERTICAL_LINE_TO) |
| .append(String.valueOf(point_plus1.y)); |
| } else if (point_plus1.y == point.y) { // This is a horizontal line |
| sb.append(PATH_HORIZONTAL_LINE_TO) |
| .append(String.valueOf(point_plus1.x)); |
| } else { |
| sb.append(PATH_LINE_TO) |
| .append(String.valueOf(point_plus1.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus1.y)); |
| } |
| offset++; |
| } else if (point.onCurve && !point_plus1.onCurve && point_plus2.onCurve) { |
| // This is a curve with no implied points |
| sb.append(PATH_QUAD_TO) |
| .append(String.valueOf(point_plus1.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus1.y)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus2.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus2.y)); |
| offset+=2; |
| } else if (point.onCurve && !point_plus1.onCurve && !point_plus2.onCurve) { |
| // This is a curve with one implied point |
| sb.append(PATH_QUAD_TO) |
| .append(String.valueOf(point_plus1.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus1.y)) |
| .append(XML_SPACE) |
| .append(String.valueOf(midValue(point_plus1.x, point_plus2.x))) |
| .append(XML_SPACE) |
| .append(String.valueOf(midValue(point_plus1.y, point_plus2.y))); |
| offset+=2; |
| } else if (!point.onCurve && !point_plus1.onCurve) { |
| // This is a curve with two implied points |
| sb.append(PATH_SMOOTH_QUAD_TO) |
| .append(String.valueOf(midValue(point.x, point_plus1.x))) |
| .append(XML_SPACE) |
| .append(String.valueOf(midValue(point.y, point_plus1.y))); |
| offset++; |
| } else if (!point.onCurve && point_plus1.onCurve) { |
| sb.append(PATH_SMOOTH_QUAD_TO) |
| .append(String.valueOf(point_plus1.x)) |
| .append(XML_SPACE) |
| .append(String.valueOf(point_plus1.y)); |
| offset++; |
| } else { |
| System.out.println("drawGlyph case not catered for!!"); |
| break; |
| } |
| } |
| sb.append(PATH_CLOSE); |
| |
| return sb.toString(); |
| } |
| |
| protected static String getSVGFontFaceElement(Font font) { |
| StringBuffer sb = new StringBuffer(); |
| String fontFamily = font.getNameTable().getRecord(Table.nameFontFamilyName); |
| short unitsPerEm = font.getHeadTable().getUnitsPerEm(); |
| String panose = font.getOS2Table().getPanose().toString(); |
| short ascent = font.getHheaTable().getAscender(); |
| short descent = font.getHheaTable().getDescender(); |
| int baseline = 0; // bit 0 of head.flags will indicate if this is true |
| |
| // <!ELEMENT font-face (%descTitleMetadata;,font-face-src?,definition-src?) > |
| // <!ATTLIST font-face |
| // %stdAttrs; |
| // font-family CDATA #IMPLIED |
| // font-style CDATA #IMPLIED |
| // font-variant CDATA #IMPLIED |
| // font-weight CDATA #IMPLIED |
| // font-stretch CDATA #IMPLIED |
| // font-size CDATA #IMPLIED |
| // unicode-range CDATA #IMPLIED |
| // units-per-em %Number; #IMPLIED |
| // panose-1 CDATA #IMPLIED |
| // stemv %Number; #IMPLIED |
| // stemh %Number; #IMPLIED |
| // slope %Number; #IMPLIED |
| // cap-height %Number; #IMPLIED |
| // x-height %Number; #IMPLIED |
| // accent-height %Number; #IMPLIED |
| // ascent %Number; #IMPLIED |
| // descent %Number; #IMPLIED |
| // widths CDATA #IMPLIED |
| // bbox CDATA #IMPLIED |
| // ideographic %Number; #IMPLIED |
| // alphabetic %Number; #IMPLIED |
| // mathematical %Number; #IMPLIED |
| // hanging %Number; #IMPLIED |
| // v-ideographic %Number; #IMPLIED |
| // v-alphabetic %Number; #IMPLIED |
| // v-mathematical %Number; #IMPLIED |
| // v-hanging %Number; #IMPLIED |
| // underline-position %Number; #IMPLIED |
| // underline-thickness %Number; #IMPLIED |
| // strikethrough-position %Number; #IMPLIED |
| // strikethrough-thickness %Number; #IMPLIED |
| // overline-position %Number; #IMPLIED |
| // overline-thickness %Number; #IMPLIED > |
| |
| sb.append(XML_OPEN_TAG_START).append(SVG_FONT_FACE_TAG).append(EOL) |
| .append(XML_TAB).append(SVG_FONT_FAMILY_ATTRIBUTE).append(XML_EQUAL_QUOT).append(fontFamily).append(QUOT_EOL) |
| // .append(" font-family=\"").append(fontFamily).append("\"\r\n") |
| .append(XML_TAB).append(SVG_UNITS_PER_EM_ATTRIBUTE).append(XML_EQUAL_QUOT).append(unitsPerEm).append(QUOT_EOL) |
| //.append(" units-per-em=\"").append(unitsPerEm).append("\"\r\n") |
| .append(XML_TAB).append(SVG_PANOSE_1_ATTRIBUTE).append(XML_EQUAL_QUOT).append(panose).append(QUOT_EOL) |
| // .append(" panose-1=\"").append(panose).append("\"\r\n") |
| .append(XML_TAB).append(SVG_ASCENT_ATTRIBUTE).append(XML_EQUAL_QUOT).append(ascent).append(QUOT_EOL) |
| // .append(" ascent=\"").append(ascent).append("\"\r\n") |
| .append(XML_TAB).append(SVG_DESCENT_ATTRIBUTE).append(XML_EQUAL_QUOT).append(descent).append(QUOT_EOL) |
| // .append(" descent=\"").append(descent).append("\"\r\n") |
| .append(XML_TAB).append(SVG_ALPHABETIC_ATTRIBUTE).append(XML_EQUAL_QUOT).append(baseline).append(XML_CHAR_QUOT) |
| .append(XML_OPEN_TAG_END_NO_CHILDREN).append(EOL); |
| //.append(" baseline=\"").append(baseline).append("\"/>\r\n"); |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns a <font>...</font> block, |
| * defining the specified font. |
| * |
| * @param font The TrueType font to be converted to SVG |
| * @param id An XML id attribute for the font element |
| * @param first The first character in the output range |
| * @param last The last character in the output range |
| * @param forceAscii Force the use of the ASCII character map |
| */ |
| protected static void writeFontAsSVGFragment(PrintStream ps, Font font, String id, int first, int last, boolean autoRange, boolean forceAscii) |
| throws Exception { |
| // StringBuffer sb = new StringBuffer(); |
| // int horiz_advance_x = font.getHmtxTable().getAdvanceWidth( |
| // font.getHheaTable().getNumberOfHMetrics() - 1); |
| int horiz_advance_x = font.getOS2Table().getAvgCharWidth(); |
| |
| ps.print(XML_OPEN_TAG_START); |
| ps.print(SVG_FONT_TAG); |
| ps.print(XML_SPACE); |
| // ps.print("<font "); |
| if (id != null) { |
| ps.print(SVG_ID_ATTRIBUTE); |
| ps.print(XML_EQUAL_QUOT); |
| // ps.print("id=\""); |
| ps.print(id); |
| ps.print(XML_CHAR_QUOT); |
| ps.print(XML_SPACE); |
| // ps.print("\" "); |
| } |
| |
| ps.print(SVG_HORIZ_ADV_X_ATTRIBUTE); |
| ps.print(XML_EQUAL_QUOT); |
| // ps.print("horiz-adv-x=\""); |
| ps.print(horiz_advance_x); |
| ps.print(XML_CHAR_QUOT); |
| ps.print(XML_OPEN_TAG_END_CHILDREN); |
| // ps.println("\">"); |
| |
| ps.print(getSVGFontFaceElement(font)); |
| |
| // Decide upon a cmap table to use for our character to glyph look-up |
| CmapFormat cmapFmt = null; |
| if (forceAscii) { |
| // We've been asked to use the ASCII/Macintosh cmap format |
| cmapFmt = font.getCmapTable().getCmapFormat( |
| Table.platformMacintosh, |
| Table.encodingRoman ); |
| } else { |
| // The default behaviour is to use the Unicode cmap encoding |
| cmapFmt = font.getCmapTable().getCmapFormat( |
| Table.platformMicrosoft, |
| Table.encodingUGL ); |
| if (cmapFmt == null) { |
| // This might be a symbol font, so we'll look for an "undefined" encoding |
| cmapFmt = font.getCmapTable().getCmapFormat( |
| Table.platformMicrosoft, |
| Table.encodingUndefined ); |
| } |
| } |
| if (cmapFmt == null) { |
| throw new Exception("Cannot find a suitable cmap table"); |
| } |
| |
| // If this font includes arabic script, we want to specify |
| // substitutions for initial, medial, terminal & isolated |
| // cases. |
| GsubTable gsub = (GsubTable) font.getTable(Table.GSUB); |
| SingleSubst initialSubst = null; |
| SingleSubst medialSubst = null; |
| SingleSubst terminalSubst = null; |
| if (gsub != null) { |
| Script s = gsub.getScriptList().findScript(SCRIPT_TAG_ARAB); |
| if (s != null) { |
| LangSys ls = s.getDefaultLangSys(); |
| if (ls != null) { |
| Feature init = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_INIT); |
| Feature medi = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_MEDI); |
| Feature fina = gsub.getFeatureList().findFeature(ls, FEATURE_TAG_FINA); |
| |
| initialSubst = (SingleSubst) |
| gsub.getLookupList().getLookup(init, 0).getSubtable(0); |
| medialSubst = (SingleSubst) |
| gsub.getLookupList().getLookup(medi, 0).getSubtable(0); |
| terminalSubst = (SingleSubst) |
| gsub.getLookupList().getLookup(fina, 0).getSubtable(0); |
| } |
| } |
| } |
| |
| // Include the missing glyph |
| ps.println(getGlyphAsSVG(font, font.getGlyph(0), 0, horiz_advance_x, |
| initialSubst, medialSubst, terminalSubst, "")); |
| |
| try { |
| if (first == -1) { |
| if (!autoRange) first = DEFAULT_FIRST; |
| else first = cmapFmt.getFirst(); |
| } |
| if (last == -1) { |
| if (!autoRange) last = DEFAULT_LAST; |
| else last = cmapFmt.getLast(); |
| } |
| |
| // Include our requested range |
| for (int i = first; i <= last; i++) { |
| int glyphIndex = cmapFmt.mapCharCode(i); |
| // ps.println(String.valueOf(i) + " -> " + String.valueOf(glyphIndex)); |
| // if (font.getGlyphs()[glyphIndex] != null) |
| // sb.append(font.getGlyphs()[glyphIndex].toString() + "\n"); |
| |
| if (glyphIndex > 0) { |
| ps.println(getGlyphAsSVG( |
| font, |
| font.getGlyph(glyphIndex), |
| glyphIndex, |
| horiz_advance_x, |
| initialSubst, medialSubst, terminalSubst, |
| (32 <= i && i <= 127) ? |
| encodeEntities( String.valueOf( (char)i ) ) : |
| XML_CHAR_REF_PREFIX + Integer.toHexString(i) + XML_CHAR_REF_SUFFIX)); |
| } |
| |
| } |
| |
| // Output kerning pairs from the requested range |
| KernTable kern = (KernTable) font.getTable(Table.kern); |
| if (kern != null) { |
| KernSubtable kst = kern.getSubtable(0); |
| PostTable post = (PostTable) font.getTable(Table.post); |
| for (int i = 0; i < kst.getKerningPairCount(); i++) { |
| ps.println(getKerningPairAsSVG(kst.getKerningPair(i), post)); |
| } |
| } |
| } catch (Exception e) { |
| System.err.println(e.getMessage()); |
| } |
| |
| ps.print(XML_CLOSE_TAG_START); |
| ps.print(SVG_FONT_TAG); |
| ps.println(XML_CLOSE_TAG_END); |
| // ps.println("</font>"); |
| } |
| |
| protected static String getGlyphAsSVG( |
| Font font, |
| Glyph glyph, |
| int glyphIndex, |
| int defaultHorizAdvanceX, |
| String attrib, |
| String code) { |
| |
| StringBuffer sb = new StringBuffer(); |
| int firstIndex = 0; |
| int count = 0; |
| int i; |
| int horiz_advance_x; |
| |
| horiz_advance_x = font.getHmtxTable().getAdvanceWidth(glyphIndex); |
| |
| if (glyphIndex == 0) { |
| sb.append(XML_OPEN_TAG_START); |
| sb.append(SVG_MISSING_GLYPH_TAG); |
| // sb.append("<missing-glyph"); |
| } else { |
| |
| // Unicode value |
| sb.append(XML_OPEN_TAG_START) |
| .append(SVG_GLYPH_TAG).append(XML_SPACE).append(SVG_UNICODE_ATTRIBUTE) |
| .append(XML_EQUAL_QUOT).append(code).append(XML_CHAR_QUOT); |
| // sb.append("<glyph unicode=\"").append(code).append("\""); |
| |
| // Glyph name |
| sb.append(XML_SPACE).append(SVG_GLYPH_NAME_ATTRIBUTE).append(XML_EQUAL_QUOT) |
| // sb.append(" glyph-name=\"") |
| .append(font.getPostTable().getGlyphName(glyphIndex)) |
| // .append("\""); |
| .append(XML_CHAR_QUOT); |
| } |
| if (horiz_advance_x != defaultHorizAdvanceX) { |
| sb.append(XML_SPACE).append(SVG_HORIZ_ADV_X_ATTRIBUTE).append(XML_EQUAL_QUOT) |
| .append(horiz_advance_x).append(XML_CHAR_QUOT); |
| // sb.append(" horiz-adv-x=\"").append(horiz_advance_x).append("\""); |
| } |
| |
| if (attrib != null) { |
| sb.append(attrib); |
| } |
| |
| if (glyph != null) { |
| // sb.append(" d=\""); |
| sb.append(XML_SPACE).append(SVG_D_ATTRIBUTE).append(XML_EQUAL_QUOT); |
| for (i = 0; i < glyph.getPointCount(); i++) { |
| count++; |
| if (glyph.getPoint(i).endOfContour) { |
| sb.append(getContourAsSVGPathData(glyph, firstIndex, count)); |
| firstIndex = i + 1; |
| count = 0; |
| } |
| } |
| // sb.append("\""); |
| sb.append(XML_CHAR_QUOT); |
| } |
| |
| sb.append(XML_OPEN_TAG_END_NO_CHILDREN); |
| // sb.append("/>"); |
| |
| // Chop-up the string into 255 character lines |
| chopUpStringBuffer(sb); |
| |
| return sb.toString(); |
| } |
| |
| protected static String getGlyphAsSVG( |
| Font font, |
| Glyph glyph, |
| int glyphIndex, |
| int defaultHorizAdvanceX, |
| SingleSubst arabInitSubst, |
| SingleSubst arabMediSubst, |
| SingleSubst arabTermSubst, |
| String code) { |
| |
| StringBuffer sb = new StringBuffer(); |
| boolean substituted = false; |
| |
| // arabic = "initial | medial | terminal | isolated" |
| int arabInitGlyphIndex = glyphIndex; |
| int arabMediGlyphIndex = glyphIndex; |
| int arabTermGlyphIndex = glyphIndex; |
| if (arabInitSubst != null) { |
| arabInitGlyphIndex = arabInitSubst.substitute(glyphIndex); |
| } |
| if (arabMediSubst != null) { |
| arabMediGlyphIndex = arabMediSubst.substitute(glyphIndex); |
| } |
| if (arabTermSubst != null) { |
| arabTermGlyphIndex = arabTermSubst.substitute(glyphIndex); |
| } |
| |
| if (arabInitGlyphIndex != glyphIndex) { |
| sb.append(getGlyphAsSVG( |
| font, |
| font.getGlyph(arabInitGlyphIndex), |
| arabInitGlyphIndex, |
| defaultHorizAdvanceX, |
| // " arabic-form=\"initial\"", |
| (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + |
| SVG_INITIAL_VALUE + XML_CHAR_QUOT), |
| code)); |
| // sb.append("\r\n"); |
| sb.append(EOL); |
| substituted = true; |
| } |
| |
| if (arabMediGlyphIndex != glyphIndex) { |
| sb.append(getGlyphAsSVG( |
| font, |
| font.getGlyph(arabMediGlyphIndex), |
| arabMediGlyphIndex, |
| defaultHorizAdvanceX, |
| // " arabic-form=\"medial\"", |
| (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + |
| SVG_MEDIAL_VALUE + XML_CHAR_QUOT), |
| code)); |
| // sb.append("\r\n"); |
| sb.append(EOL); |
| substituted = true; |
| } |
| |
| if (arabTermGlyphIndex != glyphIndex) { |
| sb.append(getGlyphAsSVG( |
| font, |
| font.getGlyph(arabTermGlyphIndex), |
| arabTermGlyphIndex, |
| defaultHorizAdvanceX, |
| // " arabic-form=\"terminal\"", |
| (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + |
| SVG_TERMINAL_VALUE + XML_CHAR_QUOT), |
| code)); |
| // sb.append("\r\n"); |
| sb.append(EOL); |
| substituted = true; |
| } |
| |
| if (substituted) { |
| sb.append(getGlyphAsSVG( |
| font, |
| glyph, |
| glyphIndex, |
| defaultHorizAdvanceX, |
| // " arabic-form=\"isolated\"", |
| (XML_SPACE + SVG_ARABIC_FORM_ATTRIBUTE + XML_EQUAL_QUOT + |
| SVG_ISOLATED_VALUE + XML_CHAR_QUOT), |
| code)); |
| } else { |
| sb.append(getGlyphAsSVG( |
| font, |
| glyph, |
| glyphIndex, |
| defaultHorizAdvanceX, |
| null, |
| code)); |
| } |
| |
| return sb.toString(); |
| } |
| |
| protected static String getKerningPairAsSVG(KerningPair kp, PostTable post) { |
| StringBuffer sb = new StringBuffer(); |
| // sb.append("<hkern g1=\""); |
| sb.append(XML_OPEN_TAG_START).append(SVG_HKERN_TAG).append(XML_SPACE); |
| sb.append(SVG_G1_ATTRIBUTE).append(XML_EQUAL_QUOT); |
| |
| sb.append(post.getGlyphName(kp.getLeft())); |
| // sb.append("\" g2=\""); |
| sb.append(XML_CHAR_QUOT).append(XML_SPACE).append(SVG_G2_ATTRIBUTE).append(XML_EQUAL_QUOT); |
| |
| sb.append(post.getGlyphName(kp.getRight())); |
| // sb.append("\" k=\""); |
| sb.append(XML_CHAR_QUOT).append(XML_SPACE).append(SVG_K_ATTRIBUTE).append(XML_EQUAL_QUOT); |
| |
| // SVG kerning values are inverted from TrueType's. |
| sb.append(-kp.getValue()); |
| // sb.append("\"/>"); |
| sb.append(XML_CHAR_QUOT).append(XML_OPEN_TAG_END_NO_CHILDREN); |
| |
| return sb.toString(); |
| } |
| /* |
| protected static String getGlyphAsPath(Glyph glyph) { |
| StringBuffer sb = new StringBuffer(); |
| int firstIndex = 0; |
| int count = 0; |
| int i; |
| |
| for (i = 0; i < glyph.getPointCount(); i++) { |
| count++; |
| if (glyph.getPoint(i).endOfContour) { |
| sb.append(getContourAsSVGPathData(glyph, firstIndex, count)); |
| firstIndex = i + 1; |
| count = 0; |
| } |
| } |
| return sb.toString(); |
| } |
| |
| protected static void writeTextAsSVGFragment(PrintStream ps, Font f, int size, String text) { |
| CmapFormat cmapFmt = f.getCmapTable().getCmapFormat(Table.platformMicrosoft, Table.encodingUGL); |
| int x = 0; |
| for (short i = 0; i < text.length(); i++) { |
| int glyphIndex = cmapFmt.mapCharCode((short)text.charAt(i)); |
| Glyph glyph = f.getGlyph(glyphIndex); |
| if (glyph != null) { |
| ps.println(translateSVG(x, 0, getGlyphAsSVGPath(glyph))); |
| x += glyph.getAdvanceWidth(); |
| } |
| } |
| } |
| */ |
| protected static void writeSvgBegin(PrintStream ps) { |
| // ps.println("<?xml version=\"1.0\" standalone=\"no\"?>"); |
| // ps.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20001102//EN\""); |
| // ps.println("\"http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd\" >"); |
| // ps.println("<svg width=\"100%\" height=\"100%\">"); |
| ps.println(Messages.formatMessage(CONFIG_SVG_BEGIN, |
| new Object[]{SVG_PUBLIC_ID, SVG_SYSTEM_ID})); |
| |
| } |
| |
| protected static void writeSvgDefsBegin(PrintStream ps) { |
| // ps.println("<defs>"); |
| ps.println(XML_OPEN_TAG_START + SVG_DEFS_TAG + XML_OPEN_TAG_END_CHILDREN); |
| } |
| |
| protected static void writeSvgDefsEnd(PrintStream ps) { |
| // ps.println("</defs>"); |
| ps.println(XML_CLOSE_TAG_START + SVG_DEFS_TAG + XML_CLOSE_TAG_END); |
| } |
| |
| protected static void writeSvgEnd(PrintStream ps) { |
| // ps.println("</svg>"); |
| ps.println(XML_CLOSE_TAG_START + SVG_SVG_TAG + XML_CLOSE_TAG_END); |
| } |
| |
| protected static void writeSvgTestCard(PrintStream ps, String fontFamily) { |
| ps.println(Messages.formatMessage(CONFIG_SVG_TEST_CARD_START, null)); |
| ps.println(fontFamily); |
| ps.println(Messages.formatMessage(CONFIG_SVG_TEST_CARD_END, null)); |
| |
| /*ps.println("<g style=\"font-family: '" + fontFamily + "'; font-size:18;fill:black\">"); |
| ps.println("<text x=\"20\" y=\"60\"> !"#$%&'()*+,-./0123456789:;<>?</text>"); |
| ps.println("<text x=\"20\" y=\"120\">@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_</text>"); |
| ps.println("<text x=\"20\" y=\"180\">`abcdefghijklmnopqrstuvwxyz{|}~</text>"); |
| ps.println("<text x=\"20\" y=\"240\">€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ</text>"); |
| ps.println("<text x=\"20\" y=\"300\"> ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿</text>"); |
| ps.println("<text x=\"20\" y=\"360\">ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß</text>"); |
| ps.println("<text x=\"20\" y=\"420\">àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ</text>"); |
| ps.println("</g>");*/ |
| } |
| |
| public static final char ARG_KEY_START_CHAR = '-'; |
| public static final String ARG_KEY_CHAR_RANGE_LOW = "-l"; |
| public static final String ARG_KEY_CHAR_RANGE_HIGH = "-h"; |
| public static final String ARG_KEY_ID = "-id"; |
| public static final String ARG_KEY_ASCII = "-ascii"; |
| public static final String ARG_KEY_TESTCARD = "-testcard"; |
| public static final String ARG_KEY_AUTO_RANGE = "-autorange"; |
| public static final String ARG_KEY_OUTPUT_PATH = "-o"; |
| |
| /** |
| * Starts the application. |
| * @param args an array of command-line arguments |
| */ |
| public static void main(String[] args) { |
| try { |
| String path = parseArgs(args, null); |
| String low = parseArgs(args, ARG_KEY_CHAR_RANGE_LOW); |
| String high = parseArgs(args, ARG_KEY_CHAR_RANGE_HIGH); |
| String id = parseArgs(args, ARG_KEY_ID); |
| String ascii = parseArgs(args, ARG_KEY_ASCII); |
| String testCard = parseArgs(args, ARG_KEY_TESTCARD); |
| String outPath = parseArgs(args, ARG_KEY_OUTPUT_PATH); |
| String autoRange = parseArgs(args, ARG_KEY_AUTO_RANGE); |
| PrintStream ps = null; |
| FileOutputStream fos = null; |
| |
| // What are we outputting to? |
| if (outPath != null) { |
| // If an output path was specified, write to a file |
| fos = new FileOutputStream(outPath); |
| ps = new PrintStream(fos); |
| } else { |
| // Otherwise we'll just put it to stdout |
| ps = System.out; |
| } |
| |
| // The font path is the only required argument |
| if (path != null) { |
| Font font = Font.create(path); |
| |
| // Write the various parts of the SVG file |
| writeSvgBegin(ps); |
| writeSvgDefsBegin(ps); |
| writeFontAsSVGFragment( |
| ps, |
| font, |
| id, |
| (low != null ? Integer.parseInt(low) : -1), |
| (high != null ? Integer.parseInt(high) : -1), |
| (autoRange != null), |
| (ascii != null)); |
| writeSvgDefsEnd(ps); |
| if (testCard != null) { |
| String fontFamily = font.getNameTable().getRecord(Table.nameFontFamilyName); |
| writeSvgTestCard(ps, fontFamily); |
| } |
| writeSvgEnd(ps); |
| |
| // Close the output stream (if we have one) |
| if (fos != null) { |
| fos.close(); |
| } |
| } else { |
| usage(); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| System.err.println(e.getMessage()); |
| usage(); |
| } |
| } |
| |
| private static void chopUpStringBuffer(StringBuffer sb) { |
| if (sb.length() < 256) { |
| return; |
| } else { |
| // Being rather simplistic about it, for now we'll insert a newline after |
| // 240 chars |
| for (int i = 240; i < sb.length(); i++) { |
| if (sb.charAt(i) == ' ') { |
| sb.setCharAt(i, '\n'); |
| i += 240; |
| } |
| } |
| } |
| } |
| |
| private static int midValue(int a, int b) { |
| return a + (b - a)/2; |
| } |
| |
| /*private static String translateSVG(int x, int y, String svgText) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("<g transform=\"translate(") |
| .append(String.valueOf(x)) |
| .append(" ") |
| .append(String.valueOf(y)) |
| .append(")\">") |
| .append(svgText) |
| .append("</g>"); |
| return sb.toString(); |
| }*/ |
| |
| private static String parseArgs(String[] args, String name) { |
| for (int i = 0; i < args.length; i++) { |
| if (name == null) { |
| if (args[i].charAt(0) != ARG_KEY_START_CHAR) { |
| return args[i]; |
| } |
| } else if (name.equalsIgnoreCase(args[i])) { |
| if ((i < args.length - 1) && (args[i+1].charAt(0) != ARG_KEY_START_CHAR)) { |
| return args[i+1]; |
| } else { |
| return args[i]; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static void usage() { |
| System.err.println(Messages.formatMessage(CONFIG_USAGE, null)); |
| } |
| } |