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
155local function makehash(filename,sub,instance)
156 local name = cleanname(file.basename(filename))
157 if instance then
158 return formatters["%s-%s-%s"](name,sub or 0,cleanname(instance))
159 else
160 return formatters["%s-%s"] (name,sub or 0)
161 end
162end
163
164local function loadoutlines(cache,filename,sub,instance)
165 local base = file.basename(filename)
166 local name = file.removesuffix(base)
167 local kind = file.suffix(filename)
168 local attr = lfs.attributes(filename)
169 local size = attr and attr.size or 0
170 local time = attr and attr.modification or 0
171 local sub = tonumber(sub)
172
173
174
175 if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then
176 local hash = makehash(filename,sub,instance)
177 data = containers.read(cache,hash)
178 if not data or data.time ~= time or data.size ~= size then
179 data = otf.readers.loadshapes(filename,sub,instance)
180 if data then
181 data.size = size
182 data.format = data.format or (kind == "otf" and "opentype") or "truetype"
183 data.time = time
184 packoutlines(data)
185 containers.write(cache,hash,data)
186 data = containers.read(cache,hash)
187 end
188 end
189 unpackoutlines(data)
190 elseif size > 0 and (kind == "pfb") then
191 local hash = containers.cleanname(base)
192 data = containers.read(cache,hash)
193 if not data or data.time ~= time or data.size ~= size then
194 data = afm.readers.loadshapes(filename)
195 if data then
196 data.size = size
197 data.format = "type1"
198 data.time = time
199 packoutlines(data)
200 containers.write(cache,hash,data)
201 data = containers.read(cache,hash)
202 end
203 end
204 unpackoutlines(data)
205 else
206 data = {
207 filename = filename,
208 size = 0,
209 time = time,
210 format = "unknown",
211 units = 1000,
212 glyphs = { }
213 }
214 end
215 return data
216end
217
218local function cachethem(cache,hash,data)
219 containers.write(cache,hash,data,compact_streams)
220 return containers.read(cache,hash)
221end
222
223local function loadstreams(cache,filename,sub,instance)
224 local base = file.basename(filename)
225 local name = file.removesuffix(base)
226 local kind = lower(file.suffix(filename))
227 local attr = lfs.attributes(filename)
228 local size = attr and attr.size or 0
229 local time = attr and attr.modification or 0
230 local sub = tonumber(sub)
231 if size > 0 and (kind == "otf" or kind == "ttf" or kind == "ttc") then
232 local hash = makehash(filename,sub,instance)
233 data = containers.read(cache,hash)
234 if not data or data.time ~= time or data.size ~= size then
235 data = otf.readers.loadshapes(filename,sub,instance,true)
236 if data then
237 local glyphs = data.glyphs
238 local streams = { }
239
240 if glyphs then
241 for i=0,#glyphs do
242 local glyph = glyphs[i]
243 if glyph then
244 streams[i] = glyph.stream or ""
245
246 else
247 streams[i] = ""
248
249 end
250 end
251 end
252 data.streams = streams
253
254 data.glyphs = nil
255 data.size = size
256 data.format = data.format or (kind == "otf" and "opentype") or "truetype"
257 data.time = time
258 data = cachethem(cache,hash,data)
259 end
260 end
261 elseif size > 0 and (kind == "pfb") then
262 local hash = makehash(filename,sub,instance)
263 data = containers.read(cache,hash)
264 if not data or data.time ~= time or data.size ~= size then
265 local names, encoding, streams, metadata = pfb.loadvector(filename,false,true)
266 if streams then
267 local fontbbox = metadata.fontbbox or { 0, 0, 0, 0 }
268
269 for i=0,#streams do
270 local s = streams[i]
271 streams[i] = s.stream or "\14"
272
273 end
274 data = {
275 filename = filename,
276 size = size,
277 time = time,
278 format = "type1",
279 streams = streams,
280
281 fontheader = {
282 fontversion = metadata.version,
283 units = 1000,
284 xmin = fontbbox[1],
285 ymin = fontbbox[2],
286 xmax = fontbbox[3],
287 ymax = fontbbox[4],
288 },
289 horizontalheader = {
290 ascender = 0,
291 descender = 0,
292 },
293 maximumprofile = {
294 nofglyphs = #streams + 1,
295 },
296 names = {
297 copyright = metadata.copyright,
298 family = metadata.familyname,
299 fullname = metadata.fullname,
300 fontname = metadata.fontname,
301 subfamily = metadata.subfamilyname,
302 trademark = metadata.trademark,
303 notice = metadata.notice,
304 version = metadata.version,
305 },
306 cffinfo = {
307 familyname = metadata.familyname,
308 fullname = metadata.fullname,
309 italicangle = metadata.italicangle,
310 monospaced = metadata.isfixedpitch and true or false,
311 underlineposition = metadata.underlineposition,
312 underlinethickness = metadata.underlinethickness,
313 weight = metadata.weight,
314 },
315 }
316 data = cachethem(cache,hash,data)
317 end
318 end
319 else
320 data = {
321 filename = filename,
322 size = 0,
323 time = time,
324 format = "unknown",
325 streams = { }
326 }
327 end
328 return data
329end
330
331local loadedshapes = { }
332local loadedstreams = { }
333
334local function loadoutlinedata(fontdata,streams)
335 local properties = fontdata.properties
336 local filename = properties.filename
337 local subindex = fontdata.subindex
338 local instance = properties.instance
339 local hash = makehash(filename,subindex,instance)
340 local loaded = loadedshapes[hash]
341 if not loaded then
342 loaded = loadoutlines(shapescache,filename,subindex,instance)
343 loadedshapes[hash] = loaded
344 end
345 return loaded
346end
347
348hashes.shapes = table.setmetatableindex(function(t,k)
349 local f = identifiers[k]
350 if f then
351 return loadoutlinedata(f)
352 end
353end)
354
355local function getstreamhash(fontid)
356 local fontdata = identifiers[fontid]
357 if fontdata then
358 local properties = fontdata.properties
359 local fonthash = makehash(properties.filename,properties.subfont,properties.instance)
360 return fonthash, fontdata
361 end
362end
363
364local function loadstreamdata(fontdata)
365 local properties = fontdata.properties
366 local shared = fontdata.shared
367 local rawdata = shared and shared.rawdata
368 local metadata = rawdata and rawdata.metadata
369 local filename = properties.filename
370 local subindex = metadata and metadata.subfontindex
371 local instance = properties.instance
372 local hash = makehash(filename,subindex,instance)
373 local loaded = loadedstreams[hash]
374 if not loaded then
375 loaded = loadstreams(streamscache,filename,subindex,instance)
376 loadedstreams[hash] = loaded
377 end
378 return loaded
379end
380
381hashes.streams = table.setmetatableindex(function(t,k)
382 local f = identifiers[k]
383 if f then
384 return loadstreamdata(f)
385 end
386end)
387
388otf.loadoutlinedata = loadoutlinedata
389otf.loadstreamdata = loadstreamdata
390otf.loadshapes = loadshapes
391otf.getstreamhash = getstreamhash
392
393local streams = fonts.hashes.streams
394
395
396
397callback.register("glyph_stream_provider",function(id,index,mode)
398 if id > 0 then
399 local streams = streams[id].streams
400
401 if streams then
402 return streams[index] or ""
403 end
404 end
405 return ""
406end)
407 |