Skip to content

Arena/753d47d5 linkco pdf - #717

Closed
b-lincko wants to merge 45 commits into
storytold:mainfrom
b-lincko:arena/753d47d5-linkco-pdf
Closed

b-lincko wants to merge 45 commits into
storytold:mainfrom
b-lincko:arena/753d47d5-linkco-pdf

Conversation

@b-lincko

Copy link
Copy Markdown

No description provided.

b-lincko and others added 18 commits October 8, 2026 13:46
…vices & Contracting Company W.L.L.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…nd Windows installer

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ows installers

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…endencies in build.py and installer.py

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…d icon_pill warning

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…blem icons, and corporate profile

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
… high-DPI printing

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ase notes, and README verification

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…nkco PDF Editor

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…indows 0.5.0 packaging

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…e print rendering

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…t-cli preview

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
… fix Low-IL/WinForms preview rendering

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
PreviewHandler.cs (rewritten):
- Renders at the pane width x zoom (DPI-aware) via `pdfcraft-cli preview --width/--max-px`,
  re-renders when the pane grows, caps poster-sized pages, background render with cancellation.
- Per-user (HKCU) registration on .pdf, SystemFileAssociations\.pdf, own ProgIds and the
  UserChoice/UserChoiceLatest ProgId; never shared browser ProgIds; backs up and restores every
  value; ThreadingModel=Apartment, DisableLowILProcessIsolation=1, Mark-of-the-Web removed.
- Public API: RegisterPreviewHandler(path[, machineWide]), UnregisterPreviewHandler(),
  QueryEffectiveHandler(), IsEffectiveHandler(), Diagnose(). Records a DLL stamp and the
  default-app ProgId so the app can tell cheaply when to register again.

pdfcraft-cli: `preview --width PX --max-px N` (+ unit and integration tests).

pdfcraft (GUI): new windows_preview module checks the registration in the background via
`reg query` and only runs the DLL's RegisterPreviewHandler when the DLL changed/moved or the
default PDF app changed. Skipped in portable mode or with LINKCO_NO_PREVIEW_REGISTRATION=1.

Installers:
- Setup EXE: registers via the DLL (with fallback), unregisters HKCU+HKLM on uninstall, stops
  prevhost and retries file replacement/deletion.
- MSI/NSIS: ThreadingModel Apartment; uninstall removes the uninstalling user's registration
  before the DLL is deleted. test-msi.ps1 asserts both.
- build.py/installer.py: shared compile_preview_handler (/codepage:65001), rebuild when stale,
  register + Diagnose() report after a dev build; packaging never registers staging copies.
- test-windows-integration.ps1: registration API checks, Diagnose(), valid-PDF render test.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Thumbnail provider (IThumbnailProvider, CLSID {3D8CDE4B-E969-481F-BEB0-5E3B98287416})
- New LinkcoPdfThumbnailProvider in LinkcoPdfPreviewHandler.dll (IInitializeWithStream and
  IInitializeWithFile): renders page 1 through `pdfcraft-cli preview` at below-normal priority
  with an 8 s timeout, fits it into the requested square and returns an opaque HBITMAP.
  Password-protected or damaged PDFs keep the normal icon.
- Registered only on SystemFileAssociations\.pdf (consulted last) and Linkco's own ProgIDs, so
  another PDF app's thumbnail provider keeps priority; anything it replaces is backed up and
  restored on unregister. Per-user (app/build), NSIS and MSI registration, plus Setup.exe
  fallback cleanup. Registration schema 3 -> 4 so existing installs re-register once.
- Diagnose() reports the effective thumbnail provider and its per-hive CodeBase.
- test-windows-integration.ps1 checks the class and interfaces and renders a real 256 px
  thumbnail end to end; test-msi.ps1 checks the MSI's thumbnail rows.

