Skip to content

Write shapes, labels, and connectors into the deck and the PDF as objects - #138

Merged
marcosqlbi merged 6 commits into
mainfrom
feature/native-design-export
Sep 17, 2026
Merged

marcosqlbi merged 6 commits into
mainfrom
feature/native-design-export

Conversation

@marcosqlbi

@marcosqlbi marcosqlbi commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Shapes, labels, and connectors reached the editable deck and the vector PDF as pixels: #130, #132, and #131 each drew their objects into the transparent overlay picture so that nothing vanished from an export. That was the holding position the plan named in decision 10. This is the sixth pull request of the 1.6.0 plan, which replaces it: a shape leaves as a PowerPoint shape and as a PDF path, a label as a text box and as text on the page, and a connector as a PowerPoint connector and as a stroked path with a filled head. A deck can now be recoloured and typed into, and a PDF's labels can be selected and searched.

What it affects: the editable deck and the vector PDF only. A picture slide, a raster page, preview.png, the Explorer thumbnail, and Ctrl+C all still go through BoardSurface and are what they were. The overlay picture is written as a single predicate in EditableSlide.NeedsOverlay, which lists the kinds that have no element yet; after this it is the ink alone, and only on the path where the writer cannot draw strokes, so a vector page emits no overlay at all. main was merged in rather than rebased onto, and the overlay filter was the one conflict.

PowerPoint

A shape becomes a p:sp with the preset whose outline is the one the board draws, an a:solidFill carrying a:alpha for a tinted fill or a:noFill, and an a:ln with the width in EMU, a round join, and round caps. The adjust values are worked back from the box, because a preset measures them against its shorter side while the board's fractions are of the box — except the block arrow's shaft, which the preset measures against the height.

Board object Written as
Rounded rectangle roundRect, adj 20000
Ellipse ellipse
Triangle triangle
Pentagon pentagon
Block arrow rightArrow, adj1 the shaft fraction, adj2 the head from the box
Parallelogram parallelogram, adj the slant from the box
Diamond diamond
Stadium roundRect, adj 50000
Label p:sp with a:xfrm rot, wrap="none", autofit off, insets zero, anchor top-left
Line and Arrow connector p:cxnSp with straightConnector1, flipH / flipV for its direction
Curved arrow p:sp with an a:custGeom holding the board's own cubic
Arrow and Curved arrow head a:tailEnd type="triangle" w="med" len="med"

A label's runs carry b, i, u="sng", sz in hundredths of a point, a:solidFill, and a:latin typeface, one paragraph per line. Element order on the slide follows z-order, as it already did for pictures and text containers.

flowChartTerminator was the other candidate for the stadium and was not taken: its ends are not semicircles on a box wider than it is tall, while a rounded rectangle whose corners have taken the whole of its shorter side is exactly what ShapeGeometry draws. curvedConnector3 was the other candidate for the curved arrow and was not taken either: it leaves its ends along the box's own axes whatever it is bound to, while the board's curve leaves along the side it is bound to, so the two bend differently often enough to be noticed.

PDF

A shape is one path built from ShapeGeometry.Describe — the description the screen is drawn from — with straight parts as lines and curved ones as cubic Béziers of a quarter turn at most, so a circle is a circle at any zoom rather than the 64-gon the hit test walks. It is filled with the tint through PDFsharp's alpha state and stroked with the outline. A label is text under a matrix turned about the label's centre, its lines spread over the height the board measured them at, underlined by a stroked line at the face's own underline position and thickness. A connector is ConnectorGeometry.LinePath stroked with round caps and ConnectorGeometry.Arrowhead filled — the same two calls BoardSurface makes, in slide pixels — so the page and the screen agree about where the line stops and how big the head is.

PDFsharp's Core build resolves no fonts by itself, so the writer already read Segoe UI and Consolas from the Windows fonts folder and embedded them; that resolver now knows every family a label offers, so a label's own face is embedded rather than replaced. The standard-14 mapping the task offered as the alternative is not needed.

Label font Embedded faces On a Windows without it
Segoe UI segoeui, segoeuib, segoeuii, segoeuiz Arial
Calibri calibri, calibrib, calibrii, calibriz Arial
Arial arial, arialbd, ariali, arialbi Arial
Georgia georgia, georgiab, georgiai, georgiaz Times New Roman
Times New Roman times, timesbd, timesi, timesbi Times New Roman
Consolas consola, consolab, consolai, consolaz Courier New
Cascadia Mono Consolas, as above Courier New
Comic Sans MS comic, comicbd, comici, comicz Arial
Segoe Print segoepr, segoeprb, and segoepr again for italic Arial
anything else Segoe UI, as the page furniture already does Arial

