漢字的書寫效果的實現


 

本文記錄一下如何基於字體實現漢字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://**.**.**.**'

 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM