asciidoctor-pdf 1.5.0.alpha.18 → 1.5.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  require_relative 'core_ext/object'
2
2
  require_relative 'core_ext/array'
3
+ require_relative 'core_ext/hash'
3
4
  require_relative 'core_ext/numeric'
4
5
  require_relative 'core_ext/string'
5
6
  require_relative 'core_ext/regexp'
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def compact
3
+ select {|_, val| val }
4
+ end unless method_defined? :compact
5
+ end
@@ -7,3 +7,4 @@ require_relative 'formatted_text/inline_destination_marker'
7
7
  require_relative 'formatted_text/inline_image_arranger'
8
8
  require_relative 'formatted_text/inline_image_renderer'
9
9
  require_relative 'formatted_text/inline_text_aligner'
10
+ require_relative 'formatted_text/text_background_and_border_renderer'
@@ -66,7 +66,9 @@ module InlineImageArranger
66
66
  svg_obj = ::Prawn::SVG::Interface.new ::File.read(image_path), doc,
67
67
  at: doc.bounds.top_left,
68
68
  width: image_w,
69
- fallback_font_name: doc.default_svg_font
69
+ fallback_font_name: doc.default_svg_font,
70
+ enable_web_requests: doc.allow_uri_read,
71
+ enable_file_requests_with_root: (::File.dirname image_path)
70
72
  svg_size = image_w ? svg_obj.document.sizing :
71
73
  # NOTE convert intrinsic dimensions to points; constrain to content width
72
74
  (svg_obj.resize width: [(to_pt svg_obj.document.sizing.output_width, :px), available_w].min)
@@ -356,44 +356,44 @@ module Markup
356
356
  r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
357
357
  r0 = r1
358
358
  else
359
- if (match_len = has_terminal?('code', false, index))
359
+ if (match_len = has_terminal?('strong', false, index))
360
360
  r2 = instantiate_node(SyntaxNode,input, index...(index + match_len))
361
361
  @index += match_len
362
362
  else
363
- terminal_parse_failure('code')
363
+ terminal_parse_failure('strong')
364
364
  r2 = nil
365
365
  end
366
366
  if r2
367
367
  r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
368
368
  r0 = r2
369
369
  else
370
- if (match_len = has_terminal?('color', false, index))
370
+ if (match_len = has_terminal?('em', false, index))
371
371
  r3 = instantiate_node(SyntaxNode,input, index...(index + match_len))
372
372
  @index += match_len
373
373
  else
374
- terminal_parse_failure('color')
374
+ terminal_parse_failure('em')
375
375
  r3 = nil
376
376
  end
377
377
  if r3
378
378
  r3 = SyntaxNode.new(input, (index-1)...index) if r3 == true
379
379
  r0 = r3
380
380
  else
381
- if (match_len = has_terminal?('del', false, index))
381
+ if (match_len = has_terminal?('code', false, index))
382
382
  r4 = instantiate_node(SyntaxNode,input, index...(index + match_len))
383
383
  @index += match_len
384
384
  else
385
- terminal_parse_failure('del')
385
+ terminal_parse_failure('code')
386
386
  r4 = nil
387
387
  end
388
388
  if r4
389
389
  r4 = SyntaxNode.new(input, (index-1)...index) if r4 == true
390
390
  r0 = r4
391
391
  else
392
- if (match_len = has_terminal?('em', false, index))
392
+ if (match_len = has_terminal?('color', false, index))
393
393
  r5 = instantiate_node(SyntaxNode,input, index...(index + match_len))
394
394
  @index += match_len
395
395
  else
396
- terminal_parse_failure('em')
396
+ terminal_parse_failure('color')
397
397
  r5 = nil
398
398
  end
399
399
  if r5
@@ -422,11 +422,11 @@ module Markup
422
422
  r7 = SyntaxNode.new(input, (index-1)...index) if r7 == true
423
423
  r0 = r7
424
424
  else
425
- if (match_len = has_terminal?('strong', false, index))
425
+ if (match_len = has_terminal?('button', false, index))
426
426
  r8 = instantiate_node(SyntaxNode,input, index...(index + match_len))
427
427
  @index += match_len
428
428
  else
429
- terminal_parse_failure('strong')
429
+ terminal_parse_failure('button')
430
430
  r8 = nil
431
431
  end
432
432
  if r8
@@ -455,8 +455,20 @@ module Markup
455
455
  r10 = SyntaxNode.new(input, (index-1)...index) if r10 == true
