    Печать в Delphi 

    ГЛАВА 23 

    Джо Хечт 

    До недавнего времени существовали только терминалы TTY (телетайпные) и 
алфавитно-цифровые принтеры. Не правда ли, печатать в те дни было очень легко! 
С появлением и развитием графических дисплеев и графических принтеров любая 
стоящая программа должна иметь специализированный аппаратный интерфейс для 
каждого поддерживаемого ею типа принтера. До появления Microsoft Windows 
программист должен был быть экспертом по широкому диапазону принтеров. Часто 
при установке программного продукта разработка интерфейса принтера для 
программы была одной из самых значительных проблем, с которой нужно было 
справляться самостоятельно. 
    Одной из первоначальных целей Microsoft Windows было представить 
программам возможность использовать общий интерфейс аппаратной части. Сегодня 
пользователи" нё'должНы "заботиться о программной поддержке всех существующих 
принтеров. Но все-таки мы тратим слишком много времени на попытки заставить 
приложения работать с драйверами печати, над которым и теперь почти не имеем власти. 
    Ниже перечислены основные вопросы, рассматриваемые в этой главе. 
    •	Работа драйвера печати и связанные с ним проблемы 
    •	Работа с цветом 
    •	Шрифты 
    •	DC (контекст устройства) принтера 
    Ш	На что стоит обратить внимание 
    •	Сложные полилинии 
    •	Сложные области 
    •	16-и 32-битовые драйверы принтеров 
    •	Некорректная информация о принтере 
    В	Процедуры отказа 
    В	Использование объекта TPrinter 
    Л	Использование процедуры WriteLn 
    в	Использование каниы принтера 
    В	Смена принтеров и их установок 
    И   Установки принтера: функции GetDevCaps и De-viceCapabilities 
    •   Использование функции DeviceCapabilities 

    Драйверы печати 

    Информация о драйверах печати и о проблемах, связанных с их использованием. 

    Шрифты и контекст устройства 

    Как работать со шрифтами, контекстом устройства принтера, сложными 
областями, 16- и 32-драйверами печати и разрядными управляющими кодами принтера. 

    Объект TPrinter, процедура WriteLn и канва принтера 

    Как использовать объект TPrinter, который Delphi инкапсулирует из системы 
печати Windows, упростить печать с помощью процедуры WriteLn, а также получить 
информацию о канве принтера. 

    Смена принтеров и их установок 

    Использование функций Get-DevCaps И DeviceCapabilities, управляющих кодов 
принтера и работа с канвой объекта 
    TPrinter. 

    Печать форм 

    В этом разделе рассказывается о печати форм. 

    В обход объекта TPrinter 

    Здесь вы узнаете, как обойтись без объекта TPrinter. 

537

    •	Управление принтером 
    •	Работа с канвой объекта TPrinter 
    •	Что может не работать 
    •	Печать форм 
    •	Работа в обход объекта TPrinter 

    Как работают драйверы печати 

    Windows GDI (интерфейс графического устройства) позволяет приложениям 
общаться с устройствами графического вывода в эдакой аппаратно-независимой 
манере. GDI предоставляет приложению дескриптор контекста дисплея (Dc) или 
информационного контекста (1с). Дескриптор DC позволяет вызвать GDI для 
рисования для данного контекста устройства (прямоугольников., форм, текста и 
т.д.), тогда как ^ пpeдJ^шьaяfiIJIpИДOЖSШ;ЦOJO^oжИJacJь_ получить информацию 
о_ конкретном устройстве (количество пикселей на дюйм, размер страницы и 
т.д.). И хотя иногда информацию об устройстве можно получить из DC, рисовать с 
помощью 1с невозможно. Информационный контекст удобен в том случае, когда вы 
хотите получить информацию об устройстве без дополнительных накладных расходов 
на инициализацию устройства для рисования. 
    В Delphi включены многие самостоятельные информационные вызовы и вызовы 
для рисования в свойства и методы объекта TPrinter. TPrinter затем вызывает 
для вас Windows GDI. Windows GDI опрашивает драйвер, чтобы проверить, способен 
ли он выполнить заданную работу. Если драйвер непосредственно поддерживает эту 
операцию, то GDI передает запрос драйверуL 
    В противном случае GDI опрашивает драйвер, чтобы выяснить, какие операции 
он может выполнить для получения необходимого результата на более низком 
уровне, а затем моделирует заданную операцию с помощью вызовов нижнего 
уровня.	*_ . _........ 
    Хорошим примером этого является вызов для рисования прямоугольника. Если 
драйвер не осуществляет непосредственного рисования прямоугольника, GDI может 
опросить драйвер, рисует ли он линии и какие именно. Если драйвер не рисует 
линий, GDI может опросить драйвер, рисует ли он пиксели. Если'^рисует, то GDI 
будет формировать прямоугольник, рисуя побитовое изображение и отсылая пиксели 
драйверу. Часто драйверы принтеров сообщают, что они поддерживают данную 
функцию, а сами вызывают GDI, который и выполняет вместо них всю грязную 
работу. Аналогично GDI может опросить видеодрайвер, чтобы выяснить, может ли 
он осуществить требуемую операцию. Если может, то эта операция выполняется с 
использованием видеодрайвера, который передает операцию либо программному 
обеспечению (прямо в его специализированной аппаратной части), либо обратно 
функциям поддержки GDI. 
    В случае, если ваше приложение потерпело неудачу при печати, подключите 
сразу и видеодрайвер, и драйвер печати. Драйверы, загружаемые с Windows, — это 
наилучшие драйверы, в чем можно убедиться на практике. Если вы изменили 
видеодрайвер и приложение выполнило печать правильно, предложите заказчикам 
приобрести обновленный видеодрайвер. Если это не исправило ситуацию, 
попытайтесь инсталлировать обновленный драйвер принтера. 
    Очень часто производители поставляют заказчику драйверы для своих 
принтеров. И часто повторный переход на стандартный драйвер, поставляемый с 
Microsoft Windows, устраняет проблемы, связанные с печатью. Иногда и выбор 
драйвера, работающего с аналогичной моделью принтера, устраняет сходные проблемы. 

    Проблемы, с которыми вы столкнетесь 

    Хотя система печати Microsoft Windows значительно упростила процесс 
печати, вы не можете быть уверенными, что то, что вы видите на дисплее, будет 
точно так напечатано на бумаге. 
    Во-первых, обратите внимание на то, что принтер имеет не такое разрешение, 
как дисплей. Прямоугольник, нарисованный на дисплее высотой 10 пикселей, будет 
выглядеть крошечным, если распечатать его на лазерном принтере с разрешением 
600 dpi. Кстати, разрешение принтеров варьируется примерно от 75 до 4000 dpi 
(точек на дюйм). Если в вашем приложении используется карандаш с шириной, 
равной единице, то результат может выглядеть хорошо на дисплее и. возможно, 
даже на лазерном принтере с разрешением 300 dpi. Однако, чтобы разглядеть эту 
линию на принтере высокого разрешения Linotronic Imagesetter, потребуется 
микроскоп, так как линия будет иметь ширину меньше 1/3500 дюйма. 
    Во-вторых, всегда имейте в виду, что пользователю может понадобиться 
окончательный результат работы вашей программы распечатанным большим тиражом 
на офсетном типографском прессе. Сегодня многие печатные прессы воспринимают 
электронную версию оттиска; они создают печатные стереотипы автоматически, 
прямо на прессе! Учтите, что окончательный вывод для офсетной печати сплошных 
линий может начаться с их разбиения на мелкие фрагменты, что-то около 1/288 
дюйма, и экранное изображение может стать не очень-то привлекательным, если 
разрешение экрана больше чем 65 dpi. 

    538	Часть VI. Сложный материал 

    Всегда масштабируйте полученный результат, чтобы он хорошо выглядел на 
устройстве, для которого вы осуществляете печать. Вы легко можете это сделать, 
установив координаты окончательного вывода с использованием постоянных единиц 
измерения, например дюймов, а не пикселей. 
    Получить информацию непосредственнсГйзТЯЗ! можно путем вызова функции 
Windows GetDeviceCaps (). 

    Horizontallnch   := GetDeviceCaps(Printer.Canvas.Handle,   LOGPIXELSX); 
Verticallnch        :=  GetDeviceCaps(Printer.Canvas.Handle,   LOGPIXELSY); 

    Особо отметим, что традиционно измерения при печати выражались в пунктах 
(1/72 дюйма). Используя пункты, можно управлять размещением графического 
вывода, объявив пару переменных в программе: 

    var PointsX : double; var PointsY : double; 
    а затем инициализировав эти переменные следующим образом: 
    PointsX   :=  GetDeviceCaps(Printer.Canvas.Handle,   LOGPIXELSX)/72; 
PointsY   :=  GetDeviceCaps(Printer.Canvas.Handle,   LOGPIXELSX)/72; 

    И теперь, если вы захотите что-нибудь нарисовать, можете использовать для 
измерений пункты (при этом результат будет верно масштабирован на любом устройстве!}., 
    ""   Приблизительно так будет выглядеть пример рисования прямоугольника 
двухдюймовой ширины и трехдюймовой высоты, расположенного на расстоянии одного 
дюйма от верхнего левого угла страницы (без учета границ принтера). 

    with  Printer.Canvas	do 
    Rectangle(Round	(PointsX   *   72), 
    Round	(PointsY   *   72), 
    Round	(PointsX   *   216), 
    Round	(PointsY   *   288)); 

    Единственный случай, когда этот пример не работает, если шрифты 
масштабируются на устройстве, на котором принтер имеет разное разрешение по 
разным направлениям, а устройство не сообщает об этом различии. Всегда полезно 
проверить, не является ли результат и надлежащее устройство Fax-принтером. 
Некоторые факсы имеют установку extra fine, когда разрешение по горизонтали и 
по вертикали различно.	~—— 

    Работа с цветом 

    Теперь вам может захотеться раскрасить прямоугольник одним из цветов, 
доступных в Windows. Если ваш принтер поддерживает цвет, вы, возможно, 
получите нечто близкое к тому, чего ожидали. Если устройство может управлять 
широким диапазоном цветов, получится точный цвет, если нет, то цвет будет размытым. 
    Размытые цвета получают на видеодисплее благодаря шаблонам из красных, 
зеленых и синих точек, слитых вместе для создания требуемой иллюзии. При 
низком разрешении шаблоны весьма заметны, при высоком разрешении — почти нет. 
    Цвета, напечатанные на бумаге, существенно отличаются от цветов, 
