Как найти фактическую область печати?(PrintDocument) - PullRequest
43 голосов
/ 06 января 2012

Почему так сложно найти этот магический прямоугольник?

В событии OnPrintPage у меня есть PrintPageEventArgs, и я пытаюсь рисовать с использованием графики в пределах максимальной области печати. ​​

Я пытался использовать PageBounds, PrintableArea, Graphics.VisibleClipBounds и т. Д. Все не в состоянии последовательно получить область рисования, особенно при переключении из альбомной ориентации в книжную. Кажется, что PrintableArea никогда не меняется при переключении с альбомного на портретное.

Я также заметил, что существует разница в том, как устанавливается Graphics.VisibleClipBounds в зависимости от того, выполняю ли я предварительный просмотр и фактическую печать. В предварительном просмотре всегда отображается Портрет ширина / высота, поэтому я должен проверить, является ли это предварительным просмотром, и я должен вручную поменять ширину / высоту, когда это Пейзаж.

Мне нужен алгоритм для вычисления области печати , поскольку он относится к текущему графическому контексту , а не к произвольной теоретической области печати, которая не используется в реальном чертеже.


Моя проблема связана со смещением графической матрицы. До сих пор я заметил серьезные несоответствия между тем, как Графический контекст предварительно переводится с использованием жестких полей в зависимости от таких факторов, как:

  • Если OriginAtMargins имеет значение true или false (ведет себя не так, как я думаю)
  • Если я печатаю на принтере или использую PrintPreviewControl (я должен проверить, является ли это печатью для предварительного просмотра или печатью на странице для правильной обработки перевода)
  • Если я использую принтер дома или принтер на работе (оба ведут себя по-разному)

Есть ли стандартный способ справиться с этим? Должен ли я просто сбросить матрицу? Когда я устанавливаю OriginAtMargins в true, Графика предварительно переводится в 84,84, но мои поля равны 100,100. Твердые поля 16,16. Разве это не должно быть переведено на 100,100? Так как 0,0 должно быть в границах страницы, а не в жестких полях.

По сути, мой метод всегда должен работать для получения лучшего печатного прямоугольника. Мне просто нужен последовательный, не зависящий от устройства способ, чтобы убедиться, что источник моего рисунка (0, 0) находится в верхнем левом углу страницы, чтобы вышеупомянутый прямоугольник мне пригодился.

Ответы [ 3 ]

72 голосов
/ 13 января 2012

Вашему вопросу не хватает ясности относительно того, что такое «лучший» прямоугольник. Я предполагаю, что вы имеете в виду самый большой прямоугольник, который будет напечатан на 100% при печати. ​​

Итак, давайте начнем с того, что мы понимаем, что такое «происхождение» графического объекта печатного документа и как свойство OriginAtMargins влияет на это начало.

OriginAtMargins - получает или задает значение, указывающее, является ли позиция графического объекта, связанного со страницей, находится внутри указанные пользователем поля или в верхнем левом углу области печати страницы.
- Определение класса PrintDocument на MSDN

Таким образом, с OriginAtMargins, установленным на false (по умолчанию), графический объект будет отрегулирован по прямоугольнику PrintableArea (примерно 5/32 от каждого края страницы для моего лазерного принтера, старые лазерные принтеры могут быть больше, новые струйные принтеры могут печатайте прямо до края, программные принтеры PDF будут печатать прямо до края). Таким образом, 0,0 в моем графическом объекте на самом деле составляет 16,16 на физической странице моего лазерного принтера (ваш принтер может отличаться).

При заданных по умолчанию полях в 1 дюйм и значении OriginAtMargins, равном true, графический объект будет отрегулирован по прямоугольнику 100 100 650 110 для обычной страницы с портретной буквой. Это один дюйм внутри каждого физического края страницы. Таким образом, 0,0 в вашем графическом объекте на самом деле составляет 100 100 на физической странице.

Поля также называются «мягкими полями», так как они определены в программном обеспечении и не зависят от физического устройства печати. Это означает, что они будут применены к текущему размеру страницы в программном обеспечении и отражают фактический размер страницы в книжной или альбомной ориентации.

PrintableArea также известен как «жесткие поля», которые отражают физические ограничения вашего печатающего устройства. Это будет варьироваться от принтера к принтеру, от производителя к производителю. Поскольку это аппаратные измерения, они не вращаются, когда вы устанавливаете страницу в альбомную / книжную ориентацию. Физические ограничения на принтере не изменятся независимо от настроек печати программного обеспечения, поэтому нам необходимо убедиться, что мы применяем их на правильной оси в зависимости от настроек программного обеспечения для документа печати (ориентация).

