File: tex_theme_renderer.rb

package info (click to toggle)
ruby-rouge 4.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,844 kB
  • sloc: ruby: 38,489; sed: 2,071; perl: 152; makefile: 8
file content (132 lines) | stat: -rw-r--r-- 4,090 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  class TexThemeRenderer
    def initialize(theme, opts={})
      @theme = theme
      @prefix = opts.fetch(:prefix) { 'RG' }
    end

    # Our general strategy is this:
    #
    # * First, define the \RG{tokname}{content} command, which will
    #   expand into \RG@tok@tokname{content}. We use \csname...\endcsname
    #   to interpolate into a command.
    #
    # * Define the default RG* environment, which will enclose the whole
    #   thing. By default this will simply set \ttfamily (select monospace font)
    #   but it can be overridden with \renewcommand by the user to be
    #   any other formatting.
    #
    # * Define all the colors using xcolors \definecolor command. First we define
    #   every palette color with a name such as RG@palette@themneame@colorname.
    #   Then we find all foreground and background colors that have literal html
    #   colors embedded in them and define them with names such as
    #   RG@palette@themename@000000. While html allows three-letter colors such
    #   as #FFF, xcolor requires all six characters to be present, so we make sure
    #   to normalize that as well as the case convention in #inline_name.
    #
    # * Define the token commands RG@tok@xx. These will take the content as the
    #   argument and format it according to the theme, referring to the color
    #   in the palette.
    def render(&b)
      yield <<'END'.gsub('RG', @prefix)
\makeatletter
\def\RG#1#2{\csname RG@tok@#1\endcsname{#2}}%
\newenvironment{RG*}{\ttfamily}{\relax}%
END

      base = @theme.class.base_style
      yield "\\definecolor{#{@prefix}@fgcolor}{HTML}{#{inline_name(base.fg || '#000000')}}"
      yield "\\definecolor{#{@prefix}@bgcolor}{HTML}{#{inline_name(base.bg || '#FFFFFF')}}"

      render_palette(@theme.palette, &b)

      @theme.styles.each do |tok, style|
        render_inline_pallete(style, &b)
      end

      Token.each_token do |tok|
        style = @theme.class.get_own_style(tok)
        style ? render_style(tok, style, &b) : render_blank(tok, &b)
      end
      yield '\makeatother'
    end

    def render_palette(palette, &b)
      palette.each do |name, color|
        hex = inline_name(color)

        yield "\\definecolor{#{palette_name(name)}}{HTML}{#{hex}}%"
      end
    end

    def render_inline_pallete(style, &b)
      gen_inline(style[:fg], &b)
      gen_inline(style[:bg], &b)
    end

    def inline_name(color)
      color =~ /^#(\h+)/ or return nil

      # xcolor does not support 3-character HTML colors,
      # so we convert them here
      case $1.size
      when 6
        $1
      when 3
        # duplicate every character: abc -> aabbcc
        $1.gsub(/\h/, '\0\0')
      else
        raise "invalid HTML color: #{$1}"
      end.upcase
    end

    def gen_inline(name, &b)
      # detect inline colors
      hex = inline_name(name)
      return unless hex

      @gen_inline ||= {}
      @gen_inline[hex] ||= begin
        yield "\\definecolor{#{palette_name(hex)}}{HTML}{#{hex}}%"
      end
    end

    def camelize(name)
      name.gsub(/_(.)/) { $1.upcase }
    end

    def palette_name(name)
      name = inline_name(name) || name.to_s

      "#{@prefix}@palette@#{camelize(@theme.name)}@#{camelize(name.to_s)}"
    end

    def token_name(tok)
      "\\csname #@prefix@tok@#{tok.shortname}\\endcsname"
    end

    def render_blank(tok, &b)
      "\\expandafter\\def#{token_name(tok)}#1{#1}"
    end

    def render_style(tok, style, &b)
      out = String.new('')
      out << "\\expandafter\\def#{token_name(tok)}#1{"
      out << "\\fboxsep=0pt\\colorbox{#{palette_name(style[:bg])}}{" if style[:bg]
      out << '\\textbf{' if style[:bold]
      out << '\\textit{' if style[:italic]
      out << "\\textcolor{#{palette_name(style[:fg])}}{" if style[:fg]
      out << "#1"
      # close the right number of curlies
      out << "}" if style[:bold]
      out << "}" if style[:italic]
      out << "}" if style[:fg]
      out << "}" if style[:bg]
      out << "}%"
      yield out
    end
  end
end