hexapdf 0.10.0 → 0.11.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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +35 -50
  5. data/VERSION +1 -1
  6. data/lib/hexapdf/cli.rb +4 -0
  7. data/lib/hexapdf/cli/command.rb +6 -2
  8. data/lib/hexapdf/cli/image2pdf.rb +141 -0
  9. data/lib/hexapdf/cli/info.rb +1 -1
  10. data/lib/hexapdf/cli/inspect.rb +32 -2
  11. data/lib/hexapdf/cli/modify.rb +1 -1
  12. data/lib/hexapdf/cli/optimize.rb +1 -1
  13. data/lib/hexapdf/cli/watermark.rb +130 -0
  14. data/lib/hexapdf/composer.rb +2 -2
  15. data/lib/hexapdf/configuration.rb +7 -1
  16. data/lib/hexapdf/content/canvas.rb +2 -2
  17. data/lib/hexapdf/content/graphic_object/arc.rb +2 -2
  18. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -2
  19. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  20. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  21. data/lib/hexapdf/dictionary.rb +11 -3
  22. data/lib/hexapdf/dictionary_fields.rb +32 -3
  23. data/lib/hexapdf/document.rb +7 -3
  24. data/lib/hexapdf/document/files.rb +1 -1
  25. data/lib/hexapdf/document/fonts.rb +21 -1
  26. data/lib/hexapdf/document/pages.rb +2 -2
  27. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -2
  28. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  29. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  30. data/lib/hexapdf/font/true_type/table/os2.rb +4 -4
  31. data/lib/hexapdf/font/true_type_wrapper.rb +16 -16
  32. data/lib/hexapdf/font/type1_wrapper.rb +16 -16
  33. data/lib/hexapdf/font_loader.rb +2 -0
  34. data/lib/hexapdf/font_loader/from_configuration.rb +5 -0
  35. data/lib/hexapdf/font_loader/standard14.rb +5 -0
  36. data/lib/hexapdf/image_loader/png.rb +1 -1
  37. data/lib/hexapdf/layout/box.rb +2 -2
  38. data/lib/hexapdf/layout/image_box.rb +1 -1
  39. data/lib/hexapdf/layout/style.rb +50 -24
  40. data/lib/hexapdf/layout/text_box.rb +1 -1
  41. data/lib/hexapdf/layout/text_fragment.rb +2 -2
  42. data/lib/hexapdf/layout/text_layouter.rb +14 -10
  43. data/lib/hexapdf/name_tree_node.rb +3 -3
  44. data/lib/hexapdf/number_tree_node.rb +3 -3
  45. data/lib/hexapdf/pdf_array.rb +207 -0
  46. data/lib/hexapdf/rectangle.rb +12 -12
  47. data/lib/hexapdf/serializer.rb +1 -1
  48. data/lib/hexapdf/stream.rb +6 -4
  49. data/lib/hexapdf/task/optimize.rb +3 -3
  50. data/lib/hexapdf/type.rb +2 -0
  51. data/lib/hexapdf/type/acro_form.rb +51 -0
  52. data/lib/hexapdf/type/acro_form/field.rb +129 -0
  53. data/lib/hexapdf/type/acro_form/form.rb +124 -0
  54. data/lib/hexapdf/type/action.rb +1 -1
  55. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  56. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  57. data/lib/hexapdf/type/actions/launch.rb +1 -1
  58. data/lib/hexapdf/type/annotation.rb +2 -2
  59. data/lib/hexapdf/type/annotations.rb +1 -0
  60. data/lib/hexapdf/type/annotations/link.rb +4 -15
  61. data/lib/hexapdf/type/annotations/markup_annotation.rb +2 -1
  62. data/lib/hexapdf/type/annotations/text.rb +3 -6
  63. data/lib/hexapdf/type/annotations/widget.rb +90 -0
  64. data/lib/hexapdf/type/catalog.rb +12 -9
  65. data/lib/hexapdf/type/cid_font.rb +3 -3
  66. data/lib/hexapdf/type/file_specification.rb +2 -2
  67. data/lib/hexapdf/type/font_descriptor.rb +5 -2
  68. data/lib/hexapdf/type/font_simple.rb +1 -1
  69. data/lib/hexapdf/type/font_type0.rb +1 -1
  70. data/lib/hexapdf/type/font_type3.rb +1 -1
  71. data/lib/hexapdf/type/form.rb +2 -2
  72. data/lib/hexapdf/type/graphics_state_parameter.rb +11 -6
  73. data/lib/hexapdf/type/icon_fit.rb +58 -0
  74. data/lib/hexapdf/type/image.rb +14 -8
  75. data/lib/hexapdf/type/info.rb +2 -1
  76. data/lib/hexapdf/type/page.rb +4 -4
  77. data/lib/hexapdf/type/page_tree_node.rb +3 -7
  78. data/lib/hexapdf/type/resources.rb +1 -1
  79. data/lib/hexapdf/type/trailer.rb +4 -4
  80. data/lib/hexapdf/type/viewer_preferences.rb +7 -4
  81. data/lib/hexapdf/type/xref_stream.rb +2 -2
  82. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  83. data/lib/hexapdf/version.rb +1 -1
  84. data/man/man1/hexapdf.1 +77 -8
  85. data/test/hexapdf/content/test_canvas.rb +2 -2
  86. data/test/hexapdf/content/test_processor.rb +3 -3
  87. data/test/hexapdf/document/test_files.rb +4 -4
  88. data/test/hexapdf/document/test_fonts.rb +13 -1
  89. data/test/hexapdf/document/test_images.rb +6 -6
  90. data/test/hexapdf/document/test_pages.rb +8 -8
  91. data/test/hexapdf/encryption/test_security_handler.rb +7 -7
  92. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -5
  93. data/test/hexapdf/font/test_true_type_wrapper.rb +2 -2
  94. data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
  95. data/test/hexapdf/font_loader/test_standard14.rb +10 -0
  96. data/test/hexapdf/image_loader/test_jpeg.rb +1 -1
  97. data/test/hexapdf/image_loader/test_png.rb +3 -3
  98. data/test/hexapdf/layout/test_box.rb +2 -2
  99. data/test/hexapdf/layout/test_frame.rb +1 -1
  100. data/test/hexapdf/layout/test_image_box.rb +1 -1
  101. data/test/hexapdf/layout/test_style.rb +18 -13
  102. data/test/hexapdf/layout/test_text_box.rb +1 -1
  103. data/test/hexapdf/layout/test_text_layouter.rb +11 -6
  104. data/test/hexapdf/task/test_dereference.rb +2 -2
  105. data/test/hexapdf/task/test_optimize.rb +11 -11
  106. data/test/hexapdf/test_composer.rb +1 -1
  107. data/test/hexapdf/test_dictionary.rb +10 -2
  108. data/test/hexapdf/test_dictionary_fields.rb +27 -3
  109. data/test/hexapdf/test_document.rb +16 -15
  110. data/test/hexapdf/test_importer.rb +4 -4
  111. data/test/hexapdf/test_object.rb +1 -1
  112. data/test/hexapdf/test_pdf_array.rb +162 -0
  113. data/test/hexapdf/test_rectangle.rb +3 -5
  114. data/test/hexapdf/test_serializer.rb +1 -1
  115. data/test/hexapdf/test_stream.rb +1 -0
  116. data/test/hexapdf/test_writer.rb +3 -3
  117. data/test/hexapdf/type/acro_form/test_field.rb +85 -0
  118. data/test/hexapdf/type/acro_form/test_form.rb +69 -0
  119. data/test/hexapdf/type/annotations/test_text.rb +2 -6
  120. data/test/hexapdf/type/annotations/test_widget.rb +24 -0
  121. data/test/hexapdf/type/test_annotation.rb +1 -1
  122. data/test/hexapdf/type/test_catalog.rb +1 -1
  123. data/test/hexapdf/type/test_cid_font.rb +3 -3
  124. data/test/hexapdf/type/test_font.rb +2 -2
  125. data/test/hexapdf/type/test_font_descriptor.rb +2 -1
  126. data/test/hexapdf/type/test_font_simple.rb +3 -3
  127. data/test/hexapdf/type/test_font_true_type.rb +6 -6
  128. data/test/hexapdf/type/test_font_type0.rb +5 -5
  129. data/test/hexapdf/type/test_font_type1.rb +8 -8
  130. data/test/hexapdf/type/test_font_type3.rb +4 -4
  131. data/test/hexapdf/type/test_image.rb +16 -12
  132. data/test/hexapdf/type/test_page.rb +11 -11
  133. data/test/hexapdf/type/test_page_tree_node.rb +20 -20
  134. data/test/hexapdf/type/test_resources.rb +6 -6
  135. data/test/hexapdf/type/test_trailer.rb +5 -2
  136. data/test/hexapdf/type/test_xref_stream.rb +1 -0
  137. data/test/hexapdf/utils/test_sorted_tree_node.rb +35 -35
  138. metadata +23 -7
  139. data/test/hexapdf/type/annotations/test_link.rb +0 -19
