1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:math' show max, min; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | |
9 | /// A class that describes how textual contents should be scaled for better |
10 | /// readability. |
11 | /// |
12 | /// The [scale] function computes the scaled font size given the original |
13 | /// unscaled font size specified by app developers. |
14 | /// |
15 | /// The [==] operator defines the equality of 2 [TextScaler]s, which the |
16 | /// framework uses to determine whether text widgets should rebuild when their |
17 | /// [TextScaler] changes. Consider overriding the [==] operator if applicable |
18 | /// to avoid unnecessary rebuilds. |
19 | @immutable |
20 | abstract class TextScaler { |
21 | /// Creates a TextScaler. |
22 | const TextScaler(); |
23 | |
24 | /// Creates a proportional [TextScaler] that scales the incoming font size by |
25 | /// multiplying it with the given `textScaleFactor`. |
26 | const factory TextScaler.linear(double textScaleFactor) = _LinearTextScaler; |
27 | |
28 | /// A [TextScaler] that doesn't scale the input font size. |
29 | /// |
30 | /// This is equivalent to `TextScaler.linear(1.0)`, the [TextScaler.scale] |
31 | /// implementation always returns the input font size as-is. |
32 | static const TextScaler noScaling = _LinearTextScaler(1.0); |
33 | |
34 | /// Computes the scaled font size (in logical pixels) with the given unscaled |
35 | /// `fontSize` (in logical pixels). |
36 | /// |
37 | /// The input `fontSize` must be finite and non-negative. |
38 | /// |
39 | /// When given the same `fontSize` input, this method returns the same value. |
40 | /// The output of a larger input `fontSize` is typically larger than that of a |
41 | /// smaller input, but on unusual occasions they may produce the same output. |
42 | /// For example, some platforms use single-precision floats to represent font |
43 | /// sizes, as a result of truncation two different unscaled font sizes can be |
44 | /// scaled to the same value. |
45 | double scale(double fontSize); |
46 | |
47 | /// The estimated number of font pixels for each logical pixel. This property |
48 | /// exists only for backward compatibility purposes, and will be removed in |
49 | /// a future version of Flutter. |
50 | /// |
51 | /// The value of this property is only an estimate, so it may not reflect the |
52 | /// exact text scaling strategy this [TextScaler] represents, especially when |
53 | /// this [TextScaler] is not linear. Consider using [TextScaler.scale] instead. |
54 | @Deprecated( |
55 | 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. ' |
56 | 'This feature was deprecated after v3.12.0-2.0.pre.' , |
57 | ) |
58 | double get textScaleFactor; |
59 | |
60 | /// Returns a new [TextScaler] that restricts the scaled font size to within |
61 | /// the range `[minScaleFactor * fontSize, maxScaleFactor * fontSize]`. |
62 | TextScaler clamp({double minScaleFactor = 0, double maxScaleFactor = double.infinity}) { |
63 | assert(maxScaleFactor >= minScaleFactor); |
64 | assert(!maxScaleFactor.isNaN); |
65 | assert(minScaleFactor.isFinite); |
66 | assert(minScaleFactor >= 0); |
67 | |
68 | return minScaleFactor == maxScaleFactor |
69 | ? TextScaler.linear(minScaleFactor) |
70 | : _ClampedTextScaler(this, minScaleFactor, maxScaleFactor); |
71 | } |
72 | } |
73 | |
74 | final class _LinearTextScaler implements TextScaler { |
75 | const _LinearTextScaler(this.textScaleFactor) : assert(textScaleFactor >= 0); |
76 | |
77 | @override |
78 | final double textScaleFactor; |
79 | |
80 | @override |
81 | double scale(double fontSize) { |
82 | assert(fontSize >= 0); |
83 | assert(fontSize.isFinite); |
84 | return fontSize * textScaleFactor; |
85 | } |
86 | |
87 | @override |
88 | TextScaler clamp({double minScaleFactor = 0, double maxScaleFactor = double.infinity}) { |
89 | assert(maxScaleFactor >= minScaleFactor); |
90 | assert(!maxScaleFactor.isNaN); |
91 | assert(minScaleFactor.isFinite); |
92 | assert(minScaleFactor >= 0); |
93 | |
94 | final double newScaleFactor = clampDouble(textScaleFactor, minScaleFactor, maxScaleFactor); |
95 | return newScaleFactor == textScaleFactor ? this : _LinearTextScaler(newScaleFactor); |
96 | } |
97 | |
98 | @override |
99 | bool operator ==(Object other) { |
100 | if (identical(this, other)) { |
101 | return true; |
102 | } |
103 | return other is _LinearTextScaler && other.textScaleFactor == textScaleFactor; |
104 | } |
105 | |
106 | @override |
107 | int get hashCode => textScaleFactor.hashCode; |
108 | |
109 | @override |
110 | String toString() => textScaleFactor == 1.0 ? 'no scaling' : 'linear ( ${textScaleFactor}x)' ; |
111 | } |
112 | |
113 | final class _ClampedTextScaler implements TextScaler { |
114 | const _ClampedTextScaler(this.scaler, this.minScale, this.maxScale) : assert(maxScale > minScale); |
115 | final TextScaler scaler; |
116 | final double minScale; |
117 | final double maxScale; |
118 | |
119 | @override |
120 | double get textScaleFactor => clampDouble(scaler.textScaleFactor, minScale, maxScale); |
121 | |
122 | @override |
123 | double scale(double fontSize) { |
124 | assert(fontSize >= 0); |
125 | assert(fontSize.isFinite); |
126 | return minScale == maxScale |
127 | ? minScale * fontSize |
128 | : clampDouble(scaler.scale(fontSize), minScale * fontSize, maxScale * fontSize); |
129 | } |
130 | |
131 | @override |
132 | TextScaler clamp({double minScaleFactor = 0, double maxScaleFactor = double.infinity}) { |
133 | return minScaleFactor == maxScaleFactor |
134 | ? _LinearTextScaler(minScaleFactor) |
135 | : _ClampedTextScaler(scaler, max(minScaleFactor, minScale), min(maxScaleFactor, maxScale)); |
136 | } |
137 | |
138 | @override |
139 | bool operator ==(Object other) { |
140 | if (identical(this, other)) { |
141 | return true; |
142 | } |
143 | return other is _ClampedTextScaler && |
144 | minScale == other.minScale && |
145 | maxScale == other.maxScale && |
146 | (minScale == maxScale || scaler == other.scaler); |
147 | } |
148 | |
149 | @override |
150 | int get hashCode => |
151 | minScale == maxScale ? minScale.hashCode : Object.hash(scaler, minScale, maxScale); |
152 | } |
153 | |