asciidoctor-pdf 1.5.0.beta.8 → 1.5.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +49 -0
  3. data/LICENSE.adoc +1 -1
  4. data/NOTICE.adoc +1 -1
  5. data/README.adoc +43 -47
  6. data/asciidoctor-pdf.gemspec +5 -1
  7. data/bin/asciidoctor-pdf-optimize +1 -1
  8. data/data/themes/base-theme.yml +4 -3
  9. data/data/themes/default-theme.yml +10 -5
  10. data/docs/theming-guide.adoc +286 -22
  11. data/lib/asciidoctor-pdf.rb +1 -0
  12. data/lib/asciidoctor-pdf/converter.rb +1 -0
  13. data/lib/asciidoctor-pdf/version.rb +1 -0
  14. data/lib/asciidoctor/pdf.rb +13 -2
  15. data/lib/asciidoctor/pdf/converter.rb +3962 -3955
  16. data/lib/asciidoctor/pdf/ext.rb +9 -0
  17. data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -0
  18. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +1 -0
  19. data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +1 -0
  20. data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +1 -0
  21. data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +18 -16
  22. data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +3 -2
  23. data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +2 -1
  24. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +3 -4
  25. data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +8 -6
  26. data/lib/asciidoctor/pdf/ext/core.rb +2 -0
  27. data/lib/asciidoctor/pdf/ext/core/array.rb +1 -0
  28. data/lib/asciidoctor/pdf/ext/core/hash.rb +1 -0
  29. data/lib/asciidoctor/pdf/ext/core/numeric.rb +4 -3
  30. data/lib/asciidoctor/pdf/ext/core/object.rb +1 -0
  31. data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +8 -1
  32. data/lib/asciidoctor/pdf/ext/core/regexp.rb +1 -0
  33. data/lib/asciidoctor/pdf/ext/core/string.rb +6 -7
  34. data/lib/asciidoctor/pdf/ext/pdf-core.rb +1 -0
  35. data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +3 -4
  36. data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +2 -1
  37. data/lib/asciidoctor/pdf/ext/prawn-svg.rb +1 -0
  38. data/lib/asciidoctor/pdf/ext/prawn-svg/interface.rb +11 -8
  39. data/lib/asciidoctor/pdf/ext/prawn-table.rb +2 -1
  40. data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +9 -10
  41. data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +62 -57
  42. data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +5 -3
  43. data/lib/asciidoctor/pdf/ext/prawn-templates.rb +1 -0
  44. data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
  45. data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +73 -72
  46. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +814 -818
  47. data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +4 -3
  48. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +2 -1
  49. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +7 -2
  50. data/lib/asciidoctor/pdf/ext/prawn/images.rb +45 -44
  51. data/lib/asciidoctor/pdf/ext/pygments.rb +34 -0
  52. data/lib/asciidoctor/pdf/ext/rouge.rb +1 -1
  53. data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +181 -149
  54. data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
  55. data/lib/asciidoctor/pdf/formatted_text.rb +2 -0
  56. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +35 -34
  57. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +8 -7
  58. data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +13 -14
  59. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +112 -133
  60. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +43 -41
  61. data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +15 -14
  62. data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +43 -0
  63. data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +46 -37
  64. data/lib/asciidoctor/pdf/formatted_text/transform.rb +371 -352
  65. data/lib/asciidoctor/pdf/index_catalog.rb +99 -95
  66. data/lib/asciidoctor/pdf/measurements.rb +51 -48
  67. data/lib/asciidoctor/pdf/optimizer.rb +34 -31
  68. data/lib/asciidoctor/pdf/pdfmark.rb +34 -33
  69. data/lib/asciidoctor/pdf/roman_numeral.rb +80 -79
  70. data/lib/asciidoctor/pdf/sanitizer.rb +38 -37
  71. data/lib/asciidoctor/pdf/temporary_path.rb +10 -9
  72. data/lib/asciidoctor/pdf/text_transformer.rb +101 -100
  73. data/lib/asciidoctor/pdf/theme_loader.rb +258 -256
  74. data/lib/asciidoctor/pdf/version.rb +5 -4
  75. metadata +55 -6
  76. data/lib/asciidoctor/pdf/ext/rouge/themes/bw.rb +0 -39
  77. data/lib/asciidoctor/pdf/ext/ttfunk.rb +0 -9
