Actually, the size problem was that otvalid/gxvalid were always compiled
into the library.
I've improved meson.build to parse modules.cfg to get the list of all main
and auxiliary modules, to replicate what the Make-based build does.

Now the default build is 712 KiB for all build systems on my machine (Linux
x64 Kubuntu 19.10)

(NOTE: I'm really not a fan of modules.cfg and hope to get rid of it in the
future, but for now, it's better to keep things this way for consistency).

I also got rid of the builds/meson/ directory entirely. The unmodified
ftconfig.h is now used, with HAVE_UNISTD_H and HAVE_FCNTL_H added as
compile-time macros instead.


Le lun. 24 août 2020 à 23:27, David Turner <da...@freetype.org> a écrit :

>
>
> Le lun. 24 août 2020 à 22:34, Werner LEMBERG <w...@gnu.org> a écrit :
>
>>
>> Hello David,
>>
>>
>> > - builds/meson/ftconfig.h.in: template versions of
>> >   ftconfig.h to be used by the Meson build. It is processed
>> >   by Meson, which will turn #mesondefine statements into
>> >   #define / #undef depending on build configuration.
>>
>> having a meson-specific `ftconfig.h.in` file is not ideal.  I think we
>> should avoid that.
>>
>>
> Please change that.  A simple solution could be to always create
>> `ftconfig.h` from a template even for a `make devel` or an autoconf
>> build.
>>
>> I was just trying to reproduce what we're currently doing with
> builds/unix/ftconfig.h.in. I agree this is far from ideal though.
>
> I propose to get rid of this template entirely for Meson, by defining
> HAVE_UNISTD_H and HAVE_FCNTL_H
> as simple compiler arguments when building the library. There is really
> little reason for these to appear in ftconfig.h anyway.
>
> For the builds/unix/ftconfig.h.in case, there is also
> FT_USE_AUTOCONF_SIZEOF_TYPES, whose utility currently escapes me
> (i.e. FreeType's auto-detection of SIZEOF_INT and SIZEOF_LONG should be
> largely sufficient, at least for any C89 compiler).
> I didn't want to touch that, but we should consider removing these
> entirely, unless there is a practical benefit to it
> (since I suspect it is here for historical reasons). Any opinion on this?
> I'd be nice to get rid of this template as well.
>
>
>> Will check the rest of your code soon.
>>
>> > 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:                         784 KiB
>>
>> Can this be improved for meson?
>>
>> I think it's possible. I've been looking at it. The compiled objects are
> exactly the same, but the Meson build will link
> /usr/lib/x86_64-linux-gnu/libbz2.a
> /usr/lib/x86_64-linux-gnu/libbz2.so.1.0.4 (both of them), while the CMake
> build will only use the shared library version!
> And my libbz2.a is about 80 KiB which fits the difference. Looks like
> we're statically linked libz2.a by mistake here. Let me try to fix it.
>
>
>>     Werner
>>
>
From fa873def5c2162d5c5209ffaf0676fea5467288b Mon Sep 17 00:00:00 2001
From: David Turner <david.turner....@gmail.com>
Date: Sun, 17 May 2020 18:45:41 +0200
Subject: [build] Add Meson build project files.

This adds a few files to build the FreeType 2 library
with the Meson build system:

- meson.build: top-level Meson build file for the library.

- meson_options.txt: user-selectable options for the build.
  These can be printed with 'meson configure', and selected
  as '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: A script invoked by
  meson.build to process the original ftoption.h and
  enable or disabled configuration macro variables based
  on the available dependencies. This is similar to what
  other build systems are using (i.e. Meson configure_file()
  is not used here).

- scripts/extract_freetype_version.py: A script invoked by
  meson.build to extract the FreeType version number from
  <freetype/freetype.h>

- scripts/extract_libtool_version.py: A script invoked by
  meson.build to extract the libtool revision_info from
  builds/unix/configure.raw and generate the corresponding
  shared library suffix.

- scripts/generate_reference_docs.py: A script invoked
  by meson.build to generate the FreeType 2 reference
  documentation (using the docwriter / mkdocs packages
  which must be installed previously).

Example usage:

  # Configure Meson build to generate release binaries comparable to
  # to the ones from the autotools/make build system.
  meson setup build-meson --prefix=/ --buildtype=debugoptimized --strip -Db_ndebug=true

  # Build and install to /tmp/aa/, this includes a pkg-config file.
  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
---
 builds/unix/ftsystem.c              |   2 +-
 meson.build                         | 338 ++++++++++++++++++++++++++++
 meson_options.txt                   |   6 +
 scripts/extract_freetype_version.py |  94 ++++++++
 scripts/extract_libtool_version.py  |  87 +++++++
 scripts/generate_reference_docs.py  |  58 +++++
 scripts/parse_modules_cfg.py        | 139 ++++++++++++
 scripts/process_ftoption_h.py       |  92 ++++++++
 8 files changed, 815 insertions(+), 1 deletion(-)
 create mode 100644 meson.build
 create mode 100644 meson_options.txt
 create mode 100644 scripts/extract_freetype_version.py
 create mode 100644 scripts/extract_libtool_version.py
 create mode 100644 scripts/generate_reference_docs.py
 create mode 100644 scripts/parse_modules_cfg.py
 create mode 100644 scripts/process_ftoption_h.py

diff --git a/builds/unix/ftsystem.c b/builds/unix/ftsystem.c
index dc11dd1e9..400ab7b7c 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..183ea10ba
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,338 @@
+# Small experimental Meson project file for FreeType 2
+
+project('freetype2', 'c', 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 ]
+# Or even better, simplify the configuration since we don't really need
+# dynamic modules anymore.
+#
+
+# 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')
+foreach mod: ft_main_modules
+  source = mod
+  if mod == 'winfonts'
+    source = 'winfnt'
+  endif
+  if 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
+  # Handle special cases for source file names.
+  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
+
+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: Technically HAVE_STDINT_H seems to be obsolete / unused.
+has_unistd_h = cc.has_header('unistd.h')
+has_fcntl_h = cc.has_header('fcntl.h')
+
+if has_unistd_h
+  ft2_defines += [ '-DHAVE_UNISTD_H=1' ]
+endif
+if has_fcntl_h
+  ft2_defines += [ '-DHAVE_FCNTL_H' ]
+endif
+
+if has_unistd_h and has_fcntl_h
+  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() / 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: Wihtout '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 ]
+
+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@'
+    ],
+)
+
+# TODO(david): Generate distribution package.
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 000000000..e3626e276
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,6 @@
+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..b83f392a0
--- /dev/null
+++ b/scripts/extract_freetype_version.py
@@ -0,0 +1,94 @@
+#!/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_MINOR = re.compile(r'^#define\s+FREETYPE_MINOR\s+(.*)$')
+RE_PATCH = re.compile(r'^#define\s+FREETYPE_PATCH\s+(.*)$')
+
+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..b1a5be155
--- /dev/null
+++ b/scripts/extract_libtool_version.py
@@ -0,0 +1,87 @@
+#!/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..f8a2fbaf6
--- /dev/null
+++ b/scripts/generate_reference_docs.py
@@ -0,0 +1,58 @@
+#!/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..015f7eb12
--- /dev/null
+++ b/scripts/parse_modules_cfg.py
@@ -0,0 +1,139 @@
+#!/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..9613bf390
--- /dev/null
+++ b/scripts/process_ftoption_h.py
@@ -0,0 +1,92 @@
+#!/usr/bin/python
+"""Toggle settings in ftoption.h file based on command-line arguments.
+
+This script takes an ftoption.h file as input, and will rewrite #define/#undef
+lines in it based on --enable=CONFIG_VARNAME or --disable=CONFIG_VARNAME
+arguments passed to it, where CONFIG_VARNAME is a 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
+will exit 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())
-- 
2.20.1

Reply via email to