hexapdf 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/lib/hexapdf/cli/command.rb +63 -63
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +0 -1
- data/lib/hexapdf/cli/optimize.rb +5 -5
- data/lib/hexapdf/configuration.rb +21 -0
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
- data/lib/hexapdf/document/annotations.rb +115 -0
- data/lib/hexapdf/document.rb +28 -7
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -0
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
- data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
- data/lib/hexapdf/type/annotation.rb +59 -1
- data/lib/hexapdf/type/annotations/appearance_generator.rb +273 -0
- data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
- data/lib/hexapdf/type/annotations/line.rb +521 -0
- data/lib/hexapdf/type/annotations/widget.rb +2 -96
- data/lib/hexapdf/type/annotations.rb +3 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +0 -1
- data/lib/hexapdf/xref_section.rb +7 -4
- data/test/hexapdf/content/test_graphics_state.rb +2 -3
- data/test/hexapdf/content/test_operator.rb +4 -5
- data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
- data/test/hexapdf/digital_signature/test_handler.rb +2 -3
- data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
- data/test/hexapdf/document/test_annotations.rb +33 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_document.rb +11 -3
- data/test/hexapdf/test_stream.rb +1 -2
- data/test/hexapdf/test_xref_section.rb +1 -1
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +398 -0
- data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
- data/test/hexapdf/type/annotations/test_line.rb +189 -0
- data/test/hexapdf/type/annotations/test_widget.rb +0 -81
- data/test/hexapdf/type/test_annotation.rb +55 -0
- data/test/hexapdf/type/test_form.rb +6 -0
- metadata +10 -2
@@ -0,0 +1,521 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2025 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'hexapdf/type/annotation'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
module Type
|
41
|
+
module Annotations
|
42
|
+
|
43
|
+
# A line annotation is a markup annotation that displays a single straight line.
|
44
|
+
#
|
45
|
+
# The style of the line annotation, like adding leader lines, changing the colors and so on,
|
46
|
+
# can be customized using the provided convenience methods.
|
47
|
+
#
|
48
|
+
# Note that changing the line width and color is done using the included
|
49
|
+
# BorderStyling#border_style. While that method allows special styling of the line (like
|
50
|
+
# :beveled), only a simple line dash pattern is supported by the line annotation.
|
51
|
+
#
|
52
|
+
# Example:
|
53
|
+
#
|
54
|
+
# #>pdf-small
|
55
|
+
# doc.annotations.create_line(doc.pages[0], start_point: [30, 20], end_point: [90, 60]).
|
56
|
+
# border_style(color: "hp-blue", width: 2, style: [3, 1]).
|
57
|
+
# leader_line_length(15).
|
58
|
+
# leader_line_extension_length(10).
|
59
|
+
# leader_line_offset(5).
|
60
|
+
# interior_color("hp-orange").
|
61
|
+
# line_ending_style(start_style: :circle, end_style: :open_arrow).
|
62
|
+
# captioned(true).
|
63
|
+
# contents("Caption").
|
64
|
+
# caption_position(:top).
|
65
|
+
# caption_offset(0, 5).
|
66
|
+
# regenerate_appearance
|
67
|
+
# canvas.line(30, 20, 90, 60).stroke
|
68
|
+
#
|
69
|
+
# See: PDF2.0 s12.5.6.7, HexaPDF::Type::MarkupAnnotation
|
70
|
+
class Line < MarkupAnnotation
|
71
|
+
|
72
|
+
include BorderStyling
|
73
|
+
|
74
|
+
define_field :Subtype, type: Symbol, required: true, default: :Line
|
75
|
+
define_field :L, type: PDFArray, required: true
|
76
|
+
define_field :BS, type: :Border
|
77
|
+
define_field :LE, type: PDFArray, default: [:None, :None], version: '1.4'
|
78
|
+
define_field :IC, type: PDFArray, version: '1.4'
|
79
|
+
define_field :LL, type: Numeric, default: 0, version: '1.6'
|
80
|
+
define_field :LLE, type: Numeric, default: 0, version: '1.6'
|
81
|
+
define_field :Cap, type: Boolean, default: false, version: '1.6'
|
82
|
+
define_field :IT, type: Symbol, version: '1.6',
|
83
|
+
allowed_values: [:LineArrow, :LineDimension]
|
84
|
+
define_field :LLO, type: Numeric, version: '1.7'
|
85
|
+
define_field :CP, type: Symbol, default: :Inline, version: '1.7',
|
86
|
+
allowed_values: [:Inline, :Top]
|
87
|
+
define_field :Measure, type: Dictionary, version: '1.7'
|
88
|
+
define_field :CO, type: PDFArray, default: [0, 0], version: '1.7'
|
89
|
+
|
90
|
+
# :call-seq:
|
91
|
+
# line.line => [x0, y0, x1, y1]
|
92
|
+
# line.line(x0, y0, x1, y1) => line
|
93
|
+
#
|
94
|
+
# Returns the start point and end point of the line as an array of four numbers [x0, y0, x1,
|
95
|
+
# y1] when no argument is given. Otherwise sets the start and end point of the line and
|
96
|
+
# returns self.
|
97
|
+
#
|
98
|
+
# This is the only required setting for a line annotation. Note, however, that without
|
99
|
+
# setting an appropriate color through #border_style the line will be transparent.
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
#
|
103
|
+
# #>pdf-small
|
104
|
+
# doc.annotations.
|
105
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
106
|
+
# regenerate_appearance
|
107
|
+
def line(x0 = nil, y0 = nil, x1 = nil, y1 = nil)
|
108
|
+
if x0.nil? && y0.nil? && x1.nil? && y1.nil?
|
109
|
+
self[:L].to_ary
|
110
|
+
elsif !x0 || !y0 || !x1 || !y1
|
111
|
+
raise ArgumentError, "All four arguments x0, y0, x1, y1 must be provided"
|
112
|
+
else
|
113
|
+
self[:L] = [x0, y0, x1, y1]
|
114
|
+
self
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Maps HexaPDF names to PDF names.
|
119
|
+
LINE_ENDING_STYLE_MAP = { # :nodoc:
|
120
|
+
Square: :Square, square: :Square,
|
121
|
+
Circle: :Circle, circle: :Circle,
|
122
|
+
Diamond: :Diamond, diamond: :Diamond,
|
123
|
+
OpenArrow: :OpenArrow, open_arrow: :OpenArrow,
|
124
|
+
ClosedArrow: :ClosedArrow, closed_arrow: :ClosedArrow,
|
125
|
+
None: :None, none: :None,
|
126
|
+
Butt: :Butt, butt: :Butt,
|
127
|
+
ROpenArrow: :ROpenArrow, ropen_arrow: :ROpenArrow,
|
128
|
+
RClosedArrow: :RClosedArrow, rclosed_arrow: :RClosedArrow,
|
129
|
+
Slash: :Slash, slash: :Slash,
|
130
|
+
}.freeze
|
131
|
+
LINE_ENDING_STYLE_REVERSE_MAP = LINE_ENDING_STYLE_MAP.invert # :nodoc:
|
132
|
+
|
133
|
+
|
134
|
+
# Describes the line ending style for a line annotation, i.e. the +start_style+ and the
|
135
|
+
# +end_style+.
|
136
|
+
#
|
137
|
+
# See Line#line_ending_style for more information.
|
138
|
+
LineEndingStyle = Struct.new(:start_style, :end_style)
|
139
|
+
|
140
|
+
# :call-seq:
|
141
|
+
# line.line_ending_style => style
|
142
|
+
# line.line_ending_style(start_style: :none, end_style: :none) => line
|
143
|
+
#
|
144
|
+
# Returns a LineEndingStyle instance holding the current line ending styles when no argument
|
145
|
+
# is given. Otherwise sets the line ending style of the line and returns self.
|
146
|
+
#
|
147
|
+
# When returning the styles, unknown line ending styles are mapped to :none.
|
148
|
+
#
|
149
|
+
# When setting the line ending style, arguments that are not provided will use the currently
|
150
|
+
# defined value or fall back to the default of +:none+.
|
151
|
+
#
|
152
|
+
# Possible line ending styles (the first one is the HexaPDF name, the second the PDF name):
|
153
|
+
#
|
154
|
+
# :square or :Square::
|
155
|
+
# A square filled with the annotation's interior colour, if any.
|
156
|
+
#
|
157
|
+
# #>pdf-small-hide
|
158
|
+
# doc.annotations.
|
159
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
160
|
+
# interior_color("hp-orange").
|
161
|
+
# line_ending_style(end_style: :square).
|
162
|
+
# regenerate_appearance
|
163
|
+
#
|
164
|
+
# :circle or :Circle::
|
165
|
+
# A circle filled with the annotation’s interior colour, if any.
|
166
|
+
#
|
167
|
+
# #>pdf-small-hide
|
168
|
+
# doc.annotations.
|
169
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
170
|
+
# interior_color("hp-orange").
|
171
|
+
# line_ending_style(end_style: :circle).
|
172
|
+
# regenerate_appearance
|
173
|
+
#
|
174
|
+
# :diamond or :Diamond::
|
175
|
+
# A diamond shape filled with the annotation’s interior colour, if any.
|
176
|
+
#
|
177
|
+
# #>pdf-small-hide
|
178
|
+
# doc.annotations.
|
179
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
180
|
+
# interior_color("hp-orange").
|
181
|
+
# line_ending_style(end_style: :diamond).
|
182
|
+
# regenerate_appearance
|
183
|
+
#
|
184
|
+
# :open_arrow or :OpenArrow::
|
185
|
+
# Two short lines meeting in an acute angle to form an open arrowhead.
|
186
|
+
#
|
187
|
+
# #>pdf-small-hide
|
188
|
+
# doc.annotations.
|
189
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
190
|
+
# interior_color("hp-orange").
|
191
|
+
# line_ending_style(end_style: :open_arrow).
|
192
|
+
# regenerate_appearance
|
193
|
+
#
|
194
|
+
# :closed_arrow or :ClosedArrow::
|
195
|
+
# Two short lines meeting in an acute angle as in the +:open_arrow+ style and connected
|
196
|
+
# by a third line to form a triangular closed arrowhead filled with the annotation’s
|
197
|
+
# interior colour, if any.
|
198
|
+
#
|
199
|
+
# #>pdf-small-hide
|
200
|
+
# doc.annotations.
|
201
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
202
|
+
# interior_color("hp-orange").
|
203
|
+
# line_ending_style(end_style: :closed_arrow).
|
204
|
+
# regenerate_appearance
|
205
|
+
#
|
206
|
+
# :none or :None::
|
207
|
+
# No line ending.
|
208
|
+
#
|
209
|
+
# #>pdf-small-hide
|
210
|
+
# doc.annotations.
|
211
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
212
|
+
# interior_color("hp-orange").
|
213
|
+
# line_ending_style(end_style: :none).
|
214
|
+
# regenerate_appearance
|
215
|
+
#
|
216
|
+
# :butt or :Butt::
|
217
|
+
# A short line at the endpoint perpendicular to the line itself.
|
218
|
+
#
|
219
|
+
# #>pdf-small-hide
|
220
|
+
# doc.annotations.
|
221
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
222
|
+
# interior_color("hp-orange").
|
223
|
+
# line_ending_style(end_style: :butt).
|
224
|
+
# regenerate_appearance
|
225
|
+
#
|
226
|
+
# :ropen_arrow or :ROpenArrow::
|
227
|
+
# Two short lines in the reverse direction from +:open_arrow+.
|
228
|
+
#
|
229
|
+
# #>pdf-small-hide
|
230
|
+
# doc.annotations.
|
231
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
232
|
+
# interior_color("hp-orange").
|
233
|
+
# line_ending_style(end_style: :ropen_arrow).
|
234
|
+
# regenerate_appearance
|
235
|
+
#
|
236
|
+
# :rclosed_arrow or :RClosedArrow::
|
237
|
+
# A triangular closed arrowhead in the reverse direction from +:closed_arrow+.
|
238
|
+
#
|
239
|
+
# #>pdf-small-hide
|
240
|
+
# doc.annotations.
|
241
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
242
|
+
# interior_color("hp-orange").
|
243
|
+
# line_ending_style(end_style: :rclosed_arrow).
|
244
|
+
# regenerate_appearance
|
245
|
+
#
|
246
|
+
# :slash or :Slash::
|
247
|
+
# A short line at the endpoint approximately 30 degrees clockwise from perpendicular to
|
248
|
+
# the line itself.
|
249
|
+
#
|
250
|
+
# #>pdf-small-hide
|
251
|
+
# doc.annotations.
|
252
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
253
|
+
# interior_color("hp-orange").
|
254
|
+
# line_ending_style(end_style: :slash).
|
255
|
+
# regenerate_appearance
|
256
|
+
def line_ending_style(start_style: :UNSET, end_style: :UNSET)
|
257
|
+
if start_style == :UNSET && end_style == :UNSET
|
258
|
+
le = self[:LE]
|
259
|
+
LineEndingStyle.new(LINE_ENDING_STYLE_REVERSE_MAP.fetch(le[0], :none),
|
260
|
+
LINE_ENDING_STYLE_REVERSE_MAP.fetch(le[1], :none))
|
261
|
+
else
|
262
|
+
start_style = self[:LE][0] if start_style == :UNSET
|
263
|
+
end_style = self[:LE][1] if end_style == :UNSET
|
264
|
+
start_style = LINE_ENDING_STYLE_MAP.fetch(start_style) do
|
265
|
+
raise ArgumentError, "Invalid line ending style: #{start_style.inspect}"
|
266
|
+
end
|
267
|
+
end_style = LINE_ENDING_STYLE_MAP.fetch(end_style) do
|
268
|
+
raise ArgumentError, "Invalid line ending style: #{end_style.inspect}"
|
269
|
+
end
|
270
|
+
self[:LE] = [start_style, end_style]
|
271
|
+
self
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# :call-seq:
|
276
|
+
# line.interior_color => color or nil
|
277
|
+
# line.interior_color(*color) => line
|
278
|
+
#
|
279
|
+
# Returns the interior color or +nil+ (in case the interior color should be transparent)
|
280
|
+
# when no argument is given. Otherwise sets the interior color and returns self.
|
281
|
+
#
|
282
|
+
# The interior color is used to fill the line endings depending on the line ending styles.
|
283
|
+
#
|
284
|
+
# +color+:: The interior color. See
|
285
|
+
# HexaPDF::Content::ColorSpace.device_color_from_specification for information on
|
286
|
+
# the allowed arguments.
|
287
|
+
#
|
288
|
+
# If the special value +:transparent+ is used when setting the color, no color is
|
289
|
+
# used for filling the line endings.
|
290
|
+
#
|
291
|
+
# Also see: #line_ending_style
|
292
|
+
def interior_color(*color)
|
293
|
+
if color.empty?
|
294
|
+
color = self[:IC]
|
295
|
+
color && !color.empty? ? Content::ColorSpace.prenormalized_device_color(color.value) : nil
|
296
|
+
else
|
297
|
+
color = if color.length == 1 && color.first == :transparent
|
298
|
+
[]
|
299
|
+
else
|
300
|
+
Content::ColorSpace.device_color_from_specification(color).components
|
301
|
+
end
|
302
|
+
self[:IC] = color
|
303
|
+
self
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# :call-seq:
|
308
|
+
# line.leader_line_length => leader_line_length
|
309
|
+
# line.leader_line_length(length) => line
|
310
|
+
#
|
311
|
+
# Returns the leader line length when no argument is given. Otherwise sets the leader line
|
312
|
+
# length and returns self.
|
313
|
+
#
|
314
|
+
# Leader lines extend from the line's end points perpendicular to the line. If the length
|
315
|
+
# value is positive, the leader lines appear in the clockwise direction, otherwise in the
|
316
|
+
# opposite direction.
|
317
|
+
#
|
318
|
+
# Note: The "line's end points" mean the actually drawn line and not the one specified with
|
319
|
+
# #line as those two are different when leader lines are involved.
|
320
|
+
#
|
321
|
+
# A value of zero means that no leader lines are used.
|
322
|
+
#
|
323
|
+
# Example:
|
324
|
+
#
|
325
|
+
# #>pdf-small
|
326
|
+
# doc.annotations.
|
327
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
328
|
+
# leader_line_length(15).
|
329
|
+
# regenerate_appearance
|
330
|
+
# canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
|
331
|
+
#
|
332
|
+
# Also see: #leader_line_extension_length, #leader_line_offset
|
333
|
+
def leader_line_length(length = nil)
|
334
|
+
length ? (self[:LL] = length; self) : self[:LL]
|
335
|
+
end
|
336
|
+
|
337
|
+
# :call-seq:
|
338
|
+
# line.leader_line_extension_length => leader_line_extension_length
|
339
|
+
# line.leader_line_extension_length(length) => line
|
340
|
+
#
|
341
|
+
# Returns the leader line extension length when no argument is given. Otherwise sets the
|
342
|
+
# leader line extension length and returns self.
|
343
|
+
#
|
344
|
+
# Leader line extensions extend from the line into the opposite direction of the leader
|
345
|
+
# lines.
|
346
|
+
#
|
347
|
+
# The argument +length+ must be non-negative.
|
348
|
+
#
|
349
|
+
# If the leader line extension length is set to a positive value, the leader line length
|
350
|
+
# also needs to be specified.
|
351
|
+
#
|
352
|
+
# Example:
|
353
|
+
#
|
354
|
+
# #>pdf-small
|
355
|
+
# doc.annotations.
|
356
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
357
|
+
# leader_line_length(15).
|
358
|
+
# leader_line_extension_length(5).
|
359
|
+
# regenerate_appearance
|
360
|
+
# canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
|
361
|
+
#
|
362
|
+
# Also see: #leader_line_length, #leader_line_offset
|
363
|
+
def leader_line_extension_length(length = nil)
|
364
|
+
if length
|
365
|
+
raise ArgumentError, "length must be non-negative" if length < 0
|
366
|
+
self[:LLE] = length
|
367
|
+
self
|
368
|
+
else
|
369
|
+
self[:LLE]
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# :call-seq:
|
374
|
+
# line.leader_line_offset => leader_line_offset
|
375
|
+
# line.leader_line_offset(number) => line
|
376
|
+
#
|
377
|
+
# Returns the leader line offset when no argument is given. Otherwise sets the leader line
|
378
|
+
# offset and returns self.
|
379
|
+
#
|
380
|
+
# The leader line offset is a non-negative number that describes the offset of the leader
|
381
|
+
# lines from the endpoints of the line.
|
382
|
+
#
|
383
|
+
# Example:
|
384
|
+
#
|
385
|
+
# #>pdf-small
|
386
|
+
# doc.annotations.
|
387
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
388
|
+
# leader_line_length(15).
|
389
|
+
# leader_line_offset(5).
|
390
|
+
# regenerate_appearance
|
391
|
+
# canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
|
392
|
+
#
|
393
|
+
# Also see: #leader_line_length, #leader_line_extension_length
|
394
|
+
def leader_line_offset(offset = nil)
|
395
|
+
offset ? (self[:LLO] = offset; self) : self[:LLO] || 0
|
396
|
+
end
|
397
|
+
|
398
|
+
# :call-seq:
|
399
|
+
# line.captioned => true or false
|
400
|
+
# line.captioned(value) => line
|
401
|
+
#
|
402
|
+
# Returns +true+ (if the line has a visible caption) or +false+ (no visible caption) when no
|
403
|
+
# argument is given. Otherwise sets whether a caption should be visible and returns self.
|
404
|
+
#
|
405
|
+
# If a caption should be shown, the text specified by the /Contents or /RC entries is shown
|
406
|
+
# in the appearance of the line.
|
407
|
+
#
|
408
|
+
# Example:
|
409
|
+
#
|
410
|
+
# #>pdf-small-hide
|
411
|
+
# doc.annotations.
|
412
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
413
|
+
# contents("Inline text").
|
414
|
+
# captioned(true).
|
415
|
+
# regenerate_appearance
|
416
|
+
# Also see: #caption_position, #caption_offset
|
417
|
+
def captioned(value = nil)
|
418
|
+
value ? (self[:Cap] = value; self) : self[:Cap]
|
419
|
+
end
|
420
|
+
|
421
|
+
# Maps HexaPDF names to PDF names.
|
422
|
+
CAPTION_POSITION_MAP = { # :nodoc:
|
423
|
+
Inline: :Inline, inline: :Inline,
|
424
|
+
Top: :Top, top: :Top,
|
425
|
+
}.freeze
|
426
|
+
CAPTION_POSITION_REVERSE_MAP = CAPTION_POSITION_MAP.invert # :nodoc:
|
427
|
+
|
428
|
+
# :call-seq:
|
429
|
+
# line.caption_position => caption_position
|
430
|
+
# line.caption_position(value) => line
|
431
|
+
#
|
432
|
+
# Returns the caption position when no argument is given. Otherwise sets the caption
|
433
|
+
# position and returns self.
|
434
|
+
#
|
435
|
+
# Possible caption positions are (the first one is the HexaPDF name, the second the PDF
|
436
|
+
# name):
|
437
|
+
#
|
438
|
+
# :inline or :Inline::
|
439
|
+
# The caption is centered inside the line (default).
|
440
|
+
#
|
441
|
+
# #>pdf-small-hide
|
442
|
+
# doc.annotations.
|
443
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
444
|
+
# contents("Inline text").
|
445
|
+
# captioned(true).
|
446
|
+
# caption_position(:inline).
|
447
|
+
# regenerate_appearance
|
448
|
+
#
|
449
|
+
# :top or :Top::
|
450
|
+
# The caption is on the top of the line.
|
451
|
+
#
|
452
|
+
# #>pdf-small-hide
|
453
|
+
# doc.annotations.
|
454
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
455
|
+
# contents("Top text").
|
456
|
+
# captioned(true).
|
457
|
+
# caption_position(:top).
|
458
|
+
# regenerate_appearance
|
459
|
+
#
|
460
|
+
# Also see: #captioned, #caption_offset
|
461
|
+
def caption_position(value = nil)
|
462
|
+
if value
|
463
|
+
value = CAPTION_POSITION_MAP.fetch(value) do
|
464
|
+
raise ArgumentError, "Invalid caption position: #{value.inspect}"
|
465
|
+
end
|
466
|
+
self[:CP] = value
|
467
|
+
self
|
468
|
+
else
|
469
|
+
CAPTION_POSITION_REVERSE_MAP[self[:CP]]
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# :call-seq:
|
474
|
+
# line.caption_offset => caption_offset
|
475
|
+
# line.caption_offset(x, y) => line
|
476
|
+
#
|
477
|
+
# Returns the caption offset when no argument is given. Otherwise sets the caption offset
|
478
|
+
# and returns self.
|
479
|
+
#
|
480
|
+
# The caption offset is an array of two numbers that specify the horizontal and vertical
|
481
|
+
# offsets of the caption from its normal position. A positive horizontal offset means moving
|
482
|
+
# the caption to the right. A positive vertical offset means shifting the caption up.
|
483
|
+
#
|
484
|
+
# Example:
|
485
|
+
#
|
486
|
+
# #>pdf-small-hide
|
487
|
+
# doc.annotations.
|
488
|
+
# create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
|
489
|
+
# contents("Top text").
|
490
|
+
# captioned(true).
|
491
|
+
# caption_position(:top).
|
492
|
+
# caption_offset(20, 10).
|
493
|
+
# regenerate_appearance
|
494
|
+
#
|
495
|
+
# Also see: #captioned, #caption_position
|
496
|
+
def caption_offset(x = nil, y = nil)
|
497
|
+
x || y ? (self[:CO] = [x || 0, y || 0]; self) : self[:CO].to_ary
|
498
|
+
end
|
499
|
+
|
500
|
+
private
|
501
|
+
|
502
|
+
def perform_validation #:nodoc:
|
503
|
+
super
|
504
|
+
if self[:LLE] < 0
|
505
|
+
yield('/LLE must be a non-negative number', true)
|
506
|
+
self[:LLE] = -self[:LLE]
|
507
|
+
end
|
508
|
+
if key?(:LLO) && self[:LLO] < 0
|
509
|
+
yield('/LLO must be a non-negative number', true)
|
510
|
+
self[:LLO] = -self[:LLO]
|
511
|
+
end
|
512
|
+
if self[:LLE] > 0 && self[:LL] == 0
|
513
|
+
yield("/LL required to be non-zero if /LLE is set")
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
end
|
518
|
+
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
@@ -48,6 +48,8 @@ module HexaPDF
|
|
48
48
|
# See: PDF2.0 s12.5.6.19, HexaPDF::Type::Annotation
|
49
49
|
class Widget < Annotation
|
50
50
|
|
51
|
+
include BorderStyling
|
52
|
+
|
51
53
|
# The dictionary used by the /MK key of the widget annotation.
|
52
54
|
class AppearanceCharacteristics < Dictionary
|
53
55
|
|
@@ -122,102 +124,6 @@ module HexaPDF
|
|
122
124
|
end
|
123
125
|
end
|
124
126
|
|
125
|
-
# Describes the border of an annotation.
|
126
|
-
#
|
127
|
-
# The +color+ property is either +nil+ if the border is transparent or else a device color
|
128
|
-
# object - see HexaPDF::Content::ColorSpace.
|
129
|
-
#
|
130
|
-
# The +style+ property can be one of the following:
|
131
|
-
#
|
132
|
-
# :solid:: Solid line.
|
133
|
-
# :beveled:: Embossed rectangle seemingly raised above the surface of the page.
|
134
|
-
# :inset:: Engraved rectangle receeding into the page.
|
135
|
-
# :underlined:: Underlined, i.e. only the bottom border is draw.
|
136
|
-
# Array: Dash array describing how to dash the line.
|
137
|
-
BorderStyle = Struct.new(:width, :color, :style, :horizontal_corner_radius,
|
138
|
-
:vertical_corner_radius)
|
139
|
-
|
140
|
-
# :call-seq:
|
141
|
-
# widget.border_style => border_style
|
142
|
-
# widget.border_style(color: 0, width: 1, style: :solid) => widget
|
143
|
-
#
|
144
|
-
# Returns a BorderStyle instance representing the border style of the widget when no
|
145
|
-
# argument is given. Otherwise sets the border style of the widget and returns self.
|
146
|
-
#
|
147
|
-
# When setting a border style, arguments that are not provided will use the default: a
|
148
|
-
# border with a solid, black, 1pt wide line. This also means that multiple invocations will
|
149
|
-
# reset *all* prior values.
|
150
|
-
#
|
151
|
-
# +color+:: The color of the border. See
|
152
|
-
# HexaPDF::Content::ColorSpace.device_color_from_specification for information on
|
153
|
-
# the allowed arguments.
|
154
|
-
#
|
155
|
-
# If the special value +:transparent+ is used when setting the color, a
|
156
|
-
# transparent is used. A transparent border will return a +nil+ value when getting
|
157
|
-
# the border color.
|
158
|
-
#
|
159
|
-
# +width+:: The width of the border. If set to 0, no border is shown.
|
160
|
-
#
|
161
|
-
# +style+:: Defines how the border is drawn. can be one of the following:
|
162
|
-
#
|
163
|
-
# +:solid+:: Draws a solid border.
|
164
|
-
# +:beveled+:: Draws a beveled border.
|
165
|
-
# +:inset+:: Draws an inset border.
|
166
|
-
# +:underlined+:: Draws only the bottom border.
|
167
|
-
# Array:: An array specifying a line dash pattern (see
|
168
|
-
# HexaPDF::Content::LineDashPattern)
|
169
|
-
def border_style(color: nil, width: nil, style: nil)
|
170
|
-
if color || width || style
|
171
|
-
color = if color == :transparent
|
172
|
-
[]
|
173
|
-
else
|
174
|
-
Content::ColorSpace.device_color_from_specification(color || 0).components
|
175
|
-
end
|
176
|
-
width ||= 1
|
177
|
-
style ||= :solid
|
178
|
-
|
179
|
-
(self[:MK] ||= {})[:BC] = color
|
180
|
-
bs = self[:BS] = {W: width}
|
181
|
-
case style
|
182
|
-
when :solid then bs[:S] = :S
|
183
|
-
when :beveled then bs[:S] = :B
|
184
|
-
when :inset then bs[:S] = :I
|
185
|
-
when :underlined then bs[:S] = :U
|
186
|
-
when Array
|
187
|
-
bs[:S] = :D
|
188
|
-
bs[:D] = style
|
189
|
-
else
|
190
|
-
raise ArgumentError, "Unknown value #{style} for style argument"
|
191
|
-
end
|
192
|
-
self
|
193
|
-
else
|
194
|
-
result = BorderStyle.new(1, nil, :solid, 0, 0)
|
195
|
-
if (ac = self[:MK]) && (bc = ac[:BC]) && !bc.empty?
|
196
|
-
result.color = Content::ColorSpace.prenormalized_device_color(bc.value)
|
197
|
-
end
|
198
|
-
|
199
|
-
if (bs = self[:BS])
|
200
|
-
result.width = bs[:W] if bs.key?(:W)
|
201
|
-
result.style = case bs[:S]
|
202
|
-
when :S then :solid
|
203
|
-
when :B then :beveled
|
204
|
-
when :I then :inset
|
205
|
-
when :U then :underlined
|
206
|
-
when :D then bs[:D].value
|
207
|
-
else :solid
|
208
|
-
end
|
209
|
-
elsif key?(:Border)
|
210
|
-
border = self[:Border]
|
211
|
-
result.horizontal_corner_radius = border[0]
|
212
|
-
result.vertical_corner_radius = border[1]
|
213
|
-
result.width = border[2]
|
214
|
-
result.style = border[3] if border[3]
|
215
|
-
end
|
216
|
-
|
217
|
-
result
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
127
|
# Describes the marker style of a check box or radio button widget.
|
222
128
|
class MarkerStyle
|
223
129
|
|
@@ -48,6 +48,9 @@ module HexaPDF
|
|
48
48
|
autoload(:Text, 'hexapdf/type/annotations/text')
|
49
49
|
autoload(:Link, 'hexapdf/type/annotations/link')
|
50
50
|
autoload(:Widget, 'hexapdf/type/annotations/widget')
|
51
|
+
autoload(:BorderStyling, 'hexapdf/type/annotations/border_styling')
|
52
|
+
autoload(:Line, 'hexapdf/type/annotations/line')
|
53
|
+
autoload(:AppearanceGenerator, 'hexapdf/type/annotations/appearance_generator')
|
51
54
|
|
52
55
|
end
|
53
56
|
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -167,14 +167,14 @@ module HexaPDF
|
|
167
167
|
# bounding box.
|
168
168
|
#
|
169
169
|
# *Note* that a canvas can only be retrieved for initially empty form XObjects!
|
170
|
-
def canvas
|
170
|
+
def canvas(translate: true)
|
171
171
|
cache(:canvas) do
|
172
172
|
unless stream.empty?
|
173
173
|
raise HexaPDF::Error, "Cannot create a canvas for a form XObjects with contents"
|
174
174
|
end
|
175
175
|
|
176
176
|
canvas = Content::Canvas.new(self)
|
177
|
-
if box.left != 0 || box.bottom != 0
|
177
|
+
if translate && (box.left != 0 || box.bottom != 0)
|
178
178
|
canvas.save_graphics_state.translate(box.left, box.bottom)
|
179
179
|
end
|
180
180
|
self.stream = canvas.stream_data
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -150,7 +150,6 @@ module HexaPDF
|
|
150
150
|
|
151
151
|
xref_section = XRefSection.new
|
152
152
|
xref_section.mark_as_initial_section! unless previous_xref_pos
|
153
|
-
xref_section.add_free_entry(0, 65535) if previous_xref_pos.nil?
|
154
153
|
rev.each do |obj|
|
155
154
|
if obj.null?
|
156
155
|
xref_section.add_free_entry(obj.oid, obj.gen)
|