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,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PDF::Core
|
|
4
|
+
# What a PDF `q` saves and `Q` restores, as far as Gamba writes it.
|
|
5
|
+
class GraphicState
|
|
6
|
+
UNDASHED = {dash: nil, space: nil, phase: 0}.freeze
|
|
7
|
+
# The text state parameters Prawn sets, with their defaults, in the order a new page writes them back; each
|
|
8
|
+
# state records them as last written in it.
|
|
9
|
+
DEFAULT_TEXT_STATE = {text_rendering_mode: :fill, character_spacing: 0, word_spacing: 0, horizontal_text_scaling: 100, rise: 0}.freeze
|
|
10
|
+
TEXT_STATE_WRITERS = DEFAULT_TEXT_STATE.to_h { |setting, _default| [setting, :"#{setting}="] }.freeze
|
|
11
|
+
|
|
12
|
+
attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width, :fill_color, :stroke_color,
|
|
13
|
+
*DEFAULT_TEXT_STATE.keys
|
|
14
|
+
# What Prawn's state leaves out: the pattern painting in place of each colour since a gradient was set, and the
|
|
15
|
+
# blend mode's ExtGState, {name:, reference:} each, nil for none and for Normal.
|
|
16
|
+
attr_accessor :fill_pattern, :stroke_pattern, :blend_mode
|
|
17
|
+
# The save that opened this state on its page, or that it was reopened for on a new page; a copy has none.
|
|
18
|
+
attr_accessor :save
|
|
19
|
+
|
|
20
|
+
def initialize(previous_state = nil)
|
|
21
|
+
if previous_state
|
|
22
|
+
@color_space = previous_state.color_space.dup
|
|
23
|
+
@fill_color = previous_state.fill_color.dup
|
|
24
|
+
@stroke_color = previous_state.stroke_color.dup
|
|
25
|
+
@dash = previous_state.dash.dup
|
|
26
|
+
@cap_style = previous_state.cap_style
|
|
27
|
+
@join_style = previous_state.join_style
|
|
28
|
+
@line_width = previous_state.line_width
|
|
29
|
+
@fill_pattern = previous_state.fill_pattern
|
|
30
|
+
@stroke_pattern = previous_state.stroke_pattern
|
|
31
|
+
@blend_mode = previous_state.blend_mode
|
|
32
|
+
copy_text_state(previous_state)
|
|
33
|
+
else
|
|
34
|
+
@color_space = {}
|
|
35
|
+
@fill_color = "000000"
|
|
36
|
+
@stroke_color = "000000"
|
|
37
|
+
@dash = UNDASHED.dup
|
|
38
|
+
@cap_style = :butt
|
|
39
|
+
@join_style = :miter
|
|
40
|
+
@line_width = 1
|
|
41
|
+
reset_text_state
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The text state recorded in another state, the default one for nil.
|
|
46
|
+
def copy_text_state(other)
|
|
47
|
+
return reset_text_state unless other
|
|
48
|
+
|
|
49
|
+
@text_rendering_mode = other.text_rendering_mode
|
|
50
|
+
@character_spacing = other.character_spacing
|
|
51
|
+
@word_spacing = other.word_spacing
|
|
52
|
+
@horizontal_text_scaling = other.horizontal_text_scaling
|
|
53
|
+
@rise = other.rise
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def reset_text_state
|
|
57
|
+
@text_rendering_mode = :fill
|
|
58
|
+
@character_spacing = 0
|
|
59
|
+
@word_spacing = 0
|
|
60
|
+
@horizontal_text_scaling = 100
|
|
61
|
+
@rise = 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# What fills or strokes: the pattern's name, else the colour.
|
|
65
|
+
def fill_paint
|
|
66
|
+
fill_pattern ? fill_pattern[:name] : fill_color
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def stroke_paint
|
|
70
|
+
stroke_pattern ? stroke_pattern[:name] : stroke_color
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def dup
|
|
74
|
+
GraphicState.new(self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def dash_setting
|
|
78
|
+
return "[] 0 d" unless dash[:dash]
|
|
79
|
+
|
|
80
|
+
lengths = dash[:dash].is_a?(Array) ? dash[:dash] : [dash[:dash], dash[:space]]
|
|
81
|
+
"[#{PDF::Core.real_params(lengths)}] #{PDF::Core.real(dash[:phase])} d"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Graphics
|
|
5
|
+
module Color
|
|
6
|
+
COLOR_SPACES = %i[DeviceRGB DeviceCMYK Pattern].freeze
|
|
7
|
+
OPERATOR_BY_TYPE = {fill: "scn", stroke: "SCN"}.freeze
|
|
8
|
+
COLOR_SPACE_OPERATOR_BY_TYPE = {fill: "cs", stroke: "CS"}.freeze
|
|
9
|
+
# A document uses a handful of colours; computed ones (gradients drawn by hand) must not grow it forever.
|
|
10
|
+
HEX_COLORS_CACHED = 256
|
|
11
|
+
PDF_OPERANDS_BY_HEX_COLOR = {}
|
|
12
|
+
|
|
13
|
+
def fill_color(*color)
|
|
14
|
+
return current_fill_color if color.empty?
|
|
15
|
+
|
|
16
|
+
self.current_fill_color = process_color(*color)
|
|
17
|
+
set_fill_color
|
|
18
|
+
end
|
|
19
|
+
alias_method :fill_color=, :fill_color
|
|
20
|
+
|
|
21
|
+
def stroke_color(*color)
|
|
22
|
+
return current_stroke_color if color.empty?
|
|
23
|
+
|
|
24
|
+
color = process_color(*color)
|
|
25
|
+
self.current_stroke_color = color
|
|
26
|
+
set_stroke_color(color)
|
|
27
|
+
end
|
|
28
|
+
alias_method :stroke_color=, :stroke_color
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def rgb2hex(rgb)
|
|
33
|
+
rgb.map { format("%02x", _1) }.join
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def hex2rgb(hex)
|
|
37
|
+
[hex[0..1], hex[2..3], hex[4..5]].map { _1.to_i(16) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def process_color(*color)
|
|
43
|
+
case color.size
|
|
44
|
+
when 1 then color[0]
|
|
45
|
+
when 4 then color
|
|
46
|
+
else raise ArgumentError, "wrong number of arguments supplied"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def color_type(color)
|
|
51
|
+
case color
|
|
52
|
+
when String
|
|
53
|
+
return :RGB if /\A\h{6}\z/.match?(color)
|
|
54
|
+
when Array
|
|
55
|
+
return :RGB if color.length == 3
|
|
56
|
+
return :CMYK if color.length == 4
|
|
57
|
+
end
|
|
58
|
+
raise ArgumentError, "Unknown type of color: #{color.inspect}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def normalize_color(color)
|
|
62
|
+
case color_type(color)
|
|
63
|
+
when :RGB
|
|
64
|
+
r, g, b = hex2rgb(color)
|
|
65
|
+
[r / 255.0, g / 255.0, b / 255.0]
|
|
66
|
+
when :CMYK
|
|
67
|
+
color.map { _1 / 100.0 }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def color_to_s(color)
|
|
72
|
+
return PDF::Core.real_params(normalize_color(color)) unless color.is_a?(String)
|
|
73
|
+
|
|
74
|
+
PDF_OPERANDS_BY_HEX_COLOR.fetch(color) do
|
|
75
|
+
operands = PDF::Core.real_params(normalize_color(color))
|
|
76
|
+
PDF_OPERANDS_BY_HEX_COLOR[color.dup.freeze] = operands if PDF_OPERANDS_BY_HEX_COLOR.size < HEX_COLORS_CACHED
|
|
77
|
+
operands
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def color_space(color)
|
|
82
|
+
(color_type(color) == :RGB) ? :DeviceRGB : :DeviceCMYK
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set_color_space(type, color_space)
|
|
86
|
+
return if current_color_space(type) == color_space && !page.in_stamp_stream?
|
|
87
|
+
|
|
88
|
+
set_current_color_space(color_space, type)
|
|
89
|
+
raise ArgumentError, "unknown color space: '#{color_space}'" unless COLOR_SPACES.include?(color_space)
|
|
90
|
+
|
|
91
|
+
operator = COLOR_SPACE_OPERATOR_BY_TYPE.fetch(type) { raise ArgumentError, "unknown type '#{type}'" }
|
|
92
|
+
add_content("/#{color_space} #{operator}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A pattern paints until a colour of the same type replaces it. One the page's resources hold is recorded, to
|
|
96
|
+
# paint with on the next page.
|
|
97
|
+
def set_color(type, color, options = {})
|
|
98
|
+
operator = OPERATOR_BY_TYPE.fetch(type) { raise ArgumentError, "unknown type '#{type}'" }
|
|
99
|
+
if options[:pattern]
|
|
100
|
+
reference = page.dictionary.data[:Resources]&.dig(:Pattern, color)
|
|
101
|
+
set_color_space(type, :Pattern)
|
|
102
|
+
add_content("/#{color} #{operator}")
|
|
103
|
+
record_pattern(type, reference && {name: color, reference:})
|
|
104
|
+
else
|
|
105
|
+
set_color_space(type, color_space(color))
|
|
106
|
+
write_color(color_to_s(color), operator)
|
|
107
|
+
state = graphic_state
|
|
108
|
+
(type == :fill) ? state.fill_pattern &&= nil : state.stroke_pattern &&= nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A pattern, {name:, reference:}, on this page's resources too.
|
|
113
|
+
def write_pattern(type, pattern)
|
|
114
|
+
(page.resources[:Pattern] ||= {})[pattern[:name]] ||= pattern[:reference]
|
|
115
|
+
set_color_space(type, :Pattern)
|
|
116
|
+
add_content("/#{pattern[:name]} #{OPERATOR_BY_TYPE.fetch(type)}")
|
|
117
|
+
record_pattern(type, pattern)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def record_pattern(type, pattern)
|
|
121
|
+
(type == :fill) ? graphic_state.fill_pattern = pattern : graphic_state.stroke_pattern = pattern
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def write_fill_paint
|
|
125
|
+
(pattern = graphic_state.fill_pattern) ? write_pattern(:fill, pattern) : set_fill_color
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def write_stroke_paint
|
|
129
|
+
(pattern = graphic_state.stroke_pattern) ? write_pattern(:stroke, pattern) : set_stroke_color
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def set_fill_color(color = nil)
|
|
133
|
+
set_color(:fill, color || current_fill_color)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def set_stroke_color(color = nil)
|
|
137
|
+
set_color(:stroke, color || current_stroke_color)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def update_colors
|
|
141
|
+
set_fill_color
|
|
142
|
+
set_stroke_color
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def current_color_space(type)
|
|
146
|
+
graphic_state.color_space[type]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def set_current_color_space(color_space, type)
|
|
150
|
+
graphic_state.color_space[type] = color_space
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def current_fill_color
|
|
154
|
+
graphic_state.fill_color
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def current_fill_color=(color)
|
|
158
|
+
graphic_state.fill_color = color
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def current_stroke_color
|
|
162
|
+
graphic_state.stroke_color
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def current_stroke_color=(color)
|
|
166
|
+
graphic_state.stroke_color = color
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def write_color(color, operator)
|
|
170
|
+
add_content("#{color} #{operator}")
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
|
|
5
|
+
module Prawn
|
|
6
|
+
module Graphics
|
|
7
|
+
# Axial and radial gradients, as shading patterns (ISO 32000-1 §8.7.4.5).
|
|
8
|
+
module Patterns
|
|
9
|
+
GradientStop = Data.define(:position, :color)
|
|
10
|
+
Gradient = Data.define(:type, :apply_transformations, :stops, :from, :to, :r1, :r2)
|
|
11
|
+
IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0].freeze
|
|
12
|
+
|
|
13
|
+
def fill_gradient(*, **)
|
|
14
|
+
set_gradient(:fill, *, **)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stroke_gradient(*, **)
|
|
18
|
+
set_gradient(:stroke, *, **)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def set_gradient(type, *, **)
|
|
24
|
+
gradient = parse_gradient_arguments(*, **)
|
|
25
|
+
name = "SP#{gradient_registry_key(gradient)}"
|
|
26
|
+
reference = (@gradient_registry ||= {})[name] ||= create_gradient_pattern(gradient)
|
|
27
|
+
Color::OPERATOR_BY_TYPE.fetch(type) { raise ArgumentError, "unknown type '#{type}'" }
|
|
28
|
+
write_pattern(type, {name:, reference:})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_gradient_arguments(*arguments, from: nil, to: nil, r1: nil, r2: nil, stops: nil, apply_transformations: nil)
|
|
32
|
+
case arguments.length
|
|
33
|
+
when 0 then apply_transformations = true if apply_transformations.nil?
|
|
34
|
+
when 4 then from, to, *stops = arguments
|
|
35
|
+
when 6 then from, r1, to, r2, *stops = arguments
|
|
36
|
+
else raise ArgumentError, "Unknown type of gradient: #{arguments.inspect}"
|
|
37
|
+
end
|
|
38
|
+
raise ArgumentError, "At least two stops must be specified" if stops.length < 2
|
|
39
|
+
|
|
40
|
+
stops = stops.each_with_index.map do |stop, index|
|
|
41
|
+
if stop.is_a?(Array) || stop.is_a?(Hash)
|
|
42
|
+
position, color = stop
|
|
43
|
+
else
|
|
44
|
+
position = index / (Float(stops.length) - 1)
|
|
45
|
+
color = stop
|
|
46
|
+
end
|
|
47
|
+
raise ArgumentError, "position must be between 0 and 1" unless (0..1).cover?(position)
|
|
48
|
+
|
|
49
|
+
GradientStop.new(position, normalize_color(color))
|
|
50
|
+
end
|
|
51
|
+
raise ArgumentError, "The first stop must have a position of 0" if stops.first.position != 0
|
|
52
|
+
raise ArgumentError, "The last stop must have a position of 1" if stops.last.position != 1
|
|
53
|
+
color_types = stops.map { color_type(_1.color) }
|
|
54
|
+
color_types.uniq!
|
|
55
|
+
raise ArgumentError, "All colors must be of the same color space" if color_types.length > 1
|
|
56
|
+
|
|
57
|
+
Gradient.new(r1 ? :radial : :axial, apply_transformations, stops, from, to, r1, r2)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def gradient_registry_key(gradient)
|
|
61
|
+
_x1, _y1, x2, y2, transformation = gradient_coordinates(gradient)
|
|
62
|
+
key = [gradient.type.to_s, transformation, x2, y2, gradient.r1 || -1, gradient.r2 || -1, gradient.stops.length,
|
|
63
|
+
gradient.stops.map { |stop| [stop.position, stop.color] }].flatten
|
|
64
|
+
Digest::SHA1.hexdigest(key.join(","))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_gradient_pattern(gradient)
|
|
68
|
+
if gradient.apply_transformations.nil? && current_transformation_matrix_with_translation(0, 0) != IDENTITY_MATRIX
|
|
69
|
+
warn "Gradients in Prawn 2.x and lower are not correctly positioned when a transformation has been made to the " \
|
|
70
|
+
"document. Pass 'apply_transformations: true' to correctly transform the gradient, or see " \
|
|
71
|
+
"https://github.com/prawnpdf/prawn/wiki/Gradient-Transformations for more information."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
functions = gradient.stops.each_cons(2).map do |first, second|
|
|
75
|
+
ref!(FunctionType: 2, Domain: [0.0, 1.0], C0: first.color, C1: second.color, N: 1.0)
|
|
76
|
+
end
|
|
77
|
+
function =
|
|
78
|
+
if functions.length == 1
|
|
79
|
+
functions.first
|
|
80
|
+
else
|
|
81
|
+
ref!(FunctionType: 3, Domain: [0.0, 1.0], Functions: functions, Bounds: gradient.stops[1..-2].map(&:position),
|
|
82
|
+
Encode: [0.0, 1.0] * functions.length)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
x1, y1, x2, y2, transformation = gradient_coordinates(gradient)
|
|
86
|
+
coordinates = (gradient.type == :axial) ? [0, 0, x2 - x1, y2 - y1] : [0, 0, gradient.r1, x2 - x1, y2 - y1, gradient.r2]
|
|
87
|
+
shading = ref!(ShadingType: (gradient.type == :axial) ? 2 : 3, ColorSpace: color_space(gradient.stops.first.color),
|
|
88
|
+
Coords: coordinates, Function: function, Extend: [true, true])
|
|
89
|
+
ref!(PatternType: 2, Shading: shading, Matrix: transformation)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def gradient_coordinates(gradient)
|
|
93
|
+
x1, y1 = map_to_absolute(gradient.from)
|
|
94
|
+
x2, y2 = map_to_absolute(gradient.to)
|
|
95
|
+
transformation = gradient.apply_transformations ? current_transformation_matrix_with_translation(x1, y1) : [1, 0, 0, 1, x1, y1]
|
|
96
|
+
[x1, y1, x2, y2, transformation]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Graphics
|
|
5
|
+
module Dash
|
|
6
|
+
def dash(length = nil, options = {})
|
|
7
|
+
return current_dash_state if length.nil?
|
|
8
|
+
|
|
9
|
+
length = Array(length)
|
|
10
|
+
raise ArgumentError, "Zero length dashes are invalid. Call #undash to disable dashes." if length.all?(&:zero?)
|
|
11
|
+
raise ArgumentError, "Negative numbers are not allowed for dash lengths." if length.any?(&:negative?)
|
|
12
|
+
|
|
13
|
+
length = length.first if length.length == 1
|
|
14
|
+
self.current_dash_state = {dash: length, space: length.is_a?(Array) ? nil : options[:space] || length, phase: options[:phase] || 0}
|
|
15
|
+
write_stroke_dash
|
|
16
|
+
end
|
|
17
|
+
alias_method :dash=, :dash
|
|
18
|
+
|
|
19
|
+
def undash
|
|
20
|
+
self.current_dash_state = PDF::Core::GraphicState::UNDASHED.dup
|
|
21
|
+
write_stroke_dash
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def dashed?
|
|
25
|
+
current_dash_state != PDF::Core::GraphicState::UNDASHED
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def write_stroke_dash
|
|
31
|
+
add_content(graphic_state.dash_setting)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def current_dash_state=(dash_options)
|
|
35
|
+
graphic_state.dash = dash_options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_dash_state
|
|
39
|
+
graphic_state.dash
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module CapStyle
|
|
44
|
+
CAP_STYLES = {butt: 0, round: 1, projecting_square: 2}.freeze
|
|
45
|
+
|
|
46
|
+
def cap_style(style = nil)
|
|
47
|
+
return graphic_state.cap_style || :butt if style.nil?
|
|
48
|
+
|
|
49
|
+
graphic_state.cap_style = style
|
|
50
|
+
write_stroke_cap_style
|
|
51
|
+
end
|
|
52
|
+
alias_method :cap_style=, :cap_style
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def write_stroke_cap_style
|
|
57
|
+
add_content("#{CAP_STYLES[graphic_state.cap_style]} J")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module JoinStyle
|
|
62
|
+
JOIN_STYLES = {miter: 0, round: 1, bevel: 2}.freeze
|
|
63
|
+
|
|
64
|
+
def join_style(style = nil)
|
|
65
|
+
return graphic_state.join_style || :miter if style.nil?
|
|
66
|
+
|
|
67
|
+
graphic_state.join_style = style
|
|
68
|
+
unless JOIN_STYLES.key?(style)
|
|
69
|
+
raise Errors::InvalidJoinStyle, "#{style} is not a recognized join style. Valid styles are #{JOIN_STYLES.keys.join(", ")}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
write_stroke_join_style
|
|
73
|
+
end
|
|
74
|
+
alias_method :join_style=, :join_style
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def write_stroke_join_style
|
|
79
|
+
add_content("#{JOIN_STYLES[graphic_state.join_style]} j")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Transparency
|
|
84
|
+
def transparent(opacity, stroke_opacity = opacity)
|
|
85
|
+
min_version(1.4)
|
|
86
|
+
opacity = opacity.clamp(0.0, 1.0)
|
|
87
|
+
stroke_opacity = stroke_opacity.clamp(0.0, 1.0)
|
|
88
|
+
@opacity_dictionary_registry ||= {}
|
|
89
|
+
ext_gstate = @opacity_dictionary_registry["#{opacity}_#{stroke_opacity}"] ||=
|
|
90
|
+
{name: "Tr#{@opacity_dictionary_registry.length + 1}", reference: ref!(Type: :ExtGState, CA: stroke_opacity, ca: opacity)}
|
|
91
|
+
ext_gstate_block(ext_gstate) { yield }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module BlendMode
|
|
96
|
+
def blend_mode(blend_mode = :Normal)
|
|
97
|
+
min_version(1.4)
|
|
98
|
+
ext_gstate = blend_mode_ext_gstate(blend_mode)
|
|
99
|
+
return ext_gstate_block(ext_gstate) { yield } if block_given?
|
|
100
|
+
|
|
101
|
+
write_ext_gstate(ext_gstate)
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def blend_mode_ext_gstate(blend_mode)
|
|
108
|
+
@blend_mode_dictionary_registry ||= {}
|
|
109
|
+
name = "BM#{Array(blend_mode).join}"
|
|
110
|
+
@blend_mode_dictionary_registry[name] ||= {name:, reference: ref!(Type: :ExtGState, BM: blend_mode)}
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module Transformation
|
|
115
|
+
def rotate(angle, options = {}, &block)
|
|
116
|
+
Prawn.verify_options(:origin, options)
|
|
117
|
+
radians = degree_to_rad(angle)
|
|
118
|
+
cos = Math.cos(radians)
|
|
119
|
+
sin = Math.sin(radians)
|
|
120
|
+
return transformation_matrix(cos, sin, -sin, cos, 0, 0, &block) if options[:origin].nil?
|
|
121
|
+
raise Errors::BlockRequired unless block
|
|
122
|
+
|
|
123
|
+
x = options[:origin][0] + bounds.absolute_left
|
|
124
|
+
y = options[:origin][1] + bounds.absolute_bottom
|
|
125
|
+
translate(x - ((x * cos) - (y * sin)), y - ((x * sin) + (y * cos))) do
|
|
126
|
+
transformation_matrix(cos, sin, -sin, cos, 0, 0, &block)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def translate(x, y, &)
|
|
131
|
+
transformation_matrix(1, 0, 0, 1, x, y, &)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def scale(factor, options = {}, &block)
|
|
135
|
+
Prawn.verify_options(:origin, options)
|
|
136
|
+
return transformation_matrix(factor, 0, 0, factor, 0, 0, &block) if options[:origin].nil?
|
|
137
|
+
raise Errors::BlockRequired unless block
|
|
138
|
+
|
|
139
|
+
x = options[:origin][0] + bounds.absolute_left
|
|
140
|
+
y = options[:origin][1] + bounds.absolute_bottom
|
|
141
|
+
translate(x - (factor * x), y - (factor * y)) do
|
|
142
|
+
transformation_matrix(factor, 0, 0, factor, 0, 0, &block)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def transformation_matrix(*matrix)
|
|
147
|
+
raise ArgumentError, "Transformation matrix must have exacty 6 elements" if matrix.length != 6
|
|
148
|
+
|
|
149
|
+
save_graphics_state if block_given?
|
|
150
|
+
add_to_transformation_stack(*matrix)
|
|
151
|
+
add_content("#{PDF::Core.real_params(matrix)} cm")
|
|
152
|
+
return unless block_given?
|
|
153
|
+
|
|
154
|
+
yield
|
|
155
|
+
restore_graphics_state
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|