Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +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 or PDF: the board is cut into areas where it is empty, one slide or page per area, with an overview first. A deck carries the text containers in the speaker notes, or, as Editable slides, puts images and text containers on the slide as objects with the ink over them; a PDF has a bookmark per page and can put the whole board on one page to zoom into. 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
- **File → Export** to PowerPoint or PDF: the board is cut into areas where it is empty, one slide or page per area, with an overview first. A deck carries the text containers in the speaker notes, or, as Editable slides, puts images and text containers on the slide as objects with the ink over them; a PDF has a bookmark per page, can put the whole board on one page to zoom into, and as Vector pages carries the ink as paths and the text as selectable text. 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
13 changes: 8 additions & 5 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,13 +591,16 @@ a long-lived MIT library, both managed-only, so they pass the tests decision 21
dependency. The writers live in a new assembly, `SQLBI.Whiteboard.Export`, with no WPF
dependency, which the signing step lists.

A PDF page is also a picture, for now. PDF has one freedom a slide lacks, a page of any
A PDF page is a picture by default. PDF has one freedom a slide lacks, a page of any
size, and the export uses it: the whole board can go on one page the shape of the board,
rendered at up to six thousand pixels on the longer edge, for a reader who zooms. Pages get
a bookmark each and a footer with the board name, date, and page number. Vector pages,
with selectable text and ink as paths, are a later phase, and so is Print through
Microsoft Print to PDF, which was weighed as the zero-dependency route and kept for later
because the driver, not the application, asks for the file name.
a bookmark each and a footer with the board name, date, and page number. **Vector** is the
other page content: ink as paths that sweep the nib WPF uses along each stroke, text as
text in embedded subsets of Segoe UI and Consolas, images as images, so the page stays
sharp at any zoom and the code can be selected. It is the same element list the editable
deck uses, with the ink as strokes in their own z-order rather than one overlay. Print
through Microsoft Print to PDF was weighed as the zero-dependency route and kept for
later, because the driver, not the application, asks for the file name.

---

Expand Down
29 changes: 29 additions & 0 deletions docs/export-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,32 @@ which phase made it and why.
exported as a placeholder.
- **The picture is still rendered for an editable page** and kept as the page's own
picture in the model, so a page can fall back to it; PowerPoint does not receive it.

## E4 — Vector PDF

- **Page content is a choice on the dialog, Picture or Vector**, for PDF only, with
Picture the default for the same reason as the deck: exact beats best effort until
someone needs to select text.
- **The vector page reuses the editable slide's elements**, with one difference: the ink
goes out as strokes in their own z-order between the containers, not as one overlay,
because a path costs nothing to layer correctly.
- **A stroke is the sweep of its nib.** For each pair of points the writer fills the
convex hull of the two nib polygons, which is exactly the area a convex nib sweeps
along a segment, and joins every hull into one path filled once with the non-zero rule,
so a translucent stroke does not darken where its own segments overlap. The nibs follow
WPF: a pen is an ellipse of diameter thickness × 2 × pressure (pressure 0.5 is the
nominal width, which is also why a mouse draws at an even width), a highlighter a flat
rectangle four thicknesses wide and two tall with no pressure, calligraphy a tall bar
three thicknesses high and 0.65 wide following pressure.
- **An opaque highlighter is drawn at half opacity**, which is how WPF renders an ink
highlighter whose color has no alpha; the board's highlighter swatches are opaque.
- **Text is laid out line by line in the writer**: paragraphs on line breaks, runs
measured left to right, wrapped at the last space that fits and then at a character, in
the language's font at the size the board used. It approximates WPF's layout rather than
reproducing it, so a line may wrap a character earlier or later than on screen.
- **Fonts are embedded as subsets** from the Windows fonts folder: Segoe UI and Consolas,
with Arial and Courier New standing in when they are missing. Segoe UI and Consolas
both allow editable embedding, which covers a subset in a document. Any other family
a future language service names falls back to Segoe UI until it is added to the
resolver.
- **The overview page stays a picture** in both modes; it is a map, not content.
2 changes: 1 addition & 1 deletion site/guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ <h3>Files</h3>
<p>The <span class="ui">File</span> row holds five commands: <span class="ui">New</span>, <span class="ui">Open</span>, <span class="ui">Save</span>, <span class="ui">Save As</span>, and <span class="ui">Export</span>. <kbd>Ctrl</kbd><span class="plus">+</span><kbd>S</kbd> and <kbd>Ctrl</kbd><span class="plus">+</span><kbd>O</kbd> save and open a <code>.wboard</code> without opening the row at all.</p>
<p><span class="ui">Export</span> (<kbd>Ctrl</kbd><span class="plus">+</span><kbd>E</kbd>) writes the board as a PowerPoint 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, 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 the <span class="ui">Gap between areas</span> slider and <span class="ui">Smallest text on a slide</span> change the cut as you move them. <span class="ui">Drawing order</span> puts slides in the order the areas were begun; <span class="ui">Reading order</span> goes top-left to bottom-right.</p>
<p><span class="ui">Slide content</span> chooses between <span class="ui">Picture</span>, one exact picture of the area, and <span class="ui">Editable</span>, which puts images and text containers on the slide as PowerPoint objects, keeps the DAX and SQL colors as text runs, and lays all the ink over them as one transparent picture. Editable is for a deck that will be reworked; Picture is for one that will be shown.</p>
<p>Choose <span class="ui">PDF</span> in the same dialog for a document instead: the same areas, one per A4 or Letter page in landscape, each with a bookmark, and a footer with the board name, the date, and the page number. <span class="ui">Whole board on one page</span> writes a single page the shape of the board, to be read by zooming, which suits a board that is one drawing.</p>
<p>Choose <span class="ui">PDF</span> in the same dialog for a document instead: the same areas, one per A4 or Letter page in landscape, each with a bookmark, and a footer with the board name, the date, and the page number. <span class="ui">Whole board on one page</span> writes a single page the shape of the board, to be read by zooming, which suits a board that is one drawing. <span class="ui">Page content</span> chooses between <span class="ui">Picture</span>, exact, and <span class="ui">Vector</span>, which draws the ink as paths and the text as text, so the page stays sharp at any zoom and DAX or SQL can be selected and copied from it.</p>
<p>The released installer registers that type and <code>.wimport</code>. The pre-release (Dev) channel does not, so it can sit beside a released copy without stealing the file association.</p>

