crampedText function

List<CrampedText> crampedText(
  1. RenderObject root, {
  2. double? vertical,
  3. double? horizontal,
})

Text under root whose glyphs come within vertical pixels of the top or bottom, or horizontal pixels of the sides, of the smallest hand-drawn border around it, or spill past it. The defaults are kWiredInkPadding, the space every skribble control keeps.

Gaps are measured in the content's own size, so a scaled-down preview is judged by its proportions; text and ink hidden by scrolling or clips are ignored.

Ink wobbles and has width, so a gap that is fine for a straight CSS border looks cramped here: these minimums are measured from the ideal edge, before the pen.

Implementation

List<CrampedText> crampedText(
  RenderObject root, {
  double? vertical,
  double? horizontal,
}) {
  final minimumVertical = vertical ?? kWiredInkPadding.top;
  final minimumHorizontal = horizontal ?? kWiredInkPadding.left;
  final borders = _borders(root);
  final cramped = <CrampedText>[];
  for (final (text, bounds, uncut, scale) in _texts(root)) {
    Rect? border;
    var whole = Rect.zero;
    var owner = '?';
    for (final (candidate, name, uncut) in borders) {
      // The text lives in a border when most of it is inside; a neighbour
      // straddling the line belongs to something else.
      final inside = candidate.intersect(bounds);
      if (inside.width <= 0 ||
          inside.height <= 0 ||
          inside.width * inside.height < bounds.width * bounds.height / 2) {
        continue;
      }
      // The smallest border around the text is the one it lives in.
      if (border == null ||
          candidate.width * candidate.height < border.width * border.height) {
        border = candidate;
        whole = uncut;
        owner = name;
      }
    }
    // Badges and other micro labels keep their own tight pill; text that
    // lives in one is not measured against the larger shape around it.
    if (border == null || border.shortestSide <= 24) continue;
    // A side a clip has cut off, of the ink or of the text, shows nothing
    // to crowd.
    double gap(double value, {required bool cut}) =>
        cut ? double.infinity : value / scale;
    final gaps = EdgeInsets.fromLTRB(
      gap(
        bounds.left - border.left,
        cut: border.left > whole.left + .5 || bounds.left > uncut.left + .5,
      ),
      gap(
        bounds.top - border.top,
        cut: border.top > whole.top + .5 || bounds.top > uncut.top + .5,
      ),
      gap(
        border.right - bounds.right,
        cut: border.right < whole.right - .5 || bounds.right < uncut.right - .5,
      ),
      gap(
        border.bottom - bounds.bottom,
        cut:
            border.bottom < whole.bottom - .5 ||
            bounds.bottom < uncut.bottom - .5,
      ),
    );
    // Half a pixel of slack absorbs glyph side bearings and rounding.
    if (math.min(gaps.top, gaps.bottom) < minimumVertical - .5 ||
        math.min(gaps.left, gaps.right) < minimumHorizontal - .5) {
      cramped.add(CrampedText(text, border, gaps, owner));
    }
  }
  return cramped;
}