Document text sub- and superscripts

This commit is contained in:
Laurenz 2022-12-20 17:11:42 +01:00
parent 13807ce020
commit 08daade59f
2 changed files with 123 additions and 39 deletions

View File

@ -53,6 +53,7 @@ fn scope() -> Scope {
std.def_func::<math::AccNode>("acc"); std.def_func::<math::AccNode>("acc");
std.def_func::<math::FracNode>("frac"); std.def_func::<math::FracNode>("frac");
std.def_func::<math::BinomNode>("binom"); std.def_func::<math::BinomNode>("binom");
std.def_func::<math::ScriptNode>("script");
std.def_func::<math::SqrtNode>("sqrt"); std.def_func::<math::SqrtNode>("sqrt");
std.def_func::<math::FloorNode>("floor"); std.def_func::<math::FloorNode>("floor");
std.def_func::<math::CeilNode>("ceil"); std.def_func::<math::CeilNode>("ceil");

View File

@ -5,39 +5,50 @@ use super::{variant, SpaceNode, TextNode, TextSize};
use crate::prelude::*; use crate::prelude::*;
/// # Subscript /// # Subscript
/// Sub- or superscript text. /// Set text in subscript.
/// ///
/// The text is rendered smaller and its baseline is raised/lowered. To provide /// The text is rendered smaller and its baseline is lowered.
/// the best typography possible, we first try to transform the text to ///
/// superscript codepoints. If that fails, we fall back to rendering shrunk /// _Note:_ In the future, this might be unified with the [script](@script)
/// normal letters in a raised way. /// function that handles subscripts in math.
///
/// ## Example
/// ```
/// Revenue#sub[yearly]
/// ```
/// ///
/// ## Parameters /// ## Parameters
/// - body: Content (positional, required) /// - body: Content (positional, required)
/// The text to display in sub- or superscript. /// The text to display in subscript.
/// ///
/// ## Category /// ## Category
/// text /// text
#[func] #[func]
#[capable(Show)] #[capable(Show)]
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ShiftNode<const S: ShiftKind>(pub Content); pub struct SubNode(pub Content);
/// Shift the text into superscript.
pub type SuperNode = ShiftNode<SUPERSCRIPT>;
/// Shift the text into subscript.
pub type SubNode = ShiftNode<SUBSCRIPT>;
#[node] #[node]
impl<const S: ShiftKind> ShiftNode<S> { impl SubNode {
/// Whether to prefer the dedicated sub- and superscript characters of the /// Whether to prefer the dedicated subscript characters of the font.
/// font. ///
/// If this is enabled, Typst first tries to transform the text to subscript
/// codepoints. If that fails, it falls back to rendering lowered and shrunk
/// normal letters.
///
/// # Example
/// ```
/// N#sub(typographic: true)[1]
/// N#sub(typographic: false)[1]
/// ```
pub const TYPOGRAPHIC: bool = true; pub const TYPOGRAPHIC: bool = true;
/// The baseline shift for synthetic sub- and superscripts. /// The baseline shift for synthetic subcripts. Does not apply if
pub const BASELINE: Length = /// `typographic` is true and the font has subscript codepoints for the
Em::new(if S == SUPERSCRIPT { -0.5 } else { 0.2 }).into(); /// given `body`.
/// The font size for synthetic sub- and superscripts. pub const BASELINE: Length = Em::new(0.2).into();
/// The font size for synthetic subscripts. Does not apply if
/// `typographic` is true and the font has subscript codepoints for the
/// given `body`.
pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
@ -52,7 +63,7 @@ impl<const S: ShiftKind> ShiftNode<S> {
} }
} }
impl<const S: ShiftKind> Show for ShiftNode<S> { impl Show for SubNode {
fn show( fn show(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -61,7 +72,91 @@ impl<const S: ShiftKind> Show for ShiftNode<S> {
) -> SourceResult<Content> { ) -> SourceResult<Content> {
let mut transformed = None; let mut transformed = None;
if styles.get(Self::TYPOGRAPHIC) { if styles.get(Self::TYPOGRAPHIC) {
if let Some(text) = search_text(&self.0, S) { if let Some(text) = search_text(&self.0, true) {
if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text));
}
}
};
Ok(transformed.unwrap_or_else(|| {
let mut map = StyleMap::new();
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
map.set(TextNode::SIZE, styles.get(Self::SIZE));
self.0.clone().styled_with_map(map)
}))
}
}
/// # Superscript
/// Set text in superscript.
///
/// The text is rendered smaller and its baseline is raised.
///
/// _Note:_ In the future, this might be unified with the [script](@script)
/// function that handles superscripts in math.
///
/// ## Example
/// ```
/// 1#super[st] try!
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The text to display in superscript.
///
/// ## Category
/// text
#[func]
#[capable(Show)]
#[derive(Debug, Hash)]
pub struct SuperNode(pub Content);
#[node]
impl SuperNode {
/// Whether to prefer the dedicated superscript characters of the font.
///
/// If this is enabled, Typst first tries to transform the text to
/// superscript codepoints. If that fails, it falls back to rendering
/// raised and shrunk normal letters.
///
/// # Example
/// ```
/// N#super(typographic: true)[1]
/// N#super(typographic: false)[1]
/// ```
pub const TYPOGRAPHIC: bool = true;
/// The baseline shift for synthetic superscripts. Does not apply if
/// `typographic` is true and the font has superscript codepoints for the
/// given `body`.
pub const BASELINE: Length = Em::new(-0.5).into();
/// The font size for synthetic superscripts. Does not apply if
/// `typographic` is true and the font has superscript codepoints for the
/// given `body`.
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"body" => Some(Value::Content(self.0.clone())),
_ => None,
}
}
}
impl Show for SuperNode {
fn show(
&self,
vt: &mut Vt,
_: &Content,
styles: StyleChain,
) -> SourceResult<Content> {
let mut transformed = None;
if styles.get(Self::TYPOGRAPHIC) {
if let Some(text) = search_text(&self.0, false) {
if is_shapable(vt, &text, styles) { if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text)); transformed = Some(TextNode::packed(text));
} }
@ -79,15 +174,15 @@ impl<const S: ShiftKind> Show for ShiftNode<S> {
/// Find and transform the text contained in `content` to the given script kind /// Find and transform the text contained in `content` to the given script kind
/// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes. /// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes.
fn search_text(content: &Content, mode: ShiftKind) -> Option<EcoString> { fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
if content.is::<SpaceNode>() { if content.is::<SpaceNode>() {
Some(' '.into()) Some(' '.into())
} else if let Some(text) = content.to::<TextNode>() { } else if let Some(text) = content.to::<TextNode>() {
convert_script(&text.0, mode) convert_script(&text.0, sub)
} else if let Some(seq) = content.to::<SequenceNode>() { } else if let Some(seq) = content.to::<SequenceNode>() {
let mut full = EcoString::new(); let mut full = EcoString::new();
for item in seq.0.iter() { for item in seq.0.iter() {
match search_text(item, mode) { match search_text(item, sub) {
Some(text) => full.push_str(&text), Some(text) => full.push_str(&text),
None => return None, None => return None,
} }
@ -117,12 +212,9 @@ fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool {
/// Convert a string to sub- or superscript codepoints if all characters /// Convert a string to sub- or superscript codepoints if all characters
/// can be mapped to such a codepoint. /// can be mapped to such a codepoint.
fn convert_script(text: &str, mode: ShiftKind) -> Option<EcoString> { fn convert_script(text: &str, sub: bool) -> Option<EcoString> {
let mut result = EcoString::with_capacity(text.len()); let mut result = EcoString::with_capacity(text.len());
let converter = match mode { let converter = if sub { to_subscript_codepoint } else { to_superscript_codepoint };
SUPERSCRIPT => to_superscript_codepoint,
SUBSCRIPT | _ => to_subscript_codepoint,
};
for c in text.chars() { for c in text.chars() {
match converter(c) { match converter(c) {
@ -180,12 +272,3 @@ fn to_subscript_codepoint(c: char) -> Option<char> {
_ => return None, _ => return None,
}) })
} }
/// A category of script.
pub type ShiftKind = usize;
/// Text that is rendered smaller and raised, also known as superior.
const SUPERSCRIPT: ShiftKind = 0;
/// Text that is rendered smaller and lowered, also known as inferior.
const SUBSCRIPT: ShiftKind = 1;