Skip to content

PDF File Generation? There is an API for that!

I’ve heard it several times: how can I export to PDF from Xojo? Sure, there are lots of answers pointing to a bunch of resources, including excellent plug-ins from third parties. But can you accomplish the same thing using an already available API? Yes, there is a remote API for that! The requirement is that your Xojo app will need to have access to Internet … and, of course, you’ll need to do just a bit of coding.

So I have a challenge for you, you can download the already canned Xojo class ready to use … or you can continue reading and discover how easy is to use Xojo to generate your PDF files!

The service we are going to use is the OpenSource software Docverter, able to convert any plain, HTML, Markdown or LaTeX file to PDF, Docx, RTF or ePub using a simple HTTP API. In my tests it worked like a charm from the HTML + CSS returned from Markdown Parser class of my own.

In fact it is possible to use this service stright from a Shell class instance, but I’ve found it more portable and multiplatform to wrap it in a Class so you can simply drop it in your project, passing the file or files you want, and specifying the conversion you want to get back; as simple as that!

For that API wrapper we are going to create a new class derived from HTTPSocket. Let’s name it HTTPDocConverter and put as the next step these String constants that will ease its use:

  • kDOCX As String = "docx"; scope: public.
  • kePUB As String = "ePub"; scope: public.
  • kMOBI As String = "mobi"; scope: public.
  • kRTF As String = "rtf"; scope: public.
  • kRemoteAPI As String = "http://c.docverter.com/convert"; scope: private.

I think that is a good habit to get accustomed to constants wherever Strings or other frozen values are involved in order to minimize possible minor bugs in the code.

Now it is time to implement the class Constructor passing along three parameters that will simplify its use:

  • convertDataTo As String. This is the text that identifies the format we want our document converted to and that is why we defined the Constants in the previous step.
  • inputFile As folderItem. A valid FolderItem file pointing to the file we want to use as the source for the conversion process.
  • notification as callback. This is new Delegate Type we will define in the next step, and that will be used by the class to, well, notify of the process completion. This way, your code and UI will not be locked.

So our class Constructor will have the following signature:

Constructor( convertDataTo As String, inputFile As FolderItem, notification as callback )

Once we add the Constructor method, the Xojo IDE will put the right class initialization here, and we will complete the code with the following sentences:

Super.Constructor
registeredCallback = notification
encodeRequest( inputFile, convertDataTo )

Time to add to the class a new Delegate Type (Insert > Delegate), using the following signature in the Inspector Panel:

  • Delegate Name: callback
  • Parameters: content As String, documentType As String
  • Scope: Private.

That is: our class will accept as a callback Delegate any method that accepts two strings. In fact our class will return the already converted data into the argument content, and also the document type used for the conversion into the documentType argument.

With our Delegate already set, it’s time to add the registeredCallback Property to the class, and that will be responsible to point to the Delegated method passed to the class instance throught the Constructor:

  • Name: registeredCallback
  • Type: callback
  • Scope: Private

In fact, let’s add two more Properties:

  • Name: convertedToType. This one will contain the conversion format.
  • Type: String
  • Scope: Private

 

  • Name: receivedData. This property will contain the converted data received from the remote service.
  • Type: String
  • Scope: Private

Encoding the Post Request … the right way

With all our properties set, it is time to add the Method encodeRequest. Here is where we will take care of constructing our Post Header request including the data for the source file we have to send to the remote URL, along all the required stuff. Create the Method with the following signature:

  • Method Name: encodeRequest
  • Parameters: inputFile As Folderitem, convertdataTo as String

And write the following snippet of code in the resulting Code Editor:

