typst_library/layout/
frame.rs

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