module Asciidoctor::Prawn::Extensions

Constants

ColumnBox
DetectEmptyFirstPage
DetectEmptyFirstPageProc
Extent
FontAwesomeIconSets
FontStyleToSet
IconSetPrefixes
IconSets
InhibitNewPageProc
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

NewPageRequiredError
PlaceholderChar

NOTE: must use a visible char for placeholder or else Prawn won’t reserve space for the fragment

Position
ScratchExtent

Public Instance Methods

advance_page(options = {}) click to toggle source

This method is a smarter version of start_new_page. It only calls start_new_page options are specified and the current page is the last page in the document. Otherwise, it advances the cursor to the next page (or column) using Bounds#move_past_bottom.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 955
def advance_page options = {}
  options.empty? || !last_page? ? bounds.move_past_bottom : (start_new_page options)
end
allocate_scratch_prototype() click to toggle source

Scratch

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 967
def allocate_scratch_prototype
  @scratch_prototype = create_scratch_prototype { ::Marshal.load ::Marshal.dump self }
end
arrange_block(node) { |extent)| ... } click to toggle source

Yields to the specified block multiple times, first to determine where to render the content so it fits properly, then once more, this time providing access to the content’s extent, to ink the content in the primary document.

This method yields to the specified block in a scratch document by calling dry_run to determine where the content should start in the primary document. In the process, it also computes the extent of the content. It then returns to the primary document and yields to the block again, this time passing in the extent of the content. The extent can be used to draw a border and/or background under the content before inking it.

This method is intended to enclose the conversion of a single content block, such as a sidebar or example block. The arrange logic attempts to keep unbreakable content on the same page, keeps the top caption pinned to the top of the content, computes the extent of the content for the purpose of drawing a border and/or background underneath it, and ensures that the extent does not begin near the bottom of a page if the first line of content doesn’t fit. If unbreakable content does not fit on a single page, the content is treated as breakable.

The block passed to this method should use advance_page to move to the next page rather than start_new_page. Using start_new_page can mangle the calculation of content block’s extent.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 1004
def arrange_block node, &block
  keep_together = (node.option? 'unbreakable') && !at_page_top?
  doc = node.document
  block_for_scratch = proc do
    push_scratch doc
    instance_exec(&block)
  ensure
    pop_scratch doc
  end
  extent = dry_run keep_together: keep_together, onto: [self, keep_together], &block_for_scratch
  scratch? ? block_for_scratch.call : (yield extent)
end
at_page_top?() click to toggle source

Returns whether the cursor is at the top of the page (i.e., margin box).

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 254
def at_page_top?
  @y == (ColumnBox === bounds ? bounds : @margin_box).absolute_top
end
bounds_margin_left() click to toggle source

Returns the total left margin (to the page edge) for the current bounds.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 220
def bounds_margin_left
  bounds.absolute_left
end
bounds_margin_right() click to toggle source

Returns the total right margin (to the page edge) for the current bounds.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 226
def bounds_margin_right
  page.dimensions[2] - bounds.absolute_right
end
calc_line_metrics(line_height, font = self.font, font_size = self.font_size) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 415
def calc_line_metrics line_height, 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
catalog() click to toggle source

Retrieves the catalog reference data for the PDF.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 127
def catalog
  state.store.root
end
column_box(point, options, &block) click to toggle source

Wraps the column_box method and automatically sets the height unless the :height option is specified.

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 697
def column_box point, options, &block
  options[:height] = cursor unless options.key? :height
  super
end
conceal_page_top() { || ... } click to toggle source

Prevents at_page_top? from returning true while yielding to the specified block.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 260
def conceal_page_top
  old_top = (outer_bounds = ColumnBox === bounds ? bounds : @margin_box).absolute_top
  outer_bounds.instance_variable_set :@y, old_top + 0.0001
  yield
ensure
  outer_bounds.instance_variable_set :@y, old_top
end
delete_current_page() click to toggle source

Deletes the current page and move the cursor to the previous page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 865
def delete_current_page
  pg = page_number
  pdf_store = state.store
  content_id = page.content.identifier
  page_ref = page.dictionary
  (prune_dests = proc do |node|
    node.children.delete_if {|it| ::PDF::Core::NameTree::Node === it ? prune_dests[it] : it.value.data[0] == page_ref }
    false
  end)[dests.data]
  # NOTE: cannot delete objects and IDs, otherwise references get corrupted; so just reset the value
  (pdf_store.instance_variable_get :@objects)[content_id] = ::PDF::Core::Reference.new content_id, {}
  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
dest_top(page_num = nil) click to toggle source

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/ext/prawn/extensions.rb, line 281
def dest_top page_num = nil
  dest_xyz 0, page_height, nil, (page_num ? state.pages[page_num - 1] : page)
end
draw_indented_formatted_line(string, options) click to toggle source

NOTE: override built-in draw_indented_formatted_line to set first_line flag

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 462
def draw_indented_formatted_line string, options
  super string, (options.merge first_line: true)
end
dry_run(keep_together: nil, pages_advanced: 0, single_page: nil, onto: nil, &block) click to toggle source

Yields to the specified block within the context of a scratch document up to three times to acertain the extent of the content block.

The purpose of this method is two-fold. First, it works out the position where the rendered content should start in the calling document. Then, it precomputes the extent of the content starting from that position.

This method returns the content’s extent (the range from the start page and cursor to the end page and cursor) as a ScratchExtent object or, if the onto keyword parameter is specified, an Extent object. A ScratchExtent always starts the page range at 1. When the ScratchExtent is positioned onto the primary document using ScratchExtent#position_onto, that’s when the cursor may be advanced to the next page.

This method performs all work in a scratch document (or documents). It begins by starting a new page in the scratch document, first creating the scratch document if necessary. It then applies all the settings from the main document to the scratch document that impact rendering. This includes the bounds, the cursor position, and the font settings. This method assumes that the content area remains constant when content flows from one page to the next.

From this point, the number of attempts the method makes is determined by the value of the keep_together keyword parameter. If the value is true (or the parent document is inhibiting page creation), it starts from the top of the page, yields to the block, and tries to fit the content on the current page. If the content fits, it computes and returns the ScratchExtent (or Extent). If the content does not fit, it first checks if this scenario should stop the operation. If the parent document is inhibiting page creation, it bubbles the error. If the single_page keyword argument is :enforce, it raises a CannotFit error. If the single_page keyword argument is true, it returns a ScratchExtent (or Extent) that represents a full page. If none of those conditions are met, it restarts with the keep_together parameter unset.

If the keep_together parameter is not true, the method tries to render the content in the scratch document from the location of the cursor in the main document. If the cursor is at the top of the page, no special conditions are applied (this is the last attempt). The content is rendered and the extent is computed based on where the content ended up (minus a trailing empty page). If the cursor is not at the top of the page, the method renders the content while listening for a page creation event before any content is written. If a new page is created, and no content is written on the first page, the method restarts with the cursor at the top of the page.

Note that if the block has content that itself requires a dry run, that nested dry run will be performed in a separate scratch document.

