Tutorial

Getting Started
The first command you will need to give ddoc is dpStartDoc. This tells ddoc that you want to print or preview a document. It returns a handle to the new document. Some folks get a little intimidated when they hear the word handle. Don't be. It's just an identifier for the document. It must be passed to the engine so that ddoc knows which document you're working on. It's necessary in the 16-bit world because all of your .exe programs that call ddoc16.dll share the same copy of the dll. It's needed in the 32-bit world because all threads of a program share the dll.

dpStartDoc has quite a few parameters. Don't let this intimidate you either! Usually, you'll want to create a wrapper function so you don't have to remember all of the parameters. Below I describe the parameters and then give an example of how a simple wrapper can make calling it easier.

Parameter Description
hParent Reserved for future use. Please put a Zero here
zTitle Title of the document, displayed in the title bar of the viewer and identifies the print job
zFile Name of the document to create. This parameter may be passed as "" if you want the viewer to determine a unique temp file and use that. Note: If you pass "" and want to find out what the file name is, you can to call dpGetFileName.to fill a buffer with the name. Note that "" is a null string. You can pass ByVal %NULL in PowerBasic, or '' in Delphi.
iUOM Either DDOC_INCH or DDOC_CM indicating whether you are referencing positions in inches or cm
iPaper Paper size for the first page - see the DDOC_PAPER_xxx constants
iOrient Page orientation for the first page
iBin Paper Bin for the first page - see the DDOC_BIN_xxx constants
iOptions Other document options: this sets the initial zoom setting (DDOC_ZOOMFIT, etc - see constants) and turns on and off some optional buttons (DDOC_ALLOWEMAPI, etc.)

'- Here is a wrapper function for dpStartDoc
'  If you have to dpStartDoc a bunch of times in your
'  program, odds are the function will look similar each
'  time it's called. Anyway, here's a wrapper.
'
Function myStartDoc ( zTitle$ ) as Integer
   myStartDoc = dpStartDoc ( 0, zTitle$, '', DDOC_INCH, DDOC_PAPER_LETTER, _
         DDOC_PORTRAIT, DDOC_SYSTEM_DEFAULT, DDOC_ZOOMWIDTH )
End Function

Now that we know how to initialize the document, here's a basic outline of how to work ddoc.

'- Here's a skeleton call 
'
Sub MainSub ()
   
   Dim hDoc%
   hDoc% = myStartDoc ( "My Document" )
   if hDoc% < 1 then
      MsgBox "Unable to init document"
   else
   
      '- This will pop up an empty preview window 
      '  . . . empty because I didn't execute 
      '  any text or drawing commands.
      '
      dpEndDoc hDoc%, DDOC_END_VIEW + DDOC_END_DELETE
   end if

End Sub

Working With Text
This section is based on the skeleton described in the first section of this text. Working with text is the most basic feature of ddoc. ddoc provides an interface that allows the programmer to use any True Type font installed on the system, print a line of text, print text at any angle, and wrap text in a given area. Here is a list of the text manipulation functions in ddoc:

  • dpAngleText
  • dpClipText
  • dpFont
  • dpFontSpacing
  • dpSetTabs
  • dpTabText
  • dpText
  • dpTextWidth
  • dpWrapText
  • dpWrapContinue
  • dpWrapCount

Changing fonts and printing basic lines of text
After a document is initialized with dpStartDoc, the next thing a programmer must do is specify which font will be used by calling dpFont. Once specified, the new font will be selected every time a text function is called until:
  1. dpFont is called again
  2. The next page is started by calling dpNewPage

It is important to remember that dpFont needs to always be called after dpStartDoc and after dpNewPage. If you forget to call dpFont, the default font will be set to Arial 10point. Another tip stick with the basic 3 fonts Arial, Times New Roman, and Courier New (or other fonts that come standard with the system) unless you are absolutely positive that your users have other fonts installed. Trying to print a document with a font that isn't installed on the system can lead to unpredictable results. Note that, as of v1.8, ddoc supports the dpFontSpacing call. This allows the programmer to add extra space (expressed in points) to be added between each letter of all text calls (except dpWrapText and dpWrapContinue).