packaging/windows/csharp-check
- `node packaging/windows/csharp-check/check.cjs` compiles PreviewHandler.cs and the Setup EXE as
  csc.exe does (C# 5, .NET Framework 4.8 reference assemblies, /warn:4) on Linux/macOS/Windows,
  using Roslyn in the .NET WebAssembly runtime (@live-codes/blazor-wasm, MIT) and
  mono/reference-assemblies (MIT), downloaded into a git-ignored cache. Nothing ships.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ofile

Linkco PDF Editor registers its preview handler and thumbnail provider per user, for each user
who starts it, but the uninstallers only cleaned up the uninstalling user. Every other account was
left with HKCU entries pointing Explorer at the deleted DLL: blank previews and thumbnails, even
where another PDF app's handler had worked before (its backup was never restored).

- PreviewHandler.cs: UnregisterPreviewHandlerForAllUsers() visits every user profile
  (ProfileList, S-1-5-21-* and Entra ID S-1-12-1-*): signed-in users' hives in place, the others
  mounted from NTUSER.DAT + UsrClass.dat (backup/restore privileges) and unmounted right after.
  For users with a Linkco registration it runs the existing restore-or-remove logic. HKLM is left
  to the installers.
- RegRoot: a registry root that sends "Software\Classes\..." to a separate classes hive. The
  registration helpers now take it (an implicit conversion keeps the RegistryKey callers as they
  were), so the same code serves HKCU, HKLM and mounted profiles.
- Cleanup is now right for users other than the one running it: their own UserChoice, a sweep
  for any of their ProgIDs still pointing at us, and restoring a backup only if its CLSID exists
  machine-wide or for that user.
- MSI: a new deferred, non-impersonated (LocalSystem) custom action before RemoveFiles.
  NSIS and the machine-wide Setup EXE call it elevated.
- NSIS: the uninstall command was a 1110-character -EncodedCommand. NSIS cuts strings at 1024,
  so it never ran. It is now a ~420-character readable -Command.
- csharp-check: also runs the cleanup code taken from PreviewHandler.cs against an in-memory
  registry (tests/), with 35 checks: split and single hives, restoring per-user and machine-wide
  handlers, leaving unrelated keys and other users unchanged, no leaked handles, idempotence.
- test-windows-integration.ps1 / test-msi.ps1: split-hive cleanup against the real registry
  (scratch HKCU keys) and the MSI custom action's deferred/no-impersonate flags.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…tall paths

The app decides at start-up whether the per-user registration is current by reading it through
reg.exe and comparing the recorded DLL path with its own. reg.exe prints in the console's OEM code
page, which can't carry most non-ASCII text, so for a path containing e.g. an Arabic Windows user
name (the per-user install lives under C:\Users\<name>\AppData\Local\Programs) the paths never
matched. The app then ran PowerShell to register again, and the DLL broadcast an association
change that refreshes Explorer, on every start.

- PreviewHandler.cs writes PreviewHandlerDllKey: the path in ASCII (letters upper-cased; '%',
  control and non-ASCII UTF-16 units as %XXXX). Unregister removes it.
- windows_preview.rs builds the same key from the path's exact UTF-16 (OsStrExt::encode_wide) and
  compares that. Older registrations, which have no key, register again once.
- Shared test vectors (ASCII, Arabic, '%', Latin-1, surrogate pair) in the Rust unit tests (8 pass)
  and csharp-check's tests (38 checks), so the two implementations can't drift apart.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ding

Brings in the 42 upstream changes since the last merge, adapted to Linkco PDF Editor:
Arabic interface (ar.tsv, right-to-left labels, Noto Sans Arabic via craft-fonts
8dcdacd), Windows single instance (files open as tabs in the running window),
Per-Monitor V2 DPI manifest, signing of encrypted documents, smart-card PIN prompts,
signer key-usage/EKU checks, redaction word lists and FOIA/Privacy Act code sets,
bookmarks from tagged headings, ICC profiles kept, /UserUnit pages, rotated text,
update-check rate-limit message, UI fixes and render performance work.

Linkco adaptations:
- single_instance: socket in %LOCALAPPDATA%\Linkco PDF Editor (beside Recovery),
  Linkco names in logs and docs; LinkcoPDFEditor.exe in the module docs.
- build.rs keeps the Linkco VERSIONINFO and embeds the new DPI manifest.
- main.rs: the preview-handler registration check runs only in a launch that opens a
  window, not in one that hands its files to the running app and exits.
- updates.rs keeps the b-lincko/linkco-pdf release URL and Linkco User-Agent, plus
  upstream's rate-limit handling.
- ar.tsv: PdfCraft -> Linkco PDF Editor, plus Arabic for the Linkco home-page strings;
  the Arabic bidi test follows the new name.
- x509: the unknown-critical-extension message names Linkco PDF Editor.
- ATTRIBUTION.toml/.md: catalog hashes recomputed, ar.tsv entry and the craft-fonts
  usage text (Noto Sans Arabic) taken from upstream; ATTRIBUTION.md regenerated.
- build.py / installer.py fetch craft-fonts at the commit pinned in release.yml
  (--no-craft-fonts / LINKCO_NO_CRAFT_FONTS=1 / CRAFT_FONTS_DIR) so local builds can
  write Arabic and Japanese text into PDFs, and ship the fonts' OFL-*.txt licences.
- README and RELEASE_NOTES describe the new features.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
b-lincko and others added 11 commits October 11, 2026 06:10
…co branding

All 75 upstream changes, keeping the Linkco PDF Editor name, branding, Windows
integration and installers.

Printing: keep Linkco's Windows backend (3-tier printer detection with status,
150/300/600 dpi sheets via System.Drawing.Printing, zero margins, closest paper,
landscape when wide); upstream's windows_print.ps1 backend is not adopted. Port
upstream's background work onto it: printers are listed off the UI thread
(PrinterSource now yields PrinterDetail with status), "Check again" also lists in
the background, and jobs are rendered and spooled on a worker thread. The engine
gains Session::print_max_dpi + render_imposed_sheets so rendering needs only the
imposed bytes; render_print_sheets keeps its signature.

