CHAPTER 23

HOW TO SEND DATA TO THE PRINTER.

OVERVIEW:

There is a whole variety of considerations involved in communicating with a printer - that is, if you want to understand all possibilities that are available. For example, the printer may or may not have a spooler, or the spooler may or may not be activated. A spooler is like a buffer that stores printer jobs in a queue, so that your application is not disabled until the printer has finished printing, as would be the case if you wrote to the printer directly. There are Windows API's for communicating with the printer directly, or with the spooler directly.

I do not intend to describe all such possibilities here,

as I have not experimented with them myself. Instead, I will deal with the simplest method of communicating with the printer registered as the default printer. Using the default printer allows the system to handle most of the decisions, so that you can communicate with the printer by means of only a few basic API functions. Even so, there are still a number of problems that need to be sorted out before you can communicate with the printer satisfactorily. The code for printing a picture, or bitmap, for example is a little different to that for printing text.

THE PRINTER DIALOG BOX:

The first task, in using the printer, is to put the usual printer dialog box on the screen, and this is done by using the PrintDlg API function. This has only one parameter, but this parameter is the address of a very large PRINTDLG data structure. When using the default printer, however, the complications involved in this can be avoided, and most of the

parameters of the data structure can be set to zero, as in the following code (eq is a HLA macro that sets the LHS '=' to the dword value on the right. eq.w specifies word size values. set.d sets the dword parameter after the '=' equal to the second parameter):

eq [PRINTDLG.StructureSize], '=', 42h
eq [PRINTDLG.OwnerWindowHandle], '=', [WINDOW.Handle]
eq [PRINTDLG.DevmodeMemoryHandle], '=', 0
eq [PRINTDLG.DevnamesMemoryHandle], '=', 0
eq [PRINTDLG.PrinterDeviceContext], '=', 0
eq [PRINTDLG.Flags], '=', PD_RETURNDC
eq.w [PRINTDLG.StartPageNo], '=', 0
eq.w [PRINTDLG.EndPageNo], '=', 0
eq.w [PRINTDLG.MinPagesNo], '=', 0
eq.w [PRINTDLG.MaxPagesNo], '=', 0
eq.w [PRINTDLG.NoOfCopies], '=', 0
eq [PRINTDLG.hInstance], '=', 0
eq [PRINTDLG.CustomHookData], '=', 0
eq [PRINTDLG.PrintHookFnAddr], '=', 0
eq [PRINTDLG.SetupHookFnAddr], '=', 0
eq [PRINTDLG.PrintTemplateName], '=', 0
eq [PRINTDLG.SetupTemplateName], '=', 0
eq [PRINTDLG.PrintTemplateHandle], '=', 0
eq [PRINTDLG.SetupTemplateHandle], '=', 0
;
api PrintDlg,PRINTDLG
cmp eax,0
je near .exit
set.d '=',[PRINTER.DC],[PRINTDLG.PrinterDeviceContext]

The above is sufficient to put a dialog box on the screen. It requires only the structure size, the window handle, and the PD_RETURNDC flag, which specifies that the PrintDlg function should return a printer device context value, or handle, in the

PRINTDLG.PrinterDeviceContext element of the data structure. You can, if necessary, also fill in the minpagesno and maxpagesno elements, in the case of a multi page document, in order to allow the user to print only some specific page or pages.

SENDING DATA TO THE PRINTER:

Before sending the actual data to the printer, the printer must be informed that data is about to be sent, and how it will be sent. There is a possibly confusing variety of API functions, apart from those used to send the actual data, that can be used to give instructions to a printer. When using the default printer, in the most straightforward manner, only four of these will give the desired result: StartDoc, StartPage, EndPage, and EndDoc. StartDoc

requires the address of a DOCINFO data structure as a parameter. Apart from the actual data transmission API functions, and prior calculations involving pixel resolution values, described further on, these are all that are required to prepare the printer for printing. An example of code showing the use of these is the following PRINTER.PrintDoc procedure:

