gamba 2.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +80 -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 +148 -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/gamba/annotations.rb +21 -0
- data/lib/gamba/combine.rb +307 -0
- data/lib/gamba/document/bounding_box.rb +193 -0
- data/lib/gamba/document/column_box.rb +91 -0
- data/lib/gamba/document/internals.rb +264 -0
- data/lib/gamba/document/span.rb +25 -0
- data/lib/gamba/document.rb +268 -0
- data/lib/gamba/encoding.rb +13 -0
- data/lib/gamba/errors.rb +39 -0
- data/lib/gamba/font.rb +228 -0
- data/lib/gamba/fonts/afm.rb +171 -0
- data/lib/gamba/fonts/otf.rb +50 -0
- data/lib/gamba/fonts/ttf.rb +413 -0
- data/lib/gamba/graphic_state.rb +84 -0
- data/lib/gamba/graphics/color.rb +174 -0
- data/lib/gamba/graphics/patterns.rb +100 -0
- data/lib/gamba/graphics/style.rb +159 -0
- data/lib/gamba/graphics.rb +212 -0
- data/lib/gamba/grid.rb +216 -0
- data/lib/gamba/images/image.rb +271 -0
- data/lib/gamba/images/png_decoder.rb +65 -0
- data/lib/gamba/images.rb +115 -0
- data/lib/gamba/measurement_extensions.rb +40 -0
- data/lib/gamba/measurements.rb +62 -0
- data/lib/gamba/outline.rb +148 -0
- data/lib/gamba/page.rb +125 -0
- data/lib/gamba/page_geometry.rb +27 -0
- data/lib/gamba/pdf/decryption.rb +165 -0
- data/lib/gamba/pdf/outline_node.rb +53 -0
- data/lib/gamba/pdf/reader.rb +616 -0
- data/lib/gamba/pdf/reference.rb +31 -0
- data/lib/gamba/pdf/writer.rb +105 -0
- data/lib/gamba/pdf.rb +87 -0
- data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
- data/lib/gamba/pdf_a.rb +132 -0
- data/lib/gamba/repeater.rb +79 -0
- data/lib/gamba/security.rb +111 -0
- data/lib/gamba/stamp.rb +76 -0
- data/lib/gamba/table/cell/kinds.rb +246 -0
- data/lib/gamba/table/cell.rb +331 -0
- data/lib/gamba/table/cells.rb +208 -0
- data/lib/gamba/table.rb +298 -0
- data/lib/gamba/text/box.rb +16 -0
- data/lib/gamba/text/formatted/arranger.rb +217 -0
- data/lib/gamba/text/formatted/box.rb +312 -0
- data/lib/gamba/text/formatted/fragment.rb +205 -0
- data/lib/gamba/text/formatted/line_wrap.rb +205 -0
- data/lib/gamba/text/formatted/parser.rb +150 -0
- data/lib/gamba/text/formatted/wrap.rb +103 -0
- data/lib/gamba/text.rb +310 -0
- data/lib/gamba/transformation_stack.rb +43 -0
- data/lib/gamba/true_type/cff_subset.rb +157 -0
- data/lib/gamba/true_type/file.rb +372 -0
- data/lib/gamba/true_type/open_type_layout.rb +189 -0
- data/lib/gamba/true_type/simple_subsets.rb +108 -0
- data/lib/gamba/true_type/subset.rb +206 -0
- data/lib/gamba/version.rb +10 -0
- data/lib/gamba/view.rb +28 -0
- data/lib/gamba.rb +124 -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 +180 -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 +169 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +315 -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 +19 -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 +28 -0
- data/lib/prawn/svg/elements/ellipse.rb +20 -0
- data/lib/prawn/svg/elements/gradient.rb +183 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +178 -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 +352 -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 +39 -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 +52 -0
- data/lib/prawn.rb +3 -0
- metadata +263 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Images
|
|
5
|
+
# Undoes PNG's per-row filters (PNG specification §9), giving back raw samples: needed to split an alpha
|
|
6
|
+
# channel or a palette's transparency into a separate soft mask.
|
|
7
|
+
module PngDecoder
|
|
8
|
+
NONE = 0
|
|
9
|
+
SUB = 1
|
|
10
|
+
UP = 2
|
|
11
|
+
AVERAGE = 3
|
|
12
|
+
PAETH = 4
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Rows of unfiltered bytes, from the inflated data of a non-interlaced image.
|
|
17
|
+
def unfilter(data, row_bytes, bytes_per_pixel)
|
|
18
|
+
previous = ("\x00" * row_bytes).b
|
|
19
|
+
row_count = data.bytesize / (row_bytes + 1)
|
|
20
|
+
Array.new(row_count) do |row_index|
|
|
21
|
+
start = row_index * (row_bytes + 1)
|
|
22
|
+
filter = data.getbyte(start)
|
|
23
|
+
row = data.byteslice(start + 1, row_bytes).b
|
|
24
|
+
unfilter_row!(row, previous, filter, bytes_per_pixel)
|
|
25
|
+
previous = row
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def unfilter_row!(row, previous, filter, bytes_per_pixel)
|
|
30
|
+
case filter
|
|
31
|
+
when NONE then nil
|
|
32
|
+
when SUB
|
|
33
|
+
(bytes_per_pixel...row.bytesize).each { |index| row.setbyte(index, (row.getbyte(index) + row.getbyte(index - bytes_per_pixel)) & 0xFF) }
|
|
34
|
+
when UP
|
|
35
|
+
row.bytesize.times { |index| row.setbyte(index, (row.getbyte(index) + previous.getbyte(index)) & 0xFF) }
|
|
36
|
+
when AVERAGE
|
|
37
|
+
row.bytesize.times do |index|
|
|
38
|
+
left = (index >= bytes_per_pixel) ? row.getbyte(index - bytes_per_pixel) : 0
|
|
39
|
+
row.setbyte(index, (row.getbyte(index) + ((left + previous.getbyte(index)) >> 1)) & 0xFF)
|
|
40
|
+
end
|
|
41
|
+
when PAETH
|
|
42
|
+
row.bytesize.times do |index|
|
|
43
|
+
left = (index >= bytes_per_pixel) ? row.getbyte(index - bytes_per_pixel) : 0
|
|
44
|
+
up = previous.getbyte(index)
|
|
45
|
+
up_left = (index >= bytes_per_pixel) ? previous.getbyte(index - bytes_per_pixel) : 0
|
|
46
|
+
row.setbyte(index, (row.getbyte(index) + paeth(left, up, up_left)) & 0xFF)
|
|
47
|
+
end
|
|
48
|
+
else raise Errors::UnsupportedImageType, "PNG uses an unknown filter type #{filter}"
|
|
49
|
+
end
|
|
50
|
+
row
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def paeth(left, up, up_left)
|
|
54
|
+
estimate = left + up - up_left
|
|
55
|
+
distance_left = (estimate - left).abs
|
|
56
|
+
distance_up = (estimate - up).abs
|
|
57
|
+
distance_up_left = (estimate - up_left).abs
|
|
58
|
+
if distance_left <= distance_up && distance_left <= distance_up_left then left
|
|
59
|
+
elsif distance_up <= distance_up_left then up
|
|
60
|
+
else up_left
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/gamba/images.rb
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Prawn
|
|
7
|
+
def self.image_handler
|
|
8
|
+
@image_handler ||= ImageHandler.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# The image formats Gamba knows, tried in order on an image's first bytes.
|
|
12
|
+
class ImageHandler
|
|
13
|
+
def initialize
|
|
14
|
+
@handlers = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register(handler)
|
|
18
|
+
@handlers.delete(handler)
|
|
19
|
+
@handlers.push(handler)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def register!(handler)
|
|
23
|
+
@handlers.delete(handler)
|
|
24
|
+
@handlers.unshift(handler)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def unregister(handler)
|
|
28
|
+
@handlers.delete(handler)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find(image_blob)
|
|
32
|
+
@handlers.find { _1.can_render?(image_blob) } || raise(Errors::UnsupportedImageType, "image file is an unrecognised format")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module Images
|
|
37
|
+
def image(file, options = {})
|
|
38
|
+
Prawn.verify_options(%i[at position vposition height width scale fit], options)
|
|
39
|
+
pdf_object, info = build_image_object(file)
|
|
40
|
+
embed_image(pdf_object, info, options)
|
|
41
|
+
info
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The same image bytes are embedded once per document, however many times they are placed.
|
|
45
|
+
def build_image_object(file)
|
|
46
|
+
image_content = verify_and_read_image(file)
|
|
47
|
+
entry = image_registry[Digest::SHA1.hexdigest(image_content)] ||= begin
|
|
48
|
+
info = Prawn.image_handler.find(image_content).new(image_content)
|
|
49
|
+
min_version(info.min_pdf_version) if info.respond_to?(:min_pdf_version)
|
|
50
|
+
{obj: info.build_pdf_object(self), info:}
|
|
51
|
+
end
|
|
52
|
+
[entry[:obj], entry[:info]]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def embed_image(pdf_object, info, options)
|
|
56
|
+
width, height = info.calc_image_dimensions(options)
|
|
57
|
+
if options[:at]
|
|
58
|
+
x, y = map_to_absolute(options[:at])
|
|
59
|
+
else
|
|
60
|
+
x, y = image_position(width, height, options)
|
|
61
|
+
move_text_position(height)
|
|
62
|
+
end
|
|
63
|
+
label = "I#{@image_counter = (@image_counter || 0) + 1}"
|
|
64
|
+
page.xobjects[label] = pdf_object
|
|
65
|
+
add_content("\nq\n#{PDF::Core.real_params([width, 0, 0, height, x, y - height])} cm\n/#{label} Do\nQ")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def verify_and_read_image(io_or_path)
|
|
71
|
+
if io_or_path.respond_to?(:rewind)
|
|
72
|
+
io_or_path.rewind
|
|
73
|
+
io_or_path.binmode if io_or_path.respond_to?(:binmode)
|
|
74
|
+
return io_or_path.read
|
|
75
|
+
end
|
|
76
|
+
path = Pathname.new(io_or_path)
|
|
77
|
+
raise ArgumentError, "#{path} not found" unless path.file?
|
|
78
|
+
|
|
79
|
+
path.binread
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def image_position(width, height, options)
|
|
83
|
+
options[:position] ||= :left
|
|
84
|
+
y =
|
|
85
|
+
case options[:vposition]
|
|
86
|
+
when :top then bounds.absolute_top
|
|
87
|
+
when :center then bounds.absolute_top - ((bounds.height - height) / 2.0)
|
|
88
|
+
when :bottom then bounds.absolute_bottom + height
|
|
89
|
+
when Numeric then bounds.absolute_top - options[:vposition]
|
|
90
|
+
else determine_y_with_page_flow(height)
|
|
91
|
+
end
|
|
92
|
+
x =
|
|
93
|
+
case options[:position]
|
|
94
|
+
when :left then bounds.left_side
|
|
95
|
+
when :center then bounds.left_side + ((bounds.width - width) / 2.0)
|
|
96
|
+
when :right then bounds.right_side - width
|
|
97
|
+
when Numeric then options[:position] + bounds.left_side
|
|
98
|
+
end
|
|
99
|
+
[x, y]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def determine_y_with_page_flow(height)
|
|
103
|
+
bounds.move_past_bottom if overruns_page?(height)
|
|
104
|
+
y
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def overruns_page?(height)
|
|
108
|
+
(y - height) < reference_bounds.absolute_bottom
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def image_registry
|
|
112
|
+
@image_registry ||= {}
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "measurements"
|
|
4
|
+
|
|
5
|
+
# 10.mm, 2.cm, 1.in…: lengths in PDF points.
|
|
6
|
+
class Numeric
|
|
7
|
+
include Prawn::Measurements
|
|
8
|
+
|
|
9
|
+
def mm
|
|
10
|
+
mm2pt(self)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def cm
|
|
14
|
+
cm2pt(self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dm
|
|
18
|
+
dm2pt(self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def m
|
|
22
|
+
m2pt(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def in
|
|
26
|
+
in2pt(self)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def yd
|
|
30
|
+
yd2pt(self)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ft
|
|
34
|
+
ft2pt(self)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def pt
|
|
38
|
+
pt2pt(self)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
# Conversions between metric and imperial units and PDF points (1/72 inch).
|
|
5
|
+
module Measurements
|
|
6
|
+
def cm2mm(cm)
|
|
7
|
+
cm * 10
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def dm2mm(dm)
|
|
11
|
+
dm * 100
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def m2mm(m)
|
|
15
|
+
m * 1000
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ft2in(ft)
|
|
19
|
+
ft * 12
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def yd2in(yd)
|
|
23
|
+
yd * 36
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def pt2pt(pt)
|
|
27
|
+
pt
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def in2pt(inch)
|
|
31
|
+
inch * 72
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ft2pt(ft)
|
|
35
|
+
in2pt(ft2in(ft))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def yd2pt(yd)
|
|
39
|
+
in2pt(yd2in(yd))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def mm2pt(mm)
|
|
43
|
+
mm * (72 / 25.4)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cm2pt(cm)
|
|
47
|
+
mm2pt(cm2mm(cm))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def dm2pt(dm)
|
|
51
|
+
mm2pt(dm2mm(dm))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def m2pt(m)
|
|
55
|
+
mm2pt(m2mm(m))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def pt2mm(pt)
|
|
59
|
+
pt * 1 / mm2pt(1)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
class Document
|
|
5
|
+
def outline
|
|
6
|
+
@outline ||= Outline.new(self)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# The document outline (bookmarks): sections holding pages and subsections, each pointing at a page.
|
|
11
|
+
class Outline
|
|
12
|
+
attr_accessor :parent, :prev, :document, :items
|
|
13
|
+
|
|
14
|
+
def initialize(document)
|
|
15
|
+
@document = document
|
|
16
|
+
@parent = root
|
|
17
|
+
@prev = nil
|
|
18
|
+
@items = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def page_number
|
|
22
|
+
@document.page_number
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def define(&block)
|
|
26
|
+
instance_eval(&block) if block
|
|
27
|
+
end
|
|
28
|
+
alias_method :update, :define
|
|
29
|
+
|
|
30
|
+
def add_subsection_to(title, position = :last, &)
|
|
31
|
+
@parent = items[title] || raise(Errors::UnknownOutlineTitle, "\n No outline item with title: '#{title}' exists in the outline tree")
|
|
32
|
+
@prev = (position == :first) ? nil : @parent.data.last
|
|
33
|
+
insert_section((position == :first) ? @parent.data.first : nil, &)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def insert_section_after(title, &)
|
|
37
|
+
@prev = items[title] || raise(Errors::UnknownOutlineTitle, "\n No outline item with title: '#{title}' exists in the outline tree")
|
|
38
|
+
@parent = @prev.data.parent
|
|
39
|
+
insert_section(@prev.data.next, &)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def section(title, options = {}, &)
|
|
43
|
+
add_outline_item(title, options, &)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def page(options = {})
|
|
47
|
+
raise Errors::RequiredOption, "\nTitle is a required option for page" unless options[:title]
|
|
48
|
+
|
|
49
|
+
add_outline_item(options[:title], options)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def root
|
|
55
|
+
document.outlines_reference
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_outline_item(title, options, &block)
|
|
59
|
+
outline_item = create_outline_item(title, options)
|
|
60
|
+
prev.data.next = outline_item if prev
|
|
61
|
+
parent.data.first = outline_item unless prev
|
|
62
|
+
parent.data.last = outline_item
|
|
63
|
+
increase_count
|
|
64
|
+
self.prev = block ? nil : outline_item
|
|
65
|
+
self.parent = outline_item if block
|
|
66
|
+
yield if block
|
|
67
|
+
return unless parent == outline_item
|
|
68
|
+
|
|
69
|
+
self.prev = outline_item
|
|
70
|
+
self.parent = outline_item.data.parent
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_outline_item(title, options)
|
|
74
|
+
outline_item = PDF::Core::OutlineItem.new(title, parent, options)
|
|
75
|
+
case options[:destination]
|
|
76
|
+
when Integer then outline_item.dest = [document.pages[options[:destination] - 1].dictionary, :Fit]
|
|
77
|
+
when Array then outline_item.dest = options[:destination]
|
|
78
|
+
end
|
|
79
|
+
outline_item.prev = prev if @prev
|
|
80
|
+
items[title] = document.ref!(outline_item)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def increase_count
|
|
84
|
+
counting_parent = parent
|
|
85
|
+
while counting_parent
|
|
86
|
+
counting_parent.data.count += 1
|
|
87
|
+
counting_parent = (counting_parent == root) ? nil : counting_parent.data.parent
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def insert_section(next_item)
|
|
92
|
+
last = @parent.data.last
|
|
93
|
+
yield if block_given?
|
|
94
|
+
if next_item
|
|
95
|
+
next_item.data.prev = @prev
|
|
96
|
+
@prev.data.next = next_item
|
|
97
|
+
@parent.data.last = last
|
|
98
|
+
end
|
|
99
|
+
@parent = root
|
|
100
|
+
@prev = root.data.last
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
module PDF::Core
|
|
106
|
+
module Destinations
|
|
107
|
+
def dests
|
|
108
|
+
names.data[:Dests] ||= ref!(PDF::Core::NameTree.new)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def add_dest(name, reference)
|
|
112
|
+
reference = ref!(reference) unless reference.is_a?(PDF::Core::Reference)
|
|
113
|
+
dests.data.add(name, reference)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def dest_xyz(left, top, zoom = nil, dest_page = page)
|
|
117
|
+
[dest_page.dictionary, :XYZ, left, top, zoom]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def dest_fit(dest_page = page)
|
|
121
|
+
[dest_page.dictionary, :Fit]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def dest_fit_horizontally(top, dest_page = page)
|
|
125
|
+
[dest_page.dictionary, :FitH, top]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def dest_fit_vertically(left, dest_page = page)
|
|
129
|
+
[dest_page.dictionary, :FitV, left]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def dest_fit_rect(left, bottom, right, top, dest_page = page)
|
|
133
|
+
[dest_page.dictionary, :FitR, left, bottom, right, top]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def dest_fit_bounds(dest_page = page)
|
|
137
|
+
[dest_page.dictionary, :FitB]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def dest_fit_bounds_horizontally(top, dest_page = page)
|
|
141
|
+
[dest_page.dictionary, :FitBH, top]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def dest_fit_bounds_vertically(left, dest_page = page)
|
|
145
|
+
[dest_page.dictionary, :FitBV, left]
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
data/lib/gamba/page.rb
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PDF::Core
|
|
4
|
+
# One page: its geometry, the content stream being written, its resources and the stack of graphic states.
|
|
5
|
+
class Page
|
|
6
|
+
DEFAULT_MARGINS = {left: 36, right: 36, top: 36, bottom: 36}.freeze
|
|
7
|
+
NO_INDENTS = {left: 0, bottom: 0, right: 0, top: 0}.freeze
|
|
8
|
+
|
|
9
|
+
attr_accessor :margins, :crops, :bleeds, :trims, :art_indents, :stack
|
|
10
|
+
attr_reader :document, :content
|
|
11
|
+
|
|
12
|
+
def initialize(document, options = {})
|
|
13
|
+
@document = document
|
|
14
|
+
@margins = options[:margins] || DEFAULT_MARGINS.dup
|
|
15
|
+
@crops = options[:crops] || NO_INDENTS
|
|
16
|
+
@bleeds = options[:bleeds] || NO_INDENTS
|
|
17
|
+
@trims = options[:trims] || NO_INDENTS
|
|
18
|
+
@art_indents = options[:art_indents] || NO_INDENTS
|
|
19
|
+
@stack = [GraphicState.new(options[:graphic_state])]
|
|
20
|
+
@size = options[:size] || "LETTER"
|
|
21
|
+
@layout = options[:layout] || :portrait
|
|
22
|
+
@content = document.ref!({}, (+"q\n").b)
|
|
23
|
+
@dictionary = document.ref!(Type: :Page, Parent: document.pages_reference, MediaBox: dimensions, Contents: @content)
|
|
24
|
+
@stamp_stream = nil
|
|
25
|
+
@stamp_dictionary = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def graphic_state
|
|
29
|
+
stack.last
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def size
|
|
33
|
+
@size || dimensions[2, 2]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def layout
|
|
37
|
+
@layout || ((dimensions[3] > dimensions[2]) ? :portrait : :landscape)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def content_stream
|
|
41
|
+
@stamp_stream || content.stream
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def dictionary
|
|
45
|
+
@stamp_dictionary || @dictionary
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Writes what the block draws into a form XObject instead of the page, starting from a copy of the current
|
|
49
|
+
# graphic settings.
|
|
50
|
+
def stamp_stream(reference)
|
|
51
|
+
@stamp_dictionary = reference
|
|
52
|
+
@stamp_stream = reference.stream
|
|
53
|
+
depth = stack.size
|
|
54
|
+
document.save_graphics_state
|
|
55
|
+
document.__send__(:freeze_stamp_graphics)
|
|
56
|
+
yield if block_given?
|
|
57
|
+
document.restore_graphics_state until stack.size == depth
|
|
58
|
+
@stamp_stream = nil
|
|
59
|
+
@stamp_dictionary = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def in_stamp_stream?
|
|
63
|
+
!@stamp_stream.nil?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resources
|
|
67
|
+
dictionary.data[:Resources] ||= {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fonts
|
|
71
|
+
resources[:Font] ||= {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def xobjects
|
|
75
|
+
resources[:XObject] ||= {}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ext_gstates
|
|
79
|
+
resources[:ExtGState] ||= {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def dimensions
|
|
83
|
+
sides = PageGeometry::SIZES[@size] || @size
|
|
84
|
+
sides =
|
|
85
|
+
case @layout
|
|
86
|
+
when :portrait then sides
|
|
87
|
+
when :landscape then sides.reverse
|
|
88
|
+
else raise Prawn::Errors::InvalidPageLayout, "Layout must be either :portrait or :landscape"
|
|
89
|
+
end
|
|
90
|
+
[0, 0, *sides]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def crop_box
|
|
94
|
+
boxed(crops)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bleed_box
|
|
98
|
+
boxed(bleeds)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def trim_box
|
|
102
|
+
boxed(trims)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def art_box
|
|
106
|
+
boxed(art_indents)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Resources are required even when empty; every box defaults to the media box, so only those that differ are
|
|
110
|
+
# written.
|
|
111
|
+
def finalize
|
|
112
|
+
resources
|
|
113
|
+
{CropBox: crop_box, BleedBox: bleed_box, TrimBox: trim_box, ArtBox: art_box}.each do |name, box|
|
|
114
|
+
dictionary.data[name] = box unless box == dimensions
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def boxed(indents)
|
|
121
|
+
left, bottom, right, top = dimensions
|
|
122
|
+
[left + indents[:left], bottom + indents[:bottom], right - indents[:right], top - indents[:top]]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PDF::Core
|
|
4
|
+
module PageGeometry
|
|
5
|
+
POINTS_PER_INCH = 72.0
|
|
6
|
+
MILLIMETERS_PER_INCH = 25.4
|
|
7
|
+
|
|
8
|
+
# ISO 216 (A, B), ISO 217 (RA, SRA) and ISO 269 (C) sizes in millimeters, then North American sizes in inches.
|
|
9
|
+
MILLIMETERS_BY_SIZE = {
|
|
10
|
+
"4A0" => [1682, 2378], "2A0" => [1189, 1682],
|
|
11
|
+
"A0" => [841, 1189], "A1" => [594, 841], "A2" => [420, 594], "A3" => [297, 420], "A4" => [210, 297],
|
|
12
|
+
"A5" => [148, 210], "A6" => [105, 148], "A7" => [74, 105], "A8" => [52, 74], "A9" => [37, 52], "A10" => [26, 37],
|
|
13
|
+
"B0" => [1000, 1414], "B1" => [707, 1000], "B2" => [500, 707], "B3" => [353, 500], "B4" => [250, 353],
|
|
14
|
+
"B5" => [176, 250], "B6" => [125, 176], "B7" => [88, 125], "B8" => [62, 88], "B9" => [44, 62], "B10" => [31, 44],
|
|
15
|
+
"C0" => [917, 1297], "C1" => [648, 917], "C2" => [458, 648], "C3" => [324, 458], "C4" => [229, 324],
|
|
16
|
+
"C5" => [162, 229], "C6" => [114, 162], "C7" => [81, 114], "C8" => [57, 81], "C9" => [40, 57], "C10" => [28, 40],
|
|
17
|
+
"RA0" => [860, 1220], "RA1" => [610, 860], "RA2" => [430, 610], "RA3" => [305, 430], "RA4" => [215, 305],
|
|
18
|
+
"SRA0" => [900, 1280], "SRA1" => [640, 900], "SRA2" => [450, 640], "SRA3" => [320, 450], "SRA4" => [225, 320],
|
|
19
|
+
"EXECUTIVE" => [184.1, 266.7]
|
|
20
|
+
}.freeze
|
|
21
|
+
INCHES_BY_SIZE = {"FOLIO" => [8.5, 13], "LEGAL" => [8.5, 14], "LETTER" => [8.5, 11], "TABLOID" => [11, 17]}.freeze
|
|
22
|
+
|
|
23
|
+
SIZES = MILLIMETERS_BY_SIZE.transform_values { |sides| sides.map { |millimeters| (millimeters * POINTS_PER_INCH / MILLIMETERS_PER_INCH).round(2) } }
|
|
24
|
+
.merge(INCHES_BY_SIZE.transform_values { |sides| sides.map { |inches| inches * POINTS_PER_INCH } })
|
|
25
|
+
.freeze
|
|
26
|
+
end
|
|
27
|
+
end
|