51 Stimmen

Warum liefert Graphics.MeasureString() eine höhere Zahl als erwartet?

Ich erstelle eine Quittung und verwende das Graphics-Objekt, um die DrawString-Methode aufzurufen und den erforderlichen Text auszudrucken.

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

Das funktioniert gut für das, was ich brauchte, um es zu tun. Ich wusste immer, was ich ausdrucken wollte, und konnte alle Fäden manuell kürzen, damit sie auf 80-mm-Bonpapier passten. Dann musste ich eine zusätzliche Funktion hinzufügen, um das Ganze flexibler zu gestalten. Der Benutzer konnte Zeichenketten eingeben, die am unteren Rand eingefügt wurden.

Da ich nicht wusste, was sie eingeben würden, habe ich einfach meine eigene Funktion für den Zeilenumbruch erstellt, die die Anzahl der umzubrechenden Zeichen und die Zeichenkette selbst enthält. Um die Anzahl der Zeichen herauszufinden, habe ich etwa so vorgegangen:

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

Die Breite gibt mir jetzt 283 an, was in mm etwa 72 entspricht, was sinnvoll ist, wenn man die Ränder auf 80 mm Papier berücksichtigt.

Aber die MeasureString-Methode gibt 10,5 auf eine Courier New 8pt Schriftart zurück. Anstatt also ungefähr das zu bekommen, was ich erwartet habe, nämlich 36 - 40, bekomme ich 26, was dazu führt, dass aus 2 Textzeilen 3-4 werden.

Die Einheiten für PrintableArea.Width sind 1/100 Zoll, und die PageUnit für das Grafikobjekt ist Display (was bei Druckern normalerweise 1/100 Zoll ist). Warum bekomme ich also nur 26 zurück?

169voto

Ian Boyd Punkte 232380

Von WindowsClient.net:

GDI+ fügt einen kleinen Betrag (1/6 em) an jedes Ende der angezeigten Zeichenfolge an. Diese 1/6 em ermöglichen es, Glyphen mit überhängenden Enden (z. B. kursive ' f '), und gibt GDI+ einen kleinen Spielraum, um die Erweiterung des Rasters zu unterstützen.

Die Standardaktion von DrawString bei der Anzeige von Nebenläufen gegen Sie arbeiten:

  • Erstens fügt das Standard-StringFormat ein zusätzliches 1/6 em an jedem Ende der Ausgabe hinzu;
  • Zweitens darf sich die Saite um bis zu einem em zusammenziehen, wenn die Spannweite des Rasters geringer ist als vorgesehen.

Um diese Probleme zu vermeiden:

  • Immer bestehen MeasureString et DrawString ein StringFormat, das auf dem typografischen StringFormat ( GenericTypographic ).
    Einstellen der Grafiken TextRenderingHint まで TextRenderingHintAntiAlias . Diese Rendering-Methode verwendet Anti-Aliasing und Sub-Pixel-Glyphen-Positionierung, um die Notwendigkeit einer Rasteranpassung zu vermeiden, und ist daher von Natur aus auflösungsunabhängig.

Es gibt zwei Möglichkeiten, Text in .NET zu zeichnen:

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

Aus Michael Kaplans (rip) ausgezeichnetem Blog Alles aussortieren In .NET 1.1 alles verwendet GDI+ für die Wiedergabe von Text. Aber es gab einige Probleme:

  • Es gibt einige Leistungsprobleme, die durch die etwas zustandslose Natur von GDI+ verursacht werden, bei der Gerätekontexte gesetzt und dann der ursprüngliche Zustand nach jedem Aufruf wiederhergestellt wird.
  • Die Shaping-Engines für internationalen Text wurden für Windows/Uniscribe und für Avalon (Windows Presentation Foundation) mehrfach aktualisiert, nicht aber für GDI+, was dazu führt, dass die internationale Rendering-Unterstützung für neue Sprachen nicht den gleichen Qualitätsstandard aufweist.

Sie wussten also, dass sie das .NET-Framework ändern wollten, um die Verwendung von GDI+ Textwiedergabesystem, und verwenden Sie GDI . Zunächst hofften sie, sie könnten sich einfach umziehen:

graphics.DrawString