PDF document icon (resource 2), recoloured to the Linkco palette, registered by
the Setup EXE, NSIS and MSI (DefaultIcon ,-2). LICENSE-MIT, LICENSE-APACHE,
NOTICE and LICENSE-document.txt ship beside the executables in every installer
and the portable ZIP.

Version 0.6.0 (preview handler assembly and installer Version strings bumped
together). Polish catalog with the Linkco strings. ATTRIBUTION three-way merged
(pl.tsv, minimize-2.svg, document icon entries) with hashes recomputed and
ATTRIBUTION.md regenerated. README and RELEASE_NOTES updated for 0.6.0.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Whitespace-only (token-identical): long lines left by the Linkco rebrand
(x509.rs, windows_preview.rs, main.rs), the 3-tier printer code in
spool.rs, and the background-printing/render_imposed_sheets code from the
v0.6.0 merge. Checked with rustfmt 1.88 (stable) and the repository's
rustfmt.toml, which reports no changes on upstream's own files; every
tracked workspace .rs file (vendor/ excluded, as in cargo fmt) now passes.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
- The dialog follows the app window (within 24 px of its edges, up to 1600x1200,
  never smaller than the old 820 px dialog). The preview pane takes all the room
  beside the settings column, which scrolls when the window is short.
- Preview rasters are requested for the actual pane size (preview_rasters takes
  the paper box), so the bigger preview is rendered at its on-screen size.
- The texture drawn for a page is the one closest to its on-screen width
  (pick_preview_texture). egui-wgpu makes no mipmaps, so shrinking a much larger
  view raster (high zoom) into the pane dropped texels and broke up lines/text.
- Paper and page quads are snapped to whole physical pixels (no half-pixel blur).
- Unit tests for the layout, snapping and texture choice.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…veloper credit

Print screen
- Network printers are recognised however they are connected: \\server shares, Standard
  TCP/IP ports named IP_x or after the address/host (10.0.0.5, 10.0.0.5_1, host:9100), WSD,
  IPP/http(s) and LPR ports, and Win32_Printer.Network (new optional 8th TSV column; the
  7-column format still parses). spool::is_network_port + tests.
- Printer list marks network printers ("· Network"); statuses (Paused/Offline/Error) and the
  status warning are translated; a details line shows "Network printer · Port · Driver" (full
  text on hover); an empty list explains what to do (desktop builds only). kittest added.

Windows preview handler
- DiagnoseFile(path): location (local, UNC, mapped drive, OneDrive/online-only), security zone
  (urlmon MapUrlToZone, with and without the Mark-of-the-Web check), whether Explorer blocks the
  preview pane (Internet/Restricted zone since the Oct 2025 update), policy, and the fix.
- AllowNetworkPreview / DisallowNetworkPreview: opt-in, per server, per user Local intranet
  site entry under HKCU ZoneMap (Domains\<domain>\<host>, Domains\<name>, Ranges\Linkco_<ip>);
  refuses when zones are machine-wide by policy. Pure helpers tested in csharp-check (21 checks).
- Stale thumb-* temp PNGs are swept like the preview's own.

Release
- Version 1.0.0 everywhere (workspace, Cargo.lock members, preview handler assembly and the
  matching NSIS/MSI registrations). RELEASE_NOTES v1.0.0, README troubleshooting.
- Help > About: "Developer: Muhammad Bilal (Linkco)" (crate::DEVELOPER), README credit.
- 10 new strings translated in all 15 maintained catalogs.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
- Start pdfcraft-cli.exe with CreateProcess (STARTF_FORCEOFFFEEDBACK, hidden,
  below-normal priority) instead of System.Diagnostics.Process: no busy cursor
  on every selection, output captured to temp files so no wait can block on
  pipe EOF, and a kill-on-close job so the engine never outlives prevhost.
- Pane: finished renders are parked in a slot drained by BeginInvoke and by a
  watchdog timer; 'still loading' notice after 8 s; the watchdog gives up 10 s
  after the 30 s engine timeout; timed-out/failed pages offer click-to-retry;
  cancelling never leaves a stale 'Loading page N...' message.
