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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Graphics::BlendMode do
6
+ def make_blend_mode(blend_mode)
7
+ pdf.blend_mode(blend_mode) do
8
+ yield if block_given?
9
+ end
10
+ end
11
+
12
+ let(:pdf) { create_pdf }
13
+
14
+ it 'the PDF version should be at least 1.4' do
15
+ make_blend_mode(:Multiply)
16
+ str = pdf.render
17
+ expect(str[0, 8]).to eq('%PDF-1.4')
18
+ end
19
+
20
+ it 'a new extended graphics state should be created for ' \
21
+ 'each unique blend mode setting' do
22
+ make_blend_mode(:Multiply) do
23
+ make_blend_mode(:Screen)
24
+ end
25
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
26
+ expect(extgstates.length).to eq(2)
27
+ end
28
+
29
+ it 'a new extended graphics state should not be created for ' \
30
+ 'each duplicate blend mode setting' do
31
+ make_blend_mode(:Multiply) do
32
+ make_blend_mode(:Multiply)
33
+ end
34
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
35
+ expect(extgstates.length).to eq(1)
36
+ end
37
+
38
+ it 'setting the blend mode with only one parameter sets a single '\
39
+ 'blend mode value' do
40
+ make_blend_mode(:Multiply)
41
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first
42
+ expect(extgstate[:blend_mode]).to eq(:Multiply)
43
+ end
44
+
45
+ it 'setting the blend mode with multiple parameters sets an array of '\
46
+ 'blend modes' do
47
+ make_blend_mode(%i[Multiply Screen Overlay])
48
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first
49
+ expect(extgstate[:blend_mode]).to eq(%i[Multiply Screen Overlay])
50
+ end
51
+
52
+ describe 'with more than one page' do
53
+ it 'the extended graphic state resource should be added to both pages' do
54
+ make_blend_mode(:Multiply)
55
+ pdf.start_new_page
56
+ make_blend_mode(:Multiply)
57
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
58
+ extgstate = extgstates[0]
59
+ expect(extgstates.length).to eq(2)
60
+ expect(extgstate[:blend_mode]).to eq(:Multiply)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Graphics::Transparency do
6
+ def make_transparent(opacity, stroke_opacity = opacity)
7
+ pdf.transparent(opacity, stroke_opacity) do
8
+ yield if block_given?
9
+ end
10
+ end
11
+
12
+ let(:pdf) { create_pdf }
13
+
14
+ it 'the PDF version should be at least 1.4' do
15
+ make_transparent(0.5)
16
+ str = pdf.render
17
+ expect(str[0, 8]).to eq('%PDF-1.4')
18
+ end
19
+
20
+ it 'a new extended graphics state should be created for ' \
21
+ 'each unique transparency setting' do
22
+ make_transparent(0.5, 0.2) do
23
+ make_transparent(0.5, 0.75)
24
+ end
25
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
26
+ expect(extgstates.length).to eq(2)
27
+ end
28
+
29
+ it 'a new extended graphics state should not be created for ' \
30
+ 'each duplicate transparency setting' do
31
+ make_transparent(0.5, 0.75) do
32
+ make_transparent(0.5, 0.75)
33
+ end
34
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
35
+ expect(extgstates.length).to eq(1)
36
+ end
37
+
38
+ it 'setting the transparency with only one parameter sets the ' \
39
+ 'transparency for both the fill and the stroke' do
40
+ make_transparent(0.5)
41
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates[0]
42
+ expect(extgstate[:opacity]).to eq(0.5)
43
+ expect(extgstate[:stroke_opacity]).to eq(0.5)
44
+ end
45
+
46
+ it 'setting the transparency with a numerical parameter and ' \
47
+ 'a :stroke should set the fill transparency to the numerical parameter ' \
48
+ 'and the stroke transparency to the option' do
49
+ make_transparent(0.5, 0.2)
50
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates[0]
51
+ expect(extgstate[:opacity]).to eq(0.5)
52
+ expect(extgstate[:stroke_opacity]).to eq(0.2)
53
+ end
54
+
55
+ it 'does not allow negative values' do
56
+ make_transparent(-0.5, -0.2)
57
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates[0]
58
+ expect(extgstate[:opacity]).to eq(0.0)
59
+ expect(extgstate[:stroke_opacity]).to eq(0.0)
60
+ end
61
+
62
+ it 'does not allow too big values' do
63
+ make_transparent(2.0, 3.0)
64
+ extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates[0]
65
+ expect(extgstate[:opacity]).to eq(1.0)
66
+ expect(extgstate[:stroke_opacity]).to eq(1.0)
67
+ end
68
+
69
+ describe 'with more than one page' do
70
+ it 'the extended graphic state resource should be added to both pages' do
71
+ make_transparent(0.5, 0.2)
72
+ pdf.start_new_page
73
+ make_transparent(0.5, 0.2)
74
+ extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
75
+ extgstate = extgstates[0]
76
+ expect(extgstates.length).to eq(2)
77
+ expect(extgstate[:opacity]).to eq(0.5)
78
+ expect(extgstate[:stroke_opacity]).to eq(0.2)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,837 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Prawn::Graphics do
6
+ let(:pdf) { create_pdf }
7
+
8
+ describe 'When drawing a line' do
9
+ it 'draws a line from (100,600) to (100,500)' do
10
+ pdf.line([100, 600], [100, 500])
11
+
12
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
13
+
14
+ expect(line_drawing.points).to eq([[100, 600], [100, 500]])
15
+ end
16
+
17
+ it 'draws two lines at (100,600) to (100,500) and (75,100) to (50,125)' do
18
+ pdf.line(100, 600, 100, 500)
19
+ pdf.line(75, 100, 50, 125)
20
+
21
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
22
+
23
+ expect(line_drawing.points).to eq(
24
+ [[100.0, 600.0], [100.0, 500.0], [75.0, 100.0], [50.0, 125.0]]
25
+ )
26
+ end
27
+
28
+ it 'properlies set line width via line_width=' do
29
+ pdf.line_width = 10
30
+ line = PDF::Inspector::Graphics::Line.analyze(pdf.render)
31
+ expect(line.widths.first).to eq(10)
32
+ end
33
+
34
+ it 'properlies set line width via line_width(width)' do
35
+ pdf.line_width(10)
36
+ line = PDF::Inspector::Graphics::Line.analyze(pdf.render)
37
+ expect(line.widths.first).to eq(10)
38
+ end
39
+
40
+ it 'carries the current line width settings over to new pages' do
41
+ pdf.line_width(10)
42
+ pdf.start_new_page
43
+ line = PDF::Inspector::Graphics::Line.analyze(pdf.render)
44
+ expect(line.widths.length).to eq(2)
45
+ expect(line.widths[1]).to eq(10)
46
+ end
47
+
48
+ describe '(Horizontally)' do
49
+ it 'draws from [x1,pdf.y],[x2,pdf.y]' do
50
+ pdf.horizontal_line(100, 150)
51
+ line = PDF::Inspector::Graphics::Line.analyze(pdf.render)
52
+ expect(line.points).to eq([
53
+ [100.0 + pdf.bounds.absolute_left, pdf.y],
54
+ [150.0 + pdf.bounds.absolute_left, pdf.y]
55
+ ])
56
+ end
57
+
58
+ it 'draws a line from (200, 250) to (300, 250)' do
59
+ pdf.horizontal_line(200, 300, at: 250)
60
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
61
+ expect(line_drawing.points).to eq([[200, 250], [300, 250]])
62
+ end
63
+ end
64
+
65
+ describe '(Vertically)' do
66
+ it 'draws a line from (350, 300) to (350, 400)' do
67
+ pdf.vertical_line(300, 400, at: 350)
68
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
69
+ expect(line_drawing.points).to eq([[350, 300], [350, 400]])
70
+ end
71
+
72
+ it 'requires a y coordinate' do
73
+ expect { pdf.vertical_line(400, 500) }
74
+ .to raise_error(ArgumentError)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe 'When drawing a polygon' do
80
+ it 'draws each line passed to polygon()' do
81
+ pdf.polygon([100, 500], [100, 400], [200, 400])
82
+
83
+ line_drawing = PDF::Inspector::Graphics::Line.analyze(pdf.render)
84
+ expect(line_drawing.points)
85
+ .to eq([[100, 500], [100, 400], [200, 400], [100, 500]])
86
+ end
87
+ end
88
+
89
+ describe 'When drawing a rectangle' do
90
+ it 'uses a point, width, and height for coords' do
91
+ pdf.rectangle [200, 200], 50, 100
92
+
93
+ rectangles = PDF::Inspector::Graphics::Rectangle.analyze(pdf.render)
94
+ .rectangles
95
+ # PDF uses bottom left corner
96
+ expect(rectangles[0][:point]).to eq([200, 100])
97
+ expect(rectangles[0][:width]).to eq(50)
98
+ expect(rectangles[0][:height]).to eq(100)
99
+ end
100
+ end
101
+
102
+ describe 'When drawing a curve' do
103
+ it 'draws a bezier curve from 50,50 to 100,100' do
104
+ pdf.move_to [50, 50]
105
+ pdf.curve_to [100, 100], bounds: [[20, 90], [90, 70]]
106
+ curve = PDF::Inspector::Graphics::Curve.analyze(pdf.render)
107
+ expect(curve.coords)
108
+ .to eq([50.0, 50.0, 20.0, 90.0, 90.0, 70.0, 100.0, 100.0])
109
+ end
110
+
111
+ it 'draws a bezier curve from 100,100 to 50,50' do
112
+ pdf.curve [100, 100], [50, 50], bounds: [[20, 90], [90, 75]]
113
+ curve = PDF::Inspector::Graphics::Curve.analyze(pdf.render)
114
+ expect(curve.coords)
115
+ .to eq([100.0, 100.0, 20.0, 90.0, 90.0, 75.0, 50.0, 50.0])
116
+ end
117
+ end
118
+
119
+ describe 'When drawing a rounded rectangle' do
120
+ before { pdf.rounded_rectangle([50, 550], 50, 100, 10) }
121
+
122
+ let(:original_point) do
123
+ curve = PDF::Inspector::Graphics::Curve.analyze(pdf.render)
124
+ curve_points = curve.coords.each_slice(2).to_a
125
+ curve_points.shift
126
+ end
127
+ let(:all_coords) do
128
+ curve = PDF::Inspector::Graphics::Curve.analyze(pdf.render)
129
+ curve_points = curve.coords.each_slice(2).to_a
130
+ curve_points.shift
131
+ curves = curve_points.each_slice(3).to_a
132
+ line_points = PDF::Inspector::Graphics::Line.analyze(pdf.render).points
133
+ line_points.shift
134
+ line_points.zip(curves).flatten.each_slice(2).to_a.unshift original_point
135
+ end
136
+
137
+ it 'draws a rectangle by connecting lines with rounded bezier curves' do
138
+ expect(all_coords).to eq(
139
+ [
140
+ [60.0, 550.0], [90.0, 550.0], [95.5228, 550.0], [100.0, 545.5228],
141
+ [100.0, 540.0], [100.0, 460.0], [100.0, 454.4772], [95.5228, 450.0],
142
+ [90.0, 450.0], [60.0, 450.0], [54.4772, 450.0], [50.0, 454.4772],
143
+ [50.0, 460.0], [50.0, 540.0], [50.0, 545.5228], [54.4772, 550.0],
144
+ [60.0, 550.0]
145
+ ]
146
+ )
147
+ end
148
+
149
+ it 'starts and end with the same point' do
150
+ expect(original_point).to eq(all_coords.last)
151
+ end
152
+ end
153
+
154
+ describe 'When drawing an ellipse' do
155
+ let(:curve) do
156
+ pdf.ellipse [100, 100], 25, 50
157
+ PDF::Inspector::Graphics::Curve.analyze(pdf.render)
158
+ end
159
+
160
+ it 'uses a Bézier approximation' do
161
+ expect(curve.coords).to eq([
162
+ 125.0, 100.0,
163
+
164
+ 125.0, 127.6142,
165
+ 113.8071, 150,
166
+ 100.0, 150.0,
167
+
168
+ 86.1929, 150.0,
169
+ 75.0, 127.6142,
170
+ 75.0, 100.0,
171
+
172
+ 75.0, 72.3858,
173
+ 86.1929, 50.0,
174
+ 100.0, 50.0,
175
+
176
+ 113.8071, 50.0,
177
+ 125.0, 72.3858,
178
+ 125.0, 100.0,
179
+
180
+ 100.0, 100.0
181
+ ])
182
+ end
183
+
184
+ it 'moves the pointer to the center of the ellipse after drawing' do
185
+ expect(curve.coords[-2..-1]).to eq([100, 100])
186
+ end
187
+ end
188
+
189
+ describe 'When drawing a circle' do
190
+ let(:curve) do
191
+ pdf.circle [100, 100], 25
192
+ pdf.ellipse [100, 100], 25, 25
193
+ PDF::Inspector::Graphics::Curve.analyze(pdf.render)
194
+ end
195
+
196
+ it 'strokes the same path as the equivalent ellipse' do
197
+ middle = curve.coords.length / 2
198
+ expect(curve.coords[0...middle]).to eq(curve.coords[middle..-1])
199
+ end
200
+ end
201
+
202
+ describe 'When filling' do
203
+ it 'defaults to the f operator (nonzero winding number rule)' do
204
+ allow(pdf.renderer).to receive(:add_content).with('f')
205
+ pdf.fill
206
+ expect(pdf.renderer).to have_received(:add_content).with('f')
207
+ end
208
+
209
+ it 'uses f* for :fill_rule => :even_odd' do
210
+ allow(pdf.renderer).to receive(:add_content).with('f*')
211
+ pdf.fill(fill_rule: :even_odd)
212
+ expect(pdf.renderer).to have_received(:add_content).with('f*')
213
+ end
214
+
215
+ it 'uses b by default for fill_and_stroke (nonzero winding number)' do
216
+ allow(pdf.renderer).to receive(:add_content).with('b')
217
+ pdf.fill_and_stroke
218
+ expect(pdf.renderer).to have_received(:add_content).with('b')
219
+ end
220
+
221
+ it 'uses b* for fill_and_stroke(:fill_rule => :even_odd)' do
222
+ allow(pdf.renderer).to receive(:add_content).with('b*')
223
+ pdf.fill_and_stroke(fill_rule: :even_odd)
224
+ expect(pdf.renderer).to have_received(:add_content).with('b*')
225
+ end
226
+ end
227
+
228
+ describe 'When setting colors' do
229
+ it 'sets stroke colors' do
230
+ pdf.stroke_color 'ffcccc'
231
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
232
+ # 100% red, 80% green, 80% blue
233
+ expect(colors.stroke_color).to eq([1.0, 0.8, 0.8])
234
+ end
235
+
236
+ it 'sets fill colors' do
237
+ pdf.fill_color 'ccff00'
238
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
239
+ # 80% red, 100% green, 0% blue
240
+ expect(colors.fill_color).to eq([0.8, 1.0, 0])
241
+ end
242
+
243
+ it 'raises an error for a color with a leading #' do
244
+ expect { pdf.fill_color '#ccff00' }.to raise_error(ArgumentError)
245
+ end
246
+
247
+ it 'raises an error for a color string that is not a hex' do
248
+ expect { pdf.fill_color 'zcff00' }.to raise_error(ArgumentError)
249
+ end
250
+
251
+ it 'raises an error for a color string with invalid characters' do
252
+ expect { pdf.fill_color 'f0f0f?' }.to raise_error(ArgumentError)
253
+ end
254
+
255
+ it 'resets the colors on each new page if they have been defined' do
256
+ pdf.fill_color 'ccff00'
257
+
258
+ pdf.start_new_page
259
+ pdf.stroke_color 'ff00cc'
260
+
261
+ pdf.start_new_page
262
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
263
+ expect(colors.fill_color_count).to eq(3)
264
+ expect(colors.stroke_color_count).to eq(2)
265
+
266
+ expect(colors.fill_color).to eq([0.8, 1.0, 0.0])
267
+ expect(colors.stroke_color).to eq([1.0, 0.0, 0.8])
268
+ end
269
+
270
+ it 'sets the color space when setting colors on new pages to please fussy '\
271
+ 'readers' do
272
+ pdf.stroke_color '000000'
273
+ pdf.stroke { pdf.rectangle([10, 10], 10, 10) }
274
+ pdf.start_new_page
275
+ pdf.stroke_color '000000'
276
+ pdf.stroke { pdf.rectangle([10, 10], 10, 10) }
277
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
278
+ expect(colors.stroke_color_space_count[:DeviceRGB]).to eq(2)
279
+ end
280
+ end
281
+
282
+ describe 'Patterns' do
283
+ describe 'linear gradients' do
284
+ it 'creates a /Pattern resource' do
285
+ pdf.fill_gradient(
286
+ [0, pdf.bounds.height],
287
+ [pdf.bounds.width, pdf.bounds.height],
288
+ 'FF0000', '0000FF'
289
+ )
290
+
291
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
292
+ pattern = grad.patterns.values.first
293
+
294
+ expect(pattern).to_not be_nil
295
+ expect(pattern[:Shading][:ShadingType]).to eq(2)
296
+ expect(pattern[:Shading][:Coords]).to eq([0, 0, pdf.bounds.width, 0])
297
+ expect(pattern[:Shading][:Function][:C0].zip([1, 0, 0]).all? do |x1, x2|
298
+ (x1 - x2).abs < 0.01
299
+ end).to eq true
300
+ expect(pattern[:Shading][:Function][:C1].zip([0, 0, 1]).all? do |x1, x2|
301
+ (x1 - x2).abs < 0.01
302
+ end).to eq true
303
+ end
304
+
305
+ it 'creates a unique ID for each pattern resource' do
306
+ pdf.fill_gradient(
307
+ [256, 512],
308
+ [356, 512],
309
+ 'ffffff', 'fe00ff'
310
+ )
311
+ pdf.fill_gradient(
312
+ [256, 256],
313
+ [356, 256],
314
+ 'ffffff', '0000ff'
315
+ )
316
+
317
+ str = pdf.render
318
+ pattern_ids = str.scan(/SP\h{40}\s+scn/)
319
+ expect(pattern_ids.uniq.length).to eq 2
320
+ end
321
+
322
+ it 'fill_gradient should set fill color to the pattern' do
323
+ pdf.fill_gradient(
324
+ [0, pdf.bounds.height],
325
+ [pdf.bounds.width, pdf.bounds.height],
326
+ 'FF0000', '0000FF'
327
+ )
328
+
329
+ str = pdf.render
330
+ expect(str).to match(%r{/Pattern\s+cs\s*/SP\h{40}\s+scn})
331
+ end
332
+
333
+ it 'stroke_gradient should set stroke color to the pattern' do
334
+ pdf.stroke_gradient(
335
+ [0, pdf.bounds.height],
336
+ [pdf.bounds.width, pdf.bounds.height],
337
+ 'FF0000', '0000FF'
338
+ )
339
+
340
+ str = pdf.render
341
+ expect(str).to match(%r{/Pattern\s+CS\s*/SP\h{40}\s+SCN})
342
+ end
343
+
344
+ it 'uses a stitching function to render a gradient with multiple stops' do
345
+ pdf.fill_gradient(
346
+ from: [0, pdf.bounds.height],
347
+ to: [pdf.bounds.width, pdf.bounds.height],
348
+ stops: { 0 => 'FF0000', 0.8 => '00FF00', 1 => '0000FF' }
349
+ )
350
+
351
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
352
+ pattern = grad.patterns.values.first
353
+
354
+ expect(pattern).to_not be_nil
355
+
356
+ stitching = pattern[:Shading][:Function]
357
+ expect(stitching[:FunctionType]).to eq(3)
358
+ expect(stitching[:Functions]).to be_an(Array)
359
+ expect(stitching[:Functions].map { |f| f[:C0] })
360
+ .to eq([[1, 0, 0], [0, 1, 0]])
361
+ expect(stitching[:Functions].map { |f| f[:C1] })
362
+ .to eq([[0, 1, 0], [0, 0, 1]])
363
+ expect(stitching[:Bounds]).to eq([0.8])
364
+ expect(stitching[:Encode]).to eq([0, 1, 0, 1])
365
+ end
366
+
367
+ it 'uses a stitching function to render a gradient with equally spaced '\
368
+ 'stops' do
369
+ pdf.fill_gradient(
370
+ from: [0, pdf.bounds.height],
371
+ to: [pdf.bounds.width, pdf.bounds.height],
372
+ stops: %w[FF0000 00FF00 0000FF]
373
+ )
374
+
375
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
376
+ pattern = grad.patterns.values.first
377
+
378
+ expect(pattern).to_not be_nil
379
+
380
+ stitching = pattern[:Shading][:Function]
381
+ expect(stitching[:FunctionType]).to eq(3)
382
+ expect(stitching[:Functions]).to be_an(Array)
383
+ expect(stitching[:Functions].map { |f| f[:C0] })
384
+ .to eq([[1, 0, 0], [0, 1, 0]])
385
+ expect(stitching[:Functions].map { |f| f[:C1] })
386
+ .to eq([[0, 1, 0], [0, 0, 1]])
387
+ expect(stitching[:Bounds]).to eq([0.5])
388
+ end
389
+ end
390
+
391
+ describe 'radial gradients' do
392
+ it 'creates a /Pattern resource' do
393
+ pdf.fill_gradient(
394
+ [0, pdf.bounds.height], 10,
395
+ [pdf.bounds.width, pdf.bounds.height], 20,
396
+ 'FF0000', '0000FF'
397
+ )
398
+
399
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
400
+ pattern = grad.patterns.values.first
401
+
402
+ expect(pattern).to_not be_nil
403
+ expect(pattern[:Shading][:ShadingType]).to eq(3)
404
+ expect(pattern[:Shading][:Coords])
405
+ .to eq([0, 0, 10, pdf.bounds.width, 0, 20])
406
+ expect(pattern[:Shading][:Function][:C0].zip([1, 0, 0]).all? do |x1, x2|
407
+ (x1 - x2).abs < 0.01
408
+ end).to eq true
409
+ expect(pattern[:Shading][:Function][:C1].zip([0, 0, 1]).all? do |x1, x2|
410
+ (x1 - x2).abs < 0.01
411
+ end).to eq true
412
+ end
413
+
414
+ it 'fill_gradient should set fill color to the pattern' do
415
+ pdf.fill_gradient(
416
+ [0, pdf.bounds.height], 10,
417
+ [pdf.bounds.width, pdf.bounds.height], 20,
418
+ 'FF0000', '0000FF'
419
+ )
420
+
421
+ str = pdf.render
422
+ expect(str).to match(%r{/Pattern\s+cs\s*/SP\h{40}\s+scn})
423
+ end
424
+
425
+ it 'stroke_gradient should set stroke color to the pattern' do
426
+ pdf.stroke_gradient(
427
+ [0, pdf.bounds.height], 10,
428
+ [pdf.bounds.width, pdf.bounds.height], 20,
429
+ 'FF0000', '0000FF'
430
+ )
431
+
432
+ str = pdf.render
433
+ expect(str).to match(%r{/Pattern\s+CS\s*/SP\h{40}\s+SCN})
434
+ end
435
+ end
436
+
437
+ describe 'gradient transformations' do
438
+ subject(:transformations) do
439
+ pdf.scale 2 do
440
+ pdf.translate 40, 40 do
441
+ pdf.fill_gradient [0, 10], [15, 15], 'FF0000', '0000FF', opts
442
+ pdf.fill_gradient [0, 10], 15, [15, 15], 25, 'FF0000', '0000FF',
443
+ opts
444
+ end
445
+ end
446
+
447
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
448
+ grad.patterns.values.map { |pattern| pattern[:Matrix] }.uniq
449
+ end
450
+
451
+ context 'when :apply_transformations is true' do
452
+ let(:opts) { { apply_transformations: true } }
453
+
454
+ it 'uses the transformation stack to translate user co-ordinates to '\
455
+ 'document co-ordinates required by /Pattern' do
456
+ expect(transformations).to eq([[2, 0, 0, 2, 80, 100]])
457
+ end
458
+ end
459
+
460
+ context 'when :apply_transformations is false' do
461
+ let(:opts) { { apply_transformations: false } }
462
+
463
+ it "doesn't transform the gradient" do
464
+ expect(transformations).to eq([[1, 0, 0, 1, 0, 10]])
465
+ end
466
+ end
467
+
468
+ context 'when :apply_transformations is unset' do
469
+ let(:opts) { {} }
470
+
471
+ it "doesn't transform the gradient and displays a warning" do
472
+ allow(pdf).to receive(:warn).twice
473
+ expect(transformations).to eq([[1, 0, 0, 1, 0, 10]])
474
+ expect(pdf).to have_received(:warn).twice
475
+ end
476
+ end
477
+ end
478
+ end
479
+
480
+ describe 'When using painting shortcuts' do
481
+ it 'converts stroke_some_method(args) into some_method(args); stroke' do
482
+ allow(pdf).to receive(:line_to).with([100, 100])
483
+ allow(pdf).to receive(:stroke)
484
+
485
+ pdf.stroke_line_to [100, 100]
486
+
487
+ expect(pdf).to have_received(:line_to).with([100, 100])
488
+ expect(pdf).to have_received(:stroke)
489
+ end
490
+
491
+ it 'converts fill_some_method(args) into some_method(args); fill' do
492
+ allow(pdf).to receive(:line_to).with([100, 100])
493
+ allow(pdf).to receive(:fill)
494
+
495
+ pdf.fill_line_to [100, 100]
496
+
497
+ expect(pdf).to have_received(:line_to).with([100, 100])
498
+ expect(pdf).to have_received(:fill)
499
+ end
500
+
501
+ it 'does not break method_missing' do
502
+ expect { pdf.i_have_a_pretty_girlfriend_named_jia }
503
+ .to raise_error(NoMethodError)
504
+ end
505
+ end
506
+
507
+ describe 'When using graphics states' do
508
+ it 'adds the right content on save_graphics_state' do
509
+ allow(pdf.renderer).to receive(:add_content).with('q')
510
+
511
+ pdf.save_graphics_state
512
+
513
+ expect(pdf.renderer).to have_received(:add_content).with('q')
514
+ end
515
+
516
+ it 'adds the right content on restore_graphics_state' do
517
+ allow(pdf.renderer).to receive(:add_content).with('Q')
518
+
519
+ pdf.restore_graphics_state
520
+
521
+ expect(pdf.renderer).to have_received(:add_content).with('Q')
522
+ end
523
+
524
+ it 'saves and restore when save_graphics_state is used with a block' do
525
+ allow(pdf.renderer).to receive(:add_content).with('q')
526
+ allow(pdf).to receive(:foo)
527
+ allow(pdf.renderer).to receive(:add_content).with('Q')
528
+
529
+ pdf.save_graphics_state do
530
+ pdf.foo
531
+ end
532
+
533
+ expect(pdf.renderer).to have_received(:add_content).with('q').ordered
534
+ expect(pdf).to have_received(:foo).ordered
535
+ expect(pdf.renderer).to have_received(:add_content).with('Q').ordered
536
+ end
537
+
538
+ it 'adds the previous color space when restoring to a graphic state with '\
539
+ 'different color space' do
540
+ pdf.stroke_color '000000'
541
+ pdf.save_graphics_state
542
+ pdf.stroke_color 0, 0, 0, 0
543
+ pdf.restore_graphics_state
544
+ pdf.stroke_color 0, 0, 100, 0
545
+ expect(pdf.graphic_state.color_space).to eq(stroke: :DeviceCMYK)
546
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
547
+ expect(colors.color_space).to eq(:DeviceCMYK)
548
+ expect(colors.stroke_color_space_count[:DeviceCMYK]).to eq(2)
549
+ end
550
+
551
+ it 'uses the correct dash setting after restoring and starting new page' do
552
+ pdf.dash 5
553
+ pdf.save_graphics_state
554
+ pdf.dash 10
555
+ expect(pdf.graphic_state.dash[:dash]).to eq(10)
556
+ pdf.restore_graphics_state
557
+ pdf.start_new_page
558
+ expect(pdf.graphic_state.dash[:dash]).to eq(5)
559
+ end
560
+
561
+ it 'rounds dash values to four decimal places' do
562
+ pdf.dash 5.12345
563
+ expect(pdf.graphic_state.dash_setting).to eq('[5.1235 5.1235] 0.0 d')
564
+ end
565
+
566
+ it 'raises an error when dash is called w. a zero length or space' do
567
+ expect { pdf.dash(0) }.to raise_error(ArgumentError)
568
+ expect { pdf.dash([0]) }.to raise_error(ArgumentError)
569
+ expect { pdf.dash([0, 0]) }.to raise_error(ArgumentError)
570
+ end
571
+
572
+ it 'raises an error when dash is called w. negative lengths' do
573
+ expect { pdf.dash(-1) }.to raise_error(ArgumentError)
574
+ expect { pdf.dash([1, -3]) }.to raise_error(ArgumentError)
575
+ end
576
+
577
+ it 'the current graphic state keeps track of previous unchanged settings' do
578
+ pdf.stroke_color '000000'
579
+ pdf.save_graphics_state
580
+ pdf.dash 5
581
+ pdf.save_graphics_state
582
+ pdf.cap_style :round
583
+ pdf.save_graphics_state
584
+ pdf.fill_color 0, 0, 100, 0
585
+ pdf.save_graphics_state
586
+
587
+ expect(pdf.graphic_state.stroke_color).to eq('000000')
588
+ expect(pdf.graphic_state.join_style).to eq(:miter)
589
+ expect(pdf.graphic_state.fill_color).to eq([0, 0, 100, 0])
590
+ expect(pdf.graphic_state.cap_style).to eq(:round)
591
+ expect(pdf.graphic_state.color_space)
592
+ .to eq(fill: :DeviceCMYK, stroke: :DeviceRGB)
593
+ expect(pdf.graphic_state.dash).to eq(space: 5, phase: 0, dash: 5)
594
+ expect(pdf.graphic_state.line_width).to eq(1)
595
+ end
596
+
597
+ it "doesn't add extra graphic space closings when rendering multiple " \
598
+ 'times' do
599
+ pdf.render
600
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
601
+ expect(state.save_graphics_state_count).to eq(1)
602
+ expect(state.restore_graphics_state_count).to eq(1)
603
+ end
604
+
605
+ it 'adds extra graphic state enclosings when content is added on multiple '\
606
+ 'renderings' do
607
+ pdf.render
608
+ pdf.text 'Adding a bit more content'
609
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
610
+ expect(state.save_graphics_state_count).to eq(2)
611
+ expect(state.restore_graphics_state_count).to eq(2)
612
+ end
613
+
614
+ it 'adds extra graphic state enclosings when new settings are applied on '\
615
+ 'multiple renderings' do
616
+ pdf.render
617
+ pdf.stroke_color 0, 0, 0, 0
618
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
619
+ expect(state.save_graphics_state_count).to eq(2)
620
+ expect(state.restore_graphics_state_count).to eq(2)
621
+ end
622
+
623
+ it 'raise_errors error if closing an empty graphic stack' do
624
+ expect do
625
+ pdf.render
626
+ pdf.restore_graphics_state
627
+ end.to raise_error(PDF::Core::Errors::EmptyGraphicStateStack)
628
+ end
629
+
630
+ it 'copies mutable attributes when passing a previous_state to '\
631
+ 'the initializer' do
632
+ new_state = PDF::Core::GraphicState.new(pdf.graphic_state)
633
+
634
+ %i[color_space dash fill_color stroke_color].each do |attr|
635
+ expect(new_state.send(attr)).to eq(pdf.graphic_state.send(attr))
636
+ expect(new_state.send(attr)).to_not equal(pdf.graphic_state.send(attr))
637
+ end
638
+ end
639
+
640
+ it 'copies mutable attributes when duping' do
641
+ new_state = pdf.graphic_state.dup
642
+
643
+ %i[color_space dash fill_color stroke_color].each do |attr|
644
+ expect(new_state.send(attr)).to eq(pdf.graphic_state.send(attr))
645
+ expect(new_state.send(attr)).to_not equal(pdf.graphic_state.send(attr))
646
+ end
647
+ end
648
+ end
649
+
650
+ describe 'When using transformation matrix' do
651
+ # Note: The (approximate) number of significant decimal digits of precision
652
+ # in fractional part is 5 (PDF Reference, Third Edition, p. 706)
653
+
654
+ it 'sends the right content on transformation_matrix' do
655
+ allow(pdf.renderer).to receive(:add_content)
656
+ .with('1.0 0.0 0.12346 -1.0 5.5 20.0 cm')
657
+ pdf.transformation_matrix 1, 0, 0.123456789, -1.0, 5.5, 20
658
+ expect(pdf.renderer).to have_received(:add_content)
659
+ .with('1.0 0.0 0.12346 -1.0 5.5 20.0 cm')
660
+ end
661
+
662
+ it 'uses fixed digits with very small number' do
663
+ values = Array.new(6, 0.000000000001)
664
+ string = Array.new(6, '0.0').join ' '
665
+ allow(pdf.renderer).to receive(:add_content).with("#{string} cm")
666
+ pdf.transformation_matrix(*values)
667
+ expect(pdf.renderer).to have_received(:add_content).with("#{string} cm")
668
+ end
669
+
670
+ it 'is received by the inspector' do
671
+ pdf.transformation_matrix 1, 0, 0, -1, 5.5, 20
672
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
673
+ expect(matrices.matrices).to eq([[1, 0, 0, -1, 5.5, 20]])
674
+ end
675
+
676
+ it 'saves the graphics state inside the given block' do
677
+ values = Array.new(6, 0.000000000001)
678
+ string = Array.new(6, '0.0').join ' '
679
+
680
+ allow(pdf).to receive(:save_graphics_state).with(no_args)
681
+ allow(pdf.renderer).to receive(:add_content).with(any_args).twice
682
+ allow(pdf.renderer).to receive(:add_content).with("#{string} cm")
683
+ allow(pdf).to receive(:do_something)
684
+ allow(pdf).to receive(:restore_graphics_state).with(no_args)
685
+
686
+ pdf.transformation_matrix(*values) do
687
+ pdf.do_something
688
+ end
689
+
690
+ expect(pdf).to have_received(:save_graphics_state).with(no_args).ordered
691
+ expect(pdf.renderer).to have_received(:add_content).with("#{string} cm")
692
+ .ordered
693
+ expect(pdf).to have_received(:do_something).ordered
694
+ expect(pdf).to have_received(:restore_graphics_state).with(no_args)
695
+ .ordered
696
+ end
697
+ end
698
+
699
+ describe 'When using transformations shortcuts' do
700
+ let(:x) { 12 }
701
+ let(:y) { 54.32 }
702
+ let(:angle) { 12.32 }
703
+ let(:cos) { Math.cos(angle * Math::PI / 180) }
704
+ let(:sin) { Math.sin(angle * Math::PI / 180) }
705
+ let(:factor) { 0.12 }
706
+
707
+ describe '#rotate' do
708
+ it 'rotates' do
709
+ allow(pdf).to receive(:transformation_matrix)
710
+ .with(cos, sin, -sin, cos, 0, 0)
711
+ pdf.rotate(angle)
712
+ expect(pdf).to have_received(:transformation_matrix)
713
+ .with(cos, sin, -sin, cos, 0, 0)
714
+ end
715
+ end
716
+
717
+ describe '#rotate with :origin option' do
718
+ it 'rotates around the origin' do
719
+ x_prime = x * cos - y * sin
720
+ y_prime = x * sin + y * cos
721
+
722
+ pdf.rotate(angle, origin: [x, y]) { pdf.text('hello world') }
723
+
724
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
725
+ expect(matrices.matrices[0]).to eq([
726
+ 1, 0, 0, 1,
727
+ reduce_precision(x - x_prime),
728
+ reduce_precision(y - y_prime)
729
+ ])
730
+ expect(matrices.matrices[1]).to eq([
731
+ reduce_precision(cos),
732
+ reduce_precision(sin),
733
+ reduce_precision(-sin),
734
+ reduce_precision(cos),
735
+ 0, 0
736
+ ])
737
+ end
738
+
739
+ it 'rotates around the origin in a document with a margin' do
740
+ pdf = Prawn::Document.new
741
+
742
+ pdf.rotate(angle, origin: [x, y]) { pdf.text('hello world') }
743
+
744
+ x_ = x + pdf.bounds.absolute_left
745
+ y_ = y + pdf.bounds.absolute_bottom
746
+ x_prime = x_ * cos - y_ * sin
747
+ y_prime = x_ * sin + y_ * cos
748
+
749
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
750
+ expect(matrices.matrices[0]).to eq([
751
+ 1, 0, 0, 1,
752
+ reduce_precision(x_ - x_prime),
753
+ reduce_precision(y_ - y_prime)
754
+ ])
755
+ expect(matrices.matrices[1]).to eq([
756
+ reduce_precision(cos),
757
+ reduce_precision(sin),
758
+ reduce_precision(-sin),
759
+ reduce_precision(cos),
760
+ 0, 0
761
+ ])
762
+ end
763
+
764
+ it 'raise_errors BlockRequired if no block is given' do
765
+ expect do
766
+ pdf.rotate(angle, origin: [x, y])
767
+ end.to raise_error(Prawn::Errors::BlockRequired)
768
+ end
769
+ end
770
+
771
+ describe '#translate' do
772
+ it 'translates' do
773
+ x = 12
774
+ y = 54.32
775
+ allow(pdf).to receive(:transformation_matrix).with(1, 0, 0, 1, x, y)
776
+ pdf.translate(x, y)
777
+ expect(pdf).to have_received(:transformation_matrix)
778
+ .with(1, 0, 0, 1, x, y)
779
+ end
780
+ end
781
+
782
+ describe '#scale' do
783
+ it 'scales' do
784
+ allow(pdf).to receive(:transformation_matrix)
785
+ .with(factor, 0, 0, factor, 0, 0)
786
+ pdf.scale(factor)
787
+ expect(pdf).to have_received(:transformation_matrix)
788
+ .with(factor, 0, 0, factor, 0, 0)
789
+ end
790
+ end
791
+
792
+ describe '#scale with :origin option' do
793
+ it 'scales from the origin' do
794
+ x_prime = factor * x
795
+ y_prime = factor * y
796
+
797
+ pdf.scale(factor, origin: [x, y]) { pdf.text('hello world') }
798
+
799
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
800
+ expect(matrices.matrices[0]).to eq([
801
+ 1, 0, 0, 1,
802
+ reduce_precision(x - x_prime),
803
+ reduce_precision(y - y_prime)
804
+ ])
805
+ expect(matrices.matrices[1]).to eq([factor, 0, 0, factor, 0, 0])
806
+ end
807
+
808
+ it 'scales from the origin in a document with a margin' do
809
+ pdf = Prawn::Document.new
810
+ x_ = x + pdf.bounds.absolute_left
811
+ y_ = y + pdf.bounds.absolute_bottom
812
+ x_prime = factor * x_
813
+ y_prime = factor * y_
814
+
815
+ pdf.scale(factor, origin: [x, y]) { pdf.text('hello world') }
816
+
817
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
818
+ expect(matrices.matrices[0]).to eq([
819
+ 1, 0, 0, 1,
820
+ reduce_precision(x_ - x_prime),
821
+ reduce_precision(y_ - y_prime)
822
+ ])
823
+ expect(matrices.matrices[1]).to eq([factor, 0, 0, factor, 0, 0])
824
+ end
825
+
826
+ it 'raise_errors BlockRequired if no block is given' do
827
+ expect do
828
+ pdf.scale(factor, origin: [x, y])
829
+ end.to raise_error(Prawn::Errors::BlockRequired)
830
+ end
831
+ end
832
+ end
833
+
834
+ def reduce_precision(float)
835
+ float.round(5)
836
+ end
837
+ end