Sub- and superscripts
Fixes to PDF export I guess Also improved rendition for non-Latin scripts
@ -521,17 +521,18 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_text(&mut self, x: f32, y: f32, text: &Text) {
|
fn write_text(&mut self, x: f32, y: f32, text: &Text) {
|
||||||
|
*self.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
|
||||||
self.glyphs
|
self.glyphs
|
||||||
.entry(text.face_id)
|
.entry(text.face_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.extend(text.glyphs.iter().map(|g| g.id));
|
.extend(text.glyphs.iter().map(|g| g.id));
|
||||||
|
|
||||||
self.content.begin_text();
|
|
||||||
self.set_font(text.face_id, text.size);
|
|
||||||
self.set_fill(text.fill);
|
|
||||||
|
|
||||||
let face = self.fonts.get(text.face_id);
|
let face = self.fonts.get(text.face_id);
|
||||||
|
|
||||||
|
self.set_fill(text.fill);
|
||||||
|
self.set_font(text.face_id, text.size);
|
||||||
|
self.content.begin_text();
|
||||||
|
|
||||||
// Position the text.
|
// Position the text.
|
||||||
self.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]);
|
self.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]);
|
||||||
|
|
||||||
@ -568,11 +569,6 @@ impl<'a> PageExporter<'a> {
|
|||||||
items.show(Str(&encoded));
|
items.show(Str(&encoded));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.languages
|
|
||||||
.entry(text.lang)
|
|
||||||
.and_modify(|x| *x += text.glyphs.len())
|
|
||||||
.or_insert_with(|| text.glyphs.len());
|
|
||||||
|
|
||||||
items.finish();
|
items.finish();
|
||||||
positioned.finish();
|
positioned.finish();
|
||||||
self.content.end_text();
|
self.content.end_text();
|
||||||
@ -583,6 +579,14 @@ impl<'a> PageExporter<'a> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(fill) = shape.fill {
|
||||||
|
self.set_fill(fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stroke) = shape.stroke {
|
||||||
|
self.set_stroke(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
match shape.geometry {
|
match shape.geometry {
|
||||||
Geometry::Rect(size) => {
|
Geometry::Rect(size) => {
|
||||||
let w = size.x.to_f32();
|
let w = size.x.to_f32();
|
||||||
@ -606,14 +610,6 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fill) = shape.fill {
|
|
||||||
self.set_fill(fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stroke) = shape.stroke {
|
|
||||||
self.set_stroke(stroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
match (shape.fill, shape.stroke) {
|
match (shape.fill, shape.stroke) {
|
||||||
(None, None) => unreachable!(),
|
(None, None) => unreachable!(),
|
||||||
(Some(_), None) => self.content.fill_nonzero(),
|
(Some(_), None) => self.content.fill_nonzero(),
|
||||||
|
@ -28,6 +28,8 @@ pub fn new() -> Scope {
|
|||||||
std.def_node::<text::UnderlineNode>("underline");
|
std.def_node::<text::UnderlineNode>("underline");
|
||||||
std.def_node::<text::StrikethroughNode>("strike");
|
std.def_node::<text::StrikethroughNode>("strike");
|
||||||
std.def_node::<text::OverlineNode>("overline");
|
std.def_node::<text::OverlineNode>("overline");
|
||||||
|
std.def_node::<text::SuperNode>("super");
|
||||||
|
std.def_node::<text::SubNode>("sub");
|
||||||
std.def_node::<text::LinkNode>("link");
|
std.def_node::<text::LinkNode>("link");
|
||||||
std.def_node::<text::RepeatNode>("repeat");
|
std.def_node::<text::RepeatNode>("repeat");
|
||||||
std.def_fn("lower", text::lower);
|
std.def_fn("lower", text::lower);
|
||||||
|
@ -8,6 +8,7 @@ mod quotes;
|
|||||||
mod raw;
|
mod raw;
|
||||||
mod repeat;
|
mod repeat;
|
||||||
mod shaping;
|
mod shaping;
|
||||||
|
mod shift;
|
||||||
|
|
||||||
pub use deco::*;
|
pub use deco::*;
|
||||||
pub use lang::*;
|
pub use lang::*;
|
||||||
@ -17,6 +18,7 @@ pub use quotes::*;
|
|||||||
pub use raw::*;
|
pub use raw::*;
|
||||||
pub use repeat::*;
|
pub use repeat::*;
|
||||||
pub use shaping::*;
|
pub use shaping::*;
|
||||||
|
pub use shift::*;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
@ -60,6 +62,9 @@ impl TextNode {
|
|||||||
/// The width of spaces relative to the default space width.
|
/// The width of spaces relative to the default space width.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const SPACING: Relative<RawLength> = Relative::one();
|
pub const SPACING: Relative<RawLength> = Relative::one();
|
||||||
|
/// The offset of the baseline.
|
||||||
|
#[property(resolve)]
|
||||||
|
pub const BASELINE: RawLength = RawLength::zero();
|
||||||
/// Whether glyphs can hang over into the margin.
|
/// Whether glyphs can hang over into the margin.
|
||||||
pub const OVERHANG: bool = true;
|
pub const OVERHANG: bool = true;
|
||||||
/// The top end of the text bounding box.
|
/// The top end of the text bounding box.
|
||||||
@ -98,8 +103,6 @@ impl TextNode {
|
|||||||
pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
|
pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
|
||||||
/// The width of numbers / figures.
|
/// The width of numbers / figures.
|
||||||
pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
|
pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
|
||||||
/// How to position numbers.
|
|
||||||
pub const NUMBER_POSITION: NumberPosition = NumberPosition::Normal;
|
|
||||||
/// Whether to have a slash through the zero glyph. ("zero")
|
/// Whether to have a slash through the zero glyph. ("zero")
|
||||||
pub const SLASHED_ZERO: bool = false;
|
pub const SLASHED_ZERO: bool = false;
|
||||||
/// Whether to convert fractions. ("frac")
|
/// Whether to convert fractions. ("frac")
|
||||||
|
@ -551,7 +551,16 @@ fn prepare<'a>(
|
|||||||
} else {
|
} else {
|
||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||||
let frame = node.layout(ctx, &pod, styles)?.remove(0);
|
let mut frame = node.layout(ctx, &pod, styles)?.remove(0);
|
||||||
|
let shift = styles.get(TextNode::BASELINE);
|
||||||
|
|
||||||
|
if !shift.is_zero() {
|
||||||
|
Arc::make_mut(&mut frame).baseline = Some(
|
||||||
|
frame.baseline.unwrap_or(frame.size.y)
|
||||||
|
- styles.get(TextNode::BASELINE),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ pub struct ShapedGlyph {
|
|||||||
pub x_advance: Em,
|
pub x_advance: Em,
|
||||||
/// The horizontal offset of the glyph.
|
/// The horizontal offset of the glyph.
|
||||||
pub x_offset: Em,
|
pub x_offset: Em,
|
||||||
|
/// The vertical offset of the glyph.
|
||||||
|
pub y_offset: Em,
|
||||||
/// A value that is the same for all glyphs belong to one cluster.
|
/// A value that is the same for all glyphs belong to one cluster.
|
||||||
pub cluster: usize,
|
pub cluster: usize,
|
||||||
/// Whether splitting the shaping result before this glyph would yield the
|
/// Whether splitting the shaping result before this glyph would yield the
|
||||||
@ -84,8 +86,10 @@ impl<'a> ShapedText<'a> {
|
|||||||
let mut frame = Frame::new(size);
|
let mut frame = Frame::new(size);
|
||||||
frame.baseline = Some(top);
|
frame.baseline = Some(top);
|
||||||
|
|
||||||
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
for ((face_id, y_offset), group) in
|
||||||
let pos = Point::new(offset, top);
|
self.glyphs.as_ref().group_by_key(|g| (g.face_id, g.y_offset))
|
||||||
|
{
|
||||||
|
let pos = Point::new(offset, top + y_offset.at(self.size));
|
||||||
|
|
||||||
let fill = self.styles.get(TextNode::FILL);
|
let fill = self.styles.get(TextNode::FILL);
|
||||||
let glyphs = group
|
let glyphs = group
|
||||||
@ -212,11 +216,14 @@ impl<'a> ShapedText<'a> {
|
|||||||
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
||||||
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
||||||
self.width += x_advance.at(self.size);
|
self.width += x_advance.at(self.size);
|
||||||
|
let baseline_shift = self.styles.get(TextNode::BASELINE);
|
||||||
|
|
||||||
self.glyphs.to_mut().push(ShapedGlyph {
|
self.glyphs.to_mut().push(ShapedGlyph {
|
||||||
face_id,
|
face_id,
|
||||||
glyph_id: glyph_id.0,
|
glyph_id: glyph_id.0,
|
||||||
x_advance,
|
x_advance,
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
|
y_offset: Em::from_length(baseline_shift, self.size),
|
||||||
cluster,
|
cluster,
|
||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c: '-',
|
c: '-',
|
||||||
@ -401,13 +408,17 @@ fn shape_segment<'a>(
|
|||||||
let cluster = info.cluster as usize;
|
let cluster = info.cluster as usize;
|
||||||
|
|
||||||
if info.glyph_id != 0 {
|
if info.glyph_id != 0 {
|
||||||
|
let baseline_shift = ctx.styles.get(TextNode::BASELINE);
|
||||||
|
|
||||||
// Add the glyph to the shaped output.
|
// Add the glyph to the shaped output.
|
||||||
// TODO: Don't ignore y_advance and y_offset.
|
// TODO: Don't ignore y_advance.
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.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),
|
x_advance: face.to_em(pos[i].x_advance),
|
||||||
x_offset: face.to_em(pos[i].x_offset),
|
x_offset: face.to_em(pos[i].x_offset),
|
||||||
|
y_offset: face.to_em(pos[i].y_offset)
|
||||||
|
+ Em::from_length(baseline_shift, ctx.size),
|
||||||
cluster: base + cluster,
|
cluster: base + cluster,
|
||||||
safe_to_break: !info.unsafe_to_break(),
|
safe_to_break: !info.unsafe_to_break(),
|
||||||
c: text[cluster ..].chars().next().unwrap(),
|
c: text[cluster ..].chars().next().unwrap(),
|
||||||
@ -478,6 +489,7 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceI
|
|||||||
glyph_id: 0,
|
glyph_id: 0,
|
||||||
x_advance,
|
x_advance,
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
|
y_offset: Em::from_length(ctx.styles.get(TextNode::BASELINE), ctx.size),
|
||||||
cluster: base + cluster,
|
cluster: base + cluster,
|
||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c,
|
c,
|
||||||
@ -602,12 +614,6 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
|
|||||||
Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1),
|
Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
match styles.get(TextNode::NUMBER_POSITION) {
|
|
||||||
NumberPosition::Normal => {}
|
|
||||||
NumberPosition::Subscript => feat(b"subs", 1),
|
|
||||||
NumberPosition::Superscript => feat(b"sups", 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
if styles.get(TextNode::SLASHED_ZERO) {
|
if styles.get(TextNode::SLASHED_ZERO) {
|
||||||
feat(b"zero", 1);
|
feat(b"zero", 1);
|
||||||
}
|
}
|
||||||
|
181
src/library/text/shift.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use super::{variant, TextNode, TextSize};
|
||||||
|
use crate::font::FontStore;
|
||||||
|
use crate::library::prelude::*;
|
||||||
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
/// Sub or superscript text. The text is rendered smaller and its baseline is raised.
|
||||||
|
///
|
||||||
|
/// To provide the best typography possible, we first try to transform the
|
||||||
|
/// text to superscript codepoints. If that fails, we fall back to rendering
|
||||||
|
/// shrunk normal letters in a raised way.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct ShiftNode<const S: ScriptKind>(pub Content);
|
||||||
|
|
||||||
|
/// Shift the text into superscript.
|
||||||
|
pub type SuperNode = ShiftNode<SUPERSCRIPT>;
|
||||||
|
|
||||||
|
/// Shift the text into subscript.
|
||||||
|
pub type SubNode = ShiftNode<SUBSCRIPT>;
|
||||||
|
|
||||||
|
#[node(showable)]
|
||||||
|
impl<const S: ScriptKind> ShiftNode<S> {
|
||||||
|
/// Whether to prefer the dedicated sub- and superscript characters of the font.
|
||||||
|
pub const TYPOGRAPHIC: bool = true;
|
||||||
|
/// The baseline shift for synthetic sub- and superscripts.
|
||||||
|
pub const BASELINE: RawLength =
|
||||||
|
Em::new(if S == SUPERSCRIPT { -0.5 } else { 0.2 }).into();
|
||||||
|
/// The font size for synthetic sub- and superscripts.
|
||||||
|
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
||||||
|
|
||||||
|
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
||||||
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const S: ScriptKind> Show for ShiftNode<S> {
|
||||||
|
fn unguard(&self, sel: Selector) -> ShowNode {
|
||||||
|
Self(self.0.unguard(sel)).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, _: StyleChain) -> Dict {
|
||||||
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
|
let mut transformed = None;
|
||||||
|
if styles.get(ShiftNode::<S>::TYPOGRAPHIC) {
|
||||||
|
if let Some(text) = search_text(&self.0, S) {
|
||||||
|
if check_str_in_family(&mut ctx.fonts, &text, styles) {
|
||||||
|
transformed = Some(Content::Text(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(transformed.unwrap_or_else(|| {
|
||||||
|
let mut map = StyleMap::new();
|
||||||
|
map.set(TextNode::BASELINE, styles.get(ShiftNode::<S>::BASELINE));
|
||||||
|
map.set(TextNode::SIZE, styles.get(ShiftNode::<S>::SIZE));
|
||||||
|
self.0.clone().styled_with_map(map)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find and transform the text contained in `content` iff it only consists of
|
||||||
|
/// `Text`, `Space`, and `Empty` leaf nodes. The text is transformed to the
|
||||||
|
/// given script kind.
|
||||||
|
fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
||||||
|
match content {
|
||||||
|
Content::Text(_) => {
|
||||||
|
if let Content::Text(t) = content {
|
||||||
|
if let Some(sup) = convert_script(t, mode) {
|
||||||
|
return Some(sup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Content::Space => Some(EcoString::from(" ")),
|
||||||
|
Content::Empty => Some(EcoString::new()),
|
||||||
|
Content::Styled(arc) => search_text(&arc.0, mode),
|
||||||
|
Content::Sequence(seq) => {
|
||||||
|
let mut full = EcoString::new();
|
||||||
|
for item in seq.iter() {
|
||||||
|
match search_text(item, mode) {
|
||||||
|
Some(text) => full.push_str(&text),
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(full)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the first retrievable family contains all code points of the
|
||||||
|
/// given string.
|
||||||
|
fn check_str_in_family(fonts: &mut FontStore, text: &str, styles: StyleChain) -> bool {
|
||||||
|
for family in styles.get(TextNode::FAMILY).iter() {
|
||||||
|
if let Some(face_id) = fonts.select(family.as_str(), variant(styles)) {
|
||||||
|
let face = fonts.get(face_id);
|
||||||
|
let ttf = face.ttf();
|
||||||
|
return text.chars().all(|c| ttf.glyph_index(c).is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a string to sub- or superscript codepoints if all characters
|
||||||
|
/// can be mapped to such a codepoint.
|
||||||
|
fn convert_script(text: &str, mode: ScriptKind) -> Option<EcoString> {
|
||||||
|
let mut result = EcoString::with_capacity(text.len());
|
||||||
|
let converter = match mode {
|
||||||
|
SUPERSCRIPT => to_superscript_codepoint,
|
||||||
|
SUBSCRIPT => to_subscript_codepoint,
|
||||||
|
_ => panic!("unknown script kind"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in text.chars() {
|
||||||
|
match converter(c) {
|
||||||
|
Some(c) => result.push(c),
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a character to its corresponding Unicode superscript.
|
||||||
|
fn to_superscript_codepoint(c: char) -> Option<char> {
|
||||||
|
char::from_u32(match c {
|
||||||
|
'0' => 0x2070,
|
||||||
|
'1' => 0x00B9,
|
||||||
|
'2' => 0x00B2,
|
||||||
|
'3' => 0x00B3,
|
||||||
|
'4' ..= '9' => 0x2070 + (c as u32 + 4 - '4' as u32),
|
||||||
|
'+' => 0x207A,
|
||||||
|
'-' => 0x207B,
|
||||||
|
'=' => 0x207C,
|
||||||
|
'(' => 0x207D,
|
||||||
|
')' => 0x207E,
|
||||||
|
'n' => 0x207F,
|
||||||
|
'i' => 0x2071,
|
||||||
|
' ' => 0x0020,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a character to its corresponding Unicode subscript.
|
||||||
|
fn to_subscript_codepoint(c: char) -> Option<char> {
|
||||||
|
char::from_u32(match c {
|
||||||
|
'0' => 0x2080,
|
||||||
|
'1' ..= '9' => 0x2080 + (c as u32 - '0' as u32),
|
||||||
|
'+' => 0x208A,
|
||||||
|
'-' => 0x208B,
|
||||||
|
'=' => 0x208C,
|
||||||
|
'(' => 0x208D,
|
||||||
|
')' => 0x208E,
|
||||||
|
'a' => 0x2090,
|
||||||
|
'e' => 0x2091,
|
||||||
|
'o' => 0x2092,
|
||||||
|
'x' => 0x2093,
|
||||||
|
'h' => 0x2095,
|
||||||
|
'k' => 0x2096,
|
||||||
|
'l' => 0x2097,
|
||||||
|
'm' => 0x2098,
|
||||||
|
'n' => 0x2099,
|
||||||
|
'p' => 0x209A,
|
||||||
|
's' => 0x209B,
|
||||||
|
't' => 0x209C,
|
||||||
|
' ' => 0x0020,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A category of script.
|
||||||
|
pub type ScriptKind = usize;
|
||||||
|
|
||||||
|
/// Text that is rendered smaller and raised, also known as superior.
|
||||||
|
const SUPERSCRIPT: ScriptKind = 0;
|
||||||
|
|
||||||
|
/// Text that is rendered smaller and lowered, also known as inferior.
|
||||||
|
const SUBSCRIPT: ScriptKind = 1;
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
BIN
tests/ref/text/shifts.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
@ -1,4 +1,9 @@
|
|||||||
// Test text baseline.
|
// Test text baseline.
|
||||||
|
|
||||||
---
|
---
|
||||||
Hi #text(1.5em)[You], #text(0.75em)[how are you?]
|
Hi #text(1.5em)[You], #text(0.75em)[how are you?]
|
||||||
|
|
||||||
|
Our cockatoo was one of the
|
||||||
|
#text(baseline: -0.2em)[#circle(radius: 2pt) first]
|
||||||
|
#text(baseline: 0.2em)[birds #circle(radius: 2pt)]
|
||||||
|
that ever learned to mimic a human voice.
|
||||||
|
@ -34,13 +34,6 @@ fi vs. #text(ligatures: false)[No fi]
|
|||||||
#text(number-width: "tabular")[3456789123] \
|
#text(number-width: "tabular")[3456789123] \
|
||||||
#text(number-width: "tabular")[0123456789]
|
#text(number-width: "tabular")[0123456789]
|
||||||
|
|
||||||
---
|
|
||||||
// Test number position.
|
|
||||||
#set text("IBM Plex Sans")
|
|
||||||
#text(number-position: "normal")[C2H4] \
|
|
||||||
#text(number-position: "subscript")[C2H4] \
|
|
||||||
#text(number-position: "superscript")[C2H4]
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test extra number stuff.
|
// Test extra number stuff.
|
||||||
#set text("IBM Plex Sans")
|
#set text("IBM Plex Sans")
|
||||||
@ -65,10 +58,6 @@ fi vs. #text(features: (liga: 0))[No fi]
|
|||||||
// Error: 24-25 expected string or auto, found integer
|
// Error: 24-25 expected string or auto, found integer
|
||||||
#set text(number-type: 2)
|
#set text(number-type: 2)
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 24-35 expected "lining" or "old-style"
|
|
||||||
#set text(number-type: "different")
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 21-26 expected array of strings or dictionary mapping tags to integers, found boolean
|
// Error: 21-26 expected array of strings or dictionary mapping tags to integers, found boolean
|
||||||
#set text(features: false)
|
#set text(features: false)
|
||||||
|
12
tests/typ/text/shifts.typ
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Test sub- and superscipt shifts.
|
||||||
|
|
||||||
|
---
|
||||||
|
#table(columns: 3,
|
||||||
|
[Typo.], [Fallb.], [Synth],
|
||||||
|
[x#super[1]], [x#super[5n]], [x#super[2 #square(width: 6pt)]],
|
||||||
|
[x#sub[1]], [x#sub[5n]], [x#sub[2 #square(width: 6pt)]],
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
#set super(typographic: false, baseline: -0.25em, size: 0.7em)
|
||||||
|
n#super[1], n#sub[2], ... n#super[N]
|