hexapdf 0.7.0 → 0.8.0

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