Highlight bold / italic / monospace 🎨
This commit is contained in:
parent
751812f451
commit
02dc29d18a
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
10
src/lib.rs
10
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
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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)]);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user