47

この魔法の Rectangle を見つけるのが難しいのはなぜですか?

OnPrintPage イベントには PrintPageEventArgs があり、印刷可能な最大領域の範囲内で Graphics を使用して描画しようとしています。

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

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

実際の描画で使用されない任意の理論上の印刷領域ではなく、現在の Graphics context に関連する印刷可能領域を計算するアルゴリズムが必要です。


私の懸念は、Graphics マトリックスのオフセットを扱うことです。これまでのところ、次のような要因に応じて、ハード マージンを使用してグラフィックス コンテキストが事前に翻訳される方法に重大な矛盾があることに気付きました。

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

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

基本的に、私の方法は、印刷可能な最良の長方形を取得する際に常に機能するはずです。上記の Rectangle が役立つようにするには、描画の原点 (0, 0) がページの左上にあることを確認する、デバイスに依存しない一貫した方法が必要です。

4

3 に答える 3

81

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

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

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

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

デフォルトの 1 インチのページ マージンを にOriginAtMargins設定するtrueと、グラフィック オブジェクトは通常の縦長のレター ページの 100、100、650、1100 の長方形に調整されます。これは、物理的な各ページの端から 1 インチ内側にあります。したがって、グラフィック オブジェクトの 0,0 は、物理ページでは実際には 100,100 です。

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

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

したがって、投稿したサンプル コードの大まかなモデルに従って、表示されたまま可能な限り大きな四角形を描画する PrintDocument.PrintPage イベント ハンドラーを次に示します (デフォルトPrintDocument.OriginsAtMarginsfalse)。これを設定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);
}
于 2012-01-12T20:17:39.957 に答える
3

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

于 2012-01-10T15:17:58.897 に答える
0

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

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
于 2012-01-11T19:47:34.237 に答える