Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scaling nested RichText widgets for accessibility

I'm wrestling with how RichText handles nested spans that contain other RichText instances. It works fine as long as the user doesn't change the phone's default font size. But if they have changed the display font size, things start going awry.

Consider the case where I'm parsing some HTML that looks like:

<underline><italic><bold>xxxxx</bold></italic></underline>

Conceptually this can be modelled as a TextSpan inside a TextSpan inside a TextSpan, so I might have some contrived code that looks like:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final inputText = 'The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.';
    final initialSpan = TextSpan(text: inputText, style: TextStyle(color: Colors.black));

    final boldText = _buildStyledElement(context, initialSpan, Style.bold);
    final boldItalised = _buildStyledElement(context, WidgetSpan(child: boldText), Style.italic);
    final boldItalisedUnderlined = _buildStyledElement(context, WidgetSpan(child: boldItalised), Style.underline);

    return new Scaffold(
      appBar: AppBar(title: Text('')),
      body: Column(
        children: [
          Text(inputText), // a simple baseline to compare
          boldText,
          boldItalised,
          boldItalisedUnderlined,
        ],
      ),
    );
  }

  RichText _buildStyledElement(BuildContext context, InlineSpan span, Style style) {
    // note we're not applying the style because it isn't important... the style parameter
    // is just a means of wrapping our heads around a real-world example of nesting
    return RichText(text: span);
  }
}

As you can see on the left, it looks great with normal font size but on the right, the three RichText cases don't scale when you adjust the phone's fonts size.

No scaling

That's easy to explain though... because the default scaling factor for a RichText is 1.0 so there's no scaling. Let's fix that by changing the creation code to look like:

return RichText(text: span, textScaleFactor: MediaQuery.of(context).textScaleFactor);

As you can see below, the normal font still looks good, but woah... things scale up real quickly due to the RichText nesting (I'm assuming it is applying the scale factor onto already scaled children).

Scaling based on device

Now, one thing I've found is that if I allow all the RichText widgets to be created using a scale factor of 1.0, and then wrap the outermost in a RichText that is using the device's scale factor, it /almost/ works. For example, remove the scaling in _buildStyledElement and add this row into the main Column widget:

RichText(text: WidgetSpan(child: boldItalisedUnderlined), textScaleFactor: MediaQuery.of(context).textScaleFactor),

As you can see, things look good at the normal font size and actually scale to the correct size for large fonts. However, the wrapping is now broken.

Just scaling the outer widget

I'm assuming that this is because the size that is calculated by the child RichText widgets (because they are using a scale factor of 1.0) doesn't marry up with the total calculated space when using the media query's scale factor.

Sooo... I guess my question is whether there is a way to actually get this to work properly with nested RichText instances. I know that for this specific example there may be some other options... but I'm talking about the general case where RichText widgets embed other RichText widgets.

Any thoughts would be much appreciated. Sorry for the long post.

like image 999
Craig Edwards Avatar asked Jun 04 '20 07:06

Craig Edwards


1 Answers

Regardless of not having a definite solution there's a workaround that I found particularly useful for my purpose:

You need to set the text scale factor for the Html Widget fixed to 1, and choose a default font size for your average text on the HTML the multiply it to the current scale factor of the screen (not the fixed manually).

class ExampleWidget extends StatelessWidget {
  final String data;
  ExampleWidget({this.data});
  @override
  Widget build(BuildContext context) {
    double scaleFactor = MediaQuery.of(context).textScaleFactor;
    int standardFontSize = 14;
    int h3FontSize = 18;
    return MediaQuery(
      data: MediaQueryData(textScaleFactor: 1),
      child: Html(
        data: data,
        style: {
          'body': Style(fontSize: FontSize(standardFontSize * scaleFactor)),
          'h3': Style(fontSize: FontSize(h3FontSize * scaleFactor))
          // other header fonts...
        },
      ),
    );
  }
}

The downside is needing to manually implement every time you need a new font size but it might solve an urgent problem.

like image 64
Aylan Boscarino Avatar answered Oct 28 '22 14:10

Aylan Boscarino