TweetFollow Us on Twitter

Dynamic PDF

Volume Number: 16 (2000)
Issue Number: 3
Column Tag: Programming Techniques

Dynamic PDF Made Easy

by Kas Thomas

It's simpler than you think to generate custom PDF documents in real time using Perl and CGI

Adobe's Portable Document Format (PDF) has won universal acclaim as document interchange format, because of its ability to deliver graphically rich, structured content consistently across multiple operating environments. Because of its strong support for vector graphics, font embedding, hypertext links, and other advanced features, PDF is a powerful, far-reaching document standard. But that also makes it a relatively complex standard. (For details, see the September 1999 MacTech.) PDF's complexity, in turn, has discouraged many web developers from attempting to write PDF files dynamically, at the server. Automatically generated HTML pages are common; but when was the last time you saw an auto-generated PDF web page?

It turns out that with a little foreknowledge of PDF's internal workings, and a good grasp of CGI fundamentals, it's not that hard to put together server scripts that will generate PDF on demand. There's no question that dynamic PDF can be a challenge to do right. But if your dynamic document needs are modest (for example, if you'd simply like to be able to generate a custom "Thank You" page at run time) and your Perl skills are passable (i.e., you've graduated from writing "Hello World" to generating the much more impressive string "Internal Server Error"), you can automate the serving of custom PDF pages with surprisingly little effort. In fact, a Perl script comprising no more than five or six dozen lines of code will get you started.

Our Strategy

Our strategy will be simple: We want to be able to collect an arbitrary glob of "user text" from a web form, and convert that text to a PDF page served back to the user via HTTP, suitable for viewing in a browser equipped with the free Acrobat Viewer plug-in. For simplicity's sake, we'll start by omitting any attempts at vector graphics, colored text, rotated text, styled text in various point sizes, etc. (But I hasten to add that before this article is finished, we're going to be doing all of those things and more.) For starters, we just want to serve the user's words back to him, at any convenient point size, in black and white, on a letter-sized PDF page. For sake of convenience, we're also going to generate the "data collection" form and our PDF reply from one and the same Perl script. That way, we don't have to keep track of multiple documents: an HTML form, Perl scripts, CSS stylesheets, etc. If you want to add that complexity to the picture yourself later, so be it. Here, we're going to keep things stone-simple.

I might point out that "live" copies of the dynamic-PDF scripts developed below can be found at http://www.acroforms.com/dynamicPDF.html (which is a portal page with links to several working scripts).

I also want to stress that when we're done, the PDF pages we will have generated can be saved to disk using your browser's Save As feature. (Reader won't save the docs, but your browser will.) Thus, we will have achieved the Holy Grail of the PDF world: writing PDF without using Distiller or Acrobat.

Once we've gotten our basic approach nailed down, we'll branch out into fancy features like colored backgrounds, stroked/filled paths, rotated text, styled text, etc. But first, a detour into the land of PDF internal structure.

PDF at the Subatomic Level

Internally, a PDF file consists of plain-ASCII object descriptions, following a notably Postscript-like (i.e., postfix-notation) syntax. All PDF files have a header (containing version information), a cross-reference (or 'xref') table containing offsets to the various objects that make up the file, and a trailer containing references to the root object as well as the byte offset from the start of the file to the beginning of the 'xref' table. (See my story in see the September 1999 MacTech for more information.) The formal PDF specification, available online at http://partners.adobe.com/asn/developer/PDFS/TN/PDFSPEC.PDF, describes the relationships between PDF objects (and the meanings of various dictionary "key" entries) in comprehensive detail, if you're interested. But we're going to skip all that, because most of it is not necessary for what we're doing.

It turns out you can bend (or even break) a lot of the "rules" spelled out in the PDF spec, without making Acrobat Reader unhappy, if you know a thing or two about PDF internals. An instructive exercise in this regard is to make Adobe's Distiller program generate a small "Hello World" type of file, then examine the file in a text editor-and try to find superfluous data in it that can be removed. If you try this, you will quickly discover that /Info objects, for example, contain meta-data about the PDF file (such as the doc's author, subject, producer, keywords, etc.) that can easily be dispensed with. Likewise, Adobe tends to insert long /ID objects (containing a kind of "digital fingerprint" for identification of the file) in PDF files; these aren't strictly needed.

If you keep removing superfluous objects and data items from a PDF file, you may be amazed at how much "data" you can actually get rid of without making Reader complain. A careful re-reading of the PDF Specification usually reveals that items you thought were mandatory are actually optional. Even the all-important 'xref' table isn't strictly needed, since Acrobat Reader can (and will, and does) generate the necessary byte offsets itself, at runtime, if the table is defective or missing.

In late 1999, I sponsored a contest at <www.acroforms.com> in which the goal was to produce the smallest possible PDF file that wrote "Hello World" to the screen without making Acrobat Reader generate any error dialogs. Amazingly, the winner of that contest submitted a file that was only a little more than 200 bytes long:

%PDF-1.
1 0 obj<</Pages 2 0 R>>
2 0 obj<</Kids[<</Parent 2 0
R/Resources<</Font<</R
<</Subtype/Type1/BaseFont/Arial>>
>>>>/Contents<<>>stream
BT/R 14
Tf(Hello World!)Tj
endstream
>>]>>
trailer<</Root 1 0 R>>

Note that the second object (starting with "2 0 obj...") is extra-long, with many nested "dictionary" entries, ending only just before the word "trailer" at the bottom of the file. This particular PDF file uses 14-point Arial as the typeface. No font encodings are embedded in the file, however, because Arial is one of the base-14 fonts that Acrobat Reader ships with. (Arial replaced Helvetica in the version-4.0 release of Reader.) Every machine that has Acrobat Reader is guaranteed to have Arial.

There are many things technically "wrong" with this short PDF file, including the fact that it has no 'xref' table and contains a text stream that is opened with 'BT' (for Begin Text) but is never closed with 'ET' (End Text). In many ways, it's a wonder Reader can even parse and display this file. Yet it does.

There is one small technical difficulty with this file that impacts viewing: Namely, no positioning information is given for the text - which means (since Reader "zeroes out" the current transformation matrix, or CTM, before displaying any block of text) that the starting "pen position" for displaying "Hello World!" in this instance is the origin of user space: i.e., the lower left corner of the page. You have to scroll down to the very bottom of the page to see the single line of text. To fix this requires that we add a transformation matrix of our own to the text stream:

%PDF-1.
1 0 obj<</Pages 2 0 R>>
2 0 obj<</Kids[<</Parent 2 0
R/Resources<</Font<</R
<</Subtype/Type1/BaseFont/Arial>>
>>>>/Contents<<>>stream
BT/R 14
1 0 0 1 72 720 Tm
Tf(Hello World!)Tj
endstream
>>]>>
trailer<</Root 1 0 R>>

Can you spot the change? We've added a line that ends with 'Tm' (the transform-matrix operator). The six-number matrix will look familiar to you if you've studied Postscript. The first four numbers are scaling and skewing factors (signifying no change in those characteristics, in this case); the final two numbers are translation values in 'x' and 'y', respectively. Since the default user space in PDF has 72 units to the inch, using values of 72 and 720 result in the starting position for our text results in our text being drawn one inch from the left edge of the page and ten inches up from the bottom. (In PDF, 'y' units get bigger as you go up.) Now our 14-point text will be drawn where the user can see it.

In the Perl script that follows, we're going to use a variation of the foregoing PDF file as the basis (the "template," if you will) for our dynamically generated PDF page. While some experts may cringe at the thought of using a "broken" PDF file as the starting point for this kind of exercise, the fact is that for illustrative purposes, a "subatomic" template file of this kind is hard to beat. Besides, the proof of the pudding is that Acrobat Reader doesn't in any way "choke" on the end result. What the user sees in his (or her) browser is a fully functional PDF file: one that can be saved to disk (using the browser's Save As feature) for later use.

The Common Gateway

Collecting information from users is a common task in the world of the Web, calling for the use of interactive forms. Sometimes, the form in question is a static HTML file stored on a server; but increasingly, HTML forms themselves are dynamically generated at the server in response to user requests. That is, the form itself doesn't exist until a CGI script, often written in Perl, generates it.

CGI (the Common Gateway Interface) is nothing more than a set of conventions for pushing and pulling information across an HTTP connection. Forms, and the scripts that retrieve information from web forms, use the CGI protocol to get the job done. Learning to write CGI scripts in Perl is an art unto itself, but the job is made tremendously easier if you take advantage of some of the excellent free Perl libraries available for implementing CGI processes.

Arguably the best Perl library for this task is Lincoln Stein's powerful CGI.pm package. This set of routines (which takes the pain out of writing HTML at the server and dealing with web forms) is so useful that it now ships with Perl as part of the "full install" of the language. What this means is that any Unix server that has Perl 5.003, patchlevel 7 or higher (as almost all do) automatically has CGI.pm, waiting to be called from any Perl script.

When you use CGI.pm, you don't have to worry about whether incoming form data is being supplied via GET or POST, where it's being stored on the server, or any of the nasty parsing details involved in extracting urlencoded data from HTTP streams. To get the value of a form field called "UserName" from a Perl script at runtime using CGI.pm, all you have to do is:

$name = param('UserName');

Here, $name refers to the local scalar variable into which we wish to store the value from "UserName." If the user has typed "John Smith" into the relevant form field before Submitting the form data to the server, then $name will contain "John Smith" after this line of code executes. The param() function in CGI.pm takes care of finding the incoming data, decoding it if it's urlencoded, etc.

Dynamic Forms Using CGI.pm

One task CGI.pm excels at is generating HTML code dynamically. For example, to create the HTML form shown in Figure 1, all we have to do is execute the following few lines of Perl:

  print header,
 		start_html(-title=>'PDF Bounceback, by Kas Thomas',
                   -bgcolor=>'#FFFFFF'),		
 		p(h2('PDF Text Entry'));

	print start_form,
		textarea(-name=>'UsersText',
		         -rows=>24,
		         -columns=>60),p,
        submit(-name=>'action',-value=>'Generate PDF');
    
  end_form;


Figure 1.A form created dynamically using CGI.pm.

The function h2('content here') applies an HTML level-2 headline tag around the text "content here"; likewise, p() applies paragraph tags around a block of text, and so forth. Nesting the function calls causes the relevant HTML tags to nest correctly. A common idiom in Perl for getting multiple strings to print to an output stream is to separate the strings with commas and put them between print at the start and a semicolon at the end. Functions like header(), start_form(), and end_form() are part of the CGI.pm package. (In Perl, parentheses after a function name do not constitute a "function call operator." They are therefore optional, if no arguments are given.)

If you write a Perl script containing the foregoing lines of code, name the script "Form.pl", and place it in the "cgi-bin" directory of your web server, then whenever anybody goes to the script's URL, the script will launch automatically and write the form shown in Figure 1 to the caller's browser. This is how many (if not most) dynamic forms work.

A Dynamic PDF Script

At this point, believe it or not, we're in a position to put together a full, working Perl script for producing an HTML form dynamically, retrieving the contents of that (filled-out) form, and auto-generating a PDF reply back to the user's browser. Listing 1 shows such a script, complete.

Listing 1: dPDF.pl


dPDF.pl

A complete Perl script for generating an HTML form, retrieving the

form's contents, and writing the contents back out as a PDF file.


#!/usr/bin/perl
# ------------------------------
# Script: dPDF.pl (simple script to output PDF dynamically)
# © 2000 by Kas Thomas. Updates at www.acroforms.com.
# ------------------------------
use CGI qw/:standard/;

# Check params, and if null, show form...
DrawForm() unless param();

$allText = param('UsersText');
 
print header("application/pdf");   # output PDF header

Send_PDF_Leader();

foreach $line (split /\n/,$allText) {
    
	 print '(' . $line . ')Tj';
     print "\n";   
	 print 'T*';				 # goto start of next line		    
     print "\n";   
	  
}

Send_PDF_Trailer();

# - - - - - - - - - - DrawForm() - - - - - - - - - - -
sub DrawForm {

	print header,
 		start_html(-title=>'PDF Bounceback, by Kas Thomas',
                   -bgcolor=>'#FFFFFF'),		
 		p(h2(font({-color=>'red'},'PDF Text Entry')));

	print start_form,
		textarea(-name=>'UsersText',
		         -rows=>24,
		         -columns=>60,
		         -wrap=>virtual),p,
        submit(-name=>'action',-value=>'Generate PDF');
    
    end_form;
    
    exit;
}

# - - - - - - - - - - Send_PDF_Leader() - - - - - - - -
sub Send_PDF_Leader {

print <<PDF_LEADER;
%PDF-1.
1 0 obj<</Pages 2 0 R>>
2 0 obj<</Kids[<</Parent 2 0 R/Resources<</Font<</R
<</Subtype/Type1/BaseFont/Arial>>
>>>>/Contents<<>>stream
BT/R 14
1 0 0 1 72 700 Tm
15 TL
Tf()Tj
PDF_LEADER
}

# - - - - - - - - - - Send_PDF_Trailer() - - - - - - - -
sub Send_PDF_Trailer {

print <<PDF_TRAILER;
endstream
>>]>>
trailer<</Root 1 0 R>>
PDF_TRAILER
}

After a use directive to make sure Perl knows we need to use the CGI.pm package, we check to see whether the CGI.pm function param() returns nothing, in which case it means the script is being called for the first time. If the script is being called for the first time, it means we need to generate the HTML form of Figure 1 - which is accomplished by the subroutine DrawForm(). Otherwise, if param() is not nil, we're responding to a hit on the "Generate PDF" button (which is the form's equivalent of a Submit button).

If we're responding to a hit on the Generate PDF button, the first order of business is to retrieve the user-entered text, which we do with:

$allText = param('UsersText');

The textarea field of the HTML form is named "UsersText"; hence, we want to retrieve whatever the user typed there. The foregoing line of code captures the user's text into a single big string (a scalar variable, in Perl parlance) called $allText. We'll have occasion to parse that big string into smaller constituents (representing individual lines of text) in a moment.

Before we can write PDF to the user's browser, we have to open an HTTP connection and send an HTTP header with the appropriate MIME type for PDF (which is "application/pdf"). This is the browser's way of knowing what kind of file is about to arrive. The browser can take appropriate action in terms of launching helper apps, handing off control to plug-ins, or handling the transaction itself. In this instance, if the user has the Acrobat browser plug-in installed, the browser will hand control to the plug-in. It's also possible that the user has set the browser up such that incoming PDF files are redirected to the standalone Acrobat Reader app. If the MIME type "application/pdf" has not been registered with the browser, the browser will react to the incoming header by asking the user what to do with the about-to-arrive unknown file type. The user can then cancel out of the transaction, store the incoming file to disk, etc.

Opening the HTTP connection and sending the proper header is done with:

print header("application/pdf");

After that, our script goes to a subroutine called Send_PDF_Leader(). This subroutine simply writes 200 bytes or so of raw PDF to the HTTP connection. Most of that code should look familiar to you from our discussion of minimal PDF files earlier.

Writing the User's Text

Next comes our script's "main loop," you might say, in which we parse the user's text into individual lines and write them out as PDF text-stream data. We use Perl's split() function to divide the $allText string into substrings, splitting at every newline. Then we write a line of user text, in parentheses, followed by the 'Tj' operator (PDF's "writeline" operator), and after every line of text so processed, we write a 'T*', which (in PDF) means to drop down to the start of the next line (using the current leading) and prepare the virtual pen to write more text.

After writing every line of user text, we call Send_PDF_Trailer() to write out the last few bytes of our PDF template. At that point, we're finished.

An example of a dynamically generated PDF page using this script is shown in Figure 2.


Figure 2.A dynamically generated PDF file.

The Great Escape

At this point, we have a script that obediently writes our user's text input as 14-point Arial, in black ink on a white PDF page. Not very interesting-looking. It would be nice if we could jazz things up a bit by drawing our text in color, perhaps in various additional sizes. It would also be nice if we could specify something other than a pure white background. But before we proceed to increase the spiff factor of our PDF, we need to attend to one small detail.

PDF represents strings internally as ASCII character sequences inside parentheses. Thus, whenever a string contains parentheses as part of the text, they need to be "escaped" with a preceding backslash. Otherwise, the PDF interpreter, upon encountering a right-parenthesis inside a block of text, may think the text string has been terminated! The right-paren will be treated as an end-of-string marker. This could lead to strange errors if our user is in the habit (as many of us are) of loading up text with parenthetical remarks.

Fortunately, it's not hard to trap parens and "escape them" automatically for the user. Of course, we also have to trap and escape backslashes in the user's text, because (again) PDF treats all backslashes as "special" in a text stream. To deal with parens and backslashes, we declare an associative array in Perl as follows:

%PDF_ESCAPES = ('\\' => '\\\\', 
                ')'  => '\)', 
                '('  => '\(');

The way this special kind of array (sometimes called a hash) works is that when we want to look up the "replacement" for, say, a bare right-paren, we just need to do:

$PDF_ESCAPES { ')' };

Note the dollar sign preceding the hash name (and the curly braces enclosing the array index). In Perl, the dollar sign means we are coercing the array lookup into a scalar context. When the above statement evaluates, it will evaluate to the lookup value '\)' (i.e., the string representing our properly escaped paren).

Of course, Perl (like PDF) also treats backslashes as special, which means that a string representing a literal backslash needs to be escaped - with a backslash! That is, the string '\\' prints as one backslash (not two) in Perl. Likewise, '\\\\' is treated as two literal backslashes in a row, not four. That's why the first lookup item in our hash, namely '\\\\', is associated with '\\'. (Confused yet?)

Now. We need to check every line of user input before writing it as raw PDF, so that any parens or backslashes contained in the user's text get converted to their properly escaped equivalents, preventing Acrobat Reader from choking. We can accomplish this essential check with the following line of code, inserted in our "main loop":

$line =~ s/([\\()])/$PDF_ESCAPES{$1}/ge;

"Wow," you're probably muttering, "what a weird-looking line of code!" And indeed it is. A Perl fan will recognize it immediately as a powerful regex-based substitution (regex being short for "regular expression"). In Perl, a statement like

$myString =~ s/this/that/g;

means to scan the contents of $myString, looking for the substring 'this', and upon finding a match, substitute (per the leading lowercase 's') the string 'that'; and oh, by the way, do this repeatedly (globally) throughout the entire contents of $myString. (That's what the trailing lowercase 'g' means.)

In our somewhat complicated substitution operation, we're scanning for any match of the regular expression [\\()], which is a character class with three members: the backslash character (which we have to escape with a backslash, for Perl's benefit; hence '\\'), the left-paren, and the right-paren. Square brackets mean that these characters form a class of allowable "match" characters. Any match of any one of these characters (in any order) inside the target string will trigger a "hit." Enclosing the entire character class within parentheses, as in ([\\()]), means we want Perl to store a temporary copy of any match that occurs, in a variable called $1. (If you're not familiar with this idiom, don't spend too much time worrying about it right now. You can always read up on regexes later.) Our replacement value, which Perl consults whenever a "hit" happens, in this case is $PDF_ESCAPES{$1} - that is to say, we use the match value ($1) as an index into our associative array, in order to retrieve the appropriate replacement string. This trick will only work, however, if we remember to put a trailing lowercase 'e' on the end of the expression, which means evaluate the replacement string as a Perl expression. Otherwise, Perl will literally substitute whatever's in the replacement position, unevaluated. That's not what we want.

Upping the Spiff Factor

As I started to say before, it would be nice if we could "spiff up" our PDF output a bit, augmenting it with colored text, styled text, or perhaps some vector graphics or different background colors, etc. - anything except plain old black text on a white background.

Adding extra spiff turns out to be astonishingly easy. All we need to do is let the user "drop down" into low-level PDF whenever he or she wants to. Follow me for a minute. Suppose we invent a special "escape command" of our own, signified by (let's say) the ASCII string <@>. We'll call this the "escape-to-PDF" operator. To get back to normal text-writing mode, we'll require the use of </@>, which we'll call the "escape-to-text" operator. Anything bracketed by <@> and </@> gets treated as raw PDF. While your user is in "raw PDF" mode, he can make use of any of the Postscript-like page operators and vector-drawing commands recognized by the PDF Specification: m for moveto, l for lineto, rg for setting the RGB color, Tm for set matrix, B for fill and stroke path, etc. (See the URL given earlier for the formal PDF Specification, at Adobe's web site.)

Using the same regex-based substitution trick that we used before, we can add one more lookup table (or associative array) to our script, and add one more regex-substitution line to our main loop. Only this time, the hash will return a value of ')Tj ' for any hit on <@>, and a value of ' (' for any hit on </@>. That way, whenever the user types <@> as part of his text stream, our script outputs a PDF end-of-string marker, followed by the PDF "write line" operator (Tj), spoofing Acrobat Reader into letting the user write raw page operators, if he wants to. When the user types </@>, the script outputs a "begin string" marker, and Reader is spoofed into going back into text-stream mode.

What Can You Do With It?

Now we've got quite a bit of power in our hands. If you know a little bit of PDF, you can create almost any kind of display magic you want with your dynamically generated PDF document. Let's try the following code entered into our form's main text area:

<@>0.85 0.85 0.85 RG</@>
<@>1 J 110 w 250 600 m 250 600 l B</@>
<@>.6 .6 .6 RG</@>
<@>18 w 72 72 m 72 720 l 540 720 l 540 72 l s</@>
<@>0 1 -1 0 144 520 Tm</@>

Let's try some rotated text.

The end result is shown in Figure 3. We've created a PDF document containing a large grey box (going around the edges of the page), a filed grey circle, and the words "Let's try some rotated text" running vertically up the page.


Figure 3.Custom-generated PDF containing rotated text and stroked vector graphics.

To understand what's going on, let's look at the above form input, one line at a time. The first five lines are encapsulated by <@> and </@> , which means we're writing raw PDF. The first line of raw PDF contains 0.85 0.85 0.85 RG, which sets the stroke color (in our default RGB color space) to 15% grey. In PDF, RGB channel values run from zero to 1.0, with 1.0 being brightest. Therefore, setting each channel to 0.85 makes for 15% grey (or 85% white).

The second line is more complicated. Basically, it says to set the default line end-cap style to rounded end-caps (that's what '1 J' means); set the default line width to 110 pixels (thus '110 w'); move the pen to page coordinates of 250,600 ('m' means "moveto"); draw a line to 250,600; and finally, stroke the path ('B'). In plain English: "Draw a zero-length line, 110 pixels wide, at 250,600 with rounded end caps." Since the line has zero length, only the end caps are drawn. In this way, we spoof Acrobat into drawing a perfect circle, which looks filled (although really it's only stroked). This is an old Postscript hack for drawing filled circles. The reason we use it here is that PDF doesn't have a "draw circle" operator, just Bézier-curve operators. (I don't know about you, but I find the idea of drawing a circle using linked Bézier segments too painful to think about.)

Next, in the third line of raw PDF, we set the stroke color to 40% grey (60% white) with .6 .6 .6 RG.

In line four, we change the default stroke width to 18 pixels ('18 w'); move the pen to 72,72; draw a line to 72,720; another line to 540,720; a line to 540,72; and then we use the 's' command, which in PDF means "close this path and stroke it." This is how we produced a thick, dark-grey line going around the margins of the page.

Finally, in the fifth line we perform some matrix magic. Transform matrices, in PDF (as in Postscript) consist of six numbers, the first four of which specify scaling and rotation. To rotate the coordinate system, just make sure the first four numbers represent (consecutively) the cosine, sine, negated sine, and cosine of the desired angle. In our example, we used 90 degrees as the angle (hence the matrix values 0, 1, -1, and 0). The final two numbers in the transform matrix represent translation values in x and y. That is, we move the origin of space to whatever these numbers are (in this case, 144,520). Recall that the origin starts life at the lower left corner of the page.

Now when our text is drawn, it begins at 144,520 but runs at 90 degrees up the page. Since text is (by default) filled but not stroked, it gets drawn in black (the default fill color, which we have not changed).

Simple as pi.

Conclusion

We've covered an incredible amount of ground in a relatively small space. Not only have we shown how to serve dynamic PDF pages from a compact (80 lines or so) Perl script, but we've integrated a low-level "raw PDF" editor into the data-input form such that if the user wants to, he can produce sophisticated graphics effects inside the generated PDF page. (A "live" copy of this script, and others similar to it, can be found online at http://www.acroforms.com/dynamicPDF.html.)

Of course, many useful additions could still be made to our little script. A useful exercise would be to add such features as:

  • Additional fonts, and a facility for the user to change font sizes on the fly.
  • Automatic line-wrapping, with or without justification.
  • Custom margin widths.
  • Custom background colors. (This is just a matter of drawing a filled, colored rectangle large enough to cover the entire page.)
  • Stylesheets.

You can probably think of lots of other features that would be nice to have. The good news is, now you're in a position to implement them, because (as you now know) dynamic PDF really isn't that hard.


Kas Thomas (kt@svgcentral.com) consults for Adobe Systems and writes a regular column about PDF-related programming (covering JavaScript, CGI, dynamic PDF, and related issues) for www.acroforms.com. He has been programming in C and assembly on the Mac since MC68000 days and remembers when the MacOS ran in black-and-white.

 
AAPL
$102.05
Apple Inc.
+2.29
MSFT
$44.80
Microsoft Corpora
+0.72
GOOG
$521.93
Google Inc.
+1.09

MacTech Search:
Community Search:

Software Updates via MacUpdate

RapidWeaver 6.0 - Create template-based...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
NTFS 12.0.39 - Provides full read and wr...
Paragon NTFS breaks down the barriers between Windows and OS X. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS, providing full read and write access to... Read more
RestoreMeNot 2.0.3 - Disable window rest...
RestoreMeNot provides a simple way to disable the window restoration for individual applications so that you can fine-tune this behavior to suit your needs. Please note that RestoreMeNot is designed... Read more
Macgo Blu-ray Player 2.10.9.1750 - Blu-r...
Macgo Mac Blu-ray Player can bring you the most unforgettable Blu-ray experience on your Mac. Overview Macgo Mac Blu-ray Player can satisfy just about every need you could possibly have in a Blu-ray... Read more
Apple iOS 8.1 - The latest version of Ap...
The latest version of iOS can be downloaded through iTunes. Apple iOS 8 comes with big updates to apps you use every day, like Messages and Photos. A whole new way to share content with your family.... Read more
TechTool Pro 7.0.5 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PDFKey Pro 4.0.2 - Edit and print passwo...
PDFKey Pro can unlock PDF documents protected for printing and copying when you've forgotten your password. It can now also protect your PDF files with a password to prevent unauthorized access and/... Read more
Yasu 2.9.1 - System maintenance app; per...
Yasu was originally created with System Administrators who service large groups of workstations in mind, Yasu (Yet Another System Utility) was made to do a specific group of maintenance tasks... Read more
Hazel 3.3 - Create rules for organizing...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a... Read more
Autopano Giga 3.7 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more

Latest Forum Discussions

See All

Sago Mini Monsters Celebrates Halloween...
Sago Mini Monsters Celebrates Halloween with Fun Costumes and Special Treats. Posted by Jessica Fisher on October 21st, 2014 [ permal | Read more »
Inferno 2 Review
Inferno 2 Review By Andrew Fisher on October 21st, 2014 Our Rating: :: TWIN STICK GOODNESSUniversal App - Designed for iPhone and iPad With tight controls and awesome, stark visuals, Inferno 2 is loads of fun.   | Read more »
Clips Review
Clips Review By Jennifer Allen on October 21st, 2014 Our Rating: :: CONVENIENT PASTINGUniversal App - Designed for iPhone and iPad Making copying and pasting more powerful than usual, Clips is a great way to move stuff around.   | Read more »
MonSense Review
MonSense Review By Jennifer Allen on October 21st, 2014 Our Rating: :: ORGANIZED FINANCESiPhone App - Designed for the iPhone, compatible with the iPad Organize your finances with the quick and easy to use, MonSense.   | Read more »
This Week at 148Apps: October 13-17, 201...
Expert App Reviewers   So little time and so very many apps. What’s a poor iPhone/iPad lover to do? Fortunately, 148Apps is here to give you the rundown on the latest and greatest releases. And we even have a tremendous back catalog of reviews; just... | Read more »
Angry Birds Transformers Review
Angry Birds Transformers Review By Jennifer Allen on October 20th, 2014 Our Rating: :: TRANSFORMED BIRDSUniversal App - Designed for iPhone and iPad Transformed in a way you wouldn’t expect, Angry Birds Transformers is a quite... | Read more »
GAMEVIL Announces the Upcoming Launch of...
GAMEVIL Announces the Upcoming Launch of Mark of the Dragon Posted by Jessica Fisher on October 20th, 2014 [ permalink ] Mark of the Dragon, by GAMEVIL, put | Read more »
Interview With the Angry Birds Transform...
Angry Birds Transformers recently transformed and rolled out worldwide. This run-and-gun title is a hit with young Transformers fans, but the ample references to classic Transformers fandom has also earned it a place in the hearts of long-time... | Read more »
Hail to the King: Deathbat Review
Hail to the King: Deathbat Review By Rob Thomas on October 20th, 2014 Our Rating: :: SO FAR AWAYUniversal App - Designed for iPhone and iPad Hail to the King: Deathbat may feel like “Coming Home” for Avenged Sevenfold’s faithful,... | Read more »
Find Free Food on Campus with Ypay
Find Free Food on Campus with Ypay Posted by Jessica Fisher on October 20th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »

Price Scanner via MacPrices.net

Select MacBook Airs $100 off MSRP, free shipp...
B&H Photo has 2014 a couple of MacBook Airs on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels Desktop and LoJack for... Read more
13-inch 2.5GHz MacBook Pro on sale for $100 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
Strong iPhone, Mac And App Store Sales Drive...
Apple on Monday announced financial results for its fiscal 2014 fourth quarter ended September 27, 2014. The Company posted quarterly revenue of $42.1 billion and quarterly net profit of $8.5 billion... Read more
Apple Posts How-To For OS X Recovery
OS X 10.7 Lion and later include OS X Recovery. This feature includes all of the tools you need to reinstall OS X, repair your disk, and even restore from a Time Machine backup. OS X Recovery... Read more
Mac OS X Versions (Builds) Supported By Vario...
Apple Support has posted a handy resource explaining which Mac OS X versions (builds) originally shipped with or are available for your computer via retail discs, downloads, or Software Update. Apple... Read more
Deals on 2011 13-inch MacBook Airs, from $649
Daily Steals has the Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB) available for $699 with a 90 day warranty. The Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB SSD) is available for $649 at Other... Read more
2013 15-inch 2.0GHz Retina MacBook Pro availa...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
Updated iPad Prices
We’ve updated our iPad Air Price Tracker and our iPad mini Price Tracker with the latest information on prices and availability from Apple and other resellers, including the new iPad Air 2 and the... Read more
Apple Pay Available to Millions of Visa Cardh...
Visa Inc. brings secure, convenient payments to iPad Air 2 and iPad mini 3as well as iPhone 6 and 6 Plus. Starting October 20th, eligible Visa cardholders in the U.S. will be able to use Apple Pay,... Read more
Textkraft Pocket – the missing TextEdit for i...
infovole GmbH has announced the release and immediate availability of Textkraft Pocket 1.0, a professional text editor and note taking app for Apple’s iPhone. In March 2014 rumors were all about... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.