mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Document text sub- and superscripts
This commit is contained in:
parent
13807ce020
commit
08daade59f
@ -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");
|
||||||
|
@ -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;
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user