Did you ever need to "modify" pdf files, or "recreate" them with
fields changed or added. Do you ever want to generate nice "invoices"
or other "forms" documents, but dont want to break out the ruler to
"recreate" the form.
Now there's a tool that can take a pdf form, and convert it to ruby code.
This "unifies" pdf::reader and pdf::writer libraries.
pdftoruby generates ruby code from a supplied pdf.
You can then "edit" the ruby code to add/delete/modfiy and run the code,
it will generate the pdf file using pdf::writer.
I've used the result code in a rails production application for months now, without issue.
http://code.google.com/p/pdftoruby/
Tuesday, May 20, 2008
Creating PDF::Writer Compatible PNG Files
I've fought PNG for a while. Scanning google countless times to
figure out how to get a PNG to write, in as few as lines of possible.
So my final choice is to use a "clone" of png gem, with a few changes.
One, make sure to write in "binary", so your file opens in the library must
be in a "b" option. The default install of PNG, does not have the "b" option,
which means it will work perfectly fine on linux/unix, and not at all in windows.
The second problem is the "color" space type. The default is 6, which means there
is a transparency byte per pixel. This of course does not work with PDF::writer.
So My version uses "2", which drops the transparency byte, and writes 3 bytes.
The later versions of png gem, switch to using inline, which require a compiler to work. Since it makes installing pdftoruby a pain, I wanted something simple.
class String # :nodoc:
##
# Calculates a CRC using the algorithm in the PNG specification.
def png_crc
unless defined? @@crc then
@@crc = Array.new(256)
256.times do |n|
c = n
8.times do
c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
end
@@crc[n] = c
end
end
c = 0xffffffff
each_byte do |b|
c = @@crc[(c^b) & 0xff] ^ (c >> 8)
end
return c ^ 0xffffffff
end
end
## gsw - Make sure we have version 1 - as version 2 will require major havoc - also fix the "binary" issue
# A pure Ruby Portable Network Graphics (PNG) writer.
#
# http://www.libpng.org/pub/png/spec/1.2/
#
# PNG supports:
# + 8 bit truecolor PNGs
#
# PNG does not support:
# + any other color depth
# + extra data chunks
# + filters
#
# = Example
#
# require 'png'
#
# canvas = PNG::Canvas.new 200, 200
# canvas[100, 100] = PNG::Color::Black
# canvas.line 50, 50, 100, 50, PNG::Color::Blue
# png = PNG.new canvas
# png.save 'blah.png'
class PNG
##
# Creates a PNG chunk of type +type+ that contains +data+.
def self.chunk(type, data="")
[data.size, type, data, (type + data).png_crc].pack("Na*a*N")
end
##
# Creates a new PNG object using +canvas+
def initialize(canvas)
@height = canvas.height
@width = canvas.width
@bits = 8
@data = canvas.data
end
##
# Writes the PNG to +path+.
def save(path)
File.open(path, "wb") do |f|
f.write [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signature
f.write PNG.chunk('IHDR',
[ @height, @width, @bits, 2, 0, 0, 0 ].pack("N2C5"))
# 0 == filter type code "none"
data = @data.map { |row| [0] + row.map { |p| [p.values[0], p.values[1], p.values[2]] } }.flatten
#data = @data.map { |row| [0] + row.map { |p| p.values } }.flatten
f.write PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), 9))
f.write PNG.chunk('IEND', '')
end
end
##
# RGBA colors
class Color
attr_reader :values
##
# Creates a new color with values +red+, +green+, +blue+, and +alpha+.
def initialize(red, green, blue, alpha)
@values = [red, green, blue, alpha]
end
##
# Transparent white
Background = Color.new 0xFF, 0xFF, 0xFF, 0x00
White = Color.new 0xFF, 0xFF, 0xFF, 0xFF
Black = Color.new 0x00, 0x00, 0x00, 0xFF
Gray = Color.new 0x7F, 0x7F, 0x7F, 0xFF
Red = Color.new 0xFF, 0x00, 0x00, 0xFF
Orange = Color.new 0xFF, 0xA5, 0x00, 0xFF
Yellow = Color.new 0xFF, 0xFF, 0x00, 0xFF
Green = Color.new 0x00, 0xFF, 0x00, 0xFF
Blue = Color.new 0x00, 0x00, 0xFF, 0xFF
Purple = Color.new 0XFF, 0x00, 0xFF, 0xFF
##
# Red component
def r; @values[0]; end
##
# Green component
def g; @values[1]; end
##
# Blue component
def b; @values[2]; end
##
# Alpha transparency component
def a; @values[3]; end
##
# Blends +color+ into this color returning a new blended color.
def blend(color)
return Color.new((r * (0xFF - color.a) + color.r * color.a) >> 8,
(g * (0xFF - color.a) + color.g * color.a) >> 8,
(b * (0xFF - color.a) + color.b * color.a) >> 8,
(a * (0xFF - color.a) + color.a * color.a) >> 8)
end
##
# Returns a new color with an alpha value adjusted by +i+.
def intensity(i)
return Color.new(r,b,g,(a*i) >> 8)
end
def inspect # :nodoc:
"#<%s %02x %02x %02x %02x>" % [self.class, *@values]
end
end
##
# PNG canvas
class Canvas
##
# Height of the canvas
attr_reader :height
##
# Width of the canvas
attr_reader :width
##
# Raw data
attr_reader :data
def initialize(height, width, background = Color::White)
@height = height
@width = width
@data = Array.new(@width) { |x| Array.new(@height) { background } }
end
##
# Retrieves the color of the pixel at (+x+, +y+).
def [](x, y)
raise "bad x value #{x} >= #{@height}" if x >= @height
raise "bad y value #{y} >= #{@width}" if y >= @width
@data[y][x]
end
##
# Sets the color of the pixel at (+x+, +y+) to +color+.
def []=(x, y, color)
raise "bad x value #{x} >= #{@height}" if x >= @height
raise "bad y value #{y} >= #{@width}" if y >= @width
@data[y][x] = color
end
##
# Iterates over each pixel in the canvas.
def each
@data.each_with_index do |row, y|
row.each_with_index do |pixel, x|
yield x, y, color
end
end
end
##
# Blends +color+ onto the color at point (+x+, +y+).
def point(x, y, color)
self[x,y] = self[x,y].blend(color)
end
##
# Draws a line using Xiaolin Wu's antialiasing technique.
#
# http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
def line(x0, y0, x1, y1, color)
dx = x1 - x0
sx = dx < 0 ? -1 : 1
dx *= sx # TODO: abs?
dy = y1 - y0
# 'easy' cases
if dy == 0 then
Range.new(*[x0,x1].sort).each do |x|
point(x, y0, color)
end
return
end
if dx == 0 then
(y0..y1).each do |y|
point(x0, y, color)
end
return
end
if dx == dy then
Range.new(*[x0,x1].sort).each do |x|
point(x, y0, color)
y0 += 1
end
return
end
# main loop
point(x0, y0, color)
e_acc = 0
if dy > dx then # vertical displacement
e = (dx << 16) / dy
(y0...y1-1).each do |i|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
x0 = x0 + sx if (e_acc <= e_acc_temp)
w = 0xFF-(e_acc >> 8)
point(x0, y0, color.intensity(w))
y0 = y0 + 1
point(x0 + sx, y0, color.intensity(0xFF-w))
end
point(x1, y1, color)
return
end
# horizontal displacement
e = (dy << 16) / dx
(x0...(x1-sx)).each do |i|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
y0 += 1 if (e_acc <= e_acc_temp)
w = 0xFF-(e_acc >> 8)
point(x0, y0, color.intensity(w))
x0 += sx
point(x0, y0 + 1, color.intensity(0xFF-w))
end
point(x1, y1, color)
end
end
end
figure out how to get a PNG to write, in as few as lines of possible.
So my final choice is to use a "clone" of png gem, with a few changes.
One, make sure to write in "binary", so your file opens in the library must
be in a "b" option. The default install of PNG, does not have the "b" option,
which means it will work perfectly fine on linux/unix, and not at all in windows.
The second problem is the "color" space type. The default is 6, which means there
is a transparency byte per pixel. This of course does not work with PDF::writer.
So My version uses "2", which drops the transparency byte, and writes 3 bytes.
The later versions of png gem, switch to using inline, which require a compiler to work. Since it makes installing pdftoruby a pain, I wanted something simple.
class String # :nodoc:
##
# Calculates a CRC using the algorithm in the PNG specification.
def png_crc
unless defined? @@crc then
@@crc = Array.new(256)
256.times do |n|
c = n
8.times do
c = (c & 1 == 1) ? 0xedb88320 ^ (c >> 1) : c >> 1
end
@@crc[n] = c
end
end
c = 0xffffffff
each_byte do |b|
c = @@crc[(c^b) & 0xff] ^ (c >> 8)
end
return c ^ 0xffffffff
end
end
## gsw - Make sure we have version 1 - as version 2 will require major havoc - also fix the "binary" issue
# A pure Ruby Portable Network Graphics (PNG) writer.
#
# http://www.libpng.org/pub/png/spec/1.2/
#
# PNG supports:
# + 8 bit truecolor PNGs
#
# PNG does not support:
# + any other color depth
# + extra data chunks
# + filters
#
# = Example
#
# require 'png'
#
# canvas = PNG::Canvas.new 200, 200
# canvas[100, 100] = PNG::Color::Black
# canvas.line 50, 50, 100, 50, PNG::Color::Blue
# png = PNG.new canvas
# png.save 'blah.png'
class PNG
##
# Creates a PNG chunk of type +type+ that contains +data+.
def self.chunk(type, data="")
[data.size, type, data, (type + data).png_crc].pack("Na*a*N")
end
##
# Creates a new PNG object using +canvas+
def initialize(canvas)
@height = canvas.height
@width = canvas.width
@bits = 8
@data = canvas.data
end
##
# Writes the PNG to +path+.
def save(path)
File.open(path, "wb") do |f|
f.write [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") # PNG signature
f.write PNG.chunk('IHDR',
[ @height, @width, @bits, 2, 0, 0, 0 ].pack("N2C5"))
# 0 == filter type code "none"
data = @data.map { |row| [0] + row.map { |p| [p.values[0], p.values[1], p.values[2]] } }.flatten
#data = @data.map { |row| [0] + row.map { |p| p.values } }.flatten
f.write PNG.chunk('IDAT', Zlib::Deflate.deflate(data.pack("C*"), 9))
f.write PNG.chunk('IEND', '')
end
end
##
# RGBA colors
class Color
attr_reader :values
##
# Creates a new color with values +red+, +green+, +blue+, and +alpha+.
def initialize(red, green, blue, alpha)
@values = [red, green, blue, alpha]
end
##
# Transparent white
Background = Color.new 0xFF, 0xFF, 0xFF, 0x00
White = Color.new 0xFF, 0xFF, 0xFF, 0xFF
Black = Color.new 0x00, 0x00, 0x00, 0xFF
Gray = Color.new 0x7F, 0x7F, 0x7F, 0xFF
Red = Color.new 0xFF, 0x00, 0x00, 0xFF
Orange = Color.new 0xFF, 0xA5, 0x00, 0xFF
Yellow = Color.new 0xFF, 0xFF, 0x00, 0xFF
Green = Color.new 0x00, 0xFF, 0x00, 0xFF
Blue = Color.new 0x00, 0x00, 0xFF, 0xFF
Purple = Color.new 0XFF, 0x00, 0xFF, 0xFF
##
# Red component
def r; @values[0]; end
##
# Green component
def g; @values[1]; end
##
# Blue component
def b; @values[2]; end
##
# Alpha transparency component
def a; @values[3]; end
##
# Blends +color+ into this color returning a new blended color.
def blend(color)
return Color.new((r * (0xFF - color.a) + color.r * color.a) >> 8,
(g * (0xFF - color.a) + color.g * color.a) >> 8,
(b * (0xFF - color.a) + color.b * color.a) >> 8,
(a * (0xFF - color.a) + color.a * color.a) >> 8)
end
##
# Returns a new color with an alpha value adjusted by +i+.
def intensity(i)
return Color.new(r,b,g,(a*i) >> 8)
end
def inspect # :nodoc:
"#<%s %02x %02x %02x %02x>" % [self.class, *@values]
end
end
##
# PNG canvas
class Canvas
##
# Height of the canvas
attr_reader :height
##
# Width of the canvas
attr_reader :width
##
# Raw data
attr_reader :data
def initialize(height, width, background = Color::White)
@height = height
@width = width
@data = Array.new(@width) { |x| Array.new(@height) { background } }
end
##
# Retrieves the color of the pixel at (+x+, +y+).
def [](x, y)
raise "bad x value #{x} >= #{@height}" if x >= @height
raise "bad y value #{y} >= #{@width}" if y >= @width
@data[y][x]
end
##
# Sets the color of the pixel at (+x+, +y+) to +color+.
def []=(x, y, color)
raise "bad x value #{x} >= #{@height}" if x >= @height
raise "bad y value #{y} >= #{@width}" if y >= @width
@data[y][x] = color
end
##
# Iterates over each pixel in the canvas.
def each
@data.each_with_index do |row, y|
row.each_with_index do |pixel, x|
yield x, y, color
end
end
end
##
# Blends +color+ onto the color at point (+x+, +y+).
def point(x, y, color)
self[x,y] = self[x,y].blend(color)
end
##
# Draws a line using Xiaolin Wu's antialiasing technique.
#
# http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
def line(x0, y0, x1, y1, color)
dx = x1 - x0
sx = dx < 0 ? -1 : 1
dx *= sx # TODO: abs?
dy = y1 - y0
# 'easy' cases
if dy == 0 then
Range.new(*[x0,x1].sort).each do |x|
point(x, y0, color)
end
return
end
if dx == 0 then
(y0..y1).each do |y|
point(x0, y, color)
end
return
end
if dx == dy then
Range.new(*[x0,x1].sort).each do |x|
point(x, y0, color)
y0 += 1
end
return
end
# main loop
point(x0, y0, color)
e_acc = 0
if dy > dx then # vertical displacement
e = (dx << 16) / dy
(y0...y1-1).each do |i|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
x0 = x0 + sx if (e_acc <= e_acc_temp)
w = 0xFF-(e_acc >> 8)
point(x0, y0, color.intensity(w))
y0 = y0 + 1
point(x0 + sx, y0, color.intensity(0xFF-w))
end
point(x1, y1, color)
return
end
# horizontal displacement
e = (dy << 16) / dx
(x0...(x1-sx)).each do |i|
e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
y0 += 1 if (e_acc <= e_acc_temp)
w = 0xFF-(e_acc >> 8)
point(x0, y0, color.intensity(w))
x0 += sx
point(x0, y0 + 1, color.intensity(0xFF-w))
end
point(x1, y1, color)
end
end
end
Subscribe to:
Posts (Atom)