prawn 2.1.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (280) 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 +9 -41
  7. data/lib/prawn.rb +37 -49
  8. data/lib/prawn/document.rb +193 -141
  9. data/lib/prawn/document/bounding_box.rb +50 -30
  10. data/lib/prawn/document/column_box.rb +7 -7
  11. data/lib/prawn/document/internals.rb +8 -6
  12. data/lib/prawn/document/span.rb +22 -16
  13. data/lib/prawn/encoding.rb +69 -68
  14. data/lib/prawn/errors.rb +12 -7
  15. data/lib/prawn/font.rb +104 -69
  16. data/lib/prawn/font_metric_cache.rb +20 -13
  17. data/lib/prawn/{font → fonts}/afm.rb +108 -72
  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 +126 -81
  22. data/lib/prawn/graphics.rb +119 -81
  23. data/lib/prawn/graphics/blend_mode.rb +9 -8
  24. data/lib/prawn/graphics/cap_style.rb +3 -3
  25. data/lib/prawn/graphics/color.rb +43 -39
  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 +204 -102
  29. data/lib/prawn/graphics/transformation.rb +15 -9
  30. data/lib/prawn/graphics/transparency.rb +17 -13
  31. data/lib/prawn/grid.rb +84 -48
  32. data/lib/prawn/image_handler.rb +5 -5
  33. data/lib/prawn/images.rb +60 -49
  34. data/lib/prawn/images/image.rb +2 -1
  35. data/lib/prawn/images/jpg.rb +31 -22
  36. data/lib/prawn/images/png.rb +67 -63
  37. data/lib/prawn/measurement_extensions.rb +10 -9
  38. data/lib/prawn/measurements.rb +19 -15
  39. data/lib/prawn/outline.rb +98 -77
  40. data/lib/prawn/repeater.rb +15 -11
  41. data/lib/prawn/security.rb +93 -70
  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 +76 -60
  46. data/lib/prawn/text/box.rb +18 -14
  47. data/lib/prawn/text/formatted.rb +5 -5
  48. data/lib/prawn/text/formatted/arranger.rb +80 -40
  49. data/lib/prawn/text/formatted/box.rb +140 -101
  50. data/lib/prawn/text/formatted/fragment.rb +11 -14
  51. data/lib/prawn/text/formatted/line_wrap.rb +128 -67
  52. data/lib/prawn/text/formatted/parser.rb +147 -123
  53. data/lib/prawn/text/formatted/wrap.rb +48 -32
  54. data/lib/prawn/transformation_stack.rb +7 -5
  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 +7 -8
  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 +7 -8
  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 +25 -18
  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 +15 -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 +18 -15
  82. data/manual/example_helper.rb +5 -4
  83. data/manual/graphics/blend_mode.rb +12 -9
  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 +5 -6
  88. data/manual/graphics/fill_rules.rb +10 -10
  89. data/manual/graphics/gradients.rb +27 -21
  90. data/manual/graphics/graphics.rb +48 -40
  91. data/manual/graphics/helper.rb +19 -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 +3 -4
  99. data/manual/graphics/stroke_cap.rb +6 -7
  100. data/manual/graphics/stroke_dash.rb +15 -16
  101. data/manual/graphics/stroke_join.rb +5 -6
  102. data/manual/graphics/translate.rb +10 -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 +10 -11
  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 +16 -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 +21 -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 +10 -8
  137. data/manual/text/formatted_callbacks.rb +33 -24
  138. data/manual/text/formatted_text.rb +36 -25
  139. data/manual/text/free_flowing_text.rb +22 -23
  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 +12 -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 +33 -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 +20 -19
  155. data/manual/text/utf8.rb +11 -12
  156. data/manual/text/win_ansi_charset.rb +27 -25
  157. data/prawn.gemspec +41 -34
  158. data/spec/extensions/encoding_helpers.rb +3 -3
  159. data/spec/prawn/document/bounding_box_spec.rb +550 -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 +44 -0
  167. data/spec/prawn/document_spec.rb +805 -0
  168. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  169. data/spec/prawn/font_spec.rb +544 -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 +872 -0
  173. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  174. data/spec/{image_handler_spec.rb → prawn/image_handler_spec.rb} +14 -14
  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 +229 -0
  178. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  179. data/spec/prawn/outline_spec.rb +512 -0
  180. data/spec/prawn/repeater_spec.rb +166 -0
  181. data/spec/prawn/soft_mask_spec.rb +74 -0
  182. data/spec/prawn/stamp_spec.rb +173 -0
  183. data/spec/prawn/text/box_spec.rb +1110 -0
  184. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  185. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  186. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  187. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  188. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  189. data/spec/prawn/text_draw_text_spec.rb +150 -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/{transformation_stack_spec.rb → prawn/transformation_stack_spec.rb} +22 -19
  195. data/spec/prawn/view_spec.rb +63 -0
  196. data/spec/prawn_manual_spec.rb +35 -0
  197. data/spec/spec_helper.rb +18 -19
  198. metadata +102 -222
  199. metadata.gz.sig +0 -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/blend_modes_bottom_layer.jpg +0 -0
  206. data/data/images/blend_modes_top_layer.jpg +0 -0
  207. data/data/images/dice.alpha +0 -0
  208. data/data/images/dice.color +0 -0
  209. data/data/images/dice.png +0 -0
  210. data/data/images/dice_interlaced.png +0 -0
  211. data/data/images/fractal.jpg +0 -0
  212. data/data/images/indexed_color.dat +0 -0
  213. data/data/images/indexed_color.png +0 -0
  214. data/data/images/indexed_transparency.png +0 -0
  215. data/data/images/indexed_transparency_alpha.dat +0 -0
  216. data/data/images/indexed_transparency_color.dat +0 -0
  217. data/data/images/letterhead.jpg +0 -0
  218. data/data/images/license.md +0 -8
  219. data/data/images/page_white_text.alpha +0 -0
  220. data/data/images/page_white_text.color +0 -0
  221. data/data/images/page_white_text.png +0 -0
  222. data/data/images/pigs.jpg +0 -0
  223. data/data/images/prawn.png +0 -0
  224. data/data/images/ruport.png +0 -0
  225. data/data/images/ruport_data.dat +0 -0
  226. data/data/images/ruport_transparent.png +0 -0
  227. data/data/images/ruport_type0.png +0 -0
  228. data/data/images/stef.jpg +0 -0
  229. data/data/images/tru256.bmp +0 -0
  230. data/data/images/web-links.dat +0 -1
  231. data/data/images/web-links.png +0 -0
  232. data/data/pdfs/complex_template.pdf +0 -0
  233. data/data/pdfs/contains_ttf_font.pdf +0 -0
  234. data/data/pdfs/encrypted.pdf +0 -0
  235. data/data/pdfs/form.pdf +1 -819
  236. data/data/pdfs/hexagon.pdf +0 -61
  237. data/data/pdfs/indirect_reference.pdf +0 -86
  238. data/data/pdfs/multipage_template.pdf +0 -127
  239. data/data/pdfs/nested_pages.pdf +0 -118
  240. data/data/pdfs/page_without_mediabox.pdf +0 -193
  241. data/data/pdfs/resources_as_indirect_object.pdf +0 -83
  242. data/data/pdfs/two_hexagons.pdf +0 -90
  243. data/data/pdfs/version_1_6.pdf +0 -61
  244. data/data/shift_jis_text.txt +0 -1
  245. data/spec/acceptance/png_spec.rb +0 -35
  246. data/spec/annotations_spec.rb +0 -67
  247. data/spec/blend_mode_spec.rb +0 -71
  248. data/spec/bounding_box_spec.rb +0 -501
  249. data/spec/column_box_spec.rb +0 -59
  250. data/spec/destinations_spec.rb +0 -13
  251. data/spec/document_spec.rb +0 -738
  252. data/spec/font_metric_cache_spec.rb +0 -52
  253. data/spec/font_spec.rb +0 -475
  254. data/spec/formatted_text_arranger_spec.rb +0 -452
  255. data/spec/formatted_text_box_spec.rb +0 -716
  256. data/spec/formatted_text_fragment_spec.rb +0 -299
  257. data/spec/graphics_spec.rb +0 -705
  258. data/spec/grid_spec.rb +0 -95
  259. data/spec/images_spec.rb +0 -167
  260. data/spec/inline_formatted_text_parser_spec.rb +0 -568
  261. data/spec/jpg_spec.rb +0 -23
  262. data/spec/line_wrap_spec.rb +0 -366
  263. data/spec/measurement_units_spec.rb +0 -22
  264. data/spec/outline_spec.rb +0 -409
  265. data/spec/png_spec.rb +0 -257
  266. data/spec/reference_spec.rb +0 -25
  267. data/spec/repeater_spec.rb +0 -154
  268. data/spec/security_spec.rb +0 -151
  269. data/spec/soft_mask_spec.rb +0 -78
  270. data/spec/span_spec.rb +0 -43
  271. data/spec/stamp_spec.rb +0 -179
  272. data/spec/stroke_styles_spec.rb +0 -208
  273. data/spec/text_at_spec.rb +0 -142
  274. data/spec/text_box_spec.rb +0 -1042
  275. data/spec/text_rendering_mode_spec.rb +0 -45
  276. data/spec/text_spacing_spec.rb +0 -93
  277. data/spec/text_spec.rb +0 -543
  278. data/spec/text_with_inline_formatting_spec.rb +0 -35
  279. data/spec/transparency_spec.rb +0 -91
  280. 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,872 @@
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.52285, 550.0], [100.0, 545.52285],
141
+ [100.0, 540.0], [100.0, 460.0], [100.0, 454.47715], [95.52285, 450.0],
142
+ [90.0, 450.0], [60.0, 450.0], [54.47715, 450.0], [50.0, 454.47715],
143
+ [50.0, 460.0], [50.0, 540.0], [50.0, 545.52285], [54.47715, 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.61424,
165
+ 113.80712, 150,
166
+ 100.0, 150.0,
167
+
168
+ 86.19288, 150.0,
169
+ 75.0, 127.61424,
170
+ 75.0, 100.0,
171
+
172
+ 75.0, 72.38576,
173
+ 86.19288, 50.0,
174
+ 100.0, 50.0,
175
+
176
+ 113.80712, 50.0,
177
+ 125.0, 72.38576,
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',
289
+ '0000FF'
290
+ )
291
+
292
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
293
+ pattern = grad.patterns.values.first
294
+
295
+ expect(pattern).to_not be_nil
296
+ expect(pattern[:Shading][:ShadingType]).to eq(2)
297
+ expect(pattern[:Shading][:Coords]).to eq([0, 0, pdf.bounds.width, 0])
298
+ expect(pattern[:Shading][:Function][:C0].zip([1, 0, 0]).all? do |x1, x2|
299
+ (x1 - x2).abs < 0.01
300
+ end).to eq true
301
+ expect(pattern[:Shading][:Function][:C1].zip([0, 0, 1]).all? do |x1, x2|
302
+ (x1 - x2).abs < 0.01
303
+ end).to eq true
304
+ end
305
+
306
+ it 'creates a unique ID for each pattern resource' do
307
+ pdf.fill_gradient(
308
+ [256, 512],
309
+ [356, 512],
310
+ 'ffffff',
311
+ 'fe00ff'
312
+ )
313
+ pdf.fill_gradient(
314
+ [256, 256],
315
+ [356, 256],
316
+ 'ffffff',
317
+ '0000ff'
318
+ )
319
+
320
+ str = pdf.render
321
+ pattern_ids = str.scan(/SP\h{40}\s+scn/)
322
+ expect(pattern_ids.uniq.length).to eq 2
323
+ end
324
+
325
+ it 'fill_gradient should set fill color to the pattern' do
326
+ pdf.fill_gradient(
327
+ [0, pdf.bounds.height],
328
+ [pdf.bounds.width, pdf.bounds.height],
329
+ 'FF0000',
330
+ '0000FF'
331
+ )
332
+
333
+ str = pdf.render
334
+ expect(str).to match(%r{/Pattern\s+cs\s*/SP\h{40}\s+scn})
335
+ end
336
+
337
+ it 'stroke_gradient should set stroke color to the pattern' do
338
+ pdf.stroke_gradient(
339
+ [0, pdf.bounds.height],
340
+ [pdf.bounds.width, pdf.bounds.height],
341
+ 'FF0000',
342
+ '0000FF'
343
+ )
344
+
345
+ str = pdf.render
346
+ expect(str).to match(%r{/Pattern\s+CS\s*/SP\h{40}\s+SCN})
347
+ end
348
+
349
+ it 'uses a stitching function to render a gradient with multiple stops' do
350
+ pdf.fill_gradient(
351
+ from: [0, pdf.bounds.height],
352
+ to: [pdf.bounds.width, pdf.bounds.height],
353
+ stops: { 0 => 'FF0000', 0.8 => '00FF00', 1 => '0000FF' }
354
+ )
355
+
356
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
357
+ pattern = grad.patterns.values.first
358
+
359
+ expect(pattern).to_not be_nil
360
+
361
+ stitching = pattern[:Shading][:Function]
362
+ expect(stitching[:FunctionType]).to eq(3)
363
+ expect(stitching[:Functions]).to be_an(Array)
364
+ expect(stitching[:Functions].map { |f| f[:C0] })
365
+ .to eq([[1, 0, 0], [0, 1, 0]])
366
+ expect(stitching[:Functions].map { |f| f[:C1] })
367
+ .to eq([[0, 1, 0], [0, 0, 1]])
368
+ expect(stitching[:Bounds]).to eq([0.8])
369
+ expect(stitching[:Encode]).to eq([0, 1, 0, 1])
370
+ end
371
+
372
+ it 'uses a stitching function to render a gradient with equally spaced '\
373
+ 'stops' do
374
+ pdf.fill_gradient(
375
+ from: [0, pdf.bounds.height],
376
+ to: [pdf.bounds.width, pdf.bounds.height],
377
+ stops: %w[FF0000 00FF00 0000FF]
378
+ )
379
+
380
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
381
+ pattern = grad.patterns.values.first
382
+
383
+ expect(pattern).to_not be_nil
384
+
385
+ stitching = pattern[:Shading][:Function]
386
+ expect(stitching[:FunctionType]).to eq(3)
387
+ expect(stitching[:Functions]).to be_an(Array)
388
+ expect(stitching[:Functions].map { |f| f[:C0] })
389
+ .to eq([[1, 0, 0], [0, 1, 0]])
390
+ expect(stitching[:Functions].map { |f| f[:C1] })
391
+ .to eq([[0, 1, 0], [0, 0, 1]])
392
+ expect(stitching[:Bounds]).to eq([0.5])
393
+ end
394
+ end
395
+
396
+ describe 'radial gradients' do
397
+ it 'creates a /Pattern resource' do
398
+ pdf.fill_gradient(
399
+ [0, pdf.bounds.height],
400
+ 10,
401
+ [pdf.bounds.width, pdf.bounds.height],
402
+ 20,
403
+ 'FF0000',
404
+ '0000FF'
405
+ )
406
+
407
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
408
+ pattern = grad.patterns.values.first
409
+
410
+ expect(pattern).to_not be_nil
411
+ expect(pattern[:Shading][:ShadingType]).to eq(3)
412
+ expect(pattern[:Shading][:Coords])
413
+ .to eq([0, 0, 10, pdf.bounds.width, 0, 20])
414
+ expect(pattern[:Shading][:Function][:C0].zip([1, 0, 0]).all? do |x1, x2|
415
+ (x1 - x2).abs < 0.01
416
+ end).to eq true
417
+ expect(pattern[:Shading][:Function][:C1].zip([0, 0, 1]).all? do |x1, x2|
418
+ (x1 - x2).abs < 0.01
419
+ end).to eq true
420
+ end
421
+
422
+ it 'fill_gradient should set fill color to the pattern' do
423
+ pdf.fill_gradient(
424
+ [0, pdf.bounds.height],
425
+ 10,
426
+ [pdf.bounds.width, pdf.bounds.height],
427
+ 20,
428
+ 'FF0000',
429
+ '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
+
436
+ it 'stroke_gradient should set stroke color to the pattern' do
437
+ pdf.stroke_gradient(
438
+ [0, pdf.bounds.height],
439
+ 10,
440
+ [pdf.bounds.width, pdf.bounds.height],
441
+ 20,
442
+ 'FF0000',
443
+ '0000FF'
444
+ )
445
+
446
+ str = pdf.render
447
+ expect(str).to match(%r{/Pattern\s+CS\s*/SP\h{40}\s+SCN})
448
+ end
449
+ end
450
+
451
+ describe 'gradient transformations' do
452
+ subject(:transformations) do
453
+ pdf.scale 2 do
454
+ pdf.translate 40, 40 do
455
+ pdf.fill_gradient [0, 10], [15, 15], 'FF0000', '0000FF', **opts
456
+ pdf.fill_gradient [0, 10], 15, [15, 15], 25, 'FF0000', '0000FF', **opts
457
+ end
458
+ end
459
+
460
+ grad = PDF::Inspector::Graphics::Pattern.analyze(pdf.render)
461
+ grad.patterns.values.map { |pattern| pattern[:Matrix] }.uniq
462
+ end
463
+
464
+ context 'when :apply_transformations is true' do
465
+ let(:opts) { { apply_transformations: true } }
466
+
467
+ it 'uses the transformation stack to translate user co-ordinates to '\
468
+ 'document co-ordinates required by /Pattern' do
469
+ expect(transformations).to eq([[2, 0, 0, 2, 80, 100]])
470
+ end
471
+ end
472
+
473
+ context 'when :apply_transformations is false' do
474
+ let(:opts) { { apply_transformations: false } }
475
+
476
+ it "doesn't transform the gradient" do
477
+ expect(transformations).to eq([[1, 0, 0, 1, 0, 10]])
478
+ end
479
+ end
480
+
481
+ context 'when :apply_transformations is unset' do
482
+ let(:opts) { {} }
483
+
484
+ it "doesn't transform the gradient and displays a warning" do
485
+ allow(pdf).to receive(:warn).twice
486
+ expect(transformations).to eq([[1, 0, 0, 1, 0, 10]])
487
+ expect(pdf).to have_received(:warn).twice
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ describe 'When using painting shortcuts' do
494
+ it 'converts stroke_some_method(args) into some_method(args); stroke' do
495
+ allow(pdf).to receive(:line_to).with([100, 100])
496
+ allow(pdf).to receive(:stroke)
497
+
498
+ pdf.stroke_line_to [100, 100]
499
+
500
+ expect(pdf).to have_received(:line_to).with([100, 100])
501
+ expect(pdf).to have_received(:stroke)
502
+ end
503
+
504
+ it 'converts fill_some_method(args) into some_method(args); fill' do
505
+ allow(pdf).to receive(:line_to).with([100, 100])
506
+ allow(pdf).to receive(:fill)
507
+
508
+ pdf.fill_line_to [100, 100]
509
+
510
+ expect(pdf).to have_received(:line_to).with([100, 100])
511
+ expect(pdf).to have_received(:fill)
512
+ end
513
+
514
+ it 'does not break method_missing' do
515
+ expect { pdf.i_have_a_pretty_girlfriend_named_jia }
516
+ .to raise_error(NoMethodError)
517
+ end
518
+ end
519
+
520
+ describe 'When using graphics states' do
521
+ it 'adds the right content on save_graphics_state' do
522
+ allow(pdf.renderer).to receive(:add_content).with('q')
523
+
524
+ pdf.save_graphics_state
525
+
526
+ expect(pdf.renderer).to have_received(:add_content).with('q')
527
+ end
528
+
529
+ it 'adds the right content on restore_graphics_state' do
530
+ allow(pdf.renderer).to receive(:add_content).with('Q')
531
+
532
+ pdf.restore_graphics_state
533
+
534
+ expect(pdf.renderer).to have_received(:add_content).with('Q')
535
+ end
536
+
537
+ it 'saves and restore when save_graphics_state is used with a block' do
538
+ allow(pdf.renderer).to receive(:add_content).with('q')
539
+ allow(pdf).to receive(:foo)
540
+ allow(pdf.renderer).to receive(:add_content).with('Q')
541
+
542
+ pdf.save_graphics_state do
543
+ pdf.foo
544
+ end
545
+
546
+ expect(pdf.renderer).to have_received(:add_content).with('q').ordered
547
+ expect(pdf).to have_received(:foo).ordered
548
+ expect(pdf.renderer).to have_received(:add_content).with('Q').ordered
549
+ end
550
+
551
+ it 'adds the previous color space when restoring to a graphic state with '\
552
+ 'different color space' do
553
+ pdf.stroke_color '000000'
554
+ pdf.save_graphics_state
555
+ pdf.stroke_color 0, 0, 0, 0
556
+ pdf.restore_graphics_state
557
+ pdf.stroke_color 0, 0, 100, 0
558
+ expect(pdf.graphic_state.color_space).to eq(stroke: :DeviceCMYK)
559
+ colors = PDF::Inspector::Graphics::Color.analyze(pdf.render)
560
+ expect(colors.color_space).to eq(:DeviceCMYK)
561
+ expect(colors.stroke_color_space_count[:DeviceCMYK]).to eq(2)
562
+ end
563
+
564
+ it 'uses the correct dash setting after restoring and starting new page' do
565
+ pdf.dash 5
566
+ pdf.save_graphics_state
567
+ pdf.dash 10
568
+ expect(pdf.graphic_state.dash[:dash]).to eq(10)
569
+ pdf.restore_graphics_state
570
+ pdf.start_new_page
571
+ expect(pdf.graphic_state.dash[:dash]).to eq(5)
572
+ end
573
+
574
+ it 'rounds dash values to four decimal places' do
575
+ pdf.dash 5.12345
576
+ expect(pdf.graphic_state.dash_setting).to eq('[5.12345 5.12345] 0.0 d')
577
+ end
578
+
579
+ it 'raises an error when dash is called w. a zero length or space' do
580
+ expect { pdf.dash(0) }.to raise_error(ArgumentError)
581
+ expect { pdf.dash([0]) }.to raise_error(ArgumentError)
582
+ expect { pdf.dash([0, 0]) }.to raise_error(ArgumentError)
583
+ end
584
+
585
+ it 'raises an error when dash is called w. negative lengths' do
586
+ expect { pdf.dash(-1) }.to raise_error(ArgumentError)
587
+ expect { pdf.dash([1, -3]) }.to raise_error(ArgumentError)
588
+ end
589
+
590
+ it 'the current graphic state keeps track of previous unchanged settings' do
591
+ pdf.stroke_color '000000'
592
+ pdf.save_graphics_state
593
+ pdf.dash 5
594
+ pdf.save_graphics_state
595
+ pdf.cap_style :round
596
+ pdf.save_graphics_state
597
+ pdf.fill_color 0, 0, 100, 0
598
+ pdf.save_graphics_state
599
+
600
+ expect(pdf.graphic_state.stroke_color).to eq('000000')
601
+ expect(pdf.graphic_state.join_style).to eq(:miter)
602
+ expect(pdf.graphic_state.fill_color).to eq([0, 0, 100, 0])
603
+ expect(pdf.graphic_state.cap_style).to eq(:round)
604
+ expect(pdf.graphic_state.color_space)
605
+ .to eq(fill: :DeviceCMYK, stroke: :DeviceRGB)
606
+ expect(pdf.graphic_state.dash).to eq(space: 5, phase: 0, dash: 5)
607
+ expect(pdf.graphic_state.line_width).to eq(1)
608
+ end
609
+
610
+ it "doesn't add extra graphic space closings when rendering multiple " \
611
+ 'times' do
612
+ pdf.render
613
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
614
+ expect(state.save_graphics_state_count).to eq(1)
615
+ expect(state.restore_graphics_state_count).to eq(1)
616
+ end
617
+
618
+ it 'adds extra graphic state enclosings when content is added on multiple '\
619
+ 'renderings' do
620
+ pdf.render
621
+ pdf.text 'Adding a bit more content'
622
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
623
+ expect(state.save_graphics_state_count).to eq(2)
624
+ expect(state.restore_graphics_state_count).to eq(2)
625
+ end
626
+
627
+ it 'adds extra graphic state enclosings when new settings are applied on '\
628
+ 'multiple renderings' do
629
+ pdf.render
630
+ pdf.stroke_color 0, 0, 0, 0
631
+ state = PDF::Inspector::Graphics::State.analyze(pdf.render)
632
+ expect(state.save_graphics_state_count).to eq(2)
633
+ expect(state.restore_graphics_state_count).to eq(2)
634
+ end
635
+
636
+ it 'raise_errors error if closing an empty graphic stack' do
637
+ expect do
638
+ pdf.render
639
+ pdf.restore_graphics_state
640
+ end.to raise_error(PDF::Core::Errors::EmptyGraphicStateStack)
641
+ end
642
+
643
+ it 'copies mutable attributes when passing a previous_state to '\
644
+ 'the initializer' do
645
+ new_state = PDF::Core::GraphicState.new(pdf.graphic_state)
646
+
647
+ %i[color_space dash fill_color stroke_color].each do |attr|
648
+ expect(new_state.public_send(attr)).to eq(pdf.graphic_state.public_send(attr))
649
+ expect(new_state.public_send(attr)).to_not equal(pdf.graphic_state.public_send(attr))
650
+ end
651
+ end
652
+
653
+ it 'copies mutable attributes when duping' do
654
+ new_state = pdf.graphic_state.dup
655
+
656
+ %i[color_space dash fill_color stroke_color].each do |attr|
657
+ expect(new_state.public_send(attr)).to eq(pdf.graphic_state.public_send(attr))
658
+ expect(new_state.public_send(attr)).to_not equal(pdf.graphic_state.public_send(attr))
659
+ end
660
+ end
661
+ end
662
+
663
+ describe 'When using transformation matrix' do
664
+ # NOTE: The (approximate) number of significant decimal digits of precision
665
+ # in fractional part is 5 (PDF Reference, Third Edition, p. 706)
666
+
667
+ it 'sends the right content on transformation_matrix' do
668
+ allow(pdf.renderer).to receive(:add_content)
669
+ .with('1.0 0.0 0.12346 -1.0 5.5 20.0 cm')
670
+ pdf.transformation_matrix 1, 0, 0.123456789, -1.0, 5.5, 20
671
+ expect(pdf.renderer).to have_received(:add_content)
672
+ .with('1.0 0.0 0.12346 -1.0 5.5 20.0 cm')
673
+ end
674
+
675
+ it 'uses fixed digits with very small number' do
676
+ values = Array.new(6, 0.000000000001)
677
+ string = Array.new(6, '0.0').join ' '
678
+ allow(pdf.renderer).to receive(:add_content).with("#{string} cm")
679
+ pdf.transformation_matrix(*values)
680
+ expect(pdf.renderer).to have_received(:add_content).with("#{string} cm")
681
+ end
682
+
683
+ it 'is received by the inspector' do
684
+ pdf.transformation_matrix 1, 0, 0, -1, 5.5, 20
685
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
686
+ expect(matrices.matrices).to eq([[1, 0, 0, -1, 5.5, 20]])
687
+ end
688
+
689
+ it 'saves the graphics state inside the given block' do
690
+ values = Array.new(6, 0.000000000001)
691
+ string = Array.new(6, '0.0').join ' '
692
+
693
+ allow(pdf).to receive(:save_graphics_state).with(no_args)
694
+ allow(pdf.renderer).to receive(:add_content).with(any_args).twice
695
+ allow(pdf.renderer).to receive(:add_content).with("#{string} cm")
696
+ allow(pdf).to receive(:do_something)
697
+ allow(pdf).to receive(:restore_graphics_state).with(no_args)
698
+
699
+ pdf.transformation_matrix(*values) do
700
+ pdf.do_something
701
+ end
702
+
703
+ expect(pdf).to have_received(:save_graphics_state).with(no_args).ordered
704
+ expect(pdf.renderer).to have_received(:add_content).with("#{string} cm")
705
+ .ordered
706
+ expect(pdf).to have_received(:do_something).ordered
707
+ expect(pdf).to have_received(:restore_graphics_state).with(no_args)
708
+ .ordered
709
+ end
710
+
711
+ it 'properly serializes the matrix', issue: 1178 do
712
+ pdf.transformation_matrix 1, 0, 0, 1, 0.0, 1.8977874316715393e-05
713
+ rendered_document = pdf.render
714
+
715
+ expect(rendered_document).to_not include('2.0e-05')
716
+
717
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(rendered_document)
718
+ expect(matrices.matrices).to eq([[1, 0, 0, 1, 0.0, 0.00002]])
719
+ end
720
+ end
721
+
722
+ describe 'When using transformations shortcuts' do
723
+ describe '#rotate' do
724
+ let(:angle) { 12.32 }
725
+ let(:cos) { Math.cos(angle * Math::PI / 180) }
726
+ let(:sin) { Math.sin(angle * Math::PI / 180) }
727
+
728
+ it 'rotates' do
729
+ allow(pdf).to receive(:transformation_matrix)
730
+ .with(cos, sin, -sin, cos, 0, 0)
731
+ pdf.rotate(angle)
732
+ expect(pdf).to have_received(:transformation_matrix)
733
+ .with(cos, sin, -sin, cos, 0, 0)
734
+ end
735
+ end
736
+
737
+ describe '#rotate with :origin option' do
738
+ let(:angle) { 12.32 }
739
+ let(:cos) { Math.cos(angle * Math::PI / 180) }
740
+ let(:sin) { Math.sin(angle * Math::PI / 180) }
741
+
742
+ it 'rotates around the origin' do
743
+ x = 12
744
+ y = 54.32
745
+ x_prime = x * cos - y * sin
746
+ y_prime = x * sin + y * cos
747
+
748
+ pdf.rotate(angle, origin: [x, y]) { pdf.text('hello world') }
749
+
750
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
751
+ expect(matrices.matrices[0]).to eq([
752
+ 1, 0, 0, 1,
753
+ reduce_precision(x - x_prime),
754
+ reduce_precision(y - y_prime)
755
+ ])
756
+ expect(matrices.matrices[1]).to eq([
757
+ reduce_precision(cos),
758
+ reduce_precision(sin),
759
+ reduce_precision(-sin),
760
+ reduce_precision(cos),
761
+ 0, 0
762
+ ])
763
+ end
764
+
765
+ it 'rotates around the origin in a document with a margin' do
766
+ x = 12
767
+ y = 54.32
768
+ pdf = Prawn::Document.new
769
+
770
+ pdf.rotate(angle, origin: [x, y]) { pdf.text('hello world') }
771
+
772
+ x_ = x + pdf.bounds.absolute_left
773
+ y_ = y + pdf.bounds.absolute_bottom
774
+ x_prime = x_ * cos - y_ * sin
775
+ y_prime = x_ * sin + y_ * cos
776
+
777
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
778
+ expect(matrices.matrices[0]).to eq([
779
+ 1, 0, 0, 1,
780
+ reduce_precision(x_ - x_prime),
781
+ reduce_precision(y_ - y_prime)
782
+ ])
783
+ expect(matrices.matrices[1]).to eq([
784
+ reduce_precision(cos),
785
+ reduce_precision(sin),
786
+ reduce_precision(-sin),
787
+ reduce_precision(cos),
788
+ 0, 0
789
+ ])
790
+ end
791
+
792
+ it 'raise_errors BlockRequired if no block is given' do
793
+ expect do
794
+ pdf.rotate(angle, origin: [0, 0])
795
+ end.to raise_error(Prawn::Errors::BlockRequired)
796
+ end
797
+ end
798
+
799
+ describe '#translate' do
800
+ it 'translates' do
801
+ x = 12
802
+ y = 54.32
803
+ allow(pdf).to receive(:transformation_matrix).with(1, 0, 0, 1, x, y)
804
+ pdf.translate(x, y)
805
+ expect(pdf).to have_received(:transformation_matrix)
806
+ .with(1, 0, 0, 1, x, y)
807
+ end
808
+ end
809
+
810
+ describe '#scale' do
811
+ it 'scales' do
812
+ factor = 0.12
813
+ allow(pdf).to receive(:transformation_matrix)
814
+ .with(factor, 0, 0, factor, 0, 0)
815
+ pdf.scale(factor)
816
+ expect(pdf).to have_received(:transformation_matrix)
817
+ .with(factor, 0, 0, factor, 0, 0)
818
+ end
819
+ end
820
+
821
+ describe '#scale with :origin option' do
822
+ let(:factor) { 0.12 }
823
+
824
+ it 'scales from the origin' do
825
+ x = 12
826
+ y = 54.32
827
+ x_prime = factor * x
828
+ y_prime = factor * y
829
+
830
+ pdf.scale(factor, origin: [x, y]) { pdf.text('hello world') }
831
+
832
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
833
+ expect(matrices.matrices[0]).to eq([
834
+ 1, 0, 0, 1,
835
+ reduce_precision(x - x_prime),
836
+ reduce_precision(y - y_prime)
837
+ ])
838
+ expect(matrices.matrices[1]).to eq([factor, 0, 0, factor, 0, 0])
839
+ end
840
+
841
+ it 'scales from the origin in a document with a margin' do
842
+ x = 12
843
+ y = 54.32
844
+ pdf = Prawn::Document.new
845
+ x_ = x + pdf.bounds.absolute_left
846
+ y_ = y + pdf.bounds.absolute_bottom
847
+ x_prime = factor * x_
848
+ y_prime = factor * y_
849
+
850
+ pdf.scale(factor, origin: [x, y]) { pdf.text('hello world') }
851
+
852
+ matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
853
+ expect(matrices.matrices[0]).to eq([
854
+ 1, 0, 0, 1,
855
+ reduce_precision(x_ - x_prime),
856
+ reduce_precision(y_ - y_prime)
857
+ ])
858
+ expect(matrices.matrices[1]).to eq([factor, 0, 0, factor, 0, 0])
859
+ end
860
+
861
+ it 'raise_errors BlockRequired if no block is given' do
862
+ expect do
863
+ pdf.scale(factor, origin: [0, 0])
864
+ end.to raise_error(Prawn::Errors::BlockRequired)
865
+ end
866
+ end
867
+ end
868
+
869
+ def reduce_precision(float)
870
+ float.round(5)
871
+ end
872
+ end