web-dev-qa-db-ja.com

実際の印刷可能領域を見つける方法は? (PrintDocument)

なぜこの魔法の長方形を見つけるのがそんなに難しいのですか?

OnPrintPageイベントにはPrintPageEventArgsがあり、最大印刷可能領域の境界内でグラフィックを使用して描画しようとしています。

PageBounds、PrintableArea、Graphics.VisibleClipBoundsなどを使用してみました。特に横から縦にレイアウトを切り替える場合、すべてが一貫して描画領域を取得できません。 PrintableAreaは、横から縦に切り替えても変わらないようです。

また、印刷プレビューと実際の印刷を行うかどうかによって、Graphics.VisibleClipBoundsの設定方法に違いがあることに気付きました。プレビューでは常にポートレートの幅/高さが表示されるため、プレビューであるかどうかを確認し、横の場合は幅/高さを手動で交換する必要があります。

印刷可能な領域を計算するアルゴリズムが必要です現在のグラフィックスコンテキストに関連するためであり、実際の描画では使用されない任意の理論的な印刷領域ではありません。


私の懸念は、グラフィックスマトリックスオフセットの処理です。これまでのところ、次のような要因に応じてハードマージンを使用して、Graphicsコンテキストがどのように事前変換されるかについて、重大な矛盾に気づきました。

  • OriginAtMarginsがtrueまたはfalseの場合(思ったように動作しない)
  • プリンターに印刷している場合、またはPrintPreviewControlを使用している場合(これがプレビューへの印刷か、ページへの印刷が翻訳を適切に処理するかどうかを確認する必要があります)
  • 自宅でプリンターを使用している場合、または職場でプリンターを使用している場合(どちらも動作が異なる)

これを処理する標準的な方法はありますか?マトリックスをリセットするだけですか? OriginAtMarginsをtrueに設定すると、グラフィックは84,84に事前に変換されますが、マージンは100,100です。ハードマージンは16,16です。 100,100に変換すべきではないでしょうか? 0,0はハードマージンではなく、ページ境界にある必要があります。

基本的に、私のメソッドは常に最高の印刷可能な長方形を取得するように機能します。上記のRectangleを使用するために、描画のOrigin(0、0)がページの左上にあることを確認する一貫した、デバイスに依存しない方法が必要です。

43
Trevor Elliott

あなたの質問は、「最良の」長方形が何であるかについて、少し明確に欠けています。印刷すると100%見える最大の長方形を意味すると仮定します。

したがって、印刷ドキュメントのグラフィックスオブジェクトの「原点」とは何か、OriginAtMarginsプロパティがこのOriginにどのように影響するかを理解することから始めましょう。

OriginAtMargins-ページに関連付けられたグラフィックスオブジェクトの位置がユーザー指定のマージンのすぐ内側か、またはの左上隅にあるかどうかを示す値を取得または設定しますページの印刷可能領域
- MSDNのPrintDocumentクラス定義

OriginAtMarginsfalse(デフォルト)に設定すると、グラフィックスオブジェクトはPrintableAreaの長方形に調整されます(レーザープリンターの場合、各ページエッジから約5/32、古いレーザープリンターはさらに多くなりますが、新しいインクジェットはEdgeに直接印刷される可能性があります。ソフトウェアPDFプリンターはEdgeに直接印刷されます)。したがって、グラフィックオブジェクトの0,0は実際にはレーザープリンターの物理ページで16,16です。 (お使いのプリンターは異なる場合があります)。

デフォルトの1インチのページ余白とOriginAtMarginstrueに設定すると、グラフィックスオブジェクトは通常の縦長レターページの100,100,650,1100の長方形に調整されます。これは、各物理ページエッジの内側1インチです。したがって、グラフィックスオブジェクトの0,0は、実際の物理ページでは100,100です。

マージンはソフトウェアで定義され、物理的な印刷デバイスの影響を受けないため、「ソフトマージン」とも呼ばれます。これは、ソフトウェアで現在のページサイズに適用され、実際のページディメンションの縦または横を反映することを意味します。

