prawn 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -2
  3. data/Gemfile +18 -0
  4. data/LICENSE +1 -1
  5. data/README.md +17 -4
  6. data/Rakefile +18 -22
  7. data/data/images/indexed_color.dat +0 -0
  8. data/data/images/indexed_color.png +0 -0
  9. data/data/pdfs/nested_pages.pdf +13 -13
  10. data/lib/pdf/core.rb +35 -0
  11. data/lib/{prawn → pdf}/core/annotations.rb +6 -7
  12. data/lib/{prawn → pdf}/core/byte_string.rb +1 -1
  13. data/lib/{prawn → pdf}/core/destinations.rb +23 -23
  14. data/lib/{prawn → pdf}/core/document_state.rb +8 -8
  15. data/lib/pdf/core/filter_list.rb +51 -0
  16. data/lib/pdf/core/filters.rb +36 -0
  17. data/lib/pdf/core/graphics_state.rb +68 -0
  18. data/lib/{prawn → pdf}/core/literal_string.rb +1 -1
  19. data/lib/{prawn → pdf}/core/name_tree.rb +14 -2
  20. data/lib/{prawn → pdf}/core/object_store.rb +80 -24
  21. data/lib/pdf/core/outline.rb +315 -0
  22. data/lib/{prawn → pdf}/core/page.rb +23 -26
  23. data/lib/{prawn/document → pdf/core}/page_geometry.rb +11 -21
  24. data/lib/{prawn → pdf}/core/pdf_object.rb +48 -32
  25. data/lib/{prawn → pdf}/core/reference.rb +35 -44
  26. data/lib/pdf/core/stream.rb +98 -0
  27. data/lib/{prawn → pdf}/core/text.rb +24 -17
  28. data/lib/prawn.rb +95 -17
  29. data/lib/prawn/compatibility.rb +66 -26
  30. data/lib/prawn/document.rb +48 -30
  31. data/lib/prawn/document/bounding_box.rb +3 -3
  32. data/lib/prawn/document/column_box.rb +46 -8
  33. data/lib/prawn/document/graphics_state.rb +10 -73
  34. data/lib/prawn/document/internals.rb +24 -23
  35. data/lib/prawn/document/snapshot.rb +6 -7
  36. data/lib/prawn/document/span.rb +10 -10
  37. data/lib/prawn/encoding.rb +7 -7
  38. data/lib/prawn/errors.rb +18 -29
  39. data/lib/prawn/font.rb +64 -28
  40. data/lib/prawn/font/afm.rb +32 -74
  41. data/lib/prawn/font/dfont.rb +2 -2
  42. data/lib/prawn/font/ttf.rb +28 -57
  43. data/lib/prawn/font_metric_cache.rb +45 -0
  44. data/lib/prawn/graphics.rb +307 -41
  45. data/lib/prawn/graphics/cap_style.rb +3 -3
  46. data/lib/prawn/graphics/color.rb +12 -5
  47. data/lib/prawn/graphics/dash.rb +52 -31
  48. data/lib/prawn/graphics/join_style.rb +7 -7
  49. data/lib/prawn/graphics/patterns.rb +137 -0
  50. data/lib/prawn/graphics/transformation.rb +9 -9
  51. data/lib/prawn/graphics/transparency.rb +1 -1
  52. data/lib/prawn/image_handler.rb +30 -0
  53. data/lib/prawn/images.rb +86 -105
  54. data/lib/prawn/images/image.rb +48 -0
  55. data/lib/prawn/images/jpg.rb +14 -10
  56. data/lib/prawn/images/png.rb +50 -37
  57. data/lib/prawn/layout.rb +2 -2
  58. data/lib/prawn/layout/grid.rb +51 -51
  59. data/lib/prawn/measurement_extensions.rb +5 -5
  60. data/lib/prawn/measurements.rb +25 -21
  61. data/lib/prawn/outline.rb +4 -308
  62. data/lib/prawn/repeater.rb +8 -8
  63. data/lib/prawn/security.rb +50 -36
  64. data/lib/prawn/soft_mask.rb +94 -0
  65. data/lib/prawn/stamp.rb +3 -3
  66. data/lib/prawn/table.rb +292 -118
  67. data/lib/prawn/table/cell.rb +272 -45
  68. data/lib/prawn/table/cell/image.rb +70 -0
  69. data/lib/prawn/table/cell/in_table.rb +2 -2
  70. data/lib/prawn/table/cell/span_dummy.rb +92 -0
  71. data/lib/prawn/table/cell/subtable.rb +2 -2
  72. data/lib/prawn/table/cell/text.rb +42 -24
  73. data/lib/prawn/table/cells.rb +137 -48
  74. data/lib/prawn/text.rb +35 -23
  75. data/lib/prawn/text/box.rb +18 -5
  76. data/lib/prawn/text/formatted.rb +5 -4
  77. data/lib/prawn/text/formatted/arranger.rb +292 -0
  78. data/lib/prawn/text/formatted/box.rb +52 -13
  79. data/lib/prawn/text/formatted/fragment.rb +37 -22
  80. data/lib/prawn/text/formatted/line_wrap.rb +286 -0
  81. data/lib/prawn/text/formatted/parser.rb +14 -6
  82. data/lib/prawn/text/formatted/wrap.rb +151 -0
  83. data/lib/prawn/utilities.rb +44 -0
  84. data/manual/basic_concepts/adding_pages.rb +27 -0
  85. data/manual/basic_concepts/basic_concepts.rb +34 -0
  86. data/manual/basic_concepts/creation.rb +39 -0
  87. data/manual/basic_concepts/cursor.rb +33 -0
  88. data/manual/basic_concepts/measurement.rb +25 -0
  89. data/manual/basic_concepts/origin.rb +38 -0
  90. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  91. data/manual/bounding_box/bounding_box.rb +39 -0
  92. data/manual/bounding_box/bounds.rb +49 -0
  93. data/manual/bounding_box/canvas.rb +24 -0
  94. data/manual/bounding_box/creation.rb +23 -0
  95. data/manual/bounding_box/indentation.rb +46 -0
  96. data/manual/bounding_box/nesting.rb +45 -0
  97. data/manual/bounding_box/russian_boxes.rb +40 -0
  98. data/manual/bounding_box/stretchy.rb +31 -0
  99. data/manual/document_and_page_options/background.rb +27 -0
  100. data/manual/document_and_page_options/document_and_page_options.rb +31 -0
  101. data/manual/document_and_page_options/metadata.rb +23 -0
  102. data/manual/document_and_page_options/page_margins.rb +38 -0
  103. data/manual/document_and_page_options/page_size.rb +34 -0
  104. data/manual/example_file.rb +116 -0
  105. data/manual/example_helper.rb +411 -0
  106. data/manual/example_package.rb +53 -0
  107. data/manual/example_section.rb +46 -0
  108. data/manual/graphics/circle_and_ellipse.rb +22 -0
  109. data/manual/graphics/color.rb +24 -0
  110. data/manual/graphics/common_lines.rb +28 -0
  111. data/manual/graphics/fill_and_stroke.rb +42 -0
  112. data/manual/graphics/fill_rules.rb +37 -0
  113. data/manual/graphics/gradients.rb +37 -0
  114. data/manual/graphics/graphics.rb +58 -0
  115. data/manual/graphics/helper.rb +24 -0
  116. data/manual/graphics/line_width.rb +35 -0
  117. data/manual/graphics/lines_and_curves.rb +41 -0
  118. data/manual/graphics/polygon.rb +29 -0
  119. data/manual/graphics/rectangle.rb +21 -0
  120. data/manual/graphics/rotate.rb +28 -0
  121. data/manual/graphics/scale.rb +41 -0
  122. data/manual/graphics/soft_masks.rb +46 -0
  123. data/manual/graphics/stroke_cap.rb +31 -0
  124. data/manual/graphics/stroke_dash.rb +48 -0
  125. data/manual/graphics/stroke_join.rb +30 -0
  126. data/manual/graphics/translate.rb +29 -0
  127. data/manual/graphics/transparency.rb +35 -0
  128. data/manual/images/absolute_position.rb +23 -0
  129. data/manual/images/fit.rb +21 -0
  130. data/manual/images/horizontal.rb +25 -0
  131. data/manual/images/images.rb +40 -0
  132. data/manual/images/plain_image.rb +18 -0
  133. data/manual/images/scale.rb +22 -0
  134. data/manual/images/vertical.rb +28 -0
  135. data/manual/images/width_and_height.rb +25 -0
  136. data/manual/layout/boxes.rb +27 -0
  137. data/manual/layout/content.rb +25 -0
  138. data/manual/layout/layout.rb +28 -0
  139. data/manual/layout/simple_grid.rb +23 -0
  140. data/manual/manual/cover.rb +35 -0
  141. data/manual/manual/foreword.rb +85 -0
  142. data/manual/manual/how_to_read_this_manual.rb +41 -0
  143. data/manual/manual/manual.rb +35 -0
  144. data/manual/outline/add_subsection_to.rb +61 -0
  145. data/manual/outline/insert_section_after.rb +47 -0
  146. data/manual/outline/outline.rb +32 -0
  147. data/manual/outline/sections_and_pages.rb +67 -0
  148. data/manual/repeatable_content/page_numbering.rb +54 -0
  149. data/manual/repeatable_content/repeatable_content.rb +31 -0
  150. data/manual/repeatable_content/repeater.rb +55 -0
  151. data/manual/repeatable_content/stamp.rb +41 -0
  152. data/manual/security/encryption.rb +31 -0
  153. data/manual/security/permissions.rb +38 -0
  154. data/manual/security/security.rb +28 -0
  155. data/manual/syntax_highlight.rb +52 -0
  156. data/manual/table/basic_block.rb +53 -0
  157. data/manual/table/before_rendering_page.rb +26 -0
  158. data/manual/table/cell_border_lines.rb +24 -0
  159. data/manual/table/cell_borders_and_bg.rb +31 -0
  160. data/manual/table/cell_dimensions.rb +30 -0
  161. data/manual/table/cell_text.rb +38 -0
  162. data/manual/table/column_widths.rb +30 -0
  163. data/manual/table/content_and_subtables.rb +39 -0
  164. data/manual/table/creation.rb +27 -0
  165. data/manual/table/filtering.rb +36 -0
  166. data/manual/table/flow_and_header.rb +17 -0
  167. data/manual/table/image_cells.rb +33 -0
  168. data/manual/table/position.rb +29 -0
  169. data/manual/table/row_colors.rb +20 -0
  170. data/manual/table/span.rb +30 -0
  171. data/manual/table/style.rb +22 -0
  172. data/manual/table/table.rb +52 -0
  173. data/manual/table/width.rb +27 -0
  174. data/manual/templates/full_template.rb +25 -0
  175. data/manual/templates/page_template.rb +48 -0
  176. data/manual/templates/templates.rb +27 -0
  177. data/manual/text/alignment.rb +44 -0
  178. data/manual/text/color.rb +24 -0
  179. data/manual/text/column_box.rb +32 -0
  180. data/manual/text/fallback_fonts.rb +37 -0
  181. data/manual/text/font.rb +41 -0
  182. data/manual/text/font_size.rb +45 -0
  183. data/manual/text/font_style.rb +23 -0
  184. data/manual/text/formatted_callbacks.rb +60 -0
  185. data/manual/text/formatted_text.rb +54 -0
  186. data/manual/text/free_flowing_text.rb +51 -0
  187. data/manual/text/group.rb +29 -0
  188. data/manual/text/inline.rb +43 -0
  189. data/manual/text/kerning_and_character_spacing.rb +39 -0
  190. data/manual/text/leading.rb +25 -0
  191. data/manual/text/line_wrapping.rb +41 -0
  192. data/manual/text/paragraph_indentation.rb +26 -0
  193. data/manual/text/positioned_text.rb +38 -0
  194. data/manual/text/registering_families.rb +48 -0
  195. data/manual/text/rendering_and_color.rb +37 -0
  196. data/manual/text/right_to_left_text.rb +43 -0
  197. data/manual/text/rotation.rb +43 -0
  198. data/manual/text/single_usage.rb +37 -0
  199. data/manual/text/text.rb +75 -0
  200. data/manual/text/text_box_excess.rb +32 -0
  201. data/manual/text/text_box_extensions.rb +45 -0
  202. data/manual/text/text_box_overflow.rb +44 -0
  203. data/manual/text/utf8.rb +28 -0
  204. data/{examples/m17n → manual/text}/win_ansi_charset.rb +14 -10
  205. data/prawn.gemspec +18 -12
  206. data/spec/acceptance/png.rb +23 -0
  207. data/spec/annotations_spec.rb +16 -32
  208. data/spec/bounding_box_spec.rb +128 -15
  209. data/spec/cell_spec.rb +169 -38
  210. data/spec/column_box_spec.rb +33 -0
  211. data/spec/destinations_spec.rb +5 -5
  212. data/spec/document_spec.rb +150 -104
  213. data/spec/extensions/encoding_helpers.rb +10 -0
  214. data/spec/extensions/mocha.rb +1 -0
  215. data/spec/filters_spec.rb +34 -0
  216. data/spec/font_metric_cache_spec.rb +52 -0
  217. data/spec/font_spec.rb +183 -97
  218. data/spec/formatted_text_arranger_spec.rb +43 -43
  219. data/spec/formatted_text_box_spec.rb +30 -20
  220. data/spec/formatted_text_fragment_spec.rb +8 -8
  221. data/spec/graphics_spec.rb +158 -69
  222. data/spec/grid_spec.rb +15 -15
  223. data/spec/image_handler_spec.rb +42 -0
  224. data/spec/images_spec.rb +49 -24
  225. data/spec/inline_formatted_text_parser_spec.rb +73 -19
  226. data/spec/jpg_spec.rb +4 -4
  227. data/spec/line_wrap_spec.rb +26 -26
  228. data/spec/measurement_units_spec.rb +6 -6
  229. data/spec/name_tree_spec.rb +21 -21
  230. data/spec/object_store_spec.rb +39 -39
  231. data/spec/outline_spec.rb +93 -53
  232. data/spec/pdf_object_spec.rb +88 -86
  233. data/spec/png_spec.rb +31 -28
  234. data/spec/reference_spec.rb +32 -32
  235. data/spec/repeater_spec.rb +25 -11
  236. data/spec/security_spec.rb +44 -12
  237. data/spec/snapshot_spec.rb +8 -9
  238. data/spec/soft_mask_spec.rb +117 -0
  239. data/spec/span_spec.rb +10 -15
  240. data/spec/spec_helper.rb +25 -8
  241. data/spec/stamp_spec.rb +29 -30
  242. data/spec/stream_spec.rb +58 -0
  243. data/spec/stroke_styles_spec.rb +36 -18
  244. data/spec/table/span_dummy_spec.rb +17 -0
  245. data/spec/table_spec.rb +697 -105
  246. data/spec/template_spec.rb +108 -54
  247. data/spec/text_at_spec.rb +18 -17
  248. data/spec/text_box_spec.rb +111 -62
  249. data/spec/text_rendering_mode_spec.rb +5 -5
  250. data/spec/text_spacing_spec.rb +4 -4
  251. data/spec/text_spec.rb +57 -49
  252. data/spec/transparency_spec.rb +5 -5
  253. metadata +421 -213
  254. data/data/fonts/Action Man.dfont +0 -0
  255. data/data/fonts/Activa.ttf +0 -0
  256. data/data/fonts/Chalkboard.ttf +0 -0
  257. data/data/fonts/DejaVuSans.ttf +0 -0
  258. data/data/fonts/Dustismo_Roman.ttf +0 -0
  259. data/data/fonts/comicsans.ttf +0 -0
  260. data/data/fonts/gkai00mp.ttf +0 -0
  261. data/data/images/rails.dat +0 -0
  262. data/data/images/rails.png +0 -0
  263. data/examples/bounding_box/russian_boxes.rb +0 -37
  264. data/examples/example_helper.rb +0 -11
  265. data/examples/general/context_sensitive_headers.rb +0 -38
  266. data/examples/graphics/cmyk.rb +0 -13
  267. data/examples/graphics/gradient.rb +0 -23
  268. data/examples/graphics/png_types.rb +0 -23
  269. data/examples/graphics/remote_images.rb +0 -13
  270. data/examples/m17n/full_win_ansi_character_list.rb +0 -20
  271. data/examples/m17n/sjis.rb +0 -29
  272. data/examples/table/bill.rb +0 -54
  273. data/examples/table/header.rb +0 -15
  274. data/examples/text/font_calculations.rb +0 -92
  275. data/examples/text/hyphenation.rb +0 -45
  276. data/examples/text/indent_paragraphs.rb +0 -24
  277. data/lib/prawn/core.rb +0 -85
  278. data/lib/prawn/core/text/formatted/arranger.rb +0 -294
  279. data/lib/prawn/core/text/formatted/line_wrap.rb +0 -273
  280. data/lib/prawn/core/text/formatted/wrap.rb +0 -153
  281. data/lib/prawn/graphics/gradient.rb +0 -84
  282. data/lib/prawn/security/arcfour.rb +0 -51
