lib/rouge/tex_theme_renderer.rb



# -*- 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)
      out = "\\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