1if not modules then modules = { } end modules ['font-col'] = {
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
9
10
11
12local context, commands, trackers, logs = context, commands, trackers, logs
13local node, nodes, fonts, characters = node, nodes, fonts, characters
14local file, lpeg, table, string = file, lpeg, table, string
15
16local type, next, tonumber, toboolean = type, next, tonumber, toboolean
17local gmatch = string.gmatch
18local fastcopy = table.fastcopy
19local formatters = string.formatters
20
21local nuts = nodes.nuts
22
23local setfont = nuts.setfont
24
25local nextchar = nuts.traversers.char
26
27local settings_to_hash = utilities.parsers.settings_to_hash
28
29local trace_collecting = false trackers.register("fonts.collecting", function(v) trace_collecting = v end)
30
31local report_fonts = logs.reporter("fonts","collections")
32
33local enableaction = nodes.tasks.enableaction
34local disableaction = nodes.tasks.disableaction
35
36local collections = fonts.collections or { }
37fonts.collections = collections
38
39local definitions = collections.definitions or { }
40collections.definitions = definitions
41
42local vectors = collections.vectors or { }
43collections.vectors = vectors
44
45local helpers = fonts.helpers
46local charcommand = helpers.commands.char
47local rightcommand = helpers.commands.right
48local addprivate = helpers.addprivate
49local hasprivate = helpers.hasprivate
50local fontpatternhassize = helpers.fontpatternhassize
51
52local hashes = fonts.hashes
53local fontdata = hashes.identifiers
54local fontquads = hashes.quads
55local chardata = hashes.characters
56local propdata = hashes.properties
57local mathparameters = hashes.mathparameters
58
59local currentfont = font.current
60local addcharacters = font.addcharacters
61
62local implement = interfaces.implement
63
64local list = { }
65local current = 0
66local enabled = false
67
68local validvectors = table.setmetatableindex(function(t,k)
69 local v = false
70 if not mathparameters[k] then
71 v = vectors[k]
72 end
73 t[k] = v
74 return v
75end)
76
77local function checkenabled()
78
79 if next(vectors) then
80 if not enabled then
81 enableaction("processors","fonts.collections.process")
82 enabled = true
83 end
84 else
85 if enabled then
86 disableaction("processors","fonts.collections.process")
87 enabled = false
88 end
89 end
90end
91
92collections.checkenabled = checkenabled
93
94function collections.reset(name,font)
95 if font and font ~= "" then
96 local d = definitions[name]
97 if d then
98 d[font] = nil
99 if not next(d) then
100 definitions[name] = nil
101 end
102 end
103 else
104 definitions[name] = nil
105 end
106end
107
108function collections.define(name,font,ranges,details)
109
110
111 local d = definitions[name]
112 if not d then
113 d = { }
114 definitions[name] = d
115 end
116 if name and trace_collecting then
117 report_fonts("extending collection %a using %a",name,font)
118 end
119 details = settings_to_hash(details)
120
121 local offset = details.offset
122 if type(offset) == "string" then
123 offset = characters.getrange(offset,true) or false
124 else
125 offset = tonumber(offset) or false
126 end
127 local target = details.target
128 if type(target) == "string" then
129 target = characters.getrange(target,true) or false
130 else
131 target = tonumber(target) or false
132 end
133 local rscale = tonumber (details.rscale) or 1
134 local force = toboolean(details.force,true)
135 local check = toboolean(details.check,true)
136 local factor = tonumber(details.factor)
137 local features = details.features
138 for s in gmatch(ranges,"[^, ]+") do
139 local start, stop, description, gaps = characters.getrange(s,true)
140 if start and stop then
141 if trace_collecting then
142 if description then
143 report_fonts("using range %a, slots %U - %U, description %a)",s,start,stop,description)
144 end
145 for i=1,#d do
146 local di = d[i]
147 if (start >= di.start and start <= di.stop) or (stop >= di.start and stop <= di.stop) then
148 report_fonts("overlapping ranges %U - %U and %U - %U",start,stop,di.start,di.stop)
149 end
150 end
151 end
152 d[#d+1] = {
153 font = font,
154 start = start,
155 stop = stop,
156 gaps = gaps,
157 offset = offset,
158 target = target,
159 rscale = rscale,
160 force = force,
161 check = check,
162 method = details.method,
163 factor = factor,
164 features = features,
165 }
166 end
167 end
168end
169
170
171
172function collections.registermain(name)
173 local last = currentfont()
174 if trace_collecting then
175 report_fonts("registering font %a with name %a",last,name)
176 end
177 list[#list+1] = last
178end
179
180
181
182
183local uccodes = characters.uccodes
184local lccodes = characters.lccodes
185
186local methods = {
187 lowercase = function(oldchars,newchars,vector,start,stop,cloneid)
188 for k, v in next, oldchars do
189 if k >= start and k <= stop then
190 local lccode = lccodes[k]
191 if k ~= lccode and newchars[lccode] then
192 vector[k] = { cloneid, lccode }
193 end
194 end
195 end
196 end,
197 uppercase = function(oldchars,newchars,vector,start,stop,cloneid)
198 for k, v in next, oldchars do
199 if k >= start and k <= stop then
200 local uccode = uccodes[k]
201 if k ~= uccode and newchars[uccode] then
202 vector[k] = { cloneid, uccode }
203 end
204 end
205 end
206 end,
207}
208
209function collections.clonevector(name)
210 statistics.starttiming(fonts)
211 if trace_collecting then
212 report_fonts("processing collection %a",name)
213 end
214 local definitions = definitions[name]
215 local vector = { }
216 vectors[current] = vector
217 for i=1,#definitions do
218 local definition = definitions[i]
219 local name = definition.font
220 local start = definition.start
221 local stop = definition.stop
222 local check = definition.check
223 local force = definition.force
224 local offset = definition.offset or start
225 local remap = definition.remap
226 local target = definition.target
227 local method = definition.method
228 local cloneid = list[i]
229 local oldchars = fontdata[current].characters
230 local newchars = fontdata[cloneid].characters
231 local factor = definition.factor
232 if factor then
233 vector.factor = factor
234 end
235 if trace_collecting then
236 if target then
237 report_fonts("remapping font %a to %a for range %U - %U, offset %X, target %U",current,cloneid,start,stop,offset,target)
238 else
239 report_fonts("remapping font %a to %a for range %U - %U, offset %X",current,cloneid,start,stop,offset)
240 end
241 end
242 if method then
243 method = methods[method]
244 end
245 if method then
246 method(oldchars,newchars,vector,start,stop,cloneid)
247 elseif check then
248 if target then
249 for unicode = start, stop do
250 local unic = unicode + offset - start
251 if not newchars[target] then
252
253 elseif force or (not vector[unic] and not oldchars[unic]) then
254 vector[unic] = { cloneid, target }
255 end
256 target = target + 1
257 end
258 elseif remap then
259
260 else
261 for unicode = start, stop do
262 local unic = unicode + offset - start
263 if not newchars[unicode] then
264
265 elseif force or (not vector[unic] and not oldchars[unic]) then
266 vector[unic] = cloneid
267 end
268 end
269 end
270 else
271 if target then
272 for unicode = start, stop do
273 local unic = unicode + offset - start
274 if force or (not vector[unic] and not oldchars[unic]) then
275 vector[unic] = { cloneid, target }
276 end
277 target = target + 1
278 end
279 elseif remap then
280 for unicode = start, stop do
281 local unic = unicode + offset - start
282 if force or (not vector[unic] and not oldchars[unic]) then
283 vector[unic] = { cloneid, remap[unicode] }
284 end
285 end
286 else
287 for unicode = start, stop do
288 local unic = unicode + offset - start
289 if force or (not vector[unic] and not oldchars[unic]) then
290 vector[unic] = cloneid
291 end
292 end
293 end
294 end
295 end
296 if trace_collecting then
297 report_fonts("activating collection %a for font %a",name,current)
298 end
299 statistics.stoptiming(fonts)
300
301 if validvectors[current] then
302 checkenabled()
303 end
304end
305
306
307
308
309
310
311
312
313function collections.prepare(name)
314 current = currentfont()
315 if vectors[current] then
316 return
317 end
318 local properties = propdata[current]
319 local mathsize = properties.mathsize
320 if mathsize == 1 or mathsize == 2 or mathsize == 3 then
321 return
322 end
323 local d = definitions[name]
324 if d then
325 if trace_collecting then
326 local filename = file.basename(properties.filename or "?")
327 report_fonts("applying collection %a to %a, file %a",name,current,filename)
328 end
329 list = { }
330 context.pushcatcodes("prt")
331 context.font_fallbacks_start_cloning()
332 for i=1,#d do
333 local f = d[i]
334 local name = f.font
335 local scale = f.rscale or 1
336 if fontpatternhassize(name) then
337 context.font_fallbacks_clone_unique(name,scale)
338 else
339 context.font_fallbacks_clone_inherited(name,scale)
340 end
341 context.font_fallbacks_register_main(name)
342 end
343 context.font_fallbacks_prepare_clone_vectors(name)
344 context.font_fallbacks_stop_cloning()
345 context.popcatcodes()
346 end
347end
348
349function collections.report(message)
350 if trace_collecting then
351 report_fonts("tex: %s",message)
352 end
353end
354
355local function monoslot(font,char,parent,factor)
356 local tfmdata = fontdata[font]
357 local privatename = formatters["faked mono %s"](char)
358 local privateslot = hasprivate(tfmdata,privatename)
359 if privateslot then
360 return privateslot
361 else
362 local characters = tfmdata.characters
363 local properties = tfmdata.properties
364 local width = factor * fontquads[parent]
365 local character = characters[char]
366 if character then
367
368
369 local data = {
370
371 width = width,
372 height = character.height,
373 depth = character.depth,
374 commands = {
375 rightcommand[(width - character.width or 0)/2],
376 charcommand[char],
377 }
378 }
379 local u = addprivate(tfmdata, privatename, data)
380 addcharacters(properties.id, {
381 type = "real",
382 characters = {
383 [u] = data
384 },
385 } )
386 return u
387 else
388 return char
389 end
390 end
391end
392
393function collections.process(head)
394 for n, char, font in nextchar, head do
395 local vector = validvectors[font]
396 if vector then
397 local vect = vector[char]
398 if not vect then
399
400 elseif type(vect) == "table" then
401 local newfont = vect[1]
402 local newchar = vect[2]
403 if trace_collecting then
404 report_fonts("remapping character %C in font %a to character %C in font %a%s",
405 char,font,newchar,newfont,not chardata[newfont][newchar] and " (missing)" or ""
406 )
407 end
408 setfont(n,newfont,newchar)
409 else
410 local fakemono = vector.factor
411 if trace_collecting then
412 report_fonts("remapping font %a to %a for character %C%s",
413 font,vect,char,not chardata[vect][char] and " (missing)" or ""
414 )
415 end
416 if fakemono then
417 setfont(n,vect,monoslot(vect,char,font,fakemono))
418 else
419 setfont(n,vect)
420 end
421 end
422 end
423 end
424 return head
425end
426
427function collections.found(font,char)
428 if not char then
429 font, char = currentfont(), font
430 end
431 if chardata[font][char] then
432 return true
433 else
434 local v = vectors[font]
435 return v and v[char] and true or false
436 end
437end
438
439
440
441implement {
442 name = "fontcollectiondefine",
443 actions = collections.define,
444 arguments = "4 strings",
445}
446
447implement {
448 name = "fontcollectionreset",
449 actions = collections.reset,
450 arguments = "2 strings",
451}
452
453implement {
454 name = "fontcollectionprepare",
455 actions = collections.prepare,
456 arguments = "string"
457}
458
459implement {
460 name = "fontcollectionreport",
461 actions = collections.report,
462 arguments = "string"
463}
464
465implement {
466 name = "fontcollectionregister",
467 actions = collections.registermain,
468 arguments = "string"
469}
470
471implement {
472 name = "fontcollectionclone",
473 actions = collections.clonevector,
474 arguments = "string"
475}
476
477implement {
478 name = "doifelsecharinfont",
479 actions = { collections.found, commands.doifelse },
480 arguments = "integer"
481}
482 |