The same rule that highlights a transition direction now highlights an animation's direction and text build.
Co-authored-by: Cursor <cursoragent@cursor.com>
slide.new kept a leading tab as text, while text.set already turned it into a bullet level. The desktop --help flag was opened as a missing file.
Co-authored-by: Cursor <cursoragent@cursor.com>
Every transition and Effect Option was built in PowerPoint (COM), exported as
video and compared frame by frame with DeckCraft at the same progress
(mean abs diff over 150 variants 35.7 -> 15.0, 0-255).
- anim: transition_frame(kind, option, t, aspect) with measured curves
(smoothstep fades, ease-in-out moves, Cover/Uncover/Pan curves), directions
and option meanings, soft edges as per-corner opacity, 3-D effects as
perspective-projected cards, Dynamic Content moving slide contents over
cross-fading backgrounds (Source::OldBack/NewBack/OldContent/NewContent);
layer_mesh subdivides perspective quads so textures stay straight.
- New options: Page Curl Single, Glitter Hexagons, Shred In/Out, Zoom and
Rotate; PowerPoint-style Effect Options labels.
- render: composite_transition (CPU twin of the show), render_slide_part,
render_transition; deckcraft-cli render-transition FILE --slide N --t T.
- show: per-vertex opacity meshes, slide parts rendered once per transition.
- pptx: PowerPoint's elements and defaults (p:strips diagonal wipes, p14:warp
and p:newsflash for Zoom, p:zoom for Shape In/Out, glitter pattern, shred
rectangle/dir, prstTrans invX per preset, pageCurlSingle, defaults for
randomBar/doors/window/pan/zoom); PowerPoint reopens DeckCraft's files with
the same effects.
- Tests: measured curves, probes of which slide shows where at t, option
coverage, per-frame triangle budget, PowerPoint XML and defaults round trip.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A table whose style the file defines (its parts and background drawn
since #292 and #294) edits like the others: the caret is laid out with
the file style's lettering (a regular header where the built-in style
bolds it), a double-click selects the word drawn there, a click moves
the caret to the clicked cell and a border drag resizes the columns
while the cell stays edited.
Tab moved into the cells covered by a merged cell, editing text that is
never drawn. It now goes to the next visible cell; Tab in the last
visible cell still adds a row, and Shift+Tab in the first cell stays.
Hovering a table border shows a resize cursor (new hit kinds
TableColumn and TableRow) and dragging it resizes, one undo step per
drag, as in PowerPoint:
- an inner column border moves between its two columns and the table
keeps its width;
- the right edge widens or narrows the last column and the table;
- a row's bottom border (the last one is the table's bottom edge) sets
that row's height, which stays a minimum: rows still grow to fit
their text.
Columns stop at 12 pt. Borders hidden inside merged cells aren't hit;
the left and top edges still move the table. A border drag while a
cell is edited keeps editing it. The drag goes through
table.columnWidth, which takes a new keepTotal option (the next column
absorbs the change), and table.rowHeight. Hit testing is done in the
table's own frame, so rotated tables resize along their axes.
- A double-click in a cell puts the caret where clicked and selects the
word there, as in shapes (it used to put the caret at the end).
- While a cell is edited, a click in the same table moves the caret to
the clicked cell and position; a drag selects text within the cell
and Shift+click extends only within it.
- A click in an already selected table enters the clicked cell at the
click; dragging the table still moves it.
- The release that follows a double-click no longer re-enters the text
at the click, which collapsed the word just selected (shapes too).
Cells are found from the drawn cell rects, so merged cells and rows
that grew to fit their text are hit where they are drawn.
Editing a table cell outlined the cell but drew no caret or selection,
and Up/Down/Home/End fell back to paragraph moves, because the text
layout used for drawing, hit testing and caret movement only existed
for shapes.
text::text_frame now lays out the edited text as it is drawn and gives
the transform to the slide, for shapes and cells alike: a cell's text is
laid out in its rect (merges honoured) with the cell's margins, anchor
and the table style's look, through the renderer's own cell_body and
cell_rects. layout_for and tools::text_pos use it, and the canvas draws
the caret and selection from it. The cell outline follows merged cells.
The Export dialog had no visible control: the native menu bar that lists
Export… is not installed, so only the command palette reached it. The
quick access row now has an Export button (the existing Export icon)
that opens the export dialog, enabled when a presentation is open.
A right click on an unselected shape opened the slide's context menu
(Layout, Reset Slide...) because only the primary button reached the
pointer tool; the shape had to be left-clicked first. The right-button
press now goes through Session::pointer as PointerKind::ContextClick
(`contextclick` on the control channel and MCP) and selects like
PowerPoint before the menu opens: an unselected shape is selected alone
(a text box or placeholder is not put into text editing, nothing is
dragged), a selected shape keeps the selection, multiple selection
included, and empty slide clears it. In the box whose text is being
edited, the caret and text selection stay, and the context menu offers
text items (Paragraph, Bullets, Numbering, Link) next to Cut, Copy and
Paste, which act on the selected text.
While a text box or placeholder is being edited, every press inside its
box placed the caret, so the box could not be moved without leaving
editing first. Like PowerPoint, a band on either side of the box edge
(the hit tolerance wide, in the shape's rotated frame) is now its frame:
hit_test reports Hit::Frame there, the canvas shows the move cursor, and
a press leaves text editing, selects the box and drags it as one undo
step. A click without a drag just selects the whole box. Inside the box
the caret and text selection work as before; resize and adjust handles
still win. The text-click zone now follows the rotated box instead of
its axis-aligned bounds.
A SmartArt drawing shape keeps its text rectangle in dsp:txXfrm, apart
from the shape's own box. The importer ignored it and laid the text out
in the shape itself. In list layouts the shape is a tall, narrow box
turned by 90 degrees, so the text came out as narrow vertical columns,
in the editor and in the exported PPTX.
The text rectangle uses the shape's coordinate space and is turned by
the shape's rotation plus its own:
- When it is the shape's text area shrunk by some margin, with the same
orientation, the shape keeps its text and the margin is added to its
insets.
- Otherwise (a rectangle reaching outside the shape, or turned against
it) the text moves to a new shape without fill or outline, placed on
the rectangle right above its shape in the diagram's group.
Both export as plain DrawingML (positive insets, an ordinary rotated
shape), which other readers lay out the same way.
Test: a SmartArt drawing built in the test, with a text rectangle wider
than its shape, a turned shape with upright text, and a rectangle
inside its shape; the laid-out text box is checked on import and after
a PPTX round trip.
Upstream ce1c7b7 ("Charts: axis number formats, automatic major unit,
hidden axes") replaced the value-axis scaling this branch changed, and
already draws #144's chart as 0-45 in steps of 5. Take its structure and
API as they are (axis_scale with fixed bounds/unit and a room-based
interval count, Chart::axis_scale(secondary, max_intervals), axis_range,
combo charts' secondary axis) and drop this branch's auto_scale.
On top of it, the Office rules upstream lacked:
- Automatic minimum: zero only when every value is >= 0 and the smallest
is below 5/6 of the largest; otherwise the first major unit at or below
min - (max - min)/20. All-negative data mirrors it (the axis ends at
zero only when the largest is above 5/6 of the smallest). Headroom is
5 % of the data range, as documented. Chart::axis_scale feeds it the
plotted extremes (stacked sums still count from zero, 100 % series are
0-1); axis_range and value_range keep returning zero-inclusive ranges.
- A fixed limit beyond the data makes the automatic end give way instead
of producing an inverted axis.
- The renderer stops values past the axis limits at the plot edge, on
both value axes.
The model tests from this branch are ported to the new signature with
ten intervals of room.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Value axes showed raw numbers ("800000") with a fixed ~5-interval scale and
fixed label gutters, deleted axes were still drawn, and data labels ignored
their number format, so Russian business charts read "516600000" where
PowerPoint shows "516 600 000 ₽".
- New deckcraft_model::numfmt: Excel number format codes (sections for
positive/negative/zero, quoted and escaped literals, _x padding, *x fill,
[$sym-lcid] currency, [colour]/conditions dropped, 0/#/? placeholders,
grouping with ',' or the locale's literal space, trailing-comma scaling,
%, General) with digit grouping and decimal mark from the chart's
language (c:lang: ru-RU -> "800 000", "12,5%").
- Reading: each value axis' numFmt, c:delete, scaling min/max and
majorUnit, for the primary and a combo chart's secondary axis; data
labels' numFmt (series, then group, else the source data's formatCode);
the chart's c:lang. The primary value axis is the one on the left
(bottom) and the secondary the one on the right/top or crossing at max,
whatever the plot groups' order.
- Scale (Chart::axis_scale): fixed bounds and unit where given, else the
data plus 5% headroom rounded out to the smallest 1-2-5 unit that leaves
as many intervals as the axis has room for labels, as PowerPoint does
(0-800 000 by 100 000, 0-7 000 by 1 000 on the sample deck).
- Rendering: labels in the axis format, gutters sized to the longest label,
deleted axes draw no labels and take no room, data labels in their own
format.
A combo chart (columns plus lines, often with the lines on a second value
axis) was read with each series' type but drawn as lines only, on one
scale, so a spend-and-clicks chart showed the clicks as a flat line along
the bottom and no columns.
- Reading: a plot group whose value axis isn't the first group's is on the
secondary axis (Series::secondary_axis, now set).
- Model: Chart::series_kind, on_secondary and axis_range (per axis; a combo
stacks only its stacked series).
- Rendering: bar-like series are drawn as bars (clustered or stacked among
themselves), the others as lines/areas over them, each against its own
axis; the secondary axis' labels go on the right. Line series use their
line's colour and width (where PowerPoint keeps them), also in the legend.
Long category labels wrap onto two lines.
RUSTSEC-2026-0194 (quadratic duplicate-attribute check) and RUSTSEC-2026-0195 (unbounded NsReader
namespace allocation) are fixed in quick-xml 0.41. deckcraft-pptx already reads attributes with
with_checks(false) and does not use NsReader, so neither is reachable today; the bump keeps the
workspace on a supported quick-xml and leaves one copy (wayland-scanner already locks 0.41.0).
decode_and_unescape_value() is deprecated in 0.41 (clippy -D warnings); decoded_and_normalized_value
(Implicit1_0, ...) is what the deprecated method now does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A chart relationship must not be able to replace a slide or another package part when the deck is saved.
Co-authored-by: Cursor <cursoragent@cursor.com>
PowerPoint stores chart data in ppt/embeddings. Saving used to drop that part and the chart relationship, so the deck still rendered but the workbook was gone. An edit still omits the workbook rather than writing numbers that no longer match it.
Co-authored-by: Cursor <cursoragent@cursor.com>
animation.remove's remark belongs to its `ids` option, so describe shows
it there instead of as a note on the whole command. A command with an
all-optional set of parameters is suggested as a bare id.
[claude-opus-5-5] on behalf of hoblin
The font scan also listed faces with none of the tables skrifa reads
glyph outlines from (glyf, CFF, CFF2, VARC): faces whose glyphs are
bitmaps only or are stored as outlines in Apple's hvgl table.
FontDb::outline, which the renderer reads glyph outlines from, returns
an empty path for every glyph of such a face. On macOS 27.0.1 the scan
lists "GB18030 Bitmap" (/System/Library/Fonts/Supplemental/NISC18030.ttf),
which stores its glyphs as bitmaps (bdat) and has no color glyph
tables, so the renderer draws none of the 27,544 characters it maps.
file_face_names now leaves out a face whose table directory has none of
those tables, as vectorcraft's font scan does (storytold/vectorcraft#781).
* Engine: file.info and PowerPoint-style print slide ranges (#76)
file.info reports what File › Info shows (name, path, format, unsaved
changes, slide and hidden-slide counts, sections, size on disk,
properties). file.export's PDF `slides` also takes the range typed in
PowerPoint's Print page ("1-3, 5", 1-based); bad or out-of-range text is
an error instead of a silent full export.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* UI: the File tab (Backstage) like PowerPoint's (#76)
File (first ribbon tab) or Alt+F opens a full-window view in place of the
ribbon, slides and status bar: a side menu (Back, Home, New, Open, Info,
Save, Save As, Print, Export, Close; Account and Options at the bottom)
and the chosen page. Esc or Back returns; Up/Down walk the side menu and
show each page as it gets the focus; Enter runs Save/Close/Options.
Laid out after PowerPoint for Windows as observed: 160-pt menu a step
darker than the page, outline icons, current page in the accent colour.
The start screen is now File › Home (as in PowerPoint), with only Home,
New and Open at the top and no Back. Pages call existing commands and
file helpers: file.new {theme} and the sample deck (Home, New); recent
files with search, Browse and Recover Unsaved (Open); file.info and
file.properties, Accessibility Checker, Revert (Info); file.save /
file.saveAs / file.export by type (Save, Save As); a PDF with the Print
page's slides, layout (full page, notes, handouts 1-9), hidden slides and
frames, opened in the system viewer or downloaded on the web, with a
page preview (Print, Ctrl+P); Create PDF, Create Handouts and Change File
Type (Export); appearance and About (Account); Preferences (Options).
Share (cloud) is left out.
Close and Ctrl+W now ask Save / Don't Save / Cancel about unsaved
changes instead of closing at once. The Export dialog shares
SlideApp::export_as with the Export page. New UI commands: app.backstage
{page}, app.closePresentation, app.print; ui.inspect reports the page.
New icons drawn in code: back, save as, account, recent, new file, open
folder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Docs: File tab (Backstage) in ui-parity, gaps, checklist and progress log (#76)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Outlines, dashes, arrowheads and ink strokes were drawn under the full
transform, including the mapping of the group's child coordinate space onto its
box. A group whose children use small legacy child coordinates (scale ~1600)
therefore stroked 0.75 pt lines about 1200 pt wide and covered the slide. Inside
a group the outline is now carried through that mapping and stroked under the
slide-level view, so the line width, dash lengths and arrowhead sizes stay in
points, also for groups scaled unevenly.
* ActiveX controls show their pictures
A slide's p:controls are read: each control with a p:pic (the image PowerPoint shows for it,
often a WMF) becomes a picture above the slide's shapes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Metafiles draw their text, pattern and hatch brushes
deckcraft-metafile (still kurbo + thiserror only, so it builds for wasm):
- Text: WMF EXTTEXTOUT/TEXTOUT and EMF EXTTEXTOUTW/EXTTEXTOUTA/SMALLTEXTOUT with the
selected font (CREATEFONTINDIRECT, EXTCREATEFONTINDIRECTW: face, signed LOGFONT height,
width, weight, italic, underline, strike-out, escapement, charset), text and background
colour, background mode, TA_* alignment and TA_UPDATECP, the dx array (ETO_PDY pairs too),
ETO_OPAQUE fills and ETO_CLIPPED clips. 8-bit strings decode through their charset
(Windows-1252/1250/1251; symbol fonts keep their codes at U+F000; double-byte sets keep
their advances). The neutral Item::Text carries a text-space-to-picture transform, so the
mapping and escapement are folded in and glyphs are never mirrored.
- Brushes: WMF DIBCREATEPATTERNBRUSH, EMF CREATEDIBPATTERNBRUSHPT and CREATEMONOBRUSH, and
BS_HATCHED, as Item::Pattern tiles in 96-dpi pixels; WMF PATBLT. The XOR / AND-pattern /
XOR see-through shadow of old PowerPoint WMFs draws its pattern in the XOR colour.
- MFCOMMENT escapes with an embedded EMF are ignored: PowerPoint draws the WMF's records.
- Pattern pixels and text count against the byte budget; strings are capped.
deckcraft-render draws text through deckcraft-fonts with the face slide text would use
(document_face), symbol fonts mapped as bullets map them (deckcraft_text::symbol_char, now
public, with Webdings), per-character fallback, the file's advances, GDI cell metrics for
top/bottom alignment, underline/strike-out and synthetic bold/italic. Tiles below 1.5 output
pixels per cell are drawn as their average colour.
On the 24 corpus slides that show a metafile, mean abs diff against PowerPoint goes from
28.31 to 5.06 (with the OLE/ActiveX preview commits).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Text: Ctrl+arrows, Ctrl+Backspace/Delete and the other text keys work like PowerPoint (#268)
Observed black-box in PowerPoint for Windows on synthetic text:
- Word stops (crates/model/src/words.rs): words, punctuation runs, symbol
runs, tabs, line breaks and each CJK character are tokens; trailing spaces
join the token before. it's, a-b, 3.14, 1,000 are one word; e.g., a_b and
x@y.com are not. Grapheme clusters never split.
- Ctrl+Left/Right and Ctrl+Shift+Left/Right move by those stops, across
paragraphs; with a selection they go on from its start/end. In a
right-to-left paragraph Left and Right swap.
- Ctrl+Backspace deletes to the previous stop; Ctrl+Delete deletes the rest
of the word, or spaces plus the next word, then applies PowerPoint's smart
spacing. With a selection both work from its start.
- Ctrl+Up/Down by paragraph, Ctrl+Home/End to the text's ends.
- End on a wrapped line goes past the trailing space and the caret stays
drawn at that line's end (TextSel::line_end); End again stays, Home
returns to the same line.
- Ctrl+]/[ and Ctrl+Shift+>/< change font size (egui's Period/Comma/
bracket key names now match registry shortcuts).
- Shift+F3 cycles lower -> Title -> UPPER; a caret changes its word.
- Double-click selects the word without trailing spaces; triple-click
includes the paragraph mark.
- Mac keeps Option for words and Cmd for line/text ends (Session::mac_keys).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* rustfmt
* Canvas: one caret, drawn at a wrapped line's end after End
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #133: Selection pane engine: restack inside groups, Show/Hide All, Lock like PowerPoint
- arrange.bringForward/sendBackward/bringToFront/sendToBack and arrange.reorder work on a
group's children, which stay in their group (as PowerPoint's pane arrows and drag do);
steps that change nothing leave no undo step.
- selection.select (pane click: Ctrl toggles, children and top-level objects don't mix,
picking a hidden object shows it), selection.showAll (everything, children too) and
selection.hideAll (top level).
- shape.lock cascades through groups; locked objects can be selected, formatted and typed
in, but not moved, sized, rotated, flipped, reshaped or cropped (commands and canvas).
- PPTX: Lock reads and writes PowerPoint's full locking attribute set per element kind
(observed by saving locked objects from PowerPoint); partial locks aren't Lock.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Front-most first with groups folding; drag a row to restack among its siblings (accent
insertion line); Bring Forward / Send Backward arrows; Show All / Hide All; per-row eye and
padlock; double-click or F2 renames in place; Ctrl+click toggles, Shift+click selects a run;
arrows, Home, End and Esc in the list; right-click menu. Lock/Unlock on the canvas menu,
greyed handles on locked objects. egui_kittest tests drive the pane.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Text: input method composition as commands, one undo step per composition (#101)
text.compose shows an input method's preedit in place at the insertion point and replaces it on
each update; an empty preedit cancels and restores the text and selection. text.commit replaces
the composition with the result as one undo step. Any other edit, key or click settles an open
composition first. The composition is underlined under the baseline (thicker under the part being
converted), and the caret line is measured for empty placeholders too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* UI: Korean, Japanese and Chinese input and interface text (#101)
The slide turns the input method on while it takes typing, with the candidate window at the
caret, and forwards egui Ime events to text.compose / text.commit, so a Korean keyboard composes
syllables instead of separate jamo. Interface text in scripts the interface fonts lack (Hangul,
Simplified Chinese, Thai...) borrows faces from the font database (installed fonts such as Malgun
Gothic; nothing bundled): file names, titles, notes, outline, sections, comments and typed text.
Ported and widened from WordCraft's #565/#840 fallback. The notes pane keeps text typed before
focus leaves in the same frame (Escape, another slide). Control channel: ui.ime.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PowerPoint's Normal view draws shapes, pictures and text that sit partly
or wholly off the slide on the area around it, at full strength, with
their effects, master and layout graphics included; the slide show,
exports and thumbnails clip to the slide. DeckCraft clipped everything to
the slide in the editor too.
- render: `slide_extent`/`layout_extent` estimate how far a slide's
drawing reaches (boxes, outlines, shadow, glow, reflection);
`render_slide_drawn` also reports what a render actually drew (text
spilling out of its box, table rows that grew); `pasteboard_region`
picks the pixels the editor renders: the slide plus what reaches off
it and is in view, rounded to 256 px tiles so scrolling rarely
re-renders. A slide with nothing off it renders exactly as before.
- ui-egui: the canvas texture covers that region (slide and master
views); it re-renders only when the slide, zoom or the region changes,
and a second time only if text drew beyond the estimate.
- Hit testing, selection, marquee and moves already worked in slide
space; tests now cover them off the slide.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict in crates/fonts/src/lib.rs: both sides added tests at the same
place; all three are kept. #322 independently serialised font loading in
try_family (loaded_families). This branch's loading lock and batched
add_fonts stay for the system-fallback path, but load_family no longer
skips a family with some face loaded: a lookup by a Windows family name
loads only that style's file, and the family's other files must load
later (windows_family_names_find_their_faces).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Text: a missing font is drawn in Calibri, as PowerPoint does
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fonts: Windows family names find their faces; PowerPoint's twins for missing fonts
"Segoe UI Semibold", "Arial Black" and the like are the faces' Windows (GDI) family
names; a family's installed files now load together; Liberation Sans and Serif draw as
Arial and Times New Roman when missing, as PowerPoint draws them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fonts: Office cloud fonts (Aptos…) keep their open substitute
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The caret was always stroked black. On a dark slide (the Nocturne theme,
or any dark background with light text) it could not be seen. It is now
drawn in the colour that text typed at the caret takes, and falls back to
the theme token when that text has no fill.
An empty placeholder showed no caret at all: its body holds no paragraph
until something is typed, so the edit layout had no line for the caret to
stand on. The edit layout now treats such a body as one empty paragraph,
and the canvas leaves the placeholder's prompt out while it is being typed
into, so the caret does not sit inside "Click to add title".
Fixes#318.
lineInv, nonIsoscelesTrapezoid, pieWedge, the left-right / up-down / quad
arrow callouts, circularArrow / leftCircularArrow / leftRightCircularArrow,
the four curved arrows, swooshArrow, callout1-3, accentCallout1-3,
borderCallout2-3, accentBorderCallout1-3, ellipseRibbon / ellipseRibbon2,
leftRightRibbon, flowChartOfflineStorage, gear6 / gear9, funnel, the corner
tabs (corner, square, plaque) and chartX / chartStar / chartPlus no longer
fall back to plain rectangles.
Each outline is our own construction in crates/geom/src/preset/extra.rs,
fitted black-box against PowerPoint renders of synthetic probe decks at
default, changed and extreme adjust values and several aspect ratios
(outline within ~1pt on 300-480pt shapes). Tests cover catalog presence,
hostile sizes/adjusts and measured outline points.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Text warps: draw WordArt prstTxWarp presets, round-trip their adjusts
All 40 DrawingML text-warp presets now render instead of drawing flat text.
A warp is a list of bands over the shape's text rectangle (insets ignored):
fill bands stretch the ink of their lines between a top and a bottom guide
(positions by arc length, one horizontal scale for the whole text), follow
bands (arch, circle, button) set lines at natural size along an ellipse arc,
lines stacking outwards on parallel curves, with no wrapping. Lines are
shared over multi-band presets in order (ceil(n/k) per band).
The guide curves were fitted to PowerPoint renders of synthetic decks of
"I" bars (black-box observation, defaults and changed adjusts, 1-3 lines);
most presets match within about a point on a 420 x 200 pt box.
- geom: warp module (guides, presets, band mapping) with unit tests
- render: glyph outlines and underlines flattened, subdivided and mapped
- text: follow warps lay out without wrapping
- model/pptx: BodyProps.warp_adj read from and written to prstTxWarp/avLst
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Text warps: bands with fewer lines keep the fuller band's text height
Multi-band fill warps (Deflate-Inflate, Button Pour) map a box as tall as
the tallest band's ink onto every band, centred on the band's own ink, as
PowerPoint does: a band holding one line next to a band of two keeps the
line at the same size instead of stretching it over the whole band.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>