eideticrml 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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