blob: 554cfb892fa5a58d827ab6842d1de39c35ae11ab [file] [log] [blame] [edit]
// Copyright 2014 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:math' show max, min;
import 'package:flutter/foundation.dart';
/// A class that describes how textual contents should be scaled for better
/// readability.
///
/// The [scale] function computes the scaled font size given the original
/// unscaled font size specified by app developers.
///
/// The [==] operator defines the equality of 2 [TextScaler]s, which the
/// framework uses to determine whether text widgets should rebuild when their
/// [TextScaler] changes. Consider overridding the [==] operator if applicable
/// to avoid unnecessary rebuilds.
@immutable
abstract class TextScaler {
/// Creates a TextScaler.
const TextScaler();
/// Creates a proportional [TextScaler] that scales the incoming font size by
/// multiplying it with the given `textScaleFactor`.
const factory TextScaler.linear(double textScaleFactor) = _LinearTextScaler;
/// A [TextScaler] that doesn't scale the input font size.
///
/// This is equivalent to `TextScaler.linear(1.0)`, the [TextScaler.scale]
/// implementation always returns the input font size as-is.
static const TextScaler noScaling = _LinearTextScaler(1.0);
/// Computes the scaled font size (in logical pixels) with the given unscaled
/// `fontSize` (in logical pixels).
///
/// The input `fontSize` must be finite and non-negative.
///
/// When given the same `fontSize` input, this method returns the same value.
/// The output of a larger input `fontSize` is typically larger than that of a
/// smaller input, but on unusual occasions they may produce the same output.
/// For example, some platforms use single-precision floats to represent font
/// sizes, as a result of truncation two different unscaled font sizes can be
/// scaled to the same value.
double scale(double fontSize);
/// The estimated number of font pixels for each logical pixel. This property
/// exists only for backward compatibility purposes, and will be removed in
/// a future version of Flutter.
///
/// The value of this property is only an estimate, so it may not reflect the
/// exact text scaling strategy this [TextScaler] represents, especially when
/// this [TextScaler] is not linear. Consider using [TextScaler.scale] instead.
@Deprecated(
'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. '
'This feature was deprecated after v3.12.0-2.0.pre.',
)
double get textScaleFactor;
/// Returns a new [TextScaler] that restricts the scaled font size to within
/// the range `[minScaleFactor * fontSize, maxScaleFactor * fontSize]`.
TextScaler clamp({ double minScaleFactor = 0, double maxScaleFactor = double.infinity }) {
assert(maxScaleFactor >= minScaleFactor);
assert(!maxScaleFactor.isNaN);
assert(minScaleFactor.isFinite);
assert(minScaleFactor >= 0);
return minScaleFactor == maxScaleFactor
? TextScaler.linear(minScaleFactor)
: _ClampedTextScaler(this, minScaleFactor, maxScaleFactor);
}
}
final class _LinearTextScaler implements TextScaler {
const _LinearTextScaler(this.textScaleFactor) : assert(textScaleFactor >= 0);
@override
final double textScaleFactor;
@override
double scale(double fontSize) {
assert(fontSize >= 0);
assert(fontSize.isFinite);
return fontSize * textScaleFactor;
}
@override
TextScaler clamp({ double minScaleFactor = 0, double maxScaleFactor = double.infinity }) {
assert(maxScaleFactor >= minScaleFactor);
assert(!maxScaleFactor.isNaN);
assert(minScaleFactor.isFinite);
assert(minScaleFactor >= 0);
final double newScaleFactor = clampDouble(textScaleFactor, minScaleFactor, maxScaleFactor);
return newScaleFactor == textScaleFactor ? this : _LinearTextScaler(newScaleFactor);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is _LinearTextScaler && other.textScaleFactor == textScaleFactor;
}
@override
int get hashCode => textScaleFactor.hashCode;
@override
String toString() => textScaleFactor == 1.0 ? 'no scaling' : 'linear (${textScaleFactor}x)';
}
final class _ClampedTextScaler implements TextScaler {
const _ClampedTextScaler(this.scaler, this.minScale, this.maxScale) : assert(maxScale > minScale);
final TextScaler scaler;
final double minScale;
final double maxScale;
@override
double get textScaleFactor => clampDouble(scaler.textScaleFactor, minScale, maxScale);
@override
double scale(double fontSize) {
assert(fontSize >= 0);
assert(fontSize.isFinite);
return minScale == maxScale
? minScale * fontSize
: clampDouble(scaler.scale(fontSize), minScale * fontSize, maxScale * fontSize);
}
@override
TextScaler clamp({ double minScaleFactor = 0, double maxScaleFactor = double.infinity }) {
return minScaleFactor == maxScaleFactor
? _LinearTextScaler(minScaleFactor)
: _ClampedTextScaler(scaler, max(minScaleFactor, minScale), min(maxScaleFactor, maxScale));
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is _ClampedTextScaler
&& minScale == other.minScale
&& maxScale == other.maxScale
&& (minScale == maxScale || scaler == other.scaler);
}
@override
int get hashCode => minScale == maxScale ? minScale.hashCode : Object.hash(scaler, minScale, maxScale);
}