@@ -75,10 +75,10 @@ module HexaPDF
75
75
  # used to change the orientation to :landscape if needed.
76
76
  def add(page = nil, orientation: :portrait)
77
77
  if page.kind_of?(Array)
78
- page = @document.add(Type: :Page, MediaBox: page)
78
+ page = @document.add({Type: :Page, MediaBox: page})
79
79
  elsif page.kind_of?(Symbol)
80
80
  box = Type::Page.media_box(page, orientation: orientation)
81
- page = @document.add(Type: :Page, MediaBox: box)
81
+ page = @document.add({Type: :Page, MediaBox: box})
82
82
  end
83
83
  @document.catalog.pages.add_page(page)
84
84
  end
@@ -275,7 +275,7 @@ module HexaPDF
275
275
  dict[:StmF] = dict[:StrF] = :StdCF
276
276
  end
277
277
 
278
- if dict[:R] <= 4 && !document.trailer[:ID].kind_of?(Array)
278
+ if dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
279
279
  document.trailer.set_random_id
280
280
  end
281
281
 
@@ -310,7 +310,7 @@ module HexaPDF
310
310
  elsif ![2, 3, 4, 6].include?(dict[:R])
311
311
  raise(HexaPDF::UnsupportedEncryptionError,
312
312
  "Invalid /R value for standard security handler")
