asGzip: pdfDeflateCompressedStream
"
self asGzip: (((FileStream fileNamed: ' :rms-essays.pdf') position: 412) next: 57)
"
| header |
header ¨ ('1f8b08' padded: #right to: 20 with: $0) orig.
≠ (header, (pdfDeflateCompressedStream copyFrom: 3 to: (pdfDeflateCompressedStream size))) openInWorkspaceWithTitle: 'obj.gz'.

Explanation:

asGzip: consumes the pdfDeflateCompressedStream which is a string representation of a deflated (ie: compressed) stream inside a PDF. A header is defined which looks like this in hex: ‘1f8b0800000000000000’. This new header replaces the old one, typically ‘789c’ (in hex) . In this example I present the new concatenated string in a new window (a workspace) for saving to disk, or for further operations.

Point:

asGzip: thus enables you to decompress objects typically found inside a PDF stream. Once decompressed the user is presented with data which is merely a string containing PDF commands. Since this process is easily reversible, because we have standard compression tools available, this enables us to edit PDFs with compressed streams and to write edited PDFs using the same compression method.

asDeflatedStream: gzipOriginalStream
"
(ZLibReadStream on: (self asDeflatedStream: ((FileStream fileNamed: '12.28:STUFFI~1.GZ') contentsOfEntireFile))) contents.
"
| header guess input |
header ¨ '789c' orig.
(((gzipOriginalStream copyFrom: 3 to: 3) asCleanHex) = '08')
		ifFalse: [self error: 'This file does not use the deflate compression method']
	ifTrue: [(((gzipOriginalStream copyFrom: 4 to: 10) asCleanHex) = ('0' padded: #right to: 14 with: $0))
		ifFalse: [guess ¨ (gzipOriginalStream copyFrom: 9 to: (9+8+1+3)).
		input ¨ FillInTheBlank request: 'Please Verify!!' initialAnswer: guess.
		≠ header,(gzipOriginalStream copyFrom: (9+1+(input size)) to: (gzipOriginalStream 			size))]
	ifTrue: [≠ header,(gzipOriginalStream copyFrom: 11 to: (gzipOriginalStream size))]
].

Explanation:

asDeflatedStream: consumes gzipOriginalStream which is a string representation of a gzip file. This is the complete filestream after having either created a new compressed file with any standard compression program either inside Mac, Windows, GNU/Linux or Smalltalk or whatever. A new header is defined which is ‘789c’ (in hex). This new header is appended. If the gzip program has created a simple header, ‘1f8b0800000000000000’ (in hex), then we just read ahead sufficiently (ie. we look at the 11th byte!) and concatenate that to the new header. If the header has more complexity, ie. a file name at byte 9, we guess at a standard length (based on DOS 8.3 file names). A dialog box is presented to the user to let them know that the user has to check how long the file name actually is. (The user has to look at the file in a file browser to determine this). Then the script automatically continues, just like in the first case, and concatenates the new header onto the body of the data stream.

Point:

1) This data is now in the correct form for using inside a PDF data stream. Its purpose therefore is for allowing edited work to be re-introduced to a pre-existing complex PDF using the compression method it was already using!

2) Both of these compression related scripts show just how much mileage you can get from using nothing but strings and concatenation. Since the environment that the scripts are ported to don’t have to know anything about how to actually perform the computations for compression/decompression. You can, in an object oriented way, inherit from the black box programs like gzip, WinZip, or StuffIt!
xrefify:pdf
"
self xrefify: 'RIFFS.PDF'.
"
| grep n data bytePosition |
data ¨ ((FileStream fileNamed: pdf) contentsOfEntireFile) withSqueakLineEndings.
grep ¨ '0 obj'.
1 to: ((data occurrencesOf: Character cr)+1) do:
	[:line |
	n ¨ ((data lineNumber: line) findString: grep).
	(n > 0)
		ifTrue: [
		bytePosition ¨ (data findString: (data lineNumber: line))-1.
		Transcript cr; show: ((data lineNumber: line) asInteger).
	 	Transcript show:':'.
		Transcript show: (bytePosition asString asXref).
			]
	].

Explanation:
Another basic tool that is necessary both for creating and editing PDFs is a script that generates the cross-reference (= xref). This version represents my basic algorithm I wrote under GNU/Linux some years back, this is a successful port, which is quite amazing, since I’m not actually making use of a grep program as such, so I had to build up that functionality. At a functional level, then, xrefify consumes a string which is actually a PDF. It reads it line by line. If a line matches ‘0 obj’ then it outputs the object number by translating that match to an integer. Eg. if the current line is:

‘29 0 obj’

The output would be an integer:

29

Next, the script gives the bytePosition of the object. In keeping with my original xrefify script, bytePosition returns the position in bytes up to the return character before the line begins!

Point:
PDFs are constructed out of objects which are referenced exactly, so the xref, represents this table. To be able to write, edit or even read a PDF means to have constructed means of manipulating the xref. Clearly, this is very trivial for the computer to calculate. Its power lies just in its usefulness. Since, if I make a change in a pre-existing and working PDF and change one object’s size such that it is larger than before, and if I don’t account for that change (by redoing the xref)) then that PDF will present an error message on loading to the user. This, then, is one of the fundamental building blocks, which can very easily be constructed just out of strings.
withTjify: t1 
| t2 t3 t4 t5 t6 t7 t8 |
	t1 isNumber not | (t1 < 1)
		ifTrue: [self error: 'too narrow'].
	t2 _ OrderedCollection new.
	t3 _ 0.
	[t3 < self size]
		whileTrue: [t4 _ t3 + 1.
			t6 _ t4 + t1 - 1 min: self size.
			t7 _ self copyFrom: t4 to: t6.
			(t8 _ t7 indexOf: Character cr) > 0
				ifTrue: [t6 _ t4 + t8 - 1.
					t7 _ self copyFrom: t4 to: t6].
			t3 _ t6 == self size
					ifTrue: [t6]
					ifFalse: [t4 + t7 lastSpacePosition - 1].
			t3 <= t4
				ifTrue: [t3 _ t6].
			t2 add: ((((self copyFrom: t4 to: t3) copyReplaceAll: Character cr asString with: '') copyReplaceAll: '(' with: '\(') copyReplaceAll: ')' with: '\)')].
	t2 size > 0
		ifFalse: [^''].
	t5 _ t2 first.
	2
		to: t2 size
		do: [:t9 | t5 _ t5 ,  ') Tj', Character cr asString,'T* (',(t2 at: t9)].
