hexapdf 0.11.7 → 0.12.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +18 -9
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +1 -1
  32. data/lib/hexapdf/cli/inspect.rb +1 -1
  33. data/lib/hexapdf/cli/merge.rb +1 -1
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +66 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +1 -1
  55. data/lib/hexapdf/dictionary_fields.rb +1 -1
  56. data/lib/hexapdf/document.rb +14 -5
  57. data/lib/hexapdf/document/files.rb +1 -1
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +3 -14
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +7 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +1 -1
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +9 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +68 -52
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +1 -1
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +1 -1
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +1 -1
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +2 -2
  147. data/lib/hexapdf/parser.rb +4 -3
  148. data/lib/hexapdf/pdf_array.rb +1 -1
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +1 -1
  153. data/lib/hexapdf/serializer.rb +2 -2
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +1 -1
  158. data/lib/hexapdf/tokenizer.rb +1 -1
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +220 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +157 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +1 -1
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +239 -2
  180. data/lib/hexapdf/type/catalog.rb +21 -1
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +1 -1
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +1 -1
  187. data/lib/hexapdf/type/font_true_type.rb +1 -1
  188. data/lib/hexapdf/type/font_type0.rb +1 -1
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +10 -1
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +3 -1
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +20 -5
  199. data/lib/hexapdf/type/page_tree_node.rb +8 -11
  200. data/lib/hexapdf/type/resources.rb +16 -3
  201. data/lib/hexapdf/type/trailer.rb +2 -3
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/content/common.rb +2 -2
  216. data/test/hexapdf/content/test_color_space.rb +71 -8
  217. data/test/hexapdf/content/test_operator.rb +22 -22
  218. data/test/hexapdf/content/test_parser.rb +14 -0
  219. data/test/hexapdf/document/test_fonts.rb +1 -1
  220. data/test/hexapdf/document/test_pages.rb +6 -6
  221. data/test/hexapdf/encryption/test_security_handler.rb +4 -0
  222. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  223. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  224. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  225. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  226. data/test/hexapdf/layout/test_style.rb +1 -1
  227. data/test/hexapdf/test_document.rb +12 -0
  228. data/test/hexapdf/test_parser.rb +10 -0
  229. data/test/hexapdf/test_rectangle.rb +14 -0
  230. data/test/hexapdf/test_revision.rb +3 -0
  231. data/test/hexapdf/test_serializer.rb +3 -3
  232. data/test/hexapdf/test_writer.rb +2 -2
  233. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
  234. data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
  235. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  236. data/test/hexapdf/type/acro_form/test_field.rb +124 -6
  237. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  238. data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
  239. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  240. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  241. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  242. data/test/hexapdf/type/test_annotation.rb +45 -0
  243. data/test/hexapdf/type/test_catalog.rb +18 -0
  244. data/test/hexapdf/type/test_font.rb +5 -0
  245. data/test/hexapdf/type/test_font_type1.rb +8 -0
  246. data/test/hexapdf/type/test_form.rb +18 -0
  247. data/test/hexapdf/type/test_image.rb +7 -0
  248. data/test/hexapdf/type/test_page.rb +37 -6
  249. data/test/hexapdf/type/test_page_tree_node.rb +20 -12
  250. data/test/hexapdf/type/test_resources.rb +20 -0
  251. data/test/hexapdf/type/test_trailer.rb +4 -0
  252. data/test/hexapdf/utils/test_bit_field.rb +13 -1
  253. data/test/test_helper.rb +1 -1
  254. metadata +38 -18
  255. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -132,7 +132,7 @@ module HexaPDF
132
132
  def self.deep_copy(object)
133
133
  case object
134
134
  when Hash
135
- object.each_with_object({}) {|(key, val), memo| memo[key] = deep_copy(val) }
135
+ object.transform_values {|value| deep_copy(value) }
136
136
  when Array
137
137
  object.map {|o| deep_copy(o) }
138
138
  when HexaPDF::Object
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -281,7 +281,8 @@ module HexaPDF
281
281
  @io.seek(0, IO::SEEK_END)
282
282
  step_size = 1024
283
283
  pos = @io.pos
284
- eof_not_found = startxref_missing = false
284
+ eof_not_found = pos == 0
285
+ startxref_missing = false
285
286
 
286
287
  while pos != 0
287
288
  @io.pos = [pos - step_size, 0].max
@@ -333,7 +334,7 @@ module HexaPDF
333
334
  # See: PDF1.7 s7.5.2, ADB1.7 sH.3-3.4.1
334
335
  def retrieve_pdf_header_offset_and_version
335
336
  @io.seek(0)
336
- @header_offset = @io.read(1024).index(/%PDF-(\d\.\d)/) || 0
337
+ @header_offset = (@io.read(1024) || '').index(/%PDF-(\d\.\d)/) || 0
337
338
  @header_version = $1
338
339
  end
339
340
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -59,31 +59,61 @@ module HexaPDF
59
59
  self[0]
60
60
  end
61
61
 
62
+ # Sets the x-coordinate of the bottom-left corner to the given value.
63
+ def left=(x)
64
+ value[0] = x
65
+ end
66
+
62
67
  # Returns the x-coordinate of the top-right corner.
63
68
  def right
64
69
  self[2]
65
70
  end
66
71
 
72
+ # Sets the x-coordinate of the top-right corner to the given value.
73
+ def right=(x)
74
+ value[2] = x
75
+ end
76
+
67
77
  # Returns the y-coordinate of the bottom-left corner.
68
78
  def bottom
69
79
  self[1]
70
80
  end
71
81
 
82
+ # Sets the y-coordinate of the bottom-left corner to the given value.
83
+ def bottom=(y)
84
+ value[1] = y
85
+ end
86
+
72
87
  # Returns the y-coordinate of the top-right corner.
73
88
  def top
74
89
  self[3]
75
90
  end
76
91
 
92
+ # Sets the y-coordinate of the top-right corner to the given value.
93
+ def top=(y)
94
+ value[3] = y
95
+ end
96
+
77
97
  # Returns the width of the rectangle.
78
98
  def width
79
99
  self[2] - self[0]
80
100
  end
81
101
 
102
+ # Sets the width of the rectangle to the given value.
103
+ def width=(val)
104
+ self[2] = self[0] + val
105
+ end
106
+
82
107
  # Returns the height of the rectangle.
83
108
  def height
84
109
  self[3] - self[1]
85
110
  end
86
111
 
112
+ # Sets the height of the rectangle to the given value.
113
+ def height=(val)
114
+ self[3] = self[1] + val
115
+ end
116
+
87
117
  # Compares this rectangle to +other+ like in Object#== but also allows comparison to simple
88
118
  # arrays if the rectangle is a direct object.
89
119
  def ==(other)
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -172,6 +172,7 @@ module HexaPDF
172
172
 
173
173
  obj = object(ref_or_oid)
174
174
  obj.data.value = nil
175
+ obj.document = nil
175
176
  if mark_as_free
176
177
  add_without_check(HexaPDF::Object.new(nil, oid: obj.oid, gen: obj.gen))
177
178
  else
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -187,7 +187,7 @@ module HexaPDF
187
187
  begin
188
188
  str = obj.to_s.dup.force_encoding(Encoding::BINARY)
189
189
  str.gsub!(NAME_REGEXP, NAME_SUBSTS)
190
- "/#{str}"
190
+ str.empty? ? "/ " : "/#{str}"
191
191
  end
192
192
  end
193
193
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -44,6 +44,12 @@ module HexaPDF
44
44
 
45
45
  autoload(:Form, 'hexapdf/type/acro_form/form')
46
46
  autoload(:Field, 'hexapdf/type/acro_form/field')
47
+ autoload(:VariableTextField, 'hexapdf/type/acro_form/variable_text_field')
48
+ autoload(:TextField, 'hexapdf/type/acro_form/text_field')
49
+ autoload(:ButtonField, 'hexapdf/type/acro_form/button_field')
50
+ autoload(:ChoiceField, 'hexapdf/type/acro_form/choice_field')
51
+
52
+ autoload(:AppearanceGenerator, 'hexapdf/type/acro_form/appearance_generator')
47
53
 
