summaryrefslogtreecommitdiff
path: root/library
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
parent1a390deaea040191cf0e5937bd8e1427b49db71b (diff)
Jump to source and preview
Diffstat (limited to 'library')
-rw-r--r--library/src/layout/par.rs59
-rw-r--r--library/src/math/fragment.rs2
-rw-r--r--library/src/text/shaping.rs44
3 files changed, 91 insertions, 14 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));
}
diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs
index 73daa4b2..de456b82 100644
--- a/library/src/math/fragment.rs
+++ b/library/src/math/fragment.rs
@@ -222,6 +222,8 @@ impl GlyphFragment {
c: self.c,
x_advance: Em::from_length(self.width, self.font_size),
x_offset: Em::zero(),
+ span: Span::detached(),
+ offset: 0,
}],
};
let size = Size::new(self.width, self.ascent + self.descent);
diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs
index e7ce4027..a96238d9 100644
--- a/library/src/text/shaping.rs
+++ b/library/src/text/shaping.rs
@@ -6,6 +6,7 @@ use typst::font::{Font, FontVariant};
use typst::util::SliceExt;
use super::*;
+use crate::layout::SpanMapper;
use crate::prelude::*;
/// The result of shaping text.
@@ -14,6 +15,8 @@ use crate::prelude::*;
/// measured, used to reshape substrings more quickly and converted into a
/// frame.
pub struct ShapedText<'a> {
+ /// The start of the text in the full paragraph.
+ pub base: usize,
/// The text that was shaped.
pub text: &'a str,
/// The text direction.
@@ -53,6 +56,10 @@ pub struct ShapedGlyph {
pub safe_to_break: bool,
/// The first char in this glyph's cluster.
pub c: char,
+ /// The source code location of the text.
+ pub span: Span,
+ /// The offset within the spanned text.
+ pub offset: u16,
}
impl ShapedGlyph {
@@ -110,6 +117,8 @@ impl<'a> ShapedText<'a> {
},
x_offset: glyph.x_offset,
c: glyph.c,
+ span: glyph.span,
+ offset: glyph.offset,
})
.collect();
@@ -187,9 +196,15 @@ impl<'a> ShapedText<'a> {
/// Reshape a range of the shaped text, reusing information from this
/// shaping process if possible.
- pub fn reshape(&'a self, vt: &Vt, text_range: Range<usize>) -> ShapedText<'a> {
+ pub fn reshape(
+ &'a self,
+ vt: &Vt,
+ spans: &SpanMapper,
+ text_range: Range<usize>,
+ ) -> ShapedText<'a> {
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
Self {
+ base: self.base + text_range.start,
text: &self.text[text_range],
dir: self.dir,
styles: self.styles,
@@ -199,7 +214,14 @@ impl<'a> ShapedText<'a> {
glyphs: Cow::Borrowed(glyphs),
}
} else {
- shape(vt, &self.text[text_range], self.styles, self.dir)
+ shape(
+ vt,
+ self.base + text_range.start,
+ &self.text[text_range],
+ spans,
+ self.styles,
+ self.dir,
+ )
}
}
@@ -225,6 +247,8 @@ impl<'a> ShapedText<'a> {
cluster,
safe_to_break: true,
c: '-',
+ span: Span::detached(),
+ offset: 0,
});
Some(())
});
@@ -298,6 +322,8 @@ impl Debug for ShapedText<'_> {
/// Holds shaping results and metadata common to all shaped segments.
struct ShapingContext<'a> {
vt: &'a Vt<'a>,
+ base: usize,
+ spans: &'a SpanMapper,
glyphs: Vec<ShapedGlyph>,
used: Vec<Font>,
styles: StyleChain<'a>,
@@ -311,13 +337,17 @@ struct ShapingContext<'a> {
/// Shape text into [`ShapedText`].
pub fn shape<'a>(
vt: &Vt,
+ base: usize,
text: &'a str,
+ spans: &SpanMapper,
styles: StyleChain<'a>,
dir: Dir,
) -> ShapedText<'a> {
let size = TextNode::size_in(styles);
let mut ctx = ShapingContext {
vt,
+ base,
+ spans,
size,
glyphs: vec![],
used: vec![],
@@ -335,6 +365,7 @@ pub fn shape<'a>(
track_and_space(&mut ctx);
ShapedText {
+ base,
text,
dir,
styles,
@@ -410,6 +441,7 @@ fn shape_segment<'a>(
if info.glyph_id != 0 {
// Add the glyph to the shaped output.
// TODO: Don't ignore y_advance.
+ let (span, offset) = ctx.spans.span_at(ctx.base + cluster);
ctx.glyphs.push(ShapedGlyph {
font: font.clone(),
glyph_id: info.glyph_id as u16,
@@ -419,6 +451,8 @@ fn shape_segment<'a>(
cluster: base + cluster,
safe_to_break: !info.unsafe_to_break(),
c: text[cluster..].chars().next().unwrap(),
+ span,
+ offset,
});
} else {
// Determine the source text range for the tofu sequence.
@@ -478,15 +512,19 @@ fn shape_segment<'a>(
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
let x_advance = font.advance(0).unwrap_or_default();
for (cluster, c) in text.char_indices() {
+ let cluster = base + cluster;
+ let (span, offset) = ctx.spans.span_at(ctx.base + cluster);
ctx.glyphs.push(ShapedGlyph {
font: font.clone(),
glyph_id: 0,
x_advance,
x_offset: Em::zero(),
y_offset: Em::zero(),
- cluster: base + cluster,
+ cluster,
safe_to_break: true,
c,
+ span,
+ offset,
});
}
}