diff --git a/configure.ac b/configure.ac index 918a50c1..d5c191ce 100644 --- a/configure.ac +++ b/configure.ac @@ -26,7 +26,12 @@ VIPS_MAJOR_VERSION=vips_major_version() VIPS_MINOR_VERSION=vips_minor_version() VIPS_MICRO_VERSION=vips_micro_version() VIPS_VERSION=vips_version() -VIPS_VERSION_STRING=$VIPS_VERSION-`date -u -r $srcdir/ChangeLog` +VIPS_VERSION_STRING="$VIPS_VERSION-$(date -u -r $srcdir/ChangeLog)" + +# packages add to these as we find them +VIPS_CFLAGS="" +VIPS_INCLUDES="" +VIPS_LIBS="" # libtool library versioning ... not user-visible (except as part of the # library file name) and does not correspond to major/minor/micro above @@ -408,6 +413,9 @@ PKG_CHECK_MODULES(DATE_TIME_FORMAT_ISO8601, glib-2.0 >= 2.62, ] ) +VIPS_CFLAGS="$VIPS_CFLAGS $GIO_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $GIO_LIBS" + # if available, we use pthread_setattr_default_np() to raise the per-thread # stack size ... musl (libc on Alpine), for example, has a very small stack per # thread by default @@ -447,6 +455,9 @@ if test x"$expat_found" = x"no"; then exit 1 fi +VIPS_CFLAGS="$VIPS_CFLAGS $EXPAT_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $EXPAT_LIBS" + # optional supporting libraries AC_ARG_WITH([gsf], @@ -478,6 +489,9 @@ if test x"$with_gsf" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $GSF_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $GSF_LIBS" + AC_ARG_WITH([fftw], AS_HELP_STRING([--without-fftw], [build without fftw (default: test)])) @@ -493,6 +507,9 @@ if test x"$with_fftw" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $FFTW_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $FFTW_LIBS" + # ImageMagick AC_ARG_WITH([magick], AS_HELP_STRING([--without-magick], [build without libMagic (default: test)])) @@ -652,6 +669,9 @@ else enable_magicksave=no fi +VIPS_CFLAGS="$VIPS_CFLAGS $MAGICK_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $MAGICK_LIBS" + # orc AC_ARG_WITH([orc], AS_HELP_STRING([--without-orc], [build without orc (default: test)])) @@ -688,6 +708,9 @@ if test x"$with_orc" = x"yes"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $ORC_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $ORC_LIBS" + # lcms ... refuse to use lcms1 AC_ARG_WITH([lcms], AS_HELP_STRING([--without-lcms], [build without lcms (default: test)])) @@ -708,6 +731,9 @@ fi # is detected AM_CONDITIONAL(ENABLE_LCMS, [test x"$with_lcms" != x"no"]) +VIPS_CFLAGS="$VIPS_CFLAGS $LCMS_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $LCMS_LIBS" + # OpenEXR AC_ARG_WITH([OpenEXR], AS_HELP_STRING([--without-OpenEXR], [build without OpenEXR (default: test)])) @@ -725,6 +751,9 @@ if test x"$with_OpenEXR" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $OPENEXR_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $OPENEXR_LIBS" + # nifti AC_ARG_WITH([nifti], AS_HELP_STRING([--without-nifti], [build without nifti (default: test)])) @@ -738,6 +767,10 @@ if test x"$with_nifti" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS" + # libheif AC_ARG_WITH([heif], AS_HELP_STRING([--without-heif], [build without libheif (default: test)])) @@ -805,6 +838,9 @@ if test x"$with_heif" = x"yes"; then CFLAGS="$save_CFLAGS" fi +VIPS_CFLAGS="$VIPS_CFLAGS $HEIF_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $HEIF_LIBS" + # pdfium AC_ARG_WITH([pdfium], AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) @@ -825,6 +861,9 @@ if test x"$with_pdfium" != x"no"; then ]) fi +VIPS_CFLAGS="$VIPS_CFLAGS $PDFIUM_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $PDFIUM_LIBS" + # poppler AC_ARG_WITH([poppler], AS_HELP_STRING([--without-poppler], [build without poppler (default: test)])) @@ -840,6 +879,9 @@ if test x"$with_poppler" != x"no"; then ]) fi +VIPS_CFLAGS="$VIPS_CFLAGS $POPPLER_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $POPPLER_LIBS" + # librsvg AC_ARG_WITH([rsvg], AS_HELP_STRING([--without-rsvg], [build without rsvg (default: test)])) @@ -856,6 +898,9 @@ if test x"$with_rsvg" != x"no"; then ]) fi +VIPS_CFLAGS="$VIPS_CFLAGS $RSVG_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $RSVG_LIBS" + # zlib # some platforms, like macosx, are missing the .pc files for zlib, so # we fall back to FIND_ZLIB @@ -879,6 +924,10 @@ if test x"$with_zlib" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $ZLIB_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $ZLIB_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $ZLIB_LIBS" + # OpenSlide AC_ARG_WITH([openslide], AS_HELP_STRING([--without-openslide], @@ -906,6 +955,9 @@ if test x"$with_openslide" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $OPENSLIDE_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $OPENSLIDE_LIBS" + # matio AC_ARG_WITH([matio], AS_HELP_STRING([--without-matio], [build without matio (default: test)])) @@ -922,6 +974,9 @@ if test x"$with_matio" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $MATIO_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $MATIO_LIBS" + # not external libraries, but have options to disable them, helps to # reduce attack surface @@ -965,6 +1020,9 @@ if test x"$with_cfitsio" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $CFITSIO_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $CFITSIO_LIBS" + # libwebp ... target 0.6+ to reduce complication # webp has the stuff for handling metadata in two separate libraries -- we # insit on having all of them @@ -983,6 +1041,9 @@ if test x"$with_libwebp" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $LIBWEBP_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $LIBWEBP_LIBS" + # pangoft2 AC_ARG_WITH([pangoft2], AS_HELP_STRING([--without-pangoft2], @@ -1000,6 +1061,9 @@ if test x"$with_pangoft2" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $PANGOFT2_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $PANGOFT2_LIBS" + # look for TIFF with pkg-config ... fall back to our tester # pkgconfig support for libtiff starts with libtiff-4 AC_ARG_WITH([tiff], @@ -1035,6 +1099,10 @@ if test x"$with_tiff" != x"no"; then INCLUDES="$save_INCLUDES" fi +VIPS_CFLAGS="$VIPS_CFLAGS $TIFF_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $TIFF_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $TIFF_LIBS" + # giflib FIND_GIFLIB( [with_giflib="yes (found by search)" @@ -1044,6 +1112,10 @@ FIND_GIFLIB( ] ) +VIPS_CFLAGS="$VIPS_CFLAGS $GIFLIB_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $GIFLIB_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $GIFLIB_LIBS" + # Look for libspng first # 0.6.1 uses "libspng.pc", git master libspng uses "spng.pc" AC_ARG_WITH([libspng], @@ -1067,6 +1139,9 @@ if test x"$with_libspng" != x"no"; then ) fi +VIPS_CFLAGS="$VIPS_CFLAGS $SPNG_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $SPNG_LIBS" + # look for PNG with pkg-config ... fall back to our tester # we can have both PNG and SPNG enabled, with SPNG for read and PNG for # write @@ -1102,6 +1177,10 @@ if test x"$with_png" != x"no"; then CFLAGS="$save_CFLAGS" fi +VIPS_CFLAGS="$VIPS_CFLAGS $PNG_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $PNG_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $PNG_LIBS" + # look for libimagequant with pkg-config (only if libpng is enabled) AC_ARG_WITH([imagequant], AS_HELP_STRING([--without-imagequant], [build without imagequant (default: test)])) @@ -1120,6 +1199,9 @@ else with_imagequant=no fi +VIPS_CFLAGS="$VIPS_CFLAGS $IMAGEQUANT_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $IMAGEQUANT_LIBS" + # look for libjpeg with pkg-config ... fall back to our tester AC_ARG_WITH([jpeg], AS_HELP_STRING([--without-jpeg], [build without libjpeg (default: test)])) @@ -1157,6 +1239,10 @@ if test x"$with_jpeg" != x"no"; then CFLAGS="$save_CFLAGS" fi +VIPS_CFLAGS="$VIPS_CFLAGS $JPEG_CFLAGS" +VIPS_INCLUDES="$VIPS_INCLUDES $JPEG_INCLUDES" +VIPS_LIBS="$VIPS_LIBS $JPEG_LIBS" + # libexif AC_ARG_WITH([libexif], AS_HELP_STRING([--without-libexif], [build without libexif (default: test)])) @@ -1184,6 +1270,9 @@ if test x"$with_libexif" != x"no"; then CPPFLAGS="$save_CPPFLAGS" fi +VIPS_CFLAGS="$VIPS_CFLAGS $EXIF_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $EXIF_LIBS" + # fuzzing AC_ARG_VAR([LIB_FUZZING_ENGINE], [fuzzing library, e.g. /path/to/libFuzzer.a]) @@ -1191,14 +1280,47 @@ if test x"$LIB_FUZZING_ENGINE" = x; then LIB_FUZZING_ENGINE="libstandaloneengine.a" fi -# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS -VIPS_CFLAGS="$VIPS_CFLAGS $GIO_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $SPNG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_CFLAGS $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS $HEIF_CFLAGS" VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" -VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES $NIFTI_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $HEIF_LIBS $MAGICK_LIBS $SPNG_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GIO_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_CFLAGS="$VIPS_CFLAGS $VIPS_DEBUG_FLAGS $REQUIRED_CFLAGS" +VIPS_LIBS="$VIPS_LIBS $REQUIRED_LIBS -lm" # autoconf hates multi-line AC_SUBST so we have to have another copy of this # thing -VIPS_CONFIG="enable debug: $enable_debug, enable deprecated library components: $enable_deprecated, enable docs with gtkdoc: $enable_gtk_doc, gobject introspection: $found_introspection, enable radiance support: $with_radiance, enable analyze support: $with_analyze, enable PPM support: $with_ppm, generate C++ docs: $with_doxygen, use fftw3 for FFT: $with_fftw, Magick package: $with_magickpackage, Magick API version: $magick_version, load with libMagick: $enable_magickload, save with libMagick: $enable_magicksave, accelerate loops with orc: $with_orc, ICC profile support with lcms: $with_lcms, file import with niftiio: $with_nifti, file import with libheif: $with_heif, file import with OpenEXR: $with_OpenEXR, file import with OpenSlide: $with_openslide, file import with matio: $with_matio, PDF import with PDFium: $with_pdfium, PDF import with poppler-glib: $with_poppler, SVG import with librsvg-2.0: $with_rsvg, zlib: $with_zlib, file import with cfitsio: $with_cfitsio, file import/export with libwebp: $with_libwebp, text rendering with pangoft2: $with_pangoft2, file import/export with libspng: $with_libspng, file import/export with libpng: $with_png, support 8bpp PNG quantisation: $with_imagequant, file import/export with libtiff: $with_tiff, file import/export with giflib: $with_giflib, file import/export with libjpeg: $with_jpeg, image pyramid export: $with_gsf, use libexif to load/save JPEG metadata: $with_libexif" +VIPS_CONFIG="\ +enable debug: $enable_debug, \ +enable deprecated library components: $enable_deprecated, \ +enable docs with gtkdoc: $enable_gtk_doc, \ +gobject introspection: $found_introspection, \ +RAD load/save: $with_radiance, \ +Analyze7 load/save: $with_analyze, \ +PPM load/save: $with_ppm, \ +generate C++ docs: $with_doxygen, \ +use fftw3 for FFT: $with_fftw, \ +accelerate loops with orc: $with_orc, \ +ICC profile support with lcms: $with_lcms, \ +zlib: $with_zlib, \ +text rendering with pangoft2: $with_pangoft2, \ +EXIF metadata support with libexif: $with_libexif, \ +JPEG load/save with libjpeg: $with_jpeg, \ +PNG load with libspng: $with_libspng, \ +PNG load/save with libpng: $with_png, \ +8bpp PNG quantisation: $with_imagequant, \ +TIFF load/save with libtiff: $with_tiff, \ +image pyramid save: $with_gsf, \ +GIF load with giflib: $with_giflib, \ +HEIC/AVIF load/save with libheif: $with_heif, \ +WebP load/save with libwebp: $with_libwebp, \ +PDF load with PDFium: $with_pdfium, \ +PDF load with poppler-glib: $with_poppler, \ +SVG load with librsvg-2.0: $with_rsvg, \ +EXR load/save with OpenEXR: $with_OpenEXR, \ +slide load with OpenSlide: $with_openslide, \ +Matlab load with matio: $with_matio, \ +NIfTI load/save with niftiio: $with_nifti, \ +FITS load/save with cfitsio: $with_cfitsio, \ +Magick package: $with_magickpackage, \ +Magick API version: $magick_version, \ +load with libMagickCore: $enable_magickload, \ +save with libMagickCore: $enable_magicksave" AC_SUBST(VIPS_LIBDIR) @@ -1261,51 +1383,53 @@ AC_OUTPUT # also add any new items to VIPS_CONFIG above AC_MSG_RESULT([dnl -* build options +## Build options enable debug: $enable_debug enable deprecated library components: $enable_deprecated enable docs with gtkdoc: $enable_gtk_doc -gobject introspection: $found_introspection -enable radiance support: $with_radiance -enable analyze support: $with_analyze -enable PPM support: $with_ppm -generate C++ docs: $with_doxygen +gobject introspection: $found_introspection +RAD load/save: $with_radiance +Analyze7 load/save: $with_analyze +PPM load/save: $with_ppm +generate C++ docs: $with_doxygen -* optional dependencies -use fftw3 for FFT: $with_fftw -Magick package: $with_magickpackage -Magick API version: $magick_version -load with libMagick: $enable_magickload -save with libMagick: $enable_magicksave -accelerate loops with orc: $with_orc - (requires orc-0.4.11 or later) -ICC profile support with lcms: $with_lcms -file import with niftiio: $with_nifti -file import with libheif: $with_heif -file import with OpenEXR: $with_OpenEXR -file import with OpenSlide: $with_openslide - (requires openslide-3.3.0 or later) -file import with matio: $with_matio -PDF import with PDFium: $with_pdfium -PDF import with poppler-glib: $with_poppler - (requires poppler-glib 0.16.0 or later) -SVG import with librsvg-2.0: $with_rsvg - (requires librsvg-2.0 2.34.0 or later) -zlib: $with_zlib -file import with cfitsio: $with_cfitsio -file import/export with libwebp: $with_libwebp - (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) -text rendering with pangoft2: $with_pangoft2 -file import/export with libspng: $with_libspng - (requires libspng-0.6 or later) -file import/export with libpng: $with_png - (requires libpng-1.2.9 or later) -support 8bpp PNG quantisation: $with_imagequant - (requires libimagequant) -file import/export with libtiff: $with_tiff -file import/export with giflib: $with_giflib -file import/export with libjpeg: $with_jpeg -image pyramid export: $with_gsf - (requires libgsf-1 1.14.26 or later) -use libexif to load/save JPEG metadata: $with_libexif +## Optional dependencies +use fftw3 for FFT: $with_fftw +accelerate loops with orc: $with_orc + (requires orc-0.4.11 or later) +ICC profile support with lcms: $with_lcms +zlib: $with_zlib +text rendering with pangoft2: $with_pangoft2 +EXIF metadata support with libexif: $with_libexif + +## File format support +JPEG load/save with libjpeg: $with_jpeg +PNG load with libspng: $with_libspng + (requires libspng-0.6 or later) +PNG load/save with libpng: $with_png + (requires libpng-1.2.9 or later) +8bpp PNG quantisation: $with_imagequant + (requires libimagequant) +TIFF load/save with libtiff: $with_tiff +image pyramid save: $with_gsf + (requires libgsf-1 1.14.26 or later) +GIF load with giflib: $with_giflib +HEIC/AVIF load/save with libheif: $with_heif +WebP load/save with libwebp: $with_libwebp + (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) +PDF load with PDFium: $with_pdfium +PDF load with poppler-glib: $with_poppler + (requires poppler-glib 0.16.0 or later) +SVG load with librsvg-2.0: $with_rsvg + (requires librsvg-2.0 2.34.0 or later) +EXR load/save with OpenEXR: $with_OpenEXR +slide load with OpenSlide: $with_openslide + (requires openslide-3.3.0 or later) +Matlab load with matio: $with_matio +NIfTI load/save with niftiio: $with_nifti +FITS load/save with cfitsio: $with_cfitsio +Magick package: $with_magickpackage +Magick API version: $magick_version +load with libMagickCore: $enable_magickload +save with libMagickCore: $enable_magicksave ]) diff --git a/doc/binding.xml b/doc/binding.xml index 956b0101..24e30f7b 100644 --- a/doc/binding.xml +++ b/doc/binding.xml @@ -19,10 +19,35 @@ Don’t bind the top-level C API - The libvips C API (vips_add() and so on) is very inconvenient and dangerous to use from other languages due to its heavy use of varargs. + The libvips C API (vips_add() and so on) was designed to be easy for humans to write. It is inconvenient and dangerous to use from other languages due to its heavy use of varargs. - It’s much better to use the layer below. This lower layer is structured as create operator, set parameters, execute, extract results. For example, you can execute vips_invert() like this: + It’s much better to use the layer below. This lower layer is structured as: + + + + + Create operator. You can use vips_operation_new() to make a new VipsOperation object from an operator nickname, like "add". + + + + + Set parameters. You can loop over the operation with vips_argument_map() to get the name and type of each input argument. For each argument, you need to get the value from your language, convert to a GValue, then use g_object_set_property() to set that value on the operator. + + + + + Execute with vips_cache_operation_build(). + + + + + Extract results. Again, you loop over the operator arguments with vips_argument_map(), but instead of inputs, this time you look for output arguments. You extract their value with g_object_get_property(), and pass the value back to your language. + + + + + For example, you can execute vips_invert() like this: /* compile with @@ -111,17 +136,11 @@ main( int argc, char **argv ) return( 0 ); } - - libvips has a couple of extra things to let you examine the arguments and types of an operator at runtime. Use vips_argument_map() to loop over all the arguments of an operator, and vips_object_get_argument() to fetch the type and flags of a specific argument. - - - Use vips_operation_get_flags() to get general information about an operator. - Compiled language which can call C - The C++ binding uses this lower layer to define a function called VImage::call() which can call any libvips operator with a not-varargs set of variable arguments. + The C++ binding uses this lower layer to define a function called VImage::call() which can call any libvips operator with a set of variable arguments. A small Python program walks the set of all libvips operators and generates a set of static bindings. For example: @@ -139,7 +158,7 @@ VImage VImage::invert( VOption *options ) } - So from C++ you can call any libvips operator (though without type-safety) with VImage::call(), or use the member functions on VImage to get type-safe calls for at least the required operator arguments. + So from C++ you can call any libvips operator (though without static typechecking) with VImage::call(), or use the member functions on VImage to get type-checked calls for at least the required operator arguments. The VImage class also adds automatic reference counting, constant expansion, operator overloads, and various other useful features. @@ -151,13 +170,13 @@ VImage VImage::invert( VOption *options ) Languages like Ruby, Python, JavaScript and LuaJIT can’t call C directly, but they do support FFI. The bindings for these languages work rather like C++, but use FFI to call into libvips and run operations. - Since these languages are dynamic, they can add another trick: they intercept the method-missing hook and attempt to run any method calls not implemented by the Image class as libvips operators. This makes these bindings self-writing: they only contain a small amount of code and just expose everything they find in the libvips class hierarchy. + Since these languages are dynamic, they can add another trick: they intercept the method-missing hook and attempt to run any method calls not implemented by the Image class as libvips operators. In effect, the binding is generated at runtime. Dynamic langauge without FFI - PHP does not have FFI, unfortunately, so for this language a small native module implements the general vips_call() function for PHP language types, and a larger pure PHP layer makes it convenient to use. + PHP does not have a useful FFI, unfortunately, so for this language a small C module implements the general vips_call() function for PHP language types, and a larger pure PHP layer makes it convenient to use.