The block passed to dry run should take steps to avoid leaving the document state modified. This can be done by calling ‘push_scratch doc` at the start of the block and `pop_scratch` in the ensure clause of the block.

options - A Hash of options that configure the dry run computation:

:keep_together - A Boolean indicating whether an attempt should be made to keep
the content on the same page (optional, default: false).
:single_page - A Boolean indicating whether the operation should stop if the
content exceeds the height of a single page.
:onto - The document onto which to position the scratch extent. If this option is
set, the method returns an Extent instance (optional, default: false)
:pages_advanced - The number of pages the content has been advanced during this
operation (internal only) (optional, default: 0)

Returns an Extent or ScratchExtent object that describes the bounds of the content block.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 1132
def dry_run keep_together: nil, pages_advanced: 0, single_page: nil, onto: nil, &block
  (scratch_pdf = scratch).start_new_page layout: page.layout, margin: page_margin
  saved_bounds = scratch_pdf.bounds
  scratch_pdf.bounds = bounds.dup.tap do |bounds_copy|
    bounds_copy.instance_variable_set :@document, scratch_pdf
    bounds_copy.instance_variable_set :@parent, saved_bounds
    bounds_copy.single_file if ColumnBox === bounds_copy
  end
  scratch_pdf.move_cursor_to cursor unless (scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
  scratch_start_cursor = scratch_pdf.cursor
  scratch_start_page = scratch_pdf.page_number
  inhibit_new_page = state.on_page_create_callback == InhibitNewPageProc
  restart = nil
  scratch_pdf.font font_family, size: font_size, style: font_style do
    prev_font_scale, scratch_pdf.font_scale = scratch_pdf.font_scale, font_scale
    if keep_together || inhibit_new_page
      if (restart = scratch_pdf.perform_on_single_page { scratch_pdf.instance_exec(&block) })
        # NOTE: propogate NewPageRequiredError from nested block, which is rendered in separate scratch document
        raise NewPageRequiredError if inhibit_new_page
        if single_page
          raise ::Prawn::Errors::CannotFit if single_page == :enforce
          # single_page and onto are mutually exclusive
          return ScratchExtent.new scratch_start_page, scratch_start_cursor, scratch_start_page, 0
        end
      end
    elsif scratch_start_at_top
      scratch_pdf.instance_exec(&block)
    elsif (restart = scratch_pdf.stop_if_first_page_empty { scratch_pdf.instance_exec(&block) })
      pages_advanced += 1
    end
  ensure
    scratch_pdf.font_scale = prev_font_scale
  end
  return dry_run pages_advanced: pages_advanced, onto: onto, &block if restart
  scratch_end_page = scratch_pdf.page_number - scratch_start_page + (scratch_start_page = 1)
  if pages_advanced > 0
    scratch_start_page += pages_advanced
    scratch_end_page += pages_advanced
  end
  scratch_end_cursor = scratch_pdf.cursor
  # NOTE: drop trailing blank page and move cursor to end of previous page
  if scratch_end_page > scratch_start_page && scratch_pdf.at_page_top?
    scratch_end_page -= 1
    scratch_end_cursor = 0
  end
  extent = ScratchExtent.new scratch_start_page, scratch_start_cursor, scratch_end_page, scratch_end_cursor
  onto ? extent.position_onto(*onto) : extent
ensure
  scratch_pdf.bounds = saved_bounds
end
effective_page_height() click to toggle source

Returns the effective (writable) height of the page

If inside a fixed-height bounding box, returns height of box.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 155
def effective_page_height
  reference_bounds.height
end
expand_grid_values(shorthand, default = nil) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 638
def expand_grid_values shorthand, default = nil
  if ::Array === shorthand
    case shorthand.size
    when 1
      [(value0 = shorthand[0] || default), value0]
    when 2
      shorthand.map {|it| it || default }
    when 4
      if Asciidoctor::PDF::ThemeLoader::CMYKColorValue === shorthand
        [shorthand, shorthand]
      else
        (shorthand.slice 0, 2).map {|it| it || default }
      end
    else
      (shorthand.slice 0, 2).map {|it| it || default }
    end
  else
    [(value0 = shorthand || default), value0]
  end
end
expand_indent_value(value) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 610
def expand_indent_value value
  (::Array === value ? (value.slice 0, 2) : (::Array.new 2, value)).map(&:to_f)
end
expand_margin_value(shorthand)
expand_padding_value(shorthand) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 614
def expand_padding_value shorthand
  (@edge_shorthand_cache ||= ::Hash.new do |store, key|
    if ::Array === key
      case key.size
      when 1
        value = [(value0 = key[0] || 0), value0, value0, value0]
      when 2
        value = [(value0 = key[0] || 0), (value1 = key[1] || 0), value0, value1]
      when 3
        value = [key[0] || 0, (value1 = key[1] || 0), key[2] || 0, value1]
      when 4
        value = key.map {|it| it || 0 }
      else
        value = (key.slice 0, 4).map {|it| it || 0 }
      end
    else
      value = [(value0 = key || 0), value0, value0, value0]
    end
    store[key] = value
  end)[shorthand]
end
Also aliased as: expand_margin_value
expand_rect_values(shorthand, default = nil) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 659
def expand_rect_values shorthand, default = nil
  if ::Array === shorthand
    case shorthand.size
    when 1
      [(value0 = shorthand[0] || default), value0, value0, value0]
    when 2
      [(value0 = shorthand[0] || default), (value1 = shorthand[1] || default), value0, value1]
    when 3
      [shorthand[0] || default, (value1 = shorthand[1] || default), shorthand[2] || default, value1]
    when 4
      if Asciidoctor::PDF::ThemeLoader::CMYKColorValue === shorthand
        [shorthand, shorthand, shorthand, shorthand]
      else
        shorthand.map {|it| it || default }
      end
    else
      (shorthand.slice 0, 4).map {|it| it || default }
    end
  else
    [(value0 = shorthand || default), value0, value0, value0]
  end
end
fill_absolute_bounds(f_color) click to toggle source

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/ext/prawn/extensions.rb, line 732
def fill_absolute_bounds f_color
  canvas { fill_bounds f_color }
end
fill_and_stroke_bounds(f_color = fill_color, s_color = stroke_color, options = {}) click to toggle source

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/ext/prawn/extensions.rb, line 741
def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {}
  no_fill = !f_color || f_color == 'transparent'
  if ::Array === (s_width = options[:line_width] || 0)
    s_width = [s_width[0], s_width[1], s_width[0], s_width[1]] if s_width.size == 2
    s_width_max = (s_width = s_width.map {|it| it || 0 }).max
    radius = 0
  else
    radius = options[:radius] || 0
  end
  no_stroke = !s_color || s_color == 'transparent' || (s_width_max || s_width) == 0
  return if no_fill && no_stroke
  save_graphics_state do
    # fill
    unless no_fill
      fill_color f_color
      fill_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
    end

    next if no_stroke

    # stroke
    if s_width_max
      s_width_top, s_width_right, s_width_bottom, s_width_left = s_width
      projection_top, projection_right, projection_bottom, projection_left = s_width.map {|it| it * 0.5 }
      if s_width_top > 0
        stroke_horizontal_rule s_color, line_width: s_width_top, line_style: options[:line_style], left_projection: projection_left, right_projection: projection_right
      end
      if s_width_right > 0
        stroke_vertical_rule s_color, line_width: s_width_right, line_style: options[:line_style], at: bounds.width, top_projection: projection_top, bottom_projection: projection_bottom
      end
      if s_width_bottom > 0
        stroke_horizontal_rule s_color, line_width: s_width_bottom, line_style: options[:line_style], at: bounds.height, left_projection: projection_left, right_projection: projection_right
      end
      if s_width_left > 0
        stroke_vertical_rule s_color, line_width: s_width_left, line_style: options[:line_style], top_projection: projection_top, bottom_projection: projection_bottom
      end
    else
      stroke_color s_color
      case options[:line_style]
      when :dashed
        line_width s_width
        dash s_width * 4
      when :dotted
        line_width s_width
        dash s_width
      when :double
        single_line_width = s_width / 3.0
        line_width single_line_width
        inner_line_offset = single_line_width * 2
        inner_top_left = [bounds.left + inner_line_offset, bounds.top - inner_line_offset]
        stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
        stroke_rounded_rectangle inner_top_left, bounds.width - (inner_line_offset * 2), bounds.height - (inner_line_offset * 2), radius
        next
      else # :solid
        line_width s_width
      end
      stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
    end
  end
end
fill_bounds(f_color) click to toggle source

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/ext/prawn/extensions.rb, line 722
def fill_bounds f_color
  prev_fill_color = fill_color
  fill_color f_color
  fill_rectangle bounds.top_left, bounds.width, bounds.height
  fill_color prev_fill_color
end
fill_formatted_text_box(text, options) click to toggle source

NOTE: override built-in fill_formatted_text_box to insert leading before second line when :first_line is true

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 437
def fill_formatted_text_box text, options
  if (initial_gap = options[:initial_gap]) && !text.empty? && text[0][:from_page] != page_number
    self.y -= initial_gap
  end
  merge_text_box_positioning_options options
  box = ::Prawn::Text::Formatted::Box.new text, options
  remaining_fragments = box.render
  @no_text_printed = box.nothing_printed?
  @all_text_printed = box.everything_printed?
  unless remaining_fragments.empty? || (remaining_fragments[0][:from_page] ||= page_number) == page_number
    log :error, %(cannot fit formatted text on page: #{remaining_fragments.map {|it| it[:image_path] || it[:text] }.join})
    page.tare_content_stream
    remaining_fragments = {}
  end

  if @final_gap || (options[:first_line] && !(@no_text_printed || @all_text_printed))
    self.y -= box.height + box.line_gap + box.leading
  else
    self.y -= box.height
  end

  remaining_fragments
end
float() { || ... } click to toggle source

Override the built-in float method to add support for restoring the current column of a ColumnBox

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 532
def float
  original_page_number = page_number
  original_y = y
  original_column = bounds.current_column if ColumnBox === bounds
  yield
  go_to_page original_page_number unless page_number == original_page_number
  self.y = original_y
  bounds.current_column = original_column if original_column
end
flow_bounding_box(options = {}) { || ... } click to toggle source

A flowing version of bounding_box. If the content runs to another page, the cursor starts at the top of the page instead of from the original cursor position. Similar to span, except the :position option is limited to a numeric value and additional options are passed through to bounding_box.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 707
def flow_bounding_box options = {}
  original_y, original_x = y, bounds.absolute_left
  canvas do
    bounding_box [original_x + (options.delete :position).to_f, @margin_box.absolute_top], options do
      self.y = original_y
      yield
    end
  end
end
font(name = nil, options = {}) click to toggle source

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.

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 321
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
end
font_family() click to toggle source

Retrieves the current font name (i.e., family).

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 334
def font_family
  font.options[:family]
end
Also aliased as: font_name
font_info() click to toggle source

Retrieves the current font info (family, style, size) as a Hash

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 342
def font_info
  { family: font.options[:family], style: (font.options[:style] || :normal), size: @font_size }
end
font_name()
Alias for: font_family
font_size(points = nil) click to toggle source

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.

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 362
def font_size points = nil
  return @font_size unless points
  if ::String === points
    if points.end_with? 'rem'
      super @root_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)
    else
      super points.to_f
    end
  else
    super points
  end
end
font_style(style = nil) click to toggle source

Set the font style on the document, if a style is given, otherwise return the current font style.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 348
def font_style style = nil
  if style
    font font.options[:family], style: style
  else
    font.options[:style] || :normal
  end
end
font_styles(style = font_style) click to toggle source

Retreives the collection of font styles from the given font style key, which defaults to the current font style.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 397
def font_styles style = font_style
  FontStyleToSet[style].dup
end
generate_margin_box() click to toggle source

remove once fixed upstream; see github.com/prawnpdf/prawn/pull/1122

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 160
def generate_margin_box
  page_w, page_h = (page = state.page).dimensions.slice 2, 2
  page_m = page.margins
  prev_margin_box, @margin_box = @margin_box, (::Prawn::Document::BoundingBox.new self, nil, [page_m[:left], page_h - page_m[:top]], width: page_w - page_m[:left] - page_m[:right], height: page_h - page_m[:top] - page_m[:bottom])

  # update bounding box if not flowing from the previous page
  unless @bounding_box&.parent
    prev_margin_box = @bounding_box
    @bounding_box = @margin_box
  end

  # maintains indentation settings across page breaks
  if prev_margin_box
    @margin_box.add_left_padding prev_margin_box.total_left_padding
    @margin_box.add_right_padding prev_margin_box.total_right_padding
  end

  nil
end
get_dest(name, node = dests.data) click to toggle source

Gets the destination registered for the specified name. The return value matches that which was passed to the add_dest method.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 288
def get_dest name, node = dests.data
  node.children.each do |child|
    if ::PDF::Core::NameTree::Value === child
      return child.value.data if child.name == name
    elsif (found = get_dest name, child)
      return found
    end
  end
  nil
end
hyphenate_text(text, hyphenator) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 524
def hyphenate_text text, hyphenator
  hyphenate_words_pcdata text, hyphenator
end
icon_font_data(family) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 407
def icon_font_data family
  ::Prawn::Icon::FontData.load self, family
end
image_page(file, options = {}) click to toggle source

Create a new page for the specified image.

The image is positioned relative to the boundaries of the page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 929
def image_page file, options = {}
  start_new_page_discretely
  ex = nil
  float do
    canvas do
      image file, ({ position: :center, vposition: :center }.merge options)
    rescue
      ex = $!
    end
  end
  raise ex if ex
  nil
end
import_page(file, options = {}) { || ... } click to toggle source

Import the specified page into the current document.

By default, advance to the next page afterwards, creating it if necessary. This behavior can be disabled by passing the option ‘advance: false`. However, due to how page creation works in Prawn, understand that advancing to the next page is necessary to prevent the size & layout of the imported page from affecting a newly created page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 894
def import_page file, options = {}
  prev_page_layout = page.layout
  prev_page_size = page.size
  prev_page_margin = page_margin
  prev_bounds = bounds
  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_current_page if options[:replace]
  # NOTE: use functionality provided by prawn-templates
  start_new_page_discretely template: file, template_page: options[:page]
  # prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert
  @text_rendering_mode = prev_text_rendering_mode
  if page.imported_page?
    yield if block_given?
    # 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
    if options.fetch :advance, true
      advance_page layout: prev_page_layout, margin: prev_page_margin, size: prev_page_size
      (@bounding_box = prev_bounds).reset_top if ColumnBox === prev_bounds
    end
  elsif options.fetch :advance_if_missing, true
    delete_current_page
    # NOTE: see previous comment
    advance_page layout: prev_page_layout, margin: prev_page_margin, size: prev_page_size
    @y = (@bounding_box = prev_bounds).reset_top if ColumnBox === prev_bounds
  else
    delete_current_page
  end
  nil
