{"id":1270,"date":"2012-11-09T19:28:52","date_gmt":"2012-11-09T07:28:52","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=1270"},"modified":"2012-11-09T19:28:52","modified_gmt":"2012-11-09T07:28:52","slug":"text-metrics-font-styles-tcanvas-and-device-contexts","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/1270\/","title":{"rendered":"Text Metrics, Font Styles, TCanvas and Device Contexts"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>In putting some finishing polish on the GUI console of my <strong>Smoketest<\/strong> framework (see, I <em>am<\/em> working on it!) I ran into something that I remember I once knew and &#8211; seemingly &#8211; had forgotten, about mixing API level access to a GDI device context with the high-level <strong>TCanvas<\/strong> access that is conveniently provided for us.<br \/>\n<!--more--><br \/>\nFor a lot of basic drawing work, <strong>TCanvas<\/strong> is plenty good enough, but there are certain things that you cannot do with it, at least not directly.<\/p>\n<p>One of these is &#8211; or rather, used to be :<\/p>\n<ul>\n<li><em>Calculating the width of some text when rendered into some<br \/>\n     rectangle constrained to a <strong>maximum<\/strong> width, using ellipsis to fit if necessary.<\/em><\/li>\n<\/ul>\n<p>And other more complex text measurements other than just &#8220;how wide&#8221; or &#8220;how high&#8221; is this line of text.<\/p>\n<p>I say &#8220;<em>used to be<\/em>&#8220;, because you <em>can<\/em> now achieve this using <strong>TCanvas<\/strong>, as of at least Delphi 2010 (possibly <em>much<\/em> earlier &#8211; I haven&#8217;t checked), courtesy of additional overloads of the <strong>TextRect()<\/strong> method.  But previously to perform this calculation required that a call be made to the Windows GDI <strong>DrawTextEx()<\/strong> function.  Since <em>Smoketest<\/em> aims to remain compatible &#8211; at the core framework implementation level &#8211; with <strong>Delphi 7<\/strong>, this rules out the use of the new overload and demands the use of the Windows API.<\/p>\n<p>In any case, the point of this post is about the general problem of mixing certain GDI and <strong>TCanvas<\/strong> operations, not about solving that particular, specific problem.  I only use it as an example.<\/p>\n<p>So, first of all, let&#8217;s see the symptoms of the problem.<\/p>\n<p>The issue arose in my case with a simple calculation of the width of a label which was then used to determine the left edge position of a following piece of text.  This is most clearly seen in the &#8220;Inspections&#8221; output of the self-test suite:<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.20.28-.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.20.28-.png?resize=402%2C229&#038;ssl=1\" alt=\"\" title=\"Inconsistent spacing between label and text\" width=\"402\" height=\"229\" class=\"aligncenter size-full wp-image-1271\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.20.28-.png?w=402&amp;ssl=1 402w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.20.28-.png?resize=300%2C170&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.20.28-.png?resize=150%2C85&amp;ssl=1 150w\" sizes=\"(max-width: 402px) 100vw, 402px\" data-recalc-dims=\"1\" \/><\/a><\/p>\n<p>The problem is the inconsistent spacing between the labels and the following value.  This spacing should be consistent and indeed, in the code, it is a simple absolute pixel adjustment applied to the <strong>TRect<\/strong> subsequently to be used for text value:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n    aCanvas.Font.Style := [fsBold];\r\n    DrawTextEx(dc, PChar(LabelText), Length(LabelText), LabelRect,\r\n               DT_CALCRECT or DT_WORD_ELLIPSIS or DT_NOPREFIX, NIL);\r\n    TextRect.Left := LabelRect.Right + 4;\r\n<\/pre>\n<p>Do you see the problem ?  Don&#8217;t worry, it isn&#8217;t immediately obvious.<\/p>\n<p>The <strong>dc<\/strong> parameter passed to <strong>DrawTextEx()<\/strong> is the <strong>HDC<\/strong> (device context handle) of the canvas we are performing the calculations for.  Since there are a large number of calculations being performed in this canvas, this handle is obtained and stored in a local variable for re-use, to avoid having to repeatedly use the <em>aCanvas.Handle<\/em> accessor. <\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  var\r\n    dc: HDC;\r\n  begin\r\n    :\r\n    dc := aCanvas.Handle;\r\n    :\r\n  end;\r\n<\/pre>\n<p>This however is the key to the problem.  Or at least a partner in the crime.  The accomplice is the fact that the label text we are calculating the width of is rendered in bold, requiring a change in the font properties.<\/p>\n<p>If we look at the implementation of <strong>TCanvas<\/strong> we find that this installed it&#8217;s own listener (handler if you prefer) on the <strong>OnChanged<\/strong> event of it&#8217;s <strong>Font<\/strong> object.  Changing the properties of the <strong>Font<\/strong> results in the following:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\nprocedure TCanvas.FontChanged(AFont: TObject);\r\nbegin\r\n  if csFontValid in State then\r\n  begin\r\n    Exclude(State, csFontValid);\r\n    SelectObject(FHandle, StockFont);\r\n  end;\r\nend;\r\n<\/pre>\n<p>Because the <strong>Font<\/strong> has changed, the canvas removes the <strong>csFontValid<\/strong> flag from it&#8217;s internal state.  But it also selects the <strong>StockFont<\/strong> into the device context.  i.e. having changed the properties of the <strong>Font<\/strong>, the device context is now using a completely different Font (unless the new font properties happen by coincidence to reflect those of <strong>StockFont<\/strong> itself of course)!<\/p>\n<p>If we were only using <strong>TCanvas<\/strong> methods, this would not be a problem.<\/p>\n<p>When we next access the <strong>TCanvas.Handle<\/strong> property, the canvas checks the internal state to make sure that everything is good to go.  If not, it takes whatever steps are necessary to rectify that.<\/p>\n<p>In the case of the <strong>Font<\/strong> properties having changed, the lack of <strong>csFontValid<\/strong> in the state will cause the canvas to create the required GDI font object and select it into the device context for us.<\/p>\n<p>But because I am specifically avoiding using that accessor, the device context is left with <strong>StockFont<\/strong> selected, which is absolutely not the font I expected, and in this case has quite different characteristics.  Which mean that when I then start measuring text, I get results that are not correct for the font I will subsequently use to actually render the text (the actual drawing code is able to use <strong>TCanvas<\/strong>, without having to mix-in GDI calls, so the problem only arises in calculations for the layout which that drawing code then relies on).<\/p>\n<p>The solution therefore is simply to ensure that after changing the <strong>Font<\/strong> properties, that I &#8220;touch&#8221; the <strong>TCanvas<\/strong> Handle property in order to ensure that the internal state of the device context is brought up-to-update:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n    aCanvas.Font.Style := [fsBold];\r\n    dc := aCanvas.Handle;  \/\/ Cause the changed font to be selected into the canvas\r\n     :\r\n    \/\/ Proceed to use dc ...\r\n<\/pre>\n<p>With all of the relevant areas of my layout code addressed, I now get the result I was aiming for.  Nice, consistent spacing:<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.22.30-.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.22.30-.png?resize=406%2C225&#038;ssl=1\" alt=\"\" title=\"Label spacing consistent as expected and required\" width=\"406\" height=\"225\" class=\"aligncenter size-full wp-image-1278\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.22.30-.png?w=406&amp;ssl=1 406w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.22.30-.png?resize=300%2C166&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2012-11-09-at-19.22.30-.png?resize=150%2C83&amp;ssl=1 150w\" sizes=\"(max-width: 406px) 100vw, 406px\" data-recalc-dims=\"1\" \/><\/a><\/p>\n<p>It&#8217;s worth noting that the problem isn&#8217;t that our cached HDC is no longer &#8220;valid&#8221;.  The value of <strong>Handle<\/strong> (<strong>dc<\/strong>) itself doesn&#8217;t actually change since the device context doesn&#8217;t need to be re-created, we simply need the <em>side-effects<\/em> that come from simply accessing the handle.<\/p>\n<p>Also worth noting is that this behaviour is also seen with the other GDI objects exposed through wrapper objects on the canvas &#8211; the <strong>Pen<\/strong> and the <strong>Brush<\/strong>.<\/p>\n<p>The final thing to mention is that the tempting looking <strong>TCanvas.Refresh()<\/strong> method is a bit of a red-herring, since it does the exact <em>opposite<\/em> of what you might expect &#8211; it DE-selects all the current <strong>Pen<\/strong>, <strong>Brush<\/strong> and <strong>Font<\/strong> objects and selects the GDI <strong>StockPen<\/strong>, <strong>StockBrush<\/strong> and <strong>StockFont<\/strong>, marking all these objects as invalid so that they ALL get reselected when Handle is accessed.<\/p>\n<p><strong>Refresh()<\/strong> should perhaps really be called <strong>RefreshWhenINextAccessHandle()<\/strong>.<\/p>\n<h3>Footnote<\/h3>\n<p>There <em>is<\/em> a mechanism in <strong>TCanvas<\/strong> to enable you to explicitly request that a particular graphic object be selected into the device context &#8211; <strong>RequiredState()<\/strong>.  This is used all over the place in <strong>TCanvas<\/strong> itself to ensure that the objects required for any particular operation are up-to-date in the device context.<\/p>\n<p>Unfortunately this mechanism is a protected implementation detail, so if a well-behaved consumer of the class needs to trigger this mechanism, all they can do is rely on side-effects.  (When I say well-behaved, I mean if you don&#8217;t want to have to fiddle around with &#8220;cracker&#8221; classes or helpers etc to gain access to the protected members through a side-door)<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> In putting some finishing polish on the GUI console of my Smoketest framework (see, I am working on it!) I ran into something that I remember I once knew and &#8211; seemingly &#8211; had forgotten, about mixing API level access to a GDI device context with the high-level TCanvas access that is conveniently provided for [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[292,190,189,191],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-ku","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":586,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/586\/","url_meta":{"origin":1270,"position":0},"title":"Absolute (for) Beginners","date":"15 Oct 2009","format":false,"excerpt":"I casually suggested the use of the \"absolute\" keyword in response to a question on the NZ DUG mailing list today. \u00a0I thought nothing of it but someone mentioned that it had been years since he'd seen anyone use it, so I thought maybe it was worth bringing to wider\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2231,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2231\/","url_meta":{"origin":1270,"position":1},"title":"Did you get the Memo ?","date":"01 Aug 2014","format":false,"excerpt":"People looking for a cheap Android tablet have a new option from a respected player: The Asus Pad 7 Although not exactly falling over themselves in excitement (it is an entry level, budget device after all), reviewers are finding a lot to like in this device. But Delphi developers hoping\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2327,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2327\/","url_meta":{"origin":1270,"position":2},"title":"On The Shoulders of Giants&#8230;","date":"18 Dec 2014","format":false,"excerpt":"When discussing mobile device application development using Oxygene or other RemObjects Elements technologies, the question of user interface designers doesn't usually take long to come up (particularly with Delphi developers). Up to now the answer has always been Xcode Interface Builder for iOS\/OS X, Visual Studio WinForms\/WPF Designers for .Net\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1833,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1833\/","url_meta":{"origin":1270,"position":3},"title":"Importing an Android Class For Use in Delphi","date":"03 Oct 2013","format":false,"excerpt":"In a previous post I noted the absence of the BatteryManager class in the AndroidAPI.JNI units. This class contains some constants useful when reading battery information. I showed how to use a suitably massaged literal in place of these missing constants, but in response to observations from Paul and Brian\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1882,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1882\/","url_meta":{"origin":1270,"position":4},"title":"Not Your Grand-Daddy&#8217;s Pascal (or Java)","date":"15 Oct 2013","format":false,"excerpt":"I've mentioned some of the cool stuff in the Oxygene language in various posts and thought it would be a good idea to list them again, along with some others that I've not previously mentioned. Oxygene Everywhere First some of the core language features that are available on all supported\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":700,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/700\/","url_meta":{"origin":1270,"position":5},"title":"The case for case[]","date":"30 Nov 2010","format":false,"excerpt":"Eric Grange (resurrector of the increasingly interesting looking DWS project) recently posted about a new idea he has had for the DWS engine, which in turn gave me an idea, or rather, prompted me to come up with what I think may be a new spin on an old one.\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1270"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=1270"}],"version-history":[{"count":9,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1270\/revisions"}],"predecessor-version":[{"id":1281,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1270\/revisions\/1281"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=1270"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=1270"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=1270"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}