Friday, July 15, 2011

Sending an FDF response back to a PDF document

One way of getting values out of a form-fillable PDF is to use the 'submitForm' javascript method from within the PDF document, like so:

function saveValues(){
    var arr = [];
    //Gather values into an array
    for( var i = 0 ; i < this.numFields; i++){
        arr[i] = this.getNthFieldName(i);
    }
    this.submitForm({
      cURL: "http://foo.com/saveValues#FDF",
      aFields: arr,
      bEmpty: "true",
      cSubmitAs: "HTML"});
}
Note the '#FDF' on the end of the url. If the PDF is viewed in a browser, this tells the browser that it should expect an FDF file as the response. Without this, the browser will treat the response as a regular web page. In this example, 'submitForm' function sends the values in the array 'arr' to 'http://foo.com/saveValues' as HTML key-value pairs. The function also supports a few other options that you can look up later. Perhaps you are wondering why we want an FDF response instead of some other type of response. The answer, in my case, is that it gives the PDF document in the browser a response without having the browser go to a different page. For example, if you want to save some values that the user has entered in the fields of a PDF and send them to a server, you don't want the browser to go to a different page because the user may still be filling in the fields. An FDF response allows you to save without leaving the page, and you can call a javascript function on the return trip. As an example, here is about the simplest FDF you might need:
%FDF-1.2
1 0 obj
<<
/FDF
<<
/JavaScript << /After (app.alert("Data Imported Successfully");) >>
>>
>>
endobj
trailer
<<
/Root 1 0 R
>>
%%EOF
I found that useful snippet here:

http://acrobatusers.com/forum/javascript/submitform-and-server-response-http-post

The important part to note is the 'app.alert'. On the return trip, this will cause the PDF to display 'Data Imported Successfully' in an alert box. However, notice that this is just a javascript call to the app.alert function. Replacing this with a function that exists in the PDF will allow you to call that function (or functions) on the return, allowing custom functionality:

Javascript in the PDF document:
function saveValues(){
    var arr = [];
    //Gather values into an array
    for( var i = 0 ; i < this.numFields; i++){
        arr[i] = this.getNthFieldName(i);
    }
    this.submitForm({
      cURL: "http://foo.com/saveValues#FDF",
      aFields: arr,
      bEmpty: "true",
      cSubmitAs: "HTML"});
}
function valuesSaved(){
    app.alert("Values were saved");
}
function doSomethingElse(text_1, text_2){
    //Do stuff here
}

FDF text to be returned:
%FDF-1.2
1 0 obj
<<
/FDF
<<
/JavaScript << /After (valuesSaved(); doSomethingElse('customText','moreText');) >>
>>
>>
endobj
trailer
<<
/Root 1 0 R
>>
%%EOF

Cache Problems:
One thing to be aware of is that browsers like to cache all files they download. So, if you are trying to send back dynamically generated FDF files, you should probably tell the browser not to cache it. One option is to add a random number on the end of the url you are sending the values to. For example, adding '?number=12345' to the url in the example could help, where '12345' is a random number that changes for each call to submitForm. Sending headers similar to the following might also help:
headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
headers["Pragma"] = "no-cache"
headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"

Fixing a warning: deprecated conversion from string constant to 'char*'

When using C++, you may get the warning "deprecated conversion from string constant to 'char*'" when compiling something similar to the following:

void foo(char *){}
...
foo("hi");

I have found three options to overcome this problem:

1. Add the 'const' keyword to the function definition:
void foo(const char *){}

2. Cast the string when calling the function:
foo((char *)"hi");

3. Create a char array to pass to the function:
char message[] = "hi";
foo(message);

Each might work better than the other 2 for a given situation. I leave it up to you to figure out which is best for your situation.

Running a Qt application without the IDE on Windows

When writing a Qt application, you may find yourself at the point where you need to run the program without the IDE. If you have the paid-for version, that isn't so hard, as everything should be compiled into the executable. But if you are using the LGPL version, your executable needs to have the Qt dlls close at hand, either in the same folder or in your path. I found this forum to be useful:

http://www.qtforum.org/article/24337/error-running-qt-application.html

To make things complicated, there are actually 2 sets of dlls with the same name. tymek's post was what helped me: you have to get the dlls from the \qt\bin folder, not the \bin folder. I believe one set is for debug versions, and one set is for release versions of your program, but I could be wrong.

Friday, March 4, 2011

Compiling FTGL-2.1.3~rc5 on Windows Using the Mingw Compiler Bundled With Qt

