Finding the exact bounds of text

9

I need to know the exact bounds a piece of text -- the equivalent of getTextBounds for Android. I realize this goes somewhat counter to Flutter's design, but I am using text in a non-traditional way (as if the text were, say, embedded into an artistic picture at a precise location and size).

I've tried three methods:

  • TextPainter's minIntrinsicWidth and height. Source below. This produces a bounding box with space on all sides:

    enter image description here

    I need the sides of that rectangle to be right up against the black pixels of the '8'. (In this particular case, width and maxIntrinsicWidth give the same value as minIntrinsicWidth; also, preferredLineHeight gives the same value as height.)

  • Paragraph's getBoxesForRange -- basically the same result as with TextPainter.

  • FittedBox -- also leaves spaces, no matter the BoxFit enum value. I used this answer as a starting point.

TextPainter code follows. (I realize this is inefficient; I don't care until I get the needed bounding box. I used Scene/Picture/etc in hopes of finding appropriate functionality in the lower levels.)

import 'dart:ui';
import 'dart:typed_data';
import 'package:flutter/painting.dart';

const black = Color(0xff000000);
const white = Color(0xffffffff);

final identityTransform = new Float64List(16)
  ..[0] = 1.0
  ..[5] = 1.0
  ..[10] = 1.0
  ..[15] = 1.0;

TextPainter createTextPainter() {
  return new TextPainter(
    text: new TextSpan(
      text: '8',
      style: new TextStyle(
        color: black,
        fontSize: 200.0,
        fontFamily: "Roboto",
      ),
    ),
    textDirection: TextDirection.ltr,
  )..layout();
}

void drawBackground(Canvas canvas, Rect screen) {
  canvas.drawRect(
      screen,
      new Paint()
        ..color = white
        ..style = PaintingStyle.fill);
}

Picture createPicture() {
  final recorder = new PictureRecorder();
  final screen = Offset.zero & window.physicalSize;
  final canvas = new Canvas(recorder, screen);
  final offset = screen.center;
  final painter = createTextPainter();
  final bounds = offset & Size(painter.minIntrinsicWidth, painter.height);
  drawBackground(canvas, screen);
  painter.paint(canvas, offset);
  canvas.drawRect(
      bounds,
      new Paint()
        ..color = black
        ..style = PaintingStyle.stroke
        ..strokeWidth = 3.0);
  return recorder.endRecording();
}

Scene createScene() {
  final builder = new SceneBuilder()
    ..pushTransform(identityTransform)
    ..addPicture(Offset.zero, createPicture())
    ..pop();
  return builder.build();
}

void beginFrame(Duration timeStamp) {
  window.render(createScene());
}

void main() {
  window.onBeginFrame = beginFrame;
  window.scheduleFrame();
}
text
flutter
asked on Stack Overflow Aug 16, 2018 by Brian Engel • edited Aug 31, 2019 by Uwe Keim

2 Answers

1

To ignore that padding you have to make really low level calls as the Text and the space practically get painted together. I tried searching if there is any way other way out in on the Internet but couldn't find anything which could actually solve your issue.

I tried hunting through the source code of Flutter SDK (Widget_Library), but all I could see is that the final resultant widget is a RichText whose source code disappears into thin air. (I couldn't find that widget's definition as I just searched it on GitHub...In Android Studio you might get some reference to it by hovering over it and pressing Ctrl+LeftClick... But I am pretty sure that it will take you to another unknown world as fonts are painted based on their font family(which would be .ttf or .otf file...Either ways they'll be painted along with the padding your referring to).

All you can do for now is create our own character set widget which accepts a raw string and reads through each character and paints a number/character already defined by you using some custom paint function,

or if your ready to let go that padding to save some time(which might or might not be crucial for you right now), you either use some predefined function to get that along with the space or use the below solution to get the dimensions of any widget you want.

I need to know the exact bounds a piece of text...

The easiest way to know the width of the any widget is by using a GlobalKey

Declare and initialize a variable to store a GlobalKey,

GlobalKey textKey = GlobalKey();

Pass the GlobalKey to the key parameter of your Text widget,

Text(
  "hello",
   key: textKey,
 )

Now I'll wrap the above widget with a GestureDetector to print the width of the above widget.

GestureDetector(
  onTap: (){
    print(textKey.currentContext.size.width); //This is your width
    print(textKey.currentContext.size.height); //This is your height
      },
   child: Text(
     "hello",
     key: textKey,
       ),
     )

As I mentioned earlier you can use it on any widget, so can you use it on TextSpan widget:

GestureDetector(
  onTap: (){
    print(textKey.currentContext.size.width); //This is your width
      },
TextSpan(
      key: textKey,
      text: '8',
      style: new TextStyle(
        color: black,
        fontSize: 200.0,
        fontFamily: "Roboto",
      ),
    ),
  )

Now with the help of GlobalKey() you can get all the information required about that piece of Text and paint your boundary/rectangle accordingly.

Note: GlobalKey() gives your widget a unique identity, so don't reuse it on multiple widgets.

==================

Edit:

If can somehow get the color of each and every pixel in your app/text, then there is a possibility of getting what your trying to achieve.

Note: You'll have to find the offset/padding of each side separately.

x1,y1 are the co-ordinates for the starting point of your container and x2,y2 are the ending points.

minL,minR,minT,minB are the offset(s) you were looking for.

Basic Logic: Scan each line vertically/horizontally based on the side taken under consideration, until you get a pixel whose color equals the text color and do this for all the imaginary lines until you get the minimum offset(distance of the text from the boundary) for all sides.

Imagine your drawing a line from the boundary of your Container until you meet one of the bounds of the text(/meet the text color) and you do that for all other set of (horizontal[Left/Right] or vertical[Top/Bottom])lines and you just keep the shortest distance in your mind.

// Left Side
for(i=y1+1;i<y2;i++)
 for(j=x1+1;j<x2;j++)
   if(colorof(j,i)==textcolor && minL>j) {minL = j; break;}

// Right Side
for(i=y1+1;i<y2;i++)
 for(j=x2-1;j>x1;j--)
   if(colorof(j,i)==textcolor && minR>j) {minR = j; break;}

// Top Side
for(i=x1+1;i<x2;i++)
 for(j=y1;j<y2;j++)
   if(colorof(i,j)==textcolor && minT>j) {minT = j; break;}

// Bottom Side
for(i=x1+1;i<x1;i++)
 for(j=y2;j>y1;j--)
   if(colorof(i,j)==textcolor && minB>j) {minB = j; break;}

x1 = x1 + minL;
y1 = y1 + minT;
x2 = x2 - minR;
y2 = y2 - minB;

While implementing the above logic ensure that there is no other overlapping/underlying widget(Scaffold/Stack Combo) whose color somehow matches the text color.

answered on Stack Overflow Sep 7, 2019 by Son of Stackoverflow • edited Sep 7, 2019 by Son of Stackoverflow
0

You can use the TextPainter class to determine the width of some text. https://api.flutter.dev/flutter/painting/TextPainter-class.html Flutter uses that during layout to determine the size of a text widget.

Not sure if the result from TextPainter will include a small amount of padding or not, though if it does, the already proposed solution will likely do so too.

answered on Stack Overflow Sep 3, 2019 by Tom Robinson

User contributions licensed under CC BY-SA 3.0