313
- elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(Array)
313
+ elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
314
314
  raise(HexaPDF::EncryptionError,
315
315
  "Document ID for needed for decryption")
316
316
  end
@@ -95,7 +95,7 @@ module HexaPDF
95
95
  when :Supplement
96
96
  cmap.supplement = value if value.kind_of?(Integer)
97
97
  when :CMapName
98
- cmap.name = value.to_s.force_encoding(::Encoding::UTF_8) if value.kind_of?(Symbol)
98
+ cmap.name = value.to_s.dup.force_encoding(::Encoding::UTF_8) if value.kind_of?(Symbol)
99
99
  when :WMode
100
100
  cmap.wmode = value
101
101
  end
@@ -76,8 +76,8 @@ module HexaPDF
76
76
 
77
77
  # Apple Mac style information.
78
78
  attr_accessor :mac_style
79
- bit_field(:mac_style, bold: 0, italic: 1, underline: 2, outline: 3, shadow: 4,
80
- condensed: 5, extended: 6)
79
+ bit_field(:mac_style, {bold: 0, italic: 1, underline: 2, outline: 3, shadow: 4,
80
+ condensed: 5, extended: 6})
81
81
 
82
82
  # The smallest readable size in pixels per em for this font.
83
83
  attr_accessor :smallest_readable_size
@@ -65,8 +65,8 @@ module HexaPDF
65
65
 
66
66
  # Characteristics and properties of this font.
67
67
  attr_accessor :type
68
- bit_field(:type, restricted_license_embedding: 1, preview_and_print_embedding: 2,
69
- editable_embedding: 3, no_subsetting: 8, bitmap_embedding_only: 9)
68
+ bit_field(:type, {restricted_license_embedding: 1, preview_and_print_embedding: 2,
69
+ editable_embedding: 3, no_subsetting: 8, bitmap_embedding_only: 9})
70
70
 
71
71
  # Recommended horizontal size in pixels for subscripts
72
72
  attr_accessor :subscript_x_size
@@ -112,8 +112,8 @@ module HexaPDF
112
112
 
113
113
  # Information concerning the nature of the font patterns.
114
114
  attr_accessor :selection
115
- bit_field(:selection, italic: 0, underscore: 1, negative: 2, outlined: 3, strikeout: 4,
116
- bold: 5, regular: 6, use_typo_metrics: 7, wws: 8, oblique: 9)
115
+ bit_field(:selection, {italic: 0, underscore: 1, negative: 2, outlined: 3, strikeout: 4,
116
+ bold: 5, regular: 6, use_typo_metrics: 7, wws: 8, oblique: 9})
117
117
 
