-
Notifications
You must be signed in to change notification settings - Fork 3
/
luametalatex-font-t1.lua
158 lines (140 loc) · 6.77 KB
/
luametalatex-font-t1.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
local white = (lpeg.S'\0\9\10\12\13\32' + '%' * (1 - lpeg.S'\r\n')^0)^1 -- Whitespace
local regular = 1-lpeg.S'()<>[]{}/%\0\9\10\12\13\32'
local lastbase = '123456789abcdefghiklmnopqrstuvwxyz'
local number = lpeg.Cmt(lpeg.R'09'^1/tonumber * '#', function(s, p, base)
if base < 2 then return end
local pattern
if base <= 10 then
pattern = lpeg.R('0' .. lastbase:sub(base-1, base-1))
else
pattern = lpeg.R'09' + lpeg.R('a' .. lastbase:sub(base-1, base-1)) + lpeg.R('A' .. lastbase:sub(base-1, base-1):upper())
end
local num, p = (lpeg.C(pattern^1) * lpeg.Cp()):match(s, p)
return p, num and tonumber(num, base)
end)
+ (lpeg.S'+-'^-1 * ('.' * lpeg.R'09'^1 + lpeg.R'09'^1 * lpeg.P'.'^-1 * lpeg.R'09'^0) * (lpeg.S'eE' * lpeg.S'+-'^-1 * lpeg.R'09'^1)^-1)/tonumber
local literalstring = lpeg.P{'(' * lpeg.Cs((
lpeg.P'\\n'/'\n'+lpeg.P'\\r'/'\r'+lpeg.P'\\t'/'\t'+lpeg.P'\\b'/'\b'+lpeg.P'\\f'/'\f'
+'\\'*lpeg.C(lpeg.R'07'*lpeg.R'07'^-2)/function(n)return string.char(tonumber(n, 8))end
+'\\'*('\n' + ('\r' * lpeg.P'\n'^-1))/''
+'\\'*lpeg.C(1)/1
+('\n' + ('\r' * lpeg.P'\n'^-1))/'\n'
+(1-lpeg.S'()\\')+lpeg.V(1))^0) * ')'}
local hexstring = '<' * lpeg.Cs((
lpeg.C(lpeg.R'09'+lpeg.R'af'+lpeg.R'AF')*(lpeg.C(lpeg.R'09'+lpeg.R'af'+lpeg.R'AF')+lpeg.Cc'0')/function(a,b)return string.char(tonumber(a..b, 16))end)^0) * '>'
local name = lpeg.C(regular^1)
local lname = '/' * name / 1
local boolean = (lpeg.P'true' + 'false')/{["true"] = true, ["false"] = false}
-- Everything above this line works pretty reliable and can be understood by reading the PostScript specs.
-- This is Type1 specific. The only thing which might need adjustment is adding alternative spellings for -|, RD, |-, |, etc.
local binary_bytes = lpeg.Cmt(number*white^-1*(lpeg.P'-| ' + 'RD '), function(s, p, l)return p+l, s:sub(p, p+l-1) end)*white^-1*(lpeg.P"|-"+"|"+"ND"+"NP")
-- Attention: The |-, |, ND, NP already contain an implicit `def`
local function decrypt(key, n, cipher)
-- Generally you should never implement your own crypto. So we call a well known, peer reviewed,
-- high-quality cryptographic library. --- Ha-Ha, of course we are implementing this by ourselves.
-- That might be completely insecure, but given that the encryption keys are well known constants
-- documented in the T1 Spec, there is no need to worry about it.
-- Also I do not think any cryptographic library would implement this anyway, it doesn't even
-- really deserve the term encryption.
local decoded = {string.byte(cipher, 1,-1)}
for i=1,#decoded do
local c = decoded[i]
decoded[i] = c ~ (key>>8)
key = (((c+key)&0xFFFF)*52845+22719)&0xFFFF
end
return string.char(table.unpack(decoded, n+1))
end
local anytype = {
hexstring
+ literalstring
+ lpeg.V'array'
+ number
+ lname
+ boolean
+ name,
array = lpeg.Ct( '[' * (white^-1 * lpeg.V(1))^0 * white^-1 * ']' -- Arrays have two possible syntaxes
+ '{' * (white^-1 * lpeg.V(1))^0 * white^-1 * '}') * (white * "executeonly")^-1
+ number/0 * white^-1 * 'array' * lpeg.Cf(lpeg.Ct'' * lpeg.Cg(white * 'dup' * white * number * white^-1 * lpeg.V(1) * white^-1 * 'put')^0, rawset)
}
local function skip_until(p)
if type(p) == 'string' then p = p * -name end
return (white + anytype - p)^0/0
end
local skip_to_begin = skip_until'begin' * 'begin'
local def_like = (lpeg.P'def' + 'ND' + '|-') * -name
local encoding = '/' * lpeg.C'Encoding' * -name
* skip_until'dup'
* lpeg.Cf(lpeg.Ct''
* lpeg.Cg("dup"*white*number*white^-1*lname*white^-1*"put"*white)^0
, rawset)
* ("readonly"*white)^-1 * "def"
local charstr = '/' * lpeg.C'CharStrings' * -name
* skip_until(lname) -- sometimes we get weird stuff in between. Just make sure that we don't swallow a charname
* lpeg.Cf(lpeg.Ct''
* lpeg.Cg(lname*white^-1*binary_bytes*white)^0 -- Remember: binary_bytes includes a `def`
, rawset)
* lpeg.P"end"*white
local subrs = '/' * lpeg.C'Subrs' * -name
* skip_until'dup'
* lpeg.Cf(lpeg.Ct''
* lpeg.Cg("dup"*white^-1*number*white^-1*binary_bytes*white)^0
, rawset)
* (lpeg.P"readonly"*white)^-1 * (lpeg.P"noaccess"*white)^-1*(lpeg.P"def"+"ND"+"|-")
-- lpeg.V(2) == dict_entries
local dict = skip_to_begin * lpeg.V(2) * white^-1 * 'end' * white * ('readonly' * white)^-1 * ('noaccess' * white)^-1 * def_like
local dict_entry = encoding + subrs +
'/' * lpeg.C'FontInfo' * dict +
lname -- key
* white^-1
* anytype -- value
* ((white + anytype - (def_like + 'dict' + 'array') * -name)/0 * white^-1)^0 -- Sometimes we get Postscript code in between.
* def_like
local dict_entries = lpeg.P{
lpeg.Cf(lpeg.Carg(1) * lpeg.Cg(white^-1*lpeg.V(3))^0, rawset),
lpeg.Cf(lpeg.Ct'' * lpeg.Cg(white^-1*lpeg.V(3))^0, rawset),
dict_entry,
}
local function parse_private(offset, str)
local mydict, found
offset = (skip_to_begin * lpeg.Cp()):match(str, offset)
-- Scan the dictionary
mydict, offset = (dict_entries*lpeg.Cp()):match(str, offset, {})
return mydict, offset
end
local function continue_maintable(offset, str, mydict)
mydict, offset = (dict_entries*lpeg.Cp()):match(str, offset, mydict)
local found = (white^-1*lname):match(str, offset)
if found == "Private" then -- Scanned separatly because it isn't always ended in a regular way
mydict.Private, offset = parse_private(offset, str)
return continue_maintable(offset, str, mydict)
elseif found == "CharStrings" then -- This could be included in normal scanning, but it is our signal to terminate
found, mydict.CharStrings, offset = (charstr*lpeg.Cp()):match(str, offset)
return mydict
else
local newoffset = ((white+name)^1/0*lpeg.Cp()):match(str, offset)
if newoffset and offset <= #str then
return continue_maintable(newoffset, str, mydict)
end
end
error[[Unable to read Type 1 font]]
end
local function parse_maintable(offset, str)
local found
offset = (skip_to_begin * lpeg.Cp()):match(str, offset)
return continue_maintable(offset, str, {})
end
return function(data)
local preface, private = string.unpack("<xxs4xxs4", data)
private = decrypt(55665, 4, private)
local after = parse_maintable(1, preface .. private)
local lenIV = after.Private.lenIV or 4
local chars = after.CharStrings
for k, v in pairs(chars) do
chars[k] = decrypt(4330, lenIV, v)
end
local subrs = after.Private.Subrs
for k, v in pairs(subrs) do
subrs[k] = decrypt(4330, lenIV, v)
end
return after
end