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,108 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/layout/width_from_polygon'
5
+
6
+ describe HexaPDF::Layout::WidthFromPolygon do
7
+ def create_width_spec(polygon, offset = 0)
8
+ HexaPDF::Layout::WidthFromPolygon.new(polygon, offset)
9
+ end
10
+
11
+ it "respects the offset" do
12
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [10, 5]), 5)
13
+ assert_equal([0, 8], ws.call(0, 1))
14
+ end
15
+
16
+ it "works in the case bottom and top line are the same" do
17
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [10, 5]))
18
+ assert_equal([0, 0], ws.call(0, 0))
19
+ assert_equal([0, 0], ws.call(5, 0))
20
+ end
21
+
22
+ it "works when the first segment has not the minimal x-value" do
23
+ ws = create_width_spec(Geom2D::Polygon([10, 10], [10, 0], [0, 0], [5, 10]))
24
+ assert_equal([5, 5], ws.call(0, 1))
25
+ assert_equal([2.5, 7.5], ws.call(5, 1))
26
+ end
27
+
28
+ it "works when the polygon is specified in counterclockwise order" do
29
+ ws = create_width_spec(Geom2D::Polygon([10, 10], [5, 10], [0, 0], [10, 0]))
30
+ assert_equal([5, 5], ws.call(0, 1))
31
+ assert_equal([2.5, 7.5], ws.call(5, 1))
32
+ end
33
+
34
+ it "works if some segments only cross the top line" do
35
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 11], [4, 9], [6, 11], [10, 10],
36
+ [10, 0]))
37
+ assert_equal([0, 3, 2, 5], ws.call(1, 2))
38
+ end
39
+
40
+ it "works if some segments only cross the bottom line" do
41
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 4], [4, 6], [6, 4], [10, 10],
42
+ [10, 0]))
43
+ assert_equal([0, 1, 7, 2], ws.call(3, 2))
44
+ end
45
+
46
+ it "works if some non-horizontal segments don't cross the top/bottom line at all" do
47
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [2, 4], [4, 6.5], [6, 6], [10, 10],
48
+ [10, 0]))
49
+ assert_equal([0, 1, 6, 3], ws.call(3, 2))
50
+ end
51
+
52
+ it "works if there is no available space" do
53
+ ws = create_width_spec(Geom2D::Polygon([0, 0], [0, 10], [5, 9], [10, 10], [10, 0]))
54
+ assert_equal([0, 0], ws.call(0, 2))
55
+ end
56
+
57
+ it "works if the first processed segment doesn't cross both lines" do
58
+ ws = create_width_spec(Geom2D::Polygon([0, 5], [0, 0], [10, 0], [10, 10], [5, 10], [5, 5]))
59
+ assert_equal([5, 5], ws.call(4, 2))
60
+ end
61
+
62
+ describe "multiple polygons" do
63
+ it "rectangle in rectangle" do
64
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
65
+ Geom2D::Polygon([2, 2], [2, 8], [8, 8], [8, 2])))
66
+ assert_equal([0, 2, 6, 2], ws.call(1, 8))
67
+ assert_equal([0, 10], ws.call(0, 2))
68
+ assert_equal([0, 2, 6, 2], ws.call(2, 1))
69
+ assert_equal([0, 2, 6, 2], ws.call(7, 2))
70
+ end
71
+
72
+ it "rectangle in rectangle with reverse direction" do
73
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
74
+ Geom2D::Polygon([2, 8], [2, 2], [8, 2], [8, 8])))
75
+ assert_equal([0, 2, 6, 2], ws.call(7, 2))
76
+ assert_equal([0, 2, 6, 2], ws.call(1, 8))
77
+ assert_equal([0, 10], ws.call(0, 2))
78
+ assert_equal([0, 2, 6, 2], ws.call(2, 1))
79
+ end
80
+
81
+ it "first segment of inner polygon is between the lines, polygon crosses both lines" do
82
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
83
+ Geom2D::Polygon([2, 4], [2, 6], [8, 8], [8, 2])))
84
+ assert_equal([0, 10], ws.call(0, 2))
85
+ assert_equal([0, 5, 3, 2], ws.call(2, 1).map {|f| f.round(5) })
86
+ assert_equal([0, 2, 6, 2], ws.call(3, 4))
87
+ end
88
+
89
+ it "first segment of inner polygon is between the lines, polygon crosses one line" do
90
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
91
+ Geom2D::Polygon([2, 4], [4, 6], [8, 2])))
92
+ assert_equal([0, 2, 5, 3], ws.call(3, 4))
93
+ end
94
+
95
+ it "polygon is partly between the lines, maximum between the lines" do
96
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
97
+ Geom2D::Polygon([2, 4], [2, 6], [8, 8], [9, 5],
98
+ [8, 2])))
99
+ assert_equal([0, 2, 7, 1], ws.call(3, 4))
100
+ end
101
+
102
+ it "polygon is partly between the lines, maximum is at an line crossing" do
103
+ ws = create_width_spec(Geom2D::PolygonSet(Geom2D::Polygon([0, 0], [0, 10], [10, 10], [10, 0]),
104
+ Geom2D::Polygon([2, 4], [8, 8], [5, 5], [8, 2])))
105
+ assert_equal([0, 2, 5, 3], ws.call(3, 4))
106
+ end
107
+ end
108
+ end
@@ -10,7 +10,7 @@ describe HexaPDF::DictionaryFields do
10
10
 
