hexapdf 0.21.1 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9792b3c8be5fd3c3521aa5fe1a66e1315f677494f199e3e8b0e1eb85c9b967c7
4
- data.tar.gz: a66f1d6751cac4ca2fc6a35bf493ba60eefee96edca090ae92ed810b756d8761
3
+ metadata.gz: 1388a344a539c7273603549e014c635c4864af4de8665a260b15ac66d19ac461
4
+ data.tar.gz: 471e0f4933ac37348ac5ed217446031e742099de26773b25bb23c49fc8480a05
5
5
  SHA512:
6
- metadata.gz: 111570ec411684ffe5214833419128b697721c4e5fdd16b61578c3d459a8ab9dde411780dd582e06620d52a260062df30a116b7c3c8544647f5e61f131c6bdc5
7
- data.tar.gz: 3d098cb4098f6a02bc832cbe70aebc0b28a40a21b33b53c8e8e055f240fd513a5d96f53846c62ebbc2a00d17d6f99271dd3850e98a70cebb6f32e1a7e279cc87
6
+ metadata.gz: 0ab8c16c85475e68f12faea47f8dbf7e167ebdc864d11fb1151e107af1d3678b867ad0d7a6184155e1b55ed1469d3e7443f39184e0250974f5a7df9a920adb99
7
+ data.tar.gz: 43c80b555018068baf314d6ed7d6a03ae0a359f2e8be498341985ecb75879fd414d066d7c356b47fb896ec0e4aa74c2044f6a5e7965922de1136ddacf409765f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 0.22.0 - 2022-03-26
2
+
3
+ ### Added
4
+
5
+ - Support for writing images with an ICCBased color space
6
+ - Support for writing images with soft masks
7
+
8
+ ### Changed
9
+
10
+ - CLI command `hexapdf form` to show a warning when working with a file
11
+ containing an XFA form
12
+
13
+ ### Fixed
14
+
15
+ - [HexaPDF::Type::AcroForm::Form#field_by_name] to work correctly when field
16
+ name parts are UTF-16BE encoded
17
+ - `hexapdf inspect` command 'revision' to correctly detect the end of revisions
18
+ - [HexaPDF::DictionaryFields::StringConverter] to use correct method name
19
+ `HexaPDF::Document#config`
20
+
21
+
1
22
  ## 0.21.1 - 2022-03-12
2
23
 
3
24
  ### Fixed
@@ -97,6 +97,10 @@ module HexaPDF
97
97
  end
98
98
  with_document(in_file, password: @password, out_file: out_file,
99
99
  incremental: @incremental) do |doc|
100
+ if doc.acro_form[:XFA]
101
+ $stderr.puts "Warning: Unsupported XFA form detected, some things may not work correctly"
102
+ end
103
+
100
104
  if !doc.acro_form
101
105
  raise "This PDF doesn't contain an interactive form"
102
106
  elsif out_file
@@ -342,10 +342,14 @@ module HexaPDF
342
342
  end_index = sig[:ByteRange][-2] + sig[:ByteRange][-1]
343
343
  else
344
344
  io.seek(startxrefs[index], IO::SEEK_SET)
345
+ buffer = ''.b
345
346
  while io.pos < startxrefs[index + 1]
346
- if io.gets =~ /^\s*%%EOF\s*$/
347
- end_index = io.pos
347
+ buffer << io.read(1_000)
348
+ if (buffer_index = buffer.index(/(?:\n|\r\n?)\s*%%EOF\s*(?:\n|\r\n?)/))
349
+ end_index = io.pos - buffer.size + buffer_index + $~[0].size
350
+ break
348
351
  end
352
+ buffer = buffer[-20..-1]
349
353
  end
350
354
  end
351
355
  yield(rev, index, rev.next_free_oid - 1, sig, end_index)
@@ -241,7 +241,7 @@ module HexaPDF
241
241
  if str.valid_encoding?
242
242
  str.encode!(Encoding::UTF_8)
243
243
  else
244
- document.configuration['document.on_invalid_string'].call(str)
244
+ document.config['document.on_invalid_string'].call(str)
245
245
  end
246
246
  else
247
247
  Utils::PDFDocEncoding.convert_to_utf8(str)
@@ -139,13 +139,19 @@ module HexaPDF
139
139
  def field_by_name(name)
140
140
  fields = root_fields
141
141
  field = nil
142
+
142
143
  name.split('.').each do |part|
143
- field = fields&.find {|f| f[:T] == part }
144
- break unless field
145
- field = document.wrap(field, type: :XXAcroFormField,
146
- subtype: Field.inherited_value(field, :FT))
147
- fields = field[:Kids] unless field.terminal_field?
144
+ field = nil
145
+ fields&.each do |f|
146
+ f = document.wrap(f, type: :XXAcroFormField,
147
+ subtype: Field.inherited_value(f, :FT))
148
+ next unless f[:T] == part
149
+ field = f
150
+ fields = field[:Kids] unless field.terminal_field?
151
+ break
152
+ end
148
153
  end
154
+
149
155
  field
150
156
  end
151
157
 
@@ -163,13 +163,23 @@ module HexaPDF
163
163
  result.color_space = :cmyk
164
164
  result.components = 4
165
165
  result.writable = false if result.type == :png
166
+ when :ICCBased
167
+ result.color_space = :icc
168
+ result.components = self[:ColorSpace][1][:N]
169
+ result.writable = false if result.type == :png && result.components == 4
166
170
  else
167
171
  result.color_space = :other
168
172
  result.components = -1
169
173
  result.writable = false if result.type == :png
170
174
  end
171
175
 
172
- result.writable = false if self[:SMask]
176
+ smask = self[:SMask]
177
+ if smask && (result.type != :png ||
178
+ !(result.bits_per_component == 8 || result.bits_per_component == 16) ||
179
+ result.bits_per_component != smask[:BitsPerComponent] ||
180
+ result.width != smask[:Width] || result.height != smask[:Height])
181
+ result.writable = false
182
+ end
173
183
 
174
184
  result
175
185
  end
@@ -224,10 +234,18 @@ module HexaPDF
224
234
  ImageLoader::PNG::INDEXED
225
235
  elsif info.color_space == :rgb
226
236
  ImageLoader::PNG::TRUECOLOR
237
+ elsif info.color_space == :icc
238
+ info.components == 3 ? ImageLoader::PNG::TRUECOLOR : ImageLoader::PNG::GREYSCALE
227
239
  else
228
240
  ImageLoader::PNG::GREYSCALE
229
241
  end
230
242
 
243
+ if self[:SMask] && color_type != ImageLoader::PNG::INDEXED
244
+ color_type += 4 # change it to TrueColor/Greyscale with Alpha
245
+ end
246
+
247
+ flate_decode = config.constantize('filter.map', :FlateDecode)
248
+
231
249
  io << png_chunk('IHDR', [info.width, info.height, info.bits_per_component,
232
250
  color_type, 0, 0, 0].pack('N2C5'))
233
251
 
@@ -239,6 +257,12 @@ module HexaPDF
239
257
  png_chunk('cHRM', [31270, 32900, 64000, 33000, 30000, 60000, 15000, 6000].pack('N8'))
240
258
  end
241
259
 
260
+ if info.color_space == :icc
261
+ _, stream = *self[:ColorSpace]
262
+ data = flate_decode.encoder(stream.stream_decoder)
263
+ io << png_chunk('iCCP', "ICCProfile\x00\x00".b << Filter.string_from_source(data))
264
+ end
265
+
242
266
  if color_type == ImageLoader::PNG::INDEXED
243
267
  palette_data = self[:ColorSpace][3]
244
268
  palette_data = palette_data.stream unless palette_data.kind_of?(String)
@@ -258,11 +282,14 @@ module HexaPDF
258
282
 
259
283
  filter, = *self[:Filter]
260
284
  decode_parms, = *self[:DecodeParms]
261
- if filter == :FlateDecode && decode_parms && decode_parms[:Predictor].to_i >= 10
285
+ if self[:SMask]
286
+ data = flate_decode.encoder(Fiber.new { png_combine_image_and_soft_mask(info) }, Predictor: 15,
287
+ Colors: info.components + 1, Columns: info.width,
288
+ BitsPerComponent: info.bits_per_component)
289
+ elsif filter == :FlateDecode && decode_parms && decode_parms[:Predictor].to_i >= 10
262
290
  data = stream_source
263
291
  else
264
292
  colors = (color_type == ImageLoader::PNG::INDEXED ? 1 : info.components)
265
- flate_decode = config.constantize('filter.map', :FlateDecode)
266
293
  data = flate_decode.encoder(stream_decoder, Predictor: 15,
267
294
  Colors: colors, Columns: info.width,
268
295
  BitsPerComponent: info.bits_per_component)
@@ -277,6 +304,23 @@ module HexaPDF
277
304
  [data.length].pack("N") << type << data << [Zlib.crc32(data, Zlib.crc32(type))].pack("N")
278
305
  end
279
306
 
307
+ # Combines the image data with the soft mask data as needed for a PNG data stream.
308
+ def png_combine_image_and_soft_mask(info)
309
+ bytes_per_colors = info.bits_per_component * info.components / 8
310
+ bytes_per_alpha = info.bits_per_component / 8
311
+ image_data = stream
312
+ mask_data = self[:SMask].stream
313
+
314
+ data = ''.b
315
+ ii = im = 0
316
+ while ii < image_data.length
317
+ data << image_data[ii, bytes_per_colors] << mask_data[im, bytes_per_alpha]
318
+ ii += bytes_per_colors
319
+ im += bytes_per_alpha
320
+ end
321
+ data
322
+ end
323
+
280
324
  end
281
325
 
282
326
  end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.21.1'
40
+ VERSION = '0.22.0'
41
41
 
42
42
  end
@@ -134,7 +134,7 @@ describe HexaPDF::DictionaryFields do
134
134
  assert_equal(Encoding::UTF_8, str.encoding)
135
135
  end
136
136
 
137
- def configuration
137
+ def config
138
138
  HexaPDF::Configuration.with_defaults
139
139
  end
140
140
 
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.21.1)>>
43
+ <</Producer(HexaPDF version 0.22.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.21.1)>>
75
+ <</Producer(HexaPDF version 0.22.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -86,7 +86,8 @@ describe HexaPDF::Type::AcroForm::Form do
86
86
  before do
87
87
  @acro_form[:Fields] = [
88
88
  {T: "root only", Kids: [{Subtype: :Widget}]},
89
- {T: "children", Kids: [{T: "child", FT: :Btn}, {T: "sub", Kids: [{T: "child"}]}]},
89
+ {T: "children", Kids: [{T: "\xFE\xFF".b << "child".encode('UTF-16BE').b, FT: :Btn},
90
+ {T: "sub", Kids: [{T: "child"}]}]},
90
91
  ]
91
92
  end
92
93
 
@@ -115,16 +115,42 @@ describe HexaPDF::Type::Image do
115
115
  assert(info.indexed)
116
116
  assert(info.writable)
117
117
 
118
- @image[:ColorSpace] = :ICCBased
118
+ @image[:ColorSpace] = [:ICCBased, {N: 3}]
119
119
  info = @image.info
120
- assert_equal(:other, info.color_space)
121
- assert_equal(-1, info.components)
120
+ assert_equal(:icc, info.color_space)
121
+ assert_equal(3, info.components)
122
+ assert(info.writable)
123
+ @image[:ColorSpace] = [:ICCBased, {N: 4}]
124
+ info = @image.info
125
+ refute(info.writable)
122
126
  end
123
127
 
124
128
  it "processes the SMask entry" do
125
- @image[:SMask] = :something
126
- info = @image.info
127
- refute(info.writable)
129
+ @image[:SMask] = {BitsPerComponent: 8, Width: 10, Height: 5}
130
+
131
+ @image[:BitsPerComponent] = 8
132
+ assert(@image.info.writable)
133
+
134
+ @image[:BitsPerComponent] = 16
135
+ refute(@image.info.writable)
136
+ @image[:SMask][:BitsPerComponent] = 16
137
+ assert(@image.info.writable)
138
+
139
+ @image[:BitsPerComponent] = 4
140
+ refute(@image.info.writable)
141
+ @image[:SMask][:BitsPerComponent] = 4
142
+ refute(@image.info.writable)
143
+
144
+ @image[:BitsPerComponent] = @image[:SMask][:BitsPerComponent] = 8
145
+ @image[:SMask][:Width] = 8
146
+ refute(@image.info.writable)
147
+
148
+ @image[:SMask][:Width] = 10
149
+ @image[:SMask][:Height] = 8
150
+ refute(@image.info.writable)
151
+
152
+ @image[:SMask][:Height] = 5
153
+ assert(@image.info.writable)
128
154
  end
129
155
  end
130
156
 
@@ -184,7 +210,7 @@ describe HexaPDF::Type::Image do
184
210
  end
185
211
 
186
212
  Dir.glob(File.join(TEST_DATA_DIR, 'images', '*.png')).each do |png_file|
187
- next if png_file =~ /alpha/
213
+ next if png_file =~ /indexed-alpha/
188
214
  it "writes #{File.basename(png_file)} correctly as PNG file" do
189
215
  image = @doc.images.add(png_file)
190
216
  if png_file =~ /greyscale-1bit.png/ # force use of arrays for one image
@@ -208,6 +234,7 @@ describe HexaPDF::Type::Image do
208
234
  else
209
235
  assert_nil(new_image[:Mask], "file: #{png_file}")
210
236
  end
237
+ assert(new_image[:SMask]) if png_file =~ /alpha/
211
238
  assert_equal(image.stream, new_image.stream, "file: #{png_file}")
212
239
 
213
240
  # ColorSpace is currently not always preserved, e.g. with CalRGB
@@ -236,6 +263,15 @@ describe HexaPDF::Type::Image do
236
263
  assert_equal(image.stream, new_image.stream)
237
264
  end
238
265
 
266
+ it "works for images with an ICCBased color space" do
267
+ image = @doc.add({Type: :XObject, Subtype: :Image, Width: 2, Height: 2, BitsPerComponent: 2,
268
+ ColorSpace: [:ICCBased, @doc.wrap({}, stream: 'abcd')]})
269
+ image.stream = HexaPDF::StreamData.new(filter: :ASCIIHexDecode) { "10 B0".b }
270
+ image.write(@file.path)
271
+ assert_valid_png(@file.path)
272
+ assert_match(/iCCPICCProfile\x00\x00/, File.binread(@file.path))
273
+ end
274
+
239
275
  it "fails if an unsupported stream filter is used" do
240
276
  image = @doc.images.add(@jpg)
241
277
  image.set_filter([:DCTDecode, :ASCIIHexDecode])
@@ -244,13 +280,13 @@ describe HexaPDF::Type::Image do
244
280
 
245
281
  it "fails if an unsupported colorspace is used" do
246
282
  image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, BitsPerComponent: 8,
247
- ColorSpace: :ICCBased})
283
+ ColorSpace: :Unknown})
248
284
  assert_raises(HexaPDF::Error) { image.write(@file) }
