use super::*;
use crate::diag::bail;
use crate::foundations::{Packed, Resolve};
use crate::introspection::{SplitLocator, Tag, TagElem};
use crate::layout::{
Abs, AlignElem, BoxElem, Dir, Fr, Frame, HElem, InlineElem, InlineItem, Sizing,
Spacing,
};
use crate::syntax::Span;
use crate::text::{
is_default_ignorable, LinebreakElem, SmartQuoteElem, SmartQuoter, SmartQuotes,
SpaceElem, TextElem,
};
use crate::utils::Numeric;
const SPACING_REPLACE: &str = " "; const OBJ_REPLACE: &str = "\u{FFFC}";
const LTR_EMBEDDING: &str = "\u{202A}";
const RTL_EMBEDDING: &str = "\u{202B}";
const POP_EMBEDDING: &str = "\u{202C}";
const LTR_ISOLATE: &str = "\u{2066}";
const POP_ISOLATE: &str = "\u{2069}";
#[derive(Debug)]
pub enum Item<'a> {
Text(ShapedText<'a>),
Absolute(Abs, bool),
Fractional(Fr, Option<(&'a Packed<BoxElem>, Locator<'a>, StyleChain<'a>)>),
Frame(Frame, StyleChain<'a>),
Tag(&'a Tag),
Skip(&'static str),
}
impl<'a> Item<'a> {
pub fn text(&self) -> Option<&ShapedText<'a>> {
match self {
Self::Text(shaped) => Some(shaped),
_ => None,
}
}
pub fn text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
match self {
Self::Text(shaped) => Some(shaped),
_ => None,
}
}
pub fn textual(&self) -> &str {
match self {
Self::Text(shaped) => shaped.text,
Self::Absolute(_, _) | Self::Fractional(_, _) => SPACING_REPLACE,
Self::Frame(_, _) => OBJ_REPLACE,
Self::Tag(_) => "",
Self::Skip(s) => s,
}
}
pub fn textual_len(&self) -> usize {
self.textual().len()
}
pub fn natural_width(&self) -> Abs {
match self {
Self::Text(shaped) => shaped.width,
Self::Absolute(v, _) => *v,
Self::Frame(frame, _) => frame.width(),
Self::Fractional(_, _) | Self::Tag(_) => Abs::zero(),
Self::Skip(_) => Abs::zero(),
}
}
}
#[derive(Debug)]
pub enum Segment<'a> {
Text(usize, StyleChain<'a>),
Item(Item<'a>),
}
impl Segment<'_> {
pub fn textual_len(&self) -> usize {
match self {
Self::Text(len, _) => *len,
Self::Item(item) => item.textual_len(),
}
}
}
#[typst_macros::time]
pub fn collect<'a>(
children: &'a StyleVec,
engine: &mut Engine<'_>,
locator: &mut SplitLocator<'a>,
styles: &'a StyleChain<'a>,
region: Size,
consecutive: bool,
) -> SourceResult<(String, Vec<Segment<'a>>, SpanMapper)> {
let mut collector = Collector::new(2 + children.len());
let mut quoter = SmartQuoter::new();
let outer_dir = TextElem::dir_in(*styles);
let first_line_indent = ParElem::first_line_indent_in(*styles);
if !first_line_indent.is_zero()
&& consecutive
&& AlignElem::alignment_in(*styles).resolve(*styles).x == outer_dir.start().into()
{
collector.push_item(Item::Absolute(first_line_indent.resolve(*styles), false));
collector.spans.push(1, Span::detached());
}
let hang = ParElem::hanging_indent_in(*styles);
if !hang.is_zero() {
collector.push_item(Item::Absolute(-hang, false));
collector.spans.push(1, Span::detached());
}
for (child, styles) in children.iter(styles) {
let prev_len = collector.full.len();
if child.is::<SpaceElem>() {
collector.push_text(" ", styles);
} else if let Some(elem) = child.to_packed::<TextElem>() {
collector.build_text(styles, |full| {
let dir = TextElem::dir_in(styles);
if dir != outer_dir {
match dir {
Dir::LTR => full.push_str(LTR_EMBEDDING),
Dir::RTL => full.push_str(RTL_EMBEDDING),
_ => {}
}
}
if let Some(case) = TextElem::case_in(styles) {
full.push_str(&case.apply(elem.text()));
} else {
full.push_str(elem.text());
}
if dir != outer_dir {
full.push_str(POP_EMBEDDING);
}
});
} else if let Some(elem) = child.to_packed::<HElem>() {
let amount = elem.amount();
if amount.is_zero() {
continue;
}
collector.push_item(match amount {
Spacing::Fr(fr) => Item::Fractional(*fr, None),
Spacing::Rel(rel) => Item::Absolute(
rel.resolve(styles).relative_to(region.x),
elem.weak(styles),
),
});
} else if let Some(elem) = child.to_packed::<LinebreakElem>() {
collector
.push_text(if elem.justify(styles) { "\u{2028}" } else { "\n" }, styles);
} else if let Some(elem) = child.to_packed::<SmartQuoteElem>() {
let double = elem.double(styles);
if elem.enabled(styles) {
let quotes = SmartQuotes::get(
elem.quotes(styles),
TextElem::lang_in(styles),
TextElem::region_in(styles),
elem.alternative(styles),
);
let before =
collector.full.chars().rev().find(|&c| !is_default_ignorable(c));
let quote = quoter.quote(before, "es, double);
collector.push_text(quote, styles);
} else {
collector.push_text(if double { "\"" } else { "'" }, styles);
}
} else if let Some(elem) = child.to_packed::<InlineElem>() {
collector.push_item(Item::Skip(LTR_ISOLATE));
for item in elem.layout(engine, locator.next(&elem.span()), styles, region)? {
match item {
InlineItem::Space(space, weak) => {
collector.push_item(Item::Absolute(space, weak));
}
InlineItem::Frame(frame) => {
collector.push_item(Item::Frame(frame, styles));
}
}
}
collector.push_item(Item::Skip(POP_ISOLATE));
} else if let Some(elem) = child.to_packed::<BoxElem>() {
let loc = locator.next(&elem.span());
if let Sizing::Fr(v) = elem.width(styles) {
collector.push_item(Item::Fractional(v, Some((elem, loc, styles))));
} else {
let frame = elem.layout(engine, loc, styles, region)?;
collector.push_item(Item::Frame(frame, styles));
}
} else if let Some(elem) = child.to_packed::<TagElem>() {
collector.push_item(Item::Tag(&elem.tag));
} else {
bail!(child.span(), "unexpected paragraph child");
};
let len = collector.full.len() - prev_len;
collector.spans.push(len, child.span());
}
Ok((collector.full, collector.segments, collector.spans))
}
struct Collector<'a> {
full: String,
segments: Vec<Segment<'a>>,
spans: SpanMapper,
}
impl<'a> Collector<'a> {
fn new(capacity: usize) -> Self {
Self {
full: String::new(),
segments: Vec::with_capacity(capacity),
spans: SpanMapper::new(),
}
}
fn push_text(&mut self, text: &str, styles: StyleChain<'a>) {
self.full.push_str(text);
self.push_segment(Segment::Text(text.len(), styles));
}
fn build_text<F>(&mut self, styles: StyleChain<'a>, f: F)
where
F: FnOnce(&mut String),
{
let prev = self.full.len();
f(&mut self.full);
let len = self.full.len() - prev;
self.push_segment(Segment::Text(len, styles));
}
fn push_item(&mut self, item: Item<'a>) {
self.full.push_str(item.textual());
self.push_segment(Segment::Item(item));
}
fn push_segment(&mut self, segment: Segment<'a>) {
match (self.segments.last_mut(), &segment) {
(Some(Segment::Text(last_len, last_styles)), Segment::Text(len, styles))
if *last_styles == *styles =>
{
*last_len += *len;
}
(
Some(Segment::Item(Item::Absolute(prev_amount, true))),
Segment::Item(Item::Absolute(amount, true)),
) => {
*prev_amount = (*prev_amount).max(*amount);
}
_ => self.segments.push(segment),
}
}
}
#[derive(Default)]
pub struct SpanMapper(Vec<(usize, Span)>);
impl SpanMapper {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, len: usize, span: Span) {
self.0.push((len, span));
}
pub fn span_at(&self, offset: usize) -> (Span, u16) {
let mut cursor = 0;
for &(len, span) in &self.0 {
if (cursor..cursor + len).contains(&offset) {
return (span, u16::try_from(offset - cursor).unwrap_or(0));
}
cursor += len;
}
(Span::detached(), 0)
}
}