отображаемых на видеодисплее. На видеодисплее различные сочетания красного, 
зеленого и синего проецируются на люминофор, который освещает экран. Там, где 
нет цвета, точка будет черной (цвет выключенного дисплея), а при объединении 
красного, зеленого и синего точка дисплея кажется белой. 
    При печати на бумаге драйвер использует прямую противоположность RGB. 
Краска для печати разработана так, чтобы быть до некоторой степени прозрачной. 
В том месте, где нет цвета при печати, бумага выглядит белой, а там, где цвет 
полный, — черной (бумага не будет видна под краской). Цвета краски инверсны 
красному, зеленому и синему. 
    Для создания размытого цвета объединяются различные сочетания 
ярко-голубого, пурпурного и желтого цветов (CMY). Из-за того что краска не 
идеально чиста, при наложении голубого, пурпурного и желтого цветов для 
получения черного цвета на самом деле получается грязно-коричневый. Принтеры 
высококачественной цветной печати используют в дополнение краску черного цвета 
(CMYK), чтобы уменьшить расход цветных красок. В такой системе удаляются 
соответствующие сочетания голубого, пурпурного и желтого, а на их место 
подставляется черный цвет. Поскольку GDI ожидает, что цвета будут представлены 
в сочетаниях красного, зеленого и синего, драйвер принтера несет 
ответственность за преобразование в цветовую модель CMYK. Если для вашего 
приложения важен точный вывод цвета, можете приобрести лицензированную систему 
подбора цветов (такую, как Pantone® или FocalTone®), которая сможет точно 
подобрать цвета для высококачественного вывода на печать. 
    Если у вас монохромный принтер, цвет должен конвертироваться в размытый 
монохромный шаблон. Вот здесь-то возникает довольно много ошибок, поскольку не 
все драйверы одинаково управляют таким преобразованием. В идеале вы можете 
помочь драйверу, выполнив некоторые преобразования самостоятельно, применяя 
такую же формулу, которая используется в чернобелом телевидении для 
отображения цветного сигнала. 

    Глава 23. Печать в Delphi	539 

    Установите равные значения составляющих цвета RGB следующим образом. 

    Red	:=	Round(    (   0.3   *   red   )   +   (   0.59   *   green   )   +  
 (   0.11   *   blue   )    ); 
    Green	:=	Red; 
    Blue	:=	Red; 

    Эта формула конвертирует заданное значение красного, зеленого и синего в 
равную смесь красного, зеленого и синего цветов. Такая комбинация образует 
серый цвет, который и будет моделировать цвет. 
    Если принтер поддерживает палитры, можно избежать размытости, создав 
палитру и использовав предопределенные цвета. Но большинство принтеров ее не поддерживает. 
    Вы можете узнать, является ли принтер цветным и поддерживает ли он 
палитры, вызвав функцию GetDeviceCaps () следующим образом. 

    if   (  GetDeviceCaps(Printer.Canvas.Handle,   BITSPIXEL)   * 
    GetDeviceCaps   (Printer.Canvas.Handle,   BITSPIXEL))   >  1  then 
    ItsAColorPrinter   :=  true; if   (GetDeviceCaps(Printer.Canvas.Handle,   
RASTERCAPS)   and 
    RC_PALETTE  =  RC_PALETTE   then 
    ThePrinterSupportsPalette   :=  true; 

    Цвета карандаша не становятся размытыми, если только вы не используете 
стиль карандаша PSINSIDEFRAME и ширину карандаша, которая больше одного 
пикселя. Результатом использования стиля карандаша PSINSIDEFRAME являются 
формы, имеющие контуры, полностью закрашенные внутри. Если карандаш не 
размытый, то его цвет отображается ближайшим чистым цветом. Это может привести 
к неожиданным результатам. Для того чтобы получить ближайший чистый цвет, 
которым будет рисовать ваш карандаш, используйте функцию Windows 
GetNearestColor (). 
    Кисти, напротив, поддерживают размытость. Единственная неувязка связана с 
серыми и шаблонными кистями. Значения темно-серого цвета на бумаге выглядят 
совсем не так хорошо, как на экране, где для получения цвета необходима 
большая работа с использованием размытого шаблона. Шаблоны на самом деле очень 
маленькие, а человеческий глаз воспринимает белый цвет лучше, чем черный. 
Кисти шаблонов также демонстрируют этот эффект. Шаблон кисти определен как 
растровое изображение размером 8x8 пикселей. Этот шаблон может блестяще 
выглядеть на экране, но при использовании принтера с высоким разрешением 
превратится в сплошную черноту. Есть только один выход из такого положения — 
вручную рисовать шаблоны, все время помня о разрешении устройства. 

    Шрифты 

    Если вы собираетесь выводить текст, обратите внимание на следующее. Важно 
то, что принтер может не поддерживать той установки шрифтов, которую 
поддерживает дисплей. Вполне возможно, что представления шрифта на экране и на 
принтере будут существенно различаться. 
    Единственный способ обойти эту проблему — позволить пользователю выбирать 
только шрифты TrueType. Они одинаково хорошо выглядят как на принтере, так и 
на экране и, в отличие от шрифтов GDI, могут быть масштабированы до почти 
любого размера. Может случиться, что конечный пользователь купил пакет шрифтов 
Adobe ATM, и в этом случае он вряд ли обрадуется тому, что не сможет 
использовать их в вашем приложении. 
    В диалоге FontDialog Delphi можно разрешить выбор только шрифтов TrueType, 
включив опцию f dTrueType-Only. В хорошо разработанных приложениях обычно на 
панель инструментов помещается раскрывающийся список шрифтов и для принтера, и 
для экрана. Вы можете создать такой список, сравнивая попарно имеющиеся шрифты 
в свойстве Fonts объектов TScreen и TPrinter, помещая в список названия только 
совпадающих шрифтов. 
    Создается впечатление, что основная неувязка, относящаяся к печати и 
шрифтам в Delphi, связана с установкой свойства шрифта точки-на-дюйм, которое 
не всегда может быть инициализировано должным образом. Это было большой 
проблемой в Delphi 1.0 и возникало время от времени в Delphi 2.O. В Delphi 3.0 
эта проблема, кажется, совсем исчезла. Следуя хорош ему стилю 
программирования, необходимо добавлять следующую строку программы при каждом 
изменений шрифтов при печати. 

    Jointer.Canvas.Font.PixelsPerlnch   := GetDeviceCaps(Printer.Canvas.Handle, 
    LOGPIXELSY) ; 

    3io свойство используется для масштабирования шрифта до точного 
размера, когда вы применяете свойство size. Отметим, что свойство size 
устанавливает заданный размер шрифта в пунктах, а свойство height 
устанавливает высоту шрифта в пикселях. Значения свойств size и height могут 
быть отрицательными, так как GDI может выполнять измерения для шрифта 
различными способами. Если значение высоты отрицательно, измерение базируется 
на квадрате__ "ЕМ" (типографски правильный способ измерения шрифта), тогда как 
положительная высота указывает на наличие абсолютного масштабируемого размера 
шрифта. Если вы печатаете и предоставляете выбор размеров пользователю, 
представляйте числа в пунктах. 

    540	Часть VI. Сложный материал 

    Для отступа между линиями размер пункта следует увеличить на 20%. 
Традиционно в бланках используется отступ в 6 пунктов. Чтобы создать бланк для 
печатной машины, используйте шрифт в 10 пунктов и отступ между строками, 
равный 12 пунктам. 
    Выравнивать текст необходимо по базовой линии, а не по верху текстового 
блока (как установлено по умолчанию). Чтобы понять, что такое базовая линия, 
разберемся сначала с верхними и нижними выносными элементами букв. Сначала 
рассмотрим нижний выносной элемент. Если вы посмотрите на слово автор, то 
увидите, что первые четыре буквы внизу находятся на одной линии, тогда как 
пятая опускается ниже этой отметки. Нижним выносным элементом является часть 
буквы "р", расположенная ниже остальных букв. 
    Верхний выносной элемент аналогичен нижнему. Многие символы национальных 
алфавитов имеют знак ударения. Такие значки являются верхними выносными 
элементами. Если вы выберете выравнивание по умолчанию, то строки вашего 
текста не всегда будут выровнены правильно. Поэтому необходимо выравнивать 
текст по базовой линии. Для выравнивания используйте функцию Windows SetTextAtign'OT"" 
    Функция SetTextAlign () позволяет выбирать выравнивание как по 
горизонтали, так и по вертикали. Вам доступны некоторые установки, которые 
можно комбинировать. Общепринятыми установками по вертикали являются 
TA_BASELINE, ТА_ВОТТОМ и ТА__ТОР, а по горизонтали — TA_CENTER, TA_LEFT и 
TA_RIGHT. Две другие установки (TA_UPDATECP и TA_NOUPDATECP) определяют, как 
обновляется текущая позиция печати. Опция TA_NOUPDATECP устанавливается по 
умолчанию, при этом_ вы сами отвечаете за установку координат х, у, где вы 
хотели бы разместить текст. Перед изменением установок желательно объявить 
переменную для хранения прежних установок, чтобы можно было восстановить 
значения установок после того, как вы сделаете все необходимые установки для текста. 
    Следующий фрагмент программы является примером изменения выравнивания 
текста так, чтобы он был выровнен по левому краю и по базовой линии без 
обновления текущей позиции печати. 

    var  OldTextAlign   :   integer; begin 
    OldTextAlign   :=   SetTextAlign(Printer.Canvas.Handle, 
    TA_LEFT   or  TA_BASELINE   or  TA_NOUPDATECP); 
    {Тут что-то  делаем с  текстом} 
    SetTextAlign(Printer.Canvas.Handle,OldTextAlign); end; 


    Контекст устройства принтера 

    Интересно отметить, что DC (контекст устройства) принтера сильно 
отличается от DC дисплея. Хотя общепринятым является перенос растровых 
изображений между DC различных устройств, основанными на дисплее, почти 
невозможно проделать это же с бс принтеров. Во-первых, учтите, что, после того 
как образ распечатан на бумаге, просто невозможно передать его обратно другому 
Ьс. Вы не можете опять прочесть напечатанные точки с листа бумаги (без 
предварительного сканирования). Это же справедливо и для многократных 
переносов изображения BitBlts () с различными ROP-кодами. ROP-коды (коды 
растровых операций) позволяют объединять изображения для получения специальных 
эффектов, таких как прозрачность. Многие драйверы печати поддерживают только 
SRCCOPY (прямое копирование), WHITENESS (заполнение белым) и BLACKNESS 
(заполнение черным). Запомните: с изображением, перенесенным на принтер, 
ничего сделать нельзя. Кроме того, некоторые драйверы печати не поддерживают 
нескольких контекстов устройства одновременно. Если вы планируете использовать 
множественные Ос принтера, будьте осторожны! Блиттинг (от англ, 
bits-block-transfer— перенос растрового изображения поблочно. — Прим. перев.) 
растрового изображения с экрана на принтер— тоже плохая идея, так как в 
Windows блиттинг между устройствами не допускается. Подразумевается, что 
монохромные образы перенос ятся между устрой-сттвамиГно блиттинг между 
устройствами обычно работает только благодаря хорошо написанному драйверу печати. 
    Замечание напоследок. Часто DC принтера, который вы получаете, является в 
действительности DC метафайла, который впоследствии будет передаваться в буфер 
драйвера принтера. DC метафайлов предназначены только для "чтения (read only) 
— вы не можете повлиять ни на одну из функций, которые возвращают информацию. 
Лучше получить необходимую информацию (о ширине текста и т.п.) до того, как вы 
будете печатать, через информационный контекст (1с), используя функцию Windows 
Createlc ().	-•—...-. ._———.,„ 

    На что стоит обратить внимание 

    То, что хорошо на словах, не всегда срабатывает при печати в Windows. В 
следующих разделах рассматриваются "неногзмадщости", которые необходимо иметь 
в виду. 

    Сложные ломаные 

    Некоторые драйверы печати не поддерживают свыше 750 сегментов в ломаной. 
Хотя 32-разрядный GDI может вмешаться и смоделировать верный выходной 
результат, это не всегда работает так, как запланировано. Мораль: делайте свои 
ломаные настолько простыми, насколько это возможно. 

    Глава 23. Печать в Delphi	541 


    Сложные области 

    В 16-разрядной версии Windows сложные области вызывают у пользователя 
сильнейшую головную боль. Проблема, по-видимому, связана со способом хранения 
области в куче памяти (heap) GDI. Действительно сложные или большие области в 
соединении с высоким разрешением принтера могут вызвать переполнение кучи GDI. 
В Windows 95 размер этой кучи был увеличен, а в NT он практически не ограничен. 

    16- и 32-разрядные драйверы печати 

    Если вы создаете и 16-, и 32-разрядную версии вашей программы, будьте 
осторожны! Даже если вы не делаете этих версий, имейте в виду: Windows 95 
позволяет использовать эти драйверы печати посредством специальных "санок", 
когда 32-разрядные приложения могут использовать 16-разрядные драйверы печати, 
и наоборот. Прямая загрузка 32-разрядного драйвера печати с использованием 
функции Windows LoadLibrary () теперь недопустима. Для обратной совместимости 
16-разрядные приложения могут использовать функцию LoadLibrary () для загрузки 
32-разрядного драйвера печати. Поскольку драйвер^печати в Windows — это 
динамическая библиотека (DLL), основная работа происходит за сценой, неявно. 
Поэтому многие проблемы, связанные с печатью, которые существовали в 
16-разрядной Windows, до сих пор существуют и в 32-разрядной Windows. 

    Некорректная информация о принтере 

    Далеко не все драйверы печати работают так, как вы ожидаете. И 
производители драйверов принтеров не всегда в этом виноваты. Для создания 
драйвера принтера необходимы документация Microsoft и исходная программа. 
Существует много случаев, когда производитель вынужден догадываться, что 
делать в различных случаях, например при изменении ориентации страницы с 
портретной (portrait) на альбомную (landscape). Должен ли драйвер печати 
изменять эти величины, передавая их как горизонтальное и вертикальное 
разрешения принтера? Здесь может быть два варианта ответа. Первый — вы 
физически не можете изменить горизонтальное и вертикальное разрешения принтера 
(можно изменить только установку ориентации). И второй — приложения могут 
ожидать ряда изменений. Правда же состоит в том, что некоторые драйверы 
могут;, а некоторые не могут, изменять горизонтальное и вертикальное 
раз]эеШ?НИЯ^ееди вы изменили ориентацию страницы. Напоследок скажу, что я 
написал достаточное количество тестовых программ, чтобы проверить, что же "в 
действительности происходит во время выполнения программы, и в соответствии с 
этим приспосабливал вывод моих результатов. Приемлемый тест должен выглядеть 
примерно так. 
    Получаю горизонтальное и  вертикальное  разрешения принтера. Изменяю 