@@ -0,0 +1,51 @@
1
+ module PDF
2
+ module Core
3
+ class FilterList
4
+ def initialize
5
+ @list = []
6
+ end
7
+
8
+ def <<(filter)
9
+ case filter
10
+ when Symbol
11
+ @list << [filter, nil]
12
+ when ::Hash
13
+ filter.each do |name, params|
14
+ @list << [name, params]
15
+ end
16
+ else
17
+ raise "Can not interpret input as filter: #{filter.inspect}"
18
+ end
19
+
20
+ self
21
+ end
22
+
23
+ def normalized
24
+ @list
25
+ end
26
+ alias_method :to_a, :normalized
27
+
28
+ def names
29
+ @list.map do |(name, _)|
30
+ name
31
+ end
32
+ end
33
+
34
+ def decode_params
35
+ @list.map do |(_, params)|
36
+ params
37
+ end
38
+ end
39
+
40
+ def inspect
41
+ @list.inspect
42
+ end
43
+
44
+ def each(&block)
45
+ @list.each do |filter|
46
+ block.call(filter)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn/core/filters.rb : Implements stream filters
4
+ #
5
+ # Copyright February 2013, Alexander Mankuta. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require 'zlib'
10
+
11
+ module PDF
12
+ module Core
13
+ module Filters
14
+ module FlateDecode
15
+ def self.encode(stream, params = nil)
16
+ Zlib::Deflate.deflate(stream)
17
+ end
18
+
19
+ def self.decode(stream, params = nil)
20
+ Zlib::Inflate.inflate(stream)
21
+ end
22
+ end
23
+
24
+ # Pass through stub
25
+ module DCTDecode
26
+ def self.encode(stream, params = nil)
27
+ stream
28
+ end
29
+
30
+ def self.decode(stream, params = nil)
31
+ stream
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Implements graphics state saving and restoring
4
+ #
5
+ # Copyright January 2010, Michael Witrant. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details
8
+ #
9
+
10
+
11
+ module PDF
12
+ module Core
13
+ class GraphicStateStack
14
+ attr_accessor :stack
15
+
16
+ def initialize(previous_state = nil)
17
+ self.stack = [GraphicState.new(previous_state)]
18
+ end
19
+
20
+ def save_graphic_state(graphic_state = nil)
21
+ stack.push(GraphicState.new(graphic_state || current_state))
22
+ end
23
+
24
+ def restore_graphic_state
25
+ if stack.empty?
26
+ raise PDF::Core::Errors::EmptyGraphicStateStack,
27
+ "\n You have reached the end of the graphic state stack"
28
+ end
29
+ stack.pop
30
+ end
31
+
32
+ def current_state
33
+ stack.last
34
+ end
35
+
36
+ def present?
37
+ stack.size > 0
38
+ end
39
+
40
+ def empty?
41
+ stack.empty?
42
+ end
43
+
44
+ end
45
+
46
+ class GraphicState
47
+ attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width, :fill_color, :stroke_color
48
+
49
+ def initialize(previous_state = nil)
50
+ @color_space = previous_state ? previous_state.color_space.dup : {}
51
+ @fill_color = previous_state ? previous_state.fill_color : "000000"
52
+ @stroke_color = previous_state ? previous_state.stroke_color : "000000"
53
+ @dash = previous_state ? previous_state.dash : { :dash => nil, :space => nil, :phase => 0 }
54
+ @cap_style = previous_state ? previous_state.cap_style : :butt
55
+ @join_style = previous_state ? previous_state.join_style : :miter
56
+ @line_width = previous_state ? previous_state.line_width : 1
57
+ end
58
+
59
+ def dash_setting
60
+ if @dash[:dash].kind_of?(Array)
61
+ "[#{@dash[:dash].join(' ')}] #{@dash[:phase]} d"
62
+ else
63
+ "[#{@dash[:dash]} #{@dash[:space]}] #{@dash[:phase]} d"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- module Prawn
2
+ module PDF
3
3
  module Core