PRINTER.PrintDoc:
call PRINTER.GetVirtualScreenSize
;
eq [DOCINFO.StructureSize], '=', 14h
eq [DOCINFO.DocNameAddr], '=', PRINTER.DocName
eq [DOCINFO.OutputFilenameAddr], '=', 0
eq [DOCINFO.DataTypeNameAddr], '=', 0
eq [DOCINFO.JobTypeFlag], '=', 0
;
set.d '=',[PRINTER.DC],[PRINTDLG.PrinterDeviceContext]
api StartDoc,[PRINTER.DC],DOCINFO
for [PRINTER.NoOfPages]
call PRINTER.PrintPage
end.for
api EndDoc,[PRINTER.DC]
api DeleteDC,[PRINTER.DC]
ret
PRINTER.PrintPage:
api StartPage,[PRINTER.DC]
call PRINTER.Print Bitmap ; or call PRINTER.PrintText
api EndPage,[PRINTER.DC]
ret

The GetVirtualScreenSize refers to a procedure that you will have to code in order to carry out pixel resolution calculations, described further on. The DOCINFO structure need contain only the structure size, and an address of a string naming the document, which can be anything, and the other elements can ordinarily be set to zero. The printer device context handle is obtained from the PRINTDLG data structure via the PRINTDLG.PrinterDeviceContext element. The StartDoc function indicates to the printer the start of the entire printing job. The

StartPage and EndPage functions indicate the start and end of each page, and the PrintBitmap or PrintText procedures refer to the actual data codes for printing a single page of data. There can be a series of StartPage, data, EndPage sequences, which will cause the printer to print the data from each data call on a separate page, which is why this code sequence is put in a separate procedure (PRINTER.PrintPage), and called from a 'for' loop. The EndDoc function indicates to the printer the end of the entire printing job.

PRINTING A PICTURE:

The principal problem in printing a picture via a printer is due to the normally different screen resolutions of the printer and the video screen.

Suppose, for example, you have what appears to be a reasonable sized picture on your video screen, say 300 x 300 pixels on an 1280 x 1024 display. If you send this as a 300 x 300 pixel bitmap straight to the printer, and print it on an A4 size page, you will get only a small picture that does not compare at all with the size of the picture on the screen. This is because the pixels or 'dots' that a printer puts on a page are much smaller and closer together than the pixels on a video screen. You may be looking at a video screen 1280 pixels wide, but a printer may have 3000 dots across an A4 page width. Therefore, to print out a comparable sized picture from the screen, the bitmap will have to be multiplied or magnified before being printed by the printer. In the above example, the bitmap would have to be magnified more that twice its original size.

Magnifying a bitmap, however, creates problems of its own - i.e., if you simply copy each horizontal and vertical line of the bitmap a number of times to increase its size, straight lines on the picture at an angle to the vertical and horizontal will develop a jagged appearance. Therefore, you will need to create

some kind of algorithm for multiplying the picture size if you want to avoid or reduce this. On the other hand, if a simple multiplication will do, you can have the system multiply the bitmap data for you, and aviod the difficulty of doing it yourself.

In view of the above considerations, there are two possible methods, involving two possible API functions, for sending picture, or bitmap, data to the printer. If you want to use your own code to multiply the size of the bitmap, before sending it to the printer, you can use the BitBlt API function to send the bitmap data to the printer, in the same way as it is used to send bitmap data to the video screen, the only difference being that the API function is given the printer device context rather than the screen device context. If you don't want to use your own code to multiply the size of the bitmap, you can use the StretchBlt API function, which automatically multiplies the size of the bitmap for you. However, you must first use a multiplying factor to calculate the different sizes of the bitmap, in pixels, on the two devices, even thought you don't convert the bitmap data itself, because the StretchBlt function needs this information in order to create the proper multiplying factor for itself. The multiplying factor is obtained as follows:

