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,38 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brent Rowland on 2008-01-16.
4
+ # Copyright (c) 2008 Eidetic Software. All rights reserved.
5
+
6
+ module EideticRML
7
+ module Widgets
8
+ class WidgetFactory
9
+ def initialize
10
+ @klasses = {}
11
+ end
12
+
13
+ def register_widget(tag, klass)
14
+ @klasses[tag] = klass
15
+ end
16
+
17
+ def has_widget?(tag)
18
+ !!@klasses[tag]
19
+ end
20
+
21
+ def make_widget(tag, parent, attrs={})
22
+ attrs['tag'] = tag unless attrs['tag']
23
+ attrs['class'] = '' if attrs['class'].nil? and attrs[:class].nil?
24
+ @klasses[tag].new(parent, attrs)
25
+ end
26
+
27
+ @@factories = {}
28
+
29
+ def self.for_namespace(namespace)
30
+ @@factories[namespace]
31
+ end
32
+
33
+ def self.register_factory(namespace, factory)
34
+ @@factories[namespace] = factory
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,1895 @@
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_styles'
8
+ require 'erml_rules'
9
+ require 'singleton'
10
+ require 'erml_widget_factories'
11
+
12
+ module EideticRML
13
+ module Widgets
14
+ class StdWidgetFactory < WidgetFactory
15
+ include Singleton
16
+
17
+ WidgetFactory.register_factory('std', self.instance)
18
+ end
19
+
20
+ class Widget
21
+ include Support
22
+
23
+ attr_reader :width_pct, :height_pct, :max_width_pct, :max_height_pct
24
+ attr_accessor :parent, :disabled
25
+ attr_writer :printed, :visible
26
+
27
+ def initialize(parent, attrs={})
28
+ @parent = parent
29
+ @display = parent.display
30
+ parent.children << self if parent.respond_to?(:children)
31
+ attributes(attrs)
32
+ @visible = true
33
+ end
34
+
35
+ def clone
36
+ @clone ||= super
37
+ end
38
+
39
+ def initialize_copy(other)
40
+ @is_clone = true
41
+ end
42
+
43
+ def attributes(attrs)
44
+ attrs = attrs.inject({}) { |m, kv| m[kv.first.to_s.sub(/^class$/,'klass')] = kv.last; m } # stringify keys
45
+ pre_keys, post_keys = attributes_first & attrs.keys, attributes_last & attrs.keys # keys are sorted same as attributes_first
46
+ keys = attrs.keys - pre_keys - post_keys
47
+ pre_keys.each { |key| attribute(key, attrs[key]) }
48
+ keys.each { |key| attribute(key, attrs[key]) }
49
+ post_keys.each { |key| attribute(key, attrs[key]) }
50
+ end
51
+
52
+ def align(value=nil)
53
+ return @align if value.nil?
54
+ @align = value.to_sym if [:top, :right, :bottom, :left].include?(value.to_sym)
55
+ end
56
+
57
+ def position(value=nil)
58
+ return @position || :static if value.nil?
59
+ @position = value.to_sym if [:static, :relative, :absolute].include?(value.to_sym)
60
+ end
61
+
62
+ def tag(value=nil)
63
+ return @tag if value.nil?
64
+ @tag = $1.freeze if value.to_s =~ /^(\w+)$/
65
+ @path = nil
66
+ end
67
+
68
+ def id(value=nil)
69
+ return @id if value.nil?
70
+ if value.to_s =~ /^(\w+)$/
71
+ @id = $1.freeze
72
+ root.widgets[@id] = self
73
+ end
74
+ @path = nil
75
+ end
76
+
77
+ def klass(value=nil)
78
+ return @klass if value.nil?
79
+ @klass = $1.freeze if value.to_s =~ /^\s*(\w+(\s+\w+)*)\s*$/
80
+ @path = nil
81
+ return if parent.nil?
82
+ attributes(root.rules.matching(path).inject({}) { |attrs, rule| attrs.update(rule.attrs) })
83
+ end
84
+
85
+ def selector_tag
86
+ value = (tag || '').dup
87
+ value << '#' << id unless id.nil?
88
+ value << '.' << klass.split(/\s/).join('.') unless klass.nil?
89
+ value
90
+ end
91
+
92
+ def path
93
+ @path ||= (parent.nil? ? selector_tag : parent.path.dup << '/' << selector_tag).freeze
94
+ end
95
+
96
+ def left(value=nil, units=nil)
97
+ return rel_x(shifted_x(@left || ((@right.nil? or width.nil?) ? nil : @right - width))) if value.nil?
98
+ return to_units(value, left) if value.is_a?(Symbol)
99
+ @position = :relative if position == :static and value.respond_to?(:to_str)
100
+ @left = parse_measurement_pts(value, units || self.units)
101
+ @left = parent.width + @left if @left < 0
102
+ @width = @right - @left unless @right.nil?
103
+ end
104
+
105
+ def top(value=nil, units=nil)
106
+ return rel_y(shifted_y(@top || ((@bottom.nil? or height.nil?) ? nil : @bottom - height))) if value.nil?
107
+ return to_units(value, top) if value.is_a?(Symbol)
108
+ @position = :relative if position == :static and value.respond_to?(:to_str)
109
+ @top = parse_measurement_pts(value, units || self.units)
110
+ @top = parent.height + @top if @top < 0
111
+ @height = @bottom - @top unless @bottom.nil?
112
+ end
113
+
114
+ def right(value=nil, units=nil)
115
+ return rel_y(shifted_x(@right || ((@left.nil? or width.nil?) ? nil : @left + width))) if value.nil?
116
+ return to_units(value, right) if value.is_a?(Symbol)
117
+ @position = :relative if position == :static and value.respond_to?(:to_str)
118
+ @right = parse_measurement_pts(value, units || self.units)
119
+ @right = parent.width + @right if @right <= 0
120
+ @width = @right - @left unless @left.nil?
121
+ end
122
+
123
+ def bottom(value=nil, units=nil)
124
+ return rel_y(shifted_y(@bottom || ((@top.nil? or height.nil?) ? nil : @top + height))) if value.nil?
125
+ return to_units(value, bottom) if value.is_a?(Symbol)
126
+ @position = :relative if position == :static and value.respond_to?(:to_str)
127
+ @bottom = parse_measurement_pts(value, units || self.units)
128
+ @bottom = parent.height + @bottom if @bottom <= 0
129
+ @height = @bottom - @top unless @top.nil?
130
+ end
131
+
132
+ def shift(value=nil, units=nil)
133
+ return [shift_x(units || :pt), shift_y(units || :pt)] if value.nil?
134
+ if value.respond_to?(:to_str)
135
+ x, y = value.to_s.split(',', 2)
136
+ else
137
+ x, y = Array(value)
138
+ end
139
+ @shift_x, @shift_y = parse_measurement_pts(x, units || self.units), parse_measurement_pts(y, units || self.units)
140
+ end
141
+
142
+ def has_height?
143
+ @height and !@height_pct
144
+ end
145
+
146
+ def has_width?
147
+ @width and !@width_pct
148
+ end
149
+
150
+ def preferred_width(writer, units=:pt)
151
+ @width ? to_units(units, @width) : nil
152
+ end
153
+
154
+ def preferred_height(writer, units=:pt)
155
+ @height ? to_units(units, @height) : nil
156
+ end
157
+
158
+ def width(value=nil, units=nil)
159
+ return @width_pct ? @width_pct * parent.content_width : @width if value.nil?
160
+ return to_units(value, width) if value.is_a?(Symbol)
161
+ if value =~ /(\d+(\.\d+)?)%/
162
+ @width_pct = $1.to_f.quo(100)
163
+ # @width = @width_pct * parent.content_width
164
+ @width = nil
165
+ elsif value.to_s =~ /^[+-]/
166
+ @width = parent.content_width + parse_measurement_pts(value, units || self.units)
167
+ @width_pct = nil
168
+ else
169
+ @width = parse_measurement_pts(value, units || self.units)
170
+ @width_pct = nil
171
+ end
172
+ @right = nil unless @left.nil?
173
+ width_set
174
+ end
175
+
176
+ def height(value=nil, units=nil)
177
+ return @height_pct ? @height_pct * parent.content_height : @height if value.nil?
178
+ return to_units(value, height) if value.is_a?(Symbol)
179
+ if value =~ /(\d+(\.\d+)?)%/
180
+ @height_pct = $1.to_f.quo(100)
181
+ @height = @height_pct * parent.content_height
182
+ elsif value.to_s =~ /^[+-]/
183
+ @height = parent.content_height + parse_measurement_pts(value, units || self.units)
184
+ @height_pct = nil
185
+ else
186
+ @height = parse_measurement_pts(value, units || self.units)
187
+ @height_pct = nil
188
+ end
189
+ @bottom = nil unless @top.nil?
190
+ height_set
191
+ end
192
+
193
+ def max_width(value=nil, units=nil)
194
+ return @max_width_pct ? @max_width_pct * parent.content_width : @max_width if value.nil?
195
+ return to_units(value, max_width) if value.is_a?(Symbol)
196
+ if value =~ /(\d+(\.\d+)?)%/
197
+ @max_width_pct = $1.to_f.quo(100)
198
+ @max_width = @max_width_pct * parent.content_width
199
+ elsif value.to_s =~ /^[+-]/
200
+ @max_width = parent.content_width + parse_measurement_pts(value, units || self.units)
201
+ @max_width_pct = nil
202
+ else
203
+ @max_width = parse_measurement_pts(value, units || self.units)
204
+ @max_width_pct = nil
205
+ end
206
+ end
207
+
208
+ def max_height(value=nil, units=nil)
209
+ return @max_height_pct ? @max_height_pct * parent.content_height : @max_height if value.nil?
210
+ return to_units(value, max_height) if value.is_a?(Symbol)
211
+ if value =~ /(\d+(\.\d+)?)%/
212
+ @max_height_pct = $1.to_f.quo(100)
213
+ @max_height = @max_height_pct * parent.content_height
214
+ elsif value.to_s =~ /^[+-]/
215
+ @max_height = parent.content_height + parse_measurement_pts(value, units || self.units)
216
+ @max_height_pct = nil
217
+ else
218
+ @max_height = parse_measurement_pts(value, units || self.units)
219
+ @hmax_height_pct = nil
220
+ end
221
+ end
222
+
223
+ def max_content_height
224
+ max_height_avail - non_content_height
225
+ end
226
+
227
+ def max_height_avail
228
+ height || parent.max_content_height - ((top || parent.content_top) - parent.content_top)
229
+ end
230
+
231
+ def content_top(units=:pt)
232
+ to_units(units, top + margin_top + padding_top)
233
+ end
234
+
235
+ def content_right(units=:pt)
236
+ to_units(units, right - margin_right - padding_right)
237
+ end
238
+
239
+ def content_bottom(units=:pt)
240
+ to_units(units, bottom - margin_bottom - padding_bottom)
241
+ end
242
+
243
+ def content_left(units=:pt)
244
+ to_units(units, left + margin_left + padding_left)
245
+ end
246
+
247
+ def content_height(units=:pt)
248
+ to_units(units, (height || 0) - non_content_height)
249
+ end
250
+
251
+ def content_width(units=:pt)
252
+ to_units(units, (width || 0) - non_content_width)
253
+ end
254
+
255
+ def non_content_height
256
+ margin_top + padding_top + padding_bottom + margin_bottom
257
+ end
258
+
259
+ def non_content_width
260
+ margin_left + padding_left + padding_right + margin_right
261
+ end
262
+
263
+ def before_layout
264
+ @orig_width, @orig_height = @width, @height
265
+ end
266
+
267
+ def layout_widget(writer)
268
+ # override this method
269
+ end
270
+
271
+ def after_layout
272
+ # override this method
273
+ end
274
+
275
+ def units(value=nil)
276
+ # inherited
277
+ return @units || parent.units if value.nil?
278
+ @units = value.to_sym if EideticPDF::UNIT_CONVERSION[value.to_sym]
279
+ end
280
+
281
+ def border(value=nil)
282
+ return @border if value.nil?
283
+ @border = pen_style_for(value)
284
+ @border_top = @border_right = @border_bottom = @border_left = nil
285
+ end
286
+
287
+ def border_top(value=nil)
288
+ return @border_top || @border if value.nil?
289
+ @border_top = pen_style_for(value)
290
+ end
291
+
292
+ def border_right(value=nil)
293
+ return @border_right || @border if value.nil?
294
+ @border_right = pen_style_for(value)
295
+ end
296
+
297
+ def border_bottom(value=nil)
298
+ return @border_bottom || @border if value.nil?
299
+ @border_bottom = pen_style_for(value)
300
+ end
301
+
302
+ def border_left(value=nil)
303
+ return @border_left || @border if value.nil?
304
+ @border_left = pen_style_for(value)
305
+ end
306
+
307
+ def fill(value=nil)
308
+ return @fill if value.nil?
309
+ @fill = brush_style_for(value)
310
+ end
311
+
312
+ def margin_top(value=nil)
313
+ return @margin_top || 0 if value.nil?
314
+ return to_units(value, margin_top) if value.is_a?(Symbol)
315
+ @margin_top = parse_measurement_pts(value, units)
316
+ end
317
+
318
+ def margin_right(value=nil)
319
+ return @margin_right || 0 if value.nil?
320
+ return to_units(value, margin_right) if value.is_a?(Symbol)
321
+ @margin_right = parse_measurement_pts(value, units)
322
+ end
323
+
324
+ def margin_bottom(value=nil)
325
+ return @margin_bottom || 0 if value.nil?
326
+ return to_units(value, margin_bottom) if value.is_a?(Symbol)
327
+ @margin_bottom = parse_measurement_pts(value, units)
328
+ end
329
+
330
+ def margin_left(value=nil)
331
+ return @margin_left || 0 if value.nil?
332
+ return to_units(value, margin_left) if value.is_a?(Symbol)
333
+ @margin_left = parse_measurement_pts(value, units)
334
+ end
335
+
336
+ def margin(value=nil)
337
+ return [margin_top, margin_right, margin_bottom, margin_left] if value.nil?
338
+ return [margin_top(value), margin_right(value), margin_bottom(value), margin_left(value)] if value.is_a?(Symbol)
339
+ if value.respond_to?(:to_str)
340
+ value = value.split(',').map do |n|
341
+ parse_measurement_pts(n, units)
342
+ end
343
+ else
344
+ value = Array(value).map { |m| from_units(units, m) }
345
+ end
346
+ m = case value.size
347
+ when 4 then value
348
+ when 2 then value * 2
349
+ when 1 then value * 4
350
+ else nil
351
+ end
352
+ @margin_top, @margin_right, @margin_bottom, @margin_left = m unless m.nil?
353
+ end
354
+
355
+ def default_padding_top
356
+ 0
357
+ end
358
+
359
+ def default_padding_right
360
+ 0
361
+ end
362
+
363
+ def default_padding_bottom
364
+ 0
365
+ end
366
+
367
+ def default_padding_left
368
+ 0
369
+ end
370
+
371
+ def padding_top(value=nil)
372
+ return @padding_top || default_padding_top if value.nil?
373
+ return to_units(value, padding_top) if value.is_a?(Symbol)
374
+ @padding_top = parse_measurement_pts(value, units)
375
+ end
376
+
377
+ def padding_right(value=nil)
378
+ return @padding_right || default_padding_right if value.nil?
379
+ return to_units(value, padding_right) if value.is_a?(Symbol)
380
+ @padding_right = parse_measurement_pts(value, units)
381
+ end
382
+
383
+ def padding_bottom(value=nil)
384
+ return @padding_bottom || default_padding_bottom if value.nil?
385
+ return to_units(value, padding_bottom) if value.is_a?(Symbol)
386
+ @padding_bottom = parse_measurement_pts(value, units)
387
+ end
388
+
389
+ def padding_left(value=nil)
390
+ return @padding_left || default_padding_left if value.nil?
391
+ return to_units(value, padding_left) if value.is_a?(Symbol)
392
+ @padding_left = parse_measurement_pts(value, units)
393
+ end
394
+
395
+ def padding(value=nil)
396
+ return [padding_top, padding_right, padding_bottom, padding_left] if value.nil?
397
+ return [padding_top(value), padding_right(value), padding_bottom(value), padding_left(value)] if value.is_a?(Symbol)
398
+ if value.respond_to?(:to_str)
399
+ value = value.split(',').map do |n|
400
+ parse_measurement_pts(n, units)
401
+ end
402
+ else
403
+ value = Array(value).map { |p| from_units(units, p) }
404
+ end
405
+ p = case value.size
406
+ when 4 then value
407
+ when 2 then value * 2
408
+ when 1 then value * 4
409
+ else nil
410
+ end
411
+ @padding_top, @padding_right, @padding_bottom, @padding_left = p unless p.nil?
412
+ end
413
+
414
+ def font(value=nil)
415
+ # inherited
416
+ return @font || parent.font if value.nil?
417
+ return @font = font.clone if value == :copy
418
+ @font = font_style_for(value)
419
+ end
420
+
421
+ def font_color(value=nil)
422
+ return font.color if value.nil?
423
+ font(:copy).color(value)
424
+ end
425
+
426
+ def font_size(value=nil)
427
+ return font.size if value.nil?
428
+ font(:copy).size(value)
429
+ end
430
+
431
+ def font_style(value=nil)
432
+ return font.style if value.nil?
433
+ @font = font.clone
434
+ @font.style(value)
435
+ end
436
+
437
+ def font_weight(value=nil)
438
+ return font.weight if value.nil?
439
+ @font = font.clone
440
+ @font.weight(value)
441
+ end
442
+
443
+ def print(writer)
444
+ # $stderr.puts "<<<try printing #{object_id} (#{tag}) visible: #{visible}"
445
+ return if visible == false
446
+ return if disabled
447
+ # $stderr.puts "print #{tag} { left = #{left}, top = #{top}}"
448
+ before_print(writer)
449
+ if @rotate.nil?
450
+ paint_background(writer)
451
+ draw_content(writer)
452
+ draw_border(writer)
453
+ else
454
+ writer.rotate(rotate, origin_x, origin_y) do
455
+ paint_background(writer)
456
+ draw_content(writer)
457
+ draw_border(writer)
458
+ end
459
+ end
460
+ @printed = true
461
+ # $stderr.puts ">>>printed #{object_id} (#{tag})"
462
+ @width = @orig_width if @orig_width
463
+ @height = @orig_height if @orig_height
464
+ rescue Exception => e
465
+ raise RuntimeError, e.message + "\nError printing #{path}.", e.backtrace
466
+ end
467
+
468
+ def printed
469
+ @printed or @disabled
470
+ end
471
+
472
+ def root
473
+ parent.nil? ? self : parent.root
474
+ end
475
+
476
+ def root_page
477
+ parent.root_page
478
+ end
479
+
480
+ def display(value=nil)
481
+ return @display || :once if value.nil?
482
+ @display = value.to_sym if [:once, :always, :first, :succeeding, :even, :odd].include?(value.to_sym)
483
+ end
484
+
485
+ def display_for_page(document_page_no, section_page_no)
486
+ # puts "display_for_page: #{display.inspect}"
487
+ case display
488
+ when :always then true
489
+ when :first then section_page_no == 1
490
+ when :succeeding then section_page_no > 1
491
+ when :even then document_page_no.even?
492
+ when :odd then document_page_no.odd?
493
+ else false
494
+ end
495
+ end
496
+
497
+ def colspan(value=nil)
498
+ return @colspan || 1 if value.nil?
499
+ @colspan = value.to_i if value.to_i >= 1
500
+ end
501
+
502
+ def rowspan(value=nil)
503
+ return @rowspan || 1 if value.nil?
504
+ @rowspan = value.to_i if value.to_i >= 1
505
+ end
506
+
507
+ def origin_x(value=nil)
508
+ if value.nil?
509
+ case @origin_x
510
+ when 'left' then left
511
+ when 'center' then (left + right).quo(2)
512
+ when 'right' then right
513
+ else left
514
+ end
515
+ else
516
+ @origin_x = value.strip
517
+ end
518
+ end
519
+
520
+ def origin_y(value=nil)
521
+ if value.nil?
522
+ case @origin_y
523
+ when 'top' then top
524
+ when 'middle' then (top + bottom).quo(2)
525
+ when 'bottom' then bottom
526
+ else top
527
+ end
528
+ else
529
+ @origin_y = value.strip
530
+ end
531
+ end
532
+
533
+ def rotate(value=nil)
534
+ return @rotate if value.nil?
535
+ @rotate = value.to_f
536
+ end
537
+
538
+ def z_index(value=nil)
539
+ return @z_index || 0 if value.nil?
540
+ @z_index = value.to_i
541
+ end
542
+
543
+ def visible(bounds=nil)
544
+ return @visible if bounds.nil?
545
+ return 0 if left.nil? or top.nil? or right.nil? or bottom.nil?
546
+ (left >= bounds.left and top >= bounds.top and right <= bounds.right and bottom <= bounds.bottom) ? 1 : 0
547
+ end
548
+
549
+ # def postpone
550
+ # @visible = false
551
+ # @postponed ||= 0
552
+ # @postponed += 1
553
+ # @disabled = true if @postponed > 1
554
+ # end
555
+
556
+ def leaf?
557
+ true
558
+ end
559
+
560
+ def leaves
561
+ leaf? ? 1 : 0
562
+ end
563
+
564
+ protected
565
+ def attribute(id, value)
566
+ keys = id.to_s.split('.', 2)
567
+ if keys.size == 1
568
+ self.send(keys[0], value)
569
+ else
570
+ self.send(keys[0], :copy).send(keys[1], value)
571
+ end
572
+ rescue NoMethodError => e
573
+ raise ArgumentError, "Unknown attribute #{id}", e.backtrace
574
+ end
575
+
576
+ def attributes_first
577
+ @@attributes_first ||= %w(id tag class units position
578
+ left top width height right bottom
579
+ margin margin_top margin_right margin_bottom margin_left
580
+ padding padding_top padding_right padding_bottom padding_left
581
+ font border fill).freeze
582
+ end
583
+
584
+ def attributes_last
585
+ @@attributes_last ||= %w(text).freeze
586
+ end
587
+
588
+ def before_print(writer)
589
+ # override this method
590
+ end
591
+
592
+ def brush_style_for(id)
593
+ bs = root.styles.for_id(id) || root.styles.for_id("brush_#{id}")
594
+ bs = root.styles.add('brush', :id => "brush_#{id}", :color => id) if bs.nil? and EideticPDF::PdfK::NAMED_COLORS[id]
595
+ raise ArgumentError, "Brush Style #{id} not found." unless bs.is_a?(Styles::BrushStyle)
596
+ bs
597
+ end
598
+
599
+ def draw_border(writer, pen=nil)
600
+ if pen
601
+ pen.apply(writer)
602
+ writer.rectangle(left + margin_left, top + margin_top,
603
+ width - margin_left - margin_right, height - margin_top - margin_bottom)
604
+ elsif [@border_top, @border_right, @border_bottom, @border_left].all? { |b| b.nil? }
605
+ unless @border.nil?
606
+ @border.apply(writer)
607
+ writer.rectangle(left + margin_left, top + margin_top,
608
+ width - margin_left - margin_right, height - margin_top - margin_bottom)
609
+ end
610
+ else
611
+ unless @border_top.nil?
612
+ @border_top.apply(writer)
613
+ writer.move_to(left + margin_left, top + margin_top) # top left
614
+ writer.line_to(right - margin_right, top + margin_top) # top right
615
+ end
616
+ unless @border_right.nil?
617
+ @border_right.apply(writer)
618
+ writer.move_to(right - margin_right, top + margin_top) # top right
619
+ writer.line_to(right - margin_right, bottom - margin_bottom) # bottom right
620
+ end
621
+ unless @border_bottom.nil?
622
+ @border_bottom.apply(writer)
623
+ writer.move_to(right - margin_right, bottom - margin_bottom) # bottom right
624
+ writer.line_to(left + margin_left, bottom - margin_bottom) # bottom left
625
+ end
626
+ unless @border_left.nil?
627
+ @border_left.apply(writer)
628
+ writer.move_to(left + margin_left, bottom - margin_bottom) # bottom left
629
+ writer.line_to(left + margin_left, top + margin_top) # top left
630
+ end
631
+ end
632
+ end
633
+
634
+ def draw_content(writer)
635
+ # override this method
636
+ end
637
+
638
+ def font_style_for(id)
639
+ fs = root.styles.for_id(id)
640
+ raise ArgumentError, "Font Style #{id} not found." unless fs.is_a?(Styles::FontStyle)
641
+ fs
642
+ end
643
+
644
+ def height_set
645
+ # override this callback
646
+ end
647
+
648
+ def paint_background(writer)
649
+ unless @fill.nil?
650
+ @fill.apply(writer)
651
+ writer.rectangle(left + margin_left, top + margin_top,
652
+ width - margin_left - margin_right, height - margin_top - margin_bottom,
653
+ :fill => true, :border => false)
654
+ end
655
+ end
656
+
657
+ def pen_style_for(id)
658
+ ps = root.styles.for_id(id) || root.styles.for_id("pen_#{id}")
659
+ ps = root.styles.add('pen', :id => "pen_#{id}", :pattern => 'solid', :color => id) if ps.nil? and EideticPDF::PdfK::NAMED_COLORS[id]
660
+ raise ArgumentError, "Pen Style #{id} not found." unless ps.is_a?(Styles::PenStyle)
661
+ ps
662
+ end
663
+
664
+ def shift_x(units=:pt)
665
+ to_units(units, @shift_x || 0)
666
+ end
667
+
668
+ def shift_y(units=:pt)
669
+ to_units(units, @shift_y || 0)
670
+ end
671
+
672
+ def shifted_x(value)
673
+ value.nil? ? nil : shift_x + value
674
+ end
675
+
676
+ def shifted_y(value)
677
+ value.nil? ? nil : shift_y + value
678
+ end
679
+
680
+ def rel_x(value)
681
+ value.nil? ? nil : (position == :relative ? parent.left + value : value)
682
+ end
683
+
684
+ def rel_y(value)
685
+ value.nil? ? nil : (position == :relative ? parent.top + value : value)
686
+ end
687
+
688
+ def widget_for(id)
689
+ root.widgets[id]
690
+ end
691
+
692
+ def width_set
693
+ # override this callback
694
+ end
695
+ end
696
+
697
+ module HasLocation
698
+ def x(value=nil)
699
+ return @x if value.nil?
700
+ return to_units(value, @x) if value.is_a?(Symbol)
701
+ @position = :relative if position == :static and value.respond_to?(:to_str)
702
+ @x = parse_measurement_pts(value, units)
703
+ @x = parent.width - parent.margin_right + @x if @x < 0
704
+ end
705
+
706
+ def y(value=nil)
707
+ return @y if value.nil?
708
+ return to_units(value, @y) if value.is_a?(Symbol)
709
+ @position = :relative if position == :static and value.respond_to?(:to_str)
710
+ @y = parse_measurement_pts(value, units)
711
+ @y = parent.height - parent.margin_bottom + @y if @y < 0
712
+ end
713
+ end
714
+
715
+ module Shape
716
+ include HasLocation
717
+
718
+ protected
719
+ def draw_border(writer)
720
+ # suppress default behavior
721
+ debug_pen_style = "debug_#{tag}_border"
722
+ if root.styles.for_id(debug_pen_style)
723
+ super(writer, pen_style_for(debug_pen_style))
724
+ end
725
+ end
726
+
727
+ def paint_background(writer)
728
+ # suppress default behavior
729
+ end
730
+ end
731
+
732
+ class Arc < Widget
733
+ StdWidgetFactory.instance.register_widget('arc', self)
734
+
735
+ include Shape
736
+
737
+ def r(value=nil)
738
+ # TODO
739
+ end
740
+
741
+ def start_angle(value=nil)
742
+ # TODO
743
+ end
744
+
745
+ def end_angle(value=nil)
746
+ # TODO
747
+ end
748
+
749
+ protected
750
+ def draw_content(writer)
751
+ # TODO
752
+ end
753
+ end
754
+
755
+ class Arch < Arc
756
+ StdWidgetFactory.instance.register_widget('arc', self)
757
+
758
+ undef_method :r
759
+
760
+ def r1(value=nil)
761
+ # TODO
762
+ end
763
+
764
+ def r2(value=nil)
765
+ # TODO
766
+ end
767
+
768
+ protected
769
+ def draw_content(writer)
770
+ # TODO
771
+ end
772
+ end
773
+
774
+ class Image < Widget
775
+ StdWidgetFactory.instance.register_widget('image', self)
776
+
777
+ def has_height?
778
+ true
779
+ end
780
+
781
+ def has_width?
782
+ true
783
+ end
784
+
785
+ def preferred_width(writer, units=:pt)
786
+ if @width.nil? and @height
787
+ w = @height * image(writer).width.quo(image(writer).height)
788
+ else
789
+ w = @width || image(writer).width
790
+ end
791
+ to_units(units, w)
792
+ end
793
+
794
+ def preferred_height(writer, units=:pt)
795
+ if @height.nil? and @width
796
+ # $stderr.puts "path A, width = #{@width}"
797
+ h = @width * image(writer).height.quo(image(writer).width)
798
+ else
799
+ # $stderr.puts "path B"
800
+ h = @height || image(writer).height
801
+ end
802
+ # $stderr.puts "h = #{h}"
803
+ to_units(units, h)
804
+ end
805
+
806
+ def url(value=nil)
807
+ return @url if value.nil?
808
+ @url = value.to_s
809
+ end
810
+
811
+ protected
812
+ def draw_content(writer)
813
+ # $stderr.puts "print image: #{url}"
814
+ writer.print_image_file(load_image(writer), left, top, width, height)
815
+ end
816
+
817
+ def image(writer)
818
+ load_image(writer) if @image.nil?
819
+ @image
820
+ end
821
+
822
+ def load_image(writer)
823
+ raise Exception, "Image url must be specified." if url.nil?
824
+ @image, @name = writer.load_image(url, stream) if @image.nil?
825
+ url
826
+ end
827
+
828
+ def stream
829
+ @stream ||= open(url, EideticPDF::ImageReadMode) { |io| io.read }
830
+ end
831
+ end
832
+
833
+ class Line < Widget
834
+ StdWidgetFactory.instance.register_widget('line', self)
835
+
836
+ include Shape
837
+
838
+ def angle(value=nil)
839
+ return @angle || 0 if value.nil?
840
+ @angle = value.to_f
841
+ end
842
+
843
+ def length(value=nil)
844
+ # return @length || Math::sqrt(content_width ** 2 + content_height ** 2) if value.nil?
845
+ return @length || calc_length if value.nil?
846
+ return to_units(value, length) if value.is_a?(Symbol)
847
+ @length = parse_measurement_pts(value, units)
848
+ end
849
+
850
+ def preferred_width(writer, units=:pt)
851
+ w = @width || preferred_content_width + non_content_width
852
+ to_units(units, w)
853
+ end
854
+
855
+ def preferred_height(writer, units=:pt)
856
+ h = @height || preferred_content_height + non_content_height
857
+ to_units(units, h)
858
+ end
859
+
860
+ def style(value=nil)
861
+ return @style || pen_style_for('solid') if value.nil?
862
+ @style = pen_style_for(value)
863
+ end
864
+
865
+ protected
866
+ def calc_length
867
+ return 0 if width.nil? or height.nil?
868
+ l = Math::sqrt(content_width ** 2 + content_height ** 2)
869
+ w = Math::cos(angle.degrees) * l
870
+ h = Math::sin(angle.degrees) * l
871
+ if w > content_width
872
+ l *= content_width.quo(w)
873
+ elsif h > content_height
874
+ l *= content_height.quo(h)
875
+ end
876
+ l
877
+ end
878
+
879
+ def draw_content(writer)
880
+ # puts "cw: #{content_width}, ch: #{content_height}"
881
+ style.apply(writer)
882
+ if position == :absolute
883
+ raise Exception, "x and y must be set." unless @x and @y
884
+ writer.line(@x, @y, angle, length)
885
+ elsif position == :relative
886
+ raise Exception, "x and y must be set." unless @x and @y
887
+ writer.line(parent.content_left + @x, parent.content_top + @y, angle, length)
888
+ else
889
+ @x, @y = origin_for_quadrant(quadrant(angle))
890
+ writer.line(@x, @y, angle, length)
891
+ end
892
+ end
893
+
894
+ def origin_for_quadrant(quadrant)
895
+ x_offset = (content_width - preferred_content_width).quo(2)
896
+ y_offset = (content_height - preferred_content_height).quo(2)
897
+ # puts "x_offset: #{x_offset}, y_offset: #{y_offset}"
898
+ case quadrant
899
+ when 1 then [content_left + x_offset, content_bottom - y_offset]
900
+ when 2 then [content_right - x_offset, content_bottom - y_offset]
901
+ when 3 then [content_right - x_offset, content_top + y_offset]
902
+ else [content_left + x_offset, content_top + y_offset]
903
+ end
904
+ end
905
+
906
+ def preferred_content_width
907
+ Math::cos(angle.degrees) * length
908
+ end
909
+
910
+ def preferred_content_height
911
+ Math::sin(angle.degrees) * length
912
+ end
913
+
914
+ def quadrant(angle)
915
+ a = angle % 360
916
+ if a <= 90 then 1
917
+ elsif a <= 180 then 2
918
+ elsif a <= 270 then 3
919
+ else 4
920
+ end
921
+ end
922
+ end
923
+
924
+ class Pie < Arc
925
+ StdWidgetFactory.instance.register_widget('pie', self)
926
+
927
+ protected
928
+ def draw_content(writer)
929
+ # TODO
930
+ end
931
+ end
932
+
933
+ class Star < Widget
934
+ StdWidgetFactory.instance.register_widget('star', self)
935
+
936
+ include Shape
937
+
938
+ def reverse(value=nil)
939
+ # TODO
940
+ end
941
+
942
+ def rotation(value=nil)
943
+ # TODO
944
+ end
945
+
946
+ def points(value=nil)
947
+ # TODO
948
+ end
949
+
950
+ def r(value=nil)
951
+ # TODO
952
+ end
953
+
954
+ protected
955
+ def draw_content(writer)
956
+ # TODO
957
+ end
958
+ end
959
+
960
+ module Text
961
+ def layout_widget(writer)
962
+ super(writer)
963
+ font.apply(writer)
964
+ end
965
+
966
+ def has_height?
967
+ true
968
+ end
969
+
970
+ def has_width?
971
+ !@width_pct
972
+ end
973
+
974
+ def strikeout(value=nil)
975
+ return font.strikeout if value.nil?
976
+ font(:copy).strikeout(value)
977
+ end
978
+
979
+ def underline(value=nil)
980
+ return font.underline if value.nil?
981
+ font(:copy).underline(value)
982
+ end
983
+
984
+ def line_height(value=nil)
985
+ return font.line_height if value.nil?
986
+ font(:copy).line_height(value)
987
+ end
988
+
989
+ protected
990
+ def draw_content(writer)
991
+ font.apply(writer)
992
+ end
993
+ end
994
+
995
+ class Span < Widget
996
+ StdWidgetFactory.instance.register_widget('span', self)
997
+
998
+ include Text
999
+
1000
+ def initialize(parent, attrs={})
1001
+ raise ArgumentError, "Span must be child of Paragraph, Label or another Span." unless valid_parent?(parent)
1002
+ super(parent, attrs)
1003
+ end
1004
+
1005
+ def printed
1006
+ true
1007
+ end
1008
+
1009
+ def text(value=nil, font=nil)
1010
+ return super if value.nil?
1011
+ parent.text(value, font || self.font)
1012
+ end
1013
+
1014
+ private
1015
+ def valid_parent?(parent)
1016
+ parent.is_a?(Span) or parent.is_a?(Paragraph) or parent.is_a?(Label)
1017
+ end
1018
+ end
1019
+
1020
+ class PageNo < Span
1021
+ StdWidgetFactory.instance.register_widget('pageno', self)
1022
+
1023
+ def initialize(parent, attrs={})
1024
+ super(parent, attrs)
1025
+ parent.text(self)
1026
+ end
1027
+
1028
+ def before_layout
1029
+ super
1030
+ root.document_page_no = @new_page_no if @new_page_no
1031
+ end
1032
+
1033
+ def text(value=nil)
1034
+ return root.document_page_no.to_s if value.nil?
1035
+ @new_page_no = value.to_i
1036
+ end
1037
+
1038
+ def to_s
1039
+ text
1040
+ end
1041
+ end
1042
+
1043
+ class PreformattedText < Widget
1044
+ StdWidgetFactory.instance.register_widget('pre', self)
1045
+
1046
+ include Text
1047
+
1048
+ def initialize(parent, attrs={})
1049
+ @lines = []
1050
+ super(parent, attrs)
1051
+ font('fixed') if @font.nil?
1052
+ end
1053
+
1054
+ def text(value=nil)
1055
+ return @lines if value.nil?
1056
+ value.lstrip! if @lines.empty?
1057
+ @lines.concat(value.split("\n")) unless value.empty?
1058
+ end
1059
+
1060
+ def layout_widget(writer)
1061
+ super(writer)
1062
+ @lines.pop while @lines.last.strip.empty?
1063
+ @height ||= preferred_height(writer)
1064
+ end
1065
+
1066
+ def preferred_width(writer, units=:pt)
1067
+ font.apply(writer)
1068
+ @preferred_width = @width || @lines.map { |line| writer.width(line) }.max + non_content_width
1069
+ to_units(units, @preferred_width)
1070
+ end
1071
+
1072
+ def preferred_height(writer, units=:pt)
1073
+ font.apply(writer)
1074
+ @preferred_height = writer.height(@lines) + non_content_height - (writer.height - writer.height.quo(writer.line_height))
1075
+ to_units(units, @preferred_height)
1076
+ end
1077
+
1078
+ def url(value=nil)
1079
+ return @url if value.nil?
1080
+ @url = value
1081
+ text(text_for(@url))
1082
+ end
1083
+
1084
+ protected
1085
+ def draw_content(writer)
1086
+ raise Exception, "left & top must be set: #{text.inspect}" if left.nil? or top.nil?
1087
+ font.apply(writer)
1088
+ writer.puts_xy(content_left, content_top + writer.text_ascent, @lines)
1089
+ end
1090
+
1091
+ def text_for(url)
1092
+ open(url) { |f| f.read }
1093
+ rescue Exception => e
1094
+ raise RuntimeError, "Error opening #{url}", e.backtrace
1095
+ end
1096
+ end
1097
+
1098
+ class Container < Widget
1099
+ StdWidgetFactory.instance.register_widget('div', self)
1100
+
1101
+ attr_reader :children
1102
+
1103
+ def initialize(parent, attrs={})
1104
+ super(parent, attrs)
1105
+ @children = []
1106
+ end
1107
+
1108
+ def initialize_copy(other)
1109
+ super(other)
1110
+ @children = other.children.map { |child| child.clone }
1111
+ @children.each { |child| child.parent = self }
1112
+ end
1113
+
1114
+ def after_layout
1115
+ # puts "after_layout: #{tag}"
1116
+ layout.manager.after_layout(self) unless layout.nil?
1117
+ children.each do |widget|
1118
+ widget.after_layout if widget.visible
1119
+ end
1120
+ end
1121
+
1122
+ def cols(value=nil)
1123
+ return @cols if value.nil?
1124
+ @cols = value.to_i if value.to_i > 0
1125
+ end
1126
+
1127
+ def layout(value=nil)
1128
+ return @layout_style || layout('vbox') if value.nil?
1129
+ @layout_style = layout_style_for(value)
1130
+ end
1131
+
1132
+ def layout_container(writer)
1133
+ layout.manager.layout(self, writer)
1134
+ # returns count
1135
+ end
1136
+
1137
+ def layout_widget(writer)
1138
+ super(writer)
1139
+ layout_container(writer)
1140
+ end
1141
+
1142
+ def leaf?
1143
+ children.empty?
1144
+ end
1145
+
1146
+ def leaves
1147
+ visible ? children.select { |w| w.visible }.inject(0) { |m, widget| m + widget.leaves } + super : 0
1148
+ end
1149
+
1150
+ def more(flag=nil)
1151
+ parent.more(flag)
1152
+ end
1153
+
1154
+ def order(value=nil)
1155
+ return @order || :rows if value.nil?
1156
+ @order = value.to_sym if [:rows, :cols].include?(value.to_sym)
1157
+ end
1158
+
1159
+ def overflow(value=nil)
1160
+ return @overflow if value.nil?
1161
+ @overflow = case value
1162
+ when true, 'true' then true
1163
+ when false, 'false' then false
1164
+ else value.to_s
1165
+ end
1166
+ end
1167
+
1168
+ def paragraph_style(value=nil)
1169
+ return @paragraph_style || parent.paragraph_style if value.nil?
1170
+ @paragraph_style = paragraph_style_for(value)
1171
+ end
1172
+
1173
+ def preferred_content_height(writer)
1174
+ @preferred_content_height ||= layout.manager.preferred_height(layout_grid, writer)
1175
+ end
1176
+
1177
+ def preferred_content_width(writer)
1178
+ @preferred_content_width ||= layout.manager.preferred_width(layout_grid, writer)
1179
+ end
1180
+
1181
+ def preferred_height(writer, units=:pt)
1182
+ @preferred_height ||= @height || (preferred_content_height(writer) || return) + non_content_height
1183
+ to_units(units, overflow ? [@preferred_height, max_height_avail].min : @preferred_height)
1184
+ end
1185
+
1186
+ def preferred_width(writer, units=:pt)
1187
+ @preferred_width ||= @width || (preferred_content_width(writer) || return) + non_content_width
1188
+ to_units(units, @preferred_width)
1189
+ end
1190
+
1191
+ def printed
1192
+ disabled or (super and children.all? { |widget| widget.printed })
1193
+ end
1194
+
1195
+ def rows(value=nil)
1196
+ return @rows if value.nil?
1197
+ @rows = value.to_i if value.to_i > 0
1198
+ end
1199
+
1200
+ def source(id=nil)
1201
+ return children if id.nil?
1202
+ @children = widget_for(id).children
1203
+ end
1204
+
1205
+ def visible(bounds=nil)
1206
+ bounds.nil? ? super : super + children.inject(0) { |total, widget| total + widget.visible(bounds) }
1207
+ end
1208
+
1209
+ protected
1210
+ def draw_content(writer)
1211
+ super(writer)
1212
+ children.sort { |a, b| a.z_index <=> b.z_index }.each { |child| child.print(writer) }
1213
+ end
1214
+
1215
+ def layout_grid
1216
+ @layout_grid ||= layout.manager.grid(self)
1217
+ end
1218
+
1219
+ def layout_style_for(id)
1220
+ ls = root.styles.for_id(id)
1221
+ raise ArgumentError, "Layout Style #{id} not found." unless ls.is_a?(Styles::LayoutStyle)
1222
+ ls
1223
+ end
1224
+
1225
+ def paragraph_style_for(id)
1226
+ ps = root.styles.for_id(id)
1227
+ raise ArgumentError, "Paragraph Style #{id} not found." unless ps.is_a?(Styles::ParagraphStyle)
1228
+ ps
1229
+ end
1230
+ end
1231
+
1232
+ class Circle < Container
1233
+ StdWidgetFactory.instance.register_widget('circle', self)
1234
+
1235
+ include Shape
1236
+
1237
+ def before_layout
1238
+ super
1239
+ @width ||= @height
1240
+ @height ||= @width
1241
+ end
1242
+
1243
+ def clip(value=nil)
1244
+ # TODO
1245
+ end
1246
+
1247
+ def default_padding_top
1248
+ (@preferred_height and @preferred_content_height) ? @preferred_radius - @preferred_content_height / 2.0 : 0
1249
+ end
1250
+
1251
+ def default_padding_right
1252
+ (@preferred_width and @preferred_content_width) ? @preferred_radius - @preferred_content_width / 2.0 : 0
1253
+ end
1254
+
1255
+ def default_padding_bottom
1256
+ (@preferred_height and @preferred_content_height) ? @preferred_radius - @preferred_content_height / 2.0 : 0
1257
+ end
1258
+
1259
+ def default_padding_left
1260
+ (@preferred_width and @preferred_content_width) ? @preferred_radius - @preferred_content_width / 2.0 : 0
1261
+ end
1262
+
1263
+ def preferred_radius(writer, units=:pt)
1264
+ @preferred_radius ||= begin
1265
+ pcw, pch = preferred_content_width(writer), preferred_content_height(writer)
1266
+ return if pcw.nil? and pch.nil?
1267
+ Math.sqrt(((pcw || pch) / 2.0) ** 2 + ((pch || pcw) / 2.0) ** 2)
1268
+ end
1269
+ to_units(units, @preferred_radius)
1270
+ end
1271
+
1272
+ def preferred_height(writer, units=:pt)
1273
+ @preferred_height ||= @height || @width || ((preferred_radius(writer) || return) * 2 + non_content_height)
1274
+ to_units(units, @preferred_height)
1275
+ end
1276
+
1277
+ def preferred_width(writer, units=:pt)
1278
+ @preferred_width ||= @width || @height || ((preferred_radius(writer) || return) * 2 + non_content_width)
1279
+ to_units(units, @preferred_width)
1280
+ end
1281
+
1282
+ def r(value=nil, units=nil)
1283
+ return @r if value.nil?
1284
+ return to_units(value, @r) if value.is_a?(Symbol)
1285
+ @r = parse_measurement_pts(value, units || self.units)
1286
+ @r = [(width - margin_left - margin_right).quo(2), (height - margin_top - margin_bottom).quo(2)].min + @r if @r < 0
1287
+ @width ||= @r * 2 + margin_left + margin_right
1288
+ @height ||= @r * 2 + margin_top + margin_bottom
1289
+ end
1290
+
1291
+ def reverse(value=nil)
1292
+ # TODO
1293
+ end
1294
+
1295
+ protected
1296
+ def before_print(writer)
1297
+ # puts "left: #{left}, margin_left: #{margin_left}, right: #{right}, margin_right: #{margin_right}"
1298
+ @x ||= (left + margin_left + right - margin_right).quo(2)
1299
+ @y ||= (top + margin_top + bottom - margin_bottom).quo(2)
1300
+ # puts "before_print 1 @r: #{@r.inspect}"
1301
+ # puts "@r ||= [(#{width} - #{margin_left} - #{margin_right}).quo(2), (#{height} - #{margin_top} - #{margin_bottom}).quo(2)].min"
1302
+ @r ||= [(width - margin_left - margin_right).quo(2), (height - margin_top - margin_bottom).quo(2)].min
1303
+ # @r ||= preferred_radius(writer)
1304
+ # @width ||= @r * 2 + margin_left + margin_right
1305
+ # @height ||= @r * 2 + margin_top + margin_bottom
1306
+ # puts "before_print 2 @r: #{@r.inspect}"
1307
+ super(writer)
1308
+ end
1309
+
1310
+ def height_set
1311
+ @preferred_height ||= @height
1312
+ @preferred_width ||= @height
1313
+ end
1314
+
1315
+ def width_set
1316
+ @preferred_width ||= @width
1317
+ @preferred_height ||= @width
1318
+ end
1319
+
1320
+ def x_offset
1321
+ (position == :relative) ? parent.content_left : 0
1322
+ end
1323
+
1324
+ def y_offset
1325
+ (position == :relative) ? parent.content_top : 0
1326
+ end
1327
+
1328
+ def draw_content(writer)
1329
+ super(writer)
1330
+ options = {}
1331
+ options[:border] = !!@border
1332
+ options[:fill] = !!@fill
1333
+ @border.apply(writer) unless @border.nil?
1334
+ @fill.apply(writer) unless @fill.nil?
1335
+ # puts "writer.circle(#{@x} + #{x_offset}, #{@y} + #{y_offset}, #{r}, #{options.inspect})"
1336
+ writer.circle(@x + x_offset, @y + y_offset, r, options)
1337
+ end
1338
+ end
1339
+
1340
+ class Ellipse < Container
1341
+ StdWidgetFactory.instance.register_widget('ellipse', self)
1342
+
1343
+ include Shape
1344
+
1345
+ def rotation(value=nil)
1346
+ # TODO
1347
+ end
1348
+
1349
+ def rx(value=nil)
1350
+ return @rx if value.nil?
1351
+ return to_units(value, @rx) if value.is_a?(Symbol)
1352
+ @rx = parse_measurement_pts(value, units || self.units)
1353
+ @rx = (width - margin_left - margin_right).quo(2) + @rx if @rx < 0
1354
+ @width ||= @rx * 2 + margin_left + margin_right
1355
+ end
1356
+
1357
+ def ry(value=nil)
1358
+ return @ry if value.nil?
1359
+ return to_units(value, @ry) if value.is_a?(Symbol)
1360
+ @ry = parse_measurement_pts(value, units || self.units)
1361
+ @ry = (height - margin_top - margin_bottom).quo(2) + @ry if @ry < 0
1362
+ @height ||= @ry * 2 + margin_top + margin_bottom
1363
+ end
1364
+
1365
+ protected
1366
+ def draw_content(writer)
1367
+ @x ||= (content_left + content_right).quo(2)
1368
+ @y ||= (content_top + content_bottom).quo(2)
1369
+ @rx ||= (width - margin_left - margin_right).quo(2)
1370
+ @ry ||= (height - margin_top - margin_bottom).quo(2)
1371
+ options = {}
1372
+ options[:border] = !!@border
1373
+ options[:fill] = !!@fill
1374
+ @border.apply(writer) unless @border.nil?
1375
+ @fill.apply(writer) unless @fill.nil?
1376
+ x_offset, y_offset = (position == :relative) ? [parent.content_left, parent.content_top] : [0, 0]
1377
+ writer.ellipse(@x + x_offset, @y + y_offset, @rx, @ry, options)
1378
+ super(writer)
1379
+ end
1380
+ end
1381
+
1382
+ class Label < Container
1383
+ StdWidgetFactory.instance.register_widget('label', self)
1384
+
1385
+ include HasLocation
1386
+ include Text
1387
+
1388
+ def angle(value=nil)
1389
+ return style.angle if value.nil?
1390
+ style(:copy).angle(value)
1391
+ end
1392
+
1393
+ def before_layout
1394
+ children.each { |child| child.before_layout }
1395
+ end
1396
+
1397
+ def layout_container(writer)
1398
+ # suppress default behavior
1399
+ 0
1400
+ end
1401
+
1402
+ def preferred_width(writer, units=:pt)
1403
+ font.apply(writer)
1404
+ to_units(units, writer.width(text) + non_content_width)
1405
+ end
1406
+
1407
+ def preferred_height(writer, units=:pt)
1408
+ font.apply(writer)
1409
+ to_units(units, writer.text_height + non_content_height)
1410
+ end
1411
+
1412
+ def style(value=nil)
1413
+ return @style ||= label_style_for('label') if value.nil?
1414
+ return @style = style.clone if value == :copy
1415
+ @style = label_style_for(value)
1416
+ end
1417
+
1418
+ def text(value=nil)
1419
+ @text_pieces ||= []
1420
+ return @text_pieces.join if value.nil?
1421
+ value.lstrip! if @text_pieces.empty? and value.respond_to?(:lstrip!)
1422
+ @text_pieces << value unless value.respond_to?(:empty?) and value.empty?
1423
+ end
1424
+
1425
+ def text_align(value=nil)
1426
+ return style.text_align if value.nil?
1427
+ style(:copy).text_align(value)
1428
+ end
1429
+
1430
+ protected
1431
+ def before_print(writer)
1432
+ @width ||= preferred_width(writer)
1433
+ @height ||= preferred_height(writer)
1434
+ super(writer)
1435
+ end
1436
+
1437
+ def draw_content(writer)
1438
+ super(writer)
1439
+ options = { :angle => angle, :underline => underline }
1440
+ @y ||= content_top
1441
+ case text_align
1442
+ when :left
1443
+ @x ||= content_left
1444
+ when :center
1445
+ @x ||= (content_left + content_right).quo(2)
1446
+ options[:align] = :center
1447
+ when :right
1448
+ @x ||= content_right
1449
+ options[:align] = :right
1450
+ end
1451
+ writer.print_xy(@x, @y + writer.text_ascent, text, options)
1452
+ end
1453
+
1454
+ def label_style_for(id)
1455
+ ls = root.styles.for_id(id)
1456
+ raise ArgumentError, "Label Style #{id} not found." unless ls.is_a?(Styles::LabelStyle)
1457
+ ls
1458
+ end
1459
+ end
1460
+
1461
+ class Paragraph < Container
1462
+ StdWidgetFactory.instance.register_widget('p', self)
1463
+
1464
+ include Text
1465
+
1466
+ def before_layout
1467
+ children.each { |child| child.before_layout }
1468
+ end
1469
+
1470
+ def bullet(value=nil)
1471
+ return @bullet.nil? ? style.bullet : @bullet if value.nil?
1472
+ @bullet = bullet_style_for(value)
1473
+ end
1474
+
1475
+ def layout_container(writer)
1476
+ # suppress default behavior
1477
+ 0
1478
+ end
1479
+
1480
+ def layout_widget(writer)
1481
+ super(writer)
1482
+ @height ||= preferred_height(writer)
1483
+ end
1484
+
1485
+ def preferred_width(writer, units=:pt)
1486
+ @preferred_width = @width || begin
1487
+ (rich_text(writer).width(root_page.width - bullet_width - non_content_width) || 0) + bullet_width + non_content_width + 1
1488
+ end
1489
+ to_units(units, @preferred_width)
1490
+ end
1491
+
1492
+ def preferred_height(writer, units=:pt)
1493
+ @preferred_height = @height || begin
1494
+ ph = if width.nil?
1495
+ rich_text(writer).height(parent.content_width - bullet_width - non_content_width) * line_height
1496
+ else
1497
+ rich_text(writer).height(content_width - bullet_width) * line_height
1498
+ end
1499
+ @preferred_height = ph + non_content_height - rich_text(writer).height * (line_height - 1)
1500
+ end
1501
+ to_units(units, @preferred_height)
1502
+ end
1503
+
1504
+ def style(value=nil)
1505
+ # inherited
1506
+ return @style || parent.paragraph_style if value.nil?
1507
+ return @style || @style = parent.paragraph_style.clone if value == :copy
1508
+ @style = paragraph_style_for(value)
1509
+ end
1510
+
1511
+ def text(value=nil, font=nil)
1512
+ return @text_pieces if value.nil?
1513
+ value.gsub!(/\n\s*/, ' ') if value.respond_to?(:gsub!)
1514
+ @text_pieces ||= []
1515
+ value.lstrip! if @text_pieces.empty? and value.respond_to?(:lstrip!)
1516
+ @text_pieces << [value, font || self.font] unless value.respond_to?(:empty?) and value.empty?
1517
+ end
1518
+
1519
+ def text_align(value=nil)
1520
+ return style.text_align if value.nil?
1521
+ @style = style.clone
1522
+ @style.text_align(value)
1523
+ end
1524
+
1525
+ protected
1526
+ def bullet_style_for(id)
1527
+ bs = root.styles.for_id(id)
1528
+ raise ArgumentError, "Bullet Style #{value} not found." unless bs.is_a?(Styles::BulletStyle)
1529
+ bs
1530
+ end
1531
+
1532
+ def bullet_width
1533
+ bullet.nil? ? 0 : bullet.width
1534
+ end
1535
+
1536
+ def draw_content(writer)
1537
+ super(writer)
1538
+ options = { :align => style.text_align, :underline => underline, :width => content_width }
1539
+ unless bullet.nil?
1540
+ bullet.apply(writer)
1541
+ options[:bullet] = bullet.id unless bullet.nil?
1542
+ end
1543
+ pen_style_for('solid').apply(writer)
1544
+ raise Exception, "left & top must be set: #{text.inspect}" if left.nil? or top.nil?
1545
+ writer.paragraph_xy(content_left, content_top + rich_text(writer).ascent(content_width), rich_text(writer), options)
1546
+ @rich_text = nil
1547
+ end
1548
+
1549
+ def paragraph_style_for(id)
1550
+ ps = root.styles.for_id(id)
1551
+ raise ArgumentError, "Paragraph Style #{id} not found." unless ps.is_a?(Styles::ParagraphStyle)
1552
+ ps
1553
+ end
1554
+
1555
+ def rich_text(writer)
1556
+ if @rich_text.nil?
1557
+ @text_pieces ||= []
1558
+ # Trim trailing whitespace.
1559
+ while !@text_pieces.empty? and @text_pieces.last[0].respond_to?(:to_str) and @text_pieces.last[0].rstrip!
1560
+ @text_pieces.pop if @text_pieces.last[0].empty?
1561
+ end
1562
+ @rich_text = EideticPDF::PdfText::RichText.new
1563
+ @text_pieces.each do |piece|
1564
+ text, font = piece
1565
+ font.apply(writer)
1566
+ @rich_text.add(text.to_s, writer.font, :color => font.color, :underline => font.underline)
1567
+ end unless @text_pieces.nil?
1568
+ end
1569
+ @rich_text
1570
+ end
1571
+ end
1572
+
1573
+ class Polygon < Container
1574
+ StdWidgetFactory.instance.register_widget('polygon', self)
1575
+
1576
+ include Shape
1577
+
1578
+ def clip(value=nil)
1579
+ # TODO
1580
+ end
1581
+
1582
+ def r(value=nil)
1583
+ return @r if value.nil?
1584
+ return to_units(value, @r) if value.is_a?(Symbol)
1585
+ @r = parse_measurement_pts(value, units || self.units)
1586
+ @r = [(width - margin_left - margin_right).quo(2), (height - margin_top - margin_bottom).quo(2)].min + @r if @r < 0
1587
+ @width ||= @r * 2 + margin_left + margin_right
1588
+ @height ||= @r * 2 + margin_top + margin_bottom
1589
+ end
1590
+
1591
+ def reverse(value=nil)
1592
+ # TODO
1593
+ end
1594
+
1595
+ def rotation(value=nil)
1596
+ return @rotation if value.nil?
1597
+ @rotation = value.to_f
1598
+ end
1599
+
1600
+ def sides(value=nil)
1601
+ return @sides || 3 if value.nil?
1602
+ @sides = value.to_i if value.to_i >= 3
1603
+ end
1604
+
1605
+ protected
1606
+ def draw_content(writer)
1607
+ @x ||= (content_left + content_right).quo(2)
1608
+ @y ||= (content_top + content_bottom).quo(2)
1609
+ @r ||= [(width - margin_left - margin_right).quo(2), (height - margin_top - margin_bottom).quo(2)].min
1610
+ options = {}
1611
+ options[:border] = !!@border
1612
+ options[:fill] = !!@fill
1613
+ options[:rotation] = @rotation
1614
+ @border.apply(writer) unless @border.nil?
1615
+ @fill.apply(writer) unless @fill.nil?
1616
+ x_offset, y_offset = (position == :relative) ? [parent.content_left, parent.content_top] : [0, 0]
1617
+ writer.polygon(@x + x_offset, @y + y_offset, @r, sides, options)
1618
+ super(writer)
1619
+ end
1620
+ end
1621
+
1622
+ class Rectangle < Container
1623
+ StdWidgetFactory.instance.register_widget('rect', self)
1624
+
1625
+ include Shape
1626
+
1627
+ def clip(value=nil)
1628
+ # TODO
1629
+ end
1630
+
1631
+ def corners(value=nil)
1632
+ return @corners if value.nil?
1633
+ value = value.split(',') if value.respond_to?(:to_str)
1634
+ value = Array(value)
1635
+ @corners = value.map { |n| parse_measurement_pts(n, units) } if [1,2,4,8].include?(value.size)
1636
+ end
1637
+
1638
+ # def path(value=nil)
1639
+ # # TODO
1640
+ # end
1641
+
1642
+ def reverse(value=nil)
1643
+ # TODO
1644
+ end
1645
+
1646
+ protected
1647
+ def draw_content(writer)
1648
+ raise Exception, "left, top, width & height must be set" if [left, top, width, height].any? { |value| value.nil? }
1649
+ options = {}
1650
+ options[:corners] = @corners unless @corners.nil?
1651
+ options[:border] = !!@border
1652
+ options[:fill] = !!@fill
1653
+ @border.apply(writer) unless @border.nil?
1654
+ @fill.apply(writer) unless @fill.nil?
1655
+ writer.rectangle(left + margin_left, top + margin_top,
1656
+ width - margin_left - margin_right, height - margin_top - margin_bottom,
1657
+ options)
1658
+ super(writer)
1659
+ end
1660
+ end
1661
+
1662
+ class Page < Container
1663
+ StdWidgetFactory.instance.register_widget('page', self)
1664
+
1665
+ def initialize(parent, attrs={})
1666
+ @default_margin = true
1667
+ @more = true
1668
+ @overflow = true
1669
+ super(parent, attrs)
1670
+ raise ArgumentError, "Page must be child of Document." unless parent.nil? or parent.is_a?(Document)
1671
+ end
1672
+
1673
+ def bottom(units=:pt)
1674
+ height(units)
1675
+ end
1676
+
1677
+ def compress(value=nil)
1678
+ # TODO
1679
+ end
1680
+
1681
+ def crop(value=nil)
1682
+ # inherited
1683
+ # TODO
1684
+ end
1685
+
1686
+ def height(units=:pt)
1687
+ to_units(units, style.height)
1688
+ end
1689
+
1690
+ def left(units=:pt)
1691
+ 0
1692
+ end
1693
+
1694
+ def margin(value=nil)
1695
+ # inherited
1696
+ return @default_margin ? parent.margin(value) : super(value) if value.nil? or value.is_a?(Symbol)
1697
+ super(value)
1698
+ @default_margin = false
1699
+ end
1700
+
1701
+ def margin_top(value=nil)
1702
+ # inherited
1703
+ return @default_margin ? parent.margin_top(value) : super(value) if value.nil? or value.is_a?(Symbol)
1704
+ super(value)
1705
+ @default_margin = false
1706
+ end
1707
+
1708
+ def margin_right(value=nil)
1709
+ # inherited
1710
+ return @default_margin ? parent.margin_right(value) : super(value) if value.nil? or value.is_a?(Symbol)
1711
+ margin(margin) if @default_margin
1712
+ super(value)
1713
+ @default_margin = false
1714
+ end
1715
+
1716
+ def margin_bottom(value=nil)
1717
+ # inherited
1718
+ return @default_margin ? parent.margin_bottom(value) : super(value) if value.nil? or value.is_a?(Symbol)
1719
+ margin(margin) if @default_margin
1720
+ super(value)
1721
+ @default_margin = false
1722
+ end
1723
+
1724
+ def margin_left(value=nil)
1725
+ # inherited
1726
+ return @default_margin ? parent.margin_left(value) : super(value) if value.nil? or value.is_a?(Symbol)
1727
+ margin(margin) if @default_margin
1728
+ super(value)
1729
+ @default_margin = false
1730
+ end
1731
+
1732
+ def more(flag=nil)
1733
+ return @more if flag.nil?
1734
+ @more = flag
1735
+ end
1736
+
1737
+ def orientation(value=nil)
1738
+ # inherited
1739
+ return style.orientation if value.nil?
1740
+ style(:copy).orientation(value)
1741
+ end
1742
+
1743
+ def positioned_widgets
1744
+ @positioned_widgets ||= Hash.new(0)
1745
+ end
1746
+
1747
+ def print(writer)
1748
+ root.section_page_no = 0
1749
+ while more
1750
+ more(false)
1751
+ writer.open_page(:page_size => size, :orientation => orientation)
1752
+ root.document_page_no += 1
1753
+ root.section_page_no += 1
1754
+ positioned_widgets.clear
1755
+ # $stderr.puts "----before layout widget"
1756
+ layout_widget(writer)
1757
+ # after_layout
1758
+ # $stderr.puts "----before super print"
1759
+ super(writer)
1760
+ # $stderr.puts "----after super print"
1761
+ writer.close_page
1762
+ break if positioned_widgets[:static] == 0
1763
+ end
1764
+ end
1765
+
1766
+ def right(units=:pt)
1767
+ width(units)
1768
+ end
1769
+
1770
+ def root_page
1771
+ self
1772
+ end
1773
+
1774
+ def rotate(value=nil)
1775
+ # inherited
1776
+ # TODO
1777
+ end
1778
+
1779
+ def size(value=nil)
1780
+ # inherited
1781
+ return style.size if value.nil?
1782
+ style(:copy).size(value)
1783
+ end
1784
+
1785
+ def style(value=nil)
1786
+ # inherited
1787
+ return @page_style || parent.page_style if value.nil?
1788
+ return @page_style || @page_style = parent.page_style.clone if value == :copy
1789
+ @page_style = page_style_for(value)
1790
+ end
1791
+
1792
+ def top(units=:pt)
1793
+ 0
1794
+ end
1795
+
1796
+ def width(units=:pt)
1797
+ to_units(units, style.width)
1798
+ end
1799
+
1800
+ protected
1801
+ def page_style_for(id)
1802
+ ps = root.styles.for_id(id)
1803
+ raise ArgumentError, "Page Style #{id} not found." unless ps.is_a?(Styles::PageStyle)
1804
+ ps
1805
+ end
1806
+ end
1807
+
1808
+ class Document < Page
1809
+ StdWidgetFactory.instance.register_widget('erml', self)
1810
+
1811
+ alias :pages :children
1812
+ attr_accessor :document_page_no, :section_page_no
1813
+
1814
+ def initialize(parent=nil, attrs={})
1815
+ super(parent, attrs)
1816
+ @default_margin = false
1817
+ init_default_styles
1818
+ end
1819
+
1820
+ # def orientation(value=nil)
1821
+ # return @orientation || :portrait if value.nil?
1822
+ # super(value)
1823
+ # end
1824
+
1825
+ def rules
1826
+ @rules ||= Rules::RuleCollection.new
1827
+ end
1828
+
1829
+ def page_style(value=nil)
1830
+ return @page_style if value.nil?
1831
+ super(value)
1832
+ end
1833
+
1834
+ def pages_up(value=nil)
1835
+ return @pages_up || [1, 1] if value.nil?
1836
+ if value.respond_to?(:to_str)
1837
+ x, y = value.to_s.split(',', 2)
1838
+ else
1839
+ x, y = Array(value)
1840
+ end
1841
+ @pages_up = [x.to_i, y.to_i]
1842
+ end
1843
+
1844
+ def pages_up_layout(value=nil)
1845
+ return @pages_up_layout || :across if value.nil?
1846
+ @pages_up_layout = value.to_sym if [:across, :down].include?(value.to_sym)
1847
+ end
1848
+
1849
+ def print(writer)
1850
+ @document_page_no = 0
1851
+ writer.open(:v_text_align => :base, :pages_up => pages_up, :pages_up_layout => pages_up_layout, :orientation => orientation, :text_encoding => 'UTF-8')
1852
+ pages.each do |page|
1853
+ page.print(writer)
1854
+ end
1855
+ writer.close
1856
+ end
1857
+
1858
+ def styles
1859
+ @styles ||= Styles::StyleCollection.new
1860
+ end
1861
+
1862
+ def to_s
1863
+ writer = EideticPDF::DocumentWriter.new
1864
+ print(writer)
1865
+ writer.to_s
1866
+ end
1867
+
1868
+ def units(value=nil)
1869
+ return @units || :pt if value.nil?
1870
+ super(value)
1871
+ end
1872
+
1873
+ def widgets
1874
+ @widgets ||= {}
1875
+ end
1876
+
1877
+ private
1878
+ def init_default_styles
1879
+ @page_style = styles.add('page', :id => 'page')
1880
+ @font = styles.add('font', :id => 'font')
1881
+ @label_style = styles.add('label', :id => 'label')
1882
+ @paragraph_style = styles.add('para', :id => 'p')
1883
+ styles.add('layout', :id => 'absolute', :manager => 'absolute')
1884
+ styles.add('layout', :id => 'flow', :manager => 'flow', :padding => 5)
1885
+ styles.add('layout', :id => 'hbox', :manager => 'hbox')
1886
+ styles.add('layout', :id => 'vbox', :manager => 'vbox')
1887
+ styles.add('layout', :id => 'table', :manager => 'table', :padding => 5)
1888
+ styles.add('pen', :id => 'solid', :pattern => 'solid', :color => 'Black')
1889
+ styles.add('pen', :id => 'dotted', :pattern => 'dotted', :color => 'Black')
1890
+ styles.add('pen', :id => 'dashed', :pattern => 'dashed', :color => 'Black')
1891
+ styles.add('font', :id => 'fixed', :name => 'Courier', :size => 10)
1892
+ end
1893
+ end
1894
+ end
1895
+ end