hexapdf 0.7.0 → 0.8.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. data/examples/text_layouter_shapes.rb +0 -170
@@ -0,0 +1,61 @@
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-2018 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
+
34
+ require 'hexapdf/type/font_simple'
35
+
36
+ module HexaPDF
37
+ module Type
38
+
39
+ # Represents a Type 3 font.
40
+ #
41
+ # See: PDF1.7 s9.6.5
42
+ class FontType3 < FontSimple
43
+
44
+ define_field :Subtype, type: Symbol, required: true, default: :Type3
45
+ define_field :Name, type: Symbol
46
+ define_field :FontBBox, type: Rectangle, required: true
47
+ define_field :FontMatrix, type: Array, required: true
48
+ define_field :CharProcs, type: Dictionary, required: true
49
+ define_field :Resources, type: Dictionary, version: '1.2'
50
+
51
+ private
52
+
53
+ def perform_validation
54
+ super
55
+ yield("Required field Encoding is not set", false) if self[:Encoding].nil?
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -58,18 +58,18 @@ module HexaPDF
58
58
  define_field :op, type: Boolean, version: "1.3"
59
59
  define_field :OPM, type: Integer, version: "1.3"
60
60
  define_field :Font, type: Array, version: "1.3"
61
- define_field :BG, type: [Dictionary, Hash, Stream]
62
- define_field :BG2, type: [Dictionary, Hash, Stream, Symbol], version: "1.3"
63
- define_field :UCR, type: [Dictionary, Hash, Stream]
64
- define_field :UCR2, type: [Dictionary, Hash, Stream, Symbol], version: "1.3"
65
- define_field :TR, type: [Dictionary, Hash, Stream, Array, Symbol]
66
- define_field :TR2, type: [Dictionary, Hash, Stream, Array, Symbol], version: "1.3"
67
- define_field :HT, type: [Dictionary, Hash, Stream, Symbol]
61
+ define_field :BG, type: [Dictionary, Stream]
62
+ define_field :BG2, type: [Dictionary, Stream, Symbol], version: "1.3"
63
+ define_field :UCR, type: [Dictionary, Stream]
64
+ define_field :UCR2, type: [Dictionary, Stream, Symbol], version: "1.3"
65
+ define_field :TR, type: [Dictionary, Stream, Array, Symbol]
66
+ define_field :TR2, type: [Dictionary, Stream, Array, Symbol], version: "1.3"
67
+ define_field :HT, type: [Dictionary, Stream, Symbol]
68
68
  define_field :FL, type: Numeric, version: "1.3"
69
69
  define_field :SM, type: Numeric, version: "1.3"
70
70
  define_field :SA, type: Boolean
71
71
  define_field :BM, type: [Symbol, Array], version: "1.4"
72
- define_field :SMask, type: [Dictionary, Hash, Symbol], version: "1.4"
72
+ define_field :SMask, type: [Dictionary, Symbol], version: "1.4"
73
73
  define_field :CA, type: Numeric, version: "1.4"
74
74
  define_field :ca, type: Numeric, version: "1.4"
75
75
  define_field :AIS, type: Boolean, version: "1.4"
@@ -76,6 +76,16 @@ module HexaPDF
76
76
  # facility and not when the image is part of a loaded PDF file.
77
77
  attr_accessor :source_path
78
78
 
79
+ # Returns the width of the image.
80
+ def width
81
+ self[:Width]
82
+ end
83
+
84
+ # Returns the height of the image.
85
+ def height
86
+ self[:Height]
87
+ end
88
+
79
89
  # Returns an Info structure with information about the image.
80
90
  #
81
91
  # Available accessors:
@@ -36,6 +36,7 @@ require 'hexapdf/dictionary'
36
36
  require 'hexapdf/stream'
37
37
  require 'hexapdf/type/page_tree_node'
38
38
  require 'hexapdf/content'
39
+ require 'hexapdf/content/transformation_matrix'
39
40
 
40
41
  module HexaPDF
41
42
  module Type
@@ -129,7 +130,7 @@ module HexaPDF
129
130
  define_field :TrimBox, type: Rectangle, version: '1.3'
130
131
  define_field :ArtBox, type: Rectangle, version: '1.3'
131
132
  define_field :BoxColorInfo, type: Dictionary, version: '1.4'
132
- define_field :Contents, type: [Array, Stream]
133
+ define_field :Contents, type: [Stream, Array]
133
134
  define_field :Rotate, type: Integer, default: 0
134
135
  define_field :Group, type: Dictionary, version: '1.4'
135
136
  define_field :Thumb, type: Stream
@@ -182,7 +183,13 @@ module HexaPDF
182
183
  end
183
184
  end
184
185
 
185
- # Returns the rectangle defining a certain kind of box for the page.
186
+ # :call-seq:
187
+ # page.box(type = :media) -> box
188
+ # page.box(type = :media, rectangle) -> rectangle
189
+ #
190
+ # If no +rectangle+ is given, returns the rectangle defining a certain kind of box for the
191
+ # page. Otherwise sets the value for the given box type to +rectangle+ (an array with four
192
+ # values or a HexaPDF::Rectangle).
186
193
  #
187
194
  # This method should be used instead of directly accessing any of /MediaBox, /CropBox,
188
195
  # /BleedBox, /ArtBox or /TrimBox because it also takes the fallback values into account!
@@ -209,15 +216,81 @@ module HexaPDF
209
216
  # author. The default is the crop box.
210
217
  #
211
218
  # See: PDF1.7 s14.11.2
212
- def box(type = :media)
213
- case type
214
- when :media then self[:MediaBox]
215
- when :crop then self[:CropBox] || self[:MediaBox]
216
- when :bleed then self[:BleedBox] || self[:CropBox] || self[:MediaBox]
217
- when :trim then self[:TrimBox] || self[:CropBox] || self[:MediaBox]
218
- when :art then self[:ArtBox] || self[:CropBox] || self[:MediaBox]
219
+ def box(type = :media, rectangle = nil)
220
+ if rectangle
221
+ case type
222
+ when :media, :crop, :bleed, :trim, :art
223
+ self["#{type.capitalize}Box".to_sym] = rectangle
224
+ else
225
+ raise ArgumentError, "Unsupported page box type provided: #{type}"
226
+ end
227
+ else
228
+ case type
229
+ when :media then self[:MediaBox]
230
+ when :crop then self[:CropBox] || self[:MediaBox]
231
+ when :bleed then self[:BleedBox] || self[:CropBox] || self[:MediaBox]
232
+ when :trim then self[:TrimBox] || self[:CropBox] || self[:MediaBox]
233
+ when :art then self[:ArtBox] || self[:CropBox] || self[:MediaBox]
234
+ else
235
+ raise ArgumentError, "Unsupported page box type provided: #{type}"
236
+ end
237
+ end
238
+ end
239
+
240
+ # Returns the orientation of the media box, either :portrait or :landscape.
241
+ def orientation
242
+ box = self[:MediaBox]
243
+ rotation = self[:Rotate]
244
+ if (box.height > box.width && (rotation == 0 || rotation == 180)) ||
245
+ (box.height < box.width && (rotation == 90 || rotation == 270))
246
+ :portrait
247
+ else
248
+ :landscape
249
+ end
250
+ end
251
+
252
+ # Rotates the page +angle+ degrees counterclockwise where +angle+ has to be a multiple of 90.
253
+ #
254
+ # Positive values rotate the page to the left, negative values to the right. If +flatten+ is
255
+ # +true+, the rotation is not done via the page's meta data but by "rotating" the canvas
256
+ # itself.
257
+ #
258
+ # Note that the :Rotate key of a page object describes the angle in a clockwise orientation
259
+ # but this method uses counterclockwise rotation to be consistent with other rotation methods
260
+ # (e.g. HexaPDF::Content::Canvas#rotate).
261
+ def rotate(angle, flatten: false)
262
+ if angle % 90 != 0
263
+ raise ArgumentError, "Page rotation has to be multiple of 90 degrees"
264
+ end
265
+
266
+ cw_angle = (self[:Rotate] - angle) % 360
267
+
268
+ if flatten
269
+ delete(:Rotate)
270
+ return if cw_angle == 0
271
+
272
+ matrix, llx, lly, urx, ury = \
273
+ case cw_angle
274
+ when 90
275
+ [HexaPDF::Content::TransformationMatrix.new(0, -1, 1, 0),
276
+ box.right, box.bottom, box.left, box.top]
277
+ when 180
278
+ [HexaPDF::Content::TransformationMatrix.new(-1, 0, 0, -1),
279
+ box.right, box.top, box.left, box.bottom]
280
+ when 270
281
+ [HexaPDF::Content::TransformationMatrix.new(0, 1, -1, 0),
282
+ box.left, box.top, box.right, box.bottom]
283
+ end
284
+ [:MediaBox, :CropBox, :BleedBox, :TrimBox, :ArtBox].each do |box|
285
+ next unless key?(box)
286
+ self[box].value = matrix.evaluate(llx, lly).concat(matrix.evaluate(urx, ury))
287
+ end
288
+
289
+ before_contents = document.add({}, stream: " q #{matrix.to_a.join(' ')} cm ")
290
+ after_contents = document.add({}, stream: " Q ")
291
+ self[:Contents] = [before_contents, *self[:Contents], after_contents]
219
292
  else
220
- raise ArgumentError, "Unsupported page box type provided: #{type}"
293
+ self[:Rotate] = cw_angle
221
294
  end
222
295
  end
223
296
 
@@ -34,6 +34,6 @@
34
34
  module HexaPDF
35
35
 
36
36
  # The version of HexaPDF.
37
- VERSION = '0.7.0'
37
+ VERSION = '0.8.0'
38
38
 
39
39
  end
@@ -51,7 +51,7 @@ module CommonTokenizerTests
51
51
  <</Name 5>>
52
52
 
53
53
  % Test
54
- EOF
54
+ EOF
55
55
 
56
56
  expected_tokens = [
57
57
  true, false,
@@ -125,7 +125,7 @@ module CommonTokenizerTests
125
125
  create_tokenizer(<<-EOF.chomp.gsub(/^ {8}/, ''))
126
126
  true false null 123 34.5 (string) <4E6F76> /Name
127
127
  [5 6 /Name] <</Name 5/Null null>>
128
- EOF
128
+ EOF
129
129
  assert_equal(true, @tokenizer.next_object)
130
130
  assert_equal(false, @tokenizer.next_object)
131
131
  assert_nil(@tokenizer.next_object)
@@ -0,0 +1,79 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/content'
7
+ require 'hexapdf/content/graphic_object'
8
+
9
+ describe HexaPDF::Content::GraphicObject::Geom2D do
10
+ before do
11
+ @obj = HexaPDF::Content::GraphicObject::Geom2D.new
12
+ end
13
+
14
+ it "allows creation via the ::configure method" do
15
+ obj = HexaPDF::Content::GraphicObject::Geom2D.configure(object: Geom2D::Point(5, 5))
16
+ assert_equal(Geom2D::Point(5, 5), obj.object)
17
+ end
18
+
19
+ it "creates a default Geom2D drawing support object" do
20
+ obj = HexaPDF::Content::GraphicObject::Geom2D.new
21
+ assert_nil(obj.object)
22
+ assert_equal(1, obj.point_radius)
23
+ assert_equal(false, obj.path_only)
24
+ end
25
+
26
+ it "allows configuration of the object" do
27
+ @obj.configure(object: Geom2D::Point(5, 5), point_radius: 3, path_only: true)
28
+ assert_equal(Geom2D::Point(5, 5), @obj.object)
29
+ assert_equal(3, @obj.point_radius)
30
+ assert_equal(true, @obj.path_only)
31
+ end
32
+
33
+ describe "draw" do
34
+ before do
35
+ doc = HexaPDF::Document.new
36
+ @canvas = doc.pages.add.canvas
37
+ end
38
+
39
+ it "draws a Geom2D::Point onto the canvas" do
40
+ @obj.object = Geom2D::Point(5, 5)
41
+ @obj.draw(@canvas)
42
+ assert_operators(@canvas.contents,
43
+ [:move_to, :curve_to, :curve_to, :curve_to, :curve_to, :curve_to, :curve_to,
44
+ :close_subpath, :fill_path_non_zero],
45
+ only_names: true)
46
+ end
47
+
48
+ it "draws a Geom2D::Segment onto the canvas" do
49
+ @obj.object = Geom2D::Segment([5, 6], [10, 11])
50
+ @obj.draw(@canvas)
51
+ assert_operators(@canvas.contents,
52
+ [[:move_to, [5, 6]], [:line_to, [10, 11]], [:stroke_path]])
53
+ end
54
+
55
+ it "draws a Geom2D::Polygon onto the canvas" do
56
+ @obj.object = Geom2D::Polygon([5, 6], [10, 11], [7, 9])
57
+ @obj.draw(@canvas)
58
+ assert_operators(@canvas.contents,
59
+ [[:move_to, [5, 6]], [:line_to, [10, 11]], [:line_to, [7, 9]],
60
+ [:stroke_path]])
61
+ end
62
+
63
+ it "draws a Geom2D::PolygonSet onto the canvas" do
64
+ @obj.object = Geom2D::PolygonSet(Geom2D::Polygon([5, 6], [10, 11], [7, 9]),
65
+ Geom2D::Polygon([0, 0], [4, 0], [2, 3]))
66
+ @obj.draw(@canvas)
67
+ assert_operators(@canvas.contents,
68
+ [[:move_to, [5, 6]], [:line_to, [10, 11]], [:line_to, [7, 9]],
69
+ [:close_subpath],
70
+ [:move_to, [0, 0]], [:line_to, [4, 0]], [:line_to, [2, 3]],
71
+ [:close_subpath], [:stroke_path]])
72
+ end
73
+
74
+ it "fails for unkown classes" do
75
+ @obj.object = 5
76
+ assert_raises(HexaPDF::Error) { @obj.draw(@canvas) }
77
+ end
78
+ end
79
+ end
@@ -76,7 +76,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
76
76
  end
77
77
  end
78
78
 
79
- if basename !~ /\Auserpwd/
79
+ unless basename.start_with?("userpwd")
80
80
  it "can decrypt the encrypted file #{basename} with the owner password" do
81
81
  begin
82
82
  doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
@@ -111,7 +111,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
111
111
  assert_equal(:Font, cidfont[:Type])
112
112
  assert_equal(:CIDFontType2, cidfont[:Subtype])
113
113
  assert_equal({Registry: "Adobe", Ordering: "Identity", Supplement: 0},
114
- cidfont[:CIDSystemInfo])
114
+ cidfont[:CIDSystemInfo].value)
115
115
  assert_equal(:Identity, cidfont[:CIDToGIDMap])
116
116
  assert_equal(@font_wrapper.glyph(3).width, cidfont[:DW])
117
117
  assert_equal([2, [glyph.width]], cidfont[:W])
@@ -75,7 +75,7 @@ describe HexaPDF::Font::Type1Wrapper do
75
75
  code = @symbol_wrapper.encode(@symbol_wrapper.glyph(:plus))
76
76
  @doc.dispatch_message(:complete_objects)
77
77
  assert_equal("\x21", code)
78
- assert_equal({Differences: [32, :space, :plus]}, @symbol_wrapper.dict[:Encoding])
78
+ assert_equal({Differences: [32, :space, :plus]}, @symbol_wrapper.dict[:Encoding].value)
79
79
  end
80
80
  end
81
81
  end
@@ -11,7 +11,7 @@ describe HexaPDF::Font::TrueType::Table::Cmap do
11
11
  [0, 1, 28],
12
12
  [3, 1, 28 + f0.length],
13
13
  [1, 0, 28],
14
- ].map {|a| a.pack('n2N') }.join('') << f0 << f0
14
+ ].map {|a| a.pack('n2N') }.join('') << f0 << [10, 22, 0, 0, 2, 10, 13].pack('nN2N2n2')
15
15
  set_up_stub_true_type_font(data)
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ describe HexaPDF::Font::TrueType::Table::Directory do
22
22
  dir = HexaPDF::Font::TrueType::Table::Directory.new(@file, @self_entry)
23
23
  entry = dir.entry('CUST')
24
24
  assert_equal('CUST', entry.tag)
25
- assert_equal('----'.unpack('N').first, entry.checksum)
25
+ assert_equal('----'.unpack1('N'), entry.checksum)
26
26
  assert_equal(28, entry.offset)
27
27
  assert_equal(5, entry.length)
28
28
  end
@@ -8,7 +8,7 @@ describe HexaPDF::Font::TrueType::Table::Head do
8
8
  before do
9
9
  data = [1, 0, 2, 6554, 0, 42, 0x5f0f, 0x3CF5, 3, 64].pack('n*')
10
10
  @time = Time.new(2016, 05, 01)
11
- data << ([(@time - HexaPDF::Font::TrueType::Table::TIME_EPOCH).to_i] * 2).pack('Q>*')
11
+ data << ([(@time - HexaPDF::Font::TrueType::Table::TIME_EPOCH).to_i] * 2).pack('q>*')
12
12
  data << [-132, -152, 3423, 4231, 3, 9, -2, 0, 0].pack('s>4n2s>3')
13
13
  set_up_stub_true_type_font(data)
14
14
  end
@@ -43,8 +43,12 @@ describe HexaPDF::Font::TrueType::Table::Head do
43
43
 
44
44
  describe "checksum_valid?" do
45
45
  it "checks whether an entry's checksum is valid" do
46
- data = 254.chr * 12 + [0x5F0F3CF5].pack('N') + 254.chr * 36 + 0.chr * 4
47
- @entry.checksum = (0xfefefefe * 11 + 0x5F0F3CF5) % 2**32
46
+ data = 254.chr * 12 + # fields before checksum field
47
+ [0x5F0F3CF5].pack('N') + # checksum field
48
+ 254.chr * 4 +
49
+ 0.chr * 4 + 254.chr * 4 + 0.chr * 4 + 254.chr * 4 + # date fields
50
+ 254.chr * 16 + 0.chr * 4
51
+ @entry.checksum = (0xfefefefe * 9 + 0x5F0F3CF5) % 2**32
48
52
  table = create_table(:Head, data)
49
53
  assert(table.checksum_valid?)
50
54
  end
@@ -10,21 +10,31 @@ describe HexaPDF::Layout::Box do
10
10
  HexaPDF::Layout::Box.new(*args, &block)
11
11
  end
12
12
 
13
- describe "initialize" do
13
+ describe "::create" do
14
+ it "passes the block on to #initialize" do
15
+ block = proc {}
16
+ box = HexaPDF::Layout::Box.create(&block)
17
+ assert_same(block, box.instance_eval { @draw_block })
18
+ end
19
+
20
+ it "allows specifying style options" do
21
+ box = HexaPDF::Layout::Box.create(background_color: 20)
22
+ assert_equal(20, box.style.background_color)
23
+ end
24
+
14
25
  it "takes content width and height" do
15
- box = create_box(content_width: 100, content_height: 200)
26
+ box = HexaPDF::Layout::Box.create(width: 100, height: 200, content_box: true,
27
+ padding: 10, border: {width: 10})
16
28
  assert_equal(100, box.content_width)
17
29
  assert_equal(200, box.content_height)
18
30
  end
31
+ end
19
32
 
33
+ describe "initialize" do
20
34
  it "takes box width and height" do
21
35
  box = create_box(width: 100, height: 200)
22
- assert_equal(100, box.content_width)
23
- assert_equal(200, box.content_height)
24
-
25
- box = create_box(width: 100, height: 200, style: {padding: [20, 10], border: {width: [10, 5]}})
26
- assert_equal(70, box.content_width)
27
- assert_equal(140, box.content_height)
36
+ assert_equal(100, box.width)
37
+ assert_equal(200, box.height)
28
38
  end
29
39
 
30
40
  it "allows passing a Style object or a hash" do
@@ -36,16 +46,41 @@ describe HexaPDF::Layout::Box do
36
46
  end
37
47
  end
38
48
 
39
- it "returns the full width and height of the box" do
40
- box = create_box(content_width: 100, content_height: 200,
41
- style: {padding: [20, 10], border: {width: [10, 5]}})
42
- assert_equal(130, box.width)
43
- assert_equal(260, box.height)
49
+ describe "fit" do
50
+ before do
51
+ @frame = Object.new
52
+ end
53
+
54
+ it "fits a fixed sized box" do
55
+ box = create_box(width: 50, height: 50)
56
+ assert(box.fit(100, 100, @frame))
57
+ assert_equal(50, box.width)
58
+ assert_equal(50, box.height)
59
+ end
60
+
61
+ it "uses the maximum available width" do
62
+ box = create_box(height: 50)
63
+ assert(box.fit(100, 100, @frame))
64
+ assert_equal(100, box.width)
65
+ assert_equal(50, box.height)
66
+ end
67
+
68
+ it "uses the maximum available height" do
69
+ box = create_box(width: 50)
70
+ assert(box.fit(100, 100, @frame))
71
+ assert_equal(50, box.width)
72
+ assert_equal(100, box.height)
73
+ end
74
+
75
+ it "returns false if the box doesn't fit" do
76
+ box = create_box(width: 101)
77
+ refute(box.fit(100, 100, @frame))
78
+ end
44
79
  end
45
80
 
46
81
  describe "draw" do
47
82
  it "draws the box onto the canvas" do
48
- box = create_box(content_width: 100, content_height: 100) do |canvas, _|
83
+ box = create_box(width: 150, height: 130) do |canvas, _|
49
84
  canvas.line_width(15)
50
85
  end
51
86
  box.style.background_color = 0.5
@@ -69,6 +104,8 @@ describe HexaPDF::Layout::Box do
69
104
  [:restore_graphics_state],
70
105
  [:save_graphics_state],
71
106
  [:set_line_width, [5]],
107
+ [:append_rectangle, [5, 5, 150, 130]],
108
+ [:clip_path_non_zero], [:end_path],
72
109
  [:append_rectangle, [7.5, 7.5, 145, 125]],
73
110
  [:stroke_path],
74
111
  [:restore_graphics_state],
@@ -86,8 +123,13 @@ describe HexaPDF::Layout::Box do
86
123
 
87
124
  it "draws nothing onto the canvas if the box is empty" do
88
125
  @canvas = HexaPDF::Document.new.pages.add.canvas
89
- create_box.draw(@canvas, 5, 5)
126
+ box = create_box
127
+ box.draw(@canvas, 5, 5)
90
128
  assert_operators(@canvas.contents, [])
129
+ refute(box.style.background_color?)
130
+ refute(box.style.underlays?)
131
+ refute(box.style.border?)
132
+ refute(box.style.overlays?)
91
133
  end
92
134
  end
93
135