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.
- checksums.yaml +7 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +107 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +178 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +170 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +312 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
- data/lib/prawn/svg/elements/ellipse.rb +22 -0
- data/lib/prawn/svg/elements/gradient.rb +184 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +177 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +346 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +37 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +53 -0
- data/lib/prawn.rb +10 -0
- data/lib/prawnx/annotations.rb +21 -0
- data/lib/prawnx/document/bounding_box.rb +193 -0
- data/lib/prawnx/document/column_box.rb +91 -0
- data/lib/prawnx/document/internals.rb +144 -0
- data/lib/prawnx/document/span.rb +25 -0
- data/lib/prawnx/document.rb +253 -0
- data/lib/prawnx/encoding.rb +29 -0
- data/lib/prawnx/errors.rb +37 -0
- data/lib/prawnx/font.rb +214 -0
- data/lib/prawnx/fonts/afm.rb +171 -0
- data/lib/prawnx/fonts/otf.rb +50 -0
- data/lib/prawnx/fonts/ttf.rb +410 -0
- data/lib/prawnx/graphic_state.rb +41 -0
- data/lib/prawnx/graphics/color.rb +148 -0
- data/lib/prawnx/graphics/patterns.rb +100 -0
- data/lib/prawnx/graphics/style.rb +171 -0
- data/lib/prawnx/graphics.rb +208 -0
- data/lib/prawnx/grid.rb +216 -0
- data/lib/prawnx/images/image.rb +244 -0
- data/lib/prawnx/images/png_decoder.rb +65 -0
- data/lib/prawnx/images.rb +115 -0
- data/lib/prawnx/measurement_extensions.rb +40 -0
- data/lib/prawnx/measurements.rb +62 -0
- data/lib/prawnx/outline.rb +146 -0
- data/lib/prawnx/page.rb +125 -0
- data/lib/prawnx/page_geometry.rb +27 -0
- data/lib/prawnx/pdf/outline_node.rb +53 -0
- data/lib/prawnx/pdf/reference.rb +31 -0
- data/lib/prawnx/pdf/writer.rb +105 -0
- data/lib/prawnx/pdf.rb +66 -0
- data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
- data/lib/prawnx/pdf_a.rb +130 -0
- data/lib/prawnx/pdf_core.rb +25 -0
- data/lib/prawnx/repeater.rb +79 -0
- data/lib/prawnx/security.rb +108 -0
- data/lib/prawnx/stamp.rb +74 -0
- data/lib/prawnx/table/cell/kinds.rb +246 -0
- data/lib/prawnx/table/cell.rb +331 -0
- data/lib/prawnx/table/cells.rb +208 -0
- data/lib/prawnx/table.rb +296 -0
- data/lib/prawnx/text/box.rb +16 -0
- data/lib/prawnx/text/formatted/arranger.rb +217 -0
- data/lib/prawnx/text/formatted/box.rb +312 -0
- data/lib/prawnx/text/formatted/fragment.rb +205 -0
- data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
- data/lib/prawnx/text/formatted/parser.rb +149 -0
- data/lib/prawnx/text/formatted/wrap.rb +103 -0
- data/lib/prawnx/text.rb +281 -0
- data/lib/prawnx/transformation_stack.rb +43 -0
- data/lib/prawnx/true_type/cff_subset.rb +157 -0
- data/lib/prawnx/true_type/file.rb +371 -0
- data/lib/prawnx/true_type/open_type_layout.rb +189 -0
- data/lib/prawnx/true_type/simple_subsets.rb +94 -0
- data/lib/prawnx/true_type/subset.rb +187 -0
- data/lib/prawnx/version.rb +7 -0
- data/lib/prawnx/view.rb +28 -0
- data/lib/prawnx.rb +102 -0
- 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
|
data/lib/prawn/table.rb
ADDED
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
|