source: git/lib/afm2txf.pl @ b35c4bc

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since b35c4bc was 64ba8bb, checked in by Olly Betts <olly@…>, 13 years ago

lib/afm2txf.pl: Fix handling of PostScript? character names which are
used by more than one Unicode value ("space" and "hyphen" are the
current examples). This fixes the colour key for date to have dashes
in the dates. Fix not to bother writing empty space into the texture
map for spaces - just set width and height to 0 and skip to the
width.
src/fnt.cc: Remove special case kludge for if space isn't defined,
which is no longer needed thanks to the above fix.

git-svn-id: file:///home/survex-svn/survex/trunk@3794 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

  • Property mode set to 100755
File size: 10.5 KB
Line 
1#!/usr/bin/perl -w
2
3# afm2txf.pl 0.2
4#
5# Generates .txf font textures from Type 1 fonts
6# Requirements: Ghostscript, ImageMagick
7#
8# Usage:
9#       afm2txf.pl [-o OUTPUT.txf] whatever.afm
10#
11# Changelog:
12#       0.2 (06/28/2002): Generate fonts with proper padding
13#       0.1 (06/28/2002): Initial version
14#
15# Copyright (C) 2002 Andrew James Ross
16# Copyright (C) 2010,2011 Olly Betts
17#
18# This program is free software; you can redistribute it and/or modify
19# it under the terms of the GNU General Public License version 2 as
20# published by the Free Software Foundation.
21
22use strict;
23
24my $output;
25if (scalar @ARGV >= 1) {
26    my $arg = $ARGV[0];
27    if ($arg =~ s/^-o//) {
28        shift;
29        if ($arg eq '') {
30            if (scalar @ARGV == 0) {
31                die "-o needs an argument\n";
32            }
33            $output = shift;
34        } else {
35            $output = $arg;
36        }
37    }
38}
39
40my $METRICS = shift or die; # AFM file
41
42# Texture size
43my $TEXSIZ = 256;
44
45# Padding around each character, for mipmap separation
46my $PADDING = 4;
47
48# Antialiasing multiplier.  Should be 16 for production work.  As low
49# as 4 works well for testing.
50my $DOWNSAMPLE = 16;
51
52# The printable ISO-8859-1 characters (and space and hard space) and their
53# postscript glyph names.  We use names because not all postscript
54# fonts are encoded using ASCII.  AFM metrics generated by ttf2afm, in
55# fact, don't have any numerical character IDs at all.  In principle,
56# this mechanism will work for any 8 bit font encoding, you just have
57# to do the legwork of figuring out the name to ID mapping.
58my %REVCHARS = (32=>'space', 33=>'exclam', 34=>'quotedbl',
59             35=>'numbersign', 36=>'dollar', 37=>'percent',
60             38=>'ampersand', 39=>'quotesingle', 40=>'parenleft',
61             41=>'parenright', 42=>'asterisk', 43=>'plus',
62             44=>'comma', 45=>'hyphen', 46=>'period', 47=>'slash',
63             48=>'zero', 49=>'one', 50=>'two', 51=>'three',
64             52=>'four', 53=>'five', 54=>'six', 55=>'seven',
65             56=>'eight', 57=>'nine', 58=>'colon', 59=>'semicolon',
66             60=>'less', 61=>'equal', 62=>'greater', 63=>'question',
67             64=>'at', 65=>'A', 66=>'B', 67=>'C', 68=>'D', 69=>'E',
68             70=>'F', 71=>'G', 72=>'H', 73=>'I', 74=>'J', 75=>'K',
69             76=>'L', 77=>'M', 78=>'N', 79=>'O', 80=>'P', 81=>'Q',
70             82=>'R', 83=>'S', 84=>'T', 85=>'U', 86=>'V', 87=>'W',
71             88=>'X', 89=>'Y', 90=>'Z', 91=>'bracketleft',
72             92=>'backslash', 93=>'bracketright', 94=>'asciicircum',
73             95=>'underscore', 96=>'quoteleft', 97=>'a', 98=>'b', 99=>'c',
74             100=>'d', 101=>'e', 102=>'f', 103=>'g', 104=>'h',
75             105=>'i', 106=>'j', 107=>'k', 108=>'l', 109=>'m',
76             110=>'n', 111=>'o', 112=>'p', 113=>'q', 114=>'r',
77             115=>'s', 116=>'t', 117=>'u', 118=>'v', 119=>'w',
78             120=>'x', 121=>'y', 122=>'z', 123=>'braceleft',
79             124=>'bar', 125=>'braceright', 126=>'asciitilde',
80             160=>'space', 161=>'exclamdown', 162=>'cent', 163=>'sterling',
81             164=>'currency', 165=>'yen', 166=>'brokenbar', 167=>'section',
82             168=>'dieresis', 169=>'copyright', 170=>'ordfeminine', 171=>'guillemotleft',
83             172=>'logicalnot', 173=>'hyphen', 174=>'registered', 175=>'macron',
84             176=>'degree', 177=>'plusminus', 178=>'twosuperior', 179=>'threesuperior',
85             180=>'acute', 181=>'mu', 182=>'paragraph', 183=>'bullet',
86             184=>'cedilla', 185=>'onesuperior', 186=>'ordmasculine', 187=>'guillemotright',
87             188=>'onequarter', 189=>'onehalf', 190=>'threequarters', 191=>'questiondown',
88             192=>'Agrave', 193=>'Aacute', 194=>'Acircumflex', 195=>'Atilde',
89             196=>'Adieresis', 197=>'Aring', 198=>'AE', 199=>'Ccedilla',
90             200=>'Egrave', 201=>'Eacute', 202=>'Ecircumflex', 203=>'Edieresis',
91             204=>'Igrave', 205=>'Iacute', 206=>'Icircumflex', 207=>'Idieresis',
92             208=>'Eth', 209=>'Ntilde', 210=>'Ograve', 211=>'Oacute',
93             212=>'Ocircumflex', 213=>'Otilde', 214=>'Odieresis', 215=>'multiply',
94             216=>'Oslash', 217=>'Ugrave', 218=>'Uacute', 219=>'Ucircumflex',
95             220=>'Udieresis', 221=>'Yacute', 222=>'Thorn', 223=>'germandbls',
96             224=>'agrave', 225=>'aacute', 226=>'acircumflex', 227=>'atilde',
97             228=>'adieresis', 229=>'aring', 230=>'ae', 231=>'ccedilla',
98             232=>'egrave', 233=>'eacute', 234=>'ecircumflex', 235=>'edieresis',
99             236=>'igrave', 237=>'iacute', 238=>'icircumflex', 239=>'idieresis',
100             240=>'eth', 241=>'ntilde', 242=>'ograve', 243=>'oacute',
101             244=>'ocircumflex', 245=>'otilde', 246=>'odieresis', 247=>'divide',
102             248=>'oslash', 249=>'ugrave', 250=>'uacute', 251=>'ucircumflex',
103             252=>'udieresis', 253=>'yacute', 254=>'thorn', 255=>'ydieresis'
104             );
105
106my %CHARS;
107while (my ($k, $v) = each %REVCHARS) {
108    $CHARS{$v} = $k unless exists $CHARS{$v};
109}
110
111my %metrics = ();
112my %positions = ();
113
114#
115# Parse the font metrics.  This is a 5 element array.  All numbers are
116# expressed as a fraction of the line spacing.
117# 0:    nominal width (distance to the next character)
118# 1, 2: Coordinates of the lower left corner of the bounding box,
119#       relative to the nominal position.
120# 3, 4: Size of the bounding box
121#
122print STDERR "Reading font metrics...\n";
123my $FONT;
124open METRICS, '<', $METRICS or die $!;
125my $m;
126while (defined($m = <METRICS>)) {
127    if ($m =~ /^FontName (\S*)/) { $FONT = $1; next; }
128    next unless $m =~ /^C /;
129    chomp $m;
130
131    die "No name: $m" if $m !~ /N\s+([^\s]+)\s+;/;
132    my $name = $1;
133
134    die "No box: $m"
135        if $m !~ /B\s+([-0-9]+)\s+([-0-9]+)\s+([-0-9]+)\s+([-0-9]+)\s+;/;
136    my ($left, $bottom, $right, $top) = ($1/1000, $2/1000, $3/1000, $4/1000);
137
138    die "No width: $m" if $m !~ /WX\s+([-0-9]+)/;
139    my $nomwid = $1/1000; # nominal, not physical width!
140
141    # The coordinates of the corner relative to the character
142    # "position"
143    my ($x, $y) = (-$left, -$bottom);
144    my ($w, $h) = ($right-$left, $top-$bottom);
145
146    $metrics{$name} = [$nomwid, $x, $y, $w, $h];
147}
148close METRICS;
149
150die "No FontName found in metrics" if not defined $FONT;
151
152# Sanitise $FONT.
153$FONT =~ s!/!_!g;
154
155#
156# Find the height of the tallest character, and print some warnings
157#
158my $maxhgt = 0;
159foreach my $c (keys %CHARS) {
160    if(!defined $metrics{$c}) {
161        print STDERR "% WARNING: no metrics for char $c.  Skipping.\n";
162        next;
163    }
164    if($metrics{$c}->[4] > $maxhgt) { $maxhgt = $metrics{$c}->[4]; }
165}
166if($maxhgt == 0) {
167    print STDERR "No usable characters found.  Bailing out.\n";
168    exit 1;
169}
170
171#
172# Do the layout.  Keep increasing the row count until the characters
173# just fit.  This isn't terribly elegant, but it's simple.
174#
175print STDERR "Laying out";
176my $rows = 1;
177my $PS;
178my $LINEHGT;
179while(!defined ($PS = genPostscript($rows))) { $rows++; }
180print STDERR " ($rows rows)\n";
181
182#
183# Call ghostscript to render
184#
185print STDERR "Rendering Postscript...\n";
186my $res = $TEXSIZ * $DOWNSAMPLE;
187my $pid = open PS, "|gs -r$res -g${res}x${res} -sDEVICE=ppm -sOutputFile=\Q$FONT\E.ppm > /dev/null";
188die "Couldn't spawn ghostscript interpreter" if !defined $pid;
189foreach (@$PS) {
190    print PS "$_\n";
191}
192close PS;
193waitpid($pid, 0);
194
195#
196# Downsample with ImageMagick
197#
198print STDERR "Antialiasing image...\n";
199system("mogrify -geometry ${TEXSIZ}x${TEXSIZ} \Q$FONT\E.ppm") == 0
200    or die "Couldn't rescale $FONT.ppm";
201
202#
203# Generate the .txf file
204#
205print STDERR "Generating textured font file...\n";
206
207# Prune undefined glyphs
208foreach my $c (keys %metrics) {
209    delete $metrics{$c} if !defined $CHARS{$c};
210}
211
212sub round { sprintf "%.0f", $_[0] }
213$output = "$FONT.txf" unless defined $output;
214open TXF, '>', $output or die;
215print TXF pack "V", 0x667874ff;
216print TXF pack "V", 0x12345678;
217print TXF pack "V", 0;
218print TXF pack "V", $TEXSIZ;
219print TXF pack "V", $TEXSIZ;
220print TXF pack "V", round($TEXSIZ * $LINEHGT);
221print TXF pack "V", 0;
222my @chars = sort keys %REVCHARS;
223print TXF pack "V", scalar @chars;
224foreach my $c (@chars) {
225    my $name = $REVCHARS{$c};
226    my $m = $metrics{$name};
227    my $p = $positions{$name};
228    my $step = round($m->[0] * $LINEHGT * $TEXSIZ);
229
230    # Pad the bounding box, to handle areas that outside.  This can
231    # happen due to thick lines in the font path, or be an artifact of
232    # the downsampling.
233    my ($w, $h, $xoff, $yoff, $x, $y);
234    if ($name eq 'space') {
235        $w = $h = $xoff = $yoff = $x = $y = 0;
236    } else {
237        $w = round($m->[3] * $LINEHGT * $TEXSIZ + 2*$PADDING);
238        $h = round($m->[4] * $LINEHGT * $TEXSIZ + 2*$PADDING);
239        $xoff = -round($m->[1] * $LINEHGT * $TEXSIZ) - $PADDING;
240        $yoff = -round($m->[2] * $LINEHGT * $TEXSIZ) - $PADDING;
241        $x = round($p->[0] * $TEXSIZ - $PADDING);
242        $y = round($p->[1] * $TEXSIZ - $PADDING);
243    }
244    print TXF pack "v", $c;
245    print TXF pack "C", $w;
246    print TXF pack "C", $h;
247    print TXF pack "c", $xoff;
248    print TXF pack "c", $yoff;
249    print TXF pack "C", $step;
250    print TXF pack "C", 0;
251    print TXF pack "v", $x;
252    print TXF pack "v", $y;
253}
254
255# Read in the .ppm file, dump the duplicate color values (ghostscript
256# won't generate pgm's) and write to the end of the .txf.  Remember to
257# swap the order of the rows; OpenGL textures are bottom-up.
258open PPM, "$FONT.ppm" or die;
259my $pixel;
260foreach my $r (1 .. $TEXSIZ) {
261    seek PPM, -3*$r*$TEXSIZ, 2 or die;
262    foreach (1 .. $TEXSIZ) {
263        read PPM, $pixel, 3 or die;
264        print TXF substr($pixel, 0, 1);
265    }
266}
267close PPM;
268close TXF;
269
270# Clean up
271unlink("$FONT.ppm");
272
273########################################################################
274########################################################################
275########################################################################
276
277sub genPostscript {
278    my $rows = shift;
279    my $rowhgt = 1/$rows;
280
281    my @PS = ();
282
283    # The canonical "point size" number, in texture space
284    $LINEHGT = ($rowhgt - 2*$PADDING/$TEXSIZ) / $maxhgt;
285
286    # Get to where we want.  Draw the whole thing in a 1 inch square at
287    # the bottom left of the "page".
288    push @PS, "72 72 scale";
289
290    # Fill the square with black
291    push @PS, "0 setgray";
292    push @PS, "-1 -1 moveto";
293    push @PS, "-1 1 lineto 1 1 lineto 1 -1 lineto";
294    push @PS, "closepath";
295    push @PS, "fill";
296
297    # Draw in white
298    push @PS, "1 setgray";
299
300    # Generate our PUSH @PS, font
301    push @PS, "/$FONT findfont $LINEHGT scalefont setfont";
302
303    my $x = $PADDING/$TEXSIZ;
304    my $y = 1 - $rowhgt + $PADDING/$TEXSIZ;
305    my @chars = sort { $CHARS{$a} <=> $CHARS{$b} } (keys %CHARS);
306    foreach my $c (@chars) {
307        next if $c eq 'space';
308        my $m = $metrics{$c};
309        next if !defined $m;
310
311        # No space?
312        my $w = $m->[3]*$LINEHGT;
313        if($x + $w + $PADDING/$TEXSIZ > 1) {
314            $x = $PADDING/$TEXSIZ;
315            $y -= $rowhgt;
316            return undef if $y < 0;
317        }
318
319        # Record where in the texture the box ended up
320        $positions{$c} = [$x, $y];
321
322        my $vx = $x + $m->[1]*$LINEHGT;
323        my $vy = $y + $m->[2]*$LINEHGT;
324
325        push @PS, "$vx $vy moveto";
326        push @PS, "/$c glyphshow";
327
328        # Next box...
329        $x += $w + 2*$PADDING/$TEXSIZ;
330    }
331
332    push @PS, "showpage";
333
334    return \@PS;
335}
Note: See TracBrowser for help on using the repository browser.