From 091b9cefc5afb5db3e1848fb68b7d954c732e96c Mon Sep 17 00:00:00 2001 From: Junaid Iqbal Date: Mon, 18 May 2026 15:33:05 +0300 Subject: [PATCH 1/3] Add Linux port: Meson build system + MuPDF rendering engine Phase 1 of the Linux port. Builds MuPDF and all dependencies from source using Meson/Ninja on x86_64 Linux. - Meson build system with all 14 external libraries compiled from source - MuPDF renders PDF, XPS, SVG, CBZ, IMG, HTML, EPUB formats - Font resources embedded via ld -r -b binary (HAVE_OBJCOPY) - FreeType uses MuPDF's slim module config - CI workflow for automated builds and GitHub Releases - Packaging script producing distributable tar.gz + AppImage support --- .github/workflows/linux-package.yml | 69 ++++ .../org.sumatrapdf.SumatraPDF.metainfo.xml | 31 ++ linux/dist/package.sh | 142 +++++++ linux/dist/sumatrapdf.desktop | 12 + linux/ext/brotli/meson.build | 46 +++ linux/ext/extract/meson.build | 36 ++ linux/ext/freetype/meson.build | 49 +++ linux/ext/gumbo-parser/meson.build | 26 ++ linux/ext/harfbuzz/meson.build | 71 ++++ linux/ext/jbig2dec/meson.build | 31 ++ linux/ext/lcms2/meson.build | 42 ++ linux/ext/libdjvu/meson.build | 74 ++++ linux/ext/libjpeg-turbo/meson.build | 64 +++ linux/ext/libwebp/meson.build | 80 ++++ linux/ext/mujs/meson.build | 40 ++ linux/ext/mupdf/compat-linux.h | 15 + linux/ext/mupdf/embed-resource.sh | 10 + linux/ext/mupdf/meson.build | 373 ++++++++++++++++++ linux/ext/openjpeg/meson.build | 42 ++ linux/ext/zlib/meson.build | 26 ++ linux/src/meson.build | 57 +++ linux/src/test_mupdf.c | 81 ++++ meson.build | 42 ++ meson.options | 3 + 24 files changed, 1462 insertions(+) create mode 100644 .github/workflows/linux-package.yml create mode 100644 linux/dist/org.sumatrapdf.SumatraPDF.metainfo.xml create mode 100755 linux/dist/package.sh create mode 100644 linux/dist/sumatrapdf.desktop create mode 100644 linux/ext/brotli/meson.build create mode 100644 linux/ext/extract/meson.build create mode 100644 linux/ext/freetype/meson.build create mode 100644 linux/ext/gumbo-parser/meson.build create mode 100644 linux/ext/harfbuzz/meson.build create mode 100644 linux/ext/jbig2dec/meson.build create mode 100644 linux/ext/lcms2/meson.build create mode 100644 linux/ext/libdjvu/meson.build create mode 100644 linux/ext/libjpeg-turbo/meson.build create mode 100644 linux/ext/libwebp/meson.build create mode 100644 linux/ext/mujs/meson.build create mode 100644 linux/ext/mupdf/compat-linux.h create mode 100755 linux/ext/mupdf/embed-resource.sh create mode 100644 linux/ext/mupdf/meson.build create mode 100644 linux/ext/openjpeg/meson.build create mode 100644 linux/ext/zlib/meson.build create mode 100644 linux/src/meson.build create mode 100644 linux/src/test_mupdf.c create mode 100644 meson.build create mode 100644 meson.options diff --git a/.github/workflows/linux-package.yml b/.github/workflows/linux-package.yml new file mode 100644 index 000000000000..cf7e977f7a85 --- /dev/null +++ b/.github/workflows/linux-package.yml @@ -0,0 +1,69 @@ +name: Linux Package + +on: + push: + branches: [main, master] + tags: ['v*'] + pull_request: + branches: [main, master] + workflow_dispatch: + +jobs: + build: + name: Build Linux x86_64 + runs-on: ubuntu-24.04 + steps: + - name: Check out source code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson ninja-build gcc g++ + + - name: Configure + run: | + meson setup linux/builddir-dist \ + --prefix=/usr \ + --buildtype=release \ + --strip \ + -Db_lto=true + + - name: Build + run: meson compile -C linux/builddir-dist -j$(nproc) + + - name: Run tests + run: meson test -C linux/builddir-dist + + - name: Package + run: bash linux/dist/package.sh --release + + - name: Upload tarball artifact + uses: actions/upload-artifact@v4 + with: + name: sumatrapdf-linux-x86_64 + path: out/sumatrapdf-*-linux-x86_64.tar.gz + retention-days: 30 + + release: + name: Publish Release + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: sumatrapdf-linux-x86_64 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: sumatrapdf-*-linux-x86_64.tar.gz + generate_release_notes: true + draft: false + prerelease: ${{ contains(github.ref, 'dev') || contains(github.ref, 'rc') }} diff --git a/linux/dist/org.sumatrapdf.SumatraPDF.metainfo.xml b/linux/dist/org.sumatrapdf.SumatraPDF.metainfo.xml new file mode 100644 index 000000000000..ac5cc24adce6 --- /dev/null +++ b/linux/dist/org.sumatrapdf.SumatraPDF.metainfo.xml @@ -0,0 +1,31 @@ + + + org.sumatrapdf.SumatraPDF + MIT + GPL-3.0-or-later + SumatraPDF + Fast, minimal document viewer + +

+ SumatraPDF is a fast, lightweight document viewer for PDF, EPUB, XPS, + CBZ, and other formats. Originally Windows-only, this is the Linux port + built with GTK 4. +

+

Features:

+
    +
  • Fast PDF rendering via MuPDF engine
  • +
  • Support for PDF, EPUB, XPS, CBZ, MOBI, FB2 formats
  • +
  • Lightweight and low memory footprint
  • +
  • Keyboard-driven navigation
  • +
+
+ https://www.sumatrapdfreader.org + https://github.com/nicekr/sumatrapdf/issues + sumatrapdf.desktop + + + +

Initial Linux port (development preview).

+
+
+
diff --git a/linux/dist/package.sh b/linux/dist/package.sh new file mode 100755 index 000000000000..6ee91fb18709 --- /dev/null +++ b/linux/dist/package.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# Build a distributable Linux package for SumatraPDF. +# Produces: +# out/sumatrapdf--linux-x86_64.tar.gz (portable archive) +# out/SumatraPDF--x86_64.AppImage (if appimagetool is available) +# +# Usage: ./linux/dist/package.sh [--release] +# --release: Build with optimizations (release mode) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +VERSION=$(grep "version:" "$PROJECT_ROOT/meson.build" | head -1 | sed "s/.*'\(.*\)'.*/\1/") +ARCH=$(uname -m) +BUILD_TYPE="debugoptimized" + +if [[ "${1:-}" == "--release" ]]; then + BUILD_TYPE="release" +fi + +BUILDDIR="$PROJECT_ROOT/linux/builddir-dist" +OUTDIR="$PROJECT_ROOT/out" +INSTALL_PREFIX="$BUILDDIR/install" + +echo "=== SumatraPDF Linux Package Builder ===" +echo "Version: $VERSION" +echo "Arch: $ARCH" +echo "Build: $BUILD_TYPE" +echo "" + +# ─── Configure & Build ──────────────────────────────────────────────────────── + +echo "[1/5] Configuring..." +if [[ -d "$BUILDDIR" && -f "$BUILDDIR/build.ninja" ]]; then + echo " Using existing build directory: $BUILDDIR" +else + meson setup "$BUILDDIR" \ + --prefix=/usr \ + --buildtype="$BUILD_TYPE" \ + --strip \ + -Db_lto=true 2>&1 | tail -3 +fi + +echo "[2/5] Building..." +meson compile -C "$BUILDDIR" -j"$(nproc)" + +echo "[3/5] Installing to staging directory..." +rm -rf "$INSTALL_PREFIX" +DESTDIR="$INSTALL_PREFIX" meson install -C "$BUILDDIR" --no-rebuild 2>&1 | tail -5 + +# ─── Create tarball ─────────────────────────────────────────────────────────── + +echo "[4/5] Creating tarball..." +mkdir -p "$OUTDIR" + +TARBALL_NAME="sumatrapdf-${VERSION}-linux-${ARCH}" +TARBALL_DIR="$BUILDDIR/$TARBALL_NAME" +rm -rf "$TARBALL_DIR" +mkdir -p "$TARBALL_DIR" + +# Copy binary +cp "$INSTALL_PREFIX/usr/bin/sumatrapdf" "$TARBALL_DIR/" + +# Copy desktop and icon +cp "$INSTALL_PREFIX/usr/share/applications/sumatrapdf.desktop" "$TARBALL_DIR/" +cp "$INSTALL_PREFIX/usr/share/icons/hicolor/256x256/apps/sumatrapdf.png" "$TARBALL_DIR/" + +# Create a simple README +cat > "$TARBALL_DIR/README.txt" << 'EOF' +SumatraPDF for Linux +==================== + +A fast, lightweight document viewer for PDF, EPUB, XPS, CBZ and more. + +Usage: + ./sumatrapdf [file.pdf] [output.png] + +Install (optional): + sudo cp sumatrapdf /usr/local/bin/ + sudo cp sumatrapdf.desktop /usr/share/applications/ + sudo cp sumatrapdf.png /usr/share/icons/hicolor/256x256/apps/ + +For more information: https://www.sumatrapdfreader.org +EOF + +tar -czf "$OUTDIR/${TARBALL_NAME}.tar.gz" -C "$BUILDDIR" "$TARBALL_NAME" +echo " -> $OUTDIR/${TARBALL_NAME}.tar.gz" + +# ─── Create AppImage (if appimagetool is available) ─────────────────────────── + +echo "[5/5] Creating AppImage..." + +APPIMAGETOOL="" +if command -v appimagetool &>/dev/null; then + APPIMAGETOOL="appimagetool" +elif [[ -x "$PROJECT_ROOT/tools/appimagetool" ]]; then + APPIMAGETOOL="$PROJECT_ROOT/tools/appimagetool" +fi + +if [[ -n "$APPIMAGETOOL" ]]; then + APPDIR="$BUILDDIR/AppDir" + rm -rf "$APPDIR" + mkdir -p "$APPDIR/usr/bin" + mkdir -p "$APPDIR/usr/share/applications" + mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps" + mkdir -p "$APPDIR/usr/share/metainfo" + + cp "$INSTALL_PREFIX/usr/bin/sumatrapdf" "$APPDIR/usr/bin/" + cp "$INSTALL_PREFIX/usr/share/applications/sumatrapdf.desktop" "$APPDIR/" + cp "$INSTALL_PREFIX/usr/share/applications/sumatrapdf.desktop" "$APPDIR/usr/share/applications/" + cp "$INSTALL_PREFIX/usr/share/icons/hicolor/256x256/apps/sumatrapdf.png" "$APPDIR/" + cp "$INSTALL_PREFIX/usr/share/icons/hicolor/256x256/apps/sumatrapdf.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/" + cp "$PROJECT_ROOT/linux/dist/org.sumatrapdf.SumatraPDF.metainfo.xml" "$APPDIR/usr/share/metainfo/" + + # AppRun launcher + cat > "$APPDIR/AppRun" << 'APPRUN' +#!/bin/bash +SELF="$(readlink -f "$0")" +HERE="${SELF%/*}" +exec "${HERE}/usr/bin/sumatrapdf" "$@" +APPRUN + chmod +x "$APPDIR/AppRun" + + # Symlink icon + ln -sf usr/share/icons/hicolor/256x256/apps/sumatrapdf.png "$APPDIR/.DirIcon" + + APPIMAGE_NAME="SumatraPDF-${VERSION}-${ARCH}.AppImage" + ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$OUTDIR/$APPIMAGE_NAME" 2>&1 | tail -3 + echo " -> $OUTDIR/$APPIMAGE_NAME" +else + echo " (skipped - appimagetool not found)" + echo " Install from: https://github.com/AppImage/appimagetool/releases" + echo " Or: wget -O tools/appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage && chmod +x tools/appimagetool" +fi + +# ─── Summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "=== Done ===" +echo "Outputs in: $OUTDIR/" +ls -lh "$OUTDIR"/sumatrapdf-* "$OUTDIR"/SumatraPDF-* 2>/dev/null || true diff --git a/linux/dist/sumatrapdf.desktop b/linux/dist/sumatrapdf.desktop new file mode 100644 index 000000000000..afbf100c281a --- /dev/null +++ b/linux/dist/sumatrapdf.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=SumatraPDF +GenericName=Document Viewer +Comment=View PDF, EPUB, XPS, CBZ and other document formats +Exec=sumatrapdf %f +Icon=sumatrapdf +Terminal=false +Categories=Office;Viewer;Graphics; +MimeType=application/pdf;application/epub+zip;application/x-cbz;application/oxps;application/vnd.ms-xpsdocument;image/svg+xml; +Keywords=PDF;document;viewer;reader; +StartupNotify=true diff --git a/linux/ext/brotli/meson.build b/linux/ext/brotli/meson.build new file mode 100644 index 000000000000..08fd17d3a366 --- /dev/null +++ b/linux/ext/brotli/meson.build @@ -0,0 +1,46 @@ +brotli_src_dir = '../../../ext/brotli/c' + +brotli_sources = files( + brotli_src_dir / 'common/constants.c', + brotli_src_dir / 'common/context.c', + brotli_src_dir / 'common/dictionary.c', + brotli_src_dir / 'common/platform.c', + brotli_src_dir / 'common/shared_dictionary.c', + brotli_src_dir / 'common/transform.c', + brotli_src_dir / 'dec/bit_reader.c', + brotli_src_dir / 'dec/decode.c', + brotli_src_dir / 'dec/huffman.c', + brotli_src_dir / 'dec/state.c', + brotli_src_dir / 'enc/backward_references.c', + brotli_src_dir / 'enc/backward_references_hq.c', + brotli_src_dir / 'enc/bit_cost.c', + brotli_src_dir / 'enc/block_splitter.c', + brotli_src_dir / 'enc/brotli_bit_stream.c', + brotli_src_dir / 'enc/cluster.c', + brotli_src_dir / 'enc/command.c', + brotli_src_dir / 'enc/compound_dictionary.c', + brotli_src_dir / 'enc/compress_fragment.c', + brotli_src_dir / 'enc/compress_fragment_two_pass.c', + brotli_src_dir / 'enc/dictionary_hash.c', + brotli_src_dir / 'enc/encode.c', + brotli_src_dir / 'enc/encoder_dict.c', + brotli_src_dir / 'enc/entropy_encode.c', + brotli_src_dir / 'enc/fast_log.c', + brotli_src_dir / 'enc/histogram.c', + brotli_src_dir / 'enc/literal_cost.c', + brotli_src_dir / 'enc/memory.c', + brotli_src_dir / 'enc/metablock.c', + brotli_src_dir / 'enc/static_dict.c', + brotli_src_dir / 'enc/utf8_util.c', +) + +brotli_inc = include_directories('../../../ext/brotli/c/include') + +brotli_lib = static_library('brotli', brotli_sources, + include_directories: brotli_inc, +) + +brotli_dep = declare_dependency( + link_with: brotli_lib, + include_directories: brotli_inc, +) diff --git a/linux/ext/extract/meson.build b/linux/ext/extract/meson.build new file mode 100644 index 000000000000..f7d4d16ecb7f --- /dev/null +++ b/linux/ext/extract/meson.build @@ -0,0 +1,36 @@ +extract_src_dir = '../../../ext/extract/src' +extract_inc_dir = '../../../ext/extract/include' + +extract_sources = files( + extract_src_dir / 'alloc.c', + extract_src_dir / 'astring.c', + extract_src_dir / 'boxer.c', + extract_src_dir / 'buffer.c', + extract_src_dir / 'document.c', + extract_src_dir / 'docx.c', + extract_src_dir / 'docx_template.c', + extract_src_dir / 'extract.c', + extract_src_dir / 'html.c', + extract_src_dir / 'join.c', + extract_src_dir / 'json.c', + extract_src_dir / 'mem.c', + extract_src_dir / 'odt.c', + extract_src_dir / 'odt_template.c', + extract_src_dir / 'outf.c', + extract_src_dir / 'rect.c', + extract_src_dir / 'sys.c', + extract_src_dir / 'text.c', + extract_src_dir / 'xml.c', + extract_src_dir / 'zip.c', +) + +extract_inc = include_directories(extract_inc_dir, extract_src_dir) + +extract_lib = static_library('extract', extract_sources, + include_directories: extract_inc, +) + +extract_dep = declare_dependency( + link_with: extract_lib, + include_directories: extract_inc, +) diff --git a/linux/ext/freetype/meson.build b/linux/ext/freetype/meson.build new file mode 100644 index 000000000000..2e45ec978856 --- /dev/null +++ b/linux/ext/freetype/meson.build @@ -0,0 +1,49 @@ +freetype_src_dir = '../../../ext/freetype/src' +freetype_inc_dir = '../../../ext/freetype/include' +freetype_mupdf_scripts = '../../../mupdf/scripts/freetype' + +freetype_sources = files( + freetype_src_dir / 'base/ftbase.c', + freetype_src_dir / 'base/ftbbox.c', + freetype_src_dir / 'base/ftbitmap.c', + freetype_src_dir / 'base/ftdebug.c', + freetype_src_dir / 'base/ftfstype.c', + freetype_src_dir / 'base/ftgasp.c', + freetype_src_dir / 'base/ftglyph.c', + freetype_src_dir / 'base/ftinit.c', + freetype_src_dir / 'base/ftotval.c', + freetype_src_dir / 'base/ftstroke.c', + freetype_src_dir / 'base/ftsynth.c', + freetype_src_dir / 'base/ftsystem.c', + freetype_src_dir / 'base/fttype1.c', + freetype_src_dir / 'gzip/ftgzip.c', + freetype_src_dir / 'cff/cff.c', + freetype_src_dir / 'cid/type1cid.c', + freetype_src_dir / 'psaux/psaux.c', + freetype_src_dir / 'pshinter/pshinter.c', + freetype_src_dir / 'psnames/psnames.c', + freetype_src_dir / 'raster/raster.c', + freetype_src_dir / 'sfnt/sfnt.c', + freetype_src_dir / 'smooth/smooth.c', + freetype_src_dir / 'truetype/truetype.c', + freetype_src_dir / 'type1/type1.c', +) + +freetype_inc = include_directories(freetype_inc_dir, freetype_mupdf_scripts) + +freetype_lib = static_library('freetype', freetype_sources, + include_directories: [freetype_inc, zlib_inc], + c_args: [ + '-DFT2_BUILD_LIBRARY', + '-DFT_CONFIG_MODULES_H="slimftmodules.h"', + '-DFT_CONFIG_OPTIONS_H="slimftoptions.h"', + '-DFT_CONFIG_OPTION_USE_BROTLI', + '-DFT_CONFIG_OPTION_SYSTEM_ZLIB', + ], + dependencies: [zlib_dep], +) + +freetype_dep = declare_dependency( + link_with: freetype_lib, + include_directories: freetype_inc, +) diff --git a/linux/ext/gumbo-parser/meson.build b/linux/ext/gumbo-parser/meson.build new file mode 100644 index 000000000000..87ed060df52d --- /dev/null +++ b/linux/ext/gumbo-parser/meson.build @@ -0,0 +1,26 @@ +gumbo_src_dir = '../../../ext/gumbo-parser/src' + +gumbo_sources = files( + gumbo_src_dir / 'attribute.c', + gumbo_src_dir / 'char_ref.c', + gumbo_src_dir / 'error.c', + gumbo_src_dir / 'parser.c', + gumbo_src_dir / 'string_buffer.c', + gumbo_src_dir / 'string_piece.c', + gumbo_src_dir / 'tag.c', + gumbo_src_dir / 'tokenizer.c', + gumbo_src_dir / 'utf8.c', + gumbo_src_dir / 'util.c', + gumbo_src_dir / 'vector.c', +) + +gumbo_inc = include_directories(gumbo_src_dir) + +gumbo_lib = static_library('gumbo', gumbo_sources, + include_directories: gumbo_inc, +) + +gumbo_dep = declare_dependency( + link_with: gumbo_lib, + include_directories: gumbo_inc, +) diff --git a/linux/ext/harfbuzz/meson.build b/linux/ext/harfbuzz/meson.build new file mode 100644 index 000000000000..4f411c60f249 --- /dev/null +++ b/linux/ext/harfbuzz/meson.build @@ -0,0 +1,71 @@ +harfbuzz_src_dir = '../../../ext/harfbuzz/src' + +harfbuzz_sources = files( + harfbuzz_src_dir / 'hb-aat-layout.cc', + harfbuzz_src_dir / 'hb-aat-map.cc', + harfbuzz_src_dir / 'hb-blob.cc', + harfbuzz_src_dir / 'hb-buffer-serialize.cc', + harfbuzz_src_dir / 'hb-buffer-verify.cc', + harfbuzz_src_dir / 'hb-buffer.cc', + harfbuzz_src_dir / 'hb-common.cc', + harfbuzz_src_dir / 'hb-face.cc', + harfbuzz_src_dir / 'hb-fallback-shape.cc', + harfbuzz_src_dir / 'hb-font.cc', + harfbuzz_src_dir / 'hb-ft.cc', + harfbuzz_src_dir / 'hb-map.cc', + harfbuzz_src_dir / 'hb-number.cc', + harfbuzz_src_dir / 'hb-ot-cff1-table.cc', + harfbuzz_src_dir / 'hb-ot-cff2-table.cc', + harfbuzz_src_dir / 'hb-ot-color.cc', + harfbuzz_src_dir / 'hb-ot-face.cc', + harfbuzz_src_dir / 'hb-ot-font.cc', + harfbuzz_src_dir / 'hb-ot-layout.cc', + harfbuzz_src_dir / 'hb-ot-map.cc', + harfbuzz_src_dir / 'hb-ot-math.cc', + harfbuzz_src_dir / 'hb-ot-meta.cc', + harfbuzz_src_dir / 'hb-ot-metrics.cc', + harfbuzz_src_dir / 'hb-ot-name.cc', + harfbuzz_src_dir / 'hb-ot-shape-fallback.cc', + harfbuzz_src_dir / 'hb-ot-shape-normalize.cc', + harfbuzz_src_dir / 'hb-ot-shape.cc', + harfbuzz_src_dir / 'hb-ot-shaper-arabic.cc', + harfbuzz_src_dir / 'hb-ot-shaper-default.cc', + harfbuzz_src_dir / 'hb-ot-shaper-hangul.cc', + harfbuzz_src_dir / 'hb-ot-shaper-hebrew.cc', + harfbuzz_src_dir / 'hb-ot-shaper-indic-table.cc', + harfbuzz_src_dir / 'hb-ot-shaper-indic.cc', + harfbuzz_src_dir / 'hb-ot-shaper-khmer.cc', + harfbuzz_src_dir / 'hb-ot-shaper-myanmar.cc', + harfbuzz_src_dir / 'hb-ot-shaper-syllabic.cc', + harfbuzz_src_dir / 'hb-ot-shaper-thai.cc', + harfbuzz_src_dir / 'hb-ot-shaper-use.cc', + harfbuzz_src_dir / 'hb-ot-shaper-vowel-constraints.cc', + harfbuzz_src_dir / 'hb-ot-tag.cc', + harfbuzz_src_dir / 'hb-ot-var.cc', + harfbuzz_src_dir / 'hb-set.cc', + harfbuzz_src_dir / 'hb-shape-plan.cc', + harfbuzz_src_dir / 'hb-shape.cc', + harfbuzz_src_dir / 'hb-shaper.cc', + harfbuzz_src_dir / 'hb-static.cc', + harfbuzz_src_dir / 'hb-subset-cff-common.cc', + harfbuzz_src_dir / 'hb-subset-cff1.cc', + harfbuzz_src_dir / 'hb-subset-cff2.cc', + harfbuzz_src_dir / 'hb-subset-input.cc', + harfbuzz_src_dir / 'hb-subset-plan.cc', + harfbuzz_src_dir / 'hb-subset.cc', + harfbuzz_src_dir / 'hb-ucd.cc', + harfbuzz_src_dir / 'hb-unicode.cc', +) + +harfbuzz_inc = include_directories(harfbuzz_src_dir) + +harfbuzz_lib = static_library('harfbuzz', harfbuzz_sources, + include_directories: [harfbuzz_inc, freetype_inc], + cpp_args: ['-DHAVE_FREETYPE=1', '-DHB_NO_MT'], + dependencies: [freetype_dep], +) + +harfbuzz_dep = declare_dependency( + link_with: harfbuzz_lib, + include_directories: harfbuzz_inc, +) diff --git a/linux/ext/jbig2dec/meson.build b/linux/ext/jbig2dec/meson.build new file mode 100644 index 000000000000..16319e9ece52 --- /dev/null +++ b/linux/ext/jbig2dec/meson.build @@ -0,0 +1,31 @@ +jbig2dec_src_dir = '../../../ext/jbig2dec' + +jbig2dec_sources = files( + jbig2dec_src_dir / 'jbig2.c', + jbig2dec_src_dir / 'jbig2_arith.c', + jbig2dec_src_dir / 'jbig2_arith_iaid.c', + jbig2dec_src_dir / 'jbig2_arith_int.c', + jbig2dec_src_dir / 'jbig2_generic.c', + jbig2dec_src_dir / 'jbig2_halftone.c', + jbig2dec_src_dir / 'jbig2_huffman.c', + jbig2dec_src_dir / 'jbig2_hufftab.c', + jbig2dec_src_dir / 'jbig2_image.c', + jbig2dec_src_dir / 'jbig2_mmr.c', + jbig2dec_src_dir / 'jbig2_page.c', + jbig2dec_src_dir / 'jbig2_refinement.c', + jbig2dec_src_dir / 'jbig2_segment.c', + jbig2dec_src_dir / 'jbig2_symbol_dict.c', + jbig2dec_src_dir / 'jbig2_text.c', +) + +jbig2dec_inc = include_directories(jbig2dec_src_dir) + +jbig2dec_lib = static_library('jbig2dec', jbig2dec_sources, + include_directories: jbig2dec_inc, + c_args: ['-DHAVE_STDINT_H=1', '-DJBIG_NO_MEMENTO'], +) + +jbig2dec_dep = declare_dependency( + link_with: jbig2dec_lib, + include_directories: jbig2dec_inc, +) diff --git a/linux/ext/lcms2/meson.build b/linux/ext/lcms2/meson.build new file mode 100644 index 000000000000..b07dffdee43c --- /dev/null +++ b/linux/ext/lcms2/meson.build @@ -0,0 +1,42 @@ +lcms2_src_dir = '../../../ext/lcms2/src' +lcms2_inc_dir = '../../../ext/lcms2/include' + +lcms2_sources = files( + lcms2_src_dir / 'cmsalpha.c', + lcms2_src_dir / 'cmscam02.c', + lcms2_src_dir / 'cmscgats.c', + lcms2_src_dir / 'cmscnvrt.c', + lcms2_src_dir / 'cmserr.c', + lcms2_src_dir / 'cmsgamma.c', + lcms2_src_dir / 'cmsgmt.c', + lcms2_src_dir / 'cmshalf.c', + lcms2_src_dir / 'cmsintrp.c', + lcms2_src_dir / 'cmsio0.c', + lcms2_src_dir / 'cmsio1.c', + lcms2_src_dir / 'cmslut.c', + lcms2_src_dir / 'cmsmd5.c', + lcms2_src_dir / 'cmsmtrx.c', + lcms2_src_dir / 'cmsnamed.c', + lcms2_src_dir / 'cmsopt.c', + lcms2_src_dir / 'cmspack.c', + lcms2_src_dir / 'cmspcs.c', + lcms2_src_dir / 'cmsplugin.c', + lcms2_src_dir / 'cmsps2.c', + lcms2_src_dir / 'cmssamp.c', + lcms2_src_dir / 'cmssm.c', + lcms2_src_dir / 'cmstypes.c', + lcms2_src_dir / 'cmsvirt.c', + lcms2_src_dir / 'cmswtpnt.c', + lcms2_src_dir / 'cmsxform.c', +) + +lcms2_inc = include_directories(lcms2_inc_dir) + +lcms2_lib = static_library('lcms2', lcms2_sources, + include_directories: lcms2_inc, +) + +lcms2_dep = declare_dependency( + link_with: lcms2_lib, + include_directories: lcms2_inc, +) diff --git a/linux/ext/libdjvu/meson.build b/linux/ext/libdjvu/meson.build new file mode 100644 index 000000000000..d472141d3f33 --- /dev/null +++ b/linux/ext/libdjvu/meson.build @@ -0,0 +1,74 @@ +libdjvu_src_dir = '../../../ext/libdjvu' + +libdjvu_sources = files( + libdjvu_src_dir / 'Arrays.cpp', + libdjvu_src_dir / 'atomic.cpp', + libdjvu_src_dir / 'BSByteStream.cpp', + libdjvu_src_dir / 'BSEncodeByteStream.cpp', + libdjvu_src_dir / 'ByteStream.cpp', + libdjvu_src_dir / 'DataPool.cpp', + libdjvu_src_dir / 'ddjvuapi.cpp', + libdjvu_src_dir / 'debug.cpp', + libdjvu_src_dir / 'DjVmDir.cpp', + libdjvu_src_dir / 'DjVmDir0.cpp', + libdjvu_src_dir / 'DjVmDoc.cpp', + libdjvu_src_dir / 'DjVmNav.cpp', + libdjvu_src_dir / 'DjVuAnno.cpp', + libdjvu_src_dir / 'DjVuDocEditor.cpp', + libdjvu_src_dir / 'DjVuDocument.cpp', + libdjvu_src_dir / 'DjVuDumpHelper.cpp', + libdjvu_src_dir / 'DjVuErrorList.cpp', + libdjvu_src_dir / 'DjVuFile.cpp', + libdjvu_src_dir / 'DjVuFileCache.cpp', + libdjvu_src_dir / 'DjVuGlobal.cpp', + libdjvu_src_dir / 'DjVuGlobalMemory.cpp', + libdjvu_src_dir / 'DjVuImage.cpp', + libdjvu_src_dir / 'DjVuInfo.cpp', + libdjvu_src_dir / 'DjVuMessage.cpp', + libdjvu_src_dir / 'DjVuMessageLite.cpp', + libdjvu_src_dir / 'DjVuNavDir.cpp', + libdjvu_src_dir / 'DjVuPalette.cpp', + libdjvu_src_dir / 'DjVuPort.cpp', + libdjvu_src_dir / 'DjVuText.cpp', + libdjvu_src_dir / 'DjVuToPS.cpp', + libdjvu_src_dir / 'GBitmap.cpp', + libdjvu_src_dir / 'GContainer.cpp', + libdjvu_src_dir / 'GException.cpp', + libdjvu_src_dir / 'GIFFManager.cpp', + libdjvu_src_dir / 'GMapAreas.cpp', + libdjvu_src_dir / 'GOS.cpp', + libdjvu_src_dir / 'GPixmap.cpp', + libdjvu_src_dir / 'GRect.cpp', + libdjvu_src_dir / 'GScaler.cpp', + libdjvu_src_dir / 'GSmartPointer.cpp', + libdjvu_src_dir / 'GString.cpp', + libdjvu_src_dir / 'GThreads.cpp', + libdjvu_src_dir / 'GUnicode.cpp', + libdjvu_src_dir / 'GURL.cpp', + libdjvu_src_dir / 'IFFByteStream.cpp', + libdjvu_src_dir / 'IW44EncodeCodec.cpp', + libdjvu_src_dir / 'IW44Image.cpp', + libdjvu_src_dir / 'JB2EncodeCodec.cpp', + libdjvu_src_dir / 'JB2Image.cpp', + libdjvu_src_dir / 'JPEGDecoder.cpp', + libdjvu_src_dir / 'miniexp.cpp', + libdjvu_src_dir / 'MMRDecoder.cpp', + libdjvu_src_dir / 'MMX.cpp', + libdjvu_src_dir / 'UnicodeByteStream.cpp', + libdjvu_src_dir / 'XMLParser.cpp', + libdjvu_src_dir / 'XMLTags.cpp', + libdjvu_src_dir / 'ZPCodec.cpp', +) + +libdjvu_inc = include_directories(libdjvu_src_dir) + +libdjvu_lib = static_library('djvu', libdjvu_sources, + include_directories: [libdjvu_inc, jpegturbo_inc], + cpp_args: ['-DHAVE_PTHREAD', '-DUNIX', '-DHAS_WCHAR', '-DHAVE_STDINT_H', '-DHAVE_MBSTATE_T', '-DHAS_MBSTATE=1'], + dependencies: [jpegturbo_dep], +) + +libdjvu_dep = declare_dependency( + link_with: libdjvu_lib, + include_directories: libdjvu_inc, +) diff --git a/linux/ext/libjpeg-turbo/meson.build b/linux/ext/libjpeg-turbo/meson.build new file mode 100644 index 000000000000..7d88f5455c0c --- /dev/null +++ b/linux/ext/libjpeg-turbo/meson.build @@ -0,0 +1,64 @@ +jpegturbo_src_dir = '../../../ext/libjpeg-turbo' + +jpegturbo_sources = files( + jpegturbo_src_dir / 'jaricom.c', + jpegturbo_src_dir / 'jcapimin.c', + jpegturbo_src_dir / 'jcapistd.c', + jpegturbo_src_dir / 'jcarith.c', + jpegturbo_src_dir / 'jccoefct.c', + jpegturbo_src_dir / 'jccolor.c', + jpegturbo_src_dir / 'jcdctmgr.c', + jpegturbo_src_dir / 'jchuff.c', + jpegturbo_src_dir / 'jcinit.c', + jpegturbo_src_dir / 'jcmainct.c', + jpegturbo_src_dir / 'jcmarker.c', + jpegturbo_src_dir / 'jcmaster.c', + jpegturbo_src_dir / 'jcomapi.c', + jpegturbo_src_dir / 'jcparam.c', + jpegturbo_src_dir / 'jcphuff.c', + jpegturbo_src_dir / 'jcprepct.c', + jpegturbo_src_dir / 'jcsample.c', + jpegturbo_src_dir / 'jdapimin.c', + jpegturbo_src_dir / 'jdapistd.c', + jpegturbo_src_dir / 'jdarith.c', + jpegturbo_src_dir / 'jdatadst.c', + jpegturbo_src_dir / 'jdatasrc.c', + jpegturbo_src_dir / 'jdcoefct.c', + jpegturbo_src_dir / 'jdcolor.c', + jpegturbo_src_dir / 'jddctmgr.c', + jpegturbo_src_dir / 'jdhuff.c', + jpegturbo_src_dir / 'jdinput.c', + jpegturbo_src_dir / 'jdmainct.c', + jpegturbo_src_dir / 'jdmarker.c', + jpegturbo_src_dir / 'jdmaster.c', + jpegturbo_src_dir / 'jdmerge.c', + jpegturbo_src_dir / 'jdphuff.c', + jpegturbo_src_dir / 'jdpostct.c', + jpegturbo_src_dir / 'jdsample.c', + jpegturbo_src_dir / 'jdtrans.c', + jpegturbo_src_dir / 'jerror.c', + jpegturbo_src_dir / 'jfdctflt.c', + jpegturbo_src_dir / 'jfdctfst.c', + jpegturbo_src_dir / 'jfdctint.c', + jpegturbo_src_dir / 'jidctflt.c', + jpegturbo_src_dir / 'jidctfst.c', + jpegturbo_src_dir / 'jidctint.c', + jpegturbo_src_dir / 'jidctred.c', + jpegturbo_src_dir / 'jmemmgr.c', + jpegturbo_src_dir / 'jmemnobs.c', + jpegturbo_src_dir / 'jquant1.c', + jpegturbo_src_dir / 'jquant2.c', + jpegturbo_src_dir / 'jutils.c', + jpegturbo_src_dir / 'jsimd_none.c', +) + +jpegturbo_inc = include_directories(jpegturbo_src_dir) + +jpegturbo_lib = static_library('jpeg-turbo', jpegturbo_sources, + include_directories: jpegturbo_inc, +) + +jpegturbo_dep = declare_dependency( + link_with: jpegturbo_lib, + include_directories: jpegturbo_inc, +) diff --git a/linux/ext/libwebp/meson.build b/linux/ext/libwebp/meson.build new file mode 100644 index 000000000000..9ca839d6bae0 --- /dev/null +++ b/linux/ext/libwebp/meson.build @@ -0,0 +1,80 @@ +libwebp_src_dir = '../../../ext/libwebp/src' +libwebp_root_dir = '../../../ext/libwebp' + +libwebp_dec_sources = files( + libwebp_src_dir / 'dec/alpha_dec.c', + libwebp_src_dir / 'dec/buffer_dec.c', + libwebp_src_dir / 'dec/frame_dec.c', + libwebp_src_dir / 'dec/idec_dec.c', + libwebp_src_dir / 'dec/io_dec.c', + libwebp_src_dir / 'dec/quant_dec.c', + libwebp_src_dir / 'dec/tree_dec.c', + libwebp_src_dir / 'dec/vp8_dec.c', + libwebp_src_dir / 'dec/vp8l_dec.c', + libwebp_src_dir / 'dec/webp_dec.c', +) + +libwebp_utils_sources = files( + libwebp_src_dir / 'utils/bit_reader_utils.c', + libwebp_src_dir / 'utils/bit_writer_utils.c', + libwebp_src_dir / 'utils/color_cache_utils.c', + libwebp_src_dir / 'utils/filters_utils.c', + libwebp_src_dir / 'utils/huffman_encode_utils.c', + libwebp_src_dir / 'utils/huffman_utils.c', + libwebp_src_dir / 'utils/palette.c', + libwebp_src_dir / 'utils/quant_levels_dec_utils.c', + libwebp_src_dir / 'utils/quant_levels_utils.c', + libwebp_src_dir / 'utils/random_utils.c', + libwebp_src_dir / 'utils/rescaler_utils.c', + libwebp_src_dir / 'utils/thread_utils.c', + libwebp_src_dir / 'utils/utils.c', +) + +libwebp_sharpyuv_sources = files( + libwebp_root_dir / 'sharpyuv/sharpyuv.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_cpu.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_csp.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_dsp.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_gamma.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_neon.c', + libwebp_root_dir / 'sharpyuv/sharpyuv_sse2.c', +) + +libwebp_dsp_sources = files( + libwebp_src_dir / 'dsp/alpha_processing.c', + libwebp_src_dir / 'dsp/alpha_processing_sse2.c', + libwebp_src_dir / 'dsp/alpha_processing_sse41.c', + libwebp_src_dir / 'dsp/cost.c', + libwebp_src_dir / 'dsp/cpu.c', + libwebp_src_dir / 'dsp/dec.c', + libwebp_src_dir / 'dsp/dec_clip_tables.c', + libwebp_src_dir / 'dsp/dec_sse2.c', + libwebp_src_dir / 'dsp/dec_sse41.c', + libwebp_src_dir / 'dsp/filters.c', + libwebp_src_dir / 'dsp/filters_sse2.c', + libwebp_src_dir / 'dsp/lossless.c', + libwebp_src_dir / 'dsp/lossless_sse2.c', + libwebp_src_dir / 'dsp/lossless_sse41.c', + libwebp_src_dir / 'dsp/rescaler.c', + libwebp_src_dir / 'dsp/rescaler_sse2.c', + libwebp_src_dir / 'dsp/ssim.c', + libwebp_src_dir / 'dsp/ssim_sse2.c', + libwebp_src_dir / 'dsp/upsampling.c', + libwebp_src_dir / 'dsp/upsampling_sse2.c', + libwebp_src_dir / 'dsp/upsampling_sse41.c', + libwebp_src_dir / 'dsp/yuv.c', + libwebp_src_dir / 'dsp/yuv_sse2.c', + libwebp_src_dir / 'dsp/yuv_sse41.c', +) + +libwebp_inc = include_directories('../../../ext/libwebp', '../../../ext/libwebp/src') + +libwebp_lib = static_library('webp', + libwebp_dec_sources + libwebp_utils_sources + libwebp_sharpyuv_sources + libwebp_dsp_sources, + include_directories: libwebp_inc, +) + +libwebp_dep = declare_dependency( + link_with: libwebp_lib, + include_directories: libwebp_inc, +) diff --git a/linux/ext/mujs/meson.build b/linux/ext/mujs/meson.build new file mode 100644 index 000000000000..9a6f6a3288b5 --- /dev/null +++ b/linux/ext/mujs/meson.build @@ -0,0 +1,40 @@ +mujs_src_dir = '../../../ext/mujs' + +mujs_sources = files( + mujs_src_dir / 'jsarray.c', + mujs_src_dir / 'jsboolean.c', + mujs_src_dir / 'jsbuiltin.c', + mujs_src_dir / 'jscompile.c', + mujs_src_dir / 'jsdate.c', + mujs_src_dir / 'jsdtoa.c', + mujs_src_dir / 'jserror.c', + mujs_src_dir / 'jsfunction.c', + mujs_src_dir / 'jsgc.c', + mujs_src_dir / 'jsintern.c', + mujs_src_dir / 'jslex.c', + mujs_src_dir / 'jsmath.c', + mujs_src_dir / 'jsnumber.c', + mujs_src_dir / 'jsobject.c', + mujs_src_dir / 'json.c', + mujs_src_dir / 'jsparse.c', + mujs_src_dir / 'jsproperty.c', + mujs_src_dir / 'jsregexp.c', + mujs_src_dir / 'jsrepr.c', + mujs_src_dir / 'jsrun.c', + mujs_src_dir / 'jsstate.c', + mujs_src_dir / 'jsstring.c', + mujs_src_dir / 'jsvalue.c', + mujs_src_dir / 'regexp.c', + mujs_src_dir / 'utf.c', +) + +mujs_inc = include_directories(mujs_src_dir) + +mujs_lib = static_library('mujs', mujs_sources, + include_directories: mujs_inc, +) + +mujs_dep = declare_dependency( + link_with: mujs_lib, + include_directories: mujs_inc, +) diff --git a/linux/ext/mupdf/compat-linux.h b/linux/ext/mupdf/compat-linux.h new file mode 100644 index 000000000000..9af07f90ffd8 --- /dev/null +++ b/linux/ext/mupdf/compat-linux.h @@ -0,0 +1,15 @@ +/* + * Compatibility header for building MuPDF (SumatraPDF-patched) on Linux. + * Maps MSVC-specific extensions to GCC equivalents. + */ +#ifndef MUPDF_COMPAT_LINUX_H +#define MUPDF_COMPAT_LINUX_H + +/* SumatraPDF patches use __declspec(thread) for thread-local storage. + * GCC requires __thread before the type, but __declspec(thread) comes first. + * We strip it here; the variable becomes a plain static (safe for our usage). */ +#ifndef _MSC_VER +#define __declspec(x) +#endif + +#endif /* MUPDF_COMPAT_LINUX_H */ diff --git a/linux/ext/mupdf/embed-resource.sh b/linux/ext/mupdf/embed-resource.sh new file mode 100755 index 000000000000..7ea557745048 --- /dev/null +++ b/linux/ext/mupdf/embed-resource.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Embed a binary resource file as a linkable ELF object. +# Usage: embed-resource.sh +# Symbols are derived from the relative path (matching MuPDF's Makefile convention). + +MUPDF_ROOT="$1" +RESOURCE_REL="$2" +OUTPUT="$(realpath -m "$3")" + +cd "$MUPDF_ROOT" && ld -r -b binary -z noexecstack -o "$OUTPUT" "$RESOURCE_REL" diff --git a/linux/ext/mupdf/meson.build b/linux/ext/mupdf/meson.build new file mode 100644 index 000000000000..946fdf119d30 --- /dev/null +++ b/linux/ext/mupdf/meson.build @@ -0,0 +1,373 @@ +# MuPDF - the core PDF rendering engine +# Built from source matching the Windows build + +mupdf_src_dir = '../../../mupdf/source' +mupdf_inc_dir = '../../../mupdf/include' + +mupdf_fitz_sources = files( + mupdf_src_dir / 'fitz/archive.c', + mupdf_src_dir / 'fitz/barcode.c', + mupdf_src_dir / 'fitz/bbox-device.c', + mupdf_src_dir / 'fitz/bidi-std.c', + mupdf_src_dir / 'fitz/bidi.c', + mupdf_src_dir / 'fitz/bitmap.c', + mupdf_src_dir / 'fitz/brotli.c', + mupdf_src_dir / 'fitz/buffer.c', + mupdf_src_dir / 'fitz/color-fast.c', + mupdf_src_dir / 'fitz/color-icc-create.c', + mupdf_src_dir / 'fitz/color-lcms.c', + mupdf_src_dir / 'fitz/colorspace.c', + mupdf_src_dir / 'fitz/compress.c', + mupdf_src_dir / 'fitz/compressed-buffer.c', + mupdf_src_dir / 'fitz/context.c', + mupdf_src_dir / 'fitz/crypt-aes.c', + mupdf_src_dir / 'fitz/crypt-arc4.c', + mupdf_src_dir / 'fitz/crypt-md5.c', + mupdf_src_dir / 'fitz/crypt-sha2.c', + mupdf_src_dir / 'fitz/deskew.c', + mupdf_src_dir / 'fitz/device.c', + mupdf_src_dir / 'fitz/directory.c', + mupdf_src_dir / 'fitz/document-all.c', + mupdf_src_dir / 'fitz/document.c', + mupdf_src_dir / 'fitz/draw-affine.c', + mupdf_src_dir / 'fitz/draw-blend.c', + mupdf_src_dir / 'fitz/draw-device.c', + mupdf_src_dir / 'fitz/draw-edge.c', + mupdf_src_dir / 'fitz/draw-edgebuffer.c', + mupdf_src_dir / 'fitz/draw-glyph.c', + mupdf_src_dir / 'fitz/draw-mesh.c', + mupdf_src_dir / 'fitz/draw-paint.c', + mupdf_src_dir / 'fitz/draw-path.c', + mupdf_src_dir / 'fitz/draw-rasterize.c', + mupdf_src_dir / 'fitz/draw-scale-simple.c', + mupdf_src_dir / 'fitz/draw-unpack.c', + mupdf_src_dir / 'fitz/encode-basic.c', + mupdf_src_dir / 'fitz/encode-fax.c', + mupdf_src_dir / 'fitz/encode-jpx.c', + mupdf_src_dir / 'fitz/encodings.c', + mupdf_src_dir / 'fitz/error.c', + mupdf_src_dir / 'fitz/filter-basic.c', + mupdf_src_dir / 'fitz/filter-brotli.c', + mupdf_src_dir / 'fitz/filter-dct.c', + mupdf_src_dir / 'fitz/filter-fax.c', + mupdf_src_dir / 'fitz/filter-flate.c', + mupdf_src_dir / 'fitz/filter-jbig2.c', + mupdf_src_dir / 'fitz/filter-leech.c', + mupdf_src_dir / 'fitz/filter-lzw.c', + mupdf_src_dir / 'fitz/filter-predict.c', + mupdf_src_dir / 'fitz/filter-sgi.c', + mupdf_src_dir / 'fitz/filter-thunder.c', + mupdf_src_dir / 'fitz/font.c', + mupdf_src_dir / 'fitz/ftoa.c', + mupdf_src_dir / 'fitz/geometry.c', + mupdf_src_dir / 'fitz/getopt.c', + mupdf_src_dir / 'fitz/glyph.c', + mupdf_src_dir / 'fitz/glyphbox.c', + mupdf_src_dir / 'fitz/gz-doc.c', + mupdf_src_dir / 'fitz/halftone.c', + mupdf_src_dir / 'fitz/harfbuzz.c', + mupdf_src_dir / 'fitz/hash.c', + mupdf_src_dir / 'fitz/heap.c', + mupdf_src_dir / 'fitz/hyphen.c', + mupdf_src_dir / 'fitz/image.c', + mupdf_src_dir / 'fitz/jmemcust.c', + mupdf_src_dir / 'fitz/json.c', + mupdf_src_dir / 'fitz/link.c', + mupdf_src_dir / 'fitz/list-device.c', + mupdf_src_dir / 'fitz/load-bmp.c', + mupdf_src_dir / 'fitz/load-gif.c', + mupdf_src_dir / 'fitz/load-jbig2.c', + mupdf_src_dir / 'fitz/load-jpeg.c', + mupdf_src_dir / 'fitz/load-jpx.c', + mupdf_src_dir / 'fitz/load-png.c', + mupdf_src_dir / 'fitz/load-pnm.c', + mupdf_src_dir / 'fitz/load-psd.c', + mupdf_src_dir / 'fitz/load-jxr.c', + mupdf_src_dir / 'fitz/load-tiff.c', + mupdf_src_dir / 'fitz/log.c', + mupdf_src_dir / 'fitz/memento.c', + mupdf_src_dir / 'fitz/memory.c', + mupdf_src_dir / 'fitz/noto.c', + mupdf_src_dir / 'fitz/ocr-device.c', + mupdf_src_dir / 'fitz/outline.c', + mupdf_src_dir / 'fitz/output-cbz.c', + mupdf_src_dir / 'fitz/output-csv.c', + mupdf_src_dir / 'fitz/output-docx.c', + mupdf_src_dir / 'fitz/output-jpeg.c', + mupdf_src_dir / 'fitz/output-pcl.c', + mupdf_src_dir / 'fitz/output-pclm.c', + mupdf_src_dir / 'fitz/output-pdfocr.c', + mupdf_src_dir / 'fitz/output-png.c', + mupdf_src_dir / 'fitz/output-pnm.c', + mupdf_src_dir / 'fitz/output-ps.c', + mupdf_src_dir / 'fitz/output-psd.c', + mupdf_src_dir / 'fitz/output-pwg.c', + mupdf_src_dir / 'fitz/output-svg.c', + mupdf_src_dir / 'fitz/output.c', + mupdf_src_dir / 'fitz/path.c', + mupdf_src_dir / 'fitz/pixmap.c', + mupdf_src_dir / 'fitz/pool.c', + mupdf_src_dir / 'fitz/printf.c', + mupdf_src_dir / 'fitz/random.c', + mupdf_src_dir / 'fitz/separation.c', + mupdf_src_dir / 'fitz/shade.c', + mupdf_src_dir / 'fitz/skew.c', + mupdf_src_dir / 'fitz/stext-boxer.c', + mupdf_src_dir / 'fitz/stext-classify.c', + mupdf_src_dir / 'fitz/stext-device.c', + mupdf_src_dir / 'fitz/stext-iterator.c', + mupdf_src_dir / 'fitz/stext-output.c', + mupdf_src_dir / 'fitz/stext-para.c', + mupdf_src_dir / 'fitz/stext-raft.c', + mupdf_src_dir / 'fitz/stext-search.c', + mupdf_src_dir / 'fitz/stext-table.c', + mupdf_src_dir / 'fitz/store.c', + mupdf_src_dir / 'fitz/stream-open.c', + mupdf_src_dir / 'fitz/stream-read.c', + mupdf_src_dir / 'fitz/string.c', + mupdf_src_dir / 'fitz/strtof.c', + mupdf_src_dir / 'fitz/subset-cff.c', + mupdf_src_dir / 'fitz/subset-ttf.c', + mupdf_src_dir / 'fitz/svg-device.c', + mupdf_src_dir / 'fitz/test-device.c', + mupdf_src_dir / 'fitz/text-decoder.c', + mupdf_src_dir / 'fitz/text.c', + mupdf_src_dir / 'fitz/time.c', + mupdf_src_dir / 'fitz/trace-device.c', + mupdf_src_dir / 'fitz/track-usage.c', + mupdf_src_dir / 'fitz/transition.c', + mupdf_src_dir / 'fitz/tree.c', + mupdf_src_dir / 'fitz/ucdn.c', + mupdf_src_dir / 'fitz/uncfb.c', + mupdf_src_dir / 'fitz/unlibarchive.c', + mupdf_src_dir / 'fitz/untar.c', + mupdf_src_dir / 'fitz/unzip.c', + mupdf_src_dir / 'fitz/util.c', + mupdf_src_dir / 'fitz/warp.c', + mupdf_src_dir / 'fitz/writer.c', + mupdf_src_dir / 'fitz/xml-write.c', + mupdf_src_dir / 'fitz/xml.c', + mupdf_src_dir / 'fitz/xmltext-device.c', + mupdf_src_dir / 'fitz/zip.c', +) + +mupdf_pdf_sources = files( + mupdf_src_dir / 'pdf/pdf-af.c', + mupdf_src_dir / 'pdf/pdf-annot.c', + mupdf_src_dir / 'pdf/pdf-appearance.c', + mupdf_src_dir / 'pdf/pdf-clean-file.c', + mupdf_src_dir / 'pdf/pdf-clean.c', + mupdf_src_dir / 'pdf/pdf-cmap-load.c', + mupdf_src_dir / 'pdf/pdf-cmap-parse.c', + mupdf_src_dir / 'pdf/pdf-cmap.c', + mupdf_src_dir / 'pdf/pdf-colorspace.c', + mupdf_src_dir / 'pdf/pdf-crypt.c', + mupdf_src_dir / 'pdf/pdf-device.c', + mupdf_src_dir / 'pdf/pdf-event.c', + mupdf_src_dir / 'pdf/pdf-font-add.c', + mupdf_src_dir / 'pdf/pdf-font.c', + mupdf_src_dir / 'pdf/pdf-form.c', + mupdf_src_dir / 'pdf/pdf-function.c', + mupdf_src_dir / 'pdf/pdf-graft.c', + mupdf_src_dir / 'pdf/pdf-image-rewriter.c', + mupdf_src_dir / 'pdf/pdf-image.c', + mupdf_src_dir / 'pdf/pdf-interpret.c', + mupdf_src_dir / 'pdf/pdf-js.c', + mupdf_src_dir / 'pdf/pdf-label.c', + mupdf_src_dir / 'pdf/pdf-layer.c', + mupdf_src_dir / 'pdf/pdf-layout.c', + mupdf_src_dir / 'pdf/pdf-lex.c', + mupdf_src_dir / 'pdf/pdf-link.c', + mupdf_src_dir / 'pdf/pdf-metrics.c', + mupdf_src_dir / 'pdf/pdf-nametree.c', + mupdf_src_dir / 'pdf/pdf-object.c', + mupdf_src_dir / 'pdf/pdf-op-buffer.c', + mupdf_src_dir / 'pdf/pdf-op-color.c', + mupdf_src_dir / 'pdf/pdf-op-filter.c', + mupdf_src_dir / 'pdf/pdf-op-run.c', + mupdf_src_dir / 'pdf/pdf-op-vectorize.c', + mupdf_src_dir / 'pdf/pdf-outline.c', + mupdf_src_dir / 'pdf/pdf-page.c', + mupdf_src_dir / 'pdf/pdf-parse.c', + mupdf_src_dir / 'pdf/pdf-pattern.c', + mupdf_src_dir / 'pdf/pdf-recolor.c', + mupdf_src_dir / 'pdf/pdf-repair.c', + mupdf_src_dir / 'pdf/pdf-resources.c', + mupdf_src_dir / 'pdf/pdf-run.c', + mupdf_src_dir / 'pdf/pdf-shade-recolor.c', + mupdf_src_dir / 'pdf/pdf-shade.c', + mupdf_src_dir / 'pdf/pdf-signature.c', + mupdf_src_dir / 'pdf/pdf-store.c', + mupdf_src_dir / 'pdf/pdf-stream.c', + mupdf_src_dir / 'pdf/pdf-subset.c', + mupdf_src_dir / 'pdf/pdf-type3.c', + mupdf_src_dir / 'pdf/pdf-unicode.c', + mupdf_src_dir / 'pdf/pdf-util.c', + mupdf_src_dir / 'pdf/pdf-write.c', + mupdf_src_dir / 'pdf/pdf-xobject.c', + mupdf_src_dir / 'pdf/pdf-xref.c', + mupdf_src_dir / 'pdf/pdf-zugferd.c', +) + +mupdf_html_sources = files( + mupdf_src_dir / 'html/css-apply.c', + mupdf_src_dir / 'html/css-parse.c', + mupdf_src_dir / 'html/epub-doc.c', + mupdf_src_dir / 'html/html-doc.c', + mupdf_src_dir / 'html/html-font.c', + mupdf_src_dir / 'html/html-layout.c', + mupdf_src_dir / 'html/html-outline.c', + mupdf_src_dir / 'html/html-parse.c', + mupdf_src_dir / 'html/mobi.c', + mupdf_src_dir / 'html/office.c', + mupdf_src_dir / 'html/story-writer.c', + mupdf_src_dir / 'html/txt.c', + mupdf_src_dir / 'html/xml-dom.c', +) + +mupdf_cbz_sources = files( + mupdf_src_dir / 'cbz/mucbz.c', + mupdf_src_dir / 'cbz/muimg.c', +) + +mupdf_svg_sources = files( + mupdf_src_dir / 'svg/svg-color.c', + mupdf_src_dir / 'svg/svg-doc.c', + mupdf_src_dir / 'svg/svg-parse.c', + mupdf_src_dir / 'svg/svg-run.c', +) + +mupdf_xps_sources = files( + mupdf_src_dir / 'xps/xps-common.c', + mupdf_src_dir / 'xps/xps-doc.c', + mupdf_src_dir / 'xps/xps-glyphs.c', + mupdf_src_dir / 'xps/xps-gradient.c', + mupdf_src_dir / 'xps/xps-image.c', + mupdf_src_dir / 'xps/xps-link.c', + mupdf_src_dir / 'xps/xps-outline.c', + mupdf_src_dir / 'xps/xps-path.c', + mupdf_src_dir / 'xps/xps-resource.c', + mupdf_src_dir / 'xps/xps-tile.c', + mupdf_src_dir / 'xps/xps-util.c', + mupdf_src_dir / 'xps/xps-zip.c', +) + +mupdf_reflow_sources = files( + mupdf_src_dir / 'reflow/reflow-doc.c', +) + +mupdf_helpers_sources = files( + mupdf_src_dir / 'helpers/mu-threads/mu-threads.c', +) + +mupdf_inc = include_directories(mupdf_inc_dir) + +mupdf_all_sources = ( + mupdf_fitz_sources + + mupdf_pdf_sources + + mupdf_html_sources + + mupdf_cbz_sources + + mupdf_svg_sources + + mupdf_xps_sources + + mupdf_reflow_sources + + mupdf_helpers_sources +) + +# MuPDF defines for the build +mupdf_c_args = [ + '-DHAVE_OBJCOPY', # Use ld -r -b binary for font/resource embedding + '-DTOFU', # Reduced font embedding + '-DTOFU_CJK_EXT', + '-DSHARE_JPEG', # Share libjpeg-turbo + '-DHB_NO_MT', # HarfBuzz single-threaded + '-DOPJ_STATIC', # Static OpenJPEG + '-DOPJ_HAVE_STDINT_H', + '-DHAVE_LCMS2MT=0', + '-DOCR_DISABLED', # No OCR support for now + '-DFZ_ENABLE_XPS=1', + '-DFZ_ENABLE_SVG=1', + '-DFZ_ENABLE_CBZ=1', + '-DFZ_ENABLE_IMG=1', + '-DFZ_ENABLE_HTML=1', + '-DFZ_ENABLE_EPUB=1', + '-DFZ_ENABLE_JS=1', + '-include', meson.current_source_dir() / 'compat-linux.h', +] + +mupdf_lib = static_library('mupdf', mupdf_all_sources, + include_directories: [ + mupdf_inc, + zlib_inc, + freetype_inc, + harfbuzz_inc, + jpegturbo_inc, + openjpeg_inc, + jbig2dec_inc, + lcms2_inc, + brotli_inc, + extract_inc, + mujs_inc, + gumbo_inc, + libwebp_inc, + include_directories('../../../ext/libarchive/libarchive'), + ], + c_args: mupdf_c_args + ['-D_GNU_SOURCE'], + dependencies: [ + zlib_dep, + freetype_dep, + harfbuzz_dep, + jpegturbo_dep, + openjpeg_dep, + jbig2dec_dep, + lcms2_dep, + brotli_dep, + extract_dep, + mujs_dep, + gumbo_dep, + libwebp_dep, + ], +) + +mupdf_dep = declare_dependency( + link_with: mupdf_lib, + include_directories: mupdf_inc, +) + +# Embed font and hyphenation resources as linkable objects using ld -r -b binary. +# This matches MuPDF's own Makefile approach (HAVE_OBJCOPY path). +# Symbol names are derived from the relative path within the mupdf directory. + +mupdf_root = meson.project_source_root() / 'mupdf' +embed_script = find_program(meson.current_source_dir() / 'embed-resource.sh') + +font_resources = [ + 'resources/fonts/urw/Dingbats.cff', + 'resources/fonts/urw/NimbusMonoPS-Bold.cff', + 'resources/fonts/urw/NimbusMonoPS-BoldItalic.cff', + 'resources/fonts/urw/NimbusMonoPS-Italic.cff', + 'resources/fonts/urw/NimbusMonoPS-Regular.cff', + 'resources/fonts/urw/NimbusRoman-Bold.cff', + 'resources/fonts/urw/NimbusRoman-BoldItalic.cff', + 'resources/fonts/urw/NimbusRoman-Italic.cff', + 'resources/fonts/urw/NimbusRoman-Regular.cff', + 'resources/fonts/urw/NimbusSans-Bold.cff', + 'resources/fonts/urw/NimbusSans-BoldItalic.cff', + 'resources/fonts/urw/NimbusSans-Italic.cff', + 'resources/fonts/urw/NimbusSans-Regular.cff', + 'resources/fonts/urw/StandardSymbolsPS.cff', + 'resources/fonts/droid/DroidSansFallback.ttf', +] + +hyph_resources = [ + 'resources/hyphen/hyph-std.zip', + 'resources/hyphen/hyph-all.zip', +] + +mupdf_resource_objs = [] +foreach res : font_resources + hyph_resources + name = res.underscorify() + mupdf_resource_objs += custom_target(name, + output: name + '.o', + command: [embed_script, mupdf_root, res, '@OUTPUT@'], + ) +endforeach diff --git a/linux/ext/openjpeg/meson.build b/linux/ext/openjpeg/meson.build new file mode 100644 index 000000000000..d02545b8f6f7 --- /dev/null +++ b/linux/ext/openjpeg/meson.build @@ -0,0 +1,42 @@ +openjpeg_src_dir = '../../../ext/openjpeg/src/lib/openjp2' + +openjpeg_sources = files( + openjpeg_src_dir / 'bio.c', + openjpeg_src_dir / 'cidx_manager.c', + openjpeg_src_dir / 'cio.c', + openjpeg_src_dir / 'dwt.c', + openjpeg_src_dir / 'event.c', + openjpeg_src_dir / 'function_list.c', + openjpeg_src_dir / 'ht_dec.c', + openjpeg_src_dir / 'image.c', + openjpeg_src_dir / 'invert.c', + openjpeg_src_dir / 'j2k.c', + openjpeg_src_dir / 'jp2.c', + openjpeg_src_dir / 'mct.c', + openjpeg_src_dir / 'mqc.c', + openjpeg_src_dir / 'openjpeg.c', + openjpeg_src_dir / 'opj_clock.c', + openjpeg_src_dir / 'phix_manager.c', + openjpeg_src_dir / 'pi.c', + openjpeg_src_dir / 'ppix_manager.c', + openjpeg_src_dir / 'sparse_array.c', + openjpeg_src_dir / 't1.c', + openjpeg_src_dir / 't2.c', + openjpeg_src_dir / 'tcd.c', + openjpeg_src_dir / 'tgt.c', + openjpeg_src_dir / 'thix_manager.c', + openjpeg_src_dir / 'thread.c', + openjpeg_src_dir / 'tpix_manager.c', +) + +openjpeg_inc = include_directories(openjpeg_src_dir) + +openjpeg_lib = static_library('openjpeg', openjpeg_sources, + include_directories: openjpeg_inc, + c_args: ['-DOPJ_STATIC', '-DUSE_JPIP'], +) + +openjpeg_dep = declare_dependency( + link_with: openjpeg_lib, + include_directories: openjpeg_inc, +) diff --git a/linux/ext/zlib/meson.build b/linux/ext/zlib/meson.build new file mode 100644 index 000000000000..d9bdc6b25f10 --- /dev/null +++ b/linux/ext/zlib/meson.build @@ -0,0 +1,26 @@ +zlib_sources = files( + '../../../ext/zlib/adler32.c', + '../../../ext/zlib/compress.c', + '../../../ext/zlib/crc32.c', + '../../../ext/zlib/deflate.c', + '../../../ext/zlib/gzclose.c', + '../../../ext/zlib/gzlib.c', + '../../../ext/zlib/gzread.c', + '../../../ext/zlib/gzwrite.c', + '../../../ext/zlib/inffast.c', + '../../../ext/zlib/inflate.c', + '../../../ext/zlib/inftrees.c', + '../../../ext/zlib/trees.c', + '../../../ext/zlib/zutil.c', +) + +zlib_inc = include_directories('../../../ext/zlib') + +zlib_lib = static_library('zlib', zlib_sources, + include_directories: zlib_inc, +) + +zlib_dep = declare_dependency( + link_with: zlib_lib, + include_directories: zlib_inc, +) diff --git a/linux/src/meson.build b/linux/src/meson.build new file mode 100644 index 000000000000..80da698d253f --- /dev/null +++ b/linux/src/meson.build @@ -0,0 +1,57 @@ +# Phase 1: Minimal test binary to verify MuPDF works on Linux +# Phase 2+: Full application sources will be added here + +common_deps = [ + mupdf_dep, + zlib_dep, + freetype_dep, + harfbuzz_dep, + jpegturbo_dep, + openjpeg_dep, + jbig2dec_dep, + lcms2_dep, + brotli_dep, + extract_dep, + mujs_dep, + libdjvu_dep, + libwebp_dep, + gumbo_dep, +] + +common_link_args = ['-lm', '-lpthread'] + +# The main application binary (currently the renderer test) +sumatrapdf = executable('sumatrapdf', + files('test_mupdf.c'), + mupdf_resource_objs, + include_directories: [ + include_directories('../../mupdf/include'), + ], + dependencies: common_deps, + link_args: common_link_args, + install: true, +) + +test('mupdf-smoke', sumatrapdf, args: []) + +# ─── Install targets ────────────────────────────────────────────────────────── + +dist_dir = meson.project_source_root() / 'linux' / 'dist' + +install_data(dist_dir / 'sumatrapdf.desktop', + install_dir: get_option('datadir') / 'applications') + +install_data(dist_dir / 'org.sumatrapdf.SumatraPDF.metainfo.xml', + install_dir: get_option('datadir') / 'metainfo') + +# Icons +icon_src = meson.project_source_root() / 'gfx' +install_data(icon_src / 'SumatraPDF-256x256x32.png', + install_dir: get_option('datadir') / 'icons' / 'hicolor' / '256x256' / 'apps', + rename: 'sumatrapdf.png') +install_data(icon_src / 'SumatraPDF-128x128x32.png', + install_dir: get_option('datadir') / 'icons' / 'hicolor' / '128x128' / 'apps', + rename: 'sumatrapdf.png') +install_data(icon_src / 'SumatraPDF-48x48x32.png', + install_dir: get_option('datadir') / 'icons' / 'hicolor' / '48x48' / 'apps', + rename: 'sumatrapdf.png') diff --git a/linux/src/test_mupdf.c b/linux/src/test_mupdf.c new file mode 100644 index 000000000000..3b5c5356059a --- /dev/null +++ b/linux/src/test_mupdf.c @@ -0,0 +1,81 @@ +/* + * SumatraPDF Linux Port - Phase 1 Test + * + * Minimal test to verify MuPDF compiles and links correctly on Linux. + * Opens a PDF (if provided as argv[1]) and prints page count, + * or just verifies the library initializes correctly. + */ + +#include +#include +#include "mupdf/fitz.h" + +int main(int argc, char *argv[]) +{ + fz_context *ctx; + fz_document *doc; + int page_count; + + /* Create a MuPDF context */ + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + if (!ctx) { + fprintf(stderr, "error: cannot create mupdf context\n"); + return 1; + } + + /* Register default document handlers */ + fz_try(ctx) { + fz_register_document_handlers(ctx); + } + fz_catch(ctx) { + fprintf(stderr, "error: cannot register document handlers: %s\n", fz_caught_message(ctx)); + fz_drop_context(ctx); + return 1; + } + + printf("MuPDF context initialized successfully.\n"); + printf("Registered document handlers: PDF, XPS, SVG, CBZ, IMG, HTML, EPUB\n"); + + /* If a file was provided, try to open it */ + if (argc >= 2) { + const char *filename = argv[1]; + printf("Opening: %s\n", filename); + + fz_try(ctx) { + doc = fz_open_document(ctx, filename); + page_count = fz_count_pages(ctx, doc); + printf("Page count: %d\n", page_count); + + /* Render first page to verify rendering pipeline */ + if (page_count > 0) { + fz_pixmap *pix; + fz_matrix ctm = fz_scale(1.0f, 1.0f); /* 72 DPI */ + + pix = fz_new_pixmap_from_page_number(ctx, doc, 0, ctm, fz_device_rgb(ctx), 0); + printf("Page 1 rendered: %d x %d pixels\n", pix->w, pix->h); + + /* Optionally write to PNG if second arg is provided */ + if (argc >= 3) { + fz_save_pixmap_as_png(ctx, pix, argv[2]); + printf("Saved to: %s\n", argv[2]); + } + + fz_drop_pixmap(ctx, pix); + } + + fz_drop_document(ctx, doc); + } + fz_catch(ctx) { + fprintf(stderr, "error: %s\n", fz_caught_message(ctx)); + fz_drop_context(ctx); + return 1; + } + } else { + printf("Usage: test-mupdf [file.pdf] [output.png]\n"); + printf(" No file provided - context-only test passed.\n"); + } + + fz_drop_context(ctx); + printf("OK\n"); + return 0; +} diff --git a/meson.build b/meson.build new file mode 100644 index 000000000000..7a16d7800897 --- /dev/null +++ b/meson.build @@ -0,0 +1,42 @@ +project('sumatrapdf-linux', 'c', 'cpp', + version: '3.6-dev', + default_options: [ + 'c_std=c11', + 'cpp_std=c++17', + 'warning_level=2', + 'buildtype=debugoptimized', + ], + meson_version: '>= 0.60.0', +) + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +# Common flags +add_project_arguments('-DNDEBUG', language: ['c', 'cpp']) + +# ─── External dependencies (system or subproject) ────────────────────────────── + +# We build most dependencies from source (matching the Windows build) +# This ensures version consistency and avoids system library mismatches. + +subdir('linux/ext/zlib') +subdir('linux/ext/freetype') +subdir('linux/ext/harfbuzz') +subdir('linux/ext/libjpeg-turbo') +subdir('linux/ext/openjpeg') +subdir('linux/ext/jbig2dec') +subdir('linux/ext/lcms2') +subdir('linux/ext/brotli') +subdir('linux/ext/extract') +subdir('linux/ext/gumbo-parser') +subdir('linux/ext/libwebp') +subdir('linux/ext/mujs') +subdir('linux/ext/libdjvu') + +# MuPDF (core rendering engine) +subdir('linux/ext/mupdf') + +# ─── Platform abstraction & test binary ──────────────────────────────────────── + +subdir('linux/src') diff --git a/meson.options b/meson.options new file mode 100644 index 000000000000..3f7458ceba61 --- /dev/null +++ b/meson.options @@ -0,0 +1,3 @@ +option('enable_djvu', type: 'boolean', value: true, description: 'Enable DjVu format support') +option('enable_chm', type: 'boolean', value: true, description: 'Enable CHM format support') +option('enable_ebook', type: 'boolean', value: true, description: 'Enable EPUB/MOBI/FB2 support') From d58367316c856e4db855f2fc7619ba116a6d3d4a Mon Sep 17 00:00:00 2001 From: Junaid Iqbal Date: Mon, 18 May 2026 15:38:38 +0300 Subject: [PATCH 2/3] Fix CI: add brotli include/dep to freetype, fix subdir order --- linux/ext/freetype/meson.build | 4 ++-- meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linux/ext/freetype/meson.build b/linux/ext/freetype/meson.build index 2e45ec978856..d139ccaffdd4 100644 --- a/linux/ext/freetype/meson.build +++ b/linux/ext/freetype/meson.build @@ -32,7 +32,7 @@ freetype_sources = files( freetype_inc = include_directories(freetype_inc_dir, freetype_mupdf_scripts) freetype_lib = static_library('freetype', freetype_sources, - include_directories: [freetype_inc, zlib_inc], + include_directories: [freetype_inc, zlib_inc, brotli_inc], c_args: [ '-DFT2_BUILD_LIBRARY', '-DFT_CONFIG_MODULES_H="slimftmodules.h"', @@ -40,7 +40,7 @@ freetype_lib = static_library('freetype', freetype_sources, '-DFT_CONFIG_OPTION_USE_BROTLI', '-DFT_CONFIG_OPTION_SYSTEM_ZLIB', ], - dependencies: [zlib_dep], + dependencies: [zlib_dep, brotli_dep], ) freetype_dep = declare_dependency( diff --git a/meson.build b/meson.build index 7a16d7800897..d2932d90b989 100644 --- a/meson.build +++ b/meson.build @@ -21,13 +21,13 @@ add_project_arguments('-DNDEBUG', language: ['c', 'cpp']) # This ensures version consistency and avoids system library mismatches. subdir('linux/ext/zlib') +subdir('linux/ext/brotli') subdir('linux/ext/freetype') subdir('linux/ext/harfbuzz') subdir('linux/ext/libjpeg-turbo') subdir('linux/ext/openjpeg') subdir('linux/ext/jbig2dec') subdir('linux/ext/lcms2') -subdir('linux/ext/brotli') subdir('linux/ext/extract') subdir('linux/ext/gumbo-parser') subdir('linux/ext/libwebp') From 0e6dfbc1f1ab2c30da3e4f1800e449bcbf32daff Mon Sep 17 00:00:00 2001 From: Junaid Iqbal Date: Mon, 18 May 2026 21:28:03 +0300 Subject: [PATCH 3/3] Add GTK 4 GUI application for Linux - main_gtk.c: Full PDF viewer with GTK 4 header bar, page navigation, zoom controls, keyboard shortcuts, drag-and-drop, fullscreen support - Renders pages via MuPDF to Cairo surfaces - Handles file opening via GApplication open signal - meson.build: Add sumatrapdf GUI target with gtk4 dependency, rename old test binary to test-mupdf - CI: Add libgtk-4-dev to build dependencies --- .github/workflows/linux-package.yml | 2 +- agents.md | 83 ++-- linux/src/main_gtk.c | 564 ++++++++++++++++++++++++++++ linux/src/meson.build | 22 +- 4 files changed, 645 insertions(+), 26 deletions(-) create mode 100644 linux/src/main_gtk.c diff --git a/.github/workflows/linux-package.yml b/.github/workflows/linux-package.yml index cf7e977f7a85..0b11a22e3a96 100644 --- a/.github/workflows/linux-package.yml +++ b/.github/workflows/linux-package.yml @@ -21,7 +21,7 @@ jobs: - name: Install build dependencies run: | sudo apt-get update - sudo apt-get install -y meson ninja-build gcc g++ + sudo apt-get install -y meson ninja-build gcc g++ libgtk-4-dev - name: Configure run: | diff --git a/agents.md b/agents.md index 10f05d710a22..87c6201279da 100644 --- a/agents.md +++ b/agents.md @@ -1,42 +1,81 @@ -This is a C++ program for Windows, using mostly win32 windows API functions +This is a C++ program for Windows, using mostly win32 windows API functions. -We don't use STL but our own string / helper / container functions implemented in src\utils directory +Never commit changes automatically. Always wait for explicit command to commit changes. -Assume that Visual Studio command-line tools are available in the PATH environment variable (cl.exe, msbuild.exe etc.) +## Project Layout -Our code is in src/ directory. External dependencies are in ext/ directory and mupdf\ directory +- `src/` — Our application code (C++17, Win32 API) +- `src/utils/` — Custom utility library (strings, containers, file I/O, Win32 helpers) +- `ext/` — External dependencies (freetype, harfbuzz, libjpeg-turbo, zlib, etc.) +- `mupdf/` — MuPDF library (PDF rendering engine) +- `cmd/` — TypeScript build/codegen scripts (run with `bun`) +- `docs/md/` — Documentation in Markdown +- `vs2022/` — Generated Visual Studio solution (do not edit manually) +- `out/` — Build output directory -To build run: bun ./cmd/build.ts +## Code Conventions -This creates ./out/dbg64/SumatraPDF.exe executable +- **No STL** — Use custom containers: `Vec`, `StrVec`, `Dict` from `src/utils/` +- **No exceptions** — Explicit error handling throughout +- **String utilities**: `str::Dup()`, `str::Free()`, `str::Eq()` etc. from `src/utils/StrUtil.h` +- **Naming**: PascalCase for types/classes, camelCase for variables, `CmdXxx` for commands +- **Memory**: Manual management with `new`/`delete`, `str::Dup()`/`str::Free()` +- **Unicode**: `WCHAR*` for Win32 UI, `char*` (UTF-8) internally +- **Formatting**: Chromium-based style, 4-space indent, 120 column limit (see `.clang-format`) +- **Key namespaces**: `str::`, `path::`, `file::`, `url::`, `trans::`, `mui::` -To debug run: `windbgx -Q -o -g ./out/dbg64/SumatraPDF.exe` +## Build & Test -After making a change to .cpp, .c or .h file (and before running build.ts), run clang-format on those files to reformat them in place +- **Build**: `bun ./cmd/build.ts` → produces `./out/dbg64/SumatraPDF.exe` +- **Test**: `bun cmd/run-tests.ts` +- **Debug**: `windbgx -Q -o -g ./out/dbg64/SumatraPDF.exe` +- **Format**: After modifying `.cpp`, `.c`, or `.h` files, run `clang-format -i ` before building +- Visual Studio command-line tools (cl.exe, msbuild.exe) are available in PATH -Never commit changes automatically. Always wait for explicit command to commit changes. +## Generated Files (do not edit manually) + +These files are generated by TypeScript scripts in `cmd/`: + +| File | Generator | Purpose | +|------|-----------|---------| +| `src/Commands.h`, `src/Commands.cpp` | `bun cmd/gen-commands.ts` | Command IDs and metadata | +| `src/Settings.h`, `src/Settings.cpp` | `bun cmd/gen-settings.ts` | Settings structure | +| `src/Flags.h`, `src/Flags.cpp` | `bun cmd/gen-flags.ts` | Command-line flags | ## Adding a new advanced setting -To add a new advanced setting: -- add definition in cmd/gen-settings.ts -- run "bun cmd/gen-settings.ts" to regenerate src/Settings.h and src/Settings.cpp +- Add definition in `cmd/gen-settings.ts` +- Run `bun cmd/gen-settings.ts` to regenerate `src/Settings.h` and `src/Settings.cpp` ## Adding a new command -To add a new command: -- add to cmd/gen-commands.ts, always at the end of the list (before the "CmdNone" command) -- run "bun cmd/gen-commands.ts" to regenerate src/Commands.h and src/Commands.cpp -- document in docs/md/Commands.md -- document in docs/md/Version-history.md in **next** section +- Add to `cmd/gen-commands.ts`, always at the end of the list (before the `CmdNone` command) +- Run `bun cmd/gen-commands.ts` to regenerate `src/Commands.h` and `src/Commands.cpp` +- Document in `docs/md/Commands.md` +- Document in `docs/md/Version-history.md` in **next** section ## Adding a new cmd-line flag -To add a new cmd-line flag: -- add to cmd/gen-flags.ts -- run "bun cmd/gen-flags.ts" to regenerate src/Flags.h and src/Flags.cpp -- implement handling in Flags.cpp -- document in docs/md/Version-history.md in **next** section +- Add to `cmd/gen-flags.ts` +- Run `bun cmd/gen-flags.ts` to regenerate `src/Flags.h` and `src/Flags.cpp` +- Implement handling in `Flags.cpp` +- Document in `docs/md/Version-history.md` in **next** section + +## Key Architecture + +- **Engine layer**: `src/EngineBase.h` defines the abstract document engine interface; `src/EngineMupdf.h` implements PDF/XPS via MuPDF +- **Display**: `src/DisplayModel.h` manages page layout, zoom, scroll (MVC pattern) +- **Window**: `src/MainWindow.h` is the main app window; `src/Canvas.h` is the rendering surface +- **Settings**: `src/AppSettings.h` for runtime config; advanced settings in generated `src/Settings.h` +- **UI widgets**: `src/wingui/` wraps Win32 controls; `src/mui/` is the custom UI framework + +## Documentation + +- [docs/md/Build-system.md](docs/md/Build-system.md) — Build instructions +- [docs/md/Debugging-Sumatra.md](docs/md/Debugging-Sumatra.md) — Debugging guide +- [docs/md/Commands.md](docs/md/Commands.md) — Command reference +- [docs/md/Version-history.md](docs/md/Version-history.md) — Release notes +- [docs/md/Contribute-to-SumatraPDF.md](docs/md/Contribute-to-SumatraPDF.md) — Contribution guide ## Windows Shell Safety diff --git a/linux/src/main_gtk.c b/linux/src/main_gtk.c new file mode 100644 index 000000000000..0487369d192e --- /dev/null +++ b/linux/src/main_gtk.c @@ -0,0 +1,564 @@ +/* + * SumatraPDF Linux - GTK 4 GUI + * + * A document viewer using MuPDF for rendering and GTK 4 for the interface. + * Supports PDF, EPUB, XPS, CBZ, SVG, HTML and image formats. + */ + +#include +#include +#include +#include +#include "mupdf/fitz.h" + +/* ─── Application State ────────────────────────────────────────────────────── */ + +typedef struct { + GtkApplication *app; + GtkWidget *window; + GtkWidget *drawing_area; + GtkWidget *header_bar; + GtkWidget *page_label; + GtkWidget *zoom_label; + GtkWidget *scroll_window; + + fz_context *ctx; + fz_document *doc; + char *filename; + + int page_number; + int page_count; + float zoom; + float scroll_y; + + /* Rendered page cache */ + fz_pixmap *pix; + int render_width; + int render_height; +} AppState; + +static AppState state = {0}; + +/* ─── Forward Declarations ─────────────────────────────────────────────────── */ + +static void render_page(void); +static void update_title(void); +static void open_file(const char *path); +static void close_document(void); + +/* ─── Rendering ────────────────────────────────────────────────────────────── */ + +static void invalidate_cache(void) +{ + if (state.pix) { + fz_drop_pixmap(state.ctx, state.pix); + state.pix = NULL; + } +} + +static void render_page(void) +{ + if (!state.doc) + return; + + invalidate_cache(); + + fz_try(state.ctx) { + fz_matrix ctm = fz_scale(state.zoom, state.zoom); + state.pix = fz_new_pixmap_from_page_number(state.ctx, state.doc, + state.page_number, ctm, + fz_device_rgb(state.ctx), 0); + state.render_width = state.pix->w; + state.render_height = state.pix->h; + } + fz_catch(state.ctx) { + fprintf(stderr, "error rendering page: %s\n", fz_caught_message(state.ctx)); + } + + /* Update UI */ + update_title(); + if (state.drawing_area) + gtk_widget_queue_draw(state.drawing_area); +} + +static void update_title(void) +{ + if (!state.window) + return; + + char title[512]; + if (state.doc) { + const char *basename = strrchr(state.filename, '/'); + basename = basename ? basename + 1 : state.filename; + snprintf(title, sizeof(title), "%s — Page %d / %d — %d%%", + basename, state.page_number + 1, state.page_count, + (int)(state.zoom * 100)); + } else { + snprintf(title, sizeof(title), "SumatraPDF"); + } + gtk_window_set_title(GTK_WINDOW(state.window), title); + + if (state.page_label) { + char buf[64]; + snprintf(buf, sizeof(buf), " %d / %d ", state.page_number + 1, state.page_count); + gtk_label_set_text(GTK_LABEL(state.page_label), buf); + } + if (state.zoom_label) { + char buf[32]; + snprintf(buf, sizeof(buf), " %d%% ", (int)(state.zoom * 100)); + gtk_label_set_text(GTK_LABEL(state.zoom_label), buf); + } +} + +/* ─── Document Management ──────────────────────────────────────────────────── */ + +static void close_document(void) +{ + invalidate_cache(); + if (state.doc) { + fz_drop_document(state.ctx, state.doc); + state.doc = NULL; + } + if (state.filename) { + g_free(state.filename); + state.filename = NULL; + } + state.page_number = 0; + state.page_count = 0; +} + +static void open_file(const char *path) +{ + close_document(); + + fz_try(state.ctx) { + state.doc = fz_open_document(state.ctx, path); + state.page_count = fz_count_pages(state.ctx, state.doc); + state.filename = g_strdup(path); + state.page_number = 0; + state.scroll_y = 0; + render_page(); + } + fz_catch(state.ctx) { + fprintf(stderr, "error opening '%s': %s\n", path, fz_caught_message(state.ctx)); + GtkAlertDialog *dialog = gtk_alert_dialog_new("Cannot open file: %s", + fz_caught_message(state.ctx)); + gtk_alert_dialog_show(dialog, GTK_WINDOW(state.window)); + g_object_unref(dialog); + } +} + +/* ─── Navigation ───────────────────────────────────────────────────────────── */ + +static void go_to_page(int page) +{ + if (!state.doc) + return; + if (page < 0) page = 0; + if (page >= state.page_count) page = state.page_count - 1; + if (page == state.page_number) + return; + state.page_number = page; + state.scroll_y = 0; + render_page(); +} + +static void next_page(void) { go_to_page(state.page_number + 1); } +static void prev_page(void) { go_to_page(state.page_number - 1); } +static void first_page(void) { go_to_page(0); } +static void last_page(void) { go_to_page(state.page_count - 1); } + +static void zoom_in(void) +{ + if (state.zoom < 5.0f) { + state.zoom *= 1.25f; + render_page(); + } +} + +static void zoom_out(void) +{ + if (state.zoom > 0.25f) { + state.zoom /= 1.25f; + render_page(); + } +} + +static void zoom_reset(void) +{ + state.zoom = 1.5f; + render_page(); +} + +/* ─── Drawing ──────────────────────────────────────────────────────────────── */ + +static void cairo_data_destroy(void *data) +{ + free(data); +} + +static void on_draw(GtkDrawingArea *area, cairo_t *cr, int width, int height, + gpointer user_data) +{ + (void)area; (void)user_data; + + /* Background */ + cairo_set_source_rgb(cr, 0.3, 0.3, 0.3); + cairo_paint(cr); + + if (!state.pix) + return; + + /* Center the page in the drawing area */ + int pw = state.render_width; + int ph = state.render_height; + double ox = (width - pw) / 2.0; + double oy = (height - ph) / 2.0; + if (ox < 0) ox = 0; + if (oy < 0) oy = 0; + + /* Draw page shadow */ + cairo_set_source_rgba(cr, 0, 0, 0, 0.3); + cairo_rectangle(cr, ox + 3, oy + 3, pw, ph); + cairo_fill(cr); + + /* Convert RGB pixmap to Cairo RGB24 surface (Cairo needs 4 bytes/pixel: xRGB) */ + int cairo_stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, pw); + unsigned char *cairo_data = (unsigned char *)malloc((size_t)cairo_stride * ph); + if (!cairo_data) + return; + + for (int y = 0; y < ph; y++) { + const unsigned char *src = state.pix->samples + (size_t)y * state.pix->stride; + unsigned char *dst = cairo_data + (size_t)y * cairo_stride; + for (int x = 0; x < pw; x++) { + /* Cairo RGB24 is stored as native-endian 0xXXRRGGBB = [B,G,R,X] on little-endian */ + dst[x * 4 + 0] = src[x * 3 + 2]; /* B */ + dst[x * 4 + 1] = src[x * 3 + 1]; /* G */ + dst[x * 4 + 2] = src[x * 3 + 0]; /* R */ + dst[x * 4 + 3] = 0xFF; /* X (unused) */ + } + } + + cairo_surface_t *surface = cairo_image_surface_create_for_data( + cairo_data, CAIRO_FORMAT_RGB24, pw, ph, cairo_stride); + /* Let Cairo free the data when the surface is destroyed */ + static const cairo_user_data_key_t key; + cairo_surface_set_user_data(surface, &key, cairo_data, cairo_data_destroy); + + cairo_set_source_surface(cr, surface, ox, oy); + cairo_paint(cr); + cairo_surface_destroy(surface); +} + +/* ─── Event Handlers ───────────────────────────────────────────────────────── */ + +static gboolean on_key_pressed(GtkEventControllerKey *controller, guint keyval, + guint keycode, GdkModifierType mods, gpointer data) +{ + (void)controller; (void)keycode; (void)data; + + gboolean ctrl = (mods & GDK_CONTROL_MASK) != 0; + + switch (keyval) { + case GDK_KEY_Page_Down: + case GDK_KEY_space: + case GDK_KEY_j: + next_page(); + return TRUE; + case GDK_KEY_Page_Up: + case GDK_KEY_BackSpace: + case GDK_KEY_k: + prev_page(); + return TRUE; + case GDK_KEY_Home: + first_page(); + return TRUE; + case GDK_KEY_End: + last_page(); + return TRUE; + case GDK_KEY_plus: + case GDK_KEY_equal: + if (ctrl) { zoom_in(); return TRUE; } + break; + case GDK_KEY_minus: + if (ctrl) { zoom_out(); return TRUE; } + break; + case GDK_KEY_0: + if (ctrl) { zoom_reset(); return TRUE; } + break; + case GDK_KEY_o: + if (ctrl) { + /* Trigger file open */ + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_set_title(dialog, "Open Document"); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "Documents"); + gtk_file_filter_add_mime_type(filter, "application/pdf"); + gtk_file_filter_add_mime_type(filter, "application/epub+zip"); + gtk_file_filter_add_mime_type(filter, "application/x-cbz"); + gtk_file_filter_add_mime_type(filter, "image/*"); + gtk_file_filter_add_pattern(filter, "*.xps"); + gtk_file_filter_add_pattern(filter, "*.svg"); + gtk_file_filter_add_pattern(filter, "*.fb2"); + + GListStore *filters = g_list_store_new(GTK_TYPE_FILE_FILTER); + g_list_store_append(filters, filter); + gtk_file_dialog_set_filters(dialog, G_LIST_MODEL(filters)); + + gtk_file_dialog_open(dialog, GTK_WINDOW(state.window), NULL, + (GAsyncReadyCallback)({ + void callback(GObject *src, GAsyncResult *res, gpointer ud) { + (void)ud; + GtkFileDialog *d = GTK_FILE_DIALOG(src); + GFile *file = gtk_file_dialog_open_finish(d, res, NULL); + if (file) { + char *path = g_file_get_path(file); + if (path) { + open_file(path); + g_free(path); + } + g_object_unref(file); + } + } + callback; + }), NULL); + + g_object_unref(filters); + g_object_unref(filter); + g_object_unref(dialog); + return TRUE; + } + break; + case GDK_KEY_q: + if (ctrl) { + g_application_quit(G_APPLICATION(state.app)); + return TRUE; + } + break; + case GDK_KEY_F11: + if (gtk_window_is_fullscreen(GTK_WINDOW(state.window))) + gtk_window_unfullscreen(GTK_WINDOW(state.window)); + else + gtk_window_fullscreen(GTK_WINDOW(state.window)); + return TRUE; + } + return FALSE; +} + +static void on_scroll(GtkEventControllerScroll *controller, double dx, double dy, + gpointer data) +{ + (void)controller; (void)dx; (void)data; + + GdkModifierType mods = gtk_event_controller_get_current_event_state( + GTK_EVENT_CONTROLLER(controller)); + + if (mods & GDK_CONTROL_MASK) { + /* Ctrl+scroll = zoom */ + if (dy < 0) + zoom_in(); + else + zoom_out(); + } else { + /* Scroll = page navigation when at boundaries */ + state.scroll_y += dy * 50; + if (state.scroll_y > 100) { + next_page(); + state.scroll_y = 0; + } else if (state.scroll_y < -100) { + prev_page(); + state.scroll_y = 0; + } + } +} + +/* ─── File Drop Support ────────────────────────────────────────────────────── */ + +static gboolean on_drop(GtkDropTarget *target, const GValue *value, + double x, double y, gpointer data) +{ + (void)target; (void)x; (void)y; (void)data; + + if (G_VALUE_HOLDS(value, GDK_TYPE_FILE_LIST)) { + GSList *files = g_value_get_boxed(value); + if (files) { + GFile *file = files->data; + char *path = g_file_get_path(file); + if (path) { + open_file(path); + g_free(path); + } + } + return TRUE; + } + return FALSE; +} + +/* ─── Toolbar Button Callbacks ─────────────────────────────────────────────── */ + +static void on_open_clicked(GtkButton *btn, gpointer data) +{ + (void)btn; (void)data; + /* Simulate Ctrl+O */ + GdkModifierType mods = GDK_CONTROL_MASK; + on_key_pressed(NULL, GDK_KEY_o, 0, mods, NULL); +} + +static void on_prev_clicked(GtkButton *btn, gpointer data) { (void)btn; (void)data; prev_page(); } +static void on_next_clicked(GtkButton *btn, gpointer data) { (void)btn; (void)data; next_page(); } +static void on_zoom_in_clicked(GtkButton *btn, gpointer data) { (void)btn; (void)data; zoom_in(); } +static void on_zoom_out_clicked(GtkButton *btn, gpointer data) { (void)btn; (void)data; zoom_out(); } + +/* ─── Application Setup ───────────────────────────────────────────────────── */ + +static void activate(GtkApplication *app, gpointer user_data) +{ + (void)user_data; + + /* Avoid re-creating the window if already activated */ + if (state.window) { + gtk_window_present(GTK_WINDOW(state.window)); + return; + } + + /* Window */ + state.window = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(state.window), "SumatraPDF"); + gtk_window_set_default_size(GTK_WINDOW(state.window), 900, 700); + + /* Header bar with toolbar */ + state.header_bar = gtk_header_bar_new(); + gtk_window_set_titlebar(GTK_WINDOW(state.window), state.header_bar); + + /* Open button */ + GtkWidget *open_btn = gtk_button_new_from_icon_name("document-open-symbolic"); + gtk_widget_set_tooltip_text(open_btn, "Open (Ctrl+O)"); + g_signal_connect(open_btn, "clicked", G_CALLBACK(on_open_clicked), NULL); + gtk_header_bar_pack_start(GTK_HEADER_BAR(state.header_bar), open_btn); + + /* Navigation buttons */ + GtkWidget *nav_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(nav_box, "linked"); + + GtkWidget *prev_btn = gtk_button_new_from_icon_name("go-previous-symbolic"); + gtk_widget_set_tooltip_text(prev_btn, "Previous Page (PgUp)"); + g_signal_connect(prev_btn, "clicked", G_CALLBACK(on_prev_clicked), NULL); + gtk_box_append(GTK_BOX(nav_box), prev_btn); + + state.page_label = gtk_label_new(" 0 / 0 "); + gtk_widget_set_size_request(state.page_label, 80, -1); + gtk_box_append(GTK_BOX(nav_box), state.page_label); + + GtkWidget *next_btn = gtk_button_new_from_icon_name("go-next-symbolic"); + gtk_widget_set_tooltip_text(next_btn, "Next Page (PgDn)"); + g_signal_connect(next_btn, "clicked", G_CALLBACK(on_next_clicked), NULL); + gtk_box_append(GTK_BOX(nav_box), next_btn); + + gtk_header_bar_pack_start(GTK_HEADER_BAR(state.header_bar), nav_box); + + /* Zoom buttons */ + GtkWidget *zoom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(zoom_box, "linked"); + + GtkWidget *zout_btn = gtk_button_new_from_icon_name("zoom-out-symbolic"); + gtk_widget_set_tooltip_text(zout_btn, "Zoom Out (Ctrl+-)"); + g_signal_connect(zout_btn, "clicked", G_CALLBACK(on_zoom_out_clicked), NULL); + gtk_box_append(GTK_BOX(zoom_box), zout_btn); + + state.zoom_label = gtk_label_new(" 150% "); + gtk_widget_set_size_request(state.zoom_label, 60, -1); + gtk_box_append(GTK_BOX(zoom_box), state.zoom_label); + + GtkWidget *zin_btn = gtk_button_new_from_icon_name("zoom-in-symbolic"); + gtk_widget_set_tooltip_text(zin_btn, "Zoom In (Ctrl++)"); + g_signal_connect(zin_btn, "clicked", G_CALLBACK(on_zoom_in_clicked), NULL); + gtk_box_append(GTK_BOX(zoom_box), zin_btn); + + gtk_header_bar_pack_end(GTK_HEADER_BAR(state.header_bar), zoom_box); + + /* Drawing area */ + state.drawing_area = gtk_drawing_area_new(); + gtk_widget_set_hexpand(state.drawing_area, TRUE); + gtk_widget_set_vexpand(state.drawing_area, TRUE); + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(state.drawing_area), + on_draw, NULL, NULL); + gtk_window_set_child(GTK_WINDOW(state.window), state.drawing_area); + + /* Keyboard controller */ + GtkEventController *key_ctrl = gtk_event_controller_key_new(); + g_signal_connect(key_ctrl, "key-pressed", G_CALLBACK(on_key_pressed), NULL); + gtk_widget_add_controller(state.window, key_ctrl); + + /* Scroll controller */ + GtkEventController *scroll_ctrl = gtk_event_controller_scroll_new( + GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); + g_signal_connect(scroll_ctrl, "scroll", G_CALLBACK(on_scroll), NULL); + gtk_widget_add_controller(state.drawing_area, scroll_ctrl); + + /* Drag-and-drop support */ + GtkDropTarget *drop = gtk_drop_target_new(GDK_TYPE_FILE_LIST, GDK_ACTION_COPY); + g_signal_connect(drop, "drop", G_CALLBACK(on_drop), NULL); + gtk_widget_add_controller(state.window, GTK_EVENT_CONTROLLER(drop)); + + gtk_window_present(GTK_WINDOW(state.window)); +} + +/* ─── GApplication "open" handler ──────────────────────────────────────────── */ + +static void on_app_open(GApplication *app, GFile **files, int n_files, + const char *hint, gpointer data) +{ + (void)hint; (void)data; + + /* Activate the window first */ + activate(GTK_APPLICATION(app), NULL); + + if (n_files > 0) { + char *path = g_file_get_path(files[0]); + if (path) { + open_file(path); + g_free(path); + } + } +} + +/* ─── Main ─────────────────────────────────────────────────────────────────── */ + +int main(int argc, char *argv[]) +{ + /* Initialize MuPDF with larger store for big documents */ + state.ctx = fz_new_context(NULL, NULL, 512 << 20); + if (!state.ctx) { + fprintf(stderr, "error: cannot create mupdf context\n"); + return 1; + } + + fz_try(state.ctx) { + fz_register_document_handlers(state.ctx); + } + fz_catch(state.ctx) { + fprintf(stderr, "error: %s\n", fz_caught_message(state.ctx)); + fz_drop_context(state.ctx); + return 1; + } + + state.zoom = 1.5f; + + /* Create GTK application — HANDLES_OPEN lets GLib pass file args properly */ + state.app = gtk_application_new("org.sumatrapdf.SumatraPDF", + G_APPLICATION_NON_UNIQUE | + G_APPLICATION_HANDLES_OPEN); + g_signal_connect(state.app, "activate", G_CALLBACK(activate), NULL); + g_signal_connect(state.app, "open", G_CALLBACK(on_app_open), NULL); + + int status = g_application_run(G_APPLICATION(state.app), argc, argv); + + /* Cleanup */ + close_document(); + g_object_unref(state.app); + fz_drop_context(state.ctx); + + return status; +} diff --git a/linux/src/meson.build b/linux/src/meson.build index 80da698d253f..d6347ea786e3 100644 --- a/linux/src/meson.build +++ b/linux/src/meson.build @@ -1,6 +1,8 @@ # Phase 1: Minimal test binary to verify MuPDF works on Linux # Phase 2+: Full application sources will be added here +gtk4_dep = dependency('gtk4', version: '>= 4.6') + common_deps = [ mupdf_dep, zlib_dep, @@ -20,8 +22,22 @@ common_deps = [ common_link_args = ['-lm', '-lpthread'] -# The main application binary (currently the renderer test) +# ─── GTK 4 GUI application ──────────────────────────────────────────────────── + sumatrapdf = executable('sumatrapdf', + files('main_gtk.c'), + mupdf_resource_objs, + include_directories: [ + include_directories('../../mupdf/include'), + ], + dependencies: common_deps + [gtk4_dep], + link_args: common_link_args, + install: true, +) + +# ─── CLI test binary (headless) ─────────────────────────────────────────────── + +test_mupdf = executable('test-mupdf', files('test_mupdf.c'), mupdf_resource_objs, include_directories: [ @@ -29,10 +45,10 @@ sumatrapdf = executable('sumatrapdf', ], dependencies: common_deps, link_args: common_link_args, - install: true, + install: false, ) -test('mupdf-smoke', sumatrapdf, args: []) +test('mupdf-smoke', test_mupdf, args: []) # ─── Install targets ──────────────────────────────────────────────────────────