From 5d935b958385e5f25e48a604d88a7cc495f76a5e Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 16 2020 12:29:39 +0000 Subject: perl-Font-TTF-1.06 base --- diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..fb5b49e --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,7 @@ +Bob Hallissy +Alan Ward +David Raymond +Jon Coblentz +Jonathan Kew +NIIBE Yutaka +Nicolas Spalinger \ No newline at end of file diff --git a/Changes b/Changes new file mode 100644 index 0000000..cd3826f --- /dev/null +++ b/Changes @@ -0,0 +1,91 @@ +1.06 2016-08-17 + +* Source repo moved from Subversion to Github +* OpenType script and lang tags updated from ISO/IEC 14496-22:2015, draft amendment 2 +* Various POD improvements +* Add deepcopy mode to Dumper::ttfdump() +* Bug fixes +* Wasn't installing on Windows Perl 5.22 and up +* Reading mark-to-ligature lookups would crash if anchors were omitted +* Incorrect extension lookup structure +* Multiple fixes in Silf table processing +* rt.cpan.org #106562 Uninitialized value warnings +* rt.cpan.org #106816 spelling errors in manpage + +1.05 2015-01-26 r1069 + +* Add support for GSUB Type 8 Reverse-chaining substitution +* OpenType script/lang/feature tags now based on ISO/IEC 14496-22 +* Remove deprecated GDL_old.pm +* +* Bug fixes: +* Fix rt.cpan.org 92150, 93597 +* Force 0xFFFF sentry to be in a segment by itself in format 4 cmap subtables +* Less aggressive cmap format 4 optimization to eliminate USV holes +* Fix various issues reading WOFF-compressed font tables +* Fix reading DSIG + +1.04 2014-01-09 r994 + +* Bug fixes: +* Fix rt.cpan.org 80671, 80672; simplify fix for #42553 per OP +* Shared tables in TTC weren't working +* Quiet a cleanup warning in TTC processing +* Update licensing info for test fonts to OFL + +1.03 2013-11-10 r969 + +* Add $t->minsize() to all tables for assisting with font subsetting +* Add deduping to Name table writing +* Add OS/2 table method to guess at Unicode and Codepage range bits. +* Add support for cmap format 13 subtables +* Expunge notion of 'dflt' language tag from Ttopen.pm +* Bug fixes + +1.02 2012-08-30 r862 + +* Fix typo in Useall to get case right + +1.01 2012-08-30 r859 + +* Add IO::String prerequisite to make BSD and other testing environments happy + +1.00 2012-08-28 r857 + +* Major change to glyph creation/editing -- now utilizes ' isDirty' flag +* Add support for: +* V4 OS/2 table +* Reading WOFF fonts +* MarkFilterSets in GDEF +* Feature parameters (for cvxx, ssxx and size features) +* Additional Graphite tables (Silf, Glac, and Gloc) +* Updated MS Lang IDs to Dec 2011 +* Finish implementation of coverage table sorting +* Copyright and licensing updated to CPAN requirements +* Bug fixes + +0.48 2010-12-14 r692 + +* Ensure coverage tables are properly sorted +* Additional OT tags (from OpenType 1.6) +* Bug fixes + +0.47 2009-08-10 r649 + +* Add support for Graphite Sill table +* Handle kerning tables version > 0 +* Bug fixes + +0.46 2009-01-26 r577 + +* Bug fixes +* Change Copyright to Artistic License 2.0 + +0.45 2008-06-11 r527 + +* 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. + +Some previous changes documented in lib/Font/TTF/Changes_old.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cbda892 --- /dev/null +++ b/LICENSE @@ -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/MANIFEST b/MANIFEST new file mode 100644 index 0000000..6fffffd --- /dev/null +++ b/MANIFEST @@ -0,0 +1,96 @@ +buildutils/convert_tags.pl +buildutils/convert_tags_from_html.pl +Changes +CONTRIBUTORS +lib/Font/TTF.pm +lib/Font/TTF/AATKern.pm +lib/Font/TTF/AATutils.pm +lib/Font/TTF/Anchor.pm +lib/Font/TTF/Bsln.pm +lib/Font/TTF/Changes_old.txt +lib/Font/TTF/Cmap.pm +lib/Font/TTF/Coverage.pm +lib/Font/TTF/Cvt_.pm +lib/Font/TTF/Delta.pm +lib/Font/TTF/DSIG.pm +lib/Font/TTF/Dumper.pm +lib/Font/TTF/EBDT.pm +lib/Font/TTF/EBLC.pm +lib/Font/TTF/Fdsc.pm +lib/Font/TTF/Feat.pm +lib/Font/TTF/Features/Cvar.pm +lib/Font/TTF/Features/Size.pm +lib/Font/TTF/Features/Sset.pm +lib/Font/TTF/Fmtx.pm +lib/Font/TTF/Font.pm +lib/Font/TTF/Fpgm.pm +lib/Font/TTF/GDEF.pm +lib/Font/TTF/Glat.pm +lib/Font/TTF/Gloc.pm +lib/Font/TTF/Glyf.pm +lib/Font/TTF/Glyph.pm +lib/Font/TTF/GPOS.pm +lib/Font/TTF/GrFeat.pm +lib/Font/TTF/GSUB.pm +lib/Font/TTF/Hdmx.pm +lib/Font/TTF/Head.pm +lib/Font/TTF/Hhea.pm +lib/Font/TTF/Hmtx.pm +lib/Font/TTF/Kern.pm +lib/Font/TTF/Kern/ClassArray.pm +lib/Font/TTF/Kern/CompactClassArray.pm +lib/Font/TTF/Kern/OrderedList.pm +lib/Font/TTF/Kern/StateTable.pm +lib/Font/TTF/Kern/Subtable.pm +lib/Font/TTF/Loca.pm +lib/Font/TTF/LTSH.pm +lib/Font/TTF/Manual.pod +lib/Font/TTF/Maxp.pm +lib/Font/TTF/Mort.pm +lib/Font/TTF/Mort/Chain.pm +lib/Font/TTF/Mort/Contextual.pm +lib/Font/TTF/Mort/Insertion.pm +lib/Font/TTF/Mort/Ligature.pm +lib/Font/TTF/Mort/Noncontextual.pm +lib/Font/TTF/Mort/Rearrangement.pm +lib/Font/TTF/Mort/Subtable.pm +lib/Font/TTF/Name.pm +lib/Font/TTF/OldCmap.pm +lib/Font/TTF/OldMort.pm +lib/Font/TTF/OS_2.pm +lib/Font/TTF/OTTags.pm +lib/Font/TTF/PCLT.pm +lib/Font/TTF/Post.pm +lib/Font/TTF/Prep.pm +lib/Font/TTF/Prop.pm +lib/Font/TTF/PSNames.pm +lib/Font/TTF/Segarr.pm +lib/Font/TTF/Silf.pm +lib/Font/TTF/Sill.pm +lib/Font/TTF/Table.pm +lib/Font/TTF/Ttc.pm +lib/Font/TTF/Ttopen.pm +lib/Font/TTF/Useall.pm +lib/Font/TTF/Utils.pm +lib/Font/TTF/Vhea.pm +lib/Font/TTF/Vmtx.pm +lib/Font/TTF/Win32.pm +lib/Font/TTF/Woff.pm +lib/Font/TTF/Woff/MetaData.pm +lib/Font/TTF/Woff/PrivateData.pm +lib/Font/TTF/XMLparse.pm +lib/ttfmod.pl +LICENSE +Makefile.PL +MANIFEST This list of files +MANIFEST.SKIP +README.TXT +t/changes.t +t/OFL.txt +t/tags.t +t/testfont.ttf +t/testfont.woff +t/ttfcopy.t +TODO +META.yml Module YAML meta-data (added by MakeMaker) +META.json Module JSON meta-data (added by MakeMaker) diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 0000000..f4fa04c --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,35 @@ +blib/ +\.\$\$\$ +\.tmp +\.temp +\.bak +\.patch +CVS/ +\.tar +\.tgz +\.old +misc/ +Build/ +exes/ +\.cvsignore +^# +(^|/)\.git +\.svn/ +Makefile$ +\.par$ +-stamp$ +debian/ +pm_to_blib +\~$ +dev/ +build/ +^build(?!utils) +dists/ +dest-deb/ +^libfont- +description-pak +^doc +^doc-pak +\.swp +^\.hg/ +^MYMETA\..*$ diff --git a/META.json b/META.json new file mode 100644 index 0000000..ca1645e --- /dev/null +++ b/META.json @@ -0,0 +1,31 @@ +{ + "abstract" : "TTF font support for Perl", + "author" : [ + "martin_hosken@sil.org" + ], + "dynamic_config" : 1, + "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921", + "license" : [ + "artistic_2" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Font-TTF", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "release_status" : "stable", + "resources" : { + "repository" : { + "type" : "git", + "url" : "https://github.com/silnrsi/font-ttf.git", + "web" : "https://github.com/silnrsi/font-ttf" + } + }, + "version" : "1.06" +} diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..22025b7 --- /dev/null +++ b/META.yml @@ -0,0 +1,19 @@ +--- +abstract: 'TTF font support for Perl' +author: + - martin_hosken@sil.org +build_requires: {} +dynamic_config: 1 +generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921' +license: artistic_2 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: 1.4 +name: Font-TTF +no_index: + directory: + - t + - inc +resources: + repository: https://github.com/silnrsi/font-ttf.git +version: 1.06 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..ef783a4 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,113 @@ +use strict; +use ExtUtils::MakeMaker; +use Getopt::Std; + +our ($opt_d, $opt_r, $opt_v); +getopts('d:rv:'); + +my %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; +} + +my (@extras, @theselibs); + +@theselibs = (grep {-f } glob("lib/Font/TTF/*"), "lib/Font/TTF.pm"); + +# incantation to enable MY::pm_to_blib later on +if ($^O eq 'MSWin32') +{ + @extras = ('dist' => { 'TO_UNIX' => 'perl -Mtounix -e "tounix(\"$(DISTVNAME)\")"' }); +} + +if ($^O eq 'linux') +{ + + *MY::postamble = sub + { + 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/\s+$//o; + if ($svn and $svn ne "exported") + { + $svn =~ s/[0-9]*://og; + } + else + { + $svn = `hg identify -n`; + $svn =~ s/\+?\s+$//o; + } + $package =~ s/::/-/g; + $package = "lib${package}-perl"; + $pversion .= "+$svn" unless ($opt_r); + $fpackage = "$package-$pversion"; + + $res = <<"EOT"; +debsrc: dist + rm -fr $self->{'DISTVNAME'} + rm -fr $package + rm -fr $fpackage + tar xvzf $self->{'DISTVNAME'}.tar.gz + cp -r $self->{'DISTVNAME'} $package + tar cvzf ${package}_${pversion}.orig.tar.gz $package + cp -r $package $fpackage + cp -a debian $fpackage + cd $fpackage && dch -v $pversion "interim dev release" && dpkg-buildpackage -S + +# make deb builds an interim deb from svn source for release +deb: deb-base +EOT + + foreach my $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; + }; + +} + +my %makeinfo = ( + NAME => 'Font::TTF', + VERSION_FROM => 'lib/Font/TTF.pm', +# 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", + LICENSE => "artistic_2", + PREREQ_PM => { + 'IO::String' => 0 + }, + META_MERGE => { + 'meta-spec' => { version => 2 }, + resources => { + repository => { + type => 'git', + url => 'https://github.com/silnrsi/font-ttf.git', + web => 'https://github.com/silnrsi/font-ttf', + } + } + }, + @extras + ); + +WriteMakefile(%makeinfo); + diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..961e702 --- /dev/null +++ b/README.TXT @@ -0,0 +1,97 @@ +=head1 INTRODUCTION + +Perl module for TrueType/OpenType 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 + +Windows users should use dmake instead of make. + +=head1 CHANGES + +See Changes for an overview of recent changes. + +=head2 Future Changes + +I do not anticipate any more restructuring changes (but reserve the right to do so). + +=head1 AUTHOR + +Martin Hosken L. +(see CONTRIBUTORS for other authors). + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +The fonts in the test suite are released under the SIL Open Font License 1.1, see t/OFL.txt. + +=cut diff --git a/TODO b/TODO new file mode 100644 index 0000000..472a58c --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +* Currently no support for format 1 Name tables + +* 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/buildutils/convert_tags.pl b/buildutils/convert_tags.pl new file mode 100644 index 0000000..6aaa953 --- /dev/null +++ b/buildutils/convert_tags.pl @@ -0,0 +1,96 @@ +# This utility script interprets a plain-text file that typically is generated by +# cut&paste from the tables contained in the three reference pages of the OpenType Spec: +# Script tags: http://www.microsoft.com/typography/otspec/scripttags.htm +# Langauge tags: http://www.microsoft.com/typography/otspec/languagetags.htm +# Feature tags: http://www.microsoft.com/typography/otspec/featurelist.htm +# Alternatively, input can be VOLT's tags.txt +# Output (to stdout) is in perl syntax for the hash initialization, e.g.: +# "Arabic" => "arab", +# "Armenian" => "armn", +# This output can the be transferred to Tags.pm +# +# Bob Hallissy 2008-01-31 + +use strict; + +my $which; +my %iso639list; + +while (<>) +{ + s/\s+$//o; # trim trailing whitespace (including line ending). + if (/^\s*$/o) + { + print "\n"; # Just print empty lines + next; + } + + s/^\s+//o; # trim leading whitespace + + if (/^"(SCRIPT|LANGUAGE|FEATURE)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"/) + { + # VOLT's tags.txt + my ($type, $name, $tag) = ($1, $2, $3); + print "\n\n//$type\n\n" if $type != $which; + $which = $type; + print " \"$name\" => '$tag',\n"; + } + + elsif (/^'(.{1,4})'\s+(.*)$/o) + { + # Special reverse formatting for feature names + my ($name, $tag) = ($2, $1); + $tag .= " " x (4 - length($tag)); # pad tag + print " \"$name\" => '$tag',\n"; + } + + elsif (/^'(.{1,4})-(.{1,4})'\s+(.*)$/o) + { + # Special reverse formatting for feature names like 'cv01-cv99' + my ($name, $tag1, $tag2) = ($3, $1, $2); + for my $tag ($tag1 .. $tag2) + { + $tag =~ /(\d+)$/; + my $index = $1; + $tag .= " " x (4 - length($tag)); # pad tag + print " \"$name $index\" => '$tag',\n"; + } + } + elsif (/^([^\t]*)\t([\w]{2,4})(?: +(\([^\t]*\)))?(?:\t(.*))?$/o) + { + # Script and language names + my ($name, $tag, $extra, $iso639list) = ($1, $2, $3, $4); + $name =~ s/\s*\(Standard\)\s*//oi; # Remove "(Standard)" from French and German entries + $name .= " $extra" if defined $extra; # Dhivehi has "(deprecated)" after the "DHV " tag -- move it to name. + $tag .= " " x (4 - length($tag)); # pad tag + print " \"$name\" => '$tag',\n"; + if (defined $iso639list) + { + $iso639list =~ s/,//g; + $iso639list{$tag} = $iso639list # Save for later + } + } + else + { + print "UNEXPECTED DATA: '$_'\n"; + } +} + +print "\n"; +foreach my $tag (sort keys(%iso639list)) +{ + printf " '$tag' => '$iso639list{$tag}',\n"; +} + +=head1 AUTHOR + +Bob Hallissy L. + +=head1 LICENSING + +Copyright (c) 1998-2014, SIL International (http://www.sil.org) + +This script is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut diff --git a/buildutils/convert_tags_from_html.pl b/buildutils/convert_tags_from_html.pl new file mode 100644 index 0000000..57b275f --- /dev/null +++ b/buildutils/convert_tags_from_html.pl @@ -0,0 +1,189 @@ +# This utility script interprets HTML files from MS's version of the OT spec +# to generate tags script for OTTags.pm +# The three files processed are scripttags.htm, featurelist.htm, and languagetags.htm +# These files are assumed to be in "C:\Reference\Microsoft\OpenType 1.6" unless +# a folder name is supplied as the sole argument on the command name. +# +# Output (to stdout) is in perl syntax for the hash initialization, e.g.: +# "Arabic" => "arab", +# "Armenian" => "armn", +# This output can the be transferred to Tags.pm +# +# Bob Hallissy 2010-09-16 + +use strict; + +use File::Spec::Functions; +use HTML::Parser; + +my $dir = ($ARGV[0] ? $ARGV[0] : "/Reference/Microsoft/OpenType 1.6"); + +die "Cannot locate .HTM files in '$dir'.\n" unless ( + -f catfile($dir, "languagetags.htm") and + -f catfile($dir, "featurelist.htm") and + -f catfile($dir, "scripttags.htm") + ); + +my $filename; +my $which; # either LANGUAGE, FEATURE, or SCRIPT + +my $curText; # Text accumulator. +my $curCol; # Which column of the table we're processing -- reset to 0 by +my $td; # ref to array of text from a containing + +my (%tttags, %iso639list); # Accumulated data + +sub text +{ + my ($self, $text) = @_; + $curText .= $text; +} + +sub start +{ + my ($self, $tagname) = @_; + $curText = ''; + if ($tagname eq 'tr') + { + $curCol = 0; + undef $td; + } +} + +sub end +{ + my ($self, $tagname) = @_; + if ($tagname eq 'th') + { + if ($curCol++ == 0) + { + # confirm which table we have: + $curText =~ /^(\S+)/; + $which = uc($1); + die "Unexpected table header '$curText' in '$filename'./n" unless $filename =~ /^${which}/i; + } + } + elsif ($tagname eq 'td') + { + # trip leading and trailing whitespace and quotes: + $curText =~ s/[\s']+$//; + $curText =~ s/^[\s']+//; + # fold dashes to hyphen-minus: + $curText =~ s/[\x{2010}-\x{201F}]/-/g; + $td->[$curCol++] = $curText; + } + elsif ($tagname eq 'tr' && defined $td) + { + # Ok -- got a complete row of data to work with + + # Feature table is reversed with tag being first: + $td = [ reverse @{$td} ] if $which eq "FEATURE"; + + # So now + # $td->[0] is the name (of script, language, or feature(s)) + # $td->[1] is the tag name plus possibly extra stuff + # $td->[3], if exists, is comma-separated iso639 language codes + + my ($name, $tag, $iso639list) = @{$td}; + + if ($tag =~ /^(\S+)\s+(.+)$/) + { + # Extra text after the tag name, such as Dhivehi has "(deprecated)" after the "DHV " tag -- move it to name. + $tag = $1; + $name .= " $2"; + } + + if ($tag =~ /^(.{1,4})-(.{1,4})$/) + { + # Special handling for feature names like 'cv01-cv99' + my ($tag1, $tag2) = ($1, $2); + for my $tag ($tag1 .. $tag2) + { + $tag =~ /(\d+)$/; + my $index = $1; + $tag .= ' ' x (4 - length($tag)); # pad tag + $tttags{$which}{"$name $index"} = "$tag"; + } + } + else + { + # Normal tags + # Pad the tag: + $tag .= ' ' x (4 - length($tag)); + $tttags{$which}{$name} = $tag; + } + + if (defined $iso639list) + { + $iso639list =~ s/[, ]+/ /g; # Strip commas, leaving space. + $iso639list{$tag} = $iso639list # Save for later + } + } +} + +sub VerifyAnsi +{ + my $str = shift; + my $strA = $str; + $strA =~ s/[^\x00-\x7F]/?/g; + print STDERR "Wide data:\n$strA\n$str\n" if $str ne $strA; +} + +my $p = HTML::Parser->new( + api_version => 3, + start_h => [\&start, 'self,tagname'], + end_h => [\&end, 'self,tagname'], + text_h => [\&text, 'self,text'], + report_tags => [qw(table th tr td)], + ); + +foreach (qw (scripttags.htm languagetags.htm featurelist.htm)) +{ + $filename = $_; + my $fh; + open($fh, "<:utf8", catfile($dir, $filename)) || die "cannot open '$filename': $!/n"; + $p->parse_file($fh); + close $fh; +} + +print < {\n"; + # Alpha order by name (not tag) + foreach my $name (sort keys (%{$tttags{$which}})) + { + VerifyAnsi "$name => $tttags{$which}{$name}"; + print " \"$name\" => '$tttags{$which}{$name}',\n"; + } + print " },\n\n"; +} +print ");\n\n"; + +print "\%iso639 = (\n"; +foreach my $tag (sort keys(%iso639list)) +{ + VerifyAnsi "$tag => $iso639list{$tag}"; + printf " '$tag' => '$iso639list{$tag}',\n"; +} +print ");\n"; + +=head1 AUTHOR + +Bob Hallissy L. + +=head1 LICENSING + +Copyright (c) 1998-2014, SIL International (http://www.sil.org) + +This script is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut + diff --git a/lib/Font/TTF.pm b/lib/Font/TTF.pm new file mode 100644 index 0000000..5613ed7 --- /dev/null +++ b/lib/Font/TTF.pm @@ -0,0 +1,59 @@ +package Font::TTF; + +$VERSION = '1.06'; # RMH 02-Aug-2016 Bug fixes; updated OT tags; +# $VERSION = '1.05'; # MJPH 19-Jan-2015 Bug fixes; updated OT tags; GSUB Lookup Type 8 support +# $VERSION = '1.04'; # MJPH 8-Jan-2014 License, POD, and perl -w tidying; bug fixes +# $VERSION = '1.03'; # MJPH 5-Sep-2013 Add $t->minsize() +# $VERSION = '1.02'; # MJPH 30-Aug-2012 Fix case typo in Useall +# $VERSION = '1.01'; # MJPH 30-Aug-2012 add IO::String prerequisite +# $VERSION = '1.00'; # MJPH 21-Aug-2012 OS/2, OT & Graphite improvements; bug fixes +# $VERSION = '0.48'; # MJPH 15-DEC-2010 Bug fixes +# $VERSION = '0.47'; # MJPH 7-AUG-2009 Minor bug fix in Name.pm +# $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. + +=head1 AUTHOR + +Martin Hosken L. +(see CONTRIBUTORS for other authors). + +Repository available at L + +=head1 HISTORY + +See F file for a change log. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +The fonts in the test suite are released under the Open Font License 1.1, see F. + + +=head1 SEE ALSO + +L + +=cut diff --git a/lib/Font/TTF/AATKern.pm b/lib/Font/TTF/AATKern.pm new file mode 100644 index 0000000..f75e81c --- /dev/null +++ b/lib/Font/TTF/AATKern.pm @@ -0,0 +1,150 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/AATutils.pm b/lib/Font/TTF/AATutils.pm new file mode 100644 index 0000000..544dc93 --- /dev/null +++ b/lib/Font/TTF/AATutils.pm @@ -0,0 +1,712 @@ +package Font::TTF::AATutils; + +=head1 NAME + +Font::TTF::AATutils - Utility functions for AAT tables + +=cut + +use strict; +use vars qw(@ISA @EXPORT); +require Exporter; + +use Font::TTF::Utils; +use Font::TTF::Segarr; +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; + +=head1 AUTHOR + +Jonathan Kew L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Anchor.pm b/lib/Font/TTF/Anchor.pm new file mode 100644 index 0000000..fdacb9c --- /dev/null +++ b/lib/Font/TTF/Anchor.pm @@ -0,0 +1,224 @@ +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; +} + +1; + + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut diff --git a/lib/Font/TTF/Bsln.pm b/lib/Font/TTF/Bsln.pm new file mode 100644 index 0000000..30c42b7 --- /dev/null +++ b/lib/Font/TTF/Bsln.pm @@ -0,0 +1,176 @@ +package Font::TTF::Bsln; + +=head1 NAME + +Font::TTF::Bsln - Baseline table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over + +=item version + +=item xformat + +=item defaultBaseline + +=item deltas + +=item stdGlyph + +=item ctlPoints + +=item lookupFormat + +=item lookup + +=back + +=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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Changes_old.txt b/lib/Font/TTF/Changes_old.txt new file mode 100644 index 0000000..7c939f4 --- /dev/null +++ b/lib/Font/TTF/Changes_old.txt @@ -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/lib/Font/TTF/Cmap.pm b/lib/Font/TTF/Cmap.pm new file mode 100644 index 0000000..3d8c3cf --- /dev/null +++ b/lib/Font/TTF/Cmap.pm @@ -0,0 +1,746 @@ +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 preceded 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]) + +Each subtable also has its own instance variables which are, again, not +preceded 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 + +=back + +The following cmap options are controlled by instance variables that start with a space: + +=over 4 + +=item allowholes + +By default, when generating format 4 cmap subtables character codes that point to glyph zero +(normally called .notdef) are not included in the subtable. In some cases including some of these +character codes can result in a smaller format 4 subtable. To enable this behavior, set allowholes +to non-zero. + +=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, $keepzeros) = @_; + $self->SUPER::read or return $self; + + my ($dat, $i, $j, $k, $id, @ids, $s); + my ($start, $end, $range, $delta, $form, $len, $num, $ver, $sg); + my ($fh) = $self->{' INFILE'}; + + $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 || $keepzeros); + } + } + } elsif ($form == 8 || $form == 12 || $form == 13) + { + $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} = $form == 13 ? $sg : $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; + return $s 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", map {defined $_ ? $_ : 0} @{$s->{'val'}}{0 .. 255})); + } elsif ($s->{'Format'} == 6) + { + $fh->print(pack("n2", $keys[0], $keys[-1] - $keys[0] + 1)); + $fh->print(pack("n*", map {defined $_ ? $_ : 0} @{$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 (@starts, @ends, @deltas, @range); + + # There appears to be a bug in Windows that requires the final 0xFFFF (sentry) + # to be in a segment by itself -- otherwise Windows 7 and 8 (at least) won't install + # or preview the font, complaining that it doesn't appear to be a valid font. + # Therefore we can't just add 0XFFFF to the USV list as we used to do: + # push(@keys, 0xFFFF) unless ($keys[-1] == 0xFFFF); + # Instead, for now *remove* 0xFFFF from the USV list, and add a segement + # for it after all the other segments are computed. + pop @keys if $keys[-1] == 0xFFFF; + + # Step 1: divide into maximal length idDelta runs + + my ($prevUSV, $prevgid); + for ($j = 0; $j <= $#keys; $j++) + { + my $u = $keys[$j]; + my $g = $s->{'val'}{$u}; + if ($j == 0 || $u != $prevUSV+1 || $g != $prevgid+1) + { + push @ends, $prevUSV unless $j == 0; + push @starts, $u; + push @range, 0; + } + $prevUSV = $u; + $prevgid = $g; + } + push @ends, $prevUSV; + + # Step 2: find each macro-range + + my ($start, $end); # Start and end of macro-range + for ($start = 0; $start < $#starts; $start++) + { + next if $ends[$start] - $starts[$start] > 7; # if count > 8, we always treat this as a run unto itself + for ($end = $start+1; $end <= $#starts; $end++) + { + last if $starts[$end] - $ends[$end-1] > ($self->{' allowholes'} ? 5 : 1) + || $ends[$end] - $starts[$end] > 7; # gap > 4 or count > 8 so $end is beyond end of macro-range + } + $end--; #Ending index of this macro-range + + # Step 3: optimize this macro-range (from $start through $end) + L1: for ($j = $start; $j < $end; ) + { + my $size1 = ($range[$j] ? 8 + 2 * ($ends[$j] - $starts[$j] + 1) : 8); # size of first range (which may now be idRange type) + for (my $k = $j+1; $k <= $end; $k++) + { + if (8 + 2 * ($ends[$k] - $starts[$j] + 1) <= $size1 + 8 * ($k - $j)) + { + # Need to coalesce $j..$k into $j: + $ends[$j] = $ends[$k]; + $range[$j] = 1; # for now use boolean to indicate this is an idRange segment + splice @starts, $j+1, $k-$j; + splice @ends, $j+1, $k-$j; + splice @range, $j+1, $k-$j; + $end -= ($k-$j); + next L1; # Note that $j isn't incremented so this is a redo + } + } + # Nothing coalesced + $j++; + } + + # Finished with this macro-range + $start = $end; + } + + # Ok, add the final segment containing the sentry value + push(@keys, 0xFFFF); + push @starts, 0xFFFF; + push @ends, 0xFFFF; + push @range, 0; + + # What is left is a collection of segments that will represent the cmap in mimimum-sized format 4 subtable + + my ($num, $count, $sRange, $eSel, $eShift); + + $num = scalar(@starts); + $count = 0; + for ($j = 0; $j < $num; $j++) + { + if ($range[$j]) + { + $range[$j] = ($count + $num - $j) << 1; + $count += $ends[$j] - $starts[$j] + 1; + push @deltas, 0; + } + else + { + push @deltas, ($s->{'val'}{$starts[$j]} || 0) - $starts[$j]; + } + } + + ($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)); + $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 || $s->{'Format'} == 13) + { + my (@jobs, $start, $current, $curr_glyf, $map); + + $current = 0; $curr_glyf = 0; + $map = "\000" x 8192; + foreach $j (@keys) + { + if ($j > 0xFFFF && $s->{'Format'} == 8) + { + if (defined $s->{'val'}{$j >> 16}) + { $s->{'Format'} = 12; } + vec($map, $j >> 16, 1) = 1; + } + if ($j != $current + 1 || $s->{'val'}{$j} != ($s->{'Format'} == 13 ? $curr_glyf : $curr_glyf + 1)) + { + push (@jobs, [$start, $current, $s->{'Format'} == 13 ? $curr_glyf : $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, $s->{'Format'} == 13 ? $curr_glyf : $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 $t->minsize() + +Returns the minimum size this table can be in bytes. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + + +=head2 $t->update + +Tidies the cmap table. + +Removes MS Fmt12 cmap if it is no longer needed. + +Removes from all cmaps any codepoint that map to GID=0. Note that such entries will +be re-introduced as necessary depending on the cmap format. + +=cut + +sub update +{ + my ($self) = @_; + my ($max, $code, $gid, @keep); + + return undef unless ($self->SUPER::update); + + foreach my $s (@{$self->{'Tables'}}) + { + $max = 0; + while (($code, $gid) = each %{$s->{'val'}}) + { + if ($gid) + { + # remember max USV + $max = $code if $max < $code; + } + else + { + # Remove unneeded key + delete $s->{'val'}{$code}; # nb: this is a safe delete according to perldoc perlfunc. + } + } + push @keep, $s unless $s->{'Platform'} == 3 && $s->{'Encoding'} == 10 && $s->{'Format'} == 12 && $max <= 0xFFFF; + } + + $self->{'Tables'} = [ @keep ]; + + delete $self->{' mstable'}; # Force rediscovery of this. + + $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 * + +Format 14 (Unicode Variation Sequences) cmaps are not supported. + +=back + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Coverage.pm b/lib/Font/TTF/Coverage.pm new file mode 100644 index 0000000..db5ab26 --- /dev/null +++ b/lib/Font/TTF/Coverage.pm @@ -0,0 +1,351 @@ +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: + +=over + +=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. + +=item max + +Maximum class value in a class table. + +=back + +=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 + +our $dontsort; + +sub new +{ + my ($class) = shift; + my ($isCover) = shift; + my ($self) = {}; + + $self->{'cover'} = $isCover; + if ($isCover) + { + $self->{'count'} = 0; + my ($v); + foreach $v (@_) + { $self->{'val'}{$v} = $self->{'count'}++; } + } + else + { + $self->{'max'} = 0; + $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); + $self->{'count'} = $num; + } 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'} = $c; + } + } 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'}}; + + if ($self->{'cover'}) + { $self->sort() unless ($self->{'dontsort'} or $dontsort); } + else + { @gids = grep {$self->{'val'}{$_} > 0} @gids;} # class value=0 is not explicitly coded in class table + + $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 || scalar (@gids) == 0); } +# 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++; + } + if (scalar(@gids)) + { + &$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 or class table. +Returns the index or class 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'}{$_}"} sort { $a <=> $b} 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 +{ } + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Cvt_.pm b/lib/Font/TTF/Cvt_.pm new file mode 100644 index 0000000..d7695ac --- /dev/null +++ b/lib/Font/TTF/Cvt_.pm @@ -0,0 +1,90 @@ +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/DSIG.pm b/lib/Font/TTF/DSIG.pm new file mode 100644 index 0000000..af9a24d --- /dev/null +++ b/lib/Font/TTF/DSIG.pm @@ -0,0 +1,88 @@ +package Font::TTF::DSIG; + +use strict; +use vars qw(@ISA); + +require Font::TTF::Table; +use Font::TTF::Utils; + +@ISA = qw(Font::TTF::Table); + +sub create +{ + my ($class) = @_; + my ($self) = { 'version' => 1, 'numtables' => 0, 'perms' => 0 }; + bless $self, ref $class || $class; + return $self; +} + +sub read +{ + my ($self) = @_; + my ($dat, $i, @records, $r); + + $self->SUPER::read || return $self; + $self->{' INFILE'}->read($dat, 8); + ($self->{'version'}, $self->{'numtables'}, $self->{'perms'}) = unpack("Nnn", $dat); + for ($i = 0; $i < $self->{'numtables'}; $i++) + { + $self->{' INFILE'}->read($dat, 12); + push (@records, [unpack("N3", $dat)]); + } + foreach $r (@records) + { + if ($r->[0] == 1) + { + $self->{' INFILE'}->seek($self->{' OFFSET'} + $r->[2],0); + $self->{' INFILE'}->read($dat, $r->[1]); + push @{$self->{'records'}}, substr($dat, 8); + } + } + $self; +} + +sub isempty +{ + my ($self) = @_; + return $self->read->{'numtables'} == 0; +} + +sub out +{ + my ($self, $fh) = @_; + my ($i, $curlen); + + return $self->SUPER::out($fh) unless $self->{' read'}; # this is never true + $fh->print(pack("Nnn", $self->{'version'}, $self->{'numtables'}, $self->{'perms'})); + $curlen = 0; + for ($i = 0; $i < $self->{'numtables'}; $i++) + { + $fh->print(pack("N3", 1, length($self->{'records'}[$i]) + 8, $curlen + $self->{'numtables'} * 12 + 8)); + $curlen += length($self->{'records'}[$i]) + 8; + } + for ($i = 0; $i < $self->{'numtables'}; $i++) + { + $fh->print(pack("nnN", 0, 0, length($self->{'records'}[$i]))); + $fh->print($self->{'records'}[$i]); + } + $self; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Delta.pm b/lib/Font/TTF/Delta.pm new file mode 100644 index 0000000..718d493 --- /dev/null +++ b/lib/Font/TTF/Delta.pm @@ -0,0 +1,186 @@ +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. + +=over + +=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. + +=back + +=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"); +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Dumper.pm b/lib/Font/TTF/Dumper.pm new file mode 100644 index 0000000..ffb8e17 --- /dev/null +++ b/lib/Font/TTF/Dumper.pm @@ -0,0 +1,97 @@ +package Font::TTF::Dumper; + +=head1 NAME + +Font::TTF::Dumper - Debug dump of a font datastructure, avoiding recursion on ' PARENT' + +=head1 SYNOPSIS + + use 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 font table with name and other options + print ttfdump($font->{'head'}, 'head', %opts); + + # 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 main purpose of this module is to 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.) + +If $opts{'d'}, then set Deepcopy mode to minimize use of crossreferencing. + +=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, %opts) = @_; + 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->Deepcopy($opts{'d'}); # Caller controls whether to use crossreferencing + $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 AUTHOR + +Bob Hallissy L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/EBDT.pm b/lib/Font/TTF/EBDT.pm new file mode 100644 index 0000000..6ba07a5 --- /dev/null +++ b/lib/Font/TTF/EBDT.pm @@ -0,0 +1,311 @@ +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); + my ($i, $dat); + my ($eblc) = $self->{' PARENT'}->{'EBLC'}; + my ($bst_array); + + $eblc->read; + $self->SUPER::read || return $self; + $fh = $self->{' INFILE'}; + + # 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) = @_; + + return $self->SUPER::out($fh) unless $self->{' read'}; + + 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. +This was written at the CodeFest Akihabara 2006 hosted by FSIJ. + +?? patch sent with licensing requirements or not? + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + + +=cut + diff --git a/lib/Font/TTF/EBLC.pm b/lib/Font/TTF/EBLC.pm new file mode 100644 index 0000000..2eca08c --- /dev/null +++ b/lib/Font/TTF/EBLC.pm @@ -0,0 +1,259 @@ +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) = @_; + + $self->SUPER::read or return $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); + + + # 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); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + 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. +This was written at the CodeFest Akihabara 2006 hosted by FSIJ. + +Patch sent with licensing requirements?? + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Fdsc.pm b/lib/Font/TTF/Fdsc.pm new file mode 100644 index 0000000..d919cae --- /dev/null +++ b/lib/Font/TTF/Fdsc.pm @@ -0,0 +1,138 @@ +package Font::TTF::Fdsc; + +=head1 NAME + +Font::TTF::Fdsc - Font Descriptors table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over + +=item version + +=item descriptors + +Hash keyed by descriptor tags + +=back + +=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'} || {}; + + $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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Feat.pm b/lib/Font/TTF/Feat.pm new file mode 100644 index 0000000..90f8d58 --- /dev/null +++ b/lib/Font/TTF/Feat.pm @@ -0,0 +1,215 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 6; +} + + +=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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Features/Cvar.pm b/lib/Font/TTF/Features/Cvar.pm new file mode 100644 index 0000000..3b8f9c1 --- /dev/null +++ b/lib/Font/TTF/Features/Cvar.pm @@ -0,0 +1,165 @@ +package Font::TTF::Features::Cvar; + +=head1 NAME + +Font::TTF::Features::Size - Class for Character Variants Feature Parameters + +=head1 DESCRIPTION + +Handles the Feature Parameters valus forCharacter Variants features + +=head1 INSTANCE VARIABLES + +=over 4 + +=item INFILE + +The read file handle + +=item OFFSET + +Location of the file in the input file + +=item Format + +Table format - set to 0 + +=item UINameID + +The 'name' table name ID that specifies a string (or strings, for multiple +languages) for a user-interface label for this feature + +=item TooltipNameID + +The 'name' table name ID for tooltip text for the feature + +=item SampleTextNameID + +The 'name' table name ID for sample text to illustrate the feature + +=item NumNamedParms + +The number of named parameters + +=item FirstNamedParmID + +The 'name' table name ID for the first named parameter + +=item Characters + +An array holding the unicode values of the characters for which the feature +provides glyph variants + +=back + +=head1 METHODS + +=cut + +use Font::TTF::Utils; +use strict; + +=head2 $t->read + +Reads the Feature Params + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($off) = $self->{' OFFSET'}; + my ($dat, $i, $charcount); + $fh->seek($off, 0); + $fh->read($dat, 14); + ( $self->{'Format'} + ,$self->{'UINameID'} + ,$self->{'TooltipNameID'} + ,$self->{'SampleTextNameID'} + ,$self->{'NumNamedParm'} + ,$self->{'FirstNamedParmID'} + ,$charcount ) = TTF_Unpack("S*", $dat); + +# Now read the list of characters. Since these are 24bit insigned integers, need to +# read add a zero value byte to the front then treat as a 32bit integer + + foreach $i (0 .. $charcount-1) + { + $fh->read($dat,3); + $dat = pack("C","0") . $dat; + $self->{'Characters'}->[$i] = TTF_Unpack("L",$dat); + } + + return $self; +} + +=head2 $t->out($fh) + +Writes the FeatureParams table to the output + +=cut + + + +sub out +{ + my ($self, $fh) = @_; + my $chars = $self->{'Characters'}; + my $charcount = 0; + if ($chars) { $charcount = scalar @{$chars} } + my ($dat, $i); + + $fh->print(TTF_Pack("S*" + ,$self->{'Format'} + ,$self->{'UINameID'} + ,$self->{'TooltipNameID'} + ,$self->{'SampleTextNameID'} + ,$self->{'NumNamedParms'} + ,$self->{'FirstNamedParmID'} + ,$charcount )); + + foreach $i ( 0 .. $charcount-1) + { + $dat = substr ( TTF_Pack("L",$chars->[$i]) ,1,3); # Pack as long then remove first byte to get UINT22 + $fh->print($dat); + } + + $self; +} + +=head2 Font::TTF::Features::Sset->new() + +Creates a new FeatureParams object. +Values for INFILE and OFFSET canbe passed as parameters + +=cut + +sub new +{ + my ($class,%parms) = @_; + my ($self) = {}; + my ($p); + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + bless $self, $class; +} + +sub out_xml +{ +} + +1; + +=head1 AUTHOR + +David Raymond L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut diff --git a/lib/Font/TTF/Features/Size.pm b/lib/Font/TTF/Features/Size.pm new file mode 100644 index 0000000..74c1ce5 --- /dev/null +++ b/lib/Font/TTF/Features/Size.pm @@ -0,0 +1,131 @@ +package Font::TTF::Features::Size; + +=head1 NAME + +Font::TTF::Features::Size - Class for Size Feature Parameters + +=head1 DESCRIPTION + +Handles the Feature Parameters valus for Size features + +=head1 INSTANCE VARIABLES + +=over 4 + +=item INFILE + +The read file handle + +=item OFFSET + +Location of the file in the input file + +=item DesignSize + +Design Size in decipoints + +=item SubFID + +Identifier for the fonts in a subfamily + +=item SubFNameID + +The 'name' table name ID for the subfamily name + +=item MinSize + +Bottom end of recomended usage size range + +=item MaxSize + +Top end of recomended usage size range + + +=back + +=head1 METHODS + +=cut + +use Font::TTF::Utils; +use strict; + +=head2 $t->read + +Reads the Feature Params + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($off) = $self->{' OFFSET'}; + my $dat; + $fh->seek($off, 0); + $fh->read($dat, 10); + + ( $self->{'DesignSize'} + ,$self->{'SubFID'} + ,$self->{'SubFNameID'} + ,$self->{'MinSize'} + ,$self->{'MaxSize'} ) = TTF_Unpack("S*", $dat); + + return $self; +} + +=head2 $t->out($fh) + +Writes the FeatureParams table to the output + +=cut + +sub out +{ + my ($self, $fh) = @_; + + $fh->print(TTF_Pack("S*" + ,$self->{'DesignSize'} + ,$self->{'SubFID'} + ,$self->{'SubFNameID'} + ,$self->{'MinSize'} + ,$self->{'MaxSize'} )); + $self; +} + +=head2 Font::TTF::Features::Sset->new() + +Creates a new FeatureParams object. Table instance variables are passed in +at this point as an associative array. + +=cut + +sub new +{ + my ($class,%parms) = @_; + my ($self) = {}; + my ($p); + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + bless $self, $class; +} + +sub out_xml +{ +} + +1; + +=head1 AUTHOR + +David Raymond L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut + diff --git a/lib/Font/TTF/Features/Sset.pm b/lib/Font/TTF/Features/Sset.pm new file mode 100644 index 0000000..78d4fe6 --- /dev/null +++ b/lib/Font/TTF/Features/Sset.pm @@ -0,0 +1,108 @@ +package Font::TTF::Features::Sset; + +=head1 NAME + +Font::TTF::Features::Sset - Class for Stylistic Set Feature Parameters + +=head1 DESCRIPTION + +Handles the Feature Parameters valus for Stylistic Sets + +=head1 INSTANCE VARIABLES + +=over 4 + +=item INFILE + +The read file handle + +=item OFFSET + +Location of the file in the input file + +=item Version + +The minor version number, currently always 0 + +=item UINameID + +The 'name' table name ID that specifies a string (or strings, for multiple +languages) for a user-interface label for this feature + +=back + +=head1 METHODS + +=cut + +use Font::TTF::Utils; +use strict; + +=head2 $t->read + +Reads the Feature Params + +=cut + +sub read +{ + my ($self) = @_; + my ($fh) = $self->{' INFILE'}; + my ($off) = $self->{' OFFSET'}; + my $dat; + $fh->seek($off, 0); + $fh->read($dat, 4); + ($self->{'Version'}, $self->{'UINameID'}) = TTF_Unpack("SS", $dat); + + return $self; +} + +=head2 $t->out($fh) + +Writes the FeatureParams table to the output + +=cut + +sub out +{ + my ($self, $fh) = @_; + $fh->print(TTF_Pack("S", $self->{'Version'})); + $fh->print(TTF_Pack("S", $self->{'UINameID'})); + $self; +} + +=head2 Font::TTF::Features::Sset->new() + +Creates a new FeatureParams object. +Values for INFILE and OFFSET canbe passed as parameters + +=cut + +sub new +{ + my ($class,%parms) = @_; + my ($self) = {}; + my ($p); + foreach $p (keys %parms) + { $self->{" $p"} = $parms{$p}; } + bless $self, $class; +} + +sub out_xml +{ +} + +1; + +=head1 AUTHOR + +David Raymond L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut diff --git a/lib/Font/TTF/Fmtx.pm b/lib/Font/TTF/Fmtx.pm new file mode 100644 index 0000000..f7640a7 --- /dev/null +++ b/lib/Font/TTF/Fmtx.pm @@ -0,0 +1,118 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Font.pm b/lib/Font/TTF/Font.pm new file mode 100644 index 0000000..2dd5f22 --- /dev/null +++ b/lib/Font/TTF/Font.pm @@ -0,0 +1,927 @@ +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 + Glat Font::TTF::Glat + Gloc Font::TTF::Gloc + LTSH Font::TTF::LTSH + OS/2 Font::TTF::OS_2 + PCLT Font::TTF::PCLT + Sill Font::TTF::Sill + Silf Font::TTF::Silf + 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 + DSIG FONT::TTF::DSIG + +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 +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 nocompress + +Is the default value controlling WOFF output table compression. If undef, all tables will be compressed if there is +a size benefit in doing so. +It may be set to an array of tagnames naming tables that should not be compressed, or to a scalar integer specifying a +table size threshold below which tables will not be compressed. +Note that individual L objects may override this default. 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. + +=item WOFF + +Contains a reference to a C object. + +=back + +=head1 METHODS + +=cut + +use IO::File; + +use strict; +use vars qw(%tables $VERSION $dumper); +use Symbol(); + +require 5.004; + +my $havezlib = eval {require Compress::Zlib}; + +$VERSION = 0.39; # MJPH 2-FEB-2008 Add DSIG table +# $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', + 'DSIG' => 'Font::TTF::DSIG', + 'EBDT' => 'Font::TTF::EBDT', + 'EBLC' => 'Font::TTF::EBLC', + 'Feat' => 'Font::TTF::GrFeat', + 'GDEF' => 'Font::TTF::GDEF', + 'Glat' => 'Font::TTF::Glat', + 'Gloc' => 'Font::TTF::Gloc', + 'GPOS' => 'Font::TTF::GPOS', + 'GSUB' => 'Font::TTF::GSUB', + 'Glat' => 'Font::TTF::Glat', + 'Gloc' => 'Font::TTF::Gloc', + 'LTSH' => 'Font::TTF::LTSH', + 'OS/2' => 'Font::TTF::OS_2', + 'PCLT' => 'Font::TTF::PCLT', + 'Sill' => 'Font::TTF::Sill', + 'Silf' => 'Font::TTF::Silf', + '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 location C<$self->{' OFFSET'}> 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); + my ($iswoff, $woffLength, $sfntSize, $zlen); # needed for WOFF files + + $fh->seek($self->{' OFFSET'}, 0); + $fh->read($dat, 4); + $ver = unpack("N", $dat); + $iswoff = ($ver == unpack('N', 'wOFF')); + if ($iswoff) + { + require Font::TTF::Woff; + my $woff = Font::TTF::Woff->new(PARENT => $self); + $fh->read($dat, 32); + ($ver, $woffLength, $dir_num, undef, $sfntSize, $woff->{'majorVersion'}, $woff->{'minorVersion'}, + $off, $zlen, $len) = unpack('NNnnNnnNNN', $dat); + # TODO: According to WOFF spec we should verify $woffLength and $sfntSize, and fail if the values are wrong. + if ($off) + { + # Font has metadata + if ($off + $zlen > $woffLength) + { + warn "invalid WOFF header in $self->{' fname'}: meta data beyond end."; + return undef; + } + require Font::TTF::Woff::MetaData; + $woff->{'metaData'} = Font::TTF::Woff::MetaData->new( + PARENT => $woff, + INFILE => $fh, + OFFSET => $off, + LENGTH => $len, + ZLENGTH => $zlen); + } + + $fh->read($dat, 8); + ($off, $len) = unpack('NN', $dat); + if ($off) + { + # Font has private data + if ($off + $len > $woffLength) + { + warn "invalid WOFF header in $self->{' fname'}: private data beyond end."; + return undef; + } + require Font::TTF::Woff::PrivateData; + $woff->{'privateData'} = Font::TTF::Woff::PrivateData->new( + PARENT => $woff, + INFILE => $fh, + OFFSET => $off, + LENGTH => $len); + } + + $self->{' WOFF'} = $woff; + } + else + { + $fh->read($dat, 8); + $dir_num = unpack("n", $dat); + } + + $ver == 1 << 16 # TrueType outlines + || $ver == unpack('N', 'OTTO') # 0x4F54544F CFF outlines + || $ver == unpack('N', 'true') # 0x74727565 Mac sfnts + or return undef; # else unrecognized type + + + for ($i = 0; $i < $dir_num; $i++) + { + $fh->read($dat, $iswoff ? 20 : 16) || die "Reading table entry"; + if ($iswoff) + { + ($name, $off, $zlen, $len, $check) = unpack("a4NNNN", $dat); + if ($off + $zlen > $woffLength || $zlen > $len) + { + my $err; + $err = "Offset + compressed length > total length. " if $off + $zlen > $woffLength; + $err = "Compressed length > uncompressed length. " if $zlen > $len; + warn "invalid WOFF '$name' table in $self->{' fname'}: $err\n"; + return undef; + } + } + else + { + ($name, $check, $off, $len) = unpack("a4NNN", $dat); + $zlen = $len; + } + $self->{$name} = $self->{' PARENT'}->find($self, $name, $check, $off, $len) and 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, + ZLENGTH => $zlen, + 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 cannot 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); + + my ($iswoff); # , $woffLength, $sfntSize, $zlen); # needed for WOFF files + + 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'}); + + if ($iswoff) + { + } + else + { + ($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; + if ($iswoff && $havezlib && + # output font is WOFF -- should we try to compress this table? + exists ($self->{$k}->{' nocompress'}) ? $self->{$k}->{' nocompress'} != -1 : + ref($self->{' nocompress'}) eq 'ARRAY' ? !exists($self->{' nocompress'}{$k}) : + ref($self->{' nocompress'}) eq 'SCALAR' && $self->{' nocompress'} != -1) + { + # Yes -- we may want to compress this table. + # Create string file handle to hold uncompressed table + my $dat; + my $fh2 = IO::String->new($dat); + binmode $fh2; + $self->{$k}->out($fh2); + $len = $fh2->tell(); + close $fh2; + + # Is table long enough to try compression? + unless ( + exists ($self->{$k}->{' nocompress'}) && $len <= $self->{$k}->{' nocompress'} || + ref($self->{' nocompress'}) eq 'SCALAR' && $len <= $self->{' nocompress'}) + { + # Yes -- so compress and check lengths: + my $zdat = Compress::Zlib::compress($dat); + my $zlen = bytes::length($zdat); + if ($zlen < $len) + { + # write the compressed $zdat + + } + else + { + # write the uncompressed $dat + } + } + else + { + # write uncompressed $dat + } + + + } + else + { + # Output table normally + $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 and WOFF + +The current Font::TTF::Font::out method does not support the writing of TrueType +Collections or WOFF files. + +=item DSIG + +Haven't figured out how to correctly calculate and output digital signature (DSIG) table + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Fpgm.pm b/lib/Font/TTF/Fpgm.pm new file mode 100644 index 0000000..b51adb1 --- /dev/null +++ b/lib/Font/TTF/Fpgm.pm @@ -0,0 +1,97 @@ +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/GDEF.pm b/lib/Font/TTF/GDEF.pm new file mode 100644 index 0000000..12005d4 --- /dev/null +++ b/lib/Font/TTF/GDEF.pm @@ -0,0 +1,476 @@ +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 up to 5 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 + +This class definition table defines the classes of mark glyphs that can be selected +for processing using the MarkAttachmentType field of lookup FLAG words. + +=item MARKSETS + +Contains an array of coverage tables indexed by the FILTER value of a lookup. + +=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; # Prior to 2012-07, this config variable did more than it does today. + # Currently all it does is force the GDEF to include a field in the + # header for the MARKS class definition. That is, it makes sure the + # font is compatible with at least the OT 1.2 specification. + +=head2 $t->read + +Reads the table into the data structure + +=cut + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($boff) = $self->{' OFFSET'}; + my ($dat, $goff, $aoff, $loff, $macoff, $mgsoff, $r, $s, $bloc); + + $bloc = $fh->tell(); + $fh->read($dat, 10); + ($self->{'Version'}, $goff, $aoff, $loff) = TTF_Unpack('LS3', $dat); + + # GDEF is the ONLY table that uses a ULONG for version rather than a Fixed or USHORT, + # and this seems to clearly be a hack. + # OpenType 1.2 introduced MarkAttachClassDef but left the table version at 0x00010000 + # Up through OpenType 1.5, the Version field was typed as Fixed. + # OpenType 1.6 introduced MarkGlyphSetsDef and bumped table version to 0x00010002 (sic) + # and changed it to ULONG. + + + # Thus some trickery here to read the table correctly! + + if ($self->{'Version'} > 0x00010000) + { + # Ok, header guaranteed to have both MarAttachClassDef MarkGlyphSetsDef + $fh->read($dat, 4); + ($macoff, $mgsoff) = TTF_Unpack('S2', $dat); + } + else + { + # What I've seen in other code (examples: + # http://skia.googlecode.com/svn/trunk/third_party/harfbuzz/src/harfbuzz-gdef.c and + # http://doxygen.reactos.org/d0/d55/otvalid_8h_a0487daffceceb98ba425bbf2806fbaff.html + # ) is to read GPOS and GDEF and see if any lookups have a + # MarkAttachType in the upper byte of their flag word and if so then assume that the + # MarkAttachClassDef field is in the header. While this is probably the most + # reliable way to do it, it would require us to read GSUB and GPOS. + # Prior to 2012-07 what we did is depend on our $new_gdef class variable to tell us + # whether to assume a MarkAttachClassDef. + # What we do now is see if the header actually has room for the MarkAttachClassDef field. + + my $minOffset = $self->{' LENGTH'}; + foreach ($goff, $aoff, $loff) + { + $minOffset = $_ if $_ > 0 && $_ < $minOffset; + } + if ($minOffset >= 12) + { + # There is room for the field, so read it: + $fh->read($dat, 2); + ($macoff) = TTF_Unpack('S', $dat); + # Sanity check: + $macoff = 0 if $macoff >= $self->{' LENGTH'}; + } + } + + if ($goff > 0) + { + $fh->seek($goff + $boff, 0); + $self->{'GLYPH'} = 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 + $loff}) + { $comp->{'DEVICE'} = $self->{' CACHE'}{$off + $s + $r + $loff}; } + else + { + $fh->seek($off + $s + $r + $loff + $boff, 0); + $comp->{'DEVICE'} = Font::TTF::Delta->new->read($fh); + $self->{' CACHE'}{$off + $s + $r + $loff} = $comp->{'DEVICE'}; + } + } + push (@$srec, $comp); + } + push (@{$self->{'LIG'}{'LIGS'}}, $srec); + } + } + + if ($macoff > 0) + { + $fh->seek($macoff + $boff, 0); + $self->{'MARKS'} = Font::TTF::Coverage->new(0)->read($fh); + } + + if ($mgsoff > 0) + { + my ($fmt, $count, $off); + $fh->seek($mgsoff + $boff, 0); + $fh->read($dat, 4); + ($fmt, $count) = TTF_Unpack('SS', $dat); + # Sanity check opportunity: Could verify $fmt == 1, but I don't. + $self->{'MARKSETS'} = []; + $fh->read($dat, $count << 2); # NB: These offets are ULONGs! + foreach $off (TTF_Unpack('L*', $dat)) + { + unless (defined $self->{' CACHE'}{$off + $mgsoff}) + { + $fh->seek($off + $mgsoff + $boff, 0); + $self->{' CACHE'}{$off + $mgsoff} = Font::TTF::Coverage->new(1)->read($fh); + } + push @{$self->{'MARKSETS'}}, $self->{' CACHE'}{$off + $mgsoff}; + } + } + + $self; +} + + +=head2 $t->out($fh) + +Writes out this table. + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($goff, $aoff, $loff, $macoff, $mgsoff, @offs, $loc1, $coff, $loc); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $loc = $fh->tell(); + if (defined $self->{'MARKSETS'} && @{$self->{'MARKSETS'}} > 0) + { + $self->{'Version'} = 0x00010002 if ($self->{'Version'} < 0x00010002); + $fh->print(TTF_Pack('LSSSSS', $self->{'Version'}, 0, 0, 0, 0, 0)); + } + else + { + $self->{'Version'} = 0x00010000; + if ($new_gdef || defined $self->{'MARKS'}) + { $fh->print(TTF_Pack('LSSSS', $self->{'Version'}, 0, 0, 0, 0)); } + else + { $fh->print(TTF_Pack('LSSS', $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 (defined $self->{'MARKS'}) + { + $macoff = $fh->tell() - $loc; + $self->{'MARKS'}->out($fh); + } + + if (defined $self->{'MARKSETS'}) + { + + my (@reftables, $ctables, $c, $out); + $ctables = {}; + $mgsoff = $fh->tell() - $loc; + $out = TTF_Pack('SS', 1, $#{$self->{'MARKSETS'}}+1); + foreach $c (@{$self->{'MARKSETS'}}) + { + $out .= pack('N', Font::TTF::Ttopen::ref_cache($c, $ctables, length($out), 'N')); + } + push (@reftables, [$ctables, 0]); + Font::TTF::Ttopen::out_final($fh, $out, \@reftables); + } + + $loc1 = $fh->tell(); + $fh->seek($loc + 4, 0); + if ($mgsoff) + { $fh->print(TTF_Pack('S5', $goff, $aoff, $loff, $macoff, $mgsoff)); } + elsif ($macoff) + { $fh->print(TTF_Pack('S4', $goff, $aoff, $loff, $macoff)); } + else + { $fh->print(TTF_Pack('S3', $goff, $aoff, $loff)); } + $fh->seek($loc1, 0); + $self; +} + + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 10; +} + + +=head2 $t->update + +Sort COVERAGE tables. + +=cut + +sub update +{ + my ($self) = @_; + + return undef unless ($self->SUPER::update); + + unless ($Font::TTF::Coverage::dontsort) + { + if (defined $self->{'ATTACH'} and defined $self->{'ATTACH'}{'COVERAGE'} and !$self->{'ATTACH'}{'COVERAGE'}{'dontsort'} ) + { + my @map = $self->{'ATTACH'}{'COVERAGE'}->sort(); + if (defined $self->{'ATTACH'}{'POINTS'}) + { + # And also a POINTS array which now needs to be re-sorted + my $newpoints = []; + foreach (0 .. $#map) + { push @{$newpoints}, $self->{'ATTACH'}{'POINTS'}[$map[$_]]; } + $self->{'ATTACH'}{'POINTS'} = $newpoints; + } + } + if (defined $self->{'LIG'} and defined $self->{'LIG'}{'COVERAGE'} and !$self->{'LIG'}{'COVERAGE'}{'dontsort'} ) + { + my @map = $self->{'LIG'}{'COVERAGE'}->sort(); + if (defined $self->{'LIG'}{'LIGS'}) + { + # And also a LIGS array which now needs to be re-sorted + my $newligs = []; + foreach (0 .. $#map) + { push @{$newligs}, $self->{'LIG'}{'LIGS'}[$map[$_]]; } + $self->{'LIG'}{'LIGS'} = $newligs; + } + } + if (defined $self->{'MARKSETS'}) + { + foreach (@{$self->{'MARKSETS'}}) + {$_->sort();} # Don't care about map + } + } + + $self; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut diff --git a/lib/Font/TTF/GPOS.pm b/lib/Font/TTF/GPOS.pm new file mode 100644 index 0000000..4df6582 --- /dev/null +++ b/lib/Font/TTF/GPOS.pm @@ -0,0 +1,711 @@ +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 is 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'}]; # Attaching mark + $lookup->{'COVERAGE'} = $self->read_cover($count, $loc, $lookup, $fh, 1); # base/lig/mark being attached to + $fh->read($dat, 6); + ($mcount, $moff, $boff) = TTF_Unpack('S*', $dat); + # Read MarkArray + $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)]); + } + # Read BaseArray/LigatureArray/Mark2Array + $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, $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($dat, $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 not 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 has not 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; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/GSUB.pm b/lib/Font/TTF/GSUB.pm new file mode 100644 index 0000000..1550a3d --- /dev/null +++ b/lib/Font/TTF/GSUB.pm @@ -0,0 +1,299 @@ +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 offsets and lookups 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 + +=item r + +The action array is a list of replacement glyphs in coverage order. This ACTION_TYPE +is used only for Type 8 Reverse Chaining lookups which, by design, are single glyph +substitution. + +=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 12 different lookup types found in the GSUB table definition: + + 1.1 1.2 2 3 4 5.1 5.2 5.3 6.1 6.2 6.3 8 + ACTION_TYPE o g g a g l l l l l l r + MATCH_TYPE g g c o g c o 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 == 8) + { + $t = {}; + 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->{'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->{'POST'} = [@subst]; + } + $fh->read($dat, 2); + $count = TTF_Unpack('S', $dat); + $fh->read($dat, $count << 1); + $t->{'ACTION'} = [TTF_Unpack('S*', $dat)]; + $lookup->{'RULES'} = [[$t]]; + $lookup->{'ACTION_TYPE'} = 'r'; + $lookup->{'MATCH_TYPE'} = 'o'; + } 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 == 8) + { + $out = pack("nn", $fmt, Font::TTF::Ttopen::ref_cache($lookup->{'COVERAGE'}, $ctables, 2 + $base)); + $r = $lookup->{'RULES'}[0][0]; + $out .= pack('n', 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->{'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*", $#{$r->{'ACTION'}} + 1, @{$r->{'ACTION'}}); + } 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; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Glat.pm b/lib/Font/TTF/Glat.pm new file mode 100644 index 0000000..b341815 --- /dev/null +++ b/lib/Font/TTF/Glat.pm @@ -0,0 +1,146 @@ +package Font::TTF::Glat; + +=head1 NAME + +Font::TTF::Glat - Hold glyph attributes + +=head1 DESCRIPTION + +Holds glyph attributes associated with each glyph. + +=over 4 + +=item Version + +Table format version + +=item attribs + +An array of hashes. On array entry for each glyph id. Since the glyph attributes are usually in a sparse +array, they are stored in a hash keyed by the attribute id and with the value as attribute value. + +=cut + +use Font::TTF::Table; +use Font::TTF::Utils; +use strict; +use vars qw(@ISA); +@ISA = qw(Font::TTF::Table); + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($gloc) = $self->{' PARENT'}{'Gloc'}; + my ($fh) = $self->{' INFILE'}; + my ($numGlyphs); + my ($base) = $self->{' OFFSET'}; + my ($dat, $i); + + $gloc->read; + $numGlyphs = $gloc->{'numGlyphs'}; + $fh->seek($base, 0); + $fh->read($dat, 4); + ($self->{'Version'}) = TTF_Unpack('v', $dat); + + for ($i = 0; $i < $numGlyphs; $i++) + { + my ($j) = 0; + my ($num) = $gloc->{'locations'}[$i + 1] - $gloc->{'locations'}[$i]; + my ($first, $number, @vals); + $fh->seek($base + $gloc->{'locations'}[$i], 0); + $fh->read($dat, $num); + while ($j < $num) + { + if ($self->{'Version'} > 1) + { + ($first, $number) = unpack("n2", substr($dat, $j, 4)); + @vals = unpack("n$number", substr($dat, $j + 4, $number * 2)); + $j += ($number + 2) * 2; + } + else + { + ($first, $number) = unpack("C2", substr($dat, $j, 2)); + @vals = unpack("n$number", substr($dat, $j + 2, $number * 2)); + $j += $number * 2 + 2; + } + for (my $k = 0; $k < $number; $k++) + { $self->{'attribs'}[$i]{$first + $k} = $vals[$k]; } + } + } +} + +sub out +{ + my ($self, $fh) = @_; + my ($gloc) = $self->{' PARENT'}{'Gloc'}; + my ($numGlyphs) = 0; + my ($base) = $fh->tell(); + my ($i, $type); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + $numGlyphs = scalar @{$self->{'attribs'}}; + if ($gloc->{'numAttrib'} > 256) + { + $self->{'Version'} = 2; + $type = "n"; + } + else + { + $self->{'Version'} = 1; + $type = "C"; + } + + $gloc->{'locations'} = []; + $fh->print(TTF_Pack('v', $self->{'Version'})); + for ($i = 0; $i < $numGlyphs; $i++) + { + my (@a) = sort {$a <=> $b} keys %{$self->{'attribs'}[$i]}; + push(@{$gloc->{'locations'}}, $fh->tell() - $base); + while (@a) + { + my ($first) = shift(@a); + my ($next) = $first; + my (@v, $j); + while (@a and $a[0] <= $next + 2) + { $next = shift(@a); } + for ($j = $first; $j <= $next; $j++) + { push (@v, $self->{'attribs'}[$i]{$j}); } + { $fh->print(pack("${type}2n*", $first, $next - $first + 1, @v)); } + } + } + push(@{$gloc->{'locations'}}, $fh->tell() - $base); +} + +=back + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut diff --git a/lib/Font/TTF/Gloc.pm b/lib/Font/TTF/Gloc.pm new file mode 100644 index 0000000..6a0ee83 --- /dev/null +++ b/lib/Font/TTF/Gloc.pm @@ -0,0 +1,118 @@ +package Font::TTF::Gloc; + +=head1 NAME + +Font::TTF::Gloc - Offsets into Glat table for the start of the attributes for each glyph + +=head1 DESCRIPTION + +The Gloc table is a bit like the Loca table only for Graphite glyph attributes. The table +has the following elements: + +=over 4 + +=item Version + +Table format version + +=item numAttrib + +Maximum number of attributes associated with a glyph. + +=item locations + +An array of offsets into the Glat table for the start of each glyph + +=item names + +If defined, an array of name table name ids indexed by attribute number. + +=cut + +use Font::TTF::Table; +use Font::TTF::Utils; +use strict; +use vars qw(@ISA); +@ISA = qw(Font::TTF::Table); + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($numGlyphs); + my ($dat, $flags); + + $fh->read($dat, 4); + ($self->{'Version'}) = TTF_Unpack("v", $dat); + $fh->read($dat, 4); + ($flags, $self->{'numAttrib'}) = TTF_Unpack("SS", $dat); + $numGlyphs = ($self->{' LENGTH'} - 8 - ($flags & 2 ? $self->{'numAttrib'} * 2 : 0)) / (($flags & 1) ? 4 : 2) - 1; + $self->{'numGlyphs'} = $numGlyphs; + if ($flags & 1) + { + $fh->read($dat, 4 * ($numGlyphs + 1)); + $self->{'locations'} = [unpack("N*", $dat)]; + } + else + { + $fh->read($dat, 2 * ($numGlyphs + 1)); + $self->{'locations'} = [unpack("n*", $dat)]; + } + if ($flags & 2) + { + $fh->read($dat, 2 * $self->{'numAttrib'}); + $self->{'names'} = [unpack("n*", $dat)]; + } + return $self; +} + +sub out +{ + my ($self, $fh) = @_; + my ($numGlyphs) = 0; + my ($flags, $num); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + $numGlyphs = scalar @{$self->{' PARENT'}{'Glat'}{'attribs'}}; + $num = $self->{'numAttrib'}; + $flags = 1 if ($self->{'locations'}[-1] > 0xFFFF); + $flags |= 2 if ($self->{'names'}); + $fh->print(TTF_Pack("vSS", $self->{'Version'}, $flags, $num)); + $fh->write(pack(($flags & 1 ? "N" : "n") . ($numGlyphs + 1), @{$self->{'locations'}})); + if ($flags & 2) + { $fh->write(pack("n$num", @{$self->{'names'}})); } +} + +=back + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 8; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/Font/TTF/Glyf.pm b/lib/Font/TTF/Glyf.pm new file mode 100644 index 0000000..7a12e3f --- /dev/null +++ b/lib/Font/TTF/Glyf.pm @@ -0,0 +1,180 @@ +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 will not 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; +} + +# Internal function called by loca -- decompresses WOFF data if needed. + +sub _read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + # Nothing else to do + $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 + +# ' match for syntax coloring + +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Glyph.pm b/lib/Font/TTF/Glyph.pm new file mode 100644 index 0000000..4f0961e --- /dev/null +++ b/lib/Font/TTF/Glyph.pm @@ -0,0 +1,902 @@ +package Font::TTF::Glyph; + +=head1 NAME + +Font::TTF::Glyph - Holds a information for a single glyph + +=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. + +=head2 Variables for all glyphs: + +The standard attributes each glyph has are: + +=over 4 + +=item numberOfContours + +For simple glyphs this will be the count of contours. For compound glyphs this will be -1. + +=item xMin + +=item yMin + +=item xMax + +=item yMax + +These identify the bounding box of the glyph. + +=back + +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 + +=head2 Variables for simple glyphs (numberOfContours E= 0): + +=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. + +=item numPoints + +This is a generated value which contains the total number of points for this simple glyph. + +=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 + +=head2 Variables for compound glyphs (numberOfContours == -1): + +=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 + +=head2 Private instance variables: + +=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 is located, 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. +The application must 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 (numberOfContours and bounding box) 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 (defined $self->{' read'} && $self->{' read'} > 0); + $self->{' read'} = 1; + $fh->seek($self->{' LOC'} + $self->{' BASE'}, 0); + $fh->read($dat, $self->{' LEN'}); + TTF_Read_Fields($self, $self->{' DAT'} = $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. + +=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'}, $context->{'addresses'}{$addr}); + return $self; + } + else + { + $context->{'fh'}->printf("%s\n", $depth, $context->{'gid'}); + } + + $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->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 $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<' isDirty'>. + +=cut + +sub update +{ + my ($self) = @_; + my ($dat, $loc, $len, $flag, $x, $y, $i, $comp, $num, @rflags, $repeat); + + return $self unless ($self->{' isDirty'}); + $self->read_dat->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); + $repeat = 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); + } + if ($i > 0 && $rflags[-1] == $flag && $repeat < 255) + { + $repeat++; + } else + { + if ($repeat) + { + $rflags[-1] |= 8; + push @rflags, $repeat; + } + push @rflags, $flag; + $repeat = 0; + } + $self->{'flags'}[$i] = $flag; + } + # Add final repeat if needed, then pack up the flag bytes: + if ($repeat) + { + $rflags[-1] |= 8; + push @rflags, $repeat; + } + $self->{' DAT'} .= pack("C*", @rflags); + 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 (defined $self->{' read'} && $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 nonexistent glyphs +under INSTANCE VARIABLES above. This function will not attempt to +filter out nonexistent 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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/GrFeat.pm b/lib/Font/TTF/GrFeat.pm new file mode 100644 index 0000000..525ce99 --- /dev/null +++ b/lib/Font/TTF/GrFeat.pm @@ -0,0 +1,320 @@ +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))}; + $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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 6; +} + +=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 %s, %s, default: %d name %d # '%s'\n", + $_->{'feature'} > 0x01000000 ? '"' . $self->num_to_tag($_->{'feature'}) . '"' : $_->{'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); +} + +=head2 $t->tag_to_num ($feat_str) + +Convert an alphanumeric feature id tag (string) to a number (32-bit). +Tags are normally 4 chars. Graphite ignores space +padding if it is present, so we do the same here. + +=cut + +sub tag_to_num +{ + my ($self, $feat_tag) = @_; + my $new_feat_num; + + if ($feat_tag > 0) + {$new_feat_num = $feat_tag;} # already a number, so just return it. + else + { + $feat_tag =~ s/[ \000]+$//o; # strip trailing nulls or space + $new_feat_num = unpack('N', pack('a4', $feat_tag)); #adds null padding on right if less than 4 chars + } + + return $new_feat_num; +} + +=head2 $t->num_to_tag ($feat_num) + +Convert a feature id number (32-bit) back to a tag (string). +Trailing space or null padding is removed. +Feature id numbers that do not represent alphanumeric tags +are returned unchanged. + +=cut + +sub num_to_tag +{ + my ($self, $feat_num) = @_; + my $new_feat_tag; + + if ($feat_num > 0x01000000) + { + $new_feat_tag = unpack('a4', pack('N', $feat_num)); + $new_feat_tag =~ s/[ \000]+$//o; # strip trailing nulls or space + } + else + {$new_feat_tag = $feat_num;} + + return $new_feat_tag; +} + +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). + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Hdmx.pm b/lib/Font/TTF/Hdmx.pm new file mode 100644 index 0000000..2b2f7c5 --- /dev/null +++ b/lib/Font/TTF/Hdmx.pm @@ -0,0 +1,172 @@ +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) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($numg, $ppem, $i, $numt, $dat, $len); + + $numg = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + + $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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 8; +} + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Head.pm b/lib/Font/TTF/Head.pm new file mode 100644 index 0000000..e2211ba --- /dev/null +++ b/lib/Font/TTF/Head.pm @@ -0,0 +1,273 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 54; +} + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Hhea.pm b/lib/Font/TTF/Hhea.pm new file mode 100644 index 0000000..a7d5fdc --- /dev/null +++ b/lib/Font/TTF/Hhea.pm @@ -0,0 +1,182 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 36; +} + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Hmtx.pm b/lib/Font/TTF/Hmtx.pm new file mode 100644 index 0000000..9fba573 --- /dev/null +++ b/lib/Font/TTF/Hmtx.pm @@ -0,0 +1,224 @@ +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) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($i, $dat); + + 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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Kern.pm b/lib/Font/TTF/Kern.pm new file mode 100644 index 0000000..1ff2728 --- /dev/null +++ b/lib/Font/TTF/Kern.pm @@ -0,0 +1,364 @@ +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 + +Each subtable has a number of instance variables. + +=over 4 + +=item kern + +A two level hash array containing kerning values. The indexing is left +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; +use Font::TTF::Kern::Subtable; + +@ISA = qw(Font::TTF::Table); +my @subtables = qw(OrderedList StateTable ClassArray CompactClassArray); + +=head2 $t->read + +Reads the whole kerning table into structures + +=cut + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($dat, $i, $numt, $len, $cov, $t); + + $fh->read($dat, 4); + ($self->{'Version'}, $numt) = unpack("n2", $dat); + if ($self->{'Version'} > 0) + { + $fh->read($dat, 4, 4); + ($self->{'Version'}, $numt) = TTF_Unpack("vL", $dat); + } + $self->{'Num'} = $numt; + + for ($i = 0; $i < $numt; $i++) + { + if ($self->{'Version'} > 0) + { + $fh->read($dat, 8); + my ($length, $coverage, $index) = unpack("Nnn", $dat); + my ($type) = $coverage & 0xFF; + $t = Font::TTF::Kern::Subtable->create($type, $coverage, $length); + $t->read($fh); + } + else + { + $t = $self->read_subtable($fh); + } + push (@{$self->{'tables'}}, $t); + } + $self; +} + +sub read_subtable +{ + my ($self, $fh) = @_; + my ($dat, $len, $cov, $t); + + $t = {}; + $fh->read($dat, 6); + ($t->{'Version'}, $len, $cov) = unpack("n3", $dat); + $t->{'coverage'} = $cov & 255; + $t->{'type'} = $cov >> 8; + if ($t->{'Version'} == 0) + { + # NB: Cambria is an example of a font that plays an unsual trick: The + # kern table is much larger than can be represented by the header $len + # would allow. So we use the number of pairs to figure out how much to read. + $fh->read($dat, 8); + $t->{'Num'} = unpack("n", $dat); + $fh->read($dat, $t->{'Num'} * 6); + my (@vals) = unpack("n*", $dat); + 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); + + $fh->read($dat, $len - 6); + $wid = unpack("n", $dat); + $off = unpack("n", substr($dat, 2)); + ($t->{'left_first'}, $numg) = unpack("n2", substr($dat, $off)); + $t->{'left'} = [unpack("n$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("n$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)); + } + } + return $t; +} + + +=head2 $t->out($fh) + +Outputs the kerning tables to the given file + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($i, $l, $r, $t); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + + if ($self->{'Version'} > 0) + { $fh->print(TTF_Pack("vL", $self->{'Version'}, $self->{'Num'})); } + else + { $fh->print(pack("n2", $self->{'Version'}, $self->{'Num'})); } + + for ($i = 0; $i < $self->{'Num'}; $i++) + { + $t = $self->{'tables'}[$i]; + + if ($self->{'Version'} > 0) + { $t->out($fh); } + else + { $self->out_subtable($fh, $t); } + } + $self; +} + + +sub out_subtable +{ + my ($self, $fh, $t) = @_; + my ($loc) = $fh->tell(); + my ($loc1, $l, $r); + + $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); +} + + +=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; +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Kern/ClassArray.pm b/lib/Font/TTF/Kern/ClassArray.pm new file mode 100644 index 0000000..c43446d --- /dev/null +++ b/lib/Font/TTF/Kern/ClassArray.pm @@ -0,0 +1,163 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Kern/CompactClassArray.pm b/lib/Font/TTF/Kern/CompactClassArray.pm new file mode 100644 index 0000000..a2f2345 --- /dev/null +++ b/lib/Font/TTF/Kern/CompactClassArray.pm @@ -0,0 +1,103 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Kern/OrderedList.pm b/lib/Font/TTF/Kern/OrderedList.pm new file mode 100644 index 0000000..512693e --- /dev/null +++ b/lib/Font/TTF/Kern/OrderedList.pm @@ -0,0 +1,118 @@ +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 out_xml +{ + my ($self, $context, $depth, $k, $val) = @_; + my ($fh) = $context->{'fh'}; + + my $postVal = $self->post()->{'VAL'}; + + $fh = 'STDOUT' unless defined $fh; + foreach (@{$self->{'kernPairs'}}) { + $fh->printf("$depth$context->{'indent'}\n", $postVal->[$_->{'left'}], $postVal->[$_->{'right'}], $_->{'kern'}); + } +} + + +sub type +{ + return 'kernOrderedList'; +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Jonathan Kew L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Kern/StateTable.pm b/lib/Font/TTF/Kern/StateTable.pm new file mode 100644 index 0000000..6183130 --- /dev/null +++ b/lib/Font/TTF/Kern/StateTable.pm @@ -0,0 +1,153 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Kern/Subtable.pm b/lib/Font/TTF/Kern/Subtable.pm new file mode 100644 index 0000000..cbe6df3 --- /dev/null +++ b/lib/Font/TTF/Kern/Subtable.pm @@ -0,0 +1,185 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/LTSH.pm b/lib/Font/TTF/LTSH.pm new file mode 100644 index 0000000..9f59eb9 --- /dev/null +++ b/lib/Font/TTF/LTSH.pm @@ -0,0 +1,110 @@ +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) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($numg, $dat); + + $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; +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Loca.pm b/lib/Font/TTF/Loca.pm new file mode 100644 index 0000000..8cddd0c --- /dev/null +++ b/lib/Font/TTF/Loca.pm @@ -0,0 +1,197 @@ +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, do not 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) = @_; + + # Do this before $self->SUPER::read because this can alter the file pointer: + my ($glyfLoc) = $self->{' PARENT'}{'glyf'}->_read->{' OFFSET'}; # May seek on $fh! + + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($locFmt) = $self->{' PARENT'}{'head'}{'indexToLocFormat'}; + my ($numGlyphs) = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + my ($dat, $last, $i, $loc); + + $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 => $self->{' PARENT'}{'glyf'}{' INFILE'}, + 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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Manual.pod b/lib/Font/TTF/Manual.pod new file mode 100644 index 0000000..ae07faa --- /dev/null +++ b/lib/Font/TTF/Manual.pod @@ -0,0 +1,220 @@ +=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 L. +(see CONTRIBUTORS for other authors). + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut + +1; diff --git a/lib/Font/TTF/Maxp.pm b/lib/Font/TTF/Maxp.pm new file mode 100644 index 0000000..ab67a0e --- /dev/null +++ b/lib/Font/TTF/Maxp.pm @@ -0,0 +1,199 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Mort.pm b/lib/Font/TTF/Mort.pm new file mode 100644 index 0000000..d9196d7 --- /dev/null +++ b/lib/Font/TTF/Mort.pm @@ -0,0 +1,130 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 8; +} + +=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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Chain.pm b/lib/Font/TTF/Mort/Chain.pm new file mode 100644 index 0000000..c08ba7b --- /dev/null +++ b/lib/Font/TTF/Mort/Chain.pm @@ -0,0 +1,206 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Contextual.pm b/lib/Font/TTF/Mort/Contextual.pm new file mode 100644 index 0000000..1c70a19 --- /dev/null +++ b/lib/Font/TTF/Mort/Contextual.pm @@ -0,0 +1,166 @@ +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 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, 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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Insertion.pm b/lib/Font/TTF/Mort/Insertion.pm new file mode 100644 index 0000000..75d39aa --- /dev/null +++ b/lib/Font/TTF/Mort/Insertion.pm @@ -0,0 +1,189 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Ligature.pm b/lib/Font/TTF/Mort/Ligature.pm new file mode 100644 index 0000000..c5f1a08 --- /dev/null +++ b/lib/Font/TTF/Mort/Ligature.pm @@ -0,0 +1,256 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Noncontextual.pm b/lib/Font/TTF/Mort/Noncontextual.pm new file mode 100644 index 0000000..52788cb --- /dev/null +++ b/lib/Font/TTF/Mort/Noncontextual.pm @@ -0,0 +1,105 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Mort/Rearrangement.pm b/lib/Font/TTF/Mort/Rearrangement.pm new file mode 100644 index 0000000..2e3d393 --- /dev/null +++ b/lib/Font/TTF/Mort/Rearrangement.pm @@ -0,0 +1,118 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + + diff --git a/lib/Font/TTF/Mort/Subtable.pm b/lib/Font/TTF/Mort/Subtable.pm new file mode 100644 index 0000000..c5497d7 --- /dev/null +++ b/lib/Font/TTF/Mort/Subtable.pm @@ -0,0 +1,210 @@ +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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Name.pm b/lib/Font/TTF/Name.pm new file mode 100644 index 0000000..73b6a16 --- /dev/null +++ b/lib/Font/TTF/Name.pm @@ -0,0 +1,916 @@ +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) = @_; + $self->SUPER::read or return $self; + + my ($fh) = $self->{' INFILE'}; + my ($dat, $num, $stroff, $i, $pid, $eid, $lid, $nid, $len, $off, $here); + + $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 && @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, $loc, $stroff, $endloc, $str_trans); + my (%dedup, @strings, @offsets, $strcount); + + return $self->SUPER::out($fh) unless $self->{' read'}; + + $strcount = 0; + $offsets[0] = 0; + $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 && @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); } + } + my ($str_ind); + unless (defined $dedup{$str_trans}) + { + use bytes; + $dedup{$str_trans} = $strcount; + $strings[$strcount] = $str_trans; + $strcount++; + $offsets[$strcount] = $offsets[$strcount-1] + bytes::length($str_trans); + } + $str_ind = $dedup{$str_trans}; + push (@todo, [$pid, $eid, $lid, $nid, $str_ind]); + } + } + } + } + + @todo = (sort {$a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] + || $a->[3] <=> $b->[3]} @todo); + foreach $todo (@todo) + { $fh->print(pack("n6", @{$todo}[0..3], $offsets[$todo->[4]+1] - $offsets[$todo->[4]], $offsets[$todo->[4]])); } + + $stroff = $fh->tell() - $loc; + foreach my $str (@strings) + { $fh->print($str); } + + $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 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 6; +} + +=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 || @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 remove_name($nid) + +Removes all strings with the given name id from the table. + +=cut + +sub remove_name +{ + my ($self, $nid) = @_; + + delete $self->{'strings'}[$nid]; +} + +=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 be 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/(?:\r?)\n/\r\n/og if $pid == 3; + $strNL =~ s/(?:\r?)\n/\r/og if $pid == 1; + foreach $eid (0 .. $#{$self->{'strings'}[$nid][$pid]}) + { + if (defined $self->{'strings'}[$nid][$pid][$eid]) + { + my ($isincover) = 0; + foreach $c (@cover) + { + if ($c->[0] == $pid && $c->[1] == $eid) + { + $isincover = 1; + last; + } + } + push(@cover, [$pid, $eid]) if (!$isincover); + } + 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 .. $#cover) + { + next unless (defined $cover[$c] && $cover[$c][0] == $pid && $cover[$c][1] == $eid); + delete $cover[$c]; + last; + } + } + } + } + foreach $c (@cover) + { + next unless (defined $c && scalar @$c); + 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 1 if ($pid == 0); # all languages are equal in unicode since nothing defined + return ($lid == $lang) if ($lang != 0 || $lang eq '0'); + return !index(lc($langid), lc($lang)) || !index(lc($lang), lc($langid)); +} + +=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; +} + +=head2 Font::TTF::Name->pe_list() + +Returns an array of references to two-element arrays +containing pid,eid pairs that already exist in this name table. +Useful for creating @cover parameter to set_name(). + +=cut + +sub pe_list +{ + my ($self) = @_; + my (@cover, %ids); + + foreach my $nid (0 .. $#{$self->{'strings'}}) + { + if (defined $self->{'strings'}[$nid]) + { + foreach my $pid (0 .. $#{$self->{'strings'}[$nid]}) + { + if (defined $self->{'strings'}[$nid][$pid]) + { + foreach my $eid (0 .. $#{$self->{'strings'}[$nid][$pid]}) + { + if (defined $self->{'strings'}[$nid][$pid][$eid] && !$ids{$pid}{$eid}) + { + $ids{$pid}{$eid} = 1; + push @cover, [$pid, $eid]; + } + } + } + } + } + } + return @cover; +} + + +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", "-Ploc-SA", "-145"]], + ['bg-BG'], + ['ca-ES'], + ['zh', ['-TW', '-CN', '-HK', '-SG', '-MO', "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "zh", "-Hant"]], + ["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", "-AE", "-BH", "-EG", + "-JO", "-KW", "-TR", "-YE"]], + ["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", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "nn", "nb"]], + ["pl-PL"], + ["pt", ["-BR", "-PT"]], + ["rm-CH"], + ["ro", ["-RO", "_MD"]], + ["ru-RU"], + ["hr", ["-HR", "-Latn-CS", "Cyrl-CS", "-BA", "", "-Latn-BA", "-Cyrl-BA", + "", "". "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "bs-Cyrl", "bs-Latn", "sr-Cyrl", "sr-Latn", "", "bs", "sr"]], + ["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", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "-Cyrl"]], + ["fa-IR"], + ["vi-VN"], + ["hy-AM"], + ["az", ["-Latn-AZ", "-Cyrl-AZ", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "-Cyrl", "-Latn"]], + ["eu-ES"], + ["wen". ["wen-DE", "dsb-DE", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "dsb"]], + ["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", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "smn", "sms", "smj"]], + ["ga-IE"], + ["yi"], + ["ms", ["-MY", "-BN"]], + ["kk-KZ"], +# 0040 + ["ky-KG"], + ["sw-KE"], + ["tk-TM"], + ["uz", ["-Latn-UZ", "-Cyrl-UZ", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "-Cyrl", "-Latn"]], + ["tt-RU"], + ["bn", ["-IN", "-BD"]], + ["pa", ["-IN", "-Arab-PK", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "-Arab"]], + ["gu-IN"], + ["or-IN"], + ["ta-IN"], + ["te-IN"], + ["kn-IN"], + ["ml-IN"], + ["as-IN"], + ["mr-IN"], + ["sa-IN"], +# 0050 + ["mn", ["-Cyrl-MN", "-Mong-CN", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "-Cyrl", "-Mong"]], + ["bo", ["-CN", "-BT"]], + ["cy-GB"], + ["km-KH"], + ["lo-LA"], + ["my"], + ["gl-ES"], + ["kok-IN"], + ["mni"], + ["sd", ["-IN", "-PK", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "-Arab"]], + ["syr-SY"], + ["si-LK"], + ["chr", ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "-Cher"]], + ["iu", ["-Cans-CA", "-Latn-CA", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "-Cans", "-Latn"]], + ["am-ET"], + ["tmz", ["-Arab", "-Latn-DZ", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "-Latn"]], +# 0060 + ["ks"], + ["ne", ["-NP", "-IN"]], + ["fy-NL"], + ["ps-AF"], + ["fil-PH"], + ["dv-MV"], + ["bin-NG"], + ["fuv", ["-NG", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ff-Latn"]], + ["ha", ["-Latn-NG", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "-Latn"]], + ["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"], + ["mi-NZ"], + ["oc-FR"], + ["co-FR"], + ["gsw-FR"], + ["sah-RU"], + ["qut-GT"], + ["rw-RW"], + ["wo-SN"], + [""], # (unassigned) + [""], # (unassigned) + [""], # (unassigned) + ["gbz-AF"], + [""], # (unassigned) + [""], # (unassigned) + [""], # (unassigned) +# 0090 + [""], # (unassigned) + ["gd-GB"], + ["ku", ["-Arab-IQ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "-Arab"]], + ["quc-CO"], +); +# 0501 = qps-ploc +# 05fe = qps-ploca +# 09ff = qps-plocm + +@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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/OS_2.pm b/lib/Font/TTF/OS_2.pm new file mode 100644 index 0000000..b52312e --- /dev/null +++ b/lib/Font/TTF/OS_2.pm @@ -0,0 +1,1036 @@ +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 @ranges @codepages); +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 + '' => '' # i.e. v4 is structurally identical to v3 + ); + +@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, 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 < 5; $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 < 5) + { + $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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 78; +} + +=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; + } + + $self->{'ulUnicodeRange2'} &= ~0x2000000; + 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; +} + + +=head2 $t->guessRangeBits (\%map, [$cp_threshold, [%u_threshold]]) + +Set the ulCodePageRange and ulUnicodeRange fields based on characters actually present in the font. + +%map is a hash keyed by USV returning non-zero for characters present (e.g. use {'val'} +a from Unicode cmap). + +The two optional parameters are percentage of characters within the codepage or unicode range that need +to be present to constitute coverage. A threshold of 0 causes corresponding range bits to +be set if any characters are present at all, while a negative value causes the corresponding +range bits to be unchanged. Defaults are 50 and 0, respectively. + +For codepage bits, the threshold is percentage of characters between 0xC0 and 0xFF that need +to be present to constitute coverage). For codepages other than 1252, +characters (e.g., punctuation) that are defined identically to cp1252 are ignored +for the purposes of this percentage calculation. Looks only for SBCS codepages, not DBCS. + +For Unicode range bits that represent multiple ranges, e.g., bit 29 represents: + + Latin Extended Additional 1E00-1EFF + Latin Extended-C 2C60-2C7F + Latin Extended-D A720-A7FF + +the bit will be set if any of these ranges meet the threshold requirement. + +=cut + +sub guessRangeBits +{ + my ($self, $ucmap, $cp_threshold, $u_threshold) = @_; + $cp_threshold = 50 unless defined ($cp_threshold); + $u_threshold = 0 unless defined ($u_threshold); + my $j; # index into codepages or ranges + my $u; # USV + + unless (ref($codepages[0])) + { + # One-time work to convert range data + + # unpack codepages. But first make sure we have Zlib + eval {require Compress::Zlib} || croak("Cannot unpack codepage data Compress::Zlib not available"); + + for ($j = 0; $j <= $#codepages; $j++) + { + next unless $codepages[$j]; + $codepages[$j] = [unpack("n*", Compress::Zlib::uncompress(unpack("u", $codepages[$j])))]; + warn ("Got nothing for codepage # $j\n") if $#{$codepages[$j]} < 128; + } + + # convert Unicode array data to hash + my @newRanges; + for ($j = 0; $j <= $#ranges; $j++) + { + next unless $ranges[$j][2] =~ m/^([0-9a-f]{4,6})-([0-9a-f]{4,6})$/oi; + my $s = hex ($1); + my $e = hex ($2); + next unless $e > $s; + push @newRanges, {start => $s, end => $e, bit => $ranges[$j][0], name => $ranges[$j][1]}; + } + @ranges = sort {$a->{'end'} <=> $b->{'end'}} (@newRanges); + } + + if ($cp_threshold >= 0) + { + my $cpr; # codepage range vector + $cp_threshold = 100 if $cp_threshold > 100; + vec($cpr, 63 ,1) = 0; # Get all 64 bits into the vector + for $j (0 .. $#codepages) # For each codepage + { + # Count the number of chars from @range part of codepage that are present in the font + my $present = 0; + my $total = 0; + foreach (0x20 .. 0xFF) + { + $u = $codepages[$j][$_]; + next if $u == 0xFFFD; # Ignore undefined things in codepage + # For codepage 1252, ignore upper ansi altogether + # For other codepages, ignore characters that are same as in 1252. + next if $j > 0 ? $u == $codepages[0][$_] : $u > 0x007F; + $total++; + $present++ if exists $ucmap->{$u} && $ucmap->{$u} > 0; + } + #printf STDERR "DBG: Got $present / $total (%0.3f%%) in codepage bit $j\n", $present * 100 / $total; + #print STDERR "DBG: setting bit $j\n" if $cp_threshold == 0 ? ($present > 0) : (($present * 100 / $total) >= $cp_threshold); + vec($cpr, $j, 1) = 1 if $cp_threshold == 0 ? ($present > 0) : (($present * 100 / $total) >= $cp_threshold); + #print STDERR "\n"; + } + ($self->{'ulCodePageRange1'}, $self->{'ulCodePageRange2'}) = unpack ('V2', $cpr); + } + + if ($u_threshold >= 0) + { + my $ucr; # Unicode range vector + my @count; # Count of chars present in each range + $u_threshold = 100 if $u_threshold > 100; + vec($ucr, 127,1) = 0; # Get all 128 bits into the vector + $j = 0; +CHAR: for $u (sort {$a <=> $b} keys %{$ucmap}) + { + while ($u > $ranges[$j]{'end'}) + { + last CHAR if ++$j > $#ranges; + } + next if $u < $ranges[$j]{'start'}; + $count[$j]++ if $ucmap->{$u}; + } + foreach $j (0 .. $#ranges) + { + vec($ucr, $ranges[$j]{'bit'}, 1) = 1 if $u_threshold == 0 ? + ($count[$j] > 0) : + (($count[$j] * 100 / ($ranges[$j]{'end'} - $ranges[$j]{'start'} + 1)) >= $u_threshold); + } + ($self->{'ulUnicodeRange1'},$self->{'ulUnicodeRange2'},$self->{'ulUnicodeRange3'},$self->{'ulUnicodeRange4'}) = + unpack ('V4', $ucr); + } +} + +BEGIN { + +@codepages = ( +<<'EOT', # Bit 0: cp1252 +M>)P-R>-B%E```-#S+1O+O%NU;-MVRURV[7?H"3*7;2_;M6S;K=7Y>Q`1)8VT +MTDDO@XPRR2R+K++)+H><\"BJJI+(JJJJFNAIJJJ6V.NJJI[X&&FJDL2:::J:Y%EIJI;4VVFJGO0XZ +MZJ2S+KKJ)EYW/?342V]]]-5/?P,,-$B"P888:ICA1AAIE-'&&&N<\2:8:)+) +MIIAJFNEFF&F6V>:8:Y[Y%E@8$E-30J'(HE`LQ(408J*20I5(0J@;Z9::$IF? +MFO)_"X2"H4@H&F)#=,@3E1P3&QD><=\%% +MEUQVQ5777'?#3;?<=D>RN^ZY[X&''GGLB:>>>>Z%EUYY[8VWWGGO@X\^^>R+ +3K[[Y[H>??OGMCQ1_I?X#Q@N!>@`` +EOT + +<<'EOT', # Bit 1: cp1250 +M>)P-Q^=+U'$`Q_'WY^>LW%K=G:/OG6:VM4R[*RU'.7.EY2BW5E:.MOM?Z8%8 +MXB#!,O"1$8A$/2G:$D40$?6D2)`.NP=O>+T!8>&'/P$$$D0P&]C()D(()8QP +M(H@DBFABV,P6MF+#CH-8XH@G@6T8G+A()(GM)+.#%':RB]WL82_[V$\J:1S@ +M(.D<(H-,#N/&PQ&.DD4VQSA.#KGDD<\)3E)`(4444T(IIRBCG`HJJ>(TU=1P +MAK/44D<]#9SC/(TTT4P+K;313@>=7.`BE^CB,E>X2C<]]-+'-:YS@YO`08889H11,[7N-0Y?"2;9&./T*57-QJUZM6M8_;ZW&;N),_'&9:)-S+K7 +MZ5*+\:A!'1K1`'>M)>NM1XK +M0$TL*,M:4;:&5,TB3V3Q5&X%LJ00EF7CF1P\YX7"%*$\%?"25ZK@-6]4JV[> +MJY>/K*B53ZKA,U_DQU=Y%,0WA?)==GXHEI_\4K@BE:]"?O-'E?QE577J84U] ++_,.K-NO=?_3I:`(` +EOT + +<<'EOT', # Bit 2: cp1251 +M>)P%P=52%5``0-$-;!KISG,OW=W=#=(IV-W=_H+^@0^.CB,Z^J`^^&V.K@7$ +M$$L<$D\"B2213`JII'&*=#+()(ML#3;;89H==]MCG@$/.<,0Q9SG'>2YPD4MYQWT>\)!'/.8)3WG&\YHVQQH5B-T)YJ`DA1,)):#,Y])MB +MFJEFN!X*0U$H#64A&G)"[K^_D:A[8F>V02'WS+1SX9PV?E"R=\ +MY9N)?.>'"6[[CI_\XK=KD7PW^>.N\6ZY8Z999IMCKGGF6V"A119;8JEEEEMA +M,&+42JNLML9:ZZRWP4:;;+;%5MMLM\-.N^RVQU[[['?`08<<=L11QQQWPDFG +3G';&6>><=\%%EUQVQ=7_+3E$L0`` +EOT + +<<'EOT', # Bit 3: cp1253 +M>)PUB>^."+'_X$ +M,(U`@@AF.B&$$L8,#`ZYPESSCLBT3[%%HPDR$,<;AMF@WL;8U,A-H@DR("35.XVO\;,OA=+>XD9=R +M%>@>%512134UU%+GKO6X:*#1^--,"ZVT*9]V.NC4?3W00[KUB!X]UA,]U3,5 +MJDC%>JX2O5"I7JI,K_1:;_16[U2N][:E"E6J2M6J4:WJ]$'UN+>O15O>K3-_7KNW[HIW[IM_YH0(,:TK#^ZI]M_0=K +#AH5\ +EOT + +<<'EOT', # Bit 4: cp1254 +M>)PER6-ST'$``.#GOVPLM]9OJY9KR\NV[99M^SOT";)MV[S,NVQ=]JWKNN?E +M@TB,##+*)+,LLLHFNQQRRB6W//+*)[]8!1142&%%%%5,<7%*B%=2D"!1*:65 +MD:2L\ +M"2::9+(IIIIFNAEFFF6V.>::9[X%%H;UZ7]"L6A1B`])(82$F),A.4H+J5'7 +M]#__A2*A:(@+)4)BB`T%8NXE)$:#0]VHV[^+9EELB:6666Z%E599;8VUUEEO +M@XTVV6R+K;;9;H>==MEMC[WVV>^`@PXY[(BCCCGNA)-..>V,L\XY[X*++D7Q +M+KOBJFNNN^&F6VZ[XZY[47(TT'T///3(8T\\])Q%C]=7#@`X;L\3_X"Z)3*4E[G)9Q +MCFLR(MDK71'GN/K.)\[I.,_=\]X\+^"%@T$,1@QA*,/P9C@C&,DH?/!E-&,8 +MBQ_^C&,\$P@@D""""2&4,"9B.'$QB6DR_B?.X![``"[00"S67^9F_H]OI +MZG?Q`RL%7.<&A59&$<64<)-2.KE%.;>IX`Z55%%-#;7444\#C33QFV9::*6- +M=E6J2M6J4:WJ5*\&-:K)XU:S6M2J-K7KKN[IOA[HNWZH3S_UZW_97_1(C]6A +M)WJJ9WJN3KW02W7IE;KU6F_T5N_T7A_T49_T65_T53WZIMY_OWS,U^/^`\"V +"EBL` +EOT + +<<'EOT', # Bit 6: cp1256 +M>)P%P;=/%``8A^'W$'\J'*`"2N>[`XX.1^?.1N_UL``*=A'L8$,4-^.J$R,1 +M&Q$%%+N2N/@'N+@:%UN"BXDA./@\@(,@5A',:L0:UK*.$$)Q$D8X$:QG`QN) +M)(IH-K&9&&*)(YX$$DDB&<.%FQ122<-#.AEDDD4V.>221SY>"BBDB&)***6, +MXS"A7&.,J +MUQCGNLUHW.(LK5P#>^\X.?_%*CFECBMYK5HE:U\4?MZN"O.EE6@!7^6;A% +'Z--_?PY;L``` +EOT + +<<'EOT', # Bit 7: cp1257 +M>)P-B4=/%&``1-^L%`M86(1=FM\N106E]R)2;6`7%"P@%CI([_X5HT8E8N]1 +M.5GBV2@22;P("6G"S?HK"""0(()9S1K6LHX00EG/!C:RB3"< +MA+.9"")QX2:*:&*()8XM&#QXB2>!1)+8RC:VDTP*.]A)*FFDDT$F66230RYY +MY%-`(4444\(N2ME-&>544$D5U>QA+_O8SP%JJ.4@ASC,$8YRC..XR"4NTTH;[73021?=]-#+%?KH9X!!AAAFA%'&&&>" +M2::X:F;\/A-EB3-)QAB/M71+@=_''<='7EIW&;>),;'&:YPFW._S>.U6:/\' +MCN\VKUFN\HSGO.`OKU3/:][PEEF6 +M%*!4H2#>\5XNA2F$3QI5A+S*5;**U*0*5?%9-7SA*W/J4YD:U,Z"QC7)#P4J +M35(P/UF46TZ%LJPQ12I>>4I1L9I5J6I^J9;?K/!'_2I7HSKXIPE-.>;_`YNL +";XP` +EOT + +<<'EOT', # Bit 8: cp1258 +M>)PER>5.EF$`@.'KY;,[,%#1!U3L[NYNL+N[$_LR&;.= +M.J>S&>K<_>^Z$8D3DT=>^>170$&%%%9$4<445T))I906KXRRRBDO004559*H +MLBJ"),FJJJ:Z%#745$MM==153WT---1(8TTTU4QS+;342FMMM-5.>QUTU$EG +M773537<]]-1+;WWTU4]_`PPT2*HT@PTQU###C3#2**.-,=8XXTTPT22333'5 +M--/-,-,LL\TQUSSS+;#0(HLML=0RZ99;8:555EMC;=B9FQ,J1.M"Y9`20DB* +MRPH-_TJK*#4WYW^A?$@(E4)B2`ZE0WQ<=E+R7VL=I?U[4;H,F=;;8*---MMB +MJVVVVV&G77;;8Z]]]CO@H$,..^*H8XX[X:133COCK'/.1W$NN"C+)9==<=6U +M&-?=<#,JX5:LH-ON1!GNNN>^!QYZ)#O:$XMY[(FGGD4QS[WPTBNOO?'6NUCD +@O0\^1B5]BE7UV9)Q=C=52&U``!1?W11(@@00N`8*[4\'=W>KN[OHK_8#^8J=]Z.0!9E_.S,Z< +M!;+()H=<\LBG@$****:$4LJ0JH)T&2!AH)-)&BF19: +M2=-&.QUTTD4W/?321S\###+$,".,,L8X$TQRB^QSP"%'''/"*6=\X2G +M/.,Y+WC)*U[SAK>\XST?^,@G/O.%KWSC.S_X&7[__?.?D,ZL\X18B(=$2(94 +MB(3H1^"BRZY +@[(JKKKGNAIMNN>V.N^ZY[X&''GGLB:>>91[^`:-3=+0` +EOT + +undef, # Bit 17: Reserved +undef, # Bit 18: Reserved +undef, # Bit 19: Reserved +undef, # Bit 20: Reserved +undef, # Bit 21: Reserved +undef, # Bit 22: Reserved +undef, # Bit 23: Reserved +undef, # Bit 24: Reserved +undef, # Bit 25: Reserved +undef, # Bit 26: Reserved +undef, # Bit 27: Reserved +undef, # Bit 28: Reserved +undef, # Bit 29: Reserved +undef, # Bit 30: Reserved +undef, # Bit 31: Reserved +undef, # Bit 32: Reserved +undef, # Bit 33: Reserved +undef, # Bit 34: Reserved +undef, # Bit 35: Reserved +undef, # Bit 36: Reserved +undef, # Bit 37: Reserved +undef, # Bit 38: Reserved +undef, # Bit 39: Reserved +undef, # Bit 40: Reserved +undef, # Bit 41: Reserved +undef, # Bit 42: Reserved +undef, # Bit 43: Reserved +undef, # Bit 44: Reserved +undef, # Bit 45: Reserved +undef, # Bit 46: Reserved +undef, # Bit 47: Reserved + +<<'EOT', # Bit 48: cp869 +M>)Q%R>=7#0`]I+*/_:1Q@(.DX2OY`_W^Z/M!/-ZTT.!W.0!4Y`W1#-_58Q?_VEEIH5@F==*F5.K7IB9[J +MITK5I]\J4[GG5/E;308V56;F[S,I?NJTK5>F#IEF#)EJJ'JC$_ +M\[<("[=@PR+E4:VE6))E6H:E6;QEJ4Z/5*\&-:I)S>I0I[K,83Y69`5ZIN=V +M32_4K9=ZI=?JT1OUZJW>Z;T^Z),^ZK,*:*-#7_15WVC4=Q723I-^Z)?:]<<\ +'>/X"GB5Z;@`` +EOT + +<<'EOT', # Bit 49: cp866 +M>)P%P45#$P``AN$7_!0#<=088]381XYN&-W=#:,;N]N#!P\>C#_`P1_@S;_G +M\P`II'(#<9-;I'&;.]SE'NG<)X,'!,@DBVQRR"5('B'R"5-`A$****:$4J*4 +M$<.44T$E55130YQ:ZJBG@4:::*:%5MIHIX-.NN@F00^]]-'/`(,,,.<"RZY +MXB&/>,P3GO*,Y[S@):]XS1O>\H[W?.`CG_BL@#*5I6SE*%=!Y2FD?(55H(@* +M5:1BE:A4494I)JM<%:I4E:I5H[AJ5:=Z-:A136I6BUK5IG9UJ%-=ZE9"/>I5 +MGW_ZEW\[U3'O^\"K7O&A%[SF'6][RP'GN,5Q1XP33GK7FU[VN4^\YWE?^=1G +M/O*Q-[SN12_YTA<..=W?_-7?_<-?U*\!#6I(PQK1J,8TK@E-:DK3FM&LYI2B +:!4G+2M.:,I3D;S2??]%P:9`_ON;Z/\R_26T` +EOT + +<<'EOT', # Bit 50: cp865 +M>)P%P3=,%&``AN$7?FR(>BH@W>,_/A%%;-BQ*V*G]W9P1[=W:0X.#@P(BR,& +M([$,8B0F1J.)B'%D,0XD*K$W@AHIDNCS`#[X8O!C$I.9PE2FX<]T`IC!3&;A +M8#9SF$L@000SCQ!""2.<""*)8CY.HK&XB$$L():%Q+&(Q<2SA`26LHSEK&`E +MB:QB-6M8RSK6LX$D-K*)S6QA*]O8S@YVDLPN4MC-'O:RC_T213P&%%%%,":6X*:,<#UXJJ*2*:FJHI8Y#'.8(1SG&<4YPDE.^\)5/#/&#;_3RC!=\H(_?_.$G?QGC'R\9 +M8)1.7CF[?-IXRW=^,O8N16F;*5I7*E +M*4=%*E2!'`I4HN(5(92D8I4H7YFJ5H5*E:HZ5:I*'GF5IURE*T.UJE&(`G1) +M%]6B5ETPW;PV[>:)Z31/Z3&]YKJY8FZ8>S;*])D>&V?==%NO];B)P=R'EHSW$<#J$<(/NK3`*,AC6A,$YKBIQG-:4$H +M+6E%:\)H0UO:$4X$D40137MBB"6.#GAT)-Z*Z$1GNM"5;G0G@1[TI!>]Z4-? +M^I%(?P8PD$$,9@A#26(8PQE!,B-)812C&<-84DEC'..9P$0F,9DI3"6=:4QG +M!C.9Q6SF,)=YS&WYOP=T4$=UW/PZH5,Z:QF6:5F6 +M;3F6:WF6;P56J*<6I4I=TW6+Y;1V:*=V:Z_N:9_VZY".Z)A*=%)E.J\+*E>% +M+NFJJE2MF[JEV[JOQYSC(G74ZJXEZYEJ]5*O]5;O]5&?]44_5*,'>J2'>J-B +<2]<[?=`G?=5W/=%S_=0OO=(+?7.EP<`_%"[`E0`` +EOT + +<<'EOT', # Bit 52: cp863 +M>)P%P4E0#7``Q_%O_9&E>!(M4J__ZQJ_=4I;L.3@X=$A= +M'!N9:#+3HF+&,@W&.L/%Q3##N%3,<&#&6`X^'\`'7PS3F,X,_)C)+&8S!W\" +MF,L\',PGD`4$L9!%!!-"*&$L)IPE1!")DR@L+J(1,2QE&;$L)XYX5K"25220 +MR&K6D,1:UK&>#6QD$YM)9@M;V48*V]G!3G:QFU322">#/>QE'_LYP$$RR2*; +M''+)(Y\""BFBF!)**:.<"BIQ4T4U'KS44$L=]3302!.'.,P1CM),"\;,YAQ;O""Y[SD)Z]XPU]^ +M"UR\$M'G"?A_1S3U?4J2[Y*EIN52E? +M>:I6E@I4IE*5R*$@)2E>X4+)*E>%BI6K>M6H4IEJ4JWJY)%712I4MG+4J`:% +MR%^7=4GMZM!%,\1'TV7&38]YS*AY8GK-5=-G1FR$>6I&;:QU,V2]UN-RNJ+X +39=,9M*')P%P3=,U'$KHMBQ8V_8Z;T=W-'MO<+@X,"@L#BB +M1(UE$",QL403C;/8.V+7Q,$XF#CY/$`$D3CZ8?1G``,9Q&"&$,50AC$<'R,8 +MR2BB&O9P$8VL9DM9))%-CGDDD<^ +M!1121#$EE%)&.154$J2*:D*$J:&6.NIIH)$FMK*-[>Q@)[O8S1[VLH_]'.`@ +MASC,$8YRC&9[9#WVV)[84WMFS^V%O;17]MK>V%M[9^^MUSY8GWVT3_;9OMA7 +M^V;?[8?]Y"R=G/-?B&BCCU_\X1^_Z>$2-[D;\'&%.]SF#)>YI5-J4[LBE:R@ +MJI2O/%4K2P4J4ZE*Y%.TTI2J>*%TE:M"Q)P%P4=(%7``Q_&O_FU;O4K+W?/__%6F;=NVRZRTW'L]?<_=WD/MT*&#!]-+ +M1T,H)`\928<,C4SI$`2!1)@-VB($185F]/D`/OAB\&,2DYG"5*8QG1GX,Y-9 +MS,;!'.8RCP`"F<\"@@@FA%#""">"A3B)Q.(B"K&(Q2PAFJ7$$,LREK."E:QB +M-6N(8RWK6,\&-K*)S<2SA:UL8SL[V,DN=K.'!/:2R#[VO)1302555%-#+8T>-RT,%#NKG!;1[HFIK5(E]%R:U292E3 +M94I5M@I5H'PY%*`XQ2I,*%Y%*E:>,E2E6-:3*]I,X_I,GWFIKENVLT]&V'Z39>-MFXZK==Z7$Y7)+]M +2(G=L,/=MB+.!NVJE]3^&>7'F +EOT + +<<'EOT', # Bit 55: cp860 +M>)P%P4E0#7``Q_&O_G8A*MKD]7_]4F2/0F07*NW[]NJ]=DOVO0X=.CB@BV,T +MPU@.931FJ"DR6:8.%0?&#.I@&0[(&,;!YP-,P`O#1"8QF2E,91K3F8$W,YG% +M;'R8PUQ\\<.?>221SX%%%)$,264XJ*,\(E!7O*5/A[SG%Z>,FJE6A4J6J3I6JDEL>Y2E7Z)P-RE-W%W```-#[7[:6E_O%Y67;7BUKM5HMVSWTT&OU!;)MVSR9YV3K9)_: +MZST7$5'22"N=]#+(*)/,LL@JF^QRR"F7W/*(EE<^^1504"&%Q2BBJ&***Z&D +M4DH+RBBKG/(JJ"A6)955454UU=40IZ9::JNCKGKJ:Z"A1AIKHJEFFFNAI59: +M:Z.M=MKKH*-..NNBJVZZB]=#3PEZZ:V/OOKI;X"!!AELB$1##9-DN!&2C31* +MBM'&&&N<\2:8:)+)IIAJFNEFF&F6V>:8Z[0_7GGLJ8>>>>&U-U[ZX'VDFA-. +M.NNY4[[XYI/??D:JNNF^'Y:['1D0&>B1=S[[Y:,KD:*18@[;9(.##EAJO?UA +M85@4%H>H4-I1QQRQ-B2&KB$A]+?,RI`S1(>X$!MB@M#`$\=#GQ`?DD-2&!2Z +MA!0K[+/..>>=^??719=<#@5"UK`@S+?*A3#/-0]<=]57-^Q,/;?<=<\=;_VS +)P%P<52$``40-$+7$`%*>D&>3327=)("4A(HU@H=K<+%V[5'V#AC`O'+_#G +M/`=(()$D))D44CG#6+;7;898]]#CCD!C\9@G/.49SWG!2U[QFC>\Y1WO^F.F2EYRST3:S;#?;:6OM,M=N\YRTQDXO\(=_ +M\3U^Q,](C!JG#/LLC*-8BM,=\.\QQP>:J?/XZ +8;Y.]%CAKO8NV>,4&9ZSC=YQR^A\'$$.V +EOT + +<<'EOT', # Bit 58: cp852 +M>)P%P=M/S@$%)Z.1%^BG',HG8M2#L^C)]63ZJFG(M+!N;,+ +M%]WB'^C"QH:MBRYLKIBMF7'#*H=FS&9FN6%LIJW7"Q`.88030211+&,Y*UB) +MBVABB,5-'*N()X'5K&$M'A))(ID44DEC'>M)9P,;R<#8Q&8RV<)6LLAF&]O9 +MP4YVL9L]Y+"7?>SG`+GD<9!\"BBDB&)***6,<@YQF`HJ.4(5U1SE&,!@(T;B[00R]]]'.12USF +M"E>YQG4&&&2(848898QI_O.=+WQ5KZ+XIE)^R"L?/S7`JDSEJE10K?+S7K7J8Y%YA9CBD3/O?'"FG3GN\9?' +13'+?>:=^U:O!)IA8`NR$89P` +EOT + +<<'EOT', # Bit 59: cp775 +M>)P%PQ@)[LHHYP**@D09#=[V$L5^ZBFAOT@[30(A&CA"FB:,TO87YUJ\/"5F_MUF81J[4N#2I&"?(H785J48\Z-61N +M<]F8C=IUNV$C?."KJE7!;^9XHDH5JT2E\BNH9`4<#S,\6Q33#Q'V@>75\` +EOT + +<<'EOT', # Bit 60: cp737 +M>)P%P65W#@``AN';'FT8AFV&>>V1T]/3-;WN[M*=\\%QQMG!-IVOH]LFIKOK +M[_CJNH`.A"`ZTHG.=*$KW>A.#T+I22]Z$T8?^M*/*8RC>G,8":SF$T"][&,_]6I2LUIT0B=U +M2J=U1F=U3N=U01=U29<5U!5=U35=UPW=U"W=5JO:]%A/]%3/U*[G>J&7>J77 +M>J.W>J?W^J!/^JC/^J*O^J;O;G*S6QSB6!>[Q!E.=ZF3G>E\YSG780YWO.,< +M;9S@`A12(I#T0%5-/FX,$_P-K +#(&Q0 +EOT + +undef, # Bit 61: cp708 (unknown) + +<<'EOT', # Bit 62: cp850 +M>)P%P5-C$&```,#;UC&5[JV6WW++=0P^]5G\@V[9MV[;M.T2(E$QR +M*:242FIII)5.E/0RR"B3S++(*IOL@@XXZZ:R+KKKIKH>>>NFMC[X2]=/?``,-,M@00R499K@11AIE +MM#'&&F>\"2::9+(IIIIFNAF.^>V%AQZ[[XEG7GKEN7?>>NVP(TYXZJC/OOKH +MEQ_^N>JV[Q:Y[EK$7`^\\2+/;>>2>=)P%P3=,%&``AN$7?FR(>BH@(.#Q'Y^*(C;LV!LJ*+VW@SNZO1?`P<&!`6%Q +M1`D8RR!&8H(03$2,HXN3B4#LC5@B`4GT>0`??#'X,8G)3&$JT_!G.@',8":S +M<#";.TED'_LY0!+)'.00*:221CH9 +M9))%-CGDDD<^!1121#$EN"FE#`]>RJF@DBJJJ:&6PQSA*,`Y$WQBF+<,\HX/?.8+'QGA.U_IYQDO>,\`O_G#3_XRQC]> +M\9J;M-'NO.73S!#?^,4X/WC)';KIWS4,;:09,EXVQ;CJMUWI<3E<4HS:1 +1^S:41S;,V<`#M=+Z'T-Z<84` +EOT + +); + +# The following taken directly from OT Spec: + +@ranges = ( + [ 0, "Basic Latin", "0000-007F" ], + [ 1, "Latin-1 Supplement", "0080-00FF" ], + [ 2, "Latin Extended-A", "0100-017F" ], + [ 3, "Latin Extended-B", "0180-024F" ], + [ 4, "IPA Extensions", "0250-02AF" ], + [ 4, "Phonetic Extensions", "1D00-1D7F" ], + [ 4, "Phonetic Extensions Supplement", "1D80-1DBF" ], + [ 5, "Spacing Modifier Letters", "02B0-02FF" ], + [ 5, "Modifier Tone Letters", "A700-A71F" ], + [ 6, "Combining Diacritical Marks", "0300-036F" ], + [ 6, "Combining Diacritical Marks Supplement", "1DC0-1DFF" ], + [ 7, "Greek and Coptic", "0370-03FF" ], + [ 8, "Coptic", "2C80-2CFF" ], + [ 9, "Cyrillic", "0400-04FF" ], + [ 9, "Cyrillic Supplement", "0500-052F" ], + [ 9, "Cyrillic Extended-A", "2DE0-2DFF" ], + [ 9, "Cyrillic Extended-B", "A640-A69F" ], + [ 10, "Armenian", "0530-058F" ], + [ 11, "Hebrew", "0590-05FF" ], + [ 12, "Vai", "A500-A63F" ], + [ 13, "Arabic", "0600-06FF" ], + [ 13, "Arabic Supplement", "0750-077F" ], + [ 14, "NKo", "07C0-07FF" ], + [ 15, "Devanagari", "0900-097F" ], + [ 16, "Bengali", "0980-09FF" ], + [ 17, "Gurmukhi", "0A00-0A7F" ], + [ 18, "Gujarati", "0A80-0AFF" ], + [ 19, "Oriya", "0B00-0B7F" ], + [ 20, "Tamil", "0B80-0BFF" ], + [ 21, "Telugu", "0C00-0C7F" ], + [ 22, "Kannada", "0C80-0CFF" ], + [ 23, "Malayalam", "0D00-0D7F" ], + [ 24, "Thai", "0E00-0E7F" ], + [ 25, "Lao", "0E80-0EFF" ], + [ 26, "Georgian", "10A0-10FF" ], + [ 26, "Georgian Supplement", "2D00-2D2F" ], + [ 27, "Balinese", "1B00-1B7F" ], + [ 28, "Hangul Jamo", "1100-11FF" ], + [ 29, "Latin Extended Additional", "1E00-1EFF" ], + [ 29, "Latin Extended-C", "2C60-2C7F" ], + [ 29, "Latin Extended-D", "A720-A7FF" ], + [ 30, "Greek Extended", "1F00-1FFF" ], + [ 31, "General Punctuation", "2000-206F" ], + [ 31, "Supplemental Punctuation", "2E00-2E7F" ], + [ 32, "Superscripts And Subscripts", "2070-209F" ], + [ 33, "Currency Symbols", "20A0-20CF" ], + [ 34, "Combining Diacritical Marks For Symbols", "20D0-20FF" ], + [ 35, "Letterlike Symbols", "2100-214F" ], + [ 36, "Number Forms", "2150-218F" ], + [ 37, "Arrows", "2190-21FF" ], + [ 37, "Supplemental Arrows-A", "27F0-27FF" ], + [ 37, "Supplemental Arrows-B", "2900-297F" ], + [ 37, "Miscellaneous Symbols and Arrows", "2B00-2BFF" ], + [ 38, "Mathematical Operators", "2200-22FF" ], + [ 38, "Supplemental Mathematical Operators", "2A00-2AFF" ], + [ 38, "Miscellaneous Mathematical Symbols-A", "27C0-27EF" ], + [ 38, "Miscellaneous Mathematical Symbols-B", "2980-29FF" ], + [ 39, "Miscellaneous Technical", "2300-23FF" ], + [ 40, "Control Pictures", "2400-243F" ], + [ 41, "Optical Character Recognition", "2440-245F" ], + [ 42, "Enclosed Alphanumerics", "2460-24FF" ], + [ 43, "Box Drawing", "2500-257F" ], + [ 44, "Block Elements", "2580-259F" ], + [ 45, "Geometric Shapes", "25A0-25FF" ], + [ 46, "Miscellaneous Symbols", "2600-26FF" ], + [ 47, "Dingbats", "2700-27BF" ], + [ 48, "CJK Symbols And Punctuation", "3000-303F" ], + [ 49, "Hiragana", "3040-309F" ], + [ 50, "Katakana", "30A0-30FF" ], + [ 50, "Katakana Phonetic Extensions", "31F0-31FF" ], + [ 51, "Bopomofo", "3100-312F" ], + [ 51, "Bopomofo Extended", "31A0-31BF" ], + [ 52, "Hangul Compatibility Jamo", "3130-318F" ], + [ 53, "Phags-pa", "A840-A87F" ], + [ 54, "Enclosed CJK Letters And Months", "3200-32FF" ], + [ 55, "CJK Compatibility", "3300-33FF" ], + [ 56, "Hangul Syllables", "AC00-D7AF" ], + [ 57, "Non-Plane 0 * ", "D800-DFFF" ], + [ 58, "Phoenician", "10900-1091F" ], + [ 59, "CJK Unified Ideographs", "4E00-9FFF" ], + [ 59, "CJK Radicals Supplement", "2E80-2EFF" ], + [ 59, "Kangxi Radicals", "2F00-2FDF" ], + [ 59, "Ideographic Description Characters", "2FF0-2FFF" ], + [ 59, "CJK Unified Ideographs Extension A", "3400-4DBF" ], + [ 59, "CJK Unified Ideographs Extension B", "20000-2A6DF" ], + [ 59, "Kanbun", "3190-319F" ], + [ 60, "Private Use Area (plane 0)", "E000-F8FF" ], + [ 61, "CJK Strokes", "31C0-31EF" ], + [ 61, "CJK Compatibility Ideographs", "F900-FAFF" ], + [ 61, "CJK Compatibility Ideographs Supplement", "2F800-2FA1F" ], + [ 62, "Alphabetic Presentation Forms", "FB00-FB4F" ], + [ 63, "Arabic Presentation Forms-A", "FB50-FDFF" ], + [ 64, "Combining Half Marks", "FE20-FE2F" ], + [ 65, "Vertical Forms", "FE10-FE1F" ], + [ 65, "CJK Compatibility Forms", "FE30-FE4F" ], + [ 66, "Small Form Variants", "FE50-FE6F" ], + [ 67, "Arabic Presentation Forms-B", "FE70-FEFF" ], + [ 68, "Halfwidth And Fullwidth Forms", "FF00-FFEF" ], + [ 69, "Specials", "FFF0-FFFF" ], + [ 70, "Tibetan", "0F00-0FFF" ], + [ 71, "Syriac", "0700-074F" ], + [ 72, "Thaana", "0780-07BF" ], + [ 73, "Sinhala", "0D80-0DFF" ], + [ 74, "Myanmar", "1000-109F" ], + [ 75, "Ethiopic", "1200-137F" ], + [ 75, "Ethiopic Supplement", "1380-139F" ], + [ 75, "Ethiopic Extended", "2D80-2DDF" ], + [ 76, "Cherokee", "13A0-13FF" ], + [ 77, "Unified Canadian Aboriginal Syllabics", "1400-167F" ], + [ 78, "Ogham", "1680-169F" ], + [ 79, "Runic", "16A0-16FF" ], + [ 80, "Khmer", "1780-17FF" ], + [ 80, "Khmer Symbols", "19E0-19FF" ], + [ 81, "Mongolian", "1800-18AF" ], + [ 82, "Braille Patterns", "2800-28FF" ], + [ 83, "Yi Syllables", "A000-A48F" ], + [ 83, "Yi Radicals", "A490-A4CF" ], + [ 84, "Tagalog", "1700-171F" ], + [ 85, "Hanunoo", "1720-173F" ], + [ 85, "Buhid", "1740-175F" ], + [ 85, "Tagbanwa", "1760-177F" ], + [ 85, "Old Italic", "10300-1032F" ], + [ 86, "Gothic", "10330-1034F" ], + [ 87, "Deseret", "10400-1044F" ], + [ 88, "Byzantine Musical Symbols", "1D000-1D0FF" ], + [ 88, "Musical Symbols", "1D100-1D1FF" ], + [ 88, "Ancient Greek Musical Notation", "1D200-1D24F" ], + [ 89, "Mathematical Alphanumeric Symbols", "1D400-1D7FF" ], + [ 90, "Private Use (plane 15)", "FF000-FFFFD" ], + [ 90, "Private Use (plane 16)", "100000-10FFFD" ], + [ 91, "Variation Selectors", "FE00-FE0F" ], + [ 91, "Variation Selectors Supplement", "E0100-E01EF" ], + [ 92, "Tags", "E0000-E007F" ], + [ 93, "Limbu", "1900-194F" ], + [ 94, "Tai Le", "1950-197F" ], + [ 95, "New Tai Lue", "1980-19DF" ], + [ 96, "Buginese", "1A00-1A1F" ], + [ 97, "Glagolitic", "2C00-2C5F" ], + [ 98, "Tifinagh", "2D30-2D7F" ], + [ 99, "Yijing Hexagram Symbols", "4DC0-4DFF" ], + [ 100, "Syloti Nagri", "A800-A82F" ], + [ 101, "Linear B Syllabary", "10000-1007F" ], + [ 101, "Linear B Ideograms", "10080-100FF" ], + [ 101, "Aegean Numbers", "10100-1013F" ], + [ 102, "Ancient Greek Numbers", "10140-1018F" ], + [ 103, "Ugaritic", "10380-1039F" ], + [ 104, "Old Persian", "103A0-103DF" ], + [ 105, "Shavian", "10450-1047F" ], + [ 106, "Osmanya", "10480-104AF" ], + [ 107, "Cypriot Syllabary", "10800-1083F" ], + [ 108, "Kharoshthi", "10A00-10A5F" ], + [ 109, "Tai Xuan Jing Symbols", "1D300-1D35F" ], + [ 110, "Cuneiform", "12000-123FF" ], + [ 110, "Cuneiform Numbers and Punctuation", "12400-1247F" ], + [ 111, "Counting Rod Numerals", "1D360-1D37F" ], + [ 112, "Sundanese", "1B80-1BBF" ], + [ 113, "Lepcha", "1C00-1C4F" ], + [ 114, "Ol Chiki", "1C50-1C7F" ], + [ 115, "Saurashtra", "A880-A8DF" ], + [ 116, "Kayah Li", "A900-A92F" ], + [ 117, "Rejang", "A930-A95F" ], + [ 118, "Cham", "AA00-AA5F" ], + [ 119, "Ancient Symbols", "10190-101CF" ], + [ 120, "Phaistos Disc", "101D0-101FF" ], + [ 121, "Carian", "102A0-102DF" ], + [ 121, "Lycian", "10280-1029F" ], + [ 121, "Lydian", "10920-1093F" ], + [ 122, "Domino Tiles", "1F030-1F09F" ], + [ 122, "Mahjong Tiles", "1F000-1F02F" ], + [ 123-127, "Reserved", "" ], +); + +} + + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/OTTags.pm b/lib/Font/TTF/OTTags.pm new file mode 100644 index 0000000..41f7703 --- /dev/null +++ b/lib/Font/TTF/OTTags.pm @@ -0,0 +1,1698 @@ +use utf8; # NB: This file includes non-ASCII string constants in UTF-8 encoding + +package Font::TTF::OTTags; + +=head1 NAME + +Font::TTF::OTTags - Utilities for TrueType/OpenType tags + +=head1 SYNOPSIS + + use Font::TTF::OTTags qw( %tttags %ttnames %iso639 readtagsfile ); + + # Look at built-in stuff: + $script_tag = $tttags{'SCRIPT'}{'Cypriot Syllabary'}; # returns 'cprt' + $lang_name = $ttnames{'LANGUAGE'}{'ALT '}; # returns 'Altai' + + # Map iso639-2 tag to/from OT lang tag + @isotags = $iso639{'ALT '}; # returns [ 'atv', 'alt' ] + $lang_tag = $iso639{'atv'}; # returns 'ALT ' + + # 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 + +=item SCRIPT + +retrieve script tag or name + +=item LANGUAGE + +retrieve language tag or name + +=item FEATURE + +retrieve feature tag or name + +=back + +Built-in data has been derived from the 2014-07-11 draft of the +3rd edition of ISO/IEC 14496-22 +(Information technology - Coding of audio-visual objects - Part 22: Open Font Format) +which, when finalized and approved, will replace the second edition (ISO/IEC 14496-22:2009). + +=head1 METHODS + +=cut + +use strict; +use vars qw( %tttags %ttnames %iso639 @EXPORT_OK @ISA ); +require Exporter; +@ISA = qw( Exporter ); +@EXPORT_OK = qw( %tttags %ttnames %iso639 readtagsfile); + + +# All data below derived Microsoft OpenType specification 1.6 + +%tttags = ( + +'SCRIPT' => { + "Adlam" => 'adlm', + "Ahom" => 'ahom', + "Anatolian Hieroglyphs" => 'hluw', + "Arabic" => 'arab', + "Armenian" => 'armn', + "Avestan" => 'avst', + "Balinese" => 'bali', + "Bamum" => 'bamu', + "Bassa Vah" => 'bass', + "Batak" => 'batk', + "Bengali" => 'beng', + "Bengali v.2" => 'bng2', + "Bhaiksuki" => 'bhks', + "Bopomofo" => 'bopo', + "Brahmi" => 'brah', + "Braille" => 'brai', + "Buginese" => 'bugi', + "Buhid" => 'buhd', + "Byzantine Music" => 'byzm', + "Canadian Syllabics" => 'cans', + "Carian" => 'cari', + "Caucasian Albanian" => 'aghb', + "Chakma" => 'cakm', + "Cham" => 'cham', + "Cherokee" => 'cher', + "CJK Ideographic" => 'hani', + "Coptic" => 'copt', + "Cypriot Syllabary" => 'cprt', + "Cyrillic" => 'cyrl', + "Default" => 'DFLT', + "Deseret" => 'dsrt', + "Devanagari" => 'deva', + "Devanagari v.2" => 'dev2', + "Duployan" => 'dupl', + "Egyptian hieroglyphs" => 'egyp', + "Elbasan" => 'elba', + "Ethiopic" => 'ethi', + "Georgian" => 'geor', + "Glagolitic" => 'glag', + "Gothic" => 'goth', + "Grantha" => 'gran', + "Greek" => 'grek', + "Gujarati" => 'gujr', + "Gujarati v.2" => 'gjr2', + "Gurmukhi" => 'guru', + "Gurmukhi v.2" => 'gur2', + "Hangul" => 'hang', + "Hangul Jamo" => 'jamo', + "Hanunoo" => 'hano', + "Hatran" => 'hatr', + "Hebrew" => 'hebr', + "Hiragana" => 'kana', + "Imperial Aramaic" => 'armi', + "Inscriptional Pahlavi" => 'phli', + "Inscriptional Parthian" => 'prti', + "Javanese" => 'java', + "Kaithi" => 'kthi', + "Kannada" => 'knda', + "Kannada v.2" => 'knd2', + "Katakana" => 'kana', + "Kayah Li" => 'kali', + "Kharosthi" => 'khar', + "Khmer" => 'khmr', + "Khojki" => 'khoj', + "Khudawadi" => 'sind', + "Lao" => 'lao ', + "Latin" => 'latn', + "Lepcha" => 'lepc', + "Limbu" => 'limb', + "Linear A" => 'lina', + "Linear B" => 'linb', + "Lisu (Fraser)" => 'lisu', + "Lycian" => 'lyci', + "Lydian" => 'lydi', + "Mahajani" => 'mahj', + "Marchen" => 'marc', + "Malayalam" => 'mlym', + "Malayalam v.2" => 'mlm2', + "Mandaic, Mandaean" => 'mand', + "Manichaean" => 'mani', + "Mathematical Alphanumeric Symbols" => 'math', + "Meitei Mayek (Meithei, Meetei)" => 'mtei', + "Mende Kikakui" => 'mend', + "Meroitic Cursive" => 'merc', + "Meroitic Hieroglyphs" => 'mero', + "Miao" => 'plrd', + "Modi" => 'modi', + "Mongolian" => 'mong', + "Mro" => 'mroo', + "Multani" => 'mult', + "Musical Symbols" => 'musc', + "Myanmar" => 'mymr', + "Myanmar v.2" => 'mym2', + "Nabataean" => 'nbat', + "Newa" => 'newa', + "New Tai Lue" => 'talu', + "N'Ko" => 'nko ', + "Odia (formerly Oriya)" => 'orya', + "Odia (formerly Oriya) v.2" => 'ory2', + "Ogham" => 'ogam', + "Ol Chiki" => 'olck', + "Old Italic" => 'ital', + "Old Hungarian" => 'hung', + "Old North Arabian" => 'narb', + "Old Permic" => 'perm', + "Old Persian Cuneiform" => 'xpeo', + "Old South Arabian" => 'sarb', + "Old Turkic, Orkhon Runic" => 'orkh', + "Osage" => 'osge', + "Osmanya" => 'osma', + "Pahawh Hmong" => 'hmng', + "Palmyrene" => 'palm', + "Pau Cin Hau" => 'pauc', + "Phags-pa" => 'phag', + "Phoenician" => 'phnx', + "Psalter Pahlavi" => 'phlp', + "Rejang" => 'rjng', + "Runic" => 'runr', + "Samaritan" => 'samr', + "Saurashtra" => 'saur', + "Sharada" => 'shrd', + "Shavian" => 'shaw', + "Siddham" => 'sidd', + "Sign Writing" => 'sgnw', + "Sinhala" => 'sinh', + "Sora Sompeng" => 'sora', + "Sumero-Akkadian Cuneiform" => 'xsux', + "Sundanese" => 'sund', + "Syloti Nagri" => 'sylo', + "Syriac" => 'syrc', + "Tagalog" => 'tglg', + "Tagbanwa" => 'tagb', + "Tai Le" => 'tale', + "Tai Tham (Lanna)" => 'lana', + "Tai Viet" => 'tavt', + "Takri" => 'takr', + "Tamil" => 'taml', + "Tamil v.2" => 'tml2', + "Tangut" => 'tang', + "Telugu" => 'telu', + "Telugu v.2" => 'tel2', + "Thaana" => 'thaa', + "Thai" => 'thai', + "Tibetan" => 'tibt', + "Tifinagh" => 'tfng', + "Tirhuta" => 'tirh', + "Ugaritic Cuneiform" => 'ugar', + "Vai" => 'vai ', + "Warang Citi" => 'wara', + "Yi" => 'yi ', + }, + +'LANGUAGE' => { + "Aari" => 'ARI ', + "Abaza" => 'ABA ', + "Abkhazian" => 'ABK ', + "Achi" => 'ACR ', + "Acholi" => 'ACH ', + "Adyghe" => 'ADY ', + "Afar" => 'AFR ', + "Afrikaans" => 'AFK ', + "Agaw" => 'AGW ', + "Aiton" => 'AIO ', + "Akan" => 'AKA ', + "Albanian" => 'SQI ', + "Alsatian" => 'ALS ', + "Altai" => 'ALT ', + "Amharic" => 'AMH ', + "Anglo-Saxon" => 'ANG ', + "Arabic" => 'ARA ', + "Aragonese" => 'ARG ', + "Arakwal" => 'RKW ', + "Armenian East" => 'HYE0', + "Armenian" => 'HYE ', + "Aromanian" => 'RUP ', + "Arpitan" => 'FRP ', + "Assamese" => 'ASM ', + "Asturian" => 'AST ', + "Athapaskan" => 'ATH ', + "Avar" => 'AVR ', + "Awadhi" => 'AWA ', + "Aymara" => 'AYM ', + "Azerbaijani" => 'AZE ', + "Badaga" => 'BAD ', + "Baghelkhandi" => 'BAG ', + "Bagri" => 'BGQ ', + "Balante" => 'BLN ', + "Balinese" => 'BAN ', + "Balkar" => 'BAL ', + "Balti" => 'BLT ', + "Baluchi" => 'BLI ', + "Bambara (Bamanankan)" => 'BMB ', + "Bamileke" => 'BML ', + "Banda" => 'BAD0', + "Bandjalang" => 'BDY ', + "Baoulé" => 'BAU ', + "Bashkir" => 'BSH ', + "Basque" => 'EUQ ', + "Batak Simalungun" => 'BTS ', + "Batak Toba" => 'BBC ', + "Bavarian" => 'BAR ', + "Belarusian" => 'BEL ', + "Bemba" => 'BEM ', + "Bench" => 'BCH ', + "Bengali" => 'BEN ', + "Berber" => 'BBR ', + "Beti" => 'BTI ', + "Bhili" => 'BHI ', + "Bhojpuri" => 'BHO ', + "Bible Cree" => 'BCR ', + "Bikol" => 'BIK ', + "Bilen" => 'BIL ', + "Bishnupriya Manipuri" => 'BPY ', + "Bislama" => 'BIS ', + "Blackfoot" => 'BKF ', + "Bodo" => 'BRX ', + "Bosnian" => 'BOS ', + "Bouyei" => 'PCC ', + "Brahui" => 'BRH ', + "Braj Bhasha" => 'BRI ', + "Breton" => 'BRE ', + "Bugis" => 'BUG ', + "Bulgarian" => 'BGR ', + "Burmese" => 'BRM ', + "Burushaski" => 'BSK ', + "Cajun French" => 'FRC ', + "Carrier" => 'CRR ', + "Catalan" => 'CAT ', + "Cebuano" => 'CEB ', + "Central Yupik" => 'ESU ', + "Chaha Gurage" => 'CHG ', + "Chamorro" => 'CHA ', + "Chattisgarhi" => 'CHH ', + "Chechen" => 'CHE ', + "Cherokee" => 'CHR ', + "Cheyenne" => 'CHY ', + "Chichewa (Chewa, Nyanja)" => 'CHI ', + "Chiga" => 'CGG ', + "Chin" => 'QIN ', + "Chinese Phonetic" => 'ZHP ', + "Chinese Simplified" => 'ZHS ', + "Chinese Traditional" => 'ZHT ', + "Chinese, Hong Kong SAR" => 'ZHH ', + "Chipewyan" => 'CHP ', + "Chittagonian" => 'CTG ', + "Choctaw" => 'CHO ', + "Chukchi" => 'CHK ', + "Church Slavonic" => 'CSL ', + "Chuukese" => 'CHK0', + "Chuvash" => 'CHU ', + "Comorian" => 'CMR ', + "Coptic" => 'COP ', + "Cornish" => 'COR ', + "Corsican" => 'COS ', + "Cree" => 'CRE ', + "Creoles" => 'CPP ', + "Crimean Tatar" => 'CRT ', + "Croatian" => 'HRV ', + "Czech" => 'CSY ', + "Dan" => 'DNJ ', + "Dangme" => 'DNG ', + "Danish" => 'DAN ', + "Dargwa" => 'DAR ', + "Dari" => 'DRI ', + "Dayi" => 'DAX ', + "Dehong Dai" => 'TDD ', + "Dhangu" => 'DHG ', + "Dhuwal" => 'DUJ ', + "Dimli" => 'DIQ ', + "Dinka" => 'DNK ', + "Divehi (Dhivehi, Maldivian) (deprecated)" => 'DHV ', + "Divehi (Dhivehi, Maldivian)" => 'DIV ', + "Djambarrpuyngu" => 'DJR0', + "Dogri" => 'DGO ', + "Dogri" => 'DGR ', + "Dungan" => 'DUN ', + "Dutch (Flemish)" => 'FLE ', + "Dutch" => 'NLD ', + "Dzongkha" => 'DZN ', + "Eastern Cree" => 'ECR ', + "Eastern Maninkakan" => 'EMK ', + "Eastern Pwo Karen" => 'KJP ', + "Ebira" => 'EBI ', + "Edo" => 'EDO ', + "Efik" => 'EFI ', + "English" => 'ENG ', + "Erzya" => 'ERZ ', + "Esperanto" => 'NTO ', + "Estonian" => 'ETI ', + "Even" => 'EVN ', + "Evenki" => 'EVK ', + "Ewe" => 'EWE ', + "Fang" => 'FAN0', + "Fanti" => 'FAT ', + "Faroese" => 'FOS ', + "Fe'fe'" => 'FMP ', + "Fijian" => 'FJI ', + "Filipino" => 'PIL ', + "Finnish" => 'FIN ', + "Fon" => 'FON ', + "Forest Nenets" => 'FNE ', + "French Antillean" => 'FAN ', + "French" => 'FRA ', + "Frisian" => 'FRI ', + "Friulian" => 'FRL ', + "Fulah" => 'FUL ', + "Futa" => 'FTA ', + "Ga" => 'GAD ', + "Gagauz" => 'GAG ', + "Galician" => 'GAL ', + "Ganda" => 'LUG ', + "Garhwali" => 'GAW ', + "Garo" => 'GRO ', + "Garshuni" => 'GAR ', + "Ge'ez" => 'GEZ ', + "Georgian" => 'KAT ', + "German" => 'DEU ', + "Gilaki" => 'GLK ', + "Gilyak" => 'GIL ', + "Githabul" => 'GIH ', + "Gogo" => 'GOG ', + "Gondi" => 'GON ', + "Greek" => 'ELL ', + "Greenlandic" => 'GRN ', + "Guarani" => 'GUA ', + "Gujarati" => 'GUJ ', + "Gumatj" => 'GNN ', + "Gumuz" => 'GMZ ', + "Gupapuyngu" => 'GUF ', + "Gusii" => 'GUZ ', + "Haitian (Haitian Creole)" => 'HAI ', + "Halam" => 'HAL ', + "Hammer-Banna" => 'HBN ', + "Harari" => 'HRI ', + "Harauti" => 'HAR ', + "Haryanvi" => 'BGC ', + "Hausa" => 'HAU ', + "Hawaiian" => 'HAW ', + "Haya" => 'HAY ', + "Hazaragi" => 'HAZ ', + "Hebrew" => 'IWR ', + "Herero" => 'HER ', + "High Mari" => 'HMA ', + "Hiligaynon" => 'HIL ', + "Hindi" => 'HIN ', + "Hindko" => 'HND ', + "Hiri Motu" => 'HMO ', + "Hmong Daw" => 'MWW ', + "Hmong" => 'HMN ', + "Ho" => 'HO ', + "Hungarian" => 'HUN ', + "Iban" => 'IBA ', + "Ibibio" => 'IBB ', + "Icelandic" => 'ISL ', + "Ido" => 'IDO ', + "Igbo" => 'IBO ', + "Ijo languages" => 'IJO ', + "Ilokano" => 'ILO ', + "Inari Sami" => 'ISM ', + "Indonesian" => 'IND ', + "Ingush" => 'ING ', + "Interlingua" => 'INA ', + "Interlingue" => 'ILE ', + "Inuktitut" => 'INU ', + "Inupiat" => 'IPK ', + "Irish Traditional" => 'IRT ', + "Irish" => 'IRI ', + "Italian" => 'ITA ', + "Jamaican Creole" => 'JAM ', + "Japanese" => 'JAN ', + "Javanese" => 'JAV ', + "Jula" => 'JUL ', + "Kabardian" => 'KAB ', + "Kabuverdianu (Crioulo)" => 'KEA ', + "Kabyle" => 'KAB0', + "Kachchi" => 'KAC ', + "Kalenjin" => 'KAL ', + "Kalmyk" => 'KLM ', + "Kamba" => 'KMB ', + "Kanauji" => 'BJJ ', + "Kannada" => 'KAN ', + "Kanuri" => 'KNR ', + "Kaqchikel" => 'CAK ', + "Karachay" => 'KAR ', + "Karaim" => 'KRM ', + "Karakalpak" => 'KRK ', + "Karelian" => 'KRL ', + "Karen" => 'KRN ', + "Kashmiri" => 'KSH ', + "Kashubian" => 'CSB ', + "Kazakh" => 'KAZ ', + "Kebena" => 'KEB ', + "Kekchi" => 'KEK ', + "Khakass" => 'KHA ', + "Khamti Shan" => 'KHT ', + "Khanty-Kazim" => 'KHK ', + "Khanty-Shurishkar" => 'KHS ', + "Khanty-Vakhi" => 'KHV ', + "Khasi" => 'KSI ', + "Khmer" => 'KHM ', + "Khorasani Turkic" => 'KMZ ', + "Khowar" => 'KHW ', + "Khutsuri Georgian" => 'KGE ', + "Kikongo" => 'KON ', + "Kikuyu (Gikuyu)" => 'KIK ', + "Kildin Sami" => 'KSM ', + "Kinyarwanda" => 'RUA ', + "Kirghiz (Kyrgyz)" => 'KIR ', + "Kiribati (Gilbertese)" => 'GIL0', + "Kirmanjki" => 'KIU ', + "Kisii" => 'KIS ', + "Kituba" => 'MKW ', + "Kodagu" => 'KOD ', + "Kokni" => 'KKN ', + "Komi" => 'KOM ', + "Komi-Permyak" => 'KOP ', + "Komi-Zyrian" => 'KOZ ', + "Komo" => 'KMO ', + "Komso" => 'KMS ', + "Kongo" => 'KON0', + "Konkani" => 'KOK ', + "Koorete" => 'KRT ', + "Korean Old Hangul" => 'KOH ', + "Korean" => 'KOR ', + "Koryak" => 'KYK ', + "Kosraean" => 'KOS ', + "Kpelle (Guinea)" => 'GKP ', + "Kpelle (Liberia)" => 'XPE ', + "Kpelle" => 'KPL ', + "Krio" => 'KRI ', + "Krymchak" => 'JCT ', + "Kuanyama" => 'KUA ', + "Kui" => 'KUI ', + "Kulvi" => 'KUL ', + "Kumaoni" => 'KMN ', + "Kumyk" => 'KUM ', + "Kurdish" => 'KUR ', + "Kurukh" => 'KUU ', + "Kuy" => 'KUY ', + "K'iche'" => 'QUC ', + "L-Cree" => 'LCR ', + "Ladakhi" => 'LDK ', + "Ladin" => 'LAD ', + "Ladino" => 'JUD ', + "Lahuli" => 'LAH ', + "Lak" => 'LAK ', + "Laki" => 'LKI ', + "Lambani" => 'LAM ', + "Lampung" => 'LJP ', + "Lao" => 'LAO ', + "Latin" => 'LAT ', + "Latvian" => 'LVI ', + "Laz" => 'LAZ ', + "Lezgi" => 'LEZ ', + "Ligurian" => 'LIJ ', + "Limbu" => 'LMB ', + "Limburgish" => 'LIM ', + "Lingala" => 'LIN ', + "Lisu" => 'LIS ', + "Lithuanian" => 'LTH ', + "Lojban" => 'JBO ', + "Loma" => 'LOM ', + "Lombard" => 'LMO ', + "Lomwe" => 'LMW ', + "Low Mari" => 'LMA ', + "Low Saxon" => 'NDS ', + "Lower Sorbian" => 'LSB ', + "Luba-Katanga" => 'LUB ', + "Luba-Lulua" => 'LUA ', + "Lule Sami" => 'LSM ', + "Luo" => 'LUO ', + "Luri" => 'LRC ', + "Luxembourgish" => 'LTZ ', + "Luyia" => 'LUH ', + "Lü" => 'XBD ', + "Macedonian" => 'MKD ', + "Madura" => 'MAD ', + "Magahi" => 'MAG ', + "Maithili" => 'MTH ', + "Majang" => 'MAJ ', + "Makasar" => 'MKR ', + "Makhuwa" => 'MAK ', + "Makonde" => 'KDE ', + "Malagasy" => 'MLG ', + "Malay" => 'MLY ', + "Malayalam Reformed" => 'MLR ', + "Malayalam Traditional" => 'MAL ', + "Male" => 'MLE ', + "Malinke" => 'MLN ', + "Maltese" => 'MTS ', + "Mam" => 'MAM ', + "Manchu" => 'MCH ', + "Mandar" => 'MDR ', + "Mandinka" => 'MND ', + "Maninka" => 'MNK ', + "Manipuri" => 'MNI ', + "Mansi" => 'MAN ', + "Manx" => 'MNX ', + "Maori" => 'MRI ', + "Mapudungun" => 'MAP ', + "Marathi" => 'MAR ', + "Marshallese" => 'MAH ', + "Marwari" => 'MAW ', + "Mayan" => 'MYN ', + "Mazanderani" => 'MZN ', + "Mbembe Tigon" => 'NZA ', + "Mbo" => 'MBO ', + "Mbundu" => 'MBN ', + "Me'en" => 'MEN ', + "Medumba" => 'BYV ', + "Mende" => 'MDE ', + "Meru" => 'MER ', + "Mewati" => 'WTM ', + "Minangkabau" => 'MIN ', + "Minjangbal" => 'XJB ', + "Mirandese" => 'MWL ', + "Mizo" => 'MIZ ', + "Mohawk" => 'MOH ', + "Moksha" => 'MOK ', + "Moldavian" => 'MOL ', + "Mon" => 'MON ', + "Mongolian" => 'MNG ', + "Moose Cree" => 'MCR ', + "Morisyen" => 'MFE ', + "Moroccan" => 'MOR ', + "Mossi" => 'MOS ', + "Mundari" => 'MUN ', + "Muscogee" => 'MUS ', + "N'Ko" => 'NKO ', + "N-Cree" => 'NCR ', + "Naga-Assamese" => 'NAG ', + "Nagari" => 'NGR ', + "Nahuatl" => 'NAH ', + "Nanai" => 'NAN ', + "Naskapi" => 'NAS ', + "Nauruan" => 'NAU ', + "Navajo" => 'NAV ', + "Ndau" => 'NDC ', + "Ndebele" => 'NDB ', + "Ndonga" => 'NDG ', + "Neapolitan" => 'NAP ', + "Nepali" => 'NEP ', + "Newari" => 'NEW ', + "Ngbaka" => 'NGA ', + "Nigerian Fulfulde" => 'FUV ', + "Nimadi" => 'NOE ', + "Nisi" => 'NIS ', + "Niuean" => 'NIU ', + "Nogai" => 'NOG ', + "Norfolk" => 'PIH ', + "North Slavey" => 'SCS ', + "Northern Sami" => 'NSM ', + "Northern Thai" => 'NTA ', + "Norway House Cree" => 'NHC ', + "Norwegian Nynorsk (Nynorsk, Norwegian)" => 'NYN ', + "Norwegian" => 'NOR ', + "Novial" => 'NOV ', + "Nyamwezi" => 'NYM ', + "Nyankole" => 'NKL ', + "Occitan" => 'OCI ', + "Odia (formerly Oriya)" => 'ORI ', + "Oji-Cree" => 'OCR ', + "Ojibway" => 'OJB ', + "Old Irish" => 'SGA ', + "Oromo" => 'ORO ', + "Ossetian" => 'OSS ', + "Pa'o Karen" => 'BLK ', + "Palauan" => 'PAU ', + "Palaung" => 'PLG ', + "Palestinian Aramaic" => 'PAA ', + "Pali" => 'PAL ', + "Palpa" => 'PAP ', + "Pampangan" => 'PAM ', + "Pangasinan" => 'PAG ', + "Papiamentu" => 'PAP0', + "Pashto" => 'PAS ', + "Pennsylvania German" => 'PDC ', + "Persian" => 'FAR ', + "Phake" => 'PHK ', + "Phonetic transcription - Americanist conventions" => 'APPH', + "Phonetic transcription - IPA conventions" => 'IPPH', + "Picard" => 'PCD ', + "Piemontese" => 'PMS ', + "Pocomchi" => 'POH ', + "Pohnpeian" => 'PON ', + "Polish" => 'PLK ', + "Polytonic Greek" => 'PGR ', + "Portuguese" => 'PTG ', + "Provencal" => 'PRO ', + "Punjabi" => 'PAN ', + "Quechua (Bolivia)" => 'QUH ', + "Quechua (Ecuador)" => 'QVI ', + "Quechua (Peru)" => 'QWH ', + "Quechua" => 'QUZ ', + "R-Cree" => 'RCR ', + "Rajasthani" => 'RAJ ', + "Rakhine" => 'ARK ', + "Rarotongan" => 'RAR ', + "Rejang" => 'REJ ', + "Riang" => 'RIA ', + "Ripuarian" => 'KSH0', + "Ritarungo" => 'RIT ', + "Romanian" => 'ROM ', + "Romansh" => 'RMS ', + "Romany" => 'ROY ', + "Rotuman" => 'RTM ', + "Rundi" => 'RUN ', + "Russian Buriat" => 'RBU ', + "Russian" => 'RUS ', + "Rusyn" => 'RSY ', + "Sadri" => 'SAD ', + "Sakha" => 'YAK ', + "Samoan" => 'SMO ', + "Samogitian" => 'SGS ', + "San Blas Kuna" => 'CUK ', + "Sango" => 'SGO ', + "Sanskrit" => 'SAN ', + "Santali" => 'SAT ', + "Sardinian" => 'SRD ', + "Sasak" => 'SAS ', + "Saterland Frisian" => 'STQ ', + "Sayisi" => 'SAY ', + "Scots" => 'SCO ', + "Scottish Gaelic (Gaelic)" => 'GAE ', + "Sekota" => 'SEK ', + "Selkup" => 'SEL ', + "Sena" => 'SNA ', + "Seraiki" => 'SRK ', + "Serbian" => 'SRB ', + "Serer" => 'SRR ', + "Shan" => 'SHN ', + "Shona" => 'SNA0', + "Sibe" => 'SIB ', + "Sicilian" => 'SCN ', + "Sidamo" => 'SID ', + "Silesian" => 'SZL ', + "Silte Gurage" => 'SIG ', + "Sindhi" => 'SND ', + "Sinhala (Sinhalese)" => 'SNH ', + "Skolt Sami" => 'SKS ', + "Slavey" => 'SLA ', + "Slovak" => 'SKY ', + "Slovenian" => 'SLV ', + "Sodo Gurage" => 'SOG ', + "Soga" => 'XOG ', + "Somali" => 'SML ', + "Songe" => 'SOP ', + "Soninke" => 'SNK ', + "Sotho, Northern" => 'NSO ', + "Sotho, Southern" => 'SOT ', + "South Slavey" => 'SSL ', + "Southern Kiwai" => 'KJD ', + "Southern Sami" => 'SSM ', + "Spanish" => 'ESP ', + "Standard Morrocan Tamazigh" => 'ZGH ', + "Sukuma" => 'SUK ', + "Sundanese" => 'SUN ', + "Suri" => 'SUR ', + "Sutu" => 'SXT ', + "Svan" => 'SVA ', + "Swadaya Aramaic" => 'SWA ', + "Swahili" => 'SWK ', + "Swati" => 'SWZ ', + "Swedish" => 'SVE ', + "Sylheti" => 'SYL ', + "Syriac" => 'SYR ', + "S'gaw Karen" => 'KSW ', + "TH-Cree" => 'TCR ', + "Tabasaran" => 'TAB ', + "Tachelhit" => 'SHI ', + "Tagalog" => 'TGL ', + "Tahitian" => 'THT ', + "Tajik" => 'TAJ ', + "Tamashek" => 'TMH ', + "Tamazight" => 'TZM ', + "Tamil" => 'TAM ', + "Tarifit" => 'RIF ', + "Tatar" => 'TAT ', + "Telugu" => 'TEL ', + "Temne" => 'TMN ', + "Tetum" => 'TET ', + "Thai" => 'THA ', + "Tibetan" => 'TIB ', + "Tigre" => 'TGR ', + "Tigrinya" => 'TGY ', + "Tiv" => 'TIV ', + "Todo" => 'TOD ', + "Tok Pisin" => 'TPI ', + "Toma" => 'TOD0', + "Tonga" => 'TNG ', + "Tongan" => 'TGN ', + "Torki" => 'AZB ', + "Tsonga" => 'TSG ', + "Tswana" => 'TNA ', + "Tulu" => 'TUL ', + "Tumbuka" => 'TUM ', + "Tundra Nenets" => 'TNE ', + "Turkish" => 'TRK ', + "Turkmen" => 'TKM ', + "Turoyo Aramaic" => 'TUA ', + "Tuvalu" => 'TVL ', + "Tuvin" => 'TUV ', + "Twi" => 'TWI ', + "Tzotzil" => 'TZO ', + "Tày" => 'TYZ ', + "Udmurt" => 'UDM ', + "Ukrainian" => 'UKR ', + "Umbundu" => 'UMB ', + "Upper Saxon" => 'SXU ', + "Upper Sorbian" => 'USB ', + "Urdu" => 'URD ', + "Uyghur" => 'UYG ', + "Uzbek" => 'UZB ', + "Venda" => 'VEN ', + "Venetian" => 'VEC ', + "Vietnamese" => 'VIT ', + "Vlax Romani" => 'RMY ', + "Volapük" => 'VOL ', + "Võro" => 'VRO ', + "Wa" => 'WA ', + "Wagdi" => 'WAG ', + "Walloon" => 'WLN ', + "Waray-Waray" => 'WAR ', + "Wayuu" => 'GUC ', + "Welsh" => 'WEL ', + "West-Cree" => 'WCR ', + "Western Kayah" => 'KYU ', + "Western Panjabi" => 'PNB ', + "Western Pwo Karen" => 'PWO ', + "Wolof" => 'WLF ', + "Woods Cree" => 'DCR ', + "Xhosa" => 'XHS ', + "Y-Cree" => 'YCR ', + "Yao" => 'YAO ', + "Yapese" => 'YAP ', + "Yi Classic" => 'YIC ', + "Yi Modern" => 'YIM ', + "Yiddish" => 'JII ', + "Yoruba" => 'YBA ', + "Zamboanga Chavacano" => 'CBK ', + "Zande" => 'ZND ', + "Zarma" => 'DJR ', + "Zazaki" => 'ZZA ', + "Zealandic" => 'ZEA ', + "Zhuang" => 'ZHA ', + "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', + "Character Variants 01" => 'cv01', + "Character Variants 02" => 'cv02', + "Character Variants 03" => 'cv03', + "Character Variants 04" => 'cv04', + "Character Variants 05" => 'cv05', + "Character Variants 06" => 'cv06', + "Character Variants 07" => 'cv07', + "Character Variants 08" => 'cv08', + "Character Variants 09" => 'cv09', + "Character Variants 10" => 'cv10', + "Character Variants 11" => 'cv11', + "Character Variants 12" => 'cv12', + "Character Variants 13" => 'cv13', + "Character Variants 14" => 'cv14', + "Character Variants 15" => 'cv15', + "Character Variants 16" => 'cv16', + "Character Variants 17" => 'cv17', + "Character Variants 18" => 'cv18', + "Character Variants 19" => 'cv19', + "Character Variants 20" => 'cv20', + "Character Variants 21" => 'cv21', + "Character Variants 22" => 'cv22', + "Character Variants 23" => 'cv23', + "Character Variants 24" => 'cv24', + "Character Variants 25" => 'cv25', + "Character Variants 26" => 'cv26', + "Character Variants 27" => 'cv27', + "Character Variants 28" => 'cv28', + "Character Variants 29" => 'cv29', + "Character Variants 30" => 'cv30', + "Character Variants 31" => 'cv31', + "Character Variants 32" => 'cv32', + "Character Variants 33" => 'cv33', + "Character Variants 34" => 'cv34', + "Character Variants 35" => 'cv35', + "Character Variants 36" => 'cv36', + "Character Variants 37" => 'cv37', + "Character Variants 38" => 'cv38', + "Character Variants 39" => 'cv39', + "Character Variants 40" => 'cv40', + "Character Variants 41" => 'cv41', + "Character Variants 42" => 'cv42', + "Character Variants 43" => 'cv43', + "Character Variants 44" => 'cv44', + "Character Variants 45" => 'cv45', + "Character Variants 46" => 'cv46', + "Character Variants 47" => 'cv47', + "Character Variants 48" => 'cv48', + "Character Variants 49" => 'cv49', + "Character Variants 50" => 'cv50', + "Character Variants 51" => 'cv51', + "Character Variants 52" => 'cv52', + "Character Variants 53" => 'cv53', + "Character Variants 54" => 'cv54', + "Character Variants 55" => 'cv55', + "Character Variants 56" => 'cv56', + "Character Variants 57" => 'cv57', + "Character Variants 58" => 'cv58', + "Character Variants 59" => 'cv59', + "Character Variants 60" => 'cv60', + "Character Variants 61" => 'cv61', + "Character Variants 62" => 'cv62', + "Character Variants 63" => 'cv63', + "Character Variants 64" => 'cv64', + "Character Variants 65" => 'cv65', + "Character Variants 66" => 'cv66', + "Character Variants 67" => 'cv67', + "Character Variants 68" => 'cv68', + "Character Variants 69" => 'cv69', + "Character Variants 70" => 'cv70', + "Character Variants 71" => 'cv71', + "Character Variants 72" => 'cv72', + "Character Variants 73" => 'cv73', + "Character Variants 74" => 'cv74', + "Character Variants 75" => 'cv75', + "Character Variants 76" => 'cv76', + "Character Variants 77" => 'cv77', + "Character Variants 78" => 'cv78', + "Character Variants 79" => 'cv79', + "Character Variants 80" => 'cv80', + "Character Variants 81" => 'cv81', + "Character Variants 82" => 'cv82', + "Character Variants 83" => 'cv83', + "Character Variants 84" => 'cv84', + "Character Variants 85" => 'cv85', + "Character Variants 86" => 'cv86', + "Character Variants 87" => 'cv87', + "Character Variants 88" => 'cv88', + "Character Variants 89" => 'cv89', + "Character Variants 90" => 'cv90', + "Character Variants 91" => 'cv91', + "Character Variants 92" => 'cv92', + "Character Variants 93" => 'cv93', + "Character Variants 94" => 'cv94', + "Character Variants 95" => 'cv95', + "Character Variants 96" => 'cv96', + "Character Variants 97" => 'cv97', + "Character Variants 98" => 'cv98', + "Character Variants 99" => 'cv99', + "Conjunct Form After Ro" => 'cfar', + "Conjunct Forms" => 'cjct', + "Contextual Alternates" => 'calt', + "Contextual Ligatures" => 'clig', + "Contextual Swash" => 'cswh', + "Cursive Positioning" => 'curs', + "Denominators" => 'dnom', + "Discretionary Ligatures" => 'dlig', + "Distances" => 'dist', + "Dotless Forms" => 'dtls', + "Expert Forms" => 'expt', + "Final Glyph on Line Alternates" => 'falt', + "Flattened ascent forms" => 'flac', + "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 Kanji Forms (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', + "Left-to-right glyph alternates" => 'ltra', + "Left-to-right mirrored forms" => 'ltrm', + "Lining Figures" => 'lnum', + "Localized Forms" => 'locl', + "Mark Positioning via Substitution" => 'mset', + "Mark Positioning" => 'mark', + "Mark to Mark Positioning" => 'mkmk', + "Math script style alternates" => 'ssty', + "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 Contextual Alternates" => 'rclt', + "Required Ligatures" => 'rlig', + "Right Bounds" => 'rtbd', + "Right-to-left alternates" => 'rtla', + "Right-to-left mirrored forms" => 'rtlm', + "Ruby Notation Forms" => 'ruby', + "Scientific Inferiors" => 'sinf', + "Simplified Forms" => 'smpl', + "Slashed Zero" => 'zero', + "Small Capitals From Capitals" => 'c2sc', + "Small Capitals" => 'smcp', + "Standard Ligatures" => 'liga', + "Stretching Glyph Decomposition" => 'stch', + "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', + }, + +); + +%iso639 = ( + 'ABA ' => 'abq', + 'ABK ' => 'abk', + 'ACH ' => 'ach', + 'ACR ' => 'acr', + 'ADY ' => 'ady', + 'AFK ' => 'afr', + 'AFR ' => 'aar', + 'AGW ' => 'ahg', + 'AIO ' => 'aio', + 'AKA ' => 'aka', + 'ALS ' => 'gsw', + 'ALT ' => 'atv alt', + 'AMH ' => 'amh', + 'ANG ' => 'ang', + 'ARA ' => 'ara', + 'ARG ' => 'arg', + 'ARI ' => 'aiw', + 'ARK ' => 'mhv rmz rki', + 'ASM ' => 'asm', + 'AST ' => 'ast', + 'ATH ' => 'apk apj apl apm apw nav bea sek bcr caf crx clc gwi haa chp dgr scs xsl srs ing hoi koy hup ktw mvb wlk coq ctc gce tol tuu kkz tgx tht aht tfn taa tau tcb kuu tce ttm txc', + 'AVR ' => 'ava', + 'AWA ' => 'awa', + 'AYM ' => 'aym', + 'AZB ' => 'azb', + 'AZE ' => 'aze', + 'BAD ' => 'bfq', + 'BAD0' => 'bad', + 'BAG ' => 'bfy', + 'BAL ' => 'krc', + 'BAN ' => 'ban', + 'BAR ' => 'bar', + 'BAU ' => 'bci', + 'BBC ' => 'bbc', + 'BCH ' => 'bcq', + 'BDY ' => 'bdy', + 'BEL ' => 'bel', + 'BEM ' => 'bem', + 'BEN ' => 'ben', + 'BGC ' => 'bgc', + 'BGQ ' => 'bgq', + 'BGR ' => 'bul', + 'BHI ' => 'bhi bhb', + 'BHO ' => 'bho', + 'BIK ' => 'bik bhk bcl bto cts bln', + 'BIL ' => 'byn', + 'BIS ' => 'bis', + 'BJJ ' => 'bjj', + 'BKF ' => 'bla', + 'BLI ' => 'bal', + 'BLK ' => 'blk', + 'BLN ' => 'bjt ble', + 'BLT ' => 'bft', + 'BMB ' => 'bam', + 'BOS ' => 'bos', + 'BPY ' => 'bpy', + 'BRE ' => 'bre', + 'BRH ' => 'brh', + 'BRI ' => 'bra', + 'BRM ' => 'mya', + 'BRX ' => 'brx', + 'BSH ' => 'bak', + "BSK " => 'bsk', + 'BTI ' => 'btb', + 'BTS ' => 'bts', + 'BUG ' => 'bug', + "BYV " => 'byv', + 'CAK ' => 'cak', + 'CAT ' => 'cat', + 'CBK ' => 'cbk', + 'CEB ' => 'ceb', + 'CGG ' => 'cgg', + 'CHA ' => 'cha', + 'CHE ' => 'che', + 'CHG ' => 'sgw', + 'CHH ' => 'hne', + 'CHI ' => 'nya', + 'CHK ' => 'ckt', + 'CHK0' => 'chk', + 'CHO ' => 'cho', + 'CHP ' => 'chp', + 'CHR ' => 'chr', + 'CHU ' => 'chv', + 'CHY ' => 'chy', + 'CMR ' => 'swb wlc wni zdj', + 'COP ' => 'cop', + 'COR ' => 'cor', + 'COS ' => 'cos', + 'CPP ' => 'cpp', + 'CRE ' => 'cre', + 'CRR ' => 'crx caf', + 'CRT ' => 'crh', + 'CSB ' => 'csb', + 'CSL ' => 'chu', + 'CSY ' => 'ces', + 'CTG ' => 'ctg', + 'CUK ' => 'cuk', + 'DAN ' => 'dan', + 'DAR ' => 'dar', + 'DAX ' => 'dax', + 'DCR ' => 'cwd', + 'DEU ' => 'deu', + 'DGO ' => 'dgo', + 'DGR ' => 'doi', + 'DHG ' => 'dhg', + 'DHV ' => 'div', + 'DIQ ' => 'diq', + 'DIV ' => 'div', + 'DJR ' => 'dje', + 'DJR0' => 'djr', + 'DNG ' => 'ada', + 'DNJ ' => 'dnj', + 'DNK ' => 'din', + 'DRI ' => 'prs', + 'DUJ ' => 'duj', + 'DUN ' => 'dng', + 'DZN ' => 'dzo', + 'EBI ' => 'igb', + 'ECR ' => 'crj crl', + 'EDO ' => 'bin', + 'EFI ' => 'efi', + 'ELL ' => 'ell', + 'EMK ' => 'emk', + 'ENG ' => 'eng', + 'ERZ ' => 'myv', + 'ESP ' => 'spa', + 'ESU ' => 'esu', + 'ETI ' => 'est', + 'EUQ ' => 'eus', + 'EVK ' => 'evn', + 'EVN ' => 'eve', + 'EWE ' => 'ewe', + 'FAN ' => 'acf', + 'FAN0' => 'fan', + 'FAR ' => 'fas', + 'FAT ' => 'fat', + 'FIN ' => 'fin', + 'FJI ' => 'fij', + 'FLE ' => 'vls', + "FMP " => 'fmp', + 'FNE ' => 'enf', + 'FON ' => 'fon', + 'FOS ' => 'fao', + 'FRA ' => 'fra', + 'FRC ' => 'frc', + 'FRI ' => 'fry', + 'FRL ' => 'fur', + 'FRP ' => 'frp', + 'FTA ' => 'fuf', + 'FUL ' => 'ful', + 'FUV ' => 'fuv', + 'GAD ' => 'gaa', + 'GAE ' => 'gla', + 'GAG ' => 'gag', + 'GAL ' => 'glg', + 'GAW ' => 'gbm', + 'GEZ ' => 'gez', + 'GIH ' => 'gih', + 'GIL ' => 'niv', + 'GIL0' => 'gil', + 'GKP ' => 'gkp', + 'GLK ' => 'glk', + 'GMZ ' => 'guk', + 'GNN ' => 'gnn', + 'GOG ' => 'gog', + 'GON ' => 'gon gno ggo', + 'GRN ' => 'kal', + 'GRO ' => 'grt', + 'GUA ' => 'grn', + 'GUC ' => 'guc', + 'GUF ' => 'guf', + 'GUJ ' => 'guj', + 'GUZ ' => 'guz', + 'HAI ' => 'hat', + 'HAL ' => 'flm', + 'HAR ' => 'hoj', + 'HAU ' => 'hau', + 'HAW ' => 'haw', + 'HAY ' => 'hay', + 'HAZ ' => 'haz', + 'HBN ' => 'amf', + 'HER ' => 'her', + 'HIL ' => 'hil', + 'HIN ' => 'hin', + 'HMA ' => 'mrj', + 'HMN ' => 'hmn', + 'HMO ' => 'hmo', + 'HND ' => 'hno hnd', + 'HO ' => 'hoc', + 'HRI ' => 'har', + 'HRV ' => 'hrv', + 'HUN ' => 'hun', + 'HYE ' => 'hye', + 'HYE0' => 'hye', + 'IBA ' => 'iba', + 'IBB ' => 'ibb', + 'IBO ' => 'ibo', + 'IDO ' => 'ido', + 'IJO ' => 'ijc', + 'ILE ' => 'ile', + 'ILO ' => 'ilo', + 'INA ' => 'ina', + 'IND ' => 'ind', + 'ING ' => 'inh', + 'INU ' => 'iku', + 'IPK ' => 'ipk', + 'IRI ' => 'gle', + 'IRT ' => 'gle', + 'ISL ' => 'isl', + 'ISM ' => 'smn', + 'ITA ' => 'ita', + 'IWR ' => 'heb', + 'JAM ' => 'jam', + 'JAN ' => 'jpn', + 'JAV ' => 'jav', + 'JBO ' => 'jbo', + "JCT " => 'jct', + 'JII ' => 'yid', + 'JUD ' => 'lad', + 'JUL ' => 'dyu', + 'KAB ' => 'kbd', + 'KAB0' => 'kab', + 'KAC ' => 'kfr', + 'KAL ' => 'kln', + 'KAN ' => 'kan', + 'KAR ' => 'krc', + 'KAT ' => 'kat', + 'KAZ ' => 'kaz', + 'KDE ' => 'kde', + 'KEA ' => 'kea', + 'KEB ' => 'ktb', + 'KEK ' => 'kek', + 'KGE ' => 'kat', + 'KHA ' => 'kjh', + 'KHK ' => 'kca', + 'KHM ' => 'khm', + 'KHS ' => 'kca', + 'KHT ' => 'kht', + 'KHV ' => 'kca', + 'KHW ' => 'khw', + 'KIK ' => 'kik', + 'KIR ' => 'kir', + 'KIS ' => 'kqs kss', + 'KIU ' => 'kiu', + 'KJD ' => 'kjd', + 'KJP ' => 'kjp', + 'KKN ' => 'kex', + 'KLM ' => 'xal', + 'KMB ' => 'kam', + 'KMN ' => 'kfy', + 'KMO ' => 'kmw', + 'KMS ' => 'kxc', + "KMZ " => 'kmz', + 'KNR ' => 'kau', + 'KOD ' => 'kfa', + 'KOH ' => 'okm', + 'KOK ' => 'kok', + 'KOM ' => 'kom', + 'KON ' => 'ktu', + 'KON0' => 'kon', + 'KOP ' => 'koi', + 'KOR ' => 'kor', + 'KOS ' => 'kos', + 'KOZ ' => 'kpv', + 'KPL ' => 'kpe', + 'KRI ' => 'kri', + 'KRK ' => 'kaa', + 'KRL ' => 'krl', + 'KRM ' => 'kdr', + 'KRN ' => 'kar', + 'KRT ' => 'kqy', + 'KSH ' => 'kas', + 'KSH0' => 'ksh', + 'KSI ' => 'kha', + 'KSM ' => 'sjd', + 'KSW ' => 'ksw', + 'KUA ' => 'kua', + 'KUI ' => 'kxu', + 'KUL ' => 'kfx', + 'KUM ' => 'kum', + 'KUR ' => 'kur', + 'KUU ' => 'kru', + 'KUY ' => 'kdt', + 'KYK ' => 'kpy', + 'KYU ' => 'kyu', + 'LAD ' => 'lld', + 'LAH ' => 'bfu', + 'LAK ' => 'lbe', + 'LAM ' => 'lmn', + 'LAO ' => 'lao', + 'LAT ' => 'lat', + 'LAZ ' => 'lzz', + 'LCR ' => 'crm', + 'LDK ' => 'lbj', + 'LEZ ' => 'lez', + 'LIJ ' => 'lij', + 'LIM ' => 'lim', + 'LIN ' => 'lin', + 'LIS ' => 'lis', + 'LJP ' => 'ljp', + 'LKI ' => 'lki', + 'LMA ' => 'mhr', + 'LMB ' => 'lif', + 'LMO ' => 'lmo', + 'LMW ' => 'ngl', + 'LOM ' => 'lom', + 'LRC ' => 'lrc luz bqi zum', + 'LSB ' => 'dsb', + 'LSM ' => 'smj', + 'LTH ' => 'lit', + 'LTZ ' => 'ltz', + 'LUA ' => 'lua', + 'LUB ' => 'lub', + 'LUG ' => 'lug', + 'LUH ' => 'luy', + 'LUO ' => 'luo', + 'LVI ' => 'lav', + 'MAD ' => 'mad', + 'MAG ' => 'mag', + 'MAH ' => 'mah', + 'MAJ ' => 'mpe', + 'MAK ' => 'vmw', + 'MAL ' => 'mal', + 'MAM ' => 'mam', + 'MAN ' => 'mns', + 'MAP ' => 'arn', + 'MAR ' => 'mar', + 'MAW ' => 'mwr dhd rwr mve wry mtr swv', + 'MBN ' => 'kmb', + "MBO " => 'mbo', + 'MCH ' => 'mnc', + 'MCR ' => 'crm', + 'MDE ' => 'men', + 'MDR ' => 'mdr', + 'MEN ' => 'mym', + 'MER ' => 'mer', + 'MFE ' => 'mfe', + 'MIN ' => 'min', + 'MIZ ' => 'lus', + 'MKD ' => 'mkd', + 'MKR ' => 'mak', + 'MKW ' => 'mkw', + 'MLE ' => 'mdy', + 'MLG ' => 'mlg', + 'MLN ' => 'mlq', + 'MLR ' => 'mal', + 'MLY ' => 'msa', + 'MND ' => 'mnk', + 'MNG ' => 'mon', + 'MNI ' => 'mni', + 'MNK ' => 'man mnk myq mku msc emk mwk mlq', + 'MNX ' => 'glv', + 'MOH ' => 'mho', + 'MOK ' => 'mdf', + 'MOL ' => 'mol', + 'MON ' => 'mnw', + 'MOS ' => 'mos', + 'MRI ' => 'mri', + 'MTH ' => 'mai', + 'MTS ' => 'mlt', + 'MUN ' => 'unr', + 'MUS ' => 'mus', + 'MWL ' => 'mwl', + 'MWW ' => 'mww', + 'MYN ' => 'myn', + 'MZN ' => 'mzn', + 'NAG ' => 'nag', + 'NAH ' => 'nah', + 'NAN ' => 'gld', + 'NAP ' => 'nap', + 'NAS ' => 'nsk', + 'NAU ' => 'nau', + 'NAV ' => 'nav', + 'NCR ' => 'csw', + 'NDB ' => 'nbl nde', + 'NDC ' => 'ndc', + 'NDG ' => 'ndo', + 'NDS ' => 'nds', + 'NEP ' => 'nep', + 'NEW ' => 'new', + 'NGA ' => 'nga', + 'NHC ' => 'csw', + 'NIS ' => 'dap', + 'NIU ' => 'niu', + 'NKL ' => 'nyn', + 'NKO ' => 'ngo', + 'NLD ' => 'nld', + 'NOE ' => 'noe', + 'NOG ' => 'nog', + 'NOR ' => 'nob', + 'NOV ' => 'nov', + 'NSM ' => 'sme', + 'NSO ' => 'nso', + 'NTA ' => 'nod', + 'NTO ' => 'epo', + 'NYM ' => 'nym', + 'NYN ' => 'nno', + "NZA " => 'nza', + 'OCI ' => 'oci', + 'OCR ' => 'ojs', + 'OJB ' => 'oji', + 'ORI ' => 'ori', + 'ORO ' => 'orm', + 'OSS ' => 'oss', + 'PAA ' => 'sam', + 'PAG ' => 'pag', + 'PAL ' => 'pli', + 'PAM ' => 'pam', + 'PAN ' => 'pan', + 'PAP ' => 'plp', + 'PAP0' => 'pap', + 'PAS ' => 'pus', + 'PAU ' => 'pau', + 'PCC ' => 'pcc', + 'PCD ' => 'pcd', + 'PDC ' => 'pdc', + 'PGR ' => 'ell', + 'PHK ' => 'phk', + 'PIH ' => 'pih', + 'PIL ' => 'fil', + 'PLG ' => 'pce rbb pll', + 'PLK ' => 'pol', + 'PMS ' => 'pms', + 'PNB ' => 'pnb', + 'POH ' => 'poh', + 'PON ' => 'pon', + 'PRO ' => 'pro', + 'PTG ' => 'por', + 'PWO ' => 'pwo', + 'QIN ' => 'bgr cnh cnw czt sez tcp csy ctd flm pck tcz zom cmr dao hlt cka cnk mrh mwg cbl cnb csh', + 'QUC ' => 'quc', + 'QUH ' => 'quh', + 'QUZ ' => 'quz', + 'QVI ' => 'qvi', + 'QWH ' => 'qwh', + 'RAJ ' => 'raj', + 'RAR ' => 'rar', + 'RBU ' => 'bxr', + 'RCR ' => 'atj', + 'REJ ' => 'rej', + 'RIA ' => 'ria', + 'RIF ' => 'rif', + 'RIT ' => 'rit', + 'RKW ' => 'rkw', + 'RMS ' => 'roh', + 'RMY ' => 'rmy', + 'ROM ' => 'ron', + 'ROY ' => 'rom', + 'RSY ' => 'rue', + 'RTM ' => 'rtm', + 'RUA ' => 'kin', + 'RUN ' => 'run', + 'RUP ' => 'rup', + 'RUS ' => 'rus', + 'SAD ' => 'sck', + 'SAN ' => 'san', + 'SAS ' => 'sas', + 'SAT ' => 'sat', + 'SAY ' => 'chp', + 'SCN ' => 'scn', + 'SCO ' => 'sco', + "SCS " => 'scs', + 'SEK ' => 'xan', + 'SEL ' => 'sel', + 'SGA ' => 'sga', + 'SGO ' => 'sag', + 'SGS ' => 'sgs', + 'SHI ' => 'shi', + 'SHN ' => 'shn', + 'SIB ' => 'sjo', + 'SID ' => 'sid', + 'SIG ' => 'xst', + 'SKS ' => 'sms', + 'SKY ' => 'slk', + "SLA " => 'scs xsl', + 'SLV ' => 'slv', + 'SML ' => 'som', + 'SMO ' => 'smo', + 'SNA ' => 'she', + 'SNA0' => 'sna', + 'SND ' => 'snd', + 'SNH ' => 'sin', + 'SNK ' => 'snk', + 'SOG ' => 'gru', + 'SOP ' => 'sop', + 'SOT ' => 'sot', + 'SQI ' => 'gsw', + 'SRB ' => 'srp', + 'SRD ' => 'srd', + 'SRK ' => 'skr', + 'SRR ' => 'srr', + 'SSL ' => 'xsl', + 'SSM ' => 'sma', + 'STQ ' => 'stq', + 'SUK ' => 'suk', + 'SUN ' => 'sun', + 'SUR ' => 'suq', + 'SVA ' => 'sva', + 'SVE ' => 'swe', + 'SWA ' => 'aii', + 'SWK ' => 'swa', + 'SWZ ' => 'ssw', + 'SXT ' => 'ngo', + 'SXU ' => 'sxu', + 'SYL ' => 'syl', + 'SYR ' => 'syr', + 'SZL ' => 'szl', + 'TAB ' => 'tab', + 'TAJ ' => 'tgk', + 'TAM ' => 'tam', + 'TAT ' => 'tat', + 'TCR ' => 'cwd', + 'TDD ' => 'tdd', + 'TEL ' => 'tel', + 'TET ' => 'tet', + 'TGL ' => 'tgl', + 'TGN ' => 'ton', + 'TGR ' => 'tig', + 'TGY ' => 'tir', + 'THA ' => 'tha', + 'THT ' => 'tah', + 'TIB ' => 'bod', + 'TIV ' => 'tiv', + 'TKM ' => 'tuk', + 'TMH ' => 'tmh', + 'TMN ' => 'tem', + 'TNA ' => 'tsn', + 'TNE ' => 'enh', + 'TNG ' => 'toi', + 'TOD ' => 'xal', + 'TOD0' => 'tod', + 'TPI ' => 'tpi', + 'TRK ' => 'tur', + 'TSG ' => 'tso', + 'TUA ' => 'tru', + 'TUL ' => 'tcy', + 'TUM ' => 'tum', + 'TUV ' => 'tyv', + 'TVL ' => 'tvl', + 'TWI ' => 'twi', + 'TYZ ' => 'tyz', + 'TZM ' => 'tzm', + 'TZO ' => 'tzo', + 'UDM ' => 'udm', + 'UKR ' => 'ukr', + 'UMB ' => 'umb', + 'URD ' => 'urd', + 'USB ' => 'hsb', + 'UYG ' => 'uig', + 'UZB ' => 'uzb uzn uzs', + 'VEC ' => 'vec', + 'VEN ' => 'ven', + 'VIT ' => 'vie', + 'VOL ' => 'vol', + 'VRO ' => 'vro', + 'WA ' => 'wbm', + 'WAG ' => 'wbr', + 'WAR ' => 'war', + 'WCR ' => 'crk', + 'WEL ' => 'cym', + 'WLF ' => 'wol', + 'WLN ' => 'wln', + 'WTM ' => 'wtm', + 'XBD ' => 'khb', + 'XHS ' => 'xho', + 'XJB ' => 'xjb', + 'XOG ' => 'xog', + 'XPE ' => 'xpe', + 'YAK ' => 'sah', + 'YAO ' => 'yao', + 'YAP ' => 'yap', + 'YBA ' => 'yor', + 'YCR ' => 'cre', + 'YIM ' => 'iii', + 'ZEA ' => 'zea', + 'ZGH ' => 'zgh', + 'ZHA ' => 'zha', + 'ZHH ' => 'zho', + 'ZHP ' => 'zho', + 'ZHS ' => 'zho', + 'ZHT ' => 'zho', + 'ZND ' => 'zne', + 'ZUL ' => 'zul', + 'ZZA ' => 'zza', +); + +{ + foreach my $s (qw ( SCRIPT LANGUAGE FEATURE ) ) + { + map { $ttnames{$s}{$tttags{$s}{$_}} = $_ } keys %{$tttags{$s}}; + } + + # For ISO639 info, the raw data is a space-separated list of ISO639 + # language IDs. We convert that list to an array. + + foreach my $tag (keys %iso639) + { + my $list = $iso639{$tag}; + $iso639{$tag} = [ split(' ', $list) ]; + # Also set the reverse mapping: + map { $iso639{$_} = $tag } @{$iso639{$tag}}; + } +} + + +=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 cannot 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; + +=head1 AUTHOR + +Bob Hallissy. L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/Font/TTF/OldCmap.pm b/lib/Font/TTF/OldCmap.pm new file mode 100644 index 0000000..7509149 --- /dev/null +++ b/lib/Font/TTF/OldCmap.pm @@ -0,0 +1,370 @@ +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 preceded 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 +preceded 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) = @_; + $self->SUPER::read or return $self; + + my ($dat, $i, $j, $k, $id, @ids, $s); + my ($start, $end, $range, $delta, $form, $len, $num, $ver); + my ($fh) = $self->{' INFILE'}; + + $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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/OldMort.pm b/lib/Font/TTF/OldMort.pm new file mode 100644 index 0000000..517709c --- /dev/null +++ b/lib/Font/TTF/OldMort.pm @@ -0,0 +1,722 @@ +package Font::TTF::OldMort; + +=head1 NAME + +Font::TTF::OldMort - Glyph Metamorphosis table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over + +=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 + +=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); + +=over + +=back + +=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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/Font/TTF/PCLT.pm b/lib/Font/TTF/PCLT.pm new file mode 100644 index 0000000..d9a2c6f --- /dev/null +++ b/lib/Font/TTF/PCLT.pm @@ -0,0 +1,143 @@ +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)); +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 54; +} + +1; + +=head1 BUGS + +None known + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/PSNames.pm b/lib/Font/TTF/PSNames.pm new file mode 100644 index 0000000..686889e --- /dev/null +++ b/lib/Font/TTF/PSNames.pm @@ -0,0 +1,4458 @@ +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"; +# } +# } + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Post.pm b/lib/Font/TTF/Post.pm new file mode 100644 index 0000000..a3f29e2 --- /dev/null +++ b/lib/Font/TTF/Post.pm @@ -0,0 +1,329 @@ +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 +cannot 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) = @_; + $self->SUPER::read or return $self; + + my ($dat, $dat1, $i, $off, $c, $maxoff, $form, $angle, $numGlyphs); + my ($fh) = $self->{' INFILE'}; + + $numGlyphs = $self->{' PARENT'}{'maxp'}{'numGlyphs'}; + 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; +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 32; +} + +1; + +=head1 BUGS + +=over 4 + +=item * + +No support for type 4 tables + +=back + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Prep.pm b/lib/Font/TTF/Prep.pm new file mode 100644 index 0000000..dae7193 --- /dev/null +++ b/lib/Font/TTF/Prep.pm @@ -0,0 +1,99 @@ +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Prop.pm b/lib/Font/TTF/Prop.pm new file mode 100644 index 0000000..16c4dcc --- /dev/null +++ b/lib/Font/TTF/Prop.pm @@ -0,0 +1,179 @@ +package Font::TTF::Prop; + +=head1 NAME + +Font::TTF::Prop - Glyph Properties table in a font + +=head1 DESCRIPTION + +=head1 INSTANCE VARIABLES + +=over + +=item version + +=item default + +=item lookup + +Hash of property values keyed by glyph number + +=item lookupFormat + +=back + +=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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 8; +} + +=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. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Segarr.pm b/lib/Font/TTF/Segarr.pm new file mode 100644 index 0000000..514b605 --- /dev/null +++ b/lib/Font/TTF/Segarr.pm @@ -0,0 +1,385 @@ +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Silf.pm b/lib/Font/TTF/Silf.pm new file mode 100644 index 0000000..9244c68 --- /dev/null +++ b/lib/Font/TTF/Silf.pm @@ -0,0 +1,900 @@ +package Font::TTF::Silf; + +=head1 NAME + +Font::TTF::Silf - The main Graphite table + +=head1 DESCRIPTION + +The Silf table holds the core of the Graphite rules for a font. A Silf table has +potentially multiple silf subtables, although there is usually only one. Within a silf subtable, +there are a number of passes which contain the actual finite state machines to match rules +and the constraint and action code to be executed when a rule matches. + +=head1 INSTANCE VARIABLES + +=over 4 + +=item Version + +Silf table format version + +=item Compiler + +Lowest compiler version necessary to fully support the semantics expressed in this +Graphite description + +=item SILF + +An array of Silf subtables + +=over 4 + +=item maxGlyphID + +The maximum glyph id referenced including pseudo and non glyphs + +=item Ascent + +Extra ascent to be added to the font ascent. + +=item Descent + +Extra descent to be added to the font descent. Both values are assumed to be +positive for a descender below the base line. + +=item substPass + +Pass index into PASS of the first substitution pass. + +=item posPass + +Pass index into PASS of the first positioning pass. + +=item justPass + +Pass index into PASS of the first justification pass. + +=item bidiPass + +Pass index of the pass before which the bidirectional processing pass will be executed. +0xFF indicates that there is no bidi pass to be executed. + +=item Flags + +A bitfield of flags: + + 0 - Indicates there are line end contextual rules in one of the passes + +=item maxPreContext + +Maximum length of a context preceding a cross line boundary contextualisation. + +=item maxPostContext + +Maximum length of a context following a cross line boundary contextualsation. + +=item attrPseudo + +Glyph attribute for the actual glyph id associated with a pseudo glyph. + +=item attrBreakWeight + +Glyph attribute number of the attribute holding the default breakweight associated with a glyph. + +=item attrDirectionality + +Glyph attribute number of the attribute holding the default directionality value associated with a glyph. + +=item JUST + +The may be a number of justification levels each with their own property values. +This points to an array of hashes, one for each justification level. + +=over 4 + +=item attrStretch + +Glyph attribute number for the amount of stretch allowed before this glyph. + +=item attrShrink + +Glyph attribute number for the amount of shrink allowed before this glyph. + +=item attrStep + +Glyph attribute number specifying the minimum granularity of actual spacing associated with this glyph at this level. + +=item attrWeight + +Glyph attribute number giving the weight associated with spreading space across a run of glyphs. + +=item runto + +Which level starts the next stage. + +=back + +=item numLigComp + +Number of initial glyph attributes that represent ligature components + +=item numUserAttr + +Number of user defined slot attributes referenced. Tells the engine how much space to +allocate to a slot for user attributes. + +=item maxCompPerLig + +Maximum number of components per ligature. + +=item direction + +Supported directions for this writing system + +=item CRIT_FEATURE + +Array of critical features. + +=item scripts + +Array of script tags that indicate which set of GDL rules to execute if there is more than one in a font. + +=item lbGID + +Glyph ID of the linebreak pseudo glyph. + +=item pseudos + +Hash of Unicode values to pseduo glyph ids. + +=item classes + +This is an array of classes, each of which is an array of glyph ids in class order. + +=item PASS + +The details of rules and actions are stored in passes. This value is an array of pass subobjects one for each pass. + +=over 4 + +=item flags + +This is a bitfield: + + 0 - If true, this pass makes no change to the slot stream considered as a sequence of glyph ids. + Only slot attributes are expected to change (for example during positioning). + +=item maxRuleLoop + +How many times the engine will allow rules to be tested and run without the engine advancing through the +input slot stream. + +=item maxRuleContext + +Number of slots of input needed to run this pass. + +=item maxBackup + +Number of slots by which the following pass needs to trail this pass (i.e. the maximum this pass is allowed to back up). + +=item numRules + +Number of action code blocks, and so uncompressed rules, in this pass. + +=item numRows + +Number of rows in the finite state machine. + +=item numTransitional + +Number of rows in the finite state machine that are not final states. This specifies the number of rows in the fsm +element. + +=item numSuccess + +Number of success states. A success state may also be a transitional state. + +=item numColumns + +Number of columns in the finite state machine. + +=item colmap + +A hash, indexed by glyphid, that gives the fsm column number associated with that glyphid. If not present, then +the glyphid is not part of the fsm and will finish fsm processing if it occurs. + +=item rulemap + +An array of arrays, one for each success state. Each array holds a list of rule numbers associated with that state. + +=item minRulePreContext + +Minimum number of items in a rule's precontext. + +=item maxRulePreContext + +The maximum number of items in any rule's precontext. + +=item startStates + +Array of starting state numbers dependeing on the length of actual precontext. +There are maxRulePreContext - minRulePreContext + 1 of these. + +=item ruleSortKeys + +An array of sort keys one for each rule giving the length of the rule including its precontext. + +=item rulePreContexts + +An array of precontext lengths for each rule. + +=item fsm + +A two dimensional array such that $p->{'fsm'}[$row][$col] gives the row of the next node to try in the fsm. + +=item passConstraintLen + +Length in bytes of the passConstraint code. + +=item passConstraintCode + +A byte string holding the pass constraint code. + +=item constraintCode + +An array of byte strings holding the constraint code for each rule. + +=item actionCode + +An array of byte strings holding the action code for each rule. + +=back + +=back + +=back + +=cut + +use Font::TTF::Table; +use Font::TTF::Utils; +use strict; +use vars qw(@ISA); + +@ISA = qw(Font::TTF::Table); + +=head2 @opcodes + +Each array holds the name of the opcode, the number of operand bytes and a string describing the operands. +The characters in the string have the following meaning: + + c - lsb of class id + C - msb of class id + f - feature index + g - lsb of glyph attribute id + G - msb of glyph attribute id + l - lsb of a 32-bit extension to a 16-bit number + L - msb of a 32-bit number + m - glyph metric id + n - lsb of a number + N - msb of a 16-bit number + o - offset (jump) + s - slot reference + S - slot attribute id + v - variable number of following arguments + +=cut + +our @opcodes = ( ["nop", 0, ""], ["push_byte", 1, "n"], ["push_byte_u", 1, "n"], ["push_short", 2, "Nn"], + ["push_short_u", 2, "Nn"], ["push_long", 4, "LlNn"], ["add", 0, ""], ["sub", 0, ""], + ["mul", 0, ""], ["div", 0, ""], ["min", 0, ""], ["max", 0, ""], + ["neg", 0, ""], ["trunc8", 0, ""], ["trunc16", 0, ""], ["cond", 0, ""], + ["and", 0, ""], ["or", 0, ""], ["not", 0, ""], ["equal", 0, ""], # 16 + ["not_eq", 0, ""], ["less", 0, ""], ["gtr", 0, ""], ["less_eq", 0, ""], + ["gtr_eq", 0, ""], ["next", 0, ""], ["next_n", 1, "n"], ["copy_next", 0, ""], + ["put_glyph_8bit_obs", 1, "c"], ["put_subs_8bit_obs", 3, "scc"], ["put_copy", 1, "s"], ["insert", 0, ""], + ["delete", 0, ""], ["assoc", -1, "v"], ["cntxt_item", 2, "so"], ["attr_set", 1, "S"], # 32 + ["attr_add", 1, "S"], ["attr_sub", 1, "S"], ["attr_set_slot", 1, "S"], ["iattr_set_slot", 2, "Sn"], + ["push_slot_attr", 2, "Ss"], ["push_glyph_attr_obs", 2, "gs"], ["push_glyph_metric", 3, "msn"], ["push_feat", 2, "fs"], + ["push_att_to_gattr_obs", 2, "gs"], ["push_att_to_glyph_metric", 3, "msn"], ["push_islot_attr", 3, "Ssn"], ["push_iglyph_attr", 3, "gsn"], + ["pop_ret", 0, ""], ["ret_zero", 0, ""], ["ret_true", 0, ""], ["iattr_set", 2, "Sn"], # 48 + ["iattr_add", 2, "Sn"], ["iattr_sub", 2, "Sn"], ["push_proc_state", 1, "n"], ["push_version", 0, ""], + ["put_subs", 5, "sCcCc"], ["put_subs2", 4, "cscc"], ["put_subs3", 7, "scscscc"], ["put_glyph", 2, "Cc"], + ["push_glyph_attr", 3, "Ggs"], ["push_att_to_glyph_attr", 3, "Ggs"], ["bitand", 0, ""], ["bitor", 0, ""], + ["bitnot", 0, ""], ["setbits", 4, "NnNn"], ["setfeat", 2, "fs"] ); # 64 + +my ($i) = 0; +our %opnames = map {$_->[0] => $i++} @opcodes; + +=head2 read + +Reads the Silf table into the internal data structure + +=cut + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($dat, $d); + my ($fh) = $self->{' INFILE'}; + my ($moff) = $self->{' OFFSET'}; + my ($numsilf, @silfo); + + $fh->read($dat, 4); + ($self->{'Version'}) = TTF_Unpack("v", $dat); + if ($self->{'Version'} >= 3) + { + $fh->read($dat, 4); + ($self->{'Compiler'}) = TTF_Unpack("v", $dat); + } + $fh->read($dat, 4); + ($numsilf) = TTF_Unpack("S", $dat); + $fh->read($dat, $numsilf * 4); + foreach my $i (0 .. $numsilf - 1) + { push (@silfo, TTF_Unpack("L", substr($dat, $i * 4, 4))); } + + foreach my $sili (0 .. $numsilf - 1) + { + my ($silf) = {}; + my (@passo, @classo, $classbase, $numJust, $numCritFeatures, $numScript, $numPasses, $numPseudo, $i); + + push (@{$self->{'SILF'}}, $silf); + $fh->seek($moff + $silfo[$sili], 0); + if ($self->{'Version'} >= 3) + { + $fh->read($dat, 8); + ($silf->{'Version'}) = TTF_Unpack("v", $dat); + } + $fh->read($dat, 20); + ($silf->{'maxGlyphID'}, $silf->{'Ascent'}, $silf->{'Descent'}, + $numPasses, $silf->{'substPass'}, $silf->{'posPass'}, $silf->{'justPass'}, $silf->{'bidiPass'}, + $silf->{'Flags'}, $silf->{'maxPreContext'}, $silf->{'maxPostContext'}, $silf->{'attrPseudo'}, + $silf->{'attrBreakWeight'}, $silf->{'attrDirectionality'}, $silf->{'attrMirror'}, $silf->{'passBits'}, $numJust) = + TTF_Unpack("SssCCCCCCCCCCCCCC", $dat); + if ($numJust) + { + foreach my $j (0 .. $silf->{'numJust'} - 1) + { + my ($just) = {}; + push (@{$silf->{'JUST'}}, $just); + $fh->read($dat, 8); + ($just->{'attrStretch'}, $just->{'attrShrink'}, $just->{'attrStep'}, $just->{'attrWeight'}, + $just->{'runto'}) = TTF_Unpack("CCCCC", $dat); + } + } + $fh->read($dat, 10); + ($silf->{'numLigComp'}, $silf->{'numUserAttr'}, $silf->{'maxCompPerLig'}, $silf->{'direction'}, + $silf->{'attCollisions'}, $d, $d, $d, $numCritFeatures) = TTF_Unpack("SCCCCCCCC", $dat); + if ($numCritFeatures) + { + $fh->read($dat, $numCritFeatures * 2); + $silf->{'CRIT_FEATURE'} = [TTF_Unpack("S$numCritFeatures", $dat)]; + } + $fh->read($dat, 2); + ($d, $numScript) = TTF_Unpack("CC", $dat); + if ($numScript) + { + $fh->read($dat, $numScript * 4); + foreach (0 .. $numScript - 1) + { push (@{$silf->{'scripts'}}, unpack('a4', substr($dat, $_ * 4, 4))); } + } + $fh->read($dat, 2); + ($silf->{'lbGID'}) = TTF_Unpack("S", $dat); + $fh->read($dat, $numPasses * 4 + 4); + @passo = unpack("N*", $dat); + $fh->read($dat, 8); + ($numPseudo) = TTF_Unpack("S", $dat); + if ($numPseudo) + { + $fh->read($dat, $numPseudo * 6); + foreach (0 .. $numPseudo - 1) + { + my ($uni, $gid) = TTF_Unpack("LS", substr($dat, $_ * 6, 6)); + $silf->{'pseudos'}{$uni} = $gid; + } + } + $classbase = $fh->tell(); + $fh->read($dat, 4); + my ($numClasses, $numLinearClasses) = TTF_Unpack("SS", $dat); + $silf->{'numLinearClasses'} = $numLinearClasses; + $fh->read($dat, ($numClasses + 1) * ($self->{'Version'} >= 4 ? 4 : 2)); + @classo = unpack($self->{'Version'} >= 4 ? "N*" : "n*", $dat); + $fh->read($dat, $classo[-1] - $classo[0]); + for ($i = 0; $i < $numLinearClasses; $i++) + { + push (@{$silf->{'classes'}}, [unpack("n*", substr($dat, $classo[$i] - $classo[0], + $classo[$i+1] - $classo[$i]))]) + } + for ($i = $numLinearClasses; $i < $numClasses; $i++) + { + my (@res); + my (@c) = unpack("n*", substr($dat, $classo[$i] - $classo[0] + 8, $classo[$i+1] - $classo[$i] - 8)); + for (my $j = 0; $j < @c; $j += 2) + { $res[$c[$j+1]] = $c[$j]; } + push (@{$silf->{'classes'}}, \@res); + } + foreach (0 .. $numPasses - 1) + { $self->read_pass($fh, $passo[$_], $moff + $silfo[$sili], $silf, $_); } + } + return $self; +} + +sub chopcode +{ + my ($dest, $dat, $offsets, $isconstraint) = @_; + my ($last) = $offsets->[-1]; + my ($i); + + for ($i = $#{$offsets} - 1; $i >= 0; $i--) + { + if ((!$isconstraint || $offsets->[$i]) && $offsets->[$i] != $last) + { + unshift(@{$dest}, substr($dat, $offsets->[$i], $last - $offsets->[$i])); + $last = $offsets->[$i]; + } + else + { unshift(@{$dest}, ""); } + } +} + + +sub read_pass +{ + my ($self, $fh, $offset, $base, $silf, $id) = @_; + my ($pass) = {'id' => $id}; + my ($d, $dat, $i, @orulemap, @oconstraints, @oactions, $numRanges); + + $fh->seek($offset + $base, 0); + # printf "pass base = %04X\n", $offset; + push (@{$silf->{'PASS'}}, $pass); + $fh->read($dat, 40); + ($pass->{'flags'}, $pass->{'maxRuleLoop'}, $pass->{'maxRuleContext'}, $pass->{'maxBackup'}, + $pass->{'numRules'}, $d, $d, $d, $d, $d, $pass->{'numRows'}, $pass->{'numTransitional'}, + $pass->{'numSuccess'}, $pass->{'numColumns'}, $numRanges) = + TTF_Unpack("CCCCSSLLLLSSSSS", $dat); + $fh->read($dat, $numRanges * 6); + foreach $i (0 .. $numRanges - 1) + { + my ($first, $last, $col) = TTF_Unpack('SSS', substr($dat, $i * 6, 6)); + foreach ($first .. $last) + { $pass->{'colmap'}{$_} = $col; } + } + $fh->read($dat, $pass->{'numSuccess'} * 2 + 2); + @orulemap = unpack("n*", $dat); + $fh->read($dat, $orulemap[-1] * 2); + foreach (0 .. $pass->{'numSuccess'} - 1) + { push (@{$pass->{'rulemap'}}, [unpack("n*", substr($dat, $orulemap[$_] * 2, ($orulemap[$_+1] - $orulemap[$_]) * 2))]); } + $fh->read($dat, 2); + ($pass->{'minRulePreContext'}, $pass->{'maxRulePreContext'}) = TTF_Unpack("CC", $dat); + $fh->read($dat, ($pass->{'maxRulePreContext'} - $pass->{'minRulePreContext'} + 1) * 2); + $pass->{'startStates'} = [unpack('n*', $dat)]; + $fh->read($dat, $pass->{'numRules'} * 2); + $pass->{'ruleSortKeys'} = [unpack('n*', $dat)]; + $fh->read($dat, $pass->{'numRules'}); + $pass->{'rulePreContexts'} = [unpack('C*', $dat)]; + $fh->read($dat, 3); + ($pass->{'collisionThreshold'}, $pass->{'passConstraintLen'}) = TTF_Unpack("CS", $dat); + $fh->read($dat, ($pass->{'numRules'} + 1) * 2); + @oconstraints = unpack('n*', $dat); + $fh->read($dat, ($pass->{'numRules'} + 1) * 2); + @oactions = unpack('n*', $dat); + foreach (0 .. $pass->{'numTransitional'} - 1) + { + $fh->read($dat, $pass->{'numColumns'} * 2); + push (@{$pass->{'fsm'}}, [unpack('n*', $dat)]); + } + $fh->read($dat, 1); + if ($pass->{'passConstraintLen'}) + { $fh->read($pass->{'passConstraintCode'}, $pass->{'passConstraintLen'}); } + $fh->read($dat, $oconstraints[-1]); + $pass->{'constraintCode'} = []; + chopcode($pass->{'constraintCode'}, $dat, \@oconstraints, 1); + $fh->read($dat, $oactions[-1]); + $pass->{'actionCode'} = []; + chopcode($pass->{'actionCode'}, $dat, \@oactions, 0); + return $pass; +} + +sub chopranges +{ + my ($map, $numg) = @_; + my ($dat, $numRanges); + my (@keys) = sort {$a <=> $b} keys %{$map}; + my ($first, $last, $col, $g); + + $first = -1; + $last = -1; + $col = -1; + foreach $g (@keys) + { + next unless ($g > 0 or $g eq '0'); + if ($g != $last + 1 || $map->{$g} != $col) + { + if ($col != -1) + { + $dat .= pack("nnn", $first, $last, $col); + $numRanges++; + } + $first = $last = $g; + $col = $map->{$g}; + } + else + { $last++; } + } + if ($col != -1) + { + $dat .= pack("nnn", $first, $last, $col); + $numRanges++; + } + return ($numRanges, $dat); +} + +sub unpack_code +{ + my ($self, $str) = @_; + my (@res, $i, $j); + my ($l) = length($str); + + for ($i = 0; $i < $l; ) + { + my ($a) = unpack('C', substr($str, $i, 1)); + my ($o) = $opcodes[$a]; + my (@args); + my (@types) = split('', $o->[2]); + ++$i; + for ($j = 0; $j < @types; ++$j) + { + my ($t) = $types[$j]; + if ($t eq 'v') + { + my ($n) = unpack('C', substr($str, $i, 1)); + push (@args, unpack('C*', substr($str, $i + 1, $n))); + $i += $n + 1; + } + elsif ($t eq 'L' or $t eq 'N' or $t eq 'G' or $t eq 'C') + { + push (@args, unpack('n', substr($str, $i, 2))); + $i += 2; + $j++; + } + else + { + push (@args, unpack($t eq 's' ? 'c' : 'C', substr($str, $i, 1))); + $i++; + } + } + push (@res, [$o->[0], @args]); + } + return @res; +} + +sub pack_code +{ + my ($self, $cmds) = @_; + my ($res); + + foreach my $c (@{$cmds}) + { + my ($ind) = $opnames{$c->[0]}; + my ($i) = 1; + $res .= pack('C', $ind); + # my (@types) = unpack('C*', $opcodes[$ind][2]); + my (@types) = split('', $opcodes[$ind][2]); + for (my $j = 0; $j < @types; $j++) + { + my ($t) = $types[$j]; + if ($t eq 'v') + { + my ($n) = scalar @{$c} - 1; + $res .= pack('C*', $n, @{$c}[1..$#{$c}]); + $i += $n; + } + elsif ($t eq 'C' or $t eq 'G' or $t eq 'L' or $t eq 'N') + { + $res .= pack('n', $c->[$i]); + $j++; + } + else + { $res .= pack($t eq 's' ? 'c' : 'C', $c->[$i]); } + $i++; + } + } + return $res; +} + +sub packcode +{ + my ($code, $isconstraint) = @_; + my ($dat, $c, $res); + + $c = 1; + $dat = "\000"; + foreach (@{$code}) + { + if ($_) + { + push(@{$res}, $c); + $dat .= $_; + $c += length($_); + } + else + { push(@{$res}, $isconstraint ? 0 : $c); } + } + push(@{$res}, $c); + return ($res, $dat); +} + +sub out_pass +{ + my ($self, $fh, $pass, $silf, $subbase) = @_; + my (@orulemap, $dat, $actiondat, $numRanges, $c); + my (@offsets, $res, $pbase); + + $pbase = $fh->tell(); + # printf "pass base = %04X, ", $pbase - $subbase; + $fh->print(TTF_Pack("CCCCSSLLLLSSSS", $pass->{'flags'}, $pass->{'maxRuleLoop'}, $pass->{'maxRuleContext'}, + $pass->{'maxBackup'}, $pass->{'numRules'}, 24, 0, 0, 0, 0, $pass->{'numRows'}, + $pass->{'numTransitional'}, $pass->{'numSuccess'}, $pass->{'numColumns'})); + ($numRanges, $dat) = chopranges($pass->{'colmap'}); +# print "numranges = $numRanges\n"; + $fh->print(TTF_Pack("SSSS", TTF_bininfo($numRanges, 6))); + $fh->print($dat); + $dat = ""; + $c = 0; +# print "transitions = $pass->{'numTransitional'}, success = $pass->{'numSuccess'}, rows = $pass->{'numRows'}\n"; + my ($sucbase) = $pass->{'numRows'} - $pass->{'numSuccess'}; + foreach (0 .. ($pass->{'numSuccess'} - 1)) + { + push(@orulemap, $c); + if (defined $pass->{'rulemap'}[$_]) + { + $dat .= pack("n*", @{$pass->{'rulemap'}[$_]}); + $c += @{$pass->{'rulemap'}[$_]}; + } + else + { + print "No rules for " . ($sucbase + $_); + if ($sucbase + $_ < $pass->{'numTransitional'}) + { print ": (" . join(",", @{$pass->{'fsm'}[$sucbase + $_]}) . ")"; } + print "\n"; + } + } + push (@orulemap, $c); + $fh->print(pack("n*", @orulemap)); + $fh->print($dat); + $fh->print(TTF_Pack("CC", $pass->{'minRulePreContext'}, $pass->{'maxRulePreContext'})); + $fh->print(pack("n*", @{$pass->{'startStates'}})); + $fh->print(pack("n*", @{$pass->{'ruleSortKeys'}})); + $fh->print(pack("C*", @{$pass->{'rulePreContexts'}})); + $fh->print(TTF_Pack("CS", 0, $pass->{'passConstraintLen'})); + my ($oconstraints, $oactions); + ($oconstraints, $dat) = packcode($pass->{'constraintCode'}, 1); + ($oactions, $actiondat) = packcode($pass->{'actionCode'}, 0); +# printf "constraint offsets @ %X\n", $fh->tell(); + $fh->print(pack("n*", @{$oconstraints})); +# printf "action offsets @ %X\n", $fh->tell(); + $fh->print(pack("n*", @{$oactions})); +# printf "fsm @ %X\n", $fh->tell(); + foreach (@{$pass->{'fsm'}}) + { $fh->print(pack("n*", @{$_})); } +# printf "end of fsm @ %X\n", $fh->tell(); + $fh->print(pack("C", $pass->{'collisionThreshold'})); + push(@offsets, $fh->tell() - $subbase); + $fh->print($pass->{'passConstraintCode'}); + push(@offsets, $fh->tell() - $subbase); + $fh->print($dat); + push(@offsets, $fh->tell() - $subbase); + $fh->print($actiondat); + push(@offsets, 0); + print join(", ", @offsets) . "\n"; + $res = $fh->tell(); + $fh->seek($pbase + 8, 0); + $fh->print(pack("N*", @offsets)); + $fh->seek($res, 0); +# printf "end = %04X\n", $res - $subbase; + return $res; +} + +=head2 out + +Outputs a Silf data structure to a font file in binary format + +=cut + +sub out +{ + my ($self, $fh) = @_; + my ($silf, $base, $subbase, $silfc, $end); + + return $self->SUPER::out($fh) unless ($self->{' read'}); + $base = $fh->tell(); + if ($self->{'Version'} >= 3) + { $fh->print(TTF_Pack("vvSS", $self->{'Version'}, $self->{'Compiler'}, $#{$self->{'SILF'}} + 1, 0)); } + else + { $fh->print(TTF_Pack("vSS", $self->{'Version'}, $#{$self->{'SILF'}} + 1, 0)); } + $fh->print(pack('N*', (0) x (@{$self->{'SILF'}}))); + foreach $silf (@{$self->{'SILF'}}) + { + my ($subbase) = $fh->tell(); + my ($numlin, $i, @opasses, $oPasses, $oPseudo, $ooPasses); + if ($self->{'Version'} >= 3) + { + $fh->seek($base + 12 + $silfc * 4, 0); + $fh->print(pack('N', $subbase - $base)); + $fh->seek($subbase, 0); + $fh->print(TTF_Pack("vSS", $silf->{'Version'}, $ooPasses, $oPseudo)); + } + else + { + $fh->seek($base + 8 + $silfc * 4, 0); + $fh->print(pack('N', $subbase - $base)); + $fh->seek($subbase, 0); + } + $fh->print(TTF_Pack("SssCCCCCCCCCCCCCC", + $silf->{'maxGlyphID'}, $silf->{'Ascent'}, $silf->{'Descent'}, + scalar @{$silf->{'PASS'}}, $silf->{'substPass'}, $silf->{'posPass'}, $silf->{'justPass'}, $silf->{'bidiPass'}, + $silf->{'Flags'}, $silf->{'maxPreContext'}, $silf->{'maxPostContext'}, $silf->{'attrPseudo'}, + $silf->{'attrBreakWeight'}, $silf->{'attrDirectionality'}, $silf->{'attrMirror'}, $silf->{'passBits'}, $#{$silf->{'JUST'}} + 1)); + foreach (@{$silf->{'JUST'}}) + { $fh->print(TTF_Pack("CCCCCCCC", $_->{'attrStretch'}, $_->{'attrShrink'}, $_->{'attrStep'}, + $_->{'attrWeight'}, $_->{'runto'}, 0, 0, 0)); } + + $fh->print(TTF_Pack("SCCCCCCCC", $silf->{'numLigComp'}, $silf->{'numUserAttr'}, $silf->{'maxCompPerLig'}, + $silf->{'direction'}, $silf->{'attCollisions'}, 0, 0, 0, $#{$silf->{'CRIT_FEATURE'}} + 1)); + $fh->print(pack("n*", @{$silf->{'CRIT_FEATURE'}})); + $fh->print(TTF_Pack("CC", 0, $#{$silf->{'scripts'}} + 1)); + foreach (@{$self->{'scripts'}}) + { $fh->print(pack("a4", $_)); } + $fh->print(TTF_Pack("S", $silf->{'lbGID'})); + $ooPasses = $fh->tell(); + if ($silf->{'PASS'}) { $fh->print(pack("N*", (0) x (@{$silf->{'PASS'}} + 1)));} + $oPseudo = $fh->tell() - $subbase; + my (@pskeys) = keys %{$silf->{'pseudos'}}; + $fh->print(TTF_Pack("SSSS", TTF_bininfo(scalar @pskeys, 6))); + foreach my $k (sort {$a <=> $b} @pskeys) + { $fh->print(TTF_Pack("Ls", $k, $silf->{'pseudos'}{$k})); } + $numlin = $silf->{'numLinearClasses'}; + $fh->print(TTF_Pack("SS", scalar @{$silf->{'classes'}}, $numlin)); + my (@coffsets); + # printf "%X, ", $fh->tell() - $base; + my ($cbase) = (scalar @{$silf->{'classes'}} + 1) * ($self->{'Version'} >= 4 ? 4 : 2) + 4; + for ($i = 0; $i < $numlin; $i++) + { + push (@coffsets, $cbase); + $cbase += 2 * scalar @{$silf->{'classes'}[$i]}; + } + my (@nonlinclasses); + for ($i = $numlin; $i < @{$silf->{'classes'}}; $i++) + { + my (@c, $d, @d); + my $c = $silf->{'classes'}[$i]; + push (@coffsets, $cbase); + @c = sort {$c->[$a] <=> $c->[$b]} (0 .. $#{$c}); + foreach $d (@c) + { push (@d, $c->[$d], $d); } + push (@nonlinclasses, [@d]); + my ($len) = scalar @d; + $cbase += 8 + 2 * $len; + } + push (@coffsets, $cbase); + $fh->print(pack(($self->{'Version'} >= 4 ? 'N*' : 'n*'), @coffsets)); + for ($i = 0; $i < $numlin; $i++) + { $fh->print(pack("n*", @{$silf->{'classes'}[$i]})); } + # printf "%X, ", $fh->tell() - $base; + for ($i = $numlin; $i < @{$silf->{'classes'}}; $i++) + { + + my ($num) = scalar @{$nonlinclasses[$i-$numlin]}; + my (@bin) = TTF_bininfo($num/2, 1); + $fh->print(TTF_Pack("SSSS", @bin)); + $fh->print(pack("n*", @{$nonlinclasses[$i-$numlin]})); + } + $oPasses = $fh->tell() - $subbase; +# printf "original pass = %04X\n", $oPasses; + push (@opasses, $oPasses); + foreach (@{$silf->{'PASS'}}) + { push(@opasses, $self->out_pass($fh, $_, $silf, $subbase) - $subbase); } + $end = $fh->tell(); + $fh->seek($ooPasses, 0); + $fh->print(pack("N*", @opasses)); + if ($self->{'Version'} >= 3) + { + $fh->seek($subbase + 4, 0); + $fh->print(TTF_Pack("SS", $ooPasses - $subbase, $oPseudo)); + } + $fh->seek($end, 0); + $silfc++; + } +} + +sub XML_element +{ + my ($self, $context, $depth, $k, $val, $ind) = @_; + my ($fh) = $context->{'fh'}; + my ($i); + + return $self if ($k eq 'LOC'); + + if ($k eq 'classes') + { + $fh->print("$depth\n"); + foreach $i (0 .. $#{$val}) + { + $fh->printf("$depth \n", $i); + $fh->printf("$depth " . join(" ", map{sprintf("%d", $_)} @{$val->[$i]})); + $fh->print("\n$depth \n"); + } + $fh->print("$depth\n"); + } + elsif ($k eq 'fsm') + { + $fh->print("$depth\n"); + my ($i) = 0; + foreach (@{$val}) + { $fh->print("$depth " . join(" ", @{$_}) . "\n"); $i++; } + $fh->print("$depth\n"); + } + elsif ($k eq 'colmap') + { + my ($i); + $fh->print("$depth"); + foreach my $k (sort {$a <=> $b} keys %{$val}) + { + if ($i++ % 8 == 0) + { $fh->print("\n$depth "); } + $fh->printf(" %d=%d", $k, $val->{$k}); + } + $fh->print("\n$depth\n"); + } + elsif ($k eq 'constraintCode' or $k eq 'actionCode') + { + $fh->print("$depth<$k>\n"); + foreach my $i (0 .. $#{$val}) + { + my (@rules) = $self->unpack_code($val->[$i]); + next unless (@rules); + $fh->print("$depth [$i])) . "'>\n"); + foreach my $r (@rules) + { $fh->print("$depth $r->[0]: ". join(", ", @{$r}[1..$#{$r}]) . "\n"); } + $fh->print("$depth \n"); + } + $fh->print("$depth\n"); + } + else + { return $self->SUPER::XML_element($context, $depth, $k, $val, $ind); } + + $self; +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 4; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut diff --git a/lib/Font/TTF/Sill.pm b/lib/Font/TTF/Sill.pm new file mode 100644 index 0000000..1977709 --- /dev/null +++ b/lib/Font/TTF/Sill.pm @@ -0,0 +1,143 @@ +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; +} + +=head2 $t->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 6; +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Table.pm b/lib/Font/TTF/Table.pm new file mode 100644 index 0000000..5367c6e --- /dev/null +++ b/lib/Font/TTF/Table.pm @@ -0,0 +1,464 @@ +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 nocompress + +If set, overrides the font default for WOFF table compression. Is a scalar integer specifying a +table size threshold below which this table will not be compressed. Set to -1 to never +compress; 0 to always compress. + +=item INFILE + +The read file handle + +=item OFFSET + +Location of the file in the input file + +=item LENGTH + +Length in the input directory + +=item ZLENGTH + +Compressed length of the table if a WOFF font. 0 < ZLENGTH < LENGTH implies table is compressed. + +=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; +use IO::String; +$VERSION = 0.0001; + +my $havezlib = eval {require Compress::Zlib}; + +=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> + +For WOFF-compressed tables, the table is first decompressed and a +replacement file handle is created for reading the decompressed data. In this +case ORIGINALOFFSET will preserve the original value of OFFSET for +applications that care. + +=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); + if (0 < $self->{' ZLENGTH'} && $self->{' ZLENGTH'} < $self->{' LENGTH'}) + { + # WOFF table is compressed. Uncompress it to memory and create new fh + die ("Cannot uncompress WOFF data: Compress::Zlib not present.\n") unless $havezlib; + $self->{' ORIGINALOFFSET'} = $self->{' OFFSET'}; # Preserve this for those who care + my $dat; + $self->{' INFILE'}->read($dat, $self->{' ZLENGTH'}); + $dat = Compress::Zlib::uncompress($dat); + warn "$self->{' NAME'} table decompressed to wrong length" if $self->{' LENGTH'} != bytes::length($dat); + $self->{' INFILE'} = IO::String->new($dat); + binmode $self->{' INFILE'}; + $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); + if (0 < $self->{' ZLENGTH'} && $self->{' ZLENGTH'} < $self->{' LENGTH'}) + { + # WOFF table is compressed. Uncompress it directly to ' dat' + die ("Cannot uncompress WOFF data: Compress::Zlib not present.\n") unless $havezlib; + my $dat; + $self->{' INFILE'}->read($dat, $self->{' ZLENGTH'}); + $dat = Compress::Zlib::uncompress($dat); + warn "$self->{' NAME'} table decompressed to wrong length" if $self->{' LENGTH'} != bytes::length($dat); + $self->{' dat'} = $dat; + } + else + { + $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'}; + + if (0 < $self->{' ZLENGTH'} && $self->{' ZLENGTH'} < $self->{' LENGTH'}) + { + # WOFF table is compressed. Have to uncompress first + $self->read_dat; + $fh->print($self->{' dat'}); + return $self; + } + + # We don't really have to keep the following code... we could have + # just always done a full read_dat() on the table. But the following + # is more memory-friendly so I've kept it for the more common case + # of non-compressed tables. + + $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, $ind) = @_; + 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; + } + + if ($ind) + { $fh->printf("%s<%s i='%d'>\n", $depth, $k, $ind); } + else + { $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') + { + my ($c) = 1; + foreach $d (@{$dat}) + { $self->XML_element($context, $ndepth, 'elem', $d, $c++); } + } + 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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 0; +} + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Ttc.pm b/lib/Font/TTF/Ttc.pm new file mode 100644 index 0000000..116221a --- /dev/null +++ b/lib/Font/TTF/Ttc.pm @@ -0,0 +1,176 @@ +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'}) if $self->{' INFILE'}; + undef; +} + +1; + +=head1 BUGS + +No known bugs, but then not ever executed! + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Ttopen.pm b/lib/Font/TTF/Ttopen.pm new file mode 100644 index 0000000..15e0c14 --- /dev/null +++ b/lib/Font/TTF/Ttopen.pm @@ -0,0 +1,1345 @@ +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 preceded 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 preceded 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 preceded by a space and gives the offset relative to the start of the whole +table of this particular feature. + +=item PARMS + +If FeatureParams are defined for this feature, this contains a reference to the corresponding FeatureParams object. Otherwise set to null. + +=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 preceded 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 FILTER + +Holds the MarkFilteringSet (that is, the index into GDEF->MARKSETS) for the lookup. + +=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 preceded 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. In most cases, each element of the array +corresponds to an element in the coverage table (governed by the coverage index). +In a few caess, such as when 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 preceding 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 + +=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 + +=back + +=back + +=head1 METHODS + +=cut + +use Font::TTF::Table; +use Font::TTF::Utils; +use Font::TTF::Coverage; + +use strict; +use vars qw(@ISA %FeatParams); + +@ISA = qw(Font::TTF::Table); + +%FeatParams = ( + 'ss' => 'Font::TTF::Features::Sset', + 'cv' => 'Font::TTF::Features::Cvar', + 'si' => 'Font::TTF::Features::Size', + ); + +=head2 $t->read + +Reads the table passing control to the subclass to handle the subtable specifics + +=cut + +sub read +{ + my ($self) = @_; + $self->SUPER::read or return $self; + + my ($dat, $i, $l, $oScript, $oFeat, $oLook, $tag, $nScript, $off, $dLang, $nLang, $lTag); + my ($nFeat, $oParms, $FType, $nLook, $nSub, $j, $temp, $t); + my ($fh) = $self->{' INFILE'}; + my ($moff) = $self->{' OFFSET'}; + + $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) + { + $oFeat=$moff + $l->{$tag}{' OFFSET'}; + $fh->seek($oFeat, 0); + $fh->read($dat, 4); + ($oParms, $nLook) = unpack("n2", $dat); + $fh->read($dat, $nLook * 2); + $l->{$tag}{'LOOKUPS'} = [unpack("n*", $dat)]; + $l->{$tag}{'PARMS'}=""; + if ($oParms > 0) + { + $FType=$FeatParams{substr($tag,0,2)}; + if ($FType) + { + $t=$FType; + if ($^O eq "MacOS") + { $t =~ s/^|::/:/oig; } + else + { $t =~ s|::|/|oig; } + require "$t.pm"; + $l->{$tag}{'PARMS'} = $FType->new( INFILE => $fh, + OFFSET => $oFeat+$oParms); + $l->{$tag}{'PARMS'}->read; + } + } + + } + +# 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') + { + # Make copies of referenced languages for each reference. + 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); + my @offsets = unpack("n*", $dat); + if ($l->{'FLAG'} & 0x0010) + { + $fh->read($dat, 2); + $l->{'FILTER'} = 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 LANG_TAGS list for a script, 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, $oFtable, $oParms, $FType, $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}; + $oFtable = tell($fh) - $base - $oFeat; + $tag->{' OFFSET'} = $oFtable; + $fh->print(pack("n*", 0, $#{$tag->{'LOOKUPS'}} + 1, @{$tag->{'LOOKUPS'}})); + if ($tag->{'PARMS'}) + { + $end = $fh->tell(); + $oParms = $end - $oFtable - $base - $oFeat; + $fh->seek($oFtable + $base + $oFeat,0); + $fh->print(pack("n",$oParms)); + $fh->seek($end,0); + $tag->{'PARMS'}->out($fh); + } + } + $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; + + # LookupListTable (including room for offsets to LookupTables) + $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); + $tag->{' OFFSET'} = $fh->tell() - $base - $oLook; # offset to this extension lookup + # LookupTable (including actual offsets to subtables) + $fh->print(pack("nnn", $ext, $tag->{'FLAG'}, $nSub)); + $fh->print(pack("n*", map {6 + $nSub * 2 + $_ * 8 + ($tag->{'FLAG'} & 0x0010 ? 2 : 0) } (0 .. $nSub-1))); + $fh->print(pack("n", $tag->{'FILTER'})) if $tag->{'FLAG'} & 0x0010; + $tag->{' EXT_OFFSET'} = $fh->tell(); # = first extension lookup subtable + 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) + { + # LookupTable (including room for subtable offsets) + $fh->print(pack("nnn", $tag->{'TYPE'}, $tag->{'FLAG'}, $nSub)); + $fh->print(pack("n", 0) x $nSub); + $fh->print(pack("n", $tag->{'FILTER'})) if $tag->{'FLAG'} & 0x0010; + } + else + { $end = $tag->{' EXT_OFFSET'}; } # Extension offsets computed relative to start of first Extension subtable -- corrected later + 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 POST)) # only Input and Lookahead sequences count (Lookbehind doesn't) -- see OT spec. + { + $lgt += @{$m->{$_}} if exists $m->{$_}; + } + $maxcontext = $lgt if $lgt > $maxcontext; + } + } + + } + } + + $maxcontext; +} + + +=head2 $t->update + +Perform various housekeeping items: + +For all lookups, set/clear 0x0010 bit of flag words based on 'FILTER' value. + +Sort COVERAGE table and RULES for all lookups. + +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); + + if (defined ($self->{'LOOKUP'})) + { + + # make flag word agree with mark filter setting: + for my $l (@{$self->{'LOOKUP'}}) + { + if (defined $l->{'FILTER'}) + { $l->{'FLAG'} |= 0x0010; } + else + { $l->{'FLAG'} &= ~0x0010; } + } + + unless ($Font::TTF::Coverage::dontsort) + { + # Sort coverage tables and rules of all lookups by glyphID + # The lookup types that need to be sorted are: + # GSUB: 1.2 2 3 4 5.1 6.1 8 (However GSUB type 8 lookups are not yet supported by Font::TTF) + # GPOS: 1.2 2.1 3 4 5 6 7.1 8.1 + + for my $l (@{$self->{'LOOKUP'}}) + { + next unless defined $l->{'SUB'}; + for my $sub (@{$l->{'SUB'}}) + { + if (defined $sub->{'COVERAGE'} and $sub->{'COVERAGE'}{'cover'} and !$sub->{'COVERAGE'}{'dontsort'}) + { + # OK! Found a lookup with coverage table: + my @map = $sub->{'COVERAGE'}->sort(); + if (defined $sub->{'RULES'} and ($sub->{'MATCH_TYPE'} =~ /g/ or $sub->{'ACTION_TYPE'} =~ /[gvea]/)) + { + # And also a RULES table which now needs to be re-sorted + my $newrules = []; + foreach (0 .. $#map) + { push @{$newrules}, $sub->{'RULES'}[$map[$_]]; } + $sub->{'RULES'} = $newrules; + } + } + + # Special case for Mark positioning -- need to also sort the MarkArray + if (exists($sub->{'MARKS'}) and ref($sub->{'MATCH'}[0]) =~ /Cover/ and $sub->{'MATCH'}[0]{'cover'} and !$sub->{'MATCH'}[0]{'dontsort'}) + { + my @map = $sub->{'MATCH'}[0]->sort(); + my $newmarks = []; + foreach (0 .. $#map) + { push @{$newmarks}, $sub->{'MARKS'}[$map[$_]]; } + $sub->{'MARKS'} = $newmarks; + } + } + } + } + } + + # 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 && defined $self->{' PARENT'}{$sibling}; + $sibling = $self->{' PARENT'}{$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' => [] }; + } + + if (defined $sibScript->{'DEFAULT'} && !defined $myScript->{'DEFAULT'}) + { + # Create default lang for this script. + $myScript->{'DEFAULT'} = { '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 has not 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 [, $template]) + +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, $template) = @_; + + return 0 unless defined $obj; + $template ||= 'n'; + unless (defined $cache->{"$obj"}) + { push (@{$cache->{''}}, $obj); } + push (@{$cache->{"$obj"}}, [$offset, $template]); + 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 (@{$r->[0]{$str}}) + { + $s = pack($_->[1], $master_cache->{$str} - $offs); + substr($out, $_->[0], length($s)) = $s; + } + } + } + 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; +} + +1; + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + diff --git a/lib/Font/TTF/Useall.pm b/lib/Font/TTF/Useall.pm new file mode 100644 index 0000000..c140eb9 --- /dev/null +++ b/lib/Font/TTF/Useall.pm @@ -0,0 +1,102 @@ +=head1 NAME + +Font::TTF::Useall - shortcut to 'use' all the Font::TTF modules + +=head1 SYNOPSIS + + use Font::TTF::Useall; + +=head1 DESCRIPTION + +Useful for debugging, this module simply does a 'use' on all the other +modules that are part of Font::TTF. + +=cut + +use Font::TTF::Ttc; +use Font::TTF::PSNames; +use Font::TTF::OTTags; +use Font::TTF::EBDT; +use Font::TTF::EBLC; +use Font::TTF::DSIG; +use Font::TTF::Sill; +use Font::TTF::Silf; +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; +use Font::TTF::Features::Cvar; +use Font::TTF::Features::Size; +use Font::TTF::Features::Sset; +use Font::TTF::Woff; +use Font::TTF::Woff::MetaData; +use Font::TTF::Woff::PrivateData; +use Font::TTF::Glat; +use Font::TTF::Gloc; +use Font::TTF::Dumper; + + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut diff --git a/lib/Font/TTF/Utils.pm b/lib/Font/TTF/Utils.pm new file mode 100644 index 0000000..2beb1b3 --- /dev/null +++ b/lib/Font/TTF/Utils.pm @@ -0,0 +1,639 @@ +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 = sprintf("%d.%04X", $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/\.([0-9a-f]+)$//oi) + { + $frac = $1; + $frac .= "0" x (4 - length($frac)); + } + else + { $frac = 0; } + $dat .= pack('nn', $res, hex($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, 'isDirty' => 1); + 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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Vhea.pm b/lib/Font/TTF/Vhea.pm new file mode 100644 index 0000000..f4f630a --- /dev/null +++ b/lib/Font/TTF/Vhea.pm @@ -0,0 +1,181 @@ +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->minsize() + +Returns the minimum size this table can be. If it is smaller than this, then the table +must be bad and should be deleted or whatever. + +=cut + +sub minsize +{ + return 36; +} + + +=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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Vmtx.pm b/lib/Font/TTF/Vmtx.pm new file mode 100644 index 0000000..9d53290 --- /dev/null +++ b/lib/Font/TTF/Vmtx.pm @@ -0,0 +1,96 @@ +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 L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut + + diff --git a/lib/Font/TTF/Win32.pm b/lib/Font/TTF/Win32.pm new file mode 100644 index 0000000..fcbf3ad --- /dev/null +++ b/lib/Font/TTF/Win32.pm @@ -0,0 +1,49 @@ +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; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/Font/TTF/Woff.pm b/lib/Font/TTF/Woff.pm new file mode 100644 index 0000000..0e151c2 --- /dev/null +++ b/lib/Font/TTF/Woff.pm @@ -0,0 +1,61 @@ +package Font::TTF::Woff; + +=head1 NAME + +Font::TTF::WOFF - holds Web Open Font File (WOFF) data for the font + +=head1 DESCRIPTION + +This contains the WOFF packaging data. + +=head1 INSTANCE VARIABLES + +This object supports the following instance variables (which, because they +reflect the structure of the table, do not begin with a space): + +=over + +=item majorVersion + +=item minorVersion + +The two version integers come directly from the WOFF font header. + +=item metaData + +Contains a reference to Font::TTF::Woff::Meta structure, if the font has WOFF metadata. + +=item privateData + +Contains a reference to a Font::TTF::Woff::Private structure, if the font has a WOFF private data block + +=back + +=head1 METHODS + +=cut + +use strict; +use vars qw(@ISA %fields @field_info); + +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +1; + +=head1 AUTHOR + +Bob Hallissy. L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/Font/TTF/Woff/MetaData.pm b/lib/Font/TTF/Woff/MetaData.pm new file mode 100644 index 0000000..5202e07 --- /dev/null +++ b/lib/Font/TTF/Woff/MetaData.pm @@ -0,0 +1,31 @@ +package Font::TTF::Woff::MetaData; + +=head1 NAME + +Font::TTF::Woff::MetaData - WOFF metadata + +=head1 DESCRIPTION + +Currently a stub, thus read() results in read_dat() + +=cut + +use Font::TTF::Utils; +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +1; + +=head1 AUTHOR + +Bob Hallissy. L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut diff --git a/lib/Font/TTF/Woff/PrivateData.pm b/lib/Font/TTF/Woff/PrivateData.pm new file mode 100644 index 0000000..ccebdc6 --- /dev/null +++ b/lib/Font/TTF/Woff/PrivateData.pm @@ -0,0 +1,32 @@ +package Font::TTF::Woff::PrivateData; + +=head1 NAME + +Font::TTF::Woff::PrivateData - WOFF Private data + +=head1 DESCRIPTION + +Currently a stub, thus read() results in read_dat() + +=cut + +use Font::TTF::Utils; +require Font::TTF::Table; + +@ISA = qw(Font::TTF::Table); + +1; + +=head1 AUTHOR + +Bob Hallissy. L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut + diff --git a/lib/Font/TTF/XMLparse.pm b/lib/Font/TTF/XMLparse.pm new file mode 100644 index 0000000..f3ff38e --- /dev/null +++ b/lib/Font/TTF/XMLparse.pm @@ -0,0 +1,192 @@ +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 + +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 + +=back + +=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); } +} + +1; + +=head1 AUTHOR + +Martin Hosken L. + + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This module is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + + + +=cut \ No newline at end of file diff --git a/lib/ttfmod.pl b/lib/ttfmod.pl new file mode 100644 index 0000000..c578a1a --- /dev/null +++ b/lib/ttfmod.pl @@ -0,0 +1,187 @@ +# 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; } + +=head1 AUTHOR + +Martin Hosken L. + +=head1 LICENSING + +Copyright (c) 1998-2016, SIL International (http://www.sil.org) + +This script is released under the terms of the Artistic License 2.0. +For details, see the full text of the license in the file LICENSE. + +=cut \ No newline at end of file diff --git a/t/OFL.txt b/t/OFL.txt new file mode 100644 index 0000000..e9d5b90 --- /dev/null +++ b/t/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2005-2008, SIL International (http://www.sil.org) +with Reserved Font Names "Gentium" and "SIL" + +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. \ No newline at end of file diff --git a/t/changes.t b/t/changes.t new file mode 100644 index 0000000..0a76701 --- /dev/null +++ b/t/changes.t @@ -0,0 +1,7 @@ +#!/usr/bin/perl +use strict; +use Test::More; + +eval 'use Test::CPAN::Changes'; +plan skip_all => 'Test::CPAN::Changes required for this test' if $@; +changes_ok(); diff --git a/t/tags.t b/t/tags.t new file mode 100755 index 0000000..2176910 --- /dev/null +++ b/t/tags.t @@ -0,0 +1,17 @@ +#! /usr/bin/perl +use strict; + +use Test::Simple tests => 6; +use Font::TTF::OTTags qw( %tttags %ttnames %iso639 readtagsfile); + +ok($tttags{'SCRIPT'}{'Cypriot Syllabary'} eq 'cprt', 'tttags{SCRIPT}'); + +ok($ttnames{'LANGUAGE'}{'AFK '} eq 'Afrikaans', 'ttnames{LANGUAGE}'); + +ok($ttnames{'LANGUAGE'}{'DHV '} eq 'Divehi (Dhivehi, Maldivian) (deprecated)' && $ttnames{'LANGUAGE'}{'DIV '} eq 'Divehi (Dhivehi, Maldivian)', 'ttnames{LANGUAGE} Dhivehi'); + +ok($ttnames{'FEATURE'}{'cv01'} eq 'Character Variants 01', 'ttnames{FEATURE}'); + +ok($iso639{'atv'} eq 'ALT ', 'iso639{atv}'); + +ok($iso639{'ALT '}->[0] eq 'atv' && $iso639{'ALT '}->[1] eq 'alt', 'iso639{ALT}'); \ No newline at end of file diff --git a/t/testfont.ttf b/t/testfont.ttf new file mode 100644 index 0000000..fbff8b4 Binary files /dev/null and b/t/testfont.ttf differ diff --git a/t/testfont.woff b/t/testfont.woff new file mode 100644 index 0000000..bc97136 Binary files /dev/null and b/t/testfont.woff differ diff --git a/t/ttfcopy.t b/t/ttfcopy.t new file mode 100755 index 0000000..98d8eab --- /dev/null +++ b/t/ttfcopy.t @@ -0,0 +1,25 @@ +#!/usr/bin/perl + +use Test::Simple tests => 4; +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); + +# same test with WOFF input: +$f = Font::TTF::Font->open("t/testfont.woff"); +ok($f); +$f->tables_do(sub { $_[0]->read; }); +$f->{'loca'}->glyphs_do(sub {$_[0]->read_dat; }); +$f->out("t/temp2.ttf"); +$res = compare("t/temp2.ttf", "t/testfont.ttf"); +ok(!$res); +unlink "t/temp2.ttf" unless ($res); +