hexapdf 1.3.0 → 1.4.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/lib/hexapdf/cli/form.rb +9 -4
  4. data/lib/hexapdf/configuration.rb +10 -0
  5. data/lib/hexapdf/dictionary_fields.rb +1 -1
  6. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -2
  7. data/lib/hexapdf/document/annotations.rb +47 -0
  8. data/lib/hexapdf/document/layout.rb +73 -33
  9. data/lib/hexapdf/document/metadata.rb +10 -3
  10. data/lib/hexapdf/document.rb +9 -0
  11. data/lib/hexapdf/encryption/standard_security_handler.rb +7 -2
  12. data/lib/hexapdf/layout/box.rb +5 -0
  13. data/lib/hexapdf/layout/container_box.rb +63 -28
  14. data/lib/hexapdf/layout/style.rb +28 -13
  15. data/lib/hexapdf/layout/table_box.rb +20 -2
  16. data/lib/hexapdf/type/annotations/appearance_generator.rb +94 -16
  17. data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
  18. data/lib/hexapdf/type/annotations/line.rb +1 -157
  19. data/lib/hexapdf/type/annotations/line_ending_styling.rb +208 -0
  20. data/lib/hexapdf/type/annotations/markup_annotation.rb +0 -1
  21. data/lib/hexapdf/type/annotations/polygon.rb +64 -0
  22. data/lib/hexapdf/type/annotations/polygon_polyline.rb +109 -0
  23. data/lib/hexapdf/type/annotations/polyline.rb +64 -0
  24. data/lib/hexapdf/type/annotations.rb +4 -0
  25. data/lib/hexapdf/type/measure.rb +57 -0
  26. data/lib/hexapdf/type.rb +1 -0
  27. data/lib/hexapdf/version.rb +1 -1
  28. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +0 -1
  29. data/test/hexapdf/document/test_annotations.rb +20 -0
  30. data/test/hexapdf/document/test_layout.rb +16 -10
  31. data/test/hexapdf/document/test_metadata.rb +13 -1
  32. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -1
  33. data/test/hexapdf/layout/test_box.rb +8 -0
  34. data/test/hexapdf/layout/test_container_box.rb +34 -6
  35. data/test/hexapdf/layout/test_page_style.rb +1 -1
  36. data/test/hexapdf/layout/test_style.rb +20 -1
  37. data/test/hexapdf/layout/test_table_box.rb +14 -1
  38. data/test/hexapdf/test_dictionary_fields.rb +1 -0
  39. data/test/hexapdf/type/annotations/test_appearance_generator.rb +126 -0
  40. data/test/hexapdf/type/annotations/test_line.rb +0 -25
  41. data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
  42. data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
  43. metadata +9 -2
@@ -150,14 +150,15 @@ module HexaPDF
150
150
  end
151
151
 
152
152
  # :call-seq:
153
- # quad.set(value)
154
- # quad.set(array)
155
- # quad.set(quad)
153
+ # quad.set(value) -> quad
154
+ # quad.set(array) -> quad
155
+ # quad.set(hash) -> quad
156
+ # quad.set(quad) -> quad
156
157
  #
157
- # Sets all values of the quad.
158
+ # Sets all values of the quad and returns it.
158
159
  #
159
- # * If a single value is provided that is neither a Quad nor an array, it is handled as if
160
- # an array with one value was given.
160
+ # * If a single value is provided that is neither a Quad nor an array nor a hash, it is
161
+ # handled as if an array with one value was given.
161
162
  #
162
163
  # * If a Quad is provided, its values are used.
163
164
  #
@@ -170,6 +171,9 @@ module HexaPDF
170
171
  # third value.
171
172
  # * Four or more values: Top is set to the first, right to the second, bottom to the third
172
173
  # and left to the fourth value.
174
+ #
175
+ # * If a hash is provided, the keys +:top+, +:bottom+, +:left+ and +:right+ are used to set
176
+ # the respective value. All unspecified keys that have not been set before are set to 0.
173
177
  def set(obj)
