0

I have a method in my cubit that captures a widget as an image and I am testing this method. Long story short this method calls _captureFromWidget (see below, the code is copied from package screenshot), which accepts the Widget and returns a Uint8List. (I am not testing that the package works correctly, I am testing that my method and its parameters work correctly).

The problem is, that in real app the widget is captured correctly, but in the test, the font of all the Text widgets is not rendered correctly, and boxes are shown instead of letters.

I know the reason of this, see here, and here.

I tried loading the font as they suggested:

in pubspec.yaml:

  assets:
    - assets/fonts/

and I have in the assets/fonts folder my font: AbrilFatface-Regular.ttf

in my test:

      final Future<ByteData> abrilFatFaceFontData = rootBundle.load('assets/fonts/AbrilFatface-Regular.ttf');
      final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData);
      await fontLoader.load();

     Text text = ...; // text that uses custom font from above
     cubit.captureWidget(...); // will invoke _captureFromWidget

but still, boxes are shown instead of letters in the captured image: enter image description here this is the result of calling the same methods with the same arguments from the real app: enter image description here

So how to provide the font to the test correctly?

Here is the code that captures the widget:

  /// [context] parameter is used to Inherit App Theme and MediaQuery data.
  Future<Uint8List> _captureFromWidget(
    widgets.Widget widget, {
    required Duration delay,
    double? pixelRatio,
    widgets.BuildContext? context,
  }) async {
    // Retry counter
    int retryCounter = 3;
    bool isDirty = false;

    widgets.Widget child = widget;

    if (context != null) {
      // Inherit Theme and MediaQuery of app
      child = widgets.InheritedTheme.captureAll(
        context,
        widgets.MediaQuery(data: widgets.MediaQuery.of(context), child: child),
      );
    }

    final RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();

    Size logicalSize = ui.window.physicalSize / ui.window.devicePixelRatio;
    Size imageSize = ui.window.physicalSize;

    assert(logicalSize.aspectRatio.toPrecision(5) == imageSize.aspectRatio.toPrecision(5));

    final RenderView renderView = RenderView(
      window: ui.window,
      child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
      configuration: ViewConfiguration(
        size: logicalSize,
        devicePixelRatio: pixelRatio ?? 1.0,
      ),
    );

    final PipelineOwner pipelineOwner = PipelineOwner();
    final widgets.BuildOwner buildOwner = widgets.BuildOwner(
        focusManager: widgets.FocusManager(),
        onBuildScheduled: () {
          ///
          ///current render is dirty, mark it.
          ///
          isDirty = true;
        });

    pipelineOwner.rootNode = renderView;
    renderView.prepareInitialFrame();

    final widgets.RenderObjectToWidgetElement<RenderBox> rootElement = widgets.RenderObjectToWidgetAdapter<RenderBox>(
        container: repaintBoundary,
        child: widgets.Directionality(
          textDirection: TextDirection.ltr,
          child: child,
        )).attachToRenderTree(
      buildOwner,
    );
    // Render Widget
    buildOwner.buildScope(
      rootElement,
    );
    buildOwner.finalizeTree();

    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();

    ui.Image? image;

    do {
      // Reset the dirty flag
      isDirty = false;

      image = await repaintBoundary.toImage(pixelRatio: pixelRatio ?? (imageSize.width / logicalSize.width));

      // This delay should increase with Widget tree Size
      await Future.delayed(delay);

      // Check does this require rebuild
      if (isDirty) {
        // Previous capture has been updated, re-render again.
        buildOwner.buildScope(
          rootElement,
        );
        buildOwner.finalizeTree();
        pipelineOwner.flushLayout();
        pipelineOwner.flushCompositingBits();
        pipelineOwner.flushPaint();
      }
      retryCounter--;
      //retry until capture is successful
    } while (isDirty && retryCounter >= 0);

    final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);

    return byteData!.buffer.asUint8List();
  }
HII
  • 3,420
  • 1
  • 14
  • 35

1 Answers1

1

Turns out I was doing everything correctly except one thing.

The line final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData); will determine the fontFamily name that must be used in the TextStyle of the Text element.

and since I was using AbrilFatFace as fontFamily, the test was not working.

So the solution is to use in the fontFamily, the same name passed to the FontLoader, since thats the name the font will be identified with.

HII
  • 3,420
  • 1
  • 14
  • 35