¿Por qué Graphics.MeasureString () devuelve un número superior al esperado?

Estoy generando un recibo y estoy usando el objeto Graphics para llamar al método DrawString e imprimir el texto requerido.

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

Esto funciona bien para lo que necesitaba hacer. Siempre supe lo que estaba imprimiendo, así que pude recortar manualmente cualquier cadena para que se ajustara correctamente al papel de recibo de 80 mm. Luego tuve que agregar un poco más de funcionalidad que lo haría más flexible. El usuario podría pasar cadenas que se agregarían a la parte inferior.

Como no sabía lo que iban a poner, acabo de crear mi propia función de ajuste de palabras que incluye varios caracteres para envolver y la cadena misma. Para averiguar el número de caracteres, estaba haciendo algo como esto:

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

Ahora el ancho me devuelve 283, que en mm es aproximadamente 72, lo que tiene sentido cuando cuentas los márgenes en papel de 80 mm.

Pero el método MeasureString está devolviendo 10.5 en una fuente Courier New 8pt. Entonces, en lugar de evitar lo que esperaba 36 – 40, obtengo 26, lo que resulta en 2 líneas de texto que se convierten en 3-4.

Las unidades para PrintableArea.Width son 1 / 100th de una pulgada, y la PageUnit para el objeto gráfico es Display (que dice que es típicamente 1 / 100th de una pulgada para impresoras). Entonces, ¿por qué solo estoy recuperando 26?

Desde WindowsClient.net:

GDI + agrega una pequeña cantidad (1/6 em) a cada extremo de cada cadena que se muestra. Este 1/6 em permite glifos con extremos salientes (como cursiva ‘ f ‘), y también le da a GDI + una pequeña cantidad de margen para ayudar con la expansión del ajuste de la grilla.

La acción predeterminada de DrawString funcionará en su contra al mostrar ejecuciones adyacentes:

  • En primer lugar, StringFormat por defecto agrega 1/6 em extra en cada extremo de cada salida;
  • En segundo lugar, cuando los anchos ajustados a la rejilla son menores que los diseñados, la cuerda puede contraerse hasta por una em.

Para evitar estos problemas:

  • Siempre pase MeasureString y DrawString a StringFormat en función del tipográfico StringFormat ( GenericTypographic ).
    Establezca Graphics TextRenderingHint en TextRenderingHintAntiAlias . Este método de renderizado utiliza posicionamiento anti-aliasing y glifo sub-pixel para evitar la necesidad de ajuste a la cuadrícula, y por lo tanto es independiente de la resolución intrínsecamente.

Hay dos formas de dibujar texto en .NET:

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

Del excelente blog de Michael Kaplan (rip) Sorting It All Out , en .NET 1.1 todo usó GDI + para la representación de texto. Pero hubo algunos problemas:

  • Hay algunos problemas de rendimiento causados ​​por la naturaleza algo apátrida de GDI +, donde los contextos del dispositivo se establecerían y luego se restauraría el original después de cada llamada.
  • Los motores de configuración para texto internacional se han actualizado muchas veces para Windows / Uniscribe y para Avalon (Windows Presentation Foundation), pero no se han actualizado para GDI +, lo que hace que el soporte de representación internacional para nuevos idiomas no tenga el mismo nivel de calidad.

Entonces sabían que querían cambiar el framework .NET para dejar de usar el sistema de renderizado de texto de GDI + y usar GDI . Al principio esperaban que simplemente pudieran cambiar:

 graphics.DrawString 

para llamar a la antigua API de DrawText lugar de GDI +. Pero no pudieron hacer que el ajuste y el ajuste del texto coincidan exactamente como lo hizo GDI +. Así que se vieron obligados a mantener los graphics.DrawString DrawString para llamar a GDI + (razones de compatibilidad; personas que llamaban a graphics.DrawString DrawString de repente se daría cuenta de que su texto no se ajustaba como solía hacerlo).

