Move, rename and switch some things (boring) 🚚

- Problems -> Diagnostics
- Position -> Pos
- offset_spans -> Offset trait
- Size -> Length (and some more size types renamed)
- Paper into its own module
- scope::Parser -> parsing::CallParser
- Create `Decorations` alias
- Remove lots of double newlines
- Switch from f32 to f64
This commit is contained in:
Laurenz 2020-07-29 18:09:51 +02:00
parent f34ba3dcda
commit bbcdeb128c
32 changed files with 891 additions and 927 deletions

View File

@ -8,7 +8,6 @@ use typstc::{Typesetter, DebugErrorProvider};
use typstc::toddle::query::fs::EagerFsProvider;
use typstc::export::pdf;
fn main() {
if let Err(err) = run() {
eprintln!("error: {}", err);

91
src/diagnostic.rs Normal file
View File

@ -0,0 +1,91 @@
//! Diagnostics (errors / warnings) in source code.
//!
//! There are no fatal errors. The document will always compile and yield a
//! layout. However, this is a best effort process and bad things will still
//! generate errors and warnings.
use serde::Serialize;
use crate::syntax::span::SpanVec;
/// A list of spanned diagnostics.
pub type Diagnostics = SpanVec<Diagnostic>;
/// A diagnostic that arose in parsing or layouting.
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Diagnostic {
/// How severe / important the diagnostic is.
pub level: Level,
/// A message describing the diagnostic.
pub message: String,
}
/// How severe / important a diagnostic is.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Level {
Warning,
Error,
}
impl Diagnostic {
/// Create a new diagnostic from message and level.
pub fn new(message: impl Into<String>, level: Level) -> Self {
Self { message: message.into(), level }
}
}
/// Construct a diagnostic with `Error` level.
///
/// ```
/// # use typstc::error;
/// # use typstc::syntax::span::Span;
/// # use typstc::Feedback;
/// # let span = Span::ZERO;
/// # let mut feedback = Feedback::new();
/// # let name = "";
/// // Create formatted error values.
/// let error = error!("expected {}", name);
///
/// // Create spanned errors.
/// let spanned = error!(span, "there is an error here");
///
/// // Create an error and directly add it to existing feedback.
/// error!(@feedback, span, "oh no!");
/// ```
#[macro_export]
macro_rules! error {
($($tts:tt)*) => {
$crate::__impl_diagnostic!($crate::diagnostic::Level::Error; $($tts)*)
};
}
/// Construct a diagnostic with `Warning` level.
///
/// This works exactly like `error!`. See its documentation for more
/// information.
#[macro_export]
macro_rules! warning {
($($tts:tt)*) => {
$crate::__impl_diagnostic!($crate::diagnostic::Level::Warning; $($tts)*)
};
}
/// Backs the `error!` and `warning!` macros.
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_diagnostic {
($level:expr; @$feedback:expr, $($tts:tt)*) => {
$feedback.diagnostics.push($crate::__impl_diagnostic!($level; $($tts)*));
};
($level:expr; $fmt:literal $($tts:tt)*) => {
$crate::diagnostic::Diagnostic::new(format!($fmt $($tts)*), $level)
};
($level:expr; $span:expr, $fmt:literal $($tts:tt)*) => {
$crate::syntax::span::Spanned::new(
$crate::__impl_diagnostic!($level; $fmt $($tts)*),
$span,
)
};
}

View File

@ -23,8 +23,7 @@ use toddle::tables::{
use crate::GlobalFontLoader;
use crate::layout::{MultiLayout, Layout, LayoutAction};
use crate::size::Size;
use crate::length::Length;
/// Export a layouted list of boxes. The same font loader as used for
/// layouting needs to be passed in here since the layout only contains
@ -128,8 +127,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new(
0.0,
0.0,
page.dimensions.x.to_pt(),
page.dimensions.y.to_pt(),
page.dimensions.x.to_pt() as f32,
page.dimensions.y.to_pt() as f32,
);
self.writer.write_obj(
@ -167,14 +166,14 @@ impl<'a, W: Write> PdfExporter<'a, W> {
LayoutAction::SetFont(id, size) => {
active_font = (self.font_remap[id], size.to_pt());
text.tf(active_font.0 as u32 + 1, size.to_pt());
text.tf(active_font.0 as u32 + 1, size.to_pt() as f32);
}
LayoutAction::WriteText(string) => {
if let Some(pos) = next_pos.take() {
let x = pos.x.to_pt();
let y = (page.dimensions.y - pos.y - Size::pt(active_font.1)).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
let y = (page.dimensions.y - pos.y - Length::pt(active_font.1)).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
}
text.tj(self.fonts[active_font.0].encode_text(&string)?);
@ -219,8 +218,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
// Extract information from the head and hmtx tables.
let head = font.read_table::<Header>()?;
let font_unit_ratio = 1.0 / (head.units_per_em as f32);
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
let font_unit_ratio = 1.0 / (head.units_per_em as f64);
let font_unit_to_size = |x| Length::pt(font_unit_ratio * x);
let font_unit_to_glyph_unit = |fu| {
let size = font_unit_to_size(fu);
(1000.0 * size.to_pt()).round() as GlyphUnit
@ -228,10 +227,10 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let italic = head.mac_style.contains(MacStyleFlags::ITALIC);
let bounding_box = Rect::new(
font_unit_to_glyph_unit(head.x_min as f32),
font_unit_to_glyph_unit(head.y_min as f32),
font_unit_to_glyph_unit(head.x_max as f32),
font_unit_to_glyph_unit(head.y_max as f32),
font_unit_to_glyph_unit(head.x_min as f64),
font_unit_to_glyph_unit(head.y_min as f64),
font_unit_to_glyph_unit(head.x_max as f64),
font_unit_to_glyph_unit(head.y_max as f64),
);
// Transform the width into PDF units.
@ -239,7 +238,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
.read_table::<HorizontalMetrics>()?
.metrics
.iter()
.map(|m| font_unit_to_glyph_unit(m.advance_width as f32))
.map(|m| font_unit_to_glyph_unit(m.advance_width as f64))
.collect();
// Write the CID font referencing the font descriptor.
@ -274,12 +273,12 @@ impl<'a, W: Write> PdfExporter<'a, W> {
// Write the font descriptor (contains the global information about the font).
self.writer.write_obj(id + 2, FontDescriptor::new(base_font, flags, italic_angle)
.font_bbox(bounding_box)
.ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32))
.descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32))
.ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f64))
.descent(font_unit_to_glyph_unit(os2.s_typo_descender as f64))
.cap_height(font_unit_to_glyph_unit(
os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32,
os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f64,
))
.stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit)
.stem_v((10.0 + 0.244 * (os2.us_weight_class as f64 - 50.0)) as GlyphUnit)
.font_file_2(id + 4)
)?;

View File

