Class: Crawdad::Paragraph
- Inherits:
-
Object
- Object
- Crawdad::Paragraph
- Includes:
- Tokens
- Defined in:
- lib/crawdad/paragraph.rb,
lib/crawdad/ffi/paragraph.rb
Defined Under Namespace
Modules: C
Constant Summary
Constants included from Tokens
Instance Attribute Summary collapse
-
#width ⇒ Object
Width of the paragraph of text.
Instance Method Summary collapse
-
#adjustment_ratio(node_a, b) ⇒ Object
Calculates the adjustment ratio r by which a line from a to b would have to be adjusted to fit in the given length.
-
#each_legal_breakpoint ⇒ Object
For each item before which we could break, yields two values:.
-
#initialize(stream, options = {}) ⇒ Paragraph
constructor
A new instance of Paragraph.
-
#lines(threshold = 5) ⇒ Object
Returns an array of optimally sized lines.
- #optimum_breakpoints(threshold = 5) ⇒ Object
Methods included from Tokens
#box, #box_content, #glue, #glue_shrink, #glue_stretch, #penalty, #penalty_flagged?, #penalty_penalty, #token_type, #token_width
Constructor Details
#initialize(stream, options = {}) ⇒ Paragraph
Returns a new instance of Paragraph.
14 15 16 17 18 19 |
# File 'lib/crawdad/paragraph.rb', line 14 def initialize(stream, ={}) @stream = stream @width = [:width] @flagged_penalty = [:flagged_penalty] || 3000 @fitness_penalty = [:fitness_penalty] || 100 end |
Instance Attribute Details
#width ⇒ Object
Width of the paragraph of text.
23 24 25 |
# File 'lib/crawdad/paragraph.rb', line 23 def width @width end |
Instance Method Details
#adjustment_ratio(node_a, b) ⇒ Object
Calculates the adjustment ratio r by which a line from a to b would have to be adjusted to fit in the given length. r==0 means the natural widths are perfect. r==-1 means all of the shrinkability has been used; r==1 means all of the stretchability has been used.
Arguments:
node_a
-
Breakpoint node of our starting point (on the active list).
b
-
Index (into
stream
) of the breakpoint under consideration.
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/crawdad/paragraph.rb', line 173 def adjustment_ratio(node_a, b) item_b = @stream[b] # Find the width from a to b. w = @total_width - node_a.total_width # Add penalty width (hyphen) if we are breaking at a penalty w += token_width(item_b) if token_type(item_b) == :penalty target_width = @width case when w < target_width stretch = @total_stretch - node_a.total_stretch (stretch > 0) ? (target_width - w) / stretch.to_f : Infinity when w > target_width shrink = @total_shrink - node_a.total_shrink (shrink > 0) ? (target_width - w) / shrink.to_f : Infinity else 0 end end |
#each_legal_breakpoint ⇒ Object
For each item before which we could break, yields two values:
item
-
The item we can break before (glue or penalty).
i
-
The index of
item
in the stream.
Updates the @total_width, @total_stretch, and @total_shrink variables as it moves over the stream, to allow quick calculation of the width/stretch/shrink from the last breakpoint node.
Legal breakpoints are either:
-
glue immediately following a box, or
-
a penalty less than positive infinity.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/crawdad/paragraph.rb', line 138 def each_legal_breakpoint @total_width = 0 @total_stretch = 0 @total_shrink = 0 @stream.each_with_index do |item, i| case token_type(item) when :box @total_width += token_width(item) when :glue # We can break here if we immediately follow a box. yield(item, i) if token_type(@stream[i-1]) == :box @total_width += token_width(item) @total_stretch += glue_stretch(item) @total_shrink += glue_shrink(item) when :penalty # We can break here unless inhibited by an infinite penalty. yield(item, i) unless penalty_penalty(item) == Infinity else raise ArgumentError, "Unknown item: #{item.inspect}" end end end |
#lines(threshold = 5) ⇒ Object
Returns an array of optimally sized lines. Each line in the array consists of two elements [tokens, breakpoint]. tokens
is an array of tokens taken sequentially from the input stream. breakpoint
is a Crawdad::Breakpoint object representing data about the line (primarily the adjustment ratio).
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/crawdad/paragraph.rb', line 30 def lines(threshold=5) ls = [] breakpoints = optimum_breakpoints(threshold) # When we break on penalties, we want them to show up at the *end* of the # line so that we can put hyphens there if needed. So adjust the # breakpoint positions to make that the case. breakpoints.each do |b| b.position += 1 if token_type(@stream[b.position]) == :penalty end breakpoints.each_cons(2) do |a, b| last = (b == breakpoints[-1]) ? b.position : b.position - 1 ls << [@stream[a.position..last], b] end ls end |
#optimum_breakpoints(threshold = 5) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/crawdad/paragraph.rb', line 48 def optimum_breakpoints(threshold=5) active_nodes = [Breakpoint.starting_node] each_legal_breakpoint do |item, bi| # "Main Loop" (Digital Typography p. 118) if active_nodes.empty? raise "No feasible solution. Try relaxing threshold." end ai = 0 while active_nodes[ai] # For each fitness class, keep track of the nodes with the fewest # demerits so far. best = [nil] * 4 while a = active_nodes[ai] j = a.line + 1 # current line r = adjustment_ratio(a, bi) if r < -1 || (token_type(item) == :penalty && penalty_penalty(item) == -Infinity && a.position < @stream.length - 1) active_nodes.delete_at(ai) else ai += 1 end if r >= -1 && r <= threshold d = calculate_demerits(r, item, a) + a.total_demerits c = self.class.fitness_class(r) # Penalize consecutive lines more than one fitness class away from # each other. if (c - a.fitness_class).abs > 1 d += @fitness_penalty end # Update high scores if this is a new best. if best[c].nil? || d < best[c][:demerits] best[c] = {:node => a, :demerits => d, :ratio => r} end end # Add nodes to the active list before moving to the next line. if (next_node = active_nodes[ai]) && next_node.line >= j break end end # If we found any best nodes, add them to the active list. if ai && ai < active_nodes.length - 1 active_nodes[ai, 0] = new_active_nodes(best, bi) else active_nodes.concat new_active_nodes(best, bi) end end end # At this point, everything in active_nodes should point to the final # element of our stream (the forced break). Now we pick the one with the # fewest total demerits. node = active_nodes.sort_by { |n| n.total_demerits }.first nodes = [] begin nodes.unshift(node) end while node = node.previous nodes end |