prawnx 0.1.1

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 (160) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +339 -0
  4. data/GPLv3 +674 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-prawn-svg +21 -0
  7. data/README.md +107 -0
  8. data/data/fonts/Courier-Bold.afm +342 -0
  9. data/data/fonts/Courier-BoldOblique.afm +342 -0
  10. data/data/fonts/Courier-Oblique.afm +342 -0
  11. data/data/fonts/Courier.afm +342 -0
  12. data/data/fonts/Helvetica-Bold.afm +2827 -0
  13. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  14. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  15. data/data/fonts/Helvetica.afm +3051 -0
  16. data/data/fonts/MustRead.html +19 -0
  17. data/data/fonts/Symbol.afm +213 -0
  18. data/data/fonts/Times-Bold.afm +2588 -0
  19. data/data/fonts/Times-BoldItalic.afm +2384 -0
  20. data/data/fonts/Times-Italic.afm +2667 -0
  21. data/data/fonts/Times-Roman.afm +2419 -0
  22. data/data/fonts/ZapfDingbats.afm +225 -0
  23. data/lib/pdf/core.rb +4 -0
  24. data/lib/prawn/measurement_extensions.rb +4 -0
  25. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  26. data/lib/prawn/svg/attributes/mask.rb +16 -0
  27. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  28. data/lib/prawn/svg/attributes/space.rb +10 -0
  29. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  30. data/lib/prawn/svg/attributes/transform.rb +10 -0
  31. data/lib/prawn/svg/attributes.rb +6 -0
  32. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  33. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  34. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  35. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  36. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  37. data/lib/prawn/svg/color.rb +240 -0
  38. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  39. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  40. data/lib/prawn/svg/css/font_parser.rb +46 -0
  41. data/lib/prawn/svg/css/selector_parser.rb +178 -0
  42. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  43. data/lib/prawn/svg/css/values_parser.rb +68 -0
  44. data/lib/prawn/svg/document.rb +170 -0
  45. data/lib/prawn/svg/elements/anchor.rb +10 -0
  46. data/lib/prawn/svg/elements/base.rb +312 -0
  47. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  48. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  49. data/lib/prawn/svg/elements/circle.rb +25 -0
  50. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  51. data/lib/prawn/svg/elements/container.rb +10 -0
  52. data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
  53. data/lib/prawn/svg/elements/ellipse.rb +22 -0
  54. data/lib/prawn/svg/elements/gradient.rb +184 -0
  55. data/lib/prawn/svg/elements/ignored.rb +5 -0
  56. data/lib/prawn/svg/elements/image.rb +177 -0
  57. data/lib/prawn/svg/elements/line.rb +27 -0
  58. data/lib/prawn/svg/elements/marker.rb +72 -0
  59. data/lib/prawn/svg/elements/mask.rb +65 -0
  60. data/lib/prawn/svg/elements/path.rb +294 -0
  61. data/lib/prawn/svg/elements/pattern.rb +150 -0
  62. data/lib/prawn/svg/elements/polygon.rb +25 -0
  63. data/lib/prawn/svg/elements/polyline.rb +23 -0
  64. data/lib/prawn/svg/elements/rect.rb +64 -0
  65. data/lib/prawn/svg/elements/root.rb +22 -0
  66. data/lib/prawn/svg/elements/switch.rb +92 -0
  67. data/lib/prawn/svg/elements/text.rb +99 -0
  68. data/lib/prawn/svg/elements/text_component.rb +346 -0
  69. data/lib/prawn/svg/elements/text_node.rb +308 -0
  70. data/lib/prawn/svg/elements/text_path.rb +153 -0
  71. data/lib/prawn/svg/elements/use.rb +142 -0
  72. data/lib/prawn/svg/elements/viewport.rb +29 -0
  73. data/lib/prawn/svg/elements.rb +46 -0
  74. data/lib/prawn/svg/extension.rb +25 -0
  75. data/lib/prawn/svg/font.rb +20 -0
  76. data/lib/prawn/svg/font_metrics.rb +144 -0
  77. data/lib/prawn/svg/font_registry.rb +315 -0
  78. data/lib/prawn/svg/funciri.rb +14 -0
  79. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  80. data/lib/prawn/svg/gradients.rb +53 -0
  81. data/lib/prawn/svg/interface.rb +72 -0
  82. data/lib/prawn/svg/length.rb +43 -0
  83. data/lib/prawn/svg/link_renderer.rb +40 -0
  84. data/lib/prawn/svg/loaders/data.rb +17 -0
  85. data/lib/prawn/svg/loaders/file.rb +111 -0
  86. data/lib/prawn/svg/loaders/web.rb +24 -0
  87. data/lib/prawn/svg/paint.rb +67 -0
  88. data/lib/prawn/svg/pathable.rb +153 -0
  89. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  90. data/lib/prawn/svg/pdfmatrix.rb +37 -0
  91. data/lib/prawn/svg/percentage.rb +24 -0
  92. data/lib/prawn/svg/properties.rb +329 -0
  93. data/lib/prawn/svg/renderer.rb +249 -0
  94. data/lib/prawn/svg/state.rb +24 -0
  95. data/lib/prawn/svg/transform_parser.rb +78 -0
  96. data/lib/prawn/svg/ttc.rb +35 -0
  97. data/lib/prawn/svg/ttf.rb +95 -0
  98. data/lib/prawn/svg/url_loader.rb +46 -0
  99. data/lib/prawn/svg/version.rb +5 -0
  100. data/lib/prawn/table.rb +4 -0
  101. data/lib/prawn-svg.rb +53 -0
  102. data/lib/prawn.rb +10 -0
  103. data/lib/prawnx/annotations.rb +21 -0
  104. data/lib/prawnx/document/bounding_box.rb +193 -0
  105. data/lib/prawnx/document/column_box.rb +91 -0
  106. data/lib/prawnx/document/internals.rb +144 -0
  107. data/lib/prawnx/document/span.rb +25 -0
  108. data/lib/prawnx/document.rb +253 -0
  109. data/lib/prawnx/encoding.rb +29 -0
  110. data/lib/prawnx/errors.rb +37 -0
  111. data/lib/prawnx/font.rb +214 -0
  112. data/lib/prawnx/fonts/afm.rb +171 -0
  113. data/lib/prawnx/fonts/otf.rb +50 -0
  114. data/lib/prawnx/fonts/ttf.rb +410 -0
  115. data/lib/prawnx/graphic_state.rb +41 -0
  116. data/lib/prawnx/graphics/color.rb +148 -0
  117. data/lib/prawnx/graphics/patterns.rb +100 -0
  118. data/lib/prawnx/graphics/style.rb +171 -0
  119. data/lib/prawnx/graphics.rb +208 -0
  120. data/lib/prawnx/grid.rb +216 -0
  121. data/lib/prawnx/images/image.rb +244 -0
  122. data/lib/prawnx/images/png_decoder.rb +65 -0
  123. data/lib/prawnx/images.rb +115 -0
  124. data/lib/prawnx/measurement_extensions.rb +40 -0
  125. data/lib/prawnx/measurements.rb +62 -0
  126. data/lib/prawnx/outline.rb +146 -0
  127. data/lib/prawnx/page.rb +125 -0
  128. data/lib/prawnx/page_geometry.rb +27 -0
  129. data/lib/prawnx/pdf/outline_node.rb +53 -0
  130. data/lib/prawnx/pdf/reference.rb +31 -0
  131. data/lib/prawnx/pdf/writer.rb +105 -0
  132. data/lib/prawnx/pdf.rb +66 -0
  133. data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
  134. data/lib/prawnx/pdf_a.rb +130 -0
  135. data/lib/prawnx/pdf_core.rb +25 -0
  136. data/lib/prawnx/repeater.rb +79 -0
  137. data/lib/prawnx/security.rb +108 -0
  138. data/lib/prawnx/stamp.rb +74 -0
  139. data/lib/prawnx/table/cell/kinds.rb +246 -0
  140. data/lib/prawnx/table/cell.rb +331 -0
  141. data/lib/prawnx/table/cells.rb +208 -0
  142. data/lib/prawnx/table.rb +296 -0
  143. data/lib/prawnx/text/box.rb +16 -0
  144. data/lib/prawnx/text/formatted/arranger.rb +217 -0
  145. data/lib/prawnx/text/formatted/box.rb +312 -0
  146. data/lib/prawnx/text/formatted/fragment.rb +205 -0
  147. data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
  148. data/lib/prawnx/text/formatted/parser.rb +149 -0
  149. data/lib/prawnx/text/formatted/wrap.rb +103 -0
  150. data/lib/prawnx/text.rb +281 -0
  151. data/lib/prawnx/transformation_stack.rb +43 -0
  152. data/lib/prawnx/true_type/cff_subset.rb +157 -0
  153. data/lib/prawnx/true_type/file.rb +371 -0
  154. data/lib/prawnx/true_type/open_type_layout.rb +189 -0
  155. data/lib/prawnx/true_type/simple_subsets.rb +94 -0
  156. data/lib/prawnx/true_type/subset.rb +187 -0
  157. data/lib/prawnx/version.rb +7 -0
  158. data/lib/prawnx/view.rb +28 -0
  159. data/lib/prawnx.rb +102 -0
  160. metadata +251 -0
