Skip to content

Commit ad261a8

Browse files
committed
Read PDF, Office and SVG documents in the standalone viewer and the tray
DiffEngineViewer now compares PDF, docx, xlsx and pptx files, and draws SVGs beside their text. Each document shows as text, as its page drawn, or both (the default), cycled with r. [ and ] turn pages, and an entry opens at its first page that differs. - Text: an SVG's source, a PDF's text page by page (PDFium through Morph.PDFium), Office documents as Markdown (Morph). - Pages: rendered to png, so they reach every head as an ordinary ImagePane. No head or ABI field knows what a document is; r, [ and ] are additive DeviewKey values with no DEVIEW_VERSION bump. - The renderers live in DiffEngineViewer.Documents, loaded into an isolated AssemblyLoadContext in the viewer process. Reading and drawing run on DocumentWatch's thread, from a copy of each file checked against its hash, so the listener thread only hashes. - Shipped in a documents/ folder of the three tool packages and of the tray (one shared viewer/documents/), never in DiffEngine's bundled viewer, which stays the size it was. Payloads carry only their own RIDs' natives and no pdbs. - DiffEngine offers the viewer for .pdf/.docx/.xlsx/.pptx only when the copy it resolved has the documents folder. - Packaging.Tests: no documents in DiffEngine's bundle, and every documents folder complete for the RIDs it ships. The committed Linux and macOS natives need the build-native workflow to pick up the new keys; the footer buttons reach the same commands until then.
1 parent f69ad60 commit ad261a8

88 files changed

Lines changed: 4446 additions & 166 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitattributes‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
*.bin binary
44
*.png binary
55
*.docx binary
6+
*.xlsx binary
7+
*.pptx binary
8+
*.pdf binary
69
*.dll binary
710
*.so binary
811
*.dylib binary

‎claude.md‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,38 @@ apart.
177177
three fit from `ImagePane.Width/Height` — the file header's numbers, not the decoder's — one blank
178178
line under the pane's rows, so the placement rule lives once. Headers are sniffed by hand
179179
(`ImageHeader`) rather than by System.Drawing, which does not exist on macOS or Linux.
180+
- Documents (PDF, docx, xlsx, pptx, and SVG drawn beside its text) need **`src/DiffEngineViewer.Documents`**,
181+
a separate assembly with Morph, Morph.PDFium, Skia and the OpenXml SDK behind it: tens of MB per
182+
RID. So it ships only in a `documents/` folder of the three tool packages and of the tray (one folder
183+
at `viewer/documents/`, which each RID copy finds one directory up), never in DiffEngine's bundle.
184+
`Documents.targets` adds it, and only to RID-less builds - the bundle is the only per-RID publish -
185+
which is the same switch the Mac and Linux heads use for their own natives. Its natives and `.pdb`s
186+
are trimmed inside that project, from the items `deps.json` is generated from, because
187+
`AssemblyDependencyResolver` refuses a folder whose `deps.json` names a missing asset for the
188+
running RID.
189+
- `DocumentPlugin.Find()` looks for the folder; absent, it is null all the way down and every
190+
file reads exactly as before. Present, it loads on first use into a `DocumentLoadContext`, one
191+
per process (PDFium's lock is a static of its assembly), through two methods bound by name with
192+
BCL types only. It is **handed** to every reader from `ViewerProgram` - `FileSide.Read(path,
193+
documents)`, `TrackedEntry`, `TrackedWatch`, `OwnerLink`, `MessageHandler` - never found by
194+
each, so a test process does not read with whatever folder sits beside it.
195+
- In process, by choice: a native fault in PDFium or Skia ends the window. A hang is given up on
196+
after `DocumentWatch.Timeout`, and a PDF left behind holds PDFium's lock, so PDFs then fail at once.
197+
- `FileSide.Read` only hashes a document, because it runs on the listener thread a test process
198+
waits on. `DocumentWatch` (owned, attached and file modes) does the slow part from a copy taken
199+
under the cache's hash directory, checked against the hash, so nothing holds a lock on the user's
200+
file and what is drawn is what the hash says. Text replaces the entry once both sides are read
201+
(`ViewerSession.TextRead`, by reference, as `Refresh` does); pages go into
202+
`SessionState.Renders` by content hash a page at a time, so they never rebuild an entry or close
203+
a menu, and `Sync` never undoes them.
204+
- A page is an ordinary `ImagePane` pointing at a png, so no head and no ABI field knows what a
205+
document is. Both view halves `ScreenBuilder.PaneRows` and the heads place the page under the rows
206+
that are left; everything that scrolls a pane uses `PaneRows`, the queue column `BodyRows`.
207+
Which page shows and which differ is said in the headers and status line, for the reason above.
208+
`r`, `[` and `]` are additive `DeviewKey` values, as `m` was: no `DEVIEW_VERSION` bump, and the
209+
footer buttons reach the same commands from a shim built before them.
210+
- DiffEngine offers the viewer `DocumentExtensions.Paged` only when `ViewerDocuments.Beside` finds
211+
the folder by the resolved exe (beside, one up, or in the tool store behind a shim).
180212
- Text selection is a view, and makes the same bargain images do. A drag arrives as both of its
181213
ends at once, in rows of the whole side rather than of the visible slice: a head knows the scroll
182214
top it drew the press with, so only it can resolve one that spans a wheel notch, and reporting
@@ -339,7 +371,8 @@ apart.
339371
**Packaging.Tests (`src/Packaging.Tests/`):**
340372
- Opens each `.nupkg` a Release build drops in `nugets` and snapshots its entry list, plus a few
341373
invariants a snapshot states poorly: an apphost with no assembly beside it, a viewer file in the
342-
tray package, an incomplete bundled head.
374+
tray package, an incomplete bundled head, a documents folder in DiffEngine's bundle, and a
375+
documents folder whose `deps.json` names a native it does not carry.
343376
- Exists because package content is assembled by several unrelated MSBuild mechanisms and nothing
344377
else asserts the result. The failure mode it was written for is stale build output: `PackAsTool`
345378
packages the publish directory wholesale, and MSBuild never removes a file that stopped being

‎docs/diff-tool.md‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);
298298
macOS, Dear ImGui through raylib on Linux
299299
* Compares images by format, dimensions and content, and draws them, with
300300
whichever formats each platform's own decoder reads
301+
* The standalone tool and the copy installed with DiffEngineTray also read PDF,
302+
docx, xlsx and pptx files - as text, as pages drawn, or both - and draw SVGs
303+
beside their text. Offered for those files only when the copy found is one of
304+
these, never the bundled one, which stays small
301305

