Pourquoi Graphics.MeasureString () renvoie-t-il un nombre plus élevé que prévu?
-
05-07-2019 - |
Question
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 passer des chaînes qui seraient ajoutées au bas.
Comme je ne savais pas ce qu'ils allaient mettre, je viens de créer ma propre fonction de retour à la ligne qui englobe un certain nombre de caractères 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. Donc, au lieu d’éviter ce que j’espérais être entre 36 et 40, j’en ai 26, ce qui donne deux lignes de texte transformées en 3-4.
Les unités pour PrintableArea.Width sont égales à 1 / 100ème de pouce et le PageUnit pour l’objet graphique est Display (ce qui est généralement 1 / 100ème de pouce pour les imprimantes). Alors, pourquoi n’en ai-je que 26?
La solution
À partir 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 aux extrémités débordantes (comme l'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 pour l'affichage des exécutions adjacentes:
- Tout d'abord, le format par défaut StringFormat 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
etDrawString
à un StringFormat basé sur le typographique StringFormat (GenericTypographic
).
DéfinissezTextRenderingHint
surTextRenderingHintAntiAlias ??
. Cette méthode de rendu utilise un anti-crénelage et un positionnement des glyphes de sous-pixels pour éviter la nécessité d'une adaptation 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
etgraphics.DrawString
) - GDI (
TextRenderer.MeasureText
etTextRenderer.DrawText
)
Tiré de l'excellent blog de Michael Kaplan (rip) Tout trier , dans .NET 1.1, tout ce qui était 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 +: les contextes de périphérique sont définis, puis l'original est restauré après chaque appel.
- Les moteurs de mise en forme du texte international ont été mis à jour plusieurs fois pour Windows / Uniscribe et Avalon (Windows Presentation Foundation), mais pas pour GDI +, ce qui empêche la prise en charge du rendu international des nouvelles langues qualité.
Ils savaient donc qu'ils souhaitaient modifier le cadre .NET afin de cesser d'utiliser le système de rendu de texte de GDI + et d'utiliser GDI . Au début, ils espéraient pouvoir changer simplement:
graphics.DrawString
pour 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 ne fermait pas la manière dont il était écrit. habitué).
Une nouvelle classe statique TextRenderer
a été créée pour envelopper le rendu de texte GDI. Il existe deux méthodes:
TextRenderer.MeasureText
TextRenderer.DrawText
Remarque:
TextRenderer
est un wrapper autour de GDI, alors quegraphics.DrawString
est toujours un wrapper autour de GDI +.
Il y avait ensuite la question de savoir quoi faire avec tous les contrôles .NET existants, par exemple:
.-
Étiquette
Bouton -
-
TextBox
Ils voulaient les changer pour utiliser TextRenderer
(c'est-à-dire 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é le 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 " compatibles ").
Vous désactivez le 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
Qualité utilisée 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)
Échantillons
Voici quelques comparaisons entre le rendu de texte GDI + (graphics.DrawString) et le rendu de texte GDI (TextRenderer.DrawText):
GDI + : TextRenderingHintClearTypeGridFit
, GDI : CLEARTYPE_QUALITY
:
GDI + : TextRenderingHintAntiAlias ??
, GDI : ANTIALIASED_QUALITY
:
GDI + : TextRenderingHintAntiAliasGridFit
, GDI : non pris en charge, utilise ANTIALIASED_QUALITY :
GDI + : TextRenderingHintSingleBitPerPixelGridFit
, GDI : PROOF_QUALITY
:
GDI + : TextRenderingHintSingleBitPerPixel
, GDI : DRAFT_QUALITY
:
Je trouve étrange que DRAFT_QUALITY
soit identique à PROOF_QUALITY
, qui est identique à CLEARTYPE_QUALITY
.
Voir aussi
- UseCompatibleTextRendering - Compatible avec whaaaaaat?
- Tout classer: un coup d’œil sur TextRenderer de Whidbey
- MSDN: structure de LOGFONT
- AppCompat Guy: Performances de rendu de texte GDI vs GDI +
- GDI + Texte, Indépendance de résolution et Méthodes de rendu. Ou - Pourquoi mon texte est-il différent en GDI + et en GDI?
Autres conseils
Lorsque vous créez une police 'Courier New' avec Size = 11, vous obtenez une sortie similaire à celle 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).
Ainsi, cette police affiche 14 x 14 pixels.
Mais TextRenderer.MeasureText ()
renvoie plutôt 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 standard. Si vous avez besoin des valeurs exactes également pour les polices italiques (qui ont toujours un surplomb à droite), vous devez lire les liens mentionnés dans cet article: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font
APPENDICE: 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, consultez 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);
}
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.