Class: Rouge::Formatters::Prawn

Inherits:
Formatter
  • Object
show all
Defined in:
lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb

Overview

Transforms a token stream into an array of formatted text fragments for use with Prawn.

Defined Under Namespace

Classes: BackgroundColorizer

Constant Summary collapse

Tokens =
::Rouge::Token::Tokens
LineOrientedTokens =
[
  ::Rouge::Token::Tokens::Generic::Inserted,
  ::Rouge::Token::Tokens::Generic::Deleted,
  ::Rouge::Token::Tokens::Generic::Heading,
  ::Rouge::Token::Tokens::Generic::Subheading,
]
LF =
?\n
DummyText =
?\u0000
NoBreakSpace =
?\u00a0
InnerIndent =
%(#{LF} )
GuardedIndent =
NoBreakSpace
GuardedInnerIndent =
%(#{LF}#{NoBreakSpace})
BoldStyle =
[:bold].to_set
ItalicStyle =
[:italic].to_set
BoldItalicStyle =
[:bold, :italic].to_set
UnderlineStyle =
[:underline].to_set

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Prawn

Returns a new instance of Prawn.



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 29

def initialize opts = {}
  unless ::Rouge::Theme === (theme = opts[:theme])
    unless theme && ((::Class === theme && theme < ::Rouge::Theme) || (theme = ::Rouge::Theme.find theme))
      theme = ::Rouge::Themes::AsciidoctorPDFDefault
    end
    theme = theme.new
  end
  @theme = theme
  @normalized_colors = {}
  @background_colorizer = BackgroundColorizer.new line_gap: opts[:line_gap]
  @linenum_fragment_base = create_fragment Tokens::Generic::Lineno
  @highlight_line_fragment = create_highlight_line_fragment opts[:highlight_background_color]
end

Instance Method Details

#background_colorObject



43
44
45
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 43

def background_color
  @background_color ||= (normalize_color ((@theme.style_for Tokens::Text) || (::Rouge::Theme::Style.new @theme)).bg)
end

#create_fragment(tok, val = nil) ⇒ Object

TODO: method could still be optimized (for instance, check if val is LF or empty)



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 121

def create_fragment tok, val = nil
  fragment = val ? { text: val } : {}
  if (style_rules = @theme.style_for tok)
    if (bg = normalize_color style_rules.bg) && bg != background_color
      fragment[:background_color] = bg
      fragment[:callback] = @background_colorizer
      if LineOrientedTokens.include? tok
        fragment[:inline_block] = true unless style_rules[:inline_block] == false
        fragment[:extend] = true unless style_rules[:extend] == false
      else
        fragment[:inline_block] = true if style_rules[:inline_block]
        fragment[:extend] = true if style_rules[:extend]
      end
    end
    if (fg = normalize_color style_rules.fg)
      fragment[:color] = fg
    end
    if style_rules[:bold]
      fragment[:styles] = (style_rules[:italic] ? BoldItalicStyle : BoldStyle).dup
    elsif style_rules[:italic]
      fragment[:styles] = ItalicStyle.dup
    end
    if style_rules[:underline]
      if fragment.key? :styles
        fragment[:styles] |= UnderlineStyle
      else
        fragment[:styles] = UnderlineStyle.dup
      end
    end
  end
  fragment
end

#create_highlight_line_fragment(bg_color) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 158

def create_highlight_line_fragment bg_color
  {
    background_color: (bg_color || 'FFFFCC'),
    callback: @background_colorizer,
    extend: true,
    highlight: true,
    inline_block: true,
    text: DummyText,
    width: 0,
  }
end

#create_linenum_fragment(linenum) ⇒ Object



154
155
156
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 154

def create_linenum_fragment linenum
  @linenum_fragment_base.merge text: %(#{linenum} ), linenum: linenum
end

#normalize_color(raw) ⇒ Object



170
171
172
173
174
175
176
177
178
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 170

def normalize_color raw
  return unless raw
  if (normalized = @normalized_colors[raw])
    normalized
  else
    normalized = raw.slice 1, raw.length
    @normalized_colors[raw] = normalized.length == 3 ? normalized.each_char.map {|c| c * 2 }.join : normalized
  end
end

#stream(tokens, opts = {}) ⇒ Object Also known as: format



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb', line 47

def stream tokens, opts = {}
  line_numbers = opts[:line_numbers]
  highlight_lines = opts[:highlight_lines]
  if line_numbers || highlight_lines
    linenum = (linenum = opts[:start_line] || 1) > 0 ? linenum : 1
    fragments = []
    line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
    fragments << @highlight_line_fragment.merge if highlight_lines && highlight_lines[linenum]
    tokens.each do |tok, val|
      if val == LF
        fragments << { text: LF }
        linenum += 1
        line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
        fragments << @highlight_line_fragment.merge if highlight_lines && highlight_lines[linenum]
      elsif val.include? LF
        # NOTE: we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
        line_oriented = val.end_with? LF
        base_fragment = create_fragment tok, val
        val.each_line do |line|
          if start_of_line
            line[0] = GuardedIndent if line.start_with? ' '
            start_of_line = nil
          end
          fragments << (line_oriented ? (base_fragment.merge text: line, inline_block: true) : (base_fragment.merge text: line))
          next unless line.end_with? LF
          # NOTE: eagerly append linenum fragment or line highlight if there's a next line
          linenum += 1
          line_numbers ? (fragments << (create_linenum_fragment linenum)) : (start_of_line = true)
          fragments << @highlight_line_fragment.merge if highlight_lines && highlight_lines[linenum]
        end
      else
        if start_of_line
          val[0] = GuardedIndent if val.start_with? ' '
          start_of_line = nil
        end
        fragments << (create_fragment tok, val)
      end
    end
    # NOTE: pad numbers that have less digits than the largest line number
    # FIXME: we could store these fragments so we don't have find them again
    if line_numbers && (linenum_w = linenum.to_s.length) > 1
      # NOTE: extra column is the trailing space after the line number
      linenum_w += 1
      fragments.each do |fragment|
        fragment[:text] = (fragment[:text].rjust linenum_w, NoBreakSpace).to_s if fragment[:linenum]
      end
    end
    fragments
  else
    start_of_line = true
    tokens.map do |tok, val|
      # match one or more consecutive endlines
      if val == LF || (val == (LF * val.length))
        start_of_line = true
        { text: val }
      else
        val[0] = GuardedIndent if start_of_line && (val.start_with? ' ')
        val.gsub! InnerIndent, GuardedInnerIndent if val.include? InnerIndent
        # QUESTION: do we need the call to create_fragment if val contains only spaces? consider bg
        #fragment = create_fragment tok, val
        fragment = val.rstrip.empty? ? { text: val } : (create_fragment tok, val)
        # NOTE: we assume if the fragment ends in a line feed, the intention was to match a line-oriented form
        fragment[:inline_block] = true if (start_of_line = val.end_with? LF)
        fragment
      end
    end
  end
end