prawn-git 2.0.1
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/.yardopts +10 -0
- data/COPYING +2 -0
- data/GPLv2 +340 -0
- data/GPLv3 +674 -0
- data/Gemfile +11 -0
- data/LICENSE +56 -0
- data/Rakefile +55 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/data/images/16bit.alpha +0 -0
- data/data/images/16bit.color +0 -0
- data/data/images/16bit.png +0 -0
- data/data/images/arrow.png +0 -0
- data/data/images/arrow2.png +0 -0
- data/data/images/dice.alpha +0 -0
- data/data/images/dice.color +0 -0
- data/data/images/dice.png +0 -0
- data/data/images/dice_interlaced.png +0 -0
- data/data/images/fractal.jpg +0 -0
- data/data/images/indexed_color.dat +0 -0
- data/data/images/indexed_color.png +0 -0
- data/data/images/letterhead.jpg +0 -0
- data/data/images/license.md +8 -0
- data/data/images/page_white_text.alpha +0 -0
- data/data/images/page_white_text.color +0 -0
- data/data/images/page_white_text.png +0 -0
- data/data/images/pal_bk.png +0 -0
- data/data/images/pigs.jpg +0 -0
- data/data/images/prawn.png +0 -0
- data/data/images/ruport.png +0 -0
- data/data/images/ruport_data.dat +0 -0
- data/data/images/ruport_transparent.png +0 -0
- data/data/images/ruport_type0.png +0 -0
- data/data/images/stef.jpg +0 -0
- data/data/images/tru256.bmp +0 -0
- data/data/images/web-links.dat +1 -0
- data/data/images/web-links.png +0 -0
- data/data/pdfs/complex_template.pdf +0 -0
- data/data/pdfs/contains_ttf_font.pdf +0 -0
- data/data/pdfs/encrypted.pdf +0 -0
- data/data/pdfs/form.pdf +820 -0
- data/data/pdfs/hexagon.pdf +61 -0
- data/data/pdfs/indirect_reference.pdf +86 -0
- data/data/pdfs/multipage_template.pdf +127 -0
- data/data/pdfs/nested_pages.pdf +118 -0
- data/data/pdfs/page_without_mediabox.pdf +193 -0
- data/data/pdfs/resources_as_indirect_object.pdf +83 -0
- data/data/pdfs/two_hexagons.pdf +90 -0
- data/data/pdfs/version_1_6.pdf +61 -0
- data/data/shift_jis_text.txt +1 -0
- data/lib/prawn.rb +89 -0
- data/lib/prawn/document.rb +706 -0
- data/lib/prawn/document/bounding_box.rb +539 -0
- data/lib/prawn/document/column_box.rb +144 -0
- data/lib/prawn/document/internals.rb +58 -0
- data/lib/prawn/document/span.rb +57 -0
- data/lib/prawn/encoding.rb +87 -0
- data/lib/prawn/errors.rb +80 -0
- data/lib/prawn/font.rb +413 -0
- data/lib/prawn/font/afm.rb +256 -0
- data/lib/prawn/font/dfont.rb +43 -0
- data/lib/prawn/font/ttf.rb +355 -0
- data/lib/prawn/font_metric_cache.rb +46 -0
- data/lib/prawn/graphics.rb +646 -0
- data/lib/prawn/graphics/cap_style.rb +47 -0
- data/lib/prawn/graphics/color.rb +232 -0
- data/lib/prawn/graphics/dash.rb +109 -0
- data/lib/prawn/graphics/join_style.rb +49 -0
- data/lib/prawn/graphics/patterns.rb +126 -0
- data/lib/prawn/graphics/transformation.rb +157 -0
- data/lib/prawn/graphics/transparency.rb +101 -0
- data/lib/prawn/grid.rb +279 -0
- data/lib/prawn/image_handler.rb +44 -0
- data/lib/prawn/images.rb +199 -0
- data/lib/prawn/images/image.rb +49 -0
- data/lib/prawn/images/jpg.rb +91 -0
- data/lib/prawn/images/png.rb +290 -0
- data/lib/prawn/measurement_extensions.rb +50 -0
- data/lib/prawn/measurements.rb +77 -0
- data/lib/prawn/outline.rb +289 -0
- data/lib/prawn/repeater.rb +124 -0
- data/lib/prawn/security.rb +288 -0
- data/lib/prawn/security/arcfour.rb +54 -0
- data/lib/prawn/soft_mask.rb +94 -0
- data/lib/prawn/stamp.rb +136 -0
- data/lib/prawn/text.rb +437 -0
- data/lib/prawn/text/box.rb +141 -0
- data/lib/prawn/text/formatted.rb +7 -0
- data/lib/prawn/text/formatted/arranger.rb +290 -0
- data/lib/prawn/text/formatted/box.rb +614 -0
- data/lib/prawn/text/formatted/fragment.rb +264 -0
- data/lib/prawn/text/formatted/line_wrap.rb +277 -0
- data/lib/prawn/text/formatted/parser.rb +224 -0
- data/lib/prawn/text/formatted/wrap.rb +160 -0
- data/lib/prawn/utilities.rb +46 -0
- data/lib/prawn/version.rb +5 -0
- data/lib/prawn/view.rb +91 -0
- data/manual/absolute_position.pdf +0 -0
- data/manual/basic_concepts/adding_pages.rb +27 -0
- data/manual/basic_concepts/basic_concepts.rb +36 -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/basic_concepts/view.rb +42 -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/contents.rb +29 -0
- data/manual/cover.rb +39 -0
- data/manual/document_and_page_options/background.rb +27 -0
- data/manual/document_and_page_options/document_and_page_options.rb +32 -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/document_and_page_options/print_scaling.rb +20 -0
- data/manual/example_helper.rb +7 -0
- data/manual/graphics/circle_and_ellipse.rb +22 -0
- data/manual/graphics/color.rb +24 -0
- data/manual/graphics/common_lines.rb +30 -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/how_to_read_this_manual.rb +40 -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/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/alternate_page_numbering.rb +32 -0
- data/manual/repeatable_content/page_numbering.rb +54 -0
- data/manual/repeatable_content/repeatable_content.rb +32 -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/table.rb +16 -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 +50 -0
- data/manual/text/free_flowing_text.rb +51 -0
- data/manual/text/inline.rb +41 -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 +34 -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 +47 -0
- data/manual/text/rotation.rb +43 -0
- data/manual/text/single_usage.rb +37 -0
- data/manual/text/text.rb +73 -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 +48 -0
- data/manual/text/utf8.rb +28 -0
- data/manual/text/win_ansi_charset.rb +60 -0
- data/prawn.gemspec +45 -0
- data/spec/acceptance/png.rb +25 -0
- data/spec/annotations_spec.rb +74 -0
- data/spec/bounding_box_spec.rb +510 -0
- data/spec/column_box_spec.rb +65 -0
- data/spec/data/curves.pdf +66 -0
- data/spec/destinations_spec.rb +15 -0
- data/spec/document_spec.rb +748 -0
- data/spec/extensions/encoding_helpers.rb +11 -0
- data/spec/extensions/mocha.rb +46 -0
- data/spec/font_metric_cache_spec.rb +52 -0
- data/spec/font_spec.rb +474 -0
- data/spec/formatted_text_arranger_spec.rb +421 -0
- data/spec/formatted_text_box_spec.rb +705 -0
- data/spec/formatted_text_fragment_spec.rb +298 -0
- data/spec/graphics_spec.rb +683 -0
- data/spec/grid_spec.rb +96 -0
- data/spec/image_handler_spec.rb +54 -0
- data/spec/images_spec.rb +153 -0
- data/spec/inline_formatted_text_parser_spec.rb +564 -0
- data/spec/jpg_spec.rb +25 -0
- data/spec/line_wrap_spec.rb +367 -0
- data/spec/measurement_units_spec.rb +25 -0
- data/spec/outline_spec.rb +430 -0
- data/spec/png_spec.rb +245 -0
- data/spec/reference_spec.rb +25 -0
- data/spec/repeater_spec.rb +160 -0
- data/spec/security_spec.rb +158 -0
- data/spec/soft_mask_spec.rb +79 -0
- data/spec/span_spec.rb +44 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/stamp_spec.rb +160 -0
- data/spec/stroke_styles_spec.rb +211 -0
- data/spec/text_at_spec.rb +143 -0
- data/spec/text_box_spec.rb +1043 -0
- data/spec/text_rendering_mode_spec.rb +45 -0
- data/spec/text_spacing_spec.rb +93 -0
- data/spec/text_spec.rb +557 -0
- data/spec/text_with_inline_formatting_spec.rb +35 -0
- data/spec/transparency_spec.rb +91 -0
- data/spec/view_spec.rb +43 -0
- metadata +509 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# ImageHandler provides a way to register image processors with Prawn
|
4
|
+
#
|
5
|
+
# Contributed by Evan Sharp in November 2013.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
module Prawn
|
10
|
+
# @group Extension API
|
11
|
+
|
12
|
+
def self.image_handler
|
13
|
+
@image_handler ||= ImageHandler.new
|
14
|
+
end
|
15
|
+
|
16
|
+
class ImageHandler
|
17
|
+
def initialize
|
18
|
+
@handlers = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(handler)
|
22
|
+
@handlers.delete(handler)
|
23
|
+
@handlers.push handler
|
24
|
+
end
|
25
|
+
|
26
|
+
def register!(handler)
|
27
|
+
@handlers.delete(handler)
|
28
|
+
@handlers.unshift handler
|
29
|
+
end
|
30
|
+
|
31
|
+
def unregister(handler)
|
32
|
+
@handlers.reject!{ |h| h == handler }
|
33
|
+
end
|
34
|
+
|
35
|
+
def find(image_blob)
|
36
|
+
handler = @handlers.find{ |h| h.can_render? image_blob }
|
37
|
+
|
38
|
+
return handler if handler
|
39
|
+
|
40
|
+
raise Prawn::Errors::UnsupportedImageType,
|
41
|
+
"image file is an unrecognised format"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/prawn/images.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# images.rb : Implements PDF image embedding
|
3
|
+
#
|
4
|
+
# Copyright April 2008, James Healy, Gregory Brown. All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
7
|
+
|
8
|
+
require 'digest/sha1'
|
9
|
+
require 'pathname'
|
10
|
+
|
11
|
+
module Prawn
|
12
|
+
|
13
|
+
module Images
|
14
|
+
# @group Stable API
|
15
|
+
|
16
|
+
# Add the image at filename to the current page. Currently only
|
17
|
+
# JPG and PNG files are supported. (Note that processing PNG
|
18
|
+
# images with alpha channels can be processor and memory intensive.)
|
19
|
+
#
|
20
|
+
# Arguments:
|
21
|
+
# <tt>file</tt>:: path to file or an object that responds to #read and #rewind
|
22
|
+
#
|
23
|
+
# Options:
|
24
|
+
# <tt>:at</tt>:: an array [x,y] with the location of the top left corner of the image.
|
25
|
+
# <tt>:position</tt>:: One of (:left, :center, :right) or an x-offset
|
26
|
+
# <tt>:vposition</tt>:: One of (:top, :center, :center) or an y-offset
|
27
|
+
# <tt>:height</tt>:: the height of the image [actual height of the image]
|
28
|
+
# <tt>:width</tt>:: the width of the image [actual width of the image]
|
29
|
+
# <tt>:scale</tt>:: scale the dimensions of the image proportionally
|
30
|
+
# <tt>:fit</tt>:: scale the dimensions of the image proportionally to fit inside [width,height]
|
31
|
+
#
|
32
|
+
# Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do
|
33
|
+
# pigs = "#{Prawn::DATADIR}/images/pigs.jpg"
|
34
|
+
# image pigs, :at => [50,450], :width => 450
|
35
|
+
#
|
36
|
+
# dice = "#{Prawn::DATADIR}/images/dice.png"
|
37
|
+
# image dice, :at => [50, 450], :scale => 0.75
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# If only one of :width / :height are provided, the image will be scaled
|
41
|
+
# proportionally. When both are provided, the image will be stretched to
|
42
|
+
# fit the dimensions without maintaining the aspect ratio.
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# If :at is provided, the image will be place in the current page but
|
46
|
+
# the text position will not be changed.
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# If instead of an explicit filename, an object with a read method is
|
50
|
+
# passed as +file+, you can embed images from IO objects and things
|
51
|
+
# that act like them (including Tempfiles and open-uri objects).
|
52
|
+
#
|
53
|
+
# require "open-uri"
|
54
|
+
#
|
55
|
+
# Prawn::Document.generate("remote_images.pdf") do
|
56
|
+
# image open("http://prawn.majesticseacreature.com/media/prawn_logo.png")
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# This method returns an image info object which can be used to check the
|
60
|
+
# dimensions of an image object if needed.
|
61
|
+
# (See also: Prawn::Images::PNG , Prawn::Images::JPG)
|
62
|
+
#
|
63
|
+
def image(file, options={})
|
64
|
+
Prawn.verify_options [:at, :position, :vposition, :height,
|
65
|
+
:width, :scale, :fit], options
|
66
|
+
|
67
|
+
pdf_obj, info = build_image_object(file)
|
68
|
+
embed_image(pdf_obj, info, options)
|
69
|
+
|
70
|
+
info
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Builds an info object (Prawn::Images::*) and a PDF reference representing
|
75
|
+
# the given image. Return a pair: [pdf_obj, info].
|
76
|
+
#
|
77
|
+
# @private
|
78
|
+
def build_image_object(file)
|
79
|
+
io = verify_and_open_image(file)
|
80
|
+
image_content = io.read
|
81
|
+
image_sha1 = Digest::SHA1.hexdigest(image_content)
|
82
|
+
|
83
|
+
# if this image has already been embedded, just reuse it
|
84
|
+
if image_registry[image_sha1]
|
85
|
+
info = image_registry[image_sha1][:info]
|
86
|
+
image_obj = image_registry[image_sha1][:obj]
|
87
|
+
else
|
88
|
+
# Build the image object
|
89
|
+
info = Prawn.image_handler.find(image_content).new(image_content)
|
90
|
+
|
91
|
+
# Bump PDF version if the image requires it
|
92
|
+
renderer.min_version(info.min_pdf_version) if info.respond_to?(:min_pdf_version)
|
93
|
+
|
94
|
+
# Add the image to the PDF and register it in case we see it again.
|
95
|
+
image_obj = info.build_pdf_object(self)
|
96
|
+
image_registry[image_sha1] = {:obj => image_obj, :info => info}
|
97
|
+
end
|
98
|
+
|
99
|
+
[image_obj, info]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Given a PDF image resource <tt>pdf_obj</tt> that has been added to the
|
103
|
+
# page's resources and an <tt>info</tt> object (the pair returned from
|
104
|
+
# build_image_object), embed the image according to the <tt>options</tt>
|
105
|
+
# given.
|
106
|
+
#
|
107
|
+
# @private
|
108
|
+
def embed_image(pdf_obj, info, options)
|
109
|
+
# find where the image will be placed and how big it will be
|
110
|
+
w,h = info.calc_image_dimensions(options)
|
111
|
+
|
112
|
+
if options[:at]
|
113
|
+
x,y = map_to_absolute(options[:at])
|
114
|
+
else
|
115
|
+
x,y = image_position(w,h,options)
|
116
|
+
move_text_position h
|
117
|
+
end
|
118
|
+
|
119
|
+
# add a reference to the image object to the current page
|
120
|
+
# resource list and give it a label
|
121
|
+
label = "I#{next_image_id}"
|
122
|
+
state.page.xobjects.merge!(label => pdf_obj)
|
123
|
+
|
124
|
+
cm_params = PDF::Core.real_params([ w, 0, 0, h, x, y - h])
|
125
|
+
renderer.add_content("\nq\n#{cm_params} cm\n/#{label} Do\nQ")
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def verify_and_open_image(io_or_path)
|
131
|
+
# File or IO
|
132
|
+
if io_or_path.respond_to?(:rewind)
|
133
|
+
io = io_or_path
|
134
|
+
# Rewind if the object we're passed is an IO, so that multiple embeds of
|
135
|
+
# the same IO object will work
|
136
|
+
io.rewind
|
137
|
+
# read the file as binary so the size is calculated correctly
|
138
|
+
# guard binmode because some objects acting io-like don't implement it
|
139
|
+
io.binmode if io.respond_to?(:binmode)
|
140
|
+
return io
|
141
|
+
end
|
142
|
+
# String or Pathname
|
143
|
+
io_or_path = Pathname.new(io_or_path)
|
144
|
+
raise ArgumentError, "#{io_or_path} not found" unless io_or_path.file?
|
145
|
+
io = io_or_path.open('rb')
|
146
|
+
io
|
147
|
+
end
|
148
|
+
|
149
|
+
def image_position(w,h,options)
|
150
|
+
options[:position] ||= :left
|
151
|
+
|
152
|
+
y = case options[:vposition]
|
153
|
+
when :top
|
154
|
+
bounds.absolute_top
|
155
|
+
when :center
|
156
|
+
bounds.absolute_top - (bounds.height - h) / 2.0
|
157
|
+
when :bottom
|
158
|
+
bounds.absolute_bottom + h
|
159
|
+
when Numeric
|
160
|
+
bounds.absolute_top - options[:vposition]
|
161
|
+
else
|
162
|
+
determine_y_with_page_flow(h)
|
163
|
+
end
|
164
|
+
|
165
|
+
x = case options[:position]
|
166
|
+
when :left
|
167
|
+
bounds.left_side
|
168
|
+
when :center
|
169
|
+
bounds.left_side + (bounds.width - w) / 2.0
|
170
|
+
when :right
|
171
|
+
bounds.right_side - w
|
172
|
+
when Numeric
|
173
|
+
options[:position] + bounds.left_side
|
174
|
+
end
|
175
|
+
|
176
|
+
return [x,y]
|
177
|
+
end
|
178
|
+
|
179
|
+
def determine_y_with_page_flow(h)
|
180
|
+
if overruns_page?(h)
|
181
|
+
bounds.move_past_bottom
|
182
|
+
end
|
183
|
+
self.y
|
184
|
+
end
|
185
|
+
|
186
|
+
def overruns_page?(h)
|
187
|
+
(self.y - h) < reference_bounds.absolute_bottom
|
188
|
+
end
|
189
|
+
|
190
|
+
def image_registry
|
191
|
+
@image_registry ||= {}
|
192
|
+
end
|
193
|
+
|
194
|
+
def next_image_id
|
195
|
+
@image_counter ||= 0
|
196
|
+
@image_counter += 1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# image.rb : Base class for image info objects
|
3
|
+
#
|
4
|
+
# Copyright September 2011, Brad Ediger. All rights reserved.
|
5
|
+
#
|
6
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
7
|
+
|
8
|
+
require 'digest/sha1'
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
module Images
|
12
|
+
class Image
|
13
|
+
# @group Extension API
|
14
|
+
|
15
|
+
def calc_image_dimensions(options)
|
16
|
+
w = options[:width] || width
|
17
|
+
h = options[:height] || height
|
18
|
+
|
19
|
+
if options[:width] && !options[:height]
|
20
|
+
wp = w / width.to_f
|
21
|
+
w = width * wp
|
22
|
+
h = height * wp
|
23
|
+
elsif options[:height] && !options[:width]
|
24
|
+
hp = h / height.to_f
|
25
|
+
w = width * hp
|
26
|
+
h = height * hp
|
27
|
+
elsif options[:scale]
|
28
|
+
w = width * options[:scale]
|
29
|
+
h = height * options[:scale]
|
30
|
+
elsif options[:fit]
|
31
|
+
bw, bh = options[:fit]
|
32
|
+
bp = bw / bh.to_f
|
33
|
+
ip = width / height.to_f
|
34
|
+
if ip > bp
|
35
|
+
w = bw
|
36
|
+
h = bw / ip
|
37
|
+
else
|
38
|
+
h = bh
|
39
|
+
w = bh * ip
|
40
|
+
end
|
41
|
+
end
|
42
|
+
self.scaled_width = w
|
43
|
+
self.scaled_height = h
|
44
|
+
|
45
|
+
[w,h]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
# jpg.rb : Extracts the data from a JPG that is needed for embedding
|
4
|
+
#
|
5
|
+
# Copyright April 2008, James Healy. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
module Prawn
|
12
|
+
module Images
|
13
|
+
|
14
|
+
# A convenience class that wraps the logic for extracting the parts
|
15
|
+
# of a JPG image that we need to embed them in a PDF
|
16
|
+
#
|
17
|
+
class JPG < Image
|
18
|
+
# @group Extension API
|
19
|
+
|
20
|
+
attr_reader :width, :height, :bits, :channels
|
21
|
+
attr_accessor :scaled_width, :scaled_height
|
22
|
+
|
23
|
+
JPEG_SOF_BLOCKS = [0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF]
|
24
|
+
|
25
|
+
def self.can_render?(image_blob)
|
26
|
+
image_blob[0, 3].unpack("C*") == [255, 216, 255]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Process a new JPG image
|
30
|
+
#
|
31
|
+
# <tt>:data</tt>:: A binary string of JPEG data
|
32
|
+
#
|
33
|
+
def initialize(data)
|
34
|
+
@data = data
|
35
|
+
d = StringIO.new(@data)
|
36
|
+
d.binmode
|
37
|
+
|
38
|
+
c_marker = 0xff # Section marker.
|
39
|
+
d.seek(2) # Skip the first two bytes of JPEG identifier.
|
40
|
+
loop do
|
41
|
+
marker, code, length = d.read(4).unpack('CCn')
|
42
|
+
raise "JPEG marker not found!" if marker != c_marker
|
43
|
+
|
44
|
+
if JPEG_SOF_BLOCKS.include?(code)
|
45
|
+
@bits, @height, @width, @channels = d.read(6).unpack("CnnC")
|
46
|
+
break
|
47
|
+
end
|
48
|
+
|
49
|
+
d.seek(length - 2, IO::SEEK_CUR)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Build a PDF object representing this image in +document+, and return
|
54
|
+
# a Reference to it.
|
55
|
+
#
|
56
|
+
def build_pdf_object(document)
|
57
|
+
color_space = case channels
|
58
|
+
when 1
|
59
|
+
:DeviceGray
|
60
|
+
when 3
|
61
|
+
:DeviceRGB
|
62
|
+
when 4
|
63
|
+
:DeviceCMYK
|
64
|
+
else
|
65
|
+
raise ArgumentError, 'JPG uses an unsupported number of channels'
|
66
|
+
end
|
67
|
+
|
68
|
+
obj = document.ref!(
|
69
|
+
:Type => :XObject,
|
70
|
+
:Subtype => :Image,
|
71
|
+
:ColorSpace => color_space,
|
72
|
+
:BitsPerComponent => bits,
|
73
|
+
:Width => width,
|
74
|
+
:Height => height
|
75
|
+
)
|
76
|
+
|
77
|
+
# add extra decode params for CMYK images. By swapping the
|
78
|
+
# min and max values from the default, we invert the colours. See
|
79
|
+
# section 4.8.4 of the spec.
|
80
|
+
if color_space == :DeviceCMYK
|
81
|
+
obj.data[:Decode] = [ 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 ]
|
82
|
+
end
|
83
|
+
|
84
|
+
obj.stream << @data
|
85
|
+
obj.stream.filters << :DCTDecode
|
86
|
+
obj
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
# png.rb : Extracts the data from a PNG that is needed for embedding
|
4
|
+
#
|
5
|
+
# Based on some similar code in PDF::Writer by Austin Ziegler
|
6
|
+
#
|
7
|
+
# Copyright April 2008, James Healy. All Rights Reserved.
|
8
|
+
#
|
9
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
10
|
+
|
11
|
+
require 'stringio'
|
12
|
+
require 'enumerator'
|
13
|
+
|
14
|
+
module Prawn
|
15
|
+
module Images
|
16
|
+
# A convenience class that wraps the logic for extracting the parts
|
17
|
+
# of a PNG image that we need to embed them in a PDF
|
18
|
+
#
|
19
|
+
class PNG < Image
|
20
|
+
# @group Extension API
|
21
|
+
|
22
|
+
attr_reader :palette, :img_data, :transparency
|
23
|
+
attr_reader :width, :height, :bits
|
24
|
+
attr_reader :color_type, :compression_method, :filter_method
|
25
|
+
attr_reader :interlace_method, :alpha_channel
|
26
|
+
attr_accessor :scaled_width, :scaled_height
|
27
|
+
|
28
|
+
def self.can_render?(image_blob)
|
29
|
+
image_blob[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Process a new PNG image
|
33
|
+
#
|
34
|
+
# <tt>data</tt>:: A binary string of PNG data
|
35
|
+
#
|
36
|
+
def initialize(data)
|
37
|
+
data = StringIO.new(data.dup)
|
38
|
+
|
39
|
+
data.read(8) # Skip the default header
|
40
|
+
|
41
|
+
@palette = ""
|
42
|
+
@img_data = ""
|
43
|
+
@transparency = {}
|
44
|
+
|
45
|
+
loop do
|
46
|
+
chunk_size = data.read(4).unpack("N")[0]
|
47
|
+
section = data.read(4)
|
48
|
+
case section
|
49
|
+
when 'IHDR'
|
50
|
+
# we can grab other interesting values from here (like width,
|
51
|
+
# height, etc)
|
52
|
+
values = data.read(chunk_size).unpack("NNCCCCC")
|
53
|
+
|
54
|
+
@width = values[0]
|
55
|
+
@height = values[1]
|
56
|
+
@bits = values[2]
|
57
|
+
@color_type = values[3]
|
58
|
+
@compression_method = values[4]
|
59
|
+
@filter_method = values[5]
|
60
|
+
@interlace_method = values[6]
|
61
|
+
when 'PLTE'
|
62
|
+
@palette << data.read(chunk_size)
|
63
|
+
when 'IDAT'
|
64
|
+
@img_data << data.read(chunk_size)
|
65
|
+
when 'tRNS'
|
66
|
+
# This chunk can only occur once and it must occur after the
|
67
|
+
# PLTE chunk and before the IDAT chunk
|
68
|
+
@transparency = {}
|
69
|
+
case @color_type
|
70
|
+
when 3
|
71
|
+
raise Errors::UnsupportedImageType,
|
72
|
+
"Pallete-based transparency in PNG is not currently supported.\n" +
|
73
|
+
"See https://github.com/prawnpdf/prawn/issues/783"
|
74
|
+
when 0
|
75
|
+
# Greyscale. Corresponding to entries in the PLTE chunk.
|
76
|
+
# Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
|
77
|
+
grayval = data.read(chunk_size).unpack("n").first
|
78
|
+
@transparency[:grayscale] = grayval
|
79
|
+
when 2
|
80
|
+
# True colour with proper alpha channel.
|
81
|
+
@transparency[:rgb] = data.read(chunk_size).unpack("nnn")
|
82
|
+
end
|
83
|
+
when 'IEND'
|
84
|
+
# we've got everything we need, exit the loop
|
85
|
+
break
|
86
|
+
else
|
87
|
+
# unknown (or un-important) section, skip over it
|
88
|
+
data.seek(data.pos + chunk_size)
|
89
|
+
end
|
90
|
+
|
91
|
+
data.read(4) # Skip the CRC
|
92
|
+
end
|
93
|
+
|
94
|
+
@img_data = Zlib::Inflate.inflate(@img_data)
|
95
|
+
end
|
96
|
+
|
97
|
+
# number of color components to each pixel
|
98
|
+
#
|
99
|
+
def colors
|
100
|
+
case self.color_type
|
101
|
+
when 0, 3, 4
|
102
|
+
return 1
|
103
|
+
when 2, 6
|
104
|
+
return 3
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# split the alpha channel data from the raw image data in images
|
109
|
+
# where it's required.
|
110
|
+
#
|
111
|
+
def split_alpha_channel!
|
112
|
+
split_image_data if alpha_channel?
|
113
|
+
end
|
114
|
+
|
115
|
+
def alpha_channel?
|
116
|
+
@color_type == 4 || @color_type == 6
|
117
|
+
end
|
118
|
+
|
119
|
+
# Build a PDF object representing this image in +document+, and return
|
120
|
+
# a Reference to it.
|
121
|
+
#
|
122
|
+
def build_pdf_object(document)
|
123
|
+
if compression_method != 0
|
124
|
+
raise Errors::UnsupportedImageType,
|
125
|
+
'PNG uses an unsupported compression method'
|
126
|
+
end
|
127
|
+
|
128
|
+
if filter_method != 0
|
129
|
+
raise Errors::UnsupportedImageType,
|
130
|
+
'PNG uses an unsupported filter method'
|
131
|
+
end
|
132
|
+
|
133
|
+
if interlace_method != 0
|
134
|
+
raise Errors::UnsupportedImageType,
|
135
|
+
'PNG uses unsupported interlace method'
|
136
|
+
end
|
137
|
+
|
138
|
+
# some PNG types store the colour and alpha channel data together,
|
139
|
+
# which the PDF spec doesn't like, so split it out.
|
140
|
+
split_alpha_channel!
|
141
|
+
|
142
|
+
case colors
|
143
|
+
when 1
|
144
|
+
color = :DeviceGray
|
145
|
+
when 3
|
146
|
+
color = :DeviceRGB
|
147
|
+
else
|
148
|
+
raise Errors::UnsupportedImageType,
|
149
|
+
"PNG uses an unsupported number of colors (#{png.colors})"
|
150
|
+
end
|
151
|
+
|
152
|
+
# build the image dict
|
153
|
+
obj = document.ref!(
|
154
|
+
:Type => :XObject,
|
155
|
+
:Subtype => :Image,
|
156
|
+
:Height => height,
|
157
|
+
:Width => width,
|
158
|
+
:BitsPerComponent => bits
|
159
|
+
)
|
160
|
+
|
161
|
+
# append the actual image data to the object as a stream
|
162
|
+
obj << img_data
|
163
|
+
|
164
|
+
obj.stream.filters << {
|
165
|
+
:FlateDecode => {
|
166
|
+
:Predictor => 15,
|
167
|
+
:Colors => colors,
|
168
|
+
:BitsPerComponent => bits,
|
169
|
+
:Columns => width
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
# sort out the colours of the image
|
174
|
+
if palette.empty?
|
175
|
+
obj.data[:ColorSpace] = color
|
176
|
+
else
|
177
|
+
# embed the colour palette in the PDF as a object stream
|
178
|
+
palette_obj = document.ref!({})
|
179
|
+
palette_obj << palette
|
180
|
+
|
181
|
+
# build the color space array for the image
|
182
|
+
obj.data[:ColorSpace] = [:Indexed,
|
183
|
+
:DeviceRGB,
|
184
|
+
(palette.size / 3) -1,
|
185
|
+
palette_obj]
|
186
|
+
end
|
187
|
+
|
188
|
+
# *************************************
|
189
|
+
# add transparency data if necessary
|
190
|
+
# *************************************
|
191
|
+
|
192
|
+
# For PNG color types 0, 2 and 3, the transparency data is stored in
|
193
|
+
# a dedicated PNG chunk, and is exposed via the transparency attribute
|
194
|
+
# of the PNG class.
|
195
|
+
if transparency[:grayscale]
|
196
|
+
# Use Color Key Masking (spec section 4.8.5)
|
197
|
+
# - An array with N elements, where N is two times the number of color
|
198
|
+
# components.
|
199
|
+
val = transparency[:grayscale]
|
200
|
+
obj.data[:Mask] = [val, val]
|
201
|
+
elsif transparency[:rgb]
|
202
|
+
# Use Color Key Masking (spec section 4.8.5)
|
203
|
+
# - An array with N elements, where N is two times the number of color
|
204
|
+
# components.
|
205
|
+
rgb = transparency[:rgb]
|
206
|
+
obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten
|
207
|
+
end
|
208
|
+
|
209
|
+
# For PNG color types 4 and 6, the transparency data is stored as a alpha
|
210
|
+
# channel mixed in with the main image data. The PNG class seperates
|
211
|
+
# it out for us and makes it available via the alpha_channel attribute
|
212
|
+
if alpha_channel?
|
213
|
+
smask_obj = document.ref!(
|
214
|
+
:Type => :XObject,
|
215
|
+
:Subtype => :Image,
|
216
|
+
:Height => height,
|
217
|
+
:Width => width,
|
218
|
+
:BitsPerComponent => bits,
|
219
|
+
:ColorSpace => :DeviceGray,
|
220
|
+
:Decode => [0, 1]
|
221
|
+
)
|
222
|
+
smask_obj.stream << alpha_channel
|
223
|
+
|
224
|
+
smask_obj.stream.filters << {
|
225
|
+
:FlateDecode => {
|
226
|
+
:Predictor => 15,
|
227
|
+
:Colors => 1,
|
228
|
+
:BitsPerComponent => bits,
|
229
|
+
:Columns => width
|
230
|
+
}
|
231
|
+
}
|
232
|
+
obj.data[:SMask] = smask_obj
|
233
|
+
end
|
234
|
+
|
235
|
+
obj
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns the minimum PDF version required to support this image.
|
239
|
+
def min_pdf_version
|
240
|
+
if bits > 8
|
241
|
+
# 16-bit color only supported in 1.5+ (ISO 32000-1:2008 8.9.5.1)
|
242
|
+
1.5
|
243
|
+
elsif alpha_channel?
|
244
|
+
# Need transparency for SMask
|
245
|
+
1.4
|
246
|
+
else
|
247
|
+
1.0
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def split_image_data
|
254
|
+
alpha_bytes = bits / 8
|
255
|
+
color_bytes = colors * bits / 8
|
256
|
+
|
257
|
+
scanline_length = (color_bytes + alpha_bytes) * self.width + 1
|
258
|
+
scanlines = @img_data.bytesize / scanline_length
|
259
|
+
pixels = self.width * self.height
|
260
|
+
|
261
|
+
data = StringIO.new(@img_data)
|
262
|
+
data.binmode
|
263
|
+
|
264
|
+
color_data = [0x00].pack('C') * (pixels * color_bytes + scanlines)
|
265
|
+
color = StringIO.new(color_data)
|
266
|
+
color.binmode
|
267
|
+
|
268
|
+
@alpha_channel = [0x00].pack('C') * (pixels * alpha_bytes + scanlines)
|
269
|
+
alpha = StringIO.new(@alpha_channel)
|
270
|
+
alpha.binmode
|
271
|
+
|
272
|
+
scanlines.times do |line|
|
273
|
+
data.seek(line * scanline_length)
|
274
|
+
|
275
|
+
filter = data.getbyte
|
276
|
+
|
277
|
+
color.putc filter
|
278
|
+
alpha.putc filter
|
279
|
+
|
280
|
+
self.width.times do
|
281
|
+
color.write data.read(color_bytes)
|
282
|
+
alpha.write data.read(alpha_bytes)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
@img_data = color_data
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|