Module: Pdfrb::Layout::Hyphenation
- Defined in:
- lib/pdfrb/layout/hyphenation.rb
Overview
Knuth-Liang hyphenation algorithm, pure Ruby. Uses a small built-in pattern set covering common English patterns; callers can supply their own pattern list for other languages.
Patterns come from TeX's .pat files. Each pattern has letters and digits where the digits encode the hyphenation weights (0=never, 1-9=allowed). Example: ".abc1de" means "before 'd' in 'abcde' you may hyphenate".
The full English pattern set is ~62 KB. We ship a curated
subset; users who need full coverage can pass their own
patterns: at construction.
Constant Summary collapse
- PATTERNS_EN =
Curated subset covering common English word endings and prefixes. Not exhaustive but useful for plain prose. Numbers are weights; a weight of 1 means "may hyphenate here".
Loaded from data/pdfrb/layout/hyphenation_en.txt so the patterns stay reviewable; falls back to an empty list if the data file is missing.
begin path = File.("../../../data/pdfrb/layout/hyphenation_en.txt", __dir__) File.exist?(path) ? File.readlines(path, chomp: true).reject(&:empty?) : [] rescue StandardError [] end.freeze
Class Method Summary collapse
- .apply_pattern(weights, padded, pattern) ⇒ Object
-
.decode_pattern(pattern) ⇒ Object
Decode a Knuth-Liang pattern into [letters, weights].
-
.hyphenate_positions(word, patterns: PATTERNS_EN) ⇒ Array<Integer>
Positions in
wordwhere a hyphen may be inserted (between characters). -
.split(word, patterns: PATTERNS_EN, with_hyphen: true) ⇒ Object
Split
wordinto segments at valid hyphenation points. -
.word_weights(word, patterns) ⇒ Object
Compute the per-character weights by overlaying every matching pattern.
Class Method Details
.apply_pattern(weights, padded, pattern) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/pdfrb/layout/hyphenation.rb', line 105 def apply_pattern(weights, padded, pattern) letters, pat_weights = decode_pattern(pattern) start = 0 while (idx = padded.index(letters, start)) pat_weights.each_with_index do |w, i| next if w.zero? target = idx + i weights[target] = [weights[target], w].max if weights[target] end start = idx + 1 end end |
.decode_pattern(pattern) ⇒ Object
Decode a Knuth-Liang pattern into [letters, weights]. The letters are the alphabetic chars; the weights array has weights.length == letters.length + 1 (the slots between letters, including before and after).
91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/pdfrb/layout/hyphenation.rb', line 91 def decode_pattern(pattern) letters = +"" weights = [0] pattern.each_char do |ch| if /[0-9]/.match?(ch) weights[-1] = [weights[-1], ch.to_i].max else letters << ch weights << 0 end end [letters, weights] end |
.hyphenate_positions(word, patterns: PATTERNS_EN) ⇒ Array<Integer>
Returns positions in word where a hyphen
may be inserted (between characters).
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/pdfrb/layout/hyphenation.rb', line 38 def hyphenate_positions(word, patterns: PATTERNS_EN) return [] if word.length < 4 lowered = word.downcase.gsub(/[^a-z]/, "") return [] if lowered.length < 4 weights = word_weights(lowered, patterns) positions = [] # Odd weights between two characters mean a hyphen is allowed. # Skip first and last character per Knuth-Liang rule. (1...lowered.length).each do |i| next if i < 2 || i > lowered.length - 2 positions << i if weights[i].odd? end positions end |
.split(word, patterns: PATTERNS_EN, with_hyphen: true) ⇒ Object
Split word into segments at valid hyphenation points.
Includes the hyphen char (U+2010) on non-final segments when
with_hyphen: is true.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/pdfrb/layout/hyphenation.rb', line 59 def split(word, patterns: PATTERNS_EN, with_hyphen: true) positions = hyphenate_positions(word, patterns: patterns) return [word] if positions.empty? segments = [] prev = 0 positions.each do |pos| segment = word[prev...pos] || "" segment += "‐" if with_hyphen segments << segment prev = pos end segments << (word[prev..] || "") segments end |
.word_weights(word, patterns) ⇒ Object
Compute the per-character weights by overlaying every matching pattern. The weight at position i in the word is the max across all matching patterns at that inter-character slot.
78 79 80 81 82 83 84 85 |
# File 'lib/pdfrb/layout/hyphenation.rb', line 78 def word_weights(word, patterns) padded = ".#{word}." weights = ::Array.new(word.length + 1, 0) patterns.each do |pattern| apply_pattern(weights, padded, pattern) end weights end |