1if not modules then modules = { } end modules [ ' font-otl ' ] = {
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
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26local lower = string . lower
27local type , next , tonumber , tostring , unpack = type , next , tonumber , tostring , unpack
28local abs = math . abs
29local derivetable = table . derive
30local formatters = string . formatters
31
32local setmetatableindex = table . setmetatableindex
33local allocate = utilities . storage . allocate
34local registertracker = trackers . register
35local registerdirective = directives . register
36local starttiming = statistics . starttiming
37local stoptiming = statistics . stoptiming
38local elapsedtime = statistics . elapsedtime
39local findbinfile = resolvers . findbinfile
40
41
42
43local trace_loading = false registertracker ( " otf.loading " , function ( v ) trace_loading = v end )
44local trace_features = false registertracker ( " otf.features " , function ( v ) trace_features = v end )
45
46
47
48local trace_defining = false registertracker ( " fonts.defining " , function ( v ) trace_defining = v end )
49
50local report_otf = logs . reporter ( " fonts " , " otf loading " )
51
52local fonts = fonts
53local otf = fonts . handlers . otf
54
55otf . version = 3 . 111
56otf . cache = containers . define ( " fonts " , " otl " , otf . version , true )
57otf . svgcache = containers . define ( " fonts " , " svg " , otf . version , true )
58otf . pngcache = containers . define ( " fonts " , " png " , otf . version , true )
59otf . pdfcache = containers . define ( " fonts " , " pdf " , otf . version , true )
60otf . mpscache = containers . define ( " fonts " , " mps " , otf . version , true )
61
62otf . svgenabled = false
63otf . pngenabled = false
64
65local otfreaders = otf . readers
66
67local hashes = fonts . hashes
68local definers = fonts . definers
69local readers = fonts . readers
70local constructors = fonts . constructors
71
72local otffeatures = constructors . features . otf
73local registerotffeature = otffeatures . register
74
75local otfenhancers = constructors . enhancers . otf
76local registerotfenhancer = otfenhancers . register
77
78local forceload = false
79local cleanup = 0
80local syncspace = true
81local forcenotdef = false
82
83local privateoffset = fonts . constructors and fonts . constructors . privateoffset or 0xF0000
84
85local applyruntimefixes = fonts . treatments and fonts . treatments . applyfixes
86
87local wildcard = " * "
88local default = " dflt "
89
90local formats = fonts . formats
91
92formats . otf = " opentype "
93formats . ttf = " truetype "
94formats . ttc = " truetype "
95
96registerdirective ( " fonts.otf.loader.cleanup " , function ( v ) cleanup = tonumber ( v ) or ( v and 1 ) or 0 end )
97registerdirective ( " fonts.otf.loader.force " , function ( v ) forceload = v end )
98registerdirective ( " fonts.otf.loader.syncspace " , function ( v ) syncspace = v end )
99registerdirective ( " fonts.otf.loader.forcenotdef " , function ( v ) forcenotdef = v end )
100
101
102
103registerotfenhancer ( " check extra features " , function ( ) end )
104
105
106
107
108local checkmemory = utilities . lua and utilities . lua . checkmemory
109local threshold = 100
110local tracememory = false
111
112registertracker ( " fonts.otf.loader.memory " , function ( v ) tracememory = v end )
113
114if not checkmemory then
115
116 local collectgarbage = collectgarbage
117
118 checkmemory = function ( previous , threshold )
119 local current = collectgarbage ( " count " )
120 if previous then
121 local checked = ( threshold or 64 ) * 1024
122 if current - previous > checked then
123 collectgarbage ( " collect " )
124 current = collectgarbage ( " count " )
125 end
126 end
127 return current
128 end
129
130end
131
132function otf . load ( filename , sub , instance )
133 local base = file . basename ( file . removesuffix ( filename ) )
134 local name = file . removesuffix ( base )
135 local attr = lfs . attributes ( filename )
136 local size = attr and attr . size or 0
137 local time = attr and attr . modification or 0
138
139 if sub = = " " then
140 sub = false
141 end
142 local hash = name
143 if sub then
144 hash = hash . . " - " . . sub
145 end
146 if instance then
147 hash = hash . . " - " . . instance
148 end
149 hash = containers . cleanname ( hash )
150 local data = containers . read ( otf . cache , hash )
151 local reload = not data or data . size ~ = size or data . time ~ = time or data . tableversion ~ = otfreaders . tableversion
152 if forceload then
153 report_otf ( " forced reload of %a due to hard coded flag " , filename )
154 reload = true
155 end
156 if reload then
157 report_otf ( " loading %a, hash %a " , filename , hash )
158
159 starttiming ( otfreaders , true )
160 data = otfreaders . loadfont ( filename , sub or 1 , instance )
161 if data then
162
163 local used = checkmemory ( )
164 local resources = data . resources
165 local svgshapes = resources . svgshapes
166 local pngshapes = resources . pngshapes
167 if cleanup = = 0 then
168 checkmemory ( used , threshold , tracememory )
169 end
170 if svgshapes then
171 resources . svgshapes = nil
172 if otf . svgenabled then
173 local timestamp = os . date ( )
174
175 containers . write ( otf . svgcache , hash , {
176 svgshapes = svgshapes ,
177 timestamp = timestamp ,
178 } )
179 data . properties . svg = {
180 hash = hash ,
181 timestamp = timestamp ,
182 }
183 end
184 if cleanup > 1 then
185 collectgarbage ( " collect " )
186 else
187 checkmemory ( used , threshold , tracememory )
188 end
189 end
190 if pngshapes then
191 resources . pngshapes = nil
192 if otf . pngenabled then
193 local timestamp = os . date ( )
194
195 containers . write ( otf . pngcache , hash , {
196 pngshapes = pngshapes ,
197 timestamp = timestamp ,
198 } )
199 data . properties . png = {
200 hash = hash ,
201 timestamp = timestamp ,
202 }
203 end
204 if cleanup > 1 then
205 collectgarbage ( " collect " )
206 else
207 checkmemory ( used , threshold , tracememory )
208 end
209 end
210
211 otfreaders . compact ( data )
212 if cleanup = = 0 then
213 checkmemory ( used , threshold , tracememory )
214 end
215 otfreaders . rehash ( data , " unicodes " )
216 otfreaders . addunicodetable ( data )
217 otfreaders . extend ( data )
218 if cleanup = = 0 then
219 checkmemory ( used , threshold , tracememory )
220 end
221 otfreaders . pack ( data )
222 report_otf ( " loading done " )
223 report_otf ( " saving %a in cache " , filename )
224 data = containers . write ( otf . cache , hash , data )
225 if cleanup > 1 then
226 collectgarbage ( " collect " )
227 else
228 checkmemory ( used , threshold , tracememory )
229 end
230 stoptiming ( otfreaders )
231 if elapsedtime then
232 report_otf ( " loading, optimizing, packing and caching time %s " , elapsedtime ( otfreaders ) )
233 end
234 if cleanup > 3 then
235 collectgarbage ( " collect " )
236 else
237 checkmemory ( used , threshold , tracememory )
238 end
239 data = containers . read ( otf . cache , hash )
240 if cleanup > 2 then
241 collectgarbage ( " collect " )
242 else
243 checkmemory ( used , threshold , tracememory )
244 end
245 else
246 stoptiming ( otfreaders )
247 data = nil
248 report_otf ( " loading failed due to read error " )
249 end
250 end
251 if data then
252 if trace_defining then
253 report_otf ( " loading from cache using hash %a " , hash )
254 end
255
256 otfreaders . unpack ( data )
257 otfreaders . expand ( data )
258 otfreaders . addunicodetable ( data )
259
260 otfenhancers . apply ( data , filename , data )
261
262
263
264 if applyruntimefixes then
265 applyruntimefixes ( filename , data )
266 end
267
268 data . metadata . math = data . resources . mathconstants
269
270
271
272 local classes = data . resources . classes
273 if not classes then
274 local descriptions = data . descriptions
275 classes = setmetatableindex ( function ( t , k )
276 local d = descriptions [ k ]
277 local v = ( d and d . class or " base " ) or false
278 t [ k ] = v
279 return v
280 end )
281 data . resources . classes = classes
282 end
283
284 end
285
286 return data
287end
288
289
290
291function otf . setfeatures ( tfmdata , features )
292 local okay = constructors . initializefeatures ( " otf " , tfmdata , features , trace_features , report_otf )
293 if okay then
294 return constructors . collectprocessors ( " otf " , tfmdata , features , trace_features , report_otf )
295 else
296 return { }
297 end
298end
299
300
301
302
303
304
305
306
307
308
309
310
311local function copytotfm ( data , cache_id )
312 if data then
313 local metadata = data . metadata
314 local properties = derivetable ( data . properties )
315 local descriptions = derivetable ( data . descriptions )
316 local goodies = derivetable ( data . goodies )
317 local characters = { }
318 local parameters = { }
319 local mathparameters = { }
320
321 local resources = data . resources
322 local unicodes = resources . unicodes
323 local spaceunits = 500
324 local spacer = " space "
325 local designsize = metadata . designsize or 100
326 local minsize = metadata . minsize or designsize
327 local maxsize = metadata . maxsize or designsize
328 local mathspecs = metadata . math
329
330 if designsize = = 0 then
331 designsize = 100
332 minsize = 100
333 maxsize = 100
334 end
335 if mathspecs then
336 for name , value in next , mathspecs do
337 mathparameters [ name ] = value
338 end
339 end
340 for unicode in next , data . descriptions do
341 characters [ unicode ] = { }
342 end
343 if mathspecs then
344 for unicode , character in next , characters do
345 local d = descriptions [ unicode ]
346 local m = d . math
347 if m then
348
349
350 local italic = m . italic
351 local vitalic = m . vitalic
352
353 local variants = m . hvariants
354 local parts = m . hparts
355 if variants then
356 local c = character
357 for i = 1 , # variants do
358
359 local un = variants [ i ]
360 c . next = un
361 c = characters [ un ]
362 end
363 c . horiz_variants = parts
364 elseif parts then
365 character . horiz_variants = parts
366 italic = m . hitalic
367 end
368
369 local variants = m . vvariants
370 local parts = m . vparts
371 if variants then
372 local c = character
373 for i = 1 , # variants do
374
375 local un = variants [ i ]
376 c . next = un
377 c = characters [ un ]
378 end
379 c . vert_variants = parts
380 elseif parts then
381 character . vert_variants = parts
382 end
383
384 if italic and italic ~ = 0 then
385 character . italic = italic
386 end
387
388 if vitalic and vitalic ~ = 0 then
389 character . vert_italic = vitalic
390 end
391
392 local accent = m . accent
393 if accent then
394 character . accent = accent
395 end
396
397 local kerns = m . kerns
398 if kerns then
399 character . mathkerns = kerns
400 end
401 end
402 end
403 end
404
405
406 local filename = constructors . checkedfilename ( resources )
407 local fontname = metadata . fontname
408 local fullname = metadata . fullname or fontname
409 local psname = fontname or fullname
410 local subfont = metadata . subfontindex
411 local units = metadata . units or 1000
412
413 if units = = 0 then
414 units = 1000
415 metadata . units = 1000
416 report_otf ( " changing %a units to %a " , 0 , units )
417 end
418
419 local monospaced = metadata . monospaced
420 local charwidth = metadata . averagewidth
421 local charxheight = metadata . xheight
422 local italicangle = metadata . italicangle
423 local hasitalics = metadata . hasitalics
424 properties . monospaced = monospaced
425 properties . hasitalics = hasitalics
426 parameters . italicangle = italicangle
427 parameters . charwidth = charwidth
428 parameters . charxheight = charxheight
429
430 local space = 0x0020
431 local emdash = 0x2014
432 if monospaced then
433 if descriptions [ space ] then
434 spaceunits , spacer = descriptions [ space ] . width , " space "
435 end
436 if not spaceunits and descriptions [ emdash ] then
437 spaceunits , spacer = descriptions [ emdash ] . width , " emdash "
438 end
439 if not spaceunits and charwidth then
440 spaceunits , spacer = charwidth , " charwidth "
441 end
442 else
443 if descriptions [ space ] then
444 spaceunits , spacer = descriptions [ space ] . width , " space "
445 end
446 if not spaceunits and descriptions [ emdash ] then
447 spaceunits , spacer = descriptions [ emdash ] . width / 2 , " emdash/2 "
448 end
449 if not spaceunits and charwidth then
450 spaceunits , spacer = charwidth , " charwidth "
451 end
452 end
453 spaceunits = tonumber ( spaceunits ) or units / 2
454
455 parameters . slant = 0
456 parameters . space = spaceunits
457 parameters . space_stretch = 1 * units / 2
458 parameters . space_shrink = 1 * units / 3
459 parameters . x_height = 2 * units / 5
460 parameters . quad = units
461 if spaceunits < 2 * units / 5 then
462
463 end
464 if italicangle and italicangle ~ = 0 then
465 parameters . italicangle = italicangle
466 parameters . italicfactor = math . cos ( math . rad ( 90 + italicangle ) )
467 parameters . slant = - math . tan ( italicangle * math . pi / 180 )
468 end
469 if monospaced then
470 parameters . space_stretch = 0
471 parameters . space_shrink = 0
472 elseif syncspace then
473 parameters . space_stretch = spaceunits / 2
474 parameters . space_shrink = spaceunits / 3
475 end
476 parameters . extra_space = parameters . space_shrink
477 if charxheight then
478 parameters . x_height = charxheight
479 else
480 local x = 0x0078
481 if x then
482 local x = descriptions [ x ]
483 if x then
484 parameters . x_height = x . height
485 end
486 end
487 end
488
489 parameters . designsize = ( designsize / 10 ) * 65536
490 parameters . minsize = ( minsize / 10 ) * 65536
491 parameters . maxsize = ( maxsize / 10 ) * 65536
492 parameters . ascender = abs ( metadata . ascender or 0 )
493 parameters . descender = abs ( metadata . descender or 0 )
494 parameters . units = units
495 parameters . vheight = metadata . defaultvheight
496
497 properties . space = spacer
498 properties . encodingbytes = 2
499 properties . format = data . format or formats . otf
500 properties . filename = filename
501 properties . fontname = fontname
502 properties . fullname = fullname
503 properties . psname = psname
504 properties . name = filename or fullname
505 properties . subfont = subfont
506
507
508
509
510 properties . private = properties . private or data . private or privateoffset
511
512 return {
513 characters = characters ,
514 descriptions = descriptions ,
515 parameters = parameters ,
516 mathparameters = mathparameters ,
517 resources = resources ,
518 properties = properties ,
519 goodies = goodies ,
520 }
521 end
522end
523
524
525
526
527
528
529
530local converters = {
531 woff = {
532 cachename = " webfonts " ,
533 action = otf . readers . woff2otf ,
534 }
535}
536
537
538
539
540local function checkconversion ( specification )
541 local filename = specification . filename
542 local converter = converters [ lower ( file . suffix ( filename ) ) ]
543 if converter then
544 local base = file . basename ( filename )
545 local name = file . removesuffix ( base )
546 local attr = lfs . attributes ( filename )
547 local size = attr and attr . size or 0
548 local time = attr and attr . modification or 0
549 if size > 0 then
550 local cleanname = containers . cleanname ( name )
551 local cachename = caches . setfirstwritablefile ( cleanname , converter . cachename )
552 if not io . exists ( cachename ) or ( time ~ = lfs . attributes ( cachename ) . modification ) then
553 report_otf ( " caching font %a in %a " , filename , cachename )
554 converter . action ( filename , cachename )
555 lfs . touch ( cachename , time , time )
556 end
557 specification . filename = cachename
558 end
559 end
560end
561
562local function otftotfm ( specification )
563 local cache_id = specification . hash
564 local tfmdata = containers . read ( constructors . cache , cache_id )
565 if not tfmdata then
566
567 checkconversion ( specification )
568
569 local name = specification . name
570 local sub = specification . sub
571 local subindex = specification . subindex
572 local filename = specification . filename
573 local features = specification . features . normal
574 local instance = specification . instance or ( features and features . axis )
575 local rawdata = otf . load ( filename , sub , instance )
576 if rawdata and next ( rawdata ) then
577 local descriptions = rawdata . descriptions
578 rawdata . lookuphash = { }
579 tfmdata = copytotfm ( rawdata , cache_id )
580 if tfmdata and next ( tfmdata ) then
581
582 local features = constructors . checkedfeatures ( " otf " , features )
583 local shared = tfmdata . shared
584 if not shared then
585 shared = { }
586 tfmdata . shared = shared
587 end
588 shared . rawdata = rawdata
589
590 shared . dynamics = { }
591
592 tfmdata . changed = { }
593 shared . features = features
594 shared . processes = otf . setfeatures ( tfmdata , features )
595 end
596 end
597 containers . write ( constructors . cache , cache_id , tfmdata )
598 end
599 return tfmdata
600end
601
602local function read_from_otf ( specification )
603 local tfmdata = otftotfm ( specification )
604 if tfmdata then
605
606 tfmdata . properties . name = specification . name
607 tfmdata . properties . sub = specification . sub
608
609 tfmdata = constructors . scale ( tfmdata , specification )
610 local allfeatures = tfmdata . shared . features or specification . features . normal
611 constructors . applymanipulators ( " otf " , tfmdata , allfeatures , trace_features , report_otf )
612 constructors . setname ( tfmdata , specification )
613 fonts . loggers . register ( tfmdata , file . suffix ( specification . filename ) , specification )
614 end
615 return tfmdata
616end
617
618local function checkmathsize ( tfmdata , mathsize )
619 local mathdata = tfmdata . shared . rawdata . metadata . math
620 local mathsize = tonumber ( mathsize )
621 if mathdata then
622 local parameters = tfmdata . parameters
623 parameters . scriptpercentage = mathdata . ScriptPercentScaleDown
624 parameters . scriptscriptpercentage = mathdata . ScriptScriptPercentScaleDown
625 parameters . mathsize = mathsize
626 end
627end
628
629registerotffeature {
630 name = " mathsize " ,
631 description = " apply mathsize specified in the font " ,
632 initializers = {
633 base = checkmathsize ,
634 node = checkmathsize ,
635 }
636}
637
638
639
640function otf . collectlookups ( rawdata , kind , script , language )
641 if not kind then
642 return
643 end
644 if not script then
645 script = default
646 end
647 if not language then
648 language = default
649 end
650 local lookupcache = rawdata . lookupcache
651 if not lookupcache then
652 lookupcache = { }
653 rawdata . lookupcache = lookupcache
654 end
655 local kindlookup = lookupcache [ kind ]
656 if not kindlookup then
657 kindlookup = { }
658 lookupcache [ kind ] = kindlookup
659 end
660 local scriptlookup = kindlookup [ script ]
661 if not scriptlookup then
662 scriptlookup = { }
663 kindlookup [ script ] = scriptlookup
664 end
665 local languagelookup = scriptlookup [ language ]
666 if not languagelookup then
667 local sequences = rawdata . resources . sequences
668 local featuremap = { }
669 local featurelist = { }
670 if sequences then
671 for s = 1 , # sequences do
672 local sequence = sequences [ s ]
673 local features = sequence . features
674 if features then
675 features = features [ kind ]
676 if features then
677
678 features = features [ script ] or features [ wildcard ]
679 if features then
680
681 features = features [ language ] or features [ wildcard ]
682 if features then
683 if not featuremap [ sequence ] then
684 featuremap [ sequence ] = true
685 featurelist [ # featurelist + 1 ] = sequence
686 end
687 end
688 end
689 end
690 end
691 end
692 if # featurelist = = 0 then
693 featuremap , featurelist = false , false
694 end
695 else
696 featuremap , featurelist = false , false
697 end
698 languagelookup = { featuremap , featurelist }
699 scriptlookup [ language ] = languagelookup
700 end
701 return unpack ( languagelookup )
702end
703
704
705
706local function getgsub ( tfmdata , k , kind , value )
707 local shared = tfmdata . shared
708 local rawdata = shared and shared . rawdata
709 if rawdata then
710 local sequences = rawdata . resources . sequences
711 if sequences then
712 local properties = tfmdata . properties
713 local validlookups , lookuplist = otf . collectlookups ( rawdata , kind , properties . script , properties . language )
714 if validlookups then
715
716 for i = 1 , # lookuplist do
717 local lookup = lookuplist [ i ]
718 local steps = lookup . steps
719 local nofsteps = lookup . nofsteps
720 for i = 1 , nofsteps do
721 local coverage = steps [ i ] . coverage
722 if coverage then
723 local found = coverage [ k ]
724 if found then
725 return found , lookup . type
726 end
727 end
728 end
729 end
730 end
731 end
732 end
733end
734
735otf . getgsub = getgsub
736
737function otf . getsubstitution ( tfmdata , k , kind , value )
738 local found , kind = getgsub ( tfmdata , k , kind , value )
739 if not found then
740
741 elseif kind = = " gsub_single " then
742 return found
743 elseif kind = = " gsub_alternate " then
744 local choice = tonumber ( value ) or 1
745 return found [ choice ] or found [ 1 ] or k
746 end
747 return k
748end
749
750otf . getalternate = otf . getsubstitution
751
752function otf . getmultiple ( tfmdata , k , kind )
753 local found , kind = getgsub ( tfmdata , k , kind )
754 if found and kind = = " gsub_multiple " then
755 return found
756 end
757 return { k }
758end
759
760function otf . getkern ( tfmdata , left , right , kind )
761 local kerns = getgsub ( tfmdata , left , kind or " kern " , true )
762 if kerns then
763 local found = kerns [ right ]
764 local kind = type ( found )
765 if kind = = " table " then
766 found = found [ 1 ] [ 3 ]
767 elseif kind ~ = " number " then
768 found = false
769 end
770 if found then
771 return found * tfmdata . parameters . factor
772 end
773 end
774 return 0
775end
776
777local function check_otf ( forced , specification , suffix )
778 local name = specification . name
779 if forced then
780 name = specification . forcedname
781 end
782 local fullname = findbinfile ( name , suffix ) or " "
783 if fullname = = " " then
784 fullname = fonts . names . getfilename ( name , suffix ) or " "
785 end
786 if fullname ~ = " " and not fonts . names . ignoredfile ( fullname ) then
787 specification . filename = fullname
788 return read_from_otf ( specification )
789 end
790end
791
792local function opentypereader ( specification , suffix )
793 local forced = specification . forced or " "
794 if formats [ forced ] then
795 return check_otf ( true , specification , forced )
796 else
797 return check_otf ( false , specification , suffix )
798 end
799end
800
801readers . opentype = opentypereader
802
803function readers . otf ( specification ) return opentypereader ( specification , " otf " ) end
804function readers . ttf ( specification ) return opentypereader ( specification , " ttf " ) end
805function readers . ttc ( specification ) return opentypereader ( specification , " ttf " ) end
806
807function readers . woff ( specification )
808 checkconversion ( specification )
809 opentypereader ( specification , " " )
810end
811
812
813
814function otf . scriptandlanguage ( tfmdata , attr )
815 local properties = tfmdata . properties
816 return properties . script or " dflt " , properties . language or " dflt "
817end
818
819
820
821local function justset ( coverage , unicode , replacement )
822 coverage [ unicode ] = replacement
823end
824
825otf . coverup = {
826 stepkey = " steps " ,
827 actions = {
828 chainsubstitution = justset ,
829 chainposition = justset ,
830 substitution = justset ,
831 alternate = justset ,
832 multiple = justset ,
833 kern = justset ,
834 pair = justset ,
835 single = justset ,
836 ligature = function ( coverage , unicode , ligature )
837 local first = ligature [ 1 ]
838 local tree = coverage [ first ]
839 if not tree then
840 tree = { }
841 coverage [ first ] = tree
842 end
843 for i = 2 , # ligature do
844 local l = ligature [ i ]
845 local t = tree [ l ]
846 if not t then
847 t = { }
848 tree [ l ] = t
849 end
850 tree = t
851 end
852 tree . ligature = unicode
853 end ,
854 } ,
855 register = function ( coverage , featuretype , format )
856 return {
857 format = format ,
858 coverage = coverage ,
859 }
860 end
861}
862 |