4
4
  # This is used to differentiate strings that must be encoded as
5
5
  # a *literal* string, versus those that can be encoded in
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
  #
9
- module Prawn
9
+ module PDF
10
10
  module Core
11
11
  module NameTree #:nodoc:
12
12
  class Node #:nodoc:
@@ -98,6 +98,18 @@ module Prawn
98
98
  end
99
99
  end
100
100
 
101
+ # Returns a deep copy of this node, without copying expensive things
102
+ # like the ref to @document.
103
+ #
104
+ def deep_copy
105
+ node = dup
106
+ node.instance_variable_set("@children",
107
+ Marshal.load(Marshal.dump(children)))
108
+ node.instance_variable_set("@ref",
109
+ node.ref ? node.ref.deep_copy : nil)
110
+ node
111
+ end
112
+
101
113
  protected
102
114
 
103
115
  def split(node)
@@ -145,7 +157,7 @@ module Prawn
145
157
  attr_reader :value
146
158
 
147
159
  def initialize(name, value)
148
- @name, @value = Prawn::Core::LiteralString.new(name), value
160
+ @name, @value = PDF::Core::LiteralString.new(name), value
149
161
  end
150
162
 
151
163
  def <=>(leaf)
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- # prawn/core/object_store.rb : Implements PDF object repository for Prawn
3
+ # Implements PDF object repository
4
4
  #
