読者です 読者をやめる 読者になる 読者になる

しろもじメモランダム

文字についてあれこれと。

アウトラインのSVGからフォントを生成 #かな書いてみる

書体

IVSやら何やら他の話題に飛びついていたので間が空いてしまったが、明朝かな書体制作のつづき。前回はアウトラインを作ったので、今回はここからフォントを生成したい。

SVGの分割

まず、これがアウトラインのファイル。Inkscape で作成し、SVGとして保存した。アウトラインは outline という名前のレイヤーの中に作っている。仮想ボディのサイズは100pxとした。

これをグリフごとに分割し、1文字1ファイルにする。今回は(も?)Perl で簡単なスクリプトを書き、これを利用した。単純なものなので、path 要素以外の要素(グループも含む)に対応していないなどいろいろと制限はある。また XML::Simple を利用しているので、実行するには XML::Simple のインストールが必要。

#!/usr/bin/perl
#
# SVGをグリフごとに分割
# usage: perl split_svg.pl svg_sheet.svg list.txt

use strict;
use warnings;
use autodie;
use utf8;
use 5.010;
binmode STDOUT, ":utf8";

use constant OUT_DIR => "glyphs";
use constant PIXELS_PER_EM =>  100;
use constant UNITS_PER_EM  => 1000;

use XML::Simple;

my ($svg_file, $list_file) = @ARGV;

# SVG 読み込み
my $svg = XMLin($svg_file, forcearray => 1, keyattr => []);

# 行数・列数を求める
my $row_max = int ($svg->{height} / PIXELS_PER_EM) - 1;
my $col_max = int ($svg->{width} / PIXELS_PER_EM) - 1;

# outline レイヤーを探す
my $groups = $svg->{g};
my $group_outline;
foreach my $group (@$groups) {
  if ($group->{'inkscape:label'} eq "outline") {
    $group_outline = $group;
    last;
  }
}
die "outlineレイヤーが存在しません" unless defined $group_outline;

# 変換行列を求める
my $transform = $group_outline->{transform};
my ($a, $b, $c, $d, $e, $f) = (1, 0, 0, 1, 0, 0);
if (!defined $transform) {
  # noop
} elsif ($transform =~ m/translate\((?<tx>-?\d+(.\d+)?(e-?\d+)?),(?<ty>-?\d+(.\d+)?(e-?\d+)?)\)/) {
  $e = $+{tx};
  $f = $+{ty};
} elsif ($transform =~ m/matrix\((?<a>-?\d+(.\d+)?(e-?\d+)?),(?<b>-?\d+(.\d+)?(e-?\d+)?)\),(?<c>-?\d+(.\d+)?(e-?\d+)?),(?<d>-?\d+(.\d+)?(e-?\d+)?),(?<e>-?\d+(.\d+)?(e-?\d+)?),(?<f>-?\d+(.\d+)?(e-?\d+)?)/) {
  $a = $+{a};  $c = $+{c};  $e = $+{e};
  $b = $+{b};  $d = $+{d};  $f = $+{f};
} else {
  die "未対応のtransformです: $transform"
}

# グリフごとに path を格納する変数
my @glyphs = ();
foreach my $row (0 .. $row_max) {
  foreach my $col (0 .. $col_max) {
    $glyphs[$row][$col] = [];
  }
}

# @glyphs にパスを格納
my $paths = $group_outline->{path};
foreach my $path (@$paths) {
  my @data_args = split /\s+/, $path->{d};
  my $new_data = "";
  my ($col, $row);
  my $is_moveto_point = 1;
  foreach my $data_arg (@data_args) {
    if ($data_arg =~ m/(?<x>-?\d+(.\d+)?),(?<y>-?\d+(.\d+)?)/) {
      my $x = $+{x};
      my $y = $+{y};
      if ($is_moveto_point) {
        $x = $a * $x + $c * $y + $e;
        $y = $b * $x + $d * $y + $f;
        $col = int ($x / PIXELS_PER_EM);
        $row = int ($y / PIXELS_PER_EM);
        $x %= PIXELS_PER_EM;
        $y %= PIXELS_PER_EM;
        $is_moveto_point = 0;
      }
      $x *= UNITS_PER_EM / PIXELS_PER_EM;
      $y *= UNITS_PER_EM / PIXELS_PER_EM;
      $data_arg = "$x,$y"
    }
    $new_data .= "$data_arg ";
  } 
  next if ($col < 0 || $col_max < $col || $row < 0 || $row_max < $row);
  $path->{d} = $new_data;
  push @{$glyphs[$row][$col]}, $path;
}

