<commit_before>require 'prawn'
require 'prawn-svg'
require 'asciidoctor/prawn'
require 'roman_numeral'

Prawn::Document.extensions << Asciidoctor::Prawn::Extensions

module Asciidoctor
class PDFRenderer < ::Prawn::Document
  include ::Prawn::Measurements

  def self.unicode_char number
    [number].pack 'U*'
  end

  FONTS_DIR = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', 'data', 'fonts'))

  IndentationPattern = /^ +/
  TabSpaces = ' ' * 4
  NonBreakingSpace = unicode_char 0x00a0
  EmDash = unicode_char 0x2014
  LowercaseGreekA = unicode_char 0x03b1
  Bullets = {
    disc: (unicode_char 0x2022),
    circle: (unicode_char 0x25e6),
    square: (unicode_char 0x25aa)
  }
  BuiltInCharEntities = {
    '&lt;' => '<',
    '&gt;' => '>',
    '&amp;' => '&'
  }
  BuiltInCharEntitiesPattern = /(?:#{BuiltInCharEntities.keys * '|'})/
  AdmonitionIcons = {
    note: (unicode_char 0xf0eb)
  }

  def self.render doc, output_filename, theme, options = {}
    options = build_document_options doc, theme, options
    generate output_filename, options do
      @theme = theme
      @output_filename = output_filename
      register_fonts
      init_scratch_prototype
      @font_color = @theme.base_font_color
      render_document_node doc
      #@prototype.render_file 'scratch.pdf'
      generate_pdfmarks_file doc
    end
  end

  def font_path font_file
    ::File.join FONTS_DIR, font_file
  end

  def register_fonts
    # FIXME read from theme
    register_font LiberationSans: {
      normal: (font_path 'liberation_sans-normal.ttf'),
      bold: (font_path 'liberation_sans-bold.ttf'),
      italic: (font_path 'liberation_sans-italic.ttf'),
      bold_italic: (font_path 'liberation_sans-bold_italic.ttf')
    }

    register_font LiberationMono: {
      normal: (font_path 'liberation_mono-normal.ttf'),
      bold: (font_path 'liberation_mono-bold.ttf'),
      italic: (font_path 'liberation_mono-italic.ttf'),
      bold_italic: (font_path 'liberation_mono-bold_italic.ttf')
    }

    #register_font FontAwesome: {
    #  normal: (font_path 'fontawesome-webfont.ttf')
    #}

    #@fallback_fonts ||= []
    #@fallback_fonts << 'LiberationSans'
    default_kerning true
  end

  def render_node node
    if node.is_a? ::Asciidoctor::AbstractBlock
      context = node.context
      node.document.playback_attributes node.attributes unless context == :document
      dispatch = %(render_#{context}_node).to_sym

      if respond_to? dispatch
        send dispatch, node
      else
        render_fallback node
      end
    end
  end

  def render_children node
    node.blocks.each {|child| render_node child } if node.blocks?
  end

  def render_section_content section
    render_children section
  end

  # TODO honor content model
  def render_node_content node
    if node.blocks?
      render_children node
    elsif (string = node.content)
      prose string
    end
  end

  def render_list_item_content node
    prose node.text if node.text?
    render_children node
  end

  def render_fallback node
    puts "Unhandled node: #{node}"
  end

  def theme_fill_and_stroke_bounds category
    fill_and_stroke_bounds @theme[%(#{category}_background_color)], @theme[%(#{category}_border_color)], {
      line_width: @theme[%(#{category}_border_width)],
      radius: @theme[%(#{category}_border_radius)]
    }
  end

  def theme_font category, options = {}
    # QUESTION should we fallback to base_font_* or just leave current setting?
    family = @theme[%(#{category}_font_family)] || @theme.base_font_family
    if (level = options[:level])
      size = @theme[%(#{category}_font_size_h#{level})] || @theme.base_font_size
    else
      size = @theme[%(#{category}_font_size)] || @theme.base_font_size
    end
    style = (@theme[%(#{category}_font_style)] || :normal).to_sym
    prev_font_color = @font_color
    @font_color = @theme[%(#{category}_font_color)] || prev_font_color
    font family, size: size, style: style do
      yield
    end
    @font_color = prev_font_color
  end

  def render_document_node doc
    if (bg_color = @theme.page_background_color) && !(['transparent', 'FFFFFF'].include? bg_color)
      on_page_create do
        canvas do
          fill_bounds bg_color
        end
      end
    end

    start_new_page

    font @theme.base_font_family, size: @theme.base_font_size

    # FIXME move me to a title page method!
    # only create title page if doctype=book!
    theme_font :heading, level: 1 do
      heading doc.doctitle, align: :center
    end
    move_down @theme.vertical_rhythm
    if doc.attr? 'authors'
      prose doc.attr('authors'), align: :center, margin_bottom: @theme.vertical_rhythm / 2
    end
    prose [(doc.attr? 'revnumber') ? %(v#{doc.attr 'revnumber'}) : nil, (doc.attr 'revdate')].compact * ', ', align: :center
    start_new_page

    @list_numbers = []
    @list_bullets = []
    render_children doc

    add_page_numbers
    add_outline doc
  end

  def render_section_node section
    theme_font :heading, level: (section.level + 1) do
      sect_title = section.title
      # FIXME someone hackish...need to sort out a cleaner approach here
      if cursor < (height_of sect_title) + @theme.heading_margin_top + @theme.heading_margin_bottom + @theme.base_line_height_length * 1.5
        start_new_page
      end<commit_msg>use version label on title page, put date on new line<commit_after>require 'prawn'
require 'prawn-svg'
require 'asciidoctor/prawn'
require 'roman_numeral'

Prawn::Document.extensions << Asciidoctor::Prawn::Extensions

module Asciidoctor
class PDFRenderer < ::Prawn::Document
  include ::Prawn::Measurements

  def self.unicode_char number
    [number].pack 'U*'
  end

  FONTS_DIR = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', 'data', 'fonts'))

  IndentationPattern = /^ +/
  TabSpaces = ' ' * 4
  NonBreakingSpace = unicode_char 0x00a0
  EmDash = unicode_char 0x2014
  LowercaseGreekA = unicode_char 0x03b1
  Bullets = {
    disc: (unicode_char 0x2022),
    circle: (unicode_char 0x25e6),
    square: (unicode_char 0x25aa)
  }
  BuiltInCharEntities = {
    '&lt;' => '<',
    '&gt;' => '>',
    '&amp;' => '&'
  }
  BuiltInCharEntitiesPattern = /(?:#{BuiltInCharEntities.keys * '|'})/
  AdmonitionIcons = {
    note: (unicode_char 0xf0eb)
  }

  def self.render doc, output_filename, theme, options = {}
    options = build_document_options doc, theme, options
    generate output_filename, options do
      @theme = theme
      @output_filename = output_filename
      register_fonts
      init_scratch_prototype
      @font_color = @theme.base_font_color
      render_document_node doc
      #@prototype.render_file 'scratch.pdf'
      generate_pdfmarks_file doc
    end
  end

  def font_path font_file
    ::File.join FONTS_DIR, font_file
  end

  def register_fonts
    # FIXME read from theme
    register_font LiberationSans: {
      normal: (font_path 'liberation_sans-normal.ttf'),
      bold: (font_path 'liberation_sans-bold.ttf'),
      italic: (font_path 'liberation_sans-italic.ttf'),
      bold_italic: (font_path 'liberation_sans-bold_italic.ttf')
    }

    register_font LiberationMono: {
      normal: (font_path 'liberation_mono-normal.ttf'),
      bold: (font_path 'liberation_mono-bold.ttf'),
      italic: (font_path 'liberation_mono-italic.ttf'),
      bold_italic: (font_path 'liberation_mono-bold_italic.ttf')
    }

    #register_font FontAwesome: {
    #  normal: (font_path 'fontawesome-webfont.ttf')
    #}

    #@fallback_fonts ||= []
    #@fallback_fonts << 'LiberationSans'
    default_kerning true
  end

  def render_node node
    if node.is_a? ::Asciidoctor::AbstractBlock
      context = node.context
      node.document.playback_attributes node.attributes unless context == :document
      dispatch = %(render_#{context}_node).to_sym

      if respond_to? dispatch
        send dispatch, node
      else
        render_fallback node
      end
    end
  end

  def render_children node
    node.blocks.each {|child| render_node child } if node.blocks?
  end

  def render_section_content section
    render_children section
  end

  # TODO honor content model
  def render_node_content node
    if node.blocks?
      render_children node
    elsif (string = node.content)
      prose string
    end
  end

  def render_list_item_content node
    prose node.text if node.text?
    render_children node
  end

  def render_fallback node
    puts "Unhandled node: #{node}"
  end

  def theme_fill_and_stroke_bounds category
    fill_and_stroke_bounds @theme[%(#{category}_background_color)], @theme[%(#{category}_border_color)], {
      line_width: @theme[%(#{category}_border_width)],
      radius: @theme[%(#{category}_border_radius)]
    }
  end

  def theme_font category, options = {}
    # QUESTION should we fallback to base_font_* or just leave current setting?
    family = @theme[%(#{category}_font_family)] || @theme.base_font_family
    if (level = options[:level])
      size = @theme[%(#{category}_font_size_h#{level})] || @theme.base_font_size
    else
      size = @theme[%(#{category}_font_size)] || @theme.base_font_size
    end
    style = (@theme[%(#{category}_font_style)] || :normal).to_sym
    prev_font_color = @font_color
    @font_color = @theme[%(#{category}_font_color)] || prev_font_color
    font family, size: size, style: style do
      yield
    end
    @font_color = prev_font_color
  end

  def render_document_node doc
    if (bg_color = @theme.page_background_color) && !(['transparent', 'FFFFFF'].include? bg_color)
      on_page_create do
        canvas do
          fill_bounds bg_color
        end
      end
    end

    start_new_page

    font @theme.base_font_family, size: @theme.base_font_size

    # FIXME move me to a title page method!
    # only create title page if doctype=book!
    theme_font :heading, level: 1 do
      heading doc.doctitle, align: :center
    end
    move_down @theme.vertical_rhythm
    if doc.attr? 'authors'
      prose doc.attr('authors'), align: :center, margin_bottom: @theme.vertical_rhythm / 2
    end
    prose [(doc.attr? 'revnumber') ? %(#{doc.attr 'version-label'} #{doc.attr 'revnumber'}) : nil, (doc.attr 'revdate')].compact * "\n", align: :center, margin_top: @theme.vertical_rhythm * 5, normalize: false
    start_new_page

    @list_numbers = []
    @list_bullets = []
    render_children doc

    add_page_numbers
    add_outline doc
  end

  def render_section_node section
    theme_font :heading, level: (section.level + 1) do
      sect_title = section.title
      # FIXME someone hackish...need to sort out a cleaner approach here
      if cursor < (height_of sect_title) + @theme.heading_margin_top + @theme.heading_margin_bottom + @theme.base_line_height_length * 1.5
        start_new_page
      end
{'fraction_non_alphanumeric': 0.10944305439718284, 'fraction_numerical': 0.0062088777685107965, 'mean_word_length': 3.4193284193284192, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3374125', 'n_tokens_mistral': 3717, 'n_tokens_neox': 3585, 'n_words': 799}