1if not modules then modules = { } end modules ['font-ogr'] = {
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 tostring, tonumber, next, type, rawget = tostring, tonumber, next, type, rawget
13local round, max, mod, div = math.round, math.max, math.mod, math.div
14local find = string.find
15local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash
16local utfbyte = utf.byte
17local formatters = string.formatters
18local settings_to_hash_strict, settings_to_array = utilities.parsers.settings_to_hash_strict, utilities.parsers.settings_to_array
19
20local otf = fonts.handlers.otf
21local otfregister = otf.features.register
22otf.svgenabled = true
23otf.pngenabled = true
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39local report_fonts = logs.reporter("backend","fonts")
40local trace_fonts trackers.register("backend.fonts",function(v) trace_fonts = v end)
41
42do
43
44
45
46 local dropins = { }
47 fonts.dropins = dropins
48 local droppedin = 0
49 local identifiers = fonts.hashes.identifiers
50
51 function dropins.nextid()
52 droppedin = droppedin - 1
53 return droppedin
54 end
55
56
57
58 function dropins.provide(method,t_tfmdata,indexdata,...)
59 local droppedin = dropins.nextid()
60 local t_characters = t_tfmdata.characters
61 local t_descriptions = t_tfmdata.descriptions
62 local t_properties = t_tfmdata.properties
63 local d_tfmdata = setmetatableindex({ },t_tfmdata)
64 local d_properties = setmetatableindex({ },t_properties)
65 local d_basefontname = "ContextRuntimeFont" .. droppedin
66 d_properties.basefontname = d_basefontname
67 d_tfmdata.properties = d_properties
68 local d_characters = { }
69 local d_descriptions = { }
70 d_tfmdata.characters = d_characters
71 d_tfmdata.descriptions = d_descriptions
72 d_tfmdata.parentdata = t_tfmdata
73 d_properties.instance = - droppedin
74 identifiers[droppedin] = d_tfmdata
75 local fonts = t_tfmdata.fonts or { }
76 t_tfmdata.fonts = fonts
77 d_properties.format = "type3"
78 d_properties.method = method
79 d_properties.indexdata = { indexdata, ... }
80 local slot = #fonts + 1
81 fonts[slot] = { id = droppedin }
82 if trace_fonts then
83 report_fonts("registering dropin %a using method %a",d_basefontname,method)
84 end
85 return slot, droppedin, d_tfmdata, d_properties
86 end
87
88
89
90 function dropins.clone(method,tfmdata,shapes,...)
91 if method and shapes then
92 local characters = tfmdata.characters
93 local descriptions = tfmdata.descriptions
94 local droppedin, tfmdrop, dropchars, dropdescs, colrshapes, props
95 local idx = 255
96 local slot = 0
97
98 for k, v in next, characters do
99 local index = v.index
100 if index then
101 local description = descriptions[k]
102 if description then
103 local shape = shapes[index]
104 if shape then
105 if idx >= 255 then
106 idx = 1
107 colrshapes = setmetatableindex({ },shapes)
108 slot, droppedin, tfmdrop, props = dropins.provide(method,tfmdata,colrshapes)
109 dropchars = tfmdrop.characters
110 dropdescs = tfmdrop.descriptions
111 else
112 idx = idx + 1
113 end
114 colrshapes[idx] = shape
115
116 v.commands = { { "slot", slot, idx } }
117
118 local c = { commands = false, index = idx, dropin = tfmdrop }
119 local d = { }
120 setmetatableindex(c,v)
121 setmetatableindex(d,description)
122 dropchars[idx] = c
123 dropdescs[idx] = d
124 end
125 end
126 end
127 end
128 else
129
130 end
131 end
132
133 function dropins.swap(method,tfmdata,shapes)
134 if method and shapes then
135 local characters = tfmdata.characters
136 local descriptions = tfmdata.descriptions
137 local droppedin = tfmdata.droppedin
138 local tfmdrop = tfmdata.tfmdrop
139 local dropchars = tfmdata.dropchars
140 local dropdescs = tfmdata.dropdescs
141 local colrshapes = tfmdata.colrshapes
142 local idx = tfmdata.dropindex or 255
143 local slot = tfmdata.dropslot or 0
144
145 for k, v in next, characters do
146 local description = descriptions[k]
147 if description then
148 local shape = shapes[k]
149 if shape then
150 if idx >= 255 then
151 idx = 1
152 colrshapes = setmetatableindex({ },shapes)
153 slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
154 dropchars = tfmdrop.characters
155 dropdescs = tfmdrop.descriptions
156 else
157 idx = idx + 1
158 end
159 colrshapes[idx] = shape
160
161 v.commands = { { "slot", slot, idx } }
162
163 local c = { commands = false, index = idx, dropin = tfmdrop }
164 local d = { }
165 setmetatableindex(c,v)
166 setmetatableindex(d,description)
167 dropchars[idx] = c
168 dropdescs[idx] = d
169 end
170 end
171 end
172 tfmdata.droppedin = droppedin
173 tfmdata.tfmdrop = tfmdrop
174 tfmdata.dropchars = dropchars
175 tfmdata.dropdescs = dropdescs
176 tfmdata.colrshapes = colrshapes
177 tfmdata.dropindex = idx
178 tfmdata.dropslot = slot
179 else
180
181 end
182 end
183
184 function dropins.swapone(method,tfmdata,shape,unicode)
185 if method and shape then
186 local characters = tfmdata.characters
187 local descriptions = tfmdata.descriptions
188 local droppedin = tfmdata.droppedin
189 local tfmdrop = tfmdata.tfmdrop
190 local dropchars = tfmdata.dropchars
191
192 local colrshapes = tfmdata.colrshapes
193 local idx = tfmdata.dropindex or 255
194 local slot = tfmdata.dropslot or 0
195 local character = characters[unicode]
196
197 if character then
198 if idx >= 255 then
199 idx = 1
200 colrshapes = setmetatableindex({ },shapes)
201 slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
202 dropchars = tfmdrop.characters
203 dropdescs = tfmdrop.descriptions
204 else
205 idx = idx + 1
206 end
207 colrshapes[idx] = shape.code
208
209 character.commands = { { "slot", slot, idx } }
210
211 local c = { commands = false, index = idx, dropin = tfmdrop }
212
213 setmetatableindex(c,character)
214
215 dropchars[idx] = c
216
217 end
218 tfmdata.droppedin = droppedin
219 tfmdata.tfmdrop = tfmdrop
220 tfmdata.dropchars = dropchars
221
222 tfmdata.colrshapes = colrshapes
223 tfmdata.dropindex = idx
224 tfmdata.dropslot = slot
225 end
226 end
227
228end
229
230do
231
232 local dropins = fonts.dropins
233
234 local shapes = setmetatableindex(function(t,k)
235 local v = {
236 glyphs = { },
237 parameters = {
238 units = 10
239 },
240 }
241 t[k] = v
242 return v
243 end)
244
245 function dropins.getshape(name,n)
246 local s = shapes[name]
247 return s and s.glyphs and s.glyphs[n]
248 end
249
250 function dropins.getshapes(name)
251 return shapes[name]
252 end
253
254 function dropins.registerglyphs(parameters)
255 local category = parameters.name
256 local target = shapes[category].parameters
257 for k, v in next, parameters do
258 if k ~= "glyphs" then
259 target[k] = v
260 end
261 end
262 end
263
264 function dropins.registerglyph(parameters)
265 local category = parameters.category
266 local unicode = parameters.unicode
267 local private = parameters.private
268 local unichar = parameters.unichar
269 if private then
270 unicode = fonts.helpers.newprivateslot(private)
271 elseif type(unichar) == "string" then
272 unicode = utfbyte(unichar)
273 else
274 local unitype = type(unicode)
275 if unitype == "string" then
276 local uninumber = tonumber(unicode)
277 if uninumber then
278 unicode = round(uninumber)
279 else
280 unicode = utfbyte(unicode)
281 end
282 elseif unitype == "number" then
283 unicode = round(unicode)
284 end
285 end
286 if unicode then
287 parameters.unicode = unicode
288
289 shapes[category].glyphs[unicode] = parameters
290 else
291
292 end
293 end
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 local function hascolorspec(t)
310 for k, v in next, t do
311 if find(k,"color") then
312 return true
313 end
314 end
315 return false
316 end
317
318
319
320 local function initializemps(tfmdata,kind,value)
321 if value then
322 local specification = settings_to_hash_strict(value)
323 if not specification or not next(specification) then
324 specification = { category = value }
325 end
326
327
328
329 local category = specification.category
330 if category and category ~= "" then
331 local categories = settings_to_array(category)
332 local usedshapes = nil
333 local index = 0
334 local spread = tonumber(specification.spread or 0)
335 local hascolor = hascolorspec(specification)
336
337 specification.spread = spread
338
339 local preroll = specification.preroll
340 if preroll then
341 metapost.simple(instance,"begingroup;",true,true)
342 metapost.setparameterset("mpsfont",specification)
343 metapost.simple("simplefun",preroll)
344 metapost.setparameterset("mpsfont")
345 metapost.simple(instance,"endgroup;",true,true)
346 end
347
348 for i=1,#categories do
349 local category = categories[i]
350 local mpsshapes = shapes[category]
351 if mpsshapes then
352 local properties = tfmdata.properties
353 local parameters = tfmdata.parameters
354 local characters = tfmdata.characters
355 local descriptions = tfmdata.descriptions
356 local mpsparameters = mpsshapes.parameters
357 local units = mpsparameters.units or 1000
358 local defaultwidth = mpsparameters.width or 0
359 local defaultheight = mpsparameters.height or 0
360 local defaultdepth = mpsparameters.depth or 0
361 local usecolor = mpsparameters.usecolor
362 local spread = spread * units
363 local defaultcode = mpsparameters.code or ""
364 local scale = parameters.size / units
365 if hascolor then
366
367 usecolor = false
368 else
369
370 end
371 usedshapes = usedshapes or {
372 instance = "simplefun",
373 units = units,
374 usecolor = usecolor,
375 specification = specification,
376 shapes = mpsshapes,
377 }
378
379 for unicode, shape in sortedhash(mpsshapes.glyphs) do
380 index = index + 1
381 local wd = shape.width or defaultwidth
382 local ht = shape.height or defaultheight
383 local dp = shape.depth or defaultdepth
384 local bb = shape.boundingbox
385 local uc = shape.tounicode
386 if uc then
387 uc = round(uc)
388 end
389 if bb then
390 for i=1,4 do bb[i] = scale * bb[i] end
391 end
392 local newc = {
393 index = index,
394 width = scale * (wd + spread),
395 height = scale * ht,
396 depth = scale * dp,
397 boundingbox = bb,
398 unicode = uc or unicode,
399
400 }
401
402 characters [unicode] = newc
403 descriptions[unicode] = newc
404 usedshapes [unicode] = shape.code or defaultcode
405
406
407
408 if uc and uc ~= unicode then
409 local c = characters[uc]
410 if c then
411 local v = c.variants
412 if v then
413 v[#v + 1] = unicode
414 else
415 c.variants = { unicode }
416 end
417 end
418 end
419 end
420 end
421 end
422 if usedshapes then
423
424
425 dropins.swap("mps",tfmdata,usedshapes)
426 end
427 end
428 end
429 end
430
431
432
433
434 otfregister {
435 name = "metapost",
436 description = "metapost glyphs",
437 manipulators = {
438 base = initializemps,
439 node = initializemps,
440 }
441 }
442
443end
444
445
446
447local startactualtext = nil
448local stopactualtext = nil
449
450function otf.getactualtext(s)
451 if not startactualtext then
452 startactualtext = backends.codeinjections.startunicodetoactualtextdirect
453 stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect
454 end
455 return startactualtext(s), stopactualtext()
456end
457
458local sharedpalettes = { } do
459
460 local colors = attributes.list[attributes.private('color')] or { }
461 local transparencies = attributes.list[attributes.private('transparency')] or { }
462
463 function otf.registerpalette(name,values)
464 sharedpalettes[name] = values
465 for i=1,#values do
466 local v = values[i]
467 if v == "textcolor" then
468 values[i] = false
469 elseif type(v) == "table" then
470 values[i] = { kind = "values", data = v }
471 else
472 values[i] = { kind = "attributes", color = colors[v], transparency = transparencies[v] }
473 end
474 end
475 end
476
477end
478
479local initializeoverlay do
480
481 initializeoverlay = function(tfmdata,kind,value)
482 if value then
483 local resources = tfmdata.resources
484 local palettes = resources.colorpalettes
485 if palettes then
486 local colorvalues = false
487 local colordata = sharedpalettes[value]
488 if colordata and #colordata > 0 then
489 colorvalues = {
490 kind = "user",
491 data = colordata,
492 }
493 else
494 colordata = palettes[tonumber(value) or 1] or palettes[1]
495 if colordata and #colordata > 0 then
496 colorvalues = {
497 kind = "font",
498 data = colordata,
499 }
500 end
501 end
502 if colorvalues then
503 local characters = tfmdata.characters
504 local descriptions = tfmdata.descriptions
505 local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
506 local idx = 255
507 local slot = 0
508
509
510
511 for k, v in next, characters do
512 local index = v.index
513 if index then
514 local description = descriptions[k]
515 if description then
516 local colorlist = description.colors
517 if colorlist then
518 if idx >= 255 then
519 idx = 1
520 colrshapes = { }
521 slot, droppedin, tfmdrop = fonts.dropins.provide("color",tfmdata,colrshapes,colorvalues)
522 dropchars = tfmdrop.characters
523 dropdescs = tfmdrop.descriptions
524 else
525 idx = idx + 1
526 end
527
528 colrshapes[idx] = description
529
530 local u = { "use", 0 }
531 for i=1,#colorlist do
532 u[i+2] = colorlist[i].slot
533 end
534 v.commands = { u, { "slot", slot, idx } }
535
536 local c = { commands = false, index = idx, dropin = tfmdata }
537 local d = { }
538 setmetatableindex(c,v)
539 setmetatableindex(d,description)
540 dropchars[idx] = c
541 dropdescs[idx] = d
542 end
543 end
544 end
545 end
546 return true
547 end
548 end
549 end
550 end
551
552 fonts.handlers.otf.features.register {
553 name = "colr",
554 description = "color glyphs",
555 manipulators = {
556 base = initializeoverlay,
557 node = initializeoverlay,
558 }
559 }
560
561end
562
563local initializesvg do
564
565 local report_svg = logs.reporter("fonts","svg")
566
567 local cached = true
568
569 directives.register("fonts.svg.cached", function(v) cached = v end)
570
571 initializesvg = function(tfmdata,kind,value)
572 if value then
573 local properties = tfmdata.properties
574 local svg = properties.svg
575 local hash = svg and svg.hash
576 local timestamp = svg and svg.timestamp
577 if not hash then
578 return
579 end
580 local shapes = nil
581 local method = nil
582 local enforce = attributes.colors.model == "cmyk"
583 if cached and not enforce then
584
585 local pdfhash = hash .. "-svg"
586 local pdffile = containers.read(otf.pdfcache,pdfhash)
587 local pdfshapes = pdffile and pdffile.pdfshapes
588 local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf"))
589 if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then
590 local svgfile = containers.read(otf.svgcache,hash)
591 local svgshapes = svgfile and svgfile.svgshapes
592 pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg,tfmdata.parameters.units) or { }
593
594 containers.write(otf.pdfcache, pdfhash, {
595 pdfshapes = pdfshapes,
596 timestamp = timestamp,
597 })
598 end
599 shapes = pdfshapes
600 method = "pdf"
601 else
602 local mpsfile = containers.read(otf.mpscache,hash)
603 local mpsshapes = mpsfile and mpsfile.mpsshapes
604 if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then
605 local svgfile = containers.read(otf.svgcache,hash)
606 local svgshapes = svgfile and svgfile.svgshapes
607
608 mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg,tfmdata.parameters.units) or { }
609 if enforce then
610
611 mpsshapes.preamble = "interim svgforcecmyk := 1;"
612 end
613 containers.write(otf.mpscache, hash, {
614 mpsshapes = mpsshapes,
615 timestamp = timestamp,
616 })
617 end
618 shapes = mpsshapes
619 method = "mps"
620 end
621 if shapes then
622 shapes.fixdepth = value == "fixdepth"
623 fonts.dropins.clone(method,tfmdata,shapes)
624 end
625 return true
626 end
627 end
628
629 otfregister {
630 name = "svg",
631 description = "svg glyphs",
632 manipulators = {
633 base = initializesvg,
634 node = initializesvg,
635 }
636 }
637
638end
639
640local initializepng do
641
642
643
644
645 local colors = attributes.colors
646
647 local report_png = logs.reporter("fonts","png conversion")
648
649 initializepng = function(tfmdata,kind,value)
650 if value then
651 local properties = tfmdata.properties
652 local png = properties.png
653 local hash = png and png.hash
654 local timestamp = png and png.timestamp
655 if not hash then
656 return
657 end
658 local pngfile = containers.read(otf.pngcache,hash)
659 local pngshapes = pngfile and pngfile.pngshapes
660 if pngshapes then
661 if colors.model == "cmyk" then
662 pngshapes.enforcecmyk = true
663 end
664 fonts.dropins.clone("png",tfmdata,pngshapes)
665 end
666 return true
667 end
668 end
669
670 otfregister {
671 name = "sbix",
672 description = "sbix glyphs",
673 manipulators = {
674 base = initializepng,
675 node = initializepng,
676 }
677 }
678
679 otfregister {
680 name = "cblc",
681 description = "cblc glyphs",
682 manipulators = {
683 base = initializepng,
684 node = initializepng,
685 }
686 }
687
688end
689
690do
691
692
693
694
695 local function initializecolor(tfmdata,kind,value)
696 if value == "auto" then
697 return
698 initializeoverlay(tfmdata,kind,value) or
699 initializesvg(tfmdata,kind,value) or
700 initializepng(tfmdata,kind,value)
701 elseif value == "overlay" then
702 return initializeoverlay(tfmdata,kind,value)
703 elseif value == "svg" then
704 return initializesvg(tfmdata,kind,value)
705 elseif value == "png" or value == "bitmap" then
706 return initializepng(tfmdata,kind,value)
707 else
708
709 end
710 end
711
712 otfregister {
713 name = "color",
714 description = "color glyphs",
715 manipulators = {
716 base = initializecolor,
717 node = initializecolor,
718 }
719 }
720
721end
722 |