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.
Thanks to my sponsors:
If you liked what you saw, please support my work!
Here's another article just for you:
I use the draw.io desktop app to
make diagrams for my website. I run it on an actual desktop, like Windows or
macOS, but the asset pipeline that converts .drawio
files, to .pdf
, to
.svg
, and then to .svg
again (but smaller) runs on Linux.
So I have a Rust program somewhere that opens headless chromium, and loads just the HTML/JS/CSS part of draw.io I need to render my diagrams, and then use Chromium's "print to PDF" functionality to save a PDF.