prawn 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +2 -2
- data/Gemfile +18 -0
- data/LICENSE +1 -1
- data/README.md +17 -4
- data/Rakefile +18 -22
- data/data/images/indexed_color.dat +0 -0
- data/data/images/indexed_color.png +0 -0
- data/data/pdfs/nested_pages.pdf +13 -13
- data/lib/pdf/core.rb +35 -0
- data/lib/{prawn → pdf}/core/annotations.rb +6 -7
- data/lib/{prawn → pdf}/core/byte_string.rb +1 -1
- data/lib/{prawn → pdf}/core/destinations.rb +23 -23
- data/lib/{prawn → pdf}/core/document_state.rb +8 -8
- data/lib/pdf/core/filter_list.rb +51 -0
- data/lib/pdf/core/filters.rb +36 -0
- data/lib/pdf/core/graphics_state.rb +68 -0
- data/lib/{prawn → pdf}/core/literal_string.rb +1 -1
- data/lib/{prawn → pdf}/core/name_tree.rb +14 -2
- data/lib/{prawn → pdf}/core/object_store.rb +80 -24
- data/lib/pdf/core/outline.rb +315 -0
- data/lib/{prawn → pdf}/core/page.rb +23 -26
- data/lib/{prawn/document → pdf/core}/page_geometry.rb +11 -21
- data/lib/{prawn → pdf}/core/pdf_object.rb +48 -32
- data/lib/{prawn → pdf}/core/reference.rb +35 -44
- data/lib/pdf/core/stream.rb +98 -0
- data/lib/{prawn → pdf}/core/text.rb +24 -17
- data/lib/prawn.rb +95 -17
- data/lib/prawn/compatibility.rb +66 -26
- data/lib/prawn/document.rb +48 -30
- data/lib/prawn/document/bounding_box.rb +3 -3
- data/lib/prawn/document/column_box.rb +46 -8
- data/lib/prawn/document/graphics_state.rb +10 -73
- data/lib/prawn/document/internals.rb +24 -23
- data/lib/prawn/document/snapshot.rb +6 -7
- data/lib/prawn/document/span.rb +10 -10
- data/lib/prawn/encoding.rb +7 -7
- data/lib/prawn/errors.rb +18 -29
- data/lib/prawn/font.rb +64 -28
- data/lib/prawn/font/afm.rb +32 -74
- data/lib/prawn/font/dfont.rb +2 -2
- data/lib/prawn/font/ttf.rb +28 -57
- data/lib/prawn/font_metric_cache.rb +45 -0
- data/lib/prawn/graphics.rb +307 -41
- data/lib/prawn/graphics/cap_style.rb +3 -3
- data/lib/prawn/graphics/color.rb +12 -5
- data/lib/prawn/graphics/dash.rb +52 -31
- data/lib/prawn/graphics/join_style.rb +7 -7
- data/lib/prawn/graphics/patterns.rb +137 -0
- data/lib/prawn/graphics/transformation.rb +9 -9
- data/lib/prawn/graphics/transparency.rb +1 -1
- data/lib/prawn/image_handler.rb +30 -0
- data/lib/prawn/images.rb +86 -105
- data/lib/prawn/images/image.rb +48 -0
- data/lib/prawn/images/jpg.rb +14 -10
- data/lib/prawn/images/png.rb +50 -37
- data/lib/prawn/layout.rb +2 -2
- data/lib/prawn/layout/grid.rb +51 -51
- data/lib/prawn/measurement_extensions.rb +5 -5
- data/lib/prawn/measurements.rb +25 -21
- data/lib/prawn/outline.rb +4 -308
- data/lib/prawn/repeater.rb +8 -8
- data/lib/prawn/security.rb +50 -36
- data/lib/prawn/soft_mask.rb +94 -0
- data/lib/prawn/stamp.rb +3 -3
- data/lib/prawn/table.rb +292 -118
- data/lib/prawn/table/cell.rb +272 -45
- data/lib/prawn/table/cell/image.rb +70 -0
- data/lib/prawn/table/cell/in_table.rb +2 -2
- data/lib/prawn/table/cell/span_dummy.rb +92 -0
- data/lib/prawn/table/cell/subtable.rb +2 -2
- data/lib/prawn/table/cell/text.rb +42 -24
- data/lib/prawn/table/cells.rb +137 -48
- data/lib/prawn/text.rb +35 -23
- data/lib/prawn/text/box.rb +18 -5
- data/lib/prawn/text/formatted.rb +5 -4
- data/lib/prawn/text/formatted/arranger.rb +292 -0
- data/lib/prawn/text/formatted/box.rb +52 -13
- data/lib/prawn/text/formatted/fragment.rb +37 -22
- data/lib/prawn/text/formatted/line_wrap.rb +286 -0
- data/lib/prawn/text/formatted/parser.rb +14 -6
- data/lib/prawn/text/formatted/wrap.rb +151 -0
- data/lib/prawn/utilities.rb +44 -0
- data/manual/basic_concepts/adding_pages.rb +27 -0
- data/manual/basic_concepts/basic_concepts.rb +34 -0
- data/manual/basic_concepts/creation.rb +39 -0
- data/manual/basic_concepts/cursor.rb +33 -0
- data/manual/basic_concepts/measurement.rb +25 -0
- data/manual/basic_concepts/origin.rb +38 -0
- data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
- data/manual/bounding_box/bounding_box.rb +39 -0
- data/manual/bounding_box/bounds.rb +49 -0
- data/manual/bounding_box/canvas.rb +24 -0
- data/manual/bounding_box/creation.rb +23 -0
- data/manual/bounding_box/indentation.rb +46 -0
- data/manual/bounding_box/nesting.rb +45 -0
- data/manual/bounding_box/russian_boxes.rb +40 -0
- data/manual/bounding_box/stretchy.rb +31 -0
- data/manual/document_and_page_options/background.rb +27 -0
- data/manual/document_and_page_options/document_and_page_options.rb +31 -0
- data/manual/document_and_page_options/metadata.rb +23 -0
- data/manual/document_and_page_options/page_margins.rb +38 -0
- data/manual/document_and_page_options/page_size.rb +34 -0
- data/manual/example_file.rb +116 -0
- data/manual/example_helper.rb +411 -0
- data/manual/example_package.rb +53 -0
- data/manual/example_section.rb +46 -0
- data/manual/graphics/circle_and_ellipse.rb +22 -0
- data/manual/graphics/color.rb +24 -0
- data/manual/graphics/common_lines.rb +28 -0
- data/manual/graphics/fill_and_stroke.rb +42 -0
- data/manual/graphics/fill_rules.rb +37 -0
- data/manual/graphics/gradients.rb +37 -0
- data/manual/graphics/graphics.rb +58 -0
- data/manual/graphics/helper.rb +24 -0
- data/manual/graphics/line_width.rb +35 -0
- data/manual/graphics/lines_and_curves.rb +41 -0
- data/manual/graphics/polygon.rb +29 -0
- data/manual/graphics/rectangle.rb +21 -0
- data/manual/graphics/rotate.rb +28 -0
- data/manual/graphics/scale.rb +41 -0
- data/manual/graphics/soft_masks.rb +46 -0
- data/manual/graphics/stroke_cap.rb +31 -0
- data/manual/graphics/stroke_dash.rb +48 -0
- data/manual/graphics/stroke_join.rb +30 -0
- data/manual/graphics/translate.rb +29 -0
- data/manual/graphics/transparency.rb +35 -0
- data/manual/images/absolute_position.rb +23 -0
- data/manual/images/fit.rb +21 -0
- data/manual/images/horizontal.rb +25 -0
- data/manual/images/images.rb +40 -0
- data/manual/images/plain_image.rb +18 -0
- data/manual/images/scale.rb +22 -0
- data/manual/images/vertical.rb +28 -0
- data/manual/images/width_and_height.rb +25 -0
- data/manual/layout/boxes.rb +27 -0
- data/manual/layout/content.rb +25 -0
- data/manual/layout/layout.rb +28 -0
- data/manual/layout/simple_grid.rb +23 -0
- data/manual/manual/cover.rb +35 -0
- data/manual/manual/foreword.rb +85 -0
- data/manual/manual/how_to_read_this_manual.rb +41 -0
- data/manual/manual/manual.rb +35 -0
- data/manual/outline/add_subsection_to.rb +61 -0
- data/manual/outline/insert_section_after.rb +47 -0
- data/manual/outline/outline.rb +32 -0
- data/manual/outline/sections_and_pages.rb +67 -0
- data/manual/repeatable_content/page_numbering.rb +54 -0
- data/manual/repeatable_content/repeatable_content.rb +31 -0
- data/manual/repeatable_content/repeater.rb +55 -0
- data/manual/repeatable_content/stamp.rb +41 -0
- data/manual/security/encryption.rb +31 -0
- data/manual/security/permissions.rb +38 -0
- data/manual/security/security.rb +28 -0
- data/manual/syntax_highlight.rb +52 -0
- data/manual/table/basic_block.rb +53 -0
- data/manual/table/before_rendering_page.rb +26 -0
- data/manual/table/cell_border_lines.rb +24 -0
- data/manual/table/cell_borders_and_bg.rb +31 -0
- data/manual/table/cell_dimensions.rb +30 -0
- data/manual/table/cell_text.rb +38 -0
- data/manual/table/column_widths.rb +30 -0
- data/manual/table/content_and_subtables.rb +39 -0
- data/manual/table/creation.rb +27 -0
- data/manual/table/filtering.rb +36 -0
- data/manual/table/flow_and_header.rb +17 -0
- data/manual/table/image_cells.rb +33 -0
- data/manual/table/position.rb +29 -0
- data/manual/table/row_colors.rb +20 -0
- data/manual/table/span.rb +30 -0
- data/manual/table/style.rb +22 -0
- data/manual/table/table.rb +52 -0
- data/manual/table/width.rb +27 -0
- data/manual/templates/full_template.rb +25 -0
- data/manual/templates/page_template.rb +48 -0
- data/manual/templates/templates.rb +27 -0
- data/manual/text/alignment.rb +44 -0
- data/manual/text/color.rb +24 -0
- data/manual/text/column_box.rb +32 -0
- data/manual/text/fallback_fonts.rb +37 -0
- data/manual/text/font.rb +41 -0
- data/manual/text/font_size.rb +45 -0
- data/manual/text/font_style.rb +23 -0
- data/manual/text/formatted_callbacks.rb +60 -0
- data/manual/text/formatted_text.rb +54 -0
- data/manual/text/free_flowing_text.rb +51 -0
- data/manual/text/group.rb +29 -0
- data/manual/text/inline.rb +43 -0
- data/manual/text/kerning_and_character_spacing.rb +39 -0
- data/manual/text/leading.rb +25 -0
- data/manual/text/line_wrapping.rb +41 -0
- data/manual/text/paragraph_indentation.rb +26 -0
- data/manual/text/positioned_text.rb +38 -0
- data/manual/text/registering_families.rb +48 -0
- data/manual/text/rendering_and_color.rb +37 -0
- data/manual/text/right_to_left_text.rb +43 -0
- data/manual/text/rotation.rb +43 -0
- data/manual/text/single_usage.rb +37 -0
- data/manual/text/text.rb +75 -0
- data/manual/text/text_box_excess.rb +32 -0
- data/manual/text/text_box_extensions.rb +45 -0
- data/manual/text/text_box_overflow.rb +44 -0
- data/manual/text/utf8.rb +28 -0
- data/{examples/m17n → manual/text}/win_ansi_charset.rb +14 -10
- data/prawn.gemspec +18 -12
- data/spec/acceptance/png.rb +23 -0
- data/spec/annotations_spec.rb +16 -32
- data/spec/bounding_box_spec.rb +128 -15
- data/spec/cell_spec.rb +169 -38
- data/spec/column_box_spec.rb +33 -0
- data/spec/destinations_spec.rb +5 -5
- data/spec/document_spec.rb +150 -104
- data/spec/extensions/encoding_helpers.rb +10 -0
- data/spec/extensions/mocha.rb +1 -0
- data/spec/filters_spec.rb +34 -0
- data/spec/font_metric_cache_spec.rb +52 -0
- data/spec/font_spec.rb +183 -97
- data/spec/formatted_text_arranger_spec.rb +43 -43
- data/spec/formatted_text_box_spec.rb +30 -20
- data/spec/formatted_text_fragment_spec.rb +8 -8
- data/spec/graphics_spec.rb +158 -69
- data/spec/grid_spec.rb +15 -15
- data/spec/image_handler_spec.rb +42 -0
- data/spec/images_spec.rb +49 -24
- data/spec/inline_formatted_text_parser_spec.rb +73 -19
- data/spec/jpg_spec.rb +4 -4
- data/spec/line_wrap_spec.rb +26 -26
- data/spec/measurement_units_spec.rb +6 -6
- data/spec/name_tree_spec.rb +21 -21
- data/spec/object_store_spec.rb +39 -39
- data/spec/outline_spec.rb +93 -53
- data/spec/pdf_object_spec.rb +88 -86
- data/spec/png_spec.rb +31 -28
- data/spec/reference_spec.rb +32 -32
- data/spec/repeater_spec.rb +25 -11
- data/spec/security_spec.rb +44 -12
- data/spec/snapshot_spec.rb +8 -9
- data/spec/soft_mask_spec.rb +117 -0
- data/spec/span_spec.rb +10 -15
- data/spec/spec_helper.rb +25 -8
- data/spec/stamp_spec.rb +29 -30
- data/spec/stream_spec.rb +58 -0
- data/spec/stroke_styles_spec.rb +36 -18
- data/spec/table/span_dummy_spec.rb +17 -0
- data/spec/table_spec.rb +697 -105
- data/spec/template_spec.rb +108 -54
- data/spec/text_at_spec.rb +18 -17
- data/spec/text_box_spec.rb +111 -62
- data/spec/text_rendering_mode_spec.rb +5 -5
- data/spec/text_spacing_spec.rb +4 -4
- data/spec/text_spec.rb +57 -49
- data/spec/transparency_spec.rb +5 -5
- metadata +421 -213
- data/data/fonts/Action Man.dfont +0 -0
- data/data/fonts/Activa.ttf +0 -0
- data/data/fonts/Chalkboard.ttf +0 -0
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/data/fonts/Dustismo_Roman.ttf +0 -0
- data/data/fonts/comicsans.ttf +0 -0
- data/data/fonts/gkai00mp.ttf +0 -0
- data/data/images/rails.dat +0 -0
- data/data/images/rails.png +0 -0
- data/examples/bounding_box/russian_boxes.rb +0 -37
- data/examples/example_helper.rb +0 -11
- data/examples/general/context_sensitive_headers.rb +0 -38
- data/examples/graphics/cmyk.rb +0 -13
- data/examples/graphics/gradient.rb +0 -23
- data/examples/graphics/png_types.rb +0 -23
- data/examples/graphics/remote_images.rb +0 -13
- data/examples/m17n/full_win_ansi_character_list.rb +0 -20
- data/examples/m17n/sjis.rb +0 -29
- data/examples/table/bill.rb +0 -54
- data/examples/table/header.rb +0 -15
- data/examples/text/font_calculations.rb +0 -92
- data/examples/text/hyphenation.rb +0 -45
- data/examples/text/indent_paragraphs.rb +0 -24
- data/lib/prawn/core.rb +0 -85
- data/lib/prawn/core/text/formatted/arranger.rb +0 -294
- data/lib/prawn/core/text/formatted/line_wrap.rb +0 -273
- data/lib/prawn/core/text/formatted/wrap.rb +0 -153
- data/lib/prawn/graphics/gradient.rb +0 -84
- 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
|
@@ -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
|
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 =
|
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
|
-
#
|
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
|
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
|
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?(
|
59
|
+
reference = if args.first.is_a?(PDF::Core::Reference)
|
60
60
|
args.first
|
61
61
|
else
|
62
|
-
|
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(
|
144
|
+
def import_page(input, page_num)
|
145
145
|
@loaded_objects = {}
|
146
|
-
|
147
|
-
|
146
|
+
if template_id = indexed_template(input, page_num)
|
147
|
+
return template_id
|
148
148
|
end
|
149
149
|
|
150
|
-
|
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?
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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 :
|
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
|