Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Create a simple PDF file from a text file!

Usefull Functions & Procedures

Create a simple PDF file from a text file!

by  GriffMG  Posted    (Edited  )
** UPDATE 3 *** 24/11/2011
It seems that acrobat specify a set of characters that need to be 'escaped'
for some readers to be able to display even simple text correctly.
So I have modified the text reader function to do the escaping as the file is read.

** UPDATE 2 ***

I have finally worked out to get a bit of precision into the xref block and stream sizes - and now it doesn't throw any errors in PDF X-Change viewer, which makes it about right so far as I am concerned.

** UPDATE 1 ***

I have just realised how the code would be dificult to work with if you cut and pasted it from here. So, I have zipped it up and put it on my web site

http://www.finedata.com/shdata/pdfcreator.zip

** End of Update **


Recently, I needed to convert a simpe text file into a PDF that could be
emailed to recipients.

I tried looking for an existing package, but they were either silly money
or just not very good... so I found a working VB example and partly ported it,
partly rewrote it for VFP.

It started as an enormous single function, but I have broken it down into a
few smaller ones to improve readability.

To execute it you just include the various functions in your source file
and call the PDFCREATE function with the input text file and the output pdf

PDFCREATE("C:\TEMP\MyTextFile.txt","C:\TEMP\MyPDFFILE.pdf")

You can now specify a number of lines per page (third parameter) which defaults to zero to allow for control by chr(12) in the text file, the font to use (fourth parameter) and the size of the font (fifth parameter)

PDFCREATE("C:\TEMP\MyTextFile.txt","C:\TEMP\MyPDFFILE.pdf",0,"Courier",10)


Code:
PDFCREATE("C:\APPS\ACTIONSCAN\FAXFOLDER\XR200365.001","C:\APPS\ACTIONSCAN\FAXFOLDER\XR200365.PDF",0,"Courier",10)

FUNCTION PDFCREATE
	PARAMETERS m.INFILENAME,m.OUTFILENAME,m.PAGELENGTH,m.NAMEFONT,m.SIZEFONT
	PRIVATE m.INFILENAME,m.OUTFILENAME
	PRIVATE m.CRLF
	PRIVATE XREF_END_CHAR
	PRIVATE PDFOBJECT_END
	PRIVATE PDFOBJECT_BEGIN
	PRIVATE PDFXREFMARKER
	PRIVATE m.STRPAGES
	PRIVATE m.NOPAGES
	PRIVATE m.STARTAT
	PRIVATE STRFONTNAME
	PRIVATE m.NOLINES
	PRIVATE m.CURRPAGE
	DECLARE ARRXREF(10)
	DECLARE	ARRDATA(29)
	DECLARE ARRTMP(1)
	IF PCOUNT() < 5
		m.SIZEFONT = 12
	ENDIF
	IF PCOUNT() < 4
		m.NAMEFONT = "Courier-Bold"
	ENDIF
	IF PCOUNT() < 3
		m.PAGELENGTH = 0  && 0 is used to force control to look for chr(12) for new page
	ENDIF

	m.STARTAT = 1
	STRFONTNAME = m.NAMEFONT
	PDFOBJECT_END = "endobj"
	PDFOBJECT_BEGIN = " 0 obj"
	PDFXREFMARKER	= "PDFXREFMARKER"
	XREF_END_CHAR = " 00000 n"
	
	m.OBJECTCOUNT = 6  && AT LEAST 6 OBJECT BLOCKS
	m.CRLF = CHR(13)+CHR(10)
	ARRXREF(1)  = "xref"
	ARRXREF(2)  = "0 10"
	ARRXREF(3)  = "0000000000 65535 f"
	ARRXREF(4)  = PDFXREFMARKER  && position of obj 1 - title block
	ARRXREF(5)  = PDFXREFMARKER  && position of obj 2 - catalogue
	ARRXREF(6)  = PDFXREFMARKER  && position of obj 3 - describes the number of pages
	ARRXREF(7)  = PDFXREFMARKER  && position of obj 4 - fonts
	ARRXREF(8)  = PDFXREFMARKER  && position of obj 5 - encoding
	ARRXREF(9)  = PDFXREFMARKER  && position of obj 6 - font description ?
	ARRXREF(10) = PDFXREFMARKER  && position of obj 7 - describes the container for the pages

	PDFINITIALISE()

	m.NOPAGES = PDFREADTEXTFILE(m.INFILENAME)

	m.STARTAT = 1
	m.STRPAGES = ""
	FOR m.CURRPAGE = 1 TO m.NOPAGES
		PDFCREATEPAGE()
	NEXT

	** this adds in the objects numbered 2 & 3 - at the end because the pdf does not HAVE to be in numerical order
	PDFADDCATALOGDETAILS()

	ARRXREF(5) = ARRXREF(ALEN(ARRXREF))
	ARRXREF(ALEN(ARRXREF)) = ""


	** INSERT THE UNIQUE POSITION INTO THE XREF TABLE
	ARRXREF(6) = PDFXREFMARKER
	m.OBJECTCOUNT = m.OBJECTCOUNT + 1
	** INSERT THE NUMBER OF 'OBJECTS' INTO THE XREFS TABLE
	ARRXREF(2) = "0 " + ALLTRIM(STR(m.OBJECTCOUNT))

	** ADD THE FOOTER DETAILS AND THEN APPEND TO THE DATA ARRAY
	PDFFOOTER()

	** WRITE THE DATA ARRAY OUT TO THE PDF FILE
	PDFWRITE(m.OUTFILENAME)

	RETURN(.T.)

