Skip to main content

typst_library/layout/
frame.rs

1//! Finished documents.
2
3use std::fmt::{self, Debug, Formatter};
4use std::sync::Arc;
5
6use typst_syntax::Span;
7use typst_utils::{LazyHash, Numeric};
8
9use crate::foundations::Label;
10use crate::introspection::{Location, Tag};
11use crate::layout::{Abs, Axes, FixedAlignment, Point, Size, Transform};
12use crate::model::Destination;
13use crate::text::TextItem;
14use crate::visualize::{Color, Curve, FixedStroke, Geometry, Image, Paint, Shape};
15
16/// A finished layout with items at fixed positions.
17#[derive(Default, Clone, Hash)]
18pub struct Frame {
19    /// The size of the frame.
20    size: Size,
21    /// The baseline of the frame measured from the top. If this is `None`, the
22    /// frame's implicit baseline is at the bottom.
23    baseline: Option<Abs>,
24    /// The items composing this layout.
25    items: Arc<LazyHash<Vec<(Point, FrameItem)>>>,
26    /// The hardness of this frame.
27    ///
28    /// Determines whether it is a boundary for gradient drawing.
29    kind: FrameKind,
30}
31
32/// Constructor, accessors and setters.
33impl Frame {
34    /// Create a new, empty frame.
35    ///
36    /// Panics if the size is not finite.
37    #[track_caller]
38    pub fn new(size: Size, kind: FrameKind) -> Self {
39        assert!(size.is_finite());
40        Self {
41            size,
42            baseline: None,
43            items: Arc::new(LazyHash::new(vec![])),
44            kind,
45        }
46    }
47
48    /// Create a new, empty soft frame.
49    ///
50    /// Panics if the size is not finite.
51    #[track_caller]
52    pub fn soft(size: Size) -> Self {
53        Self::new(size, FrameKind::Soft)
54    }
55
56    /// Create a new, empty hard frame.
57    ///
58    /// Panics if the size is not finite.
59    #[track_caller]
60    pub fn hard(size: Size) -> Self {
61        Self::new(size, FrameKind::Hard)
62    }
63
64    /// Sets the frame's hardness.
65    pub fn set_kind(&mut self, kind: FrameKind) {
66        self.kind = kind;
67    }
68
69    /// Sets the frame's hardness builder-style.
70    pub fn with_kind(mut self, kind: FrameKind) -> Self {
71        self.kind = kind;
72        self
73    }
74
75    /// Whether the frame is hard or soft.
76    pub fn kind(&self) -> FrameKind {
77        self.kind
78    }
79
80    /// Whether the frame contains no items.
81    pub fn is_empty(&self) -> bool {
82        self.items.is_empty()
83    }
84
85    /// The size of the frame.
86    pub fn size(&self) -> Size {
87        self.size
88    }
89
90    /// The size of the frame, mutably.
91    pub fn size_mut(&mut self) -> &mut Size {
92        &mut self.size
93    }
94
95    /// Set the size of the frame.
96    pub fn set_size(&mut self, size: Size) {
97        self.size = size;
98    }
99
100    /// The width of the frame.
101    pub fn width(&self) -> Abs {
102        self.size.x
103    }
104
105    /// The height of the frame.
106    pub fn height(&self) -> Abs {
107        self.size.y
108    }
109
110    /// The vertical position of the frame's baseline.
111    pub fn baseline(&self) -> Abs {
112        self.baseline.unwrap_or(self.size.y)
113    }
114
115    /// Whether the frame has a non-default baseline.
116    pub fn has_baseline(&self) -> bool {
117        self.baseline.is_some()
118    }
119
120    /// Set the frame's baseline from the top.
121    pub fn set_baseline(&mut self, baseline: Abs) {
122        self.baseline = Some(baseline);
123    }
124
125    /// Remove the frame's natural baseline. This might be needed after
126    /// applying a certain transformation that would invalidate the baseline
127    /// position, in such a way that the ideal new position is not clear.
128    pub fn clear_baseline(&mut self) {
129        self.baseline = None;
130    }
131
132    /// The distance from the baseline to the top of the frame.
133    ///
134    /// This is the same as `baseline()`, but more in line with the terminology
135    /// used in math layout.
136    pub fn ascent(&self) -> Abs {
137        self.baseline()
138    }
139
140    /// The distance from the baseline to the bottom of the frame.
141    pub fn descent(&self) -> Abs {
142        self.size.y - self.baseline()
143    }
144
145    /// An iterator over the items inside this frame alongside their positions
146    /// relative to the top-left of the frame.
147    pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> {
148        self.items.iter()
149    }
150}
151
152/// Insert items and subframes.
153impl Frame {
154    /// The layer the next item will be added on. This corresponds to the number
155    /// of items in the frame.
156    pub fn layer(&self) -> usize {
157        self.items.len()
158    }
159
160    /// Add an item at a position in the foreground.
161    pub fn push(&mut self, pos: Point, item: FrameItem) {
162        Arc::make_mut(&mut self.items).push((pos, item));
163    }
164
165    /// Add multiple items at a position in the foreground.
166    ///
167    /// The first item in the iterator will be the one that is most in the
168    /// background.
169    pub fn push_multiple<I>(&mut self, items: I)
170    where
171        I: IntoIterator<Item = (Point, FrameItem)>,
172    {
173        Arc::make_mut(&mut self.items).extend(items);
174    }
175
176    /// Add a frame at a position in the foreground.
177    ///
178    /// Automatically decides whether to inline the frame or to include it as a
179    /// group based on the number of items in it.
180    pub fn push_frame(&mut self, pos: Point, frame: Frame) {
181        if self.should_inline(&frame) {
182            self.inline(self.layer(), pos, frame);
183        } else {
184            self.push(pos, FrameItem::Group(GroupItem::new(frame)));
185        }
186    }
187
188    /// Insert an item at the given layer in the frame.
189    ///
190    /// This panics if the layer is greater than the number of layers present.
191    #[track_caller]
192    pub fn insert(&mut self, layer: usize, pos: Point, item: FrameItem) {
193        Arc::make_mut(&mut self.items).insert(layer, (pos, item));
194    }
195
196    /// Add an item at a position in the background.
197    pub fn prepend(&mut self, pos: Point, item: FrameItem) {
198        self.insert(0, pos, item);
199    }
200
201    /// Add multiple items at a position in the background.
202    ///
203    /// The first item in the iterator will be the one that is most in the
204    /// background.
205    pub fn prepend_multiple<I>(&mut self, items: I)
206    where
207        I: IntoIterator<Item = (Point, FrameItem)>,
208    {
209        Arc::make_mut(&mut self.items).splice(0..0, items);
210    }
211
212    /// Add a frame at a position in the background.
213    pub fn prepend_frame(&mut self, pos: Point, frame: Frame) {
214        if self.should_inline(&frame) {
215            self.inline(0, pos, frame);
216        } else {
217            self.prepend(pos, FrameItem::Group(GroupItem::new(frame)));
218        }
219    }
220
221    /// Filters out items in the frame in-place.
222    pub fn retain(&mut self, mut filter: impl FnMut(&mut FrameItem) -> bool) {
223        Arc::make_mut(&mut self.items).retain_mut(|(_, item)| filter(item));
224    }
225
226    /// Whether the given frame should be inlined.
227    fn should_inline(&self, frame: &Frame) -> bool {
228        // We do not inline big frames and hard frames.
229        frame.kind().is_soft() && (self.items.is_empty() || frame.items.len() <= 5)
230    }
231
232    /// Inline a frame at the given layer.
233    fn inline(&mut self, layer: usize, pos: Point, frame: Frame) {
234        // Skip work if there's nothing to do.
235        if frame.items.is_empty() {
236            return;
237        }
238
239        // Try to just reuse the items.
240        if pos.is_zero() && self.items.is_empty() {
241            self.items = frame.items;
242            return;
243        }
244
245        // Try to transfer the items without adjusting the position.
246        // Also try to reuse the items if the Arc isn't shared.
247        let range = layer..layer;
248        if pos.is_zero() {
249            let sink = Arc::make_mut(&mut self.items);
250            match Arc::try_unwrap(frame.items) {
251                Ok(items) => {
252                    sink.splice(range, items.into_inner());
253                }
254                Err(arc) => {
255                    sink.splice(range, arc.iter().cloned());
256                }
257            }
258            return;
259        }
260
261        // We have to adjust the item positions.
262        // But still try to reuse the items if the Arc isn't shared.
263        let sink = Arc::make_mut(&mut self.items);
264        match Arc::try_unwrap(frame.items) {
265            Ok(items) => {
266                sink.splice(
267                    range,
268                    items.into_inner().into_iter().map(|(p, e)| (p + pos, e)),
269                );
270            }
271            Err(arc) => {
272                sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e)));
273            }
274        }
275    }
276}
277
278/// Modify the frame.
279impl Frame {
280    /// Remove all items from the frame.
281    pub fn clear(&mut self) {
282        if Arc::strong_count(&self.items) == 1 {
283            Arc::make_mut(&mut self.items).clear();
284        } else {
285            self.items = Arc::new(LazyHash::new(vec![]));
286        }
287    }
288
289    /// Adjust the frame's size, translate the original content by an offset
290    /// computed according to the given alignments, and return the amount of
291    /// offset.
292    pub fn resize(&mut self, target: Size, align: Axes<FixedAlignment>) -> Point {
293        if self.size == target {
294            return Point::zero();
295        }
296        let offset =
297            align.zip_map(target - self.size, FixedAlignment::position).to_point();
298        self.size = target;
299        self.translate(offset);
300        offset
301    }
302
303    /// Move the baseline and contents of the frame by an offset.
304    pub fn translate(&mut self, offset: Point) {
305        if !offset.is_zero() {
306            if let Some(baseline) = &mut self.baseline {
307                *baseline += offset.y;
308            }
309            for (point, _) in Arc::make_mut(&mut self.items).iter_mut() {
310                *point += offset;
311            }
312        }
313    }
314
315    /// Move the contents of the frame without changing the baseline.
316    pub fn translate_visual(&mut self, offset: Point) {
317        if !offset.is_zero() {
318            for (point, _) in Arc::make_mut(&mut self.items).iter_mut() {
319                *point += offset;
320            }
321        }
322    }
323
324    /// Hide all content in the frame, but keep metadata.
325    pub fn hide(&mut self) {
326        self.retain(|item| match item {
327            FrameItem::Group(group) => {
328                group.frame.hide();
329                !group.frame.is_empty()
330            }
331            FrameItem::Tag(_) => true,
332            _ => false,
333        });
334    }
335
336    /// Add a background fill.
337    pub fn fill(&mut self, fill: impl Into<Paint>) {
338        self.prepend(
339            Point::zero(),
340            FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()),
341        );
342    }
343
344    /// Arbitrarily transform the contents of the frame.
345    pub fn transform(&mut self, transform: Transform) {
346        if !self.is_empty() {
347            self.group(|g| g.transform = transform);
348        }
349    }
350
351    /// Clip the contents of a frame to a clip curve.
352    ///
353    /// The clip curve can be the size of the frame in the case of a rectangular
354    /// frame. In the case of a frame with rounded corner, this should be a
355    /// curve that matches the frame's outline.
356    pub fn clip(&mut self, clip_curve: Curve) {
357        if !self.is_empty() {
358            self.group(|g| g.clip = Some(clip_curve));
359        }
360    }
361
362    /// Add a label to the frame.
363    pub fn label(&mut self, label: Label) {
364        self.group(|g| g.label = Some(label));
365    }
366
367    /// Set a parent for the frame. As a result, all elements in the frame
368    /// become logically ordered immediately after the given location.
369    pub fn set_parent(&mut self, parent: FrameParent) {
370        if !self.is_empty() {
371            self.group(|g| g.parent = Some(parent));
372        }
373    }
374
375    /// Wrap the frame's contents in a group and modify that group with `f`.
376    fn group<F>(&mut self, f: F)
377    where
378        F: FnOnce(&mut GroupItem),
379    {
380        let mut wrapper = Frame::soft(self.size);
381        wrapper.baseline = self.baseline;
382        let mut group = GroupItem::new(std::mem::take(self));
383        f(&mut group);
384        wrapper.push(Point::zero(), FrameItem::Group(group));
385        *self = wrapper;
386    }
387}
388
389/// Tools for debugging.
390impl Frame {
391    /// Add a full size aqua background and a red baseline for debugging.
392    pub fn mark_box(mut self) -> Self {
393        self.mark_box_in_place();
394        self
395    }
396
397    /// Debug in place. Add a full size aqua background and a red baseline for debugging.
398    pub fn mark_box_in_place(&mut self) {
399        self.insert(
400            0,
401            Point::zero(),
402            FrameItem::Shape(
403                Geometry::Rect(self.size).filled(Color::TEAL.with_alpha(0.5)),
404                Span::detached(),
405            ),
406        );
407        self.insert(
408            1,
409            Point::with_y(self.baseline()),
410            FrameItem::Shape(
411                Geometry::Line(Point::with_x(self.size.x))
412                    .stroked(FixedStroke::from_pair(Color::RED, Abs::pt(1.0))),
413                Span::detached(),
414            ),
415        );
416    }
417
418    /// Add a green marker at a position for debugging.
419    pub fn mark_point(&mut self, pos: Point) {
420        let radius = Abs::pt(2.0);
421        self.push(
422            pos - Point::splat(radius),
423            FrameItem::Shape(
424                Geometry::Curve(Curve::ellipse(Size::splat(2.0 * radius)))
425                    .filled(Color::GREEN),
426                Span::detached(),
427            ),
428        );
429    }
430
431    /// Add a green marker line at a position for debugging.
432    pub fn mark_line(&mut self, y: Abs) {
433        self.push(
434            Point::with_y(y),
435            FrameItem::Shape(
436                Geometry::Line(Point::with_x(self.size.x))
437                    .stroked(FixedStroke::from_pair(Color::GREEN, Abs::pt(1.0))),
438                Span::detached(),
439            ),
440        );
441    }
442}
443
444impl Debug for Frame {
445    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
446        f.write_str("Frame ")?;
447        f.debug_list()
448            .entries(self.items.iter().map(|(_, item)| item))
449            .finish()
450    }
451}
452
453/// The hardness of a frame.
454///
455/// This corresponds to whether or not the frame is considered to be the
456/// innermost parent of its contents. This is used to determine the coordinate
457/// reference system for gradients.
458#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
459pub enum FrameKind {
460    /// A container which follows its parent's size.
461    ///
462    /// Soft frames are the default since they do not impact the layout of
463    /// a gradient set on one of its children.
464    #[default]
465    Soft,
466    /// A container which uses its own size.
467    ///
468    /// This is used for pages, blocks, and boxes.
469    Hard,
470}
471
472impl FrameKind {
473    /// Returns `true` if the frame is soft.
474    pub fn is_soft(self) -> bool {
475        matches!(self, Self::Soft)
476    }
477
478    /// Returns `true` if the frame is hard.
479    pub fn is_hard(self) -> bool {
480        matches!(self, Self::Hard)
481    }
482}
483
484/// The building block frames are composed of.
485#[derive(Clone, Hash)]
486pub enum FrameItem {
487    /// A subframe with optional transformation and clipping.
488    Group(GroupItem),
489    /// A run of shaped text.
490    Text(TextItem),
491    /// A geometric shape with optional fill and stroke.
492    Shape(Shape, Span),
493    /// An image and its size.
494    Image(Image, Size, Span),
495    /// An internal or external link to a destination.
496    Link(Destination, Size),
497    /// An introspectable element that produced something within this frame.
498    Tag(Tag),
499}
500
501impl Debug for FrameItem {
502    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
503        match self {
504            Self::Group(group) => group.fmt(f),
505            Self::Text(text) => write!(f, "{text:?}"),
506            Self::Shape(shape, _) => write!(f, "{shape:?}"),
507            Self::Image(image, _, _) => write!(f, "{image:?}"),
508            Self::Link(dest, _) => write!(f, "Link({dest:?})"),
509            Self::Tag(tag) => write!(f, "{tag:?}"),
510        }
511    }
512}
513
514/// A subframe with optional transformation and clipping.
515#[derive(Clone, Hash)]
516pub struct GroupItem {
517    /// The group's frame.
518    pub frame: Frame,
519    /// A transformation to apply to the group.
520    pub transform: Transform,
521    /// A curve which should be used to clip the group.
522    pub clip: Option<Curve>,
523    /// The group's label.
524    pub label: Option<Label>,
525    /// The group's logical parent. All elements in this group are logically
526    /// ordered immediately after the parent's start location. This can be
527    /// thought of as inserting the elements at the end but still inside of the
528    /// parent.
529    pub parent: Option<FrameParent>,
530}
531
532impl GroupItem {
533    /// Create a new group with default settings.
534    pub fn new(frame: Frame) -> Self {
535        Self {
536            frame,
537            transform: Transform::identity(),
538            clip: None,
539            label: None,
540            parent: None,
541        }
542    }
543}
544
545impl Debug for GroupItem {
546    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
547        f.write_str("Group ")?;
548        self.frame.fmt(f)
549    }
550}
551
552/// The parent of a [`GroupItem`]. The child will be logically ordered at the
553/// end but still inside of the parent.
554#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
555pub struct FrameParent {
556    /// The location of the parent element.
557    pub location: Location,
558    /// Whether the child has inherited the parent's styles.
559    pub inherit: Inherit,
560}
561
562impl FrameParent {
563    /// Create a new frame parent.
564    pub const fn new(location: Location, inherit: Inherit) -> Self {
565        Self { location, inherit }
566    }
567}
568
569/// Whether the child has inherited the parent's styles.
570///
571/// # Inherited
572///
573/// The placed text will inherit the styles from its parent (the place element).
574/// So `explanation` will be underlined.
575///
576/// ```typ
577/// #underline[
578///   Some text #place(float: true, bottom + right)[explanation].
579/// ]
580/// ```
581///
582/// # Not Inherited
583///
584/// The footnote entry won't inherit the styles from it's parent (the footnote).
585/// So `explanation` won't be underlined, unless other styles apply.
586///
587/// ```typ
588/// #underline[
589///   Some text #footnote[explanation].
590/// ]
591/// ```
592#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
593pub enum Inherit {
594    Yes,
595    No,
596}