//! Finished documents. use std::fmt::{self, Debug, Formatter, Write}; use std::num::NonZeroUsize; use std::str::FromStr; use std::sync::Arc; use ecow::EcoString; use crate::eval::{cast_from_value, cast_to_value, dict, Dict, Value}; use crate::font::Font; use crate::geom::{ self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Length, Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; use crate::model::{Content, Location, MetaElem, StyleChain}; use crate::syntax::Span; /// A finished document with metadata and page frames. #[derive(Debug, Default, Clone, Hash)] pub struct Document { /// The page frames. pub pages: Vec, /// The document's title. pub title: Option, /// The document's author. pub author: Vec, } /// A finished layout with items at fixed positions. #[derive(Default, Clone, Hash)] pub struct Frame { /// The size of the frame. size: Size, /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. baseline: Option, /// The items composing this layout. items: Arc>, } /// Constructor, accessors and setters. impl Frame { /// Create a new, empty frame. /// /// Panics the size is not finite. #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); Self { size, baseline: None, items: Arc::new(vec![]) } } /// Whether the frame contains no items. pub fn is_empty(&self) -> bool { self.items.is_empty() } /// The size of the frame. pub fn size(&self) -> Size { self.size } /// The size of the frame, mutably. pub fn size_mut(&mut self) -> &mut Size { &mut self.size } /// Set the size of the frame. pub fn set_size(&mut self, size: Size) { self.size = size; } /// The width of the frame. pub fn width(&self) -> Abs { self.size.x } /// The height of the frame. pub fn height(&self) -> Abs { self.size.y } /// The vertical position of the frame's baseline. pub fn baseline(&self) -> Abs { self.baseline.unwrap_or(self.size.y) } /// Whether the frame has a non-default baseline. pub fn has_baseline(&self) -> bool { self.baseline.is_some() } /// Set the frame's baseline from the top. pub fn set_baseline(&mut self, baseline: Abs) { self.baseline = Some(baseline); } /// The distance from the baseline to the top of the frame. /// /// This is the same as `baseline()`, but more in line with the terminology /// used in math layout. pub fn ascent(&self) -> Abs { self.baseline() } /// The distance from the baseline to the bottom of the frame. pub fn descent(&self) -> Abs { self.size.y - self.baseline() } /// An iterator over the items inside this frame alongside their positions /// relative to the top-left of the frame. pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> { self.items.iter() } /// Approximately recover the text inside of the frame and its children. pub fn text(&self) -> EcoString { let mut text = EcoString::new(); for (_, item) in self.items() { match item { FrameItem::Text(item) => { for glyph in &item.glyphs { text.push(glyph.c); } } FrameItem::Group(group) => text.push_str(&group.frame.text()), _ => {} } } text } } /// Insert items and subframes. impl Frame { /// The layer the next item will be added on. This corresponds to the number /// of items in the frame. pub fn layer(&self) -> usize { self.items.len() } /// Add an item at a position in the foreground. pub fn push(&mut self, pos: Point, item: FrameItem) { Arc::make_mut(&mut self.items).push((pos, item)); } /// Add a frame at a position in the foreground. /// /// Automatically decides whether to inline the frame or to include it as a /// group based on the number of items in it. pub fn push_frame(&mut self, pos: Point, frame: Frame) { if self.should_inline(&frame) { self.inline(self.layer(), pos, frame); } else { self.push(pos, FrameItem::Group(GroupItem::new(frame))); } } /// Insert an item at the given layer in the frame. /// /// This panics if the layer is greater than the number of layers present. #[track_caller] pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) { Arc::make_mut(&mut self.items).insert(layer, (pos, items)); } /// Add an item at a position in the background. pub fn prepend(&mut self, pos: Point, item: FrameItem) { Arc::make_mut(&mut self.items).insert(0, (pos, item)); } /// Add multiple items at a position in the background. /// /// The first item in the iterator will be the one that is most in the /// background. pub fn prepend_multiple(&mut self, items: I) where I: IntoIterator, { Arc::make_mut(&mut self.items).splice(0..0, items); } /// Add a frame at a position in the background. pub fn prepend_frame(&mut self, pos: Point, frame: Frame) { if self.should_inline(&frame) { self.inline(0, pos, frame); } else { self.prepend(pos, FrameItem::Group(GroupItem::new(frame))); } } /// Whether the given frame should be inlined. fn should_inline(&self, frame: &Frame) -> bool { self.items.is_empty() || frame.items.len() <= 5 } /// Inline a frame at the given layer. fn inline(&mut self, layer: usize, pos: Point, frame: Frame) { // Try to just reuse the items. if pos.is_zero() && self.items.is_empty() { self.items = frame.items; return; } // Try to transfer the items without adjusting the position. // Also try to reuse the items if the Arc isn't shared. let range = layer..layer; if pos.is_zero() { let sink = Arc::make_mut(&mut self.items); match Arc::try_unwrap(frame.items) { Ok(items) => { sink.splice(range, items); } Err(arc) => { sink.splice(range, arc.iter().cloned()); } } return; } // We have to adjust the item positions. // But still try to reuse the items if the Arc isn't shared. let sink = Arc::make_mut(&mut self.items); match Arc::try_unwrap(frame.items) { Ok(items) => { sink.splice(range, items.into_iter().map(|(p, e)| (p + pos, e))); } Err(arc) => { sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e))); } } } } /// Modify the frame. impl Frame { /// Remove all items from the frame. pub fn clear(&mut self) { if Arc::strong_count(&self.items) == 1 { Arc::make_mut(&mut self.items).clear(); } else { self.items = Arc::new(vec![]); } } /// Resize the frame to a new size, distributing new space according to the /// given alignments. pub fn resize(&mut self, target: Size, aligns: Axes) { if self.size != target { let offset = Point::new( aligns.x.position(target.x - self.size.x), aligns.y.position(target.y - self.size.y), ); self.size = target; self.translate(offset); } } /// Move the baseline and contents of the frame by an offset. pub fn translate(&mut self, offset: Point) { if !offset.is_zero() { if let Some(baseline) = &mut self.baseline { *baseline += offset.y; } for (point, _) in Arc::make_mut(&mut self.items) { *point += offset; } } } /// Attach the metadata from this style chain to the frame. pub fn meta(&mut self, styles: StyleChain, force: bool) { if force || !self.is_empty() { self.meta_iter(MetaElem::data_in(styles)); } } /// Attach metadata from an iterator. pub fn meta_iter(&mut self, iter: impl IntoIterator) { for meta in iter { if matches!(meta, Meta::Hide) { self.clear(); break; } self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); } } /// Add a background fill. pub fn fill(&mut self, fill: Paint) { self.prepend( Point::zero(), FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), ); } /// Add a fill and stroke with optional radius and outset to the frame. pub fn fill_and_stroke( &mut self, fill: Option, stroke: Sides>, outset: Sides>, radius: Corners>, span: Span, ) { let outset = outset.relative_to(self.size()); let size = self.size() + outset.sum_by_axis(); let pos = Point::new(-outset.left, -outset.top); let radius = radius.map(|side| side.relative_to(size.x.min(size.y) / 2.0)); self.prepend_multiple( rounded_rect(size, radius, fill, stroke) .into_iter() .map(|x| (pos, FrameItem::Shape(x, span))), ) } /// Arbitrarily transform the contents of the frame. pub fn transform(&mut self, transform: Transform) { if !self.is_empty() { self.group(|g| g.transform = transform); } } /// Clip the contents of a frame to its size. pub fn clip(&mut self) { if !self.is_empty() { self.group(|g| g.clips = true); } } /// Wrap the frame's contents in a group and modify that group with `f`. fn group(&mut self, f: F) where F: FnOnce(&mut GroupItem), { let mut wrapper = Frame::new(self.size); wrapper.baseline = self.baseline; let mut group = GroupItem::new(std::mem::take(self)); f(&mut group); wrapper.push(Point::zero(), FrameItem::Group(group)); *self = wrapper; } } /// Tools for debugging. impl Frame { /// Add a full size aqua background and a red baseline for debugging. pub fn debug(mut self) -> Self { self.insert( 0, Point::zero(), FrameItem::Shape( Geometry::Rect(self.size) .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), Span::detached(), ), ); self.insert( 1, Point::with_y(self.baseline()), FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::RED.into(), thickness: Abs::pt(1.0), ..Stroke::default() }), Span::detached(), ), ); self } /// Add a green marker at a position for debugging. pub fn mark_point(&mut self, pos: Point) { let radius = Abs::pt(2.0); self.push( pos - Point::splat(radius), FrameItem::Shape( geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), Span::detached(), ), ); } /// Add a green marker line at a position for debugging. pub fn mark_line(&mut self, y: Abs) { self.push( Point::with_y(y), FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::GREEN.into(), thickness: Abs::pt(1.0), ..Stroke::default() }), Span::detached(), ), ); } } impl Debug for Frame { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Frame ")?; f.debug_list() .entries(self.items.iter().map(|(_, item)| item)) .finish() } } /// The building block frames are composed of. #[derive(Clone, Hash)] pub enum FrameItem { /// A subframe with optional transformation and clipping. Group(GroupItem), /// A run of shaped text. Text(TextItem), /// A geometric shape with optional fill and stroke. Shape(Shape, Span), /// An image and its size. Image(Image, Size, Span), /// Meta information and the region it applies to. Meta(Meta, Size), } impl Debug for FrameItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Group(group) => group.fmt(f), Self::Text(text) => write!(f, "{text:?}"), Self::Shape(shape, _) => write!(f, "{shape:?}"), Self::Image(image, _, _) => write!(f, "{image:?}"), Self::Meta(meta, _) => write!(f, "{meta:?}"), } } } /// A subframe with optional transformation and clipping. #[derive(Clone, Hash)] pub struct GroupItem { /// The group's frame. pub frame: Frame, /// A transformation to apply to the group. pub transform: Transform, /// Whether the frame should be a clipping boundary. pub clips: bool, } impl GroupItem { /// Create a new group with default settings. pub fn new(frame: Frame) -> Self { Self { frame, transform: Transform::identity(), clips: false, } } } impl Debug for GroupItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Group ")?; self.frame.fmt(f) } } /// A run of shaped text. #[derive(Clone, Eq, PartialEq, Hash)] pub struct TextItem { /// The font the glyphs are contained in. pub font: Font, /// The font size. pub size: Abs, /// Glyph color. pub fill: Paint, /// The natural language of the text. pub lang: Lang, /// The glyphs. pub glyphs: Vec, } impl TextItem { /// The width of the text run. pub fn width(&self) -> Abs { self.glyphs.iter().map(|g| g.x_advance).sum::().at(self.size) } } impl Debug for TextItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // This is only a rough approxmiation of the source text. f.write_str("Text(\"")?; for glyph in &self.glyphs { for c in glyph.c.escape_debug() { f.write_char(c)?; } } f.write_str("\")") } } /// A glyph in a run of shaped text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Glyph { /// The glyph's index in the font. pub id: u16, /// The advance width of the glyph. pub x_advance: Em, /// The horizontal offset of the glyph. pub x_offset: Em, /// The first character of the glyph's cluster. pub c: char, /// The source code location of the text. pub span: Span, /// The offset within the spanned text. pub offset: u16, } /// An identifier for a natural language. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Lang([u8; 3], u8); impl Lang { pub const ARABIC: Self = Self(*b"ar ", 2); pub const BOKMÃ…L: Self = Self(*b"nb ", 2); pub const CHINESE: Self = Self(*b"zh ", 2); pub const CZECH: Self = Self(*b"cs ", 2); pub const ENGLISH: Self = Self(*b"en ", 2); pub const FRENCH: Self = Self(*b"fr ", 2); pub const GERMAN: Self = Self(*b"de ", 2); pub const ITALIAN: Self = Self(*b"it ", 2); pub const NYNORSK: Self = Self(*b"nn ", 2); pub const POLISH: Self = Self(*b"pl ", 2); pub const PORTUGUESE: Self = Self(*b"pt ", 2); pub const RUSSIAN: Self = Self(*b"ru ", 2); pub const SLOVENIAN: Self = Self(*b"sl ", 2); pub const SPANISH: Self = Self(*b"es ", 2); pub const UKRAINIAN: Self = Self(*b"ua ", 2); pub const VIETNAMESE: Self = Self(*b"vi ", 2); /// Return the language code as an all lowercase string slice. pub fn as_str(&self) -> &str { std::str::from_utf8(&self.0[..usize::from(self.1)]).unwrap_or_default() } /// The default direction for the language. pub fn dir(self) -> Dir { match self.as_str() { "ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur" | "yi" => Dir::RTL, _ => Dir::LTR, } } } impl FromStr for Lang { type Err = &'static str; /// Construct a language from a two- or three-byte ISO 639-1/2/3 code. fn from_str(iso: &str) -> Result { let len = iso.len(); if matches!(len, 2..=3) && iso.is_ascii() { let mut bytes = [b' '; 3]; bytes[..len].copy_from_slice(iso.as_bytes()); bytes.make_ascii_lowercase(); Ok(Self(bytes, len as u8)) } else { Err("expected two or three letter language code (ISO 639-1/2/3)") } } } cast_from_value! { Lang, string: EcoString => Self::from_str(&string)?, } cast_to_value! { v: Lang => v.as_str().into() } /// An identifier for a region somewhere in the world. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Region([u8; 2]); impl Region { /// Return the region code as an all uppercase string slice. pub fn as_str(&self) -> &str { std::str::from_utf8(&self.0).unwrap_or_default() } } impl FromStr for Region { type Err = &'static str; /// Construct a region from its two-byte ISO 3166-1 alpha-2 code. fn from_str(iso: &str) -> Result { if iso.len() == 2 && iso.is_ascii() { let mut bytes: [u8; 2] = iso.as_bytes().try_into().unwrap(); bytes.make_ascii_uppercase(); Ok(Self(bytes)) } else { Err("expected two letter region code (ISO 3166-1 alpha-2)") } } } cast_from_value! { Region, string: EcoString => Self::from_str(&string)?, } cast_to_value! { v: Region => v.as_str().into() } /// Meta information that isn't visible or renderable. #[derive(Debug, Clone, PartialEq, Hash)] pub enum Meta { /// An internal or external link to a destination. Link(Destination), /// An identifiable element that produces something within the area this /// metadata is attached to. Elem(Content), /// The numbering of the current page. PageNumbering(Value), /// Indicates that content should be hidden. This variant doesn't appear /// in the final frames as it is removed alongside the content that should /// be hidden. Hide, } cast_from_value! { Meta: "meta", } /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { /// A link to a URL. Url(EcoString), /// A link to a point on a page. Position(Position), /// An unresolved link to a location in the document. Location(Location), } cast_from_value! { Destination, v: EcoString => Self::Url(v), v: Position => Self::Position(v), v: Location => Self::Location(v), } cast_to_value! { v: Destination => match v { Destination::Url(v) => v.into(), Destination::Position(v) => v.into(), Destination::Location(v) => v.into(), } } /// A physical position in a document. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Position { /// The page, starting at 1. pub page: NonZeroUsize, /// The exact coordinates on the page (from the top left, as usual). pub point: Point, } cast_from_value! { Position, mut dict: Dict => { let page = dict.take("page")?.cast()?; let x: Length = dict.take("x")?.cast()?; let y: Length = dict.take("y")?.cast()?; dict.finish(&["page", "x", "y"])?; Self { page, point: Point::new(x.abs, y.abs) } }, } cast_to_value! { v: Position => Value::Dict(dict! { "page" => Value::Int(v.page.get() as i64), "x" => Value::Length(v.point.x.into()), "y" => Value::Length(v.point.y.into()), }) }