mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
State-based monospace handling
This commit is contained in:
parent
dcfbf95220
commit
ec5384c97f
@ -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(" "));
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user