风暴烈酒辰

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

本文记录一下如何基于字体实现汉字svg描点的过程

 

参考项目:

1makemeahanzi     https://github.com/skishore/makemeahanzi

2hanzi-writer-data    https://github.com/chanind/hanzi-writer-data

3hanzi-writer    https://github.com/chanind/hanzi-writer

 

生成dictionary.txt和graphics.txt文件

生成这两个文件我们需要用到makemeahanzi

 

运行Makemeahanzi-tools

 

首先下载makemeahanzi项目的tools分支

 

该项目是用meteor框架写的,那么我们想要运行该项目,需要安装Node环境和Meteor

 

安装Node

直接上官网怼吧 传送门:https://nodejs.org/en/download/,安装完成后记得配置一下环境变量,cmd输入node -v 显示版本号即为安装成功。

 

安装Meteor

 

说到安装Meteor,那么Meteor是什么?以下内容来自 https://www.w3cschool.cn/discovermeteor/rbj81jjm.html

Meteor 是什么?

Meteor 是一个构建在 Node.js 之上的平台,用来开发实时网页程序。Meteor 位于程序数据库和用户界面之间,保持二者之间的数据同步更新。

因为 Meteor 是基于 Node.js 开发的,所以在客户端和服务器端都使用 JavaScript 作为开发语言。而且,Meteor 程序的代码还能在前后两端共用。

Meteor 这个平台很强大,网页程序开发过程中的很多复杂、容易出错的功能都能抽象出来,实现起来很简单。

为什么使用 Meteor?

那么,你为什么要花时间学习 Meteor,而不去学其他框架呢?拨开 Meteor 的各种功能,我们认为原因只有一个:因为 Meteor 易于学习。

而且,和其他框架不同,使用 Meteor,几小时之内就能开发出一个正常运行的实时网页程序。如果之前做过前端开发,对 JavaScript 已经有所了解,甚至都不用再学习一门新的编程语言。

Meteor 可能就是你要找的理想框架,当然,也可能不是。既然只要几晚或一个周末就能上手,为什么不试试呢?

因为丫自带一个MongoDB,可以前端后端一把梭,再也不用和后端攻城狮因为接口文档撕逼了。 

 

 

安装环境参考Meteor官网 传送门:https://www.meteor.com/install,

linux下使用命令

curl https://install.meteor.com/ | sh

 

windows环境下使用命令

choco install meteor

 

PSWTF???choco是什么鬼?

choco其实是Chocolatey的命令,Chocolateywindows的包管理工具,类似yamapt-get

如果没有安装Chocolatey,那么需要先安装Chocolatey。传送门:https://chocolatey.org/install

依照文档有两种安装方式,cmdpowershell

 

cmd安装:

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

 

powershell安装:

Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

 

PS:如果安装失败,可能是网络问题,需要梯子

 

安装成功后再次执行choco install meteor,等待安装完成即可

至于Meteor如何使用,可以阅读官方文档,这里不细说了,因为我也得看官方文档。传送门:https://guide.meteor.com/index.html

 

运行项目

Node和Meteor安装完成后,我们开始运行项目

项目目录:

 

 

 

运行项目之前先把public文件夹拷贝到.meteor文件夹里面,VSCODE打开项目,输入meteor,等待项目跑起来,然后浏览器输入 http://localhost:3000# 进入到server文件夹下输入meteor mongo可以打开mongo,mongo地址一般是localhost:3001端口

 

 

 

 

如何使用

你可能在使用的过程中会发现和我截图的有点不一样,比如只有AR PL KaitiM GB  AR PL UKai 这两个字体,是因为我在原有代码上进行了一些修改,后面我会讲到如何修改代码使之兼容任意字体。

 

#后面输入汉字然后回车,例如http://localhost:3000/#猫

 

 

 

这时候点击一下字体,你可以生成对应字体的文字,下面是我点了FZKTJW的效果

 

 

 

那么现在是时候讲讲如何真正的使用了,他的使用方式是------->快捷键,哈哈哈哈。

快捷键a:显示上一个字

快捷键A:显示上一个未完成的字

快捷键q:显示上一个完成的字

快捷键d:显示下一个字

快捷键D:显示下一个未完成的字

快捷键e:显示下一个完成的字

快捷键r:重置当前操作

快捷键s:下一步操作

快捷键w:上一步操作

完成一个字需要六步操作,分别为:path, bridges, strokes, analysis, order, verified

path:路径

 

 

 

bridges:标记描点

 

 

strokes:笔画

通过更改描点可以控制笔画

 

 

 

analysis:分析