Note that P.MltFactor refers to a screen to printer multiplying factor. NUMBER_.DivideNos refers to a procedure for dividing numbers into one another and, in the next section, NUMBER_.MltFloatNos refers to multiplication procedure code. NUMBER.Result refers to the answers obtained from these operations. The actual code for these procedures is not shown here.

PRINTER.GetVirtualScreenSize:
set.d '==&',[P.MltFactor.x],1,[P.MltFactor.x+4],0
set.d '==&',[P.MltFactor.y],1,[P.MltFactor.y+4],0
api GetDeviceCaps,[PRINTER.DC],LOGPIXELSX
mov [PRINTER.ResolutionX],eax
api GetDeviceCaps,[PRINTER.DC],LOGPIXELSY
mov [PRINTER.ResolutionY],eax
call. .printer.g.screen.x, if.d '>',[PRINTER.ResolutionX],[SCREEN.ResolutionX]
call. .printer.g.screen.y, if.d '>',[PRINTER.ResolutionY],[SCREEN.ResolutionY]
ret
.printer.g.screen.x:
pcall NUMBER_.DivideNos,[PRINTER.ResolutionX],[SCREEN.ResolutionX]
eq [P.MltFactor.x], '=', [NUMBER.Result]
eq [P.MltFactor.x+4], '=', [NUMBER.Result+4]
ret
printer.g.screen.y:
pcall NUMBER_.DivideNos,[PRINTER.ResolutionY],[SCREEN.ResolutionY]
eq [P.MltFactor.y], '=', [NUMBER.Result]
eq [P.MltFactor.y+4], '=', [NUMBER.Result+4]
ret

The following code, using the StretchBlt API function, sends the actual bitmap data to the printer:

PRINTER.PrintBitmap:
pcall NUMBER_.MltFloatNos,[BITMAP.Width],0,[P.MltFactor.x],[P.MltFactor.x+4]
eq [P.BITMAP.Width], '=', [NUMBER.Result]
pcall NUMBER_.MltFloatNos,[BITMAP.Height],0,,[P.MltFactor.y],[P.MltFactor.y+4]
eq [P.BITMAP.Height], '=', [NUMBER.Result]
zero.d [BITMAP.X],[BITMAP.Y],[P.BITMAP.X],[P.BITMAP.Y]
call SCREEN.CreateCompatibleDC
call SCREEN.SelectBitmapObj
api StretchBlt,[PRINTER.DC],[P.BITMAP.X],[P.BITMAP.Y],[P.BITMAP.Width],[P.BITMAP.Height], [COMP.DC],[BITMAP.X],[BITMAP.Y],[BITMAP.Width],[BITMAP.Height],SRCCOPY
call SCREEN.RestoreBitmapObj
call SCREEN.DeleteCompatibleDC
call SCREEN.DeleteDC
ret

This code is the same as that described previously for printing bitmaps to the screen, except that StretchBlt is used instead of BitBlt, and [PRINTER.DC] is used instead of [DISPLAY.DC] (the StretchBlt parameters are on two lines only because they are too long, in this case, to fit on one line, but they would be on one line in the code itself)

It is necessary to be aware that the StretchBlt API function is not guaranteed to work with all devices. I believe that it will work with any modern printer, but

not necessarily with the video screen itself. That is, if you want to multiply the size of a bitmap on the video screen itself, using the StretchBlt function, it will not be guaranteed to work, and the function may fail. Your application can check whether a particular API function will work with a particular device, such as the video screen, by using the GetDeviceCaps API function, which returns a yes, no, or numeric value answer to a query about a particular capacity of a particular device.

PRINTING TEXT:

There are at least two ways to send text to the printer. You can print text to a bitmap in memory, and thus send it as a bitmap to the printer, or you can select a font into the printer device context, and have the printer print the characters directly from the font, which provides far better quality in the printed text. The latter is therefore the best way to send text data to the printer.

