tikz-gallery-generator

Custum build of stapix for tikz.pablopie.xyz

Commit
04d70d11e8ddf12551f6b736fdcc31c7e69db199
Parent
c8c8976a20630694e2fd2f88904e7bec022b6523
Author
Pablo <pablo-pie@riseup.net>
Date

Added custom MuPDF bindings to the repo

Also fixed a typo in outro.html

Diffstats

9 files changed, 948 insertions, 1 deletions

Status Name Changes Insertions Deletions
Added .gitmodules 1 file changed 3 0
Added mupdf-sys/.gitignore 1 file changed 1 0
Added mupdf-sys/Cargo.lock 1 file changed 235 0
Added mupdf-sys/Cargo.toml 1 file changed 194 0
Added mupdf-sys/build.rs 1 file changed 385 0
Added mupdf-sys/mupdf 1 file changed 1 0
Added mupdf-sys/src/lib.rs 1 file changed 39 0
Added mupdf-sys/src/wrapper.c 1 file changed 89 0
Modified src/outro.html 2 files changed 1 1
diff --git /dev/null b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "mupdf-sys/mupdf"]
+	path = mupdf-sys/mupdf
+	url = https://github.com/ArtifexSoftware/mupdf.git
diff --git /dev/null b/mupdf-sys/.gitignore
@@ -0,0 +1 @@
+/target
diff --git /dev/null b/mupdf-sys/Cargo.lock
@@ -0,0 +1,235 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.72.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+
+[[package]]
+name = "cc"
+version = "1.2.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.182"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mupdf-sys"
+version = "1.0.0"
+dependencies = [
+ "bindgen",
+ "cc",
+ "pkg-config",
+ "regex",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
diff --git /dev/null b/mupdf-sys/Cargo.toml
@@ -0,0 +1,194 @@
+[package]
+name = "mupdf-sys"
+version = "1.0.0"
+edition = "2021"
+include = [
+  "COPYING*",
+  "LICENSE*",
+  "AUTHORS*",
+
+  "*.rs",
+  "src/wrapper.c",
+
+  "mupdf/resources/hyphen/hyph-all.zip",
+  "mupdf/resources/hyphen/hyph-std.zip",
+  "mupdf/generated/resources/hyphen/*.c",
+
+  "mupdf/resources/fonts/urw/*.cff",
+  "mupdf/generated/resources/fonts/urw/*.c",
+
+  "mupdf/Make*",
+  "mupdf/platform/win32/*",
+
+  "mupdf/scripts/*.c",
+  "mupdf/source/helpers/pkcs7/*.c",
+  "mupdf/include/mupdf/*.h",
+  "mupdf/include/mupdf/helpers/*.h",
+
+  "mupdf/source/fitz/*.h",
+  "mupdf/source/fitz/*.c",
+  "mupdf/source/fitz/*.cpp",
+  "mupdf/source/fitz/icc/*.icc.h",
+  "mupdf/include/mupdf/fitz/*.h",
+
+  "mupdf/source/pdf/*.c",
+  "mupdf/source/pdf/*.h",
+  "mupdf/source/pdf/cmaps/*.h",
+  "mupdf/source/pdf/js/*.h",
+  "mupdf/include/mupdf/pdf/*.h",
+
+  "mupdf/source/xps/*.c",
+  "mupdf/source/xps/*.h",
+
+  "mupdf/source/svg/*.c",
+  "mupdf/source/svg/*.h",
+
+  "mupdf/source/html/*.c",
+  "mupdf/source/html/*.h",
+
+  "mupdf/source/reflow/*.c",
+
+  "mupdf/source/cbz/*.c",
+
+  "mupdf/thirdparty/brotli/c/include/brotli/*.h",
+  "mupdf/thirdparty/brotli/c/common/*.c",
+  "mupdf/thirdparty/brotli/c/common/*.h",
+  "mupdf/thirdparty/brotli/c/dec/*.c",
+  "mupdf/thirdparty/brotli/c/dec/*.h",
+  "mupdf/thirdparty/brotli/c/enc/*.c",
+  "mupdf/thirdparty/brotli/c/enc/*.h",
+
+  "mupdf/thirdparty/freetype/*.c",
+  "mupdf/thirdparty/freetype/src/base/*.c",
+  "mupdf/thirdparty/freetype/src/base/*.h",
+  "mupdf/thirdparty/freetype/src/cff/*.c",
+  "mupdf/thirdparty/freetype/src/cff/*.h",
+  "mupdf/thirdparty/freetype/src/cid/*.c",
+  "mupdf/thirdparty/freetype/src/cid/*.h",
+  "mupdf/thirdparty/freetype/src/psaux/*.c",
+  "mupdf/thirdparty/freetype/src/psaux/*.h",
+  "mupdf/thirdparty/freetype/src/pshinter/*.c",
+  "mupdf/thirdparty/freetype/src/pshinter/*.h",
+  "mupdf/thirdparty/freetype/src/psnames/*.c",
+  "mupdf/thirdparty/freetype/src/psnames/*.h",
+  "mupdf/thirdparty/freetype/src/raster/*.c",
+  "mupdf/thirdparty/freetype/src/raster/*.h",
+  "mupdf/thirdparty/freetype/src/sfnt/*.c",
+  "mupdf/thirdparty/freetype/src/sfnt/*.h",
+  "mupdf/thirdparty/freetype/src/smooth/*.c",
+  "mupdf/thirdparty/freetype/src/smooth/*.h",
+  "mupdf/thirdparty/freetype/src/truetype/*.c",
+  "mupdf/thirdparty/freetype/src/truetype/*.h",
+  "mupdf/thirdparty/freetype/src/type1/*.c",
+  "mupdf/thirdparty/freetype/src/type1/*.h",
+  "mupdf/thirdparty/freetype/include",
+  "mupdf/scripts/freetype/*.h",
+
+  "mupdf/thirdparty/gumbo-parser/src/*.c",
+  "mupdf/thirdparty/gumbo-parser/src/*.h",
+  "mupdf/thirdparty/gumbo-parser/visualc/include/*.h",
+
+  "mupdf/thirdparty/harfbuzz/src/*.cc",
+  "mupdf/thirdparty/harfbuzz/src/*.h",
+  "mupdf/thirdparty/harfbuzz/src/*.hh",
+  "mupdf/thirdparty/harfbuzz/src/graph/gsubgpos-context.cc",
+  "mupdf/thirdparty/harfbuzz/src/graph/*.hh",
+  "mupdf/thirdparty/harfbuzz/src/OT",
+
+  "mupdf/thirdparty/libjpeg/*.c",
+  "mupdf/thirdparty/libjpeg/*.h",
+  "mupdf/scripts/libjpeg/*.h",
+
+  "mupdf/thirdparty/lcms2/src/*.c",
+  "mupdf/thirdparty/lcms2/src/*.h",
+  "mupdf/thirdparty/lcms2/include/*.h",
+
+  "mupdf/thirdparty/mujs/*.c",
+  "mupdf/thirdparty/mujs/*.h",
+
+  "mupdf/thirdparty/zlib/*.c",
+  "mupdf/thirdparty/zlib/*.h",
+
+  "mupdf/thirdparty/jbig2dec/*.c",
+  "mupdf/thirdparty/jbig2dec/*.h",
+
+  "mupdf/thirdparty/openjpeg/src/lib/openjp2/*.c",
+  "mupdf/thirdparty/openjpeg/src/lib/openjp2/*.h",
+
+  "mupdf/thirdparty/leptonica/src/*.c",
+  "mupdf/thirdparty/leptonica/src/*.h",
+
+  "mupdf/thirdparty/tesseract/src/*.cpp",
+  "mupdf/thirdparty/tesseract/src/api/*.cpp",
+  "mupdf/thirdparty/tesseract/src/api/*.h",
+  "mupdf/thirdparty/tesseract/src/arch/*.cpp",
+  "mupdf/thirdparty/tesseract/src/arch/*.h",
+  "mupdf/thirdparty/tesseract/src/ccmain/*.cpp",
+  "mupdf/thirdparty/tesseract/src/ccmain/*.h",
+  "mupdf/thirdparty/tesseract/src/ccstruct/*.cpp",
+  "mupdf/thirdparty/tesseract/src/ccstruct/*.h",
+  "mupdf/thirdparty/tesseract/src/ccutil/*.cpp",
+  "mupdf/thirdparty/tesseract/src/ccutil/*.h",
+  "mupdf/thirdparty/tesseract/src/classify/*.cpp",
+  "mupdf/thirdparty/tesseract/src/classify/*.h",
+  "mupdf/thirdparty/tesseract/src/dict/*.cpp",
+  "mupdf/thirdparty/tesseract/src/dict/*.h",
+  "mupdf/thirdparty/tesseract/src/lstm/*.cpp",
+  "mupdf/thirdparty/tesseract/src/lstm/*.h",
+  "mupdf/thirdparty/tesseract/src/textord/*.cpp",
+  "mupdf/thirdparty/tesseract/src/textord/*.h",
+  "mupdf/thirdparty/tesseract/src/viewer/*.cpp",
+  "mupdf/thirdparty/tesseract/src/viewer/*.h",
+  "mupdf/thirdparty/tesseract/src/wordrec/*.cpp",
+  "mupdf/thirdparty/tesseract/src/wordrec/*.h",
+  "mupdf/thirdparty/tesseract/src/cutil/*.cpp",
+  "mupdf/thirdparty/tesseract/src/cutil/*.h",
+  "mupdf/thirdparty/tesseract/include/tesseract/*.h",
+  "mupdf/scripts/tesseract/*.h",
+  "mupdf/scripts/tesseract/tesseract/*.h",
+
+  "mupdf/thirdparty/extract/src/*.c",
+  "mupdf/thirdparty/extract/src/*.h",
+  "mupdf/thirdparty/extract/include/extract/*.h",
+  "mupdf/thirdparty/extract/src/template.docx",
+  "mupdf/thirdparty/extract/src/template.odt",
+  "mupdf/thirdparty/extract/src/docx_template_build.py",
+
+  "mupdf/thirdparty/zxing-cpp/core/src/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/aztec/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/aztec/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/datamatrix/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/datamatrix/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/maxicode/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/maxicode/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/oned/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/oned/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/pdf417/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/pdf417/*.h",
+  "mupdf/thirdparty/zxing-cpp/core/src/qrcode/*.cpp",
+  "mupdf/thirdparty/zxing-cpp/core/src/qrcode/*.h",
+  "mupdf/scripts/zxing-cpp/*.cpp",
+  "mupdf/scripts/zxing-cpp/*.h",
+
+  "mupdf/thirdparty/zxing-cpp/core/src/libzueci/*.c",
+  "mupdf/thirdparty/zxing-cpp/core/src/libzueci/*.h",
+
+  "mupdf/thirdparty/zint/backend/*.c",
+  "mupdf/thirdparty/zint/backend/*.h",
+  "mupdf/thirdparty/zint/backend/fonts/*.h",
+]
+description = "Custom Rust FFI binding to MuPDF"
+keywords = ["pdf", "mupdf"]
+license = "AGPL-3.0"
+links="mupdf-wrapper"
+
+[features]
+
+[build-dependencies]
+bindgen = { version = "0.72", default-features = false, features = ["runtime"] }
+cc = "1.0.50"
+pkg-config = "0.3"
+regex = "1.11"
+
+[dependencies]
diff --git /dev/null b/mupdf-sys/build.rs
@@ -0,0 +1,385 @@
+use std::{
+  env::{self, current_dir},
+  error::Error,
+  ffi::{OsStr, OsString},
+  io::ErrorKind,
+  path::{Path, PathBuf},
+  process::{Command, exit},
+  fs,
+  result,
+  thread,
+};
+
+const SUPPORTED_TARGETS: &[&str] = &["linux", "openbsd", "netbsd", "macos"];
+const BINDINGS_PATH:     &str    = "src/wrapper.c";
+
+type Result<T> = result::Result<T, Box<dyn Error>>;
+
+fn main() {
+  if let Err(e) = run() {
+    eprintln!("\n{e}");
+    exit(1);
+  }
+}
+
+fn run() -> Result<()> {
+  if fs::read_dir("mupdf").map_or(true, |d| d.count() == 0) {
+    Err(
+      "The `mupdf` directory is empty, did you forget to pull the submodules?\n\
+            Try `git submodule update --init --recursive`",
+    )?
+  }
+
+  let target = Target::from_cargo().map_err(|e| {
+    format!(
+      "Unable to detect target: {e}\n\
+            Cargo is required to build mupdf"
+    )
+  })?;
+
+  if !SUPPORTED_TARGETS.contains(&target.os.as_str()) {
+    Err(format!("Target {:?} is unsupported!", target.os))?
+  }
+
+  let src_dir = current_dir().unwrap().join("mupdf");
+  let out_dir =
+    PathBuf::from(env::var_os("OUT_DIR").ok_or("Missing OUT_DIR environment variable")?);
+
+  let docs = env::var_os("DOCS_RS").is_some();
+  if !docs {
+    let build_dir = out_dir.join("build");
+    let build_dir = build_dir.to_str().ok_or_else(|| {
+      format!("Build dir path is required to be valid UTF-8, got {build_dir:?}")
+    })?;
+
+    if let Err(e) = fs::remove_dir_all(build_dir) {
+      if e.kind() != ErrorKind::NotFound {
+        println!("cargo:warning=Unable to clear {build_dir:?}. This may lead to flaky builds that might not incorporate configurations changes: {e}");
+      }
+    }
+
+    copy_recursive(&src_dir, build_dir.as_ref(), &[".git".as_ref()])?;
+
+    // ========================================================================
+    let mut make = Make::default();
+    make.define("FZ_ENABLE_PDF",  "1");
+    make.define("FZ_ENABLE_SVG",  "1");
+    make.define("FZ_ENABLE_CBZ",  "0");
+    make.define("FZ_ENABLE_IMG",  "0");
+    make.define("FZ_ENABLE_HTML", "0");
+    make.define("FZ_ENABLE_EPUB", "0");
+    make.define("FZ_ENABLE_JS",   "0");
+
+    // NOTE: see https://github.com/ArtifexSoftware/mupdf/blob/master/source/fitz/noto.c
+    make.define("TOFU",        "1");
+    make.define("TOFU_CJK",    "1");
+    make.define("TOFU_NOTO",   "1");
+    make.define("TOFU_SYMBOL", "1");
+    make.define("TOFU_EMOJI",  "1");
+    make.define("TOFU_SIL",    "1");
+
+    make.build(&target, build_dir)?;
+
+    // ========================================================================
+    build_wrapper()
+      .map_err(|e| format!("Unable to compile mupdf wrapper:\n  {e}"))?;
+  }
+
+  generate_bindings(&out_dir.join("bindings.rs"))
+    .map_err(|e| format!("Unable to generate mupdf bindings using bindgen:\n  {e}"))?;
+
+  Ok(())
+}
+
+fn copy_recursive(src: &Path, dst: &Path, ignore: &[&OsStr]) -> Result<()> {
+  if let Err(e) = fs::create_dir(dst) {
+    if e.kind() != ErrorKind::AlreadyExists {
+      Err(format!("Unable to create {dst:?}: {e}"))?;
+    }
+  }
+
+  for entry in fs::read_dir(src)? {
+    let entry = entry?;
+    if ignore.contains(&&*entry.file_name()) {
+      continue;
+    }
+
+    let src_path = entry.path();
+    let dst_path = dst.join(entry.file_name());
+
+    let file_type = entry.file_type()?;
+
+    if file_type.is_symlink() {
+      let link = fs::read_link(&src_path)
+        .map_err(|e| format!("Couldn't read symlink {src_path:?}: {e}"))?;
+      let err = std::os::unix::fs::symlink(&link, &dst_path);
+
+      match err {
+        Ok(_) => continue,
+        Err(e) => println!(
+          "cargo:warning=Couldn't create symlink {dst_path:?} pointing to {link:?}. This might increase the size of your target folder: {e}"
+        ),
+      }
+    }
+
+    if file_type.is_file() || fs::metadata(&src_path)?.is_file() {
+      fs::copy(&src_path, &dst_path)
+        .map_err(|e| format!("Couldn't copy {src_path:?} to {dst_path:?}: {e}"))?;
+      } else {
+        copy_recursive(&src_path, &dst_path, ignore)?;
+    }
+  }
+  Ok(())
+}
+
+fn build_wrapper() -> Result<()> {
+  let mut build = cc::Build::new();
+
+  build
+    .file(BINDINGS_PATH)
+    .include("mupdf/include")
+    .extra_warnings(true)
+    .flag("-Wno-clobbered") // NOTE: remove stupid warnings on src/wrapper.c
+    .debug(true)
+    .try_compile("mupdf-wrapper")?;
+
+  Ok(())
+}
+
+fn generate_bindings(path: &Path) -> Result<()> {
+  let mut builder = bindgen::builder();
+
+  builder = builder
+    .clang_arg("-Imupdf/include")
+    .header(BINDINGS_PATH);
+
+  builder = builder
+    .allowlist_recursively(false)
+    .allowlist_type("wchar_t")
+    .allowlist_type("FILE")
+    .opaque_type("FILE")
+    .allowlist_item("max_align_t")
+    .opaque_type("max_align_t");
+
+  builder = builder
+    .allowlist_item("fz_.*")
+    .allowlist_item("FZ_.*")
+    .allowlist_item("pdf_.*")
+    .allowlist_item("PDF_.*")
+    .allowlist_type("cmap_splay")
+    .allowlist_item("ucdn_.*")
+    .allowlist_item("UCDN_.*")
+    .allowlist_item("Memento_.*")
+    .allowlist_item("mupdf_.*");
+
+  // remove va_list functions as for all of these versions using ... exist
+  builder = builder
+    .blocklist_function("Memento_vasprintf")    // Memento_asprintf
+    .blocklist_function("fz_vthrow")            // fz_throw
+    .blocklist_function("fz_vwarn")             // fz_warn
+    .blocklist_function("fz_vlog_error_printf") // fz_log_error_printf
+    .blocklist_function("fz_append_vprintf")    // fz_append_printf
+    .blocklist_function("fz_write_vprintf")     // fz_write_printf
+    .blocklist_function("fz_vsnprintf")         // fz_snprintf
+    .blocklist_function("fz_format_string");    // mupdf_format_string
+
+  // TODO: make "FZ_VERSION.*" private
+  // build config
+  builder = builder
+    .blocklist_var("FZ_ENABLE_.*")
+    .blocklist_var("FZ_PLOTTERS_.*");
+
+  // internal implementation details, considered private
+  builder = builder
+    .blocklist_item("fz_jmp_buf")
+    .blocklist_function("fz_var_imp")
+    .blocklist_function("fz_push_try")
+    .blocklist_function("fz_do_.*")
+    .blocklist_var("FZ_JMPBUF_ALIGN")
+    .blocklist_type("fz_error_stack_slot")
+    .blocklist_type("fz_error_context")
+    .blocklist_type("fz_warn_context")
+    .blocklist_type("fz_aa_context")
+    .blocklist_type("fz_activity_.*")
+    .blocklist_function("fz_register_activity_logger")
+    .opaque_type("fz_context")
+    .blocklist_type("fz_new_context_imp")
+    .blocklist_type("fz_lock")
+    .blocklist_type("fz_unlock");
+
+  builder = builder
+    .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
+
+  builder
+    .prepend_enum_name(false)
+    .use_core()
+    .generate()?
+    .write_to_file(path)?;
+
+  Ok(())
+}
+
+#[derive(Default)]
+struct Make {
+  build:      cc::Build,
+  make_flags: Vec<OsString>,
+}
+
+impl Make {
+  pub fn define(&mut self, var: &str, val: &str) {
+    self.build.define(var, val);
+  }
+
+  fn make_var(&mut self, var: &str, val: impl AsRef<OsStr>) {
+    let mut flag = OsString::from(var);
+    flag.push("=");
+    flag.push(val);
+    self.make_flags.push(flag);
+  }
+
+  fn make_bool(&mut self, var: &str, val: bool) {
+    self.make_var(var, if val { "yes" } else { "no" });
+  }
+
+  fn cpu_flags(
+    &mut self,
+    target: &Target,
+    feature: &str,
+    flag: &str,
+    make_flag: &str,
+    define: Option<&str>,
+  ) {
+    let contains = target.cpu_features.iter().any(|f| f == feature)
+      && self.build.is_flag_supported(flag).unwrap_or(true);
+    if contains {
+      self.build.flag(flag);
+      self.make_bool(make_flag, true);
+    }
+
+    if let Some(define) = define {
+      self.define(define, if contains { "1" } else { "0" });
+    }
+  }
+
+  pub fn build(mut self, target: &Target, build_dir: &str) -> Result<()> {
+    self.make_var(
+      "build",
+      if target.small_profile() {
+        "small"
+      } else if target.debug_profile() {
+        "debug"
+      } else {
+        "release"
+      },
+    );
+
+    self.make_var("OUT", build_dir);
+
+    self.make_bool("HAVE_X11",  false);
+    self.make_bool("HAVE_GLUT", false);
+    self.make_bool("HAVE_CURL", false);
+
+    self.make_bool("verbose", true);
+
+    // ========================================================================
+    self.make_bool("USE_TESSERACT",  false);
+    self.make_bool("USE_ZXINGCPP",   false);
+    self.make_bool("USE_LIBARCHIVE", false);
+
+    // ========================================================================
+    self.cpu_flags(
+      target,
+      "sse4.1",
+      "-msse4.1",
+      "HAVE_SSE4_1",
+      Some("ARCH_HAS_SSE"),
+    );
+    self.cpu_flags(target, "avx",  "-mavx",  "HAVE_AVX", None);
+    self.cpu_flags(target, "avx2", "-mavx2", "HAVE_AVX2", None);
+    self.cpu_flags(target, "fma",  "-mfma",  "HAVE_FMA", None);
+
+    // NOTE: arm
+    self.cpu_flags(
+      target,
+      "neon",
+      "-mfpu=neon",
+      "HAVE_NEON",
+      Some("ARCH_HAS_NEON"),
+    );
+    // ========================================================================
+    if let Ok(n) = thread::available_parallelism() {
+      self.make_flags.push(format!("-j{n}").into());
+    }
+
+    self.build.warnings(false);
+
+    let compiler = self.build.get_compiler();
+    self.make_var("CC", compiler.path());
+    self.make_var("XCFLAGS", compiler.cflags_env());
+
+    self.build.cpp(true);
+    let compiler = self.build.get_compiler();
+    self.make_var("CXX", compiler.path());
+    self.make_var("XCXXFLAGS", compiler.cflags_env());
+
+    let make = if cfg!(any(
+        target_os = "freebsd",
+        target_os = "openbsd",
+        target_os = "netbsd"
+    )) {
+      "gmake"
+    } else {
+      "make"
+    };
+
+    let status = Command::new(make)
+      .arg("libs")
+      .args(&self.make_flags)
+      .current_dir(build_dir)
+      .status()
+      .map_err(|e| format!("Failed to call {make}: {e}"))?;
+    if !status.success() {
+      Err(match status.code() {
+        Some(code) => format!("{make} invocation failed with status {code}"),
+        None => format!("{make} invocation failed"),
+      })?;
+    }
+
+    println!("cargo:rustc-link-search=native={build_dir}");
+    println!("cargo:rustc-link-lib=static=mupdf");
+    println!("cargo:rustc-link-lib=static=mupdf-third");
+
+    Ok(())
+  }
+}
+
+struct Target {
+  debug:     bool,
+  opt_level: String,
+  os:        String,
+
+  cpu_features: Vec<String>,
+}
+
+impl Target {
+  fn from_cargo() -> Result<Self> {
+    Ok(Self {
+      debug:     env::var_os("DEBUG").is_some_and(|s| s != "0" && s != "false"),
+      opt_level: env::var("OPT_LEVEL")?,
+      os:        env::var("CARGO_CFG_TARGET_OS")?,
+
+      cpu_features: env::var("CARGO_CFG_TARGET_FEATURE")?
+        .split(',')
+        .map(str::to_owned)
+        .collect(),
+    })
+  }
+
+  fn small_profile(&self) -> bool {
+    !self.debug && matches!(&*self.opt_level, "s" | "z")
+  }
+
+  fn debug_profile(&self) -> bool {
+    self.debug && !matches!(&*self.opt_level, "2" | "3")
+  }
+}
diff --git /dev/null b/mupdf-sys/mupdf
@@ -0,0 +1 @@
+Subproject commit 3d40818e511eaf8c49b92d8d3d4dc05fe76ab0f0
diff --git /dev/null b/mupdf-sys/src/lib.rs
@@ -0,0 +1,39 @@
+#![no_std]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(clippy::all)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+#[inline]
+pub unsafe fn fz_new_context(
+  alloc: *const fz_alloc_context,
+  locks: *const fz_locks_context,
+  max_store: usize,
+) -> *mut fz_context {
+  let version = FZ_VERSION.as_ptr() as *const i8;
+  fz_new_context_imp(alloc, locks, max_store, version)
+}
+
+/// Wrapper for [`fz_new_context`].
+pub unsafe fn mupdf_new_context() -> *mut fz_context {
+  use core::ptr;
+
+  let ctx = fz_new_context(
+    ptr::null(),
+    ptr::null(),
+    FZ_STORE_DEFAULT as usize,
+  );
+  if ctx.is_null() { return ctx; }
+
+  // SAFETY: this should really be wrapped with fz_try, but it can only fail if
+  //         ctx does not contain a document handler list (?) or the list is
+  //         already full. seems safe to assume fz_new_documment will handle
+  //         us a sane ctx
+  fz_register_document_handlers(ctx);
+
+  fz_set_warning_callback(ctx, None, ptr::null_mut());
+  fz_set_error_callback(ctx, None, ptr::null_mut());
+  ctx
+}
diff --git /dev/null b/mupdf-sys/src/wrapper.c
@@ -0,0 +1,89 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <mupdf/fitz.h>
+#include <mupdf/pdf.h>
+
+typedef struct {
+  const char*        err_msg;
+  enum fz_error_type type;
+} mupdf_result_t;
+
+fz_document* mupdf_open_doc_from_bytes(fz_context* ctx,
+                                       uint8_t* bytes, size_t size,
+                                       mupdf_result_t* result)
+{
+  fz_document* doc    = NULL;
+  fz_stream*   stream = NULL;
+
+  memset(result, 0, sizeof *result);
+
+  fz_try(ctx)
+  {
+    fz_buffer buf = {0};
+    buf.data = bytes;
+    buf.cap  = size;
+    buf.len  = size;
+
+    stream = fz_open_buffer(ctx, &buf);
+    doc    = (fz_document*)pdf_open_document_with_stream(ctx, stream);
+  }
+  fz_always(ctx)
+  {
+    fz_drop_stream(ctx, stream);
+  }
+  fz_catch(ctx)
+  {
+    result->err_msg = fz_caught_message(ctx);
+    result->type    = fz_caught(ctx);
+    doc = NULL;
+  }
+
+  return doc;
+}
+
+fz_buffer* mupdf_page_to_svg(fz_context* ctx, fz_document* doc,
+                             size_t page_number, mupdf_result_t* result)
+{
+  fz_buffer* buf  = NULL;
+  fz_page*   page = NULL;
+  fz_output* out  = NULL;
+  fz_device* dev  = NULL;
+
+  memset(result, 0, sizeof *result);
+
+  fz_try(ctx)
+  {
+    // TODO: assert page != NULL
+    // TODO: page bound checks when running in debug mode?
+    page = fz_load_page(ctx, doc, page_number);
+
+    fz_rect bbox = fz_bound_page(ctx, page);
+    float width  = bbox.x1 - bbox.x0,
+          height = bbox.y1 - bbox.y0;
+
+    buf = fz_new_buffer(ctx, 1024);            // TODO: assert buf != NULL
+    out = fz_new_output_with_buffer(ctx, buf); // TODO: assert out != NULL
+
+    // TODO: assert dev != NULL
+    dev = fz_new_svg_device(ctx, out, width, height, FZ_SVG_TEXT_AS_PATH, 0);
+
+    fz_run_page(ctx, page, dev, fz_identity, NULL);
+    fz_close_device(ctx, dev);
+    fz_close_output(ctx, out);
+  }
+  fz_always(ctx)
+  {
+    fz_drop_device(ctx, dev);
+    fz_drop_output(ctx, out);
+    fz_drop_page(ctx, page);
+  }
+  fz_catch(ctx)
+  {
+    result->err_msg = fz_caught_message(ctx);
+    result->type    = fz_caught(ctx);
+    buf = NULL;
+  }
+
+  return buf;
+}
diff --git a/src/outro.html b/src/outro.html
@@ -19,7 +19,7 @@ instructions on how to include a given picture in your documents.
 <section id="contributing">
 <h2>contributing</h2>
 <p>
-As of now, this gallery is run by <a href="https://https://www.math.univ-toulouse.fr/~tbrevide/">Thiago Brevidelli</a>. If
+As of now, this gallery is run by <a href="https://www.math.univ-toulouse.fr/~tbrevide/">Thiago Brevidelli</a>. If
 you would like to add your drawings to here please contact
 <a href="&#109;&#97;&#105;&#108;&#116;&#111;:&#116;&#104;&#105;&#97;&#103;&#111;.&#98;&#114;&#101;&#118;&#105;&#100;&#101;&#108;&#108;&#105;_&#103;&#97;&#114;&#99;&#105;&#97;@&#109;&#97;&#116;&#104;.&#117;&#110;&#105;&#118;-&#116;&#111;&#117;&#108;&#111;&#117;&#115;&#101;.&#102;&#114;">&#116;&#104;&#105;&#97;&#103;&#111;.&#98;&#114;&#101;&#118;&#105;&#100;&#101;&#108;&#108;&#105;_&#103;&#97;&#114;&#99;&#105;&#97;@&#109;&#97;&#116;&#104;.&#117;&#110;&#105;&#118;-&#116;&#111;&#117;&#108;&#111;&#117;&#115;&#101;.&#102;&#114;</a>.
 </p>