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