27.06.2013 Views

OpenType Font utility - Index of

OpenType Font utility - Index of

OpenType Font utility - Index of

SHOW MORE
SHOW LESS

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) {< &lt; > &gt; & &amp; ’ &apos; \" &quot;}<br />

111 } else {<br />

112 foreach char {< > & ’ \"} entity {&lt; &gt; &amp; &apos; &quot;} {<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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!