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