web-dev-qa-db-fra.com

Comment trouver la zone imprimable réelle? (PrintDocument)

Pourquoi est-il si difficile de découvrir ce rectangle magique?

Dans l'événement OnPrintPage, j'ai PrintPageEventArgs et j'essaie de dessiner en utilisant les graphiques dans les limites de la zone imprimable maximale.

J'ai essayé d'utiliser PageBounds, PrintableArea, Graphics.VisibleClipBounds, etc. Tous ne parviennent pas à obtenir systématiquement la zone de dessin, en particulier lors du passage de la mise en page Paysage à Portrait. PrintableArea ne semble jamais changer lorsque vous passez de Paysage à Portrait.

J'ai également remarqué qu'il existe une différence dans la définition de Graphics.VisibleClipBounds selon que je fais un aperçu avant impression et une impression réelle. Dans un aperçu, il affiche toujours la largeur/hauteur du portrait, donc je dois vérifier s'il s'agit d'un aperçu et je dois permuter manuellement la largeur/hauteur lorsqu'il s'agit d'un paysage.

J'ai besoin d'un algorithme pour calculer la zone imprimable en ce qui concerne le contexte graphique actuel, pas une zone d'impression théorique arbitraire qui n'est pas utilisée dans le dessin réel.


Ma préoccupation concerne le décalage de la matrice graphique. Jusqu'à présent, j'ai remarqué de graves incohérences entre la façon dont le contexte graphique est prétraduit en utilisant les marges dures en fonction de facteurs tels que:

  • Si OriginAtMargins est vrai ou faux (ne se comporte pas comme je le pense)
  • Si j'imprime sur une imprimante ou que j'utilise PrintPreviewControl (je dois vérifier s'il s'agit d'une impression à prévisualiser ou d'une impression à la page pour gérer correctement la traduction)
  • Si j'utilise mon imprimante à la maison ou mon imprimante au travail (les deux se comportent différemment)

Existe-t-il un moyen standard de gérer cela? Dois-je simplement réinitialiser la matrice? Lorsque j'ai défini OriginAtMargins sur true, le graphique est prétraduit à 84,84, mais mes marges sont de 100 100. Les marges fermes sont de 16,16. Ne devrait-il pas être traduit à 100 100? Puisque 0,0 devrait être aux limites de la page, pas aux marges dures.

Fondamentalement, ma méthode devrait toujours fonctionner pour obtenir le meilleur rectangle imprimable. J'ai juste besoin d'un moyen cohérent et indépendant de l'appareil pour m'assurer que mon dessin Origin (0, 0) est en haut à gauche de la page pour que le rectangle ci-dessus me soit utile.

43
Trevor Elliott

Votre question manque un peu de clarté quant au "meilleur" rectangle. Je vais supposer que vous voulez dire le plus grand rectangle qui sera visible à 100% lors de l'impression.

Commençons donc par nous assurer que nous comprenons ce que sont les "origines" de l'objet graphique du document imprimé et comment la propriété OriginAtMargins affecte cette origine.

OriginAtMargins - Obtient ou définit une valeur indiquant si la position d'un objet graphique associé à une page est située juste à l'intérieur des marges spécifiées par l'utilisateur ou dans le coin supérieur gauche du zone imprimable de la page.
- Définition de la classe PrintDocument sur MSDN

Donc, avec OriginAtMargins réglé sur false (par défaut), l'objet graphique sera ajusté au rectangle PrintableArea (environ 5/32 à partir de chaque page Edge pour mon imprimante laser, les anciennes imprimantes laser peuvent être plus, les nouveaux imprimantes à jet d'encre peuvent imprimer directement sur l'Edge, le logiciel PDF imprimeront directement sur l'Edge). Donc, 0,0 dans mon objet graphique correspond en fait à 16,16 sur la page physique de mon imprimante laser. (votre imprimante peut être différente).

Avec les marges de page par défaut de 1 pouce et OriginAtMargins définis sur true, l'objet graphique sera ajusté au rectangle 100,100,650,1100 pour une page de lettre portrait normale. C'est un pouce à l'intérieur de chaque Edge de page physique. Donc 0,0 dans votre objet graphique est en fait 100 100 sur la page physique.

Les marges sont également appelées "marges souples" car elles sont définies dans le logiciel et ne sont pas affectées par le périphérique d'impression physique. Cela signifie qu'ils seront appliqués à la taille de page actuelle dans le logiciel et refléteront le portrait ou le paysage de la dimension de page réelle.

PrintableArea est également connu sous le nom de "marges dures" qui reflètent les limites physiques de votre périphérique d'impression. Cela variera d'une imprimante à l'autre, d'un fabricant à l'autre. Étant donné qu'il s'agit de mesures matérielles, elles ne pivotent pas lorsque vous définissez la page en paysage/portrait. Les limitations physiques ne changeront pas sur l'imprimante quels que soient les paramètres d'impression du logiciel, nous devons donc nous assurer de les appliquer sur l'axe correct en fonction de nos paramètres logiciels pour le document d'impression (orientation).

