1if not modules then modules = { } end modules ['font-ocm'] = {
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
9if not context then
10 return
11elseif CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
12 return
13else
14
15
16
17
18
19
20end
21
22local tostring, tonumber, next = tostring, tonumber, next
23local round, max = math.round, math.round
24local sortedkeys, sortedhash, concat = table.sortedkeys, table.sortedhash, table.concat
25local setmetatableindex = table.setmetatableindex
26local formatters = string.formatters
27
28local otf = fonts.handlers.otf
29local otfregister = otf.features.register
30local bpfactor = number.dimenfactors.bp
31local typethree = { }
32
33callback.register("provide_charproc_data",function(action,f,...)
34 local registered = typethree[f]
35 if registered then
36 return registered(action,f,...)
37 else
38 return 0, 0
39 end
40end)
41
42local defaults = {
43 function() return 0, 0 end,
44 function() return 0, 0 end,
45 function() return 0.001, "" end,
46}
47
48local function registeractions(t)
49 return {
50 t.preroll or defaults[1],
51 t.collect or defaults[2],
52 t.wrapup or defaults[3],
53 }
54end
55
56local function registertypethreeresource(specification,n,o)
57 specification.usedobjects["X"..n] = lpdf.reference(o)
58end
59
60local function registertypethreefont(specification,n,o)
61 specification.usedfonts["F"..n] = lpdf.reference(o)
62end
63
64local function typethreeresources(specification)
65 local usedobjects = specification.usedobjects
66 local usedfonts = specification.usedfonts
67 local resources = { }
68 if next(usedobjects) then
69 resources[#resources+1] = "/XObject << " .. usedobjects() .. " >>"
70 end
71 if next(usedfonts) then
72 resources[#resources+1] = "/Font << " .. usedfonts() .. " >>"
73 end
74
75 specification.usedfonts = nil
76 specification.usedobjects = nil
77 return concat(resources, " ")
78end
79
80local function registerfont(specification,actions)
81 specification.usedfonts = lpdf.dictionary()
82 specification.usedobjects = lpdf.dictionary()
83 typethree[specification.id] = function(action,f,c)
84 return actions[action](specification,f,c)
85 end
86end
87
88fonts.handlers.typethree = {
89 register = function(id,handler)
90
91 if not typethree[id] then
92 logs.report("fonts","low level Type3 handler registered for font with id %i",id)
93 typethree[id] = handler
94 end
95 end
96}
97
98local initializeoverlay do
99
100 local f_color = formatters["%.3f %.3f %.3f rg"]
101 local f_gray = formatters["%.3f g"]
102 local sharedpalettes = { }
103 local colors = attributes.list[attributes.private('color')] or { }
104 local transparencies = attributes.list[attributes.private('transparency')] or { }
105
106 function otf.registerpalette(name,values)
107 sharedpalettes[name] = values
108 local color = lpdf.color
109 local transparency = lpdf.transparency
110 local register = colors.register
111 for i=1,#values do
112 local v = values[i]
113 if v == "textcolor" then
114 values[i] = false
115 else
116 local c = nil
117 local t = nil
118 if type(v) == "table" then
119 c = register(name,"rgb",
120 max(round((v.r or 0)*255),255)/255,
121 max(round((v.g or 0)*255),255)/255,
122 max(round((v.b or 0)*255),255)/255
123 )
124 else
125 c = colors[v]
126 t = transparencies[v]
127 end
128 if c and t then
129 values[i] = color(1,c) .. " " .. transparency(t)
130 elseif c then
131 values[i] = color(1,c)
132 elseif t then
133 values[i] = color(1,t)
134 end
135 end
136 end
137 end
138
139 local function convert(t,k)
140 local v = { }
141 for i=1,#k do
142 local p = k[i]
143 local r, g, b = p[1], p[2], p[3]
144 if r == g and g == b then
145 v[i] = f_gray(r/255)
146 else
147 v[i] = f_color(r/255,g/255,b/255)
148 end
149 end
150 t[k] = v
151 return v
152 end
153
154
155
156
157
158
159 local f_stream = formatters["%s 0 d0 %s 0 0 %s 0 %s cm /X%i Do"]
160 local fontorder = 0
161 local actions = registeractions {
162
163 preroll = function(specification,f,c)
164 local data = specification.delegated[c]
165 local colorlist = data.colorlist
166 local colorvalues = specification.colorvalues
167 local default = specification.default
168 local mainid = specification.mainid
169 local t = { "\\typethreefont{", mainid, "}" }
170 local n = 3
171 local l = nil
172 local m = #colorlist
173 for i=1,m do
174 local entry = colorlist[i]
175 local v = colorvalues[entry.class] or default
176 if v and l ~= v then
177 n = n + 1 ; t[n] = "\\typethreecode{"
178 n = n + 1 ; t[n] = v
179 n = n + 1 ; t[n] = "}"
180 l = v
181 end
182 if i < m then
183 n = n + 1 ; t[n] = "\\typethreechar{"
184 else
185 n = n + 1 ; t[n] = "\\typethreelast{"
186 end
187 n = n + 1 ; t[n] = entry.slot
188 n = n + 1 ; t[n] = "}"
189 end
190 token.set_macro("typethreemacro",concat(t))
191 tex.runlocal("typethreetoks")
192 registertypethreeresource(specification,c,tex.saveboxresource(0,nil,lpdf.collectedresources(),true))
193
194 return 0, 0
195 end,
196
197 collect = function(specification,f,c)
198 local parameters = specification.parameters
199 local data = specification.delegated[c]
200 local factor = parameters.hfactor
201 local units = parameters.units
202 local width = (data.width or 0) / factor
203 local scale = 100
204 local factor = units * bpfactor
205 local depth = (data.depth or 0)*factor
206 local shift = - depth / (10*units/1000)
207 local object = pdf.immediateobj("stream",f_stream(width,scale,scale,shift,c))
208 return object, width
209 end,
210
211 wrapup = function(specification,f)
212 return 0.001, typethreeresources(specification)
213 end,
214
215 }
216
217 local function register(specification)
218 registerfont(specification,actions)
219 end
220
221 initializeoverlay = function(tfmdata,kind,value)
222 if value then
223 local resources = tfmdata.resources
224 local palettes = resources.colorpalettes
225 if palettes then
226 local converted = resources.converted
227 if not converted then
228 converted = setmetatableindex(convert)
229 resources.converted = converted
230 end
231 local colorvalues = sharedpalettes[value]
232 local default = false
233 if colorvalues then
234 default = colorvalues[#colorvalues]
235 else
236 colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { }
237 end
238 local classes = #colorvalues
239 if classes == 0 then
240 return
241 end
242
243 local characters = tfmdata.characters
244 local descriptions = tfmdata.descriptions
245 local properties = tfmdata.properties
246 local parameters = tfmdata.parameters
247
248 properties.virtualized = true
249
250 local delegated = { }
251 local index = 0
252 local fonts = tfmdata.fonts or { }
253 local fontindex = #fonts + 1
254 tfmdata.fonts = fonts
255
256 local function flush()
257 if index > 0 then
258 fontorder = fontorder + 1
259 local f = {
260 characters = delegated,
261 parameters = parameters,
262 tounicode = true,
263 format = "type3",
264 name = "InternalTypeThreeFont" ,
265 psname = "none",
266 }
267 fonts[fontindex] = {
268 id = font.define(f),
269 delegated = delegated,
270 parameters = parameters,
271 colorvalues = colorvalues,
272 default = default,
273 }
274 end
275 fontindex = fontindex + 1
276 index = 0
277 delegated = { }
278 end
279
280 for unicode, character in sortedhash(characters) do
281 local description = descriptions[unicode]
282 if description then
283 local colorlist = description.colors
284 if colorlist then
285 if index == 255 then
286 flush()
287 end
288 index = index + 1
289 delegated[index] = {
290 width = character.width,
291 height = character.height,
292 depth = character.depth,
293 tounicode = character.tounicode,
294 colorlist = colorlist,
295 }
296 character.commands = {
297 { "slot", fontindex, index },
298 }
299 end
300 end
301 end
302
303 flush()
304 local mainid = font.nextid()
305 for i=1,#fonts do
306 local f = fonts[i]
307 if f.delegated then
308 f.mainid = mainid
309 register(f)
310 end
311 end
312
313 return true
314 end
315 end
316 end
317
318 otfregister {
319 name = "colr",
320 description = "color glyphs",
321 manipulators = {
322 base = initializeoverlay,
323 node = initializeoverlay,
324 }
325 }
326
327end
328
329do
330
331 local nofstreams = 0
332 local f_name = formatters[ [[pdf-glyph-%05i]] ]
333 local f_used = context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ]
334 local hashed = { }
335 local cache = { }
336
337 local openpdf = pdfe.new
338
339 function otf.storepdfdata(pdf)
340 if pdf then
341 local done = hashed[pdf]
342 if not done then
343 nofstreams = nofstreams + 1
344 local f = f_name(nofstreams)
345 local n = openpdf(pdf,#pdf,f)
346 done = f_used(n)
347 hashed[pdf] = done
348 end
349 return done
350 end
351 end
352
353end
354
355local pdftovirtual do
356
357 local f_stream = formatters["%s 0 d0 %s 0 0 %s %s %s cm /X%i Do"]
358 local fontorder = 0
359 local shared = { }
360 local actions = registeractions {
361
362 preroll = function(specification,f,c)
363 return 0, 0
364 end,
365
366 collect = function(specification,f,c)
367 local parameters = specification.parameters
368 local data = specification.delegated[c]
369 local desdata = data.desdata
370 local pdfdata = data.pdfdata
371 local width = desdata.width or 0
372 local height = desdata.height or 0
373 local depth = desdata.depth or 0
374 local factor = parameters.hfactor
375 local units = parameters.units
376 local typ = type(pdfdata)
377
378 local dx = 0
379 local dy = 0
380 local scale = 1
381
382 if typ == "table" then
383 data = pdfdata.data
384 dx = pdfdata.x or pdfdata.dx or 0
385 dy = pdfdata.y or pdfdata.dy or 0
386 scale = pdfdata.scale or 1
387 elseif typ == "string" then
388 data = pdfdata
389 dx = 0
390 dy = 0
391 else
392 return 0, 0
393 end
394
395 if not data then
396 return 0, 0
397 end
398
399 local name = otf.storepdfdata(data)
400 local xform = shared[name]
401
402 if not xform then
403 xform = images.embed(images.create { filename = name })
404 shared[name] = xform
405 end
406
407 registertypethreeresource(specification,c,xform.objnum)
408
409 scale = scale * (width / (xform.width * bpfactor))
410 dy = - depth + dy
411
412
413
414 local object = pdf.immediateobj("stream",f_stream(width,scale,scale,dx,dy,c)), width
415
416 return object, width
417 end,
418
419 wrapup = function(specification,f)
420 return 1/specification.parameters.units, typethreeresources(specification)
421 end,
422
423 }
424
425 local function register(specification)
426 registerfont(specification,actions)
427 end
428
429 pdftovirtual = function(tfmdata,pdfshapes,kind)
430 if not tfmdata or not pdfshapes or not kind then
431 return
432 end
433
434 local characters = tfmdata.characters
435 local descriptions = tfmdata.descriptions
436 local properties = tfmdata.properties
437 local parameters = tfmdata.parameters
438 local hfactor = parameters.hfactor
439
440 properties.virtualized = true
441
442 local storepdfdata = otf.storepdfdata
443
444 local delegated = { }
445 local index = 0
446 local fonts = tfmdata.fonts or { }
447 local fontindex = #fonts + 1
448 tfmdata.fonts = fonts
449
450 local function flush()
451 if index > 0 then
452 fontorder = fontorder + 1
453 local f = {
454 characters = delegated,
455 parameters = parameters,
456 tounicode = true,
457 format = "type3",
458 name = "InternalTypeThreeFont" .. fontorder,
459 psname = "none",
460 size = parameters.size,
461 }
462 fonts[fontindex] = {
463 id = font.define(f),
464 delegated = delegated,
465 parameters = parameters,
466 }
467 end
468 fontindex = fontindex + 1
469 index = 0
470 delegated = { }
471 end
472
473 for unicode, character in sortedhash(characters) do
474 local idx = character.index
475 if idx then
476 local pdfdata = pdfshapes[idx]
477 local description = descriptions[unicode]
478 if pdfdata and description then
479 if index == 255 then
480 flush()
481 end
482 index = index + 1
483 delegated[index] = {
484 desdata = description,
485 width = character.width,
486 height = character.width,
487 depth = character.width,
488 tounicode = character.tounicode,
489 pdfdata = pdfdata,
490 }
491 character.commands = {
492 { "slot", fontindex, index },
493 }
494 end
495 end
496 end
497
498 flush()
499 local mainid = font.nextid()
500 for i=1,#fonts do
501 local f = fonts[i]
502 if f.delegated then
503 f.mainid = mainid
504 register(f)
505 end
506 end
507
508 end
509
510end
511
512local initializesvg do
513
514 local otfsvg = otf.svg or { }
515 otf.svg = otfsvg
516 otf.svgenabled = true
517
518 local report_svg = logs.reporter("fonts","svg conversion")
519
520 local loaddata = io.loaddata
521 local savedata = io.savedata
522 local remove = os.remove
523
524 local xmlconvert = xml.convert
525 local xmlfirst = xml.first
526
527 function otfsvg.filterglyph(entry,index)
528 local d = entry.data
529 if gzip.compressed(d) then
530 d = gzip.decompress(d) or d
531 end
532 local svg = xmlconvert(d)
533 local root = svg and xmlfirst(svg,"/svg[@id='glyph"..index.."']")
534 local data = root and tostring(root)
535 return data
536 end
537
538 local runner = sandbox and sandbox.registerrunner {
539 name = "otfsvg",
540 program = "inkscape",
541 method = "pipeto",
542 template = "--export-area-drawing --shell > temp-otf-svg-shape.log",
543 reporter = report_svg,
544 }
545
546 if not runner then
547
548
549
550 runner = function()
551 return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w")
552 end
553 end
554
555
556
557
558
559
560
561
562
563
564
565
566 local new = nil
567
568 local function inkscapeformat(suffix)
569 if new == nil then
570 new = os.resultof("inkscape --version") or ""
571 new = new == "" or not find(new,"Inkscape%s*0")
572 end
573 return new and "filename" or suffix
574 end
575
576 function otfsvg.topdf(svgshapes,tfmdata)
577 local pdfshapes = { }
578 local inkscape = runner()
579 if inkscape then
580
581 local descriptions = tfmdata.descriptions
582 local nofshapes = #svgshapes
583 local s_format = inkscapeformat("pdf")
584 local f_svgfile = formatters["temp-otf-svg-shape-%i.svg"]
585 local f_pdffile = formatters["temp-otf-svg-shape-%i.pdf"]
586 local f_convert = formatters[new and "file-open:%s; export-%s:%s; export-do\n" or "%s --export-%s=%s\n"]
587 local filterglyph = otfsvg.filterglyph
588 local nofdone = 0
589 local processed = { }
590 report_svg("processing %i svg containers",nofshapes)
591 statistics.starttiming()
592 for i=1,nofshapes do
593 local entry = svgshapes[i]
594 for index=entry.first,entry.last do
595 local data = filterglyph(entry,index)
596 if data and data ~= "" then
597 local svgfile = f_svgfile(index)
598 local pdffile = f_pdffile(index)
599 savedata(svgfile,data)
600 inkscape:write(f_convert(svgfile,s_format,pdffile))
601 processed[index] = true
602 nofdone = nofdone + 1
603 if nofdone % 25 == 0 then
604 report_svg("%i shapes submitted",nofdone)
605 end
606 end
607 end
608 end
609 if nofdone % 25 ~= 0 then
610 report_svg("%i shapes submitted",nofdone)
611 end
612 report_svg("processing can be going on for a while")
613 inkscape:write("quit\n")
614 inkscape:close()
615 report_svg("processing %i pdf results",nofshapes)
616 for index in next, processed do
617 local svgfile = f_svgfile(index)
618 local pdffile = f_pdffile(index)
619
620
621 local pdfdata = loaddata(pdffile)
622 if pdfdata and pdfdata ~= "" then
623 pdfshapes[index] = {
624 data = pdfdata,
625
626
627 }
628 end
629 remove(svgfile)
630 remove(pdffile)
631 end
632 local characters = tfmdata.characters
633 for k, v in next, characters do
634 local d = descriptions[k]
635 local i = d.index
636 if i then
637 local p = pdfshapes[i]
638 if p then
639 local w = d.width
640 local l = d.boundingbox[1]
641 local r = d.boundingbox[3]
642 p.scale = (r - l) / w
643 p.x = l
644 end
645 end
646 end
647 if not next(pdfshapes) then
648 report_svg("there are no converted shapes, fix your setup")
649 end
650 statistics.stoptiming()
651 if statistics.elapsedseconds then
652 report_svg("svg conversion time %s",statistics.elapsedseconds() or "-")
653 end
654 end
655 return pdfshapes
656 end
657
658 initializesvg = function(tfmdata,kind,value)
659 if value and otf.svgenabled then
660 local svg = tfmdata.properties.svg
661 local hash = svg and svg.hash
662 local timestamp = svg and svg.timestamp
663 if not hash then
664 return
665 end
666 local pdffile = containers.read(otf.pdfcache,hash)
667 local pdfshapes = pdffile and pdffile.pdfshapes
668 if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) then
669
670
671 local svgfile = containers.read(otf.svgcache,hash)
672 local svgshapes = svgfile and svgfile.svgshapes
673 pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or { }
674 containers.write(otf.pdfcache, hash, {
675 pdfshapes = pdfshapes,
676 timestamp = timestamp,
677 })
678 end
679 pdftovirtual(tfmdata,pdfshapes,"svg")
680 return true
681 end
682 end
683
684 otfregister {
685 name = "svg",
686 description = "svg glyphs",
687 manipulators = {
688 base = initializesvg,
689 node = initializesvg,
690 }
691 }
692
693end
694
695
696
697
698
699
700
701local initializepng do
702
703
704
705
706 local otfpng = otf.png or { }
707 otf.png = otfpng
708 otf.pngenabled = true
709 local report_png = logs.reporter("fonts","png conversion")
710 local loaddata = io.loaddata
711 local savedata = io.savedata
712 local remove = os.remove
713 local texhack = [[\startTEXpage\externalfigure[temp-otf-png-shape.png]\stopTEXpage]]
714 local runner = false
715 local method = "gm"
716
717 local function initialize(v)
718 if v == "lmtx" then
719 report_png("using lmtx converter, slow but okay")
720 runner = sandbox.registerrunner {
721
722 name = "otfpng",
723 program = "mtxrun --script context",
724 template = "--once --batch --silent temp-otf-png-shape.tex > temp-otf-svg-shape.log",
725 }
726 method = v
727 elseif v == "mutool" then
728 report_png("using lmtx converter, no mask, black background")
729 runner = sandbox.registerrunner {
730
731 name = "otfpng",
732 program = "mutool",
733 template = "convert -o temp-otf-png-shape.pdf temp-otf-png-shape.png",
734 }
735 method = v
736 else
737 report_png("using lmtx converter, no mask, white background")
738 runner = sandbox.registerrunner {
739
740 name = "otfpng",
741 program = "gm",
742 template = "convert -quality 100 temp-otf-png-shape.png temp-otf-png-shape.pdf > temp-otf-svg-shape.log",
743 }
744 method = "gm"
745 end
746 return runner
747 end
748
749 directives.register("backend.otfpng.method",initialize)
750
751 local files = utilities.files
752 local openfile = files.open
753 local closefile = files.close
754 local setposition = files.setposition
755 local readstring = files.readstring
756
757 function otfpng.topdf(pngshapes,filename)
758 if pngshapes and filename then
759 local pdfshapes = { }
760 local pngfile = "temp-otf-png-shape.png"
761 local pdffile = "temp-otf-png-shape.pdf"
762 local logfile = "temp-otf-png-shape.log"
763 local texfile = "temp-otf-png-shape.tex"
764 local tucfile = "temp-otf-png-shape.tuc"
765 local nofdone = 0
766 local indices = sortedkeys(pngshapes)
767 local nofindices = #indices
768 report_png("processing %i png containers",nofindices)
769 statistics.starttiming()
770 local filehandle = openfile(filename)
771 savedata(texfile,texhack)
772 if not runner then
773 initialize()
774 end
775 for i=1,nofindices do
776 local index = indices[i]
777 local entry = pngshapes[index]
778
779 local offset = entry.o
780 local size = entry.s
781 local x = entry.x
782 local y = entry.y
783 local data = nil
784 if offset and size then
785 setposition(filehandle,offset)
786 data = readstring(filehandle,size)
787 savedata(pngfile,data)
788 runner()
789 data = loaddata(pdffile)
790 end
791 pdfshapes[index] = {
792
793
794 data = data,
795 }
796 nofdone = nofdone + 1
797 if nofdone % 100 == 0 then
798 report_png("%i shapes processed",nofdone)
799 end
800 end
801 closefile(filehandle)
802 report_png("processing %i pdf results",nofindices)
803 remove(pngfile)
804 remove(pdffile)
805 remove(logfile)
806 remove(texfile)
807 remove(tucfile)
808 statistics.stoptiming()
809 if statistics.elapsedseconds then
810 report_png("png conversion time %s",statistics.elapsedseconds() or "-")
811 end
812 return pdfshapes
813 end
814 end
815
816 initializepng = function(tfmdata,kind,value)
817 if value and otf.pngenabled then
818 local png = tfmdata.properties.png
819 local hash = png and png.hash
820 local timestamp = png and png.timestamp
821 if not hash then
822 return
823 end
824 local pdffile = containers.read(otf.pdfcache,hash)
825 local pdfshapes = pdffile and pdffile.pdfshapes
826 if not pdfshapes or pdffile.timestamp ~= timestamp or pdffile.timestamp ~= method then
827 local pngfile = containers.read(otf.pngcache,hash)
828 local filename = tfmdata.resources.filename
829 local pngshapes = pngfile and pngfile.pngshapes
830 pdfshapes = pngshapes and otfpng.topdf(pngshapes,filename) or { }
831 containers.write(otf.pdfcache, hash, {
832 pdfshapes = pdfshapes,
833 timestamp = timestamp,
834 method = method,
835 })
836 end
837
838 pdftovirtual(tfmdata,pdfshapes,"png")
839 return true
840 end
841 end
842
843 otfregister {
844 name = "sbix",
845 description = "sbix glyphs",
846 manipulators = {
847 base = initializepng,
848 node = initializepng,
849 }
850 }
851
852 otfregister {
853 name = "cblc",
854 description = "cblc glyphs",
855 manipulators = {
856 base = initializepng,
857 node = initializepng,
858 }
859 }
860
861end
862
863do
864
865 local function initializecolor(tfmdata,kind,value)
866 if value == "auto" then
867 return
868 initializeoverlay(tfmdata,kind,value) or
869 initializesvg (tfmdata,kind,value) or
870 initializepng (tfmdata,kind,value)
871 elseif value == "overlay" then
872 return initializeoverlay(tfmdata,kind,value)
873 elseif value == "svg" then
874 return initializesvg(tfmdata,kind,value)
875 elseif value == "png" or value == "bitmap" then
876 return initializepng(tfmdata,kind,value)
877 else
878
879 end
880 end
881
882 otfregister {
883 name = "color",
884 description = "color glyphs",
885 manipulators = {
886 base = initializecolor,
887 node = initializecolor,
888 }
889 }
890
891end
892
893
894
895do
896
897 local startactualtext = nil
898 local stopactualtext = nil
899
900 function otf.getactualtext(s)
901 if not startactualtext then
902 startactualtext = backends.codeinjections.startunicodetoactualtextdirect
903 stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect
904 end
905 return startactualtext(s), stopactualtext()
906 end
907
908end
909
910 |