Thanks to my sponsors: Michael Mrozek, Brandon Piña, Aiden Scandella, Yann Schwartz, Leo Shimonaka, Adam Gutglick, jatescher, Valentin Mariette, belzael, Chirag Jain, Sean Bryant, Cole Kurkowski, Niels Abildgaard, Richard Stephens, old.woman.josiah, René Ribaud, Torben Clasen, Integer 32, LLC, Foxie Solutions, Joshua Roesslein and 235 more
Porting poppler to meson
👋 This page was last updated ~3 years ago. Just so you know.
It took a hot minute.
Try several weeks.
Well, yeah. I got to contribute to a bunch of open-source projects in the meantime though, so I'm fairly pleased with it!
- libffi (for static linking)
- cairo (more static linking!)
- proxy-libintl (more static linking!)
- expat (static linking strikes again)
- poppler (for file descriptor stuff not properly gated on Windows, closed in favor of a similar MR)
- glib (work in progress, involves thread-local storage and dynamic linker/loader cursedness)
In the current state of things, I have: a fork of glib, a (tiny) fork of cairo, and a whole set of meson build files for poppler... and it's really easy to build for both Windows & Linux!
All of the following happens in poppler-meson
, but you can assume we're pretty
much starting from scratch: a lot of my fixes have already landed, and the
others will soon, so for example I don't have to pass -DFFI_STATIC_BUILD=1
myself.
Let's get through it real quick:
In poppler-meson
there's a top-level meson.build
that reads like this:
project('poppler-meson', 'c',
version: '1.0.0',
meson_version: '>= 0.60.2',
default_options: [
'default_library=static',
],
)
poppler_dep = dependency(
'poppler',
static: true,
)
Because not every fix has landed yet, we still want to override some dependencies that would normally be pulled by poppler.
subprojects/glib.wrap
points to my fork of glib:
[wrap-git]
directory = glib
url=https://gitlab.gnome.org/fasterthanlime/glib
revision=amos/win-msvc-static-linking
[provide]
dependency_names = gthread-2.0, gobject-2.0, gmodule-no-export-2.0, gmodule-export-2.0, gmodule-2.0, glib-2.0, gio-2.0, gio-win32-2.0, gio-unix-2.0
program_names = glib-genmarshal, glib-mkenums, glib-compile-schemas, glib-compile-resources, gio-querymodules, gdbus-codegen
And so does subprojects/cairo.wrap
:
[wrap-git]
url = https://gitlab.freedesktop.org/fasterthanlime/cairo.git
revision = amos/win32-static-build
Can't make your mind about branch names huh?
Less judging more shipping.
Both these subprojects have their own dependencies (which also get checked out as subprojects, but which I don't track in git), but also, they do something like this:
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('glib-2.0', libglib_dep)
endif
Which means that when poppler
or cairo
will request dependency glib-2.0
,
it'll use my fork, and not another version of glib.
subprojects/poppler.wrap
is interesting: it fetches poppler sources from the
official 21.12.0 release, and then overlays a folder on top of it, which
contains only meson.build
files:
[wrap-file]
directory = poppler
source_url = https://poppler.freedesktop.org/poppler-21.12.0.tar.xz
source_filename = poppler-21.12.0.tar.xz
source_hash = acb840c2c1ec07d07e53c57c4b3a1ff3e3ee2d888d44e1e9f2f01aaf16814de7
patch_directory = poppler-21.12.0
[provide]
dependency_names = poppler, poppler-glib
That folder is in subprojects/packagefiles/poppler-21.12.0
.
The first thing we should probably look at is meson_options.txt
: it's just a
list of features poppler can be built with & without:
option(
'glib',
type: 'feature',
value: 'enabled',
description: 'Compile poppler with glib wrapper',
)
option(
'cairo',
type: 'feature',
value: 'auto',
description: 'Enable the cairo rendering backend',
)
# (etc.)
I didn't make an effort to ensure all combinations build, I just went for the minimal build I needed, so it's not ready to contribute upstream yet.
For comparison, in CMake, these look like this:
option(ENABLE_GLIB "Compile poppler glib wrapper." ON)
The main meson.build
file starts by defining a project and some variables
we'll need later;
project('poppler', 'cpp',
version: '21.12.0',
meson_version: '>= 0.60.2',
default_options: ['default_library=static'],
)
gnome = import('gnome')
glib_required = '1.10.0'
poppler_version = meson.project_version()
poppler_version_array = poppler_version.split('.')
poppler_major_version = poppler_version_array[0].to_int()
poppler_minor_version = poppler_version_array[1].to_int()
poppler_micro_version = poppler_version_array[2].to_int()
os_unix = false
os_linux = false
os_win32 = false
os_darwin = false
# Some windowing system backends depend on the platform we're
# building for, so we need to ensure they are disabled; in other
# cases, they are the only windowing system available, so we need
# to ensure they are enabled
if host_machine.system() == 'darwin'
os_darwin = true
elif host_machine.system() == 'windows'
os_win32 = true
elif host_machine.system() == 'linux'
os_linux = true
endif
os_unix = not os_win32
poppler_prefix = get_option('prefix')
poppler_includedir = join_paths(poppler_prefix, get_option('includedir'))
poppler_libdir = join_paths(poppler_prefix, get_option('libdir'))
poppler_pkgconfigdir = join_paths(poppler_libdir, 'pkgconfig')
cc = meson.get_compiler('cpp')
Then retrieving all the options we've defined, and finding dependencies:
jpeg_opt = get_option('jpeg')
openjpeg_opt = get_option('openjpeg')
libcurl_opt = get_option('libcurl')
zlib_opt = get_option('zlib')
zlib_uncompress_opt = get_option('zlib-uncompress')
zlib_dep = dependency('zlib', required: zlib_opt)
glib_opt = get_option('glib')
glib_dep = dependency('glib-2.0',
version: '>= ' + glib_required,
static: true,
required: glib_opt,
default_options: ['tests=false'],
fallback: 'glib',
)
gobject_dep = dependency('gobject-2.0',
version: '>= ' + glib_required,
required: glib_opt,
)
gio_dep = dependency('gio-2.0',
version: '>= ' + glib_required,
required: glib_opt,
)
cairo_opt = get_option('cairo')
cairo_dep = dependency('cairo',
static: true,
required: cairo_opt,
default_options: [
'fontconfig=enabled',
'freetype=enabled',
'cogl=disabled',
'gl-backend=disabled',
'glesv2=disabled',
'glesv3=disabled',
'drm=disabled',
'openvg=disabled',
'png=enabled',
'quartz=disabled',
'qt=disabled',
'tee=disabled',
'xcb=disabled',
'xlib=disabled',
'xlib-xcb=disabled',
'zlib=enabled',
'tests=disabled',
'glib=enabled',
],
fallback: ['cairo'],
)
cairogobj_dep = dependency('cairo-gobject',
required: cairo_opt,
fallback: ['cairo'],
)
nss3_opt = get_option('nss3')
freetype_dep = dependency('freetype2',
required: true,
fallback: ['freetype2', 'freetype_dep'],
)
You can see it's.. opinionated already, disabling most of cairo's features.
Then, we have a bunch of checks! To make sure we have a certain header, or that a type is defined in a certain header, etc. Just portability stuff:
cdata = configuration_data()
# Header checks
cdata.set('HAVE_DLFCN_H', cc.has_header('dlfcn.h'))
cdata.set('HAVE_FCNTL_H', cc.has_header('fcntl.h'))
cdata.set('HAVE_STDLIB_H', cc.has_header('stdlib.h'))
cdata.set('HAVE_SYS_MMAN_H', cc.has_header('sys/mman.h'))
cdata.set('HAVE_SYS_STAT_H', cc.has_header('sys/stat.h'))
cdata.set('HAVE_UNISTD_H', cc.has_header('unistd.h'))
cdata.set('HAVE_FSEEK64', cc.has_function('fseek64'))
cdata.set('HAVE_FSEEKO', cc.has_header_symbol('stdio.h', 'fseeko'))
cdata.set('HAVE_FTELL64', cc.has_function('ftell64'))
cdata.set('HAVE_PREAD64', cc.has_function('pread64'))
cdata.set('HAVE_LSEEK64', cc.has_function('lseek64'))
cdata.set('HAVE_GMTIME_R', cc.has_function('gmtime_r'))
cdata.set('HAVE_GETTIMEOFDAY', cc.has_function('gettimeofday'))
cdata.set('HAVE_LOCALTIME_R', cc.has_function('localtime_r'))
cdata.set('HAVE_POPEN', cc.has_function('popen'))
cdata.set('HAVE_MKSTEMP', cc.has_function('mkstemp'))
cdata.set('HAVE_STRTOK_R', cc.has_function('strtok_r'))
dir_code = '''
int main(int argc, char *argv[])
{
DIR* d = 0;
return 0;
}
'''
cdata.set('HAVE_DIRENT_H', cc.compiles('#include <dirent.h>\n' + dir_code, name : 'Header <dirent.h> defines DIR'))
cdata.set('HAVE_NDIR_H', cc.compiles('#include <ndir.h>\n' + dir_code, name : 'Header <ndir.h> defines DIR'))
cdata.set('HAVE_SYS_DIR_H', cc.compiles('#include <sys/dir.h>\n' + dir_code, name : 'Header <sys/dir.h> defines DIR'))
cdata.set('HAVE_SYS_NDIR_H', cc.compiles('#include <sys/ndir.h>\n' + dir_code, name : 'Header <sys/ndir.h> defines DIR'))
cdata.set('HAVE_NANOSLEEP', cc.has_function('nanosleep'))
# Enable these unconditionally.
cdata.set('OPI_SUPPORT', '1')
cdata.set('TEXTOUT_WORD_LIST', '1')
# Stubs
cdata.set('ICONV_CONST', '')
cdata.set('POPPLER_DATADIR', '')
cdata.set('POPPLER_VERSION', poppler_version)
if libcurl_opt.enabled()
cdata.set('POPPLER_HAS_CURL_SUPPORT', '1')
endif
That configuration data passed later to "configure_file" to generate a
poppler-config.h
, in other meson.build
files (we'll get to that).
pkg-config files (.pc
) have other variables, which we also need to set: it's
mostly paths.
pkgconfig_cdata = configuration_data()
pkgconfig_cdata.set('CMAKE_INSTALL_PREFIX', poppler_prefix)
pkgconfig_cdata.set('CMAKE_INSTALL_FULL_LIBDIR', poppler_libdir)
pkgconfig_cdata.set('CMAKE_INSTALL_FULL_INCLUDEDIR', poppler_includedir)
pkgconfig_cdata.set('POPPLER_VERSION', poppler_version)
pkgconfig_cdata.set('GLIB_REQUIRED', glib_required)
pkgconfig_cdata.set('CAIRO_VERSION', cairo_dep.version())
# Configure "Requires" field & install .pc files for packagers
pkgconfig_cdata.set('PC_REQUIRES', '')
pkgconfig_cdata.set('PC_REQUIRES_PRIVATE', 'Requires.private: poppler = @0@'.format(poppler_version))
Then, there's an internal/
subdirectory, which is not in the original poppler
sources:
# put config.h in an internal directory so
# users don't include it by accident
subdir('internal')
conf_inc = include_directories('internal')
We'll get to what's inside that internal/
subdirectory later.
There's two main poppler source directories we're interested in: poppler/
,
and glib/
. There's also some base utilities, like goo/
and fofi/
, but those
are actually handled by poppler/meson.build
.
subdir('poppler')
if glib_opt.enabled()
subdir('glib')
endif
Finally, we print a nice summary of everything:
#### Summary ####
summary('zlib support', zlib_dep.found(), section: 'Features')
summary('glib support', glib_dep.found(), section: 'Features')
summary('cairo support', cairo_dep.found(), section: 'Features')
summary('Compiler', cc.get_id(), section: 'Toolchain')
summary('Linker', cc.get_linker_id(), section: 'Toolchain')
# Build
summary('Debugging', get_option('debug'), section: 'Build')
summary('Optimization', get_option('optimization'), section: 'Build')
# Directories
summary('prefix', poppler_prefix, section: 'Directories')
summary('includedir', poppler_includedir, section: 'Directories')
summary('libdir', poppler_libdir, section: 'Directories')
Here's what the summary looks like on Fedora 35, for example:
poppler 21.12.0
Features
zlib support : True
glib support : True
cairo support: True
Toolchain
Compiler : gcc
Linker : ld.bfd
Build
Debugging : False
Optimization : 3
Directories
prefix : /tmp/poppler-prefix
includedir : /tmp/poppler-prefix/include
libdir : /tmp/poppler-prefix/lib64
And here's what it looks like on Windows:
poppler 21.12.0
Features
zlib support : True
glib support : True
cairo support: True
Toolchain
Compiler : msvc
Linker : link
Build
Debugging : True
Optimization : 0
Directories
prefix : C:\poppler-prefix
includedir : C:/poppler-prefix/include
libdir : C:/poppler-prefix/lib
Wowee, everybody gets a different slash!
Well, as it turns out, Windows has accepted forward-slash as an alternate path separator for a long time.
Oh word?
Yeah! I've tried finding a definitive source on this, but I mostly found endless arguments
So for now, just trust me that they both work:
(Get-Item C:\Users\amos).Name && (Get-Item C:/Users/amos).Name
amos
amos
That's it for the top-level meson.build
file!
Now let's peek inside internal/meson.build
:
configure_file(
input: '../config.h.cmake',
output: 'config.h',
configuration: cdata,
format: 'cmake',
)
That's it! config.h.cmake
ships with poppler
, and it looks like this:
/* config.h. Generated from config.h.cmake by cmake. */
/* Build against libcurl. */
#cmakedefine ENABLE_LIBCURL 1
/* (cut.) */
/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
*/
#cmakedefine HAVE_DIRENT_H 1
/* Define to 1 if you have the <dlfcn.h> header file. */
#cmakedefine HAVE_DLFCN_H 1
/* Define to 1 if you have the <fcntl.h> header file. */
#cmakedefine HAVE_FCNTL_H 1
/* (cut.) */
Once configured, it ends up in
build/subprojects/poppler-21.12.0/internal/config.h
, looking like so:
/* config.h. Generated from config.h.cmake by cmake. */
/* Build against libcurl. */
/* #undef ENABLE_LIBCURL */
/* (cut.) */
/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
*/
#define HAVE_DIRENT_H
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H
/* Define to 1 if you have the <fcntl.h> header file. */
#define HAVE_FCNTL_H
/* (cut.) */
As you can see, the Meson folks have made it rather easy to "port from CMake", by supporting the various formats use for configuring files. We'll see one more example of this.
Time to look at poppler/meson.build
yawn are we almost done?
Patience bear (and others) - the result is worth all that work.
Mh. If you say so.
First, we configure poppler-config.h.cmake
, another magic header file:
configure_file(
input: 'poppler-config.h.cmake',
output: 'poppler-config.h',
configuration: cdata,
format: 'cmake',
)
That one contains stuff like version numbers, and some duplicates from config.h
.
For example, this:
/* Defines the poppler version. */
#ifndef POPPLER_VERSION
#define POPPLER_VERSION "${POPPLER_VERSION}"
#endif
Turns into this:
/* Defines the poppler version. */
#ifndef POPPLER_VERSION
#define POPPLER_VERSION "21.12.0"
#endif
Next, we list all source files we're gonna need. This one gets pretty long...
Ready? Set? Scroll!
poppler_sources = files([
'../goo/gbase64.cc',
'../goo/gbasename.cc',
'../goo/gfile.cc',
'../goo/GooTimer.cc',
'../goo/GooString.cc',
'../goo/NetPBMWriter.cc',
'../goo/PNGWriter.cc',
'../goo/TiffWriter.cc',
'../goo/JpegWriter.cc',
'../goo/ImgWriter.cc',
'../goo/gstrtod.cc',
'../goo/grandom.cc',
'../goo/glibc.cc',
'../goo/glibc_strtok_r.cc',
'../fofi/FoFiBase.cc',
'../fofi/FoFiEncodings.cc',
'../fofi/FoFiTrueType.cc',
'../fofi/FoFiType1.cc',
'../fofi/FoFiType1C.cc',
'../fofi/FoFiIdentifier.cc',
'Annot.cc',
'AnnotStampImageHelper.cc',
'Array.cc',
'BBoxOutputDev.cc',
'CachedFile.cc',
'Catalog.cc',
'CertificateInfo.cc',
'CharCodeToUnicode.cc',
'CMap.cc',
'DateInfo.cc',
'Decrypt.cc',
'Dict.cc',
'Error.cc',
'FDPDFDocBuilder.cc',
'FILECacheLoader.cc',
'FileSpec.cc',
'FontEncodingTables.cc',
'FontInfo.cc',
'Form.cc',
'Function.cc',
'Gfx.cc',
'GfxFont.cc',
'GfxState.cc',
'GlobalParams.cc',
'Hints.cc',
'ImageEmbeddingUtils.cc',
'JArithmeticDecoder.cc',
'JBIG2Stream.cc',
'JSInfo.cc',
'Lexer.cc',
'Linearization.cc',
'Link.cc',
'LocalPDFDocBuilder.cc',
'MarkedContentOutputDev.cc',
'Movie.cc',
'NameToCharCode.cc',
'Object.cc',
'OptionalContent.cc',
'Outline.cc',
'OutputDev.cc',
'Page.cc',
'PageLabelInfo.cc',
'PageTransition.cc',
'Parser.cc',
'PDFDoc.cc',
'PDFDocBuilder.cc',
'PDFDocEncoding.cc',
'PDFDocFactory.cc',
'PreScanOutputDev.cc',
'ProfileData.cc',
'PSOutputDev.cc',
'PSTokenizer.cc',
'Rendition.cc',
'SecurityHandler.cc',
'SignatureInfo.cc',
'Sound.cc',
'SplashOutputDev.cc',
'Stream.cc',
'StructElement.cc',
'StructTreeRoot.cc',
'TextOutputDev.cc',
'UnicodeMap.cc',
'UnicodeMapFuncs.cc',
'UnicodeTypeTable.cc',
'UTF.cc',
'ViewerPreferences.cc',
'XRef.cc',
'../splash/Splash.cc',
'../splash/SplashBitmap.cc',
'../splash/SplashClip.cc',
'../splash/SplashFTFont.cc',
'../splash/SplashFTFontEngine.cc',
'../splash/SplashFTFontFile.cc',
'../splash/SplashFont.cc',
'../splash/SplashFontEngine.cc',
'../splash/SplashFontFile.cc',
'../splash/SplashFontFileID.cc',
'../splash/SplashPath.cc',
'../splash/SplashPattern.cc',
'../splash/SplashScreen.cc',
'../splash/SplashState.cc',
'../splash/SplashXPath.cc',
'../splash/SplashXPathScanner.cc',
'CourierWidths.pregenerated.c',
'CourierBoldWidths.pregenerated.c',
'CourierBoldObliqueWidths.pregenerated.c',
'CourierObliqueWidths.pregenerated.c',
'HelveticaWidths.pregenerated.c',
'HelveticaBoldWidths.pregenerated.c',
'HelveticaBoldObliqueWidths.pregenerated.c',
'HelveticaObliqueWidths.pregenerated.c',
'SymbolWidths.pregenerated.c',
'TimesBoldWidths.pregenerated.c',
'TimesBoldItalicWidths.pregenerated.c',
'TimesItalicWidths.pregenerated.c',
'TimesRomanWidths.pregenerated.c',
'ZapfDingbatsWidths.pregenerated.c',
])
if libcurl_opt.enabled()
poppler_sources += files([
'CurlCachedFile.cc',
'CurlPDFDocBuilder.cc',
])
endif
if cairo_opt.enabled()
poppler_sources += files([
'CairoFontEngine.cc',
'CairoOutputDev.cc',
'CairoRescaleBox.cc',
])
endif
if jpeg_opt.enabled()
poppler_sources += files([
'DCTStream.cc',
])
endif
if openjpeg_opt.enabled()
poppler_sources += files([
'JPEG2000Stream.cc',
])
else
poppler_sources += files([
'JPXStream.cc',
])
endif
if zlib_opt.enabled()
poppler_sources += files([
'FlateEncoder.cc',
])
endif
if zlib_uncompress_opt.enabled()
poppler_sources += files([
'FlateStream.cc',
])
endif
if nss3_opt.enabled()
poppler_source += files([
'SignatureHandler.cc',
])
endif
Note that we use the
files function,
which returns a list[file]
- that's right, not everything is stringly-typed in
Meson!
The rest is pretty straightforward.
We declare a build target, from all those source files, with all the right include directories, specifying that that one should be installed to the prefix:
poppler = build_target('poppler',
poppler_sources,
target_type : 'static_library',
dependencies: [freetype_dep],
include_directories: include_directories('../internal', '.', '../goo', '..'),
install: true,
)
Then we declare a dependency, so other meson projects can depend on it:
poppler_dep = declare_dependency(
link_with: poppler,
include_directories : include_directories('.'),
)
And then, just like glib
and cairo
before us, we override the poppler
dependency, so that if any other meson subprojects are looking for a poppler
,
they use us and no one else:
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('poppler', poppler_dep)
endif
And finally, we configure the .pc
file:
configure_file(
input: '../poppler.pc.cmake',
output: 'poppler.pc',
configuration: pkgconfig_cdata,
format: 'cmake@',
install_dir: poppler_pkgconfigdir,
)
Which turns this:
prefix=@CMAKE_INSTALL_PREFIX@
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: poppler
Description: PDF rendering library
Version: @POPPLER_VERSION@
Libs: -L${libdir} -lpoppler
Cflags: -I${includedir}/poppler
Into this:
prefix=/tmp/poppler-prefix
libdir=/tmp/poppler-prefix/lib64
includedir=/tmp/poppler-prefix/include
Name: poppler
Description: PDF rendering library
Version: 21.12.0
Libs: -L${libdir} -lpoppler
Cflags: -I${includedir}/poppler
Wonderful!
All we have left is glib/meson.build
, which is the glib interface to poppler
(just like poppler has a QT5/QT6 interface):
It looks very similar to what we've seen so far. First we configure a third header:
cdata_features = configuration_data()
cdata_features.set('CAIRO_FEATURE', '#define POPPLER_HAS_CAIRO 1')
cdata_features.set('POPPLER_MAJOR_VERSION', poppler_major_version)
cdata_features.set('POPPLER_MINOR_VERSION', poppler_minor_version)
cdata_features.set('POPPLER_MICRO_VERSION', poppler_micro_version)
configure_file(
input: 'poppler-features.h.cmake',
output: 'poppler-features.h',
configuration: cdata_features,
format: 'cmake@',
)
Then we list our "public" headers, that will be installed into the prefix:
poppler_glib_public_headers = files([
'poppler-action.h',
'poppler-date.h',
'poppler-document.h',
'poppler-page.h',
'poppler-attachment.h',
'poppler-form-field.h',
'poppler-annot.h',
'poppler-layer.h',
'poppler-movie.h',
'poppler-media.h',
'poppler.h',
'poppler-structure-element.h',
])
install_headers(poppler_glib_public_headers,
subdir: poppler_includedir / 'poppler/glib')
Here's the tricky bit, and the reason we imported the gnome
meson module:
poppler_enums = gnome.mkenums('poppler-enums',
sources: poppler_glib_public_headers,
c_template: 'poppler-enums.c.template',
h_template: 'poppler-enums.h.template',
install_dir: poppler_includedir / 'poppler/glib',
install_header: true,
)
This configures the poppler-enums.h.template
file, that looks like this:
/*** BEGIN file-header ***/
#ifndef POPPLER_ENUMS_H
#define POPPLER_ENUMS_H
#include <glib-object.h>
#include "poppler.h"
G_BEGIN_DECLS
/*** END file-header ***/
/*** BEGIN file-production ***/
/* enumerations from "@filename@" */
/*** END file-production ***/
/*** BEGIN value-header ***/
POPPLER_PUBLIC
GType @enum_name@_get_type (void) G_GNUC_CONST;
#define POPPLER_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
/*** END value-header ***/
/*** BEGIN file-tail ***/
G_END_DECLS
#endif /* !POPPLER_ENUMS_H */
/*** END file-tail ***/
Into build/subprojects/poppler-21.12.0/glib/poppler-enums.h
, which looks like
that:
/* This file is generated by glib-mkenums, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */
#ifndef POPPLER_ENUMS_H
#define POPPLER_ENUMS_H
#include <glib-object.h>
#include "poppler.h"
G_BEGIN_DECLS
/* enumerations from "/home/amos/bearcove/poppler-meson/build/../subprojects/poppler-21.12.0/glib/poppler-action.h" */
POPPLER_PUBLIC
GType poppler_action_type_get_type (void) G_GNUC_CONST;
#define POPPLER_TYPE_ACTION_TYPE (poppler_action_type_get_type ())
POPPLER_PUBLIC
GType poppler_dest_type_get_type (void) G_GNUC_CONST;
#define POPPLER_TYPE_DEST_TYPE (poppler_dest_type_get_type ())
POPPLER_PUBLIC
GType poppler_action_movie_operation_get_type (void) G_GNUC_CONST;
#define POPPLER_TYPE_ACTION_MOVIE_OPERATION (poppler_action_movie_operation_get_type ())
POPPLER_PUBLIC
GType poppler_action_layer_action_get_type (void) G_GNUC_CONST;
#define POPPLER_TYPE_ACTION_LAYER_ACTION (poppler_action_layer_action_get_type ())
/* (cut.) */
#endif /* !POPPLER_ENUMS_H */
/* Generated data ends here */
Using a python3 script that parses headers like poppler's glib/poppler-action.h
:
#ifndef __POPPLER_ACTION_H__
#define __POPPLER_ACTION_H__
#include <glib-object.h>
#include "poppler.h"
G_BEGIN_DECLS
/* (cut.) */
typedef enum
{
POPPLER_DEST_UNKNOWN,
POPPLER_DEST_XYZ,
POPPLER_DEST_FIT,
POPPLER_DEST_FITH,
POPPLER_DEST_FITV,
POPPLER_DEST_FITR,
POPPLER_DEST_FITB,
POPPLER_DEST_FITBH,
POPPLER_DEST_FITBV,
POPPLER_DEST_NAMED
} PopplerDestType;
/* (cut.) */
That's... terrifying. They're parsing C headers from Python?
OOhhh yes.
# read lines until we have no open comments
while re.search(r'/\*([^*]|\*(?!/))*$', line):
line += curfile.readline()
# strip comments w/o options
line = re.sub(r'''/\*(?!<)
([^*]+|\*(?!/))*
\*/''', '', line)
# ignore forward declarations
if re.match(r'\s*typedef\s+enum.*;', line):
continue
m = re.match(r'''\s*typedef\s+enum\s*[_A-Za-z]*[_A-Za-z0-9]*\s*
({)?\s*
(?:/\*<
(([^*]|\*(?!/))*)
>\s*\*/)?
\s*({)?''', line, flags=re.X)
Why does that Python feel like Perl...
...because it used to be:
#!/usr/bin/env python3
# If the code below looks horrible and unpythonic, do not panic.
#
# It is.
#
# This is a manual conversion from the original Perl script to
# Python. Improvements are welcome.
#
Ahhhhhhhhhhhhhhhhhhhhhhhh-
Are you having fun?
I mean, yes. This almost makes up for not really talking about vector graphics for like ten pages.
Splendid. I knew you'd like it.
Then, we list source files, including the ones we generated with glib-mkenums
:
poppler_glib_sources = files([
'poppler-action.cc',
'poppler-date.cc',
'poppler-document.cc',
'poppler-page.cc',
'poppler-attachment.cc',
'poppler-form-field.cc',
'poppler-annot.cc',
'poppler-layer.cc',
'poppler-movie.cc',
'poppler-media.cc',
'poppler.cc',
'poppler-cached-file-loader.cc',
'poppler-input-stream.cc',
'poppler-structure-element.cc',
])
poppler_glib_generated_sources = files([
'../poppler/CairoFontEngine.cc',
'../poppler/CairoOutputDev.cc',
'../poppler/CairoRescaleBox.cc',
])
poppler_glib_generated_sources += poppler_enums
poppler_glib_sources += poppler_glib_generated_sources
And then, the usual dance: declare a build target (with a lot of dependencies
there, chaos glib is a ladder), declare a dependency, override it...
poppler_glib_cpp_args = []
if cc.get_id() == 'msvc'
poppler_glib_cpp_args += ['-DG_OS_WIN32=1']
endif
poppler_glib = build_target('poppler-glib',
poppler_glib_sources,
target_type: 'static_library',
cpp_args: poppler_glib_cpp_args,
dependencies: [glib_dep, gobject_dep, gio_dep, cairo_dep, cairogobj_dep],
include_directories: include_directories('../internal', '.', '../goo', '../poppler', '..'),
install: true,
)
poppler_glib_dep = declare_dependency(
link_with: poppler_glib,
include_directories : include_directories('.'),
)
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('poppler-glib', poppler_glib_dep)
endif
And configure the pkg-config
file:
configure_file(
input: '../poppler-glib.pc.cmake',
output: 'poppler-glib.pc',
configuration: pkgconfig_cdata,
format: 'cmake@',
install_dir: poppler_pkgconfigdir,
)
And we're home free!
The next step, of course, is to build that in CI.
Here's another article just for you:
A half-hour to learn Rust
In order to increase fluency in a programming language, one has to read a lot of it.
But how can you read a lot of it if you don't know what it means?
In this article, instead of focusing on one or two concepts, I'll try to go through as many Rust snippets as I can, and explain what the keywords and symbols they contain mean.
Ready? Go!