FUNCTION PDFREADTEXTFILE
	PARAMETERS m.INFILENAME
	PRIVATE m.INFILENAME,m.NOPAGES,I,m.LINENUMBER
	** we can read the incoming text file into a string and then fill an array with the string lines
	m.NOLINES = ALINES(ARRTMP,FILETOSTR(m.INFILENAME))
	m.NOPAGES = 1
	m.LINENUMBER = 0
	FOR I = 1 TO m.NOLINES
		m.LINENUMBER = m.LINENUMBER +1
		** STRANSFORM THE INPUT STRING TO GET RID OF HIEND ASCII CHARACTERS
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(205),"=")
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(186),"|")
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(187),"+")
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(188),"+")
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(200),"+")
		ARRTMP(I) = STRTRAN(ARRTMP(I),CHR(201),"+")
		IF CHR(12) $ ARRTMP(I) .OR. (m.LINENUMBER >= m.PAGELENGTH .AND. m.PAGELENGTH > 0)
			m.NOPAGES = m.NOPAGES +1
			m.LINENUMBER = 0
		ENDIF
	NEXT
	RETURN(m.NOPAGES)

FUNCTION PDFCREATEPAGE
	PRIVATE I,m.STARTSIZE,m.NOROWS,m.LINENUMBER,m.STREAMLENGTH
	m.OBJECTCOUNT = m.OBJECTCOUNT + 1
	m.STRPAGES = m.STRPAGES + " " + ALLTRIM(STR(m.OBJECTCOUNT)) + " 0 R"
	*INTLEN = LEN(STR(m.OBJECTCOUNT)) + LEN(STR(m.OBJECTCOUNT+1))
	** KEEP A TRACK OF WHERE THE DATA BLOCK STARTED
	m.STARTSIZE = ALEN(ARRDATA)
	** ADD 18 LINES TO THE DATA ARRAY
	DIMENSION ARRDATA(ALEN(ARRDATA)+18)
	** FILL IN THE DESCRIPTION OF THE BLOCK
	ARRDATA(m.STARTSIZE+ 1) = ALLTRIM(STR(m.OBJECTCOUNT)) + PDFOBJECT_BEGIN
	ARRDATA(m.STARTSIZE+ 2) = "<<"
	ARRDATA(m.STARTSIZE+ 3) = "/Type /Page"
	ARRDATA(m.STARTSIZE+ 4) = "/Parent 3 0 R"
	ARRDATA(m.STARTSIZE+ 5) = "/Resources 6 0 R"

	m.OBJECTCOUNT = m.OBJECTCOUNT + 1
	ARRDATA(m.STARTSIZE+ 6) = "/Contents " + ALLTRIM(STR(m.OBJECTCOUNT)) + " 0 R"
	ARRDATA(m.STARTSIZE+ 7) = ">>"
	ARRDATA(m.STARTSIZE+ 8) = PDFOBJECT_END

	DIMENSION ARRXREF(ALEN(ARRXREF)+1)
	ARRXREF(ALEN(ARRXREF)) = PDFXREFMARKER

	ARRDATA(m.STARTSIZE+ 9) = ALLTRIM(STR(m.OBJECTCOUNT)) + PDFOBJECT_BEGIN
	ARRDATA(m.STARTSIZE+ 10) = "<<"
	m.OBJECTCOUNT = m.OBJECTCOUNT + 1
	** the length if this object (distance from BT to just befor enstream) is recorded in the next object
	** having just incremented objectcount
	ARRDATA(m.STARTSIZE+ 11) = "/Length " + ALLTRIM(STR(m.OBJECTCOUNT)) + " 0 R"
	ARRDATA(m.STARTSIZE+ 12) = ">>"
	ARRDATA(m.STARTSIZE+ 13) = "stream"
	ARRDATA(m.STARTSIZE+ 14) = "BT" && begin text  
	** START STRACKING THE LENGTH OF THE STREAM
	m.STREAMLENGTH = LEN(ARRDATA(m.STARTSIZE+ 14))+2

	ARRDATA(m.STARTSIZE+ 15) = "/F1 "+ALLTRIM(STR(m.SIZEFONT))+" Tf" && fontsize
	m.STREAMLENGTH = m.STREAMLENGTH + LEN(ARRDATA(m.STARTSIZE+ 15))+2

	ARRDATA(m.STARTSIZE+ 16) = "1 0 0 1 50 802 Tm"
	m.STREAMLENGTH = m.STREAMLENGTH + LEN(ARRDATA(m.STARTSIZE+ 16))+2

	ARRDATA(m.STARTSIZE+ 17) = ALLTRIM(STR(m.SIZEFONT*1.2))+" TL" && linefeed spacing
	m.STREAMLENGTH = m.STREAMLENGTH + LEN(ARRDATA(m.STARTSIZE+ 17))+2

	** we need to scan the tmpData array for a given page
	I = m.STARTAT
	m.LINENUMBER = 0
	m.FLG = .T.
	DO WHILE I <= ALEN(ARRTMP) .AND. m.FLG
		m.LINENUMBER = m.LINENUMBER +1
		** add an entry to the data array
		DIMENSION ARRDATA(ALEN(ARRDATA)+1)
		ARRDATA(ALEN(ARRDATA)) = "T* ("+TRIM(ARRTMP(I))+") Tj"
		** add the length of this string to the stream length
		m.STREAMLENGTH = m.STREAMLENGTH + LEN(ARRDATA(ALEN(ARRDATA)))+2
		IF CHR(12) $ ARRTMP(I) .OR. (m.LINENUMBER >= m.PAGELENGTH .AND. m.PAGELENGTH > 0)
			m.STARTAT = I+1
			m.FLG = .F.
		ENDIF
		I = I + 1
	ENDDO

	m.STARTSIZE = ALEN(ARRDATA)
	** ADD ANOTHER 18 LINES TO THE DATA ARRAY
	DIMENSION ARRDATA(ALEN(ARRDATA)+18)
	ARRDATA(ALEN(ARRDATA)) = ""  && BLANK LINE?
	ARRDATA(m.STARTSIZE+ 1) = "ET"  && end text
	** add the length of this string to the stream length
	m.STREAMLENGTH = m.STREAMLENGTH + LEN(ARRDATA(m.STARTSIZE+ 1))+2
	ARRDATA(m.STARTSIZE+ 2) = "endstream"
	ARRDATA(m.STARTSIZE+ 3) = PDFOBJECT_END

	DIMENSION ARRXREF(ALEN(ARRXREF)+1)
	ARRXREF(ALEN(ARRXREF)) = PDFXREFMARKER

	ARRDATA(m.STARTSIZE+ 4) = ALLTRIM(STR(m.OBJECTCOUNT)) + PDFOBJECT_BEGIN
	ARRDATA(m.STARTSIZE+ 5) = ALLTRIM(STR(m.STREAMLENGTH))

	ARRDATA(m.STARTSIZE+ 6) = PDFOBJECT_END


	DIMENSION ARRXREF(ALEN(ARRXREF)+1)
	ARRXREF(ALEN(ARRXREF)) = PDFXREFMARKER

	RETURN(.T.)