11
11
  describe "Field" do
12
12
  before do
13
- @field = self.class::Field.new([:Integer, Integer], true, 500, false, '1.2')
13
+ @field = self.class::Field.new([:Integer, self.class::PDFByteString], true, 500, false, '1.2')
14
14
  HexaPDF::GlobalConfiguration['object.type_map'][:Integer] = Integer
15
15
  end
16
16
 
@@ -27,22 +27,26 @@ describe HexaPDF::DictionaryFields do
27
27
  end
28
28
 
29
29
  it "maps string types to constants" do
30
- assert_equal([Integer], @field.type)
30
+ assert_equal([Integer, self.class::PDFByteString, Hash, String], @field.type)
31
31
  end
32
32
 
33
- it "uses the additional types from a converter" do
34
- @field = self.class::Field.new(self.class::PDFByteString)
35
- assert_equal([self.class::PDFByteString, String], @field.type)
36
- end
33
+ describe "convert" do
34
+ it "returns the converted object, using the first usable converter" do
35
+ doc = Minitest::Mock.new
36
+ doc.expect(:wrap, :data, [Hash, Hash])
37
+ @field.convert({}, doc)
38
+ doc.verify
37
39
 
38
- it "does not allow any conversion with the identity converter" do
39
- x = '5'
40
- refute(@field.convert?(x))
41
- assert_same(x, @field.convert(x, self))
40
+ assert(@field.convert('str', self).encoding == Encoding::BINARY)
41
+ end
42
+
43
+ it "returns nil for unconvertable objects" do
44
+ assert_nil(@field.convert(5.5, self))
45
+ end
42
46
  end
43
47
 
44
48
  it "can check for a valid object" do
45
- refute(@field.valid_object?('Test'))
49
+ refute(@field.valid_object?(5.5))
46
50
  assert(@field.valid_object?(5))
47
51
  assert(@field.valid_object?(HexaPDF::Object.new(5)))
48
52
  end
@@ -59,18 +63,20 @@ describe HexaPDF::DictionaryFields do
59
63
  end
60
64
 
61
65
  it "allows conversion from a hash" do
62
- assert(@field.convert?({}))
63
66
  @doc.expect(:wrap, :data, [Hash, Hash])
64
67
  @field.convert({Test: :value}, @doc)
65
68
  @doc.verify
66
69
  end
67
70
 
68
71
  it "allows conversion from a Dictionary" do
69
- assert(@field.convert?(HexaPDF::Dictionary.new({})))
70
72
  @doc.expect(:wrap, :data, [HexaPDF::Dictionary, Hash])
71
73
  @field.convert(HexaPDF::Dictionary.new(Test: :value), @doc)
72
74
  @doc.verify
73
75
  end
76
+
77
+ it "doesn't allow conversion from nil" do
78
+ refute(@field.convert(nil, @doc))
79
+ end
74
80
  end
