prawn 0.15.0 → 1.0.0.rc1
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.
- data/COPYING +2 -2
- data/LICENSE +1 -1
- data/README.md +96 -0
- data/Rakefile +27 -30
- 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/16bit.alpha +0 -0
- data/data/images/16bit.dat +0 -0
- data/data/images/dice.alpha +0 -0
- data/data/images/dice.dat +0 -0
- data/data/images/page_white_text.alpha +0 -0
- data/data/images/page_white_text.dat +0 -0
- data/data/images/rails.dat +0 -0
- data/data/images/rails.png +0 -0
- data/data/pdfs/nested_pages.pdf +13 -13
- data/lib/prawn.rb +21 -85
- data/lib/prawn/compatibility.rb +51 -0
- data/lib/prawn/core.rb +85 -0
- data/lib/prawn/core/annotations.rb +61 -0
- data/lib/prawn/core/byte_string.rb +9 -0
- data/lib/prawn/core/destinations.rb +90 -0
- data/lib/prawn/core/document_state.rb +78 -0
- data/lib/prawn/core/literal_string.rb +16 -0
- data/lib/prawn/core/name_tree.rb +177 -0
- data/lib/prawn/core/object_store.rb +264 -0
- data/lib/prawn/core/page.rb +215 -0
- data/lib/prawn/core/pdf_object.rb +108 -0
- data/lib/prawn/core/reference.rb +115 -0
- data/lib/prawn/core/text.rb +268 -0
- data/lib/prawn/core/text/formatted/arranger.rb +294 -0
- data/lib/prawn/core/text/formatted/line_wrap.rb +273 -0
- data/lib/prawn/core/text/formatted/wrap.rb +153 -0
- data/lib/prawn/document.rb +122 -155
- data/lib/prawn/document/bounding_box.rb +7 -36
- data/lib/prawn/document/column_box.rb +10 -38
- data/lib/prawn/document/graphics_state.rb +74 -11
- data/lib/prawn/document/internals.rb +23 -24
- data/lib/prawn/document/page_geometry.rb +136 -0
- data/lib/prawn/document/snapshot.rb +6 -7
- data/lib/prawn/document/span.rb +10 -12
- data/lib/prawn/encoding.rb +10 -9
- data/lib/prawn/errors.rb +30 -15
- data/lib/prawn/font.rb +104 -136
- data/lib/prawn/font/afm.rb +44 -46
- data/lib/prawn/font/dfont.rb +3 -4
- data/lib/prawn/font/ttf.rb +50 -31
- data/lib/prawn/graphics.rb +57 -302
- data/lib/prawn/graphics/cap_style.rb +3 -4
- data/lib/prawn/graphics/color.rb +5 -13
- data/lib/prawn/graphics/dash.rb +31 -53
- data/lib/prawn/graphics/gradient.rb +84 -0
- data/lib/prawn/graphics/join_style.rb +7 -9
- data/lib/prawn/graphics/transformation.rb +9 -10
- data/lib/prawn/graphics/transparency.rb +1 -3
- data/lib/prawn/images.rb +59 -69
- data/lib/prawn/images/image.rb +22 -6
- data/lib/prawn/images/jpg.rb +14 -20
- data/lib/prawn/images/png.rb +118 -61
- data/lib/prawn/layout.rb +15 -10
- data/lib/prawn/layout/grid.rb +54 -66
- data/lib/prawn/measurement_extensions.rb +6 -10
- data/lib/prawn/measurements.rb +21 -27
- data/lib/prawn/outline.rb +308 -6
- data/lib/prawn/repeater.rb +8 -10
- data/lib/prawn/security.rb +33 -55
- data/lib/prawn/security/arcfour.rb +0 -1
- data/lib/prawn/stamp.rb +3 -5
- data/lib/prawn/table.rb +60 -188
- data/lib/prawn/table/cell.rb +44 -272
- data/lib/prawn/table/cell/image.rb +3 -2
- data/lib/prawn/table/cell/in_table.rb +2 -4
- data/lib/prawn/table/cell/subtable.rb +2 -2
- data/lib/prawn/table/cell/text.rb +18 -41
- data/lib/prawn/table/cells.rb +48 -142
- data/lib/prawn/text.rb +25 -32
- data/lib/prawn/text/box.rb +6 -12
- data/lib/prawn/text/formatted.rb +4 -5
- data/lib/prawn/text/formatted/box.rb +59 -96
- data/lib/prawn/text/formatted/fragment.rb +23 -34
- data/lib/prawn/text/formatted/parser.rb +5 -15
- data/prawn.gemspec +13 -24
- data/spec/annotations_spec.rb +32 -16
- data/spec/bounding_box_spec.rb +17 -119
- data/spec/cell_spec.rb +42 -112
- data/spec/destinations_spec.rb +5 -5
- data/spec/document_spec.rb +111 -155
- data/spec/extensions/mocha.rb +0 -1
- data/spec/font_spec.rb +99 -149
- data/spec/formatted_text_arranger_spec.rb +43 -43
- data/spec/formatted_text_box_spec.rb +44 -43
- data/spec/formatted_text_fragment_spec.rb +8 -8
- data/spec/graphics_spec.rb +68 -151
- data/spec/grid_spec.rb +15 -26
- data/spec/images_spec.rb +30 -51
- data/spec/inline_formatted_text_parser_spec.rb +20 -69
- data/spec/jpg_spec.rb +4 -4
- data/spec/line_wrap_spec.rb +28 -28
- data/spec/measurement_units_spec.rb +6 -6
- data/spec/name_tree_spec.rb +112 -0
- data/spec/object_store_spec.rb +106 -17
- data/spec/outline_spec.rb +63 -103
- data/spec/pdf_object_spec.rb +170 -0
- data/spec/png_spec.rb +25 -25
- data/spec/reference_spec.rb +65 -8
- data/spec/repeater_spec.rb +10 -10
- data/spec/security_spec.rb +12 -44
- data/spec/snapshot_spec.rb +7 -7
- data/spec/span_spec.rb +15 -10
- data/spec/spec_helper.rb +8 -32
- data/spec/stamp_spec.rb +30 -29
- data/spec/stroke_styles_spec.rb +18 -36
- data/spec/table_spec.rb +111 -706
- data/spec/template_spec.rb +297 -0
- data/spec/text_at_spec.rb +33 -19
- data/spec/text_box_spec.rb +64 -100
- data/spec/text_rendering_mode_spec.rb +5 -5
- data/spec/text_spacing_spec.rb +4 -4
- data/spec/text_spec.rb +64 -84
- data/spec/transparency_spec.rb +5 -5
- metadata +290 -463
- checksums.yaml +0 -7
- data/.yardopts +0 -10
- data/Gemfile +0 -11
- data/data/images/16bit.color +0 -0
- data/data/images/dice.color +0 -0
- data/data/images/indexed_color.dat +0 -0
- data/data/images/indexed_color.png +0 -0
- data/data/images/page_white_text.color +0 -0
- data/lib/prawn/font_metric_cache.rb +0 -47
- data/lib/prawn/graphics/patterns.rb +0 -138
- data/lib/prawn/image_handler.rb +0 -36
- data/lib/prawn/soft_mask.rb +0 -96
- data/lib/prawn/table/cell/span_dummy.rb +0 -93
- data/lib/prawn/table/column_width_calculator.rb +0 -61
- data/lib/prawn/text/formatted/arranger.rb +0 -290
- data/lib/prawn/text/formatted/line_wrap.rb +0 -266
- data/lib/prawn/text/formatted/wrap.rb +0 -150
- data/lib/prawn/utilities.rb +0 -46
- data/manual/basic_concepts/adding_pages.rb +0 -27
- data/manual/basic_concepts/basic_concepts.rb +0 -34
- data/manual/basic_concepts/creation.rb +0 -39
- data/manual/basic_concepts/cursor.rb +0 -33
- data/manual/basic_concepts/measurement.rb +0 -25
- data/manual/basic_concepts/origin.rb +0 -38
- data/manual/basic_concepts/other_cursor_helpers.rb +0 -40
- data/manual/bounding_box/bounding_box.rb +0 -39
- data/manual/bounding_box/bounds.rb +0 -49
- data/manual/bounding_box/canvas.rb +0 -24
- data/manual/bounding_box/creation.rb +0 -23
- data/manual/bounding_box/indentation.rb +0 -46
- data/manual/bounding_box/nesting.rb +0 -45
- data/manual/bounding_box/russian_boxes.rb +0 -40
- data/manual/bounding_box/stretchy.rb +0 -31
- data/manual/document_and_page_options/background.rb +0 -27
- data/manual/document_and_page_options/document_and_page_options.rb +0 -32
- data/manual/document_and_page_options/metadata.rb +0 -23
- data/manual/document_and_page_options/page_margins.rb +0 -38
- data/manual/document_and_page_options/page_size.rb +0 -34
- data/manual/document_and_page_options/print_scaling.rb +0 -20
- data/manual/example_file.rb +0 -111
- data/manual/example_helper.rb +0 -411
- data/manual/example_package.rb +0 -53
- data/manual/example_section.rb +0 -46
- data/manual/graphics/circle_and_ellipse.rb +0 -22
- data/manual/graphics/color.rb +0 -24
- data/manual/graphics/common_lines.rb +0 -30
- data/manual/graphics/fill_and_stroke.rb +0 -42
- data/manual/graphics/fill_rules.rb +0 -37
- data/manual/graphics/gradients.rb +0 -37
- data/manual/graphics/graphics.rb +0 -58
- data/manual/graphics/helper.rb +0 -24
- data/manual/graphics/line_width.rb +0 -35
- data/manual/graphics/lines_and_curves.rb +0 -41
- data/manual/graphics/polygon.rb +0 -29
- data/manual/graphics/rectangle.rb +0 -21
- data/manual/graphics/rotate.rb +0 -28
- data/manual/graphics/scale.rb +0 -41
- data/manual/graphics/soft_masks.rb +0 -46
- data/manual/graphics/stroke_cap.rb +0 -31
- data/manual/graphics/stroke_dash.rb +0 -48
- data/manual/graphics/stroke_join.rb +0 -30
- data/manual/graphics/translate.rb +0 -29
- data/manual/graphics/transparency.rb +0 -35
- data/manual/images/absolute_position.rb +0 -23
- data/manual/images/fit.rb +0 -21
- data/manual/images/horizontal.rb +0 -25
- data/manual/images/images.rb +0 -40
- data/manual/images/plain_image.rb +0 -18
- data/manual/images/scale.rb +0 -22
- data/manual/images/vertical.rb +0 -28
- data/manual/images/width_and_height.rb +0 -25
- data/manual/layout/boxes.rb +0 -27
- data/manual/layout/content.rb +0 -25
- data/manual/layout/layout.rb +0 -28
- data/manual/layout/simple_grid.rb +0 -23
- data/manual/manual/cover.rb +0 -36
- data/manual/manual/foreword.rb +0 -85
- data/manual/manual/how_to_read_this_manual.rb +0 -41
- data/manual/manual/manual.rb +0 -34
- data/manual/outline/add_subsection_to.rb +0 -61
- data/manual/outline/insert_section_after.rb +0 -47
- data/manual/outline/outline.rb +0 -32
- data/manual/outline/sections_and_pages.rb +0 -67
- data/manual/repeatable_content/page_numbering.rb +0 -54
- data/manual/repeatable_content/repeatable_content.rb +0 -31
- data/manual/repeatable_content/repeater.rb +0 -55
- data/manual/repeatable_content/stamp.rb +0 -41
- data/manual/security/encryption.rb +0 -31
- data/manual/security/permissions.rb +0 -38
- data/manual/security/security.rb +0 -28
- data/manual/syntax_highlight.rb +0 -52
- data/manual/table/basic_block.rb +0 -53
- data/manual/table/before_rendering_page.rb +0 -26
- data/manual/table/cell_border_lines.rb +0 -24
- data/manual/table/cell_borders_and_bg.rb +0 -31
- data/manual/table/cell_dimensions.rb +0 -30
- data/manual/table/cell_text.rb +0 -38
- data/manual/table/column_widths.rb +0 -30
- data/manual/table/content_and_subtables.rb +0 -39
- data/manual/table/creation.rb +0 -27
- data/manual/table/filtering.rb +0 -36
- data/manual/table/flow_and_header.rb +0 -17
- data/manual/table/image_cells.rb +0 -33
- data/manual/table/position.rb +0 -29
- data/manual/table/row_colors.rb +0 -20
- data/manual/table/span.rb +0 -30
- data/manual/table/style.rb +0 -22
- data/manual/table/table.rb +0 -52
- data/manual/table/width.rb +0 -27
- data/manual/text/alignment.rb +0 -44
- data/manual/text/color.rb +0 -24
- data/manual/text/column_box.rb +0 -32
- data/manual/text/fallback_fonts.rb +0 -37
- data/manual/text/font.rb +0 -41
- data/manual/text/font_size.rb +0 -45
- data/manual/text/font_style.rb +0 -23
- data/manual/text/formatted_callbacks.rb +0 -60
- data/manual/text/formatted_text.rb +0 -54
- data/manual/text/free_flowing_text.rb +0 -51
- data/manual/text/group.rb +0 -31
- data/manual/text/inline.rb +0 -43
- data/manual/text/kerning_and_character_spacing.rb +0 -39
- data/manual/text/leading.rb +0 -25
- data/manual/text/line_wrapping.rb +0 -41
- data/manual/text/paragraph_indentation.rb +0 -26
- data/manual/text/positioned_text.rb +0 -38
- data/manual/text/registering_families.rb +0 -48
- data/manual/text/rendering_and_color.rb +0 -37
- data/manual/text/right_to_left_text.rb +0 -43
- data/manual/text/rotation.rb +0 -43
- data/manual/text/single_usage.rb +0 -37
- data/manual/text/text.rb +0 -75
- data/manual/text/text_box_excess.rb +0 -32
- data/manual/text/text_box_extensions.rb +0 -45
- data/manual/text/text_box_overflow.rb +0 -44
- data/manual/text/utf8.rb +0 -28
- data/manual/text/win_ansi_charset.rb +0 -59
- data/spec/acceptance/png.rb +0 -23
- data/spec/column_box_spec.rb +0 -65
- data/spec/extensions/encoding_helpers.rb +0 -9
- data/spec/font_metric_cache_spec.rb +0 -52
- data/spec/image_handler_spec.rb +0 -54
- data/spec/soft_mask_spec.rb +0 -117
- data/spec/table/span_dummy_spec.rb +0 -17
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# annotations.rb : Implements low-level annotation support for PDF
|
4
|
+
#
|
5
|
+
# Copyright November 2008, Jamis Buck. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module Prawn
|
10
|
+
module Core
|
11
|
+
|
12
|
+
# Provides very low-level support for annotations.
|
13
|
+
#
|
14
|
+
module Annotations #:nodoc:
|
15
|
+
|
16
|
+
# Adds a new annotation (section 8.4 in PDF spec) to the current page.
|
17
|
+
# +options+ must be a Hash describing the annotation.
|
18
|
+
#
|
19
|
+
def annotate(options)
|
20
|
+
state.page.dictionary.data[:Annots] ||= []
|
21
|
+
options = sanitize_annotation_hash(options)
|
22
|
+
state.page.dictionary.data[:Annots] << ref!(options)
|
23
|
+
return options
|
24
|
+
end
|
25
|
+
|
26
|
+
# A convenience method for creating Text annotations. +rect+ must be an array
|
27
|
+
# of four numbers, describing the bounds of the annotation. +contents+ should
|
28
|
+
# be a string, to be shown when the annotation is activated.
|
29
|
+
#
|
30
|
+
def text_annotation(rect, contents, options={})
|
31
|
+
options = options.merge(:Subtype => :Text, :Rect => rect, :Contents => contents)
|
32
|
+
annotate(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# A convenience method for creating Link annotations. +rect+ must be an array
|
36
|
+
# of four numbers, describing the bounds of the annotation. The +options+ hash
|
37
|
+
# should include either :Dest (describing the target destination, usually as a
|
38
|
+
# string that has been recorded in the document's Dests tree), or :A (describing
|
39
|
+
# an action to perform on clicking the link), or :PA (for describing a URL to
|
40
|
+
# link to).
|
41
|
+
#
|
42
|
+
def link_annotation(rect, options={})
|
43
|
+
options = options.merge(:Subtype => :Link, :Rect => rect)
|
44
|
+
annotate(options)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def sanitize_annotation_hash(options)
|
50
|
+
options = options.merge(:Type => :Annot)
|
51
|
+
|
52
|
+
if options[:Dest].is_a?(String)
|
53
|
+
options[:Dest] = Prawn::Core::LiteralString.new(options[:Dest])
|
54
|
+
end
|
55
|
+
|
56
|
+
options
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# prawn/core/destinations.rb : Implements destination support for PDF
|
4
|
+
#
|
5
|
+
# Copyright November 2008, Jamis Buck. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
module Prawn
|
10
|
+
module Core
|
11
|
+
module Destinations #:nodoc:
|
12
|
+
|
13
|
+
# The maximum number of children to fit into a single node in the Dests tree.
|
14
|
+
NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc:
|
15
|
+
|
16
|
+
# The Dests name tree in the Name dictionary (see Prawn::Document::Internal#names).
|
17
|
+
# This name tree is used to store named destinations (PDF spec 8.2.1).
|
18
|
+
# (For more on name trees, see section 3.8.4 in the PDF spec.)
|
19
|
+
#
|
20
|
+
def dests
|
21
|
+
names.data[:Dests] ||= ref!(Prawn::Core::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a new destination to the dests name tree (see #dests). The
|
25
|
+
# +reference+ parameter will be converted into a Prawn::Reference if
|
26
|
+
# it is not already one.
|
27
|
+
#
|
28
|
+
def add_dest(name, reference)
|
29
|
+
reference = ref!(reference) unless reference.is_a?(Prawn::Core::Reference)
|
30
|
+
dests.data.add(name, reference)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a Dest specification for a specific location (and optional zoom
|
34
|
+
# level).
|
35
|
+
#
|
36
|
+
def dest_xyz(left, top, zoom=nil, dest_page=page)
|
37
|
+
[dest_page.dictionary, :XYZ, left, top, zoom]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a Dest specification that will fit the given page into the
|
41
|
+
# viewport.
|
42
|
+
#
|
43
|
+
def dest_fit(dest_page=page)
|
44
|
+
[dest_page.dictionary, :Fit]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return a Dest specification that will fit the given page horizontally
|
48
|
+
# into the viewport, aligned vertically at the given top coordinate.
|
49
|
+
#
|
50
|
+
def dest_fit_horizontally(top, dest_page=page)
|
51
|
+
[dest_page.dictionary, :FitH, top]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a Dest specification that will fit the given page vertically
|
55
|
+
# into the viewport, aligned horizontally at the given left coordinate.
|
56
|
+
#
|
57
|
+
def dest_fit_vertically(left, dest_page=page)
|
58
|
+
[dest_page.dictionary, :FitV, left]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return a Dest specification that will fit the given rectangle into the
|
62
|
+
# viewport, for the given page.
|
63
|
+
#
|
64
|
+
def dest_fit_rect(left, bottom, right, top, dest_page=page)
|
65
|
+
[dest_page.dictionary, :FitR, left, bottom, right, top]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a Dest specfication that will fit the given page's bounding box
|
69
|
+
# into the viewport.
|
70
|
+
#
|
71
|
+
def dest_fit_bounds(dest_page=page)
|
72
|
+
[dest_page.dictionary, :FitB]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Same as #dest_fit_horizontally, but works on the page's bounding box
|
76
|
+
# instead of the entire page.
|
77
|
+
#
|
78
|
+
def dest_fit_bounds_horizontally(top, dest_page=page)
|
79
|
+
[dest_page.dictionary, :FitBH, top]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Same as #dest_fit_vertically, but works on the page's bounding box
|
83
|
+
# instead of the entire page.
|
84
|
+
#
|
85
|
+
def dest_fit_bounds_vertically(left, dest_page=page)
|
86
|
+
[dest_page.dictionary, :FitBV, left]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Prawn
|
2
|
+
module Core
|
3
|
+
class DocumentState #:nodoc:
|
4
|
+
def initialize(options)
|
5
|
+
normalize_metadata(options)
|
6
|
+
|
7
|
+
if options[:template]
|
8
|
+
@store = Prawn::Core::ObjectStore.new(:template => options[:template])
|
9
|
+
else
|
10
|
+
@store = Prawn::Core::ObjectStore.new(:info => options[:info])
|
11
|
+
end
|
12
|
+
|
13
|
+
@version = 1.3
|
14
|
+
@pages = []
|
15
|
+
@page = nil
|
16
|
+
@trailer = {}
|
17
|
+
@compress = options.fetch(:compress, false)
|
18
|
+
@encrypt = options.fetch(:encrypt, false)
|
19
|
+
@encryption_key = options[:encryption_key]
|
20
|
+
@optimize_objects = options.fetch(:optimize_objects, false)
|
21
|
+
@skip_encoding = options.fetch(:skip_encoding, false)
|
22
|
+
@before_render_callbacks = []
|
23
|
+
@on_page_create_callback = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :store, :version, :pages, :page, :trailer, :compress,
|
27
|
+
:encrypt, :encryption_key, :optimize_objects, :skip_encoding,
|
28
|
+
:before_render_callbacks, :on_page_create_callback
|
29
|
+
|
30
|
+
def populate_pages_from_store(document)
|
31
|
+
return 0 if @store.page_count <= 0 || @pages.size > 0
|
32
|
+
|
33
|
+
count = (1..@store.page_count)
|
34
|
+
@pages = count.map do |index|
|
35
|
+
orig_dict_id = @store.object_id_for_page(index)
|
36
|
+
Prawn::Core::Page.new(document, :object_id => orig_dict_id)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def normalize_metadata(options)
|
42
|
+
options[:info] ||= {}
|
43
|
+
options[:info][:Creator] ||= "Prawn"
|
44
|
+
options[:info][:Producer] = "Prawn"
|
45
|
+
|
46
|
+
info = options[:info]
|
47
|
+
end
|
48
|
+
|
49
|
+
def insert_page(page, page_number)
|
50
|
+
pages.insert(page_number, page)
|
51
|
+
store.pages.data[:Kids].insert(page_number, page.dictionary)
|
52
|
+
store.pages.data[:Count] += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_page_create_action(doc)
|
56
|
+
on_page_create_callback[doc] if on_page_create_callback
|
57
|
+
end
|
58
|
+
|
59
|
+
def before_render_actions(doc)
|
60
|
+
before_render_callbacks.each{ |c| c.call(self) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def page_count
|
64
|
+
pages.length
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_body(output)
|
68
|
+
store.compact if optimize_objects
|
69
|
+
store.each do |ref|
|
70
|
+
ref.offset = output.size
|
71
|
+
output << (@encrypt ? ref.encrypted_object(@encryption_key) :
|
72
|
+
ref.object)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Prawn
|
3
|
+
module Core
|
4
|
+
# This is used to differentiate strings that must be encoded as
|
5
|
+
# a *literal* string, versus those that can be encoded in
|
6
|
+
# the PDF hexadecimal format.
|
7
|
+
#
|
8
|
+
# Some features of the PDF format appear to require that literal
|
9
|
+
# strings be used. One such feature is the /Dest key of a link
|
10
|
+
# annotation; if a hex encoded string is used there, the links
|
11
|
+
# do not work (as tested in Mac OS X Preview, and Adobe Acrobat
|
12
|
+
# Reader).
|
13
|
+
class LiteralString < String #:nodoc:
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# name_tree.rb : Implements NameTree for PDF
|
4
|
+
#
|
5
|
+
# Copyright November 2008, Jamis Buck. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
module Prawn
|
10
|
+
module Core
|
11
|
+
module NameTree #:nodoc:
|
12
|
+
class Node #:nodoc:
|
13
|
+
attr_reader :children
|
14
|
+
attr_reader :limit
|
15
|
+
attr_reader :document
|
16
|
+
attr_accessor :parent
|
17
|
+
attr_accessor :ref
|
18
|
+
|
19
|
+
def initialize(document, limit, parent=nil)
|
20
|
+
@document = document
|
21
|
+
@children = []
|
22
|
+
@limit = limit
|
23
|
+
@parent = parent
|
24
|
+
@ref = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
children.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
leaf? ? children.size : children.inject(0) { |sum, child| sum + child.size }
|
33
|
+
end
|
34
|
+
|
35
|
+
def leaf?
|
36
|
+
children.empty? || children.first.is_a?(Value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add(name, value)
|
40
|
+
self << Value.new(name, value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_hash
|
44
|
+
hash = {}
|
45
|
+
|
46
|
+
hash[:Limits] = [least, greatest] if parent
|
47
|
+
if leaf?
|
48
|
+
hash[:Names] = children if leaf?
|
49
|
+
else
|
50
|
+
hash[:Kids] = children.map { |child| child.ref }
|
51
|
+
end
|
52
|
+
|
53
|
+
return hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def least
|
57
|
+
if leaf?
|
58
|
+
children.first.name
|
59
|
+
else
|
60
|
+
children.first.least
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def greatest
|
65
|
+
if leaf?
|
66
|
+
children.last.name
|
67
|
+
else
|
68
|
+
children.last.greatest
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def <<(value)
|
73
|
+
if children.empty?
|
74
|
+
children << value
|
75
|
+
elsif leaf?
|
76
|
+
children.insert(insertion_point(value), value)
|
77
|
+
split! if children.length > limit
|
78
|
+
else
|
79
|
+
fit = children.detect { |child| child >= value }
|
80
|
+
fit = children.last unless fit
|
81
|
+
fit << value
|
82
|
+
end
|
83
|
+
|
84
|
+
value
|
85
|
+
end
|
86
|
+
|
87
|
+
def >=(value)
|
88
|
+
children.empty? || children.last >= value
|
89
|
+
end
|
90
|
+
|
91
|
+
def split!
|
92
|
+
if parent
|
93
|
+
parent.split(self)
|
94
|
+
else
|
95
|
+
left, right = new_node(self), new_node(self)
|
96
|
+
split_children(self, left, right)
|
97
|
+
children.replace([left, right])
|
98
|
+
end
|
99
|
+
end
|
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
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def split(node)
|
116
|
+
new_child = new_node(self)
|
117
|
+
split_children(node, node, new_child)
|
118
|
+
index = children.index(node)
|
119
|
+
children.insert(index+1, new_child)
|
120
|
+
split! if children.length > limit
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def new_node(parent=nil)
|
126
|
+
node = Node.new(document, limit, parent)
|
127
|
+
node.ref = document.ref!(node)
|
128
|
+
return node
|
129
|
+
end
|
130
|
+
|
131
|
+
def split_children(node, left, right)
|
132
|
+
half = (node.limit+1)/2
|
133
|
+
|
134
|
+
left_children, right_children = node.children[0...half], node.children[half..-1]
|
135
|
+
|
136
|
+
left.children.replace(left_children)
|
137
|
+
right.children.replace(right_children)
|
138
|
+
|
139
|
+
unless node.leaf?
|
140
|
+
left_children.each { |child| child.parent = left }
|
141
|
+
right_children.each { |child| child.parent = right }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def insertion_point(value)
|
146
|
+
children.each_with_index do |child, index|
|
147
|
+
return index if child >= value
|
148
|
+
end
|
149
|
+
return children.length
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Value #:nodoc:
|
154
|
+
include Comparable
|
155
|
+
|
156
|
+
attr_reader :name
|
157
|
+
attr_reader :value
|
158
|
+
|
159
|
+
def initialize(name, value)
|
160
|
+
@name, @value = Prawn::Core::LiteralString.new(name), value
|
161
|
+
end
|
162
|
+
|
163
|
+
def <=>(leaf)
|
164
|
+
name <=> leaf.name
|
165
|
+
end
|
166
|
+
|
167
|
+
def inspect
|
168
|
+
"#<Value: #{name.inspect} : #{value.inspect}>"
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_s
|
172
|
+
"#{name} : #{value}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# prawn/core/object_store.rb : Implements PDF object repository for Prawn
|
4
|
+
#
|
5
|
+
# Copyright August 2009, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
|
10
|
+
require 'pdf/reader'
|
11
|
+
|
12
|
+
module Prawn
|
13
|
+
module Core
|
14
|
+
class ObjectStore #:nodoc:
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
attr_reader :min_version
|
18
|
+
|
19
|
+
BASE_OBJECTS = %w[info pages root]
|
20
|
+
|
21
|
+
def initialize(opts = {})
|
22
|
+
@objects = {}
|
23
|
+
@identifiers = []
|
24
|
+
|
25
|
+
load_file(opts[:template]) if opts[:template]
|
26
|
+
|
27
|
+
@info ||= ref(opts[:info] || {}).identifier
|
28
|
+
@root ||= ref(:Type => :Catalog).identifier
|
29
|
+
if pages.nil?
|
30
|
+
root.data[:Pages] = ref(:Type => :Pages, :Count => 0, :Kids => [])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ref(data, &block)
|
35
|
+
push(size + 1, data, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def info
|
39
|
+
@objects[@info]
|
40
|
+
end
|
41
|
+
|
42
|
+
def root
|
43
|
+
@objects[@root]
|
44
|
+
end
|
45
|
+
|
46
|
+
def pages
|
47
|
+
root.data[:Pages]
|
48
|
+
end
|
49
|
+
|
50
|
+
def page_count
|
51
|
+
pages.data[:Count]
|
52
|
+
end
|
53
|
+
|
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
|
56
|
+
# arguments provided.
|
57
|
+
#
|
58
|
+
def push(*args, &block)
|
59
|
+
reference = if args.first.is_a?(Prawn::Core::Reference)
|
60
|
+
args.first
|
61
|
+
else
|
62
|
+
Prawn::Core::Reference.new(*args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
@objects[reference.identifier] = reference
|
66
|
+
@identifiers << reference.identifier
|
67
|
+
reference
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :<<, :push
|
71
|
+
|
72
|
+
def each
|
73
|
+
@identifiers.each do |id|
|
74
|
+
yield @objects[id]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def [](id)
|
79
|
+
@objects[id]
|
80
|
+
end
|
81
|
+
|
82
|
+
def size
|
83
|
+
@identifiers.size
|
84
|
+
end
|
85
|
+
alias_method :length, :size
|
86
|
+
|
87
|
+
def compact
|
88
|
+
# Clear live markers
|
89
|
+
each { |o| o.live = false }
|
90
|
+
|
91
|
+
# Recursively mark reachable objects live, starting from the roots
|
92
|
+
# (the only objects referenced in the trailer)
|
93
|
+
root.mark_live
|
94
|
+
info.mark_live
|
95
|
+
|
96
|
+
# Renumber live objects to eliminate gaps (shrink the xref table)
|
97
|
+
if @objects.any?{ |_, o| !o.live }
|
98
|
+
new_id = 1
|
99
|
+
new_objects = {}
|
100
|
+
new_identifiers = []
|
101
|
+
|
102
|
+
each do |obj|
|
103
|
+
if obj.live
|
104
|
+
obj.identifier = new_id
|
105
|
+
new_objects[new_id] = obj
|
106
|
+
new_identifiers << new_id
|
107
|
+
new_id += 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@objects = new_objects
|
112
|
+
@identifiers = new_identifiers
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# returns the object ID for a particular page in the document. Pages
|
117
|
+
# are indexed starting at 1 (not 0!).
|
118
|
+
#
|
119
|
+
# object_id_for_page(1)
|
120
|
+
# => 5
|
121
|
+
# object_id_for_page(10)
|
122
|
+
# => 87
|
123
|
+
# object_id_for_page(-11)
|
124
|
+
# => 17
|
125
|
+
#
|
126
|
+
def object_id_for_page(k)
|
127
|
+
k -= 1 if k > 0
|
128
|
+
flat_page_ids = get_page_objects(pages).flatten
|
129
|
+
flat_page_ids[k]
|
130
|
+
end
|
131
|
+
|
132
|
+
# imports all objects required to render a page from another PDF. The
|
133
|
+
# objects are added to the current object store, but NOT linked
|
134
|
+
# anywhere.
|
135
|
+
#
|
136
|
+
# The object ID of the root Page object is returned, it's up to the
|
137
|
+
# calling code to link that into the document structure somewhere. If
|
138
|
+
# this isn't done the imported objects will just be removed when the
|
139
|
+
# store is compacted.
|
140
|
+
#
|
141
|
+
# Imports nothing and returns nil if the requested page number doesn't
|
142
|
+
# exist. page_num is 1 indexed, so 1 indicates the first page.
|
143
|
+
#
|
144
|
+
def import_page(filename, page_num)
|
145
|
+
@loaded_objects = {}
|
146
|
+
unless File.file?(filename)
|
147
|
+
raise ArgumentError, "#{filename} does not exist"
|
148
|
+
end
|
149
|
+
|
150
|
+
hash = PDF::Reader::ObjectHash.new(filename)
|
151
|
+
ref = hash.page_references[page_num - 1]
|
152
|
+
|
153
|
+
ref.nil? ? nil : load_object_graph(hash, ref).identifier
|
154
|
+
|
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
|
158
|
+
rescue PDF::Reader::UnsupportedFeatureError
|
159
|
+
msg = "Template file contains unsupported PDF features"
|
160
|
+
raise Prawn::Errors::TemplateError, msg
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
# returns a nested array of object IDs for all pages in this object store.
|
166
|
+
#
|
167
|
+
def get_page_objects(obj)
|
168
|
+
if obj.data[:Type] == :Page
|
169
|
+
obj.identifier
|
170
|
+
elsif obj.data[:Type] == :Pages
|
171
|
+
obj.data[:Kids].map { |kid| get_page_objects(kid) }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# takes a source PDF and uses it as a template for this document.
|
176
|
+
#
|
177
|
+
def load_file(template)
|
178
|
+
unless (template.respond_to?(:seek) && template.respond_to?(:read)) ||
|
179
|
+
File.file?(template)
|
180
|
+
raise ArgumentError, "#{template} does not exist"
|
181
|
+
end
|
182
|
+
|
183
|
+
hash = PDF::Reader::ObjectHash.new(template)
|
184
|
+
src_info = hash.trailer[:Info]
|
185
|
+
src_root = hash.trailer[:Root]
|
186
|
+
@min_version = hash.pdf_version.to_f
|
187
|
+
|
188
|
+
if hash.trailer[:Encrypt]
|
189
|
+
msg = "Template file is an encrypted PDF, it can't be used as a template"
|
190
|
+
raise Prawn::Errors::TemplateError, msg
|
191
|
+
end
|
192
|
+
|
193
|
+
if src_info
|
194
|
+
@info = load_object_graph(hash, src_info).identifier
|
195
|
+
end
|
196
|
+
|
197
|
+
if src_root
|
198
|
+
@root = load_object_graph(hash, src_root).identifier
|
199
|
+
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
|
203
|
+
rescue PDF::Reader::UnsupportedFeatureError
|
204
|
+
msg = "Template file contains unsupported PDF features"
|
205
|
+
raise Prawn::Errors::TemplateError, msg
|
206
|
+
end
|
207
|
+
|
208
|
+
# recurse down an object graph from a source PDF, importing all the
|
209
|
+
# indirect objects we find.
|
210
|
+
#
|
211
|
+
# hash is the PDF::Reader::ObjectHash to extract objects from, object is
|
212
|
+
# the object to extract.
|
213
|
+
#
|
214
|
+
def load_object_graph(hash, object)
|
215
|
+
@loaded_objects ||= {}
|
216
|
+
case object
|
217
|
+
when ::Hash then
|
218
|
+
object.each { |key,value| object[key] = load_object_graph(hash, value) }
|
219
|
+
object
|
220
|
+
when Array then
|
221
|
+
object.map { |item| load_object_graph(hash, item)}
|
222
|
+
when PDF::Reader::Reference then
|
223
|
+
unless @loaded_objects.has_key?(object.id)
|
224
|
+
@loaded_objects[object.id] = ref(nil)
|
225
|
+
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
|
230
|
+
else
|
231
|
+
@loaded_objects[object.id].data = new_obj
|
232
|
+
end
|
233
|
+
end
|
234
|
+
@loaded_objects[object.id]
|
235
|
+
when PDF::Reader::Stream
|
236
|
+
# Stream is a subclass of string, so this is here to prevent the stream
|
237
|
+
# being wrapped in a LiteralString
|
238
|
+
object
|
239
|
+
when String
|
240
|
+
is_utf8?(object) ? object : Prawn::Core::ByteString.new(object)
|
241
|
+
else
|
242
|
+
object
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
ruby_18 do
|
247
|
+
def is_utf8?(str)
|
248
|
+
begin
|
249
|
+
str.unpack("U*")
|
250
|
+
true
|
251
|
+
rescue
|
252
|
+
false
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
ruby_19 do
|
257
|
+
def is_utf8?(str)
|
258
|
+
str.force_encoding("utf-8")
|
259
|
+
str.valid_encoding?
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|