Hi Alexei,

Le lun. 24 août 2020 à 17:27, Alexei Podtelezhnikov <apodt...@gmail.com> a
écrit :

> Hi David,
>
> Other build systems 'sed' to the main copy of ftoption.h, which is
> then installed and provides a record of compiled features. Werner
> convinced me that it is a good thing to have. It will be a nightmare
> to maintain them if each build system has its own version while lose
> track of compiled features. It has a lot of important documentation to
> keep in sync too. Would you please use 'sed' directly?
>
> An excellent point, thanks for pointing this out. Here's a new version of
the patch that does just that with a helper script.
I'd like to extend this scheme to more configuration variables in the
future, but after we complete this build transition.

- David

Thank you,
> Alexei
>
>
>
> On Mon, Aug 24, 2020 at 10:55 AM David Turner <da...@freetype.org> wrote:
> >
> > Hello Vincent,
> >
> > Sorry, was on vacation and pretty busy, but back now. On the topic of
> the Meson build, here's my latest update. This version has many
> improvements:
> >
> > - Proper pkg-config freetype2.pc generation and installation.
> > - Fixed shared library install suffix to match libtool-generated one
> (number computed from the version_info in build/unix/configure.raw).
> > - Auto-generation of ftmodule.h and list of base extensions, based on
> modules.cfg
> > - Generation of reference documentation (with "docs" target, but only
> inside the build directory).
> >
> > The last remaining todo is the ability to create a distribution package,
> though that might be handled by an independent script as well, since it
> needs to run "autogen.sh" in a temporary directory.
> >
> > Can you tell me about the Windows compilation failure? mmap() should not
> be an issue due to the checks in the build.meson file which should use
> src/base/ftsystem.c for Windows.
> > I've setup a Window virtual machine and will start testing with it soon.
> >
> > Regards
> >
> > - David
> >
> >
> > Le mar. 18 août 2020 à 21:11, Vincent Torri <vincent.to...@gmail.com> a
> écrit :
> >>
> >> Hello David
> >>
> >> any news on the meson build system ?
> >>
> >> regards
> >>
> >> Vincent Torri
>
>
>
> --
> Alexei A. Podtelezhnikov, PhD
>
From 047bb6fe02e82cfac3ae53db8137cadc2ca8a3eb 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>.

- 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.

- 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=release --optimization=2 --strip

  # 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:                         784 KiB
---
 builds/meson/ftconfig.h.in          |  52 +++++
 meson.build                         | 325 ++++++++++++++++++++++++++++
 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        | 127 +++++++++++
 scripts/process_ftoption_h.py       |  92 ++++++++
 8 files changed, 841 insertions(+)
 create mode 100644 builds/meson/ftconfig.h.in
 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/meson/ftconfig.h.in b/builds/meson/ftconfig.h.in
new file mode 100644
index 000000000..1b9026070
--- /dev/null
+++ b/builds/meson/ftconfig.h.in
@@ -0,0 +1,52 @@
+/****************************************************************************
+ *
+ * ftconfig.h.in
+ *
+ *   UNIX-specific configuration file (specification only).
+ *
+ * Copyright (C) 1996-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.
+ *
+ */
+
+
+  /**************************************************************************
+   *
+   * This header file contains a number of macro definitions that are used by
+   * the rest of the engine.  Most of the macros here are automatically
+   * determined at compile time, and you should not need to change it to port
+   * FreeType, except to compile the library with a non-ANSI compiler.
+   *
+   * Note however that if some specific modifications are needed, we advise
+   * you to place a modified copy in your build directory.
+   *
+   * The build directory is usually `builds/<system>`, and contains
+   * system-specific files that are always included first when building the
+   * library.
+   *
+   */
+
+#ifndef FTCONFIG_H_
+#define FTCONFIG_H_
+
+#include <ft2build.h>
+#include FT_CONFIG_OPTIONS_H
+#include FT_CONFIG_STANDARD_LIBRARY_H
+
+#mesondefine HAVE_UNISTD_H
+#mesondefine HAVE_FCNTL_H
+
+#include <freetype/config/integer-types.h>
+#include <freetype/config/public-macros.h>
+#include <freetype/config/mac-support.h>
+
+#endif /* FTCONFIG_H_ */
+
+
+/* END */
diff --git a/meson.build b/meson.build
new file mode 100644
index 000000000..d23c1e11b
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,325 @@
+# 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.
+ft2_sources += files([
+    'src/autofit/autofit.c',
+    'src/base/ftbase.c',
+    'src/bdf/bdf.c',
+    'src/cache/ftcache.c',
+    'src/cff/cff.c',
+    'src/cid/type1cid.c',
+    'src/gxvalid/gxvalid.c',
+    'src/lzw/ftlzw.c',
+    'src/otvalid/otvalid.c',
+    'src/pcf/pcf.c',
+    'src/pfr/pfr.c',
+    'src/psaux/psaux.c',
+    'src/pshinter/pshinter.c',
+    'src/psnames/psnames.c',
+    'src/raster/raster.c',
+    'src/sfnt/sfnt.c',
+    'src/smooth/smooth.c',
+    'src/truetype/truetype.c',
+    'src/type1/type1.c',
+    'src/type42/type42.c',
+    'src/winfonts/winfnt.c',
+])
+
+# 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/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',
+])
+
+# 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')
+
+configure_file(
+    input: 'builds/meson/ftconfig.h.in',
+    output: 'ftconfig.h',
+    configuration: {
+      'HAVE_UNISTD_H': has_unistd_h,
+      'HAVE_FCNTL_H': has_fcntl_h,
+    },
+    install_dir: 'include/freetype2/freetype/config')
+
+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
+bzip2_dep = meson.get_compiler('c').find_library('bz2', 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>' ]
+
+# Same for the ftconfig.h file.
+# It would be cleaner to generate it under
+# ${MESON_BUILD_DIR}/freetype/config/ftconfig.h
+ft2_defines += [ '-DFT_CONFIG_CONFIG_H=<ftconfig.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..339c32aa2
--- /dev/null
+++ b/scripts/parse_modules_cfg.py
@@ -0,0 +1,127 @@
+#!/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_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', '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 == '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