getFragmentAtPosition method
({String fragmentId, int localOffset})?
getFragmentAtPosition(
- Offset position
)
Implementation
({String fragmentId, int localOffset})? getFragmentAtPosition(
Offset position,
) {
final adjustedPosition = position - Offset(_alignmentXOffset, 0);
final textPosition = _painter.getPositionForOffset(adjustedPosition);
final result = _globalToLocal(textPosition.offset);
if (result != null) return result;
if (_lineMetricsDirty || _cachedLineMetrics == null) {
_cachedLineMetrics = _painter.computeLineMetrics();
_lineMetricsDirty = false;
}
final lineMetrics = _cachedLineMetrics!;
if (lineMetrics.isEmpty) return null;
int targetLine = lineMetrics.length - 1;
for (int i = 0; i < lineMetrics.length; i++) {
final line = lineMetrics[i];
final lineTop = line.baseline - line.ascent;
final lineBottom = line.baseline + line.descent;
if (adjustedPosition.dy >= lineTop && adjustedPosition.dy <= lineBottom) {
targetLine = i;
break;
}
if (i == 0 && adjustedPosition.dy < lineTop) {
targetLine = 0;
break;
}
}
final targetLineMetric = lineMetrics[targetLine];
final targetLineY = targetLineMetric.baseline - targetLineMetric.ascent;
final targetLineBottom =
targetLineMetric.baseline + targetLineMetric.descent;
// Limit the scan to only the character range of the target line,
// instead of iterating every character of every fragment.
final lineStartOffset = targetLine > 0
? (lineMetrics[targetLine - 1].baseline + lineMetrics[targetLine - 1].descent).round()
: 0;
final lineBoundary = _painter.getLineBoundary(TextPosition(offset: lineStartOffset));
final scanStart = lineBoundary.start;
final scanEnd = lineBoundary.end;
String? bestFragmentId;
int bestLocalOffset = 0;
double bestXDistance = double.infinity;
for (final fragment in _fragmentPositions) {
// Skip fragments entirely outside the target line's offset range
if (fragment.end <= scanStart || fragment.start >= scanEnd) continue;
final fragScanStart = fragment.start > scanStart ? fragment.start : scanStart;
final fragScanEnd = fragment.end < scanEnd ? fragment.end : scanEnd;
for (int offset = fragScanStart - fragment.start; offset < fragScanEnd - fragment.start; offset++) {
final globalOffset = fragment.start + offset;
final boxes = _painter.getBoxesForSelection(
TextSelection(
baseOffset: globalOffset,
extentOffset: globalOffset + 1,
),
);
if (boxes.isEmpty) continue;
final box = boxes.first.toRect();
final boxCenterY = (box.top + box.bottom) / 2;
if (boxCenterY < targetLineY || boxCenterY > targetLineBottom) {
continue;
}
if (box.contains(adjustedPosition)) {
final isRightHalf = adjustedPosition.dx > (box.left + box.right) / 2;
return (
fragmentId: fragment.id,
localOffset: isRightHalf ? offset + 1 : offset,
);
}
final leftDistance = (adjustedPosition.dx - box.left).abs();
final rightDistance = (adjustedPosition.dx - box.right).abs();
if (leftDistance < bestXDistance) {
bestXDistance = leftDistance;
bestFragmentId = fragment.id;
bestLocalOffset = offset;
}
if (rightDistance < bestXDistance) {
bestXDistance = rightDistance;
bestFragmentId = fragment.id;
bestLocalOffset = offset + 1;
}
}
}
if (bestFragmentId != null) {
return (fragmentId: bestFragmentId, localOffset: bestLocalOffset);
}
if (_fragmentPositions.isNotEmpty) {
return (fragmentId: _fragmentPositions.first.id, localOffset: 0);
}
return null;
}