fix: Add special handling for apostrophe hyphenation (#1318)

## Summary

* **What is the goal of this PR?** Fixing / extending the hyphenation
logic to deal with words containing an apostophe as raised in #1186
* **What changes are included?**

## Additional Context

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_ (as the
user provided a thorough analysis that I followed)
This commit is contained in:
jpirnay
2026-03-11 18:35:23 -05:00
committed by GitHub
parent f1e9dc7f30
commit 3dabd30287
4 changed files with 142 additions and 25 deletions
+10 -4
View File
@@ -11,7 +11,8 @@ class Hyphenator {
struct BreakInfo {
size_t byteOffset; // Byte position inside the UTF-8 word where a break may occur.
bool requiresInsertedHyphen; // true = a visible '-' must be rendered at the break (pattern/fallback breaks).
// false = the word already contains a hyphen at this position (explicit '-').
// false = break occurs at an existing visible separator boundary
// (explicit '-' or eligible apostrophe contraction boundary).
};
// Returns byte offsets where the word may be hyphenated.
@@ -19,12 +20,17 @@ class Hyphenator {
// Break sources (in priority order):
// 1. Explicit hyphens already present in the word (e.g. '-' or soft-hyphen U+00AD).
// When found, language patterns are additionally run on each alphabetic segment
// between hyphens so compound words can break within their parts.
// between separators so compound words can break within their parts.
// Example: "US-Satellitensystems" yields breaks after "US-" (no inserted hyphen)
// plus pattern breaks inside "Satellitensystems" (Sa|tel|li|ten|sys|tems).
// 2. Language-specific Liang patterns (e.g. German de_patterns).
// 2. Apostrophe contractions between letters (e.g. all'improvviso).
// Liang patterns are run per alphabetic segment around apostrophes.
// A direct break at the apostrophe boundary is allowed only when the left
// segment has at least 3 letters and the right segment has at least 2 letters,
// avoiding short clitics (e.g. l', d') and short contraction tails (e.g. can't).
// 3. Language-specific Liang patterns (e.g. German de_patterns).
// Example: "Quadratkilometer" -> Qua|drat|ki|lo|me|ter.
// 3. Fallback every-N-chars splitting (only when includeFallback is true AND no
// 4. Fallback every-N-chars splitting (only when includeFallback is true AND no
// pattern breaks were found). Used as a last resort to prevent a single oversized
// word from overflowing the page width.
static std::vector<BreakInfo> breakOffsets(const std::string& word, bool includeFallback);