High-resolution and free-spin wheels on Windows send fractions of a line
(e.g. 0.125 per event); rounding each one up turned a single notch into
several slides. The fractional line delta now accumulates in ui.data temp
state, resets when the direction changes, and each whole line is one
slide. slide.go is skipped when the target is the current slide.
Test: eight 0.125-line events turn exactly one slide, and a reversal
drops the leftover fraction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
A shape whose text has no anchor of its own (`<a:bodyPr/>`) was given the
anchor of the theme's shape defaults (`a:objectDefaults/a:spDef/a:bodyPr`),
which Office's themes set to `ctr`, so its text was centred in the box.
PowerPoint for Mac draws such text at the top: a synthetic deck with an
spDef anchor="ctr" and three rectangles (`<a:bodyPr/>`, anchor="t",
anchor="ctr"), exported as PDF, puts the first two at the top. The spDef
defaults are for shapes inserted later.
Opening a file now anchors every shape without an anchor at the top, as
the standard has it; the model's middle default is still written as ctr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shape_at folds a 3-D camera's turn into the shape's transform, so
per_point() measured its foreshortening as if it were a group's
child-coordinate scale: in a 1:1 group, a shape under a turned camera
got a factor other than 1 and drew its extrusion depth, contour and
bevel at the wrong size. The factor is now measured on the transform
before the turn (m with the camera's affine taken back out), and the
turned transform still draws the geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group's child coordinates scale its shapes' boxes, but extrusion
depths, contour widths and bevel sizes are points, as PowerPoint draws
them. The renderer applied them in the child space, so the group's scale
multiplied them: a 1 pt contour on a small extruded cube in a legacy
group (1360 child units across a 61 pt group) was drawn hundreds of
points wide, a band across the whole slide, and the cube's extrusion ran
off towards the vanishing point.
per_point() gives the shape-space units per point (the inverse of the
child-coordinate scale that line_space already separates for line
widths); the extrusion depth, contour width and bevel width and height
are converted with it before drawing. Outside a group it is 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paragraph properties now go only onto paragraphs the selection covers
whole, so partly covered end paragraphs and the paragraph after a
triple-click keep their bullets. Double and triple clicks apply the
painter only when they changed the text selection.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reuse Slides… used to insert every slide of the picked presentation. It now opens a list
of that presentation's slides (number and title, all ticked), with Select All / Select None
and a Keep source formatting option, and inserts only the ticked ones after the current
slide. New query slide.reuseList {path | data} gives the list; slide.reuse already took
`slides`.
Co-Authored-By: Claude <noreply@anthropic.com>
The media.* commands look the shape id up on the editor's current slide, so
the request has to be carried out against that slide too. Passing the show's
slide instead could play another slide's clip under a repeated id.
Co-Authored-By: Claude <noreply@anthropic.com>
The resize handles sit in the box's unflipped frame, so in a flipped box
(also one that a drag past the opposite edge just flipped) the dragged
handle is on the other side. Snapping looked at, and moved, the fixed
edge instead.
Co-Authored-By: Claude <noreply@anthropic.com>
The slide thumbnail pane and the Slide Sorter asked the texture cache for a
thumbnail on every row, in view or not, three or four new ones a frame. The
cache keeps at most 600 thumbnails and drops the 200 oldest beyond that, so
on a deck of more than 600 slides the pane evicted its own thumbnails before
it reached the end: it kept rendering, the rows in view went white again and
again, and it never finished.
Now only rows within the scroll area's clip (plus two rows of margin) ask for
a thumbnail and keep the pane repainting; the others are still laid out, so
scroll height, section headers, clicks and drag-to-reorder are unchanged.
Small decks behave as before.
Co-Authored-By: Claude <noreply@anthropic.com>
# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
# pick 03fce2a Thumbnails: only render the slides in view (#969)
# No commands remaining.
# You are currently rebasing.
#
# Changes to be committed:
# modified: ROADMAP.md
# modified: crates/ui-egui/src/huge_deck_tests.rs
# modified: crates/ui-egui/src/sorter.rs
# modified: crates/ui-egui/src/thumbs.rs
#
The carry-over check looked the clip up again on the previous slide. If
that slide held the same media under the same id without Play Across
Slides, the voice stopped one slide later. A slide without the id fell
back to the first match in the deck. Use the clip the voice was started
with; the next slide is only checked for another clip under that id.
Co-Authored-By: Claude <noreply@anthropic.com>
snap_resize ran snap_rect on the whole box, which looks at all three
edges on each axis and takes the nearest line. A fixed edge already on a
line (distance 0) always won, so the dragged edge never snapped; a fixed
edge near a line moved the dragged edge by its own distance. Resizing now
looks only at the dragged edges (snap_edges), also for the grid fallback.
Co-Authored-By: Claude <noreply@anthropic.com>
# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
# pick 92df608 Canvas: resizing snaps the edge being dragged (#961)
# Next command to do (1 remaining command):
# pick abb0e09 Canvas: a flipped box snaps the edge under the dragged handle (#961)
# You are currently rebasing.
#
# Changes to be committed:
# modified: ROADMAP.md
# modified: crates/engine/src/tests.rs
# modified: crates/engine/src/tools.rs
#
A clip set to play across slides kept its voice even when the next slide
holds a different clip under the same shape id, so that slide's own clip
never started. Keep the voice only when the next slide has no clip under
that id or the same media.
Co-Authored-By: Claude <noreply@anthropic.com>
The PPTX import numbers shapes per slide, so two slides can hold different
videos under one shape id. find_clip searched the whole deck and took the
first match, so the show's media cues, the keep-across-slides filter and
the media.* requests all got the first slide's clip: from slide 3 on, video 2
played. find_clip now takes the slide the request comes from and searches it
first, then the rest of the deck.
Co-Authored-By: Claude <noreply@anthropic.com>
# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
# pick 1ab070b Media: look a clip up on the slide it belongs to (#994)
# Next commands to do (3 remaining commands):
# pick ad4e3f0 Media: stop a clip across slides when the next slide reuses its id (#994)
# pick 0957b58 Media: the playing clip decides whether it carries over (#994)
# You are currently rebasing.
#
# Changes to be committed:
# modified: ROADMAP.md
# modified: crates/ui-egui/src/lib.rs
# modified: crates/ui-egui/src/media.rs
# modified: crates/ui-egui/src/show.rs
#
Found while testing crates/model/src/numfmt.rs:204-211 (sci)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/text.rs:1219 (set), via crates/model/src/text.rs:605 (TextBody::from_text)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Keep blank whole-format General fallback while hiding values selected by explicit empty sections. Pin section selection, signed zeros, semicolon boundaries and General controls with regressions. Contribution on behalf of @MMexicann.
A report on 0.7.0 said the Options dialog's Cancel did nothing, and a
reproduction on 0.8.0 found it working for the appearance toggle and the
name field, without trying the other controls. This covers the rest: the
Editing checkboxes (snap to grid, smart quotes, autocorrect) and the name
field go back with Cancel, like the first checkbox does.
Verified red with the restore of the settings taken out: the name keeps
what was typed.
Adobe symbol.txt also maps 0xA0 to the euro sign (U+20AC), which
symbol_unicode lacked. Apple's Symbol maps a few codes to different
Unicode than Adobe (0xA4 to U+2044, 0xE1/0xF1 to U+3008/U+3009), so
symbol_font_draw now tries those alternatives after the Adobe character
when the installed face doesn't cover it. The Adobe aliases for
0x44/0x57/0x6D (U+2206/U+2126/U+00B5) are not Apple alternatives and
stay out.
symbol_font_char masked every bullet character to its low byte before
checking direct coverage, so a 'bullet' (U+2022) bullet drew an
unrelated Symbol code. It now maps only symbol-font codes (low byte or
U+F0xx) and falls back to the original character when the face covers
it.
Found while testing crates/format/src/lib.rs:660-665 (slides_to_outline) and :573-574 (outline_to_slides)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The painter only applied when a shape was clicked. It now also applies
when a text range is selected by drag, double-click or triple-click.
Paragraph formatting (bullets, alignment) is copied only onto
paragraphs the selection covers whole.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/pptx/src/opc.rs:283-285 (resolve)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/slide.rs:234 (delete); same pattern in index_of :182, move_slide :269, duplicate :193 and section_add :534
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The accepting thread dropped failed accepts with incoming().flatten()
and asked again at once. When accepting keeps failing, for example while
the process has no file descriptors left, that loop keeps a core busy
until it recovers.
Wait 100 ms after a failed accept. The loop goes through a small helper
so the wait can be tested without running out of file descriptors.
The AppImage step moved usr/share/doc out of the AppDir, so the AppImage
shipped without the licence files the .deb, .rpm and .tar.gz carry from
the same staged tree. DeckCraft.app only got the craft-fonts licences, and
no licence at all when built without CRAFT_FONTS_DIR.
Keep usr/share/doc in the AppDir, and copy the docs into the app bundle
with copy_docs, as the CLI zip does, before signing.
Found while testing crates/model/src/chart.rs:1129-1135 (histogram_bins)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The reader stored a series-level `c:dLbls/c:showVal` in
`Series.data_labels`, but every drawing path tested only the chart-wide
`Chart.data_labels`: a series that asked for labels got none, and a
series that asked for none got them. The series' own setting now wins,
and a bare `<c:showVal/>` (no `val` attribute) means true, as the
schema's default.
The plot group's own setting is respected too: a group that asks for no
labels keeps them off, even when another group of a combo chart asks for
them. Before, any group that asked turned the chart-wide setting on, and
the group that said no inherited it.
Tests: `crates/pptx/tests/chart_data_labels.rs` - a bare series
`showVal` shows that series' labels while its siblings stay quiet; an
explicit `val="0"` on the first series keeps it quiet under a chart-wide
on; a combo chart keeps each group's own setting; all three settings
round-trip. Red-proof: with the series-level setting ignored, the first
two fail; with the group's setting ignored, the combo chart's bar group
shows labels it asked to keep quiet.
The model gains `Chart::series_data_labels`, the one place that decides
whether a series' labels show; the render paths (bar, column, line,
area, pie, doughnut, radar, scatter) call it.
The Options dialog applies every change as it is made, and closing it
with Cancel, Esc or the window's close button puts the settings back the
way they were. Nothing covered that, so a change to the dialog could have
dropped it unnoticed.
The kittest now covers all three ways out: a checkbox put back by Cancel,
the appearance and the interface size put back by Esc, the same checkbox
put back by the window's close button, and the dialog closing in each
case. Red-proof: with the Cancel restore removed the checkbox test fails
(the setting stays changed), with the Esc restore removed the appearance
test fails (it stays dark), and with the Esc/close restore removed the
close-button test fails (the setting stays changed).
ROADMAP progress-log row added.
The fix changed the empty-placeholder fill gate, but nothing locked the
behaviour in: a quick style's fill has to appear on an empty placeholder,
and a placeholder with no style at all still has to stay empty.
The new render test draws an empty body placeholder in the editor with a
quick style and checks that the accent fill is painted - every channel of
it - and that the same placeholder without a style stays white. With the
gate reverted to `s.fill.is_none()` alone the first assertion fails - the
placeholder stays white - so the test is red on the unfixed code.
Two gaps in the resize cursor, both found by a review of the fix:
- The handle's direction was taken from the box centre to the handle, so a
wide rectangle's corner pointed almost along an edge: a 400x80 shape's NW
corner showed a horizontal cursor. A corner drags along the diagonal
whatever the shape's proportions, so the direction is now normalised -
the diagonal at 45 degrees - and only the shape's rotation and flips
turn it.
- The cursor read the shape's own box, but the handles are drawn from the
effective one, which a placeholder inherits from its layout. Such a
placeholder showed the default arrow instead of a resize cursor.
The tests cover both: a wide shape keeps its corner cursors diagonal, and
a placeholder that inherits its box has a resize cursor. Red-proof: with
the aspect ratio back in the direction, the wide shape's NW corner shows
a horizontal cursor; with the shape's own box, the placeholder shows the
default arrow.
set_read_timeout applies to each read, so a peer that sent a byte just
before every timeout expired could hold the connection thread, and its
slot, for roughly twice the grace. Set the remaining time before each
read instead.