eideticrml 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +55 -0
  3. data/lib/erml.rb +345 -0
  4. data/lib/erml_layout_managers.rb +667 -0
  5. data/lib/erml_rules.rb +104 -0
  6. data/lib/erml_styles.rb +304 -0
  7. data/lib/erml_support.rb +105 -0
  8. data/lib/erml_widget_factories.rb +38 -0
  9. data/lib/erml_widgets.rb +1895 -0
  10. data/samples/test10_rich_text.erml +17 -0
  11. data/samples/test11_table_layout.erml +30 -0
  12. data/samples/test12_shapes.erml +32 -0
  13. data/samples/test13_polygons.erml +28 -0
  14. data/samples/test14_images.erml +19 -0
  15. data/samples/test15_lines.erml +43 -0
  16. data/samples/test16_classes.erml +34 -0
  17. data/samples/test17_rules.erml +24 -0
  18. data/samples/test18_preformatted_text.erml +9 -0
  19. data/samples/test19_erb.erml.erb +26 -0
  20. data/samples/test1_empty_doc.erml +2 -0
  21. data/samples/test20_haml.erml.haml +20 -0
  22. data/samples/test21_shift_widgets.erml +47 -0
  23. data/samples/test22_multipage_flow_layout.erml +40 -0
  24. data/samples/test23_pageno.erml +17 -0
  25. data/samples/test24_headers_footers.erml.erb +37 -0
  26. data/samples/test25_overflow.erml.erb +37 -0
  27. data/samples/test26_columns.erml.erb +42 -0
  28. data/samples/test28_landscape.erml.erb +17 -0
  29. data/samples/test29_pages_up.erml.erb +17 -0
  30. data/samples/test2_empty_page.erml +6 -0
  31. data/samples/test30_encodings.erml.haml +35 -0
  32. data/samples/test3_hello_world.erml +7 -0
  33. data/samples/test4_two_pages.erml +10 -0
  34. data/samples/test5_rounded_rect.erml +10 -0
  35. data/samples/test6_bullets.erml +16 -0
  36. data/samples/test7_flow_layout.erml +20 -0
  37. data/samples/test8_vbox_layout.erml +23 -0
  38. data/samples/test9_hbox_layout.erml +22 -0
  39. data/samples/testimg.jpg +0 -0
  40. data/test/test_erml_layout_managers.rb +106 -0
  41. data/test/test_erml_rules.rb +116 -0
  42. data/test/test_erml_styles.rb +415 -0
  43. data/test/test_erml_support.rb +140 -0
  44. data/test/test_erml_widget_factories.rb +46 -0
  45. data/test/test_erml_widgets.rb +1235 -0
  46. data/test/test_helpers.rb +18 -0
  47. metadata +102 -0
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brent Rowland on 2008-02-06.
4
+ # Copyright (c) 2008 Eidetic Software. All rights reserved.
5
+
6
+ module EideticRML
7
+ module Rules
8
+ class Rule
9
+ attr_reader :parent
10
+ attr_reader :selector
11
+ attr_reader :attrs
12
+
13
+ def initialize(rules, selector, attrs={}, parent=nil)
14
+ @rules, @parent = rules, parent
15
+ @selector = parent.nil? ? selector : parent.selector.dup << ' ' << selector
16
+ @attrs = attrs.dup
17
+ end
18
+
19
+ def root
20
+ parent.nil? ? self : parent.root
21
+ end
22
+
23
+ def update(attrs)
24
+ @attrs.update(attrs)
25
+ @selector_re = nil
26
+ end
27
+
28
+ def ===(path)
29
+ selector_re =~ path
30
+ end
31
+
32
+ GT_RE = '\\/'
33
+ SPACE_RE = GT_RE + '([^\\/]+\\/)*'
34
+ TAG_RE = '\\w+'
35
+ ID_RE = '(#\\w+)?'
36
+ MISC_CLASS_RE = '(\\.\\w+)*'
37
+ SPEC_CLASS_RE = '(\\.\\w+)*\\.%s(\\.\\w+)*'
38
+
39
+ def selector_re
40
+ @selector_re ||= Regexp.compile(Rule.selector_re_s(@selector))
41
+ end
42
+
43
+ def self.selector_re_s(selector)
44
+ selectors = selector.split(',').map { |sel| sel.strip }
45
+ result = selectors.map do |sel|
46
+ sel.split(' ').map { |group| group_re_s(group) }.join(SPACE_RE) << '$'
47
+ end
48
+ result.size > 1 ? '(' << result.join('|') << ')' : result.first
49
+ end
50
+
51
+ def self.group_re_s(group)
52
+ group.split('>').map { |item| item_re_s(item) }.join(GT_RE)
53
+ end
54
+
55
+ def self.item_re_s(item)
56
+ t, k = item.split('.', 2)
57
+ if t == ''
58
+ t = TAG_RE + ID_RE
59
+ elsif t[0] == ?#
60
+ t = TAG_RE + t
61
+ elsif !t.include?('#')
62
+ t << ID_RE
63
+ end
64
+ if k.nil? or k.empty?
65
+ k = MISC_CLASS_RE
66
+ else
67
+ k = SPEC_CLASS_RE % k
68
+ end
69
+ t + k
70
+ end
71
+
72
+ def self.parse(text)
73
+ text.scan(/\s*([^\{]+)\s*\{([^\}]+)\}/).map do |selector, rule|
74
+ selector.strip!
75
+ selector.gsub!(/\s{2,}/,' ') # collapse whitespace
76
+ selector.gsub!(/\s*>\s*/,'>') # strip whitespace from right angle brackets
77
+ attrs = rule.scan(/\s*([^:]+)\s*:\s*([^;]+)\s*;?/).inject({}) { |attrs, (k, v)| attrs[k.gsub('-', '_')] = v.strip; attrs }
78
+ [selector, attrs]
79
+ end
80
+ end
81
+ end
82
+
83
+ class RuleCollection < Array
84
+ def add(selector, attrs={})
85
+ rule = for_selector(selector)
86
+ if rule.nil?
87
+ rule = Rule.new(self, selector, attrs)
88
+ self << rule
89
+ else
90
+ rule.update(attrs)
91
+ end
92
+ rule
93
+ end
94
+
95
+ def for_selector(selector)
96
+ find { |rule| rule.selector == selector }
97
+ end
98
+
99
+ def matching(path)
100
+ select { |rule| rule === path }
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brent Rowland on 2008-01-06.
4
+ # Copyright (c) 2008, Eidetic Software. All rights reserved.
5
+
6
+ require 'erml_support'
7
+ require 'erml_layout_managers'
8
+
9
+ module EideticRML
10
+ module Styles
11
+ module HasColor
12
+ def color(value=nil)
13
+ return @color || 0 if value.nil?
14
+ @color = case value
15
+ when /^#([0-9A-Fa-f]{6})$/ # 6-digit hex color
16
+ $1.to_i(16)
17
+ when /^#([0-9A-Fa-f]{3})$/ # 3-digit hex color
18
+ $1.gsub(/(\w)/, '\\1\\1').to_i(16)
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+
25
+ module HasWidth
26
+ def width(value=nil, units=nil)
27
+ return @width || 0 if value.nil?
28
+ return from_points(@width, value) if value.is_a?(Symbol)
29
+ @width = parse_measurement_pts(value, units || self.units)
30
+ end
31
+
32
+ def units(value=nil)
33
+ return @units || :pt if value.nil?
34
+ @units = value.to_sym
35
+ end
36
+ end
37
+
38
+ class Style
39
+ include Support
40
+
41
+ def initialize(styles, attrs={})
42
+ @styles = styles
43
+ attrs.each { |key, value| self.send(key, value) }
44
+ end
45
+
46
+ def initialize_copy(other)
47
+ @id = nil
48
+ end
49
+
50
+ def from_points(value, units)
51
+ value.to_f / EideticPDF::UNIT_CONVERSION[units]
52
+ end
53
+
54
+ def id(value=nil)
55
+ return @id if value.nil?
56
+ @id = value.to_s
57
+ end
58
+
59
+ def self.register(name, klass)
60
+ (@@klasses ||= {})[name] = klass
61
+ end
62
+
63
+ def self.for_name(name)
64
+ @@klasses[name] unless @@klasses.nil?
65
+ end
66
+ end
67
+
68
+ class StyleCollection < Array
69
+ def add(name, attrs={})
70
+ raise ArgumentError, "id required for style" if attrs[:id].nil?
71
+ style = for_id(attrs[:id])
72
+ if style.nil?
73
+ if (style_class = Style.for_name(name.to_s)).nil?
74
+ raise ArgumentError, "Unknown style #{name}."
75
+ end
76
+ style = style_class.new(self, attrs)
77
+ self << style
78
+ else
79
+ attrs.each { |key, value| style.send(key, value) }
80
+ end
81
+ style
82
+ end
83
+
84
+ def for_id(id)
85
+ find { |style| style.id == id }
86
+ end
87
+ end
88
+
89
+ class PenStyle < Style
90
+ register('pen', self)
91
+
92
+ include HasColor
93
+ include HasWidth
94
+
95
+ def apply(writer)
96
+ writer.line_color(color)
97
+ writer.line_width([width, 0.001].max, units)
98
+ writer.line_dash_pattern(pattern)
99
+ writer.line_cap_style(cap)
100
+ end
101
+
102
+ def cap(value=nil)
103
+ return @cap || :butt_cap if value.nil?
104
+ @cap = value.to_sym if EideticPDF::LINE_CAP_STYLES.include?(value.to_sym)
105
+ end
106
+
107
+ def pattern(value=nil)
108
+ return @pattern || :solid if value.nil?
109
+ @pattern = EideticPDF::LINE_PATTERNS[value.to_sym] ? value.to_sym : value.to_s
110
+ end
111
+ end
112
+
113
+ class BrushStyle < Style
114
+ register('brush', self)
115
+
116
+ include HasColor
117
+
118
+ def apply(writer)
119
+ writer.fill_color(color)
120
+ end
121
+ end
122
+
123
+ class FontStyle < Style
124
+ register('font', self)
125
+
126
+ include HasColor
127
+
128
+ def apply(writer)
129
+ writer.line_height(line_height)
130
+ writer.font(name, size, :style => style, :weight => weight, :color => color, :encoding => encoding, :sub_type => sub_type)
131
+ end
132
+
133
+ def name(value=nil)
134
+ return @name || EideticPDF::PageWriter::DEFAULT_FONT[:name] if value.nil?
135
+ @name = value
136
+ end
137
+
138
+ def size(value=nil)
139
+ return @size || EideticPDF::PageWriter::DEFAULT_FONT[:size] if value.nil?
140
+ @size = value.to_f
141
+ end
142
+
143
+ def strikeout(value=nil)
144
+ return @strikeout if value.nil?
145
+ @strikeout = (value == true) || (value == 'true')
146
+ end
147
+
148
+ def style(value=nil)
149
+ return @style || '' if value.nil?
150
+ @style = value
151
+ end
152
+
153
+ def encoding(value=nil)
154
+ return @encoding || 'WinAnsiEncoding' if value.nil?
155
+ @encoding = value
156
+ end
157
+
158
+ def sub_type(value=nil)
159
+ return @sub_type || 'Type1' if value.nil?
160
+ @sub_type = value
161
+ end
162
+
163
+ def underline(value=nil)
164
+ return @underline if value.nil?
165
+ @underline = (value == true) || (value == 'true')
166
+ end
167
+
168
+ def weight(value=nil)
169
+ return @weight if value.nil?
170
+ @weight = value
171
+ end
172
+
173
+ def line_height(value=nil)
174
+ return @line_height || 1.7 if value.nil?
175
+ @line_height = value.to_f
176
+ end
177
+ end
178
+
179
+ class BulletStyle < Style
180
+ register('bullet', self)
181
+
182
+ include HasWidth
183
+
184
+ def initialize(styles, attrs={})
185
+ @width, @units = 36, :pt
186
+ super(styles, attrs)
187
+ end
188
+
189
+ def apply(writer)
190
+ unless writer.bullet(id)
191
+ writer.bullet(id, :width => width) do |w|
192
+ prev_font = w.font(@font.name, @font.size, :style => @font.style, :encoding => @font.encoding, :sub_type => @font.sub_type)
193
+ w.print(text)
194
+ w.font(prev_font)
195
+ end
196
+ end
197
+ end
198
+
199
+ def font(value=nil)
200
+ return @font if value.nil?
201
+ f = @styles.for_id(value)
202
+ raise ArgumentError, "Font Style #{value} not found." unless f.is_a?(Styles::FontStyle)
203
+ @font = f
204
+ end
205
+
206
+ def text(value=nil)
207
+ return @text if value.nil?
208
+ @text = value
209
+ end
210
+ end
211
+
212
+ class TextStyle < Style
213
+ include HasColor
214
+
215
+ def text_align(value=nil)
216
+ return @text_align || :left if value.nil?
217
+ @text_align = [:left, :center, :right, :justify].include?(value.to_sym) ? value.to_sym : @text_align
218
+ end
219
+
220
+ def apply(writer)
221
+ writer.font_color(color)
222
+ end
223
+ end
224
+
225
+ class LabelStyle < TextStyle
226
+ register('label', self)
227
+
228
+ def angle(value=nil)
229
+ return @angle || 0 if value.nil?
230
+ @angle = value.to_f
231
+ end
232
+ end
233
+
234
+ class ParagraphStyle < TextStyle
235
+ register('para', self)
236
+
237
+ def apply(writer)
238
+ super(writer)
239
+ @bullet.apply(writer) unless @bullet.nil?
240
+ end
241
+
242
+ def bullet(value=nil)
243
+ return @bullet if value.nil?
244
+ b = @styles.for_id(value)
245
+ raise ArgumentError, "Bullet Style #{value} not found." unless b.is_a?(Styles::BulletStyle)
246
+ @bullet = b
247
+ end
248
+ end
249
+
250
+ class PageStyle < Style
251
+ register('page', self)
252
+
253
+ def height(units=:pt)
254
+ from_points(EideticPDF::PageStyle::SIZES[size][orientation == :portrait ? 1 : 0], units)
255
+ end
256
+
257
+ def orientation(value=nil)
258
+ return @orientation || :portrait if value.nil?
259
+ @orientation = value.to_sym if [:portrait, :landscape].include?(value.to_sym)
260
+ end
261
+
262
+ def size(value=nil)
263
+ return @size || :letter if value.nil?
264
+ @size = value.to_sym if EideticPDF::PageStyle::SIZES[value.to_sym]
265
+ end
266
+
267
+ def width(units=:pt)
268
+ from_points(EideticPDF::PageStyle::SIZES[size][orientation == :portrait ? 0 : 1], units)
269
+ end
270
+ end
271
+
272
+ class LayoutStyle < Style
273
+ register('layout', self)
274
+
275
+ def padding(value=nil, units=:pt)
276
+ return @padding || 0 if value.nil?
277
+ return from_points(@padding || 0, value) if value.is_a?(Symbol)
278
+ @padding = parse_measurement_pts(value, units)
279
+ end
280
+
281
+ def hpadding(value=nil, units=:pt)
282
+ return @hpadding || padding if value.nil?
283
+ return from_points(@hpadding || padding, value) if value.is_a?(Symbol)
284
+ @hpadding = parse_measurement_pts(value, units)
285
+ end
286
+
287
+ def vpadding(value=nil, units=:pt)
288
+ return @vpadding || padding if value.nil?
289
+ return from_points(@vpadding || padding, value) if value.is_a?(Symbol)
290
+ @vpadding = parse_measurement_pts(value, units)
291
+ end
292
+
293
+ def units(value=nil)
294
+ return @units || :pt if value.nil?
295
+ @units = value.to_sym if EideticPDF::UNIT_CONVERSION[value.to_sym]
296
+ end
297
+
298
+ def manager(value=nil)
299
+ return @manager if value.nil?
300
+ @manager = LayoutManagers::LayoutManager.for_name(value).new(self)
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brent Rowland on 2008-01-07.
4
+ # Copyright (c) 2008 Eidetic Software. All rights reserved.
5
+
6
+ require 'rubygems'
7
+ if File.exist?(File.expand_path(File.join('..', 'eideticpdf')))
8
+ $:.unshift(File.expand_path(File.join('..', 'eideticpdf', 'lib')))
9
+ else
10
+ gem 'eideticpdf'
11
+ end
12
+ require 'epdfdw'
13
+
14
+ module EideticRML
15
+ module Support
16
+ def parse_measurement(value, units=:pt)
17
+ value, units = if value =~ /([+-]?\d+(\.\d+)?)([a-z]+)/
18
+ [$1.to_f, ($3 || :pt).to_sym]
19
+ else
20
+ [value.to_f, units.to_sym]
21
+ end
22
+ units = :pt unless EideticPDF::UNIT_CONVERSION[units]
23
+ [value, units]
24
+ end
25
+
26
+ def parse_measurement_pts(value, units=:pt)
27
+ v, u = parse_measurement(value, units)
28
+ v * EideticPDF::UNIT_CONVERSION[u]
29
+ end
30
+
31
+ def from_units(units, measurement)
32
+ measurement.to_f * EideticPDF::UNIT_CONVERSION[units]
33
+ end
34
+
35
+ def to_units(units, measurement)
36
+ measurement.to_f / EideticPDF::UNIT_CONVERSION[units]
37
+ end
38
+
39
+ module_function :parse_measurement, :parse_measurement_pts
40
+
41
+ class Bounds
42
+ attr_accessor :left, :top, :right, :bottom
43
+
44
+ def initialize(*args)
45
+ @left, @top, @right, @bottom = *args
46
+ end
47
+
48
+ def width
49
+ right - left
50
+ end
51
+
52
+ def height
53
+ bottom - top
54
+ end
55
+ end
56
+
57
+ class Grid
58
+ attr_reader :cols
59
+ attr_accessor :rows
60
+
61
+ def initialize(cols, rows)
62
+ @cols, @rows = cols, rows
63
+ @cells = Array.new(cols * rows)
64
+ end
65
+
66
+ def cols=(new_cols)
67
+ @new_cells = Array.new(new_cols * rows)
68
+ rows.times do |r|
69
+ @new_cells[r * new_cols, cols] = @cells[r * cols, cols]
70
+ end
71
+ @cells = @new_cells
72
+ @cols = new_cols
73
+ end
74
+
75
+ def rows=(value)
76
+ if value > rows
77
+ @cells.fill(nil, cols * rows, cols * (value - rows))
78
+ elsif value < rows
79
+ @cells.slice!(cols * value, cols * (rows - value))
80
+ end
81
+ @rows = value
82
+ end
83
+
84
+ def [](col, row)
85
+ @cells[row * cols + col]
86
+ end
87
+
88
+ def []=(col, row, value)
89
+ self.rows = row + 1 if row >= rows
90
+ self.cols = col + 1 if col >= cols
91
+ @cells[row * cols + col] = value
92
+ end
93
+
94
+ def col(index)
95
+ result = []
96
+ rows.times { |row| result << self[index, row] }
97
+ result
98
+ end
99
+
100
+ def row(index)
101
+ @cells.slice(index * cols, cols)
102
+ end
103
+ end
104
+ end
105
+ end