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