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