ориентацию страницы. 
    Снова получаю горизонтальное и  вертикальное разрешения принтера. Если 
горизонтальное и вертикальное разрешения изменились, использую то,   что было 
передано,   в противном случае изменяю мои переменные х и у разрешения и 
использую их. 

    Процедуры прерывания выполнения программы 

    Многие драйверы печати обманут ваши ожидания, если вы не установите 
процедуру прерывания выполнения программы. Несмотря на то что в Delphi объект 
TPrinter подключает для вас эту процедуру автоматически, если вы планируете 
управлять печатью, установите свою процедуру прерывания выполнения программы. 
Я рекомендую сделать это в самом начале. 
    | I 

    Объект TPrinter 

    Delphi инкапсулирует в объекте TPrinter систему печати Windows. Этот 
объект, подобно большинству VCL-компонентов, может значительно упростить 
создание переносимого приложения. 
    Когда-то печать в Windows была самой большой головной болью программиста. 
Чтобы вывести что-либо на печать, нужно было выполнить полный грамматический 
разбор файла WIN.INI для устройств и управлять каждой частью процесса выбора 
принтера и создания контекста устройства принтера. Даже с появлением общего 
диалогового окна принтера в Windows 3.1 распечатанный результат получали 
методом проб и ошибок. Объект TPrinter в Delphi разрешил все это и многое 
другое, обеспечив для приложения простые и мощные пути печати! 

    Использование объекта TPrinter 

    Для того чтобы воспользоваться объектом TPrinter, просто добавьте 
директиву Printers в раздел uses в начале любого программного модуля, из 
которого вы собираетесь печатать. Существует по меньшей мере три способа 
использования объекта Tprinter: использовать TPrinter для простого строкового 
вывода в стиле устройства построчной печати с процедурой Wr 
    iteLn (), использовать TPrinter вместе с методами печати VCL-компонентов и 
выбрать создание прямых GDI-команд Windows.

    542	Часть VI. Сложный материал 


    Использование Writel_n() 

    Самый легкий способ печати заключается в использовании процедуры WriteLn(). 
Она сохранилась со времен DOS, когда можно было выводить строки на 
телетайп, устройство построчной печати или в выходной файл. Разработчики 
Delphi милостиво улучшили WriteLn (), позволив ей работать в мире Windows и 
напрямую открывать порт принтера некрасиво, непрофессионально, а часто и 
просто невозможно. 
    Использовать процедуру WriteLn () для печати текстовых строк на принтере 
так же просто, как и в DOS. Может быть, принимая во внимание то, что вы можете 
изменить шрифты, это не потребует мучительного программного описания смены 
режима работы. 
    Используя процедуру WriteLn (} подобным образом, вы словно возвращаетесь в 
старые времена DOS: определяя переменную текстового файла, открывая этот файл, 
записывая какие-то строки, а затем закрывая этот текстовый файл. Однако теперь 
то, что вы пишете, не идет непосредственно на принтер. На самом деле, когда вы 
используете WriteLn () для построчноТ о""вывода на принтер, модуль Printers 
вызывает крмднду Windows_GDI TextOut (). Хорошее в этом способе то, что можно 
изменять шрифты и легко выводить графику, а плохое — нельзя отсылать свои 
собственные управляющие коды принтера (так называемые е§е.ар§-к,одьСи»Ой,КЩЙнщ 
Mejjgj это б^ет не очень-то^п?осто. Чтобы было понятий;чего это «тойт,поясню: 
пр и использовании процедуры WriteLn () с максимальной скоростью передавать 
данные непосредственно на принтер невозможно. 
    Ниже приведен пример использования процедуры WriteLn () для печати строки 
текста на принтере с переходом на следующую строку. 

    uses Printers; 
    var 
    MyFile:   TextFile; begin 
    AssignPrn(MyFile);	» 
    Rewrite(MyFile); 
    WriteLn(MyFile,    'Hello  World!'); 
    System.CloseFile(MyFile); end; 

    Если хотите, можете изменить шрифт и размер, которые будет использовать 
процедура WriteLn (), просто добавив несколько строк программы. 

    Printer.Canvas.Font.Name   :=   'Courier  New'; Printer.Canvas.Font.Style  
 :=   [fsBold]; Printer.Canvas.Font.PixelsPerlnch   := 
    GetDeviceCaps(Printer.Canvas.Handle,   LOGPIXELSY); WriteLn(MyFile,    
'Hello  World!'); 

    Каждый раз при вызове WriteLn () текст выводится текущим шрифтом, а 
текущая позиция печати переводится в первую колонку следующей строки. Если 
длина текста превышает ширину страницы, текст автоматически переносится на 
следующую строку. Когда достигается конец страницы, страница прогоняется и 
начинается следующая. Вы можете избежать перехода на следующуьс^строку, 
используя процедуру Write () вместо WriteLn (). Процедура Write () будет 
печататьГ'йрТжй, но не будет переносить текст на следующую строку, если только 
длина текста превышает ширину страницы. При использовании процедуры Write () 
вы можете даже изменять шрифть; отдельных слов. 
    При использовании Write () и WriteLn () объект TPrTnter отслеживает 
установленные координаты х, у и автоматически прогоняет страницу. 
    По этой причине~вы не должны непосредственно вызывать перевод страницы 
методом NewPage объекта TPrinter. Вы также не 
управляете_\}.еждус.трочным„иитервалом. Впрочем, существует способ обмануть 
TPrinter и получить доступ к свойствам х, у текущей строки —декларировать 
структуру PrnRec: 

    type 
    PrnRec = record 
    case Integer of 1:   ( 
    Cur : TPoint; Finish : TPoint; Height : Integer); 2:   ( 
    Tmp: array[1..(sizeof(integer)*8)] of Char); end; 


    Глава 23. Печать в Delphi	543 

    и преобразовать файловую переменную: 

    PrnRec(TTextRec(MyFile).UserData).Cur.x   :=  SomeXValue; 
PrnRec(TTextRec(MyFile).UserData).Cur.y   :=  SomeYValue; 

    Теперь вы можете просто вызвать метод NewPage объекта TPrinter (только не 
забудьте обнулить Cur.x и Cur.y). 
    Как вы, наверное, уже догадались, член Cur в структуре PrnRec используется 
для хранения текущей позиции печати на странице. Член Finish хранит размер 
доступной для печати области до конца страницы, а член Height сохраняет 
значение используемого строкового отступа. Если Height нулевой, то процедура 
Write () назначит члену Height величину высоты текущего шрифта. 
    Запомните, что в будущем такой "обман" не всегда может сработать, так что, 
