Highlight bold / italic / monospace 🎨

This commit is contained in:
Laurenz 2020-02-06 11:15:48 +01:00
parent 751812f451
commit 02dc29d18a
9 changed files with 116 additions and 34 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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::<Header>().ok()?;
let font_unit_ratio = 1.0 / (header.units_per_em as f32);

View File

@ -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
}

View File

@ -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) }
}

View File

@ -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,

View File

@ -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 {

View File

@ -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::<DebugFn>();
scope.add::<DebugFn>("f");
scope.add::<DebugFn>("n");
scope.add::<DebugFn>("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)]);
}
}

View File

@ -403,15 +403,13 @@ impl<'s> Tokens<'s> {
fn eat(&mut self) -> Option<char> {
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)