使用batik在kotlin中将TTF字体转换为SVG图像 您所在的位置:网站首页 华为ttf字体怎么安装 使用batik在kotlin中将TTF字体转换为SVG图像

使用batik在kotlin中将TTF字体转换为SVG图像

#使用batik在kotlin中将TTF字体转换为SVG图像| 来源: 网络整理| 查看: 265

在各类页面开发中可能都会遇到一种需求,用户需要在某个地方展示一种特殊字体,如果是英文尚且还好,但如果是中文字体的话,这个字体很有可能有20MB~50MB的大小,只为了那一行字,就要导入这样大一个字体包,总会觉得有些不妥,应该怎么解决这样的问题呢?

第一感就是直接将文字提前做成图片,不引入字体,无论是项目的打包大小,还是运行内存上要稍好一些。但如果这样,就又会引出一个问题,如果字体如果要参与动画效果,直接用生成好的图片去做这种动画,效果多多少少会比矢量字体直接参与差一些,运算的消耗也要稍高一点,而且如果要求很清晰的图片,又是要加载一张大图才行。

遇到这样的问题,我便考虑到做成SVG,它也是矢量格式的图片,无论放到多大都一样清晰,做各种各样的动画效果都非常便捷,体积也非常小,而且在这样的文本文件上进行一些细小的改动也很方便,能更好的参与版本控制。

但又产生一个问题,将字体生成为点位图倒还容易,但要生成为SVG就不太简便了。网络上的在线工具,往往是限制大小的,中文字体文件都太大了;要么用专业软件,但安装很大一个软件而且操作麻烦不好用;要么是收费的,往往生成出来的效果也往往需要再处理一下。而且即便是生成好了,还要反复寻找自己需要的文字再拼接起来,这无疑让很多人放弃。其实自己做这样一个工具也非常简单,我们使用语法糖丰富的kotlin来实现这样一个有趣的工具,当然做好了这样一个工具,也不单纯是为了这一需求,我们开发人员不需要安装字体制作软件便可以观赏字体的细节了,也是十分有趣的。

引入

涉及字体与SVG,我选择的是Apache Batik这个库,非常的轻便,虽然是老项目,但至今还在维护,虽然是Java库,但kotlin也是能无缝使用的,用kotlin来实现这个工具遇到需要重写的部分也能将代码变得更加清晰可读,下面先引入这个包:

dependencies { implementation("org.apache.xmlgraphics:batik-svggen:1.16") } 复制代码

其实这个库,说起来是能将TTF转成SVG,但默认的一套模式也不尽人意,官方文档介绍的是命令行的方式,但这种事情不用代码进行调用是很难达到预期的效果的,命令行再多的参数,也很难完全满足需要,但巧的就是这个库就是不考虑软件的调用,只能用命令行,用命令行调用大致就是如下的方式:

我事先准备了一个字体包ShouShuti.ttf,拷到项目目录里,这个字体文件其实有什么用什么就行了。

使用

将字体文名代入参数中,只需要一行代码即可:

fun main() { SVGFont.main(arrayOf("ShouShuti.ttf", "-l", "19968", "-h", "40869", "-o", "output.svg", "-testcard")) } 复制代码

如官方文档所说,-l是字体字符的起始位置,-h是字体字符的结束位置,字体文件遵从Unicode的编码,基本汉字的范围在4E00~9FA5,也就是19968~40869之间,所以我设定这个范围,执行这段代码,就能看到生成好了一个output.svg文件,内容如下:

... ... ... 复制代码

可以看到这是将字体全都转为glyph预定好,然后下面text再去使用。这其实就是很多年前已经废除的SVG_fonts方案,该功能已从Chrome 38(和 Opera 25)中删除了,所以这种效果其实完全是不能接受的。我们想要的是一个直接画出文字的SVG,例如一些path标签的叠加组成的文字,这可以实现吗,其实查看生成的svg文件就能发现是可以的。

解读源代码

查看最前面的一节标签,在defs>font>glyph这一层的部分,是完整的绘出了字体的形状的,19968~40869之间的汉字都在其中,其中每一个glyph都是由d来绘画出其unicode属性对应的单个文字的,d属性的部分和path标签的d属性是一样的用法,只要将这些glyph定义改成path,放到最外面一层就是可以的。那么如何改造这个方法呢?直接调用肯定是不行了,毕竟它只允许给出Unicode范围,那么只能先观察下SVGFont.main()是怎么写的(去除了无用的代码注释):

入口部分 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(); } } 复制代码

前面参数设定的部分可以跳过,往后面看就能发现实际上发挥作用的就是writeFontAsSVGFragment(),这个方法写出了字体数据,而在他前后的方法都只是写出一些必要的定义标签,能否直接去控制这个方法来生成呢?显然是不行的,因为访问级别是protected的,不过这个方法相对独立,所以抽出来我们自己重新写一遍就好,不过这个方法内容相当多,如下所示(去除了无用的代码注释):

写出字体数据标签 public static void writeFontAsSVGFragment(PrintStream ps, Font font, String id, int first, int last, boolean autoRange, boolean forceAscii) throws Exception { int horiz_advance_x = font.getOS2Table().getAvgCharWidth(); ps.print(XML_OPEN_TAG_START); ps.print(SVG_FONT_TAG); ps.print(XML_SPACE); if (id != null) { ps.print(SVG_ID_ATTRIBUTE); ps.print(XML_EQUAL_QUOT); ps.print(id); ps.print(XML_CHAR_QUOT); ps.print(XML_SPACE); } ps.print(SVG_HORIZ_ADV_X_ATTRIBUTE); ps.print(XML_EQUAL_QUOT); ps.print(horiz_advance_x); ps.print(XML_CHAR_QUOT); ps.print(XML_OPEN_TAG_END_CHILDREN); 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); if (init != null) { initialSubst = (SingleSubst) gsub.getLookupList().getLookup(init, 0).getSubtable(0); } if (medi != null) { medialSubst = (SingleSubst) gsub.getLookupList().getLookup(medi, 0).getSubtable(0); } if (fina != null) { 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 Set glyphSet = new HashSet(); for (int i = first; i 0) { // add glyph ID to set so we can filter later glyphSet.add(glyphIndex); ps.println(getGlyphAsSVG( font, font.getGlyph(glyphIndex), glyphIndex, horiz_advance_x, initialSubst, medialSubst, terminalSubst, (32


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有