1if not modules then modules = { } end modules ['font-shp'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local tonumber, next = tonumber, next
10local concat = table.concat
11local formatters, lower = string.formatters, string.lower
12
13local otf = fonts.handlers.otf
14local afm = fonts.handlers.afm
15local pfb = fonts.handlers.pfb
16
17local hashes = fonts.hashes
18local identifiers = hashes.identifiers
19
20local version = otf.version or 0.015
21local shapescache = containers.define("fonts", "shapes", version, true)
22local streamscache = containers.define("fonts", "streams", version, true)
23
24
25
26local compact_streams = false
27
28directives.register("fonts.streams.compact", function(v) compact_streams = v end)
29
30local function packoutlines(data,makesequence)
31 local subfonts = data.subfonts
32 if subfonts then
33 for i=1,#subfonts do
34 packoutlines(subfonts[i],makesequence)
35 end
36 return
37 end
38 local common = data.segments
39 if common then
40 return
41 end
42 local glyphs = data.glyphs
43 if not glyphs then
44 return
45 end
46 if makesequence then
47 for index=0,#glyphs do
48 local glyph = glyphs[index]
49 if glyph then
50 local segments = glyph.segments
51 if segments then
52 local sequence = { }
53 local nofsequence = 0
54 for i=1,#segments do
55 local segment = segments[i]
56 local nofsegment = #segment
57
58 nofsequence = nofsequence + 1
59 sequence[nofsequence] = segment[nofsegment]
60 for i=1,nofsegment-1 do
61 nofsequence = nofsequence + 1
62 sequence[nofsequence] = segment[i]
63 end
64 end
65 glyph.sequence = sequence
66 glyph.segments = nil
67 end
68 end
69 end
70 else
71 local hash = { }
72 local common = { }
73 local reverse = { }
74 local last = 0
75 for index=0,#glyphs do
76 local glyph = glyphs[index]
77 if glyph then
78 local segments = glyph.segments
79 if segments then
80 for i=1,#segments do
81 local h = concat(segments[i]," ")
82 hash[h] = (hash[h] or 0) + 1
83 end
84 end
85 end
86 end
87 for index=0,#glyphs do
88 local glyph = glyphs[index]
89 if glyph then
90 local segments = glyph.segments
91 if segments then
92 for i=1,#segments do
93 local segment = segments[i]
94 local h = concat(segment," ")
95 if hash[h] > 1 then
96 local idx = reverse[h]
97 if not idx then
98 last = last + 1
99 reverse[h] = last
100 common[last] = segment
101 idx = last
102 end
103 segments[i] = idx
104 end
105 end
106 end
107 end
108 end
109 if last > 0 then
110 data.segments = common
111 end
112 end
113end
114
115local function unpackoutlines(data)
116 local subfonts = data.subfonts
117 if subfonts then
118 for i=1,#subfonts do
119 unpackoutlines(subfonts[i])
120 end
121 return
122 end
123 local common = data.segments
124 if not common then
125 return
126 end
127 local glyphs = data.glyphs
128 if not glyphs then
129 return
130 end
131 for index=0,#glyphs do
132 local glyph = glyphs[index]
133 if glyph then
134 local segments = glyph.segments
135 if segments then
136 for i=1,#segments do
137 local c = common[segments[i]]
138 if c then
139 segments[i] = c
140 end
141 end
142 end
143 end
144 end
145 data.segments = nil
146end
147
148
149
150local readers = otf.readers
151local cleanname = otf.readers.helpers.cleanname
152
153
154
155
156
157
158
159
160
161
162
163
164local function makehash(filename,sub,instance,extrahash)
165 return formatters["%s-%s-%s-%s"](
166 cleanname(file.basename(filename)),
167 sub or 0,
168 instance and instance ~= "" and cleanname(instance) or "0",
169 extrahash and extrahash ~= "" and cleanname(extrahash) or "0"
170 )
171end
172
173local function loadoutlines(cache,filename,sub,instance,extrahash)
174 local base = file.basename(filename)
175 local name = file.removesuffix(base)
176 local kind = file.suffix(filename)
177 local attr = lfs.attributes(filename)
178 local size = attr and attr.size or 0
179 local time = attr and attr.modification or 0
180 local sub = tonumber(sub)
181
182
183 if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then
184 local hash = makehash(filename,sub,instance,extrahash)
185 data = containers.read(cache,hash)
186 if not data or data.time ~= time or data.size ~= size then
187 data = otf.readers.loadshapes(filename,sub,instance)
188 if data then
189 data.size = size
190 data.format = data.format or (kind == "otf" and "opentype") or "truetype"
191 data.time = time
192 packoutlines(data)
193 containers.write(cache,hash,data)
194 data = containers.read(cache,hash)
195 end
196 end
197 unpackoutlines(data)
198 elseif size > 0 and (kind == "pfb") then
199 local hash = containers.cleanname(base)
200 data = containers.read(cache,hash)
201 if not data or data.time ~= time or data.size ~= size then
202 data = afm.readers.loadshapes(filename)
203 if data then
204 data.size = size
205 data.format = "type1"
206 data.time = time
207 packoutlines(data)
208 containers.write(cache,hash,data)
209 data = containers.read(cache,hash)
210 end
211 end
212 unpackoutlines(data)
213 else
214 data = {
215 filename = filename,
216 size = 0,
217 time = time,
218 format = "unknown",
219 units = 1000,
220 glyphs = { }
221 }
222 end
223 return data
224end
225
226local function cachethem(cache,hash,data)
227 containers.write(cache,hash,data,compact_streams)
228 return containers.read(cache,hash)
229end
230
231local function loadstreams(cache,filename,sub,instance,extrahash)
232 local base = file.basename(filename)
233 local name = file.removesuffix(base)
234 local kind = lower(file.suffix(filename))
235 local attr = lfs.attributes(filename)
236 local size = attr and attr.size or 0
237 local time = attr and attr.modification or 0
238 local sub = tonumber(sub)
239 if size > 0 and (kind == "otf" or kind == "ttf" or kind == "ttc") then
240 local hash = makehash(filename,sub,instance,extrahash)
241 data = containers.read(cache,hash)
242 if not data or data.time ~= time or data.size ~= size then
243 data = otf.readers.loadshapes(filename,sub,instance,true)
244 if data then
245 local glyphs = data.glyphs
246 local streams = { }
247
248 if glyphs then
249 for i=0,#glyphs do
250 local glyph = glyphs[i]
251 if glyph then
252 streams[i] = glyph.stream or ""
253
254 else
255 streams[i] = ""
256
257 end
258 end
259 end
260 data.streams = streams
261
262 data.glyphs = nil
263 data.size = size
264 data.format = data.format or (kind == "otf" and "opentype") or "truetype"
265 data.time = time
266 data = cachethem(cache,hash,data)
267 end
268 end
269 elseif size > 0 and (kind == "pfb") then
270 local hash = makehash(filename,sub,instance)
271 data = containers.read(cache,hash)
272 if not data or data.time ~= time or data.size ~= size then
273 local names, encoding, streams, metadata = pfb.loadvector(filename,false,true)
274 if streams then
275 local fontbbox = metadata.fontbbox or { 0, 0, 0, 0 }
276
277 for i=0,#streams do
278 local s = streams[i]
279 streams[i] = s.stream or "\14"
280
281 end
282 data = {
283 filename = filename,
284 size = size,
285 time = time,
286 format = "type1",
287 streams = streams,
288
289 fontheader = {
290 fontversion = metadata.version,
291 units = 1000,
292 xmin = fontbbox[1],
293 ymin = fontbbox[2],
294 xmax = fontbbox[3],
295 ymax = fontbbox[4],
296 },
297 horizontalheader = {
298 ascender = 0,
299 descender = 0,
300 },
301 maximumprofile = {
302 nofglyphs = #streams + 1,
303 },
304 names = {
305 copyright = metadata.copyright,
306 family = metadata.familyname,
307 fullname = metadata.fullname,
308 fontname = metadata.fontname,
309 subfamily = metadata.subfamilyname,
310 trademark = metadata.trademark,
311 notice = metadata.notice,
312 version = metadata.version,
313 },
314 cffinfo = {
315 familyname = metadata.familyname,
316 fullname = metadata.fullname,
317 italicangle = metadata.italicangle,
318 monospaced = metadata.isfixedpitch and true or false,
319 underlineposition = metadata.underlineposition,
320 underlinethickness = metadata.underlinethickness,
321 weight = metadata.weight,
322 },
323 }
324 data = cachethem(cache,hash,data)
325 end
326 end
327 else
328 data = {
329 filename = filename,
330 size = 0,
331 time = time,
332 format = "unknown",
333 streams = { }
334 }
335 end
336 return data
337end
338
339local loadedshapes = { }
340local loadedstreams = { }
341
342local function loadoutlinedata(fontdata,streams)
343 local properties = fontdata.properties
344 local filename = properties.filename
345 local subindex = fontdata.subindex
346 local instance = properties.instance
347 local extrahash = properties.extrahash
348 local hash = makehash(filename,subindex,instance,extrahash)
349 local loaded = loadedshapes[hash]
350 if not loaded then
351 loaded = loadoutlines(shapescache,filename,subindex,instance,extrahash)
352 loadedshapes[hash] = loaded
353 end
354 return loaded
355end
356
357hashes.shapes = table.setmetatableindex(function(t,k)
358 local f = identifiers[k]
359 if f then
360 return loadoutlinedata(f)
361 end
362end)
363
364local function getstreamhash(fontid)
365 local fontdata = identifiers[fontid]
366 if fontdata then
367 local properties = fontdata.properties
368 local fonthash = makehash(properties.filename,properties.subfont,properties.instance,properties.extrahash)
369 return fonthash, fontdata
370 end
371end
372
373local function loadstreamdata(fontdata)
374 local properties = fontdata.properties
375 local shared = fontdata.shared
376 local rawdata = shared and shared.rawdata
377 local metadata = rawdata and rawdata.metadata
378 local filename = properties.filename
379 local subindex = metadata and metadata.subfontindex
380 local instance = properties.instance
381 local extrahash = properties.extrahash
382 local hash = makehash(filename,subindex,instance)
383 local loaded = loadedstreams[hash]
384 if not loaded then
385 loaded = loadstreams(streamscache,filename,subindex,instance,extrahash)
386 loadedstreams[hash] = loaded
387 end
388 return loaded
389end
390
391hashes.streams = table.setmetatableindex(function(t,k)
392 local f = identifiers[k]
393 if f then
394 return loadstreamdata(f)
395 end
396end)
397
398otf.loadoutlinedata = loadoutlinedata
399otf.loadstreamdata = loadstreamdata
400otf.loadshapes = loadshapes
401otf.getstreamhash = getstreamhash
402 |