2 Commits
Author SHA1 Message Date
Furqan-14 de4868ff49 Revert "Merge pull request 'saqib' (#5) from saqib into main"
CI / lint (push) Waiting to run
CI / build (macos-latest, macos-debug) (push) Blocked by required conditions
CI / build (ubuntu-latest, linux-debug) (push) Blocked by required conditions
CI / build (windows-latest, windows-debug) (push) Blocked by required conditions
This reverts commit a1e6b5910c, reversing
changes made to 43eba01201.
2026-05-21 11:52:27 +05:30
furqan a1e6b5910c Merge pull request 'saqib' (#5) from saqib into main
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/5
2026-05-21 06:18:23 +00:00
489 changed files with 486 additions and 66637 deletions
-30
View File
@@ -1,30 +0,0 @@
**/.git
**/.github
**/.venv
**/node_modules
**/dist
**/build
**/out
**/__pycache__
**/*.pyc
**/*.pyo
**/*.pyd
**/*.log
**/.DS_Store
**/Thumbs.db
**/.vscode
**/.idea
**/coverage
**/tmp
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
**/CMakeUserPresets.json
**/compile_commands.json
**/vcpkg
**/third_party/pdfium/depot_tools
**/third_party/pdfium/checkout
**/third_party/pdfium/install
**/third_party/skia/depot_tools
**/third_party/skia/checkout
**/third_party/skia/install
+17 -73
View File
@@ -13,6 +13,9 @@ concurrency:
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------------------
# Static checks: Rule R2 boundary + formatting. Fast, gates the build matrix.
# ---------------------------------------------------------------------------
lint:
runs-on: ubuntu-latest
steps:
@@ -21,6 +24,9 @@ jobs:
- name: Rule R2 — PDFium boundary check
run: bash scripts/check_pdfium_boundary.sh
# Pinned so CI matches the version the tree was formatted with — clang-format
# output drifts between releases, and an unpinned runner version would cause
# spurious lint failures.
- name: Install clang-format (pinned)
run: pipx install clang-format==22.1.5
@@ -30,6 +36,9 @@ jobs:
find engine \( -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.hpp' \) \
-print0 | xargs -0 clang-format --dry-run --Werror
# ---------------------------------------------------------------------------
# Build + test on all three platforms (Gate G0: compiles clean everywhere).
# ---------------------------------------------------------------------------
build:
needs: lint
strategy:
@@ -40,11 +49,9 @@ jobs:
preset: linux-debug
- os: macos-latest
preset: macos-debug
experimental: true
- os: windows-latest
preset: windows-debug
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental == true }}
env:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
steps:
@@ -57,10 +64,15 @@ jobs:
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
# GitHub-hosted runners ship vcpkg preinstalled. VCPKG_INSTALLATION_ROOT is
# a runner OS env var, so it must be promoted to GITHUB_ENV here — it is
# NOT visible via the ${{ env.* }} context in a job-level env: block.
- name: Locate vcpkg
shell: bash
run: |
echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
# The runner's preinstalled vcpkg may predate our pinned builtin-baseline;
# fetch so manifest resolution can find that commit's versions tree.
git -C "$VCPKG_INSTALLATION_ROOT" fetch --quiet origin || true
- name: Create vcpkg binary cache dir
@@ -74,6 +86,9 @@ jobs:
key: vcpkg-${{ matrix.os }}-${{ hashFiles('vcpkg.json') }}
restore-keys: vcpkg-${{ matrix.os }}-
# vcpkg.json ships without a builtin-baseline; the local bootstrap script
# normally pins it. In CI we pin it on the fly (idempotent) so the
# CMakeLists dependency-pinning guard is satisfied.
- name: Pin vcpkg dependency baseline
shell: bash
run: |
@@ -89,74 +104,3 @@ jobs:
- name: Test
run: ctest --preset ${{ matrix.preset }}
gateway:
needs: lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: gateway
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: gateway/pyproject.toml
- name: Install gateway (editable, with dev extras)
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
- name: Ruff — lint
run: python -m ruff check .
- name: Ruff — format check
run: python -m ruff format --check .
- name: Pytest
run: python -m pytest
wasm:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read pinned emsdk version
id: emsdk-version
run: |
version=$(grep '^EMSDK_VERSION=' wasm/emsdk.pinned | cut -d= -f2)
if [ -z "$version" ]; then
echo "::error::EMSDK_VERSION not found in wasm/emsdk.pinned"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v5
- name: Set up Emscripten ${{ steps.emsdk-version.outputs.version }}
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ steps.emsdk-version.outputs.version }}
actions-cache-folder: emsdk-cache-${{ steps.emsdk-version.outputs.version }}
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Configure (WASM)
run: cmake --preset wasm
- name: Build
run: cmake --build --preset wasm
- name: Smoke test
run: node wasm/hello.test.mjs
-104
View File
@@ -1,104 +0,0 @@
# Extended robustness: corpus render-stability sweep + libFuzzer run.
#
# DELIBERATELY ISOLATED from the main CI gate:
# - never triggers on push or pull_request, so it can NEVER block a merge,
# push, or pull;
# - the whole job is continue-on-error, so a crash finding or a build/runner
# problem reports red here but does not fail any required check;
# - runs on a weekly schedule and on manual dispatch only.
#
# A full 24h fuzz run needs a self-hosted runner (GitHub-hosted runners cap a job
# at 6h). Use the workflow_dispatch `duration_seconds` input for that; the weekly
# schedule does a short smoke instead.
name: Fuzz & corpus sweep
on:
schedule:
- cron: "0 3 * * 0" # Sundays 03:00 UTC — short smoke
workflow_dispatch:
inputs:
duration_seconds:
description: "libFuzzer -max_total_time (e.g. 1800 smoke, 86400 for 24h on a self-hosted runner)"
default: "1800"
sanitizers:
description: "Sanitizer set (fuzzer,address,undefined needs an ASan/UBSan-built PDFium; fuzzer = coverage-only)"
default: "fuzzer,address,undefined"
permissions:
contents: read
concurrency:
group: fuzz-${{ github.ref }}
cancel-in-progress: true
jobs:
fuzz:
# Non-blocking by construction: nothing depends on this job and it is allowed to fail.
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 1500 # permits a 24h dispatch on a self-hosted runner; hosted runners stop at 6h
env:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
FUZZ_SANITIZERS: ${{ github.event.inputs.sanitizers || 'fuzzer' }}
FUZZ_DURATION: ${{ github.event.inputs.duration_seconds || '600' }}
steps:
- uses: actions/checkout@v4
- name: Install Ninja + Clang
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build clang
- name: Locate vcpkg
shell: bash
run: |
echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"
- name: Cache vcpkg artifacts
uses: actions/cache@v4
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: vcpkg-fuzz-${{ hashFiles('vcpkg.json') }}
restore-keys: vcpkg-fuzz-
- name: Pin vcpkg dependency baseline
shell: bash
run: |
if ! grep -q '"builtin-baseline"' vcpkg.json; then
"$VCPKG_ROOT/vcpkg" x-update-baseline --add-initial-baseline
fi
- name: Configure (fuzz-linux)
run: cmake --preset fuzz-linux -DPDFENGINE_FUZZ_SANITIZERS="$FUZZ_SANITIZERS"
- name: Build fuzzer
run: cmake --build --preset fuzz-linux --target pdfengine_fuzz
- name: Fetch corpus (pinned + hash-verified)
shell: bash
run: python3 scripts/fetch_corpus.py --manifest tests/regression/corpus-manifest.json || echo "corpus fetch failed (network) — continuing with committed corpus"
- name: Render-stability sweep (replay every corpus PDF once)
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
mkdir -p engine/fuzz/artifacts
"$BIN" -runs=0 -artifact_prefix=engine/fuzz/artifacts/ corpus/ corpus/fuzz/ || true
- name: Fuzz run
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
"$BIN" -max_total_time="$FUZZ_DURATION" -print_final_stats=1 \
-artifact_prefix=engine/fuzz/artifacts/ corpus/fuzz/ corpus/ || true
- name: Upload crash artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts
path: engine/fuzz/artifacts/
if-no-files-found: ignore
+1 -23
View File
@@ -22,11 +22,6 @@ CMakeUserPresets.json
/third_party/pdfium/install/
/third_party/pdfium/.gclient*
# Skia from-source build (depot_tools / GN / Ninja)
/third_party/skia/depot_tools/
/third_party/skia/checkout/
/third_party/skia/install/
# IDE / editor
/.vs/
/.vscode/
@@ -56,27 +51,10 @@ node_modules/
/frontend/.vite/
# WASM artifacts
*.wasm
*.wasm.map
# Logs / misc
*.log
# Compiled Python bindings (binary build outputs)
gateway/*.pyd
gateway/*.so
gateway/*.dylib
# Downloaded fuzzing corpus (large; fetched via scripts/fetch_corpus.py)
corpus/fuzz/
# Large-corpus regression baseline (derived from the gitignored corpus above)
tests/regression/baseline-large/
# Fuzzer working artifacts (crashes, leaks, coverage)
engine/fuzz/artifacts/
crash-*
leak-*
timeout-*
PDF Editor Timeline.xlsx
# Local environment config / secrets
/third_party/pdfium-wasm/
+27 -52
View File
@@ -1,5 +1,10 @@
cmake_minimum_required(VERSION 3.25)
# ---------------------------------------------------------------------------
# Dependency-pinning guard (Rule: pin all dependency versions on Day 1).
# We refuse to configure until vcpkg.json carries a 'builtin-baseline', which
# scripts/bootstrap adds via `vcpkg x-update-baseline --add-initial-baseline`.
# ---------------------------------------------------------------------------
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg.json" _pdfengine_vcpkg_json)
string(JSON _pdfengine_baseline ERROR_VARIABLE _pdfengine_baseline_err
GET "${_pdfengine_vcpkg_json}" "builtin-baseline")
@@ -19,67 +24,47 @@ project(PdfEngine
HOMEPAGE_URL "https://example.invalid/pdf-engine"
LANGUAGES CXX)
# ---------------------------------------------------------------------------
# Global C++ settings — C++23, no compiler extensions (engine blueprint §1.3).
# ---------------------------------------------------------------------------
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# compile_commands.json — consumed by clang-tidy and editors.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Single output dirs so test binaries find imported shared libs at runtime.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
if(EMSCRIPTEN)
set(PDFENGINE_WASM ON CACHE INTERNAL "Building for WebAssembly")
# Only the top-level project drives testing/install defaults.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed. Use a preset: "
"cmake --preset <platform>-debug")
endif()
# ---------------------------------------------------------------------------
# Build options.
# ---------------------------------------------------------------------------
option(PDFENGINE_BUILD_TESTS "Build engine unit/smoke tests" ON)
option(PDFENGINE_ENABLE_SANITIZERS "Build with AddressSanitizer/UBSan" OFF)
option(PDFENGINE_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF)
option(PDFENGINE_WITH_PDFIUM "Link the PDFium static lib (build it first)" OFF)
option(PDFENGINE_WITH_SKIA "Link the Skia static lib (build it first)" OFF)
option(PDFENGINE_WITH_QPDF "Link QPDF for content stream extraction" OFF)
option(PDFENGINE_FUZZING "Build libFuzzer harnesses (requires Clang)" OFF)
if(PDFENGINE_FUZZING)
# The fuzz harness instruments the engine; tests/bindings are not part of it.
set(PDFENGINE_BUILD_TESTS OFF CACHE BOOL "Build engine unit/smoke tests" FORCE)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR "PDFENGINE_FUZZING requires a Clang/clang-cl toolchain "
"(set in the 'fuzz-*' preset).")
endif()
endif()
if(PDFENGINE_WASM)
set(PDFENGINE_BUILD_TESTS OFF CACHE BOOL "Build engine unit/smoke tests" FORCE)
set(PDFENGINE_ENABLE_SANITIZERS OFF CACHE BOOL "ASan/UBSan" FORCE)
# PDFium under WASM is opt-in via the wasm-pdfium preset (links the prebuilt wasm32
# libpdfium.a). The plain 'wasm' preset leaves PDFENGINE_WITH_PDFIUM=OFF (mock engine).
endif()
include(CompilerWarnings)
include(Sanitizers)
include(pdfium) # defines pdfium::pdfium when PDFENGINE_WITH_PDFIUM is ON
if(PDFENGINE_WITH_PDFIUM)
include(pdfium) # defines pdfium::pdfium when PDFENGINE_WITH_PDFIUM is ON
endif()
if(PDFENGINE_WITH_SKIA)
include(skia) # defines skia::skia when PDFENGINE_WITH_SKIA is ON
endif()
# ---------------------------------------------------------------------------
# Third-party dependencies (resolved by vcpkg via the manifest).
# ---------------------------------------------------------------------------
find_package(freetype CONFIG REQUIRED)
find_package(harfbuzz CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
if(PDFENGINE_WITH_QPDF)
find_package(qpdf CONFIG REQUIRED)
endif()
if(WIN32 AND DEFINED VCPKG_TARGET_TRIPLET)
link_directories("${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib")
link_directories("${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib")
endif()
if(PDFENGINE_BUILD_TESTS)
find_package(GTest CONFIG REQUIRED)
@@ -87,22 +72,13 @@ if(PDFENGINE_BUILD_TESTS)
include(GoogleTest)
endif()
if(PDFENGINE_WASM)
message(STATUS "PdfEngine ${PROJECT_VERSION} — WASM configuration (Phase 1/2 Enabled)")
add_subdirectory(engine)
add_subdirectory(wasm)
else()
# Only the top-level project drives testing/install defaults.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed. Use a preset: "
"cmake --preset <platform>-debug")
endif()
# ---------------------------------------------------------------------------
# Subprojects.
# ---------------------------------------------------------------------------
add_subdirectory(engine)
add_subdirectory(engine)
if(NOT PDFENGINE_FUZZING)
add_subdirectory(bindings)
endif()
endif()
# bindings/, gateway/, frontend/, wasm/ are placeholders in Phase 0 and are not
# wired into the build yet.
message(STATUS "PdfEngine ${PROJECT_VERSION} configured")
message(STATUS " C++ standard ......... ${CMAKE_CXX_STANDARD}")
@@ -110,4 +86,3 @@ message(STATUS " Build tests .......... ${PDFENGINE_BUILD_TESTS}")
message(STATUS " Sanitizers ........... ${PDFENGINE_ENABLE_SANITIZERS}")
message(STATUS " Warnings as errors ... ${PDFENGINE_WARNINGS_AS_ERRORS}")
message(STATUS " Link PDFium .......... ${PDFENGINE_WITH_PDFIUM}")
message(STATUS " Link Skia ............ ${PDFENGINE_WITH_SKIA}")
+3 -41
View File
@@ -99,50 +99,15 @@
{
"name": "wasm",
"displayName": "WASM • Emscripten hello-world (Phase 0, Rule R5)",
"displayName": "WASM • Emscripten (Phase 0 stub — validation only)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"toolchainFile": "${sourceDir}/cmake/toolchains/vcpkg-wasm.cmake",
"toolchainFile": "${sourceDir}/cmake/toolchains/wasm.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"PDFENGINE_BUILD_TESTS": "OFF",
"PDFENGINE_WITH_PDFIUM": "OFF"
}
},
{
"name": "wasm-pdfium",
"displayName": "WASM • Emscripten + PDFium (live-preview engine)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"toolchainFile": "${sourceDir}/cmake/toolchains/vcpkg-wasm.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"PDFENGINE_BUILD_TESTS": "OFF",
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
},
{
"name": "fuzz-linux",
"displayName": "Linux • libFuzzer (Clang + ASan)",
"inherits": "base",
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" },
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"PDFENGINE_FUZZING": "ON",
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_FUZZ_SANITIZERS": "fuzzer,address,undefined"
}
},
{
"name": "fuzz-linux-nosan",
"displayName": "Linux • libFuzzer (Clang, coverage-only — for non-ASan PDFium)",
"inherits": "fuzz-linux",
"cacheVariables": { "PDFENGINE_FUZZ_SANITIZERS": "fuzzer" }
}
],
@@ -156,10 +121,7 @@
{ "name": "macos-debug", "configurePreset": "macos-debug" },
{ "name": "macos-release", "configurePreset": "macos-release" },
{ "name": "macos-asan", "configurePreset": "macos-asan" },
{ "name": "wasm", "configurePreset": "wasm" },
{ "name": "wasm-pdfium", "configurePreset": "wasm-pdfium" },
{ "name": "fuzz-linux", "configurePreset": "fuzz-linux" },
{ "name": "fuzz-linux-nosan", "configurePreset": "fuzz-linux-nosan" }
{ "name": "wasm", "configurePreset": "wasm" }
],
"testPresets": [
-34
View File
@@ -1,34 +0,0 @@
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
"configurePresets": [
{
"name": "win-local",
"displayName": "Windows • Debug (local — build dir outside OneDrive/spaces)",
"inherits": "windows-debug",
"binaryDir": "C:/Users/@USERNAME@/pdfeng-build/win-local",
"cacheVariables": {
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
},
{
"name": "win-local-pdfium",
"displayName": "Windows • RelWithDebInfo + PDFium (static CRT — build dir outside OneDrive/spaces)",
"inherits": "windows-release",
"binaryDir": "C:/Users/@USERNAME@/pdfeng-build/win-local-pdfium",
"cacheVariables": {
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
}
],
"buildPresets": [
{ "name": "win-local", "configurePreset": "win-local" },
{ "name": "win-local-pdfium", "configurePreset": "win-local-pdfium" }
],
"testPresets": [
{ "name": "win-local", "inherits": "common", "configurePreset": "win-local" },
{ "name": "win-local-pdfium", "inherits": "common", "configurePreset": "win-local-pdfium" }
]
}
-1
View File
@@ -1 +0,0 @@
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

-35
View File
@@ -1,35 +0,0 @@
# Python bindings for PdfEngine using pybind11
#
find_package(pybind11 CONFIG REQUIRED)
# Declare the python module target. We name the target pdfengine_py to avoid
# target collision with the static C++ library pdfengine, but we set the
# OUTPUT_NAME to pdfengine to produce the correct importable module.
pybind11_add_module(pdfengine_py python/pdfengine_py.cpp $<TARGET_OBJECTS:pdfengine>)
set_target_properties(pdfengine_py PROPERTIES
OUTPUT_NAME "pdfengine"
ARCHIVE_OUTPUT_NAME "pdfengine_py_import"
)
target_link_libraries(pdfengine_py PRIVATE pdfengine::pdfengine)
target_include_directories(pdfengine_py PRIVATE
"${CMAKE_SOURCE_DIR}/engine/src"
)
if(MSVC)
target_link_options(pdfengine_py PRIVATE "/FORCE:MULTIPLE")
endif()
# Set warnings and sanitizers for the bindings module
pdfengine_set_warnings(pdfengine_py)
pdfengine_enable_sanitizers(pdfengine_py)
# Copy the compiled .pyd (or .so) file to the gateway/ directory so the
# Python FastAPI app and its tests can import it immediately after building.
add_custom_command(TARGET pdfengine_py POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pdfengine_py> "${CMAKE_SOURCE_DIR}/gateway/"
COMMENT "Copying compiled Python extension to gateway/ directory"
)
-534
View File
@@ -1,534 +0,0 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pdfengine/pdf_document.hpp>
#include <pdfengine/pdf_engine.hpp>
namespace py = pybind11;
namespace {
void throw_on_error(pdfengine::EngineError err) {
switch (err) {
case pdfengine::EngineError::FileNotFound:
PyErr_SetString(PyExc_FileNotFoundError, "PDF file not found");
throw py::error_already_set();
case pdfengine::EngineError::InvalidFormat:
throw py::value_error("Invalid PDF format");
case pdfengine::EngineError::PasswordRequired:
throw py::value_error("Password required to open this PDF");
case pdfengine::EngineError::InvalidPassword:
throw py::value_error("Invalid password provided for this PDF");
case pdfengine::EngineError::PageOutOfBounds:
throw py::index_error("Page index out of bounds");
case pdfengine::EngineError::RenderFailed:
throw std::runtime_error("Failed to render PDF page");
case pdfengine::EngineError::WriteFailed:
throw std::runtime_error("Failed to write PDF data");
default:
throw std::runtime_error("Unknown PDF engine error");
}
}
template<typename T>
T get_or_throw(std::expected<T, pdfengine::EngineError>&& res) {
if (!res.has_value()) {
throw_on_error(res.error());
}
return std::move(res.value());
}
void get_or_throw(std::expected<void, pdfengine::EngineError>&& res) {
if (!res.has_value()) {
throw_on_error(res.error());
}
}
}
#include <pdfengine/content_object.hpp>
#include <qpdf/qpdf_extractor.hpp>
#include <qpdf/qpdf_writer.hpp>
#include <parser/lexer.hpp>
#include <parser/parser.hpp>
#include <parser/content_builder.hpp>
#include <serializer/content_serializer.hpp>
#include <serializer/ast_serializer.hpp>
static constexpr double kTjSpaceKern = -500.0;
class StreamEditor {
public:
StreamEditor(const std::string& filepath) : filepath_(filepath) {}
py::list extract_text_objects(int page_index) {
pdfengine::qpdf_layer::QpdfExtractor extractor;
auto stream = extractor.extractPageStream(filepath_, page_index);
if (!stream.has_value()) {
throw std::runtime_error("Failed to extract page stream");
}
pdfengine::Lexer lexer(stream->decodedContent);
auto tokens = lexer.tokenize();
pdfengine::ContentParser parser(tokens);
pdfengine::ContentBuilder builder;
auto objects = builder.build(parser.parse());
py::list result;
for (const auto& obj : objects) {
if (obj->getType() == pdfengine::ContentObjectType::Text) {
auto* textObj = static_cast<pdfengine::TextObject*>(obj.get());
py::dict d;
d["text"] = py::bytes(textObj->text);
d["fontName"] = textObj->fontName;
d["fontSize"] = textObj->fontSize;
py::list tm;
for (int i = 0; i < 6; ++i) {
tm.append(textObj->tm[i]);
}
d["tm"] = tm;
result.append(d);
}
}
return result;
}
bool replace_text_object(int page_index, int object_index, const py::bytes& new_text_bytes, const std::string& dest_path) {
std::string new_text = new_text_bytes;
pdfengine::qpdf_layer::QpdfExtractor extractor;
auto stream = extractor.extractPageStream(filepath_, page_index);
if (!stream.has_value()) return false;
pdfengine::Lexer lexer(stream->decodedContent);
auto tokens = lexer.tokenize();
pdfengine::ContentParser parser(tokens);
auto operations = parser.parse();
int textCount = 0;
bool modified = false;
for (auto& op : operations) {
if (op.op == "Tj" || op.op == "'") {
if (op.operands.empty()) continue;
auto& strNode = op.operands.back();
if (strNode->type == pdfengine::AstNodeType::String || strNode->type == pdfengine::AstNodeType::HexString) {
if (textCount == object_index) {
strNode->type = pdfengine::AstNodeType::String;
strNode->stringValue = new_text;
modified = true;
break;
}
textCount++;
}
} else if (op.op == "TJ") {
if (op.operands.empty()) continue;
auto& arrNode = op.operands.back();
if (arrNode->type == pdfengine::AstNodeType::Array) {
std::string combinedText;
for (const auto& item : arrNode->arrayItems) {
if (item->type == pdfengine::AstNodeType::String) {
combinedText += item->stringValue;
} else if (item->type == pdfengine::AstNodeType::HexString) {
combinedText += std::string(item->bytesValue.begin(), item->bytesValue.end());
} else if (item->type == pdfengine::AstNodeType::Number) {
if (item->numberValue < kTjSpaceKern) combinedText += " ";
}
}
if (!combinedText.empty()) {
if (textCount == object_index) {
bool redistributed = false;
if (new_text.size() == combinedText.size()) {
std::vector<std::pair<pdfengine::AstNode*, std::string>> assign;
size_t pos = 0; bool ok = true;
for (const auto& item : arrNode->arrayItems) {
if (item->type == pdfengine::AstNodeType::String ||
item->type == pdfengine::AstNodeType::HexString) {
size_t L = (item->type == pdfengine::AstNodeType::HexString)
? item->bytesValue.size() : item->stringValue.size();
assign.emplace_back(item.get(), new_text.substr(pos, L));
pos += L;
} else if (item->type == pdfengine::AstNodeType::Number &&
item->numberValue < kTjSpaceKern) {
if (pos >= new_text.size() || new_text[pos] != ' ') { ok = false; break; }
pos += 1;
}
}
if (ok && pos == new_text.size()) {
for (auto& [node, content] : assign) {
node->type = pdfengine::AstNodeType::String;
node->stringValue = content;
}
redistributed = true;
}
}
if (!redistributed) {
arrNode->arrayItems.clear();
auto newStrNode = std::make_shared<pdfengine::AstNode>(pdfengine::AstNodeType::String);
newStrNode->stringValue = new_text;
arrNode->arrayItems.push_back(std::move(newStrNode));
}
modified = true;
break;
}
textCount++;
}
}
}
}
if (!modified) return false;
pdfengine::AstSerializer astSerializer;
std::string newRawStream = astSerializer.serialize(operations);
pdfengine::qpdf_layer::QpdfWriter writer;
auto res = writer.replacePageStreamAndSave(filepath_, dest_path, page_index, newRawStream);
return res.has_value();
}
private:
std::string filepath_;
};
PYBIND11_MODULE(pdfengine, m) {
m.doc() = "Python bindings for the PdfEngine C++ Core SDK";
py::class_<StreamEditor>(m, "StreamEditor")
.def(py::init<const std::string&>(), py::arg("filepath"))
.def("extract_text_objects", &StreamEditor::extract_text_objects, py::arg("page_index"))
.def("replace_text_object", &StreamEditor::replace_text_object, py::arg("page_index"), py::arg("object_index"), py::arg("new_text"), py::arg("dest_path"));
m.def("engine_version", &pdfengine::engineVersion, "Get the engine version string");
m.def("engine_build_info", &pdfengine::engineBuildInfo, "Get the engine build info string");
m.def("engine_has_pdfium", &pdfengine::engineHasPdfium, "Check if the engine was built with PDFium support");
m.def("engine_has_skia", &pdfengine::engineHasSkia, "Check if the engine was built with Skia support");
py::class_<pdfengine::Point2D>(m, "Point2D")
.def(py::init<double, double>(), py::arg("x") = 0.0, py::arg("y") = 0.0)
.def_readwrite("x", &pdfengine::Point2D::x)
.def_readwrite("y", &pdfengine::Point2D::y)
.def("__repr__", [](const pdfengine::Point2D& self) {
return "Point2D(x=" + std::to_string(self.x) + ", y=" + std::to_string(self.y) + ")";
});
py::class_<pdfengine::DevicePoint>(m, "DevicePoint")
.def(py::init<int, int>(), py::arg("x") = 0, py::arg("y") = 0)
.def_readwrite("x", &pdfengine::DevicePoint::x)
.def_readwrite("y", &pdfengine::DevicePoint::y)
.def("__repr__", [](const pdfengine::DevicePoint& self) {
return "DevicePoint(x=" + std::to_string(self.x) + ", y=" + std::to_string(self.y) + ")";
});
py::class_<pdfengine::DocumentMetadata>(m, "DocumentMetadata")
.def_readonly("title", &pdfengine::DocumentMetadata::title)
.def_readonly("author", &pdfengine::DocumentMetadata::author)
.def_readonly("creator", &pdfengine::DocumentMetadata::creator)
.def_readonly("producer", &pdfengine::DocumentMetadata::producer)
.def_readonly("creation_date", &pdfengine::DocumentMetadata::creationDate)
.def_readonly("modification_date", &pdfengine::DocumentMetadata::modificationDate)
.def("__repr__", [](const pdfengine::DocumentMetadata& self) {
return "DocumentMetadata(title='" + self.title + "', author='" + self.author + "')";
});
py::class_<pdfengine::DocumentPermissions>(m, "DocumentPermissions")
.def_readonly("is_encrypted", &pdfengine::DocumentPermissions::isEncrypted)
.def_readonly("encryption", &pdfengine::DocumentPermissions::encryption)
.def_readonly("security_revision", &pdfengine::DocumentPermissions::securityRevision)
.def_readonly("owner_unlocked", &pdfengine::DocumentPermissions::ownerUnlocked)
.def_readonly("can_print", &pdfengine::DocumentPermissions::canPrint)
.def_readonly("can_print_high_res", &pdfengine::DocumentPermissions::canPrintHighRes)
.def_readonly("can_modify", &pdfengine::DocumentPermissions::canModify)
.def_readonly("can_copy", &pdfengine::DocumentPermissions::canCopy)
.def_readonly("can_annotate", &pdfengine::DocumentPermissions::canAnnotate)
.def_readonly("can_fill_forms", &pdfengine::DocumentPermissions::canFillForms)
.def_readonly("can_extract_for_accessibility", &pdfengine::DocumentPermissions::canExtractForAccessibility)
.def_readonly("can_assemble", &pdfengine::DocumentPermissions::canAssemble);
py::class_<pdfengine::PageImage>(m, "PageImage")
.def_readonly("width", &pdfengine::PageImage::width)
.def_readonly("height", &pdfengine::PageImage::height)
.def_property_readonly("data", [](const pdfengine::PageImage& self) {
return py::bytes(reinterpret_cast<const char*>(self.data.data()), self.data.size());
});
py::class_<pdfengine::FontInfo>(m, "FontInfo")
.def_readonly("font_name", &pdfengine::FontInfo::fontName)
.def_readonly("type", &pdfengine::FontInfo::type)
.def_readonly("is_embedded", &pdfengine::FontInfo::isEmbedded)
.def_readonly("is_subset", &pdfengine::FontInfo::isSubset)
.def_readonly("is_vertical", &pdfengine::FontInfo::isVertical)
.def_readonly("encoding", &pdfengine::FontInfo::encoding)
.def_readonly("has_to_unicode", &pdfengine::FontInfo::hasToUnicode)
.def_readonly("cmap_name", &pdfengine::FontInfo::cmapName)
.def_readonly("cid_system_info", &pdfengine::FontInfo::cidSystemInfo)
.def_readonly("subset_tag", &pdfengine::FontInfo::subsetTag)
.def_readonly("source_type", &pdfengine::FontInfo::sourceType)
.def_readonly("substituted_from", &pdfengine::FontInfo::substitutedFrom)
.def_readonly("substituted_to", &pdfengine::FontInfo::substitutedTo)
.def_readonly("normalized_family", &pdfengine::FontInfo::normalizedFamily)
.def_readonly("internal_font_id", &pdfengine::FontInfo::internalFontId)
.def_readonly("flags", &pdfengine::FontInfo::flags)
.def_readonly("ascent", &pdfengine::FontInfo::ascent)
.def_readonly("descent", &pdfengine::FontInfo::descent)
.def_readonly("cap_height", &pdfengine::FontInfo::capHeight)
.def("__repr__", [](const pdfengine::FontInfo& self) {
return "FontInfo(font_name='" + self.fontName + "', type='" + self.type + "', is_embedded=" + (self.isEmbedded ? "True" : "False") + ")";
});
py::class_<pdfengine::Glyph>(m, "Glyph")
.def_readonly("text", &pdfengine::Glyph::text)
.def_readonly("unicode", &pdfengine::Glyph::unicode)
.def_readonly("font_name", &pdfengine::Glyph::fontName)
.def_readonly("flags", &pdfengine::Glyph::flags)
.def_readonly("font_size", &pdfengine::Glyph::fontSize)
.def_readonly("origin_x", &pdfengine::Glyph::originX)
.def_readonly("origin_y", &pdfengine::Glyph::originY)
.def_readonly("bbox_x", &pdfengine::Glyph::bboxX)
.def_readonly("bbox_y", &pdfengine::Glyph::bboxY)
.def_readonly("bbox_w", &pdfengine::Glyph::bboxW)
.def_readonly("bbox_h", &pdfengine::Glyph::bboxH)
.def_readonly("angle", &pdfengine::Glyph::angle)
.def_readonly("page_object_index", &pdfengine::Glyph::pageObjectIndex);
py::class_<pdfengine::TextRun>(m, "TextRun")
.def_readonly("text", &pdfengine::TextRun::text)
.def_readonly("font_name", &pdfengine::TextRun::fontName)
.def_readonly("flags", &pdfengine::TextRun::flags)
.def_readonly("font_size", &pdfengine::TextRun::fontSize)
.def_readonly("internal_font_id", &pdfengine::TextRun::internalFontId)
.def_readonly("is_embedded", &pdfengine::TextRun::isEmbedded)
.def_readonly("type", &pdfengine::TextRun::type)
.def_readonly("glyphs", &pdfengine::TextRun::glyphs)
.def_readonly("x", &pdfengine::TextRun::x)
.def_readonly("y", &pdfengine::TextRun::y)
.def_readonly("w", &pdfengine::TextRun::w)
.def_readonly("h", &pdfengine::TextRun::h)
.def_readonly("object_indices", &pdfengine::TextRun::objectIndices)
.def_readonly("fill_color", &pdfengine::TextRun::fillColor)
.def_readonly("para_id", &pdfengine::TextRun::paraId)
.def_readonly("font_fidelity", &pdfengine::TextRun::fontFidelity);
py::class_<pdfengine::TextLine>(m, "TextLine")
.def_readonly("runs", &pdfengine::TextLine::runs)
.def_readonly("baseline_y", &pdfengine::TextLine::baselineY)
.def_readonly("x", &pdfengine::TextLine::x)
.def_readonly("y", &pdfengine::TextLine::y)
.def_readonly("w", &pdfengine::TextLine::w)
.def_readonly("h", &pdfengine::TextLine::h);
py::class_<pdfengine::Paragraph>(m, "Paragraph")
.def_readonly("lines", &pdfengine::Paragraph::lines)
.def_readonly("x", &pdfengine::Paragraph::x)
.def_readonly("y", &pdfengine::Paragraph::y)
.def_readonly("w", &pdfengine::Paragraph::w)
.def_readonly("h", &pdfengine::Paragraph::h);
py::class_<pdfengine::PageModel>(m, "PageModel")
.def_readonly("paragraphs", &pdfengine::PageModel::paragraphs)
.def_readonly("width", &pdfengine::PageModel::width)
.def_readonly("height", &pdfengine::PageModel::height)
.def_readonly("page_index", &pdfengine::PageModel::pageIndex);
py::class_<pdfengine::PdfPage::AnnotationInfo>(m, "AnnotationInfo")
.def_readonly("id", &pdfengine::PdfPage::AnnotationInfo::id)
.def_readonly("type", &pdfengine::PdfPage::AnnotationInfo::type)
.def_readonly("x", &pdfengine::PdfPage::AnnotationInfo::x)
.def_readonly("y", &pdfengine::PdfPage::AnnotationInfo::y)
.def_readonly("width", &pdfengine::PdfPage::AnnotationInfo::width)
.def_readonly("height", &pdfengine::PdfPage::AnnotationInfo::height)
.def_readonly("color", &pdfengine::PdfPage::AnnotationInfo::color)
.def_readonly("author", &pdfengine::PdfPage::AnnotationInfo::author)
.def_readonly("content", &pdfengine::PdfPage::AnnotationInfo::content)
.def_readonly("timestamp", &pdfengine::PdfPage::AnnotationInfo::timestamp)
.def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex)
.def_readonly("thickness", &pdfengine::PdfPage::AnnotationInfo::thickness)
.def_readonly("paths", &pdfengine::PdfPage::AnnotationInfo::paths)
.def_readonly("field_name", &pdfengine::PdfPage::AnnotationInfo::fieldName)
.def_readonly("field_value", &pdfengine::PdfPage::AnnotationInfo::fieldValue)
.def_readonly("field_type", &pdfengine::PdfPage::AnnotationInfo::fieldType)
.def_readonly("field_flags", &pdfengine::PdfPage::AnnotationInfo::fieldFlags)
.def_readonly("field_options", &pdfengine::PdfPage::AnnotationInfo::fieldOptions)
.def_property_readonly("quad_points", [](const pdfengine::PdfPage::AnnotationInfo& self) {
py::list out;
for (const auto& quad : self.quadPoints) {
py::list quad_list;
for (const auto& pt : quad) {
py::dict d;
d["x"] = pt.x;
d["y"] = pt.y;
quad_list.append(d);
}
out.append(quad_list);
}
return out;
});
py::class_<pdfengine::PdfPage, std::shared_ptr<pdfengine::PdfPage>>(m, "PdfPage")
.def_property_readonly("width", &pdfengine::PdfPage::width)
.def_property_readonly("height", &pdfengine::PdfPage::height)
.def("render", [](const pdfengine::PdfPage& self, int dpi) {
return get_or_throw(self.render(dpi));
}, py::arg("dpi") = 96)
.def("render_region_raw", [](const pdfengine::PdfPage& self, int dpi, double y_top_pt, double height_pt) {
auto img = get_or_throw(self.renderRegionRaw(dpi, y_top_pt, height_pt));
return py::make_tuple(img.width, img.height,
py::bytes(reinterpret_cast<const char*>(img.data.data()), img.data.size()));
}, py::arg("dpi"), py::arg("y_top_pt"), py::arg("height_pt") = 0.0)
.def("render_tile", [](const pdfengine::PdfPage& self, int dpi, double xPt, double yPt, double wPt, double hPt) {
auto img = get_or_throw(self.renderTile(dpi, xPt, yPt, wPt, hPt));
return py::make_tuple(img.width, img.height,
py::bytes(reinterpret_cast<const char*>(img.data.data()), img.data.size()));
}, py::arg("dpi"), py::arg("xPt"), py::arg("yPt"), py::arg("wPt"), py::arg("hPt"))
.def("extract_document_model", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractDocumentModel());
})
.def("extract_text", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractText());
})
.def("extract_annotations_text", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractAnnotationsText());
})
.def("extract_annotations", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractAnnotations());
})
.def("extract_text_with_bounds", [](const pdfengine::PdfPage& self) {
auto res = get_or_throw(self.extractTextWithBounds());
py::list py_list;
for (const auto& glyph : res) {
py::dict d;
d["text"] = glyph.text;
d["x"] = glyph.x;
d["y"] = glyph.y;
d["w"] = glyph.w;
d["h"] = glyph.h;
d["fontSize"] = glyph.fontSize;
py_list.append(d);
}
return py_list;
})
.def("ordered_glyphs", [](const pdfengine::PdfPage& self) {
auto res = get_or_throw(self.orderedGlyphs());
py::list py_list;
for (const auto& g : res) {
py::dict d;
d["text"] = g.text; d["x"] = g.x; d["y"] = g.y;
d["w"] = g.w; d["h"] = g.h; d["fontSize"] = g.fontSize;
py_list.append(d);
}
return py_list;
})
.def("hit_glyph", [](const pdfengine::PdfPage& self, double x, double y) {
auto hit = get_or_throw(self.hitGlyph(x, y));
py::dict d;
d["glyphIndex"] = hit.glyphIndex;
d["caret"] = hit.caret;
d["line"] = hit.line;
return d;
}, py::arg("x"), py::arg("y"))
.def("select_range", [](const pdfengine::PdfPage& self, double ax, double ay, double bx, double by) {
auto sel = get_or_throw(self.selectRange(ax, ay, bx, by));
py::dict d;
d["startGlyph"] = sel.startGlyph;
d["endGlyph"] = sel.endGlyph;
d["text"] = sel.text;
py::list rects;
for (const auto& r : sel.rects) {
py::dict rd;
rd["x"] = r.x; rd["y"] = r.y; rd["w"] = r.w; rd["h"] = r.h;
rects.append(rd);
}
d["rects"] = rects;
return d;
}, py::arg("ax"), py::arg("ay"), py::arg("bx"), py::arg("by"))
.def("get_fonts", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.getFonts());
})
.def("get_glyph_width", [](const pdfengine::PdfPage& self, const std::string& fontName, uint32_t charcode, double fontSize) {
return get_or_throw(self.getGlyphWidth(fontName, charcode, fontSize));
}, py::arg("font_name"), py::arg("charcode"), py::arg("font_size"))
.def("page_to_device", &pdfengine::PdfPage::pageToDevice,
py::arg("page_point"), py::arg("device_width"), py::arg("device_height"), py::arg("rotate") = 0)
.def("device_to_page", &pdfengine::PdfPage::deviceToPage,
py::arg("device_point"), py::arg("device_width"), py::arg("device_height"), py::arg("rotate") = 0)
.def("extract_display_list", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractDisplayListJson());
})
.def("extract_image_xobject", [](const pdfengine::PdfPage& self, const std::string& name) {
auto res = get_or_throw(self.extractImageXObject(name));
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
}, py::arg("name"));
py::class_<pdfengine::PdfDocument, std::shared_ptr<pdfengine::PdfDocument>>(m, "PdfDocument")
.def_static("load_from_file", [](const std::string& path, const std::string& password) {
return get_or_throw(pdfengine::PdfDocument::loadFromFile(path, password));
}, py::arg("path"), py::arg("password") = "")
.def_static("load_from_memory", [](const py::bytes& bytes, const std::string& password) {
std::string_view sv = bytes;
std::vector<uint8_t> data(sv.begin(), sv.end());
return get_or_throw(pdfengine::PdfDocument::loadFromMemory(data, password));
}, py::arg("data"), py::arg("password") = "")
.def_property_readonly("page_count", &pdfengine::PdfDocument::pageCount)
.def_property_readonly("metadata", &pdfengine::PdfDocument::metadata)
.def_property_readonly("permissions", &pdfengine::PdfDocument::permissions)
.def("extract_outline", [](const pdfengine::PdfDocument& self) {
auto res = get_or_throw(self.extractOutline());
py::list out;
for (const auto& item : res) {
py::dict d;
d["title"] = item.title;
d["pageIndex"] = item.pageIndex;
d["level"] = item.level;
out.append(d);
}
return out;
})
.def("get_page", [](pdfengine::PdfDocument& self, int pageIndex) {
return get_or_throw(self.getPage(pageIndex));
}, py::arg("page_index"))
.def("get_fonts", [](const pdfengine::PdfDocument& self, int start_page, int end_page) {
return get_or_throw(self.getFonts(start_page, end_page));
}, py::arg("start_page") = 0, py::arg("end_page") = -1)
.def("get_font_data", [](const pdfengine::PdfDocument& self, const std::string& internal_font_id) {
auto res = self.getFontData(internal_font_id);
if (!res || res->empty()) {
return py::bytes();
}
return py::bytes(reinterpret_cast<const char*>(res->data()), res->size());
}, py::arg("internal_font_id"))
.def("get_reconstructed_font_data", [](pdfengine::PdfDocument& self, const std::string& internal_font_id) {
auto res = self.getReconstructedFontData(internal_font_id);
if (!res || res->empty()) {
return py::bytes();
}
return py::bytes(reinterpret_cast<const char*>(res->data()), res->size());
}, py::arg("internal_font_id"))
.def("apply_edits", [](pdfengine::PdfDocument& self, const std::string& editsJson) {
auto regions = get_or_throw(self.applyEdits(editsJson));
py::list py_regions;
for (const auto& r : regions) {
py::dict d;
d["pageIndex"] = r.pageIndex;
d["x"] = r.x;
d["y"] = r.y;
d["width"] = r.width;
d["height"] = r.height;
py_regions.append(d);
}
return py_regions;
}, py::arg("edits_json"))
.def("last_reflow_layout", [](const pdfengine::PdfDocument& self) {
return self.lastReflowLayout();
})
.def("save_incremental", [](const pdfengine::PdfDocument& self) {
std::vector<uint8_t> res = get_or_throw(self.saveIncremental());
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
})
.def("save_full", [](const pdfengine::PdfDocument& self) {
std::vector<uint8_t> res = get_or_throw(self.saveFull());
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
})
.def("save_full_for_export", [](const pdfengine::PdfDocument& self) {
std::vector<uint8_t> res = get_or_throw(self.saveFullForExport());
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
});
}
+7 -1
View File
@@ -1,6 +1,12 @@
# AddressSanitizer / UndefinedBehaviorSanitizer wiring.
# Enabled per build via -DPDFENGINE_ENABLE_SANITIZERS=ON
# Enabled per build via -DPDFENGINE_ENABLE_SANITIZERS=ON (see the *-asan presets).
# Usage: pdfengine_enable_sanitizers(<target>)
#
# Coverage by platform:
# MSVC -> ASan only (/fsanitize=address). UBSan/TSan unsupported.
# GCC/Clang -> ASan + UBSan.
# TSan is intentionally not wired here yet; it conflicts with ASan and is only
# relevant once the engine is multithreaded (Phase 3+).
function(pdfengine_enable_sanitizers target)
if(NOT PDFENGINE_ENABLE_SANITIZERS)
+29 -33
View File
@@ -1,5 +1,21 @@
# PDFium integration.
# Creates imported target pdfium::pdfium from third_party/pdfium/install/.
#
# PDFium is NOT a vcpkg package — it is built from source via depot_tools/GN/Ninja
# (see third_party/pdfium/). That build installs into:
#
# third_party/pdfium/install/
# include/ public PDFium headers (fpdfview.h, fpdf_*.h, ...)
# lib/ the static library (pdfium.lib / libpdfium.a)
#
# This module turns that install tree into an imported target: pdfium::pdfium
#
# Behaviour:
# PDFENGINE_WITH_PDFIUM = OFF (default)
# No target is created. The engine compiles with PDFium code paths
# #ifdef-ed out. This keeps Phase 0 unblocked before PDFium is built.
# PDFENGINE_WITH_PDFIUM = ON
# The install tree MUST exist; otherwise this is a hard error directing
# the developer to the build script.
if(NOT PDFENGINE_WITH_PDFIUM)
message(STATUS "PDFium: disabled (PDFENGINE_WITH_PDFIUM=OFF). "
@@ -7,39 +23,18 @@ if(NOT PDFENGINE_WITH_PDFIUM)
return()
endif()
# Desktop uses the GN-built tree; WASM uses the prebuilt Emscripten static lib
# (third_party/pdfium-wasm, fetched by get_pdfium_wasm.ps1 — a relinkable libpdfium.a).
if(EMSCRIPTEN)
set(PDFIUM_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/pdfium-wasm"
CACHE PATH "Root of the wasm32 PDFium tree (prebuilt libpdfium.a + include)")
else()
set(PDFIUM_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/pdfium/install"
CACHE PATH "Root of the PDFium install tree produced by build_pdfium.*")
endif()
set(PDFIUM_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/pdfium/install"
CACHE PATH "Root of the PDFium install tree produced by build_pdfium.*")
if(EMSCRIPTEN)
# Emscripten's toolchain sets CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY, which makes
# find_library ignore custom PATHS (it only searches the emsdk sysroot). Set the
# prebuilt wasm32 lib + headers directly instead.
set(PDFIUM_INCLUDE_DIR "${PDFIUM_INSTALL_DIR}/include")
set(PDFIUM_LIBRARY "${PDFIUM_INSTALL_DIR}/lib/libpdfium.a")
if(NOT EXISTS "${PDFIUM_INCLUDE_DIR}/fpdfview.h")
set(PDFIUM_INCLUDE_DIR "")
endif()
if(NOT EXISTS "${PDFIUM_LIBRARY}")
set(PDFIUM_LIBRARY "")
endif()
else()
find_path(PDFIUM_INCLUDE_DIR
NAMES fpdfview.h
PATHS "${PDFIUM_INSTALL_DIR}/include"
NO_DEFAULT_PATH)
find_path(PDFIUM_INCLUDE_DIR
NAMES fpdfview.h
PATHS "${PDFIUM_INSTALL_DIR}/include"
NO_DEFAULT_PATH)
find_library(PDFIUM_LIBRARY
NAMES pdfium libpdfium
PATHS "${PDFIUM_INSTALL_DIR}/lib"
NO_DEFAULT_PATH)
endif()
find_library(PDFIUM_LIBRARY
NAMES pdfium libpdfium
PATHS "${PDFIUM_INSTALL_DIR}/lib"
NO_DEFAULT_PATH)
if(NOT PDFIUM_INCLUDE_DIR OR NOT PDFIUM_LIBRARY)
message(FATAL_ERROR
@@ -56,7 +51,8 @@ set_target_properties(pdfium::pdfium PROPERTIES
IMPORTED_LOCATION "${PDFIUM_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${PDFIUM_INCLUDE_DIR}")
# PDFium is a C++ static lib; consumers on Linux also need the C++ runtime and
# pthreads. These are no-ops where irrelevant.
if(UNIX AND NOT APPLE)
set_property(TARGET pdfium::pdfium APPEND PROPERTY
INTERFACE_LINK_LIBRARIES pthread dl)
-45
View File
@@ -1,45 +0,0 @@
# Skia integration.
# Creates imported target skia::skia from third_party/skia/install/.
if(NOT PDFENGINE_WITH_SKIA)
message(STATUS "Skia: disabled (PDFENGINE_WITH_SKIA=OFF). "
"Engine builds without raw Skia linkage.")
return()
endif()
set(SKIA_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/skia/install"
CACHE PATH "Root of the Skia install tree produced by build_skia.*")
find_path(SKIA_INCLUDE_DIR
NAMES include/core/SkCanvas.h
PATHS "${SKIA_INSTALL_DIR}"
NO_DEFAULT_PATH)
find_library(SKIA_LIBRARY
NAMES skia libskia
PATHS "${SKIA_INSTALL_DIR}/lib"
NO_DEFAULT_PATH)
if(NOT SKIA_INCLUDE_DIR OR NOT SKIA_LIBRARY)
message(FATAL_ERROR
"PDFENGINE_WITH_SKIA=ON but no Skia install tree was found under:\n"
" ${SKIA_INSTALL_DIR}\n"
"Build Skia first (one-time, slow):\n"
" Windows: pwsh third_party/skia/build_skia.ps1\n"
" Unix: ./third_party/skia/build_skia.sh\n"
"See third_party/skia/README.md.")
endif()
add_library(skia::skia STATIC IMPORTED GLOBAL)
set_target_properties(skia::skia PROPERTIES
IMPORTED_LOCATION "${SKIA_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SKIA_INCLUDE_DIR}")
if(UNIX AND NOT APPLE)
set_property(TARGET skia::skia APPEND PROPERTY
INTERFACE_LINK_LIBRARIES pthread dl)
endif()
message(STATUS "Skia: found")
message(STATUS " include .. ${SKIA_INCLUDE_DIR}/include")
message(STATUS " library .. ${SKIA_LIBRARY}")
-27
View File
@@ -1,27 +0,0 @@
# WebAssembly vcpkg + Emscripten toolchain loader
# Chain-loads the vcpkg toolchain which then chain-loads the Emscripten compiler toolchain.
if(NOT DEFINED ENV{EMSDK})
message(FATAL_ERROR "EMSDK environment variable not set. Please activate Emscripten SDK first.")
endif()
if(NOT DEFINED ENV{VCPKG_ROOT})
message(FATAL_ERROR "VCPKG_ROOT environment variable not set. Please set VCPKG_ROOT first.")
endif()
set(VCPKG_TARGET_TRIPLET "wasm32-emscripten" CACHE STRING "vcpkg WASM triplet")
# Emscripten toolchain path
set(EMSDK_TOOLCHAIN "$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
if(NOT EXISTS "${EMSDK_TOOLCHAIN}")
message(FATAL_ERROR "Emscripten toolchain not found at: ${EMSDK_TOOLCHAIN}")
endif()
# Instruct vcpkg to chain-load the Emscripten toolchain file
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${EMSDK_TOOLCHAIN}" CACHE STRING "vcpkg chainload toolchain")
# Include the main vcpkg toolchain
include("$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
# Allow heap memory growth
set(CMAKE_EXE_LINKER_FLAGS_INIT "-sALLOW_MEMORY_GROWTH=1")
+8 -5
View File
@@ -1,9 +1,12 @@
# WebAssembly (Emscripten) toolchain hook.
# WebAssembly (Emscripten) toolchain hook — STUB for Phase 0.
#
# Used by the `wasm` configure preset to chain-load the real Emscripten
# toolchain from the active EMSDK. Phase 0 builds only the hello-world target
# in wasm/ (Rule R5 — never blocks shipping). Phase 2 will compile the full
# engine (PDFium + Skia + FreeType + HarfBuzz) through this same toolchain.
# The Phase 0 "WASM hello-world" task is validation-only and must NOT block
# Phase 1 (Rule R5). This file exists so the integration point is real; the
# full WASM build (PDFium + Skia + FreeType + HarfBuzz compiled together) is
# Phase 2 work.
#
# It chain-loads the real Emscripten toolchain from the active EMSDK, then lets
# vcpkg layer on top. Used by the `wasm` configure preset.
if(NOT DEFINED ENV{EMSDK})
message(FATAL_ERROR
-125
View File
@@ -1,125 +0,0 @@
# 🛠️ PDF Engine — Developer Command Cheat Sheet
This document serves as the single source of truth for commands across our developer roles.
---
## 🗺️ Developer Matrix & Focus Areas
| Role | Focus Area | Code Paths | Primary Responsibilities |
| :--- | :--- | :--- | :--- |
| **Dev 1 — Parser & SDK** | Core PDF parser, C++ wrappers, and FastAPI integration | `engine/src/parser/`<br>`gateway/` | PDFium abstraction layers, Rule R2 compliance, Python bindings, FastAPI gateway |
| **Dev 2 — Graphics & Render** | Frontend React viewer and browser WebAssembly layers | `frontend/`<br>`wasm/` | Rendering facade, Emscripten build pipeline, annotation tools, React UI components |
| **Dev 3 — Fonts & Text** | Text shaping, font embedding, and subsetting | `engine/src/fonts/`<br>`engine/src/text/` | FreeType & HarfBuzz wrappers, font subsetting, text extraction layers |
---
## ⚡ Quick Reference Command Matrix
| Task / Goal | Dev 1: Parser & SDK | Dev 2: Graphics & Render | Dev 3: Fonts & Text |
| :--- | :--- | :--- | :--- |
| **1. One-Time Setup** | `powershell scripts/bootstrap.ps1`<br>*(Setup Python env in `gateway/`)* | `powershell scripts/bootstrap.ps1`<br>*(Setup Node in `frontend/`)* | `powershell scripts/bootstrap.ps1`<br>*(Setup Python env in `gateway/`)* |
| **2. Build C++ Engine** | `powershell scripts/build_cpp.ps1` | `powershell scripts/build_wasm.ps1` *(WASM)* | `powershell scripts/build_cpp.ps1` |
| **3. Run Unit Tests** | `powershell scripts/test_cpp.ps1` | `node wasm/pdfengine.test.mjs` | `powershell scripts/test_cpp.ps1 -R "Font\|Text"` |
| **4. Run Gateway/UI Tests**| `powershell scripts/test_gateway.ps1` | — | `powershell scripts/test_gateway.ps1` |
| **5. Start Local Server** | `powershell scripts/start_gateway.ps1` | `cd frontend; npm run dev` | — |
| **6. Aggregator/CI Check** | `powershell scripts/test_phase0.ps1` | `powershell scripts/test_phase0.ps1` | `powershell scripts/test_phase0.ps1` |
---
## 🛠️ Setup & Execution Workflows
### 💻 Dev 1 — Parser & SDK Workflow
#### A. Setup Python Gateway
```powershell
powershell -ExecutionPolicy Bypass -File scripts/bootstrap.ps1
cd gateway
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
cd ..
```
#### B. Build & Local Verification
```powershell
powershell -ExecutionPolicy Bypass -File scripts/build_cpp.ps1
powershell -ExecutionPolicy Bypass -File scripts/test_cpp.ps1
powershell -ExecutionPolicy Bypass -File scripts/check_pdfium_boundary.ps1
```
#### C. Gateway Integration & Dev Loop
```powershell
powershell -ExecutionPolicy Bypass -File scripts/test_gateway.ps1
powershell -ExecutionPolicy Bypass -File scripts/start_gateway.ps1
```
---
### ⚛️ Dev 2 — Graphics & Render Workflow
#### A. Setup Frontend UI
```powershell
powershell -ExecutionPolicy Bypass -File scripts/bootstrap.ps1
cd frontend
npm install
cd ..
```
#### B. WASM Engine Compilation
```powershell
powershell -ExecutionPolicy Bypass -File scripts/build_wasm.ps1
```
#### C. Frontend Execution & Production Build
To launch the Vite web server for visual UI prototyping:
```powershell
cd frontend
npm run dev
```
To compile production bundles:
```powershell
cd frontend
npm run build
```
---
### 🔤 Dev 3 — Fonts & Text Workflow
#### A. Setup Environment
```powershell
powershell -ExecutionPolicy Bypass -File scripts/bootstrap.ps1
cd gateway
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
cd ..
```
#### B. Development & Test Loop
```powershell
powershell -ExecutionPolicy Bypass -File scripts/build_cpp.ps1
powershell -ExecutionPolicy Bypass -File scripts/test_cpp.ps1 -R "Font|Text"
powershell -ExecutionPolicy Bypass -File scripts/test_gateway.ps1
```
---
## 🏁 Environment Verification (Aggregator Check)
Before pushing any branches, all developers should run the full suite verification:
```powershell
powershell -ExecutionPolicy Bypass -File scripts/test_phase0.ps1 -Preset win-local-pdfium
```
---
## 🔧 Developer Utilities
Below is a list of other helper scripts in the repository:
* **Populate Test Corpus**:
```powershell
powershell -ExecutionPolicy Bypass -File scripts/copy_test_corpus.ps1
```
*(Copies standard testing PDFs from local PDFium source/checkout directories to the workspace `corpus/` folder).*
Binary file not shown.
Binary file not shown.
-50
View File
@@ -1,50 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/Kids [3 0 R]
/MediaBox [0 0 100 50]
/Count 1
>>
endobj
3 0 obj <<
/Type /Page
/Contents 4 0 R
/Parent 2 0 R
>>
endobj
4 0 obj <<
/Length 71
>>
stream
10 15 m
40 15 l
40 35 l
10 35 l
W n
0 0 1 RG
10 10 m
25 40 l
40 10 l
s
endstream
endobj
xref
0 5
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000156 00000 n
0000000225 00000 n
trailer <<
/Root 1 0 R
/Size 5
>>
startxref
347
%%EOF
-48
View File
@@ -1,48 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 100]
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
>>
endobj
4 0 obj <<
/Length 95
>>
stream
q
0 0 0 rg
10 25 m 190 25 l S
[6 5 4 3 2 1] 5 d
10 50 m 190 50 l S
[] 0 d
10 75 m 190 75 l S
Q
endstream
endobj
xref
0 5
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000157 00000 n
0000000226 00000 n
trailer <<
/Root 1 0 R
/Size 5
>>
startxref
372
%%EOF
-68
View File
@@ -1,68 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 200]
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
/F2 5 0 R
>>
>>
/Contents 6 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
6 0 obj <<
% Note this object deliberately does not use /Length 83.
>>
stream
BT
20 50 Td
/F1 12 Tf
(Hello, world!) Tj
0 50 Td
/F2 16 Tf
(Goodbye, world!) Tj
ET
endstream
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000157 00000 n
0000000299 00000 n
0000000377 00000 n
0000000453 00000 n
trailer <<
/Root 1 0 R
/Size 7
>>
startxref
633
%%EOF
-81
View File
@@ -1,81 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 200]
/Count 2
/Kids [3 0 R 4 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
/Contents 7 0 R
>>
endobj
4 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
/Contents 7 0 R
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
6 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
7 0 obj <<
/Length 83
>>
stream
BT
20 50 Td
/F1 12 Tf
(Hello, world!) Tj
0 50 Td
/F2 16 Tf
(Goodbye, world!) Tj
ET
endstream
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000163 00000 n
0000000305 00000 n
0000000447 00000 n
0000000525 00000 n
0000000601 00000 n
trailer <<
/Root 1 0 R
/Size 8
>>
startxref
735
%%EOF
File diff suppressed because it is too large Load Diff
-54
View File
@@ -1,54 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 300]
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
>>
endobj
4 0 obj <<
/Length 188
>>
stream
q
0 0 0 rg
0 290 10 10 re B*
10 150 50 30 re B*
0 0 1 rg
190 290 10 10 re B*
70 232 50 30 re B*
0 1 0 rg
190 0 10 10 re B*
130 150 50 30 re B*
1 0 0 rg
0 0 10 10 re B*
70 67 50 30 re B*
Q
endstream
endobj
xref
0 5
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000157 00000 n
0000000226 00000 n
trailer <<
/Root 1 0 R
/Size 5
>>
startxref
466
%%EOF
-122
View File
@@ -1,122 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [ 0 0 200 250 ]
/Count 5
/Kids [ 3 0 R 5 0 R 7 0 R 9 0 R 11 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
>>
endobj
4 0 obj <<
/Length 49
>>
stream
q
1 1 0 rg
100 0 30 50 re B*
70 67 50 30 re B*
Q
endstream
endobj
5 0 obj <<
/Type /Page
/Parent 2 0 R
/Rotate 90
/Contents 6 0 R
>>
endobj
6 0 obj <<
/Length 49
>>
stream
q
0 1 1 rg
100 0 30 50 re B*
70 67 50 30 re B*
Q
endstream
endobj
7 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 8 0 R
>>
endobj
8 0 obj <<
/Length 49
>>
stream
q
1 0 0 rg
100 0 30 50 re B*
70 67 50 30 re B*
Q
endstream
endobj
9 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 10 0 R
>>
endobj
10 0 obj <<
/Length 51
>>
stream
q
0 1 0 rg
100 0 30 50 re B*
100 150 50 30 re B*
Q
endstream
endobj
11 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 12 0 R
>>
endobj
12 0 obj <<
/Length 50
>>
stream
q
0 0 0 rg
0 90 80 60 re B*
100 150 50 30 re B*
Q
endstream
endobj
xref
0 13
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000186 00000 n
0000000255 00000 n
0000000355 00000 n
0000000437 00000 n
0000000537 00000 n
0000000606 00000 n
0000000706 00000 n
0000000776 00000 n
0000000879 00000 n
0000000950 00000 n
trailer <<
/Root 1 0 R
/Size 13
>>
startxref
1052
%%EOF
-57
View File
@@ -1,57 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj <<
/Length 33
>>
stream
BT
20 100 Td
/F1 16 Tf
( ) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000161 00000 n
0000000287 00000 n
0000000363 00000 n
trailer <<
/Root 1 0 R
/Size 6
>>
startxref
447
%%EOF
-395
View File
@@ -1,395 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
/AcroForm <<
/Fields [23 0 R]
/DR <<
/Font <<
/F1 7 0 R
>>
>>
>>
>>
endobj
2 0 obj <<
/Type /Pages
/Count 2
/Kids [3 0 R 4 0 R]
/MediaBox [0 0 612 792]
/CropBox [0 0 612 792]
/Resources <<
/Font <<
/F1 7 0 R
/F2 8 0 R
>>
/ProcSet [/PDF /Text /ImageC]
/ExtGState <<
/GS0 24 0 R
>>
>>
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 5 0 R
/Annots [15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R]
>>
endobj
4 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 6 0 R
/Annots [15 0 R 16 0 R 26 0 R]
>>
endobj
5 0 obj <<
/Length 486
>>
stream
BT
70 700 Td
/F1 18 Tf
(Link Annotations - Page 1) Tj
0 -65 Td
/F2 14 Tf
(1. Link with destination to first page) Tj
10 -20 Td
/F2 14 Tf
(2. Link with destination to second page) Tj
-12 -84 Td
/F2 10 Tf
(PDF Reference, Version 1.7, Section 8.4.5 defines Annotations) Tj
2 -53 Td
(3. An example of Highlight with text notes) Tj
0 -18 Td
(https://pdfium.googlesource.com/pdfium is link in plain text, not link annotation. These are referred to) Tj
0 -17 Td
(as WebLinks in PDFium.)Tj
ET
endstream
endobj
6 0 obj <<
/Length 185
>>
stream
BT
70 700 Td
/F1 18 Tf
(Link Annotations - Page 2) Tj
0 -65 Td
/F2 14 Tf
(1. Link with destination to first page) Tj
10 -20 Td
/F2 14 Tf
(2. Link with destination to second page) Tj
ET
endstream
endobj
7 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
8 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
9 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Length 18
/BBox [293 530 349 542]
/Resources <<
/XObject <<
/Form0 10 0 R
>>
/ExtGState <<
/GS0 25 0 R
>>
>>
>>
stream
/GS0 gs
/Form0 Do
endstream
endobj
10 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Group <<
/S /Transparency
>>
/Length 59
/BBox [293 530 349 542]
>>
stream
1.0 1.0 0.0 rg
293 530 m
349 530 l
349 542 l
293 542 l
h f
endstream
endobj
11 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Length 18
/BBox [83 440 178 453]
/Resources <<
/XObject <<
/Form0 12 0 R
>>
/ExtGState <<
/GS0 25 0 R
>>
>>
>>
stream
/GS0 gs
/Form0 Do
endstream
endobj
12 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Group <<
/S /Transparency
>>
/Length 57
/BBox [83 440 178 453]
>>
stream
0.0 1.0 1.0 rg
83 440 m
178 440 l
178 453 l
83 453 l
h f
endstream
endobj
13 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Length 18
/BBox [149 476 191 487]
/Resources <<
/XObject <<
/Form0 14 0 R
>>
/ExtGState <<
/GS0 25 0 R
>>
>>
>>
stream
/GS0 gs
/Form0 Do
endstream
endobj
14 0 obj <<
/Type /XObject
/Subtype /Form
/FormType 1
/Group <<
/S /Transparency
>>
/Length 59
/BBox [149 476 191 487]
>>
stream
0.0 1.0 0.0 rg
149 476 m
191 476 l
191 487 l
149 487 l
h f
endstream
endobj
15 0 obj <<
/Type /Annot
/Subtype /Link
/BS <<
/W 0
>>
/Rect [69 633 542 653]
/Dest [3 0 R /XYZ 200 725 0]
/F 4
>>
endobj
16 0 obj <<
/Type /Annot
/Subtype /Link
/BS <<
/W 0
>>
/Rect [80 613 542 633]
/Dest [4 0 R /XYZ 200 725 0]
/F 4
>>
endobj
17 0 obj <<
/Type /Annot
/Subtype /Link
/BS <<
/W 0
>>
/Rect [66 529 196 544]
/A <<
/Type /Action
/URI (https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf)
/S /URI
>>
/F 4
>>
endobj
18 0 obj <<
/Type /Annot
/Subtype /Link
/BS <<
/W 0
>>
/Rect [83 440 178 453]
/QuadPoints [83 453 178 453 83 440 178 440]
/A <<
/Type /Action
/URI (https://cs.chromium.org/chromium/src/third_party/pdfium/public/fpdf_text.h)
/S /URI
>>
/F 4
>>
endobj
19 0 obj <<
/Type /Annot
/Subtype /Highlight
/AP <<
/N 9 0 R
>>
/NM (Highlight-1)
/F 4
/QuadPoints [293 542 349 542 293 530 349 530]
/P 3 0 R
/C [1 0.90196 0]
/Rect [293 530 349 542]
>>
endobj
20 0 obj <<
/Type /Annot
/Subtype /Highlight
/AP <<
/N 11 0 R
>>
/NM (Highlight-2)
/F 4
/QuadPoints [83 453 178 453 83 440 178 440]
/P 3 0 R
/C [0.26667 0.78431 0.96078]
/Rect [83 440 178 453]
>>
endobj
21 0 obj <<
/Type /Annot
/Subtype /Popup
/Parent 22 0 R
/Rect [191 377 443 488]
>>
endobj
22 0 obj <<
/Type /Annot
/Subtype /Highlight
/Popup 21 0 R
/AP <<
/N 13 0 R
>>
/NM (Highlight-With-Popup-1)
/Contents (Text Note)
/QuadPoints [149 487 191 487 149 476 191 476]
/P 3 0 R
/C [0.14902 0.90196 0]
/Rect [149 476 191 487]
/F 4
>>
endobj
23 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 131072
/T (Combo1)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [70 350 170 380]
/Opt [(Highlight) (Link) (Popup) (Widget)]
>>
endobj
24 0 obj <<
/ca 1
/Type /ExtGState
/CA 1
/BM /Normal
>>
endobj
25 0 obj <<
/ca 1
/Type /ExtGState
/CA 1
/AIS false
/BM /Multiply
>>
endobj
26 0 obj <<
/Type /Annot
/Subtype /Square
/Border [0 0 2]
/C [1 0 0]
/F 4
/P 3 0 R
/Rect [50 100 60 120]
>>
endobj
xref
0 27
0000000000 65535 f
0000000015 00000 n
0000000169 00000 n
0000000439 00000 n
0000000583 00000 n
0000000685 00000 n
0000001223 00000 n
0000001460 00000 n
0000001538 00000 n
0000001614 00000 n
0000001864 00000 n
0000002087 00000 n
0000002337 00000 n
0000002557 00000 n
0000002808 00000 n
0000003031 00000 n
0000003171 00000 n
0000003311 00000 n
0000003558 00000 n
0000003842 00000 n
0000004059 00000 n
0000004286 00000 n
0000004384 00000 n
0000004659 00000 n
0000004849 00000 n
0000004920 00000 n
0000005006 00000 n
trailer <<
/Root 1 0 R
/Size 27
>>
startxref
5135
%%EOF
-162
View File
@@ -1,162 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
/Outlines 8 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/Count 2
/Kids [
3 0 R
4 0 R
]
>>
endobj
% Page number 0.
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
/Contents [6 0 R]
/MediaBox [0 0 612 792]
>>
endobj
% Page number 1.
4 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
/Contents [7 0 R]
/MediaBox [0 0 612 792]
>>
endobj
% Font resource.
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Arial
>>
endobj
% Content for page 0.
6 0 obj <<
/Length 37
>>
stream
BT
/F1 20 Tf
100 600 TD (Page1)Tj
ET
endstream
endobj
% Content for page 1.
7 0 obj <<
/Length 37
>>
stream
BT
/F1 20 Tf
100 600 TD (Page2)Tj
ET
endstream
endobj
% Root bookmark
8 0 obj <<
/Type /Outlines
/Count 3
/First 9 0 R
/Last 12 0 R
>>
endobj
% First child bookmark (leaf node)
9 0 obj <<
/Title (A Good Beginning)
/Parent 8 0 R
/Next 10 0 R
/Dest (foo)
>>
endobj
% Second child bookmark (open)
10 0 obj <<
/Title (Open Middle)
/Parent 8 0 R
/First 11 0 R
/Last 11 0 R
/Prev 9 0 R
/Next 12 0 R
/Count 1
/A <<
/Type /Action
/S /URI
/URI (https://theplay.test)
>>
>>
endobj
% First grandchild bookmark
11 0 obj <<
/Title (Open Middle Descendant)
/Parent 10 0 R
/Dest [3 0 R /XYZ 100 200 0]
>>
endobj
% Third child bookmark (closed)
12 0 obj <<
/Title (A Good Closed Ending)
/Parent 8 0 R
/First 13 0 R
/Last 14 0 R
/Prev 10 0 R
/Count -2
/Dest (bar)
>>
endobj
% Second grandchild bookmark
13 0 obj <<
/Title (A Good Closed Ending Descendant)
/Parent 12 0 R
/Next 14 0 R
/Dest (bar)
>>
endobj
% Third grandchild bookmark
14 0 obj <<
/Title (A Good Closed Ending Descendant 2)
/Parent 12 0 R
/Prev 13 0 R
/Dest (bar)
>>
endobj
xref
0 15
0000000000 65535 f
0000000015 00000 n
0000000086 00000 n
0000000184 00000 n
0000000355 00000 n
0000000527 00000 n
0000000621 00000 n
0000000731 00000 n
0000000835 00000 n
0000000950 00000 n
0000001075 00000 n
0000001310 00000 n
0000001446 00000 n
0000001617 00000 n
0000001756 00000 n
trailer <<
/Root 1 0 R
/Size 15
>>
startxref
1869
%%EOF
-109
View File
@@ -1,109 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
/AcroForm <<
/Fields [ 8 0 R 9 0 R 10 0 R ]
/DR 4 0 R
>>
>>
endobj
2 0 obj <<
/Type /Pages
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources 4 0 R
/MediaBox [ 0 0 300 600 ]
/Contents 7 0 R
/Annots [ 8 0 R 9 0 R 10 0 R ]
>>
endobj
4 0 obj <<
/Font 5 0 R
>>
endobj
5 0 obj <<
/F1 6 0 R
>>
endobj
6 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
7 0 obj <<
/Length 51
>>
stream
BT
0 0 0 rg
/F1 12 Tf
100 450 Td
(Test Form) Tj
ET
endstream
endobj
8 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 393216
/T (Combo_Editable)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [ 100 350 200 380 ]
/Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
>>
endobj
9 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 131072
/T (Combo1)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [ 100 400 200 430 ]
/Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
(Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
(Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
(Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
/V (Banana)
>>
endobj
10 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 131073
/T (Combo_ReadOnly)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [ 100 500 200 530 ]
/Opt [(Dog) (Elephant) (Frog)]
>>
endobj
xref
0 11
0000000000 65535 f
0000000015 00000 n
0000000137 00000 n
0000000202 00000 n
0000000351 00000 n
0000000386 00000 n
0000000419 00000 n
0000000495 00000 n
0000000597 00000 n
0000000803 00000 n
0000001259 00000 n
trailer <<
/Root 1 0 R
/Size 11
>>
startxref
1448
%%EOF
Binary file not shown.
-70
View File
@@ -1,70 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
/F2 5 0 R
>>
>>
/Contents 6 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
6 0 obj <<
>>
stream
BT
20 50 Td
/F1 12 Tf
(Hello, world!) Tj
0 50 Td
/F2 16 Tf
(Goodbye, world!) Tj
ET
endstream
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000161 00000 n
0000000303 00000 n
0000000381 00000 n
0000000457 00000 n
trailer<< /Root 1 0 R /Size 7 >>
startxref
578
%%EOF
xref
0 0
trailer<< /Root 1 0 R /Size 0 /Prev 578 >>
startxref
780
%%EOF
Binary file not shown.
Binary file not shown.
-165
View File
@@ -1,165 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
/AcroForm <<
/Fields [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
/DR 4 0 R
>>
>>
endobj
2 0 obj <<
/Type /Pages
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources 4 0 R
/MediaBox [0 0 300 600]
/Contents 7 0 R
/Annots [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
>>
endobj
4 0 obj <<
/Font 5 0 R
>>
endobj
5 0 obj <<
/F1 6 0 R
>>
endobj
6 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
7 0 obj <<
/Length 51
>>
stream
BT
0 0 0 rg
/F1 12 Tf
100 450 Td
(Test Form) Tj
ET
endstream
endobj
8 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 0
/T (Listbox_SingleSelect)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 350 200 380]
/Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
>>
endobj
9 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 2097152
/T (Listbox_MultiSelect)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 400 200 430]
/Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
(Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
(Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
(Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
/V (Banana)
>>
endobj
10 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 1
/T (Listbox_ReadOnly)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 500 200 530]
/Opt [(Dog) (Elephant) (Frog)]
>>
endobj
11 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 2097152
/T (Listbox_MultiSelectMultipleIndices)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 250 200 280]
/Opt [(Albania) (Belgium) (Croatia) (Denmark) (Estonia)]
/I [1 3]
>>
endobj
12 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 2097152
/T (Listbox_MultiSelectMultipleValues)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 200 200 230]
/Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
/V [(Epsilon) (Gamma)]
>>
endobj
13 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 2097152
/T (Listbox_MultiSelectMultipleMismatch)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 150 200 180]
/Opt [(Alligator) (Bear) (Cougar) (Deer) (Echidna)]
/V [(Alligator) (Cougar)]
/I [1 3 4]
>>
endobj
14 0 obj <<
/Type /Annot
/Subtype /Widget
/FT /Ch
/Ff 0
/T (Listbox_SingleSelectLastSelected)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [100 100 200 130]
/Opt [(Alberta) (British Columbia) (Manitoba) (New Brunswick)
(Newfoundland and Labrador) (Nova Scotia) (Ontario)
(Prince Edward Island) (Quebec) (Saskatchewan)]
/V (Saskatchewan)
/TI 9
>>
endobj
xref
0 15
0000000000 65535 f
0000000015 00000 n
0000000163 00000 n
0000000226 00000 n
0000000399 00000 n
0000000434 00000 n
0000000467 00000 n
0000000543 00000 n
0000000645 00000 n
0000000850 00000 n
0000001318 00000 n
0000001502 00000 n
0000001747 00000 n
0000001996 00000 n
0000002267 00000 n
trailer <<
/Root 1 0 R
/Size 15
>>
startxref
2642
%%EOF
-63
View File
@@ -1,63 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 200]
/Kids [3 0 R 3 0 R]
>>
endobj
3 0 obj <<
/Type /Pages
/Kids [4 0 R 4 0 R 4 0 R]
>>
endobj
4 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
/Contents 6 0 R
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
6 0 obj <<
/Length 44
>>
stream
BT
20 50 Td
/F1 12 Tf
(Hello, world!) Tj
ET
endstream
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000152 00000 n
0000000216 00000 n
0000000342 00000 n
0000000421 00000 n
trailer <<
/Root 1 0 R
/Size 7
>>
startxref
516
%%EOF
Binary file not shown.
-41
View File
@@ -1,41 +0,0 @@
%PDF-1.7
%¿÷¢þ
1 0 obj
<< /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 8 >> >> /Pages 2 0 R /Type /Catalog >>
endobj
2 0 obj
<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >>
endobj
3 0 obj
<< /Contents 4 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> /Type /Page >>
endobj
4 0 obj
<< /Length 352 /Filter /FlateDecode >>
stream
¥Æçÿ¥yÀL4Wˆ®{©ÈÒ
뫉û\ÐòòÆÓ‚˼`ÁS¢Éñ8¦1¾4â€v×än˳?ÁD<¨ž¤#“Õ6îçJµÔ?àšýÇÝ 3¥­Å g&eökûóA¹ŽŒpÉ@…“¶QÂÅþÊîšÔÊ>ȃ'«úœˆ l•¡:ùšÛ*ÚáS@5xw^Úuž…¦€ëv‰véúY\Bæ&Ť.õÖÕ¼ó©f(+ô£KˆN.ÁÎIÀ•èUÔæxu&²¡ÑTtàó¸®7©ÿÝÐ<c>þ"ˆþâóÈÀeêMq²U]æ¿—ø¥Ê¸õ=%dÉ®å y’†Mçƒíº7Üî‚¢øv_ݧã»çÿX POç\'f_ áYúof7}/tõaCÉàÿInhì ôòÜ\V®wRøæ[pq5X3;Ý|šš sŒt·JÕ 
endstream
endobj
5 0 obj
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >>
endobj
6 0 obj
<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >>
endobj
7 0 obj
<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <eed09d7bae817b88ec68c4bba71e4688bad49a26f13d5f1b558fd8d6246663774b7e74b08734ad7a57473b33ec19e47d> /OE <33dc073711e1735085e2efd64928fa75af518dcf25ec7ff7d3ae22976ab8ded4> /P -3136 /Perms <b360a35c81e6b6380ef770702952140a> /R 6 /StmF /StdCF /StrF /StdCF /U <0d6d86441425cdaee0cda2eec22acd2391b314ff4c0bb60c90e9955907889824f87b528709af1246ab59dd722a89cb2a> /UE <2da7189a149f6fcbdf38688a565f280f8e6a4b727a3f528dbb9e014a88ebd88f> /V 5 >>
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000130 00000 n
0000000189 00000 n
0000000327 00000 n
0000000751 00000 n
0000000848 00000 n
0000000947 00000 n
trailer << /Root 1 0 R /Size 8 /ID [<f341436d4fd6835a35fb5f4313bdd156><f341436d4fd6835a35fb5f4313bdd156>] /Encrypt 7 0 R >>
startxref
1497
%%EOF
-70
View File
@@ -1,70 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
/AcroForm << /Fields [ 4 0 R ] /DR 5 0 R >>
>>
endobj
2 0 obj
<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources 5 0 R
/MediaBox [ 0 0 300 300 ]
/Contents 8 0 R
/Annots [ 4 0 R ]
>>
endobj
4 0 obj
<<
/Type /Annot
/FT /Tx
/T (Text Box)
/DA (0 0 0 rg /F1 12 Tf)
/Rect [ 100 100 200 130 ]
/Subtype /Widget
>>
endobj
5 0 obj
<< /Font 6 0 R >>
endobj
6 0 obj
<< /F1 7 0 R >>
endobj
7 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
8 0 obj
<< /Length 51 >>
stream
BT
0 0 0 rg
/F1 12 Tf
100 150 Td
(Test Form) Tj
ET
endstream
endobj
xref
0 9
0000000000 65535 f
0000000015 00000 n
0000000114 00000 n
0000000173 00000 n
0000000309 00000 n
0000000445 00000 n
0000000478 00000 n
0000000509 00000 n
0000000585 00000 n
trailer<< /Root 1 0 R /Size 9 >>
startxref
685
%%EOF
-37
View File
@@ -1,37 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
/Collection /Test
>>
endobj
2 0 obj <<
/Type /Pages
/Count 3
/Kids [
3 0 R
]
>>
endobj
% Page number 0.
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <</F1 15 0 R>>
>>
/MediaBox [0 0 612 792]
/Tabs /R
>>
endobj
xref
0 4
0000000000 65535 f
0000000015 00000 n
0000000088 00000 n
0000000176 00000 n
trailer<< /Root 1 0 R /Size 4 >>
startxref
310
%%EOF
-71
View File
@@ -1,71 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
2 0 obj <<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
/F2 5 0 R
>>
>>
/Contents [6 0 R 7 0 R]
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
6 0 obj <<
/Filter /FlateDecode
/Length 0
>>
stream
endstream
endobj
7 0 obj <<
>>
stream
BT
20 50 Td
/F1 12 Tf
(Hello, world!) Tj
0 50 Td
/F2 16 Tf
(Goodbye, world!) Tj
ET
endstream
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000061 00000 n
0000000154 00000 n
0000000304 00000 n
0000000382 00000 n
0000000458 00000 n
0000000531 00000 n
trailer<< /Root 1 0 R /Size 8 >>
startxref
652
%%EOF
-90
View File
@@ -1,90 +0,0 @@
%PDF-1.3
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /MSung-Light /DescendantFonts [ <<
/BaseFont /MSung-Light /CIDSystemInfo <<
/Ordering (CNS1) /Registry (Adobe) /Supplement 1
>> /DW 1000 /FontDescriptor <<
/Ascent 752 /CapHeight 737 /Descent -271 /Flags 6 /FontBBox [ -160 -249 1015 888 ] /FontName /MSung-Light
/ItalicAngle 0 /Leading 148 /MaxWidth 1000 /MissingWidth 500 /StemH 45 /StemV 58
/Type /FontDescriptor /XHeight 553
>> /Subtype /CIDFontType0 /Type /Font
/W [ 1 2 250 3 [ 408 668 490 875 698 250 240 ] 10 [ 240 417 667 250 313 250 520 500 ] 18 26 500
27 28 250 29 31 667 32 [ 396 921 677 615 719 760 625 552 771 802
354 ] 43 [ 354 781 604 927 750 823 563 823 729 542
698 771 729 948 771 677 635 344 520 344
469 500 250 469 521 427 521 438 271 469
531 250 ]
75 [ 250 458 240 802 531 500 521 ] 82 [ 521 365 333 292 521 458 677 479 458 427
480 496 480 667 ] ]
>> ] /Encoding /UniGB-UCS2-H /Name /F2 /Subtype /Type0 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
>>
endobj
6 0 obj
<<
/Author (anonymous) /CreationDate (D:20260602153802+05'00') /Creator (anonymous) /Keywords () /ModDate (D:20260602153802+05'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (unspecified) /Title (untitled) /Trapped /False
>>
endobj
7 0 obj
<<
/Count 1 /Kids [ 4 0 R ] /Type /Pages
>>
endobj
8 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 107
>>
stream
GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?C]6_CBF/28[_U!/s9cYpe/lM_Qn>nC.&g0fCf=<!^TD#gi_<=5X,[c-mU(~>endstream
endobj
xref
0 9
0000000000 65535 f
0000000061 00000 n
0000000102 00000 n
0000000209 00000 n
0000001155 00000 n
0000001358 00000 n
0000001426 00000 n
0000001687 00000 n
0000001746 00000 n
trailer
<<
/ID
[<01c8dab3d2c3e771bf716fccf2b52ce4><01c8dab3d2c3e771bf716fccf2b52ce4>]
% ReportLab generated PDF document -- digest (opensource)
/Info 6 0 R
/Root 5 0 R
/Size 9
>>
startxref
1943
%%EOF
-62
View File
@@ -1,62 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 213 >>
stream
BT
/F1 12 Tf
72 720 Td
(Custom Encoding Test Document) Tj
-72 -720 Td
72 700 Td
(WinAnsi encoding verification text.) Tj
-72 -700 Td
72 680 Td
(All standard ASCII chars should decode correctly.) Tj
-72 -680 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
6 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000117 00000 n
0000000290 00000 n
0000000554 00000 n
0000000652 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
752
%%EOF
-51
View File
@@ -1,51 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources << /Font << /F1 5 0 R >> >>
>>
endobj
4 0 obj
<< /Length 58 >>
stream
BT
/F1 12 Tf
72 720 Td
(CID Text) Tj
-72 -720 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type0 /BaseFont /NotoSansCJKjp-Regular /Encoding /Identity-H /DescendantFonts [6 0 R] >>
endobj
6 0 obj
<< /Type /Font /Subtype /CIDFontType2 /BaseFont /NotoSansCJKjp-Regular /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 6 >> >>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000064 00000 n
0000000126 00000 n
0000000270 00000 n
0000000384 00000 n
0000000518 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
684
%%EOF
-63
View File
@@ -1,63 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 225 >>
stream
BT
/F1 14 Tf
72 720 Td
(Embedded TrueType Font Test Document) Tj
-72 -720 Td
/F1 12 Tf
72 700 Td
(This PDF uses a referenced TrueType font.) Tj
-72 -700 Td
72 680 Td
(Text extraction should work correctly.) Tj
-72 -680 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
6 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000117 00000 n
0000000290 00000 n
0000000566 00000 n
0000000664 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
764
%%EOF
-175
View File
@@ -1,175 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
/MediaBox [0 0 200 200]
/Resources <<
/ProcSet [/PDF /Text]
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj <<
/Length 110
>>
stream
BT
/F1 12 Tf
150 160 Td
[<01>2<02>2<03>-4<02>2<04>5<05>]TJ
ET
BT
0 100 Td
-1 0 0 1 50 40 Tm
[<01>5<05>]TJ
ET
endstream
endobj
5 0 obj <<
/Type /Font
/Subtype /TrueType
/BaseFont /BAAAAA+NotoSansHebrew-Regular
/FirstChar 0
/FontDescriptor 6 0 R
/LastChar 5
/ToUnicode 8 0 R
/Widths [600 294 235 645 397 542]
>>
endobj
6 0 obj <<
/Type /FontDescriptor
/Ascent 1069
/CapHeight 869
/Descent -293
/Flags 4
/FontBBox [-210 -252 716 869]
/FontFile2 7 0 R
/FontName /BAAAAA+NotoSansHebrew-Regular
/ItalicAngle 0
/StemV 80
>>
endobj
7 0 obj <<
/Filter [/ASCII85Decode /FlateDecode]
/Length 4240
>>
stream
GhVOeCMXsCFZt#(%p<i9AU.uO6]Ye/ifKHXbLV!I1<gsgHY$Eg-tbPt@rk=Ap?l+FQ/u;^8JASXVQS>V
nuG<j"nF$,Rii%*89oFrq6<34,1m)cJ0IF5(22*&"c<?^q>^jZEkc[.-qdl^dk1F&,fKT9F2)eaH/1QX
<gN4)W0n]g:798;]<j42dYR+b7&$II`er0Do2PVi55OH.DU&3oF7^D>=p%W;pT6OOkC.gL0;aQZ%EQ]$
CMpO/ouiOe*=oqMFW5p^ge<JWIt[XVg[cqr8aR%^g[TKTpYK&S\F4)WJbd&hhE(,./q'ef/\k-FOKm`h
f=q)o6_0Sd-Ap"u/`'bn6!o+8Zu;5]Q^2D[J,6jbf.d4Ck't7.im"Z=^^/7D4VXdnJ*hX.ia&@S<Zl_D
Xq`#Xlf3.[Q6GL-.IS+nj<JEQ1+=HefoeH8Qi0[)c<.\^P$4LTD?DamV./;?-PDR^8[b$k8'^LB2C4&'
NUg=HC6k8A'.Knb7Wh)elDCX74)L`tc?`Z\Sb;i$>AElUWZdj+!8FWu:gnKPdkmGSM;%q39@mC[7<ZEH
BP,jX746Y[8VL'e=mlrkg+[&/4Xn#1'(72pCc`cXNN+/Z0?DM&g%BgA@co7e)9J3X;X\\(5@"=N^/d""
T`@poCcT@G`,Q43B>^KnLk;$<#C1`ZRI:qa#-tMr)R?VE.RR\7=sKe@N)#YuA]JfMaHgYN/!?c&OsZTe
^cXe0%l-1*krrj[$8W`Wl)*K?>t9dJirfrR7MYH\=XPN5Q2qL_I4eudg4b]KRT\<OAl/T-PZ'?3dZ$p;
<A/YSC[`O')u&1O:[moi)'2=Vd5KR72=0E6LAR(+/#@80mS5hidn)NRbS3]PMfW2+=&LegaZXffSM\+*
%@ESe'CB7@?)cYMPG3X<A98q?pK.WHgPW4%FOOV*1/i@`;U-_0)m/crX^pV$",X/"BsMn?;R::1,iO?7
d*"n-c#gLp.bPO!Jk.*r-gP*5[&0>a[qOG`>on_9`Lhgl?)c@P!H#2#PFffIEAQP^TS_DS:Xem?lK2!M
h#c+\A=NN1)ZW-WA38,70l'k`MSW-$8rA0><3W]ffk"ZqCrdIAQBE9>j@mZ'<HHGofhsV-;mOl@]Mb1L
qSIIYnkLM"Z97EVYElh]%[>>bb#jgf**OlY[IHaK5,@C./bHbDalE":hWu.1N,@;S&gl,n3:<uV$;N?L
*(2O_s!JrF=fbn$ki"!LOY9qefX_V\<NFjuO0ngY8TpJ(,;39KIP'3^N)o:sU^S>oeCuK%7\,dgAiUN1
E!0r_=$ol73>k&7]_*E"mUSrX[62980+uUk50ME#I*V:L4N8s+bi=/qMstu,ntJ5f[(HSa5@JhaGYD:Q
]auqs7`nFM/]DAtfVG=ND6[_hnTB=9[;GHTf&F!MBNQP7P.LnAZ2VP"Ploh`?NTa8nEUYFj3^8B4<?t>
4SBp^*fhF-*fhL/*r?NkO.Sgf4:WJt*n-E0W]KmAKl)1[L<V@'WjXmHBl;cn9--aK@lTjrE@*#k?-6on
,:;=;+3B=[oI&q3p0&\dDEW8m^^H(gHjR8,7ldq.4hG]/>/mf;Hl1F^:B&.Xiqu!h(RA"GpXOmI2eJ(p
-Ufu7H4'I5!71?\VoB`KajX::mq>=Rr&Q;g]E7s$-kZ.E)/3T4mfoT]_V3^s3J#ALKI7nF?TacHK]qhj
L<ncjl]HN[$fPFZ;`(\GZEhSIf%/9H@>qA*jpO7b7bY=!,<-,5gQ6!\>V]AQ)^-GB`&C"Q_Tt5F^2b04
H^dXF*&#KkrUdWPo<maGSjIV2C<A6%Y#l1eDB-11=5MCe/mQ;H^`5k(:$aRZO01rVm/hX\fYmD8f*Dj@
M@i=qKg0)_(>4O.G!a5^E2>bO$]QiDc\2C6pdgJSEti>7EGJ-(WqC@QJiDd@NbtVSKCL`LBr7+Q7e(#%
mfT9"IKcR0OZpuAD#Y..(\(Ya*`U>DQ9W"+'>%SR14Rbu3okUD1UoZtSAfKYf.IftGdnZe:>-X$>r]=V
G-"HZ$ThCqcd_nA%GR%YOrq)?cB9h+fAIVB(kUJ,MLq_e+>iX=AF@bgIcap`;p5$2F+]o@5&$NLhu:.I
2<(<.=8A484n4$P`>fQKA"tBRWbq]tDur6ODulS&f!BB<ZPSc21WW3#buP0(+9gU#Z,bH/SFFh24G?`5
dOt)^QkNbrnhfi)90SEq&n`q[F;s7b!nXc$g#@aPQN3qbFeWl+!e=Zt`EF'M1(Bm%7HR8DbV4gg<TZ"1
'QY,(Ci/)_["1h766XD'&ADrraHq=DH%OF$jsfp?jsbDCGm`Bc4nAYbhNNN6HSW+QHSVNRk<)"p3=KZH
@5,_A#.Kh(3M>-L01N,n^DOYar*o;enr(m9jVO\;jVOZqjVO[\jEPdIJ4jW(1Ou\+n/U@A-XG!&R,,`f
2W1\3O?*%L7g,>@T`!f8eMl#PBrq/m2=C@'l+[6\!TC@KAA[MWaFrFRGAPIZkLE8R^YhJV5O]iFIg_\G
iO=6VK\u;C'>=b?fe%PhWL9K1abNRi>otj#+Z';<n+i%OCHc:GQ`]p-obCOtip.a^cf<UjY!FG)PRTLL
+"jYG+4_li`+jB7`VE<s"4AVj".>pGko/fq8/"=:R5@Xn#Q]c2U1bel!_n`0,C>VAYsbZH&PH@FJ$f#/
0td=Kg'>AR25F7&YOo!<mI]$*rRjT2aR!Q]%Fi)aOtJSn?+UM2'/_)[:p`0l2g4bJBatZEkin+Um_FDG
q;M6)[I9OhMIS=<lFaD[8\^p:_H)J0l0'R9&ZuGO_>dFFCb.uJQlb(Q4<5Au742_H^C[$nr;0XJ4b3Tg
[;48b%t<.Gdkk?8S<Vi`;Poe&S_S0PK8iOs+@Vd0N*gN+=Plp50G^#i&Kg"CSmrqAknTi"D:*>YSUrma
g=r&8<Pj&ND`$13'HCOjG_kBC'T)*ekL$!:6XuRpIX?)+^V5+S2cXNJB2ec80tlUL?G#q-m,hbS%$"V@
p"JQ?igh1:m'G\$*s_?+,4a,JI@3GnX*+U"_Zr.(+*>1jkFAnU9>ZXAU\g3ArglE9&,uBU"0[/n3+!B'
W[)MC"?*YACpqa=bi'p-F[R+Y&Jh':>DS9I;-PEL7\\5FC1<[_U$dQmm+CP5IQgiD>SQGm;oRG4k5.U/
0D?:mI/C=6)f;AM:/Ut^\F':\li2"qaZh<t\"cP;!(<3T?G(>oo5D,:=$o+C?]KqCU?geEFjWG&V"PRp
&9$#`m3'G9"&tosqAZg/@%\C2I=Z6p1:0Yl"+@)T)so1U@.,fWm/arHR8d:MJXMQVC8K5]=Jm_KIXjdB
83[K$Qla[fJ0ST]0*O=-3O'X`"r\'Q&`>h%Y8I@YUo\`'^eHY_XTc5&7^\ik\4-8s4VZgTU@:6TdipBk
I9NLLkSE!O'mP*-*Z_.eTF%\P6@O])QsDsH.[1,u#K\!Fdp$""eZ=M1#$Z$(ZO5i+BWp@?TH^qnB%D7/
h#sWFD^p`NF:Yfb85;E[:q^5Oc;Ss`!7br9/Ss$74_<U(m:SQ#s-3*J/tuGNgXsJtKtqF1bXL_J,7?@i
'BEj1Ilon*aW+o61dn83AfE7FcXE_u9)ggC=\p"(7u!%6Y7^]'TMPP?@sg_akMgC`EYfUsdCeJO#2(2D
hQ;Br<4m&15-"(TYi[P!iN8/Ok#@VVm[UO"bZs@LQZDI&pjXBX+4dX`csI+Oe#J=,^c=d&n:"<LdpP?/
57NYh_**Nu%GYGtPrZ:2pA@k$5I#/DZ?u(p.mKdY_mpagr"[aHSA,e@EE+%EftN*$X1t;gS`@?f>ZH3j
dG.dX,4]pij-88?@>Es&4"OKq4Pb_H)9:8fq`(0Ie[V>KnkIYS^43jk=+).,aXsOX(4cqM6KaT:7HMS5
5VqW;.CtD!kNC3=e+gS7q$"nkh'kl'g`'1Rk*3jhL&JE!FZWhT#NI(1R)?@JnB0)&ZC7m>1Wud3mOB<F
<?1UlEeaPIK4D$_#X<cE%HM2B7nrD$SI/J?MJ9$\[%L0[X#c.:>Z)WgX'F4+E];'H25pPfMT*QlPBH<B
2jEaIeeu%@g_SZi^/+`r>koI@22thoJ$T#-Ig:8=C[r)9A@gh.nJ+P$rtk!a7oB995m%NRR_tlI7kn;C
L?s85#0T`VHa<+&O5K'$Eo(6pn-XUq4Y6-9Re-B$rhtBq!Tfi]GEbPb2*A\G,P/,p;d\J77]S.F#9J`B
DopA\Sjp8ME&Btm*&[^S4k/0Z~>
endstream
endobj
8 0 obj <<
/Length 377
>>
stream
/CIDInit/ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo<<
/Registry (Adobe)
/Ordering (UCS)
/Supplement 0
>> def
/CMapName/Adobe-Identity-UCS def
/CMapType 2 def
1 begincodespacerange
<00> <FF>
endcodespacerange
5 beginbfchar
<01> <05DF>
<02> <05D9>
<03> <05DE>
<04> <05E0>
<05> <05D1>
endbfchar
endcmap
CMapName currentdict /CMap defineresource pop
end
end
endstream
endobj
xref
0 9
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000131 00000 n
0000000309 00000 n
0000000471 00000 n
0000000678 00000 n
0000000905 00000 n
0000005238 00000 n
trailer <<
/Root 1 0 R
/Size 9
>>
startxref
5667
%%EOF
File diff suppressed because it is too large Load Diff
Binary file not shown.
-46
View File
@@ -1,46 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources << /Font << /F1 5 0 R >> >>
>>
endobj
4 0 obj
<< /Length 64 >>
stream
BT
/F1 12 Tf
72 720 Td
(Malformed Font) Tj
-72 -720 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /UnknownType /BaseFont /BrokenFont >>
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000064 00000 n
0000000126 00000 n
0000000270 00000 n
0000000390 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
472
%%EOF
-46
View File
@@ -1,46 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources << /Font << /F1 5 0 R >> >>
>>
endobj
4 0 obj
<< /Length 60 >>
stream
BT
/F1 12 Tf
72 720 Td
(Page 1 Mix) Tj
-72 -720 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000064 00000 n
0000000126 00000 n
0000000270 00000 n
0000000386 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
488
%%EOF
-46
View File
@@ -1,46 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources << /Font << /F1 5 0 R >> >>
>>
endobj
4 0 obj
<< /Length 66 >>
stream
BT
/F1 12 Tf
72 720 Td
(No ToUnicode Map) Tj
-72 -720 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Symbol >>
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000064 00000 n
0000000126 00000 n
0000000270 00000 n
0000000392 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
464
%%EOF
-71
View File
@@ -1,71 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj <<
/Length 406
>>
stream
BT
0 0 Td
/F1 12 Tf
0.70710678118 -0.70710678118 0.70710678118 0.70710678118 100 100 Tm
(Hello,) Tj
0 0 Td
/F1 12 Tf
-0.70710678118 -0.70710678118 0.70710678118 -0.70710678118 100 100 Tm
( world!\r
) Tj
0 0 Td
/F1 12 Tf
-0.70710678118 0.70710678118 -0.70710678118 -0.70710678118 100 100 Tm
(Goodbye,) Tj
0 0 Td
/F1 12 Tf
0.70710678118 0.70710678118 -0.70710678118 0.70710678118 100 100 Tm
( world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000161 00000 n
0000000287 00000 n
0000000365 00000 n
trailer <<
/Root 1 0 R
/Size 6
>>
startxref
823
%%EOF
-71
View File
@@ -1,71 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 200]
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj <<
/Length 210
>>
stream
BT
0 0 Td
/F1 12 Tf
1 0 0 1 100 100 Tm
(Hello,) Tj
0 0 Td
/F1 12 Tf
0 1 -1 0 100 100 Tm
( world!\r
) Tj
0 0 Td
/F1 12 Tf
-1 0 0 -1 100 100 Tm
(Goodbye,) Tj
0 0 Td
/F1 12 Tf
0 -1 1 0 100 100 Tm
( world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000157 00000 n
0000000283 00000 n
0000000361 00000 n
trailer <<
/Root 1 0 R
/Size 6
>>
startxref
623
%%EOF
-46
View File
@@ -1,46 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources << /Font << /F1 5 0 R >> >>
>>
endobj
4 0 obj
<< /Length 61 >>
stream
BT
/F1 12 Tf
72 720 Td
(Subset Text) Tj
-72 -720 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /ABCDEF+Arial /FirstChar 32 /LastChar 126 >>
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000064 00000 n
0000000126 00000 n
0000000270 00000 n
0000000387 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
496
%%EOF
Binary file not shown.
-76
View File
@@ -1,76 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 532 >>
stream
BT
/F1 12 Tf
72 720 Td
(Hello World - UTF-8 Test Document) Tj
-72 -720 Td
72 700 Td
(Standard Latin Text for Encoding Verification) Tj
-72 -700 Td
72 680 Td
(Font Size Detection Sample: Small Text 12pt) Tj
-72 -680 Td
/F2 18 Tf
72 650 Td
(LARGE TEXT FOR SIZE 18PT DETECTION) Tj
-72 -650 Td
72 620 Td
(More 18pt content: ABCDEFGHabcdefgh 0123456789) Tj
-72 -620 Td
/F1 12 Tf
72 590 Td
(Back to 12pt: The quick brown fox jumps over the lazy dog) Tj
-72 -590 Td
72 570 Td
(Special chars: copyright section paragraph) Tj
-72 -570 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
6 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000117 00000 n
0000000290 00000 n
0000000873 00000 n
0000000971 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
1071
%%EOF
-49
View File
@@ -1,49 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 50 >>
stream
BT
/F1 12 Tf
72 720 Td
(Vertical Text Test) Tj
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Identity-V /Encoding /WinAnsiEncoding >>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000117 00000 n
0000000273 00000 n
0000000373 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
482
%%EOF
-146
View File
@@ -1,146 +0,0 @@
%PDF-1.7
% ò¤ô
1 0 obj <<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj <<
/Type /Pages
/MediaBox [0 0 200 200]
/Count 1
/Kids [3 0 R]
>>
endobj
3 0 obj <<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 8 0 R
>>
endobj
4 0 obj <<
/Type /Font
/Subtype /Type0
/Encoding /UniGB-UTF16-V
/BaseFont /Test
/DescendantFonts [5 0 R]
>>
endobj
5 0 obj <<
/Type /Font
/Subtype /CIDFontType2
/BaseFont /Test
/CIDSystemInfo <<
/Registry (Adobe)
/Ordering (GB1)
/Supplement 4
>>
/FontDescriptor 6 0 R
/DW 1000
/W [
1 [278] %space
2 [278] %!
41 [722] %H
56 [944] %W
69 [556] %d
70 [556] %e
77 [222] %l
80 [556] %o
83 [333] %r
]
/DW2 [0 -1000]
/W2 [
1 [-723 139 623] %space
2 [-918 139 818] %!
41 [-918 361 818] %H
56 [-918 472 818] %W
69 [-918 278 818] %d
70 [-723 278 623] %e
77 [-918 111 818] %l
80 [-723 278 623] %o
83 [-723 166.5 623] %r
]
>>
endobj
6 0 obj <<
/Type /FontDescriptor
/Ascent 718
/CapHeight 500
/Descent -207
/Flags 32
/FontBBox [-166 -225 1000 931]
/FontFile2 7 0 R
/FontName /Test
/ItalicAngle 0
/StemV 80
>>
endobj
7 0 obj <<
/Filter [/ASCII85Decode /FlateDecode]
/Length1 2456
/Length 1684
>>
stream
GhU\K?YiY@)#qml\*a1%h:c`s)m$[;?!@Nf?/*!!0XbkOrTQd[?,p@=KMg=Kc?4![MV1<a'7u=>As+]B
'495o@Y1$[N=Y"V^.<W,L*pI>$-"n:U;RjO^N4a)+9@>=#<aH4hsYsm^[pn0#QX)I2;k-WI'UY:m#)O\
!g"OF:X1W4EpNXp_Z9<>jGhF!S/7idXo\$E.-Hf!Q!CSMQ;G;Ho<MnQm<%mSRl=19)qpfU-":lYlDY":
q[SB7Kad?0hi#3nS@>;[nuK#ZMO7Yf&Pd!%@tr;<SS6@/J%QJ"c+uL"7+k>*$^oXFinbsi#J)/DIn)-'
OtV7j?AtLdEaZ<5\9I,6ls*Q:mnh.)MB'-QHi$N,Ikc"ackl.lS#dYZ!t(lZ2Y9/.d?6]&WH`a%*Dndd
5ZDU!@`<TjWWl6%Z2kjXXjaImr^Ma-T><lC1XMA)H$dDTq".B&f:Ti(-PZ;?<68ZDLN]"6A!sT%,6eE'
oKP>!?&8GX>ODU?s6Y&&nC(/%5?!?Se<]Oma7S5D.CRLdfV,J@+O5cukCK0_/Q.0>kCcDhj_RFjOj_;E
O(`NP]LJqP/$Go$)iReI-F9PG[a)Id^1$o!7U))'C_p:q:)>s-h6jks?0ZBU]Q.YOcbF+2pV*6U5[XQ,
rW@d?_^!"FZ%J`;LW@:GO\,=t2<@P/RdFJL^;'$FBD.DEp(RB7lU7pu[rQhJ`V]-6]nJOS"lL1WRHsLe
hR_DZX_;p-\@6oAhpP?iE7?hRHqVT![)6KsZj9"_?H1@.H*j]l_F7D08MnQa/(=+;_Nef!A>8-]iB"YP
pmt$6^7N"dW8GP=MTAB=AL';>ZGf;7!TLS)!H.MpfumsW'pMgZ(M@ZVaQcJ<9o\,enaja=NQ/'Gcs%$/
jG1bCp2\'8<9V+?+mGrSog0VnZuB-9'ck=UaE:OU^\E>JnWg-Y4+P?=lr)!4aqEqbZ:T.p7cO"8H:b#5
ZtjGmlA-b#\eYg8f5$4THGmU;$[\b*p<S\VN)X%g?[l!h2TSn-+-m)tma:&BWoK!jc(07P;&hpi@)^q'
ijFn<dVL/6fB9=p'%&=oY0I$Pg9nM(M3+$sq6%ThF=&Q"mqN=/6Y?Vs/IqN7lA19"W4chXZtjj1@Qn<\
"jYCeT,S26":(iDrRbDhL:,rFQ#Wq09i&+Y]+j"RYmjNjZ.[F-E*0#tBPQfdLG#DalFOJ\P3maIAMi.C
=?IFg#_aIb0k.Y3A`/iA$QB1\BBHMQ\[cf#9!QHAkgRDKWO(k8/AaqmfcFAZ!$UB&U7p#^A8D_CSdnfj
%2!@<\-\16TC<T`cu#]7eB`[X51t6.)PK[?"Nhm_@L>0+UgafRn=tkd-ms-eej'u7=0WP:EV;`hloWXt
=><Lb$rIa<N4Z*!%`VmD`B9OX/M(<>fSH20/b6eu!4$i^emK@2+:0L<j7d.5bX3mY0=Z#sTFGlH&uZs]
ZkVAJ%-k]\Ci4*8+dpI\46KfOYM8AY'1.NZ+e":":csG8H*k[\)K"Nm5uo>IOA@7`#U@D`_ASTW0K)<3
aYW/uJmllYfeL's)@7]##r<0tDW*$N97.7b["F=t;:[Fkc/aYdbK5FqQ/"^8YSA1`a5&AWZ]q)Kk>C:l
_[=Oh?39tHUP>R@HqfVJQ<+j-)8Y_688bp6H/?'ZTdYB8rjP"t&b_Lc'A9H]a8~>
endstream
endobj
8 0 obj <<
/Length 179
>>
stream
BT
/F1 12 Tf
10 190 Td
(\000H\000e\000l\000l\000o\000 ) Tj
(\000W\000o\000r\000l\000d\000!) Tj
ET
BT
/F1 12 Tf
110 190 Td
[(\000H) 100 (\000e) -100 (\000l\000l) 200 (\000o)] TJ
ET
endstream
endobj
xref
0 9
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000157 00000 n
0000000283 00000 n
0000000408 00000 n
0000001041 00000 n
0000001244 00000 n
0000003038 00000 n
trailer <<
/Root 1 0 R
/Size 9
>>
startxref
3270
%%EOF
-62
View File
@@ -1,62 +0,0 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
/F2 6 0 R
>>
>>
>>
endobj
4 0 obj
<< /Length 233 >>
stream
BT
/F1 12 Tf
72 720 Td
(ToUnicode CMap Test Document) Tj
-72 -720 Td
72 700 Td
(This PDF has a ToUnicode mapping for correct extraction.) Tj
-72 -700 Td
72 680 Td
(Unicode text should be extractable from this PDF.) Tj
-72 -680 Td
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
endobj
6 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000117 00000 n
0000000290 00000 n
0000000574 00000 n
0000000672 00000 n
trailer
<< /Size 7 /Root 1 0 R >>
startxref
772
%%EOF
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

-41
View File
@@ -1,41 +0,0 @@
services:
gateway:
build:
context: .
dockerfile: gateway/Dockerfile
network: host
image: pdf-engine-gateway:dev
container_name: pdf-engine-gateway
environment:
PDFENGINE_ENVIRONMENT: dev
PDFENGINE_ENGINE_AVAILABLE: "true"
PORT: 8765
ports:
- "8765:8765"
volumes:
- ./gateway:/home/app
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8765/health').read()" ]
interval: 20s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: development
image: pdf-engine-frontend:dev
container_name: pdf-engine-frontend
environment:
VITE_GATEWAY_URL: http://localhost:8765
ports:
- "5173:5173"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- gateway
restart: unless-stopped
View File
+273
View File
@@ -0,0 +1,273 @@
# Phase 0 — Infrastructure
> Goal of Phase 0: **make the entire stack compile and run on Linux, macOS, and
> Windows.** No editing, no rendering features. Success = the engine library
> builds clean on all three platforms and the smoke test passes. Phase 1 does
> not begin until Gate G0 and G0b are reached.
## Phase 0 task board
| # | Task | Owner | This session |
|---|------|-------|--------------|
| 1 | CMake root + vcpkg + CI/CD pipeline | Dev 1 | **Done — scaffolded** |
| 2 | PDFium build (depot_tools + GN + Ninja) | Dev 1 | Build scripts staged; not yet run |
| 3 | Skia build integration | Dev 2 | Not started |
| 4 | FreeType + HarfBuzz vcpkg integration | Dev 3 | In manifest; wrappers not started |
| 5 | FastAPI service scaffolding | Dev 1 | Placeholder dir only |
| 6 | React + TypeScript frontend scaffolding | Dev 2 | Placeholder dir only |
| 7 | WASM hello-world build (Emscripten) | Dev 1 | Toolchain hook + preset stubbed |
| 8 | Frozen interface contracts (Gate G0b) | All | Placeholder header; **not designed** |
This session delivered **Task 1 in full** plus the repository structure for
everything else. Scope was deliberately limited to the build pipeline — see
"What is intentionally not done" below.
## What was built this session
```
Code/
├── CMakeLists.txt root build; refuses to configure without a pinned baseline
├── CMakePresets.json debug/release/asan per platform + a wasm stub
├── vcpkg.json dependency manifest (freetype, harfbuzz, spdlog, gtest)
├── .clang-format .clang-tidy style + naming rules from blueprint §16.1
├── .gitignore .gitattributes .editorconfig
├── cmake/
│ ├── pdfium.cmake turns the PDFium install tree into pdfium::pdfium
│ ├── CompilerWarnings.cmake high warning levels per compiler
│ ├── Sanitizers.cmake ASan/UBSan wiring
│ └── toolchains/wasm.cmake Emscripten hook (stub)
├── engine/
│ ├── CMakeLists.txt
│ ├── include/pdfengine/ public headers (version, umbrella, pdf_document placeholder)
│ ├── src/core/ engine_info.cpp — version/build introspection
│ ├── src/parser/ pdfium_loader — the ONLY FPDF_-allowed dir (Rule R2)
│ └── tests/ gtest smoke test backing Gate G0
├── third_party/pdfium/ from-source build scripts + pinned-ref file + args.gn
├── scripts/
│ ├── bootstrap.{sh,ps1} installs vcpkg, pins the dependency baseline
│ └── check_pdfium_boundary.{sh,ps1} Rule R2 enforcement
├── .github/workflows/ci.yml Linux/macOS/Windows build matrix + lint jobs
├── bindings/ gateway/ frontend/ wasm/ corpus/ placeholder dirs with READMEs
└── docs/phase0.md this file
```
## How to build (developer onboarding)
### Prerequisites
| Tool | Version | Notes |
|------|---------|-------|
| CMake | >= 3.25 | presets v6 |
| Ninja | any recent | the only generator used |
| C++ compiler | MSVC 19.36+ / GCC 13+ / Clang 16+ | needs C++23 |
| vcpkg | — | `scripts/bootstrap` installs it if `VCPKG_ROOT` is unset |
| Git | any recent | |
| clang-format | 22.1.5 | not natively packaged on Windows — `pip install clang-format==22.1.5` (CI is pinned to this exact version) |
> **Windows:** either Visual Studio 2022/2026 (with the *"Desktop development
> with C++"* workload) or **Build Tools 2026** (no IDE — installer product
> `Microsoft.VisualStudio.Product.BuildTools`) is supported. `cl.exe` is not on
> `PATH` by default — run builds from a *Developer PowerShell* or import
> `VC\Auxiliary\Build\vcvars64.bat` first. Build Tools is not a default
> `vswhere` product, so detection scripts need `vswhere -products *` (the
> PDFium build script already does this). CI uses `ilammy/msvc-dev-cmd`.
>
> `VCPKG_ROOT` set via `setx` (or the bootstrap script's persistent install)
> does **not** propagate into already-open shells — set `$env:VCPKG_ROOT`
> explicitly in that shell, or open a new terminal. PowerShell 5.1 is fine;
> `pwsh` (7+) is not required by anything in this repo.
### Steps
```sh
# 1. One-time setup — checks tools, installs vcpkg, pins the dependency baseline.
pwsh scripts/bootstrap.ps1 # Windows
./scripts/bootstrap.sh # Linux / macOS
# 2. Configure + build + test.
cmake --preset windows-debug # linux-debug | macos-debug
cmake --build --preset windows-debug
ctest --preset windows-debug
```
The first configure compiles the vcpkg dependencies (freetype, harfbuzz,
spdlog, gtest) — slow once, cached after.
### Building with PDFium
PDFium is built separately from source (Task 2):
```sh
# Pin the revision first — edit third_party/pdfium/pdfium.pinned (see its README).
pwsh third_party/pdfium/build_pdfium.ps1 # or .sh
```
Until then the engine builds with PDFium code paths `#ifdef`-ed out, which is
the correct Phase 0 default — it keeps the pipeline green while Task 2 runs.
On Linux/macOS, enabling PDFium is a single flag added to a debug build:
```sh
cmake --preset linux-debug -DPDFENGINE_WITH_PDFIUM=ON
```
On Windows there is more to it — see the next section.
### Windows + PDFium
PDFium's static-lib GN build forces the static CRT (`/MT`, `is_debug=false`)
and offers no knob for "static lib + dynamic CRT". The engine, vcpkg deps,
and PDFium must therefore all use the same static CRT, or the link dies with
`LNK2038: 'RuntimeLibrary' mismatch`. The repo is wired for this ("Option A,
all static CRT, release-flavored"):
- The hidden `windows-base` preset in [CMakePresets.json](../CMakePresets.json)
sets `VCPKG_TARGET_TRIPLET=x64-windows-static` (vcpkg deps as static lib +
static CRT — first configure rebuilds them, ~7 min one-time) and
`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>`.
- PDFium's own [args.gn](../third_party/pdfium/args.gn) keeps `is_debug=false`
(i.e. `/MT`).
- The PDFium-linked engine build must be **RelWithDebInfo, not Debug** — a
Debug engine is `/MTd` and still mismatches PDFium's `/MT`. The plain
`windows-debug -DPDFENGINE_WITH_PDFIUM=ON` recipe will not link; use the
`win-local-pdfium` user preset below.
#### `win-local-pdfium` user preset
`CMakeUserPresets.json` is git-ignored (build dirs are per-developer and live
outside OneDrive). Drop this preset in at `Code/CMakeUserPresets.json`, with
your own username in `binaryDir`:
```json
{
"version": 6,
"configurePresets": [
{
"name": "win-local-pdfium",
"inherits": "windows-release",
"binaryDir": "C:/Users/<you>/pdfeng-build/win-local-pdfium",
"cacheVariables": { "PDFENGINE_WITH_PDFIUM": "ON" }
}
],
"buildPresets": [
{ "name": "win-local-pdfium", "configurePreset": "win-local-pdfium" }
],
"testPresets": [
{ "name": "win-local-pdfium", "inherits": "common", "configurePreset": "win-local-pdfium" }
]
}
```
Then, from a shell with `vcvars64.bat` imported:
```powershell
cmake --preset win-local-pdfium
cmake --build --preset win-local-pdfium
ctest --preset win-local-pdfium
```
Success looks like `pdfengine_smoke.exe` linking cleanly and logging
`pdfium=on`. The build dir is intentionally outside OneDrive and on a
space-free path — see the OneDrive section below for why.
#### Gotcha: depot_tools shadows `ninja`
The PDFium build adds `depot_tools` to `PATH` and the depot_tools `ninja` /
`ninja.bat` are not real Ninja — they fail with:
```
Running ninja --version failed with unknown error
... CMAKE_CXX_COMPILER not set, after EnableLanguage
```
If `depot_tools` ended up on your persistent `PATH`, CMake will pick its
broken `ninja` for the engine build. Two fixes:
- **Short-term:** point CMake at the real Ninja explicitly, e.g.
`cmake --preset win-local-pdfium -D CMAKE_MAKE_PROGRAM=C:/path/to/real/ninja.exe`.
This caches, so only the first configure needs the flag.
- **Long-term (recommended):** keep `depot_tools` **off** the persistent
`PATH`. `third_party/pdfium/build_pdfium.ps1` already prepends it
per-run, so the PDFium build still works.
## Dependency pinning
The blueprint rule is *"pin all dependency versions on Day 1, never track
rolling HEAD."* Two mechanisms:
- **vcpkg deps** — `scripts/bootstrap` runs `vcpkg x-update-baseline
--add-initial-baseline`, which writes a `builtin-baseline` commit into
`vcpkg.json`. That pins the entire dependency registry to one commit. The
root `CMakeLists.txt` **refuses to configure** until this is present.
→ **The first commit to the repo must include the bootstrapped `vcpkg.json`**,
otherwise CI fails at the configure step (by design).
- **PDFium** — `third_party/pdfium/pdfium.pinned` holds an exact commit SHA.
The build script refuses to run while it is the placeholder. Rebases are a
deliberate, scheduled (quarterly) action.
## Engineering conventions
- **Naming** (`.clang-tidy`): `CamelCase` types, `camelBack` functions,
`snake_case` file names.
- **Errors**: `std::expected<T, E>` internally; `int error_code` across the C ABI.
- **Branches**: `main`, `develop`, `feature/*`, `release/*`.
- **Rule R2**: only `engine/src/parser/` may use raw `FPDF_*` APIs —
enforced by `scripts/check_pdfium_boundary.*` locally and in CI.
## What is intentionally NOT done this session
- **PDFium is not actually built** — scripts are staged; the revision needs to
be pinned and the (long) build run as the second half of Task 2.
- **No interface contracts** — `engine/include/pdfengine/pdf_document.hpp` is a
placeholder. `PdfDocument` / `PdfPage` are designed and frozen at **Gate G0b**
in an all-devs session; nothing proceeds until it is signed off.
- **Skia / FreeType / HarfBuzz wrappers** — FreeType + HarfBuzz are in the vcpkg
manifest and link-tested, but the actual wrappers are Dev 3's Phase 0/1 work.
Skia is Dev 2's task.
- **FastAPI / React / WASM** — placeholder directories only. WASM has a toolchain
hook and preset stub so the integration point exists (Rule R5: WASM never
blocks shipping).
## Gates ahead
| Gate | Criterion | Unblocks |
|------|-----------|----------|
| **G0** | All platforms build clean; PDFium + Skia + FreeType + HarfBuzz compile | Phase 1 |
| **G0b** | `PdfDocument` / `PdfPage` contracts locked by all 3 devs | Coding begins |
The CI `build` matrix is the automated half of G0. The smoke test
(`engine/tests/smoke_test.cpp`) is what it runs.
## OneDrive warning
This checkout lives under `OneDrive\Work\Maskan\PDF Editor\Code`. The path is
both OneDrive-synced **and** contains a space (`PDF Editor`). Both bite C++
builds:
1. **Sync churn** — build output is thousands of `.obj`/`.o` files. `out/` is
git-ignored, but OneDrive still tries to upload it.
2. **File locks** — OneDrive can hold a handle on a file mid-sync, causing
intermittent "permission denied" errors during compile or link.
3. **Spaces in build paths break tooling** — vcpkg/meson (harfbuzz) fail
with `LNK1181` when `vcpkg_installed` is under the spaced path, and
`depot_tools` / GN / Ninja `.bat` wrappers cannot handle a space in their
own path at all.
**On Windows, building inside the repo path is not viable** — put the build
dir outside OneDrive on a space-free path. The `win-local-pdfium` preset
above already does this (`C:/Users/<you>/pdfeng-build/...`); do the same for
any non-PDFium preset by overriding `binaryDir`:
```powershell
cmake --preset windows-release -B C:/Users/<you>/pdfeng-build/windows-release
```
The PDFium build is even stricter: `third_party/pdfium/build_pdfium.ps1`
takes a `PDFIUM_BUILD_ROOT` env var and hard-errors if it contains a space.
Use e.g. `C:\Users\<you>\pdfium-build`.
On Linux/macOS the OneDrive path is still a sync nuisance but the toolchain
itself is fine. Either point the build dir outside OneDrive, or exclude
`out/` and `vcpkg/` from sync, or pause sync while building.
Long term, the repository should live outside OneDrive on a real Git remote.
+15 -111
View File
@@ -1,65 +1,20 @@
# pdfengine — the C++23 PDF SDK core.
#
# Phase 0 scope: a minimal-but-real static library that compiles, links against
# its vcpkg dependencies, and exposes version/build introspection. Feature
# modules (parser, render, text, core) are filled in from Phase 1 onward.
# Generate the version header from the project version.
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/include/pdfengine/version.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated/pdfengine/version.hpp"
@ONLY)
find_package(PNG REQUIRED)
add_library(pdfengine OBJECT
add_library(pdfengine STATIC
src/core/engine_info.cpp
src/core/graphics_state.cpp
src/core/display_list.cpp
src/core/path_interpreter.cpp
src/core/skia_renderer.cpp
src/parser/content_stream_parser.cpp
src/parser/decoration_builder.cpp
src/text/selection.cpp
src/text/text_layout_engine.cpp
src/fonts/face/font_face.cpp
src/fonts/face/free_type_manager.cpp
src/fonts/loader/font_resolver.cpp
src/fonts/shaping/hb_shaper.cpp
src/fonts/cache/glyph_bitmap.cpp
src/fonts/cache/glyph_cache.cpp
src/fonts/pdf_fonts/types/truetype_font.cpp
src/fonts/pdf_fonts/types/type1_font.cpp
src/fonts/pdf_fonts/types/cid_font.cpp
src/fonts/pdf_fonts/font_loader.cpp
src/fonts/pdf_fonts/font_descriptor.cpp
src/fonts/pdf_fonts/font_fallback.cpp
src/fonts/pdf_fonts/font_subset.cpp
src/fonts/pdf_fonts/font_cmap_builder.cpp
src/fonts/pdf_fonts/embedded_font_reconstructor.cpp
src/fonts/pdf_fonts/encoding/encoding.cpp
src/fonts/pdf_fonts/encoding/tounicode_parser.cpp
src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp
src/parser/pdfium_loader.cpp
src/parser/pdfium_document.cpp
src/parser/pdfium_internal.cpp
src/parser/pdfium_reflow.cpp
src/parser/pdfium_page.cpp
src/parser/pdfium_page_model.cpp
src/parser/pdfium_fonts.cpp
src/parser/pdfium_edit_session.cpp
src/parser/pdfium_edit.cpp
src/parser/pdfium_edit_replace.cpp
src/parser/pdfium_edit_reflow.cpp
src/parser/pdfium_edit_annotations.cpp
src/parser/pdfium_edit_pages.cpp
src/parser/pdfium_edit_images.cpp
src/qpdf/qpdf_extractor.cpp
src/qpdf/qpdf_font_extractor.cpp
src/qpdf/qpdf_writer.cpp
src/qpdf/qpdf_resource_resolver.cpp
src/core/image_decoder.cpp
src/parser/lexer.cpp
src/parser/parser.cpp
src/parser/content_builder.cpp
src/serializer/content_serializer.cpp
src/serializer/ast_serializer.cpp
)
add_library(pdfengine::pdfengine ALIAS pdfengine)
set_target_properties(pdfengine PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(pdfengine
PUBLIC
@@ -69,47 +24,23 @@ target_include_directories(pdfengine
"${CMAKE_CURRENT_SOURCE_DIR}/src"
)
# Dependencies resolved by vcpkg.
# spdlog - logging, used now (engine blueprint §13.1).
# freetype/harfbuzz - linked now to prove the vcpkg toolchain end to end;
# actually exercised by Dev 3 from Phase 1 onward.
target_link_libraries(pdfengine
PUBLIC
spdlog::spdlog
PRIVATE
freetype
harfbuzz::harfbuzz
harfbuzz::harfbuzz-subset
PNG::PNG
nlohmann_json::nlohmann_json
)
# PDFium is optional in Phase 0 (built from source separately). When enabled,
# only this target gets the macro + link — and only src/parser/ uses it (R2).
if(PDFENGINE_WITH_PDFIUM)
target_link_libraries(pdfengine PRIVATE pdfium::pdfium)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_PDFIUM)
# PDFium statically bundles its own libjpeg, zlib, etc. which conflicts with vcpkg.
# We use LLD, so we can safely allow multiple definitions to pick the first one.
target_link_options(pdfengine PUBLIC "-Wl,--allow-multiple-definition")
endif()
if(EMSCRIPTEN)
target_compile_definitions(pdfengine PRIVATE PDFENGINE_FONT_DIR="/fonts")
else()
target_compile_definitions(pdfengine PRIVATE PDFENGINE_FONT_DIR="${CMAKE_SOURCE_DIR}/engine/assets/fonts")
endif()
if(PDFENGINE_WITH_SKIA)
target_link_libraries(pdfengine PRIVATE skia::skia)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_SKIA)
endif()
if(PDFENGINE_WITH_QPDF)
find_package(ZLIB REQUIRED)
find_package(JPEG REQUIRED)
if(NOT TARGET zs)
add_library(zs ALIAS ZLIB::ZLIB)
endif()
if(NOT TARGET jpeg)
add_library(jpeg ALIAS JPEG::JPEG)
endif()
target_link_libraries(pdfengine PRIVATE qpdf::libqpdf ZLIB::ZLIB JPEG::JPEG)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_QPDF)
target_compile_definitions(pdfengine PRIVATE PDFENGINE_WITH_PDFIUM)
endif()
pdfengine_set_warnings(pdfengine)
@@ -118,30 +49,3 @@ pdfengine_enable_sanitizers(pdfengine)
if(PDFENGINE_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(PDFENGINE_FUZZING)
set(PDFENGINE_FUZZ_SANITIZERS "fuzzer,address,undefined" CACHE STRING
"Sanitizer set for fuzzing (e.g. 'fuzzer,address,undefined' or just 'fuzzer')")
string(REPLACE "," ";" _fuzz_sans "${PDFENGINE_FUZZ_SANITIZERS}")
set(_fuzz_runtime_sans "")
foreach(_s IN LISTS _fuzz_sans)
if(NOT _s STREQUAL "fuzzer")
list(APPEND _fuzz_runtime_sans "${_s}")
endif()
endforeach()
list(JOIN _fuzz_runtime_sans "," _fuzz_runtime_str)
target_compile_options(pdfengine PRIVATE -fsanitize=fuzzer-no-link -fno-omit-frame-pointer)
if(_fuzz_runtime_str)
target_compile_options(pdfengine PRIVATE -fsanitize=${_fuzz_runtime_str})
target_link_options(pdfengine PUBLIC -fsanitize=${_fuzz_runtime_str})
endif()
add_executable(pdfengine_fuzz fuzz/fuzz_load.cpp)
target_link_libraries(pdfengine_fuzz PRIVATE pdfengine)
target_compile_options(pdfengine_fuzz PRIVATE
-fsanitize=${PDFENGINE_FUZZ_SANITIZERS} -fno-omit-frame-pointer)
target_link_options(pdfengine_fuzz PRIVATE
-fsanitize=${PDFENGINE_FUZZ_SANITIZERS})
endif()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-65
View File
@@ -1,65 +0,0 @@
# Fuzzing the PDF engine
`fuzz_load.cpp` is a libFuzzer harness that drives the full
**load → metadata → outline → render → text → annotations → hit-test → select**
path with arbitrary bytes. Combined with AddressSanitizer it surfaces crashes,
OOMs, and undefined behaviour in the parsing and rendering code.
Resource ceilings from `pdfengine/hardened_limits.h` keep the fuzzer focused on
logic bugs instead of trivial out-of-memory inputs (and those same ceilings now
guard the production render path against integer-overflow / OOM).
## Linux (primary)
Clang + libFuzzer + ASan is best supported on Linux. PDFium must be built with
the same Clang toolchain (so ASan is consistent across the static lib).
```bash
# Full ASan + coverage fuzzer
cmake --preset fuzz-linux
cmake --build --preset fuzz-linux
# If your PDFium static lib is NOT ASan-instrumented, use coverage-only:
cmake --preset fuzz-linux-nosan
cmake --build --preset fuzz-linux-nosan
# Run it against the downloaded corpus as a seed set
python scripts/fetch_corpus.py # populates corpus/fuzz/ (gitignored)
mkdir -p engine/fuzz/artifacts
./out/build/fuzz-linux/bin/pdfengine_fuzz \
-artifact_prefix=engine/fuzz/artifacts/ \
corpus/fuzz/ corpus/
```
`corpus/fuzz/` and `corpus/` are passed as seed corpora; new coverage-expanding
inputs are written back into the first directory. Crashes land in
`engine/fuzz/artifacts/` (gitignored).
## Windows (clang-cl)
Native Windows fuzzing needs a Clang toolchain *and* a PDFium static lib built
with the matching runtime. Configure with clang-cl and the existing
`x64-windows-static` triplet, then enable fuzzing:
```powershell
cmake -S . -B C:/Users/<you>/pdfeng-build/fuzz-win -G Ninja `
-DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl `
-DVCPKG_TARGET_TRIPLET=x64-windows-static `
-DPDFENGINE_FUZZING=ON -DPDFENGINE_WITH_PDFIUM=ON `
-DPDFENGINE_FUZZ_SANITIZERS=fuzzer `
--toolchain "$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake --build C:/Users/<you>/pdfeng-build/fuzz-win
```
Use `PDFENGINE_FUZZ_SANITIZERS=fuzzer` (coverage-only) on Windows unless the
whole dependency chain — including PDFium — is ASan-built, since mixing an
ASan binary with a non-ASan MSVC static lib does not link cleanly.
## Reproducing a crash
```bash
./pdfengine_fuzz engine/fuzz/artifacts/crash-<hash>
```
The ASan report points at the offending allocation/access; the input file is the
minimal reproducer (run with `-minimize_crash=1` to shrink further).
-43
View File
@@ -1,43 +0,0 @@
#include "pdfengine/hardened_limits.h"
#include "pdfengine/pdf_document.hpp"
#include <cstddef>
#include <cstdint>
#include <vector>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
using namespace pdfengine;
if (!limits::documentSizeOk(size)) return 0;
std::vector<uint8_t> bytes(data, data + size);
auto doc = PdfDocument::loadFromMemory(bytes, "");
if (!doc) return 0;
PdfDocument& d = **doc;
const int pages = d.pageCount();
if (!limits::pageCountOk(pages)) return 0;
(void)d.metadata();
(void)d.extractOutline();
const int limit = pages < 3 ? pages : 3;
for (int i = 0; i < limit; ++i) {
auto page = d.getPage(i);
if (!page) continue;
PdfPage& p = **page;
(void)p.render(72);
(void)p.extractText();
(void)p.extractAnnotations();
auto glyphs = p.orderedGlyphs();
if (glyphs && !glyphs->empty()) {
const auto& g = glyphs->front();
(void)p.hitGlyph(g.x, g.y);
(void)p.selectRange(g.x, g.y, g.x + 50.0, g.y + 20.0);
}
}
return 0;
}
-42
View File
@@ -1,42 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <memory>
namespace pdfengine {
enum class AstNodeType {
Number,
Name,
String,
HexString,
Boolean,
Null,
Array,
Dictionary
};
class AstNode {
public:
AstNodeType type;
std::string stringValue;
std::vector<uint8_t> bytesValue;
double numberValue = 0.0;
bool boolValue = false;
std::vector<std::shared_ptr<AstNode>> arrayItems;
std::unordered_map<std::string, std::shared_ptr<AstNode>> dictItems;
AstNode() = default;
explicit AstNode(AstNodeType t) : type(t) {}
};
struct Operation {
std::string op;
std::vector<std::shared_ptr<AstNode>> operands;
};
}
@@ -1,75 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <pdfengine/graphics_state.hpp>
#include <pdfengine/path.hpp>
namespace pdfengine {
enum class ContentObjectType {
Text,
Path,
Image,
Unknown
};
enum class XObjectType {
Image,
Form,
Pattern,
Unknown
};
class ContentObject {
public:
virtual ~ContentObject() = default;
virtual ContentObjectType getType() const = 0;
};
class TextObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Text; }
std::string text;
std::string fontName;
double fontSize = 0.0;
double tm[6] = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
};
class ImageObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Image; }
std::string name;
int width = 0;
int height = 0;
std::string colorSpace;
std::string filter;
int bitsPerComponent = 8;
bool hasSoftMask = false;
std::vector<uint8_t> pixelData;
Matrix transform;
};
enum class PathPaintOp {
Stroke,
Fill,
FillStroke
};
class PathObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Path; }
Path path;
PathPaintOp paintOp = PathPaintOp::Stroke;
FillRule fillRule = FillRule::NonZero;
Matrix transform;
};
}
@@ -1,27 +0,0 @@
#pragma once
#include <string>
#include <vector>
namespace pdfengine {
struct ExtractedStream {
std::string rawContent;
std::string decodedContent;
int pageIndex = 0;
std::vector<std::string> filters;
bool compressed = false;
bool multiStream = false;
};
struct StreamVerification {
bool hasBT = false;
bool hasET = false;
bool hasTf = false;
bool hasTj = false;
bool multiStream = false;
};
StreamVerification verifyContentStream(const ExtractedStream& stream);
}
-118
View File
@@ -1,118 +0,0 @@
#pragma once
#include <vector>
#include <memory>
#include <string>
#include <pdfengine/graphics_state.hpp>
#include <pdfengine/path.hpp>
#include <pdfengine/image.hpp>
namespace pdfengine {
class CommandVisitor;
struct Command {
virtual ~Command() = default;
virtual void accept(CommandVisitor& visitor) const = 0;
};
struct SaveStateCommand : public Command {
void accept(CommandVisitor& visitor) const override;
};
struct RestoreStateCommand : public Command {
void accept(CommandVisitor& visitor) const override;
};
struct SetTransformCommand : public Command {
Matrix matrix;
explicit SetTransformCommand(const Matrix& m) : matrix(m) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillRectCommand : public Command {
float x, y, width, height;
FillRectCommand(float _x, float _y, float w, float h) : x(_x), y(_y), width(w), height(h) {}
void accept(CommandVisitor& visitor) const override;
};
struct DrawTextCommand : public Command {
std::string text;
float x, y;
DrawTextCommand(std::string _text, float _x, float _y) : text(std::move(_text)), x(_x), y(_y) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillPathCommand : public Command {
Path path;
FillRule rule;
explicit FillPathCommand(Path _path, FillRule _rule = FillRule::NonZero) : path(std::move(_path)), rule(_rule) {}
void accept(CommandVisitor& visitor) const override;
};
struct StrokePathCommand : public Command {
Path path;
explicit StrokePathCommand(Path _path) : path(std::move(_path)) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillStrokePathCommand : public Command {
Path path;
FillRule rule;
explicit FillStrokePathCommand(Path _path, FillRule _rule = FillRule::NonZero) : path(std::move(_path)), rule(_rule) {}
void accept(CommandVisitor& visitor) const override;
};
struct DrawImageCommand : public Command {
ImageInfo image;
Matrix matrix;
float opacity;
DrawImageCommand(ImageInfo img, Matrix m, float op = 1.0f)
: image(std::move(img)), matrix(m), opacity(op) {}
void accept(CommandVisitor& visitor) const override;
};
class CommandVisitor {
public:
virtual ~CommandVisitor() = default;
virtual void visit(const SaveStateCommand& cmd) = 0;
virtual void visit(const RestoreStateCommand& cmd) = 0;
virtual void visit(const SetTransformCommand& cmd) = 0;
virtual void visit(const FillRectCommand& cmd) = 0;
virtual void visit(const DrawTextCommand& cmd) = 0;
virtual void visit(const FillPathCommand& cmd) = 0;
virtual void visit(const StrokePathCommand& cmd) = 0;
virtual void visit(const FillStrokePathCommand& cmd) = 0;
virtual void visit(const DrawImageCommand& cmd) = 0;
};
class DisplayList {
public:
DisplayList() = default;
void addCommand(std::unique_ptr<Command> cmd);
void replay(CommandVisitor& visitor) const;
void saveState();
void restoreState();
void setTransform(const Matrix& m);
void fillRect(float x, float y, float w, float h);
void drawText(const std::string& text, float x, float y);
void fillPath(const Path& path, FillRule rule = FillRule::NonZero);
void strokePath(const Path& path);
void fillStrokePath(const Path& path, FillRule rule = FillRule::NonZero);
void drawImage(const ImageInfo& image, const Matrix& m, float opacity = 1.0f);
[[nodiscard]] size_t size() const noexcept { return m_commands.size(); }
void clear() { m_commands.clear(); }
private:
std::vector<std::unique_ptr<Command>> m_commands;
};
}
-93
View File
@@ -1,93 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <expected>
#include <cstdint>
#include "pdfengine/pdf_document.hpp"
namespace pdfengine {
struct Rect {
double x = 0.0;
double y = 0.0;
double width = 0.0;
double height = 0.0;
};
using RectList = std::vector<Rect>;
struct ParagraphBounds {
Rect rect;
};
struct GlyphInfo {
uint32_t glyphId = 0;
uint32_t cluster = 0;
double x = 0.0;
double y = 0.0;
double advance = 0.0;
double width = 0.0;
double ascent = 0.0;
double descent = 0.0;
std::string text;
};
struct LineInfo {
int id = 0;
Rect rect;
double baselineY = 0.0;
};
struct CaretState {
int offset = 0;
Rect rect;
};
// Internal comprehensive layout state
struct LayoutResult {
ParagraphBounds bounds;
std::vector<LineInfo> lines;
std::vector<GlyphInfo> glyphs;
std::vector<Rect> selectionRects;
CaretState caret;
RectList dirtyRects;
};
// Stable, lightweight view for WASM export
struct LayoutView {
std::vector<LineInfo> lines;
std::vector<GlyphInfo> glyphs;
CaretState caret;
RectList dirtyRects;
};
class EditSession {
public:
virtual ~EditSession() = default;
static std::shared_ptr<EditSession> StartEditSession(
std::shared_ptr<PdfDocument> doc,
int pageIndex,
const std::string& paraId
);
// Returns a stable, lightweight view of the layout
virtual LayoutView GetLayoutView() const = 0;
// Geometry queries against the cached layout
// offset represents the caret insertion point (between glyphs)
virtual int HitTest(double x, double y) const = 0;
virtual Rect GetCaretRect(int offset) const = 0;
virtual std::vector<Rect> GetSelectionRects(int startOffset, int endOffset) const = 0;
// Mutates paragraph, marks cache dirty, recalculates
virtual void ApplyEdit(const std::string& editOpJson) = 0;
// Rendering & Lifecycle
virtual std::vector<uint8_t> RenderDirtyRegion(int dpi, const Rect& region) const = 0;
virtual bool CommitEdit() = 0;
virtual void CancelEdit() = 0;
};
} // namespace pdfengine
@@ -1,51 +0,0 @@
#pragma once
#include <vector>
#include <stdexcept>
namespace pdfengine {
struct Matrix {
float a = 1.0f, b = 0.0f;
float c = 0.0f, d = 1.0f;
float e = 0.0f, f = 0.0f;
Matrix() = default;
Matrix(float _a, float _b, float _c, float _d, float _e, float _f)
: a(_a), b(_b), c(_c), d(_d), e(_e), f(_f) {}
[[nodiscard]] Matrix multiply(const Matrix& other) const noexcept;
void transform(float& x, float& y) const noexcept;
};
struct Color {
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
};
struct GraphicsState {
Matrix ctm;
Color fillColor;
Color strokeColor;
float lineWidth = 1.0f;
};
class GraphicsStateStack {
public:
GraphicsStateStack();
void push();
void pop();
[[nodiscard]] GraphicsState& current();
[[nodiscard]] const GraphicsState& current() const;
private:
std::vector<GraphicsState> m_stack;
};
}
@@ -1,43 +0,0 @@
#ifndef PDFENGINE_HARDENED_LIMITS_H
#define PDFENGINE_HARDENED_LIMITS_H
#include <cstdint>
namespace pdfengine::limits {
inline constexpr std::uint64_t kMaxDocumentBytes = 1ull << 30;
inline constexpr double kMaxPageDimensionPt = 200'000.0;
inline constexpr int kMaxPageCount = 100'000;
inline constexpr int kMaxObjects = 5'000'000;
inline constexpr std::int64_t kMaxRasterPixels = 256ll * 1024 * 1024;
inline constexpr bool pageDimensionsOk(double widthPt, double heightPt) noexcept {
return widthPt > 0.0 && heightPt > 0.0 && widthPt <= kMaxPageDimensionPt &&
heightPt <= kMaxPageDimensionPt;
}
inline constexpr bool rasterSizeOk(std::int64_t widthPx, std::int64_t heightPx) noexcept {
if (widthPx <= 0 || heightPx <= 0) return false;
if (widthPx > kMaxRasterPixels || heightPx > kMaxRasterPixels) return false;
return widthPx * heightPx <= kMaxRasterPixels;
}
inline constexpr bool documentSizeOk(std::uint64_t bytes) noexcept {
return bytes > 0 && bytes <= kMaxDocumentBytes;
}
inline constexpr bool pageCountOk(int pages) noexcept {
return pages >= 0 && pages <= kMaxPageCount;
}
inline constexpr bool objectCountOk(int objects) noexcept {
return objects >= 0 && objects <= kMaxObjects;
}
}
#endif
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include <vector>
#include <cstdint>
namespace pdfengine {
enum class ColorSpace {
DeviceGray,
DeviceRGB,
DeviceCMYK,
Indexed
};
struct ImageInfo {
int width = 0;
int height = 0;
int channels = 4;
std::vector<uint8_t> pixelData;
};
}
-69
View File
@@ -1,69 +0,0 @@
#pragma once
#include <vector>
namespace pdfengine {
enum class FillRule {
NonZero,
EvenOdd
};
// Basic point structure
struct Point {
float x = 0.0f;
float y = 0.0f;
};
class Path {
public:
enum class Verb {
MoveTo,
LineTo,
CubicBezierTo,
Close
};
struct Segment {
Verb verb;
Point points[3];
};
Path() = default;
void moveTo(float x, float y) {
m_segments.push_back({Verb::MoveTo, {{x, y}, {}, {}}});
}
void lineTo(float x, float y) {
m_segments.push_back({Verb::LineTo, {{x, y}, {}, {}}});
}
void cubicTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) {
m_segments.push_back({Verb::CubicBezierTo, {{cp1x, cp1y}, {cp2x, cp2y}, {x, y}}});
}
void close() {
m_segments.push_back({Verb::Close, {{}, {}, {}}});
}
void addRect(float x, float y, float w, float h) {
moveTo(x, y);
lineTo(x + w, y);
lineTo(x + w, y + h);
lineTo(x, y + h);
close();
}
void clear() {
m_segments.clear();
}
[[nodiscard]] const std::vector<Segment>& segments() const { return m_segments; }
[[nodiscard]] bool empty() const { return m_segments.empty(); }
private:
std::vector<Segment> m_segments;
};
}
@@ -1,14 +0,0 @@
#pragma once
#include <pdfengine/content_object.hpp>
#include <pdfengine/display_list.hpp>
namespace pdfengine {
// PathObjectInterpreter converts a PathObject into DisplayList commands
class PathObjectInterpreter {
public:
static void interpret(const PathObject& pathObj, DisplayList& displayList);
};
} // namespace pdfengine
+20 -297
View File
@@ -1,303 +1,26 @@
// pdfengine — document API.
//
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PLACEHOLDER. This header is the deliverable of Phase 0 Gate G0b: │
// │ "Frozen interface contracts" — the PdfDocument / PdfPage API structs │
// │ reviewed and locked by all three developers before Phase 1 coding. │
// │ │
// │ Do NOT fill in method signatures yet. Nothing proceeds until the │
// │ contract is signed off (see docs/phase0.md, Gate G0b). │
// │ │
// │ Rule R2: only engine/src/parser/ may touch raw FPDF_* PDFium APIs. │
// │ Everything else in the codebase goes through these types. │
// └─────────────────────────────────────────────────────────────────────────┘
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <cstdint>
#include <array>
#if __has_include(<expected>)
#include <expected>
#elif __has_include(<tl/expected.hpp>)
#include <tl/expected.hpp>
namespace std {
using tl::expected;
using tl::unexpected;
}
#endif
namespace pdfengine {
enum class EngineError {
FileNotFound,
InvalidFormat,
PasswordRequired,
InvalidPassword,
PageOutOfBounds,
RenderFailed,
WriteFailed,
Unknown
};
// Opaque, owning handle to a parsed PDF document.
// Full definition + API land at Gate G0b.
class PdfDocument;
struct DocumentMetadata {
std::string title;
std::string author;
std::string creator;
std::string producer;
std::string creationDate;
std::string modificationDate;
};
// Opaque view onto a single page of a PdfDocument.
// Full definition + API land at Gate G0b.
class PdfPage;
struct DocumentPermissions {
bool isEncrypted = false;
std::string encryption = "None";
int securityRevision = -1;
bool ownerUnlocked = false;
bool canPrint = true;
bool canPrintHighRes = true;
bool canModify = true;
bool canCopy = true;
bool canAnnotate = true;
bool canFillForms = true;
bool canExtractForAccessibility = true;
bool canAssemble = true;
};
struct PageImage {
int width;
int height;
std::vector<uint8_t> data;
};
struct Point2D {
double x;
double y;
};
struct DevicePoint {
int x;
int y;
};
struct InvalidatedRegion {
int pageIndex;
double x;
double y;
double width;
double height;
};
struct GlyphBounds {
std::string text;
double x;
double y;
double w;
double h;
double fontSize;
};
struct HitResult {
int glyphIndex = -1;
int caret = 0;
int line = -1;
};
struct TextSelection {
int startGlyph = 0;
int endGlyph = 0;
std::string text;
std::vector<GlyphBounds> rects;
};
struct FontInfo {
std::string fontName;
std::string type;
bool isEmbedded = false;
bool isSubset = false;
bool isVertical = false;
std::string encoding;
bool hasToUnicode = false;
std::string cmapName;
std::string cidSystemInfo;
std::string subsetTag;
std::string sourceType;
std::string substitutedFrom;
std::string substitutedTo;
std::string normalizedFamily;
std::string internalFontId;
uint32_t flags = 0;
double ascent = 0.0;
double descent = 0.0;
double capHeight = 0.0;
};
struct Glyph {
std::string text;
uint32_t unicode = 0;
std::string fontName;
uint32_t flags = 0;
double fontSize = 0.0;
double originX = 0.0;
double originY = 0.0;
double bboxX = 0.0, bboxY = 0.0, bboxW = 0.0, bboxH = 0.0;
double angle = 0.0;
int pageObjectIndex = -1;
int srcIndex = 0;
};
struct TextRun {
std::string text;
std::string fontName;
uint32_t flags = 0;
double fontSize = 0.0;
std::string internalFontId;
bool isEmbedded = false;
std::string type;
std::vector<Glyph> glyphs;
std::vector<int> objectIndices;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
std::string fillColor = "#000000";
std::string paraId;
std::string fontFidelity = "exact";
};
struct TextLine {
std::vector<TextRun> runs;
std::vector<Glyph> glyphs;
double angle = 0.0;
double baselineY = 0.0;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
};
struct Paragraph {
std::vector<TextLine> lines;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
};
struct PageModel {
std::vector<Paragraph> paragraphs;
double width = 0.0;
double height = 0.0;
int pageIndex = 0;
};
class PdfPage {
public:
virtual ~PdfPage() = default;
[[nodiscard]] virtual double width() const noexcept = 0;
[[nodiscard]] virtual double height() const noexcept = 0;
[[nodiscard]] virtual std::expected<PageImage, EngineError> render(int dpi = 96) const = 0;
[[nodiscard]] virtual std::expected<PageImage, EngineError>
renderRegionRaw(int dpi, double yTopPt, double heightPt) const {
(void)dpi; (void)yTopPt; (void)heightPt;
return std::unexpected(EngineError::Unknown);
}
[[nodiscard]] virtual std::expected<PageImage, EngineError>
renderTile(int dpi, double xPt, double yPt, double wPt, double hPt) const {
(void)dpi; (void)xPt; (void)yPt; (void)wPt; (void)hPt;
return std::unexpected(EngineError::Unknown);
}
[[nodiscard]] virtual std::expected<std::string, EngineError> extractText() const = 0;
[[nodiscard]] virtual std::expected<std::vector<GlyphBounds>, EngineError> extractTextWithBounds() const = 0;
[[nodiscard]] std::expected<std::vector<GlyphBounds>, EngineError> orderedGlyphs() const;
[[nodiscard]] std::expected<HitResult, EngineError> hitGlyph(double x, double y) const;
[[nodiscard]] std::expected<TextSelection, EngineError>
selectRange(double ax, double ay, double bx, double by) const;
[[nodiscard]] virtual std::expected<PageModel, EngineError> extractDocumentModel() const = 0;
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError> getFonts() const = 0;
[[nodiscard]] virtual std::expected<std::vector<std::string>, EngineError> extractAnnotationsText() const = 0;
struct AnnotationInfo {
std::string id;
std::string type;
double x = 0.0, y = 0.0, width = 0.0, height = 0.0;
std::string color;
std::string author;
std::string content;
std::string timestamp;
int pageIndex = 0;
double thickness = 0.0;
std::vector<std::vector<Point2D>> paths;
std::vector<std::array<Point2D, 4>> quadPoints;
std::string fieldName;
std::string fieldValue;
std::string fieldType;
int fieldFlags = 0;
std::vector<std::string> fieldOptions;
};
[[nodiscard]] virtual std::expected<std::vector<AnnotationInfo>, EngineError> extractAnnotations() const = 0;
[[nodiscard]] virtual std::expected<double, EngineError> getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const = 0;
[[nodiscard]] virtual DevicePoint pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
[[nodiscard]] virtual Point2D deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
[[nodiscard]] virtual std::expected<std::string, EngineError> extractDisplayListJson() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError> extractImageXObject(const std::string& name) const = 0;
};
namespace fonts::pdf_fonts { class Font; }
class PdfDocument {
public:
virtual ~PdfDocument() = default;
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
loadFromFile(const std::string& path, const std::string& password = "");
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
loadFromMemory(const std::vector<uint8_t>& data, const std::string& password = "");
[[nodiscard]] virtual int pageCount() const noexcept = 0;
[[nodiscard]] virtual DocumentMetadata metadata() const noexcept = 0;
[[nodiscard]] virtual DocumentPermissions permissions() const noexcept = 0;
struct OutlineItem {
std::string title;
int pageIndex = -1;
int level = 0;
};
[[nodiscard]] virtual std::expected<std::vector<OutlineItem>, EngineError> extractOutline() const = 0;
[[nodiscard]] virtual std::expected<std::shared_ptr<PdfPage>, EngineError>
getPage(int pageIndex) = 0;
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError>
getFonts(int startPage = 0, int endPage = -1) const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
getFontData(const std::string& internalFontId) const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
getReconstructedFontData(const std::string& internalFontId) { (void)internalFontId; return std::unexpected(EngineError::Unknown); }
virtual void registerAuxFont(const std::string& internalFontId, const std::vector<uint8_t>& sfnt) { (void)internalFontId; (void)sfnt; }
[[nodiscard]] virtual std::expected<std::shared_ptr<fonts::pdf_fonts::Font>, std::string>
getResolvedFont(const FontInfo& fontInfo) = 0;
virtual std::expected<std::vector<InvalidatedRegion>, EngineError> applyEdits(const std::string& editsJson) = 0;
[[nodiscard]] virtual std::string validateLayout(int pageIndex, const std::string& jsonStr) { (void)pageIndex; (void)jsonStr; return "{}"; }
[[nodiscard]] virtual std::string lastReflowLayout() const { return {}; }
[[nodiscard]] virtual bool lastReflowOverflowed() const { return false; }
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveIncremental() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveFull() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveFullForExport() const { return saveFull(); }
};
}
} // namespace pdfengine
+10 -3
View File
@@ -1,3 +1,8 @@
// pdfengine — public umbrella header for the PDF SDK core.
//
// Phase 0 surface only: version + build introspection. The real document API
// (PdfDocument / PdfPage) is frozen at Gate G0b and added in Phase 1 — see
// pdf_document.hpp.
#pragma once
#include <pdfengine/version.hpp>
@@ -5,14 +10,16 @@
namespace pdfengine {
// Human-readable engine version, e.g. "0.1.0".
[[nodiscard]] std::string_view engineVersion() noexcept;
// One-line build descriptor, e.g. "pdfengine 0.1.0 (pdfium=off)".
[[nodiscard]] std::string_view engineBuildInfo() noexcept;
// True if this build was compiled and linked against the PDFium parser core.
[[nodiscard]] bool engineHasPdfium() noexcept;
[[nodiscard]] bool engineHasSkia() noexcept;
// Emits engineBuildInfo() through spdlog at info level.
void engineLogBuildInfo();
}
} // namespace pdfengine
@@ -1,30 +0,0 @@
#pragma once
#include <pdfengine/display_list.hpp>
#include <pdfengine/graphics_state.hpp>
class SkCanvas;
namespace pdfengine {
class SkiaRenderer : public CommandVisitor {
public:
explicit SkiaRenderer(SkCanvas* canvas);
void visit(const SaveStateCommand& cmd) override;
void visit(const RestoreStateCommand& cmd) override;
void visit(const SetTransformCommand& cmd) override;
void visit(const FillRectCommand& cmd) override;
void visit(const DrawTextCommand& cmd) override;
void visit(const FillPathCommand& cmd) override;
void visit(const StrokePathCommand& cmd) override;
void visit(const FillStrokePathCommand& cmd) override;
void visit(const DrawImageCommand& cmd) override;
void render(const DisplayList& displayList);
private:
SkCanvas* m_canvas;
GraphicsStateStack m_stateStack;
};
}
@@ -1,66 +0,0 @@
#pragma once
#include "pdfengine/edit_session.hpp"
#include "fonts/shaping/hb_shaper.hpp"
#include "fonts/face/font_face.hpp"
#include <string>
#include <vector>
#include <memory>
namespace pdfengine::text {
struct LayoutConstraints {
double columnLeft = 0.0;
double columnRight = 0.0;
double firstBaselineY = 0.0;
double leading = 0.0;
std::string align = "left";
double hangingIndent = 0.0;
};
class LineBreaker {
public:
LineBreaker(const LayoutConstraints& constraints);
// Processes a stream of shaped glyphs and applies line breaking
void ProcessRun(
const std::vector<fonts::ShapedGlyph>& shapedGlyphs,
const std::string& runText,
double fontSize,
const fonts::FontFace& face,
double scale
);
// Finalizes the layout and populates the LayoutResult
void Finalize(LayoutResult& outLayout);
private:
LayoutConstraints constraints_;
double currentX_;
double currentY_;
// Internal state for word wrapping
std::vector<GlyphInfo> currentLineGlyphs_;
std::vector<LineInfo> finishedLines_;
std::vector<GlyphInfo> allGlyphs_;
void CommitLine();
};
class TextLayoutEngine {
public:
TextLayoutEngine();
// Computes layout for a given text run (simplified for single font for now)
LayoutResult ComputeLayout(
const std::string& text,
const LayoutConstraints& constraints,
fonts::FontFace& fontFace,
double fontSize
);
private:
fonts::HbShaper shaper_;
};
} // namespace pdfengine::text
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
namespace pdfengine {
enum class TokenType {
Operator,
String,
HexString,
Name,
Number,
ArrayStart,
ArrayEnd,
DictStart,
DictEnd,
Boolean,
Null,
EndOfStream
};
struct Token {
TokenType type;
std::string stringValue;
std::vector<uint8_t> bytesValue;
double numberValue = 0.0;
size_t startOffset = 0;
size_t endOffset = 0;
};
}
-63
View File
@@ -1,63 +0,0 @@
#include <pdfengine/display_list.hpp>
namespace pdfengine {
void SaveStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void RestoreStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void SetTransformCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillRectCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DrawTextCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillPathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void StrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillStrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DrawImageCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DisplayList::addCommand(std::unique_ptr<Command> cmd) {
if (cmd) {
m_commands.push_back(std::move(cmd));
}
}
void DisplayList::replay(CommandVisitor& visitor) const {
for (const auto& cmd : m_commands) {
cmd->accept(visitor);
}
}
void DisplayList::saveState() {
addCommand(std::make_unique<SaveStateCommand>());
}
void DisplayList::restoreState() {
addCommand(std::make_unique<RestoreStateCommand>());
}
void DisplayList::setTransform(const Matrix& m) {
addCommand(std::make_unique<SetTransformCommand>(m));
}
void DisplayList::fillRect(float x, float y, float w, float h) {
addCommand(std::make_unique<FillRectCommand>(x, y, w, h));
}
void DisplayList::drawText(const std::string& text, float x, float y) {
addCommand(std::make_unique<DrawTextCommand>(text, x, y));
}
void DisplayList::fillPath(const Path& path, FillRule rule) {
addCommand(std::make_unique<FillPathCommand>(path, rule));
}
void DisplayList::strokePath(const Path& path) {
addCommand(std::make_unique<StrokePathCommand>(path));
}
void DisplayList::fillStrokePath(const Path& path, FillRule rule) {
addCommand(std::make_unique<FillStrokePathCommand>(path, rule));
}
void DisplayList::drawImage(const ImageInfo& image, const Matrix& m, float opacity) {
addCommand(std::make_unique<DrawImageCommand>(image, m, opacity));
}
}
+1 -10
View File
@@ -14,20 +14,11 @@ bool engineHasPdfium() noexcept {
return parser::pdfiumAvailable();
}
bool engineHasSkia() noexcept {
#ifdef PDFENGINE_WITH_SKIA
return true;
#else
return false;
#endif
}
std::string_view engineBuildInfo() noexcept {
static const std::string info = [] {
std::string s = "pdfengine ";
s += version_string;
s += parser::pdfiumAvailable() ? " (pdfium=on)" : " (pdfium=off)";
s += engineHasSkia() ? " (skia=on)" : " (skia=off)";
return s;
}();
return info;
@@ -37,4 +28,4 @@ void engineLogBuildInfo() {
spdlog::info("{}", engineBuildInfo());
}
}
} // namespace pdfengine
-50
View File
@@ -1,50 +0,0 @@
#include <pdfengine/graphics_state.hpp>
namespace pdfengine {
Matrix Matrix::multiply(const Matrix& other) const noexcept {
return Matrix(
a * other.a + b * other.c,
a * other.b + b * other.d,
c * other.a + d * other.c,
c * other.b + d * other.d,
e * other.a + f * other.c + other.e,
e * other.b + f * other.d + other.f
);
}
void Matrix::transform(float& x, float& y) const noexcept {
float newX = a * x + c * y + e;
float newY = b * x + d * y + f;
x = newX;
y = newY;
}
GraphicsStateStack::GraphicsStateStack() {
m_stack.emplace_back();
}
void GraphicsStateStack::push() {
if (!m_stack.empty()) {
m_stack.push_back(m_stack.back());
} else {
m_stack.emplace_back();
}
}
void GraphicsStateStack::pop() {
if (m_stack.size() > 1) {
m_stack.pop_back();
} else {
}
}
GraphicsState& GraphicsStateStack::current() {
return m_stack.back();
}
const GraphicsState& GraphicsStateStack::current() const {
return m_stack.back();
}
}
-324
View File
@@ -1,324 +0,0 @@
#include "image_decoder.hpp"
#include <algorithm>
#include <csetjmp>
#include <cstdio>
#include <cmath>
#include <iostream>
#include <jpeglib.h>
namespace {
std::string nameValue(QPDFObjectHandle dict, const char* key) {
if (!dict.isDictionary() || !dict.hasKey(key)) {
return {};
}
auto value = dict.getKey(key);
if (value.isName()) {
return value.getName();
}
if (value.isArray() && value.getArrayNItems() > 0 && value.getArrayItem(0).isName()) {
return value.getArrayItem(0).getName();
}
return {};
}
int intValue(QPDFObjectHandle dict, const char* key, int fallback) {
if (!dict.isDictionary() || !dict.hasKey(key)) {
return fallback;
}
return static_cast<int>(dict.getKey(key).getNumericValue());
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4324)
#endif
struct JpegErrorManager {
jpeg_error_mgr pub;
std::jmp_buf jump;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
void jpegErrorExit(j_common_ptr cinfo) {
auto* manager = reinterpret_cast<JpegErrorManager*>(cinfo->err);
longjmp(manager->jump, 1);
}
}
namespace pdfengine {
std::vector<uint8_t> ImageDecoder::decode(QPDFObjectHandle imageStream,
const std::string& colorSpace,
int width,
int height,
int bitsPerComponent,
const std::string& filter) {
if (!imageStream.isStream()) {
return {};
}
const bool isJpeg = (filter == "/DCTDecode");
std::shared_ptr<Buffer> buffer;
if (isJpeg) {
buffer = imageStream.getRawStreamData();
} else {
buffer = imageStream.getStreamData();
}
if (!buffer) {
return {};
}
std::vector<unsigned char> rawBytes(buffer->getBuffer(), buffer->getBuffer() + buffer->getSize());
std::vector<uint8_t> rgba;
if (isJpeg) {
rgba = decodeJpeg(rawBytes);
} else {
if (bitsPerComponent != 8) {
std::cerr << "Warning: bitsPerComponent " << bitsPerComponent
<< " not fully supported yet for raw images.\n";
}
if (colorSpace == "/DeviceGray") {
rgba = convertGrayToRgba(rawBytes, width, height);
} else if (colorSpace == "/DeviceRGB") {
rgba = convertRgbToRgba(rawBytes, width, height);
} else if (colorSpace == "/DeviceCMYK") {
rgba = convertCmykToRgba(rawBytes, width, height);
} else {
std::cerr << "Warning: Unsupported color space " << colorSpace
<< ", falling back to RGB extraction.\n";
rgba = convertRgbToRgba(rawBytes, width, height);
}
}
applySoftMask(imageStream, rgba, width, height);
return rgba;
}
std::vector<uint8_t> ImageDecoder::decodeJpeg(const std::vector<unsigned char>& jpegBytes) {
if (jpegBytes.empty()) {
return {};
}
jpeg_decompress_struct cinfo{};
JpegErrorManager jerr{};
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4611)
#endif
if (setjmp(jerr.jump)) {
jpeg_destroy_decompress(&cinfo);
std::cerr << "Error: Failed to decode JPEG image stream\n";
return {};
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, const_cast<unsigned char*>(jpegBytes.data()),
static_cast<unsigned long>(jpegBytes.size()));
jpeg_read_header(&cinfo, TRUE);
const bool cmykSource = cinfo.jpeg_color_space == JCS_CMYK ||
cinfo.jpeg_color_space == JCS_YCCK;
cinfo.out_color_space = cmykSource ? JCS_CMYK : JCS_RGB;
jpeg_start_decompress(&cinfo);
const int width = static_cast<int>(cinfo.output_width);
const int height = static_cast<int>(cinfo.output_height);
const int components = static_cast<int>(cinfo.output_components);
const int rowStride = width * components;
std::vector<uint8_t> rgba(static_cast<size_t>(width) * height * 4);
std::vector<JSAMPLE> row(static_cast<size_t>(rowStride));
while (cinfo.output_scanline < cinfo.output_height) {
JSAMPROW rowPointer = row.data();
const int y = static_cast<int>(cinfo.output_scanline);
jpeg_read_scanlines(&cinfo, &rowPointer, 1);
for (int x = 0; x < width; ++x) {
const auto src = static_cast<size_t>(x) * components;
const auto dst = (static_cast<size_t>(y) * width + x) * 4;
if (components == 1) {
const uint8_t gray = row[src];
rgba[dst + 0] = gray;
rgba[dst + 1] = gray;
rgba[dst + 2] = gray;
} else if (components == 4) {
const float c = row[src + 0] / 255.0f;
const float m = row[src + 1] / 255.0f;
const float yv = row[src + 2] / 255.0f;
const float k = row[src + 3] / 255.0f;
rgba[dst + 0] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - c) * (1.0f - k), 0.0f, 255.0f));
rgba[dst + 1] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - m) * (1.0f - k), 0.0f, 255.0f));
rgba[dst + 2] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - yv) * (1.0f - k), 0.0f, 255.0f));
} else {
rgba[dst + 0] = row[src + 0];
rgba[dst + 1] = row[src + 1];
rgba[dst + 2] = row[src + 2];
}
rgba[dst + 3] = 255;
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return rgba;
}
void ImageDecoder::applySoftMask(QPDFObjectHandle imageStream,
std::vector<uint8_t>& rgba,
int width,
int height) {
if (rgba.size() != static_cast<size_t>(width) * height * 4) {
return;
}
auto dict = imageStream.getDict();
if (!dict.isDictionary() || !dict.hasKey("/SMask")) {
return;
}
auto smask = dict.getKey("/SMask");
if (!smask.isStream()) {
return;
}
auto smaskDict = smask.getDict();
const int maskWidth = intValue(smaskDict, "/Width", 0);
const int maskHeight = intValue(smaskDict, "/Height", 0);
if (maskWidth != width || maskHeight != height) {
std::cerr << "Warning: Soft mask dimensions do not match image dimensions.\n";
return;
}
const int maskBpc = intValue(smaskDict, "/BitsPerComponent", 8);
const std::string maskColorSpace = nameValue(smaskDict, "/ColorSpace").empty()
? "/DeviceGray"
: nameValue(smaskDict, "/ColorSpace");
const std::string maskFilter = nameValue(smaskDict, "/Filter");
auto maskRgba = decode(smask, maskColorSpace, maskWidth, maskHeight, maskBpc, maskFilter);
if (maskRgba.size() != rgba.size()) {
return;
}
for (int i = 0; i < width * height; ++i) {
rgba[static_cast<size_t>(i) * 4 + 3] = maskRgba[static_cast<size_t>(i) * 4];
}
}
std::vector<uint8_t> ImageDecoder::convertGrayToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i < std::min(static_cast<int>(rawBytes.size()), expectedSize); ++i) {
uint8_t gray = rawBytes[i];
rgba.push_back(gray);
rgba.push_back(gray);
rgba.push_back(gray);
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
std::vector<uint8_t> ImageDecoder::convertRgbToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height * 3;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i + 2 < std::min(static_cast<int>(rawBytes.size()), expectedSize); i += 3) {
rgba.push_back(rawBytes[i]);
rgba.push_back(rawBytes[i + 1]);
rgba.push_back(rawBytes[i + 2]);
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
std::vector<uint8_t> ImageDecoder::convertCmykToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height * 4;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i + 3 < std::min(static_cast<int>(rawBytes.size()), expectedSize); i += 4) {
float c = rawBytes[i] / 255.0f;
float m = rawBytes[i + 1] / 255.0f;
float y = rawBytes[i + 2] / 255.0f;
float k = rawBytes[i + 3] / 255.0f;
auto applyInk = [](float paper, float processColor, float amount) {
return paper * ((1.0f - amount) + amount * (processColor / 255.0f));
};
float r = 255.0f;
float g = 255.0f;
float b = 255.0f;
r = applyInk(r, 0.0f, c);
g = applyInk(g, 174.0f, c);
b = applyInk(b, 239.0f, c);
r = applyInk(r, 237.0f, m);
g = applyInk(g, 0.0f, m);
b = applyInk(b, 140.0f, m);
r = applyInk(r, 255.0f, y);
g = applyInk(g, 241.0f, y);
b = applyInk(b, 0.0f, y);
r = applyInk(r, 35.0f, k);
g = applyInk(g, 31.0f, k);
b = applyInk(b, 32.0f, k);
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(r), 0l, 255l)));
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(g), 0l, 255l)));
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(b), 0l, 255l)));
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <qpdf/QPDFObjectHandle.hh>
namespace pdfengine {
class ImageDecoder {
public:
static std::vector<uint8_t> decode(QPDFObjectHandle imageStream,
const std::string& colorSpace,
int width, int height,
int bitsPerComponent,
const std::string& filter);
private:
static std::vector<uint8_t> decodeJpeg(const std::vector<unsigned char>& jpegBytes);
static void applySoftMask(QPDFObjectHandle imageStream, std::vector<uint8_t>& rgba, int width, int height);
static std::vector<uint8_t> convertGrayToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
static std::vector<uint8_t> convertRgbToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
static std::vector<uint8_t> convertCmykToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
};
}
-28
View File
@@ -1,28 +0,0 @@
#include <pdfengine/path_interpreter.hpp>
namespace pdfengine {
void PathObjectInterpreter::interpret(const PathObject& pathObj, DisplayList& displayList) {
if (pathObj.path.empty()) {
return;
}
displayList.saveState();
displayList.setTransform(pathObj.transform);
switch (pathObj.paintOp) {
case PathPaintOp::Stroke:
displayList.strokePath(pathObj.path);
break;
case PathPaintOp::Fill:
displayList.fillPath(pathObj.path, pathObj.fillRule);
break;
case PathPaintOp::FillStroke:
displayList.fillStrokePath(pathObj.path, pathObj.fillRule);
break;
}
displayList.restoreState();
}
} // namespace pdfengine
-296
View File
@@ -1,296 +0,0 @@
#include "pdfengine/skia_renderer.hpp"
#ifdef PDFENGINE_WITH_SKIA
#include <include/core/SkCanvas.h>
#include <include/core/SkPaint.h>
#include <include/core/SkMatrix.h>
#include <include/core/SkFont.h>
#include <include/core/SkTypeface.h>
#include <include/core/SkPath.h>
#include <include/core/SkImage.h>
#include <include/core/SkData.h>
#include <include/core/SkImageInfo.h>
#endif
namespace pdfengine {
SkiaRenderer::SkiaRenderer(SkCanvas* canvas) : m_canvas(canvas) {}
void SkiaRenderer::render(const DisplayList& displayList) {
if (!m_canvas) return;
displayList.replay(*this);
}
void SkiaRenderer::visit(const SaveStateCommand& cmd) {
(void)cmd;
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) m_canvas->save();
#endif
m_stateStack.push();
}
void SkiaRenderer::visit(const RestoreStateCommand& cmd) {
(void)cmd;
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) m_canvas->restore();
#endif
m_stateStack.pop();
}
void SkiaRenderer::visit(const SetTransformCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) {
SkMatrix skMatrix;
skMatrix.setAll(
cmd.matrix.a, cmd.matrix.c, cmd.matrix.e,
cmd.matrix.b, cmd.matrix.d, cmd.matrix.f,
0.0f, 0.0f, 1.0f
);
m_canvas->concat(skMatrix);
}
#endif
m_stateStack.current().ctm = m_stateStack.current().ctm.multiply(cmd.matrix);
}
void SkiaRenderer::visit(const FillRectCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas) return;
SkPaint paint;
paint.setAntiAlias(true);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
SkRect rect = SkRect::MakeXYWH(cmd.x, cmd.y, cmd.width, cmd.height);
m_canvas->drawRect(rect, paint);
#endif
}
void SkiaRenderer::visit(const DrawTextCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas) return;
SkPaint paint;
paint.setAntiAlias(true);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
SkFont font(nullptr, 12.0f);
m_canvas->drawString(cmd.text.c_str(), cmd.x, cmd.y, font, paint);
#endif
}
void SkiaRenderer::visit(const FillPathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
if (cmd.rule == FillRule::EvenOdd) {
skPath.setFillType(SkPathFillType::kEvenOdd);
} else {
skPath.setFillType(SkPathFillType::kWinding);
}
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kFill_Style);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
m_canvas->drawPath(skPath, paint);
#endif
}
void SkiaRenderer::visit(const StrokePathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
paint.setStrokeWidth(1.0f);
m_canvas->drawPath(skPath, paint);
#endif
}
void SkiaRenderer::visit(const FillStrokePathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
if (cmd.rule == FillRule::EvenOdd) {
skPath.setFillType(SkPathFillType::kEvenOdd);
} else {
skPath.setFillType(SkPathFillType::kWinding);
}
const auto& color = m_stateStack.current().fillColor;
// First fill
SkPaint fillPaint;
fillPaint.setAntiAlias(true);
fillPaint.setStyle(SkPaint::kFill_Style);
fillPaint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
m_canvas->drawPath(skPath, fillPaint);
// Then stroke
SkPaint strokePaint;
strokePaint.setAntiAlias(true);
strokePaint.setStyle(SkPaint::kStroke_Style);
strokePaint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
strokePaint.setStrokeWidth(1.0f);
m_canvas->drawPath(skPath, strokePaint);
#endif
}
void SkiaRenderer::visit(const DrawImageCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.image.pixelData.empty()) return;
SkImageInfo info = SkImageInfo::Make(
cmd.image.width,
cmd.image.height,
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType
);
sk_sp<SkData> data = SkData::MakeWithCopy(
cmd.image.pixelData.data(),
cmd.image.pixelData.size()
);
sk_sp<SkImage> skImage = SkImages::RasterFromData(
info,
std::move(data),
cmd.image.width * 4
);
if (skImage) {
m_canvas->save();
SkMatrix skMatrix;
skMatrix.setAll(
cmd.matrix.a, cmd.matrix.c, cmd.matrix.e,
cmd.matrix.b, cmd.matrix.d, cmd.matrix.f,
0.0f, 0.0f, 1.0f
);
m_canvas->concat(skMatrix);
SkRect destRect = SkRect::MakeXYWH(0, 0, 1.0f, 1.0f);
SkPaint paint;
paint.setAlphaf(cmd.opacity);
m_canvas->drawImageRect(
skImage.get(),
destRect,
SkSamplingOptions(SkFilterMode::kLinear),
&paint
);
m_canvas->restore();
}
#endif
}
}
-1
View File
@@ -1 +0,0 @@
#include "fonts/cache/glyph_bitmap.hpp"
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <vector>
namespace pdfengine::fonts {
struct GlyphBitmap {
std::vector<unsigned char> pixels;
int width = 0;
int height = 0;
int bearingX = 0;
int bearingY = 0;
double advance = 0.0;
};
}
-118
View File
@@ -1,118 +0,0 @@
#include "fonts/cache/glyph_cache.hpp"
namespace pdfengine::fonts {
GlyphCache::GlyphCache(std::size_t capacity)
: capacity_(capacity) {
std::size_t num_shards = (capacity < 16) ? 1 : 16;
for (std::size_t i = 0; i < num_shards; ++i) {
shards_.push_back(std::make_unique<Shard>());
}
}
GlyphCache::~GlyphCache() = default;
std::optional<GlyphBitmap> GlyphCache::get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize) {
uint64_t fontId = fontFace.getId();
if (fontId == 0) {
return std::nullopt;
}
GlyphCacheKey key{fontId, glyphIndex, fontSize};
std::size_t shard_idx = getShardIndex(key);
auto& shard = *shards_[shard_idx];
std::lock_guard<std::mutex> lock(shard.mutex_);
auto it = shard.cache_map_.find(key);
if (it == shard.cache_map_.end()) {
shard.misses_++;
return std::nullopt;
}
shard.hits_++;
shard.lru_list_.splice(shard.lru_list_.begin(), shard.lru_list_, it->second.second);
return it->second.first;
}
void GlyphCache::insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap) {
uint64_t fontId = fontFace.getId();
if (fontId == 0) {
return;
}
GlyphCacheKey key{fontId, glyphIndex, fontSize};
std::size_t shard_idx = getShardIndex(key);
auto& shard = *shards_[shard_idx];
std::lock_guard<std::mutex> lock(shard.mutex_);
auto it = shard.cache_map_.find(key);
if (it != shard.cache_map_.end()) {
it->second.first = bitmap;
shard.lru_list_.splice(shard.lru_list_.begin(), shard.lru_list_, it->second.second);
return;
}
std::size_t shard_capacity = (capacity_ + shards_.size() - 1) / shards_.size();
if (shard.cache_map_.size() >= shard_capacity && shard_capacity > 0) {
GlyphCacheKey oldest = shard.lru_list_.back();
shard.cache_map_.erase(oldest);
shard.lru_list_.pop_back();
}
if (shard_capacity > 0) {
shard.lru_list_.push_front(key);
shard.cache_map_[key] = std::make_pair(bitmap, shard.lru_list_.begin());
}
}
std::size_t GlyphCache::size() const {
std::size_t total = 0;
for (const auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
total += shard->cache_map_.size();
}
return total;
}
std::size_t GlyphCache::capacity() const {
return capacity_;
}
void GlyphCache::clear() {
for (auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
shard->cache_map_.clear();
shard->lru_list_.clear();
shard->hits_ = 0;
shard->misses_ = 0;
}
}
double GlyphCache::hitRate() const {
std::size_t total_hits = 0;
std::size_t total_misses = 0;
for (const auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
total_hits += shard->hits_;
total_misses += shard->misses_;
}
std::size_t total = total_hits + total_misses;
if (total == 0) {
return 0.0;
}
return static_cast<double>(total_hits) / total;
}
void GlyphCache::resetStats() {
for (auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
shard->hits_ = 0;
shard->misses_ = 0;
}
}
}
-84
View File
@@ -1,84 +0,0 @@
#pragma once
#include "fonts/face/font_face.hpp"
#include "fonts/cache/glyph_bitmap.hpp"
#include <optional>
#include <unordered_map>
#include <list>
#include <cstddef>
#include <mutex>
#include <memory>
namespace pdfengine::fonts {
struct GlyphCacheKey {
uint64_t fontId;
unsigned int glyphIndex;
unsigned int fontSize;
bool operator==(const GlyphCacheKey& other) const {
return fontId == other.fontId &&
glyphIndex == other.glyphIndex &&
fontSize == other.fontSize;
}
};
struct GlyphCacheKeyHash {
std::size_t operator()(const GlyphCacheKey& key) const {
std::size_t h1 = std::hash<uint64_t>{}(key.fontId);
std::size_t h2 = std::hash<unsigned int>{}(key.glyphIndex);
std::size_t h3 = std::hash<unsigned int>{}(key.fontSize);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)) ^ (h3 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2));
}
};
class GlyphCache {
public:
explicit GlyphCache(std::size_t capacity);
~GlyphCache();
GlyphCache(const GlyphCache&) = delete;
GlyphCache& operator=(const GlyphCache&) = delete;
GlyphCache(GlyphCache&&) noexcept = default;
GlyphCache& operator=(GlyphCache&&) noexcept = default;
std::optional<GlyphBitmap> get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize);
void insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap);
std::size_t size() const;
std::size_t capacity() const;
void clear();
double hitRate() const;
void resetStats();
private:
std::size_t capacity_;
using CacheIterator = std::list<GlyphCacheKey>::iterator;
struct Shard {
std::size_t hits_ = 0;
std::size_t misses_ = 0;
std::list<GlyphCacheKey> lru_list_;
std::unordered_map<
GlyphCacheKey,
std::pair<GlyphBitmap, CacheIterator>,
GlyphCacheKeyHash
> cache_map_;
mutable std::mutex mutex_;
};
static constexpr std::size_t NUM_SHARDS = 16;
std::vector<std::unique_ptr<Shard>> shards_;
std::size_t getShardIndex(const GlyphCacheKey& key) const {
return GlyphCacheKeyHash{}(key) % shards_.size();
}
};
}

Some files were not shown because too many files have changed in this diff Show More