prawn 2.1.0 → 2.4.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 (280) 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 +9 -41
  7. data/lib/prawn.rb +37 -49
  8. data/lib/prawn/document.rb +193 -141
  9. data/lib/prawn/document/bounding_box.rb +50 -30
  10. data/lib/prawn/document/column_box.rb +7 -7
  11. data/lib/prawn/document/internals.rb +8 -6
  12. data/lib/prawn/document/span.rb +22 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +104 -69
  16. data/lib/prawn/font_metric_cache.rb +20 -13
  17. data/lib/prawn/{font → fonts}/afm.rb +108 -72
  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 +126 -81
  22. data/lib/prawn/graphics.rb +119 -81
  23. data/lib/prawn/graphics/blend_mode.rb +9 -8
  24. data/lib/prawn/graphics/cap_style.rb +3 -3
  25. data/lib/prawn/graphics/color.rb +43 -39
  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 +204 -102
  29. data/lib/prawn/graphics/transformation.rb +15 -9
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +84 -48
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +60 -49
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +31 -22
  36. data/lib/prawn/images/png.rb +67 -63
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +98 -77
  40. data/lib/prawn/repeater.rb +15 -11
  41. data/lib/prawn/security.rb +93 -70
  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 +76 -60
  46. data/lib/prawn/text/box.rb +18 -14
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +80 -40
  49. data/lib/prawn/text/formatted/box.rb +140 -101
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +128 -67
  52. data/lib/prawn/text/formatted/parser.rb +147 -123
  53. data/lib/prawn/text/formatted/wrap.rb +48 -32
  54. data/lib/prawn/transformation_stack.rb +7 -5
  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 +7 -8
  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 +7 -8
  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 +25 -18
  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 +15 -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 +18 -15
  82. data/manual/example_helper.rb +5 -4
  83. data/manual/graphics/blend_mode.rb +12 -9
  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 +5 -6
  88. data/manual/graphics/fill_rules.rb +10 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -40
  91. data/manual/graphics/helper.rb +19 -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 +3 -4
  99. data/manual/graphics/stroke_cap.rb +6 -7
  100. data/manual/graphics/stroke_dash.rb +15 -16
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +10 -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 +10 -11
  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 +16 -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 +21 -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 +10 -8
  137. data/manual/text/formatted_callbacks.rb +33 -24
  138. data/manual/text/formatted_text.rb +36 -25
  139. data/manual/text/free_flowing_text.rb +22 -23
  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 +12 -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 +33 -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 +20 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +27 -25
  157. data/prawn.gemspec +41 -34
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +550 -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 +44 -0
  167. data/spec/prawn/document_spec.rb +805 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +544 -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 +872 -0
  173. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  174. data/spec/{image_handler_spec.rb → prawn/image_handler_spec.rb} +14 -14
  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 +229 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +512 -0
  180. data/spec/prawn/repeater_spec.rb +166 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +173 -0
  183. data/spec/prawn/text/box_spec.rb +1110 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +150 -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/{transformation_stack_spec.rb → prawn/transformation_stack_spec.rb} +22 -19
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +18 -19
  198. metadata +102 -222
  199. metadata.gz.sig +0 -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/blend_modes_bottom_layer.jpg +0 -0
  206. data/data/images/blend_modes_top_layer.jpg +0 -0
  207. data/data/images/dice.alpha +0 -0
  208. data/data/images/dice.color +0 -0
  209. data/data/images/dice.png +0 -0
  210. data/data/images/dice_interlaced.png +0 -0
  211. data/data/images/fractal.jpg +0 -0
  212. data/data/images/indexed_color.dat +0 -0
  213. data/data/images/indexed_color.png +0 -0
  214. data/data/images/indexed_transparency.png +0 -0
  215. data/data/images/indexed_transparency_alpha.dat +0 -0
  216. data/data/images/indexed_transparency_color.dat +0 -0
  217. data/data/images/letterhead.jpg +0 -0
  218. data/data/images/license.md +0 -8
  219. data/data/images/page_white_text.alpha +0 -0
  220. data/data/images/page_white_text.color +0 -0
  221. data/data/images/page_white_text.png +0 -0
  222. data/data/images/pigs.jpg +0 -0
  223. data/data/images/prawn.png +0 -0
  224. data/data/images/ruport.png +0 -0
  225. data/data/images/ruport_data.dat +0 -0
  226. data/data/images/ruport_transparent.png +0 -0
  227. data/data/images/ruport_type0.png +0 -0
  228. data/data/images/stef.jpg +0 -0
  229. data/data/images/tru256.bmp +0 -0
  230. data/data/images/web-links.dat +0 -1
  231. data/data/images/web-links.png +0 -0
  232. data/data/pdfs/complex_template.pdf +0 -0
  233. data/data/pdfs/contains_ttf_font.pdf +0 -0
  234. data/data/pdfs/encrypted.pdf +0 -0
  235. data/data/pdfs/form.pdf +1 -819
  236. data/data/pdfs/hexagon.pdf +0 -61
  237. data/data/pdfs/indirect_reference.pdf +0 -86
  238. data/data/pdfs/multipage_template.pdf +0 -127
  239. data/data/pdfs/nested_pages.pdf +0 -118
  240. data/data/pdfs/page_without_mediabox.pdf +0 -193
  241. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  242. data/data/pdfs/two_hexagons.pdf +0 -90
  243. data/data/pdfs/version_1_6.pdf +0 -61
  244. data/data/shift_jis_text.txt +0 -1
  245. data/spec/acceptance/png_spec.rb +0 -35
  246. data/spec/annotations_spec.rb +0 -67
  247. data/spec/blend_mode_spec.rb +0 -71
  248. data/spec/bounding_box_spec.rb +0 -501
  249. data/spec/column_box_spec.rb +0 -59
  250. data/spec/destinations_spec.rb +0 -13
  251. data/spec/document_spec.rb +0 -738
  252. data/spec/font_metric_cache_spec.rb +0 -52
  253. data/spec/font_spec.rb +0 -475
  254. data/spec/formatted_text_arranger_spec.rb +0 -452
  255. data/spec/formatted_text_box_spec.rb +0 -716
  256. data/spec/formatted_text_fragment_spec.rb +0 -299
  257. data/spec/graphics_spec.rb +0 -705
  258. data/spec/grid_spec.rb +0 -95
  259. data/spec/images_spec.rb +0 -167
  260. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  261. data/spec/jpg_spec.rb +0 -23
  262. data/spec/line_wrap_spec.rb +0 -366
  263. data/spec/measurement_units_spec.rb +0 -22
  264. data/spec/outline_spec.rb +0 -409
  265. data/spec/png_spec.rb +0 -257
  266. data/spec/reference_spec.rb +0 -25
  267. data/spec/repeater_spec.rb +0 -154
  268. data/spec/security_spec.rb +0 -151
  269. data/spec/soft_mask_spec.rb +0 -78
  270. data/spec/span_spec.rb +0 -43
  271. data/spec/stamp_spec.rb +0 -179
  272. data/spec/stroke_styles_spec.rb +0 -208
  273. data/spec/text_at_spec.rb +0 -142
  274. data/spec/text_box_spec.rb +0 -1042
  275. data/spec/text_rendering_mode_spec.rb +0 -45
  276. data/spec/text_spacing_spec.rb +0 -93
  277. data/spec/text_spec.rb +0 -543
  278. data/spec/text_with_inline_formatting_spec.rb +0 -35
  279. data/spec/transparency_spec.rb +0 -91
  280. data/spec/view_spec.rb +0 -42