FUNCTION PDFWRITE
	PARAMETERS m.OUTFILENAME
	PRIVATE I, m.OUTFILENAME,m.STRING,m.TEMPBIT,X,XREFINDEX,M.OFFSETPOSN
	DECLARE ARRXREFS(m.OBJECTCOUNT-1) && SET UP A NEW XREFS TABLE TO HOLD POSITIONS OF ALL BLOCKS
	FOR X = 1 TO ALEN(ARRXREFS)
		ARRXREFS(X)=0
	NEXT
	m.OFFSETPOSN = 0
	m.STRING = ""
	XREFINDEX = 0
	FOR I = 1 TO ALEN(ARRDATA)
		m.TEMPBIT = ARRDATA(I)
		IF TYPE("m.tempbit") = "C" .AND. !EMPTY(m.TEMPBIT)
			DO CASE
				CASE RIGHT(UPPER(m.TEMPBIT),LEN(PDFOBJECT_BEGIN)) = UPPER(PDFOBJECT_BEGIN)
					** WE HAVE HERE THE STARTING POSITION OF AN OBJECT - WHICH WE NEED IN THE XREFS TABLE
					** the xref we are interested in is the value of the bit before the PDFOBJECTBEGIN
					X =  VAL(LEFT(m.TEMPBIT,LEN(m.TEMPBIT)-LEN(PDFOBJECT_BEGIN)))
					ARRXREFS(X) = LEN(m.STRING)
				CASE UPPER(m.TEMPBIT) = PDFXREFMARKER && THIS IS AN XREF IN WAITING...
					XREFINDEX = XREFINDEX +1
					m.TEMPBIT = RIGHT("0000000000"+ALLTRIM(STR(ARRXREFS(XREFINDEX))),10) + XREF_END_CHAR
				CASE UPPER(m.TEMPBIT) == "XREF"
					** make a note of the position that the xref block starts
					m.OFFSETPOSN = LEN(m.STRING)
				CASE UPPER(m.TEMPBIT) == "M.OFFSETPOSN"
					** put the position of the xref block into the string 
					m.TEMPBIT = ALLTRIM(STR(m.OFFSETPOSN))
			ENDCASE
			m.STRING = m.STRING + m.TEMPBIT+m.CRLF
		ENDIF
	NEXT
	STRTOFILE(m.STRING,m.OUTFILENAME)
	RETURN(.T.)