The best API function to use when printing text is the DrawText API function, which allows specification, if necessary, of both the coordinate values of the rectangle in which the text is to be printed, and also its width and height. You can do this for each printed character, the advantage being that you can cause the printed text to look the same as it does on in your program window on the video screen. This is especially true if you are printing both text and graphics on the same page. Using the TextOut API function, letting the system take care of line breaks and the like automatically, will mean that the printed text may not be placed in the same way as it is in your screen window.

The DrawText function, however, also allows printing an entire block of text in a rectangle of suitable size, and is not restricted to printing characters one at a time, but it requires the inclusion of the character codes 0dh, 0ah (13,10 in decimal),

which are the carriage return, new line characters, in order to create line breaks.

To precisely specify the position of a text character on the printed page requires converting the width and height of the body of the text, in screen pixels, to a corresponding width and height, in printer pixels, on the printed page. This is not an excessively complicated operation. It requires only to obtain the horizontal and vertical pixel resolutions of both devices, and set up horizontal and vertical multiplying factors in order to convert any measurement from one device to the other, using the same code as indicated previously when working with bitmaps.

You don't have to correspondingly multiply the point size of the font assigned to the printer, however, because the font will be selected into a device context connected with the printer, which will reconcile the font with the device horizontal and vertical pixel resolutions automatically. I am assuming that you will be using a true type font, like arial, etc.

The following code also assumes that the screen text width and height on the video screen are such as to display the contents of an A4 size page. If not, page text width and height parameters would have to be calculated.

PRINTER.PrintText:
pcall NUMBER_.MltFloatNos,[SCREEN.TEXT.Width],0,[P.MltFactor.x],[P.MltFactor.x+4]
eq [P.TEXT.Width], '=', [NUMBER.Result]
pcall NUMBER_.MltFloatNos,[SCREEN.TEXT.Height],0,[P.MltFactor.y],[P.MltFactor.y+4]
eq [P.TEXT.Height], '=', [NUMBER.Result]
set.d'==&',[RECT],0,[RECT+4],0,[RECT+8],[P.TEXT.Width], [RECT+0ch],[P.TEXT.Height]
call CHARS.SRC_.TransferTo._PRINTER.SRC
eq [CHARS.No], '=', [Text_.P.TEXT.END.CHAR.No], '-', [Text_.P.TEXT.START.CHAR.No], '+', 1
api DrawText,[PRINTER.DC],PRINTER.CHARS,[CHARS.No],RECT,DT_LEFT|DT_WORDBREAK
ret

                  Part I: Part I Introduction
                  Chapter 1: Binary numbers, code, and procedures
                  Chapter 3: Assembly Language
                  Chapter 4: Assembly Language Code example
                  Chapter 5: Macros and HLA
                  Chapter 6: HLA code example
                  Chapter 7: The Windows operating system
                  Chapter 8: Data Structures
                  Chapter 9: How to Create a Windows Program
                  Chapter 10: How to Create an Exe File
                  Chapter 11: Structured Programming and OOP
                  Part II: Part II Introduction
                  Chapter 12: Debugging a Windows Program Code
                  Chapter 13: Painting the Window Client Area
                  Chapter 14: Creating Window Menus
                  Chapter 15: How to Create Toolbars
                  Chapter 16: How to Create Popup Menus
                  Chapter 17: About the Windows Clipboard
                  Chapter 18: How to Create Bitmaps
                  Chapter 19: Icons and the Ico Format
                  Chapter 20: Common Dialog Boxes
                  Chapter 21: Working with Files
                  Chapter 22: Scrollbars and Scrolling
                  Chapter 23: How to Send Data to the Printer
                  Chapter 24: Miscellaneous Topics

© Alen, June 2014
alen@alenspage.net


Material on this page may be reproduced
for personal use only.