возможно, лучше создать замену для Write () и WriteLn (), и сделать это, в 
общем-то, несложно. Просто используйте метод TextOut канвы принтера и 
отслеживайте х и у текущего положения все время, пока вы работаете с ним! 
Чтобы сделать это, вам необходимы, кроме всего прочего, такие методы объекта 
TPrinter, как BeginDoc, NewPage и EndDoc. 

    Использование канвы принтера 

    Для того чтобы управлять процессом печати в Delphi, вам необходимо самим 
распоряжаться установкой и отменой работы принтера, используя такие методы 
объекта TPrinter, как BeginDoc, NewPage и EndDoc. Если вы хотите оборвать 
страницу, просто вызовите метод NewPage. Метод EndDoc будет прогонять 
последнюю страницу. 
    Код выглядит следующим образом. 

    Printer.BeginDoc; 
    Printer.Canvas.TextOut(20,20, 'A line of text - page 1'); 
    Printer.NewPage; 
    Printer.Canvas.TextOut(20,20, 'A line of text - page 2'); 
    Printer.EndDoc; 


    Специально для начинающих: изменяем принтеры и установки 

    Вы можете запросто менять выбранный принтер (но не в момент печати). 
Перечень доступных принтеров можно получить из списка, заключенного в свойстве 
Printer. Printers, а индекс текущего выбранного принтера— в свойстве Printer. 
Printerlndex. Вы можете назначить текущим любой доступный принтер из списка, 
изменяя свойство Printer. Printerlndex, и сменить текущий принтер на 
установленный по умолчанию в Windows, присвоив свойству Printer.Printerlndex значение-1. 
    Ниже приведен пример заполнения компонента ListBox доступными принтерами и 
выделения в ListBox текущего принтера. 

    ListBox.Item	:=  Printer.Printers; ListBox.Itemlndex   :=  Printer.Printerlndex; 

    Кроме того, вы можете использовать компоненты TPrinterDialog и 
TPrinterSetupDialog для изменения и принтера, и установок текущего выбранного принтера: 

    PrintDialogl.Execute; 

    или PrinterSetupDialogl.Execute; 

    Выбирайте, что хотите. Если вам необходимо распечатать несколько страниц, 
то лучше использовать TPrinterDialog; тогда как для изменений лотков подачи 
бумаги принтера и тому подобного удобнее воспользоваться TPrinterSetupDialog. 
Оба диалоговых окна принтера позволяют пользователю выбрать свойства принтера 
непосредственно из этих окон. 
    Объект TPrinter имеет два других метода для опроса и установки принтера— 
GetPrinter и SetPrinter. Оба этих метода не являются ни рекомендованными, ни 
хорошо документированными. Если уж вы действительно захотите вернуться к 
аппаратному обеспечению и начать выбирать устройства печати, файлы драйверов и 
установки портов, при

    544	Часть VI. Сложный материал 

    обретите хорошую книгу по программированию в Windows на низком уровне и 
решите задачу управления процессом печати от начала и до конца. Еще одной 
причиной использования этих методов является доступ к структуре принтера 
DevMode для изменения таких установок принтера, как лотки для бумаги, 
количество копий и пр. В следующих разделах будет представлен более приятный 
способ управления этими установками, а сейчас — пример использования метода 

    GetPrinter. 
    var 
    Device   :   array[0...cchDeviceName-1]   of Char; Driver   :   
array[0..(MAX_PATH-1)]   of  Char; Port        :   array[0..32]   of  Char; 
hDMode   :   THandle; begin 
    Printer.GetPrinter(Device, Driver, Port, hDMode); 
    Labell.Caption   :=   Device; Label2.Caption   :=  Driver; Labels.Caption  
 :=   Portend; 

    Этот фрагмент начинается с объявления переменных для хранения имен 
устройства, драйвера, порта и ссылки на структуру принтера DevMode. Затем 
вызывается метод GetPrinter () и вы устанавливаете метки так, чтобы был виден 
результат работы. В общем случае для драйвера будет возвращена пустая строка. 
    Теперь, когда jibi узнали имя устройства, имя драйвера и имя порта и 
хотите использовать эту информацию, вызовите MeTOO^eTFrinter следующим образом. 

    Printer.SetPrinter('HPLJ-IID', 'HPPCL', 'FILE:', 0) ; 

    Заметим, что ссылка на структуру DevMode не была использована. Если у вас 
есть ссыдка_на правильную структуру DevMode, можете использовать ее в качестве 
четвертого параметра. Кроме того, можете добавить новый принтер, который не 
был инсталлирован в Windows, в список принтеров объекта TPrinter, заполняя 
параметры соответствующими данными, как показано ниже. 

    Printer.SetPrinter('SOMEDRV', 
    'C:\SOMEDIR\SOMEDRV, 
    'FILE:', 
    0); 

    Это не совсем хорошо, так как для правильной работы большинство драйверов 
принтеров должно быть инсталлировано в Windows. Однако в некоторых случаях эта 
идея может быть полезной. Она также может применяться для смены порта, который 
использует данный драйвер. 
    Для программного управления установками принтера нужно поработать со 
структурой принтера DevMode. Структура DevMode текущего выбранного принтера 
может быть возвращена методом GetPrinter объекта TPrinter. Получив ссылку на 
структуру DevMode, можете вызвать функцию Windows Global Lock (), которая 
возвращает указатель на эту структуру, а затем внести изменения в 
аппаратно-независимую часть структуры. 
    Структура DevMode является уникальной для каждого принтера. Первый раздел 
структуры одинаков для всех принтеров, а второй обязательно определяется 
используемым драйвером. Вы можете опросить члена структуры DevMode. dmSize, 
чтобы получить размер общей части структуры драйвера DevMode, а для выяснения 
размера частной части структуры драйвера DevMode опросите члена структуры 
DevMode. dmDriverExtra. Если вы хотите скопировать" и сохранить полностью 
структуру DevMode, выделите и скопируйте количество байтов, равное (DevMode . 
dmSize + DevMode.dmDriverExtra). 
    Основная структура DevMode определяется как имеющая следующие общие члены. 

    dmDeviceName	{Имя устройства} 
    dmSpecVersion	{Минимальная версия Windows, требуемая драйвером} 
    dmDriverVersion	{Номер версии драйвера} 
    dmSize	{Размер общей части структуры DevMode} 
    dmDriverExtra	{Размер особенной части структуры DevMode} 
    dmFields	{Инициализируемые флагами части структуры DevMode} 


    Глава 23. Печать в Delphi	545 
    18 Использование Delphi 3. Специальное издание 


    dmOrientation	{Ориентация страницы} 
    dmPaperSize	{Предопределенный размер страницы или нуль, если размер 
устанавливается пользовате	лем} 
    dmPaperLenght	{Используется только при установке размера страницы пользователем} 
    dmPaperWidth	{Используется только при установке размера страницы пользователем} 
    dmS с а 1 е	{Масштаб печати} 
    dmCopies	{Необходимое количество копий} 
    dmDefaultSource	{Должен быть нуль} 
    dmPrintQuality	{Предопределенное разрешение или разрешение принтера по х} 
    dmColor	{Если требуется цветная печать} 
    dmDuplex	{Если требуется двусторонняя печать} 
    dmYResolution	{Нуль или разрешение принтера по у, если dmPrintQuality>0} 

    16- и 32-разрядная версии Windows определяют дополнительные флаги для 
общей части структуры DevMode, которые могут и не устанавливаться для всех 
принтеров. Вообще-то, лучше не применять эти установки, кроме случая, когда вы 
точно знаете принтер, для которого используется поддержка этих дополнительных установок. 
    Вы можете сменить члены структуры DevMode, установив значение для 
соответствующего члена, а затем установив соответствующий бит члена структуры 
dmFields, чтобы дать возможность драйверу узнать, какой член инициализирован. 
Сделать это можно следующим образом. 

    DevMode.dmPaperSize   :=   DMPAPER_LEGAL; 
    DevMode.dmFields	:=  DevMode.dmFields  or  DM_PAPERSIZE; 

    Ниже приведен завершенный фрагмент изменения структуры принтера DevMode. 

    var 
    Device   :   array[0...cchDeviceName-1]   of Char; Driver   :   
array[0..(MAX_PATH-1)]   of  Char; Port   :   array[0..32]   of  Char; hDMode  
 :   THandle; pDMode   :   PDevMode; begin 
    Printer.Printerlndex^x:=  Printer.PrinterIndex; Printer.GetPrinter(Device, 
  Driver,    Port,   hDMode); if hDMode  <>  0  then begin 
    pDMode   :=  GlobalLock(hDMode); if pDMode  <>  nil  then begin 
pDModeA.dmCopies   :=  5; 
    pDMode'4 .dmFields   :=  pDMode'4. dmFields   or   DM_COPIES; 
Globa_lUnloc_k (hDMode} ; 
    "PrTnter.PrinterIndex:=  Printer.PrinterIndex; end; end; 
    Printer.BeginDoc ; 
    Printer.Canvas.TextOut(100,   100,    'Test   1'); Printer.EndDoc; end; 

    Сначала объявляются переменные для хранения имен устройства, драйвера, 
порта и ссылки и указателя на структуру принтера DevMode. Первая строка кода 
повторно инициализирует принтер. После вызова метода GetPrinter проверяется, 
существует ли зарегистрированная ссылка на структуру DevMode, и, если 
существует, она блокируется и вы делаете изменения. Затем вы снимаете 
блокировку с ссылки и переустанавливаете принтер. Отметим, что эти изменения 
должны быть сделаны до начала работы принтера. Заметим также, что не все 
изменения будут поддерживаться на всех принтерах. 
    Все это, конечно, свежо, но вам может понадобиться возможность изменять 
установки, во~время рабагьцщзинтера. У вас есть два варианта выбора— разбить 
процесс на несколько этапов или Полностью исправить объект TPrinter. Второй 
вариант предпочтительнее, так как он может предоставить достаточно 
возможностей по управлению и не приведет к перегрузке Print Manager различными 
работами без особой необходимости. Этот метод 
работает на драйверах печати, созданных для версий Windows 3.1 и выше. Он не 
должен вызывать проблем на драйверах, построенных для предыдущих версий, 
потому что эти драйверы будут просто возвращать ошибку Windows GDI в том 
случае, если драйвер не поддерживает изменений структуры DevMode во время 
работы. "Заплата" (измененный так, как вам нужно, фрагмент 
программы) будет проверять, возвращена ли эта ошибка, и, если возвращена, вы 
будете разбивать сеанс на печатание отдельных страниц, как на отдельные сеансы. 

    546	Часть VI. Сложный материал 

    Чтобы сделать исправление в объекте TPrinter, вы, во-первых, должны иметь 
