lib/prawn/document.rb in prawn-2.1.0 vs lib/prawn/document.rb in prawn-2.2.0

- old
+ new

@@ -1,28 +1,26 @@ -# encoding: utf-8 - # document.rb : Implements PDF document generation for Prawn # # Copyright April 2008, Gregory Brown. All Rights Reserved. # # This is free software. Please see the LICENSE and COPYING files for details. -require "stringio" +require 'stringio' -require_relative "document/bounding_box" -require_relative "document/column_box" -require_relative "document/internals" -require_relative "document/span" +require_relative 'document/bounding_box' +require_relative 'document/column_box' +require_relative 'document/internals' +require_relative 'document/span' module Prawn # The Prawn::Document class is how you start creating a PDF document. # # There are three basic ways you can instantiate PDF Documents in Prawn, they - # are through assignment, implicit block or explicit block. Below is an exmple - # of each type, each example does exactly the same thing, makes a PDF document - # with all the defaults and puts in the default font "Hello There" and then - # saves it to the current directory as "example.pdf" + # are through assignment, implicit block or explicit block. Below is an + # example of each type, each example does exactly the same thing, makes a PDF + # document with all the defaults and puts in the default font "Hello There" + # and then saves it to the current directory as "example.pdf" # # For example, assignment can be like this: # # pdf = Prawn::Document.new # pdf.text "Hello There" @@ -40,12 +38,12 @@ # words = "Hello There" # Prawn::Document.generate "example.pdf" do |pdf| # pdf.text words # end # - # Usually, the block forms are used when you are simply creating a PDF document - # that you want to immediately save or render out. + # Usually, the block forms are used when you are simply creating a PDF + # document that you want to immediately save or render out. # # See the new and generate methods for further details on the above. # class Document include Prawn::Document::Internals @@ -62,14 +60,16 @@ # @group Extension API # NOTE: We probably need to rethink the options validation system, but this # constant temporarily allows for extensions to modify the list. - VALID_OPTIONS = [:page_size, :page_layout, :margin, :left_margin, - :right_margin, :top_margin, :bottom_margin, :skip_page_creation, - :compress, :background, :info, - :text_formatter, :print_scaling] + VALID_OPTIONS = [ + :page_size, :page_layout, :margin, :left_margin, + :right_margin, :top_margin, :bottom_margin, :skip_page_creation, + :compress, :background, :info, + :text_formatter, :print_scaling + ].freeze # Any module added to this array will be included into instances of # Prawn::Document at the per-object level. These will also be inherited by # any subclasses. # @@ -151,45 +151,54 @@ # <tt>:margin</tt>:: Sets the margin on all sides in points [0.5 inch] # <tt>:left_margin</tt>:: Sets the left margin in points [0.5 inch] # <tt>:right_margin</tt>:: Sets the right margin in points [0.5 inch] # <tt>:top_margin</tt>:: Sets the top margin in points [0.5 inch] # <tt>:bottom_margin</tt>:: Sets the bottom margin in points [0.5 inch] - # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false] - # <tt>:compress</tt>:: Compresses content streams before rendering them [false] - # <tt>:background</tt>:: An image path to be used as background on all pages [nil] + # <tt>:skip_page_creation</tt>:: Creates a document without starting the + # first page [false] + # <tt>:compress</tt>:: Compresses content streams before rendering them + # [false] + # <tt>:background</tt>:: An image path to be used as background on all pages + # [nil] # <tt>:background_scale</tt>:: Backgound image scale [1] [nil] - # <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil] - # <tt>:text_formatter</tt>: The text formatter to use for <tt>:inline_format</tt>ted text [Prawn::Text::Formatted::Parser] + # <tt>:info</tt>:: Generic hash allowing for custom metadata properties + # [nil] + # <tt>:text_formatter</tt>: The text formatter to use for + # <tt>:inline_format</tt>ted text + # [Prawn::Text::Formatted::Parser] # - # Setting e.g. the :margin to 100 points and the :left_margin to 50 will result in margins - # of 100 points on every side except for the left, where it will be 50. + # Setting e.g. the :margin to 100 points and the :left_margin to 50 will + # result in margins of 100 points on every side except for the left, where + # it will be 50. # # The :margin can also be an array much like CSS shorthand: # # # Top and bottom are 20, left and right are 100. # :margin => [20, 100] # # Top is 50, left and right are 100, bottom is 20. # :margin => [50, 100, 20] # # Top is 10, right is 20, bottom is 30, left is 40. # :margin => [10, 20, 30, 40] # - # Additionally, :page_size can be specified as a simple two value array giving - # the width and height of the document you need in PDF Points. + # Additionally, :page_size can be specified as a simple two value array + # giving the width and height of the document you need in PDF Points. # # Usage: # # # New document, US Letter paper, portrait orientation # pdf = Prawn::Document.new # # # New document, A4 paper, landscaped - # pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape) + # pdf = Prawn::Document.new(page_size: "A4", page_layout: :landscape) # # # New document, Custom size - # pdf = Prawn::Document.new(:page_size => [200, 300]) + # pdf = Prawn::Document.new(page_size: [200, 300]) # # # New document, with background - # pdf = Prawn::Document.new(:background => "#{Prawn::DATADIR}/images/pigs.jpg") + # pdf = Prawn::Document.new( + # background: "#{Prawn::DATADIR}/images/pigs.jpg" + # ) # def initialize(options = {}, &block) options = options.dup Prawn.verify_options VALID_OPTIONS, options @@ -197,25 +206,26 @@ # need to fix, as the refactoring breaks this # raise NotImplementedError if options[:skip_page_creation] self.class.extensions.reverse_each { |e| extend e } self.state = PDF::Core::DocumentState.new(options) - self.state.populate_pages_from_store(self) + state.populate_pages_from_store(self) renderer.min_version(state.store.min_version) if state.store.min_version renderer.min_version(1.6) if options[:print_scaling] == :none @background = options[:background] @background_scale = options[:background_scale] || 1 - @font_size = 12 + @font_size = 12 - @bounding_box = nil - @margin_box = nil + @bounding_box = nil + @margin_box = nil @page_number = 0 - @text_formatter = options.delete(:text_formatter) || Text::Formatted::Parser + @text_formatter = options.delete(:text_formatter) || + Text::Formatted::Parser options[:size] = options.delete(:page_size) options[:layout] = options.delete(:page_layout) initialize_first_page(options) @@ -238,28 +248,32 @@ # pdf.start_new_page(:size => "LEGAL", :layout => :landscape) # pdf.start_new_page(:left_margin => 50, :right_margin => 50) # pdf.start_new_page(:margin => 100) # def start_new_page(options = {}) - if last_page = state.page - last_page_size = last_page.size - last_page_layout = last_page.layout + last_page = state.page + if last_page + last_page_size = last_page.size + last_page_layout = last_page.layout last_page_margins = last_page.margins.dup end page_options = { - :size => options[:size] || last_page_size, - :layout => options[:layout] || last_page_layout, - :margins => last_page_margins + size: options[:size] || last_page_size, + layout: options[:layout] || last_page_layout, + margins: last_page_margins } if last_page - new_graphic_state = last_page.graphic_state.dup if last_page.graphic_state + if last_page.graphic_state + new_graphic_state = last_page.graphic_state.dup + end - # erase the color space so that it gets reset on new page for fussy pdf-readers + # erase the color space so that it gets reset on new page for fussy + # pdf-readers new_graphic_state.color_space = {} if new_graphic_state - page_options.merge!(:graphic_state => new_graphic_state) + page_options[:graphic_state] = new_graphic_state end state.page = PDF::Core::Page.new(self, page_options) apply_margin_options(options) @@ -275,11 +289,15 @@ unless options[:orphan] state.insert_page(state.page, @page_number) @page_number += 1 - canvas { image(@background, :scale => @background_scale, :at => bounds.top_left) } if @background + if @background + canvas do + image(@background, scale: @background_scale, at: bounds.top_left) + end + end @y = @bounding_box.absolute_top float do state.on_page_create_action(self) end @@ -363,27 +381,28 @@ # Renders the PDF document to file. # # pdf.render_file "foo.pdf" # def render_file(filename) - File.open(filename, "wb") { |f| render(f) } + File.open(filename, 'wb') { |f| render(f) } end # The bounds method returns the current bounding box you are currently in, # which is by default the box represented by the margin box on the # document itself. When called from within a created <tt>bounding_box</tt> # block, the box defined by that call will be returned instead of the # document margin box. # - # Another important point about bounding boxes is that all x and y measurements - # within a bounding box code block are relative to the bottom left corner of the - # bounding box. + # Another important point about bounding boxes is that all x and + # y measurements within a bounding box code block are relative to the bottom + # left corner of the bounding box. # # For example: # # Prawn::Document.new do - # # In the default "margin box" of a Prawn document of 0.5in along each edge + # # In the default "margin box" of a Prawn document of 0.5in along each + # # edge # # # Draw a border around the page (the manual way) # stroke do # line(bounds.bottom_left, bounds.bottom_right) # line(bounds.bottom_right, bounds.top_right) @@ -406,12 +425,12 @@ @bounding_box.reference_bounds end # Sets Document#bounds to the BoundingBox provided. See above for a brief # description of what a bounding box is. This function is useful if you - # really need to change the bounding box manually, but usually, just entering - # and exiting bounding box code blocks is good enough. + # really need to change the bounding box manually, but usually, just + # entering and exiting bounding box code blocks is good enough. # def bounds=(bounding_box) @bounding_box = bounding_box end @@ -420,12 +439,12 @@ # def move_up(n) self.y += n end - # Moves down the document by n points relative to the current position inside - # the current bounding box. + # Moves down the document by n points relative to the current position + # inside the current bounding box. # def move_down(n) self.y -= n end @@ -484,54 +503,58 @@ # def indent(left, right = 0, &block) bounds.indent(left, right, &block) end - # Places a text box on specified pages for page numbering. This should be called - # towards the end of document creation, after all your content is already in - # place. In your template string, <page> refers to the current page, and - # <total> refers to the total amount of pages in the document. Page numbering should - # occur at the end of your Prawn::Document.generate block because the method iterates - # through existing pages after they are created. + # Places a text box on specified pages for page numbering. This should be + # called towards the end of document creation, after all your content is + # already in place. In your template string, <page> refers to the current + # page, and <total> refers to the total amount of pages in the document. + # Page numbering should occur at the end of your Prawn::Document.generate + # block because the method iterates through existing pages after they are + # created. # # Parameters are: # # <tt>string</tt>:: Template string for page number wording. # Should include '<page>' and, optionally, '<total>'. # <tt>options</tt>:: A hash for page numbering and text box options. - # <tt>:page_filter</tt>:: A filter to specify which pages to place page numbers on. - # Refer to the method 'page_match?' + # <tt>:page_filter</tt>:: A filter to specify which pages to place page + # numbers on. Refer to the method 'page_match?' # <tt>:start_count_at</tt>:: The starting count to increment pages from. - # <tt>:total_pages</tt>:: If provided, will replace <total> with the value given. - # Useful to override the total number of pages when using - # the start_count_at option. + # <tt>:total_pages</tt>:: If provided, will replace <total> with the + # value given. Useful to override the total + # number of pages when using the start_count_at + # option. # <tt>:color</tt>:: Text fill color. # - # Please refer to Prawn::Text::text_box for additional options concerning text - # formatting and placement. + # Please refer to Prawn::Text::text_box for additional options + # concerning text formatting and placement. # - # Example: Print page numbers on every page except for the first. Start counting from - # five. + # Example: + # Print page numbers on every page except for the first. Start counting + # from five. # - # Prawn::Document.generate("page_with_numbering.pdf") do - # number_pages "<page> in a total of <total>", - # {:start_count_at => 5, - # :page_filter => lambda{ |pg| pg != 1 }, - # :at => [bounds.right - 50, 0], - # :align => :right, - # :size => 14} - # end + # Prawn::Document.generate("page_with_numbering.pdf") do + # number_pages "<page> in a total of <total>", { + # start_count_at: 5, + # page_filter: lambda { |pg| pg != 1 }, + # at: [bounds.right - 50, 0], + # align: :right, + # size: 14 + # } + # end # def number_pages(string, options = {}) opts = options.dup start_count_at = opts.delete(:start_count_at).to_i - if opts.key?(:page_filter) - page_filter = opts.delete(:page_filter) - else - page_filter = :all - end + page_filter = if opts.key?(:page_filter) + opts.delete(:page_filter) + else + :all + end total_pages = opts.delete(:total_pages) txtcolor = opts.delete(:color) # An explicit height so that we can draw page numbers in the margins opts[:height] = 50 unless opts.key?(:height) @@ -547,16 +570,18 @@ start_count_at.to_i end end if page_match?(page_filter, p) go_to_page(p) - # have to use fill_color here otherwise text reverts back to default fill color + # have to use fill_color here otherwise text reverts back to default + # fill color fill_color txtcolor unless txtcolor.nil? total_pages = total_pages.nil? ? page_count : total_pages - str = string.gsub("<page>", "#{pseudopage}").gsub("<total>", "#{total_pages}") + str = string.gsub('<page>', pseudopage.to_s) + .gsub('<total>', total_pages.to_s) text_box str, opts - start_count = true # increment page count as soon as first match found + start_count = true # increment page count as soon as first match found end pseudopage += 1 if start_count end end @@ -570,25 +595,25 @@ # # Raises CannotGroup if the provided content is too large to fit alone in # the current page or column. # # @private - def group(*a, &b) - fail NotImplementedError, - "Document#group has been disabled because its implementation " \ - "lead to corrupted documents whenever a page boundary was " \ - "crossed. We will try to work on reimplementing it in a " \ - "future release" + def group(*_a) + raise NotImplementedError, + 'Document#group has been disabled because its implementation ' \ + 'lead to corrupted documents whenever a page boundary was ' \ + 'crossed. We will try to work on reimplementing it in a ' \ + 'future release' end # @private def transaction - fail NotImplementedError, - "Document#transaction has been disabled because its implementation " \ - "lead to corrupted documents whenever a page boundary was " \ - "crossed. We will try to work on reimplementing it in a " \ - "future release" + raise NotImplementedError, + 'Document#transaction has been disabled because its implementation ' \ + 'lead to corrupted documents whenever a page boundary was ' \ + 'crossed. We will try to work on reimplementing it in a ' \ + 'future release' end # Provides a way to execute a block of code repeatedly based on a # page_filter. # @@ -615,12 +640,12 @@ end # @private def mask(*fields) - # Stores the current state of the named attributes, executes the block, and - # then restores the original values after the block has executed. + # Stores the current state of the named attributes, executes the block, + # and then restores the original values after the block has executed. # -- I will remove the nodoc if/when this feature is a little less hacky stored = {} fields.each { |f| stored[f] = send(f) } yield fields.each { |f| send("#{f}=", stored[f]) } @@ -628,11 +653,11 @@ # @group Extension API def initialize_first_page(options) if options[:skip_page_creation] - start_new_page(options.merge(:orphan => true)) + start_new_page(options.merge(orphan: true)) else start_new_page(options) end end @@ -646,31 +671,33 @@ state.page end private - # setting override_settings to true ensures that a new graphic state does not end up using - # previous settings. + # setting override_settings to true ensures that a new graphic state does + # not end up using previous settings. def use_graphic_settings(override_settings = false) - set_fill_color if current_fill_color != "000000" || override_settings - set_stroke_color if current_stroke_color != "000000" || override_settings + set_fill_color if current_fill_color != '000000' || override_settings + set_stroke_color if current_stroke_color != '000000' || override_settings write_line_width if line_width != 1 || override_settings write_stroke_cap_style if cap_style != :butt || override_settings write_stroke_join_style if join_style != :miter || override_settings write_stroke_dash if dashed? || override_settings end def generate_margin_box old_margin_box = @margin_box - page = state.page + page = state.page @margin_box = BoundingBox.new( self, - nil, # margin box has no parent - [ page.margins[:left], page.dimensions[-1] - page.margins[:top] ] , - :width => page.dimensions[-2] - (page.margins[:left] + page.margins[:right]), - :height => page.dimensions[-1] - (page.margins[:top] + page.margins[:bottom]) + nil, # margin box has no parent + [page.margins[:left], page.dimensions[-1] - page.margins[:top]], + width: page.dimensions[-2] - + (page.margins[:left] + page.margins[:right]), + height: page.dimensions[-1] - + (page.margins[:top] + page.margins[:bottom]) ) # This check maintains indentation settings across page breaks if old_margin_box @margin_box.add_left_padding(old_margin_box.total_left_padding) @@ -681,24 +708,22 @@ # @bounding_box = @margin_box unless @bounding_box && @bounding_box.parent end def apply_margin_options(options) - if options[:margin] - # Treat :margin as CSS shorthand with 1-4 values. - margin = Array(options[:margin]) - positions = { 4 => [0, 1, 2, 3], 3 => [0, 1, 2, 1], - 2 => [0, 1, 0, 1], 1 => [0, 0, 0, 0] }[margin.length] + sides = [:top, :right, :bottom, :left] + margin = Array(options[:margin]) - [:top, :right, :bottom, :left].zip(positions).each do |p, i| - options[:"#{p}_margin"] ||= margin[i] - end - end + # Treat :margin as CSS shorthand with 1-4 values. + positions = { + 4 => [0, 1, 2, 3], 3 => [0, 1, 2, 1], + 2 => [0, 1, 0, 1], 1 => [0, 0, 0, 0], + 0 => [] + }[margin.length] - [:left, :right, :top, :bottom].each do |side| - if margin = options[:"#{side}_margin"] - state.page.margins[side] = margin - end + sides.zip(positions).each do |side, pos| + new_margin = options["#{side}_margin"] || (margin[pos] if pos) + state.page.margins[side] = new_margin if new_margin end end def font_metric_cache #:nodoc: @font_metric_cache ||= FontMetricCache.new(self)