分析字的结构,偏旁部首等待,如果结构没有完成,下一步会先去完成结构

 

 

对于“猫”这个字需要我们先完成“犭”和“苗”的构建,按s进入下一步

 

 

 

我们完成了“犭”的部分,接下来按D回到未完成的“猫”继续下一步

 

 

按s进入下一步完成苗的构建

 

 

此处跳过N部,总之构建完所有依赖的结构后按D回到未完成的“猫”继续搞

 

 

 

按s进入下一步

order:顺序,笔顺

这里可以调整笔画的顺序(直接拖动)和笔顺Reverse是调整方向

 

 

 

verified:检查并且完成

 

 

 

到此我们完成了对于猫这个字的所有构建,已经可以导出路径文件了,那么如何导出路径文件呢?

导出路径文件和生成svg文件

导出路径文件

打开F12,切换到Console,然后输入

Meteor.call("backup");

进行一下备份,然后输入

Meteor.call('export');就可以在项目文件夹里面的.meteor文件夹下生成dictionary.txt和graphics.txt文件,稍后再讲这俩文件怎么用

 

生成svg文件

F12,Console输入

Meteor.call('exportSVGs');

在项目文件夹.meteor文件夹下会生成.svg文件夹,里面就是字体的svg文件

PS:如果生成的svg文件是空的,那么需要修改一下代码,将lib/animation.html 文件复制到private文件夹下并且覆盖原文件

源码分析

上面讲了一大坨,这里讲一下具体的实现原理

首先是项目结构:

 

 

 

 

.meteor:运行时的根目录

.vscode:vscode的配置文件

client:客户端文件

dist:项目构建后的目录

lib:公共库文件

packages:npm包

private:私有目录

public:公开目录,里面存放字体和汉字语言库,这个文件夹需要拷贝到.meteor里面

server:服务器文件

 

项目里面的源码文件有很多,下面说几个用到的

1、client/editor.js对应的是编辑页面的逻辑,快捷键,操作步骤定义都在这个文件里。

2client/lib/path.js对应的是生成路径的逻辑,这里可以自己添加字体。添加字体的方法如下:

 

Template.path_stage.helpers({

  alternative: () => Session.get('stages.path.alternative') || '?',

  options: () => [{font: 'arphic/gkai00mp.ttf', label: 'AR PL KaitiM GB'},

                  {font: 'arphic/UKaiCN.ttf', label: 'AR PL UKai'},

                  {font: 'arphic/simhei.ttf', label: 'simhei'},

                  {font: 'arphic/FZKTJW.TTF', label: 'FZKTJW'},

                  {font: 'arphic/FZLanTingYuanS-B-GB-TEST.ttf', label: 'FZLanTingYuanS-B-GB-TEST'},],

});

 

在options里面添加好字体后,将字体文件拷贝到.meteor/public/arphic/文件夹下即可

 

修改path.js使之兼容各种字体

本项目中用到的字体大小是1024的,如果一般字体没有这么大,比如256的,那么生成出来的字会很小,不能沾满整个画布,那么坐标也是无法使用的。我们可以修改path.js中的

 

// We avoid arrow functions in this map so that this is bound to the template.

Template.path_stage.events({

  'blur .value': function(event) {

    const text = $(event.target).text();

    const value = text.length === 1 && text !== '?' ? text : undefined;

    if (value === stage.alternative) {

      $(event.target).text(value || '?');

    } else {

      stage.alternative = value;

      stage.forceRefresh();

    }

  },

  'click .option': function(event) {

    const label = this.label;

    const character = stage.character;

    assert(character.length === 1);

    Session.set('modal.text', `Loading ${label}...`);

    Session.set('modal.value', 0);

    opentype.load(this.font, (error, font) => {

      stage.alternative = undefined;

      if (error) {

        stage.onGetPath(`Error loading ${label}: ${error}`);

        return;

      }

      Session.set('modal.text', `Extracting ${character} from ${label}...`);

      Session.set('modal.value', 0.5);

      const index = font.charToGlyphIndex(character);

      const glyph = font.glyphs.get(index);

      if (glyph.unicode !== character.codePointAt(0)) {

        stage.onGetPath(`${character} is not present in ${label}.`);

        return;

      }

      const commands = font.getPath(character,0,0,1024).commands;

      // TODO(skishore): We may want a try/catch around this call.

      // const path = svg.convertCommandsToPath(glyph.path.commands);

      const path = svg.convertCommandsToPath(commands);

      stage.onGetPath(undefined, path);

    });

  },

});