After a font is selected, you can print some text. There are 5 things you need to tell ddoc in order to have it print:

  1. Which viewer handle are you using? This makes it thread-safe as a program can be building more than one document simultaneously.
  2. How far over on the page should it print (x coordinate)?
  3. How far down (y coordinate)?
  4. How do you want the text aligned left, right, centered, or decimal?
  5. Finally, what words do you want printed?

Use dpText to print out a standard line of text. There are a few things to know about dpText:

  1. It doesn't wrap text.
  2. It doesn't care how long the text is, it will print as much as it can given the width of the page..
  3. It always prints with the font specified by the last call to dpFont. If this font is invalid, the computer will select a font that's "close". Close, however, might not be close enough, so, as mentioned above, make sure that you are selecting a font that is on the system. To be safe, stick with Arial, Times, and Courier.
e.g.
Dim hPreview as Integer

hPreview = dpStartDoc( 0, "MyPreview", "", DDOC_INCH, DDOC_PAPER_LETTER, _
DDOC_PORTRAIT, DDOC_BIN_UPPER, DDOC_ZOOMFIT  )
if hPreview < 1 then
   MsgBox "Unable to initilaize the print engine. Status =" + str$(hPreview)
else
   dpFont hPreview, DDOC_FONTBOLD + DDOC_FONTITALIC, 14, vbBlack, "Arial"
   dpText hPreview, 4.25, 1, DDOC_CENTER, "Centered, Arial 14 pt, Bold-Italic"
   dpFont hPreview, DDOC_FONTNORMAL, 9, vbBlack, "Courier New"
   dpText hPreview, 1, 2, DDOC_LEFT, "Courier New, 9 point"
   dpFont hPreview, DDOC_FONTUNDERLINE, 10, vbBlack, "Times New Roman"
   dpText hPreview, 1, 3, DDOC_LEFT, "Section"
   dpText hPreview, 2, 3, DDOC_RIGHT, "Value"

   dpFont hPreview, DDOC_FONTNORMAL, 10, vbBlack, "Times New Roman"
   dpText hPreview, 1, 3.2, DDOC_LEFT, "Sec. A"
   dpText hPreview, 2, 3.2, DDOC_RIGHT, "100,000"
   dpEndDoc hPreview, DDOC_END_VIEW + DDOC_END_DELETE
end if

Width of text and baseline alignment
ddoc (as of version 1.6) has a new function to determine the width of any text before printing it. This call, dpTextWidth, takes the text you want to print and returns how wide the text would be (in current units of measure - either cm or inches) if printed to the default printer. This is useful if you want to print more text (perhaps in a different font) where the last text left off. There is an important note, if you want to do this: by default, ddoc prints text with respect to the TOP of the text being printed. That is, if you tell dpText to print at 1" down and 1" over, it prints the line so that the top of the first character lines up with this coordinate. If you want to print multiple strings of text, one after the other on the same line using a different font, this isn't good - most people expect that text printed in different size fonts, on the same line will have their baselines aligned, not the tops of the letters! In order for text to be printed and have it use coordinates with respect to the baseline of the text instead of the top of the text, you need to add a flag, DDOC_FONTBASELINE, to the dpFont command. All text printed after this line will have it's text printed with respect to the baseline instead of to the top of the text. For example, the following code should output aligned to the baseline:

currentX! = 3
dpFont hPrev%, DDOC_FONTBASELINE, 10, vbBlack, "Times New Roman"
dpText hPrev%, currentX!, 1, DDOC_LEFT, "This"

currentX! = currentX! + dpTextWidth(hPrev%, "This ")
dpFont hPrev%, DDOC_FONTBASELINE or DDOC_FONTBOLD, _
14, vbBlack, "Times New Roman"
dpText hPrev%, currentX!, 1, DDOC_LEFT, "Text "

currentX! = currentX! + dpTextWidth(hPrev%, "This ")
dpFont hPrev%, DDOC_FONTBASELINE, 10, vbBlack, "Times New Roman"
dpText hPrev%, currentX!, 1, DDOC_LEFT, "is Baseline "

