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