174
178
  case obj
175
179
  when Quad
@@ -182,9 +186,15 @@ module HexaPDF
182
186
  @bottom = obj[2] || obj[0]
183
187
  @left = obj[3] || obj[1] || obj[0]
184
188
  @right = obj[1] || obj[0]
189
+ when Hash
190
+ @top = obj[:top] || @top || 0
191
+ @bottom = obj[:bottom] || @bottom || 0
192
+ @left = obj[:left] || @left || 0
193
+ @right = obj[:right] || @right || 0
185
194
  else
186
195
  @top = @bottom = @left = @right = obj
187
196
  end
197
+ self
188
198
  end
189
199
 
190
200
  # Returns +true+ if the quad effectively contains only one value.
@@ -398,6 +408,9 @@ module HexaPDF
398
408
  # bottom-left corner of the box during the drawing operations.
399
409
  class Layers
400
410
 
411
+ # The array holding all raw layer definitions.
412
+ attr_reader :layers
413
+
401
414
  # Creates a new Layers object popuplated with the given +layers+.
402
415
  def initialize(layers = nil)
403
416
  @layers = []
@@ -1527,10 +1540,10 @@ module HexaPDF
1527
1540
  [:text_rendering_mode, "Content::TextRenderingMode::FILL",
1528
1541
  {setter: "Content::TextRenderingMode.normalize(value)"}],