5
5
  # Copyright August 2009, Brad Ediger. All Rights Reserved.
6
6
  #
@@ -9,7 +9,7 @@
9
9
 
10
10
  require 'pdf/reader'
11
11
 
12
- module Prawn
12
+ module PDF
13
13
  module Core
14
14
  class ObjectStore #:nodoc:
15
15
  include Enumerable
@@ -52,14 +52,14 @@ module Prawn
52
52
  end
53
53
 
54
54
  # Adds the given reference to the store and returns the reference object.
55
- # If the object provided is not a Prawn::Core::Reference, one is created from the
55
+ # If the object provided is not a PDF::Core::Reference, one is created from the
56
56
  # arguments provided.
57
57
  #
58
58
  def push(*args, &block)
59
- reference = if args.first.is_a?(Prawn::Core::Reference)
59
+ reference = if args.first.is_a?(PDF::Core::Reference)
60
60
  args.first
61
61
  else
62
- Prawn::Core::Reference.new(*args, &block)
62
+ PDF::Core::Reference.new(*args, &block)
63
63
  end
64
64
 
65
65
  @objects[reference.identifier] = reference
@@ -141,27 +141,83 @@ module Prawn
141
141
  # Imports nothing and returns nil if the requested page number doesn't
142
142
  # exist. page_num is 1 indexed, so 1 indicates the first page.
