State-based monospace handling

This commit is contained in:
Laurenz 2021-07-25 01:16:38 +02:00
parent dcfbf95220
commit ec5384c97f
5 changed files with 73 additions and 57 deletions

View File

@ -1,7 +1,7 @@
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use super::{Exec, ExecWithMap, FontFamily, State}; use super::{Exec, ExecWithMap, State};
use crate::diag::{Diag, DiagSet, Pass}; use crate::diag::{Diag, DiagSet, Pass};
use crate::eco::EcoString; use crate::eco::EcoString;
use crate::eval::{ExprMap, Template}; use crate::eval::{ExprMap, Template};
@ -44,15 +44,6 @@ impl ExecContext {
self.diags.insert(diag); self.diags.insert(diag);
} }
/// Set the font to monospace.
pub fn set_monospace(&mut self) {
self.state
.font_mut()
.families_mut()
.list
.insert(0, FontFamily::Monospace);
}
/// Execute a template and return the result as a stack node. /// Execute a template and return the result as a stack node.
pub fn exec_template_stack(&mut self, template: &Template) -> StackNode { pub fn exec_template_stack(&mut self, template: &Template) -> StackNode {
self.exec_stack(|ctx| template.exec(ctx)) self.exec_stack(|ctx| template.exec(ctx))
@ -83,6 +74,14 @@ impl ExecContext {
self.stack.par.push(self.make_text_node(text)); self.stack.par.push(self.make_text_node(text));
} }
/// Push text, but in monospace.
pub fn push_monospace_text(&mut self, text: impl Into<EcoString>) {
let prev = Rc::clone(&self.state.font);
self.state.font_mut().monospace = true;
self.push_text(text);
self.state.font = prev;
}
/// Push a word space into the active paragraph. /// Push a word space into the active paragraph.
pub fn push_word_space(&mut self) { pub fn push_word_space(&mut self) {
self.stack.par.push_soft(self.make_text_node(" ")); self.stack.par.push_soft(self.make_text_node(" "));

View File

@ -7,7 +7,6 @@ pub use context::*;
pub use state::*; pub use state::*;
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc;
use crate::diag::Pass; use crate::diag::Pass;
use crate::eco::EcoString; use crate::eco::EcoString;
@ -75,10 +74,7 @@ impl Exec for RawNode {
ctx.parbreak(); ctx.parbreak();
} }
let snapshot = Rc::clone(&ctx.state.font); ctx.push_monospace_text(&self.text);
ctx.set_monospace();
ctx.push_text(&self.text);
ctx.state.font = snapshot;
if self.block { if self.block {
ctx.parbreak(); ctx.parbreak();
@ -143,14 +139,9 @@ impl Exec for Value {
Value::Str(v) => ctx.push_text(v), Value::Str(v) => ctx.push_text(v),
Value::Template(v) => v.exec(ctx), Value::Template(v) => v.exec(ctx),
Value::Error => {} Value::Error => {}
other => { // For values which can't be shown "naturally", we print the
// For values which can't be shown "naturally", we print // representation in monospace.
// the representation in monospace. other => ctx.push_monospace_text(pretty(other)),
let prev = Rc::clone(&ctx.state.font.families);
ctx.set_monospace();
ctx.push_text(pretty(other));
ctx.state.font_mut().families = prev;
}
} }
} }
} }

View File

@ -111,6 +111,14 @@ pub struct FontState {
pub families: Rc<FamilyList>, pub families: Rc<FamilyList>,
/// The selected font variant. /// The selected font variant.
pub variant: FontVariant, pub variant: FontVariant,
/// Whether the strong toggle is active or inactive. This determines
/// whether the next `*` adds or removes font weight.
pub strong: bool,
/// Whether the emphasis toggle is active or inactive. This determines
/// whether the next `_` makes italic or non-italic.
pub emph: bool,
/// Whether the monospace toggle is active or inactive.
pub monospace: bool,
/// The font size. /// The font size.
pub size: Length, pub size: Length,
/// The top end of the text bounding box. /// The top end of the text bounding box.
@ -119,12 +127,6 @@ pub struct FontState {
pub bottom_edge: VerticalFontMetric, pub bottom_edge: VerticalFontMetric,
/// Glyph color. /// Glyph color.
pub fill: Paint, pub fill: Paint,
/// Whether the strong toggle is active or inactive. This determines
/// whether the next `*` adds or removes font weight.
pub strong: bool,
/// Whether the emphasis toggle is active or inactive. This determines
/// whether the next `_` makes italic or non-italic.
pub emph: bool,
/// The specifications for a strikethrough line, if any. /// The specifications for a strikethrough line, if any.
pub strikethrough: Option<Rc<LineState>>, pub strikethrough: Option<Rc<LineState>>,
/// The specifications for a underline, if any. /// The specifications for a underline, if any.
@ -138,6 +140,35 @@ impl FontState {
pub fn families_mut(&mut self) -> &mut FamilyList { pub fn families_mut(&mut self) -> &mut FamilyList {
Rc::make_mut(&mut self.families) Rc::make_mut(&mut self.families)
} }
/// The canonical family iterator.
pub fn families(&self) -> impl Iterator<Item = &str> + Clone {
let head = if self.monospace {
self.families.monospace.as_slice()
} else {
&[]
};
head.iter().map(String::as_str).chain(self.families.iter())
}
/// The canonical variant with `strong` and `emph` factored in.
pub fn variant(&self) -> FontVariant {
let mut variant = self.variant;
if self.strong {
variant.weight = variant.weight.thicken(300);
}
if self.emph {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
FontStyle::Oblique => FontStyle::Normal,
}
}
variant
}
} }
impl Default for FontState { impl Default for FontState {
@ -149,12 +180,13 @@ impl Default for FontState {
weight: FontWeight::REGULAR, weight: FontWeight::REGULAR,
stretch: FontStretch::NORMAL, stretch: FontStretch::NORMAL,
}, },
strong: false,
emph: false,
monospace: false,
size: Length::pt(11.0), size: Length::pt(11.0),
top_edge: VerticalFontMetric::CapHeight, top_edge: VerticalFontMetric::CapHeight,
bottom_edge: VerticalFontMetric::Baseline, bottom_edge: VerticalFontMetric::Baseline,
fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)),
strong: false,
emph: false,
strikethrough: None, strikethrough: None,
underline: None, underline: None,
overline: None, overline: None,