копию исходного текста модулей VCL Delphi. Если вы переносите программу между 
16- и 32-разрядными версиями, 16-разрядная версия исходных текстов VCL 
поставляется с RAD-пакетом компании Borland. В Delphi 2.0 исходные тексты VCL 
включены в поставку Delphi 2.0 Developer и в Delphi 2.0 Client/Server Suite. 
    Изменения сделать довольно легко. Чего действительно хотелось бы, так это 
ввести новое событие, которое возникает сразу перед созданием каждой новой 
страницы, и изменить установки принтера с помощью функции Windows GDI ResetDc 
(). O^HKima_ResetDc () должна вызываться между двумя страницами, a Delphi не 
имеет событий, которые это делают. Хотя в обычном случае было бы несложно 
создать новый компонент", объект ТРг1п-Еёг"относйтся"к спецйфичЕШш случаям. 
Во-первых, вы хотите иметь один раз написанное изменение, которое работало бы 
во всех версиях Delphi и со всеми уже использующимися компонентами объекта 
TPrinter. И еще одна проблема: TPrinter способен сохранять загруженный принтер 
и готов к работе все время, но вы не хотите, чтобы одновременно существовало 
несколько копий работающих принтеров. На самом деле многие драйверы просто не 
поддерживают множественных копий. И последней проблемой является недопущение 
возникновения ошибок в других компонентах VCL при изменении интерфейсной части 
об ъекта TPrinter. 
    Добавлять событие к объекту TPrinter, не изменяя интерфейсной части 
объекта, декларации класса или подкласса, невозможно! 
    Способ преодоления этого препятствия пришел ко мне однажды в пятницу, 
когда я сидел с удочкой на берегу. Создать модуль с однофункциональным 
указателем, который должен быть инициализирован в нуль (nil), добавить 
оператор uses в раздел implementation объекта TPrinter для указания на новую 
процедуру, чтобы указатель функции мог быть вызван из объекта Tprinter, и 
добавить оператор uses в выполняемую часть процедуры приложения, чтобы вы 
могли назначить свое событие. 
    Для этого, во-первых, создайте новый каталог в каталоге Delphi \SOURCE\ и 
назовите его PATCHED и, во-вторых, скопируйте PRINTERS . PAS из каталога 
Delphi SOURCEWCL в каталог SOURCE\PATCHED. 
    Добавьте новый модуль в каталог Delphi SOURCE\PATCHED, назвав его 
PRNPAGE.PAS. Исходный текст этого модуля должен выглядеть примерно так. 

    unit PrnPage; interface 
    {$IFDEF Win32} 
    uses Windows; 
    {$ELSE} 
    uses WinTypes; 
    const MAX_PATH = 144; 
    {$EDNIF} 
    procedure JobStart; 
    var PageProc : procedure(DC : HOC; DeviceMode : THandle); 
    implementation 
    uses Printers; 
    procedure JobStart; var 
    Device : array[0..cchDeviceName-1] of Char; 
    Driver : array[0..(MAX_PATH-1)] of Char; 
    Port : array[0..32] of Char; 
    hDMode : THandle; begin 
    if SPageProc = nil then exit; 
    Printer.Printerlndex := Printer.Printerlndex; 
    Printer.GetPrinter(Device, Driver, Port, hDMode); 
    if hDMode <> 0 then begin PageProc(0, hDMode); Printer.Printerlndex := Printer.Printerlndex; 
    end; end; 
    begin 
    @PageProc := nil; end. 


    Глава 23. Печать в Delphi	547 

    Этот простой модуль содержит переменную процедуры, которая будет 
использоваться для привязки события "страница приложения" к объекту TPrinter. 
Переменная процедуры PageProc инициализируется в nil, когда процедура 
загружается впервые, так что, если вы даже решите не назначать события, в 
программе не возникнет ошибки. 
    Этот модуль содержит также процедуру JobStart (). Ее можно вызывать в 
начале каждого сеанса печати, когда бы вы ни захотели изменить структуру 
принтера DevMode до начала работы с документом. JobStart () позволяет 
выполнять изменения для принтеров, которые не поддерживают изменений структуры 
DevMode между страницами. 
    Для того чтобы использовать библиотеку процедур PrnPage, нужноТнести 
изменения в объект TPrinter. Откройте модуль PRINTERS. PAS в каталоге Delphi 
SOURCE\PATCHED и добавьте PrnPage к оператору uses в выполняемой части файла 
PRINTERS . PAS следующим образом. 

    implementation 
    uses  Consts,   PrnPage; 

    Найдите два места в PRINTERS. PAS, которые имеют такую строку: 

    StartPage(DC); 

    Вставьте следующий фрагмент кода непосредственно перед строками, которые 
начинаются со StartPage (Dc) в файле PRINTERS.PAS. 

    if  @PrnPage.PageProc  <>  nil   then 
    PrnPage.PageProc(DC,   DeviceMode); 

    Эти два места, куда вы вставляете данный фрагмент, включены в методы 
BeginDoc и NewPage. Фрагмент, который вы добавили, позволит объекту TPrinter 
"опрашивать" ваше приложение прямо перед началом работы с каждой страницей, 
представляя возможность приложению изменять структуру принтера DevMode. 
    И наконец, добавьте полный путь каталога Delphi SOURCE\PATCHED для_пути 
поиска библиотек, выбрав его из меню Delphi Tools^Options. Вернитесь к 
странице Library и добавьте следующую строку в началбТгуТй поиска библиотеки 
Library Path (замените на правильный путь для вашего каталога Delphi): 

    С:\Borland\Delphi2.0\Source\Patched; 

    Затем щелкните на кнопке ОК и перезапустите Delphi. 
    Убедитесь, что вы не забыли выбрать команду build all, когда будете 
запускать повторную компиляцию проекта. Теперь, когда бы вы ни захотели 
печатать, добавьте модуль PrnPage прямо после модуля Printers в строку uses 
выполняемой части модуля, из которого вы печатаете в своем приложении. 

    implementation 
    {$IFDEF Win32} 
    uses  Printers,   PrnPage; {$ELSE} 
    uses  Print,   Printers,   PrnPage; {$ENDIF} 

    Добавьте в текст модуля следующую процедуру, модифицирующую установки, 