1529
1542
  [:subscript, false,
1530
- {setter: "value; superscript(false) if superscript",
1543
+ {setter: "value; superscript(false) if value && superscript? && superscript",
1531
1544
  valid_values: [true, false]}],
1532
1545
  [:superscript, false,
1533
- {setter: "value; subscript(false) if subscript",
1546
+ {setter: "value; subscript(false) if value && subscript? && subscript",
1534
1547
  valid_values: [true, false]}],
1535
1548
  [:underline, false, {valid_values: [true, false]}],
1536
1549
  [:strikeout, false, {valid_values: [true, false]}],
@@ -1557,8 +1570,10 @@ module HexaPDF
1557
1570
  [:fill_horizontal, nil],
1558
1571
  [:background_color, nil],
1559
1572
  [:background_alpha, 1],
1560
- [:padding, "Quad.new(0)", {setter: "Quad.new(value)"}],
1561
- [:margin, "Quad.new(0)", {setter: "Quad.new(value)"}],
1573
+ [:padding, "Quad.new(0)",
1574
+ {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
1575
+ [:margin, "Quad.new(0)",
1576
+ {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
1562
1577
  [:border, "Border.new", {setter: "Border.new(**value)"}],
1563
1578
  [:overlays, "Layers.new", {setter: "Layers.new(value)"}],
1564
1579
  [:underlays, "Layers.new", {setter: "Layers.new(value)"}],
@@ -1641,9 +1656,9 @@ module HexaPDF
1641
1656
 
1642
1657
  # The calculated text rise, taking superscript and subscript into account.
1643
1658
  def calculated_text_rise
1644
- if superscript
1659
+ if superscript? && superscript
1645
1660
  text_rise + font_size * 0.33
1646
- elsif subscript
1661
+ elsif subscript? && subscript
1647
1662
  text_rise - font_size * 0.20
1648
1663
  else
1649
1664
  text_rise
@@ -1652,7 +1667,7 @@ module HexaPDF
1652
1667
 
1653
1668
  # The calculated font size, taking superscript and subscript into account.
1654
1669
  def calculated_font_size
1655
- (superscript || subscript ? 0.583 : 1) * font_size
1670
+ ((superscript? && superscript) || (subscript? && subscript) ? 0.583 : 1) * font_size
1656
1671
  end
1657
1672
 
1658
1673
  # Returns the correct offset from the baseline for the underline.
@@ -126,6 +126,15 @@ module HexaPDF
126
126
  # [layout.text('E'), layout.text('F')]]
127
127
  # composer.column(height: 90) {|col| col.table(cells, header: header, footer: footer) }
128
128
  #
129
+ # While the width of a cell is determined by the #column_widths array, the height is
130
+ # automatically determined during fitting of the content. However, it is also possible to use a
131
+ # fixed height (only if the actual content is smaller or equal than it):
132
+ #
133
+ # #>pdf-composer
134
+ # cells = [[{content: layout.text('A'), height: 5}, layout.text('B')],
135
+ # [{content: layout.text('C'), height: 40}, layout.text('D')]]
136
+ # composer.table(cells)
137
+ #
129
138
  # The cells can be styled using a callable object for more complex styling:
130
139
  #
131
140
  # #>pdf-composer
@@ -191,13 +200,14 @@ module HexaPDF
191
200
  attr_accessor :children
192
201
 
193
202
  # Creates a new Cell instance.
194
- def initialize(row:, column:, children: nil, row_span: nil, col_span: nil, **kwargs)
203
+ def initialize(row:, column:, children: nil, min_height: nil, row_span: nil, col_span: nil, **kwargs)
195
204
  super(**kwargs, width: 0, height: 0)
196
205
  @children = children
197
206
  @row = row
198
207
  @column = column
199
208
  @row_span = row_span || 1
200
209
  @col_span = col_span || 1
210
+ @min_height = min_height
201
211
  style.border.width.set(1) unless style.border?
202
212
  style.border.draw_on_bounds = true
203
213
  style.padding.set(5) unless style.padding?
@@ -257,6 +267,11 @@ module HexaPDF
257
267
  @fit_results = []
258
268
  fit_result.success!
259
269
  end
270
+
271
+ if @min_height && @height < @min_height
272
+ @height = @preferred_height = @min_height
273
+ fit_result.failure! if available_height < @height
274
+ end
260
275
  end
261
276
 
262
277
  # Draws the content of the cell.
@@ -298,6 +313,8 @@ module HexaPDF
298
313
  #
299
314
  # +:col_span+:: An integer specifying the number of columsn this cell should span.
300
315
  #
316
+ # +:min_height+:: A number specifying the minimum height of the table cell.
317
+ #
301
318
  # +:properties+:: A hash of properties (see Box#properties) to be set on the cell itself.
302
319
  #
303
320
  # All other key-value pairs are taken to be cell styling information (like
@@ -513,11 +530,12 @@ module HexaPDF
513
530
  children = content.delete(:content)
514
531
  row_span = content.delete(:row_span)
515
532
  col_span = content.delete(:col_span)
533
+ min_height = content.delete(:min_height)
516
534
  properties = content.delete(:properties)
517
535
  style = content
518
536
  end
519
537
  cell = Cell.new(children: children, row: row_index, column: col_index,
520
- row_span: row_span, col_span: col_span)
538
+ row_span: row_span, col_span: col_span, min_height: min_height)
521
539
  cell_style_block&.call(cell)
522
540
  cell.style.update(**style) if style
523
541
  cell.properties.update(properties) if properties
@@ -75,6 +75,8 @@ module HexaPDF
75
75
  when :Line then create_line_appearance
76
76
  when :Square then create_square_circle_appearance(:square)
77
77
  when :Circle then create_square_circle_appearance(:circle)
78
+ when :Polygon then create_polygon_polyline_appearance(:polygon)
79
+ when :PolyLine then create_polygon_polyline_appearance(:polyline)
78
80
  else
79
81
  raise HexaPDF::Error, "Appearance regeneration for #{@annot[:Subtype]} not yet supported"
80
82
  end
@@ -143,18 +145,8 @@ module HexaPDF
143
145
  # the caption when calculating the bounding box.
144
146
  #
145
147
  # The result could still be improved by tailoring to the specific line ending style.
146
- calculate_le_padding = lambda do |le_style|
147
- case le_style
148
- when :square, :circle, :diamond, :slash, :open_arrow, :closed_arrow
149
- 3 * style.width
150
- when :ropen_arrow, :rclosed_arrow
151
- 10 * style.width
152
- else
153
- 0
154
- end
155
- end
156
- dstart = calculate_le_padding.call(line_ending_style.start_style)
157
- dend = calculate_le_padding.call(line_ending_style.end_style)
148
+ dstart = calculate_line_ending_padding(line_ending_style.start_style, style.width)
149
+ dend = calculate_line_ending_padding(line_ending_style.end_style, style.width)
158
150
  if captioned
159
151
  cap_ulx = x0 + cos_angle * cap_x - sin_angle * cap_y
160
152
  cap_uly = y0 + sin_angle * cap_x + cos_angle * cap_y
@@ -299,6 +291,90 @@ module HexaPDF
299
291
  end
300
292
  end
301
293
 
294
+ # Creates the appropriate appearance for a polygon or polyline annotation depending on the
295
+ # given +type+ (which can either be +:polygon+ or +:polyline+).
296
+ #
297
+ # The cloudy border effect is not supported.
298
+ #
299
+ # See: HexaPDF::Type::Annotations::Polygon, HexaPDF::Type::Annotations::Polyline
300
+ def create_polygon_polyline_appearance(type)
301
+ # Prepare the annotation
302
+ form = (@annot[:AP] ||= {})[:N] ||=
303
+ @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, 0, 0]})
304
+ form.contents = ""
305
+ @annot.flag(:print)
306
+ @annot.unflag(:hidden)
307
+
308
+ # Get all needed values from the annotation
309
+ vertices = @annot.vertices
310
+ border_style = @annot.border_style
311
+ line_ending_style = @annot.line_ending_style
312
+ opacity = @annot.opacity
313
+ interior_color = @annot.interior_color
314
+
315
+ # Calculate the annotation's rectangle as well as the form bounding box
316
+ padding_start = calculate_line_ending_padding(line_ending_style.start_style, border_style.width)
317
+ padding_end = calculate_line_ending_padding(line_ending_style.end_style, border_style.width)
318
+ x_coords, y_coords = vertices.partition.with_index {|_, index| index.even? }
319
+ min_x, max_x = (x_coords + [x_coords[0] + padding_start, x_coords[0] - padding_start,
320
+ x_coords[-1] + padding_end, x_coords[-1] - padding_end]).minmax
321
+ min_y, max_y = (y_coords + [y_coords[0] + padding_start, y_coords[0] - padding_start,
322
+ y_coords[-1] + padding_end, y_coords[-1] - padding_end]).minmax
323
+
324
+ padding = 4 * border_style.width
325
+ rect = [min_x - padding, min_y - padding, max_x + padding, max_y + padding]
326
+ @annot[:Rect] = rect
327
+ form[:BBox] = rect.dup
328
+
329
+ return if vertices.length < 4
330
+
331
+ # Set the appropriate graphics state
332
+ canvas = form.canvas(translate: false)
333
+ canvas.opacity(**opacity.to_h)
334
+ canvas.stroke_color(border_style.color) if border_style.color
335
+ canvas.fill_color(interior_color) if interior_color
336
+ canvas.line_width(border_style.width)
337
+ canvas.line_dash_pattern(border_style.style) if border_style.style.kind_of?(Array)
338
+
339
+ stroke_op = (border_style.color ? :stroke : :end_path)
340
+ fill_op = (border_style.color && interior_color ? :fill_stroke :
341
+ (border_style.color ? :stroke : (interior_color ? :fill : :end_path)))
342
+
343
+ # Draw the polygon/polyline
344
+ canvas.send(type, *vertices)
345
+ canvas.send(type == :polygon ? fill_op : stroke_op)
346
+
347
+ return unless type == :polyline
348
+
349
+ # Draw line endings
350
+ angle_start = Math.atan2(y_coords[1] - y_coords[0], x_coords[1] - x_coords[0])
351
+ angle_end = Math.atan2(y_coords[-2] - y_coords[-1], x_coords[-2] - x_coords[-1])
352
+ if line_ending_style.start_style != :none
353
+ do_fill = draw_line_ending(canvas, line_ending_style.start_style,
354
+ x_coords[0], y_coords[0], border_style.width, angle_start)
355
+ canvas.send(do_fill ? fill_op : stroke_op)
356
+ end
357
+ if line_ending_style.end_style != :none
358
+ do_fill = draw_line_ending(canvas, line_ending_style.end_style,
359
+ x_coords[-1], y_coords[-1], border_style.width, angle_end)
360
+ canvas.send(do_fill ? fill_op : stroke_op)
361
+ end
362
+
363
+ end
364
+
365
+ # Calculates the padding needed around the line endings based on the line ending +style+ and
366
+ # the +border_width+.
367
+ def calculate_line_ending_padding(style, border_width)
368
+ case style
369
+ when :square, :circle, :diamond, :slash, :open_arrow, :closed_arrow
370
+ 3 * border_width
371
+ when :ropen_arrow, :rclosed_arrow
372
+ 10 * border_width
373
+ else
374
+ 0
375
+ end
376
+ end
377
+
302
378
  # Draws the line ending style +type+ at the position (+x+, +y+) and returns +true+ if the
303
379
  # shape needs to be filled.
304
380
  #
@@ -319,11 +395,13 @@ module HexaPDF
319
395
  canvas.polygon(x + lw3, y, x, y + lw3, x - lw3, y, x, y - lw3)
320
396
  true
321
397
  when :open_arrow, :closed_arrow, :ropen_arrow, :rclosed_arrow
322
- arrow_cos = Math.cos(Math::PI / 6 + angle)
323
- arrow_sin = Math.sin(Math::PI / 6 + angle)
398
+ arrow_cos_up = Math.cos(angle + Math::PI / 6)
399
+ arrow_sin_up = Math.sin(angle + Math::PI / 6)
400
+ arrow_cos_down = Math.cos(angle - Math::PI / 6)
401
+ arrow_sin_down = Math.sin(angle - Math::PI / 6)
324
402
  dir = (type == :ropen_arrow || type == :rclosed_arrow ? -1 : 1)
325
- canvas.polyline(x + dir * arrow_cos * 3 * lw3, y + arrow_sin * 3 * lw3, x, y,
326
- x + dir * arrow_cos * 3 * lw3, y - arrow_sin * 3 * lw3)
403
+ canvas.polyline(x + dir * arrow_cos_up * 3 * lw3, y + arrow_sin_up * 3 * lw3, x, y,
404
+ x + dir * arrow_cos_down * 3 * lw3, y + arrow_sin_down * 3 * lw3)
327
405
  if type == :closed_arrow || type == :rclosed_arrow
328
406
  canvas.close_subpath
329
407
  true
@@ -61,7 +61,7 @@ module HexaPDF
61
61
  # the allowed arguments.
62
62
  #
63
63
  # If the special value +:transparent+ is used when setting the color, no color is
64
- # used for filling the line endings.
64
+ # used for filling.
65
65
  def interior_color(*color)
66
66
  if color.empty?
67
67
  color = self[:IC]
@@ -71,6 +71,7 @@ module HexaPDF
71
71
 
72
72
  include BorderStyling
73
73
  include InteriorColor
74
+ include LineEndingStyling
74
75
 
75
76
  define_field :Subtype, type: Symbol, required: true, default: :Line
76
77
  define_field :L, type: PDFArray, required: true
@@ -116,163 +117,6 @@ module HexaPDF
116
117
  end
117
118
  end
118
119
 
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
120
  # :call-seq:
277
121
  # line.leader_line_length => leader_line_length
278
122
  # line.leader_line_length(length) => line