summaryrefslogtreecommitdiff
path: root/src/library/text/mod.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 11:44:53 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 13:35:39 +0100
commit37a7afddfaffd44cb9bc013c9506599267e08983 (patch)
tree20e7d62d3c5418baff01a21d0406b91bf3096214 /src/library/text/mod.rs
parent56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff)
Split crates
Diffstat (limited to 'src/library/text/mod.rs')
-rw-r--r--src/library/text/mod.rs593
1 files changed, 0 insertions, 593 deletions
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
deleted file mode 100644
index 18e747d0..00000000
--- a/src/library/text/mod.rs
+++ /dev/null
@@ -1,593 +0,0 @@
-//! Text handling and paragraph layout.
-
-mod deco;
-mod link;
-mod par;
-mod quotes;
-mod raw;
-mod shaping;
-mod shift;
-
-pub use deco::*;
-pub use link::*;
-pub use par::*;
-pub use quotes::*;
-pub use raw::*;
-pub use shaping::*;
-pub use shift::*;
-
-use std::borrow::Cow;
-
-use rustybuzz::Tag;
-
-use crate::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
-use crate::library::prelude::*;
-use crate::util::EcoString;
-
-/// A single run of text with the same style.
-#[derive(Debug, Clone, Hash)]
-pub struct TextNode(pub EcoString);
-
-#[node]
-impl TextNode {
- /// A prioritized sequence of font families.
- #[property(skip, referenced)]
- pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
- /// Whether to allow font fallback when the primary font list contains no
- /// match.
- pub const FALLBACK: bool = true;
-
- /// How the font is styled.
- pub const STYLE: FontStyle = FontStyle::Normal;
- /// The boldness / thickness of the font's glyphs.
- pub const WEIGHT: FontWeight = FontWeight::REGULAR;
- /// The width of the glyphs.
- pub const STRETCH: FontStretch = FontStretch::NORMAL;
-
- /// The size of the glyphs.
- #[property(shorthand, fold)]
- pub const SIZE: TextSize = Abs::pt(11.0);
- /// The glyph fill color.
- #[property(shorthand)]
- pub const FILL: Paint = Color::BLACK.into();
- /// The amount of space that should be added between characters.
- #[property(resolve)]
- pub const TRACKING: Length = Length::zero();
- /// The width of spaces relative to the font's space width.
- #[property(resolve)]
- pub const SPACING: Rel<Length> = Rel::one();
- /// The offset of the baseline.
- #[property(resolve)]
- pub const BASELINE: Length = Length::zero();
- /// Whether certain glyphs can hang over into the margin.
- pub const OVERHANG: bool = true;
- /// The top end of the text bounding box.
- pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight);
- /// The bottom end of the text bounding box.
- pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
-
- /// An ISO 639-1/2/3 language code.
- pub const LANG: Lang = Lang::ENGLISH;
- /// An ISO 3166-1 alpha-2 region code.
- pub const REGION: Option<Region> = None;
- /// The direction for text and inline objects. When `auto`, the direction is
- /// automatically inferred from the language.
- #[property(resolve)]
- pub const DIR: Smart<HorizontalDir> = Smart::Auto;
- /// Whether to hyphenate text to improve line breaking. When `auto`, words
- /// will will be hyphenated if and only if justification is enabled.
- #[property(resolve)]
- pub const HYPHENATE: Smart<Hyphenate> = Smart::Auto;
- /// Whether to apply smart quotes.
- pub const SMART_QUOTES: bool = true;
-
- /// Whether to apply kerning ("kern").
- pub const KERNING: bool = true;
- /// Whether to apply stylistic alternates. ("salt")
- pub const ALTERNATES: bool = false;
- /// Which stylistic set to apply. ("ss01" - "ss20")
- pub const STYLISTIC_SET: Option<StylisticSet> = None;
- /// Whether standard ligatures are active. ("liga", "clig")
- pub const LIGATURES: bool = true;
- /// Whether ligatures that should be used sparingly are active. ("dlig")
- pub const DISCRETIONARY_LIGATURES: bool = false;
- /// Whether historical ligatures are active. ("hlig")
- pub const HISTORICAL_LIGATURES: bool = false;
- /// Which kind of numbers / figures to select.
- pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
- /// The width of numbers / figures.
- pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
- /// Whether to have a slash through the zero glyph. ("zero")
- pub const SLASHED_ZERO: bool = false;
- /// Whether to convert fractions. ("frac")
- pub const FRACTIONS: bool = false;
- /// Raw OpenType features to apply.
- #[property(fold)]
- pub const FEATURES: Vec<(Tag, u32)> = vec![];
-
- /// Whether the font weight should be increased by 300.
- #[property(skip, fold)]
- pub const BOLD: Toggle = false;
- /// Whether the font style should be inverted.
- #[property(skip, fold)]
- pub const ITALIC: Toggle = false;
- /// A case transformation that should be applied to the text.
- #[property(skip)]
- pub const CASE: Option<Case> = None;
- /// Whether small capital glyphs should be used. ("smcp")
- #[property(skip)]
- pub const SMALLCAPS: bool = false;
- /// A destination the text should be linked to.
- #[property(skip, referenced)]
- pub const LINK: Option<Destination> = None;
- /// Decorative lines.
- #[property(skip, fold)]
- pub const DECO: Decoration = vec![];
-
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- // The text constructor is special: It doesn't create a text node.
- // Instead, it leaves the passed argument structurally unchanged, but
- // styles all text in it.
- args.expect("body")
- }
-
- fn set(...) {
- if let Some(family) = args.named("family")? {
- styles.set(Self::FAMILY, family);
- } else {
- let mut count = 0;
- let mut content = false;
- for item in args.items.iter().filter(|item| item.name.is_none()) {
- if EcoString::is(&item.value) {
- count += 1;
- } else if <Content as Cast<Spanned<Value>>>::is(&item.value) {
- content = true;
- }
- }
-
- // Skip the final string if it's needed as the body.
- if constructor && !content && count > 0 {
- count -= 1;
- }
-
- if count > 0 {
- let mut list = Vec::with_capacity(count);
- for _ in 0 .. count {
- list.push(args.find()?.unwrap());
- }
-
- styles.set(Self::FAMILY, list);
- }
- }
- }
-}
-
-/// A lowercased font family like "arial".
-#[derive(Clone, Eq, PartialEq, Hash)]
-pub struct FontFamily(EcoString);
-
-impl FontFamily {
- /// Create a named font family variant.
- pub fn new(string: &str) -> Self {
- Self(string.to_lowercase().into())
- }
-
- /// The lowercased family name.
- pub fn as_str(&self) -> &str {
- &self.0
- }
-}
-
-impl Debug for FontFamily {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
-castable! {
- FontFamily,
- Expected: "string",
- Value::Str(string) => Self::new(&string),
-}
-
-castable! {
- Vec<FontFamily>,
- Expected: "string or array of strings",
- Value::Str(string) => vec![FontFamily::new(&string)],
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .map(|string: EcoString| FontFamily::new(&string))
- .collect(),
-}
-
-castable! {
- FontStyle,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "normal" => Self::Normal,
- "italic" => Self::Italic,
- "oblique" => Self::Oblique,
- _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
- },
-}
-
-castable! {
- FontWeight,
- Expected: "integer or string",
- Value::Int(v) => Value::Int(v)
- .cast::<usize>()?
- .try_into()
- .map_or(Self::BLACK, Self::from_number),
- Value::Str(string) => match string.as_str() {
- "thin" => Self::THIN,
- "extralight" => Self::EXTRALIGHT,
- "light" => Self::LIGHT,
- "regular" => Self::REGULAR,
- "medium" => Self::MEDIUM,
- "semibold" => Self::SEMIBOLD,
- "bold" => Self::BOLD,
- "extrabold" => Self::EXTRABOLD,
- "black" => Self::BLACK,
- _ => Err("unknown font weight")?,
- },
-}
-
-castable! {
- FontStretch,
- Expected: "ratio",
- Value::Ratio(v) => Self::from_ratio(v.get() as f32),
-}
-
-/// The size of text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct TextSize(pub Length);
-
-impl Fold for TextSize {
- type Output = Abs;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- self.0.em.at(outer) + self.0.abs
- }
-}
-
-castable!(TextSize: Length);
-
-/// Specifies the bottom or top edge of text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum TextEdge {
- /// An edge specified using one of the well-known font metrics.
- Metric(VerticalFontMetric),
- /// An edge specified as a length.
- Length(Length),
-}
-
-impl TextEdge {
- /// Resolve the value of the text edge given a font's metrics.
- pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Abs {
- match self {
- Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
- Self::Length(length) => length.resolve(styles),
- }
- }
-}
-
-castable! {
- TextEdge,
- Expected: "string or length",
- Value::Length(v) => Self::Length(v),
- Value::Str(string) => Self::Metric(match string.as_str() {
- "ascender" => VerticalFontMetric::Ascender,
- "cap-height" => VerticalFontMetric::CapHeight,
- "x-height" => VerticalFontMetric::XHeight,
- "baseline" => VerticalFontMetric::Baseline,
- "descender" => VerticalFontMetric::Descender,
- _ => Err("unknown font metric")?,
- }),
-}
-
-castable! {
- Lang,
- Expected: "string",
- Value::Str(string) => Self::from_str(&string)
- .ok_or("expected two or three letter language code (ISO 639-1/2/3)")?,
-}
-
-castable! {
- Region,
- Expected: "string",
- Value::Str(string) => Self::from_str(&string)
- .ok_or("expected two letter region code (ISO 3166-1 alpha-2)")?,
-}
-
-/// The direction of text and inline objects in their line.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct HorizontalDir(pub Dir);
-
-castable! {
- HorizontalDir,
- Expected: "direction",
- @dir: Dir => match dir.axis() {
- Axis::X => Self(*dir),
- Axis::Y => Err("must be horizontal")?,
- },
-}
-
-impl Resolve for Smart<HorizontalDir> {
- type Output = Dir;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- match self {
- Smart::Auto => styles.get(TextNode::LANG).dir(),
- Smart::Custom(dir) => dir.0,
- }
- }
-}
-
-/// Whether to hyphenate text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Hyphenate(pub bool);
-
-castable!(Hyphenate: bool);
-
-impl Resolve for Smart<Hyphenate> {
- type Output = bool;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- match self {
- Smart::Auto => styles.get(ParNode::JUSTIFY),
- Smart::Custom(v) => v.0,
- }
- }
-}
-
-/// A stylistic set in a font.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StylisticSet(u8);
-
-impl StylisticSet {
- /// Create a new set, clamping to 1-20.
- pub fn new(index: u8) -> Self {
- Self(index.clamp(1, 20))
- }
-
- /// Get the value, guaranteed to be 1-20.
- pub fn get(self) -> u8 {
- self.0
- }
-}
-
-castable! {
- StylisticSet,
- Expected: "integer",
- Value::Int(v) => match v {
- 1 ..= 20 => Self::new(v as u8),
- _ => Err("must be between 1 and 20")?,
- },
-}
-
-/// Which kind of numbers / figures to select.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum NumberType {
- /// Numbers that fit well with capital text. ("lnum")
- Lining,
- /// Numbers that fit well into a flow of upper- and lowercase text. ("onum")
- OldStyle,
-}
-
-castable! {
- NumberType,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "lining" => Self::Lining,
- "old-style" => Self::OldStyle,
- _ => Err(r#"expected "lining" or "old-style""#)?,
- },
-}
-
-/// The width of numbers / figures.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum NumberWidth {
- /// Number widths are glyph specific. ("pnum")
- Proportional,
- /// All numbers are of equal width / monospaced. ("tnum")
- Tabular,
-}
-
-castable! {
- NumberWidth,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "proportional" => Self::Proportional,
- "tabular" => Self::Tabular,
- _ => Err(r#"expected "proportional" or "tabular""#)?,
- },
-}
-
-castable! {
- Vec<(Tag, u32)>,
- Expected: "array of strings or dictionary mapping tags to integers",
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
- .collect(),
- Value::Dict(values) => values
- .into_iter()
- .filter_map(|(k, v)| {
- let tag = Tag::from_bytes_lossy(k.as_bytes());
- let num = v.cast::<i64>().ok()?.try_into().ok()?;
- Some((tag, num))
- })
- .collect(),
-}
-
-impl Fold for Vec<(Tag, u32)> {
- type Output = Self;
-
- fn fold(mut self, outer: Self::Output) -> Self::Output {
- self.extend(outer);
- self
- }
-}
-
-/// A text space.
-#[derive(Debug, Clone, Hash)]
-pub struct SpaceNode;
-
-#[node]
-impl SpaceNode {
- fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
- Ok(Self.pack())
- }
-}
-
-/// A line break.
-#[derive(Debug, Clone, Hash)]
-pub struct LinebreakNode {
- pub justify: bool,
-}
-
-#[node]
-impl LinebreakNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let justify = args.named("justify")?.unwrap_or(false);
- Ok(Self { justify }.pack())
- }
-}
-
-/// A smart quote.
-#[derive(Debug, Clone, Hash)]
-pub struct SmartQuoteNode {
- pub double: bool,
-}
-
-#[node]
-impl SmartQuoteNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let double = args.named("double")?.unwrap_or(true);
- Ok(Self { double }.pack())
- }
-}
-
-/// Convert a string or content to lowercase.
-pub fn lower(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- case(Case::Lower, args)
-}
-
-/// Convert a string or content to uppercase.
-pub fn upper(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- case(Case::Upper, args)
-}
-
-/// Change the case of text.
-fn case(case: Case, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect("string or content")?;
- Ok(match v {
- Value::Str(v) => Value::Str(case.apply(&v).into()),
- Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))),
- v => bail!(span, "expected string or content, found {}", v.type_name()),
- })
-}
-
-/// A case transformation on text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Case {
- /// Everything is uppercased.
- Upper,
- /// Everything is lowercased.
- Lower,
-}
-
-impl Case {
- /// Apply the case to a string.
- pub fn apply(self, text: &str) -> String {
- match self {
- Self::Upper => text.to_uppercase(),
- Self::Lower => text.to_lowercase(),
- }
- }
-}
-
-/// Display text in small capitals.
-pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let body: Content = args.expect("content")?;
- Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
-}
-
-/// Strong text, rendered in boldface by default.
-#[derive(Debug, Hash)]
-pub struct StrongNode(pub Content);
-
-#[node(Show)]
-impl StrongNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.expect("body")?).pack())
- }
-}
-
-impl Show for StrongNode {
- fn unguard_parts(&self, sel: Selector) -> Content {
- Self(self.0.unguard(sel)).pack()
- }
-
- fn field(&self, name: &str) -> Option<Value> {
- match name {
- "body" => Some(Value::Content(self.0.clone())),
- _ => None,
- }
- }
-
- fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
- Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
- }
-}
-
-/// Emphasized text, rendered with an italic font by default.
-#[derive(Debug, Hash)]
-pub struct EmphNode(pub Content);
-
-#[node(Show)]
-impl EmphNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.expect("body")?).pack())
- }
-}
-
-impl Show for EmphNode {
- fn unguard_parts(&self, sel: Selector) -> Content {
- Self(self.0.unguard(sel)).pack()
- }
-
- fn field(&self, name: &str) -> Option<Value> {
- match name {
- "body" => Some(Value::Content(self.0.clone())),
- _ => None,
- }
- }
-
- fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
- Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
- }
-}
-
-/// A toggle that turns on and off alternatingly if folded.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Toggle;
-
-impl Fold for Toggle {
- type Output = bool;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- !outer
- }
-}
-
-impl Fold for Decoration {
- type Output = Vec<Self>;
-
- fn fold(self, mut outer: Self::Output) -> Self::Output {
- outer.insert(0, self);
- outer
- }
-}