которые вы хотели бы изменить. 

    procedure ThePageProc(DC : HOC; 
    hDMode : THandle) ; var 
    pDMode : pDevMode; begin 
    if hDMode = 0 then exit; 
    pDMode := GlobalLock(hDMode); 
    if pDMode = nil then exit; 
    pDMode'^ . dmCopies    := 3; 
    pDMode^dmPaperSize := DMPAPER_LEGAL; 
    pDModeA.dmFields    := pDModeA . dmFields or DM__COPIES; 
    pDMode".drnFields    := pDModeA.dmFields or DM_PAPERSIZE; 
    if DC <> 0 then 
    {$IFDEF Win32} 
    ResetDc(DC, pDMode"); 
    {$ELSE} 


    548	Часть VI. Сложный материал 


    ResetDc(DC,   pDMode); {$ENDIF} 
    GlobalUnlock(hDMode) ; end; 

    Когда вы захотите печатать и изменять установки между страницами, просто 
назначьте переменную PageProc в библиотеке PrnPage для указания на вашу 
процедуру, которая и будет выполнять изменения. Вот такой фокус нужен для 
этого назначения. 

    @PrnPage.PageProc   :=  @ThePageProc; 
    Печатайте, зная, что объект TPrinter будет вызывать вашу процедуру 
PageProc каждый раз, когда начинается новая печать или новая страница. 
    Ниже приведен простенький пример. 
    @PrnPage.PageProc   :=  @ThePageProc; 
    JobStart; 
    Printer.BeginDoc; 
    Windows.TextOut(Printer.Canvas.Handle, 
    100, 100, 
    'Test1, 
    4); 
    Printer.EndDoc; @PrnPage.PageProc := nil; 

    Обратите внимание, что после окончания печати необходимо сбросить 
переменную PageProc библиотеки PrnPage в nil. 
    Ну как, вы готовы к выполнению более сложного примера? В нем показано, как 
использовать новую систему печати для печати нескольких страниц и изменять 
установки устройства между требуемыми страницами даже в том случае, если 
устройство не поддерживает изменений установок между страницами! 

    implementation 
    {$IFDEF Win32} 
    uses Printers, PrnPage; 
    {$ELSE} 
    uses Print, Printers, PrnPage; 
    type ShortString = string; 
    {$ENDIF} 
    var 
    PrintSeparatePages : bool; CurrentPage : integer; LastPage : integer; 
    procedure ThePageProc(DC : HOC; 
    hDMode : THandle); {$IFDEF Win32} 
    far; 
    {$ENDIF} var 
    pDMode : PDevMode; begin 
    if hDMode = 0 then exit; pDMode := GlobalLock(hDMode); if pDMode = nil 
then exit; 
    if odd(CurrentPage) then begin 
    pDModeЛ.dmCopies    := 3; 
    pDMode^.dmPaperSize := DMPAPER_LEGAL; end else begin 
    pDMode^.dmCopies    := 2; 
    pDMode^.dmPaperSize := DMPAPER_LETTER; end; 
    pDModeЛ.dmFields := pDMode".dmFields or DM_COPIES; pDMode".dmFields := 
pDModeл.dmFieIds or DM_PAPERSIZE; 


    Глава 23. Печать в Delphi	549 


    if DC <> 0 then 
    {$IFDEF Win32} 
    if ResetDcfDC, pDMode'4) о 0 then 
    {$ELSE} 
    if ResetDc(DC, pDMode) <> 0 then 
    {$ENDIF} 
    PrintSeparatePages := false; GlobalUnlock(hDMode); end; 
    procedure TForml.ButtonlClick(Sender : TObject); var 
    s : ShortString; begin 
    PrinterSeparatePages := True; CurrentPage := 1; LastPage := 3; 
    SPrnPage.PageProc := @ThePageProc; while CurrentPage <= LastPage do begin 
if PrintSeparatePages then begin JobStart; Printer.BeginDoc; end; 
    Str(CurrentPage, s); s := 'Printing Page ' + s; s[Length(s) + I] := #0; 
TextOut(Printer.Canvas.Handle, 100, 100, @s[l], Length (s)); Inc(CurrentPage}; 
    if CurrentPage <= LastPage then begin if PrintSeparatePages then 
    Printer.EndDoc; else 
    Printer.NewPage; end; end; 
    Printer.EndDoc; SPrnPage.PageProc := nil; end ; 

    Этот фрагмент кода позволяет напечатать две копии каждой нечетной страницы 
на бумаге стандартного формата и две копии каждой четной страницы на бумаге 
формата Letter. Программа, приведенная в примере, к тому же управляет 
•изменениями установок между страницами на принтере, который не поддерживает 
переустановку принтера, разбивая при необходимости процесс на отдельные 
задания печати. Имейте в виду, что некоторые компоненты VCL могут не ожидать 
изменений установок устройства "на ходу". Хорошенько протестируйте программу и 
подготовьте использование вызовов Windows GDI, где это необходимо. И наконец, 
убедитесь, что вы объектам канвы (карандашам, кистям и т.п.) вернули прежние 
установки, которые были в начале печати (до изменения установок устройства). 

    Установки принтера: функции GetDevCaps() и DeviceCapabilitiesO 

    Вы уже использовали функцию Windows GetDeviceCaps () для получения 
информации о принтере. Различные доступные запросы, передаваемые функции 
GetDeviceCaps () в качестве параметра, приведены в разделе Win32 справочной 
системы Delphi. Если вы переносите программу между 16- и 32-разрядной версиями 
Windows, имейте в виду, что не все 32-разрядные запросы доступны в 
16-разрядной версии Windows. Кроме того, помните, что некоторые запросы не 
всегда возвращают правильную информацию. Из того, что возвращенное запросом 
количество карандашей равно нулю, не следует, что устройство не использует 
карандашей; а из того, что устройство описано, как имеющее нулевое число 
шрифтов или даже -1, не следует, что вы не можете вывести текст. Некоторые 
справочники даже внушают следующее: если описанная технология не является 
RASTERPRINTER, то устройство не может создавать растровых изображений, и если 
RasterCaps описывает устройство, как, например, не поддерживающее методы 
StretchBlt и StretchDib или 

    550	Часть VI. Сложный материал 

    создание полилиний, то вы не можете использовать эти функции. На самом 
деле это неправда, так как даже векторный плоттер может рисовать растровые 
изображения, проводя много маленьких линий, т.е., если устройство не способно 
растягивать растровые изображения, использовать аппаратно-независимые 
растровые изображения или создавать многоугольники (GDH iy^ieT вмешиваться и 
мрдедироаать треб^емую_фущцшю). 
    Есть одна очень важная особенность, которая будет определять способность к 
графической печати. Если ваш принтер вообще не поддерживает графику (как, 
например, устройство построчной печати TTY), то использовать графику не 
удастся. Многие справочники содержат неправильную информацию о применении 
RC_BITBLT для проверки поддержки графики. Проверить, может ли принтер 
обеспечить графический вывод, можно, сравнив результаты выполнения запросов 
RASTERCAPS иRC_NONE(0x0000). 

    if   (GetDeviceCaps(Printer.Canvas.Handle,   RASTERCAPS)   and RC_NONE)    
=   RC_NONE   then This_Is_A_Text_Only_Printer   :=  true; 

    Другим великолепным путем получения информации от устройства печати 
является опрос функции принтера De-viceCapabilities. Хотя в Delphi 1.0 и 3 
функция декларирована правильно, в Delphi 2.0 декларация функции неверна из-за 
путаницы в документации Microsoft MSDN Knowledgebase и справочном файле 
Microsoft Win32. В связи с тем, что в документации Microsoft допущена ошибка, 
рассмотрим этот вопрос в нашей книге. 
    Во-первых, в документации утверждается, что можно вызвать функцию 
DeviceCapabilitiesEx (). Это неверно. Вызовем функцию DeviceCapabilities О . 
Если вы до сих пор используете Delphi 2.0, для правильной работы повторно 
декларируйте три функции DeviceCapabilities (). Если у вас есть исходный текст 
VCL, то лучший способ сделать такую коррекцию— исправить файл WINDOWS. PAS. Я 
предпочитаю метод, подобный, описанному в предыдущем разделе, в котором был 
исправлен объект TPrinter. Скопируйте файл WINDOWS. PAS из каталога Delphi 
\50trkCETRTTL\WIN\ в каталог SOURCE\PATCHED\ и сделайте ряд изменений.	,—•
    ШЖйтёЖёдУю^йё^писанш! функций в разделе implementation файла WINDOWS . 
PAS, находящегося в каталоге SOURCE\PATCHED\. 

    function  DeviceCapabilitiesA;	external  gdi32  name	'DeviceCapabilitiesA'; 
    function  DeviceCapabilitiesW;	external   gdi32  name	'DeviceCapabilitiesW'; 
    function  DeviceCapabilities;	external   gdi32   name	'DeviceCapabilitiesA'; 
    Теперь измените их, чтобы они выглядели следующим образом. 
    function	DeviceCapabilitiesA;   external   'winspool.drv'   name 
    4>	'DeviceCapabilitiesA'; 
    function	DeviceCapabilitiesW;   external   'winspool.drv'   name 
    4>	'DeviceCapabilitiesW'; 
    function	DeviceCapabilities;   external   'winspool.drv'   name 
    "Ъ	'DeviceCapabilitiesA'; 


    Использование DeviceCapabilities() 

    Для получения информации о количестве копий, которые данное устройство 
может сделать для одного описания страницы, используйте функцию 
DeviceCapabilities (). 

    var 
    Device : array! 0..cchDeviceName — 1 ] of Char; Driver : array[ 
0..(MAX_PATH - 1) ] of Char; Port : array[ 0..32 ] of Char; hDMode : THandle; 
pDMode : PDevMode; NumOfCopiesPossible : integer; begin 
    Printer.GetPrinter(Device, Driver, Port, hDMode); if hDMode <> 0 then begin 
    pDMode := GlobalLock(hDMode); if pDMode <> nil then begin 
NumOfCopiesPossible := 
    DeviceCapabilities(Device, Port, DC_COPIES, nil, pDMode); 
GlobalUnlock(hDMode); end; end; end; 

    Глава 23. Печать в Delphi	551 

    Существует много различных установок, о которых вы можете узнать у 
принтера, используя функцию DeviceCapa-bilities(). По возможности всегда 
следует проверять способности устройства, прежде чем требовать изменений в 
устройстве. Вы можете получить список поддерживаемых запросов, просмотрев 
раздел Win32 справочной системы Delphi. Здесь вы обнаружите запросы обо всем: 
от поддерживаемого размера бумаги до названия каждого лотка для бумаги, 
который поддерживается принтером. Часто приходится вызывать функцию 
DeviceCapabilities () дважды для заданного запроса. Первый раз — для получения 
размера структуры, необходимой для запроса, а второй — для получения 
собственно данных. Рассмотрим в качестве примера запрос DC_PAPERNAMES. При 
первом вызове DeviceCapabilities () вы передадите нуль в качестве четвертого 
параметра, и DeviceCapabilities () возвратит вам количество стандартных 
форматов бумаги, поддерживаемых принтером. Затем вы выделите достаточно 
большую структуру для хранения всех стандартных форматов бумаги и вновь 
вызовете DeviceCapabilities (), только на этот раз вы передадите в качестве 
четвертого параметра указатель на выделенную вами область памяти. 

    type TPaperName  =  array[0..63]   of  char; 
    type  TPaperNames  = array[0..0]   of TPaperName; 
    type   PPaperName  =   TPaperNames; 
    procedure TForml .ButtonlClick (Sender : Tobject) ; 
    var 
    Device	array [0 .. cchDeviceName -1] of Char; 
    Driver	array[0. . (MAX PATH-1}] of Char; 
    Port	array[0..32] of Char; 
    hDMode	THandle; 
    pDMode	PDevMode; 
    pPapers	PPaperName; 
    NumPapers : integer; 
    i   :   integer; begin 
    Printer.GetPrinter(Device,   Driver,   Port,   hDMode); if  hDMode  <>   0 
  then begin 
    pDMode   :=  GlobalLock(hDMode); if pDMode  <>  nil  then begin NumPapers  
 := 
    DeviceCapabilities(Device,   Port,   DC_PAPERNAMES, 
    nil,   pDMode); if NumPapers  >  0  then begin 
    GetMem(pPapers,   NumPapers   *   sizeof (TPaperName}); 
DeviceCapabilities(Device,   Port,   DC_PAPERNAMES, 
    PCHAR(pPapers),   pDMode); for   i   :=   0   to   (NumPapers  —  1}   do Memol.Lines.Add(рРарегзЛ[i]); 
    FreeMem(pPapers,   NumPapers   *   sizeof(TPaperName)); end; 
    GlobalUnlock(hDMode); end; end; end; 

    В этом примере декларирован новый тип TPaperName, в котором хранится 
символьная строка, заканчивающаяся нулевым байтом, из 63 символов и места для 
нулевого окончания. Затем декларирован новый тип TPaperNames, который 
представляет собой массив из элементов TPaperName. Этот массив TPaperNames 
декларирован как способный хранить только один элемент PaperName. Это сделано 
для получения доступа к дополнительным элементам динамически размещенного 
массива во время выполнения программы. И наконец, декларирован тип указатель 
на массив элементов Paper-Name с именем PPaperNames. 
    Когда вы впервые вызываете функцию DeviceCapabilities О , вы передаете 
четвертый параметр как нулевой (nil), так что функция будет возвращать 
количество стандартов бумаги. Теперь вы знаете, сколько памяти нужно выделить. 
На следующем этапе вы выделяете память для хранения названий бумаги и вновь 
вызываете функцию DeviceCapabilities О, которая возвращает стандарты бумаги 
при передаче ей в качестве четвертого параметра указателя на вашу область 
памяти, преобразованного в тип PChar (чтобы осчастливить компилятор). 
    Следующий фрагмент программы написан специально для того, чтобы обмануть 
компилятор при оценке элементов массива для заполнения вашего компонента memo 
перечнем стандартов бумаги. При декларации массива вы изначально задали ему 
размер, равный одному элементу, но выделили объем памяти, достаточный для 
нескольких элементов. Поскольку вы получаете доступ к индексу массива через 
переменную, компилятор не будет реагировать на то, что индекс 

    552	Часть VI. Сложный материал 

    вышел за пределы массива. Дл^ того чтобы этот фокус сработал во время 
выполнения программы, должна быть отключена проверка попадания индекса в 
указанные границы и вы не должны переходить границы массива, так как при этом 
вы ТШбо получите путаницу в других переменных, либо вызовете сбой защиты. 
Последним шагом вы освобождаете и очищаете выделеннуювами область памяти. 
    И последнее замечание о переносимости: если вы переносили программу из 
