1if not modules then modules = { } end modules ['font-ctx'] = {
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
15local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber
16
17local format, gmatch, match, find, lower, upper, gsub, byte, topattern = string.format, string.gmatch, string.match, string.find, string.lower, string.upper, string.gsub, string.byte, string.topattern
18local concat, serialize, sort, fastcopy, mergedtable = table.concat, table.serialize, table.sort, table.fastcopy, table.merged
19local sortedhash, sortedkeys, sequenced = table.sortedhash, table.sortedkeys, table.sequenced
20local parsers = utilities.parsers
21local settings_to_hash, hash_to_string, settings_to_array = parsers.settings_to_hash, parsers.hash_to_string, parsers.settings_to_array
22local formatcolumns = utilities.formatters.formatcolumns
23local mergehashes = utilities.parsers.mergehashes
24local formatters = string.formatters
25local basename = file.basename
26
27local utfchar, utfbyte = utf.char, utf.byte
28local round = math.round
29
30local context, commands = context, commands
31
32local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match
33
34local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end)
35local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
36local trace_designsize = false trackers.register("fonts.designsize", function(v) trace_designsize = v end)
37local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end)
38local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end)
39local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end)
40local trace_merge = false trackers.register("fonts.merge", function(v) trace_merge = v end)
41
42local report = logs.reporter("fonts")
43local report_features = logs.reporter("fonts","features")
44local report_cummulative = logs.reporter("fonts","cummulative")
45local report_defining = logs.reporter("fonts","defining")
46local report_status = logs.reporter("fonts","status")
47local report_mapfiles = logs.reporter("fonts","mapfiles")
48
49local setmetatableindex = table.setmetatableindex
50
51local implement = interfaces.implement
52
53local chardata = characters.data
54
55local fonts = fonts
56local handlers = fonts.handlers
57local otf = handlers.otf
58local afm = handlers.afm
59local tfm = handlers.tfm
60local names = fonts.names
61local definers = fonts.definers
62local specifiers = fonts.specifiers
63local constructors = fonts.constructors
64local loggers = fonts.loggers
65local fontgoodies = fonts.goodies
66local helpers = fonts.helpers
67local hashes = fonts.hashes
68local currentfont = font.current
69local definefont = font.define
70
71local getprivateslot = helpers.getprivateslot
72
73local cleanname = names.cleanname
74
75local encodings = fonts.encodings
76
77local aglunicodes = nil
78
79local nuts = nodes.nuts
80local tonut = nuts.tonut
81
82local nextchar = nuts.traversers.char
83
84local getattr = nuts.getattr
85local setattr = nuts.setattr
86local getstate = nuts.getstate
87local setsubtype = nuts.setsubtype
88
89local texgetdimen = tex.getdimen
90local texsetcount = tex.setcount
91local texget = tex.get
92
93local texdefinefont = tex.definefont
94local texsp = tex.sp
95
96local fontdata = hashes.identifiers
97local characters = hashes.characters
98local descriptions = hashes.descriptions
99local properties = hashes.properties
100local resources = hashes.resources
101local unicodes = hashes.unicodes
102local csnames = hashes.csnames
103local lastmathids = hashes.lastmathids
104local exheights = hashes.exheights
105local emwidths = hashes.emwidths
106local parameters = hashes.parameters
107
108local designsizefilename = fontgoodies.designsizes.filename
109
110local ctx_char = context.char
111local ctx_safechar = context.safechar
112local ctx_getvalue = context.getvalue
113
114local otffeatures = otf.features
115local otftables = otf.tables
116
117local registerotffeature = otffeatures.register
118
119local sequencers = utilities.sequencers
120local appendgroup = sequencers.appendgroup
121local appendaction = sequencers.appendaction
122
123specifiers.contextsetups = specifiers.contextsetups or { }
124specifiers.contextnumbers = specifiers.contextnumbers or { }
125specifiers.contextmerged = specifiers.contextmerged or { }
126specifiers.synonyms = specifiers.synonyms or { }
127
128local setups = specifiers.contextsetups
129local numbers = specifiers.contextnumbers
130local merged = specifiers.contextmerged
131local synonyms = specifiers.synonyms
132
133storage.register("fonts/setups" , setups , "fonts.specifiers.contextsetups" )
134storage.register("fonts/numbers", numbers, "fonts.specifiers.contextnumbers")
135storage.register("fonts/merged", merged, "fonts.specifiers.contextmerged")
136storage.register("fonts/synonyms", synonyms, "fonts.specifiers.synonyms")
137
138
139
140if environment.initex then
141 setmetatableindex(setups,function(t,k)
142 return type(k) == "number" and rawget(t,numbers[k]) or nil
143 end)
144else
145 setmetatableindex(setups,function(t,k)
146 local v = type(k) == "number" and rawget(t,numbers[k])
147 if v then
148 t[k] = v
149 return v
150 end
151 end)
152end
153
154
155
156local function getfontname(tfmdata)
157 return basename(type(tfmdata) == "number" and properties[tfmdata].name or tfmdata.properties.name)
158end
159
160helpers.name = getfontname
161
162local addformatter = utilities.strings.formatters.add
163
164addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name })
165addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced })
166
167
168
169constructors.resolvevirtualtoo = true
170constructors.sharefonts = true
171constructors.nofsharedfonts = 0
172constructors.nofsharedhashes = 0
173constructors.nofsharedvectors = 0
174constructors.noffontsloaded = 0
175constructors.autocleanup = true
176
177
178
179
180
181
182
183
184
185
186
187function constructors.cleanuptable(tfmdata)
188 if constructors.autocleanup and tfmdata.properties.virtualized then
189 for k, v in next, tfmdata.characters do
190 if v.commands then v.commands = nil end
191
192 end
193 end
194end
195
196do
197
198 local shares = { }
199 local hashes = { }
200
201
202 local nofinstances = 0
203 local instances = setmetatableindex(function(t,k)
204 nofinstances = nofinstances + 1
205 t[k] = nofinstances
206 return nofinstances
207 end)
208
209 function constructors.trytosharefont(target,tfmdata)
210 constructors.noffontsloaded = constructors.noffontsloaded + 1
211 if constructors.sharefonts then
212 local fonthash = target.specification.hash
213 if fonthash then
214 local properties = target.properties
215 local fullname = target.fullname
216 local fontname = target.fontname
217 local psname = target.psname
218
219 local instance = properties.instance
220 if instance then
221 local format = tfmdata.properties.format
222 if format == "opentype" then
223 target.streamprovider = 1
224 elseif format == "truetype" then
225 target.streamprovider = 2
226 else
227 target.streamprovider = 0
228 end
229 if target.streamprovider > 0 then
230 if fullname then
231 fullname = fullname .. ":" .. instances[instance]
232 target.fullname = fullname
233 end
234 if fontname then
235 fontname = fontname .. ":" .. instances[instance]
236 target.fontname = fontname
237 end
238 if psname then
239
240
241
242 psname = psname .. ":" .. instances[instance]
243 target.psname = psname
244 end
245 end
246 end
247
248 local sharedname = hashes[fonthash]
249 if sharedname then
250
251
252 if trace_defining then
253 report_defining("font %a uses backend resources of font %a (%s)",target.fullname,sharedname,"common hash")
254 end
255 target.fullname = sharedname
256 properties.sharedwith = sharedname
257 constructors.nofsharedfonts = constructors.nofsharedfonts + 1
258 constructors.nofsharedhashes = constructors.nofsharedhashes + 1
259 else
260
261
262
263
264 local characters = target.characters
265 local n = 1
266 local t = { target.psname }
267
268 if instance then
269 n = n + 1
270 t[n] = instance
271 end
272
273 local u = sortedkeys(characters)
274 for i=1,#u do
275 local k = u[i]
276 n = n + 1 ; t[n] = k
277 n = n + 1 ; t[n] = characters[k].index or k
278 end
279 local checksum = md5.HEX(concat(t," "))
280 local sharedname = shares[checksum]
281 local fullname = target.fullname
282 if sharedname then
283 if trace_defining then
284 report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector")
285 end
286 fullname = sharedname
287 properties.sharedwith= sharedname
288 constructors.nofsharedfonts = constructors.nofsharedfonts + 1
289 constructors.nofsharedvectors = constructors.nofsharedvectors + 1
290 else
291 shares[checksum] = fullname
292 end
293 target.fullname = fullname
294 hashes[fonthash] = fullname
295 end
296 end
297 end
298 end
299
300end
301
302directives.register("fonts.checksharing",function(v)
303 if not v then
304 report_defining("font sharing in backend is disabled")
305 end
306 constructors.sharefonts = v
307end)
308
309function definers.resetnullfont()
310
311 local parameters = fonts.nulldata.parameters
312
313 parameters.slant = 0
314 parameters.space = 0
315 parameters.space_stretch = 0
316 parameters.space_shrink = 0
317 parameters.x_height = 0
318 parameters.quad = 0
319 parameters.extra_space = 0
320 parameters.designsize = 655360
321
322 constructors.enhanceparameters(parameters)
323
324 definers.resetnullfont = function() end
325end
326
327
328
329
330
331implement {
332 name = "resetnullfont",
333 onlyonce = true,
334 permanent = false,
335 actions = function()
336 for i=1,7 do
337 context([[\fontdimen%s\nullfont\zeropoint]],i)
338 end
339 definers.resetnullfont()
340 end
341}
342
343
344
345
346
347local needsnodemode = {
348
349 gsub_multiple = true,
350
351
352 gsub_context = true,
353 gsub_contextchain = true,
354 gsub_reversecontextchain = true,
355
356
357 gpos_mark2base = true,
358 gpos_mark2ligature = true,
359 gpos_mark2mark = true,
360 gpos_cursive = true,
361
362
363 gpos_context = true,
364 gpos_contextchain = true,
365}
366
367otftables.scripts.auto = "automatic fallback to latn when no dflt present"
368
369
370
371local function checkedscript(tfmdata,resources,features)
372 local latn = false
373 local script = false
374 if resources.features then
375 for g, list in next, resources.features do
376 for f, scripts in next, list do
377 if scripts.dflt then
378 script = "dflt"
379 break
380 elseif scripts.latn then
381 latn = true
382 end
383 end
384 end
385 end
386 if not script then
387 script = latn and "latn" or "dflt"
388 end
389 if trace_automode then
390 report_defining("auto script mode, using script %a in font %!font:name!",script,tfmdata)
391 end
392 features.script = script
393 return script
394end
395
396
397
398local function checkedmode(tfmdata,resources,features)
399 local sequences = resources.sequences
400 if sequences and #sequences > 0 then
401 local script = features.script or "dflt"
402 local language = features.language or "dflt"
403 for feature, value in next, features do
404 if value then
405 local found = false
406 for i=1,#sequences do
407 local sequence = sequences[i]
408 local features = sequence.features
409 if features then
410 local scripts = features[feature]
411 if scripts then
412 local languages = scripts[script]
413 if languages and languages[language] then
414 if found then
415
416 if trace_automode then
417 report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s",
418 "node",tfmdata,feature,script,language,"multiple lookups")
419 end
420 features.mode = "node"
421 return "node"
422 elseif needsnodemode[sequence.type] then
423 if trace_automode then
424 report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s",
425 "node",tfmdata,feature,script,language,"no base support")
426 end
427 features.mode = "node"
428 return "node"
429 else
430
431 found = true
432 end
433 end
434 end
435 end
436 end
437 end
438 end
439 end
440 if trace_automode then
441 report_defining("forcing mode base, font %!font:name!",tfmdata)
442 end
443 features.mode = "base"
444 return "base"
445end
446
447definers.checkedscript = checkedscript
448definers.checkedmode = checkedmode
449
450local function modechecker(tfmdata,features,mode)
451 if trace_features then
452 report_features("fontname %!font:name!, features %!font:features!",tfmdata,features)
453 end
454 local rawdata = tfmdata.shared.rawdata
455 local resources = rawdata and rawdata.resources
456 local script = features.script
457 if resources then
458 if script == "auto" then
459 script = checkedscript(tfmdata,resources,features)
460 end
461 if mode == "auto" then
462 mode = checkedmode(tfmdata,resources,features)
463 end
464 else
465 report_features("missing resources for font %!font:name!",tfmdata)
466 end
467 return mode
468end
469
470registerotffeature {
471
472
473 name = "mode",
474 modechecker = modechecker,
475}
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495do
496
497 local beforecopyingcharacters = sequencers.new {
498 name = "beforecopyingcharacters",
499 arguments = "target,original",
500 }
501
502 appendgroup(beforecopyingcharacters,"before")
503 appendgroup(beforecopyingcharacters,"system")
504 appendgroup(beforecopyingcharacters,"after" )
505
506 function constructors.beforecopyingcharacters(original,target)
507 local runner = beforecopyingcharacters.runner
508 if runner then
509 runner(original,target)
510 end
511 end
512
513 local aftercopyingcharacters = sequencers.new {
514 name = "aftercopyingcharacters",
515 arguments = "target,original",
516 }
517
518 appendgroup(aftercopyingcharacters,"before")
519 appendgroup(aftercopyingcharacters,"system")
520 appendgroup(aftercopyingcharacters,"after" )
521
522 function constructors.aftercopyingcharacters(original,target)
523 local runner = aftercopyingcharacters.runner
524 if runner then
525 runner(original,target)
526 end
527 end
528
529end
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545local loadfont = definers.loadfont
546
547function definers.loadfont(specification,size,id)
548 local variants = definers.methods.variants
549 local virtualfeatures = specification.features.virtual
550 if virtualfeatures and virtualfeatures.preset then
551 local variant = variants[virtualfeatures.preset]
552 if variant then
553 return variant(specification,size,id)
554 end
555 else
556 local tfmdata = loadfont(specification,size,id)
557
558 return tfmdata
559 end
560end
561
562local function predefined(specification)
563 local variants = definers.methods.variants
564 local detail = specification.detail
565 if detail ~= "" and variants[detail] then
566 specification.features.virtual = { preset = detail }
567 end
568 return specification
569end
570
571definers.registersplit("@", predefined,"virtual")
572
573local normalize_features = otffeatures.normalize
574
575local function definecontext(name,t)
576 local number = setups[name] and setups[name].number or 0
577 if number == 0 then
578 number = #numbers + 1
579 numbers[number] = name
580 end
581 t.number = number
582 setups[name] = t
583 return number, t
584end
585
586
587
588
589
590
591
592local h = setmetatableindex(function(t,k)
593 local v = "," .. k .. ","
594 t[k] = v
595 return v
596end)
597
598
599
600
601
602
603
604
605
606
607local function presetcontext(name,parent,features)
608 if features == "" and find(parent,"=",1,true) then
609 features = parent
610 parent = ""
611 end
612 if not features or features == "" then
613 features = { }
614 elseif type(features) == "string" then
615 features = normalize_features(settings_to_hash(features))
616
617
618 for key, value in next, features do
619 if type(value) == "string" and find(value,"[=]") then
620 local t = settings_to_hash(value)
621 if next(t) then
622 features[key] = sequenced(normalize_features(t,true),",")
623 end
624 end
625 end
626 else
627 features = normalize_features(features)
628 end
629
630 if parent ~= "" then
631 for p in gmatch(parent,"[^, ]+") do
632 local s = setups[p]
633 if s then
634 for k, v in next, s do
635
636
637 if features[k] == nil then
638 features[k] = v
639 end
640 end
641 else
642
643 end
644 end
645 end
646
647
648
649
650
651
652
653
654
655
656
657 for feature,value in next, features do
658 if value == nil then
659 local default = default_features[feature]
660 if default ~= nil then
661 features[feature] = default
662 end
663 end
664 end
665
666
667 local t = { }
668 for k,v in next, features do
669
670 t[k] = v
671 end
672
673
674 local number = setups[name] and setups[name].number or 0
675 if number == 0 then
676 number = #numbers + 1
677 numbers[number] = name
678 end
679
680 t.number = number
681
682
683
684
685
686
687
688
689 setups[name] = t
690 return number, t
691end
692
693local function adaptcontext(pattern,features)
694 local pattern = topattern(pattern,false,true)
695 for name in next, setups do
696 if find(name,pattern) then
697 presetcontext(name,name,features)
698 end
699 end
700end
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733local function contextnumber(name)
734 local t = setups[name]
735 return t and t.number or 0
736end
737
738local function mergecontext(currentnumber,extraname,option)
739 local extra = setups[extraname]
740 if extra then
741 local current = setups[numbers[currentnumber]]
742 local mergedfeatures = { }
743 local mergedname = nil
744 if option < 0 then
745 if current then
746 for k, v in next, current do
747 if not extra[k] then
748 mergedfeatures[k] = v
749 end
750 end
751 end
752 mergedname = currentnumber .. "-" .. extraname
753 else
754 if current then
755 for k, v in next, current do
756 mergedfeatures[k] = v
757 end
758 end
759 for k, v in next, extra do
760 mergedfeatures[k] = v
761 end
762 mergedname = currentnumber .. "+" .. extraname
763 end
764 local number = #numbers + 1
765 mergedfeatures.number = number
766 numbers[number] = mergedname
767 merged[number] = option
768 setups[mergedname] = mergedfeatures
769 return number
770 else
771 return currentnumber
772 end
773end
774
775local extrasets = { }
776
777setmetatableindex(extrasets,function(t,k)
778 local v = mergehashes(setups,k)
779 t[k] = v
780 return v
781end)
782
783local function mergecontextfeatures(currentname,extraname,how,mergedname)
784 local extra = setups[extraname] or extrasets[extraname]
785 if extra then
786 local current = setups[currentname]
787 local mergedfeatures = { }
788 if how == "+" then
789 if current then
790 for k, v in next, current do
791 mergedfeatures[k] = v
792 end
793 end
794 for k, v in next, extra do
795 mergedfeatures[k] = v
796 end
797 if trace_merge then
798 report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"add",current or { },extra,mergedfeatures)
799 end
800 elseif how == "-" then
801 if current then
802 for k, v in next, current do
803 mergedfeatures[k] = v
804 end
805 end
806 for k, v in next, extra do
807
808 if v == true then
809 mergedfeatures[k] = false
810 end
811 end
812 if trace_merge then
813 report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"subtract",current or { },extra,mergedfeatures)
814 end
815 else
816 for k, v in next, extra do
817 mergedfeatures[k] = v
818 end
819 if trace_merge then
820 report_features("merge %a, method %a, result %|T",mergedname,"replace",mergedfeatures)
821 end
822 end
823 local number = #numbers + 1
824 mergedfeatures.number = number
825 numbers[number] = mergedname
826 merged[number] = option
827 setups[mergedname] = mergedfeatures
828 return number
829 else
830 return numbers[currentname] or 0
831 end
832end
833
834local function registercontext(fontnumber,extraname,option)
835 local extra = setups[extraname]
836 if extra then
837 local mergedfeatures = { }
838 local mergedname = nil
839 if option < 0 then
840 mergedname = fontnumber .. "-" .. extraname
841 else
842 mergedname = fontnumber .. "+" .. extraname
843 end
844 for k, v in next, extra do
845 mergedfeatures[k] = v
846 end
847 local number = #numbers + 1
848 mergedfeatures.number = number
849 numbers[number] = mergedname
850 merged[number] = option
851 setups[mergedname] = mergedfeatures
852 return number
853 else
854 return 0
855 end
856end
857
858local function registercontextfeature(mergedname,extraname,how)
859 local extra = setups[extraname]
860 if extra then
861 local mergedfeatures = { }
862 for k, v in next, extra do
863 mergedfeatures[k] = v
864 end
865 local number = #numbers + 1
866 mergedfeatures.number = number
867 numbers[number] = mergedname
868 merged[number] = how == "=" and 1 or 2
869 setups[mergedname] = mergedfeatures
870 return number
871 else
872 report_features("unknown feature %a cannot be merged into %a using method %a",extraname,mergedname,how)
873 return 0
874 end
875end
876
877specifiers.presetcontext = presetcontext
878specifiers.contextnumber = contextnumber
879specifiers.mergecontext = mergecontext
880specifiers.registercontext = registercontext
881specifiers.definecontext = definecontext
882
883
884
885constructors.hashmethods.virtual = function(list)
886 local s = { }
887 local n = 0
888 for k, v in next, list do
889 n = n + 1
890
891
892
893
894
895
896
897 s[n] = k .. "=" .. tostring(v)
898 end
899 if n > 0 then
900 sort(s)
901 return concat(s,"+")
902 end
903end
904
905if not JITSUPPORTED then
906
907 constructors.hashmethods.normal = function(list)
908 local s = { }
909 local n = 0
910 for k, v in next, list do
911 if not k then
912
913 elseif k == "number" or k == "features" then
914
915 else
916 n = n + 1
917 if type(v) == "table" then
918
919 local t = { }
920 local m = 0
921 for k, v in next, v do
922 m = m + 1
923 t[m] = format("%q=%q",k,v)
924 end
925 sort(t)
926 s[n] = format("%q={%s}",k,concat(t,","))
927 else
928 s[n] = format("%q=%q",k,v)
929 end
930 end
931 end
932 if n > 0 then
933 sort(s)
934 return concat(s,"+")
935 end
936 end
937
938 constructors.hashmethods.virtual = function(list)
939 local s = { }
940 local n = 0
941 for k, v in next, list do
942 n = n + 1
943 s[n] = format("%q=%q",k,v)
944 end
945 if n > 0 then
946 sort(s)
947 return concat(s,"+")
948 end
949 end
950
951end
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979function specifiers.showcontext(name)
980 return setups[name] or setups[numbers[name]] or setups[numbers[tonumber(name)]] or { }
981end
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005local function splitcontext(features)
1006 local n, sf
1007
1008 if find(features,"[,=]") then
1009
1010
1011
1012
1013
1014
1015 setups[features] = nil
1016
1017
1018 n, sf = presetcontext(features,features,"")
1019 else
1020 sf = setups[features]
1021 if not sf then
1022
1023 n, sf = presetcontext(features,"","")
1024 end
1025 end
1026 return fastcopy(sf)
1027end
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070specifiers.splitcontext = splitcontext
1071
1072function specifiers.contexttostring(name,kind,separator,yes,no,strict,omit)
1073 return hash_to_string(
1074 mergedtable(handlers[kind].features.defaults or {},setups[name] or {}),
1075 separator, yes, no, strict, omit or { "number" }
1076 )
1077end
1078
1079local function starred(features)
1080 local detail = features.detail
1081 if detail and detail ~= "" then
1082 features.features.normal = splitcontext(detail)
1083 else
1084 features.features.normal = { }
1085 end
1086 return features
1087end
1088
1089definers.registersplit('*',starred,"featureset")
1090
1091
1092
1093local space = P(" ")
1094local spaces = space^0
1095local separator = S(";,")
1096local equal = P("=")
1097local sometext = C((1-equal-space-separator)^1)
1098local truevalue = P("+") * spaces * sometext * Cc(true)
1099local falsevalue = P("-") * spaces * sometext * Cc(false)
1100local somevalue = sometext * spaces * Cc(true)
1101local keyvalue = sometext * spaces * equal * spaces * sometext
1102local pattern = Cf(Ct("") * (space + separator + Cg(falsevalue + truevalue + keyvalue + somevalue))^0, rawset)
1103
1104local function colonized(specification)
1105 specification.features.normal = normalize_features(lpegmatch(pattern,specification.detail))
1106 return specification
1107end
1108
1109definers.registersplit(":",colonized,"direct")
1110
1111
1112
1113local sizepattern, splitpattern, specialscale do
1114
1115
1116
1117 local leftparent = (P"(")
1118 local rightparent = (P")")
1119 local leftbrace = (P"{")
1120 local rightbrace = (P"}")
1121 local withinparents = leftparent * (1-rightparent)^0 * rightparent
1122 local withinbraces = leftbrace * (1-rightbrace )^0 * rightbrace
1123 local value = C((withinparents + withinbraces + (1-space))^1)
1124 local dimension = C((space/"" + P(1))^1)
1125 local rest = C(P(1)^0)
1126 local scale_none = Cc(0)
1127 local scale_at = (P("at") +P("@")) * Cc(1) * spaces * dimension
1128 local scale_sa = P("sa") * Cc(2) * spaces * dimension
1129 local scale_mo = P("mo") * Cc(3) * spaces * dimension
1130 local scale_scaled = P("scaled") * Cc(4) * spaces * dimension
1131 local scale_ht = P("ht") * Cc(5) * spaces * dimension
1132 local scale_cp = P("cp") * Cc(6) * spaces * dimension
1133
1134 specialscale = { [5] = "ht", [6] = "cp" }
1135
1136 sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none)
1137 splitpattern = spaces * value * spaces * rest
1138
1139end
1140
1141function helpers.splitfontpattern(str)
1142 local name, size = lpegmatch(splitpattern,str)
1143 local kind, size = lpegmatch(sizepattern,size)
1144 return name, kind, size
1145end
1146
1147function helpers.fontpatternhassize(str)
1148 local name, size = lpegmatch(splitpattern,str)
1149 local kind, size = lpegmatch(sizepattern,size)
1150 return size or false
1151end
1152
1153local specification
1154
1155local getspecification = definers.getspecification
1156
1157
1158
1159
1160local specifiers = { }
1161
1162do
1163
1164 local starttiming = statistics.starttiming
1165 local stoptiming = statistics.stoptiming
1166
1167 local setmacro = tokens.setters.macro
1168
1169 implement {
1170 name = "definefont_one",
1171 arguments = "string",
1172 actions = function(str)
1173 starttiming(fonts)
1174 if trace_defining then
1175 report_defining("memory usage before: %s",statistics.memused())
1176 report_defining("start stage one: %s",str)
1177 end
1178 local fullname, size = lpegmatch(splitpattern,str)
1179 local lookup, name, sub, method, detail = getspecification(fullname)
1180 if not name then
1181 report_defining("strange definition %a",str)
1182
1183 elseif name == "unknown" then
1184
1185 else
1186
1187 setmacro("somefontname",name,"global")
1188 end
1189
1190 if size and size ~= "" then
1191 local mode, size = lpegmatch(sizepattern,size)
1192 if size and mode then
1193 texsetcount("scaledfontmode",mode)
1194
1195 setmacro("somefontsize",size)
1196 else
1197 texsetcount("scaledfontmode",0)
1198
1199 end
1200 elseif true then
1201
1202 texsetcount("scaledfontmode",2)
1203
1204 else
1205 texsetcount("scaledfontmode",0)
1206
1207 end
1208 specification = definers.makespecification(str,lookup,name,sub,method,detail,size)
1209
1210 if trace_defining then
1211 report_defining("stop stage one")
1212 end
1213 end
1214 }
1215
1216 local function nice_cs(cs)
1217 return (gsub(cs,".->", ""))
1218 end
1219
1220 local n = 0
1221 local busy = false
1222 local combinefeatures = false
1223
1224 directives.register("fonts.features.combine",function(v)
1225 combinefeatures = v
1226 end)
1227
1228 implement {
1229 name = "definefont_two",
1230 arguments = {
1231 "boolean", "string", "string", "integer", "integer", "string", "string", "string", "string",
1232 "integer", "integer", "integer", "string", "string", "string", "string", "integer",
1233 },
1234 actions = function (
1235 global,
1236 cs,
1237 str,
1238 size,
1239 inheritancemode,
1240 classfeatures,
1241 fontfeatures,
1242 classfallbacks,
1243 fontfallbacks,
1244 mathsize,
1245 textsize,
1246 relativeid,
1247 classgoodies,
1248 goodies,
1249 classdesignsize,
1250 fontdesignsize,
1251 scaledfontmode
1252 )
1253 if trace_defining then
1254 report_defining("start stage two: %s, size %s, features %a & %a, mode %a",str,size,classfeatures,fontfeatures,inheritancemode)
1255 end
1256
1257 local lookup, name, sub, method, detail = getspecification(str or "")
1258
1259 local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or ""
1260 local designname = designsizefilename(name,designsize,size)
1261 if designname and designname ~= "" then
1262 if trace_defining or trace_designsize then
1263 report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname)
1264 end
1265
1266 local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname)
1267 if o_lookup and o_lookup ~= "" then lookup = o_lookup end
1268 if o_method and o_method ~= "" then method = o_method end
1269 if o_detail and o_detail ~= "" then detail = o_detail end
1270 name = o_name
1271 sub = o_sub
1272 end
1273
1274
1275 if lookup and lookup ~= "" then
1276 specification.lookup = lookup
1277 end
1278 if relativeid and relativeid ~= "" then
1279 local id = tonumber(relativeid) or 0
1280 specification.relativeid = id > 0 and id
1281 end
1282
1283 specification.name = name
1284 specification.size = size
1285 specification.sub = (sub and sub ~= "" and sub) or specification.sub
1286 specification.mathsize = mathsize
1287 specification.textsize = textsize
1288 specification.goodies = goodies
1289 specification.cs = cs
1290 specification.global = global
1291 specification.scalemode = scaledfontmode
1292 if detail and detail ~= "" then
1293 specification.method = method or "*"
1294 specification.detail = detail
1295 elseif specification.detail and specification.detail ~= "" then
1296
1297 elseif inheritancemode == 0 then
1298
1299 elseif inheritancemode == 1 then
1300
1301 if fontfeatures and fontfeatures ~= "" then
1302 specification.method = "*"
1303 specification.detail = fontfeatures
1304 end
1305 if fontfallbacks and fontfallbacks ~= "" then
1306 specification.fallbacks = fontfallbacks
1307 end
1308 elseif inheritancemode == 2 then
1309
1310 if classfeatures and classfeatures ~= "" then
1311 specification.method = "*"
1312 specification.detail = classfeatures
1313 end
1314 if classfallbacks and classfallbacks ~= "" then
1315 specification.fallbacks = classfallbacks
1316 end
1317 elseif inheritancemode == 3 then
1318
1319 if combinefeatures then
1320 if classfeatures and classfeatures ~= "" then
1321 specification.method = "*"
1322 if fontfeatures and fontfeatures ~= "" and fontfeatures ~= classfeatures then
1323 specification.detail = classfeatures .. "," .. fontfeatures
1324 else
1325 specification.detail = classfeatures
1326 end
1327 elseif fontfeatures and fontfeatures ~= "" then
1328 specification.method = "*"
1329 specification.detail = fontfeatures
1330 end
1331 else
1332 if fontfeatures and fontfeatures ~= "" then
1333 specification.method = "*"
1334 specification.detail = fontfeatures
1335 elseif classfeatures and classfeatures ~= "" then
1336 specification.method = "*"
1337 specification.detail = classfeatures
1338 end
1339 end
1340 if fontfallbacks and fontfallbacks ~= "" then
1341 specification.fallbacks = fontfallbacks
1342 elseif classfallbacks and classfallbacks ~= "" then
1343 specification.fallbacks = classfallbacks
1344 end
1345 elseif inheritancemode == 4 then
1346
1347 if combinefeatures then
1348 if fontfeatures and fontfeatures ~= "" then
1349 specification.method = "*"
1350 if classfeatures and classfeatures ~= "" and classfeatures ~= fontfeatures then
1351 specification.detail = fontfeatures .. "," .. classfeatures
1352 else
1353 specification.detail = fontfeatures
1354 end
1355 elseif classfeatures and classfeatures ~= "" then
1356 specification.method = "*"
1357 specification.detail = classfeatures
1358 end
1359 else
1360 if classfeatures and classfeatures ~= "" then
1361 specification.method = "*"
1362 specification.detail = classfeatures
1363 elseif fontfeatures and fontfeatures ~= "" then
1364 specification.method = "*"
1365 specification.detail = fontfeatures
1366 end
1367 end
1368 if classfallbacks and classfallbacks ~= "" then
1369 specification.fallbacks = classfallbacks
1370 elseif fontfallbacks and fontfallbacks ~= "" then
1371 specification.fallbacks = fontfallbacks
1372 end
1373 end
1374
1375 local tfmdata = definers.read(specification,size)
1376
1377 local lastfontid = 0
1378 local tfmtype = type(tfmdata)
1379 if tfmtype == "table" then
1380
1381 local characters = tfmdata.characters
1382 local parameters = tfmdata.parameters
1383 local properties = tfmdata.properties
1384
1385 characters[0] = nil
1386
1387
1388
1389
1390 local fallbacks = specification.fallbacks or ""
1391 local mathsize = (mathsize == 1 or mathsize == 2 or mathsize == 3) and mathsize or nil
1392 if fallbacks ~= "" and mathsize and not busy then
1393 busy = true
1394
1395
1396
1397
1398
1399 if trace_defining then
1400 report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
1401 name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,1)
1402 end
1403 mathematics.resolvefallbacks(tfmdata,specification,fallbacks)
1404 context(function()
1405 busy = false
1406 mathematics.finishfallbacks(tfmdata,specification,fallbacks)
1407 tfmdata.original = specification.specification
1408 local id = definefont(tfmdata)
1409 csnames[id] = specification.cs
1410 properties.id = id
1411 definers.register(tfmdata,id)
1412 texdefinefont(global,cs,id)
1413 constructors.cleanuptable(tfmdata)
1414 constructors.finalize(tfmdata)
1415 if trace_defining then
1416 report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
1417 name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,2)
1418 end
1419
1420 local size = round(tfmdata.parameters.size or 655360)
1421 setmacro("somefontsize",size.."sp")
1422
1423 texsetcount("scaledfontsize",size)
1424 lastfontid = id
1425
1426 if trace_defining then
1427 report_defining("memory usage after: %s",statistics.memused())
1428 report_defining("stop stage two")
1429 end
1430
1431 texsetcount("global","lastfontid",lastfontid)
1432 specifiers[lastfontid] = { str, size }
1433 if not mathsize then
1434
1435 elseif mathsize == 0 then
1436
1437 else
1438
1439 lastmathids[mathsize] = lastfontid
1440 end
1441 stoptiming(fonts)
1442 end)
1443 return
1444 else
1445 tfmdata.original = specification.specification
1446 local id = definefont(tfmdata)
1447 csnames[id] = specification.cs
1448 properties.id = id
1449 definers.register(tfmdata,id)
1450 texdefinefont(global,cs,id)
1451 constructors.cleanuptable(tfmdata)
1452 constructors.finalize(tfmdata)
1453 if trace_defining then
1454 report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
1455 name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,"-")
1456 end
1457
1458 local size = round(tfmdata.parameters.size or 655360)
1459 setmacro("somefontsize",size.."sp")
1460
1461 texsetcount("scaledfontsize",size)
1462 lastfontid = id
1463 end
1464 elseif tfmtype == "number" then
1465 if trace_defining then
1466 report_defining("reusing %s, id %a, target %a, features %a / %a, fallbacks %a / %a, goodies %a / %a, designsize %a / %a",
1467 name,tfmdata,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,classgoodies,goodies,classdesignsize,fontdesignsize)
1468 end
1469 csnames[tfmdata] = specification.cs
1470 texdefinefont(global,cs,tfmdata)
1471
1472 local size = round(fontdata[tfmdata].parameters.size or 0)
1473
1474 setmacro("somefontsize",size.."sp")
1475 texsetcount("scaledfontsize",size)
1476 lastfontid = tfmdata
1477 else
1478 report_defining("unable to define %a as %a",name,nice_cs(cs))
1479 lastfontid = -1
1480 texsetcount("scaledfontsize",0)
1481
1482 end
1483 if trace_defining then
1484 report_defining("memory usage after: %s",statistics.memused())
1485 report_defining("stop stage two")
1486 end
1487
1488 texsetcount("global","lastfontid",lastfontid)
1489 specifiers[lastfontid] = { str, size }
1490 if not mathsize then
1491
1492 elseif mathsize == 0 then
1493
1494 else
1495
1496 lastmathids[mathsize] = lastfontid
1497 end
1498
1499 stoptiming(fonts)
1500 end
1501 }
1502
1503 implement {
1504 name = "specifiedfontspec",
1505 arguments = "integer",
1506 actions = function(id)
1507 local f = specifiers[id]
1508 if f then
1509 context(f[1])
1510 end
1511 end
1512 }
1513
1514 implement {
1515 name = "specifiedfontsize",
1516 arguments = "integer",
1517 actions = function(id)
1518 local f = specifiers[id]
1519 if f then
1520 context(f[2])
1521 end
1522 end
1523 }
1524
1525 implement {
1526 name = "specifiedfont",
1527 arguments = { "integer", "number" },
1528 actions = function(id,size)
1529 local f = specifiers[id]
1530 if f and size then
1531 context("%s at %0.2p",f[1],size * f[2])
1532 end
1533 end
1534 }
1535
1536
1537 local function define(specification)
1538
1539 local name = specification.name
1540 if not name or name == "" then
1541 return -1
1542 else
1543 starttiming(fonts)
1544
1545
1546
1547 local lookup, name, sub, method, detail = getspecification(name or "")
1548
1549 specification.name = (name ~= "" and name) or specification.name
1550
1551 specification.lookup = specification.lookup or (lookup ~= "" and lookup) or "file"
1552 specification.size = specification.size or 655260
1553 specification.sub = specification.sub or (sub ~= "" and sub) or ""
1554 specification.method = specification.method or (method ~= "" and method) or "*"
1555 specification.detail = specification.detail or (detail ~= "" and detail) or ""
1556
1557 if type(specification.size) == "string" then
1558 specification.size = texsp(specification.size) or 655260
1559 end
1560
1561 specification.specification = ""
1562 specification.resolved = ""
1563 specification.forced = ""
1564 specification.features = { }
1565
1566
1567
1568 local cs = specification.cs
1569 if cs == "" then
1570 cs = nil
1571 specification.cs = nil
1572 specification.global = false
1573 elseif specification.global == nil then
1574 specification.global = false
1575 end
1576
1577 local tfmdata = definers.read(specification,specification.size)
1578 if not tfmdata then
1579 return -1, nil
1580 elseif type(tfmdata) == "number" then
1581 if cs then
1582 texdefinefont(specification.global,cs,tfmdata)
1583 csnames[tfmdata] = cs
1584 end
1585 stoptiming(fonts)
1586 return tfmdata, fontdata[tfmdata]
1587 else
1588 local id = definefont(tfmdata)
1589 tfmdata.properties.id = id
1590 definers.register(tfmdata,id)
1591 if cs then
1592 texdefinefont(specification.global,cs,id)
1593 csnames[id] = cs
1594 end
1595 constructors.cleanuptable(tfmdata)
1596 constructors.finalize(tfmdata)
1597 stoptiming(fonts)
1598 return id, tfmdata
1599 end
1600 end
1601 end
1602
1603 definers.define = define
1604
1605
1606
1607
1608
1609 local n = 0
1610
1611 function definers.internal(specification,cs)
1612 specification = specification or { }
1613 local name = specification.name
1614 local size = tonumber(specification.size)
1615 local number = tonumber(specification.number)
1616 local id = nil
1617 if not size then
1618 size = texgetdimen("bodyfontsize")
1619 end
1620 if number then
1621 id = number
1622 elseif name and name ~= "" then
1623 local cs = cs or specification.cs
1624 if not cs then
1625 n = n + 1
1626
1627 cs = "internal font " .. n
1628 else
1629 specification.cs = cs
1630 end
1631 id = define {
1632 name = name,
1633 size = size,
1634 cs = cs,
1635 }
1636 end
1637 if not id then
1638 id = currentfont()
1639 end
1640 return id, csnames[id]
1641 end
1642
1643 callbacks.register('define_font', definers.read, "definition of fonts (tfmdata preparation)")
1644
1645
1646
1647 local infofont = 0
1648
1649 function fonts.infofont()
1650 if infofont == 0 then
1651 infofont = define { name = "dejavusansmono", size = texsp("6pt") }
1652 end
1653 return infofont
1654 end
1655
1656
1657
1658 implement { name = "tf", actions = function() setmacro("fontalternative","tf") end }
1659 implement { name = "bf", actions = function() setmacro("fontalternative","bf") end }
1660 implement { name = "it", actions = function() setmacro("fontalternative","it") end }
1661 implement { name = "sl", actions = function() setmacro("fontalternative","sl") end }
1662 implement { name = "bi", actions = function() setmacro("fontalternative","bi") end }
1663 implement { name = "bs", actions = function() setmacro("fontalternative","bs") end }
1664
1665end
1666
1667local enable_auto_r_scale = false
1668
1669experiments.register("fonts.autorscale", function(v)
1670 enable_auto_r_scale = v
1671end)
1672
1673
1674
1675
1676
1677local calculatescale = constructors.calculatescale
1678
1679function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specification)
1680 if specification then
1681 local scalemode = specification.scalemode
1682 local special = scalemode and specialscale[scalemode]
1683 if special then
1684
1685 local parameters = tfmdata.parameters
1686
1687 if special == "ht" then
1688 local height = parameters.ascender / parameters.units
1689 scaledpoints = scaledpoints / height
1690 elseif special == "cp" then
1691 local glyph = tfmdata.descriptions[utfbyte("X")]
1692 local height = (glyph and glyph.height or parameters.ascender) / parameters.units
1693 scaledpoints = scaledpoints / height
1694 end
1695 end
1696 end
1697 local scaledpoints, delta = calculatescale(tfmdata,scaledpoints)
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709 return round(scaledpoints), round(delta)
1710end
1711
1712local designsizes = constructors.designsizes
1713
1714
1715
1716
1717function constructors.hashinstance(specification,force)
1718 local hash = specification.hash
1719 local size = specification.size
1720 local fallbacks = specification.fallbacks
1721 if force or not hash then
1722 hash = constructors.hashfeatures(specification)
1723 specification.hash = hash
1724 end
1725 if size < 1000 and designsizes[hash] then
1726 size = round(constructors.scaled(size,designsizes[hash]))
1727 else
1728 size = round(size)
1729 end
1730 specification.size = size
1731 if fallbacks then
1732 return hash .. ' @ ' .. size .. ' @ ' .. fallbacks
1733 else
1734 local scalemode = specification.scalemode
1735 local special = scalemode and specialscale[scalemode]
1736 if special then
1737 return hash .. ' @ ' .. size .. ' @ ' .. special
1738 else
1739 return hash .. ' @ ' .. size
1740 end
1741 end
1742end
1743
1744
1745
1746local resolvers = definers.resolvers
1747local hashfeatures = constructors.hashfeatures
1748
1749function definers.resolve(specification)
1750 if not specification.resolved or specification.resolved == "" then
1751 local r = resolvers[specification.lookup]
1752 if r then
1753 r(specification)
1754 end
1755 end
1756 if specification.forced == "" then
1757 specification.forced = nil
1758 else
1759 specification.forced = specification.forced
1760 end
1761
1762
1763
1764 local goodies = specification.goodies
1765 if goodies and goodies ~= "" then
1766
1767 local normal = specification.features.normal
1768 if not normal then
1769 specification.features.normal = { goodies = goodies }
1770 elseif not normal.goodies then
1771 local g = normal.goodies
1772 if g and g ~= "" then
1773 normal.goodies = formatters["%s,%s"](g,goodies)
1774 else
1775 normal.goodies = goodies
1776 end
1777 end
1778 end
1779
1780 local hash = hashfeatures(specification)
1781 local name = specification.name or "badfont"
1782 local sub = specification.sub
1783 if sub and sub ~= "" then
1784 specification.hash = lower(name .. " @ " .. sub .. ' @ ' .. hash)
1785 else
1786 specification.hash = lower(name .. " @ " .. ' @ ' .. hash)
1787 end
1788
1789 return specification
1790end
1791
1792
1793
1794local mappings = fonts.mappings
1795
1796local loaded = {
1797
1798
1799
1800
1801}
1802
1803function mappings.loadfile(name)
1804 name = file.addsuffix(name,"map")
1805 if not loaded[name] then
1806 if trace_mapfiles then
1807 report_mapfiles("loading map file %a",name)
1808 end
1809 lpdf.setmapfile(name)
1810 loaded[name] = true
1811 end
1812end
1813
1814local loaded = {
1815}
1816
1817function mappings.loadline(how,line)
1818 if line then
1819 how = how .. " " .. line
1820 elseif how == "" then
1821 how = "= " .. line
1822 end
1823 if not loaded[how] then
1824 if trace_mapfiles then
1825 report_mapfiles("processing map line %a",line)
1826 end
1827 lpdf.setmapline(how)
1828 loaded[how] = true
1829 end
1830end
1831
1832function mappings.reset()
1833 lpdf.setmapfile("")
1834end
1835
1836implement {
1837 name = "loadmapfile",
1838 actions = mappings.loadfile,
1839 arguments = "string"
1840}
1841
1842implement {
1843 name = "loadmapline",
1844 actions = mappings.loadline,
1845 arguments = "string"
1846}
1847
1848implement {
1849 name = "resetmapfiles",
1850 actions = mappings.reset,
1851 arguments = "string"
1852}
1853
1854
1855
1856
1857
1858local pattern = P("P")
1859 * (lpeg.patterns.hexdigit^4 / function(s) return tonumber(s,16) end)
1860 * P(-1)
1861
1862local function nametoslot(name)
1863 local t = type(name)
1864 if t == "string" then
1865 local unic = unicodes[true]
1866 local slot = unic[name]
1867 if slot then
1868 return slot
1869 end
1870
1871 local slot = unic[gsub(name,"_"," ")] or unic[gsub(name,"_","-")] or
1872 unic[gsub(name,"-"," ")] or unic[gsub(name,"-","_")] or
1873 unic[gsub(name," ","_")] or unic[gsub(name," ","-")]
1874 if slot then
1875 return slot
1876 end
1877
1878 if not aglunicodes then
1879 aglunicodes = encodings.agl.unicodes
1880 end
1881 local char = characters[true]
1882 local slot = aglunicodes[name]
1883 if slot and char[slot] then
1884 return slot
1885 end
1886 local slot = lpegmatch(pattern,name)
1887 if slot and char[slot] then
1888 return slot
1889 end
1890
1891 elseif t == "number" then
1892 if characters[true][name] then
1893 return slot
1894 else
1895
1896 end
1897 end
1898end
1899
1900local found = { }
1901
1902local function descriptiontoslot(name)
1903 local t = type(name)
1904 if t == "string" then
1905
1906 local list = sortedkeys(chardata)
1907 local slot = found[name]
1908 local char = characters[true]
1909 if slot then
1910 return char[slot] and slot or nil
1911 end
1912 local NAME = upper(name)
1913 for i=1,#list do
1914 slot = list[i]
1915 local c = chardata[slot]
1916 local d = c.description
1917 if d == NAME then
1918 found[name] = slot
1919 return char[slot] and slot or nil
1920 end
1921 end
1922 for i=1,#list do
1923 slot = list[i]
1924 local c = chardata[slot]
1925 local s = c.synonyms
1926 if s then
1927 for i=1,#s do
1928 local si = s[i]
1929 if si == name then
1930 found[name] = si
1931 return char[slot] and slot or nil
1932 end
1933 end
1934 end
1935 end
1936 for i=1,#list do
1937 slot = list[i]
1938 local c = chardata[slot]
1939 local d = c.description
1940 if d and find(d,NAME) then
1941 found[name] = slot
1942 return char[slot] and slot or nil
1943 end
1944 end
1945 for i=1,#list do
1946 slot = list[i]
1947 local c = chardata[slot]
1948 local s = c.synonyms
1949 if s then
1950 for i=1,#s do
1951 local si = s[i]
1952 if find(s[i],name) then
1953 found[name] = si
1954 return char[slot] and slot or nil
1955 end
1956 end
1957 end
1958 end
1959
1960 elseif t == "number" then
1961 if characters[true][name] then
1962 return slot
1963 else
1964
1965 end
1966 end
1967end
1968
1969local function indextoslot(font,index)
1970 if not index then
1971 index = font
1972 font = true
1973 end
1974 local r = resources[font]
1975 if r then
1976 local indices = r.indices
1977 if not indices then
1978 indices = { }
1979 local c = characters[font]
1980 for unicode, data in next, c do
1981 local di = data.index
1982 if di then
1983 indices[di] = unicode
1984 end
1985 end
1986 r.indices = indices
1987 end
1988 return indices[tonumber(index)]
1989 end
1990end
1991
1992do
1993
1994 local entities = characters.entities
1995 local lowered = { }
1996
1997 setmetatableindex(lowered,function(t,k)
1998 for k, v in next, entities do
1999 local l = lower(k)
2000 if not entities[l] then
2001 lowered[l] = v
2002 end
2003 end
2004 setmetatableindex(lowered,nil)
2005 return lowered[k]
2006 end)
2007
2008 local methods = {
2009
2010 e = function(name)
2011 return entities[name] or lowered[name] or name
2012 end,
2013
2014 x = function(name)
2015 local n = tonumber(name,16)
2016 return n and utfchar(n) or name
2017 end,
2018
2019 d = function(name)
2020 local n = tonumber(name)
2021 return n and utfchar(n) or name
2022 end,
2023
2024 s = function(name)
2025 local n = tonumber(name,16)
2026 local n = n and indextoslot(n)
2027 return n and utfchar(n) or name
2028 end,
2029
2030 i = function(name)
2031 local n = tonumber(name)
2032 local n = n and indextoslot(n)
2033 return n and utfchar(n) or name
2034 end,
2035
2036 n = function(name)
2037 local n = nametoslot(name)
2038 return n and utfchar(n) or name
2039 end,
2040
2041 u = function(name)
2042 local n = descriptiontoslot(name,false)
2043 return n and utfchar(n) or name
2044 end,
2045
2046 a = function(name)
2047 local n = nametoslot(name) or descriptiontoslot(name)
2048 return n and utfchar(n) or name
2049 end,
2050
2051 c = function(name)
2052 return name
2053 end,
2054 }
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066 local splitter = C(1) * P(":") * C(P(1)^1) / function(method,name)
2067 local action = methods[method]
2068 return action and action(name) or name
2069 end
2070
2071 local function tochar(str)
2072 local t = type(str)
2073 if t == "number" then
2074 return utfchar(str)
2075 elseif t == "string" then
2076 return lpegmatch(splitter,str) or str
2077 else
2078 return str
2079 end
2080 end
2081
2082 helpers.nametoslot = nametoslot
2083 helpers.descriptiontoslot = descriptiontoslot
2084 helpers.indextoslot = indextoslot
2085 helpers.tochar = tochar
2086
2087
2088
2089 implement {
2090 name = "fontchar",
2091 actions = { nametoslot, ctx_char },
2092 arguments = "string",
2093 }
2094
2095 implement {
2096 name = "fontcharbyindex",
2097 actions = { indextoslot, ctx_char },
2098 arguments = "integer",
2099 }
2100
2101 implement {
2102 name = "tochar",
2103 actions = { tochar, ctx_safechar },
2104 arguments = "string",
2105 }
2106
2107end
2108
2109
2110
2111function loggers.reportdefinedfonts()
2112 if trace_usage then
2113 local t, tn = { }, 0
2114 for id, data in sortedhash(fontdata) do
2115 local properties = data.properties or { }
2116 local parameters = data.parameters or { }
2117 tn = tn + 1
2118 t[tn] = {
2119 formatters["%03i"](id or 0),
2120 formatters["%p" ](parameters.size or 0),
2121 properties.type or "real",
2122 properties.format or "unknown",
2123 properties.name or "",
2124 properties.psname or "",
2125 properties.fullname or "",
2126 properties.sharedwith or "",
2127 }
2128 end
2129 formatcolumns(t," ")
2130
2131 logs.startfilelogging(report,"defined fonts")
2132 for k=1,tn do
2133 report(t[k])
2134 end
2135 logs.stopfilelogging()
2136 end
2137end
2138
2139logs.registerfinalactions(loggers.reportdefinedfonts)
2140
2141function loggers.reportusedfeatures()
2142
2143 if trace_usage then
2144 local t, n = { }, #numbers
2145 for i=1,n do
2146 local name = numbers[i]
2147 local setup = setups[name]
2148 local n = setup.number
2149 setup.number = nil
2150 t[i] = { i, name, sequenced(setup,false,true) }
2151 setup.number = n
2152 end
2153 formatcolumns(t," ")
2154 logs.startfilelogging(report,"defined featuresets")
2155 for k=1,n do
2156 report(t[k])
2157 end
2158 logs.stopfilelogging()
2159 end
2160end
2161
2162logs.registerfinalactions(loggers.reportusedfeatures)
2163
2164
2165
2166statistics.register("font engine", function()
2167 local elapsed = statistics.elapsedseconds(fonts)
2168 local nofshared = constructors.nofsharedfonts or 0
2169 local nofloaded = constructors.noffontsloaded or 0
2170 if nofshared > 0 then
2171 return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, %s shared in backend, %s common vectors, %s common hashes, load time %s",
2172 otf.version,afm.version,tfm.version,nofloaded,
2173 nofshared,constructors.nofsharedvectors,constructors.nofsharedhashes,
2174 elapsed)
2175 elseif nofloaded > 0 and elapsed then
2176 return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, load time %s",
2177 otf.version,afm.version,tfm.version,nofloaded,
2178 elapsed)
2179 else
2180 return format("otf %0.3f, afm %0.3f, tfm %0.3f",
2181 otf.version,afm.version,tfm.version)
2182 end
2183end)
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200local Shapes = {
2201 serif = "Serif",
2202 sans = "Sans",
2203 mono = "Mono",
2204}
2205
2206local ctx_startfontclass = context.startfontclass
2207local ctx_stopfontclass = context.stopfontclass
2208local ctx_definefontsynonym = context.definefontsynonym
2209local ctx_dofastdefinetypeface = context.dofastdefinetypeface
2210
2211function fonts.definetypeface(name,t)
2212 if type(name) == "table" then
2213
2214 t = name
2215 elseif t then
2216 if type(t) == "string" then
2217
2218 t = settings_to_hash(name)
2219 else
2220
2221 end
2222 t.name = t.name or name
2223 else
2224
2225 t = settings_to_hash(name)
2226 end
2227 local p = t.preset and fonts.typefaces[t.preset] or { }
2228 local name = t.name or "unknowntypeface"
2229 local shortcut = t.shortcut or p.shortcut or "rm"
2230 local size = t.size or p.size or "default"
2231 local shape = t.shape or p.shape or "serif"
2232 local fontname = t.fontname or p.fontname or "unknown"
2233 local normalweight = t.normalweight or t.weight or p.normalweight or p.weight or "normal"
2234 local boldweight = t.boldweight or t.weight or p.boldweight or p.weight or "normal"
2235 local normalwidth = t.normalwidth or t.width or p.normalwidth or p.width or "normal"
2236 local boldwidth = t.boldwidth or t.width or p.boldwidth or p.width or "normal"
2237 Shape = Shapes[shape] or "Serif"
2238 ctx_startfontclass { name }
2239 ctx_definefontsynonym( { formatters["%s"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, normalweight, normalwidth) } )
2240 ctx_definefontsynonym( { formatters["%sBold"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, boldweight, boldwidth ) } )
2241 ctx_definefontsynonym( { formatters["%sBoldItalic"](Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, boldweight, boldwidth ) } )
2242 ctx_definefontsynonym( { formatters["%sItalic"] (Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, normalweight, normalwidth) } )
2243 ctx_stopfontclass()
2244 local settings = sequenced({ features= t.features },",")
2245 ctx_dofastdefinetypeface(name, shortcut, shape, size, settings)
2246end
2247
2248implement {
2249 name = "definetypeface",
2250 actions = fonts.definetypeface,
2251 arguments = "2 strings"
2252}
2253
2254function fonts.current()
2255 return fontdata[currentfont()] or fontdata[0]
2256end
2257
2258function fonts.currentid()
2259 return currentfont() or 0
2260end
2261
2262
2263
2264
2265local dimenfactors = number.dimenfactors
2266
2267function helpers.dimenfactor(unit,id)
2268 if unit == "ex" then
2269 return id and exheights[id] or 282460
2270 elseif unit == "em" then
2271 return id and emwidths [id] or 655360
2272 else
2273 local du = dimenfactors[unit]
2274 return du and 1/du or tonumber(unit) or 1
2275 end
2276end
2277
2278local function digitwidth(font)
2279 local tfmdata = fontdata[font]
2280 local parameters = tfmdata.parameters
2281 local width = parameters.digitwidth
2282 if not width then
2283 width = round(parameters.quad/2)
2284 local characters = tfmdata.characters
2285 for i=48,57 do
2286 local wd = round(characters[i].width)
2287 if wd > width then
2288 width = wd
2289 end
2290 end
2291 parameters.digitwidth = width
2292 end
2293 return width
2294end
2295
2296helpers.getdigitwidth = digitwidth
2297helpers.setdigitwidth = digitwidth
2298
2299
2300
2301function helpers.getparameters(tfmdata)
2302 local p = { }
2303 local m = p
2304 local parameters = tfmdata.parameters
2305 while true do
2306 for k, v in next, parameters do
2307 m[k] = v
2308 end
2309 parameters = getmetatable(parameters)
2310 parameters = parameters and parameters.__index
2311 if type(parameters) == "table" then
2312 m = { }
2313 p.metatable = m
2314 else
2315 break
2316 end
2317 end
2318 return p
2319end
2320
2321if environment.initex then
2322
2323 local function names(t)
2324 local nt = #t
2325 if nt > 0 then
2326 local n = { }
2327 for i=1,nt do
2328 n[i] = t[i].name
2329 end
2330 return concat(n," ")
2331 else
2332 return "-"
2333 end
2334 end
2335
2336 statistics.register("font processing", function()
2337 local l = { }
2338 for what, handler in table.sortedpairs(handlers) do
2339 local features = handler and handler.features
2340 if features then
2341 l[#l+1] = format("[%s (base initializers: %s) (base processors: %s) (base manipulators: %s) (node initializers: %s) (node processors: %s) (node manipulators: %s)]",
2342 what,
2343 names(features.initializers.base),
2344 names(features.processors .base),
2345 names(features.manipulators.base),
2346 names(features.initializers.node),
2347 names(features.processors .node),
2348 names(features.manipulators.node)
2349 )
2350 end
2351 end
2352 return concat(l, " | ")
2353 end)
2354
2355end
2356
2357
2358
2359
2360
2361
2362
2363setmetatableindex(dimenfactors, function(t,k)
2364 if k == "ex" then
2365 return 1/exheights[currentfont()]
2366 elseif k == "em" then
2367 return 1/emwidths[currentfont()]
2368 elseif k == "pct" or k == "%" then
2369 return 1/(texget("hsize")/100)
2370 else
2371
2372 return false
2373 end
2374end)
2375
2376dimenfactors.ex = nil
2377dimenfactors.em = nil
2378dimenfactors["%"] = nil
2379dimenfactors.pct = nil
2380
2381
2382
2383
2384do
2385
2386
2387
2388 local texsetglyphdata = tex.setglyphdata
2389 local texgetglyphdata = tex.getglyphdata
2390
2391 if not texsetglyphdata then
2392
2393 local texsetattribute = tex.setattribute
2394 local texgetattribute = tex.getattribute
2395
2396 texsetglyphdata = function(n) return texsetattribute(0,n) end
2397 texgetglyphdata = function() return texgetattribute(0) end
2398
2399 tex.setglyphdata = texsetglyphdata
2400 tex.getglyphdata = texgetglyphdata
2401
2402 end
2403
2404
2405
2406 local setmacro = tokens.setters.macro
2407
2408 function constructors.currentfonthasfeature(n)
2409 local f = fontdata[currentfont()]
2410 if not f then return end f = f.shared
2411 if not f then return end f = f.rawdata
2412 if not f then return end f = f.resources
2413 if not f then return end f = f.features
2414 return f and (f.gpos[n] or f.gsub[n])
2415 end
2416
2417 local ctx_doifelse = commands.doifelse
2418 local ctx_doif = commands.doif
2419
2420 implement {
2421 name = "doifelsecurrentfonthasfeature",
2422 actions = { constructors.currentfonthasfeature, ctx_doifelse },
2423 arguments = "string"
2424 }
2425
2426 local f_strip = formatters["%0.2fpt"]
2427 local stripper = lpeg.patterns.stripzeros
2428
2429 local cache = { }
2430
2431 local hows = {
2432 ["+"] = "add",
2433 ["-"] = "subtract",
2434 ["="] = "replace",
2435 }
2436
2437 local function setfeature(how,parent,name,font)
2438 if not how or how == 0 then
2439 if trace_features and texgetglyphdata() ~= 0 then
2440 report_cummulative("font %!font:name!, reset",fontdata[font or true])
2441 end
2442 texsetglyphdata(0)
2443 elseif how == true or how == 1 then
2444 local hash = "feature > " .. parent
2445 local done = cache[hash]
2446 if trace_features and done then
2447 report_cummulative("font %!font:name!, revive %a : %!font:features!",fontdata[font or true],parent,setups[numbers[done]])
2448 end
2449 texsetglyphdata(done or 0)
2450 else
2451 local full = parent .. how .. name
2452 local hash = "feature > " .. full
2453 local done = cache[hash]
2454 if not done then
2455 local n = setups[full]
2456 if n then
2457
2458 else
2459 n = mergecontextfeatures(parent,name,how,full)
2460 end
2461 done = registercontextfeature(hash,full,how)
2462 cache[hash] = done
2463 if trace_features then
2464 report_cummulative("font %!font:name!, %s %a : %!font:features!",fontdata[font or true],hows[how],full,setups[numbers[done]])
2465 end
2466 end
2467 texsetglyphdata(done)
2468 end
2469 end
2470
2471 local function resetfeature()
2472 if trace_features and texgetglyphdata() ~= 0 then
2473 report_cummulative("font %!font:name!, reset",fontdata[true])
2474 end
2475 texsetglyphdata(0)
2476 end
2477
2478 local function setfontfeature(tag)
2479 texsetglyphdata(contextnumber(tag))
2480 end
2481
2482 local function resetfontfeature()
2483 texsetglyphdata(0)
2484 end
2485
2486 implement {
2487 name = "nbfs",
2488 arguments = "dimen",
2489 actions = function(d)
2490 context(lpegmatch(stripper,f_strip(d/65536)))
2491 end
2492 }
2493
2494 implement {
2495 name = "featureattribute",
2496 arguments = "string",
2497 actions = { contextnumber, context }
2498 }
2499
2500 implement {
2501 name = "setfontfeature",
2502 arguments = "string",
2503 actions = setfontfeature,
2504 }
2505
2506 implement {
2507 name = "resetfontfeature",
2508
2509 actions = resetfontfeature,
2510 }
2511
2512 implement {
2513 name = "setfontofid",
2514 arguments = "integer",
2515 actions = function(id)
2516 ctx_getvalue(csnames[id])
2517 end
2518 }
2519
2520 implement {
2521 name = "definefontfeature",
2522 arguments = "3 strings",
2523 actions = presetcontext,
2524 }
2525
2526 implement {
2527 name = "doifelsefontfeature",
2528 arguments = "string",
2529 actions = function(name) ctx_doifelse(contextnumber(name) > 1) end,
2530 }
2531
2532 implement {
2533 name = "doifunknownfontfeature",
2534 arguments = "string",
2535 actions = function(name) ctx_doif(contextnumber(name) == 0) end,
2536 }
2537
2538 implement {
2539 name = "adaptfontfeature",
2540 arguments = "2 strings",
2541 actions = adaptcontext
2542 }
2543
2544 local function registerlanguagefeatures()
2545 local specifications = languages.data.specifications
2546 for i=1,#specifications do
2547 local specification = specifications[i]
2548 local language = specification.opentype
2549 if language then
2550 local script = specification.opentypescript or specification.script
2551 if script then
2552 local context = specification.context
2553 if type(context) == "table" then
2554 for i=1,#context do
2555 definecontext(context[i], { language = language, script = script})
2556 end
2557 elseif type(context) == "string" then
2558 definecontext(context, { language = language, script = script})
2559 end
2560 end
2561 end
2562 end
2563 end
2564
2565 constructors.setfeature = setfeature
2566 constructors.resetfeature = resetfeature
2567
2568 implement { name = "resetfeature", actions = resetfeature }
2569 implement { name = "addfeature", actions = setfeature, arguments = { "'+'", "string", "string" } }
2570 implement { name = "subtractfeature", actions = setfeature, arguments = { "'-'", "string", "string" } }
2571 implement { name = "replacefeature", actions = setfeature, arguments = { "'='", "string", "string" } }
2572 implement { name = "revivefeature", actions = setfeature, arguments = { true, "string" } }
2573
2574 implement {
2575 name = "featurelist",
2576 actions = { fonts.specifiers.contexttostring, context },
2577 arguments = { "string", "'otf'", "string", "'yes'", "'no'", true }
2578 }
2579
2580 implement {
2581 name = "registerlanguagefeatures",
2582 actions = registerlanguagefeatures,
2583 }
2584
2585end
2586
2587
2588
2589
2590
2591do
2592
2593 local report = logs.reporter("otf","variants")
2594
2595 local function replace(tfmdata,feature,value)
2596 local characters = tfmdata.characters
2597 local variants = tfmdata.resources.variants
2598 if variants then
2599 local t = { }
2600 for k, v in sortedhash(variants) do
2601 t[#t+1] = formatters["0x%X (%i)"](k,k)
2602 end
2603 value = tonumber(value) or 0xFE00
2604 report("fontname : %s",tfmdata.properties.fontname)
2605 report("available: % t",t)
2606 local v = variants[value]
2607 if v then
2608 report("using : %X (%i)",value,value)
2609 for k, v in next, v do
2610 local c = characters[v]
2611 if c then
2612 characters[k] = c
2613 end
2614 end
2615 else
2616 report("unknown : %X (%i)",value,value)
2617 end
2618 end
2619 end
2620
2621 registerotffeature {
2622 name = 'variant',
2623 description = 'unicode variant',
2624 manipulators = {
2625 base = replace,
2626 node = replace,
2627 }
2628 }
2629
2630end
2631
2632
2633
2634
2635
2636do
2637
2638 local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end)
2639
2640 local analyzers = fonts.analyzers
2641 local methods = analyzers.methods
2642
2643 local unsetvalue = attributes.unsetvalue
2644
2645 local a_color = attributes.private('color')
2646 local a_colormodel = attributes.private('colormodel')
2647 local m_color = attributes.list[a_color] or { }
2648
2649 local glyph_code = nodes.nodecodes.glyph
2650
2651 local states = analyzers.states
2652
2653 local colornames = {
2654 [states.init] = "font:1",
2655 [states.medi] = "font:2",
2656 [states.fina] = "font:3",
2657 [states.isol] = "font:4",
2658 [states.mark] = "font:5",
2659 [states.rest] = "font:6",
2660 [states.rphf] = "font:1",
2661 [states.half] = "font:2",
2662 [states.pref] = "font:3",
2663 [states.blwf] = "font:4",
2664 [states.pstf] = "font:5",
2665 }
2666
2667
2668
2669
2670 local function markstates(head)
2671 if head then
2672 head = tonut(head)
2673 local model = getattr(head,a_colormodel) or 1
2674 for glyph in nextchar, head do
2675 local a = getstate(glyph)
2676 if a then
2677 local name = colornames[a]
2678 if name then
2679 local color = m_color[name]
2680 if color then
2681 setattr(glyph,a_colormodel,model)
2682 setattr(glyph,a_color,color)
2683 end
2684 end
2685 end
2686 end
2687 end
2688 end
2689
2690 local function analyzeprocessor(head,font,attr)
2691 local tfmdata = fontdata[font]
2692 local script, language = otf.scriptandlanguage(tfmdata,attr)
2693 local action = methods[script]
2694 if not action then
2695 return head, false
2696 end
2697 if type(action) == "function" then
2698 local head, done = action(head,font,attr)
2699 if done and trace_analyzing then
2700 markstates(head)
2701 end
2702 return head, done
2703 end
2704 action = action[language]
2705 if action then
2706 local head, done = action(head,font,attr)
2707 if done and trace_analyzing then
2708 markstates(head)
2709 end
2710 return head, done
2711 else
2712 return head, false
2713 end
2714 end
2715
2716 registerotffeature {
2717 name = "analyze",
2718 processors = {
2719 node = analyzeprocessor,
2720 }
2721 }
2722
2723
2724 function methods.nocolor(head,font,attr)
2725 for n, c, f in nextchar, head do
2726 if not font or f == font then
2727 setattr(n,a_color,unsetvalue)
2728 end
2729 end
2730 return head, true
2731 end
2732
2733end
2734
2735
2736local function purefontname(name)
2737 if type(name) == "number" then
2738 name = getfontname(name)
2739 end
2740 if type(name) == "string" then
2741 return basename(name)
2742 end
2743end
2744
2745implement {
2746 name = "purefontname",
2747 actions = { purefontname, context },
2748 arguments = "string",
2749}
2750
2751local sharedstorage = storage.shared
2752
2753local list = sharedstorage.bodyfontsizes or { }
2754local unknown = sharedstorage.unknownbodyfontsizes or { }
2755
2756sharedstorage.bodyfontsizes = list
2757sharedstorage.unknownbodyfontsizes = unknown
2758
2759implement {
2760 name = "registerbodyfontsize",
2761 arguments = "string",
2762 actions = function(size)
2763 list[size] = true
2764 end
2765}
2766
2767interfaces.implement {
2768 name = "registerunknownbodysize",
2769 arguments = "string",
2770 actions = function(size)
2771 if not unknown[size] then
2772 interfaces.showmessage("fonts",14,size)
2773 end
2774 unknown[size] = true
2775 end,
2776}
2777
2778implement {
2779 name = "getbodyfontsizes",
2780 arguments = "string",
2781 actions = function(separator)
2782 context(concat(sortedkeys(list),separator))
2783 end
2784}
2785
2786implement {
2787 name = "processbodyfontsizes",
2788 arguments = "string",
2789 actions = function(command)
2790 local keys = sortedkeys(list)
2791 if command then
2792 local action = context[command]
2793 for i=1,#keys do
2794 action(keys[i])
2795 end
2796 else
2797 context(concat(keys,","))
2798 end
2799 end
2800}
2801
2802implement {
2803 name = "cleanfontname",
2804 actions = { cleanname, context },
2805 arguments = "string"
2806}
2807
2808implement {
2809 name = "fontlookupinitialize",
2810 actions = names.lookup,
2811 arguments = "string",
2812}
2813
2814implement {
2815 name = "fontlookupnoffound",
2816 actions = { names.noflookups, context },
2817}
2818
2819implement {
2820 name = "fontlookupgetkeyofindex",
2821 actions = { names.getlookupkey, context },
2822 arguments = { "string", "integer"}
2823}
2824
2825implement {
2826 name = "fontlookupgetkey",
2827 actions = { names.getlookupkey, context },
2828 arguments = "string"
2829}
2830
2831
2832
2833function commands.showchardata(n)
2834 local tfmdata = fontdata[currentfont()]
2835 if tfmdata then
2836 if type(n) == "string" then
2837 n = utfbyte(n)
2838 end
2839 local chr = tfmdata.characters[n]
2840 if chr then
2841 report_status("%s @ %s => %U => %c => %s",tfmdata.properties.fullname,tfmdata.parameters.size,n,n,serialize(chr,false))
2842 end
2843 end
2844end
2845
2846function commands.showfontparameters(tfmdata)
2847
2848 local tfmdata = tfmdata or fontdata[currentfont()]
2849 if tfmdata then
2850 local parameters = tfmdata.parameters
2851 local mathparameters = tfmdata.mathparameters
2852 local properties = tfmdata.properties
2853 local hasparameters = parameters and next(parameters)
2854 local hasmathparameters = mathparameters and next(mathparameters)
2855 if hasparameters then
2856 report_status("%s @ %s => text parameters => %s",properties.fullname,parameters.size,serialize(parameters,false))
2857 end
2858 if hasmathparameters then
2859 report_status("%s @ %s => math parameters => %s",properties.fullname,parameters.size,serialize(mathparameters,false))
2860 end
2861 if not hasparameters and not hasmathparameters then
2862 report_status("%s @ %s => no text parameters and/or math parameters",properties.fullname,parameters.size)
2863 end
2864 end
2865end
2866
2867implement {
2868 name = "currentdesignsize",
2869 actions = function()
2870 context(parameters[currentfont()].designsize)
2871 end
2872}
2873
2874implement {
2875 name = "doifelsefontpresent",
2876 actions = { names.exists, commands.doifelse },
2877 arguments = "string"
2878}
2879
2880
2881
2882
2883constructors.privateslots = constructors.privateslots or { }
2884
2885storage.register("fonts/constructors/privateslots", constructors.privateslots, "fonts.constructors.privateslots")
2886
2887do
2888
2889 local privateslots = constructors.privateslots
2890 local lastprivateslot = 0xFD000
2891
2892 constructors.privateslots = setmetatableindex(privateslots,function(t,k)
2893 local v = lastprivateslot
2894 lastprivateslot = lastprivateslot + 1
2895 t[k] = v
2896 return v
2897 end)
2898
2899 implement {
2900 name = "getprivateglyphslot",
2901 actions = function(name) context(privateslots[name]) end,
2902 arguments = "string",
2903 }
2904
2905end
2906
2907
2908
2909function helpers.getcoloredglyphs(tfmdata)
2910 if type(tfmdata) == "number" then
2911 tfmdata = fontdata[tfmdata]
2912 end
2913 if not tfmdata then
2914 tfmdata = fontdata[true]
2915 end
2916 local characters = tfmdata.characters
2917 local descriptions = tfmdata.descriptions
2918 local collected = { }
2919 for unicode, character in next, characters do
2920 local description = descriptions[unicode]
2921 if description and (description.colors or character.svg) then
2922 collected[#collected+1] = unicode
2923 end
2924 end
2925 table.sort(collected)
2926 return collected
2927end
2928
2929
2930
2931statistics.register("body font sizes", function()
2932 if next(unknown) then
2933 return formatters["defined: % t, undefined: % t"](sortedkeys(list),sortedkeys(unknown))
2934 end
2935end)
2936
2937statistics.register("used fonts",function()
2938 if trace_usage then
2939 local filename = file.nameonly(environment.jobname) .. "-fonts-usage.lua"
2940 if next(fontdata) then
2941 local files = { }
2942 local list = { }
2943 for id, tfmdata in sortedhash(fontdata) do
2944 local filename = tfmdata.properties.filename
2945 if filename then
2946 local filedata = files[filename]
2947 if filedata then
2948 filedata.instances = filedata.instances + 1
2949 else
2950 local rawdata = tfmdata.shared and tfmdata.shared.rawdata
2951 local metadata = rawdata and rawdata.metadata
2952 files[filename] = {
2953 instances = 1,
2954 filename = filename,
2955 version = metadata and metadata.version,
2956 size = rawdata and rawdata.size,
2957 }
2958 end
2959 else
2960
2961 end
2962 end
2963 for k, v in sortedhash(files) do
2964 list[#list+1] = v
2965 end
2966 table.save(filename,list)
2967 else
2968 os.remove(filename)
2969 end
2970 end
2971end)
2972
2973
2974
2975do
2976
2977 local settings_to_array = utilities.parsers.settings_to_array
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000 implement {
3001 name = "definefontcolorpalette",
3002 arguments = "2 strings",
3003 actions = function(name,set)
3004 otf.registerpalette(name,settings_to_array(set))
3005 end
3006 }
3007
3008end
3009
3010do
3011
3012 local pattern = C((1-S("* "))^1)
3013
3014 implement {
3015 name = "truefontname",
3016 arguments = "string",
3017 actions = function(s)
3018
3019 context(lpegmatch(pattern,s) or s)
3020 end
3021 }
3022
3023end
3024
3025do
3026
3027 local function getinstancespec(id)
3028 local data = fontdata[id or true]
3029 local shared = data.shared
3030 local resources = shared and shared.rawdata.resources
3031 if resources then
3032 local instancespec = data.properties.instance
3033 if instancespec then
3034 local variabledata = resources.variabledata
3035 if variabledata then
3036 local instances = variabledata.instances
3037 if instances then
3038 for i=1,#instances do
3039 local instance = instances[i]
3040 if cleanname(instance.subfamily)== instancespec then
3041 local values = table.copy(instance.values)
3042 local axis = variabledata.axis
3043 for i=1,#values do
3044 for j=1,#axis do
3045 if values[i].axis == axis[j].tag then
3046 values[i].name = axis[j].name
3047 break
3048 end
3049 end
3050 end
3051 return values
3052 end
3053 end
3054 end
3055 end
3056 end
3057 end
3058 end
3059
3060 helpers.getinstancespec = getinstancespec
3061
3062 implement {
3063 name = "currentfontinstancespec",
3064 actions = function()
3065 local t = getinstancespec()
3066 if t then
3067 for i=1,#t do
3068 if i > 1 then
3069 context.space()
3070 end
3071 local ti = t[i]
3072 context("%s=%s",ti.name,ti.value)
3073 end
3074 end
3075 end
3076 }
3077
3078end
3079
3080
3081
3082do
3083
3084 local identical = table.identical
3085 local copy = table.copy
3086 local fontdata = fonts.hashes.identifiers
3087 local addcharacters = font.addcharacters
3088
3089
3090
3091
3092
3093 local trace_adding = false
3094 local report_adding = logs.reporter("fonts","add characters")
3095
3096 trackers.register("fonts.addcharacters",function(v) trace_adding = v end)
3097
3098 if addcharacters then
3099
3100 function fonts.constructors.addcharacters(id,list)
3101 local newchar = list.characters
3102 if newchar then
3103 local data = fontdata[id]
3104 local newfont = list.fonts
3105 local oldchar = data.characters
3106 local oldfont = data.fonts
3107 addcharacters(id, {
3108 characters = newchar,
3109 fonts = newfont,
3110 nomath = not data.properties.hasmath,
3111 })
3112
3113
3114 if newfont then
3115 if oldfont then
3116 local oldn = #oldfont
3117 local newn = #newfont
3118 for n=1,newn do
3119 local ok = false
3120 local nf = newfont[n]
3121 for o=1,oldn do
3122 if identical(nf,oldfont[o]) then
3123 ok = true
3124 break
3125 end
3126 end
3127 if not ok then
3128 oldn = oldn + 1
3129 oldfont[oldn] = newfont[i]
3130 end
3131 end
3132 else
3133 data.fonts = newfont
3134 end
3135 end
3136
3137
3138 for u, c in next, newchar do
3139 if trace_adding then
3140 report_adding("adding character %U to font %!font:name!",u,id)
3141 end
3142 oldchar[u] = c
3143 end
3144 end
3145 end
3146
3147 else
3148 function fonts.constructors.addcharacters(id,list)
3149 report_adding("adding characters to %!font:name! is not yet supported",id)
3150 end
3151 end
3152
3153 implement {
3154 name = "addfontpath",
3155 arguments = "string",
3156 actions = function(list)
3157 names.addruntimepath(settings_to_array(list))
3158 end
3159 }
3160
3161end
3162
3163
3164
3165do
3166
3167 local getfontoffamily = font.getfontoffamily
3168 local new_glyph = nodes.pool.glyph
3169 local fontproperties = fonts.hashes.properties
3170
3171 local function getprivateslot(id,name)
3172 if not name then
3173 name = id
3174 id = currentfont()
3175 end
3176 local properties = fontproperties[id]
3177 local privates = properties and properties.privates
3178 return privates and privates[name]
3179 end
3180
3181 local function getprivatenode(tfmdata,name)
3182 if type(tfmdata) == "number" then
3183 tfmdata = fontdata[tfmdata]
3184 end
3185 local properties = tfmdata.properties
3186 local font = properties.id
3187 local slot = getprivateslot(font,name)
3188 if slot then
3189
3190 local char = tfmdata.characters[slot]
3191 local tonode = char.tonode
3192 if tonode then
3193 return tonode(font,char)
3194 else
3195 return new_glyph(font,slot)
3196 end
3197 end
3198 end
3199
3200 local function getprivatecharornode(tfmdata,name)
3201 if type(tfmdata) == "number" then
3202 tfmdata = fontdata[tfmdata]
3203 end
3204 local properties = tfmdata.properties
3205 local font = properties.id
3206 local slot = getprivateslot(font,name)
3207 if slot then
3208
3209 local char = tfmdata.characters[slot]
3210 local tonode = char.tonode
3211 if tonode then
3212 return "node", tonode(tfmdata,char)
3213 else
3214 return "char", slot
3215 end
3216 end
3217 end
3218
3219 helpers.getprivateslot = getprivateslot
3220 helpers.getprivatenode = getprivatenode
3221 helpers.getprivatecharornode = getprivatecharornode
3222
3223 implement {
3224 name = "getprivatechar",
3225 arguments = "string",
3226 actions = function(name)
3227 local p = getprivateslot(name)
3228 if p then
3229 context(utfchar(p))
3230 end
3231 end
3232 }
3233
3234 implement {
3235 name = "getprivatemathchar",
3236 arguments = "string",
3237 actions = function(name)
3238 local p = getprivateslot(getfontoffamily(0),name)
3239 if p then
3240 context(utfchar(p))
3241 end
3242 end
3243 }
3244
3245 implement {
3246 name = "getprivateslot",
3247 arguments = "string",
3248 actions = function(name)
3249 local p = getprivateslot(name)
3250 if p then
3251 context(p)
3252 end
3253 end
3254 }
3255
3256end
3257
3258
3259
3260function fonts.helpers.collectanchors(tfmdata)
3261
3262 local resources = tfmdata.resources
3263
3264 if not resources or resources.anchors then
3265 return resources.anchors
3266 end
3267
3268 local anchors = { }
3269
3270 local function set(unicode,target,class,anchor)
3271 local a = anchors[unicode]
3272 if not a then
3273 anchors[unicode] = { [target] = { anchor } }
3274 return
3275 end
3276 local t = a[target]
3277 if not t then
3278 a[target] = { anchor }
3279 return
3280 end
3281 local x = anchor[1]
3282 local y = anchor[2]
3283 for k, v in next, t do
3284 if v[1] == x and v[2] == y then
3285 return
3286 end
3287 end
3288 t[#t+1] = anchor
3289 end
3290
3291 local function getanchors(steps,target)
3292 for i=1,#steps do
3293 local step = steps[i]
3294 local coverage = step.coverage
3295 for unicode, data in next, coverage do
3296 local class = data[1]
3297 local anchor = data[2]
3298 if anchor[1] ~= 0 or anchor[2] ~= 0 then
3299 set(unicode,target,class,anchor)
3300 end
3301 end
3302 end
3303 end
3304
3305 local function getcursives(steps)
3306 for i=1,#steps do
3307 local step = steps[i]
3308 local coverage = step.coverage
3309 for unicode, data in next, coverage do
3310 local class = data[1]
3311 local en = data[2]
3312 local ex = data[3]
3313 if en then
3314 set(unicode,"entry",class,en)
3315 end
3316 if ex then
3317 set(unicode,"exit", class,ex)
3318 end
3319 end
3320 end
3321 end
3322
3323 local function collect(list)
3324 if list then
3325 for i=1,#list do
3326 local entry = list[i]
3327 local steps = entry.steps
3328 local kind = entry.type
3329 if kind == "gpos_mark2mark" then
3330 getanchors(steps,"mark")
3331 elseif kind == "gpos_mark2base" then
3332 getanchors(steps,"base")
3333 elseif kind == "gpos_mark2ligature" then
3334 getanchors(steps,"ligature")
3335 elseif kind == "gpos_cursive" then
3336 getcursives(steps)
3337 end
3338 end
3339 end
3340 end
3341
3342 collect(resources.sequences)
3343 collect(resources.sublookups)
3344
3345 local function sorter(a,b)
3346 if a[1] == b[1] then
3347 return a[2] < b[2]
3348 else
3349 return a[1] < b[1]
3350 end
3351 end
3352
3353 for unicode, old in next, anchors do
3354 for target, list in next, old do
3355 sort(list,sorter)
3356 end
3357 end
3358
3359 resources.anchors = anchors
3360
3361 return anchors
3362
3363end
3364 |