um die alte DrawText API anstelle von GDI+. Aber sie konnten den Textumbruch und die Abstände nicht genau so gestalten, wie es GDI+ tat. Also waren sie gezwungen, die graphics.DrawString GDI+ aufzurufen (aus Gründen der Kompatibilität; Leute, die graphics.DrawString plötzlich feststellen, dass ihr Text nicht mehr so umbrochen wird wie früher).

Eine neue statische TextRenderer Klasse wurde erstellt, um GDI-Text-Rendering zu verpacken. Sie hat zwei Methoden:

TextRenderer.MeasureText
TextRenderer.DrawText

Anmerkung: TextRenderer ist ein Wrapper um GDI, während graphics.DrawString ist immer noch ein Wrapper um GDI+.


Dann war da noch die Frage, was man mit all den vorhandenen .NET-Steuerelementen machen sollte, z.B.:

  • Label
  • Button
  • TextBox

Sie wollten sie umstellen auf die Verwendung von TextRenderer (d.h. GDI), aber sie mussten vorsichtig sein. Es könnte Leute geben, die darauf angewiesen sind, dass ihre Steuerelemente wie in .NET 1.1 gezeichnet werden. Und so wurde " kompatible Textdarstellung ".

Standardmäßig verhalten sich Steuerelemente in Anwendungen wie in .NET 1.1 (sie sind " kompatibel ").

Sie abbiegen Kompatibilitätsmodus durch Aufruf:

Application.SetCompatibleTextRenderingDefault(false);

Dies macht Ihre Anwendung besser, schneller und mit besserer internationaler Unterstützung. Zusammengefasst:

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

Es ist auch nützlich, die Zuordnung zwischen GDI+ TextRenderingHint und die entsprechende LOGFONT Qualität für das Zeichnen von GDI-Schriften verwendet:

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)

Proben

Hier sind einige Vergleiche zwischen GDI+ (graphics.DrawString) und GDI (TextRenderer.DrawText) Textrendering:

GDI+ : TextRenderingHintClearTypeGridFit , GDI : CLEARTYPE_QUALITY :

enter image description here

GDI+ : TextRenderingHintAntiAlias , GDI : ANTIALIASED_QUALITY :

enter image description here

GDI+ : TextRenderingHintAntiAliasGridFit , GDI : nicht unterstützt, verwendet ANTIALIASED_QUALITY :

enter image description here

GDI+ : TextRenderingHintSingleBitPerPixelGridFit , GDI : PROOF_QUALITY :

enter image description here

GDI+ : TextRenderingHintSingleBitPerPixel , GDI : DRAFT_QUALITY :

enter image description here

Ich finde es merkwürdig, dass DRAFT_QUALITY ist identisch mit PROOF_QUALITY die identisch ist mit CLEARTYPE_QUALITY .

Siehe auch

9voto

Elmue Punkte 6302

Courier New Size 11

Wenn Sie eine Schriftart 'Courier New' mit Größe = 11 erstellen, erhalten Sie eine Ausgabe wie in der Abbildung oben. Sie sehen, dass die Höhe 14 Pixel beträgt, ohne die Unterstreichung. Die Breite beträgt genau 14 Pixel (7 Pixel für jedes Zeichen).

Diese Schriftart wird also mit 14x14 Pixeln dargestellt.

Aber TextRenderer.MeasureText() gibt stattdessen eine Breite von 21 Pixeln zurück. Wenn Sie genaue Werte benötigen, ist dies nutzlos.

Die Lösung ist der folgende Code:

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 wird die richtige Größe enthalten: 14x14

WICHTIG! Dieser Code misst korrekt eine normale Schriftart. Wenn Sie die genauen Werte auch für kursive Schriftarten (die immer einen Überhang auf der rechten Seite haben) benötigen, sollten Sie die Links lesen, die in diesem Artikel erwähnt werden: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font

APPENDIX: Für diejenigen, die noch nie API-Aufrufe in C# verwendet haben, hier ein Hinweis, wie man die Klasse Win32. Dies ist nicht vollständig. Für weitere Details schauen Sie bitte unter 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);
}

0voto

Cheva Punkte 211

Hier ist eine Erklärung, die Ihnen helfen kann zu verstehen, wie es funktioniert. und was die Gründe für die Leerzeichen von mehr oder weniger vor und nach jedem Zeichen sind.

GDI DrawString-Konfigurator-Anwendung

Bildschirmaufzeichnung

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X