16-разрядной версии Windows и использовали функцию LoadLibrary, чтобы получить 
ссылку на драйвер принтера, не ожидайте, что это сработает и в 32-разрядной 
версии Windows. Вместо этого используйте предыдущий фрагмент программы, ибо 
он-то точно допускает переносимость. Вы можете больше не использовать функцию 
LoadLibrary, так как Windows 95 позволяет 32-разрядным приложениям выполнять 
печать с использованием 16-разрядных драйверов печати, а вы не можете напрямую 
загружать 16-разрядную DLL из 32-разрядного приложения. Теперь Windows делает 
это автоматически. 

    Управляющие коды принтера 

    С первых дней существования Windows управляющие коды (escapes) принтера 
использовались для управления принтером в том случае, когда нужно было вызвать 
драйвер на нижнем уровне. Обычно такие вызовы заключались в запуске работы 
печати, установке процедуры прерывания, протяжке страницы и окончании работы. 
Со стороны приложения все^п^авляющие коды заменялись вызовами функций GDI, 
которые являлись оболочками старых управляющих кодов. Вы и теперь можете 
вызвать функции принтера, которые инициализируют управляющие коды; и если вы 
действи-'теТГьно хотите полностью управлять работой вашего принтера, вы так и сделаете. 
    За исключением минимального набора управляющих кодов, который должен 
поддерживать драйвер принтера (SETABORTPROC, ЗТАКТООсГЫЕМГКАМЁ7ЕЫОООС и 
ABORTDOC), многие драйверы печати поддерживают и дополнительные управляющие 
коды. Одни из этих кодов считаются стандартными и доступными для большинства 
драйверов печати, а другие доступны только для специализированных принтеров. 
Все драйверы должны поддерживать управляющий код QUERYESCSUPPORT, который 
используется, чтобы запросить, поддерживается ли заданный управляющий код. 
    Когда вы вызываете функцию Windows GDI Escape (), GDI переводит этот вызов 
в вызов внутренней функции драйвера печати Control (), обеспечивающей 
выполнение всех необходимых манипуляций. Время от времени GDI вмешивается и 
"крадет" вызов, не пропуская его через драйвер. Иногда GDI возвращает 
правильную информацию, а иногда неправильную. Ярким примером такого поведения 
является запрос по управляющему коду NEWFRAME. В 32-разрядной вер-auTWindows 
GDI возвращает "Этот управляющий код не поддерживается", хотя на самом деле 
это не так. 
    Необыкновенный трюк, который демонстрирует GDI, — это не просто вызов 
функции. Как входные, так и выходные данные также должны быть обработаны и 
переданы. Ниже приведен пример вызова функции Escape для запроса, поддерживает 
ли принтер получение отступа при печати (поле страницы). В том случае, если 
этот управляющий код будет выполнен успешно, вы получите отступ при печати. 

    var 
    Testlnt    : integer; PageMargin : TPOINT; begin	"""  '  """ PageMargin.x 
,:= 0; PageMargin.у := 0; Testlnt := GETPRINTINGOFFSET; if 
Escape(Printer.Canvas.Handle, QUERYESCSUPPORT, sizeof(testin), OTestlnt, nil) 
= 0 then 
    ShowMessage('Escape is not implemented') else begin 
    if Escape(Printer.Canvas.Handle, GETPRINTINGOFFSET, 0, 
    nil, 
    @PageMargin) <= 0 then begin PageMargin.x := 0; PageMargin.у : = 0; end; 
end; end; 


    Глава 23. Печать в Delphi	553 

    Замечание напоследок: 32-разрядная версия Windows определяет новую функцию 
ExtEscape (), которая позволяет обходить многие вмешательства GDI, когда 
вызывается Escape. Windows GDI все еще использует большинство из "фокусов", 
необходимых для такого вызова, и если GDI распознает вызов Escape, то она 
"фокусничает" и изменяет любой из буферов, которые вы передали в качестве 
параметров (при необходимости). Если же вызов Escape не распознается GDI, то 
переданные указатели изменяются, но данные гарантированно не конвертируются. 
Это означает, что, если вызывается 16-разрядный драйвер из 32-разрядного 
приложения и этот драйвер ожидает целого числа, вы должны отослать 16 бит 
целого типа. Это также означает, что если вы отослали указатель на структуру, 
которая содержит указатель в структуре, вам не повезло! Хотя GDI будет 
проделывать свои "фокусы" и менять указатель на структуру, он не будет делать 
этого с указателем в структуре. Если вы отослали неопознанный вызов Escape, 
вам может понадобиться узнать, каким является драйвер (16- или 32-разрядным), 
прежде чем решиться на изменения. Хотя вызов ExtEscape () и может быть 
полезен, не принимайте его на веру. Всегда хорошенько проверяйте 
запланированный вызов Escape, прежде чем решите, что он будет работать должным образом. 

    Специально для начинающих: работа с канвой объекта TPrinter 

    Несмотря на то что свойство принтера Canvas предоставляет неплохой набор 
методов управления большинством графических возможностей, вы будете счастливы 
узнать, что можете использовать любой вызов Windows GDI или связаться почти со 
всеми сторонними графическими библиотеками. Ключ к успеху— свойство Canvas . 
Handle объекта TPrinter. Этот дескриптор— стандартный в Windows контекст 
устройства принтера, и вы можете использовать его всякий раз, когда 
требуется.сшдка_на DC. Имейте в виду, что необходимо вызвать метод BeginDoc 
объекта TPrinter (или использовать AssignPrn и Rewrite файла), прежде чем 
использовать этот дескриптор. 

    Что может не работать 

    Большинство основных свойств и методов объекта TPrinter хорошо работает и 
значительно упрощает печать в Windows. В некоторых случаях, когда канва на 
принтере не так великолепна, как на экране, ваша графическая стратегия может 
нуждаться в пересмотре. 
    Рассмотрим такой пример. Ваше приложение благополучно печатало себе, но 
пользователь разнервничался и отменил процесс печати. Контекст устройства 
принтера уже разрушен, а вы все еще продолжаете вывод на канву принтера. Ваше 
приложение может довести до ручки даже блестящую операционную систему, не 
говоря уж о Windows. Всегда просматривайте с помощью метода принтера Aborted, 
не прервал ли пользователь процесс печати. Кроме того, это может позволить 
системе более эффективно распределять время. 
    Другая ситуация, когда вы нуждаетесь в смене стратегии, — использование 
метода канвы, который плохо работает на принтере. В эту категорию попадает 
большинство методов для TImage, так как они разработаны для экранных 
контекстов устройств. Если вы выбрали одну программу для рисования и на 
экране, и на принтере, можете проверить свойство принтера Printing, чтобы 
определить, нужно ли дорабатывать фрагмент программы. 
    Хорошим примером является перенос растровых изображений. Вы никогда не 
должны переносить на DC принтера растровые изображения, основанные на DC 
экрана. Наоборот, вы должны получать образ как аппаратно-независимое растровое 
изображение (DIB), а затем передавать этот DIB на принтер. Ниже в этой главе 
будет приведен пример, как выполнить все это. Кроме того, вам может 
понадобиться специальная доработка некоторых компонентов VCL. Имеются в виду 
компоненты memo и grid, ибо здесь вам придется-таки засучить рукава и 
проделать всю черновую работу, если вы хотите распечатать результат. 

    Печать форм 

    Несмотря на то, что каждая форма имеет метод для печати, вы можете 
выполнить эту задачу самостоятельно. 
    Получение растрового изображения для успешной печати на разнообразных 
моделях принтеров — возможно, одна из наиболее сложных задач, с которыми 
приходится сталкиваться при работе в Windows. Если вы не выполняете ее от 
начала до конца, то в конечном счете должны будете положиться при переводе 
изображения на драйверы видео и печати. В решении этой проблемы, 
представленном в файле MANUALS. TXT (из поставки Delphi), .есть две фатальные 
ошибки. Вы должны выбрать палитру, применяемую во всех контекстах устройств, 
которые "вы используете для перевода изображения, и не можете всегда 
полагаться на видеодрайвер для правильного заполнения информации палитры DIB 
(аппаратно-независимого растрового изображения). 

    554	Часть VI. Сложный материал 

    Примером того, как это делается, является получение экранного снимка 
формы. Если видеодрайвер является устройством, поддерживающим палитру, вам 
необходимо перехватить текущую системную палитру и использовать ее в контексте 
устройства, применяемом во время печати образа экрана. Если текущий режим 
видеодрайвера поддерживает больше 256 цветов, необходим 24-битовый DIB, в 
противном случае нужен 256-цветный DIB. Если и принтер, и видеодрайвер 
поддерживают палитры, вы устанавливаете палитру на контекст устройства 
принтера во время работы. Последним шагом является масштабирование результата, 
чтобы правильно заполнить страницу (принимая во внимание границы принтера) и 
расположить результат в ее центре. Замечание: ваша форма должна быть самым 
верхним окном, кроме того, она должна быть видна на экране полностью. В 
противном случае вы получите распечатку лишь части того, что показано на 
экране в данный момент. Если вы запускаете событие печати с помощью кнопки или 
используете всплывающее диалого вое окно печати, добавьте некоторые вызовы к 
Application. ProcessMessages, чтобы скрыть кнопку или окно, прежде чем 
сделать снимок экрана.	•—•—- — 

    uses Printers; 
    procedure TForml.ButtonlClick( Sender : Tobject); var 
    dc : HOC; 
    isSrcDcPalDevice : BOOL; 
    isPrnDcPalDevice : BOOL; 
    MemDc : hdc; 
    MemBitmap : hBitmap; 
    OldMemBitmap : hBitmap; 
    hDibHeader : Thandle; 
    pDibHeader : pointer; 
    hBits : Thandle; 
    pBits : pointer; 
    Testlnt : integer; 
    PageWidth : integer; 
    PageHeight : integer; 
    PageMargin : TPoint; 
    ImagablePageWidth : integer; 
    ImagablePageHeight : integer; 
    OffsetX : integer; 
    OffsetY : integer; 
    ScaleX : integer; 
    ScaleY : integer; 
    ppal : PLOGPALETTE; 
    pal : hPalette; 
    OldPal : hPalette; 
    i : integer; begin 
    { Допускаем перерисовку кнопки. } Application.ProcessMessages; 
    { Получаем контекст устройства экрана } { для снимка экрана и 
конвертирования. } dc := GetDc(0); 
    { Создаем совместимый контекст устройства. } MemDc := CreateCompatibleDc(dc,); 
    { Создаем совместимое растровое изображение. } MemBitmap := CreateCompatibleBitmap(Dc, 
    forml.width, 
    fоrml.height); 
    { Выбираем растровое изображение в контексте устройства памяти. } 
