Perché Graphics.MeasureString () restituisce un numero maggiore del previsto?
-
05-07-2019 - |
Domanda
Sto generando una ricevuta e sto usando l'oggetto Graphics per chiamare il metodo DrawString per stampare il testo richiesto.
graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);
Funziona benissimo per quello che mi serviva. Ho sempre saputo cosa stavo stampando, quindi potevo tagliare manualmente tutte le stringhe in modo che si adattassero correttamente alla carta per ricevute da 80 mm. Quindi ho dovuto aggiungere un po 'di funzionalità in più per renderlo più flessibile. L'utente potrebbe passare stringhe che verrebbero aggiunte in fondo.
Dato che non sapevo cosa avrebbero messo, ho appena creato la mia funzione di a capo automatico che include un numero di caratteri da avvolgere e la stringa stessa. Per scoprire il numero di personaggi, stavo facendo qualcosa del genere:
float width = document.DefaultPageSettings.PrintableArea.Width;
int max = (int)(width / graphics.MeasureString("a", font).Width);
Ora la larghezza mi sta restituendo 283, che in mm è di circa 72, il che ha senso quando si tengono conto dei margini su carta da 80 mm.
Ma il metodo MeasureString sta restituendo 10.5 su un font Courier New 8pt. Quindi, invece di aggirare ciò che mi aspettavo fosse 36 - 40, ne ottengo 26, con il risultato che 2 righe di testo vengono trasformate in 3-4.
Le unità per PrintableArea.Width sono 1/100 di pollice e PageUnit per l'oggetto grafico è Display (che indica in genere 1 / 100esimo di pollice per le stampanti). Quindi, perché sto solo tornando 26?
Soluzione
Da WindowsClient.net:
GDI + aggiunge una piccola quantità (1/6 em) a ciascuna estremità di ogni stringa visualizzata. Questo 1/6 em consente di glifi con estremità sporgenti (come il corsivo ' f ') e offre inoltre a GDI + un piccolo margine di manovra per aiutare con l'espansione della griglia.
L'azione predefinita di
DrawString
funzionerà contro di te nella visualizzazione di percorsi adiacenti:
- Innanzitutto StringFormat predefinito aggiunge un ulteriore 1/6 em a ciascuna estremità di ogni output;
- In secondo luogo, quando le larghezze adattate alla griglia sono inferiori a quelle progettate, la stringa può contrarsi fino a un massimo.
Per evitare questi problemi:
- Passa sempre
MeasureString
eDrawString
un StringFormat basato sul StringFormat tipografico (GenericTypographic
).
ImpostareTextRenderingHint
suTextRenderingHintAntiAlias ??
. Questo metodo di rendering utilizza l'anti-aliasing e il posizionamento dei glifi sub-pixel per evitare la necessità di adattamento alla griglia ed è quindi intrinsecamente indipendente dalla risoluzione.
Esistono due modi per disegnare il testo in .NET:
- GDI + (
graphics.MeasureString
egraphics.DrawString
) - GDI (
TextRenderer.MeasureText
eTextRenderer.DrawText
)
Dall'eccellente blog (rip) di Michael Kaplan Ordinando tutto , in .NET 1.1 tutto usato GDI + per il rendering del testo. Ma c'erano alcuni problemi:
- Ci sono alcuni problemi di prestazioni causati dalla natura alquanto apolide di GDI +, in cui verranno impostati i contesti del dispositivo e ripristinato l'originale dopo ogni chiamata.
- I motori di modellazione per il testo internazionale sono stati aggiornati più volte per Windows / Uniscribe e per Avalon (Windows Presentation Foundation), ma non sono stati aggiornati per GDI +, il che fa sì che il supporto del rendering internazionale per nuove lingue non abbia lo stesso livello di qualità.
Quindi sapevano che volevano cambiare il framework .NET per smettere di usare il sistema di rendering del testo di GDI + e usare GDI . Inizialmente speravano di poter semplicemente cambiare:
graphics.DrawString
per chiamare la vecchia API DrawText
invece di GDI +. Ma non sono stati in grado di far corrispondere la disposizione del testo e la spaziatura esattamente come ha fatto GDI +. Quindi furono costretti a mantenere graphics.DrawString
per chiamare GDI + (ragioni di compatibilità; le persone che chiamavano graphics.DrawString
avrebbero improvvisamente scoperto che il loro testo non andava a finire come abituato a).
È stata creata una nuova classe statica TextRenderer
per avvolgere il rendering del testo GDI. Ha due metodi:
TextRenderer.MeasureText
TextRenderer.DrawText
Nota:
TextRenderer
è un wrapper per GDI, mentregraphics.DrawString
è ancora un wrapper per GDI +.
Quindi c'era il problema di cosa fare con tutti i controlli .NET esistenti, ad esempio:
-
Label
-
Pulsante
-
TextBox
Volevano cambiarli per usare TextRenderer
(cioè GDI), ma dovevano stare attenti. Potrebbero esserci persone che dipendono dai loro controlli disegnando come in .NET 1.1. E così è nato " rendering di testo compatibile " ;.
Per impostazione predefinita, i controlli nell'applicazione si comportano come in .NET 1.1 (sono " compatibili ").
disattivi la modalità di compatibilità chiamando:
Application.SetCompatibleTextRenderingDefault(false);
Questo rende la tua applicazione migliore, più veloce, con un migliore supporto internazionale. Per riassumere:
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
È anche utile annotare il mapping tra GDI + TextRenderingHint
e il corrispondente LOGFONT
Qualità utilizzato per il disegno di caratteri 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)
Campioni
Ecco alcuni confronti di GDI + (graphics.DrawString) contro il rendering di testo GDI (TextRenderer.DrawText):
GDI + : TextRenderingHintClearTypeGridFit
, GDI : CLEARTYPE_QUALITY
:
GDI + : TextRenderingHintAntiAlias ??
, GDI : ANTIALIASED_QUALITY
:
GDI + : TextRenderingHintAntiAliasGridFit
, GDI : non supportato, utilizza ANTIALIASED_QUALITY :
GDI + : TextRenderingHintSingleBitPerPixelGridFit
, GDI : PROOF_QUALITY
:
GDI + : TextRenderingHintSingleBitPerPixel
, GDI : DRAFT_QUALITY
:
Trovo strano che DRAFT_QUALITY
sia identico a PROOF_QUALITY
, che è identico a CLEARTYPE_QUALITY
.
Vedi anche
- UseCompatibleTextRendering - Compatibile con whaaaaaat?
- Ordinamento di tutto: una rapida occhiata a Whidbey's TextRenderer
- MSDN: Struttura LOGFONT
- AppCompat Guy: GDI vs. GDI + Prestazioni di rendering del testo
- GDI + Testo, indipendenza della risoluzione e metodi di rendering. Oppure - Perché il mio testo appare diverso in GDI + e in GDI?
Altri suggerimenti
Quando crei un carattere 'Courier New' con Size = 11 otterrai un output come nell'immagine sopra. Si vede che l'altezza è di 14 pixel senza includere la sottolineatura. La larghezza è esattamente 14 pixel (7 pixel per ogni personaggio).
Quindi questo carattere viene visualizzato con pixel 14x14.
Ma TextRenderer.MeasureText ()
restituisce invece una larghezza di 21 pixel. Se hai bisogno di valori esatti questo è inutile.
La soluzione è il seguente codice:
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 conterrà la dimensione corretta: 14x14
ATTENZIONE: Questo codice misura correttamente un carattere normale. Se hai bisogno dei valori esatti anche per i caratteri in corsivo (che hanno sempre una sporgenza a destra), dovresti leggere i link menzionati in questo articolo: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font
APPENDICE: Per coloro che non hanno mai usato le chiamate API in C # ecco un suggerimento su come creare la classe Win32. Questo non è completo Per maggiori dettagli dai un'occhiata a 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);
}
Ecco una spiegazione che può aiutarti a capire come funziona. e cosa causa gli spazi di più o meno prima e dopo ogni personaggio.