GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Optimize GDI+ text measurement and rendering performance · DebugST/STTextBox@b323ed4

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • ST.Library.UI.STTextBox/Implementation
    • STTextBoxGDIPRender.cs
    • STTextBoxGDIRender.cs
    • TextView
      • QuickTextView.cs
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎ST.Library.UI.STTextBox/Implementation/STTextBoxGDIPRender.cs‎

Copy file name to clipboardExpand all lines: ST.Library.UI.STTextBox/Implementation/STTextBoxGDIPRender.cs
+125-54Lines changed: 125 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ public class STTextBoxGDIPRender : ISTTextBoxRender, IDisposable
2929
private SolidBrush m_brush;
3030
private Graphics m_g_onpaint;
3131
private Graphics m_g_private;
32+
private Bitmap m_bmp_measure;
33+
private Graphics m_g_measure;
3234
private float m_italic_width;
3335

3436
private Rectangle m_rect_temp;
3537
private Rectangle m_rect_line;
3638
private Dictionary<string, int> m_dic_width_cache;
37-
39+
private readonly Dictionary<char, float> m_measure_cache = new Dictionary<char, float>();
40+
private readonly Dictionary<string, float> m_dic_width_cache_float = new Dictionary<string, float>();
41+
private readonly float[] m_ascii_widths = new float[128];
3842

3943
private Control m_ctrl;
4044
private int m_nCounter;
@@ -57,20 +61,31 @@ public void BindControl(Control ctrl) {
5761
}
5862
m_ctrl = ctrl;
5963
m_ctrl.FontChanged += m_ctrl_FontChanged;
64+
this.InitMeasureGraphics();
6065
this.InitData(m_ctrl.Font);
6166
}
6267

6368
public void UnbindControl() {
64-
m_ctrl.FontChanged -= m_ctrl_FontChanged;
69+
if (m_ctrl != null) {
70+
m_ctrl.FontChanged -= m_ctrl_FontChanged;
71+
m_ctrl = null;
72+
}
73+
if (m_g_measure != null) {
74+
m_g_measure.Dispose();
75+
m_g_measure = null;
76+
}
77+
if (m_bmp_measure != null) {
78+
m_bmp_measure.Dispose();
79+
m_bmp_measure = null;
80+
}
6581
}
6682

6783
public void OnBeginPaint(Graphics g) {
68-
//m_g_onpaint = g;
69-
//m_b_onpaint = true;
70-
this.XDPIZoom = g.DpiX / 96;
71-
this.YDPIZoom = g.DpiY / 96;
72-
if (this.XDPIZoom < 1) this.XDPIZoom = 1;
73-
if (this.YDPIZoom < 1) this.YDPIZoom = 1;
84+
this.XDPIZoom = g.DpiX / 96f;
85+
this.YDPIZoom = g.DpiY / 96f;
86+
if (this.XDPIZoom < 1f) this.XDPIZoom = 1f;
87+
if (this.YDPIZoom < 1f) this.YDPIZoom = 1f;
88+
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
7489
m_g_onpaint = g;
7590
}
7691

@@ -79,20 +94,37 @@ public void OnEndPaint(Graphics g) {
7994
}
8095

