Module: Pdfrb::Layout::JustificationKashidas

Defined in:
lib/pdfrb/layout/justification_kashidas.rb

Overview

Arabic kashida justification: stretches Arabic letters using the tatweel (U+0640) character to fill a line, instead of (or in addition to) inter-word spacing. The technique inserts tatweel after selected letters (those with right-joining behaviour) to grow the line width toward the target.

Constant Summary collapse

RIGHT_JOINING =

Arabic letters with right-joining behaviour (kashida can be inserted after these). Subset — covers the most common letters. Full list per Unicode Arabic Joining_Type.

%w[
  ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي
  ـب ـت ـث ـج ـح ـخ ـس ـش ـص ـض ـط ـظ ـع ـغ ـف ـق ـك ـل ـم ـن ـه
].freeze
TATWEEL =
"ـ"

Class Method Summary collapse

Class Method Details

.insert_at(string, pos, char) ⇒ Object



67
68
69
# File 'lib/pdfrb/layout/justification_kashidas.rb', line 67

def insert_at(string, pos, char)
  string[0...pos] + char + string[pos..]
end

.justify(text, target_width:, measure:) ⇒ Object

Insert kashidas into text to grow it to target_width using measure (a callable that returns the width of a string). Returns the modified string. If the natural width already meets target_width, returns text unchanged.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/pdfrb/layout/justification_kashidas.rb', line 27

def justify(text, target_width:, measure:)
  return text if text.to_s.empty?

  natural = measure.call(text)
  return text if natural >= target_width

  candidates = kashida_insertion_points(text)
  return text if candidates.empty?

  # Greedy: add one tatweel per candidate until target reached.
  result = text.to_s.dup
  added = 0
  loop do
    break if natural >= target_width
    break if added >= candidates.length * 3 # cap to avoid runaway

    pos = candidates[added % candidates.length]
    result = insert_at(result, pos + added, TATWEEL)
    added += 1
    natural = measure.call(result)
  end
  result
end

.kashida_insertion_points(text) ⇒ Object

Indices in text where a tatweel may be inserted (after a right-joining letter that's not already followed by a space).



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/pdfrb/layout/justification_kashidas.rb', line 53

def kashida_insertion_points(text)
  points = []
  prev = nil
  text.to_s.each_char.with_index do |ch, i|
    next_char = text.to_s[i + 1]
    if RIGHT_JOINING.include?(ch) && next_char &&
        RIGHT_JOINING.include?(next_char)
      points << (i + 1)
    end
    prev = ch
  end
  points
end