Tuesday, May 20, 2008

Annoucing pdftoruby

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/

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