hexapdf 1.1.1 → 1.3.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/README.md +1 -1
  4. data/lib/hexapdf/cli/command.rb +63 -63
  5. data/lib/hexapdf/cli/inspect.rb +14 -5
  6. data/lib/hexapdf/cli/modify.rb +0 -1
  7. data/lib/hexapdf/cli/optimize.rb +5 -5
  8. data/lib/hexapdf/composer.rb +14 -0
  9. data/lib/hexapdf/configuration.rb +26 -0
  10. data/lib/hexapdf/content/graphics_state.rb +1 -1
  11. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
  12. data/lib/hexapdf/document/annotations.rb +173 -0
  13. data/lib/hexapdf/document/layout.rb +45 -6
  14. data/lib/hexapdf/document.rb +28 -7
  15. data/lib/hexapdf/error.rb +11 -3
  16. data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
  17. data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
  18. data/lib/hexapdf/font/type1_wrapper.rb +1 -0
  19. data/lib/hexapdf/layout/style.rb +101 -7
  20. data/lib/hexapdf/object.rb +2 -2
  21. data/lib/hexapdf/pdf_array.rb +25 -3
  22. data/lib/hexapdf/tokenizer.rb +4 -1
  23. data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
  24. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  25. data/lib/hexapdf/type/acro_form/form.rb +7 -6
  26. data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
  27. data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
  28. data/lib/hexapdf/type/annotation.rb +71 -1
  29. data/lib/hexapdf/type/annotations/appearance_generator.rb +348 -0
  30. data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
  31. data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
  32. data/lib/hexapdf/type/annotations/circle.rb +65 -0
  33. data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
  34. data/lib/hexapdf/type/annotations/line.rb +490 -0
  35. data/lib/hexapdf/type/annotations/square.rb +65 -0
  36. data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
  37. data/lib/hexapdf/type/annotations/widget.rb +52 -116
  38. data/lib/hexapdf/type/annotations.rb +8 -0
  39. data/lib/hexapdf/type/form.rb +2 -2
  40. data/lib/hexapdf/version.rb +1 -1
  41. data/lib/hexapdf/writer.rb +0 -1
  42. data/lib/hexapdf/xref_section.rb +7 -4
  43. data/test/hexapdf/content/test_graphics_state.rb +2 -3
  44. data/test/hexapdf/content/test_operator.rb +4 -5
  45. data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
  46. data/test/hexapdf/digital_signature/test_handler.rb +2 -3
  47. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
  48. data/test/hexapdf/document/test_annotations.rb +55 -0
  49. data/test/hexapdf/document/test_layout.rb +24 -2
  50. data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
  51. data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
  52. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  53. data/test/hexapdf/layout/test_style.rb +27 -2
  54. data/test/hexapdf/task/test_optimize.rb +1 -1
  55. data/test/hexapdf/test_composer.rb +7 -0
  56. data/test/hexapdf/test_document.rb +11 -3
  57. data/test/hexapdf/test_object.rb +1 -1
  58. data/test/hexapdf/test_pdf_array.rb +36 -3
  59. data/test/hexapdf/test_stream.rb +1 -2
  60. data/test/hexapdf/test_xref_section.rb +1 -1
  61. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
  62. data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
  63. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  64. data/test/hexapdf/type/acro_form/test_form.rb +17 -1
  65. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
  66. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  67. data/test/hexapdf/type/annotations/test_appearance_generator.rb +482 -0
  68. data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
  69. data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
  70. data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
  71. data/test/hexapdf/type/annotations/test_line.rb +169 -0
  72. data/test/hexapdf/type/annotations/test_widget.rb +35 -81
  73. data/test/hexapdf/type/test_annotation.rb +55 -0
  74. data/test/hexapdf/type/test_form.rb +6 -0
  75. metadata +17 -2
@@ -0,0 +1,490 @@
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 and those from the included
47
+ # modules.
48
+ #
49
+ # Note that while BorderStyling#border_style 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
+ include InteriorColor
74
+
75
+ define_field :Subtype, type: Symbol, required: true, default: :Line
76
+ define_field :L, type: PDFArray, required: true
77
+ define_field :BS, type: :Border
78
+ define_field :LE, type: PDFArray, default: [:None, :None], version: '1.4'
79
+ define_field :IC, type: PDFArray, version: '1.4'
80
+ define_field :LL, type: Numeric, default: 0, version: '1.6'
81
+ define_field :LLE, type: Numeric, default: 0, version: '1.6'
82
+ define_field :Cap, type: Boolean, default: false, version: '1.6'
83
+ define_field :IT, type: Symbol, version: '1.6',
84
+ allowed_values: [:LineArrow, :LineDimension]
85
+ define_field :LLO, type: Numeric, version: '1.7'
86
+ define_field :CP, type: Symbol, default: :Inline, version: '1.7',
87
+ allowed_values: [:Inline, :Top]
88
+ define_field :Measure, type: Dictionary, version: '1.7'
89
+ define_field :CO, type: PDFArray, default: [0, 0], version: '1.7'
90
+
91
+ # :call-seq:
92
+ # line.line => [x0, y0, x1, y1]
93
+ # line.line(x0, y0, x1, y1) => line
94
+ #
95
+ # Returns the start point and end point of the line as an array of four numbers [x0, y0, x1,
96
+ # y1] when no argument is given. Otherwise sets the start and end point of the line and
97
+ # returns self.
98
+ #
99
+ # This is the only required setting for a line annotation. Note, however, that without
100
+ # setting an appropriate color through #border_style the line will be transparent.
101
+ #
102
+ # Example:
103
+ #
104
+ # #>pdf-small
105
+ # doc.annotations.
106
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
107
+ # regenerate_appearance
108
+ def line(x0 = nil, y0 = nil, x1 = nil, y1 = nil)
109
+ if x0.nil? && y0.nil? && x1.nil? && y1.nil?
110
+ self[:L].to_ary
111
+ elsif !x0 || !y0 || !x1 || !y1
112
+ raise ArgumentError, "All four arguments x0, y0, x1, y1 must be provided"
113
+ else
114
+ self[:L] = [x0, y0, x1, y1]
115
+ self
116
+ end
117
+ end
118
+
119
+ # Maps HexaPDF names to PDF names.
120
+ LINE_ENDING_STYLE_MAP = { # :nodoc:
121
+ Square: :Square, square: :Square,
122
+ Circle: :Circle, circle: :Circle,
123
+ Diamond: :Diamond, diamond: :Diamond,
124
+ OpenArrow: :OpenArrow, open_arrow: :OpenArrow,
125
+ ClosedArrow: :ClosedArrow, closed_arrow: :ClosedArrow,
126
+ None: :None, none: :None,
127
+ Butt: :Butt, butt: :Butt,
128
+ ROpenArrow: :ROpenArrow, ropen_arrow: :ROpenArrow,
129
+ RClosedArrow: :RClosedArrow, rclosed_arrow: :RClosedArrow,
130
+ Slash: :Slash, slash: :Slash,
131
+ }.freeze
132
+ LINE_ENDING_STYLE_REVERSE_MAP = LINE_ENDING_STYLE_MAP.invert # :nodoc:
133
+
134
+
135
+ # Describes the line ending style for a line annotation, i.e. the +start_style+ and the
136
+ # +end_style+.
137
+ #
138
+ # See Line#line_ending_style for more information.
139
+ LineEndingStyle = Struct.new(:start_style, :end_style)
140
+
141
+ # :call-seq:
142
+ # line.line_ending_style => style
143
+ # line.line_ending_style(start_style: :none, end_style: :none) => line
144
+ #
145
+ # Returns a LineEndingStyle instance holding the current line ending styles when no argument
146
+ # is given. Otherwise sets the line ending style of the line and returns self.
147
+ #
148
+ # When returning the styles, unknown line ending styles are mapped to :none.
149
+ #
150
+ # When setting the line ending style, arguments that are not provided will use the currently
151
+ # defined value or fall back to the default of +:none+.
152
+ #
153
+ # Possible line ending styles (the first one is the HexaPDF name, the second the PDF name):
154
+ #
155
+ # :square or :Square::
156
+ # A square filled with the annotation's interior colour, if any.
157
+ #
158
+ # #>pdf-small-hide
159
+ # doc.annotations.
160
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
161
+ # interior_color("hp-orange").
162
+ # line_ending_style(end_style: :square).
163
+ # regenerate_appearance
164
+ #
165
+ # :circle or :Circle::
166
+ # A circle filled with the annotation’s interior colour, if any.
167
+ #
168
+ # #>pdf-small-hide
169
+ # doc.annotations.
170
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
171
+ # interior_color("hp-orange").
172
+ # line_ending_style(end_style: :circle).
173
+ # regenerate_appearance
174
+ #
175
+ # :diamond or :Diamond::
176
+ # A diamond shape filled with the annotation’s interior colour, if any.
177
+ #
178
+ # #>pdf-small-hide
179
+ # doc.annotations.
180
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
181
+ # interior_color("hp-orange").
182
+ # line_ending_style(end_style: :diamond).
183
+ # regenerate_appearance
184
+ #
185
+ # :open_arrow or :OpenArrow::
186
+ # Two short lines meeting in an acute angle to form an open arrowhead.
187
+ #
188
+ # #>pdf-small-hide
189
+ # doc.annotations.
190
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
191
+ # interior_color("hp-orange").
192
+ # line_ending_style(end_style: :open_arrow).
193
+ # regenerate_appearance
194
+ #
195
+ # :closed_arrow or :ClosedArrow::
196
+ # Two short lines meeting in an acute angle as in the +:open_arrow+ style and connected
197
+ # by a third line to form a triangular closed arrowhead filled with the annotation’s
198
+ # interior colour, if any.
199
+ #
200
+ # #>pdf-small-hide
201
+ # doc.annotations.
202
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
203
+ # interior_color("hp-orange").
204
+ # line_ending_style(end_style: :closed_arrow).
205
+ # regenerate_appearance
206
+ #
207
+ # :none or :None::
208
+ # No line ending.
209
+ #
210
+ # #>pdf-small-hide
211
+ # doc.annotations.
212
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
213
+ # interior_color("hp-orange").
214
+ # line_ending_style(end_style: :none).
215
+ # regenerate_appearance
216
+ #
217
+ # :butt or :Butt::
218
+ # A short line at the endpoint perpendicular to the line itself.
219
+ #
220
+ # #>pdf-small-hide
221
+ # doc.annotations.
222
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
223
+ # interior_color("hp-orange").
224
+ # line_ending_style(end_style: :butt).
225
+ # regenerate_appearance
226
+ #
227
+ # :ropen_arrow or :ROpenArrow::
228
+ # Two short lines in the reverse direction from +:open_arrow+.
229
+ #
230
+ # #>pdf-small-hide
231
+ # doc.annotations.
232
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
233
+ # interior_color("hp-orange").
234
+ # line_ending_style(end_style: :ropen_arrow).
235
+ # regenerate_appearance
236
+ #
237
+ # :rclosed_arrow or :RClosedArrow::
238
+ # A triangular closed arrowhead in the reverse direction from +:closed_arrow+.
239
+ #
240
+ # #>pdf-small-hide
241
+ # doc.annotations.
242
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
243
+ # interior_color("hp-orange").
244
+ # line_ending_style(end_style: :rclosed_arrow).
245
+ # regenerate_appearance
246
+ #
247
+ # :slash or :Slash::
248
+ # A short line at the endpoint approximately 30 degrees clockwise from perpendicular to
249
+ # the line itself.
250
+ #
251
+ # #>pdf-small-hide
252
+ # doc.annotations.
253
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
254
+ # interior_color("hp-orange").
255
+ # line_ending_style(end_style: :slash).
256
+ # regenerate_appearance
257
+ def line_ending_style(start_style: :UNSET, end_style: :UNSET)
258
+ if start_style == :UNSET && end_style == :UNSET
259
+ le = self[:LE]
260
+ LineEndingStyle.new(LINE_ENDING_STYLE_REVERSE_MAP.fetch(le[0], :none),
261
+ LINE_ENDING_STYLE_REVERSE_MAP.fetch(le[1], :none))
262
+ else
263
+ start_style = self[:LE][0] if start_style == :UNSET
264
+ end_style = self[:LE][1] if end_style == :UNSET
265
+ start_style = LINE_ENDING_STYLE_MAP.fetch(start_style) do
266
+ raise ArgumentError, "Invalid line ending style: #{start_style.inspect}"
267
+ end
268
+ end_style = LINE_ENDING_STYLE_MAP.fetch(end_style) do
269
+ raise ArgumentError, "Invalid line ending style: #{end_style.inspect}"
270
+ end
271
+ self[:LE] = [start_style, end_style]
272
+ self
273
+ end
274
+ end
275
+
276
+ # :call-seq:
277
+ # line.leader_line_length => leader_line_length
278
+ # line.leader_line_length(length) => line
279
+ #
280
+ # Returns the leader line length when no argument is given. Otherwise sets the leader line
281
+ # length and returns self.
282
+ #
283
+ # Leader lines extend from the line's end points perpendicular to the line. If the length
284
+ # value is positive, the leader lines appear in the clockwise direction, otherwise in the
285
+ # opposite direction.
286
+ #
287
+ # Note: The "line's end points" mean the actually drawn line and not the one specified with
288
+ # #line as those two are different when leader lines are involved.
289
+ #
290
+ # A value of zero means that no leader lines are used.
291
+ #
292
+ # Example:
293
+ #
294
+ # #>pdf-small
295
+ # doc.annotations.
296
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
297
+ # leader_line_length(15).
298
+ # regenerate_appearance
299
+ # canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
300
+ #
301
+ # Also see: #leader_line_extension_length, #leader_line_offset
302
+ def leader_line_length(length = nil)
303
+ length ? (self[:LL] = length; self) : self[:LL]
304
+ end
305
+
306
+ # :call-seq:
307
+ # line.leader_line_extension_length => leader_line_extension_length
308
+ # line.leader_line_extension_length(length) => line
309
+ #
310
+ # Returns the leader line extension length when no argument is given. Otherwise sets the
311
+ # leader line extension length and returns self.
312
+ #
313
+ # Leader line extensions extend from the line into the opposite direction of the leader
314
+ # lines.
315
+ #
316
+ # The argument +length+ must be non-negative.
317
+ #
318
+ # If the leader line extension length is set to a positive value, the leader line length
319
+ # also needs to be specified.
320
+ #
321
+ # Example:
322
+ #
323
+ # #>pdf-small
324
+ # doc.annotations.
325
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
326
+ # leader_line_length(15).
327
+ # leader_line_extension_length(5).
328
+ # regenerate_appearance
329
+ # canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
330
+ #
331
+ # Also see: #leader_line_length, #leader_line_offset
332
+ def leader_line_extension_length(length = nil)
333
+ if length
334
+ raise ArgumentError, "length must be non-negative" if length < 0
335
+ self[:LLE] = length
336
+ self
337
+ else
338
+ self[:LLE]
339
+ end
340
+ end
341
+
342
+ # :call-seq:
343
+ # line.leader_line_offset => leader_line_offset
344
+ # line.leader_line_offset(number) => line
345
+ #
346
+ # Returns the leader line offset when no argument is given. Otherwise sets the leader line
347
+ # offset and returns self.
348
+ #
349
+ # The leader line offset is a non-negative number that describes the offset of the leader
350
+ # lines from the endpoints of the line.
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_offset(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_extension_length
363
+ def leader_line_offset(offset = nil)
364
+ offset ? (self[:LLO] = offset; self) : self[:LLO] || 0
365
+ end
366
+
367
+ # :call-seq:
368
+ # line.captioned => true or false
369
+ # line.captioned(value) => line
370
+ #
371
+ # Returns +true+ (if the line has a visible caption) or +false+ (no visible caption) when no
372
+ # argument is given. Otherwise sets whether a caption should be visible and returns self.
373
+ #
374
+ # If a caption should be shown, the text specified by the /Contents or /RC entries is shown
375
+ # in the appearance of the line.
376
+ #
377
+ # Example:
378
+ #
379
+ # #>pdf-small-hide
380
+ # doc.annotations.
381
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
382
+ # contents("Inline text").
383
+ # captioned(true).
384
+ # regenerate_appearance
385
+ # Also see: #caption_position, #caption_offset
386
+ def captioned(value = nil)
387
+ value ? (self[:Cap] = value; self) : self[:Cap]
388
+ end
389
+
390
+ # Maps HexaPDF names to PDF names.
391
+ CAPTION_POSITION_MAP = { # :nodoc:
392
+ Inline: :Inline, inline: :Inline,
393
+ Top: :Top, top: :Top,
394
+ }.freeze
395
+ CAPTION_POSITION_REVERSE_MAP = CAPTION_POSITION_MAP.invert # :nodoc:
396
+
397
+ # :call-seq:
398
+ # line.caption_position => caption_position
399
+ # line.caption_position(value) => line
400
+ #
401
+ # Returns the caption position when no argument is given. Otherwise sets the caption
402
+ # position and returns self.
403
+ #
404
+ # Possible caption positions are (the first one is the HexaPDF name, the second the PDF
405
+ # name):
406
+ #
407
+ # :inline or :Inline::
408
+ # The caption is centered inside the line (default).
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
+ # caption_position(:inline).
416
+ # regenerate_appearance
417
+ #
418
+ # :top or :Top::
419
+ # The caption is on the top of the line.
420
+ #
421
+ # #>pdf-small-hide
422
+ # doc.annotations.
423
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
424
+ # contents("Top text").
425
+ # captioned(true).
426
+ # caption_position(:top).
427
+ # regenerate_appearance
428
+ #
429
+ # Also see: #captioned, #caption_offset
430
+ def caption_position(value = nil)
431
+ if value
432
+ value = CAPTION_POSITION_MAP.fetch(value) do
433
+ raise ArgumentError, "Invalid caption position: #{value.inspect}"
434
+ end
435
+ self[:CP] = value
436
+ self
437
+ else
438
+ CAPTION_POSITION_REVERSE_MAP[self[:CP]]
439
+ end
440
+ end
441
+
442
+ # :call-seq:
443
+ # line.caption_offset => caption_offset
444
+ # line.caption_offset(x, y) => line
445
+ #
446
+ # Returns the caption offset when no argument is given. Otherwise sets the caption offset
447
+ # and returns self.
448
+ #
449
+ # The caption offset is an array of two numbers that specify the horizontal and vertical
450
+ # offsets of the caption from its normal position. A positive horizontal offset means moving
451
+ # the caption to the right. A positive vertical offset means shifting the caption up.
452
+ #
453
+ # Example:
454
+ #
455
+ # #>pdf-small-hide
456
+ # doc.annotations.
457
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
458
+ # contents("Top text").
459
+ # captioned(true).
460
+ # caption_position(:top).
461
+ # caption_offset(20, 10).
462
+ # regenerate_appearance
463
+ #
464
+ # Also see: #captioned, #caption_position
465
+ def caption_offset(x = nil, y = nil)
466
+ x || y ? (self[:CO] = [x || 0, y || 0]; self) : self[:CO].to_ary
467
+ end
468
+
469
+ private
470
+
471
+ def perform_validation #:nodoc:
472
+ super
473
+ if self[:LLE] < 0
474
+ yield('/LLE must be a non-negative number', true)
475
+ self[:LLE] = -self[:LLE]
476
+ end
477
+ if key?(:LLO) && self[:LLO] < 0
478
+ yield('/LLO must be a non-negative number', true)
479
+ self[:LLO] = -self[:LLO]
480
+ end
481
+ if self[:LLE] > 0 && self[:LL] == 0
482
+ yield("/LL required to be non-zero if /LLE is set")
483
+ end
484
+ end
485
+
486
+ end
487
+
488
+ end
489
+ end
490
+ end
@@ -0,0 +1,65 @@
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/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # A square annotation displays a rectangle inside the annotation rectangle (the "square" name
44
+ # defined by the PDF specification is a bit misleading).
45
+ #
46
+ # Also see SquareCircle for more information.
47
+ #
48
+ # Example:
49
+ #
50
+ # #>pdf-small
51
+ # doc.annotations.create_rectangle(doc.pages[0], 20, 30, 60, 40).
52
+ # border_style(color: "hp-blue", width: 2, style: [3, 1]).
53
+ # interior_color("hp-orange").
54
+ # regenerate_appearance
55
+ #
56
+ # See: PDF2.0 s12.5.6.8, HexaPDF::Type::Annotations::SquareCircle,
57
+ class Square < SquareCircle
58
+
59
+ define_field :Subtype, type: Symbol, required: true, default: :Square
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,77 @@
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/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # This is the base class for the square and circle markup annotations which display a
44
+ # rectangle or ellipse inside the annotation rectangle.
45
+ #
46
+ # The styling is done through methods included by various modules:
47
+ #
48
+ # * Changing the line width, line dash pattern and color is done using the method
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 square and circle
51
+ # annotations.
52
+ #
53
+ # * The interior color can be changed through InteriorColor#interior_color.
54
+ #
55
+ # * The border effect can be changed through BorderEffect#border_effect. Note that cloudy
56
+ # borders are not supported.
57
+ #
58
+ # See: PDF2.0 s12.5.6.8, HexaPDF::Type::Annotations::Square,
59
+ # HexaPDF::Type::Annotations::Circle, HexaPDF::Type::MarkupAnnotation
60
+ class SquareCircle < MarkupAnnotation
61
+
62
+ include BorderStyling
63
+ include BorderEffect
64
+ include InteriorColor
65
+
66
+ # Field Subtype is defined in the two subclasses
67
+ define_field :BS, type: :Border
68
+ define_field :IC, type: PDFArray, version: '1.4'
69
+ define_field :BE, type: :XXBorderEffect, version: '1.5'
70
+ # Array instead of Rectangle, see https://github.com/pdf-association/pdf-issues/issues/524
71
+ define_field :RD, type: PDFArray, version: '1.5'
72
+
73
+ end
74
+
75
+ end
76
+ end
77
+ end