summaryrefslogtreecommitdiff
path: root/src/library/text
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-01 16:56:35 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-02 09:18:33 +0100
commit37ac5d966ebaf97ac79c507028cd5b742b510b89 (patch)
tree249d43ff0f8d880cb5d00c236993f8ff0c1f10d8 /src/library/text
parentf547c97072881069417acd3b79b08fb7ecf40ba2 (diff)
More dynamic content representation
Diffstat (limited to 'src/library/text')
-rw-r--r--src/library/text/deco.rs6
-rw-r--r--src/library/text/link.rs22
-rw-r--r--src/library/text/mod.rs101
-rw-r--r--src/library/text/par.rs60
-rw-r--r--src/library/text/raw.rs21
-rw-r--r--src/library/text/repeat.rs24
-rw-r--r--src/library/text/shift.rs47
7 files changed, 156 insertions, 125 deletions
diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs
index ead928f6..158647f2 100644
--- a/src/library/text/deco.rs
+++ b/src/library/text/deco.rs
@@ -17,7 +17,7 @@ pub type StrikethroughNode = DecoNode<STRIKETHROUGH>;
/// Typeset overlined text.
pub type OverlineNode = DecoNode<OVERLINE>;
-#[node(showable)]
+#[node(Show)]
impl<const L: DecoLine> DecoNode<L> {
/// How to stroke the line. The text color and thickness are read from the
/// font tables if `auto`.
@@ -35,12 +35,12 @@ impl<const L: DecoLine> DecoNode<L> {
pub const EVADE: bool = true;
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
+ Ok(Self(args.expect("body")?).pack())
}
}
impl<const L: DecoLine> Show for DecoNode<L> {
- fn unguard(&self, sel: Selector) -> ShowNode {
+ fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
diff --git a/src/library/text/link.rs b/src/library/text/link.rs
index 11d55956..1e9adc3e 100644
--- a/src/library/text/link.rs
+++ b/src/library/text/link.rs
@@ -17,7 +17,7 @@ impl LinkNode {
}
}
-#[node(showable)]
+#[node(Show)]
impl LinkNode {
/// The fill color of text in the link. Just the surrounding text color
/// if `auto`.
@@ -26,14 +26,12 @@ impl LinkNode {
pub const UNDERLINE: Smart<bool> = Smart::Auto;
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show({
- let dest = args.expect::<Destination>("destination")?;
- let body = match dest {
- Destination::Url(_) => args.eat()?,
- Destination::Internal(_) => Some(args.expect("body")?),
- };
- Self { dest, body }
- }))
+ let dest = args.expect::<Destination>("destination")?;
+ let body = match dest {
+ Destination::Url(_) => args.eat()?,
+ Destination::Internal(_) => Some(args.expect("body")?),
+ };
+ Ok(Self { dest, body }.pack())
}
}
@@ -50,7 +48,7 @@ castable! {
}
impl Show for LinkNode {
- fn unguard(&self, sel: Selector) -> ShowNode {
+ fn unguard_parts(&self, sel: Selector) -> Content {
Self {
dest: self.dest.clone(),
body: self.body.as_ref().map(|body| body.unguard(sel)),
@@ -83,9 +81,9 @@ impl Show for LinkNode {
text = text.trim_start_matches(prefix);
}
let shorter = text.len() < url.len();
- Content::Text(if shorter { text.into() } else { url.clone() })
+ TextNode(if shorter { text.into() } else { url.clone() }).pack()
}
- Destination::Internal(_) => Content::Empty,
+ Destination::Internal(_) => Content::empty(),
})
.styled(TextNode::LINK, Some(self.dest.clone())))
}
diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs
index d7d2ee38..18e747d0 100644
--- a/src/library/text/mod.rs
+++ b/src/library/text/mod.rs
@@ -5,7 +5,6 @@ mod link;
mod par;
mod quotes;
mod raw;
-mod repeat;
mod shaping;
mod shift;
@@ -14,7 +13,6 @@ pub use link::*;
pub use par::*;
pub use quotes::*;
pub use raw::*;
-pub use repeat::*;
pub use shaping::*;
pub use shift::*;
@@ -27,8 +25,8 @@ use crate::library::prelude::*;
use crate::util::EcoString;
/// A single run of text with the same style.
-#[derive(Hash)]
-pub struct TextNode;
+#[derive(Debug, Clone, Hash)]
+pub struct TextNode(pub EcoString);
#[node]
impl TextNode {
@@ -142,7 +140,7 @@ impl TextNode {
for item in args.items.iter().filter(|item| item.name.is_none()) {
if EcoString::is(&item.value) {
count += 1;
- } else if Content::is(&item.value) {
+ } else if <Content as Cast<Spanned<Value>>>::is(&item.value) {
content = true;
}
}
@@ -433,6 +431,45 @@ impl Fold for Vec<(Tag, u32)> {
}
}
+/// 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)
@@ -478,40 +515,19 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
}
-/// 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
- }
-}
-
/// Strong text, rendered in boldface by default.
#[derive(Debug, Hash)]
pub struct StrongNode(pub Content);
-#[node(showable)]
+#[node(Show)]
impl StrongNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
+ Ok(Self(args.expect("body")?).pack())
}
}
impl Show for StrongNode {
- fn unguard(&self, sel: Selector) -> ShowNode {
+ fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
@@ -531,15 +547,15 @@ impl Show for StrongNode {
#[derive(Debug, Hash)]
pub struct EmphNode(pub Content);
-#[node(showable)]
+#[node(Show)]
impl EmphNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
+ Ok(Self(args.expect("body")?).pack())
}
}
impl Show for EmphNode {
- fn unguard(&self, sel: Selector) -> ShowNode {
+ fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
@@ -554,3 +570,24 @@ impl Show for EmphNode {
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
+ }
+}
diff --git a/src/library/text/par.rs b/src/library/text/par.rs
index 477bf97f..7c862366 100644
--- a/src/library/text/par.rs
+++ b/src/library/text/par.rs
@@ -1,10 +1,10 @@
use std::cmp::Ordering;
-use unicode_bidi::{BidiInfo, Level};
+use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator;
-use super::{shape, Lang, Quoter, Quotes, RepeatNode, ShapedText, TextNode};
+use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
use crate::library::layout::Spacing;
use crate::library::prelude::*;
use crate::util::EcoString;
@@ -23,10 +23,10 @@ pub enum ParChild {
/// Horizontal spacing between other children.
Spacing(Spacing),
/// An arbitrary inline-level node.
- Node(LayoutNode),
+ Node(Content),
}
-#[node]
+#[node(Layout)]
impl ParNode {
/// The spacing between lines.
#[property(resolve)]
@@ -54,9 +54,9 @@ impl ParNode {
// node. Instead, it just ensures that the passed content lives is in a
// separate paragraph and styles it.
Ok(Content::sequence(vec![
- Content::Parbreak,
+ ParbreakNode.pack(),
args.expect("body")?,
- Content::Parbreak,
+ ParbreakNode.pack(),
]))
}
}
@@ -82,6 +82,10 @@ impl Layout for ParNode {
// Stack the lines into one frame per region.
stack(&p, world, &lines, regions)
}
+
+ fn level(&self) -> Level {
+ Level::Block
+ }
}
impl Debug for ParNode {
@@ -166,23 +170,39 @@ impl Resolve for Smart<Linebreaks> {
}
/// A paragraph break.
+#[derive(Debug, Clone, Hash)]
pub struct ParbreakNode;
#[node]
impl ParbreakNode {
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
- Ok(Content::Parbreak)
+ Ok(Self.pack())
}
}
-/// A line break.
-pub struct LinebreakNode;
+/// A node that should be repeated to fill up a line.
+#[derive(Debug, Hash)]
+pub struct RepeatNode(pub Content);
-#[node]
-impl LinebreakNode {
+#[node(Layout)]
+impl RepeatNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let justify = args.named("justify")?.unwrap_or(false);
- Ok(Content::Linebreak { justify })
+ Ok(Self(args.expect("body")?).pack())
+ }
+}
+
+impl Layout for RepeatNode {
+ fn layout(
+ &self,
+ world: Tracked<dyn World>,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>> {
+ self.0.layout_inline(world, regions, styles)
+ }
+
+ fn level(&self) -> Level {
+ Level::Inline
}
}
@@ -272,7 +292,7 @@ enum Segment<'a> {
/// Horizontal spacing between other segments.
Spacing(Spacing),
/// An arbitrary inline-level layout node.
- Node(&'a LayoutNode),
+ Node(&'a Content),
}
impl Segment<'_> {
@@ -504,8 +524,8 @@ fn prepare<'a>(
styles: StyleChain<'a>,
) -> SourceResult<Preparation<'a>> {
let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) {
- Dir::LTR => Some(Level::ltr()),
- Dir::RTL => Some(Level::rtl()),
+ Dir::LTR => Some(BidiLevel::ltr()),
+ Dir::RTL => Some(BidiLevel::rtl()),
_ => None,
});
@@ -529,12 +549,12 @@ fn prepare<'a>(
}
},
Segment::Node(node) => {
- if let Some(repeat) = node.downcast() {
+ if let Some(repeat) = node.downcast::<RepeatNode>() {
items.push(Item::Repeat(repeat, styles));
} else {
let size = Size::new(regions.first.x, regions.base.y);
let pod = Regions::one(size, regions.base, Axes::splat(false));
- let mut frame = node.layout(world, &pod, styles)?.remove(0);
+ let mut frame = node.layout_inline(world, &pod, styles)?.remove(0);
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
frame.apply_role(Role::GenericInline);
items.push(Item::Frame(frame));
@@ -566,13 +586,13 @@ fn shape_range<'a>(
range: Range,
styles: StyleChain<'a>,
) {
- let mut process = |text, level: Level| {
+ let mut process = |text, level: BidiLevel| {
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
let shaped = shape(world, text, styles, dir);
items.push(Item::Text(shaped));
};
- let mut prev_level = Level::ltr();
+ let mut prev_level = BidiLevel::ltr();
let mut prev_script = Script::Unknown;
let mut cursor = range.start;
diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs
index 85e8133c..0c769636 100644
--- a/src/library/text/raw.rs
+++ b/src/library/text/raw.rs
@@ -5,8 +5,8 @@ use syntect::highlighting::{
};
use syntect::parsing::SyntaxSet;
-use super::{FontFamily, Hyphenate, TextNode};
-use crate::library::layout::BlockSpacing;
+use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
+use crate::library::layout::{BlockNode, BlockSpacing};
use crate::library::prelude::*;
/// Monospaced text with optional syntax highlighting.
@@ -18,7 +18,7 @@ pub struct RawNode {
pub block: bool,
}
-#[node(showable)]
+#[node(Show)]
impl RawNode {
/// The language to syntax-highlight in.
#[property(referenced)]
@@ -34,15 +34,16 @@ impl RawNode {
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show(Self {
+ Ok(Self {
text: args.expect("text")?,
block: args.named("block")?.unwrap_or(false),
- }))
+ }
+ .pack())
}
}
impl Show for RawNode {
- fn unguard(&self, _: Selector) -> ShowNode {
+ fn unguard_parts(&self, _: Selector) -> Content {
Self { text: self.text.clone(), ..*self }.pack()
}
@@ -86,7 +87,7 @@ impl Show for RawNode {
let mut highlighter = HighlightLines::new(syntax, &THEME);
for (i, line) in self.text.lines().enumerate() {
if i != 0 {
- seq.push(Content::Linebreak { justify: false });
+ seq.push(LinebreakNode { justify: false }.pack());
}
for (style, piece) in
@@ -98,11 +99,11 @@ impl Show for RawNode {
Content::sequence(seq)
} else {
- Content::Text(self.text.clone())
+ TextNode(self.text.clone()).pack()
};
if self.block {
- realized = Content::block(realized);
+ realized = BlockNode(realized).pack();
}
let mut map = StyleMap::new();
@@ -132,7 +133,7 @@ impl Show for RawNode {
/// Style a piece of text with a syntect style.
fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
- let mut body = Content::Text(piece.into());
+ let mut body = TextNode(piece.into()).pack();
let paint = style.foreground.into();
if paint != foreground {
diff --git a/src/library/text/repeat.rs b/src/library/text/repeat.rs
deleted file mode 100644
index e3bae3fc..00000000
--- a/src/library/text/repeat.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use crate::library::prelude::*;
-
-/// A node that should be repeated to fill up a line.
-#[derive(Debug, Hash)]
-pub struct RepeatNode(pub LayoutNode);
-
-#[node]
-impl RepeatNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::inline(Self(args.expect("body")?)))
- }
-}
-
-impl Layout for RepeatNode {
- fn layout(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // The actual repeating happens directly in the paragraph.
- self.0.layout(world, regions, styles)
- }
-}
diff --git a/src/library/text/shift.rs b/src/library/text/shift.rs
index e2a636a6..c3cf8b03 100644
--- a/src/library/text/shift.rs
+++ b/src/library/text/shift.rs
@@ -1,5 +1,6 @@
-use super::{variant, TextNode, TextSize};
+use super::{variant, SpaceNode, TextNode, TextSize};
use crate::library::prelude::*;
+use crate::model::SequenceNode;
use crate::util::EcoString;
/// Sub or superscript text.
@@ -17,7 +18,7 @@ pub type SuperNode = ShiftNode<SUPERSCRIPT>;
/// Shift the text into subscript.
pub type SubNode = ShiftNode<SUBSCRIPT>;
-#[node]
+#[node(Show)]
impl<const S: ScriptKind> ShiftNode<S> {
/// Whether to prefer the dedicated sub- and superscript characters of the
/// font.
@@ -29,12 +30,12 @@ impl<const S: ScriptKind> ShiftNode<S> {
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Content::show(Self(args.expect("body")?)))
+ Ok(Self(args.expect("body")?).pack())
}
}
impl<const S: ScriptKind> Show for ShiftNode<S> {
- fn unguard(&self, _: Selector) -> ShowNode {
+ fn unguard_parts(&self, _: Selector) -> Content {
Self(self.0.clone()).pack()
}
@@ -54,7 +55,7 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
if styles.get(Self::TYPOGRAPHIC) {
if let Some(text) = search_text(&self.0, S) {
if is_shapable(world, &text, styles) {
- transformed = Some(Content::Text(text));
+ transformed = Some(TextNode(text).pack());
}
}
};
@@ -71,28 +72,26 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
/// 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.
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
+ if content.is_empty() {
+ Some(EcoString::new())
+ } else if content.is::<SpaceNode>() {
+ Some(' '.into())
+ } else if let Some(text) = content.downcast::<TextNode>() {
+ if let Some(sup) = convert_script(&text.0, mode) {
+ return Some(sup);
}
- Content::Space => Some(' '.into()),
- Content::Empty => Some(EcoString::new()),
- 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,
- }
+ None
+ } else if let Some(seq) = content.downcast::<SequenceNode>() {
+ let mut full = EcoString::new();
+ for item in seq.0.iter() {
+ match search_text(item, mode) {
+ Some(text) => full.push_str(&text),
+ None => return None,
}
- Some(full)
}
- _ => None,
+ Some(full)
+ } else {
+ None
}
}