web-dev-qa-db-fra.com

Pourquoi Graphics.MeasureString () renvoie-t-il un nombre plus élevé que prévu?

Je génère un reçu et j'utilise l'objet Graphics pour appeler la méthode DrawString afin d'imprimer le texte requis.

graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);

Cela fonctionne bien pour ce que j'avais besoin de faire. J'ai toujours su ce que j'imprimais afin de pouvoir couper manuellement les chaînes pour qu'elles tiennent correctement sur du papier de reçu de 80 mm. Ensuite, j'ai dû ajouter un peu plus de fonctionnalités pour rendre cela plus flexible. L'utilisateur peut transmettre des chaînes qui seraient ajoutées au bas.

Comme je ne savais pas ce qu'ils allaient mettre, je venais de créer ma propre fonction d'habillage de Word, qui prend un certain nombre de caractères à insérer et la chaîne elle-même. Afin de connaître le nombre de caractères, je faisais quelque chose comme ceci:

float width = document.DefaultPageSettings.PrintableArea.Width;
int max = (int)(width / graphics.MeasureString("a", font).Width);

Maintenant, la largeur me renvoie 283, ce qui en mm est environ 72, ce qui est logique lorsque vous prenez en compte les marges sur du papier 80 mm.

Mais la méthode MeasureString renvoie 10.5 sur une police Courier New 8pt. Ainsi, au lieu d’éviter ce que j’espérais être entre 36 et 40, j’en ai 26, ce qui donne 2 lignes de texte transformées en 3-4.

Les unités pour PrintableArea.Width sont au 1/100ème de pouce et le PageUnit pour l’objet graphique est Display (ce qui est typiquement 1/100ème pour les imprimantes). Alors pourquoi je ne récupère que 26 ans?

43
Brandon

De WindowsClient.net:

GDI + ajoute une petite quantité (1/6 em) à chaque extrémité de chaque chaîne affichée. Ce 1/6 em tient compte des glyphes avec des extrémités en surplomb (comme italique 'f'), et donne également à GDI + une petite marge de manœuvre pour aider à l'expansion de l'ajustement de la grille.

L'action par défaut de DrawString fonctionnera contre vous lors de l'affichage des exécutions adjacentes: 

  • Premièrement, le StringFormat par défaut ajoute un 1/6 supplémentaire à chaque extrémité de chaque sortie; 
  • Deuxièmement, lorsque les largeurs ajustées de la grille sont inférieures à celles prévues, la chaîne est autorisée à se contracter jusqu’à un em.

Pour éviter ces problèmes:

  • Passez toujours MeasureString et DrawString à un StringFormat basé sur le StringFormat typographique (GenericTypographic).
    Réglez les graphiques TextRenderingHint sur TextRenderingHintAntiAlias. Cette méthode de rendu utilise un anti-aliasing et un positionnement du glyphe sous-pixel pour éviter le besoin d'ajustement de la grille. Elle est donc intrinsèquement indépendante de la résolution.

Il existe deux manières de dessiner du texte dans .NET:

  • GDI + (graphics.MeasureString et graphics.DrawString)
  • GDI (TextRenderer.MeasureText et TextRenderer.DrawText)

De l'excellent blog de Michael Kaplan (rip) - Tout classer , dans .NET 1.1, tout utilisé GDI + pour le rendu du texte. Mais il y avait quelques problèmes:

  • Certains problèmes de performances sont dus à la nature un peu sans état de GDI +, dans laquelle les contextes de périphérique sont définis, puis l'original restauré après chaque appel.
  • Les moteurs de mise en forme du texte international ont été mis à jour de nombreuses fois pour Windows/Uniscribe et Avalon (Windows Presentation Foundation), mais pas pour GDI +, ce qui empêche la prise en charge du rendu international pour les nouvelles langues du même niveau de qualité.

Ils savaient donc qu'ils voulaient changer le framework .NET pour cesser d'utiliser le système de rendu de texte de GDI + , et d'utiliserGDI. Au début, ils espéraient pouvoir changer simplement:

graphics.DrawString

appeler l'ancienne API DrawText au lieu de GDI +. Mais ils ne pouvaient pas faire en sorte que le retour à la ligne et l'espacement du texte correspondent exactement à ce que GDI + faisait. Ils ont donc été obligés de garder graphics.DrawString pour appeler GDI + (raisons de compatibilité; les personnes qui appelaient graphics.DrawString s'apercevraient soudainement que leur texte n'enveloppait pas le contenu comme auparavant). 

Une nouvelle classe TextRenderer statique a été créée pour envelopper le rendu du texte GDI. Il a deux méthodes:

TextRenderer.MeasureText
TextRenderer.DrawText

Remarque: TextRenderer est un wrapper autour de GDI, alors que graphics.DrawString est toujours un wrapper autour de GDI +.


Ensuite, il y avait la question de savoir quoi faire avec tous les contrôles .NET existants, par exemple:

  • Label
  • Button
  • TextBox

Ils voulaient les utiliser pour utiliser TextRenderer (c.-à-d. GDI), mais ils devaient faire attention. Il se peut que certaines personnes dépendent de leurs contrôles comme elles le faisaient dans .NET 1.1. Et ainsi est né "rendu de texte compatible".

Par défaut, les contrôles de l’application se comportent comme ils le faisaient dans .NET 1.1 (ils sont "compatible").

Vous éteignez mode de compatibilité en appelant:

Application.SetCompatibleTextRenderingDefault(false);

Cela rend votre application meilleure, plus rapide, avec un meilleur support international. Pour résumer:

SetCompatibleTextRenderingDefault(true)  SetCompatibleTextRenderingDefault(false)
=======================================  ========================================
 default                                  opt-in
 bad                                      good
 the one we don't want to use             the one we want to use
 uses GDI+ for text rendering             uses GDI for text rendering
 graphics.MeasureString                   TextRenderer.MeasureText
 graphics.DrawString                      TextRenderer.DrawText
 Behaves same as 1.1                      Behaves *similar* to 1.1
                                          Looks better
                                          Localizes better
                                          Faster

Il est également utile de noter le mappage entre GDI + TextRenderingHint et le LOGFONT Quality utilisé pour le dessin de police GDI:

TextRenderingHint           mapped by TextRenderer to LOGFONT quality
========================    =========================================================
ClearTypeGridFit            CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit            ANTIALIASED_QUALITY (4)
AntiAlias                   ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit    PROOF_QUALITY (2)
SingleBitPerPixel           DRAFT_QUALITY (1)
else (e.g.SystemDefault)    DEFAULT_QUALITY (0)

Des échantillons

Voici quelques comparaisons des rendus de texte GDI + (graphics.DrawString) GDI (TextRenderer.DrawText):

GDI + : TextRenderingHintClearTypeGridFit,GDI: CLEARTYPE_QUALITY:

enter image description here

GDI + : TextRenderingHintAntiAlias,GDI: ANTIALIASED_QUALITY:

enter image description here

GDI + : TextRenderingHintAntiAliasGridFit,GDI: non pris en charge, utilise ANTIALIASED_QUALITY} _:

enter image description here

GDI + : TextRenderingHintSingleBitPerPixelGridFit,GDI: PROOF_QUALITY:

enter image description here

GDI + : TextRenderingHintSingleBitPerPixel,GDI: DRAFT_QUALITY:

enter image description here

je trouve étrange que DRAFT_QUALITY soit identique à PROOF_QUALITY, ce qui est identique à CLEARTYPE_QUALITY.

Voir aussi  

144
Ian Boyd

Courier New Size 11

Lorsque vous créez une police 'Courier New' avec Size = 11, vous obtenez un résultat similaire à celui de l'image ci-dessus. Vous voyez que la hauteur est de 14 pixels, sans compter le soulignement. La largeur est exactement de 14 pixels (7 pixels pour chaque caractère).

Donc, cette police rend 14x14 pixels.

Mais TextRenderer.MeasureText() renvoie une largeur de 21 pixels. Si vous avez besoin de valeurs exactes, cela ne sert à rien.

La solution est le code suivant:

Font i_Courier = new Font("Courier New", 11, GraphicsUnit.Pixel);

Win32.SIZE k_Size;
using (Bitmap i_Bmp = new Bitmap(200, 200, PixelFormat.Format24bppRgb))
{
    using (Graphics i_Graph = Graphics.FromImage(i_Bmp))
    {
        IntPtr h_DC = i_Graph.GetHdc();
        IntPtr h_OldFont = Win32.SelectObject(h_DC, i_Courier.ToHfont());

        Win32.GetTextExtentPoint32(h_DC, "Áp", 2, out k_Size);

        Win32.SelectObject(h_DC, h_OldFont);
        i_Graph.ReleaseHdc();
    }
}

k_Size contiendra la taille correcte: 14x14

IMPORTANT: Ce code mesure correctement une police normale. Si vous avez besoin des valeurs exactes également pour les polices italiques (qui ont toujours un surplomb à droite), vous devez lire le liens mentionnés dans cet article: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font

ANNEXE: Pour ceux qui n'ont jamais utilisé d'appels d'API en C #, voici un indice sur la création de la classe Win32. Ce n'est pas complet. Pour plus de détails, regardez http://www.pinvoke.net

using System.Runtime.InteropServices;

public class Win32
{       
    [StructLayout(LayoutKind.Sequential)]
    public struct SIZE
    {
        public int cx;
        public int cy;
    }

    [DllImport("Gdi32.dll")]
    public static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int cbString, out SIZE lpSize);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
}
5
Elmue

Voici une explication qui peut vous aider à comprendre comment cela fonctionne. et ce qui cause les espaces de plus ou moins avant et après chaque caractère.

GDI App DrawString Configurator

Capture d'écran

0
Cheva