If you are trying to compile FTGL on Windows, you may have run into the same problem I did: it's not really set up to make that easy. I couldn't get it to work with Cygwin's configure tool. So I tried to make my own Makefile. After searching a fair amount, I found this page to be useful:

http://www.pitt.edu/~mwr12/compcorner/linuxstuff/ftgl.html

I followed Step 3, adding the following to the bottom of FTGL/ftgl.h just before the last #endif:

#ifdef WIN32
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <windows.h>
#endif

Then, after studying the Makefile for FTGL, and after looking at the script in Step 5 (from the website linked above), I finally came up with a Makefile that worked for me:

# Makefile.in generated by automake 1.10.1 from Makefile.am.
# src/Makefile.  Generated from Makefile.in by configure.

# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.


top_builddir = ..
COMPILER_DIR = C:/Qt/2010.05/mingw
CONFIG_HEADER = $(top_builddir)/config.h
CPPFLAGS =  -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wshadow -Wsign-compare
CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS)
CXX = g++ -g -O2
DEFAULT_INCLUDES = -I. -I$(top_builddir) -IFTGL/
DEFS = -DHAVE_CONFIG_H
FT2_CFLAGS = -I$(COMPILER_DIR)/include/freetype -I$(COMPILER_DIR)/include
FT2_LIBS = -L$(COMPILER_DIR)/lib -lfreetype
GL_CFLAGS = -I$(COMPILER_DIR)/include
GL_LIBS = -lglu32 -lopengl32
HEADER_FLAGS = -I$(COMPILER_DIR)/include
NULL = 
libftgl_dll_CXXFLAGS = $(FT2_CFLAGS) $(GL_CFLAGS)
libftgl_dll_LIBADD = $(GL_LIBS) $(FT2_LIBS)

libftgl_a_objects = \
    FTBuffer.o \
    FTCharmap.o \
    FTContour.o \
    FTFace.o \
    FTGlyphContainer.o \
    FTLibrary.o \
    FTPoint.o \
    FTSize.o \
    FTVectoriser.o \
    $(ftglyph_objects) \
    $(ftfont_objects) \
    $(ftlayout_objects) \
    $(NULL)

libftgl_a_SOURCES = \
    FTBuffer.cpp \
    FTCharmap.cpp \
    FTCharmap.h \
    FTCharToGlyphIndexMap.h \
    FTContour.cpp \
    FTContour.h \
    FTFace.cpp \
    FTFace.h \
    FTGlyphContainer.cpp \
    FTGlyphContainer.h \
    FTInternals.h \
    FTLibrary.cpp \
    FTLibrary.h \
    FTList.h \
    FTPoint.cpp \
    FTSize.cpp \
    FTSize.h \
    FTVector.h \
    FTVectoriser.cpp \
    FTVectoriser.h \
    FTUnicode.h \
    $(ftglyph_sources) \
    $(ftfont_sources) \
    $(ftlayout_sources) \
    $(ftgl_headers) \
    $(NULL)

ftgl_headers = \
    FTGL/ftgl.h \
    FTGL/FTBBox.h \
    FTGL/FTBuffer.h \
    FTGL/FTPoint.h \
    FTGL/FTGlyph.h \
    FTGL/FTBitmapGlyph.h \
    FTGL/FTBufferGlyph.h \
    FTGL/FTExtrdGlyph.h \
    FTGL/FTOutlineGlyph.h \
    FTGL/FTPixmapGlyph.h \
    FTGL/FTPolyGlyph.h \
    FTGL/FTTextureGlyph.h \
    FTGL/FTFont.h \
    FTGL/FTGLBitmapFont.h \
    FTGL/FTBufferFont.h \
    FTGL/FTGLExtrdFont.h \
    FTGL/FTGLOutlineFont.h \
    FTGL/FTGLPixmapFont.h \
    FTGL/FTGLPolygonFont.h \
    FTGL/FTGLTextureFont.h \
    FTGL/FTLayout.h \
    FTGL/FTSimpleLayout.h \
    ${NULL}

ftglyph_objects = \
    FTGlyph/FTGlyph.o \
    FTGlyph/FTGlyphGlue.o \
    FTGlyph/FTBitmapGlyph.o \
    FTGlyph/FTBufferGlyph.o \
    FTGlyph/FTExtrudeGlyph.o \
    FTGlyph/FTOutlineGlyph.o \
    FTGlyph/FTPixmapGlyph.o \
    FTGlyph/FTPolygonGlyph.o \
    FTGlyph/FTTextureGlyph.o \
    $(NULL)

