diff options
Diffstat (limited to 'src/doc.rs')
| -rw-r--r-- | src/doc.rs | 719 |
1 files changed, 0 insertions, 719 deletions
diff --git a/src/doc.rs b/src/doc.rs deleted file mode 100644 index de16cece..00000000 --- a/src/doc.rs +++ /dev/null @@ -1,719 +0,0 @@ -//! Finished documents. - -use std::fmt::{self, Debug, Formatter}; -use std::num::NonZeroUsize; -use std::ops::Range; -use std::str::FromStr; -use std::sync::Arc; - -use ecow::EcoString; - -use crate::eval::{cast, 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<Frame>, - /// The document's title. - pub title: Option<EcoString>, - /// The document's author. - pub author: Vec<EcoString>, -} - -/// 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<Abs>, - /// The items composing this layout. - items: Arc<Vec<(Point, FrameItem)>>, -} - -/// 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() - } -} - -/// 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<I>(&mut self, items: I) - where - I: IntoIterator<Item = (Point, FrameItem)>, - { - 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<Align>) { - 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<Item = Meta>) { - let mut hide = false; - for meta in iter { - if matches!(meta, Meta::Hide) { - hide = true; - } else { - self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); - } - } - if hide { - Arc::make_mut(&mut self.items).retain(|(_, item)| { - matches!(item, FrameItem::Group(_) | FrameItem::Meta(Meta::Elem(_), _)) - }); - } - } - - /// 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<Paint>, - stroke: Sides<Option<Stroke>>, - outset: Sides<Rel<Abs>>, - radius: Corners<Rel<Abs>>, - 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<F>(&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 item's plain text. - pub text: EcoString, - /// The glyphs. - pub glyphs: Vec<Glyph>, -} - -impl TextItem { - /// The width of the text run. - pub fn width(&self) -> Abs { - self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size) - } -} - -impl Debug for TextItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Text(")?; - self.text.fmt(f)?; - f.write_str(")") - } -} - -/// A glyph in a run of shaped text. -#[derive(Debug, 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 range of the glyph in its item's text. - pub range: Range<u16>, - /// The source code location of the text. - pub span: (Span, u16), -} - -impl Glyph { - /// The range of the glyph in its item's text. - pub fn range(&self) -> Range<usize> { - usize::from(self.range.start)..usize::from(self.range.end) - } -} - -/// 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 ALBANIAN: Self = Self(*b"sq ", 2); - 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 DANISH: Self = Self(*b"da ", 2); - pub const DUTCH: Self = Self(*b"nl ", 2); - pub const ENGLISH: Self = Self(*b"en ", 2); - pub const FILIPINO: Self = Self(*b"tl ", 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 JAPANESE: Self = Self(*b"ja ", 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 SWEDISH: Self = Self(*b"sv ", 2); - pub const TURKISH: Self = Self(*b"tr ", 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<Self, Self::Err> { - 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! { - Lang, - self => self.as_str().into_value(), - string: EcoString => Self::from_str(&string)?, -} - -/// 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 PartialEq<&str> for Region { - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -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<Self, Self::Err> { - 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! { - Region, - self => self.as_str().into_value(), - string: EcoString => Self::from_str(&string)?, -} - -/// Meta information that isn't visible or renderable. -#[derive(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! { - type Meta: "meta", -} - -impl Debug for Meta { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Link(dest) => write!(f, "Link({dest:?})"), - Self::Elem(content) => write!(f, "Elem({:?})", content.func()), - Self::PageNumbering(value) => write!(f, "PageNumbering({value:?})"), - Self::Hide => f.pad("Hide"), - } - } -} - -/// 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! { - Destination, - self => match self { - Self::Url(v) => v.into_value(), - Self::Position(v) => v.into_value(), - Self::Location(v) => v.into_value(), - }, - v: EcoString => Self::Url(v), - v: Position => Self::Position(v), - v: Location => Self::Location(v), -} - -/// 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! { - Position, - self => Value::Dict(self.into()), - 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) } - }, -} - -impl From<Position> for Dict { - fn from(pos: Position) -> Self { - dict! { - "page" => pos.page, - "x" => pos.point.x, - "y" => pos.point.y, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::util::option_eq; - - #[test] - fn test_region_option_eq() { - let region = Some(Region([b'U', b'S'])); - assert!(option_eq(region, "US")); - assert!(!option_eq(region, "AB")); - } - - #[test] - fn test_document_is_send() { - fn ensure_send<T: Send>() {} - ensure_send::<Document>(); - } -} |