249
285
  end
250
286
 
251
287
  it "fails if an indexed image with an unsupported colorspace is used" do
252
288
  image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, BitsPerComponent: 8,
253
- ColorSpace: [:Indexed, :ICCBased, 0, "0"]})
289
+ ColorSpace: [:Indexed, :Unknown, 0, "0"]})
254
290
  assert_raises(HexaPDF::Error) { image.write(@file) }
255
291
  end
256
292
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.1
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-12 00:00:00.000000000 Z
11
+ date: 2022-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -217,7 +217,6 @@ files:
217
217
  - examples/017-frame_text_flow.rb
218
218
  - examples/018-composer.rb
219
219
  - examples/019-acro_form.rb
220
- - examples/020-column_box.rb
221
220
  - examples/emoji-smile.png
222
221
  - examples/emoji-wink.png
223
222
  - examples/machupicchu.jpg
@@ -334,7 +333,6 @@ files:
334
333
  - lib/hexapdf/importer.rb
335
334
  - lib/hexapdf/layout.rb
336
335
  - lib/hexapdf/layout/box.rb
337
- - lib/hexapdf/layout/column_box.rb
338
336
  - lib/hexapdf/layout/frame.rb
339
337
  - lib/hexapdf/layout/image_box.rb
340
338
  - lib/hexapdf/layout/inline_box.rb
