class Asciidoctor::PDF::ThemeLoader

Constants

AddSubtractOpRx
BaseThemePath
DataDir
DefaultThemePath
FontsDir
HexColorEntryRx
LoneVariableRx
MultiplyDivideOpRx
PrecisionFuncRx
ThemesDir
VariableRx

Public Class Methods

load_base_theme() click to toggle source

NOTE base theme is loaded “as is” (no post-processing)

# File lib/asciidoctor-pdf/theme_loader.rb, line 63
def self.load_base_theme
  ::OpenStruct.new(::SafeYAML.load_file BaseThemePath)
end
load_file(filename, theme_data = nil, theme_path = nil) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 76
def self.load_file filename, theme_data = nil, theme_path = nil
  yaml_data = ::SafeYAML.load (::File.read filename, encoding: ::Encoding::UTF_8).each_line.map {|l| l.sub HexColorEntryRx, '\k<k>: \'\k<v>\'' }.join
  if ::Hash === yaml_data && (extend_files = yaml_data.delete 'extends')
    [*extend_files].each do |extend_file|
      if extend_file == 'default'
        extend_file = resolve_theme_file extend_file, (extend_theme_path = ThemesDir)
      elsif extend_file.start_with? './'
        extend_file = resolve_theme_file extend_file, (extend_theme_path = (::File.dirname ::File.absolute_path filename))
      else
        extend_file = resolve_theme_file extend_file, (extend_theme_path = theme_path)
      end
      theme_data = load_file extend_file, theme_data, extend_theme_path
    end
  end
  self.new.load yaml_data, theme_data, theme_path
end
load_theme(theme_name = nil, theme_path = nil, opts = {}) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 67
def self.load_theme theme_name = nil, theme_path = nil, opts = {}
  if (theme_file = resolve_theme_file theme_name, theme_path) == BaseThemePath ||
      (theme_file != DefaultThemePath && (opts.fetch :apply_base_theme, true))
    theme_data = load_base_theme
  end

  theme_file == BaseThemePath ? theme_data : (load_file theme_file, theme_data, theme_path)
end
resolve_theme_asset(asset_path, theme_path = nil) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 58
def self.resolve_theme_asset asset_path, theme_path = nil
  ::File.expand_path asset_path, (theme_path || ThemesDir)
