diff options
-rw-r--r-- | ChangeLog | 66 | ||||
-rw-r--r-- | builds/unix/ftsystem.c | 2 | ||||
-rw-r--r-- | meson.build | 368 | ||||
-rw-r--r-- | meson_options.txt | 47 | ||||
-rw-r--r-- | scripts/extract_freetype_version.py | 107 | ||||
-rw-r--r-- | scripts/extract_libtool_version.py | 105 | ||||
-rw-r--r-- | scripts/generate_reference_docs.py | 79 | ||||
-rw-r--r-- | scripts/parse_modules_cfg.py | 160 | ||||
-rw-r--r-- | scripts/process_ftoption_h.py | 105 |
9 files changed, 1038 insertions, 1 deletions
@@ -1,3 +1,69 @@ +2020-09-21 David Turner <david@freetype.org> + + Add Meson build project file. + + Example usage: + + # Configure Meson build in directory `build-meson` to generate + # release binaries comparable to to the ones from the + # autotools/make build system. + meson setup build-meson \ + --prefix=/usr/local \ + --buildtype=debugoptimized \ + --strip \ + -Db_ndebug=true + + # After configuring the Meson build with the above command, + # compile and install to `/usr/local/`; this includes a pkg-config + # file. + ninja -C build-meson install + + # Alternatively, compile and install to `/tmp/aa/usr/local/...` + # for packaging. + DESTDIR=/tmp/aa ninja -C build-meson install + + # Generate documentation under `build-meson/docs`. + ninja -C build-meson docs + + Library size comparison for stripped `libfreetype.so` generated by + all three build systems: + + - Default build (autotools + libtool): 712 KiB + - CMake build (RelWithDebInfo): 712 KiB + - Meson build: 712 KiB + + + * meson.build: New top-level Meson build file for the library. + + * meson_options.txt: New file. It holds user-selectable options for + the build, which can be printed with `meson configure`, and selected + at `meson setup` or `meson --reconfigure` time with + `-D<option>=<value>`. + + * scripts/parse_modules_cfg.py: A script invoked by `meson.build` to + parse `modules.cfg` and extract important information out of it + (i.e., the list of modules). + + * scripts/process_ftoption_h.py: New script invoked by `meson.build` + to process the original `ftoption.h` file. It enables or disables + configuration macro variables based on the available dependencies. + This is similar to what other build systems are using (i.e., Meson's + `configure_file()` command is not used here). + + * scripts/extract_freetype_version.py: New script invoked by + `meson.build` to extract the FreeType version number from + `<freetype/freetype.h>`. + + * scripts/extract_libtool_version.py: New script invoked by + `meson.build` to extract the libtool `revision_info` data from + `builds/unix/configure.raw`, and to generate the corresponding + shared library suffix. + + * scripts/generate_reference_docs.py: New script invoked by + `meson.build` to generate the FreeType 2 reference documentation + (using the `docwriter` and `mkdocs` packages, which must be already + installed). + 2020-09-11 Alexei Podtelezhnikov <apodtele@gmail.com> [raster] Improve the second pass (#58373). diff --git a/builds/unix/ftsystem.c b/builds/unix/ftsystem.c index 8437a6689..b4d71d40e 100644 --- a/builds/unix/ftsystem.c +++ b/builds/unix/ftsystem.c @@ -18,7 +18,7 @@ #include <ft2build.h> /* we use our special ftconfig.h file, not the standard one */ -#include <ftconfig.h> +#include FT_CONFIG_CONFIG_H #include <freetype/internal/ftdebug.h> #include <freetype/ftsystem.h> #include <freetype/fterrors.h> diff --git a/meson.build b/meson.build new file mode 100644 index 000000000..35254093a --- /dev/null +++ b/meson.build @@ -0,0 +1,368 @@ +# +# Meson project file for FreeType 2 +# + +# Copyright (C) 2020 by +# David Turner, Robert Wilhelm, and Werner Lemberg. +# +# This file is part of the FreeType project, and may only be used, modified, +# and distributed under the terms of the FreeType project license, +# LICENSE.TXT. By continuing to use, modify, or distribute this file you +# indicate that you have read the license and understand and accept it +# fully. + + +project('freetype2', 'c', + meson_version: '>= 0.55.0', + default_options: ['default_library=both'], +) + +# +# Rules to compile the FreeType 2 library itself +# + + +# Apparently meson doesn't provide a read_file() function, so instead +# running an external command is required. + +python = import('python') +python_exe = python.find_installation(required: true) + +ft2_version = run_command(python_exe, + files('scripts/extract_freetype_version.py'), + files('include/freetype/freetype.h')).stdout().strip() + +ft2_libtool_version = run_command(python_exe, + files('scripts/extract_libtool_version.py'), + '--soversion', + files('builds/unix/configure.raw')).stdout().strip() + +ft2_includes = include_directories('include') + + +# Generate a custom `ftmodule.h` version based on the content of +# `modules.cfg`. + +ftmodule_h = custom_target('ftmodule.h', + output: 'ftmodule.h', + input: 'modules.cfg', + command: [python_exe, files('scripts/parse_modules_cfg.py'), + '--format=ftmodule.h', '@INPUT@', '--output', '@OUTPUT@'], + install: true, + install_dir: 'include/freetype2/freetype/config', +) +ft2_sources = [ftmodule_h] + + +# FreeType 2 modules. + +ft_main_modules = run_command(python_exe, + files('scripts/parse_modules_cfg.py'), + '--format=main-modules', + files('modules.cfg')).stdout().strip().split() + +ft2_sources += files([ + 'src/base/ftbase.c', + 'src/base/ftinit.c', +]) + +foreach mod: ft_main_modules + source = mod + if mod == 'winfonts' + source = 'winfnt' + elif mod == 'cid' + source = 'type1cid' + endif + ft2_sources += 'src/@0@/@1@.c'.format(mod, source) +endforeach + +# NOTE: The `gzip` and `bzip2` aux modules are handled through options. +ft_aux_modules = run_command(python_exe, + files('scripts/parse_modules_cfg.py'), + '--format=aux-modules', + files('modules.cfg')).stdout().strip().split() + +foreach auxmod: ft_aux_modules + source = auxmod + # Most sources are named `src/<module>/<module>.c`, but there are a few + # exceptions handled here. + if auxmod == 'cache' + source = 'ftcache' + elif auxmod == 'lzw' + source = 'ftlzw' + elif auxmod == 'gzip' or auxmod == 'bzip2' + # Handled through options instead, see below. + continue + endif + ft2_sources += 'src/@0@/@1@.c'.format(auxmod, source) +endforeach + + +# FreeType 2 base extensions. +# Normally configured through `modules.cfg`. + +base_extensions = run_command(python_exe, + files('scripts/parse_modules_cfg.py'), + '--format=base-extensions-list', + files('modules.cfg')).stdout().split() + +foreach ext: base_extensions + ft2_sources += files('src/base/' + ext) +endforeach + + +# Header files. + +ft2_public_headers = files([ + 'include/freetype/freetype.h', + 'include/freetype/ftadvanc.h', + 'include/freetype/ftbbox.h', + 'include/freetype/ftbdf.h', + 'include/freetype/ftbitmap.h', + 'include/freetype/ftbzip2.h', + 'include/freetype/ftcache.h', + 'include/freetype/ftchapters.h', + 'include/freetype/ftcolor.h', + 'include/freetype/ftdriver.h', + 'include/freetype/fterrdef.h', + 'include/freetype/fterrors.h', + 'include/freetype/ftfntfmt.h', + 'include/freetype/ftgasp.h', + 'include/freetype/ftglyph.h', + 'include/freetype/ftgxval.h', + 'include/freetype/ftgzip.h', + 'include/freetype/ftimage.h', + 'include/freetype/ftincrem.h', + 'include/freetype/ftlcdfil.h', + 'include/freetype/ftlist.h', + 'include/freetype/ftlzw.h', + 'include/freetype/ftmac.h', + 'include/freetype/ftmm.h', + 'include/freetype/ftmodapi.h', + 'include/freetype/ftmoderr.h', + 'include/freetype/ftotval.h', + 'include/freetype/ftoutln.h', + 'include/freetype/ftparams.h', + 'include/freetype/ftpfr.h', + 'include/freetype/ftrender.h', + 'include/freetype/ftsizes.h', + 'include/freetype/ftsnames.h', + 'include/freetype/ftstroke.h', + 'include/freetype/ftsynth.h', + 'include/freetype/ftsystem.h', + 'include/freetype/fttrigon.h', + 'include/freetype/fttypes.h', + 'include/freetype/ftwinfnt.h', + 'include/freetype/t1tables.h', + 'include/freetype/ttnameid.h', + 'include/freetype/tttables.h', + 'include/freetype/tttags.h', +]) + +ft2_config_headers = files([ + 'include/freetype/config/ftconfig.h', + 'include/freetype/config/ftheader.h', + 'include/freetype/config/ftstdlib.h', + 'include/freetype/config/integer-types.h', + 'include/freetype/config/mac-support.h', + 'include/freetype/config/public-macros.h', +]) + +ft2_defines = [] + + +# System support file. + +cc = meson.get_compiler('c') + +# NOTE: msys2 on Windows has `unistd.h` and `fcntl.h` but not `sys/mman.h`! +has_unistd_h = cc.has_header('unistd.h') +has_fcntl_h = cc.has_header('fcntl.h') +has_sys_mman_h = cc.has_header('sys/mman.h') + +if has_unistd_h + ft2_defines += ['-DHAVE_UNISTD_H=1'] +endif +if has_fcntl_h + ft2_defines += ['-DHAVE_FCNTL_H'] +endif + +mmap_option = get_option('mmap') +if mmap_option.auto() + use_mmap = has_unistd_h and has_fcntl_h and has_sys_mman_h +else + use_mmap = mmap_option.enabled() +endif +if use_mmap + # This version of ftsystem.c uses mmap() to read input font files. + ft2_sources += files(['builds/unix/ftsystem.c',]) +else + ft2_sources += files(['src/base/ftsystem.c',]) +endif + + +# Debug support file +# +# NOTE: Some specialized versions exist for other platforms not supported by +# Meson. Most implementation differences are extremely minor, i.e., in the +# implementation of FT_Message() and FT_Panic(), and getting the `FT2_DEBUG` +# value from the environment, when this is supported. A smaller refactor +# might make these platform-specific files much smaller, and could be moved +# into `ftsystem.c` as well. +# +if host_machine.system() == 'windows' + ft2_debug_src = 'builds/windows/ftdebug.c' +else + ft2_debug_src = 'src/base/ftdebug.c' +endif +ft2_sources += files([ft2_debug_src]) + +ft2_deps = [] + + +# Generate `ftoption.h` based on available dependencies. + +ftoption_command = [python_exe, + files('scripts/process_ftoption_h.py'), + '@INPUT@', '--output=@OUTPUT@'] + +# GZip support +zlib_option = get_option('zlib') +if zlib_option == 'disabled' + ftoption_command += ['--disable=FT_CONFIG_OPTION_USE_ZLIB'] +else + ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_ZLIB'] + if zlib_option == 'builtin' + ftoption_command += ['--disable=FT_CONFIG_OPTION_SYSTEM_ZLIB'] + else + # Probe for the system version. + zlib_system = dependency('zlib', required: zlib_option == 'system') + ft2_deps += [zlib_system] + ftoption_command += ['--enable=FT_CONFIG_OPTION_SYSTEM_ZLIB'] + endif + ft2_sources += files(['src/gzip/ftgzip.c',]) +endif + +# BZip2 support +# +# IMPORTANT NOTE: Without `static: false` here, Meson will find both the +# static library version and the shared library version when they are +# installed on the system, and will try to link them *both* to the final +# library! +bzip2_dep = meson.get_compiler('c').find_library('bz2', + static: false, + required: get_option('bzip2')) +if bzip2_dep.found() + ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_BZIP2'] + ft2_sources += files(['src/bzip2/ftbzip2.c',]) + ft2_deps += [bzip2_dep] +endif + +# PNG support +libpng_dep = dependency('libpng', required: get_option('png')) +ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_PNG'] +ft2_deps += [libpng_dep] + +# Harfbuzz support +harfbuzz_dep = dependency('harfbuzz', + version: '>= 1.8.0', + required: get_option('harfbuzz')) +ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_HARFBUZZ'] +ft2_deps += [harfbuzz_dep] + +# Brotli decompression support +brotli_dep = dependency('libbrotlidec', required: get_option('brotli')) +ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_BROTLI'] +ft2_deps += [brotli_dep] + +# We can now generate `ftoption.h`. +ftoption_h = custom_target('ftoption.h', + input: 'include/freetype/config/ftoption.h', + output: 'ftoption.h', + command: ftoption_command, + install: true, + install_dir: 'include/freetype2/freetype/config', +) +ft2_sources += ftoption_h + + +# QUESTION: What if the compiler doesn't support `-D` but uses `/D` instead +# as on Windows? +# +# Other build systems have something like c_defines to list defines in a +# more portable way. For now assume the compiler supports `-D` (hint: Visual +# Studio does). +ft2_defines += ['-DFT2_BUILD_LIBRARY=1'] + + +# Ensure that the `ftoption.h` file generated above will be used to build +# FreeType. Unfortunately, and very surprisingly, configure_file() does not +# support putting the output file in a sub-directory, so we have to override +# the default which is `<freetype/config/ftoption.h>`. +# +# It would be cleaner to generate the file directly into +# `${MESON_BUILD_DIR}/freetype/config/ftoption.h`. See +# 'https://github.com/mesonbuild/meson/issues/2320' for details. +ft2_defines += ['-DFT_CONFIG_OPTIONS_H=<ftoption.h>'] + +ft2_c_args = ft2_defines +if cc.has_function_attribute('visibility:hidden') + ft2_c_args += ['-fvisibility=hidden'] +endif + +ft2_lib = library('freetype', + sources: ft2_sources + [ftmodule_h], + c_args: ft2_c_args, + include_directories: ft2_includes, + dependencies: ft2_deps, + install: true, + version: ft2_libtool_version, +) + + +# To be used by other projects including this one through subproject(). +freetype2_dep = declare_dependency( + include_directories: ft2_includes, + link_with: ft2_lib, + version: ft2_libtool_version) + + +# NOTE: Using both `install_dir` and `subdir` doesn't seem to work below, +# i.e., the subdir value seems to be ignored, contrary to examples in the +# Meson documentation. +install_headers('include/ft2build.h', + install_dir: 'include/freetype2') +install_headers(ft2_public_headers, + install_dir: 'include/freetype2/freetype') +install_headers(ft2_config_headers, + install_dir: 'include/freetype2/freetype/config') + + +# TODO(david): Declare_dependency() for using this in a Meson subproject +# +pkgconfig = import('pkgconfig') +pkgconfig.generate(ft2_lib, + filebase: 'freetype2', + name: 'FreeType 2', + description: 'A free, high-quality, and portable font engine.', + url: 'https://freetype.org', + subdirs: 'freetype2', + version: ft2_libtool_version, +) + + +# NOTE: Unlike the old `make refdoc` command, this generates the +# documentation under `$BUILD/docs/` since Meson doesn't support modifying +# the source root directory (which is a good thing). +gen_docs = custom_target('freetype2 reference documentation', + output: 'docs', + input: ft2_public_headers + ft2_config_headers, + command: [python_exe, + files('scripts/generate_reference_docs.py'), + '--version=' + ft2_version, + '--input-dir=' + meson.source_root(), + '--output-dir=@OUTPUT@' + ], +) + +# EOF diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..74ed16baa --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,47 @@ +# +# meson_options.txt +# + +# Copyright (C) 2020 by +# David Turner, Robert Wilhelm, and Werner Lemberg. +# +# This file is part of the FreeType project, and may only be used, modified, +# and distributed under the terms of the FreeType project license, +# LICENSE.TXT. By continuing to use, modify, or distribute this file you +# indicate that you have read the license and understand and accept it +# fully. + + +option('zlib', + type: 'combo', + choices: ['disabled', 'auto', 'builtin', 'system'], + value: 'auto', + description: 'Support reading gzip-compressed font files.') + +option('bzip2', + type: 'feature', + value: 'auto', + description: 'Support reading bzip2-compressed font files.') + +option('png', + type: 'feature', + value: 'auto', + description: 'Support color bitmap glyph formats in the PNG format.' + + 'Requires libpng.') + +option('harfbuzz', + type: 'feature', + value: 'auto', + description: 'Use Harfbuzz library to improve auto-hinting.' + + ' If available, many glyphs not directly addressable' + + ' by a font\'s character map will be hinted also.') + +option('brotli', + type: 'feature', + value: 'auto', + description: 'Use Brotli library to support decompressing WOFF2 fonts.') + +option('mmap', + type: 'feature', + value: 'auto', + description: 'Use mmap() to open font files for faster parsing.') diff --git a/scripts/extract_freetype_version.py b/scripts/extract_freetype_version.py new file mode 100644 index 000000000..e9ebb724c --- /dev/null +++ b/scripts/extract_freetype_version.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +"""Extract the FreeType version numbers from `<freetype/freetype.h>`. + +This script parses the header to extract the version number defined there. +By default, the full dotted version number is printed, but `--major`, +`--minor` or `--patch` can be used to only print one of these values +instead. +""" + +from __future__ import print_function + +import argparse +import os +import re +import sys + +# Expected input: +# +# ... +# #define FREETYPE_MAJOR 2 +# #define FREETYPE_MINOR 10 +# #define FREETYPE_PATCH 2 +# ... + +RE_MAJOR = re.compile(r"^ #define \s+ FREETYPE_MAJOR \s+ (.*) $", re.X) +RE_MINOR = re.compile(r"^ #define \s+ FREETYPE_MINOR \s+ (.*) $", re.X) +RE_PATCH = re.compile(r"^ #define \s+ FREETYPE_PATCH \s+ (.*) $", re.X) + + +def parse_freetype_header(header): + major = None + minor = None + patch = None + + for line in header.splitlines(): + line = line.rstrip() + m = RE_MAJOR.match(line) + if m: + assert major == None, "FREETYPE_MAJOR appears more than once!" + major = m.group(1) + continue + + m = RE_MINOR.match(line) + if m: + assert minor == None, "FREETYPE_MINOR appears more than once!" + minor = m.group(1) + continue + + m = RE_PATCH.match(line) + if m: + assert patch == None, "FREETYPE_PATCH appears more than once!" + patch = m.group(1) + continue + + assert ( + major and minor and patch + ), "This header is missing one of FREETYPE_MAJOR, FREETYPE_MINOR or FREETYPE_PATCH!" + + return (major, minor, patch) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--major", + action="store_true", + help="Only print the major version number.", + ) + group.add_argument( + "--minor", + action="store_true", + help="Only print the minor version number.", + ) + group.add_argument( + "--patch", + action="store_true", + help="Only print the patch version number.", + ) + + parser.add_argument( + "input", + metavar="FREETYPE_H", + help="The input freetype.h header to parse.", + ) + + args = parser.parse_args() + with open(args.input) as f: + header = f.read() + + version = parse_freetype_header(header) + + if args.major: + print(version[0]) + elif args.minor: + print(version[1]) + elif args.patch: + print(version[2]) + else: + print("%s.%s.%s" % version) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/extract_libtool_version.py b/scripts/extract_libtool_version.py new file mode 100644 index 000000000..0569481b3 --- /dev/null +++ b/scripts/extract_libtool_version.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +"""Extract the libtool version from `configure.raw`. + +This script parses the `configure.raw` file to extract the libtool version +number. By default, the full dotted version number is printed, but +`--major`, `--minor` or `--patch` can be used to only print one of these +values instead. +""" + +from __future__ import print_function + +import argparse +import os +import re +import sys + +# Expected input: +# +# ... +# version_info='23:2:17' +# ... + +RE_VERSION_INFO = re.compile(r"^version_info='(\d+):(\d+):(\d+)'") + + +def parse_configure_raw(header): + major = None + minor = None + patch = None + + for line in header.splitlines(): + line = line.rstrip() + m = RE_VERSION_INFO.match(line) + if m: + assert major == None, "version_info appears more than once!" + major = m.group(1) + minor = m.group(2) + patch = m.group(3) + continue + + assert ( + major and minor and patch + ), "This input file is missing a version_info definition!" + + return (major, minor, patch) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--major", + action="store_true", + help="Only print the major version number.", + ) + group.add_argument( + "--minor", + action="store_true", + help="Only print the minor version number.", + ) + group.add_argument( + "--patch", + action="store_true", + help="Only print the patch version number.", + ) + group.add_argument( + "--soversion", + action="store_true", + help="Only print the libtool library suffix.", + ) + + parser.add_argument( + "input", + metavar="CONFIGURE_RAW", + help="The input configure.raw file to parse.", + ) + + args = parser.parse_args() + with open(args.input) as f: + raw_file = f.read() + + version = parse_configure_raw(raw_file) + + if args.major: + print(version[0]) + elif args.minor: + print(version[1]) + elif args.patch: + print(version[2]) + elif args.soversion: + # Convert libtool version_info to the library suffix. + # (current,revision, age) -> (current - age, age, revision) + print( + "%d.%s.%s" + % (int(version[0]) - int(version[2]), version[2], version[1]) + ) + else: + print("%s.%s.%s" % version) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate_reference_docs.py b/scripts/generate_reference_docs.py new file mode 100644 index 000000000..219017c9d --- /dev/null +++ b/scripts/generate_reference_docs.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +"""Generate FreeType reference documentation.""" + +from __future__ import print_function + +import argparse +import glob +import os +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument( + "--input-dir", + required=True, + help="Top-level FreeType source directory.", + ) + + parser.add_argument( + "--version", required=True, help='FreeType version (e.g. "2.x.y").' + ) + + parser.add_argument( + "--output-dir", required=True, help="Output directory." + ) + + args = parser.parse_args() + + # Get the list of input files of interest. + include_dir = os.path.join(args.input_dir, "include") + include_config_dir = os.path.join(include_dir, "config") + include_cache_dir = os.path.join(include_dir, "cache") + + all_headers = ( + glob.glob(os.path.join(args.input_dir, "include", "freetype", "*.h")) + + glob.glob( + os.path.join( + args.input_dir, "include", "freetype", "config", "*.h" + ) + ) + + glob.glob( + os.path.join( + args.input_dir, "include", "freetype", "cache", "*.h" + ) + ) + ) + + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + else: + assert os.path.isdir(args.output_dir), ( + "Not a directory: " + args.output_dir + ) + + cmds = [ + sys.executable, + "-m", + "docwriter", + "--prefix=ft2", + "--title=FreeType-" + args.version, + "--site=reference", + "--output=" + args.output_dir, + ] + all_headers + + print("Running docwriter...") + subprocess.check_call(cmds) + + print("Building static site...") + subprocess.check_call( + [sys.executable, "-m", "mkdocs", "build"], cwd=args.output_dir + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/parse_modules_cfg.py b/scripts/parse_modules_cfg.py new file mode 100644 index 000000000..e0f760561 --- /dev/null +++ b/scripts/parse_modules_cfg.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +"""Parse modules.cfg and dump its output either as ftmodule.h or a list of +base extensions. +""" + +from __future__ import print_function + +import argparse +import os +import re +import sys + +# Expected input: +# +# ... +# FONT_MODULES += <name> +# HINTING_MODULES += <name> +# RASTER_MODULES += <name> +# AUX_MODULES += <name> +# BASE_EXTENSIONS += <name> +# ... + + +def parse_modules_cfg(input_file): + + lists = { + "FONT_MODULES": [], + "HINTING_MODULES": [], + "RASTER_MODULES": [], + "AUX_MODULES": [], + "BASE_EXTENSIONS": [], + } + + for line in input_file.splitlines(): + line = line.rstrip() + # Ignore empty lines and those that start with a comment. + if not line or line[0] == "#": + continue + + items = line.split() + assert len(items) == 3 and items[1] == "+=", ( + "Unexpected input line [%s]" % line + ) + assert items[0] in lists, ( + "Unexpected configuration variable name " + items[0] + ) + + lists[items[0]].append(items[2]) + + return lists + + +def generate_ftmodule(lists): + result = "/* This is a generated file. */\n" + for driver in lists["FONT_MODULES"]: + if driver == "sfnt": # Special case for the sfnt 'driver'. + result += "FT_USE_MODULE( FT_Module_Class, sfnt_module_class )\n" + continue + + name = { + "truetype": "tt", + "type1": "t1", + "cid": "t1cid", + "type42": "t42", + "winfonts": "winfnt", + }.get(driver, driver) + result += ( + "FT_USE_MODULE( FT_Driver_ClassRec, %s_driver_class )\n" % name + ) + + for module in lists["HINTING_MODULES"]: + result += ( + "FT_USE_MODULE( FT_Module_Class, %s_module_class )\n" % module + ) + + for module in lists["RASTER_MODULES"]: + name = { + "raster": "ft_raster1", + "smooth": "ft_smooth", + }.get(module) + result += ( + "FT_USE_MODULE( FT_Renderer_Class, %s_renderer_class )\n" % name + ) + + for module in lists["AUX_MODULES"]: + if module in ("psaux", "psnames", "otvalid", "gxvalid"): + result += ( + "FT_USE_MODULE( FT_Module_Class, %s_module_class )\n" % module + ) + + result += "/* EOF */\n" + return result + + +def generate_main_modules(lists): + return "\n".join( + lists["FONT_MODULES"] + + lists["HINTING_MODULES"] + + lists["RASTER_MODULES"] + ) + + +def generate_aux_modules(lists): + return "\n".join(lists["AUX_MODULES"]) + + +def generate_base_extensions(lists): + return "\n".join(lists["BASE_EXTENSIONS"]) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument( + "--format", + required=True, + choices=( + "ftmodule.h", + "main-modules", + "aux-modules", + "base-extensions-list", + ), + help="Select output format.", + ) + + parser.add_argument( + "input", + metavar="CONFIGURE_RAW", + help="The input configure.raw file to parse.", + ) + + parser.add_argument("--output", help="Output file (default is stdout).") + + args = parser.parse_args() + with open(args.input) as f: + input_data = f.read() + + lists = parse_modules_cfg(input_data) + + if args.format == "ftmodule.h": + result = generate_ftmodule(lists) + elif args.format == "main-modules": + result = generate_main_modules(lists) + elif args.format == "aux-modules": + result = generate_aux_modules(lists) + elif args.format == "base-extensions-list": + result = generate_base_extensions(lists) + else: + assert False, "Invalid output format!" + + if args.output: + with open(args.output, "w") as f: + f.write(result) + else: + print(result) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/process_ftoption_h.py b/scripts/process_ftoption_h.py new file mode 100644 index 000000000..b5f80c314 --- /dev/null +++ b/scripts/process_ftoption_h.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +"""Toggle settings in `ftoption.h` file based on command-line arguments. + +This script takes an `ftoption.h` file as input and rewrites +`#define`/`#undef` lines in it based on `--enable=CONFIG_VARNAME` or +`--disable=CONFIG_VARNAME` arguments passed to it, where `CONFIG_VARNAME` is +configuration variable name, such as `FT_CONFIG_OPTION_USE_LZW`, that may +appear in the file. + +Note that if one of `CONFIG_VARNAME` is not found in the input file, this +script exits with an error message listing the missing variable names. +""" + +import argparse +import os +import re +import sys + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument( + "input", metavar="FTOPTION_H", help="Path to input ftoption.h file." + ) + + parser.add_argument("--output", help="Output to file instead of stdout.") + + parser.add_argument( + "--enable", + action="append", + default=[], + help="Enable a given build option (e.g. FT_CONFIG_OPTION_USE_LZW).", + ) + + parser.add_argument( + "--disable", + action="append", + default=[], + help="Disable a given build option.", + ) + + args = parser.parse_args() + + common_options = set(args.enable) & set(args.disable) + if common_options: + parser.error( + "Options cannot be both enabled and disabled: %s" + % sorted(common_options) + ) + return 1 + + with open(args.input) as f: + input_file = f.read() + + options_seen = set() + + new_lines = [] + for line in input_file.splitlines(): + # Expected formats: + # #define <CONFIG_VAR> + # /* #define <CONFIG_VAR> */ + # #undef <CONFIG_VAR> + line = line.rstrip() + if line.startswith("/* #define ") and line.endswith(" */"): + option_name = line[11:-3].strip() + option_enabled = False + elif line.startswith("#define "): + option_name = line[8:].strip() + option_enabled = True + elif line.startswith("#undef "): + option_name = line[7:].strip() + option_enabled = False + else: + new_lines.append(line) + continue + + options_seen.add(option_name) + if option_enabled and option_name in args.disable: + line = "#undef " + option_name + elif not option_enabled and option_name in args.enable: + line = "#define " + option_name + new_lines.append(line) + + result = "\n".join(new_lines) + + # Sanity check that all command-line options were actually processed. + cmdline_options = set(args.enable) | set(args.disable) + assert cmdline_options.issubset( + options_seen + ), "Could not find options in input file: " + ", ".join( + sorted(cmdline_options - options_seen) + ) + + if args.output: + with open(args.output, "w") as f: + f.write(result) + else: + print(result) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |