]> git.lyx.org Git - features.git/blob - development/tools/listFontWithLang.pl
3fe4d2e808b69ff4c34f7340e4c319a8a8125f2c
[features.git] / development / tools / listFontWithLang.pl
1 #! /usr/bin/env perl
2 # -*- mode: perl; -*-
3
4 # file listFontWithLang.pl
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING
7 # or at http://www.lyx.org/about/licence.php
8 #
9 # author Kornel Benko
10 # Full author contact details are available in the file CREDITS
11 # or at https://www.lyx.org/Credits
12 #
13 # Usage: listFontWithLang.pl <options>
14 #   Displays installed system font names selected by <options>
15 #   Option-strings with more that 1 char need be prefixed by '--'
16 #
17 # Option to get list of options: -h
18 #
19 # Some equivalencies for instance with option -n
20 #       -n arial
21 #       -N=arial
22 #       --nAme=Arial
23 #       --name arial
24 # Options and option-parameter are case insensitive
25
26 BEGIN {
27     use File::Spec;
28     my $p = File::Spec->rel2abs( __FILE__ );
29     $p =~ s/[\/\\]?[^\/\\]+$//;
30     unshift(@INC, $p);
31 }
32
33 use strict;
34 use warnings;
35 use Encode;
36 use GetOptions;
37
38 sub convertlang($);
39 sub extractlist($$$);   # my ($l, $islang, $txt, $rres) = @_;
40 sub getIndexes($$);
41 sub getVal($$$);        # my ($l, $txtval, $txtlang) = @_;
42 sub getproperties($$$$);
43 sub ismathfont($$);
44 sub correctstyle($);
45 sub decimalUnicode($);
46 sub contains($$);
47 sub sprintIntervalls($);
48
49 # Following fields for a parameter can be defined:
50 # fieldname:         Name of entry in %options
51 # type:              [:=][sif], ':' = optional, '=' = required, 's' = string, 'i' = integer, 'f' = float
52 # alias:             reference to a list of aliases e.g. ["alias1", "alias2", ... ]
53 # listsep:           Separator for multiple data
54 # comment:           Parameter description
55 my @optionsDef = (
56   # help + verbose already handled in 'GetOptions'
57   ["n",
58    {fieldname => "FontName", listsep => ',',
59     type => "=s", alias => ["name"],
60     comment => "Select font-names matching these (comma separated) regexes"},],
61   ["nn",
62    {fieldname => "NFontName",
63     type => "=s", listsep => ',',
64     comment => "Select font-names NOT matching these (comma separated) regexes"},],
65   ["p",
66    {fieldname => "Property",
67     type => "=s", listsep => ',',
68     comment => "Select fonts with properties matching these (comma separated) regexes"},],
69   ["np",
70    {fieldname => "NProperty",
71     type => "=s", listsep => ',',
72     comment => "Select fonts with properties NOT matching these (comma separated) regexes"},],
73   ["s",
74    {fieldname => "Scripts",
75     type => "=s", listsep => ',',
76     comment => "Select fonts with scripts matching these (comma separated) regexes"},],
77   ["ns",
78    {fieldname => "NScripts",
79     type => "=s", listsep => ',',
80     comment => "Select fonts with scripts NOT matching these (comma separated) regexes"},],
81   ["math",
82    {fieldname => "Math",
83     comment => "Select fonts probably containing math glyphs"},],
84   ["c",
85    {fieldname => "Contains",  alias => ["contains"],
86     type => "=s", listsep => ',',
87     comment => "Select fonts containing all these (possibly comma separated) glyphs",
88     comment2 => "____example: -c=\"0-9,u+32-u+x7f\"",}],
89   ["l",
90    {fieldname => "Lang",
91     type => "=s", alias=>["lang"],
92     comment => "Comma separated list of desired languages"},],
93   ["pc",
94    {fieldname => "PrintCharset", alias => ["printcharset"],
95     comment => "Print intervals of supported unicode character values"},],
96   ["pl",
97    {fieldname => "PrintLangs", alias => ["printlangs"],
98     comment => "Print supported languages"},],
99   ["pp",
100    {fieldname => "PrintProperties", alias => ["printproperties"],
101     comment => "Print properties from weight, slant and width"},],
102   ["ps",
103    {fieldname => "PrintScripts", alias => ["printscripts"],
104     comment => "Print supported scripts"},],
105   ["pf",
106    {fieldname => "PrintFiles", alias => ["printfiles"],
107     comment => "Print font file names"},],
108   ["pw",
109    {fieldname => "PrintWarnings",
110     comment => "Print warnings about discarded/overwritten fonts, conflicting styles"},],
111 );
112 my %options = %{&handleOptions(\@optionsDef)};
113
114 $options{Lang} = "" if (! defined($options{Lang}));
115
116 #############################################################
117
118 my @langs = split(',', $options{Lang});
119 for my $lg (@langs) {
120   $lg = &convertlang($lg);
121 }
122
123 if (defined($options{Contains})) {
124   my %glyphs = ();         # To ignore duplicates
125   for my $a1 (@{$options{Contains}}) {
126     for my $e (decimalUnicode($a1)) {
127       $glyphs{$e} = 1;
128     }
129   }
130   # create intervalls
131   my @glyphs = sort {$a <=> $b;} keys %glyphs;
132
133   # $options{Contains} no longer needed, so use it for unicode-point intervalls
134   $options{Contains} = [];
135   my ($first, $last) = (undef, undef);
136   for my $i (@glyphs) {
137     if (! defined($last)) {
138       $first = $i;
139       $last = $i;
140       next;
141     }
142     if ($i == $last+1) {
143       $last = $i;
144       next;
145     }
146     push(@{$options{Contains}}, [$first, $last]);
147     $first = $i;
148     $last = $i;
149   }
150   if (defined($last)) {
151     push(@{$options{Contains}}, [$first, $last]);
152   }
153   if (exists($options{verbose})) {
154     print "Checking for unicode-points: " . &sprintIntervalls($options{Contains}) . "\n";
155   }
156 }
157
158 my $cmd = "fc-list";
159 if (defined($langs[0])) {
160   $cmd .= " :lang=" . join(',', @langs);
161 }
162
163 my $format = "foundry=\"%{foundry}\"" .
164     " postscriptname=\"%{postscriptname}\"" .
165     " fn=\"%{fullname}\" fnl=\"%{fullnamelang}\"" .
166     " family=\"%{family}\" flang=\"%{familylang}\" " .
167     " style=\"%{style}\" stylelang=\"%{stylelang}\"";
168
169 if (exists($options{PrintScripts}) || defined($options{Scripts}) || defined($options{NSpripts}) || exists($options{Math})) {
170   $format .= " script=\"%{capability}\"";
171 }
172 if (exists($options{PrintLangs}) || defined($langs[0])) {
173   $format .= " lang=\"%{lang}\"";
174 }
175 if (exists($options{PrintProperties}) || defined($options{Property}) || defined($options{NProperty})) {
176   $format .= " weight=%{weight} slant=%{slant} width=%{width} spacing=%{spacing}";
177 }
178 if (defined($options{Contains}) || exists($options{PrintCharset})) {
179   $format .= " charset=\"%{charset}\"";
180 }
181 $format .= " file=\"%{file}\" abcd\\n";
182 $cmd .= " -f '$format'";
183 #print "$cmd\n";
184
185 my %ftypes = (
186   # Dummy internal map
187   0 => "Serif",
188   100 => "Sans",
189   110 => "Script",
190   120 => "Fraktur",
191   130 => "Fancy",
192   140 => "Initials",
193   200 => "Symbol",
194   "default" => "Serif",
195 );
196
197 my %weights = (
198   0 => "Thin",
199   40 => "Extralight",
200   50 => "Light",
201   55 => "Semilight",
202   75 => "Book",
203   80 => "Regular",
204   100 => "Medium",
205   180 => "Semibold",
206   200 => "Bold",
207   205 => "Extrabold",
208   210 => "Black",
209   215 => "ExtraBlack",
210 );
211
212 my %slants = (
213   0 => "Roman",
214   100 => "Italic",
215   110 => "Oblique",
216 );
217
218 my %widths = (
219   50 => "Ultracondensed",
220   63 => "Extracondensed",
221   75 => "Condensed",
222   87 => "Semicondensed",
223   100 => "Normal",
224   113 => "Semiexpanded",
225   125 => "Expanded",
226   150 => "Extraexpanded",
227   200 => "Ultraexpanded",
228 );
229
230 my %spacings = (
231   0 => "Proportional",
232   90 => "Dual",
233   100 => "Mono",
234   110 => "Charcell",
235   "default" => "Proportional",
236 );
237
238 # Build reverse mappings, (not needed yet)
239 for my $txt (qw(ftypes weights slants widths spacings)) {
240   my $map;
241   eval "\$map = \\%$txt";
242   for my $key (keys %{$map}) {
243     next if ($key !~ /^\d+$/);
244     my $value = lc($map->{$key});
245     $map->{"r"}->{$value} = $key;
246   }
247 }
248
249 # key:= fontname
250 #     subkey foundry
251 #            subfoundry
252 my %collectedfonts = ();
253 my %fontpriority = (
254   otf => 0,                # type 2, opentype CFF (Compact Font Format)
255   ttc => 1.1,              # type 1 (True Type font Collection)
256   ttf => 1.2,              # type 1 (True Type Font)
257   woff=> 1.3,              # type 1 (Web Open Font Format)
258   t1  => 1.4,              # type 1 (postscript)
259   pfb => 1.5,              # type 1 (Printer Font Binary)
260   pfa => 1.6,              # type 1 (Printer Font Ascii)
261   pcf => 5,                # Bitmap (Packaged Collaboration Files)?
262 );
263 my $nexttype = 6;
264
265 # list of regexes for known sans serif fonts
266 my %sansFonts = (
267   "value" => 100,          # Sans serif
268   "a" => qr/^(arial|andika|angostura|anonymous|arab|aroania|arimo|asap)/i,
269   "b" => qr/^b(aekmuk|ebas|erenika|eteckna|euron|lue)/i,
270   "c" => qr/^c(abin|aliban|antarell|arbon|arlito|handas|hivo|mu bright|omfortaa|omic|oolvetica|ortoba|ousine|uprum|wtex(hei|yen)|yklop|ypro)/i,
271   "d" => qr/^(d2coding|dimnah|dosis|dyuthi)/i,
272   "e" => qr/^(electron|engebrechtre)/i,
273   "f" => qr/^(fandolhei|fetamont|fira|font awesome 5|forgotten)/i,
274   "g" => qr/^(gardiner|garuda|gfs ?neo|gillius|granada|graph|guanine|gunplay)/i,
275   "h" => qr/^(hack|hani|haramain|harano|harmattan|hor\b)/i,
276   "i" => qr/^(ibm plex|ikarius|inconsolata|induni.?h|iwona)/i,
277   "j" => qr/^(jara|jura)/i,
278   "k" => qr/^(kalimati|kanji|karla|kayrawan|kenyan|keraleeyam|khalid|khmer [or]|kiloji|klaudia|komatu|kurier)/i,
279   "l" => qr/^l(aksaman|arabie|ato|eague|exend|exigulim|ibel|iberation|ibre franklin|ibris|inux biolinum|obster|ogix|ohit|oma)/i,
280   "m" => qr/^m(\+ |anchu|anjari|arcellus|ashq|eera|etal|igmix|igu|ikachan|intspirit|ona|onlam|ono(fonto|id|isome|noki)|ontserrat|otoyal|ukti|usica)/i,
281   "n" => qr/^(nachlieli|nada|nafees|nagham|nanum(barunpen|square)|nice)/i,
282   "o" => qr/^(ocr|okolaks|opendyslexic|ostorah|ouhud|over|oxygen)/i,
283   "p" => qr/^(padauk|pagul|paktype|pakenham|palladio|petra|phetsarath|play\b|poiret|port\b|primer\b|prociono|pt\b|purisa)/i,
284   "q" => qr/^(qt(ancient|helvet|avanti|doghaus|eratype|eurotype|floraline|frank|fritz|future|greece|howard|letter|optimum)|quercus)/i,
285   "r" => qr/^(rachana|radio\b|raleway|ricty|roboto|rosario)/i,
286   "s" => qr/^(salem|samanata|sawasdee|shado|sharja|simple|sophia|soul|source|switzera)/i,
287   "t" => qr/^(tarablus|teen|texgyre(adventor|heros)|tiresias|trebuchet|tscu|tuffy)/i,
288   "u" => qr/^(ubuntu|ukij (bom|chechek|cjk|diwani|ekran|elipbe|inchike|jelliy|kufi|qara|qolyazma|teng|title|tor)|umpush|un ?(dinaru|jamo|graphic|taza|vada|yetgul)|uni(kurd|space|versalis)|uroob|urw ?classico)/i,
289   "v" => qr/^(veranda|vn ?urwclassico)/i,
290   "w" => qr/^(waree)/i,
291   "y" => qr/^(yanone)/i,
292   "z" => qr/^(zekton|zero)/i,
293 );
294 my %scriptFonts = (
295   "value" => 110,          # Script
296   "c" => qr/^(chancery)/i,
297   "d" => qr/^(dancing)/i,
298   "e" => qr/^(elegante)/i,
299   "k" => qr/^(kaushan|karumbi)/i,
300   "m" => qr/^(mathjax_script|miama)/i,
301   "n" => qr/^(nanum (brush|pen) script)/i,
302   "q" => qr/^qt(arabian|boulevard|brushstroke|chancery|coronation|florencia|handwriting|linostroke|merry|pandora|slogan)/i,
303   "r" => qr/^(romande.*|ruf)script/i,
304   "u" => qr/^(un ?pilgi|urw ?chancery)/i,
305 );
306
307 my %fraktFonts = (
308   "value" => 120,          # Fraktur
309   "j" => qr/^(jsmath.?euf)/i,
310   "m" => qr/^(missaali)/i,
311   "o" => qr/^(oldania)/i,
312   "q" => qr/^qt(blackforest|cloisteredmonk|dublinirish|fraktur|heidelbergtype|(lino|london)scroll)/i,
313 );
314
315 my %fancyFonts = (
316   "value" => 130,          # Fancy
317   "c" => qr/^(cretino)/i,
318   "g" => qr/^(gfs.?theo)/i,
319 );
320
321 my %initialFonts = (
322   "value" => 140,          # Initials
323   "e" => qr/^(eb.?garamond.?init)/i,
324   "l" => qr/^(libertinus|linux).*initials/i,
325   "y" => qr/^(yinit)/i,
326 );
327
328 my %symbolFonts = (
329   "value" => 200,          # Symbol
330   "a" => qr/^(academicons)/i,
331   "c" => qr/^(caladings|ccicons|chess)/i,
332   "d" => qr/^(dingbats|drmsym)/i,
333   "e" => qr/^(elusiveicons|emoji)/i,
334   "f" => qr/^(fdsymbol|fourierorns)/i,
335   "h" => qr/^(hots)/i,
336   "m" => qr/^(marvosym|material)/i,
337   "n" => qr/^(noto.*emoji)/i,
338   "o" => qr/^(octicons)/i,
339   "q" => qr/^(qtdingbits)/i,
340   "t" => qr/^(typicons|twemoji)/i,
341   "w" => qr/^(webdings)/i,
342 );
343
344 if (open(FI,  "$cmd |")) {
345  NXTLINE: while (my $l = <FI>) {
346     chomp($l);
347     while ($l !~ /abcd$/) {
348       $l .= <FI>;
349       chomp($l);
350     }
351     my $file = "";
352     my $fonttype;
353     if ($l =~ /file=\"([^\"]+)\"/) {
354       $file = $1;
355       #next if ($file !~ /\.(otf|ttf|pfa|pfb|pcf|ttc)$/i);
356       if ($file !~ /\.([a-z0-9]{2,5})$/i) {
357         print "Unhandled extension for file $file\n";
358         next;
359       }
360       $fonttype = lc($1);
361       if (! defined($fontpriority{$fonttype})) {
362         print "Added extension $fonttype for file $file\n";
363         $fontpriority{$fonttype} = $nexttype;
364         $nexttype++;
365       }
366     }
367     my %usedlangs = ();
368     if ($l =~ / lang=\"([^\"]+)\"/) {
369       my @ll = split(/\|/, $1);
370       for my $lx (@ll) {
371         $usedlangs{&convertlang($lx)} = 1;
372       }
373     }
374
375     for my $lang (@langs) {
376       next NXTLINE if (! defined($usedlangs{$lang}));
377     }
378     my $style = &getVal($l, "style", "stylelang");
379     $style =~ s/^\\040//;
380     my $fullname = &getVal($l, "fn", "fnl");
381     my $postscriptname = "";
382     if ($l =~ /postscriptname=\"([^\"]+)\"/) {
383       $postscriptname = $1;
384     }
385     my $family = &getVal($l, "family", "flang");
386     $family =~ s/\\040/\-/;
387     my $fontname;
388     if (length($fullname) < 3) {
389       if (length($postscriptname) < 2) {
390         $fontname = "$family $style";
391       }
392       else {
393         $fontname = $postscriptname;
394       }
395     }
396     else {
397       $fontname = $fullname;
398     }
399     if (defined($options{NFontName})) {
400       for my $fn (@{$options{NFontName}}) {
401         next NXTLINE if ($fontname =~ /$fn/i);
402       }
403     }
404     if (defined($options{FontName})) {
405       for my $fn (@{$options{FontName}}) {
406         next NXTLINE if ($fontname !~ /$fn/i);
407       }
408     }
409     my @charlist = ();
410     if (defined($options{Contains}) || exists($options{PrintCharset})) {
411       if ($l =~ / charset=\"([^\"]+)\"/) {
412         my @list = split(/\s+/, $1);
413         for my $e (@list) {
414           my ($l, $h) = split('-', $e);
415           $h = $l if (! defined($h));
416           push(@charlist, [hex($l), hex($h)]);
417         }
418       }
419       if (defined($options{Contains})) {
420         for my $g (@{$options{Contains}}) {
421           next NXTLINE if (! contains($g, \@charlist));
422         }
423       }
424     }
425     my $props = "";
426     my @errors = ();
427     if (exists($options{PrintProperties}) || defined($options{Property}) || defined($options{NProperty})) {
428       my $properties = getproperties($l, $fontname, $style, \@errors);
429       if (defined($options{Property})) {
430         for my $pn (@{$options{Property}}) {
431           next NXTLINE if ($properties !~ /$pn/i);
432         }
433       }
434       if (defined($options{NProperty})) {
435         for my $pn (@{$options{NProperty}}) {
436           next NXTLINE if ($properties =~ /$pn/i);
437         }
438       }
439       if (exists($options{PrintProperties})) {
440         $props .= " ($properties)";
441       }
442     }
443
444     if (exists($options{PrintLangs})) {
445       $props .= '(' . join(',', sort keys %usedlangs) . ')';
446     }
447     if (exists($options{PrintCharset})) {
448       $props .= '(' . &sprintIntervalls(\@charlist) . ')';
449     }
450     if (exists($options{PrintScripts}) || defined($options{Scripts}) || defined($options{NScripts}) || exists($options{Math})) {
451       my @scripts = ();
452       my $scripts = "";
453       if ($l =~ / script=\"([^\"]+)\"/) {
454         @scripts = split(/\s+/, $1);
455         for my $ent (@scripts) {
456           $ent =~ s/^\s*otlayout://;
457           $ent = lc($ent);
458         }
459         $scripts = join(',', @scripts);
460       }
461       if (exists($options{Math})) {
462         next NXTLINE if (! &ismathfont($fontname,\@scripts));
463       }
464       if (exists($options{PrintScripts})) {
465         $props .= "($scripts)";
466       }
467       if (!defined($scripts[0])) {
468         # No script defined in font, so check only $options{Scripts}
469         next NXTLINE if (defined($options{Scripts}));
470       }
471       else {
472         if (defined($options{Scripts})) {
473           for my $s (@{$options{Scripts}}) {
474             next NXTLINE if ($scripts !~ /$s/i);
475           }
476         }
477         if (defined($options{NScripts})) {
478           for my $s (@{$options{NScripts}}) {
479             next NXTLINE if ($scripts =~ /$s/i);
480           }
481         }
482       }
483     }
484     my $foundry = "";
485     if ($l =~ /foundry=\"([^\"]+)\"/) {
486       $foundry = $1;
487       $foundry =~ s/^\s+//;
488       $foundry =~ s/\s+$//;
489     }
490     if (defined($collectedfonts{$fontname}->{$foundry}->{errors})) {
491       # Apparently not the first one, so add some info
492       my $oldfonttype = $collectedfonts{$fontname}->{$foundry}->{fonttype};
493       if (defined($errors[0])) {
494         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, @errors);
495       }
496       if ($fontpriority{$oldfonttype} > $fontpriority{$fonttype}) {
497         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, "Warning: overwriting old info for file: " . $collectedfonts{$fontname}->{$foundry}->{file});
498       }
499       else {
500         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, "Warning: discarding new info for file: $file");
501         next;
502       }
503     }
504     else {
505       $collectedfonts{$fontname}->{$foundry}->{errors} = \@errors;
506     }
507     $collectedfonts{$fontname}->{$foundry}->{props} = $props;
508     $collectedfonts{$fontname}->{$foundry}->{file} = $file;
509     $collectedfonts{$fontname}->{$foundry}->{fonttype} = $fonttype;
510   }
511   close(FI);
512 }
513
514 for my $fontname (sort keys %collectedfonts) {
515   my @foundries = sort keys %{$collectedfonts{$fontname}};
516   my $printfoundries = 0;
517   if (defined($foundries[1])) {
518     $printfoundries = 1;
519   }
520   for my $foundry (@foundries) {
521     if (exists($options{PrintWarnings})) {
522       for my $err (@{$collectedfonts{$fontname}->{$foundry}->{errors}}) {
523         print "$err\n";
524       }
525     }
526     my $fn = "Font : $fontname";
527     if ($printfoundries && ($foundry ne "")) {
528       $fn .= " \[$foundry\]";
529     }
530     print $fn;
531     print $collectedfonts{$fontname}->{$foundry}->{props};
532     if (exists($options{PrintFiles})) {
533       print ": " . $collectedfonts{$fontname}->{$foundry}->{file} . "\n";
534     }
535     else {
536       print "\n";
537     }
538   }
539 }
540
541 exit(0);
542 #################################################################################
543 sub convertlang($)
544 {
545   my ($ilang) = @_;
546   if ($ilang =~ /^\s*([a-z]+)([\-_]([a-z]+))?\s*$/i) {
547     my ($x, $y) = ($1, $3);
548     if (defined($y)) {
549       $ilang = lc($x) . '-' . lc($y);
550     }
551     else {
552       $ilang = lc($x);
553     }
554   }
555   return($ilang);
556 }
557
558 sub extractlist($$$)
559 {
560   my ($l, $islang, $txt, $rres) = @_;
561   my @res = ();
562   if ($l =~ /$txt=\"([^\"]+)\"/) {
563     @res = split(',', $1);
564     if ($islang) {
565       for my $lg (@res) {
566         $lg = &convertlang($lg);
567       }
568     }
569   }
570   @{$rres} = @res;
571 }
572
573 sub getIndexes($$)
574 {
575   my ($lang, $rlangs) = @_;
576   my @res = ();
577
578   for (my $i = 0; defined($rlangs->[$i]); $i++) {
579     if ($rlangs->[$i] eq $lang) {
580       push(@res, $i);
581     }
582   }
583   return(\@res);
584 }
585
586 sub getVal($$$)
587 {
588   my ($l, $txtval, $txtlang) = @_;
589   my @values = ();
590   my @langs = ();
591   &extractlist($l, 0, $txtval, \@values);
592   return("") if (! defined($values[0]));
593   &extractlist($l, 1, $txtlang, \@langs);
594   my $i = &getIndexes("en", \@langs);
595   my $res = "";
596   for my $k (@{$i}) {
597     if (defined($values[$k]) && (length($values[$k]) > length($res))) {
598       $res = $values[$k];
599     }
600   }
601   return($values[0]) if ($res eq "");
602   return($res);
603 }
604
605 sub getsinglevalue($$$)
606 {
607   my ($l, $txt, $rMap, $rget) = @_;
608   my $val;
609   if ($l =~ / $txt=(\d+)/) {
610     $val = $1;
611     # Search for nearest value to $val
612     if (defined($rMap->{$val})) {
613       return($rMap->{$val});
614     }
615     my $maxv = -1;
616     my $minv = 1000;
617     for my $key (keys %{$rMap}) {
618       next if ($key !~ /^\d+$/);
619       my $diff = abs($key - $val);
620       if ($diff < $minv) {
621         $maxv = $key;
622         $minv = $diff;
623       }
624       elsif ($diff == $minv) {
625         if ($key < $maxv) {
626           $maxv = $key;
627         }
628       }
629     }
630     if (! defined($rMap->{$maxv})) {
631       print "ERROR2: txt=$txt, val=$val\n";
632       exit(-2);
633     }
634     if ($val > $maxv) {
635       return($rMap->{$maxv} . "+$minv");
636     }
637     else {
638       return($rMap->{$maxv} . "-$minv");
639     }
640   }
641   else {
642     return(undef);
643   }
644 }
645
646 sub addTxt($$)
647 {
648   my ($txt, $val) = @_;
649   return("$txt($val)");
650 }
651
652 sub getftype($$)
653 {
654   my ($fontname, $style) = @_;
655   if ($fontname =~ /(sans)[-_ ]?(serif)?/i) {
656     return($ftypes{100}); # Sans Serif
657   }
658   elsif ($fontname =~ /gothic|dotum|gulim/i) {
659     if ($fontname =~ /bisrat gothic/i) {
660       return($ftypes{0});    # Serif
661     }
662     else {
663       return($ftypes{100}); # Sans Serif
664     }
665   }
666   elsif ($fontname =~ /serif|times|mincho|batang/i) {
667     if ($fontname =~ /good times/i) {
668       return($ftypes{100}); # Sans Serif
669     }
670     elsif ($fontname !~ /initials/i) {
671       return($ftypes{0});    # Serif
672     }
673   }
674   # Now check for fonts without a hint in font name
675   if ($fontname =~ /([a-z])/i) {
676     my $key = lc($1);
677     for my $rFonts (\%sansFonts, \%scriptFonts, \%fraktFonts, \%fancyFonts, \%initialFonts, \%symbolFonts) {
678       if (defined($rFonts->{$key})) {
679         if ($fontname =~ $rFonts->{$key}) {
680           return($ftypes{$rFonts->{"value"}});
681         }
682       }
683     }
684   }
685   if ("$fontname" =~ /^bpg/i) {
686     if ("$fontname" =~ /bpg (courier gpl|elite)/i) {
687       return($ftypes{0});    # Serif
688     }
689     else {
690       return($ftypes{100}); # Sans Serif
691     }
692   }
693   elsif ("$fontname" =~ /^dustismo/i) {
694     if ("$fontname" =~ /^dustismo roman/i) {
695       return($ftypes{0});    # Serif
696     }
697     else {
698       return($ftypes{100}); # Sans Serif
699     }
700   }
701   elsif ("$fontname" =~ /^go\b/i) {
702     if ("$fontname" =~ /^go mono/i) {
703       return($ftypes{0});    # Serif
704     }
705     else {
706       return($ftypes{100}); # Sans Serif
707     }
708   }
709   else {
710     return(undef);
711   }
712 }
713
714 sub getweight($$)
715 {
716   my ($fontname, $style) = @_;
717   my $result = undef;
718   for my $info ($style, $fontname) {
719     for my $key (keys %weights) {
720       next if ($key !~ /^\d+$/);
721       my $val = $weights{$key};
722       if ($info =~ /\b$val\b/i) {
723         return($val);
724       }
725     }
726   }
727   return($result);
728 }
729
730 sub getwidth($$)
731 {
732   my ($fontname, $style) = @_;
733   my $result = undef;
734   for my $key (keys %widths) {
735     next if ($key !~ /^\d+$/);
736     for my $info ($style, $fontname) {
737       if ($info =~ /\b$widths{$key}\b/i) {
738         return($widths{$key});
739       }
740       if ($info =~ /\bRegular\b/) {
741         if (!defined($result)) {
742           $result = $widths{100};
743         }
744       }
745     }
746   }
747   return($result);
748 }
749
750 sub getslant($$)
751 {
752   my ($fontname, $style) = @_;
753   for my $key (keys %slants) {
754     next if ($key !~ /^\d+$/);
755     if ($style =~ /\b$slants{$key}\b/i) {
756       return($slants{$key});
757     }
758   }
759   return(undef);
760 }
761
762 sub getspacing($$)
763 {
764   my ($fontname, $style) = @_;
765   for my $key (keys %spacings) {
766     next if ($key !~ /^\d+$/);
767     if ($style =~ /\b$spacings{$key}\b/i) {
768       return($spacings{$key});
769     }
770   }
771   if ("$fontname $style" =~ /(\bmono\b|luximono|typewriter|cursor|fixed)\b/i) {
772     return($spacings{100}); # Mono
773   }
774   else {
775     return(undef);
776   }
777 }
778
779 sub ismathfont($$)
780 {
781   my ($fontname, $rCapability) = @_;
782
783   return 1 if ($fontname =~ /math/i);
784   for my $cap (@{$rCapability}) {
785     return 1 if ($cap eq "math");
786   }
787   return 0;
788 }
789
790 sub getproperties($$$$)
791 {
792   my ($l, $fontname, $style, $rerrors) = @_;
793   my $newstyle = &correctstyle($style);
794   my $newfam = &correctstyle($fontname);
795   my @properties = ();
796
797   for my $txt (qw(ftype weight width slant spacing)) {
798     my ($map, $rget);
799     eval("\$map = " . '\%' . $txt . 's');
800     eval('$rget = \&' . "get$txt");
801     my $val2 = getsinglevalue($l, $txt, $map);
802     my $val1 = $rget->($newfam, $newstyle);
803     my $val;
804     if (defined($val2) && defined($val1) && ($val2 ne $val1)) {
805       if (($txt =~/^(weight|slant)$/) && ($newstyle =~ /$val1/)){
806         # style overrides weight and slant
807         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val1 from style");
808         $val = $val1;
809       }
810       elsif ($newfam =~ /$val1/) {
811         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val1 from fontname");
812         $val = $val1;
813       }
814       else {
815         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val2 from $txt-property");
816         $val = $val2;
817       }
818     }
819     elsif (! defined($val2)) {
820       $val = $val1;
821     }
822     else {
823       $val = $val2;
824     }
825     if (defined($val)) {
826       push(@properties, &addTxt($txt,$val));
827     }
828     else {
829       if (defined($map->{"default"})) {
830         push(@properties, &addTxt($txt,$map->{"default"}));
831       }
832       else {
833         push(@{$rerrors}, "Undefined value for $txt");
834       }
835     }
836   }
837   return(join(' ', @properties));
838 }
839
840 sub correctstyle($)
841 {
842   my ($style) = @_;
843   $style =~ s/^\\040//;
844   $style =~ s/^\s*\d+\s*//;
845   $style =~ s/\s*\d+$//;
846   $style =~ s/italic/ Italic/i;
847   $style =~ s/oblique/ Oblique/i;
848   $style =~ s/[\-_]/ /g;
849   $style =~ s/\breg\b/Regular/i;
850   $style =~ s/\bregita(lic)?\b/Regular Italic/i;
851   $style =~ s/\bregobl(ique)?\b/Regular Oblique/i;
852   $style =~ s/medium/Medium /i;
853   $style =~ s/\bmedita(lic)?\b/Medium Italic/i;
854   $style =~ s/\bmedobl(ique)?\b/Medium Oblique/i;
855   $style =~ s/\bmed\b/Medium /i;
856   $style =~ s/\bdemi\b/SemiBold/i;
857   $style =~ s/\bex(pd|t)\b/Expanded/i;
858   $style =~ s/semi ?cond(ensed)?/SemiCondensed/i;
859   $style =~ s/[sd]emi ?(bold|bd|bol)/SemiBold/i;
860   $style =~ s/semi ?(expanded|extended|expd)/SemiExpanded/i;
861   $style =~ s/[sd]emi ?light/SemiLight/i;
862   $style =~ s/ultra ?(expanded|extended|expd)/UltraExpanded/i;
863   $style =~ s/light/Light /i;
864   $style =~ s/\blt\b/Light /i;
865   $style =~ s/(ultra|extra)(light|lt)/ExtraLight /i;
866   $style =~ s/\bheavy\b/Extrabold/i;
867   $style =~ s/\bhairline\b/Extralight/i;
868   $style =~ s/\bcond\b/Condensed/i;
869   $style =~ s/(roman)?slanted/ Italic/i;
870   $style =~ s/\bslant\b/Italic/i;
871   $style =~ s/\b(SC|Small(caps(alt)?)?)\b/SmallCaps/i;
872   $style =~ s/w3 mono/Dual/i;
873   $style =~ s/Regul[ea]r/Regular/i;
874   $style =~ s/Megablack/ExtraBlack/i;
875   $style =~ s/  +/ /g;
876   return($style);
877 }
878
879 # return list of unicode values of the input string
880 #Allow input of intervals (e.g. 'a-z')
881 sub decimalUnicode($)
882 {
883   my ($a) = @_;
884   my @res = ();
885   # Convert to unicode chars first
886   while ($a =~ /^(.*)u\+(0?x[\da-f]+|\d+)(.*)$/i) {
887     my ($prev, $d, $post) = ($1, $2, $3);
888     if ($d =~ /^0?x(.+)$/) {
889       $d = hex($1);
890     }
891     my $chr = encode('utf-8', chr($d));
892     $a = $prev . $chr . $post;
893   }
894   # $a is now a string of unicode chars
895   my $u = decode('utf-8', $a);
896   my @a = split(//, $u);
897   my $interval = 0;
898   my $start = undef;
899   for my $x (@a) {
900     if ($x eq '-') {    # Interval
901       $interval = 1;
902       next;
903     }
904     if ($interval && defined($start)) {
905       if (ord($x) < $start) {
906         for (my $i = $start - 1; $i >= ord($x); $i--) {
907           push(@res, $i);
908         }
909       }
910       else {
911         for (my $i = $start + 1; $i <= ord($x); $i++) {
912           push(@res, $i);
913         }
914       }
915       $start = undef;
916     }
917     else {
918       $start = ord($x);
919       push(@res, $start);
920     }
921     $interval = 0;
922   }
923   return(@res);
924 }
925
926
927 # check if the glyph-values in interval @{$ri} are contained
928 # in one of the (sorted) intervals
929 sub contains($$)
930 {
931   # ok if
932   # ...re0..........re1...
933   # ......start..end......
934   my ($ri, $rList) = @_;
935   my $start = $ri->[0];
936   my $end = $ri->[1];
937
938   for my $re (@{$rList}) {
939     next if ($re->[1] < $start);
940     # now we found a possible matching interval
941     return 1 if (($start >= $re->[0]) && ($end <= $re->[1]));
942     return 0;
943   }
944   return 0;
945 }
946
947 sub sprintIntervalls($)
948 {
949   my ($rList) = @_;
950   my @out = ();
951   for my $rE (@{$rList}) {
952     if ($rE->[0] != $rE->[1]) {
953       push(@out, $rE->[0] . '-' . $rE->[1]);
954     }
955     else {
956       push(@out, $rE->[0]);
957     }
958   }
959   return join(',', @out);
960 }