From 02dc29d18a7b67edf0eaa5d125be22eec6cfebb7 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 6 Feb 2020 11:15:48 +0100 Subject: [PATCH] =?UTF-8?q?Highlight=20bold=20/=20italic=20/=20monospace?= =?UTF-8?q?=20=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 1 + src/layout/model.rs | 65 ++++++++++++++++++++++++++++--------------- src/layout/text.rs | 25 +++++++++++++---- src/lib.rs | 10 ++++++- src/library/mod.rs | 2 ++ src/style.rs | 3 ++ src/syntax/mod.rs | 8 +++++- src/syntax/parsing.rs | 30 ++++++++++++++++++++ src/syntax/tokens.rs | 6 ++-- 9 files changed, 116 insertions(+), 34 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3a095eef3..24b1b79cb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,7 @@ pub struct Error { /// How severe / important an error 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, diff --git a/src/layout/model.rs b/src/layout/model.rs index 343205ec0..7e899f2e3 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -5,12 +5,13 @@ use std::future::Future; use std::pin::Pin; use smallvec::smallvec; +use toddle::query::FontStyle; use crate::{Pass, Feedback}; use crate::GlobalFontLoader; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::size::{Size, Size2D}; -use crate::syntax::{Model, SyntaxModel, Node}; +use crate::syntax::{Model, SyntaxModel, Node, Decoration}; use crate::syntax::span::{Span, Spanned}; use super::line::{LineLayouter, LineContext}; use super::text::{layout_text, TextContext}; @@ -47,7 +48,7 @@ pub struct LayoutContext<'a> { /// Whether the layout that is to be created will be nested in a parent /// container. pub nested: bool, - /// Whether to debug render a box around the layout. + /// Whether to render debug boxs around layouts if `nested` is true. pub debug: bool, } @@ -118,7 +119,7 @@ impl<'a> ModelLayouter<'a> { axes: ctx.axes, alignment: ctx.alignment, repeat: ctx.repeat, - debug: ctx.debug, + debug: ctx.debug && ctx.nested, line_spacing: ctx.style.text.line_spacing(), }), style: ctx.style.clone(), @@ -137,7 +138,6 @@ impl<'a> ModelLayouter<'a> { style: &self.style, spaces: self.layouter.remaining(), nested: true, - debug: false, .. self.ctx }).await; @@ -157,29 +157,48 @@ impl<'a> ModelLayouter<'a> { ) -> DynFuture<'r, ()> { Box::pin(async move { use Node::*; - for node in &model.nodes { - match &node.v { + for Spanned { v: node, span } in &model.nodes { + let decorate = |this: &mut ModelLayouter, deco| { + this.feedback.decos.push(Spanned::new(deco, *span)); + }; + + match node { Space => self.layout_space(), Newline => self.layout_paragraph(), - Text(text) => self.layout_text(text).await, - ToggleItalic => self.style.text.variant.style.toggle(), - ToggleBolder => { - let fac = if self.style.text.bolder { -1 } else { 1 }; - self.style.text.variant.weight.0 += 300 * fac; - self.style.text.bolder = !self.style.text.bolder; - } - ToggleMonospace => { - let list = &mut self.style.text.fallback.list; - match list.get(0).map(|s| s.as_str()) { - Some("monospace") => { list.remove(0); }, - _ => list.insert(0, "monospace".to_string()), + Text(text) => { + if self.style.text.variant.style == FontStyle::Italic { + decorate(self, Decoration::Italic); } - self.style.text.fallback.flatten(); + + if self.style.text.bolder { + decorate(self, Decoration::Bold); + } + + if self.style.text.monospace { + decorate(self, Decoration::Monospace); + } + + self.layout_text(text).await; + } + + ToggleItalic => { + self.style.text.variant.style.toggle(); + decorate(self, Decoration::Italic); + } + + ToggleBolder => { + self.style.text.bolder = !self.style.text.bolder; + decorate(self, Decoration::Bold); + } + + ToggleMonospace => { + self.style.text.monospace = !self.style.text.monospace; + decorate(self, Decoration::Monospace); } Node::Model(model) => { - self.layout(Spanned::new(model.as_ref(), node.span)).await; + self.layout(Spanned::new(model.as_ref(), *span)).await; } } } @@ -195,7 +214,7 @@ impl<'a> ModelLayouter<'a> { fn execute_command<'r>( &'r mut self, command: Command<'r>, - span: Span, + model_span: Span, ) -> DynFuture<'r, ()> { Box::pin(async move { use Command::*; @@ -213,7 +232,7 @@ impl<'a> ModelLayouter<'a> { BreakParagraph => self.layout_paragraph(), BreakPage => { if self.ctx.nested { - self.feedback.errors.push(err!(span; + self.feedback.errors.push(err!(model_span; "page break cannot be issued from nested context")); } else { self.layouter.finish_space(true) @@ -226,7 +245,7 @@ impl<'a> ModelLayouter<'a> { } SetPageStyle(style) => { if self.ctx.nested { - self.feedback.errors.push(err!(span; + self.feedback.errors.push(err!(model_span; "page style cannot be changed from nested context")); } else { self.style.page = style; diff --git a/src/layout/text.rs b/src/layout/text.rs index c6fa45d19..614d59fd0 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -113,13 +113,28 @@ impl<'a> TextLayouter<'a> { async fn select_font(&mut self, c: char) -> Option<(FontIndex, Size)> { let mut loader = self.ctx.loader.borrow_mut(); - let query = FontQuery { - fallback: &self.ctx.style.fallback, - variant: self.ctx.style.variant, - c, + let mut variant = self.ctx.style.variant; + if self.ctx.style.bolder { + variant.weight.0 += 300; + } + + let queried = if self.ctx.style.monospace { + loader.get(FontQuery { + // FIXME: This is a hack. + fallback: std::iter::once("source code pro") + .chain(self.ctx.style.fallback.iter()), + variant, + c, + }).await + } else { + loader.get(FontQuery { + fallback: self.ctx.style.fallback.iter(), + variant, + c, + }).await }; - if let Some((font, index)) = loader.get(query).await { + if let Some((font, index)) = queried { // Determine the width of the char. let header = font.read_table::
().ok()?; let font_unit_ratio = 1.0 / (header.units_per_em as f32); diff --git a/src/lib.rs b/src/lib.rs index 7136b2bde..4a92d0960 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,8 @@ pub struct Typesetter { style: LayoutStyle, /// The standard library scope. scope: Scope, + /// Whether to render debug boxes. + debug: bool, } /// The font loader type used in the [`Typesetter`]. @@ -83,6 +85,7 @@ impl Typesetter { loader: RefCell::new(FontLoader::new(provider)), style: LayoutStyle::default(), scope: Scope::with_std(), + debug: false, } } @@ -96,6 +99,11 @@ impl Typesetter { self.style.page = style; } + /// Set whether to render debug boxes. + pub fn set_debug(&mut self, debug: bool) { + self.debug = debug; + } + /// A reference to the backing font loader. pub fn loader(&self) -> &GlobalFontLoader { &self.loader @@ -126,7 +134,7 @@ impl Typesetter { axes: LayoutAxes::new(LeftToRight, TopToBottom), alignment: LayoutAlignment::new(Origin, Origin), nested: false, - debug: false, + debug: self.debug, }, ).await } diff --git a/src/library/mod.rs b/src/library/mod.rs index 08dffdd73..08608d05d 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -54,6 +54,8 @@ function! { } parse(header, body, ctx, f) { + header.args.pos.items.clear(); + header.args.key.pairs.clear(); ValFunc { body: body!(opt: body, ctx, f) } } diff --git a/src/style.rs b/src/style.rs index 075baa5ac..57d6b6f3b 100644 --- a/src/style.rs +++ b/src/style.rs @@ -24,6 +24,8 @@ pub struct TextStyle { /// Whether the bolder toggle is active or inactive. This determines /// whether the next `*` adds or removes font weight. pub bolder: bool, + /// Whether the monospace toggle is active or inactive. + pub monospace: bool, /// The base font size. pub base_font_size: Size, /// The font scale to apply on the base font size. @@ -77,6 +79,7 @@ impl Default for TextStyle { weight: FontWeight(400), }, bolder: false, + monospace: false, base_font_size: Size::pt(11.0), font_scale: 1.0, word_spacing_scale: 0.25, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 8596a618b..620a929eb 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -100,7 +100,6 @@ pub enum Decoration { /// ^^^ /// ``` ValidFuncName, - /// An invalid function name: /// ```typst /// [blabla] @@ -114,6 +113,13 @@ pub enum Decoration { /// ^^^^^ /// ``` ArgumentKey, + + /// Italic. + Italic, + /// Bold. + Bold, + /// Monospace. + Monospace, } impl dyn Model { diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 1526a5cbb..d24985a6d 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -375,6 +375,7 @@ mod tests { Space as S, Newline as N, ToggleItalic as Italic, ToggleBolder as Bold, ToggleMonospace as Mono, }; + use Decoration::*; pub use Expr::{Number as Num, Bool}; pub fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) } @@ -421,9 +422,32 @@ mod tests { }; } + /// Test whether the given string yields the given decorations. + macro_rules! d { + ($s:expr => [$(($sl:tt:$sc:tt, $el:tt:$ec:tt, $d:expr)),* $(,)?]) => { + let ctx = ParseContext { scope: &scope() }; + let decos = parse(Position::ZERO, $s, ctx).feedback.decos; + + let expected = vec![ + $(Spanned { + v: $d, + span: Span { + start: Position { line: $sl, column: $sc }, + end: Position { line: $el, column: $ec }, + }, + }),* + ]; + + if decos != expected { + fail($s, decos, expected); + } + }; + } + fn scope() -> Scope { let mut scope = Scope::new::(); scope.add::("f"); + scope.add::("n"); scope.add::("box"); scope } @@ -552,4 +576,10 @@ mod tests { (0:21, 0:21, "expected closing bracket"), ]); } + + #[test] + fn parse_decos() { + d!("*Technische Universität Berlin* [n]\n [n]" + => [(0:33, 0:34, ValidFuncName), (1:33, 1:34, ValidFuncName)]); + } } diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 7b52f655a..f4ea5daf5 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -403,15 +403,13 @@ impl<'s> Tokens<'s> { fn eat(&mut self) -> Option { let c = self.iter.next()?; - let len = c.len_utf8(); - - self.index += len; + self.index += c.len_utf8(); if is_newline_char(c) && !(c == '\r' && self.peek() == Some('\n')) { self.position.line += 1; self.position.column = 0; } else { - self.position.column += len; + self.position.column += 1; } Some(c)