@ -52,8 +52,8 @@ pub trait ParseFunc {
///
/// parse(header, body, state, f) {
/// let body = body!(opt: body, state, f);
/// let hidden = header.args.pos.get::<bool>(&mut f.problems)
/// .or_missing(&mut f.problems, header.name.span, "hidden")
/// let hidden = header.args.pos.get::<bool>(&mut f.diagnostics)
/// .or_missing(&mut f.diagnostics, header.name.span, "hidden")
/// .unwrap_or(false);
///
/// HiderFunc { body: if hidden { None } else { body } }

View File

@ -4,23 +4,22 @@ use std::fmt::{self, Debug, Formatter};
use serde::ser::{Serialize, Serializer, SerializeTuple};
use toddle::query::FontIndex;
use crate::size::{Size, Size2D};
use crate::length::{Length, Size};
use super::Layout;
use self::LayoutAction::*;
/// A layouting action, which is the basic building block layouts are composed
/// of.
#[derive(Clone, PartialEq)]
pub enum LayoutAction {
/// Move to an absolute position.
MoveAbsolute(Size2D),
MoveAbsolute(Size),
/// Set the font given the index from the font loader and font size.
SetFont(FontIndex, Size),
SetFont(FontIndex, Length),
/// Write text at the current position.
WriteText(String),
/// Visualize a box for debugging purposes.
DebugBox(Size2D),
DebugBox(Size),
}
impl Serialize for LayoutAction {
@ -81,11 +80,11 @@ impl Debug for LayoutAction {
/// position.
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutActions {
origin: Size2D,
origin: Size,
actions: Vec<LayoutAction>,
active_font: (FontIndex, Size),
next_pos: Option<Size2D>,
next_font: Option<(FontIndex, Size)>,
active_font: (FontIndex, Length),
next_pos: Option<Size>,
next_font: Option<(FontIndex, Length)>,
}
impl LayoutActions {
@ -93,8 +92,8 @@ impl LayoutActions {
pub fn new() -> LayoutActions {
LayoutActions {
actions: vec![],
origin: Size2D::ZERO,
active_font: (FontIndex::MAX, Size::ZERO),
origin: Size::ZERO,
active_font: (FontIndex::MAX, Length::ZERO),
next_pos: None,
next_font: None,
}
@ -126,7 +125,7 @@ impl LayoutActions {
/// Add a layout at a position. All move actions inside the layout are
/// translated by the position.
pub fn add_layout(&mut self, position: Size2D, layout: Layout) {
pub fn add_layout(&mut self, position: Size, layout: Layout) {
self.flush_position();
self.origin = position;

View File

@ -11,7 +11,6 @@
use super::stack::{StackLayouter, StackContext};
use super::*;
/// Performs the line layouting.
#[derive(Debug)]
pub struct LineLayouter {
@ -40,7 +39,7 @@ pub struct LineContext {
/// extent of the layout.
pub debug: bool,
/// The line spacing.
pub line_spacing: Size,
pub line_spacing: Length,
}
/// A line run is a sequence of boxes with the same alignment that are arranged
@ -49,10 +48,10 @@ pub struct LineContext {
#[derive(Debug)]
struct LineRun {
/// The so-far accumulated layouts in the line.
layouts: Vec<(Size, Layout)>,
/// The width (primary size) and maximal height (secondary size) of the
layouts: Vec<(Length, Layout)>,
/// The width (primary length) and maximal height (secondary length) of the
/// line.
size: Size2D,
size: Size,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined. Once a
@ -61,7 +60,7 @@ struct LineRun {
alignment: Option<LayoutAlignment>,
/// If another line run with different alignment already took up some space
/// of the line, this run has less space and how much is stored here.
usable: Option<Size>,
usable: Option<Length>,
/// A possibly cached soft spacing or spacing state.
last_spacing: LastSpacing,
}
@ -156,7 +155,7 @@ impl LineLayouter {
///
/// This specifies how much more fits before a line break needs to be
/// issued.
fn usable(&self) -> Size2D {
fn usable(&self) -> Size {
// The base is the usable space per stack layouter.
let mut usable = self.stack.usable().generalized(self.ctx.axes);
@ -171,7 +170,7 @@ impl LineLayouter {
}
/// Add spacing along the primary axis to the line.
pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
pub fn add_primary_spacing(&mut self, mut spacing: Length, kind: SpacingKind) {
match kind {
// A hard space is simply an empty box.
SpacingKind::Hard => {
@ -197,7 +196,7 @@ impl LineLayouter {
}
/// Finish the line and add secondary spacing to the underlying stack.
pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) {
pub fn add_secondary_spacing(&mut self, spacing: Length, kind: SpacingKind) {
self.finish_line_if_not_empty();
self.stack.add_spacing(spacing, kind)
}
@ -219,7 +218,7 @@ impl LineLayouter {
}
/// Update the line spacing.
pub fn set_line_spacing(&mut self, line_spacing: Size) {
pub fn set_line_spacing(&mut self, line_spacing: Length) {
self.ctx.line_spacing = line_spacing;
}
@ -235,7 +234,7 @@ impl LineLayouter {
/// Whether the currently set line is empty.
pub fn line_is_empty(&self) -> bool {
self.run.size == Size2D::ZERO && self.run.layouts.is_empty()
self.run.size == Size::ZERO && self.run.layouts.is_empty()
}
/// Finish the last line and compute the final list of boxes.
@ -265,7 +264,7 @@ impl LineLayouter {
- layout.dimensions.primary(self.ctx.axes),
};
let pos = Size2D::with_x(x);
let pos = Size::with_x(x);
actions.add_layout(pos, layout);
}
@ -293,7 +292,7 @@ impl LineRun {
fn new() -> LineRun {
LineRun {
layouts: vec![],
size: Size2D::ZERO,
size: Size::ZERO,
alignment: None,
usable: None,
last_spacing: LastSpacing::Hard,

View File

@ -5,7 +5,7 @@ use smallvec::SmallVec;
use serde::Serialize;
use toddle::query::FontIndex;
use crate::size::{Size, Size2D, SizeBox};
use crate::length::{Length, Size, Margins};
use self::prelude::*;
pub mod line;
@ -27,7 +27,6 @@ pub mod prelude {
pub use super::Alignment::{self, *};
}
/// A collection of layouts.
pub type MultiLayout = Vec<Layout>;
@ -35,7 +34,7 @@ pub type MultiLayout = Vec<Layout>;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Layout {
/// The size of the box.
pub dimensions: Size2D,
pub dimensions: Size,
/// How to align this layout in a parent container.
#[serde(skip)]
pub alignment: LayoutAlignment,
@ -66,9 +65,9 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct LayoutSpace {
/// The maximum size of the box to layout in.
pub dimensions: Size2D,
pub dimensions: Size,
/// Padding that should be respected on each side.
pub padding: SizeBox,
pub padding: Margins,
/// Whether to expand the dimensions of the resulting layout to the full
/// dimensions of this space or to shrink them to fit the content.
pub expansion: LayoutExpansion,
@ -77,12 +76,12 @@ pub struct LayoutSpace {
impl LayoutSpace {
/// The offset from the origin to the start of content, that is,
/// `(padding.left, padding.top)`.
pub fn start(&self) -> Size2D {
Size2D::new(self.padding.left, self.padding.top)
pub fn start(&self) -> Size {
Size::new(self.padding.left, self.padding.top)
}
/// The actually usable area (dimensions minus padding).
pub fn usable(&self) -> Size2D {
pub fn usable(&self) -> Size {
self.dimensions.unpadded(self.padding)
}
@ -90,7 +89,7 @@ impl LayoutSpace {
pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace {
dimensions: self.usable(),
padding: SizeBox::ZERO,
padding: Margins::ZERO,
expansion: LayoutExpansion::new(false, false),
}
}
@ -369,17 +368,17 @@ enum LastSpacing {
/// The last item was hard spacing.
Hard,
/// The last item was soft spacing with the given width and level.
Soft(Size, u32),
Soft(Length, u32),
/// The last item was not spacing.
None,
}
impl LastSpacing {
/// The size of the soft space if this is a soft space or zero otherwise.
fn soft_or_zero(self) -> Size {
/// The length of the soft space if this is a soft space or zero otherwise.
fn soft_or_zero(self) -> Length {
match self {
LastSpacing::Soft(space, _) => space,
_ => Size::ZERO,
_ => Length::ZERO,
}
}
}

View File

@ -10,14 +10,13 @@ use toddle::query::FontStyle;
use crate::{Pass, Feedback};
use crate::GlobalFontLoader;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::size::{Size, Size2D};
use crate::length::{Length, Size};
use crate::syntax::{Model, SyntaxModel, Node, Decoration};
use crate::syntax::span::{Span, Spanned};
use super::line::{LineLayouter, LineContext};
use super::text::{layout_text, TextContext};
use super::*;
/// Performs the model layouting.
#[derive(Debug)]
pub struct ModelLayouter<'a> {
@ -36,7 +35,7 @@ pub struct LayoutContext<'a> {
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// The base unpadded dimensions of this container (for relative sizing).
pub base: Size2D,
pub base: Size,
/// The spaces to layout in.
pub spaces: LayoutSpaces,
/// Whether to have repeated spaces or to use only the first and only once.
@ -76,7 +75,7 @@ pub enum Command<'a> {
/// Add spacing of given [kind](super::SpacingKind) along the primary or
/// secondary axis. The spacing kind defines how the spacing interacts with
/// surrounding spacing.
AddSpacing(Size, SpacingKind, GenericAxis),
AddSpacing(Length, SpacingKind, GenericAxis),
/// Start a new line.
BreakLine,
@ -159,7 +158,7 @@ impl<'a> ModelLayouter<'a> {
for Spanned { v: node, span } in &model.nodes {
let decorate = |this: &mut ModelLayouter, deco| {
this.feedback.decos.push(Spanned::new(deco, *span));
this.feedback.decorations.push(Spanned::new(deco, *span));
};
match node {

View File

@ -22,10 +22,9 @@
//! sentence in the second box.
use smallvec::smallvec;
use crate::size::ValueBox;
use crate::length::Value4;
use super::*;
/// Performs the stack layouting.
#[derive(Debug)]
pub struct StackLayouter {
@ -66,14 +65,14 @@ struct Space {
/// The so-far accumulated layouts.
layouts: Vec<(LayoutAxes, Layout)>,
/// The specialized size of this space.
size: Size2D,
size: Size,
/// The specialized remaining space.
usable: Size2D,
usable: Size,
/// The specialized extra-needed dimensions to affect the size at all.
extra: Size2D,
extra: Size,
/// The rulers of a space dictate which alignments for new boxes are still
/// allowed and which require a new space to be started.
rulers: ValueBox<Alignment>,
rulers: Value4<Alignment>,
/// The last added spacing if the last added thing was spacing.
last_spacing: LastSpacing,
}
@ -129,13 +128,13 @@ impl StackLayouter {
}
/// Add secondary spacing to the stack.
pub fn add_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
pub fn add_spacing(&mut self, mut spacing: Length, kind: SpacingKind) {
match kind {
// A hard space is simply an empty box.
SpacingKind::Hard => {
// Reduce the spacing such that it definitely fits.
spacing.min_eq(self.space.usable.secondary(self.ctx.axes));
let dimensions = Size2D::with_y(spacing);
let dimensions = Size::with_y(spacing);
self.update_metrics(dimensions);
self.space.layouts.push((self.ctx.axes, Layout {
@ -165,17 +164,17 @@ impl StackLayouter {
/// Update the size metrics to reflect that a layout or spacing with the
/// given generalized dimensions has been added.
fn update_metrics(&mut self, dimensions: Size2D) {
fn update_metrics(&mut self, dimensions: Size) {
let axes = self.ctx.axes;
let mut size = self.space.size.generalized(axes);
let mut extra = self.space.extra.generalized(axes);
size.x += (dimensions.x - extra.x).max(Size::ZERO);
size.y += (dimensions.y - extra.y).max(Size::ZERO);
size.x += (dimensions.x - extra.x).max(Length::ZERO);
size.y += (dimensions.y - extra.y).max(Length::ZERO);
extra.x.max_eq(dimensions.x);
extra.y = (extra.y - dimensions.y).max(Size::ZERO);
extra.y = (extra.y - dimensions.y).max(Length::ZERO);
self.space.size = size.specialized(axes);
self.space.extra = extra.specialized(axes);
@ -233,7 +232,7 @@ impl StackLayouter {
/// Move to the first space that can fit the given dimensions or do nothing
/// if no space is capable of that.
pub fn skip_to_fitting_space(&mut self, dimensions: Size2D) {
pub fn skip_to_fitting_space(&mut self, dimensions: Size) {
let start = self.next_space();
for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
if space.usable().fits(dimensions) {
@ -251,7 +250,7 @@ impl StackLayouter {
let mut spaces = smallvec![LayoutSpace {
dimensions,
padding: SizeBox::ZERO,
padding: Margins::ZERO,
expansion: LayoutExpansion::new(false, false),
}];
@ -263,15 +262,15 @@ impl StackLayouter {
}
/// The remaining usable size.
pub fn usable(&self) -> Size2D {
pub fn usable(&self) -> Size {
self.space.usable
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
- Size::with_y(self.space.last_spacing.soft_or_zero())
.specialized(self.ctx.axes)
}
/// Whether the current layout space (not subspace) is empty.
pub fn space_is_empty(&self) -> bool {
self.space.size == Size2D::ZERO && self.space.layouts.is_empty()
self.space.size == Size::ZERO && self.space.layouts.is_empty()
}
/// Whether the current layout space is the last is the followup list.
@ -310,7 +309,7 @@ impl StackLayouter {
let start = space.start();
let mut bounds = vec![];
let mut bound = SizeBox {
let mut bound = Margins {
left: start.x,
top: start.y,
right: start.x + self.space.size.x,
@ -337,7 +336,7 @@ impl StackLayouter {
// The `x` field stores the maximal primary extent in one axis-aligned
// run, while the `y` fields stores the accumulated secondary extent.
let mut extent = Size2D::ZERO;
let mut extent = Size::ZERO;
let mut rotation = Vertical;
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
@ -349,7 +348,7 @@ impl StackLayouter {
// is reset for this new axis-aligned run.
if rotation != axes.secondary.axis() {
extent.y = extent.x;
extent.x = Size::ZERO;
extent.x = Length::ZERO;
rotation = axes.secondary.axis();
}
@ -383,11 +382,11 @@ impl StackLayouter {
// The space in which this layout is aligned is given by the
// distances between the borders of it's bounding box.
let usable =
Size2D::new(bound.right - bound.left, bound.bottom - bound.top)
Size::new(bound.right - bound.left, bound.bottom - bound.top)
.generalized(axes);
let local = usable.anchor(alignment, axes) - size.anchor(alignment, axes);
let pos = Size2D::new(bound.left, bound.top) + local.specialized(axes);
let pos = Size::new(bound.left, bound.top) + local.specialized(axes);
actions.add_layout(pos, layout);
}
@ -417,15 +416,15 @@ impl StackLayouter {
}
impl Space {
fn new(index: usize, hard: bool, usable: Size2D) -> Space {
fn new(index: usize, hard: bool, usable: Size) -> Space {
Space {
index,
hard,
layouts: vec![],
size: Size2D::ZERO,
size: Size::ZERO,
usable,
extra: Size2D::ZERO,
rulers: ValueBox::with_all(Origin),
extra: Size::ZERO,
rulers: Value4::with_all(Origin),
last_spacing: LastSpacing::Hard,
}
}

View File

@ -8,11 +8,10 @@ use toddle::query::{FontQuery, FontIndex};
use toddle::tables::{CharMap, Header, HorizontalMetrics};
use crate::GlobalFontLoader;
use crate::size::{Size, Size2D};
use crate::length::{Length, Size};
use crate::style::TextStyle;
use super::*;
/// Performs the text layouting.
#[derive(Debug)]
struct TextLayouter<'a> {
@ -21,7 +20,7 @@ struct TextLayouter<'a> {
actions: LayoutActions,
buffer: String,
active_font: FontIndex,
width: Size,
width: Length,
}
/// The context for text layouting.
@ -54,7 +53,7 @@ impl<'a> TextLayouter<'a> {
actions: LayoutActions::new(),
buffer: String::new(),
active_font: FontIndex::MAX,
width: Size::ZERO,
width: Length::ZERO,
}
}
@ -77,7 +76,7 @@ impl<'a> TextLayouter<'a> {
}
Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
dimensions: Size::new(self.width, self.ctx.style.font_size()),
alignment: self.ctx.alignment,
actions: self.actions.into_vec(),
}
@ -110,7 +109,7 @@ impl<'a> TextLayouter<'a> {
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
async fn select_font(&mut self, c: char) -> Option<(FontIndex, Size)> {
async fn select_font(&mut self, c: char) -> Option<(FontIndex, Length)> {
let mut loader = self.ctx.loader.borrow_mut();
let mut variant = self.ctx.style.variant;
@ -127,8 +126,8 @@ impl<'a> TextLayouter<'a> {
if let Some((font, index)) = loader.get(query).await {
// Determine the width of the char.
let header = font.read_table::<Header>().ok()?;
let font_unit_ratio = 1.0 / (header.units_per_em as f32);
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
let font_unit_ratio = 1.0 / (header.units_per_em as f64);
let font_unit_to_size = |x| Length::pt(font_unit_ratio * x);
let glyph = font
.read_table::<CharMap>()
@ -139,7 +138,7 @@ impl<'a> TextLayouter<'a> {
.read_table::<HorizontalMetrics>()
.ok()?
.get(glyph)?
.advance_width as f32;
.advance_width as f64;
let char_width = font_unit_to_size(glyph_width)
* self.ctx.style.font_size().to_pt();

View File

@ -8,173 +8,166 @@ use serde::Serialize;
use crate::layout::prelude::*;
/// A general spacing type.
#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct Size {
/// The size in typographic points (1/72 inches).
pub points: f32,
pub struct Length {
/// The length in typographic points (1/72 inches).
pub points: f64,
}
impl Size {
/// The zeroed size.
pub const ZERO: Size = Size { points: 0.0 };
impl Length {
/// The zeroed length.
pub const ZERO: Length = Length { points: 0.0 };
/// Create a size from an amount of points.
pub fn pt(points: f32) -> Size { Size { points } }
/// Create a length from an amount of points.
pub fn pt(points: f64) -> Length { Length { points } }
/// Create a size from an amount of millimeters.
pub fn mm(mm: f32) -> Size { Size { points: 2.83465 * mm } }
/// Create a length from an amount of millimeters.
pub fn mm(mm: f64) -> Length { Length { points: 2.83465 * mm } }
/// Create a size from an amount of centimeters.
pub fn cm(cm: f32) -> Size { Size { points: 28.3465 * cm } }
/// Create a length from an amount of centimeters.
pub fn cm(cm: f64) -> Length { Length { points: 28.3465 * cm } }
/// Create a size from an amount of inches.
pub fn inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
/// Create a length from an amount of inches.
pub fn inches(inches: f64) -> Length { Length { points: 72.0 * inches } }
/// Convert this size into points.
pub fn to_pt(self) -> f32 { self.points }
/// Convert this length into points.
pub fn to_pt(self) -> f64 { self.points }
/// Convert this size into millimeters.
pub fn to_mm(self) -> f32 { self.points * 0.352778 }
/// Convert this length into millimeters.
pub fn to_mm(self) -> f64 { self.points * 0.352778 }
/// Convert this size into centimeters.
pub fn to_cm(self) -> f32 { self.points * 0.0352778 }
/// Convert this length into centimeters.
pub fn to_cm(self) -> f64 { self.points * 0.0352778 }
/// Convert this size into inches.
pub fn to_inches(self) -> f32 { self.points * 0.0138889 }
/// Convert this length into inches.
pub fn to_inches(self) -> f64 { self.points * 0.0138889 }
/// The maximum of this and the other size.
pub fn max(self, other: Size) -> Size {
/// The maximum of this and the other length.
pub fn max(self, other: Length) -> Length {
if self > other { self } else { other }
}
/// The minimum of this and the other size.
pub fn min(self, other: Size) -> Size {
/// The minimum of this and the other length.
pub fn min(self, other: Length) -> Length {
if self <= other { self } else { other }
}
/// Set this size to the maximum of itself and the other size.
pub fn max_eq(&mut self, other: Size) { *self = self.max(other); }
/// Set this length to the maximum of itself and the other length.
pub fn max_eq(&mut self, other: Length) { *self = self.max(other); }
/// Set this size to the minimum of itself and the other size.
pub fn min_eq(&mut self, other: Size) { *self = self.min(other); }
/// Set this length to the minimum of itself and the other length.
pub fn min_eq(&mut self, other: Length) { *self = self.min(other); }
/// The anchor position along the given direction for an item with the given
/// alignment in a container with this size.
pub fn anchor(self, alignment: Alignment, direction: Direction) -> Size {
/// alignment in a container with this length.
pub fn anchor(self, alignment: Alignment, direction: Direction) -> Length {
match (direction.is_positive(), alignment) {
(true, Origin) | (false, End) => Size::ZERO,
(true, Origin) | (false, End) => Length::ZERO,
(_, Center) => self / 2,
(true, End) | (false, Origin) => self,
}
}
}
impl Display for Size {
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}pt", self.points)
}
}
impl Debug for Size {
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Neg for Size {
type Output = Size;
impl Neg for Length {
type Output = Length;
fn neg(self) -> Size {
Size { points: -self.points }
fn neg(self) -> Length {
Length { points: -self.points }
}
}
impl Sum for Size {
fn sum<I>(iter: I) -> Size
where I: Iterator<Item = Size> {
iter.fold(Size::ZERO, Add::add)
impl Sum for Length {
fn sum<I>(iter: I) -> Length
where I: Iterator<Item = Length> {
iter.fold(Length::ZERO, Add::add)
}
}
/// Either an absolute size or a factor of some entity.
/// Either an absolute length or a factor of some entity.
#[derive(Copy, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum ScaleSize {
Absolute(Size),
Scaled(f32),
pub enum ScaleLength {
Absolute(Length),
Scaled(f64),
}
impl ScaleSize {
impl ScaleLength {
/// Use the absolute value or scale the entity.
pub fn scaled(&self, entity: Size) -> Size {
pub fn scaled(&self, entity: Length) -> Length {
match self {
ScaleSize::Absolute(s) => *s,
ScaleSize::Scaled(s) => *s * entity,
ScaleLength::Absolute(s) => *s,
ScaleLength::Scaled(s) => *s * entity,
}
}
}
impl Display for ScaleSize {
impl Display for ScaleLength {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ScaleSize::Absolute(size) => write!(f, "{}", size),
ScaleSize::Scaled(scale) => write!(f, "{}%", scale * 100.0),
ScaleLength::Absolute(length) => write!(f, "{}", length),
ScaleLength::Scaled(scale) => write!(f, "{}%", scale * 100.0),
}
}
}
impl Debug for ScaleSize {
impl Debug for ScaleLength {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
/// A scale size that is scaled by the font size.
pub type FSize = ScaleSize;
/// A scale size that is scaled by the size of the padded parent container.
pub type PSize = ScaleSize;
/// A value in two dimensions.
#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct Value2D<T> {
pub struct Value2<T> {
/// The horizontal component.
pub x: T,
/// The vertical component.
pub y: T,
}
impl<T: Clone> Value2D<T> {
impl<T: Clone> Value2<T> {
/// Create a new 2D-value from two values.
pub fn new(x: T, y: T) -> Value2D<T> { Value2D { x, y } }
pub fn new(x: T, y: T) -> Value2<T> { Value2 { x, y } }
/// Create a new 2D-value with `x` set to a value and `y` to default.
pub fn with_x(x: T) -> Value2D<T> where T: Default {
Value2D { x, y: T::default() }
pub fn with_x(x: T) -> Value2<T> where T: Default {
Value2 { x, y: T::default() }
}
/// Create a new 2D-value with `y` set to a value and `x` to default.
pub fn with_y(y: T) -> Value2D<T> where T: Default {
Value2D { x: T::default(), y }
pub fn with_y(y: T) -> Value2<T> where T: Default {
Value2 { x: T::default(), y }
}
/// Create a new 2D-value with the primary axis set to a value and the other
/// one to default.
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
Value2D::with_x(v).generalized(axes)
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
Value2::with_x(v).generalized(axes)
}
/// Create a new 2D-value with the secondary axis set to a value and the
/// other one to default.
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
Value2D::with_y(v).generalized(axes)
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
Value2::with_y(v).generalized(axes)
}
/// Create a 2D-value with `x` and `y` set to the same value `s`.
pub fn with_all(s: T) -> Value2D<T> { Value2D { x: s.clone(), y: s } }
pub fn with_all(s: T) -> Value2<T> { Value2 { x: s.clone(), y: s } }
/// Get the specificed component.
pub fn get(self, axis: SpecificAxis) -> T {
@ -216,16 +209,16 @@ impl<T: Clone> Value2D<T> {
/// axes, that is:
/// - `x` describes the primary axis instead of the horizontal one.
/// - `y` describes the secondary axis instead of the vertical one.
pub fn generalized(self, axes: LayoutAxes) -> Value2D<T> {
pub fn generalized(self, axes: LayoutAxes) -> Value2<T> {
match axes.primary.axis() {
Horizontal => self,
Vertical => Value2D { x: self.y, y: self.x },
Vertical => Value2 { x: self.y, y: self.x },
}
}
/// Returns the specialized version of this generalized Size2D (inverse to
/// `generalized`).
pub fn specialized(self, axes: LayoutAxes) -> Value2D<T> {
pub fn specialized(self, axes: LayoutAxes) -> Value2<T> {
// In fact, generalized is its own inverse. For reasons of clarity
// at the call site, we still have this second function.
self.generalized(axes)
@ -237,7 +230,7 @@ impl<T: Clone> Value2D<T> {
}
}
impl<T> Debug for Value2D<T> where T: Debug {
impl<T> Debug for Value2<T> where T: Debug {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_list()
.entry(&self.x)
@ -247,83 +240,61 @@ impl<T> Debug for Value2D<T> where T: Debug {
}
/// A position or extent in 2-dimensional space.
pub type Size2D = Value2D<Size>;
pub type Size = Value2<Length>;
impl Size2D {
/// The zeroed 2D-size.
pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO };
impl Size {
/// The zeroed 2D-length.
pub const ZERO: Size = Size { x: Length::ZERO, y: Length::ZERO };
/// Whether the given 2D-size fits into this one, that is, both coordinate
/// Whether the given 2D-length fits into this one, that is, both coordinate
/// values are smaller or equal.
pub fn fits(self, other: Size2D) -> bool {
pub fn fits(self, other: Size) -> bool {
self.x >= other.x && self.y >= other.y
}
/// Return a 2D-size padded by the paddings of the given box.
pub fn padded(self, padding: SizeBox) -> Size2D {
Size2D {
/// Return a 2D-length padded by the paddings of the given box.
pub fn padded(self, padding: Margins) -> Size {
Size {
x: self.x + padding.left + padding.right,
y: self.y + padding.top + padding.bottom,
}
}
/// Return a 2D-size reduced by the paddings of the given box.
pub fn unpadded(self, padding: SizeBox) -> Size2D {
Size2D {
/// Return a 2D-length reduced by the paddings of the given box.
pub fn unpadded(self, padding: Margins) -> Size {
Size {
x: self.x - padding.left - padding.right,
y: self.y - padding.top - padding.bottom,
}
}
/// The anchor position along the given axis for an item with the given
/// alignment in a container with this size.
/// alignment in a container with this length.
///
/// This assumes the size to be generalized such that `x` corresponds to the
/// This assumes the length to be generalized such that `x` corresponds to the
/// primary axis.
pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size2D {
Size2D {
pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size {
Size {
x: self.x.anchor(alignment.primary, axes.primary),
y: self.y.anchor(alignment.secondary, axes.secondary),
}
}
}
impl Neg for Size2D {
type Output = Size2D;
impl Neg for Size {
type Output = Size;
fn neg(self) -> Size2D {
Size2D {
fn neg(self) -> Size {
Size {
x: -self.x,
y: -self.y,
}
}
}
/// A value that is stretchable in an interval from a minimal through an optimal
/// to a maximal value.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct StretchValue<T> {
/// The minimum this value can be stretched to.
pub min: T,
/// The optimum for this value.
pub opt: T,
/// The maximum this value can be stretched to.
pub max: T,
}
impl<T> StretchValue<T> {
/// Create a new stretch size from minimum, optimal and maximum values.
pub fn new(min: T, opt: T, max: T) -> StretchValue<T> {
StretchValue { min, opt, max }
}
}
/// A size that is stretchable.
pub type StretchSize = StretchValue<Size>;
/// A value in four dimensions.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)]
pub struct ValueBox<T> {
pub struct Value4<T> {
/// The left extent.
pub left: T,
/// The top extent.
@ -334,15 +305,15 @@ pub struct ValueBox<T> {
pub bottom: T,
}
impl<T: Clone> ValueBox<T> {
impl<T: Clone> Value4<T> {
/// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox<T> {
ValueBox { left, top, right, bottom }
pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> {
Value4 { left, top, right, bottom }
}
/// Create a box with all four fields set to the same value `s`.
pub fn with_all(value: T) -> ValueBox<T> {
ValueBox {
pub fn with_all(value: T) -> Value4<T> {
Value4 {
left: value.clone(),
top: value.clone(),
right: value.clone(),
@ -369,7 +340,7 @@ impl<T: Clone> ValueBox<T> {
/// Set all values to the given value.
pub fn set_all(&mut self, value: T) {
*self = ValueBox::with_all(value);
*self = Value4::with_all(value);
}
/// Set the `left` and `right` values.
@ -385,47 +356,47 @@ impl<T: Clone> ValueBox<T> {
}
}
/// A size in four dimensions.
pub type SizeBox = ValueBox<Size>;
/// A length in four dimensions.
pub type Margins = Value4<Length>;
impl SizeBox {
/// The zeroed size box.
pub const ZERO: SizeBox = SizeBox {
left: Size::ZERO,
top: Size::ZERO,
right: Size::ZERO,
bottom: Size::ZERO,
impl Margins {
/// The zeroed length box.
pub const ZERO: Margins = Margins {
left: Length::ZERO,
top: Length::ZERO,
right: Length::ZERO,
bottom: Length::ZERO,
};
}
impl FromStr for Size {
type Err = ParseSizeError;
impl FromStr for Length {
type Err = ParseLengthError;
fn from_str(src: &str) -> Result<Size, ParseSizeError> {
fn from_str(src: &str) -> Result<Length, ParseLengthError> {
let func = match () {
_ if src.ends_with("pt") => Size::pt,
_ if src.ends_with("mm") => Size::mm,
_ if src.ends_with("cm") => Size::cm,
_ if src.ends_with("in") => Size::inches,
_ => return Err(ParseSizeError(())),
_ if src.ends_with("pt") => Length::pt,
_ if src.ends_with("mm") => Length::mm,
_ if src.ends_with("cm") => Length::cm,
_ if src.ends_with("in") => Length::inches,
_ => return Err(ParseLengthError),
};
Ok(func(src[..src.len() - 2]
.parse::<f32>()
.map_err(|_| ParseSizeError(()))?))
.parse::<f64>()
.map_err(|_| ParseLengthError)?))
}
}
/// An error which can be returned when parsing a size.
/// An error which can be returned when parsing a length.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParseSizeError(());
pub struct ParseLengthError;
impl std::error::Error for ParseSizeError {}
impl std::error::Error for ParseLengthError {}
impl Display for ParseSizeError {
impl Display for ParseLengthError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("invalid string for size")
f.write_str("invalid string for length")
}
}
@ -448,7 +419,7 @@ macro_rules! implement_traits {
})*
$(implement_traits!(@$w i32, $ty $t $o $($rest)*);)*
$(implement_traits!(@$w f32, $ty $t $o $($rest)*);)*
$(implement_traits!(@$w f64, $ty $t $o $($rest)*);)*
};
(@front $num:ty, $ty:ident $t:ident $o:ident
@ -458,7 +429,7 @@ macro_rules! implement_traits {
impl $tr<$ty> for $num {
type Output = $ty;
fn $tf($t, $o: $ty) -> $ty {
$ty { $($f: $tr::$tf($t as f32, $o.$f),)* }
$ty { $($f: $tr::$tf($t as f64, $o.$f),)* }
}
}
};
@ -470,12 +441,12 @@ macro_rules! implement_traits {
impl $tr<$num> for $ty {
type Output = $ty;
fn $tf($t, $o: $num) -> $ty {
$ty { $($f: $tr::$tf($t.$f, $o as f32),)* }
$ty { $($f: $tr::$tf($t.$f, $o as f64),)* }
}
}
impl $at<$num> for $ty {
fn $af(&mut $t, $o: $num) { $($at::$af(&mut $t.$f, $o as f32);)* }
fn $af(&mut $t, $o: $num) { $($at::$af(&mut $t.$f, $o as f64);)* }
}
};
}
@ -499,5 +470,5 @@ macro_rules! implement_size {
};
}
implement_size! { Size(self, other) [points] }
implement_size! { Size2D(self, other) [x, y] }
implement_size! { Length(self, other) [points] }
implement_size! { Size(self, other) [x, y] }

View File

@ -27,12 +27,11 @@ use toddle::{Font, OwnedData};
use toddle::query::{FontLoader, SharedFontLoader};
use toddle::query::{FontProvider, FontIndex, FontDescriptor};
use crate::problem::Problems;
use crate::diagnostic::Diagnostics;
use crate::layout::MultiLayout;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::{SyntaxModel, Scope, Decoration, ParseState, parse};
use crate::syntax::span::{Position, SpanVec, offset_spans};
use crate::syntax::{Decorations, SyntaxModel, Scope, ParseState, parse};
use crate::syntax::span::{Offset, Pos};
/// Declare a module and reexport all its contents.
macro_rules! pub_use_mod {
@ -45,17 +44,17 @@ macro_rules! pub_use_mod {
#[macro_use]
mod macros;
#[macro_use]
pub mod problem;
pub mod diagnostic;
pub mod export;
#[macro_use]
pub mod func;
pub mod layout;
pub mod library;
pub mod size;
pub mod length;
pub mod paper;
pub mod style;
pub mod syntax;
/// Transforms source code into typesetted layouts.
///
/// A typesetter can be configured through various methods.
@ -112,7 +111,7 @@ impl Typesetter {
/// Parse source code into a syntax tree.
pub fn parse(&self, src: &str) -> Pass<SyntaxModel> {
parse(src, Position::ZERO, &self.parse_state)
parse(src, Pos::ZERO, &self.parse_state)
}
/// Layout a syntax tree and return the produced layout.
@ -176,18 +175,18 @@ impl<T> Pass<T> {
/// User feedback data accumulated during a compilation pass.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Feedback {
/// Problems in the source code.
pub problems: Problems,
/// Diagnostics in the source code.
pub diagnostics: Diagnostics,
/// Decorations of the source code for semantic syntax highlighting.
pub decos: SpanVec<Decoration>,
pub decorations: Decorations,
}
impl Feedback {
/// Create a new feedback instance without errors and decos.
pub fn new() -> Feedback {
Feedback {
problems: vec![],
decos: vec![],
diagnostics: vec![],
decorations: vec![],
}
}
@ -199,15 +198,15 @@ impl Feedback {
/// Add other feedback data to this feedback.
pub fn extend(&mut self, other: Feedback) {
self.problems.extend(other.problems);
self.decos.extend(other.decos);
self.diagnostics.extend(other.diagnostics);
self.decorations.extend(other.decorations);
}
/// Add more feedback whose spans are local and need to be offset by an
/// `offset` to be correct in this feedback's context.
pub fn extend_offset(&mut self, more: Feedback, offset: Position) {
self.problems.extend(offset_spans(more.problems, offset));
self.decos.extend(offset_spans(more.decos, offset));
pub fn extend_offset(&mut self, more: Feedback, offset: Pos) {
self.diagnostics.extend(more.diagnostics.offset(offset));
self.decorations.extend(more.decorations.offset(offset));
}
}

View File

@ -1,8 +1,7 @@
use toddle::query::{FontWeight, FontStyle};
use crate::size::FSize;
use crate::length::ScaleLength;
use super::*;
function! {
/// `font.family`: Set the font family.
#[derive(Debug, Clone, PartialEq)]
@ -13,17 +12,17 @@ function! {
}
parse(header, body, ctx, f) {
let list = header.args.pos.get_all::<StringLike>(&mut f.problems)
let list = header.args.pos.get_all::<StringLike>(&mut f.diagnostics)
.map(|s| s.0.to_lowercase())
.collect();
let tuples: Vec<_> = header.args.key
.get_all::<String, Tuple>(&mut f.problems)
.get_all::<String, Tuple>(&mut f.diagnostics)
.collect();
let classes = tuples.into_iter()
.map(|(class, mut tuple)| {
let fallback = tuple.get_all::<StringLike>(&mut f.problems)
let fallback = tuple.get_all::<StringLike>(&mut f.diagnostics)
.map(|s| s.0.to_lowercase())
.collect();
(class.to_lowercase(), fallback)
@ -64,8 +63,8 @@ function! {
parse(header, body, ctx, f) {
FontStyleFunc {
body: body!(opt: body, ctx, f),
style: header.args.pos.get::<FontStyle>(&mut f.problems)
.or_missing(&mut f.problems, header.name.span, "style"),
style: header.args.pos.get::<FontStyle>(&mut f.diagnostics)
.or_missing(&mut f.diagnostics, header.name.span, "style"),
}
}
@ -84,7 +83,7 @@ function! {
parse(header, body, ctx, f) {
let body = body!(opt: body, ctx, f);
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.problems)
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.diagnostics)
.map(|Spanned { v: (weight, is_clamped), span }| {
if is_clamped {
warning!(
@ -96,7 +95,7 @@ function! {
weight
})
.or_missing(&mut f.problems, header.name.span, "weight");
.or_missing(&mut f.diagnostics, header.name.span, "weight");
FontWeightFunc { body, weight }
}
@ -111,25 +110,25 @@ function! {
#[derive(Debug, Clone, PartialEq)]
pub struct FontSizeFunc {
body: Option<SyntaxModel>,
size: Option<FSize>,
size: Option<ScaleLength>,
}
parse(header, body, ctx, f) {
FontSizeFunc {
body: body!(opt: body, ctx, f),
size: header.args.pos.get::<FSize>(&mut f.problems)
.or_missing(&mut f.problems, header.name.span, "size")
size: header.args.pos.get::<ScaleLength>(&mut f.diagnostics)
.or_missing(&mut f.diagnostics, header.name.span, "size")
}
}
layout(self, ctx, f) {
styled(&self.body, ctx, self.size, |t, s| {
match s {
FSize::Absolute(size) => {
ScaleLength::Absolute(size) => {
t.base_font_size = size;
t.font_scale = 1.0;
}
FSize::Scaled(scale) => t.font_scale = scale,
ScaleLength::Scaled(scale) => t.font_scale = scale,
}
})
}

View File

@ -1,7 +1,6 @@
use crate::size::PSize;
use crate::length::ScaleLength;
use super::*;
function! {
/// `align`: Aligns content along the layouting axes.
#[derive(Debug, Clone, PartialEq)]
@ -13,14 +12,14 @@ function! {
parse(header, body, ctx, f) {
AlignFunc {
body: body!(opt: body, ctx, f),
map: PosAxisMap::parse::<AxisKey>(&mut f.problems, &mut header.args),
map: PosAxisMap::parse::<AxisKey>(&mut f.diagnostics, &mut header.args),
}
}
layout(self, ctx, f) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(&mut f.problems, ctx.axes, |alignment| {
let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |alignment| {
alignment.axis().map(|s| s.to_generic(ctx.axes))
});
@ -61,14 +60,14 @@ function! {
DirectionFunc {
name_span: header.name.span,
body: body!(opt: body, ctx, f),
map: PosAxisMap::parse::<AxisKey>(&mut f.problems, &mut header.args),
map: PosAxisMap::parse::<AxisKey>(&mut f.diagnostics, &mut header.args),
}
}
layout(self, ctx, f) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(&mut f.problems, ctx.axes, |direction| {
let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |direction| {
Some(direction.axis().to_generic(ctx.axes))
});
@ -103,15 +102,15 @@ function! {
#[derive(Debug, Clone, PartialEq)]
pub struct BoxFunc {
body: SyntaxModel,
extents: AxisMap<PSize>,
extents: AxisMap<ScaleLength>,
debug: Option<bool>,
}
parse(header, body, ctx, f) {
BoxFunc {
body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()),
extents: AxisMap::parse::<ExtentKey>(&mut f.problems, &mut header.args.key),
debug: header.args.key.get::<bool>(&mut f.problems, "debug"),
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
debug: header.args.key.get::<bool>(&mut f.diagnostics, "debug"),
}
}
@ -123,12 +122,12 @@ function! {
ctx.debug = debug;
}
let map = self.extents.dedup(&mut f.problems, ctx.axes);
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
for &axis in &[Horizontal, Vertical] {
if let Some(psize) = map.get(axis) {
let size = psize.scaled(ctx.base.get(axis));
*ctx.base.get_mut(axis) = size;
*ctx.spaces[0].dimensions.get_mut(axis) = size;
if let Some(scale) = map.get(axis) {
let length = scale.scaled(ctx.base.get(axis));
*ctx.base.get_mut(axis) = length;
*ctx.spaces[0].dimensions.get_mut(axis) = length;
*ctx.spaces[0].expansion.get_mut(axis) = true;
}
}

View File

@ -8,7 +8,6 @@ pub_use_mod!(layout);
pub_use_mod!(page);
pub_use_mod!(spacing);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
let mut std = Scope::new::<ValFunc>();

View File

@ -1,23 +1,22 @@
use crate::size::Size;
use crate::style::{Paper, PaperClass};
use crate::length::Length;
use crate::paper::{Paper, PaperClass};
use super::*;
function! {
/// `page.size`: Set the size of pages.
#[derive(Debug, Clone, PartialEq)]
pub struct PageSizeFunc {
paper: Option<Paper>,
extents: AxisMap<Size>,
extents: AxisMap<Length>,
flip: bool,
}
parse(header, body, state, f) {
body!(nope: body, f);
PageSizeFunc {
paper: header.args.pos.get::<Paper>(&mut f.problems),
extents: AxisMap::parse::<ExtentKey>(&mut f.problems, &mut header.args.key),
flip: header.args.key.get::<bool>(&mut f.problems, "flip").unwrap_or(false),
paper: header.args.pos.get::<Paper>(&mut f.diagnostics),
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
flip: header.args.key.get::<bool>(&mut f.diagnostics, "flip").unwrap_or(false),
}
}
@ -26,12 +25,12 @@ function! {
if let Some(paper) = self.paper {
style.class = paper.class;
style.dimensions = paper.dimensions;
style.dimensions = paper.size();
} else {
style.class = PaperClass::Custom;
}
let map = self.extents.dedup(&mut f.problems, ctx.axes);
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
map.with(Horizontal, |&width| style.dimensions.x = width);
map.with(Vertical, |&height| style.dimensions.y = height);
@ -53,13 +52,13 @@ function! {
parse(header, body, state, f) {
body!(nope: body, f);
PageMarginsFunc {
padding: PaddingMap::parse(&mut f.problems, &mut header.args),
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
}
}
layout(self, ctx, f) {
let mut style = ctx.style.page;
self.padding.apply(&mut f.problems, ctx.axes, &mut style.margins);
self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins);
vec![SetPageStyle(style)]
}
}

View File

@ -1,10 +1,9 @@
use crate::size::FSize;
use crate::length::ScaleLength;
use crate::layout::SpacingKind;
use super::*;
use self::ContentKind::*;
function! {
/// `line.break`, `n`: Ends the current line.
#[derive(Debug, Default, Clone, PartialEq)]
@ -41,7 +40,7 @@ function! {
pub struct ContentSpacingFunc {
body: Option<SyntaxModel>,
content: ContentKind,
spacing: Option<f32>,
spacing: Option<f64>,
}
type Meta = ContentKind;
@ -50,9 +49,9 @@ function! {
ContentSpacingFunc {
body: body!(opt: body, state, f),
content: meta,
spacing: header.args.pos.get::<f64>(&mut f.problems)
.map(|num| num as f32)
.or_missing(&mut f.problems, header.name.span, "spacing"),
spacing: header.args.pos.get::<f64>(&mut f.diagnostics)
.map(|num| num as f64)
.or_missing(&mut f.diagnostics, header.name.span, "spacing"),
}
}
@ -79,7 +78,7 @@ function! {
/// `spacing`, `h`, `v`: Adds spacing along an axis.
#[derive(Debug, Clone, PartialEq)]
pub struct SpacingFunc {
spacing: Option<(AxisKey, FSize)>,
spacing: Option<(AxisKey, ScaleLength)>,
}
type Meta = Option<SpecificAxis>;
@ -88,11 +87,11 @@ function! {
body!(nope: body, f);
SpacingFunc {
spacing: if let Some(axis) = meta {
header.args.pos.get::<FSize>(&mut f.problems)
header.args.pos.get::<ScaleLength>(&mut f.diagnostics)
.map(|s| (AxisKey::Specific(axis), s))
} else {
header.args.key.get_with_key::<AxisKey, FSize>(&mut f.problems)
}.or_missing(&mut f.problems, header.name.span, "spacing"),
header.args.key.get_with_key::<AxisKey, ScaleLength>(&mut f.diagnostics)
}.or_missing(&mut f.diagnostics, header.name.span, "spacing"),
}
}

275
src/paper.rs Normal file
View File

@ -0,0 +1,275 @@
//! Predefined papers.
use crate::length::{Length, Size, Value4, ScaleLength};
/// Specification of a paper.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper {
/// The kind of paper, which defines the default margins.
pub class: PaperClass,
/// The width of the paper.
pub width: Length,
/// The height of the paper.
pub height: Length,
}
impl Paper {
/// The paper with the given name.
pub fn from_name(name: &str) -> Option<Paper> {
parse_paper(name)
}
/// The size of the paper.
pub fn size(self) -> Size {
Size::new(self.width, self.height)
}
}
/// Paper classes define default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PaperClass {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
pub fn default_margins(self) -> Value4<ScaleLength> {
use PaperClass::*;
let values = |l, t, r, b| Value4::new(
ScaleLength::Scaled(l),
ScaleLength::Scaled(t),
ScaleLength::Scaled(r),
ScaleLength::Scaled(b),
);
match self {
Custom => values(0.1190, 0.0842, 0.1190, 0.0842),
Base => values(0.1190, 0.0842, 0.1190, 0.0842),
US => values(0.1760, 0.1092, 0.1760, 0.0910),
Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294),
Book => values(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
macro_rules! papers {
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
$(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)*
fn parse_paper(paper: &str) -> Option<Paper> {
match paper.to_lowercase().as_str() {
$($($pats)* => Some($var),)*
_ => None,
}
}
};
(@$var:ident, $names:expr, $class:ident, $width:expr, $height:expr) => {
#[doc = "Paper with name `"]
#[doc = $names]
#[doc = "`."]
pub const $var: Paper = Paper {
width: Length { points: 2.83465 * $width },
height: Length { points: 2.83465 * $height },
class: PaperClass::$class,
};
};
}
// All paper sizes in mm.
papers! {
// ---------------------------------------------------------------------- //
// ISO 216
// A Series
(PAPER_A0: Base, 841.0, 1189.0, "a0" | "poster")
(PAPER_A1: Base, 594.0, 841.0, "a1")
(PAPER_A2: Base, 420.0, 594.0, "a2")
(PAPER_A3: Base, 297.0, 420.0, "a3")
(PAPER_A4: Base, 210.0, 297.0, "a4")
(PAPER_A5: Base, 148.0, 210.0, "a5")
(PAPER_A6: Book, 105.0, 148.0, "a6")
(PAPER_A7: Base, 74.0, 105.0, "a7" | "iso-7810-id-2" | "id-2" | "visa" | "flyer")
(PAPER_A8: Base, 52.0, 74.0, "a8")
(PAPER_A9: Base, 37.0, 52.0, "a9")
(PAPER_A10: Base, 26.0, 37.0, "a10")
(PAPER_A11: Base, 18.0, 26.0, "a11")
// B Series
(PAPER_B1: Base, 707.0, 1000.0, "b1" | "flipchart")
(PAPER_B2: Base, 500.0, 707.0, "b2")
(PAPER_B3: Base, 353.0, 500.0, "b3")
(PAPER_B4: Base, 250.0, 353.0, "b4" | "sheet-music")
(PAPER_B5: Book, 176.0, 250.0, "b5")
(PAPER_B6: Book, 125.0, 176.0, "b6" | "book")
(PAPER_B7: Base, 88.0, 125.0, "b7" | "passport" | "iso-7810-id-3" | "id-3")
(PAPER_B8: Base, 62.0, 88.0, "b8")
// C Series
(PAPER_C3: Base, 324.0, 458.0, "c3")
(PAPER_C4: Base, 229.0, 324.0, "c4")
(PAPER_C5: Base, 162.0, 229.0, "c5")
(PAPER_C6: Base, 114.0, 162.0, "c6")
(PAPER_C7: Base, 81.0, 114.0, "c7")
(PAPER_C8: Base, 57.0, 81.0, "c8")
// D Series (DIN extension to ISO)
(PAPER_D3: Base, 272.0, 385.0, "din-d3")
(PAPER_D4: Base, 192.0, 272.0, "din-d4")
(PAPER_D5: Base, 136.0, 192.0, "din-d5" | "dvd")
(PAPER_D6: Base, 96.0, 136.0, "din-d6")
(PAPER_D7: Base, 68.0, 96.0, "din-d7")
(PAPER_D8: Base, 48.0, 68.0, "din-d8")
// Academically relevant SIS extensions
(PAPER_G5: Base, 169.0, 239.0, "sis-g5")
(PAPER_E5: Base, 115.0, 220.0, "sis-e5")
// ---------------------------------------------------------------------- //
// Unites States
// Customary
(PAPER_FOLIO: US, 210.0, 330.0, "folio" | "us-folio" | "us-f4")
(PAPER_LETTER: US, 216.0, 279.0, "letter" | "ansi-a" |
"american-quarto" | "carta")
(PAPER_LEGAL: US, 216.0, 356.0, "legal")
(PAPER_TABLOID: Newspaper, 279.0, 432.0, "tabloid" | "ansi-b")
(PAPER_LEDGER: Base, 432.0, 279.0, "ledger")
(PAPER_JUNIOR_LEGAL: US, 127.0, 203.0, "junior-legal" | "index-card")
(PAPER_HALF_LETTER: Base, 140.0, 216.0, "half-letter")
(PAPER_GOVERNMENT_LETTER: US, 203.0, 267.0, "government-letter")
(PAPER_GOVERNMENT_LEGAL: US, 216.0, 330.0, "government-legal" | "officio")
// ANSI Extensions
(PAPER_ANSI_C: Base, 432.0, 559.0, "ansi-c")
(PAPER_ANSI_D: Base, 559.0, 864.0, "ansi-d")
(PAPER_ANSI_E: Base, 864.0, 1118.0, "ansi-e")
(PAPER_ENGINEERING_F: Base, 711.0, 1016.0, "engineering-f" | "engineering" |
"navfac" | "aerospace")
// Architectural Paper
(PAPER_ARCH_A: Base, 229.0, 305.0, "arch-a" | "arch-1")
(PAPER_ARCH_B: Base, 305.0, 457.0, "arch-b" | "arch-2" | "extra-tabloide")
(PAPER_ARCH_C: Base, 457.0, 610.0, "arch-c" | "arch-3")
(PAPER_ARCH_D: Base, 610.0, 914.0, "arch-d" | "arch-4")
(PAPER_ARCH_E1: Base, 762.0, 1067.0, "arch-e1" | "arch-5")
(PAPER_ARCH_E: Base, 914.0, 1219.0, "arch-e" | "arch-6")
// ---------------------------------------------------------------------- //
// Japan
// JIS B Series
(PAPER_JIS_B0: Base, 1030.0, 1456.0, "jis-b0" | "jb0")
(PAPER_JIS_B1: Base, 728.0, 1030.0, "jis-b1" | "jb1")
(PAPER_JIS_B2: Base, 515.0, 728.0, "jis-b2" | "jb2")
(PAPER_JIS_B3: Base, 364.0, 515.0, "jis-b3" | "jb3")
(PAPER_JIS_B4: Base, 257.0, 364.0, "jis-b4" | "jb4")
(PAPER_JIS_B5: Base, 182.0, 257.0, "jis-b5" | "jb5")
(PAPER_JIS_B6: Base, 128.0, 182.0, "jis-b6" | "jb6")
(PAPER_JIS_B7: Base, 91.0, 128.0, "jis-b7" | "jb7")
(PAPER_JIS_B8: Base, 64.0, 91.0, "jis-b8" | "jb8")
(PAPER_JIS_B9: Base, 45.0, 64.0, "jis-b9" | "jb9")
(PAPER_JIS_B10: Base, 32.0, 45.0, "jis-b10" | "jb10")
(PAPER_JIS_B11: Base, 22.0, 32.0, "jis-b11" | "jb11")
// Traditional
(PAPER_SHIROKU_BAN_4: Base, 264.0, 379.0, "shiroku-ban-4")
(PAPER_SHIROKU_BAN_5: Base, 189.0, 262.0, "shiroku-ban-5")
(PAPER_SHIROKU_BAN_6: Base, 127.0, 188.0, "shiroku-ban-6")
(PAPER_KIKU_4: Base, 227.0, 306.0, "kiku-4")
(PAPER_KIKU_5: Base, 151.0, 227.0, "kiku-5")
// ---------------------------------------------------------------------- //
// China
// Chinese D Series
(PAPER_SAC_D0: Base, 764.0, 1064.0, "sac-d0" | "cn-d0")
(PAPER_SAC_D1: Base, 532.0, 760.0, "sac-d1" | "cn-d1")
(PAPER_SAC_D2: Base, 380.0, 528.0, "sac-d2" | "cn-d2")
(PAPER_SAC_D3: Base, 264.0, 376.0, "sac-d3" | "cn-d3")
(PAPER_SAC_D4: Base, 188.0, 260.0, "sac-d4" | "cn-d4")
(PAPER_SAC_D5: Base, 130.0, 184.0, "sac-d5" | "cn-d5")
(PAPER_SAC_D6: Base, 92.0, 126.0, "sac-d6" | "cn-d6")
// ---------------------------------------------------------------------- //
// United Kingdom Imperial (Assortment)
(PAPER_MONARCH: Base, 184.0, 267.0, "monarch")
(PAPER_QUARTO: Base, 229.0, 279.0, "quarto" | "us-quarto")
(PAPER_UK_QUARTO: Base, 203.0, 254.0, "uk-quarto" | "imperial-quarto")
(PAPER_UK_FOOLSCAP: Base, 343.0, 432.0, "foolscap" | "us-foolscap")
(PAPER_FOOLSCAP: Base, 203.0, 330.0, "imperial-foolscap" | "uk-foolscap")
(PAPER_POTT: Base, 318.0, 381.0, "pott")
(PAPER_CROWN: Base, 318.0, 508.0, "crown")
(PAPER_PINCHED_POST: Base, 375.0, 470.0, "pinched-post")
(PAPER_POST: Base, 394.0, 489.0, "post")
(PAPER_LARGE_POST: Base, 419.0, 533.0, "large-post")
(PAPER_DEMY: Base, 445.0, 572.0, "demy")
(PAPER_ROYAL: Base, 508.0, 635.0, "royal")
(PAPER_DOUBLE_CROWN: Base, 508.0, 762.0, "double-crown" | "theatre")
(PAPER_ELEPHANT: Base, 584.0, 711.0, "elephant")
(PAPER_DOUBLE_ROYAL: Base, 635.0, 1016.0, "double-royal" | "rail")
(PAPER_QUAD_CROWN: Base, 762.0, 1016.0, "quad-crown" | "cinema")
// ---------------------------------------------------------------------- //
// French Traditional (AFNOR)
(PAPER_CLOCHE: Base, 300.0, 400.0, "cloche")
(PAPER_POT: Base, 310.0, 400.0, "pot" | "ecolier" | "écolier")
(PAPER_TELLIERE: Base, 340.0, 440.0, "telliere" | "tellière")
(PAPER_COURONNE_ECRITURE: Base, 360.0, 460.0, "couronne-ecriture" |
"couronne" | "couronne-écriture")
(PAPER_COURONNE_EDITION: Base, 370.0, 470.0, "couronne-edition" |
"couronne-édition")
(PAPER_ROBERTO: Base, 390.0, 500.0, "roberto")
(PAPER_ECU: Base, 400.0, 520.0, "ecu" | "écu")
(PAPER_COQUILLE: Base, 440.0, 560.0, "coquille")
(PAPER_CARRE: Base, 450.0, 560.0, "carre" | "carré")
(PAPER_CAVALIER: Base, 460.0, 620.0, "cavalier")
(PAPER_DEMI_RAISIN: Base, 325.0, 500.0, "demi-raisin")
(PAPER_RAISIN: Base, 500.0, 650.0, "raisin" | "dessin")
(PAPER_DOUBLE_RAISIN: Base, 650.0, 1000.0, "double-raisin")
(PAPER_JESUS: Base, 560.0, 760.0, "jesus" | "jésus")
(PAPER_SOLEIL: Base, 600.0, 800.0, "soleil")
(PAPER_COLOMBIER_AFFICHE: Base, 600.0, 800.0, "colombier-affiche" | "affiche")
(PAPER_COLOMBIER_COMMERCIAL: Base, 630.0, 900.0, "colombier-commercial")
(PAPER_PETIT_AIGLE: Base, 700.0, 940.0, "petit-aigle")
(PAPER_GRAND_AIGLE: Base, 750.0, 1060.0, "grand-aigle" | "napoleon")
(PAPER_GRAND_MONDE: Base, 900.0, 1260.0, "grand-monde")
(PAPER_UNIVERS: Base, 1000.0, 1300.0, "univers" | "universe")
// ---------------------------------------------------------------------- //
// Newspaper
(PAPER_COMPACT: Newspaper, 280.0, 430.0, "compact")
(PAPER_BERLINER: Newspaper, 315.0, 470.0, "berliner" | "midi")
(PAPER_RHENISH: Newspaper, 350.0, 520.0, "rhenish")
(PAPER_BROADSHEET: Newspaper, 381.0, 578.0, "broadsheet" | "newspaper")
(PAPER_NEW_YORK_TIMES: Newspaper, 305.0, 559.0, "new-york-times" | "times")
// ---------------------------------------------------------------------- //
// Books
(PAPER_FOLIO_BOOK: Book, 304.8, 482.6, "book-folio")
(PAPER_QUARTO_BOOK: Book, 241.3, 304.8, "book-quarto")
(PAPER_OCTAVO_BOOK: Book, 152.4, 228.6, "book-octavo")
(PAPER_16_MO_BOOK: Book, 101.6, 171.45, "book-16mo")
(PAPER_32_MO_BOOK: Book, 88.9, 139.7, "book-32mo")
// ---------------------------------------------------------------------- //
// Various
(PAPER_ID_1: Base, 85.6, 53.98, "id-card" | "id-1" | "iso-7810-id-1" |
"eu-business-card" | "business-card")
(PAPER_US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card")
(PAPER_JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card")
(PAPER_CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card")
(PAPER_A4_16_9: Base, 297.0, 167.0625, "presentation-16-9" | "presentation")
(PAPER_A4_4_3: Base, 280.0, 210.0, "presentation-4-3")
(PAPER_POSTCARD: Base, 152.4, 101.6, "postcard")
}

View File

@ -1,94 +0,0 @@
//! Problems (errors / warnings) in _Typst_ documents.
//!
//! There are no fatal errors in _Typst_. The document will always compile and
//! yield a layout. However, this is a best effort process and bad things will
//! still generate errors and warnings.
use serde::Serialize;
use crate::syntax::span::SpanVec;
/// A list of spanned problems.
pub type Problems = SpanVec<Problem>;
/// A problem that arose in parsing or layouting.
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Problem {
/// How severe / important the problem is.
pub severity: Severity,
/// A message describing the problem.
pub message: String,
}
/// How severe / important a problem is.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Severity {
/// Something in the code is not good.
Warning,
/// Something in the code is wrong!
Error,
}
impl Problem {
/// Create a new problem from message and severity.
pub fn new(message: impl Into<String>, severity: Severity) -> Self {
Self { message: message.into(), severity }
}
}
/// Construct a problem with `Error` severity.
///
/// ```
/// # use typstc::error;
/// # use typstc::syntax::span::Span;
/// # use typstc::Feedback;
/// # let span = Span::ZERO;
/// # let mut feedback = Feedback::new();
/// # let name = "";
/// // Create formatted error values.
/// let error = error!("expected {}", name);
///
/// // Create spanned errors.
/// let spanned = error!(span, "there is an error here");
///
/// // Create an error and directly add it to existing feedback.
/// error!(@feedback, span, "oh no!");
/// ```
#[macro_export]
macro_rules! error {
($($tts:tt)*) => {
$crate::__impl_problem!($crate::problem::Severity::Error; $($tts)*)
};
}
/// Construct a problem with `Warning` severity.
///
/// This works exactly like `error!`. See its documentation for more
/// information.
#[macro_export]
macro_rules! warning {
($($tts:tt)*) => {
$crate::__impl_problem!($crate::problem::Severity::Warning; $($tts)*)
};
}
/// Backs the `error!` and `warning!` macros.
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_problem {
($severity:expr; @$feedback:expr, $($tts:tt)*) => {
$feedback.problems.push($crate::__impl_problem!($severity; $($tts)*));
};
($severity:expr; $fmt:literal $($tts:tt)*) => {
$crate::problem::Problem::new(format!($fmt $($tts)*), $severity)
};
($severity:expr; $span:expr, $fmt:literal $($tts:tt)*) => {
$crate::syntax::span::Spanned::new(
$crate::__impl_problem!($severity; $fmt $($tts)*),
$span,
)
};
}

View File

@ -2,8 +2,8 @@
use toddle::fallback;
use toddle::query::{FallbackTree, FontVariant, FontStyle, FontWeight};
use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize};
use crate::length::{Length, Size, Margins, Value4, ScaleLength};
use crate::paper::{Paper, PaperClass, PAPER_A4};
/// Defines properties of pages and text.
#[derive(Debug, Default, Clone, PartialEq)]
@ -25,35 +25,35 @@ pub struct TextStyle {
/// whether the next `*` adds or removes font weight.
pub bolder: bool,
/// The base font size.
pub base_font_size: Size,
pub base_font_size: Length,
/// The font scale to apply on the base font size.
pub font_scale: f32,
pub font_scale: f64,
/// The word spacing (as a multiple of the font size).
pub word_spacing_scale: f32,
pub word_spacing_scale: f64,
/// The line spacing (as a multiple of the font size).
pub line_spacing_scale: f32,
pub line_spacing_scale: f64,
/// The paragraphs spacing (as a multiple of the font size).
pub paragraph_spacing_scale: f32,
pub paragraph_spacing_scale: f64,
}
impl TextStyle {
/// The scaled font size.
pub fn font_size(&self) -> Size {
pub fn font_size(&self) -> Length {
self.base_font_size * self.font_scale
}
/// The absolute word spacing.
pub fn word_spacing(&self) -> Size {
pub fn word_spacing(&self) -> Length {
self.word_spacing_scale * self.font_size()
}
/// The absolute line spacing.
pub fn line_spacing(&self) -> Size {
pub fn line_spacing(&self) -> Length {
(self.line_spacing_scale - 1.0) * self.font_size()
}
/// The absolute paragraph spacing.
pub fn paragraph_spacing(&self) -> Size {
pub fn paragraph_spacing(&self) -> Length {
(self.paragraph_spacing_scale - 1.0) * self.font_size()
}
}
@ -77,7 +77,7 @@ impl Default for TextStyle {
weight: FontWeight(400),
},
bolder: false,
base_font_size: Size::pt(11.0),
base_font_size: Length::pt(11.0),
font_scale: 1.0,
word_spacing_scale: 0.25,
line_spacing_scale: 1.2,
@ -92,10 +92,10 @@ pub struct PageStyle {
/// The class of this page.
pub class: PaperClass,
/// The width and height of the page.
pub dimensions: Size2D,
pub dimensions: Size,
/// The amount of white space on each side. If a side is set to `None`, the
/// default for the paper class is used.
pub margins: ValueBox<Option<PSize>>,
pub margins: Value4<Option<ScaleLength>>,
}
impl PageStyle {
@ -103,17 +103,17 @@ impl PageStyle {
pub fn new(paper: Paper) -> PageStyle {
PageStyle {
class: paper.class,
dimensions: paper.dimensions,
margins: ValueBox::with_all(None),
dimensions: paper.size(),
margins: Value4::with_all(None),
}
}
/// The absolute margins.
pub fn margins(&self) -> SizeBox {
pub fn margins(&self) -> Margins {
let dims = self.dimensions;
let default = self.class.default_margins();
SizeBox {
Margins {
left: self.margins.left.unwrap_or(default.left).scaled(dims.x),
top: self.margins.top.unwrap_or(default.top).scaled(dims.y),
right: self.margins.right.unwrap_or(default.right).scaled(dims.x),
@ -127,270 +127,3 @@ impl Default for PageStyle {
PageStyle::new(PAPER_A4)
}
}
/// Details about a type of paper.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper {
/// The kind of paper, which defines the default margins.
pub class: PaperClass,
/// The size of the paper.
pub dimensions: Size2D,
}
impl Paper {
/// The paper with the given name.
pub fn from_name(name: &str) -> Option<Paper> {
parse_paper(name)
}
}
/// Paper classes define default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PaperClass {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
pub fn default_margins(self) -> ValueBox<PSize> {
use PaperClass::*;
let values = |l, t, r, b| ValueBox::new(
PSize::Scaled(l),
PSize::Scaled(t),
PSize::Scaled(r),
PSize::Scaled(b),
);
match self {
Custom => values(0.1190, 0.0842, 0.1190, 0.0842),
Base => values(0.1190, 0.0842, 0.1190, 0.0842),
US => values(0.1760, 0.1092, 0.1760, 0.0910),
Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294),
Book => values(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
macro_rules! papers {
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
$(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)*
fn parse_paper(paper: &str) -> Option<Paper> {
match paper.to_lowercase().as_str() {
$($($pats)* => Some($var),)*
_ => None,
}
}
};
(@$var:ident, $names:expr, $class:ident, $width:expr, $height:expr) => {
#[doc = "Paper with name `"]
#[doc = $names]
#[doc = "`."]
pub const $var: Paper = Paper {
dimensions: Size2D {
x: Size { points: 2.83465 * $width },
y: Size { points: 2.83465 * $height },
},
class: PaperClass::$class,
};
};
}
// All paper sizes in mm.
papers! {
// ---------------------------------------------------------------------- //
// ISO 216
// A Series
(PAPER_A0: Base, 841.0, 1189.0, "a0" | "poster")
(PAPER_A1: Base, 594.0, 841.0, "a1")
(PAPER_A2: Base, 420.0, 594.0, "a2")
(PAPER_A3: Base, 297.0, 420.0, "a3")
(PAPER_A4: Base, 210.0, 297.0, "a4")
(PAPER_A5: Base, 148.0, 210.0, "a5")
(PAPER_A6: Book, 105.0, 148.0, "a6")
(PAPER_A7: Base, 74.0, 105.0, "a7" | "iso-7810-id-2" | "id-2" | "visa" | "flyer")
(PAPER_A8: Base, 52.0, 74.0, "a8")
(PAPER_A9: Base, 37.0, 52.0, "a9")
(PAPER_A10: Base, 26.0, 37.0, "a10")
(PAPER_A11: Base, 18.0, 26.0, "a11")
// B Series
(PAPER_B1: Base, 707.0, 1000.0, "b1" | "flipchart")
(PAPER_B2: Base, 500.0, 707.0, "b2")
(PAPER_B3: Base, 353.0, 500.0, "b3")
(PAPER_B4: Base, 250.0, 353.0, "b4" | "sheet-music")
(PAPER_B5: Book, 176.0, 250.0, "b5")
(PAPER_B6: Book, 125.0, 176.0, "b6" | "book")
(PAPER_B7: Base, 88.0, 125.0, "b7" | "passport" | "iso-7810-id-3" | "id-3")
(PAPER_B8: Base, 62.0, 88.0, "b8")
// C Series
(PAPER_C3: Base, 324.0, 458.0, "c3")
(PAPER_C4: Base, 229.0, 324.0, "c4")
(PAPER_C5: Base, 162.0, 229.0, "c5")
(PAPER_C6: Base, 114.0, 162.0, "c6")
(PAPER_C7: Base, 81.0, 114.0, "c7")
(PAPER_C8: Base, 57.0, 81.0, "c8")
// D Series (DIN extension to ISO)
(PAPER_D3: Base, 272.0, 385.0, "din-d3")
(PAPER_D4: Base, 192.0, 272.0, "din-d4")
(PAPER_D5: Base, 136.0, 192.0, "din-d5" | "dvd")
(PAPER_D6: Base, 96.0, 136.0, "din-d6")
(PAPER_D7: Base, 68.0, 96.0, "din-d7")
(PAPER_D8: Base, 48.0, 68.0, "din-d8")
// Academically relevant SIS extensions
(PAPER_G5: Base, 169.0, 239.0, "sis-g5")
(PAPER_E5: Base, 115.0, 220.0, "sis-e5")
// ---------------------------------------------------------------------- //
// Unites States
// Customary
(PAPER_FOLIO: US, 210.0, 330.0, "folio" | "us-folio" | "us-f4")
(PAPER_LETTER: US, 216.0, 279.0, "letter" | "ansi-a" |
"american-quarto" | "carta")
(PAPER_LEGAL: US, 216.0, 356.0, "legal")
(PAPER_TABLOID: Newspaper, 279.0, 432.0, "tabloid" | "ansi-b")
(PAPER_LEDGER: Base, 432.0, 279.0, "ledger")
(PAPER_JUNIOR_LEGAL: US, 127.0, 203.0, "junior-legal" | "index-card")
(PAPER_HALF_LETTER: Base, 140.0, 216.0, "half-letter")
(PAPER_GOVERNMENT_LETTER: US, 203.0, 267.0, "government-letter")
(PAPER_GOVERNMENT_LEGAL: US, 216.0, 330.0, "government-legal" | "officio")
// ANSI Extensions
(PAPER_ANSI_C: Base, 432.0, 559.0, "ansi-c")
(PAPER_ANSI_D: Base, 559.0, 864.0, "ansi-d")
(PAPER_ANSI_E: Base, 864.0, 1118.0, "ansi-e")
(PAPER_ENGINEERING_F: Base, 711.0, 1016.0, "engineering-f" | "engineering" |
"navfac" | "aerospace")
// Architectural Paper
(PAPER_ARCH_A: Base, 229.0, 305.0, "arch-a" | "arch-1")
(PAPER_ARCH_B: Base, 305.0, 457.0, "arch-b" | "arch-2" | "extra-tabloide")
(PAPER_ARCH_C: Base, 457.0, 610.0, "arch-c" | "arch-3")
(PAPER_ARCH_D: Base, 610.0, 914.0, "arch-d" | "arch-4")
(PAPER_ARCH_E1: Base, 762.0, 1067.0, "arch-e1" | "arch-5")
(PAPER_ARCH_E: Base, 914.0, 1219.0, "arch-e" | "arch-6")
// ---------------------------------------------------------------------- //
// Japan
// JIS B Series
(PAPER_JIS_B0: Base, 1030.0, 1456.0, "jis-b0" | "jb0")
(PAPER_JIS_B1: Base, 728.0, 1030.0, "jis-b1" | "jb1")
(PAPER_JIS_B2: Base, 515.0, 728.0, "jis-b2" | "jb2")
(PAPER_JIS_B3: Base, 364.0, 515.0, "jis-b3" | "jb3")
(PAPER_JIS_B4: Base, 257.0, 364.0, "jis-b4" | "jb4")
(PAPER_JIS_B5: Base, 182.0, 257.0, "jis-b5" | "jb5")
(PAPER_JIS_B6: Base, 128.0, 182.0, "jis-b6" | "jb6")
(PAPER_JIS_B7: Base, 91.0, 128.0, "jis-b7" | "jb7")
(PAPER_JIS_B8: Base, 64.0, 91.0, "jis-b8" | "jb8")
(PAPER_JIS_B9: Base, 45.0, 64.0, "jis-b9" | "jb9")
(PAPER_JIS_B10: Base, 32.0, 45.0, "jis-b10" | "jb10")
(PAPER_JIS_B11: Base, 22.0, 32.0, "jis-b11" | "jb11")
// Traditional
(PAPER_SHIROKU_BAN_4: Base, 264.0, 379.0, "shiroku-ban-4")
(PAPER_SHIROKU_BAN_5: Base, 189.0, 262.0, "shiroku-ban-5")
(PAPER_SHIROKU_BAN_6: Base, 127.0, 188.0, "shiroku-ban-6")
(PAPER_KIKU_4: Base, 227.0, 306.0, "kiku-4")
(PAPER_KIKU_5: Base, 151.0, 227.0, "kiku-5")
// ---------------------------------------------------------------------- //
// China
// Chinese D Series
(PAPER_SAC_D0: Base, 764.0, 1064.0, "sac-d0" | "cn-d0")
(PAPER_SAC_D1: Base, 532.0, 760.0, "sac-d1" | "cn-d1")
(PAPER_SAC_D2: Base, 380.0, 528.0, "sac-d2" | "cn-d2")
(PAPER_SAC_D3: Base, 264.0, 376.0, "sac-d3" | "cn-d3")
(PAPER_SAC_D4: Base, 188.0, 260.0, "sac-d4" | "cn-d4")
(PAPER_SAC_D5: Base, 130.0, 184.0, "sac-d5" | "cn-d5")
(PAPER_SAC_D6: Base, 92.0, 126.0, "sac-d6" | "cn-d6")
// ---------------------------------------------------------------------- //
// United Kingdom Imperial (Assortment)
(PAPER_MONARCH: Base, 184.0, 267.0, "monarch")
(PAPER_QUARTO: Base, 229.0, 279.0, "quarto" | "us-quarto")
(PAPER_UK_QUARTO: Base, 203.0, 254.0, "uk-quarto" | "imperial-quarto")
(PAPER_UK_FOOLSCAP: Base, 343.0, 432.0, "foolscap" | "us-foolscap")
(PAPER_FOOLSCAP: Base, 203.0, 330.0, "imperial-foolscap" | "uk-foolscap")
(PAPER_POTT: Base, 318.0, 381.0, "pott")
(PAPER_CROWN: Base, 318.0, 508.0, "crown")
(PAPER_PINCHED_POST: Base, 375.0, 470.0, "pinched-post")
(PAPER_POST: Base, 394.0, 489.0, "post")
(PAPER_LARGE_POST: Base, 419.0, 533.0, "large-post")
(PAPER_DEMY: Base, 445.0, 572.0, "demy")
(PAPER_ROYAL: Base, 508.0, 635.0, "royal")
(PAPER_DOUBLE_CROWN: Base, 508.0, 762.0, "double-crown" | "theatre")
(PAPER_ELEPHANT: Base, 584.0, 711.0, "elephant")
(PAPER_DOUBLE_ROYAL: Base, 635.0, 1016.0, "double-royal" | "rail")
(PAPER_QUAD_CROWN: Base, 762.0, 1016.0, "quad-crown" | "cinema")
// ---------------------------------------------------------------------- //
// French Traditional (AFNOR)
(PAPER_CLOCHE: Base, 300.0, 400.0, "cloche")
(PAPER_POT: Base, 310.0, 400.0, "pot" | "ecolier" | "écolier")
(PAPER_TELLIERE: Base, 340.0, 440.0, "telliere" | "tellière")
(PAPER_COURONNE_ECRITURE: Base, 360.0, 460.0, "couronne-ecriture" |
"couronne" | "couronne-écriture")
(PAPER_COURONNE_EDITION: Base, 370.0, 470.0, "couronne-edition" |
"couronne-édition")
(PAPER_ROBERTO: Base, 390.0, 500.0, "roberto")
(PAPER_ECU: Base, 400.0, 520.0, "ecu" | "écu")
(PAPER_COQUILLE: Base, 440.0, 560.0, "coquille")
(PAPER_CARRE: Base, 450.0, 560.0, "carre" | "carré")
(PAPER_CAVALIER: Base, 460.0, 620.0, "cavalier")
(PAPER_DEMI_RAISIN: Base, 325.0, 500.0, "demi-raisin")
(PAPER_RAISIN: Base, 500.0, 650.0, "raisin" | "dessin")
(PAPER_DOUBLE_RAISIN: Base, 650.0, 1000.0, "double-raisin")
(PAPER_JESUS: Base, 560.0, 760.0, "jesus" | "jésus")
(PAPER_SOLEIL: Base, 600.0, 800.0, "soleil")
(PAPER_COLOMBIER_AFFICHE: Base, 600.0, 800.0, "colombier-affiche" | "affiche")
(PAPER_COLOMBIER_COMMERCIAL: Base, 630.0, 900.0, "colombier-commercial")
(PAPER_PETIT_AIGLE: Base, 700.0, 940.0, "petit-aigle")
(PAPER_GRAND_AIGLE: Base, 750.0, 1060.0, "grand-aigle" | "napoleon")
(PAPER_GRAND_MONDE: Base, 900.0, 1260.0, "grand-monde")
(PAPER_UNIVERS: Base, 1000.0, 1300.0, "univers" | "universe")
// ---------------------------------------------------------------------- //
// Newspaper
(PAPER_COMPACT: Newspaper, 280.0, 430.0, "compact")
(PAPER_BERLINER: Newspaper, 315.0, 470.0, "berliner" | "midi")
(PAPER_RHENISH: Newspaper, 350.0, 520.0, "rhenish")
(PAPER_BROADSHEET: Newspaper, 381.0, 578.0, "broadsheet" | "newspaper")
(PAPER_NEW_YORK_TIMES: Newspaper, 305.0, 559.0, "new-york-times" | "times")
// ---------------------------------------------------------------------- //
// Books
(PAPER_FOLIO_BOOK: Book, 304.8, 482.6, "book-folio")
(PAPER_QUARTO_BOOK: Book, 241.3, 304.8, "book-quarto")
(PAPER_OCTAVO_BOOK: Book, 152.4, 228.6, "book-octavo")
(PAPER_16_MO_BOOK: Book, 101.6, 171.45, "book-16mo")
(PAPER_32_MO_BOOK: Book, 88.9, 139.7, "book-32mo")
// ---------------------------------------------------------------------- //
// Various
(PAPER_ID_1: Base, 85.6, 53.98, "id-card" | "id-1" | "iso-7810-id-1" |
"eu-business-card" | "business-card")
(PAPER_US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card")
(PAPER_JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card")
(PAPER_CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card")
(PAPER_A4_16_9: Base, 297.0, 167.0625, "presentation-16-9" | "presentation")
(PAPER_A4_4_3: Base, 280.0, 210.0, "presentation-4-3")
(PAPER_POSTCARD: Base, 152.4, 101.6, "postcard")
}

View File

@ -6,13 +6,12 @@ use std::ops::Deref;
use std::str::FromStr;
use std::u8;
use crate::problem::Problems;
use crate::size::Size;
use crate::diagnostic::Diagnostics;
use crate::length::Length;
use super::func::{Key, Value};
use super::span::{Span, Spanned};
use super::tokens::is_identifier;
/// An argument or return value.
#[derive(Clone, PartialEq)]
pub enum Expr {
@ -22,8 +21,8 @@ pub enum Expr {
Str(String),
/// A number: `1.2, 200%`.
Number(f64),
/// A size: `2cm, 5.2in`.
Size(Size),
/// A length: `2cm, 5.2in`.
Length(Length),
/// A bool: `true, false`.
Bool(bool),
/// A color value, including the alpha channel: `#f79143ff`.
@ -32,7 +31,7 @@ pub enum Expr {
Tuple(Tuple),
/// A named tuple: `cmyk(37.7, 0, 3.9, 1.1)`.
NamedTuple(NamedTuple),
/// An object: `{ fit: false, size: 12pt }`.
/// An object: `{ fit: false, width: 12pt }`.
Object(Object),
/// An operator that negates the contained expression.
Neg(Box<Spanned<Expr>>),
@ -54,7 +53,7 @@ impl Expr {
Ident(_) => "identifier",
Str(_) => "string",
Number(_) => "number",
Size(_) => "size",
Length(_) => "length",
Bool(_) => "bool",
Color(_) => "color",
Tuple(_) => "tuple",
@ -76,7 +75,7 @@ impl Debug for Expr {
Ident(i) => i.fmt(f),
Str(s) => s.fmt(f),
Number(n) => n.fmt(f),
Size(s) => s.fmt(f),
Length(s) => s.fmt(f),
Bool(b) => b.fmt(f),
Color(c) => c.fmt(f),
Tuple(t) => t.fmt(f),
@ -128,7 +127,7 @@ impl Debug for Ident {
///
/// # Example
/// ```typst
/// [box: background=#423abaff]
/// [page: background=#423abaff]
/// ^^^^^^^^
/// ```
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
@ -256,28 +255,28 @@ impl Tuple {
}
/// Extract (and remove) the first matching value and remove and generate
/// problems for all previous items that did not match.
pub fn get<V: Value>(&mut self, problems: &mut Problems) -> Option<V> {
/// diagnostics for all previous items that did not match.
pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics) -> Option<V> {
while !self.items.is_empty() {
let expr = self.items.remove(0);
let span = expr.span;
match V::parse(expr) {
Ok(output) => return Some(output),
Err(v) => problems.push(Spanned { v, span }),
Err(v) => diagnostics.push(Spanned { v, span }),
}
}
None
}
/// Extract and return an iterator over all values that match and generate
/// problems for all items that do not match.
pub fn get_all<'a, V: Value>(&'a mut self, problems: &'a mut Problems)
/// diagnostics for all items that do not match.
pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics)
-> impl Iterator<Item=V> + 'a {
self.items.drain(..).filter_map(move |expr| {
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(v) => { problems.push(Spanned { v, span }); None }
Err(v) => { diagnostics.push(Spanned { v, span }); None }
}
})
}
@ -351,13 +350,9 @@ impl Deref for NamedTuple {
/// A key-value collection of identifiers and associated expressions.
///
/// The pairs themselves are not spanned, but the combined spans can easily be
/// retrieved by merging the spans of key and value as happening in
/// [`FuncArg::span`](super::func::FuncArg::span).
///
/// # Example
/// ```typst
/// { fit: false, size: 12cm, items: (1, 2, 3) }
/// { fit: false, width: 12cm, items: (1, 2, 3) }
/// ```
#[derive(Default, Clone, PartialEq)]
pub struct Object {
@ -398,9 +393,9 @@ impl Object {
///
/// Inserts an error if the value does not match. If the key is not
/// contained, no error is inserted.
pub fn get<V: Value>(&mut self, problems: &mut Problems, key: &str) -> Option<V> {
pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics, key: &str) -> Option<V> {
let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?;
self.get_index::<V>(problems, index)
self.get_index::<V>(diagnostics, index)
}
/// Extract (and remove) a pair with a matching key and value.
@ -409,12 +404,12 @@ impl Object {
/// found, no error is inserted.
pub fn get_with_key<K: Key, V: Value>(
&mut self,
problems: &mut Problems,
diagnostics: &mut Diagnostics,
) -> Option<(K, V)> {
for (index, pair) in self.pairs.iter().enumerate() {
let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span };
if let Some(key) = K::parse(key) {
return self.get_index::<V>(problems, index).map(|value| (key, value));
return self.get_index::<V>(diagnostics, index).map(|value| (key, value));
}
}
None
@ -425,7 +420,7 @@ impl Object {
/// Inserts errors for values that do not match.
pub fn get_all<'a, K: Key, V: Value>(
&'a mut self,
problems: &'a mut Problems,
diagnostics: &'a mut Diagnostics,
) -> impl Iterator<Item=(K, V)> + 'a {
let mut index = 0;
std::iter::from_fn(move || {
@ -434,7 +429,7 @@ impl Object {
let key = Spanned { v: key.v.as_str(), span: key.span };
Some(if let Some(key) = K::parse(key) {
self.get_index::<V>(problems, index).map(|v| (key, v))
self.get_index::<V>(diagnostics, index).map(|v| (key, v))
} else {
index += 1;
None
@ -454,20 +449,20 @@ impl Object {
/// ```
pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>(
&'a mut self,
problems: &'a mut Problems,
diagnostics: &'a mut Diagnostics,
) -> impl Iterator<Item=Spanned<(K, V)>> + 'a {
self.get_all::<Spanned<K>, Spanned<V>>(problems)
self.get_all::<Spanned<K>, Spanned<V>>(diagnostics)
.map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span)))
}
/// Extract the argument at the given index and insert an error if the value
/// does not match.
fn get_index<V: Value>(&mut self, problems: &mut Problems, index: usize) -> Option<V> {
fn get_index<V: Value>(&mut self, diagnostics: &mut Diagnostics, index: usize) -> Option<V> {
let expr = self.pairs.remove(index).v.value;
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(v) => { problems.push(Spanned { v, span }); None }
Err(v) => { diagnostics.push(Spanned { v, span }); None }
}
}

View File

@ -7,7 +7,6 @@ use super::*;
use self::AxisKey::*;
use self::PaddingKey::*;
/// Key types are used to extract keyword arguments from
/// [`Objects`](crate::syntax::expr::Object). They represent the key part of a
/// keyword argument.

View File

@ -1,18 +1,17 @@
//! Deduplicating maps and keys for argument parsing.
use crate::problem::Problems;
use crate::diagnostic::Diagnostics;
use crate::layout::prelude::*;
use crate::size::{PSize, ValueBox};
use crate::length::{ScaleLength, Value4};
use crate::syntax::span::Spanned;
use super::keys::*;
use super::values::*;
use super::*;
/// A map which deduplicates redundant arguments.
///
/// Whenever a duplicate argument is inserted into the map, through the
/// functions `from_iter`, `insert` or `extend` an problems is added to the error
/// functions `from_iter`, `insert` or `extend` an diagnostics is added to the error
/// list that needs to be passed to those functions.
///
/// All entries need to have span information to enable the error reporting.
@ -28,27 +27,27 @@ impl<K, V> DedupMap<K, V> where K: Eq {
}
/// Create a new map from an iterator of spanned keys and values.
pub fn from_iter<I>(problems: &mut Problems, iter: I) -> DedupMap<K, V>
pub fn from_iter<I>(diagnostics: &mut Diagnostics, iter: I) -> DedupMap<K, V>
where I: IntoIterator<Item=Spanned<(K, V)>> {
let mut map = DedupMap::new();
map.extend(problems, iter);
map.extend(diagnostics, iter);
map
}
/// Add a spanned key-value pair.
pub fn insert(&mut self, problems: &mut Problems, entry: Spanned<(K, V)>) {
pub fn insert(&mut self, diagnostics: &mut Diagnostics, entry: Spanned<(K, V)>) {
if self.map.iter().any(|e| e.v.0 == entry.v.0) {
problems.push(error!(entry.span, "duplicate argument"));
diagnostics.push(error!(entry.span, "duplicate argument"));
} else {
self.map.push(entry);
}
}
/// Add multiple spanned key-value pairs.
pub fn extend<I>(&mut self, problems: &mut Problems, items: I)
pub fn extend<I>(&mut self, diagnostics: &mut Diagnostics, items: I)
where I: IntoIterator<Item=Spanned<(K, V)>> {
for item in items.into_iter() {
self.insert(problems, item);
self.insert(diagnostics, item);
}
}
@ -71,15 +70,15 @@ impl<K, V> DedupMap<K, V> where K: Eq {
}
/// Create a new map where keys and values are mapped to new keys and
/// values. When the mapping introduces new duplicates, problems are
/// values. When the mapping introduces new duplicates, diagnostics are
/// generated.
pub fn dedup<F, K2, V2>(&self, problems: &mut Problems, mut f: F) -> DedupMap<K2, V2>
pub fn dedup<F, K2, V2>(&self, diagnostics: &mut Diagnostics, mut f: F) -> DedupMap<K2, V2>
where F: FnMut(&K, &V) -> (K2, V2), K2: Eq {
let mut map = DedupMap::new();
for Spanned { v: (key, value), span } in self.map.iter() {
let (key, value) = f(key, value);
map.insert(problems, Spanned { v: (key, value), span: *span });
map.insert(diagnostics, Spanned { v: (key, value), span: *span });
}
map
@ -98,21 +97,21 @@ pub struct AxisMap<V>(DedupMap<AxisKey, V>);
impl<V: Value> AxisMap<V> {
/// Parse an axis map from the object.
pub fn parse<K>(
problems: &mut Problems,
diagnostics: &mut Diagnostics,
object: &mut Object,
) -> AxisMap<V> where K: Key + Into<AxisKey> {
let values: Vec<_> = object
.get_all_spanned::<K, V>(problems)
.get_all_spanned::<K, V>(diagnostics)
.map(|s| s.map(|(k, v)| (k.into(), v)))
.collect();
AxisMap(DedupMap::from_iter(problems, values))
AxisMap(DedupMap::from_iter(diagnostics, values))
}
/// Deduplicate from specific or generic to just specific axes.
pub fn dedup(&self, problems: &mut Problems, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
pub fn dedup(&self, diagnostics: &mut Diagnostics, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
where V: Clone {
self.0.dedup(problems, |key, val| (key.to_specific(axes), val.clone()))
self.0.dedup(diagnostics, |key, val| (key.to_specific(axes), val.clone()))
}
}
@ -124,23 +123,23 @@ pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
impl<V: Value> PosAxisMap<V> {
/// Parse a positional/axis map from the function arguments.
pub fn parse<K>(
problems: &mut Problems,
diagnostics: &mut Diagnostics,
args: &mut FuncArgs,
) -> PosAxisMap<V> where K: Key + Into<AxisKey> {
let mut map = DedupMap::new();
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(problems) {
map.insert(problems, Spanned { v: (key, v), span })
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(diagnostics) {
map.insert(diagnostics, Spanned { v: (key, v), span })
}
}
let keywords: Vec<_> = args.key
.get_all_spanned::<K, V>(problems)
.get_all_spanned::<K, V>(diagnostics)
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v)))
.collect();
map.extend(problems, keywords);
map.extend(diagnostics, keywords);
PosAxisMap(map)
}
@ -149,7 +148,7 @@ impl<V: Value> PosAxisMap<V> {
/// or specific axes to just generic axes.
pub fn dedup<F>(
&self,
problems: &mut Problems,
diagnostics: &mut Diagnostics,
axes: LayoutAxes,
mut f: F,
) -> DedupMap<GenericAxis, V>
@ -157,7 +156,7 @@ impl<V: Value> PosAxisMap<V> {
F: FnMut(&V) -> Option<GenericAxis>,
V: Clone,
{
self.0.dedup(problems, |key, val| {
self.0.dedup(diagnostics, |key, val| {
(match key {
PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary),
PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary),
@ -171,24 +170,24 @@ impl<V: Value> PosAxisMap<V> {
/// A map for storing padding given for a combination of all sides, opposing
/// sides or single sides.
#[derive(Debug, Clone, PartialEq)]
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<PSize>>);
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<ScaleLength>>);
impl PaddingMap {
/// Parse a padding map from the function arguments.
pub fn parse(problems: &mut Problems, args: &mut FuncArgs) -> PaddingMap {
pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap {
let mut map = DedupMap::new();
let all = args.pos.get::<Spanned<Defaultable<PSize>>>(problems);
let all = args.pos.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics);
if let Some(Spanned { v, span }) = all {
map.insert(problems, Spanned { v: (PaddingKey::All, v.into()), span });
map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span });
}
let paddings: Vec<_> = args.key
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<PSize>>(problems)
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<ScaleLength>>(diagnostics)
.map(|s| s.map(|(k, v)| (k, v.into())))
.collect();
map.extend(problems, paddings);
map.extend(diagnostics, paddings);
PaddingMap(map)
}
@ -196,13 +195,13 @@ impl PaddingMap {
/// Apply the specified padding on a value box of optional, scalable sizes.
pub fn apply(
&self,
problems: &mut Problems,
diagnostics: &mut Diagnostics,
axes: LayoutAxes,
padding: &mut ValueBox<Option<PSize>>
padding: &mut Value4<Option<ScaleLength>>
) {
use PaddingKey::*;
let map = self.0.dedup(problems, |key, &val| {
let map = self.0.dedup(diagnostics, |key, &val| {
(match key {
All => All,
Both(axis) => Both(axis.to_specific(axes)),

View File

@ -1,7 +1,7 @@
//! Primitives for argument parsing in library functions.
use std::iter::FromIterator;
use crate::problem::{Problem, Problems};
use crate::diagnostic::{Diagnostic, Diagnostics};
use super::expr::{Expr, Ident, Tuple, Object, Pair};
use super::span::{Span, Spanned};
@ -85,13 +85,13 @@ pub enum FuncArg {
pub trait OptionExt: Sized {
/// Add an error about a missing argument `arg` with the given span if the
/// option is `None`.
fn or_missing(self, problems: &mut Problems, span: Span, arg: &str) -> Self;
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self;
}
impl<T> OptionExt for Option<T> {
fn or_missing(self, problems: &mut Problems, span: Span, arg: &str) -> Self {
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self {
if self.is_none() {
problems.push(error!(span, "missing argument: {}", arg));
diagnostics.push(error!(span, "missing argument: {}", arg));
}
self
}

View File

@ -4,13 +4,12 @@ use std::fmt::{self, Display, Formatter};
use toddle::query::{FontStyle, FontWeight};
use crate::layout::prelude::*;
use crate::size::{Size, ScaleSize};
use crate::style::Paper;
use crate::length::{Length, ScaleLength};
use crate::paper::Paper;
use super::*;
use self::AlignmentValue::*;
/// Value types are used to extract the values of positional and keyword
/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and
/// [`Objects`](crate::syntax::expr::Object). They represent the value part of
@ -24,14 +23,14 @@ use self::AlignmentValue::*;
/// An implementation for `bool` might look as follows:
/// ```
/// # use typstc::error;
/// # use typstc::problem::Problem;
/// # use typstc::diagnostic::Diagnostic;
/// # use typstc::syntax::expr::Expr;
/// # use typstc::syntax::func::Value;
/// # use typstc::syntax::span::Spanned;
/// # struct Bool; /*
/// impl Value for bool {
/// # */ impl Value for Bool {
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
/// match expr.v {
/// # /*
/// Expr::Bool(b) => Ok(b),
@ -44,11 +43,11 @@ use self::AlignmentValue::*;
pub trait Value: Sized {
/// Parse an expression into this value or return an error if the expression
/// is valid for this value type.
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem>;
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic>;
}
impl<V: Value> Value for Spanned<V> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
let span = expr.span;
V::parse(expr).map(|v| Spanned { v, span })
}
@ -58,7 +57,7 @@ impl<V: Value> Value for Spanned<V> {
macro_rules! value {
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl Value for $type {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
#[allow(unreachable_patterns)]
match expr.v {
$($p => Ok($r)),*,
@ -77,13 +76,13 @@ value!(Ident, "identifier", Expr::Ident(i) => i);
value!(String, "string", Expr::Str(s) => s);
value!(f64, "number", Expr::Number(n) => n);
value!(bool, "bool", Expr::Bool(b) => b);
value!(Size, "size", Expr::Size(s) => s);
value!(Length, "length", Expr::Length(s) => s);
value!(Tuple, "tuple", Expr::Tuple(t) => t);
value!(Object, "object", Expr::Object(o) => o);
value!(ScaleSize, "number or size",
Expr::Size(size) => ScaleSize::Absolute(size),
Expr::Number(scale) => ScaleSize::Scaled(scale as f32),
value!(ScaleLength, "number or length",
Expr::Length(length) => ScaleLength::Absolute(length),
Expr::Number(scale) => ScaleLength::Scaled(scale as f64),
);
/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
@ -108,20 +107,20 @@ impl From<StringLike> for String {
/// # Example
/// ```
/// # use typstc::syntax::func::{FuncArgs, Defaultable};
/// # use typstc::size::Size;
/// # use typstc::length::Length;
/// # let mut args = FuncArgs::new();
/// # let mut errors = vec![];
/// args.key.get::<Defaultable<Size>>(&mut errors, "size");
/// args.key.get::<Defaultable<Length>>(&mut errors, "length");
/// ```
/// This will yield.
/// ```typst
/// [func: size=default] => None
/// [func: size=2cm] => Some(Size::cm(2.0))
/// [func: length=default] => None
/// [func: length=2cm] => Some(Length::cm(2.0))
/// ```
pub struct Defaultable<V>(pub Option<V>);
impl<V: Value> Value for Defaultable<V> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
Ok(Defaultable(match expr.v {
Expr::Ident(ident) if ident.as_str() == "default" => None,
_ => Some(V::parse(expr)?)
@ -136,7 +135,7 @@ impl<V> From<Defaultable<V>> for Option<V> {
}
impl Value for FontStyle {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
FontStyle::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| error!("invalid font style"))
}
@ -145,7 +144,7 @@ impl Value for FontStyle {
/// The additional boolean specifies whether a number was clamped into the range
/// 100 - 900 to make it a valid font weight.
impl Value for (FontWeight, bool) {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
match expr.v {
Expr::Number(weight) => {
let weight = weight.round();
@ -170,14 +169,14 @@ impl Value for (FontWeight, bool) {
}
impl Value for Paper {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
Paper::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| error!("invalid paper type"))
}
}
impl Value for Direction {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
Ok(match Ident::parse(expr)?.as_str() {
"left-to-right" | "ltr" | "LTR" => LeftToRight,
"right-to-left" | "rtl" | "RTL" => RightToLeft,
@ -250,7 +249,7 @@ impl AlignmentValue {
}
impl Value for AlignmentValue {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
Ok(match Ident::parse(expr)?.as_str() {
"origin" => Align(Origin),
"center" => Align(Center),

View File

@ -20,7 +20,6 @@ pub_use_mod!(scope);
pub_use_mod!(parsing);
pub_use_mod!(tokens);
/// Represents a parsed piece of source that can be layouted and in the future
/// also be queried for information used for refactorings, autocomplete, etc.
#[async_trait(?Send)]
@ -94,6 +93,9 @@ impl PartialEq for Node {
}
}
/// A list of spanned decorations.
pub type Decorations = SpanVec<Decoration>;
/// Decorations for semantic syntax highlighting.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
@ -110,7 +112,6 @@ pub enum Decoration {
/// ^^^^^^
/// ```
InvalidFuncName,
/// A key of a keyword argument:
/// ```typst
/// [box: width=5cm]
@ -123,7 +124,6 @@ pub enum Decoration {
/// ^^^^ ^^^^^
/// ```
ObjectKey,
/// An italic word.
Italic,
/// A bold word.

View File

@ -4,9 +4,12 @@ use std::str::FromStr;
use super::expr::*;
use super::func::{FuncCall, FuncHeader, FuncArgs, FuncArg};
use super::span::{Position, Span, Spanned};
use super::span::{Pos, Span, Spanned};
use super::*;
/// A function which parses a function call into a model.
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>;
/// The state which can influence how a string of source code is parsed.
///
/// Parsing is pure - when passed in the same state and source code, the output
@ -22,7 +25,7 @@ pub struct ParseState {
/// `offset` position. This is used to make spans of a function body relative to
/// the start of the function as a whole as opposed to the start of the
/// function's body.
pub fn parse(src: &str, offset: Position, state: &ParseState) -> Pass<SyntaxModel> {
pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
let mut model = SyntaxModel::new();
let mut feedback = Feedback::new();
@ -102,7 +105,7 @@ impl<'s> FuncParser<'s> {
state,
// Start at column 1 because the opening bracket is also part of
// the function, but not part of the `header` string.
tokens: Tokens::new(header, Position::new(0, 1), TokenMode::Header),
tokens: Tokens::new(header, Pos::new(0, 1), TokenMode::Header),
peeked: None,
body,
feedback: Feedback::new(),
@ -127,7 +130,7 @@ impl<'s> FuncParser<'s> {
}
};
self.feedback.decos.push(Spanned::new(deco, header.name.span));
self.feedback.decorations.push(Spanned::new(deco, header.name.span));
(parser, header)
} else {
// Parse the body with the fallback parser even when the header is
@ -186,7 +189,7 @@ impl<'s> FuncParser<'s> {
self.skip_white();
let key = ident;
self.feedback.decos.push(
self.feedback.decorations.push(
Spanned::new(Decoration::ArgumentKey, key.span)
);
@ -325,7 +328,7 @@ impl FuncParser<'_> {
}
Token::ExprNumber(n) => self.eat_span(Expr::Number(n)),
Token::ExprSize(s) => self.eat_span(Expr::Size(s)),
Token::ExprLength(s) => self.eat_span(Expr::Length(s)),
Token::ExprBool(b) => self.eat_span(Expr::Bool(b)),
Token::ExprHex(s) => {
if let Ok(color) = RgbaColor::from_str(s) {
@ -423,7 +426,7 @@ impl FuncParser<'_> {
continue;
}
self.feedback.decos.push(
self.feedback.decorations.push(
Spanned::new(Decoration::ObjectKey, key.span)
);
@ -464,7 +467,7 @@ impl FuncParser<'_> {
}
}
fn expect_at(&mut self, token: Token<'_>, pos: Position) -> bool {
fn expect_at(&mut self, token: Token<'_>, pos: Pos) -> bool {
if self.check(token) {
self.eat();
true
@ -485,11 +488,11 @@ impl FuncParser<'_> {
}
}
fn expected_at(&mut self, thing: &str, pos: Position) {
fn expected_at(&mut self, thing: &str, pos: Pos) {
error!(@self.feedback, Span::at(pos), "expected {}", thing);
}
fn expected_found_or_at(&mut self, thing: &str, pos: Position) {
fn expected_found_or_at(&mut self, thing: &str, pos: Pos) {
if self.eof() {
self.expected_at(thing, pos)
} else {
@ -544,7 +547,7 @@ impl<'s> FuncParser<'s> {
self.peek().is_none()
}
fn pos(&self) -> Position {
fn pos(&self) -> Pos {
self.peeked.flatten()
.map(|s| s.span.start)
.unwrap_or_else(|| self.tokens.pos())
@ -604,13 +607,13 @@ fn unescape_raw(raw: &str) -> Vec<String> {
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use crate::size::Size;
use crate::length::Length;
use super::super::test::{check, DebugFn};
use super::super::func::Value;
use super::*;
use Decoration::*;
use Expr::{Number as Num, Size as Sz, Bool};
use Expr::{Number as Num, Length as Len, Bool};
use Node::{
Space as S, ToggleItalic as Italic, ToggleBolder as Bold,
Parbreak, Linebreak,
@ -625,7 +628,7 @@ mod tests {
p!($source => [$($model)*], []);
};
($source:expr => [$($model:tt)*], [$($problems:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
($source:expr => [$($model:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
let mut scope = Scope::new::<DebugFn>();
scope.add::<DebugFn>("f");
scope.add::<DebugFn>("n");
@ -633,25 +636,25 @@ mod tests {
scope.add::<DebugFn>("val");
let state = ParseState { scope };
let pass = parse($source, Position::ZERO, &state);
let pass = parse($source, Pos::ZERO, &state);
// Test model.
let (exp, cmp) = span_vec![$($model)*];
check($source, exp, pass.output.nodes, cmp);
// Test problems.
let (exp, cmp) = span_vec![$($problems)*];
// Test diagnostics.
let (exp, cmp) = span_vec![$($diagnostics)*];
let exp = exp.into_iter()
.map(|s: Spanned<&str>| s.map(|e| e.to_string()))
.collect::<Vec<_>>();
let found = pass.feedback.problems.into_iter()
let found = pass.feedback.diagnostics.into_iter()
.map(|s| s.map(|e| e.message))
.collect::<Vec<_>>();
check($source, exp, found, cmp);
// Test decos.
$(let (exp, cmp) = span_vec![$($decos)*];
check($source, exp, pass.feedback.decos, cmp);)?
check($source, exp, pass.feedback.decorations, cmp);)?
};
}
@ -664,7 +667,6 @@ mod tests {
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
fn Color(r: u8, g: u8, b: u8, a: u8) -> Expr { Expr::Color(RgbaColor::new(r, g, b, a)) }
fn ColorStr(color: &str) -> Expr { Expr::Color(RgbaColor::from_str(color).expect("invalid test color")) }
fn ColorHealed() -> Expr { Expr::Color(RgbaColor::new_healed(0, 0, 0, 255)) }
@ -878,8 +880,8 @@ mod tests {
pval!("name" => (Id("name")));
pval!("\"hi\"" => (Str("hi")));
pval!("3.14" => (Num(3.14)));
pval!("4.5cm" => (Sz(Size::cm(4.5))));
pval!("12e1pt" => (Pt(12e1)));
pval!("4.5cm" => (Len(Length::cm(4.5))));
pval!("12e1pt" => (Len(Length::pt(12e1))));
pval!("#f7a20500" => (ColorStr("f7a20500")));
pval!("\"a\n[]\\\"string\"" => (Str("a\n[]\"string")));
@ -890,10 +892,10 @@ mod tests {
pval!("(hi)" => (Id("hi")));
// Math.
pval!("3.2in + 6pt" => (Add(Sz(Size::inches(3.2)), Sz(Size::pt(6.0)))));
pval!("3.2in + 6pt" => (Add(Len(Length::inches(3.2)), Len(Length::pt(6.0)))));
pval!("5 - 0.01" => (Sub(Num(5.0), Num(0.01))));
pval!("(3mm * 2)" => (Mul(Sz(Size::mm(3.0)), Num(2.0))));
pval!("12e-3cm/1pt" => (Div(Sz(Size::cm(12e-3)), Sz(Size::pt(1.0)))));
pval!("(3mm * 2)" => (Mul(Len(Length::mm(3.0)), Num(2.0))));
pval!("12e-3cm/1pt" => (Div(Len(Length::cm(12e-3)), Len(Length::pt(1.0)))));
// Unclosed string.
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
@ -912,26 +914,24 @@ mod tests {
fn parse_complex_mathematical_expressions() {
// Valid expressions.
pval!("(3.2in + 6pt)*(5/2-1)" => (Mul(
Add(Sz(Size::inches(3.2)), Sz(Size::pt(6.0))),
Add(Len(Length::inches(3.2)), Len(Length::pt(6.0))),
Sub(Div(Num(5.0), Num(2.0)), Num(1.0))
)));
pval!("(6.3E+2+4* - 3.2pt)/2" => (Div(
Add(Num(6.3e2),Mul(Num(4.0), Neg(Pt(3.2)))),
Add(Num(6.3e2), Mul(Num(4.0), Neg(Len(Length::pt(3.2))))),
Num(2.0)
)));
// Associativity of multiplication and division.
p!("[val: 3/4*5]" =>
[func!("val": (Mul(Div(Num(3.0), Num(4.0)), Num(5.0))), {})]
);
pval!("3/4*5" => (Mul(Div(Num(3.0), Num(4.0)), Num(5.0))));
// Invalid expressions.
p!("[val: 4pt--]" => [func!("val": (Pt(4.0)))], [
p!("[val: 4pt--]" => [func!("val": (Len(Length::pt(4.0))))], [
(0:10, 0:11, "dangling minus"),
(0:6, 0:10, "missing right summand")
]);
p!("[val: 3mm+4pt*]" =>
[func!("val": (Add(Sz(Size::mm(3.0)), Pt(4.0))))],
[func!("val": (Add(Len(Length::mm(3.0)), Len(Length::pt(4.0)))))],
[(0:10, 0:14, "missing right factor")],
);
}
@ -976,7 +976,7 @@ mod tests {
// Nested tuples.
pval!("css(1pt, rgb(90, 102, 254), \"solid\")" => (named_tuple!(
"css",
Pt(1.0),
Len(Length::pt(1.0)),
named_tuple!("rgb", Num(90.0), Num(102.0), Num(254.0)),
Str("solid"),
)));
@ -1012,7 +1012,7 @@ mod tests {
// Missing key.
p!("[val: {,}]" => [val()], [(0:7, 0:8, "expected key, found comma")]);
p!("[val: { 12pt }]" => [val()], [(0:8, 0:12, "expected key, found size")]);
p!("[val: { 12pt }]" => [val()], [(0:8, 0:12, "expected key, found length")]);
p!("[val: { : }]" => [val()], [(0:8, 0:9, "expected key, found colon")]);
// Missing colon.
@ -1053,7 +1053,7 @@ mod tests {
Num(1.0),
object!(
"ab" => tuple!(),
"d" => tuple!(Num(3.0), Pt(14.0)),
"d" => tuple!(Num(3.0), Len(Length::pt(14.0))),
),
),
Bool(false),
@ -1085,7 +1085,7 @@ mod tests {
#[test]
fn parse_multiple_mixed_arguments() {
p!("[val: 12pt, key=value]" =>
[func!("val": (Pt(12.0)), { "key" => Id("value") })], [],
[func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [],
[(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)],
);
pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b"), });
@ -1144,7 +1144,7 @@ mod tests {
fn parse_invalid_commas() {
// Missing commas.
p!("[val: 1pt 1]" =>
[func!("val": (Pt(1.0), Num(1.0)), {})],
[func!("val": (Len(Length::pt(1.0)), Num(1.0)), {})],
[(0:9, 0:9, "expected comma")],
);
p!(r#"[val: _"s"]"# =>

View File

@ -3,16 +3,14 @@
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use crate::Pass;
use crate::func::ParseFunc;
use super::func::FuncCall;
use super::parsing::ParseState;
use super::parsing::CallParser;
use super::Model;
/// A map from identifiers to function parsers.
pub struct Scope {
parsers: HashMap<String, Box<Parser>>,
fallback: Box<Parser>,
parsers: HashMap<String, Box<CallParser>>,
fallback: Box<CallParser>,
}
impl Scope {
@ -22,7 +20,7 @@ impl Scope {
where F: ParseFunc<Meta=()> + Model + 'static {
Scope {
parsers: HashMap::new(),
fallback: parser::<F>(()),
fallback: make_parser::<F>(()),
}
}
@ -43,17 +41,17 @@ impl Scope {
where F: ParseFunc + Model + 'static {
self.parsers.insert(
name.to_string(),
parser::<F>(metadata),
make_parser::<F>(metadata),
);
}
/// Return the parser with the given name if there is one.
pub fn get_parser(&self, name: &str) -> Option<&Parser> {
pub fn get_parser(&self, name: &str) -> Option<&CallParser> {
self.parsers.get(name).map(AsRef::as_ref)
}
/// Return the fallback parser.
pub fn get_fallback_parser(&self) -> &Parser {
pub fn get_fallback_parser(&self) -> &CallParser {
&*self.fallback
}
}
@ -66,11 +64,7 @@ impl Debug for Scope {
}
}
/// A function which parses the source of a function into a model type which
/// implements [`Model`].
type Parser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>;
fn parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<Parser>
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
where F: ParseFunc + Model + 'static {
Box::new(move |f, s| {
F::parse(f, s, metadata.clone())

View File

@ -4,16 +4,22 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Sub};
use serde::Serialize;
/// Span offsetting.
pub trait Offset {
/// Offset all spans contained in `Self` by the given position.
fn offset(self, by: Pos) -> Self;
}
/// A vector of spanned values of type `T`.
pub type SpanVec<T> = Vec<Spanned<T>>;
/// [Offset](Span::offset) all spans in a vector of spanned things by a start
/// position.
pub fn offset_spans<T>(
vec: SpanVec<T>,
start: Position,
) -> impl Iterator<Item=Spanned<T>> {
vec.into_iter().map(move |s| s.map_span(|span| span.offset(start)))
impl<T> Offset for SpanVec<T> {
fn offset(mut self, by: Pos) -> Self {
for spanned in &mut self {
spanned.span = spanned.span.offset(by);
}
self
}
}
/// A value with the span it corresponds to in the source code.
@ -53,6 +59,12 @@ impl<T> Spanned<T> {
}
}
impl<T> Offset for Spanned<T> {
fn offset(self, by: Pos) -> Self {
self.map_span(|span| span.offset(by))
}
}
impl<T: Debug> Debug for Spanned<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.v.fmt(f)?;
@ -68,20 +80,25 @@ impl<T: Debug> Debug for Spanned<T> {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct Span {
/// The inclusive start position.
pub start: Position,
pub start: Pos,
/// The inclusive end position.
pub end: Position,
pub end: Pos,
}
impl Span {
/// The zero span.
pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO };
pub const ZERO: Span = Span { start: Pos::ZERO, end: Pos::ZERO };
/// Create a new span from start and end positions.
pub fn new(start: Position, end: Position) -> Span {
pub fn new(start: Pos, end: Pos) -> Span {
Span { start, end }
}
/// Create a span including just a single position.
pub fn at(pos: Pos) -> Span {
Span { start: pos, end: pos }
}
/// Create a new span with the earlier start and later end position.
pub fn merge(a: Span, b: Span) -> Span {
Span {
@ -90,24 +107,17 @@ impl Span {
}
}
/// Create a span including just a single position.
pub fn at(pos: Position) -> Span {
Span { start: pos, end: pos }
}
/// Expand a span by merging it with another span.
pub fn expand(&mut self, other: Span) {
*self = Span::merge(*self, other)
}
}
/// Offset a span by a start position.
///
/// This is, for example, used to translate error spans from function local
/// to global.
pub fn offset(self, start: Position) -> Span {
impl Offset for Span {
fn offset(self, by: Pos) -> Self {
Span {
start: start + self.start,
end: start + self.end,
start: self.start.offset(by),
end: self.end.offset(by),
}
}
}
@ -120,34 +130,40 @@ impl Debug for Span {
/// Zero-indexed line-column position in source code.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
pub struct Position {
pub struct Pos {
/// The zero-indexed line.
pub line: usize,
/// The zero-indexed column.
pub column: usize,
}
impl Position {
impl Pos {
/// The line 0, column 0 position.
pub const ZERO: Position = Position { line: 0, column: 0 };
pub const ZERO: Pos = Pos { line: 0, column: 0 };
/// Create a new position from line and column.
pub fn new(line: usize, column: usize) -> Position {
Position { line, column }
pub fn new(line: usize, column: usize) -> Pos {
Pos { line, column }
}
}
impl Add for Position {
type Output = Position;
impl Offset for Pos {
fn offset(self, by: Pos) -> Self {
by + self
}
}
fn add(self, rhs: Position) -> Position {
impl Add for Pos {
type Output = Pos;
fn add(self, rhs: Pos) -> Pos {
if rhs.line == 0 {
Position {
Pos {
line: self.line,
column: self.column + rhs.column
}
} else {
Position {
Pos {
line: self.line + rhs.line,
column: rhs.column,
}
@ -155,17 +171,17 @@ impl Add for Position {
}
}
impl Sub for Position {
type Output = Position;
impl Sub for Pos {
type Output = Pos;
fn sub(self, rhs: Position) -> Position {
fn sub(self, rhs: Pos) -> Pos {
if self.line == rhs.line {
Position {
Pos {
line: 0,
column: self.column - rhs.column
}
} else {
Position {
Pos {
line: self.line - rhs.line,
column: self.column,
}
@ -173,7 +189,7 @@ impl Sub for Position {
}
}
impl Debug for Position {
impl Debug for Pos {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}

View File

@ -39,11 +39,11 @@ macro_rules! span_vec {
macro_rules! span_item {
(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => ({
use $crate::syntax::span::{Position, Span, Spanned};
use $crate::syntax::span::{Pos, Span, Spanned};
Spanned {
span: Span::new(
Position::new($sl, $sc),
Position::new($el, $ec)
Pos::new($sl, $sc),
Pos::new($el, $ec)
),
v: $v
}

View File

@ -2,8 +2,8 @@ use std::iter::Peekable;
use std::str::Chars;
use unicode_xid::UnicodeXID;
use crate::size::Size;
use super::span::{Position, Span, Spanned};
use crate::length::Length;
use super::span::{Pos, Span, Spanned};
use Token::*;
use TokenMode::*;
@ -73,8 +73,8 @@ pub enum Token<'s> {
},
/// A number in a function header: `3.14`.
ExprNumber(f64),
/// A size in a function header: `12pt`.
ExprSize(Size),
/// A length in a function header: `12pt`.
ExprLength(Length),
/// A boolean in a function header: `true | false`.
ExprBool(bool),
/// A hex value in a function header: `#20d82a`.
@ -129,7 +129,7 @@ impl<'s> Token<'s> {
ExprIdent(_) => "identifier",
ExprStr { .. } => "string",
ExprNumber(_) => "number",
ExprSize(_) => "size",
ExprLength(_) => "length",
ExprBool(_) => "bool",
ExprHex(_) => "hex value",
Plus => "plus",
@ -153,7 +153,7 @@ pub struct Tokens<'s> {
src: &'s str,
mode: TokenMode,
iter: Peekable<Chars<'s>>,
position: Position,
pos: Pos,
index: usize,
}
@ -172,12 +172,12 @@ impl<'s> Tokens<'s> {
///
/// The first token's span starts an the given `offset` position instead of
/// the zero position.
pub fn new(src: &'s str, offset: Position, mode: TokenMode) -> Tokens<'s> {
pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Tokens<'s> {
Tokens {
src,
mode,
iter: src.chars().peekable(),
position: offset,
pos: offset,
index: 0,
}
}
@ -190,8 +190,8 @@ impl<'s> Tokens<'s> {
/// The line-colunn position in the source at which the last token ends and
/// next token will start.
pub fn pos(&self) -> Position {
self.position
pub fn pos(&self) -> Pos {
self.pos
}
}
@ -315,14 +315,14 @@ impl<'s> Tokens<'s> {
}, true, 0, -2).0)
}
fn read_whitespace(&mut self, start: Position) -> Token<'s> {
fn read_whitespace(&mut self, start: Pos) -> Token<'s> {
self.read_string_until(|n| !n.is_whitespace(), false, 0, 0);
let end = self.pos();
Space(end.line - start.line)
}
fn read_function(&mut self, start: Position) -> Token<'s> {
fn read_function(&mut self, start: Pos) -> Token<'s> {
let (header, terminated) = self.read_function_part(Header);
self.eat();
@ -354,7 +354,7 @@ impl<'s> Tokens<'s> {
self.eat();
match n {
'[' => { self.read_function(Position::ZERO); }
'[' => { self.read_function(Pos::ZERO); }
'/' if self.peek() == Some('/') => { self.read_line_comment(); }
'/' if self.peek() == Some('*') => { self.read_block_comment(); }
'"' if mode == Header => { self.read_string(); }
@ -427,8 +427,8 @@ impl<'s> Tokens<'s> {
ExprNumber(num)
} else if let Some(num) = parse_percentage(text) {
ExprNumber(num / 100.0)
} else if let Ok(size) = text.parse::<Size>() {
ExprSize(size)
} else if let Ok(length) = text.parse::<Length>() {
ExprLength(length)
} else if is_identifier(text) {
ExprIdent(text)
} else {
@ -476,10 +476,10 @@ impl<'s> Tokens<'s> {
self.index += c.len_utf8();
if is_newline_char(c) && !(c == '\r' && self.peek() == Some('\n')) {
self.position.line += 1;
self.position.column = 0;
self.pos.line += 1;
self.pos.column = 0;
} else {
self.position.column += 1;
self.pos.column += 1;
}
Some(c)
@ -543,7 +543,7 @@ mod tests {
LeftBrace as LB, RightBrace as RB,
ExprIdent as Id,
ExprNumber as Num,
ExprSize as Sz,
ExprLength as Len,
ExprBool as Bool,
ExprHex as Hex,
Text as T,
@ -557,7 +557,7 @@ mod tests {
macro_rules! t {
($mode:expr, $source:expr => [$($tokens:tt)*]) => {
let (exp, spans) = span_vec![$($tokens)*];
let found = Tokens::new($source, Position::ZERO, $mode).collect::<Vec<_>>();
let found = Tokens::new($source, Pos::ZERO, $mode).collect::<Vec<_>>();
check($source, exp, found, spans);
}
}
@ -640,13 +640,13 @@ mod tests {
t!(Header, "__main__" => [Id("__main__")]);
t!(Header, ".func.box" => [Id(".func.box")]);
t!(Header, "arg, _b, _1" => [Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")]);
t!(Header, "12_pt, 12pt" => [Invalid("12_pt"), Comma, S(0), Sz(Size::pt(12.0))]);
t!(Header, "1e5in" => [Sz(Size::inches(100000.0))]);
t!(Header, "2.3cm" => [Sz(Size::cm(2.3))]);
t!(Header, "12e-3in" => [Sz(Size::inches(12e-3))]);
t!(Header, "6.1cm + 4pt,a=1*2" => [Sz(Size::cm(6.1)), S(0), Plus, S(0), Sz(Size::pt(4.0)), Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0)]);
t!(Header, "12_pt, 12pt" => [Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0))]);
t!(Header, "1e5in" => [Len(Length::inches(100000.0))]);
t!(Header, "2.3cm" => [Len(Length::cm(2.3))]);
t!(Header, "12e-3in" => [Len(Length::inches(12e-3))]);
t!(Header, "6.1cm + 4pt,a=1*2" => [Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)), Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0)]);
t!(Header, "(5 - 1) / 2.1" => [LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP, S(0), Slash, S(0), Num(2.1)]);
t!(Header, "02.4mm" => [Sz(Size::mm(2.4))]);
t!(Header, "02.4mm" => [Len(Length::mm(2.4))]);
t!(Header, "2.4.cm" => [Invalid("2.4.cm")]);
t!(Header, "(1,2)" => [LP, Num(1.0), Comma, Num(2.0), RP]);
t!(Header, "{abc}" => [LB, Id("abc"), RB]);

View File

@ -12,13 +12,13 @@ use futures_executor::block_on;
use typstc::{Typesetter, DebugErrorProvider};
use typstc::layout::MultiLayout;
use typstc::size::{Size, Size2D, ValueBox};
use typstc::style::{PageStyle, PaperClass};
use typstc::length::{Length, Size, Value4};
use typstc::style::PageStyle;
use typstc::paper::PaperClass;
use typstc::export::pdf;
use toddle::query::FontIndex;
use toddle::query::fs::EagerFsProvider;
type DynResult<T> = Result<T, Box<dyn Error>>;
fn main() -> DynResult<()> {
@ -72,8 +72,8 @@ fn test(name: &str, src: &str) -> DynResult<()> {
typesetter.set_page_style(PageStyle {
class: PaperClass::Custom,
dimensions: Size2D::with_all(Size::pt(250.0)),
margins: ValueBox::with_all(None),
dimensions: Size::with_all(Length::pt(250.0)),
margins: Value4::with_all(None),
});
let layouts = compile(&typesetter, src);
@ -123,14 +123,14 @@ fn test(name: &str, src: &str) -> DynResult<()> {
fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout {
if cfg!(debug_assertions) {
let typeset = block_on(typesetter.typeset(src));
let problems = typeset.feedback.problems;
let diagnostics = typeset.feedback.diagnostics;
if !problems.is_empty() {
for problem in problems {
if !diagnostics.is_empty() {
for diagnostic in diagnostics {
println!(" {:?} {:?}: {}",
problem.v.severity,
problem.span,
problem.v.message
diagnostic.v.level,
diagnostic.span,
diagnostic.v.message
);
}
}