hexapdf 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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: