Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
<strong>OpenType</strong> <strong>Font</strong> <strong>utility</strong><br />
Lars Hellström<br />
2009/09/02–March 13, 2011<br />
Abstract<br />
An obstacle to using <strong>OpenType</strong> and other sfnt-housed fonts with TEX is<br />
that the information is embedded in a binary file format and thus not easily<br />
accessible for traditional tools such as fontinst. sfntutil aims to extract the<br />
information in a more accessible text format, but leaves the task <strong>of</strong> making<br />
a “TEX font” from it to other tools.<br />
Contents<br />
I Preparations 2<br />
1 Preliminaries 2<br />
2 Representation formats 3<br />
2.1 TDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
2.2 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />
2.3 TEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />
II Parsing <strong>OpenType</strong> (and friends) 14<br />
3 Overall file structure 14<br />
4 Generalities on parsing tables 21<br />
4.1 Binary data parsing . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />
5 head and OS/2 tables 29<br />
5.1 The fdsc table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
5.2 The FNAM table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />
6 hmtx, hhea, and maxp tables 34<br />
6.1 The HFMX table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
7 post tables 38<br />
1
8 name table 41<br />
9 The cmap table 45<br />
10 glyf and loca tables 50<br />
11 kern tables 51<br />
12 CFF tables 56<br />
13 GPOS and GSUB tables 64<br />
III Conversion to other formats 73<br />
14 Generating PostScript CID<strong>Font</strong>s 73<br />
15 Conversion to fontinst metrics 83<br />
15.1 Gathering glyph information . . . . . . . . . . . . . . . . . . . . . 83<br />
15.2 Generating metrics . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />
15.3 Overall control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87<br />
IV Putting it all together 87<br />
16 The program 87<br />
16.1 Generalities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87<br />
16.2 Specific commands . . . . . . . . . . . . . . . . . . . . . . . . . . . 93<br />
Part I<br />
Preparations<br />
1 Preliminaries<br />
Many 8.5-isms are relied heavily upon in the code below (particularly unsigned<br />
numbers with binary scan), so Tcl 8.5 is a requirement.<br />
1 〈∗pkg〉<br />
2 package require Tcl 8.5<br />
The md5 package (from tcllib) is used for computing XUID values.<br />
3 package require md5 2.0<br />
2
2 Representation formats<br />
2.1 TDL<br />
Parsed data is, as a rule, returned from the parser in an experimental format<br />
tentatively called TDL for Tcl Data Language. This might look as follows:<br />
sfnt-table tag head {<br />
/fontRevision 3<br />
/flags 0000000000000011<br />
/unitsPerEm 1000<br />
/created {Sun Sep 09 01:46:40 GMT 2001}<br />
/modified {Sat Jan 10 13:37:04 GMT 2004}<br />
/bbox 40 -4 922 664<br />
macStyle italic 1<br />
/lowestRecPPEM 6<br />
/fontDirectionHint 2<br />
/indexToLocFormat 0<br />
/glyphDataFormat 0<br />
}<br />
The overall syntax <strong>of</strong> TDL code is the same as for a Tcl script, except that command<br />
and variable substitutions are unsupported (the interpretation <strong>of</strong> unquoted<br />
[ and $ characters is unspecified and can generally be expected to vary). “Command<br />
names” generally specify what something is, whereas the data is found in<br />
the arguments.<br />
There is a rather strong correspondence <strong>of</strong> TDL to XML in that TDL “command<br />
names” which can be names <strong>of</strong> XML elements—i.e., those that match the<br />
regexp<br />
[[:alpha:]_:][[:alnum:]_:.-]*<br />
—are required to have the syntax<br />
{name} {attribute} {value} ∗ {TDL code} ?<br />
and correspond to an XML element by the same name, with the given explicit<br />
attributes, and with contents <strong>of</strong> that element the XML which corresponds to the<br />
given {TDL code} (or empty contents if that is omitted). Thus the sfnt-table<br />
element above corresponds to a piece <strong>of</strong> XML code on the form<br />
〈contents〉<br />
and the macStyle element corresponds to the XML element<br />
<br />
This is the primary mechanism in TDL for nesting data containers.<br />
Command names which do not match the above regexp, particularly command<br />
names which begin with ‘/’, need not (and typically will not) adhere to this syntax;<br />
instead they may rely on position in argument sequence for identifying the<br />
3
prettyTDL::theinterp<br />
(slave interp.)<br />
prettyTDL::prettyprint<br />
(proc)<br />
meaning <strong>of</strong> an argument. This <strong>of</strong>ten has the advantage <strong>of</strong> permitting a more succint<br />
encoding <strong>of</strong> data than is possible in XML, and it also lends itself better to<br />
the generation <strong>of</strong> TEX-parsable data, as TEX macros are heavily geared towards<br />
positional syntax. Non-XML command names may still have a correspondence to<br />
some XML element, but this correspondence must be set up separately for each<br />
command.<br />
A particular TDL command <strong>of</strong> the non-XML variety is ‘/’, which is used for<br />
encoding character data. Its syntax is<br />
/ {string} ∗<br />
and the various {string}s are concatenated in the manner <strong>of</strong> append, i.e., without<br />
space between them. Several / commands in sequence is equivalent to one with<br />
the combined argument sequences.<br />
Having specified that much, it is possible to define an operation on TDL data,<br />
namely prettyprinting <strong>of</strong> it.<br />
4 namespace eval prettyTDL {<br />
The idea is that TDL data gets parsed by being evaluated in an empty slave<br />
interpreter.<br />
5 interp create -safe [list [namespace current]::theinterp]<br />
6 theinterp hide namespace<br />
7 theinterp invokehidden namespace delete ::<br />
8 }<br />
The central command point for prettyprinting is the prettyprint procedure,<br />
which has the call syntax<br />
prettyTDL::prettyprint {script} {option} {value} ∗<br />
and returns the prettyprinting <strong>of</strong> the {script}. The supported {option}s are:<br />
-indent Basic indent string for the code block. Defaults to the empty string.<br />
-step Indent step, as a string to append to the -indent. Defaults to three spaces.<br />
The way the prettyprinting works is that each command appends the prettyprinted<br />
form <strong>of</strong> itself, preceded by the appropriate indentation and followed by<br />
res (local var.) a newline, to the local variable res in this procedure. The local array O has two<br />
O (local array) entries -indent and -step which contain the current values <strong>of</strong> these parameters.<br />
9 proc prettyTDL::prettyprint {script args} {<br />
10 set res ""<br />
11 array set O {-indent "" -step { }}<br />
12 array set O $args<br />
13 theinterp eval $script<br />
14 return $res<br />
15 }<br />
4
Since it’s a somewhat esoteric topic, it may be appropriate here to spell out how<br />
the use <strong>of</strong> a slave interpreter interacts with the call stack: every interpreter has<br />
a separate stack <strong>of</strong> contexts. Hence a master interpreter procedure that has an<br />
alias in the slave interpreter will have variables <strong>of</strong> the prettyprint procedure at<br />
upvar 1, if that slave interpeter alias was invoked as part <strong>of</strong> the prettyinterp\<br />
eval $script. Therefore local prettyprint contexts can nest, even though<br />
prettyinterp stays within the same global context throughout.<br />
prettyTDL::unknown (proc) Via an alias, this procedure serves as the unknown handler in the prettyTDL::theinterp<br />
slave interpreter.<br />
16 prettyTDL::theinterp alias unknown\<br />
[namespace current]::prettyTDL::unknown<br />
Its call syntax is thus<br />
prettyTDL::unknown {name} {argument} ∗<br />
and its main task is to distinguish XML-style commands from the rest, as the<br />
bodies <strong>of</strong> the former should be given special care when prettyprinting.<br />
17 proc prettyTDL::unknown {name args} {<br />
18 upvar 1 res res O O<br />
19 switch -regexp -- $name {<br />
20 {^[[:alpha:]_:][[:alnum:]_:.-]*$} {<br />
21 if {[llength $args] % 2} then {<br />
22 set body [lindex $args end]<br />
23 set args [lreplace $args end end]<br />
24 } else {<br />
25 set body ""<br />
26 }<br />
27 append res $O(-indent) [linsert $args 0 $name]<br />
28 if {[regexp {\S} $body]} then {<br />
29 append res " \{\n" [<br />
30 prettyprint $body {*}[array get O] -indent\<br />
$O(-indent)$O(-step)<br />
32 ] $O(-indent) \}<br />
33 }<br />
34 append res \n<br />
35 }<br />
36 default {<br />
37 append res $O(-indent) [linsert $args 0 $name] \n<br />
38 }<br />
39 }<br />
40 }<br />
prettyTDL::slash (proc) For the ‘/’ TDL command, it is possible to do slightly better than for an arbitrary<br />
non-XML command, since one can split multiline strings at newlines to preserve<br />
the indentation. The logic is as follows: if an argument contains a newline (and<br />
that argument is not the last being only a newline), then emit one ‘/’ command<br />
for everything up to and including that newline, and process what comes after<br />
5
it anew. The newline itself is expressed as a separate ‘\n’ argument (backslash<br />
substitution).<br />
41 proc prettyTDL::slash {args} {<br />
42 upvar 1 res res O(-indent) indent<br />
43 set L [list /]<br />
44 for {set i 0} {$i < [llength $args]} {incr i} {<br />
45 set n [string first \n [lindex $args $i]]<br />
46 if {$n0} then {<br />
51 lappend L [string range [lindex $args $i] 0 $n-1]<br />
52 }<br />
53 append res $indent $L { \n} \n<br />
54 set L [list /]<br />
55 lset args $i [string replace [lindex $args $i] 0 $n]<br />
56 incr i -1<br />
57 }<br />
58 if {[llength $L] > 1} then {<br />
59 append res $indent $L \n<br />
60 }<br />
61 }<br />
62 prettyTDL::theinterp alias / [namespace current]::prettyTDL::slash<br />
2.2 XML<br />
Another useful operation is that <strong>of</strong> converting a TDL script to equivalent XML,<br />
which is what the following does—or rather, it converts a TDL script to a list <strong>of</strong><br />
tdom data-trees. A data-tree is an encoding (using built-in Tcl data containers) <strong>of</strong><br />
general XML elements; technically it is a list on one <strong>of</strong> the two forms<br />
{element-name} {attribute-dict} {children}<br />
#text {character data}<br />
where the {children} is again a list (possibly empty) <strong>of</strong> data-trees. The<br />
{attribute-dict} is a dictionary mapping attribute names to their values, neither<br />
<strong>of</strong> which have any quoting <strong>of</strong> special characters. Similarly, the {character data}<br />
is text between tags, without any XML-quoting.<br />
For XML-commands and the ‘/’ command, this conversion is obvious, but for<br />
other non-XML commands some kind <strong>of</strong> XML encoding will have to be invented.<br />
The basic method is to express those as TDL:cmd elements, which must conform<br />
to the DTD fragment<br />
<br />
<br />
<br />
<br />
6
TDL:cmd (XML element)<br />
TDL:arg (XML element)<br />
TDLtoXML::theinterp<br />
(slave interp.)<br />
In other words, the command name is made an attribute <strong>of</strong> the TDL:cmd element,<br />
while the arguments appear in sequence as the character data contents <strong>of</strong> TDL:arg<br />
elements. It may be observed that this makes the whitespace contents in TDL:arg<br />
elements highly significant.<br />
63 namespace eval TDLtoXML {<br />
The implementation <strong>of</strong> this converter has very much in common with the TDL<br />
prettyprinter. For example, it uses an empty slave interpreter for parsing TDL<br />
scripts.<br />
64 interp create -safe [list [namespace current]::theinterp]<br />
65 theinterp hide namespace<br />
66 theinterp invokehidden namespace delete ::<br />
67 }<br />
TDLtoXML::main (proc) The central command point for the conversion is the main procedure, which has<br />
the call syntax<br />
res (local var.)<br />
TDLtoXML::main {script}<br />
and returns the list <strong>of</strong> data-trees to which the {script} corresponds.<br />
The way the conversion works is that each command appends the data-tree<br />
form <strong>of</strong> itself, as a list element, to the local variable res in this procedure.<br />
68 proc TDLtoXML::main {script} {<br />
69 set res {}<br />
70 theinterp eval $script<br />
71 return $res<br />
72 }<br />
TDLtoXML::unknown (proc) Via an alias, this procedure serves as the unknown handler in the slave interpreter.<br />
73 TDLtoXML::theinterp alias unknown [namespace current]::TDLtoXML::unknown<br />
Its call syntax is thus<br />
prettyTDL::unknown {name} {argument} ∗<br />
and its main task is to distinguish XML-style commands from the rest, as they<br />
convert to XML in rather different ways.<br />
74 proc TDLtoXML::unknown {name args} {<br />
75 switch -regexp -- $name {<br />
76 {^[[:alpha:]_:][[:alnum:]_:.-]*$} {<br />
77 set tree [list $name]<br />
78 if {[llength $args] % 2} then {<br />
79 lappend tree [lreplace $args end end]\<br />
[main [lindex $args end]]<br />
81 } else {<br />
82 lappend tree $args {}<br />
83 }<br />
84 }<br />
85 default {<br />
7
86 set L {}<br />
87 foreach arg $args {<br />
88 lappend L [list TDL:arg {} [list [list \#text $arg]]]<br />
89 }<br />
90 set tree [list TDL:cmd [list name $name] $L]<br />
91 }<br />
92 }<br />
93 uplevel 1 [list ::lappend res $tree]<br />
94 }<br />
TDLtoXML::slash (proc) The ‘/’ TDL command needs special treatment, since it should not be expressed<br />
using TDL:cmd. The implementation here takes the easy route <strong>of</strong> making each<br />
argument a separate #text item.<br />
TDLtoXML::xml_from_trees<br />
(proc)<br />
95 proc TDLtoXML::slash {args} {<br />
96 upvar 1 res res<br />
97 foreach arg $args {<br />
98 lappend res [list \#text $arg]<br />
99 }<br />
100 }<br />
101 TDLtoXML::theinterp alias / [namespace current]::TDLtoXML::slash<br />
Other commands may <strong>of</strong> course also have custom conversions to XML; the<br />
above is merely the bare minimum needed to make the system work.<br />
In order to convert data-trees to proper XML, one may use the tdom package, or<br />
(in cases where readability is not an issue) use the following lightweight generator.<br />
This procedure is the toplevel generator <strong>of</strong> XML code from a list <strong>of</strong> data-tree. The<br />
call syntax is<br />
TDLtoXML::xml_from_trees {list <strong>of</strong> trees} {option} {value} ∗<br />
where {list <strong>of</strong> trees} <strong>of</strong> course is the list <strong>of</strong> those trees that should be converted<br />
and the return value is XML code for the corresponding sequence <strong>of</strong> elements (and<br />
whatever). The supported options are:<br />
-nodesep Basic separator inserted between nodes <strong>of</strong> the trees; defaults to a newline.<br />
Setting it to empty reduces the output size, but may well have the<br />
effect <strong>of</strong> making the entire result a single very long line.<br />
-indentstep An indentation step, defaults to being empty. Some repetition (corresponding<br />
to nesting depth) <strong>of</strong> this string follows the -nodesep.<br />
-charmap A dictionary <strong>of</strong> character to entity mappings used with string map<br />
to quote problematic characters in XML character data. Defaults to being<br />
empty. The five syntax characters ‘’, ‘&’, ‘’’, and ‘"’ are automatically<br />
inserted into this dictionary if they are not already there.<br />
102 proc TDLtoXML::xml_from_trees {treeL args} {<br />
103 array set Opt {<br />
104 -nodesep \n<br />
8
TDLtoXML:<br />
:xml_from_treenode (proc)<br />
105 -indentstep ""<br />
106 -charmap {}<br />
107 }<br />
108 array set Opt $args<br />
109 if {![dict size $Opt(-charmap)]} then {<br />
110 set Opt(-charmap) {< < > > & & ’ ' \" "}<br />
111 } else {<br />
112 foreach char {< > & ’ \"} entity {< > & ' "} {<br />
113 if {![dict exists $O(-charmap) $char]} then {<br />
114 dict set $O(-charmap) $char $entity<br />
115 }<br />
116 }<br />
117 }<br />
118 set res ""<br />
119 foreach tree $treeL {<br />
120 append res [<br />
121 xml_from_treenode $tree $Opt(-nodesep) $Opt(-indentstep)\<br />
$Opt(-charmap)<br />
123 ]<br />
124 }<br />
125 return $res<br />
126 }<br />
This is a more internal procedure that generates the XML fragment for a particular<br />
branch <strong>of</strong> the tree. The call syntax is<br />
xml_from_treenode {branch} {node-sep} {indent-step} {char-map}<br />
Not surprisingly, it relies on recursion to convert the tree as a whole.<br />
127 proc TDLtoXML::xml_from_treenode {tree sep indent map} {<br />
128 switch -- [lindex $tree 0] "#text" - "#cdata" {<br />
129 return [string map $map [lindex $tree 1]]<br />
130 } "#comment" {<br />
131 return ""<br />
132 } "#pi" {<br />
133 return ""<br />
134 } "TDL:arg" {<br />
A special case is added for TDL:arg elements, to preserve the amount <strong>of</strong> whitespace<br />
there.<br />
135 set sep ""<br />
136 set indent ""<br />
137 }<br />
138 set res ""<br />
9
TDLtoTeX::theinterp<br />
(slave interp.)<br />
145 foreach child [lindex $tree 2] {<br />
146 append res $subsep\<br />
[xml_from_treenode $child $subsep $indent $map]<br />
148 }<br />
149 append res $sep ""<br />
150 } else {<br />
151 append res "/>"<br />
152 }<br />
153 return $res<br />
154 }<br />
2.3 TEX<br />
The model for the TEX-style output format is fontinst’s encoding and metric files,<br />
i.e., elements are either atomic commands (with a fixed number <strong>of</strong> arguments)<br />
or composite constructions with delimiting beginning-<strong>of</strong> and end-<strong>of</strong> commands.<br />
Interesting content should as a rule have special purpose commands—if not for<br />
some other reason, then because TEX is stricter with respect to what characters<br />
are allowed in command names than XML and Tcl—but there are some general<br />
translation mechanisms in force that can be useful for novel or unusual data structures.<br />
155 namespace eval TDLtoTeX {<br />
For the implementation, we need another interpreter.<br />
156 interp create -safe [list [namespace current]::theinterp]<br />
157 theinterp hide namespace<br />
158 theinterp invokehidden namespace delete ::<br />
159 }<br />
An XML-style command {tag} can be converted to a block<br />
\ELEMENT{〈tag〉}{<br />
〈attributes〉<br />
}<br />
〈children〉<br />
\ENDELEMENT{〈tag〉}<br />
where an 〈attribute〉 is a command on the form<br />
\ATTR{〈name〉}{〈value〉}<br />
Likewise, a ‘/’ command can be converted to a sequence <strong>of</strong><br />
\TEXT{〈argument〉}<br />
commands. Non-XML commands are on the other hand by default only included<br />
as comments (in TDL form), since their variadic nature make them hard to map<br />
to TEX commands without knowing their meanings.<br />
The above incorporates some changes relative to its first form:<br />
10
TDLtoTeX::quotechars<br />
(proc)<br />
• The 〈attributes〉 appear as an argument <strong>of</strong> \ELEMENT; they used to just be<br />
a part <strong>of</strong> the element body. The reason for this change is that the original<br />
approach would have made it rather difficult to write TEX macros that convert<br />
the above back to TDL or XML: the \ELEMENT command would have<br />
to rely on subsequent commands to complete its operation, since it cannot<br />
know how many \ATTR will follow.<br />
• The \ENDELEMENT did not have any argument. While not essential, it can<br />
become easier to implement this command if the 〈tag〉 is supplied explicitly<br />
(consider whether to map to \endsetglyph, \endsetslot, \endvarchar, or<br />
whatever).<br />
The 〈tag〉, 〈name〉, 〈value〉, and 〈argument〉 above are all basically strings, but<br />
since TEX doesn’t have a distinguished strting type, it is necessary to clarify what<br />
this means in terms <strong>of</strong> octets in a file. The basic rules are:<br />
• Non-ASCII characters are UTF-8 encoded.<br />
• ASCII characters <strong>of</strong> categories 11 (letter) and 12 (other) represent themselves.<br />
• Spaces are represented as spaces in positions where they are not gobbled by<br />
TEX’s parser, and by the control sequence \space otherwise.<br />
• Control characters are for the moment ignored, in the hope that they won’t<br />
occur where it would mess things up.<br />
• Other characters are expressed using their L ATEX internal character representations,<br />
which means<br />
# becomes \#<br />
$ becomes \$<br />
% becomes \%<br />
& becomes \&<br />
^ becomes \textasciicircum<br />
_ becomes \_<br />
~ becomes \textasciitilde<br />
\ becomes \textbackslash<br />
{ becomes \{<br />
} becomes \}<br />
The use <strong>of</strong> \$, \_, \{, and \} here is not canonical, but these control sequences<br />
are valid aliases for the characters in question, and should serve to<br />
improve readability.<br />
This procedure takes a string as argument and returns it with all special characters<br />
LICR-quoted, as explained above. The first step is to replace anything sensitive<br />
by its command equivalent. The ‘~’ character is temporarily used as an “end <strong>of</strong><br />
control sequence” marker, but will be turned into a proper space later.<br />
160 proc TDLtoTeX::quotechars {str} {<br />
11
161 set qstr [string map {<br />
162 \# {\#} \$ {\$} % {\%} & {\&} ^ {\textasciicircum~} _ {\_}<br />
163 ~ {\textasciitilde~} \\ {\textbackslash~} \{ {\{} \} {\}}<br />
164 " " {\space~}<br />
165 } $str]<br />
The second step is to convert those \space~s that don’t follow a ~ to proper<br />
spaces, since it is safe to do so and it enhances readability.<br />
166 regsub -all {([^~])\\space~} $qstr {\1 } qstr<br />
Next, all remaining ~ not followed by a letter can be dropped.<br />
167 regsub -all {~([^A-Za-z])} $qstr {\1} qstr<br />
Finally, all other ~ must be converted to proper spaces.<br />
168 return [string map {~ { }} $qstr]<br />
169 }<br />
TDLtoTeX::main (proc) The central command point for TDL-to-TEX conversion is the main procedure,<br />
which has the call syntax<br />
TDLtoTeX::main {script} {option} {value} ∗<br />
and returns the TEX counterpart <strong>of</strong> the {script}. The supported {option}s are:<br />
-indent Basic indent string for the code block. Defaults to the empty string.<br />
-step Indent step, as a string to append to the -indent. Defaults to two spaces.<br />
The way the conversion works is that each command appends its own conversion,<br />
preceded by the appropriate indentation and followed by a newline, to the<br />
res (local var.) local variable res in this procedure. The local array O has two entries -indent<br />
O (local array) and -step which contain the current values <strong>of</strong> these parameters.<br />
170 proc TDLtoTeX::main {script args} {<br />
171 set res ""<br />
172 array set O {-indent "" -step { }}<br />
173 array set O $args<br />
174 theinterp eval $script<br />
175 return $res<br />
176 }<br />
TDLtoTeX::unknown (proc) Via an alias, this procedure serves as the unknown handler in the TDLtoTeX::theinterp<br />
slave interpreter.<br />
177 TDLtoTeX::theinterp alias unknown [namespace current]::TDLtoTeX::unknown<br />
Its call syntax is thus<br />
TDLtoTeX::unknown {name} {argument} ∗<br />
and its main task is to distinguish XML-style commands from the rest, as former<br />
are converted but the latter are not.<br />
178 proc TDLtoTeX::unknown {name args} {<br />
179 upvar 1 res res O O<br />
12
180 switch -regexp -- $name {<br />
181 {^[[:alpha:]_:][[:alnum:]_:.-]*$} {<br />
182 if {[llength $args] % 2} then {<br />
183 set body [lindex $args end]<br />
184 set args [lreplace $args end end]<br />
185 } else {<br />
186 set body ""<br />
187 }<br />
188 set tag [quotechars $name]<br />
189 append res $O(-indent) "\\ELEMENT\{$tag\}\{"<br />
190 set nextindent $O(-indent)$O(-step)<br />
191 if {[llength $args]} then {append res \n}<br />
192 foreach {key value} $args {<br />
193 append res $nextindent "\\ATTR\{" [quotechars $key] "\}\{"\<br />
[quotechars $value] "\}\n"<br />
195 }<br />
196 if {[llength $args]} then {append res $O(-indent)}<br />
197 append res \}\n<br />
198 append res [main $body {*}[array get O] -indent $nextindent]<br />
199 append res $O(-indent) "\\ENDELEMENT\{$tag\}\n"<br />
200 }<br />
201 default {<br />
202 append res $O(-indent) {% } [<br />
203 string map [list \n "\n$O(-indent)% "]\<br />
[linsert $args 0 $name]<br />
204 ] \n<br />
205 }<br />
206 }<br />
207 }<br />
TDLtoTeX::slash (proc) The ‘/’ TDL command should<br />
208 proc TDLtoTeX::slash {args} {<br />
209 upvar 1 res res O O<br />
210 foreach arg $args {<br />
211 append res $O(-indent) "\\TEXT\{" [quotechars $arg] "\}\n"<br />
212 }<br />
213 }<br />
214 TDLtoTeX::theinterp alias / [namespace current]::TDLtoTeX::slash<br />
Many minor pieces <strong>of</strong> information scattered throughout the tables are likely to<br />
be turned into fontinst \setint commands, so they are expressed in terms <strong>of</strong> the<br />
TDL command<br />
/setint {var-name} {number}<br />
where {var-name} is equal to the corresponding fontinst integer variable name,<br />
but {number} need not be an integer, although its value is converted to match<br />
the fontinst counterpart.<br />
13
TDLtoTeX::setint (proc) The main thing that needs to be done when converting a /setint element to a<br />
\setint command is to round the {number}.<br />
/dontsetint (element)<br />
sfnt::parse_file_header<br />
(proc)<br />
215 namespace eval TDLtoTeX {<br />
216 proc setint {varname number} {<br />
217 uplevel 1 {::append res $O(-indent)} [list [<br />
218 format {\setint{%s}{%d}} [quotechars $varname]\<br />
[expr {round($number)}]<br />
220 ] \n]<br />
221 }<br />
222 theinterp alias /setint [namespace current]::setint<br />
223 }<br />
Many other pieces <strong>of</strong> information are in some sense similar to the /setint<br />
ones, but also highly unlikely to ever be <strong>of</strong> interest in the TEX context. These<br />
can instead be encoded as /dontsetint commands, where the {var-name} should<br />
usually be chosen equal to the <strong>OpenType</strong>/TrueType specification name for the<br />
field.<br />
/dontsetint {var-name} {number}<br />
Part II<br />
Parsing <strong>OpenType</strong> (and friends)<br />
3 Overall file structure<br />
The first problem when reading an <strong>OpenType</strong> font is to figure out where the<br />
various tables are located. The basic parser returns a TDL representation <strong>of</strong> the<br />
table directory, using the following elements:<br />
sfnt-font tag {header tag} ? name {font name} ? {tables}<br />
sfnt-table tag {tag} start {filepos} length {length} {contents} ?<br />
The idea is that the sfnt-table elements might later be filled in with the actual<br />
table contents, but one might just as well prefer keep a parsed form <strong>of</strong> the table<br />
separate from the overall directory. Using a TDL format for this information<br />
has the advantage that it extends nicely to TrueType Collection files (just return<br />
several sfnt-font elements).<br />
The general namespace for <strong>OpenType</strong>-related data is sfnt.<br />
224 namespace eval sfnt {}<br />
This procedure takes a channel (which must be readable, seekable, and configured<br />
-translation binary) as argument, and returns the TDL form <strong>of</strong> the corresponding<br />
sfnt-font element. In more detail, the call syntax is<br />
parse_file_header {channel} {position} ? {base} ? {attribute}<br />
{value} ∗ 14
where {position} defaults to 0 and is the position relative to the start <strong>of</strong> the file<br />
where the sfnt header is expected. The {base} also defaults to 0 and is the base<br />
position for whole-sfnt <strong>of</strong>fsets; it would be nonzero if the file is embedded in a<br />
larger file. Any additional arguments are treated as extra attribute–value pairs to<br />
add to the sfnt-font element.<br />
Calling parse_file_header typically has the side-effect <strong>of</strong> changing the current<br />
position in {channel}.<br />
The starting point is easy enough: read the initial tag, try to recognise it, and<br />
proceed accordingly. Typically, a known <strong>OpenType</strong> tag is encountered, and we<br />
can get on with the directory.<br />
225 proc sfnt::parse_file_header {F {pos 0} {base 0} args} {<br />
226 seek $F $pos<br />
227 set tag [read $F 4]<br />
228 switch -- $tag {<br />
229 true {}<br />
230 OTTO {}<br />
231 \x00\x01\x00\x00 {set tag ver:1.0}<br />
232 typ1 {}<br />
It might also be a TrueType Collection file, in which case the component files are<br />
read by recursive calls.<br />
233 ttcf {<br />
234 binary scan [read $F 8] SuH4Iu verMaj verMin num<strong>Font</strong>s<br />
235 if {$verMaj2} then {<br />
236 return -code error "Unknown TTC major version: $verMaj"<br />
237 }<br />
238 binary scan [read $F [expr {4*$num<strong>Font</strong>s}]] Iu*] <strong>of</strong>fsetL<br />
239 set dsig_pos [tell $F]<br />
240 set res {}<br />
241 foreach <strong>of</strong>fset $<strong>of</strong>fsetL {<br />
242 append res\<br />
[parse_file_header $F [expr {$base+$<strong>of</strong>fset}] $base]<br />
244 }<br />
245 if {$verMaj==2} then {<br />
246 seek $F $dsig_pos<br />
247 binary scan [read $F 12] a4IuIu dsigTag <strong>of</strong>s len<br />
248 if {$dsigTag eq "DSIG"} then {<br />
249 append res [list sfnt-table tag $dsigTag start\<br />
[expr {$base+$<strong>of</strong>s}] length $len] \n<br />
251 }<br />
252 }<br />
253 return $res<br />
254 }<br />
The following are headers for AppleSingle and AppleDouble files, respectively. An<br />
<strong>OpenType</strong> file might be found a few levels further down.<br />
255 \x00\x05\x16\x00 - \x00\x05\x16\x07 {<br />
256 return [parse_applesingle $F $pos]<br />
257 }<br />
15
If it’s not any <strong>of</strong> the above, then what could it be? Well, it could be a .dfont<br />
file, i.e., a Macintosh Suitcase-in-datafork file. These don’t carry any magic number,<br />
but one can apply some consistency checks on the header information: the<br />
supposed beginnings <strong>of</strong> resource data or resource map can’t be before position 16<br />
in the file, the supposed resource map and resource data blocks mustn’t overlap,<br />
and they must fit within the file. If all that holds, we dare suppose that the file is<br />
a (data-fork) resource file, and call on parse_resource_map to parse it for us.<br />
258 default {<br />
259 if {$pos==0} then {<br />
260 binary scan $tag[read $F 12] IuIuIuIu resDataOfs resMapOfs\<br />
resDataLen resMapLen<br />
262 seek $F 0 end<br />
263 set endpos [tell $F]<br />
264 if {<br />
265 $resDataOfs=16 &&\<br />
$resDataOfs+$resDataLen
sfnt::parse_resfile_map<br />
(proc)<br />
Mac-resource (element)<br />
293 append res \}\n<br />
294 }<br />
This procedure parses a Macintosh resource map, returning it as a list <strong>of</strong> tables not<br />
unlike those <strong>of</strong> an <strong>OpenType</strong> font, but in the case <strong>of</strong> sfnt resources it additionally<br />
invokes parse_file_header recursively to obtain the internal table structure. The<br />
call syntax is<br />
parse_resfile_map {channel} {data-start} {map-start} {data-len}<br />
{map-len}<br />
and the return value is a TDL representation <strong>of</strong> the resource map, in terms <strong>of</strong><br />
Mac-resource commands. These have the general element-command syntax, and<br />
sport the following attributes:<br />
type Four-character resource type.<br />
ID Resource ID (16-bit integer).<br />
name Resource string.<br />
start File position <strong>of</strong> beginning <strong>of</strong> resource data.<br />
length Length <strong>of</strong> resource data, in bytes.<br />
295 proc sfnt::parse_resfile_map {F dataOfs mapOfs dataLen mapLen} {<br />
296 seek $F $mapOfs<br />
297 set resMap [read $F $mapLen]<br />
298 binary scan $resMap @24SuSuS refBase nameBase numTypes<br />
299 set refBase<br />
300 set nameBase<br />
301 set numTypes<br />
302 set res ""<br />
303 for {set i 0; set pos 30} {$i
sfnt::parse_applesingle<br />
(proc)<br />
315 if {$nameOfs != 0xFFFF} then {<br />
316 set namePos [expr {$nameBase+$nameOfs}]<br />
317 binary scan $resMap @${namePos}cu len<br />
318 binary scan $resMap @${namePos}cua${len} "" name<br />
319 lappend L name [encoding convertfrom macRoman $name]<br />
320 }<br />
321 if {$type eq "sfnt"} then {<br />
322 set resPos [expr {$dataOfs+$start+4}]<br />
323 append res [parse_file_header $F $resPos $resPos {*}$L]<br />
324 } else {<br />
325 set resPos [expr {$dataOfs+$start}]<br />
326 seek $F $resPos<br />
327 incr resPos 4<br />
328 binary scan [read $F 4] Iu resLen<br />
329 lappend L start $resPos length $resLen<br />
330 append res [linsert $L 0 Mac-resource] \n<br />
331 }<br />
332 }<br />
333 }<br />
334 return $res<br />
335 }<br />
This procedure parses an AppleSingle or AppleDouble file into entities, and calls<br />
parse_resfile_map to parse the resource fork in more detail. This provides<br />
support for reading TrueType fonts stored in Mac OS resource forks that have<br />
first undergone transportation into a single-forked file system.<br />
The call syntax is<br />
parse_applesingle {channel} {start}<br />
AppleSingleEntity and the return value is a TDL script that is a sequence <strong>of</strong> AppleSingleEntity<br />
(element) commands. These have the general element-command syntax, and sport the following<br />
attributes:<br />
id Name <strong>of</strong> fork or other information item: data, resource, real name, comment,<br />
etc.<br />
start File position <strong>of</strong> beginning <strong>of</strong> resource data.<br />
length Length <strong>of</strong> resource data, in bytes.<br />
/AppleSingleHomeFS There will also be an /AppleSingleHomeFS command that takes the name <strong>of</strong> the<br />
(element) “home file system” as its only argument.<br />
336 proc sfnt::parse_applesingle {F pos} {<br />
337 seek $F $pos<br />
338 binary scan [read $F 26] H8H8A16Su magic version filesys count<br />
339 set res "\# Magic number: 0x$magic\n"<br />
340 append res "\# Format version: $version" \n<br />
341 append res [list /AppleSingleHomeFS $filesys] \n<br />
342 binary scan [read $F [expr {12*$count}]] Iu* entityL<br />
18
343 foreach {id <strong>of</strong>fset length} $entityL {<br />
344 append res [list AppleSingleEntity id [<br />
345 if {$id0} {} {incr i -1<br />
372 lappend stack [list $i]<br />
373 }<br />
374 while {[llength $stack]} {<br />
375 set idx [lindex $stack end]<br />
376 set stack [lreplace $stack [set stack end] end]<br />
377 set item [lindex $treeL $idx]<br />
378 switch -glob -- [lindex $item 0] {#*} {} sfnt-table {<br />
379 set tag [dict get [lindex $item 1] tag]<br />
380 set match 0<br />
381 foreach pat $patL {<br />
382 if {[string match $pat $tag]} then {<br />
19
sfnt::combine_tables<br />
(proc)<br />
383 set match 1<br />
384 break<br />
385 }<br />
386 }<br />
387 if {$match} then {<br />
388 seek $F [dict get [lindex $item 1] start]<br />
389 lappend res $tag\<br />
[read $F [dict get [lindex $item 1] length]]<br />
391 }<br />
392 } default {<br />
393 for {set i [llength [lindex $item 2]]} {$i>0} {} {incr i -1<br />
394 lappend stack [linsert $idx end 2 $i]<br />
395 }<br />
396 }<br />
397 }<br />
398 return $res<br />
399 }<br />
This procedure recombines a dictionary <strong>of</strong> tables within an sfnt wrapper structure.<br />
The call syntax is<br />
combine_tables {table-dict} {sfnt version}<br />
and the return values is a list <strong>of</strong> bytearrays, the join (without padding) <strong>of</strong> which<br />
will have a valid sfnt structure. The first element <strong>of</strong> the list contains the <strong>of</strong>fset<br />
table (i.e., file header) and table directory. Remaining elements contain the individual<br />
tables in the same order as in the {table-dict}, but padded to be multiples<br />
<strong>of</strong> four bytes long.<br />
If one <strong>of</strong> the tables is a head table (which ought to be the case, since the result<br />
isn’t a valid <strong>OpenType</strong> font without it) then the checkSumAdjustment field <strong>of</strong><br />
that table is updated as well.<br />
The basic approach is to construct the result table by table, and in parallel<br />
collect data for the first list element. Data which isn’t known from the start will be<br />
corrected later. pos is the “current position” within the “file” being constructed.<br />
dirD maps table tag to dictionaries <strong>of</strong> the remaining information needed for the<br />
table directory.<br />
400 proc sfnt::combine_tables {tableD sfntver} {<br />
401 set res [list {}]<br />
402 set numTables [dict size $tableD]<br />
403 set pos [expr {12+16*$numTables}]<br />
404 set dirD [dict create]<br />
405 dict for {tag data} $tableD {<br />
406 dict set dirD $tag <strong>of</strong>fset $pos<br />
407 dict set dirD $tag length [string length $data]<br />
408 binary scan $data\0\0\0 Iu* wordL<br />
409 if {$tag eq "head"} then {<br />
410 lset wordL 2 0<br />
411 set headidx [llength $res]<br />
412 }<br />
20
sfnt::〈table〉 (namespace)<br />
413 dict set dirD $tag checkSum\<br />
[expr {[::tcl::mathop::+ {*}$wordL] & 0xFFffFFff}]<br />
415 lappend res [binary format Iu* $wordL]<br />
416 incr pos [expr {4*[llength $wordL]}]<br />
417 }<br />
Now enough is known to construct the initial list element.<br />
418 set entrySelector 0<br />
419 set searchRange 16<br />
420 for {set n $numTables} {$n>1} {set n [expr {$n/2}]} {<br />
421 incr entrySelector<br />
422 incr searchRange $searchRange<br />
423 }<br />
424 set data [binary format a4SuSuSuSu $sfntver $numTables $searchRange\<br />
$entrySelector [expr {16*$numTables-$searchRange}]]<br />
426 foreach tag [lsort [dict keys $dirD]] {<br />
427 append data\<br />
[binary format a4IuIuIu $tag [dict get $dirD $tag checkSum]\<br />
[dict get $dirD $tag <strong>of</strong>fset] [dict get $dirD $tag length]]<br />
430 }<br />
431 lset res 0 $data<br />
Finally compute the overall checksum, if there was a head table.<br />
432 if {[info exists headidx]} then {<br />
433 binary scan $data Iu* wordL<br />
434 set sum [tcl::mathop::+ {*}$wordL]<br />
435 dict for {tag D} $dirD {<br />
436 incr sum [dict get $D checkSum]<br />
437 }<br />
438 lset res $headidx [string replace [lindex $res $headidx] 8 11\<br />
[binary format Iu [expr {(0xB1B0AFBA - $sum) & 0xFFffFFff}]]]<br />
440 }<br />
441 return $res<br />
442 }<br />
4 Generalities on parsing tables<br />
In general, stuff that is useful for parsing 〈table〉 tables are kept in the<br />
sfnt::〈table〉 namespace. In particular, an sfnt::〈table〉::parse procedure (if<br />
it exists) will dump the table contents in TDL format.<br />
A catch is that sometimes data in one table determines the interpretation <strong>of</strong><br />
data in another. In order to facilitate sharing <strong>of</strong> such data, commands that parse<br />
particular tables take a {gdict} (for ‘global dict’) argument, which is a dictionary<br />
where such globally relevant pieces <strong>of</strong> information can be placed. Some notable<br />
entries are:<br />
funit The length <strong>of</strong> an “funit” in AFM units (0.001 em).<br />
numGlyphs The number <strong>of</strong> glyphs in the font.<br />
21
sfnt::〈table〉:<br />
:parse (proc)<br />
The general syntax for a parse procedure is<br />
parse {data} {gdict} {gdict-var} ?<br />
where {data} is the table (a bytearray) to parse, and the return value is its translation<br />
to TDL. The {gdict} is a dictionary <strong>of</strong> data parsed from other tables, and<br />
the {gdict-var} is (if given) the name <strong>of</strong> a variable in the calling context which<br />
should be set to a version <strong>of</strong> the {gdict} which is updated with information from<br />
the {table} table.<br />
Because <strong>of</strong> such dependencies, a {table} can specify that it should not be parsed<br />
until some other tables have contributed their entries to the gdict. This is done<br />
sfnt::〈table〉:<br />
by defining a parse_after variable, whose value is the list <strong>of</strong> tables which should<br />
:parse_after (var.) be parsed first.<br />
sfnt::expand:<br />
:interpreter (theinterp)<br />
sfnt::expand:<br />
:unknown res (local (proc) var.)<br />
A useful operation on sfnt-font elements is to fill in their sfnt-tables with what<br />
one gets from parsing them. This is implemented in the sfnt::expand namespace,<br />
whose theinterp command is an empty interpreter set up to do precisely that<br />
when evaluating the return value <strong>of</strong> e.g. parse_file_header.<br />
443 namespace eval sfnt::expand {<br />
444 interp create -safe [list [namespace current]::theinterp]<br />
445 theinterp hide namespace<br />
446 theinterp invokehidden namespace delete ::<br />
As usual for TDL-to-TDL operations, the implementation <strong>of</strong> a command in the<br />
slave interpreter is supposed to append the operation result to the res variable in<br />
the master’s calling context. This is <strong>of</strong>ten the task <strong>of</strong> the unknown procedure, for<br />
which the slave’s unknown command is an alias.<br />
In order to permit more context than just the res variable to be available, this<br />
unknown procedure makes an explicit uplevel for the recursion. To facilitate this,<br />
the fully qualified name <strong>of</strong> theinterp is cached in the alias definition.<br />
447 theinterp alias unknown [namespace current]::unknown\<br />
[namespace current]::theinterp<br />
449 }<br />
The call syntax <strong>of</strong> this handler procedure is therefore<br />
sfnt::expand::unknown {slave} {name} {argument} ∗<br />
where {slave} is the name <strong>of</strong> the slave interpreter command to call.<br />
The main task is to recurse over the bodies <strong>of</strong> XML-style commands, when<br />
present and nonempty.<br />
450 proc sfnt::expand::unknown {slave name args} {<br />
451 upvar 1 res res<br />
452 if {![<br />
453 regexp {^[[:alpha:]_:][[:alnum:]_:.-]*$} $name<br />
454 ] || [llength $args]%2 == 0} then {<br />
455 append res [linsert $args 0 $name] \n<br />
456 } else {<br />
457 set body [lindex $args end]<br />
22
sfnt-table command<br />
sfnt::expand:<br />
:sfnt-table (proc)<br />
458 append res [linsert [lreplace $args end end] 0 $name]<br />
459 if {[regexp {\S} $body]} then {<br />
460 append res " \{\n"<br />
461 uplevel 1 [list $slave eval $body]<br />
462 append res \}<br />
463 }<br />
464 append res \n<br />
465 }<br />
466 }<br />
Prehaps somewhat surprisingly, the expansion <strong>of</strong> a table is not handled by the<br />
actual sfnt-table command; the reason for this is that the prescribed parsing<br />
order for tables typically will not match the order they are given in. Therefore<br />
it is necessary to rather let the surrounding sfnt-font command control the<br />
parsing, and mostly restrict sfnt-table to storing away its attributes in the<br />
table (local array) calling context. To that end, the table array in that context should have as index<br />
the tag <strong>of</strong> a table and as value the dictionary <strong>of</strong> attributes <strong>of</strong> that table, including<br />
the tag. If adding an entry to this array, the sfnt-table command need not<br />
contribute anything to the res. Conversely, should the sfnt-table command<br />
already have a nonempty body, then going the table route would throw away<br />
information. Therefore the response in that case is to just append the sfnt-table<br />
element as given to res (not processing the body any further).<br />
table1L (local var.) In addition, the tag is appended to the table1L or table0L variable in the<br />
table0L (local var.) calling context, the former <strong>of</strong> which is the list <strong>of</strong> tables to expand and the latter <strong>of</strong><br />
which is the list <strong>of</strong> tables to not expand. The purpose <strong>of</strong> having these lists, rather<br />
than looking into the table array, is to preserve the given order <strong>of</strong> the tables to<br />
the extent that it is compatible with parsing order.<br />
467 proc sfnt::expand::sfnt-table {args} {<br />
468 upvar 1 table table res res table1L table1L table0L table0L O O<br />
469 if {[llength $args]%2} then {<br />
470 set body [lindex $args end]<br />
471 set args [lreplace $args [set args end] end]<br />
472 } else {<br />
473 set body ""<br />
474 }<br />
475 set tag [dict get $args tag]<br />
476 if {$body eq ""} then {<br />
477 set table($tag) $args<br />
478 } else {<br />
479 append res [list sfnt-table {*}$args $body] \n<br />
480 }<br />
The -which is an option <strong>of</strong> the main procedure, below.<br />
481 foreach pat $O(-which) {<br />
482 if {[string match $pat $tag]} then {<br />
483 lappend table1L $tag<br />
484 return<br />
485 }<br />
486 }<br />
23
487 lappend table0L $tag<br />
488 }<br />
489 sfnt::expand::theinterp alias sfnt-table\<br />
[namespace which sfnt::expand::sfnt-table]<br />
/datum command A downside <strong>of</strong> not parsing already-parsed sfnt-tables is that one won’t get their<br />
contributions to the gdict, so some other mechanism for providing that information<br />
/datum (element) must be provided. To that end, the /datum command may be used to embed gdict<br />
entries into the body <strong>of</strong> an sfnt-font. It has the syntax<br />
gdict (local var.)<br />
/datum {key} {value} ∗<br />
and associates each given {key} with the corresponding {value}. There may be<br />
several /datum commands, in which case their contributions are merged.<br />
What the concrete /datum command does is that it acts on the gdict variable<br />
in the master calling context, lappending the arguments (since that is likely to<br />
be faster than a dict replace when there are several small appends). Also, this<br />
avoids having to create a separate proc as alias target (though at the cost <strong>of</strong><br />
slightly weaker error-checking).<br />
491 sfnt::expand::theinterp alias /datum ::lappend gdict<br />
sfnt::expand::main (proc) Before going into the actual expansion <strong>of</strong> font tables, there is however the matter<br />
<strong>of</strong> where the data is going to come from. Continuing with the pattern <strong>of</strong> “calling<br />
thefile (local var.) context res” for returning results, one might declare that the thefile variable<br />
in that context should hold a seekable channel with table data at the positions<br />
specified by start attributes <strong>of</strong> sfnt-table elements.<br />
Primarily responsible for setting the whole thing up is the main procedure,<br />
which has the call syntax<br />
O (local array)<br />
sfnt-font command<br />
sfnt::expand:<br />
:sfnt-font (proc)<br />
sfnt::expand::main {thefile} {TDL} {option} {value} ∗<br />
and returns the expansion <strong>of</strong> the {TDL}. All options are dumped into the O array,<br />
where other commands may access them. Currently the only option implemented<br />
is:<br />
-which Takes a list <strong>of</strong> string match patterns as argument. Only those tables<br />
whose tags match one <strong>of</strong> these patterns will have been expanded in the<br />
result. The default value is ‘*’, i.e., “expand everything”.<br />
492 proc sfnt::expand::main {thefile TDL args} {<br />
493 set res ""<br />
494 array set O {-which *}<br />
495 array set O $args<br />
496 theinterp eval $TDL<br />
497 return $res<br />
498 }<br />
To recap, the sfnt-font command has the syntax<br />
sfnt-font {attribute} {value} ∗ {body} ?<br />
24
where, for this to be <strong>of</strong> any use, the {body} should be present and contain several<br />
sfnt-table elements (and possibly some /gdict elements). The {attribute}s are<br />
mostly ignored (since parsing doesn’t depend on them), but should be preserved.<br />
The first step is to process the {body}, thus collecting information about where<br />
in thefile unparsed tables can be found.<br />
499 proc sfnt::expand::sfnt-font {args} {<br />
500 upvar 1 res res thefile thefile O O<br />
501 if {[llength $args] % 2 == 0} then {<br />
502 append res [linsert $args 0 sfnt-font] \n<br />
503 return<br />
504 }<br />
505 append res [linsert [lrange $args 0 end-1] 0 sfnt-font] " \{\n"<br />
506 set table0L {}<br />
507 set table1L {}<br />
Some very old (and probably broken) fonts manufactured by the Type1Enabler [?]<br />
(they are PS type 1 fonts in an sfnt wrapper) lack a head table, but have kerns<br />
that seem consistent with the AFM if the funit is 1, so let’s throw that in as a<br />
default.<br />
508 set gdict [dict create funit 1.0]<br />
509 theinterp eval [lindex $args end]<br />
The next step is to parse the tables. The idea here is that a table is assumed to<br />
have been parsed if it does not have an entry in the table array, so it is safe to<br />
proceed with parsing a table if it doesn’t have a parse_after list, or no item on<br />
that list has an entry in the table array. This implies that table dependencies<br />
are “s<strong>of</strong>t”: if a table is missing from a font, then it never makes it into the table<br />
array and will therefore not block the parsing <strong>of</strong> anything that depends on it.<br />
Tables that don’t have a parser go into the noparseL list, the elements <strong>of</strong> which<br />
are attribute dictionaries. Another such list is stack, which handles the nesting<br />
that one table depends on another. By moving items from the table array to<br />
the stack, it is possible to prevent dependency loops from creating infinite loops<br />
here; a table counts as parsed as soon as the requirements for parsing it are being<br />
considered, even though processing it might not happen for quite some time yet.<br />
510 set noparseL {}<br />
511 foreach tag $table1L {<br />
512 if {![info exists table($tag)]} then {continue}<br />
513 set stack [list $table($tag)]<br />
514 unset table($tag)<br />
515 while {[llength $stack]} {<br />
516 set tag [dict get [lindex $stack end] tag]<br />
517 if {[info exists [namespace parent]::${tag}::parse_after]}\<br />
then {<br />
519 set ok 1<br />
520 foreach pretag\<br />
[set [namespace parent]::${tag}::parse_after] {<br />
522 if {[info exists table($pretag)]} then {<br />
523 set ok 0<br />
524 break<br />
25
525 }<br />
526 }<br />
527 if {!$ok} then {<br />
528 lappend stack $table($pretag)<br />
529 unset table($pretag)<br />
530 continue<br />
531 }<br />
532 }<br />
At this point, the tag <strong>of</strong> a table which we are ready to parse has been determined,<br />
but is there a parser for it?<br />
533 set cmd [namespace which [namespace parent]::${tag}::parse]<br />
534 if {$cmd eq ""} then {<br />
535 lappend noparseL [lindex $stack end]<br />
536 } else {<br />
At this point, a parser has been found, so run it, but be prepared to catch any<br />
errors that might arise when doing so.<br />
537 set D [lindex $stack end]<br />
538 append res [linsert $D 0 sfnt-table]<br />
539 if {[catch {<br />
540 seek $thefile [dict get $D start]<br />
541 $cmd [read $thefile [dict get $D length]] $gdict gdict<br />
542 } result]} then {<br />
543 append res " \{\n" "# Error parsing table:\n# "\<br />
[join [split $::errorInfo \n] "\n# "] \n\}<br />
545 } else {<br />
Having run the parser, it is now necessary to make another check whether to<br />
include the data in the result. It may well happen that a wanted table depended<br />
on one which wasn’t wanted.<br />
546 set ok 0<br />
547 foreach pat $O(-which) {<br />
548 if {[string match $pat $tag]} then {<br />
549 set ok 1; break<br />
550 }<br />
551 }<br />
552 if {$ok} then {<br />
553 append res " \{\n" $result \}<br />
554 }<br />
555 }<br />
556 append res \n<br />
557 }<br />
558 set stack [lreplace $stack [set stack end] end]<br />
559 }<br />
560 }<br />
The third step is to record the gdict entries. Entries whose names begin with #<br />
are not included, so there one can put data which is <strong>of</strong> a more internal nature.<br />
561 dict for {key value} $gdict {<br />
562 if {[string match #* $key]} then {continue}<br />
26
sfnt::parse_as_hexdump<br />
(proc)<br />
563 append res [list /datum $key $value] \n<br />
564 }<br />
The fourth step is to emit the unparsed tables from table0L. This is similar to<br />
the second step, but much simpler.<br />
565 foreach tag $table0L {<br />
566 if {![info exists table($tag)]} then {continue}<br />
567 append res [linsert $table($tag) 0 sfnt-table] \n<br />
568 }<br />
The final step is to emit the noparseL elements. A comment is placed in front <strong>of</strong><br />
these to explain their status.<br />
569 if {[llength $noparseL]} then {<br />
570 append res {# The following tables have no parsers:} \n<br />
571 foreach D $noparseL {<br />
572 append res [linsert $D 0 sfnt-table] \n<br />
573 }<br />
574 }<br />
575 append res \}\n<br />
576 }<br />
577 sfnt::expand::theinterp alias sfnt-font\<br />
[namespace which sfnt::expand::sfnt-font]<br />
4.1 Binary data parsing<br />
For tables without dedicated parsers, a way <strong>of</strong> at least showing the data can be<br />
to do a hexdump <strong>of</strong> it. To that end, there is an element<br />
/hexdump {<strong>of</strong>fset} {byte} + {string}<br />
which encodes the fact that the bytes at <strong>of</strong>fset {<strong>of</strong>fset} and forward are the {byte}s<br />
(as far as these suffice). The {string} is an informative decoding <strong>of</strong> these bytes as<br />
a string <strong>of</strong> text; that it is informative means one should not expect it to uniquely<br />
determine the {byte}s, and different /hexdata may have used different encodings<br />
for the {string}.<br />
It should be noted that the {<strong>of</strong>fset} is also in hexadecimal, without any ishexadecimal<br />
prefix. This means it can be padded with zeroes to have the same<br />
length as in neighbouring /hexdata lines.<br />
This procedure can be used as a table parser, and generates /hexdump elements.<br />
The call syntax is<br />
sfnt::parse_as_hexdump {options} {data} {gdict} {gdict-var} ?<br />
where the last two arguments are ignored. The {data} argument is the binary data<br />
to parse. The {options} argument is a dictionary which can be used to configure<br />
how the data should be parsed. The recognised entries are:<br />
-bytesperline The number <strong>of</strong> {byte}s to put in each /hexdump element. Defaults<br />
to 16.<br />
27
sfnt:<br />
:hexdump_char_from_byte<br />
(proc)<br />
-limit The maximal number <strong>of</strong> bytes to dump; since some tables can be very<br />
large, you don’t want to hex-encode all <strong>of</strong> them. Defaults to 512 (giving 32<br />
lines <strong>of</strong> 16 bytes each).<br />
579 proc sfnt::parse_as_hexdump {options data gdict {gdictvar ""}} {<br />
580 array set O {-limit 512 -bytesperline 16}<br />
581 array set O $options<br />
582 set last [expr {min([string length $data],$O(-limit))-1}]<br />
583 set fspec %0[string length [format %03x $last]]x<br />
584 set res ""<br />
585 set byteL {}<br />
586 set str {}<br />
587 for {set pos 0} {$pos= $O(-bytesperline)} then {<br />
596 append res { } [list $str] \n<br />
597 set byteL {}<br />
598 set str {}<br />
599 }<br />
600 }<br />
601 if {[llength $byteL]} then {append res { } [list $str] \n}<br />
602 if {[string length $data] > $pos} then {<br />
603 append res [list /comment [format {%d bytes elided.}\<br />
[expr {[string length $data] - $pos}]]] \n<br />
605 }<br />
606 return $res<br />
607 }<br />
This is a basic procedure which encodes a byte as a “string” character for a<br />
/hexdump. Currently it just replaces anything non-visible-ASCII by ‘.’ (period).<br />
608 proc sfnt::hexdump_char_from_byte {byte} {<br />
609 if {$byte126} then {<br />
610 return .<br />
611 } else {<br />
612 return [format %c $byte]<br />
613 }<br />
614 }<br />
As a demo, here’s how to hexdump ENCO and TYP1 tables:<br />
615 namespace eval sfnt::ENCO {<br />
616 interp alias {} [namespace current]::parse {}\<br />
[namespace parent]::parse_as_hexdump {}<br />
618 }<br />
619 namespace eval sfnt::TYP1 {<br />
28
620 interp alias {} [namespace current]::parse {}\<br />
[namespace parent]::parse_as_hexdump {}<br />
622 }<br />
5 head and OS/2 tables<br />
623 namespace eval sfnt::head {}<br />
sfnt::head::parse (proc) The call syntax <strong>of</strong> this procedure is<br />
<strong>Font</strong>Revision (element)<br />
/flag (element)<br />
sfnt::head::parse {data} {gdict} {gdict-var} ?<br />
The {gdict} is a dictionary <strong>of</strong> global font information, but values in it will be<br />
overridden by what is in the {data}. If a {gdict-var} is specified, then the variable<br />
in the calling context by that name will be set to the updated value <strong>of</strong> {gdict}.<br />
624 proc sfnt::head::parse {data gdict {gdictvar ""}} {<br />
625 binary scan $data H8H8IuH8B16SuWWS4B16SuSSS tableVersion\<br />
fontRevision checkSumAdjustment magicNumber flags unitsPerEm\<br />
created modified bbox macStyle lowestRecPPEM fontDirectionHint\<br />
indexToLocFormat glyphDataFormat<br />
629 set res ""<br />
630 if {$tableVersion ne "00010000"} then {<br />
631 append res "# Table version: 0x$tableVersion\n"<br />
632 if {![string match 0001* $tableVersion]} then {return $res}<br />
633 }<br />
<strong>Font</strong> revision numbers are encoded in elements, but a complication is that the<br />
underlying binary value is a Fixed, since the interpretation <strong>of</strong> this as a version<br />
number . . . varies. Therefore attempts are made at several different interpretations,<br />
and those that make sense are given as attributes.<br />
634 set L [list <strong>Font</strong>Revision hex $fontRevision]<br />
635 if {[regexp {^([0-9]{3})([0-9])([0-9])([0-9]{3})$} $fontRevision ""\<br />
a b c d]} then {<br />
637 lappend L bcd [string map {| ""} [string trim "$a|$b.$c|$d" 0]]<br />
638 }<br />
639 lappend L num\<br />
[format %.4f [expr {[lindex [scan $fontRevision %x] 0] / 65536.0}]]<br />
641 lappend L shortshort [format %d.%d {*}[scan $fontRevision %4x%4x]]<br />
642 append res $L \n<br />
The encoding <strong>of</strong> the various flag words is a tricky issue, since on one hand it’s<br />
no good to have many different element names, and on the other it’s no good to<br />
have complicated element syntaxes. The /flag element takes as its only argument<br />
the name <strong>of</strong> a flag, and its presence signifies that this flag is set to 1, whereas its<br />
absence would mean the flag is 0.<br />
643 foreach bit [split $flags ""] name {<br />
644 15 14 {Optimized for ClearType} {<strong>Font</strong> converted}<br />
645 {MicroType lossless} {Has Indic-style rearrangement}<br />
646 {Has strong right-to-left} {Has default metamorphosis}<br />
29
647 {requires layout for correct linguistic rendering}<br />
648 6 {Vertical baseline at x=0}<br />
649 {Instructions may alter advance width} {Force ppem to integer}<br />
650 {Instructions may depend on point size}<br />
651 {Left sidebearing point at x=0} {Baseline at y=0}<br />
652 } {<br />
653 if {$bit} then {append res [list /flag $name] \n}<br />
654 }<br />
designunits<br />
The unitsPerEm value is such a DESIGNUNITS, so its get encoding as setting the<br />
designunits integer. Technically, the designunits fontinst variable happens to<br />
(fontinst variable) be a dimen rather than an integer, but it’s only informational anyway.<br />
/when (element)<br />
/<strong>Font</strong>BBox (element)<br />
655 append res [list /setint designunits $unitsPerEm] \n<br />
656 dict set gdict funit [expr {1e3/$unitsPerEm}]<br />
The /when element has two arguments: (the name for) the event it is a time <strong>of</strong>,<br />
and the actual time in seconds since the Unix epoch.<br />
657 append res [list /when created [expr {$created-2082844800}]] \n<br />
658 append res [list /when modified [expr {$modified-2082844800}]] \n<br />
The /<strong>Font</strong>BBox element has four arguments: min-x (left), min-y (bottom), maxx<br />
(right), and max-y (top).<br />
659 set L [list /<strong>Font</strong>BBox]<br />
660 foreach c $bbox {<br />
661 lappend L [expr {$c*[dict get $gdict funit]}]<br />
662 }<br />
663 append res $L \n<br />
The Mac style is treated as another set <strong>of</strong> flags.<br />
664 foreach bit [split $macStyle ""] name {<br />
665 15 14 13 12 11 10 9 8 7<br />
666 extended condensed shadow outline underline italic bold<br />
667 } {<br />
668 if {$bit} then {append res [list /flag $name] \n}<br />
669 }<br />
lowestReadablePPEM The lowestReadablePPEM value also gets encoded as a variable value.<br />
(fontinst variable) 670 append res [list /dontsetint lowestRecPPEM $lowestRecPPEM] \n<br />
fontDirectionHint The fontDirectionHint value says something about how much LTR or RTL the<br />
(fontinst variable) font is, but the field is deprecated.<br />
671 append res [list /dontsetint fontDirectionHint $fontDirectionHint] \n<br />
The indexToLocFormat and glyphDataFormat are only <strong>of</strong> interest when parsing<br />
other tables, so they are included in the output only as comments. indexToLoc-<br />
Format is put in the gdict, however.<br />
672 dict set gdict indexToLocFormat $indexToLocFormat<br />
673 append res "\# indexToLocFormat is $indexToLocFormat.\n"<br />
674 append res "\# glyphDataFormat is $glyphDataFormat.\n"<br />
675 if {$gdictvar ne ""} then {<br />
676 uplevel 1 [list ::set $gdictvar $gdict]<br />
677 }<br />
30
when command<br />
TDLtoTeX::when (proc)<br />
sfnt::OS/2:<br />
:parse_after (var.)<br />
678 return $res<br />
679 }<br />
The /when command can be TEXified as \comments.<br />
680 namespace eval TDLtoTeX {<br />
681 proc when {name time} {<br />
682 uplevel 1 {::append res $O(-indent)} [list [<br />
683 format {\comment{%s %s.}} [string totitle $name]\<br />
[clock format $time -gmt 1]<br />
685 ] \n]<br />
686 }<br />
687 theinterp alias /when [namespace which when]<br />
688 }<br />
Parsing OS/2 tables requires knowing the funit, so it must be done after head.<br />
689 namespace eval sfnt::OS/2 {<br />
690 variable parse_after head<br />
691 }<br />
sfnt::OS/2::parse (proc) The call syntax <strong>of</strong> this procedure is<br />
linegap (fontinst integer)<br />
sfnt::OS/2::parse {data} {gdict} {gdict-var} ?<br />
The {gdict} is a dictionary <strong>of</strong> global font information, but values in it will be<br />
overridden by what is in the {data}. If a {gdict-var} is specified, then the variable<br />
in the calling context by that name will be set to the updated value <strong>of</strong> {gdict}.<br />
692 proc sfnt::OS/2::parse {data gdict {gdictvar ""}} {<br />
693 set numEntries [binary scan $data\<br />
SuSSuSuB16S4S4S2c2cu10B128a4B16SuSuSSSSuSuB64SSSuSuSu Version\<br />
xAvgCharWidth usWeightClass usWidthClass fsType subscriptSizePos\<br />
superscriptSizePos strikeoutSizePos sFamilyClass Panose\<br />
UnicodeCoverage achVendID fsSelection usFirstChar<strong>Index</strong>\<br />
usLastChar<strong>Index</strong> sTypoAscender sTypoDescender sTypoLineGap\<br />
usWinAscent usWinDescent codePageRange sxHeight sCapHeight\<br />
usDefaultChar usBreakChar usMaxContext]<br />
701 set res "# Version: $Version\n"<br />
702 set funit [dict get $gdict funit]<br />
The linegap value is supposed to produce something like the baselineskip when<br />
added to the ascender and descender, so it would probably be larger than the<br />
\lineskiplimit.<br />
The difference between sub1 and sub2 is that the latter comes into play only<br />
when there is a superscript, so sub1 seems a likelier interpretation <strong>of</strong> what a “word<br />
processor” would supply. As for the three sup〈n〉 parameters, TEX uses all <strong>of</strong> them<br />
in the same way, but chooses one depending on the current math style. sup2 is<br />
what would be used for normal \textstyle formulae.<br />
703 set ySubscriptYOffset [lindex $subscriptSizePos 3]<br />
704 set ySuperscriptYOffset [lindex $superscriptSizePos 3]<br />
705 foreach {var int} {<br />
31
scriptsizepos (element)<br />
/Panose (element)<br />
descriptor (element)<br />
706 xAvgCharWidth averagewidth<br />
707 sTypoAscender ascender<br />
708 sTypoDescender descender_neg<br />
709 sTypoLineGap linegap<br />
710 usWinAscent maxheight<br />
711 usWinDescent maxdepth<br />
712 sxHeight xheight<br />
713 sCapHeight capheight<br />
714 ySubscriptYOffset sub1<br />
715 ySuperscriptYOffset sup2<br />
716 } {<br />
717 if {[info exists $var]} then {<br />
718 append res [list /setint $int [expr {$funit*[set $var]}]] \n<br />
719 }<br />
720 }<br />
The /scriptsizepos element has the syntax<br />
/scriptsizepos {type} {x-scale} {y-scale} {x-<strong>of</strong>s} {y-<strong>of</strong>s}<br />
where {type} is one <strong>of</strong> super and sub. The {x-scale} and {y-scale} are interpreted<br />
as by \xscalefont and \yscalefont respectively. The {y-<strong>of</strong>s} is negative for<br />
subscripts.<br />
721 lset subscriptSizePos 3 [expr {-[lindex $subscriptSizePos 3]}]<br />
722 foreach L [list $subscriptSizePos $superscriptSizePos] type\<br />
{sub super} {<br />
724 set L2 [list /scriptsizepos $type]<br />
725 foreach val $L {lappend L2 [expr {$funit*$val}]}<br />
726 append res $L2 \n<br />
727 }<br />
The /Panose element has the syntax<br />
/Panose {family type} {serif style} {weight} {proportion} {contrast}<br />
{stroke variation} {arm style} {letterform} {midline} {xheight}<br />
The arguments are the raw numbers, not their interpretations.<br />
728 append res [list /Panose {*}$Panose] \n<br />
729 if {$gdictvar ne ""} then {<br />
730 uplevel 1 [list ::set $gdictvar $gdict]<br />
731 }<br />
732 return $res<br />
733 }<br />
5.1 The fdsc table<br />
Slightly duplicating the /Panose information is the Apple-defined [?] fdsc (font<br />
descriptors) table. This is basically a dictionary, but since the tags could in<br />
principle contain arbitrary characters, it seems unwise to use them as attribute<br />
names. Hence the table is encoded as a sequence <strong>of</strong> element, which carry tag and<br />
value attributes.<br />
32
sfnt::fdsc::parse (proc) The call syntax <strong>of</strong> this procedure is<br />
FNAM (table)<br />
sfnt::fdsc::parse {data} {gdict} {gdict-var} ?<br />
The {gdict} and {gdict-var} arguments are currently ignored.<br />
734 namespace eval sfnt::fdsc {<br />
735 proc parse {data gdict {gdictvar ""}} {<br />
736 binary scan $data H8Iu tableVersion count<br />
737 set res ""<br />
738 if {$tableVersion ne "00010000"} then {<br />
739 append res "# Table version: 0x$tableVersion\n"<br />
740 if {![string match 0001* $tableVersion]} then {return $res}<br />
741 }<br />
742 set pos 8<br />
743 for {} {$count>=1} {incr count -1} {<br />
744 binary scan $data @${pos}a4I tag value<br />
745 incr pos 8<br />
746 switch -- $tag "nalf" {<br />
747 set name [lindex {Alphabetic Dingbats {Pi characters}\<br />
Fleurons {Decorative borders} {International symbols}\<br />
{Math symbols}} $value]<br />
750 if {$name eq ""} then {set name $value}<br />
751 append res [list descriptor tag $tag value $name] \n<br />
752 } default {<br />
753 append res\<br />
[list descriptor tag $tag value [expr {$value/65536.0}]]\<br />
\n<br />
755 }<br />
756 }<br />
757 return $res<br />
758 }<br />
759 }<br />
5.2 The FNAM table<br />
Before <strong>OpenType</strong>, there was an older (but rather obscure) format <strong>of</strong> wrapping<br />
PS fonts in sfnt containers, and these fonts had a set <strong>of</strong> tables which deviate<br />
considerably from what the TrueType and <strong>OpenType</strong> standards prescribe. The<br />
FNAM table purpose-wise overlap a bit with the OS/2 table, in that it specifies font<br />
style.<br />
sfnt::FNAM::parse (proc) The call syntax <strong>of</strong> this procedure is<br />
sfnt::FNAM::parse {data} {gdict} {gdict-var} ?<br />
The {gdict} and {gdict-var} arguments are currently ignored.<br />
760 namespace eval sfnt::FNAM {}<br />
761 proc sfnt::FNAM::parse {data gdict {gdictvar ""}} {<br />
762 binary scan $data H8Su tableVersion encSets<br />
33
FOND-association (tag)<br />
763 set res ""<br />
764 if {$tableVersion ne "00010000"} then {<br />
765 append res "# Table version: 0x$tableVersion\n"<br />
766 if {![string match 0001* $tableVersion]} then {return $res}<br />
767 }<br />
The following data is conceptually an array <strong>of</strong> <strong>of</strong>fsets <strong>of</strong> the starts <strong>of</strong> selector<br />
subtables, with an extra <strong>of</strong>fset afterwards marking the end to the table, but since<br />
each new <strong>of</strong>fset also marks the end <strong>of</strong> the previous subtable it is convenient to<br />
rather parse it as a position <strong>of</strong> the first subtable and a list <strong>of</strong> ends <strong>of</strong> subtables.<br />
768 binary scan $data @6SuSu${encSets} pos <strong>of</strong>fsets<br />
769 set sel 0<br />
770 foreach end $<strong>of</strong>fsets {<br />
771 while {$pos < $end} {<br />
The FOND-association tag effectively describes an entry this font would have<br />
in the user interface list <strong>of</strong> fonts. The name attribute gives the font (family)<br />
name, whereas the style attribute is a list <strong>of</strong> QuickDraw styles applied to it.<br />
The selector attribute specifies (in reference to the cmap or ENCO table) the font<br />
subset that this UI font would expose. [?, Sec. 9]<br />
772 set L [list FOND-association]<br />
773 binary scan $data @${pos}cucu style len<br />
774 incr pos 2<br />
775 binary scan $data @${pos}a${len} name<br />
776 incr pos $len<br />
777 lappend L name [encoding convertfrom macRoman $name]<br />
778 set L2 {}<br />
779 foreach flag {<br />
780 bold italic underline outline shadow condensed extended<br />
781 } {<br />
782 if {$style & 1} then {lappend L2 $flag}<br />
783 set style [expr {$style >> 1}]<br />
784 }<br />
785 lappend L style $L2 selector $sel<br />
786 append res $L \n<br />
787 }<br />
788 incr sel<br />
789 set pos $end<br />
790 }<br />
791 return $res<br />
792 }<br />
6 hmtx, hhea, and maxp tables<br />
An obviously interesting table is hmtx, since this is where the glyph advance widths<br />
are to be found. Unfortunately, the format <strong>of</strong> this table is rather bizarre; it is<br />
concerned more with specifying left sidebearing points than with widths (you can<br />
default widths, but need to give a sidebearing for each glyph). Also, the table<br />
34
consists <strong>of</strong> two arrays, the sizes <strong>of</strong> which are not stored in this table, but in the<br />
hhea and maxp tables instead.<br />
sfnt::maxp::parse (proc) The only generally useful information in the maxp table is numGlyphs, which is<br />
exported to the gdict. Then there are a bunch <strong>of</strong> fields which essentially only<br />
say how much resources <strong>of</strong> various types that the TrueType renderer would need;<br />
these are just dumped as comments.<br />
793 namespace eval sfnt::maxp {<br />
794 proc parse {data gdict {gdictvar ""}} {<br />
795 binary scan $data H8Su version numGlyphs<br />
796 set res "# ’maxp’ version 0x$version\n"<br />
797 if {$version in {00005000 00010000}} then {<br />
798 dict set gdict numGlyphs $numGlyphs<br />
799 append res [list /numGlyphs $numGlyphs] \n<br />
800 }<br />
801 if {$version eq "00010000"} then {<br />
802 binary scan $data @6Su* L<br />
803 foreach val $L name {<br />
804 maxPoints maxContours maxCompositePoints<br />
805 maxCompositeContours maxZones maxTwilightPoints maxStorage<br />
806 maxFunctionDefs maxInstructionDefs maxStackElements<br />
807 maxSizeOfInstructions maxComponentElements<br />
808 maxComponentDepth<br />
809 } {<br />
810 append res "# $name $val\n"<br />
811 }<br />
812 }<br />
813 if {$gdictvar ne ""} then {<br />
814 uplevel 1 [list ::set $gdictvar $gdict]<br />
815 }<br />
816 return $res<br />
817 }<br />
818 }<br />
sfnt::hhea::parse (proc) The hhea table contains some typographically interesting entries, but those are<br />
duplicated in the OS/2 table, so again this is mostly needed to parse the hmtx<br />
table. Still, there’s little harm in exporting what can be found.<br />
819 namespace eval sfnt::hhea {<br />
820 variable parse_after head<br />
821 proc parse {data gdict {gdictvar ""}} {<br />
822 binary scan $data H8SSSSuSSSSSSx8SSu version Ascender Descender\<br />
LineGap advanceWidthMax minLeftSideBearing minRightSideBearing\<br />
xMaxExtent caretSlopeRise caretSlopeRun caretOffset\<br />
metricDataFormat numberOfHMetrics<br />
827 set res "# ’hhea’ version 0x$version\n"<br />
828 if {$version ne "00010000"} then {return $res}<br />
829 set funit [dict get $gdict funit]<br />
The “ascender” and “descender” values here are probably closer to being maximal<br />
height and depth than height and depth <strong>of</strong> actual ascenders. There is no fontinst<br />
35
sfnt::hmtx::parse (proc)<br />
/glyphwidth (element)<br />
name for “line gap” (though TEX’s \lineskip is pretty close), but the line gap<br />
value suggests a baselineskip value.<br />
830 append res [list /setint maxheight [expr {$Ascender*$funit}]] \n<br />
831 append res [list /setint maxdepth_neg [expr {$Descender*$funit}]]\<br />
\n<br />
832 append res [list /setint baselineskip\<br />
[expr {($Ascender-$Descender+$LineGap)*$funit}]] \n<br />
834 foreach var {advanceWidthMax minLeftSideBearing\<br />
minRightSideBearing xMaxExtent} {<br />
836 append res [list /dontsetint $var [expr {$funit*[set $var]}]]\<br />
\n<br />
838 }<br />
The caret slope information isn’t likely to be used with TEX either, but can do<br />
with being collected under a custom heading. Hence<br />
/caretSlope {rise} {run} {<strong>of</strong>fset}<br />
specifies the slant (like italicslant) as run/rise and {<strong>of</strong>fset} as a horizontal<br />
<strong>of</strong>fset to add to the position <strong>of</strong> the caret. (Adobe technote #5180 contains an<br />
illustration <strong>of</strong> these parameters, which suggests the <strong>of</strong>fset should actually be subtracted<br />
from the x-position.)<br />
839 append res [list /caretSlope $caretSlopeRise $caretSlopeRun\<br />
[expr {$funit*$caretOffset}]] \n<br />
The metricDataFormat and numberOfHMetrics entries are logically headers for<br />
the hmtx table, so they are simply stored into the gdict.<br />
841 dict set gdict metricDataFormat $metricDataFormat<br />
842 dict set gdict numberOfHMetrics $numberOfHMetrics<br />
843 if {$gdictvar ne ""} then {<br />
844 uplevel 1 [list ::set $gdictvar $gdict]<br />
845 }<br />
846 return $res<br />
847 }<br />
848 }<br />
849 namespace eval sfnt::hmtx {<br />
850 variable parse_after {head maxp hhea}<br />
851 proc parse {data gdict {var ""}} {<br />
852 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
853 binary scan $data [<br />
854 format S%dS%d [expr {2*[dict get $gdict numberOfHMetrics]}]\<br />
[expr {[dict get $gdict numGlyphs] -\<br />
[dict get $gdict numberOfHMetrics]}]<br />
857 ] wsL sL<br />
Glyph width data is encoded using /glyphwidth elements, which have the syntax<br />
/glyphwidth {GID} {width} {lsb} ?<br />
36
sfnt::HFMX:<br />
:parse_after (var.)<br />
where the optional {lsb} is the left sidebearing and defaults to 0 (since this is the<br />
only possible value for a CFF font).<br />
858 set res ""<br />
859 set funit [dict get $gdict funit]<br />
860 set n -1; foreach {w lsb} $wsL {incr n<br />
861 set L [list /glyphwidth $n [expr {$w*$funit}]]<br />
862 if {$lsb != 0} then {lappend L [expr {$lsb*$funit}]}<br />
863 append res $L \n<br />
864 }<br />
865 foreach lsb $sL {incr n<br />
866 set L [list /glyphwidth $n [expr {$w*$funit}]]<br />
867 if {$lsb != 0} then {lappend L [expr {$lsb*$funit}]}<br />
868 append res $L \n<br />
869 }<br />
870 return $res<br />
871 }<br />
872 }<br />
6.1 The HFMX table<br />
Another variant table that might be found in pre-<strong>OpenType</strong> sfnt-wrapped PS<br />
fonts is HFMX, which overlaps with the hhea table.<br />
Parsing HFMX tables requires knowing the funit, so it must be done after head.<br />
873 namespace eval sfnt::HFMX {<br />
874 variable parse_after head<br />
875 }<br />
sfnt::HFMX::parse (proc) The call syntax <strong>of</strong> this procedure is<br />
sfnt::HFMX::parse {data} {gdict} {gdict-var} ?<br />
The {gdict} is a dictionary <strong>of</strong> global font information, but values in it will be<br />
overridden by what is in the {data}. A {gdict-var} will be ignored.<br />
876 proc sfnt::HFMX::parse {data gdict {gdictvar ""}} {<br />
877 binary scan $data H8SSSSSS tableVersion ascent descent lineGap\<br />
caretSlopeRise caretSlopeRun caretOffset<br />
879 set res ""<br />
880 if {$tableVersion ne "00010000"} then {<br />
881 append res "# Table version: 0x$tableVersion\n"<br />
882 if {![string match 0001* $tableVersion]} then {return $res}<br />
883 }<br />
884 set funit [dict get $gdict funit]<br />
885 append res [list /setint maxheight [expr {$ascent*$funit}]] \n<br />
886 append res [list /setint maxdepth_neg [expr {$descent*$funit}]] \n<br />
887 append res [list /setint baselineskip\<br />
[expr {($ascent-$descent+$lineGap)*$funit}]] \n<br />
889 append res [list /caretSlope $caretSlopeRise $caretSlopeRun\<br />
[expr {$funit*$caretOffset}]] \n<br />
37
sfnt::post:<br />
:base_258_glyphs (var.)<br />
891 return $res<br />
892 }<br />
7 post tables<br />
The post table is one source <strong>of</strong> glyph name information, and as such interesting<br />
for fontinst use. The TDL command for associating a glyph ID with a name is<br />
simply<br />
/glyphname {GID} {name}<br />
Most other data in the post table is encoded as /setint elements.<br />
Versions 1.0 and 2.0 <strong>of</strong> the post table start out with a list <strong>of</strong> 258 standard glyph<br />
names, which do not get encoded into the font. Hence a copy <strong>of</strong> this list must be<br />
part <strong>of</strong> this program instead.<br />
893 namespace eval sfnt::post {<br />
894 variable base_258_glyphs {<br />
895 .notdef .null nonmarkingreturn space exclam quotedbl numbersign<br />
896 dollar percent ampersand quotesingle parenleft parenright<br />
897 asterisk plus comma hyphen period slash zero one two three four<br />
898 five six seven eight nine colon semicolon less equal greater<br />
899 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<br />
900 bracketleft backslash bracketright asciicircum underscore grave<br />
901 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<br />
902 bar braceright asciitilde Adieresis Aring Ccedilla Eacute Ntilde<br />
903 Odieresis Udieresis aacute agrave acircumflex adieresis atilde<br />
904 aring ccedilla eacute egrave ecircumflex edieresis iacute igrave<br />
905 icircumflex idieresis ntilde oacute ograve ocircumflex odieresis<br />
906 otilde uacute ugrave ucircumflex udieresis dagger degree cent<br />
907 sterling section bullet paragraph germandbls registered<br />
908 copyright trademark acute dieresis notequal AE Oslash infinity<br />
909 plusminus lessequal greaterequal yen mu partialdiff summation<br />
910 product pi integral ordfeminine ordmasculine Omega ae oslash<br />
911 questiondown exclamdown logicalnot radical florin approxequal<br />
912 Delta guillemotleft guillemotright ellipsis nonbreakingspace<br />
913 Agrave Atilde Otilde OE oe endash emdash quotedblleft<br />
914 quotedblright quoteleft quoteright divide lozenge ydieresis<br />
915 Ydieresis fraction currency guilsinglleft guilsinglright fi fl<br />
916 daggerdbl periodcentered quotesinglbase quotedblbase perthousand<br />
917 Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute<br />
918 Icircumflex Idieresis Igrave Oacute Ocircumflex apple Ograve<br />
919 Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve<br />
920 dotaccent ring cedilla hungarumlaut ogonek caron Lslash lslash<br />
921 Scaron scaron Zcaron zcaron brokenbar Eth eth Yacute yacute<br />
922 Thorn thorn minus multiply onesuperior twosuperior threesuperior<br />
923 onehalf onequarter threequarters franc Gbreve gbreve Idotaccent<br />
924 Scedilla scedilla Cacute cacute Ccaron ccaron dcroat<br />
925 }<br />
38
926 variable parse_after head<br />
927 }<br />
sfnt::post::parse (proc) Being a parse procedure, this returns the TDL counterpart <strong>of</strong> a post table. Its<br />
call syntax is<br />
sfnt::post::parse {binary data} {global dict}<br />
and the interesting piece <strong>of</strong> information in the {global dict} is the unitsPerEm<br />
value, which is needed to interpret FWord values.<br />
928 proc sfnt::post::parse {data gdict {gdictvar ""}} {<br />
929 binary scan $data H8ISSIuIu4 version italicAngle underlinePosition\<br />
underlineThickness isFixedPitch minMaxMem<br />
932 set funit [dict get $gdict funit]<br />
933 set res "# ’post’ version 0x$version\n"<br />
934 append res [list /setint italicslant\<br />
[expr {-1000*tan($italicAngle*2.663161090079238e-7)}]] \n<br />
2.663161090079238 · 10−7 ≈ π/180/216 .<br />
underlinetop<br />
The underlinetop quantity appears to be new to fontinst. It is defined to be<br />
(fontinst variable) the y-position <strong>of</strong> the top edge <strong>of</strong> the underlining stroke (i.e., the same as in the<br />
post table, which is different from what would be in an AFM file)<br />
/minMaxPSMem (element)<br />
936 append res\<br />
[list /setint underlinetop [expr {$underlinePosition*$funit}]] \n<br />
938 append res [list /setint underlinethickness\<br />
[expr {$underlineThickness*$funit}]] \n<br />
940 if {$isFixedPitch} then {<br />
941 append res [list /setint monowidth $isFixedPitch] \n<br />
942 }<br />
943 if {$minMaxMem ne "0 0 0 0"} then {<br />
944 append res [linsert $minMaxMem 0 /minMaxPSMem] \n<br />
945 }<br />
The /minMaxPSMem element encodes information about how much memory the<br />
font requires when downloaded into a PS interpreter; it is probably not so interesting<br />
these days, with growing RAM sizes also in printers. The Type 42 values<br />
correspond to memory requirements when downloading what is actually in the<br />
sfnt font, whereas the Type 1 values correspond to memory requirements when<br />
downloading an equivalent Type 1 font (which would usually be taken from a<br />
separate external file rather than autoconverted from the TrueType, I think).<br />
/minMaxPSMem {Type 42 minimum} {Type 42 maximum} {Type 1<br />
minimum} {Type 1 maximum}<br />
After these preliminaries comes the more interesting collection <strong>of</strong> glyph names.<br />
Version 1.0 is just a fixed list <strong>of</strong> 258 glyphs.<br />
946 variable base_258_glyphs<br />
947 switch -- $version 00010000 {<br />
948 set n -1; foreach name $base_258_glyphs {incr n<br />
949 append res [list /glyphname $n $name] \n<br />
39
glyphccode (element)<br />
950 }<br />
951 } 00020000 {<br />
Version 2.0 is the main form <strong>of</strong> the table. The code below reads a name string in<br />
the same step as the length <strong>of</strong> the next string, because it is easy to have binary do<br />
that. A consequence is that the last binary probably won’t update len because<br />
the byte where this value is expected is after the end <strong>of</strong> data, but then this len<br />
value wouldn’t be used either.<br />
952 binary scan $data @32Su numberOfGlyphs<br />
953 binary scan $data @34S$numberOfGlyphs indexL<br />
954 set pos [expr {34+2*$numberOfGlyphs}]<br />
955 binary scan $data @${pos}cu len<br />
956 incr pos<br />
957 set nameL $base_258_glyphs<br />
958 set n -1; foreach index $indexL {incr n<br />
959 while {$index >= [llength $nameL]} {<br />
960 set npos [expr {$pos+$len+1}]<br />
961 binary scan $data @${pos}a${len}cu name len<br />
962 set pos $npos<br />
963 lappend nameL $name<br />
964 }<br />
965 append res [list /glyphname $n [lindex $nameL $index]] \n<br />
966 }<br />
967 } 00025000 {<br />
Version 2.5 has been deprecated, but is in principle a possibility. I haven’t been<br />
able to test it, though.<br />
968 append res {# WARNING: Parsing <strong>of</strong> table version 2.5 is untested.}\<br />
\n<br />
969 binary scan $data @32Su numberOfGlyphs<br />
970 binary scan $data @34c${numberOfGlyphs} <strong>of</strong>fsetL<br />
971 set n -1; foreach <strong>of</strong>s $<strong>of</strong>fsetL {incr n<br />
972 append res [list /glyphname $n\<br />
[lindex $base_258_glyphs [expr {($n+$<strong>of</strong>s)%258}]]] \n<br />
974 }<br />
975 } 00040000 {<br />
post table version 3.0 says nothing about the glyph names. For a version 4.0<br />
table, the glyph names should be <strong>of</strong> the form a〈hex〉, but these are kind <strong>of</strong> fake;<br />
rather than be looked up in a table, they are supposed to be parsed to extract<br />
the four 〈hex〉 digits. Hence they are not encoded as /glyphname commands, but<br />
rather as /glyphccode elements, which have the syntax<br />
/glyphccode {GID} {code}<br />
where the {code} is four hex digits. ToDo: Research how these {code}s are used on<br />
the PS side. A plausible guess is that they are looked up as 16-bit character codes<br />
(in some two-byte encoding CMap). I’ve seen comments that they are related to<br />
<strong>OpenType</strong> cmaps, but fail to specify exactly which one if there are several.<br />
976 binary scan $data @32Su numberOfGlyphs<br />
40
nameid (element)<br />
code (attribute)<br />
description (attribute)<br />
sfnt::name:<br />
:description (var.)<br />
977 binary scan $data @34Su${numberOfGlyphs} codeL<br />
978 set n -1; foreach code $codeL {incr n<br />
979 append res [list /glyphccode $n [format %04X $code]] \n<br />
980 }<br />
981 }<br />
982 return $res<br />
983 }<br />
8 name table<br />
The name table is primarily a list <strong>of</strong> strings, the interpretations <strong>of</strong> which are<br />
determined by their “name id”, but it additionally has the feature that a string<br />
may have variant forms depending on platform, encoding, and language. Therefore<br />
there is a container element nameid which collects all strings with a particular id,<br />
and various item elements which hold the particular string data. The nameid<br />
element have a required attribute code which holds the numeric string id, and an<br />
optional attribute description which gives a textual interpretation <strong>of</strong> the code<br />
(there doesn’t seem to be any symbolic names defined for the strings).<br />
The description variable is a list <strong>of</strong> descriptions for the standard name strings.<br />
Empty string means no description.<br />
984 namespace eval sfnt::name {<br />
985 variable description {<br />
986 {Copyright notice}<br />
987 {<strong>Font</strong> Family name}<br />
988 {<strong>Font</strong> Subfamily name}<br />
989 {Unique font identifier}<br />
990 {Full font name}<br />
991 {Version string}<br />
992 {Postscript name}<br />
993 Trademark<br />
994 Manufacturer<br />
995 Designer<br />
996 Description<br />
997 {Vendor URL}<br />
998 {Designer URL}<br />
999 {License Description}<br />
1000 {License Info URL}<br />
1001 {}<br />
1002 {Preferred Family}<br />
1003 {Preferred Subfamily}<br />
1004 {Compatible Full name}<br />
1005 {Sample text}<br />
1006 {PostScript CID findfont name}<br />
1007 {WWS family name}<br />
1008 {WWS subfamily name}<br />
1009 }<br />
1010 }<br />
41
namestr (element)<br />
sfnt::name:<br />
:PELdict (proc)<br />
sfnt::name::tclenc<br />
(array)<br />
The normal item element is /namestr, which has the syntax<br />
/namestr {string} {platform-encoding-language} +<br />
where {string} is the actual string being encoded. The {platform-encoding-language}s<br />
are dictionaries specifying these properties <strong>of</strong> the underlying name record; multiple<br />
name records that produce the same {string} will be combined. Keys that<br />
may occur in a platform-encoding-language dictionary are<br />
-platform May take a numeric value or one <strong>of</strong> the strings Unicode, Macintosh,<br />
ISO, Windows, and Custom.<br />
-enc The numeric encoding id.<br />
-lang The numeric language id.<br />
-language A textual language tag or name; the decoded equivalent <strong>of</strong> the -lang.<br />
This procedure constructs a platform-encoding-language dictionary for given ids.<br />
The call syntax is<br />
PELdict {platform} {encoding} {language} {language-tag-list}<br />
where the fourth argument is the list <strong>of</strong> strings that the language tag records<br />
encode.<br />
1011 proc sfnt::name::PELdict {plat enc lang ltL} {<br />
1012 if {$plat=0x8000} then {<br />
1022 incr lang -0x8000<br />
1023 if {[lindex $ltL $lang] ne ""} then {<br />
1024 dict set res -language [lindex $ltL $lang]<br />
1025 }<br />
1026 }<br />
1027 return $res<br />
1028 }<br />
1029 namespace eval sfnt::name {<br />
1030 namespace export PELdict<br />
1031 }<br />
The tclenc array maps combinations <strong>of</strong> numeric platform and encoding ids to Tcl<br />
encoding names. Indices that are lists <strong>of</strong> length two have the form<br />
{platform} {encoding}<br />
42
namebytearray (element)<br />
sfnt::name:<br />
:encoding_convertfrom<br />
(proc)<br />
and should be checked first. Indices that are lists <strong>of</strong> length one are just the platform<br />
id and should be used for all encodings on that platform which do not have a more<br />
specific match.<br />
1032 array set [namespace current]::sfnt::name::tclenc {<br />
1033 0 UTF-16BE<br />
1034 {1 0} macRoman<br />
1035 {1 1} macJapan<br />
1036 {1 6} macGreek<br />
1037 {1 7} macCyrillic<br />
1038 {1 21} macThai<br />
1039 {1 29} macCentEuro<br />
1040 {2 0} ascii<br />
1041 {2 1} UTF-16BE<br />
1042 {2 2} iso8859-1<br />
1043 {3 1} UTF-16BE<br />
1044 {3 2} shiftjis<br />
1045 {3 4} big5<br />
1046 {3 10} UTF-16BE<br />
1047 }<br />
Warning: Many <strong>of</strong> these entires are little more than guesses; the specification is<br />
not very clear, and I haven’t had any examples to test the unusual combinations<br />
on. It is however quite possible that the unclarity <strong>of</strong> the specification reflects an<br />
uncertainty among its authors about what was being specified.<br />
For items where the encoding is unknown, the string cannot be decoded, so<br />
these will instead have to be represented by the /namebytearray element, which<br />
has the syntax<br />
/namebytearray {binary data} {platform-encoding-language} +<br />
This is a replacement for encoding convertfrom to handle the platform dependence<br />
<strong>of</strong> the built-in unicode encoding. The call syntax is<br />
encoding_convertfrom {encoding} {bytearray}<br />
and this defaults to calling encoding convertfrom, unless {encoding} is UTF-16BE,<br />
in which case converting from unicode may require byte-swapping.<br />
1048 namespace eval sfnt::name {<br />
1049 proc encoding_convertfrom {enc str} {<br />
1050 if {$enc eq "UTF-16BE"} then {<br />
1051 set enc unicode<br />
1052 if {$::tcl_platform(byteOrder) eq "littleEndian"} then {<br />
1053 binary scan $str Su* words<br />
1054 set str [binary format su* $words]<br />
1055 }<br />
1056 }<br />
1057 encoding convertfrom $enc $str<br />
1058 }<br />
1059 namespace export encoding_convertfrom<br />
1060 }<br />
43
sfnt::name::parse PSBaseName (proc) The parse procedure may augment the gdict with two items, namely PSBaseName<br />
PSType0Name (string 6) and PSType0Name (string 20). These should only occur in one form, but<br />
if there are several then it is random which will be picked.<br />
1061 proc sfnt::name::parse {data gdict {gdictvar ""}} {<br />
1062 variable description<br />
1063 variable tclenc<br />
1064 set res ""<br />
1065 set D [rawnestdict $data]<br />
1066 binary scan $data SuSuSu format ncount base<br />
1067 set lTagL {}<br />
1068 if {$format==1} then {<br />
1069 set pos [expr {6+12*$ncount}]<br />
1070 binary scan $data @${pos}Su lcount<br />
1071 incr pos 2<br />
1072 binary scan $data @${pos}Su[expr {2*$lcount}] lRecL<br />
1073 foreach {length <strong>of</strong>fset} $lRecL {<br />
1074 binary scan $data @${base}x${<strong>of</strong>fset}a${length} tag<br />
1075 lappend lTagL [encoding_convertfrom UTF-16BE $tag]<br />
1076 }<br />
1077 }<br />
1078 foreach nameid [lsort -integer [dict keys $D]] {<br />
1079 append res [list nameid code $nameid]<br />
1080 if {[lindex $description $nameid] ne ""} then {<br />
1081 append res " " [list description [lindex $description $nameid]]<br />
1082 }<br />
1083 append res " \{\n"<br />
1084 set strD [dict create]<br />
1085 set rawD {}<br />
1086 dict for {plat D2} [dict get $D $nameid] {<br />
1087 dict for {enc D3} $D2 {<br />
1088 set enckey [list $plat $enc]<br />
1089 if {![info exists tclenc($enckey)]} then\<br />
{set enckey [list $plat]}<br />
1091 dict for {lang raw} $D3 {<br />
1092 set pel [PELdict $plat $enc $lang $lTagL]<br />
1093 if {[info exists tclenc($enckey)]} then {<br />
1094 dict lappend strD\<br />
[encoding_convertfrom $tclenc($enckey) $raw] $pel<br />
1096 } else {<br />
1097 dict lappend rawD $raw $pel<br />
1098 }<br />
1099 }<br />
1100 }<br />
1101 }<br />
1102 dict for {str L} $strD {<br />
1103 append res [linsert $L 0 /namestr $str] \n<br />
1104 if {$nameid == 6} then {<br />
1105 dict set gdict PSBaseName $str<br />
1106 } elseif {$nameid == 20} then {<br />
44
sfnt::name:<br />
:rawnestdict (proc)<br />
/charmap (element)<br />
1107 dict set gdict PSType0Name $str<br />
1108 }<br />
1109 }<br />
1110 dict for {raw L} $rawD {<br />
1111 append res [linsert $L 0 /namebytearray $raw] \n<br />
1112 }<br />
1113 append res "\}\n"<br />
1114 }<br />
1115 if {$gdictvar ne ""} then {<br />
1116 uplevel 1 [list ::set $gdictvar $gdict]<br />
1117 }<br />
1118 return $res<br />
1119 }<br />
This procedure returns a four levels nested dictionary with the raw (undecoded)<br />
strings in the name table. The keys are, in sequence, the numeric nameID,<br />
platformID, encodingID, and languageID. The call syntax is<br />
sfnt::name::rawnestdict {bytearray}<br />
1120 proc sfnt::name::rawnestdict {data} {<br />
1121 set res [dict create]<br />
1122 binary scan $data SuSuSu format count base<br />
1123 for {set rpos 6; set n 0} {$n
charmap command The {string}s <strong>of</strong> a /charmap can contain pretty arbitrary characters, and in particular<br />
control character can be troublesome in output (even though Tcl handles<br />
them correctly internally). Therefore it is useful to have a prettyprinting <strong>of</strong> this<br />
command which converts these to suitable escape sequences.<br />
1131 proc prettyTDL::/charmap {hex gid args} {<br />
1132 upvar 1 res res O(-indent) indent<br />
1133 append res $indent [list /charmap $hex $gid]<br />
1134 foreach str $args {<br />
By using list on a string which is unbalanced with respect to braces, it is possible<br />
to get something which works as a bareword in a list; this makes sure syntax<br />
characters are properly quoted, and will have rewritten newline, tab, etc. as their<br />
usual letter-escapes. Hence remaining control characters can be expressed using<br />
octal escapes.<br />
1135 set arg [string range [list "$str \{"] 0 end-4]<br />
1136 append res " " [string map {<br />
1137 \0 {\000} \1 {\001} \2 {\002} \3 {\003} \4 {\004} \5 {\005}<br />
1138 \6 {\006} \7 {\007} \10 {\010} \11 {\011} \12 {\012}<br />
1139 \13 {\013} \14 {\014} \15 {\015} \16 {\016} \17 {\017}<br />
1140 \20 {\020} \21 {\021} \22 {\022} \23 {\023} \24 {\024}<br />
1141 \25 {\025} \26 {\026} \27 {\027} \30 {\030} \31 {\031}<br />
1142 \32 {\032} \33 {\033} \34 {\034} \35 {\035} \36 {\036}<br />
1143 \37 {\037}<br />
1144 } $arg]<br />
1145 }<br />
1146 append res \n<br />
1147 }<br />
1148 prettyTDL::theinterp alias /charmap\<br />
[namespace which prettyTDL::/charmap]<br />
cmap (element) There is also a cmap element which surrounds each cmap subtable. Notable<br />
format (attribute) attributes <strong>of</strong> this element are format, which details the subtable format, and<br />
<strong>of</strong>fset (attribute) <strong>of</strong>fset, which details the <strong>of</strong>fset <strong>of</strong> the subtable from the beginning <strong>of</strong> the table.<br />
plat-enc-lang (element) cmap elements also contain plat-enc-lang elements, with attributes platform,<br />
platform (attribute) enc, lang, and language as in a platform–encoding–language dictionary. These<br />
enc (attribute) elements have no contents, but rather encode that the subtable that the surround-<br />
lang (attribute) ing cmap element encodes was mapped to by this combination <strong>of</strong> values.<br />
language (attribute) 1149 namespace eval sfnt::cmap {<br />
1150 namespace import [namespace parent]::name::PELdict<br />
1151<br />
1152 }<br />
namespace import [namespace parent]::name::encoding_convertfrom<br />
sfnt::cmap::parse (proc) The top-level parse procedure handles subtables and the plat-enc-lang tagging<br />
<strong>of</strong> these. Parsing <strong>of</strong> subtable format n is handled by the parse_format_n procedure,<br />
although to add a subformat parser it is necessary to add it to the final<br />
switch over formats.<br />
1153 proc sfnt::cmap::parse {data gdict {gdictvar ""}} {<br />
1154 upvar #0 [namespace parent]::name::tclenc Tclenc<br />
46
1155 binary scan $data SuSu version count<br />
1156 set res "# Table version $version.\n"<br />
1157 if {$version != 0} then {return $res}<br />
All references to a particular subtable are collected in the Subtable array, whose<br />
entries are lists <strong>of</strong> the form<br />
{platform} {encoding} ∗<br />
1158 set pos 4<br />
1159 for {} {$count >= 1} {incr count -1} {<br />
1160 binary scan $data @${pos}SuSuIu plat enc <strong>of</strong>fset<br />
1161 incr pos 8<br />
1162 lappend Subtable($<strong>of</strong>fset) $plat $enc<br />
1163 }<br />
Then each subtable is decoded once.<br />
1164 foreach <strong>of</strong>fset [lsort -integer [array names Subtable]] {<br />
1165 set L [list cmap <strong>of</strong>fset $<strong>of</strong>fset]<br />
1166 binary scan $data @${<strong>of</strong>fset}Su format<br />
1167 lappend L format $format<br />
1168 append res $L " \{\n"<br />
The language field is the third (if one counts as Apple does) in all subtable formats,<br />
but since it is not the same length in formats 8–12 as it is in formats 0–6, it is<br />
necessary to have a preliminary switch just for the purpose <strong>of</strong> reading it. And<br />
while we’re at that, we might as well take note <strong>of</strong> the length field too.<br />
1169 switch -- $format 0 - 2 - 4 - 6 {<br />
1170 binary scan $data @${<strong>of</strong>fset}x2SuSu length language<br />
1171 } 8 - 10 - 12 {<br />
1172 binary scan $data @${<strong>of</strong>fset}x2SuIuIu subformat length language<br />
1174 if {$subformat != 0} then {<br />
1175 append res "# Subformat $subformat.\n"<br />
1176 }<br />
1177 }<br />
1178 if {[info exists length]} then {<br />
1179 append res "# Subtable length $length bytes.\n"<br />
1180 unset length<br />
1181 }<br />
1182 set tclencL {}<br />
1183 foreach {plat enc} $Subtable($<strong>of</strong>fset) {<br />
1184 set L [list plat-enc-lang]<br />
1185 dict for {key val} [PELdict $plat $enc $language {}] {<br />
1186 lappend L [string trimleft $key -] $val<br />
1187 }<br />
1188 append res $L \n<br />
1189 if {[info exists Tclenc([list $plat $enc])]} then {<br />
1190 lappend tclencL $Tclenc([list $plat $enc])<br />
1191 } elseif {[info exists Tclenc([list $plat])]} then {<br />
1192 lappend tclencL $Tclenc([list $plat])<br />
1193 }<br />
1194 }<br />
47
sfnt::cmap:<br />
:parse_format_0 (proc)<br />
sfnt::cmap:<br />
:parse_format_2 (proc)<br />
Here is where one must modify the procedure to add parsing <strong>of</strong> more subtable<br />
formats.<br />
1195 switch -- $format 0 - 2 {<br />
1196 append res [parse_format_$format $data $<strong>of</strong>fset $tclencL]<br />
1197 }<br />
1198 append res \}\n<br />
1199 }<br />
1200 return $res<br />
1201 }<br />
This procedure parses cmap subtables in format 0. It has the call syntax<br />
parse_format_0 {table} {position} {tclenc-list}<br />
and returns the parsed data as TDL code. The {table} is the entire cmap table and<br />
the {position} is where in that table the subtable to parse begins. The {tclenc-list}<br />
is a list <strong>of</strong> Tcl encodings which should be tried for decoding the character code<br />
being mapped.<br />
Subtable format 0 is just an array <strong>of</strong> 256 GIDs in bytes.<br />
1202 proc sfnt::cmap::parse_format_0 {data pos encL} {<br />
1203 binary scan $data @${pos}x6cu256 L<br />
1204 set res ""<br />
1205 set code -1; foreach gid $L {incr code<br />
1206 if {$gid == 0} then {continue}<br />
1207 append res [list /charmap [format %02X $code] $gid]<br />
1208 foreach enc $encL {<br />
1209 append res { }\<br />
[list [encoding_convertfrom $enc [binary format cu $code]]]<br />
1211 }<br />
1212 append res \n<br />
1213 }<br />
1214 return $res<br />
1215 }<br />
This procedure parses cmap subtables in format 2. It has the call syntax<br />
parse_format_2 {table} {position} {tclenc-list}<br />
and returns the parsed data as TDL code. The {table} is the entire cmap table and<br />
the {position} is where in that table the subtable to parse begins. The {tclenc-list}<br />
is a list <strong>of</strong> Tcl encodings which should be tried for decoding the character code<br />
being mapped.<br />
Subtable format 2 supports two-byte encodings and mixed one/two-byte encodings.<br />
It has a three level structure where the first and second levels hold byte<br />
<strong>of</strong>fsets to selected the second and third respectively level entries. Actual glyph<br />
indices are computed by adding numbers in the second and third levels.<br />
The fixed length part is the first level (256 words), so the procedure is organised<br />
as an outer loop over the first level and an inner loop over the second level.<br />
1216 proc sfnt::cmap::parse_format_2 {data pos encL} {<br />
48
1217 binary scan $data @${pos}x6Su256 L<br />
1218 set res ""<br />
1219 set hi -1; foreach key $L {incr hi<br />
1220 set pos2 [expr {$pos+518+$key}]<br />
1221 binary scan $data @${pos2}SuSuSSu firstCode entryCount idDelta\<br />
idRangeOffset<br />
If the key is 0 then the hi byte is the only byte, and it is used also as index for a<br />
GID datum.<br />
1223 if {$key == 0} then {<br />
1224 if {$hi < $firstCode || $hi >= $firstCode+$entryCount} then\<br />
{continue}<br />
1226 binary scan $data\<br />
@[expr {$pos2 + 8 + $idRangeOffset + $hi-$firstCode}]S gid<br />
1229 if {!$gid} then {continue}<br />
1230 set mapL [list [format %02X $hi] $gid]<br />
1231 } else {<br />
Otherwise there is an entire range <strong>of</strong> GID data, and each <strong>of</strong> them contributes one<br />
/charmap entry.<br />
1232 binary scan $data @${pos2}x8x${idRangeOffset}S${entryCount}\<br />
gidL<br />
1234 set mapL {}<br />
1235 foreach gid $gidL {<br />
1236 if {$gid} then {<br />
1237 lappend mapL [format %02X%02X $hi $firstCode] $gid<br />
1238 }<br />
1239 incr firstCode<br />
1240 }<br />
1241 }<br />
Manufacturing actual /charmap entries is common in the two cases, since for<br />
example the idDelta aspect still remains to be handled. The mapL list is used as<br />
intermediate storage for data.<br />
1242 foreach {hex gid} $mapL {<br />
1243 set L [list /charmap $hex]<br />
1244 lappend L [expr {($gid+$idDelta) & 65535}]<br />
1245 set bytes [binary format H* $hex]<br />
1246 foreach enc $encL {<br />
1247 append L [encoding_convertfrom $enc $bytes]<br />
1248 }<br />
1249 append res $L \n<br />
1250 }<br />
1251 }<br />
1252 return $res<br />
1253 }<br />
Note: This is currently untested, since I haven’t yet found any font with a format<br />
2 cmap table.<br />
49
glyphbbox (element)<br />
sfnt::glyf:<br />
:parse_bboxes (proc)<br />
sfnt::glyf:<br />
:parse_loca (proc)<br />
10 glyf and loca tables<br />
<strong>OpenType</strong> doesn’t provide glyph-wise bounding box inforation separately from<br />
the glyph outlines, but there is an explicit bounding box in each glyph header.<br />
This information is encoded using /glyphbbox elements, which have the syntax<br />
/glyphbbox {GID} {left} {bottom} {right} {top}<br />
This is different from the fontinst \setglyphbb command only in that it specified<br />
the glyph by number rather than by name.<br />
1254 namespace eval sfnt::glyf {}<br />
Since this doesn’t parse the whole <strong>of</strong> the glyf table, it shouldn’t be the general<br />
sfnt::glyf::parse procedure, but on the other hand this allows us to diverge<br />
from the standard table-parse syntax and instead use<br />
sfnt::glyf::parse_bboxes {loca} {glyf } {gdict}<br />
since the loca table is all about keeping track <strong>of</strong> where in the glyf table the glyph<br />
headers are located.<br />
1255 proc sfnt::glyf::parse_bboxes {loca glyf gdict} {<br />
1256 set posL [parse_loca $loca $gdict]<br />
1257 set res ""<br />
1258 for {set n 0} {$n < [dict get $gdict numGlyphs]} {incr n} {<br />
1259 if {[lindex $posL $n] != [lindex $posL $n+1]} then {<br />
1260 binary scan $glyf @[lindex $posL $n]SS4 numberOfContours bbox<br />
1261 set L [list /glyphbbox $n]<br />
1262 foreach b $bbox {<br />
1263 lappend L [expr {$b*[dict get $gdict funit]}]<br />
1264 }<br />
1265 append res $L \n<br />
1266 }<br />
1267 }<br />
1268 return $res<br />
1269 }<br />
The parsing <strong>of</strong> the loca table is slightly unintuitive, so it is handled by a separate<br />
procedure. The call syntax is<br />
parse_loca {loca} {gdict}<br />
but the return value is the list <strong>of</strong> <strong>of</strong>fsets rather than some TDL-encoding <strong>of</strong> the<br />
same.<br />
1270 proc sfnt::glyf::parse_loca {loca gdict} {<br />
1271 if {[dict get $gdict indexToLocFormat]} then {<br />
1272 binary scan $loca Iu* posL<br />
1273 } else {<br />
1274 binary scan $loca Su* L<br />
1275 set posL {}<br />
50
1276 foreach c $L {lappend posL [expr {2*$c}]}<br />
1277 }<br />
1278 return $posL<br />
1279 }<br />
sfnt::glyf::parse (proc) Still, in want <strong>of</strong> a more thorough glyf parser, parse_bboxes is a decent substitute.<br />
#loca (gdict entry)<br />
1280 proc sfnt::glyf::parse {glyf gdict {var ""}} {<br />
1281 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
1282 return "# Outline data elided.\n[<br />
1283 parse_bboxes [dict get $gdict #loca] $glyf $gdict<br />
1284 ]"<br />
1285 }<br />
The above assumes the gdict contains a #loca entry with the raw contents <strong>of</strong> the<br />
loca table.<br />
sfnt::glyf:<br />
Even that parser requires several tables to have been parsed before.<br />
:parse_after (var.) 1286 namespace eval sfnt::glyf {<br />
1287 variable parse_after {head maxp loca}<br />
1288 }<br />
sfnt::loca:<br />
:parse_after (var.)<br />
Proper parsing <strong>of</strong> the loca table requires knowing the indexToLocFormat from<br />
the head table, but the “just copy to gdict” parsing done below could in principle<br />
do without that.<br />
1289 namespace eval sfnt::loca {variable parse_after head}<br />
sfnt::loca::parse (proc) Besides copying the raw data to the gdict,<br />
sfnt::kern:<br />
:parse_after (var.)<br />
1290 proc sfnt::loca::parse {data gdict {var ""}} {<br />
1291 dict set gdict #loca $data<br />
1292 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
1293 return "# Indices into ’glyf’ table elided.\n"<br />
1294 }<br />
11 kern tables<br />
Parsing kern tables requires knowing the funit, and must hence come after the<br />
head table.<br />
1295 namespace eval sfnt::kern {<br />
1296 variable parse_after head<br />
1297 }<br />
sfnt::kern::parse (proc) The main parse procedure mostly iterates over the subtables, using parse_subtable<br />
to parse these.<br />
1298 proc sfnt::kern::parse {data gdict {var ""}} {<br />
1299 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
1300 binary scan $data SuSu version numsub<br />
1301 if {$version == 0} then {<br />
1302 set pos 4<br />
51
sfnt::kern:<br />
:parse_subtable (proc)<br />
kern-table (element)<br />
/kernpair (element)<br />
1303 set res ""<br />
1304 for {} {$numsub>0} {incr numsub -1} {<br />
1305 append res [parse_subtable $data pos [dict get $gdict funit]]<br />
1306 }<br />
1307 return $res<br />
1308 } elseif {$version == 1} then {<br />
Although not mentioned in the OFF spec, there is also a version 1 <strong>of</strong> the table<br />
which is used by Apple. It is quite similar to version 0, but some fields are 32 bit<br />
wide rather than 16 bit wide.<br />
1309 binary scan $data H8Iu version numsub<br />
1310 set res "# ’kern’ table version 0x$version.\n"<br />
1311 set pos 8<br />
1312 for {} {$numsub>0} {incr numsub -1} {<br />
1313 append res\<br />
[parse_long_subtable $data pos [dict get $gdict funit]]<br />
1314 }<br />
1315 return $res<br />
1316 } else {<br />
1317 return "# Could not parse ’kern’ table version $version.\n"<br />
1318 }<br />
1319 }<br />
This procedure parses a kern subtable. It has the call syntax<br />
parse_subtable {data} {start-var} {funit}<br />
where {data} is binary data and {start-var} is the name <strong>of</strong> a variable in the calling<br />
context that contains the index into the {data} <strong>of</strong> the beginning <strong>of</strong> the subtable<br />
to parse. The procedure returns TDL code for the subtable and increments the<br />
{start-var} to point at the first byte after the table.<br />
Each subtable is parsed as a kern-table element, which may have the at-<br />
tributes horizontal, minimum, cross-stream, and override, corresponding to<br />
bits in the coverage field <strong>of</strong> the subtable header. If an attribute is present, the<br />
value <strong>of</strong> the corresponding bit is 1, whereas it is 0 if omitted.<br />
Individual kern pairs are expressed using /kernpair elements, which have the<br />
syntax<br />
/kernpair {left} {right} {amount}<br />
where {left} and {right} are lists <strong>of</strong> GIDs, and {amount} is in AFM units. It<br />
means this amount should be added between all pairs <strong>of</strong> one {left} glyph and one<br />
{right} glyph.<br />
1320 proc sfnt::kern::parse_subtable {data startvar funit} {<br />
1321 upvar 1 $startvar start<br />
1322 binary scan $data @${start}SuSucub8 version length format coverage<br />
1323 if {$version != 0} then {<br />
1324 incr start $length<br />
1325 return "# Could not parse version $version subtable ($length\<br />
bytes).\n"<br />
52
sfnt::kern:<br />
:parse_format_0 (proc)<br />
sfnt::kern:<br />
:parse_format_2 (proc)<br />
1327 }<br />
1328 set res [list kern-table]<br />
1329 foreach bit [split $coverage ""] attr {<br />
1330 horizontal minimum cross-stream override bit4 bit5 bit6 bit7<br />
1331 } {if {$bit} then {lappend res $attr 1}}<br />
1332 append res " \{\n"<br />
1333 switch -- $format 0 {<br />
1334 parse_format_0 res $data [expr {$start+6}] $funit<br />
1335 } 2 {<br />
1336 parse_format_2 res $data $start $funit<br />
1337 } default {<br />
1338 append res "# Unknown subtable format: $format\n"<br />
1339 }<br />
1340 incr start $length<br />
1341 append res \}\n<br />
1342 }<br />
This procedure parses a format 0 kern subtable. It has the call syntax<br />
parse_format_0 {res-var} {data} {pos} {funit}<br />
where {res-var} is a variable in the calling context to which the TDL interpretation<br />
<strong>of</strong> the table should be appended, {data} is binary data containing the subtable to<br />
parse with the first byte after the subtable header at position {pos}, and {funit}<br />
is the font unit. There is no particular return value.<br />
1343 proc sfnt::kern::parse_format_0 {resvar data pos funit} {<br />
1344 upvar 1 $resvar res<br />
1345 binary scan $data @${pos}Su npairs<br />
1346 binary scan $data @${pos}x8Su[expr {3*$npairs}] L<br />
1347 foreach {left right value} $L {<br />
1348 append res [list /kernpair [list $left] [list $right]\<br />
[expr {$funit*(($value^0x8000)-0x8000)}]] \n<br />
1350 }<br />
1351 }<br />
This procedure parses a format 2 kern subtable. It has the call syntax<br />
parse_format_2 {res-var} {data} {start} {funit}<br />
where {res-var} is a variable in the calling context to which the TDL interpretation<br />
<strong>of</strong> the table should be appended, {data} is binary data containing the subtable<br />
to parse beginning (with header, assumed to be 8 bytes long) at position {start},<br />
and {funit} is the font unit. There is no particular return value.<br />
1352 proc sfnt::kern::parse_format_2 {resvar data start funit} {<br />
1353 upvar 1 $resvar res<br />
1354 binary scan $data @${start}x8SuSuSuSu width leftOfs rightOfs arrayOfs<br />
1356 binary scan @${start}x${leftOfs}SuSu gid num<br />
1357 binary scan @${start}x${leftOfs}x4Su${num} L<br />
1358 foreach class $L {<br />
1359 lappend lA($class) $gid<br />
53
sfnt::kern:<br />
:parse_long_subtable<br />
(proc)<br />
kern-table (element)<br />
1360 incr gid<br />
1361 }<br />
1362 binary scan @${start}x${rightOfs}SuSu gid num<br />
1363 binary scan @${start}x${leftOfs}x4Su${num} L<br />
1364 foreach class $L {<br />
1365 lappend rA($class) $gid<br />
1366 incr gid<br />
1367 }<br />
1368 incr start $arrayOfs<br />
1369 foreach lc [array names lA] {<br />
1370 foreach rc [array names rA] {<br />
1371 binary scan @${start}x${lc}x${rc}S value<br />
1372 if {$value != 0} then {<br />
1373 append res\<br />
[list /kernpair $lA($lc) $rA($rc) [expr {$value*$funit}]]\<br />
\n<br />
1375 }<br />
1376 }<br />
1377 }<br />
1378 }<br />
This procedure parses a subtable in the long (32-bit; version 1) kind <strong>of</strong> kern table.<br />
It has the call syntax<br />
parse_long_subtable {data} {start-var} {funit}<br />
where {data} is binary data and {start-var} is the name <strong>of</strong> a variable in the calling<br />
context that contains the index into the {data} <strong>of</strong> the beginning <strong>of</strong> the subtable<br />
to parse. The procedure returns TDL code for the subtable and increments the<br />
{start-var} to point at the first byte after the table.<br />
Each subtable is parsed as a kern-table element, which may have the at-<br />
tributes horizontal and cross-stream, corresponding to bits in the coverage<br />
field <strong>of</strong> the subtable header. If one <strong>of</strong> these attributes is present, its value must be<br />
boolean true, with boolean false being the default. (For compatibility with table<br />
version 0, the horizontal mode is 1 even though the corresponding binary bit will<br />
in fact be 0 in this case.) There may also be a variation attribute, whose value<br />
is the tuple index for the font that this subtable applies to; this has to do with<br />
variation fonts (whatever those are).<br />
1379 proc sfnt::kern::parse_long_subtable {data startvar funit} {<br />
1380 upvar 1 $startvar start<br />
1381 binary scan $data @${start}IuB8cuSu length coverage format tuple<br />
1382 set res [list kern-table]<br />
1383 if {![string index $coverage 0]} then {lappend res horizontal 1}<br />
1384 if {[string index $coverage 1]} then {lappend res cross-stream 1}<br />
1385 if {[string index $coverage 2]} then {<br />
1386 lappend res variation $tuple<br />
1387 }<br />
1388 append res " \{\n"<br />
1389 switch -- $format 0 {<br />
54
sfnt::kern:<br />
:parse_format_3 (proc)<br />
1390 parse_format_0 res $data [expr {$start+8}] $funit<br />
1391 } 1 {<br />
1392 append res "# Sorry, no parser for format 1 (automaton) ’kern’\<br />
subtables.\n"<br />
1394 } 2 {<br />
1395 parse_format_2 res $data $start $funit<br />
1396 } 3 {<br />
1397 parse_format_3 res $data [expr {$start+8}] $funit<br />
1398 } default {<br />
1399 append res "# Unknown subtable format: $format\n"<br />
1400 }<br />
1401 incr start $length<br />
1402 append res \}\n<br />
1403 }<br />
This procedure parses a format 3 kern subtable. It has the call syntax<br />
parse_format_3 {res-var} {data} {pos} {funit}<br />
where {res-var} is a variable in the calling context to which the TDL interpretation<br />
<strong>of</strong> the table should be appended, {data} is binary data containing the subtable to<br />
parse with the first byte after the subtable header at position {pos}, and {funit}<br />
is the font unit. There is no particular return value.<br />
1404 proc sfnt::kern::parse_format_3 {resvar data pos funit} {<br />
1405 upvar 1 $resvar res<br />
1406 binary scan $data @${pos}Sucucucucu glyphs kvals lclass rclass\<br />
reserved<br />
1408 incr pos 6<br />
1409 binary scan $data\<br />
@${pos}S${kvals}cu${glyphs}cu${glyphs}cu[expr {$lclass*$rclass}]\<br />
kernValL lClassL rClassL kern<strong>Index</strong>L<br />
1412 set n -1; foreach lc $lClassL rc $rClassL {incr n<br />
1413 lappend LA($lc) $n<br />
1414 lappend RA($rc) $n<br />
1415 }<br />
1416 set lc 0; set rc 0<br />
1417 foreach item $kern<strong>Index</strong>L {<br />
1418 if {[lindex $kernValL $item] != 0} then {<br />
1419 append res [<br />
1420 list /kernpair [lappend LA($lc)] [lappend RA($rc)]\<br />
[expr {$funit*[lindex $kernValL $item]}]<br />
1422 ] \n<br />
1423 }<br />
1424 if {[incr rc]>=$rclass} then {set rc 0; incr lc}<br />
1425 }<br />
1426 }<br />
55
sfnt::CFF::parse_index<br />
(proc)<br />
sfnt::CFF:<br />
:standard_strings (var.)<br />
12 CFF tables<br />
The main namespace for CFF-related things is CFF rather than CFF as the general<br />
parser format would require, since the extra space would make command names<br />
somewhat awkward to write. If need for a general format parser arises, then that<br />
can be an alias in the CFF namespace.<br />
1427 namespace eval sfnt::CFF {}<br />
This procedure parses an <strong>Index</strong> data structure and returns the data as a list with<br />
unparsed elements. The call syntax is<br />
parse_index {data} {position} {after-var} ?<br />
where {data} is binary data containing the <strong>Index</strong> and {position} is the position<br />
in {data} where the <strong>Index</strong> begins. The {after-var}, if provided, is the name <strong>of</strong> a<br />
variable in the calling context which will be set to the position <strong>of</strong> the first byte in<br />
{data} after the <strong>Index</strong>.<br />
1428 proc sfnt::CFF::parse_index {data pos {aftervar ""}} {<br />
1429 binary scan $data @${pos}Suc count <strong>of</strong>fsize<br />
1430 if {!$count} then {<br />
1431 incr pos 2<br />
1432 if {$aftervar ne ""} then {<br />
1433 uplevel 1 [list ::set $aftervar $pos]<br />
1434 }<br />
1435 return {}<br />
1436 }<br />
1437 incr pos 3<br />
1438 binary scan $data @${pos}H[expr {2*$<strong>of</strong>fsize*($count+1)}] <strong>of</strong>fsets<br />
1439 regsub -all [format {.{%d}} [expr {2*$<strong>of</strong>fsize}]] $<strong>of</strong>fsets {0x& } <strong>of</strong>sL<br />
1440 incr pos [expr {$<strong>of</strong>fsize*($count+1)-1}]<br />
1441 set res {}<br />
1442 for {set n 0} {$n
1457 N O P Q R S T U V W X Y Z bracketleft backslash bracketright<br />
1458 asciicircum underscore quoteleft a b c d e f g h i j k l m n o p q<br />
1459 r s t u v w x y z braceleft bar braceright asciitilde exclamdown<br />
1460 cent sterling fraction yen florin section currency quotesingle<br />
1461 quotedblleft guillemotleft guilsinglleft guilsinglright fi fl<br />
1462 endash dagger daggerdbl periodcentered paragraph bullet<br />
1463 quotesinglbase quotedblbase quotedblright guillemotright ellipsis<br />
1464 perthousand questiondown grave acute circumflex tilde macron breve<br />
1465 dotaccent dieresis ring cedilla hungarumlaut ogonek caron emdash AE<br />
1466 ordfeminine Lslash Oslash OE ordmasculine ae dotlessi lslash oslash<br />
1467 oe germandbls onesuperior logicalnot mu trademark Eth onehalf<br />
1468 plusminus Thorn onequarter divide brokenbar degree thorn<br />
1469 threequarters twosuperior registered minus eth multiply<br />
1470 threesuperior copyright Aacute Acircumflex Adieresis Agrave Aring<br />
1471 Atilde Ccedilla Eacute Ecircumflex Edieresis Egrave Iacute<br />
1472 Icircumflex Idieresis Igrave Ntilde Oacute Ocircumflex Odieresis<br />
1473 Ograve Otilde Scaron Uacute Ucircumflex Udieresis Ugrave Yacute<br />
1474 Ydieresis Zcaron aacute acircumflex adieresis agrave aring atilde<br />
1475 ccedilla eacute ecircumflex edieresis egrave iacute icircumflex<br />
1476 idieresis igrave ntilde oacute ocircumflex odieresis ograve otilde<br />
1477 scaron uacute ucircumflex udieresis ugrave yacute ydieresis zcaron<br />
1478 exclamsmall Hungarumlautsmall dollaroldstyle dollarsuperior<br />
1479 ampersandsmall Acutesmall parenleftsuperior parenrightsuperior<br />
1480 twodotenleader onedotenleader zerooldstyle oneoldstyle twooldstyle<br />
1481 threeoldstyle fouroldstyle fiveoldstyle sixoldstyle sevenoldstyle<br />
1482 eightoldstyle nineoldstyle commasuperior threequartersemdash<br />
1483 periodsuperior questionsmall asuperior bsuperior centsuperior<br />
1484 dsuperior esuperior isuperior lsuperior msuperior nsuperior<br />
1485 osuperior rsuperior ssuperior tsuperior ff ffi ffl<br />
1486 parenleftinferior parenrightinferior Circumflexsmall hyphensuperior<br />
1487 Gravesmall Asmall Bsmall Csmall Dsmall Esmall Fsmall Gsmall Hsmall<br />
1488 Ismall Jsmall Ksmall Lsmall Msmall Nsmall Osmall Psmall Qsmall<br />
1489 Rsmall Ssmall Tsmall Usmall Vsmall Wsmall Xsmall Ysmall Zsmall<br />
1490 colonmonetary onefitted rupiah Tildesmall exclamdownsmall<br />
1491 centoldstyle Lslashsmall Scaronsmall Zcaronsmall Dieresissmall<br />
1492 Brevesmall Caronsmall Dotaccentsmall Macronsmall figuredash<br />
1493 hypheninferior Ogoneksmall Ringsmall Cedillasmall questiondownsmall<br />
1494 oneeighth threeeighths fiveeighths seveneighths onethird twothirds<br />
1495 zerosuperior foursuperior fivesuperior sixsuperior sevensuperior<br />
1496 eightsuperior ninesuperior zeroinferior oneinferior twoinferior<br />
1497 threeinferior fourinferior fiveinferior sixinferior seveninferior<br />
1498 eightinferior nineinferior centinferior dollarinferior<br />
1499 periodinferior commainferior Agravesmall Aacutesmall<br />
1500 Acircumflexsmall Atildesmall Adieresissmall Aringsmall AEsmall<br />
1501 Ccedillasmall Egravesmall Eacutesmall Ecircumflexsmall<br />
1502 Edieresissmall Igravesmall Iacutesmall Icircumflexsmall<br />
1503 Idieresissmall Ethsmall Ntildesmall Ogravesmall Oacutesmall<br />
1504 Ocircumflexsmall Otildesmall Odieresissmall OEsmall Oslashsmall<br />
1505 Ugravesmall Uacutesmall Ucircumflexsmall Udieresissmall Yacutesmall<br />
1506 Thornsmall Ydieresissmall 001.000 001.001 001.002 001.003 Black<br />
57
sfnt::CFF::parse_dict<br />
(proc)<br />
1507 Bold Book Light Medium Regular Roman Semibold<br />
1508 }<br />
This procedure parses CFF dictionary bytecode and returns it as a list <strong>of</strong> TDL<br />
elements. The call syntax is<br />
parse_dict {data} {self-pos} {string-list}<br />
where {data} is precisely the bytes to interpret as a dictionary. The {self-pos} is<br />
the nominal position <strong>of</strong> the {data}; this is used when interpreting relative pointers<br />
to other data structures. The {string-list} is the SID-indexed list <strong>of</strong> known strings.<br />
The idea for this parser is to first use a regexp to cut the bytecode up into a<br />
list <strong>of</strong> tokens, then process these with tools appropriate for each type.<br />
1509 proc sfnt::CFF::parse_dict {data pos stringL} {<br />
1510 set tokenL [regexp -all -inline {(?x)<br />
1511 [\040-\366] | # One-byte number, -107..107<br />
1512 [\367-\376]. | # Two-byte number, -1131..1131<br />
1513 \034.. | # Three-byte number, -32768..32767<br />
1514 \035.... | # Five-byte number; 32-bit 2’s-complement<br />
1515 \036<br />
1516 [^\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF\xCF\xDF\xEF\xFF]*<br />
1517 [\x0F\x1F\x2F\x3F\x4F\x5F\x6F\x7F\x8F\x9F\xAF\xBF\xCF\xDF\xEF\xFF]<br />
1518 # Real number (BCD-coded)<br />
1519 | [\000-\013\015-025] # One-byte operators<br />
1520 | \014. # Two-byte operators<br />
1521 } $data]<br />
Operand tokens push items on the stack, operator tokens empty it.<br />
1522 set stack {}<br />
1523 set res {}<br />
1524 foreach token $tokenL {<br />
1525 binary scan $token cucu b0 b1<br />
1526 if {$b0>=28} then {<br />
1527 if {$b0==28} then {<br />
1528 binary scan $token x1S num<br />
1529 lappend stack $num<br />
1530 } elseif {$b0==29} then {<br />
1531 binary scan $token x1I num<br />
1532 lappend stack $num<br />
1533 } elseif {$b0==30} then {<br />
1534 binary scan $token x1H* num<br />
1535 lappend stack [string map {a . b E c E- d {} e - f {}} $num]<br />
1536 } elseif {$b0
The bytecode is first translated to a symbolic name, then that name is used to<br />
decide how to interpret the operands.<br />
1544 if {$b0!=12} then {<br />
1545 set name [lindex {<br />
1546 version Notice FullName FamilyName Weight <strong>Font</strong>BBox<br />
1547 BlueValues OtherBlues FamilyBlues FamilyOtherBlues<br />
1548 StdHW StdVW escape UniqueID XUID charset Encoding<br />
1549 CharStrings Private Subrs defaultWidthX nominalWidthX<br />
1550 } $b0]<br />
1551 } else {<br />
1552 set name [lindex {<br />
1553 Copyright isFixedPitch ItalicAngle UnderlinePosition<br />
1554 UnderlineThickness PaintType CharstringType <strong>Font</strong>Matrix<br />
1555 StrokeWidth BlueScale BlueShift BlueFuzz StemSnapH<br />
1556 StemSnapV ForceBold -Reserved- -Reserved- LanguageGroup<br />
1557 ExpansionFactor initialRandomSeed SyntheticBase<br />
1558 PostScript Base<strong>Font</strong>Name Base<strong>Font</strong>Blend -Reserved-<br />
1559 -Reserved- -Reserved- -Reserved- -Reserved- -Reserved-<br />
1560 ROS CID<strong>Font</strong>Version CID<strong>Font</strong>Revision CID<strong>Font</strong>Type CIDCount<br />
1561 UIDBase FDArray FDSelect <strong>Font</strong>Name<br />
1562 } $b1]<br />
1563 }<br />
1564 switch -- $name {<br />
The following operators have one SID operand. The corresponding string becomes<br />
the only argument <strong>of</strong> the command, which is thus positional. The syntaxes are:<br />
/version {string}<br />
/Notice {string}<br />
/FullName {string}<br />
/FamilyName {string}<br />
/Weight {string}<br />
/PostScript {string}<br />
/Base<strong>Font</strong>Name {string}<br />
/<strong>Font</strong>Name {string}<br />
1565 version - Notice - FullName - FamilyName - Weight -<br />
1566 PostScript - Base<strong>Font</strong>Name - <strong>Font</strong>Name {<br />
1567 lappend res\<br />
[list /$name [lindex $stringL [lindex $stack 0]]]<br />
1569 }<br />
The following operators have a delta-encoded array <strong>of</strong> operands. The decoded<br />
array becomes the args (again positional) <strong>of</strong> the command. Thus syntaxes are:<br />
/BlueValues {value} ∗<br />
/OtherBlues {value} ∗<br />
/FamilyBlues {value} ∗<br />
/FamilyOtherBlues {value} ∗<br />
/StemSnapH {value} ∗<br />
59
StemSnapV {value} ∗<br />
/Base<strong>Font</strong>Blend {value} ∗<br />
1570 BlueValues - OtherBlues - FamilyBlues - FamilyOtherBlues -<br />
1571 StemSnapH - StemSnapV - Base<strong>Font</strong>Blend {<br />
1572 set sum 0<br />
1573 set cmd [list /$name]<br />
1574 foreach num $stack {<br />
1575 set sum [expr {$sum+$num}]<br />
1576 lappend cmd $sum<br />
1577 }<br />
1578 lappend res $cmd<br />
1579 }<br />
The following operators point to a separate data structure, and the interpretation<br />
<strong>of</strong> that should become the TDL body <strong>of</strong> the corresponding element, but for now<br />
the location <strong>of</strong> that data structure is encoded in the start attribute.<br />
1580 charset - Encoding - CharStrings - FDArray - FDSelect {<br />
1581 lappend res [list $name start [lindex $stack 0]]<br />
1582 }<br />
The Private operator is similar, but since it points directly to a dict, it is necessary<br />
to also encode the length <strong>of</strong> the pointed-to data structure.<br />
1583 Private {<br />
1584 lappend res [list $name start [lindex $stack 1] length\<br />
[lindex $stack 0]]<br />
1586 }<br />
The Subrs operator has a self-relative <strong>of</strong>fset, but is otherwise similar to e.g. charset.<br />
1587 Subrs {<br />
1588 lappend res\<br />
[list $name start [expr {$pos+[lindex $stack 0]}]]<br />
1590 }<br />
The ROS operator has the most complex operand structure, with two SIDs and a<br />
number. Its syntax is<br />
/ROS {registry} {ordering} {supplement}<br />
1591 ROS {<br />
1592 set cmd [list /$name]<br />
1593 lappend cmd [lindex $stringL [lindex $stack 0]]<br />
1594 lappend cmd [lindex $stringL [lindex $stack 1]]<br />
1595 lappend cmd [lindex $stack 2]<br />
1596 lappend res $cmd<br />
1597 }<br />
For all other operators, the contents <strong>of</strong> the stack are directly made the positional<br />
arguments <strong>of</strong> the TDL command. This covers all argument structures described<br />
as number, array, or boolean. ToDo: Document their syntaxes.<br />
1598 default {<br />
1599 lappend res [linsert $stack 0 /$name]<br />
60
sfnt::CFF::parse_charset<br />
(proc)<br />
1600 }<br />
1601 }<br />
1602 set stack {}<br />
1603 }<br />
1604 }<br />
1605 return $res<br />
1606 }<br />
This procedure parses the charset data from a CFF font and returns the corresponding<br />
TDL script, typically a long sequence <strong>of</strong> /glyphname commands. The<br />
call syntax is<br />
parse_charset {CFF } {start} {numglyphs} {is CID?} {string-list}<br />
{end-pos-var} ?<br />
where {CFF } is the entire CFF table and {start} is the position within this table<br />
where the charset data to parse begins; this is necessary since the length <strong>of</strong> this<br />
data is not explicit in the data structure. Instead it is supposed to go on until all<br />
glyphs are covered, which is why {numglyphs} provides this information. Finally,<br />
if an {end-pos-var} is specified then the variable by that name in the calling<br />
context will be set to the first position after the charset data.<br />
Glyph names are encoded as SIDs, so the {string-list} must also be provided.<br />
However, in a CID<strong>Font</strong> (which is signalled by passing boolean true as {is CID?})<br />
the SIDs are rather CIDs, so in that case the generated commands are rather<br />
/glyphCID {GID} {CID}<br />
The first byte determines the data format.<br />
1607 proc sfnt::CFF::parse_charset\<br />
{data pos nglyphs isCID stringL {endvar ""}} {<br />
1609 binary scan $data @${pos}cu format<br />
1610 incr pos<br />
1611 set res "/glyphname 0 .notdef\n"<br />
1612 if {$isCID} then {<br />
1613 set mkcmd [list apply {{gid sid} {<br />
1614 list /glyphCID $gid $sid<br />
1615 }}]<br />
1616 } else {<br />
1617 set mkcmd [list apply {{strL gid sid} {<br />
1618 list /glyphname $gid [lindex $strL $sid]<br />
1619 }} $stringL]<br />
1620 }<br />
1621 switch -- $format 0 {<br />
1622 append res "# Format 0 data:\n"<br />
1623 binary scan $data @${pos}Su[expr {$nglyphs-1}] sidL<br />
1624 set n 0; foreach sid $sidL {incr n<br />
1625 append res [{*}$mkcmd $n $sid] \n<br />
1626 }<br />
1627 incr pos [expr {2*($nglyphs-1)}]<br />
61
1628 } 1 {<br />
1629 append res "# Format 1 data:\n"<br />
1630 for {set n 1} {$n=0} {incr count -1} {<br />
1634 append res [{*}$mkcmd $n $sid] \n<br />
1635 incr n; incr sid<br />
1636 }<br />
1637 }<br />
1638 } 2 {<br />
1639 append res "# Format 2 data:\n"<br />
1640 for {set n 1} {$n=0} {incr count -1} {<br />
1645 append res [{*}$mkcmd $n $sid] \n<br />
1646 incr n; incr sid<br />
1647 }<br />
1648 }<br />
1649 } default {<br />
1650 error "Unknown charset format: $format"<br />
1651 }<br />
1652 if {$endvar ne ""} then {<br />
1653 uplevel 1 [list ::set $endvar $pos]<br />
1654 }<br />
1655 return $res<br />
1656 }<br />
sfnt::CFF::parse (proc) This procedure is the top-level parser for CFF tables. As such, it has the call<br />
syntax<br />
sfnt::CFF::parse {data} {gdict} {gdict-var}<br />
and returns a TDL representation <strong>of</strong> the CFF table contents, but it might more<br />
<strong>of</strong>ten be called as sfnt::CFF ::parse.<br />
1657 namespace eval sfnt {<br />
1658 interp alias {} [namespace current]::CFF\ ::parse {}\<br />
[namespace current]::CFF::parse<br />
1660 }<br />
The CFF table is mostly independent <strong>of</strong> the rest <strong>of</strong> the tables, so there is<br />
nothing strict that it can contribute to the gdict. It may however have something<br />
similar internally.<br />
1661 proc sfnt::CFF::parse {data gdict {var ""}} {<br />
1662 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
The first thing to parse is as always the header.<br />
1663 binary scan $data cucucucu major minor pos <strong>of</strong>fSize<br />
62
1664 set res "# Format version $major.$minor\n"<br />
1665 if {$major != 1} then {return $res}<br />
1666 append res [list /CFF-<strong>of</strong>fSize $<strong>of</strong>fSize] \n<br />
Then comes the four global indices (name, top dict, string, and global subr).<br />
1667 set nameL [parse_index $data $pos pos]<br />
1668 append res "# [llength $nameL] fonts\n"<br />
1669 set topdictL [parse_index $data $pos pos]<br />
1670 variable standard_strings<br />
1671 set stringL [list {*}$standard_strings {*}[<br />
1672 parse_index $data $pos pos<br />
1673 ]]<br />
1674 append res "# [llength $stringL] strings (including\<br />
[llength $standard_strings] standard)\n"<br />
1676 set gsubrL [parse_index $data $pos pos]<br />
With the list <strong>of</strong> strings known, the top dicts can be parsed. The following is rather<br />
preliminary.<br />
1677 foreach name $nameL td $topdictL {<br />
1678 append res [list CFF-font name $name] " \{\n"<br />
1679 set pass2L {}<br />
1680 set isCID<strong>Font</strong> 0<br />
1681 foreach element [parse_dict $td 0 $stringL] {<br />
1682 switch -- [lindex $element 0] "charset" {<br />
1683 lappend pass2L $element<br />
1684 continue<br />
1685 } "CharStrings" {<br />
1686 set charstrL [parse_index $data [lindex $element 2]]<br />
1687 } "/ROS" {<br />
1688 set isCID<strong>Font</strong> 1<br />
1689 } "Private" {<br />
1690 set D [lrange $element 1 end]<br />
1691 append res $element " \{\n"<br />
1692 binary scan $data @[dict get $D start]a[dict get $D length]\<br />
subdata<br />
1694 foreach element [<br />
1695 parse_dict $subdata [dict get $D start] $stringL<br />
1696 ] {<br />
1697 append res $element \n<br />
1698 }<br />
1699 append res \}\n<br />
1700 continue<br />
1701 }<br />
1702 append res $element \n<br />
1703 }<br />
1704 foreach element $pass2L {<br />
1705 switch -- [lindex $element 0] "charset" {<br />
1706 append res $element " \{\n" [<br />
1707 parse_charset $data [lindex $element 2]\<br />
[llength $charstrL] $isCID<strong>Font</strong> $stringL<br />
1709 ] \}\n<br />
63
sfnt::GPOS:<br />
:scriptlist_by_feature<br />
(proc)<br />
ScriptRecord (element)<br />
LangSysRecord (element)<br />
1710 }<br />
1711 }<br />
1712 append res \}\n<br />
1713 }<br />
1714 return $res<br />
1715 }<br />
13 GPOS and GSUB tables<br />
The GPOS and GSUB tables are organised as Directed Acyclic Graphs in three major<br />
levels: scriptlist, feature list, and lookup list. The way they are supposed to be<br />
used is that the client first determines what features (entries in the feature list)<br />
should be active, by looking up the current combination <strong>of</strong> script and language in<br />
the scriptlist. Then whenever something (forming a ligature, placing an accent,<br />
kerning, etc.) might need to be done, the client should examine what the active<br />
features (that employ the operation in question) wants to do, by looking up the<br />
current situation in the referenced lookup tables, and only then get some hard<br />
data. Experience has furthermore shown that in particular the language to feature<br />
mapping can be very many to one, and the lookup tables can be huge. Hence it<br />
is not feasible to simply expand the DAG into a tree, which would have been the<br />
most straightforward way <strong>of</strong> decoding the given information.<br />
With older metrics formats in mind, the most practical way <strong>of</strong> organising<br />
the decoded information instead seems to be to make the lookup tables the outermost<br />
elements, and reverse the arrows from feature to lookup and script to<br />
feature. This means a lookuptable element typically contains one or several<br />
FeatureRecord elements, which in turn contains ScriptRecord elements, which<br />
contain LangSysRecord elements. These FeatureRecord elements can be viewed<br />
as declarations <strong>of</strong> the purpose <strong>of</strong> the data in the lookuptable, and conversion<br />
procedures would typically look at it to determine whether to (a) use the data in<br />
this table or (b) ignore it.<br />
The construction <strong>of</strong> such FeatureRecord elements is shared between the table<br />
types. Because <strong>of</strong> alphabetical order, these shared things live in the GPOS<br />
namespace.<br />
1716 namespace eval sfnt::GPOS {<br />
1717 variable parse_after head<br />
1718 }<br />
This procedure parses a scriptlist table. The call syntax is<br />
scriptlist_by_feature {data} {position}<br />
where the {position} is the point within the bytearray {data} where the scriptlist<br />
table begins. The return value is a dictionary that maps feature-list indices to a<br />
TDL encoding <strong>of</strong> the subset <strong>of</strong> the scriptlist which employs the given feature.<br />
The outermost element type is the ScriptRecord, which carries a manda-<br />
tory tag attribute and has a body containing LangSysRecord elements. The<br />
LangSysRecord element also carries a tag attribute, but it is not mandatory; the<br />
64
sfnt::GPOS:<br />
:featurelist_by_lookup<br />
(proc)<br />
default LangSysRecord (if any) for a script does not carry a tag. A LangSysRecord<br />
may also carry a required attribute, which if present must have a boolean<br />
true value; this means the feature is marked as required for this language. A<br />
LangSysRecord is currently always empty.<br />
To avoid trivial elements in the dictionary, the TDL fragments are constructed<br />
in a bottom-up manner, with an intermediate dictionary being constructed for<br />
each script record in the table.<br />
1719 proc sfnt::GPOS::scriptlist_by_feature {data pos} {<br />
1720 binary scan $data @${pos}Su scount<br />
1721 set res [dict create]<br />
1722 for {set spos [expr {$pos+2}]} {$scount>0}\<br />
{incr scount -1; incr spos 6} {<br />
1724 binary scan $data @${spos}a4Su stag l<strong>of</strong>s<br />
1725 set lpos [expr {$pos+$l<strong>of</strong>s}]<br />
1726 binary scan $data @${lpos}SuSu defaultOfs lcount<br />
1727 set recordL {}<br />
1728 if {$defaultOfs} then {lappend recordL "" $defaultOfs}<br />
1729 for {set rpos [expr {$lpos+4}]} {$lcount>0}\<br />
{incr lcount -1; incr rpos 6} {<br />
1731 binary scan $data @${rpos}a4Su tag <strong>of</strong>s<br />
1732 lappend recordL $tag $<strong>of</strong>s<br />
1733 }<br />
1734 set D [dict create]<br />
1735 foreach {tag <strong>of</strong>s} $recordL {<br />
1736 incr <strong>of</strong>s $lpos<br />
1737 set L [list LangSysRecord]<br />
1738 if {$tag ne ""} then {lappend L tag $tag}<br />
1739 binary scan $data @${<strong>of</strong>s}SuSuSu LookupOrder ReqFeature<strong>Index</strong>\<br />
fcount<br />
1741 if {$ReqFeature<strong>Index</strong> != 0xFFFF} then {<br />
1742 dict append D $ReqFeature<strong>Index</strong> $L { required yes} \n<br />
1743 }<br />
1744 binary scan $data @${<strong>of</strong>s}x6Su${fcount} indexL<br />
1745 foreach feature $indexL {<br />
1746 dict append D $feature $L \n<br />
1747 }<br />
1748 }<br />
1749 dict for {feature body} $D {<br />
1750 dict append res $feature [list ScriptRecord tag $stag] " \{\n"\<br />
$body "\}\n"<br />
1752 }<br />
1753 }<br />
1754 return $res<br />
1755 }<br />
This procedure parses a featurelist as a table <strong>of</strong> features per lookup. The call<br />
syntax is<br />
featurelist_by_lookup {data} {pos} {body-dict}<br />
65
Feature (element)<br />
sfnt::GPOS:<br />
:parse_feature (proc)<br />
FeatureRecord (element)<br />
where {pos} is the position in the {data} at which the featurelist starts. The<br />
{body-dict} is indexed by feature index and contains material to be placed in the<br />
body <strong>of</strong> the corresponding Feature element.<br />
A Feature element marks a surrounding lookuptable as being used for this<br />
feature. It carries a tag attribute that is the feature tag (kern, liga, etc.).<br />
1756 proc sfnt::GPOS::featurelist_by_lookup {data pos bodyD} {<br />
1757 binary scan $data @${pos}Su fcount<br />
1758 set res [dict create]<br />
1759 for {set index 0} {$index < $fcount} {incr index} {<br />
1760 binary scan $data @[expr {6*$index+2+$pos}]a4Su tag <strong>of</strong>s<br />
1761 set code [list Feature tag $tag]<br />
1762 incr <strong>of</strong>s $pos<br />
1763 binary scan $data @${<strong>of</strong>s}SuSu params count<br />
1764 if {$params} then {<br />
1765 append code " " paramOfs [expr {$params+$pos}]]<br />
1766 }<br />
1767 if {[dict exists $bodyD $index]} then {<br />
1768 append code " \{\n" [dict get $bodyD $index] "\}"<br />
1769 }<br />
1770 append code \n<br />
1771 binary scan $data @${<strong>of</strong>s}x4Su${count} iL<br />
1772 foreach i $iL {dict append res $i $code}<br />
1773 }<br />
1774 return $res<br />
1775 }<br />
This procedure parses one feature table from a feature list, returning a TDL<br />
encoding <strong>of</strong> the information, namely a FeatureRecord element. The call syntax<br />
is<br />
parse_feature {data} {prefix} {index} {required?}<br />
where {data} is the binary representation <strong>of</strong> the feature list and {index} is the<br />
index <strong>of</strong> the feature to parse. The {prefix} is a command prefix with the call<br />
syntax<br />
〈prefix〉 {lookup-index}<br />
that is supposed to return the TDL encoding <strong>of</strong> the lookup record.<br />
The return value consists <strong>of</strong> a single FeatureRecord element, the body <strong>of</strong> which<br />
is produced by the {prefix} callback. A FeatureRecord element always carries a<br />
tag attribute which identifies it further. It may also carry a required attribute<br />
if parse_feature was called with {required?} being true, and an index feature<br />
specifying the {index}. Finally, it will carry a FeatureParams attribute if that<br />
field <strong>of</strong> the record is nonzero; the value <strong>of</strong> that attribute is then the position within<br />
the {data} where the feature parameters data structure is expected to begin. No<br />
interpretation <strong>of</strong> that data structure is given, since it depends on the tag (and<br />
most features don’t use it).<br />
1776 proc sfnt::GPOS::parse_feature {data prefix index required} {<br />
66
sfnt::GPOS:<br />
:parse_lookuplist (proc)<br />
lookuptable (element)<br />
1777 binary scan $data Su count<br />
1778 if {$index >= $count} then {<br />
1779 return "\# Asked for feature $index, but only $count in table.\n"<br />
1780 }<br />
1781 binary scan $data @[expr {6*$index+2}]a4Su tag <strong>of</strong>s<br />
1782 binary scan $data @${<strong>of</strong>s}SuSu params count<br />
1783 set res [list FeatureRecord tag $tag index $index]<br />
1784 if {$required} then {append res " " [list required $required]}<br />
1785 if {$params} then {<br />
1786 append res " " [list FeatureParams [expr {$params+$<strong>of</strong>s}]]<br />
1787 }<br />
1788 append res " \{\n"<br />
1789 binary scan $data @${<strong>of</strong>s}x4Su${count} iL<br />
1790 foreach i $iL {append res [{*}$prefix $i]}<br />
1791 append res "\}\n"<br />
1792 return $res<br />
1793 }<br />
This procedure parses a lookup list, using a callback for parsing subtables. The<br />
call syntax is<br />
parse_lookuplist {data} {start} {prefix-list} {description-list}<br />
{header-dict}<br />
and the return value is the parsed data, in TDL format. The {data} is binary<br />
data (typically the whole GPOS or GSUB table) in which the lookuplist can be found<br />
starting at position {start}.<br />
The {prefix-list} is a list indexed by lookup type (hence the first element is not<br />
used) whose elements, if nonempty, are command prefixes used to parse subtables<br />
<strong>of</strong> that type; they have the call syntax<br />
〈prefix〉 {data} {st-start}<br />
where {data} is as for parse_lookuplist and {st-start} is the position <strong>of</strong> the<br />
beginning <strong>of</strong> the subtable to parse. The return value is the parsed data, in TDL<br />
format.<br />
The {description-list} is a list similarly indexed by lookup type, whose elements<br />
(if nonempty) are single line descriptions <strong>of</strong> the types. These are included as<br />
comments in the output.<br />
The {header-dict} is a dictionary indexed by lookup index. If a lookup has an<br />
entry in this dictionary, then the value <strong>of</strong> that entry is included in the result, just<br />
below the description comment. It is typically used to include Feature data.<br />
Each lookup table parsed is wrapped up in a lookuptable element, whose<br />
body contains the component subtables. Important attributes are:<br />
index The index <strong>of</strong> the table, as used by a /lookup element when referencing it.<br />
type The numeric type <strong>of</strong> the table.<br />
RightToLeft Bit 0 <strong>of</strong> LookupFlag word. If present, the value <strong>of</strong> this flag is a<br />
boolean true.<br />
67
IgnoreBaseGlyphs Bit 1 <strong>of</strong> LookupFlag word. If present, the value <strong>of</strong> this flag is<br />
a boolean true.<br />
IgnoreLigatures Bit 2 <strong>of</strong> LookupFlag word. If present, the value <strong>of</strong> this flag is<br />
a boolean true.<br />
IgnoreMarks Bit 3 <strong>of</strong> LookupFlag word. If present, the value <strong>of</strong> this flag is a<br />
boolean true.<br />
MarkFilteringSet If present, the value is an index into a GDEF table mark glyph<br />
set structure, and mark glyphs not in the set should be ignored by the layout<br />
engine.<br />
MarkAttachmentType If present (i.e., nonzero), marks <strong>of</strong> attachment type different<br />
from this should be ignored by the layout engine.<br />
1794 proc sfnt::GPOS::parse_lookuplist\<br />
{data start prefixL descriptionL headerD} {<br />
1796 binary scan $data @${start}Su numLookups<br />
1797 binary scan $data @${start}x2Su${numLookups} lookupOfsL<br />
1798 set res ""<br />
1799 set index 0<br />
1800 foreach <strong>of</strong>s $lookupOfsL {<br />
1801 incr <strong>of</strong>s $start<br />
1802 binary scan $data @${<strong>of</strong>s}Sucub8Su type MarkAttachmentType\<br />
bitfield count<br />
1804 set D [dict create index $index type $type]<br />
1805 foreach bit [split $bitfield ""] name {<br />
1806 RightToLeft IgnoreBaseGlyphs IgnoreLigatures IgnoreMarks\<br />
MarkFilteringSet bit5 bit6 bit7<br />
1808 } {<br />
1809 if {$bit} then {dict set D $name 1}<br />
1810 }<br />
1811 if {$MarkAttachmentType} then {<br />
1812 dict set D MarkAttachmentType $MarkAttachmentType<br />
1813 }<br />
1814 binary scan $data @${<strong>of</strong>s}x6Su${count}Su subOfsL MarkFilteringSet<br />
1815 if {[dict exists $D MarkFilteringSet]} then {<br />
1816 dict set D MarkFilteringSet $MarkFilteringSet<br />
1817 }<br />
1818 append res [linsert $D 0 lookuptable] " \{\n"<br />
1819 if {[lindex $descriptionL $type] ne ""} then {<br />
1820 append res "# [lindex $descriptionL $type]\n"<br />
1821 }<br />
1822 if {[dict exists $headerD $index]} then {<br />
1823 append res [dict get $headerD $index]<br />
1824 }<br />
1825 set prefix [lindex $prefixL $type]<br />
1826 if {[llength $prefix]} then {<br />
1827 foreach s<strong>of</strong>s $subOfsL {<br />
68
sfnt::GPOS:<br />
:coverage (proc)<br />
adjust-table (element)<br />
/adjustpair (element)<br />
sfnt::GPOS:<br />
:valueformat (proc)<br />
1828 append res [{*}$prefix $data [expr {$<strong>of</strong>s+$s<strong>of</strong>s}]]<br />
1829 }<br />
1830 } else {<br />
1831 append res "# $count subtables, but no parser.\n"<br />
1832 }<br />
1833 append res \}\n<br />
1834 incr index<br />
1835 }<br />
1836 return $res<br />
1837 }<br />
This procedure parses a coverage table, returning a list <strong>of</strong> GIDs. The call syntax<br />
is<br />
coverage {data} {start}<br />
where {data} is binary data containing the coverage table, and {start} is the<br />
position in {data} at which the table begins.<br />
1838 proc sfnt::GPOS::coverage {data start} {<br />
1839 binary scan $data @${start}SuSu format count<br />
1840 set res {}<br />
1841 if {$format == 1} then {<br />
1842 binary scan $data @${start}x4Su${count} res<br />
1843 } elseif {$format == 2} then {<br />
1844 binary scan $data @${start}x4Su[expr {3*$count}] L<br />
1845 foreach {first last idx} $L {<br />
1846 for {} {$first
sfnt::GPOS:<br />
:classtable (proc)<br />
XPlacement x-axis adjustment <strong>of</strong> glyph position<br />
YPlacement y-axis adjustment <strong>of</strong> glyph position<br />
XAdvance x-axis adjustment <strong>of</strong> glyph advance width<br />
YAdvance y-axis adjustment <strong>of</strong> glyph advance width<br />
XPlaDevice Offset to device table<br />
YPlaDevice Offset to device table<br />
XAdvDevice Offset to device table<br />
YAdvDevice Offset to device table<br />
A {suffix} <strong>of</strong> 2 is common for data related to the second glyph in a pair.<br />
1851 proc sfnt::GPOS::valueformat {num {suffix ""}} {<br />
1852 set res {}<br />
1853 set mask 1<br />
1854 foreach field {<br />
1855 XPlacement YPlacement XAdvance YAdvance<br />
1856 XPlaDevice YPlaDevice XAdvDevice YAdvDevice<br />
1857 reserved8 reserved9 reserved10 reserved11 reserved12 reserved13<br />
1858 reserved14 reserved15<br />
1859 } {<br />
1860 if {$num & $mask} then {<br />
1861 lappend res $field$suffix<br />
1862 }<br />
1863 set mask [expr {$mask
sfnt::GPOS:<br />
:parse_pairpos (proc)<br />
1882 }<br />
1883 }<br />
1884 set res {}<br />
1885 while {[array size A]} {<br />
1886 set n [llength $res]<br />
1887 lappend res [lappend A($n)]<br />
1888 unset A($n)<br />
1889 }<br />
1890 return $res<br />
1891 }<br />
This procedure parses a GPOS lookup type 2 subtable, returning the data as TDL.<br />
The call syntax is<br />
parse_pairpos {unit} {data} {start}<br />
where {start} is the start position in {data} <strong>of</strong> the subtable to parse, and {unit}<br />
is the font design unit (funit).<br />
1892 proc sfnt::GPOS::parse_pairpos {funit data start} {<br />
1893 binary scan $data @${start}SuSuSuSu format covOfs vf1 vf2<br />
1894 if {$format < 1 || $format > 2} then {<br />
1895 return "# Unknwon PairPos format $format.\n"<br />
1896 }<br />
1897 set leftL [coverage $data [expr {$start+$covOfs}]]<br />
1898 set valueL [concat [valueformat $vf1] [valueformat $vf2 2]]<br />
1899 set res [list adjust-table values $valueL]<br />
1900 append res " \{\n# Format $format subtable.\n"<br />
1901 if {$format == 1} then {<br />
1902 binary scan $data @${start}x8Su numPairSet<br />
1903 binary scan $data @${start}x10Su${numPairSet} <strong>of</strong>sL<br />
1904 foreach <strong>of</strong>s $<strong>of</strong>sL left $leftL {<br />
1905 if {[catch {incr <strong>of</strong>s $start}]} then {break}<br />
1906 binary scan $data @${<strong>of</strong>s}Su count<br />
1907 binary scan $data\<br />
@${<strong>of</strong>s}x2S[expr {$count*(1+[llength $valueL])}] L<br />
1909 foreach [concat right $valueL] $L {<br />
1910 set cmd [list /adjustpair [list $left]\<br />
[list [expr {$right & 0xFFFF}]]]<br />
1912 foreach var $valueL {<br />
1913 switch -glob -- $var *Device* {<br />
1914 lappend cmd *<br />
1915 } default {<br />
1916 lappend cmd [expr {$funit*[set $var]}]<br />
1917 }<br />
1918 }<br />
1919 append res $cmd \n<br />
1920 }<br />
1921 }<br />
1922 } else {<br />
1923 binary scan $data @${start}x8SuSuSuSu lOfs rOfs rows cols<br />
71
1924 set leftCL [classtable $data [expr {$start+$lOfs}]]<br />
1925 set rightCL [classtable $data [expr {$start+$rOfs}]]<br />
1926 binary scan $data\<br />
@${start}x16S[expr {$rows*$cols*[llength $valueL]}] L<br />
1928 set pos 0<br />
1929 for {set lc 0} {$lc < $rows} {incr lc} {<br />
1930 for {set rc 0} {$rc < $cols} {incr rc} {<br />
1931 set cmd [list /adjustpair [lindex $leftCL $lc]\<br />
[lindex $rightCL $rc]]<br />
1933 set nonzero 0<br />
1934 foreach var $valueL {<br />
1935 switch -glob -- $var *Device* {<br />
1936 lappend cmd *<br />
1937 set nonzero 1<br />
1938 } default {<br />
1939 lappend cmd [expr {$funit*[lindex $L $pos]}]<br />
1940 if {[lindex $L $pos]} then {set nonzero 1}<br />
1941 }<br />
1942 incr pos<br />
1943 }<br />
1944 if {$nonzero} then {append res $cmd \n}<br />
1945 }<br />
1946 }<br />
1947 }<br />
1948 append res \}\n<br />
1949 }<br />
sfnt::GPOS::parse (proc) ToDo: Correct description<br />
1950 proc sfnt::GPOS::parse {data gdict {var ""}} {<br />
1951 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
1952 binary scan $data H8SuSuSu version scriptOfs featureOfs lookupOfs<br />
1953 set res ""<br />
1954 if {$version ne "00010000"} then {<br />
1955 append res "# Version: $version" \n<br />
1956 if {![string match 0001* $version]} then {return $res}<br />
1957 }<br />
1958 set scriptD [scriptlist_by_feature $data $scriptOfs]<br />
1959 set featureD [featurelist_by_lookup $data $featureOfs $scriptD]<br />
1960 append res [<br />
1961 parse_lookuplist $data $lookupOfs [<br />
1962 list "" {} [list parse_pairpos [dict get $gdict funit]]<br />
1963 ] {<br />
1964 ""<br />
1965 "Single adjustment"<br />
1966 "Pair adjustment"<br />
1967 "Cursive attachment"<br />
1968 "MarkToBase attachment"<br />
1969 "MarkToLigature attachment"<br />
1970 "MarkToMark attachment"<br />
1971 "Context positioning"<br />
72
1972 "Chained Context positioning"<br />
1973 "Extension positioning"<br />
1974 } $featureD<br />
1975 ]<br />
1976 }<br />
sfnt::GSUB::parse (proc) The same procedure can also be used for the GSUB table.<br />
1977 namespace eval sfnt::GSUB {<br />
1978 namespace path [list [namespace parent]::GPOS]<br />
1979 }<br />
1980 proc sfnt::GSUB::parse {data gdict {var ""}} {<br />
1981 if {$var ne ""} then {uplevel 1 [list ::set $var $gdict]}<br />
1982 binary scan $data H8SuSuSu version scriptOfs featureOfs lookupOfs<br />
1983 set res ""<br />
1984 if {$version ne "00010000"} then {<br />
1985 append res "# Version: $version" \n<br />
1986 if {![string match 0001* $version]} then {return $res}<br />
1987 }<br />
1988 set scriptD [scriptlist_by_feature $data $scriptOfs]<br />
1989 set featureD [featurelist_by_lookup $data $featureOfs $scriptD]<br />
1990 append res [<br />
1991 parse_lookuplist $data $lookupOfs {} {<br />
1992 ""<br />
1993 "Single"<br />
1994 "Multiple"<br />
1995 "Alternate"<br />
1996 "Ligature"<br />
1997 "Context"<br />
1998 "Chaining Context"<br />
1999 "Extension Substitution"<br />
2000 "Reverse chaining context single"<br />
2001 } $featureD<br />
2002 ]<br />
2003 }<br />
Part III<br />
Conversion to other formats<br />
14 Generating PostScript CID<strong>Font</strong>s<br />
This section deals with the issues <strong>of</strong> defining a CID<strong>Font</strong> in a PS interpreter.<br />
2004 namespace eval sfnt::postscript\<br />
{namespace path [list [namespace parent]]}<br />
The TrueType-flavoured type <strong>of</strong> CID<strong>Font</strong> (type 2) is the easiest to generate,<br />
as CFF fonts have the CID<strong>Font</strong>-or-<strong>Font</strong> status encoded in the binary data; for<br />
73
sfnt::postscript:<br />
:write_truetype (proc)<br />
TrueType it is rather the PS wrapper that determines what kind <strong>of</strong> resource is<br />
being defined.<br />
This procedure creates a new file with the PS code to set up a TrueType font as<br />
a type 2 CID<strong>Font</strong>. The call syntax is<br />
sfnt::postscript::write_truetype {filename} {table-dict} {extra-dict}<br />
where the {filename} is the name <strong>of</strong> the file to write to and {table-dict} is a<br />
dictionary with the tables (binary data) to put in the font. This should contain<br />
the head, hhea, hmtx, maxp, loca, prep, fpgm, glyf, and cvt tables.<br />
The {extra-dict} is a dictionary <strong>of</strong> additional information that might get put<br />
in the generated file. The necessary entries are:<br />
<strong>Font</strong>Name The PS name given to the CID<strong>Font</strong> (not including slash for literate<br />
name).<br />
Additional entries considered are:<br />
<strong>Font</strong>Info PS code for putting a dictionary (which is used as <strong>Font</strong>Info dictionary)<br />
on the operand stack.<br />
2005 proc sfnt::postscript::write_truetype {fname tdict xdict} {<br />
2006 set F [open $fname w]<br />
2007 puts $F "%!PS-Adobe-3.0 Resource-CID<strong>Font</strong>"<br />
The CIDInit procset does not appear to be needed to define the CID<strong>Font</strong> as such,<br />
but it is needed to define the CMaps.<br />
2008 puts $F {%%DocumentNeededResources: procset CIDInit}<br />
2009 puts $F {%%IncludeResource: procset CIDInit}<br />
2010 puts $F "%%BeginResource: CID<strong>Font</strong> [dict get $xdict <strong>Font</strong>Name]"<br />
2011 binary scan [dict get $tdict head] @4SuSu version revision<br />
2012 puts $F [format {%%Version: %d %d} $version $revision]<br />
Now for the actual font dictionary. It begins with some rather basic entries.<br />
2013 puts $F {20 dict begin}<br />
2014 puts $F {/CID<strong>Font</strong>Type 2 def}<br />
2015 puts $F {/<strong>Font</strong>Type 42 def}<br />
2016 set fontname [dict get $xdict <strong>Font</strong>Name]<br />
2017 puts $F "/CID<strong>Font</strong>Name /$fontname def"<br />
2018 if {[dict exists $xdict <strong>Font</strong>Info]} then {<br />
2019 puts $F "/<strong>Font</strong>Info [dict get $xdict <strong>Font</strong>Info] def"<br />
2020 }<br />
Next comes the <strong>Font</strong>Matrix and <strong>Font</strong>BBox. For the latter, it is necessary to parse<br />
and convert some entries from the head table.<br />
2021 puts $F {/<strong>Font</strong>Matrix [1 0 0 1 0 0] def}<br />
2022 binary scan [dict get $tdict head] @18Sux16S4 unitsPerEm bbox<br />
2023 set bbox2 {}<br />
2024 foreach c $bbox {lappend bbox2 [expr {double($c)/$unitsPerEm}]}<br />
2025 puts $F [format {/<strong>Font</strong>BBox [%g %g %g %g] def} {*}$bbox2]<br />
74
sfnt::postscript:<br />
:write_composefonts<br />
(proc)<br />
After that comes some entries that have to do with the correspondence between<br />
CIDs and GIDs. This information is not provided at the PS level, so the two are<br />
simply set to be equal, but one might in production want to have some fallback<br />
code for the CIDMap entry in order to support PS interpreters older than version<br />
3011.<br />
2026 binary scan [dict get $tdict maxp] @4Su numGlyphs<br />
2027 puts $F "/CIDCount $numGlyphs def"<br />
2028 puts $F {/GDBytes 2 def}<br />
2029 puts $F {/CIDMap 0 def}<br />
2030 puts $F {/CIDSystemInfo > def}<br />
Next comes the big entry: the actual TrueType data. This is also used to compute<br />
values for the XUID array.<br />
2032 set blocks [sfnt::combine_tables $tdict \0\1\0\0]<br />
2033 puts -nonewline $F "/sfnts "<br />
2034 write_sfnts $F $blocks [linsert [dict keys $tdict] 0 {}] 1<br />
2035 puts $F " def"<br />
2036 set md5tok [::md5::MD5Init]<br />
2037 foreach block $blocks {::md5::MD5Update $md5tok $block}<br />
2038 binary scan [::md5::MD5Final $md5tok] I4 XUID<br />
2039 puts $F "/XUID \[42 $XUID\] def"<br />
Finally some degenerate but sometimes required entries are added, before the<br />
resource is defined.<br />
2040 puts $F {/Encoding StandardEncoding def}<br />
2041 puts $F {/CharStrings > def}<br />
2042 puts $F {/PaintType 0 def}<br />
2043 puts $F {CID<strong>Font</strong>Name currentdict end /CID<strong>Font</strong> defineresource pop}<br />
2044 puts $F {%%EndResource}<br />
But this is not the end <strong>of</strong> the story. Having a CID<strong>Font</strong> is nice, but it is better to<br />
also have some composite fonts defined on top <strong>of</strong> it.<br />
2045 write_composefonts $F $fontname<br />
After that, we’re quite done.<br />
2046 puts $F {%%EOF}<br />
2047 close $F<br />
2048 }<br />
This procedure writes PS code defining two composite fonts on top <strong>of</strong> a CID<strong>Font</strong>.<br />
The call syntax is<br />
write_composefont {channel} {name}<br />
and there is no particular return value; the code is written to the {channel}.<br />
More concretely, the PS code written presumes a CID<strong>Font</strong> named {name} (no<br />
initial slash) has been defined, and will define the following five resources:<br />
75
sfnt::postscript:<br />
:write_identity_cmap<br />
(proc)<br />
CMap Identity-H<br />
<strong>Font</strong> (Type 0) 〈name〉-Identity-H<br />
CID<strong>Font</strong> 〈name〉-rlap<br />
CMap Shifted201C<br />
<strong>Font</strong> (Type 0) 〈name〉-rlap-Shifted201C<br />
—though the CMaps are only defined if they don’t already exist.<br />
2049 proc sfnt::postscript::write_composefonts {F fontname} {<br />
Technical complication: Some PS interpreters appear to have composefont in the<br />
CIDInit procset rather than in the system dictionary. For that reason, this procset<br />
is left on the dictionary stack throughout the definition <strong>of</strong> composite fonts.<br />
2050 puts $F {/CIDInit /ProcSet findresource begin}<br />
2051 write_identity_cmap $F<br />
2052 puts $F "%%BeginResource: font $fontname-Identity-H"<br />
2053 puts $F [format {/%s-Identity-H /Identity-H [/%s] composefont pop}\<br />
$fontname $fontname]<br />
2055 puts $F {%%EndResource}<br />
The following is a classical method <strong>of</strong> modifying a PS font: find its font dictionary,<br />
copy everything except the FID entry from it into a new dictionary, make changes<br />
there, and finally define the new font. Though some operators accept (or even<br />
require) the dictionary instead <strong>of</strong> the font name, it is still necessary to define the<br />
thing under some name in order to make it a proper font or CID<strong>Font</strong>.<br />
2056 puts $F "%%BeginResource: CID<strong>Font</strong> $fontname-rlap"<br />
2057 puts $F "/$fontname /CID<strong>Font</strong> findresource"<br />
2058 puts $F {dup length 1 add dict begin}<br />
2059 puts $F {{ 1 index /FID ne {def} {pop pop} ifelse } forall}<br />
2060 puts $F {/CDevProc {pop 10 8 roll pop pop 0 0 10 2 roll} def}<br />
2061 puts $F "/$fontname-rlap currentdict end /CID<strong>Font</strong> defineresource pop"<br />
2063 puts $F {%%EndResource}<br />
2064 write_shifted_cmap $F 201C<br />
2065 puts $F "%%BeginResource: font $fontname-rlap-Shifted201C"<br />
2066 puts $F [format\<br />
{/%s-rlap-Shifted201C /Shifted201C [/%s-rlap] composefont pop}\<br />
$fontname $fontname]<br />
2069 puts $F {%%EndResource}<br />
2070 puts $F {end}<br />
2071 }<br />
This procedure writes PS code defining the Identity-H CMap to a channel. The<br />
call syntax is simply<br />
write_identity_cmap {channel}<br />
and there is no particular return value.<br />
The code written takes care to check whether the CMap in question already<br />
exists, and avoids redefining it in that case. It presumes that the CIDInit procset<br />
is present on the dictionary stack.<br />
2072 proc sfnt::postscript::write_identity_cmap {F} {<br />
76
sfnt::postscript:<br />
:write_shifted_cmap<br />
(proc)<br />
2073 puts $F {%%BeginResource: CMap Identity-H}<br />
2074 puts $F "/Identity-H /CMap resourcestatus {pop pop} \{"<br />
2075 puts $F {5 dict begin begincmap}<br />
2076 puts $F {/CMapType 1 def}<br />
2077 puts $F {/CMapName /Identity-H def}<br />
2078 puts $F {/CIDSystemInfo > def}<br />
2080 puts $F {1 begincodespacerange endcodespacerange}<br />
2081 puts $F {0 usefont}<br />
2082 puts $F {1 begincidrange 0 endcidrange}<br />
2083 puts $F {endcmap CMapName currentdict end}<br />
2084 puts $F {/CMap defineresource pop}<br />
2085 puts $F "\} ifelse"<br />
2086 puts $F {%%EndResource}<br />
2087 }<br />
This procedure writes PS code defining the CMap Shifted〈hex〉 to a channel. The<br />
call syntax is<br />
write_shifted_cmap {channel} {hex}<br />
where {hex} is the (typically four digit) hexadecimal number which should become<br />
the character code <strong>of</strong> glyph 0. There is no particular return value.<br />
The code written takes care to check whether the CMap in question already<br />
exists, and avoids redefining it in that case. It presumes that the CIDInit procset<br />
is present on the dictionary stack.<br />
2088 proc sfnt::postscript::write_shifted_cmap {F hex} {<br />
2089 scan $hex %x num<br />
2090 set name "Shifted$hex"<br />
2091 puts $F "%%BeginResource: CMap $name"<br />
2092 puts $F "/$name /CMap resourcestatus {pop pop} \{"<br />
2093 puts $F {5 dict begin begincmap}<br />
2094 puts $F {/CMapType 1 def}<br />
2095 puts $F "/CMapName /$name def"<br />
2096 puts $F {/CIDSystemInfo > def}<br />
2098 puts $F {1 begincodespacerange endcodespacerange}<br />
2099 puts $F {0 usefont}<br />
2100 puts $F {2 begincidrange}<br />
2101 puts $F\<br />
[format { %d} [expr {$num-1}] [expr {0x10000-$num}]]<br />
2103 puts $F [format { 0} $num]<br />
2104 puts $F {endcidrange}<br />
2105 puts $F {endcmap CMapName currentdict end}<br />
2106 puts $F {/CMap defineresource pop}<br />
2107 puts $F "\} ifelse"<br />
2108 puts $F {%%EndResource}<br />
2109 }<br />
77
sfnt::postscript:<br />
:write_sfnts (proc)<br />
This procedure writes PS code for an sfnts array <strong>of</strong> strings that together contain<br />
a TrueType font. The call syntax is<br />
write_sfnts {channel} {blocks} {legend} {comment?}<br />
where {channel} is the channel to write to, {blocks} are the blocks <strong>of</strong> binary data<br />
(one for the file header, and one for each table) to write, and {legend} is a list<br />
<strong>of</strong> tags (the file header should be represented by an empty string) identifying the<br />
contents <strong>of</strong> each block. The {legend} is needed to know where to split the glyf<br />
table (if necessary).<br />
The {comment?} is a boolean signalling whether to put in comments between<br />
the blocks, specifying what each new block contains. These might disrupt functionality<br />
if the font is to be made resident in the printer, but should not present<br />
any problem if the font is embedded in a document.<br />
The list <strong>of</strong> blocks is processed twice. On the first run, the size constraints are<br />
enforced and the legends are turned into full comments. On the second run, the<br />
material is written out.<br />
2110 proc sfnt::postscript::write_sfnts {F blocks legend commentQ} {<br />
2111 set bL {}<br />
2112 set cL {}<br />
2113 foreach block $blocks tag $legend {<br />
2114 if {[string length $block]
multiple <strong>of</strong> 2 bytes long.<br />
2128 set startPos 0; set startGlyph 0<br />
2129 while {[string length $block] - $startPos > 65534} {<br />
To protect against the remote possibility <strong>of</strong> someone creating a TrueType glyph<br />
too long to fit in a PS string (or, more likely, corrupted data), there is first a check<br />
that the beginning <strong>of</strong> the next glyph is not too far away.<br />
2130 if {[lindex $locaL $startGlyph+1] - $startPos > 65534} {<br />
2131 lappend bL\<br />
[string range $block $startPos $startPos+65533]<br />
2133 lappend cL\<br />
[format {Freaky! Glyph %d is longer than 65534 bytes}\<br />
$startGlyph]<br />
2135 incr startPos 65534<br />
2136 continue<br />
2137 }<br />
The binary search proceeds with upper pointing at a glyph boundary too far away<br />
and lower pointing at one which is close enough.<br />
2138 set lower $startGlyph; set upper $numGlyphs<br />
2139 while {$upper-$lower>1} {<br />
2140 set middle [expr {($lower+$upper)/2}]<br />
2141 if {[lindex $locaL $middle]-$startPos > 65534} then {<br />
2142 set upper $middle<br />
2143 } else {<br />
2144 set lower $middle<br />
2145 }<br />
2146 }<br />
2147 lappend bL\<br />
[string range $block $startPos [lindex $locaL $lower]-1]<br />
2149 lappend cL [format {’glyf’ table, glyphs %d..%d}\<br />
$startGlyph [expr {$lower-1}]]<br />
2151 set startPos [lindex $locaL $lower]<br />
2152 set startGlyph $lower<br />
2153 }<br />
2154 lappend bL [string range $block $startPos end]<br />
2155 lappend cL [format {’glyf’ table, glyphs %d..%d} $startGlyph\<br />
[expr {$numGlyphs-1}]]<br />
2157 } else {<br />
If some table other than glyf has become too long, it is simply split in blocks <strong>of</strong><br />
65532 bytes.<br />
2158 for {set startPos 0} {$startPos
sfnt::postscript:<br />
:togid (slave)<br />
sfnt::postscript:<br />
:tdl_togid (proc)<br />
With that over, writing the data out isn’t so difficult.<br />
2166 puts -nonewline $F "\[ "<br />
2167 foreach block $bL cmt $cL {<br />
2168 if {$commentQ && $cmt ne ""} then {<br />
2169 puts -nonewline $F "% $cmt:\n
-progress A command prefix, which is called to report progress (on a per-target<br />
basis). Its call syntax is merely<br />
〈value〉 {message}<br />
and there is no particular return value.<br />
The return value <strong>of</strong> tdl_togid is the list <strong>of</strong> those {target list} elements that were<br />
not matched to any sfnt-font. This makes it possible (at least in principle) to<br />
chain TDL calls.<br />
2189 proc sfnt::postscript::tdl_togid {targetL thefile script args} {<br />
2190 set Opt(-names) {}<br />
2191 array set Opt $args<br />
2192 set nexttarget 0<br />
2193 togid eval $script<br />
2194 lrange $targetL $nexttarget end<br />
2195 }<br />
targetL (local var.)<br />
As usual, the underlying operations are expected to access certain local variables<br />
<strong>of</strong> tdl_togid using upvar. The targetL variable holds the list <strong>of</strong> targets, and the<br />
nexttarget (local var.) nexttarget variable holds the index into targetL <strong>of</strong> the next target. The thefile<br />
thefile (local var.) variable holds source file channel. Finally, the Opt array holds the options that<br />
Opt (local array) were passed; one should therefore inspect the Opt(-names) and Opt(-progress)<br />
entries (the latter <strong>of</strong> which need not be defined).<br />
sfnt::postscript:<br />
:psgid_unknown (proc)<br />
sfnt::postscript:<br />
:psgid_sfnt-font (proc)<br />
As usual, the unknown handler recurses over the bodies <strong>of</strong> XML-style commands,<br />
when present. Its call syntax is<br />
sfnt::postscript::psgid_unknown {slave} {name} {argument} ∗<br />
where {slave} is the name <strong>of</strong> the slave interpreter command to call.<br />
2196 namespace eval sfnt::postscript {<br />
2197 proc psgid_unknown {slave name args} {<br />
2198 if {[<br />
2199 regexp {^[[:alpha:]_:][[:alnum:]_:.-]*$} $name<br />
2200 ] && [llength $args]%2 == 1} then {<br />
2201 uplevel 1 [list $slave eval [lindex $args end]]<br />
tailcall would fit nicely here, but it’s not worth bumping the Tcl version requirement<br />
over.<br />
2202 }<br />
2203 }<br />
2204 togid alias unknown [namespace which psgid_unknown]\<br />
[namespace which togid]<br />
2206 }<br />
Output is generated (and a target is consumed) when a sfnt-font element is<br />
encountered.<br />
2207 proc sfnt::postscript::psgid_sfnt-font {args} {<br />
2208 upvar 1 targetL targetL nexttarget nexttarget thefile F Opt Opt<br />
81
2209 if {[llength $args]%2} then {<br />
2210 set tableL [TDLtoXML::main [lindex $args end]]<br />
2211 set args [lreplace $args end end]<br />
2212 } else {<br />
2213 set tableL {}<br />
2214 }<br />
2215 set tableD [fetch_tables $F $tableL\<br />
{head hhea hmtx maxp loca prep fpgm glyf {cvt } name}]<br />
2217 set target [lindex $targetL $nexttarget]<br />
2218 if {[llength $Opt(-names)] > $nexttarget} then {<br />
2219 set psfontname [lindex $Opt(-names) $nexttarget]<br />
2220 } else {<br />
A somewhat tricky issue is what to name the PS font (when the -names option<br />
is not used as an override). The following sources are considered, in order <strong>of</strong><br />
increasing priority:<br />
1. The target file name (tail minus suffix).<br />
2. The sfnt-font attribute name.<br />
3. Item 6 (Postscript name) in the name table.<br />
Regardless <strong>of</strong> source, any forbidden characters are washed out <strong>of</strong> the name before<br />
it is used.<br />
2221 set psfontname [file rootname [file tail $target]]<br />
2222 if {[dict exists $args name]} then {<br />
2223 set psfontname [dict get $args name]<br />
2224 }<br />
2225 if {[dict exists $tableD name]} then {<br />
2226 foreach item\<br />
[TDLtoXML::main [name::parse [dict get $tableD name] {}]] {<br />
2228 if {[lindex $item 0] ne "nameid"} then {continue}<br />
2229 if {[dict get [lindex $item 1] code] != 6} then {continue}<br />
2230 foreach item [lindex $item 2] {<br />
2231 if {[lindex $item 0] eq "TDL:cmd" &&\<br />
[dict get [lindex $item 1] name] eq "/namestr"} then {<br />
2234 set psfontname [lindex $item 2 0 2 0 1]<br />
That somewhat opaque lindex reflects the actual structure <strong>of</strong> the data after conversion<br />
to an XML-tree. The sought string is character data, so it is element 1 <strong>of</strong><br />
a #text list. This character data is the only child <strong>of</strong> a TDL:arg element, so that<br />
prefixes 2 0. The TDL:arg element is itself the first child <strong>of</strong> the TDL:cmd element<br />
currently in item, so this prefixes another 2 0.<br />
2235 break<br />
2236 }<br />
2237 }<br />
2238 break<br />
2239 }<br />
2240 }<br />
2241 }<br />
82
2242 set extraD [dict create <strong>Font</strong>Name [join [<br />
2243 regexp -all -inline {[!-$&’*-.0-;=?-Z\\^-z|~]+} $psfontname<br />
2244 ] ""]]<br />
2245 dict unset tableD name<br />
2246 if {$target eq ""} then {<br />
2247 set msg "No target specified for /[dict get $extraD <strong>Font</strong>Name]."<br />
2248 } elseif {[dict exists $tableD glyf]} then {<br />
2249 write_truetype $target $tableD $extraD<br />
2250 set msg "Wrote /[dict get $extraD <strong>Font</strong>Name] to $target."<br />
2251 } else {<br />
2252 set msg "No TrueType outlines to write to $target."<br />
2253 }<br />
2254 if {[info exists Opt(-progress)]} then {<br />
2255 {*}$Opt(-progress) $msg<br />
2256 }<br />
2257 incr nexttarget<br />
2258 }<br />
2259 namespace eval sfnt::postscript {<br />
2260 togid alias sfnt-font [namespace which psgid_sfnt-font]<br />
2261 }<br />
15 Conversion to fontinst metrics<br />
The large-scale structure <strong>of</strong> the fontinstification operation is similar to that <strong>of</strong> the<br />
psgid operation: the TOC <strong>of</strong> the input file is processed, a list <strong>of</strong> output names<br />
must be supplied, and it is the encounter <strong>of</strong> an sfnt-font element that triggers<br />
the generation proper.<br />
sfnt::make_mtx (proc) The main controller is the make_mtx procedure. It has the call syntax<br />
15.1 Gathering glyph information<br />
Many pieces <strong>of</strong> information which occur together in fontinst metric files are spread<br />
out over several sfnt tables, so before anything can be written, it is necessary<br />
to collect these pieces. In particular one must determine the glyph names, since<br />
these are what will be used as identifiers. The basic idea is (as usual) to evaluate<br />
the body <strong>of</strong> an sfnt-font element in a separate interpreter, and define the<br />
commands there to store away what useful information they may hold in a huge<br />
nested dictionary, which will then be the gathering <strong>of</strong> font information.<br />
sfnt::gather (proc) The command to call to produce a gathering is sfnt::gather, which has the call<br />
syntax<br />
gathering (local var.)<br />
sfnt::gather {script}<br />
and returns the gathering dictionary produced from the {script}.<br />
This dictionary is stored in the gathering local variable in the context from<br />
83
sfnt::gather:<br />
:unknown (proc)<br />
which the script is evaluated in the helper interpreter.<br />
2262 proc sfnt::gather {script} {<br />
2263 set gathering [dict create]<br />
2264 gather::theinterp eval $script<br />
2265 return $gathering<br />
2266 }<br />
Most things related to the implementation <strong>of</strong> gather resides in the sfnt::gather<br />
namespace.<br />
2267 namespace eval sfnt::gather {}<br />
The unknown handler follows the pattern <strong>of</strong> sfnt::postscript::psgid_unknown,<br />
by having the slave interpreter command name as an extra argument.<br />
2268 proc sfnt::gather::unknown {slave name args} {<br />
2269 if {[<br />
2270 regexp {^[[:alpha:]_:][[:alnum:]_:.-]*$} $name<br />
2271 ] && [llength $args]%2 == 1} then {<br />
2272 uplevel 1 [list $slave eval [lindex $args end]]<br />
2273 }<br />
2274 }<br />
sfnt::gather::theinterp The gathering interpreter, as already referenced above, is called theinterp.<br />
(slave interp.) 2275 namespace eval sfnt::gather {<br />
2276 interp create -safe [list [namespace current]::theinterp]<br />
2277 theinterp hide namespace<br />
2278 theinterp invokehidden namespace delete ::<br />
2279<br />
2281 }<br />
theinterp alias unknown [namespace which unknown]\<br />
[namespace which theinterp]<br />
fontinst (gathering entry)<br />
Dictionary entries whose names are GIDs (sensible interpretation: whose<br />
names consist entirely <strong>of</strong> digits) are themselves dictionaries with information about<br />
that particular glyph. Notable entries are:<br />
bbox Bounding box (a four-element list {left} {bottom} {right} {top}).<br />
ccode Hex digits <strong>of</strong> Unicode code point. Version 4.0 post tables provide these<br />
instead <strong>of</strong> names.<br />
CID CID value from CFF font. Would produce a distinct glyph name if combined<br />
with registry and ordering (from /ROS element).<br />
name PS-style glyph name.<br />
width Advance width.<br />
<strong>Font</strong>inst integers are put in the fontinst sub-dictionary under their usual names.<br />
84
setint command<br />
/dontsetint command<br />
sfnt::gather:<br />
:glyphdatum (proc)<br />
/glyphname command<br />
/glyphccode command<br />
/glyphCID command<br />
/glyphbbox command<br />
sfnt::gather:<br />
:glyphbbox (proc)<br />
/glyphwidth command<br />
sfnt::gather:<br />
:glyphwidth (proc)<br />
/ROS command<br />
sfnt::gather::ROS ROS (gathering (proc) entry)<br />
Recording a /setint (or /dontsetint) can be a direct alias for dict set. (This<br />
implies last definition wins semantics rather than the \setint first definition wins<br />
semantics, but this difference is not expected to be relevant here.)<br />
2282 sfnt::gather::theinterp alias /setint dict set gathering fontinst<br />
2283 sfnt::gather::theinterp alias /dontsetint dict set gathering fontinst<br />
The /glyphname command, on the other hand, does not quite have the arguments<br />
in the order dict set would require. (Sometimes one misses features that TEX’s<br />
macros provide.) Hence it is useful to define a generic glyphdatum command with<br />
the syntax<br />
glyphdatum {key} {GID} {datum}<br />
that sets the {key} entry in {GID}’s subdictionary <strong>of</strong> the gathering to {datum}.<br />
2284 namespace eval sfnt::gather {<br />
2285 proc glyphdatum {key gid datum} {<br />
2286 uplevel 1 [list ::dict set gathering $gid $key $datum]<br />
2287 }<br />
2288 theinterp alias /glyphname [namespace which glyphdatum] name<br />
The same principle can be used for /glyphccode and /glyphCID, which may be<br />
other sources <strong>of</strong> glyph names.<br />
2289 theinterp alias /glyphccode [namespace which glyphdatum] ccode<br />
2290 theinterp alias /glyphCID [namespace which glyphdatum] CID<br />
2291 }<br />
The same glyphdatum proc cannot be used for the /glyphbbox command, as it<br />
needs to combine four arguments into a list.<br />
2292 namespace eval sfnt::gather {<br />
2293 proc glyphbbox {gid left bottom right top} {<br />
2294 uplevel 1 [list ::dict set gathering $gid bbox\<br />
[list $left $bottom $right $top]]<br />
2296 }<br />
2297 theinterp alias /glyphbbox [namespace which glyphbbox]<br />
2298 }<br />
Similarly the /glyphwidth command needs a separate helper proc, since it may<br />
come with an left sidebearing x-coordinate. That optional argument is (as usual)<br />
ignored.<br />
2299 namespace eval sfnt::gather {<br />
2300 proc glyphwidth {gid width {lsx 0}} {<br />
2301 uplevel 1 [list ::dict set gathering $gid width $width]<br />
2302 }<br />
2303 theinterp alias /glyphwidth [namespace which glyphwidth]<br />
2304 }<br />
In order to make sense <strong>of</strong> a CID, it is necessary to know the registry and ordering.<br />
The /ROS data are stored as a three element list in the ROS gathering entry.<br />
85
<strong>Font</strong>BBox command<br />
/Panose command<br />
/BlueValues command<br />
/OtherBlues command<br />
/FamilyBlues command<br />
/FamilyOtherBlues<br />
command<br />
/StemSnapH command<br />
/StemSnapV command<br />
2305 namespace eval sfnt::gather {<br />
2306 proc ROS {registry ordering supplement} {<br />
2307 uplevel 1 [list ::dict set gathering ROS\<br />
[list $registry $ordering $supplement]]<br />
2309 }<br />
2310 theinterp alias /ROS [namespace which ROS]<br />
2311 }<br />
Another class <strong>of</strong> element command syntaxes is those where one wants the gathering<br />
element to be the list <strong>of</strong> the element arguments. It turns out dict lappend works<br />
well as “helper” for this.<br />
2312 apply {args {<br />
2313 foreach name $args {<br />
2314 theinterp alias /$name dict lappend gathering $name<br />
2315 }<br />
2316 } sfnt::gather} <strong>Font</strong>BBox Panose \<br />
Two elements which can be handled this way are <strong>Font</strong>BBox and Panose, although<br />
this implies that one must take care to only use the first four and ten respectively<br />
elements in these entries, as duplicates <strong>of</strong> these commands could produce longer<br />
entries.<br />
The vertical alignment line positions (“blue values”) elements BlueValues,<br />
OtherBlues, FamilyBlues, and FamilyOtherBlues more naturally fit into the<br />
lappend pattern.<br />
2317 BlueValues OtherBlues FamilyBlues FamilyOtherBlues \<br />
As do StemSnapH, StemSnapV, and Base<strong>Font</strong>Blend.<br />
2318 StemSnapH StemSnapV Base<strong>Font</strong>Blend<br />
/Base<strong>Font</strong>Blend command<br />
/<strong>Font</strong>Name command Speking <strong>of</strong> Base<strong>Font</strong>: it, <strong>Font</strong>Name, and FullName are three strings which can<br />
/FullName command usably be saved away in separate entries.<br />
/Base<strong>Font</strong> command<br />
2319 namespace eval sfnt::gather {<br />
2320 theinterp alias /<strong>Font</strong>Name dict set gathering <strong>Font</strong>Name<br />
2321 theinterp alias /FullName dict set gathering FullName<br />
2322 theinterp alias /Base<strong>Font</strong> dict set gathering Base<strong>Font</strong><br />
2323 }<br />
15.2 Generating metrics<br />
With the basic information gathered<br />
86
15.3 Overall control<br />
Part IV<br />
Putting it all together<br />
16 The program<br />
16.1 Generalities<br />
sfntutil (ensemble) The command-line program sfntutil maps directly to an ensemble <strong>of</strong> the same<br />
name. Something is a subcommand <strong>of</strong> the program if and only if it is a subcommand<br />
<strong>of</strong> this ensemble.<br />
eval subcommand<br />
source subcommand<br />
2324 namespace eval sfntutil {<br />
2325 namespace ensemble create<br />
The traditional rule that things beginning with lower case letters are public and<br />
everything else private is applied. Hence you can add a subcommand to the<br />
program merely by defining it in the sfntutil namespace.<br />
2326 namespace export {[a-z]*}<br />
To provide hackability, there are two subcommands<br />
sfntutil eval {script} {subcommand} ? {arg} ∗<br />
sfntutil source {file} {subcommand} ? {arg} ∗<br />
that eval and source a script and file respectively, after which they recursively<br />
call sfntutil to interpret the rest <strong>of</strong> the arguments as another subcommand.<br />
2327 proc eval {script args} {<br />
2328 uplevel #0 $script<br />
2329 if {[llength $args]} then {[namespace current] {*}$args}<br />
2330 }<br />
2331 proc source {fname args} {<br />
2332 uplevel #0 [list ::source $fname]<br />
2333 if {[llength $args]} then {[namespace current] {*}$args}<br />
2334 }<br />
2335 }<br />
Note that defining commands with these names mean Tcl’s core eval and source<br />
command cannot be called from within this namespace without qualification. For<br />
that reason, one might want to put the actual implementation <strong>of</strong> a command in<br />
some other namespace, and only place an alias to it here.<br />
eval subcommand Another basic subcommand is the help command. This is mostly another ensemble,<br />
but since ensembles cannot do something sensible when the subcommand<br />
name is missing, a small wrapper proc is needed to handle that situation.<br />
2336 proc sfntutil::help {{topic ""} args} {<br />
2337 if {$topic ne ""} then {<br />
87
2338 Help $topic {*}$args<br />
2339 } else {<br />
2340 puts stderr {Write: sfntutil help TOPIC; for example}<br />
2341 puts stderr\<br />
{ sfntutil help commands -- to get a list <strong>of</strong> commands}<br />
2343 puts stderr\<br />
{ sfntutil help topics -- to get a list <strong>of</strong> help topics}<br />
2345 puts stderr\<br />
{ sfntutil help COMMAND -- to get help for COMMAND}<br />
2347 }<br />
2348 }<br />
sfntutil::Help (ensemble) One important point for the Help ensemble is that it should have an -unknown<br />
handler. Another point is that the namespace is in all lower case.<br />
2349 namespace eval sfntutil::help {<br />
2350 namespace ensemble create -command [namespace parent]::Help -unknown\<br />
[list ::apply {{cmdns helpns helpcmd topic args} {<br />
The -unknown handler is implemented as an anonymous procedure executing in<br />
the :: namespace, just to avoid bugs due to core commands unexpectedly having<br />
namesakes in the sfntutil::help namespace. This means it is convenient to<br />
embed the fully qualified names <strong>of</strong> the sfntutil and sfntutil::help namespaces<br />
as arguments in the handler prefix (see also below).<br />
The first thing the handler should do is admit it doesn’t have any help.<br />
2352 puts stderr "No help for $topic."<br />
Then it’s best to check whether the specified topic happens to be a command.<br />
2353 if {[string match *::* $topic] ||\<br />
[namespace which ${cmdns}::${topic}] eq ""} then {return list}<br />
2356 set match 0<br />
2357 foreach pattern [namespace inscope $cmdns {namespace export}] {<br />
2359 if {[string match $pattern $topic]} then {set match 1}<br />
2360 }<br />
2361 if {!$match} then {return {list}}<br />
2362 puts stderr "It is a command, though."<br />
If it is a proc, then we might even be able to produce a guess at what its syntax<br />
is, by looking at its argument specifiers.<br />
2363 if {![llength [info procs\<br />
${cmdns}::[string map {* {\*} ? {\?} \[ {\[}} $topic]]]} then\<br />
{return {list}}<br />
2366 set syntax [list sfntutil $topic]<br />
2367 foreach arg [info args ${cmdns}::${topic}] {<br />
2368 if {[info default ${cmdns}::${topic} $arg default]} then {<br />
2369 lappend syntax ?$arg?<br />
2370 } else {<br />
2371 lappend syntax $arg<br />
2372 }<br />
2373 }<br />
2374 if {[lindex $syntax end] eq "args"} then {<br />
88
sfntutil::help:<br />
:commands (proc)<br />
sfntutil::help:<br />
:description (array)<br />
2375 lset syntax end "?arg"<br />
2376 lappend syntax "...?"<br />
2377 }<br />
2378 puts stderr "Surface syntax is: $syntax"<br />
Either way, the default handler must return a command prefix that does effectively<br />
nothing, so list comes in handy.<br />
2379 return {list}<br />
2380 } ::} [namespace parent] [namespace current]]<br />
2381 namespace export {[a-z]*}<br />
2382 }<br />
OK, it was promised above that the commands help topic would return a list <strong>of</strong><br />
all commands, so we’d better define sfntutil::help::commands as something<br />
which does precisely that.<br />
However, the above sort-<strong>of</strong> hints that the commands list should contain short descriptions<br />
<strong>of</strong> the various commands, and that information must be stored somewhere.<br />
A simple implementation is to keep an array in the sfntutil::help<br />
namespace which is indexed by command name and whose entries contains the<br />
strings.<br />
2383 namespace eval sfntutil::help {<br />
2384 variable description<br />
2385 set description(help) "Get help at the command line"<br />
2386 set description(eval) "Evaluate arg as Tcl script, then execute\<br />
another sfntutil command"<br />
2388 set description(source) "Source a Tcl script, then execute another\<br />
sfntutil command"<br />
2390 }<br />
The commands procedure itself first builds the list <strong>of</strong> commands, and then<br />
formats the table. It is not assumed that the namespace export patterns will<br />
stay the same; someone might need to add specific commands not beginning with<br />
a lower case letter.<br />
2391 proc sfntutil::help::commands {} {<br />
2392 set ns [namespace parent]<br />
2393 set cmdL {}<br />
2394 foreach pat [namespace eval $ns {namespace export}] {<br />
2395 foreach cmd [info commands ${ns}::${pat}] {<br />
2396 lappend cmdL [namespace tail $cmd]<br />
2397 }<br />
2398 }<br />
2399 set cmdL [lsort -unique $cmdL]<br />
2400 set width 0<br />
2401 foreach cmd $cmdL {<br />
2402 if {[string length $cmd] > $width} then {<br />
2403 set width [string length $cmd]<br />
2404 }<br />
2405 }<br />
89
sfntutil::help:<br />
:topics (proc)<br />
sfntutil::stuff:<br />
:openfont (proc)<br />
2406 puts stderr "Available sfntutil subcommands:"<br />
2407 variable description<br />
2408 foreach cmd $cmdL {<br />
2409 puts stderr [format { %-*s -- %s} $width $cmd [expr {<br />
2410 [info exists description($cmd)] ? $description($cmd)<br />
2411 : "no description"<br />
2412 }]]<br />
2413 }<br />
2414 }<br />
The topics command is pretty much the same idea, but it looks in the help namespace<br />
instead. Also, it is simpler since it doesn’t bother with tabular formatting or<br />
descriptions.<br />
2415 proc sfntutil::help::topics {} {<br />
2416 set ns [namespace current]<br />
2417 set cmdL {}<br />
2418 foreach pat [namespace eval $ns {namespace export}] {<br />
2419 foreach cmd [info commands ${ns}::${pat}] {<br />
2420 lappend cmdL [namespace tail $cmd]<br />
2421 }<br />
2422 }<br />
2423 puts stderr "Available help topics:\<br />
[join [lsort -unique $cmdL] {, }]"<br />
2424 }<br />
2425 namespace eval sfntutil::stuff {}<br />
This procedure opens a font file and reads its table <strong>of</strong> contents, returning that and<br />
the open channel handle to the caller. If the file could not be interpreted, then a<br />
check is made <strong>of</strong> whether the data might be in the resource-fork, and advice on<br />
the matter is written to stderr.<br />
The call syntax is<br />
openfont {filename}<br />
and the return value is a list<br />
{channel} {TDL}<br />
where the {TDL} is the contents in TDL format.<br />
2426 proc sfntutil::stuff::openfont {fname} {<br />
2427 set F [open $fname rb]<br />
2428 if {![catch {<br />
2429 sfnt::parse_file_header $F<br />
2430 } res]} then {<br />
2431 return [list $F $res]<br />
2432 }<br />
2433 set attrD [file attributes $fname]<br />
2434 if {[dict exists $attrD -rsrclength] &&\<br />
[dict get $attrD -rsrclength] > 0} then {<br />
90
sfntutil::stuff:<br />
:stdout (proc)<br />
sfntutil::stuff:<br />
:alloptions (proc)<br />
2436 puts stderr "Could not find font data in file $fname."<br />
2437 puts stderr "It has a resource fork though, which I cannot open\<br />
directly;"<br />
2439 puts stderr "the font data might be in there. To make it\<br />
accessible, do as follows:\n"<br />
2441 puts stderr " 1. Mount a writable single-fork file system.\n"<br />
2442 puts stderr " If you don’t have any physical single-fork file\<br />
system at hand"<br />
2444 puts stderr " (or can’t tell, because you’re not sure what it\<br />
means), then"<br />
2446 puts stderr " a writable disk image will do the trick. Use the\<br />
Disk Utility"<br />
2448 puts stderr " to create one, selecting MS-DOS or Unix as file\<br />
system.\n"<br />
2450 puts stderr " 2. In the Finder, drag $fname unto the other\<br />
filesystem.\n"<br />
2452 puts stderr " 3. Try the sfntutil command again, on the newly\<br />
created file"<br />
2454 puts stderr " ._[file tail $fname] in the other filesystem.\n"<br />
2455 }<br />
2456 return [list $F [list /error $res]]<br />
2457 }<br />
This procedure is used for sending data to stdout. It handles reconfiguring the<br />
channel to use UTF-8. It also only writes something in the 〈cmdline〉 setting,<br />
but always returns its argument as given. This is easier on the eyes when calling<br />
sfntutil as a Tcl command.<br />
The call syntax is<br />
sfntutil::stuff::stdout {data}<br />
2458 proc sfntutil::stuff::stdout {data} {<br />
2459 〈∗cmdline〉<br />
2460 fconfigure stdout -encoding utf-8<br />
2461 puts stdout $data<br />
2462 〈/cmdline〉<br />
2463 return $data<br />
2464 }<br />
This is Yet Another option parser, since the option syntax <strong>of</strong> the one in tcllib (i.e.,<br />
cmdline) wasn’t quite what I wanted. . . With this, options are entered on the form<br />
-- ? 〈name〉(=〈value〉) ?<br />
and they are returned in an array indexed by 〈name〉. A missing 〈value〉 (which<br />
would typically happen for boolean switches) is treated as the empty string. Note<br />
that each option is precisely one word.<br />
The two “empty” options ‘-’ and ‘--’ have special interpretation. ‘-’ quotes the<br />
following argument from being interpreted as an option. ‘--’ quotes all subsequent<br />
arguments from being interpreted as options.<br />
91
sfntutil::help:<br />
:options (alias)<br />
The command which does this has the syntax<br />
stuff::alloptions {target-array} {args-var} {optlist}<br />
where {target-array} is the name <strong>of</strong> the array into which the parsed option values<br />
should be stored, {args-var} is the name <strong>of</strong> the variable (which will be modified)<br />
holding the list <strong>of</strong> arguments to parse out all options from, and {optlist} is the<br />
list <strong>of</strong> allowed option 〈name〉s (i.e., without dashes).<br />
2465 proc sfntutil::stuff::alloptions {arrname argsname optlist} {<br />
2466 upvar 1 $arrname Opt $argsname argv<br />
2467 set newL {}<br />
2468 set quoted 0<br />
2469 set pos 0; foreach arg $argv {<br />
2470 if {$quoted} then {<br />
2471 lappend newL $arg<br />
2472 incr quoted -1<br />
2473 } else {<br />
2474 switch -glob -- $arg "-" {<br />
2475 set quoted 1<br />
2476 } "--" {<br />
2477 set quoted -1<br />
2478 } "-*" {<br />
2479 regexp {^--?([^=]*)=?(.*)$} $arg "" name value<br />
2480 if {$name ni $optlist} then {<br />
2481 puts stderr "Unknown option -$name ignored."<br />
2482 } else {<br />
2483 set Opt($name) $value<br />
2484 }<br />
2485 } default {<br />
2486 lappend newL $arg<br />
2487 }<br />
2488 }<br />
2489 }<br />
2490 set argv $newL<br />
2491 }<br />
Since the above is going to be the standard option processor in sfntutil, it warrants<br />
a help topic.<br />
2492 interp alias {} sfntutil::help::options {} puts stderr {<br />
2493 Options <strong>of</strong> sfntutil subcommands usually come at the end,<br />
2494 i.e., after the subcommand and its direct argument.<br />
2495 Each subcommand has its own set <strong>of</strong> options.<br />
2496 Unknown options are warned about but ignored.<br />
2497<br />
2498 Every option is precisely one command word.<br />
2499 An option has one <strong>of</strong> the following forms:<br />
2500 -NAME<br />
2501 --NAME<br />
2502 -NAME=VALUE<br />
92
2503 --NAME=VALUE<br />
2504 The forms with one dash are equivalent to those with two dashes.<br />
2505 Every option can take a VALUE, but those that are boolean switches<br />
2506 will ignore it.<br />
2507<br />
2508 The two forms - and -- can be used to quote subsequent words<br />
2509 from being interpreted as options.<br />
2510 A single dash quotes one following word.<br />
2511 A double dash quotes all following words.<br />
2512 }<br />
16.2 Specific commands<br />
sfntutil::dump (proc) This procedure implements the dump command. It has the call syntax<br />
dump {font file} {option} ∗<br />
2513 set sfntutil::help::description(dump) "Dump sfnt-housed data in text\<br />
form"<br />
2515 proc sfntutil::dump {fontfile args} {<br />
2516 array set Opt {format TDL detail metrics}<br />
2517 stuff::alloptions Opt args {format detail only}<br />
2518 lassign [stuff::openfont $fontfile] F data<br />
2519 if {[string tolower $Opt(detail)] ne "toc"} then {<br />
2520 if {[info exists Opt(only)]} then {<br />
2521 set which [split $Opt(only) ,]<br />
2522 } else {<br />
2523 set which *<br />
2524 }<br />
2525 set data [sfnt::expand::main $F $data -which $which]<br />
2526 }<br />
2527 close $F<br />
2528 switch -- [string toupper $Opt(format)] "TDL" {<br />
2529 set data [prettyTDL::prettyprint $data]<br />
2530 } "XML" {<br />
2531 set data [TDLtoXML::main $data]<br />
2532 } "TEX" {<br />
2533 set data [TDLtoTeX::main $data]<br />
2534 }<br />
2535 stuff::stdout $data<br />
2536 }<br />
And now the help text.<br />
2537 interp alias {} sfntutil::help::dump {} puts stderr {<br />
2538 sfntutil dump - Dump contents <strong>of</strong> sfnt file as text to standard out<br />
2539 Syntax:<br />
2540 sfntutil dump FILENAME ?OPTION ...?<br />
2541<br />
2542 Recognised options:<br />
2543 -format: The output format. One <strong>of</strong> (case ignored):<br />
93
2544 TDL (default) - Tcl Data Language: the internal format <strong>of</strong>\<br />
sfntutil.<br />
2545 XML - Translation to XML <strong>of</strong> the TDL data.<br />
2546 TeX - Translation to TeX <strong>of</strong> the TDL data.<br />
2547 raw - Like TDL, but not prettyprinted. Contains some extra\<br />
comments<br />
2548 giving more details <strong>of</strong> how data was encoded in the file,<br />
2549 so can be useful for debugging.<br />
2550 -detail: The detail in the file contents are given. One <strong>of</strong> (case<br />
2551 ignored):<br />
2552 toc - Only give a "table <strong>of</strong> contents" <strong>of</strong> the file, listing the<br />
2553 individual sfnt tables but none <strong>of</strong> their contents.<br />
2554 metrics (default) -<br />
2555 Parse tables that might be relevant for metrics, but<br />
2556 don’t bother about outlines or rendering.<br />
2557 -only: If not taking -detail=toc, then this selects which tables<br />
2558 to parse. The value is a comma-separated list <strong>of</strong><br />
2559 [string match] (i.e., glob-style) patterns, and tables are<br />
2560 parsed if their tag matches some element in this list.<br />
2561 Example:<br />
2562 -only=h*,OS/2<br />
2563 Parse tables beginning with h (head, hhea, hmtx, etc.), and<br />
2564 also the OS/2 table.<br />
2565 }<br />
sfntutil::psgid (proc) This procedure is the top-level implementation <strong>of</strong> the psgid command, which<br />
generates PS file(s) defining the font(s) as CID<strong>Font</strong>s with the GID as CID. The<br />
call syntax is<br />
sfntutil psgid {source font file} {target file/option} ∗<br />
Output is written to the {target file}s.<br />
2566 set sfntutil::help::description(psgid) "Convert to PS CID-font(s)"<br />
2568 proc sfntutil::psgid {fontfile args} {<br />
2569 stuff::alloptions Opt args {names}<br />
If a target name does not have a suffix, then a .psfont suffix is appended.<br />
2570 set nameL {}<br />
2571 foreach name $args {<br />
2572 if {[file extension $name] eq ""} then {<br />
2573 lappend nameL $name.psfont<br />
2574 } else {<br />
2575 lappend nameL $name<br />
2576 }<br />
2577 }<br />
2578 lassign [stuff::openfont $fontfile] F script<br />
2579 foreach name [<br />
2580 sfnt::postscript::tdl_togid $nameL $F $script {*}[<br />
2581 if {[info exists Opt(names)]} then {<br />
2582 list -names [split [string trim $Opt(names) /] /]<br />
94
2583 }<br />
2584 ] -progress {puts stdout}<br />
2585 ] {<br />
2586 puts stdout "Ignoring unused target $name."<br />
2587 }<br />
2588 }<br />
And now the help text.<br />
2589 interp alias {} sfntutil::help::psgid {} puts stderr {<br />
2590 sfntutil psgid - Use outline data to generate Postscript CID<strong>Font</strong>(s)<br />
2591 having CID=GID.<br />
2592 Syntax:<br />
2593 sfntutil psgid SOURCE ?OPTION-or-TARGET ...?<br />
2594<br />
2595 For each font within the SOURCE, PS code defining that font as<br />
2596 a CID<strong>Font</strong> is written to one <strong>of</strong> the TARGET files (overwriting previous<br />
2597 content without asking). If there are more fonts than TARGETs, or<br />
2598 more TARGETs than fonts, then nothing is done for the superfluous<br />
2599 items. A .psfont suffix is appended to TARGETs without extension.<br />
2600<br />
2601 The only recognised option is -names, which takes as value<br />
2602 a slash-separated list <strong>of</strong> PS names. If provided, then names in this<br />
2603 list will override names found in the SOURCE. Extra slashes at the<br />
2604 beginning or end <strong>of</strong> the value are ignored, so you may also supply<br />
2605 the concatenation <strong>of</strong> the font names written with initial slashes.<br />
2606<br />
2607 One output line is generated for each font in the SOURCE. psgid without<br />
2608 TARGETs can be used to produce a listing <strong>of</strong> fonts in the SOURCE.<br />
2609 }<br />
2610 〈/pkg〉<br />
2611 〈∗cmdline〉<br />
2612 if {![llength $argv]} then {set argv help}<br />
2613 sfntutil {*}$argv<br />
2614 〈/cmdline〉<br />
References<br />
[1] mpsuzuki et al.: plan to support sfnt-wrapped CID-keyed font. Thread on<br />
freetype-devel@nongnu.org mailing list, 2008-08-29 ff. http://lists.nongnu.<br />
org/archive/html/freetype-devel/2008-08/msg00038.html<br />
<strong>Index</strong><br />
Numbers written in italic refer to the page where the corresponding entry is described;<br />
numbers underlined refer to the code line <strong>of</strong> the definition; numbers in<br />
roman refer to the code lines where the entry is used.<br />
Symbols<br />
/AppleSingleHomeFS (element) . . . . 18<br />
95
Base<strong>Font</strong> (element), sfnt::gather:<br />
:theinterp interpreter . . . . . 86<br />
/Base<strong>Font</strong>Blend (element) . . . . . . . . 60<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/Base<strong>Font</strong>Name (element) . . . . . . . . 59<br />
/BlueValues (element) . . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/FamilyBlues (element) . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/FamilyName (element) . . . . . . . . . . 59<br />
/FamilyOtherBlues (element) . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/<strong>Font</strong>BBox (element) . . . . . . . . . . . . 30<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/<strong>Font</strong>Name (element) . . . . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/FullName (element) . . . . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/Notice (element) . . . . . . . . . . . . . . 59<br />
/OtherBlues (element) . . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/Panose (element) . . . . . . . . . . . . . . 32<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/PostScript (element) . . . . . . . . . . 59<br />
/ROS (element) . . . . . . . . . . . . . . . . 60<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/StemSnapH (element) . . . . . . . . . . . 59<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/StemSnapV (element) . . . . . . . . . . . 60<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 86<br />
/Weight (element) . . . . . . . . . . . . . . 59<br />
/adjustpair (element) . . . . . . . . . . 69<br />
/caretSlope (element) . . . . . . . . . . 36<br />
/charmap (element) . . . . . . . . . . . . . 45<br />
prettyTDL::theinterp interpreter 46<br />
/datum (element) . . . . . . . . . . . . . . 24<br />
sfnt::expand::theinterp<br />
interpreter . . . . . . . . . . . . . . 24<br />
96<br />
/dontsetint (element) . . . . . . . . . . 14<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/flag (element) . . . . . . . . . . . . . . . 29<br />
/glyphCID (element) . . . . . . . . . . . . 61<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/glyphbbox (element) . . . . . . . . . . . 50<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/glyphccode (element) . . . . . . . . . . 40<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/glyphname (element) . . . . . . . . . . . 38<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/glyphwidth (element) . . . . . . . . . . 36<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/hexdump (element) . . . . . . . . . . . . . 27<br />
/kernpair (element) . . . . . . . . . . . . 52<br />
/minMaxPSMem (element) . . . . . . . . . 39<br />
/namebytearray (element) . . . . . . . . 43<br />
/namestr (element) . . . . . . . . . . . . . 42<br />
/scriptsizepos (element) . . . . . . . . 32<br />
/setint (element) . . . . . . . . . . . . . . 13<br />
sfnt::gather::theinterp<br />
interpreter . . . . . . . . . . . . . . 85<br />
/version (element) . . . . . . . . . . . . . 59<br />
/when (element) . . . . . . . . . . . . . . . 30<br />
TDLtoTeX::theinterp interpreter 31<br />
#loca (gdict entry) . . . . . . . . . . . . . 51<br />
A<br />
adjust-table (element) . . . . . . . . . 69<br />
alloptions (proc), sfntutil::stuff<br />
namespace . . . . . . . . . . . . . . 91<br />
AppleSingleEntity (element) . . . . . 18<br />
ascender (fontinst integer) . . . . . . . 31<br />
B<br />
base_258_glyphs (var.), sfnt::post<br />
namespace . . . . . . . . . . . . . . 38<br />
Base<strong>Font</strong> (gathering entry) . . . . . . . 86<br />
Base<strong>Font</strong>Blend (gathering entry) . . . 86<br />
baselineskip (fontinst integer) . . . . 31<br />
BlueValues (gathering entry) . . . . . 86<br />
C<br />
classtable (proc), sfnt::GPOS namespace<br />
. . . . . . . . . . . . . . . . . . 70
cmap (element) . . . . . . . . . . . . . . . . 46<br />
code (attribute) . . . . . . . . . . . . . . . 41<br />
combine_tables (proc), sfnt namespace<br />
. . . . . . . . . . . . . . . . . . 20<br />
commands (proc), sfntutil::help<br />
namespace . . . . . . . . . . . . . . 89<br />
coverage (proc), sfnt::GPOS namespace<br />
. . . . . . . . . . . . . . . . . . 69<br />
D<br />
descender (fontinst integer) . . . . . . 31<br />
description (array), sfntutil::help<br />
namespace . . . . . . . . . . . . . . 89<br />
description (attribute) . . . . . . . . . 41<br />
description (var.), sfnt::name namespace<br />
. . . . . . . . . . . . . . . . . . 41<br />
descriptor (element) . . . . . . . . . . . 32<br />
designunits (fontinst variable) . . . . 30<br />
dump (proc), sfntutil namespace . . 93<br />
E<br />
enc (attribute) . . . . . . . . . . . . . . . . 46<br />
encoding_convertfrom (proc), sfnt:<br />
:name namespace . . . . . . . . . 43<br />
F<br />
FamilyBlues (gathering entry) . . . . . 86<br />
FamilyOtherBlues (gathering entry) 86<br />
Feature (element) . . . . . . . . . . . . . . 66<br />
featurelist_by_lookup (proc), sfnt:<br />
:GPOS namespace . . . . . . . . . 65<br />
FeatureRecord (element) . . . . . . . . 66<br />
fetch_tables (proc), sfnt namespace 19<br />
FNAM (table) . . . . . . . . . . . . . . . . . . 33<br />
FOND-association (tag) . . . . . . . . . 34<br />
<strong>Font</strong>BBox (gathering entry) . . . . . . . 86<br />
fontDirectionHint (fontinst variable) 30<br />
fontinst (gathering entry) . . . . . . . 84<br />
<strong>Font</strong>Name (gathering entry) . . . . . . . 86<br />
<strong>Font</strong>Revision (element) . . . . . . . . . 29<br />
format (attribute) . . . . . . . . . . . . . 46<br />
FullName (gathering entry) . . . . . . . 86<br />
funit (gdict entry) . . . . . . . . . . . . . 21<br />
G<br />
gather (proc), sfnt namespace . . . . 83<br />
gathering (local var.) . . . . . . . . . . . 83<br />
gdict (local var.) . . . . . . . . . . . . . . 24<br />
glyphbbox (proc), sfnt::gather<br />
namespace . . . . . . . . . . . . . . 85<br />
97<br />
glyphdatum (proc), sfnt::gather<br />
namespace . . . . . . . . . . . . . . 85<br />
glyphwidth (proc), sfnt::gather<br />
namespace . . . . . . . . . . . . . . 85<br />
H<br />
Help (ensemble), sfntutil namespace 88<br />
hexdump_char_from_byte (proc), sfnt<br />
namespace . . . . . . . . . . . . . . 28<br />
I<br />
interpreter (theinterp), sfnt:<br />
:expand namespace . . . . . . . 22<br />
K<br />
kern-table (element) . . . . . . . . 52, 54<br />
L<br />
lang (attribute) . . . . . . . . . . . . . . . 46<br />
LangSysRecord (element) . . . . . . . . 64<br />
language (attribute) . . . . . . . . . . . . 46<br />
linegap (fontinst integer) . . . . . . . . 31<br />
lookuptable (element) . . . . . . . . . . 67<br />
lowestReadablePPEM (fontinst variable)<br />
. . . . . . . . . . . . . . . . . . 30<br />
M<br />
Mac-resource (element) . . . . . . . . . 17<br />
main (proc)<br />
sfnt::expand namespace . . . . . . 24<br />
TDLtoTeX namespace . . . . . . . . . 12<br />
TDLtoXML namespace . . . . . . . . . . 7<br />
make_mtx (proc), sfnt namespace . . 83<br />
N<br />
nameid (element) . . . . . . . . . . . . . . 41<br />
nexttarget (local var.) . . . . . . . . . . 81<br />
numGlyphs (gdict entry) . . . . . . . . . 21<br />
O<br />
O (local array) . . . . . . . . . . . . . 4, 12, 24<br />
<strong>of</strong>fset (attribute) . . . . . . . . . . . . . 46<br />
openfont (proc), sfntutil::stuff<br />
namespace . . . . . . . . . . . . . . 90<br />
Opt (local array) . . . . . . . . . . . . . . . 81<br />
options (alias), sfntutil::help<br />
namespace . . . . . . . . . . . . . . 92<br />
OtherBlues (gathering entry) . . . . . 86<br />
P<br />
Panose (gathering entry) . . . . . . . . . 86<br />
parse (proc)<br />
sfnt::GSUB namespace . . . . . . . 73
sfnt::hhea namespace . . . . . . . 35<br />
sfnt::hmtx namespace . . . . . . . 36<br />
sfnt::maxp namespace . . . . . . . 35<br />
sfnt::〈table〉 namespace . . . . . . 22<br />
sfnt::CFF namespace . . . . . . . . 62<br />
sfnt::cmap namespace . . . . . . . 46<br />
sfnt::fdsc namespace . . . . . . . 33<br />
sfnt::FNAM namespace . . . . . . . 33<br />
sfnt::glyf namespace . . . . . . . 51<br />
sfnt::GPOS namespace . . . . . . . 72<br />
sfnt::head namespace . . . . . . . 29<br />
sfnt::HFMX namespace . . . . . . . 37<br />
sfnt::kern namespace . . . . . . . 51<br />
sfnt::loca namespace . . . . . . . 51<br />
sfnt::name namespace . . . . . . . 44<br />
sfnt::OS/2 namespace . . . . . . . 31<br />
sfnt::post namespace . . . . . . . 39<br />
parse_after (var.)<br />
sfnt::〈table〉 namespace . . . . . . 22<br />
sfnt::glyf namespace . . . . . . . 51<br />
sfnt::HFMX namespace . . . . . . . 37<br />
sfnt::kern namespace . . . . . . . 51<br />
sfnt::loca namespace . . . . . . . 51<br />
sfnt::OS/2 namespace . . . . . . . 31<br />
parse_applesingle (proc), sfnt<br />
namespace . . . . . . . . . . . . . . 18<br />
parse_as_hexdump (proc), sfnt namespace<br />
. . . . . . . . . . . . . . . . . . 27<br />
parse_bboxes (proc), sfnt::glyf<br />
namespace . . . . . . . . . . . . . . 50<br />
parse_charset (proc), sfnt::CFF<br />
namespace . . . . . . . . . . . . . . 61<br />
parse_dict (proc), sfnt::CFF namespace<br />
. . . . . . . . . . . . . . . . . . 58<br />
parse_feature (proc), sfnt::GPOS<br />
namespace . . . . . . . . . . . . . . 66<br />
parse_file_header (proc), sfnt<br />
namespace . . . . . . . . . . . . . . 14<br />
parse_format_0 (proc)<br />
sfnt::cmap namespace . . . . . . . 48<br />
sfnt::kern namespace . . . . . . . 53<br />
parse_format_2 (proc)<br />
sfnt::cmap namespace . . . . . . . 48<br />
sfnt::kern namespace . . . . . . . 53<br />
parse_format_3 (proc), sfnt::kern<br />
namespace . . . . . . . . . . . . . . 55<br />
parse_index (proc), sfnt::CFF namespace<br />
. . . . . . . . . . . . . . . . . . 56<br />
parse_loca (proc), sfnt::glyf namespace<br />
. . . . . . . . . . . . . . . . . . 50<br />
98<br />
parse_long_subtable (proc), sfnt:<br />
:kern namespace . . . . . . . . . 54<br />
parse_lookuplist (proc), sfnt::GPOS<br />
namespace . . . . . . . . . . . . . . 67<br />
parse_pairpos (proc), sfnt::GPOS<br />
namespace . . . . . . . . . . . . . . 71<br />
parse_resfile_map (proc), sfnt<br />
namespace . . . . . . . . . . . . . . 17<br />
parse_subtable (proc), sfnt::kern<br />
namespace . . . . . . . . . . . . . . 52<br />
PELdict (proc), sfnt::name namespace<br />
. . . . . . . . . . . . . . . . . . 42<br />
plat-enc-lang (element) . . . . . . . . 46<br />
platform (attribute) . . . . . . . . . . . . 46<br />
post (table) . . . . . . . . . . . . . . . . . . 38<br />
prettyprint (proc), prettyTDL namespace<br />
. . . . . . . . . . . . . . . . . . . 4<br />
PSBaseName . . . . . . . . . . . . . . . . . . 44<br />
psgid (proc), sfntutil namespace . 94<br />
psgid_sfnt-font (proc), sfnt:<br />
:postscript namespace . . . . 81<br />
psgid_unknown (proc), sfnt:<br />
:postscript namespace . . . . 81<br />
PSType0Name . . . . . . . . . . . . . . . . . . 44<br />
Q<br />
quotechars (proc), TDLtoTeX namespace<br />
. . . . . . . . . . . . . . . . . . 11<br />
R<br />
rawnestdict (proc), sfnt::name<br />
namespace . . . . . . . . . . . . . . 45<br />
res (local var.) . . . . . . . . . . 4, 7, 12, 22<br />
ROS (gathering entry) . . . . . . . . . . . 85<br />
ROS (proc), sfnt::gather namespace 85<br />
S<br />
scriptlist_by_feature (proc), sfnt:<br />
:GPOS namespace . . . . . . . . . 64<br />
ScriptRecord (element) . . . . . . . . . 64<br />
setint (proc), TDLtoTeX namespace . 14<br />
sfnt-font (element) . . . . . . . . . . . . 14<br />
sfnt::expand::theinterp<br />
interpreter . . . . . . . . . . . . . . 24<br />
sfnt-font (proc), sfnt::expand<br />
namespace . . . . . . . . . . . . . . 24<br />
sfnt-table (element) . . . . . . . . . . . 14<br />
sfnt::expand::theinterp<br />
interpreter . . . . . . . . . . . . . . 23
sfnt-table (proc), sfnt::expand<br />
namespace . . . . . . . . . . . . . . 23<br />
sfnt::〈table〉 (namespace) . . . . . . . 21<br />
sfntutil (ensemble), global namespace<br />
. . . . . . . . . . . . . . . . . . 87<br />
eval subcommand . . . . . . . . . 87<br />
source subcommand . . . . . . . 87<br />
slash (proc)<br />
prettyTDL namespace . . . . . . . . . 5<br />
TDLtoTeX namespace . . . . . . . . . 13<br />
TDLtoXML namespace . . . . . . . . . . 8<br />
standard_strings (var.), sfnt::CFF<br />
namespace . . . . . . . . . . . . . . 56<br />
stdout (proc), sfntutil::stuff<br />
namespace . . . . . . . . . . . . . . 91<br />
StemSnapH (gathering entry) . . . . . . 86<br />
StemSnapV (gathering entry) . . . . . . 86<br />
sub1 (fontinst integer) . . . . . . . . . . . 31<br />
sub2 (fontinst integer) . . . . . . . . . . . 31<br />
sup2 (fontinst integer) . . . . . . . . . . . 31<br />
T<br />
table (local array) . . . . . . . . . . . . . 23<br />
table0L (local var.) . . . . . . . . . . . . 23<br />
table1L (local var.) . . . . . . . . . . . . 23<br />
targetL (local var.) . . . . . . . . . . . . 81<br />
tclenc (array), sfnt::name namespace<br />
. . . . . . . . . . . . . . . . . . 42<br />
TDL:arg (XML element) . . . . . . . . . . 7<br />
TDL:cmd (XML element) . . . . . . . . . . 7<br />
tdl_togid (proc), sfnt::postscript<br />
namespace . . . . . . . . . . . . . . 80<br />
thefile (local var.) . . . . . . . . . 24, 81<br />
theinterp (slave interp.)<br />
prettyTDL namespace . . . . . . . . . 4<br />
sfnt::gather namespace . . . . . . 84<br />
TDLtoTeX namespace . . . . . . . . . 10<br />
99<br />
TDLtoXML namespace . . . . . . . . . . 7<br />
togid (slave), sfnt::postscript<br />
namespace . . . . . . . . . . . . . . 80<br />
topics (proc), sfntutil::help namespace<br />
. . . . . . . . . . . . . . . . . . 90<br />
U<br />
underlinetop (fontinst variable) . . . 39<br />
unknown (proc)<br />
prettyTDL namespace . . . . . . . . . 5<br />
sfnt::expand namespace . . . . . . 22<br />
sfnt::gather namespace . . . . . . 84<br />
TDLtoTeX namespace . . . . . . . . . 12<br />
TDLtoXML namespace . . . . . . . . . . 7<br />
V<br />
valueformat (proc), sfnt::GPOS<br />
namespace . . . . . . . . . . . . . . 69<br />
W<br />
when (proc), TDLtoTeX namespace . . 31<br />
write_composefonts (proc), sfnt:<br />
:postscript namespace . . . . 75<br />
write_identity_cmap (proc), sfnt:<br />
:postscript namespace . . . . 76<br />
write_sfnts (proc), sfnt:<br />
:postscript namespace . . . . 78<br />
write_shifted_cmap (proc), sfnt:<br />
:postscript namespace . . . . 77<br />
write_truetype (proc), sfnt:<br />
:postscript namespace . . . . 74<br />
X<br />
xml_from_treenode (proc), TDLtoXML<br />
namespace . . . . . . . . . . . . . . . 9<br />
xml_from_trees (proc), TDLtoXML<br />
namespace . . . . . . . . . . . . . . . 8