# リスト読み込み
my @glyphname_list = ();
open my $fh_list, '<:utf8', $list_file;
while (my $line = <$fh_list>) {
  chomp $line;
  my @list = map { sprintf "u%x", (unpack "U*", $_) } split / +/, $line;
  push @glyphname_list, \@list;
}
close $fh_list;

# 各グリフの SVG を生成
mkdir OUT_DIR if !-d OUT_DIR;
foreach my $row (0 .. $row_max) {
  foreach my $col (0 .. $col_max) {
    my $svg;
    $svg->{'xmlns:sodipodi'} = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
    $svg->{'xmlns:inkscape'} = "http://www.inkscape.org/namespaces/inkscape";
    $svg->{width} = UNITS_PER_EM;
    $svg->{height} = UNITS_PER_EM;
    $svg->{path} = $glyphs[$row][$col];
    my $svg_string = '<?xml version="1.0" encoding="UTF-8"?>';
    $svg_string .= "\n" . XMLout($svg, RootName => "svg");
    my $glyphname = $glyphname_list[$row][$col];
    next if (!defined $glyphname || $glyphname eq "u3000"); # u3000:全角スペース
    open my $fh_out, '>', OUT_DIR . "/$glyphname.svg";
    print $fh_out $svg_string;
    close $fh_out;
  }
}

exit 0;

SVGのどのマスがどのグリフか、割り当て表も用意しておく。

あ い う え お
か き く け こ
さ し す せ そ
た ち つ て と
な に ぬ ね の
は ひ ふ へ ほ
ま み む め も
や   ゆ   よ
ら り る れ ろ
わ ゐ ん ゑ を

これを例えば

> perl split_svg.pl hiragana_1.svg hiragana_list.txt

として実行すれば、glyphs ディレクトリに一字一字のSVGが生成される。

FontForge でフォント化

そして、このバラしたSVGファイルを FontForge に取り込み、フォントを生成する。

FontForge ではSVGの1pxがフォントの1ユニットに相当する。また、OpenType の標準は1000ユニット/emなので、インポート元のSVGを1000×1000pxにしておくとちょうど良い。今回は、split_svg.pl でバラすときにSVGのサイズを1000×1000pxへ変換しているので、あとはそのままインポートするだけでよい。

#!/usr/bin/fontforge -script

if ($argc != 2)
  Print("usage: fontforge -script " + $0 + " [version]")
  Quit()
endif

_version      = $1
_fontfilename = "zeromin_" + _version + ".otf"
_importfiles  = "glyphs/u*.svg"

New()

# .notdef作成
Select(0x0000)
SetWidth(1000)
SetGlyphName(".notdef")

# エンコードUnicodeを指定
Reencode("unicode")

# SVGをすべてインポート
Import(_importfiles, 0)

# 自動ヒントづけOFF
SelectAll()
DontAutoHint()

# パスの統合
RemoveOverlap()

# 整数値に丸める
RoundToInt()

# 半角スペース作成
Select(0u0020)
SetWidth(500)

# 全角スペース作成
Select(0u3000)
SetWidth(1000)

# フォント情報設定
SetFontNames("ZeroMin",\
             "ZeroMin",\
             "ZeroMin",\
             "Regular",\
             "© 2012 mashabow",\
             _version) 
SetOS2Value("WinAscent", 880)
SetOS2Value("WinDescent", 120)
SetOS2Value("HHeadAscent", 880)
SetOS2Value("HHeadDescent", -120)

# OTF生成
Generate(_fontfilename)
Print("generated: "+ _fontfilename)

Close()

Quit()

このスクリプトFontForge で動かせば*1、とりあえずはフォントが生成される。最初の明朝体ということで「ZeroMin」と仮に名付けてみた。

試し打ち

出来上がったフォントをインストールして、さっそく試し打ち。

んー。初めてにしてはまずまずなような気もしないでもないけど、やっぱり字面の大きさや寄り引き、太さなんかのバラつきが目立つ。実用にはちょっと堪えない。

というわけで、まだまだ先は長そう。

*1:unofficial fontforge-cygwin でのスクリプトの動かし方はこの記事を参照。