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