mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Merge 0ed8280e3e4c1d34648543ea65cdb2f38d1ef1b1 into 9b09146a6b5e936966ed7ee73bce9dd2df3810ae
This commit is contained in:
commit
eb1d7b849a
@ -26,6 +26,7 @@ const LINE_SEPARATOR: char = '\u{2028}'; // We use LS to distinguish justified b
|
|||||||
/// first and last one since they may be broken apart by the start or end of the
|
/// first and last one since they may be broken apart by the start or end of the
|
||||||
/// line, respectively. But even those can partially reuse previous results when
|
/// line, respectively. But even those can partially reuse previous results when
|
||||||
/// the break index is safe-to-break per rustybuzz.
|
/// the break index is safe-to-break per rustybuzz.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Line<'a> {
|
pub struct Line<'a> {
|
||||||
/// The items the line is made of.
|
/// The items the line is made of.
|
||||||
pub items: Items<'a>,
|
pub items: Items<'a>,
|
||||||
@ -219,7 +220,7 @@ fn collect_items<'a>(
|
|||||||
// Add fallback text to expand the line height, if necessary.
|
// Add fallback text to expand the line height, if necessary.
|
||||||
if !items.iter().any(|item| matches!(item, Item::Text(_))) {
|
if !items.iter().any(|item| matches!(item, Item::Text(_))) {
|
||||||
if let Some(fallback) = fallback {
|
if let Some(fallback) = fallback {
|
||||||
items.push(fallback);
|
items.push(fallback, usize::MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,12 +271,13 @@ fn collect_range<'a>(
|
|||||||
items: &mut Items<'a>,
|
items: &mut Items<'a>,
|
||||||
fallback: &mut Option<ItemEntry<'a>>,
|
fallback: &mut Option<ItemEntry<'a>>,
|
||||||
) {
|
) {
|
||||||
for (subrange, item) in p.slice(range.clone()) {
|
for (idx, run) in p.slice(range.clone()).enumerate() {
|
||||||
// All non-text items are just kept, they can't be split.
|
// All non-text items are just kept, they can't be split.
|
||||||
let Item::Text(shaped) = item else {
|
let Item::Text(shaped) = &run.item else {
|
||||||
items.push(item);
|
items.push(&run.item, idx);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
let subrange = &run.range;
|
||||||
|
|
||||||
// The intersection range of the item, the subrange, and the line's
|
// The intersection range of the item, the subrange, and the line's
|
||||||
// trimming.
|
// trimming.
|
||||||
@ -293,10 +295,10 @@ fn collect_range<'a>(
|
|||||||
} else if split {
|
} else if split {
|
||||||
// When the item is split in half, reshape it.
|
// When the item is split in half, reshape it.
|
||||||
let reshaped = shaped.reshape(engine, sliced);
|
let reshaped = shaped.reshape(engine, sliced);
|
||||||
items.push(Item::Text(reshaped));
|
items.push(Item::Text(reshaped), idx);
|
||||||
} else {
|
} else {
|
||||||
// When the item is fully contained, just keep it.
|
// When the item is fully contained, just keep it.
|
||||||
items.push(item);
|
items.push(&run.item, idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,16 +501,16 @@ pub fn commit(
|
|||||||
|
|
||||||
// Build the frames and determine the height and baseline.
|
// Build the frames and determine the height and baseline.
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
for item in line.items.iter() {
|
for item in line.items.indexed_iter() {
|
||||||
let mut push = |offset: &mut Abs, frame: Frame| {
|
let mut push = |offset: &mut Abs, frame: Frame, idx: usize| {
|
||||||
let width = frame.width();
|
let width = frame.width();
|
||||||
top.set_max(frame.baseline());
|
top.set_max(frame.baseline());
|
||||||
bottom.set_max(frame.size().y - frame.baseline());
|
bottom.set_max(frame.size().y - frame.baseline());
|
||||||
frames.push((*offset, frame));
|
frames.push((*offset, frame, idx));
|
||||||
*offset += width;
|
*offset += width;
|
||||||
};
|
};
|
||||||
|
|
||||||
match item {
|
match &*item.item {
|
||||||
Item::Absolute(v, _) => {
|
Item::Absolute(v, _) => {
|
||||||
offset += *v;
|
offset += *v;
|
||||||
}
|
}
|
||||||
@ -520,7 +522,7 @@ pub fn commit(
|
|||||||
layout_box(elem, engine, loc.relayout(), styles, region)
|
layout_box(elem, engine, loc.relayout(), styles, region)
|
||||||
})?;
|
})?;
|
||||||
apply_baseline_shift(&mut frame, *styles);
|
apply_baseline_shift(&mut frame, *styles);
|
||||||
push(&mut offset, frame);
|
push(&mut offset, frame, item.idx);
|
||||||
} else {
|
} else {
|
||||||
offset += amount;
|
offset += amount;
|
||||||
}
|
}
|
||||||
@ -532,15 +534,15 @@ pub fn commit(
|
|||||||
justification_ratio,
|
justification_ratio,
|
||||||
extra_justification,
|
extra_justification,
|
||||||
);
|
);
|
||||||
push(&mut offset, frame);
|
push(&mut offset, frame, item.idx);
|
||||||
}
|
}
|
||||||
Item::Frame(frame) => {
|
Item::Frame(frame) => {
|
||||||
push(&mut offset, frame.clone());
|
push(&mut offset, frame.clone(), item.idx);
|
||||||
}
|
}
|
||||||
Item::Tag(tag) => {
|
Item::Tag(tag) => {
|
||||||
let mut frame = Frame::soft(Size::zero());
|
let mut frame = Frame::soft(Size::zero());
|
||||||
frame.push(Point::zero(), FrameItem::Tag((*tag).clone()));
|
frame.push(Point::zero(), FrameItem::Tag((*tag).clone()));
|
||||||
frames.push((offset, frame));
|
frames.push((offset, frame, item.idx));
|
||||||
}
|
}
|
||||||
Item::Skip(_) => {}
|
Item::Skip(_) => {}
|
||||||
}
|
}
|
||||||
@ -559,8 +561,9 @@ pub fn commit(
|
|||||||
add_par_line_marker(&mut output, marker, engine, locator, top);
|
add_par_line_marker(&mut output, marker, engine, locator, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frames.sort_by_key(|(_, _, idx)| *idx);
|
||||||
// Construct the line's frame.
|
// Construct the line's frame.
|
||||||
for (offset, frame) in frames {
|
for (offset, frame, _) in frames {
|
||||||
let x = offset + p.config.align.position(remaining);
|
let x = offset + p.config.align.position(remaining);
|
||||||
let y = top - frame.baseline();
|
let y = top - frame.baseline();
|
||||||
output.push_frame(Point::new(x, y), frame);
|
output.push_frame(Point::new(x, y), frame);
|
||||||
@ -627,7 +630,7 @@ fn overhang(c: char) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of owned or borrowed inline items.
|
/// A collection of owned or borrowed inline items.
|
||||||
pub struct Items<'a>(Vec<ItemEntry<'a>>);
|
pub struct Items<'a>(Vec<IndexedItemEntry<'a>>);
|
||||||
|
|
||||||
impl<'a> Items<'a> {
|
impl<'a> Items<'a> {
|
||||||
/// Create empty items.
|
/// Create empty items.
|
||||||
@ -636,33 +639,38 @@ impl<'a> Items<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push a new item.
|
/// Push a new item.
|
||||||
pub fn push(&mut self, entry: impl Into<ItemEntry<'a>>) {
|
pub fn push(&mut self, entry: impl Into<ItemEntry<'a>>, idx: usize) {
|
||||||
self.0.push(entry.into());
|
self.0.push(IndexedItemEntry { item: entry.into(), idx });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the items
|
/// Iterate over the items
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &Item<'a>> {
|
pub fn iter(&self) -> impl Iterator<Item = &Item<'a>> {
|
||||||
self.0.iter().map(|item| &**item)
|
self.0.iter().map(|item| &*item.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the items with indices
|
||||||
|
pub fn indexed_iter(&self) -> impl Iterator<Item = &IndexedItemEntry<'a>> {
|
||||||
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the first item.
|
/// Access the first item.
|
||||||
pub fn first(&self) -> Option<&Item<'a>> {
|
pub fn first(&self) -> Option<&Item<'a>> {
|
||||||
self.0.first().map(|item| &**item)
|
self.0.first().map(|item| &*item.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the last item.
|
/// Access the last item.
|
||||||
pub fn last(&self) -> Option<&Item<'a>> {
|
pub fn last(&self) -> Option<&Item<'a>> {
|
||||||
self.0.last().map(|item| &**item)
|
self.0.last().map(|item| &*item.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the first item mutably, if it is text.
|
/// Access the first item mutably, if it is text.
|
||||||
pub fn first_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
pub fn first_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
||||||
self.0.first_mut()?.text_mut()
|
self.0.first_mut()?.item.text_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the last item mutably, if it is text.
|
/// Access the last item mutably, if it is text.
|
||||||
pub fn last_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
pub fn last_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
||||||
self.0.last_mut()?.text_mut()
|
self.0.last_mut()?.item.text_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reorder the items starting at the given index to RTL.
|
/// Reorder the items starting at the given index to RTL.
|
||||||
@ -673,12 +681,17 @@ impl<'a> Items<'a> {
|
|||||||
|
|
||||||
impl<'a> FromIterator<ItemEntry<'a>> for Items<'a> {
|
impl<'a> FromIterator<ItemEntry<'a>> for Items<'a> {
|
||||||
fn from_iter<I: IntoIterator<Item = ItemEntry<'a>>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item = ItemEntry<'a>>>(iter: I) -> Self {
|
||||||
Self(iter.into_iter().collect())
|
Self(
|
||||||
|
iter.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, item)| IndexedItemEntry { item, idx })
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for Items<'a> {
|
impl<'a> Deref for Items<'a> {
|
||||||
type Target = Vec<ItemEntry<'a>>;
|
type Target = Vec<IndexedItemEntry<'a>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
@ -697,7 +710,18 @@ impl Debug for Items<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An item accompanied by its position within a line.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IndexedItemEntry<'a> {
|
||||||
|
pub item: ItemEntry<'a>,
|
||||||
|
pub idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
/// A reference to or a boxed item.
|
/// A reference to or a boxed item.
|
||||||
|
///
|
||||||
|
/// This is conceptually similar to a [`Cow<'a, Item<'a>>`][std::borrow::Cow],
|
||||||
|
/// but we box owned items since an [`Item`] is much bigger than
|
||||||
|
/// a box.
|
||||||
pub enum ItemEntry<'a> {
|
pub enum ItemEntry<'a> {
|
||||||
Ref(&'a Item<'a>),
|
Ref(&'a Item<'a>),
|
||||||
Box(Box<Item<'a>>),
|
Box(Box<Item<'a>>),
|
||||||
|
@ -823,8 +823,8 @@ fn linebreak_link(link: &str, mut f: impl FnMut(usize)) {
|
|||||||
/// Whether hyphenation is enabled at the given offset.
|
/// Whether hyphenation is enabled at the given offset.
|
||||||
fn hyphenate_at(p: &Preparation, offset: usize) -> bool {
|
fn hyphenate_at(p: &Preparation, offset: usize) -> bool {
|
||||||
p.config.hyphenate.unwrap_or_else(|| {
|
p.config.hyphenate.unwrap_or_else(|| {
|
||||||
let (_, item) = p.get(offset);
|
let run = p.get(offset);
|
||||||
match item.text() {
|
match run.item.text() {
|
||||||
Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify),
|
Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
@ -834,8 +834,8 @@ fn hyphenate_at(p: &Preparation, offset: usize) -> bool {
|
|||||||
/// The text language at the given offset.
|
/// The text language at the given offset.
|
||||||
fn lang_at(p: &Preparation, offset: usize) -> Option<hypher::Lang> {
|
fn lang_at(p: &Preparation, offset: usize) -> Option<hypher::Lang> {
|
||||||
let lang = p.config.lang.or_else(|| {
|
let lang = p.config.lang.or_else(|| {
|
||||||
let (_, item) = p.get(offset);
|
let run = p.get(offset);
|
||||||
let styles = item.text()?.styles;
|
let styles = run.item.text()?.styles;
|
||||||
Some(TextElem::lang_in(styles))
|
Some(TextElem::lang_in(styles))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -900,8 +900,8 @@ impl Estimates {
|
|||||||
let mut shrinkability = CumulativeVec::with_capacity(cap);
|
let mut shrinkability = CumulativeVec::with_capacity(cap);
|
||||||
let mut justifiables = CumulativeVec::with_capacity(cap);
|
let mut justifiables = CumulativeVec::with_capacity(cap);
|
||||||
|
|
||||||
for (range, item) in p.items.iter() {
|
for run in p.items.iter() {
|
||||||
if let Item::Text(shaped) = item {
|
if let Item::Text(shaped) = &run.item {
|
||||||
for g in shaped.glyphs.iter() {
|
for g in shaped.glyphs.iter() {
|
||||||
let byte_len = g.range.len();
|
let byte_len = g.range.len();
|
||||||
let stretch = g.stretchability().0 + g.stretchability().1;
|
let stretch = g.stretchability().0 + g.stretchability().1;
|
||||||
@ -912,13 +912,13 @@ impl Estimates {
|
|||||||
justifiables.push(byte_len, g.is_justifiable() as usize);
|
justifiables.push(byte_len, g.is_justifiable() as usize);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
widths.push(range.len(), item.natural_width());
|
widths.push(run.range.len(), run.item.natural_width());
|
||||||
}
|
}
|
||||||
|
|
||||||
widths.adjust(range.end);
|
widths.adjust(run.range.end);
|
||||||
stretchability.adjust(range.end);
|
stretchability.adjust(run.range.end);
|
||||||
shrinkability.adjust(range.end);
|
shrinkability.adjust(run.range.end);
|
||||||
justifiables.adjust(range.end);
|
justifiables.adjust(run.range.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -3,6 +3,12 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Run<'a> {
|
||||||
|
pub item: Item<'a>,
|
||||||
|
pub range: Range,
|
||||||
|
}
|
||||||
|
|
||||||
/// A representation in which children are already layouted and text is already
|
/// A representation in which children are already layouted and text is already
|
||||||
/// preshaped.
|
/// preshaped.
|
||||||
///
|
///
|
||||||
@ -20,22 +26,22 @@ pub struct Preparation<'a> {
|
|||||||
/// direction).
|
/// direction).
|
||||||
pub bidi: Option<BidiInfo<'a>>,
|
pub bidi: Option<BidiInfo<'a>>,
|
||||||
/// Text runs, spacing and layouted elements.
|
/// Text runs, spacing and layouted elements.
|
||||||
pub items: Vec<(Range, Item<'a>)>,
|
pub items: Vec<Run<'a>>,
|
||||||
/// Maps from byte indices to item indices.
|
/// Maps from byte indices to item indices.
|
||||||
pub indices: Vec<usize>,
|
pub indices: Vec<usize>,
|
||||||
/// The span mapper.
|
/// The span mapper.
|
||||||
pub spans: SpanMapper,
|
pub spans: SpanMapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Preparation<'a> {
|
impl Preparation<'_> {
|
||||||
/// Get the item that contains the given `text_offset`.
|
/// Get the item that contains the given `text_offset`.
|
||||||
pub fn get(&self, offset: usize) -> &(Range, Item<'a>) {
|
pub fn get(&self, offset: usize) -> &Run {
|
||||||
let idx = self.indices.get(offset).copied().unwrap_or(0);
|
let idx = self.indices.get(offset).copied().unwrap_or(0);
|
||||||
&self.items[idx]
|
&self.items[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the items that intersect the given `sliced` range.
|
/// Iterate over the items that intersect the given `sliced` range.
|
||||||
pub fn slice(&self, sliced: Range) -> impl Iterator<Item = &(Range, Item<'a>)> {
|
pub fn slice(&self, sliced: Range) -> impl Iterator<Item = &Run> {
|
||||||
// Usually, we don't want empty-range items at the start of the line
|
// Usually, we don't want empty-range items at the start of the line
|
||||||
// (because they will be part of the previous line), but for the first
|
// (because they will be part of the previous line), but for the first
|
||||||
// line, we need to keep them.
|
// line, we need to keep them.
|
||||||
@ -43,8 +49,8 @@ impl<'a> Preparation<'a> {
|
|||||||
0 => 0,
|
0 => 0,
|
||||||
n => self.indices.get(n).copied().unwrap_or(0),
|
n => self.indices.get(n).copied().unwrap_or(0),
|
||||||
};
|
};
|
||||||
self.items[start..].iter().take_while(move |(range, _)| {
|
self.items[start..].iter().take_while(move |run| {
|
||||||
range.start < sliced.end || range.end <= sliced.end
|
run.range.start < sliced.end || run.range.end <= sliced.end
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +90,9 @@ pub fn prepare<'a>(
|
|||||||
Segment::Text(_, styles) => {
|
Segment::Text(_, styles) => {
|
||||||
shape_range(&mut items, engine, text, &bidi, range, styles);
|
shape_range(&mut items, engine, text, &bidi, range, styles);
|
||||||
}
|
}
|
||||||
Segment::Item(item) => items.push((range, item)),
|
Segment::Item(item) => {
|
||||||
|
items.push(Run { range, item });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = end;
|
cursor = end;
|
||||||
@ -92,8 +100,8 @@ pub fn prepare<'a>(
|
|||||||
|
|
||||||
// Build the mapping from byte to item indices.
|
// Build the mapping from byte to item indices.
|
||||||
let mut indices = Vec::with_capacity(text.len());
|
let mut indices = Vec::with_capacity(text.len());
|
||||||
for (i, (range, _)) in items.iter().enumerate() {
|
for (i, run) in items.iter().enumerate() {
|
||||||
indices.extend(range.clone().map(|_| i));
|
indices.extend(run.range.clone().map(|_| i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.cjk_latin_spacing {
|
if config.cjk_latin_spacing {
|
||||||
@ -113,15 +121,15 @@ pub fn prepare<'a>(
|
|||||||
/// Add some spacing between Han characters and western characters. See
|
/// Add some spacing between Han characters and western characters. See
|
||||||
/// Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition
|
/// Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition
|
||||||
/// in Horizontal Written Mode
|
/// in Horizontal Written Mode
|
||||||
fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) {
|
fn add_cjk_latin_spacing(items: &mut [Run]) {
|
||||||
let mut items = items
|
let mut items = items
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|(_, x)| !matches!(x, Item::Tag(_)))
|
.filter(|run| !matches!(run.item, Item::Tag(_)))
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
let mut prev: Option<&ShapedGlyph> = None;
|
let mut prev: Option<&ShapedGlyph> = None;
|
||||||
while let Some((_, item)) = items.next() {
|
while let Some(run) = items.next() {
|
||||||
let Some(text) = item.text_mut() else {
|
let Some(text) = run.item.text_mut() else {
|
||||||
prev = None;
|
prev = None;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -135,7 +143,7 @@ fn add_cjk_latin_spacing(items: &mut [(Range, Item)]) {
|
|||||||
let next = glyphs.peek().map(|n| n as _).or_else(|| {
|
let next = glyphs.peek().map(|n| n as _).or_else(|| {
|
||||||
items
|
items
|
||||||
.peek()
|
.peek()
|
||||||
.and_then(|(_, i)| i.text())
|
.and_then(|run| run.item.text())
|
||||||
.and_then(|shaped| shaped.glyphs.first())
|
.and_then(|shaped| shaped.glyphs.first())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ use typst_utils::SliceExt;
|
|||||||
use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||||
use unicode_script::{Script, UnicodeScript};
|
use unicode_script::{Script, UnicodeScript};
|
||||||
|
|
||||||
|
use super::prepare::Run;
|
||||||
use super::{decorate, Item, Range, SpanMapper};
|
use super::{decorate, Item, Range, SpanMapper};
|
||||||
use crate::modifiers::{FrameModifiers, FrameModify};
|
use crate::modifiers::{FrameModifiers, FrameModify};
|
||||||
|
|
||||||
@ -592,7 +593,7 @@ impl Debug for ShapedText<'_> {
|
|||||||
/// Group a range of text by BiDi level and script, shape the runs and generate
|
/// Group a range of text by BiDi level and script, shape the runs and generate
|
||||||
/// items for them.
|
/// items for them.
|
||||||
pub fn shape_range<'a>(
|
pub fn shape_range<'a>(
|
||||||
items: &mut Vec<(Range, Item<'a>)>,
|
items: &mut Vec<Run<'a>>,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
bidi: &BidiInfo<'a>,
|
bidi: &BidiInfo<'a>,
|
||||||
@ -606,7 +607,7 @@ pub fn shape_range<'a>(
|
|||||||
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
||||||
let shaped =
|
let shaped =
|
||||||
shape(engine, range.start, &text[range.clone()], styles, dir, lang, region);
|
shape(engine, range.start, &text[range.clone()], styles, dir, lang, region);
|
||||||
items.push((range, Item::Text(shaped)));
|
items.push(Run { range, item: Item::Text(shaped) });
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut prev_level = BidiLevel::ltr();
|
let mut prev_level = BidiLevel::ltr();
|
||||||
|
@ -539,6 +539,7 @@ impl IntoValue for CslSource {
|
|||||||
/// memoization) for the whole document. This setup is necessary because
|
/// memoization) for the whole document. This setup is necessary because
|
||||||
/// citation formatting is inherently stateful and we need access to all
|
/// citation formatting is inherently stateful and we need access to all
|
||||||
/// citations to do it.
|
/// citations to do it.
|
||||||
|
#[derive(Debug)]
|
||||||
pub(super) struct Works {
|
pub(super) struct Works {
|
||||||
/// Maps from the location of a citation group to its rendered content.
|
/// Maps from the location of a citation group to its rendered content.
|
||||||
pub citations: HashMap<Location, SourceResult<Content>>,
|
pub citations: HashMap<Location, SourceResult<Content>>,
|
||||||
@ -571,7 +572,7 @@ impl Works {
|
|||||||
|
|
||||||
/// Context for generating the bibliography.
|
/// Context for generating the bibliography.
|
||||||
struct Generator<'a> {
|
struct Generator<'a> {
|
||||||
/// The routines that is used to evaluate mathematical material in citations.
|
/// The routines that are used to evaluate mathematical material in citations.
|
||||||
routines: &'a Routines,
|
routines: &'a Routines,
|
||||||
/// The world that is used to evaluate mathematical material in citations.
|
/// The world that is used to evaluate mathematical material in citations.
|
||||||
world: Tracked<'a, dyn World + 'a>,
|
world: Tracked<'a, dyn World + 'a>,
|
||||||
@ -588,7 +589,7 @@ struct Generator<'a> {
|
|||||||
|
|
||||||
/// Details about a group of merged citations. All citations are put into groups
|
/// Details about a group of merged citations. All citations are put into groups
|
||||||
/// of adjacent ones (e.g., `@foo @bar` will merge into a group of length two).
|
/// of adjacent ones (e.g., `@foo @bar` will merge into a group of length two).
|
||||||
/// Even single citations will be put into groups of length ones.
|
/// Even single citations will be put into groups of length one.
|
||||||
struct GroupInfo {
|
struct GroupInfo {
|
||||||
/// The group's location.
|
/// The group's location.
|
||||||
location: Location,
|
location: Location,
|
||||||
|
BIN
tests/ref/issue-5775-cite-order-rtl.png
Normal file
BIN
tests/ref/issue-5775-cite-order-rtl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
@ -147,3 +147,13 @@ B #cite(<netwok>) #cite(<arrgh>).
|
|||||||
// Error: 7-17 expected label, found string
|
// Error: 7-17 expected label, found string
|
||||||
// Hint: 7-17 use `label("%@&#*!\\")` to create a label
|
// Hint: 7-17 use `label("%@&#*!\\")` to create a label
|
||||||
#cite("%@&#*!\\")
|
#cite("%@&#*!\\")
|
||||||
|
|
||||||
|
--- issue-5775-cite-order-rtl ---
|
||||||
|
// Test citation order in RTL text.
|
||||||
|
#set page(width: 300pt)
|
||||||
|
این است
|
||||||
|
@tolkien54
|
||||||
|
و این یکی هست
|
||||||
|
@arrgh
|
||||||
|
|
||||||
|
#bibliography("/assets/bib/works.bib")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user