end
resolve_theme_file(theme_name = nil, theme_path = nil) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 45
def self.resolve_theme_file theme_name = nil, theme_path = nil
  theme_name ||= 'default'
  # if .yml extension is given, don't append -theme.yml
  if (theme_name.end_with? '.yml')
    # FIXME restrict to jail!
    # QUESTION why are we not using expand_path in this case?
    theme_path ? (::File.join theme_path, theme_name) : theme_name
  else
    # QUESTION should we append '-theme.yml' or just '.yml'?
    ::File.expand_path %(#{theme_name}-theme.yml), (theme_path || ThemesDir)
  end
end

Public Instance Methods

load(hash, theme_data = nil, theme_path = nil) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 93
def load hash, theme_data = nil, theme_path = nil
  theme_data ||= ::OpenStruct.new
  return theme_data unless ::Hash === hash
  base_code_font_family = theme_data.delete 'code_font_family'
  base_conum_font_family = theme_data.delete 'conum_font_family'
  hash.inject(theme_data) {|data, (key, val)| process_entry key, val, data }
  # NOTE remap legacy running content keys (e.g., header_recto_content_left => header_recto_left_content)
  %w(header_recto header_verso footer_recto footer_verso).each do |periphery_face|
    %w(left center right).each do |align|
      if (val = theme_data.delete %(#{periphery_face}_content_#{align}))
        theme_data[%(#{periphery_face}_#{align}_content)] = val
      end
    end
  end
  theme_data.base_align ||= 'left'
  theme_data.code_font_family ||= (theme_data.literal_font_family || base_code_font_family)
  theme_data.conum_font_family ||= (theme_data.literal_font_family || base_conum_font_family)
  # QUESTION should we do any other post-load calculations or defaults?
  theme_data
end

Private Instance Methods

evaluate(expr, vars) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 138
def evaluate expr, vars
  case expr
  when ::String
    evaluate_math(expand_vars expr, vars)
  when ::Array
    expr.map {|e| evaluate e, vars }
  else
    expr
  end
end
evaluate_math(expr) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 174
def evaluate_math expr
  return expr if !(::String === expr) || ColorValue === expr
  # resolve measurement values (e.g., 0.5in => 36)
  # QUESTION should we round the value? perhaps leave that to the precision functions
  # NOTE leave % as a string; handled by converter for now
  expr = resolve_measurement_values(original = expr)
  while true
    result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
    unchanged = (result == expr)
    expr = result
    break if unchanged
  end
  while true
    result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
    unchanged = (result == expr)
    expr = result
    break if unchanged
  end
  if (expr.end_with? ')') && expr =~ PrecisionFuncRx
    op = $1
    offset = op.length + 1
    expr = expr[offset...-1].to_f.send op.to_sym
  end
  if expr == original
    original
  else
    (int_val = expr.to_i) == (flt_val = expr.to_f) ? int_val : flt_val
  end
end
expand_vars(expr, vars) click to toggle source

NOTE we assume expr is a String

# File lib/asciidoctor-pdf/theme_loader.rb, line 150
def expand_vars expr, vars
  if (idx = (expr.index '$'))
    if idx == 0 && expr =~ LoneVariableRx
      if vars.respond_to? $1
        vars[$1]
      else
        logger.warn %(unknown variable reference in PDF theme: $#{$1})
        expr
      end
    else
      expr.gsub(VariableRx) {
        if vars.respond_to? $1
          vars[$1]
        else
          logger.warn %(unknown variable reference in PDF theme: $#{$1})
          $&
        end
      }
    end
  else
    expr
  end
end
process_entry(key, val, data) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 116
def process_entry key, val, data
  if key.start_with? 'font_'
    data[key] = val
  elsif key.start_with? 'admonition_icon_'
    data[key] = (val || {}).map do |(key2, val2)|
      [key2.to_sym, (key2.end_with? '_color') ? to_color(evaluate val2, data) : (evaluate val2, data)]
    end.to_h
  elsif ::Hash === val
    val.each do |key2, val2|
      process_entry %(#{key}_#{key2.tr '-', '_'}), val2, data
    end
  elsif key.end_with? '_color'
    # QUESTION do we need to evaluate_math in this case?
    data[key] = to_color(evaluate val, data)
  elsif %(#{key.chomp '_'}_).include? '_content_'
    data[key] = (expand_vars val.to_s, data).to_s
  else
    data[key] = evaluate val, data
  end
  data
end
to_color(value) click to toggle source
# File lib/asciidoctor-pdf/theme_loader.rb, line 204
def to_color value
  case value
  when ColorValue
    # already converted
    return value
  when ::String
    if value == 'transparent'
      # FIXME should we have a TransparentColorValue class?
      return HexColorValue.new value
    elsif value.length == 6
      return HexColorValue.new value.upcase
    end
  when ::Array
    case value.length
    # CMYK value
    when 4
      value = value.map do |e|
        if ::Numeric === e
          e = e * 100.0 unless e > 1
        else
          e = e.to_s.chomp('%').to_f
        end
        e == (int_e = e.to_i) ? int_e : e
      end
      case value
      when [0, 0, 0, 0]
        return HexColorValue.new 'FFFFFF'
      when [100, 100, 100, 100]
        return HexColorValue.new '000000'
      else
        value.extend CmykColorValue
        return value
      end
    # RGB value
    when 3
      return HexColorValue.new value.map {|e| '%02X' % e }.join
    # Nonsense array value; flatten to string
    else
      value = value.join
    end
  else
    # Unknown type; coerce to a string
    value = value.to_s
  end
  value = case value.length
  when 6
    value
  when 3
    # expand hex shorthand (e.g., f00 -> ff0000)
    value.each_char.map {|c| c * 2 }.join
  else
    # truncate or pad with leading zeros (e.g., ff -> 0000ff)
    value[0..5].rjust 6, '0'
  end
  HexColorValue.new value.upcase
end