FUNCTION PDFADDCATALOGDETAILS
	PRIVATE m.STARTSIZE
	m.STARTSIZE = ALEN(ARRDATA)
	** ADD 15 LINES TO THE DATA ARRAY
	DIMENSION ARRDATA(ALEN(ARRDATA)+15)
	ARRDATA(ALEN(ARRDATA)) = ""
	ARRDATA(m.STARTSIZE + 1) = "2" + PDFOBJECT_BEGIN
	ARRDATA(m.STARTSIZE + 2) = "<<"
	ARRDATA(m.STARTSIZE + 3) = "/Type /Catalog"
	ARRDATA(m.STARTSIZE + 4) = "/Pages 3 0 R"   && the number of pages is in section/obj number 3
	ARRDATA(m.STARTSIZE + 5) = "/PageLayout /OneColumn"
	ARRDATA(m.STARTSIZE + 6) = ">>"
	ARRDATA(m.STARTSIZE + 7) = PDFOBJECT_END
	ARRDATA(m.STARTSIZE + 8) = "3" + PDFOBJECT_BEGIN
	ARRDATA(m.STARTSIZE + 9) = "<<"
	ARRDATA(m.STARTSIZE + 10) = "/Type /Pages"
	ARRDATA(m.STARTSIZE + 11) = "/Count " + ALLTRIM(STR(m.NOPAGES))
	ARRDATA(m.STARTSIZE + 12) = "/MediaBox [ 0 0 595 842 ]"
	ARRDATA(m.STARTSIZE + 13) = "/Kids [" + m.STRPAGES + " ]"
	ARRDATA(m.STARTSIZE + 14) = ">>"
	ARRDATA(m.STARTSIZE + 15) = PDFOBJECT_END
	RETURN(.T.)

