diff --git a/data/themes/base-theme.yml b/data/themes/base-theme.yml new file mode 100644 index 000000000..0427c4004 --- /dev/null +++ b/data/themes/base-theme.yml @@ -0,0 +1,87 @@ +# NOTE file is read "as is"; variables are not parsed; key layout must be flat +# FIXME internal use of vertical_rhythm & horizontal_rhythm needs to be deprecated +vertical_rhythm: 12 +horizontal_rhythm: 12 +page_background_color: 'FFFFFF' +page_layout: portrait +# 36 is equivalent to 0.5in +page_margin: 36 +page_size: LETTER +base_align: left +#base_font_color: '333333' +base_font_color: '000000' +#base_font_family: Times-Roman +base_font_family: Helvetica +base_font_size: 12 +# QUESTION should we rename to min_font_size? +base_font_size_min: 9 +base_font_style: normal +base_line_height: 1.15 +base_line_height_length: 13.8 +base_border_color: 'EEEEEE' +base_border_width: 0.5 +link_font_color: '0000EE' +literal_font_family: Courier +heading_font_style: bold +heading_h1_font_size: 24 +heading_h2_font_size: 18 +heading_h3_font_size: 16 +heading_h4_font_size: 14 +heading_h5_font_size: 12 +heading_h6_font_size: 10 +heading_line_height: 1.15 +heading_margin_top: 4 +heading_margin_bottom: 12 +title_page_align: center +title_page_line_height: 1.15 +title_page_logo_top: 10% +title_page_title_top: 40% +title_page_title_font_size: 18 +title_page_subtitle_font_size: 14 +title_page_authors_margin_top: 12 +outline_list_indent: 30 +outline_list_item_spacing: 6 +description_list_description_indent: 30 +description_list_term_font_style: normal +# NOTE currently block_padding _is_ the sidebar padding +block_padding: 12 +block_margin_top: 0 +block_margin_bottom: 12 +caption_align: left +caption_font_style: italic +caption_margin_inside: 4 +caption_margin_outside: 0 +abstract_font_size: 13.5 +abstract_line_height: 1.4 +abstract_padding: 0 +admonition_border_color: 'EEEEEE' +admonition_border_width: 0.5 +blockquote_border_color: 'EEEEEE' +blockquote_border_width: 4 +code_font_family: Courier +code_font_size: 10.5 +code_line_height: 1.2 +code_padding: 9 +code_border_color: 'EEEEEE' +code_border_width: 0.5 +conum_line_height: 1.15 +example_border_color: 'EEEEEE' +example_border_width: 0.5 +image_align: left +lead_font_size: 13.5 +lead_line_height: 1.4 +prose_margin_top: 0 +prose_margin_bottom: 12 +sidebar_background_color: 'EEEEEE' +sidebar_title: +sidebar_title_align: center +sidebar_title_font_style: bold +table_border_color: '000000' +table_border_width: 0.5 +table_cell_padding: 2 +table_head_font_style: bold +thematic_break_border_color: 'EEEEEE' +thematic_break_border_style: solid +thematic_break_border_width: 0.5 +toc_indent: 15 +toc_line_height: 1.4 diff --git a/data/themes/default-theme.yml b/data/themes/default-theme.yml index 2d3c16e48..3da159cf8 100644 --- a/data/themes/default-theme.yml +++ b/data/themes/default-theme.yml @@ -24,12 +24,10 @@ font: page: background_color: ffffff layout: portrait - # NOTE multiply inches by 72 to get pt values - #margin: [0.5 * 72, 0.67 * 72, 0.67 * 72, 0.67 * 72] margin: [0.5in, 0.67in, 0.67in, 0.67in] - # size can be a named size (e.g., A4) or custom dimensions (e.g., [8.25in, 11.69in]) - size: Letter + size: LETTER base: + align: justify # color as hex string (leading # is optional) font_color: 333333 # color as RGB array @@ -56,13 +54,12 @@ base: font_size_small: round($base_font_size * 0.85) font_size_min: $base_font_size * 0.75 font_style: normal - align: justify + border_color: eeeeee border_radius: 4 border_width: 0.5 - border_color: eeeeee # FIXME vertical_rhythm is weird; we should think in terms of ems #vertical_rhythm: $base_line_height_length * 2 / 3 -# correct line height for Noto Serif metrics +# correct line height for Noto Serif metrics (comes with built-in line height) vertical_rhythm: $base_line_height_length horizontal_rhythm: $base_line_height_length link: @@ -75,6 +72,7 @@ heading: #font_color: 181818 font_color: $base_font_color font_family: $base_font_family + font_style: bold # h1 is used for part titles h1_font_size: floor($base_font_size * 2.6) # h2 is used for chapter titles @@ -83,105 +81,101 @@ heading: h4_font_size: $base_font_size_large h5_font_size: $base_font_size h6_font_size: $base_font_size_small - font_style: bold #line_height: 1.4 - # correct line height for Noto Serif metrics + # correct line height for Noto Serif metrics (comes with built-in line height) line_height: 1.2 margin_top: $vertical_rhythm * 0.2 margin_bottom: $vertical_rhythm * 0.8 title_page: align: right - title_top: 55% - title_font_size: $heading_h1_font_size - title_font_color: 999999 - title_line_height: 0.9 - subtitle_font_size: $heading_h3_font_size - subtitle_font_style: bold_italic - subtitle_line_height: 1 - authors_margin_top: $base_font_size * 1.25 - authors_font_size: $base_font_size_large - authors_font_color: 181818 - revision_margin_top: $base_font_size * 1.25 -#prose: -# margin_top: 0 -# margin_bottom: $vertical_rhythm + logo: + top: 10% + title: + top: 55% + font_size: $heading_h1_font_size + font_color: 999999 + line_height: 0.9 + subtitle: + font_size: $heading_h3_font_size + font_style: bold_italic + line_height: 1 + authors: + margin_top: $base_font_size * 1.25 + font_size: $base_font_size_large + font_color: 181818 + revision: + margin_top: $base_font_size * 1.25 block: - #margin_top: 0 - #margin_bottom: $vertical_rhythm + margin_top: 0 + margin_bottom: $vertical_rhythm + # NOTE currently block_padding _is_ the sidebar padding padding: [$vertical_rhythm, $vertical_rhythm * 1.25, $vertical_rhythm, $vertical_rhythm * 1.25] caption: - font_style: italic align: left + font_style: italic # FIXME perhaps set line_height instead of / in addition to margins? - margin_inside: $vertical_rhythm * 0.25 + margin_inside: $vertical_rhythm / 3 + #margin_inside: $vertical_rhythm / 4 margin_outside: 0 +lead: + font_size: $base_font_size_large + line_height: 1.4 +abstract: + font_color: 5c6266 + font_size: $lead_font_size + line_height: $lead_line_height + font_style: italic +admonition: + border_color: $base_border_color + border_width: $base_border_width +blockquote: + font_color: $base_font_color + font_size: $base_font_size_large + border_color: $base_border_color + border_width: 5 + cite_font_size: $base_font_size_small + cite_font_color: 999999 # code is used for source blocks (perhaps change to source or listing?) code: font_color: $base_font_color - #font_family: Liberation Mono - #font_size: floor($base_font_size * 0.9) - #font_size: 10 - #padding: [9.5, 9.5, 9.5, 9.5] - # LiberationMono carries extra gap below line - #padding: [10, 10, 7.5, 10] - #line_height: 1.45 font_family: $literal_font_family font_size: ceil($base_font_size) - #padding: [$base_font_size, $code_font_size, $base_font_size, $code_font_size] padding: $code_font_size line_height: 1.25 background_color: f5f5f5 border_color: cccccc border_radius: $base_border_radius border_width: 0.75 -blockquote: - font_color: $base_font_color - font_size: $base_font_size_large - border_width: 5 - border_color: $base_border_color - cite_font_size: $base_font_size_small - cite_font_color: 999999 -sidebar: - border_color: $page_background_color - border_radius: $base_border_radius - border_width: $base_border_width - background_color: eeeeee - title_font_color: $heading_font_color - title_font_family: $heading_font_family - title_font_size: $heading_h4_font_size - title_font_style: $heading_font_style - title_align: center -example: - border_color: $base_border_color - border_radius: $base_border_radius - border_width: 0.75 - background_color: transparent -admonition: - border_color: $base_border_color - border_width: $base_border_width conum: font_family: M+ 1mn font_color: $literal_font_color font_size: $base_font_size line_height: 4 / 3 +example: + border_color: $base_border_color + border_radius: $base_border_radius + border_width: 0.75 + background_color: transparent image: - align_default: left - scaled_width_default: 0.5 -lead: - # QUESTION what about $base_font_size_large? - #font_size: floor($base_line_height_length * 0.8) - #font_size: floor($base_font_size * 1.15) - #line_height: 1.3 - font_size: $base_font_size_large - line_height: 1.4 -abstract: - #font_color: 404040 - font_color: 5c6266 - font_size: $lead_font_size - line_height: $lead_line_height - font_style: italic + align: left +prose: + margin_top: 0 + margin_bottom: $vertical_rhythm +sidebar: + border_color: $page_background_color + border_radius: $base_border_radius + border_width: $base_border_width + background_color: eeeeee + title: + align: center + font_color: $heading_font_color + font_family: $heading_font_family + font_size: $heading_h4_font_size + font_style: $heading_font_style thematic_break: border_color: $base_border_color + border_style: solid + border_width: $base_border_width margin_top: $vertical_rhythm * 0.5 margin_bottom: $vertical_rhythm * 1.5 description_list: @@ -196,6 +190,7 @@ table: background_color: $page_background_color #head_background_color: #head_font_color: $base_font_color + head_font_style: bold even_row_background_color: f9f9f9 #odd_row_background_color: foot_background_color: f0f0f0 @@ -204,9 +199,9 @@ table: # HACK accounting for line-height cell_padding: [3, 3, 6, 3] toc: - indent: $horizontal_rhythm dot_leader_color: dddddd - #dot_leader_content: ". " + #dot_leader_content: '. ' + indent: $horizontal_rhythm line_height: 1.4 # NOTE In addition to footer, header is also supported footer: diff --git a/docs/theming-guide.adoc b/docs/theming-guide.adoc index ce3085212..feed4b6f2 100644 --- a/docs/theming-guide.adoc +++ b/docs/theming-guide.adoc @@ -76,14 +76,10 @@ outline_list: indent: $base_font_size * 1.5 ---- -When creating a new theme, you only have to define the keys you want to override from the base theme. +When creating a new theme, you only have to define the keys you want to override from the base theme, which is loaded prior to loading your custom theme. The converter uses the information from the theme map to help construct the PDF. All the available keys are documented in <>. -IMPORTANT: Until https://github.com/asciidoctor/asciidoctor-pdf/issues/132[issue #132] is resolved, you must specify all required keys. -Missing required keys will cause the processor to crash. -The best approach is to start from the https://github.com/asciidoctor/asciidoctor-pdf/blob/master/data/themes/default-theme.yml[default theme file] and modify it to suit your needs. - Keys may be nested to an arbitrary depth to eliminate redundant prefixes (an approach inspired by SASS). Once the theme is loaded, all keys are flattened into a single map of qualified keys. Nesting is simply a shorthand way of organizing the keys. @@ -1204,9 +1200,9 @@ Block styles are applied to the following block types: |=== |Key |Value Type |Example -|image_align_default -|left, center, right, justify -|align_default: left +|image_align +|left, center, right +|align: left |=== === Lead diff --git a/lib/asciidoctor-pdf/converter.rb b/lib/asciidoctor-pdf/converter.rb index 8906ef793..d95dacce1 100644 --- a/lib/asciidoctor-pdf/converter.rb +++ b/lib/asciidoctor-pdf/converter.rb @@ -209,7 +209,7 @@ def init_pdf doc end @page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF' @fallback_fonts = [*theme.font_fallbacks] - @font_color = theme.base_font_color || '000000' + @font_color = theme.base_font_color @text_transform = nil @stamps = {} init_scratch_prototype @@ -221,8 +221,8 @@ def build_pdf_options doc, theme #compress: true, #optimize_objects: true, info: (build_pdf_info doc), - margin: (theme.page_margin || 36), - page_layout: (theme.page_layout || :portrait).to_sym, + margin: theme.page_margin, + page_layout: theme.page_layout.to_sym, skip_page_creation: true, } @@ -410,7 +410,7 @@ def convert_admonition node theme_margin :block, :top icons = node.document.attr? 'icons', 'font' label = icons ? (node.attr 'name').to_sym : node.caption.upcase - shift_base = @theme.prose_margin_bottom || @theme.vertical_rhythm + shift_base = @theme.prose_margin_bottom #shift_top = icons ? (shift_base / 3.0) : 0 #shift_bottom = icons ? ((shift_base * 2) / 3.0) : shift_base shift_top = shift_base / 3.0 @@ -491,7 +491,7 @@ def convert_open node def convert_quote_or_verse node add_dest_for_block node if node.id - border_width = @theme.blockquote_border_width || 0 + border_width = @theme.blockquote_border_width theme_margin :block, :top keep_together do |box_height = nil| start_cursor = cursor @@ -548,7 +548,7 @@ def convert_sidebar node convert_content_for_block node end # FIXME HACK compensate for margin bottom of sidebar content - move_up(@theme.prose_margin_bottom || @theme.vertical_rhythm) + move_up @theme.prose_margin_bottom end end theme_margin :block, :bottom @@ -561,10 +561,10 @@ def convert_colist node # NOTE this logic won't work for a colist nested inside a list item until Asciidoctor 1.5.3 if (self_idx = node.parent.blocks.index node) && self_idx > 0 && [:listing, :literal].include?(node.parent.blocks[self_idx - 1].context) - move_up ((@theme.block_margin_bottom || @theme.vertical_rhythm) / 2.0) + move_up @theme.block_margin_bottom / 2.0 # or we could do... - #move_up (@theme.block_margin_bottom || @theme.vertical_rhythm) - #move_down (@theme.caption_margin_inside * 2) + #move_up @theme.block_margin_bottom + #move_down @theme.caption_margin_inside * 2 end end add_dest_for_block node if node.id @@ -581,8 +581,8 @@ def convert_colist node end @list_numbers.pop # correct bottom margin of last item - list_margin_bottom = @theme.prose_margin_bottom || @theme.vertical_rhythm - margin_bottom (list_margin_bottom - (@theme.outline_list_item_spacing || (list_margin_bottom / 2.0))) + list_margin_bottom = @theme.prose_margin_bottom + margin_bottom list_margin_bottom - @theme.outline_list_item_spacing end def convert_colist_item node @@ -599,7 +599,7 @@ def convert_colist_item node indent marker_width do convert_content_for_list_item node, - margin_bottom: (@theme.outline_list_item_spacing || ((@theme.prose_margin_bottom || @theme.vertical_rhythm) / 2.0)) + margin_bottom: @theme.outline_list_item_spacing end end @@ -611,7 +611,7 @@ def convert_dlist node # FIXME extract ensure_space (or similar) method start_new_page if cursor < @theme.base_line_height_length * (terms.size + 1) terms.each do |term| - layout_prose term.text, style: (@theme.description_list_term_font_style || :normal).to_sym, margin_top: 0, margin_bottom: (@theme.vertical_rhythm / 3.0), align: :left + layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: (@theme.vertical_rhythm / 3.0), align: :left end if desc indent @theme.description_list_description_indent do @@ -694,8 +694,8 @@ def convert_outline_list node # However, don't leave gap at the bottom of a nested list unless complex || (::Asciidoctor::List === node.parent && node.parent.outline?) # correct bottom margin of last item - list_margin_bottom = @theme.prose_margin_bottom || @theme.vertical_rhythm - margin_bottom(list_margin_bottom - (@theme.outline_list_item_spacing || (list_margin_bottom / 2.0))) + list_margin_bottom = @theme.prose_margin_bottom + margin_bottom list_margin_bottom - @theme.outline_list_item_spacing end end @@ -741,7 +741,7 @@ def convert_outline_list_item node, complex = false convert_content_for_list_item node else convert_content_for_list_item node, - margin_bottom: (@theme.outline_list_item_spacing || ((@theme.prose_margin_bottom || @theme.vertical_rhythm) / 2.0)) + margin_bottom: @theme.outline_list_item_spacing end end @@ -773,7 +773,7 @@ def convert_image node # QUESTION if we advance to new page, shouldn't dest point there too? add_dest_for_block node if node.id - position = ((node.attr 'align') || @theme.image_align || :left).to_sym + position = ((node.attr 'align') || @theme.image_align).to_sym unless valid_image theme_margin :block, :top @@ -993,6 +993,7 @@ def convert_listing_or_literal node pad_box @theme.code_padding do typeset_formatted_text source_chunks, (calc_line_metrics @theme.code_line_height), + # QUESTION should we require the code_font_color to be set? color: (@theme.code_font_color || @font_color), size: adjusted_font_size end @@ -1121,7 +1122,7 @@ def convert_table node text_color: (theme.table_head_font_color || theme.table_font_color || @font_color), size: (theme.table_head_font_size || theme.table_font_size), font: (theme.table_head_font_family || theme.table_font_family), - font_style: (theme.table_head_font_style || :bold).to_sym, + font_style: theme.table_head_font_style.to_sym, colspan: cell.colspan || 1, rowspan: cell.rowspan || 1, align: (cell.attr 'halign').to_sym, @@ -1277,7 +1278,7 @@ def convert_table node def convert_thematic_break node theme_margin :thematic_break, :top - stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: (@theme.thematic_break_border_style || :solid).to_sym + stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: @theme.thematic_break_border_style.to_sym theme_margin :thematic_break, :bottom end @@ -1493,7 +1494,7 @@ def layout_title_page doc font @theme.base_font_family, size: @theme.base_font_size # QUESTION allow aligment per element on title page? - title_align = (@theme.title_page_align || :center).to_sym + title_align = @theme.title_page_align.to_sym # TODO disallow .pdf as image type if (logo_image_path = (doc.attr 'title-logo-image', @theme.title_page_logo_image)) @@ -1509,7 +1510,7 @@ def layout_title_page doc end logo_image_attrs['target'] = logo_image_path logo_image_attrs['align'] ||= (@theme.title_page_logo_align || title_align.to_s) - logo_image_top = (logo_image_attrs['top'] || @theme.title_page_logo_top || '10%') + logo_image_top = (logo_image_attrs['top'] || @theme.title_page_logo_top) # FIXME delegate to method to convert page % to y value logo_image_top = [(page_height - page_height * (logo_image_top.to_i / 100.0)), bounds.absolute_top].min float do @@ -1621,8 +1622,8 @@ def layout_heading string, opts = {} # NOTE inline_format is true by default def layout_prose string, opts = {} - top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top || 0 - bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom || @theme.vertical_rhythm + top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top + bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom if (transform = (opts.delete :text_transform) || @text_transform) string = transform_text string, transform end @@ -1640,7 +1641,7 @@ def layout_prose string, opts = {} color: @font_color, # NOTE normalize makes endlines soft (replaces "\n" with ' ') inline_format: [normalize: (opts.delete :normalize) != false], - align: (@theme.base_align || :left).to_sym + align: @theme.base_align.to_sym }.merge(opts) margin_bottom bot_margin end @@ -1667,7 +1668,7 @@ def layout_caption subject, opts = {} layout_prose string, { margin_top: margin[:top], margin_bottom: margin[:bottom], - align: (@theme.caption_align || :left).to_sym, + align: @theme.caption_align.to_sym, normalize: false }.merge(opts) if position == :top && @theme.caption_border_bottom_color @@ -1692,7 +1693,7 @@ def layout_toc doc, num_levels = 2, toc_page_number = 2, num_front_matter_pages # QUESTION shouldn't we skip this whole method if num_levels == 0? if num_levels > 0 theme_margin :toc, :top - line_metrics = calc_line_metrics @theme.toc_line_height || @theme.base_line_height + line_metrics = calc_line_metrics @theme.toc_line_height dot_width = nil theme_font :toc do dot_width = width_of(@theme.toc_dot_leader_content || DotLeaderDefault) @@ -1730,6 +1731,7 @@ def layout_toc_level sections, num_levels, line_metrics, dot_width, num_front_ma # FIXME dots don't line up if width of page numbers differ typeset_formatted_text [ { text: %(#{(@theme.toc_dot_leader_content || DotLeaderDefault) * num_dots}), color: toc_dot_color }, + # FIXME this spacing doesn't always work out { text: NoBreakSpace, size: (@font_size * 0.5) }, { text: sect_page_num.to_s, anchor: sect_anchor, color: @font_color }], line_metrics, align: :right go_to_page end_page_number if start_page_number != end_page_number @@ -1737,7 +1739,7 @@ def layout_toc_level sections, num_levels, line_metrics, dot_width, num_front_ma end end if sect.level < num_levels - indent(@theme.toc_indent || @theme.outline_list_indent) do + indent @theme.toc_indent do layout_toc_level sect.sections, num_levels, line_metrics, dot_width, num_front_matter_pages end end diff --git a/lib/asciidoctor-pdf/theme_loader.rb b/lib/asciidoctor-pdf/theme_loader.rb index 7d9c48825..90d545aa7 100644 --- a/lib/asciidoctor-pdf/theme_loader.rb +++ b/lib/asciidoctor-pdf/theme_loader.rb @@ -8,6 +8,8 @@ class ThemeLoader DataDir = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', 'data')) ThemesDir = ::File.join DataDir, 'themes' FontsDir = ::File.join DataDir, 'fonts' + DefaultThemePath = ::File.expand_path 'default-theme.yml', ThemesDir + BaseThemePath = ::File.expand_path 'base-theme.yml', ThemesDir VariableRx = /\$([a-z0-9_]+)/ LoneVariableRx = /^\$([a-z0-9_]+)$/ @@ -39,32 +41,48 @@ def self.resolve_theme_file theme_name = nil, theme_path = nil 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 + 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)) + ::File.expand_path %(#{theme_name}-theme.yml), (theme_path || ThemesDir) end end def self.resolve_theme_asset asset_path, theme_path = nil - ::File.expand_path(asset_path, (theme_path || ThemesDir)) + ::File.expand_path asset_path, (theme_path || ThemesDir) end - def self.load_theme theme_name = nil, theme_path = nil - load_file(resolve_theme_file theme_name, theme_path) + # NOTE base theme is loaded "as is" (no post-processing) + def self.load_base_theme + ::OpenStruct.new(::SafeYAML.load_file BaseThemePath) end - def self.load_file filename - data = ::IO.read(filename).each_line.map {|l| l.sub HexColorValueRx, '_color: \'\k\'' }.join - self.new.load(::SafeYAML.load data) - end + 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 + else + theme_data = nil + end - def load hash - hash.inject(::OpenStruct.new) do |data, (key, val)| - process_entry key, val, data + if theme_file == BaseThemePath + theme_data + else + # QUESTION should we do any post-load calculations or defaults? + load_file theme_file, theme_data end end + def self.load_file filename, theme_data = nil + raw_data = (::IO.read filename).each_line.map {|l| l.sub HexColorValueRx, '_color: \'\k\'' }.join + self.new.load((::SafeYAML.load raw_data), theme_data) + end + + def load hash, theme_data = nil + theme_data ||= ::OpenStruct.new + hash.inject(theme_data) {|data, (key, val)| process_entry key, val, data } + end + private def process_entry key, val, data @@ -92,7 +110,7 @@ def evaluate expr, vars # NOTE we assume expr is a String def expand_vars expr, vars if (idx = (expr.index '$')) - if idx == 0 && LoneVariableRx =~ expr + if idx == 0 && expr =~ LoneVariableRx vars[$1] else expr.gsub(VariableRx) { vars[$1] } @@ -137,7 +155,7 @@ def evaluate_math expr expr = result break if unchanged end - if (expr.end_with? ')') && PrecisionFuncRx =~ expr + if (expr.end_with? ')') && expr =~ PrecisionFuncRx op = $1 offset = op.length + 1 expr = expr[offset...-1].to_f.send op.to_sym