ftglyph_sources = \
    FTGlyph/FTGlyph.cpp \
    FTGlyph/FTGlyphImpl.h \
    FTGlyph/FTGlyphGlue.cpp \
    FTGlyph/FTBitmapGlyph.cpp \
    FTGlyph/FTBitmapGlyphImpl.h \
    FTGlyph/FTBufferGlyph.cpp \
    FTGlyph/FTBufferGlyphImpl.h \
    FTGlyph/FTExtrudeGlyph.cpp \
    FTGlyph/FTExtrudeGlyphImpl.h \
    FTGlyph/FTOutlineGlyph.cpp \
    FTGlyph/FTOutlineGlyphImpl.h \
    FTGlyph/FTPixmapGlyph.cpp \
    FTGlyph/FTPixmapGlyphImpl.h \
    FTGlyph/FTPolygonGlyph.cpp \
    FTGlyph/FTPolygonGlyphImpl.h \
    FTGlyph/FTTextureGlyph.cpp \
    FTGlyph/FTTextureGlyphImpl.h \
    $(NULL)

ftfont_objects = \
    FTFont/FTFont.o \
    FTFont/FTFontGlue.o \
    FTFont/FTBitmapFont.o \
    FTFont/FTBufferFont.o \
    FTFont/FTExtrudeFont.o \
    FTFont/FTOutlineFont.o \
    FTFont/FTPixmapFont.o \
    FTFont/FTPolygonFont.o \
    FTFont/FTTextureFont.o \
    $(NULL)

ftfont_sources = \
    FTFont/FTFont.cpp \
    FTFont/FTFontImpl.h \
    FTFont/FTFontGlue.cpp \
    FTFont/FTBitmapFont.cpp \
    FTFont/FTBitmapFontImpl.h \
    FTFont/FTBufferFont.cpp \
    FTFont/FTBufferFontImpl.h \
    FTFont/FTExtrudeFont.cpp \
    FTFont/FTExtrudeFontImpl.h \
    FTFont/FTOutlineFont.cpp \
    FTFont/FTOutlineFontImpl.h \
    FTFont/FTPixmapFont.cpp \
    FTFont/FTPixmapFontImpl.h \
    FTFont/FTPolygonFont.cpp \
    FTFont/FTPolygonFontImpl.h \
    FTFont/FTTextureFont.cpp \
    FTFont/FTTextureFontImpl.h \
    $(NULL)

ftlayout_objects = \
    FTLayout/FTLayout.o \
    FTLayout/FTLayoutGlue.o \
    FTLayout/FTSimpleLayout.o \
    $(NULL)

ftlayout_sources = \
    FTLayout/FTLayout.cpp \
    FTLayout/FTLayoutImpl.h \
    FTLayout/FTLayoutGlue.cpp \
    FTLayout/FTSimpleLayout.cpp \
    FTLayout/FTSimpleLayoutImpl.h \
    $(NULL)

all: libftgl.a ftgl.dll

.cpp.o:
 $(CXXCOMPILE) -c $(HEADER_FLAGS) -DFTGL_LIBRARY_STATIC -o $@ $<

libftgl.a: $(libftgl_a_objects)
 ar -r $@ $(libftgl_a_objects)

ftgl.dll: $(libftgl_a_SOURCES)
 $(CXXCOMPILE) $(libftgl_a_SOURCES) $(libftgl_dll_CXXFLAGS) $(libftgl_dll_LIBADD) -DFTGL_LIBRARY -DFTGL_DLL_EXPORTS -shared -Wl,--out-implib,libftgl.dll.a -o ftgl.dll
  
clean:
 @-del /S *.o
 @-del ftgl.dll
 @-del libftgl.dll.a
 @-del libftgl.a


This will compile the library for static and dynamic use. You will need to copy the FTGL folder to mingw/include. Then, to use as a static library, copy the resulting file src/libftgl.a to mingw/lib. To use as a dynamic library, copy the file src/libftgl.dll.a to mingw/lib and rename it to libftgl.a, and then copy src/ftgl.dll to the folder in your application.

As a side note, I am using this in a Qt application, so I had to update the .pro file to include the following line:

LIBS += -lftgl -lfreetype -lglu32 -lopengl32

When using ftgl.dll, I didn't need to add the '-lglu32 -lopengl32'. After using the static library, however, the linker complained about not having opengl functions defined. Also note the ordering of the libraries. Placing the opengl includes before the ftgl include didn't work. I had to put them after to satisfy the linker.

I could then follow the tutorials and get them to work. Also, another side note: The resulting text turned out to be far bigger than the -1,1 box in opengl, since it is measured in bigger units. Either scaling or using projection should help with that.