118
118
  # The minimum Unicode index in this font.
119
119
  attr_accessor :first_char_index
@@ -209,15 +209,15 @@ module HexaPDF
209
209
  #
210
210
  # See: #complete_font_dict
211
211
  def build_font_dict
212
- fd = @document.add(Type: :FontDescriptor,
213
- FontName: @wrapped_font.font_name.intern,
214
- FontWeight: @wrapped_font.weight,
215
- Flags: 0,
216
- FontBBox: @wrapped_font.bounding_box.map {|m| m * scaling_factor },
217
- ItalicAngle: @wrapped_font.italic_angle || 0,
218
- Ascent: @wrapped_font.ascender * scaling_factor,
219
- Descent: @wrapped_font.descender * scaling_factor,
220
- StemV: @wrapped_font.dominant_vertical_stem_width)
212
+ fd = @document.add({Type: :FontDescriptor,
213
+ FontName: @wrapped_font.font_name.intern,
214
+ FontWeight: @wrapped_font.weight,
215
+ Flags: 0,
216
+ FontBBox: @wrapped_font.bounding_box.map {|m| m * scaling_factor },
217
+ ItalicAngle: @wrapped_font.italic_angle || 0,
218
+ Ascent: @wrapped_font.ascender * scaling_factor,
219
+ Descent: @wrapped_font.descender * scaling_factor,
220
+ StemV: @wrapped_font.dominant_vertical_stem_width})
221
221
  if @wrapped_font[:'OS/2'].version >= 2
222
222
  fd[:CapHeight] = @wrapped_font.cap_height * scaling_factor
223
223
  fd[:XHeight] = @wrapped_font.x_height * scaling_factor
@@ -242,13 +242,13 @@ module HexaPDF
242
242
  @wrapped_font[:'OS/2'].selection_include?(:oblique)
243
243
  fd.flag(:symbolic)
244
244
 
245
- cid_font = @document.add(Type: :Font, Subtype: :CIDFontType2,
246
- BaseFont: fd[:FontName], FontDescriptor: fd,
247
- CIDSystemInfo: {Registry: "Adobe", Ordering: "Identity",
248
- Supplement: 0},
249
- CIDToGIDMap: :Identity)
250
- @document.add(Type: :Font, Subtype: :Type0, BaseFont: cid_font[:BaseFont],
251
- Encoding: :"Identity-H", DescendantFonts: [cid_font])
245
+ cid_font = @document.add({Type: :Font, Subtype: :CIDFontType2,
246
+ BaseFont: fd[:FontName], FontDescriptor: fd,
247
+ CIDSystemInfo: {Registry: "Adobe", Ordering: "Identity",
248
+ Supplement: 0},
249
+ CIDToGIDMap: :Identity})
250
+ @document.add({Type: :Font, Subtype: :Type0, BaseFont: cid_font[:BaseFont],
251
+ Encoding: :"Identity-H", DescendantFonts: [cid_font]})
252
252
  end
253
253
 
254
254
  # Makes sure that the Type0 font object as well as the CIDFont object contain all the needed
@@ -146,7 +146,7 @@ module HexaPDF
146
146
  def glyph(name)
147
147
  @name_to_glyph[name] ||=
148
148
  begin
149
- str = Encoding::GlyphList.name_to_unicode(name, @zapf_dingbats_opt)
149
+ str = Encoding::GlyphList.name_to_unicode(name, **@zapf_dingbats_opt)
150
150
  if @wrapped_font.metrics.character_metrics.key?(name)
151
151
  Glyph.new(@wrapped_font, name, str)
152
152
  else
@@ -160,7 +160,7 @@ module HexaPDF
160
160
  str.codepoints.map! do |c|
161
161
  @codepoint_to_glyph[c] ||=
162
162
  begin
163
- name = Encoding::GlyphList.unicode_to_name(+'' << c, @zapf_dingbats_opt)
163
+ name = Encoding::GlyphList.unicode_to_name(+'' << c, **@zapf_dingbats_opt)
164
164
  name = +"u" << c.to_s(16).rjust(6, '0') if name == :'.notdef'
165
165
  glyph(name)
166
166
  end
@@ -194,25 +194,25 @@ module HexaPDF
194
194
  # Generic in the sense that no information regarding the encoding or widths is included.
195
195
  def build_font_dict
196
196
  unless defined?(@fd)