<figure class="fig">
Expand Down
21 changes: 21 additions & 0 deletions src/SQLBI.Whiteboard.Core/Settings/ExportSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ public enum ExportSlideAspect
Standard = 1,
}

public enum ExportPageContent
{
/// <summary>
/// One picture of the area per page, exactly as the screen draws it.
/// </summary>
Picture = 0,

/// <summary>
/// Ink as paths, text as text, images as images: selectable, and sharp
/// at any zoom.
/// </summary>
Vector = 1,
}

public enum ExportSlideContent
{
/// <summary>
Expand Down Expand Up @@ -75,6 +89,8 @@ public sealed class ExportSettings

public ExportPageSize PageSize { get; set; } = ExportPageSize.A4;

public ExportPageContent PageContent { get; set; } = ExportPageContent.Picture;

/// <summary>
/// The board name, the date, and "n of m" at the foot of every PDF page.
/// </summary>
Expand Down Expand Up @@ -118,6 +134,11 @@ public static ExportSettings Normalize(ExportSettings? settings)
result.SlideContent = ExportSlideContent.Picture;
}

if (!Enum.IsDefined(result.PageContent))
{
result.PageContent = ExportPageContent.Picture;
}

result.GapThreshold = double.IsFinite(result.GapThreshold)
? Math.Clamp(
result.GapThreshold,
Expand Down
31 changes: 31 additions & 0 deletions src/SQLBI.Whiteboard.Export/ExportPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ public sealed record SlideTextElement(
uint BorderArgb,
uint TextArgb) : SlideElement(Bounds);

public enum SlideStrokeKind
{
/// <summary>An ellipse nib whose diameter follows pressure.</summary>
Pen = 0,

/// <summary>A flat rectangle nib four times wider than tall, with no pressure.</summary>
Highlighter = 1,

/// <summary>A tall rectangle nib, three times taller than the thickness and 0.65 of it wide, following pressure.</summary>
Calligraphy = 2,
}

public readonly record struct SlidePoint(double X, double Y, float Pressure);

/// <summary>
/// One stroke in page pixels. Thickness is the nominal width, in page pixels, at
/// the pressure WPF treats as normal (0.5): the nib is Thickness × 2 × pressure across
/// for the kinds that follow pressure. Argb keeps its alpha; a highlighter is translucent.
/// </summary>
public sealed record SlideStroke(
IReadOnlyList<SlidePoint> Points,
uint Argb,
double Thickness,
SlideStrokeKind Kind);

/// <summary>
/// Ink as vector strokes, for a writer that can draw them. The PowerPoint writer does
/// not: it takes the ink as an image element instead.
/// </summary>
public sealed record SlideInkElement(SlideRect Bounds, IReadOnlyList<SlideStroke> Strokes) : SlideElement(Bounds);

/// <summary>16:9 or 4:3.</summary>
public enum SlideAspect
{
Expand Down
Loading