@@ -1,302 +1,304 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'safe_yaml/load'
3
4
  require 'ostruct'
4
5
  require_relative 'measurements'
5
6
 
6
7
  module Asciidoctor
7
- module PDF
8
- class ThemeLoader
9
- include ::Asciidoctor::PDF::Measurements
10
- if defined? ::Asciidoctor::Logging
11
- include ::Asciidoctor::Logging
12
- else
13
- include ::Asciidoctor::LoggingShim
14
- end
8
+ module PDF
9
+ class ThemeLoader
10
+ include ::Asciidoctor::PDF::Measurements
11
+ if defined? ::Asciidoctor::Logging
12
+ include ::Asciidoctor::Logging
13
+ else
14
+ include ::Asciidoctor::LoggingShim
15
+ end
15
16
 
16
- DataDir = ::File.absolute_path %(#{__dir__}/../../../data)
17
- ThemesDir = ::File.join DataDir, 'themes'
18
- FontsDir = ::File.join DataDir, 'fonts'
19
- BaseThemePath = ::File.join ThemesDir, 'base-theme.yml'
17
+ DataDir = ::File.absolute_path %(#{__dir__}/../../../data)
18
+ ThemesDir = ::File.join DataDir, 'themes'
19
+ FontsDir = ::File.join DataDir, 'fonts'
20
+ BaseThemePath = ::File.join ThemesDir, 'base-theme.yml'
20
21
 
21
- VariableRx = /\$([a-z0-9_-]+)/
22
- LoneVariableRx = /^\$([a-z0-9_-]+)$/
23
- HexColorEntryRx = /^(?<k> *\p{Graph}+): +(?!null$)(?<q>["']?)(?<h>#)?(?<v>[a-fA-F0-9]{3,6})\k<q> *(?:#.*)?$/
24
- MultiplyDivideOpRx = /(-?\d+(?:\.\d+)?) +([*\/]) +(-?\d+(?:\.\d+)?)/
25
- AddSubtractOpRx = /(-?\d+(?:\.\d+)?) +([+\-]) +(-?\d+(?:\.\d+)?)/
26
- PrecisionFuncRx = /^(round|floor|ceil)\(/
22
+ VariableRx = /\$([a-z0-9_-]+)/
23
+ LoneVariableRx = /^\$([a-z0-9_-]+)$/
24
+ HexColorEntryRx = /^(?<k> *\p{Graph}+): +(?!null$)(?<q>["']?)(?<h>#)?(?<v>[a-fA-F0-9]{3,6})\k<q> *(?:#.*)?$/
25
+ MultiplyDivideOpRx = /(-?\d+(?:\.\d+)?) +([*\/]) +(-?\d+(?:\.\d+)?)/
26
+ AddSubtractOpRx = /(-?\d+(?:\.\d+)?) +([+\-]) +(-?\d+(?:\.\d+)?)/
27
+ PrecisionFuncRx = /^(round|floor|ceil)\(/
27
28
 
28
- # TODO implement white? & black? methods
29
- module ColorValue; end
29
+ # TODO: implement white? & black? methods
30
+ module ColorValue; end
30
31
 
31
- class HexColorValue < String
32
- include ColorValue
33
- end
32
+ class HexColorValue < String
33
+ include ColorValue
34
+ end
34
35
 
35
- # A marker module for a normalized CMYK array
36
- # Prevents normalizing CMYK value more than once
37
- module CMYKColorValue
38
- include ColorValue
39
- def to_s
40
- %([#{join ', '}])
41
- end
42
- end
36
+ # A marker module for a normalized CMYK array
37
+ # Prevents normalizing CMYK value more than once
38
+ module CMYKColorValue
39
+ include ColorValue
40
+ def to_s
41
+ %([#{join ', '}])
42
+ end
43
+ end
43
44
 
44
- def self.resolve_theme_file theme_name = nil, theme_dir = nil
45
- # NOTE if .yml extension is given, assume it's a path (don't append -theme.yml)
46
- if theme_name && (theme_name.end_with? '.yml')
47
- # FIXME restrict to jail!
48
- if theme_dir
49
- theme_path = ::File.absolute_path theme_name, (theme_dir = ::File.expand_path theme_dir)
50
- else
51
- theme_dir = ::File.dirname(theme_path = (::File.expand_path theme_name))
45
+ def self.resolve_theme_file theme_name = nil, theme_dir = nil
46
+ # NOTE if .yml extension is given, assume it's a path (don't append -theme.yml)
47
+ if theme_name && (theme_name.end_with? '.yml')
48
+ # FIXME: restrict to jail!
49
+ if theme_dir
50
+ theme_path = ::File.absolute_path theme_name, (theme_dir = ::File.expand_path theme_dir)
51
+ else
52
+ theme_path = ::File.expand_path theme_name
53
+ theme_dir = ::File.dirname theme_path
54
+ end
55
+ else
56
+ theme_dir = theme_dir ? (::File.expand_path theme_dir) : ThemesDir
57
+ theme_path = ::File.absolute_path ::File.join theme_dir, %(#{theme_name || 'default'}-theme.yml)
58
+ end
59
+ [theme_path, theme_dir]
52
60
  end
53
- else
54
- theme_dir = theme_dir ? (::File.expand_path theme_dir) : ThemesDir
55
- theme_path = ::File.absolute_path ::File.join theme_dir, %(#{theme_name || 'default'}-theme.yml)
56
- end
57
- [theme_path, theme_dir]
58
- end
59
61
 
60
- def self.resolve_theme_asset asset_path, theme_dir = nil
61
- ::File.absolute_path asset_path, (theme_dir || ThemesDir)
62
- end
62
+ def self.resolve_theme_asset asset_path, theme_dir = nil
63
+ ::File.absolute_path asset_path, (theme_dir || ThemesDir)
64
+ end
63
65
 
64
- # NOTE base theme is loaded "as is" (no post-processing)
65
- def self.load_base_theme
66
- (::OpenStruct.new ::SafeYAML.load_file BaseThemePath).tap {|theme| theme.__dir__ = ThemesDir }
67
- end
66
+ # NOTE base theme is loaded "as is" (no post-processing)
67
+ def self.load_base_theme
68
+ (::OpenStruct.new ::SafeYAML.load_file BaseThemePath).tap {|theme| theme.__dir__ = ThemesDir }
69
+ end
68
70
 
69
- def self.load_theme theme_name = nil, theme_dir = nil
70
- theme_path, theme_dir = resolve_theme_file theme_name, theme_dir
71
- if theme_path == BaseThemePath
72
- load_base_theme
73
- else
74
- theme_data = load_file theme_path, nil, theme_dir
75
- unless (::File.dirname theme_path) == ThemesDir
76
- theme_data.base_align ||= 'left'
77
- theme_data.base_line_height ||= 1
78
- theme_data.base_font_color ||= '000000'
79
- theme_data.code_font_family ||= (theme_data.literal_font_family || 'Courier')
80
- theme_data.conum_font_family ||= (theme_data.literal_font_family || 'Courier')
71
+ def self.load_theme theme_name = nil, theme_dir = nil
72
+ theme_path, theme_dir = resolve_theme_file theme_name, theme_dir
73
+ if theme_path == BaseThemePath
74
+ load_base_theme
75
+ else
76
+ theme_data = load_file theme_path, nil, theme_dir
77
+ unless (::File.dirname theme_path) == ThemesDir
78
+ theme_data.base_align ||= 'left'
79
+ theme_data.base_line_height ||= 1
80
+ theme_data.base_font_color ||= '000000'
81
+ theme_data.code_font_family ||= (theme_data.literal_font_family || 'Courier')
82
+ theme_data.conum_font_family ||= (theme_data.literal_font_family || 'Courier')
83
+ end
84
+ theme_data.__dir__ = theme_dir
85
+ theme_data
86
+ end
81
87
  end
82
- theme_data.__dir__ = theme_dir
83
- theme_data
84
- end
85
- end
86
88
 
87
- def self.load_file filename, theme_data = nil, theme_dir = nil
88
- data = ::File.read filename, mode: 'r:UTF-8', newline: :universal
89
- data = data.each_line.map {|line|
90
- line.sub(HexColorEntryRx) { %(#{(m = $~)[:k]}: #{m[:h] || (m[:k].end_with? 'color') ? "'#{m[:v]}'" : m[:v]}) }
91
- }.join unless (::File.dirname filename) == ThemesDir
92
- yaml_data = ::SafeYAML.load data
93
- if ::Hash === yaml_data && (yaml_data.key? 'extends')
94
- if (extends = yaml_data.delete 'extends')
95
- [*extends].each do |extend_path|
96
- if extend_path == 'base'
97
- theme_data = theme_data ? (::OpenStruct.new theme_data.to_h.merge load_base_theme.to_h) : load_base_theme
98
- next
99
- elsif extend_path == 'default' || extend_path == 'default-with-fallback-font'
100
- extend_path, extend_theme_dir = resolve_theme_file extend_path, ThemesDir
101
- elsif extend_path.start_with? './'
102
- extend_path, extend_theme_dir = resolve_theme_file extend_path, (::File.dirname filename)
103
- else
104
- extend_path, extend_theme_dir = resolve_theme_file extend_path, theme_dir
89
+ def self.load_file filename, theme_data = nil, theme_dir = nil
90
+ data = ::File.read filename, mode: 'r:UTF-8', newline: :universal
91
+ data = data.each_line.map {|line|
92
+ line.sub(HexColorEntryRx) { %(#{(m = $~)[:k]}: #{m[:h] || (m[:k].end_with? 'color') ? "'#{m[:v]}'" : m[:v]}) }
93
+ }.join unless (::File.dirname filename) == ThemesDir
94
+ yaml_data = ::SafeYAML.load data
95
+ if ::Hash === yaml_data && (yaml_data.key? 'extends')
96
+ if (extends = yaml_data.delete 'extends')
97
+ [*extends].each do |extend_path|
98
+ if extend_path == 'base'
99
+ theme_data = theme_data ? (::OpenStruct.new theme_data.to_h.merge load_base_theme.to_h) : load_base_theme
100
+ next
101
+ elsif extend_path == 'default' || extend_path == 'default-with-fallback-font'
102
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, ThemesDir
103
+ elsif extend_path.start_with? './'
104
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, (::File.dirname filename)
105
+ else
106
+ extend_path, extend_theme_dir = resolve_theme_file extend_path, theme_dir
107
+ end
108
+ theme_data = load_file extend_path, theme_data, extend_theme_dir
109
+ end
105
110
  end
106
- theme_data = load_file extend_path, theme_data, extend_theme_dir
111
+ else
112
+ theme_data ||= ((::File.dirname filename) == ThemesDir ? nil : load_base_theme)
107
113
  end
114
+ new.load yaml_data, theme_data
108
115
  end
109
- else
110
- theme_data ||= ((::File.dirname filename) == ThemesDir ? nil : load_base_theme)
111
- end
112
- self.new.load yaml_data, theme_data, theme_dir
113
- end
114
116
 
115
- def load hash, theme_data = nil, theme_dir = nil
116
- ::Hash === hash ? hash.reduce(theme_data || ::OpenStruct.new) {|data, (key, val)| process_entry key, val, data, true } : (theme_data || ::OpenStruct.new)
117
- end
117
+ def load hash, theme_data = nil
118
+ ::Hash === hash ? hash.reduce(theme_data || ::OpenStruct.new) {|data, (key, val)| process_entry key, val, data, true } : (theme_data || ::OpenStruct.new)
119
+ end
118
120
 
119
- private
121
+ private
120
122
 
121
- def process_entry key, val, data, normalize_key = false
122
- key = key.tr '-', '_' if normalize_key && (key.include? '-')
123
- if key == 'font'
124
- val.each do |subkey, subval|
125
- process_entry %(#{key}_#{subkey}), subval, data if subkey == 'catalog' || subkey == 'fallbacks'
126
- end if ::Hash === val
127
- elsif key == 'font_catalog'
128
- data[key] = ::Hash === val ? val.reduce({}) {|accum, (name, styles)|
129
- accum[name] = styles.reduce({}) do |subaccum, (style, path)|
130
- if (path.start_with? 'GEM_FONTS_DIR') && (sep = path[13])
131
- path = %(#{FontsDir}#{sep}#{path.slice 14, path.length})
123
+ def process_entry key, val, data, normalize_key = false
124
+ key = key.tr '-', '_' if normalize_key && (key.include? '-')
125
+ if key == 'font'
126
+ val.each do |subkey, subval|
127
+ process_entry %(#{key}_#{subkey}), subval, data if subkey == 'catalog' || subkey == 'fallbacks'
128
+ end if ::Hash === val
129
+ elsif key == 'font_catalog'
130
+ data[key] = ::Hash === val ? val.reduce({}) do |accum, (name, styles)| # rubocop:disable Style/EachWithObject
131
+ accum[name] = styles.reduce({}) do |subaccum, (style, path)| # rubocop:disable Style/EachWithObject
132
+ if (path.start_with? 'GEM_FONTS_DIR') && (sep = path[13])
133
+ path = %(#{FontsDir}#{sep}#{path.slice 14, path.length})
134
+ end
135
+ subaccum[style] = expand_vars path, data
136
+ subaccum
137
+ end if ::Hash === styles
138
+ accum
139
+ end : {}
140
+ elsif key == 'font_fallbacks'
141
+ data[key] = ::Array === val ? val.map {|name| expand_vars name.to_s, data } : []
142
+ elsif key.start_with? 'admonition_icon_'
143
+ data[key] = val ? val.map {|(key2, val2)|
144
+ key2 = key2.tr '-', '_' if key2.include? '-'
145
+ [key2.to_sym, (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)]
146
+ }.to_h : {}
147
+ elsif ::Hash === val
148
+ val.each do |subkey, subval|
149
+ process_entry %(#{key}_#{key == 'role' || !(subkey.include? '-') ? subkey : (subkey.tr '-', '_')}), subval, data
132
150
  end
133
- subaccum[style] = expand_vars path, data
134
- subaccum
135
- end if ::Hash === styles
136
- accum
137
- } : {}
138
- elsif key == 'font_fallbacks'
139
- data[key] = ::Array === val ? val.map {|name| expand_vars name.to_s, data } : []
140
- elsif key.start_with? 'admonition_icon_'
141
- data[key] = val ? val.map {|(key2, val2)|
142
- key2 = key2.tr '-', '_' if key2.include? '-'
143
- [key2.to_sym, (key2.end_with? '_color') ? to_color(evaluate val2, data) : (evaluate val2, data)]
144
- }.to_h : {}
145
- elsif ::Hash === val
146
- val.each do |subkey, subval|
147
- process_entry %(#{key}_#{key == 'role' || !(subkey.include? '-') ? subkey : (subkey.tr '-', '_')}), subval, data
151
+ elsif key.end_with? '_color'
152
+ # QUESTION do we really need to evaluate_math in this case?
153
+ data[key] = to_color evaluate val, data
154
+ elsif key.end_with? '_content'
155
+ data[key] = (expand_vars val.to_s, data).to_s
156
+ else
157
+ data[key] = evaluate val, data
158
+ end
159
+ data
148
160
  end
149
- elsif key.end_with? '_color'
150
- # QUESTION do we really need to evaluate_math in this case?
151
- data[key] = to_color(evaluate val, data)
152
- elsif key.end_with? '_content'
153
- data[key] = (expand_vars val.to_s, data).to_s
154
- else
155
- data[key] = evaluate val, data
156
- end
157
- data
158
- end
159
-
160
- def evaluate expr, vars
161
- case expr
162
- when ::String
163
- evaluate_math(expand_vars expr, vars)
164
- when ::Array
165
- expr.map {|e| evaluate e, vars }
166
- else
167
- expr
168
- end
169
- end
170
161
 
171
- # NOTE we assume expr is a String
172
- def expand_vars expr, vars
173
- if (idx = (expr.index '$'))
174
- if idx == 0 && expr =~ LoneVariableRx
175
- if (key = $1).include? '-'
176
- key = key.tr '-', '_'
177
- end
178
- if vars.respond_to? key
179
- vars[key]
162
+ def evaluate expr, vars
163
+ case expr
164
+ when ::String
165
+ evaluate_math expand_vars expr, vars
166
+ when ::Array
167
+ expr.map {|e| evaluate e, vars }
180
168
  else
181
- logger.warn %(unknown variable reference in PDF theme: $#{$1})
182
169
  expr
183
170
  end
184
- else
185
- expr.gsub(VariableRx) do
186
- if (key = $1).include? '-'
187
- key = key.tr '-', '_'
188
- end
189
- if vars.respond_to? key
190
- vars[key]
171
+ end
172
+
173
+ # NOTE we assume expr is a String
174
+ def expand_vars expr, vars
175
+ if (idx = (expr.index '$'))
176
+ if idx == 0 && expr =~ LoneVariableRx
177
+ if (key = $1).include? '-'
178
+ key = key.tr '-', '_'
179
+ end
180
+ if vars.respond_to? key
181
+ vars[key]
182
+ else
183
+ logger.warn %(unknown variable reference in PDF theme: $#{$1})
184
+ expr
185
+ end
191
186
  else
192
- logger.warn %(unknown variable reference in PDF theme: $#{$1})
193
- $&
187
+ expr.gsub VariableRx do
188
+ if (key = $1).include? '-'
189
+ key = key.tr '-', '_'
190
+ end
191
+ if vars.respond_to? key
192
+ vars[key]
193
+ else
194
+ logger.warn %(unknown variable reference in PDF theme: $#{$1})
195
+ $&
196
+ end
197
+ end
194
198
  end
199
+ else
200
+ expr
195
201
  end
196
202
  end
197
- else
198
- expr
199
- end
200
- end
201
203
 
202
- def evaluate_math expr
203
- return expr if !(::String === expr) || ColorValue === expr
204
- # resolve measurement values (e.g., 0.5in => 36)
205
- # QUESTION should we round the value? perhaps leave that to the precision functions
206
- # NOTE leave % as a string; handled by converter for now
207
- expr = resolve_measurement_values(original = expr)
208
- while true
209
- if (expr.count '*/') > 0
210
- result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
211
- unchanged = (result == expr)
212
- expr = result
213
- break if unchanged
214
- else
215
- break
216
- end
217
- end
218
- while true
219
- if (expr.count '+-') > 0
220
- result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
221
- unchanged = (result == expr)
222
- expr = result
223
- break if unchanged
224
- else
225
- break
204
+ def evaluate_math expr
205
+ return expr if !(::String === expr) || ColorValue === expr
206
+ # resolve measurement values (e.g., 0.5in => 36)
207
+ # QUESTION should we round the value? perhaps leave that to the precision functions
208
+ # NOTE leave % as a string; handled by converter for now
209
+ original, expr = expr, (resolve_measurement_values expr)
210
+ loop do
211
+ if (expr.count '*/') > 0
212
+ result = expr.gsub(MultiplyDivideOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
213
+ unchanged = (result == expr)
214
+ expr = result
215
+ break if unchanged
216
+ else
217
+ break
218
+ end
219
+ end
220
+ loop do
221
+ if (expr.count '+-') > 0
222
+ result = expr.gsub(AddSubtractOpRx) { $1.to_f.send $2.to_sym, $3.to_f }
223
+ unchanged = (result == expr)
224
+ expr = result
225
+ break if unchanged
226
+ else
227
+ break
228
+ end
229
+ end
230
+ if (expr.end_with? ')') && expr =~ PrecisionFuncRx
231
+ op = $1
232
+ offset = op.length + 1
233
+ expr = expr[offset...-1].to_f.send op.to_sym
234
+ end
235
+ if expr == original
236
+ original
237
+ else
238
+ (int_val = expr.to_i) == (flt_val = expr.to_f) ? int_val : flt_val
239
+ end
226
240
  end
227
- end
228
- if (expr.end_with? ')') && expr =~ PrecisionFuncRx
229
- op = $1
230
- offset = op.length + 1
231
- expr = expr[offset...-1].to_f.send op.to_sym
232
- end
233
- if expr == original
234
- original
235
- else
236
- (int_val = expr.to_i) == (flt_val = expr.to_f) ? int_val : flt_val
237
- end
238
- end
239
241
 
240
- def to_color value
241
- case value
242
- when ColorValue
243
- # already converted
244
- return value
245
- when ::Array
246
- case value.length
247
- # CMYK value
248
- when 4
249
- value = value.map do |e|
250
- if ::Numeric === e
251
- e = e * 100.0 unless e > 1
242
+ def to_color value
243
+ case value
244
+ when ColorValue
245
+ # already converted
246
+ return value
247
+ when ::Array
248
+ case value.length
249
+ # CMYK value
250
+ when 4
251
+ value = value.map do |e|
252
+ if ::Numeric === e
253
+ e *= 100.0 unless e > 1
254
+ else
255
+ e = e.to_s.chomp('%').to_f
256
+ end
257
+ e == (int_e = e.to_i) ? int_e : e
258
+ end
259
+ case value
260
+ when [0, 0, 0, 0]
261
+ return HexColorValue.new 'FFFFFF'
262
+ when [100, 100, 100, 100]
263
+ return HexColorValue.new '000000'
264
+ else
265
+ value.extend CMYKColorValue
266
+ return value
267
+ end
268
+ # RGB value
269
+ when 3
270
+ return HexColorValue.new value.map {|e| '%02X' % e }.join
271
+ # Nonsense array value; flatten to string
252
272
  else
253
- e = e.to_s.chomp('%').to_f
273
+ value = value.join
274
+ end
275
+ when ::String
276
+ if value == 'transparent'
277
+ # FIXME: should we have a TransparentColorValue class?
278
+ return HexColorValue.new value
279
+ elsif value.length == 6
280
+ return HexColorValue.new value.upcase
281
+ end
282
+ when ::NilClass
283
+ return nil
284
+ else
285
+ # Unknown type (usually Integer); coerce to String
286
+ if (value = value.to_s).length == 6
287
+ return HexColorValue.new value.upcase
254
288
  end
255
- e == (int_e = e.to_i) ? int_e : e
256
289
  end
257
- case value
258
- when [0, 0, 0, 0]
259
- return HexColorValue.new 'FFFFFF'
260
- when [100, 100, 100, 100]
261
- return HexColorValue.new '000000'
290
+ case value.length
291
+ when 6
292
+ resolved_value = value
293
+ when 3
294
+ # expand hex shorthand (e.g., f00 -> ff0000)
295
+ resolved_value = value.each_char.map {|c| c * 2 }.join
262
296
  else
263
- value.extend CMYKColorValue
264
- return value
297
+ # truncate or pad with leading zeros (e.g., ff -> 0000ff)
298
+ resolved_value = (value.slice 0, 6).rjust 6, '0'
265
299
  end
266
- # RGB value
267
- when 3
268
- return HexColorValue.new value.map {|e| '%02X' % e }.join
269
- # Nonsense array value; flatten to string
270
- else
271
- value = value.join
272
- end
273
- when ::String
274
- if value == 'transparent'
275
- # FIXME should we have a TransparentColorValue class?
276
- return HexColorValue.new value
277
- elsif value.length == 6
278
- return HexColorValue.new value.upcase
300
+ HexColorValue.new resolved_value.upcase
279
301
  end
280
- when ::NilClass
281
- return nil
282
- else
283
- # Unknown type (usually Integer); coerce to String
284
- if (value = value.to_s).length == 6
285
- return HexColorValue.new value.upcase
286
- end
287
- end
288
- value = case value.length
289
- when 6
290
- value
291
- when 3
292
- # expand hex shorthand (e.g., f00 -> ff0000)
293
- value.each_char.map {|c| c * 2 }.join
294
- else
295
- # truncate or pad with leading zeros (e.g., ff -> 0000ff)
296
- value[0..5].rjust 6, '0'
297
302
  end
298
- HexColorValue.new value.upcase
299
303
  end
300
304
  end
301
- end
302
- end