276 Commits
Author SHA1 Message Date
azeem a766d6d0eb Merge pull request 'ribai' (#81) from ribai into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/81
2026-08-06 06:20:05 +00:00
momorew 9134b5e205 path fixed 2026-08-04 19:24:29 +05:30
momorew 676fec5c8b save button issue 2026-08-03 16:06:58 +05:30
momorew bcdc1fca14 added the required things for the integration of the pdf editor 2026-08-03 15:17:31 +05:30
momorew 33ecf9be84 Merge branch 'fix_issue' of https://gitea.maskantech.in/gitea_admin/pdf into ribai 2026-07-31 15:06:45 +05:30
momorew 6a40ec738d Merge branch 'azeem' of https://gitea.maskantech.in/gitea_admin/pdf into ribai 2026-07-31 12:04:35 +05:30
saqib mir da872bf4eb fix the issue 2026-07-31 10:50:37 +05:30
saqib mir ca3d124b0d fix 2026-07-30 16:48:46 +05:30
azeeee05 dde7829966 feat: implement PDF viewer interface with annotation layer, thumbnail management, and toolbar components 2026-07-29 18:46:28 +05:30
saqib mir 94b7f28d9c fix 2026-07-27 15:16:50 +05:30
saqib mir 00c8839cdc cursor issue 2026-07-27 11:28:53 +05:30
azeeee05 79f32024da feat: configure Vite with React, Tailwind CSS, and allowed development hosts 2026-07-24 11:50:10 +05:30
azeem 15b756e7a2 Merge pull request 'feat: implement gateway service and add font extraction validation tests' (#79) from azeem into docker
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/79
2026-07-23 11:33:16 +00:00
azeeee05 671697d161 feat: implement gateway service and add font extraction validation tests 2026-07-23 17:02:45 +05:30
saqib mir ffc846cf17 fix 2026-07-23 13:53:22 +05:30
saqib mir 626156642e fix 2026-07-23 12:15:20 +05:30
saqib mir 185cc03df7 docker issue 2026-07-23 11:31:26 +05:30
saqib mir a707fb5216 Merge branch 'deploy' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-07-17 15:22:12 +05:30
maskantech 66f7cd345c docker 2026-07-17 15:13:26 +05:30
saqib mir 178e4df163 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-07-17 11:11:07 +05:30
furqan 3775359ab2 Merge pull request 'feat: implement PDF viewer components and scaffolding for interactive document editing' (#78) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/78
2026-07-10 06:53:32 +00:00
azeeee05 27538a9489 feat: implement PDF viewer components and scaffolding for interactive document editing 2026-07-10 12:22:09 +05:30
saqib mir 67b5aed642 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-07-10 11:20:16 +05:30
furqan b15aed60d1 Merge pull request 'azeem' (#77) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/77
2026-07-10 05:49:09 +00:00
azeeee05 ad51b4b270 feat: implement PDF viewer, floating text toolbar, and document editing gateway router 2026-07-09 12:39:47 +05:30
azeeee05 f9ec4e1680 feat: implement redaction UI and backend engine support for area-based PDF redactions 2026-07-09 11:51:28 +05:30
furqan f8af0aa611 Merge pull request 'azeem' (#75) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/75
2026-07-09 05:10:37 +00:00
azeeee05 82a07d2e6e feat: implement PDF annotation extraction and inspector API endpoints via C++ bindings 2026-07-09 10:38:26 +05:30
saqib mir f667db1f75 Render result cache (doc_hash + page + DPI) 2026-07-09 10:26:11 +05:30
saqib mir 10775936de Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-07-09 10:25:24 +05:30
azeeee05 62ad42b116 feat: implement backend and frontend support for PDF text overlays and annotations 2026-07-08 15:53:17 +05:30
furqan 46ec78e9b4 Merge pull request 'feat: implement PDF page parsing, text extraction, and document modeling logic' (#74) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/74
2026-07-08 09:47:32 +00:00
azeeee05 99f87eb7cf feat: implement PDF page parsing, text extraction, and document modeling logic 2026-07-07 19:03:11 +05:30
saqib mir 880e56b98b Tile-based rendering (partial page invalidation) 2026-07-07 11:25:09 +05:30
saqib mir ef59f2ec83 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-07-01 17:28:21 +05:30
saqib mir 72ed04f2ed fix the issue 2026-07-01 17:28:00 +05:30
furqan a383bd8704 Merge pull request 'azeem' (#72) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/72
2026-07-01 09:39:29 +00:00
azeeee05 90027823e4 chore: remove obsolete feature inventory documentationl 2026-07-01 15:07:38 +05:30
azeeee05 5b034c97b8 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-07-01 15:06:08 +05:30
azeeee05 bc3ed20a8b done the signature module 2026-07-01 15:05:37 +05:30
furqan abcd407c35 Merge pull request 'furqan' (#71) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/71
2026-06-30 15:49:48 +00:00
Furqan-14 56f538214f Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-30 21:19:09 +05:30
Furqan-14 17023abf27 feat: updated text handling bugs 2026-06-30 21:18:46 +05:30
azeeee05 afa8c0a702 feat: implement PDF toolbar, viewer components, and redaction support layer 2026-06-30 11:32:15 +05:30
furqan 8d4fa109ff Merge pull request 'furqan' (#70) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/70
2026-06-29 05:23:56 +00:00
Furqan-14 bc92adfb0c Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-29 10:51:23 +05:30
Furqan-14 3b1c36d645 fix: code refactor 2026-06-29 10:51:04 +05:30
furqan a434607276 Merge pull request 'furqan' (#69) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/69
2026-06-26 06:04:37 +00:00
Furqan-14 e795eed63e feat: updated font handling in older pdfs, along with tables 2026-06-26 11:33:28 +05:30
Furqan-14 52b45d82ea Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-24 10:42:26 +05:30
Furqan-14 e6e1e0ba7c fix: font handling in revista 2026-06-24 10:42:05 +05:30
furqan 6b5c22130f Merge pull request 'saqib' (#68) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/68
2026-06-22 11:25:33 +00:00
saqib mir 23b162f3c2 create the api for the display list 2026-06-22 16:23:59 +05:30
saqib mir caf3feea15 fix 2026-06-22 15:24:05 +05:30
saqib mir d290e65685 Path construction interpreter (m l c re S f B) 2026-06-22 15:21:39 +05:30
furqan 0ce4759e20 Merge pull request 'fix: code cleanup' (#67) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/67
2026-06-22 09:50:27 +00:00
Furqan-14 e8620947a5 fix: code cleanup 2026-06-22 15:18:47 +05:30
furqan f1b427d66e Merge pull request 'saqib' (#66) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/66
2026-06-22 05:22:32 +00:00
saqib mir ac5a1cc516 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-22 10:48:57 +05:30
furqan f4c4fc08d8 Merge pull request 'furqan' (#65) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/65
2026-06-22 05:18:42 +00:00
Furqan-14 c0bcfe47a2 fix: additional font handling, with operators font issues 2026-06-22 10:48:00 +05:30
saqib mir e7fad0f09d Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-22 10:18:00 +05:30
saqib mir e1e1c91736 fix the issue 2026-06-22 10:17:51 +05:30
saqib mir 8e9b0c8dfb test: verify image xobjects 2026-06-19 17:08:36 +05:30
Furqan-14 c4f5012351 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-18 18:25:11 +05:30
Furqan-14 c46b462dd0 feat: implemented multiple new fonts 2026-06-18 18:25:05 +05:30
furqan a14b09b8ac Merge pull request 'saqib' (#64) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/64
2026-06-18 12:32:58 +00:00
saqib mir 7e6e7ef32d Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-18 17:31:53 +05:30
furqan d77437b966 Merge pull request 'furqan' (#63) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/63
2026-06-18 11:30:30 +00:00
Furqan-14 edb991606e Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-18 17:00:00 +05:30
Furqan-14 0e028e870a fix: plans cleanup 2026-06-18 16:59:53 +05:30
furqan 686d82ef01 Merge pull request 'furqan' (#62) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/62
2026-06-18 11:19:25 +00:00
Furqan-14 c8ec1feb7f Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-18 16:48:55 +05:30
Furqan-14 a124a43877 feat: implemented multi page reflow 2026-06-18 16:48:29 +05:30
saqib mir e677da3064 Image XObject handler (Do operator) update 2026-06-18 16:14:50 +05:30
saqib mir b3d3ac4469 Image XObject handler (Do operator) 2026-06-18 16:14:29 +05:30
furqan 661600075a Merge pull request 'furqan' (#61) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/61
2026-06-18 04:34:08 +00:00
Furqan-14 ff0962b111 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-18 10:03:25 +05:30
Furqan-14 bd6ba9205f fix: updated text reflow bugs 2026-06-18 10:03:10 +05:30
Furqan-14andClaude Opus 4.8 63369d4259 fix(reflow): wrapped bullet continuation got bullet-gap spacing instead of wrap spacing
buildBulletItem computed `leading` as the median over ALL paragraph line gaps. In a bullet list
the bullet-to-bullet gap (~14.2pt) is larger than the within-bullet wrap gap (~12.2pt) and dominates
the median, so a newly-wrapped continuation line was placed ~2pt too low (visible extra spacing
between e.g. the Architecture line and its "Design Patterns…" continuation).

Use the WRAP spacing instead: prefer the edited item's own internal line gap (if it already wraps),
then any hanging-continuation gap in the block, then the all-lines median, then font*1.2. Only
affects multi-line (wrapped) bullet items; single-line edits emit no continuation so are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:40:59 +05:30
Furqan-14andClaude Opus 4.8 6edec0cf22 fix(reflow): inserted bullet text picked up the bold label font
Typing at a run boundary (e.g. near "Async" in a bullet) often lands the new text in a
browser-created wrapper element whose only child is that text. resolveStyleEl's sibling-only
search then found no data-fid run and fell back to dominantFid — which for a bullet is the bold
LABEL run (its first run) — so the inserted text rendered bold.

Make resolveStyleEl CLIMB toward the editor root and scan siblings at each level (nearest
preceding run first, then following), so it reaches the real run spans alongside the wrapper and
the inserted text continues the adjacent run's font. data-advances is still read only from the
exact wrapping span, so inherited fonts correctly re-measure. Text typed inside an existing run
is unaffected (step 1 returns its span immediately).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 20:49:30 +05:30
Furqan-14andClaude Opus 4.8 839add269a fix(reflow): center/right edits 422'd and silently reverted
Two bugs made centered/right-aligned heading edits vanish on commit:

1. Gateway: ReflowParagraphData.align was Literal["left","justify"], so a center/right reflow
   commit failed Pydantic validation -> 422 Unprocessable Entity. Widened to include
   "center","right" (matches the frontend + engine, which already handle them).

2. Frontend: gatewayService.applyEdits caught ALL errors (incl. HTTP 422/403/500) and returned
   fake success with a phantom `${documentId}_edited` id. The app then adopted that id and 404'd
   on every fetch, so the edit silently reverted instead of showing an error. Now only genuine
   network failures use the offline fallback; real HTTP errors throw the gateway's detail so the
   caller toasts a clear message and stays on the current document.

Gateway runs with --reload (auto-picks up the model change); frontend needs a refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 20:37:18 +05:30
Furqan-14andClaude Opus 4.8 ddbafbcd43 fix(build): build_wasm.ps1 builds the real wasm-pdfium engine + deploys pdfium-engine.{mjs,wasm}
build_wasm.ps1 built the `wasm` STUB preset (PDFENGINE_WITH_PDFIUM=OFF, reflow #ifdef'd out)
and deployed it to the UNUSED pdfengine.{mjs,wasm}. The frontend actually loads
pdfium-engine.{mjs,wasm} (the `wasm-pdfium` preset). So "rebuild the WASM" silently shipped a
reflow-less stub to the wrong file — engine changes never reached the browser.

- Build the `wasm-pdfium` preset (PDFium ON) into pdfeng-build/wasm-pdfium (outside OneDrive).
- Deploy bin/pdfengine.{mjs,wasm} -> public/pdfium-engine.{mjs,wasm} (the rename the frontend expects).
- Set EMSDK_QUIET before sourcing emsdk_env.ps1 (its stderr tripped $ErrorActionPreference='Stop').
- Run a new pdfium-engine.smoke.mjs (buildInfo + live-preview exports) instead of pdfengine.test.mjs,
  which targets the stub facade (mock render / engineHasSkia / getDocumentFonts) and can't pass here.
- Relax EAP around the smoke node call so a real failure reports cleanly, not as NativeCommandError.
- pdfengine.test.mjs: de-brittle the buildInfo assertion (was pinned to a stale exact phrase).

Verified: scripts/build_wasm.ps1 runs green end-to-end and deploys pdfium-engine.wasm (md5 53d1ec9f).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:24:45 +05:30
Furqan-14andClaude Opus 4.8 aff19ac027 build(wasm): deploy the REAL pdfium-engine.wasm with reflow fixes (caret x0, center/right)
The live-preview engine the frontend loads is pdfium-engine.wasm, built by the
`wasm-pdfium` CMake preset (PDFENGINE_WITH_PDFIUM=ON). The reflow code is inside
`#ifdef PDFENGINE_WITH_PDFIUM`, so the `wasm` stub preset (PDFium OFF) — which
scripts/build_wasm.ps1 builds and copies to the UNUSED pdfengine.wasm — never
contained reflow at all. Result: every prior "WASM rebuilt" step updated the wrong,
reflow-less artifact, so the caret x0 fix (and center/right) never reached the browser.

Rebuilt the wasm-pdfium preset and deployed bin/pdfengine.{mjs,wasm} ->
public/pdfium-engine.{mjs,wasm} (md5 now 53d1ec9f, reflow object 631KB vs the 18KB
stub). Bumped the cache-buster so browsers fetch the new engine.

Engine .pyd + overlay-diff gate unchanged (1.70%/1.69%, render identical); the x0
change is caret-only metadata.

Follow-up: build_wasm.ps1 builds the wrong (stub) preset and deploys the wrong file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:12:44 +05:30
Furqan-14andClaude Opus 4.8 62c4aed48c fix(reflow): caret lands on wrong char in centered/right headings
The reflow caret layout hardcoded x0 = columnLeft. That matched only left-aligned
lines; for the new center/right headings the line renders at a shifted x (e.g. a
centered title starts ~215pt while columnLeft is the page margin ~54pt), so the
frontend mapped clicks and drew the caret ~160pt off the actual glyphs.

Record the line's true starting x (after lineX/center/right) as x0 instead. x0 is
caret-only metadata read by the live editor — it does not affect glyph emission or
the rendered page, so output is unchanged (overlay-diff gate identical: 1.70%/1.69%,
STRESS PASS). Rebuilt .pyd + WASM; cache version bumped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:10:19 +05:30
Furqan-14andClaude Opus 4.8 660649a562 fix(reflow): kill bulge/merge + font-change on edit; add center/right, IME; harden Raw Text
Reflow (primary editor):
- Geometric backstop in reflow_paragraph: adopt any text object fully inside the paragraph
  bbox that the model's objectIndices omitted (PDFium glyph->object map can return -1), so it's
  deleted + its original font is resolved. Fixes both the leftover-glyph "bulge/merge" and the
  font-substitution-on-edit (same root cause). No-op on correctly-indexed paragraphs (gate-proven
  byte-identical: overlay diff unchanged at 1.69%/1.70%).
- Preserve data-fid on edited contentEditable nodes (extractFlatRuns climbs to nearest styled
  ancestor / inherits from adjacent run) so edits keep their real font instead of the dominant.
- Converge preview & commit through one buildReflowData (no preview-OK/commit-wrong drift).
- Center/right alignment: additive emission branch (greedy path only) + heading-align inference.
- IME composition handling (suppress render mid-composition; don't commit on composing Enter).
- Fix WASM document-handle leak (free superseded versions on documentId change/unmount).
- Quiet the backstop instrumentation (warn -> debug) now the root cause is confirmed.

Raw Text / StreamEditor (beta companion):
- Permission-gate both text_objects endpoints behind canModify; label tool "(beta)".
- P1a: preserve TJ kerning numbers for same-length edits (redistribute into original slots),
  fall back to single-string collapse otherwise (never worse than before).
- P1b: reject edits with characters unencodable in the run's font (clear 400 instead of corruption).
- P2a: commit Raw Text edits as a new document version (add_document) and adopt the id via
  pushHistory, so they join undo/redo instead of mutating in place.
- P2b: exact hit-boxes from real font metrics (re-measured once @font-face loads).

WASM preview rebuilt with the reflow changes; cache version bumped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:12:53 +05:30
furqan 20177b5287 Merge pull request 'furqan' (#60) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/60
2026-06-17 06:35:59 +00:00
Furqan-14 21f0bfab9a Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-17 11:39:58 +05:30
furqan 4b0ea5b963 Merge pull request 'saqib' (#59) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/59
2026-06-17 06:09:44 +00:00
Furqan-14 8f881dfbd5 feat: implemented reflow for bullets and single lines along with the fix for save on delay 2026-06-17 11:39:01 +05:30
saqib mir 69d7346803 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-17 11:26:18 +05:30
saqib mir a39c15d0be content completed 2026-06-17 11:03:47 +05:30
saqib mir 20ee7fbe20 content ad the frontend 2026-06-16 19:27:32 +05:30
saqib mir 7cd400e62a content stream 2026-06-16 19:06:48 +05:30
Furqan-14 c449cf01b9 feat: updated the text reflow engine 2026-06-16 16:45:04 +05:30
furqan f6f4149159 Merge pull request 'fix: reflow for bullets' (#58) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/58
2026-06-16 08:43:51 +00:00
Furqan-14 d53f059952 fix: reflow for bullets 2026-06-16 14:13:14 +05:30
saqib mir 32d6f1379b Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-16 10:58:17 +05:30
furqan a84ff13170 Merge pull request 'azeem' (#57) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/57
2026-06-16 05:27:46 +00:00
azeeee05 0d095cf14b Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-06-16 10:57:01 +05:30
furqan 7db06c59e7 Merge pull request 'furqan' (#56) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/56
2026-06-16 05:26:56 +00:00
Furqan-14 852177db18 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-16 10:56:20 +05:30
Furqan-14 ec509400e7 feat: implemented real flow text editor for paragraphs 2026-06-16 10:56:08 +05:30
azeeee05 42b4224bb3 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-06-16 10:54:14 +05:30
azeeee05 f8fb4feaff done undo functions 2026-06-16 10:53:37 +05:30
saqib mir 3ad646fe6a Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-12 19:17:21 +05:30
furqan 2d5bf3f464 Merge pull request 'furqan' (#55) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/55
2026-06-12 13:44:12 +00:00
Furqan-14 6b78a72f68 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-12 19:13:42 +05:30
Furqan-14 44f64a0d99 feat: implemented caret based selection at any click position 2026-06-12 19:13:29 +05:30
saqib mir 23ceda6848 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-12 15:39:27 +05:30
saqib mir 1ff5bcfd25 fix 2026-06-12 15:39:14 +05:30
furqan be45b2211b Merge pull request 'feat: implemented base editing, underline and striking' (#54) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/54
2026-06-12 09:34:50 +00:00
Furqan-14 d47895c990 feat: implemented base editing, underline and striking 2026-06-12 15:04:09 +05:30
furqan 6678f55e46 Merge pull request 'furqan' (#53) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/53
2026-06-12 06:25:14 +00:00
Furqan-14 f11bac7419 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-12 11:31:40 +05:30
Furqan-14 03cabc05a3 feat: implemented unicode and update base fonts, and sub fonts 2026-06-12 11:31:10 +05:30
furqan da2cc61496 Merge pull request 'saqib' (#52) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/52
2026-06-12 06:00:17 +00:00
saqib mir 74a2ba7b8d merge conflict solve 2026-06-11 19:36:38 +05:30
saqib mir 4d900c3d0c Underline + decoration renderer (DecorationBuilder) 2026-06-11 19:30:27 +05:30
furqan fa5a14273b Merge pull request 'azeem' (#51) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/51
2026-06-11 13:58:58 +00:00
azeeee05 18d7cd98d1 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-06-11 19:23:45 +05:30
azeeee05 948461da65 fixed issues on edit 2026-06-11 19:23:34 +05:30
furqan 6a17280972 Merge pull request 'furqan' (#50) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/50
2026-06-11 13:52:49 +00:00
Furqan-14 98c63f91fc Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-11 19:22:07 +05:30
furqan 996c2510fe Merge pull request 'saqib' (#49) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/49
2026-06-11 13:49:38 +00:00
Furqan-14 377a9044af feat: implemented hit testing, form field, content stream and base security hardening 2026-06-11 19:19:01 +05:30
saqib mir b52890818b update the pipiline 2026-06-11 18:19:26 +05:30
saqib mir 9159426037 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-11 12:02:40 +05:30
furqan a4131d828e Merge pull request 'done anotations update' (#48) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/48
2026-06-11 06:31:01 +00:00
azeeee05 c410f787f7 done anotations update 2026-06-11 11:46:15 +05:30
saqib mir 33667f8985 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-11 11:27:27 +05:30
furqan d588effd63 Merge pull request 'azeem' (#47) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/47
2026-06-11 05:56:33 +00:00
azeeee05 ab88a05d36 Image XObject handler (Do operator) 2026-06-11 11:25:27 +05:30
azeeee05 addc888545 done Image XObject handler (Do operator) 2026-06-11 11:24:35 +05:30
saqib mir aea20c428d Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-11 11:09:23 +05:30
furqan 98e3a2f89c Merge pull request 'fix: updated form fields' (#46) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/46
2026-06-11 05:38:56 +00:00
Furqan-14 27bbfd404d fix: updated form fields 2026-06-11 11:08:26 +05:30
saqib mir 9131b11932 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-11 10:24:04 +05:30
furqan a2d72a7945 Merge pull request 'fix: reworked ui components' (#45) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/45
2026-06-10 16:22:19 +00:00
Furqan-14 fa35589f81 fix: reworked ui components 2026-06-10 21:51:43 +05:30
saqib mir 9b94954613 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-10 19:33:39 +05:30
saqib mir fd0f8e3ec9 update the glyph 2026-06-10 19:33:27 +05:30
furqan 5f5c09eaa9 Merge pull request 'azeem' (#44) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/44
2026-06-10 14:01:55 +00:00
azeeee05 8f3ce037e3 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-06-10 19:30:24 +05:30
azeeee05 d2c429f047 done React: form field viewing (Widget annotations) with saving issues fixed 2026-06-10 19:29:11 +05:30
azeeee05 58b39b6780 React: form field viewing (Widget annotations) 2026-06-10 18:38:38 +05:30
furqan 7ce4cbbd8b Merge pull request 'saqib' (#43) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/43
2026-06-10 11:03:40 +00:00
saqib mir d68a9e9239 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-10 16:13:26 +05:30
furqan 41ab58ba67 Merge pull request 'fix: updated .env' (#42) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/42
2026-06-10 10:00:47 +00:00
Furqan-14 b0baf2a057 fix: updated .env 2026-06-10 15:30:29 +05:30
furqan d80ea18ea3 Merge pull request 'azeem' (#41) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/41
2026-06-10 10:00:07 +00:00
azeeee05 5dfbfe6d26 added about modal for the pdf 2026-06-10 15:29:00 +05:30
azeeee05 bda4f3925b Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-06-10 15:24:04 +05:30
furqan f46a60652e Merge pull request 'furqan' (#40) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/40
2026-06-10 09:53:16 +00:00
Furqan-14 6014a47a94 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-10 15:22:44 +05:30
Furqan-14 42993cb839 feat: updated annotations 2026-06-10 15:22:34 +05:30
azeeee05 9f30da5c03 feat: add InspectorPanel component with document management, navigation, and search tabs 2026-06-10 15:20:52 +05:30
azeeee05 7f43262385 done overall ui enhancement 2026-06-10 15:19:36 +05:30
azeeee05 4ec8fadf9f done overall ui enhnace 2026-06-10 15:16:20 +05:30
saqib mir e84d522ad3 fix 2026-06-10 14:46:56 +05:30
furqan 2b343fadbe Merge pull request 'fix: updated ui and functionalities' (#39) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/39
2026-06-10 06:12:40 +00:00
Furqan-14 ef544263a5 fix: updated ui and functionalities 2026-06-10 11:41:58 +05:30
furqan f8d7d4c042 Merge pull request 'ui completed' (#38) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/38
2026-06-09 12:26:36 +00:00
saqib mir b976af400e ui completed 2026-06-09 17:53:24 +05:30
furqan 5189535def Merge pull request 'fix: build errors, and annotation issues' (#37) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/37
2026-06-09 05:39:19 +00:00
Furqan-14 4544f5bab1 fix: build errors, and annotation issues 2026-06-09 11:08:45 +05:30
furqan 294f34cc29 Merge pull request 'saqib' (#36) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/36
2026-06-09 05:25:51 +00:00
saqib mir 345f88c516 fix 2026-06-09 10:52:54 +05:30
saqib mir c1186437a8 fix 2026-06-09 10:50:03 +05:30
furqan b54a360273 Merge pull request 'furqan' (#35) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/35
2026-06-09 05:19:09 +00:00
Furqan-14 f1fcbf0ebb Resolve verifiedPages ref compilation issue in PDFViewer 2026-06-09 10:46:41 +05:30
Furqan-14 dc30a6e845 Merge branch 'dev' into furqan, resolving OverlayLayer conflict 2026-06-09 10:45:55 +05:30
Furqan-14 83e18fff6f feat: implemented export and wasm rendering 2026-06-09 10:39:45 +05:30
furqan b86f39bb3e Merge pull request 'azeem' (#34) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/34
2026-06-09 05:06:52 +00:00
saqib mir 6e987bb7e9 phase2 2026-06-09 10:35:12 +05:30
Furqan-14 5408540847 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-08 16:54:28 +05:30
Furqan-14 5d366132ef feat: page deletion, redaction and page rotation 2026-06-08 16:53:02 +05:30
azeeee05 405f3d6af5 merged code with dev 2026-06-08 16:38:00 +05:30
furqan b92d9caae9 Merge pull request 'saqib' (#33) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/33
2026-06-08 10:59:22 +00:00
saqib mir c27a80f840 integration done and testing done 2026-06-08 16:28:10 +05:30
azeeee05 988f6b90df done the highlight 2026-06-08 16:27:53 +05:30
saqib mir 998f32c0a4 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-06-08 11:29:55 +05:30
furqan d5d30c6056 Merge pull request 'azeem' (#32) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/32
2026-06-08 05:59:37 +00:00
azeeee05 5ea14b6fd4 Merged with dev branch 2026-06-08 11:28:43 +05:30
saqib mir 1f16517160 fix the error 2026-06-08 11:27:40 +05:30
saqib mir cc152a79b5 merger 2026-06-08 11:24:27 +05:30
saqib mir 34906a6110 fix 2026-06-08 11:23:27 +05:30
furqan 2638dfe969 Merge pull request 'feat: implemented page rotation and deletion' (#31) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/31
2026-06-08 05:53:07 +00:00
Furqan-14 ed12275976 feat: implemented page rotation and deletion 2026-06-08 11:22:41 +05:30
azeeee05 31243722d3 search fuunction done 2026-06-08 11:22:15 +05:30
saqib mir bfa8d0eddf fix the issue 2026-06-06 11:21:31 +05:30
saqib mir ce5ad5bb16 fix 2026-06-05 10:27:39 +05:30
saqib mir 0862afa1ce update some minor fixes 2026-06-03 10:51:33 +05:30
furqan dc6e538c81 Merge pull request 'fix: invalid pdf format' (#30) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/30
2026-06-02 06:54:16 +00:00
Furqan-14 869b865a1d fix: invalid pdf format 2026-06-02 12:23:34 +05:30
furqan 2c797a374f Merge pull request 'furqan' (#29) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/29
2026-06-02 06:11:15 +00:00
Furqan-14 4e8fcb8aaa Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-06-02 11:40:40 +05:30
furqan e580423c41 Merge pull request 'fix the issue' (#28) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/28
2026-06-02 06:10:31 +00:00
Furqan-14 638ea5ba1c feat: Text overlays (draw new text above PDF layer) 2026-06-02 11:38:57 +05:30
saqib mir 581ef030f2 fix the issue 2026-06-02 11:16:49 +05:30
furqan 2068737fc9 Merge pull request 'feat: wasm build along with c++ facade' (#27) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/27
2026-06-01 09:59:12 +00:00
Furqan-14 6eb7c23cc4 feat: wasm build along with c++ facade 2026-06-01 15:28:32 +05:30
furqan 84a434b48a Merge pull request 'saqib' (#26) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/26
2026-06-01 09:28:46 +00:00
saqib mir 61f0b6fde6 fix update 2026-06-01 14:54:55 +05:30
saqib mir 09b3353b69 fix the environment 2026-05-27 15:51:36 +05:30
saqib mir caf6155d2e Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-27 12:48:07 +05:30
furqan ad7c659edd Merge pull request 'fix: gateway script issue' (#25) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/25
2026-05-27 07:17:58 +00:00
Furqan-14 313801b08c fix: gateway script issue 2026-05-27 12:47:38 +05:30
saqib mir 4e4ea6cf28 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-27 12:27:30 +05:30
saqib mir 0066587660 fix the issue 2026-05-27 12:27:16 +05:30
furqan 6968c99e97 Merge pull request 'fix: updated port' (#24) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/24
2026-05-27 06:55:52 +00:00
Furqan-14 3cc8ad17b1 fix: updated port 2026-05-27 12:25:00 +05:30
saqib mir 5883915016 fix the test 2026-05-26 19:20:02 +05:30
furqan b064c2c5dc Merge pull request 'azeem' (#23) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/23
2026-05-26 12:10:23 +00:00
azeeee05 a08d826459 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-05-26 17:39:31 +05:30
azeeee05 301e53d739 done INTEGRATION WITH GATEWAY TO C++ 2026-05-26 17:39:03 +05:30
saqib mir 186ae61579 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-26 16:51:53 +05:30
saqib mir acdd4d1b6e fix 2026-05-26 16:51:44 +05:30
furqan 6ae30f9e95 Merge pull request 'furqan' (#22) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/22
2026-05-26 11:19:34 +00:00
Furqan-14 867d00ad3c fix: updated build scripts 2026-05-26 16:49:02 +05:30
Furqan-14 2b2e64bbcd Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan 2026-05-26 16:48:38 +05:30
Furqan-14 876c13c089 docs: simplify developer command reference guide 2026-05-26 16:21:51 +05:30
saqib mir 15374a12cd Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-26 16:14:18 +05:30
furqan e0d446e95c Merge pull request 'fix: build error' (#21) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/21
2026-05-26 10:43:52 +00:00
Furqan-14 5e5404c330 fix: build error 2026-05-26 16:13:11 +05:30
saqib mir 253332bf02 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-26 15:57:09 +05:30
furqan 81ba95ed88 Merge pull request 'azeem' (#20) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/20
2026-05-26 10:26:58 +00:00
saqib mir 37b82b697a fix 2026-05-26 15:56:44 +05:30
azeeee05 3b74827461 merged code 2026-05-26 15:56:25 +05:30
saqib mir d2a12b4d1d fix 2026-05-26 15:55:48 +05:30
azeeee05 6e7c6973dd connected fe and gateway 2026-05-26 15:54:49 +05:30
furqan 2af092a209 Merge pull request 'feat: wasm update and annotations' (#19) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/19
2026-05-26 10:22:28 +00:00
Furqan-14 39794ca65f feat: wasm update and annotations 2026-05-26 15:52:04 +05:30
saqib mir 0685f539da Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-26 11:24:23 +05:30
saqib mir 5b73e2de2d update some functions 2026-05-26 11:24:17 +05:30
furqan ec265c663e Merge pull request 'feat: implemented Incremental save system' (#18) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/18
2026-05-26 05:53:50 +00:00
Furqan-14 bb1b2dcd50 feat: implemented Incremental save system 2026-05-26 11:23:29 +05:30
furqan c3447bb793 Merge pull request 'feat: implemented text extraction with bounding boxes' (#17) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/17
2026-05-25 09:18:03 +00:00
Furqan-14 f961c579bc feat: implemented text extraction with bounding boxes 2026-05-25 14:47:38 +05:30
furqan 5ce96ebd5d Merge pull request 'furqan' (#16) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/16
2026-05-25 05:33:10 +00:00
Furqan-14 0e142d5d41 fix: updated gitignore 2026-05-25 11:02:40 +05:30
Furqan-14 0e20484db1 chore: untrack compiled Python dynamic libraries and ignore them 2026-05-25 11:02:27 +05:30
furqan 9452bb4357 Merge pull request 'azeem' (#15) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/15
2026-05-25 05:24:30 +00:00
azeeee05 1d399c69ca Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem 2026-05-25 10:35:02 +05:30
azeeee05 21b9390e48 done phase 1 code base 2026-05-25 10:34:39 +05:30
furqan a33a96af30 Merge pull request 'saqib' (#14) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/14
2026-05-25 05:03:55 +00:00
saqib mir 1637413bf3 Font substitution system (Liberation fonts) 2026-05-25 10:28:44 +05:30
saqib mir 497443c2e9 FreeType + HarfBuzz wrappers + LRU glyph cache and PDF font resource loader (Type1/TrueType/CID) 2026-05-23 16:54:23 +05:30
azeeee05 52cef2cecf merged code with dev 2026-05-22 15:50:43 +05:30
saqib mir e424c31976 merge resolve 2026-05-22 15:50:33 +05:30
azeeee05 b9e3366e65 done graphics basic 2026-05-22 15:49:10 +05:30
saqib mir 77b59d4432 FreeType + HarfBuzz wrappers + LRU glyph cache 2026-05-22 15:48:57 +05:30
furqan 56825ca4bd Merge pull request 'feat: implemented wrapper and pdf doc page' (#13) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/13
2026-05-22 10:18:31 +00:00
Furqan-14 fe627f4e19 feat: implemented wrapper and pdf doc page 2026-05-22 15:47:48 +05:30
furqan d4fe33b007 Merge pull request 'fix: updated commands' (#12) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/12
2026-05-21 12:41:12 +00:00
Furqan-14 6006311577 fix: updated commands 2026-05-21 18:10:15 +05:30
furqan afc882985a Merge pull request 'saqib' (#11) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/11
2026-05-21 11:35:44 +00:00
saqib mir 87b26ed3f2 command 2026-05-21 17:03:03 +05:30
saqib mir 0a3b6a127e Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-21 15:28:36 +05:30
saqib mir 1aa79740fb file created 2026-05-21 15:28:15 +05:30
furqan 955819fad4 Merge pull request 'azeem' (#10) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/10
2026-05-21 09:57:37 +00:00
azeeee05 94b1d2aaa8 merged code with dev 2026-05-21 15:06:36 +05:30
azeeee05 e29e419a2e doen frontend to show the skia integration 2026-05-21 15:05:36 +05:30
azeeee05 c044fc86bc done skia base code 2026-05-21 14:59:09 +05:30
furqan 435d8e48bd Merge pull request 'saqib' (#9) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/9
2026-05-21 08:51:38 +00:00
saqib mir e2279145cf Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-21 14:20:10 +05:30
saqib mir 4326e5e8c5 remove the wasm form gitignore 2026-05-21 14:19:57 +05:30
furqan f60d9724da Merge pull request 'saqib' (#8) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/8
2026-05-21 07:23:34 +00:00
saqib mir 6e9198a939 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-21 12:51:18 +05:30
saqib mir 86ac50cb42 WASM hello-world build (Emscripten) 2026-05-21 12:51:06 +05:30
furqan 080172edfc Merge pull request 'fix: lint errors' (#7) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/7
2026-05-21 06:46:56 +00:00
Furqan-14 217b08bc9c fix: lint errors 2026-05-21 12:16:17 +05:30
furqan 85a303ea03 Merge pull request 'saqib' (#6) from saqib into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/6
2026-05-21 06:24:01 +00:00
saqib mir 81297f7ea5 Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib 2026-05-21 11:46:09 +05:30
furqan 7cca1dbce9 Merge pull request 'done base code for frontend' (#4) from azeem into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/pdf/pulls/4
2026-05-20 10:11:54 +00:00
azeeee05 bce334fb33 done base code for frontend 2026-05-20 15:39:50 +05:30
484 changed files with 65692 additions and 936 deletions
+30
View File
@@ -0,0 +1,30 @@
**/.git
**/.github
**/.venv
**/node_modules
**/dist
**/build
**/out
**/__pycache__
**/*.pyc
**/*.pyo
**/*.pyd
**/*.log
**/.DS_Store
**/Thumbs.db
**/.vscode
**/.idea
**/coverage
**/tmp
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
**/CMakeUserPresets.json
**/compile_commands.json
**/vcpkg
**/third_party/pdfium/depot_tools
**/third_party/pdfium/checkout
**/third_party/pdfium/install
**/third_party/skia/depot_tools
**/third_party/skia/checkout
**/third_party/skia/install
+104
View File
@@ -0,0 +1,104 @@
# Extended robustness: corpus render-stability sweep + libFuzzer run.
#
# DELIBERATELY ISOLATED from the main CI gate:
# - never triggers on push or pull_request, so it can NEVER block a merge,
# push, or pull;
# - the whole job is continue-on-error, so a crash finding or a build/runner
# problem reports red here but does not fail any required check;
# - runs on a weekly schedule and on manual dispatch only.
#
# A full 24h fuzz run needs a self-hosted runner (GitHub-hosted runners cap a job
# at 6h). Use the workflow_dispatch `duration_seconds` input for that; the weekly
# schedule does a short smoke instead.
name: Fuzz & corpus sweep
on:
schedule:
- cron: "0 3 * * 0" # Sundays 03:00 UTC — short smoke
workflow_dispatch:
inputs:
duration_seconds:
description: "libFuzzer -max_total_time (e.g. 1800 smoke, 86400 for 24h on a self-hosted runner)"
default: "1800"
sanitizers:
description: "Sanitizer set (fuzzer,address,undefined needs an ASan/UBSan-built PDFium; fuzzer = coverage-only)"
default: "fuzzer,address,undefined"
permissions:
contents: read
concurrency:
group: fuzz-${{ github.ref }}
cancel-in-progress: true
jobs:
fuzz:
# Non-blocking by construction: nothing depends on this job and it is allowed to fail.
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 1500 # permits a 24h dispatch on a self-hosted runner; hosted runners stop at 6h
env:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
FUZZ_SANITIZERS: ${{ github.event.inputs.sanitizers || 'fuzzer' }}
FUZZ_DURATION: ${{ github.event.inputs.duration_seconds || '600' }}
steps:
- uses: actions/checkout@v4
- name: Install Ninja + Clang
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build clang
- name: Locate vcpkg
shell: bash
run: |
echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"
- name: Cache vcpkg artifacts
uses: actions/cache@v4
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: vcpkg-fuzz-${{ hashFiles('vcpkg.json') }}
restore-keys: vcpkg-fuzz-
- name: Pin vcpkg dependency baseline
shell: bash
run: |
if ! grep -q '"builtin-baseline"' vcpkg.json; then
"$VCPKG_ROOT/vcpkg" x-update-baseline --add-initial-baseline
fi
- name: Configure (fuzz-linux)
run: cmake --preset fuzz-linux -DPDFENGINE_FUZZ_SANITIZERS="$FUZZ_SANITIZERS"
- name: Build fuzzer
run: cmake --build --preset fuzz-linux --target pdfengine_fuzz
- name: Fetch corpus (pinned + hash-verified)
shell: bash
run: python3 scripts/fetch_corpus.py --manifest tests/regression/corpus-manifest.json || echo "corpus fetch failed (network) — continuing with committed corpus"
- name: Render-stability sweep (replay every corpus PDF once)
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
mkdir -p engine/fuzz/artifacts
"$BIN" -runs=0 -artifact_prefix=engine/fuzz/artifacts/ corpus/ corpus/fuzz/ || true
- name: Fuzz run
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
"$BIN" -max_total_time="$FUZZ_DURATION" -print_final_stats=1 \
-artifact_prefix=engine/fuzz/artifacts/ corpus/fuzz/ corpus/ || true
- name: Upload crash artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts
path: engine/fuzz/artifacts/
if-no-files-found: ignore
+24 -2
View File
@@ -22,6 +22,11 @@ CMakeUserPresets.json
/third_party/pdfium/install/
/third_party/pdfium/.gclient*
# Skia from-source build (depot_tools / GN / Ninja)
/third_party/skia/depot_tools/
/third_party/skia/checkout/
/third_party/skia/install/
# IDE / editor
/.vs/
/.vscode/
@@ -51,10 +56,27 @@ node_modules/
/frontend/.vite/
# WASM artifacts
*.wasm
*.wasm.map
# Logs / misc
*.log
PDF Editor Timeline.xlsx
# Compiled Python bindings (binary build outputs)
gateway/*.pyd
gateway/*.so
gateway/*.dylib
# Downloaded fuzzing corpus (large; fetched via scripts/fetch_corpus.py)
corpus/fuzz/
# Large-corpus regression baseline (derived from the gitignored corpus above)
tests/regression/baseline-large/
# Fuzzer working artifacts (crashes, leaks, coverage)
engine/fuzz/artifacts/
crash-*
leak-*
timeout-*
PDF Editor Timeline.xlsx
# Local environment config / secrets
/third_party/pdfium-wasm/
+51 -11
View File
@@ -32,29 +32,54 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
if(EMSCRIPTEN)
message(STATUS "PdfEngine ${PROJECT_VERSION} — WASM hello-world configuration (Phase 0)")
add_subdirectory(wasm)
return()
endif()
# 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")
set(PDFENGINE_WASM ON CACHE INTERNAL "Building for WebAssembly")
endif()
option(PDFENGINE_BUILD_TESTS "Build engine unit/smoke tests" ON)
option(PDFENGINE_ENABLE_SANITIZERS "Build with AddressSanitizer/UBSan" OFF)
option(PDFENGINE_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF)
option(PDFENGINE_WITH_PDFIUM "Link the PDFium static lib (build it first)" OFF)
option(PDFENGINE_WITH_SKIA "Link the Skia static lib (build it first)" OFF)
option(PDFENGINE_WITH_QPDF "Link QPDF for content stream extraction" OFF)
option(PDFENGINE_FUZZING "Build libFuzzer harnesses (requires Clang)" OFF)
if(PDFENGINE_FUZZING)
# The fuzz harness instruments the engine; tests/bindings are not part of it.
set(PDFENGINE_BUILD_TESTS OFF CACHE BOOL "Build engine unit/smoke tests" FORCE)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR "PDFENGINE_FUZZING requires a Clang/clang-cl toolchain "
"(set in the 'fuzz-*' preset).")
endif()
endif()
if(PDFENGINE_WASM)
set(PDFENGINE_BUILD_TESTS OFF CACHE BOOL "Build engine unit/smoke tests" FORCE)
set(PDFENGINE_ENABLE_SANITIZERS OFF CACHE BOOL "ASan/UBSan" FORCE)
# PDFium under WASM is opt-in via the wasm-pdfium preset (links the prebuilt wasm32
# libpdfium.a). The plain 'wasm' preset leaves PDFENGINE_WITH_PDFIUM=OFF (mock engine).
endif()
include(CompilerWarnings)
include(Sanitizers)
include(pdfium) # defines pdfium::pdfium when PDFENGINE_WITH_PDFIUM is ON
if(PDFENGINE_WITH_PDFIUM)
include(pdfium) # defines pdfium::pdfium when PDFENGINE_WITH_PDFIUM is ON
endif()
if(PDFENGINE_WITH_SKIA)
include(skia) # defines skia::skia when PDFENGINE_WITH_SKIA is ON
endif()
find_package(freetype CONFIG REQUIRED)
find_package(harfbuzz CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
if(PDFENGINE_WITH_QPDF)
find_package(qpdf CONFIG REQUIRED)
endif()
if(WIN32 AND DEFINED VCPKG_TARGET_TRIPLET)
link_directories("${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib")
link_directories("${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib")
endif()
if(PDFENGINE_BUILD_TESTS)
find_package(GTest CONFIG REQUIRED)
@@ -62,8 +87,22 @@ if(PDFENGINE_BUILD_TESTS)
include(GoogleTest)
endif()
add_subdirectory(engine)
if(PDFENGINE_WASM)
message(STATUS "PdfEngine ${PROJECT_VERSION} — WASM configuration (Phase 1/2 Enabled)")
add_subdirectory(engine)
add_subdirectory(wasm)
else()
# Only the top-level project drives testing/install defaults.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed. Use a preset: "
"cmake --preset <platform>-debug")
endif()
add_subdirectory(engine)
if(NOT PDFENGINE_FUZZING)
add_subdirectory(bindings)
endif()
endif()
message(STATUS "PdfEngine ${PROJECT_VERSION} configured")
message(STATUS " C++ standard ......... ${CMAKE_CXX_STANDARD}")
@@ -71,3 +110,4 @@ message(STATUS " Build tests .......... ${PDFENGINE_BUILD_TESTS}")
message(STATUS " Sanitizers ........... ${PDFENGINE_ENABLE_SANITIZERS}")
message(STATUS " Warnings as errors ... ${PDFENGINE_WARNINGS_AS_ERRORS}")
message(STATUS " Link PDFium .......... ${PDFENGINE_WITH_PDFIUM}")
message(STATUS " Link Skia ............ ${PDFENGINE_WITH_SKIA}")
+40 -2
View File
@@ -102,12 +102,47 @@
"displayName": "WASM • Emscripten hello-world (Phase 0, Rule R5)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"toolchainFile": "${sourceDir}/cmake/toolchains/wasm.cmake",
"toolchainFile": "${sourceDir}/cmake/toolchains/vcpkg-wasm.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"PDFENGINE_BUILD_TESTS": "OFF",
"PDFENGINE_WITH_PDFIUM": "OFF"
}
},
{
"name": "wasm-pdfium",
"displayName": "WASM • Emscripten + PDFium (live-preview engine)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"toolchainFile": "${sourceDir}/cmake/toolchains/vcpkg-wasm.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"PDFENGINE_BUILD_TESTS": "OFF",
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
},
{
"name": "fuzz-linux",
"displayName": "Linux • libFuzzer (Clang + ASan)",
"inherits": "base",
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" },
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"PDFENGINE_FUZZING": "ON",
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_FUZZ_SANITIZERS": "fuzzer,address,undefined"
}
},
{
"name": "fuzz-linux-nosan",
"displayName": "Linux • libFuzzer (Clang, coverage-only — for non-ASan PDFium)",
"inherits": "fuzz-linux",
"cacheVariables": { "PDFENGINE_FUZZ_SANITIZERS": "fuzzer" }
}
],
@@ -121,7 +156,10 @@
{ "name": "macos-debug", "configurePreset": "macos-debug" },
{ "name": "macos-release", "configurePreset": "macos-release" },
{ "name": "macos-asan", "configurePreset": "macos-asan" },
{ "name": "wasm", "configurePreset": "wasm" }
{ "name": "wasm", "configurePreset": "wasm" },
{ "name": "wasm-pdfium", "configurePreset": "wasm-pdfium" },
{ "name": "fuzz-linux", "configurePreset": "fuzz-linux" },
{ "name": "fuzz-linux-nosan", "configurePreset": "fuzz-linux-nosan" }
],
"testPresets": [
+34
View File
@@ -0,0 +1,34 @@
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
"configurePresets": [
{
"name": "win-local",
"displayName": "Windows • Debug (local — build dir outside OneDrive/spaces)",
"inherits": "windows-debug",
"binaryDir": "C:/Users/@USERNAME@/pdfeng-build/win-local",
"cacheVariables": {
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
},
{
"name": "win-local-pdfium",
"displayName": "Windows • RelWithDebInfo + PDFium (static CRT — build dir outside OneDrive/spaces)",
"inherits": "windows-release",
"binaryDir": "C:/Users/@USERNAME@/pdfeng-build/win-local-pdfium",
"cacheVariables": {
"PDFENGINE_WITH_PDFIUM": "ON",
"PDFENGINE_WITH_QPDF": "ON"
}
}
],
"buildPresets": [
{ "name": "win-local", "configurePreset": "win-local" },
{ "name": "win-local-pdfium", "configurePreset": "win-local-pdfium" }
],
"testPresets": [
{ "name": "win-local", "inherits": "common", "configurePreset": "win-local" },
{ "name": "win-local-pdfium", "inherits": "common", "configurePreset": "win-local-pdfium" }
]
}
+1
View File
@@ -0,0 +1 @@
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

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

After

Width:  |  Height:  |  Size: 36 KiB

+41
View File
@@ -0,0 +1,41 @@
services:
gateway:
build:
context: .
dockerfile: gateway/Dockerfile
network: host
image: pdf-engine-gateway:dev
container_name: pdf-engine-gateway
environment:
PDFENGINE_ENVIRONMENT: dev
PDFENGINE_ENGINE_AVAILABLE: "true"
PORT: 8765
ports:
- "8765:8765"
volumes:
- ./gateway:/home/app
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8765/health').read()" ]
interval: 20s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: development
image: pdf-engine-frontend:dev
container_name: pdf-engine-frontend
environment:
VITE_GATEWAY_URL: http://localhost:8765
ports:
- "5173:5173"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- gateway
restart: unless-stopped
View File
-348
View File
@@ -1,348 +0,0 @@
# 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.
> **Verifying everything at once:** use `scripts/test_phase0.{ps1,sh}` to run
> the R2 boundary check, engine, gateway, and WASM smoke test as a single
> command and get one pass/fail summary. See
> ["Verifying your setup"](#verifying-your-setup--scriptstest_phase0ps1sh) below.
### 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.
## Verifying your setup — `scripts/test_phase0.{ps1,sh}`
After bootstrap, this aggregator runs every Phase 0 check in sequence and
prints one pass/fail summary. It is the local mirror of
[.github/workflows/ci.yml](../.github/workflows/ci.yml) — new teammates should
use it as the "did I set everything up right?" one-liner.
Pieces it runs, in order:
1. **Rule R2 boundary**`scripts/check_pdfium_boundary.*`
2. **Engine**`cmake --preset … --build --preset … ctest --preset …`
3. **Gateway**`pip install -e .[dev]` + ruff (lint + format check) + pytest
4. **WASM hello-world**`cmake --preset wasm` + `node wasm/hello.test.mjs`
A piece **skips** (not fails) when its toolchain is absent — no Emscripten on
`PATH` skips WASM, no `python` skips the gateway, etc. Each piece is
independent: one failure does not abort later pieces. Exit code is non-zero
only if a piece genuinely **failed**, so you can pipe it into CI.
### Windows
`vcvars64.bat` must be active in the shell (or use *Developer PowerShell for
VS*) and `VCPKG_ROOT` must be set:
```powershell
./scripts/test_phase0.ps1 -Preset win-local # debug, no PDFium - fastest
./scripts/test_phase0.ps1 -Preset win-local-pdfium # release + PDFium - full
```
Both presets live in your local `CMakeUserPresets.json` (template in the
"Windows + PDFium" section above), with `binaryDir` pointed outside OneDrive on
a space-free path.
Other useful flags: `-BinaryDir <path>` (override the preset's binaryDir),
`-SkipEngine` / `-SkipGateway` / `-SkipWasm` (skip a piece explicitly).
### Linux / macOS
```sh
./scripts/test_phase0.sh # auto-picks linux-debug or macos-debug
./scripts/test_phase0.sh linux-asan # different preset
SKIP_WASM=1 ./scripts/test_phase0.sh # if Emscripten not installed
PHASE0_BINARY_DIR=/tmp/build ./scripts/test_phase0.sh # override binaryDir
```
Compatible with bash 3.2 (macOS default), so no `brew install bash` needed.
### Gotcha: stale build dir after a triplet switch
If a build dir was previously configured against the dynamic
(`x64-windows`) vcpkg triplet and you re-run against the static
(`x64-windows-static`) one — which is what the `windows-base` preset now pins
for the static-CRT story — vcpkg correctly purges the old libs, but ninja's
build graph still references them. You will see:
```
ninja: error: 'vcpkg_installed/x64-windows/debug/lib/harfbuzz.lib' ... missing
```
Fix by regenerating from scratch:
```powershell
cmake --preset win-local --fresh # or whichever preset
./scripts/test_phase0.ps1 -Preset win-local
```
`--fresh` is the cleanest option (CMake ≥ 3.24); it preserves the build dir
but invalidates the cache so all targets are re-resolved. Alternatively, delete
the build dir and reconfigure.
## 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.
+111 -8
View File
@@ -1,19 +1,65 @@
# pdfengine — the C++23 PDF SDK core.
#
# 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
find_package(PNG REQUIRED)
add_library(pdfengine OBJECT
src/core/engine_info.cpp
src/core/graphics_state.cpp
src/core/display_list.cpp
src/core/path_interpreter.cpp
src/core/skia_renderer.cpp
src/parser/content_stream_parser.cpp
src/parser/decoration_builder.cpp
src/text/selection.cpp
src/text/text_layout_engine.cpp
src/fonts/face/font_face.cpp
src/fonts/face/free_type_manager.cpp
src/fonts/loader/font_resolver.cpp
src/fonts/shaping/hb_shaper.cpp
src/fonts/cache/glyph_bitmap.cpp
src/fonts/cache/glyph_cache.cpp
src/fonts/pdf_fonts/types/truetype_font.cpp
src/fonts/pdf_fonts/types/type1_font.cpp
src/fonts/pdf_fonts/types/cid_font.cpp
src/fonts/pdf_fonts/font_loader.cpp
src/fonts/pdf_fonts/font_descriptor.cpp
src/fonts/pdf_fonts/font_fallback.cpp
src/fonts/pdf_fonts/font_subset.cpp
src/fonts/pdf_fonts/font_cmap_builder.cpp
src/fonts/pdf_fonts/embedded_font_reconstructor.cpp
src/fonts/pdf_fonts/encoding/encoding.cpp
src/fonts/pdf_fonts/encoding/tounicode_parser.cpp
src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp
src/parser/pdfium_loader.cpp
src/fonts/font_face.cpp
src/fonts/hb_shaper.cpp
src/parser/pdfium_document.cpp
src/parser/pdfium_internal.cpp
src/parser/pdfium_reflow.cpp
src/parser/pdfium_page.cpp
src/parser/pdfium_page_model.cpp
src/parser/pdfium_fonts.cpp
src/parser/pdfium_edit_session.cpp
src/parser/pdfium_edit.cpp
src/parser/pdfium_edit_replace.cpp
src/parser/pdfium_edit_reflow.cpp
src/parser/pdfium_edit_annotations.cpp
src/parser/pdfium_edit_pages.cpp
src/parser/pdfium_edit_images.cpp
src/qpdf/qpdf_extractor.cpp
src/qpdf/qpdf_font_extractor.cpp
src/qpdf/qpdf_writer.cpp
src/qpdf/qpdf_resource_resolver.cpp
src/core/image_decoder.cpp
src/parser/lexer.cpp
src/parser/parser.cpp
src/parser/content_builder.cpp
src/serializer/content_serializer.cpp
src/serializer/ast_serializer.cpp
)
add_library(pdfengine::pdfengine ALIAS pdfengine)
set_target_properties(pdfengine PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(pdfengine
PUBLIC
@@ -29,11 +75,41 @@ target_link_libraries(pdfengine
PRIVATE
freetype
harfbuzz::harfbuzz
harfbuzz::harfbuzz-subset
PNG::PNG
nlohmann_json::nlohmann_json
)
if(PDFENGINE_WITH_PDFIUM)
target_link_libraries(pdfengine PRIVATE pdfium::pdfium)
target_compile_definitions(pdfengine PRIVATE PDFENGINE_WITH_PDFIUM)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_PDFIUM)
# PDFium statically bundles its own libjpeg, zlib, etc. which conflicts with vcpkg.
# We use LLD, so we can safely allow multiple definitions to pick the first one.
target_link_options(pdfengine PUBLIC "-Wl,--allow-multiple-definition")
endif()
if(EMSCRIPTEN)
target_compile_definitions(pdfengine PRIVATE PDFENGINE_FONT_DIR="/fonts")
else()
target_compile_definitions(pdfengine PRIVATE PDFENGINE_FONT_DIR="${CMAKE_SOURCE_DIR}/engine/assets/fonts")
endif()
if(PDFENGINE_WITH_SKIA)
target_link_libraries(pdfengine PRIVATE skia::skia)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_SKIA)
endif()
if(PDFENGINE_WITH_QPDF)
find_package(ZLIB REQUIRED)
find_package(JPEG REQUIRED)
if(NOT TARGET zs)
add_library(zs ALIAS ZLIB::ZLIB)
endif()
if(NOT TARGET jpeg)
add_library(jpeg ALIAS JPEG::JPEG)
endif()
target_link_libraries(pdfengine PRIVATE qpdf::libqpdf ZLIB::ZLIB JPEG::JPEG)
target_compile_definitions(pdfengine PUBLIC PDFENGINE_WITH_QPDF)
endif()
pdfengine_set_warnings(pdfengine)
@@ -42,3 +118,30 @@ pdfengine_enable_sanitizers(pdfengine)
if(PDFENGINE_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(PDFENGINE_FUZZING)
set(PDFENGINE_FUZZ_SANITIZERS "fuzzer,address,undefined" CACHE STRING
"Sanitizer set for fuzzing (e.g. 'fuzzer,address,undefined' or just 'fuzzer')")
string(REPLACE "," ";" _fuzz_sans "${PDFENGINE_FUZZ_SANITIZERS}")
set(_fuzz_runtime_sans "")
foreach(_s IN LISTS _fuzz_sans)
if(NOT _s STREQUAL "fuzzer")
list(APPEND _fuzz_runtime_sans "${_s}")
endif()
endforeach()
list(JOIN _fuzz_runtime_sans "," _fuzz_runtime_str)
target_compile_options(pdfengine PRIVATE -fsanitize=fuzzer-no-link -fno-omit-frame-pointer)
if(_fuzz_runtime_str)
target_compile_options(pdfengine PRIVATE -fsanitize=${_fuzz_runtime_str})
target_link_options(pdfengine PUBLIC -fsanitize=${_fuzz_runtime_str})
endif()
add_executable(pdfengine_fuzz fuzz/fuzz_load.cpp)
target_link_libraries(pdfengine_fuzz PRIVATE pdfengine)
target_compile_options(pdfengine_fuzz PRIVATE
-fsanitize=${PDFENGINE_FUZZ_SANITIZERS} -fno-omit-frame-pointer)
target_link_options(pdfengine_fuzz PRIVATE
-fsanitize=${PDFENGINE_FUZZ_SANITIZERS})
endif()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+65
View File
@@ -0,0 +1,65 @@
# Fuzzing the PDF engine
`fuzz_load.cpp` is a libFuzzer harness that drives the full
**load → metadata → outline → render → text → annotations → hit-test → select**
path with arbitrary bytes. Combined with AddressSanitizer it surfaces crashes,
OOMs, and undefined behaviour in the parsing and rendering code.
Resource ceilings from `pdfengine/hardened_limits.h` keep the fuzzer focused on
logic bugs instead of trivial out-of-memory inputs (and those same ceilings now
guard the production render path against integer-overflow / OOM).
## Linux (primary)
Clang + libFuzzer + ASan is best supported on Linux. PDFium must be built with
the same Clang toolchain (so ASan is consistent across the static lib).
```bash
# Full ASan + coverage fuzzer
cmake --preset fuzz-linux
cmake --build --preset fuzz-linux
# If your PDFium static lib is NOT ASan-instrumented, use coverage-only:
cmake --preset fuzz-linux-nosan
cmake --build --preset fuzz-linux-nosan
# Run it against the downloaded corpus as a seed set
python scripts/fetch_corpus.py # populates corpus/fuzz/ (gitignored)
mkdir -p engine/fuzz/artifacts
./out/build/fuzz-linux/bin/pdfengine_fuzz \
-artifact_prefix=engine/fuzz/artifacts/ \
corpus/fuzz/ corpus/
```
`corpus/fuzz/` and `corpus/` are passed as seed corpora; new coverage-expanding
inputs are written back into the first directory. Crashes land in
`engine/fuzz/artifacts/` (gitignored).
## Windows (clang-cl)
Native Windows fuzzing needs a Clang toolchain *and* a PDFium static lib built
with the matching runtime. Configure with clang-cl and the existing
`x64-windows-static` triplet, then enable fuzzing:
```powershell
cmake -S . -B C:/Users/<you>/pdfeng-build/fuzz-win -G Ninja `
-DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl `
-DVCPKG_TARGET_TRIPLET=x64-windows-static `
-DPDFENGINE_FUZZING=ON -DPDFENGINE_WITH_PDFIUM=ON `
-DPDFENGINE_FUZZ_SANITIZERS=fuzzer `
--toolchain "$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake --build C:/Users/<you>/pdfeng-build/fuzz-win
```
Use `PDFENGINE_FUZZ_SANITIZERS=fuzzer` (coverage-only) on Windows unless the
whole dependency chain — including PDFium — is ASan-built, since mixing an
ASan binary with a non-ASan MSVC static lib does not link cleanly.
## Reproducing a crash
```bash
./pdfengine_fuzz engine/fuzz/artifacts/crash-<hash>
```
The ASan report points at the offending allocation/access; the input file is the
minimal reproducer (run with `-minimize_crash=1` to shrink further).
+43
View File
@@ -0,0 +1,43 @@
#include "pdfengine/hardened_limits.h"
#include "pdfengine/pdf_document.hpp"
#include <cstddef>
#include <cstdint>
#include <vector>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
using namespace pdfengine;
if (!limits::documentSizeOk(size)) return 0;
std::vector<uint8_t> bytes(data, data + size);
auto doc = PdfDocument::loadFromMemory(bytes, "");
if (!doc) return 0;
PdfDocument& d = **doc;
const int pages = d.pageCount();
if (!limits::pageCountOk(pages)) return 0;
(void)d.metadata();
(void)d.extractOutline();
const int limit = pages < 3 ? pages : 3;
for (int i = 0; i < limit; ++i) {
auto page = d.getPage(i);
if (!page) continue;
PdfPage& p = **page;
(void)p.render(72);
(void)p.extractText();
(void)p.extractAnnotations();
auto glyphs = p.orderedGlyphs();
if (glyphs && !glyphs->empty()) {
const auto& g = glyphs->front();
(void)p.hitGlyph(g.x, g.y);
(void)p.selectRange(g.x, g.y, g.x + 50.0, g.y + 20.0);
}
}
return 0;
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <memory>
namespace pdfengine {
enum class AstNodeType {
Number,
Name,
String,
HexString,
Boolean,
Null,
Array,
Dictionary
};
class AstNode {
public:
AstNodeType type;
std::string stringValue;
std::vector<uint8_t> bytesValue;
double numberValue = 0.0;
bool boolValue = false;
std::vector<std::shared_ptr<AstNode>> arrayItems;
std::unordered_map<std::string, std::shared_ptr<AstNode>> dictItems;
AstNode() = default;
explicit AstNode(AstNodeType t) : type(t) {}
};
struct Operation {
std::string op;
std::vector<std::shared_ptr<AstNode>> operands;
};
}
@@ -0,0 +1,75 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <pdfengine/graphics_state.hpp>
#include <pdfengine/path.hpp>
namespace pdfengine {
enum class ContentObjectType {
Text,
Path,
Image,
Unknown
};
enum class XObjectType {
Image,
Form,
Pattern,
Unknown
};
class ContentObject {
public:
virtual ~ContentObject() = default;
virtual ContentObjectType getType() const = 0;
};
class TextObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Text; }
std::string text;
std::string fontName;
double fontSize = 0.0;
double tm[6] = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
};
class ImageObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Image; }
std::string name;
int width = 0;
int height = 0;
std::string colorSpace;
std::string filter;
int bitsPerComponent = 8;
bool hasSoftMask = false;
std::vector<uint8_t> pixelData;
Matrix transform;
};
enum class PathPaintOp {
Stroke,
Fill,
FillStroke
};
class PathObject : public ContentObject {
public:
ContentObjectType getType() const override { return ContentObjectType::Path; }
Path path;
PathPaintOp paintOp = PathPaintOp::Stroke;
FillRule fillRule = FillRule::NonZero;
Matrix transform;
};
}
@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
namespace pdfengine {
struct ExtractedStream {
std::string rawContent;
std::string decodedContent;
int pageIndex = 0;
std::vector<std::string> filters;
bool compressed = false;
bool multiStream = false;
};
struct StreamVerification {
bool hasBT = false;
bool hasET = false;
bool hasTf = false;
bool hasTj = false;
bool multiStream = false;
};
StreamVerification verifyContentStream(const ExtractedStream& stream);
}
+118
View File
@@ -0,0 +1,118 @@
#pragma once
#include <vector>
#include <memory>
#include <string>
#include <pdfengine/graphics_state.hpp>
#include <pdfengine/path.hpp>
#include <pdfengine/image.hpp>
namespace pdfengine {
class CommandVisitor;
struct Command {
virtual ~Command() = default;
virtual void accept(CommandVisitor& visitor) const = 0;
};
struct SaveStateCommand : public Command {
void accept(CommandVisitor& visitor) const override;
};
struct RestoreStateCommand : public Command {
void accept(CommandVisitor& visitor) const override;
};
struct SetTransformCommand : public Command {
Matrix matrix;
explicit SetTransformCommand(const Matrix& m) : matrix(m) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillRectCommand : public Command {
float x, y, width, height;
FillRectCommand(float _x, float _y, float w, float h) : x(_x), y(_y), width(w), height(h) {}
void accept(CommandVisitor& visitor) const override;
};
struct DrawTextCommand : public Command {
std::string text;
float x, y;
DrawTextCommand(std::string _text, float _x, float _y) : text(std::move(_text)), x(_x), y(_y) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillPathCommand : public Command {
Path path;
FillRule rule;
explicit FillPathCommand(Path _path, FillRule _rule = FillRule::NonZero) : path(std::move(_path)), rule(_rule) {}
void accept(CommandVisitor& visitor) const override;
};
struct StrokePathCommand : public Command {
Path path;
explicit StrokePathCommand(Path _path) : path(std::move(_path)) {}
void accept(CommandVisitor& visitor) const override;
};
struct FillStrokePathCommand : public Command {
Path path;
FillRule rule;
explicit FillStrokePathCommand(Path _path, FillRule _rule = FillRule::NonZero) : path(std::move(_path)), rule(_rule) {}
void accept(CommandVisitor& visitor) const override;
};
struct DrawImageCommand : public Command {
ImageInfo image;
Matrix matrix;
float opacity;
DrawImageCommand(ImageInfo img, Matrix m, float op = 1.0f)
: image(std::move(img)), matrix(m), opacity(op) {}
void accept(CommandVisitor& visitor) const override;
};
class CommandVisitor {
public:
virtual ~CommandVisitor() = default;
virtual void visit(const SaveStateCommand& cmd) = 0;
virtual void visit(const RestoreStateCommand& cmd) = 0;
virtual void visit(const SetTransformCommand& cmd) = 0;
virtual void visit(const FillRectCommand& cmd) = 0;
virtual void visit(const DrawTextCommand& cmd) = 0;
virtual void visit(const FillPathCommand& cmd) = 0;
virtual void visit(const StrokePathCommand& cmd) = 0;
virtual void visit(const FillStrokePathCommand& cmd) = 0;
virtual void visit(const DrawImageCommand& cmd) = 0;
};
class DisplayList {
public:
DisplayList() = default;
void addCommand(std::unique_ptr<Command> cmd);
void replay(CommandVisitor& visitor) const;
void saveState();
void restoreState();
void setTransform(const Matrix& m);
void fillRect(float x, float y, float w, float h);
void drawText(const std::string& text, float x, float y);
void fillPath(const Path& path, FillRule rule = FillRule::NonZero);
void strokePath(const Path& path);
void fillStrokePath(const Path& path, FillRule rule = FillRule::NonZero);
void drawImage(const ImageInfo& image, const Matrix& m, float opacity = 1.0f);
[[nodiscard]] size_t size() const noexcept { return m_commands.size(); }
void clear() { m_commands.clear(); }
private:
std::vector<std::unique_ptr<Command>> m_commands;
};
}
+93
View File
@@ -0,0 +1,93 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <expected>
#include <cstdint>
#include "pdfengine/pdf_document.hpp"
namespace pdfengine {
struct Rect {
double x = 0.0;
double y = 0.0;
double width = 0.0;
double height = 0.0;
};
using RectList = std::vector<Rect>;
struct ParagraphBounds {
Rect rect;
};
struct GlyphInfo {
uint32_t glyphId = 0;
uint32_t cluster = 0;
double x = 0.0;
double y = 0.0;
double advance = 0.0;
double width = 0.0;
double ascent = 0.0;
double descent = 0.0;
std::string text;
};
struct LineInfo {
int id = 0;
Rect rect;
double baselineY = 0.0;
};
struct CaretState {
int offset = 0;
Rect rect;
};
// Internal comprehensive layout state
struct LayoutResult {
ParagraphBounds bounds;
std::vector<LineInfo> lines;
std::vector<GlyphInfo> glyphs;
std::vector<Rect> selectionRects;
CaretState caret;
RectList dirtyRects;
};
// Stable, lightweight view for WASM export
struct LayoutView {
std::vector<LineInfo> lines;
std::vector<GlyphInfo> glyphs;
CaretState caret;
RectList dirtyRects;
};
class EditSession {
public:
virtual ~EditSession() = default;
static std::shared_ptr<EditSession> StartEditSession(
std::shared_ptr<PdfDocument> doc,
int pageIndex,
const std::string& paraId
);
// Returns a stable, lightweight view of the layout
virtual LayoutView GetLayoutView() const = 0;
// Geometry queries against the cached layout
// offset represents the caret insertion point (between glyphs)
virtual int HitTest(double x, double y) const = 0;
virtual Rect GetCaretRect(int offset) const = 0;
virtual std::vector<Rect> GetSelectionRects(int startOffset, int endOffset) const = 0;
// Mutates paragraph, marks cache dirty, recalculates
virtual void ApplyEdit(const std::string& editOpJson) = 0;
// Rendering & Lifecycle
virtual std::vector<uint8_t> RenderDirtyRegion(int dpi, const Rect& region) const = 0;
virtual bool CommitEdit() = 0;
virtual void CancelEdit() = 0;
};
} // namespace pdfengine
@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <stdexcept>
namespace pdfengine {
struct Matrix {
float a = 1.0f, b = 0.0f;
float c = 0.0f, d = 1.0f;
float e = 0.0f, f = 0.0f;
Matrix() = default;
Matrix(float _a, float _b, float _c, float _d, float _e, float _f)
: a(_a), b(_b), c(_c), d(_d), e(_e), f(_f) {}
[[nodiscard]] Matrix multiply(const Matrix& other) const noexcept;
void transform(float& x, float& y) const noexcept;
};
struct Color {
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
};
struct GraphicsState {
Matrix ctm;
Color fillColor;
Color strokeColor;
float lineWidth = 1.0f;
};
class GraphicsStateStack {
public:
GraphicsStateStack();
void push();
void pop();
[[nodiscard]] GraphicsState& current();
[[nodiscard]] const GraphicsState& current() const;
private:
std::vector<GraphicsState> m_stack;
};
}
@@ -0,0 +1,43 @@
#ifndef PDFENGINE_HARDENED_LIMITS_H
#define PDFENGINE_HARDENED_LIMITS_H
#include <cstdint>
namespace pdfengine::limits {
inline constexpr std::uint64_t kMaxDocumentBytes = 1ull << 30;
inline constexpr double kMaxPageDimensionPt = 200'000.0;
inline constexpr int kMaxPageCount = 100'000;
inline constexpr int kMaxObjects = 5'000'000;
inline constexpr std::int64_t kMaxRasterPixels = 256ll * 1024 * 1024;
inline constexpr bool pageDimensionsOk(double widthPt, double heightPt) noexcept {
return widthPt > 0.0 && heightPt > 0.0 && widthPt <= kMaxPageDimensionPt &&
heightPt <= kMaxPageDimensionPt;
}
inline constexpr bool rasterSizeOk(std::int64_t widthPx, std::int64_t heightPx) noexcept {
if (widthPx <= 0 || heightPx <= 0) return false;
if (widthPx > kMaxRasterPixels || heightPx > kMaxRasterPixels) return false;
return widthPx * heightPx <= kMaxRasterPixels;
}
inline constexpr bool documentSizeOk(std::uint64_t bytes) noexcept {
return bytes > 0 && bytes <= kMaxDocumentBytes;
}
inline constexpr bool pageCountOk(int pages) noexcept {
return pages >= 0 && pages <= kMaxPageCount;
}
inline constexpr bool objectCountOk(int objects) noexcept {
return objects >= 0 && objects <= kMaxObjects;
}
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include <cstdint>
namespace pdfengine {
enum class ColorSpace {
DeviceGray,
DeviceRGB,
DeviceCMYK,
Indexed
};
struct ImageInfo {
int width = 0;
int height = 0;
int channels = 4;
std::vector<uint8_t> pixelData;
};
}
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include <vector>
namespace pdfengine {
enum class FillRule {
NonZero,
EvenOdd
};
// Basic point structure
struct Point {
float x = 0.0f;
float y = 0.0f;
};
class Path {
public:
enum class Verb {
MoveTo,
LineTo,
CubicBezierTo,
Close
};
struct Segment {
Verb verb;
Point points[3];
};
Path() = default;
void moveTo(float x, float y) {
m_segments.push_back({Verb::MoveTo, {{x, y}, {}, {}}});
}
void lineTo(float x, float y) {
m_segments.push_back({Verb::LineTo, {{x, y}, {}, {}}});
}
void cubicTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) {
m_segments.push_back({Verb::CubicBezierTo, {{cp1x, cp1y}, {cp2x, cp2y}, {x, y}}});
}
void close() {
m_segments.push_back({Verb::Close, {{}, {}, {}}});
}
void addRect(float x, float y, float w, float h) {
moveTo(x, y);
lineTo(x + w, y);
lineTo(x + w, y + h);
lineTo(x, y + h);
close();
}
void clear() {
m_segments.clear();
}
[[nodiscard]] const std::vector<Segment>& segments() const { return m_segments; }
[[nodiscard]] bool empty() const { return m_segments.empty(); }
private:
std::vector<Segment> m_segments;
};
}
@@ -0,0 +1,14 @@
#pragma once
#include <pdfengine/content_object.hpp>
#include <pdfengine/display_list.hpp>
namespace pdfengine {
// PathObjectInterpreter converts a PathObject into DisplayList commands
class PathObjectInterpreter {
public:
static void interpret(const PathObject& pathObj, DisplayList& displayList);
};
} // namespace pdfengine
+297 -20
View File
@@ -1,26 +1,303 @@
// pdfengine — document API.
//
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ PLACEHOLDER. This header is the deliverable of Phase 0 Gate G0b: │
// │ "Frozen interface contracts" — the PdfDocument / PdfPage API structs │
// │ reviewed and locked by all three developers before Phase 1 coding. │
// │ │
// │ Do NOT fill in method signatures yet. Nothing proceeds until the │
// │ contract is signed off (see docs/phase0.md, Gate G0b). │
// │ │
// │ Rule R2: only engine/src/parser/ may touch raw FPDF_* PDFium APIs. │
// │ Everything else in the codebase goes through these types. │
// └─────────────────────────────────────────────────────────────────────────┘
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <cstdint>
#include <array>
#if __has_include(<expected>)
#include <expected>
#elif __has_include(<tl/expected.hpp>)
#include <tl/expected.hpp>
namespace std {
using tl::expected;
using tl::unexpected;
}
#endif
namespace pdfengine {
// Opaque, owning handle to a parsed PDF document.
// Full definition + API land at Gate G0b.
class PdfDocument;
enum class EngineError {
FileNotFound,
InvalidFormat,
PasswordRequired,
InvalidPassword,
PageOutOfBounds,
RenderFailed,
WriteFailed,
Unknown
};
// Opaque view onto a single page of a PdfDocument.
// Full definition + API land at Gate G0b.
class PdfPage;
struct DocumentMetadata {
std::string title;
std::string author;
std::string creator;
std::string producer;
std::string creationDate;
std::string modificationDate;
};
} // namespace pdfengine
struct DocumentPermissions {
bool isEncrypted = false;
std::string encryption = "None";
int securityRevision = -1;
bool ownerUnlocked = false;
bool canPrint = true;
bool canPrintHighRes = true;
bool canModify = true;
bool canCopy = true;
bool canAnnotate = true;
bool canFillForms = true;
bool canExtractForAccessibility = true;
bool canAssemble = true;
};
struct PageImage {
int width;
int height;
std::vector<uint8_t> data;
};
struct Point2D {
double x;
double y;
};
struct DevicePoint {
int x;
int y;
};
struct InvalidatedRegion {
int pageIndex;
double x;
double y;
double width;
double height;
};
struct GlyphBounds {
std::string text;
double x;
double y;
double w;
double h;
double fontSize;
};
struct HitResult {
int glyphIndex = -1;
int caret = 0;
int line = -1;
};
struct TextSelection {
int startGlyph = 0;
int endGlyph = 0;
std::string text;
std::vector<GlyphBounds> rects;
};
struct FontInfo {
std::string fontName;
std::string type;
bool isEmbedded = false;
bool isSubset = false;
bool isVertical = false;
std::string encoding;
bool hasToUnicode = false;
std::string cmapName;
std::string cidSystemInfo;
std::string subsetTag;
std::string sourceType;
std::string substitutedFrom;
std::string substitutedTo;
std::string normalizedFamily;
std::string internalFontId;
uint32_t flags = 0;
double ascent = 0.0;
double descent = 0.0;
double capHeight = 0.0;
};
struct Glyph {
std::string text;
uint32_t unicode = 0;
std::string fontName;
uint32_t flags = 0;
double fontSize = 0.0;
double originX = 0.0;
double originY = 0.0;
double bboxX = 0.0, bboxY = 0.0, bboxW = 0.0, bboxH = 0.0;
double angle = 0.0;
int pageObjectIndex = -1;
int srcIndex = 0;
};
struct TextRun {
std::string text;
std::string fontName;
uint32_t flags = 0;
double fontSize = 0.0;
std::string internalFontId;
bool isEmbedded = false;
std::string type;
std::vector<Glyph> glyphs;
std::vector<int> objectIndices;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
std::string fillColor = "#000000";
std::string paraId;
std::string fontFidelity = "exact";
};
struct TextLine {
std::vector<TextRun> runs;
std::vector<Glyph> glyphs;
double angle = 0.0;
double baselineY = 0.0;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
};
struct Paragraph {
std::vector<TextLine> lines;
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
};
struct PageModel {
std::vector<Paragraph> paragraphs;
double width = 0.0;
double height = 0.0;
int pageIndex = 0;
};
class PdfPage {
public:
virtual ~PdfPage() = default;
[[nodiscard]] virtual double width() const noexcept = 0;
[[nodiscard]] virtual double height() const noexcept = 0;
[[nodiscard]] virtual std::expected<PageImage, EngineError> render(int dpi = 96) const = 0;
[[nodiscard]] virtual std::expected<PageImage, EngineError>
renderRegionRaw(int dpi, double yTopPt, double heightPt) const {
(void)dpi; (void)yTopPt; (void)heightPt;
return std::unexpected(EngineError::Unknown);
}
[[nodiscard]] virtual std::expected<PageImage, EngineError>
renderTile(int dpi, double xPt, double yPt, double wPt, double hPt) const {
(void)dpi; (void)xPt; (void)yPt; (void)wPt; (void)hPt;
return std::unexpected(EngineError::Unknown);
}
[[nodiscard]] virtual std::expected<std::string, EngineError> extractText() const = 0;
[[nodiscard]] virtual std::expected<std::vector<GlyphBounds>, EngineError> extractTextWithBounds() const = 0;
[[nodiscard]] std::expected<std::vector<GlyphBounds>, EngineError> orderedGlyphs() const;
[[nodiscard]] std::expected<HitResult, EngineError> hitGlyph(double x, double y) const;
[[nodiscard]] std::expected<TextSelection, EngineError>
selectRange(double ax, double ay, double bx, double by) const;
[[nodiscard]] virtual std::expected<PageModel, EngineError> extractDocumentModel() const = 0;
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError> getFonts() const = 0;
[[nodiscard]] virtual std::expected<std::vector<std::string>, EngineError> extractAnnotationsText() const = 0;
struct AnnotationInfo {
std::string id;
std::string type;
double x = 0.0, y = 0.0, width = 0.0, height = 0.0;
std::string color;
std::string author;
std::string content;
std::string timestamp;
int pageIndex = 0;
double thickness = 0.0;
std::vector<std::vector<Point2D>> paths;
std::vector<std::array<Point2D, 4>> quadPoints;
std::string fieldName;
std::string fieldValue;
std::string fieldType;
int fieldFlags = 0;
std::vector<std::string> fieldOptions;
};
[[nodiscard]] virtual std::expected<std::vector<AnnotationInfo>, EngineError> extractAnnotations() const = 0;
[[nodiscard]] virtual std::expected<double, EngineError> getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const = 0;
[[nodiscard]] virtual DevicePoint pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
[[nodiscard]] virtual Point2D deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
[[nodiscard]] virtual std::expected<std::string, EngineError> extractDisplayListJson() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError> extractImageXObject(const std::string& name) const = 0;
};
namespace fonts::pdf_fonts { class Font; }
class PdfDocument {
public:
virtual ~PdfDocument() = default;
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
loadFromFile(const std::string& path, const std::string& password = "");
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
loadFromMemory(const std::vector<uint8_t>& data, const std::string& password = "");
[[nodiscard]] virtual int pageCount() const noexcept = 0;
[[nodiscard]] virtual DocumentMetadata metadata() const noexcept = 0;
[[nodiscard]] virtual DocumentPermissions permissions() const noexcept = 0;
struct OutlineItem {
std::string title;
int pageIndex = -1;
int level = 0;
};
[[nodiscard]] virtual std::expected<std::vector<OutlineItem>, EngineError> extractOutline() const = 0;
[[nodiscard]] virtual std::expected<std::shared_ptr<PdfPage>, EngineError>
getPage(int pageIndex) = 0;
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError>
getFonts(int startPage = 0, int endPage = -1) const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
getFontData(const std::string& internalFontId) const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
getReconstructedFontData(const std::string& internalFontId) { (void)internalFontId; return std::unexpected(EngineError::Unknown); }
virtual void registerAuxFont(const std::string& internalFontId, const std::vector<uint8_t>& sfnt) { (void)internalFontId; (void)sfnt; }
[[nodiscard]] virtual std::expected<std::shared_ptr<fonts::pdf_fonts::Font>, std::string>
getResolvedFont(const FontInfo& fontInfo) = 0;
virtual std::expected<std::vector<InvalidatedRegion>, EngineError> applyEdits(const std::string& editsJson) = 0;
[[nodiscard]] virtual std::string validateLayout(int pageIndex, const std::string& jsonStr) { (void)pageIndex; (void)jsonStr; return "{}"; }
[[nodiscard]] virtual std::string lastReflowLayout() const { return {}; }
[[nodiscard]] virtual bool lastReflowOverflowed() const { return false; }
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveIncremental() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveFull() const = 0;
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
saveFullForExport() const { return saveFull(); }
};
}
+3 -10
View File
@@ -1,8 +1,3 @@
// 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>
@@ -10,16 +5,14 @@
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.
[[nodiscard]] bool engineHasSkia() noexcept;
void engineLogBuildInfo();
} // namespace pdfengine
}
@@ -0,0 +1,30 @@
#pragma once
#include <pdfengine/display_list.hpp>
#include <pdfengine/graphics_state.hpp>
class SkCanvas;
namespace pdfengine {
class SkiaRenderer : public CommandVisitor {
public:
explicit SkiaRenderer(SkCanvas* canvas);
void visit(const SaveStateCommand& cmd) override;
void visit(const RestoreStateCommand& cmd) override;
void visit(const SetTransformCommand& cmd) override;
void visit(const FillRectCommand& cmd) override;
void visit(const DrawTextCommand& cmd) override;
void visit(const FillPathCommand& cmd) override;
void visit(const StrokePathCommand& cmd) override;
void visit(const FillStrokePathCommand& cmd) override;
void visit(const DrawImageCommand& cmd) override;
void render(const DisplayList& displayList);
private:
SkCanvas* m_canvas;
GraphicsStateStack m_stateStack;
};
}
@@ -0,0 +1,66 @@
#pragma once
#include "pdfengine/edit_session.hpp"
#include "fonts/shaping/hb_shaper.hpp"
#include "fonts/face/font_face.hpp"
#include <string>
#include <vector>
#include <memory>
namespace pdfengine::text {
struct LayoutConstraints {
double columnLeft = 0.0;
double columnRight = 0.0;
double firstBaselineY = 0.0;
double leading = 0.0;
std::string align = "left";
double hangingIndent = 0.0;
};
class LineBreaker {
public:
LineBreaker(const LayoutConstraints& constraints);
// Processes a stream of shaped glyphs and applies line breaking
void ProcessRun(
const std::vector<fonts::ShapedGlyph>& shapedGlyphs,
const std::string& runText,
double fontSize,
const fonts::FontFace& face,
double scale
);
// Finalizes the layout and populates the LayoutResult
void Finalize(LayoutResult& outLayout);
private:
LayoutConstraints constraints_;
double currentX_;
double currentY_;
// Internal state for word wrapping
std::vector<GlyphInfo> currentLineGlyphs_;
std::vector<LineInfo> finishedLines_;
std::vector<GlyphInfo> allGlyphs_;
void CommitLine();
};
class TextLayoutEngine {
public:
TextLayoutEngine();
// Computes layout for a given text run (simplified for single font for now)
LayoutResult ComputeLayout(
const std::string& text,
const LayoutConstraints& constraints,
fonts::FontFace& fontFace,
double fontSize
);
private:
fonts::HbShaper shaper_;
};
} // namespace pdfengine::text
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
namespace pdfengine {
enum class TokenType {
Operator,
String,
HexString,
Name,
Number,
ArrayStart,
ArrayEnd,
DictStart,
DictEnd,
Boolean,
Null,
EndOfStream
};
struct Token {
TokenType type;
std::string stringValue;
std::vector<uint8_t> bytesValue;
double numberValue = 0.0;
size_t startOffset = 0;
size_t endOffset = 0;
};
}
+63
View File
@@ -0,0 +1,63 @@
#include <pdfengine/display_list.hpp>
namespace pdfengine {
void SaveStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void RestoreStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void SetTransformCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillRectCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DrawTextCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillPathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void StrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void FillStrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DrawImageCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
void DisplayList::addCommand(std::unique_ptr<Command> cmd) {
if (cmd) {
m_commands.push_back(std::move(cmd));
}
}
void DisplayList::replay(CommandVisitor& visitor) const {
for (const auto& cmd : m_commands) {
cmd->accept(visitor);
}
}
void DisplayList::saveState() {
addCommand(std::make_unique<SaveStateCommand>());
}
void DisplayList::restoreState() {
addCommand(std::make_unique<RestoreStateCommand>());
}
void DisplayList::setTransform(const Matrix& m) {
addCommand(std::make_unique<SetTransformCommand>(m));
}
void DisplayList::fillRect(float x, float y, float w, float h) {
addCommand(std::make_unique<FillRectCommand>(x, y, w, h));
}
void DisplayList::drawText(const std::string& text, float x, float y) {
addCommand(std::make_unique<DrawTextCommand>(text, x, y));
}
void DisplayList::fillPath(const Path& path, FillRule rule) {
addCommand(std::make_unique<FillPathCommand>(path, rule));
}
void DisplayList::strokePath(const Path& path) {
addCommand(std::make_unique<StrokePathCommand>(path));
}
void DisplayList::fillStrokePath(const Path& path, FillRule rule) {
addCommand(std::make_unique<FillStrokePathCommand>(path, rule));
}
void DisplayList::drawImage(const ImageInfo& image, const Matrix& m, float opacity) {
addCommand(std::make_unique<DrawImageCommand>(image, m, opacity));
}
}
+10 -1
View File
@@ -14,11 +14,20 @@ bool engineHasPdfium() noexcept {
return parser::pdfiumAvailable();
}
bool engineHasSkia() noexcept {
#ifdef PDFENGINE_WITH_SKIA
return true;
#else
return false;
#endif
}
std::string_view engineBuildInfo() noexcept {
static const std::string info = [] {
std::string s = "pdfengine ";
s += version_string;
s += parser::pdfiumAvailable() ? " (pdfium=on)" : " (pdfium=off)";
s += engineHasSkia() ? " (skia=on)" : " (skia=off)";
return s;
}();
return info;
@@ -28,4 +37,4 @@ void engineLogBuildInfo() {
spdlog::info("{}", engineBuildInfo());
}
} // namespace pdfengine
}
+50
View File
@@ -0,0 +1,50 @@
#include <pdfengine/graphics_state.hpp>
namespace pdfengine {
Matrix Matrix::multiply(const Matrix& other) const noexcept {
return Matrix(
a * other.a + b * other.c,
a * other.b + b * other.d,
c * other.a + d * other.c,
c * other.b + d * other.d,
e * other.a + f * other.c + other.e,
e * other.b + f * other.d + other.f
);
}
void Matrix::transform(float& x, float& y) const noexcept {
float newX = a * x + c * y + e;
float newY = b * x + d * y + f;
x = newX;
y = newY;
}
GraphicsStateStack::GraphicsStateStack() {
m_stack.emplace_back();
}
void GraphicsStateStack::push() {
if (!m_stack.empty()) {
m_stack.push_back(m_stack.back());
} else {
m_stack.emplace_back();
}
}
void GraphicsStateStack::pop() {
if (m_stack.size() > 1) {
m_stack.pop_back();
} else {
}
}
GraphicsState& GraphicsStateStack::current() {
return m_stack.back();
}
const GraphicsState& GraphicsStateStack::current() const {
return m_stack.back();
}
}
+324
View File
@@ -0,0 +1,324 @@
#include "image_decoder.hpp"
#include <algorithm>
#include <csetjmp>
#include <cstdio>
#include <cmath>
#include <iostream>
#include <jpeglib.h>
namespace {
std::string nameValue(QPDFObjectHandle dict, const char* key) {
if (!dict.isDictionary() || !dict.hasKey(key)) {
return {};
}
auto value = dict.getKey(key);
if (value.isName()) {
return value.getName();
}
if (value.isArray() && value.getArrayNItems() > 0 && value.getArrayItem(0).isName()) {
return value.getArrayItem(0).getName();
}
return {};
}
int intValue(QPDFObjectHandle dict, const char* key, int fallback) {
if (!dict.isDictionary() || !dict.hasKey(key)) {
return fallback;
}
return static_cast<int>(dict.getKey(key).getNumericValue());
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4324)
#endif
struct JpegErrorManager {
jpeg_error_mgr pub;
std::jmp_buf jump;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
void jpegErrorExit(j_common_ptr cinfo) {
auto* manager = reinterpret_cast<JpegErrorManager*>(cinfo->err);
longjmp(manager->jump, 1);
}
}
namespace pdfengine {
std::vector<uint8_t> ImageDecoder::decode(QPDFObjectHandle imageStream,
const std::string& colorSpace,
int width,
int height,
int bitsPerComponent,
const std::string& filter) {
if (!imageStream.isStream()) {
return {};
}
const bool isJpeg = (filter == "/DCTDecode");
std::shared_ptr<Buffer> buffer;
if (isJpeg) {
buffer = imageStream.getRawStreamData();
} else {
buffer = imageStream.getStreamData();
}
if (!buffer) {
return {};
}
std::vector<unsigned char> rawBytes(buffer->getBuffer(), buffer->getBuffer() + buffer->getSize());
std::vector<uint8_t> rgba;
if (isJpeg) {
rgba = decodeJpeg(rawBytes);
} else {
if (bitsPerComponent != 8) {
std::cerr << "Warning: bitsPerComponent " << bitsPerComponent
<< " not fully supported yet for raw images.\n";
}
if (colorSpace == "/DeviceGray") {
rgba = convertGrayToRgba(rawBytes, width, height);
} else if (colorSpace == "/DeviceRGB") {
rgba = convertRgbToRgba(rawBytes, width, height);
} else if (colorSpace == "/DeviceCMYK") {
rgba = convertCmykToRgba(rawBytes, width, height);
} else {
std::cerr << "Warning: Unsupported color space " << colorSpace
<< ", falling back to RGB extraction.\n";
rgba = convertRgbToRgba(rawBytes, width, height);
}
}
applySoftMask(imageStream, rgba, width, height);
return rgba;
}
std::vector<uint8_t> ImageDecoder::decodeJpeg(const std::vector<unsigned char>& jpegBytes) {
if (jpegBytes.empty()) {
return {};
}
jpeg_decompress_struct cinfo{};
JpegErrorManager jerr{};
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4611)
#endif
if (setjmp(jerr.jump)) {
jpeg_destroy_decompress(&cinfo);
std::cerr << "Error: Failed to decode JPEG image stream\n";
return {};
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, const_cast<unsigned char*>(jpegBytes.data()),
static_cast<unsigned long>(jpegBytes.size()));
jpeg_read_header(&cinfo, TRUE);
const bool cmykSource = cinfo.jpeg_color_space == JCS_CMYK ||
cinfo.jpeg_color_space == JCS_YCCK;
cinfo.out_color_space = cmykSource ? JCS_CMYK : JCS_RGB;
jpeg_start_decompress(&cinfo);
const int width = static_cast<int>(cinfo.output_width);
const int height = static_cast<int>(cinfo.output_height);
const int components = static_cast<int>(cinfo.output_components);
const int rowStride = width * components;
std::vector<uint8_t> rgba(static_cast<size_t>(width) * height * 4);
std::vector<JSAMPLE> row(static_cast<size_t>(rowStride));
while (cinfo.output_scanline < cinfo.output_height) {
JSAMPROW rowPointer = row.data();
const int y = static_cast<int>(cinfo.output_scanline);
jpeg_read_scanlines(&cinfo, &rowPointer, 1);
for (int x = 0; x < width; ++x) {
const auto src = static_cast<size_t>(x) * components;
const auto dst = (static_cast<size_t>(y) * width + x) * 4;
if (components == 1) {
const uint8_t gray = row[src];
rgba[dst + 0] = gray;
rgba[dst + 1] = gray;
rgba[dst + 2] = gray;
} else if (components == 4) {
const float c = row[src + 0] / 255.0f;
const float m = row[src + 1] / 255.0f;
const float yv = row[src + 2] / 255.0f;
const float k = row[src + 3] / 255.0f;
rgba[dst + 0] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - c) * (1.0f - k), 0.0f, 255.0f));
rgba[dst + 1] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - m) * (1.0f - k), 0.0f, 255.0f));
rgba[dst + 2] = static_cast<uint8_t>(std::clamp(255.0f * (1.0f - yv) * (1.0f - k), 0.0f, 255.0f));
} else {
rgba[dst + 0] = row[src + 0];
rgba[dst + 1] = row[src + 1];
rgba[dst + 2] = row[src + 2];
}
rgba[dst + 3] = 255;
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return rgba;
}
void ImageDecoder::applySoftMask(QPDFObjectHandle imageStream,
std::vector<uint8_t>& rgba,
int width,
int height) {
if (rgba.size() != static_cast<size_t>(width) * height * 4) {
return;
}
auto dict = imageStream.getDict();
if (!dict.isDictionary() || !dict.hasKey("/SMask")) {
return;
}
auto smask = dict.getKey("/SMask");
if (!smask.isStream()) {
return;
}
auto smaskDict = smask.getDict();
const int maskWidth = intValue(smaskDict, "/Width", 0);
const int maskHeight = intValue(smaskDict, "/Height", 0);
if (maskWidth != width || maskHeight != height) {
std::cerr << "Warning: Soft mask dimensions do not match image dimensions.\n";
return;
}
const int maskBpc = intValue(smaskDict, "/BitsPerComponent", 8);
const std::string maskColorSpace = nameValue(smaskDict, "/ColorSpace").empty()
? "/DeviceGray"
: nameValue(smaskDict, "/ColorSpace");
const std::string maskFilter = nameValue(smaskDict, "/Filter");
auto maskRgba = decode(smask, maskColorSpace, maskWidth, maskHeight, maskBpc, maskFilter);
if (maskRgba.size() != rgba.size()) {
return;
}
for (int i = 0; i < width * height; ++i) {
rgba[static_cast<size_t>(i) * 4 + 3] = maskRgba[static_cast<size_t>(i) * 4];
}
}
std::vector<uint8_t> ImageDecoder::convertGrayToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i < std::min(static_cast<int>(rawBytes.size()), expectedSize); ++i) {
uint8_t gray = rawBytes[i];
rgba.push_back(gray);
rgba.push_back(gray);
rgba.push_back(gray);
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
std::vector<uint8_t> ImageDecoder::convertRgbToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height * 3;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i + 2 < std::min(static_cast<int>(rawBytes.size()), expectedSize); i += 3) {
rgba.push_back(rawBytes[i]);
rgba.push_back(rawBytes[i + 1]);
rgba.push_back(rawBytes[i + 2]);
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
std::vector<uint8_t> ImageDecoder::convertCmykToRgba(const std::vector<unsigned char>& rawBytes,
int width,
int height) {
int expectedSize = width * height * 4;
std::vector<uint8_t> rgba;
rgba.reserve(width * height * 4);
for (int i = 0; i + 3 < std::min(static_cast<int>(rawBytes.size()), expectedSize); i += 4) {
float c = rawBytes[i] / 255.0f;
float m = rawBytes[i + 1] / 255.0f;
float y = rawBytes[i + 2] / 255.0f;
float k = rawBytes[i + 3] / 255.0f;
auto applyInk = [](float paper, float processColor, float amount) {
return paper * ((1.0f - amount) + amount * (processColor / 255.0f));
};
float r = 255.0f;
float g = 255.0f;
float b = 255.0f;
r = applyInk(r, 0.0f, c);
g = applyInk(g, 174.0f, c);
b = applyInk(b, 239.0f, c);
r = applyInk(r, 237.0f, m);
g = applyInk(g, 0.0f, m);
b = applyInk(b, 140.0f, m);
r = applyInk(r, 255.0f, y);
g = applyInk(g, 241.0f, y);
b = applyInk(b, 0.0f, y);
r = applyInk(r, 35.0f, k);
g = applyInk(g, 31.0f, k);
b = applyInk(b, 32.0f, k);
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(r), 0l, 255l)));
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(g), 0l, 255l)));
rgba.push_back(static_cast<uint8_t>(std::clamp(std::lround(b), 0l, 255l)));
rgba.push_back(255);
}
while (rgba.size() < static_cast<size_t>(width * height * 4)) {
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(0);
rgba.push_back(255);
}
return rgba;
}
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <qpdf/QPDFObjectHandle.hh>
namespace pdfengine {
class ImageDecoder {
public:
static std::vector<uint8_t> decode(QPDFObjectHandle imageStream,
const std::string& colorSpace,
int width, int height,
int bitsPerComponent,
const std::string& filter);
private:
static std::vector<uint8_t> decodeJpeg(const std::vector<unsigned char>& jpegBytes);
static void applySoftMask(QPDFObjectHandle imageStream, std::vector<uint8_t>& rgba, int width, int height);
static std::vector<uint8_t> convertGrayToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
static std::vector<uint8_t> convertRgbToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
static std::vector<uint8_t> convertCmykToRgba(const std::vector<unsigned char>& rawBytes, int width, int height);
};
}
+28
View File
@@ -0,0 +1,28 @@
#include <pdfengine/path_interpreter.hpp>
namespace pdfengine {
void PathObjectInterpreter::interpret(const PathObject& pathObj, DisplayList& displayList) {
if (pathObj.path.empty()) {
return;
}
displayList.saveState();
displayList.setTransform(pathObj.transform);
switch (pathObj.paintOp) {
case PathPaintOp::Stroke:
displayList.strokePath(pathObj.path);
break;
case PathPaintOp::Fill:
displayList.fillPath(pathObj.path, pathObj.fillRule);
break;
case PathPaintOp::FillStroke:
displayList.fillStrokePath(pathObj.path, pathObj.fillRule);
break;
}
displayList.restoreState();
}
} // namespace pdfengine
+296
View File
@@ -0,0 +1,296 @@
#include "pdfengine/skia_renderer.hpp"
#ifdef PDFENGINE_WITH_SKIA
#include <include/core/SkCanvas.h>
#include <include/core/SkPaint.h>
#include <include/core/SkMatrix.h>
#include <include/core/SkFont.h>
#include <include/core/SkTypeface.h>
#include <include/core/SkPath.h>
#include <include/core/SkImage.h>
#include <include/core/SkData.h>
#include <include/core/SkImageInfo.h>
#endif
namespace pdfengine {
SkiaRenderer::SkiaRenderer(SkCanvas* canvas) : m_canvas(canvas) {}
void SkiaRenderer::render(const DisplayList& displayList) {
if (!m_canvas) return;
displayList.replay(*this);
}
void SkiaRenderer::visit(const SaveStateCommand& cmd) {
(void)cmd;
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) m_canvas->save();
#endif
m_stateStack.push();
}
void SkiaRenderer::visit(const RestoreStateCommand& cmd) {
(void)cmd;
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) m_canvas->restore();
#endif
m_stateStack.pop();
}
void SkiaRenderer::visit(const SetTransformCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (m_canvas) {
SkMatrix skMatrix;
skMatrix.setAll(
cmd.matrix.a, cmd.matrix.c, cmd.matrix.e,
cmd.matrix.b, cmd.matrix.d, cmd.matrix.f,
0.0f, 0.0f, 1.0f
);
m_canvas->concat(skMatrix);
}
#endif
m_stateStack.current().ctm = m_stateStack.current().ctm.multiply(cmd.matrix);
}
void SkiaRenderer::visit(const FillRectCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas) return;
SkPaint paint;
paint.setAntiAlias(true);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
SkRect rect = SkRect::MakeXYWH(cmd.x, cmd.y, cmd.width, cmd.height);
m_canvas->drawRect(rect, paint);
#endif
}
void SkiaRenderer::visit(const DrawTextCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas) return;
SkPaint paint;
paint.setAntiAlias(true);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
SkFont font(nullptr, 12.0f);
m_canvas->drawString(cmd.text.c_str(), cmd.x, cmd.y, font, paint);
#endif
}
void SkiaRenderer::visit(const FillPathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
if (cmd.rule == FillRule::EvenOdd) {
skPath.setFillType(SkPathFillType::kEvenOdd);
} else {
skPath.setFillType(SkPathFillType::kWinding);
}
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kFill_Style);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
m_canvas->drawPath(skPath, paint);
#endif
}
void SkiaRenderer::visit(const StrokePathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
const auto& color = m_stateStack.current().fillColor;
paint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
paint.setStrokeWidth(1.0f);
m_canvas->drawPath(skPath, paint);
#endif
}
void SkiaRenderer::visit(const FillStrokePathCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.path.empty()) return;
SkPath skPath;
for (const auto& segment : cmd.path.segments()) {
switch (segment.verb) {
case Path::Verb::MoveTo:
skPath.moveTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::LineTo:
skPath.lineTo(segment.points[0].x, segment.points[0].y);
break;
case Path::Verb::CubicBezierTo:
skPath.cubicTo(
segment.points[0].x, segment.points[0].y,
segment.points[1].x, segment.points[1].y,
segment.points[2].x, segment.points[2].y
);
break;
case Path::Verb::Close:
skPath.close();
break;
}
}
if (cmd.rule == FillRule::EvenOdd) {
skPath.setFillType(SkPathFillType::kEvenOdd);
} else {
skPath.setFillType(SkPathFillType::kWinding);
}
const auto& color = m_stateStack.current().fillColor;
// First fill
SkPaint fillPaint;
fillPaint.setAntiAlias(true);
fillPaint.setStyle(SkPaint::kFill_Style);
fillPaint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
m_canvas->drawPath(skPath, fillPaint);
// Then stroke
SkPaint strokePaint;
strokePaint.setAntiAlias(true);
strokePaint.setStyle(SkPaint::kStroke_Style);
strokePaint.setColor(SkColorSetARGB(255,
static_cast<uint8_t>(color.r * 255),
static_cast<uint8_t>(color.g * 255),
static_cast<uint8_t>(color.b * 255)));
strokePaint.setStrokeWidth(1.0f);
m_canvas->drawPath(skPath, strokePaint);
#endif
}
void SkiaRenderer::visit(const DrawImageCommand& cmd) {
(void)cmd;
#ifdef PDFENGINE_WITH_SKIA
if (!m_canvas || cmd.image.pixelData.empty()) return;
SkImageInfo info = SkImageInfo::Make(
cmd.image.width,
cmd.image.height,
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType
);
sk_sp<SkData> data = SkData::MakeWithCopy(
cmd.image.pixelData.data(),
cmd.image.pixelData.size()
);
sk_sp<SkImage> skImage = SkImages::RasterFromData(
info,
std::move(data),
cmd.image.width * 4
);
if (skImage) {
m_canvas->save();
SkMatrix skMatrix;
skMatrix.setAll(
cmd.matrix.a, cmd.matrix.c, cmd.matrix.e,
cmd.matrix.b, cmd.matrix.d, cmd.matrix.f,
0.0f, 0.0f, 1.0f
);
m_canvas->concat(skMatrix);
SkRect destRect = SkRect::MakeXYWH(0, 0, 1.0f, 1.0f);
SkPaint paint;
paint.setAlphaf(cmd.opacity);
m_canvas->drawImageRect(
skImage.get(),
destRect,
SkSamplingOptions(SkFilterMode::kLinear),
&paint
);
m_canvas->restore();
}
#endif
}
}
+1
View File
@@ -0,0 +1 @@
#include "fonts/cache/glyph_bitmap.hpp"
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <vector>
namespace pdfengine::fonts {
struct GlyphBitmap {
std::vector<unsigned char> pixels;
int width = 0;
int height = 0;
int bearingX = 0;
int bearingY = 0;
double advance = 0.0;
};
}
+118
View File
@@ -0,0 +1,118 @@
#include "fonts/cache/glyph_cache.hpp"
namespace pdfengine::fonts {
GlyphCache::GlyphCache(std::size_t capacity)
: capacity_(capacity) {
std::size_t num_shards = (capacity < 16) ? 1 : 16;
for (std::size_t i = 0; i < num_shards; ++i) {
shards_.push_back(std::make_unique<Shard>());
}
}
GlyphCache::~GlyphCache() = default;
std::optional<GlyphBitmap> GlyphCache::get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize) {
uint64_t fontId = fontFace.getId();
if (fontId == 0) {
return std::nullopt;
}
GlyphCacheKey key{fontId, glyphIndex, fontSize};
std::size_t shard_idx = getShardIndex(key);
auto& shard = *shards_[shard_idx];
std::lock_guard<std::mutex> lock(shard.mutex_);
auto it = shard.cache_map_.find(key);
if (it == shard.cache_map_.end()) {
shard.misses_++;
return std::nullopt;
}
shard.hits_++;
shard.lru_list_.splice(shard.lru_list_.begin(), shard.lru_list_, it->second.second);
return it->second.first;
}
void GlyphCache::insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap) {
uint64_t fontId = fontFace.getId();
if (fontId == 0) {
return;
}
GlyphCacheKey key{fontId, glyphIndex, fontSize};
std::size_t shard_idx = getShardIndex(key);
auto& shard = *shards_[shard_idx];
std::lock_guard<std::mutex> lock(shard.mutex_);
auto it = shard.cache_map_.find(key);
if (it != shard.cache_map_.end()) {
it->second.first = bitmap;
shard.lru_list_.splice(shard.lru_list_.begin(), shard.lru_list_, it->second.second);
return;
}
std::size_t shard_capacity = (capacity_ + shards_.size() - 1) / shards_.size();
if (shard.cache_map_.size() >= shard_capacity && shard_capacity > 0) {
GlyphCacheKey oldest = shard.lru_list_.back();
shard.cache_map_.erase(oldest);
shard.lru_list_.pop_back();
}
if (shard_capacity > 0) {
shard.lru_list_.push_front(key);
shard.cache_map_[key] = std::make_pair(bitmap, shard.lru_list_.begin());
}
}
std::size_t GlyphCache::size() const {
std::size_t total = 0;
for (const auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
total += shard->cache_map_.size();
}
return total;
}
std::size_t GlyphCache::capacity() const {
return capacity_;
}
void GlyphCache::clear() {
for (auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
shard->cache_map_.clear();
shard->lru_list_.clear();
shard->hits_ = 0;
shard->misses_ = 0;
}
}
double GlyphCache::hitRate() const {
std::size_t total_hits = 0;
std::size_t total_misses = 0;
for (const auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
total_hits += shard->hits_;
total_misses += shard->misses_;
}
std::size_t total = total_hits + total_misses;
if (total == 0) {
return 0.0;
}
return static_cast<double>(total_hits) / total;
}
void GlyphCache::resetStats() {
for (auto& shard : shards_) {
std::lock_guard<std::mutex> lock(shard->mutex_);
shard->hits_ = 0;
shard->misses_ = 0;
}
}
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include "fonts/face/font_face.hpp"
#include "fonts/cache/glyph_bitmap.hpp"
#include <optional>
#include <unordered_map>
#include <list>
#include <cstddef>
#include <mutex>
#include <memory>
namespace pdfengine::fonts {
struct GlyphCacheKey {
uint64_t fontId;
unsigned int glyphIndex;
unsigned int fontSize;
bool operator==(const GlyphCacheKey& other) const {
return fontId == other.fontId &&
glyphIndex == other.glyphIndex &&
fontSize == other.fontSize;
}
};
struct GlyphCacheKeyHash {
std::size_t operator()(const GlyphCacheKey& key) const {
std::size_t h1 = std::hash<uint64_t>{}(key.fontId);
std::size_t h2 = std::hash<unsigned int>{}(key.glyphIndex);
std::size_t h3 = std::hash<unsigned int>{}(key.fontSize);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)) ^ (h3 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2));
}
};
class GlyphCache {
public:
explicit GlyphCache(std::size_t capacity);
~GlyphCache();
GlyphCache(const GlyphCache&) = delete;
GlyphCache& operator=(const GlyphCache&) = delete;
GlyphCache(GlyphCache&&) noexcept = default;
GlyphCache& operator=(GlyphCache&&) noexcept = default;
std::optional<GlyphBitmap> get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize);
void insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap);
std::size_t size() const;
std::size_t capacity() const;
void clear();
double hitRate() const;
void resetStats();
private:
std::size_t capacity_;
using CacheIterator = std::list<GlyphCacheKey>::iterator;
struct Shard {
std::size_t hits_ = 0;
std::size_t misses_ = 0;
std::list<GlyphCacheKey> lru_list_;
std::unordered_map<
GlyphCacheKey,
std::pair<GlyphBitmap, CacheIterator>,
GlyphCacheKeyHash
> cache_map_;
mutable std::mutex mutex_;
};
static constexpr std::size_t NUM_SHARDS = 16;
std::vector<std::unique_ptr<Shard>> shards_;
std::size_t getShardIndex(const GlyphCacheKey& key) const {
return GlyphCacheKeyHash{}(key) % shards_.size();
}
};
}
+179
View File
@@ -0,0 +1,179 @@
#include "fonts/face/font_face.hpp"
#include "fonts/face/free_type_manager.hpp"
#include <iostream>
#include <atomic>
namespace pdfengine::fonts {
static std::atomic<uint64_t> g_font_id_counter{1};
FontFace::FontFace()
: font_id_(g_font_id_counter.fetch_add(1, std::memory_order_relaxed)),
face_(nullptr),
mutex_(std::make_unique<std::mutex>()) {
}
FontFace::~FontFace() {
if (face_) {
FT_Done_Face(face_);
}
}
FontFace::FontFace(FontFace&& other) noexcept
: font_id_(other.font_id_),
face_(other.face_),
mutex_(std::move(other.mutex_)),
font_data_(std::move(other.font_data_)) {
other.font_id_ = 0;
other.face_ = nullptr;
}
FontFace& FontFace::operator=(FontFace&& other) noexcept {
if (this != &other) {
if (face_) {
FT_Done_Face(face_);
}
font_id_ = other.font_id_;
face_ = other.face_;
mutex_ = std::move(other.mutex_);
font_data_ = std::move(other.font_data_);
other.font_id_ = 0;
other.face_ = nullptr;
}
return *this;
}
bool FontFace::loadFromFile(const std::string& path) {
if (face_) {
FT_Done_Face(face_);
face_ = nullptr;
}
font_data_.clear();
if (FT_New_Face(
FreeTypeManager::instance().getLibrary(),
path.c_str(),
0,
&face_)) {
std::cerr << "Failed to load font: "
<< path << '\n';
return false;
}
FT_Set_Pixel_Sizes(face_, 0, 16);
return true;
}
bool FontFace::loadFromMemory(const std::vector<uint8_t>& data) {
if (data.empty()) {
std::cerr << "Cannot load font from empty memory buffer\n";
return false;
}
if (face_) {
FT_Done_Face(face_);
face_ = nullptr;
}
font_data_ = data;
if (FT_New_Memory_Face(
FreeTypeManager::instance().getLibrary(),
font_data_.data(),
static_cast<FT_Long>(font_data_.size()),
0,
&face_)) {
std::cerr << "Failed to load font from memory buffer\n";
font_data_.clear();
return false;
}
FT_Set_Pixel_Sizes(face_, 0, 16);
return true;
}
FT_Face FontFace::getFace() const {
return face_;
}
bool FontFace::coversUnicode(uint32_t codepoint) const {
if (!face_) return false;
std::lock_guard<std::mutex> lock(*mutex_);
FT_CharMap prev = face_->charmap;
FT_Select_Charmap(face_, FT_ENCODING_UNICODE);
FT_UInt gid = FT_Get_Char_Index(face_, codepoint);
if (prev) FT_Set_Charmap(face_, prev);
return gid != 0;
}
std::vector<uint32_t> FontFace::coveredCodepoints() const {
std::vector<uint32_t> out;
if (!face_) return out;
std::lock_guard<std::mutex> lock(*mutex_);
FT_CharMap prev = face_->charmap;
if (FT_Select_Charmap(face_, FT_ENCODING_UNICODE) == 0) {
FT_UInt gid = 0;
FT_ULong cp = FT_Get_First_Char(face_, &gid);
while (gid != 0) { out.push_back(static_cast<uint32_t>(cp)); cp = FT_Get_Next_Char(face_, cp, &gid); }
}
if (prev) FT_Set_Charmap(face_, prev);
return out;
}
uint64_t FontFace::getId() const {
return font_id_;
}
std::mutex& FontFace::getMutex() const {
return *mutex_;
}
std::optional<GlyphBitmap> FontFace::renderGlyph(unsigned int glyphIndex, unsigned int fontSize) {
if (!face_) {
return std::nullopt;
}
std::lock_guard<std::mutex> lock(*mutex_);
if (FT_Set_Pixel_Sizes(face_, 0, fontSize)) {
return std::nullopt;
}
if (FT_Load_Glyph(face_, glyphIndex, FT_LOAD_RENDER)) {
return std::nullopt;
}
FT_GlyphSlot slot = face_->glyph;
FT_Bitmap& bitmap = slot->bitmap;
GlyphBitmap glyph_bitmap;
glyph_bitmap.width = static_cast<int>(bitmap.width);
glyph_bitmap.height = static_cast<int>(bitmap.rows);
glyph_bitmap.bearingX = slot->bitmap_left;
glyph_bitmap.bearingY = slot->bitmap_top;
glyph_bitmap.advance = static_cast<double>(slot->advance.x) / 64.0;
if (glyph_bitmap.width > 0 && glyph_bitmap.height > 0) {
glyph_bitmap.pixels.resize(glyph_bitmap.width * glyph_bitmap.height);
for (int r = 0; r < glyph_bitmap.height; ++r) {
std::copy(
bitmap.buffer + r * bitmap.pitch,
bitmap.buffer + r * bitmap.pitch + glyph_bitmap.width,
glyph_bitmap.pixels.begin() + r * glyph_bitmap.width
);
}
}
return glyph_bitmap;
}
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "fonts/cache/glyph_bitmap.hpp"
#include <optional>
#include <string>
#include <vector>
#include <cstdint>
#include <mutex>
#include <memory>
#include <ft2build.h>
#include FT_FREETYPE_H
namespace pdfengine::fonts {
class FontFace {
public:
FontFace();
~FontFace();
FontFace(const FontFace&) = delete;
FontFace& operator=(const FontFace&) = delete;
FontFace(FontFace&& other) noexcept;
FontFace& operator=(FontFace&& other) noexcept;
bool loadFromFile(const std::string& path);
bool loadFromMemory(const std::vector<uint8_t>& data);
FT_Face getFace() const;
uint64_t getId() const;
std::mutex& getMutex() const;
bool coversUnicode(uint32_t codepoint) const;
std::vector<uint32_t> coveredCodepoints() const;
std::optional<GlyphBitmap> renderGlyph(unsigned int glyphIndex, unsigned int fontSize);
private:
uint64_t font_id_;
FT_Face face_;
std::unique_ptr<std::mutex> mutex_;
std::vector<uint8_t> font_data_;
};
}
@@ -0,0 +1,28 @@
#include "fonts/face/free_type_manager.hpp"
#include <iostream>
namespace pdfengine::fonts {
FreeTypeManager& FreeTypeManager::instance() {
static FreeTypeManager instance;
return instance;
}
FreeTypeManager::FreeTypeManager() : library_(nullptr) {
if (FT_Init_FreeType(&library_)) {
std::cerr << "Failed to initialize FreeType\n";
}
}
FreeTypeManager::~FreeTypeManager() {
if (library_) {
FT_Done_FreeType(library_);
library_ = nullptr;
}
}
FT_Library FreeTypeManager::getLibrary() const {
return library_;
}
}

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