302306
#### Windows settings:
303307

@@ -312,9 +316,9 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);
312316
* Scanned paths:
313317
* `%USERPROFILE%\.dotnet\tools\DiffEngineViewer.exe`
314318
* `%USERPROFILE%\.dotnet\tools\.store\diffenginetray\*\diffenginetray\*\tools\*\any\viewer\win-x64\DiffEngineViewer.exe`
315-
* `%NUGET_PACKAGES%\diffengine\20.5.2\tools\viewer\win-x64\DiffEngineViewer.exe`
319+
* `%NUGET_PACKAGES%\diffengine\20.6.0\tools\viewer\win-x64\DiffEngineViewer.exe`
316320
* `%NUGET_PACKAGES%\diffengine\*\tools\viewer\win-x64\DiffEngineViewer.exe`
317-
* `%USERPROFILE%\.nuget\packages\diffengine\20.5.2\tools\viewer\win-x64\DiffEngineViewer.exe`
321+
* `%USERPROFILE%\.nuget\packages\diffengine\20.6.0\tools\viewer\win-x64\DiffEngineViewer.exe`
318322
* `%USERPROFILE%\.nuget\packages\diffengine\*\tools\viewer\win-x64\DiffEngineViewer.exe`
319323
* `%PATH%DiffEngineViewer.exe`
320324

