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