summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/layout/par.rs6
-rw-r--r--library/src/text/shaping.rs114
-rw-r--r--tests/ref/layout/par-justify-cjk.pngbin43928 -> 120972 bytes
-rw-r--r--tests/ref/layout/par-justify.pngbin12441 -> 41627 bytes
-rw-r--r--tests/typ/layout/par-justify-cjk.typ13
-rw-r--r--tests/typ/layout/par-justify.typ6
6 files changed, 106 insertions, 33 deletions
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index 5d33de40..29a82a01 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -906,7 +906,7 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
}
// Determine the cost of the line.
- let min_ratio = if attempt.justify { MIN_RATIO } else { 0.0 };
+ let min_ratio = if p.justify { MIN_RATIO } else { 0.0 };
let mut cost = if ratio < min_ratio {
// The line is overfull. This is the case if
// - justification is on, but we'd need to shrink too much
@@ -920,7 +920,9 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
// all breakpoints before this one become inactive since no line
// can span above the mandatory break.
active = k;
- if attempt.justify {
+ // If ratio > 0, we need to stretch the line only when justify is needed.
+ // If ratio < 0, we always need to shrink the line.
+ if (ratio > 0.0 && attempt.justify) || ratio < 0.0 {
ratio.powi(3).abs()
} else {
0.0
diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs
index 50c4bcbd..1e1ccc99 100644
--- a/library/src/text/shaping.rs
+++ b/library/src/text/shaping.rs
@@ -63,6 +63,14 @@ pub struct ShapedGlyph {
pub offset: u16,
}
+#[derive(Debug, Clone, Default)]
+pub struct Adjustability {
+ /// The left and right strechability
+ pub stretchability: (Em, Em),
+ /// The left and right shrinkability
+ pub shrinkability: (Em, Em),
+}
+
impl ShapedGlyph {
/// Whether the glyph is a space.
pub fn is_space(&self) -> bool {
@@ -71,41 +79,72 @@ impl ShapedGlyph {
/// Whether the glyph is justifiable.
pub fn is_justifiable(&self) -> bool {
- self.is_space() || self.is_cjk() || self.is_cjk_punctuation()
+ self.is_space()
+ || self.is_cjk()
+ || self.is_cjk_left_aligned_punctuation()
+ || self.is_cjk_right_aligned_punctuation()
}
pub fn is_cjk(&self) -> bool {
use Script::*;
- matches!(self.c.script(), Hiragana | Katakana | Han)
+ // U+30FC: Katakana-Hiragana Prolonged Sound Mark
+ matches!(self.c.script(), Hiragana | Katakana | Han) || self.c == '\u{30FC}'
}
- pub fn is_cjk_punctuation(&self) -> bool {
- matches!(self.c, ',' | '。' | '、' | ':' | ';')
+ /// See https://www.w3.org/TR/clreq/#punctuation_width_adjustment
+ pub fn is_cjk_left_aligned_punctuation(&self) -> bool {
+ // CJK quotation marks shares codepoints with latin quotation marks.
+ // But only the CJK ones have full width.
+ if matches!(self.c, '”' | '’') && self.x_advance == Em::one() {
+ return true;
+ }
+
+ matches!(self.c, ',' | '。' | '、' | ':' | ';' | '》' | ')' | '』' | '」')
}
- /// The stretchability of the character.
- pub fn stretchability(&self) -> Em {
- let width = self.x_advance;
- if self.is_space() {
- // The number for spaces is from Knuth-Plass' paper
- width / 2.0
- } else {
- Em::zero()
+ /// See https://www.w3.org/TR/clreq/#punctuation_width_adjustment
+ pub fn is_cjk_right_aligned_punctuation(&self) -> bool {
+ // CJK quotation marks shares codepoints with latin quotation marks.
+ // But only the CJK ones have full width.
+ if matches!(self.c, '“' | '‘') && self.x_advance == Em::one() {
+ return true;
}
+
+ matches!(self.c, '《' | '(' | '『' | '「')
}
- /// The shrinkability of the character.
- pub fn shrinkability(&self) -> Em {
+ pub fn adjustability(&self) -> Adjustability {
let width = self.x_advance;
if self.is_space() {
- // The number for spaces is from Knuth-Plass' paper
- width / 3.0
- } else if self.is_cjk_punctuation() {
- width / 2.0
+ Adjustability {
+ // The number for spaces is from Knuth-Plass' paper
+ stretchability: (Em::zero(), width / 2.0),
+ shrinkability: (Em::zero(), width / 3.0),
+ }
+ } else if self.is_cjk_left_aligned_punctuation() {
+ Adjustability {
+ stretchability: (Em::zero(), Em::zero()),
+ shrinkability: (Em::zero(), width / 2.0),
+ }
+ } else if self.is_cjk_right_aligned_punctuation() {
+ Adjustability {
+ stretchability: (Em::zero(), Em::zero()),
+ shrinkability: (width / 2.0, Em::zero()),
+ }
} else {
- Em::zero()
+ Adjustability::default()
}
}
+
+ /// The stretchability of the character.
+ pub fn stretchability(&self) -> (Em, Em) {
+ self.adjustability().stretchability
+ }
+
+ /// The shrinkability of the character.
+ pub fn shrinkability(&self) -> (Em, Em) {
+ self.adjustability().shrinkability
+ }
}
/// A side you can go toward.
@@ -146,20 +185,33 @@ impl<'a> ShapedText<'a> {
let glyphs = group
.iter()
.map(|glyph| {
- let mut justification = Em::zero();
- if justification_ratio < 0.0 {
- justification += glyph.shrinkability() * justification_ratio
+ let adjustability_left = if justification_ratio < 0.0 {
+ glyph.shrinkability().0
} else {
- justification += glyph.stretchability() * justification_ratio
- }
+ glyph.stretchability().0
+ };
+ let adjustability_right = if justification_ratio < 0.0 {
+ glyph.shrinkability().1
+ } else {
+ glyph.stretchability().1
+ };
+ let justification_left = adjustability_left * justification_ratio;
+
+ let mut justification_right =
+ adjustability_right * justification_ratio;
if glyph.is_justifiable() {
- justification += Em::from_length(extra_justification, self.size)
+ justification_right +=
+ Em::from_length(extra_justification, self.size)
}
- frame.size_mut().x += justification.at(self.size);
+
+ frame.size_mut().x += justification_left.at(self.size)
+ + justification_right.at(self.size);
Glyph {
id: glyph.glyph_id,
- x_advance: glyph.x_advance + justification,
- x_offset: glyph.x_offset,
+ x_advance: glyph.x_advance
+ + justification_left
+ + justification_right,
+ x_offset: glyph.x_offset + justification_left,
c: glyph.c,
span: glyph.span,
offset: glyph.offset,
@@ -242,7 +294,7 @@ impl<'a> ShapedText<'a> {
pub fn cjk_justifiable_at_last(&self) -> bool {
self.glyphs
.last()
- .map(|g| g.is_cjk() || g.is_cjk_punctuation())
+ .map(|g| g.is_cjk() || g.is_cjk_left_aligned_punctuation())
.unwrap_or(false)
}
@@ -250,7 +302,7 @@ impl<'a> ShapedText<'a> {
pub fn stretchability(&self) -> Abs {
self.glyphs
.iter()
- .map(|g| g.stretchability())
+ .map(|g| g.stretchability().0 + g.stretchability().1)
.sum::<Em>()
.at(self.size)
}
@@ -259,7 +311,7 @@ impl<'a> ShapedText<'a> {
pub fn shrinkability(&self) -> Abs {
self.glyphs
.iter()
- .map(|g| g.shrinkability())
+ .map(|g| g.shrinkability().0 + g.shrinkability().1)
.sum::<Em>()
.at(self.size)
}
diff --git a/tests/ref/layout/par-justify-cjk.png b/tests/ref/layout/par-justify-cjk.png
index ee58318a..5efcc1e1 100644
--- a/tests/ref/layout/par-justify-cjk.png
+++ b/tests/ref/layout/par-justify-cjk.png
Binary files differ
diff --git a/tests/ref/layout/par-justify.png b/tests/ref/layout/par-justify.png
index a16c3f7b..9f92034f 100644
--- a/tests/ref/layout/par-justify.png
+++ b/tests/ref/layout/par-justify.png
Binary files differ
diff --git a/tests/typ/layout/par-justify-cjk.typ b/tests/typ/layout/par-justify-cjk.typ
index 1016b282..17f2d1e5 100644
--- a/tests/typ/layout/par-justify-cjk.typ
+++ b/tests/typ/layout/par-justify-cjk.typ
@@ -20,4 +20,17 @@
ウィキペディア(英: Wikipedia)は、世界中のボランティアの共同作業によって執筆及び作成されるフリーの多言語インターネット百科事典である。主に寄付に依って活動している非営利団体「ウィキメディア財団」が所有・運営している。
専門家によるオンライン百科事典プロジェクトNupedia(ヌーペディア)を前身として、2001年1月、ラリー・サンガーとジミー・ウェールズ(英: Jimmy Donal "Jimbo" Wales)により英語でプロジェクトが開始された。
+]
+
+---
+// Test punctuation whitespace adjustment
+#set page(width: auto)
+#set text(lang: "zh", font: "Noto Serif CJK SC", overhang: false)
+#set par(justify: true)
+#rect(inset: 0pt, width: 80pt, fill: rgb("eee"))[
+ “引号测试”,还,
+
+ 《书名》《测试》下一行
+
+ 《书名》《测试》。
] \ No newline at end of file
diff --git a/tests/typ/layout/par-justify.typ b/tests/typ/layout/par-justify.typ
index 7034a42b..131eadfa 100644
--- a/tests/typ/layout/par-justify.typ
+++ b/tests/typ/layout/par-justify.typ
@@ -25,3 +25,9 @@ D E F #linebreak(justify: true)
// basically empty paragraph.
#set par(justify: true)
#""
+
+---
+// Test that the last line can be shrinked
+#set page(width: 155pt)
+#set par(justify: true)
+This text can be fitted in one line. \ No newline at end of file