From 57a224f51d6c019e4ce5d75efb22f34a8330423e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 31 Jul 2020 07:26:11 +0000 Subject: [PATCH] Add Meson build system See https://mesonbuild.com --- .gitlab-ci.yml | 144 +++++++ .gitlab-ci/linux-mingw-w64-64bit.txt | 20 + Makefile.am | 34 +- conf.d/link_confs.py | 33 ++ conf.d/meson.build | 92 +++++ conf.d/write-35-lang-normalize-conf.py | 36 ++ doc/edit-sgml.py | 160 ++++++++ doc/extract-man-list.py | 90 ++++ doc/meson.build | 167 ++++++++ doc/run-quiet.py | 36 ++ fc-cache/fc-cache.c | 4 + fc-cache/meson.build | 8 + fc-case/fc-case.py | 240 +++++++++++ fc-case/meson.build | 4 + fc-cat/meson.build | 8 + fc-conflist/meson.build | 8 + fc-lang/fc-lang.py | 387 ++++++++++++++++++ fc-lang/meson.build | 256 ++++++++++++ fc-list/meson.build | 8 + fc-match/meson.build | 8 + fc-pattern/meson.build | 8 + fc-query/meson.build | 9 + fc-scan/meson.build | 9 + fc-validate/meson.build | 9 + install-cache.py | 11 + its/meson.build | 6 + meson-cc-tests/flexible-array-member-test.c | 14 + meson-cc-tests/intel-atomic-primitives-test.c | 6 + meson-cc-tests/solaris-atomic-operations.c | 8 + meson.build | 362 ++++++++++++++++ meson_options.txt | 13 + po-conf/meson.build | 3 + po/meson.build | 3 + src/cutout.py | 33 ++ src/fccompat.c | 85 ++++ src/fcstdint.h.in | 1 + src/fcwindows.h | 48 +++ src/fontconfig.def.in | 234 +++++++++++ src/makealias.py | 71 ++++ src/meson.build | 91 ++++ stdin_wrapper.py | 20 + subprojects/.gitignore | 6 + subprojects/expat.wrap | 10 + subprojects/freetype2.wrap | 5 + subprojects/gperf.wrap | 5 + subprojects/libpng.wrap | 10 + subprojects/zlib.wrap | 10 + test/meson.build | 46 +++ 48 files changed, 2878 insertions(+), 1 deletion(-) create mode 100644 .gitlab-ci/linux-mingw-w64-64bit.txt create mode 100644 conf.d/link_confs.py create mode 100644 conf.d/meson.build create mode 100755 conf.d/write-35-lang-normalize-conf.py create mode 100755 doc/edit-sgml.py create mode 100755 doc/extract-man-list.py create mode 100644 doc/meson.build create mode 100755 doc/run-quiet.py create mode 100644 fc-cache/meson.build create mode 100755 fc-case/fc-case.py create mode 100644 fc-case/meson.build create mode 100644 fc-cat/meson.build create mode 100644 fc-conflist/meson.build create mode 100755 fc-lang/fc-lang.py create mode 100644 fc-lang/meson.build create mode 100644 fc-list/meson.build create mode 100644 fc-match/meson.build create mode 100644 fc-pattern/meson.build create mode 100644 fc-query/meson.build create mode 100644 fc-scan/meson.build create mode 100644 fc-validate/meson.build create mode 100644 install-cache.py create mode 100644 its/meson.build create mode 100644 meson-cc-tests/flexible-array-member-test.c create mode 100644 meson-cc-tests/intel-atomic-primitives-test.c create mode 100644 meson-cc-tests/solaris-atomic-operations.c create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 po-conf/meson.build create mode 100644 po/meson.build create mode 100644 src/cutout.py create mode 100644 src/fcstdint.h.in create mode 100644 src/fontconfig.def.in create mode 100755 src/makealias.py create mode 100644 src/meson.build create mode 100644 stdin_wrapper.py create mode 100644 subprojects/.gitignore create mode 100644 subprojects/expat.wrap create mode 100644 subprojects/freetype2.wrap create mode 100644 subprojects/gperf.wrap create mode 100644 subprojects/libpng.wrap create mode 100644 subprojects/zlib.wrap create mode 100644 test/meson.build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8e7dcf..f8e2c03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ before_script: - dnf -y upgrade --disablerepo=rawhide-modular - dnf -y install --disablerepo=rawhide-modular --allowerasing --skip-broken @buildsys-build autoconf automake libtool gettext gettext-devel gperf expat-devel freetype-devel json-c-devel git docbook-utils docbook-utils-pdf bubblewrap - dnf -y install --disablerepo=rawhide-modular --allowerasing --skip-broken mingw64-gettext mingw64-freetype mingw64-expat wine + - dnf -y install --disablerepo=rawhide-modular --allowerasing --skip-broken meson ninja-build wget shared-build: stage: build @@ -78,3 +79,146 @@ mingw-build: - build-*/test/*.log - build-*/test/*.trs - build-*/test/out* +meson-shared-build: + image: fedora:latest + stage: build + script: + - export BUILD_ID="fontconfig-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" + - export PREFIX="$(pwd)/prefix-$BUILD_ID" + - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - export MAKEFLAGS="-j4" + - meson --prefix="$PREFIX" --default-library=shared "$BUILDDIR" + - ninja -C "$BUILDDIR" + - ninja -C "$BUILDDIR" test + - ninja -C "$BUILDDIR" install + artifacts: + name: fontconfig-$CI_COMMIT_SHA-$CI_JOB_ID + when: always + paths: + - build-*/meson-logs/*txt + - prefix-* +meson-static-build: + image: fedora:latest + stage: build + script: + - export BUILD_ID="fontconfig-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" + - export PREFIX="$(pwd)/prefix-$BUILD_ID" + - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - export MAKEFLAGS="-j4" + - meson --prefix="$PREFIX" --default-library=static "$BUILDDIR" + - ninja -C "$BUILDDIR" + - ninja -C "$BUILDDIR" test + - ninja -C "$BUILDDIR" install + artifacts: + name: fontconfig-$CI_COMMIT_SHA-$CI_JOB_ID + when: always + paths: + - build-*/meson-logs/*txt + - prefix-* +meson-mingw-w64-build: + image: fedora:latest + stage: build + script: + - export BUILD_ID="fontconfig-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" + - export PREFIX="$(pwd)/prefix-$BUILD_ID" + - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - meson --prefix="$PREFIX" "$BUILDDIR" --cross-file .gitlab-ci/linux-mingw-w64-64bit.txt + - ninja -C "$BUILDDIR" + - ninja -C "$BUILDDIR" test + # install doesn't work, fccache problems, but autotools ci doesn't do that either + # - ninja -C "$BUILDDIR" install + artifacts: + name: fontconfig-$CI_COMMIT_SHA-$CI_JOB_ID + when: always + paths: + - build-*/meson-logs/*txt + - prefix-* + +# FIXME: fontconfig should probably get its own image +.build meson windows: + image: 'registry.freedesktop.org/gstreamer/gst-ci/amd64/windows:v10' + stage: 'build' + tags: + - 'docker' + - 'windows' + - '1809' + variables: + # Make sure any failure in PowerShell scripts is fatal + ErrorActionPreference: 'Stop' + WarningPreference: 'Stop' + # Uncomment the following key if need to pass custom args, as well with the + # $env:MESON_ARGS line in the `script:` blocks + # MESON_ARGS: >- + # -Dfoo=enabled + # -Dbar=disabled + before_script: + # Make sure meson is up to date, so we don't need to rebuild the image with each release + - pip3 install -U meson + script: + # For some reason, options are separated by newline instead of space, so we + # have to replace them first. + # - $env:MESON_ARGS = $env:MESON_ARGS.replace("`n"," ") + # Gitlab executes PowerShell in docker, but VsDevCmd.bat is a batch script. + # Environment variables substitutions is done by PowerShell before calling + # cmd.exe, that's why we use $env:FOO instead of %FOO% + - cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=$env:ARCH && + meson build $env:MESON_ARGS && + ninja -C build && + ninja -C build test" + +meson vs2017 amd64: + extends: '.build meson windows' + variables: + ARCH: 'amd64' + +meson vs2017 x86: + extends: '.build meson windows' + variables: + ARCH: 'x86' + +meson macos: + stage: 'build' + tags: + - gst-macos-10.15 + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_SHA}" + expire_in: '5 days' + when: 'always' + paths: + - "build/meson-logs/*txt" + before_script: + - pip3 install --upgrade pip + # Make sure meson is up to date + - pip3 install -U meson + # Need to install certificates for python + - pip3 install --upgrade certifi + # Anther way t install certificates + - open /Applications/Python\ 3.8/Install\ Certificates.command + # Get ninja + - curl -L -o ninja-mac.zip https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-mac.zip + - unzip ninja-mac.zip + - sudo cp ninja /usr/local/bin + script: + - CERT_PATH=$(python3 -m certifi) && export SSL_CERT_FILE=${CERT_PATH} && export REQUESTS_CA_BUNDLE=${CERT_PATH} && meson build + - ninja -C build + - ninja -C build test + +# msys infrastructure is a bit broken, disable for now +meson msys2: + extends: '.build meson windows' + when: 'manual' + allow_failure: true + script: + # For some reason, options are separated by newline instead of space, so we + # have to replace them first. + # - $env:MESON_ARGS = $env:MESON_ARGS.replace("`n"," ") + + - $env:PATH += ";C:\msys64\usr\bin;C:\msys64\mingw64/bin;C:\msys64\mingw32/bin" + # XXX: Copied from https://gitlab.freedesktop.org/gstreamer/gst-ci/blob/master/gitlab/ci_template.yml#L487 + # For some reason docker build hangs if this is included in the image, needs more troubleshooting + - C:\msys64\usr\bin\bash -c "pacman-key --init && pacman-key --populate msys2 && pacman-key --refresh-keys || true" + - C:\msys64\usr\bin\bash -c "pacman -Syuu --noconfirm" + - C:\msys64\usr\bin\bash -c "pacman -Sy --noconfirm --needed mingw-w64-x86_64-toolchain ninja" + - C:\msys64\usr\bin\bash -c "meson build $env:MESON_ARGS && + ninja -C build && + ninja -C build test" diff --git a/.gitlab-ci/linux-mingw-w64-64bit.txt b/.gitlab-ci/linux-mingw-w64-64bit.txt new file mode 100644 index 0000000..0535654 --- /dev/null +++ b/.gitlab-ci/linux-mingw-w64-64bit.txt @@ -0,0 +1,20 @@ +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[properties] +c_args = [] +c_link_args = [] + +[binaries] +c = 'x86_64-w64-mingw32-gcc' +cpp = 'x86_64-w64-mingw32-g++' +ar = 'x86_64-w64-mingw32-ar' +ld = 'x86_64-w64-mingw32-ld' +objcopy = 'x86_64-w64-mingw32-objcopy' +strip = 'x86_64-w64-mingw32-strip' +pkgconfig = 'x86_64-w64-mingw32-pkg-config' +windres = 'x86_64-w64-mingw32-windres' +# exe_wrapper = 'wine64' \ No newline at end of file diff --git a/Makefile.am b/Makefile.am index 91bf1a1..71ec6cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,12 +31,34 @@ endif ACLOCAL_AMFLAGS = -I m4 +MESON_FILES = \ + conf.d/link_confs.py \ + conf.d/write-35-lang-normalize-conf.py \ + doc/edit-sgml.py \ + doc/extract-man-list.py \ + doc/run-quiet.py \ + fc-case/fc-case.py \ + fc-lang/fc-lang.py \ + install-cache.py \ + meson.build \ + meson_options.txt \ + src/cutout.py \ + src/fcstdint.h.in \ + src/fcwindows.h \ + src/fontconfig.def.in \ + src/makealias.py \ + stdin_wrapper.py \ + $(wildcard $(srcdir)/*/meson.build) \ + $(wildcard $(srcdir)/meson-cc-tests/*) \ + $(wildcard $(srcdir)/subprojects/*.wrap) + EXTRA_DIST = config.rpath \ fontconfig.pc.in \ fonts.conf.in \ fonts.dtd \ fontconfig-zip.in \ - config-fixups.h + config-fixups.h \ + $(MESON_FILES) CLEANFILES = fonts.conf DISTCLEANFILES = config.cache doltcompile MAINTAINERCLEANFILES = \ @@ -153,4 +175,14 @@ debuild-dirs: distdir DISTCHECK_CONFIGURE_FLAGS = +check-versions: + @$(GREP) -e '^\s*version\s*:\s*.'$(VERSION)'.,' $(srcdir)/meson.build >/dev/null || { \ + echo "======================================================================================"; \ + echo "Meson version does not seem to match autotools version $(VERSION), update meson.build!"; \ + echo "======================================================================================"; \ + exit 1; \ + } + +all-local: check-versions + -include $(top_srcdir)/git.mk diff --git a/conf.d/link_confs.py b/conf.d/link_confs.py new file mode 100644 index 0000000..0c42efb --- /dev/null +++ b/conf.d/link_confs.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('availpath') + parser.add_argument('confpath') + parser.add_argument('links', nargs='+') + args = parser.parse_args() + + confpath = os.path.join(os.environ['MESON_INSTALL_DESTDIR_PREFIX'], args.confpath) + + if not os.path.exists(confpath): + os.makedirs(confpath) + + for link in args.links: + src = os.path.join(args.availpath, link) + dst = os.path.join(confpath, link) + try: + os.symlink(src, dst) + except NotImplementedError: + # Not supported on this version of Windows + break + except OSError as e: + # Symlink privileges are not available + if len(e.args) == 1 and 'privilege' in e.args[0]: + break + raise + except FileExistsError: + pass diff --git a/conf.d/meson.build b/conf.d/meson.build new file mode 100644 index 0000000..2cb144e --- /dev/null +++ b/conf.d/meson.build @@ -0,0 +1,92 @@ +conf_files = [ + '05-reset-dirs-sample.conf', + '09-autohint-if-no-hinting.conf', + '10-autohint.conf', + '10-hinting-full.conf', + '10-hinting-medium.conf', + '10-hinting-none.conf', + '10-hinting-slight.conf', + '10-no-sub-pixel.conf', + '10-scale-bitmap-fonts.conf', + '10-sub-pixel-bgr.conf', + '10-sub-pixel-rgb.conf', + '10-sub-pixel-vbgr.conf', + '10-sub-pixel-vrgb.conf', + '10-unhinted.conf', + '11-lcdfilter-default.conf', + '11-lcdfilter-legacy.conf', + '11-lcdfilter-light.conf', + '20-unhint-small-vera.conf', + '25-unhint-nonlatin.conf', + '30-metric-aliases.conf', + '40-nonlatin.conf', + '45-generic.conf', + '45-latin.conf', + '49-sansserif.conf', + '50-user.conf', + '51-local.conf', + '60-generic.conf', + '60-latin.conf', + '65-fonts-persian.conf', + '65-khmer.conf', + '65-nonlatin.conf', + '69-unifont.conf', + '70-no-bitmaps.conf', + '70-yes-bitmaps.conf', + '80-delicious.conf', + '90-synthetic.conf', +] + +preferred_hinting = 'slight' + +conf_links = [ + '10-hinting-@0@.conf'.format(preferred_hinting), + '10-scale-bitmap-fonts.conf', + '20-unhint-small-vera.conf', + '30-metric-aliases.conf', + '40-nonlatin.conf', + '45-generic.conf', + '45-latin.conf', + '49-sansserif.conf', + '50-user.conf', + '51-local.conf', + '60-generic.conf', + '60-latin.conf', + '65-fonts-persian.conf', + '65-nonlatin.conf', + '69-unifont.conf', + '80-delicious.conf', + '90-synthetic.conf', +] + +install_data(conf_files, install_dir: join_paths(get_option('datadir'), 'fontconfig/conf.avail')) + +meson.add_install_script('link_confs.py', + join_paths(get_option('prefix'), get_option('datadir'), 'fontconfig/conf.avail'), + join_paths(get_option('sysconfdir'), 'fonts', 'conf.d'), + conf_links, +) + +# 35-lang-normalize.conf +orths = [] +foreach o : orth_files # orth_files is from fc-lang/meson.build + o = o.split('.')[0] # strip filename suffix + if not o.contains('_') # ignore those with an underscore + orths += [o] + endif +endforeach + +custom_target('35-lang-normalize.conf', + output: '35-lang-normalize.conf', + command: [find_program('write-35-lang-normalize-conf.py'), ','.join(orths), '@OUTPUT@'], + install_dir: join_paths(get_option('datadir'), 'fontconfig/conf.avail'), + install: true) + +# README +readme_cdata = configuration_data() +readme_cdata.set('TEMPLATEDIR', fc_templatedir) +configure_file(output: 'README', + input: 'README.in', + configuration: readme_cdata, + install_dir: join_paths(get_option('sysconfdir'), 'fonts', 'conf.d'), + install: true) diff --git a/conf.d/write-35-lang-normalize-conf.py b/conf.d/write-35-lang-normalize-conf.py new file mode 100755 index 0000000..33d0d0e --- /dev/null +++ b/conf.d/write-35-lang-normalize-conf.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# fontconfig write-35-lang-normalize-conf.py + +import os +import sys + +if len(sys.argv) < 2: + print('ERROR: usage: {} ORTH_LIST [OUTPUT.CONF]'.format(sys.argv[0])) + sys.exit(-1) + +orth_list_unsorted = sys.argv[1].split(',') + +if len(sys.argv) > 2 and sys.argv[2] != '-': + f_out = open(sys.argv[2], 'w', encoding='utf8') +else: + f_out = sys.stdout + +orth_list = sorted(sys.argv[1].split(',')) + +print('', file=f_out) +print('', file=f_out) +print('', file=f_out) + +for o in orth_list: + print(f' ', file=f_out) + print(f' ', file=f_out) + print(f' {o}', file=f_out) + print(f' {o}', file=f_out) + print(f' ', file=f_out) + +print('', file=f_out) + +f_out.close() + +sys.exit(0) diff --git a/doc/edit-sgml.py b/doc/edit-sgml.py new file mode 100755 index 0000000..e83c008 --- /dev/null +++ b/doc/edit-sgml.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# +# fontconfig/doc/edit-sgml.py +# +# Copyright © 2003 Keith Packard +# Copyright © 2020 Tim-Philipp Müller +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of the author(s) not be used in +# advertising or publicity pertaining to distribution of the software without +# specific, written prior permission. The authors make no +# representations about the suitability of this software for any purpose. It +# is provided "as is" without express or implied warranty. +# +# THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +import argparse +import sys +import re + +parser = argparse.ArgumentParser() +parser.add_argument('template') +parser.add_argument('input') +parser.add_argument('output') + +args = parser.parse_known_args() + +template_fn = args[0].template +output_fn = args[0].output +input_fn = args[0].input + +# ------------- +# Read template +# ------------- + +with open(template_fn, 'r', encoding='utf8') as f: + template_text = f.read() + +template_lines = template_text.strip().split('\n') + +# ------------------------------------- +# Read replacement sets from .fncs file +# ------------------------------------- + +replacement_sets = [] + +# TODO: also allow '-' for stdin +with open(input_fn, 'r', encoding='utf8') as f: + fncs_text = f.read() + +# split into replacement sets +fncs_chunks = fncs_text.strip().split('@@') + +for chunk in fncs_chunks: + # get rid of any preamble such as license and FcFreeTypeQueryAll decl in fcfreetype.fncs + start = chunk.find('@') + if start: + chunk = chunk[start:] + + # split at '@' and remove empty lines (keep it simple instead of doing fancy + # things with regular expression matches, we control the input after all) + lines = [line for line in chunk.split('@') if line.strip()] + + replacement_set = {} + + while lines: + tag = lines.pop(0).strip() + # FIXME: this hard codes the tag used in funcs.sgml - we're lazy + if tag.startswith('PROTOTYPE'): + text = '' + else: + text = lines.pop(0).strip() + if text.endswith('%'): + text = text[:-1] + ' ' + + replacement_set[tag] = text + + if replacement_set: + replacement_sets += [replacement_set] + +# ---------------- +# Open output file +# ---------------- + +if output_fn == '-': + fout = sys.stdout +else: + fout = open(output_fn, "w", encoding='utf8') + +# ---------------- +# Process template +# ---------------- + +def do_replace(template_lines, rep, tag_suffix=''): + skip_tag = None + skip_lines = False + loop_lines = [] + loop_tag = None + + for t_line in template_lines: + # This makes processing easier and is the case for our templates + if t_line.startswith('@') and not t_line.endswith('@'): + sys.exit('Template lines starting with @ are expected to end with @, please fix me!') + + if loop_tag: + loop_lines += [t_line] + + # Check if line starts with a directive + if t_line.startswith('@?'): + tag = t_line[2:-1] + tag_suffix + if skip_tag: + sys.exit('Recursive skipping not supported, please fix me!') + skip_tag = tag + skip_lines = tag not in rep + elif t_line.startswith('@:'): + if not skip_tag: + sys.exit('Skip else but no active skip list?!') + skip_lines = skip_tag in rep + elif t_line.startswith('@;'): + if not skip_tag: + sys.exit('Skip end but no active skip list?!') + skip_tag = None + skip_lines = False + elif t_line.startswith('@{'): + if loop_tag or tag_suffix != '': + sys.exit('Recursive looping not supported, please fix me!') + loop_tag = t_line[2:-1] + elif t_line.startswith('@}'): + tag = t_line[2:-1] + tag_suffix + if not loop_tag: + sys.exit('Loop end but no active loop?!') + if loop_tag != tag: + sys.exit(f'Loop end but loop tag mismatch: {loop_tag} != {tag}!') + loop_lines.pop() # remove loop end directive + suffix = '+' + while loop_tag + suffix in rep: + do_replace(loop_lines, rep, suffix) + suffix += '+' + loop_tag = None + loop_lines = [] + else: + if not skip_lines: + # special-case inline optional substitution (hard-codes specific pattern in funcs.sgml because we're lazy) + output_line = re.sub(r'@\?(RET)@@RET@@:@(void)@;@', lambda m: rep.get(m.group(1) + tag_suffix, m.group(2)), t_line) + # replace any substitution tags with their respective substitution text + output_line = re.sub(r'@(\w+)@', lambda m: rep.get(m.group(1) + tag_suffix, ''), output_line) + print(output_line, file=fout) + +# process template for each replacement set +for rep in replacement_sets: + do_replace(template_lines, rep) diff --git a/doc/extract-man-list.py b/doc/extract-man-list.py new file mode 100755 index 0000000..9298041 --- /dev/null +++ b/doc/extract-man-list.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# fontconfig/doc/extract-man-list.py +# +# Parses .fncs files and extracts list of man pages that will be generated +# +# Copyright © 2020 Tim-Philipp Müller +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of the author(s) not be used in +# advertising or publicity pertaining to distribution of the software without +# specific, written prior permission. The authors make no +# representations about the suitability of this software for any purpose. It +# is provided "as is" without express or implied warranty. +# +# THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +import sys +import re + +replacement_sets = [] + +# ------------------------------------- +# Read replacement sets from .fncs file +# ------------------------------------- + +def read_fncs_file(fn): + global replacement_sets + + with open(fn, 'r', encoding='utf8') as f: + fncs_text = f.read() + + # split into replacement sets + fncs_chunks = fncs_text.strip().split('@@') + + for chunk in fncs_chunks: + # get rid of any preamble such as license and FcFreeTypeQueryAll decl in fcfreetype.fncs + start = chunk.find('@') + if start: + chunk = chunk[start:] + + # split at '@' and remove empty lines (keep it simple instead of doing fancy + # things with regular expression matches, we control the input after all) + lines = [line for line in chunk.split('@') if line.strip()] + + replacement_set = {} + + while lines: + tag = lines.pop(0).strip() + # FIXME: this hard codes the tag used in funcs.sgml - we're lazy + if tag.startswith('PROTOTYPE'): + text = '' + else: + text = lines.pop(0).strip() + if text.endswith('%'): + text = text[:-1] + ' ' + + replacement_set[tag] = text + + if replacement_set: + replacement_sets += [replacement_set] + +# ---------------------------------------------------------------------------- +# Main +# ---------------------------------------------------------------------------- + +if len(sys.argv) < 2: + sys.exit('Usage: {} FILE1.FNCS [FILE2.FNCS...]'.format(sys.argv[0])) + +fout = sys.stdout + +for input_fn in sys.argv[1:]: + read_fncs_file(input_fn) + +# process template for each replacement set +for rep in replacement_sets: + if 'FUNC+' in rep: + man_page_title = rep.get('TITLE', rep['FUNC']) + else: + man_page_title = rep['FUNC'] + print(man_page_title) diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..8b74c19 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,167 @@ +docbook2man = find_program('docbook2man', required: get_option('doc-man')) +docbook2txt = find_program('docbook2txt', required: get_option('doc-txt')) +docbook2pdf = find_program('docbook2pdf', required: get_option('doc-pdf')) +docbook2html = find_program('docbook2html', required: get_option('doc-html')) + +# docbook is very spammy +run_quiet = find_program('run-quiet.py') + +# .fncs files +doc_funcs_fncs = [ + 'fcatomic', + 'fcblanks', + 'fccache', + 'fccharset', + 'fcconfig', + 'fcconstant', + 'fcdircache', + 'fcfile', + 'fcfontset', + 'fcformat', + 'fcfreetype', + 'fcinit', + 'fclangset', + 'fcmatrix', + 'fcobjectset', + 'fcobjecttype', + 'fcpattern', + 'fcrange', + 'fcstring', + 'fcstrset', + 'fcvalue', + 'fcweight', +] + +fncs_files = [] +foreach f : doc_funcs_fncs + fncs_files += files('@0@.fncs'.format(f)) +endforeach + +man_pages = [] + +extract_man_list = find_program('extract-man-list.py') +man_list = run_command(extract_man_list, fncs_files, check: true).stdout().split() + +foreach m : man_list + man_pages += ['@0@.3'.format(m)] +endforeach + +# Generate sgml pages for funcs +edit_sgml = find_program('edit-sgml.py') + +# copy into build directory, it includes generated files from build directory +fontconfig_devel_sgml = configure_file(output: 'fontconfig-devel.sgml', + input: 'fontconfig-devel.sgml', + copy: true) + +fontconfig_user_sgml = configure_file(output: 'fontconfig-user.sgml', + input: 'fontconfig-user.sgml', + copy: true) + +version_conf = configuration_data() +version_conf.set('VERSION', meson.project_version()) + +configure_file(output: 'version.sgml', + input: 'version.sgml.in', + configuration: version_conf) + +confdir_conf = configuration_data() +confdir_conf.set('BASECONFIGDIR', fc_configdir) + +confdir_sgml = configure_file(output: 'confdir.sgml', + input: 'confdir.sgml.in', + configuration: confdir_conf) + +funcs_sgml = [] + +foreach f : doc_funcs_fncs + funcs_sgml += [custom_target('@0@.sgml'.format(f), + input: [files('func.sgml'), files('@0@.fncs'.format(f))], + output: '@0@.sgml'.format(f), + command: [edit_sgml, '@INPUT0@', '@INPUT1@', '@OUTPUT@'], + install: false)] +endforeach + +if docbook2man.found() + custom_target('devel-man', + input: [fontconfig_devel_sgml, funcs_sgml], + output: man_pages, + command: [run_quiet, docbook2man, '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('mandir') / 'man3', + install: true) + + # fonts.conf(5) + custom_target('fonts-conf-5-man-page', + input: [fontconfig_user_sgml], + output: 'fonts-conf.5', + command: [run_quiet, docbook2man, '@INPUT0@', '--output', '@OUTDIR@'], + install_dir: get_option('mandir') / 'man5', + build_by_default: true, + install: true) + + # Generate man pages for tools + foreach t : tools_man_pages + # docbook2man doesn't seem to have a --quiet option unfortunately + custom_target('@0@-man-page'.format(t), + input: '../@0@/@0@.sgml'.format(t), + output: '@0@.1'.format(t), + command: [run_quiet, docbook2man, '@INPUT@', '--output', '@OUTDIR@'], + install_dir: get_option('mandir') / 'man1', + install: true) + endforeach +endif + +if docbook2pdf.found() + custom_target('devel-pdf', + input: [fontconfig_devel_sgml, funcs_sgml], + output: 'fontconfig-devel.pdf', + command: [run_quiet, docbook2pdf, '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) + + custom_target('user-pdf', + input: [fontconfig_user_sgml, funcs_sgml], + output: 'fontconfig-user.pdf', + command: [run_quiet, docbook2pdf, '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) +endif + +if docbook2txt.found() + custom_target('devel-txt', + input: [fontconfig_devel_sgml, funcs_sgml], + output: 'fontconfig-devel.txt', + command: [run_quiet, docbook2txt, '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) + + custom_target('user-txt', + input: [fontconfig_user_sgml, funcs_sgml], + output: 'fontconfig-user.txt', + command: [run_quiet, docbook2txt, '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) +endif + +if docbook2html.found() + custom_target('devel-html', + input: [fontconfig_devel_sgml, funcs_sgml], + output: 'fontconfig-devel.html', + command: [run_quiet, docbook2html, '--nochunks', '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) + + custom_target('user-html', + input: [fontconfig_user_sgml, funcs_sgml], + output: 'fontconfig-user.html', + command: [run_quiet, docbook2html, '--nochunks', '@INPUT0@', '--output', '@OUTDIR@'], + build_by_default: true, + install_dir: get_option('datadir') / 'doc' / 'fontconfig', + install: true) +endif diff --git a/doc/run-quiet.py b/doc/run-quiet.py new file mode 100755 index 0000000..dff5dae --- /dev/null +++ b/doc/run-quiet.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# fontconfig/doc/run-quiet.py +# +# Runs command and discards anything it sends to stdout +# +# Copyright © 2020 Tim-Philipp Müller +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of the author(s) not be used in +# advertising or publicity pertaining to distribution of the software without +# specific, written prior permission. The authors make no +# representations about the suitability of this software for any purpose. It +# is provided "as is" without express or implied warranty. +# +# THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +import subprocess +import sys +import os + +if len(sys.argv) < 2: + sys.exit('Usage: {} PROGRAM [ARGS..]'.format(sys.argv[0])) + +command = sys.argv[1:] + +with open(os.devnull, 'w') as out: + sys.exit(subprocess.run(command, stdout=out).returncode) diff --git a/fc-cache/fc-cache.c b/fc-cache/fc-cache.c index 2e4eeb2..a99adba 100644 --- a/fc-cache/fc-cache.c +++ b/fc-cache/fc-cache.c @@ -66,6 +66,10 @@ #define O_BINARY 0 #endif +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + #ifndef HAVE_GETOPT #define HAVE_GETOPT 0 #endif diff --git a/fc-cache/meson.build b/fc-cache/meson.build new file mode 100644 index 0000000..9e0fe72 --- /dev/null +++ b/fc-cache/meson.build @@ -0,0 +1,8 @@ +fccache = executable('fc-cache', ['fc-cache.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-cache'] diff --git a/fc-case/fc-case.py b/fc-case/fc-case.py new file mode 100755 index 0000000..360bd32 --- /dev/null +++ b/fc-case/fc-case.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# +# fontconfig/fc-case/fc-case.py +# +# Copyright © 2004 Keith Packard +# Copyright © 2019 Tim-Philipp Müller +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of the author(s) not be used in +# advertising or publicity pertaining to distribution of the software without +# specific, written prior permission. The authors make no +# representations about the suitability of this software for any purpose. It +# is provided "as is" without express or implied warranty. +# +# THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +from enum import Enum +import argparse +import string +import sys + +class CaseFoldClass(Enum): + COMMON = 1 + FULL = 2 + SIMPLE = 3 + TURKIC = 4 + +class CaseFoldMethod(Enum): + RANGE = 0 + EVEN_ODD = 1 + FULL = 2 + +caseFoldClassMap = { + 'C' : CaseFoldClass.COMMON, + 'F' : CaseFoldClass.FULL, + 'S' : CaseFoldClass.SIMPLE, + 'T' : CaseFoldClass.TURKIC +} + +folds = [] + +def ucs4_to_utf8(ucs4): + utf8_rep = [] + + if ucs4 < 0x80: + utf8_rep.append(ucs4) + bits = -6 + elif ucs4 < 0x800: + utf8_rep.append(((ucs4 >> 6) & 0x1F) | 0xC0) + bits = 0 + elif ucs4 < 0x10000: + utf8_rep.append(((ucs4 >> 12) & 0x0F) | 0xE0) + bits = 6 + elif ucs4 < 0x200000: + utf8_rep.append(((ucs4 >> 18) & 0x07) | 0xF0) + bits = 12 + elif ucs4 < 0x4000000: + utf8_rep.append(((ucs4 >> 24) & 0x03) | 0xF8) + bits = 18 + elif ucs4 < 0x80000000: + utf8_rep.append(((ucs4 >> 30) & 0x01) | 0xFC) + bits = 24 + else: + return []; + + while bits >= 0: + utf8_rep.append(((ucs4 >> bits) & 0x3F) | 0x80) + bits-= 6 + + return utf8_rep + +def utf8_size(ucs4): + return len(ucs4_to_utf8(ucs4)) + +case_fold_method_name_map = { + CaseFoldMethod.RANGE: 'FC_CASE_FOLD_RANGE,', + CaseFoldMethod.EVEN_ODD: 'FC_CASE_FOLD_EVEN_ODD,', + CaseFoldMethod.FULL: 'FC_CASE_FOLD_FULL,', +} + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('case_folding_file') + parser.add_argument('--template', dest='template_file', default=None) + parser.add_argument('--output', dest='output_file', default=None) + + args = parser.parse_args() + + minFoldChar = None + maxFoldChar = None + fold = None + + foldChars = [] + maxFoldChars = 0 + + maxExpand = 0 + + # Read the standard Unicode CaseFolding.txt file + with open(args.case_folding_file, 'r', encoding='utf-8') as casefile: + for cnt, line in enumerate(casefile): + if not line or not line[0] in string.hexdigits: + continue + + # print('Line {}: {}'.format(cnt, line.strip())) + + tokens = line.split('; ') + + if len(tokens) < 3: + print('Not enough tokens in line {}'.format(cnt), file=sys.stderr) + sys.exit(1) + + # Get upper case value + upper = int(tokens.pop(0), 16) + + # Get class + cfclass = caseFoldClassMap[tokens.pop(0)] + + # Get list of result characters + lower = list(map(lambda s: int(s,16), tokens.pop(0).split())) + + # print('\t----> {:04X} {} {}'.format(upper, cfclass, lower)) + + if not minFoldChar: + minFoldChar = upper + + maxFoldChar = upper; + + if cfclass in [CaseFoldClass.COMMON, CaseFoldClass.FULL]: + if len(lower) == 1: + # foldExtends + if fold and fold['method'] == CaseFoldMethod.RANGE: + foldExtends = (lower[0] - upper) == fold['offset'] and upper == fold['upper'] + fold['count'] + elif fold and fold['method'] == CaseFoldMethod.EVEN_ODD: + foldExtends = (lower[0] - upper) == 1 and upper == (fold['upper'] + fold['count'] + 1) + else: + foldExtends = False + + if foldExtends: + # This modifies the last fold item in the array too + fold['count'] = upper - fold['upper'] + 1; + else: + fold = {} + fold['upper'] = upper + fold['offset'] = lower[0] - upper; + if fold['offset'] == 1: + fold['method'] = CaseFoldMethod.EVEN_ODD + else: + fold['method'] = CaseFoldMethod.RANGE + fold['count'] = 1 + folds.append(fold) + expand = utf8_size (lower[0]) - utf8_size(upper) + else: + fold = {} + fold['upper'] = upper + fold['method'] = CaseFoldMethod.FULL + fold['offset'] = len(foldChars) + + # add chars + for c in lower: + utf8_rep = ucs4_to_utf8(c) + # print('{} -> {}'.format(c,utf8_rep)) + for utf8_char in utf8_rep: + foldChars.append(utf8_char) + + fold['count'] = len(foldChars) - fold['offset'] + folds.append(fold) + + if fold['count'] > maxFoldChars: + maxFoldChars = fold['count'] + + expand = fold['count'] - utf8_size(upper) + if expand > maxExpand: + maxExpand = expand + + # Open output file + if args.output_file: + sys.stdout = open(args.output_file, 'w', encoding='utf-8') + + # Read the template file + if args.template_file: + tmpl_file = open(args.template_file, 'r', encoding='utf-8') + else: + tmpl_file = sys.stdin + + # Scan the input until the marker is found + # FIXME: this is a bit silly really, might just as well harcode + # the license header in the script and drop the template + for line in tmpl_file: + if line.strip() == '@@@': + break + print(line, end='') + + # Dump these tables + print('#define FC_NUM_CASE_FOLD\t{}'.format(len(folds))) + print('#define FC_NUM_CASE_FOLD_CHARS\t{}'.format(len(foldChars))) + print('#define FC_MAX_CASE_FOLD_CHARS\t{}'.format(maxFoldChars)) + print('#define FC_MAX_CASE_FOLD_EXPAND\t{}'.format(maxExpand)) + print('#define FC_MIN_FOLD_CHAR\t0x{:08x}'.format(minFoldChar)) + print('#define FC_MAX_FOLD_CHAR\t0x{:08x}'.format(maxFoldChar)) + print('') + + # Dump out ranges + print('static const FcCaseFold fcCaseFold[FC_NUM_CASE_FOLD] = {') + for f in folds: + short_offset = f['offset'] + if short_offset < -32367: + short_offset += 65536 + if short_offset > 32368: + short_offset -= 65536 + print(' {} 0x{:08x}, {:22s} 0x{:04x}, {:6d} {},'.format('{', + f['upper'], case_fold_method_name_map[f['method']], + f['count'], short_offset, '}')) + print('};\n') + + # Dump out "other" values + print('static const FcChar8\tfcCaseFoldChars[FC_NUM_CASE_FOLD_CHARS] = {') + for n, c in enumerate(foldChars): + if n == len(foldChars) - 1: + end = '' + elif n % 16 == 15: + end = ',\n' + else: + end = ',' + print('0x{:02x}'.format(c), end=end) + print('\n};') + + # And flush out the rest of the input file + for line in tmpl_file: + print(line, end='') + + sys.stdout.flush() diff --git a/fc-case/meson.build b/fc-case/meson.build new file mode 100644 index 0000000..a14b635 --- /dev/null +++ b/fc-case/meson.build @@ -0,0 +1,4 @@ +fccase_h = custom_target('fccase.h', + output: 'fccase.h', + input: ['CaseFolding.txt', 'fccase.tmpl.h'], + command: [find_program('fc-case.py'), '@INPUT0@', '--template', '@INPUT1@', '--output', '@OUTPUT@']) diff --git a/fc-cat/meson.build b/fc-cat/meson.build new file mode 100644 index 0000000..f26e4b8 --- /dev/null +++ b/fc-cat/meson.build @@ -0,0 +1,8 @@ +fccat = executable('fc-cat', ['fc-cat.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-cat'] diff --git a/fc-conflist/meson.build b/fc-conflist/meson.build new file mode 100644 index 0000000..f543cf9 --- /dev/null +++ b/fc-conflist/meson.build @@ -0,0 +1,8 @@ +fcconflist = executable('fc-conflist', ['fc-conflist.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-conflist'] diff --git a/fc-lang/fc-lang.py b/fc-lang/fc-lang.py new file mode 100755 index 0000000..cc1dea8 --- /dev/null +++ b/fc-lang/fc-lang.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +# +# fontconfig/fc-lang/fc-lang.py +# +# Copyright © 2001-2002 Keith Packard +# Copyright © 2019 Tim-Philipp Müller +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of the author(s) not be used in +# advertising or publicity pertaining to distribution of the software without +# specific, written prior permission. The authors make no +# representations about the suitability of this software for any purpose. It +# is provided "as is" without express or implied warranty. +# +# THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# fc-lang +# +# Read a set of language orthographies and build C declarations for +# charsets which can then be used to identify which languages are +# supported by a given font. +# +# TODO: this code is not very pythonic, a lot of it is a 1:1 translation +# of the C code and we could probably simplify it a bit +import argparse +import string +import sys +import os + +# we just store the leaves in a dict, we can order the leaves later if needed +class CharSet: + def __init__(self): + self.leaves = {} # leaf_number -> leaf data (= 16 uint32) + + def add_char(self, ucs4): + assert ucs4 < 0x01000000 + leaf_num = ucs4 >> 8 + if leaf_num in self.leaves: + leaf = self.leaves[leaf_num] + else: + leaf = [0, 0, 0, 0, 0, 0, 0, 0] # 256/32 = 8 + self.leaves[leaf_num] = leaf + leaf[(ucs4 & 0xff) >> 5] |= (1 << (ucs4 & 0x1f)) + #print('{:08x} [{:04x}] --> {}'.format(ucs4, ucs4>>8, leaf)) + + def del_char(self, ucs4): + assert ucs4 < 0x01000000 + leaf_num = ucs4 >> 8 + if leaf_num in self.leaves: + leaf = self.leaves[leaf_num] + leaf[(ucs4 & 0xff) >> 5] &= ~(1 << (ucs4 & 0x1f)) + # We don't bother removing the leaf if it's empty */ + #print('{:08x} [{:04x}] --> {}'.format(ucs4, ucs4>>8, leaf)) + + def equals(self, other_cs): + keys = sorted(self.leaves.keys()) + other_keys = sorted(other_cs.leaves.keys()) + if len(keys) != len(other_keys): + return False + for k1, k2 in zip(keys, other_keys): + if k1 != k2: + return False + if not leaves_equal(self.leaves[k1], other_cs.leaves[k2]): + return False + return True + +# Convert a file name into a name suitable for C declarations +def get_name(file_name): + return file_name.split('.')[0] + +# Convert a C name into a language name +def get_lang(c_name): + return c_name.replace('_', '-').replace(' ', '').lower() + +def read_orth_file(file_name): + lines = [] + with open(file_name, 'r', encoding='utf-8') as orth_file: + for num, line in enumerate(orth_file): + if line.startswith('include '): + include_fn = line[8:].strip() + lines += read_orth_file(include_fn) + else: + # remove comments and strip whitespaces + line = line.split('#')[0].strip() + line = line.split('\t')[0].strip() + # skip empty lines + if line: + lines += [(file_name, num, line)] + + return lines + +def leaves_equal(leaf1, leaf2): + for v1, v2 in zip(leaf1, leaf2): + if v1 != v2: + return False + return True + +# Build a single charset from a source file +# +# The file format is quite simple, either +# a single hex value or a pair separated with a dash +def parse_orth_file(file_name, lines): + charset = CharSet() + for fn, num, line in lines: + delete_char = line.startswith('-') + if delete_char: + line = line[1:] + if line.find('-') != -1: + parts = line.split('-') + elif line.find('..') != -1: + parts = line.split('..') + else: + parts = [line] + + start = int(parts.pop(0), 16) + end = start + if parts: + end = int(parts.pop(0), 16) + if parts: + print('ERROR: {} line {}: parse error (too many parts)'.format(fn, num)) + + for ucs4 in range(start, end+1): + if delete_char: + charset.del_char(ucs4) + else: + charset.add_char(ucs4) + + assert charset.equals(charset) # sanity check for the equals function + + return charset + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('orth_files', nargs='+', help='List of .orth files') + parser.add_argument('--directory', dest='directory', default=None) + parser.add_argument('--template', dest='template_file', default=None) + parser.add_argument('--output', dest='output_file', default=None) + + args = parser.parse_args() + + sets = [] + names = [] + langs = [] + country = [] + + total_leaves = 0 + + LangCountrySets = {} + + # Open output file + if args.output_file: + sys.stdout = open(args.output_file, 'w', encoding='utf-8') + + # Read the template file + if args.template_file: + tmpl_file = open(args.template_file, 'r', encoding='utf-8') + else: + tmpl_file = sys.stdin + + # Change into source dir if specified (after opening other files) + if args.directory: + os.chdir(args.directory) + + orth_entries = {} + for i, fn in enumerate(args.orth_files): + orth_entries[fn] = i + + for fn in sorted(orth_entries.keys()): + lines = read_orth_file(fn) + charset = parse_orth_file(fn, lines) + + sets.append(charset) + + name = get_name(fn) + names.append(name) + + lang = get_lang(name) + langs.append(lang) + if lang.find('-') != -1: + country.append(orth_entries[fn]) # maps to original index + language_family = lang.split('-')[0] + if not language_family in LangCountrySets: + LangCountrySets[language_family] = [] + LangCountrySets[language_family] += [orth_entries[fn]] + + total_leaves += len(charset.leaves) + + # Find unique leaves + leaves = [] + for s in sets: + for leaf_num in sorted(s.leaves.keys()): + leaf = s.leaves[leaf_num] + is_unique = True + for existing_leaf in leaves: + if leaves_equal(leaf, existing_leaf): + is_unique = False + break + #print('unique: ', is_unique) + if is_unique: + leaves.append(leaf) + + # Find duplicate charsets + duplicate = [] + for i, s in enumerate(sets): + dup_num = None + if i >= 1: + for j, s_cmp in enumerate(sets): + if j >= i: + break + if s_cmp.equals(s): + dup_num = j + break + + duplicate.append(dup_num) + + tn = 0 + off = {} + for i, s in enumerate(sets): + if duplicate[i]: + continue + off[i] = tn + tn += len(s.leaves) + + # Scan the input until the marker is found + # FIXME: this is a bit silly really, might just as well hardcode + # the license header in the script and drop the template + for line in tmpl_file: + if line.strip() == '@@@': + break + print(line, end='') + + print('/* total size: {} unique leaves: {} */\n'.format(total_leaves, len(leaves))) + + print('#define LEAF0 ({} * sizeof (FcLangCharSet))'.format(len(sets))) + print('#define OFF0 (LEAF0 + {} * sizeof (FcCharLeaf))'.format(len(leaves))) + print('#define NUM0 (OFF0 + {} * sizeof (uintptr_t))'.format(tn)) + print('#define SET(n) (n * sizeof (FcLangCharSet) + offsetof (FcLangCharSet, charset))') + print('#define OFF(s,o) (OFF0 + o * sizeof (uintptr_t) - SET(s))') + print('#define NUM(s,n) (NUM0 + n * sizeof (FcChar16) - SET(s))') + print('#define LEAF(o,l) (LEAF0 + l * sizeof (FcCharLeaf) - (OFF0 + o * sizeof (intptr_t)))') + print('#define fcLangCharSets (fcLangData.langCharSets)') + print('#define fcLangCharSetIndices (fcLangData.langIndices)') + print('#define fcLangCharSetIndicesInv (fcLangData.langIndicesInv)') + + assert len(sets) < 256 # FIXME: need to change index type to 16-bit below then + + print(''' +static const struct {{ + FcLangCharSet langCharSets[{}]; + FcCharLeaf leaves[{}]; + uintptr_t leaf_offsets[{}]; + FcChar16 numbers[{}]; + {} langIndices[{}]; + {} langIndicesInv[{}]; +}} fcLangData = {{'''.format(len(sets), len(leaves), tn, tn, + 'FcChar8 ', len(sets), 'FcChar8 ', len(sets))) + + # Dump sets + print('{') + for i, s in enumerate(sets): + if duplicate[i]: + j = duplicate[i] + else: + j = i + print(' {{ "{}", {{ FC_REF_CONSTANT, {}, OFF({},{}), NUM({},{}) }} }}, /* {} */'.format( + langs[i], len(sets[j].leaves), i, off[j], i, off[j], i)) + + print('},') + + # Dump leaves + print('{') + for l, leaf in enumerate(leaves): + print(' {{ {{ /* {} */'.format(l), end='') + for i in range(0, 8): # 256/32 = 8 + if i % 4 == 0: + print('\n ', end='') + print(' 0x{:08x},'.format(leaf[i]), end='') + print('\n } },') + print('},') + + # Dump leaves + print('{') + for i, s in enumerate(sets): + if duplicate[i]: + continue + + print(' /* {} */'.format(names[i])) + + for n, leaf_num in enumerate(sorted(s.leaves.keys())): + leaf = s.leaves[leaf_num] + if n % 4 == 0: + print(' ', end='') + found = [k for k, unique_leaf in enumerate(leaves) if leaves_equal(unique_leaf,leaf)] + assert found, "Couldn't find leaf in unique leaves list!" + assert len(found) == 1 + print(' LEAF({:3},{:3}),'.format(off[i], found[0]), end='') + if n % 4 == 3: + print('') + if len(s.leaves) % 4 != 0: + print('') + + print('},') + + print('{') + for i, s in enumerate(sets): + if duplicate[i]: + continue + + print(' /* {} */'.format(names[i])) + + for n, leaf_num in enumerate(sorted(s.leaves.keys())): + leaf = s.leaves[leaf_num] + if n % 8 == 0: + print(' ', end='') + print(' 0x{:04x},'.format(leaf_num), end='') + if n % 8 == 7: + print('') + if len(s.leaves) % 8 != 0: + print('') + + print('},') + + # langIndices + print('{') + for i, s in enumerate(sets): + fn = '{}.orth'.format(names[i]) + print(' {}, /* {} */'.format(orth_entries[fn], names[i])) + print('},') + + # langIndicesInv + print('{') + for i, k in enumerate(orth_entries.keys()): + name = get_name(k) + idx = names.index(name) + print(' {}, /* {} */'.format(idx, name)) + print('}') + + print('};\n') + + print('#define NUM_LANG_CHAR_SET {}'.format(len(sets))) + num_lang_set_map = (len(sets) + 31) // 32; + print('#define NUM_LANG_SET_MAP {}'.format(num_lang_set_map)) + + # Dump indices with country codes + assert len(country) > 0 + assert len(LangCountrySets) > 0 + print('') + print('static const FcChar32 fcLangCountrySets[][NUM_LANG_SET_MAP] = {') + for k in sorted(LangCountrySets.keys()): + langset_map = [0] * num_lang_set_map # initialise all zeros + for entries_id in LangCountrySets[k]: + langset_map[entries_id >> 5] |= (1 << (entries_id & 0x1f)) + print(' {', end='') + for v in langset_map: + print(' 0x{:08x},'.format(v), end='') + print(' }}, /* {} */'.format(k)) + + print('};\n') + print('#define NUM_COUNTRY_SET {}\n'.format(len(LangCountrySets))) + + # Find ranges for each letter for faster searching + # Dump sets start/finish for the fastpath + print('static const FcLangCharSetRange fcLangCharSetRanges[] = {\n') + for c in string.ascii_lowercase: # a-z + start = 9999 + stop = -1 + for i, s in enumerate(sets): + if names[i].startswith(c): + start = min(start,i) + stop = max(stop,i) + print(' {{ {}, {} }}, /* {} */'.format(start, stop, c)) + print('};\n') + + # And flush out the rest of the input file + for line in tmpl_file: + print(line, end='') + + sys.stdout.flush() diff --git a/fc-lang/meson.build b/fc-lang/meson.build new file mode 100644 index 0000000..2c5a1c5 --- /dev/null +++ b/fc-lang/meson.build @@ -0,0 +1,256 @@ +# Do not reorder, magic +orth_files = [ + 'aa.orth', + 'ab.orth', + 'af.orth', + 'am.orth', + 'ar.orth', + 'as.orth', + 'ast.orth', + 'av.orth', + 'ay.orth', + 'az_az.orth', + 'az_ir.orth', + 'ba.orth', + 'bm.orth', + 'be.orth', + 'bg.orth', + 'bh.orth', + 'bho.orth', + 'bi.orth', + 'bin.orth', + 'bn.orth', + 'bo.orth', + 'br.orth', + 'bs.orth', + 'bua.orth', + 'ca.orth', + 'ce.orth', + 'ch.orth', + 'chm.orth', + 'chr.orth', + 'co.orth', + 'cs.orth', + 'cu.orth', + 'cv.orth', + 'cy.orth', + 'da.orth', + 'de.orth', + 'dz.orth', + 'el.orth', + 'en.orth', + 'eo.orth', + 'es.orth', + 'et.orth', + 'eu.orth', + 'fa.orth', + 'fi.orth', + 'fj.orth', + 'fo.orth', + 'fr.orth', + 'ff.orth', + 'fur.orth', + 'fy.orth', + 'ga.orth', + 'gd.orth', + 'gez.orth', + 'gl.orth', + 'gn.orth', + 'gu.orth', + 'gv.orth', + 'ha.orth', + 'haw.orth', + 'he.orth', + 'hi.orth', + 'ho.orth', + 'hr.orth', + 'hu.orth', + 'hy.orth', + 'ia.orth', + 'ig.orth', + 'id.orth', + 'ie.orth', + 'ik.orth', + 'io.orth', + 'is.orth', + 'it.orth', + 'iu.orth', + 'ja.orth', + 'ka.orth', + 'kaa.orth', + 'ki.orth', + 'kk.orth', + 'kl.orth', + 'km.orth', + 'kn.orth', + 'ko.orth', + 'kok.orth', + 'ks.orth', + 'ku_am.orth', + 'ku_ir.orth', + 'kum.orth', + 'kv.orth', + 'kw.orth', + 'ky.orth', + 'la.orth', + 'lb.orth', + 'lez.orth', + 'ln.orth', + 'lo.orth', + 'lt.orth', + 'lv.orth', + 'mg.orth', + 'mh.orth', + 'mi.orth', + 'mk.orth', + 'ml.orth', + 'mn_cn.orth', + 'mo.orth', + 'mr.orth', + 'mt.orth', + 'my.orth', + 'nb.orth', + 'nds.orth', + 'ne.orth', + 'nl.orth', + 'nn.orth', + 'no.orth', + 'nr.orth', + 'nso.orth', + 'ny.orth', + 'oc.orth', + 'om.orth', + 'or.orth', + 'os.orth', + 'pa.orth', + 'pl.orth', + 'ps_af.orth', + 'ps_pk.orth', + 'pt.orth', + 'rm.orth', + 'ro.orth', + 'ru.orth', + 'sa.orth', + 'sah.orth', + 'sco.orth', + 'se.orth', + 'sel.orth', + 'sh.orth', + 'shs.orth', + 'si.orth', + 'sk.orth', + 'sl.orth', + 'sm.orth', + 'sma.orth', + 'smj.orth', + 'smn.orth', + 'sms.orth', + 'so.orth', + 'sq.orth', + 'sr.orth', + 'ss.orth', + 'st.orth', + 'sv.orth', + 'sw.orth', + 'syr.orth', + 'ta.orth', + 'te.orth', + 'tg.orth', + 'th.orth', + 'ti_er.orth', + 'ti_et.orth', + 'tig.orth', + 'tk.orth', + 'tl.orth', + 'tn.orth', + 'to.orth', + 'tr.orth', + 'ts.orth', + 'tt.orth', + 'tw.orth', + 'tyv.orth', + 'ug.orth', + 'uk.orth', + 'ur.orth', + 'uz.orth', + 've.orth', + 'vi.orth', + 'vo.orth', + 'vot.orth', + 'wa.orth', + 'wen.orth', + 'wo.orth', + 'xh.orth', + 'yap.orth', + 'yi.orth', + 'yo.orth', + 'zh_cn.orth', + 'zh_hk.orth', + 'zh_mo.orth', + 'zh_sg.orth', + 'zh_tw.orth', + 'zu.orth', + 'ak.orth', + 'an.orth', + 'ber_dz.orth', + 'ber_ma.orth', + 'byn.orth', + 'crh.orth', + 'csb.orth', + 'dv.orth', + 'ee.orth', + 'fat.orth', + 'fil.orth', + 'hne.orth', + 'hsb.orth', + 'ht.orth', + 'hz.orth', + 'ii.orth', + 'jv.orth', + 'kab.orth', + 'kj.orth', + 'kr.orth', + 'ku_iq.orth', + 'ku_tr.orth', + 'kwm.orth', + 'lg.orth', + 'li.orth', + 'mai.orth', + 'mn_mn.orth', + 'ms.orth', + 'na.orth', + 'ng.orth', + 'nv.orth', + 'ota.orth', + 'pa_pk.orth', + 'pap_an.orth', + 'pap_aw.orth', + 'qu.orth', + 'quz.orth', + 'rn.orth', + 'rw.orth', + 'sc.orth', + 'sd.orth', + 'sg.orth', + 'sid.orth', + 'sn.orth', + 'su.orth', + 'ty.orth', + 'wal.orth', + 'za.orth', + 'lah.orth', + 'nqo.orth', + 'brx.orth', + 'sat.orth', + 'doi.orth', + 'mni.orth', + 'und_zsye.orth', + 'und_zmth.orth', +] + +fclang_h = custom_target('fclang.h', + output: ['fclang.h'], + input: orth_files, + command: [find_program('fc-lang.py'), orth_files, '--template', files('fclang.tmpl.h')[0], '--output', '@OUTPUT@', '--directory', meson.current_source_dir()], + build_by_default: true, +) diff --git a/fc-list/meson.build b/fc-list/meson.build new file mode 100644 index 0000000..2f679d5 --- /dev/null +++ b/fc-list/meson.build @@ -0,0 +1,8 @@ +fclist = executable('fc-list', ['fc-list.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-list'] diff --git a/fc-match/meson.build b/fc-match/meson.build new file mode 100644 index 0000000..aca8bc8 --- /dev/null +++ b/fc-match/meson.build @@ -0,0 +1,8 @@ +fcmatch = executable('fc-match', ['fc-match.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-match'] diff --git a/fc-pattern/meson.build b/fc-pattern/meson.build new file mode 100644 index 0000000..07de245 --- /dev/null +++ b/fc-pattern/meson.build @@ -0,0 +1,8 @@ +fcpattern = executable('fc-pattern', ['fc-pattern.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-pattern'] diff --git a/fc-query/meson.build b/fc-query/meson.build new file mode 100644 index 0000000..d0f2dd4 --- /dev/null +++ b/fc-query/meson.build @@ -0,0 +1,9 @@ +fcquery = executable('fc-query', ['fc-query.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + dependencies: [freetype_dep], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-query'] diff --git a/fc-scan/meson.build b/fc-scan/meson.build new file mode 100644 index 0000000..4de2134 --- /dev/null +++ b/fc-scan/meson.build @@ -0,0 +1,9 @@ +fcscan = executable('fc-scan', ['fc-scan.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + dependencies: [freetype_dep], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-scan'] diff --git a/fc-validate/meson.build b/fc-validate/meson.build new file mode 100644 index 0000000..e2b956e --- /dev/null +++ b/fc-validate/meson.build @@ -0,0 +1,9 @@ +fcvalidate = executable('fc-validate', ['fc-validate.c', fcstdint_h, alias_headers, ft_alias_headers], + include_directories: [incbase, incsrc], + link_with: [libfontconfig], + dependencies: [freetype_dep], + c_args: c_args, + install: true, +) + +tools_man_pages += ['fc-validate'] diff --git a/install-cache.py b/install-cache.py new file mode 100644 index 0000000..8fa020c --- /dev/null +++ b/install-cache.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import sys +import argparse +import subprocess + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('fccache') + args = parser.parse_args() + sys.exit(subprocess.run([args.fccache, '-s', '-f', '-v']).returncode) diff --git a/its/meson.build b/its/meson.build new file mode 100644 index 0000000..4153b1a --- /dev/null +++ b/its/meson.build @@ -0,0 +1,6 @@ +gettext_files = [ + 'fontconfig.its', + 'fontconfig.loc', +] + +install_data(gettext_files, install_dir: join_paths(get_option('datadir'), 'gettext/its')) diff --git a/meson-cc-tests/flexible-array-member-test.c b/meson-cc-tests/flexible-array-member-test.c new file mode 100644 index 0000000..5f96756 --- /dev/null +++ b/meson-cc-tests/flexible-array-member-test.c @@ -0,0 +1,14 @@ +#include +#include +#include + +struct s { int n; double d[]; }; + +int main(void) +{ + int m = getchar (); + struct s *p = malloc (offsetof (struct s, d) + + m * sizeof (double)); + p->d[0] = 0.0; + return p->d != (double *) NULL; +} diff --git a/meson-cc-tests/intel-atomic-primitives-test.c b/meson-cc-tests/intel-atomic-primitives-test.c new file mode 100644 index 0000000..a5c8040 --- /dev/null +++ b/meson-cc-tests/intel-atomic-primitives-test.c @@ -0,0 +1,6 @@ +void memory_barrier (void) { __sync_synchronize (); } +int atomic_add (int *i) { return __sync_fetch_and_add (i, 1); } +int mutex_trylock (int *m) { return __sync_lock_test_and_set (m, 1); } +void mutex_unlock (int *m) { __sync_lock_release (m); } + +int main(void) { return 0;} diff --git a/meson-cc-tests/solaris-atomic-operations.c b/meson-cc-tests/solaris-atomic-operations.c new file mode 100644 index 0000000..fae0acd --- /dev/null +++ b/meson-cc-tests/solaris-atomic-operations.c @@ -0,0 +1,8 @@ +#include +/* This requires Solaris Studio 12.2 or newer: */ +#include +void memory_barrier (void) { __machine_rw_barrier (); } +int atomic_add (volatile unsigned *i) { return atomic_add_int_nv (i, 1); } +void *atomic_ptr_cmpxchg (volatile void **target, void *cmp, void *newval) { return atomic_cas_ptr (target, cmp, newval); } + +int main(void) { return 0; } diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..51aa821 --- /dev/null +++ b/meson.build @@ -0,0 +1,362 @@ +project('fontconfig', 'c', + version: '2.13.91', + meson_version : '>= 0.50.0', + default_options: [ 'buildtype=debugoptimized'], +) + +fc_version = meson.project_version() +version_arr = fc_version.split('.') +fc_version_major = version_arr[0].to_int() +fc_version_minor = version_arr[1].to_int() +fc_version_micro = version_arr[2].to_int() + +# Try and maintain compatibility with the previous libtool versioning +# (this is a bit of a hack, but it should work fine for our case where +# API is added, in which case LT_AGE and LIBT_CURRENT are both increased) +soversion = fc_version_major - 1 +curversion = fc_version_minor - 1 +libversion = '@0@.@1@.0'.format(soversion, curversion) +defversion = '@0@.@1@'.format(curversion, fc_version_micro) +osxversion = curversion + 1 + +freetype_req = '>= 21.0.15' + +freetype_dep = dependency('freetype2', version: freetype_req, + fallback: ['freetype2', 'freetype_dep']) + +expat_dep = dependency('expat', + fallback: ['expat', 'expat_dep']) + +cc = meson.get_compiler('c') +i18n = import('i18n') +pkgmod = import('pkgconfig') +python3 = import('python').find_installation() + +check_headers = [ + ['dirent.h'], + ['fcntl.h'], + ['stdlib.h'], + ['string.h'], + ['unistd.h'], + ['sys/statvfs.h'], + ['sys/vfs.h'], + ['sys/statfs.h'], + ['sys/param.h'], + ['sys/mount.h'], +] + +check_funcs = [ + ['link'], + ['mkstemp'], + ['mkostemp'], + ['_mktemp_s'], + ['mkdtemp'], + ['getopt'], + ['getopt_long'], + ['getprogname'], + ['getexecname'], + ['rand'], + ['random'], + ['lrand48'], + ['random_r'], + ['rand_r'], + ['readlink'], + ['fstatvfs'], + ['fstatfs'], + ['lstat'], + ['mmap'], + ['vprintf'], +] + +check_freetype_funcs = [ + ['FT_Get_BDF_Property', {'dependencies': freetype_dep}], + ['FT_Get_PS_Font_Info', {'dependencies': freetype_dep}], + ['FT_Has_PS_Glyph_Names', {'dependencies': freetype_dep}], + ['FT_Get_X11_Font_Format', {'dependencies': freetype_dep}], + ['FT_Done_MM_Var', {'dependencies': freetype_dep}], +] + +check_header_symbols = [ + ['posix_fadvise', 'fcntl.h'] +] + +check_struct_members = [ + ['struct statvfs', 'f_basetype', ['sys/statvfs.h']], + ['struct statvfs', 'f_fstypename', ['sys/statvfs.']], + ['struct statfs', 'f_flags', []], + ['struct statfs', 'f_fstypename', []], + ['struct dirent', 'd_type', ['sys/types.h', 'dirent.h']], +] + +check_sizeofs = [ + ['void *', {'conf-name': 'SIZEOF_VOID_P'}], +] + +check_alignofs = [ + ['void *', {'conf-name': 'ALIGNOF_VOID_P'}], + ['double'], +] + +add_project_arguments('-DHAVE_CONFIG_H', language: 'c') + +c_args = [] + +conf = configuration_data() +deps = [freetype_dep, expat_dep] +incbase = include_directories('.') + +# We cannot try compiling against an internal dependency +if freetype_dep.type_name() == 'internal' + foreach func: check_freetype_funcs + name = func[0] + conf.set('HAVE_@0@'.format(name.to_upper()), 1) + endforeach +else + check_funcs += check_freetype_funcs +endif + +foreach check : check_headers + name = check[0] + + if cc.has_header(name) + conf.set('HAVE_@0@'.format(name.to_upper().underscorify()), 1) + endif +endforeach + +foreach check : check_funcs + name = check[0] + opts = check.length() > 1 ? check[1] : {} + extra_deps = opts.get('dependencies', []) + + if cc.has_function(name, dependencies: extra_deps) + conf.set('HAVE_@0@'.format(name.to_upper()), 1) + endif +endforeach + +foreach check : check_header_symbols + name = check[0] + header = check[1] + + if cc.has_header_symbol(header, name) + conf.set('HAVE_@0@'.format(name.to_upper()), 1) + endif +endforeach + +foreach check : check_struct_members + struct_name = check[0] + member_name = check[1] + headers = check[2] + + prefix = '' + + foreach header : headers + prefix += '#include <@0@>\n'.format(header) + endforeach + + if cc.has_member(struct_name, member_name, prefix: prefix) + conf.set('HAVE_@0@_@1@'.format(struct_name, member_name).to_upper().underscorify(), 1) + endif +endforeach + +foreach check : check_sizeofs + type = check[0] + opts = check.length() > 1 ? check[1] : {} + + conf_name = opts.get('conf-name', 'SIZEOF_@0@'.format(type.to_upper())) + + conf.set(conf_name, cc.sizeof(type)) +endforeach + +foreach check : check_alignofs + type = check[0] + opts = check.length() > 1 ? check[1] : {} + + conf_name = opts.get('conf-name', 'ALIGNOF_@0@'.format(type.to_upper())) + + conf.set(conf_name, cc.alignment(type)) +endforeach + +if cc.compiles(files('meson-cc-tests/flexible-array-member-test.c')) + conf.set('FLEXIBLE_ARRAY_MEMBER', true) +else + conf.set('FLEXIBLE_ARRAY_MEMBER', 1) +endif + +if cc.links(files('meson-cc-tests/intel-atomic-primitives-test.c'), name: 'Intel atomics') + conf.set('HAVE_INTEL_ATOMIC_PRIMITIVES', 1) +endif + +if cc.links(files('meson-cc-tests/solaris-atomic-operations.c'), name: 'Solaris atomic ops') + conf.set('HAVE_SOLARIS_ATOMIC_OPS', 1) +endif + + +prefix = get_option('prefix') + +fonts_conf = configuration_data() + +if host_machine.system() == 'windows' + conf.set_quoted('FC_DEFAULT_FONTS', 'WINDOWSFONTDIR') + fonts_conf.set('FC_DEFAULT_FONTS', 'WINDOWSFONTDIR') + fc_cachedir = 'LOCAL_APPDATA_FONTCONFIG_CACHE' +else + conf.set_quoted('FC_DEFAULT_FONTS', '/usr/share/fonts') + fonts_conf.set('FC_DEFAULT_FONTS', '/usr/share/fonts') + fc_cachedir = join_paths(prefix, get_option('localstatedir'), 'cache', meson.project_name()) + thread_dep = dependency('threads') + conf.set('HAVE_PTHREAD', 1) + deps += [thread_dep] +endif + +fc_templatedir = join_paths(prefix, get_option('datadir'), 'fontconfig/conf.avail') +fc_baseconfigdir = join_paths(prefix, get_option('sysconfdir'), 'fonts') +fc_configdir = join_paths(fc_baseconfigdir, 'conf.d') +fc_xmldir = join_paths(prefix, get_option('datadir'), 'xml/fontconfig') + + +conf.set_quoted('CONFIGDIR', fc_configdir) +conf.set_quoted('FC_CACHEDIR', fc_cachedir) +conf.set_quoted('FC_TEMPLATEDIR', fc_templatedir) +conf.set_quoted('FONTCONFIG_PATH', fc_baseconfigdir) +conf.set_quoted('FC_FONTPATH', '') + +fonts_conf.set('FC_FONTPATH', '') +fonts_conf.set('FC_CACHEDIR', fc_cachedir) +fonts_conf.set('CONFIGDIR', fc_configdir) +# strip off fc_baseconfigdir prefix if that is the prefix +if fc_configdir.startswith(fc_baseconfigdir + '/') + fonts_conf.set('CONFIGDIR', fc_configdir.split(fc_baseconfigdir + '/')[1]) +endif + +gperf = find_program('gperf', required: build_machine.system() != 'windows') +if not gperf.found() + subproject('gperf', required: true) + gperf = find_program('gperf') +endif + +sh = find_program('sh', required : false) + +if not sh.found() # host_machine.system() == 'windows' or not sh.found() + # TODO: This is not always correct + if cc.get_id() == 'msvc' + gperf_len_type = 'size_t' + else + gperf_len_type = 'unsigned' + endif +else + gperf_test_format = ''' + #include + const char * in_word_set(const char *, @0@); + @1@ + ''' + gperf_snippet_format = 'echo foo,bar | @0@ -L ANSI-C' + gperf_snippet = run_command(sh, '-c', gperf_snippet_format.format(gperf.path())) + gperf_test = gperf_test_format.format('size_t', gperf_snippet.stdout()) + + if cc.compiles(gperf_test) + gperf_len_type = 'size_t' + else + gperf_test = gperf_test_format.format('unsigned', gperf_snippet.stdout()) + if cc.compiles(gperf_test) + gperf_len_type = 'unsigned' + else + error('unable to determine gperf len type') + endif + endif +endif + +message('gperf len type is @0@'.format(gperf_len_type)) + +conf.set('FC_GPERF_SIZE_T', gperf_len_type, + description : 'The type of gperf "len" parameter') + +conf.set('_GNU_SOURCE', true) + +conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) + +incsrc = include_directories('src') + +# We assume stdint.h is available +foreach t : ['uint64_t', 'int32_t', 'uintptr_t', 'intptr_t'] + if not cc.has_type(t, prefix: '#include ') + error('Sanity check failed: type @0@ not provided via stdint.h'.format(t)) + endif +endforeach + +fcstdint_h = configure_file( + input: 'src/fcstdint.h.in', + output: 'fcstdint.h', + copy: true) + +stdinwrapper = files('stdin_wrapper.py')[0] +makealias = files('src/makealias.py')[0] + +alias_headers = custom_target('alias_headers', + output: ['fcalias.h', 'fcaliastail.h'], + input: ['fontconfig/fontconfig.h', 'src/fcdeprecate.h', 'fontconfig/fcprivate.h'], + command: [python3, makealias, join_paths(meson.current_source_dir(), 'src'), '@OUTPUT@', '@INPUT@'], +) + +ft_alias_headers = custom_target('ft_alias_headers', + output: ['fcftalias.h', 'fcftaliastail.h'], + input: ['fontconfig/fcfreetype.h'], + command: [python3, makealias, join_paths(meson.current_source_dir(), 'src'), '@OUTPUT@', '@INPUT@'] +) + +tools_man_pages = [] + +# Do not reorder +subdir('fc-case') +subdir('fc-lang') +subdir('src') + +if not get_option('tools').disabled() + subdir('fc-cache') + subdir('fc-cat') + subdir('fc-conflist') + subdir('fc-list') + subdir('fc-match') + subdir('fc-pattern') + subdir('fc-query') + subdir('fc-scan') + subdir('fc-validate') +endif + +if not get_option('tests').disabled() + subdir('test') +endif + +subdir('conf.d') +subdir('its') + +# xgettext is optional (on Windows for instance) +if find_program('xgettext', required : get_option('nls')).found() + subdir('po') + subdir('po-conf') +endif + +if not get_option('doc').disabled() + subdir('doc') +endif + +configure_file(output: 'config.h', configuration: conf) + +configure_file(output: 'fonts.conf', + input: 'fonts.conf.in', + configuration: fonts_conf, + install_dir: fc_baseconfigdir, + install: true) + +install_data('fonts.dtd', + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'xml/fontconfig') +) + +fc_headers = [ + 'fontconfig/fontconfig.h', + 'fontconfig/fcfreetype.h', + 'fontconfig/fcprivate.h', +] + +install_headers(fc_headers, subdir: meson.project_name()) + +meson.add_install_script('install-cache.py', fccache.full_path()) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..d603049 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,13 @@ +# Common feature options +option('doc', type : 'feature', value : 'auto', yield: true, + description: 'Build documentation') +option('doc-txt', type: 'feature', value: 'auto') +option('doc-man', type: 'feature', value: 'auto') +option('doc-pdf', type: 'feature', value: 'auto') +option('doc-html', type: 'feature', value: 'auto') +option('nls', type : 'feature', value : 'auto', yield: true, + description : 'Enable native language support (translations)') +option('tests', type : 'feature', value : 'auto', yield : true, + description: 'Enable unit tests') +option('tools', type : 'feature', value : 'auto', yield : true, + description: 'Build command-line tools (fc-list, fc-query, etc.)') diff --git a/po-conf/meson.build b/po-conf/meson.build new file mode 100644 index 0000000..4567cae --- /dev/null +++ b/po-conf/meson.build @@ -0,0 +1,3 @@ +i18n.gettext(meson.project_name(), + args: '--directory=' + meson.source_root() +) diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..20152e3 --- /dev/null +++ b/po/meson.build @@ -0,0 +1,3 @@ +i18n.gettext(meson.project_name() + '-conf', + args: '--directory=' + meson.source_root() +) diff --git a/src/cutout.py b/src/cutout.py new file mode 100644 index 0000000..6fa55a3 --- /dev/null +++ b/src/cutout.py @@ -0,0 +1,33 @@ +import argparse +import subprocess +import os +import re + +if __name__== '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('input') + parser.add_argument('output') + + args = parser.parse_known_args() + print (args[0].output) + + cpp = args[1] + ret = subprocess.run(cpp + [args[0].input], stdout=subprocess.PIPE) + + stdout = ret.stdout.decode('utf8') + + with open(args[0].output, 'w') as out: + write = True + for l in stdout.split('\n'): + l = l.strip('\r') + if l.startswith('CUT_OUT_BEGIN'): + write = False + + if write and l: + stripped = re.sub('^\s+', '', l) + stripped = re.sub('\s*,\s*', ',', stripped) + if not stripped.isspace() and stripped: + out.write('%s\n' % stripped) + + if l.startswith('CUT_OUT_END'): + write = True diff --git a/src/fccompat.c b/src/fccompat.c index 53436b5..65ac84c 100644 --- a/src/fccompat.c +++ b/src/fccompat.c @@ -290,6 +290,91 @@ FcReadLink (const FcChar8 *pathname, #endif } +/* On Windows MingW provides dirent.h / openddir(), but MSVC does not */ +#ifndef HAVE_DIRENT_H + +struct DIR { + struct dirent d_ent; + HANDLE handle; + WIN32_FIND_DATA fdata; + FcBool valid; +}; + +FcPrivate DIR * +FcCompatOpendirWin32 (const char *dirname) +{ + size_t len; + char *name; + DIR *dir; + + dir = calloc (1, sizeof (struct DIR)); + if (dir == NULL) + return NULL; + + len = strlen (dirname); + name = malloc (len + 3); + if (name == NULL) + { + free (dir); + return NULL; + } + memcpy (name, dirname, len); + name[len++] = FC_DIR_SEPARATOR; + name[len++] = '*'; + name[len] = '\0'; + + dir->handle = FindFirstFileEx (name, FindExInfoBasic, &dir->fdata, FindExSearchNameMatch, NULL, 0); + + free (name); + + if (!dir->handle) + { + free (dir); + dir = NULL; + + if (GetLastError () == ERROR_FILE_NOT_FOUND) + errno = ENOENT; + else + errno = EACCES; + } + + dir->valid = FcTrue; + return dir; +} + +FcPrivate struct dirent * +FcCompatReaddirWin32 (DIR *dir) +{ + if (dir->valid != FcTrue) + return NULL; + + dir->d_ent.d_name = dir->fdata.cFileName; + + if ((dir->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + dir->d_ent.d_type = DT_DIR; + else if (dir->fdata.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) + dir->d_ent.d_type = DT_REG; + else + dir->d_ent.d_type = DT_UNKNOWN; + + if (!FindNextFile (dir->handle, &dir->fdata)) + dir->valid = FcFalse; + + return &dir->d_ent; +} + +FcPrivate int +FcCompatClosedirWin32 (DIR *dir) +{ + if (dir != NULL && dir->handle != NULL) + { + FindClose (dir->handle); + free (dir); + } + return 0; +} +#endif /* HAVE_DIRENT_H */ + #define __fccompat__ #include "fcaliastail.h" #undef __fccompat__ diff --git a/src/fcstdint.h.in b/src/fcstdint.h.in new file mode 100644 index 0000000..9a6118b --- /dev/null +++ b/src/fcstdint.h.in @@ -0,0 +1 @@ +#include diff --git a/src/fcwindows.h b/src/fcwindows.h index 2e8b9ec..d18e1af 100644 --- a/src/fcwindows.h +++ b/src/fcwindows.h @@ -44,6 +44,7 @@ # define WIN32_EXTRA_LEAN # define STRICT # include +# include #if defined(_MSC_VER) #include @@ -52,6 +53,53 @@ typedef SSIZE_T ssize_t; #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) #endif +#ifndef F_OK +#define F_OK 0 +#endif +#ifndef X_OK +#define X_OK 0 /* no execute bit on windows */ +#endif +#ifndef W_OK +#define W_OK 2 +#endif +#ifndef R_OK +#define R_OK 4 +#endif + +/* MingW provides dirent.h / openddir(), but MSVC does not */ +#ifndef HAVE_DIRENT_H + +#define HAVE_STRUCT_DIRENT_D_TYPE 1 + +typedef struct DIR DIR; + +typedef enum { + DT_UNKNOWN = 0, + DT_DIR, + DT_REG, +} DIR_TYPE; + +typedef struct dirent { + const char *d_name; + DIR_TYPE d_type; +} dirent; + +#define opendir(dirname) FcCompatOpendirWin32(dirname) +#define closedir(d) FcCompatClosedirWin32(d) +#define readdir(d) FcCompatReaddirWin32(d) + +DIR * FcCompatOpendirWin32 (const char *dirname); + +struct dirent * FcCompatReaddirWin32 (DIR *dir); + +int FcCompatClosedirWin32 (DIR *dir); + +#endif /* HAVE_DIRENT_H */ + +#endif /* _WIN32 */ + #endif /* _FCWINDOWS_H_ */ diff --git a/src/fontconfig.def.in b/src/fontconfig.def.in new file mode 100644 index 0000000..9f102be --- /dev/null +++ b/src/fontconfig.def.in @@ -0,0 +1,234 @@ +EXPORTS + FcAtomicCreate + FcAtomicDeleteNew + FcAtomicDestroy + FcAtomicLock + FcAtomicNewFile + FcAtomicOrigFile + FcAtomicReplaceOrig + FcAtomicUnlock + FcBlanksAdd + FcBlanksCreate + FcBlanksDestroy + FcBlanksIsMember + FcCacheCopySet + FcCacheCreateTagFile + FcCacheDir + FcCacheNumFont + FcCacheNumSubdir + FcCacheSubdir + FcCharSetAddChar + FcCharSetCopy + FcCharSetCount + FcCharSetCoverage + FcCharSetCreate + FcCharSetDelChar + FcCharSetDestroy + FcCharSetEqual + FcCharSetFirstPage + FcCharSetHasChar + FcCharSetIntersect + FcCharSetIntersectCount + FcCharSetIsSubset + FcCharSetMerge + FcCharSetNew + FcCharSetNextPage + FcCharSetSubtract + FcCharSetSubtractCount + FcCharSetUnion + FcConfigAppFontAddDir + FcConfigAppFontAddFile + FcConfigAppFontClear + FcConfigBuildFonts + FcConfigCreate + FcConfigDestroy + FcConfigEnableHome + FcConfigFileInfoIterGet + FcConfigFileInfoIterInit + FcConfigFileInfoIterNext + FcConfigFilename + FcConfigGetBlanks + FcConfigGetCache + FcConfigGetCacheDirs + FcConfigGetConfigDirs + FcConfigGetConfigFiles + FcConfigGetCurrent + FcConfigGetFontDirs + FcConfigGetFonts + FcConfigGetRescanInterval + FcConfigGetRescanInverval + FcConfigGetSysRoot + FcConfigHome + FcConfigParseAndLoad + FcConfigParseAndLoadFromMemory + FcConfigReference + FcConfigSetCurrent + FcConfigSetRescanInterval + FcConfigSetRescanInverval + FcConfigSetSysRoot + FcConfigSubstitute + FcConfigSubstituteWithPat + FcConfigUptoDate + FcDefaultSubstitute + FcDirCacheClean + FcDirCacheCreateUUID + FcDirCacheDeleteUUID + FcDirCacheLoad + FcDirCacheLoadFile + FcDirCacheRead + FcDirCacheRescan + FcDirCacheUnlink + FcDirCacheUnload + FcDirCacheValid + FcDirSave + FcDirScan + FcFileIsDir + FcFileScan + FcFini + FcFontList + FcFontMatch + FcFontRenderPrepare + FcFontSetAdd + FcFontSetCreate + FcFontSetDestroy + FcFontSetList + FcFontSetMatch + FcFontSetPrint + FcFontSetSort + FcFontSetSortDestroy + FcFontSort + FcFreeTypeCharIndex + FcFreeTypeCharSet + FcFreeTypeCharSetAndSpacing + FcFreeTypeQuery + FcFreeTypeQueryAll + FcFreeTypeQueryFace + FcGetDefaultLangs + FcGetLangs + FcGetVersion + FcInit + FcInitBringUptoDate + FcInitLoadConfig + FcInitLoadConfigAndFonts + FcInitReinitialize + FcLangGetCharSet + FcLangNormalize + FcLangSetAdd + FcLangSetCompare + FcLangSetContains + FcLangSetCopy + FcLangSetCreate + FcLangSetDel + FcLangSetDestroy + FcLangSetEqual + FcLangSetGetLangs + FcLangSetHash + FcLangSetHasLang + FcLangSetSubtract + FcLangSetUnion + FcMatrixCopy + FcMatrixEqual + FcMatrixMultiply + FcMatrixRotate + FcMatrixScale + FcMatrixShear + FcNameConstant + FcNameGetConstant + FcNameGetObjectType + FcNameParse + FcNameRegisterConstants + FcNameRegisterObjectTypes + FcNameUnparse + FcNameUnregisterConstants + FcNameUnregisterObjectTypes + FcObjectSetAdd + FcObjectSetBuild + FcObjectSetCreate + FcObjectSetDestroy + FcObjectSetVaBuild + FcPatternAdd + FcPatternAddBool + FcPatternAddCharSet + FcPatternAddDouble + FcPatternAddFTFace + FcPatternAddInteger + FcPatternAddLangSet + FcPatternAddMatrix + FcPatternAddRange + FcPatternAddString + FcPatternAddWeak + FcPatternBuild + FcPatternCreate + FcPatternDel + FcPatternDestroy + FcPatternDuplicate + FcPatternEqual + FcPatternEqualSubset + FcPatternFilter + FcPatternFindIter + FcPatternFormat + FcPatternGet + FcPatternGetBool + FcPatternGetCharSet + FcPatternGetDouble + FcPatternGetFTFace + FcPatternGetInteger + FcPatternGetLangSet + FcPatternGetMatrix + FcPatternGetRange + FcPatternGetString + FcPatternGetWithBinding + FcPatternHash + FcPatternIterEqual + FcPatternIterGetObject + FcPatternIterGetValue + FcPatternIterIsValid + FcPatternIterNext + FcPatternIterStart + FcPatternIterValueCount + FcPatternObjectCount + FcPatternPrint + FcPatternReference + FcPatternRemove + FcPatternVaBuild + FcRangeCopy + FcRangeCreateDouble + FcRangeCreateInteger + FcRangeDestroy + FcRangeGetDouble + FcStrBasename + FcStrBuildFilename + FcStrCmp + FcStrCmpIgnoreCase + FcStrCopy + FcStrCopyFilename + FcStrDirname + FcStrDowncase + FcStrFree + FcStrListCreate + FcStrListDone + FcStrListFirst + FcStrListNext + FcStrPlus + FcStrSetAdd + FcStrSetAddFilename + FcStrSetCreate + FcStrSetDel + FcStrSetDestroy + FcStrSetEqual + FcStrSetMember + FcStrStr + FcStrStrIgnoreCase + FcUcs4ToUtf8 + FcUtf16Len + FcUtf16ToUcs4 + FcUtf8Len + FcUtf8ToUcs4 + FcValueDestroy + FcValueEqual + FcValuePrint + FcValueSave + FcWeightFromOpenType + FcWeightFromOpenTypeDouble + FcWeightToOpenType + FcWeightToOpenTypeDouble diff --git a/src/makealias.py b/src/makealias.py new file mode 100755 index 0000000..0d5920c --- /dev/null +++ b/src/makealias.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import argparse +from collections import OrderedDict + +# cat fontconfig/fontconfig.h | grep '^Fc[^ ]* *(' | sed -e 's/ *(.*$//' + +def extract(fname): + with open(fname, 'r', encoding='utf-8') as f: + for l in f.readlines(): + l = l.rstrip() + m = re.match(r'^(Fc[^ ]*)[\s\w]*\(.*', l) + + if m and m.group(1) not in ['FcCacheDir', 'FcCacheSubdir']: + yield m.group(1) + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('srcdir') + parser.add_argument('head') + parser.add_argument('tail') + parser.add_argument('headers', nargs='+') + + args = parser.parse_args() + + definitions = {} + + for fname in os.listdir(args.srcdir): + define_name, ext = os.path.splitext(fname) + if ext != '.c': + continue + + define_name = '__%s__' % os.path.basename(define_name) + + for definition in extract(os.path.join(args.srcdir, fname)): + definitions[definition] = define_name + + declarations = OrderedDict() + + for fname in args.headers: + for declaration in extract(fname): + try: + define_name = definitions[declaration] + except KeyError: + print ('error: could not locate %s in src/*.c' % declaration) + sys.exit(1) + + declarations[declaration] = define_name + + with open(args.head, 'w') as head: + with open(args.tail, 'w') as tail: + tail.write('#if HAVE_GNUC_ATTRIBUTE\n') + last = None + for name, define_name in declarations.items(): + alias = 'IA__%s' % name + hattr = 'FC_ATTRIBUTE_VISIBILITY_HIDDEN' + head.write('extern __typeof (%s) %s %s;\n' % (name, alias, hattr)) + head.write('#define %s %s\n' % (name, alias)) + if define_name != last: + if last is not None: + tail.write('#endif /* %s */\n' % last) + tail.write('#ifdef %s\n' % define_name) + last = define_name + tail.write('# undef %s\n' % name) + cattr = '__attribute((alias("%s"))) FC_ATTRIBUTE_VISIBILITY_EXPORT' % alias + tail.write('extern __typeof (%s) %s %s;\n' % (name, name, cattr)) + tail.write('#endif /* %s */\n' % last) + tail.write('#endif /* HAVE_GNUC_ATTRIBUTE */\n') diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..ee5b8cc --- /dev/null +++ b/src/meson.build @@ -0,0 +1,91 @@ +fc_sources = [ + 'fcatomic.c', + 'fccache.c', + 'fccfg.c', + 'fccharset.c', + 'fccompat.c', + 'fcdbg.c', + 'fcdefault.c', + 'fcdir.c', + 'fcformat.c', + 'fcfreetype.c', + 'fcfs.c', + 'fcptrlist.c', + 'fchash.c', + 'fcinit.c', + 'fclang.c', + 'fclist.c', + 'fcmatch.c', + 'fcmatrix.c', + 'fcname.c', + 'fcobjs.c', + 'fcpat.c', + 'fcrange.c', + 'fcserialize.c', + 'fcstat.c', + 'fcstr.c', + 'fcweight.c', + 'fcxml.c', + 'ftglue.c', +] + +# FIXME: obviously fragile, cc.preprocess would be sweet +if cc.get_id() == 'gcc' + cpp = ['gcc', '-E', '-P'] +elif cc.get_id() == 'msvc' + cpp = ['cl', '/EP'] +elif cc.get_id() == 'clang' + cpp = ['clang', '-E', '-P'] +else + error('FIXME: implement cc.preprocess') +endif + +cpp += ['-I', join_paths(meson.current_source_dir(), '..')] + +fcobjshash_gperf = custom_target('fcobjshash.gperf', + input: 'fcobjshash.gperf.h', + output: 'fcobjshash.gperf', + command: [python3, files('cutout.py')[0], '@INPUT@', '@OUTPUT@', cpp], + build_by_default: true, +) + +fcobjshash_h = custom_target('fcobjshash.h', + input: fcobjshash_gperf, + output: 'fcobjshash.h', + command: [gperf, '--pic', '-m', '100', '@INPUT@', '--output-file', '@OUTPUT@'] +) + +# write def file with exports for windows +cdata_def = configuration_data() +cdata_def.set('DEF_VERSION', defversion) +fontconfig_def = configure_file(input: 'fontconfig.def.in', output: 'fontconfig.def', configuration: cdata_def) + +libfontconfig = library('fontconfig', + fc_sources, alias_headers, ft_alias_headers, fclang_h, fccase_h, fcobjshash_h, + c_args: c_args, + include_directories: incbase, + dependencies: deps, + vs_module_defs: fontconfig_def, + install: true, + soversion: soversion, + version: libversion, + darwin_versions: osxversion, +) + +fontconfig_dep = declare_dependency(link_with: libfontconfig, + include_directories: incbase, + dependencies: deps, +) + +pkgmod.generate(libfontconfig, + description: 'Font configuration and customization library', + filebase: 'fontconfig', + name: 'Fontconfig', + requires: ['freetype2 ' + freetype_req], + version: fc_version, + variables: [ + 'sysconfdir=@0@'.format(join_paths(prefix, get_option('sysconfdir'))), + 'localstatedir=@0@'.format(join_paths(prefix, get_option('localstatedir'))), + 'confdir=${sysconfdir}/fonts', + 'cachedir=${localstatedir}/cache/fontconfig', + ]) diff --git a/stdin_wrapper.py b/stdin_wrapper.py new file mode 100644 index 0000000..17fc628 --- /dev/null +++ b/stdin_wrapper.py @@ -0,0 +1,20 @@ +import argparse +import subprocess +import sys + +if __name__=='__main__': + parser = argparse.ArgumentParser() + parser.add_argument('prog') + parser.add_argument('input') + parser.add_argument('output') + parser.add_argument('args', nargs='*') + + args = parser.parse_args() + + unescaped_args = [arg.strip('""') for arg in args.args] + + command = [args.prog] + unescaped_args + + with open(args.output, 'w') as out: + with open(args.input, 'r') as in_: + sys.exit(subprocess.run(command, stdin=in_, stdout=out).returncode) diff --git a/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 0000000..00191bd --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,6 @@ +/expat-* +/freetype2 +/gperf +/zlib-* +/libpng-* +/packagecache diff --git a/subprojects/expat.wrap b/subprojects/expat.wrap new file mode 100644 index 0000000..0a605d0 --- /dev/null +++ b/subprojects/expat.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = expat-2.2.6 + +source_url = https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2 +source_filename = expat-2.2.6.tar.bz2 +source_hash = 17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2 + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/expat/2.2.6/1/get_zip +patch_filename = expat-2.2.6-1-wrap.zip +patch_hash = b8312fae757c7bff6f0cb430108104441a3da7a0a333809f5c80b354157eaa4d diff --git a/subprojects/freetype2.wrap b/subprojects/freetype2.wrap new file mode 100644 index 0000000..3151539 --- /dev/null +++ b/subprojects/freetype2.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory=freetype2 +url=https://github.com/centricular/freetype2.git +push-url=git@github.com:centricular/freetype2.git +revision=meson diff --git a/subprojects/gperf.wrap b/subprojects/gperf.wrap new file mode 100644 index 0000000..7489733 --- /dev/null +++ b/subprojects/gperf.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory=gperf +url=https://gitlab.freedesktop.org/tpm/gperf.git +push-url=https://gitlab.freedesktop.org/tpm/gperf.git +revision=meson diff --git a/subprojects/libpng.wrap b/subprojects/libpng.wrap new file mode 100644 index 0000000..b7af909 --- /dev/null +++ b/subprojects/libpng.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = libpng-1.6.37 + +source_url = https://github.com/glennrp/libpng/archive/v1.6.37.tar.gz +source_filename = libpng-1.6.37.tar.gz +source_hash = ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307 + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/libpng/1.6.37/1/get_zip +patch_filename = libpng-1.6.37-1-wrap.zip +patch_hash = 9a863ae8a5657315a484c94c51f9f636b1fb9f49a15196cc896b72e5f21d78f0 diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap new file mode 100644 index 0000000..91c1d4d --- /dev/null +++ b/subprojects/zlib.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = zlib-1.2.11 + +source_url = http://zlib.net/fossils/zlib-1.2.11.tar.gz +source_filename = zlib-1.2.11.tar.gz +source_hash = c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 + +patch_url = https://wrapdb.mesonbuild.com/v1/projects/zlib/1.2.11/4/get_zip +patch_filename = zlib-1.2.11-4-wrap.zip +patch_hash = f733976fbfc59e0bcde01aa9469a24eeb16faf0a4280b17e9eaa60a301d75657 diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..6b29409 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,46 @@ +tests = [ + ['test-bz89617.c', {'c_args': ['-DSRCDIR="@0@"'.format(meson.current_source_dir())]}], + ['test-bz131804.c'], + ['test-bz96676.c'], + ['test-name-parse.c'], + ['test-bz106618.c'], + ['test-bz1744377.c'], + ['test-issue180.c'], +] + +if host_machine.system() != 'windows' + tests += [ + # FIXME: ['test-migration.c'], + ['test-bz106632.c', {'c_args': ['-DFONTFILE="@0@"'.format(join_paths(meson.current_source_dir(), '4x6.pcf'))]}], + ['test-issue107.c'], # FIXME: fails on mingw + # FIXME: this needs NotoSans-hinted.zip font downloaded and unpacked into test build directory! see run-test.sh + ['test-crbug1004254.c', {'dependencies': dependency('threads')}], # for pthread + ] + + if get_option('default_library') == 'static' + tests += [ + ['test-issue110.c'], + ['test-d1f48f11.c'], + ] + endif +endif + +foreach test_data : tests + fname = test_data[0] + opts = test_data.length() > 1 ? test_data[1] : {} + extra_c_args = opts.get('c_args', []) + extra_deps = opts.get('dependencies', []) + + test_name = fname.split('.')[0].underscorify() + exe = executable(test_name, fname, + c_args: c_args + extra_c_args, + include_directories: incbase, + link_with: [libfontconfig], + dependencies: extra_deps, + ) + + test(test_name, exe) +endforeach + +# FIXME: run-test.sh stuff +# FIXME: jsonc test-conf