flutterdart

Get the exact bounds of a RenderBox object to completely redact all rendered information


I am trying to redact information from a screenshot via drawing a black rectangle over the entirety of a given RenderBox object. However, during testing I noticed that a simple TextField object with an OutlineInputBorder will cause the labelText to "bleed" into the margins above the text box when focused.

Ideally I'm ultimately looking for a way to determine with certainty the exact bounds I'd need to redact on any RenderBox object, not just this specific type of text box. This is just a common / simple example I discovered.

not focused:

not focused

focused:

focused

When I try to get the coordinates and dimensions to draw the redaction rectangle over the entire element the label text inevitably bleeds outside the rectangle and becomes visible when focused:

enter image description here

I'm looking for a way to programmatically determine the actual bounds an element is rendered in so that I can be confident I am obscuring all of it. From what I can tell the amount an element bleeds over into padding / other elements is arbitrary and it's critical that edge cases don't result in unobscured info.

The only other avenue I can think of is to search the children of the text box object and find the label as a text element and try to redact that (assuming it's a separate element) but I didn't see any obvious child elements like that and I'd like to avoid exhaustively searching all child elements if possible.

Example text field:

Padding(
  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
  child: Redact( // custom class that signals should be redacted
    child: TextField(
      decoration: InputDecoration(
        border: OutlineInputBorder(),
        labelText: 'Redacted Input 1',
      ),
      autocorrect: false,
      enableSuggestions: false,
    ),
  ),
),

Code to determine dimensions. I tried using size and paintBounds but results were the same:

if (renderObject is RenderBox && renderObject.hasSize) {
  var position = renderObject.localToGlobal(Offset.zero);
  var objectWidth = renderObject.paintBounds.width;
  var objectHeight = renderObject.paintBounds.height;
  redactionRects.add(Rect.fromLTWH(
    position.dx,
    position.dy,
    objectWidth,
    objectHeight,
  ));
}

I also tried looking at parentData to try and deal with edge cases where the parent was a padding object specifically but that wasn't sufficient and reinforced my understanding that the amount elements can bleed over is somewhat arbitrary and requires a more thorough solution.

if (renderObject.parentData is BoxParentData) {
  final parentData = renderObject.parentData as BoxParentData;
  position -= parentData.offset;
  // assume offset is symmetric
  objectWidth += (parentData.offset.dx * 2);
  objectHeight += (parentData.offset.dy * 2);
}

Very slightly improved results:

enter image description here

Any help or suggestions greatly appreciated. I am somewhat new to Flutter!


Solution

  • So it turns out my initial intuition about going through the children was correct. Something similar to this where you iterate on the child elements, while perhaps not performant in some cases, offers much better guarantees about redacting all related info.

            // redact any descendant Rects that are not fully contained within the parent
            Queue<Element> redactedChildrenQueue = Queue.from([currentElement]);
            while (redactedChildrenQueue.isNotEmpty) {
              final redactedChild = redactedChildrenQueue.removeFirst();
              final childRect = _getGlobalElementRect(redactedChild);
    
              if (!childRect.isEmpty && childRect.intersect(elementRect) != childRect) {
                redactionRects.add(childRect);
              }
              redactedChild.visitChildElements((child) {
                redactedChildrenQueue.add(child);
              });
            }
    

    Result where label is redacted:

    enter image description here