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,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Document do
6
+ let(:pdf) { create_pdf }
7
+
8
+ it 'onlies accept :position as option in debug mode' do
9
+ Prawn.debug = true
10
+ expect { pdf.span(350, x: 3) {} }
11
+ .to raise_error(Prawn::Errors::UnknownOption)
12
+ end
13
+
14
+ it 'has raise an error if :position is invalid' do
15
+ expect { pdf.span(350, position: :x) {} }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'restores the margin box when bounding box exits' do
19
+ margin_box = pdf.bounds
20
+
21
+ pdf.span(350, position: :center) do
22
+ pdf.text "Here's some centered text in a 350 point column. " * 100
23
+ end
24
+
25
+ expect(pdf.bounds).to eq(margin_box)
26
+ end
27
+
28
+ it 'does create a margin box' do
29
+ margin_box = pdf.span(350, position: :center) do
30
+ pdf.text "Here's some centered text in a 350 point column. " * 100
31
+ end
32
+
33
+ expect(margin_box.top).to eq(792.0)
34
+ expect(margin_box.bottom).to eq(0)
35
+ end
36
+ end
@@ -0,0 +1,802 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'tempfile'
5
+
6
+ describe Prawn::Document do
7
+ let(:pdf) { create_pdf }
8
+
9
+ describe '.new' do
10
+ it 'does not modify its argument' do
11
+ options = { page_layout: :landscape }
12
+ described_class.new(options)
13
+ expect(options).to eq(page_layout: :landscape)
14
+ end
15
+ end
16
+
17
+ describe 'The cursor' do
18
+ it 'equals pdf.y - bounds.absolute_bottom' do
19
+ pdf = described_class.new
20
+ expect(pdf.cursor).to eq(pdf.bounds.top)
21
+
22
+ pdf.y = 300
23
+ expect(pdf.cursor).to eq(pdf.y - pdf.bounds.absolute_bottom)
24
+ end
25
+
26
+ it 'is able to move relative to the bottom margin' do
27
+ pdf = described_class.new
28
+ pdf.move_cursor_to(10)
29
+
30
+ expect(pdf.cursor).to eq(10)
31
+ expect(pdf.y).to eq(pdf.cursor + pdf.bounds.absolute_bottom)
32
+ end
33
+ end
34
+
35
+ describe 'when generating a document with a custom text formatter' do
36
+ it 'uses the provided text formatter' do
37
+ text_formatter = Class.new do
38
+ def self.format(string)
39
+ [
40
+ {
41
+ text: string.gsub('Dr. Who?', "Just 'The Doctor'."),
42
+ styles: [],
43
+ color: nil,
44
+ link: nil,
45
+ anchor: nil,
46
+ local: nil,
47
+ font: nil,
48
+ size: nil,
49
+ character_spacing: nil
50
+ }
51
+ ]
52
+ end
53
+ end
54
+ pdf = described_class.new text_formatter: text_formatter
55
+ pdf.text 'Dr. Who?', inline_format: true
56
+ text = PDF::Inspector::Text.analyze(pdf.render)
57
+ expect(text.strings.first).to eq("Just 'The Doctor'.")
58
+ end
59
+ end
60
+
61
+ describe 'when generating a document from a subclass' do
62
+ it 'is an instance of the subclass' do
63
+ custom_document = Class.new(described_class)
64
+ custom_document.generate(Tempfile.new('generate_test').path) do |e|
65
+ expect(e.class).to eq(custom_document)
66
+ expect(e).to be_a_kind_of(described_class)
67
+ end
68
+ end
69
+
70
+ it 'retains any extensions found on Prawn::Document' do
71
+ mod1 = Module.new { attr_reader :test_extensions1 }
72
+ mod2 = Module.new { attr_reader :test_extensions2 }
73
+
74
+ described_class.extensions << mod1 << mod2
75
+
76
+ custom_document = Class.new(described_class)
77
+ expect(custom_document.extensions).to eq([mod1, mod2])
78
+
79
+ # remove the extensions we added to prawn document
80
+ described_class.extensions.delete(mod1)
81
+ described_class.extensions.delete(mod2)
82
+
83
+ expect(described_class.new.respond_to?(:test_extensions1)).to eq false
84
+ expect(described_class.new.respond_to?(:test_extensions2)).to eq false
85
+
86
+ # verify these still exist on custom class
87
+ expect(custom_document.extensions).to eq([mod1, mod2])
88
+
89
+ expect(custom_document.new.respond_to?(:test_extensions1)).to eq true
90
+ expect(custom_document.new.respond_to?(:test_extensions2)).to eq true
91
+ end
92
+ end
93
+
94
+ describe 'When creating multi-page documents' do
95
+ it 'initializes with a single page' do
96
+ page_counter = PDF::Inspector::Page.analyze(pdf.render)
97
+
98
+ expect(page_counter.pages.size).to eq(1)
99
+ expect(pdf.page_count).to eq(1)
100
+ end
101
+
102
+ it 'provides an accurate page_count' do
103
+ 3.times { pdf.start_new_page }
104
+ page_counter = PDF::Inspector::Page.analyze(pdf.render)
105
+
106
+ expect(page_counter.pages.size).to eq(4)
107
+ expect(pdf.page_count).to eq(4)
108
+ end
109
+ end
110
+
111
+ describe 'When beginning each new page' do
112
+ describe 'Background image feature' do
113
+ let(:filename) { "#{Prawn::DATADIR}/images/pigs.jpg" }
114
+ let(:pdf) { described_class.new(background: filename) }
115
+
116
+ it 'places a background image if it is in options block' do
117
+ output = pdf.render
118
+ images = PDF::Inspector::XObject.analyze(output)
119
+ # there should be 2 images in the page resources
120
+ expect(images.page_xobjects.first.size).to eq(1)
121
+ end
122
+
123
+ it 'places a background image interntally if it is in options block' do
124
+ expect(pdf.instance_variable_defined?(:@background)).to eq(true)
125
+ expect(pdf.instance_variable_get(:@background)).to eq(filename)
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#float' do
131
+ it 'restores the original y-position' do
132
+ orig_y = pdf.y
133
+ pdf.float { pdf.text 'Foo' }
134
+ expect(pdf.y).to eq(orig_y)
135
+ end
136
+
137
+ it 'teleports across pages if necessary' do
138
+ pdf.float do
139
+ pdf.text 'Foo'
140
+ pdf.start_new_page
141
+ pdf.text 'Bar'
142
+ end
143
+ pdf.text 'Baz'
144
+
145
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
146
+ expect(pages.size).to eq(2)
147
+ expect(pages[0][:strings]).to eq(%w[Foo Baz])
148
+ expect(pages[1][:strings]).to eq(['Bar'])
149
+ end
150
+ end
151
+
152
+ describe '#start_new_page' do
153
+ it "doesn't modify the options hash" do
154
+ expect do
155
+ described_class.new.start_new_page({ margin: 0 }.freeze)
156
+ end.to_not raise_error
157
+ end
158
+
159
+ it 'sets individual page margins' do
160
+ doc = described_class.new
161
+ doc.start_new_page(top_margin: 42)
162
+ expect(doc.page.margins[:top]).to eq(42)
163
+ end
164
+ end
165
+
166
+ describe '#delete_page(index)' do
167
+ before do
168
+ pdf.text 'Page one'
169
+ pdf.start_new_page
170
+ pdf.text 'Page two'
171
+ pdf.start_new_page
172
+ pdf.text 'Page three'
173
+ end
174
+
175
+ it 'destroy a specific page of the document' do
176
+ pdf.delete_page(1)
177
+ expect(pdf.page_number).to eq(2)
178
+ text_analysis = PDF::Inspector::Text.analyze(pdf.render)
179
+ expect(text_analysis.strings).to eq(['Page one', 'Page three'])
180
+ end
181
+
182
+ it 'destroy the last page of the document' do
183
+ pdf.delete_page(-1)
184
+ expect(pdf.page_number).to eq(2)
185
+ text_analysis = PDF::Inspector::Text.analyze(pdf.render)
186
+ expect(text_analysis.strings).to eq(['Page one', 'Page two'])
187
+ end
188
+
189
+ context 'with an invalid index' do
190
+ let(:expected_content) { ['Page one', 'Page two', 'Page three'] }
191
+
192
+ it 'does not destroy an invalid positve index' do
193
+ pdf.delete_page(42)
194
+ expect(pdf.page_number).to eq(3)
195
+ text_analysis = PDF::Inspector::Text.analyze(pdf.render)
196
+ expect(text_analysis.strings).to eq(expected_content)
197
+ end
198
+
199
+ it 'does not destroy an invalid negative index' do
200
+ pdf.delete_page(-42)
201
+ expect(pdf.page_number).to eq(3)
202
+ text_analysis = PDF::Inspector::Text.analyze(pdf.render)
203
+ expect(text_analysis.strings).to eq(expected_content)
204
+ end
205
+ end
206
+ end
207
+
208
+ describe '#page_number' do
209
+ it 'is 1 for a new document' do
210
+ pdf = described_class.new
211
+ expect(pdf.page_number).to eq(1)
212
+ end
213
+
214
+ it 'is 0 for documents with no pages' do
215
+ pdf = described_class.new(skip_page_creation: true)
216
+ expect(pdf.page_number).to eq(0)
217
+ end
218
+
219
+ it 'is changed by go_to_page' do
220
+ pdf = described_class.new
221
+ 10.times { pdf.start_new_page }
222
+ pdf.go_to_page 3
223
+ expect(pdf.page_number).to eq(3)
224
+ end
225
+ end
226
+
227
+ describe 'on_page_create callback' do
228
+ it 'is delegated from Document to renderer' do
229
+ expect(pdf.respond_to?(:on_page_create)).to eq true
230
+ end
231
+
232
+ it 'is invoked with document' do
233
+ called_with = nil
234
+
235
+ pdf.renderer.on_page_create { |*args| called_with = args }
236
+
237
+ pdf.start_new_page
238
+
239
+ expect(called_with).to eq([pdf])
240
+ end
241
+
242
+ it 'is invoked for each new page' do
243
+ trigger = instance_double('trigger')
244
+ allow(trigger).to receive(:fire)
245
+
246
+ pdf.renderer.on_page_create { trigger.fire }
247
+
248
+ 5.times { pdf.start_new_page }
249
+
250
+ expect(trigger).to have_received(:fire).exactly(5).times
251
+ end
252
+
253
+ it 'is replaceable' do
254
+ trigger1 = instance_double('trigger 1')
255
+ allow(trigger1).to receive(:fire)
256
+
257
+ trigger2 = instance_double('trigger 2')
258
+ allow(trigger2).to receive(:fire)
259
+
260
+ pdf.renderer.on_page_create { trigger1.fire }
261
+
262
+ pdf.start_new_page
263
+
264
+ pdf.renderer.on_page_create { trigger2.fire }
265
+
266
+ pdf.start_new_page
267
+
268
+ expect(trigger1).to have_received(:fire).once
269
+ expect(trigger2).to have_received(:fire).once
270
+ end
271
+
272
+ it 'is clearable by calling on_page_create without a block' do
273
+ trigger = instance_double('trigger')
274
+ allow(trigger).to receive(:fire)
275
+
276
+ pdf.renderer.on_page_create { trigger.fire }
277
+
278
+ pdf.start_new_page
279
+
280
+ pdf.renderer.on_page_create
281
+
282
+ pdf.start_new_page
283
+
284
+ expect(trigger).to have_received(:fire).once
285
+ end
286
+ end
287
+
288
+ describe 'compression' do
289
+ it 'does not compress the page content stream if compression is disabled' do
290
+ pdf = described_class.new(compress: false)
291
+ allow(pdf.page.content.stream).to receive(:compress!).and_return(true)
292
+
293
+ pdf.text 'Hi There' * 20
294
+ pdf.render
295
+
296
+ expect(pdf.page.content.stream).to_not have_received(:compress!)
297
+ end
298
+
299
+ it 'compresses the page content stream if compression is enabled' do
300
+ pdf = described_class.new(compress: true)
301
+ allow(pdf.page.content.stream).to receive(:compress!).and_return(true)
302
+
303
+ pdf.text 'Hi There' * 20
304
+ pdf.render
305
+
306
+ expect(pdf.page.content.stream).to have_received(:compress!).once
307
+ end
308
+
309
+ it 'results in a smaller file size when compressed' do
310
+ doc_uncompressed = described_class.new
311
+ doc_compressed = described_class.new(compress: true)
312
+ [doc_compressed, doc_uncompressed].each do |pdf|
313
+ pdf.font "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
314
+ pdf.text '更可怕的是,同质化竞争对手可以按照URL中后面这个ID来遍历' * 10
315
+ end
316
+
317
+ expect(doc_compressed.render.length).to be <
318
+ doc_uncompressed.render.length
319
+ end
320
+ end
321
+
322
+ describe 'Dometadata' do
323
+ it 'outputs strings as UTF-16 with a byte order mark' do
324
+ pdf = described_class.new(info: { Author: 'Lóránt' })
325
+ expect(pdf.state.store.info.object).to match(
326
+ # UTF-16: BOM L ó r á n t
327
+ %r{/Author\s*<feff004c00f3007200e1006e0074>}i
328
+ )
329
+ end
330
+ end
331
+
332
+ describe 'When reopening pages' do
333
+ it 'modifies the content stream size' do
334
+ pdf = described_class.new do
335
+ text 'Page 1'
336
+ start_new_page
337
+ text 'Page 2'
338
+ go_to_page 1
339
+ text 'More for page 1'
340
+ end
341
+
342
+ # Indirectly verify that the actual length does not match dictionary
343
+ # length. If it isn't, a MalformedPDFError will be raised
344
+ PDF::Inspector::Page.analyze(pdf.render)
345
+ end
346
+
347
+ it 'inserts pages after the current page when calling start_new_page' do
348
+ pdf = described_class.new
349
+ 3.times do |i|
350
+ pdf.text "Old page #{i + 1}"
351
+ pdf.start_new_page
352
+ end
353
+
354
+ pdf.go_to_page 1
355
+ pdf.start_new_page
356
+ pdf.text 'New page 2'
357
+
358
+ expect(pdf.page_number).to eq(2)
359
+
360
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
361
+ expect(pages.size).to eq(5)
362
+ expect(pages[1][:strings]).to eq(['New page 2'])
363
+ expect(pages[2][:strings]).to eq(['Old page 2'])
364
+ end
365
+
366
+ it 'restores the layout of the page' do
367
+ doc = described_class.new do
368
+ start_new_page layout: :landscape
369
+ end
370
+
371
+ lsize = [doc.bounds.width, doc.bounds.height]
372
+
373
+ expect([doc.bounds.width, doc.bounds.height]).to eq lsize
374
+ doc.go_to_page 1
375
+ expect([doc.bounds.width, doc.bounds.height]).to eq lsize.reverse
376
+ end
377
+
378
+ it 'restores the margin box of the page' do
379
+ doc = described_class.new(margin: [100, 100])
380
+ page1_bounds = doc.bounds
381
+
382
+ doc.start_new_page(margin: [200, 200])
383
+
384
+ expect([doc.bounds.width, doc.bounds.height]).to eq(
385
+ [page1_bounds.width - 200, page1_bounds.height - 200]
386
+ )
387
+
388
+ doc.go_to_page(1)
389
+
390
+ expect(doc.bounds.width).to eq page1_bounds.width
391
+ expect(doc.bounds.height).to eq page1_bounds.height
392
+ end
393
+ end
394
+
395
+ describe 'When setting page size' do
396
+ it 'defaults to LETTER' do
397
+ pdf = described_class.new
398
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
399
+ expect(pages.first[:size]).to eq(PDF::Core::PageGeometry::SIZES['LETTER'])
400
+ end
401
+
402
+ (PDF::Core::PageGeometry::SIZES.keys - ['LETTER']).each do |k|
403
+ it "provides #{k} geometry" do
404
+ pdf = described_class.new(page_size: k)
405
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
406
+ expect(pages.first[:size]).to eq(PDF::Core::PageGeometry::SIZES[k])
407
+ end
408
+ end
409
+
410
+ it 'allows custom page size' do
411
+ pdf = described_class.new(page_size: [1920, 1080])
412
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
413
+ expect(pages.first[:size]).to eq([1920, 1080])
414
+ end
415
+
416
+ it 'retains page size by default when starting a new page' do
417
+ pdf = described_class.new(page_size: 'LEGAL')
418
+ pdf.start_new_page
419
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
420
+ pages.each do |page|
421
+ expect(page[:size]).to eq(PDF::Core::PageGeometry::SIZES['LEGAL'])
422
+ end
423
+ end
424
+ end
425
+
426
+ describe 'When setting page layout' do
427
+ it 'reverses coordinates for landscape' do
428
+ pdf = described_class.new(page_size: 'A4', page_layout: :landscape)
429
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
430
+ expect(pages.first[:size]).to eq(
431
+ PDF::Core::PageGeometry::SIZES['A4'].reverse
432
+ )
433
+ end
434
+
435
+ it 'retains page layout by default when starting a new page' do
436
+ pdf = described_class.new(page_layout: :landscape)
437
+ pdf.start_new_page(trace: true)
438
+ pages = PDF::Inspector::Page.analyze(pdf.render).pages
439
+ pages.each do |page|
440
+ expect(page[:size]).to eq(
441
+ PDF::Core::PageGeometry::SIZES['LETTER'].reverse
442
+ )
443
+ end
444
+ end
445
+
446
+ it 'swaps the bounds when starting a new page with different layout' do
447
+ pdf = described_class.new
448
+ size = [pdf.bounds.width, pdf.bounds.height]
449
+ pdf.start_new_page(layout: :landscape)
450
+ expect([pdf.bounds.width, pdf.bounds.height]).to eq(size.reverse)
451
+ end
452
+ end
453
+
454
+ describe '#mask' do
455
+ it 'allows transactional restoration of attributes' do
456
+ pdf = described_class.new
457
+ y = pdf.y
458
+ line_width = pdf.line_width
459
+ pdf.mask(:y, :line_width) do
460
+ pdf.y = y + 1
461
+ pdf.line_width = line_width + 1
462
+ expect(pdf.y).to_not eq(y)
463
+ expect(pdf.line_width).to_not eq(line_width)
464
+ end
465
+ expect(pdf.y).to eq(y)
466
+ expect(pdf.line_width).to eq(line_width)
467
+ end
468
+ end
469
+
470
+ describe '#render' do
471
+ it 'returns a 8 bit encoded string on a m17n aware VM' do
472
+ pdf = described_class.new(page_size: 'A4', page_layout: :landscape)
473
+ pdf.line [100, 100], [200, 200]
474
+ str = pdf.render
475
+ expect(str.encoding.to_s).to eq('ASCII-8BIT')
476
+ end
477
+
478
+ it 'triggers before_render callbacks just before rendering' do
479
+ pdf = described_class.new
480
+
481
+ # Verify the order: finalize -> fire callbacks -> render body
482
+ allow(pdf.renderer).to receive(:finalize_all_page_contents)
483
+ .and_call_original
484
+
485
+ trigger = instance_double('trigger')
486
+ allow(trigger).to receive(:fire)
487
+
488
+ pdf.renderer.before_render { trigger.fire }
489
+
490
+ allow(pdf.renderer).to receive(:render_body).and_call_original
491
+
492
+ pdf.render(StringIO.new)
493
+
494
+ expect(pdf.renderer).to have_received(:finalize_all_page_contents)
495
+ .ordered
496
+ expect(trigger).to have_received(:fire).ordered
497
+ expect(pdf.renderer).to have_received(:render_body).ordered
498
+ end
499
+
500
+ it 'is idempotent' do
501
+ pdf = described_class.new
502
+
503
+ contents = pdf.render
504
+ contents2 = pdf.render
505
+ expect(contents2).to eq(contents)
506
+ end
507
+ end
508
+
509
+ describe 'PDF file versions' do
510
+ it 'defaults to 1.3' do
511
+ pdf = described_class.new
512
+ str = pdf.render
513
+ expect(str[0, 8]).to eq('%PDF-1.3')
514
+ end
515
+
516
+ it 'allows the default to be changed' do
517
+ pdf = described_class.new
518
+ pdf.renderer.min_version(1.4)
519
+ str = pdf.render
520
+ expect(str[0, 8]).to eq('%PDF-1.4')
521
+ end
522
+ end
523
+
524
+ describe '#go_to_page' do
525
+ it 'has 2 pages after calling start_new_page and go_to_page' do
526
+ pdf = described_class.new
527
+ pdf.text 'James'
528
+ pdf.start_new_page
529
+ pdf.text 'Anthony'
530
+ pdf.go_to_page(1)
531
+ pdf.text 'Healy'
532
+
533
+ page_counter = PDF::Inspector::Page.analyze(pdf.render)
534
+ expect(page_counter.pages.size).to eq(2)
535
+ end
536
+
537
+ it 'correctlies add text to pages' do
538
+ pdf = described_class.new
539
+ pdf.text 'James'
540
+ pdf.start_new_page
541
+ pdf.text 'Anthony'
542
+ pdf.go_to_page(1)
543
+ pdf.text 'Healy'
544
+
545
+ text = PDF::Inspector::Text.analyze(pdf.render)
546
+
547
+ expect(text.strings.size).to eq(3)
548
+ expect(text.strings.include?('James')).to eq(true)
549
+ expect(text.strings.include?('Anthony')).to eq(true)
550
+ expect(text.strings.include?('Healy')).to eq(true)
551
+ end
552
+ end
553
+
554
+ describe 'content stream characteristics' do
555
+ it 'has 1 single content stream for a single page PDF' do
556
+ pdf = described_class.new
557
+ pdf.text 'James'
558
+ output = StringIO.new(pdf.render)
559
+ hash = PDF::Reader::ObjectHash.new(output)
560
+
561
+ streams = hash.values.select { |obj| obj.is_a?(PDF::Reader::Stream) }
562
+
563
+ expect(streams.size).to eq(1)
564
+ end
565
+
566
+ it 'has 1 single content stream for a single page PDF, even if go_to_page '\
567
+ 'is used' do
568
+ pdf = described_class.new
569
+ pdf.text 'James'
570
+ pdf.go_to_page(1)
571
+ pdf.text 'Healy'
572
+ output = StringIO.new(pdf.render)
573
+ hash = PDF::Reader::ObjectHash.new(output)
574
+
575
+ streams = hash.values.select { |obj| obj.is_a?(PDF::Reader::Stream) }
576
+
577
+ expect(streams.size).to eq(1)
578
+ end
579
+ end
580
+
581
+ describe '#number_pages' do
582
+ let(:pdf) { described_class.new(skip_page_creation: true) }
583
+
584
+ it "replaces the '<page>' string with the proper page number" do
585
+ pdf.start_new_page
586
+ allow(pdf).to receive(:text_box)
587
+ pdf.number_pages '<page>, test', page_filter: :all
588
+ expect(pdf).to have_received(:text_box).with('1, test', height: 50)
589
+ end
590
+
591
+ it "replaces the '<total>' string with the total page count" do
592
+ pdf.start_new_page
593
+ allow(pdf).to receive(:text_box)
594
+ pdf.number_pages 'test, <total>', page_filter: :all
595
+ expect(pdf).to have_received(:text_box).with('test, 1', height: 50)
596
+ end
597
+
598
+ it 'must print each page if given the :all page_filter' do
599
+ 10.times { pdf.start_new_page }
600
+ allow(pdf).to receive(:text_box)
601
+ pdf.number_pages 'test', page_filter: :all
602
+ expect(pdf).to have_received(:text_box).exactly(10).times
603
+ end
604
+
605
+ it 'must print each page if no :page_filter is specified' do
606
+ 10.times { pdf.start_new_page }
607
+ allow(pdf).to receive(:text_box)
608
+ pdf.number_pages 'test'
609
+ expect(pdf).to have_received(:text_box).exactly(10).times
610
+ end
611
+
612
+ it 'must not print the page number if given a nil filter' do
613
+ 10.times { pdf.start_new_page }
614
+ allow(pdf).to receive(:text_box)
615
+ pdf.number_pages 'test', page_filter: nil
616
+ expect(pdf).to_not have_received(:text_box)
617
+ end
618
+
619
+ context 'with start_count_at option' do
620
+ [1, 2].each do |startat|
621
+ context "equal to #{startat}" do
622
+ it 'increments the pages' do
623
+ 2.times { pdf.start_new_page }
624
+ options = { page_filter: :all, start_count_at: startat }
625
+ allow(pdf).to receive(:text_box)
626
+ pdf.number_pages '<page> <total>', options
627
+
628
+ expect(pdf).to have_received(:text_box)
629
+ .with("#{startat} 2", height: 50)
630
+ expect(pdf).to have_received(:text_box)
631
+ .with("#{startat + 1} 2", height: 50)
632
+ end
633
+ end
634
+ end
635
+
636
+ [0, nil].each do |val|
637
+ context "equal to #{val}" do
638
+ it 'defaults to start at page 1' do
639
+ 3.times { pdf.start_new_page }
640
+ options = { page_filter: :all, start_count_at: val }
641
+ allow(pdf).to receive(:text_box)
642
+ pdf.number_pages '<page> <total>', options
643
+
644
+ expect(pdf).to have_received(:text_box).with('1 3', height: 50)
645
+ expect(pdf).to have_received(:text_box).with('2 3', height: 50)
646
+ expect(pdf).to have_received(:text_box).with('3 3', height: 50)
647
+ end
648
+ end
649
+ end
650
+ end
651
+
652
+ context 'with total_pages option' do
653
+ it 'allows the total pages count to be overridden' do
654
+ 2.times { pdf.start_new_page }
655
+ allow(pdf).to receive(:text_box)
656
+ pdf.number_pages '<page> <total>', page_filter: :all, total_pages: 10
657
+
658
+ expect(pdf).to have_received(:text_box).with('1 10', height: 50)
659
+ expect(pdf).to have_received(:text_box).with('2 10', height: 50)
660
+ end
661
+ end
662
+
663
+ context 'with special page filter' do
664
+ describe 'such as :odd' do
665
+ it 'increments the pages' do
666
+ 3.times { pdf.start_new_page }
667
+ allow(pdf).to receive(:text_box)
668
+ pdf.number_pages '<page> <total>', page_filter: :odd
669
+
670
+ expect(pdf).to have_received(:text_box).with('1 3', height: 50)
671
+ expect(pdf).to have_received(:text_box).with('3 3', height: 50)
672
+ expect(pdf).to_not have_received(:text_box).with('2 3', height: 50)
673
+ end
674
+ end
675
+
676
+ describe 'missing' do
677
+ it 'does not print any page numbers' do
678
+ 3.times { pdf.start_new_page }
679
+ allow(pdf).to receive(:text_box)
680
+ pdf.number_pages '<page> <total>', page_filter: nil
681
+
682
+ expect(pdf).to_not have_received(:text_box)
683
+ end
684
+ end
685
+ end
686
+
687
+ context 'with both a special page filter and a start_count_at parameter' do
688
+ describe 'such as :odd and 7' do
689
+ it 'increments the pages' do
690
+ 3.times { pdf.start_new_page }
691
+ allow(pdf).to receive(:text_box)
692
+ pdf.number_pages '<page> <total>',
693
+ page_filter: :odd,
694
+ start_count_at: 5
695
+
696
+ expect(pdf).to_not have_received(:text_box).with('1 3', height: 50)
697
+ # page 1
698
+ expect(pdf).to have_received(:text_box).with('5 3', height: 50)
699
+
700
+ # page 2
701
+ expect(pdf).to_not have_received(:text_box).with('6 3', height: 50)
702
+
703
+ # page 3
704
+ expect(pdf).to have_received(:text_box).with('7 3', height: 50)
705
+ end
706
+ end
707
+
708
+ context 'with some crazy proc and 2' do
709
+ it 'increments the pages' do
710
+ 6.times { pdf.start_new_page }
711
+ options = {
712
+ page_filter: ->(p) { p != 2 && p != 5 },
713
+ start_count_at: 4
714
+ }
715
+ allow(pdf).to receive(:text_box)
716
+ pdf.number_pages '<page> <total>', options
717
+
718
+ # page 1
719
+ expect(pdf).to have_received(:text_box).with('4 6', height: 50)
720
+
721
+ # page 2
722
+ expect(pdf).to_not have_received(:text_box).with('5 6', height: 50)
723
+
724
+ # page 3
725
+ expect(pdf).to have_received(:text_box).with('6 6', height: 50)
726
+
727
+ # page 4
728
+ expect(pdf).to have_received(:text_box).with('7 6', height: 50)
729
+
730
+ # page 5
731
+ expect(pdf).to_not have_received(:text_box).with('8 6', height: 50)
732
+
733
+ # page 6
734
+ expect(pdf).to have_received(:text_box).with('9 6', height: 50)
735
+ end
736
+ end
737
+ end
738
+
739
+ describe 'height option' do
740
+ before do
741
+ pdf.start_new_page
742
+ end
743
+
744
+ it 'with 10 height' do
745
+ allow(pdf).to receive(:text_box)
746
+ pdf.number_pages '<page> <total>', height: 10
747
+ expect(pdf).to have_received(:text_box).with('1 1', height: 10)
748
+ end
749
+
750
+ it 'with nil height' do
751
+ allow(pdf).to receive(:text_box)
752
+ pdf.number_pages '<page> <total>', height: nil
753
+ expect(pdf).to have_received(:text_box).with('1 1', height: nil)
754
+ end
755
+
756
+ it 'with no height' do
757
+ allow(pdf).to receive(:text_box)
758
+ pdf.number_pages '<page> <total>'
759
+ expect(pdf).to have_received(:text_box).with('1 1', height: 50)
760
+ end
761
+ end
762
+ end
763
+
764
+ describe '#page_match?' do
765
+ let(:pdf) do
766
+ described_class.new(skip_page_creation: true) do |pdf|
767
+ 10.times { pdf.start_new_page }
768
+ end
769
+ end
770
+
771
+ it 'returns nil given no filter' do
772
+ expect(pdf).to_not be_page_match(:nil, 1)
773
+ end
774
+
775
+ it 'must provide an :all filter' do
776
+ expect((1..pdf.page_count).all? { |i| pdf.page_match?(:all, i) })
777
+ .to eq true
778
+ end
779
+
780
+ it 'must provide an :odd filter' do
781
+ odd, even = (1..pdf.page_count).partition(&:odd?)
782
+ expect(odd.all? { |i| pdf.page_match?(:odd, i) }).to eq true
783
+ expect(even).to_not(be_any { |i| pdf.page_match?(:odd, i) })
784
+ end
785
+
786
+ it 'must be able to filter by an array of page numbers' do
787
+ fltr = [1, 2, 7]
788
+ expect((1..10).select { |i| pdf.page_match?(fltr, i) }).to eq([1, 2, 7])
789
+ end
790
+
791
+ it 'must be able to filter by a range of page numbers' do
792
+ fltr = 2..4
793
+ expect((1..10).select { |i| pdf.page_match?(fltr, i) }).to eq([2, 3, 4])
794
+ end
795
+
796
+ it 'must be able to filter by an arbitrary proc' do
797
+ fltr = ->(x) { x == 1 || (x % 3).zero? }
798
+ expect((1..10).select { |i| pdf.page_match?(fltr, i) })
799
+ .to eq([1, 3, 6, 9])
800
+ end
801
+ end
802
+ end