456
456
  r0 = r10
457
457
  else
458
- @index = i0
459
- r0 = nil
458
+ if (match_len = has_terminal?('del', false, index))
459
+ r11 = instantiate_node(SyntaxNode,input, index...(index + match_len))
460
+ @index += match_len
461
+ else
462
+ terminal_parse_failure('del')
463
+ r11 = nil
464
+ end
465
+ if r11
466
+ r11 = SyntaxNode.new(input, (index-1)...index) if r11 == true
467
+ r0 = r11
468
+ else
469
+ @index = i0
470
+ r0 = nil
471
+ end
460
472
  end
461
473
  end
462
474
  end
@@ -51,7 +51,7 @@ grammar Markup
51
51
  # QUESTION faster to do regex?
52
52
  # QUESTION can we cut stuff we aren't using? what about supporting hr?
53
53
  #'a' / 'b' / 'code' / 'color' / 'del' / 'em' / 'font' / 'i' / 'img' / 'link' / 'span' / 'strikethrough' / 'strong' / 'sub' / 'sup' / 'u'
54
- 'a' / 'code' / 'color' / 'del' / 'em' / 'font' / 'span' / 'strong' / 'sub' / 'sup'
54
+ 'a' / 'strong' / 'em' / 'code' / 'color' / 'font' / 'span' / 'button' / 'sub' / 'sup' / 'del'
55
55
  end
56
56
 
57
57
  rule void_tag_name
@@ -0,0 +1,45 @@
1
+ module Asciidoctor::Pdf::FormattedText
2
+ module TextBackgroundAndBorderRenderer
3
+ module_function
4
+
5
+ # render_behind is called before the text is printed
6
+ def render_behind fragment
7
+ return if (pdf = fragment.document).scratch?
8
+ data = fragment.format_state
9
+ if (border_offset = data[:border_offset])
10
+ at = [fragment.left - border_offset, fragment.top + border_offset]
11
+ width = fragment.width + border_offset * 2
12
+ height = fragment.height + border_offset * 2
13
+ else
14
+ at = fragment.top_left
15
+ width = fragment.width
16
+ height = fragment.height
17
+ end
18
+ border_radius = data[:border_radius]
19
+ if (background_color = data[:background_color])
20
+ prev_fill_color = pdf.fill_color
21
+ pdf.fill_color background_color
22
+ if border_radius
23
+ pdf.fill_rounded_rectangle at, width, height, border_radius
24
+ else
25
+ pdf.fill_rectangle at, width, height
26
+ end
27
+ pdf.fill_color prev_fill_color
28
+ end
29
+ if (border_width = data[:border_width])
30
+ border_color = data[:border_color]
31
+ prev_stroke_color = pdf.stroke_color
32
+ prev_line_width = pdf.line_width
33
+ pdf.stroke_color border_color
34
+ pdf.line_width border_width
35
+ if border_radius
36
+ pdf.stroke_rounded_rectangle at, width, height, border_radius
37
+ else
38
+ pdf.stroke_rectangle at, width, height
39
+ end
40
+ pdf.stroke_color prev_stroke_color
41
+ pdf.line_width prev_line_width
42
+ end
43
+ end
44
+ end
45
+ end
@@ -20,21 +20,44 @@ class Transform
20
20
  @merge_adjacent_text_nodes = options[:merge_adjacent_text_nodes]
21
21
  # TODO add support for character spacing
22
22
  if (theme = options[:theme])
