Merge Shapes: a text box merges as its letters; a picture keeps its place (#653)

This commit is contained in:
Nikita Rakitin
2026-10-14 05:07:18 +03:00
parent 457406a7a5
commit ef858b710f
3 changed files with 174 additions and 28 deletions
+2 -2
View File
@@ -49,7 +49,7 @@ pub use extent::{Region, TILE, layout_extent, pasteboard_region, slide_extent};
pub use images::{decode as decode_image, gif_frame};
pub use morph_path::PathMorph;
pub use morph_text::TextMorph;
pub use placed::{Placed, PlacedLink, PlacedText, place_slide};
pub use placed::{Placed, PlacedLink, PlacedText, place_slide, text_outline};
pub use table::row_heights as table_row_heights;
pub use transition::{SlideParts, composite_transition, render_transition};
@@ -1850,7 +1850,7 @@ thread_local! {
static GLYPH_CACHE: std::cell::RefCell<HashMap<(u32, u32), Arc<BezPath>>> = std::cell::RefCell::new(HashMap::new());
}
fn glyph(face: &deckcraft_fonts::FontFace, gid: u32) -> Arc<BezPath> {
pub(crate) fn glyph(face: &deckcraft_fonts::FontFace, gid: u32) -> Arc<BezPath> {
let key = (face.id(), gid);
if let Some(p) = GLYPH_CACHE.with(|c| c.borrow().get(&key).cloned()) {
return p;
+29
View File
@@ -123,3 +123,32 @@ fn run_link(body: &TextBody, para: usize, at: usize) -> Option<Hyperlink> {
}
None
}
/// The outlines of shape `s`'s text as drawn, in the space `m` maps shape-local points to, and
/// the colour of its first visible run: what Merge Shapes turns letters into. Empty when the
/// shape has no text.
pub fn text_outline(rctx: &Ctx, s: &Shape, m: Affine) -> (kurbo::BezPath, Option<deckcraft_color::Rgba>) {
let mut out = kurbo::BezPath::new();
let Some(body) = s.text.as_ref().filter(|b| !b.is_empty()) else { return (out, None) };
let x = resolve::xfrm(rctx, s);
let tr = shape_geometry(s, x.w, x.h).text_rect;
let layout = deckcraft_text::layout(rctx, s, body, &Opts { rect: tr, fields: &deckcraft_text::NoFields, prompt_color: None, no_shrink: false });
let m = crate::text_transform(m, tr, layout.rotation);
let mut first = None;
for run in &layout.runs {
let k = run.size / run.face.upem.max(1.0);
let skew = if run.fake_italic { Affine::new([1.0, 0.0, -0.2, 1.0, 0.0, 0.0]) } else { Affine::IDENTITY };
for (gid, gx, gy) in &run.glyphs {
let path = crate::glyph(&run.face, *gid);
if path.elements().is_empty() {
continue;
}
if first.is_none() && run.color.a > 0 {
first = Some(run.color);
}
let at = m * crate::glyph_place(run, *gid, *gx, *gy) * skew * Affine::scale(k);
out.extend(path.elements().iter().map(|e| at * *e));
}
}
(out, first)
}