diff options
Diffstat (limited to 'library/src/layout')
| -rw-r--r-- | library/src/layout/par.rs | 73 |
1 files changed, 58 insertions, 15 deletions
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 2edbd93d..96d45f2f 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -8,7 +8,8 @@ use crate::layout::AlignElem; use crate::math::EquationElem; use crate::prelude::*; use crate::text::{ - shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartQuoteElem, SpaceElem, TextElem, + is_gb_style, shape, LinebreakElem, Quoter, Quotes, ShapedText, SmartQuoteElem, + SpaceElem, TextElem, }; /// Arrange text, spacing and inline-level elements into a paragraph. @@ -354,6 +355,13 @@ impl<'a> Item<'a> { } } + fn text_mut(&mut self) -> Option<&mut ShapedText<'a>> { + match self { + Self::Text(shaped) => Some(shaped), + _ => None, + } + } + /// The text length of the item. fn len(&self) -> usize { match self { @@ -715,9 +723,12 @@ fn shape_range<'a>( spans: &SpanMapper, styles: StyleChain<'a>, ) { + let lang = TextElem::lang_in(styles); + let region = TextElem::region_in(styles); let mut process = |range: Range, level: BidiLevel| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; - let shaped = shape(vt, range.start, &bidi.text[range], spans, styles, dir); + let shaped = + shape(vt, range.start, &bidi.text[range], spans, styles, dir, lang, region); items.push(Item::Text(shaped)); }; @@ -905,15 +916,11 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L // This often happens with monospace fonts and CJK texts. ratio = 0.0; } - if ratio.is_infinite() { - // The line's not stretchable, we calculate the ratio in another way... - ratio = delta / (em / 2.0); - // ...and because it is underfull/overfull, make sure the ratio is at least 1.0. - if ratio > 0.0 { - ratio += 1.0; - } else { - ratio -= 1.0; - } + if ratio > 1.0 { + // We should stretch the line above its stretchability. Now calculate the extra amount. + let extra_stretch = (delta - adjust) / attempt.justifiables() as f64; + // Normalize the amount by half Em size. + ratio = 1.0 + extra_stretch / (em / 2.0); } // Determine the cost of the line. @@ -1124,7 +1131,9 @@ fn line<'a>( let base = expanded.end - shaped.text.len(); let start = range.start.max(base); let text = &p.bidi.text[start..range.end]; - let trimmed = text.trim_end(); + // U+200B ZERO WIDTH SPACE is used to provide a line break opportunity, + // we want to trim it too. + let trimmed = text.trim_end().trim_end_matches('\u{200B}'); range.end = start + trimmed.len(); // Deal with hyphens, dashes and justification. @@ -1132,6 +1141,11 @@ fn line<'a>( dash = hyphen || shy || trimmed.ends_with(['-', '–', '—']); justify |= text.ends_with('\u{2028}'); + // Deal with CJK punctuation at line ends. + let gb_style = is_gb_style(shaped.lang, shaped.region); + let end_cjk_punct = trimmed + .ends_with(['”', '’', ',', '。', '、', ':', ';', '》', ')', '』', '」']); + // Usually, we don't want to shape an empty string because: // - We don't want the height of trimmed whitespace in a different // font to be considered for the line height. @@ -1141,12 +1155,21 @@ fn line<'a>( // need the shaped empty string to make the line the appropriate // height. That is the case exactly if the string is empty and there // are no other items in the line. - if hyphen || start + shaped.text.len() > range.end { + if hyphen || start + shaped.text.len() > range.end || end_cjk_punct { if hyphen || start < range.end || before.is_empty() { let mut reshaped = shaped.reshape(vt, &p.spans, start..range.end); if hyphen || shy { reshaped.push_hyphen(vt); } + let punct = reshaped.glyphs.last(); + if let Some(punct) = punct { + if punct.is_cjk_left_aligned_punctuation(gb_style) { + let shrink_amount = punct.shrinkability().1; + let punct = reshaped.glyphs.to_mut().last_mut().unwrap(); + punct.shrink_right(shrink_amount); + reshaped.width -= shrink_amount.at(reshaped.size); + } + } width += reshaped.width; last = Some(Item::Text(reshaped)); } @@ -1155,6 +1178,10 @@ fn line<'a>( } } + // Deal with CJK punctuation at line starts. + let text = &p.bidi.text[range.start..end]; + let start_cjk_punct = text.starts_with(['“', '‘', '《', '(', '『', '「']); + // Reshape the start item if it's split in half. let mut first = None; if let Some((Item::Text(shaped), after)) = inner.split_first() { @@ -1163,8 +1190,8 @@ fn line<'a>( let end = range.end.min(base + shaped.text.len()); // Reshape if necessary. - if range.start + shaped.text.len() > end { - if range.start < end { + if range.start + shaped.text.len() > end || start_cjk_punct { + if range.start < end || start_cjk_punct { let reshaped = shaped.reshape(vt, &p.spans, range.start..end); width += reshaped.width; first = Some(Item::Text(reshaped)); @@ -1174,6 +1201,22 @@ fn line<'a>( } } + if start_cjk_punct { + let reshaped = first.as_mut().or(last.as_mut()).and_then(Item::text_mut); + if let Some(reshaped) = reshaped { + if let Some(punct) = reshaped.glyphs.first() { + if punct.is_cjk_right_aligned_punctuation() { + let shrink_amount = punct.shrinkability().0; + let punct = reshaped.glyphs.to_mut().first_mut().unwrap(); + punct.shrink_left(shrink_amount); + let amount_abs = shrink_amount.at(reshaped.size); + reshaped.width -= amount_abs; + width -= amount_abs; + } + } + } + } + // Measure the inner items. for item in inner { width += item.width(); |
