Porting poppler to meson

👋 This page was last updated ~3 years ago. Just so you know.

It took a hot minute.

Cool bear

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
Cool bear

Can't make your mind about branch names huh?

Amos

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
Cool bear

Wowee, everybody gets a different slash!

Amos

Well, as it turns out, Windows has accepted forward-slash as an alternate path separator for a long time.

Cool bear

Oh word?

Amos

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

Cool bear

yawn are we almost done?

Amos

Patience bear (and others) - the result is worth all that work.

Cool bear

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.) */
Cool bear

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)
Cool bear

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.
#
Cool bear

Ahhhhhhhhhhhhhhhhhhhhhhhh-

Amos

Are you having fun?

Cool bear

I mean, yes. This almost makes up for not really talking about vector graphics for like ten pages.

Amos

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.

Comment on /r/fasterthanlime

(JavaScript is required to see this. Or maybe my stuff broke)

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!

Variable bindings