Итак, следуя приблизительной модели примера кода, который вы разместили, вот обработчик события PrintDocument.PrintPage, который будет рисовать прямоугольник как можно большего размера, оставаясь при этом видимым (с значением по умолчанию PrintDocument.OriginsAtMargins, равным false). Если вы установите PrintDocument.OriginsAtMargins на true, он будет рисовать прямоугольник как можно большего размера, оставаясь видимым внутри настроенных мягких полей (по умолчанию 1 "от краев страницы).

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    // Save our print action so we know if we are printing 
    // a preview or a real document.
    printAction = e.PrintAction;

    // Set some preferences, our method should print a box with any 
    // combination of these properties being true/false.
    printDocument.OriginAtMargins = false;   //true = soft margins, false = hard margins
    printDocument.DefaultPageSettings.Landscape = false;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    // If you set printDocumet.OriginAtMargins to 'false' this event 
    // will print the largest rectangle your printer is physically 
    // capable of. This is often 1/8" - 1/4" from each page edge.
    // ----------
    // If you set printDocument.OriginAtMargins to 'false' this event
    // will print the largest rectangle permitted by the currently 
    // configured page margins. By default the page margins are 
    // usually 1" from each page edge but can be configured by the end
    // user or overridden in your code.
    // (ex: printDocument.DefaultPageSettings.Margins)

    // Grab a copy of our "soft margins" (configured printer settings)
    // Defaults to 1 inch margins, but could be configured otherwise by 
    // the end user. You can also specify some default page margins in 
    // your printDocument.DefaultPageSetting properties.
    RectangleF marginBounds = e.MarginBounds;

    // Grab a copy of our "hard margins" (printer's capabilities) 
    // This varies between printer models. Software printers like 
    // CutePDF will have no "physical limitations" and so will return 
    // the full page size 850,1100 for a letter page size.
    RectangleF printableArea = e.PageSettings.PrintableArea;

    // If we are print to a print preview control, the origin won't have 
    // been automatically adjusted for the printer's physical limitations. 
    // So let's adjust the origin for preview to reflect the printer's 
    // hard margins.
    if (printAction == PrintAction.PrintToPreview)
        g.TranslateTransform(printableArea.X, printableArea.Y);

    // Are we using soft margins or hard margins? Lets grab the correct 
    // width/height from either the soft/hard margin rectangles. The 
    // hard margins are usually a little wider than the soft margins.
    // ----------
    // Note: Margins are automatically applied to the rotated page size 
    // when the page is set to landscape, but physical hard margins are 
    // not (the printer is not physically rotating any mechanics inside, 
    // the paper still travels through the printer the same way. So we 
    // rotate in software for landscape)
    int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins 
        ? marginBounds.Width 
        : (e.PageSettings.Landscape 
            ? printableArea.Height 
            : printableArea.Width));
    int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins 
        ? marginBounds.Height 
        : (e.PageSettings.Landscape 
            ? printableArea.Width 
            : printableArea.Height));

    // Draw our rectangle which will either be the soft margin rectangle 
    // or the hard margin (printer capabilities) rectangle.
    // ----------
    // Note: we adjust the width and height minus one as it is a zero, 
    // zero based co-ordinates system. This will put the rectangle just 
    // inside the available width and height.
    g.DrawRectangle(Pens.Red, 0, 0, availableWidth - 1, availableHeight - 1);
}

Две строки, которые определяют доступную ширину и доступную высоту, - это то, что, я думаю, вы искали в своем вопросе. Эти две строки учитывают, хотите ли вы мягкие поля или жесткие поля, а также настроен ли печатный документ для альбомной или книжной ориентации.

Я использовал Math.Floor() для простого выхода, чтобы просто отбросить что-либо после десятичной дроби (например: 817.96 -> 817), просто чтобы убедиться, что доступная ширина и высота были только внутри доступных размеров. Я "не в безопасности" здесь, если вы хотите, чтобы вы могли поддерживать координаты с плавающей точкой (вместо int), просто следите за ошибками округления, которые приведут к обрезке графики (если она округляет 817,96 до 818 а затем драйвер принтера решает, что он больше не виден).

Я протестировал эту процедуру как в книжной, так и в альбомной ориентации с жесткими и мягкими полями на программном принтере Dell 3115CN, Samsung SCX-4x28 и CutePDF. Если это не помогло ответить на ваш вопрос, рассмотрите возможность пересмотра вашего вопроса, чтобы уточнить «магический прямоугольник» и «лучший прямоугольник».