end
last_page?() click to toggle source

Returns whether the current page is the last page in the document.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 270
def last_page?
  page_number == page_count
end
min_version() click to toggle source

Retrieves the compatiblity version of the PDF.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 133
def min_version
  state.version
end
move_down(n) click to toggle source

Short-circuits the call to the built-in move_down operation when n is 0.

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 558
def move_down n
  super unless n == 0
end
move_text_position(h;) click to toggle source

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/ext/prawn/extensions.rb, line 553
def move_text_position h; end
move_up(n) click to toggle source

Short-circuits the call to the built-in move_up operation when n is 0.

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 545
def move_up n
  super unless n == 0
end
pad_box(padding, node = nil) { || ... } click to toggle source

Augments the built-in pad method by adding support for specifying padding on all four sizes.

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 from all edges.'
end

pad_box [10, 5] do
  text 'A paragraph inside a box with different padding from ends and sides.'
end

pad_box [5, 10, 15, 20] do
  text 'A paragraph inside a box with different padding from each edge.'
end
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 585
def pad_box padding, node = nil
  if padding
    p_top, p_right, p_bottom, p_left = expand_padding_value padding
    # logic is intentionally inlined
    begin
      if node && ((last_block = node).content_model != :compound || (last_block = node.last_child)&.context == :paragraph)
        @bottom_gutters << { last_block => p_bottom }
      else
        @bottom_gutters << {}
      end
      move_down p_top
      bounds.add_left_padding p_left
      bounds.add_right_padding p_right
      yield
    ensure
      cursor > p_bottom ? (move_down p_bottom) : bounds.move_past_bottom unless at_page_top?
      @bottom_gutters.pop
      bounds.subtract_left_padding p_left
      bounds.subtract_right_padding p_right
    end
  else
    yield
  end
