Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .azure/pipelines/build-whiteboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ stages:
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.exe"
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.Core.dll"
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.Dax.dll"
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.Export.dll"
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.SqlServer.dll"
"$(Build.BinariesDirectory)\SQLBI.Whiteboard.ThumbnailHandler.dll"
failOnStderr: true
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ broke. The heading is parsed by `scripts/release-notes.ps1`, so keep its shape;
under it is ordinary Markdown, and the renderer handles paragraphs, lists, links, `code`
and **bold**.

## 1.3.0 - 3 September 2026

### Export a board to PowerPoint
**File → Export** (Ctrl+E) turns the board into a deck. The board is cut into areas
wherever it is empty: a picture or a note keeps the ink drawn on it, and a stroke that
spans two of them keeps them together. Each area becomes a slide, drawn exactly as the
screen draws it, with the text containers in the speaker notes so that DAX and SQL can be
copied, and an overview slide first shows where every slide sits on the board. The dialog
previews the areas, numbered, and updates as you move the gap and text-size settings.
[How areas are chosen](https://github.com/sql-bi/SQLBI-Whiteboard/blob/main/docs/export.md)
is written down, with the alternatives.

## 1.2.2 - 2 September 2026

### The Eraser, for a pen that has none
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
scripts/build-installer.ps1 both read it from here, so releasing is a reviewed change
to this line rather than an edit in a pipeline variable group.
-->
<VersionPrefix>1.2.2</VersionPrefix>
<VersionPrefix>1.3.0</VersionPrefix>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ How the project is developed and shipped is documented separately:
- Undo and redo for strokes, erasing, containers, text edits, and transformations
- Versioned ZIP-based `.wboard` documents with an embedded `preview.png`. Explorer shows that picture as the file thumbnail in the released install. The VS Code extension in `vscode/sqlbi-whiteboard` opens the same picture instead of the ZIP.
- Markdown `.wimport` recipes that build image and text containers from headings
- **File → Export** to PowerPoint: the board is cut into areas where it is empty, one slide per area with the text containers in the speaker notes and an overview slide first. The dialog shows the areas numbered on the board and updates as the settings change. [docs/export.md](docs/export.md) explains how areas are chosen
- An intentionally small floating toolbar
- A File / Edit / View / Help tab strip. Click a tab for a one-row command strip over the canvas
- Preferences for the startup monitor, full-screen start, finger drawing, mouse drawing, the pen button, snippet format order, laser trail timing and weight, toolbar position and layout, keeping the Eraser on the toolbar for a pen that has no reverse end, and (except Store installs) a daily new-version check
Expand Down Expand Up @@ -202,6 +203,7 @@ Use **Copy settings** after finding a useful combination so the exact values can
| Escape | Cancel the active text edit, close the command strip, or leave full screen or canvas only |
| Ctrl+S / Ctrl+O | Save / open a board |
| Shift+F12 | Save As |
| Ctrl+E | Export the board to PowerPoint |
| Delete | Delete the selected container and its linked strokes |
| Alt+L | Laser pointer |
| File / Edit / View / Help | Tab strip. Click a tab for a one-row command strip over the canvas. Click the canvas to hide it |
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The delivery chain works end to end: a merge to `main` builds, signs, and publis
pre-release to GitHub Releases, and one approval promotes that same build to a release.
<https://whiteboard.sqlbi.com> reads its download links from the release manifest
deployed beside it and needs no edit per release. The current product version is `VersionPrefix` in `Directory.Build.props`
(1.2.2). Identity version for the Store package is `VersionPrefix.0` (`1.2.2.0`).
(1.3.0). Identity version for the Store package is `VersionPrefix.0` (`1.3.0.0`).

Declaring that number is decision 20 in [docs/decisions.md](docs/decisions.md). What 1.0
was waiting on shipped during 0.9.x: Preferences, `.wimport`, Explorer and VS Code
Expand Down
83 changes: 83 additions & 0 deletions Whiteboard.sln
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
Expand All @@ -16,39 +17,121 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLBI.Whiteboard.Calligraph
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLBI.Whiteboard.ThumbnailHandler", "src\SQLBI.Whiteboard.ThumbnailHandler\SQLBI.Whiteboard.ThumbnailHandler.csproj", "{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLBI.Whiteboard.Export", "src\SQLBI.Whiteboard.Export\SQLBI.Whiteboard.Export.csproj", "{EE24F5C6-7C34-4613-8C82-DF34418E2B03}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|x64.ActiveCfg = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|x64.Build.0 = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Debug|x86.Build.0 = Debug|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|x64.ActiveCfg = Release|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|x64.Build.0 = Release|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|Any CPU.Build.0 = Release|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|x86.ActiveCfg = Release|Any CPU
{2FBD8067-C77F-4F5B-87C5-314423EE9C9D}.Release|x86.Build.0 = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|x64.Build.0 = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Debug|x86.Build.0 = Debug|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|x64.ActiveCfg = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|x64.Build.0 = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|Any CPU.Build.0 = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|x86.ActiveCfg = Release|Any CPU
{48A8D0B7-3C3F-4B16-92CD-3A8B7352F7E1}.Release|x86.Build.0 = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|x64.ActiveCfg = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|x64.Build.0 = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|x86.ActiveCfg = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Debug|x86.Build.0 = Debug|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|x64.ActiveCfg = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|x64.Build.0 = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|Any CPU.Build.0 = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|x86.ActiveCfg = Release|Any CPU
{B2B15228-5085-4652-B7C7-F82F3781A486}.Release|x86.Build.0 = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|x64.ActiveCfg = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|x64.Build.0 = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|x86.ActiveCfg = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Debug|x86.Build.0 = Debug|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|x64.ActiveCfg = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|x64.Build.0 = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|Any CPU.Build.0 = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|x86.ActiveCfg = Release|Any CPU
{53EF7855-88C7-4D6A-A194-4AF36CF10615}.Release|x86.Build.0 = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|x64.Build.0 = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|x86.ActiveCfg = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Debug|x86.Build.0 = Debug|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|x64.ActiveCfg = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|x64.Build.0 = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|Any CPU.Build.0 = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|x86.ActiveCfg = Release|Any CPU
{C9300257-0FA9-47E5-B719-F8C957FE5427}.Release|x86.Build.0 = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|x64.ActiveCfg = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|x64.Build.0 = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|x86.ActiveCfg = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Debug|x86.Build.0 = Debug|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|x64.ActiveCfg = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|x64.Build.0 = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|Any CPU.Build.0 = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|x86.ActiveCfg = Release|Any CPU
{B45B0BC9-F1F4-4F2F-A71D-9F73C7935512}.Release|x86.Build.0 = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|x64.ActiveCfg = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|x64.Build.0 = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|x86.ActiveCfg = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Debug|x86.Build.0 = Debug|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|x64.ActiveCfg = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|x64.Build.0 = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|Any CPU.Build.0 = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|x86.ActiveCfg = Release|Any CPU
{D4E8A1C0-7B52-4F19-9E6A-3C81D0F4A257}.Release|x86.Build.0 = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|x64.ActiveCfg = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|x64.Build.0 = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|x86.ActiveCfg = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Debug|x86.Build.0 = Debug|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x64.ActiveCfg = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x64.Build.0 = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|Any CPU.Build.0 = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x86.ActiveCfg = Release|Any CPU
{EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EE24F5C6-7C34-4613-8C82-DF34418E2B03} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
33 changes: 33 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,39 @@ else.

---

## 26. Export cuts the board where it is empty

**Implemented for PowerPoint.** The plan, with the alternatives, is
[export.md](export.md); the calls made while building it are in
[export-decisions.md](export-decisions.md).

A `.wboard` is one unbounded plane with no notion of a page, so exporting it to a deck is
first a question of where the slides are. The answer taken is the recursive whitespace
cut: a container and the strokes linked to it are one unit, every other stroke is a unit
of its own, and a region is cut at the widest empty band between the units' projections
on either axis, recursively, until it fits a slide or no band is wider than a threshold.
Nothing can be cut through, a stroke that spans two containers keeps them together, and
the cut tree gives a reading order. Bottom-up clustering produces nearly the same areas and
still needs the cut for anything larger than a slide; a grid splits objects; manual frames
on the board are the long-term answer for someone preparing a deck and are a later phase,
because they change the file format.

"Fits a slide" is defined by the smallest text the person will accept: a text container's
body is 18 world units, a 16:9 slide is 960 points wide, so 12-point text caps an area at
1440 world units. The same cap applies to areas with no text. An area that cannot be cut is
scaled down and the dialog says by how much, rather than being tiled across slides.

The slide is a picture rendered by the same `BoardSurface` that draws the screen, at twice
full HD, so calligraphy, the highlighter, and SVG come out as they are seen. The text
containers go in the speaker notes so DAX and SQL can still be copied. An editable deck,
with native text boxes and images, is a later phase.

`DocumentFormat.OpenXml` writes the file: Microsoft's own SDK, MIT, managed-only, so it
passes the tests decision 21 set for a dependency. The writer lives in a new assembly,
`SQLBI.Whiteboard.Export`, with no WPF dependency, which the signing step lists.

---

## Open questions

- arm64 is not built; add it if Surface devices matter for a pen application.
Expand Down
52 changes: 52 additions & 0 deletions docs/export-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Export: calls made while implementing

The plan in [export.md](export.md) was approved as written, with its recommendations
taken as decisions. This file lists every further call made during implementation that
was not spelled out there, so that each can be reviewed and reversed. Each entry says
which phase made it and why.

## Branches and pull requests

- One branch per phase, each branched from the previous one because each phase builds on
the last: `feature/export-e1-powerpoint`, `feature/export-e2-pdf`,
`feature/export-e3-editable-deck`, `feature/export-e4-vector-pdf`,
`feature/export-e5-frames`. Merge them in that order; after each squash merge, the next
pull request's base has to be retargeted to `main`.
- The product version was not bumped and no CHANGELOG entry was written. Deciding when a
release ships is not part of implementing a feature, and the Release notes check only
fires when `VersionPrefix` changes. The changelog lines are ready to paste from the
pull request descriptions.

## E1 — PowerPoint

- **Ctrl+E** is the shortcut, and **E** the mnemonic on the File strip (N, O, S, A were
taken; Alt+E still opens the Edit tab because Alt always selects a tab). Both places in
the key handler that know about Ctrl+S learned Ctrl+E.
- **The Export command sits at the end of the File row**, after Save As, with the same
icon treatment as the rest.
- **The dialog owns the whole flow**: preview, settings, the save dialog, progress, and
the error box. `MainWindow` only refreshes LiveView snapshots, hands over the document,
the settings, the live-frame provider, the title resolver, and the current path, and
refuses to open the dialog for an empty board.
- **Settings are remembered on Cancel too.** Someone who tuned the threshold and left
will want it back next time, and nothing about the tuning is a commitment.
- **Text container titles come from the language service**, so a DAX container is named
"DAX Code of Sales Amount" exactly as on screen. The partitioner in Core only sees the
stored title; the application passes a resolver. Image areas are named after the image's
original file name without extension, LiveViews after their source.
- **The dominant container is the largest one by area**, ties broken by creation order.
- **The picture on the slide keeps the area's own aspect ratio** and is fitted into the
slide's picture box, rather than being cropped or stretched to 16:9. The bitmap is
rendered at up to 3840×2160 (twice a full-HD slide).
- **The overview slide appears only when there are at least two areas** and the page
model is one per area; with a single area it would repeat the only slide.
- **A whole-board export names its one slide after the board file** (or "Untitled board").
- **The gap slider snaps to 10 world units**, from 20 to 400, with 80 as default.
- **Smallest text offers 9, 12, and 14 points**, as the plan said; a settings file with
another value falls back to 12.
- **The export is written to `<name>.pptx.tmp` and moved into place**, so a failure part
way through never leaves a truncated deck.
- **The new assembly is `SQLBI.Whiteboard.Export`** (net10.0, no WPF), and it is listed in
the pipeline's signing step. The smoke test project references it to open the produced
deck, which means the core-only verification now restores the Open XML SDK package; it
still needs no WPF.
Loading