- Thumbnails use the same runner.
- preview.log records Initialize/DoPreview/render timings; Diagnose() adds an
  engine self-test, the preview host integrity level and the log tail.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…w Handler'

- Guard the pane's WndProc, timers and render delivery so no exception can
  reach WinForms' thread-exception path and end prevhost.exe; log fatal
  unhandled exceptions (with stack) to preview.log.
- Diagnose(): effective COM registration (HKCU over HKLM, DLL exists /
  version / Mark of the Web), a COM activation test like the preview host's,
  and recent prevhost/handler crashes from the Application event log.
- packaging/windows/diagnose-preview.ps1: locates the registered DLL and
  prints Diagnose() (+ DiagnoseFile with -Pdf).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ler repair

Fixes the remaining causes of Explorer's "This file can't be previewed because
of an error in the Linkco PDF Preview Handler":

- Run in a dedicated prevhost.exe: own AppID {1C0ADB7B-F017-4384-BF73-95AA5BC5D084}
  with DllSurrogate=%SystemRoot%\system32\prevhost.exe, as Microsoft requires for
  managed preview handlers, instead of the shared {6d2b5079-...} host where other
  vendors' handlers (e.g. PowerToys' .NET 8 handlers) and crashes take ours down.
  Written by the DLL (HKCU/HKLM), installer.py fallback, NSIS and WiX; removed on
  unregister/uninstall. RegistrationSchema 4 -> 5 (C# and Rust) so existing
  installs re-register; the app also re-registers if the AppID key is missing.
- Every COM entry point is a guarded wrapper around a NoInlining Core method, so
  JIT/type-load failures can't escape; constructors, visuals, GetWindow, SetSite,
  QueryFocus, TranslateAccelerator guarded; RenderWorker can't kill the host;
  SetParent failures logged.
- NSIS: stop prevhost before replacing/deleting the DLL; repair the installing
  user's HKCU registration after install.
- diagnose-preview.ps1 -Repair; Diagnose() reports the host AppID/DllSurrogate,
  .NET Framework release and competing .pdf preview handlers (PowerToys named).
- Tests: registry cleanup (64 checks), test-windows-integration.ps1, test-msi.ps1.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…s runner

test-preview-host.ps1 builds LinkcoPdfPreviewHandler.dll, registers it as the app and the
installers do, and hosts it through prevhost.exe with PreviewHostTest.exe (CoCreateInstance
LOCAL_SERVER, IInitializeWithStream, SetSite, SetWindow, visuals, DoPreview), as a normal user
like Explorer, reporting each HRESULT, the surrogate's fate, what was painted, preview.log,
Diagnose() and crash events. The workflow publishes the report as annotations.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…st.cs in csharp-check

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
… output

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…omparison, install in Program Files

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
b-lincko and others added 16 commits October 11, 2026 11:49
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ercent-decode it

Uri.AbsoluteUri registered file:///C:/Program%20Files/..., and the CLR's COM
activation hands CodeBase to the assembly binder without decoding it, so
prevhost.exe looked for a 'Program%20Files' folder and failed with 0x80070002
(Explorer: 'an error in the Linkco PDF Preview Handler') for every install
under Program Files. Write file:///C:/Program Files/... instead, re-register
existing installs (schema 6, Rust check rejects escaped CodeBase), and make
Diagnose()/diagnose-preview.ps1 resolve CodeBase like the CLR and test
activation in a fresh native process.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…when UAC is off; test CodeBase forms machine-wide

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…Limited scheduled task, as Explorer

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…er and hosts the handler

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…se notes for the CodeBase fix

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…eport white page pixels

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…eport a hung host instead of rerunning it

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
… thumbnail of the capture

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…OleMarshalObject) so the pane paints

.NET hands COM a free-threaded object by default, so Explorer's calls arrived
on prevhost's RPC worker threads and DoPreview created the pane window on a
thread with no message loop: every call succeeded, but the window never
painted or answered a message (the pane stayed blank / kept loading).
Verified with a control: Microsoft's PDF previewer in the same harness
answers messages and paints.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…r registration previews end to end; release notes

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@echelon

echelon commented Oct 11, 2026

Copy link
Copy Markdown
Member

Thank you for sharing this, @b-lincko! I'm closing it, though: it is a fork's "Linkco" rebrand rather than a change to PdfCraft. It sends update checks to the fork's repository, and it adds Python, JavaScript and C# code plus third-party brand assets, which the project can't take (Rust-only product, and AGENTS.md §1 on assets). If there is a specific feature or fix in the fork you'd like upstream, a focused PR for it on top of main would be very welcome.

Please join us on Discord if you haven't already: https://discord.gg/artcraft

@echelon echelon closed this Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants