import os.path import errno import shutil import subprocess from .util import indent from .error import Error _d = os.path.dirname BASE_DIR = _d(_d(_d(_d(os.path.realpath(__file__))))) VENDOR_DIR = os.path.join(BASE_DIR, 'vendor') class FontFile(object): """Represents a font file in a particular format.""" def __init__(self, full_path, path_without_extension, format): self.full_path = full_path self.path_without_extension = path_without_extension self.format = format def moved_and_converted_to(self, output_dir, format): basename_without_ext = os.path.basename(self.path_without_extension) new_path_without_ext = os.path.join(output_dir, basename_without_ext) new_full_path = new_path_without_ext + os.extsep + format return FontFile(new_full_path, new_path_without_ext, format) def basename(self): return os.path.basename(self.full_path) def svg_id(self): return os.path.basename(self.path_without_extension) def ensure_directory_exists(path): try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise def ensure_file_directory_exists(path): ensure_directory_exists(os.path.dirname(path)) def copy_file(input_files, output_files, logger): for input_file in input_files: for output_file in output_files: input_path = input_file.full_path output_path = output_file.full_path logger.info('copying %s to %s' % (input_path, output_path)) _copy_file(input_path, output_path) return def _copy_file(input_path, output_path): ensure_file_directory_exists(output_path) try: shutil.copyfile(input_path, output_path) except shutil.SameFileError as e: pass def _devnull(mode): return open(os.devnull, mode) def _ff_escape(s): return s.replace('"', '\\"') def convert_with_fontforge(input_files, output_files, logger): for input_file in input_files: input_path = input_file.full_path output_paths = [f.full_path for f in output_files] logger.info('using FontForge to convert %s to %s' % (input_path, ', '.join(output_paths))) _convert_with_fontforge(input_path, output_paths) return def _convert_with_fontforge(input_path, output_paths): output_paths = list(output_paths) ensure_file_directory_exists(output_paths[0]) with _devnull('w') as fout: p = subprocess.Popen(['fontforge', '-lang=ff', '-script', '-'], stdin=subprocess.PIPE, stdout=fout, stderr=subprocess.PIPE) # CIDFlatten flattens CID-based fonts (e.g. otf) with multiple # sub-fonts into one single font. # See https://github.com/bdusell/webfont-generator/issues/20 p.stdin.write(('Open("%s")\nCIDFlatten()\n' % _ff_escape(input_path)).encode('utf-8')) for output_path in output_paths: p.stdin.write(('Generate("%s")\n' % _ff_escape(output_path)).encode('utf-8')) p.stdin.close() err = p.stderr.read() p.stderr.close() if p.wait() != 0: raise Error( 'FontForge conversion failed:\n' 'Output from FontForge:\n' + indent(err.decode('ascii'), ' ')) # Ensure that the files were actually generated bad_files = [p for p in output_paths if not os.path.isfile(p)] if bad_files: raise Error( 'FontForge failed to generate %s:\n' 'Output from FontForge:\n' '%s' % ( ', '.join(bad_files), indent(err.decode('ascii'), ' ') )) def convert_with_sfntly(input_files, output_files, logger): for input_file in input_files: input_path = input_file.full_path output_paths = [f.full_path for f in output_files] logger.info('using sfntly to convert %s to %s' % (input_path, ', '.join(output_paths))) _convert_with_sfntly(input_path, output_paths) SFNTLY_CLASSPATH = ':'.join([ os.path.join(BASE_DIR, 'src', 'java'), os.path.join(VENDOR_DIR, 'sfntly', 'java', 'target', 'classes') ]) def _convert_with_sfntly(input_path, output_paths): output_paths = list(output_paths) ensure_file_directory_exists(output_paths[0]) command = ['java', '-cp', SFNTLY_CLASSPATH, 'ConvertFont', input_path] for output_path in output_paths: command.append('-o') command.append(output_path) if subprocess.call(command) != 0: raise Error('sfntly conversion failed') def convert_with_woff2_compress(input_files, output_files, logger): for input_file in input_files: input_path = input_file.full_path logger.info('using woff2_compress to convert %s to woff2' % input_path) _convert_with_woff2_compress(input_path) return WOFF2_COMPRESS_PATH = os.path.join(VENDOR_DIR, 'woff2', 'woff2_compress') def _convert_with_woff2_compress(input_path): with _devnull('r') as fin, _devnull('w') as fout: code = subprocess.call([WOFF2_COMPRESS_PATH, input_path], stdin=fin, stdout=fout, stderr=fout) if code != 0: raise Error('conversion with woff2_compress failed') def convert_with_woff2_decompress(input_files, output_files, logger): for input_file in input_files: input_path = input_file.full_path logger.info('using woff2_decompress to convert %s to ttf' % input_path) _convert_with_woff2_decompress(input_path) return WOFF2_DECOMPRESS_PATH = os.path.join(VENDOR_DIR, 'woff2', 'woff2_decompress') def _convert_with_woff2_decompress(input_path): with _devnull('r') as fin, _devnull('w') as fout: code = subprocess.call([WOFF2_DECOMPRESS_PATH, input_path], stdin=fin, stdout=fout, stderr=fout) if code != 0: raise Error('conversion with woff2_decompress failed')