7 Replies Latest reply on Jan 3, 2014 9:11 AM by .:}x-=V!P=-x{:.

    Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?

    James22s22 Level 1

      1.  We need getLineMetrics to return correct values immediately after changing a TextField's width/height or any property that would affect the layout metrics, withouth having to alter other properties like setting the text to itself (p1.text = p1.text).  Currently, if you change the width of a text field to match the stage width for example, getLineMetrics will not return correct values until the next frame.... UNLESS you set the text property.

       

      2.  We also need some kind of "stage scaled" event in addition to the "stage resize" event (which only fires when stage scale mode is no_scale), because stage scaling affects the rendered size of device fonts so dramatically that we must call getLineMetrics again.  This is not the case for fonts antialiased for readability, since their size is relatively stable with scaling, as demonstrated by drawing a box around the first line once and then scaling the stage.

       

      So those are the problems.  The asterisk in the title of this post is there because it seem that TextField.getLineMetrics is accurate with device fonts, but I cannot take advantage of that accuracy without a way to detect when the player is scaled.  I can only confirm its accuracy at a 1:1 scale, since there is no way to recalculate the size of the line rectangle once the player is scaled, aside from setting a timer of some sort which is a real hack not to mention horribly inefficient with no way to detect when the stage has actually be scaled.

       

      I use device fonts because embedded fonts look terrible and blurred compared to device font rendering.  The "use device font" setting matches the appearance of text in web browsers exactly.  The only way to get embedded/advanced antialiased text in flash to approximate that of the device font look is to primarily set gridFitType to PIXEL instead of SUBPIXEL, and secondly set autokerning to true to fix problems caused by the PIXEL grid fit type.  That ensure strokes are fitted solidly to the nearest pixel, however it still lacks the "ClearType" rendering that device fonts use, which has notable color offset to improve appearance on LCD monitors, rather than the purely grayscale text that flash uses in its subpixel rendering.  Frankly, failure to use device fonts because of API issues, is the only reason why Flash sometimes doesn't look as good as HTML text and why people say text in Flash "looks blurry".  I'm tired of hearing it.  If the player simply dispatched an event when scaled and updated the metrics immediately when any property of the text field that would affect the metrics is changed, then we could all happily use device fonts and Flash text would look great.  As is stands, because of the two problems I mentioned in the opening paragraph, we're stuck dealing with these problems.
      FontComparison.png

       

       

       

      If you create two text fields named "p1" and "p2" for paragraph 1 and 2, populate them with an identical line of text and set one to "use device fonts" and the other to "antialias for readability", then use this code to draw boxes around the first line of text in each of them:

       

      import flash.text.TextField;

      import flash.text.TextLineMetrics;

      graphics.clear();

      drawBoxAroundLine( p1, 0 );

      drawBoxAroundLine( p2, 0 );

      function drawBoxAroundLine( tf:TextField, line_index:int ):void

      {

                var gutter:Number = 2;

                var tlm:TextLineMetrics = tf.getLineMetrics( line_index );

                graphics.lineStyle( 0, 0x0000ff );

                graphics.drawRect( tf.x + gutter, tf.y + gutter, tlm.width, tlm.height );

      }

       

      The box surrounding the line of text in the "use device fonts" box is way off at first.  Scaling the player demonstrates that the text width of the device font field fluctuates wildly, while the "antialias for readability" field scales with the originally drawn rectangle perfectly.  That much is fine, but again to clarify the problems I mentioned at the top of this post:

       

      Since the text width fluctuates wildly upon player resize, assuming that getLineMetrics actually works on device fonts (and that's an assumption at this point), you'd have to detect the player resize and redraw the text.  Unfortunately, Flash does not fire the player resize event unless the stage scale mode is set to NO_SCALE.  That's problem #1.  And if that's by design, then they should definitely add a SCALE event, because changes in player scale dramatically affect device font layout, which requires recalculation of text metrics.  It's a real issue for fluid layouts.

       

      The second problem is that even when handling the resize event, and for example setting the text field width's to match the Stage.stageWidth property, when the text line wraps, it's not updated until the next frame.  In other words, at the exact resize event that causes a word to wrap, calling getLineMetrics in this handler reports the previous line length before the last word on the line wrapped.  So it's delayed a frame.  The only way to get the correct metrics immediately is basically to set the text property to itself like "p1.text = p1.text".  That seems to force an update of the metrics.  Otherwise, it's delayed, and useles.  I wrote about this in an answer over a year ago, showing how sensitive the text field property order is: http://stackoverflow.com/a/9558597/88409

        • 1. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
          sinious Most Valuable Participant

          As you've noted several times, setting the .text property triggers the metrics engine to update. Similar in the component world to manually invalidating, forcing a redraw, a very common thing.

           

          While it's a workaround, is this actually causing your app any strain? A frame is typically a very short duration (even my mobile apps are 60fps). At worst case scenario, on a RESIZE you can simply wait a frame (1/60th of a second) to get the correct metrics on the TextField. Flash may actually trigger the metrics engine internally by re-assigning the text anyhow, who knows how it actually does it.

           

          Is this an application? You may use other scale modes and simply detect resizing using the container. For instance if it's a website, detect the browser window resizing and simply call a method in the SWF to alert it of the resizing. It can give you all the information on the current size of the SWF. If it's an AIR application or something without a container then you won't get that luxury (although check NativeWindowBoundsEvent). What is your target?

          • 2. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
            James22s22 Level 1

            As I've noted several times, setting the text property to its own current value should not be necessary to update the metrics, and in some subclasses of text field, setting a property to its own value is ignored as the property is not actually changing and processing such a change would cause unnecessary work which could impact application performance.  Metrics should be current upon calling getLineMetrics.  They are not.  That's the problem.

             

            From a programming perspective, having to set the text property (really "htmlText" to preserve formatting) to itself to update metrics is almost unmanagable, and doesn't even make sense considering "htmlText" is just one of a dozen properties and methods on a TextField that could invalidate the layout metrics (alignment, setTextFormat, width, height, antiAliasMode, type, etc.), and I would have to override every one of those properties so that I could set htmlText = htmlText.  Using such a subclass isn't even possible if I want to use the Flash IDE to add text fields to the stage.  I would have to iterate over the display list and replace all existing fields with my subclass, which also isn't a good workaround because there's no way to update any and all variable references that may have been made to those instances.

             

            Frome what I've read, the invalide+render event system is unreliable.  My layout framework is similar to that of Windows Forms, and performs layout immediately, with dozens of docking modes and uses suspend and resume layout calls for efficiently resizing multiple child objects in a component.  Certain calculations cannot be aggregated for a render event, because some containers are semi-reflexive, meaning they can expand to fit the child contents while also contraining the child size, depending on whether the contain was resized or the child component was resized, so as a matter of correctness the resizing calcultation must occur immediately when the child resizes, otherwise a top-down pass on the display hierarchy for resizing will not be sufficient.

             

            As far as waiting until the next frame, no that is not possible, as it will cause one frame to be completely wrong.  If I was dragging the browser window to resize it, it would look terrible as virtually every single frame during the resizing operation would be incorrect.  Also, in the case where a user clicks the maximize or restore button of the web browser, the resizing event will occur exactly once, so if the metrics are not correct when that occurs, there is no recalculation occuring on the next frame, it will just be wrong and sit there looking wrong indefinitely.

             

            In case it's not obvious by now, this is a web application.  It uses the NO_SCALE stage scaling option, so notification of the event is not actually an issue for me personally.  I was just pointing out that for anyone not using the NO_SCALE option, there is no event in Flash to detect player scale.  What you're suggesting is using a JavaScript event and using the ExternalInterface bridge to send a message, which there is no guarantee whether it will be processed in a timely matter by the player and introduces possible platform inconsistancies, depending on whether the browser has actually resized the Flash interface at that point or what state Flash is in when it tries to recalculate the size of the text.  The browser may send that event to flash before the player is actually resized, so it will be processing incorrect sizes and then resized after the fact.  That's not a good solution.  Flash needs a scale event in addition to a resize event.  I'm really surprised it doesn't have one.  At the very least, the existing resize event should be dispatched reguardless of the stage scale mode, rather than occuring exclusively in the NO_SCALE mode.

             

            Bottom line is that getLineMetrics needs to return correct values every time it is called, without having to set the "text" property immediately before calling it.  If such a requirement exists, which seems to be the case, then that needs documented in the getLineMetrics method.

            • 3. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
              sinious Most Valuable Participant

              I'm not disagreeing with you. Every visual method, property and event should update the metrics. As you've identified, it doesn't.

               

              On the "next frame" scenario, being a single frame behind and the users perception of the application accurately resizing the component is deeply in the land of personal preference. My a-typical user is not analyzing medical information that requires up to the nanosecond perfect rendering accuracy such as a scaling component that displays a MRI. Flash is not Win32. The TextField is clearly a small bit of code to graphically represent a traditional Input using the limits of it's own engine. If that representation is rendered one frame behind (1/30th or more typically today 1/60th of a second), that is completely acceptable.

               

              If you really want something more like Windows components you might want to consider Flex. I find going between visual c# with the VS IDE and components comparable at an IDE level to using the Flex IDE (if you use 4.6 and below), or if you switch to Apache (4.11) you'll just have to do it in code. In either case it's much more suited to complex, container based application programming. For example, here's the interface for a TextFlow on an Input component for Apache 4.11:

              http://flex.apache.org/asdoc/flashx/textLayout/formats/ITextLayoutFormat.html

               

              Short of rewriting for Flex, you should document the bug you found. I'll be happy to vote it up:

              http://bugbase.adobe.com

              • 4. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
                James22s22 Level 1

                You're mischaracterizing the problem.  This isn't a frame cycle issue, and it has nothing to do with differences between Flash and Win32.  And please don't suggest Flex, it's terrible.  That's why I built my own framework.  As for components using a invalidation, redraw cycle... those don't spill over into the next frame... the invalitions occur in one part of the cycle (e.g. during the scripting phase), and are resolved before the next invalidation cycle.  Also, Flash is written in Win32 on Windows, and like other game engines, there's nothing stopping any Win32 application from utilizing such a frame cycle for rendering.


                This has nothing to do with need up to the second nanosecond perfection and it's not something that can be fixed by allowing for a 1/30 second delay.. that's a serious mischaracterization of the problem.

                 

                The resize event occurs precisely once... once when the user resizes the window, once every time the player dispatches a resize event, once every time the size of a components parent container changes for whatever reason.  The metrics are either correct or they are not. If the values are not correct when getLineMetrics is called, then the screen will set there looking incorrect, not for a 30th of a second, but indefinitely... until the next resize event occurs, which will again be incorrect!


                Also, what  you're suggesting is that when the width of a textField changes, use some relatively elaborate code to delay the getLineMetrics-dependent calculations until the next frame.  First, you're assuming that a frame cycle will cause getLineMetrics to be updated.  That's not necessarily true, and secondly, why would I even resort to using expensive timers when setting "htmlText=htmlText" will force an update immediately.

                 

                The problem, again, is that because getLineMetrics doesn't resolve it's own internals as it should, and because the only way to force that resolution is to set the text to itself, I have to remember to do it for it.  Every single time the width of any text field changes.  And as I said, if I could subclass the TextField to correct such an error (and I already have, along with numerous others, via my TextFieldEx class), then I cannot use the Flash IDE to lay out text fields, because it only works with TextField instances, and not subclasses thereof.

                • 5. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
                  .:}x-=V!P=-x{:. Level 3

                  hey not sure if this will help but will updateAfterEvent work?

                   

                  event.updateAfterEvent();

                  • 6. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
                    James22s22 Level 1

                    Nah.  That triggers a render after a timer event, it's unrelated.  Besides, I could just set htmlText to itself, as I mentioned.  The problem is having to perform such an extra step before calling getLineMetrics, and that the extra step to ensure the metrics are updated isn't documented or obvious.

                    • 7. Re: Why is getLineMetrics inaccurate when using device fonts* or immediately after resizing a TextField?
                      .:}x-=V!P=-x{:. Level 3

                      cool ya one extra line of code dont hurt lol. cant beat em join em