23
- @link_font_settings = {
24
- color: theme.link_font_color,
25
- font: theme.link_font_family,
26
- size: theme.link_font_size,
27
- styles: to_styles(theme.link_font_style, theme.link_text_decoration)
28
- }.select! {|_, val| val }
29
- @monospaced_font_settings = {
30
- color: theme.literal_font_color,
31
- font: theme.literal_font_family,
32
- size: theme.literal_font_size,
33
- styles: to_styles(theme.literal_font_style)
34
- }.select! {|_, val| val }
23
+ @theme_settings = {
24
+ button: {
25
+ color: theme.button_font_color,
26
+ font: theme.button_font_family,
27
+ size: theme.button_font_size,
28
+ styles: to_styles(theme.button_font_style),
29
+ background_color: (button_bg_color = theme.button_background_color),
30
+ border_width: (button_border_width = theme.button_border_width),
31
+ border_color: button_border_width && (theme.button_border_color || theme.base_border_color),
32
+ border_offset: (button_bg_or_border = button_bg_color || button_border_width) && theme.button_border_offset,
33
+ border_radius: button_bg_or_border && theme.button_border_radius,
34
+ callback: button_bg_or_border && [TextBackgroundAndBorderRenderer],
35
+ }.compact,
36
+ code: {
37
+ color: theme.literal_font_color,
38
+ font: theme.literal_font_family,
39
+ size: theme.literal_font_size,
40
+ styles: to_styles(theme.literal_font_style),
41
+ background_color: (monospaced_bg_color = theme.literal_background_color),
42
+ border_width: (monospaced_border_width = theme.literal_border_width),
43
+ border_color: monospaced_border_width && (theme.literal_border_color || theme.base_border_color),
44
+ border_offset: (monospaced_bg_or_border = monospaced_bg_color || monospaced_border_width) && theme.literal_border_offset,
45
+ border_radius: monospaced_bg_or_border && theme.literal_border_radius,
46
+ callback: monospaced_bg_or_border && [TextBackgroundAndBorderRenderer],
47
+ }.compact,
48
+ link: {
49
+ color: theme.link_font_color,
50
+ font: theme.link_font_family,
51
+ size: theme.link_font_size,
52
+ styles: to_styles(theme.link_font_style, theme.link_text_decoration)
53
+ }.compact,
54
+ }
35
55
  else
36
- @link_font_settings = { color: '0000FF' }
37
- @monospaced_font_settings = { font: 'Courier', size: 0.9 }
56
+ @theme_settings = {
57
+ button: { font: 'Courier', styles: [:bold].to_set },
58
+ code: { font: 'Courier', size: 0.9 },
59
+ link: { color: '0000FF' },
60
+ }
38
61
  end
39
62
  end
40
63
 
@@ -79,7 +102,7 @@ class Transform
79
102
  image_format: attributes[:format],
80
103
  # a zero-width space in the text will cause the image to be duplicated
81
104
  text: (attributes[:alt].delete ZeroWidthSpace),
82
- callback: InlineImageRenderer
105
+ callback: [InlineImageRenderer],
83
106
  }
84
107
  if (img_w = attributes[:width])
85
108
  fragment[:image_width] = img_w
@@ -123,9 +146,9 @@ class Transform
123
146
  styles << :bold
124
147
  when :em
125
148
  styles << :italic
126
- when :code
127
- # NOTE prefer old value, except for styles, which should be combined
128
- fragment.update(@monospaced_font_settings) {|k, old_v, new_v| k == :styles ? old_v.merge(new_v) : old_v }
149
+ when :code, :button
150
+ # NOTE prefer old value, except for styles and callback, which should be combined
151
+ fragment.update(@theme_settings[tag_name]) {|k, oval, nval| k == :styles ? oval.merge(nval) : (k == :callback ? oval.union(nval) : oval) }
129
152
  when :color
130
153
  if !fragment[:color]
131
154
  if (rgb = attrs[:rgb])
@@ -168,7 +191,7 @@ class Transform
168
191
  fragment[:width] = value
169
192
  if (value = attrs[:align])
170
193
  fragment[:align] = value.to_sym
171
- fragment[:callback] = InlineTextAligner
194
+ (fragment[:callback] ||= []) << InlineTextAligner
172
195
  end
173
196
  end
174
197
  #if !fragment[:character_spacing] && (value = attrs[:character_spacing])
@@ -191,12 +214,12 @@ class Transform
191
214
  if (type = attrs[:type])
192
215
  fragment[:type] = type.to_sym
193
216
  end
194
- fragment[:callback] = InlineDestinationMarker
217
+ (fragment[:callback] ||= []) << InlineDestinationMarker
195
218
  visible = false
196
219
  end
197
220
  end
198
221
  # NOTE prefer old value, except for styles, which should be combined
199
- fragment.update(@link_font_settings) {|k, old_v, new_v| k == :styles ? old_v.merge(new_v) : old_v } if visible
222
+ fragment.update(@theme_settings[:link]) {|k, oval, nval| k == :styles ? oval.merge(nval) : oval } if visible
200
223
  when :sub
201
224
  styles << :subscript
202
225
  when :sup
@@ -39,7 +39,7 @@ module Extensions
39
39
 
40
40
  # Returns the effective (writable) width of the page
41
41
  #