197
- @fd = @document.wrap(Type: :FontDescriptor,
198
- FontName: @wrapped_font.font_name.intern,
199
- FontWeight: @wrapped_font.weight_class,
200
- FontBBox: @wrapped_font.bounding_box,
201
- ItalicAngle: @wrapped_font.italic_angle || 0,
202
- Ascent: @wrapped_font.ascender || 0,
203
- Descent: @wrapped_font.descender || 0,
204
- CapHeight: @wrapped_font.cap_height,
205
- XHeight: @wrapped_font.x_height,
206
- StemH: @wrapped_font.dominant_horizontal_stem_width,
207
- StemV: @wrapped_font.dominant_vertical_stem_width || 0)
197
+ @fd = @document.wrap({Type: :FontDescriptor,
198
+ FontName: @wrapped_font.font_name.intern,
199
+ FontWeight: @wrapped_font.weight_class,
200
+ FontBBox: @wrapped_font.bounding_box,
201
+ ItalicAngle: @wrapped_font.italic_angle || 0,
202
+ Ascent: @wrapped_font.ascender || 0,
203
+ Descent: @wrapped_font.descender || 0,
204
+ CapHeight: @wrapped_font.cap_height,
205
+ XHeight: @wrapped_font.x_height,
206
+ StemH: @wrapped_font.dominant_horizontal_stem_width,
207
+ StemV: @wrapped_font.dominant_vertical_stem_width || 0})
208
208
  @fd.flag(:fixed_pitch) if @wrapped_font.metrics.is_fixed_pitch
209
209
  @fd.flag(@wrapped_font.metrics.character_set == 'Special' ? :symbolic : :nonsymbolic)
210
210
  @fd.must_be_indirect = true
211
211
  end
212
212
 
213
- @document.wrap(Type: :Font, Subtype: :Type1,
214
- BaseFont: @wrapped_font.font_name.intern, Encoding: :WinAnsiEncoding,
215
- FontDescriptor: @fd)
213
+ @document.wrap({Type: :Font, Subtype: :Type1,
214
+ BaseFont: @wrapped_font.font_name.intern, Encoding: :WinAnsiEncoding,
215
+ FontDescriptor: @fd})
216
216
  end
217
217
 
218
218
  # Array of valid encoding names in PDF
@@ -60,6 +60,8 @@ module HexaPDF
60
60
  # variant:: The font variant that should be used (e.g. +:none+, +:bold+, +:italic+,
61
61
  # +:bold_italic+).
62
62
  #
63
+ # Optionally, a font loader can provide a method +available_fonts(document)+ that returns a hash
64
+ # where the keys are the font names and the values are the variants of all the provided fonts.
63
65
  #
64
66
  # == Font Wrappers
65
67
  #
@@ -69,6 +69,11 @@ module HexaPDF
69
69
  FromFile.call(document, file, subset: subset)
70
70
  end
71
71
 
72
+ # Returns a hash of the form 'font_name => [variants, ...]' of the configured fonts.
73
+ def self.available_fonts(document)
74
+ document.config['font.map'].transform_values(&:keys)
75
+ end
76
+
72
77
  end
73
78
 
74
79
  end
@@ -95,6 +95,11 @@ module HexaPDF
95
95
  HexaPDF::Font::Type1Wrapper.new(document, font, custom_encoding: custom_encoding)
96
96
  end
97
97
 
98
+ # Returns a hash of the form 'font_name => [variants, ...]' of the standard 14 PDF fonts.
99
+ def self.available_fonts(_document)
100
+ MAPPING.transform_values(&:keys)
101
+ end
102
+
98
103
  end
99
104
 
100
105
  end
@@ -295,7 +295,7 @@ module HexaPDF
295
295
  Columns: dict[:Width],
296
296
  }
297
297
  stream_opts = (from_indexed ? {} : {filter: :FlateDecode, decode_parms: decode_parms})
298
- stream = HexaPDF::StreamData.new(lambda { mask_data }, stream_opts)
298
+ stream = HexaPDF::StreamData.new(lambda { mask_data }, **stream_opts)
299
299
 