Decisions the plan did not settle

  • The curved arrow is a freeform shape, not a connector. A p:cxnSp carrying a geometry of its own is legal but not something PowerPoint writes itself, and nobody here can open the file to find out how it takes it; a freeform with a cubic is exactly how PowerPoint stores a hand-drawn curve. A curve with no connection sites has nothing to re-route to either way. The straight kinds stay real connectors.
  • The block arrow's shaft adjust is a fraction of the height, not of the shorter side. rightArrow pins adj1 at 100000, and that maximum is a shaft as thick as the shape, which only reads as the height; every other adjust here does go through the shorter side. A tall arrow is the case the two readings differ on, and it is in the list below.
  • Connections are not written. A connector bound to a shape on the board could carry stCxn / endCxn to the shape's id on the slide, and would then follow it when the shape is dragged in PowerPoint. It needs the writer's ids inside the element, so it is a follow-up rather than this pull request.
  • Cascadia Mono is written in Consolas. Windows ships it only as CascadiaMono.ttf, a variable font whose bold and italic are axes rather than files, so embedding it would give a regular face for all four styles. Recorded in docs/export.md.
  • Segoe Print has no italic file, so an italic Segoe Print label is upright on the page. Upright reads better than a sloped substitute from another family.
  • Curves rather than the flattened outline for a shape in the PDF; but a connector's curve goes out as the same 32-segment flattening the board draws, because that polyline is what the arrowhead shortening is measured along and what the screen shows.
  • A label's rectangle travels unturned, with the angle beside it, and each writer turns it about the centre. That is what the board does, and the only form both a:xfrm rot and a PDF text matrix take without undoing a rotation first.
  • ConnectorGeometry gained Controls and Flatten, both public, so the writers use the board's own curve instead of deriving one. Polyline is now written in terms of them.
  • A flat curve keeps a path to scale against. A curved connector between two side midpoints that face each other has all four of its cubic's points on one line, so the box around them has no height; the freeform's own path extents are clamped to one EMU, and the harness carries that curve.
  • The mappings are asserted through the output rather than through a new public surface: the Core harness reopens the .pptx as a ZIP and reads slide1.xml for the preset names, the corner adjust that tells a stadium from a rounded rectangle, rot="2700000", u="sng", the flips, the two tail ends, and the cubic; and it reads the produced PDF for a base font per family, accepting the stand-in face where a Windows has no such family. The WPF harness checks what the board hands the writers for a board with one shape of each kind, a turned bold underlined label, a bound curved arrow, and a free straight line.

To try by hand

  • Export a board with all eight shapes, filled and unfilled, as an Editable deck. Open it in PowerPoint: each shape should be a shape, selectable, recolourable, and resizable. Compare the stadium and the rounded rectangle with the board, and a tall block arrow with a wide one.
  • Click a shape in PowerPoint and type. The text body is there for it, and the text lands inside the shape.
  • Check a label in the deck: the words, the font, the size, bold, italic, and underline, and that a 45° or 135° label is turned the way it is on the board and sits where it sits.
  • Check the connectors: a straight arrow that runs right to left should point the same way it does on the board, and the curved one should bend the way it bends on the board. Both heads should be there.
  • Export the same board as a Vector PDF. In a reader, select a label's text and copy it; search for a word in it. Zoom to 800% and look at the ellipse and the stadium for polygon flats, and at an arrowhead for a line poking through its tip.
  • A label in each of the nine fonts, at 45°, bold and underlined, on both paths.
  • A shape drawn over ink, and ink drawn over a shape: the deck puts the ink overlay above every container, shapes and labels included, which is the known cost of the picture overlay and is now what docs/export.md says.

🤖 Generated with Claude Code

marcosqlbi and others added 4 commits September 17, 2026 13:37
An editable slide now hands the writers a shape and a label as objects: the box
and the kind, and the layout rectangle before the turn with the angle beside it.
The picture over the page is left with what no element carries, which is the ink,
and only where the writer cannot draw strokes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PowerPoint gets a shape with the preset geometry whose outline is the one the
board draws, its adjust values worked back from the box, a fill that keeps its
translucency, and a label as a text box turned about its own centre. The PDF gets
the same outline as a path, with the curved parts as cubic Beziers, and a label as
text under a turned matrix. The font resolver learns the families a label can be
written in, so the faces are embedded rather than replaced by the default one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The WPF harness builds an editable slide for a board with one shape of each kind
and a turned, bold, underlined label, and checks what it carries and what is left
for the picture. The Core harness opens what the writers produce: a preset per
kind in the slide XML, the corner adjust that tells a stadium from a rounded
rectangle, the rotation and the runs of a label, and a PDF that embeds a face for
every font a label offers. The export document gains the two mapping tables.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The overlay filter is the one conflict: main widened both lambdas for
connectors, and this branch had already replaced them with a single predicate.
The predicate keeps the connector, so nothing vanishes from an export until the
next commit gives it an element of its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A connector leaves as its own element: where it runs, what draws it, and, for a
curve, the control points of the cubic, so neither writer works the curve out a
second time. PowerPoint gets a connector with straightConnector1 and the flips
that turn it, or a freeform holding the cubic, either way with a triangle tail
end; the PDF strokes the path the board strokes, stopped at the base of its own
arrowhead, and fills the head. The overlay picture is now the ink alone.