PrintableAreaは、「ハードマージン」とも呼ばれ、印刷デバイスの物理的な制限を反映しています。これは、プリンタごとに、メーカーごとに異なります。これらはハードウェア測定であるため、ページを横向き/縦向きに設定しても回転しません。ソフトウェアの印刷設定に関係なく、プリンターの物理的な制限は変わらないため、印刷ドキュメントのソフトウェア設定(向き)に応じて、正しい軸にそれらを適用する必要があります。

したがって、投稿したサンプルコードの大まかなモデルに続いて、表示されたまま(デフォルトの_PrintDocument.OriginsAtMargins_がfalseである)できるだけ大きな長方形を描画するPrintDocument.PrintPageイベントハンドラーがあります。 _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);
}
_

使用可能な幅と使用可能な高さを決定する2行は、あなたが質問で探していたものだと思います。これらの2行では、ソフトマージンが必要かハードマージンが必要か、および印刷ドキュメントが横向きまたは縦向きのどちらに設定されているかが考慮されます。

Math.Floor()を使用して、使用可能な幅と高さが使用可能な寸法内に収まっていることを確認するために、小数点以下を削除する簡単な方法(例:817.96-> 817)を使用しました。ここでは「安全ではありません」。intの代わりにfloatベースの座標を維持したい場合は、クリップされたグラフィックスを生じる丸めエラーに注意してください(817.96から818までを丸める場合)プリンタドライバは、それが表示されなくなったと判断します)。

Dell 3115CN、Samsung SCX-4x28、CutePDFソフトウェアプリンターでハードマージンとソフトマージンの両方を使用して、この手順をポートレートとランドスケープの両方でテストしました。これで問題が適切に解決されなかった場合は、質問を修正して「魔法の長方形」と「最良の長方形」を明確にすることを検討してください。


編集:「ソフトマージン」に関する注意事項

ソフトマージンはソフトウェアに適用され、プリンターのハードウェア制限を考慮しません。これは意図的であり、仕様です。必要に応じて、印刷可能領域の外側にソフトマージンを設定すると、プリンターのドライバーによって出力がクリップされる場合があります。これがアプリケーションにとって望ましくない場合は、プログラムコードのマージンを調整する必要があります。ユーザーが印刷可能領域の外側のマージンを選択できないようにする(または、マージンがある場合は警告する)か、実際にドキュメントの印刷(描画)を開始するときに、コードに最小/最大条件を適用できます。

例:Microsoft Word 2007でページ余白を0,0,0,0に設定すると、「1つ以上余白はページの印刷可能領域の外側に設定されます。[修正]ボタンを選択して、適切な余白を増やします。 " [修正]をクリックすると、Wordはハードマージンをソフトマージンにコピーするだけなので、ダイアログにはすべてのマージン(レーザープリンタの機能)に対して0.16インチが表示されます。

これは予想される動作です。ユーザーがこの警告を無視し、0,0,0,0ページマージンを使用したため、印刷されたページがクリップされた場合、Microsoft Wordのバグ/問題ではありません。これはアプリケーションでも同じです。ユースケースで適切な場合は、制限を実施する必要があります。警告ダイアログを使用するか、コードで制限をより強く強制できます(ユーザーに選択肢を提供しないでください)。


代替戦略

よろしいので、ハードマージンだけを取得するのではなく、ソフトマージンを取得してから、印刷時にソフトマージンが印刷可能領域内にとどまるようにします。ここで別の戦略を開発しましょう。

この例では、マージンで原点を使用し、ユーザーが必要なマージンを選択できるようにしますが、選択したマージンが印刷可能領域の外にないようにコードで強制します。選択した余白が印刷可能領域の外側にある場合は、単に印刷可能領域の内側に収まるように調整します。

_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);
}
_
72
BenSwayne

現在、私のプリンターで次の作業を行っています。 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の場合、Originのハードマージンへの事前変換がすべてのプリンターで標準であるのか、それともプリンターでこれを行うだけなのかはわかりません。

3
Trevor Elliott

必要なのは、使用している用紙サイズに合わせて画像を再描画するだけだと思います。私のコードは次のとおりです。

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
0
Eric