Подтвердить что ты не робот

Как найти реальную область для печати? (PrintDocument)

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

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

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

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

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


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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Если поля по умолчанию 1 дюйм страницы и OriginAtMargins установлены на true, графический объект будет отрегулирован на прямоугольник 100,100,650,1100 для обычной портретной страницы. Это один дюйм внутри каждого физического края страницы. Таким образом, 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 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 physical limitations. 
    // So let adjust the origin for preview to reflect the printer 
    // 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), чтобы убедиться, что доступная ширина и высота находятся только в доступных размерах. Я здесь "не в безопасности", если вы хотите, чтобы вы могли поддерживать координаты на основе float (вместо int), просто будьте осторожны, чтобы наблюдать за ошибками округления, которые приведут к обрезанной графике (если она округляется с 817,96 до 818 а затем драйвер принтера решает, что больше не видно).

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


EDIT: заметки о "мягких полях"

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

Пример:Если вы устанавливаете поля страницы в 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 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 physical limitations. 
    // So let adjust the origin for preview to reflect the printer 
    // 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);
}

Ответ 2

В настоящее время на моем принтере работает следующее. У меня 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 является ложным, является стандартным для всех принтеров или если он просто делает это на моем принтере.

Ответ 3

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

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