^’T* (',t5,') Tj'
Explanation:

This script is not called directly by the user, but is invoked by running tjify. It does look a bit ugly as code, but more importantly it’s functional, and it’s a hack on someone else’s code (it is a reworked version of: withNoLineLongerThan:). I previously wrote a similar version under GNU/Linux. This version, I wanted to see how far I could get with strings. In this script all I was aiming at producing was the correct formatting for basic text inside a PDF text stream. How’s that go? Well all text has to be padded with T* ( and Tj so ‘Hello World’ would look like: ‘T* (Hello World) Tj’. More importantly, most PDF readers will not display text that displays off the defined page’s bounds. Therefore, building this script on top of withNoLineLongerThan: made a lot of sense. The original script would produce this: ‘hello
hello’ if presented with ‘hellohello’ withNoLineLongerThan:5.

Point:
To allow simple PDF textual buffers to be created. The basis of a PDF writer program.
bun64
"operates on magic string, ignoring other data if present, and decoding a B64 stream.
self bun64.
"
| whatever file magic river header base64Header base64String |
magic ¨ 'Content-Disposition: inline;'. "change as necessary, this appears to be a generally accepted one"
whatever ¨ FillInTheBlank request: 'Whatever with B64 Streamage'. "PDF, plain b64, or whatever"
(whatever match: '')
		ifTrue: [self error: 'Cancelled by luser']
		ifFalse: [file ¨ (((FileStream fileNamed: whatever) contentsOfEntireFile) withSqueakLineEndings)].
header ¨ file findString: magic.
base64Header ¨ file findString: (Character cr asString) startingAt: header.
base64String ¨ file copyFrom: (base64Header+2) to: (file size). "to cope with double returns"
river ¨ (ReadStream on: base64String). "renamed to cope with reality"
((Base64MimeConverter mimeDecodeToChars: river) contents openInWorkspaceWithTitle: 'Converted Mime Data').

Explanation:
This script enables you to read out any data that has been encoded using base64 encoding, which is commonly used for email transfer of data on the Internet. This script, essentially, is just padding for a user like myself to click on and use. However, the real benefit is masked by the magic header that I use. Using the one I use, I can actually get WinZip or StuffIt to perform the same operation. So, the real benefit? Well, PDFs only self-reference their data up to a point. Inside the xref there’s a place where you can place data. It is in this place that I like to sometimes place base64 data. This script just enables me to use PDFs more cleverly, as documentation and as methods of transmitting my scripts via the Internet. (Of course Adobe Acrobat is purported to have similar capability when it comes to have attachments. But that costs.)

Point:
The script automatically finds the right place in the PDF and then decodes whatever I’ve placed there. It allows me to use the PDF as a filesystem, since I don’t believe in traditional filesystems. So, I can view a PDF as a PDF, or as a base64 file containing, say, a Word File, or whatever. This is much more useful than zips, because you have to deliberately unpack them to see what’s in them, whereas, PDFs can more conveniently display messages about their contents.
asXref
≠ ((self padded: #left to: 10 with: $0),' 00000 n').

hex
	self flag: #knownBug. "Ugly hack, see comments!"
	≠ String
	with: $% "Yeah, neat and ugly hack so that unescapePercents will undo this all!"
	with: ('0123456789ABCDEF' at: value // 16 + 1)
	with: ('0123456789ABCDEF' at: value \\ 16 + 1)

asDirtyHex
≠ '%',((self withNoLineLongerThan: 2) copyReplaceAll:(Character cr asString) with: '%').

asCleanHex
≠ self asHex copyWithoutAll: '%'.

orig
≠ self asDirtyHex asUnHex.

Explanation:
These are just some scripts that I use. asXref doesn’t have to do much, but this just exists so that the xref produced is in keeping with the PDF standard. (The output format looks like this: ‘0000000015 00000 n’. (padded, automatically takes care of calculating any characters that it doesn’t have to print). The hex one, as the code says, makes use of unescapePercents, which is a basic script included in Squeak, mainly there to be able to decipher escaped hex in HTTP addresses in a web browser. However, it fits my uses so much, I’ve been too lazy to correct this and write a more ‘designed’ hex program. (So, my hack merely appends a % character to make it compatible with unescapePercents). asDirtyHex just reintroduces percent characters, so that unescapePercents can understand it. asCleanHex does the opposite, for cases where we don’t need the extra padding (since, it’s clearly easily reversible). orig returns the interpreted version of a user-friendly escaped hex expression. Thus, ‘789c’ orig, means: convert it to ‘%78%9c’ and tell unescapePercents to decode it. asUnHex merely makes a call to unescapePercents. (In this instance, ‘789c’ orig, would look like: ‘xú’.)

Purpose:
Most of this PDF hacking doesn’t require too sophiscated techniques. However, it is always useful to be able to play with more fundamental representations of data. The scripts above make use of hex a lot. Recently, in wanting to keep faith with approaches that allow simple, standard text editors to do the work, I’ve been forced to look at how to get Adobe Acrobat to display hexified buffers. Suffice to say that I did it, too.

browse: pdf obj: x binary: binary
"
self browse: 'RIFFS.PDF' obj: 4 binary: 1
"
| me file xref lah n |
file ¨ ((FileStream fileNamed:pdf) contentsOfEntireFile) withSqueakLineEndings.
n ¨ file findString: 'xref'.
xref ¨ file copyFrom: n to: (file size).
(xref openInWorkspaceWithTitle: pdf,'.xref'). "Provide a view"
me ¨ (xref lineNumber: (x+3)) asInteger. "correct for 2 or 3 or whatever"
lah ¨ file findString: 'endobj' startingAt: me.
(binary = 1)
	ifTrue: [(((FileStream fileNamed:pdf) position: me) next: lah-me+6) openInWorkspaceWithTitle: 'object ',(x asString),' [',pdf,']'.]
	ifFalse: [(((FileStream fileNamed:pdf) position: me) next: lah-me+6) withSqueakLineEndings openInWorkspaceWithTitle: 'object ',(x asString),' [',pdf,']'.].

Explanation:
This represents my prototype for a PDF reader program. browse: consumes a string which is a PDF. It has flags which the user selects, if the user sets the binary: flag to ‘1’, then the program just displays that object buffer without converting line endings (which could be binary data not intended to be treated as text, like graphics or whatever). Otherwise, the program will treat it as text, and display it nicely in a workspace window. The obj: flag allows the user to select which object in the PDF to read. So, if a theoretical PDF has 10 objects, and the tenth one is a JPEG, and I want the JPEG image, then I’ll type this and evaluate it to get the desired result - ie. the picture out of the PDF.

self browse: ‘theoretical.pdf’ obj: 10 binary: 1.

browse still needs to be improved to give better support for such situations. Also, it needs to support xrefs existing in multiple locations. But, it is functional code for most circumstances.

Point:
browse enables the user to get at certain data that they want, rather than having it appear that the only way to the data is through a more static rendering of a browser (as is the case with Adobe Acrobat in the main). Its main function, therefore, is to act as a reader program. However, since, PDFs contain standard formats, like JPEG, or standard compression methods, reading the xref is enough to extract the desired piece of data. This can then be worked on as desired. The template.pdf (including in the email) also allows confusing (well, for humans at least) kerning data to be interpreted by your PDF reader program like Adobe Acrobat (that is, without the overhead of loading a massive file  into memory).