红色部分即为修改的部分,这时候字体大小就会变成1024的大小,那么接下来会发现字体是反的,这时候需要修改/client/lib/external/opentype/0.4.10/opentype.js

 

Glyph.prototype.getPath = function(x, y, fontSize) {

    x = x !== undefined ? x : 0;

    y = y !== undefined ? y : 0;

    fontSize = fontSize !== undefined ? fontSize : 72;

    var scale = 1 / this.path.unitsPerEm * fontSize;

    var p = new path.Path();

    var commands = this.path.commands;

    for (var i = 0; i < commands.length; i += 1) {

        var cmd = commands[i];

        if (cmd.type === 'M') {

            p.moveTo(x + (cmd.x * scale), y + (cmd.y * scale));

        } else if (cmd.type === 'L') {

            p.lineTo(x + (cmd.x * scale), y + (cmd.y * scale));

        } else if (cmd.type === 'Q') {

            p.quadraticCurveTo(x + (cmd.x1 * scale), y + (cmd.y1 * scale),

                               x + (cmd.x * scale), y + (cmd.y * scale));

        } else if (cmd.type === 'C') {

            p.curveTo(x + (cmd.x1 * scale), y + (cmd.y1 * scale),

                      x + (cmd.x2 * scale), y + (cmd.y2 * scale),

                      x + (cmd.x * scale), y + (cmd.y * scale));

        } else if (cmd.type === 'Z') {

            p.closePath();

        }

    }

 

    return p;

};

 

修改红色部分。这时候文字就会显示正常。

OpenType.js

这里简单介绍一下opentype.js 传送门:https://github.com/opentypejs/opentype.js

opentype.js是TrueType和OpenType字体的JavaScript解析器和编写器。

它可以让我们从浏览器或Node.js 访问文本的字体,具体使用方式直接看github就可以了,上面的说明还是比较清楚的。

 

使用dictionary.txt和graphics.txt文件

如何使用这两个文件,我们需要用到hanzi-writer-data hanzi-writer

运行hanzi-writer-data

下载hanzi-writer-data,解压后的目录结构:

 

 

 

打开stroke_data_parse.py文件可以看到

dictionary_file = os.path.join(root, 'vendor/makemeahanzi/dictionary.txt')

graphics_file = os.path.join(root, 'vendor/makemeahanzi/graphics.txt')

 

他是使用dictionary.txt和graphics.txt生成单个字独立的.json文件,以供hanzi-writer使用

我们将刚才生成的dictionary.txt和graphics.txt拷贝到vendor/makemeahanzi/目录下

然后打开cmd输入python stroke_data_parse.py 会看到data文件夹下有生成好的json文件

 

 

 

 

打开文件的时候需要设置utf8编码,参考修改:

 

with open(dictionary_file,'r',encoding='UTF-8') as f:

  lines = f.readlines()

  for line in lines:

    decoded_line = json.loads(line)

    dict_data[decoded_line['character']] = decoded_line

 

with open(graphics_file,'r',encoding='UTF-8') as f:

  lines = f.readlines()

  for line in lines:

    decoded_line = json.loads(line)

    char = decoded_line.pop('character')

    graphics_data[char] = decoded_line

 

 

# write out data

 

for char in graphics_data:

  radical = get_radical_strokes(char)

  if radical:

    graphics_data[char]['radStrokes'] = radical

 

for char, data in graphics_data.items():

  out_file = os.path.join(output_dir, f'{char}.json')

  with open(out_file, 'w',encoding="UTF-8") as f:

    f.write(json.dumps(data, ensure_ascii=False))

 

with open(os.path.join(output_dir, 'all.json'), 'w',encoding="UTF-8") as f:

  f.write(json.dumps(graphics_data, ensure_ascii=False))

 

PS:等等 没有python 命令怎么办? 简单啊,装Python,记得装3x以上的版本

Python安装传送门:https://www.python.org/getit/

 

hanzi-writer

我们来看看怎么利用上面生成的json文件吧

将刚才生成的json文件拷贝到hanzi-writer项目的demo文件夹,将里面的地址替换成刚才生成的all.json数据文件,然后打开后查找刚才我们自己构建的“猫”

 

嗯,到这里就全部完成了,开心的撸吧

 

部署

meteor build dist --architecture=os.linux.x86_64

tar xvf meteor-build-test.tar.gz

export MONGO_URL='mongodb://**.**.**.**:27017/makeahanzi'

export PORT=3000

export ROOT_URL='http://**.**.**.**'

 
 

 

posted on 2019-09-25 10:00  风暴烈酒辰  阅读(3818)  评论(7编辑  收藏  举报