currentX! = currentX! + dpTextWidth(hPrev%, "is Baseline ")
dpFont hPrev%, DDOC_FONTBASELINE, 18, vbBlack, "Times New Roman"
dpText hPrev%, currentX!, 1, DDOC_LEFT, "Aligned"

The tabular report
A typical use for the print/preview engine is to print a report from a database. Suppose, for example, we wish to print a list of phone numbers and e-mail addresses for customers. ddoc will help you do this job with ease. For example, the following routine will loop through a database and print the names and phone numbers in it.

If I were to use dpText for all columns of a report, there is a chance that if the text in one column were too long, it would run over into the next column. Remember that dpText doesn't care how long the text is; it prints all the way over to the edge of the page if there is enough text. This is where you would use dpClipText. I use it below for the Name field. The name field is unable to run over into the phone field on the page because is clipped off before it gets that far. dpClipText can be very handy when you don't want to bother wrapping text and you think it might not fit in the column width that you have available.
Note: This is for example purposes only, I don't provide the actual data access routines. Unfortunately, you can't copy this code into vb and run it without providing the GetDBField$(iRecord&, sField$) function.

Sub PrintMailingList()
   
   Dim hPreview%
   Dim cy!     ' This variable keeps track of the Y position on the page.
   Dim lh!     ' This is the line-height or space between lines
   Dim c!(1 to 5) ' This array will contain column positions

   lh! = .1667 ' 6 lines per inch

   '- Define our column positions
   c!(1) = .5
   c!(2) = 1
   c!(3) = 4
   c!(4) = 5
   c!(5) = 6

   '- Start creating the Preview document
   hPreview% = dpStartDoc( . . . )
   if hPreview% < 1 then
      MsgBox "Can't initialize the print engine. Status =" + str$(hPreview)
   else
      '- Print the page header
      gosub print_header

      '- Loop through all records in the database.
      For iLoop& = 1 to NumberOfRecords&
         
         '- Check for the end of a page (if we are printing
         '  more than 10 inches down on the page, we need
         '  a new one).
         '
         if cy! > 10 then
            dpNewPage hPreview%, DDOC_PAPER_LETTER, DDOC_PORTRAIT, _                
                  DDOC_BIN_UPPER
            gosub print_header
         end if
         
         '- Print the columns of text
         dpText c!(1), cy!, DDOC_LEFT, GetDBField$(iLoop&, "ID")
         dpClipText c!(2), cy!, c!(3) - c!(2) - .1, DDOC_LEFT, _
         GetDBField$(iLoop&, "Name")
         dpText c!(3), cy!, DDOC_LEFT, GetDBField$(iLoop&, "Phone")
         dpText c!(4), cy!, DDOC_LEFT, GetDBField$(iLoop&, "Fax")
         dpText c!(5), cy!, DDOC_LEFT, GetDBField$(iLoop&, "E-Mail")
         cy! = cy! + lh!            
      Next iLoop&
   end if
exit sub

print_header:

   '- Print a title on the top of the page
   cy! = .5
   dpFont hPreview, DDOC_FONTBOLD, 12, vbBlack, "Times New Roman"
   dpText hPreview, 4.25, cy!, DDOC_CENTER, "Customer Listing"
   
   '- Print the column headings
   dpFont hPreview, DDOC_FONTUNDERLINE, 10, vbBlack, "Times New Roman"
   cy! = cy! + 2 * lh!
   dpText hPreview, c!(1), cy!, DDOC_LEFT, "ID"
   dpText hPreview, c!(2), cy!, DDOC_LEFT, "Customer Name"
   dpText hPreview, c!(3), cy!, DDOC_LEFT, "Phone"
   dpText hPreview, c!(4), cy!, DDOC_LEFT, "Fax"
   dpText hPreview, c!(5), cy!, DDOC_LEFT, "E-Mail"

   '- Set the font and Y position on the page
   '  so we're ready to print the columns of text.
   '
   cy! = cy! + 1.5 * lh!
   dpFont hPreview, DDOC_FONTNORMAL, 10, vbBlack, "Times New Roman"
   return