@@ -673,7 +671,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
673
671
  - !ruby/object:Gem::Version
674
672
  version: '0'
675
673
  requirements: []
676
- rubygems_version: 3.3.3
674
+ rubygems_version: 3.2.32
677
675
  signing_key:
678
676
  specification_version: 4
679
677
  summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
@@ -1,57 +0,0 @@
1
- # ## Column Box
2
- #
3
- # This example shows how [HexaPDF::Layout::Frame] and [HexaPDF::Layout::TextBox]
4
- # can be used to flow text around objects.
5
- #
6
- # Three boxes are placed repeatedly onto the frame until it is filled: two
7
- # floating boxes (one left, one right) and a text box. The text box is styled to
8
- # flow its content around the other two boxes.
9
- #
10
- # Usage:
11
- # : `ruby frame_text_flow.rb`
12
- #
13
-
14
- require 'hexapdf'
15
- require 'hexapdf/utils/graphics_helpers'
16
-
17
- include HexaPDF::Layout
18
- include HexaPDF::Utils::GraphicsHelpers
19
-
20
- doc = HexaPDF::Document.new
21
-
22
- sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
23
- adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
24
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
25
- ullamco laboris nisi ut aliquip ex ea commodo consequat.
26
- ".tr("\n", ' ') * 5
27
- items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
28
-
29
- page = doc.pages.add
30
- media_box = page.box(:media)
31
- canvas = page.canvas
32
- frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
33
- media_box.width - 40, media_box.height - 40)
34
-
35
- image = doc.images.add(File.join(__dir__, 'machupicchu.jpg'))
36
- iw, ih = calculate_dimensions(image.width, image.height, rwidth: 100)
37
-
38
- boxes = []
39
- 3.times do
40
- boxes << Box.create(width: iw, height: ih,
41
- margin: [10, 30], position: :float) do |canv, box|
42
- canv.image(image, at: [0, 0], width: 100)
43
- end
44
- boxes << Box.create(width: 50, height: 50, margin: 20,
45
- position: :float, position_hint: :right,
46
- border: {width: 1, color: [[255, 0, 0]]})
47
- boxes << TextBox.new(items, style: {position: :flow, align: :justify})
48
- end
49
- columns = ColumnBox.new(boxes) #, style: {position: :flow})
50
- polygon = Geom2D::Polygon([250, 350], [350, 350], [350, 500], [250, 500])
51
- #frame.remove_area(polygon)
52
- #canvas.draw(:geom2d, object: polygon)
53
- result = frame.fit(columns)
54
- p result.success?
55
- frame.draw(canvas, result)
56
-
57
- doc.write("column_box.pdf", optimize: true)
@@ -1,168 +0,0 @@
1
- # -*- encoding: utf-8; frozen_string_literal: true -*-
2
- #
3
- #--
4
- # This file is part of HexaPDF.
5
- #
6
- # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2022 Thomas Leitner
8
- #
9
- # HexaPDF is free software: you can redistribute it and/or modify it
10
- # under the terms of the GNU Affero General Public License version 3 as
11
- # published by the Free Software Foundation with the addition of the
12
- # following permission added to Section 15 as permitted in Section 7(a):
13
- # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
- # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
- # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
- #
17
- # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
- # License for more details.
21
- #
22
- # You should have received a copy of the GNU Affero General Public License
23
- # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
- #
25
- # The interactive user interfaces in modified source and object code
26
- # versions of HexaPDF must display Appropriate Legal Notices, as required
27
- # under Section 5 of the GNU Affero General Public License version 3.
28
- #
29
- # In accordance with Section 7(b) of the GNU Affero General Public
30
- # License, a covered work must retain the producer line in every PDF that
31
- # is created or manipulated using HexaPDF.
32
- #
33
- # If the GNU Affero General Public License doesn't fit your need,
34
- # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
- #++
36
- require 'hexapdf/layout/box'
37
-
38
- module HexaPDF
39
- module Layout
40
-
41
- # A ColumnBox arranges boxes in one or more columns.
42
- #
43
- # The number of columns as well as the size of the gap between the columns can be modified.
44
- class ColumnBox < Box
45
-
46
- # The child boxes of this ColumnBox.
47
- attr_reader :children
48
-
49
- # The number of columns.
50
- # TODO: allow array with column widths later like [100, :*, :*]; same for gaps
51
- attr_reader :columns
52
-
53
- # The size of the gap between the columns.
54
- attr_reader :gap
55
-
56
- # Creates a new ColumnBox object for the given +children+ boxes.
57
- def initialize(children = [], columns = 2, gap: 36, **kwargs)
58
- super(**kwargs)
59
- @children = children
60
- @columns = columns
61
- @gap = gap
62
- end
63
-
64
- # Fits the column box into the available space.
65
- def fit(available_width, available_height, frame)
66
- last_height_difference = 1_000_000
67
- height = if style.position == :flow
68
- frame.height
69
- else
70
- (@initial_height > 0 ? @initial_height : available_height) - reserved_height
71
- end
72
- while true
73
- p '-'*100
74
- @frames = []
75
- if style.position == :flow
76
- column_width = (frame.width - gap * (@columns - 1)).to_f / @columns
77
- @columns.times do |col_nr|
78
- left = (column_width + gap) * col_nr + frame.left
79
- bottom = frame.bottom
80
- rect = Geom2D::Polygon([left, bottom],
81
- [left + column_width, bottom],
82
- [left + column_width, bottom + height],
83
- [left, bottom + height])
84
- shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
85
- col_frame = Frame.new(left, bottom, column_width, height)
86
- col_frame.shape = shape
87
- @frames << col_frame
88
- end
89
- @frame_index = 0
90
- @results = @children.map {|child_box| fit_box(child_box) }
91
- @width = frame.width
92
- @height = frame.height - @frames.min_by(&:y).y
93
- else
94
- width = (@initial_width > 0 ? @initial_width : available_width) - reserved_width
95
- column_width = (width - gap * (@columns - 1)).to_f / @columns
96
- @columns.times do |col_nr|
97
- @frames << Frame.new((column_width + gap) * col_nr, 0, column_width, height)
98
- end
99
- @frame_index = 0
100
- @results = @children.map {|child_box| fit_box(child_box) }
101
- @width = width
102
- @height = height - @frames.min_by(&:y).y
103
- end
104
- min_y, max_y = @frames.minmax_by(&:y).map(&:y)
105
- p [height, @frames.map(&:y), last_height_difference, min_y, max_y]
106
- # TOOD: @result.any?(&:empty?) only for the first run!!!! if the first run fails, we
107
- # cannot balance the columns because there is too much content.
108
- # TODO: another break condition is if the @results didn't change since the last run
109
- p [:maybe_redo, min_y, max_y, height, last_height_difference]
110
- p [@results.map {|arr| arr.all? {|r| r.status }}]
111
- break if max_y != height && @results.all? {|arr| !arr.empty? && arr.all? {|r| r.success? }} &&
112
- (@results.any?(&:empty?) ||
113
- max_y - min_y >= last_height_difference ||
114
- max_y - min_y < 0.5)
115
- if max_y == 0 && min_y == 0
116
- height += last_height_difference / 4.0
117
- else
118
- last_height_difference = max_y - min_y
119
- height -= last_height_difference / 2.0
120
- end
121
- end
122
- @results.all? {|res| res.length == 1 }
123
- end
124
-
125
- private
126
-
127
- def fit_box(box)
128
- cur_frame = @frames[@frame_index]
129
- fit_results = []
130
- while cur_frame
131
- result = cur_frame.fit(box)
132
- if result.success?
133
- cur_frame.remove_area(result.mask)
134
- fit_results << result
135
- break
136
- elsif cur_frame.full?
137
- @frame_index += 1
138
- break if @frame_index == @frames.length
139
- cur_frame = @frames[@frame_index]
140
- else
141
- draw_box, box = cur_frame.split(result)
142
- if draw_box
143
- cur_frame.remove_area(result.mask)
144
- fit_results << result
145
- elsif !cur_frame.find_next_region
146
- @frame_index += 1
147
- break if @frame_index == @frames.length
148
- cur_frame = @frames[@frame_index]
149
- end
150
- end
151
- end
152
- fit_results
153
- end
154
-
155
- # Draws the child boxes onto the canvas at position [x, y].
156
- def draw_content(canvas, x, y)
157
- x = y = 0 if style.position == :flow
158
- @results.each do |result_boxes|
159
- result_boxes.each do |result|
160
- result.box.draw(canvas, x + result.x, y + result.y)
161
- end
162
- end
163
- end
164
-
165
- end
166
-
167
- end
168
- end