Files
pdf/docs/phase0.md
T
2026-05-15 10:39:16 +05:30

12 KiB

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

# 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):

# 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:

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 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 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:

{
  "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:

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 depsscripts/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).
  • PDFiumthird_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 contractsengine/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:

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.