Se creó una nueva clase estática TextRenderer para envolver el renderizado de texto GDI. Tiene dos métodos:

 TextRenderer.MeasureText TextRenderer.DrawText 

Nota: TextRenderer es un envoltorio alrededor de GDI, mientras que graphics.DrawString sigue siendo un envoltorio alrededor de GDI +.


Luego estaba la cuestión de qué hacer con todos los controles .NET existentes, por ejemplo:

  • Label
  • Button
  • TextBox

Querían cambiarlos para usar TextRenderer (es decir, GDI), pero tenían que tener cuidado. Puede haber personas que dependen de que sus controles se dibujen como lo hicieron en .NET 1.1. Y así nació ” renderización de texto compatible “.

Por defecto, los controles en la aplicación se comportan como lo hicieron en .NET 1.1 (son ” compatibles “).

Desactiva el modo de compatibilidad llamando a:

 Application.SetCompatibleTextRenderingDefault(false); 

Esto hace que su aplicación sea mejor, más rápida y con mejor soporte internacional. Para resumir:

 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 

También es útil tener en cuenta la asignación entre GDI + TextRenderingHint y la calidad LOGFONT correspondiente utilizada para el dibujo de fonts 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 (egSystemDefault) DEFAULT_QUALITY (0) 

Muestras

Aquí hay algunas comparaciones de GDI + (graphics.DrawString) versos GDI (TextRenderer.DrawText) texto que rinde:

GDI + : TextRenderingHintClearTypeGridFit , GDI : CLEARTYPE_QUALITY :

enter image description here

GDI + : TextRenderingHintAntiAlias , GDI : ANTIALIASED_QUALITY :

enter image description here

GDI + : TextRenderingHintAntiAliasGridFit , GDI : no admitido, utiliza ANTIALIASED_QUALITY :

enter image description here

GDI + : TextRenderingHintSingleBitPerPixelGridFit , GDI : PROOF_QUALITY :

enter image description here

GDI + : TextRenderingHintSingleBitPerPixel , GDI : DRAFT_QUALITY :

enter image description here

DRAFT_QUALITY parece extraño que DRAFT_QUALITY sea ​​idéntico a PROOF_QUALITY , que es idéntico a CLEARTYPE_QUALITY .

Ver también

  • UseCompatibleTextRendering – Compatible con whaaaaaat?
  • Clasificando todo: una mirada rápida al TextRenderer de Whidbey
  • MSDN: Estructura de LOGFONT
  • AppCompat Guy: rendimiento de representación de texto GDI vs. GDI +
  • Texto GDI +, independencia de resolución y métodos de representación. O bien, ¿por qué mi texto se ve diferente en GDI + y en GDI?

Courier New Size 11

Cuando crea una Fuente ‘Courier New’ con Tamaño = 11 obtendrá un resultado como en la imagen de arriba. Verá que la altura es de 14 píxeles sin incluir el subrayado. El ancho es exactamente 14 píxeles (7 píxeles por cada personaje).

Así que esta fuente rinde 14×14 píxeles.

Pero TextRenderer.MeasureText() devuelve un ancho de 21 píxeles en su lugar. Si necesita valores exactos, esto es inútil.

La solución es el siguiente código:

 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 contendrá el tamaño correcto: 14×14

IMPORTANTE: Este código mide correctamente una fuente regular. Si necesita los valores exactos también para las fonts en cursiva (que siempre tienen un saliente a la derecha), debe leer los enlaces que se mencionan en este artículo: http://www.codeproject.com/Articles/14915/Width-of- text-in-italic-font

APÉNDICE: Para aquellos que nunca han usado llamadas API en C # aquí, una pista sobre cómo crear la clase Win32. Esto no está completo. Para más detalles, echa un vistazo 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); } 

Aquí hay una explicación que puede ayudarlo a entender cómo funciona. y qué causa los espacios de más o menos antes y después de cada personaje.

Aplicación GDI DrawString Configurator

La captura de pantalla