1if not modules then modules = { } end modules ['font-syn'] = {
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
17
18
19local next, tonumber, type, tostring = next, tonumber, type, tostring
20local sub, gsub, match, find, lower, upper = string.sub, string.gsub, string.match, string.find, string.lower, string.upper
21local concat, sort, fastcopy, tohash = table.concat, table.sort, table.fastcopy, table.tohash
22local serialize, sortedhash = table.serialize, table.sortedhash
23local lpegmatch = lpeg.match
24local unpack = unpack or table.unpack
25local formatters, topattern = string.formatters, string.topattern
26local round = math.round
27local P, R, S, C, Cc, Ct, Cs = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cs
28local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
29local isfile, modificationtime = lfs.isfile, lfs.modification
30
31local allocate = utilities.storage.allocate
32local sparse = utilities.storage.sparse
33local setmetatableindex = table.setmetatableindex
34
35local removesuffix = file.removesuffix
36local splitbase = file.splitbase
37local splitname = file.splitname
38local basename = file.basename
39local nameonly = file.nameonly
40local pathpart = file.pathpart
41local suffixonly = file.suffix
42local filejoin = file.join
43local is_qualified_path = file.is_qualified_path
44local exists = io.exists
45
46local findfile = resolvers.findfile
47local cleanpath = resolvers.cleanpath
48local resolveprefix = resolvers.resolve
49
50local settings_to_hash = utilities.parsers.settings_to_hash_tolerant
51
52local trace_names = false trackers.register("fonts.names", function(v) trace_names = v end)
53local trace_warnings = false trackers.register("fonts.warnings", function(v) trace_warnings = v end)
54local trace_specifications = false trackers.register("fonts.specifications", function(v) trace_specifications = v end)
55local trace_rejections = false trackers.register("fonts.rejections", function(v) trace_rejections = v end)
56
57local report_names = logs.reporter("fonts","names")
58
59
63
64fonts = fonts or { }
65
66local names = fonts.names or allocate { }
67fonts.names = names
68
69local filters = names.filters or { }
70names.filters = filters
71
72local treatments = fonts.treatments or { }
73fonts.treatments = treatments
74
75names.data = names.data or allocate { }
76
77names.version = 1.131
78names.basename = "names"
79names.saved = false
80names.loaded = false
81names.be_clever = true
82names.enabled = true
83names.cache = containers.define("fonts","data",names.version,true)
84
85local usesystemfonts = true
86local autoreload = true
87
88directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) end)
89directives.register("fonts.usesystemfonts", function(v) usesystemfonts = toboolean(v) end)
90
91
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123local weights = Cs (
124 P("demibold")
125 + P("semibold")
126 + P("mediumbold")
127 + P("ultrabold")
128 + P("extrabold")
129 + P("ultralight")
130 + P("extralight")
131 + P("bold")
132 + P("demi")
133 + P("semi")
134 + P("light")
135 + P("medium")
136 + P("heavy")
137 + P("ultra")
138 + P("black")
139
140 + P("bol")
141 + P("regular") / "normal"
142)
143
144
145
146
147
148
149
150
151
152
153
154
155
156local normalized_weights = sparse {
157 regular = "normal",
158}
159
160local styles = Cs (
161 P("reverseoblique") / "reverseitalic"
162 + P("regular") / "normal"
163 + P("italic")
164 + P("oblique") / "italic"
165 + P("slanted")
166 + P("roman") / "normal"
167 + P("ital") / "italic"
168 + P("ita") / "italic"
169
170)
171
172local normalized_styles = sparse {
173 reverseoblique = "reverseitalic",
174 regular = "normal",
175 oblique = "italic",
176}
177
178local widths = Cs(
179 P("condensed")
180 + P("thin")
181 + P("expanded")
182 + P("cond") / "condensed"
183
184 + P("normal")
185 + P("book") / "normal"
186)
187
188local normalized_widths = sparse()
189
190local variants = Cs(
191 P("smallcaps")
192 + P("oldstyle")
193 + P("caps") / "smallcaps"
194)
195
196local normalized_variants = sparse()
197
198names.knownweights = {
199 "black",
200 "bold",
201 "demi",
202 "demibold",
203 "extrabold",
204 "heavy",
205 "light",
206 "medium",
207 "mediumbold",
208 "normal",
209 "regular",
210 "semi",
211 "semibold",
212 "ultra",
213 "ultrabold",
214 "ultralight",
215}
216
217names.knownstyles = {
218 "italic",
219 "normal",
220 "oblique",
221 "regular",
222 "reverseitalic",
223 "reverseoblique",
224 "roman",
225 "slanted",
226}
227
228names.knownwidths = {
229 "book",
230 "condensed",
231 "expanded",
232 "normal",
233 "thin",
234}
235
236names.knownvariants = {
237 "normal",
238 "oldstyle",
239 "smallcaps",
240}
241
242local remappedweights = {
243 [""] = "normal",
244 ["bol"] = "bold",
245}
246
247local remappedstyles = {
248 [""] = "normal",
249}
250
251local remappedwidths = {
252 [""] = "normal",
253}
254
255local remappedvariants = {
256 [""] = "normal",
257}
258
259names.remappedweights = remappedweights setmetatableindex(remappedweights ,"self")
260names.remappedstyles = remappedstyles setmetatableindex(remappedstyles ,"self")
261names.remappedwidths = remappedwidths setmetatableindex(remappedwidths ,"self")
262names.remappedvariants = remappedvariants setmetatableindex(remappedvariants,"self")
263
264local any = P(1)
265
266local analyzed_table
267
268local analyzer = Cs (
269 (
270 weights / function(s) analyzed_table[1] = s return "" end
271 + styles / function(s) analyzed_table[2] = s return "" end
272 + widths / function(s) analyzed_table[3] = s return "" end
273 + variants / function(s) analyzed_table[4] = s return "" end
274 + any
275 )^0
276)
277
278local splitter = lpeg.splitat("-")
279
280function names.splitspec(askedname)
281 local name, weight, style, width, variant = lpegmatch(splitter,askedname)
282 weight = weight and lpegmatch(weights, weight) or weight
283 style = style and lpegmatch(styles, style) or style
284 width = width and lpegmatch(widths, width) or width
285 variant = variant and lpegmatch(variants,variant) or variant
286 if trace_names then
287 report_names("requested name %a split in name %a, weight %a, style %a, width %a and variant %a",
288 askedname,name,weight,style,width,variant)
289 end
290 if not weight or not weight or not width or not variant then
291 weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal"
292 if trace_names then
293 report_names("request %a normalized to '%s-%s-%s-%s-%s'",
294 askedname,name,weight,style,width,variant)
295 end
296 end
297 return name or askedname, weight, style, width, variant
298end
299
300local function analyzespec(somename)
301 if somename then
302 analyzed_table = { }
303 local name = lpegmatch(analyzer,somename)
304 return name, analyzed_table[1], analyzed_table[2], analyzed_table[3], analyzed_table[4]
305 end
306end
307
308
312
313filters.afm = fonts.handlers.afm.readers.getinfo
314filters.otf = fonts.handlers.otf.readers.getinfo
315filters.ttf = filters.otf
316filters.ttc = filters.otf
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
420
421filters.list = {
422 "otf", "ttf", "ttc", "afm",
423}
424
425
426
427names.fontconfigfile = "fonts.conf"
428names.osfontdirvariable = "OSFONTDIR"
429names.extrafontsvariable = "EXTRAFONTS"
430names.runtimefontsvariable = "RUNTIMEFONTS"
431
432filters.paths = { }
433filters.names = { }
434
435function names.getpaths(trace)
436 local hash, result, r = { }, { }, 0
437 local function collect(t,where)
438 for i=1,#t do
439 local v = cleanpath(t[i])
440 v = gsub(v,"/+$","")
441 local key = lower(v)
442 report_names("variable %a specifies path %a",where,v)
443 if not hash[key] then
444 r = r + 1
445 result[r] = v
446 hash[key] = true
447 end
448 end
449 end
450 local path = names.osfontdirvariable or ""
451 if path ~= "" then
452 collect(resolvers.expandedpathlist(path),path)
453 end
454 local path = names.extrafontsvariable or ""
455 if path ~= "" then
456 collect(resolvers.expandedpathlist(path),path)
457 end
458 if xml then
459 local confname = resolvers.expansion("FONTCONFIG_FILE") or ""
460 if confname == "" then
461 confname = names.fontconfigfile or ""
462 end
463 if confname ~= "" then
464
465 local name = findfile(confname,"fontconfig files") or ""
466 if name == "" then
467
468 name = filejoin("/etc",confname)
469 if not isfile(name) then
470 name = ""
471 end
472 end
473 if name ~= "" and isfile(name) then
474 if trace_names then
475 report_names("%s fontconfig file %a","loading",name)
476 end
477 local xmldata = xml.load(name)
478
479 xml.include(xmldata,"include","",true,function(incname)
480 if not is_qualified_path(incname) then
481 local path = pathpart(name)
482 if path ~= "" then
483 incname = filejoin(path,incname)
484 end
485 end
486 if isfile(incname) then
487 if trace_names then
488 report_names("%s fontconfig file %a","merging included",incname)
489 end
490 return io.loaddata(incname)
491 elseif trace_names then
492 report_names("%s fontconfig file: %a","ignoring included",incname)
493 end
494 end)
495
496 local fontdirs = xml.collect_texts(xmldata,"dir",true)
497 if trace_names then
498 report_names("%s dirs found in fontconfig",#fontdirs)
499 end
500 collect(fontdirs,"fontconfig file")
501 end
502 end
503 end
504 sort(result)
505 function names.getpaths()
506 return result
507 end
508 return result
509end
510
511local function cleanname(name)
512 return (gsub(lower(name),"[^%a%d]",""))
513end
514
515local function cleanfilename(fullname,defaultsuffix)
516 if fullname then
517 local path, name, suffix = splitname(fullname)
518 if name then
519 name = gsub(lower(name),"[^%a%d]","")
520 if suffix and suffix ~= "" then
521 return name .. ".".. suffix
522 elseif defaultsuffix and defaultsuffix ~= "" then
523 return name .. ".".. defaultsuffix
524 else
525 return name
526 end
527 end
528 end
529 return "badfontname"
530end
531
532local sorter = function(a,b)
533 return a > b
534end
535
536
537
538names.cleanname = cleanname
539names.cleanfilename = cleanfilename
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555local function check_name(data,result,filename,modification,suffix,subfont)
556
557 local specifications = data.specifications
558
559 local fullname = result.fullname
560 local fontname = result.fontname
561 local family = result.family
562 local subfamily = result.subfamily
563 local familyname = result.familyname
564 local subfamilyname = result.subfamilyname
565
566
567 local weight = result.weight
568 local width = result.width
569 local italicangle = tonumber(result.italicangle)
570 local subfont = subfont
571 local rawname = fullname or fontname or familyname
572 local filebase = removesuffix(basename(filename))
573 local cleanfilename = cleanname(filebase)
574
575 fullname = fullname and cleanname(fullname)
576 fontname = fontname and cleanname(fontname)
577 family = family and cleanname(family)
578 subfamily = subfamily and cleanname(subfamily)
579 familyname = familyname and cleanname(familyname)
580 subfamilyname = subfamilyname and cleanname(subfamilyname)
581
582
583 weight = weight and cleanname(weight)
584 width = width and cleanname(width)
585 italicangle = italicangle == 0 and nil
586
587 local a_name, a_weight, a_style, a_width, a_variant = analyzespec(fullname or fontname or familyname)
588
589 local width = width or a_width
590 local variant = a_variant
591 local style = subfamilyname or subfamily
592 if style then
593 style = gsub(style,"[^%a]","")
594 elseif italicangle then
595 style = "italic"
596 end
597 if not variant or variant == "" then
598 variant = "normal"
599 end
600 if not weight or weight == "" then
601 weight = a_weight
602 end
603 if not style or style == "" then
604 style = a_style
605 end
606 if not familyname then
607 familyname = a_name
608 end
609 fontname = fontname or fullname or familyname or filebase
610 fullname = fullname or fontname
611 familyname = familyname or fontname
612
613 local units = result.units or 1000
614 local designsize = result.designsize or 0
615 local minsize = result.minsize or 0
616 local maxsize = result.maxsize or 0
617 local angle = result.italicangle or 0
618 local pfmwidth = result.pfmwidth or 0
619 local pfmweight = result.pfmweight or 0
620
621 local instancenames = result.instancenames
622
623 specifications[#specifications+1] = {
624 filename = filename,
625 cleanfilename = cleanfilename,
626
627 format = lower(suffix),
628 subfont = subfont,
629 rawname = rawname,
630 fullname = fullname,
631 fontname = fontname,
632 family = family,
633 subfamily = subfamily,
634 familyname = familyname,
635 subfamilyname = subfamilyname,
636
637
638 weight = weight,
639 style = style,
640 width = width,
641 variant = variant,
642 units = units ~= 1000 and units or nil,
643 pfmwidth = pfmwidth ~= 0 and pfmwidth or nil,
644 pfmweight = pfmweight ~= 0 and pfmweight or nil,
645 angle = angle ~= 0 and angle or nil,
646 minsize = minsize ~= 0 and minsize or nil,
647 maxsize = maxsize ~= 0 and maxsize or nil,
648 designsize = designsize ~= 0 and designsize or nil,
649 modification = modification ~= 0 and modification or nil,
650 instancenames = instancenames or nil,
651 }
652end
653
654local function cleanupkeywords()
655 local data = names.data
656 local specifications = names.data.specifications
657 if specifications then
658 local weights = { }
659 local styles = { }
660 local widths = { }
661 local variants = { }
662 for i=1,#specifications do
663 local s = specifications[i]
664
665 local _, b_weight, b_style, b_width, b_variant = analyzespec(s.weight)
666 local _, c_weight, c_style, c_width, c_variant = analyzespec(s.style)
667 local _, d_weight, d_style, d_width, d_variant = analyzespec(s.width)
668 local _, e_weight, e_style, e_width, e_variant = analyzespec(s.variant)
669 local _, f_weight, f_style, f_width, f_variant = analyzespec(s.fullname or "")
670 local weight = b_weight or c_weight or d_weight or e_weight or f_weight or "normal"
671 local style = b_style or c_style or d_style or e_style or f_style or "normal"
672 local width = b_width or c_width or d_width or e_width or f_width or "normal"
673 local variant = b_variant or c_variant or d_variant or e_variant or f_variant or "normal"
674 weight = remappedweights [weight or ""]
675 style = remappedstyles [style or ""]
676 width = remappedwidths [width or ""]
677 variant = remappedvariants[variant or ""]
678 weights [weight ] = (weights [weight ] or 0) + 1
679 styles [style ] = (styles [style ] or 0) + 1
680 widths [width ] = (widths [width ] or 0) + 1
681 variants[variant] = (variants[variant] or 0) + 1
682 if weight ~= s.weight then
683 s.fontweight = s.weight
684 end
685 s.weight, s.style, s.width, s.variant = weight, style, width, variant
686 end
687 local statistics = data.statistics
688 statistics.used_weights = weights
689 statistics.used_styles = styles
690 statistics.used_widths = widths
691 statistics.used_variants = variants
692 end
693end
694
695local function collectstatistics(runtime)
696 local data = names.data
697 local specifications = data.specifications
698 local statistics = data.statistics
699 if specifications then
700 local f_w = formatters["%i"]
701 local f_a = formatters["%0.2f"]
702
703 local weights = { }
704 local styles = { }
705 local widths = { }
706 local variants = { }
707
708 local angles = { }
709
710 local pfmweights = { } setmetatableindex(pfmweights,"table")
711 local pfmwidths = { } setmetatableindex(pfmwidths, "table")
712
713 for i=1,#specifications do
714 local s = specifications[i]
715
716 local weight = s.weight
717 local style = s.style
718 local width = s.width
719 local variant = s.variant
720 if weight then weights [weight ] = (weights [weight ] or 0) + 1 end
721 if style then styles [style ] = (styles [style ] or 0) + 1 end
722 if width then widths [width ] = (widths [width ] or 0) + 1 end
723 if variant then variants[variant] = (variants[variant] or 0) + 1 end
724
725 local angle = f_a(tonumber(s.angle) or 0)
726 angles[angle] = (angles[angles] or 0) + 1
727
728 local pfmweight = f_w(s.pfmweight or 0)
729 local pfmwidth = f_w(s.pfmwidth or 0)
730 local tweights = pfmweights[pfmweight]
731 local twidths = pfmwidths [pfmwidth]
732 tweights[pfmweight] = (tweights[pfmweight] or 0) + 1
733 twidths[pfmwidth] = (twidths [pfmwidth] or 0) + 1
734 end
735
736 statistics.weights = weights
737 statistics.styles = styles
738 statistics.widths = widths
739 statistics.variants = variants
740 statistics.angles = angles
741 statistics.pfmweights = pfmweights
742 statistics.pfmwidths = pfmwidths
743 statistics.fonts = #specifications
744
745 setmetatableindex(pfmweights,nil)
746 setmetatableindex(pfmwidths, nil)
747
748 report_names("")
749 report_names("statistics: ")
750 report_names("")
751 report_names("weights")
752 report_names("")
753 report_names(formatters[" %T"](weights))
754 report_names("")
755 report_names("styles")
756 report_names("")
757 report_names(formatters[" %T"](styles))
758 report_names("")
759 report_names("widths")
760 report_names("")
761 report_names(formatters[" %T"](widths))
762 report_names("")
763 report_names("variants")
764 report_names("")
765 report_names(formatters[" %T"](variants))
766 report_names("")
767 report_names("angles")
768 report_names("")
769 report_names(formatters[" %T"](angles))
770 report_names("")
771 report_names("pfmweights")
772 report_names("")
773 for k, v in sortedhash(pfmweights) do
774 report_names(formatters[" %-10s: %T"](k,v))
775 end
776 report_names("")
777 report_names("pfmwidths")
778 report_names("")
779 for k, v in sortedhash(pfmwidths) do
780 report_names(formatters[" %-10s: %T"](k,v))
781 end
782 report_names("")
783 report_names("registered fonts : %i", statistics.fonts)
784 report_names("read files : %i", statistics.readfiles)
785 report_names("skipped files : %i", statistics.skippedfiles)
786 report_names("duplicate files : %i", statistics.duplicatefiles)
787 if runtime then
788 report_names("total scan time : %0.3f seconds",runtime)
789 end
790 end
791end
792
793local function collecthashes()
794 local data = names.data
795 local mappings = data.mappings
796 local fallbacks = data.fallbacks
797 local specifications = data.specifications
798 local nofmappings = 0
799 local noffallbacks = 0
800 if specifications then
801
802 local conflicts = setmetatableindex("table")
803 for index=1,#specifications do
804 local specification = specifications[index]
805 local format = specification.format
806 local fullname = specification.fullname
807 local fontname = specification.fontname
808
809
810
811 local familyname = specification.familyname or specification.family
812 local subfamilyname = specification.subfamilyname
813 local subfamily = specification.subfamily
814 local weight = specification.weight
815 local mapping = mappings[format]
816 local fallback = fallbacks[format]
817 local instancenames = specification.instancenames
818 if fullname and not mapping[fullname] then
819 mapping[fullname] = index
820 nofmappings = nofmappings + 1
821 end
822 if fontname and not mapping[fontname] then
823 mapping[fontname] = index
824 nofmappings = nofmappings + 1
825 end
826 if instancenames then
827 for i=1,#instancenames do
828 local instance = fullname .. instancenames[i]
829 mapping[instance] = index
830 nofmappings = nofmappings + 1
831 end
832 end
833
834
835
836
837
838
839
840
841 if familyname then
842 if weight and weight ~= sub(familyname,#familyname-#weight+1,#familyname) then
843 local madename = familyname .. weight
844 if not mapping[madename] and not fallback[madename] then
845 fallback[madename] = index
846 noffallbacks = noffallbacks + 1
847 end
848 end
849 if subfamily and subfamily ~= sub(familyname,#familyname-#subfamily+1,#familyname) then
850 local extraname = familyname .. subfamily
851 if not mapping[extraname] and not fallback[extraname] then
852 fallback[extraname] = index
853 noffallbacks = noffallbacks + 1
854 end
855 end
856 if subfamilyname and subfamilyname ~= sub(familyname,#familyname-#subfamilyname+1,#familyname) then
857 local extraname = familyname .. subfamilyname
858 if not mapping[extraname] and not fallback[extraname] then
859 fallback[extraname] = index
860 noffallbacks = noffallbacks + 1
861 end
862 end
863
864 if not mapping[familyname] and not fallback[familyname] then
865 fallback[familyname] = index
866 noffallbacks = noffallbacks + 1
867 end
868 local conflict = conflicts[format]
869 conflict[familyname] = (conflict[familyname] or 0) + 1
870 end
871 end
872 for format, conflict in next, conflicts do
873 local fallback = fallbacks[format]
874 for familyname, n in next, conflict do
875 if n > 1 then
876 fallback[familyname] = nil
877 noffallbacks = noffallbacks - n
878 end
879 end
880 end
881 end
882 return nofmappings, noffallbacks
883end
884
885local function collectfamilies()
886 local data = names.data
887 local specifications = data.specifications
888 local families = data.families
889 for index=1,#specifications do
890 local familyname = specifications[index].familyname
891 local family = families[familyname]
892 if not family then
893 families[familyname] = { index }
894 else
895 family[#family+1] = index
896 end
897 end
898end
899
900local function checkduplicate(where)
901 local data = names.data
902 local mapping = data[where]
903 local specifications = data.specifications
904 local loaded = { }
905 if specifications and mapping then
906
907 local order = filters.list
908 for i=1,#order do
909 local m = mapping[order[i]]
910 for k, v in sortedhash(m) do
911 local s = specifications[v]
912 local hash = formatters["%s-%s-%s-%s-%s"](s.familyname,s.weight or "*",s.style or "*",s.width or "*",s.variant or "*")
913 local h = loaded[hash]
914 if h then
915 local ok = true
916 local fn = s.filename
917 for i=1,#h do
918 if h[i] == fn then
919 ok = false
920 break
921 end
922 end
923 if ok then
924 h[#h+1] = fn
925 end
926 else
927 loaded[hash] = { s.filename }
928 end
929 end
930 end
931 end
932 local n = 0
933 for k, v in sortedhash(loaded) do
934 local nv = #v
935 if nv > 1 then
936 if trace_warnings then
937 report_names("lookup %a clashes with %a",k,v)
938 end
939 n = n + nv
940 end
941 end
942 report_names("%a double lookups in %a",n,where)
943end
944
945local function checkduplicates()
946 checkduplicate("mappings")
947 checkduplicate("fallbacks")
948end
949
950local function sorthashes()
951 local data = names.data
952 local list = filters.list
953 local mappings = data.mappings
954 local fallbacks = data.fallbacks
955 local sorted_mappings = { }
956 local sorted_fallbacks = { }
957 data.sorted_mappings = sorted_mappings
958 data.sorted_fallbacks = sorted_fallbacks
959 for i=1,#list do
960 local l = list[i]
961 sorted_mappings [l] = table.keys(mappings[l])
962 sorted_fallbacks[l] = table.keys(fallbacks[l])
963 sort(sorted_mappings [l],sorter)
964 sort(sorted_fallbacks[l],sorter)
965 end
966 local sorted_families = table.keys(data.families)
967 data.sorted_families = sorted_families
968 sort(sorted_families,sorter)
969end
970
971local function unpackreferences()
972 local data = names.data
973 local specifications = data.specifications
974 if specifications then
975 for k, v in sortedhash(data.families) do
976 for i=1,#v do
977 v[i] = specifications[v[i]]
978 end
979 end
980 local mappings = data.mappings
981 if mappings then
982 for _, m in sortedhash(mappings) do
983 for k, v in sortedhash(m) do
984 m[k] = specifications[v]
985 end
986 end
987 end
988 local fallbacks = data.fallbacks
989 if fallbacks then
990 for _, f in sortedhash(fallbacks) do
991 for k, v in sortedhash(f) do
992 f[k] = specifications[v]
993 end
994 end
995 end
996 end
997end
998
999local function analyzefiles(olddata)
1000
1001 if not trace_warnings then
1002 report_names("warnings are disabled (tracker 'fonts.warnings')")
1003 end
1004
1005 local data = names.data
1006 local done = { }
1007 local totalnofread = 0
1008 local totalnofskipped = 0
1009 local totalnofduplicates = 0
1010 local nofread = 0
1011 local nofskipped = 0
1012 local nofduplicates = 0
1013 local skip_paths = filters.paths
1014 local skip_names = filters.names
1015 local specifications = data.specifications
1016 local oldindices = olddata and olddata.indices or { }
1017 local oldspecifications = olddata and olddata.specifications or { }
1018 local oldrejected = olddata and olddata.rejected or { }
1019 local treatmentdata = treatments.data or { }
1020
1021
1022 local function walk_tree(pathlist,suffix,identify)
1023 if pathlist then
1024 for i=1,#pathlist do
1025 local path = pathlist[i]
1026 path = cleanpath(path .. "/")
1027 path = gsub(path,"/+","/")
1028 local pattern = path .. "**." .. suffix
1029 report_names("globbing path %a",pattern)
1030 local t = dir.glob(pattern)
1031 sort(t,sorter)
1032 for j=1,#t do
1033 local completename = t[j]
1034 identify(completename,basename(completename),suffix,completename)
1035 end
1036
1037 end
1038 end
1039 end
1040
1041 local function identify(completename,name,suffix,storedname)
1042 local pathpart, basepart = splitbase(completename)
1043 nofread = nofread + 1
1044 local treatment = treatmentdata[completename] or treatmentdata[basepart]
1045 if treatment and treatment.ignored then
1046 if trace_names or trace_rejections then
1047 report_names("%s font %a is ignored, reason %a",suffix,completename,treatment.comment or "unknown")
1048 end
1049 nofskipped = nofskipped + 1
1050 elseif done[name] then
1051 if lower(completename) ~= lower(done[name]) then
1052
1053 if trace_names or trace_rejections then
1054 report_names("%s font %a already done as %a",suffix,completename,done[name])
1055 end
1056 nofduplicates = nofduplicates + 1
1057 nofskipped = nofskipped + 1
1058 end
1059 elseif not exists(completename) then
1060
1061 if trace_names or trace_rejections then
1062 report_names("%s font %a does not really exist",suffix,completename)
1063 end
1064 nofskipped = nofskipped + 1
1065 elseif not is_qualified_path(completename) and findfile(completename,suffix) == "" then
1066
1067 if trace_names or trace_rejections then
1068 report_names("%s font %a cannot be found by backend",suffix,completename)
1069 end
1070 nofskipped = nofskipped + 1
1071 else
1072 if #skip_paths > 0 then
1073 for i=1,#skip_paths do
1074 if find(pathpart,skip_paths[i]) then
1075 if trace_names or trace_rejections then
1076 report_names("rejecting path of %s font %a",suffix,completename)
1077 end
1078 nofskipped = nofskipped + 1
1079 return
1080 end
1081 end
1082 end
1083 if #skip_names > 0 then
1084 for i=1,#skip_paths do
1085 if find(basepart,skip_names[i]) then
1086 done[name] = true
1087 if trace_names or trace_rejections then
1088 report_names("rejecting name of %s font %a",suffix,completename)
1089 end
1090 nofskipped = nofskipped + 1
1091 return
1092 end
1093 end
1094 end
1095 if trace_names then
1096 report_names("identifying %s font %a",suffix,completename)
1097 end
1098
1099 local result = nil
1100 local modification = modificationtime(completename)
1101 if olddata and modification and modification > 0 then
1102 local oldindex = oldindices[storedname]
1103 if oldindex then
1104 local oldspecification = oldspecifications[oldindex]
1105 if oldspecification and oldspecification.filename == storedname then
1106 local oldmodification = oldspecification.modification
1107 if oldmodification == modification then
1108 result = oldspecification
1109 specifications[#specifications + 1] = result
1110 else
1111
1112 end
1113 else
1114
1115 end
1116 elseif oldrejected[storedname] == modification then
1117 result = false
1118 end
1119 end
1120 if result == nil then
1121 local lsuffix = lower(suffix)
1122 local result, message = filters[lsuffix](completename)
1123 if result then
1124 if #result > 0 then
1125 for r=1,#result do
1126 check_name(data,result[r],storedname,modification,suffix,r)
1127 end
1128 else
1129 check_name(data,result,storedname,modification,suffix)
1130 end
1131 if trace_warnings and message and message ~= "" then
1132 report_names("warning when identifying %s font %a, %s",suffix,completename,message)
1133 end
1134 elseif trace_warnings then
1135 nofskipped = nofskipped + 1
1136 report_names("error when identifying %s font %a, %s",suffix,completename,message or "unknown")
1137 end
1138 end
1139 done[name] = completename
1140 end
1141 logs.flush()
1142 end
1143
1144 local function traverse(what, method)
1145 local list = filters.list
1146 for n=1,#list do
1147 local suffix = list[n]
1148 local t = os.gettimeofday()
1149 nofread, nofskipped, nofduplicates = 0, 0, 0
1150 suffix = lower(suffix)
1151 report_names("identifying %s font files with suffix %a",what,suffix)
1152 method(suffix)
1153 suffix = upper(suffix)
1154 report_names("identifying %s font files with suffix %a",what,suffix)
1155 method(suffix)
1156 totalnofread, totalnofskipped, totalnofduplicates = totalnofread + nofread, totalnofskipped + nofskipped, totalnofduplicates + nofduplicates
1157 local elapsed = os.gettimeofday() - t
1158 report_names("%s %s files identified, %s skipped, %s duplicates, %s hash entries added, runtime %0.3f seconds",nofread,what,nofskipped,nofduplicates,nofread-nofskipped,elapsed)
1159 end
1160 logs.flush()
1161 end
1162
1163
1164
1165 local function withtree(suffix)
1166 resolvers.dowithfilesintree(".*%." .. suffix .. "$", function(method,root,path,name)
1167 if method == "file" or method == "tree" then
1168 local completename = root .."/" .. path .. "/" .. name
1169 completename = resolveprefix(completename)
1170 identify(completename,name,suffix,name)
1171 return true
1172 end
1173 end, function(blobtype,blobpath,pattern)
1174 blobpath = resolveprefix(blobpath)
1175 report_names("scanning path %a for %s files",blobpath,suffix)
1176 end, function(blobtype,blobpath,pattern,total,checked,done)
1177 blobpath = resolveprefix(blobpath)
1178 report_names("%s %s files checked, %s okay",checked,suffix,done)
1179 end)
1180 end
1181
1182 local function withlsr(suffix)
1183
1184
1185 local pathlist = resolvers.splitpath(resolvers.showpath("ls-R") or "")
1186 walk_tree(pathlist,suffix,identify)
1187 end
1188
1189 local function withsystem(suffix)
1190 walk_tree(names.getpaths(trace),suffix,identify)
1191 end
1192
1193 traverse("tree",withtree)
1194
1195 if not usesystemfonts then
1196 report_names("ignoring system fonts")
1197 elseif texconfig.kpse_init then
1198 traverse("lsr", withlsr)
1199 else
1200 traverse("system", withsystem)
1201 end
1202
1203 data.statistics.readfiles = totalnofread
1204 data.statistics.skippedfiles = totalnofskipped
1205 data.statistics.duplicatefiles = totalnofduplicates
1206
1207
1208
1209
1210
1211end
1212
1213local function addfilenames()
1214 local data = names.data
1215 local specifications = data.specifications
1216 local indices = { }
1217 local files = { }
1218 for i=1,#specifications do
1219 local fullname = specifications[i].filename
1220 files[cleanfilename(fullname)] = fullname
1221 indices[fullname] = i
1222 end
1223 data.files = files
1224 data.indices = indices
1225end
1226
1227local function rejectclashes()
1228 local specifications = names.data.specifications
1229 local used = { }
1230 local okay = { }
1231 local rejected = { }
1232 local o = 0
1233 for i=1,#specifications do
1234 local s = specifications[i]
1235 local f = s.fontname
1236 if f then
1237 local fnd = used[f]
1238 local fnm = s.filename
1239 if fnd then
1240 if trace_warnings then
1241 report_names("fontname %a clashes, %a rejected in favor of %a",f,fnm,fnd)
1242 end
1243 rejected[f] = s.modification
1244 else
1245 used[f] = fnm
1246 o = o + 1
1247 okay[o] = s
1248 end
1249 else
1250 o = o + 1
1251 okay[o] = s
1252 end
1253 end
1254 local d = #specifications - #okay
1255 if d > 0 then
1256 report_names("%s files rejected due to clashes",d)
1257 end
1258 names.data.specifications = okay
1259 names.data.rejected = rejected
1260end
1261
1262local function resetdata()
1263 local mappings = { }
1264 local fallbacks = { }
1265 for _, k in next, filters.list do
1266 mappings [k] = { }
1267 fallbacks[k] = { }
1268 end
1269 names.data = {
1270 version = names.version,
1271 mappings = mappings,
1272 fallbacks = fallbacks,
1273 specifications = { },
1274 families = { },
1275 statistics = { },
1276 names = { },
1277 indices = { },
1278 rejected = { },
1279 datastate = resolvers.datastate(),
1280 }
1281end
1282
1283function names.identify(force)
1284 local starttime = os.gettimeofday()
1285 resetdata()
1286 analyzefiles(not force and names.readdata(names.basename))
1287 rejectclashes()
1288 collectfamilies()
1289 cleanupkeywords()
1290 collecthashes()
1291 checkduplicates()
1292 addfilenames()
1293
1294 collectstatistics(os.gettimeofday()-starttime)
1295end
1296
1297function names.is_permitted(name)
1298 return containers.is_usable(names.cache, name)
1299end
1300function names.writedata(name,data)
1301 containers.write(names.cache,name,data)
1302end
1303function names.readdata(name)
1304 return containers.read(names.cache,name)
1305end
1306
1307function names.load(reload,force)
1308 if not names.loaded then
1309 if reload then
1310 if names.is_permitted(names.basename) then
1311 names.identify(force)
1312 names.writedata(names.basename,names.data)
1313 else
1314 report_names("unable to access database cache")
1315 end
1316 names.saved = true
1317 end
1318 local data = names.readdata(names.basename)
1319 names.data = data
1320 if not names.saved then
1321 if not data or not next(data) or not data.specifications or not next(data.specifications) then
1322 names.load(true)
1323 end
1324 names.saved = true
1325 end
1326 if not data then
1327 report_names("accessing the data table failed")
1328 else
1329 unpackreferences()
1330 sorthashes()
1331 end
1332 names.loaded = true
1333 end
1334end
1335
1336local function list_them(mapping,sorted,pattern,t,all)
1337 if mapping[pattern] then
1338 t[pattern] = mapping[pattern]
1339 else
1340 for k=1,#sorted do
1341 local v = sorted[k]
1342 if not t[v] and find(v,pattern) then
1343 t[v] = mapping[v]
1344 if not all then
1345 return
1346 end
1347 end
1348 end
1349 end
1350end
1351
1352function names.list(pattern,reload,all)
1353 names.load()
1354 if names.loaded then
1355 local t = { }
1356 local data = names.data
1357 if data then
1358 local list = filters.list
1359 local mappings = data.mappings
1360 local sorted_mappings = data.sorted_mappings
1361 local fallbacks = data.fallbacks
1362 local sorted_fallbacks = data.sorted_fallbacks
1363 for i=1,#list do
1364 local format = list[i]
1365 list_them(mappings[format],sorted_mappings[format],pattern,t,all)
1366 if next(t) and not all then
1367 return t
1368 end
1369 list_them(fallbacks[format],sorted_fallbacks[format],pattern,t,all)
1370 if next(t) and not all then
1371 return t
1372 end
1373 end
1374 end
1375 return t
1376 end
1377end
1378
1379local reloaded = false
1380
1381local function is_reloaded()
1382 if not reloaded then
1383 local data = names.data
1384 if autoreload then
1385 local c_status = serialize(resolvers.datastate())
1386 local f_status = serialize(data.datastate)
1387 if c_status == f_status then
1388 if trace_names then
1389 report_names("font database has matching configuration and file hashes")
1390 end
1391 return
1392 else
1393 report_names("font database has mismatching configuration and file hashes")
1394 end
1395 else
1396 report_names("font database is regenerated (controlled by directive 'fonts.autoreload')")
1397 end
1398 names.loaded = false
1399 reloaded = true
1400 logs.flush()
1401 names.load(true)
1402 end
1403end
1404
1405
1410
1411local function fuzzy(mapping,sorted,name,sub)
1412 local condensed = gsub(name,"[^%a%d]","")
1413 local pattern = condensed .. "$"
1414 local matches = false
1415 for k=1,#sorted do
1416 local v = sorted[k]
1417 if v == condensed then
1418 return mapping[v], v
1419 elseif find(v,pattern) then
1420 return mapping[v], v
1421 elseif find(v,condensed) then
1422 if matches then
1423 matches[#matches+1] = v
1424 else
1425 matches = { v }
1426 end
1427 end
1428 end
1429 if matches then
1430 if #matches > 1 then
1431 sort(matches,function(a,b) return #a < #b end)
1432 end
1433 matches = matches[1]
1434 return mapping[matches], matches
1435 end
1436end
1437
1438
1439
1440local function checkinstance(found,askedname)
1441 local instancenames = found.instancenames
1442 if instancenames then
1443 local fullname = found.fullname
1444 for i=1,#instancenames do
1445 local instancename = instancenames[i]
1446 if fullname .. instancename == askedname then
1447 local f = fastcopy(found)
1448 f.instances = nil
1449 f.instance = instancename
1450 return f
1451 end
1452 end
1453 end
1454 return found
1455end
1456
1457local function foundname(name,sub)
1458 local data = names.data
1459 local mappings = data.mappings
1460 local sorted_mappings = data.sorted_mappings
1461 local fallbacks = data.fallbacks
1462 local sorted_fallbacks = data.sorted_fallbacks
1463 local list = filters.list
1464
1465
1466
1467 for i=1,#list do
1468 local l = list[i]
1469 local found = mappings[l][name]
1470 if found then
1471 if trace_names then
1472 report_names("resolved via direct name match: %a",name)
1473 end
1474 return checkinstance(found,name)
1475 end
1476 end
1477 for i=1,#list do
1478 local l = list[i]
1479 local found, fname = fuzzy(mappings[l],sorted_mappings[l],name,sub)
1480 if found then
1481 if trace_names then
1482 report_names("resolved via fuzzy name match: %a onto %a",name,fname)
1483 end
1484 return checkinstance(found,name)
1485 end
1486 end
1487 for i=1,#list do
1488 local l = list[i]
1489 local found = fallbacks[l][name]
1490 if found then
1491 if trace_names then
1492 report_names("resolved via direct fallback match: %a",name)
1493 end
1494 return checkinstance(found,name)
1495 end
1496 end
1497 for i=1,#list do
1498 local l = list[i]
1499 local found, fname = fuzzy(sorted_mappings[l],sorted_fallbacks[l],name,sub)
1500 if found then
1501 if trace_names then
1502 report_names("resolved via fuzzy fallback match: %a onto %a",name,fname)
1503 end
1504 return checkinstance(found,name)
1505 end
1506 end
1507 if trace_names then
1508 report_names("font with name %a cannot be found",name)
1509 end
1510end
1511
1512function names.resolvedspecification(askedname,sub)
1513 if askedname and askedname ~= "" and names.enabled then
1514 askedname = cleanname(askedname)
1515 names.load()
1516 local found = foundname(askedname,sub)
1517 if not found and is_reloaded() then
1518 found = foundname(askedname,sub)
1519 end
1520 return found
1521 end
1522end
1523
1524function names.resolve(askedname,sub)
1525 local found = names.resolvedspecification(askedname,sub)
1526 if found then
1527 return found.filename, found.subfont and found.rawname, found.subfont, found.instance
1528 end
1529end
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542local runtimefiles = { }
1543local runtimedone = false
1544
1545local function addruntimepath(path)
1546 names.load()
1547 local paths = type(path) == "table" and path or { path }
1548 local suffixes = tohash(filters.list)
1549 for i=1,#paths do
1550 local path = resolveprefix(paths[i])
1551 if path ~= "" then
1552 local list = dir.glob(path.."/*")
1553 for i=1,#list do
1554 local fullname = list[i]
1555 local suffix = lower(suffixonly(fullname))
1556 if suffixes[suffix] then
1557 local c = cleanfilename(fullname)
1558 runtimefiles[c] = fullname
1559 if trace_names then
1560 report_names("adding runtime filename %a for %a",c,fullname)
1561 end
1562 end
1563 end
1564 end
1565 end
1566end
1567
1568local function addruntimefiles(variable)
1569 local paths = variable and resolvers.expandedpathlistfromvariable(variable)
1570 if paths and #paths > 0 then
1571 addruntimepath(paths)
1572 end
1573end
1574
1575names.addruntimepath = addruntimepath
1576names.addruntimefiles = addruntimefiles
1577
1578function names.getfilename(askedname,suffix)
1579 if not runtimedone then
1580 addruntimefiles(names.runtimefontsvariable)
1581 runtimedone = true
1582 end
1583 local cleanname = cleanfilename(askedname,suffix)
1584 local found = runtimefiles[cleanname]
1585 if found then
1586 return found
1587 end
1588 names.load()
1589 local files = names.data.files
1590 local found = files and files[cleanname] or ""
1591 if found == "" and is_reloaded() then
1592 files = names.data.files
1593 found = files and files[cleanname] or ""
1594 end
1595 if found and found ~= "" then
1596 return resolvers.findbinfile(found,suffix) or ""
1597 end
1598end
1599
1600
1601
1602local function s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family)
1603 if family then
1604 for i=1,#family do
1605 local f = family[i]
1606 if f and weight == f.weight and style == f.style and width == f.width and variant == f.variant then
1607 found[#found+1], done[f] = f, true
1608 if not all then return end
1609 end
1610 end
1611 end
1612end
1613local function m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname)
1614 for i=1,#sorted do
1615 local k = sorted[i]
1616 local family = families[k]
1617 for i=1,#family do
1618 local f = family[i]
1619 if not done[f] and weight == f.weight and style == f.style and width == f.width and variant == f.variant and find(f.fontname,strictname) then
1620 found[#found+1], done[f] = f, true
1621 if not all then return end
1622 end
1623 end
1624 end
1625end
1626
1627local function s_collect_weight_style_width(found,done,all,weight,style,width,family)
1628 if family then
1629 for i=1,#family do
1630 local f = family[i]
1631 if f and weight == f.weight and style == f.style and width == f.width then
1632 found[#found+1], done[f] = f, true
1633 if not all then return end
1634 end
1635 end
1636 end
1637end
1638local function m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname)
1639 for i=1,#sorted do
1640 local k = sorted[i]
1641 local family = families[k]
1642 for i=1,#family do
1643 local f = family[i]
1644 if not done[f] and weight == f.weight and style == f.style and width == f.width and find(f.fontname,strictname) then
1645 found[#found+1], done[f] = f, true
1646 if not all then return end
1647 end
1648 end
1649 end
1650end
1651
1652local function s_collect_weight_style(found,done,all,weight,style,family)
1653 if family then
1654 for i=1,#family do local f = family[i]
1655 if f and weight == f.weight and style == f.style then
1656 found[#found+1], done[f] = f, true
1657 if not all then return end
1658 end
1659 end
1660 end
1661end
1662local function m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname)
1663 for i=1,#sorted do
1664 local k = sorted[i]
1665 local family = families[k]
1666 for i=1,#family do
1667 local f = family[i]
1668 if not done[f] and weight == f.weight and style == f.style and find(f.fontname,strictname) then
1669 found[#found+1], done[f] = f, true
1670 if not all then return end
1671 end
1672 end
1673 end
1674end
1675
1676local function s_collect_style_width(found,done,all,style,width,family)
1677 if family then
1678 for i=1,#family do local f = family[i]
1679 if f and style == f.style and width == f.width then
1680 found[#found+1], done[f] = f, true
1681 if not all then return end
1682 end
1683 end
1684 end
1685end
1686local function m_collect_style_width(found,done,all,style,width,families,sorted,strictname)
1687 for i=1,#sorted do
1688 local k = sorted[i]
1689 local family = families[k]
1690 for i=1,#family do
1691 local f = family[i]
1692 if not done[f] and style == f.style and width == f.width and find(f.fontname,strictname) then
1693 found[#found+1], done[f] = f, true
1694 if not all then return end
1695 end
1696 end
1697 end
1698end
1699
1700local function s_collect_weight(found,done,all,weight,family)
1701 if family then
1702 for i=1,#family do local f = family[i]
1703 if f and weight == f.weight then
1704 found[#found+1], done[f] = f, true
1705 if not all then return end
1706 end
1707 end
1708 end
1709end
1710local function m_collect_weight(found,done,all,weight,families,sorted,strictname)
1711 for i=1,#sorted do
1712 local k = sorted[i]
1713 local family = families[k]
1714 for i=1,#family do
1715 local f = family[i]
1716 if not done[f] and weight == f.weight and find(f.fontname,strictname) then
1717 found[#found+1], done[f] = f, true
1718 if not all then return end
1719 end
1720 end
1721 end
1722end
1723
1724local function s_collect_style(found,done,all,style,family)
1725 if family then
1726 for i=1,#family do local f = family[i]
1727 if f and style == f.style then
1728 found[#found+1], done[f] = f, true
1729 if not all then return end
1730 end
1731 end
1732 end
1733end
1734local function m_collect_style(found,done,all,style,families,sorted,strictname)
1735 for i=1,#sorted do
1736 local k = sorted[i]
1737 local family = families[k]
1738 for i=1,#family do
1739 local f = family[i]
1740 if not done[f] and style == f.style and find(f.fontname,strictname) then
1741 found[#found+1], done[f] = f, true
1742 if not all then return end
1743 end
1744 end
1745 end
1746end
1747
1748local function s_collect_width(found,done,all,width,family)
1749 if family then
1750 for i=1,#family do local f = family[i]
1751 if f and width == f.width then
1752 found[#found+1], done[f] = f, true
1753 if not all then return end
1754 end
1755 end
1756 end
1757end
1758local function m_collect_width(found,done,all,width,families,sorted,strictname)
1759 for i=1,#sorted do
1760 local k = sorted[i]
1761 local family = families[k]
1762 for i=1,#family do
1763 local f = family[i]
1764 if not done[f] and width == f.width and find(f.fontname,strictname) then
1765 found[#found+1], done[f] = f, true
1766 if not all then return end
1767 end
1768 end
1769 end
1770end
1771
1772local function s_collect(found,done,all,family)
1773 if family then
1774 for i=1,#family do local f = family[i]
1775 if f then
1776 found[#found+1], done[f] = f, true
1777 if not all then return end
1778 end
1779 end
1780 end
1781end
1782local function m_collect(found,done,all,families,sorted,strictname)
1783 for i=1,#sorted do
1784 local k = sorted[i]
1785 local family = families[k]
1786 for i=1,#family do
1787 local f = family[i]
1788 if not done[f] and find(f.fontname,strictname) then
1789 found[#found+1], done[f] = f, true
1790 if not all then return end
1791 end
1792 end
1793 end
1794end
1795
1796local function collect(stage,found,done,name,weight,style,width,variant,all)
1797 local data = names.data
1798 local families = data.families
1799 local sorted = data.sorted_families
1800 local strictname = "^".. name
1801 local family = families[name]
1802 if trace_names then
1803 report_names("resolving name %a, weight %a, style %a, width %a, variant %a",name,weight,style,width,variant)
1804 end
1805 if weight and weight ~= "" then
1806 if style and style ~= "" then
1807 if width and width ~= "" then
1808 if variant and variant ~= "" then
1809 if trace_names then
1810 report_names("resolving stage %s, name %a, weight %a, style %a, width %a, variant %a",stage,name,weight,style,width,variant)
1811 end
1812 s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family)
1813 m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname)
1814 else
1815 if trace_names then
1816 report_names("resolving stage %s, name %a, weight %a, style %a, width %a",stage,name,weight,style,width)
1817 end
1818 s_collect_weight_style_width(found,done,all,weight,style,width,family)
1819 m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname)
1820 end
1821 else
1822 if trace_names then
1823 report_names("resolving stage %s, name %a, weight %a, style %a",stage,name,weight,style)
1824 end
1825 s_collect_weight_style(found,done,all,weight,style,family)
1826 m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname)
1827 end
1828 else
1829 if trace_names then
1830 report_names("resolving stage %s, name %a, weight %a",stage,name,weight)
1831 end
1832 s_collect_weight(found,done,all,weight,family)
1833 m_collect_weight(found,done,all,weight,families,sorted,strictname)
1834 end
1835 elseif style and style ~= "" then
1836 if width and width ~= "" then
1837 if trace_names then
1838 report_names("resolving stage %s, name %a, style %a, width %a",stage,name,style,width)
1839 end
1840 s_collect_style_width(found,done,all,style,width,family)
1841 m_collect_style_width(found,done,all,style,width,families,sorted,strictname)
1842 else
1843 if trace_names then
1844 report_names("resolving stage %s, name %a, style %a",stage,name,style)
1845 end
1846 s_collect_style(found,done,all,style,family)
1847 m_collect_style(found,done,all,style,families,sorted,strictname)
1848 end
1849 elseif width and width ~= "" then
1850 if trace_names then
1851 report_names("resolving stage %s, name %a, width %a",stage,name,width)
1852 end
1853 s_collect_width(found,done,all,width,family)
1854 m_collect_width(found,done,all,width,families,sorted,strictname)
1855 else
1856 if trace_names then
1857 report_names("resolving stage %s, name %a",stage,name)
1858 end
1859 s_collect(found,done,all,family)
1860 m_collect(found,done,all,families,sorted,strictname)
1861 end
1862end
1863
1864local function heuristic(name,weight,style,width,variant,all)
1865 local found, done = { }, { }
1866
1867 weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal"
1868 name = cleanname(name)
1869 collect(1,found,done,name,weight,style,width,variant,all)
1870
1871 if #found == 0 and variant ~= "normal" then
1872 variant = "normal"
1873 collect(4,found,done,name,weight,style,width,variant,all)
1874 end
1875 if #found == 0 and width ~= "normal" then
1876 width = "normal"
1877 collect(2,found,done,name,weight,style,width,variant,all)
1878 end
1879 if #found == 0 and weight ~= "normal" then
1880 weight = "normal"
1881 collect(3,found,done,name,weight,style,width,variant,all)
1882 end
1883 if #found == 0 and style ~= "normal" then
1884 style = "normal"
1885 collect(4,found,done,name,weight,style,width,variant,all)
1886 end
1887
1888 local nf = #found
1889 if trace_names then
1890 if nf then
1891 local t = { }
1892 for i=1,nf do
1893 t[i] = formatters["%a"](found[i].fontname)
1894 end
1895 report_names("name %a resolved to %s instances: % t",name,nf,t)
1896 else
1897 report_names("name %a unresolved",name)
1898 end
1899 end
1900 if all then
1901 return nf > 0 and found
1902 else
1903 return found[1]
1904 end
1905end
1906
1907function names.specification(askedname,weight,style,width,variant,reload,all)
1908 if askedname and askedname ~= "" and names.enabled then
1909 askedname = cleanname(askedname)
1910 names.load(reload)
1911 local found = heuristic(askedname,weight,style,width,variant,all)
1912 if not found and is_reloaded() then
1913 found = heuristic(askedname,weight,style,width,variant,all)
1914 if not filename then
1915 found = foundname(askedname)
1916 end
1917 end
1918 return found
1919 end
1920end
1921
1922function names.collect(askedname,weight,style,width,variant,reload,all)
1923 if askedname and askedname ~= "" and names.enabled then
1924 askedname = cleanname(askedname)
1925 names.load(reload)
1926 local list = heuristic(askedname,weight,style,width,variant,true)
1927 if not list or #list == 0 and is_reloaded() then
1928 list = heuristic(askedname,weight,style,width,variant,true)
1929 end
1930 return list
1931 end
1932end
1933
1934function names.collectspec(askedname,reload,all)
1935 local name, weight, style, width, variant = names.splitspec(askedname)
1936 return names.collect(name,weight,style,width,variant,reload,all)
1937end
1938
1939function names.resolvespec(askedname,sub)
1940 local found = names.specification(names.splitspec(askedname))
1941 if found then
1942 return found.filename, found.subfont and found.rawname
1943 end
1944end
1945
1946function names.collectfiles(askedname,reload)
1947 if askedname and askedname ~= "" and names.enabled then
1948 askedname = cleanname(askedname)
1949 names.load(reload)
1950 local list = { }
1951 local specifications = names.data.specifications
1952 for i=1,#specifications do
1953 local s = specifications[i]
1954 if find(cleanname(basename(s.filename)),askedname) then
1955 list[#list+1] = s
1956 end
1957 end
1958 return list
1959 end
1960end
1961
1962
1963
1964
1965
1966
1967
1968function names.exists(name)
1969 local found = false
1970 local list = filters.list
1971 for k=1,#list do
1972 local v = list[k]
1973 found = (findfile(name,v) or "") ~= ""
1974 if found then
1975 return found
1976 end
1977 end
1978 return (findfile(name,"tfm") or "") ~= "" or (names.resolve(name) or "") ~= ""
1979end
1980
1981local lastlookups, lastpattern = { }, ""
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030local function look_them_up(lookups,specification)
2031 for key, value in sortedhash(specification) do
2032 local t = { }
2033 local n = 0
2034 if find(value,"*",1,true) then
2035 value = topattern(value)
2036 for i=1,#lookups do
2037 local s = lookups[i]
2038 if find(s[key],value) then
2039 n = n + 1
2040 t[n] = lookups[i]
2041 end
2042 end
2043 else
2044 for i=1,#lookups do
2045 local s = lookups[i]
2046 if s[key] == value then
2047 n = n + 1
2048 t[n] = lookups[i]
2049 end
2050 end
2051 end
2052 if trace_names then
2053 report_names("%s matches for key %a with value %a",#t,key,value)
2054 end
2055 lookups = t
2056 end
2057 return lookups
2058end
2059
2060local function first_look(name,reload)
2061 names.load(reload)
2062 local data = names.data
2063 local specifications = data.specifications
2064 local families = data.families
2065 if name then
2066 return families[name]
2067 else
2068 return specifications
2069 end
2070end
2071
2072function names.lookup(pattern,name,reload)
2073 names.load(reload)
2074 local data = names.data
2075 local specifications = data.specifications
2076 local families = data.families
2077 local lookups = specifications
2078 if name then
2079 name = cleanname(name)
2080 end
2081 if type(pattern) == "table" then
2082 local familyname = pattern.familyname
2083 if familyname then
2084 familyname = cleanname(familyname)
2085 pattern.familyname = familyname
2086 end
2087 local lookups = first_look(name or familyname,reload)
2088 if lookups then
2089 if trace_names then
2090 report_names("starting with %s lookups for '%T'",#lookups,pattern)
2091 end
2092 lookups = look_them_up(lookups,pattern)
2093 end
2094 lastpattern = false
2095 lastlookups = lookups or { }
2096 elseif lastpattern ~= pattern then
2097 local lookups = first_look(name or (not find(pattern,"=",1,true) and pattern),reload)
2098 if lookups then
2099 if trace_names then
2100 report_names("starting with %s lookups for %a",#lookups,pattern)
2101 end
2102 local specification = settings_to_hash(pattern)
2103 local familyname = specification.familyname
2104 if familyname then
2105 familyname = cleanname(familyname)
2106 specification.familyname = familyname
2107 end
2108 lookups = look_them_up(lookups,specification)
2109 end
2110 lastpattern = pattern
2111 lastlookups = lookups or { }
2112 end
2113 return #lastlookups
2114end
2115
2116function names.getlookupkey(key,n)
2117 local l = lastlookups[n or 1]
2118 return (l and l[key]) or ""
2119end
2120
2121function names.noflookups()
2122 return #lastlookups
2123end
2124
2125function names.getlookups(pattern,name,reload)
2126 if pattern then
2127 names.lookup(pattern,name,reload)
2128 end
2129 return lastlookups
2130end
2131
2132
2133
2134local specifications = allocate()
2135names.specifications = specifications
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149function names.register(files)
2150 if files then
2151 local list, commonname = files.list, files.name
2152 if list then
2153 local n, m = 0, 0
2154 for filename, filespec in sortedhash(list) do
2155 local name = lower(filespec.name or commonname)
2156 if name and name ~= "" then
2157 local style = normalized_styles [lower(filespec.style or "normal")]
2158 local width = normalized_widths [lower(filespec.width or "normal")]
2159 local weight = normalized_weights [lower(filespec.weight or "normal")]
2160 local variant = normalized_variants[lower(filespec.variant or "normal")]
2161 local weights = specifications[name ] if not weights then weights = { } specifications[name ] = weights end
2162 local styles = weights [weight] if not styles then styles = { } weights [weight] = styles end
2163 local widths = styles [style ] if not widths then widths = { } styles [style ] = widths end
2164 local variants = widths [width ] if not variants then variants = { } widths [width ] = variants end
2165 variants[variant] = filename
2166 n = n + 1
2167 else
2168 m = m + 1
2169 end
2170 end
2171 if trace_specifications then
2172 report_names("%s filenames registered, %s filenames rejected",n,m)
2173 end
2174 end
2175 end
2176end
2177
2178function names.registered(name,weight,style,width,variant)
2179 local ok = specifications[name]
2180 ok = ok and (ok[(weight and weight ~= "" and weight ) or "normal"] or ok.normal)
2181 ok = ok and (ok[(style and style ~= "" and style ) or "normal"] or ok.normal)
2182 ok = ok and (ok[(width and width ~= "" and width ) or "normal"] or ok.normal)
2183 ok = ok and (ok[(variant and variant ~= "" and variant) or "normal"] or ok.normal)
2184
2185
2186
2187 if ok then
2188 return {
2189 filename = ok,
2190 subname = "",
2191
2192 }
2193 end
2194end
2195
2196function names.resolvespec(askedname,sub)
2197 local name, weight, style, width, variant = names.splitspec(askedname)
2198 if trace_specifications then
2199 report_names("resolving specification: %a to name=%s, weight=%s, style=%s, width=%s, variant=%s",askedname,name,weight,style,width,variant)
2200 end
2201 local found = names.registered(name,weight,style,width,variant)
2202 if found and found.filename then
2203 if trace_specifications then
2204 report_names("resolved by registered names: %a to %s",askedname,found.filename)
2205 end
2206 return found.filename, found.subname, found.rawname
2207 else
2208 found = names.specification(name,weight,style,width,variant)
2209 if found and found.filename then
2210 if trace_specifications then
2211 report_names("resolved by font database: %a to %s",askedname,found.filename)
2212 end
2213 return found.filename, found.subfont and found.rawname
2214 end
2215 end
2216 if trace_specifications then
2217 report_names("unresolved: %s",askedname)
2218 end
2219end
2220
2221function fonts.names.ignoredfile(filename)
2222 return false
2223end
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238 |