aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Hennebert <vhennebert@apache.org>2008-05-23 14:24:30 +0000
committerVincent Hennebert <vhennebert@apache.org>2008-05-23 14:24:30 +0000
commitc0441a653cd9e69f772ca25f2cd6a8e2979c242b (patch)
tree48687c23cd0045572d08e0da87faa88330ffbf29
parent5d3ea08e21e09c1913f1a6e4825cc5523edeb0f3 (diff)
downloadxmlgraphics-fop-c0441a653cd9e69f772ca25f2cd6a8e2979c242b.tar.gz
xmlgraphics-fop-c0441a653cd9e69f772ca25f2cd6a8e2979c242b.zip
A prototype implementation aimed at illustrating the interleaved page and line breaking algorithm
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_Interleaved_Page_Line_Breaking@659548 13f79535-47bb-0310-9956-ffa450edef68
-rwxr-xr-xprototype/prototype.rb996
1 files changed, 996 insertions, 0 deletions
diff --git a/prototype/prototype.rb b/prototype/prototype.rb
new file mode 100755
index 000000000..e9045cce1
--- /dev/null
+++ b/prototype/prototype.rb
@@ -0,0 +1,996 @@
+#!/usr/bin/ruby -w
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# $Id$
+
+$KCODE = 'UTF8'
+
+require 'logger'
+require 'jcode'
+
+# Taken from the AFM metrics file of the Nimbus Roman No9 L font, which has metrics
+# compatible with the Times font that will be used by dot to produce graphics
+TimesCharWidths = {
+ ' ' => 250,
+ '!' => 333,
+ '#' => 500,
+ '$' => 500,
+ '%' => 833,
+ '&' => 778,
+ '”' => 333,
+ '(' => 333,
+ ')' => 333,
+ '*' => 500,
+ '+' => 564,
+ ',' => 250,
+ '-' => 333,
+ '.' => 250,
+ '/' => 278,
+ '0' => 500,
+ '1' => 500,
+ '2' => 500,
+ '3' => 500,
+ '4' => 500,
+ '5' => 500,
+ '6' => 500,
+ '7' => 500,
+ '8' => 500,
+ '9' => 500,
+ ':' => 278,
+ ';' => 278,
+ '<' => 564,
+ '=' => 564,
+ '>' => 564,
+ '?' => 444,
+ '@' => 921,
+ 'A' => 722,
+ 'B' => 667,
+ 'C' => 667,
+ 'D' => 722,
+ 'E' => 611,
+ 'F' => 556,
+ 'G' => 722,
+ 'H' => 722,
+ 'I' => 333,
+ 'J' => 389,
+ 'K' => 722,
+ 'L' => 611,
+ 'M' => 889,
+ 'N' => 722,
+ 'O' => 722,
+ 'P' => 556,
+ 'Q' => 722,
+ 'R' => 667,
+ 'S' => 556,
+ 'T' => 611,
+ 'U' => 722,
+ 'V' => 722,
+ 'W' => 944,
+ 'X' => 722,
+ 'Y' => 722,
+ 'Z' => 611,
+ '_' => 500,
+ '“' => 333,
+ 'a' => 444,
+ 'b' => 500,
+ 'c' => 444,
+ 'd' => 500,
+ 'e' => 444,
+ 'f' => 333,
+ 'g' => 500,
+ 'h' => 500,
+ 'i' => 278,
+ 'j' => 278,
+ 'k' => 500,
+ 'l' => 278,
+ 'm' => 778,
+ 'n' => 500,
+ 'o' => 500,
+ 'p' => 500,
+ 'q' => 500,
+ 'r' => 333,
+ 's' => 389,
+ 't' => 278,
+ 'u' => 500,
+ 'v' => 500,
+ 'w' => 722,
+ 'x' => 500,
+ 'y' => 500,
+ 'z' => 444}
+
+def assert(test)
+ raise "AssertionFailedError" unless test
+end
+
+$log = Logger.new(STDERR)
+$log.level = Logger::DEBUG
+$log.formatter = Logger::Formatter.new
+class <<$log.formatter
+ def call(severity, time, progname, msg)
+ msg + "\n"
+ end
+end
+
+class KnuthElement
+ attr_reader :w
+
+ def initialize(w = 0, content = "")
+ @w = w
+ @content = content
+ end
+
+ def to_s
+ if @content
+ " \"#{@content}\""
+ else
+ ""
+ end
+ end
+
+ def getLabel
+ @content
+ end
+end
+
+class Box < KnuthElement
+ def initialize(w, content)
+ super(w, content)
+ end
+
+ def to_s
+ return "Box w = #{@w}" + super
+ end
+end
+
+class Glue < KnuthElement
+ attr_reader :stretch, :shrink
+ def initialize(w, stretch = 0, shrink = 0, content = " ")
+ super(w, content)
+ @stretch = stretch
+ @shrink = shrink
+ end
+
+ def to_s
+ return "Glue w = #{@w} str = #{@stretch} shr = #{@shrink}" + super
+ end
+end
+
+class Penalty < KnuthElement
+ attr_reader :p
+
+ Infinite = 1000
+
+ def initialize(w, p = 0, content = nil)
+ super(w, content)
+ @p = p
+ end
+
+ def is_forced_break?
+ @p <= -Infinite
+ end
+
+ def to_s
+ return "Pen w = #{@w} p = #{@p}" + super
+ end
+end
+
+# A box representing a line of a paragraph
+class LineBox < KnuthElement
+ def initialize(elements, difference)
+ @elements = elements
+ @difference = difference
+ end
+
+ def getLabel
+ label = ""
+ if @difference > 0
+ nb_glues = (@elements.select { |e| e.instance_of? Glue }).length
+ if nb_glues > 1
+ nb_spaces = @difference / TimesCharWidths[' ']
+ q, r = nb_spaces.divmod(nb_glues)
+ spaces = []
+ r.times { spaces.push(q + 1) }
+ (nb_glues - r).times { spaces.push(q) }
+ @elements.each do |elem|
+ if elem.instance_of? Glue
+ label << elem.getLabel + ' ' * spaces.pop
+ else
+ label << elem.getLabel
+ end
+ end
+ else
+ @elements.each do |elem|
+ label << elem.getLabel
+ end
+ end
+ else
+ @elements.each do |elem|
+ label << elem.getLabel
+ end
+ end
+ label
+ end
+
+ def to_s
+ label = ""
+ @elements.each do |elem|
+ label << elem.getLabel
+ end
+ label << '\n'
+ end
+end
+
+class Paragraph
+
+ def initialize(content)
+ @content = content
+ end
+
+ def get_knuth_elements
+ elements = []
+ @content.each do |word|
+ elements << Box.new(width(word), word)
+ elements << Penalty.new(0, 0)
+ elements << Glue.new(TimesCharWidths[' '], TimesCharWidths[' '] / 2,
+ TimesCharWidths[' '] / 3)
+ end
+ elements[-1] = Glue.new(0, 1000000, 0, "")
+ elements << Penalty.new(0, -Penalty::Infinite)
+ end
+
+ private
+
+ def width(word)
+ w = 0
+ for i in 0...word.size
+ w += TimesCharWidths[word[i..i]]
+ end
+ w
+ end
+end
+
+class ProgressInfo
+ attr_accessor :total_length, :total_stretch, :total_shrink, :part
+
+ def initialize(*infos)
+ case infos.length
+ when 0
+ # Default initialization
+ @total_length = @total_stretch = @total_shrink = @part = 0
+ when 1
+ # Argument is a ProgressInfo object
+ @total_length = infos[0].total_length
+ @total_stretch = infos[0].total_stretch
+ @total_shrink = infos[0].total_shrink
+ @part = infos[0].part
+ end
+ end
+
+ def reset
+ @total_length = @total_stretch = @total_shrink = @part = 0
+ end
+
+ def eql?(other)
+ @total_length == other.total_length and
+ @total_stretch == other.total_stretch and
+ @total_shrink == other.total_shrink and
+ @part == other.part
+ end
+
+ def hash
+ @total_length.hash
+ end
+
+ def to_s
+ "#@total_length+#@total_stretch−#@total_shrink"
+ end
+end
+
+class Layout
+ attr_accessor :total_demerits, :previous, :progress, :alternatives, :elements
+
+ def initialize(*other)
+ case other.length
+ when 0
+ @total_demerits = 0
+ @previous = nil
+ @progress = ProgressInfo.new
+ @alternatives = []
+ @elements = []
+ when 1
+ @total_demerits = other[0].total_demerits
+ @previous = other[0].previous
+ @progress = ProgressInfo.new(other[0].progress)
+ @alternatives = other[0].alternatives.dup
+ @elements = other[0].elements.dup
+ end
+ end
+
+ def dup
+ new_layout = Layout.new
+ new_layout.total_demerits = @total_demerits
+ new_layout.previous = @previous
+ new_layout.progress = ProgressInfo.new(@progress)
+ new_layout.alternatives = @alternatives
+ new_layout.elements = @elements.dup
+ return new_layout
+ end
+
+ def get_block_progress
+ @progress
+ end
+
+ # Dumps this layout to stdout in dot format, if not present in the handled list
+ def to_dot(handled)
+ unless handled.include?(object_id)
+ handled << object_id
+ if previous.nil?
+ puts %( "#{object_id}" [label="" shape=box width=0.2 height=0.2])
+ else
+ previous_layout = previous
+ #if instance_of? LineLayout and is_page
+ # previous_layout = previous_layout.previous
+ #end
+ if previous_layout.instance_of? LineLayout and
+ previous_layout.line_layout.progress.part == 0
+ previous_layout = previous_layout.previous
+ end
+ previous_layout.to_dot(handled)
+ label = ""
+ shape = ""
+ if instance_of? LineLayout and is_page or progress.total_length == 0
+ shape = "shape=box"
+ previous.elements.each do |elem|
+ label << elem.getLabel + '\l'
+ end
+ else
+ elements.each do |elem|
+ label << elem.getLabel + '\l'
+ end
+ end
+ puts %( "#{object_id}" [#{shape} label="#{label}"])
+ puts %( "#{previous_layout.object_id}" -> "#{object_id}")
+ alternatives.each do |alt|
+ alt.to_dot(handled)
+ puts %( "#{alt.object_id}" -> "#{object_id}" [style=dashed])
+ end
+ end
+ end
+ end
+
+ def to_s
+ label = "[ "
+ elements.each { |e| label << "#{e} " }
+ label << "]"
+ label
+ end
+end
+
+class LineLayout < Layout
+ attr_accessor :line_layout, :is_page
+
+ def initialize(*block_layout)
+ super(*block_layout)
+ @line_layout = Layout.new
+ @is_page = false
+ case block_layout.length
+ when 0 # nop
+ when 1
+ @previous = block_layout[0]
+ end
+ end
+
+ def dup
+ new_layout = LineLayout.new(self)
+ new_layout.previous = @previous
+ new_layout.line_layout = @line_layout.dup
+ new_layout.is_page = @is_page
+ return new_layout
+ end
+
+ def getLineLabel
+ label = "[ "
+ line_layout.elements.each { |e| label << e.to_s }
+ label << " ]"
+ label
+ end
+end
+
+class FeasibleBreaks
+
+ def initialize
+ @best = nil
+ @alternatives = []
+ end
+
+ def add(layout, demerits, difference=0)
+ if @best.nil?
+ @best = {'dem' => demerits, 'layout' => layout, 'diff' => difference}
+ elsif demerits < @best['dem']
+ @alternatives << @best['layout']
+ @best = {'dem' => demerits, 'layout' => layout, 'diff' => difference}
+ else
+ @alternatives << layout
+ end
+ end
+
+ def get_best
+ @best
+ end
+
+ def get_alternatives
+ @alternatives
+ end
+end
+
+class ActiveLayouts
+ def initialize
+ @layouts = []
+ end
+
+ def add(layout)
+ progress = layout.get_block_progress
+ if @layouts[progress.part]
+ if @layouts[progress.part][progress]
+ @layouts[progress.part][progress] << layout
+ else
+ @layouts[progress.part][progress] = [layout]
+ end
+ else
+ @layouts[progress.part] = {progress => [layout]}
+ end
+ end
+
+ def delete(layout)
+ progress = layout.get_block_progress
+ layouts = @layouts[progress.part][progress].delete(layout)
+ end
+
+ def clear
+ @layouts = []
+ end
+
+ def empty?
+ @layouts.inject(true) do |empty, hash|
+ empty and (hash.nil? or empty_hash?(hash))
+ end
+ end
+
+ def each(&block)
+ each_block_class do |block_layouts|
+ each_block_layout(block_layouts, &block)
+ end
+ end
+
+ def each_block_class
+ @layouts.each do |block_layouts|
+ yield block_layouts if block_layouts && !empty_hash?(block_layouts)
+ end
+ end
+
+ def each_line_class(&block)
+ each_block_class do |block_layouts|
+ block_layouts.values.each(&block)
+ end
+ end
+
+ def each_block_layout(block_layouts, &block)
+ block_layouts.each_value do |layouts|
+ layouts.collect.each(&block)
+ end
+ end
+
+ def each_line_layout(line_layouts, &block)
+ line_layouts.collect.each(&block)
+ end
+
+ # Dumps the layouts on the standard output in the dot format
+ def to_dot
+
+ handled = []
+ # page="8.27,11.69"
+ # size="11,16"
+ # margin="0.3"
+ # node [shape=none fontname="NimbusRomNo9L-Regu"]
+ puts %(digraph ActiveNodes {
+nodesep=.5; ranksep=1.5
+node [shape=none]
+edge [dir=none])
+ each do |layout|
+ layout.to_dot(handled)
+ end
+ puts "}"
+ end
+
+ def to_s
+ str = ""
+ pages = []
+ for i in 0...@layouts.size
+ pages << i if @layouts[i] && !empty_hash?(@layouts[i])
+ end
+ if pages.empty?
+ "[empty]\n"
+ else
+ pages.sort!
+ content_to_s(str, "", "", pages) do |str, prefix_first, prefix_rest, page|
+ keys = []
+ block_layouts = @layouts[page]
+ block_layouts.each_key do |progress|
+ if block_layouts[progress] and !block_layouts[progress].empty?
+ keys << progress
+ end
+ end
+ keys.sort! { |a, b| a.total_length <=> b.total_length }
+ content_to_s(str, prefix_first, prefix_rest, keys) do
+ |str, prefix_first, prefix_rest, key|
+ end_content_to_s(str, prefix_first, prefix_rest, block_layouts[key])
+ end
+ end
+ str
+ end
+ end
+
+ private
+
+ def end_content_to_s(str, prefix_first, prefix_rest, content)
+ if content.length == 1
+ str << "#{prefix_first}─── #{content[0]}\n"
+ else
+ str << "#{prefix_first}─┬─ #{content[0]}\n"
+ content[1..-2].each do |c|
+ str << "#{prefix_rest} ├─ #{c}\n"
+ end
+ str << "#{prefix_rest} └─ #{content[-1]}\n"
+ end
+ end
+
+ def content_to_s(str, prefix_first, prefix_rest, content)
+ if content.length == 1
+ label = content[0].to_s
+ first = prefix_first + "─── " + label + " "
+ rest = prefix_rest + " " * (4 + label.jlength + 1)
+ yield(str, first, rest, content[0])
+ else
+ label = content[0].to_s
+ first = prefix_first + "─┬─ " + label + " "
+ rest = prefix_rest + " │ " + " " * (label.jlength + 1)
+ yield(str, first, rest, content[0])
+ content[1..-2].each do |c|
+ label = c.to_s
+ first = prefix_rest + " ├─ " + label + " "
+ rest = prefix_rest + " │ " + " " * (label.jlength + 1)
+ yield(str, first, rest, c)
+ end
+ label = content[-1].to_s
+ first = prefix_rest + " └─ " + label + " "
+ rest = prefix_rest + " " * (4 + label.jlength + 1)
+ yield(str, first, rest, content[-1])
+ end
+ end
+
+ def empty_hash?(hash)
+ hash.values.inject(true) do |empty, a|
+ empty && a.empty?
+ end
+ end
+end
+
+class Breaker
+
+ private_class_method :new
+
+ def find_breaks(content)
+ init_breaking
+ content.each do |element|
+ if element.instance_of? Box
+ new_layouts = []
+ @layouts.each do |layout|
+ new_layouts << layout
+ get_progress(layout).total_length += element.w
+ get_elements(layout) << element
+ end
+ @layouts.clear
+ new_layouts.each { |l| @layouts.add(l) }
+ elsif element.instance_of? Glue
+ new_layouts = []
+ @layouts.each do |layout|
+ new_layouts << layout
+ elements = get_elements(layout)
+ unless elements.empty?
+ progress = get_progress(layout)
+ progress.total_length += element.w
+ progress.total_stretch += element.stretch
+ progress.total_shrink += element.shrink
+ elements << element
+ end
+ end
+ @layouts.clear
+ new_layouts.each { |l| @layouts.add(l) }
+ elsif element.instance_of? Penalty
+ consider_break(element) if element.p < Penalty::Infinite
+ else
+ handle_element(element)
+ end
+ end
+ the_end
+ end
+
+ private
+
+ def init_breaking
+ end
+
+ def get_progress(layout)
+ end
+
+ def get_elements(layout)
+ end
+
+ def consider_break(element)
+ end
+
+ def the_end
+ end
+end
+
+class PageBreaker < Breaker
+
+ public_class_method :new
+
+ def initialize(page_break_handler, line_breaker)
+ @page_break_handler = page_break_handler
+ @line_breaker = line_breaker
+ end
+
+ def init_breaking
+ @layouts = ActiveLayouts.new
+ @layouts.add(Layout.new)
+ end
+
+ def get_progress(layout)
+ layout.progress
+ end
+
+ def get_elements(layout)
+ layout.elements
+ end
+
+ def consider_break(element)
+ @page_break_handler.consider_break(element, @layouts)
+ end
+
+ def handle_element(element)
+ if element.instance_of? Paragraph
+ line_layouts = []
+ @layouts.each do |layout|
+ line_layouts << LineLayout.new(layout)
+ end
+ @layouts.clear
+ line_layouts.each { |l| @layouts.add(l) }
+ content = element.get_knuth_elements
+ @line_breaker.layouts = @layouts
+ @line_breaker.find_breaks(content)
+ end
+ end
+
+ # Dumps layouts to dot format
+ def the_end
+ @layouts.to_dot
+ end
+end
+
+class LineBreaker < Breaker
+
+ public_class_method :new
+
+ attr_writer :layouts
+
+ def initialize(line_break_handler)
+ @line_break_handler = line_break_handler
+ end
+
+ def get_progress(layout)
+ layout.line_layout.progress
+ end
+
+ def get_elements(layout)
+ layout.line_layout.elements
+ end
+
+ def consider_break(element)
+ @line_break_handler.consider_break(element, @layouts)
+ end
+end
+
+class LegalBreakHandler
+
+ private_class_method :new
+
+ InfiniteRatio = 1000
+
+ def initialize(page_dimensions)
+ @page_dims = page_dimensions
+ end
+
+ def consider_break(element, layouts)
+ new_layouts = ActiveLayouts.new
+ each_class(layouts) do |layout_class|
+ feasible_breaks = FeasibleBreaks.new
+ each_layout(layouts, layout_class) do |layout|
+ #$log.debug("Considering #{layout}")
+ difference = compute_difference(layout)
+ difference -= element.w if element.instance_of? Penalty
+ ratio = compute_adjustment_ratio(get_progress(layout), difference)
+ #$log.debug("Ratio: #{ratio}")
+ if ratio < -1.0 or element.is_forced_break?
+ difference = 0 # TODO
+ #$log.debug("Removing line-level layout: #{layout}")
+ layouts.delete(layout)
+ end
+ if -1.0 <= ratio and ratio <= get_threshold
+ d = compute_demerits(element, ratio) + get_demerits(layout)
+ feasible_breaks.add(layout, d, difference)
+ end
+ end
+ best = feasible_breaks.get_best
+ unless best.nil?
+ new_layouts.add(create_layout(best, feasible_breaks.get_alternatives))
+ end
+ end
+ unless new_layouts.empty?
+ handle_new_layouts(new_layouts)
+ new_layouts.each { |layout| layouts.add(layout) }
+ $log.debug("After break:")
+ $log.debug("#{layouts}")
+ end
+ end
+
+ private
+
+ # Executes the given block for each layout class
+ def each_class(layouts, &block)
+ end
+
+ # Executes the given block for each layout of the given class
+ def each_layout(layouts, layout_class, &block)
+ end
+
+ # Returns the maximum adjustment ratio allowed for feasible breaks
+ def get_threshold
+ end
+
+ # Computes the difference between the available space and the space occupied by
+ # the given layout
+ def compute_difference(layout)
+ end
+
+ # Returns the progress informations for the given layout, that will be used to
+ # compute the adjustment ratio
+ def get_progress(layout)
+ end
+
+ # Returns the demerits of the given layout
+ def get_demerits(layout)
+ end
+
+ # Creates and returns a new layout based on the given best break
+ def create_layouts(best, alternatives)
+ end
+
+ # Performs necessary stuff after layouts for the current legal break have been
+ # created. Basically, if inside a paragraph (line-level), call the page-level
+ # breaker
+ def handle_new_layouts(new_layouts)
+ end
+
+ def compute_adjustment_ratio(progress, difference)
+ if difference > 0.0
+ stretch = progress.total_stretch
+ #$log.debug("Stretch: #{stretch}")
+ if stretch > 0
+ return difference.to_f / stretch
+ else
+ return InfiniteRatio
+ end
+ elsif difference < 0.0
+ shrink = progress.total_shrink
+ #$log.debug("Shrink: #{shrink}")
+ if shrink > 0
+ return difference.to_f / shrink
+ else
+ return -InfiniteRatio
+ end
+ else
+ return 0
+ end
+ end
+
+ def compute_demerits(element, ratio)
+ d = 1 + 100 * ratio.abs**3
+ if element.instance_of?(Penalty) && element.p > 0
+ d = (d + element.p)**2
+ elsif element.instance_of?(Penalty) && !element.is_forced_break?
+ d = d**2 - element.p**2
+ else
+ d **= 2
+ end
+ return d
+ end
+
+end
+
+class LegalLineBreakHandler < LegalBreakHandler
+
+ public_class_method :new
+
+ def initialize(page_dimensions, paragraph_break_handler)
+ super(page_dimensions)
+ @paragraph_break_handler = paragraph_break_handler
+ end
+
+ def each_class(layouts, &block)
+ layouts.each_line_class(&block)
+ end
+
+ def each_layout(layouts, layout_class, &block)
+ layouts.each_line_layout(layout_class, &block)
+ end
+
+ def get_threshold
+ 7.6
+ end
+
+ def compute_difference(layout)
+ @page_dims[layout.progress.part][:ipd] -
+ layout.line_layout.progress.total_length
+ end
+
+ def get_progress(layout)
+ layout.line_layout.progress
+ end
+
+ def get_demerits(layout)
+ layout.line_layout.total_demerits
+ end
+
+ def create_layout(best, alternatives)
+ new_layout = LineLayout.new(best['layout'])
+ new_layout.progress.total_length += 1 # TODO line bpd
+ new_layout.alternatives = alternatives
+ new_layout.elements << LineBox.new(best['layout'].line_layout.elements.dup,
+ best['diff'])
+ new_layout.line_layout.total_demerits = best['dem']
+ new_layout.line_layout.progress.part = best['layout'].progress.part + 1
+ #$log.debug("New line break: #{new_layout}")
+ new_layout
+ end
+
+ def handle_new_layouts(new_layouts)
+ @paragraph_break_handler.consider_break(Penalty.new(0, 0), new_layouts)
+ end
+end
+
+class LegalPageBreakHandler < LegalBreakHandler
+
+ public_class_method :new
+
+ def initialize(page_dimensions)
+ super(page_dimensions)
+ end
+
+ def each_class(layouts, &block)
+ layouts.each_block_class(&block)
+ end
+
+ def each_layout(layouts, layout_class, &block)
+ layouts.each_block_layout(layout_class, &block)
+ end
+
+ def get_threshold
+ 1.0
+ end
+
+ def compute_difference(layout)
+ @page_dims[layout.progress.part][:bpd] -
+ layout.progress.total_length
+ end
+
+ def get_progress(layout)
+ layout.progress
+ end
+
+ def get_demerits(layout)
+ layout.total_demerits
+ end
+
+ def create_layout(best, alternatives)
+ new_layout = Layout.new
+ new_layout.total_demerits = best['dem']
+ new_layout.previous = best['layout'].dup
+ new_layout.progress.part = best['layout'].progress.part + 1
+ new_layout.alternatives = alternatives
+ new_layout
+ end
+end
+
+class ParagraphBreakHandler < LegalPageBreakHandler
+
+ def initialize(page_dimensions)
+ super(page_dimensions)
+ end
+
+ def get_demerits(layout)
+ layout.total_demerits + layout.line_layout.total_demerits
+ end
+
+ def create_layout(best, alternatives)
+ new_layout = LineLayout.new
+ new_layout.line_layout = best['layout'].line_layout.dup
+ new_layout.total_demerits = best['dem']
+ new_layout.previous = best['layout']
+ new_layout.progress.part = best['layout'].progress.part + 1
+ new_layout.alternatives = alternatives
+ new_layout.is_page = true
+ new_layout
+ end
+end
+
+
+class Typographer
+
+ def initialize(page_dimensions)
+ @page_dims = page_dimensions
+ @page_break_handler = LegalPageBreakHandler.new(page_dimensions)
+ paragraph_break_handler = ParagraphBreakHandler.new(page_dimensions)
+ @line_break_handler = LegalLineBreakHandler.new(page_dimensions,
+ paragraph_break_handler)
+ end
+
+ def break(content)
+ line_breaker = LineBreaker.new(@line_break_handler)
+ page_breaker = PageBreaker.new(@page_break_handler, line_breaker)
+ page_breaker.find_breaks(content)
+ end
+end
+
+
+Paragraphs = [
+ Paragraph.new(%w( In olden times when wishing still helped one, there lived a king
+ whose daughters were all beautiful, soooo much beautiful. )),
+ Penalty.new(0, 0),
+ Glue.new(1, 1, 0, ""),
+ Paragraph.new(%w( In olden times when wishing still helped one, there lived a king
+ whose daughters were all beautiful, but the youngest was so beautiful that the sun
+ itself, which has seen so much, was astonished whenever it shone in her face. )),
+# Penalty.new(0, 0),
+# Glue.new(2, 0, 1, ""),
+# Paragraph.new(%w( And now I am about to start the next paragraph and the goal is to
+# check that the algorithm is working properly, a thing I am not quite sure of. )),
+# Penalty.new(0, 0),
+# Glue.new(2, 0, 1, ""),
+# Paragraph.new(%w( In olden times when wishing still helped one, there lived a king
+# whose daughters were all beautiful, but the youngest was so beautiful that the sun
+# itself, which has seen so much, was astonished whenever it shone in her face. )),
+ Glue.new(0, 1000000, 0, ""),
+ Penalty.new(0, -Penalty::Infinite)]
+
+Typographer.new([{:ipd => 13000, :bpd => 8},
+ {:ipd => 16000, :bpd => 8},
+ {:ipd => 13000, :bpd => 8},
+ {:ipd => 13000, :bpd => 8},
+ {:ipd => 13000, :bpd => 8},
+ {:ipd => 13000, :bpd => 8}]).break(Paragraphs)