Commit a5eff16a authored by Cédric Boutillier's avatar Cédric Boutillier

Imported Upstream version 1.0.0~rc2+dfsg

parent b6010690
......@@ -7,3 +7,4 @@ Gemfile.lock
drop_to_console.rb
manual.pdf
/doc
/bin
--color
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- 2.0.0
- rbx-18mode
- rbx-19mode
- jruby-18mode
- jruby-19mode
matrix:
allow_failures:
- rvm: 2.0.0
- rvm: rbx-18mode
- rvm: rbx-19mode
- rvm: jruby-18mode
- rvm: jruby-19mode
source :rubygems
gem "ttfunk", "~>1.0.3"
gem "pdf-reader", "~>0.9.0"
gem "pdf-reader", "~> 1.2"
gem "ruby-rc4"
gem "afm"
group :development do
gem "coderay", "~> 1.0.7"
gem "rdoc"
end
group :test do
gem "pdf-inspector", "~>1.0.1", :require => "pdf/inspector"
gem "test-spec"
gem "mocha"
gem "test-unit", "1.2.3", :platforms => [:ruby_19, :mingw_19]
gem "pdf-inspector", "~> 1.0.2", :require => "pdf/inspector"
gem "rspec"
gem "mocha", :require => false
gem "rake"
end
# Prawn: Fast, Nimble PDF Generation For Ruby
[![Build Status](https://secure.travis-ci.org/prawnpdf/prawn.png)](http://travis-ci.org/prawnpdf/prawn)
Prawn is a pure Ruby PDF generation library that provides a lot of great functionality while trying to remain simple and reasonably performant. Here are some of the important features we provide:
* Vector drawing support, including lines, polygons, curves, ellipses, etc.
......@@ -33,7 +35,7 @@ You can also install from git if you'd like, the _master_ branch contains the la
## Release Policies
We may introduce backwards incompatible changes each time our minor version number is bumped, but that any tiny version number bump should be bug fixes and internal changes only. Be sure to read the release notes each time we cut a new release and lock your gems accordingly. You can find the project CHANGELOG at: https://github.com/sandal/prawn/wiki/CHANGELOG
We may introduce backwards incompatible changes each time our minor version number is bumped, but that any tiny version number bump should be bug fixes and internal changes only. Be sure to read the release notes each time we cut a new release and lock your gems accordingly. You can find the project CHANGELOG at: https://github.com/prawnpdf/prawn/wiki/CHANGELOG
## Hello World!
......@@ -73,7 +75,7 @@ Please make your posts to the list as specific as possible, including code sampl
If you've found a bug, want to submit a patch, or have a feature request, please enter a ticket into our github tracker:
<http://github.com/sandal/prawn/issues>
<http://github.com/prawnpdf/prawn/issues>
We strongly encourage bug reports to come with failing tests or at least a reduced example that demonstrates the problem. Similarly, patches should include tests, API documentation, and an update to the manual where relevant. Feel free to send a pull request early though, if you just want some feedback or a code review before preparing your code to be merged.
......@@ -87,7 +89,7 @@ Over the last several years, we've received code contributions from over 50 peop
While he was only with us for a short time before moving on to other things, we'd also like to thank Prawn core team emeritus Jamis Buck for his contributions. He was responsible for introducing font subsetting as well as the first implementation of our inline formatting support.
You can find the full list of folks who have at least one patch accepted to Prawn on github at https://github.com/sandal/prawn/contributors
You can find the full list of folks who have at least one patch accepted to Prawn on github at https://github.com/prawnpdf/prawn/contributors
## License
......
require "rubygems"
require "bundler"
Bundler.setup
require 'rake'
require 'rake/testtask'
require "rake/rdoctask"
require "rake/gempackagetask"
require 'rspec/core/rake_task'
require 'rdoc/task'
require 'rubygems/package_task'
task :default => [:test]
task :default => [:spec]
desc "Run all tests, test-spec, mocha, and pdf-reader required"
Rake::TestTask.new do |test|
# test.ruby_opts << "-w" # .should == true triggers a lot of warnings
test.libs << "spec"
test.test_files = Dir[ "spec/*_spec.rb" ]
test.verbose = true
end
desc "Run all rspec files"
RSpec::Core::RakeTask.new("spec")
desc "Show library's code statistics"
task :stats do
......@@ -26,7 +20,7 @@ task :stats do
end
desc "genrates documentation"
Rake::RDocTask.new do |rdoc|
RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include( "README",
"COPYING",
"LICENSE",
......@@ -45,7 +39,7 @@ task :manual do
end
spec = Gem::Specification.load "prawn.gemspec"
Rake::GemPackageTask.new(spec) do |pkg|
Gem::PackageTask.new(spec) do |pkg|
pkg.need_zip = true
pkg.need_tar = true
end
1.0.0.rc2
......@@ -8,7 +8,7 @@ Benchmark.bmbm do |x|
x.report("PNG Type 6") do
N.times do
Prawn::Document.new do
image "#{Prawn::BASEDIR}/data/images/dice.png"
image "#{Prawn::DATADIR}/images/dice.png"
end.render
end
end
......
if RUBY_VERSION =~ /1\.8/
require "rubygems"
class Array
def sample
self[rand(self.length)]
end
end
end
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require "prawn"
require "benchmark"
# Helpers for benchmark
class String
CHARS = ("a".."z").to_a
def self.random(length)
length.times.collect { CHARS.sample }.join
end
end
def data_for_table(columns,rows,string_size)
rows.times.collect { columns.times.collect { String.random(string_size) }}
end
def benchmark_table_generation(columns,rows,string_size,options={})
data = data_for_table(columns,rows,string_size)
Benchmark.bm do |x|
x.report("#{columns}x#{rows} table (#{columns*rows} cells, with #{string_size} char string contents#{", options = #{options.inspect}" unless options.empty?})") do
Prawn::Document.new { table(data,options) }.render
end
end
end
# Slowest case: styled table, which is very squeezed horizontally,
# so text has to be wrapped
benchmark_table_generation(26,50,10, :row_colors => ['FFFFFF','F0F0FF'], :header => true, :cell_style => {:inline_format=>true})
# Try building and rendering tables of different sizes
benchmark_table_generation(10,400,5)
benchmark_table_generation(10,200,5)
benchmark_table_generation(10,100,5)
# Try different optional arguments to Prawn::Document#table
benchmark_table_generation(10,450,5, :cell_style => {:inline_format=>true})
benchmark_table_generation(10,450,5, :row_colors => ['FFFFFF','F0F0FF'], :header => true, :cell_style => {:inline_format=>true})
......@@ -7,7 +7,7 @@ N=2000
Benchmark.bmbm do |x|
x.report("TTF text") do
Prawn::Document.new {
font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
font "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
N.times do
(1..5).each do |i|
draw_text "Hello Prawn", :at => [200, i * 100]
......
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
require 'prawn'
image_file = File.expand_path('../../data/images/prawn.png', __FILE__)
pdf = Prawn::Document.new
pdf.image image_file
pdf.render_file("works.pdf")
require 'mathn' # Re-defines '/' operation !
pdf = Prawn::Document.new
pdf.image image_file
pdf.render_file("broken.pdf")
......@@ -12,7 +12,7 @@ require "prawn"
require "prawn/layout"
Prawn::Document.generate("broken_table.pdf") do
font "#{Prawn::BASEDIR}/data/fonts/comicsans.ttf"
font "#{Prawn::DATADIR}/fonts/comicsans.ttf"
table [["foo", "baaar", "1" ],
["This is","a sample", "2" ],
["Table", "dont\ncha\nknow?", "3" ]],
......
......@@ -13,7 +13,7 @@ require "prawn"
require "prawn/layout"
Prawn::Document.generate("table_with_background_color_problems.pdf") do
font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
font "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
table [["ὕαλον ϕαγεῖν", "baaar", "1" ],
["This is","a sample", "2" ],
["Table", "dont\ncha\nknow?", "3" ],
......
......@@ -7,5 +7,5 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..','lib')
require "prawn/core"
Prawn::Document.generate('png_barcode_issue.pdf') do
image "#{Prawn::BASEDIR}/data/images/barcode_issue.png"
image "#{Prawn::DATADIR}/images/barcode_issue.png"
end
......@@ -18,7 +18,7 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..','lib')
require 'prawn/core'
Prawn::Document.generate("err.pdf") do
font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
font "#{Prawn::DATADIR}/fonts/DejaVuSans.ttf"
text "Hi there"
transaction { text "Nice, thank you" }
end
data/images/fractal.jpg

14.7 KB | W: | H:

data/images/fractal.jpg

16 KB | W: | H:

data/images/fractal.jpg
data/images/fractal.jpg
data/images/fractal.jpg
data/images/fractal.jpg
  • 2-up
  • Swipe
  • Onion skin
data/images/pigs.jpg

9.36 KB | W: | H:

data/images/pigs.jpg

9.48 KB | W: | H:

data/images/pigs.jpg
data/images/pigs.jpg
data/images/pigs.jpg
data/images/pigs.jpg
  • 2-up
  • Swipe
  • Onion skin
data/images/stef.jpg

2.35 KB | W: | H:

data/images/stef.jpg

2.35 KB | W: | H:

data/images/stef.jpg
data/images/stef.jpg
data/images/stef.jpg
data/images/stef.jpg
  • 2-up
  • Swipe
  • Onion skin
......@@ -5,9 +5,10 @@
# into the lib/prawn/core/* source tree.
#
module Prawn #:nodoc:
VERSION = "1.0.0.rc1"
VERSION = "1.0.0.rc2"
end
require "prawn/utilities"
require "prawn/core"
require "prawn/text"
require "prawn/graphics"
......@@ -16,6 +17,7 @@ require "prawn/images/image"
require "prawn/images/jpg"
require "prawn/images/png"
require "prawn/stamp"
require "prawn/soft_mask"
require "prawn/security"
require "prawn/document"
require "prawn/font"
......
# coding: utf-8
#
# Why would we ever use Ruby 1.8.7 when we can backport with something
# as simple as this?
#
# Compatibility layer to smooth over differences between Ruby implementations
# The oldest version of Ruby which is supported is MRI 1.8.7
# Ideally, all version-specific or implementation-specific code should be
# kept in this file (but that ideal has not been attained yet)
class String #:nodoc:
def first_line
self.each_line { |line| return line }
end
unless "".respond_to?(:lines)
alias_method :lines, :to_a
unless "".respond_to?(:codepoints)
def codepoints(&block)
if block_given?
unpack("U*").each(&block)
else
unpack("U*")
end
end
end
unless "".respond_to?(:each_char)
def each_char #:nodoc:
# copied from jcode
if "".respond_to?(:encode)
def normalize_to_utf8
begin
encode(Encoding::UTF_8)
rescue
raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " +
"#{text.encoding} can not be transparently converted to UTF-8. " +
"Please ensure the encoding of the string you are attempting " +
"to use is set correctly"
end
end
alias :unicode_characters :each_char
alias :unicode_length :length
else
def normalize_to_utf8
begin
# use unpack as a hackish way to verify the string is valid utf-8
unpack("U*")
return dup
rescue
raise Prawn::Errors::IncompatibleStringEncoding, "The string you " +
"are attempting to render is not encoded in valid UTF-8."
end
end
def unicode_characters
if block_given?
scan(/./m) { |x| yield x }
unpack("U*").each { |c| yield [c].pack("U") }
else
scan(/./m)
unpack("U*").map { |c| [c].pack("U") }
end
end
def unicode_length
unpack("U*").length
end
end
end
......
......@@ -29,7 +29,9 @@ module Prawn
# The base source directory for Prawn as installed on the system
#
BASEDIR = File.expand_path(File.join(dir, '..', '..'))
#
BASEDIR = File.expand_path(File.join(dir, '..','..'))
DATADIR = File.expand_path(File.join(dir, '..', '..', 'data'))
# Whe set to true, Prawn will verify hash options to ensure only valid keys
# are used. Off by default.
......
......@@ -6,6 +6,7 @@ module Prawn
if options[:template]
@store = Prawn::Core::ObjectStore.new(:template => options[:template])
@store.info.data.merge!(options[:info]) if options[:info]
else
@store = Prawn::Core::ObjectStore.new(:info => options[:info])
end
......@@ -41,7 +42,7 @@ module Prawn
def normalize_metadata(options)
options[:info] ||= {}
options[:info][:Creator] ||= "Prawn"
options[:info][:Producer] = "Prawn"
options[:info][:Producer] ||= "Prawn"
info = options[:info]
end
......
......@@ -141,16 +141,32 @@ module Prawn
# Imports nothing and returns nil if the requested page number doesn't
# exist. page_num is 1 indexed, so 1 indicates the first page.
#
def import_page(filename, page_num)
def import_page(input, page_num)
@loaded_objects = {}
unless File.file?(filename)
raise ArgumentError, "#{filename} does not exist"
if template_id = indexed_template(input, page_num)
return template_id
end
hash = PDF::Reader::ObjectHash.new(filename)
io = if input.respond_to?(:seek) && input.respond_to?(:read)
input
elsif File.file?(input.to_s)
StringIO.new(File.binread(input.to_s))
else
raise ArgumentError, "input must be an IO-like object or a filename"
end
# unless File.file?(filename)
# raise ArgumentError, "#{filename} does not exist"
# end
hash = indexed_hash(input, io)
ref = hash.page_references[page_num - 1]
ref.nil? ? nil : load_object_graph(hash, ref).identifier
if ref.nil?
nil
else
index_template(input, page_num, load_object_graph(hash, ref).identifier)
end
rescue PDF::Reader::MalformedPDFError, PDF::Reader::InvalidObjectError
msg = "Error reading template file. If you are sure it's a valid PDF, it may be a bug."
......@@ -162,6 +178,46 @@ module Prawn
private
# An index for page templates so that their loaded object graph
# can be reused without multiple loading
def template_index
@template_index ||= {}
end
# An index for the read object hash of a pdf template so that the
# object hash does not need to be parsed multiple times when using
# different pages of the pdf as page templates
def hash_index
@hash_index ||= {}
end
# returns the indexed object graph identifier for a template page if
# it exists
def indexed_template(input, page_number)
key = indexing_key(input)
template_index[key] && template_index[key][page_number]
end
# indexes the identifier for a page from a template
def index_template(input, page_number, id)
(template_index[indexing_key(input)] ||= {})[page_number] ||= id
end
# reads and indexes a new IO for a template
# if the IO has been indexed already then the parsed object hash
# is returned directly
def indexed_hash(input, io)
hash_index[indexing_key(input)] ||= PDF::Reader::ObjectHash.new(io)
end
# the index key for the input.
# uses object_id so that both a string filename or an IO stream can be
# indexed and reused provided the same object gets used in multiple page
# template calls.
def indexing_key(input)
input.object_id
end
# returns a nested array of object IDs for all pages in this object store.
#
def get_page_objects(obj)
......
......@@ -63,7 +63,6 @@ module Prawn
document.restore_graphics_state
end
@stamp_dictionary.data[:Length] = @stamp_stream.length + 1
@stamp_dictionary << @stamp_stream
@stamp_stream = nil
......@@ -84,7 +83,7 @@ module Prawn
unless dictionary.data[:Contents].is_a?(Array)
dictionary.data[:Contents] = [content]
end
@content = document.ref(:Length => 0)
@content = document.ref({})
dictionary.data[:Contents] << document.state.store[@content]
document.open_graphics_state
end
......@@ -129,11 +128,9 @@ module Prawn
if dictionary.data[:Contents].is_a?(Array)
dictionary.data[:Contents].each do |stream|
stream.compress_stream if document.compression_enabled?
stream.data[:Length] = stream.stream.size
end
else
content.compress_stream if document.compression_enabled?
content.data[:Length] = content.stream.size
end
end
......@@ -160,7 +157,7 @@ module Prawn
def init_from_object(options)
@dictionary = options[:object_id].to_i
dictionary.data[:Parent] = document.state.store.pages
dictionary.data[:Parent] = document.state.store.pages if options[:page_template]
unless dictionary.data[:Contents].is_a?(Array) # content only on leafs
@content = dictionary.data[:Contents].identifier
......@@ -175,7 +172,7 @@ module Prawn
@size = options[:size] || "LETTER"
@layout = options[:layout] || :portrait
@content = document.ref(:Length => 0)
@content = document.ref({})
content << "q" << "\n"
@dictionary = document.ref(:Type => :Page,
:Parent => document.state.store.pages,
......
......@@ -16,14 +16,21 @@ module Prawn
if "".respond_to?(:encode)
# Ruby 1.9+
def utf8_to_utf16(str)
"\xFE\xFF".force_encoding("UTF-16BE") + str.encode("UTF-16BE")
utf16 = "\xFE\xFF".force_encoding("UTF-16BE") + str.encode("UTF-16BE")
end
# encodes any string into a hex representation. The result is a string
# with only 0-9 and a-f characters. That result is valid ASCII so tag
# it as such to account for behaviour of different ruby VMs
def string_to_hex(str)
str.unpack("H*").first.force_encoding("ascii")
end
else
# Ruby 1.8
def utf8_to_utf16(str)
utf16 = "\xFE\xFF"
str.unpack("U*").each do |cp|
str.codepoints do |cp|
if cp < 0x10000 # Basic Multilingual Plane
utf16 << [cp].pack("n")
else
......@@ -36,6 +43,10 @@ module Prawn
utf16
end
def string_to_hex(str)
str.unpack("H*").first
end
end
# Serializes Ruby objects to their PDF equivalents. Most primitive objects
......@@ -57,7 +68,13 @@ module Prawn
when NilClass then "null"
when TrueClass then "true"
when FalseClass then "false"
when Numeric then String(obj)
when Numeric
if (str = String(obj)) =~ /e/i
# scientific notation is not supported in PDF
sprintf("%.16f", obj).gsub(/\.?0+\z/, "")
else
str
end
when Array
"[" << obj.map { |e| PdfObject(e, in_content_stream) }.join(' ') << "]"
when Prawn::Core::LiteralString
......@@ -71,7 +88,7 @@ module Prawn
"<" << obj.unpack("H*").first << ">"
when String
obj = utf8_to_utf16(obj) unless in_content_stream
"<" << obj.unpack("H*").first << ">"
"<" << string_to_hex(obj) << ">"
when Symbol
"/" + obj.to_s.unpack("C*").map { |n|
if n < 33 || n > 126 || [35,40,41,47,60,62].include?(n)
......
......@@ -34,7 +34,9 @@ module Prawn
def <<(data)
raise 'Cannot add data to a stream that is compressed' if @compressed
(@stream ||= "") << data
(@stream ||= "") << data
@data[:Length] = @stream.length
@stream
end
def to_s
......@@ -44,7 +46,7 @@ module Prawn
def compress_stream
@stream = Zlib::Deflate.deflate(@stream)
@data[:Filter] = :FlateDecode
@data[:Length] ||= @stream.length
@data[:Length] = @stream.length
@compressed = true
end
......@@ -100,6 +102,8 @@ module Prawn
obj.values.map{|v| [v] + referenced_objects(v) }
when Array
obj.map{|v| [v] + referenced_objects(v) }
when OutlineRoot, OutlineItem
referenced_objects(obj.to_hash)
else []
end.flatten.grep(self.class)
end
......
......@@ -131,11 +131,11 @@ module Prawn
#
# Call with an empty array to turn off fallback fonts
#
# file = "#{Prawn::BASEDIR}/data/fonts/gkai00mp.ttf"
# file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
# font_families["Kai"] = {
# :normal => { :file => file, :font => "Kai" }
# }
# file = "#{Prawn::BASEDIR}/data/fonts/Action Man.dfont"
# file = "#{Prawn::DATADIR}/fonts/Action Man.dfont"
# font_families["Action Man"] = {
# :normal => { :file => file, :font => "ActionMan" },
# }
......@@ -186,10 +186,10 @@ module Prawn
#
def text_rendering_mode(mode=nil)
return @text_rendering_mode || :fill if mode.nil?
unless MODES.keys.include?(mode)
unless MODES.key?(mode)
raise ArgumentError, "mode must be between one of #{MODES.keys.join(', ')} (#{mode})"
end
original_mode = text_rendering_mode
original_mode = @text_rendering_mode || :fill
if original_mode == mode
yield
else
......
......@@ -71,6 +71,9 @@ module Prawn
def apply_font_settings_and_add_fragment_to_line(fragment)
result = nil
@arranger.apply_font_settings do
# if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
# and zero-width spaces will be different
set_soft_hyphen_and_zero_width_space
result = add_fragment_to_line(fragment)
end
result
......@@ -141,11 +144,11 @@ module Prawn
end
def soft_hyphen
@document.font.normalize_encoding(Prawn::Text::SHY)
@soft_hyphen
end
def zero_width_space
@document.font.unicode? ? Prawn::Text::ZWSP : ""
@zero_width_space
end