End Sub

I use ddoc almost daily to write routines like the one above. This type of report is what ddoc is designed specifically to do.

Another way to print columns
The new (as of 5-1-1999), dpTabText and dpSetTabs calls allow the programmer to easily print rows of tab-delimited text. dpSetTabs specifies the places you wish to have tabs. Call it and pass it the ddoc handle and a string describing the tabs. The string is a set of space delimited tab stops. Once the tab-stops are established, subsequent calls to dpTabText will print the text according to the tab stops specified. For example:

'- This tells ddoc to set 3 tab stops 
'  The first one is Left aligned at 1" over with a max width of 2 inches
'  The next is right aligned at 4" over with a maximum width of 1 inch
'  The final tab-stop is left aligned 4.1 inches over and will print
'  until it runs out of space on the page (no maximum width)
'
sTabStops = "L1W2 R4W1 L4.1"
dpSetTabs hPrev, sTabStops

'- Printing the text at the tab-stops is a matter of calling
'  the dpTabText call telling it how far down on the page to print,
'  and passing it the columns separated by a tab character. 
'
CurrentY = 3.2  'The Y position on the page
dpTabText hPrev, CurrentY, "Col 1" + chr$(9) + "Col 2" + chr$(9) + "Col 3"

dpTabText, like all other text commands, prints all text columns with the font specified by the last call to dpFont.

Printing text at angles
ddoc provides a single call to allow you to draw text at an angle: dpAngleText This function respects the current selected font and prints the text at the angle requested. It operates very similarly to the dpText function, except that instead of a parameter indicating the text alignment, it takes the angle of the text (in degrees from 0 though 360). The angle is given as a 2-byte integer (SmallInt in Delphi, Short Int in C, Integer in PB/DLL and VB).
E.G.

'- The code below will print angled text that prints
'  at 270 degrees (straight down) along the right
'  side of letter sized paper. Note that is will
'  print in a Blue, 10-point Arial font.
'
dpFont hPreview, DDOC_FONTNORMAL, 10, vbBlue, "Arial"
dpAngleText hPreview, 8, .75, 270, "This is some text"

Wrapping text
Wrapping text in a rectangle is tricky if you just use the API. ddoc makes it a little easier. There are three calls to master if you need to print paragraphs of text. The first is dpWrapText. This call takes some text and draws it within a given rectangle until either the rectangle is full of text or all of the text has been printed. dpWrapText returns the actual number of characters printed. The other that is needed is a call to tell you how many lines were actually printed. ddoc has the dpWrapCount call to tell you how many lines were printed in the last call to dpWrapText. As of version 1.8, there is a dpWrapContinue function. This allows you to avoid having to use dpWrapCount in most cases. It continues printing the text from the previous dpWrapText or dpWrapContinue call. It returns non-zero as long is there is still text left to print. Below I give an example of how to use the paragraph printing features in ddoc:

