blob: 4b1053350f6491add48f6ad9657641c24d193c88 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import '../../engine.dart' show registerHotRestartListener;
import '../dom_renderer.dart';
// TODO(yjbanov): this is a hack we use to compute ideographic baseline; this
// number is the ratio ideographic/alphabetic for font Ahem,
// which matches the Flutter number. It may be completely wrong
// for any other font. We'll need to eventually fix this. That
// said Flutter doesn't seem to use ideographic baseline for
// anything as of this writing.
const double baselineRatioHack = 1.1662499904632568;
/// Hosts ruler DOM elements in a hidden container under a `root` [html.Node].
///
/// The `root` [html.Node] is optional. Defaults to [domRenderer.glassPaneShadow].
class RulerHost {
RulerHost({html.Node? root}) {
_rulerHost.style
..position = 'fixed'
..visibility = 'hidden'
..overflow = 'hidden'
..top = '0'
..left = '0'
..width = '0'
..height = '0';
(root ?? domRenderer.glassPaneShadow!.node).append(_rulerHost);
registerHotRestartListener(dispose);
}
/// Hosts a cache of rulers that measure text.
///
/// This element exists purely for organizational purposes. Otherwise the
/// rulers would be attached to the `<body>` element polluting the element
/// tree and making it hard to navigate. It does not serve any functional
/// purpose.
final html.Element _rulerHost = html.Element.tag('flt-ruler-host');
/// Releases the resources used by this [RulerHost].
///
/// After this is called, this object is no longer usable.
void dispose() {
_rulerHost.remove();
}
/// Adds an element used for measuring text as a child of [_rulerHost].
void addElement(html.HtmlElement element) {
_rulerHost.append(element);
}
}
// These global variables are used to memoize calls to [measureSubstring]. They
// are used to remember the last arguments passed to it, and the last return
// value.
// They are being initialized so that the compiler knows they'll never be null.
int _lastStart = -1;
int _lastEnd = -1;
String _lastText = '';
String _lastCssFont = '';
double _lastWidth = -1;
/// Measures the width of the substring of [text] starting from the index
/// [start] (inclusive) to [end] (exclusive).
///
/// This method assumes that the correct font has already been set on
/// [_canvasContext].
double measureSubstring(
html.CanvasRenderingContext2D _canvasContext,
String text,
int start,
int end, {
double? letterSpacing,
}) {
assert(0 <= start);
assert(start <= end);
assert(end <= text.length);
if (start == end) {
return 0;
}
final String cssFont = _canvasContext.font;
double width;
// TODO(mdebbar): Explore caching all widths in a map, not only the last one.
if (start == _lastStart &&
end == _lastEnd &&
text == _lastText &&
cssFont == _lastCssFont) {
// Reuse the previously calculated width if all factors that affect width
// are unchanged. The only exception is letter-spacing. We always add
// letter-spacing to the width later below.
width = _lastWidth;
} else {
final String sub =
start == 0 && end == text.length ? text : text.substring(start, end);
width = _canvasContext.measureText(sub).width!.toDouble();
}
_lastStart = start;
_lastEnd = end;
_lastText = text;
_lastCssFont = cssFont;
_lastWidth = width;
// Now add letter spacing to the width.
letterSpacing ??= 0.0;
if (letterSpacing != 0.0) {
width += letterSpacing * (end - start);
}
// What we are doing here is we are rounding to the nearest 2nd decimal
// point. So 39.999423 becomes 40, and 11.243982 becomes 11.24.
// The reason we are doing this is because we noticed that canvas API has a
// ±0.001 error margin.
return _roundWidth(width);
}
double _roundWidth(double width) {
return (width * 100).round() / 100;
}