@@ -330,9 +334,9 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);
330334
```
331335
* Scanned paths:
332336
* `%HOME%/.dotnet/tools/DiffEngineViewer`
333-
* `%NUGET_PACKAGES%/diffengine/20.5.2/tools/viewer/osx-x64/DiffEngineViewer`
337+
* `%NUGET_PACKAGES%/diffengine/20.6.0/tools/viewer/osx-x64/DiffEngineViewer`
334338
* `%NUGET_PACKAGES%/diffengine/*/tools/viewer/osx-x64/DiffEngineViewer`
335-
* `%HOME%/.nuget/packages/diffengine/20.5.2/tools/viewer/osx-x64/DiffEngineViewer`
339+
* `%HOME%/.nuget/packages/diffengine/20.6.0/tools/viewer/osx-x64/DiffEngineViewer`
336340
* `%HOME%/.nuget/packages/diffengine/*/tools/viewer/osx-x64/DiffEngineViewer`
337341
* `%PATH%DiffEngineViewer`
338342

@@ -348,9 +352,9 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);
348352
```
349353
* Scanned paths:
350354
* `%HOME%/.dotnet/tools/DiffEngineViewer`
351-
* `%NUGET_PACKAGES%/diffengine/20.5.2/tools/viewer/linux-x64/DiffEngineViewer`
355+
* `%NUGET_PACKAGES%/diffengine/20.6.0/tools/viewer/linux-x64/DiffEngineViewer`
352356
* `%NUGET_PACKAGES%/diffengine/*/tools/viewer/linux-x64/DiffEngineViewer`
353-
* `%HOME%/.nuget/packages/diffengine/20.5.2/tools/viewer/linux-x64/DiffEngineViewer`
357+
* `%HOME%/.nuget/packages/diffengine/20.6.0/tools/viewer/linux-x64/DiffEngineViewer`
354358
* `%HOME%/.nuget/packages/diffengine/*/tools/viewer/linux-x64/DiffEngineViewer`
355359
* `%PATH%DiffEngineViewer`
356360

‎docs/mdsource/tray.source.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ A delete is withdrawn when a later test run verifies against its file again, sin
5252

5353
When the tray holds it, the window becomes disposable. A viewer that is closed, killed or crashes takes nothing pending with it, and the tray opens a new one on the same queue. A snapshot arriving with no window open starts one.
5454

