summaryrefslogtreecommitdiff
path: root/library/src/layout/par.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-11 23:28:35 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-11 23:29:32 +0100
commitca6edf5283c258d8410134d678347977cb273cdd (patch)
treeb2b46ba70c054b0cbdefa06edbc5fd999cddb1fa /library/src/layout/par.rs
parent1a390deaea040191cf0e5937bd8e1427b49db71b (diff)
Jump to source and preview
Diffstat (limited to 'library/src/layout/par.rs')
-rw-r--r--library/src/layout/par.rs59
1 files changed, 48 insertions, 11 deletions
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index 8dd81d29..6178c059 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -145,12 +145,12 @@ impl ParNode {
let children = par.children();
// Collect all text into one string for BiDi analysis.
- let (text, segments) = collect(&children, &styles, consecutive)?;
+ let (text, segments, spans) = collect(&children, &styles, consecutive)?;
// Perform BiDi analysis and then prepare paragraph layout by building a
// representation on which we can do line breaking without layouting
// each and every line from scratch.
- let p = prepare(&mut vt, &children, &text, segments, styles, region)?;
+ let p = prepare(&mut vt, &children, &text, segments, spans, styles, region)?;
// Break the paragraph into lines.
let lines = linebreak(&vt, &p, region.x);
@@ -264,6 +264,8 @@ struct Preparation<'a> {
bidi: BidiInfo<'a>,
/// Text runs, spacing and layouted nodes.
items: Vec<Item<'a>>,
+ /// The span mapper.
+ spans: SpanMapper,
/// The styles shared by all children.
styles: StyleChain<'a>,
/// Whether to hyphenate if it's the same for all children.
@@ -388,6 +390,35 @@ impl<'a> Item<'a> {
}
}
+/// Maps byte offsets back to spans.
+pub struct SpanMapper(Vec<(usize, Span)>);
+
+impl SpanMapper {
+ /// Create a new span mapper.
+ pub fn new() -> Self {
+ Self(vec![])
+ }
+
+ /// Push a span for a segment with the given length.
+ pub fn push(&mut self, len: usize, span: Span) {
+ self.0.push((len, span));
+ }
+
+ /// Determine the span at the given byte offset.
+ ///
+ /// May return a detached 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)
+ }
+}
+
/// A layouted line, consisting of a sequence of layouted paragraph items that
/// are mostly borrowed from the preparation phase. This type enables you to
/// measure the size of a line in a range before comitting to building the
@@ -485,10 +516,11 @@ fn collect<'a>(
children: &'a [Content],
styles: &'a StyleChain<'a>,
consecutive: bool,
-) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>)> {
+) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
let mut full = String::new();
let mut quoter = Quoter::new();
let mut segments = vec![];
+ let mut spans = SpanMapper::new();
let mut iter = children.iter().peekable();
if consecutive {
@@ -578,6 +610,8 @@ fn collect<'a>(
quoter.last(last);
}
+ spans.push(segment.len(), child.span());
+
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
(segments.last_mut(), segment)
{
@@ -590,7 +624,7 @@ fn collect<'a>(
segments.push((segment, styles));
}
- Ok((full, segments))
+ Ok((full, segments, spans))
}
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
@@ -600,6 +634,7 @@ fn prepare<'a>(
children: &'a [Content],
text: &'a str,
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
+ spans: SpanMapper,
styles: StyleChain<'a>,
region: Size,
) -> SourceResult<Preparation<'a>> {
@@ -620,7 +655,7 @@ fn prepare<'a>(
let end = cursor + segment.len();
match segment {
Segment::Text(_) => {
- shape_range(&mut items, vt, &bidi, cursor..end, styles);
+ shape_range(&mut items, vt, &bidi, cursor..end, &spans, styles);
}
Segment::Spacing(spacing) => match spacing {
Spacing::Rel(v) => {
@@ -655,6 +690,7 @@ fn prepare<'a>(
Ok(Preparation {
bidi,
items,
+ spans,
styles,
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
lang: shared_get(styles, children, TextNode::lang_in),
@@ -670,11 +706,12 @@ fn shape_range<'a>(
vt: &Vt,
bidi: &BidiInfo<'a>,
range: Range,
+ spans: &SpanMapper,
styles: StyleChain<'a>,
) {
- let mut process = |text, level: BidiLevel| {
+ let mut process = |range: Range, level: BidiLevel| {
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
- let shaped = shape(vt, text, styles, dir);
+ let shaped = shape(vt, range.start, &bidi.text[range], spans, styles, dir);
items.push(Item::Text(shaped));
};
@@ -694,7 +731,7 @@ fn shape_range<'a>(
if level != prev_level || !is_compatible(script, prev_script) {
if cursor < i {
- process(&bidi.text[cursor..i], prev_level);
+ process(cursor..i, prev_level);
}
cursor = i;
prev_level = level;
@@ -704,7 +741,7 @@ fn shape_range<'a>(
}
}
- process(&bidi.text[cursor..range.end], prev_level);
+ process(cursor..range.end, prev_level);
}
/// Whether this is not a specific script.
@@ -1073,7 +1110,7 @@ fn line<'a>(
if hyphen || start + shaped.text.len() > range.end {
if hyphen || start < range.end || before.is_empty() {
let shifted = start - base..range.end - base;
- let mut reshaped = shaped.reshape(vt, shifted);
+ let mut reshaped = shaped.reshape(vt, &p.spans, shifted);
if hyphen || shy {
reshaped.push_hyphen(vt);
}
@@ -1096,7 +1133,7 @@ fn line<'a>(
if range.start + shaped.text.len() > end {
if range.start < end {
let shifted = range.start - base..end - base;
- let reshaped = shaped.reshape(vt, shifted);
+ let reshaped = shaped.reshape(vt, &p.spans, shifted);
width += reshaped.width;
first = Some(Item::Text(reshaped));
}