Sub PrintTextFile (fileName$)
   
   Dim hFile%, hPreview%
   Dim iLeft&, iPrinted&
   Dim Start!
   Dim sInput$

   '- The code below opens a file for
   '  sequential access. If there's an
   '  error, we bail.
   '
   On Error Resume Next
   hFile% = FreeFile
   Open fileName$ For Input As #hFile%
   
   If Err Then
      MsgBox "Unable to open the text file: " + fileName$
      Exit Sub
   End If
   On Error GoTo 0

   '- Init the ddoc print engine, bail if failed
   hPreview% = dpStartDoc(0, "Test file printing", "", DDOC_INCH, _
  DDOC_PAPER_LETTER, DDOC_PORTRAIT, _
               DDOC_BIN_UPPER, DDOC_ZOOMFIT)
   If hPreview% < 1 Then
      MsgBox "Can't initialize the print engine. Status =" + Str$(hPreview%)
      Exit Sub
   End If

   '- Loop through the text file
   Start! = 1  ' we start printing at 1" down on the page
   Do Until EOF(hFile%)
      
      '- Get the first paragraph
      Line Input #hFile%, sInput$
      iLeft& = Len(sInput$)
      Debug.Print sInput$
   
      Do
         dpFont hPreview%, DDOC_FONTNORMAL, 10, vbBlack, "Times New Roman"
         
         '- Tell the engine to print the current paragraph within
         '  the boundaries of the page (1 inch margins on 8.5x11 paper).
         '  Also, the .1667 parameter tells the engine to print each line
         '  .1667 inches apart (6 lines per inch). Remember, it returns
         '  the actual number of characters printed. By subtracting the
         '  number printed from the number in sInput$, we know when all
         '  text has been printed. The variable Start! Keeps track of
         '  where we are on the page.
         '
         iPrinted& = dpWrapText(hPreview%, 1, Start!, 7.5, 10, .1667, sInput$)
         sInput$ = Mid$(sInput$, iPrinted& + 1)
         iLeft& = iLeft& - iPrinted&
    
         '- If there are characters left to print, it means that we need
         '  to go on to the next page. Otherwise, we need to keep track
         '  of where we left off so we can continue printing the next
         '  paragraph.
         '
         If iLeft& > 1 Then
            dpNewPage hPreview%, DDOC_PAPER_LETTER, DDOC_PORTRAIT, _
                  DDOC_BIN_UPPER
            Start! = 1
         Else 
            
            '- The new starting position is the last one
            '  plus the total height of the lines that
            '  we just printed.
            '
            Start! = Start! + dpWrapCount(hPreview%) * .1667
            Exit Do
         End If
      Loop
      
      '- Get the next paragraph if we're not at the end of the file.
   Loop
   Close #hFile%
   dpEndDoc hPreview%, DDOC_END_VIEW + DDOC_END_DELETE

End Sub

Lines, Rectangles, Ellipses, Arcs, Pies, Bitmaps, and Metafiles
Lines
The ddoc API provides a single call to print a line. It takes as parameters the coordinates of the starting and ending points that define the line. Along with the defining points of the line, the programmer must provide the width of the line and the color of the line.

'- This prints a 1 point horizontal line in the color red.
'  The line is one inch down on the page and runs from
'  one inch over to 7.5 inches over on the page.
'
dpLine hPreview%, 1, 1, 7.5, 1, 1, vbRed

Rectangles and Ellipses
Rectangles and ellipses work in a similar manner to lines. Define the upper-left and lower-right corners of the rectangle, tell it what color to use for fill, and what type of line (if any) to surround the box with, and ddoc responds by printing the rectangle or ellipse. There is one trick involved to get it to print without a surrounding border, pass -1 for the line width parameter.

'- The following prints a rectangle with a blue 
'  border filled with the color red. The border is 
'  three points wide. The rectangle fills a rectangle 
'  defined by upper-left = (1,1) and lower-right = (3,2)
'
dpRect hPreview%, 1, 1, 3, 2, 3, vbRed, vbBlue

'- The following prints an ellipse a shaded background with no border.
dpEllipse hPreview%, 1, 1, 3, 2, -1, &hE0E0E0, 0

Arcs and Pie slices
To draw an arc or a pie slice, you use the dpArc and dpPie functions. Define the upper-left and lower-right corners of the rectangle that bounds the ellipse/circle from which the arc or pie slice will be taken. Tell ddoc the starting angle and how many degrees to draw (counter clock-wise). Define how wide the bounding line is, what color it is, and in the case of a pie, the fill color. To get a pie slice to print without a surrounding border, pass -1 for the line width parameter.

'- The following prints a pie with a blue 
'  border filled with the color red. The border is 
'  three points wide. The bounding rectangle is (1,1)(3, 2)
'  It starts at 45 degrees and ends at 180 degrees
'
dpPie hPreview%, 1, 1, 3, 2, 45, 135, 3, vbRed, vbBlue

'- This prints an arc shaped like a U. 
'  the line will be as thin as possible (w = 0)
'  and will be black.
'
dpArc hPreview%, 1, 1, 3, 2, 180, 180, 0, vbBlack

Bitmaps and MetaFiles
ddoc has an interface to print bitmap graphics and enhanced metafiles directly from a .bmp/.emf file. Additionally (as of ddoc v1.9d released 9/2003) .jpg files are also supported in the dpAddGraphic command only. If you need to print .gif or .png graphics, or need jpg support in a command other than dpAddGraphic I can only suggest that you use an external library to translate these graphics into .bmps for printing with ddoc if you need to use a format other than .bmp.

There are three ways to print a .bmp or .emf file. In all cases you need to provide the engine with an area to fit the graphic in and the name of the file. Note to ddrv users: unlike ddoc's little brother, ddrv, dpGraphic , dpDrawGraphic and dpEmbedGraphic by default will shrink the graphic to fit within the box, not stretch the graphic to the size of the bounding box. You can enable stretching (non-proportional) with a call to dpStretchMode. This call must be made on every page as it is reset to proportional shrinking at the beginning of a page. Additionally, the dpAddGraphic/dpDrawGraphic command supports jpg/jpeg files.

'- The following will print logo.bmp in the upper-left 
'  corner of the viewer window in a box defined by the 
'  points (1,1) and (3,2).
'
dpGraphic hPreview, 1, 1, 3, 2, "c:\mygraph.bmp"

'- or using embedding the graphic
dpEmbedGraphic hPreview, 1, 1, 3, 2, "c:\mygraph.bmp"

'- or using a referenced bitmap.
hGraphic = dpAddGraphic(hPreview, "c:\mygraph.bmp")
if hGraphic then
  dpDrawGraphic(hPreview, hGraphic, 1,1,3,2)
end if

Note the three different functions to print graphics. dpGraphic tells the print engine the location of the bmp/emf/jpg file so it can be loaded directly from the file when needed. This is the most efficient way to print graphics. dpEmbedGraphic copies the bmp into the temp file. The viewer then extracts the bmp to a temporary file when needed. dpAddGraphic stores the bitmap in the ddoc file, just like dpEmbedGraphic, but it doesn't do any drawing - once stored, dpAddGraphic returns a handle to that graphic. To print the stored graphic to the screen call dpDrawGraphic and pass it the handle. dpAddGraphic/dpDrawGraphic only store one copy of the graphic in the file, even if it's used multiple times. This is a huge space saver over dpEmbedGraphic. It is my intent to phase out dpEmbedGraphic and recommend my users use dpAddGraphic/dpDrawGraphic instead. Here are pros and cons of each graphic method:

dpGraphic
Pros: Faster, takes less storage space
Cons: Can't use this if you are going to transfer the temp file to another user via built-in email or other means of storage and recall, unless the recipient has the same path to the graphic file.

dpEmbedGraphic
Pros: Any user (email recipient or otherwise) can call up the document without needing access to the original graphic file.
Cons: Slower, takes up additional storage space as each time it's called, a graphic is added to the document.
Tip: If you're going to use the dpEmbedGraphic function, try to use it with bitmaps that are RLE compressed. The support for this type of compression is built-in and can save a lot of disk space. To convert a regular bitmap to RLE, use a good paint program (I use Paint Shop Pro from JASC) to call up the image and save it out RLE. If you need to use the same graphic more than once, use instead as that method only puts one copy of the bmp in the file - dpEmbedGraphic puts a new copy of the graphic in the file each time.

dpAddGraphic/dpDrawGraphic
Pros: Any user (email recipient or otherwise) can call up the document without needing access to the original graphic file. Use this function if you're allowing email and you need to display the same graphic more than once. This is the only function that supports .jpg/.jpeg graphics.
Cons: Slower than dpGraphic and takes up more space that dpGraphic. It takes up less space than dpEmbedGraphic, however, because a bitmap referenced only once by dpAddGraphic can be drawn as many times as you wish with dpDrawGraphic.

Note: All of the above graphics commands will shrink a bitmap to fit within the defined rectangle. If you desire the bitmap to be stretched to fit the rectangle instead, call the dpStretchMode command and put a non-zero number in the second parameter. All subsequent graphics calls will then stretch the bitmap.