mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
New interaction model
This commit is contained in:
parent
d9ce194fe7
commit
bf59c08a0a
128
library/src/core/behave.rs
Normal file
128
library/src/core/behave.rs
Normal file
@ -0,0 +1,128 @@
|
||||
//! Node interaction.
|
||||
|
||||
use typst::model::{capability, Content, StyleChain, StyleVec, StyleVecBuilder};
|
||||
|
||||
/// How a node interacts with other nodes.
|
||||
#[capability]
|
||||
pub trait Behave: 'static + Send + Sync {
|
||||
/// The node's interaction behaviour.
|
||||
fn behaviour(&self) -> Behaviour;
|
||||
|
||||
/// Whether this weak node is larger than a previous one and thus picked as
|
||||
/// the maximum when the levels are the same.
|
||||
#[allow(unused_variables)]
|
||||
fn larger(&self, prev: &Content) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// How a node interacts with other nodes in a stream.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Behaviour {
|
||||
/// A weak node which only survives when a supportive node is before and
|
||||
/// after it. Furthermore, per consecutive run of weak nodes, only one
|
||||
/// survives: The one with the lowest weakness level (or the larger one if
|
||||
/// there is a tie).
|
||||
Weak(u8),
|
||||
/// A node that enables adjacent weak nodes to exist. The default.
|
||||
Supportive,
|
||||
/// A node that destroys adjacent weak nodes.
|
||||
Destructive,
|
||||
/// A node that does not interact at all with other node, having the
|
||||
/// same effect as if it didn't exist.
|
||||
Ignorant,
|
||||
}
|
||||
|
||||
/// A wrapper around a [`StyleVecBuilder`] that allows items to interact.
|
||||
pub struct BehavedBuilder<'a> {
|
||||
/// The internal builder.
|
||||
builder: StyleVecBuilder<'a, Content>,
|
||||
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
||||
/// The option is `Some(_)` for weak items and `None` for ignorant items.
|
||||
staged: Vec<(Content, Behaviour, StyleChain<'a>)>,
|
||||
/// What the last non-ignorant item was.
|
||||
last: Behaviour,
|
||||
}
|
||||
|
||||
impl<'a> BehavedBuilder<'a> {
|
||||
/// Create a new style-vec builder.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
builder: StyleVecBuilder::new(),
|
||||
staged: vec![],
|
||||
last: Behaviour::Destructive,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the builder is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.builder.is_empty() && self.staged.is_empty()
|
||||
}
|
||||
|
||||
/// Push an item into the sequence.
|
||||
pub fn push(&mut self, item: Content, styles: StyleChain<'a>) {
|
||||
let interaction = item
|
||||
.to::<dyn Behave>()
|
||||
.map_or(Behaviour::Supportive, Behave::behaviour);
|
||||
|
||||
match interaction {
|
||||
Behaviour::Weak(level) => {
|
||||
if matches!(self.last, Behaviour::Weak(_)) {
|
||||
let item = item.to::<dyn Behave>().unwrap();
|
||||
let i = self.staged.iter().position(|prev| {
|
||||
let Behaviour::Weak(prev_level) = prev.1 else { return false };
|
||||
level < prev_level
|
||||
|| (level == prev_level && item.larger(&prev.0))
|
||||
});
|
||||
let Some(i) = i else { return };
|
||||
self.staged.remove(i);
|
||||
}
|
||||
|
||||
if self.last != Behaviour::Destructive {
|
||||
self.staged.push((item, interaction, styles));
|
||||
self.last = interaction;
|
||||
}
|
||||
}
|
||||
Behaviour::Supportive => {
|
||||
self.flush(true);
|
||||
self.builder.push(item, styles);
|
||||
self.last = interaction;
|
||||
}
|
||||
Behaviour::Destructive => {
|
||||
self.flush(false);
|
||||
self.builder.push(item, styles);
|
||||
self.last = interaction;
|
||||
}
|
||||
Behaviour::Ignorant => {
|
||||
self.staged.push((item, interaction, styles));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the contained items.
|
||||
pub fn items(&self) -> impl DoubleEndedIterator<Item = &Content> {
|
||||
self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
|
||||
}
|
||||
|
||||
/// Return the finish style vec and the common prefix chain.
|
||||
pub fn finish(mut self) -> (StyleVec<Content>, StyleChain<'a>) {
|
||||
self.flush(false);
|
||||
self.builder.finish()
|
||||
}
|
||||
|
||||
/// Push the staged items, filtering out weak items if `supportive` is
|
||||
/// false.
|
||||
fn flush(&mut self, supportive: bool) {
|
||||
for (item, interaction, styles) in self.staged.drain(..) {
|
||||
if supportive || interaction == Behaviour::Ignorant {
|
||||
self.builder.push(item, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for BehavedBuilder<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
//! Extension traits.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Additional methods on content.
|
||||
@ -33,31 +34,31 @@ pub trait ContentExt {
|
||||
|
||||
impl ContentExt for Content {
|
||||
fn strong(self) -> Self {
|
||||
text::StrongNode(self).pack()
|
||||
crate::text::StrongNode(self).pack()
|
||||
}
|
||||
|
||||
fn emph(self) -> Self {
|
||||
text::EmphNode(self).pack()
|
||||
crate::text::EmphNode(self).pack()
|
||||
}
|
||||
|
||||
fn underlined(self) -> Self {
|
||||
text::DecoNode::<{ text::UNDERLINE }>(self).pack()
|
||||
crate::text::DecoNode::<{ crate::text::UNDERLINE }>(self).pack()
|
||||
}
|
||||
|
||||
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
||||
layout::BoxNode { sizing, child: self }.pack()
|
||||
crate::layout::BoxNode { sizing, child: self }.pack()
|
||||
}
|
||||
|
||||
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
|
||||
layout::AlignNode { aligns, child: self }.pack()
|
||||
crate::layout::AlignNode { aligns, child: self }.pack()
|
||||
}
|
||||
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||
layout::PadNode { padding, child: self }.pack()
|
||||
crate::layout::PadNode { padding, child: self }.pack()
|
||||
}
|
||||
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||
layout::MoveNode { delta, child: self }.pack()
|
||||
crate::layout::MoveNode { delta, child: self }.pack()
|
||||
}
|
||||
|
||||
fn filled(self, fill: Paint) -> Self {
|
||||
@ -73,16 +74,16 @@ impl ContentExt for Content {
|
||||
pub trait StyleMapExt {
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
/// from a style chain.
|
||||
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain);
|
||||
fn set_family(&mut self, preferred: crate::text::FontFamily, existing: StyleChain);
|
||||
}
|
||||
|
||||
impl StyleMapExt for StyleMap {
|
||||
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) {
|
||||
fn set_family(&mut self, preferred: crate::text::FontFamily, existing: StyleChain) {
|
||||
self.set(
|
||||
text::TextNode::FAMILY,
|
||||
text::FallbackList(
|
||||
crate::text::TextNode::FAMILY,
|
||||
crate::text::FallbackList(
|
||||
std::iter::once(preferred)
|
||||
.chain(existing.get(text::TextNode::FAMILY).0.iter().cloned())
|
||||
.chain(existing.get(crate::text::TextNode::FAMILY).0.iter().cloned())
|
||||
.collect(),
|
||||
),
|
||||
);
|
7
library/src/core/mod.rs
Normal file
7
library/src/core/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Central definitions for the standard library.
|
||||
|
||||
mod behave;
|
||||
mod ext;
|
||||
|
||||
pub use behave::*;
|
||||
pub use ext::*;
|
@ -104,10 +104,20 @@ pub struct ColbreakNode {
|
||||
pub weak: bool,
|
||||
}
|
||||
|
||||
#[node]
|
||||
#[node(Behave)]
|
||||
impl ColbreakNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let weak = args.named("weak")?.unwrap_or(false);
|
||||
Ok(Self { weak }.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for ColbreakNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
if self.weak {
|
||||
Behaviour::Weak(1)
|
||||
} else {
|
||||
Behaviour::Destructive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,19 +66,25 @@ pub struct BlockNode(pub Content);
|
||||
impl BlockNode {
|
||||
/// The spacing between the previous and this block.
|
||||
#[property(skip)]
|
||||
pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into());
|
||||
pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
|
||||
/// The spacing between this and the following block.
|
||||
#[property(skip)]
|
||||
pub const BELOW: VNode = VNode::weak(Em::new(1.2).into());
|
||||
pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
|
||||
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Self(args.eat()?.unwrap_or_default()).pack())
|
||||
}
|
||||
|
||||
fn set(...) {
|
||||
let spacing = args.named("spacing")?.map(VNode::weak);
|
||||
styles.set_opt(Self::ABOVE, args.named("above")?.map(VNode::strong).or(spacing));
|
||||
styles.set_opt(Self::BELOW, args.named("below")?.map(VNode::strong).or(spacing));
|
||||
let spacing = args.named("spacing")?.map(VNode::block_spacing);
|
||||
styles.set_opt(
|
||||
Self::ABOVE,
|
||||
args.named("above")?.map(VNode::block_around).or(spacing),
|
||||
);
|
||||
styles.set_opt(
|
||||
Self::BELOW,
|
||||
args.named("below")?.map(VNode::block_around).or(spacing),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::{AlignNode, PlaceNode, Spacing, VNode};
|
||||
use crate::layout::BlockNode;
|
||||
use super::{AlignNode, ColbreakNode, PlaceNode, Spacing, VNode};
|
||||
use crate::prelude::*;
|
||||
use crate::text::ParNode;
|
||||
|
||||
@ -10,18 +7,7 @@ use crate::text::ParNode;
|
||||
/// This node is reponsible for layouting both the top-level content flow and
|
||||
/// the contents of boxes.
|
||||
#[derive(Hash)]
|
||||
pub struct FlowNode(pub StyleVec<FlowChild>);
|
||||
|
||||
/// A child of a flow node.
|
||||
#[derive(Hash, PartialEq)]
|
||||
pub enum FlowChild {
|
||||
/// Vertical spacing between other children.
|
||||
Spacing(VNode),
|
||||
/// Arbitrary block-level content.
|
||||
Block(Content),
|
||||
/// A column / region break.
|
||||
Colbreak,
|
||||
}
|
||||
pub struct FlowNode(pub StyleVec<Content>);
|
||||
|
||||
#[node(LayoutBlock)]
|
||||
impl FlowNode {}
|
||||
@ -33,20 +19,18 @@ impl LayoutBlock for FlowNode {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut layouter = FlowLayouter::new(regions, styles);
|
||||
let mut layouter = FlowLayouter::new(regions);
|
||||
|
||||
for (child, map) in self.0.iter() {
|
||||
let styles = map.chain(&styles);
|
||||
match child {
|
||||
FlowChild::Spacing(node) => {
|
||||
layouter.layout_spacing(node, styles);
|
||||
}
|
||||
FlowChild::Block(block) => {
|
||||
layouter.layout_block(world, block, styles)?;
|
||||
}
|
||||
FlowChild::Colbreak => {
|
||||
if let Some(&node) = child.downcast::<VNode>() {
|
||||
layouter.layout_spacing(node.amount, styles);
|
||||
} else if child.has::<dyn LayoutBlock>() {
|
||||
layouter.layout_block(world, child, styles)?;
|
||||
} else if child.is::<ColbreakNode>() {
|
||||
layouter.finish_region();
|
||||
}
|
||||
} else {
|
||||
panic!("unexpected flow child: {child:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,31 +45,10 @@ impl Debug for FlowNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FlowChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
Self::Block(block) => block.fmt(f),
|
||||
Self::Colbreak => f.pad("Colbreak"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FlowChild {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs flow layout.
|
||||
struct FlowLayouter<'a> {
|
||||
struct FlowLayouter {
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// The shared styles.
|
||||
shared: StyleChain<'a>,
|
||||
/// Whether the flow should expand to fill the region.
|
||||
expand: Axes<bool>,
|
||||
/// The full size of `regions.size` that was available before we started
|
||||
@ -95,8 +58,6 @@ struct FlowLayouter<'a> {
|
||||
used: Size,
|
||||
/// The sum of fractions in the current region.
|
||||
fr: Fr,
|
||||
/// The spacing below the last block.
|
||||
below: Option<VNode>,
|
||||
/// Spacing and layouted blocks.
|
||||
items: Vec<FlowItem>,
|
||||
/// Finished frames for previous regions.
|
||||
@ -115,9 +76,9 @@ enum FlowItem {
|
||||
Placed(Frame),
|
||||
}
|
||||
|
||||
impl<'a> FlowLayouter<'a> {
|
||||
impl FlowLayouter {
|
||||
/// Create a new flow layouter.
|
||||
fn new(regions: &Regions, shared: StyleChain<'a>) -> Self {
|
||||
fn new(regions: &Regions) -> Self {
|
||||
let expand = regions.expand;
|
||||
let full = regions.first;
|
||||
|
||||
@ -127,20 +88,18 @@ impl<'a> FlowLayouter<'a> {
|
||||
|
||||
Self {
|
||||
regions,
|
||||
shared,
|
||||
expand,
|
||||
full,
|
||||
used: Size::zero(),
|
||||
fr: Fr::zero(),
|
||||
below: None,
|
||||
items: vec![],
|
||||
finished: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout spacing.
|
||||
fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) {
|
||||
match node.amount {
|
||||
/// Actually layout the spacing.
|
||||
fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
|
||||
match spacing {
|
||||
Spacing::Relative(v) => {
|
||||
// Resolve the spacing and limit it to the remaining space.
|
||||
let resolved = v.resolve(styles).relative_to(self.full.y);
|
||||
@ -154,10 +113,6 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.fr += v;
|
||||
}
|
||||
}
|
||||
|
||||
if node.weak || node.amount.is_fractional() {
|
||||
self.below = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a block.
|
||||
@ -172,19 +127,9 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
// Add spacing between the last block and this one.
|
||||
if let Some(below) = self.below.take() {
|
||||
let above = styles.get(BlockNode::ABOVE);
|
||||
let pick_below = (above.weak && !below.weak) || (below.amount > above.amount);
|
||||
let spacing = if pick_below { below } else { above };
|
||||
self.layout_spacing(&spacing, self.shared);
|
||||
}
|
||||
|
||||
// Placed nodes that are out of flow produce placed items which aren't
|
||||
// aligned later.
|
||||
let mut is_placed = false;
|
||||
if let Some(placed) = block.downcast::<PlaceNode>() {
|
||||
is_placed = true;
|
||||
if placed.out_of_flow() {
|
||||
let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
|
||||
self.items.push(FlowItem::Placed(frame));
|
||||
@ -205,6 +150,7 @@ impl<'a> FlowLayouter<'a> {
|
||||
.unwrap_or(Align::Top),
|
||||
);
|
||||
|
||||
// Layout the block itself.
|
||||
let frames = block.layout_block(world, &self.regions, styles)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
@ -220,10 +166,6 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if !is_placed {
|
||||
self.below = Some(styles.get(BlockNode::BELOW));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -272,7 +214,6 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.full = self.regions.first;
|
||||
self.used = Size::zero();
|
||||
self.fr = Fr::zero();
|
||||
self.below = None;
|
||||
self.finished.push(output);
|
||||
}
|
||||
|
||||
|
@ -32,16 +32,18 @@ use typst::diag::SourceResult;
|
||||
use typst::frame::Frame;
|
||||
use typst::geom::*;
|
||||
use typst::model::{
|
||||
capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec,
|
||||
capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
|
||||
StyleVecBuilder, StyledNode, Target,
|
||||
};
|
||||
use typst::World;
|
||||
|
||||
use crate::core::BehavedBuilder;
|
||||
use crate::prelude::*;
|
||||
use crate::structure::{
|
||||
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
|
||||
};
|
||||
use crate::text::{
|
||||
LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
||||
LinebreakNode, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
||||
};
|
||||
|
||||
/// Root-level layout.
|
||||
@ -468,41 +470,17 @@ impl Default for DocBuilder<'_> {
|
||||
|
||||
/// Accepts flow content.
|
||||
#[derive(Default)]
|
||||
struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool);
|
||||
struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
|
||||
|
||||
impl<'a> FlowBuilder<'a> {
|
||||
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
||||
// Weak flow elements:
|
||||
// Weakness | Element
|
||||
// 0 | weak colbreak
|
||||
// 1 | weak fractional spacing
|
||||
// 2 | weak spacing
|
||||
// 3 | generated weak spacing
|
||||
// 4 | generated weak fractional spacing
|
||||
// 5 | par spacing
|
||||
|
||||
let last_was_parbreak = self.1;
|
||||
self.1 = false;
|
||||
|
||||
if content.is::<ParbreakNode>() {
|
||||
self.1 = true;
|
||||
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
|
||||
if colbreak.weak {
|
||||
self.0.weak(FlowChild::Colbreak, styles, 0);
|
||||
} else {
|
||||
self.0.destructive(FlowChild::Colbreak, styles);
|
||||
}
|
||||
} else if let Some(vertical) = content.downcast::<VNode>() {
|
||||
let child = FlowChild::Spacing(*vertical);
|
||||
let frac = vertical.amount.is_fractional();
|
||||
if vertical.weak {
|
||||
let weakness = 1 + u8::from(frac);
|
||||
self.0.weak(child, styles, weakness);
|
||||
} else if frac {
|
||||
self.0.destructive(child, styles);
|
||||
} else {
|
||||
self.0.ignorant(child, styles);
|
||||
}
|
||||
} else if content.is::<VNode>() || content.is::<ColbreakNode>() {
|
||||
self.0.push(content.clone(), styles);
|
||||
} else if content.has::<dyn LayoutBlock>() {
|
||||
if !last_was_parbreak {
|
||||
let tight = if let Some(node) = content.downcast::<ListNode>() {
|
||||
@ -517,17 +495,16 @@ impl<'a> FlowBuilder<'a> {
|
||||
|
||||
if tight {
|
||||
let leading = styles.get(ParNode::LEADING);
|
||||
let spacing = VNode::weak(leading.into());
|
||||
self.0.weak(FlowChild::Spacing(spacing), styles, 1);
|
||||
let spacing = VNode::list_attach(leading.into());
|
||||
self.0.push(spacing.pack(), styles);
|
||||
}
|
||||
}
|
||||
|
||||
let child = FlowChild::Block(content.clone());
|
||||
if content.is::<PlaceNode>() {
|
||||
self.0.ignorant(child, styles);
|
||||
} else {
|
||||
self.0.supportive(child, styles);
|
||||
}
|
||||
let above = styles.get(BlockNode::ABOVE);
|
||||
let below = styles.get(BlockNode::BELOW);
|
||||
self.0.push(above.pack(), styles);
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(below.pack(), styles);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -549,43 +526,22 @@ impl<'a> FlowBuilder<'a> {
|
||||
|
||||
/// Accepts paragraph content.
|
||||
#[derive(Default)]
|
||||
struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
|
||||
struct ParBuilder<'a>(BehavedBuilder<'a>);
|
||||
|
||||
impl<'a> ParBuilder<'a> {
|
||||
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
||||
// Weak par elements:
|
||||
// Weakness | Element
|
||||
// 0 | weak fractional spacing
|
||||
// 1 | weak spacing
|
||||
// 2 | space
|
||||
|
||||
if content.is::<SpaceNode>() {
|
||||
self.0.weak(ParChild::Text(' '.into()), styles, 2);
|
||||
} else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
|
||||
let c = if linebreak.justify { '\u{2028}' } else { '\n' };
|
||||
self.0.destructive(ParChild::Text(c.into()), styles);
|
||||
} else if let Some(horizontal) = content.downcast::<HNode>() {
|
||||
let child = ParChild::Spacing(horizontal.amount);
|
||||
let frac = horizontal.amount.is_fractional();
|
||||
if horizontal.weak {
|
||||
let weakness = u8::from(!frac);
|
||||
self.0.weak(child, styles, weakness);
|
||||
} else if frac {
|
||||
self.0.destructive(child, styles);
|
||||
} else {
|
||||
self.0.ignorant(child, styles);
|
||||
}
|
||||
} else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
|
||||
self.0.supportive(ParChild::Quote { double: quote.double }, styles);
|
||||
} else if let Some(text) = content.downcast::<TextNode>() {
|
||||
self.0.supportive(ParChild::Text(text.0.clone()), styles);
|
||||
} else if content.has::<dyn LayoutInline>() {
|
||||
self.0.supportive(ParChild::Inline(content.clone()), styles);
|
||||
} else {
|
||||
return false;
|
||||
if content.is::<SpaceNode>()
|
||||
|| content.is::<LinebreakNode>()
|
||||
|| content.is::<HNode>()
|
||||
|| content.is::<SmartQuoteNode>()
|
||||
|| content.is::<TextNode>()
|
||||
|| content.has::<dyn LayoutInline>()
|
||||
{
|
||||
self.0.push(content.clone(), styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
true
|
||||
false
|
||||
}
|
||||
|
||||
fn finish(self, parent: &mut Builder<'a>) {
|
||||
@ -600,10 +556,14 @@ impl<'a> ParBuilder<'a> {
|
||||
if !indent.is_zero()
|
||||
&& children
|
||||
.items()
|
||||
.find_map(|child| match child {
|
||||
ParChild::Spacing(_) => None,
|
||||
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
||||
ParChild::Inline(_) => Some(false),
|
||||
.find_map(|child| {
|
||||
if child.is::<TextNode>() || child.is::<SmartQuoteNode>() {
|
||||
Some(true)
|
||||
} else if child.has::<dyn LayoutInline>() {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
&& parent
|
||||
@ -611,14 +571,10 @@ impl<'a> ParBuilder<'a> {
|
||||
.0
|
||||
.items()
|
||||
.rev()
|
||||
.find_map(|child| match child {
|
||||
FlowChild::Spacing(_) => None,
|
||||
FlowChild::Block(content) => Some(content.is::<ParNode>()),
|
||||
FlowChild::Colbreak => Some(false),
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.find(|child| child.has::<dyn LayoutBlock>())
|
||||
.map_or(false, |child| child.is::<ParNode>())
|
||||
{
|
||||
children.push_front(ParChild::Spacing(indent.into()));
|
||||
children.push_front(HNode::strong(indent.into()).pack());
|
||||
}
|
||||
|
||||
parent.flow.accept(&ParNode(children).pack(), shared);
|
||||
@ -701,115 +657,3 @@ impl Default for ListBuilder<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
||||
struct CollapsingBuilder<'a, T> {
|
||||
/// The internal builder.
|
||||
builder: StyleVecBuilder<'a, T>,
|
||||
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
||||
/// The option is `Some(_)` for weak items and `None` for ignorant items.
|
||||
staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
|
||||
/// What the last non-ignorant item was.
|
||||
last: Last,
|
||||
}
|
||||
|
||||
/// What the last non-ignorant item was.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum Last {
|
||||
Weak,
|
||||
Destructive,
|
||||
Supportive,
|
||||
}
|
||||
|
||||
impl<'a, T> CollapsingBuilder<'a, T> {
|
||||
/// Create a new style-vec builder.
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
builder: StyleVecBuilder::new(),
|
||||
staged: vec![],
|
||||
last: Last::Destructive,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the builder is empty.
|
||||
fn is_empty(&self) -> bool {
|
||||
self.builder.is_empty() && self.staged.is_empty()
|
||||
}
|
||||
|
||||
/// Can only exist when there is at least one supportive item to its left
|
||||
/// and to its right, with no destructive items in between. There may be
|
||||
/// ignorant items in between in both directions.
|
||||
///
|
||||
/// Between weak items, there may be at least one per layer and among the
|
||||
/// candidates the strongest one (smallest `weakness`) wins. When tied,
|
||||
/// the one that compares larger through `PartialOrd` wins.
|
||||
fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
if self.last == Last::Destructive {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.last == Last::Weak {
|
||||
let weak = self.staged.iter().position(|(prev_item, _, prev_weakness)| {
|
||||
prev_weakness.map_or(false, |prev_weakness| {
|
||||
weakness < prev_weakness
|
||||
|| (weakness == prev_weakness && item > *prev_item)
|
||||
})
|
||||
});
|
||||
|
||||
let Some(weak) = weak else { return };
|
||||
self.staged.remove(weak);
|
||||
}
|
||||
|
||||
self.staged.push((item, styles, Some(weakness)));
|
||||
self.last = Last::Weak;
|
||||
}
|
||||
|
||||
/// Forces nearby weak items to collapse.
|
||||
fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
|
||||
self.flush(false);
|
||||
self.builder.push(item, styles);
|
||||
self.last = Last::Destructive;
|
||||
}
|
||||
|
||||
/// Allows nearby weak items to exist.
|
||||
fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
|
||||
self.flush(true);
|
||||
self.builder.push(item, styles);
|
||||
self.last = Last::Supportive;
|
||||
}
|
||||
|
||||
/// Has no influence on other items.
|
||||
fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
|
||||
self.staged.push((item, styles, None));
|
||||
}
|
||||
|
||||
/// Iterate over the contained items.
|
||||
fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
|
||||
self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
|
||||
}
|
||||
|
||||
/// Return the finish style vec and the common prefix chain.
|
||||
fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
|
||||
self.flush(false);
|
||||
self.builder.finish()
|
||||
}
|
||||
|
||||
/// Push the staged items, filtering out weak items if `supportive` is
|
||||
/// false.
|
||||
fn flush(&mut self, supportive: bool) {
|
||||
for (item, styles, meta) in self.staged.drain(..) {
|
||||
if supportive || meta.is_none() {
|
||||
self.builder.push(item, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Default for CollapsingBuilder<'a, T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PlaceNode(pub Content);
|
||||
|
||||
#[node(LayoutBlock)]
|
||||
#[node(LayoutBlock, Behave)]
|
||||
impl PlaceNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
|
||||
@ -54,3 +54,9 @@ impl PlaceNode {
|
||||
.map_or(false, |node| node.aligns.y.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for PlaceNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Ignorant
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ use crate::prelude::*;
|
||||
/// Horizontal spacing.
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub struct HNode {
|
||||
/// The amount of horizontal spacing.
|
||||
pub amount: Spacing,
|
||||
/// Whether the node is weak, see also [`Behaviour`].
|
||||
pub weak: bool,
|
||||
}
|
||||
|
||||
#[node]
|
||||
#[node(Behave)]
|
||||
impl HNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let amount = args.expect("spacing")?;
|
||||
@ -18,31 +20,98 @@ impl HNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical spacing.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
||||
pub struct VNode {
|
||||
pub amount: Spacing,
|
||||
pub weak: bool,
|
||||
}
|
||||
|
||||
impl VNode {
|
||||
/// Create weak vertical spacing.
|
||||
pub fn weak(amount: Spacing) -> Self {
|
||||
Self { amount, weak: true }
|
||||
}
|
||||
|
||||
/// Create strong vertical spacing.
|
||||
impl HNode {
|
||||
/// Normal strong spacing.
|
||||
pub fn strong(amount: Spacing) -> Self {
|
||||
Self { amount, weak: false }
|
||||
}
|
||||
|
||||
/// User-created weak spacing.
|
||||
pub fn weak(amount: Spacing) -> Self {
|
||||
Self { amount, weak: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[node]
|
||||
impl Behave for HNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
if self.amount.is_fractional() {
|
||||
Behaviour::Destructive
|
||||
} else if self.weak {
|
||||
Behaviour::Weak(1)
|
||||
} else {
|
||||
Behaviour::Ignorant
|
||||
}
|
||||
}
|
||||
|
||||
fn larger(&self, prev: &Content) -> bool {
|
||||
let Some(prev) = prev.downcast::<Self>() else { return false };
|
||||
self.amount > prev.amount
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical spacing.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
||||
pub struct VNode {
|
||||
/// The amount of vertical spacing.
|
||||
pub amount: Spacing,
|
||||
/// The node's weakness level, see also [`Behaviour`].
|
||||
pub weakness: u8,
|
||||
}
|
||||
|
||||
#[node(Behave)]
|
||||
impl VNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let amount = args.expect("spacing")?;
|
||||
let weak = args.named("weak")?.unwrap_or(false);
|
||||
Ok(Self { amount, weak }.pack())
|
||||
let node = if args.named("weak")?.unwrap_or(false) {
|
||||
Self::weak(amount)
|
||||
} else {
|
||||
Self::strong(amount)
|
||||
};
|
||||
Ok(node.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl VNode {
|
||||
/// Normal strong spacing.
|
||||
pub fn strong(amount: Spacing) -> Self {
|
||||
Self { amount, weakness: 0 }
|
||||
}
|
||||
|
||||
/// User-created weak spacing.
|
||||
pub fn weak(amount: Spacing) -> Self {
|
||||
Self { amount, weakness: 1 }
|
||||
}
|
||||
|
||||
/// Weak spacing with list attach weakness.
|
||||
pub fn list_attach(amount: Spacing) -> Self {
|
||||
Self { amount, weakness: 2 }
|
||||
}
|
||||
|
||||
/// Weak spacing with BlockNode::ABOVE/BELOW weakness.
|
||||
pub fn block_around(amount: Spacing) -> Self {
|
||||
Self { amount, weakness: 3 }
|
||||
}
|
||||
|
||||
/// Weak spacing with BlockNode::SPACING weakness.
|
||||
pub fn block_spacing(amount: Spacing) -> Self {
|
||||
Self { amount, weakness: 4 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for VNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
if self.amount.is_fractional() {
|
||||
Behaviour::Destructive
|
||||
} else if self.weakness > 0 {
|
||||
Behaviour::Weak(self.weakness)
|
||||
} else {
|
||||
Behaviour::Ignorant
|
||||
}
|
||||
}
|
||||
|
||||
fn larger(&self, prev: &Content) -> bool {
|
||||
let Some(prev) = prev.downcast::<Self>() else { return false };
|
||||
self.amount > prev.amount
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Typst's standard library.
|
||||
|
||||
pub mod base;
|
||||
pub mod core;
|
||||
pub mod graphics;
|
||||
pub mod layout;
|
||||
pub mod math;
|
||||
@ -8,8 +9,6 @@ pub mod prelude;
|
||||
pub mod structure;
|
||||
pub mod text;
|
||||
|
||||
mod ext;
|
||||
|
||||
use typst::geom::{Align, Color, Dir, GenAlign};
|
||||
use typst::model::{LangItems, Node, Scope, StyleMap};
|
||||
|
||||
|
@ -1,20 +1,32 @@
|
||||
//! Helpful imports for creating library functionality.
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use std::fmt::{self, Debug, Formatter};
|
||||
#[doc(no_inline)]
|
||||
pub use std::num::NonZeroUsize;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use comemo::Tracked;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::frame::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::model::{
|
||||
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
|
||||
Content, Dict, Finalize, Fold, Func, Key, Node, RecipeId, Resolve, Scope, Show,
|
||||
Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm,
|
||||
Content, Dict, Finalize, Fold, Func, Node, RecipeId, Resolve, Show, Smart, Str,
|
||||
StyleChain, StyleMap, StyleVec, Value, Vm,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::syntax::{Span, Spanned};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::util::{format_eco, EcoString};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::World;
|
||||
|
||||
pub use super::ext::{ContentExt, StyleMapExt};
|
||||
pub use super::layout::{LayoutBlock, LayoutInline, Regions};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::core::{Behave, Behaviour, ContentExt, StyleMapExt};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::layout::{LayoutBlock, LayoutInline, Regions};
|
||||
|
@ -50,20 +50,21 @@ impl Finalize for HeadingNode {
|
||||
_: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
let size = Em::new(match self.level.get() {
|
||||
let scale = match self.level.get() {
|
||||
1 => 1.4,
|
||||
2 => 1.2,
|
||||
_ => 1.0,
|
||||
});
|
||||
};
|
||||
|
||||
let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 });
|
||||
let below = Em::new(0.66);
|
||||
let size = Em::new(scale);
|
||||
let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 }) / scale;
|
||||
let below = Em::new(0.66) / scale;
|
||||
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::SIZE, TextSize(size.into()));
|
||||
map.set(TextNode::WEIGHT, FontWeight::BOLD);
|
||||
map.set(BlockNode::ABOVE, VNode::strong(above.into()));
|
||||
map.set(BlockNode::BELOW, VNode::strong(below.into()));
|
||||
map.set(BlockNode::ABOVE, VNode::block_around(above.into()));
|
||||
map.set(BlockNode::BELOW, VNode::block_around(below.into()));
|
||||
|
||||
Ok(realized.styled_with_map(map))
|
||||
}
|
||||
|
@ -410,20 +410,26 @@ impl Fold for FontFeatures {
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct SpaceNode;
|
||||
|
||||
#[node]
|
||||
#[node(Behave)]
|
||||
impl SpaceNode {
|
||||
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Self.pack())
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for SpaceNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Weak(2)
|
||||
}
|
||||
}
|
||||
|
||||
/// A line break.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct LinebreakNode {
|
||||
pub justify: bool,
|
||||
}
|
||||
|
||||
#[node]
|
||||
#[node(Behave)]
|
||||
impl LinebreakNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let justify = args.named("justify")?.unwrap_or(false);
|
||||
@ -431,6 +437,12 @@ impl LinebreakNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for LinebreakNode {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Destructive
|
||||
}
|
||||
}
|
||||
|
||||
/// A smart quote.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct SmartQuoteNode {
|
||||
|
@ -1,30 +1,19 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use typst::util::EcoString;
|
||||
use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||
use unicode_script::{Script, UnicodeScript};
|
||||
use xi_unicode::LineBreakIterator;
|
||||
|
||||
use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
|
||||
use crate::layout::Spacing;
|
||||
use typst::model::Key;
|
||||
|
||||
use super::{
|
||||
shape, Lang, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode,
|
||||
TextNode,
|
||||
};
|
||||
use crate::layout::{HNode, Spacing};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||
#[derive(Hash)]
|
||||
pub struct ParNode(pub StyleVec<ParChild>);
|
||||
|
||||
/// A uniformly styled atomic piece of a paragraph.
|
||||
#[derive(Hash, PartialEq)]
|
||||
pub enum ParChild {
|
||||
/// A chunk of text.
|
||||
Text(EcoString),
|
||||
/// A single or double smart quote.
|
||||
Quote { double: bool },
|
||||
/// Horizontal spacing between other children.
|
||||
Spacing(Spacing),
|
||||
/// Arbitrary inline-level content.
|
||||
Inline(Content),
|
||||
}
|
||||
pub struct ParNode(pub StyleVec<Content>);
|
||||
|
||||
#[node(LayoutBlock)]
|
||||
impl ParNode {
|
||||
@ -84,26 +73,6 @@ impl Debug for ParNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ParChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Text(text) => write!(f, "Text({:?})", text),
|
||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
Self::Inline(inline) => inline.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ParChild {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A horizontal alignment.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct HorizontalAlign(pub GenAlign);
|
||||
@ -426,43 +395,52 @@ fn collect<'a>(
|
||||
|
||||
while let Some((child, map)) = iter.next() {
|
||||
let styles = map.chain(styles);
|
||||
let segment = match child {
|
||||
ParChild::Text(text) => {
|
||||
let segment = if child.is::<SpaceNode>() {
|
||||
full.push(' ');
|
||||
Segment::Text(1)
|
||||
} else if let Some(node) = child.downcast::<TextNode>() {
|
||||
let prev = full.len();
|
||||
if let Some(case) = styles.get(TextNode::CASE) {
|
||||
full.push_str(&case.apply(text));
|
||||
full.push_str(&case.apply(&node.0));
|
||||
} else {
|
||||
full.push_str(text);
|
||||
full.push_str(&node.0);
|
||||
}
|
||||
Segment::Text(full.len() - prev)
|
||||
}
|
||||
&ParChild::Quote { double } => {
|
||||
} else if let Some(node) = child.downcast::<LinebreakNode>() {
|
||||
let c = if node.justify { '\u{2028}' } else { '\n' };
|
||||
full.push(c);
|
||||
Segment::Text(c.len_utf8())
|
||||
} else if let Some(node) = child.downcast::<SmartQuoteNode>() {
|
||||
let prev = full.len();
|
||||
if styles.get(TextNode::SMART_QUOTES) {
|
||||
let lang = styles.get(TextNode::LANG);
|
||||
let region = styles.get(TextNode::REGION);
|
||||
let quotes = Quotes::from_lang(lang, region);
|
||||
let peeked = iter.peek().and_then(|(child, _)| match child {
|
||||
ParChild::Text(text) => text.chars().next(),
|
||||
ParChild::Quote { .. } => Some('"'),
|
||||
ParChild::Spacing(_) => Some(SPACING_REPLACE),
|
||||
ParChild::Inline(_) => Some(NODE_REPLACE),
|
||||
let peeked = iter.peek().and_then(|(child, _)| {
|
||||
if let Some(node) = child.downcast::<TextNode>() {
|
||||
node.0.chars().next()
|
||||
} else if child.is::<SmartQuoteNode>() {
|
||||
Some('"')
|
||||
} else if child.is::<SpaceNode>() || child.is::<HNode>() {
|
||||
Some(SPACING_REPLACE)
|
||||
} else {
|
||||
Some(NODE_REPLACE)
|
||||
}
|
||||
});
|
||||
|
||||
full.push_str(quoter.quote("es, double, peeked));
|
||||
full.push_str(quoter.quote("es, node.double, peeked));
|
||||
} else {
|
||||
full.push(if double { '"' } else { '\'' });
|
||||
full.push(if node.double { '"' } else { '\'' });
|
||||
}
|
||||
Segment::Text(full.len() - prev)
|
||||
}
|
||||
&ParChild::Spacing(spacing) => {
|
||||
} else if let Some(&node) = child.downcast::<HNode>() {
|
||||
full.push(SPACING_REPLACE);
|
||||
Segment::Spacing(spacing)
|
||||
}
|
||||
ParChild::Inline(inline) => {
|
||||
Segment::Spacing(node.amount)
|
||||
} else if child.has::<dyn LayoutInline>() {
|
||||
full.push(NODE_REPLACE);
|
||||
Segment::Inline(inline)
|
||||
}
|
||||
Segment::Inline(child)
|
||||
} else {
|
||||
panic!("unexpected par child: {child:?}");
|
||||
};
|
||||
|
||||
if let Some(last) = full.chars().last() {
|
||||
@ -608,7 +586,7 @@ fn is_compatible(a: Script, b: Script) -> bool {
|
||||
/// paragraph.
|
||||
fn shared_get<'a, K: Key<'a>>(
|
||||
styles: StyleChain<'a>,
|
||||
children: &StyleVec<ParChild>,
|
||||
children: &StyleVec<Content>,
|
||||
key: K,
|
||||
) -> Option<K::Output> {
|
||||
children
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Loading…
x
Reference in New Issue
Block a user