diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-09-19 10:28:06 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-09-19 10:28:06 +0200 |
| commit | b98004330bf3ef372a0f0ef5c7daf1f96fd7873a (patch) | |
| tree | 693b78cabbbbb5424b538be9cd79544fa1f48569 | |
| parent | 0d12f2ab23177642eef2e6bb9c583cdd0c743b33 (diff) | |
Handle non-breaking spaces during justification
| -rw-r--r-- | src/library/text/shaping.rs | 21 | ||||
| -rw-r--r-- | tests/ref/text/linebreak.png | bin | 18957 -> 18981 bytes | |||
| -rw-r--r-- | tests/ref/text/shorthands.png | bin | 4375 -> 4842 bytes | |||
| -rw-r--r-- | tests/typ/text/shorthands.typ | 8 | ||||
| -rw-r--r-- | tools/support/typst.tmLanguage.json | 4 |
5 files changed, 25 insertions, 8 deletions
diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 9465fbd9..1bac626d 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -58,12 +58,12 @@ pub struct ShapedGlyph { impl ShapedGlyph { /// Whether the glyph is a space. pub fn is_space(&self) -> bool { - self.c == ' ' + matches!(self.c, ' ' | '\u{00A0}' | ' ') } /// Whether the glyph is justifiable. pub fn is_justifiable(&self) -> bool { - matches!(self.c, ' ' | ',' | ' ' | '。' | '、') + self.is_space() || matches!(self.c, ',' | '。' | '、') } } @@ -508,12 +508,14 @@ fn track_and_space(ctx: &mut ShapingContext) { .get(TextNode::SPACING) .map(|abs| Em::from_length(abs, ctx.size)); - if tracking.is_zero() && spacing.is_one() { - return; - } - let mut glyphs = ctx.glyphs.iter_mut().peekable(); while let Some(glyph) = glyphs.next() { + // Make non-breaking space same width as normal space. + if glyph.c == '\u{00A0}' { + let face = ctx.fonts.get(glyph.face_id); + glyph.x_advance -= nbsp_delta(face).unwrap_or_default(); + } + if glyph.is_space() { glyph.x_advance = spacing.relative_to(glyph.x_advance); } @@ -524,6 +526,13 @@ fn track_and_space(ctx: &mut ShapingContext) { } } +/// Difference between non-breaking and normal space. +fn nbsp_delta(face: &Face) -> Option<Em> { + let space = face.ttf().glyph_index(' ')?.0; + let nbsp = face.ttf().glyph_index('\u{00A0}')?.0; + Some(face.advance(nbsp)? - face.advance(space)?) +} + /// Resolve the font variant with `BOLD` and `ITALIC` factored in. pub fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( diff --git a/tests/ref/text/linebreak.png b/tests/ref/text/linebreak.png Binary files differindex 1a3f49df..77eef20e 100644 --- a/tests/ref/text/linebreak.png +++ b/tests/ref/text/linebreak.png diff --git a/tests/ref/text/shorthands.png b/tests/ref/text/shorthands.png Binary files differindex c21d49df..ddb679f5 100644 --- a/tests/ref/text/shorthands.png +++ b/tests/ref/text/shorthands.png diff --git a/tests/typ/text/shorthands.typ b/tests/typ/text/shorthands.typ index 2efd1ad1..5c94dab0 100644 --- a/tests/typ/text/shorthands.typ +++ b/tests/typ/text/shorthands.typ @@ -4,6 +4,14 @@ The non-breaking~space does work. --- +// Make sure non-breaking and normal space always +// have the same width. Even if the font decided +// differently. +#set text("Latin Modern Roman") +a b \ +a~b + +--- - En dash: -- - Em dash: --- diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 8e1a66c4..36ffade7 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -128,7 +128,7 @@ "captures": { "1": { "name": "punctuation.definition.label.typst" } } }, { - "begin": "(#)(pub|let|set|show|wrap)\\b", + "begin": "(#)(pub|let|set|rule|select|show|wrap)\\b", "end": "\n|(;)|(?=])", "beginCaptures": { "0": { "name": "keyword.other.typst" }, @@ -239,7 +239,7 @@ }, { "name": "keyword.other.typst", - "match": "\\b(pub|let|set|show|wrap|as|in|from)\\b" + "match": "\\b(pub|let|set|rule|select|show|wrap|as|in|from)\\b" }, { "name": "keyword.control.conditional.typst", |