42
- # If inside a fixed-height bounding box, returns height of box.
42
+ # If inside a bounding box, returns width of box.
43
43
  #
44
44
  def effective_page_width
45
45
  reference_bounds.width
@@ -748,20 +748,18 @@ module Extensions
748
748
  nil
749
749
  end
750
750
 
751
- # Create a new page for the specified image. If the
752
- # canvas option is true, the image is stretched to the
753
- # edges of the page (full coverage).
751
+ # Create a new page for the specified image. If the canvas option is true,
752
+ # the image is positioned relative to the boundaries of the page.
754
753
  def image_page file, options = {}
755
754
  start_new_page_discretely
756
- if options[:canvas]
757
- canvas do
758
- image file, width: bounds.width, height: bounds.height
759
- end
755
+ image_page_number = page_number
756
+ if options.delete :canvas
757
+ canvas { image file, ({ position: :center, vposition: :center }.merge options) }
760
758
  else
761
- image file, fit: [bounds.width, bounds.height]
759
+ image file, (options.merge position: :center, vposition: :center, fit: [bounds.width, bounds.height])
762
760
  end
763
- # FIXME shouldn't this be `go_to_page prev_page_number + 1`?
764
- go_to_page page_count
761
+ # NOTE advance to new page just in case the image function threw off the cursor
762
+ go_to_page image_page_number
765
763
  nil
766
764
  end
767
765
 
@@ -9,10 +9,20 @@ module Images
9
9
 
10
10
  # Dispatch to suitable image method in Prawn based on file extension.
11
11
  def image file, opts = {}
12
- # FIXME handle case when SVG is a File or IO object
13
- if ::String === file && (file.downcase.end_with? '.svg')
14
- opts[:fallback_font_name] ||= default_svg_font if respond_to? :default_svg_font
15
- svg((::File.read file), opts)
12
+ # FIXME handle case when SVG is an IO object
13
+ if ::String === file && (((opts = opts.dup).delete :format) == 'svg' || (file.downcase.end_with? '.svg'))
14
+ #opts[:enable_file_requests_with_root] = (::File.dirname file) unless opts.key? :enable_file_requests_with_root
15
+ #opts[:enable_web_requests] = allow_uri_read if !(opts.key? :enable_web_requests) && (respond_to? :allow_uri_read)
16
+ #opts[:fallback_font_name] = default_svg_font if !(opts.key? :fallback_font_name) && (respond_to? :default_svg_font)
17
+ if (opts.key? :fit) && (fit = opts.delete :fit) && !opts[:width] && !opts[:height]
18
+ svg (::File.read file), opts do |svg_doc|
19
+ max_width, max_height = fit
20
+ svg_doc.calculate_sizing requested_width: max_width if max_width && svg_doc.sizing.output_width != max_width
21
+ svg_doc.calculate_sizing requested_height: max_height if max_height && svg_doc.sizing.output_height > max_height
22
+ end
23
+ else
24
+ svg (::File.read file), opts
25
+ end
16
26
  else
17
27
  _initial_image file, opts
18
28
  end
@@ -22,9 +32,9 @@ module Images
22
32
  #
23
33
  # Returns a Hash containing :width and :height keys that map to the image's
24
34
  # intrinsic width and height values (in pixels)
25
- def intrinsic_image_dimensions path
26
- if path.end_with? '.svg'
27
- img_obj = ::Prawn::SVG::Interface.new ::File.read(path), self, {}
35
+ def intrinsic_image_dimensions path, format
36
+ if format == 'svg'
37
+ img_obj = ::Prawn::SVG::Interface.new (::File.read path), self, {}
28
38
  img_size = img_obj.document.sizing
29
39
  { width: img_size.output_width, height: img_size.output_height }
30
40
  else
@@ -32,6 +42,7 @@ module Images
32
42
  _, img_size = ::File.open(path, 'rb') {|fd| build_image_object fd }
33
43
  { width: img_size.width, height: img_size.height }
34
44
  end
45
+ rescue
35
46
  end
36
47
  end
37
48
 
@@ -19,9 +19,9 @@ class ThemeLoader
19
19
  DefaultThemePath = ::File.expand_path 'default-theme.yml', ThemesDir
20
20
  BaseThemePath = ::File.expand_path 'base-theme.yml', ThemesDir
21
21
 