FUNCTION PDFINITIALISE
	ARRDATA(1) = "%PDF-1.2 "
	ARRDATA(2) = "%MGF"
	ARRDATA(3) = "1" + PDFOBJECT_BEGIN
	ARRDATA(4) = "<<"
	ARRDATA(5) = "/Creator  (Martin Griffin of Finedata Limited)"
	ARRDATA(6) = "/Producer (VFP TXT to PDF martin.griffin@finedata.com)"
	ARRDATA(7) = "/Title    (VFPTXTTOPDF)"
	ARRDATA(8) = ">>"
	ARRDATA(9) = PDFOBJECT_END
	ARRDATA(10) = "4" + PDFOBJECT_BEGIN
	ARRDATA(11) = "<<"
	ARRDATA(12) = "/Type /Font"
	ARRDATA(13) = "/Subtype /Type1"
	ARRDATA(14) = "/Name /F1"
	ARRDATA(15) = "/Encoding 5 0 R"
	ARRDATA(16) = "/BaseFont /" + STRFONTNAME
	ARRDATA(17) = ">>"
	ARRDATA(18) = PDFOBJECT_END
	ARRDATA(19) = "5" + PDFOBJECT_BEGIN
	ARRDATA(20) = "<<"
	ARRDATA(21) = "/Type /Encoding"
	ARRDATA(22) = "/BaseEncoding /WinAnsiEncoding"
	ARRDATA(23) = ">>"
	ARRDATA(24) = PDFOBJECT_END
	ARRDATA(25) = "6" + PDFOBJECT_BEGIN
	ARRDATA(26) = "<<"
	ARRDATA(27) = "/Font << /F1 4 0 R   >>  /ProcSet [ /PDF  /Text ]"
	ARRDATA(28) = ">>"
	ARRDATA(29) = PDFOBJECT_END
	RETURN(.T.)

FUNCTION PDFFOOTER
	PRIVATE I
	DIMENSION ARRXREF(ALEN(ARRXREF)+9)
	ARRXREF(ALEN(ARRXREF)-8) = "trailer"
	ARRXREF(ALEN(ARRXREF)-7) = "<<"
	ARRXREF(ALEN(ARRXREF)-6) = "/Size " + ALLTRIM(STR(m.OBJECTCOUNT))
	ARRXREF(ALEN(ARRXREF)-5) = "/Root 2 0 R"
	ARRXREF(ALEN(ARRXREF)-4) = "/Info 1 0 R"
	ARRXREF(ALEN(ARRXREF)-3) = ">>"
	ARRXREF(ALEN(ARRXREF)-2) = "startxref"
	ARRXREF(ALEN(ARRXREF)-1) = "M.OFFSETPOSN" && REPLACE THIS VALUE AS WRITTEN OUT TO TEXT FILE
	ARRXREF(ALEN(ARRXREF)) = "%%EOF"
	FOR I = 1 TO ALEN(ARRXREF)
		IF !EMPTY(ARRXREF(I))
			DIMENSION ARRDATA(ALEN(ARRDATA)+1)
			ARRDATA(ALEN(ARRDATA)) = ARRXREF(I)
		ENDIF
	NEXT
	RETURN(.T.)
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top