module Asciidoctor::Prawn::Extensions
Constants
- FontAwesomeIconSets
- IconSets
- InitialPageContent
- LineMetrics
-
:height is the height of a line
-
:leading is spacing between adjacent lines
-
:padding_top is half line spacing, plus any line_gap in the font
-
:padding_bottom is half line spacing
-
:final_gap determines whether a gap is added below the last line
-
Public Instance Methods
This method is a smarter version of start_new_page. It calls start_new_page if the current page is the last page of the document. Otherwise, it simply advances to the next existing page.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 785 def advance_page opts = {} last_page? ? (start_new_page opts) : (go_to_page page_number + 1) end
Returns whether the cursor is at the top of the page (i.e., margin box).
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 140 def at_page_top? @y == @margin_box.absolute_top end
Returns the total left margin (to the page edge) for the current bounds.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 106 def bounds_margin_left bounds.absolute_left end
Returns the total right margin (to the page edge) for the current bounds.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 112 def bounds_margin_right page.dimensions[2] - bounds.absolute_right end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 316 def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size line_height_length = line_height * font_size leading = line_height_length - font_size half_leading = leading / 2 padding_top = half_leading + font.line_gap padding_bottom = half_leading LineMetrics.new line_height_length, leading, padding_top, padding_bottom, false end
Retrieves the catalog reference data for the PDF
.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 28 def catalog state.store.root end
Deletes the current page and move the cursor to the previous page.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 705 def delete_page pg = page_number pdf_store = state.store pdf_objs = pdf_store.instance_variable_get :@objects pdf_ids = pdf_store.instance_variable_get :@identifiers page_id = pdf_store.object_id_for_page pg content_id = page.content.identifier [page_id, content_id].each do |key| pdf_objs.delete key pdf_ids.delete key end pdf_store.pages.data[:Kids].pop pdf_store.pages.data[:Count] -= 1 state.pages.pop if pg > 1 go_to_page pg - 1 else @page_number = 0 state.page = nil end end
Generates a destination object that resolves to the top of the page specified by the page_num parameter or the current page if no page number is provided. The destination preserves the user's zoom level unlike the destinations generated by the outline builder.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 168 def dest_top page_num = nil dest_xyz 0, page_height, nil, (page_num ? state.pages[page_num - 1] : page) end
NOTE override built-in draw_indented_formatted_line
to insert leading before second line
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 361 def draw_indented_formatted_line string, opts result = super unless @no_text_printed || @all_text_printed # as of Prawn 1.2.1, we have to handle the line gap after the first line manually move_down opts[:leading] end result end
TODO document me
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 835 def dry_run &block scratch = get_scratch_document # QUESTION should we use scratch.advance_page instead? scratch.start_new_page start_page_number = scratch.page_number start_y = scratch.y if (left_padding = bounds.total_left_padding) > 0 scratch.bounds.add_left_padding left_padding end if (right_padding = bounds.total_right_padding) > 0 scratch.bounds.add_right_padding right_padding end scratch.font font_family, style: font_style, size: font_size do scratch.instance_exec(&block) end # NOTE don't count excess if cursor exceeds writable area (due to padding) full_page_height = scratch.effective_page_height partial_page_height = [full_page_height, start_y - scratch.y].min scratch.bounds.subtract_left_padding left_padding if left_padding > 0 scratch.bounds.subtract_right_padding right_padding if right_padding > 0 whole_pages = scratch.page_number - start_page_number [(whole_pages * full_page_height + partial_page_height), whole_pages, partial_page_height] end
Returns the effective (writable) height of the page
If inside a fixed-height bounding box, returns width of box.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 58 def effective_page_height reference_bounds.height end
Returns the effective (writable) width of the page
If inside a fixed-height bounding box, returns height of box.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 44 def effective_page_width reference_bounds.width end
Returns whether the current page is empty (i.e., no content has been written). Returns false if a page has not yet been created.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 147 def empty_page? # if we are at the page top, assume we didn't write anything to the page #at_page_top? # ...or use more robust, low-level check (initial value of content is "q\n") page_number > 0 && page.content.stream.filtered_stream == InitialPageContent end
Fills the absolute bounding box with the specified fill color. Before returning from this method, the original fill color on the document is restored.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 587 def fill_absolute_bounds f_color = fill_color canvas { fill_bounds f_color } end
Fills the current bounds using the specified fill color and strokes the bounds using the specified stroke color. Sets the line with if specified in the options. Before returning from this method, the original fill color, stroke color and line width on the document are restored.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 596 def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {} no_fill = !f_color || f_color == 'transparent' no_stroke = !s_color || s_color == 'transparent' || options[:line_width] == 0 return if no_fill && no_stroke save_graphics_state do radius = options[:radius] || 0 # fill unless no_fill fill_color f_color fill_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius end # stroke unless no_stroke stroke_color s_color line_width(options[:line_width] || 0.5) # FIXME think about best way to indicate dashed borders #if options.has_key? :dash_width # dash options[:dash_width], space: options[:dash_space] || 1 #end stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius #undash if options.has_key? :dash_width end end end
Fills the current bounding box with the specified fill color. Before returning from this method, the original fill color on the document is restored.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 575 def fill_bounds f_color = fill_color if f_color && f_color != 'transparent' prev_fill_color = fill_color fill_color f_color fill_rectangle bounds.top_left, bounds.width, bounds.height fill_color prev_fill_color end end
A flowing version of the bounding_box. If the content runs to another page, the cursor starts at the top of the page instead of the original cursor position. Similar to span, except you can specify an absolute left position and pass additional options through to bounding_box.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 558 def flow_bounding_box left = 0, opts = {} original_y = self.y # QUESTION should preserving original_x be an option? original_x = bounds.absolute_left - margin_box.absolute_left canvas do bounding_box [margin_box.absolute_left + original_x + left, margin_box.absolute_top], opts do self.y = original_y yield end end end
Enhances the built-in font method to allow the font size to be specified as the second option and to lazily load font-based icons.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 194 def font name = nil, options = {} if name options = { size: options } if ::Numeric === options if IconSets.include? name ::Prawn::Icon::FontData.load self, name options = options.reject {|k| k == :style } if options.key? :style end end super name, options end
Retrieves the current font name (i.e., family).
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 207 def font_family font.options[:family] end
Retrieves the current font info (family, style, size) as a Hash
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 215 def font_info { family: font.options[:family], style: (font.options[:style] || :normal), size: @font_size } end
Applies points as a scale factor of the current font if the value provided is less than or equal to 1 or it's a string (e.g., 1.1em), then delegates to the super implementation to carry out the built-in functionality.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 241 def font_size points = nil return @font_size unless points if points == 1 super @font_size elsif String === points if points.end_with? 'rem' super(@theme.base_font_size * points.to_f) elsif points.end_with? 'em' super(@font_size * points.to_f) elsif points.end_with? '%' super(@font_size * (points.to_f / 100.0)) else super points.to_f end # FIXME HACK assume em value elsif points < 1 super(@font_size * points) else super points end end
Sets the font style for the scope of the block to which this method yields. If the style is nil and no block is given, return the current font style.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 223 def font_style style = nil if block_given? font font.options[:family], style: style do yield end elsif style font font.options[:family], style: style else font.options[:style] || :normal end end
Retreives the collection of font styles from the given font style key, which defaults to the current font style.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 276 def font_styles style = font_style if style style == :bold_italic ? [:bold, :italic].to_set : [style].to_set else ::Set.new end end
Apply the font settings (family, size, styles and character spacing) from the fragment to the document, then yield to the block.
The original font settings are restored before this method returns.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 289 def fragment_font fragment f_info = font_info f_family = fragment[:font] || f_info[:family] f_size = fragment[:size] || f_info[:size] if (f_styles = fragment[:styles]) f_style = resolve_font_style f_styles else f_style = :normal end if (c_spacing = fragment[:character_spacing]) character_spacing c_spacing do font f_family, size: f_size, style: f_style do yield end end else font f_family, size: f_size, style: f_style do yield end end end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 810 def get_scratch_document # marshal if not using transaction feature #Marshal.load Marshal.dump @prototype # use cached instance, tests show it's faster #@prototype ||= ::Prawn::Document.new @scratch ||= if defined? @prototype scratch = Marshal.load Marshal.dump @prototype scratch.instance_variable_set(:@prototype, @prototype) # TODO set scratch number on scratch document scratch else logger.warn 'no scratch prototype available; instantiating fresh scratch document' ::Prawn::Document.new end end
Conditional group operation
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 801 def group_if verdict if verdict state.optimize_objects = false # optimize objects breaks group group { yield } else yield end end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 312 def icon_font_data family ::Prawn::Icon::FontData.load self, family end
Create a new page for the specified image. If the canvas option is true, the image is stretched to the edges of the page (full coverage).
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 754 def image_page file, options = {} start_new_page_discretely if options[:canvas] canvas do image file, width: bounds.width, height: bounds.height end else image file, fit: [bounds.width, bounds.height] end # FIXME shouldn't this be `go_to_page prev_page_number + 1`? go_to_page page_count nil end
Import the specified page into the current document.
By default, advance to the subsequent page, creating one if necessary. This behavior can be disabled by passing the option `advance: false`.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 732 def import_page file, opts = {} prev_page_layout = page.layout prev_page_size = page.size state.compress = false if state.compress # can't use compression if using template prev_text_rendering_mode = (defined? @text_rendering_mode) ? @text_rendering_mode : nil delete_page if opts[:replace] # NOTE use functionality provided by prawn-templates start_new_page_discretely template: file # prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert @text_rendering_mode = prev_text_rendering_mode if opts.fetch :advance, true # NOTE set page size & layout explicitly in case imported page differs # I'm not sure it's right to start a new page here, but unfortunately there's no other # way atm to prevent the size & layout of the imported page from affecting subsequent pages advance_page size: prev_page_size, layout: prev_page_layout end nil end
TODO memoize the result
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 526 def inflate_padding padding padding = [*(padding || 0)].slice 0, 4 case padding.size when 1 [padding[0], padding[0], padding[0], padding[0]] when 2 [padding[0], padding[1], padding[0], padding[1]] when 3 [padding[0], padding[1], padding[2], padding[1]] else padding end end
Attempt to keep the objects generated in the block on the same page
TODO short-circuit nested usage
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 862 def keep_together &block available_space = cursor total_height, _, _ = dry_run(&block) # NOTE technically, if we're at the page top, we don't even need to do the # dry run, except several uses of this method rely on the calculated height if total_height > available_space && !at_page_top? && total_height <= effective_page_height advance_page started_new_page = true else started_new_page = false end # HACK yield doesn't work here on JRuby (at least not when called from AsciidoctorJ) #yield remainder, started_new_page instance_exec(total_height, started_new_page, &block) end
Attempt to keep the objects generated in the block on the same page if the verdict parameter is true.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 882 def keep_together_if verdict, &block if verdict keep_together(&block) else yield end end
Returns whether the current page is the last page in the document.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 157 def last_page? page_number == page_count end
Short-circuits the call to the built-in move_down
operation when n is 0.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 455 def move_down n super unless n == 0 end
Override built-in move_text_position
method to prevent Prawn
from advancing to next page if image doesn't fit before rendering image.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 449 def move_text_position h end
Short-circuits the call to the built-in move_up
operation when n is 0.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 441 def move_up n super unless n == 0 end
Overrides the built-in pad operation to allow for asymmetric paddings.
Example:
pad 20, 10 do text 'A paragraph with twice as much top padding as bottom padding.' end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 469 def pad top, bottom = nil move_down top yield move_down(bottom || top) end
Combines the built-in pad and indent operations into a single method.
Padding may be specified as an array of four values, or as a single value. The single value is used as the padding around all four sides of the box.
If padding is nil, this method simply yields to the block and returns.
Example:
pad_box 20 do text 'A paragraph inside a blox with even padding on all sides.' end pad_box [10, 10, 10, 20] do text 'An indented paragraph inside a box with equal padding on all sides.' end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 492 def pad_box padding if padding # TODO implement shorthand combinations like in CSS p_top, p_right, p_bottom, p_left = ::Array === padding ? padding : (::Array.new 4, padding) begin # logic is intentionally inlined move_down p_top bounds.add_left_padding p_left bounds.add_right_padding p_right yield # NOTE support negative bottom padding for use with quote block if p_bottom < 0 # QUESTION should we return to previous page if top of page is reached? p_bottom < cursor - reference_bounds.top ? (move_cursor_to reference_bounds.top) : (move_down p_bottom) else p_bottom < cursor ? (move_down p_bottom) : reference_bounds.move_past_bottom end ensure bounds.subtract_left_padding p_left bounds.subtract_right_padding p_right end else yield end # alternate, delegated logic #pad padding[0], padding[2] do # indent padding[1], padding[3] do # yield # end #end end
Returns the height of the current page from edge-to-edge
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 50 def page_height page.dimensions[3] end
Returns the margins for the current page as a 4 element array (top, right, bottom, left)
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 72 def page_margin [page.margins[:top], page.margins[:right], page.margins[:bottom], page.margins[:left]] end
Returns the width of the bottom margin for the current page
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 100 def page_margin_bottom page.margins[:bottom] end
Returns the width of the left margin for the current page
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 78 def page_margin_left page.margins[:left] end
Returns the width of the right margin for the current page
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 86 def page_margin_right page.margins[:right] end
Returns the width of the top margin for the current page
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 94 def page_margin_top page.margins[:top] end
Returns the side the current page is facing, :recto or :verso.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 118 def page_side pgnum = nil, invert = nil if invert (recto_page? pgnum) ? :verso : :recto else (recto_page? pgnum) ? :recto : :verso end end
Returns the width of the current page from edge-to-edge
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 36 def page_width page.dimensions[2] end
Parse the text into an array of fragments using the text formatter.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 340 def parse_text string, options = {} return [] if string.nil? options = options.dup if (format_option = options.delete :inline_format) format_option = [] unless ::Array === format_option fragments = self.text_formatter.format string, *format_option else fragments = [{text: string}] end if (color = options.delete :color) fragments.map do |fragment| fragment[:color] ? fragment : fragment.merge(color: color) end else fragments end end
Perform an operation (such as creating a new page) without triggering the on_page_create callback
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 770 def perform_discretely if (saved_callback = state.on_page_create_callback) # equivalent to calling `on_page_create` state.on_page_create_callback = nil yield # equivalent to calling `on_page_create &saved_callback` state.on_page_create_callback = saved_callback else yield end end
Returns whether the page is a recto page.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 128 def recto_page? pgnum = nil (pgnum || page_number).odd? end
Registers a new custom font described in the data parameter after converting the font name to a String
.
Example:
register_font Roboto: { normal: 'fonts/roboto-normal.ttf', italic: 'fonts/roboto-italic.ttf', bold: 'fonts/roboto-bold.ttf', bold_italic: 'fonts/roboto-bold_italic.ttf' }
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 186 def register_font data font_families.update data.inject({}) {|accum, (key, val)| accum[key.to_s] = val; accum } end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 263 def resolve_font_style styles if styles.include? :bold (styles.include? :italic) ? :bold_italic : :bold elsif styles.include? :italic :italic else :normal end end
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 827 def scratch? (@_label ||= (state.store.info.data[:Scratch] ? :scratch : :primary)) == :scratch rescue false # NOTE this method may get called before the state is initialized end
Set the margins for the current page.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 64 def set_page_margin margin # FIXME is there a cleaner way to set margins? does it make sense to override create_new_page? apply_margin_options margin: margin generate_margin_box end
Fills and, optionally, strokes the current bounds using the fill and stroke color specified, then yields to the block. The only_if option can be used to conditionally disable this behavior.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 627 def shade_box color, line_color = nil, options = {} if (!options.has_key? :only_if) || options[:only_if] # FIXME could use save_graphics_state here previous_fill_color = current_fill_color fill_color color fill_rectangle [bounds.left, bounds.top], bounds.right, bounds.top - bounds.bottom fill_color previous_fill_color if line_color line_width 0.5 previous_stroke_color = current_stroke_color stroke_color line_color stroke_bounds stroke_color previous_stroke_color end end yield end
Stretch the current bounds to the left and right edges of the current page while yielding the specified block if the verdict argument is true. Otherwise, simply yield the specified block.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 544 def span_page_width_if verdict if verdict indent(-bounds_margin_left, -bounds_margin_right) do yield end else yield end end
Start a new page without triggering the on_page_create callback
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 791 def start_new_page_discretely options = {} perform_discretely do start_new_page options end end
Strokes a horizontal line using the current bounds. The width of the line can be specified using the line_width option.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 675 def stroke_horizontal_rule rule_color = stroke_color, options = {} rule_style = options[:line_style] rule_width = options[:line_width] || 0.5 rule_x_start = bounds.left rule_x_end = bounds.right rule_inked = false save_graphics_state do line_width rule_width stroke_color rule_color case rule_style when :dashed dash rule_width * 4 when :dotted dash rule_width when :double move_up rule_width stroke_horizontal_line rule_x_start, rule_x_end move_down rule_width * 2 stroke_horizontal_line rule_x_start, rule_x_end move_up rule_width rule_inked = true end if rule_style stroke_horizontal_line rule_x_start, rule_x_end unless rule_inked end end
A compliment to the stroke_horizontal_rule
method, strokes a vertical line using the current bounds. The width of the line can be specified using the line_width option. The horizontal (x) position can be specified using the at option.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 650 def stroke_vertical_rule rule_color = stroke_color, options = {} rule_x = options[:at] || 0 rule_y_from = bounds.top rule_y_to = bounds.bottom rule_style = options[:line_style] rule_width = options[:line_width] || 0.5 save_graphics_state do line_width rule_width stroke_color rule_color case rule_style when :dashed dash rule_width * 4 when :dotted dash rule_width when :double stroke_vertical_line rule_y_from, rule_y_to, at: (rule_x - rule_width) rule_x += rule_width end if rule_style stroke_vertical_line rule_y_from, rule_y_to, at: rule_x end end
Performs the same work as Prawn::Text.text except that the first_line_opts are applied to the first line of text renderered. It's necessary to use low-level APIs in this method so we only style the first line and not the remaining lines (which is the default behavior in Prawn
).
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 373 def text_with_formatted_first_line string, first_line_opts, opts color = opts.delete :color fragments = parse_text string, opts # NOTE the low-level APIs we're using don't recognize the :styles option, so we must resolve if (styles = opts.delete :styles) opts[:style] = resolve_font_style styles end if (first_line_styles = first_line_opts.delete :styles) first_line_opts[:style] = resolve_font_style first_line_styles end first_line_color = (first_line_opts.delete :color) || color opts = opts.merge document: self # QUESTION should we merge more carefully here? (hand-select keys?) first_line_opts = opts.merge(first_line_opts).merge single_line: true box = ::Prawn::Text::Formatted::Box.new fragments, first_line_opts # NOTE get remaining_fragments before we add color to fragments on first line if (text_indent = opts.delete :indent_paragraphs) remaining_fragments = indent text_indent do box.render dry_run: true end else remaining_fragments = box.render dry_run: true end # NOTE color must be applied per-fragment if first_line_color fragments.each {|fragment| fragment[:color] ||= first_line_color} end if text_indent indent text_indent do fill_formatted_text_box fragments, first_line_opts end else fill_formatted_text_box fragments, first_line_opts end unless remaining_fragments.empty? # NOTE color must be applied per-fragment remaining_fragments.each {|fragment| fragment[:color] ||= color } if color # as of Prawn 1.2.1, we have to handle the line gap after the first line manually move_down opts[:leading] remaining_fragments = fill_formatted_text_box remaining_fragments, opts draw_remaining_formatted_text_on_new_pages remaining_fragments, opts end end
Apply the text transform to the specified text.
Supported transform values are “uppercase”, “lowercase”, or “none” (passed as either a String
or a Symbol). When the uppercase transform is applied to the text, it correctly uppercases visible text while leaving markup and named character entities unchanged. The none transform returns the text unmodified.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 425 def transform_text text, transform case transform when :uppercase, 'uppercase' uppercase_pcdata text when :lowercase, 'lowercase' lowercase_mb text else text end end
Returns whether the page is a verso page.
# File lib/asciidoctor-pdf/prawn_ext/extensions.rb, line 134 def verso_page? pgnum = nil (pgnum || page_number).even? end