8196
public void BeginPaint() {
82-
if (m_nCounter == 0) {
83-
m_g_private = m_ctrl.CreateGraphics();
97+
if (m_g_measure == null && m_ctrl != null) {
98+
this.InitMeasureGraphics();
8499
}
85-
var g = m_g_private;
86-
this.XDPIZoom = g.DpiX / 96;
87-
this.YDPIZoom = g.DpiY / 96;
88100
m_nCounter++;
89101
}
90102

91103
public void EndPaint() {
92-
if (--m_nCounter == 0) {
93-
m_g_private.Dispose();
94-
m_g_private = null;
104+
m_nCounter--;
105+
}
106+
107+
private void InitMeasureGraphics() {
108+
if (m_g_measure != null) {
109+
m_g_measure.Dispose();
110+
m_g_measure = null;
95111
}
112+
if (m_bmp_measure != null) {
113+
m_bmp_measure.Dispose();
114+
m_bmp_measure = null;
115+
}
116+
m_bmp_measure = new Bitmap(1, 1);
117+
if (m_ctrl != null) {
118+
using (Graphics g = m_ctrl.CreateGraphics()) {
119+
m_bmp_measure.SetResolution(g.DpiX, g.DpiY);
120+
this.XDPIZoom = g.DpiX / 96f;
121+
this.YDPIZoom = g.DpiY / 96f;
122+
if (this.XDPIZoom < 1f) this.XDPIZoom = 1f;
123+
if (this.YDPIZoom < 1f) this.YDPIZoom = 1f;
124+
}
125+
}
126+
m_g_measure = Graphics.FromImage(m_bmp_measure);
127+
m_g_measure.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
96128
}
97129

98130
public int SetTabSize(int nSize) {
@@ -158,13 +190,29 @@ public float GetStringWidthFloat(string strText, TextStyle style, float fLeftWid
158190
if (strText == "\t") {
159191
return this.GetTabWidthFloat(fLeftWidth);
160192
}
161-
Graphics g = this.GetGraphics();
162-
string strKey = this.GetFontFlag(style);
163-
Font ft = this.GetFontFromFlag(strKey);
164-
float fWidth = g.MeasureString(strText, ft, 10000, m_sf).Width;
193+
bool isRegular = (style.FontStyle == FontStyle.Regular || style.FontStyle == 0);
194+
if (strText.Length == 1 && isRegular) {
195+
char ch = strText[0];
196+
if (ch < 128) {
197+
return m_ascii_widths[ch];
198+
}
199+
if (m_measure_cache.TryGetValue(ch, out float cw)) return cw;
200+
Graphics g = this.GetGraphics();
201+
cw = g.MeasureString(strText, m_ft_normal, 10000, m_sf).Width;
202+
m_measure_cache[ch] = cw;
203+
return cw;
204+
}
205+
string strKey = ((int)style.FontStyle).ToString() + ":" + strText;
206+
if (m_dic_width_cache_float.TryGetValue(strKey, out float w)) {
207+
return w;
208+
}
209+
Graphics g2 = this.GetGraphics();
210+
Font ft = this.GetFont(style.FontStyle);
211+
float fWidth = g2.MeasureString(strText, ft, 10000, m_sf).Width;
165212
if ((ft.Style & FontStyle.Italic) == FontStyle.Italic) {
166213
fWidth += m_italic_width;
167214
}
215+
m_dic_width_cache_float[strKey] = fWidth;
168216
return fWidth;
169217
}
170218

@@ -188,7 +236,9 @@ public void DrawString(string strText, TextStyle style, RectangleF rect) {
188236
g.FillRectangle(m_brush, rect.X, rect.Bottom - m_rect_line.Height, rect.Width, (float)m_rect_line.Height);
189237
}
190238
if (style.ForeColor.A > 0) {
191-
m_brush.Color = style.ForeColor;
239+
if (m_brush.Color != style.ForeColor) {
240+
m_brush.Color = style.ForeColor;
241+
}
192242
if (strText.Length == 1) {
193243
Rectangle intRect = Rectangle.Round(rect);
194244
switch (strText[0]) {
@@ -204,7 +254,8 @@ public void DrawString(string strText, TextStyle style, RectangleF rect) {
204254
return;
205255
}
206256
}
207-
g.DrawString(strText, this.GetFontFromFlag(this.GetFontFlag(style)), m_brush, rect, m_sf);
257+
Font ft = this.GetFont(style.FontStyle);
258+
g.DrawString(strText, ft, m_brush, rect, m_sf);
208259
}
209260
if ((style.FontStyle & FontStyle.Strikeout) == FontStyle.Strikeout && style.StrikeOutColor.A > 0) {
210261
m_brush.Color = style.StrikeOutColor;
@@ -299,7 +350,9 @@ private void InitData(Font ft) {
299350
if (m_ft_normal == ft) {
300351
return;
301352
}
302-
this.BeginPaint();
353+
if (m_g_measure == null && m_ctrl != null) {
354+
this.InitMeasureGraphics();
355+
}
303356
m_rect_line.Height = (int)Math.Round(this.YDPIZoom * 1);
304357
if (m_rect_line.Height == 0) m_rect_line.Height = 1;
305358
m_ft_normal = ft;
@@ -311,67 +364,85 @@ private void InitData(Font ft) {
311364
m_ft_bold_italic = new Font(ft, FontStyle.Bold | FontStyle.Italic);
312365
this.FontHeight = ft.Height;
313366
m_dic_width_cache.Clear();
367+
m_dic_width_cache_float.Clear();
368+
m_measure_cache.Clear();
314369

315370
Graphics g = this.GetGraphics();
316371
this.CharWidthFloat = g.MeasureString("W", ft, 10000, m_sf).Width;
317372

318-
//SPACE are used to calculate TAB width.
319-
int nWidth = this.GetStringWidth(" ", TextStyle.Empty, 0);
320-
m_dic_width_cache.Add(" ", nWidth);
321-
m_dic_width_cache.Add("\r", nWidth);
322-
m_dic_width_cache.Add("\n", nWidth);
323-
m_dic_width_cache.Add("\r\n", nWidth);
324-
this.SpaceWidth = nWidth;
325-
326373
float fIWidth = g.MeasureString("i", ft, 10000, m_sf).Width;
327374
this.IsMonospaced = Math.Abs(fIWidth - this.CharWidthFloat) < 0.001f;
328375
if (!this.IsMonospaced) {
329376
m_italic_width = (float)(this.FontHeight * Math.Tan(12 * Math.PI / 180));
330377
} else {
331378
m_italic_width = 0;
332379
}
333-
this.EndPaint();
380+
381+
// Pre-calculate ASCII lookup table for sub-nanosecond character measurement
382+
Array.Clear(m_ascii_widths, 0, m_ascii_widths.Length);
383+
for (char ch = ' '; ch <= '~'; ch++) {
384+
if (this.IsMonospaced) {
385+
m_ascii_widths[ch] = this.CharWidthFloat;
386+
} else {
387+
m_ascii_widths[ch] = g.MeasureString(ch.ToString(), ft, 10000, m_sf).Width;
388+
}
389+
}
390+
m_ascii_widths['\r'] = 0f;
391+
m_ascii_widths['\n'] = 0f;
392+
393+
// SPACE are used to calculate TAB width.
394+
int nWidth = (int)Math.Ceiling(m_ascii_widths[' ']);
395+
if (nWidth <= 0) nWidth = 8;
396+
m_dic_width_cache.Add(" ", nWidth);
397+
m_dic_width_cache.Add("\r", 0);
398+
m_dic_width_cache.Add("\n", 0);
399+
m_dic_width_cache.Add("\r\n", 0);
400+
this.SpaceWidth = nWidth;
334401
}
335402

336403
private Graphics GetGraphics() {
337404
if (m_g_onpaint != null) return m_g_onpaint;
338-
if (m_g_private != null) return m_g_private;
339-
throw new InvalidOperationException("The Graphics is null, please call BeginPaint() to init it.");
340-
}
341-
342-
private string GetFontFlag(TextStyle style) {
343-
string strKey = "N";
344-
if ((style.FontStyle & FontStyle.Bold) == FontStyle.Bold) {
345-
strKey += "B";
346-
}
347-
if ((style.FontStyle & FontStyle.Italic) == FontStyle.Italic) {
348-
strKey += "I";
405+
if (m_g_measure != null) return m_g_measure;
406+
if (m_ctrl != null) {
407+
this.InitMeasureGraphics();
408+
if (m_g_measure != null) return m_g_measure;
349409
}
350-
return strKey;
410+
if (m_g_private != null) return m_g_private;
411+
throw new InvalidOperationException("The Graphics is null, please call BeginPaint() or bind a control to initialize it.");
351412
}
352413

353-
private Font GetFontFromFlag(string strFlag) {
354-
switch (strFlag) {
355-
case "NBI":
356-
return m_ft_bold_italic;
357-
case "NB":
358-
return m_ft_bold;
359-
case "NI":
360-
return m_ft_italic;
361-
}
414+
private Font GetFont(FontStyle style) {
415+
bool bold = (style & FontStyle.Bold) != 0;
416+
bool italic = (style & FontStyle.Italic) != 0;
417+
if (bold && italic) return m_ft_bold_italic;
418+
if (bold) return m_ft_bold;
419+
if (italic) return m_ft_italic;
362420
return m_ft_normal;
363421
}
364422

365423
// [interface] ==========================================
366424

367425
public void Dispose() {
368-
if (m_g_private != null) m_g_private.Dispose();
426+
if (m_g_measure != null) {
427+
m_g_measure.Dispose();
428+
m_g_measure = null;
429+
}
430+
if (m_bmp_measure != null) {
431+
m_bmp_measure.Dispose();
432+
m_bmp_measure = null;
433+
}
434+
if (m_g_private != null) {
435+
m_g_private.Dispose();
436+
m_g_private = null;
437+
}
369438
if (m_ft_bold != null) m_ft_bold.Dispose();
370439
if (m_ft_italic != null) m_ft_italic.Dispose();
371440
if (m_ft_bold_italic != null) m_ft_bold_italic.Dispose();
372441
if (m_brush != null) m_brush.Dispose();
373442
if (m_sf != null) m_sf.Dispose();
374443
if (m_dic_width_cache != null) m_dic_width_cache.Clear();
444+
if (m_dic_width_cache_float != null) m_dic_width_cache_float.Clear();
445+
if (m_measure_cache != null) m_measure_cache.Clear();
375446
}
376447
}
377448
}
Collapse file

‎ST.Library.UI.STTextBox/Implementation/STTextBoxGDIRender.cs‎

Copy file name to clipboardExpand all lines: ST.Library.UI.STTextBox/Implementation/STTextBoxGDIRender.cs
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class STTextBoxGDIRender : ISTTextBoxRender, IDisposable
3939

4040
private readonly Dictionary<char, int> m_measure_cache = new Dictionary<char, int>();
4141
private readonly Dictionary<string, int> m_dic_width_cache = new Dictionary<string, int>();
42+
private readonly int[] m_ascii_widths = new int[128];
4243

4344
public STTextBoxGDIRender() {
4445
this.TabSize = 4;
@@ -122,6 +123,7 @@ public int GetStringWidth(string strText, TextStyle style, int nLeftWidth) {
122123
// Single character fast cache
123124
if (strText.Length == 1 && (style.FontStyle == FontStyle.Regular || style.FontStyle == 0)) {
124125
char ch = strText[0];
126+
if (ch < 128) return m_ascii_widths[ch];
125127
if (m_measure_cache.TryGetValue(ch, out int w)) return w;
126128
w = MeasureChar(ch, m_ft_normal);
127129
m_measure_cache[ch] = w;
@@ -284,6 +286,14 @@ private void InitData(Font ft) {
284286
int iWidth = MeasureStringWidth("i", ft);
285287
int wWidth = MeasureStringWidth("W", ft);
286288
this.IsMonospaced = (iWidth == wWidth);
289+
290+
Array.Clear(m_ascii_widths, 0, m_ascii_widths.Length);
291+
for (char ch = ' '; ch <= '~'; ch++) {
292+
m_ascii_widths[ch] = this.IsMonospaced ? nWidth : MeasureStringWidth(ch.ToString(), ft);
293+
}
294+
m_ascii_widths['\r'] = 0;
295+
m_ascii_widths['\n'] = 0;
296+
287297
this.EndPaint();
288298
}
289299

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.