55+
The viewer installed with the tray also reads [documents](/docs/viewer.md#documents): PDF, Word, Excel and PowerPoint files as text, as their pages drawn, or both, and SVGs drawn beside their text. The copy bundled in the DiffEngine package does not. On a machine with the tray installed, test runs resolve the tray's copy ahead of the bundled one, so pending documents open in a viewer that can read them.
56+
5557
"Pending Snapshots" accepts all of them. Clicking one accepts that one, and its drop down offers discard, opening the viewer on it, and opening the source file. A snapshot that failed to apply is marked with `!` and stays pending, so it can be retried once whatever blocked it is out of the way.
5658

5759
Exiting the tray writes any still-pending inline snapshots back to disk, under the source project's `obj/VerifyInline/`, where accept tooling such as [Verify.Terminal](https://github.com/VerifyTests/Verify.Terminal) still finds them. A kill or a crash skips that, and loses the queue as it loses pending moves and deletes; re-run the tests.

‎docs/mdsource/viewer.source.md‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ All three draw the same screen model, and the layout, scrolling and keyboard han
2121
* https://www.nuget.org/packages/DiffEngineViewer.Mac
2222
* https://www.nuget.org/packages/DiffEngineViewer.Linux
2323

24-
Only needed to use the viewer outside a project that references DiffEngine, since DiffEngine already bundles it.
24+
Only needed to use the viewer outside a project that references DiffEngine, since DiffEngine already bundles it, or to read [documents](#documents), which only these packages and the copy installed with [DiffEngineTray](/docs/tray.md) do.
2525

2626
```
2727
dotnet tool install -g DiffEngineViewer.Windows
@@ -72,6 +72,8 @@ Nothing is staged for inline review. A newly launched viewer gets the patch in a
7272
| `Up` `Down` `PgUp` `PgDn` `Home` `End` | Scroll |
7373
| `n` `p` | Next and previous change (also the **Next change** and **Prev change** buttons) |
7474
| `m` | Show only the changes, or every line (also the **Changes only** button) |
75+
| `r` | Cycle a [document](#documents) between text and picture, picture only, and text only (also the button naming the next view) |
76+
| `[` `]` | Previous and next page of a document (also **Prev page** and **Next page**) |
7577
| `Tab` `Shift+Tab` | Next and previous pending item |
7678
| `a` | Accept |
7779
| `Shift+A` | Accept all |
@@ -183,6 +185,37 @@ A format a head cannot decode draws nothing, and the comparison is still there i
183185
Accepting is the same act it is for text — copy the received file over the expected one, or forward the move to the tray — so nothing about reviewing an image changes what accepting one does.
184186

185187

188+
## Documents
189+
190+
The standalone tool and the copy installed with [DiffEngineTray](/docs/tray.md) carry a `documents` folder, and with it read `.pdf`, `.docx`, `.xlsx` and `.pptx` files, and draw `.svg` files. The copy bundled in the DiffEngine package does not, to stay small: the folder is about 16 MB of libraries and 22 MB of natives per platform. That copy reads these files exactly as it always has, an SVG as text.
191+
192+
A document is shown one of three ways, and `r`, or the button naming the next one, cycles between them:
193+
194+
| View | Shows |
195+
| --- | --- |
196+
| Text and picture | The text in the top half of each pane, and the page being read drawn under it. The default. |
197+
| Picture only | The page, under two rows saying what each file is. |
198+
| Text only | The text, as any text file is shown. |
199+
200+
What the text is depends on the format:
201+
202+
* SVG: the file itself.
203+
* PDF: each page's text under a `--- page N ---` line, read with [PDFium](https://pdfium.googlesource.com/pdfium) through [Morph.PDFium](https://github.com/Papyrine/Morph.PDFium).
204+
* Word, Excel and PowerPoint: the document as Markdown, from [Morph](https://github.com/Papyrine/Morph). Embedded pictures become `image-N` references rather than lines of base64.
205+
206+
Pages are drawn by PDFium for a PDF, by Morph for an Office file and by [Svg.Skia](https://github.com/wieslawsoltes/Svg.Skia) for an SVG. A spreadsheet's pages are its printed pages rather than its sheets. Drawing is deterministic, so the same page always draws to the same bytes, which is how the viewer knows which pages differ. A small SVG is drawn at a reviewable size rather than its own, since a picture is never enlarged on screen.
207+
208+
A document opens at its first page that differs, the way a text comparison opens at its first change. `[` and `]`, or **Prev page** and **Next page**, turn the page on both sides at once. With only the pictures on screen, **Prev change** and **Next change** move between the pages that differ.
209+
210+
The status line says what is known about the pair as it becomes known: `reading text` and `drawing` while that happens, which page is showing and which differ, `every page draws the same` when the files differ only where nothing shows, and `documents are identical` when the bytes match. Each pane's header names the page it shows, `received.pdf (page 2 of 5)`, or `(no page 6)` when that side has fewer.
211+
212+
Reading and drawing happen on a thread of their own once the window is up, so a long document never holds a test run waiting on the viewer. Each document is read from a copy the viewer takes, never the file itself, so it cannot hold a lock that stops it being accepted. They run inside the viewer's process: a document that hangs is given up on after two minutes, but one that crashes PDFium or Skia takes the window with it, and with no tray running, any inline snapshots the window was holding.
213+
214+
An SVG is drawn with scripts, external images and external elements turned off. A snapshot is test output, and nothing in one gets to reach the network or the disk.
215+
216+
DiffEngine offers the viewer for `.pdf`, `.docx`, `.xlsx` and `.pptx` only when the copy it resolved carries the folder. The viewer is last in the default tool order, so Word, Excel, Beyond Compare or DeltaWalker are still preferred where installed.
217+
218+
186219
## With DiffEngineTray
187220

188221
The tray starts at login, so it normally binds the port first and holds the queue. The viewer then displays it: it reads the pending snapshots back over the socket and forwards accept and discard rather than applying them. That is what `--attach` is for, and the tray starts one whenever a snapshot arrives with no window open.

‎docs/tray.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ A delete is withdrawn when a later test run verifies against its file again, sin
5959

6060
When the tray holds it, the window becomes disposable. A viewer that is closed, killed or crashes takes nothing pending with it, and the tray opens a new one on the same queue. A snapshot arriving with no window open starts one.
6161

62+
The viewer installed with the tray also reads [documents](/docs/viewer.md#documents): PDF, Word, Excel and PowerPoint files as text, as their pages drawn, or both, and SVGs drawn beside their text. The copy bundled in the DiffEngine package does not. On a machine with the tray installed, test runs resolve the tray's copy ahead of the bundled one, so pending documents open in a viewer that can read them.
63+
6264
"Pending Snapshots" accepts all of them. Clicking one accepts that one, and its drop down offers discard, opening the viewer on it, and opening the source file. A snapshot that failed to apply is marked with `!` and stays pending, so it can be retried once whatever blocked it is out of the way.
6365

6466
Exiting the tray writes any still-pending inline snapshots back to disk, under the source project's `obj/VerifyInline/`, where accept tooling such as [Verify.Terminal](https://github.com/VerifyTests/Verify.Terminal) still finds them. A kill or a crash skips that, and loses the queue as it loses pending moves and deletes; re-run the tests.

0 commit comments

Comments
 (0)