Ainsi, en suivant le modèle approximatif de l'exemple de code que vous avez publié, voici un gestionnaire d'événements PrintDocument.PrintPage qui dessinera un rectangle aussi grand que possible tout en restant visible (avec la valeur par défaut PrintDocument.OriginsAtMargins Étant false) . Si vous définissez PrintDocument.OriginsAtMargins Sur true, il dessinera un rectangle aussi grand que possible tout en étant visible à l'intérieur des marges souples configurées (par défaut à 1 "des bords de la page).

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);
}

Les deux lignes qui déterminent la largeur et la hauteur disponibles sont ce que je pense que vous cherchiez dans votre question. Ces deux lignes déterminent si vous souhaitez des marges souples ou des marges dures et si le document d'impression est configuré pour le paysage ou le portrait.

J'ai utilisé Math.Floor() pour sortir facilement tout ce qui dépasse la décimale (ex: 817.96 -> 817) juste pour m'assurer que la largeur et la hauteur disponibles étaient juste à l'intérieur des dimensions disponibles. Je suis "en sécurité" ici, si vous le souhaitez, vous pouvez conserver les coordonnées basées sur les flottants (au lieu de int), faites juste attention aux erreurs d'arrondi qui se traduiront par les graphiques écrêtés (s'il arrondit 817,96 à 818 puis le pilote d'imprimante décide que ce n'est plus visible).

J'ai testé cette procédure en mode portrait et paysage avec des marges dures et des marges douces sur une imprimante Dell 3115CN, Samsung SCX-4x28 et CutePDF. Si cela ne répond pas adéquatement à votre question, envisagez de réviser votre question pour clarifier "rectangle magique" et "meilleur rectangle".


EDIT: Notes sur les "marges souples"

Les marges logicielles sont appliquées dans le logiciel et ne prennent pas en compte les limitations matérielles de l'imprimante. C'est intentionnel et par conception. Vous pouvez définir les marges souples en dehors de la zone imprimable si vous le souhaitez et la sortie peut être coupée par le pilote de votre imprimante. Si cela n'est pas souhaitable pour votre application, vous devez ajuster les marges dans votre code de programme. Soit vous pouvez empêcher l'utilisateur de sélectionner des marges en dehors de la zone imprimable (ou les avertir si c'est le cas), soit vous pouvez appliquer certaines conditions min/max dans votre code lorsque vous commencez réellement à imprimer (dessiner) le document.

Exemple de cas: Si vous définissez les marges de la page à 0,0,0,0 dans Microsoft Word 2007, une boîte de dialogue d'avertissement s'affiche et indique "Un ou plusieurs les marges sont définies en dehors de la zone imprimable de la page. Cliquez sur le bouton Corriger pour augmenter les marges appropriées. " Si vous cliquez sur Corriger, Word copiera simplement les marges dures dans les marges molles, de sorte que la boîte de dialogue affiche désormais 0,16 "pour toutes les marges (capacités de mon imprimante laser).

Il s'agit d'un comportement attendu. Ce n'est pas un bogue/problème avec Microsoft Word si la page imprimée est coupée car l'utilisateur a ignoré cet avertissement et utilisé des marges de page de 0,0,0,0. C'est la même chose dans votre application. Vous devez appliquer les limites pour tout ce qui est approprié dans votre cas d'utilisation. Soit avec une boîte de dialogue d'avertissement, soit vous pouvez forcer la limite plus fortement dans le code (ne pas offrir de choix à l'utilisateur).


Stratégie alternative

Très bien, alors peut-être que vous ne voulez pas simplement obtenir les marges dures, mais plutôt obtenir les marges molles, puis imposer que les marges molles restent à l'intérieur de la zone imprimable lors de l'impression. Développons une autre stratégie ici.

Dans cet exemple, je vais utiliser les origines aux marges et permettre à l'utilisateur de sélectionner la marge qu'il souhaite, mais je vais appliquer dans le code que la marge sélectionnée ne se trouve pas en dehors de la zone imprimable. Si les marges sélectionnées sont en dehors de la zone imprimable, je vais simplement les ajuster pour qu'elles soient à l'intérieur de la zone imprimable.

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

Actuellement, ce qui suit fonctionne sur mon imprimante. J'ai OriginAtMargins défini sur false. Cela provoque la traduction automatique vers HardMarginX et HardMarginY lorsque j'imprime sur mon imprimante, mais AUCUNE traduction lorsque j'imprime vers PrintPreviewControl. Par conséquent, je dois vérifier ce cas.

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;
}

Si quelqu'un peut essayer ce code sur son imprimante pour vérifier qu'il fonctionne universellement, ou le corriger si je me trompe, ce serait bien.

Je ne sais pas si la pré-traduction de l'Origin vers les marges dures lorsque OriginAtMargins est fausse est standard avec toutes les imprimantes, ou si cela se fait simplement sur mon imprimante.

3
Trevor Elliott

Je pense que ce dont vous avez besoin est simplement de redessiner l'image pour l'adapter au format de papier utilisé. Voici mon code:

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