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