143
143
  #
144
- def import_page(filename, page_num)
144
+ def import_page(input, page_num)
145
145
  @loaded_objects = {}
146
- unless File.file?(filename)
147
- raise ArgumentError, "#{filename} does not exist"
146
+ if template_id = indexed_template(input, page_num)
147
+ return template_id
148
148
  end
149
149
 
150
- hash = PDF::Reader::ObjectHash.new(filename)
150
+ io = if input.respond_to?(:seek) && input.respond_to?(:read)
151
+ input
152
+ elsif File.file?(input.to_s)
153
+ StringIO.new(File.binread(input.to_s))
154
+ else
155
+ raise ArgumentError, "input must be an IO-like object or a filename"
156
+ end
157
+
158
+ # unless File.file?(filename)
159
+ # raise ArgumentError, "#{filename} does not exist"
160
+ # end
161
+
162
+ hash = indexed_hash(input, io)
151
163
  ref = hash.page_references[page_num - 1]
152
164
 
153
- ref.nil? ? nil : load_object_graph(hash, ref).identifier
165
+ if ref.nil?
166
+ nil
167
+ else
168
+ index_template(input, page_num, load_object_graph(hash, ref).identifier)
169
+ end
154
170
 
155
- rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError
156
- msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug."
157
- raise Prawn::Errors::TemplateError, msg
171
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError => e
172
+ msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug.\n#{e.message}"
173
+ raise PDF::Core::Errors::TemplateError, msg
158
174
  rescue PDF::Reader::UnsupportedFeatureError
159
175
  msg = "Template file contains unsupported PDF features"
160
- raise Prawn::Errors::TemplateError, msg
176
+ raise PDF::Core::Errors::TemplateError, msg
161
177
  end
162
178
 
163
179
  private
164
180
 