@@ -0,0 +1,35 @@
1
+ class Prawn::SVG::TTC
2
+ TTC_TAG = 'ttcf'.freeze
3
+
4
+ attr_reader :fonts
5
+
6
+ def initialize(filename)
7
+ @fonts = []
8
+ load_data_from_file(filename)
9
+ end
10
+
11
+ private
12
+
13
+ def load_data_from_file(filename)
14
+ File.open(filename, 'rb') do |f|
15
+ tag = f.read(4)
16
+ next unless tag == TTC_TAG
17
+
18
+ _major, _minor = f.read(4).unpack('nn')
19
+ font_count = f.read(4).unpack1('N')
20
+ offsets = f.read(font_count * 4).unpack('N*')
21
+
22
+ offsets.each_with_index do |offset, index|
23
+ result = Prawn::SVG::TTF.read_name_table(f, offset)
24
+ next unless result
25
+
26
+ family, subfamily = result
27
+ next unless family
28
+
29
+ weight_class = Prawn::SVG::TTF.read_weight_class(f, offset)
30
+ @fonts << { family: family, subfamily: subfamily, index: index, weight_class: weight_class }
31
+ end
32
+ end
33
+ rescue Errno::ENOENT
34
+ end
35
+ end
@@ -0,0 +1,95 @@
1
+ class Prawn::SVG::TTF
2
+ SFNT_VERSION_STRINGS = ["\x00\x01\x00\x00", 'true', 'typ1'].freeze
3
+ LANGUAGE_IDS = [0, 0x409].freeze # English, US English
4
+ UTF_16BE_PLATFORM_IDS = [0, 3].freeze # Unicode, Microsoft
5
+
6
+ attr_reader :family, :subfamily, :weight_class
7
+
8
+ def initialize(filename)
9
+ load_data_from_file(filename)
10
+ end
11
+
12
+ def self.read_weight_class(io, font_offset)
13
+ io.seek(font_offset)
14
+ offset_table = io.read(12)
15
+ return unless offset_table&.length == 12 && SFNT_VERSION_STRINGS.include?(offset_table[0..3])
16
+
17
+ table_count = (offset_table[4].ord * 256) + offset_table[5].ord
18
+ tables = io.read(table_count * 16)
19
+ return unless tables&.length == table_count * 16
20
+
21
+ table_count.times do |index|
22
+ start = index * 16
23
+ next unless tables[start..start + 3] == 'OS/2'
24
+
25
+ offset, = tables[start + 8..start + 15].unpack('NN')
26
+ io.seek(offset)
27
+ data = io.read(6)
28
+ return data[4..5].unpack1('n') if data&.length == 6
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ def self.read_name_table(io, font_offset)
35
+ io.seek(font_offset)
36
+ offset_table = io.read(12)
37
+ return unless offset_table && offset_table.length == 12 && SFNT_VERSION_STRINGS.include?(offset_table[0..3])
38
+
39
+ table_count = (offset_table[4].ord * 256) + offset_table[5].ord
40
+ tables = io.read(table_count * 16)
41
+ return unless tables && tables.length == table_count * 16
42
+
43
+ offset, length = table_count.times do |index|
44
+ start = index * 16
45
+ break tables[start + 8..start + 15].unpack('NNN') if tables[start..start + 3] == 'name'
46
+ end
47
+
48
+ return unless length
49
+
50
+ io.seek(offset)
51
+ data = io.read(length)
52
+ return unless data && data.length == length
53
+
54
+ _, name_count, string_offset = data[0..5].unpack('nnn')
55
+
56
+ names = {}
57
+ name_count.times do |index|
58
+ start = 6 + (index * 12)
59
+ platform_id, _, language_id, name_id, length, offset = data[start..start + 11].unpack('nnnnnn')
60
+ next unless offset
61
+ next unless LANGUAGE_IDS.include?(language_id)
62
+ next unless [1, 2, 16, 17].include?(name_id)
63
+
64
+ offset += string_offset
65
+ field = data[offset..offset + length - 1]
66
+ next unless field && field.length == length
67
+
68
+ names[name_id] =
69
+ if UTF_16BE_PLATFORM_IDS.include?(platform_id)
70
+ begin
71
+ field.force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8)
72
+ rescue StandardError
73
+ field
74
+ end
75
+ else
76
+ field
77
+ end
78
+ end
79
+
80
+ family = names[16] || names[1]
81
+ subfamily = names[17] || names[2]
82
+ [family, subfamily]
83
+ end
84
+
85
+ private
86
+
87
+ def load_data_from_file(filename)
88
+ File.open(filename, 'rb') do |f|
89
+ result = self.class.read_name_table(f, 0)
90
+ @family, @subfamily = result if result
91
+ @weight_class = self.class.read_weight_class(f, 0)
92
+ end
93
+ rescue Errno::ENOENT # in case the file disappears between the scan and the load, we don't want to crash
94
+ end
95
+ end
@@ -0,0 +1,46 @@
1
+ class Prawn::SVG::UrlLoader
2
+ Error = Class.new(StandardError)
3
+
4
+ attr_reader :enable_cache, :loaders
5
+
6
+ def initialize(enable_cache: false, enable_web: true, enable_file_with_root: nil)
7
+ @url_cache = {}
8
+ @enable_cache = enable_cache
9
+
10
+ @loaders = []
11
+ loaders << Prawn::SVG::Loaders::Data.new
12
+ loaders << Prawn::SVG::Loaders::Web.new if enable_web
13
+ loaders << Prawn::SVG::Loaders::File.new(enable_file_with_root) if enable_file_with_root
14
+ end
15
+
16
+ def load(url)
17
+ retrieve_from_cache(url) || perform_and_cache(url)
18
+ end
19
+
20
+ def add_to_cache(url, data)
21
+ @url_cache[url] = data
22
+ end
23
+
24
+ def retrieve_from_cache(url)
25
+ @url_cache[url]
26
+ end
27
+
28
+ private
29
+
30
+ def perform_and_cache(url)
31
+ data = perform(url)
32
+ add_to_cache(url, data) if enable_cache
33
+ data
34
+ end
35
+
36
+ def perform(url)
37
+ try_each_loader(url) or raise Error, 'No handler available for this URL scheme'
38
+ end
39
+
40
+ def try_each_loader(url)
41
+ loaders.detect do |loader|
42
+ data = loader.from_url(url)
43
+ break data if data
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module Prawn
2
+ module SVG
3
+ VERSION = '0.40.4'.freeze
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tables are part of PrawnX: this file only keeps `require "prawn/table"` working.
4
+ require "prawn"
data/lib/prawn-svg.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'matrix'
2
+ require 'rexml/document'
3
+
4
+ require 'prawn'
5
+ require 'prawn/svg/version'
6
+
7
+ require 'css_parser'
8
+
9
+ require 'prawn/svg/font_registry'
10
+ require 'prawn/svg/font_metrics'
11
+ require 'prawn/svg/calculators/arc_to_bezier_curve'
12
+ require 'prawn/svg/calculators/aspect_ratio'
13
+ require 'prawn/svg/calculators/document_sizing'
14
+ require 'prawn/svg/calculators/path_length'
15
+ require 'prawn/svg/calculators/pixels'
16
+ require 'prawn/svg/pdfmatrix'
17
+ require 'prawn/svg/transform_parser'
18
+ require 'prawn/svg/url_loader'
19
+ require 'prawn/svg/loaders/data'
20
+ require 'prawn/svg/loaders/file'
21
+ require 'prawn/svg/loaders/web'
22
+ require 'prawn/svg/color'
23
+ require 'prawn/svg/funciri'
24
+ require 'prawn/svg/length'
25
+ require 'prawn/svg/paint'
26
+ require 'prawn/svg/percentage'
27
+ require 'prawn/svg/attributes'
28
+ require 'prawn/svg/properties'
29
+ require 'prawn/svg/pathable'
30
+ require 'prawn/svg/elements'
31
+ require 'prawn/svg/extension'
32
+ require 'prawn/svg/renderer'
33
+ require 'prawn/svg/interface'
34
+ require 'prawn/svg/css/font_face_parser'
35
+ require 'prawn/svg/css/font_parser'
36
+ require 'prawn/svg/css/font_family_parser'
37
+ require 'prawn/svg/css/selector_parser'
38
+ require 'prawn/svg/css/stylesheets'
39
+ require 'prawn/svg/css/values_parser'
40
+ require 'prawn/svg/ttf'
41
+ require 'prawn/svg/ttc'
42
+ require 'prawn/svg/font'
43
+ require 'prawn/svg/gradients'
44
+ require 'prawn/svg/gradient_renderer'
45
+ require 'prawn/svg/pattern_renderer'
46
+ require 'prawn/svg/link_renderer'
47
+ require 'prawn/svg/document'
48
+ require 'prawn/svg/state'
49
+
50
+ module Prawn
51
+ Svg = SVG # backwards compatibility
52
+ end
53
+
data/lib/prawn.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Gem.loaded_specs.key?("prawn")
4
+ raise LoadError, "PrawnX replaces the prawn gem, which is in your bundle too: remove prawn (and prawn-table, " \
5
+ "prawn-svg) from your Gemfile"
6
+ end
7
+
8
+ require "prawnx"
9
+
10
+ Prawn = PrawnX
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ module Annotations
5
+ def annotate(options)
6
+ options = options.merge(Type: :Annot)
7
+ options[:F] ||= PdfA::PRINT_FLAG if pdf_a?
8
+ options[:Dest] = PDF::LiteralString.new(options[:Dest]) if options[:Dest].is_a?(String)
9
+ (page.dictionary.data[:Annots] ||= []) << ref!(options)
10
+ options
11
+ end
12
+
13
+ def text_annotation(rect, contents, options = {})
14
+ annotate(options.merge(Subtype: :Text, Rect: rect, Contents: contents))
15
+ end
16
+
17
+ def link_annotation(rect, options = {})
18
+ annotate(options.merge(Subtype: :Link, Rect: rect))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ class Document
5
+ def bounding_box(point, *arguments, &block)
6
+ init_bounding_box(block) do |parent_box|
7
+ @bounding_box = BoundingBox.new(self, parent_box, map_to_absolute(point), *arguments)
8
+ end
9
+ end
10
+
11
+ def canvas(&block)
12
+ init_bounding_box(block, hold_position: true) do
13
+ _left, _bottom, page_width, page_height = page.dimensions
14
+ @bounding_box = BoundingBox.new(self, nil, [0, page_height], width: page_width, height: page_height)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def init_bounding_box(user_block, options = {})
21
+ raise ArgumentError, "bounding boxes require a block to be drawn within the box" unless user_block
22
+
23
+ parent_box = @bounding_box
24
+ original_y = y
25
+ yield parent_box
26
+ self.y = @bounding_box.absolute_top
27
+ user_block.call
28
+ self.y = original_y if y == @bounding_box.absolute_top
29
+ self.y = @bounding_box.absolute_bottom unless options[:hold_position] || @bounding_box.stretchy?
30
+ created_box = @bounding_box
31
+ @bounding_box = parent_box
32
+ created_box
33
+ end
34
+
35
+ # A rectangle the document draws into, positioned from its top left corner; without a height, it stretches
36
+ # down to wherever the cursor ends.
37
+ class BoundingBox
38
+ class NoReferenceBounds < StandardError
39
+ def initialize(message = "Can't find reference bounds: my parent is unset")
40
+ super
41
+ end
42
+ end
43
+
44
+ attr_reader :document, :parent, :total_left_padding, :total_right_padding, :width
45
+
46
+ def initialize(document, parent, point, options = {})
47
+ raise ArgumentError, "BoundingBox needs the :width option to be set" unless options[:width]
48
+
49
+ @document = document
50
+ @parent = parent
51
+ @x, @y = point
52
+ @width = options[:width]
53
+ @height = options[:height]
54
+ @total_left_padding = 0
55
+ @total_right_padding = 0
56
+ @stretched_height = nil
57
+ end
58
+
59
+ def anchor
60
+ [@x, @y - height]
61
+ end
62
+
63
+ def indent(left_padding, right_padding = 0)
64
+ add_left_padding(left_padding)
65
+ add_right_padding(right_padding)
66
+ yield
67
+ ensure
68
+ @document.bounds.subtract_left_padding(left_padding)
69
+ @document.bounds.subtract_right_padding(right_padding)
70
+ end
71
+
72
+ def add_left_padding(left_padding)
73
+ @total_left_padding += left_padding
74
+ @x += left_padding
75
+ @width -= left_padding
76
+ end
77
+
78
+ def subtract_left_padding(left_padding)
79
+ @total_left_padding -= left_padding
80
+ @x -= left_padding
81
+ @width += left_padding
82
+ end
83
+
84
+ def add_right_padding(right_padding)
85
+ @total_right_padding += right_padding
86
+ @width -= right_padding
87
+ end
88
+
89
+ def subtract_right_padding(right_padding)
90
+ @total_right_padding -= right_padding
91
+ @width += right_padding
92
+ end
93
+
94
+ def left
95
+ 0
96
+ end
97
+
98
+ def right
99
+ @width
100
+ end
101
+
102
+ def top
103
+ height
104
+ end
105
+
106
+ def bottom
107
+ 0
108
+ end
109
+
110
+ def top_left
111
+ [left, top]
112
+ end
113
+
114
+ def top_right
115
+ [right, top]
116
+ end
117
+
118
+ def bottom_right
119
+ [right, bottom]
120
+ end
121
+
122
+ def bottom_left
123
+ [left, bottom]
124
+ end
125
+
126
+ def absolute_left
127
+ @x
128
+ end
129
+
130
+ def absolute_right
131
+ @x + width
132
+ end
133
+
134
+ def absolute_top
135
+ @y
136
+ end
137
+
138
+ def absolute_bottom
139
+ @y - height
140
+ end
141
+
142
+ def absolute_top_left
143
+ [absolute_left, absolute_top]
144
+ end
145
+
146
+ def absolute_top_right
147
+ [absolute_right, absolute_top]
148
+ end
149
+
150
+ def absolute_bottom_left
151
+ [absolute_left, absolute_bottom]
152
+ end
153
+
154
+ def absolute_bottom_right
155
+ [absolute_right, absolute_bottom]
156
+ end
157
+
158
+ def height
159
+ return @height if @height
160
+
161
+ @stretched_height = [absolute_top - @document.y, Float(@stretched_height || 0.0)].max
162
+ end
163
+ alias update_height height
164
+
165
+ def left_side
166
+ absolute_left
167
+ end
168
+
169
+ def right_side
170
+ absolute_right
171
+ end
172
+
173
+ def move_past_bottom
174
+ if @document.page_number == @document.page_count
175
+ @document.start_new_page
176
+ else
177
+ @document.go_to_page(@document.page_number + 1)
178
+ end
179
+ end
180
+
181
+ def stretchy?
182
+ !@height
183
+ end
184
+
185
+ def reference_bounds
186
+ return self unless stretchy?
187
+ raise NoReferenceBounds unless @parent
188
+
189
+ @parent.reference_bounds
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ class Document
5
+ def column_box(*arguments, &block)
6
+ init_column_box(block) do |parent_box|
7
+ map_to_absolute!(arguments[0])
8
+ @bounding_box = ColumnBox.new(self, parent_box, *arguments)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def init_column_box(user_block, options = {})
15
+ parent_box = @bounding_box
16
+ yield parent_box
17
+ self.y = @bounding_box.absolute_top
18
+ user_block.call
19
+ self.y = @bounding_box.absolute_bottom unless options[:hold_position]
20
+ @bounding_box = parent_box
21
+ end
22
+
23
+ # A bounding box whose text flows down one column, then the next, then onto a new page.
24
+ class ColumnBox < BoundingBox
25
+ DEFAULT_COLUMN_COUNT = 3
26
+
27
+ def initialize(document, parent, point, options = {})
28
+ super
29
+ @columns = options[:columns] || DEFAULT_COLUMN_COUNT
30
+ @spacer = options[:spacer] || @document.font_size
31
+ @current_column = 0
32
+ @reflow_margins = options[:reflow_margins]
33
+ end
34
+
35
+ def bare_column_width
36
+ (@width - (@spacer * (@columns - 1))) / @columns
37
+ end
38
+
39
+ def width
40
+ bare_column_width - (@total_left_padding + @total_right_padding)
41
+ end
42
+
43
+ def width_of_column
44
+ bare_column_width + @spacer
45
+ end
46
+
47
+ def left_side
48
+ absolute_left + (width_of_column * @current_column)
49
+ end
50
+
51
+ def left
52
+ width_of_column * @current_column
53
+ end
54
+
55
+ def right_side
56
+ absolute_right - (width_of_column * (@columns - (1 + @current_column)))
57
+ end
58
+
59
+ def right
60
+ left + width
61
+ end
62
+
63
+ def move_past_bottom
64
+ @current_column = (@current_column + 1) % @columns
65
+ @document.y = @y
66
+ return unless @current_column.zero?
67
+
68
+ @y = @parent.absolute_top if @reflow_margins
69
+ @document.start_new_page
70
+ end
71
+
72
+ def add_left_padding(left_padding)
73
+ @total_left_padding += left_padding
74
+ @x += left_padding
75
+ end
76
+
77
+ def subtract_left_padding(left_padding)
78
+ @total_left_padding -= left_padding
79
+ @x -= left_padding
80
+ end
81
+
82
+ def add_right_padding(right_padding)
83
+ @total_right_padding += right_padding
84
+ end
85
+
86
+ def subtract_right_padding(right_padding)
87
+ @total_right_padding -= right_padding
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ class Document
5
+ # The document's objects, pages and graphic state stack: what Prawn splits between PDF::Core's renderer and its
6
+ # document state.
7
+ module Internals
8
+ attr_reader :page, :pages, :pages_reference
9
+
10
+ def ref(data)
11
+ ref!(data).identifier
12
+ end
13
+
14
+ def ref!(data, stream = nil)
15
+ reference = PDF::Reference.new(@objects.size + 1, data, stream)
16
+ @objects << reference
17
+ reference
18
+ end
19
+
20
+ def deref(object)
21
+ object.is_a?(PDF::Reference) ? object.data : object
22
+ end
23
+
24
+ def add_content(string)
25
+ open_graphics_state if page.stack.empty?
26
+ page.content_stream << string << "\n"
27
+ end
28
+
29
+ def names
30
+ @root.data[:Names] ||= ref!(Type: :Names)
31
+ end
32
+
33
+ def outlines_reference
34
+ @root.data[:Outlines] ||= ref!(PDF::OutlineRoot.new)
35
+ end
36
+
37
+ def names?
38
+ @root.data.key?(:Names)
39
+ end
40
+
41
+ def before_render(&block)
42
+ @before_render_callbacks << block
43
+ end
44
+
45
+ def on_page_create(&block)
46
+ @on_page_create = block
47
+ end
48
+
49
+ def compression_enabled?
50
+ @compress
51
+ end
52
+
53
+ def min_version(version)
54
+ @version = version if version > @version
55
+ end
56
+
57
+ # Like Prawn, reading the state of a page whose stack was emptied opens a new one.
58
+ def graphic_state
59
+ open_graphics_state if page.stack.empty?
60
+ page.stack.last
61
+ end
62
+
63
+ def save_graphics_state(graphic_state = nil)
64
+ save_transformation_stack
65
+ page.stack.push(GraphicState.new(graphic_state || self.graphic_state))
66
+ add_content("q")
67
+ return unless block_given?
68
+
69
+ yield
70
+ restore_graphics_state
71
+ end
72
+
73
+ def restore_graphics_state
74
+ restore_transformation_stack
75
+ raise Errors::EmptyGraphicStateStack, "\n You have reached the end of the graphic state stack" if page.stack.empty?
76
+
77
+ add_content("Q")
78
+ page.stack.pop
79
+ end
80
+
81
+ def trailer
82
+ @trailer
83
+ end
84
+
85
+ # What Prawn splits into a renderer and a state is this document itself: code written for Prawn calls both.
86
+ def renderer
87
+ self
88
+ end
89
+
90
+ def state
91
+ self
92
+ end
93
+
94
+ def info
95
+ @info.data
96
+ end
97
+
98
+ private
99
+
100
+ def open_graphics_state
101
+ page.stack.push(GraphicState.new)
102
+ page.content_stream << "q\n"
103
+ end
104
+
105
+ def initialize_internals(options)
106
+ @objects = []
107
+ @info = ref!({Creator: "PrawnX", Producer: "PrawnX"}.merge(options[:info] || {}))
108
+ @root = ref!(Type: :Catalog)
109
+ @pages_reference = ref!(Type: :Pages, Count: 0, Kids: [])
110
+ @root.data[:Pages] = @pages_reference
111
+ @root.data[:ViewerPreferences] = {PrintScaling: :None} if options[:print_scaling] == :none
112
+ @version = 1.3
113
+ min_version(1.6) if options[:print_scaling] == :none
114
+ @pages = []
115
+ @page = nil
116
+ @trailer = options.fetch(:trailer, {})
117
+ @compress = options.fetch(:compress, false)
118
+ @before_render_callbacks = []
119
+ @on_page_create = nil
120
+ end
121
+
122
+ def insert_page(page, page_number)
123
+ pages.insert(page_number, page)
124
+ pages_reference.data[:Kids].insert(page_number, page.dictionary)
125
+ pages_reference.data[:Count] += 1
126
+ end
127
+
128
+ def render_document
129
+ pages.each_with_index do |page, index|
130
+ go_to_page(index + 1)
131
+ until page.stack.empty?
132
+ page.content_stream << "Q\n"
133
+ page.stack.pop
134
+ end
135
+ page.finalize
136
+ end
137
+ @before_render_callbacks.each { it.call(self) }
138
+ finalize_pdf_a if pdf_a?
139
+ PDF::Writer.call(objects: @objects, root: @root, info: @info, version: @version, trailer: @trailer, encryption: @encryption,
140
+ identified: pdf_a?)
141
+ end
142
+ end
143
+ end
144
+ end