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,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