typst/src/doc.rs
2023-03-19 22:39:19 +01:00

668 lines
19 KiB
Rust

//! 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<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()
}
/// 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<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() {
for meta in MetaElem::data_in(styles) {
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<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),
}),
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),
}),
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<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 {
// 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 ENGLISH: Self = Self(*b"en ", 2);
pub const GERMAN: Self = Self(*b"de ", 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_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<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_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),
/// 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()),
})
}