РЕДАКТИРОВАТЬ: Примечания о "Мягкие поля"

Мягкие поля применяются в программном обеспечении и не учитывают аппаратные ограничения принтера.Это намеренно и по замыслу.Вы можете установить мягкие поля за пределами области печати, если хотите, и вывод может быть обрезан драйвером вашего принтера.Если это нежелательно для вашего приложения, вам необходимо отрегулировать поля в программном коде.Либо вы можете запретить пользователю выбирать поля за пределами области печати (или предупредить их, если они это сделают), либо вы можете применить некоторые минимальные / максимальные условия в своем коде, когда вы фактически начинаете печатать (рисовать) документ.

Пример Case: Если вы установите поля страницы 0,0,0,0 в Microsoft Word 2007, появится диалоговое окно с предупреждением: «Одно или несколько полей установлены за пределами области печати страницы. Выберитекнопку Fix, чтобы увеличить соответствующие поля. "Если вы нажмете «исправить», Word просто скопирует жесткие поля в мягкие поля, поэтому в диалоговом окне теперь отображается 0,16 дюйма для всех полей (возможности моего лазерного принтера).

Это ожидаемое поведение. Это не ошибка/ проблема с Microsoft Word, если распечатанная страница обрезается, потому что пользователь проигнорировал это предупреждение и использовал поля страницы 0,0,0,0. Это то же самое в вашем приложении. Необходимо применять ограничения для всего, если это уместно в вашем использованиилибо с диалоговым окном предупреждения, либо вы можете усилить ограничение в коде (не предлагайте пользователю выбор).


Альтернативная стратегия

Хорошо, возможно, такВы не хотите просто получать жесткие поля, а скорее получить мягкие поля, а затем обеспечить, чтобы мягкие поля оставались внутри области печати при печати. ​​Давайте разработаем другую стратегию здесь.

В этом примере я будуиспользовать происхождение на полях и позволить пользователю выбирать любое поле, которое он хочет, но я собираюсь обеспечить в коде, чтоВыбранное поле не должно находиться за пределами области печати.Если выбранные поля находятся за пределами области печати, я просто настрою их так, чтобы они находились внутри области печати. ​​

PrintAction printAction = PrintAction.PrintToFile;

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    // Save our print action so we know if we are printing 
    // a preview or a real document.
    printAction = e.PrintAction;

    // We ALWAYS want true here, as we will implement the 
    // margin limitations later in code.
    printDocument.OriginAtMargins = true;

    // Set some preferences, our method should print a box with any 
    // combination of these properties being true/false.
    printDocument.DefaultPageSettings.Landscape = false;
    printDocument.DefaultPageSettings.Margins.Top = 100;
    printDocument.DefaultPageSettings.Margins.Left = 0;
    printDocument.DefaultPageSettings.Margins.Right = 50;
    printDocument.DefaultPageSettings.Margins.Bottom = 0;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    // If you set printDocumet.OriginAtMargins to 'false' this event 
    // will print the largest rectangle your printer is physically 
    // capable of. This is often 1/8" - 1/4" from each page edge.
    // ----------
    // If you set printDocument.OriginAtMargins to 'false' this event
    // will print the largest rectangle permitted by the currently 
    // configured page margins. By default the page margins are 
    // usually 1" from each page edge but can be configured by the end
    // user or overridden in your code.
    // (ex: printDocument.DefaultPageSettings.Margins)

    // Grab a copy of our "hard margins" (printer's capabilities) 
    // This varies between printer models. Software printers like 
    // CutePDF will have no "physical limitations" and so will return 
    // the full page size 850,1100 for a letter page size.
    RectangleF printableArea = e.PageSettings.PrintableArea;
    RectangleF realPrintableArea = new RectangleF(
        (e.PageSettings.Landscape ? printableArea.Y : printableArea.X),
        (e.PageSettings.Landscape ? printableArea.X : printableArea.Y),
        (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width),
        (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height)
        );

    // If we are printing to a print preview control, the origin won't have 
    // been automatically adjusted for the printer's physical limitations. 
    // So let's adjust the origin for preview to reflect the printer's 
    // hard margins.
    // ----------
    // Otherwise if we really are printing, just use the soft margins.
    g.TranslateTransform(
        ((printAction == PrintAction.PrintToPreview) 
            ? realPrintableArea.X : 0) - e.MarginBounds.X,
        ((printAction == PrintAction.PrintToPreview) 
            ? realPrintableArea.Y : 0) - e.MarginBounds.Y
    );

    // Draw the printable area rectangle in PURPLE
    Rectangle printedPrintableArea = Rectangle.Truncate(realPrintableArea);
    printedPrintableArea.Width--;
    printedPrintableArea.Height--;
    g.DrawRectangle(Pens.Purple, printedPrintableArea);

    // Grab a copy of our "soft margins" (configured printer settings)
    // Defaults to 1 inch margins, but could be configured otherwise by 
    // the end user. You can also specify some default page margins in 
    // your printDocument.DefaultPageSetting properties.
    RectangleF marginBounds = e.MarginBounds;

    // This intersects the desired margins with the printable area rectangle. 
    // If the margins go outside the printable area on any edge, it will be 
    // brought in to the appropriate printable area.
    marginBounds.Intersect(realPrintableArea);

    // Draw the margin rectangle in RED
    Rectangle printedMarginArea = Rectangle.Truncate(marginBounds);
    printedMarginArea.Width--;
    printedMarginArea.Height--;
    g.DrawRectangle(Pens.Red, printedMarginArea);
}
3 голосов
/ 10 января 2012

