mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Text: table text takes the master's other-text style
PowerPoint for Mac lays out a table's text in the master's other-text style (p:otherStyle), while shapes and text boxes take the presentation's default text style (p:defaultTextStyle). A synthetic deck with an otherStyle of 14 pt and a defaultTextStyle of 26 pt, exported as PDF, draws a table cell at 14 pt and a shape's and a text box's text at 26 pt. DeckCraft laid cells out through a stand-in shape that wasn't a table, so they took the default text style: a deck whose two styles differ (18 and 20 pt) drew its table text 2 pt too large and wrapped its cells. master_list_style sends text in a non-placeholder table to otherStyle, and Shape::table_cell builds the stand-in every cell layout uses (drawing, row heights, text placement, the caret and selection in a cell) as a table. The table shape itself resolves the same way, so the formatting state of a selected table reports the size its cells draw at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
128cb0b883
commit
aa6f45ba2c
@@ -131,6 +131,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-22 | Table text takes the master's other-text style (`otherStyle`), shapes and text boxes the presentation's default text style, as PowerPoint for Mac draws them (synthetic probe: 14 pt cells under a 14 pt `otherStyle` and a 26 pt `defaultTextStyle`); a private deck's table had been drawn 20 pt instead of 18 |
|
||||
| 2026-10-18 | Native chart sidecar bytes move from JSON arrays into referenced ZIP entries, with shared media byte/item limits and bounded reads; older inline sidecars still load. Synthetic format regressions cover large workbooks and malformed members |
|
||||
| 2026-10-17 | Opening a file caps absurd effect sizes (glow, soft edges, shadows, reflections, on shapes, text and table text) at what Format Shape allows: a glow of 10^18 pt in a .pptx made rendering ask for 34 GB and abort |
|
||||
| 2026-10-17 | Opening a .deckcraft file whose shapes repeat an id gives the repeats fresh ids (groups, layouts and masters too), so the deck validates and edits by id find one shape |
|
||||
@@ -245,6 +246,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-15 | trivial | Progress log: PPTX trigger click-step round trip. Feature scores and milestone names unchanged |
|
||||
| 2026-10-16 | trivial | Progress log: PNG export with a transparent background (#784) |
|
||||
| 2026-10-16 | trivial | Progress log: a `.ppsx` from the system starts the slide show (#862) |
|
||||
| 2026-10-22 | trivial | Progress log: table text takes the master's other-text style |
|
||||
| 2026-10-14 | trivial | remote MCP checks reply IDs, envelopes and bounded newline framing (#718) |
|
||||
| 2026-10-14 | minor | Gray list markers on empty paragraphs while editing; caret keeps the list indent before typing |
|
||||
| 2026-10-14 | minor | Community PR wave folded: headline breadth 84% over 191 features (P0 92%, P1 82%, P2 55%) and the feature-area breadths from `cargo xtask parity`; one row per area; Upcoming item 2 merged; per-PR count notes dropped from the progress log. Ready stays ~55%, stage alpha |
|
||||
|
||||
@@ -934,7 +934,7 @@ pub fn cell_range_underlines(
|
||||
return vec![];
|
||||
};
|
||||
let body = deckcraft_render::table_cell_body(&ctx, tb, row, col, cell);
|
||||
let tmp = deckcraft_model::Shape { text: Some(body.clone()), ..Default::default() };
|
||||
let tmp = deckcraft_model::Shape::table_cell(body.clone());
|
||||
let l = deckcraft_text::layout(
|
||||
&ctx,
|
||||
&tmp,
|
||||
@@ -1003,7 +1003,7 @@ fn cell_on_slide(st: &DocState, id: ShapeId, row: usize, col: usize) -> Option<C
|
||||
if body.paragraphs.is_empty() {
|
||||
body.paragraphs.push(deckcraft_model::text::Paragraph::default());
|
||||
}
|
||||
let tmp = deckcraft_model::Shape { text: Some(body.clone()), ..Default::default() };
|
||||
let tmp = deckcraft_model::Shape::table_cell(body.clone());
|
||||
let layout = deckcraft_text::layout(
|
||||
&ctx,
|
||||
&tmp,
|
||||
|
||||
@@ -439,6 +439,11 @@ impl Default for Shape {
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// A stand-in shape for laying out one table cell's `body`: a table, so the cell's text takes
|
||||
/// the master's other-text style as PowerPoint's does.
|
||||
pub fn table_cell(body: text::TextBody) -> Shape {
|
||||
Shape { text: Some(body), kind: ShapeKind::Table(Table::default()), ..Default::default() }
|
||||
}
|
||||
pub fn is_placeholder(&self) -> bool {
|
||||
self.ph.is_some()
|
||||
}
|
||||
|
||||
@@ -100,7 +100,10 @@ pub fn parents<'a>(ctx: &Ctx<'a>, shape: &Shape) -> (Option<&'a Shape>, Option<&
|
||||
}
|
||||
}
|
||||
|
||||
/// The text style group (title / body / other) a shape draws from.
|
||||
/// The text style group (title / body / other) a shape draws from. Text in a table that isn't a
|
||||
/// placeholder takes the master's other-text style, as in PowerPoint for Mac (a table cell is 14 pt
|
||||
/// under an `otherStyle` of 14 pt and a `defaultTextStyle` of 26 pt, while a shape's or a text
|
||||
/// box's text is 26 pt); other shapes take the presentation's default text style.
|
||||
pub fn master_list_style<'a>(ctx: &Ctx<'a>, shape: &Shape) -> &'a ListStyle {
|
||||
match shape.ph_type() {
|
||||
Some(PhType::Title | PhType::CtrTitle) => &ctx.master.title_style,
|
||||
@@ -116,6 +119,7 @@ pub fn master_list_style<'a>(ctx: &Ctx<'a>, shape: &Shape) -> &'a ListStyle {
|
||||
| PhType::ClipArt,
|
||||
) => &ctx.master.body_style,
|
||||
Some(_) => &ctx.master.other_style,
|
||||
None if matches!(shape.kind, crate::ShapeKind::Table(_)) => &ctx.master.other_style,
|
||||
None => &ctx.pres.default_text_style,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,3 +270,23 @@ fn ink_width_follows_pressure_and_keeps_the_old_placeholder() {
|
||||
assert!((floor - 0.64).abs() < 1e-4, "a light touch stays visible, got {floor}");
|
||||
assert!((ink_sample_width(8.0, f32::NAN, false) - 8.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_text_takes_the_masters_other_style_and_shapes_the_default_text_style() {
|
||||
// PowerPoint for Mac, with an otherStyle of 14 pt and a defaultTextStyle of 26 pt: a table
|
||||
// cell's text is 14 pt, a shape's and a text box's 26 pt.
|
||||
let mut p = Presentation::default();
|
||||
let size = |pt: f64| crate::text::LevelStyle { run: RunProps { size: Some(pt), ..Default::default() }, ..Default::default() };
|
||||
p.default_text_style.set(0, size(26.0));
|
||||
std::sync::Arc::make_mut(&mut p.masters[0]).other_style.set(0, size(14.0));
|
||||
let s = p.slides[0].clone();
|
||||
let ctx = Ctx::for_slide(&p, &s).unwrap();
|
||||
let para = Paragraph::default();
|
||||
let pt = |sh: &Shape| resolve::run(&ctx, sh, ¶, &RunProps::default()).size;
|
||||
let body = crate::text::TextBody::from_text("Cell");
|
||||
assert_eq!(pt(&Shape::table_cell(body.clone())), Some(14.0), "a table cell");
|
||||
let table = Shape { kind: ShapeKind::Table(Table::default()), ..Default::default() };
|
||||
assert_eq!(pt(&table), Some(14.0), "the table itself (its formatting state)");
|
||||
assert_eq!(pt(&Shape { text: Some(body.clone()), ..Default::default() }), Some(26.0), "a shape");
|
||||
assert_eq!(pt(&Shape { text: Some(body), text_box: true, ..Default::default() }), Some(26.0), "a text box");
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ fn place(out: &mut Placed, rctx: &Ctx, fields: &SlideFields, s: &Shape, parent:
|
||||
for (r, c, rect) in table::cell_rects(t, &table::row_heights(rctx, t, fields)) {
|
||||
let Some(cell) = t.cell(r, c).filter(|cell| !cell.text.is_empty()) else { continue };
|
||||
let body = table::cell_body(rctx, t, r, c, cell);
|
||||
let tmp = Shape { text: Some(body.clone()), ..Default::default() };
|
||||
let tmp = Shape::table_cell(body.clone());
|
||||
place_text(out, rctx, fields, s.id, &tmp, &body, rect, m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ pub fn row_heights(rctx: &Ctx, t: &Table, fields: &dyn Fields) -> Vec<f64> {
|
||||
let c1 = ci.saturating_add(cell.grid_span.max(1) as usize).min(t.cols.len());
|
||||
let (Some(x0), Some(x1)) = (xs.get(ci), xs.get(c1)) else { continue };
|
||||
let body = cell_body(rctx, t, ri, ci, cell);
|
||||
let tmp = Shape { text: Some(body.clone()), ..Default::default() };
|
||||
let tmp = Shape::table_cell(body.clone());
|
||||
let need = deckcraft_text::fit_height(rctx, &tmp, &body, Rect::new(*x0, 0.0, *x1, h.max(1.0)), fields);
|
||||
if need.is_finite() {
|
||||
h = h.max(need.min(20_000.0));
|
||||
@@ -423,7 +423,7 @@ pub fn draw(
|
||||
continue;
|
||||
}
|
||||
let body = cell_body(rctx, t, *r, *c, cell);
|
||||
let tmp = Shape { text: Some(body.clone()), ..Default::default() };
|
||||
let tmp = Shape::table_cell(body.clone());
|
||||
#[cfg(test)]
|
||||
TEXT_CELLS_DRAWN.with(|n| n.set(n.get() + 1));
|
||||
let mut l = deckcraft_text::layout(rctx, &tmp, &body, &deckcraft_text::Opts { rect: *rect, fields, prompt_color: None, no_shrink: false });
|
||||
|
||||
Reference in New Issue
Block a user