Dim formData As New Dictionary
formData.Value("input_files[]") = inputFile
formData.value("from") = "html"
formData.value("to") = convertDataTo
convertedToType = convertDataTo
Dim boundary As String = ""
Boundary = "--" + Right(EncodeHex(MD5(Str(Microseconds))), 24) + "-reQLimIT"
Static CRLF As String = EndOfLine.Windows
Dim data As New MemoryBlock(0)
Dim out As New BinaryStream(data)
For Each key As String In FormData.Keys
  out.Write("--" + Boundary + CRLF)
  If VarType(FormData.Value(Key)) = Variant.TypeString Then
    out.Write("Content-Disposition: form-data; name=""" + key + """" + CRLF + CRLF)
    out.Write(FormData.Value(key) + CRLF)
  Elseif FormData.Value(Key) IsA FolderItem Then
    Dim file As FolderItem = FormData.Value(key)
    out.Write("Content-Disposition: form-data; name=""" + key + """" + "; filename="""+inputFile.Name+""""+ CRLF)
    out.Write("Content-Type: text/html" + CRLF + CRLF)
    Dim bs As BinaryStream = BinaryStream.Open(File)
    out.Write(bs.Read(bs.Length) + CRLF)
    bs.Close
  End If
Next
out.Write("--" + Boundary + "--" + CRLF)
out.Close
Super.SetRequestContent(data, "multipart/form-data; boundary=" + Boundary)

Calling the remote API

Now that the hard part is complete, we are going to add the Method getConvertedFile, in charge of call the remote API:

  • Method Name: getConvertedFile
  • Scope: Public

And write this simple line of code in the Code Editor:

Super.post( kRemoteAPI )

This single line fires all the magic behind the scenes, now in order to get our response we need to add the Event PageReceived to our class, writing the following code in the resulting Code Editor:

If registeredCallback <> Nil Then
  registeredCallback.Invoke content, convertedToType
End If

If you want, you can also add the Error Event, so you can raise or inform about any error during the process.

Testing our class!

In order to test the class, and convert some documents along the way, add a new Method to the project Window (let’s assume its a Xojo Desktop Project). This is the one we will use as Delegate for the callback once we receive the converted data:

  • Method Name: conversionCompleted
  • Parameters: convertedData As String, documentType as String
  • Scope: Public

And write the following code in the resulting Code Editor for the Method:

Dim f As FolderItem = GetSaveFolderItem("", "ConvertedFile." + documentType)
If f <> Nil Then
  Dim tof As TextOutputStream = TextOutputStream.Create(f)
  If tof <> Nil Then
    tof.Write convertedData
    tof.Flush
    tof.Close
  End If
End If

Add now the Open Event to the same Window and write the following code, this is the one in charge of creating our class instance and firing the conversion process. In this example converting the source file to a PDF file (you only need to change the file format constant to get other kind of documents as result):

Dim f As FolderItem = GetOpenFolderItem("")
If f <> Nil Then
  Dim post As New HTTPDocConverter(HTTPDocConverter.kPDF,f, AddressOf conversioncompleted)
  post.getConvertedFile
End If

Creating PDF Files without Internet connection

What if you need to generate PDF files without having an active Internet connection or because you handle confidential information? Well, in these cases I’m sure you’ll find of interest the wkhtmltopdf tool/library.

Add it to your product bundle/folder or directory during the compilation process and you’re set!

In this case, you have to invoke it from a Shell instance passing HTML as the source file and getting the PDF as result. The downside is that the distributable product will be weighter (47 Megabytes)! Not a big problem these days, anyway.

The Last Word

As you have seen, with a couple of methods and Events we were able to create a multiplatform Class that works in 32-bit and 64-bit targets for Desktop, Console, web and Raspberry Pi deployments. I’ve been able to create PDF, DOCX, RTF and even MOBI files in no time!

Javier Rodri­guez has been the Xojo Spanish Evangelist since 2008, he’s also a Developer, Consultant and Trainer who has be using Xojo since 1998. He manages AprendeXojo.com and is the developer behind the GuancheMOS plug-in for Xojo Developers, Markdown Parser for Xojo and the Snippery app, among others

*Read this post in Spanish