48
54
  end
49
55
 
@@ -0,0 +1,401 @@
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-2020 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/error'
38
+ require 'hexapdf/layout/style'
39
+ require 'hexapdf/layout/text_fragment'
40
+
41
+ module HexaPDF
42
+ module Type
43
+ module AcroForm
44
+
45
+ # The AppearanceGenerator class provides methods for generating and updating the appearance
46
+ # streams of form fields.
47
+ #
48
+ # The only method needed is #create_appearances since this method determines to what field the
49
+ # widget belongs and therefore which appearance should be generated.
50
+ #
51
+ # The visual appearance of a field is constructed using information from the field itself as
52
+ # well as information from the widget. See the documentation for the individual methods which
53
+ # information is used in which way.
54
+ #
55
+ # By default, any existing appearances are overwritten and the +:print+ flag is set on the
56
+ # widget so that the field appearance will appear on print-outs.
57
+ #
58
+ # The visual appearances are chosen to be similar to those used by Adobe Acrobat and others.
59
+ # By subclassing and overriding the necessary methods it is possible to define custom
60
+ # appearances.
61
+ #
62
+ # See: PDF1.7 s12.5.5, s12.7
63
+ class AppearanceGenerator
64
+
65
+ # Creates a new instance for the given +widget+.
66
+ def initialize(widget)
67
+ @widget = widget
68
+ @field = widget.form_field
69
+ @document = widget.document
70
+ end
71
+
72
+ # Creates the appropriate appearances for the widget.
73
+ def create_appearances
74
+ case @field.field_type
75
+ when :Btn
76
+ if @field.check_box?
77
+ create_check_box_appearances
78
+ elsif @field.radio_button?
79
+ create_radio_button_appearances
80
+ else
81
+ raise HexaPDF::Error, "Unsupported button field type"
82
+ end
83
+ when :Tx
84
+ create_text_appearances
85
+ when :Ch
86
+ if @field.combo_box?
87
+ create_text_appearances
88
+ else
89
+ raise HexaPDF::Error, "List box not supported yet"
90
+ end
91
+ else
92
+ raise HexaPDF::Error, "Unsupported field type #{@field.field_type}"
93
+ end
94
+ end
95
+
96
+ # Creates the appropriate appearances for check boxes.
97
+ #
98
+ # For unchecked boxes an empty rectangle is drawn. When checked, a symbol from the
99
+ # ZapfDingbats font is placed inside the rectangle. How this is exactly done depends on the
100
+ # following values:
101
+ #
102
+ # * The widget's rectangle /Rect must be defined. If the height and/or width of the
103
+ # rectangle are zero, they are based on the configuration option
104
+ # +acro_form.default_font_size+ and widget's border width. In such a case the rectangle is
105
+ # appropriately updated.
106
+ #
107
+ # * The line width, style and color of the rectangle are taken from the widget's border
108
+ # style. See HexaPDF::Type::Annotations::Widget#border_style.
109
+ #
110
+ # * The background color is determined by the widget's background color. See
111
+ # HexaPDF::Type::Annotations::Widget#background_color.
112
+ #
113
+ # * The symbol (marker) as well as its size and color are determined by the marker style of
114
+ # the widget. See HexaPDF::Type::Annotations::Widget#marker_style for details.
115
+ #
116
+ # Examples:
117
+ #
118
+ # widget.border_style(color: 0)
119
+ # widget.background_color(1)
120
+ # widget.marker_style(style: :check, size: 0, color: 0)
121
+ # # => default appearance
122
+ #
123
+ # widget.border_style(color: :transparent, width: 2)
124
+ # widget.background_color(0.7)
125
+ # widget.marker_style(style: :cross)
126
+ # # => no visible rectangle, gray background, cross mark when checked
127
+ def create_check_box_appearances
128
+ unless @widget.appearance&.normal_appearance&.value&.size == 2
129
+ raise HexaPDF::Error, "Widget of check box doesn't define name for on state"
130
+ end
131
+ border_style = @widget.border_style
132
+ border_width = border_style.width
133
+
134
+ rect = update_widget(@field[:V], border_width)
135
+
136
+ off_form = @widget.appearance.normal_appearance[:Off] =
137
+ @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
138
+ apply_background_and_border(border_style, off_form.canvas)
139
+
140
+ on_form = @widget.appearance.normal_appearance[@field.check_box_on_name] =
141
+ @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
142
+ canvas = on_form.canvas
143
+ apply_background_and_border(border_style, canvas)
144
+ canvas.save_graphics_state do
145
+ draw_marker(canvas, rect, border_width, @widget.marker_style)
146
+ end
147
+ end
148
+
149
+ # Creates the appropriate appearances for radio buttons.
150
+ #
151
+ # For unselected radio buttons an empty circle (if the marker is :circle) or rectangle is
152
+ # drawn inside the widget annotation's rectangle. When selected, a symbol from the
153
+ # ZapfDingbats font is placed inside. How this is exactly done depends on the following
154
+ # values:
155
+ #
156
+ # * The widget's rectangle /Rect must be defined. If the height and/or width of the
157
+ # rectangle are zero, they are based on the configuration option
158
+ # +acro_form.default_font_size+ and the widget's border width. In such a case the
159
+ # rectangle is appropriately updated.
160
+ #
161
+ # * The line width, style and color of the circle/rectangle are taken from the widget's
162
+ # border style. See HexaPDF::Type::Annotations::Widget#border_style.
163
+ #
164
+ # * The background color is determined by the widget's background color. See
165
+ # HexaPDF::Type::Annotations::Widget#background_color.
166
+ #
167
+ # * The symbol (marker) as well as its size and color are determined by the marker style of
168
+ # the widget. See HexaPDF::Type::Annotations::Widget#marker_style for details.
169
+ #
170
+ # Examples:
171
+ #
172
+ # widget.border_style(color: 0)
173
+ # widget.background_color(1)
174
+ # widget.marker_style(style: :circle, size: 0, color: 0)
175
+ # # => default appearance
176
+ def create_radio_button_appearances
177
+ unless @widget.appearance&.normal_appearance&.value&.size == 2
178
+ raise HexaPDF::Error, "Widget of radio button doesn't define unique name for on state"
179
+ end
180
+
181
+ on_name = (@widget.appearance.normal_appearance.value.keys - [:Off]).first
182
+ border_style = @widget.border_style
183
+ marker_style = @widget.marker_style
184
+
185
+ rect = update_widget(@field[:V] == on_name ? on_name : :Off, border_style.width)
186
+
187
+ off_form = @widget.appearance.normal_appearance[:Off] =
188
+ @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
189
+ apply_background_and_border(border_style, off_form.canvas,
190
+ circular: marker_style.style == :circle)
191
+
192
+ on_form = @widget.appearance.normal_appearance[on_name] =
193
+ @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
194
+ canvas = on_form.canvas
195
+ apply_background_and_border(border_style, canvas,
196
+ circular: marker_style.style == :circle)
197
+ canvas.save_graphics_state do
198
+ draw_marker(canvas, rect, border_style.width, @widget.marker_style)
199
+ end
200
+ end
201
+
202
+ # Creates the appropriate appearances for text fields.
203
+ #
204
+ # The following describes how the appearance is built:
205
+ #
206
+ # * The font, font size and font color are taken from the associated field's default
207
+ # appearance string. See VariableTextField.
208
+ #
209
+ # * The widget's rectangle /Rect must be defined. If the height is zero, it is auto-sized
210
+ # based on the font size. If additionally the font size is zero, a font size of
211
+ # +acro_form.default_font_size+ is used. If the width is zero, the
212
+ # +acro_form.text_field.default_width+ value is used. In such cases the rectangle is
213
+ # appropriately updated.
214
+ #
215
+ # * The line width, style and color of the rectangle are taken from the widget's border
216
+ # style. See HexaPDF::Type::Annotations::Widget#border_style.
217
+ #
218
+ # * The background color is determined by the widget's background color. See
219
+ # HexaPDF::Type::Annotations::Widget#background_color.
220
+ #
221
+ # Note: Multiline, comb and rich text fields are currently not supported!
222
+ def create_text_appearances
223
+ font_name, font_size = @field.parse_default_appearance_string
224
+ default_resources = @document.acro_form.default_resources
225
+ font = default_resources.font(font_name).font_wrapper
226
+ unless font
227
+ fallback_font_name, fallback_font_options = @document.config['acro_form.fallback_font']
228
+ if fallback_font_name
229
+ font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
230
+ else
231
+ raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
232
+ end
233
+ end
234
+ style = HexaPDF::Layout::Style.new(font: font)
235
+ border_style = @widget.border_style
236
+ padding = [1, border_style.width].max
237
+
238
+ @widget[:AS] = :N
239
+ @widget.flag(:print)
240
+ rect = @widget[:Rect]
241
+ rect.width = @document.config['acro_form.text_field.default_width'] if rect.width == 0
242
+ if rect.height == 0
243
+ style.font_size = \
244
+ (font_size == 0 ? @document.config['acro_form.default_font_size'] : font_size)
245
+ rect.height = style.scaled_y_max - style.scaled_y_min + 2 * padding
246
+ end
247
+
248
+ form = (@widget[:AP] ||= {})[:N] = @document.add({Type: :XObject, Subtype: :Form,
249
+ BBox: [0, 0, rect.width, rect.height]})
250
+ form[:Resources] = HexaPDF::Object.deep_copy(default_resources)
251
+
252
+ canvas = form.canvas
253
+ apply_background_and_border(border_style, canvas)
254
+ style.font_size = calculate_font_size(font, font_size, rect, border_style)
255
+
256
+ canvas.marked_content_sequence(:Tx) do
257
+ if (value = @field.field_value)
258
+ canvas.save_graphics_state do
259
+ canvas.rectangle(padding, padding, rect.width - 2 * padding,
260
+ rect.height - 2 * padding).clip_path.end_path
261
+ fragment = HexaPDF::Layout::TextFragment.create(value, style)
262
+ # Adobe seems to be left/right-aligning based on twice the border width and
263
+ # vertically centering based on the cap height, if enough space is available
264
+ x = case @field.text_alignment
265
+ when :left then 2 * padding
266
+ when :right then [rect.width - 2 * padding - fragment.width, 2 * padding].max
267
+ when :center then [(rect.width - fragment.width) / 2.0, 2 * padding].max
268
+ end
269
+ cap_height = font.wrapped_font.cap_height * font.scaling_factor / 1000.0 *
270
+ style.font_size
271
+ y = padding + (rect.height - 2 * padding - cap_height) / 2.0
272
+ y = padding - style.scaled_font_descender if y < 0
273
+ fragment.draw(canvas, x, y)
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ alias create_combo_box_appearances create_text_appearances
280
+
281
+ private
282
+
283
+ # Updates the widget and returns its (possibly modified) rectangle.
284
+ #
285
+ # The following changes are made:
286
+ #
287
+ # * Sets the appearance state to +appearance_state+.
288
+ # * Sets the :print flag.
289
+ # * Adjusts the rectangle based on the default font size and the given border width if its
290
+ # width and/or height are zero.
291
+ def update_widget(appearance_state, border_width)
292
+ @widget[:AS] = appearance_state
293
+ @widget.flag(:print)
294
+
295
+ default_font_size = @document.config['acro_form.default_font_size']
296
+ rect = @widget[:Rect]
297
+ rect.width = default_font_size + 2 * border_width if rect.width == 0
298
+ rect.height = default_font_size + 2 * border_width if rect.height == 0
299
+ rect
300
+ end
301
+
302
+ # Applies the background and border style of the widget annotation to the appearances.
303
+ #
304
+ # If +circular+ is +true+, then the border is drawn as inscribed circle instead of as
305
+ # rectangle.
306
+ def apply_background_and_border(border_style, canvas, circular: false)
307
+ rect = @widget[:Rect]
308
+ background_color = @widget.background_color
309
+
310
+ if (border_style.width > 0 && border_style.color) || background_color
311
+ canvas.save_graphics_state
312
+ if background_color
313
+ canvas.fill_color(background_color)
314
+ if circular
315
+ canvas.circle(rect.width / 2.0, rect.height / 2.0,
316
+ [rect.width / 2.0, rect.height / 2.0].min)
317
+ else
318
+ canvas.rectangle(0, 0, rect.width, rect.height)
319
+ end
320
+ canvas.fill
321
+ end
322
+ if border_style.color
323
+ offset = [0.5, border_style.width / 2.0].max
324
+ width, height = rect.width - 2 * offset, rect.height - 2 * offset
325
+ canvas.stroke_color(border_style.color).line_width(border_style.width)
326
+ if border_style.style == :underlined # TODO: :beveleded, :inset
327
+ if circular
328
+ canvas.arc(rect.width / 2.0, rect.height / 2.0,
329
+ a: [width / 2.0, height / 2.0].min,
330
+ start_angle: 180, end_angle: 0)
331
+ else
332
+ canvas.line(offset, offset, offset + width, offset)
333
+ end
334
+ else
335
+ canvas.line_dash_pattern(border_style.style) if border_style.style.kind_of?(Array)
336
+ if circular
337
+ canvas.circle(rect.width / 2.0, rect.height / 2.0, [width / 2.0, height / 2.0].min)
338
+ else
339
+ canvas.rectangle(offset, offset, width, height)
340
+ end
341
+ end
342
+ canvas.stroke
343
+ end
344
+ canvas.restore_graphics_state
345
+ end
346
+ end
347
+
348
+ # Draws the marker defined by the marker style inside the widget's rectangle.
349
+ #
350
+ # This method can only used for check boxes and radio buttons!
351
+ def draw_marker(canvas, rect, border_width, marker_style)
352
+ if @field.radio_button? && marker_style.style == :circle
353
+ # Acrobat handles this specially
354
+ canvas.
355
+ fill_color(marker_style.color).
356
+ circle(rect.width / 2.0, rect.height / 2.0,
357
+ ([rect.width / 2.0, rect.height / 2.0].min - border_width) / 2).
358
+ fill
359
+ elsif marker_style.style == :cross # Acrobat just places a cross inside
360
+ canvas.
361
+ stroke_color(marker_style.color).
362
+ line(border_width, border_width, rect.width - border_width,
363
+ rect.height - border_width).
364
+ line(border_width, rect.height - border_width, rect.width - border_width,
365
+ border_width).
366
+ stroke
367
+ else
368
+ font = @document.fonts.add('ZapfDingbats')
369
+ mark = font.decode_utf8(@widget[:MK]&.[](:CA) || '4').first
370
+ square_width = [rect.width, rect.height].min - 2 * border_width
371
+ font_size = (marker_style.size == 0 ? square_width : marker_style.size)
372
+ mark_width = mark.width * font.scaling_factor * font_size / 1000.0
373
+ mark_height = (mark.y_max - mark.y_min) * font.scaling_factor * font_size / 1000.0
374
+ x_offset = (rect.width - square_width) / 2.0 + (square_width - mark_width) / 2.0
375
+ y_offset = (rect.height - square_width) / 2.0 + (square_width - mark_height) / 2.0 -
376
+ (mark.y_min * font.scaling_factor * font_size / 1000.0)
377
+
378
+ canvas.font(font, size: font_size)
379
+ canvas.fill_color(marker_style.color)
380
+ canvas.move_text_cursor(offset: [x_offset, y_offset]).show_glyphs_only([mark])
381
+ end
382
+ end
383
+
384
+ # Calculates the font size for text fields based on the font and font size of the default
385
+ # appearance string, the annotation rectangle and the border style.
386
+ def calculate_font_size(font, font_size, rect, border_style)
387
+ if font_size == 0
388
+ unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
389
+ font.scaling_factor / 1000.0
390
+ # The constant factor was found empirically by checking what Adobe Reader etc. do
391
+ (rect.height - 2 * border_style.width) / unit_font_size * 0.83
392
+ else
393
+ font_size
394
+ end
395
+ end
396
+
397
+ end
398
+
399
+ end
400
+ end
401
+ end