hexapdf 0.11.8 → 0.12.3

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 +126 -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 +1 -1
  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 +5 -4
  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 +405 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +305 -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/common_tokenizer_tests.rb +5 -0
  216. data/test/hexapdf/content/common.rb +2 -2
  217. data/test/hexapdf/content/test_color_space.rb +71 -8
  218. data/test/hexapdf/content/test_operator.rb +22 -22
  219. data/test/hexapdf/content/test_parser.rb +14 -0
  220. data/test/hexapdf/document/test_fonts.rb +1 -1
  221. data/test/hexapdf/document/test_pages.rb +6 -6
  222. data/test/hexapdf/encryption/test_security_handler.rb +4 -0
  223. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  224. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  225. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  226. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  227. data/test/hexapdf/layout/test_style.rb +1 -1
  228. data/test/hexapdf/test_document.rb +12 -0
  229. data/test/hexapdf/test_parser.rb +10 -0
  230. data/test/hexapdf/test_rectangle.rb +14 -0
  231. data/test/hexapdf/test_revision.rb +3 -0
  232. data/test/hexapdf/test_writer.rb +2 -2
  233. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +522 -0
  234. data/test/hexapdf/type/acro_form/test_button_field.rb +281 -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
@@ -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
@@ -249,17 +249,18 @@ module HexaPDF
249
249
  #
250
250
  # See: PDF1.7 s7.3.3
251
251
  def parse_number
252
- if (val = @ss.scan(/[+-]?\d++(?!\.)/))
252
+ val = scan_until(WHITESPACE_OR_DELIMITER_RE) || @ss.scan(/.*/)
253
+ if val.match?(/\A[+-]?\d++(?!\.)\z/)
253
254
  tmp = val.to_i
254
255
  # Handle object references, see PDF1.7 s7.3.10
255
256
  prepare_string_scanner(10)
256
257
  tmp = Reference.new(tmp, @ss[1].to_i) if @ss.scan(REFERENCE_RE)
257
258
  tmp
258
- elsif (val = @ss.scan(/[+-]?(?:\d+\.\d*|\.\d+)/))
259
+ elsif val.match?(/\A[+-]?(?:\d+\.\d*|\.\d+)\z/)
259
260
  val << '0' if val.getbyte(-1) == 46 # dot '.'
260
261
  Float(val)
261
262
  else
262
- parse_keyword
263
+ TOKEN_CACHE[val] # val is keyword
263
264
  end
264
265
  end
265
266
 
