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.
- checksums.yaml +7 -0
- data/Rakefile +55 -0
- data/lib/erml.rb +345 -0
- data/lib/erml_layout_managers.rb +667 -0
- data/lib/erml_rules.rb +104 -0
- data/lib/erml_styles.rb +304 -0
- data/lib/erml_support.rb +105 -0
- data/lib/erml_widget_factories.rb +38 -0
- data/lib/erml_widgets.rb +1895 -0
- data/samples/test10_rich_text.erml +17 -0
- data/samples/test11_table_layout.erml +30 -0
- data/samples/test12_shapes.erml +32 -0
- data/samples/test13_polygons.erml +28 -0
- data/samples/test14_images.erml +19 -0
- data/samples/test15_lines.erml +43 -0
- data/samples/test16_classes.erml +34 -0
- data/samples/test17_rules.erml +24 -0
- data/samples/test18_preformatted_text.erml +9 -0
- data/samples/test19_erb.erml.erb +26 -0
- data/samples/test1_empty_doc.erml +2 -0
- data/samples/test20_haml.erml.haml +20 -0
- data/samples/test21_shift_widgets.erml +47 -0
- data/samples/test22_multipage_flow_layout.erml +40 -0
- data/samples/test23_pageno.erml +17 -0
- data/samples/test24_headers_footers.erml.erb +37 -0
- data/samples/test25_overflow.erml.erb +37 -0
- data/samples/test26_columns.erml.erb +42 -0
- data/samples/test28_landscape.erml.erb +17 -0
- data/samples/test29_pages_up.erml.erb +17 -0
- data/samples/test2_empty_page.erml +6 -0
- data/samples/test30_encodings.erml.haml +35 -0
- data/samples/test3_hello_world.erml +7 -0
- data/samples/test4_two_pages.erml +10 -0
- data/samples/test5_rounded_rect.erml +10 -0
- data/samples/test6_bullets.erml +16 -0
- data/samples/test7_flow_layout.erml +20 -0
- data/samples/test8_vbox_layout.erml +23 -0
- data/samples/test9_hbox_layout.erml +22 -0
- data/samples/testimg.jpg +0 -0
- data/test/test_erml_layout_managers.rb +106 -0
- data/test/test_erml_rules.rb +116 -0
- data/test/test_erml_styles.rb +415 -0
- data/test/test_erml_support.rb +140 -0
- data/test/test_erml_widget_factories.rb +46 -0
- data/test/test_erml_widgets.rb +1235 -0
- data/test/test_helpers.rb +18 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5e7a24549e78789d378487d0b3b2d7c8ae015167
|
4
|
+
data.tar.gz: de5a5090642ccb6928f0b31f301e8583e7c895df
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56da9c4a8f20131617bba41606eb6f4ae8293ea5ee6c7375b5c7266ee766ba6ab71c26050f5e871af2d517e88890de165ec8ab58cae82f3bbbab3b13469ee174
|
7
|
+
data.tar.gz: ba2c27d259d1ba7eb4275eeb59878f5b887c7c696fd5249459a142fc3dfb3692c346761d29bd0142577dfd0b4efaa738ce9b0ab44e8bfd29b3a826385a795430
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/package_task'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = "eideticrml"
|
7
|
+
s.version = "0.3.0"
|
8
|
+
s.date = "2017-09-11"
|
9
|
+
s.summary = "Report Markup Language"
|
10
|
+
s.requirements = "Ruby 2.x, eideticpdf"
|
11
|
+
s.add_runtime_dependency "eideticpdf", [">= 1.0.2"]
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.autorequire = 'erml'
|
14
|
+
s.email = "brent.rowland@eideticsoftware.com"
|
15
|
+
s.homepage = "http://www.eideticsoftware.com"
|
16
|
+
s.author = "Brent Rowland, Eidetic Software, LLC"
|
17
|
+
# s.rubyforge_project = "eideticrml"
|
18
|
+
# s.test_file = "test/pdf_tests.rb"
|
19
|
+
s.has_rdoc = false
|
20
|
+
# s.extra_rdoc_files = ['README']
|
21
|
+
# s.rdoc_options << '--title' << 'Eidetic RML' << '--main' << 'README' << '-x' << 'test'
|
22
|
+
s.files = FileList["lib/*.rb"] + ['Rakefile'] + FileList["test/test*.rb"] + FileList["samples/test*.erml*"] + ['samples/testimg.jpg']
|
23
|
+
s.platform = Gem::Platform::RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
Gem::PackageTask.new(spec) do |pkg|
|
27
|
+
pkg.need_tar = true
|
28
|
+
pkg.need_zip = true
|
29
|
+
end
|
30
|
+
|
31
|
+
Rake::TestTask.new do |t|
|
32
|
+
t.test_files = FileList['test/test*.rb']
|
33
|
+
t.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Clean up files generated by tests."
|
37
|
+
task :clean do
|
38
|
+
rm Dir["*.pdf"]
|
39
|
+
rm Dir["samples/*.pdf"]
|
40
|
+
rm Dir["test/*.pdf"]
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Render test erml files to pdf."
|
44
|
+
task :ermls do
|
45
|
+
start = Time.now
|
46
|
+
pdfs = []
|
47
|
+
require_relative 'lib/erml'
|
48
|
+
Dir["samples/*.erml","samples/*.haml","samples/*.erb"].each do |erml|
|
49
|
+
puts erml
|
50
|
+
pdfs << render_erml(erml)
|
51
|
+
end
|
52
|
+
elapsed = Time.now - start
|
53
|
+
puts "Elapsed: #{(elapsed * 1000).round} ms"
|
54
|
+
`open -a Preview #{pdfs * ' '}` if (RUBY_PLATFORM =~ /darwin/) and ($0 !~ /rake_test_loader/)
|
55
|
+
end
|
data/lib/erml.rb
ADDED
@@ -0,0 +1,345 @@
|
|
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
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
7
|
+
require 'erml_support'
|
8
|
+
require 'erml_widgets'
|
9
|
+
|
10
|
+
module EideticRML
|
11
|
+
STANDARD_ALIASES = [
|
12
|
+
{ 'id' => 'h', 'tag' => 'p', 'font.weight' => 'Bold', 'text_align' => 'center', 'width' => '100%' }.freeze,
|
13
|
+
{ 'id' => 'b', 'tag' => 'span', 'font.weight' => 'Bold' }.freeze,
|
14
|
+
{ 'id' => 'i', 'tag' => 'span', 'font.style' => 'Italic' }.freeze,
|
15
|
+
{ 'id' => 'u', 'tag' => 'span', 'underline' => 'true' }.freeze,
|
16
|
+
{ 'id' => 'hbox', 'tag' => 'div', 'layout' => 'hbox' }.freeze,
|
17
|
+
{ 'id' => 'vbox', 'tag' => 'div', 'layout' => 'vbox' }.freeze,
|
18
|
+
{ 'id' => 'table', 'tag' => 'div', 'layout' => 'table' }.freeze,
|
19
|
+
{ 'id' => 'layer', 'tag' => 'div', 'position' => 'relative', 'width' => '100%', 'height' => '100%' }.freeze,
|
20
|
+
{ 'id' => 'br', 'tag' => 'label' }.freeze
|
21
|
+
]
|
22
|
+
|
23
|
+
class StyleBuilder
|
24
|
+
def initialize(styles)
|
25
|
+
@styles = styles
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(id, *args)
|
29
|
+
@styles.add(id, *args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class RuleBuilder
|
34
|
+
def initialize(rules)
|
35
|
+
@rules = rules
|
36
|
+
end
|
37
|
+
|
38
|
+
def rule(selector, attrs={})
|
39
|
+
@rules.add(selector, attrs)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class PageBuilder
|
44
|
+
undef_method :p
|
45
|
+
|
46
|
+
def initialize(doc)
|
47
|
+
@stack = [doc]
|
48
|
+
@tag_aliases = {}
|
49
|
+
EideticRML::STANDARD_ALIASES.each do |a|
|
50
|
+
a = a.dup
|
51
|
+
define(a.delete('id'), a['tag'], a)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize_copy(other)
|
56
|
+
@stack = @stack.clone
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(id, *args, &block)
|
60
|
+
if current.respond_to?(id)
|
61
|
+
current.send(id, *args)
|
62
|
+
return current
|
63
|
+
else
|
64
|
+
tag, attrs = @tag_aliases[id.to_s] || [id.to_s, {}]
|
65
|
+
attrs = attrs.merge(fix_attrs(args.first))
|
66
|
+
attrs['tag'] = id unless tag == id
|
67
|
+
factory = Widgets::StdWidgetFactory.instance # TODO: select factory by namespace
|
68
|
+
raise ArgumentError, "Unknown tag: #{tag}." unless factory.has_widget?(tag)
|
69
|
+
widget = factory.make_widget(tag, current, attrs)
|
70
|
+
@stack.push(widget)
|
71
|
+
result = if block_given?
|
72
|
+
yield
|
73
|
+
current
|
74
|
+
else
|
75
|
+
self.clone
|
76
|
+
end
|
77
|
+
@stack.pop
|
78
|
+
return result
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def define(new_tag, old_tag, attrs)
|
83
|
+
@tag_aliases[new_tag.to_s] = [old_tag.to_s, attrs.clone.freeze]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def current
|
88
|
+
@stack.last
|
89
|
+
end
|
90
|
+
|
91
|
+
def fix_attrs(attrs)
|
92
|
+
if attrs.nil?
|
93
|
+
{}
|
94
|
+
elsif attrs.respond_to?(:to_str)
|
95
|
+
{ :text => attrs }
|
96
|
+
else
|
97
|
+
attrs
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Builder
|
103
|
+
attr_reader :doc
|
104
|
+
|
105
|
+
def initialize(&block)
|
106
|
+
@doc = Widgets::Document.new
|
107
|
+
self.instance_eval(&block) if block_given?
|
108
|
+
end
|
109
|
+
|
110
|
+
def styles(&block)
|
111
|
+
@styles ||= StyleBuilder.new(@doc.styles)
|
112
|
+
@styles.instance_eval(&block) if block_given?
|
113
|
+
@styles
|
114
|
+
end
|
115
|
+
|
116
|
+
def rules(&block)
|
117
|
+
@rules ||= RuleBuilder.new(@doc.rules)
|
118
|
+
@rules.instance_eval(&block)
|
119
|
+
end
|
120
|
+
|
121
|
+
def pages(attrs={}, &block)
|
122
|
+
@doc.attributes(attrs)
|
123
|
+
@pages ||= PageBuilder.new(@doc)
|
124
|
+
@pages.instance_eval(&block) if block_given?
|
125
|
+
@pages
|
126
|
+
end
|
127
|
+
|
128
|
+
def print(options={})
|
129
|
+
file = options[:file] || "#{File.basename($0, '.rb')}.pdf"
|
130
|
+
File.open(file,'w') { |f| f.write(@doc) }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class XmlStyleParser
|
135
|
+
undef_method :p
|
136
|
+
|
137
|
+
def initialize(stack, styles)
|
138
|
+
@stack, @styles = stack, styles
|
139
|
+
@stack.push styles
|
140
|
+
end
|
141
|
+
|
142
|
+
def method_missing(id, *args)
|
143
|
+
attrs = args.first.inject({}) { |attrs, kv| attrs[kv[0].to_sym] = kv[1]; attrs }
|
144
|
+
@styles.add(id, attrs)
|
145
|
+
@stack.push @styles
|
146
|
+
end
|
147
|
+
|
148
|
+
def text(text)
|
149
|
+
# no meaningful text in styles section
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class XmlRuleParser
|
154
|
+
undef_method :p
|
155
|
+
undef_method :rule if self.private_methods.include?('rule')
|
156
|
+
|
157
|
+
def initialize(stack, rules)
|
158
|
+
@stack, @rules = stack, rules
|
159
|
+
@stack.push rules
|
160
|
+
end
|
161
|
+
|
162
|
+
def comment(text)
|
163
|
+
# puts "rule comment: #{text}"
|
164
|
+
Rules::Rule.parse(text).each { |rule| @rules.add(rule[0], rule[1]) }
|
165
|
+
end
|
166
|
+
|
167
|
+
def method_missing(id, *args)
|
168
|
+
@stack.push @rules.add(id, *args)
|
169
|
+
end
|
170
|
+
|
171
|
+
def text(text)
|
172
|
+
# no meaningful text in rules section
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class XmlPageParser
|
177
|
+
COLLISIONS = [:p, :method]
|
178
|
+
COLLISIONS.each { |symbol| undef_method(symbol) }
|
179
|
+
|
180
|
+
def initialize(stack, doc)
|
181
|
+
@stack = stack
|
182
|
+
@stack.push doc
|
183
|
+
@tag_aliases = {}
|
184
|
+
STANDARD_ALIASES.each { |definition| define(definition) }
|
185
|
+
end
|
186
|
+
|
187
|
+
def method_missing(id, *args)
|
188
|
+
# puts "page method_missing: #{id}, #{args.inspect}"
|
189
|
+
if current.respond_to?(id) and !COLLISIONS.include?(id)
|
190
|
+
current.send(id, *args)
|
191
|
+
@stack.push(current)
|
192
|
+
else
|
193
|
+
tag, attrs = @tag_aliases[id.to_s] || [id.to_s, {}]
|
194
|
+
attrs = attrs.merge(args.first)
|
195
|
+
attrs['tag'] = id unless tag == id
|
196
|
+
factory = Widgets::StdWidgetFactory.instance # TODO: select factory by namespace
|
197
|
+
raise ArgumentError, "Unknown tag: #{tag}." unless factory.has_widget?(tag)
|
198
|
+
# puts "Making #{tag} with parent #{current.class}."
|
199
|
+
widget = factory.make_widget(tag, current, attrs)
|
200
|
+
@stack.push(widget)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def define(attrs)
|
205
|
+
attrs = attrs.dup
|
206
|
+
id, tag = attrs.delete('id').to_s, attrs['tag'].to_s
|
207
|
+
raise ArgumentError, "Invalid id for define: #{id}." unless id =~ /^(\w+)$/
|
208
|
+
raise ArgumentError, "Invalid tag for define: #{tag}." unless tag =~ /^(\w+)$/
|
209
|
+
@tag_aliases[id] = [tag, attrs.freeze]
|
210
|
+
@stack.push(current)
|
211
|
+
end
|
212
|
+
|
213
|
+
def text(text)
|
214
|
+
if current.respond_to?(:text)
|
215
|
+
current.text(text)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
def current
|
221
|
+
@stack.last
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class XmlParser
|
226
|
+
attr_reader :doc
|
227
|
+
|
228
|
+
def initialize
|
229
|
+
@doc = Widgets::Document.new
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.parse(data)
|
233
|
+
require 'rexml/document'
|
234
|
+
parser = self.new
|
235
|
+
REXML::Document.parse_stream(data, parser)
|
236
|
+
parser.doc
|
237
|
+
end
|
238
|
+
|
239
|
+
def comment(text)
|
240
|
+
# puts "base comment: #{text}"
|
241
|
+
@parser.comment(text) if @parser.respond_to?(:comment)
|
242
|
+
end
|
243
|
+
|
244
|
+
def tag_start(name, attrs)
|
245
|
+
# puts "tag_start: #{name}"
|
246
|
+
if @parser.nil?
|
247
|
+
self.send(name, attrs)
|
248
|
+
else
|
249
|
+
@parser.send(name, attrs)
|
250
|
+
end
|
251
|
+
rescue Exception => e
|
252
|
+
raise ArgumentError,
|
253
|
+
"Error processing <%s>\n%s" % [attrs.inject(name) { |tag, (k, v)| tag << " #{k}=\"#{v}\"" }, e.message], e.backtrace
|
254
|
+
end
|
255
|
+
|
256
|
+
def tag_end(name)
|
257
|
+
# puts "tag_end: #{name}"
|
258
|
+
@stack.pop
|
259
|
+
@parser = nil if @stack.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
def text(text)
|
263
|
+
# puts "text: #{text.strip}"
|
264
|
+
@parser.text(text) unless @parser.nil?
|
265
|
+
end
|
266
|
+
|
267
|
+
def method_missing(id, *args)
|
268
|
+
# puts "missing: #{id}, #{args.inspect}"
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
def current
|
273
|
+
@stack.last
|
274
|
+
end
|
275
|
+
|
276
|
+
def erml(attrs)
|
277
|
+
@stack = []
|
278
|
+
end
|
279
|
+
|
280
|
+
def styles(attrs)
|
281
|
+
# puts "styles"
|
282
|
+
@parser = XmlStyleParser.new(@stack, @doc.styles)
|
283
|
+
end
|
284
|
+
|
285
|
+
def rules(attrs)
|
286
|
+
# puts "rules"
|
287
|
+
@parser = XmlRuleParser.new(@stack, @doc.rules)
|
288
|
+
if url = attrs['url']
|
289
|
+
text = open(url) { |f| f.read }
|
290
|
+
@parser.comment(text)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def pages(attrs)
|
295
|
+
# puts "pages"
|
296
|
+
@doc.attributes(attrs)
|
297
|
+
@parser = XmlPageParser.new(@stack, @doc)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def open_erml(erml, &block)
|
303
|
+
if erml =~ /\.erb$/
|
304
|
+
require 'erb'
|
305
|
+
require 'stringio'
|
306
|
+
source = open(erml) { |f| f.read }
|
307
|
+
result = ERB.new(source).result
|
308
|
+
sio = StringIO.new(result)
|
309
|
+
yield(sio)
|
310
|
+
elsif erml =~ /\.haml$/
|
311
|
+
require 'haml'
|
312
|
+
require 'stringio'
|
313
|
+
source = open(erml) { |f| f.read }
|
314
|
+
result = Haml::Engine.new(source).render
|
315
|
+
sio = StringIO.new(result)
|
316
|
+
yield(sio)
|
317
|
+
else
|
318
|
+
File.open(erml, &block)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def render_erml(erml)
|
323
|
+
doc = open_erml(erml) do |f|
|
324
|
+
begin
|
325
|
+
EideticRML::XmlParser.parse(f)
|
326
|
+
rescue Exception => e
|
327
|
+
$stderr.puts "Error in %s: %s\n%s" % [erml, e.message, e.backtrace.join("\n")]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
unless doc.nil?
|
331
|
+
pdf = erml.sub(/\.erml(\.erb|\.haml)?$/, '') << '.pdf'
|
332
|
+
File.open(pdf, 'w') { |f| f.write(doc) }
|
333
|
+
return pdf
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# ARGV.unshift "samples/test24.erml.erb" unless ARGV.size.nonzero?
|
338
|
+
if $0 == __FILE__ and erml = ARGV.shift and File.exist?(erml)
|
339
|
+
begin
|
340
|
+
pdf = render_erml(erml)
|
341
|
+
`open -a Preview #{pdf}` if pdf and (RUBY_PLATFORM =~ /darwin/) and ($0 !~ /rake_test_loader/ and $0 !~ /rcov/)
|
342
|
+
rescue Exception => e
|
343
|
+
$stderr.puts e.message, e.backtrace.join("\n")
|
344
|
+
end
|
345
|
+
end
|
@@ -0,0 +1,667 @@
|
|
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
|
+
|
8
|
+
module EideticRML
|
9
|
+
module LayoutManagers
|
10
|
+
class LayoutManager
|
11
|
+
def initialize(style)
|
12
|
+
@style = style
|
13
|
+
end
|
14
|
+
|
15
|
+
def row_grid(container)
|
16
|
+
grid = Support::Grid.new(container.children.size, 1)
|
17
|
+
container.children.each_with_index do |widget, index|
|
18
|
+
grid[index, 0] = widget
|
19
|
+
end
|
20
|
+
grid
|
21
|
+
end
|
22
|
+
|
23
|
+
def col_grid(container)
|
24
|
+
grid = Support::Grid.new(1, container.children.size)
|
25
|
+
container.children.each_with_index do |widget, index|
|
26
|
+
grid[0, index] = widget
|
27
|
+
end
|
28
|
+
grid
|
29
|
+
end
|
30
|
+
|
31
|
+
def layout(container, writer)
|
32
|
+
absolute_widgets = container.children.select { |widget| widget.position == :absolute }
|
33
|
+
layout_absolute(container, writer, absolute_widgets)
|
34
|
+
relative_widgets = container.children.select { |widget| widget.position == :relative }
|
35
|
+
layout_relative(container, writer, relative_widgets)
|
36
|
+
container.children.each { |widget| container.root_page.positioned_widgets[widget.position] += 1 if widget.visible and widget.leaf? }
|
37
|
+
# $stderr.puts "+++base+++ #{container.root_page.positioned_widgets[:static]}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def layout_absolute(container, writer, widgets)
|
41
|
+
widgets.each do |widget|
|
42
|
+
widget.before_layout
|
43
|
+
widget.position(:absolute)
|
44
|
+
widget.left(0, :pt) if widget.left.nil? and widget.right.nil?
|
45
|
+
widget.top(0, :pt) if widget.top.nil? and widget.bottom.nil?
|
46
|
+
widget.width(widget.preferred_width(writer), :pt) if widget.width.nil?
|
47
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil? # swapped
|
48
|
+
widget.layout_widget(writer) # swapped
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def layout_relative(container, writer, widgets)
|
53
|
+
widgets.each do |widget|
|
54
|
+
widget.before_layout
|
55
|
+
widget.position(:relative) if widget.position == :static
|
56
|
+
widget.left(0, :pt) if widget.left.nil? and widget.right.nil?
|
57
|
+
widget.top(0, :pt) if widget.top.nil? and widget.bottom.nil?
|
58
|
+
widget.width(widget.preferred_width(writer), :pt) if widget.width.nil?
|
59
|
+
widget.layout_widget(writer)
|
60
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def after_layout(container)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.register(name, klass)
|
68
|
+
(@@klasses ||= {})[name] = klass
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.for_name(name)
|
72
|
+
@@klasses[name] unless @@klasses.nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
def printable_widgets(container, position)
|
77
|
+
dpgno, spgno = container.root.document_page_no, container.root.section_page_no
|
78
|
+
widgets, remaining = container.children.partition do |child|
|
79
|
+
(child.position == :static) and (!child.printed or child.display_for_page(dpgno, spgno))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class AbsoluteLayout < LayoutManager
|
85
|
+
register('absolute', self)
|
86
|
+
|
87
|
+
alias :grid :row_grid
|
88
|
+
|
89
|
+
def layout(container, writer)
|
90
|
+
layout_absolute(container, writer, container.children)
|
91
|
+
end
|
92
|
+
|
93
|
+
def preferred_height(grid, writer)
|
94
|
+
return grid.row(0).empty? ? 0 : nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def preferred_width(grid, writer)
|
98
|
+
return grid.row(0).empty? ? 0 : nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class FlowLayout < LayoutManager
|
103
|
+
register('flow', self)
|
104
|
+
|
105
|
+
alias :grid :row_grid
|
106
|
+
|
107
|
+
def layout(container, writer)
|
108
|
+
cx = cy = max_y = 0
|
109
|
+
container_full = false
|
110
|
+
bottom = container.content_top + container.max_content_height
|
111
|
+
widgets, remaining = printable_widgets(container, :static)
|
112
|
+
remaining.each { |widget| widget.visible = false if widget.printed }
|
113
|
+
widgets.each do |widget|
|
114
|
+
widget.visible = !container_full
|
115
|
+
next if container_full
|
116
|
+
widget.before_layout
|
117
|
+
widget.width([widget.preferred_width(writer) || container.content_width, container.content_width].min, :pt) if widget.width.nil?
|
118
|
+
# puts "flow widget width: #{widget.width} #{widget.path}"
|
119
|
+
if cx != 0 and cx + widget.width > container.content_width
|
120
|
+
cy += max_y + @style.vpadding
|
121
|
+
cx = max_y = 0
|
122
|
+
end
|
123
|
+
widget.left(container.content_left + cx, :pt)
|
124
|
+
widget.top(container.content_top + cy, :pt)
|
125
|
+
widget.height(widget.preferred_height(writer) || 0, :pt) if widget.height.nil? # swapped
|
126
|
+
widget.layout_widget(writer) # swapped
|
127
|
+
# if container.bottom and widget.bottom > container.bottom
|
128
|
+
if widget.bottom > bottom
|
129
|
+
container_full = true
|
130
|
+
# widget.visible = (cy == 0)
|
131
|
+
widget.visible = container.root_page.positioned_widgets[:static] == 0
|
132
|
+
# $stderr.puts "+++flow+++ #{container.root_page.positioned_widgets[:static]}, visible: #{widget.visible}"
|
133
|
+
next
|
134
|
+
end
|
135
|
+
container.root_page.positioned_widgets[widget.position] += 1
|
136
|
+
cx += widget.width + @style.hpadding
|
137
|
+
max_y = [max_y, widget.height].max
|
138
|
+
end
|
139
|
+
container.more(true) if container_full and container.overflow
|
140
|
+
container.height(cy + max_y + container.non_content_height, :pt) if container.height.nil? and max_y > 0
|
141
|
+
super(container, writer)
|
142
|
+
end
|
143
|
+
|
144
|
+
def preferred_height(grid, writer)
|
145
|
+
cells = grid.row(0)
|
146
|
+
return 0 if cells.empty?
|
147
|
+
cell_heights = cells.map { |w| w.preferred_height(writer) }
|
148
|
+
return nil unless cell_heights.all?
|
149
|
+
cell_heights.max
|
150
|
+
end
|
151
|
+
|
152
|
+
def preferred_width(grid, writer)
|
153
|
+
cells = grid.row(0)
|
154
|
+
return 0 if cells.empty?
|
155
|
+
cell_widths = cells.map { |w| w.preferred_width(writer) }
|
156
|
+
return nil unless cell_widths.all?
|
157
|
+
cell_widths.inject((cells.size - 1) * @style.hpadding) { |sum, width| sum + width }
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class HBoxLayout < LayoutManager
|
162
|
+
register('hbox', self)
|
163
|
+
|
164
|
+
alias :grid :row_grid
|
165
|
+
|
166
|
+
def layout(container, writer)
|
167
|
+
container_full = false
|
168
|
+
widgets, remaining = printable_widgets(container, :static)
|
169
|
+
remaining.each { |widget| widget.visible = false if widget.printed }
|
170
|
+
static, relative = widgets.partition { |widget| widget.position == :static }
|
171
|
+
lpanels, unaligned = static.partition { |widget| widget.align == :left }
|
172
|
+
rpanels, unaligned = unaligned.partition { |widget| widget.align == :right }
|
173
|
+
percents, others = static.partition { |widget| widget.width_pct }
|
174
|
+
specified, others = others.partition { |widget| widget.width }
|
175
|
+
|
176
|
+
width_avail = container.content_width
|
177
|
+
|
178
|
+
# allocate specified widths first
|
179
|
+
specified.each do |widget|
|
180
|
+
width_avail -= widget.width
|
181
|
+
container_full = width_avail < 0
|
182
|
+
widget.disabled = container_full
|
183
|
+
width_avail -= @style.hpadding
|
184
|
+
end
|
185
|
+
|
186
|
+
# allocate percent widths next, with a minimum width of 1 point
|
187
|
+
if width_avail - (percents.size - 1) * @style.hpadding >= percents.size
|
188
|
+
width_avail -= (percents.size - 1) * @style.hpadding
|
189
|
+
total_percents = percents.inject(0) { |total, widget| total + widget.width }
|
190
|
+
ratio = width_avail.quo(total_percents)
|
191
|
+
percents.each do |widget|
|
192
|
+
widget.width(widget.width * ratio, :pt) if ratio < 1.0
|
193
|
+
width_avail -= widget.width
|
194
|
+
end
|
195
|
+
else
|
196
|
+
container_full = true
|
197
|
+
percents.each { |widget| widget.disabled = true }
|
198
|
+
end
|
199
|
+
width_avail -= @style.hpadding
|
200
|
+
|
201
|
+
# divide remaining width equally among widgets with unspecified widths
|
202
|
+
if width_avail - (others.size - 1) * @style.hpadding >= others.size
|
203
|
+
width_avail -= (others.size - 1) * @style.hpadding
|
204
|
+
others_width = width_avail.quo(others.size)
|
205
|
+
others.each { |widget| widget.width(others_width, :pt) }
|
206
|
+
else
|
207
|
+
container_full = true
|
208
|
+
others.each { |widget| widget.disabled = true }
|
209
|
+
end
|
210
|
+
|
211
|
+
static.each do |widget|
|
212
|
+
if container.align == :bottom
|
213
|
+
widget.bottom(container.content_bottom, :pt)
|
214
|
+
else
|
215
|
+
container_full = true
|
216
|
+
widget.top(container.content_top, :pt)
|
217
|
+
end
|
218
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil?
|
219
|
+
end
|
220
|
+
left = container.content_left
|
221
|
+
right = container.content_right
|
222
|
+
lpanels.each do |widget|
|
223
|
+
next if widget.disabled
|
224
|
+
widget.left(left, :pt)
|
225
|
+
left += (widget.width + @style.hpadding)
|
226
|
+
end
|
227
|
+
rpanels.reverse.each do |widget|
|
228
|
+
next if widget.disabled
|
229
|
+
widget.right(right, :pt)
|
230
|
+
right -= (widget.width + @style.hpadding)
|
231
|
+
end
|
232
|
+
unaligned.each do |widget|
|
233
|
+
next if widget.disabled
|
234
|
+
widget.left(left, :pt)
|
235
|
+
left += (widget.width + @style.hpadding)
|
236
|
+
end
|
237
|
+
if container.height.nil?
|
238
|
+
content_height = static.map { |widget| widget.height }.max || 0
|
239
|
+
container.height(content_height + container.non_content_height, :pt)
|
240
|
+
end
|
241
|
+
static.each { |widget| widget.layout_widget(writer) if widget.visible and !widget.disabled }
|
242
|
+
super(container, writer)
|
243
|
+
end
|
244
|
+
|
245
|
+
def preferred_height(grid, writer)
|
246
|
+
cells = grid.row(0)
|
247
|
+
return 0 if cells.empty?
|
248
|
+
cell_heights = cells.map { |w| w.preferred_height(writer) }
|
249
|
+
return nil unless cell_heights.all?
|
250
|
+
cell_heights.max
|
251
|
+
end
|
252
|
+
|
253
|
+
def preferred_width(grid, writer)
|
254
|
+
cells = grid.row(0)
|
255
|
+
return 0 if cells.empty?
|
256
|
+
cell_widths = cells.map { |w| w.preferred_width(writer) }
|
257
|
+
return nil unless cell_widths.all?
|
258
|
+
cell_widths.inject((cells.size - 1) * @style.hpadding) { |sum, width| sum + width }
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class VBoxLayout < LayoutManager
|
263
|
+
register('vbox', self)
|
264
|
+
|
265
|
+
alias :grid :col_grid
|
266
|
+
|
267
|
+
def layout(container, writer)
|
268
|
+
# $stderr.puts "layout container: #{container.tag}"
|
269
|
+
container_full = false
|
270
|
+
widgets, remaining = printable_widgets(container, :static)
|
271
|
+
remaining.each { |widget| widget.visible = false if widget.printed }
|
272
|
+
static, relative = widgets.partition { |widget| widget.position == :static }
|
273
|
+
headers, unaligned = static.partition { |widget| widget.align == :top }
|
274
|
+
footers, unaligned = unaligned.partition { |widget| widget.align == :bottom }
|
275
|
+
static.each do |widget|
|
276
|
+
widget.before_layout
|
277
|
+
# puts "<1> vbox widget width: #{widget.width} #{widget.path}"
|
278
|
+
widget.width([widget.preferred_width(writer) || container.content_width, container.content_width].min, :pt) if widget.width.nil?
|
279
|
+
# puts "<2> vbox widget width: #{widget.width} #{widget.path}"
|
280
|
+
widget.left(container.content_left, :pt)
|
281
|
+
end
|
282
|
+
top, dy = container.content_top, 0
|
283
|
+
bottom = container.content_top + container.max_content_height
|
284
|
+
|
285
|
+
headers.each_with_index do |widget, index|
|
286
|
+
widget.top(top, :pt)
|
287
|
+
widget.layout_widget(writer) # swapped
|
288
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil? # swapped
|
289
|
+
top += (widget.height + @style.vpadding)
|
290
|
+
dy += widget.height + ((index > 0) ? @style.vpadding : 0)
|
291
|
+
end
|
292
|
+
headers.each { |widget| widget.visible = (widget.bottom <= bottom) } # or first static widget?
|
293
|
+
|
294
|
+
unless footers.empty?
|
295
|
+
container.height('100%') if container.height.nil?
|
296
|
+
footers.reverse.each do |widget|
|
297
|
+
widget.bottom(bottom, :pt)
|
298
|
+
widget.layout_widget(writer) # swapped
|
299
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil? # swapped
|
300
|
+
bottom -= (widget.height + @style.vpadding)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
footers.each { |widget| widget.visible = (widget.top >= top) } # or first static widget?
|
304
|
+
|
305
|
+
widgets_visible = 0
|
306
|
+
unaligned.each_with_index do |widget, index|
|
307
|
+
widget.visible = !container_full
|
308
|
+
next if container_full
|
309
|
+
widget.top(top, :pt)
|
310
|
+
# puts "<1> vbox widget height: #{widget.height} #{widget.path}"
|
311
|
+
widget.layout_widget(writer) # swapped
|
312
|
+
# puts "<2> vbox widget height: #{widget.height} #{widget.path}"
|
313
|
+
widget.height(widget.preferred_height(writer), :pt) if widget.height.nil? # swapped
|
314
|
+
# puts "<3> vbox widget height: #{widget.height} #{widget.path}"
|
315
|
+
top += widget.height
|
316
|
+
dy += widget.height + (index > 0 ? @style.vpadding : 0) #if widget.visible
|
317
|
+
if top > bottom
|
318
|
+
container_full = true
|
319
|
+
widget.visible = (widgets_visible == 0)
|
320
|
+
# widget.visible = widget.leaves > 0 and container.root_page.positioned_widgets[:static] == 0
|
321
|
+
# $stderr.puts "+++vbox+++ #{container.root_page.positioned_widgets[:static]}, tag: #{widget.tag}, visible: #{widget.visible}"
|
322
|
+
end
|
323
|
+
widgets_visible += 1 if widget.visible
|
324
|
+
top += @style.vpadding
|
325
|
+
end
|
326
|
+
# set_height = container.height.nil?
|
327
|
+
# container.height(container.max_height_avail, :pt) if set_height
|
328
|
+
# unaligned.each_with_index do |widget, index|
|
329
|
+
# # widget.visible = (widget.bottom <= bottom) || (index == 0) #|| (container.overflow && widget.top < bottom)
|
330
|
+
# widget.visible = (widget.bottom <= bottom) || (container.root_page.positioned_widgets[:static] == 0) #|| (container.overflow && widget.top < bottom)
|
331
|
+
# # if widget.visible and widget.bottom > bottom and container.overflow
|
332
|
+
# # widget.layout_widget(writer)
|
333
|
+
# # end
|
334
|
+
# end
|
335
|
+
|
336
|
+
container_full = unaligned.last && !unaligned.last.visible
|
337
|
+
container.more(true) if container_full and container.overflow
|
338
|
+
# container.height(top - container.content_top + @style.vpadding, :pt) if container.height.nil?
|
339
|
+
# container.height(dy + container.non_content_height, :pt) if container.height.nil?
|
340
|
+
super(container, writer)
|
341
|
+
end
|
342
|
+
|
343
|
+
def preferred_height(grid, writer)
|
344
|
+
cells = grid.col(0)
|
345
|
+
return 0 if cells.empty?
|
346
|
+
cell_heights = cells.map { |w| w.preferred_height(writer) }
|
347
|
+
return nil unless cell_heights.all?
|
348
|
+
cell_heights.inject((cells.size - 1) * @style.vpadding) { |sum, height| sum + height }
|
349
|
+
end
|
350
|
+
|
351
|
+
def preferred_width(grid, writer)
|
352
|
+
cells = grid.col(0)
|
353
|
+
return 0 if cells.empty?
|
354
|
+
cell_widths = cells.map { |w| w.preferred_width(writer) }
|
355
|
+
return nil unless cell_widths.all?
|
356
|
+
cell_widths.max
|
357
|
+
end
|
358
|
+
|
359
|
+
# def after_layout(container)
|
360
|
+
# container.children.each do |widget|
|
361
|
+
# if widget.visible and widget.position == :static
|
362
|
+
# if widget.bottom > container.content_bottom
|
363
|
+
# widget.disabled = !container.overflow
|
364
|
+
# end
|
365
|
+
# # widget.after_layout if widget.visible
|
366
|
+
# end
|
367
|
+
# end
|
368
|
+
# end
|
369
|
+
end
|
370
|
+
|
371
|
+
class TableLayout < LayoutManager
|
372
|
+
register('table', self)
|
373
|
+
|
374
|
+
private
|
375
|
+
ROW_SPAN = 0
|
376
|
+
COL_SPAN = 0
|
377
|
+
ROW_HEIGHT = 1
|
378
|
+
COL_WIDTH = 1
|
379
|
+
def mark_grid(grid, a, b, c, d, value)
|
380
|
+
c.times do |aa|
|
381
|
+
d.times do |bb|
|
382
|
+
grid[a + aa, b + bb] = value if aa > 0 or bb > 0
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def row_grid(container)
|
388
|
+
raise ArgumentError, "cols must be specified." if container.cols.nil?
|
389
|
+
static = printable_widgets(container, :static).first
|
390
|
+
grid = Support::Grid.new(container.cols, 0)
|
391
|
+
row = col = 0
|
392
|
+
static.each do |widget|
|
393
|
+
while grid[col, row] == false
|
394
|
+
col += 1
|
395
|
+
if col >= container.cols then row += 1; col = 0 end
|
396
|
+
end
|
397
|
+
grid[col, row] = widget
|
398
|
+
mark_grid(grid, col, row, widget.colspan, widget.rowspan, false)
|
399
|
+
col += widget.colspan
|
400
|
+
raise ArgumentError, "colspan causes number of columns to exceed table size." if col > container.cols
|
401
|
+
if col == container.cols then row += 1; col = 0 end
|
402
|
+
end
|
403
|
+
grid
|
404
|
+
end
|
405
|
+
|
406
|
+
def col_grid(container)
|
407
|
+
raise ArgumentError, "rows must be specified." if container.rows.nil?
|
408
|
+
static = printable_widgets(container, :static).first
|
409
|
+
grid = Support::Grid.new(0, container.rows)
|
410
|
+
row = col = 0
|
411
|
+
static.each do |widget|
|
412
|
+
while grid[col, row] == false
|
413
|
+
row += 1
|
414
|
+
if row >= container.rows then col += 1; row = 0 end
|
415
|
+
end
|
416
|
+
if row >= container.rows then col += 1; row = 0 end
|
417
|
+
grid[col, row] = widget
|
418
|
+
mark_grid(grid, col, row, widget.colspan, widget.rowspan, false)
|
419
|
+
row += widget.rowspan
|
420
|
+
raise ArgumentError, "rowspan causes number of rows to exceed table size." if row > container.rows
|
421
|
+
end
|
422
|
+
grid
|
423
|
+
end
|
424
|
+
|
425
|
+
def detect_widths(grid, writer)
|
426
|
+
widths = []
|
427
|
+
grid.cols.times do |c|
|
428
|
+
col = grid.col(c)
|
429
|
+
widget = col.detect { |w| w and (w.colspan == 1) }
|
430
|
+
if widget.nil?
|
431
|
+
widths << [:unspecified, 0]
|
432
|
+
elsif widget.width_pct
|
433
|
+
widths << [:percent, widget.width]
|
434
|
+
elsif widget.width
|
435
|
+
widths << [:specified, widget.width]
|
436
|
+
else
|
437
|
+
widths << [:unspecified, col.map { |w| w ? w.preferred_width(writer) : 0 }.max]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
widths
|
441
|
+
end
|
442
|
+
|
443
|
+
def allocate_specified_widths(width_avail, specified)
|
444
|
+
specified.each do |w|
|
445
|
+
if width_avail < w[COL_WIDTH]
|
446
|
+
# w[COL_WIDTH] = 0
|
447
|
+
else
|
448
|
+
width_avail -= (w[COL_WIDTH] + @style.hpadding)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
width_avail
|
452
|
+
end
|
453
|
+
|
454
|
+
def allocate_percent_widths(width_avail, percents)
|
455
|
+
# allocate percent widths with a minimum width of 1 point
|
456
|
+
if width_avail - (percents.size - 1) * @style.hpadding >= percents.size
|
457
|
+
width_avail -= (percents.size - 1) * @style.hpadding
|
458
|
+
total_percents = percents.inject(0) { |total, w| total + w[COL_WIDTH] }
|
459
|
+
ratio = width_avail.quo(total_percents)
|
460
|
+
percents.each do |w|
|
461
|
+
w[COL_WIDTH] = w[COL_WIDTH] * ratio if ratio < 1.0
|
462
|
+
width_avail -= w[COL_WIDTH]
|
463
|
+
end
|
464
|
+
else
|
465
|
+
percents.each { |w| w[COL_WIDTH] = 0 }
|
466
|
+
end
|
467
|
+
width_avail -= @style.hpadding
|
468
|
+
width_avail
|
469
|
+
end
|
470
|
+
|
471
|
+
def allocate_other_widths(width_avail, others)
|
472
|
+
# divide remaining width equally among widgets with unspecified widths
|
473
|
+
if width_avail - (others.size - 1) * @style.hpadding >= others.size
|
474
|
+
width_avail -= (others.size - 1) * @style.hpadding
|
475
|
+
others_width = width_avail.quo(others.size)
|
476
|
+
others.each { |w| w[COL_WIDTH] = others_width }
|
477
|
+
else
|
478
|
+
others.each { |w| w[COL_WIDTH] = 0 }
|
479
|
+
end
|
480
|
+
width_avail
|
481
|
+
end
|
482
|
+
|
483
|
+
def layout_grid(grid, container, writer)
|
484
|
+
container_full = false
|
485
|
+
widths = detect_widths(grid, writer)
|
486
|
+
if container.width.nil?
|
487
|
+
puts "Noooooooo!!!!"
|
488
|
+
end
|
489
|
+
percents, others = widths.partition { |w| w[0] == :percent }
|
490
|
+
specified, others = others.partition { |w| w[0] == :specified }
|
491
|
+
|
492
|
+
width_avail = container.content_width
|
493
|
+
width_avail = allocate_specified_widths(width_avail, specified)
|
494
|
+
width_avail = allocate_percent_widths(width_avail, percents)
|
495
|
+
width_avail = allocate_other_widths(width_avail, others)
|
496
|
+
|
497
|
+
heights = Support::Grid.new(grid.cols, grid.rows)
|
498
|
+
grid.cols.times do |c|
|
499
|
+
grid.col(c).each_with_index do |widget, r|
|
500
|
+
next unless widget
|
501
|
+
if widths[c][COL_WIDTH] > 0
|
502
|
+
width = (0...widget.colspan).inject(0) { |width, i| width + widths[c + i][COL_WIDTH] }
|
503
|
+
widget.width(width + (widget.colspan - 1) * @style.hpadding, :pt)
|
504
|
+
else
|
505
|
+
# widget.visible = false
|
506
|
+
widget.disabled = true
|
507
|
+
next
|
508
|
+
end
|
509
|
+
heights[c, r] = [widget.rowspan, widget.height || widget.preferred_height(writer)]
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
heights.rows.times do |r|
|
514
|
+
row_heights = (0...heights.cols).map { |c| heights[c,r] }.compact
|
515
|
+
min_rowspan = row_heights.map { |rowspan, height| rowspan }.min
|
516
|
+
min_rowspan_heights = row_heights.select { |rowspan, height| rowspan == min_rowspan }
|
517
|
+
max_height = min_rowspan_heights.map { |rowspan, height| height }.max
|
518
|
+
heights.cols.times do |c|
|
519
|
+
rh = heights[c,r]
|
520
|
+
next if rh.nil?
|
521
|
+
if rh[ROW_SPAN] > min_rowspan
|
522
|
+
heights[c,r+1] = [rh[ROW_SPAN] - 1, [rh[ROW_HEIGHT] - max_height, 0].max]
|
523
|
+
end
|
524
|
+
rh[ROW_HEIGHT] = max_height
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
top = container.content_top
|
529
|
+
bottom = container.content_top + container.max_content_height
|
530
|
+
grid.rows.times do |r|
|
531
|
+
max_height = 0
|
532
|
+
left = container.content_left
|
533
|
+
grid.cols.times do |c|
|
534
|
+
if widget = grid[c, r]
|
535
|
+
widget.visible = !container_full
|
536
|
+
next if container_full
|
537
|
+
rh = heights[c,r]
|
538
|
+
next if rh.nil?
|
539
|
+
widget.top(top, :pt)
|
540
|
+
widget.left(left, :pt)
|
541
|
+
height = (0...rh[ROW_SPAN]).inject((rh[ROW_SPAN] - 1) * @style.vpadding) { |height, row_offset| height + heights[c,r+row_offset][ROW_HEIGHT] }
|
542
|
+
widget.height(height, :pt)
|
543
|
+
max_height = [max_height, rh[ROW_HEIGHT]].max if rh[ROW_SPAN] == 1
|
544
|
+
end
|
545
|
+
left += widths[c][1] + @style.hpadding
|
546
|
+
end
|
547
|
+
next if container_full
|
548
|
+
if top + max_height > bottom
|
549
|
+
container_full = true
|
550
|
+
grid.cols.times { |c| grid[c, r].visible = (r == 0) if grid[c, r] }
|
551
|
+
container.more(true) if container.overflow and (r > 0)
|
552
|
+
end
|
553
|
+
top += max_height + @style.vpadding unless container_full
|
554
|
+
end
|
555
|
+
if container.height.nil?
|
556
|
+
container.height(top - container.content_top + container.non_content_height - @style.vpadding, :pt)
|
557
|
+
end
|
558
|
+
static, remaining = printable_widgets(container, :static)
|
559
|
+
remaining.each { |widget| widget.visible = false if widget.printed }
|
560
|
+
static.each { |widget| widget.layout_widget(writer) }
|
561
|
+
end
|
562
|
+
|
563
|
+
public
|
564
|
+
def grid(container)
|
565
|
+
if container.order == :rows
|
566
|
+
row_grid(container)
|
567
|
+
else # container.order == :cols
|
568
|
+
col_grid(container)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def layout(container, writer)
|
573
|
+
layout_grid(grid(container), container, writer)
|
574
|
+
super(container, writer)
|
575
|
+
end
|
576
|
+
|
577
|
+
def preferred_height(grid, writer)
|
578
|
+
# calculate preferred heights, where available
|
579
|
+
heights = Support::Grid.new(grid.cols, grid.rows)
|
580
|
+
return 0 if heights.cols == 0 or heights.rows == 0
|
581
|
+
grid.cols.times do |c|
|
582
|
+
grid.col(c).each_with_index do |widget, r|
|
583
|
+
next unless widget
|
584
|
+
# heights[c, r] = [widget.rowspan, widget.has_height? ? widget.preferred_height(writer) : nil]
|
585
|
+
heights[c, r] = [widget.rowspan, widget.preferred_height(writer)]
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
heights.rows.times do |r|
|
590
|
+
row_heights = (0...heights.cols).map { |c| heights[c,r] }.compact
|
591
|
+
min_rowspan = row_heights.map { |rowspan, height| rowspan }.min
|
592
|
+
min_rowspan_heights = row_heights.select { |rowspan, height| rowspan == min_rowspan }
|
593
|
+
max_height = min_rowspan_heights.map { |rowspan, height| height }.compact.max
|
594
|
+
# at least one cell must specify a height
|
595
|
+
return nil if max_height.nil?
|
596
|
+
heights.cols.times do |c|
|
597
|
+
rh = heights[c,r]
|
598
|
+
next if rh.nil?
|
599
|
+
# carry height in excess of max height of cells with min_rowspan to cell in next row, subtracting vpadding
|
600
|
+
if rh[ROW_SPAN] > min_rowspan
|
601
|
+
heights[c,r+1] = [rh[ROW_SPAN] - 1, [rh[ROW_HEIGHT] - max_height - @style.vpadding, 0].max]
|
602
|
+
end
|
603
|
+
rh[ROW_HEIGHT] = max_height
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
result = 0
|
608
|
+
grid.rows.times do |r|
|
609
|
+
max_height = 0
|
610
|
+
grid.cols.times do |c|
|
611
|
+
if (widget = grid[c, r]) and (rh = heights[c,r])
|
612
|
+
height = (0...rh[ROW_SPAN]).inject((rh[ROW_SPAN] - 1) * @style.vpadding) { |height, row_offset| height + heights[c,r+row_offset][ROW_HEIGHT] }
|
613
|
+
max_height = [max_height, rh[ROW_HEIGHT]].max if rh[ROW_SPAN] == 1
|
614
|
+
end
|
615
|
+
end
|
616
|
+
result += max_height + @style.vpadding
|
617
|
+
end
|
618
|
+
result -= @style.vpadding if result > 0
|
619
|
+
end
|
620
|
+
|
621
|
+
def preferred_width(grid, writer)
|
622
|
+
# calculate preferred widths, where available
|
623
|
+
widths = Support::Grid.new(grid.cols, grid.rows)
|
624
|
+
return 0 if widths.cols == 0 or widths.rows == 0
|
625
|
+
grid.rows.times do |r|
|
626
|
+
grid.row(r).each_with_index do |widget, c|
|
627
|
+
next unless widget
|
628
|
+
# widths[c, r] = [widget.colspan, widget.has_width? ? widget.preferred_width(writer) : nil]
|
629
|
+
widths[c, r] = [widget.colspan, widget.preferred_width(writer)]
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
widths.cols.times do |c|
|
634
|
+
col_widths = (0...widths.rows).map { |r| widths[c,r] }.compact
|
635
|
+
min_colspan = col_widths.map { |colspan, width| colspan }.min
|
636
|
+
min_colspan_widths = col_widths.select { |colspan, width| colspan == min_colspan }
|
637
|
+
max_width = min_colspan_widths.map { |colspan, width| width }.compact.max
|
638
|
+
# at least one cell must specify a width
|
639
|
+
return nil if max_width.nil?
|
640
|
+
widths.rows.times do |r|
|
641
|
+
cw = widths[c,r]
|
642
|
+
next if cw.nil?
|
643
|
+
# carry width in excess of max width of cells with min_colspan to cell in next col, subtracting hpadding
|
644
|
+
if cw[COL_SPAN] > min_colspan
|
645
|
+
widths[c+1,r] = [cw[COL_SPAN] - 1, [cw[COL_WIDTH] - max_width - @style.hpadding, 0].max]
|
646
|
+
end
|
647
|
+
cw[COL_WIDTH] = max_width
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
result = 0
|
652
|
+
grid.cols.times do |c|
|
653
|
+
max_width = 0
|
654
|
+
grid.rows.times do |r|
|
655
|
+
if (widget = grid[c, r]) and (cw = widths[c, r])
|
656
|
+
width = (0...cw[COL_SPAN]).inject((cw[COL_SPAN] - 1) * @style.hpadding) { |width, col_offset| width + widths[c+col_offset,r][COL_WIDTH] }
|
657
|
+
max_width = [max_width, cw[COL_WIDTH]].max if cw[COL_SPAN] == 1
|
658
|
+
end
|
659
|
+
end
|
660
|
+
result += max_width + @style.hpadding
|
661
|
+
end
|
662
|
+
result -= @style.hpadding if result > 0
|
663
|
+
result
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|