prawn 0.12.0 → 0.13.0

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