@@ -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,405 @@
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
+ # If the font is not usable by HexaPDF (which may be due to a variety of reasons, e.g. no
210
+ # associated information in the form's default resources), the font specified by the
211
+ # configuration option +acro_form.fallback_font+ will be used.
212
+ #
213
+ # * The widget's rectangle /Rect must be defined. If the height is zero, it is auto-sized
214
+ # based on the font size. If additionally the font size is zero, a font size of
215
+ # +acro_form.default_font_size+ is used. If the width is zero, the
216
+ # +acro_form.text_field.default_width+ value is used. In such cases the rectangle is
217
+ # appropriately updated.
218
+ #
219
+ # * The line width, style and color of the rectangle are taken from the widget's border
220
+ # style. See HexaPDF::Type::Annotations::Widget#border_style.
221
+ #
222
+ # * The background color is determined by the widget's background color. See
223
+ # HexaPDF::Type::Annotations::Widget#background_color.
224
+ #
225
+ # Note: Multiline, comb and rich text fields are currently not supported!
226
+ def create_text_appearances
227
+ font_name, font_size = @field.parse_default_appearance_string
228
+ default_resources = @document.acro_form.default_resources
229
+ font = default_resources.font(font_name).font_wrapper rescue nil
230
+ unless font
231
+ fallback_font_name, fallback_font_options = @document.config['acro_form.fallback_font']
232
+ if fallback_font_name
233
+ font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
234
+ else
235
+ raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
236
+ end
237
+ end
238
+ style = HexaPDF::Layout::Style.new(font: font)
239
+ border_style = @widget.border_style
240
+ padding = [1, border_style.width].max
241
+
242
+ @widget[:AS] = :N
243
+ @widget.flag(:print)
244
+ rect = @widget[:Rect]
245
+ rect.width = @document.config['acro_form.text_field.default_width'] if rect.width == 0
246
+ if rect.height == 0
247
+ style.font_size = \
248
+ (font_size == 0 ? @document.config['acro_form.default_font_size'] : font_size)
249
+ rect.height = style.scaled_y_max - style.scaled_y_min + 2 * padding
250
+ end
251
+
252
+ form = (@widget[:AP] ||= {})[:N] = @document.add({Type: :XObject, Subtype: :Form,
253
+ BBox: [0, 0, rect.width, rect.height]})
254
+ form[:Resources] = HexaPDF::Object.deep_copy(default_resources)
255
+
256
+ canvas = form.canvas
257
+ apply_background_and_border(border_style, canvas)
258
+ style.font_size = calculate_font_size(font, font_size, rect, border_style)
259
+
260
+ canvas.marked_content_sequence(:Tx) do
261
+ if (value = @field.field_value)
262
+ canvas.save_graphics_state do
263
+ canvas.rectangle(padding, padding, rect.width - 2 * padding,
264
+ rect.height - 2 * padding).clip_path.end_path
265
+ fragment = HexaPDF::Layout::TextFragment.create(value, style)
266
+ # Adobe seems to be left/right-aligning based on twice the border width and
267
+ # vertically centering based on the cap height, if enough space is available
268
+ x = case @field.text_alignment
269
+ when :left then 2 * padding
270
+ when :right then [rect.width - 2 * padding - fragment.width, 2 * padding].max
271
+ when :center then [(rect.width - fragment.width) / 2.0, 2 * padding].max
272
+ end
273
+ cap_height = font.wrapped_font.cap_height * font.scaling_factor / 1000.0 *
274
+ style.font_size
275
+ y = padding + (rect.height - 2 * padding - cap_height) / 2.0
276
+ y = padding - style.scaled_font_descender if y < 0
277
+ fragment.draw(canvas, x, y)
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ alias create_combo_box_appearances create_text_appearances
284
+
285
+ private
286
+
287
+ # Updates the widget and returns its (possibly modified) rectangle.
288
+ #
289
+ # The following changes are made:
290
+ #
291
+ # * Sets the appearance state to +appearance_state+.
292
+ # * Sets the :print flag.
293
+ # * Adjusts the rectangle based on the default font size and the given border width if its
294
+ # width and/or height are zero.
295
+ def update_widget(appearance_state, border_width)
296
+ @widget[:AS] = appearance_state
297
+ @widget.flag(:print)
298
+
299
+ default_font_size = @document.config['acro_form.default_font_size']
300
+ rect = @widget[:Rect]
301
+ rect.width = default_font_size + 2 * border_width if rect.width == 0
302
+ rect.height = default_font_size + 2 * border_width if rect.height == 0
303
+ rect
304
+ end
305
+
306
+ # Applies the background and border style of the widget annotation to the appearances.
307
+ #
308
+ # If +circular+ is +true+, then the border is drawn as inscribed circle instead of as
309
+ # rectangle.
310
+ def apply_background_and_border(border_style, canvas, circular: false)
311
+ rect = @widget[:Rect]
312
+ background_color = @widget.background_color
313
+
314
+ if (border_style.width > 0 && border_style.color) || background_color
315
+ canvas.save_graphics_state
316
+ if background_color
317
+ canvas.fill_color(background_color)
318
+ if circular
319
+ canvas.circle(rect.width / 2.0, rect.height / 2.0,
320
+ [rect.width / 2.0, rect.height / 2.0].min)
321
+ else
322
+ canvas.rectangle(0, 0, rect.width, rect.height)
323
+ end
324
+ canvas.fill
325
+ end
326
+ if border_style.color
327
+ offset = [0.5, border_style.width / 2.0].max
328
+ width, height = rect.width - 2 * offset, rect.height - 2 * offset
329
+ canvas.stroke_color(border_style.color).line_width(border_style.width)
330
+ if border_style.style == :underlined # TODO: :beveleded, :inset
331
+ if circular
332
+ canvas.arc(rect.width / 2.0, rect.height / 2.0,
333
+ a: [width / 2.0, height / 2.0].min,
334
+ start_angle: 180, end_angle: 0)
335
+ else
336
+ canvas.line(offset, offset, offset + width, offset)
337
+ end
338
+ else
339
+ canvas.line_dash_pattern(border_style.style) if border_style.style.kind_of?(Array)
340
+ if circular
341
+ canvas.circle(rect.width / 2.0, rect.height / 2.0, [width / 2.0, height / 2.0].min)
342
+ else
343
+ canvas.rectangle(offset, offset, width, height)
344
+ end
345
+ end
346
+ canvas.stroke
347
+ end
348
+ canvas.restore_graphics_state
349
+ end
350
+ end
351
+
352
+ # Draws the marker defined by the marker style inside the widget's rectangle.
353
+ #
354
+ # This method can only used for check boxes and radio buttons!
355
+ def draw_marker(canvas, rect, border_width, marker_style)
356
+ if @field.radio_button? && marker_style.style == :circle
357
+ # Acrobat handles this specially
358
+ canvas.
359
+ fill_color(marker_style.color).
360
+ circle(rect.width / 2.0, rect.height / 2.0,
361
+ ([rect.width / 2.0, rect.height / 2.0].min - border_width) / 2).
362
+ fill
363
+ elsif marker_style.style == :cross # Acrobat just places a cross inside
364
+ canvas.
365
+ stroke_color(marker_style.color).
366
+ line(border_width, border_width, rect.width - border_width,
367
+ rect.height - border_width).
368
+ line(border_width, rect.height - border_width, rect.width - border_width,
369
+ border_width).
370
+ stroke
371
+ else
372
+ font = @document.fonts.add('ZapfDingbats')
373
+ mark = font.decode_utf8(@widget[:MK]&.[](:CA) || '4').first
374
+ square_width = [rect.width, rect.height].min - 2 * border_width
375
+ font_size = (marker_style.size == 0 ? square_width : marker_style.size)
376
+ mark_width = mark.width * font.scaling_factor * font_size / 1000.0
377
+ mark_height = (mark.y_max - mark.y_min) * font.scaling_factor * font_size / 1000.0
378
+ x_offset = (rect.width - square_width) / 2.0 + (square_width - mark_width) / 2.0
379
+ y_offset = (rect.height - square_width) / 2.0 + (square_width - mark_height) / 2.0 -
380
+ (mark.y_min * font.scaling_factor * font_size / 1000.0)
381
+
382
+ canvas.font(font, size: font_size)
383
+ canvas.fill_color(marker_style.color)
384
+ canvas.move_text_cursor(offset: [x_offset, y_offset]).show_glyphs_only([mark])
385
+ end
386
+ end
387
+
388
+ # Calculates the font size for text fields based on the font and font size of the default
389
+ # appearance string, the annotation rectangle and the border style.
390
+ def calculate_font_size(font, font_size, rect, border_style)
391
+ if font_size == 0
392
+ unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
393
+ font.scaling_factor / 1000.0
394
+ # The constant factor was found empirically by checking what Adobe Reader etc. do
395
+ (rect.height - 2 * border_style.width) / unit_font_size * 0.83
396
+ else
397
+ font_size
398
+ end
399
+ end
400
+
401
+ end
402
+
403
+ end
404
+ end
405
+ end