From: Radek Czajka Date: Mon, 18 Oct 2010 13:54:30 +0000 (+0200) Subject: embed fonts in epub, stripped to used chars X-Git-Tag: 1.7~268 X-Git-Url: https://git.mdrn.pl/librarian.git/commitdiff_plain/68b03397a0872d10d3627cea2b92ae36bd59183c?hp=ef7911fba9c330552599bc6eb9dc22606246dd7e embed fonts in epub, stripped to used chars --- diff --git a/font-optimizer/.hg/00changelog.i b/font-optimizer/.hg/00changelog.i new file mode 100644 index 0000000..d3a8311 Binary files /dev/null and b/font-optimizer/.hg/00changelog.i differ diff --git a/font-optimizer/.hg/branch b/font-optimizer/.hg/branch new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/font-optimizer/.hg/branch @@ -0,0 +1 @@ +default diff --git a/font-optimizer/.hg/branchheads.cache b/font-optimizer/.hg/branchheads.cache new file mode 100644 index 0000000..12049da --- /dev/null +++ b/font-optimizer/.hg/branchheads.cache @@ -0,0 +1,2 @@ +136826b53242c388267996d1febb0fea90193a06 28 +136826b53242c388267996d1febb0fea90193a06 default diff --git a/font-optimizer/.hg/dirstate b/font-optimizer/.hg/dirstate new file mode 100644 index 0000000..2c2e4dc Binary files /dev/null and b/font-optimizer/.hg/dirstate differ diff --git a/font-optimizer/.hg/hgrc b/font-optimizer/.hg/hgrc new file mode 100644 index 0000000..5945460 --- /dev/null +++ b/font-optimizer/.hg/hgrc @@ -0,0 +1,2 @@ +[paths] +default = http://bitbucket.org/philip/font-optimizer diff --git a/font-optimizer/.hg/requires b/font-optimizer/.hg/requires new file mode 100644 index 0000000..5175383 --- /dev/null +++ b/font-optimizer/.hg/requires @@ -0,0 +1,3 @@ +revlogv1 +store +fncache diff --git a/font-optimizer/.hg/store/00changelog.i b/font-optimizer/.hg/store/00changelog.i new file mode 100644 index 0000000..589167b Binary files /dev/null and b/font-optimizer/.hg/store/00changelog.i differ diff --git a/font-optimizer/.hg/store/00manifest.i b/font-optimizer/.hg/store/00manifest.i new file mode 100644 index 0000000..a0513b1 Binary files /dev/null and b/font-optimizer/.hg/store/00manifest.i differ diff --git a/font-optimizer/.hg/store/data/.hgignore.i b/font-optimizer/.hg/store/data/.hgignore.i new file mode 100644 index 0000000..d5dd3e2 Binary files /dev/null and b/font-optimizer/.hg/store/data/.hgignore.i differ diff --git a/font-optimizer/.hg/store/data/_font/_e_o_t_wrapper.pm.i b/font-optimizer/.hg/store/data/_font/_e_o_t_wrapper.pm.i new file mode 100644 index 0000000..1ad7150 Binary files /dev/null and b/font-optimizer/.hg/store/data/_font/_e_o_t_wrapper.pm.i differ diff --git a/font-optimizer/.hg/store/data/_font/_subsetter.pm.i b/font-optimizer/.hg/store/data/_font/_subsetter.pm.i new file mode 100644 index 0000000..5d17c4e Binary files /dev/null and b/font-optimizer/.hg/store/data/_font/_subsetter.pm.i differ diff --git a/font-optimizer/.hg/store/data/_font/_subsetter/_normalization_data.pm.i b/font-optimizer/.hg/store/data/_font/_subsetter/_normalization_data.pm.i new file mode 100644 index 0000000..93d459c Binary files /dev/null and b/font-optimizer/.hg/store/data/_font/_subsetter/_normalization_data.pm.i differ diff --git a/font-optimizer/.hg/store/data/_font/_subsetter/create-data.pl.i b/font-optimizer/.hg/store/data/_font/_subsetter/create-data.pl.i new file mode 100644 index 0000000..5ba7451 Binary files /dev/null and b/font-optimizer/.hg/store/data/_font/_subsetter/create-data.pl.i differ diff --git a/font-optimizer/.hg/store/data/_l_i_c_e_n_s_e.i b/font-optimizer/.hg/store/data/_l_i_c_e_n_s_e.i new file mode 100644 index 0000000..d0cc9aa Binary files /dev/null and b/font-optimizer/.hg/store/data/_l_i_c_e_n_s_e.i differ diff --git a/font-optimizer/.hg/store/data/_r_e_a_d_m_e.txt.i b/font-optimizer/.hg/store/data/_r_e_a_d_m_e.txt.i new file mode 100644 index 0000000..b845651 Binary files /dev/null and b/font-optimizer/.hg/store/data/_r_e_a_d_m_e.txt.i differ diff --git a/font-optimizer/.hg/store/data/convert-eot.pl.i b/font-optimizer/.hg/store/data/convert-eot.pl.i new file mode 100644 index 0000000..25a4a4b Binary files /dev/null and b/font-optimizer/.hg/store/data/convert-eot.pl.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/.cvsignore.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/.cvsignore.i new file mode 100644 index 0000000..304249e Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/.cvsignore.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_c_o_p_y_i_n_g.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_c_o_p_y_i_n_g.i new file mode 100644 index 0000000..d0f0482 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_c_o_p_y_i_n_g.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_changes.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_changes.i new file mode 100644 index 0000000..e022e4a Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_changes.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_m_a_n_i_f_e_s_t._s_k_i_p.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_m_a_n_i_f_e_s_t._s_k_i_p.i new file mode 100644 index 0000000..57b5f9d Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_m_a_n_i_f_e_s_t._s_k_i_p.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_makefile._p_l.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_makefile._p_l.i new file mode 100644 index 0000000..e008707 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_makefile._p_l.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_r_e_a_d_m_e._t_x_t.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_r_e_a_d_m_e._t_x_t.i new file mode 100644 index 0000000..8cc6335 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_r_e_a_d_m_e._t_x_t.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_t_o_d_o.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_t_o_d_o.i new file mode 100644 index 0000000..690178c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/_t_o_d_o.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f.pm.i new file mode 100644 index 0000000..8b3c878 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_t_kern.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_t_kern.pm.i new file mode 100644 index 0000000..61243bc Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_t_kern.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_tutils.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_tutils.pm.i new file mode 100644 index 0000000..81cb366 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_a_a_tutils.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_anchor.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_anchor.pm.i new file mode 100644 index 0000000..831f79c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_anchor.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_bsln.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_bsln.pm.i new file mode 100644 index 0000000..da72a7f Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_bsln.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_changes.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_changes.i new file mode 100644 index 0000000..ba56a59 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_changes.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cmap.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cmap.pm.i new file mode 100644 index 0000000..6ca0ef4 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cmap.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_coverage.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_coverage.pm.i new file mode 100644 index 0000000..fbab57c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_coverage.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cvt__.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cvt__.pm.i new file mode 100644 index 0000000..c9d0313 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_cvt__.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_delta.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_delta.pm.i new file mode 100644 index 0000000..c765c0c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_delta.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_dumper.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_dumper.pm.i new file mode 100644 index 0000000..6f21a21 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_dumper.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_d_t.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_d_t.pm.i new file mode 100644 index 0000000..b189d1c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_d_t.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_l_c.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_l_c.pm.i new file mode 100644 index 0000000..3ff5901 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_e_b_l_c.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fdsc.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fdsc.pm.i new file mode 100644 index 0000000..0cdb3e4 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fdsc.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_feat.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_feat.pm.i new file mode 100644 index 0000000..77d19bc Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_feat.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fmtx.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fmtx.pm.i new file mode 100644 index 0000000..eab522e Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fmtx.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_font.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_font.pm.i new file mode 100644 index 0000000..6749bda Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_font.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fpgm.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fpgm.pm.i new file mode 100644 index 0000000..7a3472b Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_fpgm.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_d_e_f.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_d_e_f.pm.i new file mode 100644 index 0000000..7ee4f49 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_d_e_f.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_p_o_s.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_p_o_s.pm.i new file mode 100644 index 0000000..2cb1d44 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_p_o_s.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_s_u_b.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_s_u_b.pm.i new file mode 100644 index 0000000..c15662e Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_g_s_u_b.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyf.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyf.pm.i new file mode 100644 index 0000000..0d9f502 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyf.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyph.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyph.pm.i new file mode 100644 index 0000000..59cfdd5 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_glyph.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_gr_feat.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_gr_feat.pm.i new file mode 100644 index 0000000..390bb2d Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_gr_feat.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hdmx.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hdmx.pm.i new file mode 100644 index 0000000..df875b2 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hdmx.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_head.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_head.pm.i new file mode 100644 index 0000000..2a27c08 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_head.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hhea.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hhea.pm.i new file mode 100644 index 0000000..2048db2 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hhea.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hmtx.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hmtx.pm.i new file mode 100644 index 0000000..ccebc5b Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_hmtx.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern.pm.i new file mode 100644 index 0000000..ab7c2fa Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_class_array.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_class_array.pm.i new file mode 100644 index 0000000..f8d3ac1 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_class_array.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_compact_class_array.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_compact_class_array.pm.i new file mode 100644 index 0000000..ae4c920 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_compact_class_array.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_ordered_list.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_ordered_list.pm.i new file mode 100644 index 0000000..1ee7c8a Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_ordered_list.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_state_table.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_state_table.pm.i new file mode 100644 index 0000000..8473671 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_state_table.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_subtable.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_subtable.pm.i new file mode 100644 index 0000000..a171a02 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_kern/_subtable.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_l_t_s_h.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_l_t_s_h.pm.i new file mode 100644 index 0000000..cdb08e4 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_l_t_s_h.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_loca.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_loca.pm.i new file mode 100644 index 0000000..0e9131d Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_loca.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_manual.pod.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_manual.pod.i new file mode 100644 index 0000000..94fb1f9 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_manual.pod.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_maxp.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_maxp.pm.i new file mode 100644 index 0000000..1e8a342 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_maxp.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort.pm.i new file mode 100644 index 0000000..2d39c95 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_chain.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_chain.pm.i new file mode 100644 index 0000000..2ec8260 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_chain.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_contextual.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_contextual.pm.i new file mode 100644 index 0000000..d366880 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_contextual.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_insertion.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_insertion.pm.i new file mode 100644 index 0000000..05e84e1 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_insertion.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_ligature.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_ligature.pm.i new file mode 100644 index 0000000..c36b902 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_ligature.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_noncontextual.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_noncontextual.pm.i new file mode 100644 index 0000000..6f3dd29 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_noncontextual.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_rearrangement.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_rearrangement.pm.i new file mode 100644 index 0000000..fba11e9 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_rearrangement.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_subtable.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_subtable.pm.i new file mode 100644 index 0000000..93a96a6 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_mort/_subtable.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_name.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_name.pm.i new file mode 100644 index 0000000..77d07a6 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_name.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_s__2.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_s__2.pm.i new file mode 100644 index 0000000..680915c Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_s__2.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_t_tags.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_t_tags.pm.i new file mode 100644 index 0000000..35d40fd Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_o_t_tags.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_cmap.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_cmap.pm.i new file mode 100644 index 0000000..e28e8f5 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_cmap.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_mort.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_mort.pm.i new file mode 100644 index 0000000..8569493 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_old_mort.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_c_l_t.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_c_l_t.pm.i new file mode 100644 index 0000000..8d8f7e3 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_c_l_t.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_s_names.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_s_names.pm.i new file mode 100644 index 0000000..6f687e2 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_p_s_names.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_post.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_post.pm.i new file mode 100644 index 0000000..43aea94 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_post.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prep.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prep.pm.i new file mode 100644 index 0000000..83259b6 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prep.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prop.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prop.pm.i new file mode 100644 index 0000000..1885a0f Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_prop.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_segarr.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_segarr.pm.i new file mode 100644 index 0000000..eb03abd Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_segarr.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_sill.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_sill.pm.i new file mode 100644 index 0000000..c30cc85 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_sill.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_table.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_table.pm.i new file mode 100644 index 0000000..3ca1604 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_table.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttc.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttc.pm.i new file mode 100644 index 0000000..366ba1a Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttc.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttopen.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttopen.pm.i new file mode 100644 index 0000000..bb07642 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_ttopen.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_useall.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_useall.pm.i new file mode 100644 index 0000000..727a333 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_useall.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_utils.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_utils.pm.i new file mode 100644 index 0000000..5bf9612 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_utils.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vhea.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vhea.pm.i new file mode 100644 index 0000000..1972e4b Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vhea.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vmtx.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vmtx.pm.i new file mode 100644 index 0000000..3756ed1 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_vmtx.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_win32.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_win32.pm.i new file mode 100644 index 0000000..25b5628 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_win32.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_x_m_lparse.pm.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_x_m_lparse.pm.i new file mode 100644 index 0000000..2bbc5ae Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/_font/_t_t_f/_x_m_lparse.pm.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/ttfmod.pl.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/ttfmod.pl.i new file mode 100644 index 0000000..e50d2ca Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/lib/ttfmod.pl.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/_o_f_l.txt.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/_o_f_l.txt.i new file mode 100644 index 0000000..0b8fb4b Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/_o_f_l.txt.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/tags.t.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/tags.t.i new file mode 100644 index 0000000..4181bf2 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/tags.t.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/testfont.ttf.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/testfont.ttf.i new file mode 100644 index 0000000..57ac10e Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/testfont.ttf.i differ diff --git a/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/ttfcopy.t.i b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/ttfcopy.t.i new file mode 100644 index 0000000..5ce2b98 Binary files /dev/null and b/font-optimizer/.hg/store/data/ext/_font-_t_t_f/t/ttfcopy.t.i differ diff --git a/font-optimizer/.hg/store/data/gen-tests.pl.i b/font-optimizer/.hg/store/data/gen-tests.pl.i new file mode 100644 index 0000000..815d800 Binary files /dev/null and b/font-optimizer/.hg/store/data/gen-tests.pl.i differ diff --git a/font-optimizer/.hg/store/data/list-features.pl.i b/font-optimizer/.hg/store/data/list-features.pl.i new file mode 100644 index 0000000..1b72a5d Binary files /dev/null and b/font-optimizer/.hg/store/data/list-features.pl.i differ diff --git a/font-optimizer/.hg/store/data/modify-names.pl.i b/font-optimizer/.hg/store/data/modify-names.pl.i new file mode 100644 index 0000000..dfd5924 Binary files /dev/null and b/font-optimizer/.hg/store/data/modify-names.pl.i differ diff --git a/font-optimizer/.hg/store/data/obfuscate-font.pl.i b/font-optimizer/.hg/store/data/obfuscate-font.pl.i new file mode 100644 index 0000000..7853c72 Binary files /dev/null and b/font-optimizer/.hg/store/data/obfuscate-font.pl.i differ diff --git a/font-optimizer/.hg/store/data/obfuscate-names.pl.i b/font-optimizer/.hg/store/data/obfuscate-names.pl.i new file mode 100644 index 0000000..4120d0e Binary files /dev/null and b/font-optimizer/.hg/store/data/obfuscate-names.pl.i differ diff --git a/font-optimizer/.hg/store/data/subset.pl.i b/font-optimizer/.hg/store/data/subset.pl.i new file mode 100644 index 0000000..6dd0437 Binary files /dev/null and b/font-optimizer/.hg/store/data/subset.pl.i differ diff --git a/font-optimizer/.hg/store/data/t/subsetter.pl.i b/font-optimizer/.hg/store/data/t/subsetter.pl.i new file mode 100644 index 0000000..122eaa2 Binary files /dev/null and b/font-optimizer/.hg/store/data/t/subsetter.pl.i differ diff --git a/font-optimizer/.hg/store/fncache b/font-optimizer/.hg/store/fncache new file mode 100644 index 0000000..f8f8690 --- /dev/null +++ b/font-optimizer/.hg/store/fncache @@ -0,0 +1,94 @@ +data/.hgignore.i +data/Font/EOTWrapper.pm.i +data/Font/Subsetter.pm.i +data/Font/Subsetter/NormalizationData.pm.i +data/Font/Subsetter/create-data.pl.i +data/LICENSE.i +data/README.txt.i +data/convert-eot.pl.i +data/ext/Font-TTF/.cvsignore.i +data/ext/Font-TTF/COPYING.i +data/ext/Font-TTF/Changes.i +data/ext/Font-TTF/MANIFEST.SKIP.i +data/ext/Font-TTF/Makefile.PL.i +data/ext/Font-TTF/README.TXT.i +data/ext/Font-TTF/TODO.i +data/ext/Font-TTF/lib/Font/TTF.pm.i +data/ext/Font-TTF/lib/Font/TTF/AATKern.pm.i +data/ext/Font-TTF/lib/Font/TTF/AATutils.pm.i +data/ext/Font-TTF/lib/Font/TTF/Anchor.pm.i +data/ext/Font-TTF/lib/Font/TTF/Bsln.pm.i +data/ext/Font-TTF/lib/Font/TTF/Changes.i +data/ext/Font-TTF/lib/Font/TTF/Cmap.pm.i +data/ext/Font-TTF/lib/Font/TTF/Coverage.pm.i +data/ext/Font-TTF/lib/Font/TTF/Cvt_.pm.i +data/ext/Font-TTF/lib/Font/TTF/Delta.pm.i +data/ext/Font-TTF/lib/Font/TTF/Dumper.pm.i +data/ext/Font-TTF/lib/Font/TTF/EBDT.pm.i +data/ext/Font-TTF/lib/Font/TTF/EBLC.pm.i +data/ext/Font-TTF/lib/Font/TTF/Fdsc.pm.i +data/ext/Font-TTF/lib/Font/TTF/Feat.pm.i +data/ext/Font-TTF/lib/Font/TTF/Fmtx.pm.i +data/ext/Font-TTF/lib/Font/TTF/Font.pm.i +data/ext/Font-TTF/lib/Font/TTF/Fpgm.pm.i +data/ext/Font-TTF/lib/Font/TTF/GDEF.pm.i +data/ext/Font-TTF/lib/Font/TTF/GPOS.pm.i +data/ext/Font-TTF/lib/Font/TTF/GSUB.pm.i +data/ext/Font-TTF/lib/Font/TTF/Glyf.pm.i +data/ext/Font-TTF/lib/Font/TTF/Glyph.pm.i +data/ext/Font-TTF/lib/Font/TTF/GrFeat.pm.i +data/ext/Font-TTF/lib/Font/TTF/Hdmx.pm.i +data/ext/Font-TTF/lib/Font/TTF/Head.pm.i +data/ext/Font-TTF/lib/Font/TTF/Hhea.pm.i +data/ext/Font-TTF/lib/Font/TTF/Hmtx.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern/ClassArray.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern/CompactClassArray.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern/OrderedList.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern/StateTable.pm.i +data/ext/Font-TTF/lib/Font/TTF/Kern/Subtable.pm.i +data/ext/Font-TTF/lib/Font/TTF/LTSH.pm.i +data/ext/Font-TTF/lib/Font/TTF/Loca.pm.i +data/ext/Font-TTF/lib/Font/TTF/Manual.pod.i +data/ext/Font-TTF/lib/Font/TTF/Maxp.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Chain.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Contextual.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Insertion.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Ligature.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Noncontextual.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Rearrangement.pm.i +data/ext/Font-TTF/lib/Font/TTF/Mort/Subtable.pm.i +data/ext/Font-TTF/lib/Font/TTF/Name.pm.i +data/ext/Font-TTF/lib/Font/TTF/OS_2.pm.i +data/ext/Font-TTF/lib/Font/TTF/OTTags.pm.i +data/ext/Font-TTF/lib/Font/TTF/OldCmap.pm.i +data/ext/Font-TTF/lib/Font/TTF/OldMort.pm.i +data/ext/Font-TTF/lib/Font/TTF/PCLT.pm.i +data/ext/Font-TTF/lib/Font/TTF/PSNames.pm.i +data/ext/Font-TTF/lib/Font/TTF/Post.pm.i +data/ext/Font-TTF/lib/Font/TTF/Prep.pm.i +data/ext/Font-TTF/lib/Font/TTF/Prop.pm.i +data/ext/Font-TTF/lib/Font/TTF/Segarr.pm.i +data/ext/Font-TTF/lib/Font/TTF/Sill.pm.i +data/ext/Font-TTF/lib/Font/TTF/Table.pm.i +data/ext/Font-TTF/lib/Font/TTF/Ttc.pm.i +data/ext/Font-TTF/lib/Font/TTF/Ttopen.pm.i +data/ext/Font-TTF/lib/Font/TTF/Useall.pm.i +data/ext/Font-TTF/lib/Font/TTF/Utils.pm.i +data/ext/Font-TTF/lib/Font/TTF/Vhea.pm.i +data/ext/Font-TTF/lib/Font/TTF/Vmtx.pm.i +data/ext/Font-TTF/lib/Font/TTF/Win32.pm.i +data/ext/Font-TTF/lib/Font/TTF/XMLparse.pm.i +data/ext/Font-TTF/lib/ttfmod.pl.i +data/ext/Font-TTF/t/OFL.txt.i +data/ext/Font-TTF/t/tags.t.i +data/ext/Font-TTF/t/testfont.ttf.i +data/ext/Font-TTF/t/ttfcopy.t.i +data/gen-tests.pl.i +data/list-features.pl.i +data/modify-names.pl.i +data/obfuscate-font.pl.i +data/obfuscate-names.pl.i +data/subset.pl.i +data/t/subsetter.pl.i diff --git a/font-optimizer/.hg/store/undo b/font-optimizer/.hg/store/undo new file mode 100644 index 0000000..d1ad357 Binary files /dev/null and b/font-optimizer/.hg/store/undo differ diff --git a/font-optimizer/.hg/tags.cache b/font-optimizer/.hg/tags.cache new file mode 100644 index 0000000..b27879b --- /dev/null +++ b/font-optimizer/.hg/tags.cache @@ -0,0 +1,2 @@ +28 136826b53242c388267996d1febb0fea90193a06 + diff --git a/font-optimizer/.hg/undo.branch b/font-optimizer/.hg/undo.branch new file mode 100644 index 0000000..331d858 --- /dev/null +++ b/font-optimizer/.hg/undo.branch @@ -0,0 +1 @@ +default \ No newline at end of file diff --git a/font-optimizer/.hg/undo.desc b/font-optimizer/.hg/undo.desc new file mode 100644 index 0000000..4f5da8a --- /dev/null +++ b/font-optimizer/.hg/undo.desc @@ -0,0 +1,3 @@ +0 +pull +http://bitbucket.org/philip/font-optimizer diff --git a/font-optimizer/.hg/undo.dirstate b/font-optimizer/.hg/undo.dirstate new file mode 100644 index 0000000..e69de29 diff --git a/font-optimizer/.hgignore b/font-optimizer/.hgignore new file mode 100644 index 0000000..5fe60d8 --- /dev/null +++ b/font-optimizer/.hgignore @@ -0,0 +1,4 @@ +~$ +^testoutput/ +^testfonts/ +^tmp/ diff --git a/font-optimizer/Font/EOTWrapper.pm b/font-optimizer/Font/EOTWrapper.pm new file mode 100644 index 0000000..297b930 --- /dev/null +++ b/font-optimizer/Font/EOTWrapper.pm @@ -0,0 +1,141 @@ +# Copyright (c) 2009 Philip Taylor +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +package Font::EOTWrapper; + +use strict; +use warnings; + +use Font::TTF::Font; +use Encode; + +use constant TTEMBED_SUBSET => 0x00000001; +use constant TTEMBED_TTCOMPRESSED => 0x00000004; +use constant TTEMBED_XORENCRYPTDATA => 0x10000000; +use constant DEFAULT_CHARSET => 0x01; + +sub convert { + my ($in_fn, $out_fn) = @_; + + my $font_data = do { + open my $fh, $in_fn or die "Failed to open $in_fn: $!"; + binmode $fh; + local $/; + <$fh> + }; + + my $font = Font::TTF::Font->open($in_fn) or die "Failed to open $in_fn: $!"; + + open my $out, '>', $out_fn or die "Failed to open $out_fn: $!"; + binmode $out; + + $font->{name}->read if $font->{name}; + + my $os2 = $font->{'OS/2'}; + $os2->read; + + my $rootString = ''; + + my $header = ''; + $header .= pack V => length($font_data); + $header .= pack V => 0x00020001; + $header .= pack V => TTEMBED_SUBSET; + $header .= pack C10 => map $os2->{$_}, qw(bFamilyType bSerifStyle bWeight bProportion bContrast bStrokeVariation bArmStyle bLetterform bMidline bXheight); + $header .= pack C => DEFAULT_CHARSET; + $header .= pack C => (($os2->{fsSelection} & 1) ? 1 : 0); + $header .= pack V => $os2->{usWeightClass}; + $header .= pack v => $os2->{fsType}; + $header .= pack v => 0x504C; + $header .= pack VVVV => map $os2->{$_}, qw(ulUnicodeRange1 ulUnicodeRange2 ulUnicodeRange3 ulUnicodeRange4); + $header .= pack VV => map $os2->{$_}, qw(ulCodePageRange1 ulCodePageRange2); + $header .= pack V => $font->{head}{checkSumAdjustment}; + $header .= pack VVVV => 0, 0, 0, 0; + $header .= pack v => 0; + $header .= pack 'v/a*' => encode 'utf-16le' => $font->{name}->find_name(1); # family name + $header .= pack v => 0; + $header .= pack 'v/a*' => encode 'utf-16le' => $font->{name}->find_name(2); # style name + $header .= pack v => 0; + $header .= pack 'v/a*' => encode 'utf-16le' => $font->{name}->find_name(5); # version name + $header .= pack v => 0; + $header .= pack 'v/a*' => encode 'utf-16le' => $font->{name}->find_name(4); # full name + $header .= pack v => 0; + $header .= pack 'v/a*' => encode 'utf-16le' => $rootString; + + $out->print(pack V => 4 + length($header) + length($font_data)); + $out->print($header); + $out->print($font_data); + + $font->release; +} + +sub extract { + my ($in_fn, $out_fn) = @_; + + my $eot_data = do { + open my $fh, $in_fn or die "Failed to open $in_fn: $!"; + binmode $fh; + local $/; + <$fh> + }; + + die "Error: EOT too small" if length $eot_data < 16; + + my ($eot_size, $font_data_size, $version, $flags) = unpack VVVV => substr $eot_data, 0, 16; + + die "Error: Invalid EOTSize ($eot_size, should be ".(length $eot_data).")" if $eot_size != length $eot_data; + die "Error: Invalid Version ($version)" if not ($version == 0x00020000 or $version == 0x00020001 or $version == 0x00020002); + die "Error: Can't handle compressed fonts" if $flags & TTEMBED_TTCOMPRESSED; + + # Skip the header fields + my $rest = substr $eot_data, 16+66; + + my ($family_name, $style_name, $version_name, $full_name, $rest2) = unpack 'v/a* xx v/a* xx v/a* xx v/a* a*' => $rest; + + my $font_data; + if ($version == 0x00020000) { # not 0x00010000 - spec is wrong (http://lists.w3.org/Archives/Public/www-font/2009JulSep/0862.html) + $font_data = $rest2; + } elsif ($version == 0x00020001) { + my ($root, $data) = unpack 'xx v/a* a*' => $rest2; + $font_data = $data; + } elsif ($version == 0x00020002) { + my ($root, $root_checksum, $eudc_codepage, $signature, $eudc_flags, $eudc_font, $data) + = unpack 'xx v/a* V V xx v/a* V v/a* a*' => $rest2; + $font_data = $data; + } + + if ($flags & TTEMBED_XORENCRYPTDATA) { + $font_data ^= ("\x50" x length $font_data); + } + + open my $fh, '>', $out_fn or die "Failed to open $out_fn: $!"; + binmode $fh; + print $fh $font_data; +} + +# sub rootStringChecksum { +# my $s = 0; +# $s += $_ for unpack 'C*', $_[0]; +# return $s ^ 0x50475342; +# } + +1; diff --git a/font-optimizer/Font/Subsetter.pm b/font-optimizer/Font/Subsetter.pm new file mode 100644 index 0000000..cd1c40c --- /dev/null +++ b/font-optimizer/Font/Subsetter.pm @@ -0,0 +1,1606 @@ +# Copyright (c) 2009 Philip Taylor +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +package Font::Subsetter; + +use strict; +use warnings; + +use Carp; +use Unicode::Normalize(); +use Digest::SHA qw(sha1_hex); +use Encode; + +use Font::TTF; +use Font::TTF::Font; + +if ($Font::TTF::VERSION =~ /^0\.([0-3].|4[0-5])$/) { + die "You are using an old version of Font::TTF ($Font::TTF::VERSION) - you need at least v0.46, and preferably the latest SVN trunk from ."; +} + +# Tables can be: +# REQUIRED - will fail if it's not present +# FORBIDDEN - will fail if it's present +# OPTIONAL - will be accepted regardless of whether it's there or not +# IGNORED - like OPTIONAL, but no processing will take place +# UNFINISHED - will emit a warning if it's present, because the code doesn't handle it properly yet +# DROP - will be deleted from the font +# The default for unmentioned tables is FORBIDDEN +my %font_tables = ( + 'cmap' => ['REQUIRED'], + 'head' => ['REQUIRED'], + 'hhea' => ['REQUIRED'], + 'hmtx' => ['REQUIRED'], + 'maxp' => ['REQUIRED'], + 'name' => ['REQUIRED'], + 'OS/2' => ['REQUIRED'], + 'post' => ['REQUIRED'], + # TrueType outlines: + 'cvt ' => ['IGNORED'], + 'fpgm' => ['IGNORED'], + 'glyf' => ['IGNORED'], + 'loca' => ['OPTIONAL'], + 'prep' => ['OPTIONAL'], + # PostScript outlines: (TODO: support these?) + 'CFF ' => ['FORBIDDEN'], + 'VORG' => ['FORBIDDEN'], + # Bitmap glyphs: (TODO: support these?) + 'EBDT' => ['DROP', 'embedded bitmap glyphs will be lost'], + 'EBLC' => ['DROP', 'embedded bitmap glyphs will be lost'], + 'EBSC' => ['DROP', 'embedded bitmap glyphs will be lost'], + # Advanced typographic tables: + 'BASE' => ['UNFINISHED'], + 'GDEF' => ['OPTIONAL'], + 'GPOS' => ['OPTIONAL'], + 'GSUB' => ['OPTIONAL'], + 'JSTF' => ['UNFINISHED'], + # OpenType tables: + 'DSIG' => ['DROP'], # digital signature - don't need it here + 'gasp' => ['IGNORED'], + 'hdmx' => ['OPTIONAL'], + 'kern' => ['OPTIONAL'], + 'LTSH' => ['OPTIONAL'], + 'PCLT' => ['UNFINISHED'], + 'VDMX' => ['IGNORED'], + 'vhea' => ['UNFINISHED'], + 'vmtx' => ['UNFINISHED'], + # SIL Graphite tables: + 'Feat' => ['DROP'], + 'Silf' => ['DROP'], + 'Sill' => ['DROP'], + 'Silt' => ['DROP'], + 'Glat' => ['DROP'], + 'Gloc' => ['DROP'], + # FontForge tables: + 'PfEd' => ['DROP'], + 'FFTM' => ['DROP'], + # Apple Advanced Typography tables: + # (These get dropped because it's better to use cross-platform features instead) + 'feat' => ['DROP'], + 'morx' => ['DROP'], + 'prop' => ['DROP'], + # Undocumented(?) extension for some kind of maths stuff + 'MATH' => ['DROP'], +); + +sub check_tables { + my ($self) = @_; + my $font = $self->{font}; + + my @tables = grep /^[^ ]...$/, sort keys %$font; + for (@tables) { + my $t = $font_tables{$_}; + if (not $t) { + die "Uses unrecognised table '$_'\n"; + } else { + my $status = $t->[0]; + if ($status eq 'FORBIDDEN') { + die "Uses forbidden table '$_'\n"; + } elsif ($status eq 'UNFINISHED') { + warn "Uses unhandled table '$_'\n"; + } elsif ($status eq 'DROP') { + my $note = ($t->[1] ? ' - '.$t->[1] : ''); + warn "Dropping table '$_'$note\n"; + delete $font->{$_}; + } elsif ($status eq 'OPTIONAL') { + } elsif ($status eq 'IGNORED') { + } elsif ($status eq 'REQUIRED') { + } else { + die "Invalid table status $status"; + } + } + } + # TODO: check required tables are present + # TODO: check TrueType or PostScript tables are present +} + +sub read_tables { + my ($self) = @_; + my $font = $self->{font}; + + # Read all the tables that will be needed in the future. + # (In particular, read them before modifying numGlyphs, + # beacuse they often depend on that value.) + for (qw( + cmap hmtx name OS/2 post + glyf loca + BASE GDEF GPOS GSUB JSTF + hdmx kern LTSH + )) { + $font->{$_}->read if $font->{$_}; + } +} + +sub find_codepoint_glyph_mappings { + my ($self) = @_; + my $font = $self->{font}; + + # Find the glyph->codepoint mappings + + my %glyphs; + for my $table (@{$font->{cmap}{Tables}}) { + for my $cp (keys %{$table->{val}}) { + + my $ucp; # Unicode code point + + if ($table->{Platform} == 0 # Unicode + or ($table->{Platform} == 3 and # Windows + ($table->{Encoding} == 1 or # Unicode BMP + $table->{Encoding} == 10)) # Unicode full + ) { + $ucp = $cp; + } elsif ($table->{Platform} == 1 # Mac + and $table->{Encoding} == 0) # Roman + { + $ucp = ord(decode('MacRoman', pack C => $cp)); + } else { + # This table might not map directly onto Unicode codepoints, + # so warn about it + warn "Unrecognised cmap table type (platform $table->{Platform}, encoding $table->{Encoding}) - ignoring its character/glyph mappings\n"; + next; + } + + my $g = $table->{val}{$cp}; # glyph id + $glyphs{$g}{$ucp} = 1; + } + } + $self->{glyphs} = \%glyphs; +} + +sub expand_wanted_chars { + my ($self, $chars) = @_; + # OS X browsers (via ATSUI?) appear to convert text into + # NFC before rendering it. + # So input like "i{combining grave}" is converted to "{i grave}" + # before it's even passed to the font's substitution tables. + # So if @chars contains i and {combining grave}, then we have to + # add {i grave} because that might get used. + # + # So... Include all the unchanged characters. Also include the NFC + # of each character. Then use NormalizationData to add any characters + # that can result from NFCing a string of the wanted characters. + + if (0) { # change to 1 to disable all this fancy stuff + my %cs = map { ord $_ => 1 } split '', $chars; + return %cs; + } + + my %cs = map { ord $_ => 1, ord Unicode::Normalize::NFC($_) => 1 } split '', $chars; + require Font::Subsetter::NormalizationData; + my %new_cs; + for my $c (@Font::Subsetter::NormalizationData::data) { + # Skip this if we've already got the composed character + next if $cs{$c->[0]}; + # Skip this if we don't have all the decomposed characters + next if grep !$cs{$_}, @{$c}[1..$#$c]; + # Otherwise we want the composed character + $new_cs{$c->[0]} = 1; + } + $cs{$_} = 1 for keys %new_cs; + return %cs; +} + +sub want_feature { + my ($self, $wanted, $feature) = @_; + # If no feature list was specified, accept all features + return 1 if not $wanted; + # Otherwise find the four-character tag + $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'"; + return $wanted->{$1} if exists $wanted->{$1}; + return $wanted->{DEFAULT} if exists $wanted->{DEFAULT}; + return 1; +} + +sub find_wanted_lookup_ids { + my ($self, $table) = @_; + + # If we wanted to include all lookups: + # return 0..$#{$table->{LOOKUP}}; + # but actually we only want ones used by wanted features + + my %lookups; + for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) { + next if not $self->want_feature($self->{features}, $feat_tag); + for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) { + $lookups{$_} = 1; + } + } + + # Iteratively add any chained lookups + my $changed = 1; + while ($changed) { + $changed = 0; + for my $lookup_id (0..$#{$table->{LOOKUP}}) { + next unless $lookups{$lookup_id}; + my $lookup = $table->{LOOKUP}[$lookup_id]; + for my $sub (@{$lookup->{SUB}}) { + if ($sub->{ACTION_TYPE} eq 'l') { + for my $rule (@{$sub->{RULES}}) { + for my $chain (@$rule) { + for my $action (@{$chain->{ACTION}}) { + for (0..@$action/2-1) { + # action is array of (offset, lookup) + $changed = 1 if not $lookups{$action->[$_*2+1]}; + $lookups{$action->[$_*2+1]} = 1; + } + } + } + } + } + } + } + } + + my @keys = sort { $a <=> $b } keys %lookups; + return @keys; +} + +sub find_wanted_glyphs { + my ($self, $chars) = @_; + my $font = $self->{font}; + + my %wanted_chars = $self->expand_wanted_chars($chars); + $self->{wanted_glyphs} = {}; + + # http://www.microsoft.com/typography/otspec/recom.htm suggests that fonts + # should include .notdef, .null, CR, space; so include them all here, if they + # are already defined + if ($font->{post}{VAL}) { + for my $gid (0..$#{$font->{loca}{glyphs}}) { + my $name = $font->{post}{VAL}[$gid]; + if ($name and ($name eq '.notdef' or $name eq '.null' or $name eq 'CR' or $name eq 'space')) { + $self->{wanted_glyphs}{$gid} = 1; + } + } + } else { + # If post.FormatType == 3 then we don't have any glyph names + # so just assume it's the first four + $self->{wanted_glyphs}{$_} = 1 for 0..3; + } + + # We want any glyphs used directly by any characters we want + for my $gid (keys %{$self->{glyphs}}) { + for my $cp (keys %{$self->{glyphs}{$gid}}) { + $self->{wanted_glyphs}{$gid} = 1 if $wanted_chars{$cp}; + } + } + + # Iteratively find new glyphs, until convergence + my @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}}; + while (@newly_wanted_glyphs) { + my @new_glyphs; + + if ($font->{GSUB}) { + + # Handle ligatures and similar things + # (e.g. if we want 'f' and 'i', we want the 'fi' ligature too) + # (NOTE: a lot of this code is duplicating the form of + # fix_gsub, so they ought to be kept roughly in sync) + # + # TODO: There's probably loads of bugs in here, so it + # should be checked and tested more + + for my $lookup_id ($self->find_wanted_lookup_ids($font->{GSUB})) { + my $lookup = $font->{GSUB}{LOOKUP}[$lookup_id]; + for my $sub (@{$lookup->{SUB}}) { + + # Handle the glyph-delta case + if ($sub->{ACTION_TYPE} eq 'o') { + my $adj = $sub->{ADJUST}; + if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727) + my @covs = $self->coverage_array($sub->{COVERAGE}); + for (@covs) { + # If we want the coveraged glyph, we also want + # that glyph plus delta + if ($self->{wanted_glyphs}{$_}) { + my $new = $_ + $adj; + next if $self->{wanted_glyphs}{$new}; + push @new_glyphs, $new; + $self->{wanted_glyphs}{$new} = 1; + } + } + next; + } + + # Collect the rules which might match initially something + my @rulesets; + if ($sub->{RULES}) { + if (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6) + and $sub->{FORMAT} == 2) { + # RULES corresponds to class values + # TODO: ought to filter this by classes that contain wanted glyphs + push @rulesets, @{$sub->{RULES}}; + } elsif (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6) + and $sub->{FORMAT} == 3) { + # COVERAGE is empty; accept all the RULEs, and + # we'll look inside their MATCHes later + push @rulesets, @{$sub->{RULES}}; + } else { + # COVERAGE lists glyphs, and there's a RULE for + # each, so extract the RULEs for wanted COVERAGE + # values + my @covs = $self->coverage_array($sub->{COVERAGE}); + die unless @{$sub->{RULES}} == @covs; + for my $i (0..$#covs) { + if ($self->{wanted_glyphs}{$covs[$i]}) { + push @rulesets, $sub->{RULES}[$i]; + } + } + } + } + + # Collect the rules whose MATCH matches + my @rules; + RULE: for my $rule (map @$_, @rulesets) { + if (not defined $sub->{MATCH_TYPE}) { + # No extra matching other than COVERAGE, + # so just accept this rule + } elsif ($sub->{MATCH_TYPE} eq 'g') { + # RULES->MATCH/PRE/POST are arrays of glyphs that must all match + for my $c (qw(MATCH PRE POST)) { + next unless $rule->{$c}; + next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}}; + } + } elsif ($sub->{MATCH_TYPE} eq 'o') { + # RULES->MATCH/PRE/POST are arrays of coverage tables, + # and at least one glyph from each table must match + die unless @{$sub->{RULES}} == 1; + die unless @{$sub->{RULES}[0]} == 1; + for my $c (qw(MATCH PRE POST)) { + next unless $sub->{RULES}[0][0]{$c}; + for (@{$sub->{RULES}[0][0]{$c}}) { + my $matched = 0; + for (keys %{$_->{val}}) { + if ($self->{wanted_glyphs}{$_}) { + $matched = 1; + last; + } + } + next RULE if not $matched; + } + } + } elsif ($sub->{MATCH_TYPE} eq 'c') { + # TODO: only includes rules using classes that contain + # wanted glyphs. + # For now, just conservatively accept everything. + } else { + die "Invalid MATCH_TYPE"; + } + push @rules, $rule; + } + + # Find the glyphs in the relevant actions + for my $rule (@rules) { + if ($sub->{ACTION_TYPE} eq 'g') { + die unless $rule->{ACTION}; + for my $new (@{$rule->{ACTION}}) { + next if $self->{wanted_glyphs}{$new}; + push @new_glyphs, $new; + $self->{wanted_glyphs}{$new} = 1; +# warn "adding $new"; + } + } elsif ($sub->{ACTION_TYPE} eq 'l') { + # do nothing - this is just a lookup to run some other rules + } elsif ($sub->{ACTION_TYPE} eq 'a') { + # do nothing - we don't want the alternative glyphs + } else { + die "Invalid ACTION_TYPE"; + } + } + } + } + } + + @newly_wanted_glyphs = @new_glyphs; + } + + # Now we want to add glyphs that are used for composite rendering, + # which don't participate in any GSUB behaviour + @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}}; + while (@newly_wanted_glyphs) { + my @new_glyphs; + + if ($font->{loca}) { + # If we want a composite glyph, we want all of its + # component glyphs too + # (e.g. á is the 'a' glyph plus the acute glyph): + for my $gid (@newly_wanted_glyphs) { + my $glyph = $font->{loca}{glyphs}[$gid]; + next unless $glyph; + $glyph->read; + next unless $glyph->{numberOfContours} == -1; + $glyph->read_dat; + for (@{$glyph->{comps}}) { + next if $self->{wanted_glyphs}{$_->{glyph}}; + push @new_glyphs, $_->{glyph}; + $self->{wanted_glyphs}{$_->{glyph}} = 1; + } + $glyph->update; + } + } + + @newly_wanted_glyphs = @new_glyphs; + } +} + +sub update_classdef_table { + my ($self, $table) = @_; + die "Expected table" if not $table; + die "Expected classdef" if $table->{cover}; + my @vals; + for my $gid (keys %{$table->{val}}) { + next if not $self->{wanted_glyphs}{$gid}; + my $v = $table->{val}{$gid}; + push @vals, $self->{glyph_id_old_to_new}{$gid}, $v; + } + my $ret = new Font::TTF::Coverage(0, @vals); + # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716): + # 'max' is not set by new(), so do it manually: + my $max = 0; + for (values %{$ret->{val}}) { $max = $_ if $_ > $max } + $ret->{max} = $max; + return $ret; +} + +# Returns a map such that map[old_class_value] = new_class_value +# (or undef if the class is removed) +# This differs from update_classdef_table in that it can +# reorder and optimise the class ids +sub update_mapped_classdef_table { + my ($self, $table) = @_; + die "Expected table" if not $table; + die "Expected classdef" if $table->{cover}; + my @vals; + my %used_classes; + $used_classes{0} = 1; # 0 is implicitly in every classdef + for my $gid (keys %{$table->{val}}) { + next if not $self->{wanted_glyphs}{$gid}; + my $v = $table->{val}{$gid}; + push @vals, $self->{glyph_id_old_to_new}{$gid}, $v; + $used_classes{$v} = 1; + } + + my @map_new_to_old = sort { $a <=> $b } keys %used_classes; + my @map_old_to_new; + $map_old_to_new[$map_new_to_old[$_]] = $_ for 0..$#map_new_to_old; + + # Update the class numbers + for (0..@vals/2-1) { + $vals[$_*2+1] = $map_old_to_new[$vals[$_*2+1]]; + } + + my $ret = new Font::TTF::Coverage(0, @vals); + # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716): + # 'max' is not set by new(), so do it manually: + my $max = 0; + for (values %{$ret->{val}}) { $max = $_ if $_ > $max } + $ret->{max} = $max; + return ($ret, \@map_old_to_new, \@map_new_to_old); +} + +# Removes unwanted glyphs from a coverage table, for +# cases where nobody else is referring to indexes in this table +sub update_coverage_table { + my ($self, $table) = @_; + die "Expected table" if not $table; + die "Expected cover" if not $table->{cover}; + my @vals = keys %{$table->{val}}; + @vals = grep $self->{wanted_glyphs}{$_}, @vals; + @vals = sort { $a <=> $b } @vals; + @vals = map $self->{glyph_id_old_to_new}{$_}, @vals; + return new Font::TTF::Coverage(1, @vals); +} + +# Returns a map such that map[new_coverage_index] = old_coverage_index +sub update_mapped_coverage_table { + my ($self, $table) = @_; + die "Expected table" if not $table; + die "Expected coverage" if not $table->{cover}; + + my @map; + my @new_vals; + # Get the covered values (in order) + my @vals = $self->coverage_array($table); + for my $i (0..$#vals) { + # Create a new list of all the wanted values + if ($self->{wanted_glyphs}{$vals[$i]}) { + push @new_vals, $self->{glyph_id_old_to_new}{$vals[$i]}; + push @map, $i; + } + } + return (new Font::TTF::Coverage(1, @new_vals), @map); +} + +sub coverage_array { + my ($self, $table) = @_; + Carp::confess "Expected table" if not $table; + return sort { $table->{val}{$a} <=> $table->{val}{$b} } keys %{$table->{val}}; +} + +sub empty_coverage { + my ($self, $table) = @_; + Carp::confess "Expected table" if not $table; + return 1 if not $table->{val}; + return 1 if not keys %{$table->{val}}; + return 0; +} + +# Update the loca table to delete unwanted glyphs. +# Must be called before all the other fix_* methods. +sub remove_unwanted_glyphs { + my ($self) = @_; + my $font = $self->{font}; + + return unless $font->{loca}; + + my %glyph_id_old_to_new; + my %glyph_id_new_to_old; + + my $glyphs = $font->{loca}{glyphs}; + my @new_glyphs; + for my $i (0..$#$glyphs) { + if ($self->{wanted_glyphs}{$i}) { + push @new_glyphs, $glyphs->[$i]; + $glyph_id_old_to_new{$i} = $#new_glyphs; + $glyph_id_new_to_old{$#new_glyphs} = $i; + } + } + $font->{loca}{glyphs} = \@new_glyphs; + $font->{maxp}{numGlyphs} = scalar @new_glyphs; + + $self->{glyph_id_old_to_new} = \%glyph_id_old_to_new; + $self->{glyph_id_new_to_old} = \%glyph_id_new_to_old; +} + + +# Only the platform=3 encoding=1 cmap is really needed +# (for Windows, OS X, Linux), so save space (and potentially +# enhance cross-platformness) by stripping out all the others. +# (But keep platform=3 encoding=10 too, for UCS-4 characters.) +# (And Opera 10 on OS X wants one with platform=0 too.) +sub strip_cmap { + my ($self) = @_; + my $font = $self->{font}; + + if (not grep { $_->{Platform} == 3 and $_->{Encoding} == 1 } @{$font->{cmap}{Tables}}) { + warn "No cmap found with platform=3 encoding=1 - the font is likely to not work on Windows.\n"; + # Stop now, instead of stripping out all of the cmap tables + return; + } + + my @matched_tables = grep { + ($_->{Platform} == 3 and ($_->{Encoding} == 1 || $_->{Encoding} == 10)) + or ($_->{Platform} == 0) + } @{$font->{cmap}{Tables}}; + + $font->{cmap}{Tables} = \@matched_tables; +} + +# Only the platform=3 encoding=1 names are really needed +# (for Windows, OS X, Linux), so save space (and potentially +# enhance cross-platformness) by stripping out all the others. +sub strip_name { + my ($self) = @_; + my $font = $self->{font}; + + for my $id (0..$#{$font->{name}{strings}}) { + my $str = $font->{name}{strings}[$id]; + next if not $str; + my $plat = 3; + my $enc = 1; + my $langs = $str->[$plat][$enc]; + if (not $langs) { + warn "No name found with id=$id with platform=3 encoding=1 - the font is likely to not work on Windows.\n" + unless $id == 18; # warn except for some Mac-specific names + return; + } + $font->{name}{strings}[$id] = []; + $font->{name}{strings}[$id][$plat][$enc] = $langs; + # NOTE: this keeps all the languages for each string, which is + # potentially wasteful if there are lots (but in practice most fonts + # seem to only have English) + } +} + +sub fix_cmap { + my ($self) = @_; + my $font = $self->{font}; + + # Delete mappings for unwanted glyphs + + for my $table (@{$font->{cmap}{Tables}}) { + # (Already warned about unrecognised table types + # in find_codepoint_glyph_mappings) + my %new_vals; + for my $cp (keys %{$table->{val}}) { + my $gid = $table->{val}{$cp}; + if ($self->{wanted_glyphs}{$gid}) { + $new_vals{$cp} = $self->{glyph_id_old_to_new}{$gid}; + } + } + $table->{val} = \%new_vals; + if ($table->{Format} == 0) { + @{$table->{val}}{0..255} = map { defined($_) ? $_ : 0 } @{$table->{val}}{0..255}; + } + } +} + +sub fix_head { + # TODO: Should think about: + # created + # modified + # xMin (depends on glyph data) + # yMin (depends on glyph data) + # xMax (depends on glyph data) + # yMax (depends on glyph data) +} + +sub fix_hhea { + # TODO: Should think about: + # advanceWidthMax (depends on hmtx) + # minLeftSideBearing (depends on hmtx) + # minRightSideBearing (depends on hmtx) + # xMaxExtent (depends on hmtx) +} + +sub fix_hmtx { + my ($self) = @_; + my $font = $self->{font}; + + # Map the advance/lsb arrays from old to new glyph ids + my @new_advances; + my @new_lsbs; + for my $gid (0..$font->{maxp}{numGlyphs}-1) { + push @new_advances, $font->{hmtx}{advance}[$self->{glyph_id_new_to_old}{$gid}]; + push @new_lsbs, $font->{hmtx}{lsb}[$self->{glyph_id_new_to_old}{$gid}]; + } + $font->{hmtx}{advance} = \@new_advances; + $font->{hmtx}{lsb} = \@new_lsbs; +} + +sub fix_maxp { # Must come after loca, prep, fpgm + my ($self) = @_; + my $font = $self->{font}; + + # Update some of the 'max' values that Font::TTF + # is capable of updating + $font->{maxp}->update; +} + +sub fix_os_2 { # Must come after cmap, hmtx, hhea, GPOS, GSUB + my ($self) = @_; + my $font = $self->{font}; + + # Update some of the metric values that Font::TTF + # is capable of updating + $font->{'OS/2'}->update; + + if ($font->{'OS/2'}{Version} >= 2) { + # TODO: handle cases where these are non-default + warn "Unexpected defaultChar $font->{'OS/2'}{defaultChar}\n" + unless $font->{'OS/2'}{defaultChar} == 0; + warn "Unexpected breakChar $font->{'OS/2'}{breakChar}\n" + unless $font->{'OS/2'}{breakChar} == 0x20; + } +} + +sub fix_post { + my ($self) = @_; + my $font = $self->{font}; + + if ($font->{post}{FormatType} == 0) { + warn "Invalid 'post' table type. (If you're using the obfuscate-font.pl script, make sure it comes *after* the subsetting.)\n"; + } + + # Update PostScript name mappings for new glyph ids + if ($font->{post}{VAL}) { + my @new_vals; + for my $gid (0..$font->{maxp}{numGlyphs}-1) { + push @new_vals, $font->{post}{VAL}[$self->{glyph_id_new_to_old}{$gid}]; + } + $font->{post}{VAL} = \@new_vals; + } +} + + + + +sub fix_loca { + my ($self) = @_; + my $font = $self->{font}; + + # remove_unwanted_glyphs has already removed some + # of the glyph data from this table + + # Update references inside composite glyphs + for my $glyph (@{$font->{loca}{glyphs}}) { + next unless $glyph; + $glyph->read; + next unless $glyph->{numberOfContours} == -1; + $glyph->read_dat; + for (@{$glyph->{comps}}) { + # (find_unwanted_glyphs guarantees that the + # component glyphs will be present) + $_->{glyph} = $self->{glyph_id_old_to_new}{$_->{glyph}}; + } + } +} + + + +sub fix_gdef { + my ($self) = @_; + my $font = $self->{font}; + + if ($font->{GDEF}{GLYPH}) { + $font->{GDEF}{GLYPH} = $self->update_classdef_table($font->{GDEF}{GLYPH}); + if ($self->empty_coverage($font->{GDEF}{GLYPH})) { + delete $font->{GDEF}{GLYPH}; + } + } + + if ($font->{GDEF}{MARKS}) { + $font->{GDEF}{MARKS} = $self->update_classdef_table($font->{GDEF}{MARKS}); + if ($self->empty_coverage($font->{GDEF}{MARKS})) { + delete $font->{GDEF}{MARKS}; + } + } + + if ($font->{GDEF}{ATTACH}) { + die "TODO" if $font->{GDEF}{ATTACH}{POINTS}; + $font->{GDEF}{ATTACH}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{ATTACH}{COVERAGE}); + if ($self->empty_coverage($font->{GDEF}{ATTACH}{COVERAGE})) { + delete $font->{GDEF}{ATTACH}; + } + } + + if ($font->{GDEF}{LIG}) { + + if ($font->{GDEF}{LIG}{LIGS}) { + die "GDEF LIG LIGS != COVERAGE" if + @{$font->{GDEF}{LIG}{LIGS}} != keys %{$font->{GDEF}{LIG}{COVERAGE}{val}}; + + my @coverage_map; + ($font->{GDEF}{LIG}{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($font->{GDEF}{LIG}{COVERAGE}); + $font->{GDEF}{LIG}{LIGS} = [ map $font->{GDEF}{LIG}{LIGS}[$_], @coverage_map ]; + + } else { + $font->{GDEF}{LIG}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{LIG}{COVERAGE}); + } + + if ($self->empty_coverage($font->{GDEF}{LIG}{COVERAGE})) { + delete $font->{GDEF}{LIG}; + } + } + +} + +sub fix_ttopen { + my ($self, $table, $inner) = @_; + + my @lookups; + my %lookup_map; + for my $lookup_id ($self->find_wanted_lookup_ids($table)) { + my $lookup = $table->{LOOKUP}[$lookup_id]; + my @subtables; + for my $sub (@{$lookup->{SUB}}) { + if ($inner->($lookup, $sub)) { + push @subtables, $sub; + } + } + + # Only keep lookups that have some subtables + if (@subtables) { + $lookup->{SUB} = \@subtables; + push @lookups, $lookup; + $lookup_map{$lookup_id} = $#lookups; + } + } + + $table->{LOOKUP} = \@lookups; + + # Update lookup references inside actions + for my $lookup (@{$table->{LOOKUP}}) { + for my $sub (@{$lookup->{SUB}}) { + if ($sub->{ACTION_TYPE} eq 'l') { + for my $rule (@{$sub->{RULES}}) { + for my $chain (@$rule) { + my @actions; + for my $action (@{$chain->{ACTION}}) { + my @steps; + for (0..@$action/2-1) { + # action is array of (offset, lookup) + # so just update the lookup + if (exists $lookup_map{$action->[$_*2+1]}) { + push @steps, ($action->[$_*2], $lookup_map{$action->[$_*2+1]}); + } + } + push @actions, \@steps; + } + $chain->{ACTION} = \@actions; + } + } + } + } + } + + # Remove all features that are not wanted + # and update all references to those features (in the languages list), + # and update the features' lookup references + + my @features; # array of [tag, feature] + my %kept_features; + for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) { + next unless $self->want_feature($self->{features}, $feat_tag); # drop unwanted features + my $feat = $table->{FEATURES}{$feat_tag}; + $feat->{LOOKUPS} = [ map { exists $lookup_map{$_} ? ($lookup_map{$_}) : () } @{$feat->{LOOKUPS}} ]; + next unless @{$feat->{LOOKUPS}}; # drop empty features to save some space + push @features, [ $feat_tag, $feat ]; + $kept_features{$feat_tag} = 1; + } + + $table->{FEATURES} = { + FEAT_TAGS => [map $_->[0], @features], + map +($_->[0] => $_->[1]), @features, + }; + + # Remove any references from scripts to features that no longer exist + for my $script_tag (keys %{$table->{SCRIPTS}}) { + my $script = $table->{SCRIPTS}{$script_tag}; + for my $tag ('DEFAULT', @{$script->{LANG_TAGS}}) { + next if $script->{$tag}{' REFTAG'}; # ignore langs that are just copies of another + $script->{$tag}{FEATURES} = [ + grep $kept_features{$_}, @{$script->{$tag}{FEATURES}} + ]; + + } + } + + # TODO: it'd be nice to delete languages that have no features + +} + +sub fix_gpos { + my ($self) = @_; + my $font = $self->{font}; + + $self->fix_ttopen($font->{GPOS}, + sub { + my ($lookup, $sub) = @_; + + # There's always a COVERAGE here first. + # (If it's empty, the client will skip the entire subtable, + # so we could delete it entirely, but that would involve updating + # the FEATURES->*->LOOKUPS lists too, so don't do that yet.) + # + # The rest depends on Type: + # + # Lookup Type 1 (Single Adjustment Positioning Subtable): + # Format 1: Just COVERAGE, applies same value to all + # Format 2: Just COVERAGE, RULES[n] gives value for each + # + # Lookup Type 2 (Pair Adjustment Positioning Subtable): + # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[0] gives second glyph + # Format 2: COVERAGE gives first glyph, CLASS gives first glyph class, MATCH[0] gives second glyph class + # + # Lookup Type 3 (Cursive Attachment Positioning Subtable): + # Format 1: Just COVERAGE, RULES[n] gives value for each + # + # Lookup Type 4 (MarkToBase Attachment Positioning Subtable): + # Format 1: MATCH[0] gives mark coverage, COVERAGE gives base coverage, MARKS[n] per mark, RULES[n] per base + # + # Lookup Type 5 (MarkToLigature Attachment Positioning Subtable): + # Format 1: pretty much the same as 4, but s/base/ligature/ + # + # Lookup Type 6 (MarkToMark Attachment Positioning Subtable): + # Format 1: pretty much the same as 4, but s/base/mark/ + # + # Lookup Type 7 (Contextual Positioning Subtables): + # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[o] gives next glyphs + # Format 2: COVERAGE gives first glyph, CLASS gives classes to glyphs, RULES[n] is per class + # Format 3: COVERAGE absent, RULES[0][0]{MATCH}[o] gives glyph coverages + # + # Lookup Type 8 (Chaining Contextual Positioning Subtable): + # Format 1: COVERAGE gives first glyph, RULES[n][m]{PRE/MATCH/POST} give context glyphs + # Format 2: COVERAGE gives first glyph, PRE_CLASS/CLASS/POST_CLASS give classes + # Format 3: COVERAGE absent, RULES[0][0]{PRE/MATCH/POST}[o] give coverages + # + # Lookup Type 9 (Extension Positioning): + # Not supported + + die if $lookup->{TYPE} >= 9; + + # Update the COVERAGE table, and remember some mapping + # information to update things that refer to the table + my @coverage_map; + my $old_coverage_count; + if ($sub->{COVERAGE}) { + $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}}; + ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE}); + + # If there's no coverage left, then drop this subtable + return 0 if $self->empty_coverage($sub->{COVERAGE}); + } + + if ($sub->{RULES} and $sub->{COVERAGE} and not + # Skip cases where RULES is indexed by CLASS, not COVERAGE + (($lookup->{TYPE} == 2 or + $lookup->{TYPE} == 7 or + $lookup->{TYPE} == 8) + and $sub->{FORMAT} == 2) + ) { + # There's a RULES array per COVERAGE entry, so + # shuffle them around to match the new COVERAGE + if (@{$sub->{RULES}} != $old_coverage_count) { + die "Internal error: RULES ($sub->{RULES}) does not match COVERAGE ($sub->{COVERAGE}) -- " + . @{$sub->{RULES}} . " vs $old_coverage_count."; + } + $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ]; + } + + if (not defined $sub->{MATCH_TYPE} or $sub->{MATCH_TYPE} eq 'g') { + if ($sub->{MATCH}) { + die unless @{$sub->{MATCH}} == 1; + die unless $sub->{MARKS}; + die unless @{$sub->{MARKS}} == keys %{$sub->{MATCH}[0]{val}}; + my @match_map; + ($sub->{MATCH}[0], @match_map) = $self->update_mapped_coverage_table($sub->{MATCH}[0]); + + # If there's no coverage left, then drop this subtable + return 0 if $self->empty_coverage($sub->{MATCH}[0]); + + # Update MARKS to correspond to the new MATCH coverage + $sub->{MARKS} = [ map $sub->{MARKS}[$_], @match_map ]; + } + + # RULES->MATCH is an array of glyphs, so translate them all + for (@{$sub->{RULES}}) { + for (@$_) { + $_->{MATCH} = [ map $self->{glyph_id_old_to_new}{$_}, + grep $self->{wanted_glyphs}{$_}, @{$_->{MATCH}} ]; + } + } + } elsif ($sub->{MATCH_TYPE}) { + if ($sub->{MATCH_TYPE} eq 'o') { + # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all + die unless @{$sub->{RULES}} == 1; + die unless @{$sub->{RULES}[0]} == 1; + my $r = $sub->{RULES}[0][0]; + for my $c (qw(MATCH PRE POST)) { + $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c}; + } + } elsif ($sub->{MATCH_TYPE} eq 'c') { + die "Didn't expect any rule matches" if grep $_->{MATCH}, map @$_, @{$sub->{RULES}}; + die unless @{$sub->{MATCH}} == 1; + + my $class_map; + ($sub->{CLASS}, undef, $class_map) = $self->update_mapped_classdef_table($sub->{CLASS}); + # Special case: If this results in an empty CLASS, it'll + # break in FF3.5 on Linux, so assign all the COVERAGE glyphs onto + # class 1 and update $class_map appropriately + if ($sub->{CLASS}{max} == 0) { + $sub->{CLASS} = new Font::TTF::Coverage(0, map +($_ => 1), keys %{$sub->{COVERAGE}{val}}); + $class_map = [0, 0]; # just duplicate class 0 into class 1 (this is a bit inefficient) + } + + $sub->{RULES} = [ @{$sub->{RULES}}[@$class_map] ]; + + # Update the MATCH classdef table + my $match_map; + ($sub->{MATCH}[0], undef, $match_map) = $self->update_mapped_classdef_table($sub->{MATCH}[0]); + + # If the MATCH table is now empty, drop this lookup + # (else FF3.5 on Linux drops the GPOS table entirely) + return 0 if @$match_map <= 1; + + # RULES[n] is a list of substitutions per MATCH class, so + # update all those lists for the new classdef + $sub->{RULES} = [ map { [ @{$_}[@$match_map] ] } @{$sub->{RULES}} ]; + + } else { + die "Invalid MATCH_TYPE"; + } + } + + if (($lookup->{TYPE} == 7 or + $lookup->{TYPE} == 8) + and $sub->{FORMAT} == 2) { + # Update some class tables + for my $c (qw(CLASS PRE_CLASS POST_CLASS)) { + $sub->{$c} = $self->update_classdef_table($sub->{$c}) if $sub->{$c}; + } + } + + return 1; + } + ); +} + +sub fix_gsub { + my ($self) = @_; + my $font = $self->{font}; + + $self->fix_ttopen($font->{GSUB}, + sub { + my ($lookup, $sub) = @_; + + # There's always a COVERAGE here first. + # (If it's empty, the client will skip the entire subtable, + # so we could delete it entirely, but that would involve updating + # the FEATURES->*->LOOKUPS lists and Contextual subtable indexes + # too, so don't do that yet.) + # + # The rest depends on Type: + # + # Lookup Type 1 (Single Substitution Subtable): + # Format 1: Just COVERAGE, and ADJUST gives glyph id delta + # Format 2: Just COVERAGE, then RULES[n]{ACTION}[0] gives replacement glyph for each + # + # Lookup Type 2 (Multiple Substitution Subtable): + # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives replacement glyphs (must be at least 1) + # + # Lookup Type 3 (Alternate Substitution Subtable): + # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives alternate glyphs + # [This can just be deleted since we have no way to use those glyphs] + # + # Lookup Type 4 (Ligature Substitution Subtable): + # Format 1: COVERAGE gives first glyph, RULES[n]{MATCH}[m] gives next glyphs to match, RULES[n]{ACTION}[0] gives replacement glyph + # + # Lookup Type 5 (Contextual Substitution Subtable): + # Format *: like type 7 in GPOS, but ACTION gives indexes into GSUB{LOOKUP} + # + # Lookup Type 6 (Chaining Contextual Substitution Subtable): + # Format *: like type 8 in GPOS, but ACTION gives indexes into GSUB{LOOKUP} + # + # Lookup Type 7 (Extension Substitution): + # Blah + + die if $lookup->{TYPE} >= 7; + + # Update the COVERAGE table, and remember some mapping + # information to update things that refer to the table + my @coverage_map; + my $old_coverage_count; + if ($sub->{COVERAGE}) { + $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}}; + ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE}); + + # If there's no coverage left, then drop this subtable + return 0 if $self->empty_coverage($sub->{COVERAGE}); + } + + if ($sub->{ACTION_TYPE} eq 'o') {; + my $adj = $sub->{ADJUST}; + if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727) + my @covs = $self->coverage_array($sub->{COVERAGE}); + if (@covs == 0) { + # Nothing's covered, but deleting this whole subtable is + # non-trivial so just zero it out + $sub->{ADJUST} = 0; + } elsif (@covs == 1) { + my $gid_base = $covs[0]; + my $old_gid_base = $self->{glyph_id_new_to_old}{$gid_base}; + my $old_gid = $old_gid_base + $adj; + $sub->{ADJUST} = $self->{glyph_id_old_to_new}{$old_gid} - $gid_base; + } else { + # The glyphs are probably all reordered, so we can't just + # adjust ADJUST. + # So switch this to a format 2 table: + $sub->{FORMAT} = 2; + $sub->{ACTION_TYPE} = 'g'; + delete $sub->{ADJUST}; + my @gids; + for (@covs) { + push @gids, $self->{glyph_id_old_to_new}{$self->{glyph_id_new_to_old}{$_} + $adj}; + } + $sub->{RULES} = [ map [{ACTION => [$_]}], @gids ]; + } + # Stop and keep this table, since done everything that's needed + return 1; + } + die if $sub->{ADJUST}; + + if ($sub->{RULES} and not + # Skip cases where RULES is indexed by CLASS, not COVERAGE, + # and cases where there's no COVERAGE at all + (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6) + and ($sub->{FORMAT} == 2 or $sub->{FORMAT} == 3)) + ) { + # There's a RULES array per COVERAGE entry, so + # shuffle them around to match the new COVERAGE + die unless @{$sub->{RULES}} == $old_coverage_count; + $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ]; + } + + # TODO: refactor + if ($sub->{MATCH_TYPE}) { + # Fix all the glyph indexes + if ($sub->{MATCH_TYPE} eq 'g') { + # RULES->MATCH/PRE/POST are arrays of glyphs, so translate them all, + # and if they rely on any unwanted glyphs then drop the rule entirely + for my $i (0..$#{$sub->{RULES}}) { + my $ruleset = $sub->{RULES}[$i]; + my @rules; + RULE: for my $rule (@$ruleset) { + for my $c (qw(MATCH PRE POST)) { + next unless $rule->{$c}; + next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}}; + $rule->{$c} = [ map $self->{glyph_id_old_to_new}{$_}, @{$rule->{$c}} ] + } + push @rules, $rule; + } + if (not @rules) { + # XXX: This is a really horrid hack. + # The proper solution is to delete the ruleset, + # and adjust COVERAGE to match. + push @rules, { ACTION => [0], MATCH => [-1] }; + } + $sub->{RULES}[$i] = \@rules; + } + } elsif ($sub->{MATCH_TYPE} eq 'o') { + # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all + die unless @{$sub->{RULES}} == 1; + die unless @{$sub->{RULES}[0]} == 1; + my $r = $sub->{RULES}[0][0]; + for my $c (qw(MATCH PRE POST)) { + $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c}; + } + } elsif ($sub->{MATCH_TYPE} eq 'c') { + # RULES refers to class values, which haven't changed at all, + # so we don't need to update those values + } else { + die "Invalid MATCH_TYPE"; + } + } + + my %class_maps; + for my $c (qw(CLASS PRE_CLASS POST_CLASS)) { + ($sub->{$c}, $class_maps{$c}) = $self->update_mapped_classdef_table($sub->{$c}) if $sub->{$c}; + } + + + if ($sub->{MATCH_TYPE} and $sub->{MATCH_TYPE} eq 'c') { + # To make things work in Pango, we need to change all the + # class numbers so there aren't gaps: + my %classes = ( + MATCH => 'CLASS', + PRE => 'PRE_CLASS', + POST => 'POST_CLASS', + ); + my @rules; + for my $rule (@{$sub->{RULES}}) { + my @chains; + CHAIN: for my $chain (@$rule) { + for my $c (qw(MATCH PRE POST)) { + next unless $chain->{$c}; + my $map = $class_maps{$classes{$c}} or die "Got a $c but no $classes{$c}"; + # If any of the values are for a class that no longer has + # any entries, we should drop this whole chain because + # there's no chance it's going to match + next CHAIN if grep { not defined $map->[$_] } @{$chain->{$c}}; + # Otherwise just update the class numbers + $chain->{$c} = [ map $map->[$_], @{$chain->{$c}} ]; + } + push @chains, $chain; + } + push @rules, \@chains; + } + $sub->{RULES} = \@rules; + # If all the rules are empty, drop this whole subtable (which maybe is + # needed to avoid https://bugzilla.mozilla.org/show_bug.cgi?id=475242 ?) + return 0 if not grep @$_, @{$sub->{RULES}}; + } + + if ($sub->{ACTION_TYPE}) { + if ($sub->{ACTION_TYPE} eq 'g') { + for (@{$sub->{RULES}}) { + for (@$_) { + $_->{ACTION} = [ map $self->{glyph_id_old_to_new}{$_}, + grep $self->{wanted_glyphs}{$_}, @{$_->{ACTION}} ]; + } + } + } elsif ($sub->{ACTION_TYPE} eq 'l') { + # nothing to change here + } elsif ($sub->{ACTION_TYPE} eq 'a') { + # We don't want to bother with alternate glyphs at all, + # so just delete everything. + # (We need to have empty rules, and can't just delete them + # entirely, else FontTools becomes unhappy.) + # (TODO: Maybe we do want alternate glyphs? + # If so, be sure to update find_wanted_glyphs too) + for (@{$sub->{RULES}}) { + for (@$_) { + $_->{ACTION} = []; + } + } + } elsif ($sub->{ACTION_TYPE} eq 'o') { + die "Should have handled ACTION_TYPE o earlier"; + } else { + die "Invalid ACTION_TYPE"; + } + } + + return 1; + } + ); +} + +# Fold certain GSUB features into the cmap table +sub fold_gsub { + my ($self, $features) = @_; + + my $font = $self->{font}; + my $table = $font->{GSUB}; + + # Find the lookup IDs corresponding to the desired features + + my %wanted = (DEFAULT => 0); + $wanted{$_} = 1 for @$features; + + my %lookups; + for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) { + next if not $self->want_feature(\%wanted, $feat_tag); + for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) { + $lookups{$_} = $feat_tag; + } + } + + # Find the glyph mapping from those lookups + + my %glyph_map; # (old glyph id => new glyph id) + + for my $lookup_id (0..$#{$table->{LOOKUP}}) { + next unless exists $lookups{$lookup_id}; + my $lookup = $table->{LOOKUP}[$lookup_id]; + if ($lookup->{TYPE} != 1) { + warn "GSUB lookup $lookup_id (from feature '$lookups{$lookup_id}') is not a 'single' type lookup (type=$lookup->{TYPE}), and cannot be applied.\n"; + next; + } + + # For each glyph, only the first substitution per lookup is applied, + # so we build a map of the firsts for this lookup (then fold it into + # the global map later) + my %lookup_glyph_map; + + for my $sub (@{$lookup->{SUB}}) { + my @covs = $self->coverage_array($sub->{COVERAGE}); + if ($sub->{ACTION_TYPE} eq 'o') { + my $adj = $sub->{ADJUST}; + if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727) + for my $i (0..$#covs) { + my $old = $covs[$i]; + my $new = $old + $adj; + $lookup_glyph_map{$old} = $new if not exists $lookup_glyph_map{$old}; + } + } elsif ($sub->{ACTION_TYPE} eq 'g') { + next if @covs == 0 and not $sub->{RULES}; + die unless @{$sub->{RULES}} == @covs; + for my $i (0..$#covs) { + my $old = $covs[$i]; + die unless @{$sub->{RULES}[$i]} == 1; + die unless @{$sub->{RULES}[$i][0]{ACTION}} == 1; + my $new = $sub->{RULES}[$i][0]{ACTION}[0]; + $lookup_glyph_map{$old} = $new; + } + } else { + die "Invalid ACTION_TYPE $sub->{ACTION_TYPE}"; + } + } + + # Fold the lookup's glyph map into the global glyph map + for my $gid (keys %lookup_glyph_map) { + # Add any new substitutions + $glyph_map{$gid} = $lookup_glyph_map{$gid} if not exists $glyph_map{$gid}; + } + for my $gid (keys %glyph_map) { + # Handle chained substitutions + $glyph_map{$gid} = $lookup_glyph_map{$glyph_map{$gid}} if exists $lookup_glyph_map{$glyph_map{$gid}}; + } + } + + # Apply the glyph mapping to cmap + + for my $table (@{$font->{cmap}{Tables}}) { + for my $cp (keys %{$table->{val}}) { + my $gid = $table->{val}{$cp}; + $table->{val}{$cp} = $glyph_map{$gid} if exists $glyph_map{$gid}; + } + } +} + +sub fix_hdmx { + my ($self) = @_; + my $font = $self->{font}; + + for my $ppem (grep /^\d+$/, keys %{$font->{hdmx}}) { + my @new_widths; + for my $gid (0..$font->{maxp}{numGlyphs}-1) { + push @new_widths, $font->{hdmx}{$ppem}[$self->{glyph_id_new_to_old}{$gid}]; + } + $font->{hdmx}{$ppem} = \@new_widths; + } +} + +sub fix_kern { + my ($self) = @_; + my $font = $self->{font}; + + # We don't handle version 1 kern tables yet, so just drop them entirely. + # http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html + # https://bugzilla.mozilla.org/show_bug.cgi?id=487549 + if ($font->{kern}{Version} != 0) { + warn "Unhandled kern table version $font->{kern}{Version} - deleting all kerning data\n"; + delete $font->{kern}; + return; + } + + for my $table (@{$font->{kern}{tables}}) { + if ($table->{type} == 0) { + my %kern; + for my $l (keys %{$table->{kern}}) { + next unless $self->{wanted_glyphs}{$l}; + for my $r (keys %{$table->{kern}{$l}}) { + next unless $self->{wanted_glyphs}{$r}; + $kern{$self->{glyph_id_old_to_new}{$l}}{$self->{glyph_id_old_to_new}{$r}} = $table->{kern}{$l}{$r}; + } + } + $table->{kern} = \%kern; + } elsif ($table->{type} == 2) { + die "kern table type 2 not supported yet"; + } else { + die "Invalid kern table type"; + } + } +} + +sub fix_ltsh { + my ($self) = @_; + my $font = $self->{font}; + + my @glyphs; + for my $gid (0..$font->{maxp}{numGlyphs}-1) { + push @glyphs, $font->{LTSH}{glyphs}[$self->{glyph_id_new_to_old}{$gid}]; + } + $font->{LTSH}{glyphs} = \@glyphs; +} + +sub delete_copyright { + my ($self) = @_; + my $font = $self->{font}; + # XXX - shouldn't be deleting copyright text + $font->{name}{strings}[0] = undef; + $font->{name}{strings}[10] = undef; + $font->{name}{strings}[13] = undef; +} + +sub change_name { + my ($self, $uid) = @_; + my $font = $self->{font}; + + for (1,3,4,6) { + my $str = $font->{name}{strings}[$_]; + for my $plat (0..$#$str) { + next unless $str->[$plat]; + for my $enc (0..$#{$str->[$plat]}) { + next unless $str->[$plat][$enc]; + for my $lang (keys %{$str->[$plat][$enc]}) { + next unless exists $str->[$plat][$enc]{$lang}; + $str->[$plat][$enc]{$lang} = "$uid - subset of " . $str->[$plat][$enc]{$lang}; + } + } + } + } +} + +sub license_desc_subst { + my ($self, $new) = @_; + my $font = $self->{font}; + + my $str = $font->{name}{strings}[13]; + for my $plat (0..$#$str) { + next unless $str->[$plat]; + for my $enc (0..$#{$str->[$plat]}) { + next unless $str->[$plat][$enc]; + for my $lang (keys %{$str->[$plat][$enc]}) { + next unless exists $str->[$plat][$enc]{$lang}; + $str->[$plat][$enc]{$lang} =~ s/\$\{LICENSESUBST\}/$new/g; + } + } + } +} + +# IE silently rejects non-CFF fonts if the Font Family Name is not a prefix of +# the Full Font Name. This can occur when automatically converting CFF fonts +# to non-CFF fonts, so it's useful to check and fix it here. +sub fix_full_font_name { + my ($self, $new) = @_; + my $font = $self->{font}; + + my $str1 = $font->{name}{strings}[1]; + for my $plat (0..$#$str1) { + next unless $str1->[$plat]; + for my $enc (0..$#{$str1->[$plat]}) { + next unless $str1->[$plat][$enc]; + for my $lang (keys %{$str1->[$plat][$enc]}) { + next unless exists $str1->[$plat][$enc]{$lang}; + my $name = $str1->[$plat][$enc]{$lang}; + my $fullname = $font->{name}{strings}[4][$plat][$enc]{$lang}; + if (substr($fullname, 0, length $name) ne $name) { + warn "Full Name ('$fullname') does not start with Family Name ('$name') and will break in IE - fixing automatically\n"; + $font->{name}{strings}[4][$plat][$enc]{$lang} = $name; + } + } + } + } +} + +sub new { + my $class = shift; + my $self = {}; + bless $self, $class; + return $self; +} + +sub preload { + my ($self, $filename) = @_; + my $font = Font::TTF::Font->open($filename) or die "Failed to open $filename: $!"; + $self->{font} = $font; + $self->read_tables; +} + +sub subset { + my ($self, $filename, $chars, $options) = @_; + + $self->{features} = $options->{features}; + + my $uid = substr(sha1_hex("$filename $chars"), 0, 16); + + if (not $self->{font}) { + $self->preload($filename); + } + + my $font = $self->{font}; + + $self->check_tables; + + $self->{num_glyphs_old} = $font->{maxp}{numGlyphs}; + + $self->fold_gsub($options->{fold_features}) + if $options->{fold_features}; + + my $fsType = $font->{'OS/2'}{fsType}; + warn "fsType is $fsType - subsetting and embedding might not be permitted by the license\n" if $fsType != 0; + + $self->strip_cmap; + $self->strip_name; + + $self->find_codepoint_glyph_mappings; + $self->find_wanted_glyphs($chars); + $self->remove_unwanted_glyphs; + + $self->fix_cmap; + $self->fix_head; + $self->fix_hhea; + $self->fix_hmtx; + # name: nothing to fix (though maybe could be optimised?) + $self->fix_post; + + # cvt_: nothing to fix + # fpgm: nothing to fix + # glyf: just a stub, in Font::TTF + $self->fix_loca; + # prep: nothing to fix + + # BASE: TODO + $self->fix_gdef if $font->{GDEF}; + $self->fix_gpos if $font->{GPOS}; + $self->fix_gsub if $font->{GSUB}; + # JSTF: TODO + + $self->fix_hdmx if $font->{hdmx}; + $self->fix_kern if $font->{kern}; + $self->fix_ltsh if $font->{LTSH}; + + $self->fix_maxp; # Must come after loca, prep, fpgm + $self->fix_os_2; # Must come after cmap, hmtx, hhea, GPOS, GSUB + + $self->fix_full_font_name; + + $self->change_name($uid); + + $self->license_desc_subst($options->{license_desc_subst}) + if defined $options->{license_desc_subst}; + + $self->{num_glyphs_new} = $font->{maxp}{numGlyphs}; +} + +sub num_glyphs_old { + my ($self) = @_; + return $self->{num_glyphs_old}; +} + +sub num_glyphs_new { + my ($self) = @_; + return $self->{num_glyphs_new}; +} + +sub glyph_names { + my ($self) = @_; + my $font = $self->{font}; + if (@{$font->{post}{VAL}}) { + return @{$font->{post}{VAL}}; + } + my $n = $#{$font->{loca}{glyphs}}; + return join ' ', map { chr($_) =~ /[a-zA-Z0-9- \|]/ ? "'".chr($_)."'" : sprintf 'U+%04x', $_ } map { keys %{$self->{glyphs}{$_}} } + map $self->{glyph_id_new_to_old}{$_}, 0..$n; +} + +sub feature_status { + my ($self) = @_; + my $font = $self->{font}; + my %feats; + my @feats; + for my $table (grep defined, $font->{GPOS}, $font->{GSUB}) { + for my $feature (@{$table->{FEATURES}{FEAT_TAGS}}) { + $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'"; + my $tag = $1; + next if $feats{$tag}++; + push @feats, $tag; + } + } + return @feats; +} + +sub write { + my ($self, $fh) = @_; + my $font = $self->{font}; + $font->out($fh) or die $!; +} + +sub release { + my ($self) = @_; + my $font = $self->{font}; + $font->release; +} + +1; diff --git a/font-optimizer/Font/Subsetter/NormalizationData.pm b/font-optimizer/Font/Subsetter/NormalizationData.pm new file mode 100644 index 0000000..dca81d6 --- /dev/null +++ b/font-optimizer/Font/Subsetter/NormalizationData.pm @@ -0,0 +1,1377 @@ +package Font::Subsetter::NormalizationData; +use strict; +use warnings; +our @data = ( +[192,65,768], +[193,65,769], +[194,65,770], +[195,65,771], +[196,65,776], +[197,65,778], +[199,67,807], +[200,69,768], +[201,69,769], +[202,69,770], +[203,69,776], +[204,73,768], +[205,73,769], +[206,73,770], +[207,73,776], +[209,78,771], +[210,79,768], +[211,79,769], +[212,79,770], +[213,79,771], +[214,79,776], +[217,85,768], +[218,85,769], +[219,85,770], +[220,85,776], +[221,89,769], +[224,97,768], +[225,97,769], +[226,97,770], +[227,97,771], +[228,97,776], +[229,97,778], +[231,99,807], +[232,101,768], +[233,101,769], +[234,101,770], +[235,101,776], +[236,105,768], +[237,105,769], +[238,105,770], +[239,105,776], +[241,110,771], +[242,111,768], +[243,111,769], +[244,111,770], +[245,111,771], +[246,111,776], +[249,117,768], +[250,117,769], +[251,117,770], +[252,117,776], +[253,121,769], +[255,121,776], +[256,65,772], +[257,97,772], +[258,65,774], +[259,97,774], +[260,65,808], +[261,97,808], +[262,67,769], +[263,99,769], +[264,67,770], +[265,99,770], +[266,67,775], +[267,99,775], +[268,67,780], +[269,99,780], +[270,68,780], +[271,100,780], +[274,69,772], +[275,101,772], +[276,69,774], +[277,101,774], +[278,69,775], +[279,101,775], +[280,69,808], +[281,101,808], +[282,69,780], +[283,101,780], +[284,71,770], +[285,103,770], +[286,71,774], +[287,103,774], +[288,71,775], +[289,103,775], +[290,71,807], +[291,103,807], +[292,72,770], +[293,104,770], +[296,73,771], +[297,105,771], +[298,73,772], +[299,105,772], +[300,73,774], +[301,105,774], +[302,73,808], +[303,105,808], +[304,73,775], +[308,74,770], +[309,106,770], +[310,75,807], +[311,107,807], +[313,76,769], +[314,108,769], +[315,76,807], +[316,108,807], +[317,76,780], +[318,108,780], +[323,78,769], +[324,110,769], +[325,78,807], +[326,110,807], +[327,78,780], +[328,110,780], +[332,79,772], +[333,111,772], +[334,79,774], +[335,111,774], +[336,79,779], +[337,111,779], +[340,82,769], +[341,114,769], +[342,82,807], +[343,114,807], +[344,82,780], +[345,114,780], +[346,83,769], +[347,115,769], +[348,83,770], +[349,115,770], +[350,83,807], +[351,115,807], +[352,83,780], +[353,115,780], +[354,84,807], +[355,116,807], +[356,84,780], +[357,116,780], +[360,85,771], +[361,117,771], +[362,85,772], +[363,117,772], +[364,85,774], +[365,117,774], +[366,85,778], +[367,117,778], +[368,85,779], +[369,117,779], +[370,85,808], +[371,117,808], +[372,87,770], +[373,119,770], +[374,89,770], +[375,121,770], +[376,89,776], +[377,90,769], +[378,122,769], +[379,90,775], +[380,122,775], +[381,90,780], +[382,122,780], +[416,79,795], +[417,111,795], +[431,85,795], +[432,117,795], +[461,65,780], +[462,97,780], +[463,73,780], +[464,105,780], +[465,79,780], +[466,111,780], +[467,85,780], +[468,117,780], +[469,85,776,772], +[469,220,772], +[470,117,776,772], +[470,252,772], +[471,85,776,769], +[471,220,769], +[472,117,776,769], +[472,252,769], +[473,85,776,780], +[473,220,780], +[474,117,776,780], +[474,252,780], +[475,85,776,768], +[475,220,768], +[476,117,776,768], +[476,252,768], +[478,65,776,772], +[478,196,772], +[479,97,776,772], +[479,228,772], +[480,65,775,772], +[480,550,772], +[481,97,775,772], +[481,551,772], +[482,198,772], +[483,230,772], +[486,71,780], +[487,103,780], +[488,75,780], +[489,107,780], +[490,79,808], +[491,111,808], +[492,79,808,772], +[492,332,808], +[492,490,772], +[493,111,808,772], +[493,333,808], +[493,491,772], +[494,439,780], +[495,658,780], +[496,106,780], +[500,71,769], +[501,103,769], +[504,78,768], +[505,110,768], +[506,65,778,769], +[506,197,769], +[507,97,778,769], +[507,229,769], +[508,198,769], +[509,230,769], +[510,216,769], +[511,248,769], +[512,65,783], +[513,97,783], +[514,65,785], +[515,97,785], +[516,69,783], +[517,101,783], +[518,69,785], +[519,101,785], +[520,73,783], +[521,105,783], +[522,73,785], +[523,105,785], +[524,79,783], +[525,111,783], +[526,79,785], +[527,111,785], +[528,82,783], +[529,114,783], +[530,82,785], +[531,114,785], +[532,85,783], +[533,117,783], +[534,85,785], +[535,117,785], +[536,83,806], +[537,115,806], +[538,84,806], +[539,116,806], +[542,72,780], +[543,104,780], +[550,65,775], +[551,97,775], +[552,69,807], +[553,101,807], +[554,79,776,772], +[554,214,772], +[555,111,776,772], +[555,246,772], +[556,79,771,772], +[556,213,772], +[557,111,771,772], +[557,245,772], +[558,79,775], +[559,111,775], +[560,79,775,772], +[560,558,772], +[561,111,775,772], +[561,559,772], +[562,89,772], +[563,121,772], +[901,168,769], +[902,913,769], +[904,917,769], +[905,919,769], +[906,921,769], +[908,927,769], +[910,933,769], +[911,937,769], +[912,953,776,769], +[912,970,769], +[938,921,776], +[939,933,776], +[940,945,769], +[941,949,769], +[942,951,769], +[943,953,769], +[944,965,776,769], +[944,971,769], +[970,953,776], +[971,965,776], +[972,959,769], +[973,965,769], +[974,969,769], +[979,978,769], +[980,978,776], +[1024,1045,768], +[1025,1045,776], +[1027,1043,769], +[1031,1030,776], +[1036,1050,769], +[1037,1048,768], +[1038,1059,774], +[1049,1048,774], +[1081,1080,774], +[1104,1077,768], +[1105,1077,776], +[1107,1075,769], +[1111,1110,776], +[1116,1082,769], +[1117,1080,768], +[1118,1091,774], +[1142,1140,783], +[1143,1141,783], +[1217,1046,774], +[1218,1078,774], +[1232,1040,774], +[1233,1072,774], +[1234,1040,776], +[1235,1072,776], +[1238,1045,774], +[1239,1077,774], +[1242,1240,776], +[1243,1241,776], +[1244,1046,776], +[1245,1078,776], +[1246,1047,776], +[1247,1079,776], +[1250,1048,772], +[1251,1080,772], +[1252,1048,776], +[1253,1080,776], +[1254,1054,776], +[1255,1086,776], +[1258,1256,776], +[1259,1257,776], +[1260,1069,776], +[1261,1101,776], +[1262,1059,772], +[1263,1091,772], +[1264,1059,776], +[1265,1091,776], +[1266,1059,779], +[1267,1091,779], +[1268,1063,776], +[1269,1095,776], +[1272,1067,776], +[1273,1099,776], +[1570,1575,1619], +[1571,1575,1620], +[1572,1608,1620], +[1573,1575,1621], +[1574,1610,1620], +[1728,1749,1620], +[1730,1729,1620], +[1747,1746,1620], +[2345,2344,2364], +[2353,2352,2364], +[2356,2355,2364], +[2507,2503,2494], +[2508,2503,2519], +[2888,2887,2902], +[2891,2887,2878], +[2892,2887,2903], +[2964,2962,3031], +[3018,3014,3006], +[3019,3015,3006], +[3020,3014,3031], +[3144,3142,3158], +[3264,3263,3285], +[3271,3270,3285], +[3272,3270,3286], +[3274,3270,3266], +[3275,3270,3266,3285], +[3275,3274,3285], +[3402,3398,3390], +[3403,3399,3390], +[3404,3398,3415], +[3546,3545,3530], +[3548,3545,3535], +[3549,3545,3535,3530], +[3549,3548,3530], +[3550,3545,3551], +[4134,4133,4142], +[7680,65,805], +[7681,97,805], +[7682,66,775], +[7683,98,775], +[7684,66,803], +[7685,98,803], +[7686,66,817], +[7687,98,817], +[7688,67,807,769], +[7688,262,807], +[7688,199,769], +[7689,99,807,769], +[7689,263,807], +[7689,231,769], +[7690,68,775], +[7691,100,775], +[7692,68,803], +[7693,100,803], +[7694,68,817], +[7695,100,817], +[7696,68,807], +[7697,100,807], +[7698,68,813], +[7699,100,813], +[7700,69,772,768], +[7700,274,768], +[7701,101,772,768], +[7701,275,768], +[7702,69,772,769], +[7702,274,769], +[7703,101,772,769], +[7703,275,769], +[7704,69,813], +[7705,101,813], +[7706,69,816], +[7707,101,816], +[7708,69,807,774], +[7708,276,807], +[7708,552,774], +[7709,101,807,774], +[7709,277,807], +[7709,553,774], +[7710,70,775], +[7711,102,775], +[7712,71,772], +[7713,103,772], +[7714,72,775], +[7715,104,775], +[7716,72,803], +[7717,104,803], +[7718,72,776], +[7719,104,776], +[7720,72,807], +[7721,104,807], +[7722,72,814], +[7723,104,814], +[7724,73,816], +[7725,105,816], +[7726,73,776,769], +[7726,207,769], +[7727,105,776,769], +[7727,239,769], +[7728,75,769], +[7729,107,769], +[7730,75,803], +[7731,107,803], +[7732,75,817], +[7733,107,817], +[7734,76,803], +[7735,108,803], +[7736,76,803,772], +[7736,7734,772], +[7737,108,803,772], +[7737,7735,772], +[7738,76,817], +[7739,108,817], +[7740,76,813], +[7741,108,813], +[7742,77,769], +[7743,109,769], +[7744,77,775], +[7745,109,775], +[7746,77,803], +[7747,109,803], +[7748,78,775], +[7749,110,775], +[7750,78,803], +[7751,110,803], +[7752,78,817], +[7753,110,817], +[7754,78,813], +[7755,110,813], +[7756,79,771,769], +[7756,213,769], +[7757,111,771,769], +[7757,245,769], +[7758,79,771,776], +[7758,213,776], +[7759,111,771,776], +[7759,245,776], +[7760,79,772,768], +[7760,332,768], +[7761,111,772,768], +[7761,333,768], +[7762,79,772,769], +[7762,332,769], +[7763,111,772,769], +[7763,333,769], +[7764,80,769], +[7765,112,769], +[7766,80,775], +[7767,112,775], +[7768,82,775], +[7769,114,775], +[7770,82,803], +[7771,114,803], +[7772,82,803,772], +[7772,7770,772], +[7773,114,803,772], +[7773,7771,772], +[7774,82,817], +[7775,114,817], +[7776,83,775], +[7777,115,775], +[7778,83,803], +[7779,115,803], +[7780,83,769,775], +[7780,346,775], +[7781,115,769,775], +[7781,347,775], +[7782,83,780,775], +[7782,352,775], +[7783,115,780,775], +[7783,353,775], +[7784,83,803,775], +[7784,7776,803], +[7784,7778,775], +[7785,115,803,775], +[7785,7777,803], +[7785,7779,775], +[7786,84,775], +[7787,116,775], +[7788,84,803], +[7789,116,803], +[7790,84,817], +[7791,116,817], +[7792,84,813], +[7793,116,813], +[7794,85,804], +[7795,117,804], +[7796,85,816], +[7797,117,816], +[7798,85,813], +[7799,117,813], +[7800,85,771,769], +[7800,360,769], +[7801,117,771,769], +[7801,361,769], +[7802,85,772,776], +[7802,362,776], +[7803,117,772,776], +[7803,363,776], +[7804,86,771], +[7805,118,771], +[7806,86,803], +[7807,118,803], +[7808,87,768], +[7809,119,768], +[7810,87,769], +[7811,119,769], +[7812,87,776], +[7813,119,776], +[7814,87,775], +[7815,119,775], +[7816,87,803], +[7817,119,803], +[7818,88,775], +[7819,120,775], +[7820,88,776], +[7821,120,776], +[7822,89,775], +[7823,121,775], +[7824,90,770], +[7825,122,770], +[7826,90,803], +[7827,122,803], +[7828,90,817], +[7829,122,817], +[7830,104,817], +[7831,116,776], +[7832,119,778], +[7833,121,778], +[7835,383,775], +[7840,65,803], +[7841,97,803], +[7842,65,777], +[7843,97,777], +[7844,65,770,769], +[7844,194,769], +[7845,97,770,769], +[7845,226,769], +[7846,65,770,768], +[7846,194,768], +[7847,97,770,768], +[7847,226,768], +[7848,65,770,777], +[7848,194,777], +[7849,97,770,777], +[7849,226,777], +[7850,65,770,771], +[7850,194,771], +[7851,97,770,771], +[7851,226,771], +[7852,65,803,770], +[7852,194,803], +[7852,7840,770], +[7853,97,803,770], +[7853,226,803], +[7853,7841,770], +[7854,65,774,769], +[7854,258,769], +[7855,97,774,769], +[7855,259,769], +[7856,65,774,768], +[7856,258,768], +[7857,97,774,768], +[7857,259,768], +[7858,65,774,777], +[7858,258,777], +[7859,97,774,777], +[7859,259,777], +[7860,65,774,771], +[7860,258,771], +[7861,97,774,771], +[7861,259,771], +[7862,65,803,774], +[7862,258,803], +[7862,7840,774], +[7863,97,803,774], +[7863,259,803], +[7863,7841,774], +[7864,69,803], +[7865,101,803], +[7866,69,777], +[7867,101,777], +[7868,69,771], +[7869,101,771], +[7870,69,770,769], +[7870,202,769], +[7871,101,770,769], +[7871,234,769], +[7872,69,770,768], +[7872,202,768], +[7873,101,770,768], +[7873,234,768], +[7874,69,770,777], +[7874,202,777], +[7875,101,770,777], +[7875,234,777], +[7876,69,770,771], +[7876,202,771], +[7877,101,770,771], +[7877,234,771], +[7878,69,803,770], +[7878,202,803], +[7878,7864,770], +[7879,101,803,770], +[7879,234,803], +[7879,7865,770], +[7880,73,777], +[7881,105,777], +[7882,73,803], +[7883,105,803], +[7884,79,803], +[7885,111,803], +[7886,79,777], +[7887,111,777], +[7888,79,770,769], +[7888,212,769], +[7889,111,770,769], +[7889,244,769], +[7890,79,770,768], +[7890,212,768], +[7891,111,770,768], +[7891,244,768], +[7892,79,770,777], +[7892,212,777], +[7893,111,770,777], +[7893,244,777], +[7894,79,770,771], +[7894,212,771], +[7895,111,770,771], +[7895,244,771], +[7896,79,803,770], +[7896,212,803], +[7896,7884,770], +[7897,111,803,770], +[7897,244,803], +[7897,7885,770], +[7898,79,795,769], +[7898,211,795], +[7898,416,769], +[7899,111,795,769], +[7899,243,795], +[7899,417,769], +[7900,79,795,768], +[7900,210,795], +[7900,416,768], +[7901,111,795,768], +[7901,242,795], +[7901,417,768], +[7902,79,795,777], +[7902,7886,795], +[7902,416,777], +[7903,111,795,777], +[7903,7887,795], +[7903,417,777], +[7904,79,795,771], +[7904,213,795], +[7904,416,771], +[7905,111,795,771], +[7905,245,795], +[7905,417,771], +[7906,79,795,803], +[7906,7884,795], +[7906,416,803], +[7907,111,795,803], +[7907,7885,795], +[7907,417,803], +[7908,85,803], +[7909,117,803], +[7910,85,777], +[7911,117,777], +[7912,85,795,769], +[7912,218,795], +[7912,431,769], +[7913,117,795,769], +[7913,250,795], +[7913,432,769], +[7914,85,795,768], +[7914,217,795], +[7914,431,768], +[7915,117,795,768], +[7915,249,795], +[7915,432,768], +[7916,85,795,777], +[7916,7910,795], +[7916,431,777], +[7917,117,795,777], +[7917,7911,795], +[7917,432,777], +[7918,85,795,771], +[7918,360,795], +[7918,431,771], +[7919,117,795,771], +[7919,361,795], +[7919,432,771], +[7920,85,795,803], +[7920,7908,795], +[7920,431,803], +[7921,117,795,803], +[7921,7909,795], +[7921,432,803], +[7922,89,768], +[7923,121,768], +[7924,89,803], +[7925,121,803], +[7926,89,777], +[7927,121,777], +[7928,89,771], +[7929,121,771], +[7936,945,787], +[7937,945,788], +[7938,945,787,768], +[7938,7936,768], +[7939,945,788,768], +[7939,7937,768], +[7940,945,787,769], +[7940,7936,769], +[7941,945,788,769], +[7941,7937,769], +[7942,945,787,834], +[7942,7936,834], +[7943,945,788,834], +[7943,7937,834], +[7944,913,787], +[7945,913,788], +[7946,913,787,768], +[7946,7944,768], +[7947,913,788,768], +[7947,7945,768], +[7948,913,787,769], +[7948,7944,769], +[7949,913,788,769], +[7949,7945,769], +[7950,913,787,834], +[7950,7944,834], +[7951,913,788,834], +[7951,7945,834], +[7952,949,787], +[7953,949,788], +[7954,949,787,768], +[7954,7952,768], +[7955,949,788,768], +[7955,7953,768], +[7956,949,787,769], +[7956,7952,769], +[7957,949,788,769], +[7957,7953,769], +[7960,917,787], +[7961,917,788], +[7962,917,787,768], +[7962,7960,768], +[7963,917,788,768], +[7963,7961,768], +[7964,917,787,769], +[7964,7960,769], +[7965,917,788,769], +[7965,7961,769], +[7968,951,787], +[7969,951,788], +[7970,951,787,768], +[7970,7968,768], +[7971,951,788,768], +[7971,7969,768], +[7972,951,787,769], +[7972,7968,769], +[7973,951,788,769], +[7973,7969,769], +[7974,951,787,834], +[7974,7968,834], +[7975,951,788,834], +[7975,7969,834], +[7976,919,787], +[7977,919,788], +[7978,919,787,768], +[7978,7976,768], +[7979,919,788,768], +[7979,7977,768], +[7980,919,787,769], +[7980,7976,769], +[7981,919,788,769], +[7981,7977,769], +[7982,919,787,834], +[7982,7976,834], +[7983,919,788,834], +[7983,7977,834], +[7984,953,787], +[7985,953,788], +[7986,953,787,768], +[7986,7984,768], +[7987,953,788,768], +[7987,7985,768], +[7988,953,787,769], +[7988,7984,769], +[7989,953,788,769], +[7989,7985,769], +[7990,953,787,834], +[7990,7984,834], +[7991,953,788,834], +[7991,7985,834], +[7992,921,787], +[7993,921,788], +[7994,921,787,768], +[7994,7992,768], +[7995,921,788,768], +[7995,7993,768], +[7996,921,787,769], +[7996,7992,769], +[7997,921,788,769], +[7997,7993,769], +[7998,921,787,834], +[7998,7992,834], +[7999,921,788,834], +[7999,7993,834], +[8000,959,787], +[8001,959,788], +[8002,959,787,768], +[8002,8000,768], +[8003,959,788,768], +[8003,8001,768], +[8004,959,787,769], +[8004,8000,769], +[8005,959,788,769], +[8005,8001,769], +[8008,927,787], +[8009,927,788], +[8010,927,787,768], +[8010,8008,768], +[8011,927,788,768], +[8011,8009,768], +[8012,927,787,769], +[8012,8008,769], +[8013,927,788,769], +[8013,8009,769], +[8016,965,787], +[8017,965,788], +[8018,965,787,768], +[8018,8016,768], +[8019,965,788,768], +[8019,8017,768], +[8020,965,787,769], +[8020,8016,769], +[8021,965,788,769], +[8021,8017,769], +[8022,965,787,834], +[8022,8016,834], +[8023,965,788,834], +[8023,8017,834], +[8025,933,788], +[8027,933,788,768], +[8027,8025,768], +[8029,933,788,769], +[8029,8025,769], +[8031,933,788,834], +[8031,8025,834], +[8032,969,787], +[8033,969,788], +[8034,969,787,768], +[8034,8032,768], +[8035,969,788,768], +[8035,8033,768], +[8036,969,787,769], +[8036,8032,769], +[8037,969,788,769], +[8037,8033,769], +[8038,969,787,834], +[8038,8032,834], +[8039,969,788,834], +[8039,8033,834], +[8040,937,787], +[8041,937,788], +[8042,937,787,768], +[8042,8040,768], +[8043,937,788,768], +[8043,8041,768], +[8044,937,787,769], +[8044,8040,769], +[8045,937,788,769], +[8045,8041,769], +[8046,937,787,834], +[8046,8040,834], +[8047,937,788,834], +[8047,8041,834], +[8048,945,768], +[8050,949,768], +[8052,951,768], +[8054,953,768], +[8056,959,768], +[8058,965,768], +[8060,969,768], +[8064,945,787,837], +[8064,8115,787], +[8064,7936,837], +[8065,945,788,837], +[8065,8115,788], +[8065,7937,837], +[8066,945,787,768,837], +[8066,8115,787,768], +[8066,8064,768], +[8066,7936,837,768], +[8066,7936,768,837], +[8066,7938,837], +[8067,945,788,768,837], +[8067,8115,788,768], +[8067,8065,768], +[8067,7937,837,768], +[8067,7937,768,837], +[8067,7939,837], +[8068,945,787,769,837], +[8068,8115,787,769], +[8068,8064,769], +[8068,7936,837,769], +[8068,7936,769,837], +[8068,7940,837], +[8069,945,788,769,837], +[8069,8115,788,769], +[8069,8065,769], +[8069,7937,837,769], +[8069,7937,769,837], +[8069,7941,837], +[8070,945,787,834,837], +[8070,8115,787,834], +[8070,8064,834], +[8070,7936,837,834], +[8070,7936,834,837], +[8070,7942,837], +[8071,945,788,834,837], +[8071,8115,788,834], +[8071,8065,834], +[8071,7937,837,834], +[8071,7937,834,837], +[8071,7943,837], +[8072,913,787,837], +[8072,8124,787], +[8072,7944,837], +[8073,913,788,837], +[8073,8124,788], +[8073,7945,837], +[8074,913,787,768,837], +[8074,8124,787,768], +[8074,8072,768], +[8074,7944,837,768], +[8074,7944,768,837], +[8074,7946,837], +[8075,913,788,768,837], +[8075,8124,788,768], +[8075,8073,768], +[8075,7945,837,768], +[8075,7945,768,837], +[8075,7947,837], +[8076,913,787,769,837], +[8076,8124,787,769], +[8076,8072,769], +[8076,7944,837,769], +[8076,7944,769,837], +[8076,7948,837], +[8077,913,788,769,837], +[8077,8124,788,769], +[8077,8073,769], +[8077,7945,837,769], +[8077,7945,769,837], +[8077,7949,837], +[8078,913,787,834,837], +[8078,8124,787,834], +[8078,8072,834], +[8078,7944,837,834], +[8078,7944,834,837], +[8078,7950,837], +[8079,913,788,834,837], +[8079,8124,788,834], +[8079,8073,834], +[8079,7945,837,834], +[8079,7945,834,837], +[8079,7951,837], +[8080,951,787,837], +[8080,8131,787], +[8080,7968,837], +[8081,951,788,837], +[8081,8131,788], +[8081,7969,837], +[8082,951,787,768,837], +[8082,8131,787,768], +[8082,8080,768], +[8082,7968,837,768], +[8082,7968,768,837], +[8082,7970,837], +[8083,951,788,768,837], +[8083,8131,788,768], +[8083,8081,768], +[8083,7969,837,768], +[8083,7969,768,837], +[8083,7971,837], +[8084,951,787,769,837], +[8084,8131,787,769], +[8084,8080,769], +[8084,7968,837,769], +[8084,7968,769,837], +[8084,7972,837], +[8085,951,788,769,837], +[8085,8131,788,769], +[8085,8081,769], +[8085,7969,837,769], +[8085,7969,769,837], +[8085,7973,837], +[8086,951,787,834,837], +[8086,8131,787,834], +[8086,8080,834], +[8086,7968,837,834], +[8086,7968,834,837], +[8086,7974,837], +[8087,951,788,834,837], +[8087,8131,788,834], +[8087,8081,834], +[8087,7969,837,834], +[8087,7969,834,837], +[8087,7975,837], +[8088,919,787,837], +[8088,8140,787], +[8088,7976,837], +[8089,919,788,837], +[8089,8140,788], +[8089,7977,837], +[8090,919,787,768,837], +[8090,8140,787,768], +[8090,8088,768], +[8090,7976,837,768], +[8090,7976,768,837], +[8090,7978,837], +[8091,919,788,768,837], +[8091,8140,788,768], +[8091,8089,768], +[8091,7977,837,768], +[8091,7977,768,837], +[8091,7979,837], +[8092,919,787,769,837], +[8092,8140,787,769], +[8092,8088,769], +[8092,7976,837,769], +[8092,7976,769,837], +[8092,7980,837], +[8093,919,788,769,837], +[8093,8140,788,769], +[8093,8089,769], +[8093,7977,837,769], +[8093,7977,769,837], +[8093,7981,837], +[8094,919,787,834,837], +[8094,8140,787,834], +[8094,8088,834], +[8094,7976,837,834], +[8094,7976,834,837], +[8094,7982,837], +[8095,919,788,834,837], +[8095,8140,788,834], +[8095,8089,834], +[8095,7977,837,834], +[8095,7977,834,837], +[8095,7983,837], +[8096,969,787,837], +[8096,8179,787], +[8096,8032,837], +[8097,969,788,837], +[8097,8179,788], +[8097,8033,837], +[8098,969,787,768,837], +[8098,8179,787,768], +[8098,8096,768], +[8098,8032,837,768], +[8098,8032,768,837], +[8098,8034,837], +[8099,969,788,768,837], +[8099,8179,788,768], +[8099,8097,768], +[8099,8033,837,768], +[8099,8033,768,837], +[8099,8035,837], +[8100,969,787,769,837], +[8100,8179,787,769], +[8100,8096,769], +[8100,8032,837,769], +[8100,8032,769,837], +[8100,8036,837], +[8101,969,788,769,837], +[8101,8179,788,769], +[8101,8097,769], +[8101,8033,837,769], +[8101,8033,769,837], +[8101,8037,837], +[8102,969,787,834,837], +[8102,8179,787,834], +[8102,8096,834], +[8102,8032,837,834], +[8102,8032,834,837], +[8102,8038,837], +[8103,969,788,834,837], +[8103,8179,788,834], +[8103,8097,834], +[8103,8033,837,834], +[8103,8033,834,837], +[8103,8039,837], +[8104,937,787,837], +[8104,8188,787], +[8104,8040,837], +[8105,937,788,837], +[8105,8188,788], +[8105,8041,837], +[8106,937,787,768,837], +[8106,8188,787,768], +[8106,8104,768], +[8106,8040,837,768], +[8106,8040,768,837], +[8106,8042,837], +[8107,937,788,768,837], +[8107,8188,788,768], +[8107,8105,768], +[8107,8041,837,768], +[8107,8041,768,837], +[8107,8043,837], +[8108,937,787,769,837], +[8108,8188,787,769], +[8108,8104,769], +[8108,8040,837,769], +[8108,8040,769,837], +[8108,8044,837], +[8109,937,788,769,837], +[8109,8188,788,769], +[8109,8105,769], +[8109,8041,837,769], +[8109,8041,769,837], +[8109,8045,837], +[8110,937,787,834,837], +[8110,8188,787,834], +[8110,8104,834], +[8110,8040,837,834], +[8110,8040,834,837], +[8110,8046,837], +[8111,937,788,834,837], +[8111,8188,788,834], +[8111,8105,834], +[8111,8041,837,834], +[8111,8041,834,837], +[8111,8047,837], +[8112,945,774], +[8113,945,772], +[8114,945,768,837], +[8114,8115,768], +[8114,8048,837], +[8115,945,837], +[8116,945,769,837], +[8116,8115,769], +[8116,940,837], +[8118,945,834], +[8119,945,834,837], +[8119,8115,834], +[8119,8118,837], +[8120,913,774], +[8121,913,772], +[8122,913,768], +[8124,913,837], +[8129,168,834], +[8130,951,768,837], +[8130,8131,768], +[8130,8052,837], +[8131,951,837], +[8132,951,769,837], +[8132,8131,769], +[8132,942,837], +[8134,951,834], +[8135,951,834,837], +[8135,8131,834], +[8135,8134,837], +[8136,917,768], +[8138,919,768], +[8140,919,837], +[8141,8127,768], +[8142,8127,769], +[8143,8127,834], +[8144,953,774], +[8145,953,772], +[8146,953,776,768], +[8146,970,768], +[8150,953,834], +[8151,953,776,834], +[8151,970,834], +[8152,921,774], +[8153,921,772], +[8154,921,768], +[8157,8190,768], +[8158,8190,769], +[8159,8190,834], +[8160,965,774], +[8161,965,772], +[8162,965,776,768], +[8162,971,768], +[8164,961,787], +[8165,961,788], +[8166,965,834], +[8167,965,776,834], +[8167,971,834], +[8168,933,774], +[8169,933,772], +[8170,933,768], +[8172,929,788], +[8173,168,768], +[8178,969,768,837], +[8178,8179,768], +[8178,8060,837], +[8179,969,837], +[8180,969,769,837], +[8180,8179,769], +[8180,974,837], +[8182,969,834], +[8183,969,834,837], +[8183,8179,834], +[8183,8182,837], +[8184,927,768], +[8186,937,768], +[8188,937,837], +[8602,8592,824], +[8603,8594,824], +[8622,8596,824], +[8653,8656,824], +[8654,8660,824], +[8655,8658,824], +[8708,8707,824], +[8713,8712,824], +[8716,8715,824], +[8740,8739,824], +[8742,8741,824], +[8769,8764,824], +[8772,8771,824], +[8775,8773,824], +[8777,8776,824], +[8800,61,824], +[8802,8801,824], +[8813,8781,824], +[8814,60,824], +[8815,62,824], +[8816,8804,824], +[8817,8805,824], +[8820,8818,824], +[8821,8819,824], +[8824,8822,824], +[8825,8823,824], +[8832,8826,824], +[8833,8827,824], +[8836,8834,824], +[8837,8835,824], +[8840,8838,824], +[8841,8839,824], +[8876,8866,824], +[8877,8872,824], +[8878,8873,824], +[8879,8875,824], +[8928,8828,824], +[8929,8829,824], +[8930,8849,824], +[8931,8850,824], +[8938,8882,824], +[8939,8883,824], +[8940,8884,824], +[8941,8885,824], +[12364,12363,12441], +[12366,12365,12441], +[12368,12367,12441], +[12370,12369,12441], +[12372,12371,12441], +[12374,12373,12441], +[12376,12375,12441], +[12378,12377,12441], +[12380,12379,12441], +[12382,12381,12441], +[12384,12383,12441], +[12386,12385,12441], +[12389,12388,12441], +[12391,12390,12441], +[12393,12392,12441], +[12400,12399,12441], +[12401,12399,12442], +[12403,12402,12441], +[12404,12402,12442], +[12406,12405,12441], +[12407,12405,12442], +[12409,12408,12441], +[12410,12408,12442], +[12412,12411,12441], +[12413,12411,12442], +[12436,12358,12441], +[12446,12445,12441], +[12460,12459,12441], +[12462,12461,12441], +[12464,12463,12441], +[12466,12465,12441], +[12468,12467,12441], +[12470,12469,12441], +[12472,12471,12441], +[12474,12473,12441], +[12476,12475,12441], +[12478,12477,12441], +[12480,12479,12441], +[12482,12481,12441], +[12485,12484,12441], +[12487,12486,12441], +[12489,12488,12441], +[12496,12495,12441], +[12497,12495,12442], +[12499,12498,12441], +[12500,12498,12442], +[12502,12501,12441], +[12503,12501,12442], +[12505,12504,12441], +[12506,12504,12442], +[12508,12507,12441], +[12509,12507,12442], +[12532,12454,12441], +[12535,12527,12441], +[12536,12528,12441], +[12537,12529,12441], +[12538,12530,12441], +[12542,12541,12441], +); + +1; diff --git a/font-optimizer/Font/Subsetter/create-data.pl b/font-optimizer/Font/Subsetter/create-data.pl new file mode 100644 index 0000000..e0a38fb --- /dev/null +++ b/font-optimizer/Font/Subsetter/create-data.pl @@ -0,0 +1,100 @@ +use strict; +use warnings; + +use Unicode::Normalize; + +print <) { + my @c = split /;/, $_; + # Find characters which canonically decompose (without any + # compatibility tag "") + next unless $c[5] and $c[5] !~ /^[ord($1)-ord('a')]/eg; $_ } split / /, $cs2; + # If NFC didn't collapse everything into single characters, this string is not interesting + next if grep length != 1, @x; + # If the string doesn't NFC into the desired character, it's not interesting + next unless Unicode::Normalize::NFC(join '', @x) eq chr hex $c[0]; + # This string is good + push @data, [hex $c[0], map ord, @x]; + } + } + } elsif (@norm == 4) { + my ($a, $b, $c, $d) = @norm; + for my $cs (permut([$a, $b, $c, $d], [])) { + for my $cs2 ('ab c d', 'a bc d', 'a b cd', 'ab cd', 'abc d', 'a bcd') { + my @x = map Unicode::Normalize::NFC($_), map { s/(.)/$cs->[ord($1)-ord('a')]/eg; $_ } split / /, $cs2; + next if grep length != 1, @x; + next unless Unicode::Normalize::NFC(join '', @x) eq chr hex $c[0]; + push @data, [hex $c[0], map ord, @x]; + } + } + } elsif (@norm > 4) { + die "\@norm too big"; + } +} + +print uniq(map "[".join(',', @$_)."],\n", @data); + +print < \$verbose, + 'ttf-to-eot' => \$ttf_to_eot, + 'eot-to-ttf' => \$eot_to_ttf, + ) or help(); + + @ARGV == 2 or help(); + + my ($input_file, $output_file) = @ARGV; + + if ($ttf_to_eot and $eot_to_ttf) { + help(); + } + + if (not ($ttf_to_eot or $eot_to_ttf)) { + if ($input_file =~ /\.[ot]tf$/i and $output_file =~ /\.eot$/i) { + $ttf_to_eot = 1; + } elsif ($input_file =~ /\.eot$/i and $output_file =~ /\.[ot]tf$/i) { + $eot_to_ttf = 1; + } else { + help(); + } + } + + if ($ttf_to_eot) { + Font::EOTWrapper::convert($input_file, $output_file); + } elsif ($eot_to_ttf) { + Font::EOTWrapper::extract($input_file, $output_file); + } +} diff --git a/font-optimizer/ext/Font-TTF/.cvsignore b/font-optimizer/ext/Font-TTF/.cvsignore new file mode 100644 index 0000000..459bc9c --- /dev/null +++ b/font-optimizer/ext/Font-TTF/.cvsignore @@ -0,0 +1,2 @@ +*.gz +MANIFEST diff --git a/font-optimizer/ext/Font-TTF/COPYING b/font-optimizer/ext/Font-TTF/COPYING new file mode 100644 index 0000000..cbda892 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/COPYING @@ -0,0 +1,76 @@ + +Artistic License 2.0 + +Copyright (c) 2000-2006, The Perl Foundation. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +Preamble + +This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. +Definitions + +"Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. + +"Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. + +"You" and "your" means any person who would like to copy, distribute, or modify the Package. + +"Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. + +"Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. + +"Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. + +"Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. + +"Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. + +"Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. + +"Source" form means the source code, documentation source, and configuration files for the Package. + +"Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: + +(a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. +(b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. +(c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under +(i) the Original License or +(ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. +Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. +General Provisions + +(10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. + +(11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. + +(12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. + +(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/font-optimizer/ext/Font-TTF/Changes b/font-optimizer/ext/Font-TTF/Changes new file mode 100644 index 0000000..1821632 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/Changes @@ -0,0 +1,6 @@ +0.45 + +* Introduce changelog +* tidy up 0.44 package, fix README to be more accurate +* tests failing on perl 5.8.2 and before due to no use Exporter qw(import); + Fix OTTags accordingly. diff --git a/font-optimizer/ext/Font-TTF/MANIFEST.SKIP b/font-optimizer/ext/Font-TTF/MANIFEST.SKIP new file mode 100644 index 0000000..464c50e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/MANIFEST.SKIP @@ -0,0 +1,26 @@ +blib/ +\.\$\$\$ +\.tmp +\.bak +CVS/ +\.tar +\.tgz +\.old +misc/ +Build/ +exes/ +\.cvsignore +^# +\.svn/ +Makefile$ +\.par$ +-stamp$ +debian/ +pm_to_blib +\~$ +dev/ +build/ +dists/ +^libfont- +description-pak +^doc diff --git a/font-optimizer/ext/Font-TTF/Makefile.PL b/font-optimizer/ext/Font-TTF/Makefile.PL new file mode 100644 index 0000000..c0518d5 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/Makefile.PL @@ -0,0 +1,108 @@ +use ExtUtils::MakeMaker; +use Getopt::Std; + +getopts('d:rv:'); + +%pbuilderopts = ( + 'gutsy' => '--bindmounts /media/hosk_1' + ); + +$opt_v ||= 1; + +if ($^O eq 'linux' && !defined $opt_d) +{ + $opt_d = `lsb_release -c`; + $opt_d =~ s/^.*?(\w+)\s*$/$1/o; +} + +@theselibs = (grep {-f } glob("lib/Font/TTF/*"), "lib/Font/TTF.pm"); + +# incantation to enable MY::pm_to_blib later on +if ($^O eq 'MSWin32') +{ + push(@ExtUtils::MakeMaker::Overridable, qw(pm_to_blib)); + @extras = ('dist' => { 'TO_UNIX' => 'perl -Mtounix -e "tounix(\"$(DISTVNAME)\")"' }); +} + +%makeinfo = ( + NAME => 'Font::TTF', + VERSION_FROM => 'lib/Font/TTF.pm', +# VERSION => "0.38", +# HTMLLIBPODS => {map {my $t = $_; $t=~s/\..*?$/.html/o; $t='blib/Html/'.$t; $_ => $t;} @theselibs}, +# HTMLSCRIPTPODS => {map {my $t=$_; $t=~s/\..*?$/.html/o; $t='blib/Html/'.$t; $_ => $t;} @scripts}, + AUTHOR => "martin_hosken\@sil.org", + ABSTRACT => "TTF font support for Perl", + @extras + ); + +WriteMakefile(%makeinfo); + +if ($^O eq 'MSWin32') { +# incantation to solve the problem of everyone's $Config{make} being 'nmake' +# when we want 'pmake'. And $Config{} is read only. +# actually, this is just a copy of the code from ExtUtiles::MM_Win32 tidied +# up (to expose tabs) and the dependency on Config removed +sub MY::pm_to_blib +{ + my $self = shift; + my($autodir) = $self->catdir('$(INST_LIB)','auto'); + return <<"EOT"; + +pm_to_blib: \$(TO_INST_PM) +\t$self->{NOECHO}\$(PERL) \"-I\$(INST_ARCHLINE)\" \"-I\$(INST_LIB)\" \\ +\t\"-I\$(PERL_ARCHLIB)\" \"-I\$(PERL_LIB)\" -MExtUtils::Install \\ +\t-e \"pm_to_blib({ qw[\$(PM_TO_BLIB)] }, '$autodir') +\t$self->{NOECHO}\$(TOUCH) \$@ + +EOT +} + +} +elsif ($^O eq 'linux') +{ + +sub MY::postamble +{ + my ($self) = @_; + my ($res); + my ($package) = lc($self->{'NAME'}); + my ($pversion) = $self->{'VERSION'}; + my ($svn) = `svnversion`; + my ($sign) = '--auto-debsign' if ($opt_r); + my ($fpackage); + + $svn =~ s/[0-9]*://og; + $svn =~ s/\s+$//o; + $package =~ s/::/-/; + $package = "lib${package}-perl"; + $pversion .= "+$svn" unless ($opt_r); + $fpackage = "$package-$pversion"; + + $res = <<"EOT"; +deb-base: dist + rm -fr $self->{'DISTVNAME'} + rm -fr $fpackage + tar xvzf $self->{'DISTVNAME'}.tar.gz + mv $self->{'DISTVNAME'} $fpackage + tar cfz "${package}_$pversion.orig.tar.gz" $fpackage + cp -a debian $fpackage + cd $fpackage && find . -name .svn | xargs rm -rf + +# make deb builds an interim deb from svn source for release +deb: deb-base +EOT + + foreach $d (split(' ', $opt_d)) + { + $res .= <<"EOT"; + mkdir -p dists/$d + dch -D $d -v $pversion-$opt_v -m -b -c $fpackage/debian/changelog "Auto build from perl for $d" + cd $fpackage && pdebuild --buildresult ../dists/$d -- --basetgz /var/cache/pbuilder/base-$d.tgz $pbuilderopts{$d} +EOT + } + + return $res; +} + +} + diff --git a/font-optimizer/ext/Font-TTF/README.TXT b/font-optimizer/ext/Font-TTF/README.TXT new file mode 100644 index 0000000..ad6a602 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/README.TXT @@ -0,0 +1,99 @@ + Perl Module: Font::TTF + +=head1 Introduction + +Perl module for TrueType font hacking. Supports reading, processing and +writing of the following tables: GDEF, GPOS, GSUB, LTSH, OS/2, PCLT, +bsln, cmap, cvt, fdsc, feat, fpgm, glyf, hdmx, head, hhea, hmtx, kern, +loca, maxp, mort, name, post, prep, prop, vhea, vmtx and the reading and +writing of all other table types. + +In short, you can do almost anything with a standard TrueType font with +this module. Be Brave! + +Any suggestions, improvements, additions, subclasses, etc. would be gratefully +received and probably included in a future release. Please send them to me. + +This module has been tested on Win32, Unix and Mac. + +Applications that were associated with this module have been moved to Font::TTF::Scripts where great things can be done. + +=head1 SYNOPSIS + +Here is the regression test (you provide your own font). Run it once and then +again on the output of the first run. There should be no differences between +the outputs of the two runs. + + use Font::TTF::Font; + + $f = Font::TTF::Font->open($ARGV[0]); + + # force a read of all the tables + $f->tables_do(sub { $_[0]->read; }); + + # force read of all glyphs (use read_dat to use lots of memory!) + # $f->{'loca'}->glyphs_do(sub { $_[0]->read; }); + $f->{'loca'}->glyphs_do(sub { $_[0]->read_dat; }); + # NB. no need to $g->update since $_[0]->{'glyf'}->out will do it for us + + $f->out($ARGV[1]); + $f->DESTROY; # forces close of $in and maybe memory reclaim! + +=head1 Installation + +If you have received this package as part of an Activestate PPM style .zip file +then type + + ppm install Font-TTF.ppd + +Otherwise. + +To configure this module, cd to the directory that contains this README file +and type the following. + + perl Makefile.PL + +Alternatively, if you plan to install Font::TTF somewhere other than +your system's perl library directory. You can type something like this: + + perl Makefile.PL PREFIX=/home/me/perl INSTALLDIRS=perl + +Then to build you run make. + + make + +If you have write access to the perl library directories, you may then +install by typing: + + make install + +To tidy up, type: + + make realclean + +Win32 users should use pmake instead of make. Alternatively installation can be +done on Win32 by typing: + + Setup + +Or using the install feature in tools like WinZip. + +=head1 CHANGES + +=head2 Future Changes + +I do not anticipate any more restructuring changes (but reserve the right to do so). + +=head1 AUTHOR + +Martin Hosken L + +Copyright Martin Hosken 1998 and following. + +No warranty or expression of effectiveness for anything, least of all anyone's +safety, is implied in this software or documentation. + +=head2 Licensing + +The Perl TTF module is licensed under the Perl Artistic License. + diff --git a/font-optimizer/ext/Font-TTF/TODO b/font-optimizer/ext/Font-TTF/TODO new file mode 100644 index 0000000..a562dc4 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/TODO @@ -0,0 +1,5 @@ + +* update() is a mess. What is needed is a mark sweep clean algorithm where the + dirty is used only for tables that have changed. Thus if a table is updated + it could well become dirty! Also need to be able to pass a force parameter + to force a table to update and all the tables in its dependency tree. diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF.pm new file mode 100644 index 0000000..59237d3 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF.pm @@ -0,0 +1,24 @@ +package Font::TTF; + +$VERSION = '0.46'; # MJPH 26-JAN-2009 Various bug fixes, add Sill table +# $VERSION = '0.45'; # MJPH 11-JUN-2008 Packaging tidying +# $VERSION = '0.44'; # MJPH 9-JUN-2008 Various bug fixes +# $VERSION = '0.43'; # MJPH 20-NOV-2007 Add a test! +# $VERSION = '0.42'; # MJPH 11-OCT-2007 Add Volt2ttf support +# $VERSION = '0.41'; # MJPH 27-MAR-2007 Remove warnings from font copy +# Bug fixes in Ttopen, GDEF +# Remove redundant head and maxp ->reads +# $VERSION = '0.40'; # MJPH 31-JUL-2006 Add EBDT, EBLC tables +# $VERSION = 0.39; + +1; + +=head1 NAME + +Font::TTF - Perl module for TrueType Font hacking + +=head1 DESCRIPTION + +This module allows you to do almost anything to a TrueType/OpenType Font +including modify and inspect nearly all tables. + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATKern.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATKern.pm new file mode 100644 index 0000000..6aa1e62 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATKern.pm @@ -0,0 +1,140 @@ +package Font::TTF::AATKern; + +=head1 NAME + +Font::TTF::AATKern - AAT Kern table + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Kern::Subtable; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + + $self->SUPER::read or return $self; + + my ($dat, $fh, $numSubtables); + $fh = $self->{' INFILE'}; + + $fh->read($dat, 8); + ($self->{'version'}, $numSubtables) = TTF_Unpack("vL", $dat); + + my $subtables = []; + foreach (1 .. $numSubtables) { + my $subtableStart = $fh->tell(); + + $fh->read($dat, 8); + my ($length, $coverage, $tupleIndex) = TTF_Unpack("LSS", $dat); + my $type = $coverage & 0x00ff; + + my $subtable = Font::TTF::Kern::Subtable->create($type, $coverage, $length); + $subtable->read($fh); + + $subtable->{'tupleIndex'} = $tupleIndex if $subtable->{'variation'}; + $subtable->{' PARENT'} = $self; + push @$subtables, $subtable; + } + + $self->{'subtables'} = $subtables; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + my $subtables = $self->{'subtables'}; + $fh->print(TTF_Pack("vL", $self->{'version'}, scalar @$subtables)); + + foreach (@$subtables) { + $_->out($fh); + } +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + $self->read unless $self->{' read'}; + + $fh = 'STDOUT' unless defined $fh; + + $fh->printf("version %f\n", $self->{'version'}); + + my $subtables = $self->{'subtables'}; + foreach (@$subtables) { + $_->print($fh); + } +} + +sub dumpXML +{ + my ($self, $fh) = @_; + $self->read unless $self->{' read'}; + + my $post = $self->{' PARENT'}->{'post'}; + $post->read; + + $fh = 'STDOUT' unless defined $fh; + $fh->printf("\n", $self->{'version'}); + + my $subtables = $self->{'subtables'}; + foreach (@$subtables) { + $fh->printf("<%s", $_->type); + $fh->printf(" vertical=\"1\"") if $_->{'vertical'}; + $fh->printf(" crossStream=\"1\"") if $_->{'crossStream'}; + $fh->printf(" variation=\"1\"") if $_->{'variation'}; + $fh->printf(" tupleIndex=\"%s\"", $_->{'tupleIndex'}) if exists $_->{'tupleIndex'}; + $fh->printf(">\n"); + + $_->dumpXML($fh); + + $fh->printf("\n", $_->type); + } + + $fh->printf("\n"); +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATutils.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATutils.pm new file mode 100644 index 0000000..96ceb7e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/AATutils.pm @@ -0,0 +1,689 @@ +package Font::TTF::AATutils; + +use strict; +use vars qw(@ISA @EXPORT); +require Exporter; + +use Font::TTF::Utils; +use IO::File; + +@ISA = qw(Exporter); +@EXPORT = qw( + AAT_read_lookup + AAT_pack_lookup + AAT_write_lookup + AAT_pack_classes + AAT_write_classes + AAT_pack_states + AAT_write_states + AAT_read_state_table + AAT_read_subtable + xmldump +); + +sub xmldump +{ + my ($var, $links, $depth, $processedVars, $type) = @_; + + $processedVars = {} unless (defined $processedVars); + print("\n") if $depth == 0; # not necessarily true encoding for all text! + + my $indent = "\t" x $depth; + + my ($objType, $addr) = ($var =~ m/^.+=(.+)\((.+)\)$/); + unless (defined $type) { + if (defined $addr) { + if (defined $processedVars->{$addr}) { + if ($links) { + printf("%s%s\n", $indent, "$objType"); + } + else { + printf("%s%s\n", $indent, "$objType"); + } + return; + } + $processedVars->{$addr} = 1; + } + } + + $type = ref $var unless defined $type; + + if ($type eq 'REF') { + printf("%s\n", $indent, $$var); + } + elsif ($type eq 'SCALAR') { + printf("%s%s\n", $indent, $var); + } + elsif ($type eq 'ARRAY') { + # printf("%s\n", $indent); + foreach (0 .. $#$var) { + if (ref($var->[$_])) { + printf("%s\n", $indent, $_); + xmldump($var->[$_], $links, $depth + 1, $processedVars); + printf("%s\n", $indent); + } + else { + printf("%s%s\n", $indent, $_, $var->[$_]); + } + } + # printf("%s\n", $indent); + } + elsif ($type eq 'HASH') { + # printf("%s\n", $indent); + foreach (sort keys %$var) { + if (ref($var->{$_})) { + printf("%s\n", $indent, $_); + xmldump($var->{$_}, $links, $depth + 1, $processedVars); + printf("%s\n", $indent); + } + else { + printf("%s%s\n", $indent, $_, $var->{$_}); + } + } + # printf("%s\n", $indent); + } + elsif ($type eq 'CODE') { + printf("%s\n", $indent, $var); + } + elsif ($type eq 'GLOB') { + printf("%s\n", $indent, $var); + } + elsif ($type eq '') { + printf("%s%s\n", $indent, $var); + } + else { + if ($links) { + printf("%s\n", $indent, $type, $addr); + } + else { + printf("%s\n", $indent, $type); + } + xmldump($var, $links, $depth + 1, $processedVars, $objType); + printf("%s\n", $indent); + } +} + +=head2 ($classes, $states) = AAT_read_subtable($fh, $baseOffset, $subtableStart, $limits) + +=cut + +sub AAT_read_subtable +{ + my ($fh, $baseOffset, $subtableStart, $limits) = @_; + + my $limit = 0xffffffff; + foreach (@$limits) { + $limit = $_ if ($_ > $subtableStart and $_ < $limit); + } + die if $limit == 0xffffffff; + + my $dat; + $fh->seek($baseOffset + $subtableStart, IO::File::SEEK_SET); + $fh->read($dat, $limit - $subtableStart); + + $dat; +} + +=head2 $length = AAT_write_state_table($fh, $classes, $states, $numExtraTables, $packEntry) + +$packEntry is a subroutine for packing an entry into binary form, called as + +$dat = $packEntry($entry, $entryTable, $numEntries) + +where the entry is a comma-separated list of nextStateOffset, flags, actions + +=cut + +sub AAT_pack_state_table +{ + my ($classes, $states, $numExtraTables, $packEntry) = @_; + + my ($dat) = pack("n*", (0) x (4 + $numExtraTables)); # placeholders for stateSize, classTable, stateArray, entryTable + + my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0); + my (@classTable, $i); + foreach $i (0 .. $#$classes) { + my $class = $classes->[$i]; + foreach (@$class) { + $firstGlyph = $_ if $_ < $firstGlyph; + $lastGlyph = $_ if $_ > $lastGlyph; + $classTable[$_] = $i; + } + } + + my $classTable = length($dat); + $dat .= pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1, + map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph)); + $dat .= pack("C", 0) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs + + my $stateArray = length($dat); + my (@entries, %entries); + my $state = $states->[0]; + my $stateSize = @$state; + die "stateSize below minimum allowed (4)" if $stateSize < 4; + die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1; + warn "state array has unreachable columns" if $stateSize > $#$classes + 1; + + foreach (@$states) { + die "inconsistent state size" if @$_ != $stateSize; + foreach (@$_) { + my $actions = $_->{'actions'}; + my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, $_->{'flags'}, ref($actions) eq 'ARRAY' ? @$actions : $actions); + if (not defined $entries{$entry}) { + push @entries, $entry; + $entries{$entry} = $#entries; + die "too many different state array entries" if $#entries == 256; + } + $dat .= pack("C", $entries{$entry}); + } + } + $dat .= pack("C", 0) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd + + my $entryTable = length($dat); + $dat .= map { &$packEntry($_, $entryTable, $#entries + 1) } @entries; + + my ($dat1) = pack("nnnn", $stateSize, $classTable, $stateArray, $entryTable); + substr($dat, 0, length($dat1)) = $dat1; + + return $dat; +} + +sub AAT_write_state_table +{ + my ($fh, $classes, $states, $numExtraTables, $packEntry) = @_; + + my $stateTableStart = $fh->tell(); + + $fh->print(pack("n*", (0) x (4 + $numExtraTables))); # placeholders for stateSize, classTable, stateArray, entryTable + + my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0); + my (@classTable, $i); + foreach $i (0 .. $#$classes) { + my $class = $classes->[$i]; + foreach (@$class) { + $firstGlyph = $_ if $_ < $firstGlyph; + $lastGlyph = $_ if $_ > $lastGlyph; + $classTable[$_] = $i; + } + } + + my $classTable = $fh->tell() - $stateTableStart; + $fh->print(pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1, + map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph))); + $fh->print(pack("C", 0)) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs + + my $stateArray = $fh->tell() - $stateTableStart; + my (@entries, %entries); + my $state = $states->[0]; + my $stateSize = @$state; + die "stateSize below minimum allowed (4)" if $stateSize < 4; + die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1; + warn "state array has unreachable columns" if $stateSize > $#$classes + 1; + + foreach (@$states) { + die "inconsistent state size" if @$_ != $stateSize; + foreach (@$_) { + my $actions = $_->{'actions'}; + my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, $_->{'flags'}, ref($actions) eq 'ARRAY' ? @$actions : $actions); + if (not defined $entries{$entry}) { + push @entries, $entry; + $entries{$entry} = $#entries; + die "too many different state array entries" if $#entries == 256; + } + $fh->print(pack("C", $entries{$entry})); + } + } + $fh->print(pack("C", 0)) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd + + my $entryTable = $fh->tell() - $stateTableStart; + $fh->print(map { &$packEntry($_, $entryTable, $#entries + 1) } @entries); + + my $length = $fh->tell() - $stateTableStart; + $fh->seek($stateTableStart, IO::File::SEEK_SET); + $fh->print(pack("nnnn", $stateSize, $classTable, $stateArray, $entryTable)); + + $fh->seek($stateTableStart + $length, IO::File::SEEK_SET); + $length; +} + +sub AAT_pack_classes +{ + my ($classes) = @_; + + my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0); + my (@classTable, $i); + foreach $i (0 .. $#$classes) { + my $class = $classes->[$i]; + foreach (@$class) { + $firstGlyph = $_ if $_ < $firstGlyph; + $lastGlyph = $_ if $_ > $lastGlyph; + $classTable[$_] = $i; + } + } + + my ($dat) = pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1, + map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph)); + $dat .= pack("C", 0) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs + + return $dat; +} + +sub AAT_write_classes +{ + my ($fh, $classes) = @_; + + $fh->print(AAT_pack_classes($fh, $classes)); +} + +sub AAT_pack_states +{ + my ($classes, $stateArray, $states, $buildEntryProc) = @_; + + my ($entries, %entryHash); + my $state = $states->[0]; + my $stateSize = @$state; + + die "stateSize below minimum allowed (4)" if $stateSize < 4; + die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1; + warn "state array has unreachable columns" if $stateSize > $#$classes + 1; + + my ($dat); + foreach (@$states) { + die "inconsistent state size" if @$_ != $stateSize; + foreach (@$_) { + my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, &$buildEntryProc($_)); + if (not defined $entryHash{$entry}) { + push @$entries, $entry; + $entryHash{$entry} = $#$entries; + die "too many different state array entries" if $#$entries == 256; + } + $dat .= pack("C", $entryHash{$entry}); + } + } + $dat .= pack("C", 0) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd + + ($dat, $stateSize, $entries); +} + +sub AAT_write_states +{ + my ($fh, $classes, $stateArray, $states, $buildEntryProc) = @_; + + my ($entries, %entryHash); + my $state = $states->[0]; + my $stateSize = @$state; + + die "stateSize below minimum allowed (4)" if $stateSize < 4; + die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1; + warn "state array has unreachable columns" if $stateSize > $#$classes + 1; + + foreach (@$states) { + die "inconsistent state size" if @$_ != $stateSize; + foreach (@$_) { + my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, &$buildEntryProc($_)); + if (not defined $entryHash{$entry}) { + push @$entries, $entry; + $entryHash{$entry} = $#$entries; + die "too many different state array entries" if $#$entries == 256; + } + $fh->print(pack("C", $entryHash{$entry})); + } + } + $fh->print(pack("C", 0)) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd + + ($stateSize, $entries); +} + +=head2 ($classes, $states, $entries) = AAT_read_state_table($fh, $numActionWords) + +=cut + +sub AAT_read_state_table +{ + my ($fh, $numActionWords) = @_; + + my $stateTableStart = $fh->tell(); + my $dat; + $fh->read($dat, 8); + my ($stateSize, $classTable, $stateArray, $entryTable) = unpack("nnnn", $dat); + + my $classes; # array of lists of glyphs + + $fh->seek($stateTableStart + $classTable, IO::File::SEEK_SET); + $fh->read($dat, 4); + my ($firstGlyph, $nGlyphs) = unpack("nn", $dat); + $fh->read($dat, $nGlyphs); + foreach (unpack("C*", $dat)) { + if ($_ != 1) { + my $class = $classes->[$_]; + push(@$class, $firstGlyph); + $classes->[$_] = $class unless defined $classes->[$_]; + } + $firstGlyph++; + } + + $fh->seek($stateTableStart + $stateArray, IO::File::SEEK_SET); + my $states; # array of arrays of hashes{nextState, flags, actions} + + my $entrySize = 4 + ($numActionWords * 2); + my $lastState = 1; + my $entries; + while ($#$states < $lastState) { + $fh->read($dat, $stateSize); + my @stateEntries = unpack("C*", $dat); + my $state; + foreach (@stateEntries) { + if (not defined $entries->[$_]) { + my $loc = $fh->tell(); + $fh->seek($stateTableStart + $entryTable + ($_ * $entrySize), IO::File::SEEK_SET); + $fh->read($dat, $entrySize); + my ($nextState, $flags, $actions); + ($nextState, $flags, @$actions) = unpack("n*", $dat); + $nextState -= $stateArray; + $nextState /= $stateSize; + $entries->[$_] = { 'nextState' => $nextState, 'flags' => $flags }; + $entries->[$_]->{'actions'} = $actions if $numActionWords > 0; + $lastState = $nextState if ($nextState > $lastState); + $fh->seek($loc, IO::File::SEEK_SET); + } + push(@$state, $entries->[$_]); + } + push(@$states, $state); + } + + ($classes, $states, $entries); +} + +=head2 ($format, $lookup) = AAT_read_lookup($fh, $valueSize, $length, $default) + +=cut + +sub AAT_read_lookup +{ + my ($fh, $valueSize, $length, $default) = @_; + + my $lookupStart = $fh->tell(); + my ($dat, $unpackChar); + if ($valueSize == 1) { + $unpackChar = "C"; + } + elsif ($valueSize == 2) { + $unpackChar = "n"; + } + elsif ($valueSize == 4) { + $unpackChar = "N"; + } + else { + die "unsupported value size"; + } + + $fh->read($dat, 2); + my $format = unpack("n", $dat); + my $lookup; + + if ($format == 0) { + $fh->read($dat, $length - 2); + my $i = -1; + $lookup = { map { $i++; ($_ != $default) ? ($i, $_) : () } unpack($unpackChar . "*", $dat) }; + } + + elsif ($format == 2) { + $fh->read($dat, 10); + my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat); + die if $unitSize != 4 + $valueSize; + foreach (1 .. $nUnits) { + $fh->read($dat, $unitSize); + my ($lastGlyph, $firstGlyph, $value) = unpack("nn" . $unpackChar, $dat); + if ($firstGlyph != 0xffff and $value != $default) { + foreach ($firstGlyph .. $lastGlyph) { + $lookup->{$_} = $value; + } + } + } + } + + elsif ($format == 4) { + $fh->read($dat, 10); + my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat); + die if $unitSize != 6; + foreach (1 .. $nUnits) { + $fh->read($dat, $unitSize); + my ($lastGlyph, $firstGlyph, $offset) = unpack("nnn", $dat); + if ($firstGlyph != 0xffff) { + my $loc = $fh->tell(); + $fh->seek($lookupStart + $offset, IO::File::SEEK_SET); + $fh->read($dat, ($lastGlyph - $firstGlyph + 1) * $valueSize); + my @values = unpack($unpackChar . "*", $dat); + foreach (0 .. $lastGlyph - $firstGlyph) { + $lookup->{$firstGlyph + $_} = $values[$_] if $values[$_] != $default; + } + $fh->seek($loc, IO::File::SEEK_SET); + } + } + } + + elsif ($format == 6) { + $fh->read($dat, 10); + my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat); + die if $unitSize != 2 + $valueSize; + foreach (1 .. $nUnits) { + $fh->read($dat, $unitSize); + my ($glyph, $value) = unpack("n" . $unpackChar, $dat); + $lookup->{$glyph} = $value if $glyph != 0xffff and $value != $default; + } + } + + elsif ($format == 8) { + $fh->read($dat, 4); + my ($firstGlyph, $glyphCount) = unpack("nn", $dat); + $fh->read($dat, $glyphCount * $valueSize); + $firstGlyph--; + $lookup = { map { $firstGlyph++; $_ != $default ? ($firstGlyph, $_) : () } unpack($unpackChar . "*", $dat) }; + } + + else { + die "unknown lookup format"; + } + + $fh->seek($lookupStart + $length, IO::File::SEEK_SET); + + ($format, $lookup); +} + +=head2 AAT_write_lookup($fh, $format, $lookup, $valueSize, $default) + +=cut + +sub AAT_pack_lookup +{ + my ($format, $lookup, $valueSize, $default) = @_; + + my $packChar; + if ($valueSize == 1) { + $packChar = "C"; + } + elsif ($valueSize == 2) { + $packChar = "n"; + } + elsif ($valueSize == 4) { + $packChar = "N"; + } + else { + die "unsupported value size"; + } + + my ($dat) = pack("n", $format); + + my ($firstGlyph, $lastGlyph) = (0xffff, 0); + foreach (keys %$lookup) { + $firstGlyph = $_ if $_ < $firstGlyph; + $lastGlyph = $_ if $_ > $lastGlyph; + } + my $glyphCount = $lastGlyph - $firstGlyph + 1; + + if ($format == 0) { + $dat .= pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } (0 .. $lastGlyph)); + } + + elsif ($format == 2) { + my $prev = $default; + my $segStart = $firstGlyph; + my $dat1; + foreach ($firstGlyph .. $lastGlyph + 1) { + my $val = $lookup->{$_}; + $val = $default unless defined $val; + if ($val != $prev) { + $dat1 .= pack("nn" . $packChar, $_ - 1, $segStart, $prev) if $prev != $default; + $prev = $val; + $segStart = $_; + } + } + $dat1 .= pack("nn" . $packChar, 0xffff, 0xffff, 0); + my $unitSize = 4 + $valueSize; + $dat .= pack("nnnnn", $unitSize, TTF_bininfo(length($dat1) / $unitSize, $unitSize)); + $dat .= $dat1; + } + + elsif ($format == 4) { + my $segArray = new Font::TTF::Segarr($valueSize); + $segArray->add_segment($firstGlyph, 1, map { $lookup->{$_} } ($firstGlyph .. $lastGlyph)); + my ($start, $end, $offset); + $offset = 12 + @$segArray * 6 + 6; # 12 is size of format word + binSearchHeader; 6 bytes per segment; 6 for terminating segment + my $dat1; + foreach (@$segArray) { + $start = $_->{'START'}; + $end = $start + $_->{'LEN'} - 1; + $dat1 .= pack("nnn", $end, $start, $offset); + $offset += $_->{'LEN'} * 2; + } + $dat1 .= pack("nnn", 0xffff, 0xffff, 0); + $dat .= pack("nnnnn", 6, TTF_bininfo(length($dat1) / 6, 6)); + $dat .= $dat1; + foreach (@$segArray) { + $dat1 = $_->{'VAL'}; + $dat .= pack($packChar . "*", @$dat1); + } + } + + elsif ($format == 6) { + die "unsupported" if $valueSize != 2; + my $dat1 = pack("n*", map { $_, $lookup->{$_} } sort { $a <=> $b } grep { $lookup->{$_} ne $default } keys %$lookup); + my $unitSize = 2 + $valueSize; + $dat .= pack("nnnnn", $unitSize, TTF_bininfo(length($dat1) / $unitSize, $unitSize)); + $dat .= $dat1; + } + + elsif ($format == 8) { + $dat .= pack("nn", $firstGlyph, $lastGlyph - $firstGlyph + 1); + $dat .= pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } ($firstGlyph .. $lastGlyph)); + } + + else { + die "unknown lookup format"; + } + + my $padBytes = (4 - (length($dat) & 3)) & 3; + $dat .= pack("C*", (0) x $padBytes); + + return $dat; +} + +sub AAT_write_lookup +{ + my ($fh, $format, $lookup, $valueSize, $default) = @_; + + my $lookupStart = $fh->tell(); + my $packChar; + if ($valueSize == 1) { + $packChar = "C"; + } + elsif ($valueSize == 2) { + $packChar = "n"; + } + elsif ($valueSize == 4) { + $packChar = "N"; + } + else { + die "unsupported value size"; + } + + $fh->print(pack("n", $format)); + + my ($firstGlyph, $lastGlyph) = (0xffff, 0); + foreach (keys %$lookup) { + $firstGlyph = $_ if $_ < $firstGlyph; + $lastGlyph = $_ if $_ > $lastGlyph; + } + my $glyphCount = $lastGlyph - $firstGlyph + 1; + + if ($format == 0) { + $fh->print(pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } (0 .. $lastGlyph))); + } + + elsif ($format == 2) { + my $prev = $default; + my $segStart = $firstGlyph; + my $dat; + foreach ($firstGlyph .. $lastGlyph + 1) { + my $val = $lookup->{$_}; + $val = $default unless defined $val; + if ($val != $prev) { + $dat .= pack("nn" . $packChar, $_ - 1, $segStart, $prev) if $prev != $default; + $prev = $val; + $segStart = $_; + } + } + $dat .= pack("nn" . $packChar, 0xffff, 0xffff, 0); + my $unitSize = 4 + $valueSize; + $fh->print(pack("nnnnn", $unitSize, TTF_bininfo(length($dat) / $unitSize, $unitSize))); + $fh->print($dat); + } + + elsif ($format == 4) { + my $segArray = new Font::TTF::Segarr($valueSize); + $segArray->add_segment($firstGlyph, 1, map { $lookup->{$_} } ($firstGlyph .. $lastGlyph)); + my ($start, $end, $offset); + $offset = 12 + @$segArray * 6 + 6; # 12 is size of format word + binSearchHeader; 6 bytes per segment; 6 for terminating segment + my $dat; + foreach (@$segArray) { + $start = $_->{'START'}; + $end = $start + $_->{'LEN'} - 1; + $dat .= pack("nnn", $end, $start, $offset); + $offset += $_->{'LEN'} * 2; + } + $dat .= pack("nnn", 0xffff, 0xffff, 0); + $fh->print(pack("nnnnn", 6, TTF_bininfo(length($dat) / 6, 6))); + $fh->print($dat); + foreach (@$segArray) { + $dat = $_->{'VAL'}; + $fh->print(pack($packChar . "*", @$dat)); + } + } + + elsif ($format == 6) { + die "unsupported" if $valueSize != 2; + my $dat = pack("n*", map { $_, $lookup->{$_} } sort { $a <=> $b } grep { $lookup->{$_} ne $default } keys %$lookup); + my $unitSize = 2 + $valueSize; + $fh->print(pack("nnnnn", $unitSize, TTF_bininfo(length($dat) / $unitSize, $unitSize))); + $fh->print($dat); + } + + elsif ($format == 8) { + $fh->print(pack("nn", $firstGlyph, $lastGlyph - $firstGlyph + 1)); + $fh->print(pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } ($firstGlyph .. $lastGlyph))); + } + + else { + die "unknown lookup format"; + } + + my $length = $fh->tell() - $lookupStart; + my $padBytes = (4 - ($length & 3)) & 3; + $fh->print(pack("C*", (0) x $padBytes)); + $length += $padBytes; + + $length; +} + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Anchor.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Anchor.pm new file mode 100644 index 0000000..b901e83 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Anchor.pm @@ -0,0 +1,216 @@ +package Font::TTF::Anchor; + +=head1 NAME + +Font::TTF::Anchor - Anchor points for GPOS tables + +=head1 DESCRIPTION + +The Anchor defines an anchor point on a glyph providing various information +depending on how much is available, including such information as the co-ordinates, +a curve point and even device specific modifiers. + +=head1 INSTANCE VARIABLES + +=over 4 + +=item x + +XCoordinate of the anchor point + +=item y + +YCoordinate of the anchor point + +=item p + +Curve point on the glyph to use as the anchor point + +=item xdev + +Device table (delta) for the xcoordinate + +=item ydev + +Device table (delta) for the ycoordinate + +=item xid + +XIdAnchor for multiple master horizontal metric id + +=item yid + +YIdAnchor for multiple master vertical metric id + +=back + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Utils; + + +=head2 new + +Creates a new Anchor + +=cut + +sub new +{ + my ($class) = shift; + my ($self) = {@_}; + + bless $self, $class; +} + + +=head2 read($fh) + +Reads the anchor from the given file handle at that point. The file handle is left +at an arbitrary read point, usually the end of something! + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat, $loc, $fmt, $p, $xoff, $yoff); + + $fh->read($dat, 6); + $fmt = unpack('n', $dat); + if ($fmt == 4) + { ($self->{'xid'}, $self->{'yid'}) = TTF_Unpack('S2', substr($dat,2)); } + else + { ($self->{'x'}, $self->{'y'}) = TTF_Unpack('s2', substr($dat,2)); } + + if ($fmt == 2) + { + $fh->read($dat, 2); + $self->{'p'} = unpack('n', $dat); + } elsif ($fmt == 3) + { + $fh->read($dat, 4); + ($xoff, $yoff) = unpack('n2', $dat); + $loc = $fh->tell() - 10; + if ($xoff) + { + $fh->seek($loc + $xoff, 0); + $self->{'xdev'} = Font::TTF::Delta->new->read($fh); + } + if ($yoff) + { + $fh->seek($loc + $yoff, 0); + $self->{'ydev'} = Font::TTF::Delta->new->read($fh); + } + } + $self; +} + + +=head2 out($fh, $style) + +Outputs the Anchor to the given file handle at this point also addressing issues +of deltas. If $style is set, then no output is sent to the file handle. The return +value is the output string. + +=cut + +sub out +{ + my ($self, $fh, $style) = @_; + my ($xoff, $yoff, $fmt, $out); + + if (defined $self->{'xid'} || defined $self->{'yid'}) + { $out = TTF_Pack('SSS', 4, $self->{'xid'}, $self->{'yid'}); } + elsif (defined $self->{'p'}) + { $out = TTF_Pack('Ssss', 2, @{$self}{'x', 'y', 'p'}); } + elsif (defined $self->{'xdev'} || defined $self->{'ydev'}) + { + $out = TTF_Pack('Sss', 3, @{$self}{'x', 'y'}); + if (defined $self->{'xdev'}) + { + $out .= pack('n2', 10, 0); + $out .= $self->{'xdev'}->out($fh, 1); + $yoff = length($out) - 10; + } + else + { $out .= pack('n2', 0, 0); } + if (defined $self->{'ydev'}) + { + $yoff = 10 unless $yoff; + substr($out, 8, 2) = pack('n', $yoff); + $out .= $self->{'ydev'}->out($fh, 1); + } + } else + { $out = TTF_Pack('Sss', 1, @{$self}{'x', 'y'}); } + $fh->print($out) unless $style; + $out; +} + + +sub signature +{ + my ($self) = @_; + return join (",", map {"${_}=$self->{$_}"} qw(x y p xdev ydev xid yid)); +} + + +=head2 $a->out_xml($context) + +Outputs the anchor in XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + my ($end); + + $fh->print("$depthprint(" p='$self->{'p'}'") if defined ($self->{'p'}); + $end = (defined $self->{'xdev'} || defined $self->{'ydev'} || defined $self->{'xid'} || defined $self->{'yid'}); + unless ($end) + { + $fh->print("/>\n"); + return $self; + } + + if (defined $self->{'xdev'}) + { + $fh->print("$depth$context->{'indent'}\n"); + $self->{'xdev'}->out_xml($context, $depth . ($context->{'indent'} x 2)); + $fh->print("$depth$context->{'indent'}\n"); + } + + if (defined $self->{'ydev'}) + { + $fh->print("$depth$context->{'indent'}\n"); + $self->{'ydev'}->out_xml($context, $depth . ($context->{'indent'} x 2)); + $fh->print("$depth$context->{'indent'}\n"); + } + + if (defined $self->{'xid'} || defined $self->{'yid'}) + { + $fh->print("$depth$context->{'indent'}print(" xid='$self->{'xid'}'") if defined ($self->{'xid'}); + $fh->print(" yid='$self->{'yid'}'") if defined ($self->{'yid'}); + $fh->print("/>\n"); + } + $fh->print("$depth\n"); + $self; +} + + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Bsln.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Bsln.pm new file mode 100644 index 0000000..281a512 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Bsln.pm @@ -0,0 +1,163 @@ +package Font::TTF::Bsln; + +=head1 NAME + +Font::TTF::Bsln - Baseline table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=item version + +=item xformat + +=item defaultBaseline + +=item deltas + +=item stdGlyph + +=item ctlPoints + +=item lookupFormat + +=item lookup + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); + +use Font::TTF::AATutils; +use Font::TTF::Utils; +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $fh); + + $self->SUPER::read or return $self; + + $fh = $self->{' INFILE'}; + $fh->read($dat, 8); + my ($version, $format, $defaultBaseline) = TTF_Unpack("vSS", $dat); + + if ($format == 0 or $format == 1) { + $fh->read($dat, 64); + $self->{'deltas'} = [TTF_Unpack("s*", $dat)]; + } + elsif ($format == 2 or $format == 3) { + $fh->read($dat, 2); + $self->{'stdGlyph'} = unpack("n", $dat); + $fh->read($dat, 64); + $self->{'ctlPoints'} = unpack("n*", $dat); + } + else { + die "unknown table format"; + } + + if ($format == 1 or $format == 3) { + my $len = $self->{' LENGTH'} - ($fh->tell() - $self->{' OFFSET'}); + my ($lookupFormat, $lookup) = AAT_read_lookup($fh, 2, $len, $defaultBaseline); + $self->{'lookupFormat'} = $lookupFormat; + $self->{'lookup'} = $lookup; + } + + $self->{'version'} = $version; + $self->{'format'} = $format; + $self->{'defaultBaseline'} = $defaultBaseline; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + my $format = $self->{'format'}; + my $defaultBaseline = $self->{'defaultBaseline'}; + $fh->print(TTF_Pack("vSS", $self->{'version'}, $format, $defaultBaseline)); + + AAT_write_lookup($fh, $self->{'lookupFormat'}, $self->{'lookup'}, 2, $defaultBaseline) if ($format == 1 or $format == 3); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + $self->read; + + $fh = 'STDOUT' unless defined $fh; + + my $format = $self->{'format'}; + $fh->printf("version %f\nformat %d\ndefaultBaseline %d\n", $self->{'version'}, $format, $self->{'defaultBaseline'}); + if ($format == 0 or $format == 1) { + $fh->printf("\tdeltas:\n"); + my $deltas = $self->{'deltas'}; + foreach (0 .. 31) { + $fh->printf("\t\t%d: %d%s\n", $_, $deltas->[$_], defined baselineName_($_) ? "\t# " . baselineName_($_) : ""); + } + } + if ($format == 2 or $format == 3) { + $fh->printf("\tstdGlyph = %d\n", $self->{'stdGlyph'}); + my $ctlPoints = $self->{'ctlPoints'}; + foreach (0 .. 31) { + $fh->printf("\t\t%d: %d%s\n", $_, $ctlPoints->[$_], defined baselineName_($_) ? "\t# " . baselineName_($_) : ""); + } + } + if ($format == 1 or $format == 3) { + $fh->printf("lookupFormat %d\n", $self->{'lookupFormat'}); + my $lookup = $self->{'lookup'}; + foreach (sort { $a <=> $b } keys %$lookup) { + $fh->printf("\tglyph %d: %d%s\n", $_, $lookup->{$_}, defined baselineName_($_) ? "\t# " . baselineName_($_) : ""); + } + } +} + +sub baselineName_ +{ + my ($b) = @_; + my @baselines = ( 'Roman', 'Ideographic centered', 'Ideographic low', 'Hanging', 'Math' ); + $baselines[$b]; +} + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Changes b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Changes new file mode 100644 index 0000000..2382b82 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Changes @@ -0,0 +1,108 @@ +Note. The version number implies a release point. Thus changes that go into a +version occur above the version number, not after it. + +* 0.05 +** cmap + debug reverse() + provide scripts as .pl instead of .bat to placate Unix world + rename makefile.pl to Makefile.PL to keep Unix happy + Add ttfremap script + +* 0.06 .. 0.08 + Fixes to get this stuff working in Unix + +* 0.09 + Never released + +* 0.10 +** cmap + Make reverse return the lowest codepoint that matches rather than + the highest +** font + Use IO::File everywhere to allow passing in of psuedo-file objects + rather than file names +** Utils + Debug FDot2.14 conversion + +* 0.11 +** cmap + Don't store empty entries in the cmap + +* 0.12 +Various changes to reduce warnings + +** glyph + Add update_bbox + Do full glyph writes if loca read rather than glyf read + Get glyph update working usefully. Clarify glyf->read + +* 0.13 + +** glyph + Debug update_bbox for compound glyphs + Add empty() to clear to unread state (allows apps to save memory) + +** OS/2 + update update() to account for new cmap structure + +** Post + Correct mu to pi in Postscript name list. The list now follows the + MS convention for good or ill. + +** Table + Add empty() to clear a table to its unread state + +** Scripts +*** psfix + Added. Creates Post table based on cmap information + +*** eurofix + Added bullet hacking and generally backwards, forwards, all + ways mapping. + +*** ttfenc + Now supports the difference between MS post name list and TeXs + +* 0.14 + + Sort out mix up over CVS mess + +* 0.15 + +** Table + read_dat no longer marks table as read + +** Cvt_ + Mark table as read when read + +** Fpgm + Mark table as read when read + +** Prep + Mark table as read when read + +** Font + Add support for Mac sfnt version code ('true') + Be stricter on out @fontlist, only output tables that exist + +* 0.16 + +** Install + add pmake support + +** glyph + tidy up pod + +** kern + tidy up pod + +** name + add utf8 support + +* 0.17 + +** Utils + Debug TTF_bininfo >>= seems to have stopped working! + +* 0.18 + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cmap.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cmap.pm new file mode 100644 index 0000000..bb3d63c --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cmap.pm @@ -0,0 +1,612 @@ +package Font::TTF::Cmap; + +=head1 NAME + +Font::TTF::Cmap - Character map table + +=head1 DESCRIPTION + +Looks after the character map. For ease of use, the actual cmap is held in +a hash against codepoint. Thus for a given table: + + $gid = $font->{'cmap'}{'Tables'}[0]{'val'}{$code}; + +Note that C<$code> should be a true value (0x1234) rather than a string representation. + +=head1 INSTANCE VARIABLES + +The instance variables listed here are not preceeded by a space due to their +emulating structural information in the font. + +=over 4 + +=item Num + +Number of subtables in this table + +=item Tables + +An array of subtables ([0..Num-1]) + +=back + +Each subtables also has its own instance variables which are, again, not +preceeded by a space. + +=over 4 + +=item Platform + +The platform number for this subtable + +=item Encoding + +The encoding number for this subtable + +=item Format + +Gives the stored format of this subtable + +=item Ver + +Gives the version (or language) information for this subtable + +=item val + +A hash keyed by the codepoint value (not a string) storing the glyph id + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the cmap into memory. Format 4 subtables read the whole subtable and +fill in the segmented array accordingly. + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $i, $j, $k, $id, @ids, $s); + my ($start, $end, $range, $delta, $form, $len, $num, $ver, $sg); + my ($fh) = $self->{' INFILE'}; + + $self->SUPER::read or return $self; + $fh->read($dat, 4); + $self->{'Num'} = unpack("x2n", $dat); + $self->{'Tables'} = []; + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = {}; + $fh->read($dat, 8); + ($s->{'Platform'}, $s->{'Encoding'}, $s->{'LOC'}) = (unpack("nnN", $dat)); + $s->{'LOC'} += $self->{' OFFSET'}; + push(@{$self->{'Tables'}}, $s); + } + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + $fh->seek($s->{'LOC'}, 0); + $fh->read($dat, 2); + $form = unpack("n", $dat); + + $s->{'Format'} = $form; + if ($form == 0) + { + my $j = 0; + + $fh->read($dat, 4); + ($len, $s->{'Ver'}) = unpack('n2', $dat); + $fh->read($dat, 256); + $s->{'val'} = {map {$j++; ($_ ? ($j - 1, $_) : ())} unpack("C*", $dat)}; + } elsif ($form == 6) + { + my ($start, $ecount); + + $fh->read($dat, 8); + ($len, $s->{'Ver'}, $start, $ecount) = unpack('n4', $dat); + $fh->read($dat, $ecount << 1); + $s->{'val'} = {map {$start++; ($_ ? ($start - 1, $_) : ())} unpack("n*", $dat)}; + } elsif ($form == 2) # Contributed by Huw Rogers + { + $fh->read($dat, 4); + ($len, $s->{'Ver'}) = unpack('n2', $dat); + $fh->read($dat, 512); + my ($j, $k, $l, $m, $n, @subHeaderKeys, @subHeaders, $subHeader); + $n = 1; + for ($j = 0; $j < 256; $j++) { + my $k = unpack('@'.($j<<1).'n', $dat)>>3; + $n = $k + 1 if $k >= $n; + $subHeaders[$subHeaderKeys[$j] = $k] ||= [ ]; + } + $fh->read($dat, $n<<3); # read subHeaders[] + for ($k = 0; $k < $n; $k++) { + $subHeader = $subHeaders[$k]; + $l = $k<<3; + @$subHeader = unpack('@'.$l.'n4', $dat); + $subHeader->[2] = unpack('s', pack('S', $subHeader->[2])) + if $subHeader->[2] & 0x8000; # idDelta + $subHeader->[3] = + ($subHeader->[3] - (($n - $k)<<3) + 6)>>1; # idRangeOffset + } + $fh->read($dat, $len - ($n<<3) - 518); # glyphIndexArray[] + for ($j = 0; $j < 256; $j++) { + $k = $subHeaderKeys[$j]; + $subHeader = $subHeaders[$k]; + unless ($k) { + $l = $j - $subHeader->[0]; + if ($l >= 0 && $l < $subHeader->[1]) { + $m = unpack('@'.(($l + $subHeader->[3])<<1).'n', $dat); + $m += $subHeader->[2] if $m; + $s->{'val'}{$j} = $m; + } + } else { + for ($l = 0; $l < $subHeader->[1]; $l++) { + $m = unpack('@'.(($l + $subHeader->[3])<<1).'n', $dat); + $m += $subHeader->[2] if $m; + $s->{'val'}{($j<<8) + $l + $subHeader->[0]} = $m; + } + } + } + } elsif ($form == 4) + { + $fh->read($dat, 12); + ($len, $s->{'Ver'}, $num) = unpack('n3', $dat); + $num >>= 1; + $fh->read($dat, $len - 14); + for ($j = 0; $j < $num; $j++) + { + $end = unpack("n", substr($dat, $j << 1, 2)); + $start = unpack("n", substr($dat, ($j << 1) + ($num << 1) + 2, 2)); + $delta = unpack("n", substr($dat, ($j << 1) + ($num << 2) + 2, 2)); + $delta -= 65536 if $delta > 32767; + $range = unpack("n", substr($dat, ($j << 1) + $num * 6 + 2, 2)); + for ($k = $start; $k <= $end; $k++) + { + if ($range == 0 || $range == 65535) # support the buggy FOG with its range=65535 for final segment + { $id = $k + $delta; } + else + { $id = unpack("n", substr($dat, ($j << 1) + $num * 6 + + 2 + ($k - $start) * 2 + $range, 2)) + $delta; } + $id -= 65536 if $id >= 65536; + $s->{'val'}{$k} = $id if ($id); + } + } + } elsif ($form == 8 || $form == 12) + { + $fh->read($dat, 10); + ($len, $s->{'Ver'}) = unpack('x2N2', $dat); + if ($form == 8) + { + $fh->read($dat, 8196); + $num = unpack("N", substr($dat, 8192, 4)); # don't need the map + } else + { + $fh->read($dat, 4); + $num = unpack("N", $dat); + } + $fh->read($dat, 12 * $num); + for ($j = 0; $j < $num; $j++) + { + ($start, $end, $sg) = unpack("N3", substr($dat, $j * 12, 12)); + for ($k = $start; $k <= $end; $k++) + { $s->{'val'}{$k} = $sg++; } + } + } elsif ($form == 10) + { + $fh->read($dat, 18); + ($len, $s->{'Ver'}, $start, $num) = unpack('x2N4', $dat); + $fh->read($dat, $num << 1); + for ($j = 0; $j < $num; $j++) + { $s->{'val'}{$start + $j} = unpack("n", substr($dat, $j << 1, 2)); } + } + } + $self; +} + + +=head2 $t->ms_lookup($uni) + +Finds a Unicode table, giving preference to the MS one, and looks up the given +Unicode codepoint in it to find the glyph id. + +=cut + +sub ms_lookup +{ + my ($self, $uni) = @_; + + $self->find_ms || return undef unless (defined $self->{' mstable'}); + return $self->{' mstable'}{'val'}{$uni}; +} + + +=head2 $t->find_ms + +Finds the a Unicode table, giving preference to the Microsoft one, and sets the C instance variable +to it if found. Returns the table it finds. + +=cut +sub find_ms +{ + my ($self) = @_; + my ($i, $s, $alt, $found); + + return $self->{' mstable'} if defined $self->{' mstable'}; + $self->read; + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + if ($s->{'Platform'} == 3) + { + $self->{' mstable'} = $s; + last if ($s->{'Encoding'} == 10); + $found = 1 if ($s->{'Encoding'} == 1); + } elsif ($s->{'Platform'} == 0 || ($s->{'Platform'} == 2 && $s->{'Encoding'} == 1)) + { $alt = $s; } + } + $self->{' mstable'} = $alt if ($alt && !$found); + $self->{' mstable'}; +} + + +=head2 $t->ms_enc + +Returns the encoding of the microsoft table (0 => symbol, etc.). Returns undef if there is +no Microsoft cmap. + +=cut + +sub ms_enc +{ + my ($self) = @_; + my ($s); + + return $self->{' mstable'}{'Encoding'} + if (defined $self->{' mstable'} && $self->{' mstable'}{'Platform'} == 3); + + foreach $s (@{$self->{'Tables'}}) + { + return $s->{'Encoding'} if ($s->{'Platform'} == 3); + } + return undef; +} + + +=head2 $t->out($fh) + +Writes out a cmap table to a filehandle. If it has not been read, then +just copies from input file to output + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($loc, $s, $i, $base_loc, $j, @keys); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + + $self->{'Tables'} = [sort {$a->{'Platform'} <=> $b->{'Platform'} + || $a->{'Encoding'} <=> $b->{'Encoding'} + || $a->{'Ver'} <=> $b->{'Ver'}} @{$self->{'Tables'}}]; + $self->{'Num'} = scalar @{$self->{'Tables'}}; + + $base_loc = $fh->tell(); + $fh->print(pack("n2", 0, $self->{'Num'})); + + for ($i = 0; $i < $self->{'Num'}; $i++) + { $fh->print(pack("nnN", $self->{'Tables'}[$i]{'Platform'}, $self->{'Tables'}[$i]{'Encoding'}, 0)); } + + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + if ($s->{'Format'} < 8) + { @keys = sort {$a <=> $b} grep { $_ <= 0xFFFF} keys %{$s->{'val'}}; } + else + { @keys = sort {$a <=> $b} keys %{$s->{'val'}}; } + $s->{' outloc'} = $fh->tell(); + if ($s->{'Format'} < 8) + { $fh->print(pack("n3", $s->{'Format'}, 0, $s->{'Ver'})); } # come back for length + else + { $fh->print(pack("n2N2", $s->{'Format'}, 0, 0, $s->{'Ver'})); } + + if ($s->{'Format'} == 0) + { + $fh->print(pack("C256", @{$s->{'val'}}{0 .. 255})); + } elsif ($s->{'Format'} == 6) + { + $fh->print(pack("n2", $keys[0], $keys[-1] - $keys[0] + 1)); + $fh->print(pack("n*", @{$s->{'val'}}{$keys[0] .. $keys[-1]})); + } elsif ($s->{'Format'} == 2) # Contributed by Huw Rogers + { + my ($g, $k, $h, $l, $m, $n); + my (@subHeaderKeys, @subHeaders, $subHeader, @glyphIndexArray); + $n = 0; + @subHeaderKeys = (-1) x 256; + for $j (@keys) { + next unless defined($g = $s->{'val'}{$j}); + $h = int($j>>8); + $l = ($j & 0xff); + if (($k = $subHeaderKeys[$h]) < 0) { + $subHeader = [ $l, 1, 0, 0, [ $g ] ]; + $subHeaders[$k = $n++] = $subHeader; + $subHeaderKeys[$h] = $k; + } else { + $subHeader = $subHeaders[$k]; + $m = ($l - $subHeader->[0] + 1) - $subHeader->[1]; + $subHeader->[1] += $m; + push @{$subHeader->[4]}, (0) x ($m - 1), $g - $subHeader->[2]; + } + } + @subHeaderKeys = map { $_ < 0 ? 0 : $_ } @subHeaderKeys; + $subHeader = $subHeaders[0]; + $subHeader->[3] = 0; + push @glyphIndexArray, @{$subHeader->[4]}; + splice(@$subHeader, 4); + { + my @subHeaders_ = sort {@{$a->[4]} <=> @{$b->[4]}} @subHeaders[1..$#subHeaders]; + my ($f, $d, $r, $subHeader_); + for ($k = 0; $k < @subHeaders_; $k++) { + $subHeader = $subHeaders_[$k]; + $f = $r = shift @{$subHeader->[4]}; + $subHeader->[5] = join(':', + map { + $d = $_ - $r; + $r = $_; + $d < 0 ? + sprintf('-%04x', -$d) : + sprintf('+%04x', $d) + } @{$subHeader->[4]}); + unshift @{$subHeader->[4]}, $f; + } + for ($k = 0; $k < @subHeaders_; $k++) { + $subHeader = $subHeaders_[$k]; + next unless $subHeader->[4]; + $subHeader->[3] = @glyphIndexArray; + push @glyphIndexArray, @{$subHeader->[4]}; + for ($l = $k + 1; $l < @subHeaders_; $l++) { + $subHeader_ = $subHeaders_[$l]; + next unless $subHeader_->[4]; + $d = $subHeader_->[5]; + if ($subHeader->[5] =~ /\Q$d\E/) { + my $o = length($`)/6; #` + $subHeader_->[2] += + $subHeader_->[4]->[$o] - $subHeader->[4]->[0]; + $subHeader_->[3] = $subHeader->[3] + $o; + splice(@$subHeader_, 4); + } + } + splice(@$subHeader, 4); + } + } + $fh->print(pack('n*', map { $_<<3 } @subHeaderKeys)); + for ($j = 0; $j < 256; $j++) { + $k = $subHeaderKeys[$j]; + $subHeader = $subHeaders[$k]; + } + for ($k = 0; $k < $n; $k++) { + $subHeader = $subHeaders[$k]; + $fh->print(pack('n4', + $subHeader->[0], + $subHeader->[1], + $subHeader->[2] < 0 ? + unpack('S', pack('s', $subHeader->[2])) : + $subHeader->[2], + ($subHeader->[3]<<1) + (($n - $k)<<3) - 6 + )); + } + $fh->print(pack('n*', @glyphIndexArray)); + } elsif ($s->{'Format'} == 4) + { + my ($num, $sRange, $eSel, $eShift, @starts, @ends, $doff); + my (@deltas, $delta, @range, $flat, $k, $segs, $count, $newseg, $v); + + push(@keys, 0xFFFF) unless ($keys[-1] == 0xFFFF); + $newseg = 1; $num = 0; + for ($j = 0; $j <= $#keys && $keys[$j] <= 0xFFFF; $j++) + { + $v = $s->{'val'}{$keys[$j]} || 0; + if ($newseg) + { + $delta = $v; + $doff = $j; + $flat = 1; + push(@starts, $keys[$j]); + $newseg = 0; + } + $delta = 0 if ($delta + $j - $doff != $v); + $flat = 0 if ($v == 0); + if ($j == $#keys || $keys[$j] + 1 != $keys[$j+1]) + { + push (@ends, $keys[$j]); + push (@deltas, $delta ? $delta - $keys[$doff] : 0); + push (@range, $flat); + $num++; + $newseg = 1; + } + } + + ($num, $sRange, $eSel, $eShift) = Font::TTF::Utils::TTF_bininfo($num, 2); + $fh->print(pack("n4", $num * 2, $sRange, $eSel, $eShift)); + $fh->print(pack("n*", @ends)); + $fh->print(pack("n", 0)); + $fh->print(pack("n*", @starts)); + $fh->print(pack("n*", @deltas)); + + $count = 0; + for ($j = 0; $j < $num; $j++) + { + $delta = $deltas[$j]; + if ($delta != 0 && $range[$j] == 1) + { $range[$j] = 0; } + else + { + $range[$j] = ($count + $num - $j) << 1; + $count += $ends[$j] - $starts[$j] + 1; + } + } + + $fh->print(pack("n*", @range)); + + for ($j = 0; $j < $num; $j++) + { + next if ($range[$j] == 0); + $fh->print(pack("n*", map {$_ || 0} @{$s->{'val'}}{$starts[$j] .. $ends[$j]})); + } + } elsif ($s->{'Format'} == 8 || $s->{'Format'} == 12) + { + my (@jobs, $start, $current, $curr_glyf, $map); + + $current = 0; $curr_glyf = 0; + $map = "\000" x 8192; + foreach $j (@keys) + { + if ($j > 0xFFFF) + { + if (defined $s->{'val'}{$j >> 16}) + { $s->{'Format'} = 12; } + vec($map, $j >> 16, 1) = 1; + } + if ($j != $current + 1 || $s->{'val'}{$j} != $curr_glyf + 1) + { + push (@jobs, [$start, $current, $curr_glyf - ($current - $start)]) if (defined $start); + $start = $j; $current = $j; $curr_glyf = $s->{'val'}{$j}; + } + $current = $j; + $curr_glyf = $s->{'val'}{$j}; + } + push (@jobs, [$start, $current, $curr_glyf - ($current - $start)]) if (defined $start); + $fh->print($map) if ($s->{'Format'} == 8); + $fh->print(pack('N', $#jobs + 1)); + foreach $j (@jobs) + { $fh->print(pack('N3', @{$j})); } + } elsif ($s->{'Format'} == 10) + { + $fh->print(pack('N2', $keys[0], $keys[-1] - $keys[0] + 1)); + $fh->print(pack('n*', $s->{'val'}{$keys[0] .. $keys[-1]})); + } + + $loc = $fh->tell(); + if ($s->{'Format'} < 8) + { + $fh->seek($s->{' outloc'} + 2, 0); + $fh->print(pack("n", $loc - $s->{' outloc'})); + } else + { + $fh->seek($s->{' outloc'} + 4, 0); + $fh->print(pack("N", $loc - $s->{' outloc'})); + } + $fh->seek($base_loc + 8 + ($i << 3), 0); + $fh->print(pack("N", $s->{' outloc'} - $base_loc)); + $fh->seek($loc, 0); + } + $self; +} + + +=head2 $t->XML_element($context, $depth, $name, $val) + +Outputs the elements of the cmap in XML. We only need to process val here + +=cut + +sub XML_element +{ + my ($self, $context, $depth, $k, $val) = @_; + my ($fh) = $context->{'fh'}; + my ($i); + + return $self if ($k eq 'LOC'); + return $self->SUPER::XML_element($context, $depth, $k, $val) unless ($k eq 'val'); + + $fh->print("$depth\n"); + foreach $i (sort {$a <=> $b} keys %{$val}) + { $fh->printf("%s\n", $depth . $context->{'indent'}, $i, $val->{$i}); } + $fh->print("$depth\n"); + $self; +} + +=head2 @map = $t->reverse(%opt) + +Returns a reverse map of the Unicode cmap. I.e. given a glyph gives the Unicode value for it. Options are: + +=over 4 + +=item tnum + +Table number to use rather than the default Unicode table + +=item array + +Returns each element of reverse as an array since a glyph may be mapped by more +than one Unicode value. The arrays are unsorted. Otherwise store any one unicode value for a glyph. + +=back + +=cut + +sub reverse +{ + my ($self, %opt) = @_; + my ($table) = defined $opt{'tnum'} ? $self->{'Tables'}[$opt{'tnum'}] : $self->find_ms; + my (@res, $code, $gid); + + while (($code, $gid) = each(%{$table->{'val'}})) + { + if ($opt{'array'}) + { push (@{$res[$gid]}, $code); } + else + { $res[$gid] = $code unless (defined $res[$gid] && $res[$gid] > 0 && $res[$gid] < $code); } + } + @res; +} + + +=head2 is_unicode($index) + +Returns whether the table of a given index is known to be a unicode table +(as specified in the specifications) + +=cut + +sub is_unicode +{ + my ($self, $index) = @_; + my ($pid, $eid) = ($self->{'Tables'}[$index]{'Platform'}, $self->{'Tables'}[$index]{'Encoding'}); + + return ($pid == 3 || $pid == 0 || ($pid == 2 && $eid == 1)); +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +No support for format 2 tables (MBCS) + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Coverage.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Coverage.pm new file mode 100644 index 0000000..909f910 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Coverage.pm @@ -0,0 +1,323 @@ +package Font::TTF::Coverage; + +=head1 NAME + +Font::TTF::Coverage - Opentype coverage and class definition objects + +=head1 DESCRIPTION + +Coverage tables and class definition objects are virtually identical concepts +in OpenType. Their difference comes purely in their storage. Therefore we can +say that a coverage table is a class definition in which the class definition +for each glyph is the corresponding index in the coverage table. The resulting +data structure is that a Coverage table has the following fields: + +=item cover + +A boolean to indicate whether this table is a coverage table (TRUE) or a +class definition (FALSE) + +=item val + +A hash of glyph ids against values (either coverage index or class value) + +=item fmt + +The storage format used is given here, but is recalculated when the table +is written out. + +=item count + +A count of the elements in a coverage table for use with add. Each subsequent +addition is added with the current count and increments the count. + +=head1 METHODS + +=cut + +=head2 new($isCover [, vals]) + +Creates a new coverage table or class definition table, depending upon the +value of $isCover. if $isCover then vals may be a list of glyphs to include in order. +If no $isCover, then vals is a hash of glyphs against class values. + +=cut + +sub new +{ + my ($class) = shift; + my ($isCover) = shift; + my ($self) = {}; + + $self->{'cover'} = $isCover; + $self->{'count'} = 0; + if ($isCover) + { + my ($v); + foreach $v (@_) + { $self->{'val'}{$v} = $self->{'count'}++; } + } + else + { + $self->{'val'} = {@_}; + foreach (values %{$self->{'val'}}) {$self->{'max'} = $_ if $_ > $self->{'max'}} + } + bless $self, $class; +} + + +=head2 read($fh) + +Reads the coverage/class table from the given file handle + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat, $fmt, $num, $i, $c); + + $fh->read($dat, 4); + ($fmt, $num) = unpack("n2", $dat); + $self->{'fmt'} = $fmt; + + if ($self->{'cover'}) + { + if ($fmt == 1) + { + $fh->read($dat, $num << 1); + map {$self->{'val'}{$_} = $i++} unpack("n*", $dat); + } elsif ($fmt == 2) + { + $fh->read($dat, $num * 6); + for ($i = 0; $i < $num; $i++) + { + ($first, $last, $c) = unpack("n3", substr($dat, $i * 6, 6)); + map {$self->{'val'}{$_} = $c++} ($first .. $last); + } + } + $self->{'count'} = $num; + } elsif ($fmt == 1) + { + $fh->read($dat, 2); + $first = $num; + ($num) = unpack("n", $dat); + $fh->read($dat, $num << 1); + map {$self->{'val'}{$first++} = $_; $self->{'max'} = $_ if ($_ > $self->{'max'})} unpack("n*", $dat); + } elsif ($fmt == 2) + { + $fh->read($dat, $num * 6); + for ($i = 0; $i < $num; $i++) + { + ($first, $last, $c) = unpack("n3", substr($dat, $i * 6, 6)); + map {$self->{'val'}{$_} = $c} ($first .. $last); + $self->{'max'} = $c if ($c > $self->{'max'}); + } + } + $self; +} + + +=head2 out($fh, $state) + +Writes the coverage/class table to the given file handle. If $state is 1 then +the output string is returned rather than being output to a filehandle. + +=cut + +sub out +{ + my ($self, $fh, $state) = @_; + my ($g, $eff, $grp, $out); + my ($shipout) = ($state ? sub {$out .= $_[0];} : sub {$fh->print($_[0]);}); + my (@gids) = sort {$a <=> $b} keys %{$self->{'val'}}; + + $fmt = 1; $grp = 1; $eff = 0; + for ($i = 1; $i <= $#gids; $i++) + { + if ($self->{'val'}{$gids[$i]} < $self->{'val'}{$gids[$i-1]} && $self->{'cover'}) + { + $fmt = 2; + last; + } elsif ($gids[$i] == $gids[$i-1] + 1 && ($self->{'cover'} || $self->{'val'}{$gids[$i]} == $self->{'val'}{$gids[$i-1]})) + { $eff++; } + else + { + $grp++; + $eff += $gids[$i] - $gids[$i-1] if (!$self->{'cover'}); + } + } +# if ($self->{'cover'}) + { $fmt = 2 if ($eff / $grp > 3); } +# else +# { $fmt = 2 if ($grp > 1); } + + if ($fmt == 1 && $self->{'cover'}) + { + my ($last) = 0; + &$shipout(pack('n2', 1, scalar @gids)); + &$shipout(pack('n*', @gids)); + } elsif ($fmt == 1) + { + my ($last) = $gids[0]; + &$shipout(pack("n3", 1, $last, $gids[-1] - $last + 1)); + foreach $g (@gids) + { + if ($g > $last + 1) + { &$shipout(pack('n*', (0) x ($g - $last - 1))); } + &$shipout(pack('n', $self->{'val'}{$g})); + $last = $g; + } + } else + { + my ($start, $end, $ind, $numloc, $endloc, $num); + &$shipout(pack("n2", 2, 0)); + $numloc = $fh->tell() - 2 unless $state; + + $start = 0; $end = 0; $num = 0; + while ($end < $#gids) + { + if ($gids[$end + 1] == $gids[$end] + 1 + && $self->{'val'}{$gids[$end + 1]} + == $self->{'val'}{$gids[$end]} + + ($self->{'cover'} ? 1 : 0)) + { + $end++; + next; + } + + &$shipout(pack("n3", $gids[$start], $gids[$end], + $self->{'val'}{$gids[$start]})); + $start = $end + 1; + $end++; + $num++; + } + &$shipout(pack("n3", $gids[$start], $gids[$end], + $self->{'val'}{$gids[$start]})); + $num++; + if ($state) + { substr($out, 2, 2) = pack('n', $num); } + else + { + $endloc = $fh->tell(); + $fh->seek($numloc, 0); + $fh->print(pack("n", $num)); + $fh->seek($endloc, 0); + } + } + return ($state ? $out : $self); +} + + +=head2 $c->add($glyphid[, $class]) + +Adds a glyph id to the coverage table incrementing the count so that each subsequent addition +has the next sequential number. Returns the index number of the glyphid added + +=cut + +sub add +{ + my ($self, $gid, $class) = @_; + + return $self->{'val'}{$gid} if (defined $self->{'val'}{$gid}); + if ($self->{'cover'}) + { + $self->{'val'}{$gid} = $self->{'count'}; + return $self->{'count'}++; + } + else + { + $self->{'val'}{$gid} = $class || '0'; + $self->{'max'} = $class if ($class > $self->{'max'}); + return $class; + } +} + + +=head2 $c->signature + +Returns a vector of all the glyph ids covered by this coverage table or class + +=cut + +sub signature +{ + my ($self) = @_; + my ($vec, $range, $size); + +if (0) +{ + if ($self->{'cover'}) + { $range = 1; $size = 1; } + else + { + $range = $self->{'max'}; + $size = 1; + while ($range > 1) + { + $size = $size << 1; + $range = $range >> 1; + } + $range = $self->{'max'} + 1; + } + foreach (keys %{$self->{'val'}}) + { vec($vec, $_, $size) = $self->{'val'}{$_} > $range ? $range : $self->{'val'}{$_}; } + length($vec) . ":" . $vec; +} + $vec = join(";", map{"$_,$self->{'val'}{$_}"} keys %{$self->{'val'}}); +} + +=head2 @map=$c->sort + +Sorts the coverage table so that indexes are in ascending order of glyphid. +Returns a map such that $map[$new_index]=$old_index. + +=cut + +sub sort +{ + my ($self) = @_; + my (@res, $i); + + foreach (sort {$a <=> $b} keys %{$self->{'val'}}) + { + push(@res, $self->{'val'}{$_}); + $self->{'val'}{$_} = $i++; + } + @res; +} + +=head2 $c->out_xml($context) + +Outputs this coverage/class in XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + + $fh->print("$depth<" . ($self->{'cover'} ? 'coverage' : 'class') . ">\n"); + foreach $gid (sort {$a <=> $b} keys %{$self->{'val'}}) + { + $fh->printf("$depth$context->{'indent'}\n", $gid, $self->{'val'}{$gid}); + } + $fh->print("$depth{'cover'} ? 'coverage' : 'class') . ">\n"); + $self; +} + +sub release +{ } + + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cvt_.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cvt_.pm new file mode 100644 index 0000000..2a79f5b --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Cvt_.pm @@ -0,0 +1,81 @@ +package Font::TTF::Cvt_; + +=head1 NAME + +Font::TTF::Cvt_ - Control Value Table in a TrueType font + +=head1 DESCRIPTION + +This is a minimal class adding nothing beyond a table, but is a repository +for cvt type information for those processes brave enough to address hinting. + +=head1 INSTANCE VARIABLES + +=over 4 + +=item val + +This is an array of CVT values. Thus access to the CVT is via: + + $f->{'cvt_'}{'val'}[$num]; + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA $VERSION); +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); + +$VERSION = 0.0001; + +=head2 $t->read + +Reads the CVT table into both the tables C<' dat'> variable and the C +array. + +=cut + +sub read +{ + my ($self) = @_; + + $self->read_dat || return undef; + $self->{' read'} = 1; + $self->{'val'} = [TTF_Unpack("s*", $self->{' dat'})]; + $self; +} + + +=head2 $t->update + +Updates the RAM file copy C<' dat'> to be the same as the array. + +=cut + +sub update +{ + my ($self) = @_; + + return undef unless ($self->{' read'} && $#{$self->{'val'}} >= 0); + $self->{' dat'} = TTF_Pack("s*", @{$self->{'val'}}); + $self; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Delta.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Delta.pm new file mode 100644 index 0000000..7d63757 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Delta.pm @@ -0,0 +1,173 @@ +package Font::TTF::Delta; + +=head1 NAME + +Font::TTF::Delta - Opentype Device tables + +=head1 DESCRIPTION + +Each device table corresponds to a set of deltas for a particular point over +a range of ppem values. + +=item first + +The first ppem value in the range + +=item last + +The last ppem value in the range + +=item val + +This is an array of deltas corresponding to each ppem in the range between +first and last inclusive. + +=item fmt + +This is the fmt used (log2 of number bits per value) when the device table was +read. It is recalculated on output. + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Utils; + +=head2 new + +Creates a new device table + +=cut + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + bless $self, $class; +} + + +=head2 read + +Reads a device table from the given IO object at the current location + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat, $fmt, $num, $i, $j, $mask); + + $fh->read($dat, 6); + ($self->{'first'}, $self->{'last'}, $fmt) = TTF_Unpack("S3", $dat); + $self->{'fmt'} = $fmt; + + $fmt = 1 << $fmt; + $num = ((($self->{'last'} - $self->{'first'} + 1) * $fmt) + 15) >> 8; + $fh->read($dat, $num); + + $mask = (0xffff << (16 - $fmt)) & 0xffff; + $j = 0; + for ($i = $self->{'first'}; $i <= $self->{'last'}; $i++) + { + if ($j == 0) + { + $num = TTF_Unpack("S", substr($dat, 0, 2)); + substr($dat, 0, 2) = ''; + } + push (@{$self->{'val'}}, ($num & $mask) >> (16 - $fmt)); + $num <<= $fmt; + $j += $fmt; + $j = 0 if ($j >= 16); + } + $self; +} + + +=head2 out($fh, $style) + +Outputs a device table to the given IO object at the current location, or just +returns the data to be output if $style != 0 + +=cut + +sub out +{ + my ($self, $fh, $style) = @_; + my ($dat, $fmt, $num, $mask, $j, $f, $out); + + foreach $f (@{$self->{'val'}}) + { + my ($tfmt) = $f > 0 ? $f + 1 : -$f; + $fmt = $tfmt if $tfmt > $fmt; + } + + if ($fmt > 8) + { $fmt = 3; } + elsif ($fmt > 2) + { $fmt = 2; } + else + { $fmt = 1; } + + $out = TTF_Pack("S3", $self->{'first'}, $self->{'last'}, $fmt); + + $fmt = 1 << $fmt; + $mask = 0xffff >> (16 - $fmt); + $j = 0; $dat = 0; + foreach $f (@{$self->{'val'}}) + { + $dat |= ($f & $mask) << (16 - $fmt - $j); + $j += $fmt; + if ($j >= 16) + { + $j = 0; + $out .= TTF_Pack("S", $dat); + $dat = 0; + } + } + $out .= pack('n', $dat) if ($j > 0); + $fh->print($out) unless $style; + $out; +} + +=head2 $d->signature() + +Returns a content based identifying string for this delta for +compression purposes + +=cut + +sub signature +{ + my ($self) = @_; + return join (",", $self->{'first'}, $self->{'last'}, @{$self->{'val'}}); +} + + +=head2 $d->out_xml($context) + +Outputs a delta in XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + + $fh->printf("%s\n", $depth, $self->{'first'}, $self->{'last'}); + $fh->print("$depth$context->{'indent'}" . join (' ', @{$self->{'val'}}) . "\n") if defined ($self->{'val'}); + $fh->print("$depth\n"); +} + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Dumper.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Dumper.pm new file mode 100644 index 0000000..7cc8699 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Dumper.pm @@ -0,0 +1,79 @@ +package Font::TTF::Dumper; + +=head1 NAME + +Font::TTF::Dumper - Debug dump of a font datastructure, avoiding recursion on ' PARENT' + +=head1 SYNOPSIS + + Font::TTF::Dumper; + + # Print a table from the font structure: + print ttfdump($font->{$tag}); + + # Print font table with name + print ttfdump($font->{'head'}, 'head'); + + # Print one glyph's data: + print ttfdump($font->{'loca'}->read->{'glyphs'}[$gid], "glyph_$gid"); + +=head1 DESCRIPTION + +Font::TTF data structures are trees created from hashes and arrays. When trying to figure +out how the structures work, sometimes it is helpful to use Data::Dumper on them. However, +many of the object structures have ' PARENT' links that refer back to the object's parent, +which means that Data::Dumper ends up dumping the whole font no matter what. + +The purpose of this module is to do just one thing: invoke Data::Dumper with a +filter that skips over the ' PARENT' element of any hash. + +To reduce output further, this module also skips over ' CACHE' elements and any +hash element whose value is a Font::TTF::Glyph or Font::TTF::Font object. +(Really should make this configurable.) + +=cut + +use strict; +use Data::Dumper; + +use vars qw(@EXPORT @ISA); +require Exporter; +@ISA = qw( Exporter ); +@EXPORT = qw( ttfdump ); + +my %skip = ( Font => 1, Glyph => 1 ); + +sub ttfdump +{ + my ($var, $name) = @_; + my $res; + + my $d = Data::Dumper->new([$var]); + $d->Names([$name]) if defined $name; + $d->Sortkeys(\&myfilter); # This is the trick to keep from dumping the whole font + $d->Indent(3); # I want array indicies + $d->Useqq(1); # Perlquote -- slower but there might be binary data. + $res = $d->Dump; + $d->DESTROY; + $res; +} + +sub myfilter +{ + my ($hash) = @_; + my @a = grep { + ($_ eq ' PARENT' || $_ eq ' CACHE') ? 0 : + ref($hash->{$_}) =~ m/^Font::TTF::(.*)$/ ? !$skip{$1} : + 1 + } (keys %{$hash}) ; + # Sort numerically if that is reasonable: + return [ sort {$a =~ /\D/ || $b =~ /\D/ ? $a cmp $b : $a <=> $b} @a ]; +} + +1; + +=head1 See also + +L + +=cut diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBDT.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBDT.pm new file mode 100644 index 0000000..257e8d7 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBDT.pm @@ -0,0 +1,285 @@ +package Font::TTF::EBDT; + +=head1 NAME + +Font::TTF::EBDT - Embeeded Bitmap Data Table + +=head1 DESCRIPTION + +Contains the metrics and bitmap image data. + +=head1 INSTANCE VARIABLES + +Only has 'bitmap' instance variable. It is an array of assosiative +array keyed by glyph-id. The element is an object which consists +of metric information and image data. + +=over 4 + +=item bitmap object + +=over 8 +=item format +Only 7 is supported. +=item height +=item width +=item horiBearingX +=item horiBearingY +=item horiAdvance +=item vertBearingX +=item vertBearingY +=item vertAdvance +=item imageData + +=back + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the embedded bitmap data from the TTF file into memory. +This routine should be called _after_ {'EBLC'}->read. + +=cut + +sub read +{ + my ($self) = shift; + my ($fh) = $self->{' INFILE'}; + my ($i, $dat); + my ($eblc) = $self->{' PARENT'}->{'EBLC'}; + my ($bst_array); + + $eblc->read; + $self->SUPER::read || return $self; + + # ebdtHeader + $fh->read($dat, 4); # version + + $bst_array = $eblc->{'bitmapSizeTable'}; + + for ($i = 0; $i < $eblc->{'Num'}; $i++) + { + my ($bst) = $bst_array->[$i]; + my ($format) = $bst->{'imageFormat'}; + my ($offset) = $bst->{'imageDataOffset'}; + my ($j); + my ($ist_array) = $eblc->{'indexSubTableArray'}[$i]; + my ($bitmap) = {}; + + die "Only EBDT format 7 is implemented." unless ($format == 7); + + $self->{'bitmap'}[$i] = $bitmap; + + for ($j = 0; $j < $bst->{'numberOfIndexSubTables'}; $j++) { + my ($ista) = $ist_array->[$j]; + my ($offsetArray) = $eblc->{'indexSubTable'}[$i][$j]; + my ($p, $o0, $c); + +# if ($fh->tell != $self->{' OFFSET'} + $offset) { +# $fh->seek($self->{' OFFSET'} + $offset, 0); +# } + + $p = 0; + $o0 = $offsetArray->[$p++]; + for ($c = $ista->{'firstGlyphIndex'}; $c <= $ista->{'lastGlyphIndex'}; $c++) + { + my ($b) = {}; + my ($o1) = $offsetArray->[$p++]; + my ($len) = $o1 - $o0 - 8; + +# if ($fh->tell != $self->{' OFFSET'} + $offset + $o0) { +# $fh->seek($self->{' OFFSET'} + $offset + $o0, 0); +# } + + $fh->read($dat, 8); + ($b->{'height'}, + $b->{'width'}, + $b->{'horiBearingX'}, + $b->{'horiBearingY'}, + $b->{'horiAdvance'}, + $b->{'vertBearingX'}, + $b->{'vertBearingY'}, + $b->{'vertAdvance'}) + = unpack("cccccccc", $dat); + + $fh->read($dat, $len); + $b->{'imageData'} = $dat; + $b->{'format'} = 7; # bitmap and bigMetrics + + $bitmap->{$c} = $b; + $o0 = $o1; + } + + $offset += $o0; + } + } + + $self; +} + + +=head2 $t->update + +Update EBLC information using EBDT data. + +=cut + +sub get_regions +{ + my (@l) = @_; + my (@r) = (); + my ($e); + my ($first); + my ($last); + + $first = $l[0]; + $last = $first - 1; + foreach $e (@l) { + if ($last + 1 != $e) { # not contiguous + $r[++$#r] = [$first, $last]; + $first = $e; + } + + $last = $e; + } + + $r[++$#r] = [$first, $last]; + @r; +} + +sub update +{ + my ($self) = @_; + my ($eblc) = $self->{' PARENT'}->{'EBLC'}; + my ($bst_array) = []; + my ($offset) = 4; + my ($i); + my ($bitmap_array) = $self->{'bitmap'}; + my ($istao) = 8 + 48 * $eblc->{'Num'}; + + $eblc->{'bitmapSizeTable'} = $bst_array; + + for ($i = 0; $i < $eblc->{'Num'}; $i++) { + my ($bst) = {}; + my ($ist_array) = []; + my ($j); + my ($bitmap) = $bitmap_array->[$i]; + my (@regions) = get_regions(sort {$a <=> $b} keys (%$bitmap)); + my ($aotis) = 8 * (1+$#regions); + + $bst->{'indexFormat'} = 1; + $bst->{'imageFormat'} = 7; + $bst->{'imageDataOffset'} = $offset; + $bst->{'numberOfIndexSubTables'} = 1+$#regions; + $bst->{'indexSubTableArrayOffset'} = $istao; + $bst->{'colorRef'} = 0; + + $bst->{'startGlyphIndex'} = $regions[0][0]; + $bst->{'endGlyphIndex'} = $regions[-1][1]; + $bst->{'bitDepth'} = 1; + $bst->{'flags'} = 1; # Horizontal + $bst_array->[$i] = $bst; + + $eblc->{'indexSubTableArray'}[$i] = $ist_array; + for ($j = 0; $j <= $#regions; $j++) { + my ($ista) = {}; + my ($offsetArray) = []; + my ($p, $o0, $c); + $ist_array->[$j] = $ista; + + $ista->{'firstGlyphIndex'} = $regions[$j][0]; + $ista->{'lastGlyphIndex'} = $regions[$j][1]; + $ista->{'additionalOffsetToIndexSubtable'} = $aotis; + $eblc->{'indexSubTable'}[$i][$j] = $offsetArray; + $p = 0; + $o0 = 0; + for ($c = $regions[$j][0]; $c <= $regions[$j][1]; $c++) { + my ($b) = $bitmap->{$c}; + + $offsetArray->[$p++] = $o0; + $o0 += 8 + length($b->{'imageData'}); + } + + $offsetArray->[$p++] = $o0; + + $aotis += ($regions[$j][1] - $regions[$j][0] + 1 + 1)*4; + $offset += $o0; + + # Do we need the element of 0x10007 and absolute offset here, + # at the end of offsetArray? +# if ($j + 1 <= $#regions) { +# $offsetArray->[$p++] = 0x10007; +# $offsetArray->[$p++] = $offset; +# $aotis += 8; +# } + } + + $istao += $aotis + 8; + $bst->{'indexTablesSize'} = $aotis + 8; + } +} + +=head2 $t->out($fh) + +Outputs the bitmap data of embedded bitmap for this font. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($eblc) = $self->{' PARENT'}->{'EBLC'}; + my ($i); + my ($bitmap_array) = $self->{'bitmap'}; + + $fh->print(pack("N", 0x00020000)); + + for ($i = 0; $i < $eblc->{'Num'}; $i++) { + my ($j); + my ($bitmap) = $bitmap_array->[$i]; + my (@regions) = get_regions(sort {$a <=> $b} keys (%$bitmap)); + + for ($j = 0; $j <= $#regions; $j++) { + my ($c); + + for ($c = $regions[$j][0]; $c <= $regions[$j][1]; $c++) { + my ($b) = $bitmap->{$c}; + + $fh->print(pack("cccccccc", + $b->{'height'}, $b->{'width'}, + $b->{'horiBearingX'}, $b->{'horiBearingY'}, + $b->{'horiAdvance'}, $b->{'vertBearingX'}, + $b->{'vertBearingY'}, $b->{'vertAdvance'})); + $fh->print($b->{'imageData'}); + } + } + } +} + +1; + +=head1 BUGS + +Only Format 7 is implemented. XML output is not supported (yet). + +=head1 AUTHOR + +NIIBE Yutaka L. See L for copyright and +licensing. + +This was written at the CodeFest Akihabara 2006 hosted by FSIJ. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBLC.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBLC.pm new file mode 100644 index 0000000..7ba6e68 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/EBLC.pm @@ -0,0 +1,228 @@ +package Font::TTF::EBLC; + +=head1 NAME + +Font::TTF::EBLC - Embeeded Bitmap Location Table + +=head1 DESCRIPTION + +Contains the sizes and glyph ranges of bitmaps, and the offsets to +glyph bitmap data in indexSubTables for EBDT. + +Possibly contains glyph metrics information. + +=head1 INSTANCE VARIABLES +The information specified 'B<(R)>ead only' is read only, those +are calculated from EBDT, when it is 'update'-ed. + +=over 4 + +=item bitmapSizeTable +An array of tables of following information + +=over 8 +=item indexSubTableArrayOffset (R) +=item indexTablesSize (R) +=item numberOfIndexSubTables (R) +=item colorRef +=item hori +=item vert +=item startGlyphIndex (R) +=item endGlyphIndex (R) +=item ppemX +=item ppemY +=item bitDepth +=item flags +=back + +=item indexSubTableArray (R) +An array which contains range information. + +=item indexSubTable (R) +An array which contains offsets of EBDT table. + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the location information of embedded bitmap from the TTF file into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($i, $dat); + my ($indexSubTableArrayOffset, + $indexTablesSize, + $numberOfIndexSubTables, + $colorRef); + my ($startGlyphIndex, + $endGlyphIndex, + $ppemX, $ppemY, + $bitDepth, $flags); + my (@hori, @vert); + my ($bst, $ista, $ist); + my ($j); + + $self->SUPER::read or return $self; + + # eblcHeader + $fh->read($dat, 4); + $self->{'version'} = unpack("N",$dat); + + $fh->read($dat, 4); + $self->{'Num'} = unpack("N",$dat); + + # bitmapSizeTable + for ($i = 0; $i < $self->{'Num'}; $i++) { + $fh->read($dat, 16); + ($indexSubTableArrayOffset, $indexTablesSize, + $numberOfIndexSubTables, $colorRef) = unpack("NNNN", $dat); + $fh->read($dat, 12); @hori = unpack("cccccccccccc", $dat); + $fh->read($dat, 12); @vert = unpack("cccccccccccc", $dat); + + $fh->read($dat, 8); + ($startGlyphIndex, $endGlyphIndex, + $ppemX, $ppemY, $bitDepth, $flags) = unpack("nnCCCC", $dat); + + $self->{'bitmapSizeTable'}[$i] = { + 'indexSubTableArrayOffset' => $indexSubTableArrayOffset, + 'indexTablesSize' => $indexTablesSize, + 'numberOfIndexSubTables' => $numberOfIndexSubTables, + 'colorRef' => $colorRef, + 'hori' => [@hori], + 'vert' => [@vert], + 'startGlyphIndex' => $startGlyphIndex, + 'endGlyphIndex' => $endGlyphIndex, + 'ppemX' => $ppemX, + 'ppemY' => $ppemY, + 'bitDepth' => $bitDepth, + 'flags' => $flags + }; + } + + for ($i = 0; $i < $self->{'Num'}; $i++) { + my ($count, $x); + + $bst = $self->{'bitmapSizeTable'}[$i]; + + for ($j = 0; $j < $bst->{'numberOfIndexSubTables'}; $j++) { + $ista = {}; + + # indexSubTableArray + $self->{'indexSubTableArray'}[$i][$j] = $ista; + $fh->read($dat, 8); + ($ista->{'firstGlyphIndex'}, + $ista->{'lastGlyphIndex'}, + $ista->{'additionalOffsetToIndexSubtable'}) + = unpack("nnN", $dat); + } + + # indexSubTable + # indexSubHeader + $fh->read($dat, 8); + ($bst->{'indexFormat'}, + $bst->{'imageFormat'}, + $bst->{'imageDataOffset'}) = unpack("nnN", $dat); + + die "Only indexFormat == 1 is supported" unless ($bst->{'indexFormat'} == 1); + + for ($j = 0; $j < $bst->{'numberOfIndexSubTables'}; $j++) { + $ista = $self->{'indexSubTableArray'}[$i][$j]; + $count = $ista->{'lastGlyphIndex'} - $ista->{'firstGlyphIndex'} + 1 + 1; + $fh->seek($self->{' OFFSET'} + $bst->{'indexSubTableArrayOffset'} + + $ista->{'additionalOffsetToIndexSubtable'} + 8, 0); + +# $count += 2 if $j < $bst->{'numberOfIndexSubTables'} - 1; + + $fh->read($dat, 4*$count); + + $self->{'indexSubTable'}[$i][$j] = [unpack("N*", $dat)]; + } + } + + $self; +} + +=head2 $t->out($fh) + +Outputs the location information of embedded bitmap for this font. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i); + my ($bst_array) = $self->{'bitmapSizeTable'}; + + $fh->print(pack("N", 0x00020000)); + $fh->print(pack("N", $self->{'Num'})); + + for ($i = 0; $i < $self->{'Num'}; $i++) { + my ($bst) = $bst_array->[$i]; + + $fh->print(pack("NNNN", + $bst->{'indexSubTableArrayOffset'}, + $bst->{'indexTablesSize'}, + $bst->{'numberOfIndexSubTables'}, + $bst->{'colorRef'})); + $fh->print(pack("cccccccccccc", @{$bst->{'hori'}})); + $fh->print(pack("cccccccccccc", @{$bst->{'vert'}})); + $fh->print(pack("nnCCCC", $bst->{'startGlyphIndex'}, + $bst->{'endGlyphIndex'}, $bst->{'ppemX'}, + $bst->{'ppemY'}, $bst->{'bitDepth'}, $bst->{'flags'})); + } + + for ($i = 0; $i < $self->{'Num'}; $i++) { + my ($bst) = $bst_array->[$i]; + my ($j); + + for ($j = 0; $j < $bst->{'numberOfIndexSubTables'}; $j++) { + my ($ista) = $self->{'indexSubTableArray'}[$i][$j]; + + $fh->print("nnN", + $ista->{'firstGlyphIndex'}, + $ista->{'lastGlyphIndex'}, + $ista->{'additionalOffsetToIndexSubtable'}); + } + + $fh->print(pack("nnN", $bst->{'indexFormat'}, $bst->{'imageFormat'}, + $bst->{'imageDataOffset'})); + + die "Only indexFormat == 1 is supported" unless ($bst->{'indexFormat'} == 1); + + for ($j = 0; $j < $bst->{'numberOfIndexSubTables'}; $j++) { + $fh->print(pack("N*", $self->{'indexSubTable'}[$i][$j])); + } + } +} + +1; + +=head1 BUGS + +Only indexFormat ==1 is implemented. XML output is not supported (yet). + +=head1 AUTHOR + +NIIBE Yutaka L. See L for copyright and +licensing. + +This was written at the CodeFest Akihabara 2006 hosted by FSIJ. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fdsc.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fdsc.pm new file mode 100644 index 0000000..3a81a11 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fdsc.pm @@ -0,0 +1,125 @@ +package Font::TTF::Fdsc; + +=head1 NAME + +Font::TTF::Fdsc - Font Descriptors table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=item version + +=item descriptors + +Hash keyed by descriptor tags + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields); +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $fh, $numDescs, $tag, $descs); + + $self->SUPER::read or return $self; + + $fh = $self->{' INFILE'}; + $fh->read($dat, 4); + $self->{'version'} = TTF_Unpack("v", $dat); + + $fh->read($dat, 4); + + foreach (1 .. unpack("N", $dat)) { + $fh->read($tag, 4); + $fh->read($dat, 4); + $descs->{$tag} = ($tag eq 'nalf') ? unpack("N", $dat) : TTF_Unpack("f", $dat); + } + + $self->{'descriptors'} = $descs; + + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($descs); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $fh->print(TTF_Pack("v", $self->{'version'})); + + $descs = $self->{'descriptors'} or {}; + + $fh->print(pack("N", scalar keys %$descs)); + foreach (sort keys %$descs) { + $fh->print($_); + $fh->print(($_ eq 'nalf') ? pack("N", $descs->{$_}) : TTF_Pack("f", $descs->{$_})); + } + + $self; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + my ($descs, $k); + + $self->read; + + $fh = 'STDOUT' unless defined $fh; + + $descs = $self->{'descriptors'}; + foreach $k (sort keys %$descs) { + if ($k eq 'nalf') { + $fh->printf("Descriptor '%s' = %d\n", $k, $descs->{$k}); + } + else { + $fh->printf("Descriptor '%s' = %f\n", $k, $descs->{$k}); + } + } + + $self; +} + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Feat.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Feat.pm new file mode 100644 index 0000000..c837f41 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Feat.pm @@ -0,0 +1,191 @@ +package Font::TTF::Feat; + +=head1 NAME + +Font::TTF::Feat - Font Features + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over 4 + +=item version + +=item features + +An array of hashes of the following form + +=over 8 + +=item feature + +feature id number + +=item name + +name index in name table + +=item exclusive + +exclusive flag + +=item settings + +hash of setting number against name string index + +=back + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); + +use Font::TTF::Utils; + +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the features from the TTF file into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($featureCount, $features); + + $self->SUPER::read_dat or return $self; + + ($self->{'version'}, $featureCount) = TTF_Unpack("vS", $self->{' dat'}); + + $features = []; + foreach (1 .. $featureCount) { + my ($feature, $nSettings, $settingTable, $featureFlags, $nameIndex) + = TTF_Unpack("SSLSS", substr($self->{' dat'}, $_ * 12, 12)); + push @$features, + { + 'feature' => $feature, + 'name' => $nameIndex, + 'exclusive' => (($featureFlags & 0x8000) != 0), + 'settings' => { TTF_Unpack("S*", substr($self->{' dat'}, $settingTable, $nSettings * 4)) } + }; + } + $self->{'features'} = $features; + + delete $self->{' dat'}; # no longer needed, and may become obsolete + + $self; +} + +=head2 $t->out($fh) + +Writes the features to a TTF file + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($features, $numFeatures, $settings, $featuresData, $settingsData); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $features = $self->{'features'}; + $numFeatures = @$features; + + foreach (@$features) { + $settings = $_->{'settings'}; + $featuresData .= TTF_Pack("SSLSS", + $_->{'feature'}, + scalar keys %$settings, + 12 + 12 * $numFeatures + length $settingsData, + ($_->{'exclusive'} ? 0x8000 : 0x0000), + $_->{'name'}); + foreach (sort {$a <=> $b} keys %$settings) { + $settingsData .= TTF_Pack("SS", $_, $settings->{$_}); + } + } + + $fh->print(TTF_Pack("vSSL", $self->{'version'}, $numFeatures, 0, 0)); + $fh->print($featuresData); + $fh->print($settingsData); + + $self; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + my ($names, $features, $settings); + + $self->read; + + $names = $self->{' PARENT'}->{'name'}; + $names->read; + + $fh = 'STDOUT' unless defined $fh; + + $features = $self->{'features'}; + foreach (@$features) { + $fh->printf("Feature %d, %s, name %d # '%s'\n", + $_->{'feature'}, + ($_->{'exclusive'} ? "exclusive" : "additive"), + $_->{'name'}, + $names->{'strings'}[$_->{'name'}][1][0]{0}); + $settings = $_->{'settings'}; + foreach (sort { $a <=> $b } keys %$settings) { + $fh->printf("\tSetting %d, name %d # '%s'\n", + $_, $settings->{$_}, $names->{'strings'}[$settings->{$_}][1][0]{0}); + } + } + + $self; +} + +sub settingName +{ + my ($self, $feature, $setting) = @_; + + $self->read; + + my $names = $self->{' PARENT'}->{'name'}; + $names->read; + + my $features = $self->{'features'}; + my ($featureEntry) = grep { $_->{'feature'} == $feature } @$features; + my $featureName = $names->{'strings'}[$featureEntry->{'name'}][1][0]{0}; + my $settingName = $featureEntry->{'exclusive'} + ? $names->{'strings'}[$featureEntry->{'settings'}->{$setting}][1][0]{0} + : $names->{'strings'}[$featureEntry->{'settings'}->{$setting & ~1}][1][0]{0} + . (($setting & 1) == 0 ? " On" : " Off"); + + ($featureName, $settingName); +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fmtx.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fmtx.pm new file mode 100644 index 0000000..a9311df --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fmtx.pm @@ -0,0 +1,108 @@ +package Font::TTF::Fmtx; + +=head1 NAME + +Font::TTF::Fmtx - Font Metrics table + +=head1 DESCRIPTION + +This is a simple table with just standards specified instance variables + +=head1 INSTANCE VARIABLES + + version + glyphIndex + horizontalBefore + horizontalAfter + horizontalCaretHead + horizontalCaretBase + verticalBefore + verticalAfter + verticalCaretHead + verticalCaretBase + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'version' => 'v', + 'glyphIndex' => 'L', + 'horizontalBefore' => 'c', + 'horizontalAfter' => 'c', + 'horizontalCaretHead' => 'c', + 'horizontalCaretBase' => 'c', + 'verticalBefore' => 'c', + 'verticalAfter' => 'c', + 'verticalCaretHead' => 'c', + 'verticalCaretBase' => 'c'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory as instance variables + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read or return $self; + init unless defined $fields{'glyphIndex'}; + $self->{' INFILE'}->read($dat, 16); + + TTF_Read_Fields($self, $dat, \%fields); + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $fh->print(TTF_Out_Fields($self, \%fields, 16)); + $self; +} + + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Font.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Font.pm new file mode 100644 index 0000000..8f5cc50 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Font.pm @@ -0,0 +1,767 @@ +package Font::TTF::Font; + +=head1 NAME + +Font::TTF::Font - Memory representation of a font + +=head1 SYNOPSIS + +Here is the regression test (you provide your own font). Run it once and then +again on the output of the first run. There should be no differences between +the outputs of the two runs. + + $f = Font::TTF::Font->open($ARGV[0]); + + # force a read of all the tables + $f->tables_do(sub { $_[0]->read; }); + + # force read of all glyphs (use read_dat to use lots of memory!) + # $f->{'loca'}->glyphs_do(sub { $_[0]->read; }); + $f->{'loca'}->glyphs_do(sub { $_[0]->read_dat; }); + # NB. no need to $g->update since $f->{'glyf'}->out will do it for us + + $f->out($ARGV[1]); + $f->release; # clear up memory forcefully! + +=head1 DESCRIPTION + +A Truetype font consists of a header containing a directory of tables which +constitute the rest of the file. This class holds that header and directory and +also creates objects of the appropriate type for each table within the font. +Note that it does not read each table into memory, but creates a short reference +which can be read using the form: + + $f->{$tablename}->read; + +Classes are included that support many of the different TrueType tables. For +those for which no special code exists, the table type C is used, which +defaults to L. The current tables which are supported are: + + table Font::TTF::Table - for unknown tables + EBDT Font::TTF::EBDT + EBLC Font::TTF::EBLC + Feat Font::TTF::GrFeat + GDEF Font::TTF::GDEF + GPOS Font::TTF::GPOS + GSUB Font::TTF::GSUB + LTSH Font::TTF::LTSH + OS/2 Font::TTF::OS_2 + PCLT Font::TTF::PCLT + Sill Font::TTF::Sill + bsln Font::TTF::Bsln + cmap Font::TTF::Cmap - see also Font::TTF::OldCmap + cvt Font::TTF::Cvt_ + fdsc Font::TTF::Fdsc + feat Font::TTF::Feat + fmtx Font::TTF::Fmtx + fpgm Font::TTF::Fpgm + glyf Font::TTF::Glyf - see also Font::TTF::Glyph + hdmx Font::TTF::Hdmx + head Font::TTF::Head + hhea Font::TTF::Hhea + hmtx Font::TTF::Hmtx + kern Font::TTF::Kern - see alternative Font::TTF::AATKern + loca Font::TTF::Loca + maxp Font::TTF::Maxp + mort Font::TTF::Mort - see also Font::TTF::OldMort + name Font::TTF::Name + post Font::TTF::Post + prep Font::TTF::Prep + prop Font::TTF::Prop + vhea Font::TTF::Vhea + vmtx Font::TTF::Vmtx + +Links are: + +L +L L L +L L L L +L L L L L L +L L L L L +L L L L L +L L L L L +L L L L L +L L L + + +=head1 INSTANCE VARIABLES + +Instance variables begin with a space (and have lengths greater than the 4 +characters which make up table names). + +=over + +=item nocsum + +This is used during output to disable the creation of the file checksum in the +head table. For example, during DSIG table creation, this flag will be set to +ensure that the file checksum is left at zero. + +=item noharmony + +If set, do not harmonize the script and lang trees of GPOS and GSUB tables. See L for more info. + +=item fname (R) + +Contains the filename of the font which this object was read from. + +=item INFILE (P) + +The file handle which reflects the source file for this font. + +=item OFFSET (P) + +Contains the offset from the beginning of the read file of this particular +font directory, thus providing support for TrueType Collections. + +=back + +=head1 METHODS + +=cut + +use IO::File; + +use strict; +use vars qw(%tables $VERSION $dumper); +use Symbol(); + +require 5.004; + +$VERSION = 0.38; # MJPH 2-FEB-2008 Add Sill table +# $VERSION = 0.37; # MJPH 7-OCT-2005 Force hhea update if dirty, give more OS/2 stuff in update +# $VERSION = 0.36; # MJPH 19-AUG-2005 Change cmap::reverse api to be opts based +# $VERSION = 0.35; # MJPH 4-MAY-2004 Various fixes to OpenType stuff, separate off scripts +# $VERSION = 0.34; # MJPH 22-MAY-2003 Update PSNames to latest AGL +# $VERSION = 0.33; # MJPH 9-OCT-2002 Support CFF OpenType (just by version=='OTTO'?!) +# $VERSION = 0.32; # MJPH 2-OCT-2002 Bug fixes to TTFBuilder, new methods and some +# extension table support in Ttopen and Coverage +# $VERSION = 0.31; # MJPH 1-JUL-2002 fix read format 12 cmap (bart@cs.pdx.edu) +# improve surrogate support in ttfremap +# fix return warn to return warn,undef +# ensure correct indexToLocFormat +# $VERSION = 0.30; # MJPH 28-MAY-2002 add updated release +# $VERSION = 0.29; # MJPH 9-APR-2002 update ttfbuilder, sort out surrogates +# $VERSION = 0.28; # MJPH 13-MAR-2002 update ttfbuilder, add Font::TTF::Cmap::ms_enc() +# $VERSION = 0.27; # MJPH 6-FEB-2002 update ttfbuilder, support no fpgm, no more __DATA__ +# $VERSION = 0.26; # MJPH 19-SEP-2001 Update ttfbuilder +# $VERSION = 0.25; # MJPH 18-SEP-2001 problems in update of head +# $VERSION = 0.24; # MJPH 1-AUG-2001 Sort out update +# $VERSION = 0.23; # GST 30-MAY-2001 Memory leak fixed +# $VERSION = 0.22; # MJPH 09-APR-2001 Ensure all of AAT stuff included +# $VERSION = 0.21; # MJPH 23-MAR-2001 Improve Opentype support +# $VERSION = 0.20; # MJPH 13-JAN-2001 Add XML output and some of XML input, AAT & OT tables +# $VERSION = 0.19; # MJPH 29-SEP-2000 Add cmap::is_unicode, debug makefile.pl +# $VERSION = 0.18; # MJPH 21-JUL-2000 Debug Utils::TTF_bininfo +# $VERSION = 0.17; # MJPH 16-JUN-2000 Add utf8 support to names +# $VERSION = 0.16; # MJPH 26-APR-2000 Mark read tables as read, tidy up POD +# $VERSION = 0.15; # MJPH 5-FEB-2000 Ensure right versions released +# $VERSION = 0.14; # MJPH 11-SEP-1999 Sort out Unixisms, agian! +# $VERSION = 0.13; # MJPH 9-SEP-1999 Add empty, debug update_bbox +# $VERSION = 0.12; # MJPH 22-JUL-1999 Add update_bbox +# $VERSION = 0.11; # MJPH 7-JUL-1999 Don't store empties in cmaps +# $VERSION = 0.10; # MJPH 21-JUN-1999 Use IO::File +# $VERSION = 0.09; # MJPH 9-JUN-1999 Add 5.004 require, minor tweeks in cmap +# $VERSION = 0.08; # MJPH 19-MAY-1999 Sort out line endings for Unix +# $VERSION = 0.07; # MJPH 28-APR-1999 Get the regression tests to work +# $VERSION = 0.06; # MJPH 26-APR-1999 Start to add to CVS, correct MANIFEST.SKIP +# $VERSION = 0.05; # MJPH 13-APR-1999 See changes for 0.05 +# $VERSION = 0.04; # MJPH 13-MAR-1999 Tidy up Tarball +# $VERSION = 0.03; # MJPH 9-MAR-1999 Move to Font::TTF for CPAN +# $VERSION = 0.02; # MJPH 12-FEB-1999 Add support for ' nocsum' for DSIGS +# $VERSION = 0.0001; + +%tables = ( + 'table' => 'Font::TTF::Table', + 'EBDT' => 'Font::TTF::EBDT', + 'EBLC' => 'Font::TTF::EBLC', + 'Feat' => 'Font::TTF::GrFeat', + 'GDEF' => 'Font::TTF::GDEF', + 'GPOS' => 'Font::TTF::GPOS', + 'GSUB' => 'Font::TTF::GSUB', + 'LTSH' => 'Font::TTF::LTSH', + 'OS/2' => 'Font::TTF::OS_2', + 'PCLT' => 'Font::TTF::PCLT', + 'Sill' => 'Font::TTF::Sill', + 'bsln' => 'Font::TTF::Bsln', + 'cmap' => 'Font::TTF::Cmap', + 'cvt ' => 'Font::TTF::Cvt_', + 'fdsc' => 'Font::TTF::Fdsc', + 'feat' => 'Font::TTF::Feat', + 'fmtx' => 'Font::TTF::Fmtx', + 'fpgm' => 'Font::TTF::Fpgm', + 'glyf' => 'Font::TTF::Glyf', + 'hdmx' => 'Font::TTF::Hdmx', + 'head' => 'Font::TTF::Head', + 'hhea' => 'Font::TTF::Hhea', + 'hmtx' => 'Font::TTF::Hmtx', + 'kern' => 'Font::TTF::Kern', + 'loca' => 'Font::TTF::Loca', + 'maxp' => 'Font::TTF::Maxp', + 'mort' => 'Font::TTF::Mort', + 'name' => 'Font::TTF::Name', + 'post' => 'Font::TTF::Post', + 'prep' => 'Font::TTF::Prep', + 'prop' => 'Font::TTF::Prop', + 'vhea' => 'Font::TTF::Vhea', + 'vmtx' => 'Font::TTF::Vmtx', + ); + +# This is special code because I am fed up of every time I x a table in the debugger +# I get the whole font printed. Thus substitutes my 3 line change to dumpvar into +# the debugger. Clunky, but nice. You are welcome to a copy if you want one. + +BEGIN { + my ($p); + + foreach $p (@INC) + { + if (-f "$p/mydumpvar.pl") + { + $dumper = 'mydumpvar.pl'; + last; + } + } + $dumper ||= 'dumpvar.pl'; +} + +sub main::dumpValue +{ do $dumper; &main::dumpValue; } + + +=head2 Font::TTF::Font->AddTable($tablename, $class) + +Adds the given class to be used when representing the given table name. It also +'requires' the class for you. + +=cut + +sub AddTable +{ + my ($class, $table, $useclass) = @_; + + $tables{$table} = $useclass; +# $useclass =~ s|::|/|oig; +# require "$useclass.pm"; +} + + +=head2 Font::TTF::Font->Init + +For those people who like making fonts without reading them. This subroutine +will require all the table code for the various table types for you. Not +needed if using Font::TTF::Font::read before using a table. + +=cut + +sub Init +{ + my ($class) = @_; + my ($t); + + foreach $t (values %tables) + { + $t =~ s|::|/|oig; + require "$t.pm"; + } +} + +=head2 Font::TTF::Font->new(%props) + +Creates a new font object and initialises with the given properties. This is +primarily for use when a TTF is embedded somewhere. Notice that the properties +are automatically preceded by a space when inserted into the object. This is in +order that fields do not clash with tables. + +=cut + +sub new +{ + my ($class, %props) = @_; + my ($self) = {}; + + bless $self, $class; + + foreach (keys %props) + { $self->{" $_"} = $props{$_}; } + $self; +} + + +=head2 Font::TTF::Font->open($fname) + +Reads the header and directory for the given font file and creates appropriate +objects for each table in the font. + +=cut + +sub open +{ + my ($class, $fname) = @_; + my ($fh); + my ($self) = {}; + + unless (ref($fname)) + { + $fh = IO::File->new($fname) or return undef; + binmode $fh; + } else + { $fh = $fname; } + + $self->{' INFILE'} = $fh; + $self->{' fname'} = $fname; + $self->{' OFFSET'} = 0; + bless $self, $class; + + $self->read; +} + +=head2 $f->read + +Reads a Truetype font directory starting from the current location in the file. +This has been separated from the C function to allow support for embedded +TTFs for example in TTCs. Also reads the C and C tables immediately. + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($dat, $i, $ver, $dir_num, $type, $name, $check, $off, $len, $t); + + $fh->seek($self->{' OFFSET'}, 0); + $fh->read($dat, 12); + ($ver, $dir_num) = unpack("Nn", $dat); + $ver == 1 << 16 || $ver == unpack('N', 'OTTO') || $ver == 0x74727565 or return undef; # support Mac sfnts + + for ($i = 0; $i < $dir_num; $i++) + { + $fh->read($dat, 16) || die "Reading table entry"; + ($name, $check, $off, $len) = unpack("a4NNN", $dat); + $self->{$name} = $self->{' PARENT'}->find($self, $name, $check, $off, $len) && next + if (defined $self->{' PARENT'}); + $type = $tables{$name} || 'Font::TTF::Table'; + $t = $type; + if ($^O eq "MacOS") + { $t =~ s/^|::/:/oig; } + else + { $t =~ s|::|/|oig; } + require "$t.pm"; + $self->{$name} = $type->new(PARENT => $self, + NAME => $name, + INFILE => $fh, + OFFSET => $off, + LENGTH => $len, + CSUM => $check); + } + + foreach $t ('head', 'maxp') + { $self->{$t}->read if defined $self->{$t}; } + + $self; +} + + +=head2 $f->out($fname [, @tablelist]) + +Writes a TTF file consisting of the tables in tablelist. The list is checked to +ensure that only tables that exist are output. (This means that you can't have +non table information stored in the font object with key length of exactly 4) + +In many cases the user simply wants to output all the tables in alphabetical order. +This can be done by not including a @tablelist, in which case the subroutine will +output all the defined tables in the font in alphabetical order. + +Returns $f on success and undef on failure, including warnings. + +All output files must include the C table. + +=cut + +sub out +{ + my ($self, $fname, @tlist) = @_; + my ($fh); + my ($dat, $numTables, $sRange, $eSel); + my (%dir, $k, $mloc, $count); + my ($csum, $lsum, $msum, $loc, $oldloc, $len, $shift); + + unless (ref($fname)) + { + $fh = IO::File->new("+>$fname") || return warn("Unable to open $fname for writing"), undef; + binmode $fh; + } else + { $fh = $fname; } + + $self->{' oname'} = $fname; + $self->{' outfile'} = $fh; + + if ($self->{' wantsig'}) + { + $self->{' nocsum'} = 1; +# $self->{'head'}{'checkSumAdjustment'} = 0; + $self->{' tempDSIG'} = $self->{'DSIG'}; + $self->{' tempcsum'} = $self->{'head'}{' CSUM'}; + delete $self->{'DSIG'}; + @tlist = sort {$self->{$a}{' OFFSET'} <=> $self->{$b}{' OFFSET'}} + grep (length($_) == 4 && defined $self->{$_}, keys %$self) if ($#tlist < 0); + } + elsif ($#tlist < 0) + { @tlist = sort keys %$self; } + + @tlist = grep(length($_) == 4 && defined $self->{$_}, @tlist); + $numTables = $#tlist + 1; + $numTables++ if ($self->{' wantsig'}); + + ($numTables, $sRange, $eSel, $shift) = Font::TTF::Utils::TTF_bininfo($numTables, 16); + $dat = pack("Nnnnn", 1 << 16, $numTables, $sRange, $eSel, $shift); + $fh->print($dat); + $msum = unpack("%32N*", $dat); + +# reserve place holders for each directory entry + foreach $k (@tlist) + { + $dir{$k} = pack("A4NNN", $k, 0, 0, 0); + $fh->print($dir{$k}); + } + + $fh->print(pack('A4NNN', '', 0, 0, 0)) if ($self->{' wantsig'}); + + $loc = $fh->tell(); + if ($loc & 3) + { + $fh->print(substr("\000" x 4, $loc & 3)); + $loc += 4 - ($loc & 3); + } + + foreach $k (@tlist) + { + $oldloc = $loc; + $self->{$k}->out($fh); + $loc = $fh->tell(); + $len = $loc - $oldloc; + if ($loc & 3) + { + $fh->print(substr("\000" x 4, $loc & 3)); + $loc += 4 - ($loc & 3); + } + $fh->seek($oldloc, 0); + $csum = 0; $mloc = $loc; + while ($mloc > $oldloc) + { + $count = ($mloc - $oldloc > 4096) ? 4096 : $mloc - $oldloc; + $fh->read($dat, $count); + $csum += unpack("%32N*", $dat); +# this line ensures $csum stays within 32 bit bounds, clipping as necessary + if ($csum > 0xffffffff) { $csum -= 0xffffffff; $csum--; } + $mloc -= $count; + } + $dir{$k} = pack("A4NNN", $k, $csum, $oldloc, $len); + $msum += $csum + unpack("%32N*", $dir{$k}); + while ($msum > 0xffffffff) { $msum -= 0xffffffff; $msum--; } + $fh->seek($loc, 0); + } + + unless ($self->{' nocsum'}) # assuming we want a file checksum + { +# Now we need to sort out the head table's checksum + if (!defined $dir{'head'}) + { # you have to have a head table + $fh->close(); + return warn("No 'head' table to output in $fname"), undef; + } + ($csum, $loc, $len) = unpack("x4NNN", $dir{'head'}); + $fh->seek($loc + 8, 0); + $fh->read($dat, 4); + $lsum = unpack("N", $dat); + if ($lsum != 0) + { + $csum -= $lsum; + if ($csum < 0) { $csum += 0xffffffff; $csum++; } + $msum -= $lsum * 2; # twice (in head and in csum) + while ($msum < 0) { $msum += 0xffffffff; $msum++; } + } + $lsum = 0xB1B0AFBA - $msum; + $fh->seek($loc + 8, 0); + $fh->print(pack("N", $lsum)); + $dir{'head'} = pack("A4NNN", 'head', $csum, $loc, $len); + } elsif ($self->{' wantsig'}) + { + if (!defined $dir{'head'}) + { # you have to have a head table + $fh->close(); + return warn("No 'head' table to output in $fname"), undef; + } + ($csum, $loc, $len) = unpack("x4NNN", $dir{'head'}); + $fh->seek($loc + 8, 0); + $fh->print(pack("N", 0)); +# $dir{'head'} = pack("A4NNN", 'head', $self->{' tempcsum'}, $loc, $len); + } + +# Now we can output the directory again + if ($self->{' wantsig'}) + { @tlist = sort @tlist; } + $fh->seek(12, 0); + foreach $k (@tlist) + { $fh->print($dir{$k}); } + $fh->print(pack('A4NNN', '', 0, 0, 0)) if ($self->{' wantsig'}); + $fh->close(); + $self; +} + + +=head2 $f->out_xml($filename [, @tables]) + +Outputs the font in XML format + +=cut + +sub out_xml +{ + my ($self, $fname, @tlist) = @_; + my ($fh, $context, $numTables, $k); + + $context->{'indent'} = ' ' x 4; + + unless (ref($fname)) + { + $fh = IO::File->new("+>$fname") || return warn("Unable to open $fname"), undef; + binmode $fh; + } else + { $fh = $fname; } + + unless (scalar @tlist > 0) + { + @tlist = sort keys %$self; + @tlist = grep(length($_) == 4 && defined $self->{$_}, @tlist); + } + $numTables = $#tlist + 1; + + $context->{'fh'} = $fh; + $fh->print("\n"); + $fh->print("\n\n"); + + foreach $k (@tlist) + { + $fh->print("
\n"); + $self->{$k}->out_xml($context, $context->{'indent'}); + $fh->print("
\n"); + } + + $fh->print("\n"); + $fh->close; + $self; +} + + +=head2 $f->XML_start($context, $tag, %attrs) + +Handles start messages from the XML parser. Of particular interest to us are and +. + +=cut + +sub XML_start +{ + my ($self, $context, $tag, %attrs) = @_; + my ($name, $type, $t); + + if ($tag eq 'font') + { $context->{'tree'}[-1] = $self; } + elsif ($tag eq 'table') + { + $name = $attrs{'name'}; + unless (defined $self->{$name}) + { + $type = $tables{$name} || 'Font::TTF::Table'; + $t = $type; + if ($^O eq "MacOS") + { $t =~ s/^|::/:/oig; } + else + { $t =~ s|::|/|oig; } + require "$t.pm"; + $self->{$name} = $type->new('PARENT' => $self, 'NAME' => $name, 'read' => 1); + } + $context->{'receiver'} = ($context->{'tree'}[-1] = $self->{$name}); + } + $context; +} + + +sub XML_end +{ + my ($self) = @_; + my ($context, $tag, %attrs) = @_; + my ($i); + + return undef unless ($tag eq 'table' && $attrs{'name'} eq 'loca'); + if (defined $context->{'glyphs'} && $context->{'glyphs'} ne $self->{'loca'}{'glyphs'}) + { + for ($i = 0; $i <= $#{$context->{'glyphs'}}; $i++) + { $self->{'loca'}{'glyphs'}[$i] = $context->{'glyphs'}[$i] if defined $context->{'glyphs'}[$i]; } + $context->{'glyphs'} = $self->{'loca'}{'glyphs'}; + } + return undef; +} + +=head2 $f->update + +Sends update to all the tables in the font and then resets all the isDirty +flags on each table. The data structure in now consistent as a font (we hope). + +=cut + +sub update +{ + my ($self) = @_; + + $self->tables_do(sub { $_[0]->update; }); + + $self; +} + +=head2 $f->dirty + +Dirties all the tables in the font + +=cut + +sub dirty +{ $_[0]->tables_do(sub { $_[0]->dirty; }); $_[0]; } + +=head2 $f->tables_do(&func [, tables]) + +Calls &func for each table in the font. Calls the table in alphabetical sort +order as per the order in the directory: + + &func($table, $name); + +May optionally take a list of table names in which case func is called +for each of them in the given order. +=cut + +sub tables_do +{ + my ($self, $func, @tables) = @_; + my ($t); + + foreach $t (@tables ? @tables : sort grep {length($_) == 4} keys %$self) + { &$func($self->{$t}, $t); } + $self; +} + + +=head2 $f->release + +Releases ALL of the memory used by the TTF font and all of its component +objects. After calling this method, do B expect to have anything left in +the C object. + +B, that it is important that you call this method on any +C object when you wish to destruct it and free up its memory. +Internally, we track things in a structure that can result in circular +references, and without calling 'C' these will not properly get +cleaned up by Perl. Once you've called this method, though, don't expect to be +able to do anything else with the C object; it'll have B +internal state whatsoever. + +B As part of the brute-force cleanup done here, this method +will throw a warning message whenever unexpected key values are found within +the C object. This is done to help ensure that any unexpected +and unfreed values are brought to your attention so that you can bug us to keep +the module updated properly; otherwise the potential for memory leaks due to +dangling circular references will exist. + +=cut + +sub release +{ + my ($self) = @_; + +# delete stuff that we know we can, here + + my @tofree = map { delete $self->{$_} } keys %{$self}; + + while (my $item = shift @tofree) + { + my $ref = ref($item); + if (UNIVERSAL::can($item, 'release')) + { $item->release(); } + elsif ($ref eq 'ARRAY') + { push( @tofree, @{$item} ); } + elsif (UNIVERSAL::isa($ref, 'HASH')) + { release($item); } + } + +# check that everything has gone - it better had! + foreach my $key (keys %{$self}) + { warn ref($self) . " still has '$key' key left after release.\n"; } +} + +1; + +=head1 BUGS + +Bugs abound aplenty I am sure. There is a lot of code here and plenty of scope. +The parts of the code which haven't been implemented yet are: + +=over 4 + +=item Post + +Version 4 format types are not supported yet. + +=item Cmap + +Format type 2 (MBCS) has not been implemented yet and therefore may cause +somewhat spurious results for this table type. + +=item Kern + +Only type 0 & type 2 tables are supported (type 1 & type 3 yet to come). + +=item TTC + +The current Font::TTF::Font::out method does not support the writing of TrueType +Collections. + +=back + +In addition there are weaknesses or features of this module library + +=over 4 + +=item * + +There is very little (or no) error reporting. This means that if you have +garbled data or garbled data structures, then you are liable to generate duff +fonts. + +=item * + +The exposing of the internal data structures everywhere means that doing +radical re-structuring is almost impossible. But it stop the code from becoming +ridiculously large. + +=back + +Apart from these, I try to keep the code in a state of "no known bugs", which +given the amount of testing this code has had, is not a guarantee of high +quality, yet. + +For more details see the appropriate class files. + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org + +Copyright Martin Hosken 1998. + +No warranty or expression of effectiveness, least of all regarding anyone's +safety, is implied in this software or documentation. + +=head2 Licensing + +The Perl TTF module is licensed under the Perl Artistic License. + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fpgm.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fpgm.pm new file mode 100644 index 0000000..4cc7709 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Fpgm.pm @@ -0,0 +1,87 @@ +package Font::TTF::Fpgm; + +=head1 NAME + +Font::TTF::Fpgm - Font program in a TrueType font. Called when a font is loaded + +=head1 DESCRIPTION + +This is a minimal class adding nothing beyond a table, but is a repository +for fpgm type information for those processes brave enough to address hinting. + +=cut + +use strict; +use vars qw(@ISA $VERSION); + +@ISA = qw(Font::TTF::Table); + +$VERSION = 0.0001; + +=head2 $t->read + +Reading this table is simply a process of reading all the data into the RAM +copy. Nothing more is done with it. + +=cut + +sub read +{ + $_[0]->read_dat; + $_[0]->{' read'} = 1; +} + +=head2 $t->out_xml($context, $depth) + +Outputs Fpgm program as XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + my ($dat); + + $self->read; + $dat = Font::TTF::Utils::XML_binhint($self->{' dat'}); + $dat =~ s/\n(?!$)/\n$depth$context->{'indent'}/omg; + $fh->print("$depth\n"); + $fh->print("$depth$context->{'indent'}$dat"); + $fh->print("$depth\n"); + $self; +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Parse all that hinting code + +=cut + +sub XML_end +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag eq 'code') + { + $self->{' dat'} = Font::TTF::Utils::XML_hintbin($context->{'text'}); + return $context; + } else + { return $self->SUPER::XML_end(@_); } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/GDEF.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GDEF.pm new file mode 100644 index 0000000..9519ee8 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GDEF.pm @@ -0,0 +1,313 @@ +package Font::TTF::GDEF; + +=head1 NAME + +Font::TTF::GDEF - Opentype GDEF table support + +=head1 DESCRIPTION + +The GDEF table contains various global lists of information which are apparantly +used in other places in an OpenType renderer. But precisely where is open to +speculation... + +=head1 INSTANCE VARIABLES + +There are 4 tables in the GDEF table, each with their own structure: + +=over 4 + +=item GLYPH + +This is an L Class Definition table containing information +as to what type each glyph is. + +=item ATTACH + +The attach table consists of a coverage table and then attachment points for +each glyph in the coverage table: + +=over 8 + +=item COVERAGE + +This is a coverage table + +=item POINTS + +This is an array of point elements. Each element is an array of curve points +corresponding to the attachment points on that glyph. The order of the curve points +in the array corresponds to the attachment point number specified in the MARKS +coverage table (see below). + +=back + +=item LIG + +This contains the ligature caret positioning information for ligature glyphs + +=over 8 + +=item COVERAGE + +A coverage table to say which glyphs are ligatures + +=item LIGS + +An array of elements for each ligature. Each element is an array of information +for each caret position in the ligature (there being number of components - 1 of +these, generally) + +=over 12 + +=item FMT + +This is the format of the information and is important to provide the semantics +for the value. This value must be set correctly before output + +=item VAL + +The value which has meaning according to FMT + +=item DEVICE + +For FMT = 3, a device table is also referenced which is stored here + +=back + +=back + +=item MARKS + +Due to confusion in the GDEF specification, this field is currently withdrawn until +the confusion is resolved. That way, perhaps this stuff will work! + +This class definition table stores the mark attachment point numbers for each +attachment mark, to indicate which attachment point the mark attaches to on its +base glyph. + +=back + + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Table; +use Font::TTF::Utils; +use Font::TTF::Ttopen; +use vars qw(@ISA $new_gdef); + +@ISA = qw(Font::TTF::Table); +$new_gdef = 1; + +=head2 $t->read + +Reads the table into the data structure + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($boff) = $self->{' OFFSET'}; + my ($dat, $goff, $loff, $aoff, $moff, $r, $s, $bloc); + + $self->SUPER::read or return $self; + $bloc = $fh->tell(); + if ($new_gdef) + { + $fh->read($dat, 12); + ($self->{'Version'}, $goff, $aoff, $loff, $moff) = TTF_Unpack('fS4', $dat); + } + else + { + $fh->read($dat, 10); + ($self->{'Version'}, $goff, $aoff, $loff) = TTF_Unpack('fS3', $dat); + } + + if ($goff > 0) + { + $fh->seek($goff + $boff, 0); + $self->{'GLYPH'} = Font::TTF::Coverage->new(0)->read($fh); + } + + if ($new_gdef && $moff > 0 && $moff < $self->{' LENGTH'}) + { + $fh->seek($moff + $boff, 0); + $self->{'MARKS'} = Font::TTF::Coverage->new(0)->read($fh); + } + + if ($aoff > 0) + { + my ($off, $gcount, $pcount); + + $fh->seek($aoff + $boff, 0); + $fh->read($dat, 4); + ($off, $gcount) = TTF_Unpack('SS', $dat); + $fh->read($dat, $gcount << 1); + + $fh->seek($aoff + $boff + $off, 0); + $self->{'ATTACH'}{'COVERAGE'} = Font::TTF::Coverage->new(1)->read($fh); + + foreach $r (TTF_Unpack('S*', $dat)) + { + unless ($r) + { + push (@{$self->{'ATTACH'}{'POINTS'}}, []); + next; + } + $fh->seek($aoff + $boff + $r, 0); + $fh->read($dat, 2); + $pcount = TTF_Unpack('S', $dat); + $fh->read($dat, $pcount << 1); + push (@{$self->{'ATTACH'}{'POINTS'}}, [TTF_Unpack('S*', $dat)]); + } + } + + if ($loff > 0) + { + my ($lcount, $off, $ccount, $srec, $comp); + + $fh->seek($loff + $boff, 0); + $fh->read($dat, 4); + ($off, $lcount) = TTF_Unpack('SS', $dat); + $fh->read($dat, $lcount << 1); + + $fh->seek($off + $loff + $boff, 0); + $self->{'LIG'}{'COVERAGE'} = Font::TTF::Coverage->new(1)->read($fh); + + foreach $r (TTF_Unpack('S*', $dat)) + { + $fh->seek($r + $loff + $boff, 0); + $fh->read($dat, 2); + $ccount = TTF_Unpack('S', $dat); + $fh->read($dat, $ccount << 1); + + $srec = []; + foreach $s (TTF_Unpack('S*', $dat)) + { + $comp = {}; + $fh->seek($s + $r + $loff + $boff, 0); + $fh->read($dat, 4); + ($comp->{'FMT'}, $comp->{'VAL'}) = TTF_Unpack('S*', $dat); + if ($comp->{'FMT'} == 3) + { + $fh->read($dat, 2); + $off = TTF_Unpack('S', $dat); + if (defined $self->{' CACHE'}{$off + $s + $r}) + { $comp->{'DEVICE'} = $self->{' CACHE'}{$off + $s + $r}; } + else + { + $fh->seek($off + $s + $r + $loff + $boff, 0); + $comp->{'DEVICE'} = Font::TTF::Delta->new->read($fh); + $self->{' CACHE'}{$off + $s + $r} = $comp->{'DEVICE'}; + } + } + push (@$srec, $comp); + } + push (@{$self->{'LIG'}{'LIGS'}}, $srec); + } + } + + $self; +} + + +=head2 $t->out($fh) + +Writes out this table. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($goff, $aoff, $loff, $moff, @offs, $loc1, $coff, $loc); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $loc = $fh->tell(); + if ($new_gdef) + { $fh->print(TTF_Pack('fSSSS', $self->{'Version'}, 0, 0, 0, 0)); } + else + { $fh->print(TTF_Pack('fSSS', $self->{'Version'}, 0, 0, 0)); } + + if (defined $self->{'GLYPH'}) + { + $goff = $fh->tell() - $loc; + $self->{'GLYPH'}->out($fh); + } + + if (defined $self->{'ATTACH'}) + { + my ($r); + + $aoff = $fh->tell() - $loc; + $fh->print(pack('n*', (0) x ($#{$self->{'ATTACH'}{'POINTS'}} + 3))); + foreach $r (@{$self->{'ATTACH'}{'POINTS'}}) + { + push (@offs, $fh->tell() - $loc - $aoff); + $fh->print(pack('n*', $#{$r} + 1, @$r)); + } + $coff = $fh->tell() - $loc - $aoff; + $self->{'ATTACH'}{'COVERAGE'}->out($fh); + $loc1 = $fh->tell(); + $fh->seek($aoff + $loc, 0); + $fh->print(pack('n*', $coff, $#offs + 1, @offs)); + $fh->seek($loc1, 0); + } + + if (defined $self->{'LIG'}) + { + my (@reftables, $ltables, $i, $j, $out, $r, $s); + + $ltables = {}; + $loff = $fh->tell() - $loc; + $out = pack('n*', + Font::TTF::Ttopen::ref_cache($self->{'LIG'}{'COVERAGE'}, $ltables, 0), + $#{$self->{'LIG'}{'LIGS'}} + 1, + (0) x ($#{$self->{'LIG'}{'LIGS'}} + 1)); + push (@reftables, [$ltables, 0]); + $i = 0; + foreach $r (@{$self->{'LIG'}{'LIGS'}}) + { + $ltables = {}; + $loc1 = length($out); + substr($out, ($i << 1) + 4, 2) = TTF_Pack('S', $loc1); + $out .= pack('n*', $#{$r} + 1, (0) x ($#{$r} + 1)); + @offs = (); $j = 0; + foreach $s (@$r) + { + substr($out, ($j << 1) + 2 + $loc1, 2) = + TTF_Pack('S', length($out) - $loc1); + $out .= TTF_Pack('SS', $s->{'FMT'}, $s->{'VAL'}); + $out .= pack('n', Font::TTF::Ttopen::ref_cache($s->{'DEVICE'}, + $ltables, length($out))) if ($s->{'FMT'} == 3); + $j++; + } + push (@reftables, [$ltables, $loc1]); + $i++; + } + Font::TTF::Ttopen::out_final($fh, $out, \@reftables); + } + + if ($new_gdef && defined $self->{'MARKS'}) + { + $moff = $fh->tell() - $loc; + $self->{'MARKS'}->out($fh); + } + + $loc1 = $fh->tell(); + $fh->seek($loc + 4, 0); + if ($new_gdef) + { $fh->print(TTF_Pack('S4', $goff, $aoff, $loff, $moff)); } + else + { $fh->print(TTF_Pack('S3', $goff, $aoff, $loff)); } + $fh->seek($loc1, 0); + $self; +} + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/GPOS.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GPOS.pm new file mode 100644 index 0000000..8e8b7ce --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GPOS.pm @@ -0,0 +1,701 @@ +package Font::TTF::GPOS; + +=head1 NAME + +Font::TTF::GPOS - Support for Opentype GPOS tables in conjunction with TTOpen + +=head1 DESCRIPTION + +The GPOS table is one of the most complicated tables in the TTF spec and the +corresponding data structure abstraction is also not trivial. While much of the +structure of a GPOS is shared with a GSUB table via the L + +=head1 INSTANCE VARIABLES + +Here we describe the additions and lookup specific information for GPOS tables. +Unfortunately there is no one abstraction which seems to work comfortable for +all GPOS tables, so we will also examine how the variables are used for different +lookup types. + +The following are the values allowed in the ACTION_TYPE and MATCH_TYPE variables: + +=over 4 + +=item ACTION_TYPE + +This can take any of the following values + +=over 8 + +=item a + +The ACTION is an array of anchor tables + +=item o + +Offset. There is no RULE array. The ADJUST variable contains a value record (see +later in this description) + +=item v + +The ACTION is a value record. + +=item p + +Pair adjustment. The ACTION contains an array of two value records for the matched +two glyphs. + +=item e + +Exit and Entry records. The ACTION contains an array of two anchors corresponding +to the exit and entry anchors for the glyph. + +=item l + +Indicates a lookup based contextual rule as per the GSUB table. + +=back + +=item MATCH_TYPE + +This can take any of the following values + +=over 8 + +=item g + +A glyph array + +=item c + +An array of class values + +=item o + +An array of coverage tables + +=back + +=back + +The following variables are added for Attachment Positioning Subtables: + +=over 4 + +=item MATCH + +This contains an array of glyphs to match against for all RULES. It is much like +having the same MATCH string in all RULES. In the cases it is used so far, it only +ever contains one element. + +=item MARKS + +This contains a Mark array consisting of each element being a subarray of two +elements: + +=over 8 + +=item CLASS + +The class that this mark uses on its base + +=item ANCHOR + +The anchor with which to attach this mark glyph + +=back + +The base table for mark to base, ligature or mark attachment positioning is +structured with the ACTION containing an array of anchors corresponding to each +attachment class. For ligatures, there is more than one RULE in the RULE array +corresponding to each glyph in the coverage table. + +=back + +Other variables which are provided for informational purposes are: + +=over 4 + +=item VFMT + +Value format for the adjustment of the glyph matched by the coverage table. + +=item VFMT2 + +Value format used in pair adjustment for the second glyph in the pair + +=back + +=head2 Value Records + +There is a subtype used in GPOS tables called a value record. It is used to adjust +the position of a glyph from its default position. The value record is variable +length with a bitfield at the beginning to indicate which of the following +entries are included. The bitfield is not stored since it is recalculated at +write time. + +=over 4 + +=item XPlacement + +Horizontal adjustment for placement (not affecting other unattached glyphs) + +=item YPlacement + +Vertical adjustment for placement (not affecting other unattached glyphs) + +=item XAdvance + +Adjust the advance width glyph (used only in horizontal writing systems) + +=item YAdvance + +Adjust the vertical advance (used only in vertical writing systems) + +=item XPlaDevice + +Device table for device specific adjustment of horizontal placement + +=item YPlaDevice + +Device table for device specific adjustment of vertical placement + +=item XAdvDevice + +Device table for device specific adjustment of horizontal advance + +=item YAdDevice + +Device table for device specific adjustment of vertical advance + +=item XIdPlacement + +Horizontal placement metric id (for Multiple Master fonts - but that's all I know!) + +=item YIdPlacement + +Vertical placement metric id + +=item XIdAdvance + +Horizontal advance metric id + +=item YIdAdvance + +Vertical advance metric id + +=back + +=head1 CORRESPONDANCE TO LAYOUT TYPES + +Here is what is stored in the ACTION_TYPE and MATCH_TYPE for each of the known +GPOS subtable types: + + 1.1 1.2 2.1 2.2 3 4 5 6 7.1 7.2 7.3 8.1 8.2 8.3 + ACTION_TYPE o v p p e a a a l l l l l l + MATCH_TYPE g c g c o g c o + + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Ttopen; +use Font::TTF::Delta; +use Font::TTF::Anchor; +use Font::TTF::Utils; +use vars qw(@ISA); + +@ISA = qw(Font::TTF::Ttopen); + + +=head2 read_sub + +Reads the subtable into the data structures + +=cut + +sub read_sub +{ + my ($self, $fh, $main_lookup, $sindex) = @_; + my ($type) = $main_lookup->{'TYPE'}; + my ($loc) = $fh->tell(); + my ($lookup) = $main_lookup->{'SUB'}[$sindex]; + my ($dat, $mcount, $scount, $i, $j, $count, $fmt, $fmt2, $cover, $srec, $subst); + my ($c1, $c2, $s, $moff, $boff); + + + if ($type == 8) + { + $fh->read($dat, 4); + ($fmt, $cover) = TTF_Unpack('S2', $dat); + if ($fmt < 3) + { + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + } + } else + { + $fh->read($dat, 6); + ($fmt, $cover, $count) = TTF_Unpack("S3", $dat); + } + unless ($fmt == 3 && ($type == 7 || $type == 8)) + { $lookup->{'COVERAGE'} = $self->read_cover($cover, $loc, $lookup, $fh, 1); } + + $lookup->{'FORMAT'} = $fmt; + if ($type == 1 && $fmt == 1) + { + $lookup->{'VFMT'} = $count; + $lookup->{'ADJUST'} = $self->read_value($count, $loc, $lookup, $fh); + $lookup->{'ACTION_TYPE'} = 'o'; + } elsif ($type == 1 && $fmt == 2) + { + $lookup->{'VFMT'} = $count; + $fh->read($dat, 2); + $mcount = unpack('n', $dat); + for ($i = 0; $i < $mcount; $i++) + { push (@{$lookup->{'RULES'}}, [{'ACTION' => + [$self->read_value($count, $loc, $lookup, $fh)]}]); } + $lookup->{'ACTION_TYPE'} = 'v'; + } elsif ($type == 2 && $fmt == 1) + { + $lookup->{'VFMT'} = $count; + $fh->read($dat, 4); + ($fmt2, $mcount) = unpack('n2', $dat); + $lookup->{'VFMT2'} = $fmt2; + $fh->read($dat, $mcount << 1); + foreach $s (unpack('n*', $dat)) + { + $fh->seek($loc + $s, 0); + $fh->read($dat, 2); + $scount = TTF_Unpack('S', $dat); + $subst = []; + for ($i = 0; $i < $scount; $i++) + { + $srec = {}; + $fh->read($dat, 2); + $srec->{'MATCH'} = [TTF_Unpack('S', $dat)]; + $srec->{'ACTION'} = [$self->read_value($count, $loc, $lookup, $fh), + $self->read_value($fmt2, $loc, $lookup, $fh)]; + push (@$subst, $srec); + } + push (@{$lookup->{'RULES'}}, $subst); + } + $lookup->{'ACTION_TYPE'} = 'p'; + $lookup->{'MATCH_TYPE'} = 'g'; + } elsif ($type == 2 && $fmt == 2) + { + $fh->read($dat, 10); + ($lookup->{'VFMT2'}, $c1, $c2, $mcount, $scount) = TTF_Unpack('S*', $dat); + $lookup->{'CLASS'} = $self->read_cover($c1, $loc, $lookup, $fh, 0); + $lookup->{'MATCH'} = [$self->read_cover($c2, $loc, $lookup, $fh, 0)]; + $lookup->{'VFMT'} = $count; + for ($i = 0; $i < $mcount; $i++) + { + $subst = []; + for ($j = 0; $j < $scount; $j++) + { + $srec = {}; + $srec->{'ACTION'} = [$self->read_value($lookup->{'VFMT'}, $loc, $lookup, $fh), + $self->read_value($lookup->{'VFMT2'}, $loc, $lookup, $fh)]; + push (@$subst, $srec); + } + push (@{$lookup->{'RULES'}}, $subst); + } + $lookup->{'ACTION_TYPE'} = 'p'; + $lookup->{'MATCH_TYPE'} = 'c'; + } elsif ($type == 3 && $fmt == 1) + { + $fh->read($dat, $count << 2); + for ($i = 0; $i < $count; $i++) + { push (@{$lookup->{'RULES'}}, [{'ACTION' => + [$self->read_anchor(TTF_Unpack('S', substr($dat, $i << 2, 2)), + $loc, $lookup, $fh), + $self->read_anchor(TTF_Unpack('S', substr($dat, ($i << 2) + 2, 2)), + $loc, $lookup, $fh)]}]); } + $lookup->{'ACTION_TYPE'} = 'e'; + } elsif ($type == 4 || $type == 5 || $type == 6) + { + my (@offs, $mloc, $thisloc, $ncomp, $k); + + $lookup->{'MATCH'} = [$lookup->{'COVERAGE'}]; + $lookup->{'COVERAGE'} = $self->read_cover($count, $loc, $lookup, $fh, 1); + $fh->read($dat, 6); + ($mcount, $moff, $boff) = TTF_Unpack('S*', $dat); + $fh->seek($loc + $moff, 0); + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + for ($i = 0; $i < $count; $i++) + { + $fh->read($dat, 4); + push (@{$lookup->{'MARKS'}}, [TTF_Unpack('S', $dat), + $self->read_anchor(TTF_Unpack('S', substr($dat, 2, 2)) + $moff, + $loc, $lookup, $fh)]); + } + $fh->seek($loc + $boff, 0); + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + $mloc = $fh->tell() - 2; + $thisloc = $mloc; + if ($type == 5) + { + $fh->read($dat, $count << 1); + @offs = TTF_Unpack('S*', $dat); + } + for ($i = 0; $i < $count; $i++) + { + if ($type == 5) + { + $thisloc = $mloc + $offs[$i]; + $fh->seek($thisloc, 0); + $fh->read($dat, 2); + $ncomp = TTF_Unpack('S', $dat); + } else + { $ncomp = 1; } + for ($j = 0; $j < $ncomp; $j++) + { + $subst = []; + $fh->read($dat, $mcount << 1); + for ($k = 0; $k < $mcount; $k++) + { push (@$subst, $self->read_anchor(TTF_Unpack('S', substr($dat, $k << 1, 2)) + $thisloc - $loc, + $loc, $lookup, $fh)); } + + push (@{$lookup->{'RULES'}[$i]}, {'ACTION' => $subst}); + } + } + $lookup->{'ACTION_TYPE'} = 'a'; + } elsif ($type == 7 || $type == 8) + { $self->read_context($lookup, $fh, $type - 2, $fmt, $cover, $count, $loc); } + $lookup; +} + + +=head2 $t->extension + +Returns the table type number for the extension table + +=cut + +sub extension +{ return 9; } + + +=head2 $t->out_sub + +Outputs the subtable to the given filehandle + +=cut + +sub out_sub +{ + my ($self, $fh, $main_lookup, $index, $ctables, $base) = @_; + my ($type) = $main_lookup->{'TYPE'}; + my ($lookup) = $main_lookup->{'SUB'}[$index]; + my ($fmt) = $lookup->{'FORMAT'}; + my ($out, $r, $s, $t, $i, $j, $vfmt, $vfmt2, $loc1); + my ($num) = $#{$lookup->{'RULES'}} + 1; + my ($mtables) = {}; + my (@reftables); + + if ($type == 1 && $fmt == 1) + { + $out = pack('n2', $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base)); + $vfmt = $self->fmt_value($lookup->{'ADJUST'}); + $out .= pack('n', $vfmt) . $self->out_value($lookup->{'ADJUST'}, $vfmt, $ctables, 6 + $base); + } elsif ($type == 1 && $fmt == 2) + { + $vfmt = 0; + foreach $r (@{$lookup->{'RULES'}}) + { $vfmt |= $self->fmt_value($r->[0]{'ACTION'}[0]); } + $out = pack('n4', $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $vfmt, $#{$lookup->{'RULES'}} + 1); + foreach $r (@{$lookup->{'RULES'}}) + { $out .= $self->out_value($r->[0]{'ACTION'}[0], $vfmt, $ctables, length($out) + $base); } + } elsif ($type == 2 && $fmt < 3) + { + $vfmt = 0; + $vfmt2 = 0; + foreach $r (@{$lookup->{'RULES'}}) + { + foreach $t (@$r) + { + $vfmt |= $self->fmt_value($t->{'ACTION'}[0]); + $vfmt2 |= $self->fmt_value($t->{'ACTION'}[1]); + } + } + if ($fmt == 1) + { + # start PairPosFormat1 subtable + $out = pack('n5', + $fmt, + Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $vfmt, + $vfmt2, + $#{$lookup->{'RULES'}} + 1); # PairSetCount + my $off = 0; + $off += length($out); + $off += 2 * ($#{$lookup->{'RULES'}} + 1); # there will be PairSetCount offsets here + my $pairsets = ''; + my (%cache); + foreach $r (@{$lookup->{'RULES'}}) # foreach PairSet table + { + # write offset to this PairSet at end of PairPosFormat1 table + if (defined $cache{"$r"}) + { $out .= pack('n', $cache{"$r"}); } + else + { + $out .= pack('n', $off); + $cache{"$r"} = $off; + + # generate PairSet itself (using $off as eventual offset within PairPos subtable) + my $pairset = pack('n', $#{$r} + 1); # PairValueCount + foreach $t (@$r) # foreach PairValueRecord + { + $pairset .= pack('n', $t->{'MATCH'}[0]); # SecondGlyph - MATCH has only one entry + $pairset .= + $self->out_value($t->{'ACTION'}[0], $vfmt, $ctables, $off + length($pairset) + $base); + $pairset .= + $self->out_value($t->{'ACTION'}[1], $vfmt2, $ctables, $off + length($pairset) + $base); + } + $off += length($pairset); + $pairsets .= $pairset; + } + } + $out .= $pairsets; + die "internal error: PairPos size not as calculated" if (length($out) != $off); + } else + { + $out = pack('n8', $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $vfmt, $vfmt2, + Font::TTF::Ttopen::ref_cache($lookup->{'CLASS'}, $ctables, 8 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'MATCH'}[0], $ctables, 10 + $base), + $lookup->{'CLASS'}{'max'} + 1, $lookup->{'MATCH'}[0]{'max'} + 1); + + for ($i = 0; $i <= $lookup->{'CLASS'}{'max'}; $i++) + { + for ($j = 0; $j <= $lookup->{'MATCH'}[0]{'max'}; $j++) + { + $out .= $self->out_value($lookup->{'RULES'}[$i][$j]{'ACTION'}[0], $vfmt, $ctables, length($out) + $base); + $out .= $self->out_value($lookup->{'RULES'}[$i][$j]{'ACTION'}[1], $vfmt2, $ctables, length($out) + $base); + } + } + } + } elsif ($type == 3 && $fmt == 1) + { + $out = pack('n3', $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $#{$lookup->{'RULES'}} + 1); + foreach $r (@{$lookup->{'RULES'}}) + { + $out .= pack('n2', Font::TTF::Ttopen::ref_cache($r->[0]{'ACTION'}[0], $ctables, length($out) + $base), + Font::TTF::Ttopen::ref_cache($r->[0]{'ACTION'}[1], $ctables, length($out) + 2 + $base)); + } + } elsif ($type == 4 || $type == 5 || $type == 6) + { + my ($loc_off, $loc_t, $ltables); + + $out = pack('n7', $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'MATCH'}[0], $ctables, 2 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 4 + $base), + $#{$lookup->{'RULES'}[0][0]{'ACTION'}} + 1, 12, ($#{$lookup->{'MARKS'}} + 4) << 2, + $#{$lookup->{'MARKS'}} + 1); + foreach $r (@{$lookup->{'MARKS'}}) + { $out .= pack('n2', $r->[0], Font::TTF::Ttopen::ref_cache($r->[1], $mtables, length($out) + 2)); } + push (@reftables, [$mtables, 12]); + + $loc_t = length($out); + substr($out, 10, 2) = pack('n', $loc_t); + $out .= pack('n', $#{$lookup->{'RULES'}} + 1); + if ($type == 5) + { + $loc1 = length($out); + $out .= pack('n*', (0) x ($#{$lookup->{'RULES'}} + 1)); + } + $ltables = {}; + for ($i = 0; $i <= $#{$lookup->{'RULES'}}; $i++) + { + if ($type == 5) + { + $ltables = {}; + $loc_t = length($out); + substr($out, $loc1 + ($i << 1), 2) = TTF_Pack('S', $loc_t - $loc1 + 2); + } + + $r = $lookup->{'RULES'}[$i]; + $out .= pack('n', $#{$r} + 1) if ($type == 5); + foreach $t (@$r) + { + foreach $s (@{$t->{'ACTION'}}) + { $out .= pack('n', Font::TTF::Ttopen::ref_cache($s, $ltables, length($out))); } + } + push (@reftables, [$ltables, $loc_t]) if ($type == 5); + } + push (@reftables, [$ltables, $loc_t]) unless ($type == 5); + $out = Font::TTF::Ttopen::out_final($fh, $out, \@reftables, 1); + } elsif ($type == 7 || $type == 8) + { $out = $self->out_context($lookup, $fh, $type - 2, $fmt, $ctables, $out, $num, $base); } +# push (@reftables, [$ctables, 0]); + $out; +} + + +=head2 $t->read_value($format, $base, $lookup, $fh) + +Reads a value record from the current location in the file, according to the +format given. + +=cut + +sub read_value +{ + my ($self, $fmt, $base, $lookup, $fh) = @_; + my ($flag) = 1; + my ($res) = {}; + my ($s, $i, $dat); + + $s = 0; + for ($i = 0; $i < 12; $i++) + { + $s++ if ($flag & $fmt); + $flag <<= 1; + } + + $fh->read($dat, $s << 1); + $flag = 1; $i = 0; + foreach $s (qw(XPlacement YPlacement XAdvance YAdvance)) + { + $res->{$s} = TTF_Unpack('s', substr($dat, $i++ << 1, 2)) if ($fmt & $flag); + $flag <<= 1; + } + + foreach $s (qw(XPlaDevice YPlaDevice XAdvDevice YAdvDevice)) + { + if ($fmt & $flag) + { $res->{$s} = $self->read_delta(TTF_Unpack('S', substr($i++ << 1, 2)), + $base, $lookup, $fh); } + $flag <<= 1; + } + + foreach $s (qw(XIdPlacement YIdPlacement XIdAdvance YIdAdvance)) + { + $res->{$s} = TTF_Unpack('S', substr($dat, $i++ << 1, 2)) if ($fmt & $flag); + $flag <<= 1; + } + $res; +} + + +=head2 $t->read_delta($offset, $base, $lookup, $fh) + +Reads a delta (device table) at the given offset if it hasn't already been read. +Store the offset and item in the lookup cache ($lookup->{' CACHE'}) + +=cut + +sub read_delta +{ + my ($self, $offset, $base, $lookup, $fh) = @_; + my ($loc) = $fh->tell(); + my ($res, $str); + + return undef unless $offset; + $str = sprintf("%X", $base + $offset); + return $lookup->{' CACHE'}{$str} if defined $lookup->{' CACHE'}{$str}; + $fh->seek($base + $offset, 0); + $res = Font::TTF::Delta->new->read($fh); + $fh->seek($loc, 0); + $lookup->{' CACHE'}{$str} = $res; + return $res; +} + + +=head2 $t->read_anchor($offset, $base, $lookup, $fh) + +Reads an Anchor table at the given offset if it hasn't already been read. + +=cut + +sub read_anchor +{ + my ($self, $offset, $base, $lookup, $fh) = @_; + my ($loc) = $fh->tell(); + my ($res, $str); + + return undef unless $offset; + $str = sprintf("%X", $base + $offset); + return $lookup->{' CACHE'}{$str} if defined $lookup->{' CACHE'}{$str}; + $fh->seek($base + $offset, 0); + $res = Font::TTF::Anchor->new->read($fh); + $fh->seek($loc, 0); + $lookup->{' CACHE'}{$str} = $res; + return $res; +} + + +=head2 $t->fmt_value + +Returns the value format for a given value record + +=cut + +sub fmt_value +{ + my ($self, $value) = @_; + my ($fmt) = 0; + my ($n); + + foreach $n (reverse qw(XPlacement YPlacement XAdvance YAdvance XPlaDevice YPlaDevice + XAdvDevice YAdvDevice XIdPlacement YIdPlacement XIdAdvance + YIdAdvance)) + { + $fmt <<= 1; + $fmt |= 1 if (defined $value->{$n} && (ref $value->{$n} || $value->{$n})); + } + $fmt; +} + + +=head2 $t->out_value + +Returns the output string for the outputting of the value for a given format. Also +updates the offset cache for any device tables referenced. + +=cut + +sub out_value +{ + my ($self, $value, $fmt, $tables, $offset) = @_; + my ($n, $flag, $out); + + $flag = 1; + foreach $n (qw(XPlacement YPlacement XAdvance YAdvance)) + { + $out .= pack('n', $value->{$n}) if ($flag & $fmt); + $flag <<= 1; + } + foreach $n (qw(XPlaDevice YPlaDevice XAdvDevice YAdvDevice)) + { + if ($flag & $fmt) + { + $out .= pack('n', Font::TTF::Ttopen::ref_cache( + $value->{$n}, $tables, $offset + length($out))); + } + $flag <<= 1; + } + foreach $n (qw(XIdPlacement YIdPlacement XIdAdvance YIdAdvance)) + { + $out .= pack('n', $value->{$n}) if ($flag & $fmt); + $flag <<= 1; + } + $out; +} + + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/GSUB.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GSUB.pm new file mode 100644 index 0000000..abf0f13 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GSUB.pm @@ -0,0 +1,246 @@ +package Font::TTF::GSUB; + +=head1 NAME + +Font::TTF::GSUB - Module support for the GSUB table in conjunction with TTOpen + +=head1 DESCRIPTION + +Handles the GSUB subtables in relation to Ttopen tables. Due to the variety of +different lookup types, the data structures are not all that straightforward, +although I have tried to make life easy for myself when using this! + +=head1 INSTANCE VARIABLES + +The structure of a GSUB table is the same as that given in L. +Here we give some of the semantics specific to GSUB lookups. + +=over 4 + +=item ACTION_TYPE + +This is a string taking one of 4 values indicating the nature of the information +in the ACTION array of the rule: + +=over 8 + +=item g + +The action contains a string of glyphs to replace the match string by + +=item l + +The action array contains a list of lookups and offsets to run, in order, on +the matched string + +=item a + +The action array is an unordered set of optional replacements for the matched +glyph. The application should make the selection somehow. + +=item o + +The action array is empty (in fact there is no rule array for this type of +rule) and the ADJUST value should be added to the glyph id to find the replacement +glyph id value + +=back + +=item MATCH_TYPE + +This indicates which type of information the various MATCH arrays (MATCH, PRE, +POST) hold in the rule: + +=over 8 + +=item g + +The array holds a string of glyph ids which should match exactly + +=item c + +The array holds a sequence of class definitions which each glyph should +correspondingly match to + +=item o + +The array holds offsets to coverage tables + +=back + +=back + +=head1 CORRESPONDANCE TO LAYOUT TYPES + +The following table gives the values for ACTION_TYPE and MATCH_TYPE for each +of the 11 different lookup types found in the GSUB table definition I have: + + 1.1 1.2 2 3 4 5.1 5.2 5.3 6.1 6.2 6.3 + ACTION_TYPE o g g a g l l l l l l + MATCH_TYPE g g c o g c o + +Hopefully, the rest of the uses of the variables should make sense from this +table. + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::Ttopen; + +@ISA = qw(Font::TTF::Ttopen); + +=head2 $t->read_sub($fh, $lookup, $index) + +Asked by the superclass to read in from the given file the indexth subtable from +lookup number lookup. The file is positioned ready for the read. + +=cut + +sub read_sub +{ + my ($self, $fh, $main_lookup, $sindex) = @_; + my ($type) = $main_lookup->{'TYPE'}; + my ($loc) = $fh->tell(); + my ($lookup) = $main_lookup->{'SUB'}[$sindex]; + my ($dat, $s, @subst, $t, $fmt, $cover, $count, $mcount, $scount, $i, $gid); + my (@srec); + + if ($type == 6) + { + $fh->read($dat, 4); + ($fmt, $cover) = TTF_Unpack('S2', $dat); + if ($fmt < 3) + { + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + } + } else + { + $fh->read($dat, 6); + ($fmt, $cover, $count) = TTF_Unpack("S3", $dat); + } + unless ($fmt == 3 && ($type == 5 || $type == 6)) + { $lookup->{'COVERAGE'} = $self->read_cover($cover, $loc, $lookup, $fh, 1); } + + $lookup->{'FORMAT'} = $fmt; + if ($type == 1 && $fmt == 1) + { + $count -= 65536 if ($count > 32767); + $lookup->{'ADJUST'} = $count; + $lookup->{'ACTION_TYPE'} = 'o'; + } elsif ($type == 1 && $fmt == 2) + { + $fh->read($dat, $count << 1); + @subst = TTF_Unpack('S*', $dat); + foreach $s (@subst) + { push(@{$lookup->{'RULES'}}, [{'ACTION' => [$s]}]); } + $lookup->{'ACTION_TYPE'} = 'g'; + } elsif ($type == 2 || $type == 3) + { + $fh->read($dat, $count << 1); # number of offsets + foreach $s (TTF_Unpack('S*', $dat)) + { + $fh->seek($loc + $s, 0); + $fh->read($dat, 2); + $t = TTF_Unpack('S', $dat); + $fh->read($dat, $t << 1); + push(@{$lookup->{'RULES'}}, [{'ACTION' => [TTF_Unpack('S*', $dat)]}]); + } + $lookup->{'ACTION_TYPE'} = ($type == 2 ? 'g' : 'a'); + } elsif ($type == 4) + { + $fh->read($dat, $count << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { + @subst = (); + $fh->seek($loc + $s, 0); + $fh->read($dat, 2); + $t = TTF_Unpack('S', $dat); + $fh->read($dat, $t << 1); + foreach $t (TTF_Unpack('S*', $dat)) + { + $fh->seek($loc + $s + $t, 0); + $fh->read($dat, 4); + ($gid, $mcount) = TTF_Unpack('S2', $dat); + $fh->read($dat, ($mcount - 1) << 1); + push(@subst, {'ACTION' => [$gid], 'MATCH' => [TTF_Unpack('S*', $dat)]}); + } + push(@{$lookup->{'RULES'}}, [@subst]); + } + $lookup->{'ACTION_TYPE'} = 'g'; + $lookup->{'MATCH_TYPE'} = 'g'; + } elsif ($type == 5 || $type == 6) + { $self->read_context($lookup, $fh, $type, $fmt, $cover, $count, $loc); } + $lookup; +} + + +=head2 $t->extension + +Returns the table type number for the extension table + +=cut + +sub extension +{ return 7; } + + +=head2 $t->out_sub($fh, $lookup, $index) + +Passed the filehandle to output to, suitably positioned, the lookup and subtable +index, this function outputs the subtable to $fh at that point. + +=cut + +sub out_sub +{ + my ($self, $fh, $main_lookup, $index, $ctables, $base) = @_; + my ($type) = $main_lookup->{'TYPE'}; + my ($lookup) = $main_lookup->{'SUB'}[$index]; + my ($fmt) = $lookup->{'FORMAT'}; + my ($out, $r, $t, $i, $j, $offc, $offd, $numd); + my ($num) = $#{$lookup->{'RULES'}} + 1; + + if ($type == 1) + { + $out = pack("nn", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base)); + if ($fmt == 1) + { $out .= pack("n", $lookup->{'ADJUST'}); } + else + { + $out .= pack("n", $num); + foreach $r (@{$lookup->{'RULES'}}) + { $out .= pack("n", $r->[0]{'ACTION'}[0]); } + } + } elsif ($type == 2 || $type == 3) + { + $out = pack("nnn", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $num); + $out .= pack('n*', (0) x $num); + $offc = length($out); + for ($i = 0; $i < $num; $i++) + { + $out .= pack("n*", $#{$lookup->{'RULES'}[$i][0]{'ACTION'}} + 1, + @{$lookup->{'RULES'}[$i][0]{'ACTION'}}); + substr($out, ($i << 1) + 6, 2) = pack('n', $offc); + $offc = length($out); + } + } elsif ($type == 4 || $type == 5 || $type == 6) + { $out = $self->out_context($lookup, $fh, $type, $fmt, $ctables, $out, $num, $base); } +# Font::TTF::Ttopen::out_final($fh, $out, [[$ctables, 0]]); + $out; +} + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyf.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyf.pm new file mode 100644 index 0000000..e0268f3 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyf.pm @@ -0,0 +1,158 @@ +package Font::TTF::Glyf; + +=head1 NAME + +Font::TTF::Glyf - The Glyf data table + +=head1 DESCRIPTION + +This is a stub table. The real data is held in the loca table. If you want to get a glyf +look it up in the loca table as C<$f->{'loca'}{'glyphs'}[$num]>. It won't be here! + +The difference between reading this table as opposed to the loca table is that +reading this table will cause updated glyphs to be written out rather than just +copying the glyph information from the input file. This causes font writing to be +slower. So read the glyf as opposed to the loca table if you want to change glyf +data. Read the loca table only if you are just wanting to read the glyf information. + +This class is used when writing the glyphs though. + +=head1 METHODS + +=cut + + +use strict; +use vars qw(@ISA); +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the C table instead! + +=cut + +sub read +{ + my ($self) = @_; + + $self->{' PARENT'}{'loca'}->read; + $self->{' read'} = 1; + $self; +} + + +=head2 $t->out($fh) + +Writes out all the glyphs in the parent's location table, calculating a new +output location for each one. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i, $loca, $offset, $numGlyphs); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $loca = $self->{' PARENT'}{'loca'}{'glyphs'}; + $numGlyphs = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + $offset = 0; + for ($i = 0; $i < $numGlyphs; $i++) + { + next unless defined $loca->[$i]; + $loca->[$i]->update; + $loca->[$i]{' OUTLOC'} = $offset; + $loca->[$i]->out($fh); + $offset += $loca->[$i]{' OUTLEN'}; + } + $self->{' PARENT'}{'head'}{'indexToLocFormat'} = ($offset >= 0x20000); + $self; +} + + +=head2 $t->out_xml($context, $depth) + +Outputs all the glyphs in the glyph table just where they are supposed to be output! + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + my ($loca, $i, $numGlyphs); + + $loca = $self->{' PARENT'}{'loca'}{'glyphs'}; + $numGlyphs = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + for ($i = 0; $i < $numGlyphs; $i++) + { + $context->{'gid'} = $i; + $loca->[$i]->out_xml($context, $depth) if (defined $loca->[$i]); + } + + $self; +} + + +=head2 $t->XML_start($context, $tag, %attrs) + +Pass control to glyphs as they occur + +=cut + +sub XML_start +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag eq 'glyph') + { + $context->{'tree'}[-1] = Font::TTF::Glyph->new(read => 2, PARENT => $self->{' PARENT'}); + $context->{'receiver'} = $context->{'tree'}[-1]; + } +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Collect up glyphs and put them into the loca table + +=cut + +sub XML_end +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag eq 'glyph') + { + unless (defined $context->{'glyphs'}) + { + if (defined $self->{' PARENT'}{'loca'}) + { $context->{'glyphs'} = $self->{' PARENT'}{'loca'}{'glyphs'}; } + else + { $context->{'glyphs'} = []; } + } + $context->{'glyphs'}[$attrs{'gid'}] = $context->{'tree'}[-1]; + return $context; + } else + { return $self->SUPER::XML_end(@_); } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyph.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyph.pm new file mode 100644 index 0000000..28233cf --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Glyph.pm @@ -0,0 +1,847 @@ +package Font::TTF::Glyph; + +=head1 NAME + +Font::TTF::Glyph - Holds a single glyph's information + +=head1 DESCRIPTION + +This is a single glyph description as held in a TT font. On creation only its +header is read. Thus you can get the bounding box of each glyph without having +to read all the other information. + +=head1 INSTANCE VARIABLES + +In addition to the named variables in a glyph header (C etc.), there are +also all capital instance variables for holding working information, mostly +from the location table. + +The standard attributes each glyph has are: + + numberOfContours + xMin + yMin + xMax + yMax + +There are also other, derived, instance variables for each glyph which are read +when the whole glyph is read (via C): + +=over 4 + +=item instLen + +Number of bytes in the hinting instructions (Warning this variable is deprecated, +use C{'hints'})> instead). + +=item hints + +The string containing the hinting code for the glyph + +=back + +In addition there are other attribute like instance variables for simple glyphs: + +=over 4 + +For each contour there is: + +=over 4 + +=item endPoints + +An array of endpoints for each contour in the glyph. There are +C contours in a glyph. The number of points in a glyph is +equal to the highest endpoint of a contour. + +=back + +There are also a number of arrays indexed by point number + +=over 4 + +=item flags + +The flags associated with reading this point. The flags for a point are +recalculated for a point when it is Cd. Thus the flags are not very +useful. The only important bit is bit 0 which indicates whether the point is +an 'on' curve point, or an 'off' curve point. + +=item x + +The absolute x co-ordinate of the point. + +=item y + +The absolute y co-ordinate of the point + +=back + +=back + +For composite glyphs there are other variables + +=over 4 + +=item metric + +This holds the component number (not its glyph number) of the component from +which the metrics for this glyph should be taken. + +=item comps + +This is an array of hashes for each component. Each hash has a number of +elements: + +=over 4 + +=item glyph + +The glyph number of the glyph which comprises this component of the composite. +NOTE: In some badly generated fonts, C may contain a numerical value +but that glyph might not actually exist in the font file. This could +occur in any glyph, but is particularly likely for glyphs that have +no strokes, such as SPACE, U+00A0 NO-BREAK SPACE, or +U+200B ZERO WIDTH SPACE. + +=item args + +An array of two arguments which may be an x, y co-ordinate or two attachment +points (one on the base glyph the other on the component). See flags for details. + +=item flag + +The flag for this component + +=item scale + +A 4 number array for component scaling. This allows stretching, rotating, etc. +Note that scaling applies to placement co-ordinates (rather than attachment points) +before locating rather than after. + +=back + +=item numPoints + +This is a generated value which contains the number of components read in for this +compound glyph. + +=back + +The private instance variables are: + +=over 4 + +=item INFILE (P) + +The input file form which to read any information + +=item LOC (P) + +Location relative to the start of the glyf table in the read file + +=item BASE (P) + +The location of the glyf table in the read file + +=item LEN (P) + +This is the number of bytes required by the glyph. It should be kept up to date +by calling the C method whenever any of the glyph content changes. + +=item OUTLOC (P) + +Location relative to the start of the glyf table. This variable is only active +whilst the output process is going on. It is used to inform the location table +where the glyph's location is, since the glyf table is output before the loca +table due to alphabetical ordering. + +=item OUTLEN (P) + +This indicates the length of the glyph data when it is output. This more +accurately reflects the internal memory form than the C variable which +only reflects the read file length. The C variable is only set after +calling C or C. + +=back + +=head2 Editing + +If you want to edit a glyph in some way, then you should read_dat the glyph, then +make your changes and then update the glyph or set the $g->{' isdirty'} variable. +It is the application's duty to ensure that the following instance variables are +correct, from which update will calculate the rest, including the bounding box +information. + + numPoints + numberOfContours + endPoints + x, y, flags (only flags bit 0) + instLen + hints + +For components, the numPoints, x, y, endPoints & flags are not required but +the following information is required for each component. + + flag (bits 2, 10, 11, 12) + glyph + args + scale + metric (glyph instance variable) + + +=head1 METHODS + +=cut + +use strict; +use vars qw(%fields @field_info); +use Font::TTF::Utils; +use Font::TTF::Table; + +@field_info = ( + 'numberOfContours' => 's', + 'xMin' => 's', + 'yMin' => 's', + 'xMax' => 's', + 'yMax' => 's'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head1 Font::TTF::Glyph->new(%parms) + +Creates a new glyph setting various instance variables + +=cut + +sub new +{ + my ($class, %parms) = @_; + my ($self) = {}; + my ($p); + + bless $self, $class; + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + init unless defined $fields{'xMin'}; + $self; +} + + +=head2 $g->read + +Reads the header component of the glyph (bounding box, etc.) and also the +glyph content, but into a data field rather than breaking it down into +its constituent structures. Use read_dat for this. + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($dat); + + return $self if $self->{' read'}; + $self->{' read'} = 1; + $fh->seek($self->{' LOC'} + $self->{' BASE'}, 0); + $fh->read($self->{' DAT'}, $self->{' LEN'}); + TTF_Read_Fields($self, $self->{' DAT'}, \%fields); + $self; +} + + +=head2 $g->read_dat + +Reads the contents of the glyph (components and curves, etc.) from the memory +store C into structures within the object. Then, to indicate where the +master form of the data is, it deletes the C instance variable. + +=cut + +sub read_dat +{ + my ($self) = @_; + my ($dat, $num, $max, $i, $flag, $len, $val, $val1, $fp); + + return $self if (defined $self->{' read'} && $self->{' read'} > 1); + $self->read unless $self->{' read'}; + $dat = $self->{' DAT'}; + $fp = 10; + $num = $self->{'numberOfContours'}; + if ($num > 0) + { + $self->{'endPoints'} = [unpack("n*", substr($dat, $fp, $num << 1))]; + $fp += $num << 1; + $max = 0; + foreach (@{$self->{'endPoints'}}) + { $max = $_ if $_ > $max; } +# print STDERR join(",", unpack('C*', $self->{" DAT"})); +# printf STDERR ("(%d,%d in %d=%d @ %d)", scalar @{$self->{'endPoints'}}, $max, length($dat), $self->{' LEN'}, $fp); + $max++ if (@{$self->{'endPoints'}}); + $self->{'numPoints'} = $max; + $self->{'instLen'} = unpack("n", substr($dat, $fp)); + $self->{'hints'} = substr($dat, $fp + 2, $self->{'instLen'}); + $fp += 2 + $self->{'instLen'}; +# read the flags array + for ($i = 0; $i < $max; $i++) + { + $flag = unpack("C", substr($dat, $fp++)); + $self->{'flags'}[$i] = $flag; + if ($flag & 8) + { + $len = unpack("C", substr($dat, $fp++)); + while ($len-- > 0) + { + $i++; + $self->{'flags'}[$i] = $flag; + } + } + } +#read the x array + for ($i = 0; $i < $max; $i++) + { + $flag = $self->{'flags'}[$i]; + if ($flag & 2) + { + $val = unpack("C", substr($dat, $fp++)); + $val = -$val unless ($flag & 16); + } elsif ($flag & 16) + { $val = 0; } + else + { + $val = TTF_Unpack("s", substr($dat, $fp)); + $fp += 2; + } + $self->{'x'}[$i] = $i == 0 ? $val : $self->{'x'}[$i - 1] + $val; + } +#read the y array + for ($i = 0; $i < $max; $i++) + { + $flag = $self->{'flags'}[$i]; + if ($flag & 4) + { + $val = unpack("C", substr($dat, $fp++)); + $val = -$val unless ($flag & 32); + } elsif ($flag & 32) + { $val = 0; } + else + { + $val = TTF_Unpack("s", substr($dat, $fp)); + $fp += 2; + } + $self->{'y'}[$i] = $i == 0 ? $val : $self->{'y'}[$i - 1] + $val; + } + } + +# compound glyph + elsif ($num < 0) + { + $flag = 1 << 5; # cheat to get the loop going + for ($i = 0; $flag & 32; $i++) + { + ($flag, $self->{'comps'}[$i]{'glyph'}) = unpack("n2", substr($dat, $fp)); + $fp += 4; + $self->{'comps'}[$i]{'flag'} = $flag; + if ($flag & 1) # ARGS1_AND_2_ARE_WORDS + { + $self->{'comps'}[$i]{'args'} = [TTF_Unpack("s2", substr($dat, $fp))]; + $fp += 4; + } else + { + $self->{'comps'}[$i]{'args'} = [unpack("c2", substr($dat, $fp))]; + $fp += 2; + } + + if ($flag & 8) + { + $val = TTF_Unpack("F", substr($dat, $fp)); + $fp += 2; + $self->{'comps'}[$i]{'scale'} = [$val, 0, 0, $val]; + } elsif ($flag & 64) + { + ($val, $val1) = TTF_Unpack("F2", substr($dat, $fp)); + $fp += 4; + $self->{'comps'}[$i]{'scale'} = [$val, 0, 0, $val1]; + } elsif ($flag & 128) + { + $self->{'comps'}[$i]{'scale'} = [TTF_Unpack("F4", substr($dat, $fp))]; + $fp += 8; + } + $self->{'metric'} = $i if ($flag & 512); + } + $self->{'numPoints'} = $i; + if ($flag & 256) # HAVE_INSTRUCTIONS + { + $self->{'instLen'} = unpack("n", substr($dat, $fp)); + $self->{'hints'} = substr($dat, $fp + 2, $self->{'instLen'}); + $fp += 2 + $self->{'instLen'}; + } + } + return undef if ($fp > length($dat)); + $self->{' read'} = 2; + $self; +} + + +=head2 $g->out($fh) + +Writes the glyph data to outfile + +=cut + +sub out +{ + my ($self, $fh) = @_; + + $self->read unless $self->{' read'}; + $self->update if $self->{' isDirty'}; + $fh->print($self->{' DAT'}); + $self->{' OUTLEN'} = length($self->{' DAT'}); + $self; +} + + +=head2 $g->out_xml($context, $depth) + +Outputs an XML description of the glyph + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($addr) = ($self =~ m/\((.+)\)$/o); + my ($k, $ndepth); + + if ($context->{'addresses'}{$addr}) + { + $context->{'fh'}->printf("%s\n", $depth, $context->{'gid'}, $addr); + return $self; + } + else + { + $context->{'fh'}->printf("%s\n", $depth, $context->{'gid'}, $addr); + } + + $ndepth = $depth . $context->{'indent'}; + $self->read_dat; + foreach $k (sort grep {$_ !~ m/^\s/o} keys %{$self}) + { + $self->XML_element($context, $ndepth, $k, $self->{$k}); + } + $context->{'fh'}->print("$depth\n"); + delete $context->{'done_points'}; + $self; +} + + +sub XML_element +{ + my ($self, $context, $depth, $key, $val) = @_; + my ($fh) = $context->{'fh'}; + my ($dind) = $depth . $context->{'indent'}; + my ($i); + + if ($self->{'numberOfContours'} >= 0 && ($key eq 'x' || $key eq 'y' || $key eq 'flags')) + { + return $self if ($context->{'done_points'}); + $context->{'done_points'} = 1; + + $fh->print("$depth\n"); + for ($i = 0; $i <= $#{$self->{'flags'}}; $i++) + { $fh->printf("%s\n", $dind, + $self->{'x'}[$i], $self->{'y'}[$i], $self->{'flags'}[$i]); } + $fh->print("$depth\n"); + } + elsif ($key eq 'hints') + { + my ($dat); + $fh->print("$depth\n"); +# Font::TTF::Utils::XML_hexdump($context, $depth . $context->{'indent'}, $self->{'hints'}); + $dat = Font::TTF::Utils::XML_binhint($self->{'hints'}) || ""; + $dat =~ s/\n(?!$)/\n$depth$context->{'indent'}/mg; + $fh->print("$depth$context->{'indent'}$dat"); + $fh->print("$depth\n"); + } + else + { return Font::TTF::Table::XML_element(@_); } + + $self; +} + + +=head2 $g->update + +Generates a C<$self->{'DAT'}> from the internal structures, if the data has +been read into structures in the first place. If you are building a glyph +from scratch you will need to set the instance variable C<' read'> to 2 (or +something > 1) for the update to work. + +=cut + +sub update +{ + my ($self) = @_; + my ($dat, $loc, $len, $flag, $x, $y, $i, $comp, $num); + + return $self unless (defined $self->{' read'} && $self->{' read'} > 1); + $self->update_bbox; + $self->{' DAT'} = TTF_Out_Fields($self, \%fields, 10); + $num = $self->{'numberOfContours'}; + if ($num > 0) + { + $self->{' DAT'} .= pack("n*", @{$self->{'endPoints'}}); + $len = $self->{'instLen'}; + $self->{' DAT'} .= pack("n", $len); + $self->{' DAT'} .= pack("a" . $len, substr($self->{'hints'}, 0, $len)) if ($len > 0); + for ($i = 0; $i < $self->{'numPoints'}; $i++) + { + $flag = $self->{'flags'}[$i] & 1; + if ($i == 0) + { + $x = $self->{'x'}[$i]; + $y = $self->{'y'}[$i]; + } else + { + $x = $self->{'x'}[$i] - $self->{'x'}[$i - 1]; + $y = $self->{'y'}[$i] - $self->{'y'}[$i - 1]; + } + $flag |= 16 if ($x == 0); + $flag |= 32 if ($y == 0); + if (($flag & 16) == 0 && $x < 256 && $x > -256) + { + $flag |= 2; + $flag |= 16 if ($x >= 0); + } + if (($flag & 32) == 0 && $y < 256 && $y > -256) + { + $flag |= 4; + $flag |= 32 if ($y >= 0); + } + $self->{' DAT'} .= pack("C", $flag); # sorry no repeats + $self->{'flags'}[$i] = $flag; + } + for ($i = 0; $i < $self->{'numPoints'}; $i++) + { + $flag = $self->{'flags'}[$i]; + $x = $self->{'x'}[$i] - (($i == 0) ? 0 : $self->{'x'}[$i - 1]); + if (($flag & 18) == 0) + { $self->{' DAT'} .= TTF_Pack("s", $x); } + elsif (($flag & 18) == 18) + { $self->{' DAT'} .= pack("C", $x); } + elsif (($flag & 18) == 2) + { $self->{' DAT'} .= pack("C", -$x); } + } + for ($i = 0; $i < $self->{'numPoints'}; $i++) + { + $flag = $self->{'flags'}[$i]; + $y = $self->{'y'}[$i] - (($i == 0) ? 0 : $self->{'y'}[$i - 1]); + if (($flag & 36) == 0) + { $self->{' DAT'} .= TTF_Pack("s", $y); } + elsif (($flag & 36) == 36) + { $self->{' DAT'} .= pack("C", $y); } + elsif (($flag & 36) == 4) + { $self->{' DAT'} .= pack("C", -$y); } + } + } + + elsif ($num < 0) + { + for ($i = 0; $i <= $#{$self->{'comps'}}; $i++) + { + $comp = $self->{'comps'}[$i]; + $flag = $comp->{'flag'} & 7158; # bits 2,10,11,12 + $flag |= 1 unless ($comp->{'args'}[0] > -129 && $comp->{'args'}[0] < 128 + && $comp->{'args'}[1] > -129 && $comp->{'args'}[1] < 128); + if (defined $comp->{'scale'}) + { + if ($comp->{'scale'}[1] == 0 && $comp->{'scale'}[2] == 0) + { + if ($comp->{'scale'}[0] == $comp->{'scale'}[3]) + { $flag |= 8 unless ($comp->{'scale'}[0] == 0 + || $comp->{'scale'}[0] == 1); } + else + { $flag |= 64; } + } else + { $flag |= 128; } + } + + $flag |= 512 if (defined $self->{'metric'} && $self->{'metric'} == $i); + if ($i == $#{$self->{'comps'}}) + { $flag |= 256 if (defined $self->{'instLen'} && $self->{'instLen'} > 0); } + else + { $flag |= 32; } + + $self->{' DAT'} .= pack("n", $flag); + $self->{' DAT'} .= pack("n", $comp->{'glyph'}); + $comp->{'flag'} = $flag; + + if ($flag & 1) + { $self->{' DAT'} .= TTF_Pack("s2", @{$comp->{'args'}}); } + else + { $self->{' DAT'} .= pack("CC", @{$comp->{'args'}}); } + + if ($flag & 8) + { $self->{' DAT'} .= TTF_Pack("F", $comp->{'scale'}[0]); } + elsif ($flag & 64) + { $self->{' DAT'} .= TTF_Pack("F2", $comp->{'scale'}[0], $comp->{'scale'}[3]); } + elsif ($flag & 128) + { $self->{' DAT'} .= TTF_Pack("F4", @{$comp->{'scale'}}); } + } + if (defined $self->{'instLen'} && $self->{'instLen'} > 0) + { + $len = $self->{'instLen'}; + $self->{' DAT'} .= pack("n", $len); + $self->{' DAT'} .= pack("a" . $len, substr($self->{'hints'}, 0, $len)); + } + } + my ($olen) = length($self->{' DAT'}); + $self->{' DAT'} .= ("\000") x (4 - ($olen & 3)) if ($olen & 3); + $self->{' OUTLEN'} = length($self->{' DAT'}); + $self->{' read'} = 2; # changed from 1 to 2 so we don't read_dat() again +# we leave numPoints and instLen since maxp stats use this + $self; +} + + +=head2 $g->update_bbox + +Updates the bounding box for this glyph according to the points in the glyph + +=cut + +sub update_bbox +{ + my ($self) = @_; + my ($num, $maxx, $minx, $maxy, $miny, $i, $comp, $x, $y, $compg); + + return $self unless $self->{' read'} > 1; # only if read_dat done + $miny = $minx = 65537; $maxx = $maxy = -65537; + $num = $self->{'numberOfContours'}; + if ($num > 0) + { + for ($i = 0; $i < $self->{'numPoints'}; $i++) + { + ($x, $y) = ($self->{'x'}[$i], $self->{'y'}[$i]); + + $maxx = $x if ($x > $maxx); + $minx = $x if ($x < $minx); + $maxy = $y if ($y > $maxy); + $miny = $y if ($y < $miny); + } + } + + elsif ($num < 0) + { + foreach $comp (@{$self->{'comps'}}) + { + my ($gnx, $gny, $gxx, $gxy); + my ($sxx, $sxy, $syx, $syy); + + my $otherg = $self->{' PARENT'}{'loca'}{'glyphs'}[$comp->{'glyph'}]; + # work around bad fonts: see documentation for 'comps' above + next unless (defined $otherg); + $compg = $otherg->read->update_bbox; + ($gnx, $gny, $gxx, $gxy) = @{$compg}{'xMin', 'yMin', 'xMax', 'yMax'}; + if (defined $comp->{'scale'}) + { + ($sxx, $sxy, $syx, $syy) = @{$comp->{'scale'}}; + ($gnx, $gny, $gxx, $gxy) = ($gnx*$sxx+$gny*$syx + $comp->{'args'}[0], + $gnx*$sxy+$gny*$syy + $comp->{'args'}[1], + $gxx*$sxx+$gxy*$syx + $comp->{'args'}[0], + $gxx*$sxy+$gxy*$syy + $comp->{'args'}[1]); + } elsif ($comp->{'args'}[0] || $comp->{'args'}[1]) + { + $gnx += $comp->{'args'}[0]; + $gny += $comp->{'args'}[1]; + $gxx += $comp->{'args'}[0]; + $gxy += $comp->{'args'}[1]; + } + ($gnx, $gxx) = ($gxx, $gnx) if $gnx > $gxx; + ($gny, $gxy) = ($gxy, $gny) if $gny > $gxy; + $maxx = $gxx if $gxx > $maxx; + $minx = $gnx if $gnx < $minx; + $maxy = $gxy if $gxy > $maxy; + $miny = $gny if $gny < $miny; + } + } + $self->{'xMax'} = $maxx; + $self->{'xMin'} = $minx; + $self->{'yMax'} = $maxy; + $self->{'yMin'} = $miny; + $self; +} + + +=head2 $g->maxInfo + +Returns lots of information about a glyph so that the C table can update +itself. Returns array containing contributions of this glyph to maxPoints, maxContours, +maxCompositePoints, maxCompositeContours, maxSizeOfInstructions, maxComponentElements, +and maxComponentDepth. + +=cut + +sub maxInfo +{ + my ($self) = @_; + my (@res, $i, @n); + + $self->read_dat; # make sure we've read some data + $res[4] = length($self->{'hints'}) if defined $self->{'hints'}; + $res[6] = 1; + if ($self->{'numberOfContours'} > 0) + { + $res[0] = $self->{'numPoints'}; + $res[1] = $self->{'numberOfContours'}; + } elsif ($self->{'numberOfContours'} < 0) + { + for ($i = 0; $i <= $#{$self->{'comps'}}; $i++) + { + my $otherg = + $self->{' PARENT'}{'loca'}{'glyphs'} + [$self->{'comps'}[$i]{'glyph'}]; + + # work around bad fonts: see documentation for 'comps' above + next unless (defined $otherg ); + + @n = $otherg->maxInfo; + + $res[2] += $n[2] == 0 ? $n[0] : $n[2]; + $res[3] += $n[3] == 0 ? $n[1] : $n[3]; + $res[5]++; + $res[6] = $n[6] + 1 if ($n[6] >= $res[6]); + } + } + @res; +} + +=head2 $g->empty + +Empties the glyph of all information to the level of not having been read. +Useful for saving memory in apps with many glyphs being read + +=cut + +sub empty +{ + my ($self) = @_; + my (%keep) = map {(" $_" => 1)} ('LOC', 'OUTLOC', 'PARENT', 'INFILE', 'BASE', + 'OUTLEN', 'LEN'); + map {delete $self->{$_} unless $keep{$_}} keys %$self; + + $self; +} + + +=head2 $g->get_points + +This method creates point information for a compound glyph. The information is +stored in the same place as if the glyph was not a compound, but since +numberOfContours is negative, the glyph is still marked as being a compound + +=cut + +sub get_points +{ + my ($self) = @_; + my ($comp, $compg, $nump, $e, $i); + + $self->read_dat; + return undef unless ($self->{'numberOfContours'} < 0); + + foreach $comp (@{$self->{'comps'}}) + { + $compg = $self->{' PARENT'}{'loca'}{'glyphs'}[$comp->{'glyph'}]; + # work around bad fonts: see documentation for 'comps' above + next unless (defined $compg ); + $compg->get_points; + + for ($i = 0; $i < $compg->{'numPoints'}; $i++) + { + my ($x, $y) = ($compg->{'x'}[$i], $compg->{'y'}[$i]); + if (defined $comp->{'scale'}) + { + ($x, $y) = ($x * $comp->{'scale'}[0] + $y * $comp->{'scale'}[2], + $x * $comp->{'scale'}[1] + $y * $comp->{'scale'}[3]); + } + if (defined $comp->{'args'}) + { ($x, $y) = ($x + $comp->{'args'}[0], $y + $comp->{'args'}[1]); } + push (@{$self->{'x'}}, $x); + push (@{$self->{'y'}}, $y); + push (@{$self->{'flags'}}, $compg->{'flags'}[$i]); + } + foreach $e (@{$compg->{'endPoints'}}) + { push (@{$self->{'endPoints'}}, $e + $nump); } + $nump += $compg->{'numPoints'}; + } + $self->{'numPoints'} = $nump; + $self; +} + + +=head2 $g->get_refs + +Returns an array of all the glyph ids that are used to make up this glyph. That +is all the compounds and their references and so on. If this glyph is not a +compound, then returns an empty array. + +Please note the warning about bad fonts that reference nonexistant glyphs +under INSTANCE VARIABLES above. This function will not attempt to +filter out nonexistant glyph numbers. + +=cut + +sub get_refs +{ + my ($self) = @_; + my (@res, $g); + + $self->read_dat; + return unless ($self->{'numberOfContours'} < 0); + foreach $g (@{$self->{'comps'}}) + { + push (@res, $g->{'glyph'}); + my $otherg = $self->{' PARENT'}{'loca'}{'glyphs'}[$g->{'glyph'}]; + # work around bad fonts: see documentation for 'comps' above + next unless (defined $otherg); + my @list = $otherg->get_refs; + push(@res, @list); + } + return @res; +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +The instance variables used here are somewhat clunky and inconsistent with +the other tables. + +=item * + +C doesn't re-calculate the bounding box or C. + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/GrFeat.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GrFeat.pm new file mode 100644 index 0000000..2732a69 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/GrFeat.pm @@ -0,0 +1,249 @@ +package Font::TTF::GrFeat; + +=head1 NAME + +Font::TTF::GrFeat - Graphite Font Features + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over 4 + +=item version + +=item features + +An array of hashes of the following form + +=over 8 + +=item feature + +feature id number + +=item name + +name index in name table + +=item exclusive + +exclusive flag + +=item default + +the default setting number + +=item settings + +hash of setting number against name string index + +=back + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); + +use Font::TTF::Utils; + +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the features from the TTF file into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($featureCount, $features); + + return $self if $self->{' read'}; + $self->SUPER::read_dat or return $self; + + ($self->{'version'}, $featureCount) = TTF_Unpack("vS", $self->{' dat'}); + + $features = []; + foreach (1 .. $featureCount) { + my ($feature, $nSettings, $settingTable, $featureFlags, $nameIndex, $reserved); + if ($self->{'version'} == 1) + { + ($feature, $nSettings, $settingTable, $featureFlags, $nameIndex) + = TTF_Unpack("SSLSS", substr($self->{' dat'}, $_ * 12, 12)); + #The version 1 Feat table ends with a feature (id 1) named NoName + #with zero settings but with an offset to the last entry in the setting + #array. This last setting has id 0 and an invalid name id. This last + #feature is changed to have one setting. + if ($_ == $featureCount && $nSettings == 0) {$nSettings = 1;} + } + else #version == 2 + {($feature, $nSettings, $reserved, $settingTable, $featureFlags, $nameIndex) + = TTF_Unpack("LSSLSS", substr($self->{' dat'}, 12 + ($_ - 1) * 16, 16))}; + my $feature = + { + 'feature' => $feature, + 'name' => $nameIndex, + }; + + #interpret the featureFlags & store settings + $feature->{'exclusive'} = (($featureFlags & 0x8000) != 0); + + my @settings = TTF_Unpack("S*", substr($self->{' dat'}, $settingTable, $nSettings * 4)); + if ($featureFlags & 0x4000) + {$feature->{'default'} = $featureFlags & 0x00FF;} + else + {$feature->{'default'} = @settings[0];} + $feature->{'settings'} = {@settings}; + + push(@$features, $feature); + } + + $self->{'features'} = $features; + + delete $self->{' dat'}; # no longer needed, and may become obsolete + $self->{' read'} = 1; + $self; +} + +=head2 $t->out($fh) + +Writes the features to a TTF file + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($features, $numFeatures, $settings, $featureFlags, $featuresData, $settingsData); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $features = $self->{'features'}; + $numFeatures = @$features; + $featuresData, $settingsData = ('', ''); + + foreach (@$features) { + $settings = $_->{'settings'}; + $featureFlags = ($_->{'exclusive'} ? 0x8000 : 0x0000); + +# output default setting first instead of using the featureFlags (as done below) +# $featureFlags = ($_->{'exclusive'} ? 0x8000 : 0x0000) | +# ($_->{'default'} != 0 ? 0x4000 | ($_->{'default'} & 0x00FF) +# : 0x0000); + if ($self->{'version'} == 1) + { + $featuresData .= TTF_Pack("SSLSS", + $_->{'feature'}, + scalar keys %$settings, + 12 + 12 * $numFeatures + length $settingsData, + $featureFlags, + $_->{'name'}); + } + else #version == 2 + { + $featuresData .= TTF_Pack("LSSLSS", + $_->{'feature'}, + scalar keys %$settings, + 0, + 12 + 16 * $numFeatures + length $settingsData, + $featureFlags, + $_->{'name'}); + } + + #output default setting first + #the settings may not be in their original order + my $defaultSetting = $_->{'default'}; + $settingsData .= TTF_Pack("SS", $defaultSetting, $settings->{$defaultSetting}); + foreach (sort {$a <=> $b} keys %$settings) { + if ($_ == $defaultSetting) {next;} #skip default setting + $settingsData .= TTF_Pack("SS", $_, $settings->{$_}); + } + } + + $fh->print(TTF_Pack("vSSL", $self->{'version'}, $numFeatures, 0, 0)); + $fh->print($featuresData); + $fh->print($settingsData); + + $self; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + my ($names, $features, $settings); + + $self->read; + + $names = $self->{' PARENT'}->{'name'}; + $names->read; + + $fh = 'STDOUT' unless defined $fh; + + $features = $self->{'features'}; + foreach (@$features) { + $fh->printf("Feature %d, %s, default: %d name %d # '%s'\n", + $_->{'feature'}, + ($_->{'exclusive'} ? "exclusive" : "additive"), + $_->{'default'}, + $_->{'name'}, + $names->{'strings'}[$_->{'name'}][3][1]{1033}); + $settings = $_->{'settings'}; + foreach (sort { $a <=> $b } keys %$settings) { + $fh->printf("\tSetting %d, name %d # '%s'\n", + $_, $settings->{$_}, $names->{'strings'}[$settings->{$_}][3][1]{1033}); + } + } + + $self; +} + +sub settingName +{ + my ($self, $feature, $setting) = @_; + + $self->read; + + my $names = $self->{' PARENT'}->{'name'}; + $names->read; + + my $features = $self->{'features'}; + my ($featureEntry) = grep { $_->{'feature'} == $feature } @$features; + my $featureName = $names->{'strings'}[$featureEntry->{'name'}][3][1]{1033}; + my $settingName = $featureEntry->{'exclusive'} + ? $names->{'strings'}[$featureEntry->{'settings'}->{$setting}][3][1]{1033} + : $names->{'strings'}[$featureEntry->{'settings'}->{$setting & ~1}][3][1]{1033} + . (($setting & 1) == 0 ? " On" : " Off"); + + ($featureName, $settingName); +} + +1; + +=head1 BUGS + +The version 1 Feat table ends with a feature (id 1) named NoName +with zero settings but with an offset to the last entry in the setting +array. This last setting has id 0 and an invalid name id. This last +feature is changed to have one setting. + +=head1 AUTHOR + +Alan Ward (derived from Jonathan Kew's Feat.pm). +See L for copyright and licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hdmx.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hdmx.pm new file mode 100644 index 0000000..d1e47cb --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hdmx.pm @@ -0,0 +1,149 @@ +package Font::TTF::Hdmx; + +=head1 NAME + +Font::TTF::Hdmx - Horizontal device metrics + +=head1 DESCRIPTION + +The table consists of an hash of device metric tables indexed by the ppem for +that subtable. Each subtable consists of an array of advance widths in pixels +for each glyph at that ppem (horizontally). + +=head1 INSTANCE VARIABLES + +Individual metrics are accessed using the following referencing: + + $f->{'hdmx'}{$ppem}[$glyph_num] + +In addition there is one instance variable: + +=over 4 + +=item Num + +Number of device tables. + +=back + +=head2 METHODS + +=cut + +use strict; +use vars qw(@ISA); + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the table into data structures + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($numg, $ppem, $i, $numt, $dat, $len); + + $numg = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + $self->SUPER::read or return $self; + + $fh->read($dat, 8); + ($self->{'Version'}, $numt, $len) = unpack("nnN", $dat); + $self->{'Num'} = $numt; + + for ($i = 0; $i < $numt; $i++) + { + $fh->read($dat, $len); + $ppem = unpack("C", $dat); + $self->{$ppem} = [unpack("C$numg", substr($dat, 2))]; + } + $self; +} + + +=head2 $t->out($fh) + +Outputs the device metrics for this font + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($numg, $i, $pad, $len, $numt, @ppem, $max); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + $numg = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + @ppem = grep(/^\d+$/, sort {$a <=> $b} keys %$self); + $pad = "\000" x (3 - ($numg + 1) % 4); + $len = $numg + 2 + length($pad); + $fh->print(pack("nnN", 0, $#ppem + 1, $len)); + for $i (@ppem) + { + $max = 0; + foreach (@{$self->{$i}}[0..($numg - 1)]) + { $max = $_ if $_ > $max; } + $fh->print(pack("C*", $i, $max, @{$self->{$i}}[0..($numg - 1)]) . $pad); + } + $self; +} + + +=head2 $t->tables_do(&func) + +For each subtable it calls &sub($ref, $ppem) + +=cut + +sub tables_do +{ + my ($self, $func) = @_; + my ($i); + + foreach $i (grep(/^\d+$/, %$self)) + { &$func($self->{$i}, $i); } + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $value) + +Outputs device metrics a little more tidily + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $value) = @_; + my ($fh) = $context->{'fh'}; + my ($i); + + return $self->SUPER::XML_element(@_) if (ref($value) ne 'ARRAY'); + $fh->print("$depth\n"); + for ($i = 0; $i <= $#{$value}; $i += 25) + { + $fh->print("$depth$context->{'indent'}". join(' ', @{$value}[$i .. $i + 24]) . "\n"); + } + $fh->print("$depth\n"); + $self; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Head.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Head.pm new file mode 100644 index 0000000..362e8e4 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Head.pm @@ -0,0 +1,250 @@ +package Font::TTF::Head; + +=head1 NAME + +Font::TTF::Head - The head table for a TTF Font + +=head1 DESCRIPTION + +This is a very basic table with just instance variables as described in the +TTF documentation, using the same names. One of the most commonly used is +C. + +=head1 INSTANCE VARIABLES + +The C table has no internal instance variables beyond those common to all +tables and those specified in the standard: + + version + fontRevision + checkSumAdjustment + magicNumber + flags + unitsPerEm + created + modified + xMin + yMin + xMax + yMax + macStyle + lowestRecPPEM + fontDirectionHint + indexToLocFormat + glyphDataFormat + +The two dates are held as an array of two unsigned longs (32-bits) + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'version' => 'v', + 'fontRevision' => 'f', + 'checkSumAdjustment' => 'L', + 'magicNumber' => 'L', + 'flags' => 'S', + 'unitsPerEm' => 'S', + 'created' => 'L2', + 'modified' => 'L2', + 'xMin' => 's', + 'yMin' => 's', + 'xMax' => 's', + 'yMax' => 's', + 'macStyle' => 'S', + 'lowestRecPPEM' => 'S', + 'fontDirectionHint' => 's', + 'indexToLocFormat' => 's', + 'glyphDataFormat' => 's'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory thanks to some utility functions + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read || return $self; + + init unless defined $fields{'Ascender'}; + $self->{' INFILE'}->read($dat, 54); + + TTF_Read_Fields($self, $dat, \%fields); + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. If in memory +(which is usually) the checkSumAdjustment field is set to 0 as per the default +if the file checksum is not to be considered. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; # this is never true +# $self->{'checkSumAdjustment'} = 0 unless $self->{' PARENT'}{' wantsig'}; + $fh->print(TTF_Out_Fields($self, \%fields, 54)); + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $value) + +Handles date process for the XML exporter + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $value) = @_; + my ($fh) = $context->{'fh'}; + my ($output, @time); + my (@month) = qw(JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC); + + return $self->SUPER::XML_element(@_) unless ($key eq 'created' || $key eq 'modified'); + + @time = gmtime($self->getdate($key eq 'created')); + $output = sprintf("%d/%s/%d %d:%d:%d", $time[3], $month[$time[4]], $time[5] + 1900, + $time[2], $time[1], $time[0]); + $fh->print("$depth<$key>$output\n"); + $self; +} + + +=head2 $t->update + +Updates the head table based on the glyph data and the hmtx table + +=cut + +sub update +{ + my ($self) = @_; + my ($num, $i, $loc, $hmtx); + my ($xMin, $yMin, $xMax, $yMax, $lsbx); + + return undef unless ($self->SUPER::update); + + $num = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + return undef unless (defined $self->{' PARENT'}{'hmtx'} && defined $self->{' PARENT'}{'loca'}); + $hmtx = $self->{' PARENT'}{'hmtx'}->read; + + $self->{' PARENT'}{'loca'}->update; + $hmtx->update; # if we updated, then the flags will be set anyway. + $lsbx = 1; + for ($i = 0; $i < $num; $i++) + { + $loc = $self->{' PARENT'}{'loca'}{'glyphs'}[$i]; + next unless defined $loc; + $loc->read->update_bbox; + $xMin = $loc->{'xMin'} if ($loc->{'xMin'} < $xMin || $i == 0); + $yMin = $loc->{'yMin'} if ($loc->{'yMin'} < $yMin || $i == 0); + $xMax = $loc->{'xMax'} if ($loc->{'xMax'} > $xMax); + $yMax = $loc->{'yMax'} if ($loc->{'yMax'} > $yMax); + $lsbx &= ($loc->{'xMin'} == $hmtx->{'lsb'}[$i]); + } + $self->{'xMin'} = $xMin; + $self->{'yMin'} = $yMin; + $self->{'xMax'} = $xMax; + $self->{'yMax'} = $yMax; + if ($lsbx) + { $self->{'flags'} |= 2; } + else + { $self->{'flags'} &= ~2; } + $self; +} + + +=head2 $t->getdate($is_create) + +Converts font modification time (or creation time if $is_create is set) to a 32-bit integer as returned +from time(). Returns undef if the value is out of range, either before the epoch or after the maximum +storable time. + +=cut + +sub getdate +{ + my ($self, $is_create) = @_; + my ($arr) = $self->{$is_create ? 'created' : 'modified'}; + + $arr->[1] -= 2082844800; # seconds between 1/Jan/1904 and 1/Jan/1970 (midnight) + if ($arr->[1] < 0) + { + $arr->[1] += 0xFFFFFFF; $arr->[1]++; + $arr->[0]--; + } + return undef if $arr->[0] != 0; + return $arr->[1]; +} + + +=head2 $t->setdate($time, $is_create) + +Sets the time information for modification (or creation time if $is_create is set) according to the 32-bit +time information. + +=cut + +sub setdate +{ + my ($self, $time, $is_create) = @_; + my (@arr); + + $arr[1] = $time; + if ($arr[1] >= 0x83DA4F80) + { + $arr[1] -= 0xFFFFFFFF; + $arr[1]--; + $arr[0]++; + } + $arr[1] += 2082844800; + $self->{$is_create ? 'created' : 'modified'} = \@arr; + $self; +} + + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hhea.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hhea.pm new file mode 100644 index 0000000..cb923b1 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hhea.pm @@ -0,0 +1,159 @@ +package Font::TTF::Hhea; + +=head1 NAME + +Font::TTF::Hhea - Horizontal Header table + +=head1 DESCRIPTION + +This is a simplte table with just standards specified instance variables + +=head1 INSTANCE VARIABLES + + version + Ascender + Descender + LineGap + advanceWidthMax + minLeftSideBearing + minRightSideBearing + xMaxExtent + caretSlopeRise + caretSlopeRun + metricDataFormat + numberOfHMetrics + + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'version' => 'v', + 'Ascender' => 's', + 'Descender' => 's', + 'LineGap' => 's', + 'advanceWidthMax' => 'S', + 'minLeftSideBearing' => 's', + 'minRightSideBearing' => 's', + 'xMaxExtent' => 's', + 'caretSlopeRise' => 's', + 'caretSlopeRun' => 's', + 'metricDataFormat' => '+10s', + 'numberOfHMetrics' => 'S'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory as instance variables + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read or return $self; + init unless defined $fields{'Ascender'}; + $self->{' INFILE'}->read($dat, 36); + + TTF_Read_Fields($self, $dat, \%fields); + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $self->{'numberOfHMetrics'} = $self->{' PARENT'}{'hmtx'}->numMetrics || $self->{'numberOfHMetrics'}; + $fh->print(TTF_Out_Fields($self, \%fields, 36)); + $self; +} + + +=head2 $t->update + +Updates various parameters in the hhea table from the hmtx table. + +=cut + +sub update +{ + my ($self) = @_; + my ($hmtx) = $self->{' PARENT'}{'hmtx'}; + my ($glyphs); + my ($num, $res); + my ($i, $maw, $mlsb, $mrsb, $mext, $aw, $lsb, $ext); + + return undef unless ($self->SUPER::update); + return undef unless (defined $hmtx && defined $self->{' PARENT'}{'loca'}); + + $hmtx->read->update; + $self->{' PARENT'}{'loca'}->read->update; + $glyphs = $self->{' PARENT'}{'loca'}{'glyphs'}; + $num = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + for ($i = 0; $i < $num; $i++) + { + $aw = $hmtx->{'advance'}[$i]; + $lsb = $hmtx->{'lsb'}[$i]; + if (defined $glyphs->[$i]) + { $ext = $lsb + $glyphs->[$i]->read->{'xMax'} - $glyphs->[$i]{'xMin'}; } + else + { $ext = $aw; } + $maw = $aw if ($aw > $maw); + $mlsb = $lsb if ($lsb < $mlsb or $i == 0); + $mrsb = $aw - $ext if ($aw - $ext < $mrsb or $i == 0); + $mext = $ext if ($ext > $mext); + } + $self->{'advanceWidthMax'} = $maw; + $self->{'minLeftSideBearing'} = $mlsb; + $self->{'minRightSideBearing'} = $mrsb; + $self->{'xMaxExtent'} = $mext; + $self->{'numberOfHMetrics'} = $hmtx->numMetrics; + $self; +} + + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hmtx.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hmtx.pm new file mode 100644 index 0000000..537df94 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Hmtx.pm @@ -0,0 +1,214 @@ +package Font::TTF::Hmtx; + +=head1 NAME + +Font::TTF::Hmtx - Horizontal Metrics + +=head1 DESCRIPTION + +Contains the advance width and left side bearing for each glyph. Given the +compressability of the data onto disk, this table uses information from +other tables, and thus must do part of its output during the output of +other tables + +=head1 INSTANCE VARIABLES + +The horizontal metrics are kept in two arrays by glyph id. The variable names +do not start with a space + +=over 4 + +=item advance + +An array containing the advance width for each glyph + +=item lsb + +An array containing the left side bearing for each glyph + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the horizontal metrics from the TTF file into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($numh, $numg); + + $numh = $self->{' PARENT'}{'hhea'}->read->{'numberOfHMetrics'}; + $numg = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + $self->_read($numg, $numh, "advance", "lsb"); +} + +sub _read +{ + my ($self, $numg, $numh, $tAdv, $tLsb) = @_; + my ($fh) = $self->{' INFILE'}; + my ($i, $dat); + + $self->SUPER::read or return $self; + + for ($i = 0; $i < $numh; $i++) + { + $fh->read($dat, 4); + ($self->{$tAdv}[$i], $self->{$tLsb}[$i]) = unpack("nn", $dat); + $self->{$tLsb}[$i] -= 65536 if ($self->{$tLsb}[$i] >= 32768); + } + + $i--; + while (++$i < $numg) + { + $fh->read($dat, 2); + $self->{$tAdv}[$i] = $self->{$tAdv}[$numh - 1]; + $self->{$tLsb}[$i] = unpack("n", $dat); + $self->{$tLsb}[$i] -= 65536 if ($self->{$tLsb}[$i] >= 32768); + } + $self; +} + +=head2 $t->numMetrics + +Calculates again the number of long metrics required to store the information +here. Returns undef if the table has not been read. + +=cut + +sub numMetrics +{ + my ($self) = @_; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($i); + + return undef unless $self->{' read'}; + + for ($i = $numg - 2; $i >= 0; $i--) + { last if ($self->{'advance'}[$i] != $self->{'advance'}[$i + 1]); } + + return $i + 2; +} + + +=head2 $t->out($fh) + +Writes the metrics to a TTF file. Assumes that the C has updated the +numHMetrics from here + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($numh) = $self->{' PARENT'}{'hhea'}->read->{'numberOfHMetrics'}; + $self->_out($fh, $numg, $numh, "advance", "lsb"); +} + +sub _out +{ + my ($self, $fh, $numg, $numh, $tAdv, $tLsb) = @_; + my ($i, $lsb); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + for ($i = 0; $i < $numg; $i++) + { + $lsb = $self->{$tLsb}[$i]; + $lsb += 65536 if $lsb < 0; + if ($i >= $numh) + { $fh->print(pack("n", $lsb)); } + else + { $fh->print(pack("n2", $self->{$tAdv}[$i], $lsb)); } + } + $self; +} + + +=head2 $t->update + +Updates the lsb values from the xMin from the each glyph + +=cut + +sub update +{ + my ($self) = @_; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($i); + + return undef unless ($self->SUPER::update); +# lsb & xMin must always be the same, regardless of any flags! +# return $self unless ($self->{' PARENT'}{'head'}{'flags'} & 2); # lsb & xMin the same + + $self->{' PARENT'}{'loca'}->update; + for ($i = 0; $i < $numg; $i++) + { + my ($g) = $self->{' PARENT'}{'loca'}{'glyphs'}[$i]; + if ($g) + { $self->{'lsb'}[$i] = $g->read->update_bbox->{'xMin'}; } + else + { $self->{'lsb'}[$i] = 0; } + } + $self->{' PARENT'}{'head'}{'flags'} |= 2; + $self; +} + + +=head2 $t->out_xml($context, $depth) + +Outputs the table in XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($addr) = ($self =~ m/\((.+)\)$/o); + my ($i); + + if ($context->{'addresses'}{$addr}) + { + $fh->printf("%s<%s id_ref='%s'/>\n", $depth, $context->{'name'}, $addr); + return $self; + } + else + { $fh->printf("%s<%s id='%s'>\n", $depth, $context->{'name'}, $addr); } + + $self->read; + + for ($i = 0; $i < $numg; $i++) + { $fh->print("$depth$context->{'indent'}\n"); } + + $fh->print("$depth{'name'}>\n"); + $self; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern.pm new file mode 100644 index 0000000..80f66d9 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern.pm @@ -0,0 +1,296 @@ +package Font::TTF::Kern; + +=head1 NAME + +Font::TTF::Kern - Kerning tables + +=head1 DESCRIPTION + +Kerning tables are held as an ordered collection of subtables each giving +incremental information regarding the kerning of various pairs of glyphs. + +The basic structure of the kerning data structure is: + + $kern = $f->{'kern'}{'tables'}[$tnum]{'kerns'}{$leftnum}{$rightnum}; + +Due to the possible complexity of some kerning tables the above information +is insufficient. Reference also needs to be made to the type of the table and +the coverage field. + +=head1 INSTANCE VARIABLES + +The instance variables for a kerning table are relatively straightforward. + +=over 4 + +=item Version + +Version number of the kerning table + +=item Num + +Number of subtables in the kerning table + +=item tables + +Array of subtables in the kerning table + +=over 4 + +Each subtable has a number of instance variables. + +=item kern + +A two level hash array containing kerning values. The indexing is left +value and then right value. In the case of type 2 tables, the indexing +is via left class and right class. It may seem using hashes is strange, +but most tables are not type 2 and this method saves empty array values. + +=item type + +Stores the table type. Only type 0 and type 2 tables are specified for +TrueType so far. + +=item coverage + +A bit field of coverage information regarding the kerning value. See the +TrueType specification for details. + +=item Version + +Contains the version number of the table. + +=item Num + +Number of kerning pairs in this type 0 table. + +=item left + +An array indexed by glyph - left_first which returns a class number for +the glyph in type 2 tables. + +=item right + +An array indexed by glyph - right_first which returns a class number for +the glyph in type 2 tables. + +=item left_first + +the glyph number of the first element in the left array for type 2 tables. + +=item right_first + +the glyph number of the first element in the right array for type 2 tables. + +=item num_left + +Number of left classes + +=item num_right + +Number of right classes + +=back + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the whole kerning table into structures + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($dat, $i, $numt, $len, $cov, $t); + + $self->SUPER::read or return $self; + + $fh->read($dat, 4); + ($self->{'Version'}, $numt) = unpack("n2", $dat); + $self->{'Num'} = $numt; + + for ($i = 0; $i < $numt; $i++) + { + $t = {}; + $fh->read($dat, 6); + ($t->{'Version'}, $len, $cov) = unpack("n3", $dat); + $t->{'coverage'} = $cov & 255; + $t->{'type'} = $cov >> 8; + $fh->read($dat, $len - 6); + if ($t->{'Version'} == 0) + { + $t->{'Num'} = unpack("n", $dat); + my (@vals) = unpack("n*", substr($dat, 8, $t->{'Num'} * 6)); + for (0 .. ($t->{'Num'} - 1)) + { + my ($f, $l, $v); + $f = shift @vals; + $l = shift @vals; + $v = shift @vals; + $v -= 65536 if ($v > 32767); + $t->{'kern'}{$f}{$l} = $v; + } + } elsif ($t->{'Version'} == 2) + { + my ($wid, $off, $numg, $maxl, $maxr, $j); + + $wid = unpack("n", $dat); + $off = unpack("n", substr($dat, 2)); + ($t->{'left_first'}, $numg) = unpack("n2", substr($dat, $off)); + $t->{'left'} = [unpack("C$numg", substr($dat, $off + 4))]; + foreach (@{$t->{'left'}}) + { + $_ /= $wid; + $maxl = $_ if ($_ > $maxl); + } + $t->{'left_max'} = $maxl; + + $off = unpack("n", substr($dat, 4)); + ($t->{'right_first'}, $numg) = unpack("n2", substr($dat, $off)); + $t->{'right'} = [unpack("C$numg", substr($dat, $off + 4))]; + foreach (@{$t->{'right'}}) + { + $_ >>= 1; + $maxr = $_ if ($_ > $maxr); + } + $t->{'right_max'} = $maxr; + + $off = unpack("n", substr($dat, 6)); + for ($j = 0; $j <= $maxl; $j++) + { + my ($k) = 0; + + map { $t->{'kern'}{$j}{$k} = $_ if $_; $k++; } + unpack("n$maxr", substr($dat, $off + $wid * $j)); + } + } + push (@{$self->{'tables'}}, $t); + } + $self; +} + + +=head2 $t->out($fh) + +Outputs the kerning tables to the given file + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i, $l, $r, $loc, $loc1, $t); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + $fh->print(pack("n2", $self->{'Version'}, $self->{'Num'})); + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $t = $self->{'tables'}[$i]; + $loc = $fh->tell(); + + $fh->print(pack("nnn", $t->{'Version'}, 0, $t->{'coverage'})); + if ($t->{'Version'} == 0) + { + my ($dat); + foreach $l (sort {$a <=> $b} keys %{$t->{'kern'}}) + { + foreach $r (sort {$a <=> $b} keys %{$t->{'kern'}{$l}}) + { $dat .= TTF_Pack("SSs", $l, $r, $t->{'kern'}{$l}{$r}); } + } + $fh->print(TTF_Pack("SSSS", Font::TTF::Utils::TTF_bininfo(length($dat) / 6, 6))); + $fh->print($dat); + } elsif ($t->{'Version'} == 2) + { + my ($arr); + + $fh->print(pack("nnnn", $t->{'right_max'} << 1, 8, ($#{$t->{'left'}} + 7) << 1, + ($#{$t->{'left'}} + $#{$t->{'right'}} + 10) << 1)); + + $fh->print(pack("nn", $t->{'left_first'}, $#{$t->{'left'}} + 1)); + foreach (@{$t->{'left'}}) + { $fh->print(pack("C", $_ * (($t->{'left_max'} + 1) << 1))); } + + $fh->print(pack("nn", $t->{'right_first'}, $#{$t->{'right'}} + 1)); + foreach (@{$t->{'right'}}) + { $fh->print(pack("C", $_ << 1)); } + + $arr = "\000\000" x (($t->{'left_max'} + 1) * ($t->{'right_max'} + 1)); + foreach $l (keys %{$t->{'kern'}}) + { + foreach $r (keys %{$t->{'kern'}{$l}}) + { substr($arr, ($l * ($t->{'left_max'} + 1) + $r) << 1, 2) + = pack("n", $t->{'kern'}{$l}{$r}); } + } + $fh->print($arr); + } + $loc1 = $fh->tell(); + $fh->seek($loc + 2, 0); + $fh->print(pack("n", $loc1 - $loc)); + $fh->seek($loc1, 0); + } + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $value) + +Handles outputting the kern hash into XML a little more tidily + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $value) = @_; + my ($fh) = $context->{'fh'}; + my ($f, $l); + + return $self->SUPER::XML_element(@_) unless ($key eq 'kern'); + $fh->print("$depth\n"); + foreach $f (sort {$a <=> $b} keys %{$value}) + { + foreach $l (sort {$a <=> $b} keys %{$value->{$f}}) + { $fh->print("$depth$context->{'indent'}\n"); } + } + $fh->print("$depth\n"); + $self; +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +Only supports kerning table types 0 & 2. + +=item * + +No real support functions to I anything with the kerning tables yet. + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/ClassArray.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/ClassArray.pm new file mode 100644 index 0000000..ddc630e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/ClassArray.pm @@ -0,0 +1,153 @@ +package Font::TTF::Kern::ClassArray; + +=head1 NAME + +Font::TTF::Kern::ClassArray - ClassArray Kern Subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +@ISA = qw(Font::TTF::Kern::Subtable); + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + + my $subtableStart = $fh->tell() - 8; + my $dat; + $fh->read($dat, 8); + my ($rowWidth, $leftClassTable, $rightClassTable, $array) = unpack("nnnn", $dat); + + $fh->seek($subtableStart + $leftClassTable, IO::File::SEEK_SET); + $fh->read($dat, 4); + my ($firstGlyph, $nGlyphs) = unpack("nn", $dat); + $fh->read($dat, $nGlyphs * 2); + my $leftClasses = []; + foreach (TTF_Unpack("S*", $dat)) { + push @{$leftClasses->[($_ - $array) / $rowWidth]}, $firstGlyph++; + } + + $fh->seek($subtableStart + $rightClassTable, IO::File::SEEK_SET); + $fh->read($dat, 4); + ($firstGlyph, $nGlyphs) = unpack("nn", $dat); + $fh->read($dat, $nGlyphs * 2); + my $rightClasses = []; + foreach (TTF_Unpack("S*", $dat)) { + push @{$rightClasses->[$_ / 2]}, $firstGlyph++; + } + + $fh->seek($subtableStart + $array, IO::File::SEEK_SET); + $fh->read($dat, $self->{'length'} - $array); + + my $offset = 0; + my $kernArray = []; + while ($offset < length($dat)) { + push @$kernArray, [ TTF_Unpack("s*", substr($dat, $offset, $rowWidth)) ]; + $offset += $rowWidth; + } + + $self->{'leftClasses'} = $leftClasses; + $self->{'rightClasses'} = $rightClasses; + $self->{'kernArray'} = $kernArray; + + $fh->seek($subtableStart + $self->{'length'}, IO::File::SEEK_SET); + + $self; +} + +=head2 $t->out_sub($fh) + +Writes the table to a file + +=cut + +sub out_sub +{ +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + +} + +sub dumpXML +{ + my ($self, $fh) = @_; + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + $fh->printf("\n"); + $self->dumpClasses($self->{'leftClasses'}, $fh); + $fh->printf("\n"); + + $fh->printf("\n"); + $self->dumpClasses($self->{'rightClasses'}, $fh); + $fh->printf("\n"); + + $fh->printf("\n"); + my $kernArray = $self->{'kernArray'}; + foreach (0 .. $#$kernArray) { + $fh->printf("\n", $_); + my $row = $kernArray->[$_]; + foreach (0 .. $#$row) { + $fh->printf("\n", $_, $row->[$_]); + } + $fh->printf("\n"); + } + $fh->printf("\n"); +} + +sub type +{ + return 'kernClassArray'; +} + + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/CompactClassArray.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/CompactClassArray.pm new file mode 100644 index 0000000..ab0304a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/CompactClassArray.pm @@ -0,0 +1,93 @@ +package Font::TTF::Kern::CompactClassArray; + +=head1 NAME + +Font::TTF::Kern::CompactClassArray - Compact Class Array kern subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; + +@ISA = qw(Font::TTF::Kern::Subtable); + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + + die "incomplete"; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file + +=cut + +sub out_sub +{ + my ($self, $fh) = @_; + + die "incomplete"; + + $self; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + die "incomplete"; +} + + +sub type +{ + return 'kernCompactClassArray'; +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/OrderedList.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/OrderedList.pm new file mode 100644 index 0000000..41cc8c1 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/OrderedList.pm @@ -0,0 +1,108 @@ +package Font::TTF::Kern::OrderedList; + +=head1 NAME + +Font::TTF::Kern::OrderedList - Ordered List Kern subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; + +@ISA = qw(Font::TTF::Kern::Subtable); + +sub new +{ + my ($class, @options) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + + my $dat; + $fh->read($dat, 8); + my ($nPairs, $searchRange, $entrySelector, $rangeShift) = unpack("nnnn", $dat); + + my $pairs = []; + foreach (1 .. $nPairs) { + $fh->read($dat, 6); + my ($left, $right, $kern) = TTF_Unpack("SSs", $dat); + push @$pairs, { 'left' => $left, 'right' => $right, 'kern' => $kern } if $kern != 0; + } + + $self->{'kernPairs'} = $pairs; + + $self; +} + +=head2 $t->out_sub($fh) + +Writes the table to a file + +=cut + +sub out_sub +{ + my ($self, $fh) = @_; + + my $pairs = $self->{'kernPairs'}; + $fh->print(pack("nnnn", TTF_bininfo(scalar @$pairs, 6))); + + foreach (sort { $a->{'left'} <=> $b->{'left'} or $a->{'right'} <=> $b->{'right'} } @$pairs) { + $fh->print(TTF_Pack("SSs", $_->{'left'}, $_->{'right'}, $_->{'kern'})); + } +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub dumpXML +{ + my ($self, $fh) = @_; + + my $postVal = $self->post()->{'VAL'}; + + $fh = 'STDOUT' unless defined $fh; + foreach (@{$self->{'kernPairs'}}) { + $fh->printf("\n", $postVal->[$_->{'left'}], $postVal->[$_->{'right'}], $_->{'kern'}); + } +} + + +sub type +{ + return 'kernOrderedList'; +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/StateTable.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/StateTable.pm new file mode 100644 index 0000000..51d650a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/StateTable.pm @@ -0,0 +1,143 @@ +package Font::TTF::Kern::StateTable; + +=head1 NAME + +Font::TTF::Kern::StateTable - State Table Kern subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Kern::Subtable; +use IO::File; + +@ISA = qw(Font::TTF::Kern::Subtable); + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my $stTableStart = $fh->tell(); + + my ($classes, $states, $entries) = AAT_read_state_table($fh, 0); + + foreach (@$entries) { + my $flags = $_->{'flags'}; + delete $_->{'flags'}; + $_->{'push'} = 1 if $flags & 0x8000; + $_->{'noAdvance'} = 1 if $flags & 0x4000; + $flags &= ~0xC000; + if ($flags != 0) { + my $kernList = []; + $fh->seek($stTableStart + $flags, IO::File::SEEK_SET); + while (1) { + $fh->read($dat, 2); + my $k = TTF_Unpack("s", $dat); + push @$kernList, ($k & ~1); + last if ($k & 1) != 0; + } + $_->{'kernList'} = $kernList; + } + } + + $self->{'classes'} = $classes; + $self->{'states'} = $states; + $self->{'entries'} = $entries; + + $fh->seek($stTableStart - 8 + $self->{'length'}, IO::File::SEEK_SET); + + $self; +} + +=head2 $t->out_sub($fh) + +Writes the table to a file + +=cut + +sub out_sub +{ +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ +} + +sub dumpXML +{ + my ($self, $fh) = @_; + + $fh->printf("\n"); + $self->dumpClasses($self->{'classes'}, $fh); + $fh->printf("\n"); + + $fh->printf("\n"); + my $states = $self->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\n", $_); + my $members = $states->[$_]; + foreach (0 .. $#$members) { + my $m = $members->[$_]; + $fh->printf("{'nextState'}); + $fh->printf(" push=\"1\"") if $m->{'push'}; + $fh->printf(" noAdvance=\"1\"") if $m->{'noAdvance'}; + if (exists $m->{'kernList'}) { + $fh->printf(">"); + foreach (@{$m->{'kernList'}}) { + $fh->printf("", $_); + } + $fh->printf("\n"); + } + else { + $fh->printf("/>\n"); + } + } + $fh->printf("\n"); + } + $fh->printf("\n"); +} + +sub type +{ + return 'kernStateTable'; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/Subtable.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/Subtable.pm new file mode 100644 index 0000000..b1db410 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Kern/Subtable.pm @@ -0,0 +1,175 @@ +package Font::TTF::Kern::Subtable; + +=head1 NAME + +Font::TTF::Kern::Subtable - Kern Subtable superclass for AAT + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +require Font::TTF::Kern::OrderedList; +require Font::TTF::Kern::StateTable; +require Font::TTF::Kern::ClassArray; +require Font::TTF::Kern::CompactClassArray; + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + + bless $self, $class; +} + +sub create +{ + my ($class, $type, $coverage, $length) = @_; + + $class = ref($class) || $class; + + my $subclass; + if ($type == 0) { + $subclass = 'Font::TTF::Kern::OrderedList'; + } + elsif ($type == 1) { + $subclass = 'Font::TTF::Kern::StateTable'; + } + elsif ($type == 2) { + $subclass = 'Font::TTF::Kern::ClassArray'; + } + elsif ($type == 3) { + $subclass = 'Font::TTF::Kern::CompactClassArray'; + } + + my @options; + push @options,'vertical' if ($coverage & 0x8000) != 0; + push @options,'crossStream' if ($coverage & 0x4000) != 0; + push @options,'variation' if ($coverage & 0x2000) != 0; + + my ($subTable) = $subclass->new(@options); + + map { $subTable->{$_} = 1 } @options; + + $subTable->{'type'} = $type; + $subTable->{'length'} = $length; + + $subTable; +} + +=head2 $t->out($fh) + +Writes the table to a file + +=cut + +sub out +{ + my ($self, $fh) = @_; + + my $subtableStart = $fh->tell(); + my $type = $self->{'type'}; + my $coverage = $type; + $coverage += 0x8000 if $self->{'vertical'}; + $coverage += 0x4000 if $self->{'crossStream'}; + $coverage += 0x2000 if $self->{'variation'}; + + $fh->print(TTF_Pack("LSS", 0, $coverage, $self->{'tupleIndex'})); # placeholder for length + + $self->out_sub($fh); + + my $length = $fh->tell() - $subtableStart; + my $padBytes = (4 - ($length & 3)) & 3; + $fh->print(pack("C*", (0) x $padBytes)); + $length += $padBytes; + $fh->seek($subtableStart, IO::File::SEEK_SET); + $fh->print(pack("N", $length)); + $fh->seek($subtableStart + $length, IO::File::SEEK_SET); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub post +{ + my ($self) = @_; + + my $post = $self->{' PARENT'}{' PARENT'}{'post'}; + if (defined $post) { + $post->read; + } + else { + $post = {}; + } + + return $post; +} + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + $fh = 'STDOUT' unless defined $fh; +} + +=head2 $t->print_classes($fh) + +Prints a human-readable representation of the table + +=cut + +sub print_classes +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + my $classes = $self->{'classes'}; + foreach (0 .. $#$classes) { + my $class = $classes->[$_]; + if (defined $class) { + $fh->printf("\t\tClass %d:\t%s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$class)); + } + } +} + +sub dumpClasses +{ + my ($self, $classes, $fh) = @_; + my $post = $self->post(); + + foreach (0 .. $#$classes) { + my $c = $classes->[$_]; + if ($#$c > -1) { + $fh->printf("\n", $_); + foreach (@$c) { + $fh->printf("\n", $_, $post->{'VAL'}[$_]); + } + $fh->printf("\n"); + } + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/LTSH.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/LTSH.pm new file mode 100644 index 0000000..2b2dea6 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/LTSH.pm @@ -0,0 +1,88 @@ +package Font::TTF::LTSH; + +=head1 NAME + +Font::TTF::LTSH - Linear Threshold table + +=head1 DESCRIPTION + +Holds the linear threshold for each glyph. This is the ppem value at which a +glyph's metrics become linear. The value is set to 1 if a glyph's metrics are +always linear. + +=head1 INSTANCE VARIABLES + +=over 4 + +=item glyphs + +An array of ppem values. One value per glyph + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($numg, $dat); + + $self->SUPER::read or return $self; + + $fh->read($dat, 4); + ($self->{'Version'}, $numg) = unpack("nn", $dat); + $self->{'Num'} = $numg; + + $fh->read($dat, $numg); + $self->{'glyphs'} = [unpack("C$numg", $dat)]; + $self; +} + + +=head2 $t->out($fh) + +Outputs the LTSH to the given fh. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + $fh->print(pack("nn", 0, $numg)); + $fh->print(pack("C$numg", @{$self->{'glyphs'}})); + $self; +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Loca.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Loca.pm new file mode 100644 index 0000000..ca641bb --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Loca.pm @@ -0,0 +1,183 @@ +package Font::TTF::Loca; + +=head1 NAME + +Font::TTF::Loca - the Locations table, which is intimately tied to the glyf table + +=head1 DESCRIPTION + +The location table holds the directory of locations of each glyph within the +glyf table. Due to this relationship and the unimportance of the actual locations +when it comes to holding glyphs in memory, reading the location table results +in the creation of glyph objects for each glyph and stores them here. +So if you are looking for glyphs, don't look in the C table, look here +instead. + +Things get complicated if you try to change the glyph list within the one table. +The recommendation is to create another clean location object to replace this +table in the font, ensuring that the old table is read first and to transfer +or copy glyphs across from the read table to the new table. + +=head1 INSTANCE VARIABLES + +The instance variables do not start with a space + +=over 4 + +=item glyphs + +An array of glyph objects for each glyph. + +=item glyphtype + +A string containing the class name to create for each new glyph. If empty, +defaults to L. + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +@ISA = qw(Font::TTF::Table); + +require Font::TTF::Glyph; + + +=head2 $t->new + +Creates a new location table making sure it has a glyphs array + +=cut + +sub new +{ + my ($class) = shift; + my ($res) = $class->SUPER::new(@_); + $res->{'glyphs'} = []; + $res; +} + +=head2 $t->read + +Reads the location table creating glyph objects (L) for each glyph +allowing their later reading. + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($locFmt) = $self->{' PARENT'}{'head'}{'indexToLocFormat'}; + my ($numGlyphs) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($glyfLoc) = $self->{' PARENT'}{'glyf'}{' OFFSET'}; + my ($dat, $last, $i, $loc); + + $self->SUPER::read or return $self; + $fh->read($dat, $locFmt ? 4 : 2); + $last = unpack($locFmt ? "N" : "n", $dat); + for ($i = 0; $i < $numGlyphs; $i++) + { + $fh->read($dat, $locFmt ? 4 : 2); + $loc = unpack($locFmt ? "N" : "n", $dat); + $self->{'glyphs'}[$i] = ($self->{'glyphtype'} || "Font::TTF::Glyph")->new( + LOC => $last << ($locFmt ? 0 : 1), + OUTLOC => $last << ($locFmt ? 0 : 1), + PARENT => $self->{' PARENT'}, + INFILE => $fh, + BASE => $glyfLoc, + OUTLEN => ($loc - $last) << ($locFmt ? 0 : 1), + LEN => ($loc - $last) << ($locFmt ? 0 : 1)) if ($loc != $last); + $last = $loc; + } + $self; +} + + +=head2 $t->out($fh) + +Writes the location table out to $fh. Notice that not having read the location +table implies that the glyf table has not been read either, so the numbers in +the location table are still valid. Let's hope that C and +C haven't changed otherwise we are in big trouble. + +The function uses the OUTLOC location in the glyph calculated when the glyf +table was attempted to be output. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($locFmt) = $self->{' PARENT'}{'head'}{'indexToLocFormat'}; + my ($numGlyphs) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($count, $i, $offset, $g); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + $count = 0; + for ($i = 0; $i < $numGlyphs; $i++) + { + $g = ($self->{'glyphs'}[$i]) || ""; + unless ($g) + { + $count++; + next; + } else + { + if ($locFmt) + { $fh->print(pack("N", $g->{' OUTLOC'}) x ($count + 1)); } + else + { $fh->print(pack("n", $g->{' OUTLOC'} >> 1) x ($count + 1)); } + $count = 0; + $offset = $g->{' OUTLOC'} + $g->{' OUTLEN'}; + } + } + $fh->print(pack($locFmt ? "N" : "n", ($locFmt ? $offset: $offset >> 1)) x ($count + 1)); +} + + +=head2 $t->out_xml($context, $depth) + +No need to output a loca table, this is dynamically generated + +=cut + +sub out_xml +{ return $_[0]; } + + +=head2 $t->glyphs_do(&func) + +Calls func for each glyph in this location table in numerical order: + + &func($glyph, $glyph_num) + +=cut + +sub glyphs_do +{ + my ($self, $func) = @_; + my ($i); + + for ($i = 0; $i <= $#{$self->{'glyphs'}}; $i++) + { &$func($self->{'glyphs'}[$i], $i) if defined $self->{'glyphs'}[$i]; } + $self; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Manual.pod b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Manual.pod new file mode 100644 index 0000000..c6d2938 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Manual.pod @@ -0,0 +1,214 @@ + +=head1 NAME + +Font::TTF::Manual - Information regarding the whole module set + +=head1 INTRODUCTION + +This document looks at the whole issue of how the various modules in the +TrueType Font work together. As such it is partly information on this font +system and partly information on TrueType fonts in general. + +Due to the inter-relation between so many tables in a TrueType font, different +tables will make expectations as to which other tables exist. At the very least +a font should consist of a C table and a C table. The system has +been designed around the expectation that the necessary tables for font +rendering in the Windows environment exist. But inter table dependencies have +been kept to what are considered necessary. + +This module set is not meant as a simple to use, mindless, font editing suite, +but as a low-level, get your hands dirty, know what you are doing, set of +classes for those who understand the intricacies (and there are many) of +TrueType fonts. To this end, if you get something wrong in the data structures, +etc. then this module set won't tell you and will happily create fonts which +don't work. + +At the time of writing, not every TrueType table in existence has been +implemented! Only the core basic tables of TrueType 1.0 (i.e. no embedded bitmap +tables, no postscript type tables, no OpenType tables and no GX tables) have +been implemented. If you want to help by implementing another table or two, then +please go ahead and send me your code. For a full list of tables, see +L. + + +=head2 Design Principles + +PERL is not C++. C++ encourages methods to be written for changing and reading +each instance variable in a class. If we did this in this PERL program the +results would be rather large and slow. Instead, since most access will be read +access, we expose as much of the inner storage of an object to user access +directly via hash lookup. The advantage this gives are great. For example, by +following an instance variable chain, looking up the C parameter for a +particular glyph becomes: + + $f->{'loca'}{'glyphs'}[$glyph]{'yMax'} + +Or, if we are feeling very lazy and don't mind waiting: + + $f->{'loca'}{'glyphs'}[$f->{'cmap'}->ms_lookup(0x41)]{'yMax'} + +The disadvantage of this method is that it behoves module users to behave +themselves. Thus it does not hold your hand and ensure that if you make a change +to a table, that the table is marked as I, or that other tables are +updated accordingly. + +It is up to the application developer to understand the implications of the +changes they make to a font, and to take the necessary action to ensure that the +data they get out is what they want. Thus, you could go and change the C +value on a glyph and output a new font with this change, but it is up to you to +ensure that the font's bounding box details in the C table are correct, +and even that your changing C is well motivated. + +To help with using the system, each module (or table) will not only describe the +methods it supports, which are relatively few, but also the instance variables +it supports, which are many. Most of the variables directly reflect table +attributes as specified in the OpenType specification, available from Microsoft +(L), Adobe and Apple. A list of the names +used is also given in each module, but not necessarily with any further +description. After all, this code is not a TrueType manual as well! + + +=head2 Conventions + +There are various conventions used in this system. + +Firstly we consider the documentation conventions regarding instance variables. +Each instance variable is marked indicating whether it is a B<(P)>rivate +variable which users of the module are not expected to read and certainly not +write to or a B<(R)>ead only variable which users may well want to read but not +write to. + + +=head1 METHODS + +This section examines various methods and how the various modules work with +these methods. + + +=head2 read and read_dat + +Before the data structures for a table can be accessed, they need to be filled +in from somewhere. The usual way to do this is to read an existing TrueType +font. This may be achieved by: + + $f = Font::TTF::Font->open($filename) || die "Unable to read $filename"; + +This will open an existing font and read its directory header. Notice that at +this point, none of the tables in the font have been read. (Actually, the +C and C tables are read at this point too since they contain the +commonly required parameters of): + + $f->{'head'}{'unitsPerEm'} + $f->{'maxp'}{'numGlyphs'} + +In order to be able to access information from a table, it is first necessary to +C it. Consider trying to find the advance width of a space character +(U+0020). The following code should do it: + + $f = Font::TTF::Font->open($ARGV[0]); + $snum = $f->{'cmap'}->ms_lookup(0x0020); + $sadv = $f->{'hmtx'}{'advance'}[$snum]; + print $sadv; + +This would result in the value zero being printed, which is far from correct. +But why? The first line would correctly read the font directory. The second line +would, incidently, correctly locate the space character in the Windows cmap +(assuming a non symbol encoded font). The third line would not succeed in its +task since the C table has not been filled in from the font file. To +achieve what we want we would first need to cause it to be read: + + $f->{'hmtx'}->read; + $sadv = $f->{'hmtx'}{'advance'}[$snum]; + +Or for those who are too lazy to write multiple lines, C returns the +object it reads. Thus we could write: + + $sadv = $f->{'hmtx'}->read->{'advance'}[$snum]; + +Why, if we always have to read tables before accessing information from them, +did we not have to do this for the C table? The answer lies in the method +call. It senses that the table hasn't been read and reads it for us. This will +generally happen with all method calls, it is only when we do direct data access +that we have to take the responsibility to read the table first. + +Reading a table does not necessarily result in all the data being placed into +internal data structures. In the case of a simple table C is sufficient. +In fact, the normal case is that C reads the data from the file into +an instance variable called C<' dat'> (including the space) and not into the +data structures. + +This is true except for the C class which represents a single glyph. Here +the process is reversed. Reading a C reads the data for the glyph into +the C<' dat'> instance variable and sets various header attributes for the glyph +(C, C, etc.). The data is converted out of the variable into +data structures via the C method. + +The aim, therefore, is that C should do the natural thing (read into data +structures for those tables and elements for which it is helpful -- all except +C at present) and C should do the unnatural thing: read just +the binary data for normal tables and convert binary data to data structures for +Cs. + +In summary, therefore, use C unless you want to hack around with the +internals of glyphs in which case see L for more details. + + +=head2 update + +The aim of this method is to allow the various data elements in a C font +to update themselves. All tables know how to update themselves. All tables also +contain information which cannot be I but is new knowledge in the font. +As a result, certain tables do nothing when they are updated. We can, therefore, +build an update hierarchy of tables, with the independent tables at the bottom +and C at the top: + + +--loca + | + glyf--+--maxp + | + +---+--head + | + hmtx------+--hhea + + cmap-----OS/2 + + name-- + + post-- + +There is an important universal dependency which it is up to the user to +keep up to date. This is C which is used to iterate over all +the glyphs. Note that the glyphs themselves are not held in the C table +but in the C table, so adding glyphs, etc. automatically involves keeping +the C table up to date. + +=head2 Creating fonts + +Suppose we were creating a font from scratch. How much information do we need +to supply and how much will C do for us? + +The following information is required: + + $f->{'loca'}{'glyphs'} + $f->{'head'}{'upem'} + $f->{'maxp'}{'numGlyphs'} (doesn't come from $f->{'loca'}{'glyphs'}) + $f->{'hmtx'}{'advance'} + $f->{'post'}['format'} + $f->{'post'}{'VAL'} + $f->{'cmap'} + $f->{'name'} + +Pretty much everything else is calculated for you. Details of what is needed +for a glyph may be found in L. Once we have all the +information we need (and there is lots more that you could add) then we simply + + $f->dirty; # mark all tables dirty + $f->update; # update the font + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Maxp.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Maxp.pm new file mode 100644 index 0000000..e2d81b9 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Maxp.pm @@ -0,0 +1,177 @@ +package Font::TTF::Maxp; + +=head1 NAME + +Font::TTF::Maxp - Maximum Profile table in a font + +=head1 DESCRIPTION + +A collection of useful instance variables following the TTF standard. Probably +the most used being C. Note that this particular value is +foundational and should be kept up to date by the application, it is not updated +by C. + +Handles table versions 0.5, 1.0 + +=head1 INSTANCE VARIABLES + +No others beyond those specified in the standard: + + numGlyphs + maxPoints + maxContours + maxCompositePoints + maxCompositeContours + maxZones + maxTwilightPoints + maxStorage + maxFunctionDefs + maxInstructionDefs + maxStackElements + maxSizeOfInstructions + maxComponentElements + maxComponentDepth + + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'numGlyphs' => 'S', + 'maxPoints' => 'S', + 'maxContours' => 'S', + 'maxCompositePoints' => 'S', + 'maxCompositeContours' => 'S', + 'maxZones' => 'S', + 'maxTwilightPoints' => 'S', + 'maxStorage' => 'S', + 'maxFunctionDefs' => 'S', + 'maxInstructionDefs' => 'S', + 'maxStackElements' => 'S', + 'maxSizeOfInstructions' => 'S', + 'maxComponentElements' => 'S', + 'maxComponentDepth' => 'S'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read or return $self; + + init unless defined $fields{'numGlyphs'}; # any key would do + $self->{' INFILE'}->read($dat, 4); + $self->{'version'} = TTF_Unpack("v", $dat); + + if ($self->{'version'} == 0.5) + { + $self->{' INFILE'}->read($dat, 2); + $self->{'numGlyphs'} = unpack("n", $dat); + } else + { + $self->{' INFILE'}->read($dat, 28); + TTF_Read_Fields($self, $dat, \%fields); + } + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + $fh->print(TTF_Pack("v", $self->{'version'})); + + if ($self->{'version'} == 0.5) + { $fh->print(pack("n", $self->{'numGlyphs'})); } + else + { $fh->print(TTF_Out_Fields($self, \%fields, 28)); } + $self; +} + + +=head2 $t->update + +Calculates all the maximum values for a font based on the glyphs in the font. +Only those fields which require hinting code interpretation are ignored and +left as they were read. + +=cut + +sub update +{ + my ($self) = @_; + my ($i, $num, @n, @m, $j); + my (@name) = qw(maxPoints maxContours maxCompositePoints maxCompositeContours + maxSizeOfInstructions maxComponentElements maxComponentDepth); + + return undef unless ($self->SUPER::update); + return undef if ($self->{'version'} == 0.5); # only got numGlyphs + return undef unless (defined $self->{' PARENT'}{'loca'}); + $self->{' PARENT'}{'loca'}->update; + $num = $self->{'numGlyphs'}; + + for ($i = 0; $i < $num; $i++) + { + my ($g) = $self->{' PARENT'}{'loca'}{'glyphs'}[$i] || next; + + @n = $g->maxInfo; + + for ($j = 0; $j <= $#n; $j++) + { $m[$j] = $n[$j] if $n[$j] > $m[$j]; } + } + + foreach ('prep', 'fpgm') + { $m[4] = length($self->{' PARENT'}{$_}{' dat'}) + if (defined $self->{' PARENT'}{$_} + && length($self->{' PARENT'}{$_}{' dat'}) > $m[4]); + } + + for ($j = 0; $j <= $#name; $j++) + { $self->{$name[$j]} = $m[$j]; } + $self; +} +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort.pm new file mode 100644 index 0000000..6e954cb --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort.pm @@ -0,0 +1,108 @@ +package Font::TTF::Mort; + +=head1 NAME + +Font::TTF::Mort - Glyph Metamorphosis table in a font + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Mort::Chain; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $fh, $numChains); + + $self->SUPER::read or return $self; + + $fh = $self->{' INFILE'}; + + $fh->read($dat, 8); + ($self->{'version'}, $numChains) = TTF_Unpack("vL", $dat); + + my $chains = []; + foreach (1 .. $numChains) { + my $chain = new Font::TTF::Mort::Chain->new; + $chain->read($fh); + $chain->{' PARENT'} = $self; + push @$chains, $chain; + } + + $self->{'chains'} = $chains; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + my $chains = $self->{'chains'}; + $fh->print(TTF_Pack("vL", $self->{'version'}, scalar @$chains)); + + foreach (@$chains) { + $_->out($fh); + } +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + $self->read unless $self->{' read'}; + my $feat = $self->{' PARENT'}->{'feat'}; + $feat->read; + my $post = $self->{' PARENT'}->{'post'}; + $post->read; + + $fh = 'STDOUT' unless defined $fh; + + $fh->printf("version %f\n", $self->{'version'}); + + my $chains = $self->{'chains'}; + foreach (@$chains) { + $_->print($fh); + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Chain.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Chain.pm new file mode 100644 index 0000000..b8e7b93 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Chain.pm @@ -0,0 +1,196 @@ +package Font::TTF::Mort::Chain; + +=head1 NAME + +Font::TTF::Mort::Chain - Chain Mort subtable for AAT + +=cut + +use strict; +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Mort::Subtable; +use IO::File; + +=head2 $t->new + +=cut + +sub new +{ + my ($class, %parms) = @_; + my ($self) = {}; + my ($p); + + $class = ref($class) || $class; + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + bless $self, $class; +} + +=head2 $t->read($fh) + +Reads the chain into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my $chainStart = $fh->tell(); + $fh->read($dat, 12); + my ($defaultFlags, $chainLength, $nFeatureEntries, $nSubtables) = TTF_Unpack("LLSS", $dat); + + my $featureEntries = []; + foreach (1 .. $nFeatureEntries) { + $fh->read($dat, 12); + my ($featureType, $featureSetting, $enableFlags, $disableFlags) = TTF_Unpack("SSLL", $dat); + push @$featureEntries, { + 'type' => $featureType, + 'setting' => $featureSetting, + 'enable' => $enableFlags, + 'disable' => $disableFlags + }; + } + + my $subtables = []; + foreach (1 .. $nSubtables) { + my $subtableStart = $fh->tell(); + + $fh->read($dat, 8); + my ($length, $coverage, $subFeatureFlags) = TTF_Unpack("SSL", $dat); + my $type = $coverage & 0x0007; + + my $subtable = Font::TTF::Mort::Subtable->create($type, $coverage, $subFeatureFlags, $length); + $subtable->read($fh); + $subtable->{' PARENT'} = $self; + + push @$subtables, $subtable; + $fh->seek($subtableStart + $length, IO::File::SEEK_SET); + } + + $self->{'defaultFlags'} = $defaultFlags; + $self->{'featureEntries'} = $featureEntries; + $self->{'subtables'} = $subtables; + + $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET); + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + + my $chainStart = $fh->tell(); + my ($featureEntries, $subtables) = ($_->{'featureEntries'}, $_->{'subtables'}); + $fh->print(TTF_Pack("LLSS", $_->{'defaultFlags'}, 0, scalar @$featureEntries, scalar @$subtables)); # placeholder for length + + foreach (@$featureEntries) { + $fh->print(TTF_Pack("SSLL", $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'})); + } + + foreach (@$subtables) { + $_->out($fh); + } + + my $chainLength = $fh->tell() - $chainStart; + $fh->seek($chainStart + 4, IO::File::SEEK_SET); + $fh->print(pack("N", $chainLength)); + $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the chain + +=cut + +sub feat +{ + my ($self) = @_; + + my $feat = $self->{' PARENT'}{' PARENT'}{'feat'}; + if (defined $feat) { + $feat->read; + } + else { + $feat = {}; + } + + return $feat; +} + +sub print +{ + my ($self, $fh) = @_; + + $fh->printf("version %f\n", $self->{'version'}); + + my $defaultFlags = $self->{'defaultFlags'}; + $fh->printf("chain: defaultFlags = %08x\n", $defaultFlags); + + my $feat = $self->feat(); + my $featureEntries = $self->{'featureEntries'}; + foreach (@$featureEntries) { + $fh->printf("\tfeature %d, setting %d : enableFlags = %08x, disableFlags = %08x # '%s: %s'\n", + $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'}, + $feat->settingName($_->{'type'}, $_->{'setting'})); + } + + my $subtables = $self->{'subtables'}; + foreach (@$subtables) { + my $type = $_->{'type'}; + my $subFeatureFlags = $_->{'subFeatureFlags'}; + $fh->printf("\n\t%s table, %s, %s, subFeatureFlags = %08x # %s (%s)\n", + subtable_type_($type), $_->{'direction'}, $_->{'orientation'}, $subFeatureFlags, + "Default " . ((($subFeatureFlags & $defaultFlags) != 0) ? "On" : "Off"), + join(", ", + map { + join(": ", $feat->settingName($_->{'type'}, $_->{'setting'}) ) + } grep { ($_->{'enable'} & $subFeatureFlags) != 0 } @$featureEntries + ) ); + + $_->print($fh); + } +} + +sub subtable_type_ +{ + my ($val) = @_; + my ($res); + + my @types = ( + 'Rearrangement', + 'Contextual', + 'Ligature', + undef, + 'Non-contextual', + 'Insertion', + ); + $res = $types[$val] or ('Undefined (' . $val . ')'); + + $res; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Contextual.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Contextual.pm new file mode 100644 index 0000000..fecc519 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Contextual.pm @@ -0,0 +1,157 @@ +package Font::TTF::Mort::Contextual; + +=head1 NAME + +Font::TTF::Mort::Contextual - Contextual Mort subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Mort::Subtable; +use IO::File; + +@ISA = qw(Font::TTF::AAT::Mort::Subtable); + +sub new +{ + my ($class, $direction, $orientation, $subFeatureFlags) = @_; + my ($self) = { + 'direction' => $direction, + 'orientation' => $orientation, + 'subFeatureFlags' => $subFeatureFlags + }; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 2); + + $fh->seek($stateTableStart, IO::File::SEEK_SET); + $fh->read($dat, 10); + my ($stateSize, $classTable, $stateArray, $entryTable, $mappingTables) = unpack("nnnnn", $dat); + my $limits = [$classTable, $stateArray, $entryTable, $mappingTables, $self->{'length'} - 8]; + + foreach (@$entries) { + my $actions = $_->{'actions'}; + foreach (@$actions) { + $_ = $_ ? $_ - ($mappingTables / 2) : undef; + } + } + + $self->{'classes'} = $classes; + $self->{'states'} = $states; + $self->{'mappings'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $mappingTables, $limits))]; + + $self; +} + +=head2 $t->pack_sub() + +=cut + +sub pack_sub +{ + my ($self) = @_; + + my ($dat) = pack("nnnnn", (0) x 5); # placeholders for stateSize, classTable, stateArray, entryTable, mappingTables + + my $classTable = length($dat); + my $classes = $self->{'classes'}; + $dat .= AAT_pack_classes($classes); + + my $stateArray = length($dat); + my $states = $self->{'states'}; + my ($dat1, $stateSize, $entries) = AAT_pack_states($classes, $stateArray, $states, + sub { + my $actions = $_->{'actions'}; + ( $_->{'flags'}, @$actions ) + } + ); + $dat .= $dat1; + + my $entryTable = length($dat); + my $offset = ($entryTable + 8 * @$entries) / 2; + foreach (@$entries) { + my ($nextState, $flags, @parts) = split /,/; + $dat .= pack("nnnn", $nextState, $flags, map { $_ eq "" ? 0 : $_ + $offset } @parts); + } + + my $mappingTables = length($dat); + my $mappings = $self->{'mappings'}; + $dat .= pack("n*", @$mappings); + + $dat1 = pack("nnnnn", $stateSize, $classTable, $stateArray, $entryTable, $mappingTables); + substr($dat, 0, length($dat1)) = $dat1; + + return $dat; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + $self->print_classes($fh); + + $fh->print("\n"); + my $states = $self->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + my $actions = $_->{'actions'}; + $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $mappings = $self->{'mappings'}; + foreach (0 .. $#$mappings) { + $fh->printf("\t\tMapping %d: %d [%s]\n", $_, $mappings->[$_], $post->{'VAL'}[$mappings->[$_]]); + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Insertion.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Insertion.pm new file mode 100644 index 0000000..d10018b --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Insertion.pm @@ -0,0 +1,179 @@ +package Font::TTF::Mort::Insertion; + +=head1 NAME + +Font::TTF::Mort::Insertion - Insertion Mort subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +@ISA = qw(Font::TTF::Mort::Subtable); + +sub new +{ + my ($class, $direction, $orientation, $subFeatureFlags) = @_; + my ($self) = { + 'direction' => $direction, + 'orientation' => $orientation, + 'subFeatureFlags' => $subFeatureFlags + }; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my $subtableStart = $fh->tell(); + + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 2); + + my %insertListHash; + my $insertLists; + foreach (@$entries) { + my $flags = $_->{'flags'}; + my @insertCount = (($flags & 0x03e0) >> 5, ($flags & 0x001f)); + my $actions = $_->{'actions'}; + foreach (0 .. 1) { + if ($insertCount[$_] > 0) { + $fh->seek($stateTableStart + $actions->[$_], IO::File::SEEK_SET); + $fh->read($dat, $insertCount[$_] * 2); + if (not defined $insertListHash{$dat}) { + push @$insertLists, [unpack("n*", $dat)]; + $insertListHash{$dat} = $#$insertLists; + } + $actions->[$_] = $insertListHash{$dat}; + } + else { + $actions->[$_] = undef; + } + } + } + + $self->{'classes'} = $classes; + $self->{'states'} = $states; + $self->{'insertLists'} = $insertLists; + + $self; +} + +=head2 $t->pack_sub() + +=cut + +sub pack_sub +{ + my ($self) = @_; + + my ($dat) = pack("nnnn", (0) x 4); + + my $classTable = length($dat); + my $classes = $self->{'classes'}; + $dat .= AAT_pack_classes($classes); + + my $stateArray = length($dat); + my $states = $self->{'states'}; + my ($dat1, $stateSize, $entries) = AAT_pack_states($classes, $stateArray, $states, + sub { + my $actions = $_->{'actions'}; + ( $_->{'flags'}, @$actions ) + } + ); + $dat .= $dat1; + + my $entryTable = length($dat); + my $offset = ($entryTable + 8 * @$entries); + my @insListOffsets; + my $insertLists = $self->{'insertLists'}; + foreach (@$insertLists) { + push @insListOffsets, $offset; + $offset += 2 * scalar @$_; + } + foreach (@$entries) { + my ($nextState, $flags, @lists) = split /,/; + $flags &= ~0x03ff; + $flags |= (scalar @{$insertLists->[$lists[0]]}) << 5 if $lists[0] ne ''; + $flags |= (scalar @{$insertLists->[$lists[1]]}) if $lists[1] ne ''; + $dat .= pack("nnnn", $nextState, $flags, + map { $_ eq '' ? 0 : $insListOffsets[$_] } @lists); + } + + foreach (@$insertLists) { + $dat .= pack("n*", @$_); + } + + $dat1 = pack("nnnn", $stateSize, $classTable, $stateArray, $entryTable); + substr($dat, 0, length($dat1)) = $dat1; + + return $dat; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + $self->print_classes($fh); + + $fh->print("\n"); + my $states = $self->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + my $actions = $_->{'actions'}; + $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $insertLists = $self->{'insertLists'}; + foreach (0 .. $#$insertLists) { + my $insertList = $insertLists->[$_]; + $fh->printf("\t\tList %d: %s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$insertList)); + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Ligature.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Ligature.pm new file mode 100644 index 0000000..c5cc912 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Ligature.pm @@ -0,0 +1,246 @@ +package Font::TTF::Mort::Ligature; + +=head1 NAME + +Font::TTF::Mort::Ligature - Ligature Mort subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +@ISA = qw(Font::TTF::Mort::Subtable); + +sub new +{ + my ($class, $direction, $orientation, $subFeatureFlags) = @_; + my ($self) = { + 'direction' => $direction, + 'orientation' => $orientation, + 'subFeatureFlags' => $subFeatureFlags + }; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 0); + + $fh->seek($stateTableStart, IO::File::SEEK_SET); + $fh->read($dat, 14); + my ($stateSize, $classTable, $stateArray, $entryTable, + $ligActionTable, $componentTable, $ligatureTable) = unpack("nnnnnnn", $dat); + my $limits = [$classTable, $stateArray, $entryTable, $ligActionTable, $componentTable, $ligatureTable, $self->{'length'} - 8]; + + my %actions; + my $actionLists; + foreach (@$entries) { + my $offset = $_->{'flags'} & 0x3fff; + $_->{'flags'} &= ~0x3fff; + if ($offset != 0) { + if (not defined $actions{$offset}) { + $fh->seek($stateTableStart + $offset, IO::File::SEEK_SET); + my $actionList; + while (1) { + $fh->read($dat, 4); + my $action = unpack("N", $dat); + my ($last, $store, $component) = (($action & 0x80000000) != 0, ($action & 0xC0000000) != 0, ($action & 0x3fffffff)); + $component -= 0x40000000 if $component > 0x1fffffff; + $component -= $componentTable / 2; + push @$actionList, { 'store' => $store, 'component' => $component }; + last if $last; + } + push @$actionLists, $actionList; + $actions{$offset} = $#$actionLists; + } + $_->{'actions'} = $actions{$offset}; + } + } + + $self->{'componentTable'} = $componentTable; + my $components = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $componentTable, $limits))]; + foreach (@$components) { + $_ = ($_ - $ligatureTable) . " +" if $_ >= $ligatureTable; + } + $self->{'components'} = $components; + + $self->{'ligatureTable'} = $ligatureTable; + $self->{'ligatures'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $ligatureTable, $limits))]; + + $self->{'classes'} = $classes; + $self->{'states'} = $states; + $self->{'actionLists'} = $actionLists; + + $self; +} + +=head2 $t->pack_sub($fh) + +=cut + +sub pack_sub +{ + my ($self) = @_; + my ($dat); + + $dat .= pack("nnnnnnn", (0) x 7); # placeholders for stateSize, classTable, stateArray, entryTable, actionLists, components, ligatures + + my $classTable = length($dat); + my $classes = $self->{'classes'}; + $dat .= AAT_pack_classes($classes); + + my $stateArray = length($dat); + my $states = $self->{'states'}; + + my ($dat1, $stateSize, $entries) = AAT_pack_states($classes, $stateArray, $states, + sub { + ( $_->{'flags'} & 0xc000, $_->{'actions'} ) + } + ); + $dat .= $dat1; + + my $actionLists = $self->{'actionLists'}; + my %actionListOffset; + my $actionListDataLength = 0; + my @actionListEntries; + foreach (0 .. $#$entries) { + my ($nextState, $flags, $offset) = split(/,/, $entries->[$_]); + if ($offset eq "") { + $offset = undef; + } + else { + if (defined $actionListOffset{$offset}) { + $offset = $actionListOffset{$offset}; + } + else { + $actionListOffset{$offset} = $actionListDataLength; + my $list = $actionLists->[$offset]; + $actionListDataLength += 4 * @$list; + push @actionListEntries, $list; + $offset = $actionListOffset{$offset}; + } + } + $entries->[$_] = [ $nextState, $flags, $offset ]; + } + my $entryTable = length($dat); + my $ligActionLists = ($entryTable + @$entries * 4 + 3) & ~3; + foreach (@$entries) { + $_->[2] += $ligActionLists if defined $_->[2]; + $dat .= pack("nn", $_->[0], $_->[1] + $_->[2]); + } + $dat .= pack("C*", (0) x ($ligActionLists - $entryTable - @$entries * 4)); + + die "internal error" unless length($dat) == $ligActionLists; + + my $componentTable = length($dat) + $actionListDataLength; + my $actionList; + foreach $actionList (@actionListEntries) { + foreach (0 .. $#$actionList) { + my $action = $actionList->[$_]; + my $val = $action->{'component'} + $componentTable / 2; + $val += 0x40000000 if $val < 0; + $val &= 0x3fffffff; + $val |= 0x40000000 if $action->{'store'}; + $val |= 0x80000000 if $_ == $#$actionList; + $dat .= pack("N", $val); + } + } + + die "internal error" unless length($dat) == $componentTable; + + my $components = $self->{'components'}; + my $ligatureTable = $componentTable + @$components * 2; + $dat .= pack("n*", map { (index($_, '+') >= 0 ? $ligatureTable : 0) + $_ } @$components); + + my $ligatures = $self->{'ligatures'}; + $dat .= pack("n*", @$ligatures); + + $dat1 = pack("nnnnnnn", $stateSize, $classTable, $stateArray, $entryTable, $ligActionLists, $componentTable, $ligatureTable); + substr($dat, 0, length($dat1)) = $dat1; + + return $dat; +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + $self->print_classes($fh); + + $fh->print("\n"); + my $states = $self->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, defined $_->{'actions'} ? $_->{'actions'} : "="); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $actionLists = $self->{'actionLists'}; + foreach (0 .. $#$actionLists) { + $fh->printf("\t\tList %d:\t", $_); + my $actionList = $actionLists->[$_]; + $fh->printf("%s\n", join(", ", map { ($_->{'component'} . ($_->{'store'} ? "*" : "") ) } @$actionList)); + } + + my $ligatureTable = $self->{'ligatureTable'}; + + $fh->print("\n"); + my $components = $self->{'components'}; + foreach (0 .. $#$components) { + $fh->printf("\t\tComponent %d: %s\n", $_, $components->[$_]); + } + + $fh->print("\n"); + my $ligatures = $self->{'ligatures'}; + foreach (0 .. $#$ligatures) { + $fh->printf("\t\tLigature %d: %d [%s]\n", $_, $ligatures->[$_], $post->{'VAL'}[$ligatures->[$_]]); + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Noncontextual.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Noncontextual.pm new file mode 100644 index 0000000..730982a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Noncontextual.pm @@ -0,0 +1,95 @@ +package Font::TTF::Mort::Noncontextual; + +=head1 NAME + +Font::TTF::Mort::Noncontextual - Noncontextual Mort subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; + +@ISA = qw(Font::TTF::Mort::Subtable); + +sub new +{ + my ($class, $direction, $orientation, $subFeatureFlags) = @_; + my ($self) = { + 'direction' => $direction, + 'orientation' => $orientation, + 'subFeatureFlags' => $subFeatureFlags + }; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + my ($dat); + + my ($format, $lookup) = AAT_read_lookup($fh, 2, $self->{'length'} - 8, undef); + $self->{'format'} = $format; + $self->{'lookup'} = $lookup; + + $self; +} + +=head2 $t->pack_sub($fh) + +=cut + +sub pack_sub +{ + my ($self) = @_; + + return AAT_pack_lookup($self->{'format'}, $self->{'lookup'}, 2, undef); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + my $lookup = $self->{'lookup'}; + $fh->printf("\t\tLookup format %d\n", $self->{'format'}); + if (defined $lookup) { + foreach (sort { $a <=> $b } keys %$lookup) { + $fh->printf("\t\t\t%d [%s] -> %d [%s])\n", $_, $post->{'VAL'}[$_], $lookup->{$_}, $post->{'VAL'}[$lookup->{$_}]); + } + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Rearrangement.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Rearrangement.pm new file mode 100644 index 0000000..126144f --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Rearrangement.pm @@ -0,0 +1,107 @@ +package Font::TTF::Mort::Rearrangement; + +=head1 NAME + +Font::TTF::Mort::Rearrangement - Rearrangement Mort subtable for AAT + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; + +@ISA = qw(Font::TTF::Mort::Subtable); + +sub new +{ + my ($class, $direction, $orientation, $subFeatureFlags) = @_; + my ($self) = { + 'direction' => $direction, + 'orientation' => $orientation, + 'subFeatureFlags' => $subFeatureFlags + }; + + $class = ref($class) || $class; + bless $self, $class; +} + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self, $fh) = @_; + + my ($classes, $states) = AAT_read_state_table($fh, 0); + $self->{'classes'} = $classes; + $self->{'states'} = $states; + + $self; +} + +=head2 $t->pack_sub() + +=cut + +sub pack_sub +{ + my ($self) = @_; + + return AAT_pack_state_table($self->{'classes'}, $self->{'states'}, 0); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + my $post = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + $self->print_classes($fh); + + $fh->print("\n"); + my $states = $self->{'states'}; + my @verbs = ( "0", "Ax->xA", "xD->Dx", "AxD->DxA", + "ABx->xAB", "ABx->xBA", "xCD->CDx", "xCD->DCx", + "AxCD->CDxA", "AxCD->DCxA", "ABxD->DxAB", "ABxD->DxBA", + "ABxCD->CDxAB", "ABxCD->CDxBA", "ABxCD->DCxAB", "ABxCD->DCxBA"); + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "<" if ($_->{'flags'} & 0x8000); + $flags .= ">" if ($_->{'flags'} & 0x2000); + $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, $verbs[($_->{'flags'} & 0x000f)]); + } + $fh->print("\n"); + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Subtable.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Subtable.pm new file mode 100644 index 0000000..4dd26e0 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Mort/Subtable.pm @@ -0,0 +1,200 @@ +package Font::TTF::Mort::Subtable; + +=head1 NAME + +Font::TTF::Mort::Subtable - Mort subtable superclass for AAT + +=head1 METHODS + +=cut + +use strict; +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +require Font::TTF::Mort::Rearrangement; +require Font::TTF::Mort::Contextual; +require Font::TTF::Mort::Ligature; +require Font::TTF::Mort::Noncontextual; +require Font::TTF::Mort::Insertion; + +sub new +{ + my ($class) = @_; + my ($self) = {}; + + $class = ref($class) || $class; + + bless $self, $class; +} + +sub create +{ + my ($class, $type, $coverage, $subFeatureFlags, $length) = @_; + + $class = ref($class) || $class; + + my $subclass; + if ($type == 0) { + $subclass = 'Font::TTF::Mort::Rearrangement'; + } + elsif ($type == 1) { + $subclass = 'Font::TTF::Mort::Contextual'; + } + elsif ($type == 2) { + $subclass = 'Font::TTF::Mort::Ligature'; + } + elsif ($type == 4) { + $subclass = 'Font::TTF::Mort::Noncontextual'; + } + elsif ($type == 5) { + $subclass = 'Font::TTF::Mort::Insertion'; + } + + my ($self) = $subclass->new( + (($coverage & 0x4000) ? 'RL' : 'LR'), + (($coverage & 0x2000) ? 'VH' : ($coverage & 0x8000) ? 'V' : 'H'), + $subFeatureFlags + ); + + $self->{'type'} = $type; + $self->{'length'} = $length; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file + +=cut + +sub out +{ + my ($self, $fh) = @_; + + my ($subtableStart) = $fh->tell(); + my ($type) = $self->{'type'}; + my ($coverage) = $type; + $coverage += 0x4000 if $self->{'direction'} eq 'RL'; + $coverage += 0x2000 if $self->{'orientation'} eq 'VH'; + $coverage += 0x8000 if $self->{'orientation'} eq 'V'; + + $fh->print(TTF_Pack("SSL", 0, $coverage, $self->{'subFeatureFlags'})); # placeholder for length + + my ($dat) = $self->pack_sub(); + $fh->print($dat); + + my ($length) = $fh->tell() - $subtableStart; + my ($padBytes) = (4 - ($length & 3)) & 3; + $fh->print(pack("C*", (0) x $padBytes)); + $length += $padBytes; + $fh->seek($subtableStart, IO::File::SEEK_SET); + $fh->print(pack("n", $length)); + $fh->seek($subtableStart + $length, IO::File::SEEK_SET); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub post +{ + my ($self) = @_; + + my ($post) = $self->{' PARENT'}{' PARENT'}{' PARENT'}{'post'}; + if (defined $post) { + $post->read; + } + else { + $post = {}; + } + + return $post; +} + +sub feat +{ + my ($self) = @_; + + return $self->{' PARENT'}->feat(); +} + +sub print +{ + my ($self, $fh) = @_; + + my ($feat) = $self->feat(); + my ($post) = $self->post(); + + $fh = 'STDOUT' unless defined $fh; + + my ($type) = $self->{'type'}; + my ($subFeatureFlags) = $self->{'subFeatureFlags'}; + my ($defaultFlags) = $self->{' PARENT'}{'defaultFlags'}; + my ($featureEntries) = $self->{' PARENT'}{'featureEntries'}; + $fh->printf("\n\t%s table, %s, %s, subFeatureFlags = %08x # %s (%s)\n", + subtable_type_($type), $_->{'direction'}, $_->{'orientation'}, $subFeatureFlags, + "Default " . ((($subFeatureFlags & $defaultFlags) != 0) ? "On" : "Off"), + join(", ", + map { + join(": ", $feat->settingName($_->{'type'}, $_->{'setting'}) ) + } grep { ($_->{'enable'} & $subFeatureFlags) != 0 } @$featureEntries + ) ); +} + +sub subtable_type_ +{ + my ($val) = @_; + my ($res); + + my (@types) = ( + 'Rearrangement', + 'Contextual', + 'Ligature', + undef, + 'Non-contextual', + 'Insertion', + ); + $res = $types[$val] or ('Undefined (' . $val . ')'); + + $res; +} + +=head2 $t->print_classes($fh) + +Prints a human-readable representation of the table + +=cut + +sub print_classes +{ + my ($self, $fh) = @_; + + my ($post) = $self->post(); + + my ($classes) = $self->{'classes'}; + foreach (0 .. $#$classes) { + my $class = $classes->[$_]; + if (defined $class) { + $fh->printf("\t\tClass %d:\t%s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$class)); + } + } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Name.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Name.pm new file mode 100644 index 0000000..4b1447a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Name.pm @@ -0,0 +1,782 @@ +package Font::TTF::Name; + +=head1 NAME + +Font::TTF::Name - String table for a TTF font + +=head1 DESCRIPTION + +Strings are held by number, platform, encoding and language. Strings are +accessed as: + + $f->{'name'}{'strings'}[$number][$platform_id][$encoding_id]{$language_id} + +Notice that the language is held in an associative array due to its sparse +nature on some platforms such as Microsoft ($pid = 3). Notice also that the +array order is different from the stored array order (platform, encoding, +language, number) to allow for easy manipulation of strings by number (which is +what I guess most people will want to do). + +By default, C<$Font::TTF::Name::utf8> is set to 1, and strings will be stored as UTF8 wherever +possible. The method C can be used to find out if a string in a particular +platform and encoding will be returned as UTF8. Unicode strings are always +converted if utf8 is requested. Otherwise, strings are stored according to platform: + +You now have to set <$Font::TTF::Name::utf8> to 0 to get the old behaviour. + +=over 4 + +=item Apple Unicode (platform id = 0) + +Data is stored as network ordered UCS2. There is no encoding id for this platform +but there are language ids as per Mac language ids. + +=item Mac (platform id = 1) + +Data is stored as 8-bit binary data, leaving the interpretation to the user +according to encoding id. + +=item Unicode (platform id = 2) + +Currently stored as 16-bit network ordered UCS2. Upon release of Perl 5.005 this +will change to utf8 assuming current UCS2 semantics for all encoding ids. + +=item Windows (platform id = 3) + +As per Unicode, the data is currently stored as 16-bit network ordered UCS2. Upon +release of Perl 5.005 this will change to utf8 assuming current UCS2 semantics for +all encoding ids. + +=back + +=head1 INSTANCE VARIABLES + +=over 4 + +=item strings + +An array of arrays, etc. + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA $VERSION @apple_encs @apple_encodings $utf8 $cp_1252 @cp_1252 %win_langs %langs_win %langs_mac @ms_langids @mac_langs); +use Font::TTF::Table; +use Font::TTF::Utils; +@ISA = qw(Font::TTF::Table); + +$utf8 = 1; + +{ + my ($count, $i); + eval {require Compress::Zlib;}; + unless ($@) + { + for ($i = 0; $i <= $#apple_encs; $i++) + { + $apple_encodings[0][$i] = [unpack("n*", Compress::Zlib::uncompress(unpack("u", $apple_encs[$i])))] + if (defined $apple_encs[$i]); + foreach (0 .. 127) + { $apple_encodings[0][$i][$_] = $_; } + $count = 0; + $apple_encodings[1][$i] = {map {$_ => $count++} @{$apple_encodings[0][$i]}}; + } + $cp_1252[0] = [unpack("n*", Compress::Zlib::uncompress(unpack("u", $cp_1252)))]; + $count = 0; + $cp_1252[1] = {map({$_ => $count++} @{$cp_1252[0]})}; + } + for ($i = 0; $i < $#ms_langids; $i++) + { + if (defined $ms_langids[$i][1]) + { + my ($j); + for ($j = 0; $j < $#{$ms_langids[$i][1]}; $j++) + { + my ($v) = $ms_langids[$i][1][$j]; + if ($v =~ m/^-/o) + { $win_langs{(($j + 1) << 10) + $i} = $ms_langids[$i][0] . $v; } + else + { $win_langs{(($j + 1) << 10) + $i} = $v; } + } + } + else + { $win_langs{$i + 0x400} = $ms_langids[$i][0]; } + } + %langs_win = map {my ($t) = $win_langs{$_}; my (@res) = ($t => $_); push (@res, $t => $_) if ($t =~ s/-.*$//o && ($_ & 0xFC00) == 0x400); @res} keys %win_langs; + $i = 0; + %langs_mac = map {$_ => $i++} @mac_langs; +} + + +$VERSION = 1.1; # MJPH 17-JUN-2000 Add utf8 support +# $VERSION = 1.001; # MJPH 10-AUG-1998 Put $number first in list + +=head2 $t->read + +Reads all the names into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($dat, $num, $stroff, $i, $pid, $eid, $lid, $nid, $len, $off, $here); + + $self->SUPER::read or return $self; + $fh->read($dat, 6); + ($num, $stroff) = unpack("x2nn", $dat); + for ($i = 0; $i < $num; $i++) + { + use bytes; # hack to fix bugs in 5.8.7 + read($fh, $dat, 12); + ($pid, $eid, $lid, $nid, $len, $off) = unpack("n6", $dat); + $here = $fh->tell(); + $fh->seek($self->{' OFFSET'} + $stroff + $off, 0); + $fh->read($dat, $len); + if ($utf8) + { + if ($pid == 1 && defined $apple_encodings[0][$eid]) + { $dat = TTF_word_utf8(pack("n*", map({$apple_encodings[0][$eid][$_]} unpack("C*", $dat)))); } + elsif ($pid == 2 && $eid == 2 && defined @cp_1252) + { $dat = TTF_word_utf8(pack("n*", map({$cp_1252[0][$_]} unpack("C*", $dat)))); } + elsif ($pid == 0 || $pid == 3 || ($pid == 2 && $eid == 1)) + { $dat = TTF_word_utf8($dat); } + } + $self->{'strings'}[$nid][$pid][$eid]{$lid} = $dat; + $fh->seek($here, 0); + } + $self; +} + + +=head2 $t->out($fh) + +Writes out all the strings + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($pid, $eid, $lid, $nid, $todo, @todo); + my ($len, $offset, $loc, $stroff, $endloc, $str_trans); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $loc = $fh->tell(); + $fh->print(pack("n3", 0, 0, 0)); + foreach $nid (0 .. $#{$self->{'strings'}}) + { + foreach $pid (0 .. $#{$self->{'strings'}[$nid]}) + { + foreach $eid (0 .. $#{$self->{'strings'}[$nid][$pid]}) + { + foreach $lid (sort keys %{$self->{'strings'}[$nid][$pid][$eid]}) + { + $str_trans = $self->{'strings'}[$nid][$pid][$eid]{$lid}; + if ($utf8) + { + if ($pid == 1 && defined $apple_encodings[1][$eid]) + { $str_trans = pack("C*", + map({$apple_encodings[1][$eid]{$_} || 0x3F} unpack("n*", + TTF_utf8_word($str_trans)))); } + elsif ($pid == 2 && $eid == 2 && defined @cp_1252) + { $str_trans = pack("C*", + map({$cp_1252[1][$eid]{$_} || 0x3F} unpack("n*", + TTF_utf8_word($str_trans)))); } + elsif ($pid == 2 && $eid == 0) + { $str_trans =~ s/[\xc0-\xff][\x80-\xbf]+/?/og; } + elsif ($pid == 0 || $pid == 3 || ($pid == 2 && $eid == 1)) + { $str_trans = TTF_utf8_word($str_trans); } + } + push (@todo, [$pid, $eid, $lid, $nid, $str_trans]); + } + } + } + } + + $offset = 0; + @todo = (sort {$a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] + || $a->[3] <=> $b->[3]} @todo); + foreach $todo (@todo) + { + $len = length($todo->[4]); + $fh->print(pack("n6", @{$todo}[0..3], $len, $offset)); + $offset += $len; + } + + $stroff = $fh->tell() - $loc; + foreach $todo (@todo) + { $fh->print($todo->[4]); } + + $endloc = $fh->tell(); + $fh->seek($loc, 0); + $fh->print(pack("n3", 0, $#todo + 1, $stroff)); + $fh->seek($endloc, 0); + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $value) + +Outputs the string element in nice XML (which is all the table really!) + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $value) = @_; + my ($fh) = $context->{'fh'}; + my ($nid, $pid, $eid, $lid); + + return $self->SUPER::XML_element(@_) unless ($key eq 'strings'); + + foreach $nid (0 .. $#{$self->{'strings'}}) + { + next unless ref($self->{'strings'}[$nid]); +# $fh->print("$depth\n"); + foreach $pid (0 .. $#{$self->{'strings'}[$nid]}) + { + foreach $eid (0 .. $#{$self->{'strings'}[$nid][$pid]}) + { + foreach $lid (sort {$a <=> $b} keys %{$self->{'strings'}[$nid][$pid][$eid]}) + { + my ($lang) = $self->get_lang($pid, $lid) || $lid; + $fh->printf("%s\n%s%s%s\n%s\n", + $depth, $nid, $pid, $eid, $lang, $depth, + $context->{'indent'}, $self->{'strings'}[$nid][$pid][$eid]{$lid}, $depth); + } + } + } +# $fh->print("$depth\n"); + } + $self; +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Store strings in the right place + +=cut + +sub XML_end +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag eq 'string') + { + my ($lid) = $self->find_name($attrs{'platform'}, $attrs{'language'}) || $attrs{'language'}; + $self->{'strings'}[$attrs{'id'}][$attrs{'platform'}][$attrs{'encoding'}]{$lid} + = $context->{'text'}; + return $context; + } + else + { return $self->SUPER::XML_end(@_); } +} + +=head2 is_utf8($pid, $eid) + +Returns whether a string of a given platform and encoding is going to be in UTF8 + +=cut + +sub is_utf8 +{ + my ($self, $pid, $eid) = @_; + + return ($utf8 && ($pid == 0 || $pid == 3 || ($pid == 2 && ($eid != 2 || defined @cp_1252)) + || ($pid == 1 && defined $apple_encodings[$eid]))); +} + + +=head2 find_name($nid) + +Hunts down a name in all the standard places and returns the string and for an +array context the pid, eid & lid as well + +=cut + +sub find_name +{ + my ($self, $nid) = @_; + my ($res, $pid, $eid, $lid, $look, $k); + + my (@lookup) = ([3, 1, 1033], [3, 1, -1], [3, 0, 1033], [3, 0, -1], [2, 1, -1], [2, 2, -1], [2, 0, -1], + [0, 0, 0], [1, 0, 0]); + foreach $look (@lookup) + { + ($pid, $eid, $lid) = @$look; + if ($lid == -1) + { + foreach $k (keys %{$self->{'strings'}[$nid][$pid][$eid]}) + { + if (($res = $self->{strings}[$nid][$pid][$eid]{$k}) ne '') + { + $lid = $k; + last; + } + } + } else + { $res = $self->{strings}[$nid][$pid][$eid]{$lid} } + if ($res ne '') + { return wantarray ? ($res, $pid, $eid, $lid) : $res; } + } + return ''; +} + + +=head2 set_name($nid, $str[, $lang[, @cover]]) + +Sets the given name id string to $str for all platforms and encodings that +this module can handle. If $lang is set, it is interpretted as a language +tag and if the particular language of a string is found to match, then +that string is changed, otherwise no change occurs. + +If supplied, @cover should be a list of references to two-element arrays +containing pid,eid pairs that should added to the name table if not already present. + +This function does not add any names to the table unless @cover is supplied. + +=cut + +sub set_name +{ + my ($self, $nid, $str, $lang, @cover) = @_; + my ($pid, $eid, $lid, $c); + + foreach $pid (0 .. $#{$self->{'strings'}[$nid]}) + { + my $strNL = $str; + $strNL =~ s/\n/\r\n/og if $pid == 3; + $strNL =~ s/\n/\r/og if $pid == 1; + foreach $eid (0 .. $#{$self->{'strings'}[$nid][$pid]}) + { + foreach $lid (keys %{$self->{'strings'}[$nid][$pid][$eid]}) + { + next unless (!defined $lang || $self->match_lang($pid, $lid, $lang)); + $self->{'strings'}[$nid][$pid][$eid]{$lid} = $strNL; + foreach $c (0 .. scalar @cover) + { + next unless ($cover[$c][0] == $pid && $cover[$c][1] == $eid); + delete $cover[$c]; + last; + } + } + } + } + foreach $c (@cover) + { + my ($pid, $eid) = @{$c}; + my ($lid) = $self->find_lang($pid, $lang); + my $strNL = $str; + $strNL =~ s/\n/\r\n/og if $pid == 3; + $strNL =~ s/\n/\r/og if $pid == 1; + $self->{'strings'}[$nid][$pid][$eid]{$lid} = $strNL; + } + return $self; +} + +=head2 Font::TTF::Name->match_lang($pid, $lid, $lang) + +Compares the language associated to the string of given platform and language +with the given language tag. If the language matches the tag (i.e. is equal +or more defined than the given language tag) returns true. This is calculated +by finding whether the associated language tag starts with the given language +tag. + +=cut + +sub match_lang +{ + my ($self, $pid, $lid, $lang) = @_; + my ($langid) = $self->get_lang($pid, $lid); + + return ($lid == $lang) if ($lang != 0 || $lang eq '0'); + return !index(lc($langid), lc($lang)); +} + +=head2 Font::TTF::Name->get_lang($pid, $lid) + +Returns the language tag associated with a particular platform and language id + +=cut + +sub get_lang +{ + my ($self, $pid, $lid) = @_; + + if ($pid == 3) + { return $win_langs{$lid}; } + elsif ($pid == 1) + { return $mac_langs[$lid]; } + return ''; +} + + +=head2 Font::TTF::Name->find_lang($pid, $lang) + +Looks up the language name and returns a lang id if one exists + +=cut + +sub find_lang +{ + my ($self, $pid, $lang) = @_; + + if ($pid == 3) + { return $langs_win{$lang}; } + elsif ($pid == 1) + { return $langs_mac{$lang}; } + return undef; +} + + +BEGIN { +@apple_encs = ( +<<'EOT', +M>)RES==NCW$`@.'G_S5Q*L(!#?+K1VO4:.W6IJA-:\^BM?>L>1&NP(A0Q$BL +M<*!62ZV8Z1)[K]BE$MR#O,=/7OW]7T&*6"NMI4K31EOMM)>N@XXZZ2Q#IBZZ +MZJ:['GKJ)4NVWOKHJ]\_/\!`@PR68XBAALDUW`@CC3+:&&.-,UZ>?!-,-,ED +M4TPUS70SS#3+;`7FF&N>0D7F6V"A119;8JEEEEMAI5566V.M==;;H-A&FVRV +MQ5;;_OTONJ3<%;?<5^NQ1YYXYJGG7GKME3?>>N^=#S[ZY(O/OOKNFU]^JO<[ +M!$?LLMO>$#OAH4-*4F+'[(L+E*F,6SH:%\9%]C@>1W&CN&%2:9QNO]-))5ZH +M<]9.!^/DQ/8X-V[@@#,AS0ZE+KB7R$ODA\:A26@>6H2FH9D?J17^)(I#3C@8 +MLD)V?:(^"BE.AN30,F0XK\(Y5UUVW0TW77/'W;H_;JM6HRJ1&95%M0Y'E5%5 +.5.U4]""JB`?E$` +EOT + +undef, +undef, +undef, +<<'EOT', +M>)RES[=/%```1O$WO8G_@$'J';W70Z2WHS>5WJN%8D6%D;BZ,3*P,;#C2D(8 +M,9&)08V)+4*(1((X2'(#[.:;7[[\*./_%D,L<<230"(!@B213`JII)%.!IED +MD4T.N>213P&%%%%,B!)N4LJMR[Z<"BJIHIH::JFCG@;"--)$,RVTTD8['732 +M13>WN<-=>NBECWX&&&2(848898QQ)IADBFEFF.4>]WG`0^:89X%%'O&8)SSE +M&<]9X@4O><4R*Y?_.ZRSRQ[[''#(1S[PB<]NL\D7OO&5[_S@9TR`(XXYX1=O +M.>4W9_SAG`O^7OF=O>XW*N)WV!%''7/<"2>=S?<\K5O(G[7?/Y>'``` +EOT + +<<'EOT', +M>)RED$LSEW$`A9_-^00L,H-^(=>4Y%^2J'1Q*Y+[I2(BHA`B?!%J6EM1*28S +M;9II[/PI*7*_%TUN\_*VZ%W:FN9LSYEGGD,\_Q?#$?SP)X"C!!)$,"&$$L8Q +MPCG."2(X222GB,+%:XR"42N,P5KG*-1))()H54KG.# +M--*Y20:WR"2+;'+()8]\"BBDB-O$PM +M==3SA`8::>(IS;3PC%;::'?X'^W#?&(0-Z-,,,,TL\PSQP)+K+#,*C]9XQ?K +M_.8/FVRPQ0[;[+&+S=_]_J;KX/Y6I?&U.JQ.Z[GU0@-VBNTR@;Q4G]ZI5V_U +MQG@83^-M?,PAXV6'VF'ZH&Z]4H_>J]]IO=:0W!K6B#[KBT;U56/ZIN\:UX1^ +?:%)3FM:,9C6G>2UH44M:UHI6'?)RES5=OSG$`0.$CYR.(A(3DUS]J4WOO59O6;&F+UMY[7R&(V'N^4ETZ=*"J +M:M:H=>E*0D1B)7HC1KC0[R#G^LEA,/]7((Z(EK2B-?&TH2WM:$\'.M*)SG0A +M@:YTHSL]Z$DO>M.'OO2C/P,8R*`&/X2A#&,X(QC)*$:3R!C&,H[Q3&`BDYC, +M%))(9BK3F,X,9C*+%%*9S1S22">#NN(MWO.>#.\GG(Y_YQ!>^DAT7 +M\8WZ$%$3$OC.#W(IYC=_^!N"1SWF*<]ZP1AO*:'`;*^0%V502J6'*8LRHRQR +M/.)Q3WC2TY[QG+D6FF^!19ZGR(M>BA*]3"'5(9Z8.>:YVSV-DD/CT"0T#RU" +MT]",G^YUG_L]8+$E7O6%!WUIF>4^]9K7?6R%E59YQUM6>]L:[WK/5][WH;7> +4M,X'/O&1-WSF_\` +EOT + +<<'EOT', +M>)RERT=.%5``0-&+7K'&!B(@X/L/^/3>ZZ?SZ=*K@`KVWOL:U!68.#!&8G2@ +M$Q?F5/=@SOB0XO\$$D2**:&4)&644T$E55130RUUU--`(TTTTT(K;;3302== +M=--#[[_?1S\###+$,".,DF:,<2:89(II9KC`+'/,L\`B2RRSPBIKK+/!13;9 +M8IM+7.8*.^QRE6M]SG`0]YQ&.>\)1G/.<%+WG%:][PEI0G +M/>5IL\SVC#F>-=<\\SUG@846>=Y@PFBQ)9::M,QR*ZRTRFIKK+4N!+[[CD]\ +M#I%?9O*-+XGH/N?BMON=CT7\B#MQUR5^^MY#ZH('7?:PJQYQS14/L!?S,S[$ +M=,SD*[]#DH\>==UC;K@8LD)V*`B%(3?D\2<4>=Q-3[B5R#'#66>LM\%&FVRV +GQ5;;;+?#3KOLML=>4_;9[X"##CGLB*.F'7/<"2>=)RED-DVUG$`1;=:U*Y%0C)5O^^/SSS/F>>9#"$JE7D>"D6\3S=>Q^MPU^JF +M&^M"2JJHIH9:ZJBG@4:::*:%M[32 +M1CL==_TNNNFAES[Z&6"0(889890QQIE@DG=,,% +MF;XTRVQSS#7/5[[VC<&8D?D66&C<(HLML=0RRZVPTBJ7K;;&6NNLM\%&FVRV +L):388:===MMCKP,..F2_(XXZYK#CMKGZS[YU-]QTRVUWW'7/?0]N`4(?0WT` +EOT + +<<'EOT', +M>)RED,5.0U$415=(D.X!$"ANMX^VN+M#D>+N[H4"Q5W^APF_PZ\PY.9-"`-& +MY.3LG>-"#_\3@P^'8OP$"%)"*6644T$E55130RUUU--`(TTTTT(K;;3302== +M=-OZ7OH(T<\`@PP19I@11AECG`DFF6*:&6:98YX%%EEBF15666.=#3;98IL= +M=MECGP,.B7#$,5%...6,&.=<<,D5U]QPRQWW//#($\^\\,J;G?_II)ETXS79 +M)L<$C<,['S[GYSY=?FWK6E>Z^?L'BK,:KP0E*DD>R?6E*-7E='DM9BA36A49XKI_!M<9D8J +EOT + +<<'EOT', +M>)RED,E3SW$8QU_77@<''+A]^Y5(2-F7+"%92\B^ES5ES]H,)L(8&21E*UNH +M&"8T8ZS3I(FS_T"$_`L^-^/D8)YY/^]Y/\L\"Y/Y/XN()T8"B0P@B8$,(IG! +MI#"$H0PCE>&DDG,((N99#.+VM8SP8**&0CF]C,%K:RC2*V +M4TP).]C)+G:SA[WLHY3]'.`@ASC,$-(*3WG:,R%ZSDK/!K[@1<][R2HO6^T5:ZSUJM>\[@UO +M6F>]M[SM'>]ZSX90_\"'-MIDLX^">ASPQ*?!M_C,Y[ZP->KE*U_[QK>^\WW( +CM/O!ML"=?K3#3[Z,*_AKOR]V^=5O=OO='_ZTQU^_`2-%:*`` +EOT + +undef, +undef, +undef, +undef, +undef, +undef, +undef, +undef, +undef, +<<'EOT', +M>)REC]=.E&$`1(\%&W@4004%_7:!I?>.Z-+[TJL*=K"`BH`*J,_"+2'A!7PW +MX;\2[LG<3#*9G!F2G$V!&'$***2(!,644$H9Y5102175U%!+'?4TT$@3S;30 +M2AN/:.\HSG +M+++$"U[RBM>\X2WO6&:%]WS@(Y]898W/?.$KZWQC@TVV^,X/?K+-#KO\XC=_ +M(OX!?T/"`0<=-$T+WG9 +M*U[UFNEF>%V]X4TSO666V=[VCG?-,==[WC?/?!_XT&#,N`466F3"8DLLMLD&W2#COMLML>>^V+=IX\2<7BCCGNA)-. +0.>V,L\XY[P*'[!\#D^='L@`` +EOT + +undef, +undef, +undef, +undef, +undef, +undef, +undef, +undef, +undef, +); + +$cp_1252 = ( +<<'EOT', +M>)P-SD-B'5```,#YJ6VE>DEM&[\VD]JVF?H./4'-U+93V[9M:SV;$141(Y74 +MTD@KG?0RR"B3S++(*IOL:%9-$0&YD?BH22(82XF)10.3(@U(DDB$;F_/]% +M0_Y0(!0*A4-\R!5RQ]R*BX\,#'4CB?]];B3)`@LMLM@22RVSW`HKK;):LC76 +M6F>]#3;:9+,MMMIFNQUVVF6W/?;:9[\##CKDL"-2''7,<2><=,II9YQUSGD7 +M7'3)95=<=0@` +EOT +); +#' + +@ms_langids = ( [""], + ['ar', ["-SA", "-IQ", "-EG", "-LY", "-DZ", "-MA", "-TN", + "-OM", "-YE", "-SY", "-JO", "-LB", "-KW", "-AE", + "-BH", "-QA"]], + ['bg-BG'], + ['ca-ES'], + ['zh', ['-TW', 'CN', '-HK', '-SG', '-MO']], + ["cs-CZ"], + ["da-DK"], + ["de", ["-DE", "-CH", "-AT", "-LU", "-LI"]], + ["el-GR"], + ["en", ["-US", "-UK", "-AU", "-CA", "-NZ", "-IE", "-ZA", + "-JM", "029", "-BZ", "-TT", "-ZW", "-PH", "-ID", + "-HK", "-IN", "-MY", "-SG"]], + ["es", ["-ES", "-MX", "-ES", "-GT", "-CR", "-PA", "-DO", + "-VE", "-CO", "-PE", "-AR", "-EC", "-CL", "-UY", + "-PY", "-BO", "-SV", "-HN", "-NI", "-PR", "-US"]], + ["fi-FI"], + ["fr", ["-FR", "-BE", "-CA", "-CH", "-LU", "-MC", "", + "-RE", "-CG", "-SN", "-CM", "-CI", "-ML", "-MA", + "-HT"]], + ["he-IL"], + ["hu-HU"], + ["is-IS"], +# 0010 + ["it", ["-IT", "-CH"]], + ["ja-JP"], + ["ko-KR"], + ["nl", ["-NL", "-BE"]], + ["no", ["-bok-NO", "-nyn-NO"]], + ["pl-PL"], + ["pt", ["-BR", "-PT"]], + ["rm-CH"], + ["ro", ["-RO", "_MD"]], + ["ru-RU"], + ["hr", ["-HR", "-Latn-CS", "Cyrl-CS", "-BA", "", "-Latn-BA", "-Cyrl-BA"]], + ["sk-SK"], + ["sq-AL"], + ["sv", ["-SE", "-FI"]], + ["th-TH"], + ["tr-TR"], +# 0020 + ["ur", ["-PK", "tr-IN"]], + ["id-ID"], + ["uk-UA"], + ["be-BY"], + ["sl-SL"], + ["et-EE"], + ["lv-LV"], + ["lt-LT"], + ["tg-Cyrl-TJ"], + ["fa-IR"], + ["vi-VN"], + ["hy-AM"], + ["az", ["-Latn-AZ", "-Cyrl-AZ"]], + ["eu-ES"], + ["wen". ["wen-DE", "dsb-DE"]], + ["mk-MK"], +# 0030 + ["st"], + ["ts"], + ["tn-ZA"], + ["ven"], + ["xh-ZA"], + ["zu-ZA"], + ["af-ZA"], + ["ka-GE"], + ["fo-FO"], + ["hi-IN"], + ["mt"], + ["se", ["-NO", "-SE", "-FI", "smj-NO", "smj-SE", "sma-NO", "sma-SE", + "", "smn-FI"]], + ["ga-IE"], + ["yi"], + ["ms", ["-MY", "-BN"]], + ["kk-KZ"], +# 0040 + ["ky-KG"], + ["sw-KE"], + ["tk-TM"], + ["uz", ["-Latn-UZ", "-Cyrl-UZ"]], + ["tt-RU"], + ["bn", ["-IN", "-BD"]], + ["pa", ["-IN", "-Arab-PK"]], + ["gu-IN"], + ["or-IN"], + ["ta-IN"], + ["te-IN"], + ["kn-IN"], + ["ml-IN"], + ["as-IN"], + ["mr-IN"], + ["sa-IN"], +# 0050 + ["mn", ["-Cyrl-MN", "-Mong-CN"]], + ["bo", ["-CN", "-BT"]], + ["cy-GB"], + ["km-KH"], + ["lo-LA"], + ["my"], + ["gl-ES"], + ["kok-IN"], + ["mni"], + ["sd", ["-IN", "-PK"]], + ["syr-SY"], + ["si-LK"], + ["chr"], + ["iu", ["-Cans-CA", "-Latn-CA"]], + ["am-ET"], + ["tmz", ["-Arab", "tmz-Latn-DZ"]], +# 0060 + ["ks"], + ["ne", ["-NP", "-IN"]], + ["fy-NL"], + ["ps-AF"], + ["fil-PH"], + ["dv-MV"], + ["bin-NG"], + ["fuv-NG"], + ["ha-Latn-NG"], + ["ibb-NG"], + ["yo-NG"], + ["quz", ["-BO", "-EC", "-PE"]], + ["ns-ZA"], + ["ba-RU"], + ["lb-LU"], + ["kl-GL"], +# 0070 + ["ig-NG"], + ["kau"], + ["om"], + ["ti", ["-ET". "-ER"]], + ["gn"], + ["haw"], + ["la"], + ["so"], + ["ii-CN"], + ["pap"], + ["arn-CL"], + [""], # (unassigned) + ["moh-CA"], + [""], # (unassigned) + ["br-FR"], + [""], # (unassigned) +# 0080 + ["ug-CN"], + [""], # (unassigned) + ["oc-FR"], + ["gsw-FR"], + [""], # (unassigned) + ["sah-RU"], + ["qut-GT"], + ["rw-RW"], + ["wo-SN"], + [""], # (unassigned) + [""], # (unassigned) + [""], # (unassigned) + ["gbz-AF"], +); + +@mac_langs = ( + 'en', 'fr', 'de', 'it', 'nl', 'sv', 'es', 'da', 'pt', 'no', + 'he', 'ja', 'ar', 'fi', 'el', 'is', 'mt', 'tr', 'hr', 'zh-Hant', + 'ur', 'hi', 'th', 'ko', 'lt', 'pl', 'hu', 'et', 'lv', 'se', + 'fo', 'ru' ,'zh-Hans', 'nl', 'ga', 'sq', 'ro', 'cs', 'sk', + 'sl', 'yi', 'sr', 'mk', 'bg', 'uk', 'be', 'uz', 'kk', 'az-Cyrl', + 'az-Latn', 'hy', 'ka', 'mo', 'ky', 'abh', 'tuk', 'mn-Mong', 'mn-Cyrl', 'pst', + 'ku', 'ks', 'sd', 'bo', 'ne', 'sa', 'mr', 'bn', 'as', 'gu', + 'pa', 'or', 'ml', 'kn', 'ta', 'te', 'si', 'my', 'km', 'lo', + 'vi', 'id', 'tl', 'ms-Latn', 'ms-Arab', 'am', 'ti', 'tga', 'so', 'sw', + 'rw', 'rn', 'ny', 'mg', 'eo', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', 'cy', 'eu', + 'la', 'qu', 'gn', 'ay', 'tt', 'ug', 'dz', 'jv-Latn', 'su-Latn', + 'gl', 'af', 'br', 'iu', 'gd', 'gv', 'gd-IR-x-dotabove', 'to', 'el-polyton', 'kl', + 'az-Latn' +); + +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +Unicode type strings will be stored in utf8 for all known platforms, +once Perl 5.6 has been released and I can find all the mapping tables, etc. + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/OS_2.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OS_2.pm new file mode 100644 index 0000000..0092732 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OS_2.pm @@ -0,0 +1,354 @@ +package Font::TTF::OS_2; + +=head1 NAME + +Font::TTF::OS_2 - the OS/2 table in a TTF font + +=head1 DESCRIPTION + +The OS/2 table has two versions and forms, one an extension of the other. This +module supports both forms and the switching between them. + +=head1 INSTANCE VARIABLES + +No other variables than those in table and those in the standard: + + Version + xAvgCharWidth + usWeightClass + usWidthClass + fsType + ySubscriptXSize + ySubScriptYSize + ySubscriptXOffset + ySubscriptYOffset + ySuperscriptXSize + ySuperscriptYSize + ySuperscriptXOffset + ySuperscriptYOffset + yStrikeoutSize + yStrikeoutPosition + sFamilyClass + bFamilyType + bSerifStyle + bWeight + bProportion + bContrast + bStrokeVariation + bArmStyle + bLetterform + bMidline + bXheight + ulUnicodeRange1 + ulUnicodeRange2 + ulUnicodeRange3 + ulUnicodeRange4 + achVendID + fsSelection + usFirstCharIndex + usLastCharIndex + sTypoAscender + sTypoDescender + sTypoLineGap + usWinAscent + usWinDescent + ulCodePageRange1 + ulCodePageRange2 + xHeight + CapHeight + defaultChar + breakChar + maxLookups + +Notice that versions 0, 1, 2 & 3 of the table are supported. Notice also that the +Panose variable has been broken down into its elements. + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA @fields @lens @field_info @weights); +use Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'xAvgCharWidth' => 's', + 'usWeightClass' => 'S', + 'usWidthClass' => 'S', + 'fsType' => 's', + 'ySubscriptXSize' => 's', + 'ySubScriptYSize' => 's', + 'ySubscriptXOffset' => 's', + 'ySubscriptYOffset' => 's', + 'ySuperscriptXSize' => 's', + 'ySuperscriptYSize' => 's', + 'ySuperscriptXOffset' => 's', + 'ySuperscriptYOffset' => 's', + 'yStrikeoutSize' => 's', + 'yStrikeoutPosition' => 's', + 'sFamilyClass' => 's', + 'bFamilyType' => 'C', + 'bSerifStyle' => 'C', + 'bWeight' => 'C', + 'bProportion' => 'C', + 'bContrast' => 'C', + 'bStrokeVariation' => 'C', + 'bArmStyle' => 'C', + 'bLetterform' => 'C', + 'bMidline' => 'C', + 'bXheight' => 'C', + 'ulUnicodeRange1' => 'L', + 'ulUnicodeRange2' => 'L', + 'ulUnicodeRange3' => 'L', + 'ulUnicodeRange4' => 'L', + 'achVendID' => 'L', + 'fsSelection' => 'S', + 'usFirstCharIndex' => 'S', + 'usLastCharIndex' => 'S', + 'sTypoAscender' => 'S', + 'sTypoDescender' => 's', + 'sTypoLineGap' => 'S', + 'usWinAscent' => 'S', + 'usWinDescent' => 'S', + '' => '', + 'ulCodePageRange1' => 'L', + 'ulCodePageRange2' => 'L', + '' => '', + 'xHeight' => 's', + 'CapHeight' => 's', + 'defaultChar' => 'S', + 'breakChar' => 'S', + 'maxLookups' => 's', + '' => '', # i.e. v3 is basically same as v2 + ); + +@weights = qw(64 14 27 35 100 20 14 42 63 3 6 35 20 56 56 17 4 49 56 71 31 10 18 3 18 2 166); + +use Font::TTF::Utils; + +sub init +{ + my ($k, $v, $c, $n, $i, $t, $j); + + $n = 0; + @lens = (76, 84, 94, 94); + for ($j = 0; $j < $#field_info; $j += 2) + { + if ($field_info[$j] eq '') + { + $n++; + next; + } + ($k, $v, $c) = TTF_Init_Fields($field_info[$j], $c, $field_info[$j+1]); + next unless defined $k && $k ne ""; + for ($i = $n; $i < 4; $i++) + { $fields[$i]{$k} = $v; } + } +} + + +=head2 $t->read + +Reads in the various values from disk (see details of OS/2 table) + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $ver); + + $self->SUPER::read or return $self; + + init unless defined $fields[2]{'xAvgCharWidth'}; + $self->{' INFILE'}->read($dat, 2); + $ver = unpack("n", $dat); + $self->{'Version'} = $ver; + if ($ver < 4) + { + $self->{' INFILE'}->read($dat, $lens[$ver]); + TTF_Read_Fields($self, $dat, $fields[$ver]); + } + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($ver); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $ver = $self->{'Version'}; + $fh->print(pack("n", $ver)); + $fh->print(TTF_Out_Fields($self, $fields[$ver], $lens[$ver])); + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $value) + +Tidies up the hex values to output them in hex + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $value) = @_; + my ($fh) = $context->{'fh'}; + + if ($key =~ m/^ul(?:Unicode|CodePage)Range\d$/o) + { $fh->printf("%s<%s>%08X\n", $depth, $key, $value, $key); } + elsif ($key eq 'achVendID') + { $fh->printf("%s<%s name='%s'/>\n", $depth, $key, pack('N', $value)); } + else + { return $self->SUPER::XML_element(@_); } + $self; +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Now handle them on the way back in + +=cut + +sub XML_end +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag =~ m/^ul(?:Unicode|CodePage)Range\d$/o) + { return hex($context->{'text'}); } + elsif ($tag eq 'achVendID') + { return unpack('N', $attrs{'name'}); } + else + { return $self->SUPER::XML_end(@_); } +} + +=head2 $t->update + +Updates the OS/2 table by getting information from other sources: + +Updates the C and C values based on the MS table in the +cmap. + +Updates the sTypoAscender, sTypoDescender & sTypoLineGap to be the same values +as Ascender, Descender and Linegap from the hhea table (assuming it is dirty) +and also sets usWinAscent to be the sum of Ascender+Linegap and usWinDescent to +be the negative of Descender. + +=cut + +sub update +{ + my ($self) = @_; + my ($map, @keys, $table, $i, $avg, $hmtx); + + return undef unless ($self->SUPER::update); + + $self->{' PARENT'}{'cmap'}->update; + $map = $self->{' PARENT'}{'cmap'}->find_ms || return undef; + $hmtx = $self->{' PARENT'}{'hmtx'}->read; + + @keys = sort {$a <=> $b} grep {$_ < 0x10000} keys %{$map->{'val'}}; + + $self->{'usFirstCharIndex'} = $keys[0]; + $self->{'usLastCharIndex'} = $keys[-1]; + + $table = $self->{' PARENT'}{'hhea'}->read; + + # try any way we can to get some real numbers passed around! + if (($self->{'fsSelection'} & 128) != 0) + { + # assume the user knows what they are doing and has sensible values already + } + elsif ($table->{'Ascender'} != 0 || $table->{'Descender'} != 0) + { + $self->{'sTypoAscender'} = $table->{'Ascender'}; + $self->{'sTypoDescender'} = $table->{'Descender'}; + $self->{'sTypoLineGap'} = $table->{'LineGap'}; + $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'}; + $self->{'usWinDescent'} = -$self->{'sTypoDescender'}; + } + elsif ($self->{'sTypoAscender'} != 0 || $self->{'sTypoDescender'} != 0) + { + $table->{'Ascender'} = $self->{'sTypoAscender'}; + $table->{'Descender'} = $self->{'sTypoDescender'}; + $table->{'LineGap'} = $self->{'sTypoLineGap'}; + $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'}; + $self->{'usWinDescent'} = -$self->{'sTypoDescender'}; + } + elsif ($self->{'usWinAscent'} != 0 || $self->{'usWinDescent'} != 0) + { + $self->{'sTypoAscender'} = $table->{'Ascender'} = $self->{'usWinAscent'}; + $self->{'sTypoDescender'} = $table->{'Descender'} = -$self->{'usWinDescent'}; + $self->{'sTypoLineGap'} = $table->{'LineGap'} = 0; + } + + if ($self->{'Version'} < 3) + { + for ($i = 0; $i < 26; $i++) + { $avg += $hmtx->{'advance'}[$map->{'val'}{$i + 0x0061}] * $weights[$i]; } + $avg += $hmtx->{'advance'}[$map->{'val'}{0x0020}] * $weights[-1]; + $self->{'xAvgCharWidth'} = $avg / 1000; + } + elsif ($self->{'Version'} > 2) + { + $i = 0; $avg = 0; + foreach (@{$hmtx->{'advance'}}) + { + next unless ($_); + $i++; + $avg += $_; + } + $avg /= $i if ($i); + $self->{'xAvgCharWidth'} = $avg; + } + + foreach $i (keys %{$map->{'val'}}) + { + if ($i >= 0x10000) + { + $self->{'ulUnicodeRange2'} |= 0x2000000; + last; + } + } + + $self->{'Version'} = 1 if (defined $self->{'ulCodePageRange1'} && $self->{'Version'} < 1); + $self->{'Version'} = 2 if (defined $self->{'maxLookups'} && $self->{'Version'} < 2); + + if ((exists $self->{' PARENT'}{'GPOS'} && $self->{' PARENT'}{'GPOS'}{' read'}) || + (exists $self->{' PARENT'}{'GSUB'} && $self->{' PARENT'}{'GSUB'}{' read'})) + { + # one or both of GPOS & GSUB exist and have been read or modified; so update usMaxContexts + my ($lp, $ls); + $lp = $self->{' PARENT'}{'GPOS'}->maxContext if exists $self->{' PARENT'}{'GPOS'}; + $ls = $self->{' PARENT'}{'GSUB'}->maxContext if exists $self->{' PARENT'}{'GSUB'}; + $self->{'maxLookups'} = $lp > $ls ? $lp : $ls; + } + + $self; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/OTTags.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OTTags.pm new file mode 100644 index 0000000..71338e2 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OTTags.pm @@ -0,0 +1,706 @@ +package Font::TTF::OTTags; + +=head1 NAME + +Font::TTF::OTTags - Utilities for TrueType/OpenType tags + +=head1 SYNOPSIS + + use Font::TTF::OTTags qw( %tttags %ttnames readtagsfile ); + + # Look at built-in stuff: + $script_tag = $tttags{'SCRIPT'}{'Cypriot Syllabary'}; + $lang_name = $ttnames{'LANGUAGE'}{'AFK '} + + # Read latest tags file to add to built-in definitions + readtagsfile ("C:\\Program Files\\Microsoft VOLT\\TAGS.txt"); + +First-level keys to %tttags and %ttnames are: + +=over + +'SCRIPT' -- retrieve script tag or name + +'LANGUAGE' -- retrieve language tag or name + +'FEATURE' -- retrieve feature tag or name + +=back + +Built-in data has been derived from Microsoft's tag registry at +L, +updated to draft v1.5 of the OpenType Spec. + +=head1 METHODS + +=cut + +use strict; +use vars qw( %tttags %ttnames @EXPORT_OK @ISA ); +require Exporter; +@ISA = qw( Exporter ); +@EXPORT_OK = qw( %tttags %ttnames readtagsfile); + + +%tttags = ( + +# All data below derived from VOLT 1.3 TAGS.txt file. +# For conveninence of human checking, these are now sorted alphabetically. + +'SCRIPT' => { + "Arabic" => "arab", + "Armenian" => "armn", + "Balinese" => "bali", + "Bengali v.2" => "bng2", + "Bengali" => "beng", + "Bopomofo" => "bopo", + "Braille" => "brai", + "Buginese" => "bugi", + "Buhid" => "buhd", + "Byzantine Music" => "byzm", + "CJK Ideographic" => "hani", + "Canadian Syllabics" => "cans", + "Carian" => "cari", + "Cham" => "cham", + "Cherokee" => "cher", + "Coptic" => "copt", + "Cypriot Syllabary" => "cprt", + "Cyrillic" => "cyrl", + "Default" => "DFLT", + "Deseret" => "dsrt", + "Devanagari v.2" => "dev2", + "Devanagari" => "deva", + "Ethiopic" => "ethi", + "Georgian" => "geor", + "Glagolitic" => "glag", + "Gothic" => "goth", + "Greek" => "grek", + "Gujarati v.2" => "gjr2", + "Gujarati" => "gujr", + "Gurmukhi v.2" => "gur2", + "Gurmukhi" => "guru", + "Hangul Jamo" => "jamo", + "Hangul" => "hang", + "Hanunoo" => "hano", + "Hebrew" => "hebr", + "Hiragana" => "kana", + "Javanese" => "java", + "Kannada v.2" => "knd2", + "Kannada" => "knda", + "Katakana" => "kana", + "Kayah Li" => "kali", + "Kharosthi" => "khar", + "Khmer" => "khmr", + "Lao" => "lao ", + "Latin" => "latn", + "Lepcha" => "lepc", + "Limbu" => "limb", + "Linear B" => "linb", + "Lycian" => "lyci", + "Lydian" => "lydi", + "Malayalam v.2" => "mlm2", + "Malayalam" => "mlym", + "Mathematical Alphanumeric Symbols" => "math", + "Mongolian" => "mong", + "Musical Symbols" => "musc", + "Myanmar" => "mymr", + "N'Ko" => "nko ", + "New Tai Lue" => "talu", + "Ogham" => "ogam", + "Ol Chiki" => "olck", + "Old Italic" => "ital", + "Old Persian Cuneiform" => "xpeo", + "Oriya v.2" => "ory2", + "Oriya" => "orya", + "Osmanya" => "osma", + "Phags-pa" => "phag", + "Phoenician" => "phnx", + "Rejang" => "rjng", + "Runic" => "runr", + "Saurashtra" => "saur", + "Shavian" => "shaw", + "Sinhala" => "sinh", + "Sumero-Akkadian Cuneiform" => "xsux", + "Sundanese" => "sund", + "Syloti Nagri" => "sylo", + "Syriac" => "syrc", + "Tagalog" => "tglg", + "Tagbanwa" => "tagb", + "Tai Le" => "tale", + "Tamil v.2" => "tml2", + "Tamil" => "taml", + "Telugu v.2" => "tel2", + "Telugu" => "telu", + "Thaana" => "thaa", + "Thai" => "thai", + "Tibetan" => "tibt", + "Tifinagh" => "tfng", + "Ugaritic Cuneiform" => "ugar", + "Vai" => "vai ", + "Yi" => "yi ", + }, + +'LANGUAGE' => { + "Aari" => "ARI ", + "Abaza" => "ABA ", + "Abkhazian" => "ABK ", + "Adyghe" => "ADY ", + "Afar" => "AFR ", + "Afrikaans" => "AFK ", + "Agaw" => "AGW ", + "Albanian" => "SQI ", + "Alsatian" => "ALS ", + "Altai" => "ALT ", + "Amharic" => "AMH ", + "Arabic" => "ARA ", + "Arakanese" => "ARK ", + "Armenian" => "HYE ", + "Assamese" => "ASM ", + "Athapaskan" => "ATH ", + "Avar" => "AVR ", + "Awadhi" => "AWA ", + "Aymara" => "AYM ", + "Azeri" => "AZE ", + "Badaga" => "BAD ", + "Baghelkhandi" => "BAG ", + "Balante" => "BLN ", + "Balkar" => "BAL ", + "Balochi" => "BLI ", + "Balti" => "BLT ", + "Bambara" => "BMB ", + "Bamileke" => "BML ", + "Bashkir" => "BSH ", + "Basque" => "EUQ ", + "Baule" => "BAU ", + "Belarussian" => "BEL ", + "Bemba" => "BEM ", + "Bench" => "BCH ", + "Bengali" => "BEN ", + "Berber" => "BBR ", + "Beti" => "BTI ", + "Bhili" => "BHI ", + "Bhojpuri" => "BHO ", + "Bible Cree" => "BCR ", + "Bikol" => "BIK ", + "Bilen" => "BIL ", + "Blackfoot" => "BKF ", + "Bosnian" => "BOS ", + "Brahui" => "BRH ", + "Braj Bhasha" => "BRI ", + "Breton" => "BRE ", + "Bulgarian" => "BGR ", + "Burmese" => "BRM ", + "Carrier" => "CRR ", + "Catalan" => "CAT ", + "Cebuano" => "CEB ", + "Chaha Gurage" => "CHG ", + "Chattisgarhi" => "CHH ", + "Chechen" => "CHE ", + "Cherokee" => "CHR ", + "Chichewa" => "CHI ", + "Chin" => "QIN ", + "Chinese Hong Kong" => "ZHH ", + "Chinese Phonetic" => "ZHP ", + "Chinese Simplified" => "ZHS ", + "Chinese Traditional" => "ZHT ", + "Chipewyan" => "CHP ", + "Chukchi" => "CHK ", + "Church Slavonic" => "CSL ", + "Chuvash" => "CHU ", + "Comorian" => "CMR ", + "Coptic" => "COP ", + "Corsican" => "COS ", + "Cree" => "CRE ", + "Crimean Tatar" => "CRT ", + "Croatian" => "HRV ", + "Czech" => "CSY ", + "Dangme" => "DNG ", + "Danish" => "DAN ", + "Dargwa" => "DAR ", + "Dari" => "DRI ", + "Default" => "dflt", + "Dhivehi (OBSOLETE)" => "DHV ", + "Dhivehi" => "DIV ", + "Dinka" => "DNK ", + "Djerma" => "DJR ", + "Dogri" => "DGR ", + "Dungan" => "DUN ", + "Dutch" => "NLD ", + "Dzongkha" => "DZN ", + "Eastern Cree" => "ECR ", + "Ebira" => "EBI ", + "Edo" => "EDO ", + "Efik" => "EFI ", + "English" => "ENG ", + "Erzya" => "ERZ ", + "Esperanto" => "NTO ", + "Estonian" => "ETI ", + "Even" => "EVN ", + "Evenki" => "EVK ", + "Ewe" => "EWE ", + "Faroese" => "FOS ", + "Farsi" => "FAR ", + "Fijian" => "FJI ", + "Filipino" => "PIL ", + "Finnish" => "FIN ", + "Flemish" => "FLE ", + "Fon" => "FON ", + "Forest Nenets" => "FNE ", + "French Antillean" => "FAN ", + "French" => "FRA ", + "Frisian" => "FRI ", + "Friulian" => "FRL ", + "Fulani" => "FUL ", + "Futa" => "FTA ", + "Ga" => "GAD ", + "Gaelic" => "GAE ", + "Gagauz" => "GAG ", + "Galician" => "GAL ", + "Garhwali" => "GAW ", + "Garo" => "GRO ", + "Garshuni" => "GAR ", + "Ge'ez" => "GEZ ", + "Georgian" => "KAT ", + "German" => "DEU ", + "Gilyak" => "GIL ", + "Gondi" => "GON ", + "Greek" => "ELL ", + "Greenlandic" => "GRN ", + "Guarani" => "GUA ", + "Gujarati" => "GUJ ", + "Gumuz" => "GMZ ", + "Haitian" => "HAI ", + "Halam" => "HAL ", + "Hammer-Banna" => "HBN ", + "Harari" => "HRI ", + "Harauti" => "HAR ", + "Hausa" => "HAU ", + "Hawaiin" => "HAW ", + "Hebrew" => "IWR ", + "High Mari" => "HMA ", + "Hiligaynon" => "HIL ", + "Hindi" => "HIN ", + "Hindko" => "HND ", + "Ho" => "HO ", + "Hungarian" => "HUN ", + "Icelandic" => "ISL ", + "Igbo" => "IBO ", + "Ijo" => "IJO ", + "Ilokano" => "ILO ", + "Inari Sami" => "ISM ", + "Indonesian" => "IND ", + "Ingush" => "ING ", + "Inuktitut" => "INU ", + "Irish Traditional" => "IRT ", + "Irish" => "IRI ", + "Italian" => "ITA ", + "Japanese" => "JAN ", + "Javanese" => "JAV ", + "Judezmo" => "JUD ", + "Jula" => "JUL ", + "Kabardian" => "KAB ", + "Kachchi" => "KAC ", + "Kalenjin" => "KAL ", + "Kalmyk" => "KLM ", + "Kamba" => "KMB ", + "Kannada" => "KAN ", + "Kanuri" => "KNR ", + "Karachay" => "KAR ", + "Karaim" => "KRM ", + "Karakalpak" => "KRK ", + "Karelian" => "KRL ", + "Karen" => "KRN ", + "Kashmiri" => "KSH ", + "Kazakh" => "KAZ ", + "Kebena" => "KEB ", + "Khakass" => "KHA ", + "Khanty-Kazim" => "KHK ", + "Khanty-Shurishkar" => "KHS ", + "Khanty-Vakhi" => "KHV ", + "Khasi" => "KSI ", + "Khmer" => "KHM ", + "Khowar" => "KHW ", + "Khutsuri Georgian" => "KGE ", + "Kikongo" => "KON ", + "Kikuyu" => "KIK ", + "Kildin Sami" => "KSM ", + "Kirghiz" => "KIR ", + "Kisii" => "KIS ", + "Kodagu" => "KOD ", + "Kokni" => "KKN ", + "Komi-Permyak" => "KOP ", + "Komi-Zyrian" => "KOZ ", + "Komo" => "KMO ", + "Komso" => "KMS ", + "Konkani" => "KOK ", + "Koorete" => "KRT ", + "Korean Old Hangul" => "KOH ", + "Korean" => "KOR ", + "Koryak" => "KYK ", + "Kpelle" => "KPL ", + "Krio" => "KRI ", + "Kui" => "KUI ", + "Kulvi" => "KUL ", + "Kumaoni" => "KMN ", + "Kumyk" => "KUM ", + "Kurdish" => "KUR ", + "Kurukh" => "KUU ", + "Kuy" => "KUY ", + "L-Cree" => "LCR ", + "Ladakhi" => "LDK ", + "Ladin" => "LAD ", + "Lahuli" => "LAH ", + "Lak" => "LAK ", + "Lambani" => "LAM ", + "Lao" => "LAO ", + "Latin" => "LAT ", + "Latvian" => "LVI ", + "Laz" => "LAZ ", + "Lezgi" => "LEZ ", + "Limbu" => "LMB ", + "Lingala" => "LIN ", + "Lithuanian" => "LTH ", + "Lomwe" => "LMW ", + "Low Mari" => "LMA ", + "Lower Sorbian" => "LSB ", + "Luba" => "LUB ", + "Luganda" => "LUG ", + "Luhya" => "LUH ", + "Lule Sami" => "LSM ", + "Luo" => "LUO ", + "Luxembourgish" => "LTZ ", + "Macedonian" => "MKD ", + "Maithili" => "MTH ", + "Majang" => "MAJ ", + "Makua" => "MAK ", + "Malagasy" => "MLG ", + "Malay" => "MLY ", + "Malayalam Reformed" => "MLR ", + "Malayalam Traditional" => "MAL ", + "Male" => "MLE ", + "Malinke" => "MLN ", + "Maltese" => "MTS ", + "Manchu" => "MCH ", + "Mandinka" => "MND ", + "Maninka" => "MNK ", + "Manipuri" => "MNI ", + "Mansi" => "MAN ", + "Manx Gaelic" => "MNX ", + "Maori" => "MRI ", + "Mapudungun" => "MAP ", + "Marathi" => "MAR ", + "Marwari" => "MAW ", + "Mbundu" => "MBN ", + "Me'en" => "MEN ", + "Mende" => "MDE ", + "Mizo" => "MIZ ", + "Mohawk" => "MOH ", + "Moksha" => "MOK ", + "Moldavian" => "MOL ", + "Mon" => "MON ", + "Mongolian" => "MNG ", + "Moose Cree" => "MCR ", + "Moroccan" => "MOR ", + "Mundari" => "MUN ", + "N'Ko" => "NKO ", + "N-Cree" => "NCR ", + "Naga-Assamese" => "NAG ", + "Nagari" => "NGR ", + "Nanai" => "NAN ", + "Naskapi" => "NAS ", + "Ndebele" => "NDB ", + "Ndonga" => "NDG ", + "Nepali" => "NEP ", + "Newari" => "NEW ", + "Nisi" => "NIS ", + "Niuean" => "NIU ", + "Nkole" => "NKL ", + "Nogai" => "NOG ", + "Northern Sami" => "NSM ", + "Northern Tai" => "NTA ", + "Norway House Cree" => "NHC ", + "Norwegian" => "NOR ", + "Nynorsk" => "NYN ", + "Occitan" => "OCI ", + "Oji-Cree" => "OCR ", + "Ojibway" => "OJB ", + "Oriya" => "ORI ", + "Oromo" => "ORO ", + "Ossetian" => "OSS ", + "Palaung" => "PLG ", + "Palestinian Aramaic" => "PAA ", + "Pali" => "PAL ", + "Palpa" => "PAP ", + "Pashto" => "PAS ", + "Polish" => "PLK ", + "Polytonic Greek" => "PGR ", + "Portuguese" => "PTG ", + "Provencal" => "PRO ", + "Punjabi" => "PAN ", + "R-Cree" => "RCR ", + "Rajasthani" => "RAJ ", + "Rhaeto-Romanic" => "RMS ", + "Riang" => "RIA ", + "Romanian" => "ROM ", + "Romany" => "ROY ", + "Ruanda" => "RUA ", + "Russian Buriat" => "RBU ", + "Russian" => "RUS ", + "Rusyn" => "RSY ", + "Sadri" => "SAD ", + "Samoan" => "SMO ", + "Sango" => "SGO ", + "Sanskrit" => "SAN ", + "Santali" => "SAT ", + "Saraiki" => "SRK ", + "Sayisi" => "SAY ", + "Sekota" => "SEK ", + "Selkup" => "SEL ", + "Sena" => "SNA ", + "Serbian" => "SRB ", + "Serer" => "SRR ", + "Shan" => "SHN ", + "Sibe" => "SIB ", + "Sidamo" => "SID ", + "Silte Gurage" => "SIG ", + "Sindhi" => "SND ", + "Sinhalese" => "SNH ", + "Skolt Sami" => "SKS ", + "Slavey" => "SLA ", + "Slovak" => "SKY ", + "Slovenian" => "SLV ", + "Sodo Gurage" => "SOG ", + "Somali" => "SML ", + "Soninke" => "SNK ", + "Sotho" => "SOT ", + "South Slavey" => "SSL ", + "Southern Sami" => "SSM ", + "Spanish" => "ESP ", + "Suri" => "SUR ", + "Sutu" => "SXT ", + "Svan" => "SVA ", + "Swadaya Aramaic" => "SWA ", + "Swahili" => "SWK ", + "Swazi" => "SWZ ", + "Swedish" => "SVE ", + "Syriac" => "SYR ", + "TH-Cree" => "TCR ", + "Tabasaran" => "TAB ", + "Tahitian" => "THT ", + "Tai Lue" => "XBD ", + "Tajiki" => "TAJ ", + "Tamil" => "TAM ", + "Tatar" => "TAT ", + "Telugu" => "TEL ", + "Temne" => "TMN ", + "Thai" => "THA ", + "Tibetan" => "TIB ", + "Tigre" => "TGR ", + "Tigrinya" => "TGY ", + "Todo" => "TOD ", + "Tonga" => "TNG ", + "Tongan" => "TGN ", + "Tsonga" => "TSG ", + "Tswana" => "TNA ", + "Tulu" => "TUL ", + "Tundra Nenets" => "TNE ", + "Turkish" => "TRK ", + "Turkmen" => "TKM ", + "Turoyo Aramaic" => "TUA ", + "Tuvin" => "TUV ", + "Twi" => "TWI ", + "Udmurt" => "UDM ", + "Ukrainian" => "UKR ", + "Upper Sorbian" => "USB ", + "Urdu" => "URD ", + "Uyghur" => "UYG ", + "Uzbek" => "UZB ", + "Venda" => "VEN ", + "Vietnamese" => "VIT ", + "Wa" => "WA ", + "Wagdi" => "WAG ", + "Welsh" => "WEL ", + "West-Cree" => "WCR ", + "Wolof" => "WLF ", + "Woods Cree" => "DCR ", + "Xhosa" => "XHS ", + "Y-Cree" => "YCR ", + "Yakut" => "YAK ", + "Yi Classic" => "YIC ", + "Yi Modern" => "YIM ", + "Yiddish" => "JII ", + "Yoruba" => "YBA ", + "Zande" => "ZND ", + "Zulu" => "ZUL ", + }, + +'FEATURE' => { + "Above-Base Forms" => "abvf", + "Above-Base Mark Positioning" => "abvm", + "Above-Base Substitutions" => "abvs", + "Access All Alternates" => "aalt", + "Akhands" => "akhn", + "Alternate Annotation Forms" => "nalt", + "Alternate Half Widths" => "halt", + "Alternate Vertical Half Metrics" => "vhal", + "Alternate Vertical Metrics" => "valt", + "Alternative Fractions" => "afrc", + "Below-Base Forms" => "blwf", + "Below-Base Mark Positioning" => "blwm", + "Below-Base Substitutions" => "blws", + "Capital Spacing" => "cpsp", + "Case-Sensitive Forms" => "case", + "Centered CJK Punctuation" => "cpct", + "Conjunct Forms After Ro" => "cfar", + "Conjunct Forms" => "cjct", + "Contextual Alternates" => "calt", + "Contextual Ligatures" => "clig", + "Contextual Swash" => "cswh", + "Cursive Positioning" => "curs", + "Default Processing" => "dflt", + "Denominators" => "dnom", + "Diphthongs (OBSOLETE)" => "dpng", + "Discretionary Ligatures" => "dlig", + "Distances" => "dist", + "Expert Forms" => "expt", + "Final Glyph On Line Alternates" => "falt", + "Fractions" => "frac", + "Full Widths" => "fwid", + "Glyph Composition/Decomposition" => "ccmp", + "Halant Forms" => "haln", + "Half Forms" => "half", + "Half Widths" => "hwid", + "Hangul" => "hngl", + "Historical Forms" => "hist", + "Historical Ligatures" => "hlig", + "Hojo (JIS X 0212-1990) Kanji Forms" => "hojo", + "Horizontal Kana Alternates" => "hkna", + "Initial Forms" => "init", + "Isolated Forms" => "isol", + "Italics" => "ital", + "JIS2004 Forms" => "jp04", + "JIS78 Forms" => "jp78", + "JIS83 Forms" => "jp83", + "JIS90 Forms" => "jp90", + "Justification Alternates" => "jalt", + "Kerning" => "kern", + "Leading Jamo Forms" => "ljmo", + "Left Bounds" => "lfbd", + "Lining Figures" => "lnum", + "Localized Forms" => "locl", + "Mark Positioning via Substitution" => "mset", + "Mark Positioning" => "mark", + "Mark to Mark Positioning" => "mkmk", + "Mathematical Greek" => "mgrk", + "Medial Forms #2" => "med2", + "Medial Forms" => "medi", + "NLC Kanji Forms" => "nlck", + "Nukta Forms" => "nukt", + "Numerators" => "numr", + "Oldstyle Figures" => "onum", + "Optical Bounds" => "opbd", + "Optical Size" => "size", + "Ordinals" => "ordn", + "Ornaments" => "ornm", + "Petite Capitals From Capitals" => "c2pc", + "Petite Capitals" => "pcap", + "Post-base Forms" => "pstf", + "Post-base Substitutions" => "psts", + "Pre-base Forms" => "pref", + "Pre-base Substitutions" => "pres", + "Proportional Alternate Vertical Metrics" => "vpal", + "Proportional Alternate Widths" => "palt", + "Proportional Figures" => "pnum", + "Proportional Kana" => "pkna", + "Proportional Widths" => "pwid", + "Quarter Widths" => "qwid", + "Rakar Forms" => "rkrf", + "Randomize" => "rand", + "Reph Forms" => "rphf", + "Required Ligatures" => "rlig", + "Right Bounds" => "rtbd", + "Right-To-Left Alternates" => "rtla", + "Ruby Notation Forms" => "ruby", + "Scientific Inferiors" => "sinf", + "Simplified Forms" => "smpl", + "Slashed Zero" => "zero", + "Small Capitals From Capitals" => "c2sc", + "Small Capitals" => "smcp", + "Standard Ligatures" => "liga", + "Stylistic Alternates" => "salt", + "Stylistic Set 1" => "ss01", + "Stylistic Set 10" => "ss10", + "Stylistic Set 11" => "ss11", + "Stylistic Set 12" => "ss12", + "Stylistic Set 13" => "ss13", + "Stylistic Set 14" => "ss14", + "Stylistic Set 15" => "ss15", + "Stylistic Set 16" => "ss16", + "Stylistic Set 17" => "ss17", + "Stylistic Set 18" => "ss18", + "Stylistic Set 19" => "ss19", + "Stylistic Set 2" => "ss02", + "Stylistic Set 20" => "ss20", + "Stylistic Set 3" => "ss03", + "Stylistic Set 4" => "ss04", + "Stylistic Set 5" => "ss05", + "Stylistic Set 6" => "ss06", + "Stylistic Set 7" => "ss07", + "Stylistic Set 8" => "ss08", + "Stylistic Set 9" => "ss09", + "Subscript" => "subs", + "Superscript" => "sups", + "Swash" => "swsh", + "Tabular Figures" => "tnum", + "Terminal Forms #2" => "fin2", + "Terminal Forms #3" => "fin3", + "Terminal Forms" => "fina", + "Third Widths" => "twid", + "Titling" => "titl", + "Traditional Forms" => "trad", + "Traditional Name Forms" => "tnam", + "Trailing Jamo Forms" => "tjmo", + "Unicase" => "unic", + "Vattu Variants" => "vatu", + "Vertical Alternates and Rotation" => "vrt2", + "Vertical Kana Alternates" => "vkna", + "Vertical Kerning" => "vkrn", + "Vertical Writing" => "vert", + "Vowel Jamo Forms" => "vjmo", + } +); + +{ + foreach my $s (qw ( SCRIPT LANGUAGE FEATURE ) ) + { + map { $ttnames{$s}{$tttags{$s}{$_}} = $_ } keys %{$tttags{$s}}; + } +} + + +=head2 readtagsfile ( filename ) + +Read a file in the syntax of Tags.txt (included with Microsoft VOLT) to obtain additional/replacement tag definitions. + +Returns 0 if can't open the file; else 1. + +=cut + +sub readtagsfile +{ + my $fname = shift; + open (TAGS, $fname) or return 0; + my ($what, $name, $tag); + while () + { + ($what, $name, $tag) = (m/"([^"]*)", "([^"]*)", "([^"]*)"/); #" + $ttnames{$what}{$tag} = $name; + $tttags{$what}{$name} = $tag; + } + close TAGS; + return 1; +} + + + +1; diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldCmap.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldCmap.pm new file mode 100644 index 0000000..96545e2 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldCmap.pm @@ -0,0 +1,358 @@ +package Font::TTF::OldCmap; + +=head1 NAME + +Font::TTF::OldCmap - Character map table + +This module is deprecated + +=head1 DESCRIPTION + +Looks after the character map. The primary structure used for handling a cmap +is the L which handles the segmented arrays of format 4 tables, +and in a simpler form for format 0 tables. + +Due to the complexity of working with segmented arrays, most of the handling of +such arrays is via methods rather than via instance variables. + +One important feature of a format 4 table is that it always contains a segment +with a final address of 0xFFFF. If you are creating a table from scratch this is +important (although L can work quite happily without it). + + +=head1 INSTANCE VARIABLES + +The instance variables listed here are not preceeded by a space due to their +emulating structural information in the font. + +=over 4 + +=item Num + +Number of subtables in this table + +=item Tables + +An array of subtables ([0..Num-1]) + +=back + +Each subtables also has its own instance variables which are, again, not +preceeded by a space. + +=over 4 + +=item Platform + +The platform number for this subtable + +=item Encoding + +The encoding number for this subtable + +=item Format + +Gives the stored format of this subtable + +=item Ver + +Gives the version (or language) information for this subtable + +=item val + +This points to a L which contains the content of the particular +subtable. + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +require Font::TTF::Table; +require Font::TTF::Segarr; + +@ISA = qw(Font::TTF::Table); + + +=head2 $t->read + +Reads the cmap into memory. Format 4 subtables read the whole subtable and +fill in the segmented array accordingly. + +Format 2 subtables are not read at all. + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $i, $j, $k, $id, @ids, $s); + my ($start, $end, $range, $delta, $form, $len, $num, $ver); + my ($fh) = $self->{' INFILE'}; + + $self->SUPER::read or return $self; + $fh->read($dat, 4); + $self->{'Num'} = unpack("x2n", $dat); + $self->{'Tables'} = []; + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = {}; + $fh->read($dat, 8); + ($s->{'Platform'}, $s->{'Encoding'}, $s->{'LOC'}) = (unpack("nnN", $dat)); + $s->{'LOC'} += $self->{' OFFSET'}; + push(@{$self->{'Tables'}}, $s); + } + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + $fh->seek($s->{'LOC'}, 0); + $fh->read($dat, 6); + ($form, $len, $ver) = (unpack("n3", $dat)); + + $s->{'Format'} = $form; + $s->{'Ver'} = $ver; + if ($form == 0) + { + $s->{'val'} = Font::TTF::Segarr->new; + $fh->read($dat, 256); + $s->{'val'}->fastadd_segment(0, 2, unpack("C*", $dat)); + $s->{'Start'} = 0; + $s->{'Num'} = 256; + } elsif ($form == 6) + { + my ($start, $ecount); + + $fh->read($dat, 4); + ($start, $ecount) = unpack("n2", $dat); + $fh->read($dat, $ecount << 1); + $s->{'val'} = Font::TTF::Segarr->new; + $s->{'val'}->fastadd_segment($start, 2, unpack("n*", $dat)); + $s->{'Start'} = $start; + $s->{'Num'} = $ecount; + } elsif ($form == 2) + { +# no idea what to do here yet + } elsif ($form == 4) + { + $fh->read($dat, 8); + $num = unpack("n", $dat); + $num >>= 1; + $fh->read($dat, $len - 14); + $s->{'val'} = Font::TTF::Segarr->new; + for ($j = 0; $j < $num; $j++) + { + $end = unpack("n", substr($dat, $j << 1, 2)); + $start = unpack("n", substr($dat, ($j << 1) + ($num << 1) + 2, 2)); + $delta = unpack("n", substr($dat, ($j << 1) + ($num << 2) + 2, 2)); + $delta -= 65536 if $delta > 32767; + $range = unpack("n", substr($dat, ($j << 1) + $num * 6 + 2, 2)); + @ids = (); + for ($k = $start; $k <= $end; $k++) + { + if ($range == 0) + { $id = $k + $delta; } + else + { $id = unpack("n", substr($dat, ($j << 1) + $num * 6 + + 2 + ($k - $start) * 2 + $range, 2)) + $delta; } + $id -= 65536 if $id > 65536; + push (@ids, $id); + } + $s->{'val'}->fastadd_segment($start, 0, @ids); + } + $s->{'val'}->tidy; + $s->{'Num'} = 0x10000; # always ends here + $s->{'Start'} = $s->{'val'}[0]{'START'}; + } + } + $self; +} + + +=head2 $t->ms_lookup($uni) + +Given a Unicode value in the MS table (Platform 3, Encoding 1) locates that +table and looks up the appropriate glyph number from it. + +=cut + +sub ms_lookup +{ + my ($self, $uni) = @_; + + $self->find_ms || return undef unless (defined $self->{' mstable'}); + return $self->{' mstable'}{'val'}->at($uni); +} + + +=head2 $t->find_ms + +Finds the Microsoft Unicode table and sets the C instance variable +to it if found. Returns the table it finds. + +=cut +sub find_ms +{ + my ($self) = @_; + my ($i, $s, $alt); + + return $self->{' mstable'} if defined $self->{' mstable'}; + $self->read; + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + if ($s->{'Platform'} == 3) + { + $self->{' mstable'} = $s; + last if ($s->{'Encoding'} == 1); + } elsif ($s->{'Platform'} == 0 || ($s->{'Platform'} == 2 && $s->{'Encoding'} == 1)) + { $self->{' mstable'} = $s; } + } + $self->{' mstable'}; +} + + +=head2 $t->out($fh) + +Writes out a cmap table to a filehandle. If it has not been read, then +just copies from input file to output + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($loc, $s, $i, $base_loc, $j); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $base_loc = $fh->tell(); + $fh->print(pack("n2", 0, $self->{'Num'})); + + for ($i = 0; $i < $self->{'Num'}; $i++) + { $fh->print(pack("nnN", $self->{'Tables'}[$i]{'Platform'}, $self->{'Tables'}[$i]{'Encoding'}, 0)); } + + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $s = $self->{'Tables'}[$i]; + $s->{'val'}->tidy; + $s->{' outloc'} = $fh->tell(); + $fh->print(pack("n3", $s->{'Format'}, 0, $s->{'Ver'})); # come back for length + if ($s->{'Format'} == 0) + { + $fh->print(pack("C256", $s->{'val'}->at(0, 256))); + } elsif ($s->{'Format'} == 6) + { + $fh->print(pack("n2", $s->{'Start'}, $s->{'Num'})); + $fh->print(pack("n*", $s->{'val'}->at($s->{'Start'}, $s->{'Num'}))); + } elsif ($s->{'Format'} == 2) + { + } elsif ($s->{'Format'} == 4) + { + my ($num, $sRange, $eSel); + my (@deltas, $delta, @range, $flat, $k, $segs, $count); + + $num = $#{$s->{'val'}} + 1; + $segs = $s->{'val'}; + for ($sRange = 1, $eSel = 0; $sRange <= $num; $eSel++) + { $sRange <<= 1;} + $eSel--; + $fh->print(pack("n4", $num * 2, $sRange, $eSel, ($num * 2) - $sRange)); + $fh->print(pack("n*", map {$_->{'START'} + $_->{'LEN'} - 1} @$segs)); + $fh->print(pack("n", 0)); + $fh->print(pack("n*", map {$_->{'START'}} @$segs)); + + for ($j = 0; $j < $num; $j++) + { + $delta = $segs->[$j]{'VAL'}[0]; $flat = 1; + for ($k = 1; $k < $segs->[$j]{'LEN'}; $k++) + { + if ($segs->[$j]{'VAL'}[$k] == 0) + { $flat = 0; } + if ($delta + $k != $segs->[$j]{'VAL'}[$k]) + { + $delta = 0; + last; + } + } + push (@range, $flat); + push (@deltas, ($delta ? $delta - $segs->[$j]{'START'} : 0)); + } + $fh->print(pack("n*", @deltas)); + + $count = 0; + for ($j = 0; $j < $num; $j++) + { + $delta = $deltas[$j]; + if ($delta != 0 && $range[$j] == 1) + { $range[$j] = 0; } + else + { + $range[$j] = ($count + $num - $j) << 1; + $count += $segs->[$j]{'LEN'}; + } + } + + $fh->print(pack("n*", @range)); + + for ($j = 0; $j < $num; $j++) + { + next if ($range[$j] == 0); + for ($k = 0; $k < $segs->[$j]{'LEN'}; $k++) + { $fh->print(pack("n", $segs->[$j]{'VAL'}[$k])); } + } + } + + $loc = $fh->tell(); + $fh->seek($s->{' outloc'} + 2, 0); + $fh->print(pack("n", $loc - $s->{' outloc'})); + $fh->seek($base_loc + 8 + ($i << 3), 0); + $fh->print(pack("N", $s->{' outloc'} - $base_loc)); + $fh->seek($loc, 0); + } + $self; +} + + +=head2 @map = $t->reverse([$num]) + +Returns a reverse map of the table of given number or the Microsoft +cmap. I.e. given a glyph gives the Unicode value for it. + +=cut + +sub reverse +{ + my ($self, $tnum) = @_; + my ($table) = defined $tnum ? $self->{'Tables'}[$tnum] : $self->find_ms; + my (@res, $i, $s, $first); + + foreach $s (@{$table->{'val'}}) + { + $first = $s->{'START'}; + map {$res[$_] = $first unless $res[$_]; $first++;} @{$s->{'VAL'}}; + } + @res; +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +No support for format 2 tables (MBCS) + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldMort.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldMort.pm new file mode 100644 index 0000000..4e30f42 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/OldMort.pm @@ -0,0 +1,706 @@ +package Font::TTF::OldMort; + +=head1 NAME + +Font::TTF::OldMort - Glyph Metamorphosis table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=item version + +table version number (Fixed: currently 1.0) + +=item chains + +list of metamorphosis chains, each of which has its own fields: + +=over + +=item defaultFlags + +chain's default subfeature flags (UInt32) + +=item featureEntries + +list of feature entries, each of which has fields: + +=over + +=item type + +=item setting + +=item enable + +=item disable + +=back + +=item subtables + +list of metamorphosis subtables, each of which has fields: + +=over + +=item type + +subtable type (0: rearrangement; 1: contextual substitution; 2: ligature; +4: non-contextual substitution; 5: insertion) + +=item direction + +processing direction ('LR' or 'RL') + +=item orientation + +applies to text in which orientation ('VH', 'V', or 'H') + +=item subFeatureFlags + +the subfeature flags controlling whether the table is used (UInt32) + +=back + +Further fields depend on the type of subtable: + +=over + +Rearrangement table: + +=over + +=item classes + +array of lists of glyphs + +=item states + +array of arrays of hashes{'nextState', 'flags'} + +=back + +Contextual substitution table: + +=over + +=item classes + +array of lists of glyphs + +=item states + +array of array of hashes{'nextState', 'flags', 'actions'}, where C +is an array of two elements which are offsets to be added to [marked, current] +glyph to get index into C (or C if no mapping to be applied) + +=item mappings + +list of glyph codes mapped to through the state table mappings + +=back + +Ligature table: + +Non-contextual substitution table: + +Insertion table: + +=back + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use IO::File; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $fh, $numChains); + + $self->SUPER::read or return $self; + + $fh = $self->{' INFILE'}; + + $fh->read($dat, 8); + ($self->{'version'}, $numChains) = TTF_Unpack("fL", $dat); + + my $chains = []; + foreach (1 .. $numChains) { + my $chainStart = $fh->tell(); + $fh->read($dat, 12); + my ($defaultFlags, $chainLength, $nFeatureEntries, $nSubtables) = TTF_Unpack("LLSS", $dat); + my $featureEntries = []; + foreach (1 .. $nFeatureEntries) { + $fh->read($dat, 12); + my ($featureType, $featureSetting, $enableFlags, $disableFlags) = TTF_Unpack("SSLL", $dat); + push @$featureEntries, { + 'type' => $featureType, + 'setting' => $featureSetting, + 'enable' => $enableFlags, + 'disable' => $disableFlags + }; + } + my $subtables = []; + foreach (1 .. $nSubtables) { + my $subtableStart = $fh->tell(); + $fh->read($dat, 8); + my ($length, $coverage, $subFeatureFlags) = TTF_Unpack("SSL", $dat); + my $type = $coverage & 0x0007; + + my $subtable = { + 'type' => $type, + 'direction' => (($coverage & 0x4000) ? 'RL' : 'LR'), + 'orientation' => (($coverage & 0x2000) ? 'VH' : ($coverage & 0x8000) ? 'V' : 'H'), + 'subFeatureFlags' => $subFeatureFlags + }; + + if ($type == 0) { # rearrangement + my ($classes, $states) = AAT_read_state_table($fh, 0); + $subtable->{'classes'} = $classes; + $subtable->{'states'} = $states; + } + + elsif ($type == 1) { # contextual + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 2); + + $fh->seek($stateTableStart, IO::File::SEEK_SET); + $fh->read($dat, 10); + my ($stateSize, $classTable, $stateArray, $entryTable, $mappingTables) = unpack("nnnnn", $dat); + my $limits = [$classTable, $stateArray, $entryTable, $mappingTables, $length - 8]; + + foreach (@$entries) { + my $actions = $_->{'actions'}; + foreach (@$actions) { + $_ = $_ ? $_ - ($mappingTables / 2) : undef; + } + } + + $subtable->{'classes'} = $classes; + $subtable->{'states'} = $states; + $subtable->{'mappings'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $mappingTables, $limits))]; + } + + elsif ($type == 2) { # ligature + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 0); + + $fh->seek($stateTableStart, IO::File::SEEK_SET); + $fh->read($dat, 14); + my ($stateSize, $classTable, $stateArray, $entryTable, + $ligActionTable, $componentTable, $ligatureTable) = unpack("nnnnnnn", $dat); + my $limits = [$classTable, $stateArray, $entryTable, $ligActionTable, $componentTable, $ligatureTable, $length - 8]; + + my %actions; + my $actionLists; + foreach (@$entries) { + my $offset = $_->{'flags'} & 0x3fff; + $_->{'flags'} &= ~0x3fff; + if ($offset != 0) { + if (not defined $actions{$offset}) { + $fh->seek($stateTableStart + $offset, IO::File::SEEK_SET); + my $actionList; + while (1) { + $fh->read($dat, 4); + my $action = unpack("N", $dat); + my ($last, $store, $component) = (($action & 0x80000000) != 0, ($action & 0xC0000000) != 0, ($action & 0x3fffffff)); + $component -= 0x40000000 if $component > 0x1fffffff; + $component -= $componentTable / 2; + push @$actionList, { 'store' => $store, 'component' => $component }; + last if $last; + } + push @$actionLists, $actionList; + $actions{$offset} = $#$actionLists; + } + $_->{'actions'} = $actions{$offset}; + } + } + + $subtable->{'componentTable'} = $componentTable; + my $components = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $componentTable, $limits))]; + foreach (@$components) { + $_ = ($_ - $ligatureTable) . " +" if $_ >= $ligatureTable; + } + $subtable->{'components'} = $components; + + $subtable->{'ligatureTable'} = $ligatureTable; + $subtable->{'ligatures'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $ligatureTable, $limits))]; + + $subtable->{'classes'} = $classes; + $subtable->{'states'} = $states; + $subtable->{'actionLists'} = $actionLists; + } + + elsif ($type == 4) { # non-contextual + my ($format, $lookup) = AAT_read_lookup($fh, 2, $length - 8, undef); + $subtable->{'format'} = $format; + $subtable->{'lookup'} = $lookup; + } + + elsif ($type == 5) { # insertion + my $stateTableStart = $fh->tell(); + my ($classes, $states, $entries) = AAT_read_state_table($fh, 2); + + my %insertListHash; + my $insertLists; + foreach (@$entries) { + my $flags = $_->{'flags'}; + my @insertCount = (($flags & 0x03e0) >> 5, ($flags & 0x001f)); + my $actions = $_->{'actions'}; + foreach (0 .. 1) { + if ($insertCount[$_] > 0) { + $fh->seek($stateTableStart + $actions->[$_], IO::File::SEEK_SET); + $fh->read($dat, $insertCount[$_] * 2); + if (not defined $insertListHash{$dat}) { + push @$insertLists, [unpack("n*", $dat)]; + $insertListHash{$dat} = $#$insertLists; + } + $actions->[$_] = $insertListHash{$dat}; + } + else { + $actions->[$_] = undef; + } + } + } + + $subtable->{'classes'} = $classes; + $subtable->{'states'} = $states; + $subtable->{'insertLists'} = $insertLists; + } + + else { + die "unknown subtable type"; + } + + push @$subtables, $subtable; + $fh->seek($subtableStart + $length, IO::File::SEEK_SET); + } + + push @$chains, { + 'defaultFlags' => $defaultFlags, + 'featureEntries' => $featureEntries, + 'subtables' => $subtables + }; + $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET); + } + + $self->{'chains'} = $chains; + + $self; +} + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + my $chains = $self->{'chains'}; + $fh->print(TTF_Pack("fL", $self->{'version'}, scalar @$chains)); + + foreach (@$chains) { + my $chainStart = $fh->tell(); + my ($featureEntries, $subtables) = ($_->{'featureEntries'}, $_->{'subtables'}); + $fh->print(TTF_Pack("LLSS", $_->{'defaultFlags'}, 0, scalar @$featureEntries, scalar @$subtables)); # placeholder for length + + foreach (@$featureEntries) { + $fh->print(TTF_Pack("SSLL", $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'})); + } + + foreach (@$subtables) { + my $subtableStart = $fh->tell(); + my $type = $_->{'type'}; + my $coverage = $type; + $coverage += 0x4000 if $_->{'direction'} eq 'RL'; + $coverage += 0x2000 if $_->{'orientation'} eq 'VH'; + $coverage += 0x8000 if $_->{'orientation'} eq 'V'; + + $fh->print(TTF_Pack("SSL", 0, $coverage, $_->{'subFeatureFlags'})); # placeholder for length + + if ($type == 0) { # rearrangement + AAT_write_state_table($fh, $_->{'classes'}, $_->{'states'}, 0); + } + + elsif ($type == 1) { # contextual + my $stHeader = $fh->tell(); + $fh->print(pack("nnnnn", (0) x 5)); # placeholders for stateSize, classTable, stateArray, entryTable, mappingTables + + my $classTable = $fh->tell() - $stHeader; + my $classes = $_->{'classes'}; + AAT_write_classes($fh, $classes); + + my $stateArray = $fh->tell() - $stHeader; + my $states = $_->{'states'}; + my ($stateSize, $entries) = AAT_write_states($fh, $classes, $stateArray, $states, + sub { + my $actions = $_->{'actions'}; + ( $_->{'flags'}, @$actions ) + } + ); + + my $entryTable = $fh->tell() - $stHeader; + my $offset = ($entryTable + 8 * @$entries) / 2; + foreach (@$entries) { + my ($nextState, $flags, @parts) = split /,/; + $fh->print(pack("nnnn", $nextState, $flags, map { $_ eq "" ? 0 : $_ + $offset } @parts)); + } + + my $mappingTables = $fh->tell() - $stHeader; + my $mappings = $_->{'mappings'}; + $fh->print(pack("n*", @$mappings)); + + my $loc = $fh->tell(); + $fh->seek($stHeader, IO::File::SEEK_SET); + $fh->print(pack("nnnnn", $stateSize, $classTable, $stateArray, $entryTable, $mappingTables)); + $fh->seek($loc, IO::File::SEEK_SET); + } + + elsif ($type == 2) { # ligature + my $stHeader = $fh->tell(); + $fh->print(pack("nnnnnnn", (0) x 7)); # placeholders for stateSize, classTable, stateArray, entryTable, actionLists, components, ligatures + + my $classTable = $fh->tell() - $stHeader; + my $classes = $_->{'classes'}; + AAT_write_classes($fh, $classes); + + my $stateArray = $fh->tell() - $stHeader; + my $states = $_->{'states'}; + + my ($stateSize, $entries) = AAT_write_states($fh, $classes, $stateArray, $states, + sub { + ( $_->{'flags'} & 0xc000, $_->{'actions'} ) + } + ); + + my $actionLists = $_->{'actionLists'}; + my %actionListOffset; + my $actionListDataLength = 0; + my @actionListEntries; + foreach (0 .. $#$entries) { + my ($nextState, $flags, $offset) = split(/,/, $entries->[$_]); + if ($offset eq "") { + $offset = undef; + } + else { + if (defined $actionListOffset{$offset}) { + $offset = $actionListOffset{$offset}; + } + else { + $actionListOffset{$offset} = $actionListDataLength; + my $list = $actionLists->[$offset]; + $actionListDataLength += 4 * @$list; + push @actionListEntries, $list; + $offset = $actionListOffset{$offset}; + } + } + $entries->[$_] = [ $nextState, $flags, $offset ]; + } + my $entryTable = $fh->tell() - $stHeader; + my $ligActionLists = ($entryTable + @$entries * 4 + 3) & ~3; + foreach (@$entries) { + $_->[2] += $ligActionLists if defined $_->[2]; + $fh->print(pack("nn", $_->[0], $_->[1] + $_->[2])); + } + $fh->print(pack("C*", (0) x ($ligActionLists - $entryTable - @$entries * 4))); + + die "internal error" if $fh->tell() != $ligActionLists + $stHeader; + + my $componentTable = $fh->tell() - $stHeader + $actionListDataLength; + my $actionList; + foreach $actionList (@actionListEntries) { + foreach (0 .. $#$actionList) { + my $action = $actionList->[$_]; + my $val = $action->{'component'} + $componentTable / 2; + $val += 0x40000000 if $val < 0; + $val &= 0x3fffffff; + $val |= 0x40000000 if $action->{'store'}; + $val |= 0x80000000 if $_ == $#$actionList; + $fh->print(pack("N", $val)); + } + } + + die "internal error" if $fh->tell() != $componentTable + $stHeader; + + my $components = $_->{'components'}; + my $ligatureTable = $componentTable + @$components * 2; + $fh->print(pack("n*", map { (index($_, '+') >= 0 ? $ligatureTable : 0) + $_ } @$components)); + + my $ligatures = $_->{'ligatures'}; + $fh->print(pack("n*", @$ligatures)); + + my $loc = $fh->tell(); + $fh->seek($stHeader, IO::File::SEEK_SET); + $fh->print(pack("nnnnnnn", $stateSize, $classTable, $stateArray, $entryTable, $ligActionLists, $componentTable, $ligatureTable)); + $fh->seek($loc, IO::File::SEEK_SET); + } + + elsif ($type == 4) { # non-contextual + AAT_write_lookup($fh, $_->{'format'}, $_->{'lookup'}, 2, undef); + } + + elsif ($type == 5) { # insertion + } + + else { + die "unknown subtable type"; + } + + my $length = $fh->tell() - $subtableStart; + my $padBytes = (4 - ($length & 3)) & 3; + $fh->print(pack("C*", (0) x $padBytes)); + $length += $padBytes; + $fh->seek($subtableStart, IO::File::SEEK_SET); + $fh->print(pack("n", $length)); + $fh->seek($subtableStart + $length, IO::File::SEEK_SET); + } + + my $chainLength = $fh->tell() - $chainStart; + $fh->seek($chainStart + 4, IO::File::SEEK_SET); + $fh->print(pack("N", $chainLength)); + $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET); + } +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + + $self->read; + my $feat = $self->{' PARENT'}->{'feat'}; + $feat->read; + my $post = $self->{' PARENT'}->{'post'}; + $post->read; + + $fh = 'STDOUT' unless defined $fh; + + $fh->printf("version %f\n", $self->{'version'}); + + my $chains = $self->{'chains'}; + foreach (@$chains) { + my $defaultFlags = $_->{'defaultFlags'}; + $fh->printf("chain: defaultFlags = %08x\n", $defaultFlags); + + my $featureEntries = $_->{'featureEntries'}; + foreach (@$featureEntries) { + $fh->printf("\tfeature %d, setting %d : enableFlags = %08x, disableFlags = %08x # '%s: %s'\n", + $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'}, + $feat->settingName($_->{'type'}, $_->{'setting'})); + } + + my $subtables = $_->{'subtables'}; + foreach (@$subtables) { + my $type = $_->{'type'}; + my $subFeatureFlags = $_->{'subFeatureFlags'}; + $fh->printf("\n\t%s table, %s, %s, subFeatureFlags = %08x # %s (%s)\n", + subtable_type_($type), $_->{'direction'}, $_->{'orientation'}, $subFeatureFlags, + "Default " . ((($subFeatureFlags & $defaultFlags) != 0) ? "On" : "Off"), + join(", ", + map { + join(": ", $feat->settingName($_->{'type'}, $_->{'setting'}) ) + } grep { ($_->{'enable'} & $subFeatureFlags) != 0 } @$featureEntries + ) ); + + if ($type == 0) { # rearrangement + print_classes_($fh, $_, $post); + + $fh->print("\n"); + my $states = $_->{'states'}; + my @verbs = ( "0", "Ax->xA", "xD->Dx", "AxD->DxA", + "ABx->xAB", "ABx->xBA", "xCD->CDx", "xCD->DCx", + "AxCD->CDxA", "AxCD->DCxA", "ABxD->DxAB", "ABxD->DxBA", + "ABxCD->CDxAB", "ABxCD->CDxBA", "ABxCD->DCxAB", "ABxCD->DCxBA"); + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "<" if ($_->{'flags'} & 0x8000); + $flags .= ">" if ($_->{'flags'} & 0x2000); + $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, $verbs[($_->{'flags'} & 0x000f)]); + } + $fh->print("\n"); + } + } + + elsif ($type == 1) { # contextual + print_classes_($fh, $_, $post); + + $fh->print("\n"); + my $states = $_->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + my $actions = $_->{'actions'}; + $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $mappings = $_->{'mappings'}; + foreach (0 .. $#$mappings) { + $fh->printf("\t\tMapping %d: %d [%s]\n", $_, $mappings->[$_], $post->{'VAL'}[$mappings->[$_]]); + } + } + + elsif ($type == 2) { # ligature + print_classes_($fh, $_, $post); + + $fh->print("\n"); + my $states = $_->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, defined $_->{'actions'} ? $_->{'actions'} : "="); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $actionLists = $_->{'actionLists'}; + foreach (0 .. $#$actionLists) { + $fh->printf("\t\tList %d:\t", $_); + my $actionList = $actionLists->[$_]; + $fh->printf("%s\n", join(", ", map { ($_->{'component'} . ($_->{'store'} ? "*" : "") ) } @$actionList)); + } + + my $ligatureTable = $_->{'ligatureTable'}; + + $fh->print("\n"); + my $components = $_->{'components'}; + foreach (0 .. $#$components) { + $fh->printf("\t\tComponent %d: %s\n", $_, $components->[$_]); + } + + $fh->print("\n"); + my $ligatures = $_->{'ligatures'}; + foreach (0 .. $#$ligatures) { + $fh->printf("\t\tLigature %d: %d [%s]\n", $_, $ligatures->[$_], $post->{'VAL'}[$ligatures->[$_]]); + } + } + + elsif ($type == 4) { # non-contextual + my $lookup = $_->{'lookup'}; + $fh->printf("\t\tLookup format %d\n", $_->{'format'}); + if (defined $lookup) { + foreach (sort { $a <=> $b } keys %$lookup) { + $fh->printf("\t\t\t%d [%s] -> %d [%s])\n", $_, $post->{'VAL'}[$_], $lookup->{$_}, $post->{'VAL'}[$lookup->{$_}]); + } + } + } + + elsif ($type == 5) { # insertion + print_classes_($fh, $_, $post); + + $fh->print("\n"); + my $states = $_->{'states'}; + foreach (0 .. $#$states) { + $fh->printf("\t\tState %d:", $_); + my $state = $states->[$_]; + foreach (@$state) { + my $flags; + $flags .= "!" if ($_->{'flags'} & 0x4000); + $flags .= "*" if ($_->{'flags'} & 0x8000); + my $actions = $_->{'actions'}; + $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions); + } + $fh->print("\n"); + } + + $fh->print("\n"); + my $insertLists = $_->{'insertLists'}; + foreach (0 .. $#$insertLists) { + my $insertList = $insertLists->[$_]; + $fh->printf("\t\tList %d: %s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$insertList)); + } + } + + else { + # unknown + } + } + } +} + +sub print_classes_ +{ + my ($fh, $subtable, $post) = @_; + + my $classes = $subtable->{'classes'}; + foreach (0 .. $#$classes) { + my $class = $classes->[$_]; + if (defined $class) { + $fh->printf("\t\tClass %d:\t%s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$class)); + } + } +} + +sub subtable_type_ +{ + my ($val) = @_; + my ($res); + + my @types = ( + 'Rearrangement', + 'Contextual', + 'Ligature', + undef, + 'Non-contextual', + 'Insertion', + ); + $res = $types[$val] or ('Undefined (' . $val . ')'); + + $res; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/PCLT.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/PCLT.pm new file mode 100644 index 0000000..2856c16 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/PCLT.pm @@ -0,0 +1,121 @@ +package Font::TTF::PCLT; + +=head1 NAME + +Font::TTF::PCLT - PCLT TrueType font table + +=head1 DESCRIPTION + +The PCLT table holds various pieces HP-PCL specific information. Information +here is generally not used by other software, except for the xHeight and +CapHeight which are stored here (if the table exists in a font). + +=head1 INSTANCE VARIABLES + +Only from table and the standard: + + version + FontNumber + Pitch + xHeight + Style + TypeFamily + CapHeight + SymbolSet + Typeface + CharacterComplement + FileName + StrokeWeight + WidthType + SerifStyle + +Notice that C, C and C return arrays +of unsigned characters of the appropriate length + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'version' => 'v', + 'FontNumber' => 'L', + 'Pitch' => 'S', + 'xHeight' => 'S', + 'Style' => 'S', + 'TypeFamily' => 'S', + 'CapHeight' => 'S', + 'SymbolSet' => 'S', + 'Typeface' => 'C16', + 'CharacterComplement' => 'C8', + 'FileName' => 'C6', + 'StrokeWeight' => 'C', + 'WidthType' => 'C', + 'SerifStyle' => 'c'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory thanks to some utility functions + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read || return $self; + + init unless defined $fields{'xHeight'}; + $self->{' INFILE'}->read($dat, 54); + + TTF_Read_Fields($self, $dat, \%fields); + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + $fh->print(TTF_Out_Fields($self, \%fields, 54)); +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/PSNames.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/PSNames.pm new file mode 100644 index 0000000..cb4c73b --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/PSNames.pm @@ -0,0 +1,4447 @@ +package Font::TTF::PSNames; + +=head1 NAME + +Font::TTF::PSNames - Utilities for Postscript glyph name processing + +=head1 SYNOPSIS + + use Font::TTF::PSNames qw(parse lookup); + $name = lookup($uni); + $uni = parse($name); + +=head1 METHODS + +=cut + +use strict; +use vars qw(%names %agl @EXPORT_OK @ISA); +require Exporter; +@ISA = qw( Exporter ); +@EXPORT_OK = qw( parse lookup); + +# Adobe Glyph List for New Fonts +# from http://partners.adobe.com/asn/tech/type/aglfn13.txt + +%names = ( + '0020' => 'space', + '0021' => 'exclam', + '0022' => 'quotedbl', + '0023' => 'numbersign', + '0024' => 'dollar', + '0025' => 'percent', + '0026' => 'ampersand', + '0027' => 'quotesingle', + '0028' => 'parenleft', + '0029' => 'parenright', + '002A' => 'asterisk', + '002B' => 'plus', + '002C' => 'comma', + '002D' => 'hyphen', + '002E' => 'period', + '002F' => 'slash', + '0030' => 'zero', + '0031' => 'one', + '0032' => 'two', + '0033' => 'three', + '0034' => 'four', + '0035' => 'five', + '0036' => 'six', + '0037' => 'seven', + '0038' => 'eight', + '0039' => 'nine', + '003A' => 'colon', + '003B' => 'semicolon', + '003C' => 'less', + '003D' => 'equal', + '003E' => 'greater', + '003F' => 'question', + '0040' => 'at', + '0041' => 'A', + '0042' => 'B', + '0043' => 'C', + '0044' => 'D', + '0045' => 'E', + '0046' => 'F', + '0047' => 'G', + '0048' => 'H', + '0049' => 'I', + '004A' => 'J', + '004B' => 'K', + '004C' => 'L', + '004D' => 'M', + '004E' => 'N', + '004F' => 'O', + '0050' => 'P', + '0051' => 'Q', + '0052' => 'R', + '0053' => 'S', + '0054' => 'T', + '0055' => 'U', + '0056' => 'V', + '0057' => 'W', + '0058' => 'X', + '0059' => 'Y', + '005A' => 'Z', + '005B' => 'bracketleft', + '005C' => 'backslash', + '005D' => 'bracketright', + '005E' => 'asciicircum', + '005F' => 'underscore', + '0060' => 'grave', + '0061' => 'a', + '0062' => 'b', + '0063' => 'c', + '0064' => 'd', + '0065' => 'e', + '0066' => 'f', + '0067' => 'g', + '0068' => 'h', + '0069' => 'i', + '006A' => 'j', + '006B' => 'k', + '006C' => 'l', + '006D' => 'm', + '006E' => 'n', + '006F' => 'o', + '0070' => 'p', + '0071' => 'q', + '0072' => 'r', + '0073' => 's', + '0074' => 't', + '0075' => 'u', + '0076' => 'v', + '0077' => 'w', + '0078' => 'x', + '0079' => 'y', + '007A' => 'z', + '007B' => 'braceleft', + '007C' => 'bar', + '007D' => 'braceright', + '007E' => 'asciitilde', +# '00A0' => 'space', + '00A1' => 'exclamdown', + '00A2' => 'cent', + '00A3' => 'sterling', + '00A4' => 'currency', + '00A5' => 'yen', + '00A6' => 'brokenbar', + '00A7' => 'section', + '00A8' => 'dieresis', + '00A9' => 'copyright', + '00AA' => 'ordfeminine', + '00AB' => 'guillemotleft', + '00AC' => 'logicalnot', +# '00AD' => 'hyphen', + '00AE' => 'registered', + '00AF' => 'macron', + '00B0' => 'degree', + '00B1' => 'plusminus', + '00B2' => 'twosuperior', + '00B3' => 'threesuperior', + '00B4' => 'acute', + '00B5' => 'mu', + '00B6' => 'paragraph', + '00B7' => 'periodcentered', + '00B8' => 'cedilla', + '00B9' => 'onesuperior', + '00BA' => 'ordmasculine', + '00BB' => 'guillemotright', + '00BC' => 'onequarter', + '00BD' => 'onehalf', + '00BE' => 'threequarters', + '00BF' => 'questiondown', + '00C0' => 'Agrave', + '00C1' => 'Aacute', + '00C2' => 'Acircumflex', + '00C3' => 'Atilde', + '00C4' => 'Adieresis', + '00C5' => 'Aring', + '00C6' => 'AE', + '00C7' => 'Ccedilla', + '00C8' => 'Egrave', + '00C9' => 'Eacute', + '00CA' => 'Ecircumflex', + '00CB' => 'Edieresis', + '00CC' => 'Igrave', + '00CD' => 'Iacute', + '00CE' => 'Icircumflex', + '00CF' => 'Idieresis', + '00D0' => 'Eth', + '00D1' => 'Ntilde', + '00D2' => 'Ograve', + '00D3' => 'Oacute', + '00D4' => 'Ocircumflex', + '00D5' => 'Otilde', + '00D6' => 'Odieresis', + '00D7' => 'multiply', + '00D8' => 'Oslash', + '00D9' => 'Ugrave', + '00DA' => 'Uacute', + '00DB' => 'Ucircumflex', + '00DC' => 'Udieresis', + '00DD' => 'Yacute', + '00DE' => 'Thorn', + '00DF' => 'germandbls', + '00E0' => 'agrave', + '00E1' => 'aacute', + '00E2' => 'acircumflex', + '00E3' => 'atilde', + '00E4' => 'adieresis', + '00E5' => 'aring', + '00E6' => 'ae', + '00E7' => 'ccedilla', + '00E8' => 'egrave', + '00E9' => 'eacute', + '00EA' => 'ecircumflex', + '00EB' => 'edieresis', + '00EC' => 'igrave', + '00ED' => 'iacute', + '00EE' => 'icircumflex', + '00EF' => 'idieresis', + '00F0' => 'eth', + '00F1' => 'ntilde', + '00F2' => 'ograve', + '00F3' => 'oacute', + '00F4' => 'ocircumflex', + '00F5' => 'otilde', + '00F6' => 'odieresis', + '00F7' => 'divide', + '00F8' => 'oslash', + '00F9' => 'ugrave', + '00FA' => 'uacute', + '00FB' => 'ucircumflex', + '00FC' => 'udieresis', + '00FD' => 'yacute', + '00FE' => 'thorn', + '00FF' => 'ydieresis', + '0100' => 'Amacron', + '0101' => 'amacron', + '0102' => 'Abreve', + '0103' => 'abreve', + '0104' => 'Aogonek', + '0105' => 'aogonek', + '0106' => 'Cacute', + '0107' => 'cacute', + '0108' => 'Ccircumflex', + '0109' => 'ccircumflex', + '010A' => 'Cdotaccent', + '010B' => 'cdotaccent', + '010C' => 'Ccaron', + '010D' => 'ccaron', + '010E' => 'Dcaron', + '010F' => 'dcaron', + '0110' => 'Dcroat', + '0111' => 'dcroat', + '0112' => 'Emacron', + '0113' => 'emacron', + '0114' => 'Ebreve', + '0115' => 'ebreve', + '0116' => 'Edotaccent', + '0117' => 'edotaccent', + '0118' => 'Eogonek', + '0119' => 'eogonek', + '011A' => 'Ecaron', + '011B' => 'ecaron', + '011C' => 'Gcircumflex', + '011D' => 'gcircumflex', + '011E' => 'Gbreve', + '011F' => 'gbreve', + '0120' => 'Gdotaccent', + '0121' => 'gdotaccent', + '0122' => 'Gcommaaccent', + '0123' => 'gcommaaccent', + '0124' => 'Hcircumflex', + '0125' => 'hcircumflex', + '0126' => 'Hbar', + '0127' => 'hbar', + '0128' => 'Itilde', + '0129' => 'itilde', + '012A' => 'Imacron', + '012B' => 'imacron', + '012C' => 'Ibreve', + '012D' => 'ibreve', + '012E' => 'Iogonek', + '012F' => 'iogonek', + '0130' => 'Idotaccent', + '0131' => 'dotlessi', + '0132' => 'IJ', + '0133' => 'ij', + '0134' => 'Jcircumflex', + '0135' => 'jcircumflex', + '0136' => 'Kcommaaccent', + '0137' => 'kcommaaccent', + '0138' => 'kgreenlandic', + '0139' => 'Lacute', + '013A' => 'lacute', + '013B' => 'Lcommaaccent', + '013C' => 'lcommaaccent', + '013D' => 'Lcaron', + '013E' => 'lcaron', + '013F' => 'Ldot', + '0140' => 'ldot', + '0141' => 'Lslash', + '0142' => 'lslash', + '0143' => 'Nacute', + '0144' => 'nacute', + '0145' => 'Ncommaaccent', + '0146' => 'ncommaaccent', + '0147' => 'Ncaron', + '0148' => 'ncaron', + '0149' => 'napostrophe', + '014A' => 'Eng', + '014B' => 'eng', + '014C' => 'Omacron', + '014D' => 'omacron', + '014E' => 'Obreve', + '014F' => 'obreve', + '0150' => 'Ohungarumlaut', + '0151' => 'ohungarumlaut', + '0152' => 'OE', + '0153' => 'oe', + '0154' => 'Racute', + '0155' => 'racute', + '0156' => 'Rcommaaccent', + '0157' => 'rcommaaccent', + '0158' => 'Rcaron', + '0159' => 'rcaron', + '015A' => 'Sacute', + '015B' => 'sacute', + '015C' => 'Scircumflex', + '015D' => 'scircumflex', + '015E' => 'Scedilla', + '015F' => 'scedilla', + '0160' => 'Scaron', + '0161' => 'scaron', + '0162' => 'Tcommaaccent', + '0163' => 'tcommaaccent', + '0164' => 'Tcaron', + '0165' => 'tcaron', + '0166' => 'Tbar', + '0167' => 'tbar', + '0168' => 'Utilde', + '0169' => 'utilde', + '016A' => 'Umacron', + '016B' => 'umacron', + '016C' => 'Ubreve', + '016D' => 'ubreve', + '016E' => 'Uring', + '016F' => 'uring', + '0170' => 'Uhungarumlaut', + '0171' => 'uhungarumlaut', + '0172' => 'Uogonek', + '0173' => 'uogonek', + '0174' => 'Wcircumflex', + '0175' => 'wcircumflex', + '0176' => 'Ycircumflex', + '0177' => 'ycircumflex', + '0178' => 'Ydieresis', + '0179' => 'Zacute', + '017A' => 'zacute', + '017B' => 'Zdotaccent', + '017C' => 'zdotaccent', + '017D' => 'Zcaron', + '017E' => 'zcaron', + '017F' => 'longs', + '0192' => 'florin', + '01A0' => 'Ohorn', + '01A1' => 'ohorn', + '01AF' => 'Uhorn', + '01B0' => 'uhorn', + '01E6' => 'Gcaron', + '01E7' => 'gcaron', + '01FA' => 'Aringacute', + '01FB' => 'aringacute', + '01FC' => 'AEacute', + '01FD' => 'aeacute', + '01FE' => 'Oslashacute', + '01FF' => 'oslashacute', + '0218' => 'Scommaaccent', + '0219' => 'scommaaccent', +# '021A' => 'Tcommaaccent', +# '021B' => 'tcommaaccent', + '02BC' => 'afii57929', + '02BD' => 'afii64937', + '02C6' => 'circumflex', + '02C7' => 'caron', +# '02C9' => 'macron', + '02D8' => 'breve', + '02D9' => 'dotaccent', + '02DA' => 'ring', + '02DB' => 'ogonek', + '02DC' => 'tilde', + '02DD' => 'hungarumlaut', + '0300' => 'gravecomb', + '0301' => 'acutecomb', + '0303' => 'tildecomb', + '0309' => 'hookabovecomb', + '0323' => 'dotbelowcomb', + '0384' => 'tonos', + '0385' => 'dieresistonos', + '0386' => 'Alphatonos', + '0387' => 'anoteleia', + '0388' => 'Epsilontonos', + '0389' => 'Etatonos', + '038A' => 'Iotatonos', + '038C' => 'Omicrontonos', + '038E' => 'Upsilontonos', + '038F' => 'Omegatonos', + '0390' => 'iotadieresistonos', + '0391' => 'Alpha', + '0392' => 'Beta', + '0393' => 'Gamma', +# '0394' => 'Delta', + '0395' => 'Epsilon', + '0396' => 'Zeta', + '0397' => 'Eta', + '0398' => 'Theta', + '0399' => 'Iota', + '039A' => 'Kappa', + '039B' => 'Lambda', + '039C' => 'Mu', + '039D' => 'Nu', + '039E' => 'Xi', + '039F' => 'Omicron', + '03A0' => 'Pi', + '03A1' => 'Rho', + '03A3' => 'Sigma', + '03A4' => 'Tau', + '03A5' => 'Upsilon', + '03A6' => 'Phi', + '03A7' => 'Chi', + '03A8' => 'Psi', +# '03A9' => 'Omega', + '03AA' => 'Iotadieresis', + '03AB' => 'Upsilondieresis', + '03AC' => 'alphatonos', + '03AD' => 'epsilontonos', + '03AE' => 'etatonos', + '03AF' => 'iotatonos', + '03B0' => 'upsilondieresistonos', + '03B1' => 'alpha', + '03B2' => 'beta', + '03B3' => 'gamma', + '03B4' => 'delta', + '03B5' => 'epsilon', + '03B6' => 'zeta', + '03B7' => 'eta', + '03B8' => 'theta', + '03B9' => 'iota', + '03BA' => 'kappa', + '03BB' => 'lambda', +# '03BC' => 'mu', + '03BD' => 'nu', + '03BE' => 'xi', + '03BF' => 'omicron', + '03C0' => 'pi', + '03C1' => 'rho', + '03C2' => 'sigma1', + '03C3' => 'sigma', + '03C4' => 'tau', + '03C5' => 'upsilon', + '03C6' => 'phi', + '03C7' => 'chi', + '03C8' => 'psi', + '03C9' => 'omega', + '03CA' => 'iotadieresis', + '03CB' => 'upsilondieresis', + '03CC' => 'omicrontonos', + '03CD' => 'upsilontonos', + '03CE' => 'omegatonos', + '03D1' => 'theta1', + '03D2' => 'Upsilon1', + '03D5' => 'phi1', + '03D6' => 'omega1', + '0401' => 'afii10023', + '0402' => 'afii10051', + '0403' => 'afii10052', + '0404' => 'afii10053', + '0405' => 'afii10054', + '0406' => 'afii10055', + '0407' => 'afii10056', + '0408' => 'afii10057', + '0409' => 'afii10058', + '040A' => 'afii10059', + '040B' => 'afii10060', + '040C' => 'afii10061', + '040E' => 'afii10062', + '040F' => 'afii10145', + '0410' => 'afii10017', + '0411' => 'afii10018', + '0412' => 'afii10019', + '0413' => 'afii10020', + '0414' => 'afii10021', + '0415' => 'afii10022', + '0416' => 'afii10024', + '0417' => 'afii10025', + '0418' => 'afii10026', + '0419' => 'afii10027', + '041A' => 'afii10028', + '041B' => 'afii10029', + '041C' => 'afii10030', + '041D' => 'afii10031', + '041E' => 'afii10032', + '041F' => 'afii10033', + '0420' => 'afii10034', + '0421' => 'afii10035', + '0422' => 'afii10036', + '0423' => 'afii10037', + '0424' => 'afii10038', + '0425' => 'afii10039', + '0426' => 'afii10040', + '0427' => 'afii10041', + '0428' => 'afii10042', + '0429' => 'afii10043', + '042A' => 'afii10044', + '042B' => 'afii10045', + '042C' => 'afii10046', + '042D' => 'afii10047', + '042E' => 'afii10048', + '042F' => 'afii10049', + '0430' => 'afii10065', + '0431' => 'afii10066', + '0432' => 'afii10067', + '0433' => 'afii10068', + '0434' => 'afii10069', + '0435' => 'afii10070', + '0436' => 'afii10072', + '0437' => 'afii10073', + '0438' => 'afii10074', + '0439' => 'afii10075', + '043A' => 'afii10076', + '043B' => 'afii10077', + '043C' => 'afii10078', + '043D' => 'afii10079', + '043E' => 'afii10080', + '043F' => 'afii10081', + '0440' => 'afii10082', + '0441' => 'afii10083', + '0442' => 'afii10084', + '0443' => 'afii10085', + '0444' => 'afii10086', + '0445' => 'afii10087', + '0446' => 'afii10088', + '0447' => 'afii10089', + '0448' => 'afii10090', + '0449' => 'afii10091', + '044A' => 'afii10092', + '044B' => 'afii10093', + '044C' => 'afii10094', + '044D' => 'afii10095', + '044E' => 'afii10096', + '044F' => 'afii10097', + '0451' => 'afii10071', + '0452' => 'afii10099', + '0453' => 'afii10100', + '0454' => 'afii10101', + '0455' => 'afii10102', + '0456' => 'afii10103', + '0457' => 'afii10104', + '0458' => 'afii10105', + '0459' => 'afii10106', + '045A' => 'afii10107', + '045B' => 'afii10108', + '045C' => 'afii10109', + '045E' => 'afii10110', + '045F' => 'afii10193', + '0462' => 'afii10146', + '0463' => 'afii10194', + '0472' => 'afii10147', + '0473' => 'afii10195', + '0474' => 'afii10148', + '0475' => 'afii10196', + '0490' => 'afii10050', + '0491' => 'afii10098', + '04D9' => 'afii10846', + '05B0' => 'afii57799', + '05B1' => 'afii57801', + '05B2' => 'afii57800', + '05B3' => 'afii57802', + '05B4' => 'afii57793', + '05B5' => 'afii57794', + '05B6' => 'afii57795', + '05B7' => 'afii57798', + '05B8' => 'afii57797', + '05B9' => 'afii57806', + '05BB' => 'afii57796', + '05BC' => 'afii57807', + '05BD' => 'afii57839', + '05BE' => 'afii57645', + '05BF' => 'afii57841', + '05C0' => 'afii57842', + '05C1' => 'afii57804', + '05C2' => 'afii57803', + '05C3' => 'afii57658', + '05D0' => 'afii57664', + '05D1' => 'afii57665', + '05D2' => 'afii57666', + '05D3' => 'afii57667', + '05D4' => 'afii57668', + '05D5' => 'afii57669', + '05D6' => 'afii57670', + '05D7' => 'afii57671', + '05D8' => 'afii57672', + '05D9' => 'afii57673', + '05DA' => 'afii57674', + '05DB' => 'afii57675', + '05DC' => 'afii57676', + '05DD' => 'afii57677', + '05DE' => 'afii57678', + '05DF' => 'afii57679', + '05E0' => 'afii57680', + '05E1' => 'afii57681', + '05E2' => 'afii57682', + '05E3' => 'afii57683', + '05E4' => 'afii57684', + '05E5' => 'afii57685', + '05E6' => 'afii57686', + '05E7' => 'afii57687', + '05E8' => 'afii57688', + '05E9' => 'afii57689', + '05EA' => 'afii57690', + '05F0' => 'afii57716', + '05F1' => 'afii57717', + '05F2' => 'afii57718', + '060C' => 'afii57388', + '061B' => 'afii57403', + '061F' => 'afii57407', + '0621' => 'afii57409', + '0622' => 'afii57410', + '0623' => 'afii57411', + '0624' => 'afii57412', + '0625' => 'afii57413', + '0626' => 'afii57414', + '0627' => 'afii57415', + '0628' => 'afii57416', + '0629' => 'afii57417', + '062A' => 'afii57418', + '062B' => 'afii57419', + '062C' => 'afii57420', + '062D' => 'afii57421', + '062E' => 'afii57422', + '062F' => 'afii57423', + '0630' => 'afii57424', + '0631' => 'afii57425', + '0632' => 'afii57426', + '0633' => 'afii57427', + '0634' => 'afii57428', + '0635' => 'afii57429', + '0636' => 'afii57430', + '0637' => 'afii57431', + '0638' => 'afii57432', + '0639' => 'afii57433', + '063A' => 'afii57434', + '0640' => 'afii57440', + '0641' => 'afii57441', + '0642' => 'afii57442', + '0643' => 'afii57443', + '0644' => 'afii57444', + '0645' => 'afii57445', + '0646' => 'afii57446', + '0647' => 'afii57470', + '0648' => 'afii57448', + '0649' => 'afii57449', + '064A' => 'afii57450', + '064B' => 'afii57451', + '064C' => 'afii57452', + '064D' => 'afii57453', + '064E' => 'afii57454', + '064F' => 'afii57455', + '0650' => 'afii57456', + '0651' => 'afii57457', + '0652' => 'afii57458', + '0660' => 'afii57392', + '0661' => 'afii57393', + '0662' => 'afii57394', + '0663' => 'afii57395', + '0664' => 'afii57396', + '0665' => 'afii57397', + '0666' => 'afii57398', + '0667' => 'afii57399', + '0668' => 'afii57400', + '0669' => 'afii57401', + '066A' => 'afii57381', + '066D' => 'afii63167', + '0679' => 'afii57511', + '067E' => 'afii57506', + '0686' => 'afii57507', + '0688' => 'afii57512', + '0691' => 'afii57513', + '0698' => 'afii57508', + '06A4' => 'afii57505', + '06AF' => 'afii57509', + '06BA' => 'afii57514', + '06D2' => 'afii57519', + '06D5' => 'afii57534', + '1E80' => 'Wgrave', + '1E81' => 'wgrave', + '1E82' => 'Wacute', + '1E83' => 'wacute', + '1E84' => 'Wdieresis', + '1E85' => 'wdieresis', + '1EF2' => 'Ygrave', + '1EF3' => 'ygrave', + '200C' => 'afii61664', + '200D' => 'afii301', + '200E' => 'afii299', + '200F' => 'afii300', + '2012' => 'figuredash', + '2013' => 'endash', + '2014' => 'emdash', + '2015' => 'afii00208', + '2017' => 'underscoredbl', + '2018' => 'quoteleft', + '2019' => 'quoteright', + '201A' => 'quotesinglbase', + '201B' => 'quotereversed', + '201C' => 'quotedblleft', + '201D' => 'quotedblright', + '201E' => 'quotedblbase', + '2020' => 'dagger', + '2021' => 'daggerdbl', + '2022' => 'bullet', + '2024' => 'onedotenleader', + '2025' => 'twodotenleader', + '2026' => 'ellipsis', + '202C' => 'afii61573', + '202D' => 'afii61574', + '202E' => 'afii61575', + '2030' => 'perthousand', + '2032' => 'minute', + '2033' => 'second', + '2039' => 'guilsinglleft', + '203A' => 'guilsinglright', + '203C' => 'exclamdbl', + '2044' => 'fraction', +# '2070' => 'zerosuperior', +# '2074' => 'foursuperior', +# '2075' => 'fivesuperior', +# '2076' => 'sixsuperior', +# '2077' => 'sevensuperior', +# '2078' => 'eightsuperior', +# '2079' => 'ninesuperior', +# '207D' => 'parenleftsuperior', +# '207E' => 'parenrightsuperior', +# '207F' => 'nsuperior', +# '2080' => 'zeroinferior', +# '2081' => 'oneinferior', +# '2082' => 'twoinferior', +# '2083' => 'threeinferior', +# '2084' => 'fourinferior', +# '2085' => 'fiveinferior', +# '2086' => 'sixinferior', +# '2087' => 'seveninferior', +# '2088' => 'eightinferior', +# '2089' => 'nineinferior', +# '208D' => 'parenleftinferior', +# '208E' => 'parenrightinferior', + '20A1' => 'colonmonetary', + '20A3' => 'franc', + '20A4' => 'lira', + '20A7' => 'peseta', + '20AA' => 'afii57636', + '20AB' => 'dong', + '20AC' => 'Euro', + '2105' => 'afii61248', + '2111' => 'Ifraktur', + '2113' => 'afii61289', + '2116' => 'afii61352', + '2118' => 'weierstrass', + '211C' => 'Rfraktur', + '211E' => 'prescription', + '2122' => 'trademark', + '2126' => 'Omega', + '212E' => 'estimated', + '2135' => 'aleph', + '2153' => 'onethird', + '2154' => 'twothirds', + '215B' => 'oneeighth', + '215C' => 'threeeighths', + '215D' => 'fiveeighths', + '215E' => 'seveneighths', + '2190' => 'arrowleft', + '2191' => 'arrowup', + '2192' => 'arrowright', + '2193' => 'arrowdown', + '2194' => 'arrowboth', + '2195' => 'arrowupdn', + '21A8' => 'arrowupdnbse', + '21B5' => 'carriagereturn', + '21D0' => 'arrowdblleft', + '21D1' => 'arrowdblup', + '21D2' => 'arrowdblright', + '21D3' => 'arrowdbldown', + '21D4' => 'arrowdblboth', + '2200' => 'universal', + '2202' => 'partialdiff', + '2203' => 'existential', + '2205' => 'emptyset', + '2206' => 'Delta', + '2207' => 'gradient', + '2208' => 'element', + '2209' => 'notelement', + '220B' => 'suchthat', + '220F' => 'product', + '2211' => 'summation', + '2212' => 'minus', +# '2215' => 'fraction', + '2217' => 'asteriskmath', +# '2219' => 'periodcentered', + '221A' => 'radical', + '221D' => 'proportional', + '221E' => 'infinity', + '221F' => 'orthogonal', + '2220' => 'angle', + '2227' => 'logicaland', + '2228' => 'logicalor', + '2229' => 'intersection', + '222A' => 'union', + '222B' => 'integral', + '2234' => 'therefore', + '223C' => 'similar', + '2245' => 'congruent', + '2248' => 'approxequal', + '2260' => 'notequal', + '2261' => 'equivalence', + '2264' => 'lessequal', + '2265' => 'greaterequal', + '2282' => 'propersubset', + '2283' => 'propersuperset', + '2284' => 'notsubset', + '2286' => 'reflexsubset', + '2287' => 'reflexsuperset', + '2295' => 'circleplus', + '2297' => 'circlemultiply', + '22A5' => 'perpendicular', + '22C5' => 'dotmath', + '2302' => 'house', + '2310' => 'revlogicalnot', + '2320' => 'integraltp', + '2321' => 'integralbt', + '2329' => 'angleleft', + '232A' => 'angleright', + '2500' => 'SF100000', + '2502' => 'SF110000', + '250C' => 'SF010000', + '2510' => 'SF030000', + '2514' => 'SF020000', + '2518' => 'SF040000', + '251C' => 'SF080000', + '2524' => 'SF090000', + '252C' => 'SF060000', + '2534' => 'SF070000', + '253C' => 'SF050000', + '2550' => 'SF430000', + '2551' => 'SF240000', + '2552' => 'SF510000', + '2553' => 'SF520000', + '2554' => 'SF390000', + '2555' => 'SF220000', + '2556' => 'SF210000', + '2557' => 'SF250000', + '2558' => 'SF500000', + '2559' => 'SF490000', + '255A' => 'SF380000', + '255B' => 'SF280000', + '255C' => 'SF270000', + '255D' => 'SF260000', + '255E' => 'SF360000', + '255F' => 'SF370000', + '2560' => 'SF420000', + '2561' => 'SF190000', + '2562' => 'SF200000', + '2563' => 'SF230000', + '2564' => 'SF470000', + '2565' => 'SF480000', + '2566' => 'SF410000', + '2567' => 'SF450000', + '2568' => 'SF460000', + '2569' => 'SF400000', + '256A' => 'SF540000', + '256B' => 'SF530000', + '256C' => 'SF440000', + '2580' => 'upblock', + '2584' => 'dnblock', + '2588' => 'block', + '258C' => 'lfblock', + '2590' => 'rtblock', + '2591' => 'ltshade', + '2592' => 'shade', + '2593' => 'dkshade', + '25A0' => 'filledbox', + '25A1' => 'H22073', + '25AA' => 'H18543', + '25AB' => 'H18551', + '25AC' => 'filledrect', + '25B2' => 'triagup', + '25BA' => 'triagrt', + '25BC' => 'triagdn', + '25C4' => 'triaglf', + '25CA' => 'lozenge', + '25CB' => 'circle', + '25CF' => 'H18533', + '25D8' => 'invbullet', + '25D9' => 'invcircle', + '25E6' => 'openbullet', + '263A' => 'smileface', + '263B' => 'invsmileface', + '263C' => 'sun', + '2640' => 'female', + '2642' => 'male', + '2660' => 'spade', + '2663' => 'club', + '2665' => 'heart', + '2666' => 'diamond', + '266A' => 'musicalnote', + '266B' => 'musicalnotedbl', + 'FB00' => 'ff', + 'FB01' => 'fi', + 'FB02' => 'fl', + 'FB03' => 'ffi', + 'FB04' => 'ffl', + 'FB1F' => 'afii57705', + 'FB2A' => 'afii57694', + 'FB2B' => 'afii57695', + 'FB35' => 'afii57723', + 'FB4B' => 'afii57700', +); + +# Adobe Glyph List 2.0 (sans those in glyph list for *new* fonts) -- thus +# these are all historic names that could occur in fonts +# from http://partners.adobe.com/asn/tech/type/glyphlist.txt + +%agl = ( + 'AEmacron' => "\x{01E2}", + 'AEsmall' => "\x{F7E6}", + 'Aacutesmall' => "\x{F7E1}", + 'Abreveacute' => "\x{1EAE}", + 'Abrevecyrillic' => "\x{04D0}", + 'Abrevedotbelow' => "\x{1EB6}", + 'Abrevegrave' => "\x{1EB0}", + 'Abrevehookabove' => "\x{1EB2}", + 'Abrevetilde' => "\x{1EB4}", + 'Acaron' => "\x{01CD}", + 'Acircle' => "\x{24B6}", + 'Acircumflexacute' => "\x{1EA4}", + 'Acircumflexdotbelow' => "\x{1EAC}", + 'Acircumflexgrave' => "\x{1EA6}", + 'Acircumflexhookabove' => "\x{1EA8}", + 'Acircumflexsmall' => "\x{F7E2}", + 'Acircumflextilde' => "\x{1EAA}", + 'Acute' => "\x{F6C9}", + 'Acutesmall' => "\x{F7B4}", + 'Acyrillic' => "\x{0410}", + 'Adblgrave' => "\x{0200}", + 'Adieresiscyrillic' => "\x{04D2}", + 'Adieresismacron' => "\x{01DE}", + 'Adieresissmall' => "\x{F7E4}", + 'Adotbelow' => "\x{1EA0}", + 'Adotmacron' => "\x{01E0}", + 'Agravesmall' => "\x{F7E0}", + 'Ahookabove' => "\x{1EA2}", + 'Aiecyrillic' => "\x{04D4}", + 'Ainvertedbreve' => "\x{0202}", + 'Amonospace' => "\x{FF21}", + 'Aringbelow' => "\x{1E00}", + 'Aringsmall' => "\x{F7E5}", + 'Asmall' => "\x{F761}", + 'Atildesmall' => "\x{F7E3}", + 'Aybarmenian' => "\x{0531}", + 'Bcircle' => "\x{24B7}", + 'Bdotaccent' => "\x{1E02}", + 'Bdotbelow' => "\x{1E04}", + 'Becyrillic' => "\x{0411}", + 'Benarmenian' => "\x{0532}", + 'Bhook' => "\x{0181}", + 'Blinebelow' => "\x{1E06}", + 'Bmonospace' => "\x{FF22}", + 'Brevesmall' => "\x{F6F4}", + 'Bsmall' => "\x{F762}", + 'Btopbar' => "\x{0182}", + 'Caarmenian' => "\x{053E}", + 'Caron' => "\x{F6CA}", + 'Caronsmall' => "\x{F6F5}", + 'Ccedillaacute' => "\x{1E08}", + 'Ccedillasmall' => "\x{F7E7}", + 'Ccircle' => "\x{24B8}", + 'Cdot' => "\x{010A}", + 'Cedillasmall' => "\x{F7B8}", + 'Chaarmenian' => "\x{0549}", + 'Cheabkhasiancyrillic' => "\x{04BC}", + 'Checyrillic' => "\x{0427}", + 'Chedescenderabkhasiancyrillic' => "\x{04BE}", + 'Chedescendercyrillic' => "\x{04B6}", + 'Chedieresiscyrillic' => "\x{04F4}", + 'Cheharmenian' => "\x{0543}", + 'Chekhakassiancyrillic' => "\x{04CB}", + 'Cheverticalstrokecyrillic' => "\x{04B8}", + 'Chook' => "\x{0187}", + 'Circumflexsmall' => "\x{F6F6}", + 'Cmonospace' => "\x{FF23}", + 'Coarmenian' => "\x{0551}", + 'Csmall' => "\x{F763}", + 'DZ' => "\x{01F1}", + 'DZcaron' => "\x{01C4}", + 'Daarmenian' => "\x{0534}", + 'Dafrican' => "\x{0189}", + 'Dcedilla' => "\x{1E10}", + 'Dcircle' => "\x{24B9}", + 'Dcircumflexbelow' => "\x{1E12}", + 'Ddotaccent' => "\x{1E0A}", + 'Ddotbelow' => "\x{1E0C}", + 'Decyrillic' => "\x{0414}", + 'Deicoptic' => "\x{03EE}", + 'Deltagreek' => "\x{0394}", + 'Dhook' => "\x{018A}", + 'Dieresis' => "\x{F6CB}", + 'DieresisAcute' => "\x{F6CC}", + 'DieresisGrave' => "\x{F6CD}", + 'Dieresissmall' => "\x{F7A8}", + 'Digammagreek' => "\x{03DC}", + 'Djecyrillic' => "\x{0402}", + 'Dlinebelow' => "\x{1E0E}", + 'Dmonospace' => "\x{FF24}", + 'Dotaccentsmall' => "\x{F6F7}", + 'Dslash' => "\x{0110}", + 'Dsmall' => "\x{F764}", + 'Dtopbar' => "\x{018B}", + 'Dz' => "\x{01F2}", + 'Dzcaron' => "\x{01C5}", + 'Dzeabkhasiancyrillic' => "\x{04E0}", + 'Dzecyrillic' => "\x{0405}", + 'Dzhecyrillic' => "\x{040F}", + 'Eacutesmall' => "\x{F7E9}", + 'Ecedillabreve' => "\x{1E1C}", + 'Echarmenian' => "\x{0535}", + 'Ecircle' => "\x{24BA}", + 'Ecircumflexacute' => "\x{1EBE}", + 'Ecircumflexbelow' => "\x{1E18}", + 'Ecircumflexdotbelow' => "\x{1EC6}", + 'Ecircumflexgrave' => "\x{1EC0}", + 'Ecircumflexhookabove' => "\x{1EC2}", + 'Ecircumflexsmall' => "\x{F7EA}", + 'Ecircumflextilde' => "\x{1EC4}", + 'Ecyrillic' => "\x{0404}", + 'Edblgrave' => "\x{0204}", + 'Edieresissmall' => "\x{F7EB}", + 'Edot' => "\x{0116}", + 'Edotbelow' => "\x{1EB8}", + 'Efcyrillic' => "\x{0424}", + 'Egravesmall' => "\x{F7E8}", + 'Eharmenian' => "\x{0537}", + 'Ehookabove' => "\x{1EBA}", + 'Eightroman' => "\x{2167}", + 'Einvertedbreve' => "\x{0206}", + 'Eiotifiedcyrillic' => "\x{0464}", + 'Elcyrillic' => "\x{041B}", + 'Elevenroman' => "\x{216A}", + 'Emacronacute' => "\x{1E16}", + 'Emacrongrave' => "\x{1E14}", + 'Emcyrillic' => "\x{041C}", + 'Emonospace' => "\x{FF25}", + 'Encyrillic' => "\x{041D}", + 'Endescendercyrillic' => "\x{04A2}", + 'Enghecyrillic' => "\x{04A4}", + 'Enhookcyrillic' => "\x{04C7}", + 'Eopen' => "\x{0190}", + 'Ercyrillic' => "\x{0420}", + 'Ereversed' => "\x{018E}", + 'Ereversedcyrillic' => "\x{042D}", + 'Escyrillic' => "\x{0421}", + 'Esdescendercyrillic' => "\x{04AA}", + 'Esh' => "\x{01A9}", + 'Esmall' => "\x{F765}", + 'Etarmenian' => "\x{0538}", + 'Ethsmall' => "\x{F7F0}", + 'Etilde' => "\x{1EBC}", + 'Etildebelow' => "\x{1E1A}", + 'Ezh' => "\x{01B7}", + 'Ezhcaron' => "\x{01EE}", + 'Ezhreversed' => "\x{01B8}", + 'Fcircle' => "\x{24BB}", + 'Fdotaccent' => "\x{1E1E}", + 'Feharmenian' => "\x{0556}", + 'Feicoptic' => "\x{03E4}", + 'Fhook' => "\x{0191}", + 'Fitacyrillic' => "\x{0472}", + 'Fiveroman' => "\x{2164}", + 'Fmonospace' => "\x{FF26}", + 'Fourroman' => "\x{2163}", + 'Fsmall' => "\x{F766}", + 'GBsquare' => "\x{3387}", + 'Gacute' => "\x{01F4}", + 'Gammaafrican' => "\x{0194}", + 'Gangiacoptic' => "\x{03EA}", + 'Gcedilla' => "\x{0122}", + 'Gcircle' => "\x{24BC}", + 'Gdot' => "\x{0120}", + 'Gecyrillic' => "\x{0413}", + 'Ghadarmenian' => "\x{0542}", + 'Ghemiddlehookcyrillic' => "\x{0494}", + 'Ghestrokecyrillic' => "\x{0492}", + 'Gheupturncyrillic' => "\x{0490}", + 'Ghook' => "\x{0193}", + 'Gimarmenian' => "\x{0533}", + 'Gjecyrillic' => "\x{0403}", + 'Gmacron' => "\x{1E20}", + 'Gmonospace' => "\x{FF27}", + 'Grave' => "\x{F6CE}", + 'Gravesmall' => "\x{F760}", + 'Gsmall' => "\x{F767}", + 'Gsmallhook' => "\x{029B}", + 'Gstroke' => "\x{01E4}", + 'HPsquare' => "\x{33CB}", + 'Haabkhasiancyrillic' => "\x{04A8}", + 'Hadescendercyrillic' => "\x{04B2}", + 'Hardsigncyrillic' => "\x{042A}", + 'Hbrevebelow' => "\x{1E2A}", + 'Hcedilla' => "\x{1E28}", + 'Hcircle' => "\x{24BD}", + 'Hdieresis' => "\x{1E26}", + 'Hdotaccent' => "\x{1E22}", + 'Hdotbelow' => "\x{1E24}", + 'Hmonospace' => "\x{FF28}", + 'Hoarmenian' => "\x{0540}", + 'Horicoptic' => "\x{03E8}", + 'Hsmall' => "\x{F768}", + 'Hungarumlaut' => "\x{F6CF}", + 'Hungarumlautsmall' => "\x{F6F8}", + 'Hzsquare' => "\x{3390}", + 'IAcyrillic' => "\x{042F}", + 'IUcyrillic' => "\x{042E}", + 'Iacutesmall' => "\x{F7ED}", + 'Icaron' => "\x{01CF}", + 'Icircle' => "\x{24BE}", + 'Icircumflexsmall' => "\x{F7EE}", + 'Icyrillic' => "\x{0406}", + 'Idblgrave' => "\x{0208}", + 'Idieresisacute' => "\x{1E2E}", + 'Idieresiscyrillic' => "\x{04E4}", + 'Idieresissmall' => "\x{F7EF}", + 'Idot' => "\x{0130}", + 'Idotbelow' => "\x{1ECA}", + 'Iebrevecyrillic' => "\x{04D6}", + 'Iecyrillic' => "\x{0415}", + 'Igravesmall' => "\x{F7EC}", + 'Ihookabove' => "\x{1EC8}", + 'Iicyrillic' => "\x{0418}", + 'Iinvertedbreve' => "\x{020A}", + 'Iishortcyrillic' => "\x{0419}", + 'Imacroncyrillic' => "\x{04E2}", + 'Imonospace' => "\x{FF29}", + 'Iniarmenian' => "\x{053B}", + 'Iocyrillic' => "\x{0401}", + 'Iotaafrican' => "\x{0196}", + 'Ismall' => "\x{F769}", + 'Istroke' => "\x{0197}", + 'Itildebelow' => "\x{1E2C}", + 'Izhitsacyrillic' => "\x{0474}", + 'Izhitsadblgravecyrillic' => "\x{0476}", + 'Jaarmenian' => "\x{0541}", + 'Jcircle' => "\x{24BF}", + 'Jecyrillic' => "\x{0408}", + 'Jheharmenian' => "\x{054B}", + 'Jmonospace' => "\x{FF2A}", + 'Jsmall' => "\x{F76A}", + 'KBsquare' => "\x{3385}", + 'KKsquare' => "\x{33CD}", + 'Kabashkircyrillic' => "\x{04A0}", + 'Kacute' => "\x{1E30}", + 'Kacyrillic' => "\x{041A}", + 'Kadescendercyrillic' => "\x{049A}", + 'Kahookcyrillic' => "\x{04C3}", + 'Kastrokecyrillic' => "\x{049E}", + 'Kaverticalstrokecyrillic' => "\x{049C}", + 'Kcaron' => "\x{01E8}", + 'Kcedilla' => "\x{0136}", + 'Kcircle' => "\x{24C0}", + 'Kdotbelow' => "\x{1E32}", + 'Keharmenian' => "\x{0554}", + 'Kenarmenian' => "\x{053F}", + 'Khacyrillic' => "\x{0425}", + 'Kheicoptic' => "\x{03E6}", + 'Khook' => "\x{0198}", + 'Kjecyrillic' => "\x{040C}", + 'Klinebelow' => "\x{1E34}", + 'Kmonospace' => "\x{FF2B}", + 'Koppacyrillic' => "\x{0480}", + 'Koppagreek' => "\x{03DE}", + 'Ksicyrillic' => "\x{046E}", + 'Ksmall' => "\x{F76B}", + 'LJ' => "\x{01C7}", + 'LL' => "\x{F6BF}", + 'Lcedilla' => "\x{013B}", + 'Lcircle' => "\x{24C1}", + 'Lcircumflexbelow' => "\x{1E3C}", + 'Ldotaccent' => "\x{013F}", + 'Ldotbelow' => "\x{1E36}", + 'Ldotbelowmacron' => "\x{1E38}", + 'Liwnarmenian' => "\x{053C}", + 'Lj' => "\x{01C8}", + 'Ljecyrillic' => "\x{0409}", + 'Llinebelow' => "\x{1E3A}", + 'Lmonospace' => "\x{FF2C}", + 'Lslashsmall' => "\x{F6F9}", + 'Lsmall' => "\x{F76C}", + 'MBsquare' => "\x{3386}", + 'Macron' => "\x{F6D0}", + 'Macronsmall' => "\x{F7AF}", + 'Macute' => "\x{1E3E}", + 'Mcircle' => "\x{24C2}", + 'Mdotaccent' => "\x{1E40}", + 'Mdotbelow' => "\x{1E42}", + 'Menarmenian' => "\x{0544}", + 'Mmonospace' => "\x{FF2D}", + 'Msmall' => "\x{F76D}", + 'Mturned' => "\x{019C}", + 'NJ' => "\x{01CA}", + 'Ncedilla' => "\x{0145}", + 'Ncircle' => "\x{24C3}", + 'Ncircumflexbelow' => "\x{1E4A}", + 'Ndotaccent' => "\x{1E44}", + 'Ndotbelow' => "\x{1E46}", + 'Nhookleft' => "\x{019D}", + 'Nineroman' => "\x{2168}", + 'Nj' => "\x{01CB}", + 'Njecyrillic' => "\x{040A}", + 'Nlinebelow' => "\x{1E48}", + 'Nmonospace' => "\x{FF2E}", + 'Nowarmenian' => "\x{0546}", + 'Nsmall' => "\x{F76E}", + 'Ntildesmall' => "\x{F7F1}", + 'OEsmall' => "\x{F6FA}", + 'Oacutesmall' => "\x{F7F3}", + 'Obarredcyrillic' => "\x{04E8}", + 'Obarreddieresiscyrillic' => "\x{04EA}", + 'Ocaron' => "\x{01D1}", + 'Ocenteredtilde' => "\x{019F}", + 'Ocircle' => "\x{24C4}", + 'Ocircumflexacute' => "\x{1ED0}", + 'Ocircumflexdotbelow' => "\x{1ED8}", + 'Ocircumflexgrave' => "\x{1ED2}", + 'Ocircumflexhookabove' => "\x{1ED4}", + 'Ocircumflexsmall' => "\x{F7F4}", + 'Ocircumflextilde' => "\x{1ED6}", + 'Ocyrillic' => "\x{041E}", + 'Odblacute' => "\x{0150}", + 'Odblgrave' => "\x{020C}", + 'Odieresiscyrillic' => "\x{04E6}", + 'Odieresissmall' => "\x{F7F6}", + 'Odotbelow' => "\x{1ECC}", + 'Ogoneksmall' => "\x{F6FB}", + 'Ogravesmall' => "\x{F7F2}", + 'Oharmenian' => "\x{0555}", + 'Ohm' => "\x{2126}", + 'Ohookabove' => "\x{1ECE}", + 'Ohornacute' => "\x{1EDA}", + 'Ohorndotbelow' => "\x{1EE2}", + 'Ohorngrave' => "\x{1EDC}", + 'Ohornhookabove' => "\x{1EDE}", + 'Ohorntilde' => "\x{1EE0}", + 'Oi' => "\x{01A2}", + 'Oinvertedbreve' => "\x{020E}", + 'Omacronacute' => "\x{1E52}", + 'Omacrongrave' => "\x{1E50}", + 'Omegacyrillic' => "\x{0460}", + 'Omegagreek' => "\x{03A9}", + 'Omegaroundcyrillic' => "\x{047A}", + 'Omegatitlocyrillic' => "\x{047C}", + 'Omonospace' => "\x{FF2F}", + 'Oneroman' => "\x{2160}", + 'Oogonek' => "\x{01EA}", + 'Oogonekmacron' => "\x{01EC}", + 'Oopen' => "\x{0186}", + 'Oslashsmall' => "\x{F7F8}", + 'Osmall' => "\x{F76F}", + 'Ostrokeacute' => "\x{01FE}", + 'Otcyrillic' => "\x{047E}", + 'Otildeacute' => "\x{1E4C}", + 'Otildedieresis' => "\x{1E4E}", + 'Otildesmall' => "\x{F7F5}", + 'Pacute' => "\x{1E54}", + 'Pcircle' => "\x{24C5}", + 'Pdotaccent' => "\x{1E56}", + 'Pecyrillic' => "\x{041F}", + 'Peharmenian' => "\x{054A}", + 'Pemiddlehookcyrillic' => "\x{04A6}", + 'Phook' => "\x{01A4}", + 'Piwrarmenian' => "\x{0553}", + 'Pmonospace' => "\x{FF30}", + 'Psicyrillic' => "\x{0470}", + 'Psmall' => "\x{F770}", + 'Qcircle' => "\x{24C6}", + 'Qmonospace' => "\x{FF31}", + 'Qsmall' => "\x{F771}", + 'Raarmenian' => "\x{054C}", + 'Rcedilla' => "\x{0156}", + 'Rcircle' => "\x{24C7}", + 'Rdblgrave' => "\x{0210}", + 'Rdotaccent' => "\x{1E58}", + 'Rdotbelow' => "\x{1E5A}", + 'Rdotbelowmacron' => "\x{1E5C}", + 'Reharmenian' => "\x{0550}", + 'Ringsmall' => "\x{F6FC}", + 'Rinvertedbreve' => "\x{0212}", + 'Rlinebelow' => "\x{1E5E}", + 'Rmonospace' => "\x{FF32}", + 'Rsmall' => "\x{F772}", + 'Rsmallinverted' => "\x{0281}", + 'Rsmallinvertedsuperior' => "\x{02B6}", + 'Sacutedotaccent' => "\x{1E64}", + 'Sampigreek' => "\x{03E0}", + 'Scarondotaccent' => "\x{1E66}", + 'Scaronsmall' => "\x{F6FD}", + 'Schwa' => "\x{018F}", + 'Schwacyrillic' => "\x{04D8}", + 'Schwadieresiscyrillic' => "\x{04DA}", + 'Scircle' => "\x{24C8}", + 'Sdotaccent' => "\x{1E60}", + 'Sdotbelow' => "\x{1E62}", + 'Sdotbelowdotaccent' => "\x{1E68}", + 'Seharmenian' => "\x{054D}", + 'Sevenroman' => "\x{2166}", + 'Shaarmenian' => "\x{0547}", + 'Shacyrillic' => "\x{0428}", + 'Shchacyrillic' => "\x{0429}", + 'Sheicoptic' => "\x{03E2}", + 'Shhacyrillic' => "\x{04BA}", + 'Shimacoptic' => "\x{03EC}", + 'Sixroman' => "\x{2165}", + 'Smonospace' => "\x{FF33}", + 'Softsigncyrillic' => "\x{042C}", + 'Ssmall' => "\x{F773}", + 'Stigmagreek' => "\x{03DA}", + 'Tcedilla' => "\x{0162}", + 'Tcircle' => "\x{24C9}", + 'Tcircumflexbelow' => "\x{1E70}", + 'Tdotaccent' => "\x{1E6A}", + 'Tdotbelow' => "\x{1E6C}", + 'Tecyrillic' => "\x{0422}", + 'Tedescendercyrillic' => "\x{04AC}", + 'Tenroman' => "\x{2169}", + 'Tetsecyrillic' => "\x{04B4}", + 'Thook' => "\x{01AC}", + 'Thornsmall' => "\x{F7FE}", + 'Threeroman' => "\x{2162}", + 'Tildesmall' => "\x{F6FE}", + 'Tiwnarmenian' => "\x{054F}", + 'Tlinebelow' => "\x{1E6E}", + 'Tmonospace' => "\x{FF34}", + 'Toarmenian' => "\x{0539}", + 'Tonefive' => "\x{01BC}", + 'Tonesix' => "\x{0184}", + 'Tonetwo' => "\x{01A7}", + 'Tretroflexhook' => "\x{01AE}", + 'Tsecyrillic' => "\x{0426}", + 'Tshecyrillic' => "\x{040B}", + 'Tsmall' => "\x{F774}", + 'Twelveroman' => "\x{216B}", + 'Tworoman' => "\x{2161}", + 'Uacutesmall' => "\x{F7FA}", + 'Ucaron' => "\x{01D3}", + 'Ucircle' => "\x{24CA}", + 'Ucircumflexbelow' => "\x{1E76}", + 'Ucircumflexsmall' => "\x{F7FB}", + 'Ucyrillic' => "\x{0423}", + 'Udblacute' => "\x{0170}", + 'Udblgrave' => "\x{0214}", + 'Udieresisacute' => "\x{01D7}", + 'Udieresisbelow' => "\x{1E72}", + 'Udieresiscaron' => "\x{01D9}", + 'Udieresiscyrillic' => "\x{04F0}", + 'Udieresisgrave' => "\x{01DB}", + 'Udieresismacron' => "\x{01D5}", + 'Udieresissmall' => "\x{F7FC}", + 'Udotbelow' => "\x{1EE4}", + 'Ugravesmall' => "\x{F7F9}", + 'Uhookabove' => "\x{1EE6}", + 'Uhornacute' => "\x{1EE8}", + 'Uhorndotbelow' => "\x{1EF0}", + 'Uhorngrave' => "\x{1EEA}", + 'Uhornhookabove' => "\x{1EEC}", + 'Uhorntilde' => "\x{1EEE}", + 'Uhungarumlautcyrillic' => "\x{04F2}", + 'Uinvertedbreve' => "\x{0216}", + 'Ukcyrillic' => "\x{0478}", + 'Umacroncyrillic' => "\x{04EE}", + 'Umacrondieresis' => "\x{1E7A}", + 'Umonospace' => "\x{FF35}", + 'Upsilonacutehooksymbolgreek' => "\x{03D3}", + 'Upsilonafrican' => "\x{01B1}", + 'Upsilondieresishooksymbolgreek' => "\x{03D4}", + 'Upsilonhooksymbol' => "\x{03D2}", + 'Ushortcyrillic' => "\x{040E}", + 'Usmall' => "\x{F775}", + 'Ustraightcyrillic' => "\x{04AE}", + 'Ustraightstrokecyrillic' => "\x{04B0}", + 'Utildeacute' => "\x{1E78}", + 'Utildebelow' => "\x{1E74}", + 'Vcircle' => "\x{24CB}", + 'Vdotbelow' => "\x{1E7E}", + 'Vecyrillic' => "\x{0412}", + 'Vewarmenian' => "\x{054E}", + 'Vhook' => "\x{01B2}", + 'Vmonospace' => "\x{FF36}", + 'Voarmenian' => "\x{0548}", + 'Vsmall' => "\x{F776}", + 'Vtilde' => "\x{1E7C}", + 'Wcircle' => "\x{24CC}", + 'Wdotaccent' => "\x{1E86}", + 'Wdotbelow' => "\x{1E88}", + 'Wmonospace' => "\x{FF37}", + 'Wsmall' => "\x{F777}", + 'Xcircle' => "\x{24CD}", + 'Xdieresis' => "\x{1E8C}", + 'Xdotaccent' => "\x{1E8A}", + 'Xeharmenian' => "\x{053D}", + 'Xmonospace' => "\x{FF38}", + 'Xsmall' => "\x{F778}", + 'Yacutesmall' => "\x{F7FD}", + 'Yatcyrillic' => "\x{0462}", + 'Ycircle' => "\x{24CE}", + 'Ydieresissmall' => "\x{F7FF}", + 'Ydotaccent' => "\x{1E8E}", + 'Ydotbelow' => "\x{1EF4}", + 'Yericyrillic' => "\x{042B}", + 'Yerudieresiscyrillic' => "\x{04F8}", + 'Yhook' => "\x{01B3}", + 'Yhookabove' => "\x{1EF6}", + 'Yiarmenian' => "\x{0545}", + 'Yicyrillic' => "\x{0407}", + 'Yiwnarmenian' => "\x{0552}", + 'Ymonospace' => "\x{FF39}", + 'Ysmall' => "\x{F779}", + 'Ytilde' => "\x{1EF8}", + 'Yusbigcyrillic' => "\x{046A}", + 'Yusbigiotifiedcyrillic' => "\x{046C}", + 'Yuslittlecyrillic' => "\x{0466}", + 'Yuslittleiotifiedcyrillic' => "\x{0468}", + 'Zaarmenian' => "\x{0536}", + 'Zcaronsmall' => "\x{F6FF}", + 'Zcircle' => "\x{24CF}", + 'Zcircumflex' => "\x{1E90}", + 'Zdot' => "\x{017B}", + 'Zdotbelow' => "\x{1E92}", + 'Zecyrillic' => "\x{0417}", + 'Zedescendercyrillic' => "\x{0498}", + 'Zedieresiscyrillic' => "\x{04DE}", + 'Zhearmenian' => "\x{053A}", + 'Zhebrevecyrillic' => "\x{04C1}", + 'Zhecyrillic' => "\x{0416}", + 'Zhedescendercyrillic' => "\x{0496}", + 'Zhedieresiscyrillic' => "\x{04DC}", + 'Zlinebelow' => "\x{1E94}", + 'Zmonospace' => "\x{FF3A}", + 'Zsmall' => "\x{F77A}", + 'Zstroke' => "\x{01B5}", + 'aabengali' => "\x{0986}", + 'aadeva' => "\x{0906}", + 'aagujarati' => "\x{0A86}", + 'aagurmukhi' => "\x{0A06}", + 'aamatragurmukhi' => "\x{0A3E}", + 'aarusquare' => "\x{3303}", + 'aavowelsignbengali' => "\x{09BE}", + 'aavowelsigndeva' => "\x{093E}", + 'aavowelsigngujarati' => "\x{0ABE}", + 'abbreviationmarkarmenian' => "\x{055F}", + 'abbreviationsigndeva' => "\x{0970}", + 'abengali' => "\x{0985}", + 'abopomofo' => "\x{311A}", + 'abreveacute' => "\x{1EAF}", + 'abrevecyrillic' => "\x{04D1}", + 'abrevedotbelow' => "\x{1EB7}", + 'abrevegrave' => "\x{1EB1}", + 'abrevehookabove' => "\x{1EB3}", + 'abrevetilde' => "\x{1EB5}", + 'acaron' => "\x{01CE}", + 'acircle' => "\x{24D0}", + 'acircumflexacute' => "\x{1EA5}", + 'acircumflexdotbelow' => "\x{1EAD}", + 'acircumflexgrave' => "\x{1EA7}", + 'acircumflexhookabove' => "\x{1EA9}", + 'acircumflextilde' => "\x{1EAB}", + 'acutebelowcmb' => "\x{0317}", + 'acutecmb' => "\x{0301}", + 'acutedeva' => "\x{0954}", + 'acutelowmod' => "\x{02CF}", + 'acutetonecmb' => "\x{0341}", + 'acyrillic' => "\x{0430}", + 'adblgrave' => "\x{0201}", + 'addakgurmukhi' => "\x{0A71}", + 'adeva' => "\x{0905}", + 'adieresiscyrillic' => "\x{04D3}", + 'adieresismacron' => "\x{01DF}", + 'adotbelow' => "\x{1EA1}", + 'adotmacron' => "\x{01E1}", + 'aekorean' => "\x{3150}", + 'aemacron' => "\x{01E3}", + 'afii08941' => "\x{20A4}", + 'afii10063' => "\x{F6C4}", + 'afii10064' => "\x{F6C5}", + 'afii10192' => "\x{F6C6}", + 'afii10831' => "\x{F6C7}", + 'afii10832' => "\x{F6C8}", + 'agujarati' => "\x{0A85}", + 'agurmukhi' => "\x{0A05}", + 'ahiragana' => "\x{3042}", + 'ahookabove' => "\x{1EA3}", + 'aibengali' => "\x{0990}", + 'aibopomofo' => "\x{311E}", + 'aideva' => "\x{0910}", + 'aiecyrillic' => "\x{04D5}", + 'aigujarati' => "\x{0A90}", + 'aigurmukhi' => "\x{0A10}", + 'aimatragurmukhi' => "\x{0A48}", + 'ainarabic' => "\x{0639}", + 'ainfinalarabic' => "\x{FECA}", + 'aininitialarabic' => "\x{FECB}", + 'ainmedialarabic' => "\x{FECC}", + 'ainvertedbreve' => "\x{0203}", + 'aivowelsignbengali' => "\x{09C8}", + 'aivowelsigndeva' => "\x{0948}", + 'aivowelsigngujarati' => "\x{0AC8}", + 'akatakana' => "\x{30A2}", + 'akatakanahalfwidth' => "\x{FF71}", + 'akorean' => "\x{314F}", + 'alef' => "\x{05D0}", + 'alefarabic' => "\x{0627}", + 'alefdageshhebrew' => "\x{FB30}", + 'aleffinalarabic' => "\x{FE8E}", + 'alefhamzaabovearabic' => "\x{0623}", + 'alefhamzaabovefinalarabic' => "\x{FE84}", + 'alefhamzabelowarabic' => "\x{0625}", + 'alefhamzabelowfinalarabic' => "\x{FE88}", + 'alefhebrew' => "\x{05D0}", + 'aleflamedhebrew' => "\x{FB4F}", + 'alefmaddaabovearabic' => "\x{0622}", + 'alefmaddaabovefinalarabic' => "\x{FE82}", + 'alefmaksuraarabic' => "\x{0649}", + 'alefmaksurafinalarabic' => "\x{FEF0}", + 'alefmaksurainitialarabic' => "\x{FEF3}", + 'alefmaksuramedialarabic' => "\x{FEF4}", + 'alefpatahhebrew' => "\x{FB2E}", + 'alefqamatshebrew' => "\x{FB2F}", + 'allequal' => "\x{224C}", + 'amonospace' => "\x{FF41}", + 'ampersandmonospace' => "\x{FF06}", + 'ampersandsmall' => "\x{F726}", + 'amsquare' => "\x{33C2}", + 'anbopomofo' => "\x{3122}", + 'angbopomofo' => "\x{3124}", + 'angkhankhuthai' => "\x{0E5A}", + 'anglebracketleft' => "\x{3008}", + 'anglebracketleftvertical' => "\x{FE3F}", + 'anglebracketright' => "\x{3009}", + 'anglebracketrightvertical' => "\x{FE40}", + 'angstrom' => "\x{212B}", + 'anudattadeva' => "\x{0952}", + 'anusvarabengali' => "\x{0982}", + 'anusvaradeva' => "\x{0902}", + 'anusvaragujarati' => "\x{0A82}", + 'apaatosquare' => "\x{3300}", + 'aparen' => "\x{249C}", + 'apostrophearmenian' => "\x{055A}", + 'apostrophemod' => "\x{02BC}", + 'apple' => "\x{F8FF}", + 'approaches' => "\x{2250}", + 'approxequalorimage' => "\x{2252}", + 'approximatelyequal' => "\x{2245}", + 'araeaekorean' => "\x{318E}", + 'araeakorean' => "\x{318D}", + 'arc' => "\x{2312}", + 'arighthalfring' => "\x{1E9A}", + 'aringbelow' => "\x{1E01}", + 'arrowdashdown' => "\x{21E3}", + 'arrowdashleft' => "\x{21E0}", + 'arrowdashright' => "\x{21E2}", + 'arrowdashup' => "\x{21E1}", + 'arrowdownleft' => "\x{2199}", + 'arrowdownright' => "\x{2198}", + 'arrowdownwhite' => "\x{21E9}", + 'arrowheaddownmod' => "\x{02C5}", + 'arrowheadleftmod' => "\x{02C2}", + 'arrowheadrightmod' => "\x{02C3}", + 'arrowheadupmod' => "\x{02C4}", + 'arrowhorizex' => "\x{F8E7}", + 'arrowleftdbl' => "\x{21D0}", + 'arrowleftdblstroke' => "\x{21CD}", + 'arrowleftoverright' => "\x{21C6}", + 'arrowleftwhite' => "\x{21E6}", + 'arrowrightdblstroke' => "\x{21CF}", + 'arrowrightheavy' => "\x{279E}", + 'arrowrightoverleft' => "\x{21C4}", + 'arrowrightwhite' => "\x{21E8}", + 'arrowtableft' => "\x{21E4}", + 'arrowtabright' => "\x{21E5}", + 'arrowupdownbase' => "\x{21A8}", + 'arrowupleft' => "\x{2196}", + 'arrowupleftofdown' => "\x{21C5}", + 'arrowupright' => "\x{2197}", + 'arrowupwhite' => "\x{21E7}", + 'arrowvertex' => "\x{F8E6}", + 'asciicircummonospace' => "\x{FF3E}", + 'asciitildemonospace' => "\x{FF5E}", + 'ascript' => "\x{0251}", + 'ascriptturned' => "\x{0252}", + 'asmallhiragana' => "\x{3041}", + 'asmallkatakana' => "\x{30A1}", + 'asmallkatakanahalfwidth' => "\x{FF67}", + 'asteriskaltonearabic' => "\x{066D}", + 'asteriskarabic' => "\x{066D}", + 'asteriskmonospace' => "\x{FF0A}", + 'asterisksmall' => "\x{FE61}", + 'asterism' => "\x{2042}", + 'asuperior' => "\x{F6E9}", + 'asymptoticallyequal' => "\x{2243}", + 'atmonospace' => "\x{FF20}", + 'atsmall' => "\x{FE6B}", + 'aturned' => "\x{0250}", + 'aubengali' => "\x{0994}", + 'aubopomofo' => "\x{3120}", + 'audeva' => "\x{0914}", + 'augujarati' => "\x{0A94}", + 'augurmukhi' => "\x{0A14}", + 'aulengthmarkbengali' => "\x{09D7}", + 'aumatragurmukhi' => "\x{0A4C}", + 'auvowelsignbengali' => "\x{09CC}", + 'auvowelsigndeva' => "\x{094C}", + 'auvowelsigngujarati' => "\x{0ACC}", + 'avagrahadeva' => "\x{093D}", + 'aybarmenian' => "\x{0561}", + 'ayin' => "\x{05E2}", + 'ayinaltonehebrew' => "\x{FB20}", + 'ayinhebrew' => "\x{05E2}", + 'babengali' => "\x{09AC}", + 'backslashmonospace' => "\x{FF3C}", + 'badeva' => "\x{092C}", + 'bagujarati' => "\x{0AAC}", + 'bagurmukhi' => "\x{0A2C}", + 'bahiragana' => "\x{3070}", + 'bahtthai' => "\x{0E3F}", + 'bakatakana' => "\x{30D0}", + 'barmonospace' => "\x{FF5C}", + 'bbopomofo' => "\x{3105}", + 'bcircle' => "\x{24D1}", + 'bdotaccent' => "\x{1E03}", + 'bdotbelow' => "\x{1E05}", + 'beamedsixteenthnotes' => "\x{266C}", + 'because' => "\x{2235}", + 'becyrillic' => "\x{0431}", + 'beharabic' => "\x{0628}", + 'behfinalarabic' => "\x{FE90}", + 'behinitialarabic' => "\x{FE91}", + 'behiragana' => "\x{3079}", + 'behmedialarabic' => "\x{FE92}", + 'behmeeminitialarabic' => "\x{FC9F}", + 'behmeemisolatedarabic' => "\x{FC08}", + 'behnoonfinalarabic' => "\x{FC6D}", + 'bekatakana' => "\x{30D9}", + 'benarmenian' => "\x{0562}", + 'bet' => "\x{05D1}", + 'betasymbolgreek' => "\x{03D0}", + 'betdagesh' => "\x{FB31}", + 'betdageshhebrew' => "\x{FB31}", + 'bethebrew' => "\x{05D1}", + 'betrafehebrew' => "\x{FB4C}", + 'bhabengali' => "\x{09AD}", + 'bhadeva' => "\x{092D}", + 'bhagujarati' => "\x{0AAD}", + 'bhagurmukhi' => "\x{0A2D}", + 'bhook' => "\x{0253}", + 'bihiragana' => "\x{3073}", + 'bikatakana' => "\x{30D3}", + 'bilabialclick' => "\x{0298}", + 'bindigurmukhi' => "\x{0A02}", + 'birusquare' => "\x{3331}", + 'blackcircle' => "\x{25CF}", + 'blackdiamond' => "\x{25C6}", + 'blackdownpointingtriangle' => "\x{25BC}", + 'blackleftpointingpointer' => "\x{25C4}", + 'blackleftpointingtriangle' => "\x{25C0}", + 'blacklenticularbracketleft' => "\x{3010}", + 'blacklenticularbracketleftvertical' => "\x{FE3B}", + 'blacklenticularbracketright' => "\x{3011}", + 'blacklenticularbracketrightvertical' => "\x{FE3C}", + 'blacklowerlefttriangle' => "\x{25E3}", + 'blacklowerrighttriangle' => "\x{25E2}", + 'blackrectangle' => "\x{25AC}", + 'blackrightpointingpointer' => "\x{25BA}", + 'blackrightpointingtriangle' => "\x{25B6}", + 'blacksmallsquare' => "\x{25AA}", + 'blacksmilingface' => "\x{263B}", + 'blacksquare' => "\x{25A0}", + 'blackstar' => "\x{2605}", + 'blackupperlefttriangle' => "\x{25E4}", + 'blackupperrighttriangle' => "\x{25E5}", + 'blackuppointingsmalltriangle' => "\x{25B4}", + 'blackuppointingtriangle' => "\x{25B2}", + 'blank' => "\x{2423}", + 'blinebelow' => "\x{1E07}", + 'bmonospace' => "\x{FF42}", + 'bobaimaithai' => "\x{0E1A}", + 'bohiragana' => "\x{307C}", + 'bokatakana' => "\x{30DC}", + 'bparen' => "\x{249D}", + 'bqsquare' => "\x{33C3}", + 'braceex' => "\x{F8F4}", + 'braceleftbt' => "\x{F8F3}", + 'braceleftmid' => "\x{F8F2}", + 'braceleftmonospace' => "\x{FF5B}", + 'braceleftsmall' => "\x{FE5B}", + 'bracelefttp' => "\x{F8F1}", + 'braceleftvertical' => "\x{FE37}", + 'bracerightbt' => "\x{F8FE}", + 'bracerightmid' => "\x{F8FD}", + 'bracerightmonospace' => "\x{FF5D}", + 'bracerightsmall' => "\x{FE5C}", + 'bracerighttp' => "\x{F8FC}", + 'bracerightvertical' => "\x{FE38}", + 'bracketleftbt' => "\x{F8F0}", + 'bracketleftex' => "\x{F8EF}", + 'bracketleftmonospace' => "\x{FF3B}", + 'bracketlefttp' => "\x{F8EE}", + 'bracketrightbt' => "\x{F8FB}", + 'bracketrightex' => "\x{F8FA}", + 'bracketrightmonospace' => "\x{FF3D}", + 'bracketrighttp' => "\x{F8F9}", + 'brevebelowcmb' => "\x{032E}", + 'brevecmb' => "\x{0306}", + 'breveinvertedbelowcmb' => "\x{032F}", + 'breveinvertedcmb' => "\x{0311}", + 'breveinverteddoublecmb' => "\x{0361}", + 'bridgebelowcmb' => "\x{032A}", + 'bridgeinvertedbelowcmb' => "\x{033A}", + 'bstroke' => "\x{0180}", + 'bsuperior' => "\x{F6EA}", + 'btopbar' => "\x{0183}", + 'buhiragana' => "\x{3076}", + 'bukatakana' => "\x{30D6}", + 'bulletinverse' => "\x{25D8}", + 'bulletoperator' => "\x{2219}", + 'bullseye' => "\x{25CE}", + 'caarmenian' => "\x{056E}", + 'cabengali' => "\x{099A}", + 'cadeva' => "\x{091A}", + 'cagujarati' => "\x{0A9A}", + 'cagurmukhi' => "\x{0A1A}", + 'calsquare' => "\x{3388}", + 'candrabindubengali' => "\x{0981}", + 'candrabinducmb' => "\x{0310}", + 'candrabindudeva' => "\x{0901}", + 'candrabindugujarati' => "\x{0A81}", + 'capslock' => "\x{21EA}", + 'careof' => "\x{2105}", + 'caronbelowcmb' => "\x{032C}", + 'caroncmb' => "\x{030C}", + 'cbopomofo' => "\x{3118}", + 'ccedillaacute' => "\x{1E09}", + 'ccircle' => "\x{24D2}", + 'ccurl' => "\x{0255}", + 'cdot' => "\x{010B}", + 'cdsquare' => "\x{33C5}", + 'cedillacmb' => "\x{0327}", + 'centigrade' => "\x{2103}", + 'centinferior' => "\x{F6DF}", + 'centmonospace' => "\x{FFE0}", + 'centoldstyle' => "\x{F7A2}", + 'centsuperior' => "\x{F6E0}", + 'chaarmenian' => "\x{0579}", + 'chabengali' => "\x{099B}", + 'chadeva' => "\x{091B}", + 'chagujarati' => "\x{0A9B}", + 'chagurmukhi' => "\x{0A1B}", + 'chbopomofo' => "\x{3114}", + 'cheabkhasiancyrillic' => "\x{04BD}", + 'checkmark' => "\x{2713}", + 'checyrillic' => "\x{0447}", + 'chedescenderabkhasiancyrillic' => "\x{04BF}", + 'chedescendercyrillic' => "\x{04B7}", + 'chedieresiscyrillic' => "\x{04F5}", + 'cheharmenian' => "\x{0573}", + 'chekhakassiancyrillic' => "\x{04CC}", + 'cheverticalstrokecyrillic' => "\x{04B9}", + 'chieuchacirclekorean' => "\x{3277}", + 'chieuchaparenkorean' => "\x{3217}", + 'chieuchcirclekorean' => "\x{3269}", + 'chieuchkorean' => "\x{314A}", + 'chieuchparenkorean' => "\x{3209}", + 'chochangthai' => "\x{0E0A}", + 'chochanthai' => "\x{0E08}", + 'chochingthai' => "\x{0E09}", + 'chochoethai' => "\x{0E0C}", + 'chook' => "\x{0188}", + 'cieucacirclekorean' => "\x{3276}", + 'cieucaparenkorean' => "\x{3216}", + 'cieuccirclekorean' => "\x{3268}", + 'cieuckorean' => "\x{3148}", + 'cieucparenkorean' => "\x{3208}", + 'cieucuparenkorean' => "\x{321C}", + 'circleot' => "\x{2299}", # Actual Adobe glyph list entry -- identified as typo, May 2008 + 'circledot' => "\x{2299}", # What it should have been + 'circlepostalmark' => "\x{3036}", + 'circlewithlefthalfblack' => "\x{25D0}", + 'circlewithrighthalfblack' => "\x{25D1}", + 'circumflexbelowcmb' => "\x{032D}", + 'circumflexcmb' => "\x{0302}", + 'clear' => "\x{2327}", + 'clickalveolar' => "\x{01C2}", + 'clickdental' => "\x{01C0}", + 'clicklateral' => "\x{01C1}", + 'clickretroflex' => "\x{01C3}", + 'clubsuitblack' => "\x{2663}", + 'clubsuitwhite' => "\x{2667}", + 'cmcubedsquare' => "\x{33A4}", + 'cmonospace' => "\x{FF43}", + 'cmsquaredsquare' => "\x{33A0}", + 'coarmenian' => "\x{0581}", + 'colonmonospace' => "\x{FF1A}", + 'colonsign' => "\x{20A1}", + 'colonsmall' => "\x{FE55}", + 'colontriangularhalfmod' => "\x{02D1}", + 'colontriangularmod' => "\x{02D0}", + 'commaabovecmb' => "\x{0313}", + 'commaaboverightcmb' => "\x{0315}", + 'commaaccent' => "\x{F6C3}", + 'commaarabic' => "\x{060C}", + 'commaarmenian' => "\x{055D}", + 'commainferior' => "\x{F6E1}", + 'commamonospace' => "\x{FF0C}", + 'commareversedabovecmb' => "\x{0314}", + 'commareversedmod' => "\x{02BD}", + 'commasmall' => "\x{FE50}", + 'commasuperior' => "\x{F6E2}", + 'commaturnedabovecmb' => "\x{0312}", + 'commaturnedmod' => "\x{02BB}", + 'compass' => "\x{263C}", + 'contourintegral' => "\x{222E}", + 'control' => "\x{2303}", + 'controlACK' => "\x{0006}", + 'controlBEL' => "\x{0007}", + 'controlBS' => "\x{0008}", + 'controlCAN' => "\x{0018}", + 'controlCR' => "\x{000D}", + 'controlDC1' => "\x{0011}", + 'controlDC2' => "\x{0012}", + 'controlDC3' => "\x{0013}", + 'controlDC4' => "\x{0014}", + 'controlDEL' => "\x{007F}", + 'controlDLE' => "\x{0010}", + 'controlEM' => "\x{0019}", + 'controlENQ' => "\x{0005}", + 'controlEOT' => "\x{0004}", + 'controlESC' => "\x{001B}", + 'controlETB' => "\x{0017}", + 'controlETX' => "\x{0003}", + 'controlFF' => "\x{000C}", + 'controlFS' => "\x{001C}", + 'controlGS' => "\x{001D}", + 'controlHT' => "\x{0009}", + 'controlLF' => "\x{000A}", + 'controlNAK' => "\x{0015}", + 'controlRS' => "\x{001E}", + 'controlSI' => "\x{000F}", + 'controlSO' => "\x{000E}", + 'controlSOT' => "\x{0002}", + 'controlSTX' => "\x{0001}", + 'controlSUB' => "\x{001A}", + 'controlSYN' => "\x{0016}", + 'controlUS' => "\x{001F}", + 'controlVT' => "\x{000B}", + 'copyrightsans' => "\x{F8E9}", + 'copyrightserif' => "\x{F6D9}", + 'cornerbracketleft' => "\x{300C}", + 'cornerbracketlefthalfwidth' => "\x{FF62}", + 'cornerbracketleftvertical' => "\x{FE41}", + 'cornerbracketright' => "\x{300D}", + 'cornerbracketrighthalfwidth' => "\x{FF63}", + 'cornerbracketrightvertical' => "\x{FE42}", + 'corporationsquare' => "\x{337F}", + 'cosquare' => "\x{33C7}", + 'coverkgsquare' => "\x{33C6}", + 'cparen' => "\x{249E}", + 'cruzeiro' => "\x{20A2}", + 'cstretched' => "\x{0297}", + 'curlyand' => "\x{22CF}", + 'curlyor' => "\x{22CE}", + 'cyrBreve' => "\x{F6D1}", + 'cyrFlex' => "\x{F6D2}", + 'cyrbreve' => "\x{F6D4}", + 'cyrflex' => "\x{F6D5}", + 'daarmenian' => "\x{0564}", + 'dabengali' => "\x{09A6}", + 'dadarabic' => "\x{0636}", + 'dadeva' => "\x{0926}", + 'dadfinalarabic' => "\x{FEBE}", + 'dadinitialarabic' => "\x{FEBF}", + 'dadmedialarabic' => "\x{FEC0}", + 'dagesh' => "\x{05BC}", + 'dageshhebrew' => "\x{05BC}", + 'dagujarati' => "\x{0AA6}", + 'dagurmukhi' => "\x{0A26}", + 'dahiragana' => "\x{3060}", + 'dakatakana' => "\x{30C0}", + 'dalarabic' => "\x{062F}", + 'dalet' => "\x{05D3}", + 'daletdagesh' => "\x{FB33}", + 'daletdageshhebrew' => "\x{FB33}", + 'dalethatafpatah' => "\x{05D3}\x{05B2}", + 'dalethatafpatahhebrew' => "\x{05D3}\x{05B2}", + 'dalethatafsegol' => "\x{05D3}\x{05B1}", + 'dalethatafsegolhebrew' => "\x{05D3}\x{05B1}", + 'dalethebrew' => "\x{05D3}", + 'dalethiriq' => "\x{05D3}\x{05B4}", + 'dalethiriqhebrew' => "\x{05D3}\x{05B4}", + 'daletholam' => "\x{05D3}\x{05B9}", + 'daletholamhebrew' => "\x{05D3}\x{05B9}", + 'daletpatah' => "\x{05D3}\x{05B7}", + 'daletpatahhebrew' => "\x{05D3}\x{05B7}", + 'daletqamats' => "\x{05D3}\x{05B8}", + 'daletqamatshebrew' => "\x{05D3}\x{05B8}", + 'daletqubuts' => "\x{05D3}\x{05BB}", + 'daletqubutshebrew' => "\x{05D3}\x{05BB}", + 'daletsegol' => "\x{05D3}\x{05B6}", + 'daletsegolhebrew' => "\x{05D3}\x{05B6}", + 'daletsheva' => "\x{05D3}\x{05B0}", + 'daletshevahebrew' => "\x{05D3}\x{05B0}", + 'dalettsere' => "\x{05D3}\x{05B5}", + 'dalettserehebrew' => "\x{05D3}\x{05B5}", + 'dalfinalarabic' => "\x{FEAA}", + 'dammaarabic' => "\x{064F}", + 'dammalowarabic' => "\x{064F}", + 'dammatanaltonearabic' => "\x{064C}", + 'dammatanarabic' => "\x{064C}", + 'danda' => "\x{0964}", + 'dargahebrew' => "\x{05A7}", + 'dargalefthebrew' => "\x{05A7}", + 'dasiapneumatacyrilliccmb' => "\x{0485}", + 'dblGrave' => "\x{F6D3}", + 'dblanglebracketleft' => "\x{300A}", + 'dblanglebracketleftvertical' => "\x{FE3D}", + 'dblanglebracketright' => "\x{300B}", + 'dblanglebracketrightvertical' => "\x{FE3E}", + 'dblarchinvertedbelowcmb' => "\x{032B}", + 'dblarrowleft' => "\x{21D4}", + 'dblarrowright' => "\x{21D2}", + 'dbldanda' => "\x{0965}", + 'dblgrave' => "\x{F6D6}", + 'dblgravecmb' => "\x{030F}", + 'dblintegral' => "\x{222C}", + 'dbllowline' => "\x{2017}", + 'dbllowlinecmb' => "\x{0333}", + 'dbloverlinecmb' => "\x{033F}", + 'dblprimemod' => "\x{02BA}", + 'dblverticalbar' => "\x{2016}", + 'dblverticallineabovecmb' => "\x{030E}", + 'dbopomofo' => "\x{3109}", + 'dbsquare' => "\x{33C8}", + 'dcedilla' => "\x{1E11}", + 'dcircle' => "\x{24D3}", + 'dcircumflexbelow' => "\x{1E13}", + 'ddabengali' => "\x{09A1}", + 'ddadeva' => "\x{0921}", + 'ddagujarati' => "\x{0AA1}", + 'ddagurmukhi' => "\x{0A21}", + 'ddalarabic' => "\x{0688}", + 'ddalfinalarabic' => "\x{FB89}", + 'dddhadeva' => "\x{095C}", + 'ddhabengali' => "\x{09A2}", + 'ddhadeva' => "\x{0922}", + 'ddhagujarati' => "\x{0AA2}", + 'ddhagurmukhi' => "\x{0A22}", + 'ddotaccent' => "\x{1E0B}", + 'ddotbelow' => "\x{1E0D}", + 'decimalseparatorarabic' => "\x{066B}", + 'decimalseparatorpersian' => "\x{066B}", + 'decyrillic' => "\x{0434}", + 'dehihebrew' => "\x{05AD}", + 'dehiragana' => "\x{3067}", + 'deicoptic' => "\x{03EF}", + 'dekatakana' => "\x{30C7}", + 'deleteleft' => "\x{232B}", + 'deleteright' => "\x{2326}", + 'deltaturned' => "\x{018D}", + 'denominatorminusonenumeratorbengali' => "\x{09F8}", + 'dezh' => "\x{02A4}", + 'dhabengali' => "\x{09A7}", + 'dhadeva' => "\x{0927}", + 'dhagujarati' => "\x{0AA7}", + 'dhagurmukhi' => "\x{0A27}", + 'dhook' => "\x{0257}", + 'dialytikatonos' => "\x{0385}", + 'dialytikatonoscmb' => "\x{0344}", + 'diamondsuitwhite' => "\x{2662}", + 'dieresisacute' => "\x{F6D7}", + 'dieresisbelowcmb' => "\x{0324}", + 'dieresiscmb' => "\x{0308}", + 'dieresisgrave' => "\x{F6D8}", + 'dihiragana' => "\x{3062}", + 'dikatakana' => "\x{30C2}", + 'dittomark' => "\x{3003}", + 'divides' => "\x{2223}", + 'divisionslash' => "\x{2215}", + 'djecyrillic' => "\x{0452}", + 'dlinebelow' => "\x{1E0F}", + 'dlsquare' => "\x{3397}", + 'dmacron' => "\x{0111}", + 'dmonospace' => "\x{FF44}", + 'dochadathai' => "\x{0E0E}", + 'dodekthai' => "\x{0E14}", + 'dohiragana' => "\x{3069}", + 'dokatakana' => "\x{30C9}", + 'dollarinferior' => "\x{F6E3}", + 'dollarmonospace' => "\x{FF04}", + 'dollaroldstyle' => "\x{F724}", + 'dollarsmall' => "\x{FE69}", + 'dollarsuperior' => "\x{F6E4}", + 'dorusquare' => "\x{3326}", + 'dotaccentcmb' => "\x{0307}", + 'dotbelowcmb' => "\x{0323}", + 'dotkatakana' => "\x{30FB}", + 'dotlessj' => "\x{F6BE}", + 'dotlessjstrokehook' => "\x{0284}", + 'dottedcircle' => "\x{25CC}", + 'doubleyodpatah' => "\x{FB1F}", + 'doubleyodpatahhebrew' => "\x{FB1F}", + 'downtackbelowcmb' => "\x{031E}", + 'downtackmod' => "\x{02D5}", + 'dparen' => "\x{249F}", + 'dsuperior' => "\x{F6EB}", + 'dtail' => "\x{0256}", + 'dtopbar' => "\x{018C}", + 'duhiragana' => "\x{3065}", + 'dukatakana' => "\x{30C5}", + 'dz' => "\x{01F3}", + 'dzaltone' => "\x{02A3}", + 'dzcaron' => "\x{01C6}", + 'dzcurl' => "\x{02A5}", + 'dzeabkhasiancyrillic' => "\x{04E1}", + 'dzecyrillic' => "\x{0455}", + 'dzhecyrillic' => "\x{045F}", + 'earth' => "\x{2641}", + 'ebengali' => "\x{098F}", + 'ebopomofo' => "\x{311C}", + 'ecandradeva' => "\x{090D}", + 'ecandragujarati' => "\x{0A8D}", + 'ecandravowelsigndeva' => "\x{0945}", + 'ecandravowelsigngujarati' => "\x{0AC5}", + 'ecedillabreve' => "\x{1E1D}", + 'echarmenian' => "\x{0565}", + 'echyiwnarmenian' => "\x{0587}", + 'ecircle' => "\x{24D4}", + 'ecircumflexacute' => "\x{1EBF}", + 'ecircumflexbelow' => "\x{1E19}", + 'ecircumflexdotbelow' => "\x{1EC7}", + 'ecircumflexgrave' => "\x{1EC1}", + 'ecircumflexhookabove' => "\x{1EC3}", + 'ecircumflextilde' => "\x{1EC5}", + 'ecyrillic' => "\x{0454}", + 'edblgrave' => "\x{0205}", + 'edeva' => "\x{090F}", + 'edot' => "\x{0117}", + 'edotbelow' => "\x{1EB9}", + 'eegurmukhi' => "\x{0A0F}", + 'eematragurmukhi' => "\x{0A47}", + 'efcyrillic' => "\x{0444}", + 'egujarati' => "\x{0A8F}", + 'eharmenian' => "\x{0567}", + 'ehbopomofo' => "\x{311D}", + 'ehiragana' => "\x{3048}", + 'ehookabove' => "\x{1EBB}", + 'eibopomofo' => "\x{311F}", + 'eightarabic' => "\x{0668}", + 'eightbengali' => "\x{09EE}", + 'eightcircle' => "\x{2467}", + 'eightcircleinversesansserif' => "\x{2791}", + 'eightdeva' => "\x{096E}", + 'eighteencircle' => "\x{2471}", + 'eighteenparen' => "\x{2485}", + 'eighteenperiod' => "\x{2499}", + 'eightgujarati' => "\x{0AEE}", + 'eightgurmukhi' => "\x{0A6E}", + 'eighthackarabic' => "\x{0668}", + 'eighthangzhou' => "\x{3028}", + 'eighthnotebeamed' => "\x{266B}", + 'eightideographicparen' => "\x{3227}", + 'eightinferior' => "\x{2088}", + 'eightmonospace' => "\x{FF18}", + 'eightoldstyle' => "\x{F738}", + 'eightparen' => "\x{247B}", + 'eightperiod' => "\x{248F}", + 'eightpersian' => "\x{06F8}", + 'eightroman' => "\x{2177}", + 'eightsuperior' => "\x{2078}", + 'eightthai' => "\x{0E58}", + 'einvertedbreve' => "\x{0207}", + 'eiotifiedcyrillic' => "\x{0465}", + 'ekatakana' => "\x{30A8}", + 'ekatakanahalfwidth' => "\x{FF74}", + 'ekonkargurmukhi' => "\x{0A74}", + 'ekorean' => "\x{3154}", + 'elcyrillic' => "\x{043B}", + 'elevencircle' => "\x{246A}", + 'elevenparen' => "\x{247E}", + 'elevenperiod' => "\x{2492}", + 'elevenroman' => "\x{217A}", + 'ellipsisvertical' => "\x{22EE}", + 'emacronacute' => "\x{1E17}", + 'emacrongrave' => "\x{1E15}", + 'emcyrillic' => "\x{043C}", + 'emdashvertical' => "\x{FE31}", + 'emonospace' => "\x{FF45}", + 'emphasismarkarmenian' => "\x{055B}", + 'enbopomofo' => "\x{3123}", + 'encyrillic' => "\x{043D}", + 'endashvertical' => "\x{FE32}", + 'endescendercyrillic' => "\x{04A3}", + 'engbopomofo' => "\x{3125}", + 'enghecyrillic' => "\x{04A5}", + 'enhookcyrillic' => "\x{04C8}", + 'enspace' => "\x{2002}", + 'eokorean' => "\x{3153}", + 'eopen' => "\x{025B}", + 'eopenclosed' => "\x{029A}", + 'eopenreversed' => "\x{025C}", + 'eopenreversedclosed' => "\x{025E}", + 'eopenreversedhook' => "\x{025D}", + 'eparen' => "\x{24A0}", + 'equalmonospace' => "\x{FF1D}", + 'equalsmall' => "\x{FE66}", + 'equalsuperior' => "\x{207C}", + 'erbopomofo' => "\x{3126}", + 'ercyrillic' => "\x{0440}", + 'ereversed' => "\x{0258}", + 'ereversedcyrillic' => "\x{044D}", + 'escyrillic' => "\x{0441}", + 'esdescendercyrillic' => "\x{04AB}", + 'esh' => "\x{0283}", + 'eshcurl' => "\x{0286}", + 'eshortdeva' => "\x{090E}", + 'eshortvowelsigndeva' => "\x{0946}", + 'eshreversedloop' => "\x{01AA}", + 'eshsquatreversed' => "\x{0285}", + 'esmallhiragana' => "\x{3047}", + 'esmallkatakana' => "\x{30A7}", + 'esmallkatakanahalfwidth' => "\x{FF6A}", + 'esuperior' => "\x{F6EC}", + 'etarmenian' => "\x{0568}", + 'etilde' => "\x{1EBD}", + 'etildebelow' => "\x{1E1B}", + 'etnahtafoukhhebrew' => "\x{0591}", + 'etnahtafoukhlefthebrew' => "\x{0591}", + 'etnahtahebrew' => "\x{0591}", + 'etnahtalefthebrew' => "\x{0591}", + 'eturned' => "\x{01DD}", + 'eukorean' => "\x{3161}", + 'euro' => "\x{20AC}", + 'evowelsignbengali' => "\x{09C7}", + 'evowelsigndeva' => "\x{0947}", + 'evowelsigngujarati' => "\x{0AC7}", + 'exclamarmenian' => "\x{055C}", + 'exclamdownsmall' => "\x{F7A1}", + 'exclammonospace' => "\x{FF01}", + 'exclamsmall' => "\x{F721}", + 'ezh' => "\x{0292}", + 'ezhcaron' => "\x{01EF}", + 'ezhcurl' => "\x{0293}", + 'ezhreversed' => "\x{01B9}", + 'ezhtail' => "\x{01BA}", + 'fadeva' => "\x{095E}", + 'fagurmukhi' => "\x{0A5E}", + 'fahrenheit' => "\x{2109}", + 'fathaarabic' => "\x{064E}", + 'fathalowarabic' => "\x{064E}", + 'fathatanarabic' => "\x{064B}", + 'fbopomofo' => "\x{3108}", + 'fcircle' => "\x{24D5}", + 'fdotaccent' => "\x{1E1F}", + 'feharabic' => "\x{0641}", + 'feharmenian' => "\x{0586}", + 'fehfinalarabic' => "\x{FED2}", + 'fehinitialarabic' => "\x{FED3}", + 'fehmedialarabic' => "\x{FED4}", + 'feicoptic' => "\x{03E5}", + 'fifteencircle' => "\x{246E}", + 'fifteenparen' => "\x{2482}", + 'fifteenperiod' => "\x{2496}", + 'finalkaf' => "\x{05DA}", + 'finalkafdagesh' => "\x{FB3A}", + 'finalkafdageshhebrew' => "\x{FB3A}", + 'finalkafhebrew' => "\x{05DA}", + 'finalkafqamats' => "\x{05DA}\x{05B8}", + 'finalkafqamatshebrew' => "\x{05DA}\x{05B8}", + 'finalkafsheva' => "\x{05DA}\x{05B0}", + 'finalkafshevahebrew' => "\x{05DA}\x{05B0}", + 'finalmem' => "\x{05DD}", + 'finalmemhebrew' => "\x{05DD}", + 'finalnun' => "\x{05DF}", + 'finalnunhebrew' => "\x{05DF}", + 'finalpe' => "\x{05E3}", + 'finalpehebrew' => "\x{05E3}", + 'finaltsadi' => "\x{05E5}", + 'finaltsadihebrew' => "\x{05E5}", + 'firsttonechinese' => "\x{02C9}", + 'fisheye' => "\x{25C9}", + 'fitacyrillic' => "\x{0473}", + 'fivearabic' => "\x{0665}", + 'fivebengali' => "\x{09EB}", + 'fivecircle' => "\x{2464}", + 'fivecircleinversesansserif' => "\x{278E}", + 'fivedeva' => "\x{096B}", + 'fivegujarati' => "\x{0AEB}", + 'fivegurmukhi' => "\x{0A6B}", + 'fivehackarabic' => "\x{0665}", + 'fivehangzhou' => "\x{3025}", + 'fiveideographicparen' => "\x{3224}", + 'fiveinferior' => "\x{2085}", + 'fivemonospace' => "\x{FF15}", + 'fiveoldstyle' => "\x{F735}", + 'fiveparen' => "\x{2478}", + 'fiveperiod' => "\x{248C}", + 'fivepersian' => "\x{06F5}", + 'fiveroman' => "\x{2174}", + 'fivesuperior' => "\x{2075}", + 'fivethai' => "\x{0E55}", + 'fmonospace' => "\x{FF46}", + 'fmsquare' => "\x{3399}", + 'fofanthai' => "\x{0E1F}", + 'fofathai' => "\x{0E1D}", + 'fongmanthai' => "\x{0E4F}", + 'forall' => "\x{2200}", + 'fourarabic' => "\x{0664}", + 'fourbengali' => "\x{09EA}", + 'fourcircle' => "\x{2463}", + 'fourcircleinversesansserif' => "\x{278D}", + 'fourdeva' => "\x{096A}", + 'fourgujarati' => "\x{0AEA}", + 'fourgurmukhi' => "\x{0A6A}", + 'fourhackarabic' => "\x{0664}", + 'fourhangzhou' => "\x{3024}", + 'fourideographicparen' => "\x{3223}", + 'fourinferior' => "\x{2084}", + 'fourmonospace' => "\x{FF14}", + 'fournumeratorbengali' => "\x{09F7}", + 'fouroldstyle' => "\x{F734}", + 'fourparen' => "\x{2477}", + 'fourperiod' => "\x{248B}", + 'fourpersian' => "\x{06F4}", + 'fourroman' => "\x{2173}", + 'foursuperior' => "\x{2074}", + 'fourteencircle' => "\x{246D}", + 'fourteenparen' => "\x{2481}", + 'fourteenperiod' => "\x{2495}", + 'fourthai' => "\x{0E54}", + 'fourthtonechinese' => "\x{02CB}", + 'fparen' => "\x{24A1}", + 'gabengali' => "\x{0997}", + 'gacute' => "\x{01F5}", + 'gadeva' => "\x{0917}", + 'gafarabic' => "\x{06AF}", + 'gaffinalarabic' => "\x{FB93}", + 'gafinitialarabic' => "\x{FB94}", + 'gafmedialarabic' => "\x{FB95}", + 'gagujarati' => "\x{0A97}", + 'gagurmukhi' => "\x{0A17}", + 'gahiragana' => "\x{304C}", + 'gakatakana' => "\x{30AC}", + 'gammalatinsmall' => "\x{0263}", + 'gammasuperior' => "\x{02E0}", + 'gangiacoptic' => "\x{03EB}", + 'gbopomofo' => "\x{310D}", + 'gcedilla' => "\x{0123}", + 'gcircle' => "\x{24D6}", + 'gdot' => "\x{0121}", + 'gecyrillic' => "\x{0433}", + 'gehiragana' => "\x{3052}", + 'gekatakana' => "\x{30B2}", + 'geometricallyequal' => "\x{2251}", + 'gereshaccenthebrew' => "\x{059C}", + 'gereshhebrew' => "\x{05F3}", + 'gereshmuqdamhebrew' => "\x{059D}", + 'gershayimaccenthebrew' => "\x{059E}", + 'gershayimhebrew' => "\x{05F4}", + 'getamark' => "\x{3013}", + 'ghabengali' => "\x{0998}", + 'ghadarmenian' => "\x{0572}", + 'ghadeva' => "\x{0918}", + 'ghagujarati' => "\x{0A98}", + 'ghagurmukhi' => "\x{0A18}", + 'ghainarabic' => "\x{063A}", + 'ghainfinalarabic' => "\x{FECE}", + 'ghaininitialarabic' => "\x{FECF}", + 'ghainmedialarabic' => "\x{FED0}", + 'ghemiddlehookcyrillic' => "\x{0495}", + 'ghestrokecyrillic' => "\x{0493}", + 'gheupturncyrillic' => "\x{0491}", + 'ghhadeva' => "\x{095A}", + 'ghhagurmukhi' => "\x{0A5A}", + 'ghook' => "\x{0260}", + 'ghzsquare' => "\x{3393}", + 'gihiragana' => "\x{304E}", + 'gikatakana' => "\x{30AE}", + 'gimarmenian' => "\x{0563}", + 'gimel' => "\x{05D2}", + 'gimeldagesh' => "\x{FB32}", + 'gimeldageshhebrew' => "\x{FB32}", + 'gimelhebrew' => "\x{05D2}", + 'gjecyrillic' => "\x{0453}", + 'glottalinvertedstroke' => "\x{01BE}", + 'glottalstop' => "\x{0294}", + 'glottalstopinverted' => "\x{0296}", + 'glottalstopmod' => "\x{02C0}", + 'glottalstopreversed' => "\x{0295}", + 'glottalstopreversedmod' => "\x{02C1}", + 'glottalstopreversedsuperior' => "\x{02E4}", + 'glottalstopstroke' => "\x{02A1}", + 'glottalstopstrokereversed' => "\x{02A2}", + 'gmacron' => "\x{1E21}", + 'gmonospace' => "\x{FF47}", + 'gohiragana' => "\x{3054}", + 'gokatakana' => "\x{30B4}", + 'gparen' => "\x{24A2}", + 'gpasquare' => "\x{33AC}", + 'gravebelowcmb' => "\x{0316}", + 'gravecmb' => "\x{0300}", + 'gravedeva' => "\x{0953}", + 'gravelowmod' => "\x{02CE}", + 'gravemonospace' => "\x{FF40}", + 'gravetonecmb' => "\x{0340}", + 'greaterequalorless' => "\x{22DB}", + 'greatermonospace' => "\x{FF1E}", + 'greaterorequivalent' => "\x{2273}", + 'greaterorless' => "\x{2277}", + 'greateroverequal' => "\x{2267}", + 'greatersmall' => "\x{FE65}", + 'gscript' => "\x{0261}", + 'gstroke' => "\x{01E5}", + 'guhiragana' => "\x{3050}", + 'gukatakana' => "\x{30B0}", + 'guramusquare' => "\x{3318}", + 'gysquare' => "\x{33C9}", + 'haabkhasiancyrillic' => "\x{04A9}", + 'haaltonearabic' => "\x{06C1}", + 'habengali' => "\x{09B9}", + 'hadescendercyrillic' => "\x{04B3}", + 'hadeva' => "\x{0939}", + 'hagujarati' => "\x{0AB9}", + 'hagurmukhi' => "\x{0A39}", + 'haharabic' => "\x{062D}", + 'hahfinalarabic' => "\x{FEA2}", + 'hahinitialarabic' => "\x{FEA3}", + 'hahiragana' => "\x{306F}", + 'hahmedialarabic' => "\x{FEA4}", + 'haitusquare' => "\x{332A}", + 'hakatakana' => "\x{30CF}", + 'hakatakanahalfwidth' => "\x{FF8A}", + 'halantgurmukhi' => "\x{0A4D}", + 'hamzaarabic' => "\x{0621}", + 'hamzadammaarabic' => "\x{0621}\x{064F}", + 'hamzadammatanarabic' => "\x{0621}\x{064C}", + 'hamzafathaarabic' => "\x{0621}\x{064E}", + 'hamzafathatanarabic' => "\x{0621}\x{064B}", + 'hamzalowarabic' => "\x{0621}", + 'hamzalowkasraarabic' => "\x{0621}\x{0650}", + 'hamzalowkasratanarabic' => "\x{0621}\x{064D}", + 'hamzasukunarabic' => "\x{0621}\x{0652}", + 'hangulfiller' => "\x{3164}", + 'hardsigncyrillic' => "\x{044A}", + 'harpoonleftbarbup' => "\x{21BC}", + 'harpoonrightbarbup' => "\x{21C0}", + 'hasquare' => "\x{33CA}", + 'hatafpatah' => "\x{05B2}", + 'hatafpatah16' => "\x{05B2}", + 'hatafpatah23' => "\x{05B2}", + 'hatafpatah2f' => "\x{05B2}", + 'hatafpatahhebrew' => "\x{05B2}", + 'hatafpatahnarrowhebrew' => "\x{05B2}", + 'hatafpatahquarterhebrew' => "\x{05B2}", + 'hatafpatahwidehebrew' => "\x{05B2}", + 'hatafqamats' => "\x{05B3}", + 'hatafqamats1b' => "\x{05B3}", + 'hatafqamats28' => "\x{05B3}", + 'hatafqamats34' => "\x{05B3}", + 'hatafqamatshebrew' => "\x{05B3}", + 'hatafqamatsnarrowhebrew' => "\x{05B3}", + 'hatafqamatsquarterhebrew' => "\x{05B3}", + 'hatafqamatswidehebrew' => "\x{05B3}", + 'hatafsegol' => "\x{05B1}", + 'hatafsegol17' => "\x{05B1}", + 'hatafsegol24' => "\x{05B1}", + 'hatafsegol30' => "\x{05B1}", + 'hatafsegolhebrew' => "\x{05B1}", + 'hatafsegolnarrowhebrew' => "\x{05B1}", + 'hatafsegolquarterhebrew' => "\x{05B1}", + 'hatafsegolwidehebrew' => "\x{05B1}", + 'hbopomofo' => "\x{310F}", + 'hbrevebelow' => "\x{1E2B}", + 'hcedilla' => "\x{1E29}", + 'hcircle' => "\x{24D7}", + 'hdieresis' => "\x{1E27}", + 'hdotaccent' => "\x{1E23}", + 'hdotbelow' => "\x{1E25}", + 'he' => "\x{05D4}", + 'heartsuitblack' => "\x{2665}", + 'heartsuitwhite' => "\x{2661}", + 'hedagesh' => "\x{FB34}", + 'hedageshhebrew' => "\x{FB34}", + 'hehaltonearabic' => "\x{06C1}", + 'heharabic' => "\x{0647}", + 'hehebrew' => "\x{05D4}", + 'hehfinalaltonearabic' => "\x{FBA7}", + 'hehfinalalttwoarabic' => "\x{FEEA}", + 'hehfinalarabic' => "\x{FEEA}", + 'hehhamzaabovefinalarabic' => "\x{FBA5}", + 'hehhamzaaboveisolatedarabic' => "\x{FBA4}", + 'hehinitialaltonearabic' => "\x{FBA8}", + 'hehinitialarabic' => "\x{FEEB}", + 'hehiragana' => "\x{3078}", + 'hehmedialaltonearabic' => "\x{FBA9}", + 'hehmedialarabic' => "\x{FEEC}", + 'heiseierasquare' => "\x{337B}", + 'hekatakana' => "\x{30D8}", + 'hekatakanahalfwidth' => "\x{FF8D}", + 'hekutaarusquare' => "\x{3336}", + 'henghook' => "\x{0267}", + 'herutusquare' => "\x{3339}", + 'het' => "\x{05D7}", + 'hethebrew' => "\x{05D7}", + 'hhook' => "\x{0266}", + 'hhooksuperior' => "\x{02B1}", + 'hieuhacirclekorean' => "\x{327B}", + 'hieuhaparenkorean' => "\x{321B}", + 'hieuhcirclekorean' => "\x{326D}", + 'hieuhkorean' => "\x{314E}", + 'hieuhparenkorean' => "\x{320D}", + 'hihiragana' => "\x{3072}", + 'hikatakana' => "\x{30D2}", + 'hikatakanahalfwidth' => "\x{FF8B}", + 'hiriq' => "\x{05B4}", + 'hiriq14' => "\x{05B4}", + 'hiriq21' => "\x{05B4}", + 'hiriq2d' => "\x{05B4}", + 'hiriqhebrew' => "\x{05B4}", + 'hiriqnarrowhebrew' => "\x{05B4}", + 'hiriqquarterhebrew' => "\x{05B4}", + 'hiriqwidehebrew' => "\x{05B4}", + 'hlinebelow' => "\x{1E96}", + 'hmonospace' => "\x{FF48}", + 'hoarmenian' => "\x{0570}", + 'hohipthai' => "\x{0E2B}", + 'hohiragana' => "\x{307B}", + 'hokatakana' => "\x{30DB}", + 'hokatakanahalfwidth' => "\x{FF8E}", + 'holam' => "\x{05B9}", + 'holam19' => "\x{05B9}", + 'holam26' => "\x{05B9}", + 'holam32' => "\x{05B9}", + 'holamhebrew' => "\x{05B9}", + 'holamnarrowhebrew' => "\x{05B9}", + 'holamquarterhebrew' => "\x{05B9}", + 'holamwidehebrew' => "\x{05B9}", + 'honokhukthai' => "\x{0E2E}", + 'hookcmb' => "\x{0309}", + 'hookpalatalizedbelowcmb' => "\x{0321}", + 'hookretroflexbelowcmb' => "\x{0322}", + 'hoonsquare' => "\x{3342}", + 'horicoptic' => "\x{03E9}", + 'horizontalbar' => "\x{2015}", + 'horncmb' => "\x{031B}", + 'hotsprings' => "\x{2668}", + 'hparen' => "\x{24A3}", + 'hsuperior' => "\x{02B0}", + 'hturned' => "\x{0265}", + 'huhiragana' => "\x{3075}", + 'huiitosquare' => "\x{3333}", + 'hukatakana' => "\x{30D5}", + 'hukatakanahalfwidth' => "\x{FF8C}", + 'hungarumlautcmb' => "\x{030B}", + 'hv' => "\x{0195}", + 'hypheninferior' => "\x{F6E5}", + 'hyphenmonospace' => "\x{FF0D}", + 'hyphensmall' => "\x{FE63}", + 'hyphensuperior' => "\x{F6E6}", + 'hyphentwo' => "\x{2010}", + 'iacyrillic' => "\x{044F}", + 'ibengali' => "\x{0987}", + 'ibopomofo' => "\x{3127}", + 'icaron' => "\x{01D0}", + 'icircle' => "\x{24D8}", + 'icyrillic' => "\x{0456}", + 'idblgrave' => "\x{0209}", + 'ideographearthcircle' => "\x{328F}", + 'ideographfirecircle' => "\x{328B}", + 'ideographicallianceparen' => "\x{323F}", + 'ideographiccallparen' => "\x{323A}", + 'ideographiccentrecircle' => "\x{32A5}", + 'ideographicclose' => "\x{3006}", + 'ideographiccomma' => "\x{3001}", + 'ideographiccommaleft' => "\x{FF64}", + 'ideographiccongratulationparen' => "\x{3237}", + 'ideographiccorrectcircle' => "\x{32A3}", + 'ideographicearthparen' => "\x{322F}", + 'ideographicenterpriseparen' => "\x{323D}", + 'ideographicexcellentcircle' => "\x{329D}", + 'ideographicfestivalparen' => "\x{3240}", + 'ideographicfinancialcircle' => "\x{3296}", + 'ideographicfinancialparen' => "\x{3236}", + 'ideographicfireparen' => "\x{322B}", + 'ideographichaveparen' => "\x{3232}", + 'ideographichighcircle' => "\x{32A4}", + 'ideographiciterationmark' => "\x{3005}", + 'ideographiclaborcircle' => "\x{3298}", + 'ideographiclaborparen' => "\x{3238}", + 'ideographicleftcircle' => "\x{32A7}", + 'ideographiclowcircle' => "\x{32A6}", + 'ideographicmedicinecircle' => "\x{32A9}", + 'ideographicmetalparen' => "\x{322E}", + 'ideographicmoonparen' => "\x{322A}", + 'ideographicnameparen' => "\x{3234}", + 'ideographicperiod' => "\x{3002}", + 'ideographicprintcircle' => "\x{329E}", + 'ideographicreachparen' => "\x{3243}", + 'ideographicrepresentparen' => "\x{3239}", + 'ideographicresourceparen' => "\x{323E}", + 'ideographicrightcircle' => "\x{32A8}", + 'ideographicsecretcircle' => "\x{3299}", + 'ideographicselfparen' => "\x{3242}", + 'ideographicsocietyparen' => "\x{3233}", + 'ideographicspace' => "\x{3000}", + 'ideographicspecialparen' => "\x{3235}", + 'ideographicstockparen' => "\x{3231}", + 'ideographicstudyparen' => "\x{323B}", + 'ideographicsunparen' => "\x{3230}", + 'ideographicsuperviseparen' => "\x{323C}", + 'ideographicwaterparen' => "\x{322C}", + 'ideographicwoodparen' => "\x{322D}", + 'ideographiczero' => "\x{3007}", + 'ideographmetalcircle' => "\x{328E}", + 'ideographmooncircle' => "\x{328A}", + 'ideographnamecircle' => "\x{3294}", + 'ideographsuncircle' => "\x{3290}", + 'ideographwatercircle' => "\x{328C}", + 'ideographwoodcircle' => "\x{328D}", + 'ideva' => "\x{0907}", + 'idieresisacute' => "\x{1E2F}", + 'idieresiscyrillic' => "\x{04E5}", + 'idotbelow' => "\x{1ECB}", + 'iebrevecyrillic' => "\x{04D7}", + 'iecyrillic' => "\x{0435}", + 'ieungacirclekorean' => "\x{3275}", + 'ieungaparenkorean' => "\x{3215}", + 'ieungcirclekorean' => "\x{3267}", + 'ieungkorean' => "\x{3147}", + 'ieungparenkorean' => "\x{3207}", + 'igujarati' => "\x{0A87}", + 'igurmukhi' => "\x{0A07}", + 'ihiragana' => "\x{3044}", + 'ihookabove' => "\x{1EC9}", + 'iibengali' => "\x{0988}", + 'iicyrillic' => "\x{0438}", + 'iideva' => "\x{0908}", + 'iigujarati' => "\x{0A88}", + 'iigurmukhi' => "\x{0A08}", + 'iimatragurmukhi' => "\x{0A40}", + 'iinvertedbreve' => "\x{020B}", + 'iishortcyrillic' => "\x{0439}", + 'iivowelsignbengali' => "\x{09C0}", + 'iivowelsigndeva' => "\x{0940}", + 'iivowelsigngujarati' => "\x{0AC0}", + 'ikatakana' => "\x{30A4}", + 'ikatakanahalfwidth' => "\x{FF72}", + 'ikorean' => "\x{3163}", + 'ilde' => "\x{02DC}", + 'iluyhebrew' => "\x{05AC}", + 'imacroncyrillic' => "\x{04E3}", + 'imageorapproximatelyequal' => "\x{2253}", + 'imatragurmukhi' => "\x{0A3F}", + 'imonospace' => "\x{FF49}", + 'increment' => "\x{2206}", + 'iniarmenian' => "\x{056B}", + 'integralbottom' => "\x{2321}", + 'integralex' => "\x{F8F5}", + 'integraltop' => "\x{2320}", + 'intisquare' => "\x{3305}", + 'iocyrillic' => "\x{0451}", + 'iotalatin' => "\x{0269}", + 'iparen' => "\x{24A4}", + 'irigurmukhi' => "\x{0A72}", + 'ismallhiragana' => "\x{3043}", + 'ismallkatakana' => "\x{30A3}", + 'ismallkatakanahalfwidth' => "\x{FF68}", + 'issharbengali' => "\x{09FA}", + 'istroke' => "\x{0268}", + 'isuperior' => "\x{F6ED}", + 'iterationhiragana' => "\x{309D}", + 'iterationkatakana' => "\x{30FD}", + 'itildebelow' => "\x{1E2D}", + 'iubopomofo' => "\x{3129}", + 'iucyrillic' => "\x{044E}", + 'ivowelsignbengali' => "\x{09BF}", + 'ivowelsigndeva' => "\x{093F}", + 'ivowelsigngujarati' => "\x{0ABF}", + 'izhitsacyrillic' => "\x{0475}", + 'izhitsadblgravecyrillic' => "\x{0477}", + 'jaarmenian' => "\x{0571}", + 'jabengali' => "\x{099C}", + 'jadeva' => "\x{091C}", + 'jagujarati' => "\x{0A9C}", + 'jagurmukhi' => "\x{0A1C}", + 'jbopomofo' => "\x{3110}", + 'jcaron' => "\x{01F0}", + 'jcircle' => "\x{24D9}", + 'jcrossedtail' => "\x{029D}", + 'jdotlessstroke' => "\x{025F}", + 'jecyrillic' => "\x{0458}", + 'jeemarabic' => "\x{062C}", + 'jeemfinalarabic' => "\x{FE9E}", + 'jeeminitialarabic' => "\x{FE9F}", + 'jeemmedialarabic' => "\x{FEA0}", + 'jeharabic' => "\x{0698}", + 'jehfinalarabic' => "\x{FB8B}", + 'jhabengali' => "\x{099D}", + 'jhadeva' => "\x{091D}", + 'jhagujarati' => "\x{0A9D}", + 'jhagurmukhi' => "\x{0A1D}", + 'jheharmenian' => "\x{057B}", + 'jis' => "\x{3004}", + 'jmonospace' => "\x{FF4A}", + 'jparen' => "\x{24A5}", + 'jsuperior' => "\x{02B2}", + 'kabashkircyrillic' => "\x{04A1}", + 'kabengali' => "\x{0995}", + 'kacute' => "\x{1E31}", + 'kacyrillic' => "\x{043A}", + 'kadescendercyrillic' => "\x{049B}", + 'kadeva' => "\x{0915}", + 'kaf' => "\x{05DB}", + 'kafarabic' => "\x{0643}", + 'kafdagesh' => "\x{FB3B}", + 'kafdageshhebrew' => "\x{FB3B}", + 'kaffinalarabic' => "\x{FEDA}", + 'kafhebrew' => "\x{05DB}", + 'kafinitialarabic' => "\x{FEDB}", + 'kafmedialarabic' => "\x{FEDC}", + 'kafrafehebrew' => "\x{FB4D}", + 'kagujarati' => "\x{0A95}", + 'kagurmukhi' => "\x{0A15}", + 'kahiragana' => "\x{304B}", + 'kahookcyrillic' => "\x{04C4}", + 'kakatakana' => "\x{30AB}", + 'kakatakanahalfwidth' => "\x{FF76}", + 'kappasymbolgreek' => "\x{03F0}", + 'kapyeounmieumkorean' => "\x{3171}", + 'kapyeounphieuphkorean' => "\x{3184}", + 'kapyeounpieupkorean' => "\x{3178}", + 'kapyeounssangpieupkorean' => "\x{3179}", + 'karoriisquare' => "\x{330D}", + 'kashidaautoarabic' => "\x{0640}", + 'kashidaautonosidebearingarabic' => "\x{0640}", + 'kasmallkatakana' => "\x{30F5}", + 'kasquare' => "\x{3384}", + 'kasraarabic' => "\x{0650}", + 'kasratanarabic' => "\x{064D}", + 'kastrokecyrillic' => "\x{049F}", + 'katahiraprolongmarkhalfwidth' => "\x{FF70}", + 'kaverticalstrokecyrillic' => "\x{049D}", + 'kbopomofo' => "\x{310E}", + 'kcalsquare' => "\x{3389}", + 'kcaron' => "\x{01E9}", + 'kcedilla' => "\x{0137}", + 'kcircle' => "\x{24DA}", + 'kdotbelow' => "\x{1E33}", + 'keharmenian' => "\x{0584}", + 'kehiragana' => "\x{3051}", + 'kekatakana' => "\x{30B1}", + 'kekatakanahalfwidth' => "\x{FF79}", + 'kenarmenian' => "\x{056F}", + 'kesmallkatakana' => "\x{30F6}", + 'khabengali' => "\x{0996}", + 'khacyrillic' => "\x{0445}", + 'khadeva' => "\x{0916}", + 'khagujarati' => "\x{0A96}", + 'khagurmukhi' => "\x{0A16}", + 'khaharabic' => "\x{062E}", + 'khahfinalarabic' => "\x{FEA6}", + 'khahinitialarabic' => "\x{FEA7}", + 'khahmedialarabic' => "\x{FEA8}", + 'kheicoptic' => "\x{03E7}", + 'khhadeva' => "\x{0959}", + 'khhagurmukhi' => "\x{0A59}", + 'khieukhacirclekorean' => "\x{3278}", + 'khieukhaparenkorean' => "\x{3218}", + 'khieukhcirclekorean' => "\x{326A}", + 'khieukhkorean' => "\x{314B}", + 'khieukhparenkorean' => "\x{320A}", + 'khokhaithai' => "\x{0E02}", + 'khokhonthai' => "\x{0E05}", + 'khokhuatthai' => "\x{0E03}", + 'khokhwaithai' => "\x{0E04}", + 'khomutthai' => "\x{0E5B}", + 'khook' => "\x{0199}", + 'khorakhangthai' => "\x{0E06}", + 'khzsquare' => "\x{3391}", + 'kihiragana' => "\x{304D}", + 'kikatakana' => "\x{30AD}", + 'kikatakanahalfwidth' => "\x{FF77}", + 'kiroguramusquare' => "\x{3315}", + 'kiromeetorusquare' => "\x{3316}", + 'kirosquare' => "\x{3314}", + 'kiyeokacirclekorean' => "\x{326E}", + 'kiyeokaparenkorean' => "\x{320E}", + 'kiyeokcirclekorean' => "\x{3260}", + 'kiyeokkorean' => "\x{3131}", + 'kiyeokparenkorean' => "\x{3200}", + 'kiyeoksioskorean' => "\x{3133}", + 'kjecyrillic' => "\x{045C}", + 'klinebelow' => "\x{1E35}", + 'klsquare' => "\x{3398}", + 'kmcubedsquare' => "\x{33A6}", + 'kmonospace' => "\x{FF4B}", + 'kmsquaredsquare' => "\x{33A2}", + 'kohiragana' => "\x{3053}", + 'kohmsquare' => "\x{33C0}", + 'kokaithai' => "\x{0E01}", + 'kokatakana' => "\x{30B3}", + 'kokatakanahalfwidth' => "\x{FF7A}", + 'kooposquare' => "\x{331E}", + 'koppacyrillic' => "\x{0481}", + 'koreanstandardsymbol' => "\x{327F}", + 'koroniscmb' => "\x{0343}", + 'kparen' => "\x{24A6}", + 'kpasquare' => "\x{33AA}", + 'ksicyrillic' => "\x{046F}", + 'ktsquare' => "\x{33CF}", + 'kturned' => "\x{029E}", + 'kuhiragana' => "\x{304F}", + 'kukatakana' => "\x{30AF}", + 'kukatakanahalfwidth' => "\x{FF78}", + 'kvsquare' => "\x{33B8}", + 'kwsquare' => "\x{33BE}", + 'labengali' => "\x{09B2}", + 'ladeva' => "\x{0932}", + 'lagujarati' => "\x{0AB2}", + 'lagurmukhi' => "\x{0A32}", + 'lakkhangyaothai' => "\x{0E45}", + 'lamaleffinalarabic' => "\x{FEFC}", + 'lamalefhamzaabovefinalarabic' => "\x{FEF8}", + 'lamalefhamzaaboveisolatedarabic' => "\x{FEF7}", + 'lamalefhamzabelowfinalarabic' => "\x{FEFA}", + 'lamalefhamzabelowisolatedarabic' => "\x{FEF9}", + 'lamalefisolatedarabic' => "\x{FEFB}", + 'lamalefmaddaabovefinalarabic' => "\x{FEF6}", + 'lamalefmaddaaboveisolatedarabic' => "\x{FEF5}", + 'lamarabic' => "\x{0644}", + 'lambdastroke' => "\x{019B}", + 'lamed' => "\x{05DC}", + 'lameddagesh' => "\x{FB3C}", + 'lameddageshhebrew' => "\x{FB3C}", + 'lamedhebrew' => "\x{05DC}", + 'lamedholam' => "\x{05DC}\x{05B9}", + 'lamedholamdagesh' => "\x{05DC}\x{05B9}\x{05BC}", + 'lamedholamdageshhebrew' => "\x{05DC}\x{05B9}\x{05BC}", + 'lamedholamhebrew' => "\x{05DC}\x{05B9}", + 'lamfinalarabic' => "\x{FEDE}", + 'lamhahinitialarabic' => "\x{FCCA}", + 'laminitialarabic' => "\x{FEDF}", + 'lamjeeminitialarabic' => "\x{FCC9}", + 'lamkhahinitialarabic' => "\x{FCCB}", + 'lamlamhehisolatedarabic' => "\x{FDF2}", + 'lammedialarabic' => "\x{FEE0}", + 'lammeemhahinitialarabic' => "\x{FD88}", + 'lammeeminitialarabic' => "\x{FCCC}", + 'lammeemjeeminitialarabic' => "\x{FEDF}\x{FEE4}\x{FEA0}", + 'lammeemkhahinitialarabic' => "\x{FEDF}\x{FEE4}\x{FEA8}", + 'largecircle' => "\x{25EF}", + 'lbar' => "\x{019A}", + 'lbelt' => "\x{026C}", + 'lbopomofo' => "\x{310C}", + 'lcedilla' => "\x{013C}", + 'lcircle' => "\x{24DB}", + 'lcircumflexbelow' => "\x{1E3D}", + 'ldotaccent' => "\x{0140}", + 'ldotbelow' => "\x{1E37}", + 'ldotbelowmacron' => "\x{1E39}", + 'leftangleabovecmb' => "\x{031A}", + 'lefttackbelowcmb' => "\x{0318}", + 'lessequalorgreater' => "\x{22DA}", + 'lessmonospace' => "\x{FF1C}", + 'lessorequivalent' => "\x{2272}", + 'lessorgreater' => "\x{2276}", + 'lessoverequal' => "\x{2266}", + 'lesssmall' => "\x{FE64}", + 'lezh' => "\x{026E}", + 'lhookretroflex' => "\x{026D}", + 'liwnarmenian' => "\x{056C}", + 'lj' => "\x{01C9}", + 'ljecyrillic' => "\x{0459}", + 'll' => "\x{F6C0}", + 'lladeva' => "\x{0933}", + 'llagujarati' => "\x{0AB3}", + 'llinebelow' => "\x{1E3B}", + 'llladeva' => "\x{0934}", + 'llvocalicbengali' => "\x{09E1}", + 'llvocalicdeva' => "\x{0961}", + 'llvocalicvowelsignbengali' => "\x{09E3}", + 'llvocalicvowelsigndeva' => "\x{0963}", + 'lmiddletilde' => "\x{026B}", + 'lmonospace' => "\x{FF4C}", + 'lmsquare' => "\x{33D0}", + 'lochulathai' => "\x{0E2C}", + 'logicalnotreversed' => "\x{2310}", + 'lolingthai' => "\x{0E25}", + 'lowlinecenterline' => "\x{FE4E}", + 'lowlinecmb' => "\x{0332}", + 'lowlinedashed' => "\x{FE4D}", + 'lparen' => "\x{24A7}", + 'lsquare' => "\x{2113}", + 'lsuperior' => "\x{F6EE}", + 'luthai' => "\x{0E26}", + 'lvocalicbengali' => "\x{098C}", + 'lvocalicdeva' => "\x{090C}", + 'lvocalicvowelsignbengali' => "\x{09E2}", + 'lvocalicvowelsigndeva' => "\x{0962}", + 'lxsquare' => "\x{33D3}", + 'mabengali' => "\x{09AE}", + 'macronbelowcmb' => "\x{0331}", + 'macroncmb' => "\x{0304}", + 'macronlowmod' => "\x{02CD}", + 'macronmonospace' => "\x{FFE3}", + 'macute' => "\x{1E3F}", + 'madeva' => "\x{092E}", + 'magujarati' => "\x{0AAE}", + 'magurmukhi' => "\x{0A2E}", + 'mahapakhhebrew' => "\x{05A4}", + 'mahapakhlefthebrew' => "\x{05A4}", + 'mahiragana' => "\x{307E}", + 'maichattawalowleftthai' => "\x{F895}", + 'maichattawalowrightthai' => "\x{F894}", + 'maichattawathai' => "\x{0E4B}", + 'maichattawaupperleftthai' => "\x{F893}", + 'maieklowleftthai' => "\x{F88C}", + 'maieklowrightthai' => "\x{F88B}", + 'maiekthai' => "\x{0E48}", + 'maiekupperleftthai' => "\x{F88A}", + 'maihanakatleftthai' => "\x{F884}", + 'maihanakatthai' => "\x{0E31}", + 'maitaikhuleftthai' => "\x{F889}", + 'maitaikhuthai' => "\x{0E47}", + 'maitholowleftthai' => "\x{F88F}", + 'maitholowrightthai' => "\x{F88E}", + 'maithothai' => "\x{0E49}", + 'maithoupperleftthai' => "\x{F88D}", + 'maitrilowleftthai' => "\x{F892}", + 'maitrilowrightthai' => "\x{F891}", + 'maitrithai' => "\x{0E4A}", + 'maitriupperleftthai' => "\x{F890}", + 'maiyamokthai' => "\x{0E46}", + 'makatakana' => "\x{30DE}", + 'makatakanahalfwidth' => "\x{FF8F}", + 'mansyonsquare' => "\x{3347}", + 'maqafhebrew' => "\x{05BE}", + 'mars' => "\x{2642}", + 'masoracirclehebrew' => "\x{05AF}", + 'masquare' => "\x{3383}", + 'mbopomofo' => "\x{3107}", + 'mbsquare' => "\x{33D4}", + 'mcircle' => "\x{24DC}", + 'mcubedsquare' => "\x{33A5}", + 'mdotaccent' => "\x{1E41}", + 'mdotbelow' => "\x{1E43}", + 'meemarabic' => "\x{0645}", + 'meemfinalarabic' => "\x{FEE2}", + 'meeminitialarabic' => "\x{FEE3}", + 'meemmedialarabic' => "\x{FEE4}", + 'meemmeeminitialarabic' => "\x{FCD1}", + 'meemmeemisolatedarabic' => "\x{FC48}", + 'meetorusquare' => "\x{334D}", + 'mehiragana' => "\x{3081}", + 'meizierasquare' => "\x{337E}", + 'mekatakana' => "\x{30E1}", + 'mekatakanahalfwidth' => "\x{FF92}", + 'mem' => "\x{05DE}", + 'memdagesh' => "\x{FB3E}", + 'memdageshhebrew' => "\x{FB3E}", + 'memhebrew' => "\x{05DE}", + 'menarmenian' => "\x{0574}", + 'merkhahebrew' => "\x{05A5}", + 'merkhakefulahebrew' => "\x{05A6}", + 'merkhakefulalefthebrew' => "\x{05A6}", + 'merkhalefthebrew' => "\x{05A5}", + 'mhook' => "\x{0271}", + 'mhzsquare' => "\x{3392}", + 'middledotkatakanahalfwidth' => "\x{FF65}", + 'middot' => "\x{00B7}", + 'mieumacirclekorean' => "\x{3272}", + 'mieumaparenkorean' => "\x{3212}", + 'mieumcirclekorean' => "\x{3264}", + 'mieumkorean' => "\x{3141}", + 'mieumpansioskorean' => "\x{3170}", + 'mieumparenkorean' => "\x{3204}", + 'mieumpieupkorean' => "\x{316E}", + 'mieumsioskorean' => "\x{316F}", + 'mihiragana' => "\x{307F}", + 'mikatakana' => "\x{30DF}", + 'mikatakanahalfwidth' => "\x{FF90}", + 'minusbelowcmb' => "\x{0320}", + 'minuscircle' => "\x{2296}", + 'minusmod' => "\x{02D7}", + 'minusplus' => "\x{2213}", + 'miribaarusquare' => "\x{334A}", + 'mirisquare' => "\x{3349}", + 'mlonglegturned' => "\x{0270}", + 'mlsquare' => "\x{3396}", + 'mmcubedsquare' => "\x{33A3}", + 'mmonospace' => "\x{FF4D}", + 'mmsquaredsquare' => "\x{339F}", + 'mohiragana' => "\x{3082}", + 'mohmsquare' => "\x{33C1}", + 'mokatakana' => "\x{30E2}", + 'mokatakanahalfwidth' => "\x{FF93}", + 'molsquare' => "\x{33D6}", + 'momathai' => "\x{0E21}", + 'moverssquare' => "\x{33A7}", + 'moverssquaredsquare' => "\x{33A8}", + 'mparen' => "\x{24A8}", + 'mpasquare' => "\x{33AB}", + 'mssquare' => "\x{33B3}", + 'msuperior' => "\x{F6EF}", + 'mturned' => "\x{026F}", + 'mu1' => "\x{00B5}", + 'muasquare' => "\x{3382}", + 'muchgreater' => "\x{226B}", + 'muchless' => "\x{226A}", + 'mufsquare' => "\x{338C}", + 'mugreek' => "\x{03BC}", + 'mugsquare' => "\x{338D}", + 'muhiragana' => "\x{3080}", + 'mukatakana' => "\x{30E0}", + 'mukatakanahalfwidth' => "\x{FF91}", + 'mulsquare' => "\x{3395}", + 'mumsquare' => "\x{339B}", + 'munahhebrew' => "\x{05A3}", + 'munahlefthebrew' => "\x{05A3}", + 'musicflatsign' => "\x{266D}", + 'musicsharpsign' => "\x{266F}", + 'mussquare' => "\x{33B2}", + 'muvsquare' => "\x{33B6}", + 'muwsquare' => "\x{33BC}", + 'mvmegasquare' => "\x{33B9}", + 'mvsquare' => "\x{33B7}", + 'mwmegasquare' => "\x{33BF}", + 'mwsquare' => "\x{33BD}", + 'nabengali' => "\x{09A8}", + 'nabla' => "\x{2207}", + 'nadeva' => "\x{0928}", + 'nagujarati' => "\x{0AA8}", + 'nagurmukhi' => "\x{0A28}", + 'nahiragana' => "\x{306A}", + 'nakatakana' => "\x{30CA}", + 'nakatakanahalfwidth' => "\x{FF85}", + 'nasquare' => "\x{3381}", + 'nbopomofo' => "\x{310B}", + 'nbspace' => "\x{00A0}", + 'ncedilla' => "\x{0146}", + 'ncircle' => "\x{24DD}", + 'ncircumflexbelow' => "\x{1E4B}", + 'ndotaccent' => "\x{1E45}", + 'ndotbelow' => "\x{1E47}", + 'nehiragana' => "\x{306D}", + 'nekatakana' => "\x{30CD}", + 'nekatakanahalfwidth' => "\x{FF88}", + 'newsheqelsign' => "\x{20AA}", + 'nfsquare' => "\x{338B}", + 'ngabengali' => "\x{0999}", + 'ngadeva' => "\x{0919}", + 'ngagujarati' => "\x{0A99}", + 'ngagurmukhi' => "\x{0A19}", + 'ngonguthai' => "\x{0E07}", + 'nhiragana' => "\x{3093}", + 'nhookleft' => "\x{0272}", + 'nhookretroflex' => "\x{0273}", + 'nieunacirclekorean' => "\x{326F}", + 'nieunaparenkorean' => "\x{320F}", + 'nieuncieuckorean' => "\x{3135}", + 'nieuncirclekorean' => "\x{3261}", + 'nieunhieuhkorean' => "\x{3136}", + 'nieunkorean' => "\x{3134}", + 'nieunpansioskorean' => "\x{3168}", + 'nieunparenkorean' => "\x{3201}", + 'nieunsioskorean' => "\x{3167}", + 'nieuntikeutkorean' => "\x{3166}", + 'nihiragana' => "\x{306B}", + 'nikatakana' => "\x{30CB}", + 'nikatakanahalfwidth' => "\x{FF86}", + 'nikhahitleftthai' => "\x{F899}", + 'nikhahitthai' => "\x{0E4D}", + 'ninearabic' => "\x{0669}", + 'ninebengali' => "\x{09EF}", + 'ninecircle' => "\x{2468}", + 'ninecircleinversesansserif' => "\x{2792}", + 'ninedeva' => "\x{096F}", + 'ninegujarati' => "\x{0AEF}", + 'ninegurmukhi' => "\x{0A6F}", + 'ninehackarabic' => "\x{0669}", + 'ninehangzhou' => "\x{3029}", + 'nineideographicparen' => "\x{3228}", + 'nineinferior' => "\x{2089}", + 'ninemonospace' => "\x{FF19}", + 'nineoldstyle' => "\x{F739}", + 'nineparen' => "\x{247C}", + 'nineperiod' => "\x{2490}", + 'ninepersian' => "\x{06F9}", + 'nineroman' => "\x{2178}", + 'ninesuperior' => "\x{2079}", + 'nineteencircle' => "\x{2472}", + 'nineteenparen' => "\x{2486}", + 'nineteenperiod' => "\x{249A}", + 'ninethai' => "\x{0E59}", + 'nj' => "\x{01CC}", + 'njecyrillic' => "\x{045A}", + 'nkatakana' => "\x{30F3}", + 'nkatakanahalfwidth' => "\x{FF9D}", + 'nlegrightlong' => "\x{019E}", + 'nlinebelow' => "\x{1E49}", + 'nmonospace' => "\x{FF4E}", + 'nmsquare' => "\x{339A}", + 'nnabengali' => "\x{09A3}", + 'nnadeva' => "\x{0923}", + 'nnagujarati' => "\x{0AA3}", + 'nnagurmukhi' => "\x{0A23}", + 'nnnadeva' => "\x{0929}", + 'nohiragana' => "\x{306E}", + 'nokatakana' => "\x{30CE}", + 'nokatakanahalfwidth' => "\x{FF89}", + 'nonbreakingspace' => "\x{00A0}", + 'nonenthai' => "\x{0E13}", + 'nonuthai' => "\x{0E19}", + 'noonarabic' => "\x{0646}", + 'noonfinalarabic' => "\x{FEE6}", + 'noonghunnaarabic' => "\x{06BA}", + 'noonghunnafinalarabic' => "\x{FB9F}", + 'noonhehinitialarabic' => "\x{FEE7}\x{FEEC}", + 'nooninitialarabic' => "\x{FEE7}", + 'noonjeeminitialarabic' => "\x{FCD2}", + 'noonjeemisolatedarabic' => "\x{FC4B}", + 'noonmedialarabic' => "\x{FEE8}", + 'noonmeeminitialarabic' => "\x{FCD5}", + 'noonmeemisolatedarabic' => "\x{FC4E}", + 'noonnoonfinalarabic' => "\x{FC8D}", + 'notcontains' => "\x{220C}", + 'notelementof' => "\x{2209}", + 'notgreater' => "\x{226F}", + 'notgreaternorequal' => "\x{2271}", + 'notgreaternorless' => "\x{2279}", + 'notidentical' => "\x{2262}", + 'notless' => "\x{226E}", + 'notlessnorequal' => "\x{2270}", + 'notparallel' => "\x{2226}", + 'notprecedes' => "\x{2280}", + 'notsucceeds' => "\x{2281}", + 'notsuperset' => "\x{2285}", + 'nowarmenian' => "\x{0576}", + 'nparen' => "\x{24A9}", + 'nssquare' => "\x{33B1}", + 'nsuperior' => "\x{207F}", + 'nuhiragana' => "\x{306C}", + 'nukatakana' => "\x{30CC}", + 'nukatakanahalfwidth' => "\x{FF87}", + 'nuktabengali' => "\x{09BC}", + 'nuktadeva' => "\x{093C}", + 'nuktagujarati' => "\x{0ABC}", + 'nuktagurmukhi' => "\x{0A3C}", + 'numbersignmonospace' => "\x{FF03}", + 'numbersignsmall' => "\x{FE5F}", + 'numeralsigngreek' => "\x{0374}", + 'numeralsignlowergreek' => "\x{0375}", + 'numero' => "\x{2116}", + 'nun' => "\x{05E0}", + 'nundagesh' => "\x{FB40}", + 'nundageshhebrew' => "\x{FB40}", + 'nunhebrew' => "\x{05E0}", + 'nvsquare' => "\x{33B5}", + 'nwsquare' => "\x{33BB}", + 'nyabengali' => "\x{099E}", + 'nyadeva' => "\x{091E}", + 'nyagujarati' => "\x{0A9E}", + 'nyagurmukhi' => "\x{0A1E}", + 'oangthai' => "\x{0E2D}", + 'obarred' => "\x{0275}", + 'obarredcyrillic' => "\x{04E9}", + 'obarreddieresiscyrillic' => "\x{04EB}", + 'obengali' => "\x{0993}", + 'obopomofo' => "\x{311B}", + 'ocandradeva' => "\x{0911}", + 'ocandragujarati' => "\x{0A91}", + 'ocandravowelsigndeva' => "\x{0949}", + 'ocandravowelsigngujarati' => "\x{0AC9}", + 'ocaron' => "\x{01D2}", + 'ocircle' => "\x{24DE}", + 'ocircumflexacute' => "\x{1ED1}", + 'ocircumflexdotbelow' => "\x{1ED9}", + 'ocircumflexgrave' => "\x{1ED3}", + 'ocircumflexhookabove' => "\x{1ED5}", + 'ocircumflextilde' => "\x{1ED7}", + 'ocyrillic' => "\x{043E}", + 'odblacute' => "\x{0151}", + 'odblgrave' => "\x{020D}", + 'odeva' => "\x{0913}", + 'odieresiscyrillic' => "\x{04E7}", + 'odotbelow' => "\x{1ECD}", + 'oekorean' => "\x{315A}", + 'ogonekcmb' => "\x{0328}", + 'ogujarati' => "\x{0A93}", + 'oharmenian' => "\x{0585}", + 'ohiragana' => "\x{304A}", + 'ohookabove' => "\x{1ECF}", + 'ohornacute' => "\x{1EDB}", + 'ohorndotbelow' => "\x{1EE3}", + 'ohorngrave' => "\x{1EDD}", + 'ohornhookabove' => "\x{1EDF}", + 'ohorntilde' => "\x{1EE1}", + 'oi' => "\x{01A3}", + 'oinvertedbreve' => "\x{020F}", + 'okatakana' => "\x{30AA}", + 'okatakanahalfwidth' => "\x{FF75}", + 'okorean' => "\x{3157}", + 'olehebrew' => "\x{05AB}", + 'omacronacute' => "\x{1E53}", + 'omacrongrave' => "\x{1E51}", + 'omdeva' => "\x{0950}", + 'omegacyrillic' => "\x{0461}", + 'omegalatinclosed' => "\x{0277}", + 'omegaroundcyrillic' => "\x{047B}", + 'omegatitlocyrillic' => "\x{047D}", + 'omgujarati' => "\x{0AD0}", + 'omonospace' => "\x{FF4F}", + 'onearabic' => "\x{0661}", + 'onebengali' => "\x{09E7}", + 'onecircle' => "\x{2460}", + 'onecircleinversesansserif' => "\x{278A}", + 'onedeva' => "\x{0967}", + 'onefitted' => "\x{F6DC}", + 'onegujarati' => "\x{0AE7}", + 'onegurmukhi' => "\x{0A67}", + 'onehackarabic' => "\x{0661}", + 'onehangzhou' => "\x{3021}", + 'oneideographicparen' => "\x{3220}", + 'oneinferior' => "\x{2081}", + 'onemonospace' => "\x{FF11}", + 'onenumeratorbengali' => "\x{09F4}", + 'oneoldstyle' => "\x{F731}", + 'oneparen' => "\x{2474}", + 'oneperiod' => "\x{2488}", + 'onepersian' => "\x{06F1}", + 'oneroman' => "\x{2170}", + 'onethai' => "\x{0E51}", + 'oogonek' => "\x{01EB}", + 'oogonekmacron' => "\x{01ED}", + 'oogurmukhi' => "\x{0A13}", + 'oomatragurmukhi' => "\x{0A4B}", + 'oopen' => "\x{0254}", + 'oparen' => "\x{24AA}", + 'option' => "\x{2325}", + 'oshortdeva' => "\x{0912}", + 'oshortvowelsigndeva' => "\x{094A}", + 'osmallhiragana' => "\x{3049}", + 'osmallkatakana' => "\x{30A9}", + 'osmallkatakanahalfwidth' => "\x{FF6B}", + 'ostrokeacute' => "\x{01FF}", + 'osuperior' => "\x{F6F0}", + 'otcyrillic' => "\x{047F}", + 'otildeacute' => "\x{1E4D}", + 'otildedieresis' => "\x{1E4F}", + 'oubopomofo' => "\x{3121}", + 'overline' => "\x{203E}", + 'overlinecenterline' => "\x{FE4A}", + 'overlinecmb' => "\x{0305}", + 'overlinedashed' => "\x{FE49}", + 'overlinedblwavy' => "\x{FE4C}", + 'overlinewavy' => "\x{FE4B}", + 'overscore' => "\x{00AF}", + 'ovowelsignbengali' => "\x{09CB}", + 'ovowelsigndeva' => "\x{094B}", + 'ovowelsigngujarati' => "\x{0ACB}", + 'paampssquare' => "\x{3380}", + 'paasentosquare' => "\x{332B}", + 'pabengali' => "\x{09AA}", + 'pacute' => "\x{1E55}", + 'padeva' => "\x{092A}", + 'pagedown' => "\x{21DF}", + 'pageup' => "\x{21DE}", + 'pagujarati' => "\x{0AAA}", + 'pagurmukhi' => "\x{0A2A}", + 'pahiragana' => "\x{3071}", + 'paiyannoithai' => "\x{0E2F}", + 'pakatakana' => "\x{30D1}", + 'palatalizationcyrilliccmb' => "\x{0484}", + 'palochkacyrillic' => "\x{04C0}", + 'pansioskorean' => "\x{317F}", + 'parallel' => "\x{2225}", + 'parenleftaltonearabic' => "\x{FD3E}", + 'parenleftbt' => "\x{F8ED}", + 'parenleftex' => "\x{F8EC}", + 'parenleftinferior' => "\x{208D}", + 'parenleftmonospace' => "\x{FF08}", + 'parenleftsmall' => "\x{FE59}", + 'parenleftsuperior' => "\x{207D}", + 'parenlefttp' => "\x{F8EB}", + 'parenleftvertical' => "\x{FE35}", + 'parenrightaltonearabic' => "\x{FD3F}", + 'parenrightbt' => "\x{F8F8}", + 'parenrightex' => "\x{F8F7}", + 'parenrightinferior' => "\x{208E}", + 'parenrightmonospace' => "\x{FF09}", + 'parenrightsmall' => "\x{FE5A}", + 'parenrightsuperior' => "\x{207E}", + 'parenrighttp' => "\x{F8F6}", + 'parenrightvertical' => "\x{FE36}", + 'paseqhebrew' => "\x{05C0}", + 'pashtahebrew' => "\x{0599}", + 'pasquare' => "\x{33A9}", + 'patah' => "\x{05B7}", + 'patah11' => "\x{05B7}", + 'patah1d' => "\x{05B7}", + 'patah2a' => "\x{05B7}", + 'patahhebrew' => "\x{05B7}", + 'patahnarrowhebrew' => "\x{05B7}", + 'patahquarterhebrew' => "\x{05B7}", + 'patahwidehebrew' => "\x{05B7}", + 'pazerhebrew' => "\x{05A1}", + 'pbopomofo' => "\x{3106}", + 'pcircle' => "\x{24DF}", + 'pdotaccent' => "\x{1E57}", + 'pe' => "\x{05E4}", + 'pecyrillic' => "\x{043F}", + 'pedagesh' => "\x{FB44}", + 'pedageshhebrew' => "\x{FB44}", + 'peezisquare' => "\x{333B}", + 'pefinaldageshhebrew' => "\x{FB43}", + 'peharabic' => "\x{067E}", + 'peharmenian' => "\x{057A}", + 'pehebrew' => "\x{05E4}", + 'pehfinalarabic' => "\x{FB57}", + 'pehinitialarabic' => "\x{FB58}", + 'pehiragana' => "\x{307A}", + 'pehmedialarabic' => "\x{FB59}", + 'pekatakana' => "\x{30DA}", + 'pemiddlehookcyrillic' => "\x{04A7}", + 'perafehebrew' => "\x{FB4E}", + 'percentarabic' => "\x{066A}", + 'percentmonospace' => "\x{FF05}", + 'percentsmall' => "\x{FE6A}", + 'periodarmenian' => "\x{0589}", + 'periodhalfwidth' => "\x{FF61}", + 'periodinferior' => "\x{F6E7}", + 'periodmonospace' => "\x{FF0E}", + 'periodsmall' => "\x{FE52}", + 'periodsuperior' => "\x{F6E8}", + 'perispomenigreekcmb' => "\x{0342}", + 'pfsquare' => "\x{338A}", + 'phabengali' => "\x{09AB}", + 'phadeva' => "\x{092B}", + 'phagujarati' => "\x{0AAB}", + 'phagurmukhi' => "\x{0A2B}", + 'phieuphacirclekorean' => "\x{327A}", + 'phieuphaparenkorean' => "\x{321A}", + 'phieuphcirclekorean' => "\x{326C}", + 'phieuphkorean' => "\x{314D}", + 'phieuphparenkorean' => "\x{320C}", + 'philatin' => "\x{0278}", + 'phinthuthai' => "\x{0E3A}", + 'phisymbolgreek' => "\x{03D5}", + 'phook' => "\x{01A5}", + 'phophanthai' => "\x{0E1E}", + 'phophungthai' => "\x{0E1C}", + 'phosamphaothai' => "\x{0E20}", + 'pieupacirclekorean' => "\x{3273}", + 'pieupaparenkorean' => "\x{3213}", + 'pieupcieuckorean' => "\x{3176}", + 'pieupcirclekorean' => "\x{3265}", + 'pieupkiyeokkorean' => "\x{3172}", + 'pieupkorean' => "\x{3142}", + 'pieupparenkorean' => "\x{3205}", + 'pieupsioskiyeokkorean' => "\x{3174}", + 'pieupsioskorean' => "\x{3144}", + 'pieupsiostikeutkorean' => "\x{3175}", + 'pieupthieuthkorean' => "\x{3177}", + 'pieuptikeutkorean' => "\x{3173}", + 'pihiragana' => "\x{3074}", + 'pikatakana' => "\x{30D4}", + 'pisymbolgreek' => "\x{03D6}", + 'piwrarmenian' => "\x{0583}", + 'plusbelowcmb' => "\x{031F}", + 'pluscircle' => "\x{2295}", + 'plusmod' => "\x{02D6}", + 'plusmonospace' => "\x{FF0B}", + 'plussmall' => "\x{FE62}", + 'plussuperior' => "\x{207A}", + 'pmonospace' => "\x{FF50}", + 'pmsquare' => "\x{33D8}", + 'pohiragana' => "\x{307D}", + 'pointingindexdownwhite' => "\x{261F}", + 'pointingindexleftwhite' => "\x{261C}", + 'pointingindexrightwhite' => "\x{261E}", + 'pointingindexupwhite' => "\x{261D}", + 'pokatakana' => "\x{30DD}", + 'poplathai' => "\x{0E1B}", + 'postalmark' => "\x{3012}", + 'postalmarkface' => "\x{3020}", + 'pparen' => "\x{24AB}", + 'precedes' => "\x{227A}", + 'primemod' => "\x{02B9}", + 'primereversed' => "\x{2035}", + 'projective' => "\x{2305}", + 'prolongedkana' => "\x{30FC}", + 'propellor' => "\x{2318}", + 'proportion' => "\x{2237}", + 'psicyrillic' => "\x{0471}", + 'psilipneumatacyrilliccmb' => "\x{0486}", + 'pssquare' => "\x{33B0}", + 'puhiragana' => "\x{3077}", + 'pukatakana' => "\x{30D7}", + 'pvsquare' => "\x{33B4}", + 'pwsquare' => "\x{33BA}", + 'qadeva' => "\x{0958}", + 'qadmahebrew' => "\x{05A8}", + 'qafarabic' => "\x{0642}", + 'qaffinalarabic' => "\x{FED6}", + 'qafinitialarabic' => "\x{FED7}", + 'qafmedialarabic' => "\x{FED8}", + 'qamats' => "\x{05B8}", + 'qamats10' => "\x{05B8}", + 'qamats1a' => "\x{05B8}", + 'qamats1c' => "\x{05B8}", + 'qamats27' => "\x{05B8}", + 'qamats29' => "\x{05B8}", + 'qamats33' => "\x{05B8}", + 'qamatsde' => "\x{05B8}", + 'qamatshebrew' => "\x{05B8}", + 'qamatsnarrowhebrew' => "\x{05B8}", + 'qamatsqatanhebrew' => "\x{05B8}", + 'qamatsqatannarrowhebrew' => "\x{05B8}", + 'qamatsqatanquarterhebrew' => "\x{05B8}", + 'qamatsqatanwidehebrew' => "\x{05B8}", + 'qamatsquarterhebrew' => "\x{05B8}", + 'qamatswidehebrew' => "\x{05B8}", + 'qarneyparahebrew' => "\x{059F}", + 'qbopomofo' => "\x{3111}", + 'qcircle' => "\x{24E0}", + 'qhook' => "\x{02A0}", + 'qmonospace' => "\x{FF51}", + 'qof' => "\x{05E7}", + 'qofdagesh' => "\x{FB47}", + 'qofdageshhebrew' => "\x{FB47}", + 'qofhatafpatah' => "\x{05E7}\x{05B2}", + 'qofhatafpatahhebrew' => "\x{05E7}\x{05B2}", + 'qofhatafsegol' => "\x{05E7}\x{05B1}", + 'qofhatafsegolhebrew' => "\x{05E7}\x{05B1}", + 'qofhebrew' => "\x{05E7}", + 'qofhiriq' => "\x{05E7}\x{05B4}", + 'qofhiriqhebrew' => "\x{05E7}\x{05B4}", + 'qofholam' => "\x{05E7}\x{05B9}", + 'qofholamhebrew' => "\x{05E7}\x{05B9}", + 'qofpatah' => "\x{05E7}\x{05B7}", + 'qofpatahhebrew' => "\x{05E7}\x{05B7}", + 'qofqamats' => "\x{05E7}\x{05B8}", + 'qofqamatshebrew' => "\x{05E7}\x{05B8}", + 'qofqubuts' => "\x{05E7}\x{05BB}", + 'qofqubutshebrew' => "\x{05E7}\x{05BB}", + 'qofsegol' => "\x{05E7}\x{05B6}", + 'qofsegolhebrew' => "\x{05E7}\x{05B6}", + 'qofsheva' => "\x{05E7}\x{05B0}", + 'qofshevahebrew' => "\x{05E7}\x{05B0}", + 'qoftsere' => "\x{05E7}\x{05B5}", + 'qoftserehebrew' => "\x{05E7}\x{05B5}", + 'qparen' => "\x{24AC}", + 'quarternote' => "\x{2669}", + 'qubuts' => "\x{05BB}", + 'qubuts18' => "\x{05BB}", + 'qubuts25' => "\x{05BB}", + 'qubuts31' => "\x{05BB}", + 'qubutshebrew' => "\x{05BB}", + 'qubutsnarrowhebrew' => "\x{05BB}", + 'qubutsquarterhebrew' => "\x{05BB}", + 'qubutswidehebrew' => "\x{05BB}", + 'questionarabic' => "\x{061F}", + 'questionarmenian' => "\x{055E}", + 'questiondownsmall' => "\x{F7BF}", + 'questiongreek' => "\x{037E}", + 'questionmonospace' => "\x{FF1F}", + 'questionsmall' => "\x{F73F}", + 'quotedblmonospace' => "\x{FF02}", + 'quotedblprime' => "\x{301E}", + 'quotedblprimereversed' => "\x{301D}", + 'quoteleftreversed' => "\x{201B}", + 'quoterightn' => "\x{0149}", + 'quotesinglemonospace' => "\x{FF07}", + 'raarmenian' => "\x{057C}", + 'rabengali' => "\x{09B0}", + 'radeva' => "\x{0930}", + 'radicalex' => "\x{F8E5}", + 'radoverssquare' => "\x{33AE}", + 'radoverssquaredsquare' => "\x{33AF}", + 'radsquare' => "\x{33AD}", + 'rafe' => "\x{05BF}", + 'rafehebrew' => "\x{05BF}", + 'ragujarati' => "\x{0AB0}", + 'ragurmukhi' => "\x{0A30}", + 'rahiragana' => "\x{3089}", + 'rakatakana' => "\x{30E9}", + 'rakatakanahalfwidth' => "\x{FF97}", + 'ralowerdiagonalbengali' => "\x{09F1}", + 'ramiddlediagonalbengali' => "\x{09F0}", + 'ramshorn' => "\x{0264}", + 'ratio' => "\x{2236}", + 'rbopomofo' => "\x{3116}", + 'rcedilla' => "\x{0157}", + 'rcircle' => "\x{24E1}", + 'rdblgrave' => "\x{0211}", + 'rdotaccent' => "\x{1E59}", + 'rdotbelow' => "\x{1E5B}", + 'rdotbelowmacron' => "\x{1E5D}", + 'referencemark' => "\x{203B}", + 'registersans' => "\x{F8E8}", + 'registerserif' => "\x{F6DA}", + 'reharabic' => "\x{0631}", + 'reharmenian' => "\x{0580}", + 'rehfinalarabic' => "\x{FEAE}", + 'rehiragana' => "\x{308C}", + 'rehyehaleflamarabic' => "\x{0631}\x{FEF3}\x{FE8E}\x{0644}", + 'rekatakana' => "\x{30EC}", + 'rekatakanahalfwidth' => "\x{FF9A}", + 'resh' => "\x{05E8}", + 'reshdageshhebrew' => "\x{FB48}", + 'reshhatafpatah' => "\x{05E8}\x{05B2}", + 'reshhatafpatahhebrew' => "\x{05E8}\x{05B2}", + 'reshhatafsegol' => "\x{05E8}\x{05B1}", + 'reshhatafsegolhebrew' => "\x{05E8}\x{05B1}", + 'reshhebrew' => "\x{05E8}", + 'reshhiriq' => "\x{05E8}\x{05B4}", + 'reshhiriqhebrew' => "\x{05E8}\x{05B4}", + 'reshholam' => "\x{05E8}\x{05B9}", + 'reshholamhebrew' => "\x{05E8}\x{05B9}", + 'reshpatah' => "\x{05E8}\x{05B7}", + 'reshpatahhebrew' => "\x{05E8}\x{05B7}", + 'reshqamats' => "\x{05E8}\x{05B8}", + 'reshqamatshebrew' => "\x{05E8}\x{05B8}", + 'reshqubuts' => "\x{05E8}\x{05BB}", + 'reshqubutshebrew' => "\x{05E8}\x{05BB}", + 'reshsegol' => "\x{05E8}\x{05B6}", + 'reshsegolhebrew' => "\x{05E8}\x{05B6}", + 'reshsheva' => "\x{05E8}\x{05B0}", + 'reshshevahebrew' => "\x{05E8}\x{05B0}", + 'reshtsere' => "\x{05E8}\x{05B5}", + 'reshtserehebrew' => "\x{05E8}\x{05B5}", + 'reversedtilde' => "\x{223D}", + 'reviahebrew' => "\x{0597}", + 'reviamugrashhebrew' => "\x{0597}", + 'rfishhook' => "\x{027E}", + 'rfishhookreversed' => "\x{027F}", + 'rhabengali' => "\x{09DD}", + 'rhadeva' => "\x{095D}", + 'rhook' => "\x{027D}", + 'rhookturned' => "\x{027B}", + 'rhookturnedsuperior' => "\x{02B5}", + 'rhosymbolgreek' => "\x{03F1}", + 'rhotichookmod' => "\x{02DE}", + 'rieulacirclekorean' => "\x{3271}", + 'rieulaparenkorean' => "\x{3211}", + 'rieulcirclekorean' => "\x{3263}", + 'rieulhieuhkorean' => "\x{3140}", + 'rieulkiyeokkorean' => "\x{313A}", + 'rieulkiyeoksioskorean' => "\x{3169}", + 'rieulkorean' => "\x{3139}", + 'rieulmieumkorean' => "\x{313B}", + 'rieulpansioskorean' => "\x{316C}", + 'rieulparenkorean' => "\x{3203}", + 'rieulphieuphkorean' => "\x{313F}", + 'rieulpieupkorean' => "\x{313C}", + 'rieulpieupsioskorean' => "\x{316B}", + 'rieulsioskorean' => "\x{313D}", + 'rieulthieuthkorean' => "\x{313E}", + 'rieultikeutkorean' => "\x{316A}", + 'rieulyeorinhieuhkorean' => "\x{316D}", + 'rightangle' => "\x{221F}", + 'righttackbelowcmb' => "\x{0319}", + 'righttriangle' => "\x{22BF}", + 'rihiragana' => "\x{308A}", + 'rikatakana' => "\x{30EA}", + 'rikatakanahalfwidth' => "\x{FF98}", + 'ringbelowcmb' => "\x{0325}", + 'ringcmb' => "\x{030A}", + 'ringhalfleft' => "\x{02BF}", + 'ringhalfleftarmenian' => "\x{0559}", + 'ringhalfleftbelowcmb' => "\x{031C}", + 'ringhalfleftcentered' => "\x{02D3}", + 'ringhalfright' => "\x{02BE}", + 'ringhalfrightbelowcmb' => "\x{0339}", + 'ringhalfrightcentered' => "\x{02D2}", + 'rinvertedbreve' => "\x{0213}", + 'rittorusquare' => "\x{3351}", + 'rlinebelow' => "\x{1E5F}", + 'rlongleg' => "\x{027C}", + 'rlonglegturned' => "\x{027A}", + 'rmonospace' => "\x{FF52}", + 'rohiragana' => "\x{308D}", + 'rokatakana' => "\x{30ED}", + 'rokatakanahalfwidth' => "\x{FF9B}", + 'roruathai' => "\x{0E23}", + 'rparen' => "\x{24AD}", + 'rrabengali' => "\x{09DC}", + 'rradeva' => "\x{0931}", + 'rragurmukhi' => "\x{0A5C}", + 'rreharabic' => "\x{0691}", + 'rrehfinalarabic' => "\x{FB8D}", + 'rrvocalicbengali' => "\x{09E0}", + 'rrvocalicdeva' => "\x{0960}", + 'rrvocalicgujarati' => "\x{0AE0}", + 'rrvocalicvowelsignbengali' => "\x{09C4}", + 'rrvocalicvowelsigndeva' => "\x{0944}", + 'rrvocalicvowelsigngujarati' => "\x{0AC4}", + 'rsuperior' => "\x{F6F1}", + 'rturned' => "\x{0279}", + 'rturnedsuperior' => "\x{02B4}", + 'ruhiragana' => "\x{308B}", + 'rukatakana' => "\x{30EB}", + 'rukatakanahalfwidth' => "\x{FF99}", + 'rupeemarkbengali' => "\x{09F2}", + 'rupeesignbengali' => "\x{09F3}", + 'rupiah' => "\x{F6DD}", + 'ruthai' => "\x{0E24}", + 'rvocalicbengali' => "\x{098B}", + 'rvocalicdeva' => "\x{090B}", + 'rvocalicgujarati' => "\x{0A8B}", + 'rvocalicvowelsignbengali' => "\x{09C3}", + 'rvocalicvowelsigndeva' => "\x{0943}", + 'rvocalicvowelsigngujarati' => "\x{0AC3}", + 'sabengali' => "\x{09B8}", + 'sacutedotaccent' => "\x{1E65}", + 'sadarabic' => "\x{0635}", + 'sadeva' => "\x{0938}", + 'sadfinalarabic' => "\x{FEBA}", + 'sadinitialarabic' => "\x{FEBB}", + 'sadmedialarabic' => "\x{FEBC}", + 'sagujarati' => "\x{0AB8}", + 'sagurmukhi' => "\x{0A38}", + 'sahiragana' => "\x{3055}", + 'sakatakana' => "\x{30B5}", + 'sakatakanahalfwidth' => "\x{FF7B}", + 'sallallahoualayhewasallamarabic' => "\x{FDFA}", + 'samekh' => "\x{05E1}", + 'samekhdagesh' => "\x{FB41}", + 'samekhdageshhebrew' => "\x{FB41}", + 'samekhhebrew' => "\x{05E1}", + 'saraaathai' => "\x{0E32}", + 'saraaethai' => "\x{0E41}", + 'saraaimaimalaithai' => "\x{0E44}", + 'saraaimaimuanthai' => "\x{0E43}", + 'saraamthai' => "\x{0E33}", + 'saraathai' => "\x{0E30}", + 'saraethai' => "\x{0E40}", + 'saraiileftthai' => "\x{F886}", + 'saraiithai' => "\x{0E35}", + 'saraileftthai' => "\x{F885}", + 'saraithai' => "\x{0E34}", + 'saraothai' => "\x{0E42}", + 'saraueeleftthai' => "\x{F888}", + 'saraueethai' => "\x{0E37}", + 'saraueleftthai' => "\x{F887}", + 'sarauethai' => "\x{0E36}", + 'sarauthai' => "\x{0E38}", + 'sarauuthai' => "\x{0E39}", + 'sbopomofo' => "\x{3119}", + 'scarondotaccent' => "\x{1E67}", + 'schwa' => "\x{0259}", + 'schwacyrillic' => "\x{04D9}", + 'schwadieresiscyrillic' => "\x{04DB}", + 'schwahook' => "\x{025A}", + 'scircle' => "\x{24E2}", + 'sdotaccent' => "\x{1E61}", + 'sdotbelow' => "\x{1E63}", + 'sdotbelowdotaccent' => "\x{1E69}", + 'seagullbelowcmb' => "\x{033C}", + 'secondtonechinese' => "\x{02CA}", + 'seenarabic' => "\x{0633}", + 'seenfinalarabic' => "\x{FEB2}", + 'seeninitialarabic' => "\x{FEB3}", + 'seenmedialarabic' => "\x{FEB4}", + 'segol' => "\x{05B6}", + 'segol13' => "\x{05B6}", + 'segol1f' => "\x{05B6}", + 'segol2c' => "\x{05B6}", + 'segolhebrew' => "\x{05B6}", + 'segolnarrowhebrew' => "\x{05B6}", + 'segolquarterhebrew' => "\x{05B6}", + 'segoltahebrew' => "\x{0592}", + 'segolwidehebrew' => "\x{05B6}", + 'seharmenian' => "\x{057D}", + 'sehiragana' => "\x{305B}", + 'sekatakana' => "\x{30BB}", + 'sekatakanahalfwidth' => "\x{FF7E}", + 'semicolonarabic' => "\x{061B}", + 'semicolonmonospace' => "\x{FF1B}", + 'semicolonsmall' => "\x{FE54}", + 'semivoicedmarkkana' => "\x{309C}", + 'semivoicedmarkkanahalfwidth' => "\x{FF9F}", + 'sentisquare' => "\x{3322}", + 'sentosquare' => "\x{3323}", + 'sevenarabic' => "\x{0667}", + 'sevenbengali' => "\x{09ED}", + 'sevencircle' => "\x{2466}", + 'sevencircleinversesansserif' => "\x{2790}", + 'sevendeva' => "\x{096D}", + 'sevengujarati' => "\x{0AED}", + 'sevengurmukhi' => "\x{0A6D}", + 'sevenhackarabic' => "\x{0667}", + 'sevenhangzhou' => "\x{3027}", + 'sevenideographicparen' => "\x{3226}", + 'seveninferior' => "\x{2087}", + 'sevenmonospace' => "\x{FF17}", + 'sevenoldstyle' => "\x{F737}", + 'sevenparen' => "\x{247A}", + 'sevenperiod' => "\x{248E}", + 'sevenpersian' => "\x{06F7}", + 'sevenroman' => "\x{2176}", + 'sevensuperior' => "\x{2077}", + 'seventeencircle' => "\x{2470}", + 'seventeenparen' => "\x{2484}", + 'seventeenperiod' => "\x{2498}", + 'seventhai' => "\x{0E57}", + 'sfthyphen' => "\x{00AD}", + 'shaarmenian' => "\x{0577}", + 'shabengali' => "\x{09B6}", + 'shacyrillic' => "\x{0448}", + 'shaddaarabic' => "\x{0651}", + 'shaddadammaarabic' => "\x{FC61}", + 'shaddadammatanarabic' => "\x{FC5E}", + 'shaddafathaarabic' => "\x{FC60}", + 'shaddafathatanarabic' => "\x{0651}\x{064B}", + 'shaddakasraarabic' => "\x{FC62}", + 'shaddakasratanarabic' => "\x{FC5F}", + 'shadedark' => "\x{2593}", + 'shadelight' => "\x{2591}", + 'shademedium' => "\x{2592}", + 'shadeva' => "\x{0936}", + 'shagujarati' => "\x{0AB6}", + 'shagurmukhi' => "\x{0A36}", + 'shalshelethebrew' => "\x{0593}", + 'shbopomofo' => "\x{3115}", + 'shchacyrillic' => "\x{0449}", + 'sheenarabic' => "\x{0634}", + 'sheenfinalarabic' => "\x{FEB6}", + 'sheeninitialarabic' => "\x{FEB7}", + 'sheenmedialarabic' => "\x{FEB8}", + 'sheicoptic' => "\x{03E3}", + 'sheqel' => "\x{20AA}", + 'sheqelhebrew' => "\x{20AA}", + 'sheva' => "\x{05B0}", + 'sheva115' => "\x{05B0}", + 'sheva15' => "\x{05B0}", + 'sheva22' => "\x{05B0}", + 'sheva2e' => "\x{05B0}", + 'shevahebrew' => "\x{05B0}", + 'shevanarrowhebrew' => "\x{05B0}", + 'shevaquarterhebrew' => "\x{05B0}", + 'shevawidehebrew' => "\x{05B0}", + 'shhacyrillic' => "\x{04BB}", + 'shimacoptic' => "\x{03ED}", + 'shin' => "\x{05E9}", + 'shindagesh' => "\x{FB49}", + 'shindageshhebrew' => "\x{FB49}", + 'shindageshshindot' => "\x{FB2C}", + 'shindageshshindothebrew' => "\x{FB2C}", + 'shindageshsindot' => "\x{FB2D}", + 'shindageshsindothebrew' => "\x{FB2D}", + 'shindothebrew' => "\x{05C1}", + 'shinhebrew' => "\x{05E9}", + 'shinshindot' => "\x{FB2A}", + 'shinshindothebrew' => "\x{FB2A}", + 'shinsindot' => "\x{FB2B}", + 'shinsindothebrew' => "\x{FB2B}", + 'shook' => "\x{0282}", + 'sigmafinal' => "\x{03C2}", + 'sigmalunatesymbolgreek' => "\x{03F2}", + 'sihiragana' => "\x{3057}", + 'sikatakana' => "\x{30B7}", + 'sikatakanahalfwidth' => "\x{FF7C}", + 'siluqhebrew' => "\x{05BD}", + 'siluqlefthebrew' => "\x{05BD}", + 'sindothebrew' => "\x{05C2}", + 'siosacirclekorean' => "\x{3274}", + 'siosaparenkorean' => "\x{3214}", + 'sioscieuckorean' => "\x{317E}", + 'sioscirclekorean' => "\x{3266}", + 'sioskiyeokkorean' => "\x{317A}", + 'sioskorean' => "\x{3145}", + 'siosnieunkorean' => "\x{317B}", + 'siosparenkorean' => "\x{3206}", + 'siospieupkorean' => "\x{317D}", + 'siostikeutkorean' => "\x{317C}", + 'sixarabic' => "\x{0666}", + 'sixbengali' => "\x{09EC}", + 'sixcircle' => "\x{2465}", + 'sixcircleinversesansserif' => "\x{278F}", + 'sixdeva' => "\x{096C}", + 'sixgujarati' => "\x{0AEC}", + 'sixgurmukhi' => "\x{0A6C}", + 'sixhackarabic' => "\x{0666}", + 'sixhangzhou' => "\x{3026}", + 'sixideographicparen' => "\x{3225}", + 'sixinferior' => "\x{2086}", + 'sixmonospace' => "\x{FF16}", + 'sixoldstyle' => "\x{F736}", + 'sixparen' => "\x{2479}", + 'sixperiod' => "\x{248D}", + 'sixpersian' => "\x{06F6}", + 'sixroman' => "\x{2175}", + 'sixsuperior' => "\x{2076}", + 'sixteencircle' => "\x{246F}", + 'sixteencurrencydenominatorbengali' => "\x{09F9}", + 'sixteenparen' => "\x{2483}", + 'sixteenperiod' => "\x{2497}", + 'sixthai' => "\x{0E56}", + 'slashmonospace' => "\x{FF0F}", + 'slong' => "\x{017F}", + 'slongdotaccent' => "\x{1E9B}", + 'smonospace' => "\x{FF53}", + 'sofpasuqhebrew' => "\x{05C3}", + 'softhyphen' => "\x{00AD}", + 'softsigncyrillic' => "\x{044C}", + 'sohiragana' => "\x{305D}", + 'sokatakana' => "\x{30BD}", + 'sokatakanahalfwidth' => "\x{FF7F}", + 'soliduslongoverlaycmb' => "\x{0338}", + 'solidusshortoverlaycmb' => "\x{0337}", + 'sorusithai' => "\x{0E29}", + 'sosalathai' => "\x{0E28}", + 'sosothai' => "\x{0E0B}", + 'sosuathai' => "\x{0E2A}", + 'spacehackarabic' => "\x{0020}", + 'spadesuitblack' => "\x{2660}", + 'spadesuitwhite' => "\x{2664}", + 'sparen' => "\x{24AE}", + 'squarebelowcmb' => "\x{033B}", + 'squarecc' => "\x{33C4}", + 'squarecm' => "\x{339D}", + 'squarediagonalcrosshatchfill' => "\x{25A9}", + 'squarehorizontalfill' => "\x{25A4}", + 'squarekg' => "\x{338F}", + 'squarekm' => "\x{339E}", + 'squarekmcapital' => "\x{33CE}", + 'squareln' => "\x{33D1}", + 'squarelog' => "\x{33D2}", + 'squaremg' => "\x{338E}", + 'squaremil' => "\x{33D5}", + 'squaremm' => "\x{339C}", + 'squaremsquared' => "\x{33A1}", + 'squareorthogonalcrosshatchfill' => "\x{25A6}", + 'squareupperlefttolowerrightfill' => "\x{25A7}", + 'squareupperrighttolowerleftfill' => "\x{25A8}", + 'squareverticalfill' => "\x{25A5}", + 'squarewhitewithsmallblack' => "\x{25A3}", + 'srsquare' => "\x{33DB}", + 'ssabengali' => "\x{09B7}", + 'ssadeva' => "\x{0937}", + 'ssagujarati' => "\x{0AB7}", + 'ssangcieuckorean' => "\x{3149}", + 'ssanghieuhkorean' => "\x{3185}", + 'ssangieungkorean' => "\x{3180}", + 'ssangkiyeokkorean' => "\x{3132}", + 'ssangnieunkorean' => "\x{3165}", + 'ssangpieupkorean' => "\x{3143}", + 'ssangsioskorean' => "\x{3146}", + 'ssangtikeutkorean' => "\x{3138}", + 'ssuperior' => "\x{F6F2}", + 'sterlingmonospace' => "\x{FFE1}", + 'strokelongoverlaycmb' => "\x{0336}", + 'strokeshortoverlaycmb' => "\x{0335}", + 'subset' => "\x{2282}", + 'subsetnotequal' => "\x{228A}", + 'subsetorequal' => "\x{2286}", + 'succeeds' => "\x{227B}", + 'suhiragana' => "\x{3059}", + 'sukatakana' => "\x{30B9}", + 'sukatakanahalfwidth' => "\x{FF7D}", + 'sukunarabic' => "\x{0652}", + 'superset' => "\x{2283}", + 'supersetnotequal' => "\x{228B}", + 'supersetorequal' => "\x{2287}", + 'svsquare' => "\x{33DC}", + 'syouwaerasquare' => "\x{337C}", + 'tabengali' => "\x{09A4}", + 'tackdown' => "\x{22A4}", + 'tackleft' => "\x{22A3}", + 'tadeva' => "\x{0924}", + 'tagujarati' => "\x{0AA4}", + 'tagurmukhi' => "\x{0A24}", + 'taharabic' => "\x{0637}", + 'tahfinalarabic' => "\x{FEC2}", + 'tahinitialarabic' => "\x{FEC3}", + 'tahiragana' => "\x{305F}", + 'tahmedialarabic' => "\x{FEC4}", + 'taisyouerasquare' => "\x{337D}", + 'takatakana' => "\x{30BF}", + 'takatakanahalfwidth' => "\x{FF80}", + 'tatweelarabic' => "\x{0640}", + 'tav' => "\x{05EA}", + 'tavdages' => "\x{FB4A}", + 'tavdagesh' => "\x{FB4A}", + 'tavdageshhebrew' => "\x{FB4A}", + 'tavhebrew' => "\x{05EA}", + 'tbopomofo' => "\x{310A}", + 'tccurl' => "\x{02A8}", + 'tcedilla' => "\x{0163}", + 'tcheharabic' => "\x{0686}", + 'tchehfinalarabic' => "\x{FB7B}", + 'tchehinitialarabic' => "\x{FB7C}", + 'tchehmedialarabic' => "\x{FB7D}", + 'tchehmeeminitialarabic' => "\x{FB7C}\x{FEE4}", + 'tcircle' => "\x{24E3}", + 'tcircumflexbelow' => "\x{1E71}", + 'tdieresis' => "\x{1E97}", + 'tdotaccent' => "\x{1E6B}", + 'tdotbelow' => "\x{1E6D}", + 'tecyrillic' => "\x{0442}", + 'tedescendercyrillic' => "\x{04AD}", + 'teharabic' => "\x{062A}", + 'tehfinalarabic' => "\x{FE96}", + 'tehhahinitialarabic' => "\x{FCA2}", + 'tehhahisolatedarabic' => "\x{FC0C}", + 'tehinitialarabic' => "\x{FE97}", + 'tehiragana' => "\x{3066}", + 'tehjeeminitialarabic' => "\x{FCA1}", + 'tehjeemisolatedarabic' => "\x{FC0B}", + 'tehmarbutaarabic' => "\x{0629}", + 'tehmarbutafinalarabic' => "\x{FE94}", + 'tehmedialarabic' => "\x{FE98}", + 'tehmeeminitialarabic' => "\x{FCA4}", + 'tehmeemisolatedarabic' => "\x{FC0E}", + 'tehnoonfinalarabic' => "\x{FC73}", + 'tekatakana' => "\x{30C6}", + 'tekatakanahalfwidth' => "\x{FF83}", + 'telephone' => "\x{2121}", + 'telephoneblack' => "\x{260E}", + 'telishagedolahebrew' => "\x{05A0}", + 'telishaqetanahebrew' => "\x{05A9}", + 'tencircle' => "\x{2469}", + 'tenideographicparen' => "\x{3229}", + 'tenparen' => "\x{247D}", + 'tenperiod' => "\x{2491}", + 'tenroman' => "\x{2179}", + 'tesh' => "\x{02A7}", + 'tet' => "\x{05D8}", + 'tetdagesh' => "\x{FB38}", + 'tetdageshhebrew' => "\x{FB38}", + 'tethebrew' => "\x{05D8}", + 'tetsecyrillic' => "\x{04B5}", + 'tevirhebrew' => "\x{059B}", + 'tevirlefthebrew' => "\x{059B}", + 'thabengali' => "\x{09A5}", + 'thadeva' => "\x{0925}", + 'thagujarati' => "\x{0AA5}", + 'thagurmukhi' => "\x{0A25}", + 'thalarabic' => "\x{0630}", + 'thalfinalarabic' => "\x{FEAC}", + 'thanthakhatlowleftthai' => "\x{F898}", + 'thanthakhatlowrightthai' => "\x{F897}", + 'thanthakhatthai' => "\x{0E4C}", + 'thanthakhatupperleftthai' => "\x{F896}", + 'theharabic' => "\x{062B}", + 'thehfinalarabic' => "\x{FE9A}", + 'thehinitialarabic' => "\x{FE9B}", + 'thehmedialarabic' => "\x{FE9C}", + 'thereexists' => "\x{2203}", + 'thetasymbolgreek' => "\x{03D1}", + 'thieuthacirclekorean' => "\x{3279}", + 'thieuthaparenkorean' => "\x{3219}", + 'thieuthcirclekorean' => "\x{326B}", + 'thieuthkorean' => "\x{314C}", + 'thieuthparenkorean' => "\x{320B}", + 'thirteencircle' => "\x{246C}", + 'thirteenparen' => "\x{2480}", + 'thirteenperiod' => "\x{2494}", + 'thonangmonthothai' => "\x{0E11}", + 'thook' => "\x{01AD}", + 'thophuthaothai' => "\x{0E12}", + 'thothahanthai' => "\x{0E17}", + 'thothanthai' => "\x{0E10}", + 'thothongthai' => "\x{0E18}", + 'thothungthai' => "\x{0E16}", + 'thousandcyrillic' => "\x{0482}", + 'thousandsseparatorarabic' => "\x{066C}", + 'thousandsseparatorpersian' => "\x{066C}", + 'threearabic' => "\x{0663}", + 'threebengali' => "\x{09E9}", + 'threecircle' => "\x{2462}", + 'threecircleinversesansserif' => "\x{278C}", + 'threedeva' => "\x{0969}", + 'threegujarati' => "\x{0AE9}", + 'threegurmukhi' => "\x{0A69}", + 'threehackarabic' => "\x{0663}", + 'threehangzhou' => "\x{3023}", + 'threeideographicparen' => "\x{3222}", + 'threeinferior' => "\x{2083}", + 'threemonospace' => "\x{FF13}", + 'threenumeratorbengali' => "\x{09F6}", + 'threeoldstyle' => "\x{F733}", + 'threeparen' => "\x{2476}", + 'threeperiod' => "\x{248A}", + 'threepersian' => "\x{06F3}", + 'threequartersemdash' => "\x{F6DE}", + 'threeroman' => "\x{2172}", + 'threethai' => "\x{0E53}", + 'thzsquare' => "\x{3394}", + 'tihiragana' => "\x{3061}", + 'tikatakana' => "\x{30C1}", + 'tikatakanahalfwidth' => "\x{FF81}", + 'tikeutacirclekorean' => "\x{3270}", + 'tikeutaparenkorean' => "\x{3210}", + 'tikeutcirclekorean' => "\x{3262}", + 'tikeutkorean' => "\x{3137}", + 'tikeutparenkorean' => "\x{3202}", + 'tildebelowcmb' => "\x{0330}", + 'tildecmb' => "\x{0303}", + 'tildedoublecmb' => "\x{0360}", + 'tildeoperator' => "\x{223C}", + 'tildeoverlaycmb' => "\x{0334}", + 'tildeverticalcmb' => "\x{033E}", + 'timescircle' => "\x{2297}", + 'tipehahebrew' => "\x{0596}", + 'tipehalefthebrew' => "\x{0596}", + 'tippigurmukhi' => "\x{0A70}", + 'titlocyrilliccmb' => "\x{0483}", + 'tiwnarmenian' => "\x{057F}", + 'tlinebelow' => "\x{1E6F}", + 'tmonospace' => "\x{FF54}", + 'toarmenian' => "\x{0569}", + 'tohiragana' => "\x{3068}", + 'tokatakana' => "\x{30C8}", + 'tokatakanahalfwidth' => "\x{FF84}", + 'tonebarextrahighmod' => "\x{02E5}", + 'tonebarextralowmod' => "\x{02E9}", + 'tonebarhighmod' => "\x{02E6}", + 'tonebarlowmod' => "\x{02E8}", + 'tonebarmidmod' => "\x{02E7}", + 'tonefive' => "\x{01BD}", + 'tonesix' => "\x{0185}", + 'tonetwo' => "\x{01A8}", + 'tonsquare' => "\x{3327}", + 'topatakthai' => "\x{0E0F}", + 'tortoiseshellbracketleft' => "\x{3014}", + 'tortoiseshellbracketleftsmall' => "\x{FE5D}", + 'tortoiseshellbracketleftvertical' => "\x{FE39}", + 'tortoiseshellbracketright' => "\x{3015}", + 'tortoiseshellbracketrightsmall' => "\x{FE5E}", + 'tortoiseshellbracketrightvertical' => "\x{FE3A}", + 'totaothai' => "\x{0E15}", + 'tpalatalhook' => "\x{01AB}", + 'tparen' => "\x{24AF}", + 'trademarksans' => "\x{F8EA}", + 'trademarkserif' => "\x{F6DB}", + 'tretroflexhook' => "\x{0288}", + 'ts' => "\x{02A6}", + 'tsadi' => "\x{05E6}", + 'tsadidagesh' => "\x{FB46}", + 'tsadidageshhebrew' => "\x{FB46}", + 'tsadihebrew' => "\x{05E6}", + 'tsecyrillic' => "\x{0446}", + 'tsere' => "\x{05B5}", + 'tsere12' => "\x{05B5}", + 'tsere1e' => "\x{05B5}", + 'tsere2b' => "\x{05B5}", + 'tserehebrew' => "\x{05B5}", + 'tserenarrowhebrew' => "\x{05B5}", + 'tserequarterhebrew' => "\x{05B5}", + 'tserewidehebrew' => "\x{05B5}", + 'tshecyrillic' => "\x{045B}", + 'tsuperior' => "\x{F6F3}", + 'ttabengali' => "\x{099F}", + 'ttadeva' => "\x{091F}", + 'ttagujarati' => "\x{0A9F}", + 'ttagurmukhi' => "\x{0A1F}", + 'tteharabic' => "\x{0679}", + 'ttehfinalarabic' => "\x{FB67}", + 'ttehinitialarabic' => "\x{FB68}", + 'ttehmedialarabic' => "\x{FB69}", + 'tthabengali' => "\x{09A0}", + 'tthadeva' => "\x{0920}", + 'tthagujarati' => "\x{0AA0}", + 'tthagurmukhi' => "\x{0A20}", + 'tturned' => "\x{0287}", + 'tuhiragana' => "\x{3064}", + 'tukatakana' => "\x{30C4}", + 'tukatakanahalfwidth' => "\x{FF82}", + 'tusmallhiragana' => "\x{3063}", + 'tusmallkatakana' => "\x{30C3}", + 'tusmallkatakanahalfwidth' => "\x{FF6F}", + 'twelvecircle' => "\x{246B}", + 'twelveparen' => "\x{247F}", + 'twelveperiod' => "\x{2493}", + 'twelveroman' => "\x{217B}", + 'twentycircle' => "\x{2473}", + 'twentyhangzhou' => "\x{5344}", + 'twentyparen' => "\x{2487}", + 'twentyperiod' => "\x{249B}", + 'twoarabic' => "\x{0662}", + 'twobengali' => "\x{09E8}", + 'twocircle' => "\x{2461}", + 'twocircleinversesansserif' => "\x{278B}", + 'twodeva' => "\x{0968}", + 'twodotleader' => "\x{2025}", + 'twodotleadervertical' => "\x{FE30}", + 'twogujarati' => "\x{0AE8}", + 'twogurmukhi' => "\x{0A68}", + 'twohackarabic' => "\x{0662}", + 'twohangzhou' => "\x{3022}", + 'twoideographicparen' => "\x{3221}", + 'twoinferior' => "\x{2082}", + 'twomonospace' => "\x{FF12}", + 'twonumeratorbengali' => "\x{09F5}", + 'twooldstyle' => "\x{F732}", + 'twoparen' => "\x{2475}", + 'twoperiod' => "\x{2489}", + 'twopersian' => "\x{06F2}", + 'tworoman' => "\x{2171}", + 'twostroke' => "\x{01BB}", + 'twothai' => "\x{0E52}", + 'ubar' => "\x{0289}", + 'ubengali' => "\x{0989}", + 'ubopomofo' => "\x{3128}", + 'ucaron' => "\x{01D4}", + 'ucircle' => "\x{24E4}", + 'ucircumflexbelow' => "\x{1E77}", + 'ucyrillic' => "\x{0443}", + 'udattadeva' => "\x{0951}", + 'udblacute' => "\x{0171}", + 'udblgrave' => "\x{0215}", + 'udeva' => "\x{0909}", + 'udieresisacute' => "\x{01D8}", + 'udieresisbelow' => "\x{1E73}", + 'udieresiscaron' => "\x{01DA}", + 'udieresiscyrillic' => "\x{04F1}", + 'udieresisgrave' => "\x{01DC}", + 'udieresismacron' => "\x{01D6}", + 'udotbelow' => "\x{1EE5}", + 'ugujarati' => "\x{0A89}", + 'ugurmukhi' => "\x{0A09}", + 'uhiragana' => "\x{3046}", + 'uhookabove' => "\x{1EE7}", + 'uhornacute' => "\x{1EE9}", + 'uhorndotbelow' => "\x{1EF1}", + 'uhorngrave' => "\x{1EEB}", + 'uhornhookabove' => "\x{1EED}", + 'uhorntilde' => "\x{1EEF}", + 'uhungarumlautcyrillic' => "\x{04F3}", + 'uinvertedbreve' => "\x{0217}", + 'ukatakana' => "\x{30A6}", + 'ukatakanahalfwidth' => "\x{FF73}", + 'ukcyrillic' => "\x{0479}", + 'ukorean' => "\x{315C}", + 'umacroncyrillic' => "\x{04EF}", + 'umacrondieresis' => "\x{1E7B}", + 'umatragurmukhi' => "\x{0A41}", + 'umonospace' => "\x{FF55}", + 'underscoremonospace' => "\x{FF3F}", + 'underscorevertical' => "\x{FE33}", + 'underscorewavy' => "\x{FE4F}", + 'uparen' => "\x{24B0}", + 'upperdothebrew' => "\x{05C4}", + 'upsilonlatin' => "\x{028A}", + 'uptackbelowcmb' => "\x{031D}", + 'uptackmod' => "\x{02D4}", + 'uragurmukhi' => "\x{0A73}", + 'ushortcyrillic' => "\x{045E}", + 'usmallhiragana' => "\x{3045}", + 'usmallkatakana' => "\x{30A5}", + 'usmallkatakanahalfwidth' => "\x{FF69}", + 'ustraightcyrillic' => "\x{04AF}", + 'ustraightstrokecyrillic' => "\x{04B1}", + 'utildeacute' => "\x{1E79}", + 'utildebelow' => "\x{1E75}", + 'uubengali' => "\x{098A}", + 'uudeva' => "\x{090A}", + 'uugujarati' => "\x{0A8A}", + 'uugurmukhi' => "\x{0A0A}", + 'uumatragurmukhi' => "\x{0A42}", + 'uuvowelsignbengali' => "\x{09C2}", + 'uuvowelsigndeva' => "\x{0942}", + 'uuvowelsigngujarati' => "\x{0AC2}", + 'uvowelsignbengali' => "\x{09C1}", + 'uvowelsigndeva' => "\x{0941}", + 'uvowelsigngujarati' => "\x{0AC1}", + 'vadeva' => "\x{0935}", + 'vagujarati' => "\x{0AB5}", + 'vagurmukhi' => "\x{0A35}", + 'vakatakana' => "\x{30F7}", + 'vav' => "\x{05D5}", + 'vavdagesh' => "\x{FB35}", + 'vavdagesh65' => "\x{FB35}", + 'vavdageshhebrew' => "\x{FB35}", + 'vavhebrew' => "\x{05D5}", + 'vavholam' => "\x{FB4B}", + 'vavholamhebrew' => "\x{FB4B}", + 'vavvavhebrew' => "\x{05F0}", + 'vavyodhebrew' => "\x{05F1}", + 'vcircle' => "\x{24E5}", + 'vdotbelow' => "\x{1E7F}", + 'vecyrillic' => "\x{0432}", + 'veharabic' => "\x{06A4}", + 'vehfinalarabic' => "\x{FB6B}", + 'vehinitialarabic' => "\x{FB6C}", + 'vehmedialarabic' => "\x{FB6D}", + 'vekatakana' => "\x{30F9}", + 'venus' => "\x{2640}", + 'verticalbar' => "\x{007C}", + 'verticallineabovecmb' => "\x{030D}", + 'verticallinebelowcmb' => "\x{0329}", + 'verticallinelowmod' => "\x{02CC}", + 'verticallinemod' => "\x{02C8}", + 'vewarmenian' => "\x{057E}", + 'vhook' => "\x{028B}", + 'vikatakana' => "\x{30F8}", + 'viramabengali' => "\x{09CD}", + 'viramadeva' => "\x{094D}", + 'viramagujarati' => "\x{0ACD}", + 'visargabengali' => "\x{0983}", + 'visargadeva' => "\x{0903}", + 'visargagujarati' => "\x{0A83}", + 'vmonospace' => "\x{FF56}", + 'voarmenian' => "\x{0578}", + 'voicediterationhiragana' => "\x{309E}", + 'voicediterationkatakana' => "\x{30FE}", + 'voicedmarkkana' => "\x{309B}", + 'voicedmarkkanahalfwidth' => "\x{FF9E}", + 'vokatakana' => "\x{30FA}", + 'vparen' => "\x{24B1}", + 'vtilde' => "\x{1E7D}", + 'vturned' => "\x{028C}", + 'vuhiragana' => "\x{3094}", + 'vukatakana' => "\x{30F4}", + 'waekorean' => "\x{3159}", + 'wahiragana' => "\x{308F}", + 'wakatakana' => "\x{30EF}", + 'wakatakanahalfwidth' => "\x{FF9C}", + 'wakorean' => "\x{3158}", + 'wasmallhiragana' => "\x{308E}", + 'wasmallkatakana' => "\x{30EE}", + 'wattosquare' => "\x{3357}", + 'wavedash' => "\x{301C}", + 'wavyunderscorevertical' => "\x{FE34}", + 'wawarabic' => "\x{0648}", + 'wawfinalarabic' => "\x{FEEE}", + 'wawhamzaabovearabic' => "\x{0624}", + 'wawhamzaabovefinalarabic' => "\x{FE86}", + 'wbsquare' => "\x{33DD}", + 'wcircle' => "\x{24E6}", + 'wdotaccent' => "\x{1E87}", + 'wdotbelow' => "\x{1E89}", + 'wehiragana' => "\x{3091}", + 'wekatakana' => "\x{30F1}", + 'wekorean' => "\x{315E}", + 'weokorean' => "\x{315D}", + 'whitebullet' => "\x{25E6}", + 'whitecircle' => "\x{25CB}", + 'whitecircleinverse' => "\x{25D9}", + 'whitecornerbracketleft' => "\x{300E}", + 'whitecornerbracketleftvertical' => "\x{FE43}", + 'whitecornerbracketright' => "\x{300F}", + 'whitecornerbracketrightvertical' => "\x{FE44}", + 'whitediamond' => "\x{25C7}", + 'whitediamondcontainingblacksmalldiamond' => "\x{25C8}", + 'whitedownpointingsmalltriangle' => "\x{25BF}", + 'whitedownpointingtriangle' => "\x{25BD}", + 'whiteleftpointingsmalltriangle' => "\x{25C3}", + 'whiteleftpointingtriangle' => "\x{25C1}", + 'whitelenticularbracketleft' => "\x{3016}", + 'whitelenticularbracketright' => "\x{3017}", + 'whiterightpointingsmalltriangle' => "\x{25B9}", + 'whiterightpointingtriangle' => "\x{25B7}", + 'whitesmallsquare' => "\x{25AB}", + 'whitesmilingface' => "\x{263A}", + 'whitesquare' => "\x{25A1}", + 'whitestar' => "\x{2606}", + 'whitetelephone' => "\x{260F}", + 'whitetortoiseshellbracketleft' => "\x{3018}", + 'whitetortoiseshellbracketright' => "\x{3019}", + 'whiteuppointingsmalltriangle' => "\x{25B5}", + 'whiteuppointingtriangle' => "\x{25B3}", + 'wihiragana' => "\x{3090}", + 'wikatakana' => "\x{30F0}", + 'wikorean' => "\x{315F}", + 'wmonospace' => "\x{FF57}", + 'wohiragana' => "\x{3092}", + 'wokatakana' => "\x{30F2}", + 'wokatakanahalfwidth' => "\x{FF66}", + 'won' => "\x{20A9}", + 'wonmonospace' => "\x{FFE6}", + 'wowaenthai' => "\x{0E27}", + 'wparen' => "\x{24B2}", + 'wring' => "\x{1E98}", + 'wsuperior' => "\x{02B7}", + 'wturned' => "\x{028D}", + 'wynn' => "\x{01BF}", + 'xabovecmb' => "\x{033D}", + 'xbopomofo' => "\x{3112}", + 'xcircle' => "\x{24E7}", + 'xdieresis' => "\x{1E8D}", + 'xdotaccent' => "\x{1E8B}", + 'xeharmenian' => "\x{056D}", + 'xmonospace' => "\x{FF58}", + 'xparen' => "\x{24B3}", + 'xsuperior' => "\x{02E3}", + 'yaadosquare' => "\x{334E}", + 'yabengali' => "\x{09AF}", + 'yadeva' => "\x{092F}", + 'yaekorean' => "\x{3152}", + 'yagujarati' => "\x{0AAF}", + 'yagurmukhi' => "\x{0A2F}", + 'yahiragana' => "\x{3084}", + 'yakatakana' => "\x{30E4}", + 'yakatakanahalfwidth' => "\x{FF94}", + 'yakorean' => "\x{3151}", + 'yamakkanthai' => "\x{0E4E}", + 'yasmallhiragana' => "\x{3083}", + 'yasmallkatakana' => "\x{30E3}", + 'yasmallkatakanahalfwidth' => "\x{FF6C}", + 'yatcyrillic' => "\x{0463}", + 'ycircle' => "\x{24E8}", + 'ydotaccent' => "\x{1E8F}", + 'ydotbelow' => "\x{1EF5}", + 'yeharabic' => "\x{064A}", + 'yehbarreearabic' => "\x{06D2}", + 'yehbarreefinalarabic' => "\x{FBAF}", + 'yehfinalarabic' => "\x{FEF2}", + 'yehhamzaabovearabic' => "\x{0626}", + 'yehhamzaabovefinalarabic' => "\x{FE8A}", + 'yehhamzaaboveinitialarabic' => "\x{FE8B}", + 'yehhamzaabovemedialarabic' => "\x{FE8C}", + 'yehinitialarabic' => "\x{FEF3}", + 'yehmedialarabic' => "\x{FEF4}", + 'yehmeeminitialarabic' => "\x{FCDD}", + 'yehmeemisolatedarabic' => "\x{FC58}", + 'yehnoonfinalarabic' => "\x{FC94}", + 'yehthreedotsbelowarabic' => "\x{06D1}", + 'yekorean' => "\x{3156}", + 'yenmonospace' => "\x{FFE5}", + 'yeokorean' => "\x{3155}", + 'yeorinhieuhkorean' => "\x{3186}", + 'yerahbenyomohebrew' => "\x{05AA}", + 'yerahbenyomolefthebrew' => "\x{05AA}", + 'yericyrillic' => "\x{044B}", + 'yerudieresiscyrillic' => "\x{04F9}", + 'yesieungkorean' => "\x{3181}", + 'yesieungpansioskorean' => "\x{3183}", + 'yesieungsioskorean' => "\x{3182}", + 'yetivhebrew' => "\x{059A}", + 'yhook' => "\x{01B4}", + 'yhookabove' => "\x{1EF7}", + 'yiarmenian' => "\x{0575}", + 'yicyrillic' => "\x{0457}", + 'yikorean' => "\x{3162}", + 'yinyang' => "\x{262F}", + 'yiwnarmenian' => "\x{0582}", + 'ymonospace' => "\x{FF59}", + 'yod' => "\x{05D9}", + 'yoddagesh' => "\x{FB39}", + 'yoddageshhebrew' => "\x{FB39}", + 'yodhebrew' => "\x{05D9}", + 'yodyodhebrew' => "\x{05F2}", + 'yodyodpatahhebrew' => "\x{FB1F}", + 'yohiragana' => "\x{3088}", + 'yoikorean' => "\x{3189}", + 'yokatakana' => "\x{30E8}", + 'yokatakanahalfwidth' => "\x{FF96}", + 'yokorean' => "\x{315B}", + 'yosmallhiragana' => "\x{3087}", + 'yosmallkatakana' => "\x{30E7}", + 'yosmallkatakanahalfwidth' => "\x{FF6E}", + 'yotgreek' => "\x{03F3}", + 'yoyaekorean' => "\x{3188}", + 'yoyakorean' => "\x{3187}", + 'yoyakthai' => "\x{0E22}", + 'yoyingthai' => "\x{0E0D}", + 'yparen' => "\x{24B4}", + 'ypogegrammeni' => "\x{037A}", + 'ypogegrammenigreekcmb' => "\x{0345}", + 'yr' => "\x{01A6}", + 'yring' => "\x{1E99}", + 'ysuperior' => "\x{02B8}", + 'ytilde' => "\x{1EF9}", + 'yturned' => "\x{028E}", + 'yuhiragana' => "\x{3086}", + 'yuikorean' => "\x{318C}", + 'yukatakana' => "\x{30E6}", + 'yukatakanahalfwidth' => "\x{FF95}", + 'yukorean' => "\x{3160}", + 'yusbigcyrillic' => "\x{046B}", + 'yusbigiotifiedcyrillic' => "\x{046D}", + 'yuslittlecyrillic' => "\x{0467}", + 'yuslittleiotifiedcyrillic' => "\x{0469}", + 'yusmallhiragana' => "\x{3085}", + 'yusmallkatakana' => "\x{30E5}", + 'yusmallkatakanahalfwidth' => "\x{FF6D}", + 'yuyekorean' => "\x{318B}", + 'yuyeokorean' => "\x{318A}", + 'yyabengali' => "\x{09DF}", + 'yyadeva' => "\x{095F}", + 'zaarmenian' => "\x{0566}", + 'zadeva' => "\x{095B}", + 'zagurmukhi' => "\x{0A5B}", + 'zaharabic' => "\x{0638}", + 'zahfinalarabic' => "\x{FEC6}", + 'zahinitialarabic' => "\x{FEC7}", + 'zahiragana' => "\x{3056}", + 'zahmedialarabic' => "\x{FEC8}", + 'zainarabic' => "\x{0632}", + 'zainfinalarabic' => "\x{FEB0}", + 'zakatakana' => "\x{30B6}", + 'zaqefgadolhebrew' => "\x{0595}", + 'zaqefqatanhebrew' => "\x{0594}", + 'zarqahebrew' => "\x{0598}", + 'zayin' => "\x{05D6}", + 'zayindagesh' => "\x{FB36}", + 'zayindageshhebrew' => "\x{FB36}", + 'zayinhebrew' => "\x{05D6}", + 'zbopomofo' => "\x{3117}", + 'zcircle' => "\x{24E9}", + 'zcircumflex' => "\x{1E91}", + 'zcurl' => "\x{0291}", + 'zdot' => "\x{017C}", + 'zdotbelow' => "\x{1E93}", + 'zecyrillic' => "\x{0437}", + 'zedescendercyrillic' => "\x{0499}", + 'zedieresiscyrillic' => "\x{04DF}", + 'zehiragana' => "\x{305C}", + 'zekatakana' => "\x{30BC}", + 'zeroarabic' => "\x{0660}", + 'zerobengali' => "\x{09E6}", + 'zerodeva' => "\x{0966}", + 'zerogujarati' => "\x{0AE6}", + 'zerogurmukhi' => "\x{0A66}", + 'zerohackarabic' => "\x{0660}", + 'zeroinferior' => "\x{2080}", + 'zeromonospace' => "\x{FF10}", + 'zerooldstyle' => "\x{F730}", + 'zeropersian' => "\x{06F0}", + 'zerosuperior' => "\x{2070}", + 'zerothai' => "\x{0E50}", + 'zerowidthjoiner' => "\x{FEFF}", + 'zerowidthnonjoiner' => "\x{200C}", + 'zerowidthspace' => "\x{200B}", + 'zhbopomofo' => "\x{3113}", + 'zhearmenian' => "\x{056A}", + 'zhebrevecyrillic' => "\x{04C2}", + 'zhecyrillic' => "\x{0436}", + 'zhedescendercyrillic' => "\x{0497}", + 'zhedieresiscyrillic' => "\x{04DD}", + 'zihiragana' => "\x{3058}", + 'zikatakana' => "\x{30B8}", + 'zinorhebrew' => "\x{05AE}", + 'zlinebelow' => "\x{1E95}", + 'zmonospace' => "\x{FF5A}", + 'zohiragana' => "\x{305E}", + 'zokatakana' => "\x{30BE}", + 'zparen' => "\x{24B5}", + 'zretroflexhook' => "\x{0290}", + 'zstroke' => "\x{01B6}", + 'zuhiragana' => "\x{305A}", + 'zukatakana' => "\x{30BA}", + ); + +# Add to this list the glyphs for new fonts (from aglfn13): + +map { $agl{$names{$_}} = pack('U',hex ($_))} (keys %names); + + +# %doubles = (map{$_ => "uni$_"} qw(0394 03A9 0162 2215 00AD 02C9 03BC 2219 00A0 0163)); + +=head2 lookup ( $usv [, $noAlt [, $noUni] ]) + +return the Adobe-recommended glyph name for a specific Unicode codepoint (integer). By default +returns C names rather than C or C names + +If C<$noAlt> is true, C and C names are returned rather than C. + +if C<$noUni> is true, returns undef if it would have to resort to C or C +style names. Essentially this represents a straight lookup in the Adobe-recommended list. + +=cut + +sub lookup +{ + my ($num, $noalt, $noUni) = @_; + my ($val) = sprintf("%04X", $num); + + if (defined $names{$val}) + { + return $names{$val} if ($noalt || $names{$val} !~ m/^(?:afii|SF)/o); + } + return undef if $noUni; + if ($num > 0xFFFF) + { return "u$val"; } + elsif ($num) + { return "uni$val"; } + else + { return ".notdef"; } +} + +=head2 parse ( $glyphname ) + +Parse an Adobe-conformant glyph name, generating a Unicode codepoint sequence equivalent to the glyph (or +glyph components, should the name represent a ligature). In scalar context, returns a reference to an +array of Unicodes (decimal). Array is empty if the glyph name is non-conformant. +In list context, the first item returned is the same array reference as above. The second item +is a reference to an array containing the extensions (if any) present on the glyph name. +The '.' that precedes each extension is not included. + +=cut + +sub parse +{ + my ($gname, @USVs, @extensions); + ($gname, @extensions) = split('\.', $_[0]); + # if name originally started with . (e.g., .null) then $gname will now be '' ... need to fix that up: + $gname = '.' . shift(@extensions) if $gname eq ''; + if (defined $gname) + { + foreach $gname (split('_', $gname)) + { + if ($gname =~ /^u[0-9a-fA-F]{4,6}$/) + { + push @USVs, hex(substr($gname, 1)); + } + elsif ($gname =~ /^uni([0-9a-fA-F]{4,4})+$/) + { + push @USVs, map {hex($_)} ($gname =~ /([0-9a-fA-F]{4,4})/g) + } + elsif (exists $agl{$gname}) + { + push @USVs, unpack ('U*', $agl{$gname}); + } + } + } + return \@USVs unless wantarray; + my @res = (\@USVs, \@extensions); + return @res; +} + +#Code used to parse Adobe's agl file and generate text for %agl initialization: +#while () { +# chomp; +# next if m/^#/; +# my ($gname, @nums) = split(/[; ]/); +# if ($#nums > 0 or !defined ($Font::TTF::PSNames::names{$nums[0]}) or $Font::TTF::PSNames::names{$nums[0]} ne $gname) +# { +# print "\t'$gname' => \""; +# map {print "\\x{$_}" } @nums; +# print "\",\n"; +# } +# } + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Post.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Post.pm new file mode 100644 index 0000000..4bf284e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Post.pm @@ -0,0 +1,306 @@ +package Font::TTF::Post; + +=head1 NAME + +Font::TTF::Post - Holds the Postscript names for each glyph + +=head1 DESCRIPTION + +Holds the postscript names for glyphs. Note that they are not held as an +array, but as indexes into two lists. The first list is the standard Postscript +name list defined by the TrueType standard. The second comes from the font +directly. + +Looking up a glyph from a Postscript name or a name from a glyph number is +achieved through methods rather than variable lookup. + +This class handles PostScript table types of 1, 2, 2.5 & 3, but not version 4. +Support for version 2.5 is as per Apple spec rather than MS. + +The way to look up Postscript names or glyphs is: + + $pname = $f->{'post'}{'VAL'}[$gnum]; + $gnum = $f->{'post'}{'STRINGS'}{$pname}; + +=head1 INSTANCE VARIABLES + +Due to different systems having different limitations, there are various class +variables available to control what post table types can be written. + +=over 4 + +=item $Font::TTF::Post::no25 + +If set tells Font::TTF::Post::out to use table type 2 instead of 2.5 in case apps +can't handle version 2.5. + +=item VAL + +Contains an array indexed by glyph number of Postscript names. This is used when +writing out a font. + +=item STRINGS + +An associative array of Postscript names mapping to the highest glyph with that +name. These may not be in sync with VAL. + +=back + +In addition there are the standard introductory variables defined in the +standard: + + FormatType + italicAngle + underlinePosition + underlineThickness + isFixedPitch + minMemType42 + maxMemType42 + minMemType1 + maxMemType1 + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA @base_set %base_set %fields $VERSION $no25 @field_info @base_set); +require Font::TTF::Table; +use Font::TTF::Utils; + +$no25 = 1; # officially deprecated format 2.5 tables in MS spec 1.3 + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'FormatType' => 'f', + 'italicAngle' => 'f', + 'underlinePosition' => 's', + 'underlineThickness' => 's', + 'isFixedPitch' => 'L', + 'minMemType42' => 'L', + 'maxMemType42' => 'L', + 'minMemType1' => 'L', + 'maxMemType1' => 'L'); +@base_set = qw(.notdef .null nonmarkingreturn space exclam quotedbl numbersign dollar percent ampersand quotesingle + parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six + seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q + R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h + i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde Adieresis Aring Ccedilla + Eacute Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla eacute + egrave ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute ograve ocircumflex + odieresis otilde uacute ugrave ucircumflex udieresis dagger degree cent sterling section bullet + paragraph germandbls registered copyright trademark acute dieresis notequal AE Oslash infinity + plusminus lessequal greaterequal yen mu partialdiff summation product pi integral ordfeminine + ordmasculine Omega ae oslash questiondown exclamdown logicalnot radical florin approxequal + Delta guillemotleft guillemotright ellipsis nonbreakingspace Agrave Atilde Otilde OE oe endash emdash + quotedblleft quotedblright quoteleft quoteright divide lozenge ydieresis Ydieresis fraction currency + guilsinglleft guilsinglright fi fl daggerdbl periodcentered quotesinglbase quotedblbase perthousand + Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex + apple Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve dotaccent + ring cedilla hungarumlaut ogonek caron Lslash lslash Scaron scaron Zcaron zcaron brokenbar Eth eth + Yacute yacute Thorn thorn minus multiply onesuperior twosuperior threesuperior onehalf onequarter + threequarters franc Gbreve gbreve Idotaccent Scedilla scedilla Cacute cacute Ccaron ccaron dcroat); + +$VERSION = 0.01; # MJPH 5-AUG-1998 Re-organise data structures + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } + $i = 0; + %base_set = map {$_ => $i++} @base_set; +} + + +=head2 $t->read + +Reads the Postscript table into memory from disk + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $dat1, $i, $off, $c, $maxoff, $form, $angle, $numGlyphs); + my ($fh) = $self->{' INFILE'}; + + $numGlyphs = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + $self->SUPER::read or return $self; + init unless ($fields{'FormatType'}); + $fh->read($dat, 32); + TTF_Read_Fields($self, $dat, \%fields); + + if (int($self->{'FormatType'} + .5) == 1) + { + for ($i = 0; $i < 258; $i++) + { + $self->{'VAL'}[$i] = $base_set[$i]; + $self->{'STRINGS'}{$base_set[$i]} = $i unless (defined $self->{'STRINGS'}{$base_set[$i]}); + } + } elsif (int($self->{'FormatType'} * 2 + .1) == 5) + { + $fh->read($dat, 2); + $numGlyphs = unpack("n", $dat); + $fh->read($dat, $numGlyphs); + for ($i = 0; $i < $numGlyphs; $i++) + { + $off = unpack("c", substr($dat, $i, 1)); + $self->{'VAL'}[$i] = $base_set[$i + $off]; + $self->{'STRINGS'}{$base_set[$i + $off]} = $i unless (defined $self->{'STRINGS'}{$base_set[$i + $off]}); + } + } elsif (int($self->{'FormatType'} + .5) == 2) + { + my (@strings); + + $fh->read($dat, ($numGlyphs + 1) << 1); + for ($i = 0; $i < $numGlyphs; $i++) + { + $off = unpack("n", substr($dat, ($i + 1) << 1, 2)); + $maxoff = $off if (!defined $maxoff || $off > $maxoff); + } + for ($i = 0; $i < $maxoff - 257; $i++) + { + $fh->read($dat1, 1); + $off = unpack("C", $dat1); + $fh->read($dat1, $off); + $strings[$i] = $dat1; + } + for ($i = 0; $i < $numGlyphs; $i++) + { + $off = unpack("n", substr($dat, ($i + 1) << 1, 2)); + if ($off > 257) + { + $self->{'VAL'}[$i] = $strings[$off - 258]; + $self->{'STRINGS'}{$strings[$off - 258]} = $i; + } + else + { + $self->{'VAL'}[$i] = $base_set[$off]; + $self->{'STRINGS'}{$base_set[$off]} = $i unless (defined $self->{'STRINGS'}{$base_set[$off]}); + } + } + } + $self; +} + + +=head2 $t->out($fh) + +Writes out a new Postscript name table from memory or copies from disk + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i, $num); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $num = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + init unless ($fields{'FormatType'}); + + for ($i = $#{$self->{'VAL'}}; !defined $self->{'VAL'}[$i] && $i > 0; $i--) + { pop(@{$self->{'VAL'}}); } + if ($#{$self->{'VAL'}} < 0) + { $self->{'FormatType'} = 3; } + else + { + $self->{'FormatType'} = 1; + for ($i = 0; $i < $num; $i++) + { + if (!defined $base_set{$self->{'VAL'}[$i]}) + { + $self->{'FormatType'} = 2; + last; + } + elsif ($base_set{$self->{'VAL'}[$i]} != $i) + { $self->{'FormatType'} = ($no25 ? 2 : 2.5); } + } + } + + $fh->print(TTF_Out_Fields($self, \%fields, 32)); + + return $self if (int($self->{'FormatType'} + .4) == 3); + + if (int($self->{'FormatType'} + .5) == 2) + { + my (@ind); + my ($count) = 0; + + $fh->print(pack("n", $num)); + for ($i = 0; $i < $num; $i++) + { + if (defined $base_set{$self->{'VAL'}[$i]}) + { $fh->print(pack("n", $base_set{$self->{'VAL'}[$i]})); } + else + { + $fh->print(pack("n", $count + 258)); + $ind[$count++] = $i; + } + } + for ($i = 0; $i < $count; $i++) + { + $fh->print(pack("C", length($self->{'VAL'}[$ind[$i]]))); + $fh->print($self->{'VAL'}[$ind[$i]]); + } + } elsif (int($self->{'FormatType'} * 2 + .5) == 5) + { + $fh->print(pack("n", $num)); + for ($i = 0; $i < $num; $i++) + { $fh->print(pack("c", defined $base_set{$self->{'VAL'}[$i]} ? + $base_set{$self->{'VAL'}[$i]} - $i : -$i)); } + } + + $self; +} + + +=head2 $t->XML_element($context, $depth, $key, $val) + +Outputs the names as one block of XML + +=cut + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $val) = @_; + my ($fh) = $context->{'fh'}; + my ($i); + + return $self->SUPER::XML_element(@_) unless ($key eq 'STRINGS' || $key eq 'VAL'); + return unless ($key eq 'VAL'); + + $fh->print("$depth\n"); + for ($i = 0; $i <= $#{$self->{'VAL'}}; $i++) + { $fh->print("$depth$context->{'indent'}\n"); } + $fh->print("$depth\n"); + $self; +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +No support for type 4 tables + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prep.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prep.pm new file mode 100644 index 0000000..453cf5a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prep.pm @@ -0,0 +1,89 @@ +package Font::TTF::Prep; + +=head1 NAME + +Font::TTF::Prep - Preparation hinting program. Called when ppem changes + +=head1 DESCRIPTION + +This is a minimal class adding nothing beyond a table, but is a repository +for prep type information for those processes brave enough to address hinting. + +=cut + +use strict; +use vars qw(@ISA $VERSION); +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); + +$VERSION = 0.0001; + + +=head2 $t->read + +Reads the data using C. + +=cut + +sub read +{ + $_[0]->read_dat; + $_[0]->{' read'} = 1; +} + + +=head2 $t->out_xml($context, $depth) + +Outputs Prep program as XML + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($fh) = $context->{'fh'}; + my ($dat); + + $self->read; + $dat = Font::TTF::Utils::XML_binhint($self->{' dat'}); + $dat =~ s/\n(?!$)/\n$depth$context->{'indent'}/omg; + $fh->print("$depth\n"); + $fh->print("$depth$context->{'indent'}$dat"); + $fh->print("$depth\n"); + $self; +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Parse all that hinting code + +=cut + +sub XML_end +{ + my ($self) = shift; + my ($context, $tag, %attrs) = @_; + + if ($tag eq 'code') + { + $self->{' dat'} = Font::TTF::Utils::XML_hintbin($context->{'text'}); + return $context; + } else + { return $self->SUPER::XML_end(@_); } +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prop.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prop.pm new file mode 100644 index 0000000..b74c654 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Prop.pm @@ -0,0 +1,154 @@ +package Font::TTF::Prop; + +=head1 NAME + +Font::TTF::Prop - Glyph Properties table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=item version + +=item default + +=item lookup + +Hash of property values keyed by glyph number + +=item lookupFormat + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +use Font::TTF::Utils; +use Font::TTF::AATutils; +use Font::TTF::Segarr; + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $fh); + my ($version, $lookupPresent, $default); + + $self->SUPER::read or return $self; + + $fh = $self->{' INFILE'}; + $fh->read($dat, 8); + ($version, $lookupPresent, $default) = TTF_Unpack("vSS", $dat); + + if ($lookupPresent) { + my ($format, $lookup) = AAT_read_lookup($fh, 2, $self->{' LENGTH'} - 8, $default); + $self->{'lookup'} = $lookup; + $self->{'format'} = $format; + } + + $self->{'version'} = $version; + $self->{'default'} = $default; + + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($default, $lookup); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $default = $self->{'default'}; + $lookup = $self->{'lookup'}; + $fh->print(TTF_Pack("vSS", $self->{'version'}, (defined $lookup ? 1 : 0), $default)); + + AAT_write_lookup($fh, $self->{'format'}, $lookup, 2, $default) if (defined $lookup); +} + +=head2 $t->print($fh) + +Prints a human-readable representation of the table + +=cut + +sub print +{ + my ($self, $fh) = @_; + my ($lookup); + + $self->read; + + $fh = 'STDOUT' unless defined $fh; + + $fh->printf("version %f\ndefault %04x # %s\n", $self->{'version'}, $self->{'default'}, meaning_($self->{'default'})); + $lookup = $self->{'lookup'}; + if (defined $lookup) { + $fh->printf("format %d\n", $self->{'format'}); + foreach (sort { $a <=> $b } keys %$lookup) { + $fh->printf("\t%d -> %04x # %s\n", $_, $lookup->{$_}, meaning_($lookup->{$_})); + } + } +} + +sub meaning_ +{ + my ($val) = @_; + my ($res); + + my @types = ( + "Strong left-to-right", + "Strong right-to-left", + "Arabic letter", + "European number", + "European number separator", + "European number terminator", + "Arabic number", + "Common number separator", + "Block separator", + "Segment separator", + "Whitespace", + "Other neutral"); + $res = $types[$val & 0x001f] or ("Undefined [" . ($val & 0x001f) . "]"); + + $res .= ", floater" if $val & 0x8000; + $res .= ", hang left" if $val & 0x4000; + $res .= ", hang right" if $val & 0x2000; + $res .= ", attaches on right" if $val & 0x0080; + $res .= ", pair" if $val & 0x1000; + my $pairOffset = ($val & 0x0f00) >> 8; + $pairOffset = $pairOffset - 16 if $pairOffset > 7; + $res .= $pairOffset > 0 ? " +" . $pairOffset : $pairOffset < 0 ? " " . $pairOffset : ""; + + $res; +} + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Segarr.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Segarr.pm new file mode 100644 index 0000000..980188d --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Segarr.pm @@ -0,0 +1,376 @@ +package Font::TTF::Segarr; + +=head1 NAME + +Font::TTF::Segarr - Segmented array + +=head1 DESCRIPTION + +Holds data either directly or indirectly as a series of arrays. This class +looks after the set of arrays and masks the individual sub-arrays, thus saving +a class, we hope. + +=head1 INSTANCE VARIABLES + +All instance variables do not start with a space. + +The segmented array is simply an array of segments + +Each segment is a more complex affair: + +=over 4 + +=item START + +In terms of the array, the address for the 0th element in this segment. + +=item LEN + +Number of elements in this segment + +=item VAL + +The array which contains the elements + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@types $VERSION); +$VERSION = 0.0001; + +@types = ('', 'C', 'n', '', 'N'); + +=head2 Font::TTF::Segarr->new($size) + +Creates a new segmented array with a given data size + +=cut + +sub new +{ + my ($class) = @_; + my ($self) = []; + + bless $self, (ref($class) || $class); +} + + +=head2 $s->fastadd_segment($start, $is_sparse, @dat) + +Creates a new segment and adds it to the array assuming no overlap between +the new segment and any others in the array. $is_sparse indicates whether the +passed in array contains Cs or not. If false no checking is done (which +is faster, but riskier). If equal to 2 then 0 is considered undef as well. + +Returns the number of segments inserted. + +=cut + +sub fastadd_segment +{ + my ($self) = shift; + my ($start) = shift; + my ($sparse) = shift; + my ($p, $i, $seg, @seg); + + + if ($sparse) + { + for ($i = 0; $i <= $#_; $i++) + { + if (!defined $seg && (($sparse != 2 && defined $_[$i]) || $_[$i] != 0)) + { $seg->{'START'} = $start + $i; $seg->{'VAL'} = []; } + + if (defined $seg && (($sparse == 2 && $_[$i] == 0) || !defined $_[$i])) + { + $seg->{'LEN'} = $start + $i - $seg->{'START'}; + push(@seg, $seg); + $seg = undef; + } elsif (defined $seg) + { push (@{$seg->{'VAL'}}, $_[$i]); } + } + if (defined $seg) + { + push(@seg, $seg); + $seg->{'LEN'} = $start + $i - $seg->{'START'}; + } + } else + { + $seg->{'START'} = $start; + $seg->{'LEN'} = $#_ + 1; + $seg->{'VAL'} = [@_]; + @seg = ($seg); + } + + for ($i = 0; $i <= $#$self; $i++) + { + if ($self->[$i]{'START'} > $start) + { + splice(@$self, $i, 0, @seg); + return wantarray ? @seg : scalar(@seg); + } + } + push(@$self, @seg); + return wantarray ? @seg : scalar(@seg); +} + + +=head2 $s->add_segment($start, $overwrite, @dat) + +Creates a new segment and adds it to the array allowing for possible overlaps +between the new segment and the existing ones. In the case of overlaps, elements +from the new segment are deleted unless $overwrite is set in which case the +elements already there are over-written. + +This method also checks the data coming in to see if it is sparse (i.e. contains +undef values). Gaps cause new segments to be created or not to over-write existing +values. + +=cut + +sub add_segment +{ + my ($self) = shift; + my ($start) = shift; + my ($over) = shift; + my ($seg, $i, $s, $offset, $j, $newi); + + return $self->fastadd_segment($start, $over, @_) if ($#$self < 0); + $offset = 0; + for ($i = 0; $i <= $#$self && $offset <= $#_; $i++) + { + $s = $self->[$i]; + if ($s->{'START'} <= $start + $offset) # only < for $offset == 0 + { + if ($s->{'START'} + $s->{'LEN'} > $start + $#_) + { + for ($j = $offset; $j <= $#_; $j++) + { + if ($over) + { $s->{'VAL'}[$start - $s->{'START'} + $j] = $_[$j] if defined $_[$j]; } + else + { $s->{'VAL'}[$start - $s->{'START'} + $j] ||= $_[$j] if defined $_[$j]; } + } + $offset = $#_ + 1; + last; + } elsif ($s->{'START'} + $s->{'LEN'} > $start + $offset) # is $offset needed here? + { + for ($j = $offset; $j < $s->{'START'} + $s->{'LEN'} - $start; $j++) + { + if ($over) + { $s->{'VAL'}[$start - $s->{'START'} + $j] = $_[$j] if defined $_[$j]; } + else + { $s->{'VAL'}[$start - $s->{'START'} + $j] ||= $_[$j] if defined $_[$j]; } + } + $offset = $s->{'START'} + $s->{'LEN'} - $start; + } + } else # new seg please + { + if ($s->{'START'} > $start + $#_ + 1) + { + $i += $self->fastadd_segment($start + $offset, 1, @_[$offset .. $#_]) - 1; + $offset = $#_ + 1; + } + else + { + $i += $self->fastadd_segment($start + $offset, 1, @_[$offset .. $s->{'START'} - $start]) - 1; + $offset = $s->{'START'} - $start + 1; + } + } + } + if ($offset <= $#_) + { + $seg->{'START'} = $start + $offset; + $seg->{'LEN'} = $#_ - $offset + 1; + $seg->{'VAL'} = [@_[$offset .. $#_]]; + push (@$self, $seg); + } + $self->tidy; +} + + +=head2 $s->tidy + +Merges any immediately adjacent segments + +=cut + +sub tidy +{ + my ($self) = @_; + my ($i, $sl, $s); + + for ($i = 1; $i <= $#$self; $i++) + { + $sl = $self->[$i - 1]; + $s = $self->[$i]; + if ($s->{'START'} == $sl->{'START'} + $sl->{'LEN'}) + { + $sl->{'LEN'} += $s->{'LEN'}; + push (@{$sl->{'VAL'}}, @{$s->{'VAL'}}); + splice(@$self, $i, 1); + $i--; + } + } + $self; +} + + +=head2 $s->at($addr, [$len]) + +Looks up the data held at the given address by locating the appropriate segment +etc. If $len > 1 then returns an array of values, spaces being filled with undef. + +=cut + +sub at +{ + my ($self, $addr, $len) = @_; + my ($i, $dat, $s, @res, $offset); + + $len = 1 unless defined $len; + $offset = 0; + for ($i = 0; $i <= $#$self; $i++) + { + $s = $self->[$i]; + next if ($s->{'START'} + $s->{'LEN'} < $addr + $offset); # only fires on $offset == 0 + if ($s->{'START'} > $addr + $offset) + { + push (@res, (undef) x ($s->{'START'} > $addr + $len ? + $len - $offset : $s->{'START'} - $addr - $offset)); + $offset = $s->{'START'} - $addr; + } + last if ($s->{'START'} >= $addr + $len); + + if ($s->{'START'} + $s->{'LEN'} >= $addr + $len) + { + push (@res, @{$s->{'VAL'}}[$addr + $offset - $s->{'START'} .. + $addr + $len - $s->{'START'} - 1]); + $offset = $len; + last; + } else + { + push (@res, @{$s->{'VAL'}}[$addr + $offset - $s->{'START'} .. $s->{'LEN'} - 1]); + $offset = $s->{'START'} + $s->{'LEN'} - $addr; + } + } + push (@res, (undef) x ($len - $offset)) if ($offset < $len); + return wantarray ? @res : $res[0]; +} + + +=head2 $s->remove($addr, [$len]) + +Removes the item or items from addr returning them as an array or the first +value in a scalar context. This is very like C, including padding with +undef, but it deletes stuff as it goes. + +=cut + +sub remove +{ + my ($self, $addr, $len) = @_; + my ($i, $dat, $s, @res, $offset); + + $len = 1 unless defined $len; + $offset = 0; + for ($i = 0; $i <= $#$self; $i++) + { + $s = $self->[$i]; + next if ($s->{'START'} + $s->{'LEN'} < $addr + $offset); + if ($s->{'START'} > $addr + $offset) + { + push (@res, (undef) x ($s->{'START'} > $addr + $len ? + $len - $offset : $s->{'START'} - $addr - $offset)); + $offset = $s->{'START'} - $addr; + } + last if ($s->{'START'} >= $addr + $len); + + unless ($s->{'START'} == $addr + $offset) + { + my ($seg) = {}; + + $seg->{'START'} = $s->{'START'}; + $seg->{'LEN'} = $addr + $offset - $s->{'START'}; + $seg->{'VAL'} = [splice(@{$s->{'VAL'}}, 0, $addr + $offset - $s->{'START'})]; + $s->{'LEN'} -= $addr + $offset - $s->{'START'}; + $s->{'START'} = $addr + $offset; + + splice(@$self, $i, 0, $seg); + $i++; + } + + if ($s->{'START'} + $s->{'LEN'} >= $addr + $len) + { + push (@res, splice(@{$s->{'VAL'}}, 0, $len - $offset)); + $s->{'LEN'} -= $len - $offset; + $s->{'START'} += $len - $offset; + $offset = $len; + last; + } else + { + push (@res, @{$s->{'VAL'}}); + $offset = $s->{'START'} + $s->{'LEN'} - $addr; + splice(@$self, $i, 0); + $i--; + } + } + push (@res, (undef) x ($len - $offset)) if ($offset < $len); + return wantarray ? @res : $res[0]; +} + + +=head2 $s->copy + +Deep copies this array + +=cut + +sub copy +{ + my ($self) = @_; + my ($res, $p); + + $res = []; + foreach $p (@$self) + { push (@$res, $self->copy_seg($p)); } + $res; +} + + +=head2 $s->copy_seg($seg) + +Creates a deep copy of a segment + +=cut + +sub copy_seg +{ + my ($self, $seg) = @_; + my ($p, $res); + + $res = {}; + $res->{'VAL'} = [@{$seg->{'VAL'}}]; + foreach $p (keys %$seg) + { $res->{$p} = $seg->{$p} unless defined $res->{$p}; } + $res; +} + + +1; + +=head1 BUGS + +No known bugs. + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Sill.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Sill.pm new file mode 100644 index 0000000..6a17af1 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Sill.pm @@ -0,0 +1,114 @@ +package Font::TTF::Sill; + +=head1 NAME + +Font::TTF::Sill - Graphite language mapping table + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over 4 + +=item version + +Table version number. + +=item langs + +Contains a hash where the key is the language id and the value is an array of +language records + +=back + +=head2 Language Records + +Each language record is itself an array of two values [fid, val]. fid is the +feature id and is held as a long. + +=cut + +use Font::TTF::Utils; +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +sub read +{ + my ($self) = @_; + my ($num, $i, $j); + + return $self if ($self->{' read'}); + $self->SUPER::read_dat or return $self; + + ($self->{'version'}, $num) = TTF_Unpack("vS", $self->{' dat'}); + + foreach $i (1 .. $num) # ignore bogus entry at end + { + my ($lid, $numf, $offset) = unpack("A4nn", substr($self->{' dat'}, $i * 8 + 4)); # 12 - 8 = 4 since i starts at 1. A4 strips nulls + my (@settings); + + foreach $j (1 .. $numf) + { + my ($fid, $val) = TTF_Unpack("Ls", substr($self->{' dat'}, $offset + $j * 8 - 8)); + push (@settings, [$fid, $val]); + } + $self->{'langs'}{$lid} = [@settings]; + } + delete $self->{' dat'}; + $self->{' read'} = 1; + $self; +} + +sub out +{ + my ($self, $fh) = @_; + my ($num, $range, $select, $shift) = TTF_bininfo(scalar keys %{$self->{'langs'}}, 1); + my ($offset) = $num * 8 + 20; #header = 12, dummy = 8 + my ($k, $s); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + $fh->print(TTF_Pack("vSSSS", $self->{'version'}, $num, $range, $select, $shift)); + foreach $k (sort (keys %{$self->{'langs'}}), '+1') + { + my ($numf) = scalar @{$self->{'langs'}{$k}} unless ($k eq '+1'); + $fh->print(pack("a4nn", $k, $numf, $offset)); + $offset += $numf * 8; + } + + foreach $k (sort keys %{$self->{'langs'}}) + { + foreach $s (@{$self->{'langs'}{$k}}) + { $fh->print(TTF_Pack("LsS", @{$s}, 0)); } + } + $self; +} + +sub XML_element +{ + my ($self) = shift; + my ($context, $depth, $key, $dat) = @_; + my ($fh) = $context->{'fh'}; + my ($k, $s); + + return $self->SUPER::XML_element(@_) unless ($key eq 'langs'); + foreach $k (sort keys %{$self->{'langs'}}) + { + $fh->printf("%s\n", $depth, $k); + foreach $s (@{$self->{'langs'}{$k}}) + { + my ($fid) = $s->[0]; + if ($fid > 0x00FFFFFF) + { $fid = unpack("A4", pack ("N", $fid)); } + else + { $fid = sprintf("%d", $fid); } + $fh->printf("%s%s\n", + $depth, $context->{'indent'}, $fid, $s->[1]); + } + $fh->printf("%s\n", $depth); + } + $self; +} +1; + + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Table.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Table.pm new file mode 100644 index 0000000..bbdb48d --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Table.pm @@ -0,0 +1,382 @@ +package Font::TTF::Table; + +=head1 NAME + +Font::TTF::Table - Superclass for tables and used for tables we don't have a class for + +=head1 DESCRIPTION + +Looks after the purely table aspects of a TTF table, such as whether the table +has been read before, locating the file pointer, etc. Also copies tables from +input to output. + +=head1 INSTANCE VARIABLES + +Instance variables start with a space + +=over 4 + +=item read + +Flag which indicates that the table has already been read from file. + +=item dat + +Allows the creation of unspecific tables. Data is simply output to any font +file being created. + +=item INFILE + +The read file handle + +=item OFFSET + +Location of the file in the input file + +=item LENGTH + +Length in the input directory + +=item CSUM + +Checksum read from the input file's directory + +=item PARENT + +The L that table is part of + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw($VERSION); +use Font::TTF::Utils; + +$VERSION = 0.0001; + +=head2 Font::TTF::Table->new(%parms) + +Creates a new table or subclass. Table instance variables are passed in +at this point as an associative array. + +=cut + +sub new +{ + my ($class, %parms) = @_; + my ($self) = {}; + my ($p); + + $class = ref($class) || $class; + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + bless $self, $class; +} + + +=head2 $t->read + +Reads the table from the input file. Acts as a superclass to all true tables. +This method marks the table as read and then just sets the input file pointer +but does not read any data. If the table has already been read, then returns +C else returns C<$self> + +=cut + +sub read +{ + my ($self) = @_; + + return $self->read_dat if (ref($self) eq "Font::TTF::Table"); + return undef if $self->{' read'}; + $self->{' INFILE'}->seek($self->{' OFFSET'}, 0); + $self->{' read'} = 1; + $self; +} + + +=head2 $t->read_dat + +Reads the table into the C instance variable for those tables which don't +know any better + +=cut + +sub read_dat +{ + my ($self) = @_; + +# can't just $self->read here otherwise those tables which start their read sub with +# $self->read_dat are going to permanently loop + return undef if ($self->{' read'}); +# $self->{' read'} = 1; # Let read do this, now out will call us for subclasses + $self->{' INFILE'}->seek($self->{' OFFSET'}, 0); + $self->{' INFILE'}->read($self->{' dat'}, $self->{' LENGTH'}); + $self; +} + +=head2 $t->out($fh) + +Writes out the table to the font file. If there is anything in the +C instance variable then this is output, otherwise the data is copied +from the input file to the output + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($dat, $i, $len, $count); + + if (defined $self->{' dat'}) + { + $fh->print($self->{' dat'}); + return $self; + } + + return undef unless defined $self->{' INFILE'}; + $self->{' INFILE'}->seek($self->{' OFFSET'}, 0); + $len = $self->{' LENGTH'}; + while ($len > 0) + { + $count = ($len > 4096) ? 4096 : $len; + $self->{' INFILE'}->read($dat, $count); + $fh->print($dat); + $len -= $count; + } + $self; +} + + +=head2 $t->out_xml($context) + +Outputs this table in XML format. The table is first read (if not already read) and then if +there is no subclass, then the data is dumped as hex data + +=cut + +sub out_xml +{ + my ($self, $context, $depth) = @_; + my ($k); + + if (ref($self) eq __PACKAGE__) + { + $self->read_dat; + Font::TTF::Utils::XML_hexdump($context, $depth, $self->{' dat'}); + } + else + { + $self->read; + foreach $k (sort grep {$_ !~ m/^\s/o} keys %{$self}) + { + $self->XML_element($context, $depth, $k, $self->{$k}); + } + } + $self; +} + + +=head2 $t->XML_element + +Output a particular element based on its contents. + +=cut + +sub XML_element +{ + my ($self, $context, $depth, $k, $dat) = @_; + my ($fh) = $context->{'fh'}; + my ($ndepth, $d); + + return unless defined $dat; + + if (!ref($dat)) + { + $fh->printf("%s<%s>%s\n", $depth, $k, $dat, $k); + return $self; + } + + $fh->printf("%s<%s>\n", $depth, $k); + $ndepth = $depth . $context->{'indent'}; + + if (ref($dat) eq 'SCALAR') + { $self->XML_element($context, $ndepth, 'scalar', $$dat); } + elsif (ref($dat) eq 'ARRAY') + { + foreach $d (@{$dat}) + { $self->XML_element($context, $ndepth, 'elem', $d); } + } + elsif (ref($dat) eq 'HASH') + { + foreach $d (sort grep {$_ !~ m/^\s/o} keys %{$dat}) + { $self->XML_element($context, $ndepth, $d, $dat->{$d}); } + } + else + { + $context->{'name'} = ref($dat); + $context->{'name'} =~ s/^.*://o; + $dat->out_xml($context, $ndepth); + } + + $fh->printf("%s\n", $depth, $k); + $self; +} + + +=head2 $t->XML_end($context, $tag, %attrs) + +Handles the default type of for those tables which aren't subclassed + +=cut + +sub XML_end +{ + my ($self, $context, $tag, %attrs) = @_; + my ($dat, $addr); + + return undef unless ($tag eq 'data'); + $dat = $context->{'text'}; + $dat =~ s/([0-9a-f]{2})\s*/hex($1)/oig; + if (defined $attrs{'addr'}) + { $addr = hex($attrs{'addr'}); } + else + { $addr = length($self->{' dat'}); } + substr($self->{' dat'}, $addr, length($dat)) = $dat; + return $context; +} + + +=head2 $t->dirty($val) + +This sets the dirty flag to the given value or 1 if no given value. It returns the +value of the flag + +=cut + +sub dirty +{ + my ($self, $val) = @_; + my ($res) = $self->{' isDirty'}; + + $self->{' isDirty'} = defined $val ? $val : 1; + $res; +} + +=head2 $t->update + +Each table knows how to update itself. This consists of doing whatever work +is required to ensure that the memory version of the table is consistent +and that other parameters in other tables have been updated accordingly. +I.e. by the end of sending C to all the tables, the memory version +of the font should be entirely consistent. + +Some tables which do no work indicate to themselves the need to update +themselves by setting isDirty above 1. This method resets that accordingly. + +=cut + +sub update +{ + my ($self) = @_; + + if ($self->{' isDirty'}) + { + $self->read; + $self->{' isDirty'} = 0; + return $self; + } + else + { return undef; } +} + + +=head2 $t->empty + +Clears a table of all data to the level of not having been read + +=cut + +sub empty +{ + my ($self) = @_; + my (%keep); + + foreach (qw(INFILE LENGTH OFFSET CSUM PARENT)) + { $keep{" $_"} = 1; } + + map {delete $self->{$_} unless $keep{$_}} keys %$self; + $self; +} + + +=head2 $t->release + +Releases ALL of the memory used by this table, and all of its component/child +objects. This method is called automatically by +'Font::TTF::Font-Erelease' (so you don't have to call it yourself). + +B, that it is important that this method get called at some point prior +to the actual destruction of the object. Internally, we track things in a +structure that can result in circular references, and without calling +'C' these will not properly get cleaned up by Perl. Once this +method has been called, though, don't expect to be able to do anything with the +C object; it'll have B internal state whatsoever. + +B As part of the brute-force cleanup done here, this method +will throw a warning message whenever unexpected key values are found within +the C object. This is done to help ensure that any +unexpected and unfreed values are brought to your attention so that you can bug +us to keep the module updated properly; otherwise the potential for memory +leaks due to dangling circular references will exist. + +=cut + +sub release +{ + my ($self) = @_; + +# delete stuff that we know we can, here + + my @tofree = map { delete $self->{$_} } keys %{$self}; + + while (my $item = shift @tofree) + { + my $ref = ref($item); + if (UNIVERSAL::can($item, 'release')) + { $item->release(); } + elsif ($ref eq 'ARRAY') + { push( @tofree, @{$item} ); } + elsif (UNIVERSAL::isa($ref, 'HASH')) + { release($item); } + } + +# check that everything has gone - it better had! + foreach my $key (keys %{$self}) + { warn ref($self) . " still has '$key' key left after release.\n"; } +} + + +sub __dumpvar__ +{ + my ($self, $key) = @_; + + return ($key eq ' PARENT' ? '...parent...' : $self->{$key}); +} + +1; + +=head1 BUGS + +No known bugs + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttc.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttc.pm new file mode 100644 index 0000000..b3eeef0 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttc.pm @@ -0,0 +1,164 @@ +package Font::TTF::Ttc; + +=head1 NAME + +Font::TTF::Ttc - Truetype Collection class + +=head1 DESCRIPTION + +A TrueType collection is a collection of TrueType fonts in one file in which +tables may be shared between different directories. In order to support this, +the TTC introduces the concept of a table being shared by different TrueType +fonts. This begs the question of what should happen to the ' PARENT' property +of a particular table. It is made to point to the first directory object which +refers to it. It is therefore up to the application to sort out any confusion. +Confusion only occurs if shared tables require access to non-shared tables. +This should not happen since the shared tables are dealing with glyph +information only and the private tables are dealing with encoding and glyph +identification. Thus the general direction is from identification to glyph and +not the other way around (at least not without knowledge of the particular +context). + +=head1 INSTANCE VARIABLES + +The following instance variables are preceded by a space + +=over 4 + +=item fname (P) + +Filename for this TrueType Collection + +=item INFILE (P) + +The filehandle of this collection + +=back + +The following instance variable does not start with a space + +=over 4 + +=item directs + +An array of directories (Font::TTF::Font objects) for each sub-font in the directory + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw($VERSION); + +use IO::File; + +$VERSION = 0.0001; + +=head2 Font::TTF::Ttc->open($fname) + +Opens and reads the given filename as a TrueType Collection. Reading a collection +involves reading each of the directories which go to make up the collection. + +=cut + +sub open +{ + my ($class, $fname) = @_; + my ($self) = {}; + my ($fh); + + unless (ref($fname)) + { + $fh = IO::File->new($fname) or return undef; + binmode $fh; + } else + { $fh = $fname; } + + bless $self, $class; + $self->{' INFILE'} = $fh; + $self->{' fname'} = $fname; + $fh->seek(0, 0); + $self->read; +} + + +=head2 $c->read + +Reads a Collection by reading all the directories in the collection + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($dat, $ttc, $ver, $num, $i, $loc); + + $fh->read($dat, 12); + ($ttc, $ver, $num) = unpack("A4N2", $dat); + + return undef unless $ttc eq "ttcf"; + $fh->read($dat, $num << 2); + for ($i = 0; $i < $num; $i++) + { + $loc = unpack("N", substr($dat, $i << 2, 4)); + $self->{'directs'}[$i] = Font::TTF::Font->new('INFILE' => $fh, + 'PARENT' => $self, + 'OFFSET' => $loc) || return undef; + } + for ($i = 0; $i < $num; $i++) + { $self->{'directs'}[$i]->read; } + $self; +} + + +=head2 $c->find($direct, $name, $check, $off, $len) + +Hunts around to see if a table with the given characteristics of name, checksum, +offset and length has been associated with a directory earlier in the list. +Actually on checks the offset since no two tables can share the same offset in +a TrueType font, collection or otherwise. + +=cut + +sub find +{ + my ($self, $direct, $name, $check, $off, $len) = @_; + my ($d); + + foreach $d (@{$self->{'directs'}}) + { + return undef if $d eq $direct; + next unless defined $d->{$name}; + return $d->{$name} if ($d->{$name}{' OFFSET'} == $off); + } + undef; # wierd that the font passed is not in the list! +} + + +=head2 $c->DESTROY + +Closees any opened files by us + +=cut + +sub DESTROY +{ + my ($self) = @_; + close ($self->{' INFILE'}); + undef; +} + +=head1 BUGS + +No known bugs, but then not ever executed! + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttopen.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttopen.pm new file mode 100644 index 0000000..610a66e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Ttopen.pm @@ -0,0 +1,1223 @@ +package Font::TTF::Ttopen; + +=head1 NAME + +Font::TTF::Ttopen - Opentype superclass for standard Opentype lookup based tables +(GSUB and GPOS) + +=head1 DESCRIPTION + +Handles all the script, lang, feature, lookup stuff for a +L/L table leaving the class specifics to the +subclass + +=head1 INSTANCE VARIABLES + +The instance variables of an opentype table form a complex sub-module hierarchy. + +=over 4 + +=item Version + +This contains the version of the table as a floating point number + +=item SCRIPTS + +The scripts list is a hash of script tags. Each script tag (of the form +$t->{'SCRIPTS'}{$tag}) has information below it. + +=over 8 + +=item OFFSET + +This variable is preceeded by a space and gives the offset from the start of the +table (not the table section) to the script table for this script + +=item REFTAG + +This variable is preceded by a space and gives a corresponding script tag to this +one such that the offsets in the file are the same. When writing, it is up to the +caller to ensure that the REFTAGs are set correctly, since these will be used to +assume that the scripts are identical. Note that REFTAG must refer to a script which +has no REFTAG of its own. + +=item DEFAULT + +This corresponds to the default language for this script, if there is one, and +contains the same information as an itemised language + +=item LANG_TAGS + +This contains an array of language tag strings (each 4 bytes) corresponding to +the languages listed by this script + +=item $lang + +Each language is a hash containing its information: + +=over 12 + +=item OFFSET + +This variable is preceeded by a a space and gives the offset from the start of +the whole table to the language table for this language + +=item REFTAG + +This variable is preceded by a space and has the same function as for the script +REFTAG, only for the languages within a script. + +=item RE-ORDER + +This indicates re-ordering information, and has not been set. The value should +always be 0. + +=item DEFAULT + +This holds the index of the default feature, if there is one, or -1 otherwise. + +=item FEATURES + +This is an array of feature tags for all the features enabled for this language + +=back + +=back + +=item FEATURES + +The features section of instance variables corresponds to the feature table in +the opentype table. + +=over 8 + +=item FEAT_TAGS + +This array gives the ordered list of feature tags for this table. It is used during +reading and writing for converting between feature index and feature tag. + +=back + +The rest of the FEATURES variable is itself a hash based on the feature tag for +each feature. Each feature has the following structure: + +=over 8 + +=item OFFSET + +This attribute is preceeded by a space and gives the offset relative to the start of the whole +table of this particular feature. + +=item PARMS + +This is an unused offset to the parameters for each feature + +=item LOOKUPS + +This is an array containing indices to lookups in the LOOKUP instance variable of the table + +=item INDEX + +This gives the feature index for this feature and is used during reading and writing for +converting between feature tag and feature index. + +=back + +=item LOOKUP + +This variable is an array of lookups in order and is indexed via the features of a language of a +script. Each lookup contains subtables and other information: + +=over 8 + +=item OFFSET + +This name is preceeded by a space and contains the offset from the start of the table to this +particular lookup + +=item TYPE + +This is a subclass specific type for a lookup. It stipulates the type of lookup and hence subtables +within the lookup + +=item FLAG + +Holds the lookup flag bits + +=item SUB + +This holds an array of subtables which are subclass specific. Each subtable must have +an OFFSET. The other variables described here are an abstraction used in both the +GSUB and GPOS tables which are the target subclasses of this class. + +=over 12 + +=item OFFSET + +This is preceeded by a space and gives the offset relative to the start of the table for this +subtable + +=item FORMAT + +Gives the sub-table sub format for this GSUB subtable. It is assumed that this +value is correct when it comes time to write the subtable. + +=item COVERAGE + +Most lookups consist of a coverage table corresponding to the first +glyph to match. The offset of this coverage table is stored here and the coverage +table looked up against the GSUB table proper. There are two lookups +without this initial coverage table which is used to index into the RULES array. +These lookups have one element in the RULES array which is used for the whole +match. + +=item RULES + +The rules are a complex array. Each element of the array corresponds to an +element in the coverage table (governed by the coverage index). If there is +no coverage table, then there is considered to be only one element in the rules +array. Each element of the array is itself an array corresponding to the +possibly multiple string matches which may follow the initial glyph. Each +element of this array is a hash with fixed keys corresponding to information +needed to match a glyph string or act upon it. Thus the RULES element is an +array of arrays of hashes which contain the following keys: + +=over 16 + +=item MATCH + +This contains a sequence of elements held as an array. The elements may be +glyph ids (gid), class ids (cids), or offsets to coverage tables. Each element +corresponds to one glyph in the glyph string. See MATCH_TYPE for details of +how the different element types are marked. + +=item PRE + +This array holds the sequence of elements preceeding the first match element +and has the same form as the MATCH array. + +=item POST + +This array holds the sequence of elements to be tested for following the match +string and is of the same form as the MATCH array. + +=item ACTION + +This array holds information regarding what should be done if a match is found. +The array may either hold glyph ids (which are used to replace or insert or +whatever glyphs in the glyph string) or 2 element arrays consisting of: + +=over 20 + +=item OFFSET + +Offset from the start of the matched string that the lookup should start at +when processing the substring. + +=item LOOKUP_INDEX + +The index to a lookup to be acted upon on the match string. + +=back + +=back + +=back + +=back + +=item CLASS + +For those lookups which use class categories rather than glyph ids for matching +this is the offset to the class definition used to categories glyphs in the +match string. + +=item PRE_CLASS + +This is the offset to the class definition for the before match glyphs + +=item POST_CLASS + +This is the offset to the class definition for the after match glyphs. + +=item ACTION_TYPE + +This string holds the type of information held in the ACTION variable of a RULE. +It is subclass specific. + +=item MATCH_TYPE + +This holds the type of information in the MATCH array of a RULE. This is subclass +specific. + +=item ADJUST + +This corresponds to a single action for all items in a coverage table. The meaning +is subclass specific. + +=item CACHE + +This key starts with a space + +A hash of other tables (such as coverage tables, classes, anchors, device tables) +based on the offset given in the subtable to that other information. +Note that the documentation is particularly +unhelpful here in that such tables are given as offsets relative to the +beginning of the subtable not the whole GSUB table. This includes those items which +are stored relative to another base within the subtable. + +=back + + +=head1 METHODS + +=cut + +use Font::TTF::Table; +use Font::TTF::Utils; +use Font::TTF::Coverage; +use strict; +use vars qw(@ISA); + +@ISA = qw(Font::TTF::Table); + +=head2 $t->read + +Reads the table passing control to the subclass to handle the subtable specifics + +=cut + +sub read +{ + my ($self) = @_; + my ($dat, $i, $l, $oScript, $oFeat, $oLook, $tag, $nScript, $off, $dLang, $nLang, $lTag); + my ($nFeat, $nLook, $nSub, $j, $temp); + my ($fh) = $self->{' INFILE'}; + my ($moff) = $self->{' OFFSET'}; + + $self->SUPER::read or return $self; + $fh->read($dat, 10); + ($self->{'Version'}, $oScript, $oFeat, $oLook) = TTF_Unpack("vSSS", $dat); + +# read features first so that in the script/lang hierarchy we can use feature tags + + $fh->seek($moff + $oFeat, 0); + $fh->read($dat, 2); + $nFeat = unpack("n", $dat); + $self->{'FEATURES'} = {}; + $l = $self->{'FEATURES'}; + $fh->read($dat, 6 * $nFeat); + for ($i = 0; $i < $nFeat; $i++) + { + ($tag, $off) = unpack("a4n", substr($dat, $i * 6, 6)); + while (defined $l->{$tag}) + { + if ($tag =~ m/(.*?)\s_(\d+)$/o) + { $tag = $1 . " _" . ($2 + 1); } + else + { $tag .= " _0"; } + } + $l->{$tag}{' OFFSET'} = $off + $oFeat; + $l->{$tag}{'INDEX'} = $i; + push (@{$l->{'FEAT_TAGS'}}, $tag); + } + + foreach $tag (grep {m/^.{4}(?:\s_\d+)?$/o} keys %$l) + { + $fh->seek($moff + $l->{$tag}{' OFFSET'}, 0); + $fh->read($dat, 4); + ($l->{$tag}{'PARMS'}, $nLook) = unpack("n2", $dat); + $fh->read($dat, $nLook * 2); + $l->{$tag}{'LOOKUPS'} = [unpack("n*", $dat)]; + } + +# Now the script/lang hierarchy + + $fh->seek($moff + $oScript, 0); + $fh->read($dat, 2); + $nScript = unpack("n", $dat); + $self->{'SCRIPTS'} = {}; + $l = $self->{'SCRIPTS'}; + $fh->read($dat, 6 * $nScript); + for ($i = 0; $i < $nScript; $i++) + { + ($tag, $off) = unpack("a4n", substr($dat, $i * 6, 6)); + $off += $oScript; + foreach (keys %$l) + { $l->{$tag}{' REFTAG'} = $_ if ($l->{$_}{' OFFSET'} == $off + && !defined $l->{$_}{' REFTAG'}); } + $l->{$tag}{' OFFSET'} = $off; + } + + foreach $tag (keys %$l) + { + next if ($l->{$tag}{' REFTAG'}); + $fh->seek($moff + $l->{$tag}{' OFFSET'}, 0); + $fh->read($dat, 4); + ($dLang, $nLang) = unpack("n2", $dat); + $l->{$tag}{'DEFAULT'}{' OFFSET'} = + $dLang + $l->{$tag}{' OFFSET'} if $dLang; + $fh->read($dat, 6 * $nLang); + for ($i = 0; $i < $nLang; $i++) + { + ($lTag, $off) = unpack("a4n", substr($dat, $i * 6, 6)); + $off += $l->{$tag}{' OFFSET'}; + $l->{$tag}{$lTag}{' OFFSET'} = $off; + foreach (@{$l->{$tag}{'LANG_TAGS'}}, 'DEFAULT') + { $l->{$tag}{$lTag}{' REFTAG'} = $_ if ($l->{$tag}{$_}{' OFFSET'} == $off + && !$l->{$tag}{$_}{' REFTAG'}); } + push (@{$l->{$tag}{'LANG_TAGS'}}, $lTag); + } + foreach $lTag (@{$l->{$tag}{'LANG_TAGS'}}, 'DEFAULT') + { + next unless defined $l->{$tag}{$lTag}; + next if ($l->{$tag}{$lTag}{' REFTAG'}); + $fh->seek($moff + $l->{$tag}{$lTag}{' OFFSET'}, 0); + $fh->read($dat, 6); + ($l->{$tag}{$lTag}{'RE-ORDER'}, $l->{$tag}{$lTag}{'DEFAULT'}, $nFeat) + = unpack("n3", $dat); + $fh->read($dat, $nFeat * 2); + $l->{$tag}{$lTag}{'FEATURES'} = [map {$self->{'FEATURES'}{'FEAT_TAGS'}[$_]} unpack("n*", $dat)]; + } + foreach $lTag (@{$l->{$tag}{'LANG_TAGS'}}, 'DEFAULT') + { + next unless $l->{$tag}{$lTag}{' REFTAG'}; + $temp = $l->{$tag}{$lTag}{' REFTAG'}; + $l->{$tag}{$lTag} = ©($l->{$tag}{$temp}); + $l->{$tag}{$lTag}{' REFTAG'} = $temp; + } + } + foreach $tag (keys %$l) + { + next unless $l->{$tag}{' REFTAG'}; + $temp = $l->{$tag}{' REFTAG'}; + $l->{$tag} = ©($l->{$temp}); + $l->{$tag}{' REFTAG'} = $temp; + } + +# And finally the lookups + + $fh->seek($moff + $oLook, 0); + $fh->read($dat, 2); + $nLook = unpack("n", $dat); + $fh->read($dat, $nLook * 2); + $i = 0; + map { $self->{'LOOKUP'}[$i++]{' OFFSET'} = $_; } unpack("n*", $dat); + + for ($i = 0; $i < $nLook; $i++) + { + $l = $self->{'LOOKUP'}[$i]; + $fh->seek($l->{' OFFSET'} + $moff + $oLook, 0); + $fh->read($dat, 6); + ($l->{'TYPE'}, $l->{'FLAG'}, $nSub) = unpack("n3", $dat); + $fh->read($dat, $nSub * 2); + $j = 0; + my @offsets = unpack("n*", $dat); + my $isExtension = ($l->{'TYPE'} == $self->extension()); + for ($j = 0; $j < $nSub; $j++) + { + $l->{'SUB'}[$j]{' OFFSET'} = $offsets[$j]; + $fh->seek($moff + $oLook + $l->{' OFFSET'} + $l->{'SUB'}[$j]{' OFFSET'}, 0); + if ($isExtension) + { + $fh->read($dat, 8); + my $longOff; + (undef, $l->{'TYPE'}, $longOff) = unpack("nnN", $dat); + $l->{'SUB'}[$j]{' OFFSET'} += $longOff; + $fh->seek($moff + $oLook + $l->{' OFFSET'} + $l->{'SUB'}[$j]{' OFFSET'}, 0); + } + $self->read_sub($fh, $l, $j); + } + } + return $self; +} + +=head2 $t->read_sub($fh, $lookup, $index) + +This stub is to allow subclasses to read subtables of lookups in a table specific manner. A +reference to the lookup is passed in along with the subtable index. The file is located at the +start of the subtable to be read + +=cut + +sub read_sub +{ } + + +=head2 $t->extension() + +Returns the lookup number for the extension table that allows access to 32-bit offsets. + +=cut + +sub extension +{ } + + +=head2 $t->out($fh) + +Writes this Opentype table to the output calling $t->out_sub for each sub table +at the appropriate point in the output. The assumption is that on entry the +number of scripts, languages, features, lookups, etc. are all resolved and +the relationships fixed. This includes a script's LANG_TAGS list and that all +scripts and languages in their respective dictionaries either have a REFTAG or contain +real data. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i, $j, $base, $off, $tag, $t, $l, $lTag, $oScript, @script, @tags); + my ($end, $nTags, @offs, $oFeat, $oLook, $nSub, $nSubs, $big, $out); + + return $self->SUPER::out($fh) unless $self->{' read'}; + +# First sort the features + $i = 0; + $self->{'FEATURES'}{'FEAT_TAGS'} = [sort grep {m/^.{4}(?:\s_\d+)?$/o} %{$self->{'FEATURES'}}] + if (!defined $self->{'FEATURES'}{'FEAT_TAGS'}); + foreach $t (@{$self->{'FEATURES'}{'FEAT_TAGS'}}) + { $self->{'FEATURES'}{$t}{'INDEX'} = $i++; } + + $base = $fh->tell(); + $fh->print(TTF_Pack("v", $self->{'Version'})); + $fh->print(pack("n3", 10, 0, 0)); + $oScript = $fh->tell() - $base; + @script = sort grep {length($_) == 4} keys %{$self->{'SCRIPTS'}}; + $fh->print(pack("n", $#script + 1)); + foreach $t (@script) + { $fh->print(pack("a4n", $t, 0)); } + + $end = $fh->tell(); + foreach $t (@script) + { + $fh->seek($end, 0); + $tag = $self->{'SCRIPTS'}{$t}; + next if ($tag->{' REFTAG'}); + $tag->{' OFFSET'} = tell($fh) - $base - $oScript; + $fh->print(pack("n2", 0, $#{$tag->{'LANG_TAGS'}} + 1)); + foreach $lTag (sort @{$tag->{'LANG_TAGS'}}) + { $fh->print(pack("a4n", $lTag, 0)); } + foreach $lTag (@{$tag->{'LANG_TAGS'}}, 'DEFAULT') + { + my ($def); + $l = $tag->{$lTag}; + next if (!defined $l || (defined $l->{' REFTAG'} && $l->{' REFTAG'} ne '')); + $l->{' OFFSET'} = $fh->tell() - $base - $oScript - $tag->{' OFFSET'}; + if (defined $l->{'DEFAULT'}) +# { $def = $self->{'FEATURES'}{$l->{'FEATURES'}[$l->{'DEFAULT'}]}{'INDEX'}; } + { $def = $l->{'DEFAULT'}; } + else + { $def = -1; } + $fh->print(pack("n*", $l->{'RE_ORDER'} || 0, $def, $#{$l->{'FEATURES'}} + 1, + map {$self->{'FEATURES'}{$_}{'INDEX'} || 0} @{$l->{'FEATURES'}})); + } + $end = $fh->tell(); + if ($tag->{'DEFAULT'}{' REFTAG'} || defined $tag->{'DEFAULT'}{'FEATURES'}) + { + $fh->seek($base + $oScript + $tag->{' OFFSET'}, 0); + if (defined $tag->{'DEFAULT'}{' REFTAG'}) + { + my ($ttag); + for ($ttag = $tag->{'DEFAULT'}{' REFTAG'}; defined $tag->{$ttag}{' REFTAG'}; $ttag = $tag->{$ttag}{' REFTAG'}) + { } + $off = $tag->{$ttag}{' OFFSET'}; + } + else + { $off = $tag->{'DEFAULT'}{' OFFSET'}; } + $fh->print(pack("n", $off)); + } + $fh->seek($base + $oScript + $tag->{' OFFSET'} + 4, 0); + foreach (sort @{$tag->{'LANG_TAGS'}}) + { + if (defined $tag->{$_}{' REFTAG'}) + { + my ($ttag); + for ($ttag = $tag->{$_}{' REFTAG'}; defined $tag->{$ttag}{' REFTAG'}; $ttag = $tag->{$ttag}{' REFTAG'}) + { } + $off = $tag->{$ttag}{' OFFSET'}; + } + else + { $off = $tag->{$_}{' OFFSET'}; } + $fh->print(pack("a4n", $_, $off)); + } + } + $fh->seek($base + $oScript + 2, 0); + foreach $t (@script) + { + $tag = $self->{'SCRIPTS'}{$t}; + $off = $tag->{' REFTAG'} ? $tag->{$tag->{' REFTAG'}}{' OFFSET'} : $tag->{' OFFSET'}; + $fh->print(pack("a4n", $t, $off)); + } + + $fh->seek($end, 0); + $oFeat = $end - $base; + $nTags = $#{$self->{'FEATURES'}{'FEAT_TAGS'}} + 1; + $fh->print(pack("n", $nTags)); + $fh->print(pack("a4n", " ", 0) x $nTags); + + foreach $t (@{$self->{'FEATURES'}{'FEAT_TAGS'}}) + { + $tag = $self->{'FEATURES'}{$t}; + $tag->{' OFFSET'} = tell($fh) - $base - $oFeat; + $fh->print(pack("n*", 0, $#{$tag->{'LOOKUPS'}} + 1, @{$tag->{'LOOKUPS'}})); + } + $end = $fh->tell(); + $fh->seek($oFeat + $base + 2, 0); + foreach $t (@{$self->{'FEATURES'}{'FEAT_TAGS'}}) + { $fh->print(pack("a4n", $t, $self->{'FEATURES'}{$t}{' OFFSET'})); } + + undef $big; + $fh->seek($end, 0); + $oLook = $end - $base; + + # Start Lookup List Table + $nTags = $#{$self->{'LOOKUP'}} + 1; + $fh->print(pack("n", $nTags)); + $fh->print(pack("n", 0) x $nTags); + $end = $fh->tell(); # end of LookupListTable = start of Lookups + foreach $tag (@{$self->{'LOOKUP'}}) + { $nSubs += $self->num_sub($tag); } + for ($i = 0; $i < $nTags; $i++) + { + $fh->seek($end, 0); + $tag = $self->{'LOOKUP'}[$i]; + $off = $end - $base - $oLook; # BH 2004-03-04 + # Is there room, from the start of this i'th lookup, for this and the remaining + # lookups to be wrapped in extension lookups? + if (!defined $big && $off + ($nTags - $i) * 6 + $nSubs * 10 > 65535) # BH 2004-03-04 + { + # Not enough room -- need to start an extension! + my ($k, $ext); + $ext = $self->extension(); + # Must turn previous lookup into the first extension + $i--; + $tag = $self->{'LOOKUP'}[$i]; + $end = $tag->{' OFFSET'} + $base + $oLook; + $fh->seek($end, 0); + $big = $i; + # For this and the remaining lookups, build extensions lookups + for ($j = $i; $j < $nTags; $j++) + { + $tag = $self->{'LOOKUP'}[$j]; + $nSub = $self->num_sub($tag); + $fh->print(pack("nnn", $ext, $tag->{'FLAG'}, $nSub)); + $fh->print(pack("n*", map {$_ * 8 + 6 + $nSub * 2} (0 .. $nSub-1))); # BH 2004-03-04 + $tag->{' EXT_OFFSET'} = $fh->tell(); # = first extension lookup subtable + $tag->{' OFFSET'} = $tag->{' EXT_OFFSET'} - $nSub * 2 - 6 - $base - $oLook; # offset to this extension lookup + for ($k = 0; $k < $nSub; $k++) + { $fh->print(pack('nnN', 1, $tag->{'TYPE'}, 0)); } + } + + $tag = $self->{'LOOKUP'}[$i]; + # Leave file positioned after all the extension lookups -- where the referenced lookups will start. + } + $tag->{' OFFSET'} = $off unless defined $big; # BH 2004-03-04 + $nSub = $self->num_sub($tag); + if (!defined $big) + { + $fh->print(pack("nnn", $tag->{'TYPE'}, $tag->{'FLAG'}, $nSub)); + $fh->print(pack("n", 0) x $nSub); + } + else + { $end = $tag->{' EXT_OFFSET'}; } + my (@offs, $out, @refs); + for ($j = 0; $j < $nSub; $j++) + { + my ($ctables) = {}; + my ($base) = length($out); + push(@offs, tell($fh) - $end + $base); + $out .= $self->out_sub($fh, $tag, $j, $ctables, $base); + push (@refs, [$ctables, $base]); + } + out_final($fh, $out, \@refs); + $end = $fh->tell(); + if (!defined $big) + { + $fh->seek($tag->{' OFFSET'} + $base + $oLook + 6, 0); + $fh->print(pack("n*", @offs)); + } + else + { + $fh->seek($tag->{' EXT_OFFSET'}, 0); + for ($j = 0; $j < $nSub; $j++) + { $fh->print(pack('nnN', 1, $tag->{'TYPE'}, $offs[$j] - $j * 8)); } + } + } + $fh->seek($oLook + $base + 2, 0); + $fh->print(pack("n*", map {$self->{'LOOKUP'}[$_]{' OFFSET'}} (0 .. $nTags - 1))); + $fh->seek($base + 6, 0); + $fh->print(pack('n2', $oFeat, $oLook)); + $fh->seek($end, 0); + $self; +} + + +=head2 $t->num_sub($lookup) + +Asks the subclass to count the number of subtables for a particular lookup and to +return that value. Used in out(). + +=cut + +sub num_sub +{ + my ($self, $lookup) = @_; + + return $#{$lookup->{'SUB'}} + 1; +} + + +=head2 $t->out_sub($fh, $lookup, $index) + +This stub is to allow subclasses to output subtables of lookups in a table specific manner. A +reference to the lookup is passed in along with the subtable index. The file is located at the +start of the subtable to be output + +=cut + +sub out_sub +{ } + +=head2 $t->dirty + +Setting GPOS or GSUB dirty means that OS/2 may need updating, so set it dirty. + +=cut + +sub dirty +{ + my ($self, $val) = @_; + my $res = $self->SUPER::dirty ($val); + $self->{' PARENT'}{'OS/2'}->read->dirty($val) if exists $self->{' PARENT'}{'OS/2'}; + $res; +} + +=head2 $t->maxContext + +Returns the length of the longest opentype rule in this table. + +=cut + +sub maxContext +{ + my ($self) = @_; + + # Make sure table is read + $self->read; + + # Calculate my contribution to OS/2 usMaxContext + + my ($maxcontext, $l, $s, $r, $m); + + for $l (@{$self->{'LOOKUP'}}) # Examine each lookup + { + for $s (@{$l->{'SUB'}}) # Multiple possible subtables for this lookup + { + for $r (@{$s->{'RULES'}}) # One ruleset for each covered glyph + { + for $m (@{$r}) # Multiple possible matches for this covered glyph + { + my $lgt; + $lgt++ if exists $s->{'COVERAGE'}; # Count 1 for the coverage table if it exists + for (qw(MATCH PRE POST)) + { + $lgt += @{$m->{$_}} if exists $m->{$_}; + } + $maxcontext = $lgt if $lgt > $maxcontext; + } + } + + } + } + + $maxcontext; +} + + +=head2 $t->update + +Unless $t->{' PARENT'}{' noharmony'} is true, update will make sure that GPOS and GSUB include +the same scripts and languages. Any added scripts and languages will have empty feature sets. + +=cut + +# Assumes we are called on both GSUB and GPOS. So simply ADDS scripts and languages to $self that it finds +# in the other table. + +sub update +{ + my ($self) = @_; + + return undef unless ($self->SUPER::update); + + # Enforce script/lang congruence unless asked not to: + return $self if $self->{' PARENT'}{' noharmony'}; + + # Find my sibling (GSUB or GPOS, depending on which I am) + my $sibling = ref($self) eq 'Font::TTF::GSUB' ? 'GPOS' : ref($self) eq 'Font::TTF::GPOS' ? 'GSUB' : undef; + return $self unless $sibling && exists $self->{' PARENT'}{$sibling}; + $sibling = $self->{' PARENT'}{$sibling}; + next unless defined $sibling; + + # Look through scripts defined in sibling: + for my $sTag (grep {length($_) == 4} keys %{$sibling->{'SCRIPTS'}}) + { + my $sibScript = $sibling->{'SCRIPTS'}{$sTag}; + $sibScript = $sibling->{$sibScript->{' REFTAG'}} if exists $sibScript->{' REFTAG'} && $sibScript->{' REFTAG'} ne ''; + + $self->{'SCRIPTS'}{$sTag} = {} unless defined $self->{'SCRIPTS'}{$sTag}; # Create script if not present in $self + + my $myScript = $self->{'SCRIPTS'}{$sTag}; + $myScript = $self->{$myScript->{' REFTAG'}} if exists $myScript->{' REFTAG'} && $myScript->{' REFTAG'} ne ''; + + foreach my $lTag (@{$sibScript->{'LANG_TAGS'}}) + { + # Ok, found a script/lang that is in our sibling. + next if exists $myScript->{$lTag}; # Already in $self + + # Need to create this lang: + push @{$myScript->{'LANG_TAGS'}}, $lTag; + $myScript->{$lTag} = { 'FEATURES' => [] }; + } + unless (defined $myScript->{'DEFAULT'}) + { + # Create default lang for this script. Link to 'dflt' if it exists + $myScript->{'DEFAULT'} = exists $myScript->{'dflt'} ? {' REFTAG' => 'dflt'} : { 'FEATURES' => [] }; + } + } + $self; +} + +=head1 Internal Functions & Methods + +Most of these methods are used by subclasses for handling such things as coverage +tables. + +=head2 copy($ref) + +Internal function to copy the top level of a dictionary to create a new dictionary. +Only the top level is copied. + +=cut + +sub copy +{ + my ($ref) = @_; + my ($res) = {}; + + foreach (keys %$ref) + { $res->{$_} = $ref->{$_}; } + $res; +} + + +=head2 $t->read_cover($cover_offset, $lookup_loc, $lookup, $fh, $is_cover) + +Reads a coverage table and stores the results in $lookup->{' CACHE'}, that is, if +it hasn't been read already. + +=cut + +sub read_cover +{ + my ($self, $offset, $base, $lookup, $fh, $is_cover) = @_; + my ($loc) = $fh->tell(); + my ($cover, $str); + + return undef unless $offset; + $str = sprintf("%X", $base + $offset); + return $lookup->{' CACHE'}{$str} if defined $lookup->{' CACHE'}{$str}; + $fh->seek($base + $offset, 0); + $cover = Font::TTF::Coverage->new($is_cover)->read($fh); + $fh->seek($loc, 0); + $lookup->{' CACHE'}{$str} = $cover; + return $cover; +} + + +=head2 ref_cache($obj, $cache, $offset) + +Internal function to keep track of the local positioning of subobjects such as +coverage and class definition tables, and their offsets. +What happens is that the cache is a hash of +sub objects indexed by the reference (using a string mashing of the +reference name which is valid for the duration of the reference) and holds a +list of locations in the output string which should be filled in with the +offset to the sub object when the final string is output in out_final. + +Uses tricks for Tie::Refhash + +=cut + +sub ref_cache +{ + my ($obj, $cache, $offset) = @_; + + return 0 unless defined $obj; + unless (defined $cache->{"$obj"}) + { push (@{$cache->{''}}, $obj); } + push (@{$cache->{"$obj"}}, $offset); + return 0; +} + + +=head2 out_final($fh, $out, $cache_list, $state) + +Internal function to actually output everything to the file handle given that +now we know the offset to the first sub object to be output and which sub objects +are to be output and what locations need to be updated, we can now +generate everything. $cache_list is an array of two element arrays. The first element +is a cache object, the second is an offset to be subtracted from each reference +to that object made in the cache. + +If $state is 1, then the output is not sent to the filehandle and the return value +is the string to be output. If $state is absent or 0 then output is not limited +by storing in a string first and the return value is ""; + +=cut + +sub out_final +{ + my ($fh, $out, $cache_list, $state) = @_; + my ($len) = length($out || ''); + my ($base_loc) = $state ? 0 : $fh->tell(); + my ($loc, $t, $r, $s, $master_cache, $offs, $str, %vecs); + + $fh->print($out || '') unless $state; # first output the current attempt + foreach $r (@$cache_list) + { + $offs = $r->[1]; + foreach $t (@{$r->[0]{''}}) + { + $str = "$t"; + if (!defined $master_cache->{$str}) + { + my ($vec) = $t->signature(); + if ($vecs{$vec}) + { $master_cache->{$str} = $master_cache->{$vecs{$vec}}; } + else + { + $vecs{$vec} = $str; + $master_cache->{$str} = ($state ? length($out) : $fh->tell()) + - $base_loc; + if ($state) + { $out .= $t->out($fh, 1); } + else + { $t->out($fh, 0); } + } + } + foreach $s (@{$r->[0]{$str}}) + { substr($out, $s, 2) = pack('n', $master_cache->{$str} - $offs); } + } + } + if ($state) + { return $out; } + else + { + $loc = $fh->tell(); + $fh->seek($base_loc, 0); + $fh->print($out || ''); # the corrected version + $fh->seek($loc, 0); + } +} + + +=head2 $self->read_context($lookup, $fh, $type, $fmt, $cover, $count, $loc) + +Internal method to read context (simple and chaining context) lookup subtables for +the GSUB and GPOS table types. The assumed values for $type correspond to those +for GSUB, so GPOS should adjust the values upon calling. + +=cut + +sub read_context +{ + my ($self, $lookup, $fh, $type, $fmt, $cover, $count, $loc) = @_; + my ($dat, $i, $s, $t, @subst, @srec, $mcount, $scount); + + if ($type == 5 && $fmt < 3) + { + if ($fmt == 2) + { + $fh->read($dat, 2); + $lookup->{'CLASS'} = $self->read_cover($count, $loc, $lookup, $fh, 0); + $count = TTF_Unpack('S', $dat); + } + $fh->read($dat, $count << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { + if ($s == 0) + { + push (@{$lookup->{'RULES'}}, []); + next; + } + @subst = (); + $fh->seek($loc + $s, 0); + $fh->read($dat, 2); + $t = TTF_Unpack('S', $dat); + $fh->read($dat, $t << 1); + foreach $t (TTF_Unpack('S*', $dat)) + { + $fh->seek($loc + $s + $t, 0); + @srec = (); + $fh->read($dat, 4); + ($mcount, $scount) = TTF_Unpack('S2', $dat); + $mcount--; + $fh->read($dat, ($mcount << 1) + ($scount << 2)); + for ($i = 0; $i < $scount; $i++) + { push (@srec, [TTF_Unpack('S2', substr($dat, + ($mcount << 1) + ($i << 2), 4))]); } + push (@subst, {'ACTION' => [@srec], + 'MATCH' => [TTF_Unpack('S*', + substr($dat, 0, $mcount << 1))]}); + } + push (@{$lookup->{'RULES'}}, [@subst]); + } + $lookup->{'ACTION_TYPE'} = 'l'; + $lookup->{'MATCH_TYPE'} = ($fmt == 2 ? 'c' : 'g'); + } elsif ($type == 5 && $fmt == 3) + { + $fh->read($dat, ($cover << 1) + ($count << 2)); + @subst = (); @srec = (); + for ($i = 0; $i < $cover; $i++) + { push (@subst, $self->read_cover(TTF_Unpack('S', substr($dat, $i << 1, 2)), + $loc, $lookup, $fh, 1)); } + for ($i = 0; $i < $count; $i++) + { push (@srec, [TTF_Unpack('S2', substr($dat, ($count << 1) + ($i << 2), 4))]); } + $lookup->{'RULES'} = [[{'ACTION' => [@srec], 'MATCH' => [@subst]}]]; + $lookup->{'ACTION_TYPE'} = 'l'; + $lookup->{'MATCH_TYPE'} = 'o'; + } elsif ($type == 6 && $fmt < 3) + { + if ($fmt == 2) + { + $fh->read($dat, 6); + $lookup->{'PRE_CLASS'} = $self->read_cover($count, $loc, $lookup, $fh, 0) if $count; + ($i, $mcount, $count) = TTF_Unpack('S3', $dat); # messy: 2 classes & count + $lookup->{'CLASS'} = $self->read_cover($i, $loc, $lookup, $fh, 0) if $i; + $lookup->{'POST_CLASS'} = $self->read_cover($mcount, $loc, $lookup, $fh, 0) if $mcount; + } + $fh->read($dat, $count << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { + if ($s == 0) + { + push (@{$lookup->{'RULES'}}, []); + next; + } + @subst = (); + $fh->seek($loc + $s, 0); + $fh->read($dat, 2); + $t = TTF_Unpack('S', $dat); + $fh->read($dat, $t << 1); + foreach $i (TTF_Unpack('S*', $dat)) + { + $fh->seek($loc + $s + $i, 0); + @srec = (); + $t = {}; + $fh->read($dat, 2); + $mcount = TTF_Unpack('S', $dat); + if ($mcount > 0) + { + $fh->read($dat, $mcount << 1); + $t->{'PRE'} = [TTF_Unpack('S*', $dat)]; + } + $fh->read($dat, 2); + $mcount = TTF_Unpack('S', $dat); + if ($mcount > 1) + { + $fh->read($dat, ($mcount - 1) << 1); + $t->{'MATCH'} = [TTF_Unpack('S*', $dat)]; + } + $fh->read($dat, 2); + $mcount = TTF_Unpack('S', $dat); + if ($mcount > 0) + { + $fh->read($dat, $mcount << 1); + $t->{'POST'} = [TTF_Unpack('S*', $dat)]; + } + $fh->read($dat, 2); + $scount = TTF_Unpack('S', $dat); + $fh->read($dat, $scount << 2); + for ($i = 0; $i < $scount; $i++) + { push (@srec, [TTF_Unpack('S2', substr($dat, $i << 2))]); } + $t->{'ACTION'} = [@srec]; + push (@subst, $t); + } + push (@{$lookup->{'RULES'}}, [@subst]); + } + $lookup->{'ACTION_TYPE'} = 'l'; + $lookup->{'MATCH_TYPE'} = ($fmt == 2 ? 'c' : 'g'); + } elsif ($type == 6 && $fmt == 3) + { + $t = {}; + unless ($cover == 0) + { + @subst = (); + $fh->read($dat, $cover << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { push(@subst, $self->read_cover($s, $loc, $lookup, $fh, 1)); } + $t->{'PRE'} = [@subst]; + } + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + unless ($count == 0) + { + @subst = (); + $fh->read($dat, $count << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { push(@subst, $self->read_cover($s, $loc, $lookup, $fh, 1)); } + $t->{'MATCH'} = [@subst]; + } + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + unless ($count == 0) + { + @subst = (); + $fh->read($dat, $count << 1); + foreach $s (TTF_Unpack('S*', $dat)) + { push(@subst, $self->read_cover($s, $loc, $lookup, $fh, 1)); } + $t->{'POST'} = [@subst]; + } + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + @subst = (); + $fh->read($dat, $count << 2); + for ($i = 0; $i < $count; $i++) + { push (@subst, [TTF_Unpack('S2', substr($dat, $i << 2, 4))]); } + $t->{'ACTION'} = [@subst]; + $lookup->{'RULES'} = [[$t]]; + $lookup->{'ACTION_TYPE'} = 'l'; + $lookup->{'MATCH_TYPE'} = 'o'; + } + $lookup; +} + + +=head2 $self->out_context($lookup, $fh, $type, $fmt, $ctables, $out, $num) + +Provides shared behaviour between GSUB and GPOS tables during output for context +(chained and simple) rules. In addition, support is provided here for type 4 GSUB +tables, which are not used in GPOS. The value for $type corresponds to the type +in a GSUB table so calling from GPOS should adjust the value accordingly. + +=cut + +sub out_context +{ + my ($self, $lookup, $fh, $type, $fmt, $ctables, $out, $num, $base) = @_; + my ($offc, $offd, $i, $j, $r, $t, $numd); + + $out ||= ''; + if (($type == 4 || $type == 5 || $type == 6) && ($fmt == 1 || $fmt == 2)) + { + my ($base_off); + + if ($fmt == 1) + { + $out = pack("nnn", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + $num); + $base_off = 6; + } elsif ($type == 5) + { + $out = pack("nnnn", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'CLASS'}, $ctables, 4 + $base), $num); + $base_off = 8; + } elsif ($type == 6) + { + $out = pack("n6", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'PRE_CLASS'}, $ctables, 4 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'CLASS'}, $ctables, 6 + $base), + Font::TTF::Ttopen::ref_cache($lookup->{'POST_CLASS'}, $ctables, 8 + $base), + $num); + $base_off = 12; + } + + $out .= pack('n*', (0) x $num); + $offc = length($out); + for ($i = 0; $i < $num; $i++) + { + $r = $lookup->{'RULES'}[$i]; + next unless exists $r->[0]{'ACTION'}; + $numd = $#{$r} + 1; + substr($out, ($i << 1) + $base_off, 2) = pack('n', $offc); + $out .= pack('n*', $numd, (0) x $numd); + $offd = length($out) - $offc; + for ($j = 0; $j < $numd; $j++) + { + substr($out, $offc + 2 + ($j << 1), 2) = pack('n', $offd); + if ($type == 4) + { + $out .= pack('n*', $r->[$j]{'ACTION'}[0], $#{$r->[$j]{'MATCH'}} + 2, + @{$r->[$j]{'MATCH'}}); + } elsif ($type == 5) + { + $out .= pack('n*', $#{$r->[$j]{'MATCH'}} + 2, + $#{$r->[$j]{'ACTION'}} + 1, + @{$r->[$j]{'MATCH'}}); + foreach $t (@{$r->[$j]{'ACTION'}}) + { $out .= pack('n2', @$t); } + } elsif ($type == 6) + { + $out .= pack('n*', $#{$r->[$j]{'PRE'}} + 1, @{$r->[$j]{'PRE'}}, + $#{$r->[$j]{'MATCH'}} + 2, @{$r->[$j]{'MATCH'}}, + $#{$r->[$j]{'POST'}} + 1, @{$r->[$j]{'POST'}}, + $#{$r->[$j]{'ACTION'}} + 1); + foreach $t (@{$r->[$j]{'ACTION'}}) + { $out .= pack('n2', @$t); } + } + $offd = length($out) - $offc; + } + $offc = length($out); + } + } elsif ($type == 5 && $fmt == 3) + { + $out .= pack('n3', $fmt, $#{$lookup->{'RULES'}[0][0]{'MATCH'}} + 1, + $#{$lookup->{'RULES'}[0][0]{'ACTION'}} + 1); + foreach $t (@{$lookup->{'RULES'}[0][0]{'MATCH'}}) + { $out .= pack('n', Font::TTF::Ttopen::ref_cache($t, $ctables, length($out) + $base)); } + foreach $t (@{$lookup->{'RULES'}[0][0]{'ACTION'}}) + { $out .= pack('n2', @$t); } + } elsif ($type == 6 && $fmt == 3) + { + $r = $lookup->{'RULES'}[0][0]; + no strict 'refs'; # temp fix - more code needed (probably "if" statements in the event 'PRE' or 'POST' are empty) + $out .= pack('n2', $fmt, defined $r->{'PRE'} ? scalar @{$r->{'PRE'}} : 0); + foreach $t (@{$r->{'PRE'}}) + { $out .= pack('n', Font::TTF::Ttopen::ref_cache($t, $ctables, length($out) + $base)); } + $out .= pack('n', defined $r->{'MATCH'} ? scalar @{$r->{'MATCH'}} : 0); + foreach $t (@{$r->{'MATCH'}}) + { $out .= pack('n', Font::TTF::Ttopen::ref_cache($t, $ctables, length($out) + $base)); } + $out .= pack('n', defined $r->{'POST'} ? scalar @{$r->{'POST'}} : 0); + foreach $t (@{$r->{'POST'}}) + { $out .= pack('n', Font::TTF::Ttopen::ref_cache($t, $ctables, length($out) + $base)); } + $out .= pack('n', defined $r->{'ACTION'} ? scalar @{$r->{'ACTION'}} : 0); + foreach $t (@{$r->{'ACTION'}}) + { $out .= pack('n2', @$t); } + } + $out; +} + +=head1 BUGS + +=over 4 + +=item * + +No way to share cachable items (coverage tables, classes, anchors, device tables) +across different lookups. The items are always output after the lookup and +repeated if necessary. Within lookup sharing is possible. + +=back + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + +1; + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Useall.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Useall.pm new file mode 100644 index 0000000..85e69dc --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Useall.pm @@ -0,0 +1,54 @@ +use Font::TTF::Sill; +use Font::TTF::Cvt_; +use Font::TTF::Fpgm; +use Font::TTF::Glyf; +use Font::TTF::Hdmx; +use Font::TTF::Kern; +use Font::TTF::Loca; +use Font::TTF::LTSH; +use Font::TTF::Name; +use Font::TTF::OS_2; +use Font::TTF::PCLT; +use Font::TTF::Post; +use Font::TTF::Prep; +use Font::TTF::Vmtx; +use Font::TTF::AATKern; +use Font::TTF::AATutils; +use Font::TTF::Anchor; +use Font::TTF::Bsln; +use Font::TTF::Delta; +use Font::TTF::Fdsc; +use Font::TTF::Feat; +use Font::TTF::GrFeat; +use Font::TTF::Fmtx; +use Font::TTF::GPOS; +use Font::TTF::Mort; +use Font::TTF::Prop; +use Font::TTF::GDEF; +use Font::TTF::Coverage; +use Font::TTF::GSUB; +use Font::TTF::Hhea; +use Font::TTF::Table; +use Font::TTF::Ttopen; +use Font::TTF::Glyph; +use Font::TTF::Head; +use Font::TTF::Hmtx; +use Font::TTF::Vhea; +use Font::TTF::Cmap; +use Font::TTF::Utils; +use Font::TTF::Maxp; +use Font::TTF::Font; +use Font::TTF::Kern::ClassArray; +use Font::TTF::Kern::CompactClassArray; +use Font::TTF::Kern::OrderedList; +use Font::TTF::Kern::StateTable; +use Font::TTF::Kern::Subtable; +use Font::TTF::Mort::Chain; +use Font::TTF::Mort::Contextual; +use Font::TTF::Mort::Insertion; +use Font::TTF::Mort::Ligature; +use Font::TTF::Mort::Noncontextual; +use Font::TTF::Mort::Rearrangement; +use Font::TTF::Mort::Subtable; + +1; diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Utils.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Utils.pm new file mode 100644 index 0000000..a849ee3 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Utils.pm @@ -0,0 +1,630 @@ +package Font::TTF::Utils; + +=head1 NAME + +Font::TTF::Utils - Utility functions to save fingers + +=head1 DESCRIPTION + +Lots of useful functions to save my fingers, especially for trivial tables + +=head1 FUNCTIONS + +The following functions are exported + +=cut + +use strict; +use vars qw(@ISA @EXPORT $VERSION @EXPORT_OK); +require Exporter; + +@ISA = qw(Exporter); +@EXPORT = qw(TTF_Init_Fields TTF_Read_Fields TTF_Out_Fields TTF_Pack + TTF_Unpack TTF_word_utf8 TTF_utf8_word TTF_bininfo); +@EXPORT_OK = (@EXPORT, qw(XML_hexdump)); +$VERSION = 0.0001; + +=head2 ($val, $pos) = TTF_Init_Fields ($str, $pos) + +Given a field description from the C section, creates an absolute entry +in the fields associative array for the class + +=cut + +sub TTF_Init_Fields +{ + my ($str, $pos, $inval) = @_; + my ($key, $val, $res, $len, $rel); + + $str =~ s/\r?\n$//o; + if ($inval) + { ($key, $val) = ($str, $inval); } + else + { ($key, $val) = split(',\s*', $str); } + return (undef, undef, 0) unless (defined $key && $key ne ""); + if ($val =~ m/^(\+?)(\d*)(\D+)(\d*)/oi) + { + $rel = $1; + if ($rel eq "+") + { $pos += $2; } + elsif ($2 ne "") + { $pos = $2; } + $val = $3; + $len = $4; + } + $len = "" unless defined $len; + $pos = 0 if !defined $pos || $pos eq ""; + $res = "$pos:$val:$len"; + if ($val eq "f" || $val eq 'v' || $val =~ m/^[l]/oi) + { $pos += 4 * ($len ne "" ? $len : 1); } + elsif ($val eq "F" || $val =~ m/^[s]/oi) + { $pos += 2 * ($len ne "" ? $len : 1); } + else + { $pos += 1 * ($len ne "" ? $len : 1); } + + ($key, $res, $pos); +} + + +=head2 TTF_Read_Fields($obj, $dat, $fields) + +Given a block of data large enough to account for all the fields in a table, +processes the data block to convert to the values in the objects instance +variables by name based on the list in the C block which has been run +through C + +=cut + +sub TTF_Read_Fields +{ + my ($self, $dat, $fields) = @_; + my ($pos, $type, $res, $f, $arrlen, $arr, $frac); + + foreach $f (keys %{$fields}) + { + ($pos, $type, $arrlen) = split(':', $fields->{$f}); + $pos = 0 if $pos eq ""; + if ($arrlen ne "") + { $self->{$f} = [TTF_Unpack("$type$arrlen", substr($dat, $pos))]; } + else + { $self->{$f} = TTF_Unpack("$type", substr($dat, $pos)); } + } + $self; +} + + +=head2 TTF_Unpack($fmt, $dat) + +A TrueType types equivalent of Perls C function. Thus $fmt consists of +type followed by an optional number of elements to read including *. The type +may be one of: + + c BYTE + C CHAR + f FIXED + F F2DOT14 + l LONG + L ULONG + s SHORT + S USHORT + v Version number (FIXED) + +Note that C, C and C are not data types but units. + +Returns array of scalar (first element) depending on context + +=cut + +sub TTF_Unpack +{ + my ($fmt, $dat) = @_; + my ($res, $frac, $i, $arrlen, $type, @res); + + while ($fmt =~ s/^([cflsv])(\d+|\*)?//oi) + { + $type = $1; + $arrlen = $2; + $arrlen = 1 if !defined $arrlen || $arrlen eq ""; + $arrlen = -1 if $arrlen eq "*"; + + for ($i = 0; ($arrlen == -1 && $dat ne "") || $i < $arrlen; $i++) + { + if ($type eq "f") + { + ($res, $frac) = unpack("nn", $dat); + substr($dat, 0, 4) = ""; + $res -= 65536 if $res > 32767; + $res += $frac / 65536.; + } + elsif ($type eq "v") + { + ($res, $frac) = unpack("nn", $dat); + substr($dat, 0, 4) = ""; + $res -= 65536 if $res > 32767; + $res = sprintf("%d.%X", $res, $frac); + } + elsif ($type eq "F") + { + $res = unpack("n", $dat); + substr($dat, 0, 2) = ""; +# $res -= 65536 if $res >= 32768; + $frac = $res & 0x3fff; + $res >>= 14; + $res -= 4 if $res > 1; +# $frac -= 16384 if $frac > 8191; + $res += $frac / 16384.; + } + elsif ($type =~ m/^[l]/oi) + { + $res = unpack("N", $dat); + substr($dat, 0, 4) = ""; + $res -= (1 << 32) if ($type eq "l" && $res >= 1 << 31); + } + elsif ($type =~ m/^[s]/oi) + { + $res = unpack("n", $dat); + substr($dat, 0, 2) = ""; + $res -= 65536 if ($type eq "s" && $res >= 32768); + } + elsif ($type eq "c") + { + $res = unpack("c", $dat); + substr($dat, 0, 1) = ""; + } + else + { + $res = unpack("C", $dat); + substr($dat, 0, 1) = ""; + } + push (@res, $res); + } + } + return wantarray ? @res : $res[0]; +} + + +=head2 $dat = TTF_Out_Fields($obj, $fields, $len) + +Given the fields table from C writes out the instance variables from +the object to the filehandle in TTF binary form. + +=cut + +sub TTF_Out_Fields +{ + my ($obj, $fields, $len) = @_; + my ($dat) = "\000" x $len; + my ($f, $pos, $type, $res, $arr, $arrlen, $frac); + + foreach $f (keys %{$fields}) + { + ($pos, $type, $arrlen) = split(':', $fields->{$f}); + if ($arrlen ne "") + { $res = TTF_Pack("$type$arrlen", @{$obj->{$f}}); } + else + { $res = TTF_Pack("$type", $obj->{$f}); } + substr($dat, $pos, length($res)) = $res; + } + $dat; +} + + +=head2 $dat = TTF_Pack($fmt, @data) + +The TrueType equivalent to Perl's C function. See details of C +for how to work the $fmt string. + +=cut + +sub TTF_Pack +{ + my ($fmt, @obj) = @_; + my ($type, $i, $arrlen, $dat, $res, $frac); + + $dat = ''; + while ($fmt =~ s/^([flscv])(\d+|\*)?//oi) + { + $type = $1; + $arrlen = $2 || ""; + $arrlen = $#obj + 1 if $arrlen eq "*"; + $arrlen = 1 if $arrlen eq ""; + + for ($i = 0; $i < $arrlen; $i++) + { + $res = shift(@obj) || 0; + if ($type eq "f") + { + $frac = int(($res - int($res)) * 65536); + $res = (int($res) << 16) + $frac; + $dat .= pack("N", $res); + } + elsif ($type eq "v") + { + if ($res =~ s/\.(\d+)$//o) + { + $frac = $1; + $frac .= "0" x (4 - length($frac)); + } + else + { $frac = 0; } + $dat .= pack('nn', $res, eval("0x$frac")); + } + elsif ($type eq "F") + { + $frac = int(($res - int($res)) * 16384); + $res = (int($res) << 14) + $frac; + $dat .= pack("n", $res); + } + elsif ($type =~ m/^[l]/oi) + { + $res += 1 << 32 if ($type eq 'L' && $res < 0); + $dat .= pack("N", $res); + } + elsif ($type =~ m/^[s]/oi) + { + $res += 1 << 16 if ($type eq 'S' && $res < 0); + $dat .= pack("n", $res); + } + elsif ($type eq "c") + { $dat .= pack("c", $res); } + else + { $dat .= pack("C", $res); } + } + } + $dat; +} + + +=head2 ($num, $range, $select, $shift) = TTF_bininfo($num) + +Calculates binary search information from a number of elements + +=cut + +sub TTF_bininfo +{ + my ($num, $block) = @_; + my ($range, $select, $shift); + + $range = 1; + for ($select = 0; $range <= $num; $select++) + { $range *= 2; } + $select--; $range /= 2; + $range *= $block; + + $shift = $num * $block - $range; + ($num, $range, $select, $shift); +} + + +=head2 TTF_word_utf8($str) + +Returns the UTF8 form of the 16 bit string, assumed to be in big endian order, +including surrogate handling + +=cut + +sub TTF_word_utf8 +{ + my ($str) = @_; + my ($res, $i); + my (@dat) = unpack("n*", $str); + + return pack("U*", @dat) if ($] >= 5.006); + for ($i = 0; $i <= $#dat; $i++) + { + my ($dat) = $dat[$i]; + if ($dat < 0x80) # Thanks to Gisle Aas for some of his old code + { $res .= chr($dat); } + elsif ($dat < 0x800) + { $res .= chr(0xC0 | ($dat >> 6)) . chr(0x80 | ($dat & 0x3F)); } + elsif ($dat >= 0xD800 && $dat < 0xDC00) + { + my ($dat1) = $dat[++$i]; + my ($top) = (($dat & 0x3C0) >> 6) + 1; + $res .= chr(0xF0 | ($top >> 2)) + . chr(0x80 | (($top & 1) << 4) | (($dat & 0x3C) >> 2)) + . chr(0x80 | (($dat & 0x3) << 4) | (($dat1 & 0x3C0) >> 6)) + . chr(0x80 | ($dat1 & 0x3F)); + } else + { $res .= chr(0xE0 | ($dat >> 12)) . chr(0x80 | (($dat >> 6) & 0x3F)) + . chr(0x80 | ($dat & 0x3F)); } + } + $res; +} + + +=head2 TTF_utf8_word($str) + +Returns the 16-bit form in big endian order of the UTF 8 string, including +surrogate handling to Unicode. + +=cut + +sub TTF_utf8_word +{ + my ($str) = @_; + my ($res); + + return pack("n*", unpack("U*", $str)) if ($^V ge v5.6.0); + $str = "$str"; # copy $str + while (length($str)) # Thanks to Gisle Aas for some of his old code + { + $str =~ s/^[\x80-\xBF]+//o; + if ($str =~ s/^([\x00-\x7F]+)//o) + { $res .= pack("n*", unpack("C*", $1)); } + elsif ($str =~ s/^([\xC0-\xDF])([\x80-\xBF])//o) + { $res .= pack("n", ((ord($1) & 0x1F) << 6) | (ord($2) & 0x3F)); } + elsif ($str =~ s/^([\0xE0-\xEF])([\x80-\xBF])([\x80-\xBF])//o) + { $res .= pack("n", ((ord($1) & 0x0F) << 12) + | ((ord($2) & 0x3F) << 6) + | (ord($3) & 0x3F)); } + elsif ($str =~ s/^([\xF0-\xF7])([\x80-\xBF])([\x80-\xBF])([\x80-\xBF])//o) + { + my ($b1, $b2, $b3, $b4) = (ord($1), ord($2), ord($3), ord($4)); + $res .= pack("n", ((($b1 & 0x07) << 8) | (($b2 & 0x3F) << 2) + | (($b3 & 0x30) >> 4)) + 0xD600); # account for offset + $res .= pack("n", ((($b3 & 0x0F) << 6) | ($b4 & 0x3F)) + 0xDC00); + } + elsif ($str =~ s/^[\xF8-\xFF][\x80-\xBF]*//o) + { } + } + $res; +} + + +=head2 XML_hexdump($context, $dat) + +Dumps out the given data as a sequence of blocks each 16 bytes wide + +=cut + +sub XML_hexdump +{ + my ($context, $depth, $dat) = @_; + my ($fh) = $context->{'fh'}; + my ($i, $len, $out); + + $len = length($dat); + for ($i = 0; $i < $len; $i += 16) + { + $out = join(' ', map {sprintf("%02X", ord($_))} (split('', substr($dat, $i, 16)))); + $fh->printf("%s%s\n", $depth, $i, $out); + } +} + + +=head2 XML_outhints + +Converts a binary string of hinting code into a textual representation + +=cut + +{ + my (@hints) = ( + ['SVTCA[0]'], ['SVTCA[1]'], ['SPVTCA[0]'], ['SPVTCA[1]'], ['SFVTCA[0]'], ['SFVTCA[1]'], ['SPVTL[0]'], ['SPVTL[1]'], + ['SFVTL[0]'], ['SFVTL[1]'], ['SPVFS'], ['SFVFS'], ['GPV'], ['GFV'], ['SVFTPV'], ['ISECT'], +# 10 + ['SRP0'], ['SRP1'], ['SRP2'], ['SZP0'], ['SZP1'], ['SZP2'], ['SZPS'], ['SLOOP'], + ['RTG'], ['RTHG'], ['SMD'], ['ELSE'], ['JMPR'], ['SCVTCI'], ['SSWCI'], ['SSW'], +# 20 + ['DUP'], ['POP'], ['CLEAR'], ['SWAP'], ['DEPTH'], ['CINDEX'], ['MINDEX'], ['ALIGNPTS'], + [], ['UTP'], ['LOOPCALL'], ['CALL'], ['FDEF'], ['ENDF'], ['MDAP[0]'], ['MDAP[1]'], +# 30 + ['IUP[0]'], ['IUP[1]'], ['SHP[0]'], ['SHP[1]'], ['SHC[0]'], ['SHC[1]'], ['SHZ[0]'], ['SHZ[1]'], + ['SHPIX'], ['IP'], ['MSIRP[0]'], ['MSIRP[1]'], ['ALIGNRP'], ['RTDG'], ['MIAP[0]'], ['MIAP[1]'], +# 40 + ['NPUSHB', -1, 1], ['NPUSHW', -1, 2], ['WS', 0, 0], ['RS', 0, 0], ['WCVTP', 0, 0], ['RCVT', 0, 0], ['GC[0]'], ['GC[1]'], + ['SCFS'], ['MD[0]'], ['MD[1]'], ['MPPEM'], ['MPS'], ['FLIPON'], ['FLIPOFF'], ['DEBUG'], +# 50 + ['LT'], ['LTEQ'], ['GT'], ['GTEQ'], ['EQ'], ['NEQ'], ['ODD'], ['EVEN'], + ['IF'], ['EIF'], ['AND'], ['OR'], ['NOT'], ['DELTAP1'], ['SDB'], ['SDS'], +# 60 + ['ADD'], ['SUB'], ['DIV'], ['MULT'], ['ABS'], ['NEG'], ['FLOOR'], ['CEILING'], + ['ROUND[0]'], ['ROUND[1]'], ['ROUND[2]'], ['ROUND[3]'], ['NROUND[0]'], ['NROUND[1]'], ['NROUND[2]'], ['NROUND[3]'], +# 70 + ['WCVTF'], ['DELTAP2'], ['DELTAP3'], ['DELTAC1'], ['DELTAC2'], ['DELTAC3'], ['SROUND'], ['S45ROUND'], + ['JROT'], ['JROF'], ['ROFF'], [], ['RUTG'], ['RDTG'], ['SANGW'], [], +# 80 + ['FLIPPT'], ['FLIPRGON'], ['FLIPRGOFF'], [], [], ['SCANCTRL'], ['SDPVTL[0]'], ['SDPVTL[1]'], + ['GETINFO'], ['IDEF'], ['ROLL'], ['MAX'], ['MIN'], ['SCANTYPE'], ['INSTCTRL'], [], +# 90 + [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], +# A0 + [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], +# B0 + ['PUSHB1', 1, 1], ['PUSHB2', 2, 1], ['PUSHB3', 3, 1], ['PUSHB4', 4, 1], ['PUSHB5', 5, 1], ['PUSHB6', 6, 1], ['PUSHB7', 7, 1], ['PUSHB8', 8, 1], + ['PUSHW1', 1, 2], ['PUSHW2', 2, 2], ['PUSHW3', 3, 2], ['PUSHW4', 4, 2], ['PUSHW5', 5, 2], ['PUSHW6', 6, 2], ['PUSHW7', 7, 2], ['PUSHW8', 8, 2], +# C0 + ['MDRP[0]'], ['MDRP[1]'], ['MDRP[2]'], ['MDRP[3]'], ['MDRP[4]'], ['MDRP[5]'], ['MDRP[6]'], ['MDRP[7]'], + ['MDRP[8]'], ['MDRP[9]'], ['MDRP[A]'], ['MDRP[B]'], ['MDRP[C]'], ['MDRP[D]'], ['MDRP[E]'], ['MDRP[F]'], +# D0 + ['MDRP[10]'], ['MDRP[11]'], ['MDRP[12]'], ['MDRP[13]'], ['MDRP[14]'], ['MDRP[15]'], ['MDRP[16]'], ['MDRP[17]'], + ['MDRP[18]'], ['MDRP[19]'], ['MDRP[1A]'], ['MDRP[1B]'], ['MDRP[1C]'], ['MDRP[1D]'], ['MDRP[1E]'], ['MDRP[1F]'], +# E0 + ['MIRP[0]'], ['MIRP[1]'], ['MIRP[2]'], ['MIRP[3]'], ['MIRP[4]'], ['MIRP[5]'], ['MIRP[6]'], ['MIRP[7]'], + ['MIRP[8]'], ['MIRP[9]'], ['MIRP[A]'], ['MIRP[B]'], ['MIRP[C]'], ['MIRP[D]'], ['MIRP[E]'], ['MIRP[F]'], +# F0 + ['MIRP[10]'], ['MIRP[11]'], ['MIRP[12]'], ['MIRP[13]'], ['MIRP[14]'], ['MIRP[15]'], ['MIRP[16]'], ['MIRP[17]'], + ['MIRP[18]'], ['MIRP[19]'], ['MIRP[1A]'], ['MIRP[1B]'], ['MIRP[1C]'], ['MIRP[1D]'], ['MIRP[1E]'], ['MIRP[1F]']); + + my ($i); + my (%hints) = map { $_->[0] => $i++ if (defined $_->[0]); } @hints; + + sub XML_binhint + { + my ($dat) = @_; + my ($len) = length($dat); + my ($res, $i, $text, $size, $num); + + for ($i = 0; $i < $len; $i++) + { + ($text, $num, $size) = @{$hints[ord(substr($dat, $i, 1))]}; + $num = 0 unless (defined $num); + $text = sprintf("UNK[%02X]", ord(substr($dat, $i, 1))) unless defined $text; + $res .= $text; + if ($num != 0) + { + if ($num < 0) + { + $i++; + my ($nnum) = unpack($num == -1 ? 'C' : 'n', substr($dat, $i, -$num)); + $i += -$num - 1; + $num = $nnum; + } + $res .= "\t" . join(' ', unpack($size == 1 ? 'C*' : 'n*', substr($dat, $i + 1, $num * $size))); + $i += $num * $size; + } + $res .= "\n"; + } + $res; + } + + sub XML_hintbin + { + my ($dat) = @_; + my ($l, $res, @words, $num); + + foreach $l (split(/\s*\n\s*/, $dat)) + { + @words = split(/\s*/, $l); + next unless (defined $hints{$words[0]}); + $num = $hints{$words[0]}; + $res .= pack('C', $num); + if ($hints[$num][1] < 0) + { + $res .= pack($hints[$num][1] == -1 ? 'C' : 'n', $#words); + $res .= pack($hints[$num][2] == 1 ? 'C*' : 'n*', @words[1 .. $#words]); + } + elsif ($hints[$num][1] > 0) + { + $res .= pack($hints[$num][2] == 1 ? 'C*' : 'n*', @words[1 .. $hints[$num][1]]); + } + } + $res; + } +} + + +=head2 make_circle($f, $cmap, [$dia, $sb, $opts]) + +Adds a dotted circle to a font. This function is very configurable. The +parameters passed in are: + +=over 4 + +=item $f + +Font to work with. This is required. + +=item $cmap + +A cmap table (not the 'val' sub-element of a cmap) to add the glyph too. Optional. + +=item $dia + +Optional diameter for the main circle. Defaults to 80% em + +=item $sb + +Side bearing. The left and right side-bearings are always the same. This value +defaults to 10% em. + +=back + +There are various options to control all sorts of interesting aspects of the circle + +=over 4 + +=item numDots + +Number of dots in the circle + +=item numPoints + +Number of curve points to use to create each dot + +=item uid + +Unicode reference to store this glyph under in the cmap. Defaults to 0x25CC + +=item pname + +Postscript name to give the glyph. Defaults to uni25CC. + +=item -dRadius + +Radius of each dot. + +=back + +=cut + +sub make_circle +{ + my ($font, $cmap, $dia, $sb, %opts) = @_; + my ($upem) = $font->{'head'}{'unitsPerEm'}; + my ($glyph) = Font::TTF::Glyph->new('PARENT' => $font, 'read' => 2); + my ($PI) = 3.1415926535; + my ($R, $r, $xorg, $yorg); + my ($i, $j, $numg, $maxp); + my ($numc) = $opts{'-numDots'} || 16; + my ($nump) = ($opts{'-numPoints'} * 2) || 8; + my ($uid) = $opts{'-uid'} || 0x25CC; + my ($pname) = $opts{'-pname'} || 'uni25CC'; + + $dia ||= $upem * .8; # .95 to fit exactly + $sb ||= $upem * .1; + $R = $dia / 2; + $r = $opts{'-dRadius'} || ($R * .1); + ($xorg, $yorg) = ($R + $r, $R); + + $xorg += $sb; + $font->{'post'}->read; + $font->{'glyf'}->read; + for ($i = 0; $i < $numc; $i++) + { + my ($pxorg, $pyorg) = ($xorg + $R * cos(2 * $PI * $i / $numc), + $yorg + $R * sin(2 * $PI * $i / $numc)); + for ($j = 0; $j < $nump; $j++) + { + push (@{$glyph->{'x'}}, int ($pxorg + ($j & 1 ? 1/cos(2*$PI/$nump) : 1) * $r * cos(2 * $PI * $j / $nump))); + push (@{$glyph->{'y'}}, int ($pyorg + ($j & 1 ? 1/cos(2*$PI/$nump) : 1) * $r * sin(2 * $PI * $j / $nump))); + push (@{$glyph->{'flags'}}, $j & 1 ? 0 : 1); + } + push (@{$glyph->{'endPoints'}}, $#{$glyph->{'x'}}); + } + $glyph->{'numberOfContours'} = $#{$glyph->{'endPoints'}} + 1; + $glyph->{'numPoints'} = $#{$glyph->{'x'}} + 1; + $glyph->update; + $numg = $font->{'maxp'}{'numGlyphs'}; + $font->{'maxp'}{'numGlyphs'}++; + + $font->{'hmtx'}{'advance'}[$numg] = int($xorg + $R + $r + $sb + .5); + $font->{'hmtx'}{'lsb'}[$numg] = int($xorg - $R - $r + .5); + $font->{'loca'}{'glyphs'}[$numg] = $glyph; + $cmap->{'val'}{$uid} = $numg if ($cmap); + $font->{'post'}{'VAL'}[$numg] = $pname; + delete $font->{'hdmx'}; + delete $font->{'VDMX'}; + delete $font->{'LTSH'}; + + $font->tables_do(sub {$_[0]->dirty;}); + $font->update; + return ($numg - 1); +} + + +1; + +=head1 BUGS + +No known bugs + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vhea.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vhea.pm new file mode 100644 index 0000000..2e1a49a --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vhea.pm @@ -0,0 +1,159 @@ +package Font::TTF::Vhea; + +=head1 NAME + +Font::TTF::Vhea - Vertical Header table + +=head1 DESCRIPTION + +This is a simple table with just standards specified instance variables + +=head1 INSTANCE VARIABLES + + version + Ascender + Descender + LineGap + advanceHeightMax + minTopSideBearing + minBottomSideBearing + yMaxExtent + caretSlopeRise + caretSlopeRun + metricDataFormat + numberOfVMetrics + + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); +@field_info = ( + 'version' => 'v', + 'Ascender' => 's', + 'Descender' => 's', + 'LineGap' => 's', + 'advanceHeightMax' => 'S', + 'minTopSideBearing' => 's', + 'minBottomSideBearing' => 's', + 'yMaxExtent' => 's', + 'caretSlopeRise' => 's', + 'caretSlopeRun' => 's', + 'metricDataFormat' => '+10s', + 'numberOfVMetrics' => 's'); + +sub init +{ + my ($k, $v, $c, $i); + for ($i = 0; $i < $#field_info; $i += 2) + { + ($k, $v, $c) = TTF_Init_Fields($field_info[$i], $c, $field_info[$i + 1]); + next unless defined $k && $k ne ""; + $fields{$k} = $v; + } +} + + +=head2 $t->read + +Reads the table into memory as instance variables + +=cut + +sub read +{ + my ($self) = @_; + my ($dat); + + $self->SUPER::read or return $self; + init unless defined $fields{'Ascender'}; + $self->{' INFILE'}->read($dat, 36); + + TTF_Read_Fields($self, $dat, \%fields); + $self; +} + + +=head2 $t->out($fh) + +Writes the table to a file either from memory or by copying. + +=cut + +sub out +{ + my ($self, $fh) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $self->{'numberOfVMetrics'} = $self->{' PARENT'}{'vmtx'}->numMetrics || $self->{'numberOfVMetrics'}; + $fh->print(TTF_Out_Fields($self, \%fields, 36)); + $self; +} + + +=head2 $t->update + +Updates various parameters in the hhea table from the hmtx table, assuming +the C table is dirty. + +=cut + +sub update +{ + my ($self) = @_; + my ($vmtx) = $self->{' PARENT'}{'vmtx'}; + my ($glyphs); + my ($num); + my ($i, $maw, $mlsb, $mrsb, $mext, $aw, $lsb, $ext); + + return undef unless ($self->SUPER::update); + return undef unless (defined $vmtx && defined $self->{' PARENT'}{'loca'}); + $vmtx->read->update; + $self->{' PARENT'}{'loca'}->read->update; + $glyphs = $self->{' PARENT'}{'loca'}{'glyphs'}; + $num = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + for ($i = 0; $i < $num; $i++) + { + $aw = $vmtx->{'advance'}[$i]; + $lsb = $vmtx->{'top'}[$i]; + if (defined $glyphs->[$i]) + { $ext = $lsb + $glyphs->[$i]->read->{'yMax'} - $glyphs->[$i]{'yMin'}; } + else + { $ext = $aw; } + $maw = $aw if ($aw > $maw); + $mlsb = $lsb if ($lsb < $mlsb or $i == 0); + $mrsb = $aw - $ext if ($aw - $ext < $mrsb or $i == 0); + $mext = $ext if ($ext > $mext); + } + $self->{'advanceHeightMax'} = $maw; + $self->{'minTopSideBearing'} = $mlsb; + $self->{'minBottomSideBearing'} = $mrsb; + $self->{'yMaxExtent'} = $mext; + $self->{'numberOfVMetrics'} = $vmtx->numMetrics; + $self; +} + + +1; + + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vmtx.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vmtx.pm new file mode 100644 index 0000000..b74d6df --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Vmtx.pm @@ -0,0 +1,86 @@ +package Font::TTF::Vmtx; + +=head1 NAME + +Font::TTF::Vmtx - Vertical Metrics + +=head1 DESCRIPTION + +Contains the advance height and top side bearing for each glyph. Given the +compressability of the data onto disk, this table uses information from +other tables, and thus must do part of its output during the output of +other tables + +=head1 INSTANCE VARIABLES + +The vertical metrics are kept in two arrays by glyph id. The variable names +do not start with a space + +=over 4 + +=item advance + +An array containing the advance height for each glyph + +=item top + +An array containing the top side bearing for each glyph + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA); +require Font::TTF::Hmtx; + +@ISA = qw(Font::TTF::Hmtx); + + +=head2 $t->read + +Reads the vertical metrics from the TTF file into memory + +=cut + +sub read +{ + my ($self) = @_; + my ($numh, $numg); + + $numh = $self->{' PARENT'}{'vhea'}->read->{'numberOfVMetrics'}; + $numg = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + $self->_read($numg, $numh, "advance", "top"); +} + + +=head2 $t->out($fh) + +Writes the metrics to a TTF file. Assumes that the C has updated the +numVMetrics from here + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($numg) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($numh) = $self->{' PARENT'}{'vhea'}{'numberOfVMetrics'}; + $self->_out($fh, $numg, $numh, "advance", "top"); +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken Martin_Hosken@sil.org. See L for copyright and +licensing. + +=cut + diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/Win32.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Win32.pm new file mode 100644 index 0000000..bb8886e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/Win32.pm @@ -0,0 +1,33 @@ +package Font::TTF::Win32; + +# use strict; +# use vars qw($HKEY_LOCAL_MACHINE); + +use Win32::Registry; +use Win32; +use File::Spec; +use Font::TTF::Font; + + +sub findfonts +{ + my ($sub) = @_; + my ($font_key) = 'SOFTWARE\Microsoft\Windows' . (Win32::IsWinNT() ? ' NT' : '') . '\CurrentVersion\Fonts'; + my ($regFont, $list, $l, $font, $file); + +# get entry from registry for a font of this name + $::HKEY_LOCAL_MACHINE->Open($font_key, $regFont); + $regFont->GetValues($list); + + foreach $l (sort keys %{$list}) + { + my ($fname) = $list->{$l}[0]; + next unless ($fname =~ s/\(TrueType\)$//o); + $file = File::Spec->rel2abs($list->{$l}[2], "$ENV{'windir'}/fonts"); + $font = Font::TTF::Font->open($file) || next; + &{$sub}($font, $fname); + $font->release; + } +} + +1; diff --git a/font-optimizer/ext/Font-TTF/lib/Font/TTF/XMLparse.pm b/font-optimizer/ext/Font-TTF/lib/Font/TTF/XMLparse.pm new file mode 100644 index 0000000..7b2b571 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/Font/TTF/XMLparse.pm @@ -0,0 +1,176 @@ +package Font::TTF::XMLparse; + +=head1 NAME + +Font::TTF::XMLparse - provides support for XML parsing. Requires Expat module XML::Parser::Expat + +=head1 SYNOPSIS + + use Font::TTF::Font; + use Font::TTF::XMLparse; + + $f = Font::TTF::Font->new; + read_xml($f, $ARGV[0]); + $f->out($ARGV[1]); + +=head1 DESCRIPTION + +This module contains the support routines for parsing XML and generating the +Truetype font structures as a result. The module has been separated from the rest +of the package in order to reduce the dependency that this would bring, of the +whole package on XML::Parser. This way, people without the XML::Parser can still +use the rest of the package. + +The package interacts with another package through the use of a context containing +and element 'receiver' which is an object which can possibly receive one of the +following messages: + +=over 4 + +=item XML_start + +This message is called when an open tag occurs. It is called with the context, +tag name and the attributes. The return value has no meaning. + +=item XML_end + +This messages is called when a close tag occurs. It is called with the context, +tag name and attributes (held over from when the tag was opened). There are 3 +possible return values from such a message: + +=over 8 + +=item undef + +This is the default return value indicating that default processing should +occur in which either the current element on the tree, or the text of this element +should be stored in the parent object. + +=item $context + +This magic value marks that the element should be deleted from the parent. +Nothing is stored in the parent. (This rather than '' is used to allow 0 returns.) + +=item anything + +Anything else is taken as the element content to be stored in the parent. + +=back 4 + +=back 4 + +In addition, the context hash passed to these messages contains the following +keys: + +=over 4 + +=item xml + +This is the expat xml object. The context is also available as +$context->{'xml'}{' mycontext'}. But that is a long winded way of not saying much! + +=item font + +This is the base object that was passed in for XML parsing. + +=item receiver + +This holds the current receiver of parsing events. It may be set in associated +application to adjust which objects should receive messages when. It is also stored +in the parsing stack to ensure that where an object changes it during XML_start, that +that same object that received XML_start will receive the corresponding XML_end + +=item stack + +This is the parsing stack, used internally to hold the current receiver and attributes +for each element open, as a complete hierarchy back to the root element. + +=item tree + +This element contains the storage tree corresponding to the parent of each element +in the stack. The default action is to push undef onto this stack during XML_start +and then to resolve this, either in the associated application (by changing +$context->{'tree'}[-1]) or during XML_end of a child element, by which time we know +whether we are dealing with an array or a hash or what. + +=item text + +Character processing is to insert all the characters into the text element of the +context for available use later. + +=back 4 + +=head1 METHODS + +=cut + +use XML::Parser::Expat; +require Exporter; + +use strict; +use vars qw(@ISA @EXPORT); + +@ISA = qw(Exporter); +@EXPORT = qw(read_xml); + +sub read_xml +{ + my ($font, $fname) = @_; + + my ($xml) = XML::Parser::Expat->new; + my ($context) = {'xml' => $xml, 'font' => $font}; + + $xml->setHandlers('Start' => sub { + my ($x, $tag, %attrs) = @_; + my ($context) = $x->{' mycontext'}; + my ($fn) = $context->{'receiver'}->can('XML_start'); + + push(@{$context->{'tree'}}, undef); + push(@{$context->{'stack'}}, [$context->{'receiver'}, {%attrs}]); + &{$fn}($context->{'receiver'}, $context, $tag, %attrs) if defined $fn; + }, + 'End' => sub { + my ($x, $tag) = @_; + my ($context) = $x->{' mycontext'}; + my ($fn) = $context->{'receiver'}->can('XML_end'); + my ($stackinfo) = pop(@{$context->{'stack'}}); + my ($current, $res); + + $context->{'receiver'} = $stackinfo->[0]; + $context->{'text'} =~ s/^\s*(.*?)\s*$/$1/o; + $res = &{$fn}($context->{'receiver'}, $context, $tag, %{$stackinfo->[1]}) if defined $fn; + $current = pop(@{$context->{'tree'}}); + $current = $context->{'text'} unless (defined $current); + $context->{'text'} = ''; + + if (defined $res) + { + return if ($res eq $context); + $current = $res; + } + return unless $#{$context->{'tree'}} >= 0; + if ($tag eq 'elem') + { + $context->{'tree'}[-1] = [] unless defined $context->{'tree'}[-1]; + push (@{$context->{'tree'}[-1]}, $current); + } else + { + $context->{'tree'}[-1] = {} unless defined $context->{'tree'}[-1]; + $context->{'tree'}[-1]{$tag} = $current; + } + }, + 'Char' => sub { + my ($x, $str) = @_; + $x->{' mycontext'}{'text'} .= $str; + }); + + $xml->{' mycontext'} = $context; + + $context->{'receiver'} = $font; + if (ref $fname) + { return $xml->parse($fname); } + else + { return $xml->parsefile($fname); } +} + + diff --git a/font-optimizer/ext/Font-TTF/lib/ttfmod.pl b/font-optimizer/ext/Font-TTF/lib/ttfmod.pl new file mode 100644 index 0000000..dd10f9e --- /dev/null +++ b/font-optimizer/ext/Font-TTF/lib/ttfmod.pl @@ -0,0 +1,174 @@ +# Title: TTFMOD.PL +# Author: M. Hosken +# Description: Read TTF file calling user functions for each table +# and output transformed tables to new TTF file. +# Useage: TTFMOD provides the complete control loop for processing +# the TTF files. All that the caller need supply is an +# associative array of functions to call keyed by the TTF +# table name and the two filenames. +# +# &ttfmod($infile, $outfile, *fns [, @must]); +# +# *fns is an associative array keyed by table name with +# values of the name of the subroutine in package main to +# be called to transfer the table from INFILE to OUTFILE. +# The subroutine is called with the following parameters and +# expected return values: +# +# ($len, $csum) = &sub(*INFILE, *OUTFILE, $len); +# +# INFILE and OUTFILE are the input and output streams, $len +# is the length of the table according to the directory. +# The return values are $len = new length of table to be +# given in the table directory. $csum = new value of table +# checksum. A way to test that this is correct is to +# checksum the whole file (e.g. using CSUM.BAT) and to +# ensure that the value is 0xB1B0AFBA according to a 32 bit +# checksum calculated bigendien. +# +# @must consists of a list of tables which must exist in the +# final output file, either by being there alread or by being +# inserted. +# +# Modifications: +# MJPH 1.00 22-SEP-1994 Original +# MJPH 1.1 18-MAR-1998 Added @must to ttfmod() +# MJPH 1.1.1 25-MAR-1998 Added $csum to copytab (to make reusable) + +package ttfmod; + +sub main'ttfmod { + local($infile, $outfile, *fns, @must) = @_; + + # open files as binary. Notice OUTFILE is opened for update not just write + open(INFILE, "$infile") || die "Unable top open \"$infile\" for reading"; + binmode INFILE; + open(OUTFILE, "+>$outfile") || die "Unable to open \"$outfile\" for writing"; + binmode OUTFILE; + + seek(INFILE, 0, 0); + read(INFILE, $dir_head, 12) || die "Reading table header"; + ($dir_num) = unpack("x4n", $dir_head); + print OUTFILE $dir_head; + # read and unpack table directory + for ($i = 0; $i < $dir_num; $i++) + { + read(INFILE, $dir_val, 16) || die "Reading table entry"; + $dir{unpack("a4", $dir_val)} = join(":", $i, unpack("x4NNN", $dir_val)); + print OUTFILE $dir_val; + printf STDERR "%s %08x\n", unpack("a4", $dir_val), unpack("x8N", $dir_val) + if (defined $main'opt_z); + } + foreach $n (@must) + { + next if defined $dir{$n}; + $dir{$n} = "$i:0:-1:0"; + $i++; $dir_num++; + print OUTFILE pack("a4NNN", $n, 0, -1, 0); + } + substr($dir_head, 4, 2) = pack("n", $dir_num); + $csum = unpack("%32N*", $dir_head); + $off = tell(OUTFILE); + seek(OUTFILE, 0, 0); + print OUTFILE $dir_head; + seek (OUTFILE, $off, 0); + # process tables in order they occur in the file + @dirlist = sort byoffset keys(%dir); + foreach $tab (@dirlist) + { + @tab_split = split(':', $dir{$tab}); + seek(INFILE, $tab_split[2], 0); # offset + $tab_split[2] = tell(OUTFILE); + if (defined $fns{$tab}) + { + $temp = "main'$fns{$tab}"; + ($dir_len, $sum) = &$temp(*INFILE, *OUTFILE, $tab_split[3]); + } + else + { + ($dir_len, $sum) = ©tab(*INFILE, *OUTFILE, $tab_split[3]); + } + $tab_split[3] = $dir_len; # len + $tab_split[1] = $sum; # checksum + $out_dir{$tab} = join(":", @tab_split); + } + # now output directory in same order as original directory + @dirlist = sort byindex keys(%out_dir); + foreach $tab (@dirlist) + { + @tab_split = split(':', $out_dir{$tab}); + seek (OUTFILE, 12 + $tab_split[0] * 16, 0); # directory index + print OUTFILE pack("A4N3", $tab, @tab_split[1..3]); + foreach $i (1..3, 1) # checksum directory values with csum twice + { + $csum += $tab_split[$i]; + # this line ensures $csum stays within 32 bit bounds, clipping as necessary + if ($csum > 0xffffffff) { $csum -= 0xffffffff; $csum--; } + } + # checksum the tag + $csum += unpack("N", $tab); + if ($csum > 0xffffffff) { $csum -= 0xffffffff; $csum--; } + } + # handle main checksum + @tab_split = split(':', $out_dir{"head"}); + seek(OUTFILE, $tab_split[2], 0); + read(OUTFILE, $head_head, 12); # read first bit of "head" table + @head_split = unpack("N3", $head_head); + $tab_split[1] -= $head_split[2]; # subtract old checksum + $csum -= $head_split[2] * 2; # twice because had double effect + # already + if ($csum < 0 ) { $csum += 0xffffffff; $csum++; } + $head_split[2] = 0xB1B0AFBA - $csum; # calculate new checksum + seek (OUTFILE, 12 + $tab_split[0] * 16, 0); + print OUTFILE pack("A4N3", "head", @tab_split[1..3]); + seek (OUTFILE, $tab_split[2], 0); # rewrite first bit of "head" table + print OUTFILE pack("N3", @head_split); + + # finish up + close(OUTFILE); + close(INFILE); + } + +# support function for sorting by table offset +sub byoffset { + @t1 = split(':', $dir{$a}); + @t2 = split(':', $dir{$b}); + return 1 if ($t1[2] == -1); # put inserted tables at the end + return -1 if ($t2[2] == -1); + return $t1[2] <=> $t2[2]; + } + +# support function for sorting by directory entry order +sub byindex { + $t1 = split(':', $dir{$a}, 1); + $t2 = split(':', $dir{$b}, 1); + return $t1 <=> $t2; + } + +# default table action: copies a table from input to output, recalculating +# the checksum (just to be absolutely sure). +sub copytab { + local(*INFILE, *OUTFILE, $len, $csum) = @_; + + while ($len > 0) + { + $count = ($len > 8192) ? 8192 : $len; # 8K buffering + read(INFILE, $buf, $count) == $count || die "Copying"; + $buf .= "\0" x (4 - ($count & 3)) if ($count & 3); # pad to long + print OUTFILE $buf; + $csum += unpack("%32N*", $buf); + if ($csum > 0xffffffff) { $csum -= 0xffffffff; $csum--; } + $len -= $count; + } + ($_[2], $csum); + } + +# test routine to copy file from input to output, no changes +package main; + +if ($test_package) + { + &ttfmod($ARGV[0], $ARGV[1], *dummy); + } +else + { 1; } diff --git a/font-optimizer/ext/Font-TTF/t/OFL.txt b/font-optimizer/ext/Font-TTF/t/OFL.txt new file mode 100644 index 0000000..0b4fe4b --- /dev/null +++ b/font-optimizer/ext/Font-TTF/t/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2005-2007, M. Hosken and SIL International, +with Reserved Font Name FontUtils Test + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/font-optimizer/ext/Font-TTF/t/tags.t b/font-optimizer/ext/Font-TTF/t/tags.t new file mode 100644 index 0000000..f50e521 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/t/tags.t @@ -0,0 +1,11 @@ +use strict; + +use Test::Simple tests => 3; +use Font::TTF::OTTags qw( %tttags %ttnames readtagsfile); + +ok($tttags{'SCRIPT'}{'Cypriot Syllabary'} eq 'cprt', 'tttags{SCRIPT}'); + +ok($ttnames{'LANGUAGE'}{'AFK '} eq 'Afrikaans', 'ttnames{LANGUAGE}'); + +ok($ttnames{'LANGUAGE'}{'DHV '} eq 'Dhivehi (OBSOLETE)' && $ttnames{'LANGUAGE'}{'DIV '} eq 'Dhivehi', 'ttnames{LANGUAGE} Dhivehi'); + diff --git a/font-optimizer/ext/Font-TTF/t/testfont.ttf b/font-optimizer/ext/Font-TTF/t/testfont.ttf new file mode 100644 index 0000000..d1bc3cf Binary files /dev/null and b/font-optimizer/ext/Font-TTF/t/testfont.ttf differ diff --git a/font-optimizer/ext/Font-TTF/t/ttfcopy.t b/font-optimizer/ext/Font-TTF/t/ttfcopy.t new file mode 100644 index 0000000..c8969b3 --- /dev/null +++ b/font-optimizer/ext/Font-TTF/t/ttfcopy.t @@ -0,0 +1,15 @@ +#!/usr/bin/perl + +use Test::Simple tests => 2; +use File::Compare; +use Font::TTF::Font; + +$f = Font::TTF::Font->open("t/testfont.ttf"); +ok($f); +$f->tables_do(sub { $_[0]->read; }); +$f->{'loca'}->glyphs_do(sub {$_[0]->read_dat; }); +$f->out("t/temp.ttf"); +$res = compare("t/temp.ttf", "t/testfont.ttf"); +ok(!$res); +unlink "t/temp.ttf" unless ($res); + diff --git a/font-optimizer/gen-tests.pl b/font-optimizer/gen-tests.pl new file mode 100644 index 0000000..63bb153 --- /dev/null +++ b/font-optimizer/gen-tests.pl @@ -0,0 +1,235 @@ +# This script generates various 'interesting' fonts, and outputs an HTML file +# containing the subsetted fonts and the original fonts. +# View the output in browsers (preferably multiple, on multiple platforms) to +# make sure the output looks the same as the original. + +use strict; +use warnings; + +use lib 'ext/Font-TTF/lib'; +use Font::Subsetter; +use Font::EOTWrapper; +use Encode; +use Clone; + +use utf8; + +# The following fonts need to exist in a directory called 'testfonts': +my @all = qw( + GenBasR.ttf + GenR102.TTF + LinLibertine_Re-4.1.8.ttf + DoulosSILR.ttf + DejaVuSans.ttf + DejaVuSerif.ttf + calibri.ttf + FedraSansPro-Demi.ttf +); + +my $index = $ARGV[0]; +die "Run '$0', or '$0 n' where n is the number of the test to rebuild\n" + if defined $index and $index !~ /^\d+$/; + +my @tests = ( + # These aren't proper tests (they drop features that affect the rendering) + # TODO: fix them so they are proper, and test that they're really dropping the + # unneeded glyphs etc +# [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf)], ["fluffily لا f"], [20], [qw(aalt ccmp dlig fina hlig init liga locl medi rlig salt kern mark mkmk)] ], +# [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf)], ["fluffily لا f"], [20], [qw(liga)] ], +# [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf)], ["fluffily لا f"], [20], [qw(fina init rlig)] ], +# [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf)], ["fluffily لا f"], [20], [] ], + + # Basic rendering + [ [@all], ["Hello world ABC abc 123"], [20] ], + + # Substitution and NFC issues + [ [qw(GenBasR.ttf DejaVuSans.ttf FedraSansPro-Demi.ttf)], [ + "i", + "\xec", + "i\x{0300}", + "i \x{0300}", + "ixixi", + "i\x{0300}", + ], [20, 8] ], + [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf)], [ + "s\x{0323}\x{0307}", # s, combining dot below, combining dot above + "s\x{0307}\x{0323}", # s, combining dot above, combining dot below + "\x{1e61}\x{0323}", # s with dot above, combining dot below + "\x{1e63}\x{0307}", # s with dot below, combining dot above + "\x{212b}", # angstrom + ], [20, 8] ], + + # Ligature rendering + [ [qw(LinLibertine_Re-4.1.8.ttf DejaVuSans.ttf FedraSansPro-Demi.ttf)], [ + "fluffily", + "fluffily", + "fluffily", + ], [20, 8] ], + + # GPOS issues + [ [qw(DejaVuSans.ttf FedraSansPro-Demi.ttf calibri.ttf)], + ["|VAVAV|", "ToToT", "x//x"], [20], ['kern'] ], + + # Lots of stuff + [ [@all], ["VABC(123) fTo fluffiest ffi!\@#,. \x{00e2}\x{00eb}I\x{0303}o\x{0300}u\x{030a}\x{0305}\x{0303} i\x{0331}\x{0301} \x{0d23}\x{0d4d}\x{200d} παρακαλώ хэлло 你好 表示问候 やあ التلفون הלו"], [20, 8] ], + +); + +my $common_css = <preload($fn); + $font_cache{$fn} = $s; + } + return Clone::clone($font_cache{$fn}); +} + +my %std_fonts; +# if (0) { my $j = 0; +for my $j (0..$#all) { + my $fn = $all[$j]; + (my $eot_fn = $fn) =~ s/\.[ot]tf$/.eot/i; + if (not -e "testfonts/$eot_fn") { + Font::EOTWrapper::convert("testfonts/$fn", "testfonts/$eot_fn"); + } + $common_css .= <', 'testoutput/tests.html' or die $!; + binmode $out, ':utf8'; + + print $out < + +Font tests + +EOF +} + +my $i = -1; +for my $test (@tests) { + for my $fn (@{$test->[0]}) { + for my $text (@{$test->[1]}) { + ++$i; + next if defined $index and $index != $i; + + print encode('utf-8', "$fn -- $text\n"); + + (my $text_plain = $text) =~ s/<.*?>//g; + + my $features; + if ($test->[3]) { + $features = { DEFAULT => 0 }; + $features->{$_} = 1 for @{$test->[3]}; + } + + my $s = new_font("testfonts/$fn"); + $s->subset("testfonts/$fn", $text_plain, { features => $features }); + my $path = sprintf '%03d', $i; + $s->write("testoutput/$path.ttf"); + my $old_glyphs = $s->num_glyphs_old; + my $new_glyphs = $s->num_glyphs_new; + my @glyph_names = $s->glyph_names; + $s->release; + + Font::EOTWrapper::convert("testoutput/$path.ttf", "testoutput/$path.eot"); + + my $fragment = < +\@font-face { /* for IE */ + font-family: subsetted-$i; + src: url($path.eot); +} +\@font-face { + font-family: subsetted-$i; + src: url($path.ttf) format("truetype"); +} + +EOF + + for my $size (@{$test->[2]}) { + $fragment .= <$text +
+$text

+EOF + } + + print $out qq{\n\n$fragment#} if not defined $index; + + open my $html, '>', "testoutput/$path.html"; + binmode $html, ':utf8'; + print $html < + +Font test $path + +$fragment +EOF + print $html qq{

}, (join '   ', map "$_", sort @glyph_names), qq{

}; + print $html qq{
}, dump_sizes("testoutput/$path.ttf"), qq{
}; + } + print $out "
\n" if not defined $index; + } +} + +sub dump_sizes { + my ($fn) = @_; + my $font = Font::TTF::Font->open($fn) or die "Failed to open $fn: $!"; + + my $s = 0; + my $out = ''; + for (sort keys %$font) { + next if /^ /; + my $l = $font->{$_}{' LENGTH'}; + $s += $l; + $out .= "$_: $l\n"; + } + $out .= "Total: $s\n"; + return $out; +} diff --git a/font-optimizer/list-features.pl b/font-optimizer/list-features.pl new file mode 100755 index 0000000..3264a48 --- /dev/null +++ b/font-optimizer/list-features.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib 'ext/Font-TTF/lib'; +use Font::TTF::Font; + +use Getopt::Long; + +main(); + +sub help { + print <open($input_file) or die "Error opening $input_file: $!"; + + my %feats; + my @feats; + for my $table (grep defined, $font->{GPOS}, $font->{GSUB}) { + $table->read; + for my $feature (@{$table->{FEATURES}{FEAT_TAGS}}) { + $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'"; + my $tag = $1; + next if $feats{$tag}++; + push @feats, $tag; + } + } + print map "$_\n", @feats; + + $font->release; +} diff --git a/font-optimizer/modify-names.pl b/font-optimizer/modify-names.pl new file mode 100755 index 0000000..3198bee --- /dev/null +++ b/font-optimizer/modify-names.pl @@ -0,0 +1,209 @@ +#!/usr/bin/perl -CA + # use the -CA flag so @ARGV is interpreted as UTF-8 + +use strict; +use warnings; + +binmode STDOUT, ':utf8'; + +use lib 'ext/Font-TTF/lib'; +use Font::TTF::Font; + +my @name_strings = qw( + copyright + family + subfamily + unique-identifier + full-name + version + postscript + trademark + manufacturer + designer + description + vendor-url + designer-url + license + license-url + RESERVED + preferred-family + preferred-subfamily + compatible-full + sample-text + postscript-cid + wws-family + wws-subfamily +); +my %name_strings; +$name_strings{$name_strings[$_]} = $_ for 0..$#name_strings; + +main(); + +sub help { + print <{name}{strings}[$id]; + my $exists = 0; + for my $plat (0..$#$str) { + next unless $str->[$plat]; + for my $enc (0..$#{$str->[$plat]}) { + next unless $str->[$plat][$enc]; + for my $lang (keys %{$str->[$plat][$enc]}) { + next unless exists $str->[$plat][$enc]{$lang}; + my $val = $sub->($str->[$plat][$enc]{$lang}, $plat, $enc, $lang); + $str->[$plat][$enc]{$lang} = $val; + $exists = 1 + } + } + } + if (not $exists) { + warn "Can't find existing name string '$name_strings[$id]' ($id)\n"; + } +} + + +sub json_string { + my ($str) = @_; + $str =~ s/([\\"])/\\$1/g; + $str =~ s/\r/\\r/g; + $str =~ s/\n/\\n/g; + $str =~ s/\t/\\t/g; + $str =~ s/([\x00-\x1f])/sprintf '\u%04X', ord $1/eg; + return qq{"$str"}; +} + +sub print_names { + my ($font) = @_; + my @lines; + for my $nid (0..$#name_strings) { + my $name = $font->{name}->find_name($nid); + if (length $name) { + push @lines, json_string($name_strings[$nid]).': '.json_string($name); + } + } + + print "{\n"; + print join ",\n\n", @lines; + print "\n}\n"; +} + +sub parse_id { + my ($name) = @_; + if ($name =~ /^\d+$/ and $name < @name_strings) { + return int $name; + } + my $id = $name_strings{lc $name}; + return $id if defined $id; + warn "Invalid name string identifier '$name'\n\n"; + help(); +} + +sub main { + my $verbose = 0; + my $print = 0; + my @commands; + + my @args = @ARGV; + my @rest; + while (@args) { + $_ = shift @args; + if ($_ eq '-v' or $_ eq '--verbose') { + $verbose = 1; + } elsif ($_ eq '-p' or $_ eq '--print') { + $print = 1; + push @commands, [ 'print' ]; + } elsif ($_ eq '--set') { + @args >= 2 or help(); + my $id = parse_id(shift @args); + my $val = shift @args; + push @commands, [ 'set', $id, $val ]; + } elsif ($_ eq '--append') { + @args >= 2 or help(); + my $id = parse_id(shift @args); + my $val = shift @args; + push @commands, [ 'append', $id, $val ]; + } elsif ($_ eq '--subst') { + @args >= 3 or help(); + my $id = parse_id(shift @args); + my $val1 = shift @args; + my $val2 = shift @args; + push @commands, [ 'subst', $id, $val1, $val2 ]; + } else { + push @rest, $_; + } + } + + ($print and (@rest == 1 or @rest == 2)) or @rest == 2 or help(); + + my ($input_file, $output_file) = @rest; + + my $font = Font::TTF::Font->open($input_file) or die "Error opening $input_file: $!"; + + $font->{name}->read; + + for my $cmd (@commands) { + if ($cmd->[0] eq 'print') { + print_names($font); + } elsif ($cmd->[0] eq 'set') { + my $id = $cmd->[1]; + modify_name($font, $id, sub { + my ($val, $plat, $enc, $lang) = @_; + print "Setting string $id (platform=$plat encoding=$enc lang=$lang)\n" if $verbose; + return $cmd->[2]; + }); + } elsif ($cmd->[0] eq 'append') { + my $id = $cmd->[1]; + modify_name($font, $id, sub { + my ($val, $plat, $enc, $lang) = @_; + print "Appending to string $id (platform=$plat encoding=$enc lang=$lang)\n" if $verbose; + return $val . $cmd->[2]; + }); + } elsif ($cmd->[0] eq 'subst') { + my $id = $cmd->[1]; + modify_name($font, $id, sub { + my ($val, $plat, $enc, $lang) = @_; + my $pat = quotemeta($cmd->[2]); + my $n = ($val =~ s/$pat/$cmd->[3]/g) || 0; + print "Substituting string $id (platform=$plat encoding=$enc lang=$lang) - $n match(es)\n" if $verbose; + warn "No match found for substitution on string '$name_strings[$id]'\n" if not $n; + return $val; + }); + } else { + die; + } + } + + $font->out($output_file) if $output_file; + + $font->release; +} diff --git a/font-optimizer/obfuscate-font.pl b/font-optimizer/obfuscate-font.pl new file mode 100755 index 0000000..0850426 --- /dev/null +++ b/font-optimizer/obfuscate-font.pl @@ -0,0 +1,114 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib 'ext/Font-TTF/lib'; +use Font::TTF::Font; + +use Getopt::Long; + +main(); + +sub help { + print <{name}{strings}[$id]; + for my $plat (0..$#$str) { + next unless $str->[$plat]; + for my $enc (0..$#{$str->[$plat]}) { + next unless $str->[$plat][$enc]; + for my $lang (keys %{$str->[$plat][$enc]}) { + next unless exists $str->[$plat][$enc]{$lang}; + if ($verbose) { + print "Setting string $_ (plat $plat, enc $enc) to \"$val\"\n"; + } + $str->[$plat][$enc]{$lang} = $val; + } + } + } +} + +sub strip_names { + my ($font, $verbose) = @_; + + print "Stripping names\n" if $verbose; + + $font->{name}->read; + + for (16, 17, 18) { + if ($verbose and $font->{name}{strings}[$_]) { + print "Deleting string $_\n"; + } + $font->{name}{strings}[$_] = undef; + } + + for (1, 3, 5) { + set_name($font, $_, '', $verbose); + } + + for (4, 6) { + set_name($font, $_, '-', $verbose); + } +} + +sub strip_post { + my ($font, $verbose) = @_; + + print "Stripping post table\n" if $verbose; + + # Replace it with the minimum necessary to work in browsers + # (particularly Opera is a bit fussy) + my $data = pack NNnnNNNNN => 0x10000, 0, 0, 0, 0, 0, 0, 0, 0; + $font->{post} = new Font::TTF::Table(dat => $data); +} + +sub main { + my $verbose = 0; + my $all; + my $names; + my $post; + + my $result = GetOptions( + 'verbose' => \$verbose, + 'all' => \$all, + 'names' => \$names, + 'post' => \$post, + ) or help(); + + @ARGV == 2 or help(); + + if (not ($all or $names or $post)) { help(); } + + my ($input_file, $output_file) = @ARGV; + + my $font = Font::TTF::Font->open($input_file) or die "Error opening $input_file: $!"; + + strip_names($font, $verbose) if $all or $names; + strip_post($font, $verbose) if $all or $post; + + $font->out($output_file); + + $font->release; +} diff --git a/font-optimizer/subset.pl b/font-optimizer/subset.pl new file mode 100755 index 0000000..fb2eb95 --- /dev/null +++ b/font-optimizer/subset.pl @@ -0,0 +1,131 @@ +#!/usr/bin/perl -CA + # use the -CA flag so @ARGV is interpreted as UTF-8 + +use strict; +use warnings; + +use lib 'ext/Font-TTF/lib'; +use Font::Subsetter; + +use Getopt::Long; + +main(); + +sub help { + print < \$chars, + 'charsfile=s' => \$charsfile, + 'verbose' => \$verbose, + 'include=s' => \$include, + 'exclude=s' => \$exclude, + 'apply=s' => \$apply, + 'licensesubst=s' => \$license_desc_subst, + ) or help(); + + if (defined $chars and defined $charsfile) { + print "ERROR: Only one of '--chars' and --charsfile' can be specified\n\n"; + help(); + } elsif (defined $chars) { + # just use $chars + } elsif (defined $charsfile) { + open my $f, '<', $charsfile or die "Failed to open $charsfile: $!"; + binmode $f, ':utf8'; + local $/; + $chars = <$f>; + } else { + $chars = 'test'; + } + + @ARGV == 2 or help(); + + my ($input_file, $output_file) = @ARGV; + + + if ($verbose) { + dump_sizes($input_file); + print "Generating subsetted font...\n\n"; + } + + my $features; + if ($include) { + $features = { DEFAULT => 0 }; + $features->{$_} = 1 for split /,/, $include; + } elsif ($exclude) { + $features = { DEFAULT => 1 }; + $features->{$_} = 0 for split /,/, $exclude; + } + + my $fold_features; + if ($apply) { + $fold_features = [ split /,/, $apply ]; + } + + my $subsetter = new Font::Subsetter(); + $subsetter->subset($input_file, $chars, { + features => $features, + fold_features => $fold_features, + license_desc_subst => $license_desc_subst, + }); + $subsetter->write($output_file); + + if ($verbose) { + print "\n"; + print "Features:\n "; + print join ' ', $subsetter->feature_status(); + print "\n\n"; + print "Included glyphs:\n "; + print join ' ', $subsetter->glyph_names(); + print "\n\n"; + dump_sizes($output_file); + } + + $subsetter->release(); +} + +sub dump_sizes { + my ($filename) = @_; + my $font = Font::TTF::Font->open($filename) or die "Failed to open $filename: $!"; + print "TTF table sizes:\n"; + my $s = 0; + for (sort keys %$font) { + next if /^ /; + my $l = $font->{$_}{' LENGTH'}; + $s += $l; + print " $_: $l\n"; + } + print "Total size: $s bytes\n\n"; + $font->release(); +} diff --git a/font-optimizer/t/subsetter.pl b/font-optimizer/t/subsetter.pl new file mode 100644 index 0000000..5cb15df --- /dev/null +++ b/font-optimizer/t/subsetter.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More qw(no_plan); + +use lib 'ext/Font-TTF/lib'; +use Font::Subsetter; +use Unicode::Normalize; + +# Test that we include characters needed for strings converted to NFC +for my $str ( + "i", + "\xec", + "i\x{0300}", + "\x{0300}i", + "\x{03b9}\x{0308}\x{0301}", # iota, combining diaeresis, combining acute + "s\x{0323}\x{0307}", # s, combining dot below, combining dot above + "s\x{0307}\x{0323}", # s, combining dot above, combining dot below + "\x{1e61}\x{0323}", # s with dot above, combining dot below + "\x{1e63}\x{0307}", # s with dot below, combining dot above + "\x{212b}", # angstrom +) { + my $subsetter = new Font::Subsetter; + my %chars = $subsetter->expand_wanted_chars($str); + for (map ord, split //, $str) { + ok($chars{$_}, "char ".(sprintf '%04x', $_)." in string '".(join ' ', map { sprintf '%04x', $_ } unpack 'U*', $str)."'"); + } + for (map ord, split //, Unicode::Normalize::NFC($str)) { + ok($chars{$_}, "NFC char ".(sprintf '%04x', $_)." in string '".(join ' ', map { sprintf '%04x', $_ } unpack 'U*', $str)."'"); + } +} + +# Test that spurious characters aren't included +for my $str ( + "a\xec", +) { + my $subsetter = new Font::Subsetter; + my %chars = $subsetter->expand_wanted_chars($str); + my %exp; + $exp{$_} = 1 for map ord, split //, $str; + $exp{$_} = 1 for map ord, split //, Unicode::Normalize::NFC($str); + for (sort keys %chars) { + ok($exp{$_}, "expected char ".(sprintf '%04x', $_)." from string '".(join ' ', map { sprintf '%04x', $_ } unpack 'U*', $str)."'"); + } +} diff --git a/librarian/epub.py b/librarian/epub.py index 84a745f..2584029 100644 --- a/librarian/epub.py +++ b/librarian/epub.py @@ -7,9 +7,12 @@ from __future__ import with_statement import os import os.path +import subprocess from copy import deepcopy from lxml import etree import zipfile +from tempfile import mkdtemp +from shutil import rmtree import sys sys.path.append('..') # for running from working copy @@ -206,6 +209,15 @@ class TOC(object): return counter +def used_chars(element): + """ Lists characters used in an ETree Element """ + print (element.text or '') + (element.tail or '') + chars = set((element.text or '') + (element.tail or '')) + for child in element: + chars = chars.union(used_chars(child)) + return chars + + def chop(main_text): """ divide main content of the XML file into chunks """ @@ -231,7 +243,7 @@ def chop(main_text): def transform_chunk(chunk_xml, chunk_no, annotations): - """ transforms one chunk, returns a HTML string and a TOC object """ + """ transforms one chunk, returns a HTML string, a TOC object and a set of used characters """ toc = TOC() for element in chunk_xml[0]: @@ -242,8 +254,10 @@ def transform_chunk(chunk_xml, chunk_no, annotations): element.set('sub', str(subnumber)) find_annotations(annotations, chunk_xml, chunk_no) replace_by_verse(chunk_xml) - output_html = etree.tostring(xslt(chunk_xml, res('xsltScheme.xsl')), pretty_print=True) - return output_html, toc + html_tree = xslt(chunk_xml, res('xsltScheme.xsl')) + chars = used_chars(html_tree.getroot()) + output_html = etree.tostring(html_tree, pretty_print=True) + return output_html, toc, chars def transform(provider, slug, output_file=None, output_dir=None): @@ -262,14 +276,19 @@ def transform(provider, slug, output_file=None, output_dir=None): # every input file will have a TOC entry, # pointing to starting chunk toc = TOC(node_name(input_xml.find('.//'+DCNS('title'))), chunk_counter) + chars = set() if first: # write book title page + html_tree = xslt(input_xml, res('xsltTitle.xsl')) + chars = used_chars(html_tree.getroot()) zip.writestr('OPS/title.html', - etree.tostring(xslt(input_xml, res('xsltTitle.xsl')), pretty_print=True)) + etree.tostring(html_tree, pretty_print=True)) elif children: # write title page for every parent + html_tree = xslt(input_xml, res('xsltChunkTitle.xsl')) + chars = used_chars(html_tree.getroot()) zip.writestr('OPS/part%d.html' % chunk_counter, - etree.tostring(xslt(input_xml, res('xsltChunkTitle.xsl')), pretty_print=True)) + etree.tostring(html_tree, pretty_print=True)) add_to_manifest(manifest, chunk_counter) add_to_spine(spine, chunk_counter) chunk_counter += 1 @@ -287,8 +306,9 @@ def transform(provider, slug, output_file=None, output_dir=None): replace_characters(main_text) for chunk_xml in chop(main_text): - chunk_html, chunk_toc = transform_chunk(chunk_xml, chunk_counter, annotations) + chunk_html, chunk_toc, chunk_chars = transform_chunk(chunk_xml, chunk_counter, annotations) toc.extend(chunk_toc) + chars = chars.union(chunk_chars) zip.writestr('OPS/part%d.html' % chunk_counter, chunk_html) add_to_manifest(manifest, chunk_counter) add_to_spine(spine, chunk_counter) @@ -297,10 +317,11 @@ def transform(provider, slug, output_file=None, output_dir=None): if children: for child in children: child_xml = etree.parse(provider.by_uri(child)) - child_toc, chunk_counter = transform_file(child_xml, chunk_counter, first=False) + child_toc, chunk_counter, chunk_chars = transform_file(child_xml, chunk_counter, first=False) toc.append(child_toc) + chars = chars.union(chunk_chars) - return toc, chunk_counter + return toc, chunk_counter, chars # read metadata from the first file input_xml = etree.parse(provider[slug]) @@ -352,7 +373,8 @@ def transform(provider, slug, output_file=None, output_dir=None): '') nav_map = toc_file[-1] - toc, chunk_counter = transform_file(input_xml) + toc, chunk_counter, chars = transform_file(input_xml) + if not toc.children: toc.add(u"Początek utworu", 1) toc_counter = toc.write_to_xml(nav_map, 2) @@ -367,8 +389,21 @@ def transform(provider, slug, output_file=None, output_dir=None): spine.append(etree.fromstring( '')) replace_by_verse(annotations) + html_tree = xslt(annotations, res("xsltAnnotations.xsl")) + chars = chars.union(used_chars(html_tree.getroot())) zip.writestr('OPS/annotations.html', etree.tostring( - xslt(annotations, res("xsltAnnotations.xsl")), pretty_print=True)) + html_tree, pretty_print=True)) + + # strip fonts + tmpdir = mkdtemp('-librarian-epub') + cwd = os.getcwd() + + os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../font-optimizer')) + for fname in 'DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf', 'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf': + subprocess.check_call(['./subset.pl', '--chars', ''.join(chars), res('../fonts/' + fname), os.path.join(tmpdir, fname)]) + zip.write(os.path.join(tmpdir, fname), os.path.join('OPS', fname)) + rmtree(tmpdir) + os.chdir(cwd) zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True)) contents = [] diff --git a/librarian/epub/style.css b/librarian/epub/style.css index f64978e..d05a686 100644 --- a/librarian/epub/style.css +++ b/librarian/epub/style.css @@ -1,7 +1,33 @@ +@font-face { + font-family: "DejaVu Serif"; + font-weight: normal; + font-style: normal; + src: url(DejaVuSerif.ttf); +} +@font-face { + font-family: "DejaVu Serif"; + font-weight: bold; + font-style: normal; + src: url(DejaVuSerif-Bold.ttf); +} +@font-face { + font-family: "DejaVu Serif"; + font-weight: normal; + font-style: italic; + src: url(DejaVuSerif-Italic.ttf); +} +@font-face { + font-family: "DejaVu Serif"; + font-weight: bold; + font-style: italic; + src: url(DejaVuSerif-BoldItalic.ttf); +} + + body { font-size: 12pt; - font: Georgia, "Times New Roman" , serif; + font-family: "DejaVu Serif", serif; line-height: 1.5em; margin: 0; } diff --git a/librarian/fonts/DejaVuSerif-Bold.ttf b/librarian/fonts/DejaVuSerif-Bold.ttf new file mode 100644 index 0000000..03e1f13 Binary files /dev/null and b/librarian/fonts/DejaVuSerif-Bold.ttf differ diff --git a/librarian/fonts/DejaVuSerif-BoldItalic.ttf b/librarian/fonts/DejaVuSerif-BoldItalic.ttf new file mode 100644 index 0000000..1a35350 Binary files /dev/null and b/librarian/fonts/DejaVuSerif-BoldItalic.ttf differ diff --git a/librarian/fonts/DejaVuSerif-Italic.ttf b/librarian/fonts/DejaVuSerif-Italic.ttf new file mode 100644 index 0000000..e26c4ac Binary files /dev/null and b/librarian/fonts/DejaVuSerif-Italic.ttf differ diff --git a/librarian/fonts/DejaVuSerif.ttf b/librarian/fonts/DejaVuSerif.ttf new file mode 100644 index 0000000..38d9aa0 Binary files /dev/null and b/librarian/fonts/DejaVuSerif.ttf differ