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.
- 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
|