first commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# PDF Engine C++ formatting. Applied to engine/ and bindings/.
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
Standard: c++20
|
||||
ColumnLimit: 100
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
AccessModifierOffset: -4
|
||||
NamespaceIndentation: None
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
AlignAfterOpenBracket: Align
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBraces: Attach
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Regroup
|
||||
SortIncludes: CaseInsensitive
|
||||
SeparateDefinitionBlocks: Always
|
||||
SpaceAfterCStyleCast: true
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# PDF Engine static analysis + naming enforcement.
|
||||
# Naming rules mirror the engineering blueprint section 16.1:
|
||||
# PascalCase -> types, camelCase -> functions, snake_case -> file names.
|
||||
# clang-tidy enforces identifier casing below; file-name casing is a convention
|
||||
# checked in code review.
|
||||
---
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
performance-*,
|
||||
modernize-*,
|
||||
readability-identifier-naming,
|
||||
readability-redundant-*,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
-modernize-use-trailing-return-type,
|
||||
-bugprone-easily-swappable-parameters
|
||||
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: 'engine/(include|src)/.*'
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.TypeAliasCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.FunctionCase
|
||||
value: camelBack
|
||||
- key: readability-identifier-naming.MethodCase
|
||||
value: camelBack
|
||||
- key: readability-identifier-naming.NamespaceCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.VariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.ConstantCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.MacroDefinitionCase
|
||||
value: UPPER_CASE
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: CamelCase
|
||||
@@ -0,0 +1,30 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
|
||||
# C++ engine + CMake
|
||||
[*.{c,cc,cpp,h,hpp}]
|
||||
indent_size = 4
|
||||
|
||||
[{CMakeLists.txt,*.cmake}]
|
||||
indent_size = 2
|
||||
|
||||
# Web / config
|
||||
[*.{ts,tsx,js,jsx,json,yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
# Python gateway (PEP 8)
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
# Windows scripts keep CRLF
|
||||
[*.{ps1,bat,cmd}]
|
||||
end_of_line = crlf
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,39 @@
|
||||
# Normalize line endings: let git decide on commit, check out native.
|
||||
* text=auto
|
||||
|
||||
# Scripts that MUST keep a specific ending regardless of platform.
|
||||
*.sh text eol=lf
|
||||
*.bash text eol=lf
|
||||
*.ps1 text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
# Source files - always LF in the repo.
|
||||
*.c text eol=lf
|
||||
*.cc text eol=lf
|
||||
*.cpp text eol=lf
|
||||
*.h text eol=lf
|
||||
*.hpp text eol=lf
|
||||
*.cmake text eol=lf
|
||||
*.py text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.tsx text eol=lf
|
||||
*.json text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.md text eol=lf
|
||||
CMakeLists.txt text eol=lf
|
||||
|
||||
# Binary assets - never touch.
|
||||
*.pdf binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.webp binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.wasm binary
|
||||
*.lib binary
|
||||
*.a binary
|
||||
*.dll binary
|
||||
*.so binary
|
||||
*.dylib binary
|
||||
@@ -0,0 +1,106 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Static checks: Rule R2 boundary + formatting. Fast, gates the build matrix.
|
||||
# ---------------------------------------------------------------------------
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
|
||||
- name: clang-format
|
||||
run: |
|
||||
clang-format --version
|
||||
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:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
preset: linux-debug
|
||||
- os: macos-latest
|
||||
preset: macos-debug
|
||||
- os: windows-latest
|
||||
preset: windows-debug
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Set up MSVC environment
|
||||
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
|
||||
shell: bash
|
||||
run: mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"
|
||||
|
||||
- name: Cache vcpkg artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
|
||||
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: |
|
||||
if ! grep -q '"builtin-baseline"' vcpkg.json; then
|
||||
"$VCPKG_ROOT/vcpkg" x-update-baseline --add-initial-baseline
|
||||
fi
|
||||
|
||||
- name: Configure
|
||||
run: cmake --preset ${{ matrix.preset }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build --preset ${{ matrix.preset }}
|
||||
|
||||
- name: Test
|
||||
run: ctest --preset ${{ matrix.preset }}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# Build output
|
||||
/build/
|
||||
/out/
|
||||
**/build/
|
||||
**/cmake-build-*/
|
||||
CMakeUserPresets.json
|
||||
|
||||
# vcpkg
|
||||
/vcpkg_installed/
|
||||
**/vcpkg_installed/
|
||||
/vcpkg/
|
||||
# vcpkg binary cache (CI sets VCPKG_DEFAULT_BINARY_CACHE here; may appear locally too)
|
||||
/.vcpkg-cache/
|
||||
|
||||
# CMake compile database — lives in the build dir, but tooling often drops a copy
|
||||
# or symlink at the repo root.
|
||||
/compile_commands.json
|
||||
|
||||
# PDFium from-source build (depot_tools / GN / Ninja)
|
||||
/third_party/pdfium/depot_tools/
|
||||
/third_party/pdfium/checkout/
|
||||
/third_party/pdfium/install/
|
||||
/third_party/pdfium/.gclient*
|
||||
|
||||
# IDE / editor
|
||||
/.vs/
|
||||
/.vscode/
|
||||
!/.vscode/extensions.json
|
||||
!/.vscode/settings.shared.json
|
||||
/.idea/
|
||||
*.user
|
||||
*.suo
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
desktop.ini
|
||||
|
||||
# Office lock files (the "~$" prefix Word/Excel/PowerPoint write while a doc is open)
|
||||
~$*
|
||||
|
||||
# Python (gateway)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
/gateway/.venv/
|
||||
|
||||
# Node (frontend)
|
||||
node_modules/
|
||||
/frontend/dist/
|
||||
/frontend/.vite/
|
||||
|
||||
# WASM artifacts
|
||||
*.wasm
|
||||
*.wasm.map
|
||||
|
||||
# Logs / misc
|
||||
*.log
|
||||
|
||||
PDF Editor Timeline.xlsx
|
||||
@@ -0,0 +1,88 @@
|
||||
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")
|
||||
if(_pdfengine_baseline_err OR NOT _pdfengine_baseline)
|
||||
message(FATAL_ERROR
|
||||
"vcpkg.json has no 'builtin-baseline' — the dependency registry is not "
|
||||
"pinned.\n"
|
||||
"Run the bootstrap script first:\n"
|
||||
" Windows: pwsh scripts/bootstrap.ps1\n"
|
||||
" Unix: ./scripts/bootstrap.sh\n"
|
||||
"It installs vcpkg and runs 'vcpkg x-update-baseline --add-initial-baseline'.")
|
||||
endif()
|
||||
|
||||
project(PdfEngine
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Cross-platform PDF rendering and editing SDK"
|
||||
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")
|
||||
|
||||
# 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)
|
||||
|
||||
include(CompilerWarnings)
|
||||
include(Sanitizers)
|
||||
include(pdfium) # defines pdfium::pdfium when PDFENGINE_WITH_PDFIUM is ON
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Third-party dependencies (resolved by vcpkg via the manifest).
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(freetype CONFIG REQUIRED)
|
||||
find_package(harfbuzz CONFIG REQUIRED)
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
|
||||
if(PDFENGINE_BUILD_TESTS)
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
enable_testing()
|
||||
include(GoogleTest)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Subprojects.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_subdirectory(engine)
|
||||
|
||||
# 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}")
|
||||
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}")
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"version": 6,
|
||||
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"installDir": "${sourceDir}/out/install/${presetName}",
|
||||
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||
"PDFENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }
|
||||
},
|
||||
{
|
||||
"name": "asan",
|
||||
"hidden": true,
|
||||
"inherits": "debug",
|
||||
"cacheVariables": { "PDFENGINE_ENABLE_SANITIZERS": "ON" }
|
||||
},
|
||||
|
||||
{
|
||||
"name": "windows-base",
|
||||
"hidden": true,
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" },
|
||||
"architecture": { "value": "x64", "strategy": "external" },
|
||||
"cacheVariables": {
|
||||
"VCPKG_TARGET_TRIPLET": "x64-windows-static",
|
||||
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$<CONFIG:Debug>:Debug>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug",
|
||||
"displayName": "Windows • Debug",
|
||||
"inherits": ["debug", "windows-base"]
|
||||
},
|
||||
{
|
||||
"name": "windows-release",
|
||||
"displayName": "Windows • Release",
|
||||
"inherits": ["release", "windows-base"]
|
||||
},
|
||||
{
|
||||
"name": "windows-asan",
|
||||
"displayName": "Windows • Debug + ASan",
|
||||
"inherits": ["asan", "windows-base"]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "linux-debug",
|
||||
"displayName": "Linux • Debug",
|
||||
"inherits": "debug",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" }
|
||||
},
|
||||
{
|
||||
"name": "linux-release",
|
||||
"displayName": "Linux • Release",
|
||||
"inherits": "release",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" }
|
||||
},
|
||||
{
|
||||
"name": "linux-asan",
|
||||
"displayName": "Linux • Debug + ASan/UBSan",
|
||||
"inherits": "asan",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" }
|
||||
},
|
||||
|
||||
{
|
||||
"name": "macos-debug",
|
||||
"displayName": "macOS • Debug",
|
||||
"inherits": "debug",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" }
|
||||
},
|
||||
{
|
||||
"name": "macos-release",
|
||||
"displayName": "macOS • Release",
|
||||
"inherits": "release",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" }
|
||||
},
|
||||
{
|
||||
"name": "macos-asan",
|
||||
"displayName": "macOS • Debug + ASan/UBSan",
|
||||
"inherits": "asan",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" }
|
||||
},
|
||||
|
||||
{
|
||||
"name": "wasm",
|
||||
"displayName": "WASM • Emscripten (Phase 0 stub — validation only)",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"toolchainFile": "${sourceDir}/cmake/toolchains/wasm.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"PDFENGINE_BUILD_TESTS": "OFF",
|
||||
"PDFENGINE_WITH_PDFIUM": "OFF"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"buildPresets": [
|
||||
{ "name": "windows-debug", "configurePreset": "windows-debug" },
|
||||
{ "name": "windows-release", "configurePreset": "windows-release" },
|
||||
{ "name": "windows-asan", "configurePreset": "windows-asan" },
|
||||
{ "name": "linux-debug", "configurePreset": "linux-debug" },
|
||||
{ "name": "linux-release", "configurePreset": "linux-release" },
|
||||
{ "name": "linux-asan", "configurePreset": "linux-asan" },
|
||||
{ "name": "macos-debug", "configurePreset": "macos-debug" },
|
||||
{ "name": "macos-release", "configurePreset": "macos-release" },
|
||||
{ "name": "macos-asan", "configurePreset": "macos-asan" },
|
||||
{ "name": "wasm", "configurePreset": "wasm" }
|
||||
],
|
||||
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"output": { "outputOnFailure": true },
|
||||
"execution": { "noTestsAction": "error", "stopOnFailure": false }
|
||||
},
|
||||
{ "name": "windows-debug", "inherits": "common", "configurePreset": "windows-debug" },
|
||||
{ "name": "windows-asan", "inherits": "common", "configurePreset": "windows-asan" },
|
||||
{ "name": "linux-debug", "inherits": "common", "configurePreset": "linux-debug" },
|
||||
{ "name": "linux-asan", "inherits": "common", "configurePreset": "linux-asan" },
|
||||
{ "name": "macos-debug", "inherits": "common", "configurePreset": "macos-debug" },
|
||||
{ "name": "macos-asan", "inherits": "common", "configurePreset": "macos-asan" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
# PDF Engine
|
||||
|
||||
Cross-platform, high-performance, license-safe PDF rendering and editing SDK.
|
||||
A single C++ core (PDFium + Skia + FreeType + HarfBuzz) drives native desktop,
|
||||
server-side, and browser (WASM) rendering, with a FastAPI gateway and a
|
||||
React + TypeScript viewer on top.
|
||||
|
||||
> **Status:** Phase 0 — Infrastructure. The stack must compile clean on
|
||||
> Linux, macOS, and Windows before any feature work begins. See
|
||||
> [`docs/phase0.md`](docs/phase0.md).
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Purpose |
|
||||
|------------------|----------------------------------------------------------------------|
|
||||
| `engine/` | C++23 PDF SDK core. The only code that links PDFium/Skia/FreeType. |
|
||||
| `engine/src/parser/` | **Only** place raw `FPDF_*` PDFium APIs may be used (Rule R2). |
|
||||
| `bindings/` | Language bindings — C ABI (.NET/ctypes) and pybind11 (Python). |
|
||||
| `gateway/` | FastAPI service: auth, metadata, storage, job queue. *(placeholder)* |
|
||||
| `frontend/` | React + TypeScript layered PDF viewer. *(placeholder)* |
|
||||
| `wasm/` | Emscripten build of the engine for in-browser rendering. *(placeholder)* |
|
||||
| `third_party/` | Vendored dependencies built from source (PDFium via depot_tools/GN). |
|
||||
| `cmake/` | Reusable CMake modules (`pdfium.cmake`, warnings, sanitizers). |
|
||||
| `corpus/` | Test PDF corpus for rendering/regression gates. |
|
||||
| `scripts/` | Bootstrap, lint, and developer tooling. |
|
||||
| `docs/` | Engineering docs and phase plans. |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **CMake** >= 3.25 and **Ninja**
|
||||
- A **C++23** compiler: MSVC 19.36+ (VS 2022 17.6+ **or** VS 2026 / Build
|
||||
Tools 2026 — IDE not required), GCC 13+, or Clang 16+
|
||||
- **vcpkg** (set `VCPKG_ROOT`; `scripts/bootstrap` can install it). On
|
||||
Windows, `setx VCPKG_ROOT ...` does not affect already-open shells —
|
||||
set `$env:VCPKG_ROOT` in that shell or open a new terminal.
|
||||
- **clang-format 22.1.5** (CI is pinned to this exact version) —
|
||||
`pip install clang-format==22.1.5` on Windows
|
||||
- **Python 3.11+** and **Node 20+** (for gateway/frontend, later phases)
|
||||
- For building PDFium from source: **depot_tools** (handled by
|
||||
`third_party/pdfium/build_pdfium.*` — do **not** add it to your
|
||||
persistent `PATH`; its `ninja.bat` shadows real Ninja and breaks the
|
||||
engine build, see [`docs/phase0.md`](docs/phase0.md))
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
# 1. One-time setup: checks tools, installs vcpkg, pins the dependency baseline.
|
||||
# Windows: pwsh scripts/bootstrap.ps1
|
||||
# Unix: ./scripts/bootstrap.sh
|
||||
|
||||
# 2. Build PDFium from source (slow, one-time — see third_party/pdfium/README.md).
|
||||
# Windows: pwsh third_party/pdfium/build_pdfium.ps1
|
||||
# Unix: ./third_party/pdfium/build_pdfium.sh
|
||||
|
||||
# 3. Configure + build + test via CMake presets.
|
||||
cmake --preset windows-debug # or linux-debug / macos-debug
|
||||
cmake --build --preset windows-debug
|
||||
ctest --preset windows-debug
|
||||
```
|
||||
|
||||
> **Windows + PDFium:** the engine-linked-against-PDFium build is
|
||||
> RelWithDebInfo + static CRT (not the Debug preset). Drop the
|
||||
> `win-local-pdfium` preset into a local `CMakeUserPresets.json` and use
|
||||
> that — see [`docs/phase0.md`](docs/phase0.md#windows--pdfium) for the
|
||||
> exact JSON and the reason (CRT alignment).
|
||||
|
||||
> **OneDrive note:** this checkout lives in a OneDrive-synced folder whose
|
||||
> path also contains a space. On **Windows** this is not optional — put
|
||||
> your build directory outside OneDrive on a space-free path
|
||||
> (e.g. `C:\Users\<you>\pdfeng-build\...`), or vcpkg/meson and depot_tools
|
||||
> will fail. On Linux/macOS it is just a sync nuisance — exclude `out/`
|
||||
> and `vcpkg/` from sync, or pause sync while building. See
|
||||
> [`docs/phase0.md`](docs/phase0.md#onedrive-warning) for details.
|
||||
|
||||
## Engineering rules (non-negotiable)
|
||||
|
||||
- **R1** PDFium stays the parser core — no custom parser.
|
||||
- **R2** Only `engine/src/parser/` may call raw `FPDF_*` APIs. CI enforces this.
|
||||
- **R3** No custom graphics interpreter in Phase 1/2 — use PDFium's renderer.
|
||||
- **R4** Saving is incremental (append-only xref) by default.
|
||||
- **R5** WASM never blocks shipping — server-side render is the fallback.
|
||||
|
||||
Dependency versions are **pinned** (vcpkg baseline + a pinned PDFium ref).
|
||||
Never track rolling `HEAD`.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Bindings — placeholder
|
||||
|
||||
Language bindings over the C++ engine. Not implemented yet.
|
||||
|
||||
- `c_abi/` — stable C ABI (`int error_code` returns, opaque handles). Consumed
|
||||
by .NET and Python `ctypes`. This ABI is **frozen in Phase 0** alongside the
|
||||
`PdfDocument` / `PdfPage` interface contracts (Gate G0b) and must not change
|
||||
without a version bump. Phase 3 ships `.so` / `.dylib` / `.dll` packages from it.
|
||||
- `python/` — **pybind11** module used by the FastAPI gateway. Exposes
|
||||
`render_page()`, `apply_edits()`, `extract_text()`.
|
||||
|
||||
Internal engine code uses `std::expected<T, E>`; the C ABI surface translates
|
||||
that to `int error_code` (engine blueprint §16.2).
|
||||
@@ -0,0 +1,52 @@
|
||||
# Reusable warning configuration.
|
||||
# Usage: pdfengine_set_warnings(<target> [PRIVATE|INTERFACE|PUBLIC])
|
||||
|
||||
function(pdfengine_set_warnings target)
|
||||
set(_scope PRIVATE)
|
||||
if(ARGV1)
|
||||
set(_scope ${ARGV1})
|
||||
endif()
|
||||
|
||||
set(_msvc_warnings
|
||||
/W4
|
||||
/permissive- # strict standard conformance
|
||||
/w14242 /w14254 /w14263 /w14265 /w14287
|
||||
/w14296 /w14311 /w14545 /w14546 /w14547
|
||||
/w14549 /w14555 /w14619 /w14640 /w14826
|
||||
/w14905 /w14906 /w14928
|
||||
/EHsc
|
||||
)
|
||||
|
||||
set(_gnu_clang_warnings
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Wshadow
|
||||
-Wnon-virtual-dtor
|
||||
-Wcast-align
|
||||
-Wunused
|
||||
-Woverloaded-virtual
|
||||
-Wconversion
|
||||
-Wsign-conversion
|
||||
-Wnull-dereference
|
||||
-Wdouble-promotion
|
||||
-Wformat=2
|
||||
-Wimplicit-fallthrough
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
set(_warnings ${_msvc_warnings})
|
||||
else()
|
||||
set(_warnings ${_gnu_clang_warnings})
|
||||
endif()
|
||||
|
||||
if(PDFENGINE_WARNINGS_AS_ERRORS)
|
||||
if(MSVC)
|
||||
list(APPEND _warnings /WX)
|
||||
else()
|
||||
list(APPEND _warnings -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_compile_options(${target} ${_scope} ${_warnings})
|
||||
endfunction()
|
||||
@@ -0,0 +1,28 @@
|
||||
# AddressSanitizer / UndefinedBehaviorSanitizer wiring.
|
||||
# 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)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
message(STATUS "Sanitizers: AddressSanitizer enabled for '${target}' (MSVC)")
|
||||
target_compile_options(${target} PRIVATE /fsanitize=address)
|
||||
# ASan on MSVC is incompatible with the /RTC runtime checks and incremental link.
|
||||
target_compile_options(${target} PRIVATE /Zi)
|
||||
target_link_options(${target} PRIVATE /INCREMENTAL:NO)
|
||||
else()
|
||||
message(STATUS "Sanitizers: Address + UndefinedBehavior enabled for '${target}'")
|
||||
set(_flags -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
|
||||
target_compile_options(${target} PRIVATE ${_flags} -g)
|
||||
target_link_options(${target} PRIVATE ${_flags})
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,63 @@
|
||||
# PDFium integration.
|
||||
#
|
||||
# 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). "
|
||||
"Engine builds without raw PDFium linkage.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(PDFIUM_INSTALL_DIR "${CMAKE_SOURCE_DIR}/third_party/pdfium/install"
|
||||
CACHE PATH "Root of the PDFium install tree produced by build_pdfium.*")
|
||||
|
||||
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)
|
||||
|
||||
if(NOT PDFIUM_INCLUDE_DIR OR NOT PDFIUM_LIBRARY)
|
||||
message(FATAL_ERROR
|
||||
"PDFENGINE_WITH_PDFIUM=ON but no PDFium install tree was found under:\n"
|
||||
" ${PDFIUM_INSTALL_DIR}\n"
|
||||
"Build PDFium first (one-time, slow):\n"
|
||||
" Windows: pwsh third_party/pdfium/build_pdfium.ps1\n"
|
||||
" Unix: ./third_party/pdfium/build_pdfium.sh\n"
|
||||
"See third_party/pdfium/README.md.")
|
||||
endif()
|
||||
|
||||
add_library(pdfium::pdfium STATIC IMPORTED GLOBAL)
|
||||
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)
|
||||
endif()
|
||||
|
||||
message(STATUS "PDFium: found")
|
||||
message(STATUS " include .. ${PDFIUM_INCLUDE_DIR}")
|
||||
message(STATUS " library .. ${PDFIUM_LIBRARY}")
|
||||
@@ -0,0 +1,31 @@
|
||||
# WebAssembly (Emscripten) toolchain hook — STUB for Phase 0.
|
||||
#
|
||||
# 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
|
||||
"WASM build requested but EMSDK is not set in the environment.\n"
|
||||
"Install and activate the Emscripten SDK:\n"
|
||||
" git clone https://github.com/emscripten-core/emsdk\n"
|
||||
" ./emsdk install latest && ./emsdk activate latest\n"
|
||||
" source ./emsdk_env.sh (or emsdk_env.bat on Windows)\n"
|
||||
"Then re-run: cmake --preset wasm")
|
||||
endif()
|
||||
|
||||
set(_emscripten_toolchain
|
||||
"$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
|
||||
|
||||
if(NOT EXISTS "${_emscripten_toolchain}")
|
||||
message(FATAL_ERROR "Emscripten toolchain not found at: ${_emscripten_toolchain}")
|
||||
endif()
|
||||
|
||||
include("${_emscripten_toolchain}")
|
||||
|
||||
# Engine blueprint §6.3: large PDFs can exceed the default WASM heap.
|
||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-sALLOW_MEMORY_GROWTH=1")
|
||||
@@ -0,0 +1,25 @@
|
||||
# Test PDF corpus
|
||||
|
||||
Reference PDFs used by rendering, text-extraction, and regression gates.
|
||||
|
||||
**Do not commit large or confidential PDFs here.** Binary PDFs are tracked by
|
||||
git but the corpus is expected to grow — once it does, move it to Git LFS or an
|
||||
external store and keep only a manifest here.
|
||||
|
||||
Structure (populated during Phase 1):
|
||||
|
||||
```
|
||||
corpus/
|
||||
basic/ # clean, simple PDFs — smoke tests
|
||||
fonts/ # embedded fonts, CID/CJK, Arabic/RTL
|
||||
edge-cases/ # broken xrefs, scanned, PDF/A, encrypted
|
||||
golden/ # reference PNG renders for pixel-diff tests
|
||||
manifest.json # per-file metadata + expected results
|
||||
```
|
||||
|
||||
**Risk-register rule:** every client PDF that surfaces a bug gets added to the
|
||||
corpus immediately.
|
||||
|
||||
Gates that consume this corpus:
|
||||
- **G1/G2/G3** (Phase 1): 30-PDF corpus, render + text extraction.
|
||||
- **G7** (Phase 3): 500-PDF regression corpus, SSIM > 0.95.
|
||||
+273
@@ -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.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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)
|
||||
|
||||
add_library(pdfengine STATIC
|
||||
src/core/engine_info.cpp
|
||||
src/parser/pdfium_loader.cpp
|
||||
)
|
||||
add_library(pdfengine::pdfengine ALIAS pdfengine)
|
||||
|
||||
target_include_directories(pdfengine
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>"
|
||||
PRIVATE
|
||||
"${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
|
||||
)
|
||||
|
||||
# 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 PRIVATE PDFENGINE_WITH_PDFIUM)
|
||||
endif()
|
||||
|
||||
pdfengine_set_warnings(pdfengine)
|
||||
pdfengine_enable_sanitizers(pdfengine)
|
||||
|
||||
if(PDFENGINE_BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +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
|
||||
|
||||
namespace pdfengine {
|
||||
|
||||
// Opaque, owning handle to a parsed PDF document.
|
||||
// Full definition + API land at Gate G0b.
|
||||
class PdfDocument;
|
||||
|
||||
// Opaque view onto a single page of a PdfDocument.
|
||||
// Full definition + API land at Gate G0b.
|
||||
class PdfPage;
|
||||
|
||||
} // namespace pdfengine
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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>
|
||||
#include <string_view>
|
||||
|
||||
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;
|
||||
|
||||
// Emits engineBuildInfo() through spdlog at info level.
|
||||
void engineLogBuildInfo();
|
||||
|
||||
} // namespace pdfengine
|
||||
@@ -0,0 +1,14 @@
|
||||
// Generated by CMake from version.hpp.in — do not edit the generated copy.
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pdfengine {
|
||||
|
||||
inline constexpr int version_major = @PROJECT_VERSION_MAJOR@;
|
||||
inline constexpr int version_minor = @PROJECT_VERSION_MINOR@;
|
||||
inline constexpr int version_patch = @PROJECT_VERSION_PATCH@;
|
||||
|
||||
inline constexpr std::string_view version_string = "@PROJECT_VERSION@";
|
||||
|
||||
} // namespace pdfengine
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "parser/pdfium_loader.hpp"
|
||||
|
||||
#include <pdfengine/pdf_engine.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
|
||||
namespace pdfengine {
|
||||
|
||||
std::string_view engineVersion() noexcept {
|
||||
return version_string;
|
||||
}
|
||||
|
||||
bool engineHasPdfium() noexcept {
|
||||
return parser::pdfiumAvailable();
|
||||
}
|
||||
|
||||
std::string_view engineBuildInfo() noexcept {
|
||||
static const std::string info = [] {
|
||||
std::string s = "pdfengine ";
|
||||
s += version_string;
|
||||
s += parser::pdfiumAvailable() ? " (pdfium=on)" : " (pdfium=off)";
|
||||
return s;
|
||||
}();
|
||||
return info;
|
||||
}
|
||||
|
||||
void engineLogBuildInfo() {
|
||||
spdlog::info("{}", engineBuildInfo());
|
||||
}
|
||||
|
||||
} // namespace pdfengine
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "parser/pdfium_loader.hpp"
|
||||
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
// The ONLY place in the codebase a raw PDFium header may be included (Rule R2).
|
||||
#include <fpdfview.h>
|
||||
#endif
|
||||
|
||||
namespace pdfengine::parser {
|
||||
|
||||
bool pdfiumAvailable() noexcept {
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void pdfiumInitLibrary() {
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
// PDFium expects this once per process before any document is opened.
|
||||
FPDF_InitLibrary();
|
||||
#endif
|
||||
}
|
||||
|
||||
void pdfiumDestroyLibrary() {
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
FPDF_DestroyLibrary();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace pdfengine::parser
|
||||
@@ -0,0 +1,20 @@
|
||||
// pdfengine::parser — the PDFium boundary.
|
||||
//
|
||||
// Rule R2: this directory (engine/src/parser/) is the ONLY place in the entire
|
||||
// codebase permitted to include PDFium headers or call raw FPDF_* APIs. CI
|
||||
// enforces this with scripts/check_pdfium_boundary.* — keep it that way.
|
||||
#pragma once
|
||||
|
||||
namespace pdfengine::parser {
|
||||
|
||||
// True if the engine was compiled with PDFENGINE_WITH_PDFIUM, i.e. linked
|
||||
// against the PDFium static library. False in Phase 0 default builds.
|
||||
[[nodiscard]] bool pdfiumAvailable() noexcept;
|
||||
|
||||
// Global one-time initialisation / teardown of the PDFium library.
|
||||
// No-ops when built without PDFium. Init must be called before, and destroy
|
||||
// after, any document parsing.
|
||||
void pdfiumInitLibrary();
|
||||
void pdfiumDestroyLibrary();
|
||||
|
||||
} // namespace pdfengine::parser
|
||||
@@ -0,0 +1,18 @@
|
||||
# Engine test suite. Phase 0: a single smoke test that backs Gate G0.
|
||||
|
||||
add_executable(pdfengine_smoke
|
||||
smoke_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(pdfengine_smoke
|
||||
PRIVATE
|
||||
pdfengine::pdfengine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
pdfengine_set_warnings(pdfengine_smoke)
|
||||
pdfengine_enable_sanitizers(pdfengine_smoke)
|
||||
|
||||
# Registers each TEST() with CTest so `ctest --preset ...` runs them.
|
||||
gtest_discover_tests(pdfengine_smoke)
|
||||
@@ -0,0 +1,27 @@
|
||||
// Phase 0 smoke test: proves the engine library compiles, links against its
|
||||
// vcpkg dependencies, and is callable. This is what Gate G0 ("infrastructure
|
||||
// compiles on all platforms") checks in CI.
|
||||
#include <gtest/gtest.h>
|
||||
#include <pdfengine/pdf_engine.hpp>
|
||||
#include <string_view>
|
||||
|
||||
TEST(EngineSmoke, VersionIsReported) {
|
||||
EXPECT_FALSE(pdfengine::engineVersion().empty());
|
||||
EXPECT_EQ(pdfengine::engineVersion(), pdfengine::version_string);
|
||||
}
|
||||
|
||||
TEST(EngineSmoke, BuildInfoMentionsVersion) {
|
||||
const std::string_view info = pdfengine::engineBuildInfo();
|
||||
EXPECT_NE(info.find(pdfengine::version_string), std::string_view::npos);
|
||||
}
|
||||
|
||||
TEST(EngineSmoke, BuildInfoConsistentWithPdfiumLinkage) {
|
||||
const std::string_view info = pdfengine::engineBuildInfo();
|
||||
const bool says_on = info.find("pdfium=on") != std::string_view::npos;
|
||||
EXPECT_EQ(says_on, pdfengine::engineHasPdfium());
|
||||
}
|
||||
|
||||
TEST(EngineSmoke, LogBuildInfoDoesNotThrow) {
|
||||
// Exercises the spdlog dependency end to end (compile + link + call).
|
||||
EXPECT_NO_THROW(pdfengine::engineLogBuildInfo());
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Frontend (React + TypeScript) — placeholder
|
||||
|
||||
Phase 0 task **"React + TypeScript frontend scaffolding"** (Dev 2) and all later
|
||||
viewer/editor UI lives here. Not implemented yet — this session scaffolds the
|
||||
C++ build pipeline only.
|
||||
|
||||
Planned responsibilities (see the engine blueprint §9):
|
||||
|
||||
- Layered viewer: canvas layer, selection layer, annotation layer, overlay layer.
|
||||
- PDF <-> canvas <-> zoomed coordinate mapping.
|
||||
- Virtualized page rendering (only visible pages).
|
||||
- Talks to the gateway over HTTP; later, to the engine directly via WASM.
|
||||
|
||||
Setup when implemented: Vite + React + **TypeScript strict mode** from day one.
|
||||
|
||||
```
|
||||
frontend/
|
||||
package.json
|
||||
tsconfig.json # "strict": true
|
||||
src/
|
||||
components/
|
||||
viewer/
|
||||
lib/
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# Gateway (FastAPI) — placeholder
|
||||
|
||||
Phase 0 task **"FastAPI service scaffolding"** (Dev 1) and all later API work
|
||||
lives here. Not implemented yet — this session scaffolds the C++ build pipeline
|
||||
only.
|
||||
|
||||
Planned responsibilities (see `docs/phase0.md` and the engine blueprint §8):
|
||||
|
||||
- Auth, metadata, storage (S3 / MinIO), async job queue.
|
||||
- Bridges to the C++ engine via **pybind11** (`bindings/python/`).
|
||||
- Placeholder routes + a `/health` endpoint returning `200` — **no PDF logic**
|
||||
until the engine wrappers exist.
|
||||
|
||||
When implemented:
|
||||
|
||||
```
|
||||
gateway/
|
||||
pyproject.toml
|
||||
app/
|
||||
main.py # FastAPI app + /health
|
||||
routers/
|
||||
services/
|
||||
tests/
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
#requires -Version 5.1
|
||||
# One-time developer setup (Windows):
|
||||
# - verifies required tools
|
||||
# - installs vcpkg if VCPKG_ROOT is not set
|
||||
# - pins the vcpkg dependency baseline in vcpkg.json
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$RepoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location $RepoRoot
|
||||
|
||||
Write-Host '>> Checking required tools'
|
||||
$missing = $false
|
||||
foreach ($tool in @('git', 'cmake', 'ninja')) {
|
||||
if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) {
|
||||
Write-Host " MISSING: $tool"
|
||||
$missing = $true
|
||||
}
|
||||
}
|
||||
if ($missing) { Write-Error 'Install the missing tools and re-run.' }
|
||||
|
||||
# MSVC is only on PATH inside a Developer prompt; detect the install instead.
|
||||
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||
if (Test-Path $vswhere) {
|
||||
$vc = & $vswhere -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||
if ($vc) { Write-Host " MSVC C++ toolchain: $vc" }
|
||||
else { Write-Host ' WARNING: Visual Studio found but the C++ workload (VC.Tools) is not installed.' }
|
||||
} else {
|
||||
Write-Host ' WARNING: Visual Studio not detected. Install VS 2022 with the'
|
||||
Write-Host ' "Desktop development with C++" workload (MSVC 19.36+ for C++23).'
|
||||
}
|
||||
|
||||
Write-Host '>> Setting up vcpkg'
|
||||
if (-not $env:VCPKG_ROOT) {
|
||||
$vcpkgDir = Join-Path $RepoRoot 'vcpkg'
|
||||
if (-not (Test-Path $vcpkgDir)) {
|
||||
Write-Host ' Cloning vcpkg into .\vcpkg'
|
||||
git clone https://github.com/microsoft/vcpkg.git $vcpkgDir
|
||||
}
|
||||
& (Join-Path $vcpkgDir 'bootstrap-vcpkg.bat') -disableMetrics
|
||||
$env:VCPKG_ROOT = $vcpkgDir
|
||||
Write-Host ' VCPKG_ROOT is not set in your environment. Set it permanently with:'
|
||||
Write-Host " setx VCPKG_ROOT `"$vcpkgDir`""
|
||||
} else {
|
||||
Write-Host " Using VCPKG_ROOT=$env:VCPKG_ROOT"
|
||||
}
|
||||
$vcpkgExe = Join-Path $env:VCPKG_ROOT 'vcpkg.exe'
|
||||
|
||||
Write-Host '>> Pinning the vcpkg dependency baseline'
|
||||
if (Select-String -Path 'vcpkg.json' -Pattern '"builtin-baseline"' -Quiet) {
|
||||
Write-Host ' builtin-baseline already present — leaving it pinned.'
|
||||
} else {
|
||||
& $vcpkgExe x-update-baseline --add-initial-baseline
|
||||
Write-Host ' Added builtin-baseline to vcpkg.json. Commit this change.'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '>> Bootstrap complete. Next steps:'
|
||||
Write-Host ' cmake --preset windows-debug'
|
||||
Write-Host ' cmake --build --preset windows-debug'
|
||||
Write-Host ' ctest --preset windows-debug'
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time developer setup (Linux / macOS):
|
||||
# - verifies required tools
|
||||
# - installs vcpkg if VCPKG_ROOT is not set
|
||||
# - pins the vcpkg dependency baseline in vcpkg.json
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
echo ">> Checking required tools"
|
||||
missing=0
|
||||
for tool in git cmake ninja; do
|
||||
if ! command -v "${tool}" >/dev/null 2>&1; then
|
||||
echo " MISSING: ${tool}" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [[ "${missing}" -ne 0 ]]; then
|
||||
echo "ERROR: install the missing tools and re-run." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v c++ >/dev/null 2>&1 && ! command -v clang++ >/dev/null 2>&1; then
|
||||
echo "WARNING: no C++ compiler found on PATH (need GCC 13+ or Clang 16+ for C++23)." >&2
|
||||
fi
|
||||
|
||||
echo ">> Setting up vcpkg"
|
||||
if [[ -z "${VCPKG_ROOT:-}" ]]; then
|
||||
if [[ ! -d "${REPO_ROOT}/vcpkg" ]]; then
|
||||
echo " Cloning vcpkg into ./vcpkg"
|
||||
git clone https://github.com/microsoft/vcpkg.git "${REPO_ROOT}/vcpkg"
|
||||
fi
|
||||
"${REPO_ROOT}/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
|
||||
export VCPKG_ROOT="${REPO_ROOT}/vcpkg"
|
||||
echo " VCPKG_ROOT is not set in your environment."
|
||||
echo " Add this to your shell profile (~/.bashrc, ~/.zshrc):"
|
||||
echo " export VCPKG_ROOT=\"${REPO_ROOT}/vcpkg\""
|
||||
else
|
||||
echo " Using VCPKG_ROOT=${VCPKG_ROOT}"
|
||||
fi
|
||||
VCPKG_EXE="${VCPKG_ROOT}/vcpkg"
|
||||
|
||||
echo ">> Pinning the vcpkg dependency baseline"
|
||||
if grep -q '"builtin-baseline"' vcpkg.json; then
|
||||
echo " builtin-baseline already present — leaving it pinned."
|
||||
else
|
||||
"${VCPKG_EXE}" x-update-baseline --add-initial-baseline
|
||||
echo " Added builtin-baseline to vcpkg.json. Commit this change."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo ">> Bootstrap complete. Next steps:"
|
||||
echo " cmake --preset linux-debug # or macos-debug"
|
||||
echo " cmake --build --preset linux-debug"
|
||||
echo " ctest --preset linux-debug"
|
||||
@@ -0,0 +1,42 @@
|
||||
#requires -Version 5.1
|
||||
# Rule R2 enforcement: raw PDFium APIs (FPDF_*) and PDFium headers (fpdf*.h) may
|
||||
# only appear under engine/src/parser/. Run locally and in CI.
|
||||
#
|
||||
# C/C++ comments are stripped before matching, so headers that *document* the
|
||||
# rule (e.g. engine/include/pdfengine/pdf_document.hpp) don't trip it — only
|
||||
# real code usage counts.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$RepoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
Set-Location $RepoRoot
|
||||
|
||||
$sep = [IO.Path]::DirectorySeparatorChar
|
||||
$allowed = "engine${sep}src${sep}parser${sep}"
|
||||
$pattern = 'FPDF_|#\s*include\s*[<"]fpdf'
|
||||
|
||||
$sources = Get-ChildItem -Path 'engine', 'bindings' -Recurse -File -ErrorAction SilentlyContinue `
|
||||
-Include '*.cpp', '*.cc', '*.h', '*.hpp' |
|
||||
Where-Object {
|
||||
$rel = $_.FullName.Substring($RepoRoot.Length + 1)
|
||||
-not $rel.StartsWith($allowed)
|
||||
}
|
||||
|
||||
$violations = @()
|
||||
foreach ($file in $sources) {
|
||||
$text = Get-Content -Raw -LiteralPath $file.FullName
|
||||
if (-not $text) { continue }
|
||||
# Strip // line comments and /* ... */ block comments before matching.
|
||||
$stripped = [regex]::Replace($text, '//[^\r\n]*|/\*[\s\S]*?\*/', '')
|
||||
if ($stripped -match $pattern) {
|
||||
$violations += $file.FullName.Substring($RepoRoot.Length + 1)
|
||||
}
|
||||
}
|
||||
|
||||
if ($violations) {
|
||||
Write-Host 'ERROR: Rule R2 violation - raw PDFium usage outside engine/src/parser/:'
|
||||
$violations | ForEach-Object { Write-Host " $_" }
|
||||
Write-Host 'All PDFium access must go through the parser boundary.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host 'R2 boundary check: OK (no raw PDFium usage outside engine/src/parser/)'
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rule R2 enforcement: raw PDFium APIs (FPDF_*) and PDFium headers (fpdf*.h) may
|
||||
# only appear under engine/src/parser/. Run locally and in CI.
|
||||
#
|
||||
# C/C++ comments are stripped before matching, so headers that *document* the
|
||||
# rule (e.g. engine/include/pdfengine/pdf_document.hpp) don't trip it — only
|
||||
# real code usage counts.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
pattern='FPDF_|#[[:space:]]*include[[:space:]]*[<"]fpdf'
|
||||
violations=""
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
# engine/src/parser/ is the one allowed home of raw PDFium usage.
|
||||
case "${file}" in
|
||||
engine/src/parser/*) continue ;;
|
||||
esac
|
||||
# Strip // line comments and /* ... */ block comments, then search.
|
||||
if perl -0777 -pe 's{//[^\n]*|/\*.*?\*/}{}gs' "${file}" | grep -Eq "${pattern}"; then
|
||||
violations+=" ${file}"$'\n'
|
||||
fi
|
||||
done < <(find engine bindings \
|
||||
\( -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.hpp' \) \
|
||||
-print0 2>/dev/null)
|
||||
|
||||
if [[ -n "${violations}" ]]; then
|
||||
echo "ERROR: Rule R2 violation — raw PDFium usage outside engine/src/parser/:" >&2
|
||||
printf '%s' "${violations}" >&2
|
||||
echo "All PDFium access must go through the parser boundary." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "R2 boundary check: OK (no raw PDFium usage outside engine/src/parser/)"
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
# PDFium — built from source
|
||||
|
||||
PDFium is the parser + base-rendering core of the engine (Rule R1). It is **not**
|
||||
a vcpkg package; Google ships it only as source built with their own toolchain
|
||||
(**depot_tools + GN + Ninja**). The timeline flags this as *"the single biggest
|
||||
Day-1 risk — budget a full day for depot_tools quirks."*
|
||||
|
||||
## What the build scripts do
|
||||
|
||||
`build_pdfium.ps1` (Windows) and `build_pdfium.sh` (Linux/macOS) automate:
|
||||
|
||||
1. Read the **pinned revision** from `pdfium.pinned` (refuses to run if it is
|
||||
still the placeholder — Rule: never track rolling HEAD).
|
||||
2. Clone `depot_tools` under the **build root** and run its Windows bootstrap
|
||||
(fetches bundled git + python via CIPD).
|
||||
3. `gclient config` + `gclient sync` the PDFium tree into `<build root>/checkout/`.
|
||||
4. Check out the exact pinned commit and re-sync its DEPS.
|
||||
5. Write `args.gn` for a **static, standalone, monolithic, embed-friendly** build:
|
||||
- `is_component_build = false` — one static lib, not many DLLs
|
||||
- `pdf_is_standalone = true`
|
||||
- `pdf_enable_v8 = false`, `pdf_enable_xfa = false` — no JS / XFA in v1
|
||||
- `pdf_use_skia = false` — we drive Skia ourselves later
|
||||
- `use_custom_libcxx = false` — link the system C++ runtime so PDFium is
|
||||
ABI-compatible with the rest of the engine *(critical for embedding)*
|
||||
- `pdf_use_partition_alloc = false`
|
||||
6. `gn gen` + `ninja -C out/Release pdfium`.
|
||||
7. Copy `public/*.h` → `install/include/` and the static lib → `install/lib/`.
|
||||
|
||||
`cmake/pdfium.cmake` then turns `install/` into the `pdfium::pdfium` imported
|
||||
target. Build the engine with `-DPDFENGINE_WITH_PDFIUM=ON` to link it.
|
||||
|
||||
## The build root — paths with spaces
|
||||
|
||||
**depot_tools, GN and Ninja do not support a space anywhere in their own path.**
|
||||
By default the scripts build under this directory (`third_party/pdfium/`), which
|
||||
is fine when the repo lives on a space-free path.
|
||||
|
||||
If the repo path contains a space (e.g. `OneDrive\...\PDF Editor\...`), the
|
||||
scripts **hard-error** and you must point the build root somewhere space-free:
|
||||
|
||||
```powershell
|
||||
# Windows
|
||||
$env:PDFIUM_BUILD_ROOT = 'C:\pdfium-build'
|
||||
pwsh third_party\pdfium\build_pdfium.ps1
|
||||
```
|
||||
```sh
|
||||
# Linux / macOS
|
||||
PDFIUM_BUILD_ROOT=/tmp/pdfium-build ./third_party/pdfium/build_pdfium.sh
|
||||
```
|
||||
|
||||
`depot_tools/` and `checkout/` then live under the build root; the finished
|
||||
`install/` is **always** written into `third_party/pdfium/install/` (git-ignored)
|
||||
so `cmake/pdfium.cmake` finds it in the same place regardless.
|
||||
|
||||
## Pinning the revision
|
||||
|
||||
`pdfium.pinned` holds the pinned commit SHA. It is currently pinned to a
|
||||
specific `main`-branch commit. To re-pin (a deliberate, scheduled action — the
|
||||
risk register calls for *quarterly* rebases):
|
||||
|
||||
1. Pick a commit from https://pdfium.googlesource.com/pdfium/+log/main (or the
|
||||
tip of a recent `chromium/NNNN` release branch).
|
||||
2. Update `PDFIUM_COMMIT=` in `pdfium.pinned` and commit it.
|
||||
3. Re-run the build script.
|
||||
|
||||
## Usage
|
||||
|
||||
Prerequisites: Git, Python 3, and a C++ toolchain (Windows: Visual Studio 2022+
|
||||
with the "Desktop development with C++" workload).
|
||||
|
||||
```powershell
|
||||
# Windows — set PDFIUM_BUILD_ROOT first if the repo path has a space (see above)
|
||||
pwsh third_party\pdfium\build_pdfium.ps1
|
||||
```
|
||||
```sh
|
||||
# Linux / macOS
|
||||
./third_party/pdfium/build_pdfium.sh
|
||||
```
|
||||
|
||||
Expect the first run to take a long time — the `gclient sync` alone pulls
|
||||
several GB, and the compile is lengthy. `depot_tools/`, `checkout/`, and
|
||||
`install/` are all git-ignored.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`'C:\...\PDF' is not recognized` / GN or Ninja path errors** — a space in
|
||||
the build-root path. Set `PDFIUM_BUILD_ROOT` to a space-free location.
|
||||
- **`gclient sync` aborts with "uncommitted changes"** — git's `core.autocrlf`
|
||||
rewrote a dependency checkout. The scripts already inject `core.autocrlf=false`
|
||||
per-process; if you bypass them, set it yourself.
|
||||
- **depot_tools `git`/`python` not found** — depot_tools was not bootstrapped.
|
||||
The scripts run `bootstrap\win_tools.bat`; do not set `DEPOT_TOOLS_UPDATE=0`,
|
||||
which suppresses that bootstrap.
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
# GN build args for PDFium — static, standalone, monolithic, embed-friendly.
|
||||
# Copied to out/Release/args.gn by the build scripts. See README.md for rationale.
|
||||
|
||||
is_debug = false
|
||||
|
||||
# One self-contained static library, not a component (DLL) build.
|
||||
is_component_build = false
|
||||
|
||||
# Bundle PDFium *and all its dependencies* into a single static lib
|
||||
# (out/Release/obj/pdfium.lib). Without this the `pdfium` target is just a
|
||||
# GN group — it builds the component libs but never archives them together,
|
||||
# leaving nothing for the engine to link against.
|
||||
pdf_is_complete_lib = true
|
||||
|
||||
# Standalone embedder build (no full Chromium tree).
|
||||
pdf_is_standalone = true
|
||||
|
||||
# v1 scope: no JavaScript engine, no XFA forms.
|
||||
pdf_enable_v8 = false
|
||||
pdf_enable_xfa = false
|
||||
|
||||
# We integrate Skia ourselves at the engine layer — PDFium uses its built-in
|
||||
# AGG rasterizer here.
|
||||
pdf_use_skia = false
|
||||
|
||||
# Critical for embedding: link the system C++ runtime instead of Chromium's
|
||||
# bundled libc++, so PDFium is ABI-compatible with the rest of the engine.
|
||||
use_custom_libcxx = false
|
||||
|
||||
# Avoid Chromium's PartitionAlloc — keeps the embed surface simple.
|
||||
pdf_use_partition_alloc = false
|
||||
|
||||
# Don't require the Chromium clang plugins for a standalone build.
|
||||
clang_use_chrome_plugins = false
|
||||
|
||||
# Treat warnings as warnings — PDFium upstream, not our code.
|
||||
treat_warnings_as_errors = false
|
||||
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
#requires -Version 5.1
|
||||
# Build PDFium from source (Windows) into third_party\pdfium\install\.
|
||||
#
|
||||
# depot_tools, the PDFium checkout, and the GN/Ninja build all happen under a
|
||||
# BUILD ROOT. depot_tools, GN and Ninja do NOT support spaces anywhere in their
|
||||
# own path. If this repo lives under a path containing a space (for example
|
||||
# OneDrive\...\PDF Editor\...), you MUST point the build root somewhere
|
||||
# space-free:
|
||||
#
|
||||
# $env:PDFIUM_BUILD_ROOT = 'C:\pdfium-build'
|
||||
# pwsh third_party\pdfium\build_pdfium.ps1
|
||||
#
|
||||
# The finished static lib + public headers are always installed into
|
||||
# third_party\pdfium\install\ (git-ignored) regardless of the build root, so
|
||||
# cmake/pdfium.cmake finds them in the same place either way.
|
||||
#
|
||||
# One-time and slow: a multi-GB gclient sync plus a long compile. See README.md.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Install = Join-Path $ScriptDir 'install'
|
||||
$PinnedFile = Join-Path $ScriptDir 'pdfium.pinned'
|
||||
|
||||
# Build root: depot_tools + the checkout live here. Defaults to this directory
|
||||
# (fine for repos on a space-free path), but MUST be overridden to a space-free
|
||||
# location otherwise.
|
||||
$BuildRoot = if ($env:PDFIUM_BUILD_ROOT) { $env:PDFIUM_BUILD_ROOT } else { $ScriptDir }
|
||||
if ($BuildRoot -match '\s') {
|
||||
Write-Error @"
|
||||
PDFium build root contains a space: $BuildRoot
|
||||
depot_tools, GN and Ninja cannot build under a path with spaces. Set a
|
||||
space-free build root and re-run, e.g.:
|
||||
`$env:PDFIUM_BUILD_ROOT = 'C:\pdfium-build'
|
||||
pwsh third_party\pdfium\build_pdfium.ps1
|
||||
"@
|
||||
}
|
||||
$DepotTools = Join-Path $BuildRoot 'depot_tools'
|
||||
$Checkout = Join-Path $BuildRoot 'checkout'
|
||||
Write-Host ">> Build root: $BuildRoot"
|
||||
|
||||
# --- 1. Read and validate the pinned revision -------------------------------
|
||||
$pinned = Get-Content $PinnedFile | Where-Object { $_ -match '^\s*PDFIUM_' }
|
||||
$repo = ($pinned | Where-Object { $_ -match '^PDFIUM_REPO=' }) -replace '^PDFIUM_REPO=', ''
|
||||
$commit = ($pinned | Where-Object { $_ -match '^PDFIUM_COMMIT=' }) -replace '^PDFIUM_COMMIT=', ''
|
||||
if (-not $commit -or $commit -eq 'REPLACE_WITH_PINNED_COMMIT_SHA') {
|
||||
Write-Error 'PDFium revision is not pinned. Edit pdfium.pinned first (see README.md).'
|
||||
}
|
||||
Write-Host ">> PDFium pinned at $commit"
|
||||
|
||||
# --- 2. depot_tools ---------------------------------------------------------
|
||||
New-Item -ItemType Directory -Force -Path $BuildRoot | Out-Null
|
||||
if (-not (Test-Path $DepotTools)) {
|
||||
Write-Host '>> Cloning depot_tools'
|
||||
git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git $DepotTools
|
||||
}
|
||||
$env:PATH = "$DepotTools;$env:PATH"
|
||||
# Use the locally installed Visual Studio toolchain, not Google's internal one.
|
||||
$env:DEPOT_TOOLS_WIN_TOOLCHAIN = '0'
|
||||
|
||||
# Git settings Chromium requires on Windows, injected per-process via GIT_CONFIG_*
|
||||
# so the user's global git config is never touched. core.autocrlf=false is the
|
||||
# critical one: without it, gclient sees CRLF-converted dependency checkouts as
|
||||
# "uncommitted changes" and aborts the sync.
|
||||
$env:GIT_CONFIG_COUNT = '4'
|
||||
$env:GIT_CONFIG_KEY_0 = 'core.autocrlf'; $env:GIT_CONFIG_VALUE_0 = 'false'
|
||||
$env:GIT_CONFIG_KEY_1 = 'core.filemode'; $env:GIT_CONFIG_VALUE_1 = 'false'
|
||||
$env:GIT_CONFIG_KEY_2 = 'core.fscache'; $env:GIT_CONFIG_VALUE_2 = 'true'
|
||||
$env:GIT_CONFIG_KEY_3 = 'core.preloadindex'; $env:GIT_CONFIG_VALUE_3 = 'true'
|
||||
|
||||
# Bootstrap depot_tools. On Windows it must fetch its bundled git + python via
|
||||
# CIPD and generate the git.bat / python3.bat wrappers before gclient can run.
|
||||
# NOTE: do NOT set DEPOT_TOOLS_UPDATE=0 here — that suppresses this initial
|
||||
# bootstrap, not just self-updates. Reproducibility comes from the pinned PDFium
|
||||
# revision; depot_tools itself is designed to self-manage.
|
||||
Write-Host '>> Bootstrapping depot_tools (fetches bundled git + python; one-time, slow)'
|
||||
& cmd /c "`"$DepotTools\bootstrap\win_tools.bat`""
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'depot_tools bootstrap failed.' }
|
||||
|
||||
# --- 2b. Locate Visual Studio for the GN build -----------------------------
|
||||
# gn's vs_toolchain.py finds VS via the vs<YEAR>_install env var or a fixed
|
||||
# path (...\Microsoft Visual Studio\<YEAR>). VS *Build Tools* installs under a
|
||||
# major-version path (e.g. \18\BuildTools), not the year, so the fixed-path
|
||||
# probe misses it — resolve VS with vswhere and set vs<YEAR>_install. vswhere
|
||||
# needs `-products *` to see Build Tools at all.
|
||||
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||
if (-not (Test-Path $vswhere)) {
|
||||
Write-Error 'vswhere.exe not found. Install Visual Studio 2022+ (or Build Tools) with the C++ workload.'
|
||||
}
|
||||
$vsPath = (& $vswhere -latest -prerelease -products * -property installationPath | Select-Object -First 1)
|
||||
$vsVer = (& $vswhere -latest -prerelease -products * -property installationVersion | Select-Object -First 1)
|
||||
if (-not $vsPath -or -not (Test-Path (Join-Path $vsPath 'VC\Tools\MSVC'))) {
|
||||
Write-Error 'No Visual Studio with the C++ toolchain (VC.Tools) found — install the "Desktop development with C++" workload.'
|
||||
}
|
||||
$vsYear = @{ '18' = '2026'; '17' = '2022'; '16' = '2019'; '15' = '2017' }[$vsVer.Split('.')[0]]
|
||||
if (-not $vsYear) { Write-Error "Unsupported Visual Studio major version: $vsVer (need 15/16/17/18)." }
|
||||
Write-Host ">> Visual Studio ${vsYear}: $vsPath"
|
||||
Set-Item -Path "env:vs${vsYear}_install" -Value $vsPath
|
||||
$env:GYP_MSVS_VERSION = $vsYear
|
||||
|
||||
# --- 3. Fetch / sync the PDFium tree ----------------------------------------
|
||||
New-Item -ItemType Directory -Force -Path $Checkout | Out-Null
|
||||
Push-Location $Checkout
|
||||
if (-not (Test-Path (Join-Path $Checkout 'pdfium'))) {
|
||||
Write-Host '>> gclient config (unmanaged)'
|
||||
& gclient config --unmanaged $repo
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient config failed.' }
|
||||
}
|
||||
Write-Host '>> gclient sync (pulls several GB; slow)'
|
||||
& gclient sync --no-history --shallow --reset --force
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient sync failed.' }
|
||||
|
||||
# --- 4. Pin to the exact commit + sync its DEPS -----------------------------
|
||||
Push-Location (Join-Path $Checkout 'pdfium')
|
||||
& git fetch origin $commit
|
||||
& git checkout --detach $commit
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "git checkout $commit failed." }
|
||||
& gclient sync --no-history --shallow --reset --force -D
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient sync (pinned DEPS) failed.' }
|
||||
|
||||
# --- 5. GN args: static, standalone, monolithic, embed-friendly -------------
|
||||
New-Item -ItemType Directory -Force -Path 'out\Release' | Out-Null
|
||||
Copy-Item (Join-Path $ScriptDir 'args.gn') 'out\Release\args.gn' -Force
|
||||
|
||||
# --- 6. Generate + build ----------------------------------------------------
|
||||
Write-Host '>> gn gen'
|
||||
& gn gen out/Release
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'gn gen failed.' }
|
||||
Write-Host '>> ninja (long compile)'
|
||||
& ninja -C out/Release pdfium
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error 'ninja build failed.' }
|
||||
|
||||
# --- 7. Install: public headers + static lib --------------------------------
|
||||
Write-Host ">> Installing into $Install"
|
||||
if (Test-Path $Install) { Remove-Item -Recurse -Force $Install }
|
||||
New-Item -ItemType Directory -Force -Path "$Install\include", "$Install\lib" | Out-Null
|
||||
Copy-Item 'public\*.h' "$Install\include\" -Force
|
||||
if (Test-Path 'public\cpp') { Copy-Item 'public\cpp' "$Install\include\" -Recurse -Force }
|
||||
$lib = if (Test-Path 'out\Release\obj\pdfium.lib') { 'out\Release\obj\pdfium.lib' }
|
||||
else { 'out\Release\pdfium.lib' }
|
||||
Copy-Item $lib "$Install\lib\" -Force
|
||||
|
||||
Pop-Location
|
||||
Pop-Location
|
||||
Write-Host '>> Done. Configure the engine with -DPDFENGINE_WITH_PDFIUM=ON'
|
||||
Vendored
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build PDFium from source (Linux / macOS) into third_party/pdfium/install/.
|
||||
#
|
||||
# depot_tools, the PDFium checkout, and the GN/Ninja build all happen under a
|
||||
# BUILD ROOT. depot_tools, GN and Ninja do NOT support spaces anywhere in their
|
||||
# own path. If this repo lives under a path containing a space, you MUST point
|
||||
# the build root somewhere space-free:
|
||||
#
|
||||
# PDFIUM_BUILD_ROOT=/tmp/pdfium-build ./third_party/pdfium/build_pdfium.sh
|
||||
#
|
||||
# The finished static lib + public headers are always installed into
|
||||
# third_party/pdfium/install/ (git-ignored) regardless of the build root, so
|
||||
# cmake/pdfium.cmake finds them in the same place either way.
|
||||
#
|
||||
# One-time and slow: a multi-GB gclient sync plus a long compile. See README.md.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_DIR="${SCRIPT_DIR}/install"
|
||||
PINNED_FILE="${SCRIPT_DIR}/pdfium.pinned"
|
||||
|
||||
# Build root: depot_tools + the checkout live here. Defaults to this directory
|
||||
# (fine for repos on a space-free path), but MUST be overridden otherwise.
|
||||
BUILD_ROOT="${PDFIUM_BUILD_ROOT:-${SCRIPT_DIR}}"
|
||||
case "${BUILD_ROOT}" in
|
||||
*' '*)
|
||||
echo "ERROR: PDFium build root contains a space: ${BUILD_ROOT}" >&2
|
||||
echo "depot_tools, GN and Ninja cannot build under a path with spaces." >&2
|
||||
echo "Re-run with a space-free build root, e.g.:" >&2
|
||||
echo " PDFIUM_BUILD_ROOT=/tmp/pdfium-build $0" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
DEPOT_TOOLS_DIR="${BUILD_ROOT}/depot_tools"
|
||||
CHECKOUT_DIR="${BUILD_ROOT}/checkout"
|
||||
echo ">> Build root: ${BUILD_ROOT}"
|
||||
|
||||
# --- 1. Read and validate the pinned revision -------------------------------
|
||||
PDFIUM_REPO="$(grep -E '^PDFIUM_REPO=' "${PINNED_FILE}" | cut -d= -f2-)"
|
||||
PDFIUM_COMMIT="$(grep -E '^PDFIUM_COMMIT=' "${PINNED_FILE}" | cut -d= -f2-)"
|
||||
if [[ -z "${PDFIUM_COMMIT}" || "${PDFIUM_COMMIT}" == "REPLACE_WITH_PINNED_COMMIT_SHA" ]]; then
|
||||
echo "ERROR: PDFium revision is not pinned. Edit pdfium.pinned first (see README.md)." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ">> PDFium pinned at ${PDFIUM_COMMIT}"
|
||||
|
||||
# --- 2. depot_tools ---------------------------------------------------------
|
||||
mkdir -p "${BUILD_ROOT}"
|
||||
if [[ ! -d "${DEPOT_TOOLS_DIR}" ]]; then
|
||||
echo ">> Cloning depot_tools"
|
||||
git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git \
|
||||
"${DEPOT_TOOLS_DIR}"
|
||||
fi
|
||||
export PATH="${DEPOT_TOOLS_DIR}:${PATH}"
|
||||
# Do NOT set DEPOT_TOOLS_UPDATE=0 — depot_tools is designed to self-manage, and
|
||||
# on first use it must bootstrap. Reproducibility comes from the pinned PDFium
|
||||
# revision below, not from freezing depot_tools.
|
||||
|
||||
# Git settings injected per-process via GIT_CONFIG_* so the user's global git
|
||||
# config is never touched. core.autocrlf=false avoids gclient seeing dependency
|
||||
# checkouts as "uncommitted changes" on platforms where autocrlf is enabled.
|
||||
export GIT_CONFIG_COUNT=2
|
||||
export GIT_CONFIG_KEY_0=core.autocrlf GIT_CONFIG_VALUE_0=false
|
||||
export GIT_CONFIG_KEY_1=core.filemode GIT_CONFIG_VALUE_1=false
|
||||
|
||||
# --- 3. Fetch / sync the PDFium tree ----------------------------------------
|
||||
mkdir -p "${CHECKOUT_DIR}"
|
||||
cd "${CHECKOUT_DIR}"
|
||||
if [[ ! -d "${CHECKOUT_DIR}/pdfium" ]]; then
|
||||
echo ">> gclient config (unmanaged)"
|
||||
gclient config --unmanaged "${PDFIUM_REPO}"
|
||||
fi
|
||||
echo ">> gclient sync (pulls several GB; slow)"
|
||||
gclient sync --no-history --shallow --reset --force
|
||||
|
||||
# --- 4. Pin to the exact commit + sync its DEPS -----------------------------
|
||||
cd "${CHECKOUT_DIR}/pdfium"
|
||||
git fetch origin "${PDFIUM_COMMIT}"
|
||||
git checkout --detach "${PDFIUM_COMMIT}"
|
||||
gclient sync --no-history --shallow --reset --force -D
|
||||
|
||||
# --- 5. GN args: static, standalone, monolithic, embed-friendly -------------
|
||||
mkdir -p out/Release
|
||||
cp "${SCRIPT_DIR}/args.gn" out/Release/args.gn
|
||||
|
||||
# --- 6. Generate + build ----------------------------------------------------
|
||||
echo ">> gn gen + ninja"
|
||||
gn gen out/Release
|
||||
ninja -C out/Release pdfium
|
||||
|
||||
# --- 7. Install: public headers + static lib --------------------------------
|
||||
echo ">> Installing into ${INSTALL_DIR}"
|
||||
rm -rf "${INSTALL_DIR}"
|
||||
mkdir -p "${INSTALL_DIR}/include" "${INSTALL_DIR}/lib"
|
||||
cp public/*.h "${INSTALL_DIR}/include/"
|
||||
cp -r public/cpp "${INSTALL_DIR}/include/" 2>/dev/null || true
|
||||
if [[ -f out/Release/obj/libpdfium.a ]]; then
|
||||
cp out/Release/obj/libpdfium.a "${INSTALL_DIR}/lib/"
|
||||
else
|
||||
# Fallback: some configurations emit the lib at the out-dir root.
|
||||
cp out/Release/libpdfium.a "${INSTALL_DIR}/lib/"
|
||||
fi
|
||||
|
||||
echo ">> Done. Configure the engine with -DPDFENGINE_WITH_PDFIUM=ON"
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
# PDFium pinned revision — Rule: never track rolling HEAD.
|
||||
#
|
||||
# Pinned to a specific main-branch commit (a clean dependency-roll commit).
|
||||
# Rebasing is a deliberate, scheduled (quarterly) action — bump the SHA below,
|
||||
# commit it, and re-run the build scripts.
|
||||
#
|
||||
# To re-pin: pick a commit from https://pdfium.googlesource.com/pdfium/+log/main
|
||||
# (or the tip of a recent chromium/NNNN release branch) and update PDFIUM_COMMIT.
|
||||
# The build scripts refuse to run while PDFIUM_COMMIT is the placeholder string.
|
||||
|
||||
PDFIUM_REPO=https://pdfium.googlesource.com/pdfium.git
|
||||
# main @ 2026-05-13 — "Roll third_party/cpu_features/src/ ..."
|
||||
PDFIUM_COMMIT=423b6b376015e9458d588bd9a8f7b5c4ae21f8a2
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
|
||||
"name": "pdf-engine",
|
||||
"version": "0.1.0",
|
||||
"description": [
|
||||
"Cross-platform PDF rendering and editing SDK core.",
|
||||
"'builtin-baseline' pins the entire dependency registry to one vcpkg commit",
|
||||
"(Rule: pin versions on Day 1, never track rolling HEAD). It was added by",
|
||||
"'vcpkg x-update-baseline --add-initial-baseline' via scripts/bootstrap; the",
|
||||
"root CMakeLists.txt refuses to configure if it is ever removed."
|
||||
],
|
||||
"dependencies": [
|
||||
"freetype",
|
||||
"harfbuzz",
|
||||
"spdlog",
|
||||
"gtest"
|
||||
],
|
||||
"builtin-baseline": "495848814af4cc2760e70f7440c2dbe66d3ff196"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# WASM build — placeholder
|
||||
|
||||
Phase 0 task **"WASM hello-world build (Emscripten)"** and the Phase 2 task
|
||||
**"WASM rendering path"** live here. Not implemented yet.
|
||||
|
||||
**Rule R5: WASM never blocks shipping.** Server-side rendering is always the
|
||||
fallback. The Phase 0 WASM task is *validation only* — prove that Emscripten can
|
||||
compile a C++ function callable from JS. Do not gate Phase 1 on it.
|
||||
|
||||
Planned pipeline (engine blueprint §6):
|
||||
|
||||
```sh
|
||||
emcmake cmake --preset wasm
|
||||
emmake cmake --build --preset wasm
|
||||
# outputs: engine.wasm + engine.js
|
||||
```
|
||||
|
||||
Key build flags to carry forward: `ALLOW_MEMORY_GROWTH=1`, Web Worker +
|
||||
OffscreenCanvas for off-main-thread rendering.
|
||||
|
||||
The `cmake/toolchains/wasm.cmake` hook and a `wasm` CMake preset are stubbed in
|
||||
this session so the integration point exists.
|
||||
Reference in New Issue
Block a user