OldMemBitmap := SelectObject(MemDc, MemBitmap); 
    { Получаем системную палитру, если это необходимо. } pPal := nil; Pal := 
0; OldPal := 0; 


    Глава 23. Печать в Delphi	555 


    IsSrcDcPalDevice := false; if GetDeviceCaps(dc, RASTERCAPS) and RC_PALETTE 
= RC_PALETTE then begin GetMemtpPal, sizeof(TLOGPALETTE) + (255 * 
sizeof(TPALETTEENTRY))); FillChar(рРа!л, sizeof(TLOGPALETTE) + 
    (255 * sizeof(TPALETTEENTRY)), #0); рРа1л.ра1Уегз1оп := $300; 
рРа!л.palNumEntries := 
    GetSystemPaletteEntries(dc, 0, 
    256, 
    рРа!л.palPalEntry); 
    if рРа!л.PalNumEntries <> 0 then begin pal := CreatePalette(pPalA); 
    OldPal := SelectPalette(MemDc, Pal, false); isSrcDcPalDevice := true; end else 
    FreeMem(pPal, sizeof(TLOGPALETTE) + 
    (255 * sizeof(TPALETTEENTRY))); end; 
    { Копируем экран в память. } BitBlt(MemDc, 
    О, О, 
    forml.width, forml.height, 
    Dc, 
    forml.left, forml.top, 
    SrcCopy); if isSrcDcPalDevice = true then 
    SelectPalette(MemDc, OldPal, false); 
    { Сбрасываем выбор растрового изображения в памяти. } SelectObject(MemDc, OldMemBitmap); 
    { Уничтожаем контекст устройства памяти. } DeleteDc(MemDc); 
    { Выделяем память для структуры DIB. } hDibHeader := GlobalAlloc(GHND, 
    sizeof(TBITMAPINFO) + 
    (sizeof(TRGBQUAD) * 256)); 
    { Получаем указатель на область памяти. } pDibHeader := GlobalLock(hDibHeader); 
    { Заполняем заголовок DIB так, как мы этого хотим. } FillChar(pDibHeader^, 
    sizeof(TBITMAPINFO) + (sizeof(TRGBQUAD) * 256)), 
    #0); PBITMAPINFOHEADER(pDibHeader)^.biSize := 
    sizeof(TBITMAPINFOHEADER); 
    PBITMAPINFOHEADER(pDibHeader)Л.biPlanes := 1; if isSrcDcPalDevice then 
    PBITMAPINFOHEADER(pDibHeader)^biBitCount := 8 else 
    PBITMAPINFOHEADER(pDibHeader)A.biBitCount := 24; 
PBITMAPINFOHEADER(pDibHeader)".biWidth := forml.width; 
PBITMAPINFOHEADER(pDibHeader)-^.biHeight := forml.height; 
PBITMAPINFOHEADER(pDibHeader)л.biCompression := BI_RGB; 
    { Определяем, сколько памяти необходимо для размещения битов. } GetDIBits(dc, 
    MemBitmap, 
    О, 
    forml.height, 


    556	Часть VI. Сложный материал 


    nil, 
    TBitmapInfotpDitoHeader^), 
    DIB__RGB_COLORS) ; 
    { Выделяем память под биты. } hBits := GlobalAlloc(GHND, 
    PBitmapInfoHeader(pDibHeader)Л.BiSizelmage); 
    ( Получаем указатель на область памяти. } pBits := GlobalLock(hBits); 
    { Получаем биты. } GetDIBits(dc, 
    MemBitmap, 
    0, 
    forml.height, 
    pBits, 
    PBitmapInfо(pDibHeader)Л, 
    DIB_RGB_COLORS); 
    { Если видеоустройство поддерживает палитры, заполняем в палитре DIB. } if 
isSrcDcPalDevice = true then begin 
    for i := 0 to (рРа!Л.PalNumEntries — 1) do begin 
    PBitmapInfo(pDibHeader)Л.bmiColors[i].rgbRed := 
    pPal^.palPalEntry[i].peRed; PBitmapInfo(pDibHeader)Л.bmiColors[i].rgbGreen := 
    pPalA.palPalEntry[i].peGreen; 
PBitmapInfo(pDibHeader)Л.bmiColors[i].rgbBlue := 
    рРа!л.palPalEntry[i].peBlue; end; FreeMemfpPal, sizeof(TLOGPALETTE) + 
    (255 * sizeof(TPALETTEENTRY))); end; 
    { Освобождаем контекст устройства экрана. } ReleaseDc(0, dc); 
    ( Удаляем растровое изображение в памяти. } DeleteObject(MemBitmap); 
    { Запускаем печать. } Printer.BeginDoc; 
    { Если принтер поддерживает палитры, используем их. } 
    isPrnDcPalDevice := false; 
    if ( (isSrcDcPalDevice = true) and 
    (GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and RC_PALETTE = 
RC_PALETTE)) then begin 
    OldPal := SelectPalette(Printer.Canvas.Handle, Pal, false); 
isPrnDcPalDevice := true; end; 
    { Получаем размер страницы непосредственно с принтера. } PageWidth  := 
GetDeviceCaps(Printer.Canvas.Handle, HORZRES); PageHeight := 
GetDeviceCaps(Printer.Canvas.Handle, VERTRES); 
    { Получаем границы страницы. } PageMargin.x := 0; PageMargin.y := 0; 
Testlnt := GETPRINTINGOFFSET; if Escape(Printer.Canvas.Handle, 
    QUERYESCSUPPORT, 
    sizeof(testint), 
    STestlnt, 
    nil) <> 0 then begin 


    Глава 23. Печать в Delphi 557 


    if Escape(Printer.Canvas.Handle, GETPRINTINGOFFSET, 0, 
    nil, 
    @PageMargin) <= 0 then begin PageMargin.x := 0; PageMargin.y := 0; end; end; 
    { Рассчитываем область для вывода изображения. } ImagablePageWidth  := 
PageWidth  — (2 * PageMargin.x)); ImagablePageHeight := PageHeight — (2 * PageMargin.y); 
    { Масштабируем печатаемое изображение. } if (((ImagablePageWidth <= 
ImagablePageHeight) and (Forml.Width >= Forml.Height)) or ((ImagablePageWidth 
> ImagablePageHeight) and (Forml.Height < Forml.Width))) then begin ScaleX := ImagablePageWidth; 
    ScaleY := Trunc(Forml.Height * (ImagablePageWidth/Forml.Width)); OffsetX 
:= PageMargin.x; 
    OffsetY := (PageHeight div 2) - (ScaleY div 2); end else begin 
    ScaleX := Trunc(Forml.Width * (ImagablePageHeight/Forml.Height)) ScaleY := ImagablePageHeight; 
    OffsetX := (PageWidth div 2) - (ScaleX div 2); OffsetY := PageMargin.y; end; 
    { Отсылаем растровое изображение на принтер. } 
StretchDiBits(Printer.Canvas.Handle, ~~————- offsetx, 
    OffsetY, 
    ScaleX, 
    ScaleY, 
    0, 0, 
    Forml.Width, 
    Forml.Height, 
    pBits, 
    pBitmapInfo(pDibHeader)A, 
    DIB_RGB_COLORS, 
    SRCCOPY) ; 
    { Если принтер поддерживает палитру, удаляем ее. } if isPrnDcPalDevice = 
true then 
    SelectPalette(Printer.Canvas.Handle, OldPal, false); 
    if isSrcDcPalDevice = true then DeleteObject(Pal); 
    { Очищаем выделенную память. } GlobalUnlock(hBits); GlobalFree(hBits); 
GlobalUnlock(hDibHeader); GlobalFree(hDibHeader); 
    { Заканчиваем печать. } 
    Printer. EndDoc;	>. 
    end; 

    И наконец, если вас интересует печать других изображений, кроме формы, 
можете приспособить для этого приведенную выше программу. Только помните, что 
растровое изображение нельзя выделить в контексте устройства во время переноса 
изображения с экрана на принтер и необходимо использовать для такого переноса 
Ос экрана, а не канву или DC памяти. 

    558	Часть VI. Сложный материал 


    В обход объекта TPrinter 

    Как уже упоминалось, когда вы печатаете, может быть создан метафайл в 
режиме только для чтения. Это может значительно ускорить рисование 
непосредственно для драйвера принтера и исключить этап создания промежуточного 
файла диспетчером печати (Print Manager). Вы можете сделать это, используя 
разбиение на зоны. Этот прием разрабатывался в то время, когда принтеры не 
имели достаточно памяти для хранения полной страницы графики. Приложение в 
этом случае опрашивает драйвер, чтобы получить координаты зоны (или 
прямоугольной области страницы) для рисования. 
    Затем приложение рисует правильный вывод результата для этой области и 
запрашивает координаты следующей зоны до тех пор, пока не заполнится вся 
страница. Некоторые принтеры имеют как текстовый, так и графический режимы. 
Они не могут поддерживать графику в текстовом режиме, а текст— в графическом. 
Для согласования таких принтеров вы можете получить TextBand— зону, в которой 
выводится только текст, и GraphicsBand— зону для вывода только графики. 
Основной причиной разбиения на зоны является то, что вы получаете 
действительно Ос принтера и работаете с ним, а не с Ос метафайла (чтоi 
может..иметь большое значение, когда вы напрямую создаете изображение). 
    Часто при использовании DC метафайла многие вызовы GDI срываются или 
работают неправильно. Возможно, разделение на зоны — единственный способ 
обойти эту проблему. Наконец, разбиение на зоны несовместимо с объектом 
TPrinter, так что, если вы хотите применить такое разбиение, придется решить 
задачу печати с ним от начала и до конца. 

    Резюме 

    Теперь, когда вы знаете все секреты успешной печати, можете применить их 
на практике. Несмотря на то что технология печати претерпела значительные 
изменения за эти годы, помните, что она основывается не только на том, как вы 
печатаете, но и на том, что вы печатаете. Планируйте вывод на печать. Если вы 
не очень хорошо знакомы с основами полиграфии, рассмотрите какой-нибудь хорошо 
напечатанный документ, прежде чем решить, как разместить ваш вывод, и 
выясните, что думает по этому поводу профессионал. 
    Не используйте более трех шрифтов на одной странице и выбирайте гарнитуру 
для достижения максимальной читабельности. Не применяйте только прописные 
буквы (даже в заголовках). Более 99% всего, что читает наш глаз, печатается 
строчными буквами, и я уверяю вас, что и ваш глаз может интерпретировать 
строчные символы значительно лучше прописных. 

    Глава 23. Печать в Delphi	559 

