# encoding: UTF-8 module Prawn module ManualBuilder # The Prawn::ManualBuilder::Example class holds all the helper methods # used to generate manuals. # # The overall structure is to have single example files grouped by package # folders. Each package has a package builder file (with the same name as the # package folder) that defines the inner structure of subsections and # examples. The manual is then built by loading all the packages and some # standalone pages. # # To see one of the examples check manual/basic_concepts/cursor.rb # # To see one of the package builders check # manual/basic_concepts/basic_concepts.rb # # To see how the manual is built check manual/manual/manual.rb (Yes that's a # whole load of manuals) class Example < Prawn::ManualBuilder.document_class # Values used for the manual design: # This is the default value for the margin box # BOX_MARGIN = 36 # Additional indentation to keep the line measure with a reasonable size # INNER_MARGIN = 30 # Vertical Rhythm settings # RHYTHM = 10 LEADING = 2 # Colors # BLACK = "000000" LIGHT_GRAY = "F2F2F2" GRAY = "DDDDDD" DARK_GRAY = "333333" BROWN = "A4441C" ORANGE = "F28157" LIGHT_GOLD = "FBFBBE" DARK_GOLD = "EBE389" BLUE = "0000D0" # Used to generate the url for the example files # MANUAL_URL = "http://github.com/prawnpdf/prawn/tree/master/manual" # Loads a package. Used on the manual. # def load_package(package) load_file(package, package) end # Loads a page with outline support. Used on the manual. # def load_page(package, page) load_file(package, page) outline.define do section(page.gsub("_", " ").capitalize, :destination => page_number) end end # Opens a file in a given package and evals the source # def load_file(package, file) start_new_page example = ExampleFile.new(package, "#{file}.rb") eval example.generate_block_source, nil, File.join(Prawn::ManualBuilder.manual_dir, example.parent_folder_name, example.filename), example.generated_block_line end # Creates a new ExamplePackage object and yields it to a block in order for # it to be populated with examples, sections and some introduction text. # Used on the package files. # def package(package, &block) ep = ExamplePackage.new(package) ep.instance_eval(&block) ep.render(self) end # Renders an ExamplePackage cover page. # # Starts a new page and renders the package introduction text. # def render_package_cover(package) header(package.name) instance_eval &(package.intro_block) outline.define do section(package.name, :destination => page_number, :closed => true) end end # Add the ExampleSection to the document outline within the appropriate # package. # def render_section(section) outline.add_subsection_to(section.package_name) do outline.section(section.name, :closed => true) end end # Renders an ExampleFile. # # Starts a new page and renders an introductory text, the example source and # evaluates the example source inline whenever that is appropriate according # to the ExampleFile directives. # def render_example(example) start_new_page outline.add_subsection_to(example.parent_name) do outline.page(:destination => page_number, :title => example.name) end example_header(example.parent_folder_name, example.filename) prose(example.introduction_text) code(example.source) if example.eval? eval_code(example) else source_link(example) end reset_settings end # Render the example header. Used on the example pages of the manual # def example_header(package, example) header_box do register_fonts font('DejaVu', :size => 18) do formatted_text([ { :text => package, :color => BROWN }, { :text => "/", :color => BROWN }, { :text => example, :color => ORANGE } ], :valign => :center) end end end # Register fonts used on the manual # def register_fonts kai_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/gkai00mp.ttf" font_families["Kai"] = { :normal => { :file => kai_file, :font => "Kai" } } dejavu_file = "#{Prawn::ManualBuilder::DATADIR}/fonts/DejaVuSans.ttf" font_families["DejaVu"] = { :normal => { :file => dejavu_file, :font => "DejaVu" } } end # Render a block of text after processing code tags and URLs to be used with # the inline_format option. # # Used on the introducory text for example pages of the manual and on # package pages intro # def prose(str) # Process the tags str.gsub!(/([^<]+?)<\/code>/, "\\1<\/b><\/font>") # Process the links str.gsub!(/(https?:\/\/\S+)/, "\\1") inner_box do font("Helvetica", :size => 11) do str.split(/\n\n+/).each do |paragraph| text(paragraph.gsub(/\s+/," "), :align => :justify, :inline_format => true, :leading => LEADING, :color => DARK_GRAY) move_down(RHYTHM) end end end move_down(RHYTHM) end # Render a code block. Used on the example pages of the manual # def code(str) pre_text = str.gsub(' ', Prawn::Text::NBSP) pre_text = ::CodeRay.scan(pre_text, :ruby).to_prawn font('Courier', :size => 9.5) do colored_box(pre_text, :fill_color => DARK_GRAY) end end # Renders a dashed line and evaluates the code inline # def eval_code(example) source = example.source move_down(RHYTHM) dash(3) stroke_color(BROWN) stroke_horizontal_line(-BOX_MARGIN, bounds.width + BOX_MARGIN) stroke_color(BLACK) undash move_down(RHYTHM*3) begin eval source, nil, File.join(Prawn::ManualBuilder.manual_dir, example.parent_folder_name, example.filename), example.generated_block_line rescue => e puts "Error evaluating example: #{e.message}" puts puts "---- Source: ----" puts source end end # Renders a box with the link for the example file # def source_link(example) url = "#{MANUAL_URL}/#{example.parent_folder_name}/#{example.filename}" reason = [{ :text => "This code snippet was not evaluated inline. " + "You may see its output by running the " + "example file located here:\n", :color => DARK_GRAY }, { :text => url, :color => BLUE, :link => url} ] font('Helvetica', :size => 9) do colored_box(reason, :fill_color => LIGHT_GOLD, :stroke_color => DARK_GOLD, :leading => LEADING*3) end end # Render a page header. Used on the manual lone pages and package # introductory pages # def header(str) header_box do register_fonts font('DejaVu', :size => 24) do text(str, :color => BROWN, :valign => :center) end end end # Render the arguments as a bulleted list. Used on the manual package # introductory pages # def list(*items) move_up(RHYTHM) inner_box do font("Helvetica", :size => 11) do items.each do |li| float { text("•", :color => DARK_GRAY) } indent(RHYTHM) do text(li.gsub(/\s+/," "), :inline_format => true, :color => DARK_GRAY, :leading => LEADING) end move_down(RHYTHM) end end end end # Renders the page-wide headers # def header_box(&block) bounding_box([-bounds.absolute_left, cursor + BOX_MARGIN], :width => bounds.absolute_left + bounds.absolute_right, :height => BOX_MARGIN*2 + RHYTHM*2) do fill_color LIGHT_GRAY fill_rectangle([bounds.left, bounds.top], bounds.right, bounds.top - bounds.bottom) fill_color BLACK indent(BOX_MARGIN + INNER_MARGIN, &block) end stroke_color GRAY stroke_horizontal_line(-BOX_MARGIN, bounds.width + BOX_MARGIN, :at => cursor) stroke_color BLACK move_down(RHYTHM*3) end # Renders a Bounding Box for the inner margin # def inner_box(&block) bounding_box([INNER_MARGIN, cursor], :width => bounds.width - INNER_MARGIN*2, &block) end # Renders a Bounding Box with some background color and the formatted text # inside it # def colored_box(box_text, options={}) options = { :fill_color => DARK_GRAY, :stroke_color => nil, :text_color => LIGHT_GRAY, :leading => LEADING }.merge(options) register_fonts text_options = { :leading => options[:leading], :fallback_fonts => ["DejaVu", "Kai"] } box_height = height_of_formatted(box_text, text_options) bounding_box([INNER_MARGIN + RHYTHM, cursor], :width => bounds.width - (INNER_MARGIN+RHYTHM)*2) do fill_color options[:fill_color] stroke_color options[:stroke_color] || options[:fill_color] fill_and_stroke_rounded_rectangle( [bounds.left - RHYTHM, cursor], bounds.left + bounds.right + RHYTHM*2, box_height + RHYTHM*2, 5 ) fill_color BLACK stroke_color BLACK pad(RHYTHM) do formatted_text(box_text, text_options) end end move_down(RHYTHM*2) end # Draws X and Y axis rulers beginning at the margin box origin. Used on # examples. # def stroke_axis(options={}) super({:height => (cursor - 20).to_i}.merge(options)) end # Reset some of the Prawn settings including graphics and text to their # defaults. # # Used after rendering examples so that each new example starts with a clean # slate. # def reset_settings # Text settings font("Helvetica", :size => 12) default_leading 0 self.text_direction = :ltr # Graphics settings self.line_width = 1 self.cap_style = :butt self.join_style = :miter undash fill_color BLACK stroke_color BLACK end end end end