hexapdf 0.21.1 → 0.22.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 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