300
300
  smask_dict = {
301
301
  Type: :XObject,
@@ -57,7 +57,7 @@ module HexaPDF
57
57
  # If +content_box+ is +true+, the width and height are taken to mean the content width and
58
58
  # height and the style's padding and border are removed from them appropriately.
59
59
  def self.create(width: 0, height: 0, content_box: false, **style, &block)
60
- style = Style.new(style)
60
+ style = Style.new(**style)
61
61
  if content_box
62
62
  width += style.padding.left + style.padding.right +
63
63
  style.border.width.left + style.border.width.right
@@ -96,7 +96,7 @@ module HexaPDF
96
96
  def initialize(width: 0, height: 0, style: Style.new, &block)
97
97
  @width = @initial_width = width
98
98
  @height = @initial_height = height
99
- @style = (style.kind_of?(Style) ? style : Style.new(style))
99
+ @style = (style.kind_of?(Style) ? style : Style.new(**style))
100
100
  @draw_block = block
101
101
  end
102
102
 
@@ -56,7 +56,7 @@ module HexaPDF
56
56
  # Creates a new Image box object for the given +image+ argument which needs to be an image
57
57
  # object (e.g. returned by HexaPDF::Document::Images#add).
58
58
  def initialize(image, **kwargs)
59
- super(kwargs, &:unused_draw_block)
59
+ super(**kwargs, &:unused_draw_block)
60
60
  @image = image
61
61
  end
62
62
 
@@ -523,7 +523,7 @@ module HexaPDF
523
523
  # Example:
524
524
  # Style.new(font_size: 15, align: :center, valign: center)
525
525
  def initialize(**properties)
526
- update(properties)
526
+ update(**properties)
527
527
  @scaled_item_widths = {}
528
528
  end
529
529
 
@@ -624,7 +624,8 @@ module HexaPDF
624
624
  # text_rendering_mode(mode = nil)
625
625
  #
626
626
  # The text rendering mode, i.e. whether text should be filled, stroked, clipped, invisible or
627
- # a combination thereof, defaults to :fill.
627
+ # a combination thereof, defaults to :fill. The returned values is always a normalized text
628
+ # rendering mode value.
628
629
  #
629
630
  # See: HexaPDF::Content::Canvas#text_rendering_mode
630
631
 
@@ -698,7 +699,8 @@ module HexaPDF
698
699
  # :call-seq:
699
700
  # stroke_cap_style(style = nil)
700
701
  #
701
- # The line cap style used for stroking operations (e.g. text outlines), defaults to :butt.
702
+ # The line cap style used for stroking operations (e.g. text outlines), defaults to :butt. The
703
+ # returned values is always a normalized line cap style value.
702
704
  #
703
705
  # See: HexaPDF::Content::Canvas#line_cap_style
704
706
 
@@ -708,6 +710,7 @@ module HexaPDF
708
710
  # stroke_join_style(style = nil)
709
711
  #
710
712
  # The line join style used for stroking operations (e.g. text outlines), defaults to :miter.
713
+ # The returned values is always a normalized line joine style value.
711
714
  #
712
715
  # See: HexaPDF::Content::Canvas#line_join_style
713
716
 
@@ -839,6 +842,8 @@ module HexaPDF
839
842
  # the left/right can still be used. The position hint specifies where the box should
840
843
  # float.
841
844
  #
845
+ # :flow:: Flows the content of the box inside the frame around objects.
846
+ #
842
847
  # :absolute:: Position the box at an absolute position relative to the frame. The coordinates
843
848
  # are given via the position hint.
844
849
 
@@ -877,41 +882,62 @@ module HexaPDF
877
882
  [:horizontal_scaling, 100],
878
883
  [:text_rise, 0],
879
884
  [:font_features, {}],
880
- [:text_rendering_mode, :fill],
881
- [:subscript, false, "value; superscript(false) if superscript"],
882
- [:superscript, false, "value; subscript(false) if subscript"],
883
- [:underline, false],
884
- [:strikeout, false],
885
+ [:text_rendering_mode, "Content::TextRenderingMode::FILL",
886
+ setter: "Content::TextRenderingMode.normalize(value)"],
887
+ [:subscript, false,
888
+ setter: "value; superscript(false) if superscript",
889
+ valid_values: [true, false]],
890
+ [:superscript, false,
891
+ setter: "value; subscript(false) if subscript",
892
+ valid_values: [true, false]],
893
+ [:underline, false, valid_values: [true, false]],
894
+ [:strikeout, false, valid_values: [true, false]],
885
895
  [:fill_color, "default_color"],
886
896
  [:fill_alpha, 1],
887
897
  [:stroke_color, "default_color"],
888
898
  [:stroke_alpha, 1],
889
899
  [:stroke_width, 1],
890
- [:stroke_cap_style, :butt],
891
- [:stroke_join_style, :miter],
900
+ [:stroke_cap_style, "Content::LineCapStyle::BUTT_CAP",
901
+ setter: "Content::LineCapStyle.normalize(value)"],
902
+ [:stroke_join_style, "Content::LineJoinStyle::MITER_JOIN",
903
+ setter: "Content::LineJoinStyle.normalize(value)"],
892
904
  [:stroke_miter_limit, 10.0],
893
905
  [:stroke_dash_pattern, "Content::LineDashPattern.new",
894
- "Content::LineDashPattern.normalize(value, phase)", ", phase = 0"],
895
- [:align, :left],
896
- [:valign, :top],
906
+ setter: "Content::LineDashPattern.normalize(value, phase)", extra_args: ", phase = 0"],
907
+ [:align, :left, valid_values: [:left, :center, :right, :justify]],
908
+ [:valign, :top, valid_values: [:top, :center, :bottom]],
897
909
  [:text_indent, 0],