75
81
 
76
82
  describe "StringConverter" do
@@ -79,7 +85,7 @@ describe HexaPDF::DictionaryFields do
79
85
  end
80
86
 
81
87
  it "allows conversion to UTF-8 string from binary" do
82
- assert(@field.convert?('test'.b))
88
+ refute(@field.convert("test", self))
83
89
 
84
90
  str = @field.convert("\xfe\xff\x00t\x00e\x00s\x00t".b, self)
85
91
  assert_equal('test', str)
@@ -100,8 +106,7 @@ describe HexaPDF::DictionaryFields do
100
106
  end
101
107
 
102
108
  it "allows conversion to a binary string" do
103
- assert(@field.convert?('test'))
104
- refute(@field.convert?('test'.b))
109
+ refute(@field.convert('test'.b, self))
105
110
 
106
111
  str = @field.convert("test", self)
107
112
  assert_equal('test', str)
@@ -119,9 +124,7 @@ describe HexaPDF::DictionaryFields do
119
124
  end
120
125
 
121
126
  it "allows conversion to a Time object from a binary string" do
122
- date = "D:199812231952-08'00".b
123
- refute(@field.convert?('test'.b))
124
- assert(@field.convert?(date))
127
+ refute(@field.convert('test'.b, self))
125
128
 
126
129
  [
127
130
  ["D:1998", [1998, 01, 01, 00, 00, 00, "-00:00"]],
@@ -155,8 +158,6 @@ describe HexaPDF::DictionaryFields do
155
158
  end
156
159
 
157
160
  it "allows conversion from a string" do
158
- assert(@field.convert?("test"))
159
-
160
161
  @doc = Minitest::Mock.new
161
162
  @doc.expect(:wrap, :data, [{F: 'test'}, {type: HexaPDF::Type::FileSpecification}])
162
163
  @field.convert('test', @doc)
@@ -164,8 +165,6 @@ describe HexaPDF::DictionaryFields do
164
165
  end
165
166
 
166
167
  it "allows conversion from a hash/dictionary" do
167
- assert(@field.convert?({}))
168
-
169
168
  @doc = Minitest::Mock.new
170
169
  @doc.expect(:wrap, :data, [{F: 'test'}, {type: HexaPDF::Type::FileSpecification}])
171
170
  @field.convert({F: 'test'}, @doc)
@@ -183,9 +182,6 @@ describe HexaPDF::DictionaryFields do
183
182
  end
184
183
 
185
184
  it "allows conversion to a Rectangle from an Array" do
186
- assert(@field.convert?([5, 6]))
187
- refute(@field.convert?(:name))
188
-
189
185
  doc = Minitest::Mock.new
190
186
  doc.expect(:wrap, :data, [[0, 1, 2, 3], type: HexaPDF::Rectangle])
191
187
  @field.convert([0, 1, 2, 3], doc)
@@ -436,17 +436,17 @@ describe HexaPDF::Document do
436
436
  end
437
437
 
438
438
  it "validates indirect objects" do
439
- @doc.add(Type: :Catalog)
439
+ obj = @doc.add(Type: :Catalog)
440
440
  refute(@doc.validate(auto_correct: false))
441
441
 
442
442
  called = false
443
- assert(@doc.validate { called = true })
443
+ assert(@doc.validate {|o| assert_same(obj, o); called = true })
444
444
  assert(called)
445
445
  end
446
446
 
447
447
  it "validates the trailer object" do
448
448
  @doc.trailer[:ID] = :Symbol
449
- refute(@doc.validate)
449
+ refute(@doc.validate {|obj| assert_same(@doc.trailer, obj) })
450
450
  end
451
451
  end
452
452
 
@@ -19,6 +19,7 @@ describe HexaPDF::Reference do
19
19
  obj = Object.new
20
20
  obj.define_singleton_method(:oid) { 1 }
21
21
  obj.define_singleton_method(:gen) { 0 }
22
+ obj.define_singleton_method(:<=>) {|o| HexaPDF::Reference.new(oid, gen) <=> o }
22
23
  assert_equal([obj, HexaPDF::Reference.new(1, 1), HexaPDF::Reference.new(5, 7)],
23
24
  [HexaPDF::Reference.new(5, 7), HexaPDF::Reference.new(1, 1), obj].sort)
24
25
  assert_nil(HexaPDF::Reference.new(1, 0) <=> 5)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.7.0)>>