View File

@ -6,7 +6,7 @@ use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::{Element, Frame, Glyph, LayoutContext, Text};
use crate::exec::{FontState, LineState}; use crate::exec::{FontState, LineState};
use crate::font::{Face, FaceId, FontStyle, LineMetrics}; use crate::font::{Face, FaceId, FontVariant, LineMetrics};
use crate::geom::{Dir, Length, Point, Size}; use crate::geom::{Dir, Length, Point, Size};
use crate::layout::Geometry; use crate::layout::Geometry;
use crate::util::SliceExt; use crate::util::SliceExt;
@ -188,9 +188,18 @@ pub fn shape<'a>(
state: &'a FontState, state: &'a FontState,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let mut glyphs = vec![]; let mut glyphs = vec![];
let families = state.families.iter();
if !text.is_empty() { if !text.is_empty() {
shape_segment(ctx, &mut glyphs, 0, text, dir, state, families, None); shape_segment(
ctx,
&mut glyphs,
0,
text,
dir,
state.size,
state.variant(),
state.families(),
None,
);
} }
let (size, baseline) = measure(ctx, &glyphs, state); let (size, baseline) = measure(ctx, &glyphs, state);
@ -212,7 +221,8 @@ fn shape_segment<'a>(
base: usize, base: usize,
text: &str, text: &str,
dir: Dir, dir: Dir,
state: &FontState, size: Length,
variant: FontVariant,
mut families: impl Iterator<Item = &'a str> + Clone, mut families: impl Iterator<Item = &'a str> + Clone,
mut first_face: Option<FaceId>, mut first_face: Option<FaceId>,
) { ) {
@ -221,20 +231,6 @@ fn shape_segment<'a>(
// Try to load the next available font family. // Try to load the next available font family.
match families.next() { match families.next() {
Some(family) => { Some(family) => {
let mut variant = state.variant;
if state.strong {
variant.weight = variant.weight.thicken(300);
}
if state.emph {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
FontStyle::Oblique => FontStyle::Normal,
}
}
if let Some(id) = ctx.fonts.select(family, variant) { if let Some(id) = ctx.fonts.select(family, variant) {
break (id, true); break (id, true);
} }
@ -280,8 +276,8 @@ fn shape_segment<'a>(
glyphs.push(ShapedGlyph { glyphs.push(ShapedGlyph {
face_id, face_id,
glyph_id: info.glyph_id as u16, glyph_id: info.glyph_id as u16,
x_advance: face.to_em(pos[i].x_advance).to_length(state.size), x_advance: face.to_em(pos[i].x_advance).to_length(size),
x_offset: face.to_em(pos[i].x_offset).to_length(state.size), x_offset: face.to_em(pos[i].x_offset).to_length(size),
text_index: base + cluster, text_index: base + cluster,
safe_to_break: !info.unsafe_to_break(), safe_to_break: !info.unsafe_to_break(),
}); });
@ -332,7 +328,8 @@ fn shape_segment<'a>(
base + range.start, base + range.start,
&text[range], &text[range],
dir, dir,
state, size,
variant,
families.clone(), families.clone(),
first_face, first_face,
); );
@ -362,7 +359,7 @@ fn measure(
if glyphs.is_empty() { if glyphs.is_empty() {
// When there are no glyphs, we just use the vertical metrics of the // When there are no glyphs, we just use the vertical metrics of the
// first available font. // first available font.
for family in state.families.iter() { for family in state.families() {
if let Some(face_id) = ctx.fonts.select(family, state.variant) { if let Some(face_id) = ctx.fonts.select(family, state.variant) {
expand_vertical(ctx.fonts.get(face_id)); expand_vertical(ctx.fonts.get(face_id));
break; break;

View File

@ -143,10 +143,7 @@ fn register_helpers(scope: &mut Scope, panics: Rc<RefCell<Vec<Panic>>>) {
scope.def_func("args", |_, args| { scope.def_func("args", |_, args| {
let repr = typst::pretty::pretty(args); let repr = typst::pretty::pretty(args);
args.items.clear(); args.items.clear();
Value::template(move |ctx| { Value::template(move |ctx| ctx.push_monospace_text(&repr))
ctx.set_monospace();
ctx.push_text(&repr);
})
}); });
scope.def_func("test", move |ctx, args| { scope.def_func("test", move |ctx, args| {
let lhs = args.expect::<Value>(ctx, "left-hand side"); let lhs = args.expect::<Value>(ctx, "left-hand side");