898
910
  [:line_spacing, "LineSpacing.new(type: :single)",
899
- "LineSpacing.new(value.kind_of?(Symbol) ? {type: value, value: extra_arg} : value)",
900
- ", extra_arg = nil"],
901
- [:last_line_gap, false],
911
+ setter: "LineSpacing.new(**(value.kind_of?(Symbol) ? {type: value, value: extra_arg} : value))",
912
+ extra_args: ", extra_arg = nil"],
913
+ [:last_line_gap, false, valid_values: [true, false]],
902
914
  [:background_color, nil],
903
- [:padding, "Quad.new(0)", "Quad.new(value)"],
904
- [:margin, "Quad.new(0)", "Quad.new(value)"],
905
- [:border, "Border.new", "Border.new(value)"],
906
- [:overlays, "Layers.new", "Layers.new(value)"],
907
- [:underlays, "Layers.new", "Layers.new(value)"],
908
- [:position, :default],
915
+ [:padding, "Quad.new(0)", setter: "Quad.new(value)"],
916
+ [:margin, "Quad.new(0)", setter: "Quad.new(value)"],
917
+ [:border, "Border.new", setter: "Border.new(**value)"],
918
+ [:overlays, "Layers.new", setter: "Layers.new(value)"],
919
+ [:underlays, "Layers.new", setter: "Layers.new(value)"],
920
+ [:position, :default, valid_values: [:default, :float, :flow, :absolute]],
909
921
  [:position_hint, nil],
910
- ].each do |name, default, setter = "value", extra_args = ""|
922
+ ].each do |name, default, options = {}|
911
923
  default = default.inspect unless default.kind_of?(String)
924
+ setter = options.delete(:setter) || "value"
925
+ extra_args = options.delete(:extra_args) || ""
926
+ valid_values = options.delete(:valid_values)
927
+ raise ArgumentError, "Invalid keywords: #{options.keys.join(', ')}" unless options.empty?
928
+ valid_values_const = "#{name}_valid_values".upcase
929
+ const_set(valid_values_const, valid_values)
912
930
  module_eval(<<-EOF, __FILE__, __LINE__ + 1)
913
931
  def #{name}(value = UNSET#{extra_args})
