PHP extension · TrustMark IDs

A cropped screenshot still returns the same ID.

By default, pdfwm writes the string you pass into every tile of an image, or of a rasterized PDF page. Extraction hands that same string back. There is no payload map, and the extension does not expose payload_hex.

Install the Homebrew formula document-embed. It builds the extension against Homebrew php.

brew tap dickwu/tap
brew install dickwu/tap/document-embed

Models, Pdfium, and the source build

Tile field · example id 10482

same id

Extracted id 10482

Drag the crop, or focus it and use the arrow keys. Every tile carries the example id 10482, and the readout stays on that id. In pdfwm, a partial screenshot that contains one whole tile is still traceable. This page does not run the extension.

API

Five functions. Paths are strings. Options are an array. Extraction returns id, confidence, and votes. PDF extraction also votes across pages.

  • pdfwm_embed_image_path(string $inputImagePath, string $id, string $outputImagePath, array $options = []): array
  • pdfwm_extract_image_path(string $imagePath, array $options = []): array
  • pdfwm_embed_pdf_path(string $inputPdfPath, string $id, string $outputPdfPath, array $options = []): array
  • pdfwm_extract_pdf_path(string $pdfPath, array $options = []): array
  • pdfwm_read_metadata(string $pdfPath): array

Tiling

A single whole-image TrustMark is keyed to the entire frame, so a cropped screenshot of that page cannot be decoded. Its residual, a 256×256 pattern stretched across the page, can show up as a faint shadow on white backgrounds. Embedding can split the page into a grid of roughly square tiles and watermark each tile with the same id.

A partial screenshot that contains one whole tile is still traceable. The residual is feathered to zero at tile borders. Tiles are independent, so they are watermarked in parallel across cores. Keep tile_size proportional to dpi so the tile count stays steady as you raise resolution.

Extraction tries the whole image, then sweeps multi-scale sliding windows. Votes alone are not proof of a watermark, because the decoder can emit a consistent bogus id from an unwatermarked region. Constrain results with id_codec and id_max_digits.

Tiling and partial-screenshot options
Option Default Purpose
tile_size 1280 Target tile side in pixels. 0 disables tiling and writes one whole-image watermark.
tile_feather auto Residual edge-feather width in pixels inside each tile. 0 derives it from the tile size.
search_decode true Run the sliding-window search on extract. Needed to decode tiled output and partial screenshots.
min_search_votes 2 Agreeing windows required for a high-confidence decode. A single decode is still returned at low confidence.
max_search_windows 180 Upper bound on windows scanned per image.
id_max_digits unset Reject decoded numeric ids longer than this. Paired with an id_codec constraint, it discards noise decodes from unwatermarked regions.

PDF output

PDF embedding rasterizes every page, applies the pixel watermark, and rebuilds an image-only PDF. The output does not guarantee preservation of searchable text, links, forms, bookmarks, attachments, layers, or vector structure. File size can increase substantially. Higher DPI improves raster quality and makes generation slower and larger.

Pages are embedded at the full rasterized resolution, with no silent downsampling, and stored as JPEG. jpeg_quality defaults to 92. dpi defaults to 500. A 500 DPI US-Letter page is roughly 23 MP and about 1–2 MB per page after JPEG compression. Lower dpi, or the PDFWM_DPI environment value, to trade sharpness for smaller, faster output.

Other supported options are id_codec, model_dir, pdfium_lib_path, strength, variant, version, image_format, embed_metadata, max_pages, max_pixels_per_page, and max_id_bytes.

TrustMark is not DRM, encryption, or malicious-removal-proof steganography. It improves leak tracing through screenshots and common image transforms. It cannot stop someone from taking a screenshot, photographing a screen, or attacking the watermark.

Runtime

TrustMark model files must already exist on disk. The extension never downloads models during a PHP request. The Homebrew formula does not bundle them, and the v0.1.6 release tarball does not contain them. The main branch of the source repository includes encoder_Q.onnx and decoder_Q.onnx under models/. Point PDFWM_MODEL_DIR at a directory that contains those two files.

PDFIUM_DYNAMIC_LIB_PATH is required for PDF embed and extract unless Pdfium is available as a system library. It may point at the dynamic library file or at the directory that contains the platform-named Pdfium library. The path below is the example from the README.

export PDFWM_MODEL_DIR=/opt/pdfwm/trustmark-models
export PDFIUM_DYNAMIC_LIB_PATH=/opt/pdfium/libpdfium.dylib

Restart long-running PHP processes after install or upgrade.

ID length

The default TrustMark version is BCH_5, with 61 protected data bits. The first 2 bits are a codec header, so direct IDs are short. id_codec defaults to auto, which tries uint_decimal, then decimal_bcd, then base36, then utf8_raw. An ID that does not fit fails before image or PDF processing with PdfwmIdTooLongException.

ID capacity for TrustMark BCH_5
Codec BCH_5 capacity
uint_decimal 0..576460752303423487 without leading zeroes
decimal_bcd Up to 13 digits, preserving leading zeroes
base36 Up to 10 characters in [0-9A-Z], preserving leading zeroes
utf8_raw Up to 7 UTF-8 bytes

Install

  1. Tap and install

    The formula builds pdfwm from source against the php formula, using that formula’s php-config. The build also uses rust and llvm. It installs pdfwm.so and writes ext-pdfwm.ini for the detected PHP major and minor version.

    brew tap dickwu/tap
    brew install dickwu/tap/document-embed
  2. Confirm the module

    Use the Homebrew php binary. If your shell still resolves php to a versioned formula such as php@8.3, the command below avoids that, or put the Homebrew php formula first on PATH. php -m lists the module as pdfwm_php_ext.

    $(brew --prefix php)/bin/php -m | grep pdfwm

    With Homebrew PHP 8.5 the ini file is:

    $(brew --prefix)/etc/php/8.5/conf.d/ext-pdfwm.ini
  3. Point at models and Pdfium

    Set PDFWM_MODEL_DIR before embedding or extracting. Set PDFIUM_DYNAMIC_LIB_PATH for PDF work unless Pdfium is already a system library. The export lines in Runtime are the README examples, not paths the formula creates.

  4. Restart PHP

    Restart long-running PHP processes after install or upgrade so they load the new ext-pdfwm.ini.

Or build from the source repository

The repository is dickwu/document_embed. A release build produces target/release/libpdfwm.dylib on macOS and target/release/libpdfwm.so on Linux. The smoke check in the README loads a debug build and checks that pdfwm_embed_image_path exists. Replace the path.

cargo build --release

php -d extension=/absolute/path/to/target/debug/libpdfwm.dylib -r 'var_dump(function_exists("pdfwm_embed_image_path"));'

Questions

What does document-embed install?

document-embed installs pdfwm, a Rust-based PHP extension for embedding screenshot-robust TrustMark watermarks into images and image-only PDF output. Business code passes an external string ID, and extraction returns that same ID.

How do I install it?

Run brew tap dickwu/tap, then brew install dickwu/tap/document-embed. The formula builds the extension against Homebrew php and writes an ini file for the detected PHP major and minor version.

Will a cropped screenshot still decode?

A partial screenshot that contains one whole tile is still traceable. The default tile_size is 1280. Set tile_size to 0 to disable tiling and embed a single whole-image watermark, which a cropped screenshot cannot decode.

Does the output PDF stay searchable?

No. PDF embedding rasterizes every page, applies the pixel watermark, and rebuilds an image-only PDF. It does not guarantee preservation of searchable text, links, forms, bookmarks, attachments, layers, or vector structure. File size can increase substantially.

Where do the TrustMark models come from?

TrustMark model files must already exist on disk. The extension never downloads models during a PHP request, and the Homebrew formula does not bundle them. Set PDFWM_MODEL_DIR to the directory containing encoder_Q.onnx and decoder_Q.onnx.

What does PDF embed and extract need besides the extension?

PDF embed and extract need Pdfium unless Pdfium is already available as a system library. Set PDFIUM_DYNAMIC_LIB_PATH to the dynamic library file, or to the directory containing the platform-named Pdfium library.

What does extraction return?

Extraction returns id, confidence, and votes. Confidence is 1.0 when corroborated by min_search_votes windows, and lower for a single decode. Votes are how many windows agreed. PDF extraction additionally votes across pages. Votes alone are not proof of a watermark. Constrain results with id_codec and id_max_digits so a clean page cannot be misread. pdfwm does not expose payload_hex and does not require an application-side payload mapping table.

How long can the ID be?

The default TrustMark version is BCH_5, with 61 protected data bits. The first 2 bits are a codec header. uint_decimal fits 0..576460752303423487 without leading zeroes. decimal_bcd fits up to 13 digits and preserves leading zeroes. base36 fits up to 10 characters in the set [0-9A-Z] and preserves leading zeroes. utf8_raw fits up to 7 UTF-8 bytes. id_codec defaults to auto and tries those four codecs in that order. IDs that do not fit fail before image or PDF processing with PdfwmIdTooLongException.

Is TrustMark DRM?

No. TrustMark is not DRM, encryption, or malicious-removal-proof steganography. It improves leak tracing through screenshots and common image transforms, but it cannot stop a user from taking screenshots, photographing a screen, or attacking the watermark.