22
- VariableRx = /\$([a-z0-9_]+)/
23
- LoneVariableRx = /^\$([a-z0-9_]+)$/
24
- HexColorEntryRx = /^(?<k>[[:blank:]]*[[:graph:]]+): +(?!null$)(?<q>["']?)#?(?<v>\w{3,6})\k<q> *(?:#.*)?$/
22
+ VariableRx = /\$([a-z0-9_-]+)/
23
+ LoneVariableRx = /^\$([a-z0-9_-]+)$/
24
+ HexColorEntryRx = /^(?<k> *\p{Graph}+): +(?!null$)(?<q>["']?)(?<h>#)?(?<v>[a-f0-9]{3,6})\k<q> *(?:#.*)?$/
25
25
  MultiplyDivideOpRx = /(-?\d+(?:\.\d+)?) +([*\/]) +(-?\d+(?:\.\d+)?)/
26
26
  AddSubtractOpRx = /(-?\d+(?:\.\d+)?) +([+\-]) +(-?\d+(?:\.\d+)?)/
27
27
  PrecisionFuncRx = /^(round|floor|ceil)\(/
@@ -35,7 +35,7 @@ class ThemeLoader
35
35
 
36
36
  # A marker module for a normalized CMYK array
37
37
  # Prevents normalizing CMYK value more than once
38
- module CmykColorValue
38
+ module CMYKColorValue
39
39
  include ColorValue
40
40
  def to_s
41
41
  %([#{join ', '}])
@@ -43,89 +43,89 @@ class ThemeLoader
43
43
  end
44
44
 
45
45
  def self.resolve_theme_file theme_name = nil, theme_path = nil
46
- theme_name ||= 'default'
47
- # if .yml extension is given, don't append -theme.yml
48
- if (theme_name.end_with? '.yml')
46
+ # if .yml extension is given, assume it's a path (don't append -theme.yml)
47
+ if ((theme_name ||= 'default').end_with? '.yml')
49
48
  # FIXME restrict to jail!
50
- # QUESTION why are we not using expand_path in this case?
51
- theme_path ? (::File.join theme_path, theme_name) : theme_name
49
+ theme_file = ::File.expand_path theme_name, theme_path
50
+ theme_path ||= ::File.dirname theme_file
52
51
  else
53
- # QUESTION should we append '-theme.yml' or just '.yml'?
54
- ::File.expand_path %(#{theme_name}-theme.yml), (theme_path || ThemesDir)
52
+ theme_file = ::File.expand_path %(#{theme_name}-theme.yml), (theme_path || (theme_path = ThemesDir))
55
53
  end
54
+ [theme_file, theme_path]
56
55
  end
57
56
 
58
- def self.resolve_theme_asset asset_path, theme_path = nil
57
+ def self.resolve_theme_asset asset_path, theme_path
59
58
  ::File.expand_path asset_path, (theme_path || ThemesDir)
60
59
  end
61
60
 
62
61
  # NOTE base theme is loaded "as is" (no post-processing)
63
62
  def self.load_base_theme
64
- ::OpenStruct.new(::SafeYAML.load_file BaseThemePath)
63
+ (::OpenStruct.new ::SafeYAML.load_file BaseThemePath).tap {|theme| theme.__dir__ = ThemesDir }
65
64
  end
66
65
 
67
- def self.load_theme theme_name = nil, theme_path = nil, opts = {}
68
- if (theme_file = resolve_theme_file theme_name, theme_path) == BaseThemePath ||
69
- (theme_file != DefaultThemePath && (opts.fetch :apply_base_theme, true))
70
- theme_data = load_base_theme
66
+ def self.load_theme theme_name = nil, theme_path = nil
67
+ theme_file, theme_path = resolve_theme_file theme_name, theme_path
68
+ if theme_file == BaseThemePath
69
+ load_base_theme
70
+ else
71
+ theme_data = load_file theme_file, nil, theme_path
72
+ unless theme_file == DefaultThemePath
73
+ # QUESTION should we enforce any other fallback values?
74
+ theme_data.base_align ||= 'left'
75
+ theme_data.code_font_family ||= (theme_data.literal_font_family || 'Courier')
76
+ theme_data.conum_font_family ||= (theme_data.literal_font_family || 'Courier')
77
+ end
78
+ theme_data.__dir__ = theme_path
79
+ theme_data
71
80
  end
72
-
73
- theme_file == BaseThemePath ? theme_data : (load_file theme_file, theme_data, theme_path)
74
81
  end
75
82
 
76
83
  def self.load_file filename, theme_data = nil, theme_path = nil
77
- yaml_data = ::SafeYAML.load (::File.read filename, encoding: ::Encoding::UTF_8).each_line.map {|l| l.sub HexColorEntryRx, '\k<k>: \'\k<v>\'' }.join
78
- if ::Hash === yaml_data && (extend_files = yaml_data.delete 'extends')
79
- [*extend_files].each do |extend_file|
80
- if extend_file == 'default'
81
- extend_file = resolve_theme_file extend_file, (extend_theme_path = ThemesDir)
82
- elsif extend_file.start_with? './'
83
- extend_file = resolve_theme_file extend_file, (extend_theme_path = (::File.dirname ::File.absolute_path filename))
84
- else
85
- extend_file = resolve_theme_file extend_file, (extend_theme_path = theme_path)
84
+ data = ::File.read filename, encoding: ::Encoding::UTF_8
85
+ data = data.each_line.map {|l|
86
+ l.sub(HexColorEntryRx) { %(#{(m = $~)[:k]}: #{m[:h] || (m[:k].end_with? 'color') ? "'#{m[:v]}'" : m[:v]}) }
87
+ }.join unless filename == DefaultThemePath
88
+ yaml_data = ::SafeYAML.load data
89
+ if ::Hash === yaml_data && (yaml_data.key? 'extends')
90
+ if (extends = yaml_data.delete 'extends')
91
+ [*extends].each do |extend_file|
92
+ if extend_file == 'base'
93
+ theme_data = theme_data ? (::OpenStruct.new theme_data.to_h.merge load_base_theme.to_h) : load_base_theme
94
+ next
95
+ elsif extend_file == 'default' || extend_file == 'default-with-fallback-font'
96
+ extend_file, extend_theme_path = resolve_theme_file extend_file
97
+ elsif extend_file.start_with? './'
98
+ extend_file, extend_theme_path = resolve_theme_file extend_file, (::File.dirname filename)
99
+ else
100
+ extend_file, extend_theme_path = resolve_theme_file extend_file, theme_path
101
+ end
102
+ theme_data = load_file extend_file, theme_data, extend_theme_path
86
103
  end
87
- theme_data = load_file extend_file, theme_data, extend_theme_path
88
104
  end
105
+ else
106
+ theme_data ||= (filename == DefaultThemePath ? nil : load_base_theme)
89
107
  end
90
108
  self.new.load yaml_data, theme_data, theme_path
91
109
  end
92
110
 
93
111
  def load hash, theme_data = nil, theme_path = nil
94
- theme_data ||= ::OpenStruct.new
95
- return theme_data unless ::Hash === hash
96
- base_code_font_family = theme_data.delete 'code_font_family'
97
- base_conum_font_family = theme_data.delete 'conum_font_family'
98
- hash.inject(theme_data) {|data, (key, val)| process_entry key, val, data }
99
- # NOTE remap legacy running content keys (e.g., header_recto_content_left => header_recto_left_content)
100
- %w(header_recto header_verso footer_recto footer_verso).each do |periphery_face|
101
- %w(left center right).each do |align|
102
- if (val = theme_data.delete %(#{periphery_face}_content_#{align}))
103
- theme_data[%(#{periphery_face}_#{align}_content)] = val
104
- end
105
- end
106
- end
107
- theme_data.base_align ||= 'left'
108
- theme_data.code_font_family ||= (theme_data.literal_font_family || base_code_font_family)
109
- theme_data.conum_font_family ||= (theme_data.literal_font_family || base_conum_font_family)
110
- # QUESTION should we do any other post-load calculations or defaults?
111
- theme_data
112
+ ::Hash === hash ? hash.reduce(theme_data || ::OpenStruct.new) {|data, (key, val)| process_entry key, val, data } : (theme_data || ::OpenStruct.new)
112
113
  end
113
114
 
114
115
  private
115
116
 
116
117
  def process_entry key, val, data
117
- if key.start_with? 'font_'
118
+ key = key.tr '-', '_' if key.include? '-'
119
+ if key == 'font_catalog' || key == 'font_fallbacks'
118
120
  data[key] = val
119
121
  elsif key.start_with? 'admonition_icon_'
120
122
  data[key] = (val || {}).map do |(key2, val2)|
121
123
  [key2.to_sym, (key2.end_with? '_color') ? to_color(evaluate val2, data) : (evaluate val2, data)]
122
124
  end.to_h
123
125
  elsif ::Hash === val
124
- val.each do |key2, val2|
125
- process_entry %(#{key}_#{key2.tr '-', '_'}), val2, data
126
- end
126
+ val.each {|subkey, subval| process_entry %(#{key}_#{subkey}), subval, data }
127
127
  elsif key.end_with? '_color'
128
- # QUESTION do we need to evaluate_math in this case?
128
+ # QUESTION do we really need to evaluate_math in this case?
129
129
  data[key] = to_color(evaluate val, data)
130
130
  elsif %(#{key.chomp '_'}_).include? '_content_'
131
131
  data[key] = (expand_vars val.to_s, data).to_s
@@ -150,21 +150,27 @@ class ThemeLoader
150
150
  def expand_vars expr, vars
151
151
  if (idx = (expr.index '$'))
152
152
  if idx == 0 && expr =~ LoneVariableRx
153
- if vars.respond_to? $1
154
- vars[$1]
153
+ if (key = $1).include? '-'
154
+ key = key.tr '-', '_'
155
+ end
156
+ if vars.respond_to? key
157
+ vars[key]
155
158
  else
156
159
  logger.warn %(unknown variable reference in PDF theme: $#{$1})
157
160
  expr
158
161
  end
159
162
  else
160
- expr.gsub(VariableRx) {
161
- if vars.respond_to? $1
162
- vars[$1]
163
+ expr.gsub(VariableRx) do
164
+ if (key = $1).include? '-'
165
+ key = key.tr '-', '_'
166
+ end
167
+ if vars.respond_to? key
168
+ vars[key]
163
169
  else
164
170
  logger.warn %(unknown variable reference in PDF theme: $#{$1})
165
171
  $&
166
172
  end
167
- }
173
+ end
168
174
  end
169
175
  else
170
176
  expr
@@ -178,16 +184,24 @@ class ThemeLoader
178
184
  # NOTE leave % as a string; handled by converter for now
179
185
  expr = resolve_measurement_values(original = expr)
180
186
  while true
181
- result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
182
- unchanged = (result == expr)
183
- expr = result
184
- break if unchanged
187
+ if (expr.count '*/') > 0
188
+ result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
189
+ unchanged = (result == expr)
190
+ expr = result
191
+ break if unchanged
192
+ else
193
+ break
194
+ end
185
195
  end
186
196
  while true
187
- result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
188
- unchanged = (result == expr)
189
- expr = result
190
- break if unchanged
197
+ if (expr.count '+-') > 0
198
+ result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
199
+ unchanged = (result == expr)
200
+ expr = result
201
+ break if unchanged
202
+ else
203
+ break
204
+ end
191
205
  end
192
206
  if (expr.end_with? ')') && expr =~ PrecisionFuncRx
193
207
  op = $1
@@ -206,13 +220,6 @@ class ThemeLoader
206
220
  when ColorValue
207
221
  # already converted
208
222
  return value
209
- when ::String
210
- if value == 'transparent'
211
- # FIXME should we have a TransparentColorValue class?
212
- return HexColorValue.new value
213
- elsif value.length == 6
214
- return HexColorValue.new value.upcase
215
- end
216
223
  when ::Array
217
224
  case value.length
218
225
  # CMYK value
@@ -231,7 +238,7 @@ class ThemeLoader
231
238
  when [100, 100, 100, 100]
232
239
  return HexColorValue.new '000000'
233
240
  else
234
- value.extend CmykColorValue
241
+ value.extend CMYKColorValue
235
242
  return value
236
243
  end
237
244
  # RGB value
@@ -241,9 +248,20 @@ class ThemeLoader
241
248
  else
242
249
  value = value.join
243
250
  end
251
+ when ::String
252
+ if value == 'transparent'
253
+ # FIXME should we have a TransparentColorValue class?
254
+ return HexColorValue.new value
255
+ elsif value.length == 6
256
+ return HexColorValue.new value.upcase
257
+ end
258
+ when ::NilClass
259
+ return nil
244
260
  else
245
- # Unknown type; coerce to a string
246
- value = value.to_s
261
+ # Unknown type (usually Integer); coerce to String
262
+ if (value = value.to_s).length == 6
263
+ return HexColorValue.new value.upcase
264
+ end
247
265
  end
248
266
  value = case value.length
249
267
  when 6