Two corrections that came out of a review of the shapes: the block arrow's shaft
adjust is a fraction of the height rather than of the shorter side, which is what
the preset measures it against, so a tall arrow is an arrow again; and the font
check accepts the stand-in face a Windows without a family embeds, which is the
resolver working rather than failing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@marcosqlbi marcosqlbi changed the title Write shapes and labels into the deck and the PDF as objects Write shapes, labels, and connectors into the deck and the PDF as objects Sep 17, 2026
A curved connector between two side midpoints that face each other has all four
of its points on one line, so the box around them has no height and the freeform
path had nothing to scale against. The path's own extents never reach zero now,
and the harness carries that curve.

The font check wrote every label into one document, where Arial and Times New
Roman are named for their own sake and so answered for every family that falls
back to them. It is a document per font now, so a face that quietly fell back to
the page's own is a failure again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@marcosqlbi
marcosqlbi merged commit 6eb66ef into main Sep 17, 2026
5 checks passed
@marcosqlbi
marcosqlbi deleted the feature/native-design-export branch September 17, 2026 12:11
marcosqlbi added a commit that referenced this pull request Sep 17, 2026
)

Pull request 7 of the 1.6.0 plan in `docs/design-objects.md`: the
release cut. The five
features are on `main` — the grid (#134), selection by area (#135), text
labels (#136),
shapes and the Insert tab (#137), and connectors (#139) — and none of
them touched the
prose, so this is where a person is told what they got. **#138, the
native editable export,
must merge before this one**: the release notes and the guide both say
shapes and labels
leave as objects in a deck and a PDF, which is true once it lands.

`VersionPrefix` goes 1.5.2 → 1.6.0, so `CHANGELOG.md` gains the matching
section and the
**Release notes** check applies to this pull request.
`./scripts/release-notes.ps1 -Mode
Verify -Version 1.6.0` passes, and `dotnet build Whiteboard.sln -c
Release` is clean.

## What it affects

- **`CHANGELOG.md`** — one `## 1.6.0 - 17 September 2026` section with
three entries, which
is the shape of 1.5.0 (3), 1.5.1 (1) and 1.5.2 (2): the Insert tab with
shapes,
connectors and text, selection by area, and the grid. The toolbar
preference is one
sentence inside the first entry rather than a fourth entry, because it
is where somebody
meets it. The first entry also carries the one thing a person deciding
whether to upgrade
has to know: a board holding any of the new objects does not open in
1.5.x.
- **`README.md`** — three bullets in *Included in the application*,
seven rows in
*Controls*, the Preferences lists in the bullet and in the table, and
the containers
paragraph, which now says shapes and labels are containers and that a
shape is picked up
  by its outline.
- **`site/guide.html`** — a new *Shapes, connectors, and text* section
after *Text and
code*, a paragraph on area selection under *Containers*, one sentence on
the grid under
  *Navigation*, and the Insert tab named in the command-strip section.
- **`site/shortcuts.html`** — Insert, Lasso and Grid rows, the rubber
band and Ctrl-to-add,
and the Alt-mnemonic row extended with Alt+I, Edit L, View G, and Insert
T.
- **`site/index.html`** — two feature tiles, shapes/connectors/text and
select by area.
- **`docs/decisions.md`** — decision 31, why design objects are ordinary
board objects,
why a shape is taken by its outline, and why the grid is an application
preference. The
rest stays in `docs/design-objects.md`, whose status line is now shipped
in 1.6.0.
- **`TODO.md`** — the current version, which still said 1.3.0, and what
1.6.0 left out.

## Readings taken, since nobody could be asked

- **`site/compare.html` is untouched.** It has no row saying Whiteboard
lacks shapes, text,
a grid or multi-select; the nearest, *Ink and objects*, is a statement
about strokes
travelling with a container and is still true. The brief said to change
such cells and
  nothing else, so nothing else changed.
- **`site/faq.html` is untouched.** No answer on it became wrong.
- ***Architecture* in `README.md` is untouched.** The `Core` bullet
names no types — it
says "world geometry, camera math, retained board objects, commands, hit
testing" — so
there was no list for `ShapeGeometry`, `ConnectorGeometry` and
`RotatedRectangle` to join,
  and the brief said to leave it alone in that case.
- **Escape is an edited row, not a new one**, in both the README table
and the shortcut
  list: Escape already had one, and a second would contradict it.
- **`site/changelog.html` is untouched**, as it is generated from
`CHANGELOG.md` at
  deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 20, 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.

1 participant