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(instance,"begingroup;",true,true)
344 metapost.setparameterset("mpsfont",specification)
345 metapost.simple("simplefun",preroll)
346 metapost.setparameterset("mpsfont")
347 metapost.simple(instance,"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 uc then
389 uc = round(uc)
390 end
391 if bb then
392 for i=1,4 do bb[i] = scale * bb[i] end
393 end
394 local newc = {
395 index = index,
396 width = scale * (wd + spread),
397 height = scale * ht,
398 depth = scale * dp,
399 boundingbox = bb,
400 unicode = uc or unicode,
401
402 }
403
404 characters [unicode] = newc
405 descriptions[unicode] = newc
406 usedshapes [unicode] = shape.code or defaultcode
407
408
409
410 if uc and uc ~= unicode then
411 local c = characters[uc]
412 if c then
413 local v = c.variants
414 if v then
415 v[#v + 1] = unicode
416 else
417 c.variants = { unicode }
418 end
419 end
420 end
421 end
422 end
423 end
424 if usedshapes then
425
426
427 dropins.swap("mps",tfmdata,usedshapes)
428 end
429 end
430 end
431 end
432
433
434
435
436 otfregister {
437 name = "metapost",
438 description = "metapost glyphs",
439 manipulators = {
440 base = initializemps,
441 node = initializemps,
442 }
443 }
444
445end
446
447
448
449local startactualtext = nil
450local stopactualtext = nil
451
452function otf.getactualtext(s)
453 if not startactualtext then
454 startactualtext = backends.codeinjections.startunicodetoactualtextdirect
455 stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect
456 end
457 return startactualtext(s), stopactualtext()
458end
459
460local sharedpalettes = { } do
461
462 local colors = attributes.list[attributes.private('color')] or { }
463 local transparencies = attributes.list[attributes.private('transparency')] or { }
464
465 function otf.registerpalette(name,values)
466 sharedpalettes[name] = values
467 for i=1,#values do
468 local v = values[i]
469 if v == "textcolor" then
470 values[i] = false
471 elseif type(v) == "table" then
472 values[i] = { kind = "values", data = v }
473 else
474 values[i] = { kind = "attributes", color = colors[v], transparency = transparencies[v] }
475 end
476 end
477 end
478
479end
480
481local initializeoverlay do
482
483 initializeoverlay = function(tfmdata,kind,value)
484 if value then
485 local resources = tfmdata.resources
486 local palettes = resources.colorpalettes
487 if palettes then
488 local colorvalues = false
489 local colordata = sharedpalettes[value]
490 if colordata and #colordata > 0 then
491 colorvalues = {
492 kind = "user",
493 data = colordata,
494 }
495 else
496 colordata = palettes[tonumber(value) or 1] or palettes[1]
497 if colordata and #colordata > 0 then
498 colorvalues = {
499 kind = "font",
500 data = colordata,
501 }
502 end
503 end
504 if colorvalues then
505 local characters = tfmdata.characters
506 local descriptions = tfmdata.descriptions
507 local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
508 local idx = 255
509 local slot = 0
510
511
512
513 for k, v in next, characters do
514 local index = v.index
515 if index then
516 local description = descriptions[k]
517 if description then
518 local colorlist = description.colors
519 if colorlist then
520 if idx >= 255 then
521 idx = 1
522 colrshapes = { }
523 slot, droppedin, tfmdrop = fonts.dropins.provide("color",tfmdata,colrshapes,colorvalues)
524 dropchars = tfmdrop.characters
525 dropdescs = tfmdrop.descriptions
526 else
527 idx = idx + 1
528 end
529
530 colrshapes[idx] = description
531
532 local u = { "use", 0 }
533 for i=1,#colorlist do
534 local c = colorlist[i]
535 if c then
536 u[i+2] = c.slot
537 else
538
539 end
540 end
541 v.commands = { u, slotcommand[slot][idx] }
542
543 local c = { commands = false, index = idx, dropin = tfmdata }
544 local d = { }
545 setmetatableindex(c,v)
546 setmetatableindex(d,description)
547 dropchars[idx] = c
548 dropdescs[idx] = d
549 end
550 end
551 end
552 end
553 return true
554 end
555 end
556 end
557 end
558
559 fonts.handlers.otf.features.register {
560 name = "colr",
561 description = "color glyphs",
562 manipulators = {
563 base = initializeoverlay,
564 node = initializeoverlay,
565 plug = initializeoverlay,
566 }
567 }
568
569end
570
571local initializesvg do
572
573 local report_svg = logs.reporter("fonts","svg")
574
575 local cached = true
576
577 directives.register("fonts.svg.cached", function(v) cached = v end)
578
579 initializesvg = function(tfmdata,kind,value)
580 if value then
581 local properties = tfmdata.properties
582 local svg = properties.svg
583 local hash = svg and svg.hash
584 local timestamp = svg and svg.timestamp
585 if not hash then
586 return
587 end
588 local shapes = nil
589 local method = nil
590 local enforce = attributes.colors.model == "cmyk"
591 if cached and not enforce then
592
593 local pdfhash = hash .. "-svg"
594 local pdffile = containers.read(otf.pdfcache,pdfhash)
595 local pdfshapes = pdffile and pdffile.pdfshapes
596 local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf"))
597 if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then
598 local svgfile = containers.read(otf.svgcache,hash)
599 local svgshapes = svgfile and svgfile.svgshapes
600 pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg,tfmdata.parameters.units) or { }
601
602 containers.write(otf.pdfcache, pdfhash, {
603 pdfshapes = pdfshapes,
604 timestamp = timestamp,
605 })
606 end
607 shapes = pdfshapes
608 method = "pdf"
609 else
610 local mpsfile = containers.read(otf.mpscache,hash)
611 local mpsshapes = mpsfile and mpsfile.mpsshapes
612 if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then
613 local svgfile = containers.read(otf.svgcache,hash)
614 local svgshapes = svgfile and svgfile.svgshapes
615
616 mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg,tfmdata.parameters.units) or { }
617 if enforce then
618
619 mpsshapes.preamble = "interim svgforcecmyk := 1;"
620 end
621 containers.write(otf.mpscache, hash, {
622 mpsshapes = mpsshapes,
623 timestamp = timestamp,
624 })
625 end
626 shapes = mpsshapes
627 method = "mps"
628 end
629 if shapes then
630 shapes.fixdepth = value == "fixdepth"
631 fonts.dropins.clone(method,tfmdata,shapes)
632 end
633 return true
634 end
635 end
636
637 otfregister {
638 name = "svg",
639 description = "svg glyphs",
640 manipulators = {
641 base = initializesvg,
642 node = initializesvg,
643 plug = initializesvg,
644 }
645 }
646
647end
648
649local initializepng do
650
651
652
653
654 local colors = attributes.colors
655
656 local report_png = logs.reporter("fonts","png conversion")
657
658 initializepng = function(tfmdata,kind,value)
659 if value then
660 local properties = tfmdata.properties
661 local png = properties.png
662 local hash = png and png.hash
663 local timestamp = png and png.timestamp
664 if not hash then
665 return
666 end
667 local pngfile = containers.read(otf.pngcache,hash)
668 local pngshapes = pngfile and pngfile.pngshapes
669 if pngshapes then
670 if colors.model == "cmyk" then
671 pngshapes.enforcecmyk = true
672 end
673 fonts.dropins.clone("png",tfmdata,pngshapes)
674 end
675 return true
676 end
677 end
678
679 otfregister {
680 name = "sbix",
681 description = "sbix glyphs",
682 manipulators = {
683 base = initializepng,
684 node = initializepng,
685 plug = initializepng,
686 }
687 }
688
689 otfregister {
690 name = "cblc",
691 description = "cblc glyphs",
692 manipulators = {
693 base = initializepng,
694 node = initializepng,
695 plug = initializepng,
696 }
697 }
698
699end
700
701do
702
703
704
705
706 local function initializecolor(tfmdata,kind,value)
707 if value == "auto" then
708 return
709 initializeoverlay(tfmdata,kind,value) or
710 initializesvg(tfmdata,kind,value) or
711 initializepng(tfmdata,kind,value)
712 elseif value == "overlay" then
713 return initializeoverlay(tfmdata,kind,value)
714 elseif value == "svg" then
715 return initializesvg(tfmdata,kind,value)
716 elseif value == "png" or value == "bitmap" then
717 return initializepng(tfmdata,kind,value)
718 else
719
720 end
721 end
722
723 otfregister {
724 name = "color",
725 description = "color glyphs",
726 manipulators = {
727 base = initializecolor,
728 node = initializecolor,
729 plug = initializecolor,
730 }
731 }
732
733end
734 |