end
page_height() click to toggle source

Returns the height of the current page from edge-to-edge

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 147
def page_height
  page.dimensions[3]
end
page_margin() click to toggle source

Returns the margins for the current page as a 4 element array (top, right, bottom, left)

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 190
def page_margin
  [page_margin_top, page_margin_right, page_margin_bottom, page_margin_left]
end
page_margin_bottom() click to toggle source

Returns the width of the bottom margin for the current page

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 214
def page_margin_bottom
  page.margins[:bottom]
end
page_margin_left() click to toggle source

Returns the width of the left margin for the current page

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 196
def page_margin_left
  page.margins[:left]
end
page_margin_right() click to toggle source

Returns the width of the right margin for the current page

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 202
def page_margin_right
  page.margins[:right]
end
page_margin_top() click to toggle source

Returns the width of the top margin for the current page

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 208
def page_margin_top
  page.margins[:top]
end
page_side(pgnum = nil, invert = nil) click to toggle source

Returns the side the current page is facing, :recto or :verso.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 232
def page_side pgnum = nil, invert = nil
  if invert
    (recto_page? pgnum) ? :verso : :recto
  else
    (verso_page? pgnum) ? :verso : :recto
  end
end
page_width() click to toggle source

Returns the width of the current page from edge-to-edge

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 141
def page_width
  page.dimensions[2]