181
+ # An index for page templates so that their loaded object graph
182
+ # can be reused without multiple loading
183
+ def template_index
184
+ @template_index ||= {}
185
+ end
186
+
187
+ # An index for the read object hash of a pdf template so that the
188
+ # object hash does not need to be parsed multiple times when using
189
+ # different pages of the pdf as page templates
190
+ def hash_index
191
+ @hash_index ||= {}
192
+ end
193
+
194
+ # returns the indexed object graph identifier for a template page if
195
+ # it exists
196
+ def indexed_template(input, page_number)
197
+ key = indexing_key(input)
198
+ template_index[key] && template_index[key][page_number]
199
+ end
200
+
201
+ # indexes the identifier for a page from a template
202
+ def index_template(input, page_number, id)
203
+ (template_index[indexing_key(input)] ||= {})[page_number] ||= id
204
+ end
205
+
206
+ # reads and indexes a new IO for a template
207
+ # if the IO has been indexed already then the parsed object hash
208
+ # is returned directly
209
+ def indexed_hash(input, io)
210
+ hash_index[indexing_key(input)] ||= PDF::Reader::ObjectHash.new(io)
211
+ end
212
+
213
+ # the index key for the input.
214
+ # uses object_id so that both a string filename or an IO stream can be
215
+ # indexed and reused provided the same object gets used in multiple page
216
+ # template calls.
217
+ def indexing_key(input)
218
+ input.object_id
219
+ end
220
+
165
221
  # returns a nested array of object IDs for all pages in this object store.
166
222
  #
167
223
  def get_page_objects(obj)
@@ -187,7 +243,7 @@ module Prawn
187
243
 
188
244
  if hash.trailer[:Encrypt]
189
245
  msg = "Template file is an encrypted PDF, it can't be used as a template"
190
- raise Prawn::Errors::TemplateError, msg
246
+ raise PDF::Core::Errors::TemplateError, msg
191
247
  end
192
248
 
193
249
  if src_info
@@ -197,12 +253,12 @@ module Prawn
197
253
  if src_root
198
254
  @root = load_object_graph(hash, src_root).identifier
199
255
  end
200
- rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError
201
- msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug."
202
- raise Prawn::Errors::TemplateError, msg
256
+ rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError => e
257
+ msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug.\n#{e.message}"
258
+ raise PDF::Core::Errors::TemplateError, msg
203
259
  rescue PDF::Reader::UnsupportedFeatureError
204
260
  msg = "Template file contains unsupported PDF features"
205
- raise Prawn::Errors::TemplateError, msg
261
+ raise PDF::Core::Errors::TemplateError, msg
206
262
  end
207
263
 
208
264
  # recurse down an object graph from a source PDF, importing all the
@@ -223,10 +279,10 @@ module Prawn
223
279
  unless @loaded_objects.has_key?(object.id)
224
280
  @loaded_objects[object.id] = ref(nil)
225
281
  new_obj = load_object_graph(hash, hash[object])
226
- if new_obj.kind_of?(PDF::Reader::Stream)
227
- stream_dict = load_object_graph(hash, new_obj.hash)
228
- @loaded_objects[object.id].data = stream_dict
229
- @loaded_objects[object.id] << new_obj.data
282
+ if new_obj.kind_of?(PDF::Reader::Stream)
283
+ stream_dict = load_object_graph(hash, new_obj.hash)
284
+ @loaded_objects[object.id].data = stream_dict
285
+ @loaded_objects[object.id] << new_obj.data
230
286
  else
231
287
  @loaded_objects[object.id].data = new_obj
232
288
  end
@@ -237,7 +293,7 @@ module Prawn
237
293
  # being wrapped in a LiteralString
238
294
  object
239
295
  when String
240
- is_utf8?(object) ? object : Prawn::Core::ByteString.new(object)
296
+ is_utf8?(object) ? object : PDF::Core::ByteString.new(object)
241
297
  else
242
298
  object
243
299
  end