43
+ <</Producer(HexaPDF version 0.8.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.7.0)>>
75
+ <</Producer(HexaPDF version 0.8.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/font_true_type'
6
+
7
+ describe HexaPDF::Type::FontTrueType do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ font_descriptor = @doc.add(Type: :FontDescriptor, FontName: :Something, Flags: 0b100,
11
+ FontBBox: [0, 1, 2, 3], ItalicAngle: 0, Ascent: 900,
12
+ Descent: -100, CapHeight: 800, StemV: 20)
13
+ @font = @doc.add(Type: :Font, Subtype: :TrueType, Encoding: :WinAnsiEncoding,
14
+ FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
15
+ BaseFont: :Something, FontDescriptor: font_descriptor)
16
+ end
17
+
18
+ describe "validation" do
19
+ it "requires that the FontDescriptor key is set" do
20
+ assert(@font.validate)
21
+ @font.delete(:FontDescriptor)
22
+ refute(@font.validate)
23
+ end
24
+ end
25
+ end
@@ -124,5 +124,11 @@ describe HexaPDF::Type::FontType1 do
124
124
  it "allows empty fields for standard fonts" do
125
125
  assert(@font.validate)
126
126
  end
127
+
128
+ it "requires that the FontDescriptor key is set for non-standard fonts" do
129
+ assert(@embedded_font.validate)
130
+ @embedded_font.delete(:FontDescriptor)
131
+ refute(@embedded_font.validate)
132
+ end
127
133
  end
128
134
  end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/font_type3'
6
+
7
+ describe HexaPDF::Type::FontType3 do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @font = @doc.add(Type: :Font, Subtype: :Type3, Encoding: :WinAnsiEncoding,
11
+ FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
12
+ FontBBox: [0, 0, 100, 100], FontMatrix: [1, 0, 0, 1, 0, 0],
13
+ CharProcs: {})
14
+ end
15
+
16
+ describe "validation" do
17
+ it "works for valid objects" do
18
+ assert(@font.validate)
19
+ end
20
+
21
+ it "fails if the Encoding key is missing" do
22
+ @font.delete(:Encoding)
23
+ refute(@font.validate)
24
+ end
25
+ end
26
+ end
@@ -11,6 +11,16 @@ describe HexaPDF::Type::Image do
11
11
  @doc = HexaPDF::Document.new
12
12
  end
13
13
 
14
+ it "returns the width of the image" do
15
+ @image = @doc.wrap(Subtype: :Image, Width: 10)
16
+ assert_equal(10, @image.width)
17
+ end
18
+
19
+ it "returns the height of the image" do
20
+ @image = @doc.wrap(Subtype: :Image, Height: 10)
21
+ assert_equal(10, @image.height)
22
+ end
23
+
14
24
  describe "info" do
15
25
  before do
16
26
  @image = @doc.wrap(Subtype: :Image, Width: 10, Height: 5, ColorSpace: :DeviceRGB,
@@ -118,6 +118,120 @@ describe HexaPDF::Type::Page do
118
118
  it "fails if an unknown box type is supplied" do
119
119
  assert_raises(ArgumentError) { @page.box(:undefined) }
120
120
  end
121
+
122
+ it "sets the correct box" do
123
+ @page.box(:media, :media)
124
+ assert_equal(:media, @page.box(:media))
125
+ @page.box(:crop, :crop)
126
+ assert_equal(:crop, @page.box(:crop))
127
+ @page.box(:bleed, :bleed)
128
+ assert_equal(:bleed, @page.box(:bleed))
129
+ @page.box(:trim, :trim)
130
+ assert_equal(:trim, @page.box(:trim))
131
+ @page.box(:art, :art)
132
+ assert_equal(:art, @page.box(:art))
133
+ end
134
+
135
+ it "fails if an unknown box type is supplied when setting a box" do
136
+ assert_raises(ArgumentError) { @page.box(:undefined, [1, 2, 3, 4]) }
137
+ end
138
+ end
139
+
140
+ describe "orientation" do
141
+ before do
142
+ @page = @doc.pages.add
143
+ end
144
+
145
+ it "returns :portrait for appropriate media boxes and rotation values" do
146
+ @page.box(:media, [0, 0, 100, 300])
147
+ assert_equal(:portrait, @page.orientation)
148
+ @page[:Rotate] = 0
149
+ assert_equal(:portrait, @page.orientation)
150
+ @page[:Rotate] = 180
151
+ assert_equal(:portrait, @page.orientation)
152
+
153
+ @page.box(:media, [0, 0, 300, 100])
154
+ @page[:Rotate] = 90
155
+ assert_equal(:portrait, @page.orientation)
156
+ @page[:Rotate] = 270
157
+ assert_equal(:portrait, @page.orientation)
158
+ end
159
+
160
+ it "returns :landscape for appropriate media boxes and rotation values" do
161
+ @page.box(:media, [0, 0, 300, 100])
162
+ assert_equal(:landscape, @page.orientation)
163
+ @page[:Rotate] = 0
164
+ assert_equal(:landscape, @page.orientation)
165
+ @page[:Rotate] = 180
166
+ assert_equal(:landscape, @page.orientation)
167
+
168
+ @page.box(:media, [0, 0, 100, 300])
169
+ @page[:Rotate] = 90
170
+ assert_equal(:landscape, @page.orientation)
171
+ @page[:Rotate] = 270
172
+ assert_equal(:landscape, @page.orientation)
173
+ end
174
+ end
175
+
176
+ describe "rotate" do
177
+ before do
178
+ @page = @doc.pages.add
179
+ reset_media_box
180
+ end
181
+
182
+ def reset_media_box
183
+ @page.box(:media, [50, 100, 200, 300])
184
+ end
185
+
186
+ it "works directly on the :Rotate key" do
187
+ @page.rotate(90)
188
+ assert_equal(270, @page[:Rotate])
189
+
190
+ @page.rotate(180)
191
+ assert_equal(90, @page[:Rotate])
192
+
193
+ @page.rotate(-90)
194
+ assert_equal(180, @page[:Rotate])
195
+ end
196
+
197
+ describe "flatten" do
198
+ it "adjust all page boxes" do
199
+ @page.box(:crop, @page.box)
200
+ @page.box(:bleed, @page.box)
201
+ @page.box(:trim, @page.box)
202
+ @page.box(:art, @page.box)
203
+
204
+ @page.rotate(90, flatten: true)
205
+ box = [-300, 50, -100, 200]
206
+ assert_equal(box, @page.box(:media).value)
207
+ assert_equal(box, @page.box(:crop).value)
208
+ assert_equal(box, @page.box(:bleed).value)
209
+ assert_equal(box, @page.box(:trim).value)
210
+ assert_equal(box, @page.box(:art).value)
211
+ end
212
+
213
+ it "works correctly for 90 degrees" do
214
+ @page.rotate(90, flatten: true)
215
+ assert_equal([-300, 50, -100, 200], @page.box(:media).value)
216
+ assert_equal(" q 0 1 -1 0 0 0 cm Q ", @page.contents)
217
+ end
218
+
219
+ it "works correctly for 180 degrees" do
220
+ @page.rotate(180, flatten: true)
221
+ assert_equal([-200, -300, -50, -100], @page.box(:media).value)
222
+ assert_equal(" q -1 0 0 -1 0 0 cm Q ", @page.contents)
223
+ end
224
+
225
+ it "works correctly for 270 degrees" do
226
+ @page.rotate(270, flatten: true)
227
+ assert_equal([100, -200, 300, -50], @page.box(:media).value)
228
+ assert_equal(" q 0 -1 1 0 0 0 cm Q ", @page.contents)
229
+ end
230
+ end
231
+
232
+ it "fails if the angle is not a multiple of 90" do
233
+ assert_raises(ArgumentError) { @page.rotate(27) }
234
+ end
121
235
  end
122
236
 
123
237
  describe "contents" do