@@ -0,0 +1,229 @@
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
+ info =
33
+ File.open(filename, 'rb') do |file|
34
+ pdf.image(file)
35
+ end
36
+
37
+ expect(info.height).to eq(453)
38
+ end
39
+
40
+ it 'rewinds IO objects to be able to embed them multiply' do
41
+ info =
42
+ File.open(filename, 'rb') do |file|
43
+ pdf.image(file)
44
+ pdf.image(file)
45
+ end
46
+
47
+ expect(info.height).to eq(453)
48
+ end
49
+
50
+ it 'does not close passed-in IO objects' do
51
+ File.open(filename, 'rb') do |file|
52
+ pdf.image(file)
53
+
54
+ expect(file).to_not be_closed
55
+ end
56
+ end
57
+
58
+ it 'accepts Pathname objects' do
59
+ info = pdf.image(Pathname.new(filename))
60
+ expect(info.height).to eq(453)
61
+ end
62
+
63
+ describe 'closes opened files again after getting Pathnames', issue: 975 do
64
+ describe 'spec with File message spy' do
65
+ let(:not_filename) { 'non-existent filename' }
66
+ let(:pathname_double) { instance_double('Pathname', file?: true) }
67
+
68
+ before do
69
+ file_content = File.new(filename, 'rb').read
70
+ allow(Pathname).to receive(:new).with(not_filename) { pathname_double }
71
+ allow(pathname_double).to receive(:binread) { file_content }
72
+ end
73
+
74
+ it 'uses only binread, which closes opened files' do
75
+ # this implicitly tests that a file handle is closed again
76
+ # because only stubbed binread can be called on not_filename
77
+ _info = pdf.image(not_filename)
78
+ expect(pathname_double).to have_received(:binread)
79
+ end
80
+ end
81
+
82
+ system_has_lsof = system('lsof -v > /dev/null 2>&1')
83
+ system_has_grep = system('grep --version > /dev/null 2>&1')
84
+ if system_has_lsof && system_has_grep
85
+ it 'closes opened files, spec with lsof' do
86
+ gc_was_disabled = GC.disable # GC of File would close the file
87
+ open_before = `lsof -c ruby | grep "#{filename}"`
88
+ _info = pdf.image(Pathname.new(filename))
89
+ open_after = `lsof -c ruby | grep "#{filename}"`
90
+ GC.enable unless gc_was_disabled
91
+ expect(open_after).to eq(open_before)
92
+ end
93
+ end
94
+
95
+ if RUBY_PLATFORM != 'java'
96
+ it 'closes opened files, spec with ObjectSpace' do
97
+ gc_was_disabled = GC.disable # GC of File would close the file
98
+ open_before = ObjectSpace.each_object(File).count { |f| !f.closed? }
99
+ _info = pdf.image(Pathname.new(filename))
100
+ open_after = ObjectSpace.each_object(File).count { |f| !f.closed? }
101
+ GC.enable unless gc_was_disabled
102
+ expect(open_after).to eq(open_before)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'setting the length of the bytestream' do
108
+ it 'correctlies work with images from Pathname objects' do
109
+ pdf.image(Pathname.new(filename))
110
+ expect(pdf).to have_parseable_xobjects
111
+ end
112
+
113
+ it 'correctlies work with images from IO objects' do
114
+ pdf.image(File.open(filename, 'rb'))
115
+ expect(pdf).to have_parseable_xobjects
116
+ end
117
+
118
+ it 'correctlies work with images from IO objects not set to mode rb' do
119
+ pdf.image(File.open(filename, 'r'))
120
+ expect(pdf).to have_parseable_xobjects
121
+ end
122
+ end
123
+
124
+ it 'raise_errors an UnsupportedImageType if passed a BMP' do
125
+ filename = "#{Prawn::DATADIR}/images/tru256.bmp"
126
+ expect { pdf.image filename, at: [100, 100] }
127
+ .to raise_error(Prawn::Errors::UnsupportedImageType)
128
+ end
129
+
130
+ it 'raise_errors an UnsupportedImageType if passed an interlaced PNG' do
131
+ filename = "#{Prawn::DATADIR}/images/dice_interlaced.png"
132
+ expect { pdf.image filename, at: [100, 100] }
133
+ .to raise_error(Prawn::Errors::UnsupportedImageType)
134
+ end
135
+
136
+ it 'bumps PDF version to 1.5 or greater on embedding 16-bit PNGs' do
137
+ pdf.image "#{Prawn::DATADIR}/images/16bit.png"
138
+ expect(pdf.state.version).to be >= 1.5
139
+ end
140
+
141
+ it 'embeds 16-bit alpha channels for 16-bit PNGs' do
142
+ pdf.image "#{Prawn::DATADIR}/images/16bit.png"
143
+
144
+ output = pdf.render
145
+ expect(output).to match(%r{/BitsPerComponent 16})
146
+ expect(output).to_not match(%r{/BitsPerComponent 8})
147
+ end
148
+
149
+ it 'flows an image to a new page if it will not fit on a page' do
150
+ pdf.image filename, fit: [600, 600]
151
+ pdf.image filename, fit: [600, 600]
152
+ output = StringIO.new(pdf.render, 'r+')
153
+ hash = PDF::Reader::ObjectHash.new(output)
154
+ pages = hash.values.find do |obj|
155
+ obj.is_a?(Hash) && obj[:Type] == :Pages
156
+ end[:Kids]
157
+ expect(pages.size).to eq(2)
158
+ expect(hash[pages[0]][:Resources][:XObject].keys).to eq([:I1])
159
+ expect(hash[pages[1]][:Resources][:XObject].keys).to eq([:I2])
160
+ end
161
+
162
+ it 'does not flow an image to a new page if it will fit on one page' do
163
+ pdf.image filename, fit: [400, 400]
164
+ pdf.image filename, fit: [400, 400]
165
+ output = StringIO.new(pdf.render, 'r+')
166
+ hash = PDF::Reader::ObjectHash.new(output)
167
+ pages = hash.values.find do |obj|
168
+ obj.is_a?(Hash) && obj[:Type] == :Pages
169
+ end[:Kids]
170
+ expect(pages.size).to eq(1)
171
+ expect(Set.new(hash[pages[0]][:Resources][:XObject].keys)).to eq(
172
+ Set.new(%i[I1 I2])
173
+ )
174
+ end
175
+
176
+ it 'does not start a new page just for a stretchy bounding box' do
177
+ allow(pdf).to receive(:start_new_page)
178
+ pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
179
+ pdf.image filename
180
+ end
181
+ expect(pdf).to_not have_received(:start_new_page)
182
+ end
183
+
184
+ describe ':fit option' do
185
+ it 'fits inside the defined constraints' do
186
+ info = pdf.image filename, fit: [100, 400]
187
+ expect(info.scaled_width).to be <= 100
188
+ expect(info.scaled_height).to be <= 400
189
+
190
+ info = pdf.image filename, fit: [400, 100]
191
+ expect(info.scaled_width).to be <= 400
192
+ expect(info.scaled_height).to be <= 100
193
+
194
+ info = pdf.image filename, fit: [604, 453]
195
+ expect(info.scaled_width).to eq(604)
196
+ expect(info.scaled_height).to eq(453)
197
+ end
198
+
199
+ it 'moves text position' do
200
+ y = pdf.y
201
+ pdf.image filename, fit: [100, 400]
202
+ expect(pdf.y).to be < y
203
+ end
204
+ end
205
+
206
+ describe ':at option' do
207
+ it 'does not move text position' do
208
+ y = pdf.y
209
+ pdf.image filename, at: [100, 400]
210
+ expect(pdf.y).to eq(y)
211
+ end
212
+ end
213
+
214
+ describe ':width option without :height option' do
215
+ it 'scales the width and height' do
216
+ info = pdf.image filename, width: 225
217
+ expect(info.scaled_height).to eq(168.75)
218
+ expect(info.scaled_width).to eq(225.0)
219
+ end
220
+ end
221
+
222
+ describe ':height option without :width option' do
223
+ it 'scales the width and height' do
224
+ info = pdf.image filename, height: 225
225
+ expect(info.scaled_height).to eq(225.0)
226
+ expect(info.scaled_width).to eq(300.0)
227
+ end
228
+ end
229
+ 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,512 @@
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(:hash) do
22
+ output = StringIO.new(pdf.render, 'r+')
23
+ PDF::Reader::ObjectHash.new(output)
24
+ end
25
+ let(:outline_root) do
26
+ hash.values.find do |obj|
27
+ obj.is_a?(Hash) && obj[:Type] == :Outlines
28
+ end
29
+ end
30
+
31
+ describe 'outline encoding' do
32
+ it 'stores all outline titles as UTF-16' do
33
+ hash.each_value do |obj|
34
+ next unless obj.is_a?(Hash) && obj[:Title]
35
+
36
+ title = obj[:Title].dup
37
+ title.force_encoding(Encoding::UTF_16LE)
38
+ expect(title.valid_encoding?).to eq(true)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#generate_outline' do
44
+ it 'creates a root outline dictionary item' do
45
+ expect(outline_root).to_not be_nil
46
+ end
47
+
48
+ it 'sets the first and last top items of the root outline dictionary '\
49
+ 'item' do
50
+ section1 = find_by_title('Chapter 1')
51
+
52
+ expect(referenced_object(outline_root[:First])).to eq(section1)
53
+ expect(referenced_object(outline_root[:Last])).to eq(section1)
54
+ end
55
+
56
+ describe '#create_outline_item' do
57
+ it 'creates outline items for each section and page' do
58
+ section1 = find_by_title('Chapter 1')
59
+ page1 = find_by_title('Page 1')
60
+ page2 = find_by_title('Page 2')
61
+
62
+ expect(section1).to_not be_nil
63
+ expect(page1).to_not be_nil
64
+ expect(page2).to_not be_nil
65
+ end
66
+ end
67
+
68
+ describe '#set_relations, #set_variables_for_block, and #reset_parent' do
69
+ it 'links sibling items' do
70
+ page1 = find_by_title('Page 1')
71
+ page2 = find_by_title('Page 2')
72
+
73
+ expect(referenced_object(page1[:Next])).to eq(page2)
74
+ expect(referenced_object(page2[:Prev])).to eq(page1)
75
+ end
76
+
77
+ it 'links child items to parent item' do
78
+ section1 = find_by_title('Chapter 1')
79
+ page1 = find_by_title('Page 1')
80
+ page2 = find_by_title('Page 2')
81
+
82
+ expect(referenced_object(page1[:Parent])).to eq(section1)
83
+ expect(referenced_object(page2[:Parent])).to eq(section1)
84
+ end
85
+
86
+ it 'sets the first and last child items for parent item' do
87
+ section1 = find_by_title('Chapter 1')
88
+ page1 = find_by_title('Page 1')
89
+ page2 = find_by_title('Page 2')
90
+
91
+ expect(referenced_object(section1[:First])).to eq(page1)
92
+ expect(referenced_object(section1[:Last])).to eq(page2)
93
+ end
94
+ end
95
+
96
+ describe '#increase_count' do
97
+ it 'adds the count of all descendant items' do
98
+ section1 = find_by_title('Chapter 1')
99
+ page1 = find_by_title('Page 1')
100
+ page2 = find_by_title('Page 2')
101
+
102
+ expect(outline_root[:Count]).to eq(3)
103
+ expect(section1[:Count].abs).to eq(2)
104
+ expect(page1[:Count]).to eq(0)
105
+ expect(page2[:Count]).to eq(0)
106
+ end
107
+ end
108
+
109
+ describe 'closed option' do
110
+ it "sets the item's integer count to negative" do
111
+ section1 = find_by_title('Chapter 1')
112
+
113
+ expect(section1[:Count]).to eq(-2)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe 'adding a custom destination' do
119
+ before do
120
+ pdf.start_new_page
121
+ pdf.text 'Page 3 with a destination'
122
+ pdf.add_dest('customdest', pdf.dest_xyz(200, 200))
123
+ destination = pdf.dest_xyz(200, 200)
124
+ pdf.outline.update do
125
+ page destination: destination, title: 'Custom Destination'
126
+ end
127
+ end
128
+
129
+ it 'creates an outline item' do
130
+ custom_dest = find_by_title('Custom Destination')
131
+
132
+ expect(custom_dest).to_not be_nil
133
+ end
134
+
135
+ it 'references the custom destination' do
136
+ custom_dest = find_by_title('Custom Destination')
137
+ last_page =
138
+ hash.values.find do |obj|
139
+ obj.is_a?(Hash) && obj[:Type] == :Pages
140
+ end[:Kids]
141
+ .last
142
+
143
+ expect(referenced_object(custom_dest[:Dest].first))
144
+ .to eq(referenced_object(last_page))
145
+ end
146
+ end
147
+
148
+ describe 'addding a section later with outline#section' do
149
+ before do
150
+ pdf.start_new_page
151
+ pdf.text 'Page 3. An added section '
152
+ pdf.outline.update do
153
+ section 'Added Section', destination: 3 do
154
+ page destination: 3, title: 'Page 3'
155
+ end
156
+ end
157
+ end
158
+
159
+ it 'adds new outline items to document' do
160
+ section2 = find_by_title('Added Section')
161
+ page3 = find_by_title('Page 3')
162
+
163
+ expect(section2).to_not be_nil
164
+ expect(page3).to_not be_nil
165
+ end
166
+
167
+ it 'resets the last items for root outline dictionary' do
168
+ section1 = find_by_title('Chapter 1')
169
+ section2 = find_by_title('Added Section')
170
+
171
+ expect(referenced_object(outline_root[:First])).to eq(section1)
172
+ expect(referenced_object(outline_root[:Last])).to eq(section2)
173
+ end
174
+
175
+ it 'resets the next relation for the previous last top level item' do
176
+ section1 = find_by_title('Chapter 1')
177
+ section2 = find_by_title('Added Section')
178
+
179
+ expect(referenced_object(section1[:Next])).to eq(section2)
180
+ end
181
+
182
+ it 'sets the previous relation of the addded to section' do
183
+ section1 = find_by_title('Chapter 1')
184
+ section2 = find_by_title('Added Section')
185
+
186
+ expect(referenced_object(section2[:Prev])).to eq(section1)
187
+ end
188
+
189
+ it 'increases the count of root outline dictionary' do
190
+ expect(outline_root[:Count]).to eq(5)
191
+ end
192
+ end
193
+
194
+ describe '#outline.add_subsection_to' do
195
+ context 'when positioned last' do
196
+ before do
197
+ pdf.start_new_page
198
+ pdf.text 'Page 3. An added subsection '
199
+ pdf.outline.update do
200
+ add_subsection_to 'Chapter 1' do
201
+ section 'Added SubSection', destination: 3 do
202
+ page destination: 3, title: 'Added Page 3'
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ it 'adds new outline items to document' do
209
+ subsection = find_by_title('Added SubSection')
210
+ added_page = find_by_title('Added Page 3')
211
+
212
+ expect(subsection).to_not be_nil
213
+ expect(added_page).to_not be_nil
214
+ end
215
+
216
+ it 'resets the last item for parent item dictionary' do
217
+ section1 = find_by_title('Chapter 1')
218
+ page1 = find_by_title('Page 1')
219
+ subsection = find_by_title('Added SubSection')
220
+
221
+ expect(referenced_object(section1[:First])).to eq(page1)
222
+ expect(referenced_object(section1[:Last])).to eq(subsection)
223
+ end
224
+
225
+ it "sets the prev relation for the new subsection to its parent's old "\
226
+ 'last item' do
227
+ subsection = find_by_title('Added SubSection')
228
+ page2 = find_by_title('Page 2')
229
+
230
+ expect(referenced_object(subsection[:Prev])).to eq(page2)
231
+ end
232
+
233
+ it "the subsection should become the next relation for its parent's old "\
234
+ 'last item' do
235
+ subsection = find_by_title('Added SubSection')
236
+ page2 = find_by_title('Page 2')
237
+
238
+ expect(referenced_object(page2[:Next])).to eq(subsection)
239
+ end
240
+
241
+ it 'sets the first relation for the new subsection' do
242
+ subsection = find_by_title('Added SubSection')
243
+ added_page = find_by_title('Added Page 3')
244
+
245
+ expect(referenced_object(subsection[:First])).to eq(added_page)
246
+ end
247
+
248
+ it 'sets the correct last relation of the added to section' do
249
+ subsection = find_by_title('Added SubSection')
250
+ added_page = find_by_title('Added Page 3')
251
+
252
+ expect(referenced_object(subsection[:Last])).to eq(added_page)
253
+ end
254
+
255
+ it 'increases the count of root outline dictionary' do
256
+ expect(outline_root[:Count]).to eq(5)
257
+ end
258
+ end
259
+
260
+ context 'when positioned first' do
261
+ before do
262
+ pdf.start_new_page
263
+ pdf.text 'Page 3. An added subsection '
264
+ pdf.outline.update do
265
+ add_subsection_to 'Chapter 1', :first do
266
+ section 'Added SubSection', destination: 3 do
267
+ page destination: 3, title: 'Added Page 3'
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ it 'adds new outline items to document' do
274
+ subsection = find_by_title('Added SubSection')
275
+ added_page = find_by_title('Added Page 3')
276
+
277
+ expect(subsection).to_not be_nil
278
+ expect(added_page).to_not be_nil
279
+ end
280
+
281
+ it 'resets the first item for parent item dictionary' do
282
+ section1 = find_by_title('Chapter 1')
283
+ subsection = find_by_title('Added SubSection')
284
+ page2 = find_by_title('Page 2')
285
+
286
+ expect(referenced_object(section1[:First])).to eq(subsection)
287
+ expect(referenced_object(section1[:Last])).to eq(page2)
288
+ end
289
+
290
+ it "sets the next relation for the new subsection to its parent's old "\
291
+ 'first item' do
292
+ subsection = find_by_title('Added SubSection')
293
+ page1 = find_by_title('Page 1')
294
+
295
+ expect(referenced_object(subsection[:Next])).to eq(page1)
296
+ end
297
+
298
+ it "the subsection should become the prev relation for its parent's old "\
299
+ 'first item' do
300
+ subsection = find_by_title('Added SubSection')
301
+ page1 = find_by_title('Page 1')
302
+
303
+ expect(referenced_object(page1[:Prev])).to eq(subsection)
304
+ end
305
+
306
+ it 'sets the first relation for the new subsection' do
307
+ subsection = find_by_title('Added SubSection')
308
+ added_page = find_by_title('Added Page 3')
309
+
310
+ expect(referenced_object(subsection[:First])).to eq(added_page)
311
+ end
312
+
313
+ it 'sets the correct last relation of the added to section' do
314
+ subsection = find_by_title('Added SubSection')
315
+ added_page = find_by_title('Added Page 3')
316
+
317
+ expect(referenced_object(subsection[:Last])).to eq(added_page)
318
+ end
319
+
320
+ it 'increases the count of root outline dictionary' do
321
+ expect(outline_root[:Count]).to eq(5)
322
+ end
323
+ end
324
+
325
+ it 'requires an existing title' do
326
+ expect do
327
+ pdf.go_to_page 1
328
+ pdf.start_new_page
329
+ pdf.text 'Inserted Page'
330
+ pdf.outline.update do
331
+ add_subsection_to 'Wrong page' do
332
+ page page_number, title: 'Inserted Page'
333
+ end
334
+ end
335
+ render_and_find_objects
336
+ end.to raise_error(Prawn::Errors::UnknownOutlineTitle)
337
+ end
338
+ end
339
+
340
+ describe '#outline.insert_section_after' do
341
+ describe 'inserting in the middle of another section' do
342
+ before do
343
+ pdf.go_to_page 1
344
+ pdf.start_new_page
345
+ pdf.text 'Inserted Page'
346
+ pdf.outline.update do
347
+ insert_section_after 'Page 1' do
348
+ page destination: page_number, title: 'Inserted Page'
349
+ end
350
+ end
351
+ end
352
+
353
+ it 'inserts new outline items to document' do
354
+ inserted_page = find_by_title('Inserted Page')
355
+
356
+ expect(inserted_page).to_not be_nil
357
+ end
358
+
359
+ it 'adjusts the count of all ancestors' do
360
+ section1 = find_by_title('Chapter 1')
361
+
362
+ expect(outline_root[:Count]).to eq(4)
363
+ expect(section1[:Count].abs).to eq(3)
364
+ end
365
+
366
+ describe '#adjust_relations' do
367
+ it 'resets the sibling relations of adjoining items to inserted item' do
368
+ inserted_page = find_by_title('Inserted Page')
369
+ page1 = find_by_title('Page 1')
370
+ page2 = find_by_title('Page 2')
371
+
372
+ expect(referenced_object(page1[:Next])).to eq(inserted_page)
373
+ expect(referenced_object(page2[:Prev])).to eq(inserted_page)
374
+ end
375
+
376
+ it 'sets the sibling relation of added item to adjoining items' do
377
+ inserted_page = find_by_title('Inserted Page')
378
+ page1 = find_by_title('Page 1')
379
+ page2 = find_by_title('Page 2')
380
+
381
+ expect(referenced_object(inserted_page[:Next])).to eq(page2)
382
+ expect(referenced_object(inserted_page[:Prev])).to eq(page1)
383
+ end
384
+
385
+ it 'does not affect the first and last relations of parent item' do
386
+ section1 = find_by_title('Chapter 1')
387
+ page1 = find_by_title('Page 1')
388
+ page2 = find_by_title('Page 2')
389
+
390
+ expect(referenced_object(section1[:First])).to eq(page1)
391
+ expect(referenced_object(section1[:Last])).to eq(page2)
392
+ end
393
+ end
394
+
395
+ context 'when adding another section afterwards' do
396
+ it 'has reset the root position so that a new section is added at '\
397
+ 'the end of root sections' do
398
+ pdf.start_new_page
399
+ pdf.text 'Another Inserted Page'
400
+ pdf.outline.update do
401
+ section 'Added Section' do
402
+ page destination: page_number, title: 'Inserted Page'
403
+ end
404
+ end
405
+
406
+ section1 = find_by_title('Chapter 1')
407
+ section2 = find_by_title('Added Section')
408
+
409
+ expect(referenced_object(outline_root[:Last])).to eq(section2)
410
+ expect(referenced_object(section1[:Next])).to eq(section2)
411
+ end
412
+ end
413
+ end
414
+
415
+ describe 'inserting at the end of another section' do
416
+ before do
417
+ pdf.go_to_page 2
418
+ pdf.start_new_page
419
+ pdf.text 'Inserted Page'
420
+ pdf.outline.update do
421
+ insert_section_after 'Page 2' do
422
+ page destination: page_number, title: 'Inserted Page'
423
+ end
424
+ end
425
+ end
426
+
427
+ describe '#adjust_relations' do
428
+ it 'resets the sibling relations of adjoining item to inserted item' do
429
+ page2 = find_by_title('Page 2')
430
+ inserted_page = find_by_title('Inserted Page')
431
+
432
+ expect(referenced_object(page2[:Next])).to eq(inserted_page)
433
+ end
434
+
435
+ it 'sets the sibling relation of added item to adjoining items' do
436
+ page2 = find_by_title('Page 2')
437
+ inserted_page = find_by_title('Inserted Page')
438
+
439
+ expect(referenced_object(inserted_page[:Next])).to be_nil
440
+ expect(referenced_object(inserted_page[:Prev])).to eq(page2)
441
+ end
442
+
443
+ it 'adjusts the last relation of parent item' do
444
+ section1 = find_by_title('Chapter 1')
445
+ inserted_page = find_by_title('Inserted Page')
446
+
447
+ expect(referenced_object(section1[:Last])).to eq(inserted_page)
448
+ end
449
+ end
450
+ end
451
+
452
+ it 'requires an existing title' do
453
+ expect do
454
+ pdf.go_to_page 1
455
+ pdf.start_new_page
456
+ pdf.text 'Inserted Page'
457
+ pdf.outline.update do
458
+ insert_section_after 'Wrong page' do
459
+ page destination: page_number, title: 'Inserted Page'
460
+ end
461
+ end
462
+ end.to raise_error(Prawn::Errors::UnknownOutlineTitle)
463
+ end
464
+ end
465
+
466
+ describe '#page' do
467
+ it 'requires a title option to be set' do
468
+ expect do
469
+ Prawn::Document.new do
470
+ text 'Page 1. This is the first Chapter. '
471
+ outline.define do
472
+ page destination: 1, title: nil
473
+ end
474
+ end
475
+ end.to raise_error(Prawn::Errors::RequiredOption)
476
+ end
477
+ end
478
+
479
+ describe 'foreign character encoding' do
480
+ let(:hash) do
481
+ pdf =
482
+ Prawn::Document.new do
483
+ outline.define do
484
+ section 'La pomme croquée', destination: 1, closed: true
485
+ end
486
+ end
487
+ PDF::Reader::ObjectHash.new(StringIO.new(pdf.render, 'r+'))
488
+ end
489
+
490
+ it 'handles other encodings for the title' do
491
+ object = find_by_title('La pomme croquée')
492
+ expect(object).to_not be_nil
493
+ end
494
+ end
495
+
496
+ # Outline titles are stored as UTF-16. This method accepts a UTF-8 outline
497
+ # title and returns the PDF Object that contains an outline with that name
498
+ def find_by_title(title)
499
+ hash.values.find do |obj|
500
+ next unless obj.is_a?(Hash) && obj[:Title]
501
+
502
+ title_codepoints = obj[:Title].unpack('n*')
503
+ title_codepoints.shift
504
+ utf8_title = title_codepoints.pack('U*')
505
+ utf8_title == title ? obj : nil
506
+ end
507
+ end
508
+
509
+ def referenced_object(reference)
510
+ hash[reference]
511
+ end
512
+ end