prawn 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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