@@ -29,12 +29,16 @@ public class STTextBoxGDIPRender : ISTTextBoxRender, IDisposable |
29 | 29 | private SolidBrush m_brush; |
30 | 30 | private Graphics m_g_onpaint; |
31 | 31 | private Graphics m_g_private; |
| 32 | + private Bitmap m_bmp_measure; |
| 33 | + private Graphics m_g_measure; |
32 | 34 | private float m_italic_width; |
33 | 35 |
|
34 | 36 | private Rectangle m_rect_temp; |
35 | 37 | private Rectangle m_rect_line; |
36 | 38 | 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]; |
38 | 42 |
|
39 | 43 | private Control m_ctrl; |
40 | 44 | private int m_nCounter; |
@@ -57,20 +61,31 @@ public void BindControl(Control ctrl) { |
57 | 61 | } |
58 | 62 | m_ctrl = ctrl; |
59 | 63 | m_ctrl.FontChanged += m_ctrl_FontChanged; |
| 64 | + this.InitMeasureGraphics(); |
60 | 65 | this.InitData(m_ctrl.Font); |
61 | 66 | } |
62 | 67 |
|
63 | 68 | 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 | + } |
65 | 81 | } |
66 | 82 |
|
67 | 83 | 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; |
74 | 89 | m_g_onpaint = g; |
75 | 90 | } |
76 | 91 |
|
@@ -79,20 +94,37 @@ public void OnEndPaint(Graphics g) { |
79 | 94 | } |
80 | 95 |
|
81 | 96 | 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(); |
84 | 99 | } |
85 | | - var g = m_g_private; |
86 | | - this.XDPIZoom = g.DpiX / 96; |
87 | | - this.YDPIZoom = g.DpiY / 96; |
88 | 100 | m_nCounter++; |
89 | 101 | } |
90 | 102 |
|
91 | 103 | 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; |
95 | 111 | } |
| 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; |
96 | 128 | } |
97 | 129 |
|
98 | 130 | public int SetTabSize(int nSize) { |
@@ -158,13 +190,29 @@ public float GetStringWidthFloat(string strText, TextStyle style, float fLeftWid |
158 | 190 | if (strText == "\t") { |
159 | 191 | return this.GetTabWidthFloat(fLeftWidth); |
160 | 192 | } |
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; |
165 | 212 | if ((ft.Style & FontStyle.Italic) == FontStyle.Italic) { |
166 | 213 | fWidth += m_italic_width; |
167 | 214 | } |
| 215 | + m_dic_width_cache_float[strKey] = fWidth; |
168 | 216 | return fWidth; |
169 | 217 | } |
170 | 218 |
|
@@ -188,7 +236,9 @@ public void DrawString(string strText, TextStyle style, RectangleF rect) { |
188 | 236 | g.FillRectangle(m_brush, rect.X, rect.Bottom - m_rect_line.Height, rect.Width, (float)m_rect_line.Height); |
189 | 237 | } |
190 | 238 | 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 | + } |
192 | 242 | if (strText.Length == 1) { |
193 | 243 | Rectangle intRect = Rectangle.Round(rect); |
194 | 244 | switch (strText[0]) { |
@@ -204,7 +254,8 @@ public void DrawString(string strText, TextStyle style, RectangleF rect) { |
204 | 254 | return; |
205 | 255 | } |
206 | 256 | } |
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); |
208 | 259 | } |
209 | 260 | if ((style.FontStyle & FontStyle.Strikeout) == FontStyle.Strikeout && style.StrikeOutColor.A > 0) { |
210 | 261 | m_brush.Color = style.StrikeOutColor; |
@@ -299,7 +350,9 @@ private void InitData(Font ft) { |
299 | 350 | if (m_ft_normal == ft) { |
300 | 351 | return; |
301 | 352 | } |
302 | | - this.BeginPaint(); |
| 353 | + if (m_g_measure == null && m_ctrl != null) { |
| 354 | + this.InitMeasureGraphics(); |
| 355 | + } |
303 | 356 | m_rect_line.Height = (int)Math.Round(this.YDPIZoom * 1); |
304 | 357 | if (m_rect_line.Height == 0) m_rect_line.Height = 1; |
305 | 358 | m_ft_normal = ft; |
@@ -311,67 +364,85 @@ private void InitData(Font ft) { |
311 | 364 | m_ft_bold_italic = new Font(ft, FontStyle.Bold | FontStyle.Italic); |
312 | 365 | this.FontHeight = ft.Height; |
313 | 366 | m_dic_width_cache.Clear(); |
| 367 | + m_dic_width_cache_float.Clear(); |
| 368 | + m_measure_cache.Clear(); |
314 | 369 |
|
315 | 370 | Graphics g = this.GetGraphics(); |
316 | 371 | this.CharWidthFloat = g.MeasureString("W", ft, 10000, m_sf).Width; |
317 | 372 |
|
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 | | - |
326 | 373 | float fIWidth = g.MeasureString("i", ft, 10000, m_sf).Width; |
327 | 374 | this.IsMonospaced = Math.Abs(fIWidth - this.CharWidthFloat) < 0.001f; |
328 | 375 | if (!this.IsMonospaced) { |
329 | 376 | m_italic_width = (float)(this.FontHeight * Math.Tan(12 * Math.PI / 180)); |
330 | 377 | } else { |
331 | 378 | m_italic_width = 0; |
332 | 379 | } |
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; |
334 | 401 | } |
335 | 402 |
|
336 | 403 | private Graphics GetGraphics() { |
337 | 404 | 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; |
349 | 409 | } |
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."); |
351 | 412 | } |
352 | 413 |
|
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; |
362 | 420 | return m_ft_normal; |
363 | 421 | } |
364 | 422 |
|
365 | 423 | // [interface] ========================================== |
366 | 424 |
|
367 | 425 | 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 | + } |
369 | 438 | if (m_ft_bold != null) m_ft_bold.Dispose(); |
370 | 439 | if (m_ft_italic != null) m_ft_italic.Dispose(); |
371 | 440 | if (m_ft_bold_italic != null) m_ft_bold_italic.Dispose(); |
372 | 441 | if (m_brush != null) m_brush.Dispose(); |
373 | 442 | if (m_sf != null) m_sf.Dispose(); |
374 | 443 | 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(); |
375 | 446 | } |
376 | 447 | } |
377 | 448 | } |