Ensure that hyphenation is possible after a tag (#6807)

This commit is contained in:
Laurenz 2025-08-22 18:28:11 +02:00 committed by GitHub
parent 720f01a11c
commit 206792bf38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 28 deletions

View File

@ -46,6 +46,11 @@ pub enum Item<'a> {
}
impl<'a> Item<'a> {
/// Whether this is a tag item.
pub fn is_tag(&self) -> bool {
matches!(self, Self::Tag(_))
}
/// If this a text item, return it.
pub fn text(&self) -> Option<&ShapedText<'a>> {
match self {

View File

@ -80,8 +80,7 @@ impl Line<'_> {
// CJK character at line end should not be adjusted.
if self
.items
.last()
.and_then(Item::text)
.trailing_text()
.map(|s| s.cjk_justifiable_at_last())
.unwrap_or(false)
{
@ -176,7 +175,7 @@ pub fn line<'a>(
// Add a hyphen at the line start, if a previous dash should be repeated.
if let Some(pred) = pred
&& pred.dash == Some(Dash::Hard)
&& let Some(base) = pred.items.last_text()
&& let Some(base) = pred.items.trailing_text()
&& should_repeat_hyphen(base.lang, full)
&& let Some(hyphen) =
ShapedText::hyphen(engine, p.config.fallback, base, trim, false)
@ -188,7 +187,7 @@ pub fn line<'a>(
// Add a hyphen at the line end, if we ended on a soft hyphen.
if dash == Some(Dash::Soft)
&& let Some(base) = items.last_text()
&& let Some(base) = items.trailing_text()
&& let Some(hyphen) =
ShapedText::hyphen(engine, p.config.fallback, base, trim, true)
{
@ -253,7 +252,7 @@ fn trim_weak_spacing(items: &mut Items) {
}
// Trim weak spacing at the end of the line.
while matches!(items.last(), Some(Item::Absolute(_, true))) {
while matches!(items.iter().next_back(), Some(Item::Absolute(_, true))) {
items.pop();
}
}
@ -355,7 +354,7 @@ fn adjust_cj_at_line_boundaries(p: &Preparation, text: &str, items: &mut Items)
/// Add spacing around punctuation marks for CJ glyphs at the line start.
fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) {
let Some(shaped) = items.first_text_mut() else { return };
let Some(shaped) = items.leading_text_mut() else { return };
let Some(glyph) = shaped.glyphs.first() else { return };
if glyph.is_cjk_right_aligned_punctuation() {
@ -380,7 +379,7 @@ fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) {
/// Add spacing around punctuation marks for CJ glyphs at the line end.
fn adjust_cj_at_line_end(p: &Preparation, items: &mut Items) {
let Some(shaped) = items.last_text_mut() else { return };
let Some(shaped) = items.trailing_text_mut() else { return };
let Some(glyph) = shaped.glyphs.last() else { return };
// Deal with CJK punctuation at line ends.
@ -481,7 +480,7 @@ pub fn commit(
}
// Handle hanging punctuation to the left.
if let Some(Item::Text(text)) = line.items.first()
if let Some(text) = line.items.leading_text()
&& let Some(glyph) = text.glyphs.first()
&& !text.dir.is_positive()
&& text.styles.get(TextElem::overhang)
@ -493,7 +492,7 @@ pub fn commit(
}
// Handle hanging punctuation to the right.
if let Some(Item::Text(text)) = line.items.last()
if let Some(text) = line.items.trailing_text()
&& let Some(glyph) = text.glyphs.last()
&& text.dir.is_positive()
&& text.styles.get(TextElem::overhang)
@ -685,7 +684,7 @@ impl<'a> Items<'a> {
}
/// Iterate over the items.
pub fn iter(&self) -> impl Iterator<Item = &Item<'a>> {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Item<'a>> {
self.0.iter().map(|(_, item)| &**item)
}
@ -694,33 +693,30 @@ impl<'a> Items<'a> {
///
/// Note that this is different from `.iter().enumerate()` which would
/// provide the indices in visual order!
pub fn indexed_iter(&self) -> impl Iterator<Item = &(usize, ItemEntry<'a>)> {
pub fn indexed_iter(
&self,
) -> impl DoubleEndedIterator<Item = &(usize, ItemEntry<'a>)> {
self.0.iter()
}
/// Access the first item.
pub fn first(&self) -> Option<&Item<'a>> {
self.0.first().map(|(_, item)| &**item)
/// Access the first item (skipping tags), if it is text.
pub fn leading_text(&self) -> Option<&ShapedText<'a>> {
self.0.iter().find(|(_, item)| !item.is_tag())?.1.text()
}
/// Access the last item.
pub fn last(&self) -> Option<&Item<'a>> {
self.0.last().map(|(_, item)| &**item)
/// Access the first item (skipping tags) mutably, if it is text.
pub fn leading_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
self.0.iter_mut().find(|(_, item)| !item.is_tag())?.1.text_mut()
}
/// Access the last item, if it is text.
pub fn last_text(&self) -> Option<&ShapedText<'a>> {
self.0.last()?.1.text()
/// Access the last item (skipping tags), if it is text.
pub fn trailing_text(&self) -> Option<&ShapedText<'a>> {
self.0.iter().rev().find(|(_, item)| !item.is_tag())?.1.text()
}
/// Access the first item mutably, if it is text.
pub fn first_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
self.0.first_mut()?.1.text_mut()
}
/// Access the last item mutably, if it is text.
pub fn last_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
self.0.last_mut()?.1.text_mut()
/// Access the last item (skipping tags) mutably, if it is text.
pub fn trailing_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
self.0.iter_mut().rev().find(|(_, item)| !item.is_tag())?.1.text_mut()
}
/// Reorder the items starting at the given index to RTL.

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

View File

@ -171,3 +171,12 @@ Hello-#text(red)[world]
#set text(costs: (hyphenation: 1%, runt: 2%))
#set text(costs: (widow: 3%))
#context test(text.costs, (hyphenation: 1%, runt: 2%, widow: 3%, orphan: 100%))
--- issue-hyphenate-after-tag ---
// Ensure that an invisible tag does not prevent hyphenation.
#set page(width: 50pt)
#set text(hyphenate: true)
#show "Tree": emph
#show emph: set text(red)
#show emph: it => it + metadata(none)
Treebeard