Just a quick note to anyone interested in playing around with the Lulu Publication API and Ruby on Rails, the post I wrote a few months ago has been updated to include the recent change of API URLs, and inclusion of the new API Key and Upload Token calls.
To see an example app, check out the free online baby book creator, which has just launched and is written using pretty much the exact code in my post.
Anyway, the updated Rails/Lulu API howto is just a click away.
Update
I’m very pleased to be able to say that Jonathan Spies has taken the roots of this blog post and produced a fully fledged plugin. I’ve used it on a couple of projects already. Oh how I love the web sometimes.
http://github.com/jspies/raphael.serialize
Introduction
I’ve been playing around with RaphaelJS recently (if you haven’t seen it, check it out) for an ‘avatar builder’ type project to avoid the use of Flash. It’s a really fun bit of kit to use, but I needed a way to actually save the results of the avatar to a bitmap to avoid having heavy javascript libraries every time the avatar is displayed on a page. After a bit of poking around on forums/blogs I thought I’d tie up all the key pieces in one blog post in case anyone else was in the same boat.
The output is essentially a PNG-24 alpha channel bitmap image, which can then be treated as normal with the likes of ImageMagick (I’ve been adding drop shadows to the avatars for example).
Extracting SVG data from Raphael
Unfortunately this isn’t as easy as could be. In browsers that support SVG, we can simply extract all the data from the DOM itself to reproduce it, but as Raphael uses VML on non-SVG browsers (namely, IE) that idea won’t work.
However, the Raphael Javascript object does store the data we need in order to re-produce the SVG. This requires a little bit of Javascript, and for demonstration purposes I’m using PHP.
The Javascript
So the first thing I do build up a JSON object describing the data needed to produce the SVG file, with a little help from jQuery. I’ve put the code for this on this gist. It essentially loops through the ‘paper’ variable that RaphaelJS is using to build up our own array. In mine I was using just paths and images, so that was the only data I needed, however this could easily be expanded to include all the other properties as well.
The Server Side Code (PHP)
All this file does is decode the JSON data into an associative array for use in PHP, loop through the properties and outputs an SVN file. I’ve put the PHP I’m using below as there’s not much to it:
$json = json_decode($_GET['json'], true); $output = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="'.$json[0]['width'].'" height="'.$json[0]['height'].'" xml:space="preserve"><desc>Created with Raphael</desc><defs></defs>'; for ($i=1; $i < count($json); $i++) { if ($json[$i]['type'] == "image") { $base64 = base64_encode(file_get_contents($json[$i]['src'])); $output .= '<image overflow="visible" x="'.$json[$i]["x"].'" y="'.$json[$i]["y"].'" width="'.$json[$i]["width"].'" height="'.$json[$i]["height"].'" transform="'.$json[$i]["transform"].'" preserveAspectRatio="none" xlink:href="data:image/png;base64,'.$base64.'"></image>'; } if ($json[$i]['type'] == "path") { $output .= '<path fill="'.$json[$i]['fill'].'" stroke="'.$json[$i]['stroke'].'" d="'.$json[$i]['path'].'" style="opacity: '.$json[$i]['opacity'].';" opacity="'.$json[$i]['opacity'].'" transform="'.$json[$i]['transform'].'"></path>'; } } $output .= '</svg>';
Save the SVG and send to Apache Batik for Rasterization
So this part is fairly simple. I recommend using Apache Batik for the SVG rasterization as it seemed to have better results compared to ImageMagick. The first part is simply to write the ‘SVG string’ to a file.
file_put_contents('generated/avatar.svg', $output);
Now, just send it across to Apache Batik with the exec() command, and we have a PNG!
exec("java -jar batik-rasterizer.jar /path/to/avatar.svg");
Just the beginning
So this was just a basic introduction to exporting an SVG from Raphael, and will need more work for more complex shapes and transformations. I’m planning on writing a RaphaelJS extension to make all this a bit easier, and perhaps offer out the server side code on a public domain so there’s no need to write it out…
So stay tuned. Or, if you’re already doing this, I’d be more than happy to help out.
In the past week or so I’ve been working on a little side project that is making fairly heavy use of Lulu and their new Publication API. It’s a brilliant system, and although I can’t blame them as the Lulu API is a new release, the documentation is a little thin. After a bit of poking around their Python example on Github and more into Ruby’s HTTP class, I’ve managed to find a pretty neat and easy to implement method which I’ve written about here.
To get the communication between your site and the Lulu Publication API the main libraries we need are;
require 'net/https' require 'uri' require 'curb'
…and 4 URLs, along with the API Key (free to create one) which should be defined as constants (perhaps in config/environment.rb);
LULU_AUTHENTICATE = "https://www.lulu.com/account/endpoints/authenticator.php" LULU_UPLOAD_TOKEN = "http://apps.lulu.com/api/publish/v1/request_upload_token" LULU_UPLOAD = "https://pubapp.lulu.com/api/publish/v1/upload" LULU_PUBLISH = "http://apps.lulu.com/api/publish/v1/create" API_KEY = "ABCDEF123" # replace this with your api key
Curb is simply a hook into the brilliant cURL utility and can be installed as a gem. It’s used here as it makes it dead easy to do a multipart form post, which is required for the ‘upload’ phase of the integration.
[sudo] gem install curb
Step 1 – Obtain your authToken from the Lulu API
To use the Lulu API, all you need is a normal Lulu account, which is free. You’ll need those details to get the ‘authToken’ from the Lulu API which simply authenticates all the requests. We also need to be using https for every call to the API. Anyway, here’s the code you need for step 1 (I’ve defined my LULU_AUTH_USER and LULU_AUTH_PASS in config/environment.rb for neatness).
require 'net/https' require 'uri' require 'curb' # LULU_AUTHENTICATE WITH LULU TO GET OUR auth_token url = URI.parse(LULU_AUTHENTICATE+'?api_key='+LULU_API_KEY) auth_args = { 'username' => LULU_AUTH_USER, 'password' => LULU_AUTH_PASS, 'responseType' => 'json' } http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url.path) request.set_form_data(auth_args) response = http.start {|http| http.request(request) } json = ActiveSupport::JSON.decode(response.body)
Now all we need to do is deal with that response. The last line of the code above simply gives us a decoded JSON object, which can be used like this:
if json['authenticated'] == true # we're logged in! auth_token = json['authToken'] else # something went wrong, let's review what came back and try again puts response.body end
Step 2 – Upload the PDFs
This bit probably took me the longest to work out, but as I mentioned earlier, the Curb gem came to the rescue. Make sure you’ve installed that and then we can form a new request to send our cover PDF and the book PDF. Remember that this will only work if you’ve successfully received an authToken from the authentication stage of the Lulu API.
UPDATE: Since June 2010, you now need to request an Upload Token before being able to upload files:-
url = URI.parse(LULU_UPLOAD_TOKEN+'?api_key='+LULU_API_KEY)
publish_args = {
'auth_user' => LULU_AUTH_USER,
'auth_token' => auth_token,
'api_key' => LULU_API_KEY
}
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url.path)
request.set_form_data(publish_args)
response = http.start {|http| http.request(request) }
responsebody = response.body.gsub("'", "\"") # Sadly our JSON parser won't work unless we do this (single quotes aren't valid JSON)
tokendata = ActiveSupport::JSON.decode(responsebody)
# LULU_UPLOAD THEM pdf_cover = "/tmp/cover.pdf" pdf_book = "/tmp/book.pdf" c = Curl::Easy.new(LULU_UPLOAD) c.multipart_form_post = true c.http_post( Curl::PostField.content('auth_user', LULU_AUTH_USER.to_s), Curl::PostField.content('auth_token', auth_token.to_s), Curl::PostField.content('api_key', LULU_API_KEY), # note the new API key being sent across Curl::PostField.content('upload_token', tokendata['token'].to_s), # add our upload_token to the post fields Curl::PostField.file('cover', pdf_cover), Curl::PostField.file('book', pdf_book) ) json = ActiveSupport::JSON.decode(c.body_str) if json['written_files'] # the files were uploaded! ... business logic here ... else # something went wrong, review the output below puts c.body_str end
It really is as simple as that! Now all we need to do is send the fairly large (but still, easy to interpret) JSON data structure to the final stage of the Lulu API.
Step 3 – Send Publish Data to Lulu Publication API
So this will probably look like the most complicated part, but it’s simply where we configure exactly what sort of book we want; meaning the size, paper/hardback, quality, availability etc etc. Let’s get straight into the data structure.
create_input = { "allow_ratings" => true, "project_type" => "hardcover", "access" => "direct", "bibliography" => { "title" => "My First Book", "authors" => [ { "first_name" => "Ben", "last_name" => "Barnett" } ], "category" => 4, "description" => "What a fabulous book!", "copyright_year" => "2010", "publisher" => "Brushfire Design Ltd", "language" => "EN", "country_code" => "GB" }, "physical_attributes" => { "binding_type" => "casewrap-hardcover", "trim_size" => "SIZE_825x1075", "paper_type" => "regular", "color" => "true" }, "pricing" => [ { "product" => "print", "currency_code" => "GBP", "total_price" => "34.99" } ], "file_info" => { "cover" => [{"mimetype" => "application/pdf", "filename" => "cover.pdf"}], "contents" => [{"mimetype" => "application/pdf", "filename" => "book.pdf"}] } }
The only couple of bits to really worry about are to ensure that you provide just the name of your PDFs, i.e. not the full path (mine were /tmp/cover.pdf) and to set the access type. Supported access levels are ‘private’, ‘public’ (available through search/browse), and ‘direct’.
Hopefully the above layout makes it clear how the JSON will be structured. Obviously at this stage we have a standard Object in Ruby, which we need to convert to a JSON and then send to the Publication API:
url = URI.parse(LULU_PUBLISH) publish_args = { 'auth_user' => LULU_AUTH_USER, 'auth_token' => auth_token, 'project' => create_input.to_json, 'api_key' => LULU_API_KEY # note the new API key being sent across } http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url.path) request.set_form_data(publish_args) response = http.start {|http| http.request(request) } json = ActiveSupport::JSON.decode(response.body) if json['content_id'] # we have a book! ... store the json['content_id'] for reference else # something went wrong ... review output in response.body end
That’s it!
It may look like a lot of code above, but that really is all there is to it. I found the last stage quite fiddly, but it was more to do in terms of getting my PDF to the right DPI and exact sizes etc. You can find some sample PDFs from Lulu’s Book Specifications (at the bottom of the page).
Feel free to get in touch if you need any more help getting up and running.