end
parse_text(string, options = {}) click to toggle source

Parse the text into an array of fragments using the text formatter.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 425
def parse_text string, options = {}
  return [] if string.nil?

  if (format_option = options[:inline_format])
    format_option = [] unless ::Array === format_option
    text_formatter.format string, *format_option
  else
    [text: string]
  end
end
perform_discretely() { || ... } click to toggle source

Perform an operation (such as creating a new page) without triggering the on_page_create callback

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 945
def perform_discretely
  state.on_page_create_callback = nil if (saved_callback = state.on_page_create_callback) != InhibitNewPageProc
  yield
ensure
  state.on_page_create_callback = saved_callback
end
perform_on_single_page() { || ... } click to toggle source

This method installs an on_page_create_callback that stops processing if the first page is exceeded while yielding to the specified block. If the content fits on a single page, the processing is not stopped. The purpose of this method is to determine if the content fits on a single page.

Returns a Boolean indicating whether the content fits on a single page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 1023
def perform_on_single_page
  saved_callback, state.on_page_create_callback = state.on_page_create_callback, InhibitNewPageProc
  yield
  false
rescue NewPageRequiredError
  true
ensure
  state.on_page_create_callback = saved_callback
end
recto_page?(pgnum = nil) click to toggle source

Returns whether the page is a recto page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 242
def recto_page? pgnum = nil
  (pgnum || page_number).odd?