В настоящее время на моем принтере работает следующее.У меня для OriginAtMargins установлено значение false.Это вызывает автоматический перевод в HardMarginX и HardMarginY, когда я печатаю на свой принтер, но НЕТ перевода, когда я печатаю в PrintPreviewControl.Поэтому я должен проверить этот случай.

private void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
    printAction = e.PrintAction;
    printDocument.OriginAtMargins = false;
}

private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
    Graphics g = e.Graphics;

    if (printAction != PrintAction.PrintToPreview)
        g.TranslateTransform(-e.PageSettings.HardMarginX, -e.PageSettings.HardMarginY);

    RectangleF printArea = GetBestPrintableArea(e);

    g.DrawRectangle(Pens.Red, printArea.X, printArea.Y, printArea.Width - 1, printArea.Height - 1);
}

public RectangleF GetBestPrintableArea(PrintPageEventArgs e)
{
    RectangleF marginBounds = e.MarginBounds;
    RectangleF printableArea = e.PageSettings.PrintableArea;
    RectangleF pageBounds = e.PageBounds;

    if (e.PageSettings.Landscape)
        printableArea = new RectangleF(printableArea.Y, printableArea.X, printableArea.Height, printableArea.Width);

    RectangleF bestArea = RectangleF.FromLTRB(
        (float)Math.Max(marginBounds.Left, printableArea.Left),
        (float)Math.Max(marginBounds.Top, printableArea.Top),
        (float)Math.Min(marginBounds.Right, printableArea.Right),
        (float)Math.Min(marginBounds.Bottom, printableArea.Bottom)
    );

    float bestMarginX = (float)Math.Max(bestArea.Left, pageBounds.Right - bestArea.Right);
    float bestMarginY = (float)Math.Max(bestArea.Top, pageBounds.Bottom - bestArea.Bottom);

    bestArea = RectangleF.FromLTRB(
        bestMarginX,
        bestMarginY,
        pageBounds.Right - bestMarginX,
        pageBounds.Bottom - bestMarginY
    );

    return bestArea;
}

Если кто-нибудь может попробовать этот код на своем принтере, чтобы убедиться, что он работает универсально, или исправить его, если я ошибаюсь, это было бы здорово.

Я не знаю, является ли стандартным для всех принтеров предварительный перевод источника на жесткие поля, когда значение OriginAtMargins имеет значение false, или это просто выполняется на моем принтере.

0 голосов
/ 11 января 2012

Я думаю, что вам нужно просто перерисовать изображение, чтобы оно соответствовало размеру используемой бумаги. Вот мой код:

Protected Overrides Sub OnPrintPage(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
        Dim img As Image = Nothing 'Your image source

        Dim ps As PaperSize = MyBase.PrinterSettings.DefaultPageSettings.PaperSize
        Dim pF As RectangleF = MyBase.PrinterSettings.DefaultPageSettings.PrintableArea
        Dim srcF As New RectangleF(0, 0, pg.ImageSize.Width, pg.ImageSize.Height)
        Dim dstF As New RectangleF(0, 0, pF.Width, pF.Height)

        e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        e.Graphics.DrawImage(img, dstF, srcF, GraphicsUnit.Pixel)

        MyBase.OnPrintPage(e)
End Sub
...