-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathluametalatex-font-t1tot2.lua
287 lines (287 loc) · 10.8 KB
/
luametalatex-font-t1tot2.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
local function parse_charstring(cs, subrs, result)
result = result or {{false}}
local lastresult = result[#result]
local i = 1
while i~=#cs+1 do
local cmd = cs:byte(i)
if cmd == 255 then
lastresult[#lastresult+1] = string.unpack(">i4", cs:sub(i+1, i+4))
i = i+4
elseif cmd >= 251 then
lastresult[#lastresult+1] = -((cmd-251)*256)-string.byte(cs, i+1)-108
i = i+1
elseif cmd >= 247 then
lastresult[#lastresult+1] = (cmd-247)*256+string.byte(cs, i+1)+108
i = i+1
elseif cmd >= 32 then
lastresult[#lastresult+1] = cmd-139
elseif cmd == 9 then -- closepath, implicit in Type2
elseif cmd == 10 then
local subr = subrs[lastresult[#lastresult]]
lastresult[#lastresult] = nil
parse_charstring(subr, subrs, result)
lastresult = result[#result]
elseif cmd == 11 then
break -- We do not keep subroutines, so drop returns and continue with the outer commands
elseif cmd == 12 then
i = i+1
cmd = cs:byte(i)
if cmd == 12 then -- div, we might have huge parameters, so execute directly
lastresult[#lastresult-1] = lastresult[#lastresult-1]/lastresult[#lastresult]
lastresult[#lastresult] = nil
elseif cmd == 16 then -- othersubr...
cmd = lastresult[#lastresult]
lastresult[#lastresult] = nil
local numargs = lastresult[#lastresult]
lastresult[#lastresult] = nil
if cmd == 3 then -- Hint replacement. This is easy, we support hint replacement, so we
-- keep the original subr number
assert(numargs == 1)
elseif cmd == 1 then -- Flex initialization
elseif cmd == 2 then -- Flex parameter
if result[#result-1].flex then
result[#result] = nil -- TODO: Warn if there were additional values in lastresult.
lastresult = result[#result] -- We keep collecting arguments
end
lastresult.flex = true
elseif cmd == 0 then -- Flex
local flexinit = result[#result-1]
lastresult[2] = lastresult[2] + flexinit[2]
lastresult[3] = lastresult[3] + flexinit[3]
lastresult.flex = nil
result[#result-1] = lastresult
result[#result] = nil
lastresult[#lastresult] = nil
lastresult[#lastresult] = nil
lastresult[1] = -36
lastresult = {false}
result[#result+1] = lastresult
lastresult[#lastresult+1] = "setcurrentpointmark"
elseif cmd == 12 or cmd == 13 then
local pending = {}
local results = #lastresult
for i = 1,numargs do
pending[i] = lastresult[results-numargs+i]
lastresult[results-numargs+i] = nil
end
if lastresult.pendingargs then
for i = 1,#lastresult.pendingargs do
pending[numargs+i] = lastresult.pendingargs[i]
end
end
if cmd == 12 then
lastresult.pendingargs = pending
else
lastresult.pendingargs = nil
local n = pending[1]
local i = 2
local groups = {}
for group = 1, n do
local current = {20}
local last = 0
while pending[i+1] > 0 do
last = last + pending[i]
current[#current+1] = {1, last, pending[i+1]}
last = last + pending[i+1]
i = i+2
end
last = last + pending[i]
current[#current+1] = {1, last + pending[i+1], -pending[i+1]}
groups[group] = current
i = i+2
end
n = pending[i]
i = i+1
for group = 1, n do
local current = groups[group] or {20}
local last = 0
while pending[i+1] > 0 do
last = last + pending[i]
current[#current+1] = {3, last, pending[i+1]}
last = last + pending[i+1]
i = i+2
end
last = last + pending[i]
current[#current+1] = {3, last + pending[i+1], -pending[i+1]}
groups[group] = current
i = i+2
end
assert(i == #pending+1)
table.move(groups, 1, #groups, #result, result) -- This overwrites lastresult
result[#result+1] = lastresult -- And restore lastresult
end
else
error[[UNSUPPORTED Othersubr]]
end
elseif cmd == 17 then -- pop... Ignore them, they should already be handled by othersubr.
-- Compatibility with unknown othersubrs is futile, because
-- we can't interpret PostScript
elseif cmd == 33 then -- setcurrentpoint... If we expected this, it is already handled.
-- Otherwise fail, according to the spec it should
-- only be used with othersubrs.
assert(lastresult[#lastresult] == "setcurrentpointmark")
lastresult[#lastresult] = nil
else
lastresult[1] = -cmd-1
lastresult = {false}
result[#result+1] = lastresult
end
else
lastresult[1] = cmd
lastresult = {false}
result[#result+1] = lastresult
end
i = i+1
end
return result
end
local function adjust_charstring(cs) -- Here we get a not yet optimized but parsed Type1 charstring and
-- do some adjustments to make them more "Type2-like".
cs[#cs] = nil -- parse_charstring adds a `{false}` for internal reasons. Just drop it here. FIXME: Check that #cs[#cs]==1, otherwise there were values left on the charstring stack
if cs[1][1] ~= 13 then
error[[Unsupported first Type1 operator]] -- probably cs[1][1] == sbw
-- If you find a font using this, I'm sorry for you.
end
local hoffset = cs[1][2]
if hoffset ~= 0 then
-- non-zero sidebearings :-(
for i, cmd in ipairs(cs) do
if cmd[1] == 21 or cmd[1] == 22 then
cmd[2] = cmd[2] + cs[1][2]
break
elseif cmd[1] == 4 then
cmd[3] = cmd[2]
cmd[2] = cs[1][2]
cmd[1] = 21
break
end
-- Here I rely on the fact that the first relative command is always [hvr]moveto.
-- This is based on "Use rmoveto for the first point in the path." in the T1 spec
-- for hsbw. I am not entirely sure if this is a strict requirement or if there could
-- be weird charstrings where this fails (esp. since [hv]moveto are also used in the example),
-- but I decided to take the risk.
-- hints are affected too. They do not use relative coordinates in T1, so we store the offset
-- and handle hints later
end
end
cs[1][2] = cs[1][3]
cs[1][3] = nil
cs[1][1] = nil
-- That's it for the width, now we need some hinting stuff. This would be easy, if hint replacement
-- wouldn't require hint masks in Type2. And because we really enjoy this BS, we get counter
-- hinting as an additional treat... Oh, if you actually use counter hinting: Please test this
-- and report back if it works, because this is pretty much untested.
local stems = {}
local stem3 = {20}
local cntrs = {}
-- First iterate over the charstring, recording all hints and collecting them in stems/stem3/cntrs
for i, cmd in ipairs(cs) do
if cmd[1] == 1 or cmd[1] == 3 then
stems[#stems + 1] = cmd
elseif cmd[1] == -2 or cmd[1] == -3 then
local c = cmd[1] == -2 and 3 or 1
stems[#stems + 1] = {c, cmd[2], cmd[3]}
stems[#stems + 1] = {c, cmd[4], cmd[5]}
stems[#stems + 1] = {c, cmd[6], cmd[7]}
table.move(stems, #stems-2, #stems, #stem3+1, stem3)
cs[i] = false
elseif cmd[1] == 20 then
cntrs[#cntrs+1] = cmd
table.move(cmd, 2, #cmd, #stems+1, stems)
end
end
table.sort(stems, function(first, second)
if first[1] ~= second[1] then return first[1] < second[1] end
if first[2] ~= second[2] then return first[2] < second[2] end
return first[3] < second[3]
end)
-- Now store the index of every stem in the idx member of the hint command
-- After that `j` stores the number of stems
local j,k = 1,1
if stems[1] then stems[1].idx = 1 end
for i = 2,#stems do
if stems[i][2] == stems[k][2] and stems[i][3] == stems[k][3] then
stems[i].idx = j
stems[i] = false
else
j, k = j+1, i
stems[i].idx = j
end
end
-- Now the indices are known, so the cntrmask can be written, if counters or stem3 occured.
-- This is done before writing the stem list to make the thable.insert parameters easier.
-- First translate stem3 into a counter group
if stem3[2] then
cntrs[#cntrs+1] = stem3
table.insert(cs, 2, stem3)
end
local bytes = {}
for i=1, #cntrs do
local cntr = cntrs[i]
for l = 1, math.floor((j + 7)/8) do
bytes[l] = 0
end
for l = 2, #cntr do
local idx = cntr[l].idx-1
bytes[math.floor(idx/8) + 1] = bytes[math.floor(idx/8) + 1] | (1<<(7-idx%8))
cntr[l] = nil
end
cntr[2] = string.char(table.unpack(bytes))
end
local current = 1
-- Then list the collected stems at the beginning of the charstring
if stems[current] and stems[current][1] == 1 then
local stem_tbl, last = {18}, 0
while stems[current] ~= nil and (not stems[current] or stems[current][1] == 1) do
if stems[current] then
stem_tbl[#stem_tbl + 1] = stems[current][2] - last
last = stems[current][2] + stems[current][3]
stem_tbl[#stem_tbl + 1] = stems[current][3]
end
current = current + 1
end
table.insert(cs, 2, stem_tbl)
end
if stems[current] and stems[current][1] == 3 then
local stem_tbl, last = {false}, -hoffset
while stems[current] ~= nil and (not stems[current] or stems[current][1] == 3) do
if stems[current] then
stem_tbl[#stem_tbl + 1] = stems[current][2] - last
last = stems[current][2] + stems[current][3]
stem_tbl[#stem_tbl + 1] = stems[current][3]
end
current = current + 1
end
table.insert(cs, stems[1][1] == 1 and 3 or 2, stem_tbl)
end
-- Finally, replace every run of hint commands, corresponding to a hint replacement, by a single hintmask
local i = 1
while cs[i] ~= nil do
if cs[i] and cs[i].idx then
if stem3[2] then
local s3 = stem3[2]
for l = 1, math.floor((j + 7)/8) do
bytes[l] = string.byte(s3, l)
end
else
for l = 1, math.floor((j + 7)/8) do
bytes[l] = 0
end
end
while (cs[i] or {}).idx do
local idx = cs[i].idx-1
bytes[math.floor(idx/8) + 1] = bytes[math.floor(idx/8) + 1] | (1<<(7-idx%8))
cs[i] = false
i = i+1
end
i = i-1
cs[i] = {19, string.char(table.unpack(bytes))}
end
i = i+1
end
end
return function(cs, subrs)
local parsed = parse_charstring(cs, subrs)
adjust_charstring(parsed)
return parsed
end