end
register_font(data) click to toggle source

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/ext/prawn/extensions.rb, line 313
def register_font data
  font_families.update data.transform_keys(&:to_s)
end
resolve_font_style(styles) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 384
def resolve_font_style styles
  if styles.include? :bold
    (styles.include? :italic) ? :bold_italic : :bold
  elsif styles.include? :italic
    :italic
  else
    :normal
  end
end
resolve_legacy_icon_name(name) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 411
def resolve_legacy_icon_name name
  ::Prawn::Icon::Compatibility::SHIMS[%(fa-#{name})]
end
scratch() click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 971
def scratch
  @scratch ||= ((Marshal.load Marshal.dump @scratch_prototype).send :init_scratch, self)
end
scratch?() click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 975
def scratch?
  @label == :scratch
end
set_font(font, size = nil) click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 379
def set_font font, size = nil
  @font = font
  font_size size if size
end
set_page_margin(margin) click to toggle source

Set the margins for the current page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 182
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
span_page_width_if(verdict) { || ... } click to toggle source

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/ext/prawn/extensions.rb, line 686
def span_page_width_if verdict
  if verdict
    indent(-bounds_margin_left, -bounds_margin_right) do
      yield
    end
  else
    yield
  end
end
start_new_page_discretely(options = {}) click to toggle source

Start a new page without triggering the on_page_create callback

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 961
def start_new_page_discretely options = {}
  perform_discretely { start_new_page options }
end
stop_if_first_page_empty() { || ... } click to toggle source

This method installs an on_page_create_callback that stops processing if a new page is created without writing content to the first page while yielding to the specified block. If any content is written to the first page, processing is not stopped. The purpose of this method is to check whether any content fits on the remaining space on the current page.

Returns a Boolean indicating whether any content is written on the first page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 1039
def stop_if_first_page_empty
  delegate = state.on_page_create_callback
  state.on_page_create_callback = DetectEmptyFirstPageProc.curry[delegate].extend DetectEmptyFirstPage
  yield
  false
rescue NewPageRequiredError
  true
ensure
  state.on_page_create_callback = delegate
end
stroke_horizontal_rule(rule_color = stroke_color, options = {}) click to toggle source

Strokes a horizontal line using the current bounds. The width of the line can be specified using the line_width option. The offset from the cursor can be set using the at option.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 806
def stroke_horizontal_rule rule_color = stroke_color, options = {}
  rule_y = cursor - (options[:at] || 0)
  rule_style = options[:line_style]
  rule_width = options[:line_width] || 0.5
  rule_x_start = bounds.left - (options[:left_projection] || 0)
  rule_x_end = bounds.right - (options[:right_projection] || 0)
  save_graphics_state do
    stroke_color rule_color
    case rule_style
    when :dashed
      line_width rule_width
      dash rule_width * 4
    when :dotted
      line_width rule_width
      dash rule_width
    when :double
      single_rule_width = rule_width / 3.0
      line_width single_rule_width
      stroke_horizontal_line rule_x_start, rule_x_end, at: (rule_y + single_rule_width)
      stroke_horizontal_line rule_x_start, rule_x_end, at: (rule_y - single_rule_width)
      next
    else # :solid
      line_width rule_width
    end
    stroke_horizontal_line rule_x_start, rule_x_end, at: rule_y
  end
end
stroke_vertical_rule(rule_color = stroke_color, options = {}) click to toggle source

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/ext/prawn/extensions.rb, line 839
def stroke_vertical_rule rule_color = stroke_color, options = {}
  rule_x = options[:at] || 0
  rule_y_from = bounds.top + (options[:top_projection] || 0)
  rule_y_to = bounds.bottom - (options[:bottom_projection] || 0)
  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
tare_first_page_content_stream() { || ... } click to toggle source

This method delegates to the provided block, then tares (i.e., resets) the content stream of the initial page.

The purpose of this method is to ink content while making it appear as though the page is empty. This technique allows the caller to detect whether any subsequent content was written to the page following the content inked by the block. It’s often used to keep the title of a content block with the block’s first child.

NOTE: this method should only used inside dry_run since that’s when DetectEmptyFirstPage is active

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 1059
def tare_first_page_content_stream
  return yield unless DetectEmptyFirstPage === (delegate = state.on_page_create_callback)
  on_page_create_called = nil
  state.on_page_create_callback = proc do |pdf|
    on_page_create_called = true
    pdf.state.pages[pdf.page_number - 2].tare_content_stream
    delegate.call pdf
  end
  begin
    yield
  ensure
    page.tare_content_stream unless on_page_create_called
    state.on_page_create_callback = delegate
  end
end
text_with_formatted_first_line(string, first_line_options, options) click to toggle source

Performs the same work as Prawn::Text.text except that the first_line_options 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/ext/prawn/extensions.rb, line 469
def text_with_formatted_first_line string, first_line_options, options
  if (first_line_font_color = first_line_options.delete :color)
    remaining_lines_font_color, options[:color] = options[:color], first_line_font_color
  end
  fragments = parse_text string, options
  # NOTE: the low-level APIs we're using don't recognize the :styles option, so we must resolve
  # NOTE: disabled until we have a need for it; currently handled in convert_abstract
  #if (styles = options.delete :styles)
  #  options[:style] = resolve_font_style styles
  #end
  if (first_line_styles = first_line_options.delete :styles)
    first_line_options[:style] = resolve_font_style first_line_styles
  end
  first_line_text_transform = first_line_options.delete :text_transform
  options = options.merge document: self
  @final_gap = final_gap = options.delete :final_gap
  text_indent = options.delete :indent_paragraphs
  # QUESTION: should we merge more carefully here? (hand-select keys?)
  first_line_options = (options.merge first_line_options).merge single_line: true, first_line: true
  box = ::Prawn::Text::Formatted::Box.new fragments, first_line_options
  if text_indent
    remaining_fragments = indent(text_indent) { box.render dry_run: true }
  else
    remaining_fragments = box.render dry_run: true
  end
  if remaining_fragments.empty?
    remaining_fragments = nil
  elsif (remaining_fragments[0][:from_page] ||= page_number) != page_number
    log :error, %(cannot fit formatted text on page: #{remaining_fragments.map {|it| it[:image_path] || it[:text] }.join})
    page.tare_content_stream
    remaining_fragments = nil
  end
  if first_line_text_transform
    # NOTE: applying text transform here could alter the wrapping, so isolate first line and shrink it to fit
    first_line_fragments = (box.instance_variable_get :@arranger).consumed
    fragments = first_line_fragments.map {|fragment| fragment.merge text: (transform_text fragment[:text], first_line_text_transform) }
    first_line_options[:overflow] = :shrink_to_fit
    if remaining_fragments
      @final_gap = true
      first_line_options[:force_justify] = true if first_line_options[:align] == :justify && first_line_fragments[-1][:text] != ?\n
    end
  end
  if text_indent
    indent(text_indent) { fill_formatted_text_box fragments, first_line_options }
  else
    fill_formatted_text_box fragments, first_line_options
  end
  if remaining_fragments
    options[:color] = remaining_lines_font_color if first_line_font_color
    @final_gap = final_gap if first_line_text_transform
    remaining_fragments = fill_formatted_text_box remaining_fragments, options
    draw_remaining_formatted_text_on_new_pages remaining_fragments, options
  end
end
verso_page?(pgnum = nil) click to toggle source

Returns whether the page is a verso page.

# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 248
def verso_page? pgnum = nil
  (pgnum || page_number).even?
end
width_of_string(string, options) click to toggle source

Override width of string to check for placeholder char, which uses character spacing to control width

Calls superclass method
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 403
def width_of_string string, options
  string == PlaceholderChar ? @character_spacing : super
end
with_dry_run() { |dry_run(&block).position_onto self, cursor| ... } click to toggle source
# File lib/asciidoctor/pdf/ext/prawn/extensions.rb, line 979
def with_dry_run &block
  yield dry_run(&block).position_onto self, cursor
end