prawn 2.0.2 → 2.3.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 (277) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/GPLv2 +20 -21
  5. data/Gemfile +3 -9
  6. data/Rakefile +20 -23
  7. data/lib/prawn.rb +37 -49
  8. data/lib/prawn/document.rb +181 -133
  9. data/lib/prawn/document/bounding_box.rb +41 -29
  10. data/lib/prawn/document/column_box.rb +7 -7
  11. data/lib/prawn/document/internals.rb +18 -8
  12. data/lib/prawn/document/span.rb +21 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +115 -69
  16. data/lib/prawn/font_metric_cache.rb +14 -8
  17. data/lib/prawn/{font → fonts}/afm.rb +102 -68
  18. data/lib/prawn/{font → fonts}/dfont.rb +5 -11
  19. data/lib/prawn/fonts/otf.rb +11 -0
  20. data/lib/prawn/fonts/ttc.rb +36 -0
  21. data/lib/prawn/{font → fonts}/ttf.rb +87 -68
  22. data/lib/prawn/graphics.rb +120 -80
  23. data/lib/prawn/graphics/blend_mode.rb +65 -0
  24. data/lib/prawn/graphics/cap_style.rb +3 -3
  25. data/lib/prawn/graphics/color.rb +27 -25
  26. data/lib/prawn/graphics/dash.rb +23 -11
  27. data/lib/prawn/graphics/join_style.rb +9 -3
  28. data/lib/prawn/graphics/patterns.rb +197 -67
  29. data/lib/prawn/graphics/transformation.rb +17 -8
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +48 -47
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +39 -30
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +28 -22
  36. data/lib/prawn/images/png.rb +107 -66
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +97 -77
  40. data/lib/prawn/repeater.rb +14 -10
  41. data/lib/prawn/security.rb +81 -61
  42. data/lib/prawn/security/arcfour.rb +2 -2
  43. data/lib/prawn/soft_mask.rb +26 -26
  44. data/lib/prawn/stamp.rb +20 -13
  45. data/lib/prawn/text.rb +68 -52
  46. data/lib/prawn/text/box.rb +11 -8
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +78 -49
  49. data/lib/prawn/text/formatted/box.rb +134 -100
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +121 -63
  52. data/lib/prawn/text/formatted/parser.rb +139 -117
  53. data/lib/prawn/text/formatted/wrap.rb +43 -31
  54. data/lib/prawn/transformation_stack.rb +44 -0
  55. data/lib/prawn/utilities.rb +7 -22
  56. data/lib/prawn/version.rb +2 -2
  57. data/lib/prawn/view.rb +17 -7
  58. data/manual/basic_concepts/adding_pages.rb +6 -7
  59. data/manual/basic_concepts/basic_concepts.rb +31 -22
  60. data/manual/basic_concepts/creation.rb +10 -11
  61. data/manual/basic_concepts/cursor.rb +4 -5
  62. data/manual/basic_concepts/measurement.rb +6 -7
  63. data/manual/basic_concepts/origin.rb +5 -6
  64. data/manual/basic_concepts/other_cursor_helpers.rb +11 -12
  65. data/manual/basic_concepts/view.rb +22 -16
  66. data/manual/bounding_box/bounding_box.rb +29 -24
  67. data/manual/bounding_box/bounds.rb +11 -12
  68. data/manual/bounding_box/canvas.rb +4 -5
  69. data/manual/bounding_box/creation.rb +6 -7
  70. data/manual/bounding_box/indentation.rb +14 -15
  71. data/manual/bounding_box/nesting.rb +24 -17
  72. data/manual/bounding_box/russian_boxes.rb +14 -13
  73. data/manual/bounding_box/stretchy.rb +12 -13
  74. data/manual/contents.rb +28 -22
  75. data/manual/cover.rb +33 -28
  76. data/manual/document_and_page_options/background.rb +11 -13
  77. data/manual/document_and_page_options/document_and_page_options.rb +25 -20
  78. data/manual/document_and_page_options/metadata.rb +18 -16
  79. data/manual/document_and_page_options/page_margins.rb +18 -20
  80. data/manual/document_and_page_options/page_size.rb +13 -12
  81. data/manual/document_and_page_options/print_scaling.rb +17 -15
  82. data/manual/example_helper.rb +5 -4
  83. data/manual/graphics/blend_mode.rb +52 -0
  84. data/manual/graphics/circle_and_ellipse.rb +4 -5
  85. data/manual/graphics/color.rb +7 -9
  86. data/manual/graphics/common_lines.rb +7 -8
  87. data/manual/graphics/fill_and_stroke.rb +4 -5
  88. data/manual/graphics/fill_rules.rb +9 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -39
  91. data/manual/graphics/helper.rb +12 -9
  92. data/manual/graphics/line_width.rb +8 -7
  93. data/manual/graphics/lines_and_curves.rb +7 -8
  94. data/manual/graphics/polygon.rb +6 -8
  95. data/manual/graphics/rectangle.rb +4 -5
  96. data/manual/graphics/rotate.rb +6 -7
  97. data/manual/graphics/scale.rb +14 -15
  98. data/manual/graphics/soft_masks.rb +4 -5
  99. data/manual/graphics/stroke_cap.rb +6 -7
  100. data/manual/graphics/stroke_dash.rb +11 -12
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +9 -10
  103. data/manual/graphics/transparency.rb +7 -8
  104. data/manual/how_to_read_this_manual.rb +6 -6
  105. data/manual/images/absolute_position.rb +6 -7
  106. data/manual/images/fit.rb +7 -8
  107. data/manual/images/horizontal.rb +9 -10
  108. data/manual/images/images.rb +28 -24
  109. data/manual/images/plain_image.rb +5 -6
  110. data/manual/images/scale.rb +9 -10
  111. data/manual/images/vertical.rb +13 -14
  112. data/manual/images/width_and_height.rb +10 -11
  113. data/manual/layout/boxes.rb +5 -6
  114. data/manual/layout/content.rb +7 -8
  115. data/manual/layout/layout.rb +18 -16
  116. data/manual/layout/simple_grid.rb +6 -7
  117. data/manual/outline/add_subsection_to.rb +20 -21
  118. data/manual/outline/insert_section_after.rb +15 -16
  119. data/manual/outline/outline.rb +21 -17
  120. data/manual/outline/sections_and_pages.rb +17 -18
  121. data/manual/repeatable_content/alternate_page_numbering.rb +21 -17
  122. data/manual/repeatable_content/page_numbering.rb +17 -16
  123. data/manual/repeatable_content/repeatable_content.rb +25 -19
  124. data/manual/repeatable_content/repeater.rb +14 -15
  125. data/manual/repeatable_content/stamp.rb +14 -15
  126. data/manual/security/encryption.rb +9 -10
  127. data/manual/security/permissions.rb +19 -14
  128. data/manual/security/security.rb +19 -16
  129. data/manual/table.rb +3 -3
  130. data/manual/text/alignment.rb +16 -17
  131. data/manual/text/color.rb +12 -11
  132. data/manual/text/column_box.rb +9 -10
  133. data/manual/text/fallback_fonts.rb +25 -21
  134. data/manual/text/font.rb +11 -12
  135. data/manual/text/font_size.rb +13 -14
  136. data/manual/text/font_style.rb +7 -8
  137. data/manual/text/formatted_callbacks.rb +25 -21
  138. data/manual/text/formatted_text.rb +33 -25
  139. data/manual/text/free_flowing_text.rb +20 -21
  140. data/manual/text/inline.rb +18 -19
  141. data/manual/text/kerning_and_character_spacing.rb +14 -15
  142. data/manual/text/leading.rb +7 -8
  143. data/manual/text/line_wrapping.rb +37 -18
  144. data/manual/text/paragraph_indentation.rb +13 -14
  145. data/manual/text/positioned_text.rb +15 -16
  146. data/manual/text/registering_families.rb +20 -21
  147. data/manual/text/rendering_and_color.rb +9 -10
  148. data/manual/text/right_to_left_text.rb +26 -19
  149. data/manual/text/rotation.rb +28 -23
  150. data/manual/text/single_usage.rb +8 -9
  151. data/manual/text/text.rb +57 -52
  152. data/manual/text/text_box_excess.rb +20 -17
  153. data/manual/text/text_box_extensions.rb +18 -15
  154. data/manual/text/text_box_overflow.rb +18 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +21 -19
  157. data/prawn.gemspec +45 -33
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +546 -0
  160. data/spec/prawn/document/column_box_spec.rb +75 -0
  161. data/spec/prawn/document/security_spec.rb +176 -0
  162. data/spec/prawn/document_annotations_spec.rb +76 -0
  163. data/spec/prawn/document_destinations_spec.rb +15 -0
  164. data/spec/prawn/document_grid_spec.rb +99 -0
  165. data/spec/prawn/document_reference_spec.rb +27 -0
  166. data/spec/prawn/document_span_spec.rb +36 -0
  167. data/spec/prawn/document_spec.rb +802 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +542 -0
  170. data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
  171. data/spec/prawn/graphics/transparency_spec.rb +81 -0
  172. data/spec/prawn/graphics_spec.rb +837 -0
  173. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  174. data/spec/prawn/image_handler_spec.rb +53 -0
  175. data/spec/prawn/images/jpg_spec.rb +20 -0
  176. data/spec/prawn/images/png_spec.rb +283 -0
  177. data/spec/prawn/images_spec.rb +224 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +412 -0
  180. data/spec/prawn/repeater_spec.rb +165 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +172 -0
  183. data/spec/prawn/text/box_spec.rb +1112 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +846 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +494 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +149 -0
  190. data/spec/prawn/text_rendering_mode_spec.rb +48 -0
  191. data/spec/prawn/text_spacing_spec.rb +95 -0
  192. data/spec/prawn/text_spec.rb +603 -0
  193. data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
  194. data/spec/prawn/transformation_stack_spec.rb +66 -0
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +19 -23
  198. metadata +145 -185
  199. metadata.gz.sig +4 -0
  200. data/data/images/16bit.alpha +0 -0
  201. data/data/images/16bit.color +0 -0
  202. data/data/images/16bit.png +0 -0
  203. data/data/images/arrow.png +0 -0
  204. data/data/images/arrow2.png +0 -0
  205. data/data/images/dice.alpha +0 -0
  206. data/data/images/dice.color +0 -0
  207. data/data/images/dice.png +0 -0
  208. data/data/images/dice_interlaced.png +0 -0
  209. data/data/images/fractal.jpg +0 -0
  210. data/data/images/indexed_color.dat +0 -0
  211. data/data/images/indexed_color.png +0 -0
  212. data/data/images/letterhead.jpg +0 -0
  213. data/data/images/license.md +0 -8
  214. data/data/images/page_white_text.alpha +0 -0
  215. data/data/images/page_white_text.color +0 -0
  216. data/data/images/page_white_text.png +0 -0
  217. data/data/images/pal_bk.png +0 -0
  218. data/data/images/pigs.jpg +0 -0
  219. data/data/images/prawn.png +0 -0
  220. data/data/images/ruport.png +0 -0
  221. data/data/images/ruport_data.dat +0 -0
  222. data/data/images/ruport_transparent.png +0 -0
  223. data/data/images/ruport_type0.png +0 -0
  224. data/data/images/stef.jpg +0 -0
  225. data/data/images/tru256.bmp +0 -0
  226. data/data/images/web-links.dat +0 -1
  227. data/data/images/web-links.png +0 -0
  228. data/data/pdfs/complex_template.pdf +0 -0
  229. data/data/pdfs/contains_ttf_font.pdf +0 -0
  230. data/data/pdfs/encrypted.pdf +0 -0
  231. data/data/pdfs/form.pdf +1 -819
  232. data/data/pdfs/hexagon.pdf +0 -61
  233. data/data/pdfs/indirect_reference.pdf +0 -86
  234. data/data/pdfs/multipage_template.pdf +0 -127
  235. data/data/pdfs/nested_pages.pdf +0 -118
  236. data/data/pdfs/page_without_mediabox.pdf +0 -193
  237. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  238. data/data/pdfs/two_hexagons.pdf +0 -90
  239. data/data/pdfs/version_1_6.pdf +0 -61
  240. data/data/shift_jis_text.txt +0 -1
  241. data/spec/acceptance/png.rb +0 -24
  242. data/spec/annotations_spec.rb +0 -67
  243. data/spec/bounding_box_spec.rb +0 -501
  244. data/spec/column_box_spec.rb +0 -59
  245. data/spec/destinations_spec.rb +0 -13
  246. data/spec/document_spec.rb +0 -742
  247. data/spec/extensions/mocha.rb +0 -45
  248. data/spec/font_metric_cache_spec.rb +0 -52
  249. data/spec/font_spec.rb +0 -475
  250. data/spec/formatted_text_arranger_spec.rb +0 -423
  251. data/spec/formatted_text_box_spec.rb +0 -716
  252. data/spec/formatted_text_fragment_spec.rb +0 -299
  253. data/spec/graphics_spec.rb +0 -666
  254. data/spec/grid_spec.rb +0 -95
  255. data/spec/image_handler_spec.rb +0 -53
  256. data/spec/images_spec.rb +0 -167
  257. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  258. data/spec/jpg_spec.rb +0 -23
  259. data/spec/line_wrap_spec.rb +0 -366
  260. data/spec/measurement_units_spec.rb +0 -22
  261. data/spec/outline_spec.rb +0 -409
  262. data/spec/png_spec.rb +0 -235
  263. data/spec/reference_spec.rb +0 -25
  264. data/spec/repeater_spec.rb +0 -154
  265. data/spec/security_spec.rb +0 -151
  266. data/spec/soft_mask_spec.rb +0 -78
  267. data/spec/span_spec.rb +0 -43
  268. data/spec/stamp_spec.rb +0 -179
  269. data/spec/stroke_styles_spec.rb +0 -208
  270. data/spec/text_at_spec.rb +0 -142
  271. data/spec/text_box_spec.rb +0 -1038
  272. data/spec/text_rendering_mode_spec.rb +0 -45
  273. data/spec/text_spacing_spec.rb +0 -93
  274. data/spec/text_spec.rb +0 -549
  275. data/spec/text_with_inline_formatting_spec.rb +0 -35
  276. data/spec/transparency_spec.rb +0 -91
  277. data/spec/view_spec.rb +0 -42
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'set'
5
+ require 'pathname'
6
+
7
+ describe Prawn::Images do
8
+ let(:pdf) { create_pdf }
9
+ let(:filename) { "#{Prawn::DATADIR}/images/pigs.jpg" }
10
+
11
+ it "onlies embed an image once, even if it's added multiple times" do
12
+ pdf.image filename, at: [100, 100]
13
+ pdf.image filename, at: [300, 300]
14
+
15
+ output = pdf.render
16
+ images = PDF::Inspector::XObject.analyze(output)
17
+ # there should be 2 images in the page resources
18
+ expect(images.page_xobjects.first.size).to eq(2)
19
+ # but only 1 image xobject
20
+ expect(output.scan(%r{/Type /XObject}).size).to eq(1)
21
+ end
22
+
23
+ it 'returns the image info object' do
24
+ info = pdf.image(filename)
25
+
26
+ expect(info).to be_a_kind_of(Prawn::Images::JPG)
27
+
28
+ expect(info.height).to eq(453)
29
+ end
30
+
31
+ it 'accepts IO objects' do
32
+ file = File.open(filename, 'rb')
33
+ info = pdf.image(file)
34
+
35
+ expect(info.height).to eq(453)
36
+ end
37
+
38
+ it 'rewinds IO objects to be able to embed them multiply' do
39
+ file = File.open(filename, 'rb')
40
+
41
+ pdf.image(file)
42
+ info = pdf.image(file)
43
+ expect(info.height).to eq(453)
44
+ end
45
+
46
+ it 'does not close passed-in IO objects' do
47
+ file = File.open(filename, 'rb')
48
+ _info = pdf.image(file)
49
+
50
+ expect(file).to_not be_closed
51
+ end
52
+
53
+ it 'accepts Pathname objects' do
54
+ info = pdf.image(Pathname.new(filename))
55
+ expect(info.height).to eq(453)
56
+ end
57
+
58
+ describe 'closes opened files again after getting Pathnames', issue: 975 do
59
+ describe 'spec with File message spy' do
60
+ let(:not_filename) { 'non-existent filename' }
61
+ let(:pathname_double) { instance_double('Pathname', file?: true) }
62
+
63
+ before do
64
+ file_content = File.new(filename, 'rb').read
65
+ allow(Pathname).to receive(:new).with(not_filename) { pathname_double }
66
+ allow(pathname_double).to receive(:binread) { file_content }
67
+ end
68
+
69
+ it 'uses only binread, which closes opened files' do
70
+ # this implicitly tests that a file handle is closed again
71
+ # because only stubbed binread can be called on not_filename
72
+ _info = pdf.image(not_filename)
73
+ expect(pathname_double).to have_received(:binread)
74
+ end
75
+ end
76
+
77
+ system_has_lsof = system('lsof -v > /dev/null 2>&1')
78
+ system_has_grep = system('grep --version > /dev/null 2>&1')
79
+ if system_has_lsof && system_has_grep
80
+ it 'closes opened files, spec with lsof' do
81
+ gc_was_disabled = GC.disable # GC of File would close the file
82
+ open_before = `lsof -c ruby | grep "#{filename}"`
83
+ _info = pdf.image(Pathname.new(filename))
84
+ open_after = `lsof -c ruby | grep "#{filename}"`
85
+ GC.enable unless gc_was_disabled
86
+ expect(open_after).to eq(open_before)
87
+ end
88
+ end
89
+
90
+ if RUBY_PLATFORM != 'java'
91
+ it 'closes opened files, spec with ObjectSpace' do
92
+ gc_was_disabled = GC.disable # GC of File would close the file
93
+ open_before = ObjectSpace.each_object(File).count { |f| !f.closed? }
94
+ _info = pdf.image(Pathname.new(filename))
95
+ open_after = ObjectSpace.each_object(File).count { |f| !f.closed? }
96
+ GC.enable unless gc_was_disabled
97
+ expect(open_after).to eq(open_before)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe 'setting the length of the bytestream' do
103
+ it 'correctlies work with images from Pathname objects' do
104
+ pdf.image(Pathname.new(filename))
105
+ expect(pdf).to have_parseable_xobjects
106
+ end
107
+
108
+ it 'correctlies work with images from IO objects' do
109
+ pdf.image(File.open(filename, 'rb'))
110
+ expect(pdf).to have_parseable_xobjects
111
+ end
112
+
113
+ it 'correctlies work with images from IO objects not set to mode rb' do
114
+ pdf.image(File.open(filename, 'r'))
115
+ expect(pdf).to have_parseable_xobjects
116
+ end
117
+ end
118
+
119
+ it 'raise_errors an UnsupportedImageType if passed a BMP' do
120
+ filename = "#{Prawn::DATADIR}/images/tru256.bmp"
121
+ expect { pdf.image filename, at: [100, 100] }
122
+ .to raise_error(Prawn::Errors::UnsupportedImageType)
123
+ end
124
+
125
+ it 'raise_errors an UnsupportedImageType if passed an interlaced PNG' do
126
+ filename = "#{Prawn::DATADIR}/images/dice_interlaced.png"
127
+ expect { pdf.image filename, at: [100, 100] }
128
+ .to raise_error(Prawn::Errors::UnsupportedImageType)
129
+ end
130
+
131
+ it 'bumps PDF version to 1.5 or greater on embedding 16-bit PNGs' do
132
+ pdf.image "#{Prawn::DATADIR}/images/16bit.png"
133
+ expect(pdf.state.version).to be >= 1.5
134
+ end
135
+
136
+ it 'embeds 16-bit alpha channels for 16-bit PNGs' do
137
+ pdf.image "#{Prawn::DATADIR}/images/16bit.png"
138
+
139
+ output = pdf.render
140
+ expect(output).to match(%r{/BitsPerComponent 16})
141
+ expect(output).to_not match(%r{/BitsPerComponent 8})
142
+ end
143
+
144
+ it 'flows an image to a new page if it will not fit on a page' do
145
+ pdf.image filename, fit: [600, 600]
146
+ pdf.image filename, fit: [600, 600]
147
+ output = StringIO.new(pdf.render, 'r+')
148
+ hash = PDF::Reader::ObjectHash.new(output)
149
+ pages = hash.values.find do |obj|
150
+ obj.is_a?(Hash) && obj[:Type] == :Pages
151
+ end[:Kids]
152
+ expect(pages.size).to eq(2)
153
+ expect(hash[pages[0]][:Resources][:XObject].keys).to eq([:I1])
154
+ expect(hash[pages[1]][:Resources][:XObject].keys).to eq([:I2])
155
+ end
156
+
157
+ it 'does not flow an image to a new page if it will fit on one page' do
158
+ pdf.image filename, fit: [400, 400]
159
+ pdf.image filename, fit: [400, 400]
160
+ output = StringIO.new(pdf.render, 'r+')
161
+ hash = PDF::Reader::ObjectHash.new(output)
162
+ pages = hash.values.find do |obj|
163
+ obj.is_a?(Hash) && obj[:Type] == :Pages
164
+ end[:Kids]
165
+ expect(pages.size).to eq(1)
166
+ expect(Set.new(hash[pages[0]][:Resources][:XObject].keys)).to eq(
167
+ Set.new(%i[I1 I2])
168
+ )
169
+ end
170
+
171
+ it 'does not start a new page just for a stretchy bounding box' do
172
+ allow(pdf).to receive(:start_new_page)
173
+ pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
174
+ pdf.image filename
175
+ end
176
+ expect(pdf).to_not have_received(:start_new_page)
177
+ end
178
+
179
+ describe ':fit option' do
180
+ it 'fits inside the defined constraints' do
181
+ info = pdf.image filename, fit: [100, 400]
182
+ expect(info.scaled_width).to be <= 100
183
+ expect(info.scaled_height).to be <= 400
184
+
185
+ info = pdf.image filename, fit: [400, 100]
186
+ expect(info.scaled_width).to be <= 400
187
+ expect(info.scaled_height).to be <= 100
188
+
189
+ info = pdf.image filename, fit: [604, 453]
190
+ expect(info.scaled_width).to eq(604)
191
+ expect(info.scaled_height).to eq(453)
192
+ end
193
+
194
+ it 'moves text position' do
195
+ y = pdf.y
196
+ pdf.image filename, fit: [100, 400]
197
+ expect(pdf.y).to be < y
198
+ end
199
+ end
200
+
201
+ describe ':at option' do
202
+ it 'does not move text position' do
203
+ y = pdf.y
204
+ pdf.image filename, at: [100, 400]
205
+ expect(pdf.y).to eq(y)
206
+ end
207
+ end
208
+
209
+ describe ':width option without :height option' do
210
+ it 'scales the width and height' do
211
+ info = pdf.image filename, width: 225
212
+ expect(info.scaled_height).to eq(168.75)
213
+ expect(info.scaled_width).to eq(225.0)
214
+ end
215
+ end
216
+
217
+ describe ':height option without :width option' do
218
+ it 'scales the width and height' do
219
+ info = pdf.image filename, height: 225
220
+ expect(info.scaled_height).to eq(225.0)
221
+ expect(info.scaled_width).to eq(300.0)
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'prawn/measurement_extensions'
5
+
6
+ describe Prawn::Measurements do
7
+ describe 'Numeric extensions' do
8
+ it 'converts units to PostScriptPoints' do
9
+ expect(1.mm).to be_within(0.000000001).of(2.834645669)
10
+ expect(1.mm).to eq(72 / 25.4)
11
+ expect(2.mm).to eq(2 * 72 / 25.4)
12
+ expect(3.mm).to eq(3 * 72 / 25.4)
13
+ expect(-3.mm).to eq(-3 * 72 / 25.4)
14
+ expect(1.cm).to eq(10 * 72 / 25.4)
15
+ expect(1.dm).to eq(100 * 72 / 25.4)
16
+ expect(1.m).to eq(1000 * 72 / 25.4)
17
+
18
+ expect(1.in).to eq(72)
19
+ expect(1.ft).to eq(72 * 12)
20
+ expect(1.yd).to eq(72 * 12 * 3)
21
+ expect(1.pt).to eq(1)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,412 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Outline do
6
+ let(:pdf) do
7
+ Prawn::Document.new do
8
+ text 'Page 1. This is the first Chapter. '
9
+ start_new_page
10
+ text 'Page 2. More in the first Chapter. '
11
+ start_new_page
12
+ outline.define do
13
+ section 'Chapter 1', destination: 1, closed: true do
14
+ page destination: 1, title: 'Page 1'
15
+ page destination: 2, title: 'Page 2'
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ let(:output) { StringIO.new(pdf.render, 'r+') }
22
+ let(:hash) { PDF::Reader::ObjectHash.new(output) }
23
+ let(:outline_root) do
24
+ hash.values.find do |obj|
25
+ obj.is_a?(Hash) && obj[:Type] == :Outlines
26
+ end
27
+ end
28
+ let(:pages) do
29
+ hash.values.find do |obj|
30
+ obj.is_a?(Hash) && obj[:Type] == :Pages
31
+ end[:Kids]
32
+ end
33
+ let(:section1) { find_by_title('Chapter 1') }
34
+ let(:page1) { find_by_title('Page 1') }
35
+ let(:page2) { find_by_title('Page 2') }
36
+ let(:section2) { find_by_title('Added Section') }
37
+ let(:page3) { find_by_title('Page 3') }
38
+ let(:inserted_page) { find_by_title('Inserted Page') }
39
+ let(:subsection) { find_by_title('Added SubSection') }
40
+ let(:added_page3) { find_by_title('Added Page 3') }
41
+ let(:custom_dest) { find_by_title('Custom Destination') }
42
+
43
+ describe 'outline encoding' do
44
+ it 'stores all outline titles as UTF-16' do
45
+ hash.each_value do |obj|
46
+ next unless obj.is_a?(Hash) && obj[:Title]
47
+
48
+ title = obj[:Title].dup
49
+ title.force_encoding(Encoding::UTF_16LE)
50
+ expect(title.valid_encoding?).to eq(true)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#generate_outline' do
56
+ it 'creates a root outline dictionary item' do
57
+ expect(outline_root).to_not be_nil
58
+ end
59
+
60
+ it 'sets the first and last top items of the root outline dictionary '\
61
+ 'item' do
62
+ expect(referenced_object(outline_root[:First])).to eq(section1)
63
+ expect(referenced_object(outline_root[:Last])).to eq(section1)
64
+ end
65
+
66
+ describe '#create_outline_item' do
67
+ it 'creates outline items for each section and page' do
68
+ expect(section1).to_not be_nil
69
+ expect(page1).to_not be_nil
70
+ expect(page2).to_not be_nil
71
+ end
72
+ end
73
+
74
+ describe '#set_relations, #set_variables_for_block, and #reset_parent' do
75
+ it 'links sibling items' do
76
+ expect(referenced_object(page1[:Next])).to eq(page2)
77
+ expect(referenced_object(page2[:Prev])).to eq(page1)
78
+ end
79
+
80
+ it 'links child items to parent item' do
81
+ expect(referenced_object(page1[:Parent])).to eq(section1)
82
+ expect(referenced_object(page2[:Parent])).to eq(section1)
83
+ end
84
+
85
+ it 'sets the first and last child items for parent item' do
86
+ expect(referenced_object(section1[:First])).to eq(page1)
87
+ expect(referenced_object(section1[:Last])).to eq(page2)
88
+ end
89
+ end
90
+
91
+ describe '#increase_count' do
92
+ it 'adds the count of all descendant items' do
93
+ expect(outline_root[:Count]).to eq(3)
94
+ expect(section1[:Count].abs).to eq(2)
95
+ expect(page1[:Count]).to eq(0)
96
+ expect(page2[:Count]).to eq(0)
97
+ end
98
+ end
99
+
100
+ describe 'closed option' do
101
+ it "sets the item's integer count to negative" do
102
+ expect(section1[:Count]).to eq(-2)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'adding a custom destination' do
108
+ before do
109
+ pdf.start_new_page
110
+ pdf.text 'Page 3 with a destination'
111
+ pdf.add_dest('customdest', pdf.dest_xyz(200, 200))
112
+ destination = pdf.dest_xyz(200, 200)
113
+ pdf.outline.update do
114
+ page destination: destination, title: 'Custom Destination'
115
+ end
116
+ end
117
+
118
+ it 'creates an outline item' do
119
+ expect(custom_dest).to_not be_nil
120
+ end
121
+
122
+ it 'references the custom destination' do
123
+ expect(referenced_object(custom_dest[:Dest].first))
124
+ .to eq(referenced_object(pages.last))
125
+ end
126
+ end
127
+
128
+ describe 'addding a section later with outline#section' do
129
+ before do
130
+ pdf.start_new_page
131
+ pdf.text 'Page 3. An added section '
132
+ pdf.outline.update do
133
+ section 'Added Section', destination: 3 do
134
+ page destination: 3, title: 'Page 3'
135
+ end
136
+ end
137
+ end
138
+
139
+ it 'adds new outline items to document' do
140
+ expect(section2).to_not be_nil
141
+ expect(page3).to_not be_nil
142
+ end
143
+
144
+ it 'resets the last items for root outline dictionary' do
145
+ expect(referenced_object(outline_root[:First])).to eq(section1)
146
+ expect(referenced_object(outline_root[:Last])).to eq(section2)
147
+ end
148
+
149
+ it 'resets the next relation for the previous last top level item' do
150
+ expect(referenced_object(section1[:Next])).to eq(section2)
151
+ end
152
+
153
+ it 'sets the previous relation of the addded to section' do
154
+ expect(referenced_object(section2[:Prev])).to eq(section1)
155
+ end
156
+
157
+ it 'increases the count of root outline dictionary' do
158
+ expect(outline_root[:Count]).to eq(5)
159
+ end
160
+ end
161
+
162
+ describe '#outline.add_subsection_to' do
163
+ context 'when positioned last' do
164
+ before do
165
+ pdf.start_new_page
166
+ pdf.text 'Page 3. An added subsection '
167
+ pdf.outline.update do
168
+ add_subsection_to 'Chapter 1' do
169
+ section 'Added SubSection', destination: 3 do
170
+ page destination: 3, title: 'Added Page 3'
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ it 'adds new outline items to document' do
177
+ expect(subsection).to_not be_nil
178
+ expect(added_page3).to_not be_nil
179
+ end
180
+
181
+ it 'resets the last item for parent item dictionary' do
182
+ expect(referenced_object(section1[:First])).to eq(page1)
183
+ expect(referenced_object(section1[:Last])).to eq(subsection)
184
+ end
185
+
186
+ it "sets the prev relation for the new subsection to its parent's old "\
187
+ 'last item' do
188
+ expect(referenced_object(subsection[:Prev])).to eq(page2)
189
+ end
190
+
191
+ it "the subsection should become the next relation for its parent's old "\
192
+ 'last item' do
193
+ expect(referenced_object(page2[:Next])).to eq(subsection)
194
+ end
195
+
196
+ it 'sets the first relation for the new subsection' do
197
+ expect(referenced_object(subsection[:First])).to eq(added_page3)
198
+ end
199
+
200
+ it 'sets the correct last relation of the added to section' do
201
+ expect(referenced_object(subsection[:Last])).to eq(added_page3)
202
+ end
203
+
204
+ it 'increases the count of root outline dictionary' do
205
+ expect(outline_root[:Count]).to eq(5)
206
+ end
207
+ end
208
+
209
+ context 'when positioned first' do
210
+ before do
211
+ pdf.start_new_page
212
+ pdf.text 'Page 3. An added subsection '
213
+ pdf.outline.update do
214
+ add_subsection_to 'Chapter 1', :first do
215
+ section 'Added SubSection', destination: 3 do
216
+ page destination: 3, title: 'Added Page 3'
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ it 'adds new outline items to document' do
223
+ expect(subsection).to_not be_nil
224
+ expect(added_page3).to_not be_nil
225
+ end
226
+
227
+ it 'resets the first item for parent item dictionary' do
228
+ expect(referenced_object(section1[:First])).to eq(subsection)
229
+ expect(referenced_object(section1[:Last])).to eq(page2)
230
+ end
231
+
232
+ it "sets the next relation for the new subsection to its parent's old "\
233
+ 'first item' do
234
+ expect(referenced_object(subsection[:Next])).to eq(page1)
235
+ end
236
+
237
+ it "the subsection should become the prev relation for its parent's old "\
238
+ 'first item' do
239
+ expect(referenced_object(page1[:Prev])).to eq(subsection)
240
+ end
241
+
242
+ it 'sets the first relation for the new subsection' do
243
+ expect(referenced_object(subsection[:First])).to eq(added_page3)
244
+ end
245
+
246
+ it 'sets the correct last relation of the added to section' do
247
+ expect(referenced_object(subsection[:Last])).to eq(added_page3)
248
+ end
249
+
250
+ it 'increases the count of root outline dictionary' do
251
+ expect(outline_root[:Count]).to eq(5)
252
+ end
253
+ end
254
+
255
+ it 'requires an existing title' do
256
+ expect do
257
+ pdf.go_to_page 1
258
+ pdf.start_new_page
259
+ pdf.text 'Inserted Page'
260
+ pdf.outline.update do
261
+ add_subsection_to 'Wrong page' do
262
+ page page_number, title: 'Inserted Page'
263
+ end
264
+ end
265
+ render_and_find_objects
266
+ end.to raise_error(Prawn::Errors::UnknownOutlineTitle)
267
+ end
268
+ end
269
+
270
+ describe '#outline.insert_section_after' do
271
+ describe 'inserting in the middle of another section' do
272
+ before do
273
+ pdf.go_to_page 1
274
+ pdf.start_new_page
275
+ pdf.text 'Inserted Page'
276
+ pdf.outline.update do
277
+ insert_section_after 'Page 1' do
278
+ page destination: page_number, title: 'Inserted Page'
279
+ end
280
+ end
281
+ end
282
+
283
+ it 'inserts new outline items to document' do
284
+ expect(inserted_page).to_not be_nil
285
+ end
286
+
287
+ it 'adjusts the count of all ancestors' do
288
+ expect(outline_root[:Count]).to eq(4)
289
+ expect(section1[:Count].abs).to eq(3)
290
+ end
291
+
292
+ describe '#adjust_relations' do
293
+ it 'resets the sibling relations of adjoining items to inserted item' do
294
+ expect(referenced_object(page1[:Next])).to eq(inserted_page)
295
+ expect(referenced_object(page2[:Prev])).to eq(inserted_page)
296
+ end
297
+
298
+ it 'sets the sibling relation of added item to adjoining items' do
299
+ expect(referenced_object(inserted_page[:Next])).to eq(page2)
300
+ expect(referenced_object(inserted_page[:Prev])).to eq(page1)
301
+ end
302
+
303
+ it 'does not affect the first and last relations of parent item' do
304
+ expect(referenced_object(section1[:First])).to eq(page1)
305
+ expect(referenced_object(section1[:Last])).to eq(page2)
306
+ end
307
+ end
308
+
309
+ context 'when adding another section afterwards' do
310
+ it 'has reset the root position so that a new section is added at '\
311
+ 'the end of root sections' do
312
+ pdf.start_new_page
313
+ pdf.text 'Another Inserted Page'
314
+ pdf.outline.update do
315
+ section 'Added Section' do
316
+ page destination: page_number, title: 'Inserted Page'
317
+ end
318
+ end
319
+ expect(referenced_object(outline_root[:Last])).to eq(section2)
320
+ expect(referenced_object(section1[:Next])).to eq(section2)
321
+ end
322
+ end
323
+ end
324
+
325
+ describe 'inserting at the end of another section' do
326
+ before do
327
+ pdf.go_to_page 2
328
+ pdf.start_new_page
329
+ pdf.text 'Inserted Page'
330
+ pdf.outline.update do
331
+ insert_section_after 'Page 2' do
332
+ page destination: page_number, title: 'Inserted Page'
333
+ end
334
+ end
335
+ end
336
+
337
+ describe '#adjust_relations' do
338
+ it 'resets the sibling relations of adjoining item to inserted item' do
339
+ expect(referenced_object(page2[:Next])).to eq(inserted_page)
340
+ end
341
+
342
+ it 'sets the sibling relation of added item to adjoining items' do
343
+ expect(referenced_object(inserted_page[:Next])).to be_nil
344
+ expect(referenced_object(inserted_page[:Prev])).to eq(page2)
345
+ end
346
+
347
+ it 'adjusts the last relation of parent item' do
348
+ expect(referenced_object(section1[:Last])).to eq(inserted_page)
349
+ end
350
+ end
351
+ end
352
+
353
+ it 'requires an existing title' do
354
+ expect do
355
+ pdf.go_to_page 1
356
+ pdf.start_new_page
357
+ pdf.text 'Inserted Page'
358
+ pdf.outline.update do
359
+ insert_section_after 'Wrong page' do
360
+ page destination: page_number, title: 'Inserted Page'
361
+ end
362
+ end
363
+ end.to raise_error(Prawn::Errors::UnknownOutlineTitle)
364
+ end
365
+ end
366
+
367
+ describe '#page' do
368
+ it 'requires a title option to be set' do
369
+ expect do
370
+ Prawn::Document.new do
371
+ text 'Page 1. This is the first Chapter. '
372
+ outline.define do
373
+ page destination: 1, title: nil
374
+ end
375
+ end
376
+ end.to raise_error(Prawn::Errors::RequiredOption)
377
+ end
378
+ end
379
+
380
+ describe 'foreign character encoding' do
381
+ let(:hash) do
382
+ pdf = Prawn::Document.new do
383
+ outline.define do
384
+ section 'La pomme croquée', destination: 1, closed: true
385
+ end
386
+ end
387
+ PDF::Reader::ObjectHash.new(StringIO.new(pdf.render, 'r+'))
388
+ end
389
+
390
+ it 'handles other encodings for the title' do
391
+ object = find_by_title('La pomme croquée')
392
+ expect(object).to_not be_nil
393
+ end
394
+ end
395
+
396
+ # Outline titles are stored as UTF-16. This method accepts a UTF-8 outline
397
+ # title and returns the PDF Object that contains an outline with that name
398
+ def find_by_title(title)
399
+ hash.values.find do |obj|
400
+ next unless obj.is_a?(Hash) && obj[:Title]
401
+
402
+ title_codepoints = obj[:Title].unpack('n*')
403
+ title_codepoints.shift
404
+ utf8_title = title_codepoints.pack('U*')
405
+ utf8_title == title ? obj : nil
406
+ end
407
+ end
408
+
409
+ def referenced_object(reference)
410
+ hash[reference]
411
+ end
412
+ end