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