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