#! /usr/bin/env python ############################################### # Symbolically link various TeX files locally # # and pre-build .tfm files from .mf sources. # # # # In fact, this script creates only a Ninja # # build file to perform the operations. # # # # Author: Scott Pakin # ############################################### import glob import os import re import subprocess import sys import textwrap from pathlib import Path def kpsewhich(fname): 'Find a filename in the TeX tree.' proc = subprocess.run(['kpsewhich', fname], capture_output=True, check=True, encoding='utf-8') return proc.stdout.strip() def header_string(): 'Return a "generated file" header string.' full_name = os.path.abspath(sys.argv[0]) gen_line = 'This is a generated file. DO NOT EDIT.' edit_line = f'Edit {full_name} instead.' max_chars = max(len(gen_line), len(edit_line)) hash_line = '#' * (max_chars + 4) return '\n'.join([ hash_line, '# %-*.*s #' % (max_chars, max_chars, gen_line), '# %-*.*s #' % (max_chars, max_chars, edit_line), hash_line, '', ]) # Define some special-case PK sizes. These were found empirically by # examining the PK files left over after the first LaTeX run. base_to_dpi = { 'astrosym': 1054, 'dancers': 93, 'dice3d': 375, 'fselch10': 1219, 'hands': 250, 'nkarta': 800, } # Generate a Ninja file that performs the desired operations. with open('nonlatex.ninja', 'w') as w: # Write a "generated file" warning. w.write(header_string()) # Construct a map from local filename (specified on the command line) # to absolute filename. As a side effect, delete the local filename. local2abs = {} for fname in sorted(sys.argv[1:], key=str.lower): try: os.remove(fname) except FileNotFoundError: pass full_fname = kpsewhich(fname) if full_fname != '': local2abs[Path(fname)] = Path(full_fname) # Symbolically link all absolute filenames locally. w.write(''' rule symlink command = ln -s -f $in $out description = Symlinking $in locally # Symbolically link various files into the current directory. ''') for dest, src in local2abs.items(): w.write(f'build {dest} : symlink {src}\n') # Precompile TFM and PK fonts from Metafont sources. w.write(''' # The optional DPI variable additionally scales the font relative to # the base DPI of 1200. rule mf-to-tfm-gf command = $ base="$$(basename $in .mf)" ; $ mf "\\nonstopmode; \\mode:=ljfzzz; input $$base" ; $ if [ "$DPI" ] ; then $ mf "\\nonstopmode; \\mode:=ljfzzz; mag:=$DPI/1200; input $$base" ; $ fi ; $ rm "$${base}.log" description = Generating $out from $in rule gf-to-pk command = $ gftopk $in $out description = Generating $out from $in # This is a special-case rule for avoiding "paths don't intersect" errors # in bskms10. rule mf-to-pk-1080 command = $ rm -f $out ; $ base=$$(basename $in .mf) ; $ mf-nowin --progname=mf --output-directory=$$(pwd) "\\mode:=lmaster; mag:=1.08; nonstopmode; input $$base" ; $ gftopk $${base}.1080gf ; $ rm $${base}.1080gf $${base}.log description = Generating $out from $in # Precompile Metafont fonts. ''') outputs = [str(dest) for dest in local2abs] for fname in local2abs: if fname.suffix != '.mf': continue base = fname.stem if base == 'bskms10': # bskms10 issues fatal Metafont errors ("The paths don't # intersect") when building with "--bdpi=1200 --dpi=1080", # which is invoked implicitly when building the CLSL. We # therefore manually generate bskms10.1080pk from a # "--bdpi=1000" version. w.write(f'build {base}.1080pk : mf-to-pk-1080 {fname}\n') outputs.append(f'{base}.1080pk') else: try: # Handle special cases in which a multiple DPIs are used. dpi = base_to_dpi[base] w.write(f'build {base}.tfm | {base}.1200gf {base}.{dpi}gf : mf-to-tfm-gf {fname}\n') w.write(f' DPI = {dpi}\n') w.write(f'build {base}.1200pk : gf-to-pk {base}.1200gf\n') w.write(f'build {base}.{dpi}pk : gf-to-pk {base}.{dpi}gf\n') outputs.extend([ f'{base}.tfm', f'{base}.1200gf', f'{base}.1200pk', f'{base}.{dpi}gf', f'{base}.{dpi}pk', ]) except KeyError: # Handle the common case of 1200 DPI only. w.write(f'build {base}.tfm | {base}.1200gf : mf-to-tfm-gf {fname}\n') w.write(f'build {base}.1200pk : gf-to-pk {base}.1200gf\n') outputs.extend([ f'{base}.tfm', f'{base}.1200gf', f'{base}.1200pk', ]) w.write('\n') # Define a phony rule that depends on all outputs. w.write('# Define a phony rule that depends on all of the preceding outputs.\n') w.write('build NONLATEX : phony $\n') outputs.sort(key=str.casefold) lines = textwrap.wrap(' '.join(outputs), break_long_words=False, break_on_hyphens=False, initial_indent=' ', subsequent_indent=' ') w.write('%s\n' % ' $\n'.join(lines))