4 # this file is part of mftrace - a tool to generate scalable fonts from MF sources
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License version 2
9 # as published by the Free Software Foundation
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Library General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 # Copyright (c) 2001--2006 by
22 # Han-Wen Nienhuys, Jan Nieuwenhuizen
35 localedir
= datadir
+ '/locale'
37 exec_prefix
= '@exec_prefix@'
39 def interpolate (str):
40 str = string
.replace (str, '{', '(')
41 str = string
.replace (str, '}', ')s')
42 str = string
.replace (str, '$', '%')
45 if prefix
!= '@' + 'prefix@':
46 exec_prefix
= interpolate (exec_prefix
) % vars ()
47 bindir
= interpolate (bindir
) % vars ()
48 datadir
= os
.path
.join (interpolate (datadir
) % vars (), 'mftrace')
49 libdir
= interpolate (libdir
) % vars ()
51 if datadir
== '@' + "datadir" + "@":
52 datadir
= os
.getcwd ()
55 sys
.path
.append (datadir
)
60 errorport
= sys
.stderr
62 ################################################################
63 # lilylib.py -- options and stuff
65 # source file of the GNU LilyPond music typesetter
69 gettext
.bindtextdomain ('mftrace', localedir
)
70 gettext
.textdomain ('mftrace')
76 def shell_escape_filename (str):
77 str = re
.sub ('([\'" ])', r
'\\\1', str)
81 port
.write ('%s %s\n' % (program_name
, program_version
))
85 sys
.stdout
.write ('\n')
86 sys
.stdout
.write (_ ('Copyright (c) %s by' % ' 2001--2004'))
87 sys
.stdout
.write ('\n')
88 sys
.stdout
.write (' Han-Wen Nienhuys')
89 sys
.stdout
.write (' Jan Nieuwenhuizen')
90 sys
.stdout
.write ('\n')
91 sys
.stdout
.write (_ (r
'''
92 Distributed under terms of the GNU General Public License. It comes with
94 sys
.stdout
.write ('\n')
100 errorport
.write (_ ("warning: ") + s
)
103 '''Report the error S and exit with an error status of 1.
111 errorport
.write (_ ("error: ") + s
+ '\n')
112 errorport
.write (_ ("Exiting ...") + '\n')
117 def __init__ (self
, name
=None):
120 if not os
.path
.isdir (name
):
124 self
.dir = tempfile
.mkdtemp ()
130 shutil
.rmtree (self
.dir)
140 def setup_temp (name
):
143 temp_dir
= TempDirectory (name
)
146 def popen (cmd
, mode
= 'r', ignore_error
= 0):
148 progress (_ ("Opening pipe `%s\'") % cmd
)
149 pipe
= os
.popen (cmd
, mode
)
154 def system (cmd
, ignore_error
= 0):
155 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
163 progress (_ ("Invoking `%s\'\n") % cmd
)
166 name
= re
.match ('[ \t]*([^ \t]*)', cmd
).group (1)
167 msg
= name
+ ': ' + _ ("command exited with value %d") % st
169 warning (msg
+ ' ' + _ ("(ignored)") + ' ')
176 def strip_extension (f
, ext
):
177 (p
, e
) = os
.path
.splitext (f
)
183 ################################################################
191 program_name
= 'mftrace'
193 program_version
= '@VERSION@'
194 origdir
= os
.getcwd ()
199 'TeX typewriter text': '09fbbfac.enc', # cmtt10
200 'TeX math symbols': '10037936.enc', # cmbsy
201 'ASCII caps and digits': '1b6d048e', # cminch
202 'TeX math italic': 'aae443f0.enc', # cmmi10
203 'TeX extended ASCII': 'd9b29452.enc',
204 'TeX text': 'f7b6d320.enc',
205 'TeX text without f-ligatures': '0ef0afca.enc',
206 'Extended TeX Font Encoding - Latin': 'tex256.enc',
209 'fetaBraces': 'feta-braces-a.enc',
210 'fetaNumber': 'feta-nummer10.enc',
211 'fetaMusic': 'feta20.enc',
212 'parmesanMusic': 'parmesan20.enc',
217 for d
in include_dirs
:
218 p
= os
.path
.join (d
, nm
)
221 return os
.path
.abspath (p
)
225 p
= popen ('kpsewhich %s' % shell_escape_filename (nm
)).read ()
228 if options
.dos_kpath
:
231 p
= re
.sub ('^([a-z]):', '/cygdrive/\\1', p
)
232 p
= re
.sub ('\\\\', '/', p
)
233 sys
.stderr
.write ("Got `%s' from kpsewhich, using `%s'\n" % (orig
, p
))
241 ################################################################
243 ################################################################
245 def autotrace_command (fn
, opts
):
246 opts
= " " + opts
+ " --background-color=FFFFFF --output-format=eps --input-format=pbm "
247 return options
.trace_binary
+ opts
+ backend_options \
248 + " --output-file=char.eps %s " % fn
250 def potrace_command (fn
, opts
):
251 return options
.trace_binary
+ opts \
252 + ' -u %d ' % options
.grid_scale \
254 + " -q -c --eps --output=char.eps %s " % (fn
)
257 path_to_type1_ops
= None
259 def trace_one (pbmfile
, id):
261 Run tracer, do error handling
264 status
= system (trace_command (pbmfile
, ''), 1)
267 sys
.stderr
.write ("\nUser interrupt. Exiting\n")
270 if status
== 0 and options
.keep_temp_dir
:
271 shutil
.copy2 (pbmfile
, '%s.pbm' % id)
272 shutil
.copy2 ('char.eps', '%s.eps' % id)
275 error_file
= os
.path
.join (origdir
, 'trace-bug-%s.pbm' % id)
276 shutil
.copy2 (pbmfile
, error_file
)
277 msg
= """Trace failed on bitmap. Bitmap left in `%s\'
282 Please submit a bugreport to %s development.""" \
283 % (error_file
, trace_command (error_file
, ''), options
.trace_binary
)
285 if options
.keep_trying
:
287 sys
.stderr
.write ("\nContinuing trace...\n")
290 msg
= msg
+ '\nRun mftrace with --keep-trying to produce a font anyway\n'
296 warning ("Failed, skipping character.\n")
301 def make_pbm (filename
, outname
, char_number
):
302 """ Extract bitmap from the PK file FILENAME (absolute) using `gf2pbm'.
303 Return FALSE if the glyph is not valid.
306 command
= "%s/gf2pbm -n %d -o %s %s" % (bindir
, char_number
, outname
, filename
)
307 status
= system (command
, ignore_error
= 1)
310 def read_encoding (file):
311 sys
.stderr
.write (_ ("Using encoding file: `%s'\n") % file)
313 str = open (file).read ()
314 str = re
.sub ("%.*", '', str)
315 str = re
.sub ("[\n\t \f]+", ' ', str)
316 m
= re
.search ('/([^ ]+) \[([^\]]+)\] def', str)
318 error ("Encoding file is invalid")
322 cod
= re
.sub ('[ /]+', ' ', cod
)
323 cods
= string
.split (cod
)
327 def zip_to_pairs (xs
):
330 r
.append ((xs
[0], xs
[1]))
334 def unzip_pairs (tups
):
337 lst
= lst
+ list (tups
[0])
341 def autotrace_path_to_type1_ops (at_file
, bitmap_metrics
, tfm_wid
, magnification
):
342 inv_scale
= 1000.0 / magnification
344 (size_y
, size_x
, off_x
, off_y
) = map (lambda m
, s
= inv_scale
: m
* s
,
346 ls
= open (at_file
).readlines ()
347 bbox
= (10000, 10000, -10000, -10000)
349 while ls
and ls
[0] != '*u\n':
360 while ls
[0] != '*U\n':
364 toks
= string
.split (ell
)
369 args
= map (lambda m
, s
= inv_scale
: s
* float (m
),
371 if options
.round_to_int
:
372 args
= zip_to_pairs (map (round, args
))
374 args
= zip_to_pairs (args
)
375 commands
.append ((cmd
, args
))
385 cy
= size_y
- off_y
- inv_scale
387 # t1asm seems to fuck up when using sbw. Oh well.
388 t1_outline
= ' %d %d hsbw\n' % (- off_x
, tfm_wid
)
389 bbox
= (10000, 10000, -10000, -10000)
391 for (c
, args
) in commands
:
396 if c
== 'l' or c
== 'c':
397 bbox
= update_bbox_with_point (bbox
, a
)
399 na
.append ((nx
- cx
, ny
- cy
))
404 if options
.round_to_int
:
405 a
= map (lambda x
: '%d' % int (round (x
)),
408 a
= map (lambda x
: '%d %d div' \
409 % (int (round (x
* options
.grid_scale
/inv_scale
)),
410 int (round (options
.grid_scale
/inv_scale
))),
413 t1_outline
= t1_outline
+ ' %s %s\n' % (string
.join (a
), c
)
415 t1_outline
= t1_outline
+ ' endchar '
416 t1_outline
= '{\n %s } |- \n' % t1_outline
418 return (bbox
, t1_outline
)
420 # FIXME: Cut and paste programming
421 def potrace_path_to_type1_ops (at_file
, bitmap_metrics
, tfm_wid
, magnification
):
422 inv_scale
= 1000.0 / magnification
424 (size_y
, size_x
, off_x
, off_y
) = map (lambda m
,
425 s
= inv_scale
: m
* s
,
427 ls
= open (at_file
).readlines ()
428 bbox
= (10000, 10000, -10000, -10000)
430 while ls
and ls
[0] != '0 setgray\n':
438 while ls
and ls
[0] != 'grestore\n':
445 toks
= string
.split (ell
)
450 args
= map (lambda m
, s
= inv_scale
: s
* float (m
),
452 args
= zip_to_pairs (args
)
453 commands
.append ((cmd
, args
))
455 # t1asm seems to fuck up when using sbw. Oh well.
456 t1_outline
= ' %d %d hsbw\n' % (- off_x
, tfm_wid
)
457 bbox
= (10000, 10000, -10000, -10000)
459 # Type1 fonts have relative coordinates (doubly relative for
460 # rrcurveto), so must convert moveto and rcurveto.
462 z
= (0.0, size_y
- off_y
- 1.0)
463 for (c
, args
) in commands
:
464 args
= map (lambda x
: (x
[0] * (1.0 / options
.grid_scale
),
465 x
[1] * (1.0 / options
.grid_scale
)), args
)
468 args
= [(args
[0][0] - z
[0], args
[0][1] - z
[1])]
472 lz
= (z
[0] + a
[0], z
[1] + a
[1])
473 bbox
= update_bbox_with_point (bbox
, lz
)
476 if options
.round_to_int
:
477 last_discr_z
= (int (round (z
[0])), int (round (z
[1])))
479 last_discr_z
= (z
[0], z
[1])
482 if options
.round_to_int
:
483 a
= (int (round (a
[0])), int (round (a
[1])))
486 args
.append ((a
[0] - last_discr_z
[0],
487 a
[1] - last_discr_z
[1]))
495 'closepath': 'closepath',
497 'rcurveto': 'rrcurveto',
500 'rlineto': 'rlineto',
505 t1_outline
+= ' closepath '
507 if options
.round_to_int
:
508 args
= map (lambda x
: '%d' % int (round (x
)),
511 args
= map (lambda x
: '%d %d div' \
512 % (int (round (x
*options
.grid_scale
/inv_scale
)),
513 int (round (options
.grid_scale
/inv_scale
))),
516 t1_outline
= t1_outline
+ ' %s %s\n' % (string
.join (args
), c
)
518 t1_outline
= t1_outline
+ ' endchar '
519 t1_outline
= '{\n %s } |- \n' % t1_outline
521 return (bbox
, t1_outline
)
523 def read_gf_dims (name
, c
):
524 str = popen ('%s/gf2pbm -n %d -s %s' % (bindir
, c
, name
)).read ()
525 m
= re
.search ('size: ([0-9]+)+x([0-9]+), offset: \(([0-9-]+),([0-9-]+)\)', str)
527 return tuple (map (int, m
.groups ()))
529 def trace_font (fontname
, gf_file
, metric
, glyphs
, encoding
,
530 magnification
, fontinfo
):
532 font_bbox
= (10000, 10000, -10000, -10000)
534 progress (_ ("Tracing bitmaps..."))
541 # for single glyph testing.
544 if encoding
[a
] == ".notavail":
546 valid
= metric
.has_char (a
)
548 encoding
[a
] = ".notavail"
551 valid
= make_pbm (gf_file
, 'char.pbm', a
)
553 encoding
[a
] = ".notavail"
556 (w
, h
, xo
, yo
) = read_gf_dims (gf_file
, a
)
558 if not options
.verbose
:
559 sys
.stderr
.write ('[%d' % a
)
562 # this wants the id, not the filename.
563 success
= trace_one ("char.pbm", '%s-%d' % (options
.gffile
, a
))
565 sys
.stderr
.write ("(skipping character)]")
567 encoding
[a
] = ".notavail"
570 if not options
.verbose
:
571 sys
.stderr
.write (']')
573 metric_width
= metric
.get_char (a
).width
574 tw
= int (round (metric_width
/ metric
.design_size
* 1000))
575 (bbox
, t1o
) = path_to_type1_ops ("char.eps", (h
, w
, xo
, yo
),
579 encoding
[a
] = ".notavail"
582 font_bbox
= update_bbox_with_bbox (font_bbox
, bbox
)
584 t1os
.append ('\n/%s %s ' % (encoding
[a
], t1o
))
586 if not options
.verbose
:
588 to_type1 (t1os
, font_bbox
, fontname
, encoding
, magnification
, fontinfo
)
590 def ps_encode_encoding (encoding
):
591 str = ' %d array\n0 1 %d {1 index exch /.notdef put} for\n' \
592 % (len (encoding
), len (encoding
)-1)
594 for i
in range (0, len (encoding
)):
595 if encoding
[i
] != ".notavail":
596 str = str + 'dup %d /%s put\n' % (i
, encoding
[i
])
601 def gen_unique_id (dict):
603 return 4000000 + (hash (nm
) % 1000000)
605 def to_type1 (outlines
, bbox
, fontname
, encoding
, magnification
, fontinfo
):
608 Fill in the header template for the font, append charstrings,
609 and shove result through t1asm
611 template
= r
"""%%!PS-AdobeFont-1.0: %(FontName)s %(VVV)s.%(WWW)s
613 /FontInfo 16 dict dup begin
614 /version (%(VVV)s.%(WWW)s) readonly def
615 /Notice (%(Notice)s) readonly def
616 /FullName (%(FullName)s) readonly def
617 /FamilyName (%(FamilyName)s) readonly def
618 /Weight (%(Weight)s) readonly def
619 /ItalicAngle %(ItalicAngle)s def
620 /isFixedPitch %(isFixedPitch)s def
621 /UnderlinePosition %(UnderlinePosition)s def
622 /UnderlineThickness %(UnderlineThickness)s def
624 /FontName /%(FontName)s def
627 /FontMatrix [%(xrevscale)f 0 0 %(yrevscale)f 0 0] readonly def
628 /FontBBox {%(llx)d %(lly)d %(urx)d %(ury)d} readonly def
629 /Encoding %(Encoding)s readonly def
632 dup /Private 20 dict dup begin
633 /-|{string currentfile exch readstring pop}executeonly def
634 /|-{noaccess def}executeonly def
635 /|{noaccess put}executeonly def
638 /MinFeature {16 16} |-
640 /OtherSubrs [ {} {} {} {} ] |-
646 /CharStrings %(CharStringsLen)d dict dup begin
650 /.notdef { 0 0 hsbw endchar } |-
655 dup/FontName get exch definefont
656 pop mark currentfile closefile
659 ## apparently, some fonts end the file with cleartomark. Don't know why.
661 copied_fields
= ['FontName', 'FamilyName', 'FullName', 'DesignSize',
662 'ItalicAngle', 'isFixedPitch', 'Weight']
667 'Notice': 'Generated from MetaFont bitmap by mftrace %s, http://www.xs4all.nl/~hanwen/mftrace/ ' % program_version
,
668 'UnderlinePosition': '-100',
669 'UnderlineThickness': '50',
670 'xrevscale': 1.0/1000.0,
671 'yrevscale': 1.0/1000.0,
676 'Encoding': ps_encode_encoding (encoding
),
678 # need one extra entry for .notdef
679 'CharStringsLen': len (outlines
) + 1,
680 'CharStrings': string
.join (outlines
),
681 'CharBBox': '0 0 0 0',
684 for k
in copied_fields
:
685 vars[k
] = fontinfo
[k
]
687 open ('mftrace.t1asm', 'w').write (template
% vars)
689 def update_bbox_with_point (bbox
, pt
):
690 (llx
, lly
, urx
, ury
) = bbox
691 llx
= min (pt
[0], llx
)
692 lly
= min (pt
[1], lly
)
693 urx
= max (pt
[0], urx
)
694 ury
= max (pt
[1], ury
)
696 return (llx
, lly
, urx
, ury
)
698 def update_bbox_with_bbox (bb
, dims
):
699 (llx
, lly
, urx
, ury
) = bb
700 llx
= min (llx
, dims
[0])
701 lly
= min (lly
, dims
[1])
702 urx
= max (urx
, dims
[2])
703 ury
= max (ury
, dims
[3])
705 return (llx
, lly
, urx
, ury
)
707 def get_binary (name
):
708 search_path
= string
.split (os
.environ
['PATH'], ':')
709 for p
in search_path
:
710 nm
= os
.path
.join (p
, name
)
711 if os
.path
.exists (nm
):
716 def get_fontforge_command ():
718 for ff
in ['fontforge', 'pfaedit']:
724 stat
= system ("%s -usage > pfv 2>&1 " % fontforge_cmd
,
728 warning ("Command `%s -usage' failed. Cannot simplify or convert to TTF.\n" % fontforge_cmd
)
731 if fontforge_cmd
== 'pfaedit' \
732 and re
.search ("-script", open ('pfv').read ()) == None:
733 warning ("pfaedit does not support -script. Install 020215 or later.\nCannot simplify or convert to TTF.\n")
737 def tfm2kpx (tfmname
, encoding
):
739 pl
= popen ("tftopl %s" % (tfmname
))
741 label_pattern
= re
.compile (
742 "\A \(LABEL ([DOHC]{1}) ([A-Za-z0-9]*)\)")
743 krn_pattern
= re
.compile (
744 "\A \(KRN ([DOHC]{1}) ([A-Za-z0-9]*) R (-?[\d\.]+)\)")
749 for line
in pl
.readlines ():
751 label_match
= label_pattern
.search (line
)
752 if not (label_match
is None):
753 if label_match
.group (1) == "D":
754 first
= int (label_match
.group (2))
755 elif label_match
.group (1) == "O":
756 first
= int (label_match
.group (2), 8)
757 elif label_match
.group (1) == "C":
758 first
= ord (label_match
.group (2))
760 krn_match
= krn_pattern
.search (line
)
761 if not (krn_match
is None):
762 if krn_match
.group (1) == "D":
763 second
= int (krn_match
.group (2))
764 elif krn_match
.group (1) == "O":
765 second
= int (krn_match
.group (2), 8)
766 elif krn_match
.group (1) == "C":
767 second
= ord (krn_match
.group (2))
769 krn
= round (float (krn_match
.group (3)) * 1000)
771 if (encoding
[first
] != '.notavail' and
772 encoding
[first
] != '.notdef' and
773 encoding
[second
] != '.notavail' and
774 encoding
[second
] != '.notdef'):
776 kpx_lines
.append ("KPX %s %s %d\n" % (
777 encoding
[first
], encoding
[second
], krn
))
781 def get_afm (t1_path
, tfmname
, encoding
, out_path
):
782 afm_stream
= popen ("printafm %s" % (t1_path
))
784 kpx_lines
= tfm2kpx (tfmname
, encoding
)
786 for line
in afm_stream
.readlines ():
787 afm_lines
.append (line
)
789 if re
.match (r
"^EndCharMetrics", line
, re
.I
):
790 afm_lines
.append ("StartKernData\n")
791 afm_lines
.append ("StartKernPairs %d\n" % len (kpx_lines
))
793 for kpx_line
in kpx_lines
:
794 afm_lines
.append (kpx_line
)
796 afm_lines
.append ("EndKernPairs\n")
797 afm_lines
.append ("EndKernData\n")
799 progress (_ ("Writing metrics to `%s'... ") % out_path
)
800 afm_file
= open (out_path
, 'w')
801 afm_file
.writelines (afm_lines
)
807 def assemble_font (fontname
, format
, is_raw
):
817 outname
= fontname
+ ext
819 progress (_ ("Assembling raw font to `%s'... ") % outname
)
822 system ('t1asm %s mftrace.t1asm %s' % (asm_opt
, shell_escape_filename (outname
)))
826 def make_outputs (fontname
, formats
, encoding
):
828 run pfaedit to convert to other formats
834 if (options
.simplify
or options
.round_to_int
or 'ttf' in formats
or 'svg' in formats
or 'afm' in formats
):
837 ff_command
= get_fontforge_command ()
839 if ff_needed
and ff_command
:
840 raw_name
= assemble_font (fontname
, 'pfa', 1)
843 if options
.round_to_int
:
844 simplify_cmd
= 'RoundToInt ();'
847 generate_cmds
+= 'Generate("%s");' % (fontname
+ '.' + f
)
850 simplify_cmd
='''SelectAll ();
855 AutoHint ();''' % vars()
857 pe_script
= ('''#!/usr/bin/env %(ff_command)s
865 open ('to-ttf.pe', 'w').write (pe_script
)
867 print 'Fontforge script', pe_script
868 system ("%s -script to-ttf.pe %s %s" % (ff_command
,
869 shell_escape_filename (raw_name
), shell_escape_filename (options
.tfm_file
)))
870 elif ff_needed
and (options
.simplify
or options
.round_to_int
or 'ttf' in formats
or 'svg' in formats
):
871 error(_ ("fontforge is not installed; could not perform requested command"))
875 if ('pfa' in formats
):
876 t1_path
= assemble_font (fontname
, 'pfa', 0)
878 if ('pfb' in formats
):
879 t1_path
= assemble_font (fontname
, 'pfb', 0)
881 if (t1_path
!= '' and 'afm' in formats
):
882 if get_binary("printafm"):
883 get_afm (t1_path
, options
.tfm_file
, encoding
, fontname
+ '.afm')
885 error(_ ("Neither fontforge nor ghostscript is not installed; could not perform requested command"))
888 def getenv (var
, default
):
889 if os
.environ
.has_key (var
):
890 return os
.environ
[var
]
894 def gen_pixel_font (filename
, metric
, magnification
):
896 Generate a GF file for FILENAME, such that `magnification'*mfscale
897 (default 1000 * 1.0) pixels fit on the designsize.
901 size
= metric
.design_size
903 size_points
= size
* 1/72.27 * base_dpi
905 mag
= magnification
/ size_points
907 prod
= mag
* base_dpi
909 open ('%s.%dgf' % (filename
, prod
))
912 ## MFINPUTS/TFMFONTS take kpathsea specific values;
913 ## we should analyse them any further.
914 os
.environ
['MFINPUTS'] = '%s:%s' % (origdir
,
915 getenv ('MFINPUTS', ''))
916 os
.environ
['TFMFONTS'] = '%s:%s' % (origdir
,
917 getenv ('TFMINPUTS', ''))
919 progress (_ ("Running Metafont..."))
921 cmdstr
= r
"mf '\mode:=lexmarks; mag:=%f; nonstopmode; input %s'" % (mag
, filename
)
922 if not options
.verbose
:
923 cmdstr
= cmdstr
+ ' 1>/dev/null 2>/dev/null'
924 st
= system (cmdstr
, ignore_error
= 1)
927 logfile
= '%s.log' % filename
930 if os
.path
.exists (logfile
):
931 log
= open (logfile
).read ()
932 m
= re
.search ('Output written on %s.([0-9]+)gf' % re
.escape (filename
), log
)
933 prod
= int (m
.group (1))
936 sys
.stderr
.write ('\n\nMetafont failed. Excerpt from the log file: \n\n*****')
937 m
= re
.search ("\n!", log
)
939 short_log
= log
[start
:start
+200]
940 sys
.stderr
.write (short_log
)
941 sys
.stderr
.write ('\n*****\n')
942 if re
.search ('Arithmetic overflow', log
):
943 sys
.stderr
.write ("""
945 Apparently, some numbers overflowed. Try using --magnification with a
946 lower number. (Current magnification: %d)
949 if not options
.keep_trying
or prod
== 0:
952 sys
.stderr
.write ('\n\nTrying to proceed despite of the Metafont errors...\n')
956 return "%s.%d" % (filename
, prod
)
958 def parse_command_line ():
959 p
= optparse
.OptionParser (version
="""mftrace @VERSION@
961 This program is free software. It is covered by the GNU General Public
962 License and you are welcome to change it and/or distribute copies of it
963 under certain conditions. Invoke as `mftrace --warranty' for more
966 Copyright (c) 2005--2006 by
967 Han-Wen Nienhuys <hanwen@xs4all.nl>
970 p
.usage
= "mftrace [OPTION]... FILE..."
971 p
.description
= _ ("Generate Type1 or TrueType font from Metafont source.")
973 p
.add_option ('-k', '--keep',
975 dest
="keep_temp_dir",
976 help=_ ("Keep all output in directory %s.dir") % program_name
)
977 p
.add_option ('','--magnification',
978 dest
="magnification",
982 help=_("Set magnification for MF to MAG (default: 1000)"))
983 p
.add_option ('-V', '--verbose',
986 help=_ ("Be verbose"))
987 p
.add_option ('-f', '--formats',
991 help=_("Which formats to generate (choices: AFM, PFA, PFB, TTF, SVG)"))
992 p
.add_option ('', '--simplify',
995 help=_ ("Simplify using fontforge"))
996 p
.add_option ('', '--gffile',
998 help= _("Use gf FILE instead of running Metafont"))
999 p
.add_option ('-I', '--include',
1000 dest
="include_dirs",
1003 help=_("Add to path for searching files"))
1004 p
.add_option ('','--glyphs',
1009 help= _('Process only these glyphs. LIST is comma separated'))
1010 p
.add_option ('', '--tfmfile',
1015 p
.add_option ('-e', '--encoding',
1018 dest
="encoding_file",
1020 help= _ ("Use encoding file FILE"))
1021 p
.add_option ('','--keep-trying',
1024 action
="store_true",
1025 help= _ ("Don't stop if tracing fails"))
1026 p
.add_option ('-w', '--warranty',
1027 action
="store_true",
1028 help=_ ("show warranty and copyright"))
1029 p
.add_option ('','--dos-kpath',
1031 help=_("try to use Miktex kpsewhich"))
1032 p
.add_option ('', '--potrace',
1034 help=_ ("Use potrace"))
1035 p
.add_option ('', '--autotrace',
1037 help=_ ("Use autotrace"))
1038 p
.add_option ('', '--no-afm',
1039 action
='store_false',
1042 help=_("Don't read AFM file"))
1043 p
.add_option ('','--noround',
1044 action
="store_false",
1045 dest
='round_to_int',
1047 help= ("Do not round coordinates of control points to integer values (use with --grid)"))
1048 p
.add_option ('','--grid',
1053 help=_ ("Set reciprocal grid size in em units"))
1054 p
.add_option ('-D','--define',
1055 metavar
="SYMBOL=VALUE",
1058 action
='append',help=_("Set the font info SYMBOL to VALUE"))
1061 (options
, files
) = p
.parse_args ()
1064 sys
.stderr
.write ('Need argument on command line \n')
1068 if options
.warranty
:
1072 options
.font_info
= {}
1073 for d
in options
.defs
:
1076 options
.font_info
[kv
] = 'true'
1078 options
.font_info
[kv
[0]] = '='.join (kv
[1:])
1080 def comma_sepped_to_list (x
):
1083 fs
+= f
.lower ().split (',')
1086 options
.formats
= comma_sepped_to_list (options
.formats
)
1089 for r
in options
.glyphs
:
1090 new_glyphs
+= r
.split (',')
1091 options
.glyphs
= new_glyphs
1094 for r
in options
.glyphs
:
1095 glyph_subrange
= map (int, string
.split (r
, '-'))
1096 if len (glyph_subrange
) == 2 and glyph_subrange
[0] < glyph_subrange
[1] + 1:
1097 glyph_range
+= range (glyph_subrange
[0], glyph_subrange
[1] + 1)
1099 glyph_range
.append (glyph_subrange
[0])
1101 options
.glyphs
= glyph_range
1103 options
.trace_binary
= ''
1105 options
.trace_binary
= 'potrace'
1106 elif options
.autotrace
:
1107 options
.trace_binary
= 'autotrace'
1109 if options
.formats
== []:
1110 options
.formats
= ['pfa']
1114 global trace_command
1115 global path_to_type1_ops
1117 stat
= os
.system ('potrace --version > /dev/null 2>&1 ')
1118 if options
.trace_binary
!= 'autotrace' and stat
== 0:
1119 options
.trace_binary
= 'potrace'
1121 trace_command
= potrace_command
1122 path_to_type1_ops
= potrace_path_to_type1_ops
1123 elif options
.trace_binary
== 'potrace' and stat
!= 0:
1124 error (_ ("Could not run potrace; have you installed it?"))
1126 stat
= os
.system ('autotrace --version > /dev/null 2>&1 ')
1127 if options
.trace_binary
!= 'potrace' and stat
== 0:
1128 options
.trace_binary
= 'autotrace'
1129 trace_command
= autotrace_command
1130 path_to_type1_ops
= autotrace_path_to_type1_ops
1131 elif options
.trace_binary
== 'autotrace' and stat
!= 0:
1132 error (_ ("Could not run autotrace; have you installed it?"))
1134 if not options
.trace_binary
:
1135 error (_ ("No tracing program found.\nInstall potrace or autotrace."))
1140 def derive_font_name (family
, fullname
):
1141 fullname
= re
.sub (family
, '', fullname
)
1142 family
= re
.sub (' ', '', family
)
1143 fullname
= re
.sub ('Oldstyle Figures', 'OsF', fullname
)
1144 fullname
= re
.sub ('Small Caps', 'SC', fullname
)
1145 fullname
= re
.sub ('[Mm]edium', '', fullname
)
1146 fullname
= re
.sub ('[^A-Za-z0-9]', '', fullname
)
1147 return '%s-%s' % (family
, fullname
)
1149 def cm_guess_font_info (filename
, fontinfo
):
1151 filename
= re
.sub ("cm(.*)tt", r
"cmtt\1", filename
)
1152 m
= re
.search ("([0-9]+)$", filename
)
1155 design_size
= int (m
.group (1))
1156 fontinfo
['DesignSize'] = design_size
1158 prefixes
= [("cmtt", "Computer Modern Typewriter"),
1159 ("cmvtt", "Computer Modern Variable Width Typewriter"),
1160 ("cmss", "Computer Modern Sans"),
1161 ("cm", "Computer Modern")]
1164 for (k
, v
) in prefixes
:
1165 if re
.search (k
, filename
):
1168 fontinfo
['isFixedPitch'] = 'true'
1169 filename
= re
.sub (k
, '', filename
)
1173 prefixes
= [("r", "Roman"),
1174 ("mi", "Math italic"),
1175 ("u", "Unslanted italic"),
1177 ("csc", "Small Caps"),
1178 ("ex", "Math extension"),
1179 ("ti", "Text italic"),
1182 for (k
, v
) in prefixes
:
1183 if re
.search (k
, filename
):
1185 filename
= re
.sub (k
, '', filename
)
1187 prefixes
= [("b", "Bold"),
1190 for (k
, v
) in prefixes
:
1191 if re
.search (k
, filename
):
1193 filename
= re
.sub (k
, '', filename
)
1195 prefixes
= [("c", "Condensed"),
1198 for (k
, v
) in prefixes
:
1199 if re
.search (k
, filename
):
1201 filename
= re
.sub (k
, '', filename
)
1203 fontinfo
['ItalicAngle'] = 0
1204 if re
.search ('[Ii]talic', shape
) or re
.search ('[Oo]blique', shape
):
1206 if re
.search ("Sans", family
):
1209 fontinfo
["ItalicAngle"] = a
1211 fontinfo
['Weight'] = weight
1212 fontinfo
['FamilyName'] = family
1213 full
= '%s %s %s %s %dpt' \
1214 % (family
, shape
, weight
, stretch
, design_size
)
1215 full
= re
.sub (" +", ' ', full
)
1217 fontinfo
['FullName'] = full
1218 fontinfo
['FontName'] = derive_font_name (family
, full
)
1222 def ec_guess_font_info (filename
, fontinfo
):
1224 m
= re
.search ("([0-9]+)$", filename
)
1226 design_size
= int (m
.group (1))
1227 fontinfo
['DesignSize'] = design_size
1229 prefixes
= [("ecss", "European Computer Modern Sans"),
1230 ("ectt", "European Computer Modern Typewriter"),
1231 ("ec", "European Computer Modern")]
1234 for (k
, v
) in prefixes
:
1235 if re
.search (k
, filename
):
1237 fontinfo
['isFixedPitch'] = 'true'
1239 filename
= re
.sub (k
, '', filename
)
1243 prefixes
= [("r", "Roman"),
1244 ("mi", "Math italic"),
1245 ("u", "Unslanted italic"),
1247 ("cc", "Small caps"),
1248 ("ex", "Math extension"),
1253 for (k
, v
) in prefixes
:
1254 if re
.search (k
, filename
):
1256 filename
= re
.sub (k
, '', filename
)
1258 prefixes
= [("b", "Bold"),
1261 for (k
, v
) in prefixes
:
1262 if re
.search (k
, filename
):
1264 filename
= re
.sub (k
, '', filename
)
1266 prefixes
= [("c", "Condensed"),
1269 for (k
, v
) in prefixes
:
1270 if re
.search (k
, filename
):
1272 filename
= re
.sub (k
, '', filename
)
1274 fontinfo
['ItalicAngle'] = 0
1275 if re
.search ('[Ii]talic', shape
) or re
.search ('[Oo]blique', shape
):
1277 if re
.search ("Sans", family
):
1280 fontinfo
["ItalicAngle"] = a
1282 fontinfo
['Weight'] = weight
1283 fontinfo
['FamilyName'] = family
1284 full
= '%s %s %s %s %dpt' \
1285 % (family
, shape
, weight
, stretch
, design_size
)
1286 full
= re
.sub (" +", ' ', full
)
1288 fontinfo
['FontName'] = derive_font_name (family
, full
)
1289 fontinfo
['FullName'] = full
1294 def guess_fontinfo (filename
):
1296 'FontName': filename
,
1297 'FamilyName': filename
,
1298 'Weight': 'Regular',
1301 'isFixedPitch' : 'false',
1302 'FullName': filename
,
1305 if re
.search ('^cm', filename
):
1306 fi
.update (cm_guess_font_info (filename
, fi
))
1307 elif re
.search ("^ec", filename
):
1308 fi
.update (ec_guess_font_info (filename
, fi
))
1309 elif options
.read_afm
:
1312 afmfile
= find_file (filename
+ '.afm')
1315 afmfile
= os
.path
.abspath (afmfile
)
1316 afm_struct
= afm
.read_afm_file (afmfile
)
1317 fi
.update (afm_struct
.__dict
__)
1320 sys
.stderr
.write ("Warning: no extra font information for this font.\n"
1321 + "Consider writing a XX_guess_font_info() routine.\n")
1325 def do_file (filename
):
1326 encoding_file
= options
.encoding_file
1328 include_dirs
= options
.include_dirs
1329 include_dirs
.append (origdir
)
1331 basename
= strip_extension (filename
, '.mf')
1332 progress (_ ("Font `%s'..." % basename
))
1336 if encoding_file
and not os
.path
.exists (encoding_file
):
1337 encoding_file
= find_file (encoding_file
)
1339 encoding_file
= os
.path
.abspath (encoding_file
)
1342 if options
.tfm_file
:
1343 options
.tfm_file
= os
.path
.abspath (options
.tfm_file
)
1345 tfm_try
= find_file (basename
+ '.tfm')
1347 options
.tfm_file
= tfm_try
1349 if not os
.environ
.has_key ("MFINPUTS"):
1350 os
.environ
["MFINPUTS"] = os
.getcwd () + ":"
1352 ## must change dir before calling mktextfm.
1353 if options
.keep_temp_dir
:
1356 setup_temp (os
.path
.join (os
.getcwd (), program_name
+ '.dir'))
1357 temp_dir
.clean
= nop
1362 progress ('Temporary directory is `%s\'\n' % temp_dir
)
1364 if not options
.tfm_file
:
1365 options
.tfm_file
= popen ("mktextfm %s 2>/dev/null" % shell_escape_filename (basename
)).read ()
1366 if options
.tfm_file
:
1367 options
.tfm_file
= options
.tfm_file
.strip ()
1368 options
.tfm_file
= os
.path
.abspath (options
.tfm_file
)
1370 if not options
.tfm_file
:
1371 error (_ ("Can not find a TFM file to match `%s'") % basename
)
1373 metric
= tfm
.read_tfm_file (options
.tfm_file
)
1375 fontinfo
= guess_fontinfo (basename
)
1376 fontinfo
.update (options
.font_info
)
1378 if not encoding_file
:
1379 codingfile
= 'tex256.enc'
1380 if not coding_dict
.has_key (metric
.coding
):
1381 sys
.stderr
.write ("Unknown encoding `%s'; assuming tex256.\n" % metric
.coding
)
1383 codingfile
= coding_dict
[metric
.coding
]
1385 encoding_file
= find_file (codingfile
)
1386 if not encoding_file
:
1387 error (_ ("can't find file `%s'" % codingfile
))
1389 (enc_name
, encoding
) = read_encoding (encoding_file
)
1391 if not len (options
.glyphs
):
1392 options
.glyphs
= range (0, len (encoding
))
1394 if not options
.gffile
:
1396 base
= gen_pixel_font (basename
, metric
, options
.magnification
)
1397 options
.gffile
= base
+ 'gf'
1399 options
.gffile
= find_file (options
.gffile
)
1401 # the heart of the program:
1402 trace_font (basename
, options
.gffile
, metric
, options
.glyphs
, encoding
,
1403 options
.magnification
, fontinfo
)
1405 make_outputs (basename
, options
.formats
, encoding
)
1406 for format
in options
.formats
:
1407 shutil
.copy2 (basename
+ '.' + format
, origdir
)
1416 backend_options
= getenv ('MFTRACE_BACKEND_OPTIONS', '')
1418 files
= parse_command_line ()
1419 identify (sys
.stderr
)
1421 for filename
in files
:
1423 sys
.exit (exit_value
)
1425 if __name__
=='__main__':