mirror of
https://github.com/typst/typst
synced 2025-08-23 19:24:14 +08:00
Ensure that hyphenation is possible after a tag (#6807)
This commit is contained in:
parent
720f01a11c
commit
206792bf38
@ -46,6 +46,11 @@ pub enum Item<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> 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.
|
/// If this a text item, return it.
|
||||||
pub fn text(&self) -> Option<&ShapedText<'a>> {
|
pub fn text(&self) -> Option<&ShapedText<'a>> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -80,8 +80,7 @@ impl Line<'_> {
|
|||||||
// CJK character at line end should not be adjusted.
|
// CJK character at line end should not be adjusted.
|
||||||
if self
|
if self
|
||||||
.items
|
.items
|
||||||
.last()
|
.trailing_text()
|
||||||
.and_then(Item::text)
|
|
||||||
.map(|s| s.cjk_justifiable_at_last())
|
.map(|s| s.cjk_justifiable_at_last())
|
||||||
.unwrap_or(false)
|
.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.
|
// Add a hyphen at the line start, if a previous dash should be repeated.
|
||||||
if let Some(pred) = pred
|
if let Some(pred) = pred
|
||||||
&& pred.dash == Some(Dash::Hard)
|
&& 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)
|
&& should_repeat_hyphen(base.lang, full)
|
||||||
&& let Some(hyphen) =
|
&& let Some(hyphen) =
|
||||||
ShapedText::hyphen(engine, p.config.fallback, base, trim, false)
|
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.
|
// Add a hyphen at the line end, if we ended on a soft hyphen.
|
||||||
if dash == Some(Dash::Soft)
|
if dash == Some(Dash::Soft)
|
||||||
&& let Some(base) = items.last_text()
|
&& let Some(base) = items.trailing_text()
|
||||||
&& let Some(hyphen) =
|
&& let Some(hyphen) =
|
||||||
ShapedText::hyphen(engine, p.config.fallback, base, trim, true)
|
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.
|
// 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();
|
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.
|
/// Add spacing around punctuation marks for CJ glyphs at the line start.
|
||||||
fn adjust_cj_at_line_start(p: &Preparation, items: &mut Items) {
|
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 };
|
let Some(glyph) = shaped.glyphs.first() else { return };
|
||||||
|
|
||||||
if glyph.is_cjk_right_aligned_punctuation() {
|
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.
|
/// Add spacing around punctuation marks for CJ glyphs at the line end.
|
||||||
fn adjust_cj_at_line_end(p: &Preparation, items: &mut Items) {
|
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 };
|
let Some(glyph) = shaped.glyphs.last() else { return };
|
||||||
|
|
||||||
// Deal with CJK punctuation at line ends.
|
// Deal with CJK punctuation at line ends.
|
||||||
@ -481,7 +480,7 @@ pub fn commit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle hanging punctuation to the left.
|
// 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()
|
&& let Some(glyph) = text.glyphs.first()
|
||||||
&& !text.dir.is_positive()
|
&& !text.dir.is_positive()
|
||||||
&& text.styles.get(TextElem::overhang)
|
&& text.styles.get(TextElem::overhang)
|
||||||
@ -493,7 +492,7 @@ pub fn commit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle hanging punctuation to the right.
|
// 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()
|
&& let Some(glyph) = text.glyphs.last()
|
||||||
&& text.dir.is_positive()
|
&& text.dir.is_positive()
|
||||||
&& text.styles.get(TextElem::overhang)
|
&& text.styles.get(TextElem::overhang)
|
||||||
@ -685,7 +684,7 @@ impl<'a> Items<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the items.
|
/// 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)
|
self.0.iter().map(|(_, item)| &**item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,33 +693,30 @@ impl<'a> Items<'a> {
|
|||||||
///
|
///
|
||||||
/// Note that this is different from `.iter().enumerate()` which would
|
/// Note that this is different from `.iter().enumerate()` which would
|
||||||
/// provide the indices in visual order!
|
/// 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()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the first item.
|
/// Access the first item (skipping tags), if it is text.
|
||||||
pub fn first(&self) -> Option<&Item<'a>> {
|
pub fn leading_text(&self) -> Option<&ShapedText<'a>> {
|
||||||
self.0.first().map(|(_, item)| &**item)
|
self.0.iter().find(|(_, item)| !item.is_tag())?.1.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the last item.
|
/// Access the first item (skipping tags) mutably, if it is text.
|
||||||
pub fn last(&self) -> Option<&Item<'a>> {
|
pub fn leading_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
||||||
self.0.last().map(|(_, item)| &**item)
|
self.0.iter_mut().find(|(_, item)| !item.is_tag())?.1.text_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the last item, if it is text.
|
/// Access the last item (skipping tags), if it is text.
|
||||||
pub fn last_text(&self) -> Option<&ShapedText<'a>> {
|
pub fn trailing_text(&self) -> Option<&ShapedText<'a>> {
|
||||||
self.0.last()?.1.text()
|
self.0.iter().rev().find(|(_, item)| !item.is_tag())?.1.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the first item mutably, if it is text.
|
/// Access the last item (skipping tags) mutably, if it is text.
|
||||||
pub fn first_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
pub fn trailing_text_mut(&mut self) -> Option<&mut ShapedText<'a>> {
|
||||||
self.0.first_mut()?.1.text_mut()
|
self.0.iter_mut().rev().find(|(_, item)| !item.is_tag())?.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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reorder the items starting at the given index to RTL.
|
/// Reorder the items starting at the given index to RTL.
|
||||||
|
BIN
tests/ref/issue-hyphenate-after-tag.png
Normal file
BIN
tests/ref/issue-hyphenate-after-tag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 674 B |
@ -171,3 +171,12 @@ Hello-#text(red)[world]
|
|||||||
#set text(costs: (hyphenation: 1%, runt: 2%))
|
#set text(costs: (hyphenation: 1%, runt: 2%))
|
||||||
#set text(costs: (widow: 3%))
|
#set text(costs: (widow: 3%))
|
||||||
#context test(text.costs, (hyphenation: 1%, runt: 2%, widow: 3%, orphan: 100%))
|
#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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user