@@ -0,0 +1,315 @@
1
+ module PDF
2
+ module Core
3
+ # The Outline class organizes the outline tree items for the document.
4
+ # Note that the prev and parent instance variables are adjusted while navigating
5
+ # through the nested blocks. These variables along with the presence or absense
6
+ # of blocks are the primary means by which the relations for the various
7
+ # OutlineItems and the OutlineRoot are set. Unfortunately, the best way to
8
+ # understand how this works is to follow the method calls through a real example.
9
+ #
10
+ # Some ideas for the organization of this class were gleaned from name_tree. In
11
+ # particular the way in which the OutlineItems are finally rendered into document
12
+ # objects in PdfObject through a hash.
13
+ #
14
+ class Outline
15
+
16
+ extend Forwardable
17
+ def_delegator :@document, :page_number
18
+
19
+ attr_accessor :parent
20
+ attr_accessor :prev
21
+ attr_accessor :document
22
+ attr_accessor :items
23
+
24
+ def initialize(document)
25
+ @document = document
26
+ @parent = root
27
+ @prev = nil
28
+ @items = {}
29
+ end
30
+
31
+ # Defines/Updates an outline for the document.
32
+ # The outline is an optional nested index that appears on the side of a PDF
33
+ # document usually with direct links to pages. The outline DSL is defined by nested
34
+ # blocks involving two methods: section and page; see the documentation on those methods
35
+ # for their arguments and options. Note that one can also use outline#update
36
+ # to add more sections to the end of the outline tree using the same syntax and scope.
37
+ #
38
+ # The syntax is best illustrated with an example:
39
+ #
40
+ # Prawn::Document.generate(outlined_document.pdf) do
41
+ # text "Page 1. This is the first Chapter. "
42
+ # start_new_page
43
+ # text "Page 2. More in the first Chapter. "
44
+ # start_new_page
45
+ # outline.define do
46
+ # section 'Chapter 1', :destination => 1, :closed => true do
47
+ # page :destination => 1, :title => 'Page 1'
48
+ # page :destination => 2, :title => 'Page 2'
49
+ # end
50
+ # end
51
+ # start_new_page do
52
+ # outline.update do
53
+ # section 'Chapter 2', :destination => 2, do
54
+ # page :destination => 3, :title => 'Page 3'
55
+ # end
56
+ # end
57
+ # end
58
+ #
59
+ def define(&block)
60
+ instance_eval(&block) if block
61
+ end
62
+
63
+ alias :update :define
64
+
65
+ # Inserts an outline section to the outline tree (see outline#define).
66
+ # Although you will probably choose to exclusively use outline#define so
67
+ # that your outline tree is contained and easy to manage, this method
68
+ # gives you the option to insert sections to the outline tree at any point
69
+ # during document generation. This method allows you to add a child subsection
70
+ # to any other item at any level in the outline tree.
71
+ # Currently the only way to locate the place of entry is with the title for the
72
+ # item. If your title names are not unique consider using define_outline.
73
+ # The method takes the following arguments:
74
+ # title: a string that must match an outline title to add the subsection to
75
+ # position: either :first or :last(the default) where the subsection will be placed relative
76
+ # to other child elements. If you need to position your subsection in between
77
+ # other elements then consider using #insert_section_after
78
+ # block: uses the same DSL syntax as outline#define, for example:
79
+ #
80
+ # Consider using this method inside of outline.update if you want to have the outline object
81
+ # to be scoped as self (see #insert_section_after example).
82
+ #
83
+ # go_to_page 2
84
+ # start_new_page
85
+ # text "Inserted Page"
86
+ # outline.add_subsection_to :title => 'Page 2', :first do
87
+ # outline.page :destination => page_number, :title => "Inserted Page"
88
+ # end
89
+ #
90
+ def add_subsection_to(title, position = :last, &block)
91
+ @parent = items[title]
92
+ raise Prawn::Errors::UnknownOutlineTitle,
93
+ "\n No outline item with title: '#{title}' exists in the outline tree" unless @parent
94
+ @prev = position == :first ? nil : @parent.data.last
95
+ nxt = position == :first ? @parent.data.first : nil
96
+ insert_section(nxt, &block)
97
+ end
98
+
99
+ # Inserts an outline section to the outline tree (see outline#define).
100
+ # Although you will probably choose to exclusively use outline#define so
101
+ # that your outline tree is contained and easy to manage, this method
102
+ # gives you the option to insert sections to the outline tree at any point
103
+ # during document generation. Unlike outline.add_section, this method allows
104
+ # you to enter a section after any other item at any level in the outline tree.
105
+ # Currently the only way to locate the place of entry is with the title for the
106
+ # item. If your title names are not unique consider using define_outline.
107
+ # The method takes the following arguments:
108
+ # title: the title of other section or page to insert new section after
109
+ # block: uses the same DSL syntax as outline#define, for example:
110
+ #
111
+ # go_to_page 2
112
+ # start_new_page
113
+ # text "Inserted Page"
114
+ # update_outline do
115
+ # insert_section_after :title => 'Page 2' do
116
+ # page :destination => page_number, :title => "Inserted Page"
117
+ # end
118
+ # end
119
+ #
120
+ def insert_section_after(title, &block)
121
+ @prev = items[title]
122
+ raise Prawn::Errors::UnknownOutlineTitle,
123
+ "\n No outline item with title: '#{title}' exists in the outline tree" unless @prev
124
+ @parent = @prev.data.parent
125
+ nxt = @prev.data.next
126
+ insert_section(nxt, &block)
127
+ end
128
+
129
+ # See outline#define above for documentation on how this is used in that context
130
+ #
131
+ # Adds an outine section to the outline tree.
132
+ # Although you will probably choose to exclusively use outline#define so
133
+ # that your outline tree is contained and easy to manage, this method
134
+ # gives you the option to add sections to the outline tree at any point
135
+ # during document generation. When not being called from within another #section block
136
+ # the section will be added at the top level after the other root elements of the outline.
137
+ # For more flexible placement try using outline#insert_section_after and/or
138
+ # outline#add_subsection_to
139
+ # Takes the following arguments:
140
+ # title: the outline text that appears for the section.
141
+ # options: destination - optional integer defining the page number for a destination link
142
+ # to the top of the page (using a :FIT destination).
143
+ # - or an array with a custom destination (see the #dest_* methods of the
144
+ # PDF::Destination module)
145
+ # closed - whether the section should show its nested outline elements.
146
+ # - defaults to false.
147
+ # block: more nested subsections and/or page blocks
148
+ #
149
+ # example usage:
150
+ #
151
+ # outline.section 'Added Section', :destination => 3 do
152
+ # outline.page :destionation => 3, :title => 'Page 3'
153
+ # end
154
+ def section(title, options = {}, &block)
155
+ add_outline_item(title, options, &block)
156
+ end
157
+
158
+ # See Outline#define above for more documentation on how it is used in that context
159
+ #
160
+ # Adds a page to the outline.
161
+ # Although you will probably choose to exclusively use outline#define so
162
+ # that your outline tree is contained and easy to manage, this method also
163
+ # gives you the option to add pages to the root of outline tree at any point
164
+ # during document generation. Note that the page will be added at the
165
+ # top level after the other root outline elements. For more flexible placement try
166
+ # using outline#insert_section_after and/or outline#add_subsection_to.
167
+ #
168
+ # Takes the following arguments:
169
+ # options:
170
+ # title - REQUIRED. The outline text that appears for the page.
171
+ # destination - optional integer defining the page number for a destination link
172
+ # to the top of the page (using a :FIT destination).
173
+ # - or an array with a custom destination (see the #dest_* methods of the
174
+ # PDF::Destination module)
175
+ # closed - whether the section should show its nested outline elements.
176
+ # - defaults to false.
177
+ # example usage:
178
+ #
179
+ # outline.page :title => "Very Last Page"
180
+ # Note: this method is almost identical to section except that it does not accept a block
181
+ # thereby defining the outline item as a leaf on the outline tree structure.
182
+ def page(options = {})
183
+ if options[:title]
184
+ title = options[:title]
185
+ else
186
+ raise Prawn::Errors::RequiredOption,
187
+ "\nTitle is a required option for page"
188
+ end
189
+ add_outline_item(title, options)
190
+ end
191
+
192
+ private
193
+
194
+ # The Outline dictionary (12.3.3) for this document. It is
195
+ # lazily initialized, so that documents that do not have an outline
196
+ # do not incur the additional overhead.
197
+ def root
198
+ document.state.store.root.data[:Outlines] ||= document.ref!(OutlineRoot.new)
199
+ end
200
+
201
+ def add_outline_item(title, options, &block)
202
+ outline_item = create_outline_item(title, options)
203
+ set_relations(outline_item)
204
+ increase_count
205
+ set_variables_for_block(outline_item, block)
206
+ block.call if block
207
+ reset_parent(outline_item)
208
+ end
209
+
210
+ def create_outline_item(title, options)
211
+ outline_item = OutlineItem.new(title, parent, options)
212
+
213
+ case options[:destination]
214
+ when Integer
215
+ page_index = options[:destination] - 1
216
+ outline_item.dest = [document.state.pages[page_index].dictionary, :Fit]
217
+ when Array
218
+ outline_item.dest = options[:destination]
219
+ end
220
+
221
+ outline_item.prev = prev if @prev
222
+ items[title] = document.ref!(outline_item)
223
+ end
224
+
225
+ def set_relations(outline_item)
226
+ prev.data.next = outline_item if prev
227
+ parent.data.first = outline_item unless prev
228
+ parent.data.last = outline_item
229
+ end
230
+
231
+ def increase_count
232
+ counting_parent = parent
233
+ while counting_parent
234
+ counting_parent.data.count += 1
235
+ if counting_parent == root
236
+ counting_parent = nil
237
+ else
238
+ counting_parent = counting_parent.data.parent
239
+ end
240
+ end
241
+ end
242
+
243
+ def set_variables_for_block(outline_item, block)
244
+ self.prev = block ? nil : outline_item
245
+ self.parent = outline_item if block
246
+ end
247
+
248
+ def reset_parent(outline_item)
249
+ if parent == outline_item
250
+ self.prev = outline_item
251
+ self.parent = outline_item.data.parent
252
+ end
253
+ end
254
+
255
+ def insert_section(nxt, &block)
256
+ last = @parent.data.last
257
+ if block
258
+ block.call
259
+ end
260
+ adjust_relations(nxt, last)
261
+ reset_root_positioning
262
+ end
263
+
264
+ def adjust_relations(nxt, last)
265
+ if nxt
266
+ nxt.data.prev = @prev
267
+ @prev.data.next = nxt
268
+ @parent.data.last = last
269
+ end
270
+ end
271
+
272
+ def reset_root_positioning
273
+ @parent = root
274
+ @prev = root.data.last
275
+ end
276
+
277
+ end
278
+
279
+ class OutlineRoot #:nodoc:
280
+ attr_accessor :count, :first, :last
281
+
282
+ def initialize
283
+ @count = 0
284
+ end
285
+
286
+ def to_hash
287
+ {:Type => :Outlines, :Count => count, :First => first, :Last => last}
288
+ end
289
+ end
290
+
291
+ class OutlineItem #:nodoc:
292
+ attr_accessor :count, :first, :last, :next, :prev, :parent, :title, :dest, :closed
293
+
294
+ def initialize(title, parent, options)
295
+ @closed = options[:closed]
296
+ @title = title
297
+ @parent = parent
298
+ @count = 0
299
+ end
300
+
301
+ def to_hash
302
+ hash = { :Title => title,
303
+ :Parent => parent,
304
+ :Count => closed ? -count : count }
305
+ [{:First => first}, {:Last => last}, {:Next => defined?(@next) && @next},
306
+ {:Prev => prev}, {:Dest => dest}].each do |h|
307
+ unless h.values.first.nil?
308
+ hash.merge!(h)
309
+ end
310
+ end
311
+ hash
312
+ end
313
+ end
314
+ end
315
+ end