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