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,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