mirror of
https://github.com/typst/typst
synced 2025-07-27 14:27:56 +08:00
Compare commits
7 Commits
d0d4e8e796
...
0dd19b5f6c
Author | SHA1 | Date | |
---|---|---|---|
|
0dd19b5f6c | ||
|
6594a0f530 | ||
|
70f619e896 | ||
|
91189f4061 | ||
|
e48fe5e301 | ||
|
5e202843c1 | ||
|
632186f446 |
@ -300,6 +300,7 @@ impl GlyphFragment {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let buffer = rustybuzz::shape_with_plan(font.rusty(), &plan, buffer);
|
let buffer = rustybuzz::shape_with_plan(font.rusty(), &plan, buffer);
|
||||||
|
// TODO: deal with multiple glyphs.
|
||||||
if buffer.len() != 1 {
|
if buffer.len() != 1 {
|
||||||
bail!(span, "did not get a single glyph after shaping {}", text);
|
bail!(span, "did not get a single glyph after shaping {}", text);
|
||||||
}
|
}
|
||||||
|
@ -129,44 +129,41 @@ pub fn layout_symbol(
|
|||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
assert!(
|
|
||||||
elem.text.len() <= 4 && elem.text.chars().count() == 1,
|
|
||||||
"TODO: layout multi-char symbol"
|
|
||||||
);
|
|
||||||
let elem_char = elem
|
|
||||||
.text
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.expect("TODO: should an empty symbol value forbidden?");
|
|
||||||
|
|
||||||
// Switch dotless char to normal when we have the dtls OpenType feature.
|
|
||||||
// This should happen before the main styling pass.
|
|
||||||
let dtls = style_dtls();
|
|
||||||
let (unstyled_c, symbol_styles) = match try_dotless(elem_char) {
|
|
||||||
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
|
||||||
_ => (elem_char, styles),
|
|
||||||
};
|
|
||||||
|
|
||||||
let variant = styles.get(EquationElem::variant);
|
let variant = styles.get(EquationElem::variant);
|
||||||
let bold = styles.get(EquationElem::bold);
|
let bold = styles.get(EquationElem::bold);
|
||||||
let italic = styles.get(EquationElem::italic);
|
let italic = styles.get(EquationElem::italic);
|
||||||
|
let dtls = style_dtls();
|
||||||
|
let has_dtls_feat = has_dtls_feat(ctx.font);
|
||||||
|
for cluster in elem.text.graphemes(true) {
|
||||||
|
// Switch dotless char to normal when we have the dtls OpenType feature.
|
||||||
|
// This should happen before the main styling pass.
|
||||||
|
let mut enable_dtls = false;
|
||||||
|
let text: EcoString = cluster
|
||||||
|
.chars()
|
||||||
|
.flat_map(|mut c| {
|
||||||
|
if has_dtls_feat && let Some(d) = try_dotless(c) {
|
||||||
|
enable_dtls = true;
|
||||||
|
c = d;
|
||||||
|
}
|
||||||
|
to_style(c, MathStyle::select(c, variant, bold, italic))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let styles = if enable_dtls { styles.chain(&dtls) } else { styles };
|
||||||
|
|
||||||
let style = MathStyle::select(unstyled_c, variant, bold, italic);
|
let fragment: MathFragment =
|
||||||
let text: EcoString = to_style(unstyled_c, style).collect();
|
match GlyphFragment::new(ctx.font, styles, &text, elem.span()) {
|
||||||
|
Ok(mut glyph) => {
|
||||||
let fragment: MathFragment =
|
adjust_glyph_layout(&mut glyph, ctx, styles);
|
||||||
match GlyphFragment::new(ctx.font, symbol_styles, &text, elem.span()) {
|
glyph.into()
|
||||||
Ok(mut glyph) => {
|
}
|
||||||
adjust_glyph_layout(&mut glyph, ctx, styles);
|
Err(_) => {
|
||||||
glyph.into()
|
// Not in the math font, fallback to normal inline text layout.
|
||||||
}
|
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
|
||||||
Err(_) => {
|
layout_inline_text(&text, elem.span(), ctx, styles)?.into()
|
||||||
// Not in the math font, fallback to normal inline text layout.
|
}
|
||||||
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
|
};
|
||||||
layout_inline_text(&text, elem.span(), ctx, styles)?.into()
|
ctx.push(fragment);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
ctx.push(fragment);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use serde::{Serialize, Serializer};
|
|||||||
use typst_syntax::{Span, Spanned, is_ident};
|
use typst_syntax::{Span, Spanned, is_ident};
|
||||||
use typst_utils::hash128;
|
use typst_utils::hash128;
|
||||||
|
|
||||||
use crate::diag::{DeprecationSink, SourceResult, StrResult, bail};
|
use crate::diag::{DeprecationSink, SourceResult, StrResult, bail, error};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, cast,
|
Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, cast,
|
||||||
elem, func, scope, ty,
|
elem, func, scope, ty,
|
||||||
@ -231,15 +231,30 @@ impl Symbol {
|
|||||||
// A list of modifiers, cleared & reused in each iteration.
|
// A list of modifiers, cleared & reused in each iteration.
|
||||||
let mut modifiers = Vec::new();
|
let mut modifiers = Vec::new();
|
||||||
|
|
||||||
|
let mut errors = ecow::eco_vec![];
|
||||||
|
|
||||||
// Validate the variants.
|
// Validate the variants.
|
||||||
for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
|
'variants: for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
|
||||||
modifiers.clear();
|
modifiers.clear();
|
||||||
|
|
||||||
|
if v.1.is_empty() {
|
||||||
|
errors.push(if v.0.is_empty() {
|
||||||
|
error!(span, "empty default variant")
|
||||||
|
} else {
|
||||||
|
error!(span, "empty variant: {}", v.0.repr())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if !v.0.is_empty() {
|
if !v.0.is_empty() {
|
||||||
// Collect all modifiers.
|
// Collect all modifiers.
|
||||||
for modifier in v.0.split('.') {
|
for modifier in v.0.split('.') {
|
||||||
if !is_ident(modifier) {
|
if !is_ident(modifier) {
|
||||||
bail!(span, "invalid symbol modifier: {}", modifier.repr());
|
errors.push(error!(
|
||||||
|
span,
|
||||||
|
"invalid symbol modifier: {}",
|
||||||
|
modifier.repr()
|
||||||
|
));
|
||||||
|
continue 'variants;
|
||||||
}
|
}
|
||||||
modifiers.push(modifier);
|
modifiers.push(modifier);
|
||||||
}
|
}
|
||||||
@ -250,29 +265,34 @@ impl Symbol {
|
|||||||
|
|
||||||
// Ensure that there are no duplicate modifiers.
|
// Ensure that there are no duplicate modifiers.
|
||||||
if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
|
if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
|
||||||
bail!(
|
errors.push(error!(
|
||||||
span, "duplicate modifier within variant: {}", ms[0].repr();
|
span, "duplicate modifier within variant: {}", ms[0].repr();
|
||||||
hint: "modifiers are not ordered, so each one may appear only once"
|
hint: "modifiers are not ordered, so each one may appear only once"
|
||||||
)
|
));
|
||||||
|
continue 'variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether we had this set of modifiers before.
|
// Check whether we had this set of modifiers before.
|
||||||
let hash = hash128(&modifiers);
|
let hash = hash128(&modifiers);
|
||||||
if let Some(&i) = seen.get(&hash) {
|
if let Some(&i) = seen.get(&hash) {
|
||||||
if v.0.is_empty() {
|
errors.push(if v.0.is_empty() {
|
||||||
bail!(span, "duplicate default variant");
|
error!(span, "duplicate default variant")
|
||||||
} else if v.0 == variants[i].v.0 {
|
} else if v.0 == variants[i].v.0 {
|
||||||
bail!(span, "duplicate variant: {}", v.0.repr());
|
error!(span, "duplicate variant: {}", v.0.repr())
|
||||||
} else {
|
} else {
|
||||||
bail!(
|
error!(
|
||||||
span, "duplicate variant: {}", v.0.repr();
|
span, "duplicate variant: {}", v.0.repr();
|
||||||
hint: "variants with the same modifiers are identical, regardless of their order"
|
hint: "variants with the same modifiers are identical, regardless of their order"
|
||||||
)
|
)
|
||||||
}
|
});
|
||||||
|
continue 'variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
seen.insert(hash, i);
|
seen.insert(hash, i);
|
||||||
}
|
}
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return Err(errors);
|
||||||
|
}
|
||||||
|
|
||||||
let list = variants
|
let list = variants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -386,7 +406,6 @@ pub struct SymbolVariant(EcoString, EcoString);
|
|||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
SymbolVariant,
|
SymbolVariant,
|
||||||
c: char => Self(EcoString::new(), c.into()),
|
|
||||||
s: EcoString => Self(EcoString::new(), s),
|
s: EcoString => Self(EcoString::new(), s),
|
||||||
array: Array => {
|
array: Array => {
|
||||||
let mut iter = array.into_iter();
|
let mut iter = array.into_iter();
|
||||||
|
@ -733,10 +733,12 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
|||||||
name,
|
name,
|
||||||
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST),
|
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST),
|
||||||
math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST),
|
math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST),
|
||||||
math_class: value_char.and_then(|c| {
|
// Matches `typst_layout::math::GlyphFragment::new`
|
||||||
|
math_class: value.chars().next().and_then(|c| {
|
||||||
typst_utils::default_math_class(c).map(math_class_name)
|
typst_utils::default_math_class(c).map(math_class_name)
|
||||||
}),
|
}),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
// Matches casting `Symbol` to `Accent`
|
||||||
accent: value_char
|
accent: value_char
|
||||||
.is_some_and(|c| typst::math::Accent::combine(c).is_some()),
|
.is_some_and(|c| typst::math::Accent::combine(c).is_some()),
|
||||||
alternates: symbol
|
alternates: symbol
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 558 B |
@ -21,6 +21,10 @@
|
|||||||
("lightning", "🖄"),
|
("lightning", "🖄"),
|
||||||
("fly", "🖅"),
|
("fly", "🖅"),
|
||||||
)
|
)
|
||||||
|
#let one = symbol(
|
||||||
|
"1",
|
||||||
|
("emoji", "1️")
|
||||||
|
)
|
||||||
|
|
||||||
#envelope
|
#envelope
|
||||||
#envelope.stamped
|
#envelope.stamped
|
||||||
@ -28,6 +32,8 @@
|
|||||||
#envelope.stamped.pen
|
#envelope.stamped.pen
|
||||||
#envelope.lightning
|
#envelope.lightning
|
||||||
#envelope.fly
|
#envelope.fly
|
||||||
|
#one
|
||||||
|
#one.emoji
|
||||||
|
|
||||||
--- symbol-constructor-empty ---
|
--- symbol-constructor-empty ---
|
||||||
// Error: 2-10 expected at least one variant
|
// Error: 2-10 expected at least one variant
|
||||||
@ -82,6 +88,14 @@
|
|||||||
("variant.duplicate", "y"),
|
("variant.duplicate", "y"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--- symbol-constructor-empty-variant ---
|
||||||
|
// Error: 2:3-2:5 empty default variant
|
||||||
|
// Error: 3:3-3:16 empty variant: "empty"
|
||||||
|
#symbol(
|
||||||
|
"",
|
||||||
|
("empty", "")
|
||||||
|
)
|
||||||
|
|
||||||
--- symbol-unknown-modifier ---
|
--- symbol-unknown-modifier ---
|
||||||
// Error: 13-20 unknown symbol modifier
|
// Error: 13-20 unknown symbol modifier
|
||||||
#emoji.face.garbage
|
#emoji.face.garbage
|
||||||
|
Loading…
x
Reference in New Issue
Block a user