prawn 0.13.0 → 2.4.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 (348) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +10 -0
  4. data/GPLv2 +20 -21
  5. data/Gemfile +3 -16
  6. data/Rakefile +17 -39
  7. data/lib/prawn/document/bounding_box.rb +85 -42
  8. data/lib/prawn/document/column_box.rb +21 -11
  9. data/lib/prawn/document/internals.rb +40 -147
  10. data/lib/prawn/document/span.rb +25 -17
  11. data/lib/prawn/document.rb +286 -245
  12. data/lib/prawn/encoding.rb +68 -101
  13. data/lib/prawn/errors.rb +47 -43
  14. data/lib/prawn/font.rb +204 -155
  15. data/lib/prawn/font_metric_cache.rb +25 -21
  16. data/lib/prawn/fonts/afm.rb +292 -0
  17. data/lib/prawn/{font → fonts}/dfont.rb +7 -13
  18. data/lib/prawn/fonts/otf.rb +11 -0
  19. data/lib/prawn/fonts/ttc.rb +36 -0
  20. data/lib/prawn/{font → fonts}/ttf.rb +142 -80
  21. data/lib/prawn/graphics/blend_mode.rb +65 -0
  22. data/lib/prawn/graphics/cap_style.rb +6 -5
  23. data/lib/prawn/graphics/color.rb +47 -44
  24. data/lib/prawn/graphics/dash.rb +30 -13
  25. data/lib/prawn/graphics/join_style.rb +13 -6
  26. data/lib/prawn/graphics/patterns.rb +221 -90
  27. data/lib/prawn/graphics/transformation.rb +21 -12
  28. data/lib/prawn/graphics/transparency.rb +21 -17
  29. data/lib/prawn/graphics.rb +155 -128
  30. data/lib/prawn/{layout/grid.rb → grid.rb} +110 -47
  31. data/lib/prawn/image_handler.rb +16 -2
  32. data/lib/prawn/images/image.rb +4 -2
  33. data/lib/prawn/images/jpg.rb +39 -30
  34. data/lib/prawn/images/png.rb +132 -169
  35. data/lib/prawn/images.rb +70 -62
  36. data/lib/prawn/measurement_extensions.rb +15 -10
  37. data/lib/prawn/measurements.rb +22 -23
  38. data/lib/prawn/outline.rb +301 -13
  39. data/lib/prawn/repeater.rb +19 -17
  40. data/lib/prawn/security/arcfour.rb +54 -0
  41. data/lib/prawn/security.rb +108 -86
  42. data/lib/prawn/soft_mask.rb +40 -41
  43. data/lib/prawn/stamp.rb +29 -12
  44. data/lib/prawn/text/box.rb +27 -29
  45. data/lib/prawn/text/formatted/arranger.rb +110 -67
  46. data/lib/prawn/text/formatted/box.rb +233 -165
  47. data/lib/prawn/text/formatted/fragment.rb +27 -27
  48. data/lib/prawn/text/formatted/line_wrap.rb +137 -97
  49. data/lib/prawn/text/formatted/parser.rb +149 -127
  50. data/lib/prawn/text/formatted/wrap.rb +57 -37
  51. data/lib/prawn/text/formatted.rb +6 -4
  52. data/lib/prawn/text.rb +105 -73
  53. data/lib/prawn/transformation_stack.rb +44 -0
  54. data/lib/prawn/utilities.rb +11 -21
  55. data/lib/prawn/version.rb +5 -0
  56. data/lib/prawn/view.rb +101 -0
  57. data/lib/prawn.rb +42 -68
  58. data/{data/images/fractal.jpg → manual/absolute_position.pdf} +0 -0
  59. data/manual/basic_concepts/adding_pages.rb +9 -10
  60. data/manual/basic_concepts/basic_concepts.rb +33 -24
  61. data/manual/basic_concepts/creation.rb +10 -11
  62. data/manual/basic_concepts/cursor.rb +9 -10
  63. data/manual/basic_concepts/measurement.rb +10 -11
  64. data/manual/basic_concepts/origin.rb +8 -9
  65. data/manual/basic_concepts/other_cursor_helpers.rb +17 -18
  66. data/manual/basic_concepts/view.rb +48 -0
  67. data/manual/bounding_box/bounding_box.rb +31 -29
  68. data/manual/bounding_box/bounds.rb +17 -18
  69. data/manual/bounding_box/canvas.rb +8 -9
  70. data/manual/bounding_box/creation.rb +8 -9
  71. data/manual/bounding_box/indentation.rb +22 -23
  72. data/manual/bounding_box/nesting.rb +32 -25
  73. data/manual/bounding_box/russian_boxes.rb +19 -19
  74. data/manual/bounding_box/stretchy.rb +18 -20
  75. data/manual/contents.rb +35 -0
  76. data/manual/cover.rb +43 -0
  77. data/manual/document_and_page_options/background.rb +16 -14
  78. data/manual/document_and_page_options/document_and_page_options.rb +26 -23
  79. data/manual/document_and_page_options/metadata.rb +21 -19
  80. data/manual/document_and_page_options/page_margins.rb +20 -22
  81. data/manual/document_and_page_options/page_size.rb +15 -15
  82. data/manual/document_and_page_options/print_scaling.rb +23 -0
  83. data/manual/example_helper.rb +5 -408
  84. data/manual/graphics/blend_mode.rb +52 -0
  85. data/manual/graphics/circle_and_ellipse.rb +8 -9
  86. data/manual/graphics/color.rb +11 -13
  87. data/manual/graphics/common_lines.rb +13 -12
  88. data/manual/graphics/fill_and_stroke.rb +10 -11
  89. data/manual/graphics/fill_rules.rb +13 -12
  90. data/manual/graphics/gradients.rb +28 -22
  91. data/manual/graphics/graphics.rb +52 -46
  92. data/manual/graphics/helper.rb +20 -10
  93. data/manual/graphics/line_width.rb +13 -12
  94. data/manual/graphics/lines_and_curves.rb +13 -14
  95. data/manual/graphics/polygon.rb +10 -12
  96. data/manual/graphics/rectangle.rb +7 -8
  97. data/manual/graphics/rotate.rb +9 -12
  98. data/manual/graphics/scale.rb +19 -18
  99. data/manual/graphics/soft_masks.rb +5 -7
  100. data/manual/graphics/stroke_cap.rb +10 -11
  101. data/manual/graphics/stroke_dash.rb +16 -17
  102. data/manual/graphics/stroke_join.rb +10 -11
  103. data/manual/graphics/translate.rb +13 -13
  104. data/manual/graphics/transparency.rb +11 -13
  105. data/manual/{manual/how_to_read_this_manual.rb → how_to_read_this_manual.rb} +23 -25
  106. data/manual/images/absolute_position.rb +9 -10
  107. data/manual/images/fit.rb +9 -10
  108. data/manual/images/horizontal.rb +13 -14
  109. data/manual/images/images.rb +31 -30
  110. data/manual/images/plain_image.rb +6 -7
  111. data/manual/images/scale.rb +12 -13
  112. data/manual/images/vertical.rb +19 -17
  113. data/manual/images/width_and_height.rb +13 -14
  114. data/manual/layout/boxes.rb +14 -15
  115. data/manual/layout/content.rb +12 -13
  116. data/manual/layout/layout.rb +19 -20
  117. data/manual/layout/simple_grid.rb +8 -9
  118. data/manual/outline/add_subsection_to.rb +26 -27
  119. data/manual/outline/insert_section_after.rb +19 -20
  120. data/manual/outline/outline.rb +23 -22
  121. data/manual/outline/sections_and_pages.rb +24 -25
  122. data/manual/repeatable_content/alternate_page_numbering.rb +36 -0
  123. data/manual/repeatable_content/page_numbering.rb +20 -19
  124. data/manual/repeatable_content/repeatable_content.rb +26 -22
  125. data/manual/repeatable_content/repeater.rb +18 -19
  126. data/manual/repeatable_content/stamp.rb +18 -19
  127. data/manual/security/encryption.rb +8 -11
  128. data/manual/security/permissions.rb +20 -15
  129. data/manual/security/security.rb +20 -20
  130. data/manual/table.rb +16 -0
  131. data/manual/text/alignment.rb +17 -18
  132. data/manual/text/color.rb +13 -13
  133. data/manual/text/column_box.rb +10 -12
  134. data/manual/text/fallback_fonts.rb +29 -25
  135. data/manual/text/font.rb +17 -18
  136. data/manual/text/font_size.rb +21 -22
  137. data/manual/text/font_style.rb +12 -10
  138. data/manual/text/formatted_callbacks.rb +36 -26
  139. data/manual/text/formatted_text.rb +41 -34
  140. data/manual/text/free_flowing_text.rb +28 -29
  141. data/manual/text/inline.rb +23 -26
  142. data/manual/text/kerning_and_character_spacing.rb +20 -21
  143. data/manual/text/leading.rb +10 -11
  144. data/manual/text/line_wrapping.rb +40 -21
  145. data/manual/text/paragraph_indentation.rb +17 -12
  146. data/manual/text/positioned_text.rb +19 -20
  147. data/manual/text/registering_families.rb +33 -30
  148. data/manual/text/rendering_and_color.rb +11 -12
  149. data/manual/text/right_to_left_text.rb +31 -20
  150. data/manual/text/rotation.rb +36 -27
  151. data/manual/text/single_usage.rb +13 -14
  152. data/manual/text/text.rb +62 -62
  153. data/manual/text/text_box_excess.rb +22 -19
  154. data/manual/text/text_box_extensions.rb +21 -18
  155. data/manual/text/text_box_overflow.rb +28 -21
  156. data/manual/text/utf8.rb +16 -17
  157. data/manual/text/win_ansi_charset.rb +29 -26
  158. data/prawn.gemspec +45 -43
  159. data/spec/extensions/encoding_helpers.rb +4 -3
  160. data/spec/prawn/document/bounding_box_spec.rb +550 -0
  161. data/spec/prawn/document/column_box_spec.rb +75 -0
  162. data/spec/prawn/document/security_spec.rb +176 -0
  163. data/spec/prawn/document_annotations_spec.rb +76 -0
  164. data/spec/prawn/document_destinations_spec.rb +15 -0
  165. data/spec/prawn/document_grid_spec.rb +99 -0
  166. data/spec/prawn/document_reference_spec.rb +27 -0
  167. data/spec/prawn/document_span_spec.rb +44 -0
  168. data/spec/prawn/document_spec.rb +805 -0
  169. data/spec/prawn/font_metric_cache_spec.rb +54 -0
  170. data/spec/prawn/font_spec.rb +544 -0
  171. data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
  172. data/spec/prawn/graphics/transparency_spec.rb +81 -0
  173. data/spec/prawn/graphics_spec.rb +872 -0
  174. data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
  175. data/spec/prawn/image_handler_spec.rb +53 -0
  176. data/spec/prawn/images/jpg_spec.rb +20 -0
  177. data/spec/prawn/images/png_spec.rb +283 -0
  178. data/spec/prawn/images_spec.rb +229 -0
  179. data/spec/prawn/measurements_extensions_spec.rb +24 -0
  180. data/spec/prawn/outline_spec.rb +512 -0
  181. data/spec/prawn/repeater_spec.rb +166 -0
  182. data/spec/prawn/soft_mask_spec.rb +74 -0
  183. data/spec/prawn/stamp_spec.rb +173 -0
  184. data/spec/prawn/text/box_spec.rb +1110 -0
  185. data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
  186. data/spec/prawn/text/formatted/box_spec.rb +849 -0
  187. data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
  188. data/spec/prawn/text/formatted/line_wrap_spec.rb +495 -0
  189. data/spec/prawn/text/formatted/parser_spec.rb +697 -0
  190. data/spec/prawn/text_draw_text_spec.rb +150 -0
  191. data/spec/prawn/text_rendering_mode_spec.rb +48 -0
  192. data/spec/prawn/text_spacing_spec.rb +95 -0
  193. data/spec/prawn/text_spec.rb +603 -0
  194. data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
  195. data/spec/prawn/transformation_stack_spec.rb +66 -0
  196. data/spec/prawn/view_spec.rb +63 -0
  197. data/spec/prawn_manual_spec.rb +35 -0
  198. data/spec/spec_helper.rb +22 -21
  199. data.tar.gz.sig +0 -0
  200. metadata +168 -307
  201. metadata.gz.sig +0 -0
  202. data/README.md +0 -109
  203. data/data/encodings/win_ansi.txt +0 -29
  204. data/data/images/16bit.alpha +0 -0
  205. data/data/images/16bit.dat +0 -0
  206. data/data/images/16bit.png +0 -0
  207. data/data/images/arrow.png +0 -0
  208. data/data/images/arrow2.png +0 -0
  209. data/data/images/barcode_issue.png +0 -0
  210. data/data/images/dice.alpha +0 -0
  211. data/data/images/dice.dat +0 -0
  212. data/data/images/dice.png +0 -0
  213. data/data/images/dice_interlaced.png +0 -0
  214. data/data/images/indexed_color.dat +0 -0
  215. data/data/images/indexed_color.png +0 -0
  216. data/data/images/letterhead.jpg +0 -0
  217. data/data/images/page_white_text.alpha +0 -0
  218. data/data/images/page_white_text.dat +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/lib/pdf/core/annotations.rb +0 -60
  244. data/lib/pdf/core/byte_string.rb +0 -9
  245. data/lib/pdf/core/destinations.rb +0 -90
  246. data/lib/pdf/core/document_state.rb +0 -78
  247. data/lib/pdf/core/filter_list.rb +0 -51
  248. data/lib/pdf/core/filters.rb +0 -36
  249. data/lib/pdf/core/graphics_state.rb +0 -68
  250. data/lib/pdf/core/literal_string.rb +0 -16
  251. data/lib/pdf/core/name_tree.rb +0 -177
  252. data/lib/pdf/core/object_store.rb +0 -320
  253. data/lib/pdf/core/outline.rb +0 -315
  254. data/lib/pdf/core/page.rb +0 -212
  255. data/lib/pdf/core/page_geometry.rb +0 -126
  256. data/lib/pdf/core/pdf_object.rb +0 -124
  257. data/lib/pdf/core/reference.rb +0 -103
  258. data/lib/pdf/core/stream.rb +0 -98
  259. data/lib/pdf/core/text.rb +0 -275
  260. data/lib/pdf/core.rb +0 -35
  261. data/lib/prawn/compatibility.rb +0 -91
  262. data/lib/prawn/document/graphics_state.rb +0 -73
  263. data/lib/prawn/document/snapshot.rb +0 -89
  264. data/lib/prawn/font/afm.rb +0 -203
  265. data/lib/prawn/layout.rb +0 -20
  266. data/lib/prawn/table/cell/image.rb +0 -70
  267. data/lib/prawn/table/cell/in_table.rb +0 -27
  268. data/lib/prawn/table/cell/span_dummy.rb +0 -92
  269. data/lib/prawn/table/cell/subtable.rb +0 -65
  270. data/lib/prawn/table/cell/text.rb +0 -153
  271. data/lib/prawn/table/cell.rb +0 -770
  272. data/lib/prawn/table/cells.rb +0 -295
  273. data/lib/prawn/table.rb +0 -643
  274. data/manual/example_file.rb +0 -116
  275. data/manual/example_package.rb +0 -53
  276. data/manual/example_section.rb +0 -46
  277. data/manual/manual/cover.rb +0 -35
  278. data/manual/manual/foreword.rb +0 -85
  279. data/manual/manual/manual.rb +0 -35
  280. data/manual/syntax_highlight.rb +0 -52
  281. data/manual/table/basic_block.rb +0 -53
  282. data/manual/table/before_rendering_page.rb +0 -26
  283. data/manual/table/cell_border_lines.rb +0 -24
  284. data/manual/table/cell_borders_and_bg.rb +0 -31
  285. data/manual/table/cell_dimensions.rb +0 -30
  286. data/manual/table/cell_text.rb +0 -38
  287. data/manual/table/column_widths.rb +0 -30
  288. data/manual/table/content_and_subtables.rb +0 -39
  289. data/manual/table/creation.rb +0 -27
  290. data/manual/table/filtering.rb +0 -36
  291. data/manual/table/flow_and_header.rb +0 -17
  292. data/manual/table/image_cells.rb +0 -33
  293. data/manual/table/position.rb +0 -29
  294. data/manual/table/row_colors.rb +0 -20
  295. data/manual/table/span.rb +0 -30
  296. data/manual/table/style.rb +0 -22
  297. data/manual/table/table.rb +0 -52
  298. data/manual/table/width.rb +0 -27
  299. data/manual/templates/full_template.rb +0 -25
  300. data/manual/templates/page_template.rb +0 -48
  301. data/manual/templates/templates.rb +0 -27
  302. data/manual/text/group.rb +0 -29
  303. data/spec/acceptance/png.rb +0 -23
  304. data/spec/annotations_spec.rb +0 -74
  305. data/spec/bounding_box_spec.rb +0 -493
  306. data/spec/cell_spec.rb +0 -628
  307. data/spec/column_box_spec.rb +0 -33
  308. data/spec/destinations_spec.rb +0 -15
  309. data/spec/document_spec.rb +0 -761
  310. data/spec/extensions/mocha.rb +0 -44
  311. data/spec/filters_spec.rb +0 -34
  312. data/spec/font_metric_cache_spec.rb +0 -52
  313. data/spec/font_spec.rb +0 -464
  314. data/spec/formatted_text_arranger_spec.rb +0 -421
  315. data/spec/formatted_text_box_spec.rb +0 -650
  316. data/spec/formatted_text_fragment_spec.rb +0 -298
  317. data/spec/graphics_spec.rb +0 -651
  318. data/spec/grid_spec.rb +0 -85
  319. data/spec/image_handler_spec.rb +0 -42
  320. data/spec/images_spec.rb +0 -157
  321. data/spec/inline_formatted_text_parser_spec.rb +0 -564
  322. data/spec/jpg_spec.rb +0 -25
  323. data/spec/line_wrap_spec.rb +0 -333
  324. data/spec/measurement_units_spec.rb +0 -23
  325. data/spec/name_tree_spec.rb +0 -112
  326. data/spec/object_store_spec.rb +0 -170
  327. data/spec/outline_spec.rb +0 -448
  328. data/spec/pdf_object_spec.rb +0 -172
  329. data/spec/png_spec.rb +0 -240
  330. data/spec/reference_spec.rb +0 -82
  331. data/spec/repeater_spec.rb +0 -158
  332. data/spec/security_spec.rb +0 -158
  333. data/spec/snapshot_spec.rb +0 -186
  334. data/spec/soft_mask_spec.rb +0 -117
  335. data/spec/span_spec.rb +0 -44
  336. data/spec/stamp_spec.rb +0 -158
  337. data/spec/stream_spec.rb +0 -58
  338. data/spec/stroke_styles_spec.rb +0 -211
  339. data/spec/table/span_dummy_spec.rb +0 -17
  340. data/spec/table_spec.rb +0 -1355
  341. data/spec/template_spec.rb +0 -351
  342. data/spec/text_at_spec.rb +0 -130
  343. data/spec/text_box_spec.rb +0 -1030
  344. data/spec/text_rendering_mode_spec.rb +0 -45
  345. data/spec/text_spacing_spec.rb +0 -93
  346. data/spec/text_spec.rb +0 -425
  347. data/spec/text_with_inline_formatting_spec.rb +0 -35
  348. data/spec/transparency_spec.rb +0 -89
@@ -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