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