lib/prawn/document.rb in prawn-2.4.0 vs lib/prawn/document.rb in prawn-2.5.0

- old
+ new

@@ -1,54 +1,54 @@ # frozen_string_literal: true -# 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_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. + # 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 + # 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" + # 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" - # pdf.render_file "example.pdf" + # ```ruby + # pdf = Prawn::Document.new + # pdf.text "Hello There" + # pdf.render_file "example.pdf" + # ``` # # Or you can do an implied block form: # - # Prawn::Document.generate "example.pdf" do - # text "Hello There" - # end + # ```ruby + # Prawn::Document.generate "example.pdf" do + # text "Hello There" + # end + # ``` # # Or if you need to access a variable outside the scope of the block, the # explicit block form: # - # words = "Hello There" - # Prawn::Document.generate "example.pdf" do |pdf| - # pdf.text words - # end + # ```ruby + # 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. # - # See the new and generate methods for further details on the above. - # + # See the {#initialize new} and {.generate generate} methods for further + # details on the above. class Document include Prawn::Document::Internals include PDF::Core::Annotations include PDF::Core::Destinations include Prawn::Document::Security @@ -57,43 +57,43 @@ include Prawn::Images include Prawn::Stamp include Prawn::SoftMask include Prawn::TransformationStack + alias inspect to_s + # @group Extension API # NOTE: We probably need to rethink the options validation system, but this # constant temporarily allows for extensions to modify the list. + # List of recognised options. VALID_OPTIONS = %i[ 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. + # {Prawn::Document} at the per-object level. These will also be inherited + # by any subclasses. # - # Example: - # + # @example # module MyFancyModule - # # def party! # text "It's a big party!" # end - # # end # # Prawn::Document.extensions << MyFancyModule # # Prawn::Document.generate("foo.pdf") do # party! # end # - # + # @return [Array<Module>] def self.extensions @extensions ||= [] end # @private @@ -102,116 +102,139 @@ extensions.each { |e| base.extensions << e } end # @group Stable Attributes + # Current margin box. + # @return [Prawn::Document::BoundingBox] attr_accessor :margin_box - attr_reader :margins, :y + + # Current page margins. + # @return [{:left, :top, :right, :bottom => Number}] + attr_reader :margins + + # Absolute cursor position. + # @return [Number] + attr_reader :y + + # Current page number. + # @return [Integer] attr_accessor :page_number # @group Extension Attributes + # Current text formatter. By default it's {Text::Formatted::Parser} + # @return [Object] attr_accessor :text_formatter # @group Stable API # Creates and renders a PDF document. # # When using the implicit block form, Prawn will evaluate the block - # within an instance of Prawn::Document, simplifying your syntax. + # within an instance of {Prawn::Document}, simplifying your syntax. # However, please note that you will not be able to reference variables # from the enclosing scope within this block. # - # # Using implicit block form and rendering to a file - # Prawn::Document.generate "example.pdf" do - # # self here is set to the newly instantiated Prawn::Document - # # and so any variables in the outside scope are unavailable - # font "Times-Roman" - # draw_text "Hello World", :at => [200,720], :size => 32 - # end + # ```ruby + # # Using implicit block form and rendering to a file + # Prawn::Document.generate "example.pdf" do + # # self here is set to the newly instantiated Prawn::Document + # # and so any variables in the outside scope are unavailable + # font "Times-Roman" + # draw_text "Hello World", at: [200,720], size: 32 + # end + # ``` # # If you need to access your local and instance variables, use the explicit - # block form shown below. In this case, Prawn yields an instance of - # PDF::Document and the block is an ordinary closure: + # block form shown below. In this case, Prawn yields an instance of + # {Prawn::Document} and the block is an ordinary closure: # - # # Using explicit block form and rendering to a file - # content = "Hello World" - # Prawn::Document.generate "example.pdf" do |pdf| - # # self here is left alone - # pdf.font "Times-Roman" - # pdf.draw_text content, :at => [200,720], :size => 32 - # end - # + # ```ruby + # # Using explicit block form and rendering to a file + # content = "Hello World" + # Prawn::Document.generate "example.pdf" do |pdf| + # # self here is left alone + # pdf.font "Times-Roman" + # pdf.draw_text content, at: [200,720], size: 32 + # end + # ``` def self.generate(filename, options = {}, &block) pdf = new(options, &block) pdf.render_file(filename) end - # Creates a new PDF Document. The following options are available (with - # the default values marked in []) + # Creates a new PDF Document. # - # <tt>:page_size</tt>:: One of the PDF::Core::PageGeometry sizes [LETTER] - # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt> - # <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>: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] - # - # Setting e.g. the :margin to 100 points and the :left_margin to 50 will + # 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: + # 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] + # ```ruby + # # 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 + # 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 + # @example New document, US Letter paper, portrait orientation # pdf = Prawn::Document.new # - # # New document, A4 paper, landscaped + # @example New document, A4 paper, landscaped # pdf = Prawn::Document.new(page_size: "A4", page_layout: :landscape) # - # # New document, Custom size + # @example New document, Custom size # pdf = Prawn::Document.new(page_size: [200, 300]) # - # # New document, with background + # @example New document, with background # pdf = Prawn::Document.new( # background: "#{Prawn::DATADIR}/images/pigs.jpg" # ) # + # @param options [Hash{Symbol => any}] + # @option options :page_size [String, Array(Number, Number)] (LETTER) + # One of the `PDF::Core::PageGeometry` sizes. + # @option options :page_layout [:portrait, :landscape] + # Page orientation. + # @option options :margin [Number, Array<Number>] ([32]) + # Sets the margin on all sides in points. + # @option options :left_margin [Number] (32) + # Sets the left margin in points. + # @option options :right_margin [Number] (32) + # Sets the right margin in points. + # @option options :top_margin [Number] (32) + # Sets the top margin in points. + # @option options :bottom_margin [Number] (32) + # Sets the bottom margin in points. + # @option options :skip_page_creation [Boolean] (false) + # Creates a document without starting the first page. + # @option options :compress [Boolean] (false) + # Compresses content streams before rendering them. + # @option options :background [String?] (nil) + # An image path to be used as background on all pages. + # @option options :background_scale [Number?] (1) + # Background image scale. + # @option options :info [Hash{Symbol => any}?] (nil) + # Generic hash allowing for custom metadata properties. + # @option options :text_formatter [Object] (Prawn::Text::Formatted::Parser) + # The text formatter to use for `:inline_format`ted text. def initialize(options = {}, &block) options = options.dup - Prawn.verify_options VALID_OPTIONS, options + Prawn.verify_options(VALID_OPTIONS, options) # need to fix, as the refactoring breaks this # raise NotImplementedError if options[:skip_page_creation] - self.class.extensions.reverse_each { |e| extend e } + self.class.extensions.reverse_each { |e| extend(e) } self.state = PDF::Core::DocumentState.new(options) 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 @@ -243,17 +266,38 @@ # @group Stable API # Creates and advances to a new page in the document. # # Page size, margins, and layout can also be set when generating a - # new page. These values will become the new defaults for page creation + # new page. These values will become the new defaults for page creation. # + # @example # pdf.start_new_page #=> Starts new page keeping current values - # pdf.start_new_page(:size => "LEGAL", :layout => :landscape) - # pdf.start_new_page(:left_margin => 50, :right_margin => 50) - # pdf.start_new_page(:margin => 100) + # pdf.start_new_page(size: "LEGAL", :layout => :landscape) + # pdf.start_new_page(left_margin: 50, right_margin: 50) + # pdf.start_new_page(margin: 100) # + # @param options [Hash] + # @option options :margins [Hash{:left, :right, :top, :bottom => Number}, nil] + # ({ left: 0, right: 0, top: 0, bottom: 0 }) Page margins + # @option options :crop [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS) + # Page crop box + # @option options :bleed [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS) + # Page bleed box + # @option options :trims [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS) + # Page trim box + # @option options :art_indents [Hash{:left, :right, :top, :bottom => Number}, nil] (PDF::Core::Page::ZERO_INDENTS) + # Page art box indents. + # @option options :graphic_state [PDF::Core::GraphicState, nil] (nil) + # Initial graphic state + # @option options :size [String, Array<Number>, nil] ('LETTER') + # Page size. A string identifies a named page size defined in + # `PDF::Core::PageGeometry`. An array must be a two element array + # specifying width and height in points. + # @option options :layout [:portrait, :landscape, nil] (:portrait) + # Page orientation. + # @return [void] def start_new_page(options = {}) last_page = state.page if last_page last_page_size = last_page.size last_page_layout = last_page.layout @@ -261,11 +305,11 @@ end page_options = { size: options[:size] || last_page_size, layout: options[:layout] || last_page_layout, - margins: last_page_margins + margins: last_page_margins, } if last_page if last_page.graphic_state new_graphic_state = last_page.graphic_state.dup end @@ -305,19 +349,22 @@ state.on_page_create_action(self) end end end - # Remove page of the document by index + # Remove page of the document by index. # + # @example # pdf = Prawn::Document.new # pdf.page_count #=> 1 # 3.times { pdf.start_new_page } # pdf.page_count #=> 4 # pdf.delete_page(-1) # pdf.page_count #=> 3 # + # @param index [Integer] + # @return [Boolean] def delete_page(index) return false if index.abs > (state.pages.count - 1) state.pages.delete_at(index) @@ -325,64 +372,75 @@ state.store.pages.data[:Count] -= 1 @page_number -= 1 true end - # Returns the number of pages in the document + # Number of pages in the document. # + # @example # pdf = Prawn::Document.new # pdf.page_count #=> 1 # 3.times { pdf.start_new_page } # pdf.page_count #=> 4 # + # @return [Integer] def page_count state.page_count end # Re-opens the page with the given (1-based) page number so that you can # draw on it. # - # See Prawn::Document#number_pages for a sample usage of this capability. - # + # @param page_number [Integer] + # @return [void] def go_to_page(page_number) @page_number = page_number state.page = state.pages[page_number - 1] generate_margin_box @y = @bounding_box.absolute_top end + # Set cursor absolute position. + # + # @param new_y [Number] + # @return [new_y] def y=(new_y) @y = new_y bounds.update_height end # The current y drawing position relative to the innermost bounding box, # or to the page margins at the top level. # + # @return [Number] def cursor y - bounds.absolute_bottom end # Moves to the specified y position in relative terms to the bottom margin. # + # @param new_y [Number] + # @return [void] def move_cursor_to(new_y) self.y = new_y + bounds.absolute_bottom end # Executes a block and then restores the original y position. If new pages # were created during this block, it will teleport back to the original # page when done. # + # @example # pdf.text "A" # # pdf.float do # pdf.move_down 100 # pdf.text "C" # end # # pdf.text "B" # + # @return [void] def float original_page = page_number original_y = y yield go_to_page(original_page) unless page_number == original_page @@ -390,39 +448,44 @@ end # Renders the PDF document to string. # Pass an open file descriptor to render to file. # - def render(*arguments, &block) + # @overload render(output = nil) + # @param output [#<<] + # @return [String] + def render(*arguments) (1..page_count).each do |i| - go_to_page i + go_to_page(i) repeaters.each { |r| r.run(i) } end - renderer.render(*arguments, &block) + renderer.render(*arguments) end # Renders the PDF document to file. # + # @example # pdf.render_file "foo.pdf" # + # @param filename [String] + # @return [void] def render_file(filename) 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> + # document itself. When called from within a created `bounding_box` # 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. # - # For example: - # + # @example # Prawn::Document.new do # # In the default "margin box" of a Prawn document of 0.5in along each # # edge # # # Draw a border around the page (the manual way) @@ -435,143 +498,162 @@ # # # Draw a border around the page (the easy way) # stroke_bounds # end # + # @return [Prawn::Document::BoundingBox] def bounds @bounding_box end # Returns the innermost non-stretchy bounding box. # # @private + # @return [Prawn::Document::BoundingBox] def reference_bounds @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 + # Sets {Document#bounds} to the {BoundingBox} provided. See {#bounds} 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. # + # @param bounding_box [Prawn::Document::BoundingBox] + # @return [bounding_box] def bounds=(bounding_box) @bounding_box = bounding_box end # Moves up the document by n points relative to the current position inside # the current bounding box. # + # @param amount [Number] + # @return [void] def move_up(amount) self.y += amount end # Moves down the document by n points relative to the current position # inside the current bounding box. # + # @param amount [Number] + # @return [void] def move_down(amount) self.y -= amount end # Moves down the document and then executes a block. # + # @example # pdf.text "some text" # pdf.pad_top(100) do # pdf.text "This is 100 points below the previous line of text" # end # pdf.text "This text appears right below the previous line of text" # + # @param y [Number] + # @return [void] + # @yield def pad_top(y) move_down(y) yield end # Executes a block then moves down the document # + # @example # pdf.text "some text" # pdf.pad_bottom(100) do # pdf.text "This text appears right below the previous line of text" # end # pdf.text "This is 100 points below the previous line of text" # + # @param y [Number] + # @return [void] + # @yield def pad_bottom(y) yield move_down(y) end # Moves down the document by y, executes a block, then moves down the # document by y again. # + # @example # pdf.text "some text" # pdf.pad(100) do # pdf.text "This is 100 points below the previous line of text" # end # pdf.text "This is 100 points below the previous line of text" # + # @param y [Number] + # @return [void] + # @yield def pad(y) move_down(y) yield move_down(y) end # Indents the specified number of PDF points for the duration of the block # + # @example # pdf.text "some text" # pdf.indent(20) do # pdf.text "This is indented 20 points" # end # pdf.text "This starts 20 points left of the above line " + # "and is flush with the first line" # pdf.indent 20, 20 do # pdf.text "This line is indented on both sides." # end # + # @param left [Number] + # @param right [Number] + # @yield + # @return [void] 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 + # 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: + # Please refer to {Prawn::Text#text_box} for additional options concerning + # text formatting and placement. # - # <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>: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>:color</tt>:: Text fill color. + # @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 # - # 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. - # - # 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 - # + # @param string [String] Template string for page number wording. + # Should include `<page>` and, optionally, `<total>`. + # @param options [Hash{Symbol => any}] A hash for page numbering and text box options. + # @option options :page_filter [] + # A filter to specify which pages to place page numbers on. Refer to the method {#page_match?} + # @option options :start_count_at [Integer] + # The starting count to increment pages from. + # @option options :total_pages [Integer] + # If provided, will replace `<total>` with the value given. Useful to + # override the total number of pages when using the start_count_at option. + # @option options :color [String, Array<Number>] Text fill color. def number_pages(string, options = {}) opts = options.dup - start_count_at = opts.delete(:start_count_at).to_i + start_count_at = opts.delete(:start_count_at) page_filter = if opts.key?(:page_filter) opts.delete(:page_filter) else @@ -587,70 +669,69 @@ pseudopage = 0 (1..page_count).each do |p| unless start_count pseudopage = case start_count_at - when 0 - 1 + when String + Integer(start_count_at, 10) + when (1..) + Integer(start_count_at) else - start_count_at.to_i + 1 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 - fill_color txtcolor unless txtcolor.nil? - total_pages = total_pages.nil? ? page_count : total_pages + fill_color(txtcolor) unless txtcolor.nil? + total_pages = page_count if total_pages.nil? str = string.gsub('<page>', pseudopage.to_s) .gsub('<total>', total_pages.to_s) - text_box str, opts + text_box(str, opts) start_count = true # increment page count as soon as first match found end pseudopage += 1 if start_count end end # @group Experimental API - # Attempts to group the given block vertically within the current context. - # First attempts to render it in the current position on the current page. - # If that attempt overflows, it is tried anew after starting a new context - # (page or column). Returns a logically true value if the content fits in - # one page/column, false if a new page or column was needed. - # - # Raises CannotGroup if the provided content is too large to fit alone in - # the current page or column. - # # @private def group(*_arguments) 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' + '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 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' + '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. # # Available page filters are: # :all repeats on every page # :odd repeats on odd pages - # :even repeats on even pages - # some_array repeats on every page listed in the array - # some_range repeats on every page included in the range - # some_lambda yields page number and repeats for true return values + # + # @param page_filter [:all, :odd, :even, Array<Number>, Range, Proc] + # * `:all`: repeats on every page + # * `:odd`: repeats on odd pages + # * `:even`: repeats on even pages + # * array: repeats on every page listed in the array + # * range: repeats on every page included in the range + # * lambda: yields page number and repeats for true return values + # @param page_number [Integer] + # @return [Boolean] def page_match?(page_filter, page_number) case page_filter when :all true when :odd @@ -663,23 +744,28 @@ page_filter.call(page_number) end 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. # -- I will remove the nodoc if/when this feature is a little less hacky stored = {} fields.each { |f| stored[f] = public_send(f) } yield - fields.each { |f| public_send("#{f}=", stored[f]) } + fields.each { |f| public_send(:"#{f}=", stored[f]) } end # @group Extension API + # Initializes the first page in a new document. + # This methods allows customisation of this process in extensions such as + # Prawn::Template. + # + # @param options [Hash] + # @return [void] def initialize_first_page(options) if options[:skip_page_creation] start_new_page(options.merge(orphan: true)) else start_new_page(options) @@ -718,11 +804,11 @@ 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]) + (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) @@ -742,19 +828,19 @@ positions = { 4 => [0, 1, 2, 3], 3 => [0, 1, 2, 1], 2 => [0, 1, 0, 1], 1 => [0, 0, 0, 0], - 0 => [] + 0 => [], }[margin.length] 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: + def font_metric_cache # :nodoc: @font_metric_cache ||= FontMetricCache.new(self) end end end