914
- value == UNSET ? (@#{name} ||= #{default}) : (@#{name} = #{setter}; self)
932
+ if value == UNSET
933
+ @#{name} ||= #{default}
934
+ elsif #{valid_values_const} && !#{valid_values_const}.include?(value)
935
+ raise ArgumentError, "\#{value.inspect} is not a valid #{name} value " \\
936
+ "(\#{#{valid_values_const}.map(&:inspect).join(', ')})"
937
+ else
938
+ @#{name} = #{setter}
939
+ self
940
+ end
915
941
  end
916
942
  def #{name}?
917
943
  defined?(@#{name})
@@ -48,7 +48,7 @@ module HexaPDF
48
48
  # Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
49
49
  # objects).
50
50
  def initialize(items, **kwargs)
51
- super(kwargs)
51
+ super(**kwargs)
52
52
  @tl = TextLayouter.new(style)
53
53
  @items = items
54
54
  @result = nil
@@ -65,7 +65,7 @@ module HexaPDF
65
65
  # the +options+ (in which case a new Style object is created). Regardless of the way, the
66
66
  # resulting style object needs at least the font set.
67
67
  def self.create(text, style = nil, **options)
68
- style = (style.nil? ? Style.new(options) : style)
68
+ style = (style.nil? ? Style.new(**options) : style)
69
69
  fragment = new(style.font.decode_utf8(text), style)
70
70
  TextShaper.new.shape_text(fragment)
71
71
  end
@@ -106,7 +106,7 @@ module HexaPDF
106
106
  # The argument +style+ can either be a Style object or a hash of style options.
107
107
  def initialize(items, style)
108
108
  @items = items
109
- @style = (style.kind_of?(Style) ? style : Style.new(style))
109
+ @style = (style.kind_of?(Style) ? style : Style.new(**style))
110
110
  end
111
111
 
112
112
  # The precision used to determine whether two floats represent the same value.
@@ -173,12 +173,6 @@ module HexaPDF
173
173
  :penalty
174
174
  end
175
175
 
176
- # Singleton object describing a Penalty for a mandatory paragraph break.
177
- MandatoryParagraphBreak = new(PARAGRAPH_BREAK)
178
-
179
- # Singleton object describing a Penalty for a mandatory line break.
180
- MandatoryLineBreak = new(LINE_BREAK)
181
-
182
176
  # Singleton object describing a Penalty for a prohibited break.
183
177
  ProhibitedBreak = new(Penalty::INFINITY)
184
178
 
@@ -218,6 +212,7 @@ module HexaPDF
218
212
  def self.call(items)
219
213
  result = []
220
214
  glues = {}
215
+ penalties = {}
221
216
  items.each do |item|
222
217
  if item.kind_of?(InlineBox)
223
218
  result << Box.new(item)
@@ -246,13 +241,20 @@ module HexaPDF
246
241
  Glue.new(TextFragment.new([glyph].freeze, item.style))
247
242
  result << glues[item.style]
248
243
  when "\n", "\v", "\f", "\u{85}", "\u{2029}"
249
- result << Penalty::MandatoryParagraphBreak
244
+ penalties[item.style] ||=
245
+ Penalty.new(Penalty::PARAGRAPH_BREAK, 0,
246
+ item: TextFragment.new([].freeze, item.style))
247
+ result << penalties[item.style]
250
248
  when "\u{2028}"
251
- result << Penalty::MandatoryLineBreak
249
+ result << Penalty.new(Penalty::LINE_BREAK, 0,
250
+ item: TextFragment.new([].freeze, item.style))
252
251
  when "\r"
253
252
  if !item.items[i + 1] || item.items[i + 1].kind_of?(Numeric) ||
254
253
  item.items[i + 1].str != "\n"
255
- result << Penalty::MandatoryParagraphBreak
254
+ penalties[item.style] ||=
255
+ Penalty.new(Penalty::PARAGRAPH_BREAK, 0,
256
+ item: TextFragment.new([].freeze, item.style))
257
+ result << penalties[item.style]
256
258
  end
257
259
  when '-'
258
260
  result << Penalty::Standard
@@ -386,6 +388,7 @@ module HexaPDF
386
388
  end
387
389
  when :penalty
388
390
  if item.penalty <= -Penalty::INFINITY
391
+ add_box_item(item.item) if item.item
389
392
  break unless yield(create_unjustified_line, item)
390
393
  reset_after_line_break(index + 1)
391
394
  elsif item.penalty >= Penalty::INFINITY
@@ -455,6 +458,7 @@ module HexaPDF
455
458
  end
456
459
  when :penalty
457
460
  if item.penalty <= -Penalty::INFINITY
461
+ add_box_item(item.item) if item.item
458
462
  break unless (action = yield(create_unjustified_line, item))
459
463
  reset_after_line_break_variable_width(index + 1, true, action)
460
464
  elsif item.penalty >= Penalty::INFINITY
@@ -646,7 +650,7 @@ module HexaPDF
646
650
  # The +style+ argument can either be a Style object or a hash of style options. See #style for
647
651
  # the properties that are used by the layouter.
648
652
  def initialize(style = Style.new)
649
- @style = (style.kind_of?(Style) ? style : Style.new(style))
653
+ @style = (style.kind_of?(Style) ? style : Style.new(**style))
650
654
  end
651
655
 
652
656
  # :call-seq: