ref: 66978a58874169c6a4f2f984a7461e2901901784
parent: ab6a21b73314ef302299b47b1707f5b2d03826cf
author: David Turner <david.turner.dev@gmail.com>
date: Sun May 17 14:45:41 EDT 2020
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).
--- a/ChangeLog
+++ b/ChangeLog
@@ -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).
--- 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>
--- /dev/null
+++ b/meson.build
@@ -1,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
--- /dev/null
+++ b/meson_options.txt
@@ -1,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.')
--- /dev/null
+++ b/scripts/extract_freetype_version.py
@@ -1,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())
--- /dev/null
+++ b/scripts/extract_libtool_version.py
@@ -1,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())
--- /dev/null
+++ b/scripts/generate_reference_docs.py
@@ -1,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())
--- /dev/null
+++ b/scripts/parse_modules_cfg.py
@@ -1,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())
--- /dev/null
+++ b/scripts/process_ftoption_h.py
@@ -1,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())