Another issue I found when using FTGLTextureFont is that the depth test needed to be set correctly, or the letter boxes seemed to overlap each other, blocking some of the letters. Setting the depth function using glDepthFunc(GL_LEQUAL); solved this problem for the most part. Another problem remained: at some font sizes, some of the letters seem to be clipped by either their neighbor or their own box. It is especially noticable on the letters 'e' and 'o'. This problem can be avoided by changing the font size to a size where this doesn't happen and then scaling to compensate.

Tuesday, February 8, 2011

Compiling FreeType-2.4.4 with the MinGW compiler bundled with Qt on Windows XP

I have a Qt application that uses OpenGL directly to render some things. Consequently, I wanted to add text support in OpenGL. After looking at a few options, trying one option and rejecting it, I decided to try FTGL. FTGL needs Freetype to compile, so I had to try and install Freetype. I went with Freetype-2.4.4 to get some of the new features, so I had to compile it from source (the pre-built version was only at 2.3.5). I ran into some interesting problems while compiling. The error looked something like this:

>mingw32-make
process_begin: CreateProcess(NULL, type builds\newline, ...) failed.
make (e=2): The system cannot find the file specified.
mingw32-make: *** [dos_setup] Error 2

After scouring through many of the makefiles, I finally narrowed the problem to the builds\detect.mk file. Specifically, starting at line 134. After puzzling about that for a while, I finally managed to figure out the reason: I had cygwin binaries in the path. They were conflicting with the mingw files. Removing the cygwin files from the path fixed the problem:

>mingw32-make

FreeType build system -- automatic system detection

The following settings are used:

  platform                     win32
  compiler                     gcc
  configuration directory      .\builds\win32
  configuration rules          .\builds\win32\w32-gcc.mk

If this does not correspond to your system or settings please remove the file
'config.mk' from this directory then read the INSTALL file for help.

Otherwise, simply type 'make' again to build the library.
or 'make refdoc' to build the API reference (the latter needs python).

Doing another 'mingw32-make' command finished up the compilation, putting the resulting files in \objs.

Now we'll see if it works...

Edit:

It did indeed work. I copied libfreetype.a to mingw/lib. I also copied ft2build.h and the freetype folder with the other header files to mingw/include. Using the library involved including the appropriate headers:

#include <ft2build.h>
#include FT_FREETYPE_H

- and then following the example on the Freetype website. The only other problem was that the Qt .pro file needed to be updated to include the following line:

LIBS += -lfreetype

Tuesday, October 5, 2010

Save Using ActiveRecord Without Rolling Back If Callbacks Fail

I recently came across an interesting situation when using Ruby on Rails. I wanted to make all of my models so that they could not be destroyed. The plan was to make all of the database records be "soft deleted", where each table had a column storing whether a record was "deleted". This was easily accomplished. Then, I wanted to prevent the records from being deleted. In my models, all I had to do was add a before_destroy callback and return false:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    return false
  end
end

All operations on the model instances are contained within transactions. If any of the before_* callbacks return false, the transaction is rolled back. This is all well and good, provided you don't want to insert a record in a different table in the before_destroy method. I happened to want to log any attempt to destroy records from the database, and I wanted to use the database to store the log entry:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    log = LogEntry.new
    log.action_type = "Destroy Object"
    log.save
    return false
  end
end

Unfortunately, when the before_destroy method returned false, it also rolled back the log entry insertion.

After looking around, I found a partial answer. If you call 'establish_connection' in your Rails model, you will get a new database connection for that class and all classes that inherit from it:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']
end

I am using ENV['RAILS_ENV'] so that it creates a connection for the development or the production database, depending on which environment it's in. You can also specify a different database entirely.

My other models can now work as I had intended before:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    log = LogEntry.new
    log.action_type = "Destroy Object"
    log.save
    return false
  end
end
So, that seems to have solved the problem. Well, mostly. What happens if you want to add a log entry if someone tries to destroy a log entry? You have the exact same problem as before. This time, I used a different trick for this one case:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']

  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    query = "insert into log_entries(action_type) values ("Destroy Log Entry");"
    ActiveRecord::Base.connection_pool.with_connection do |conn|
      conn.execute(query)
    end
    return false
  end
end

This time, I just grab a database connection from the connection pool. Unfortunately, you have to utilize raw queries to use the connection, but it gets the job done.

Alternatively, I could have also got a connection by using the checkout method:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']

  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    query = "insert into log_entries(action_type) values ("Destroy Log Entry");"
    conn = ActiveRecord::Base.connection_pool.checkout
    conn.execute(query)
    ActiveRecord::Base.connection_pool.checkin(conn)
    return false
  end
end

Thus, my problem was solved.