@jsenv/humanize 1.7.6 → 1.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/jsenv_humanize_browser.js +63 -35
- package/dist/browser/jsenv_humanize_browser.js.map +3 -3
- package/dist/node/jsenv_humanize_node.js +198 -87
- package/dist/node/jsenv_humanize_node.js.map +5 -5
- package/dist/node/jsenv_humanize_node_modules.js +14 -22
- package/dist/node/jsenv_humanize_node_modules.js.map +4 -6
- package/package.json +10 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSupportsColor, isUnicodeSupported,
|
|
1
|
+
import { createSupportsColor, isUnicodeSupported, eastAsianWidth } from "./jsenv_humanize_node_modules.js";
|
|
2
2
|
import stripAnsi from "strip-ansi";
|
|
3
3
|
import { stripVTControlCharacters } from "node:util";
|
|
4
4
|
import ansiEscapes from "ansi-escapes";
|
|
@@ -677,10 +677,13 @@ const derivedErrorNameArray = [
|
|
|
677
677
|
];
|
|
678
678
|
|
|
679
679
|
const inspectDate = (value, { nestedHumanize, useNew, parenthesis }) => {
|
|
680
|
-
const dateSource = nestedHumanize(value.
|
|
680
|
+
const dateSource = nestedHumanize(value.toISOString(), {
|
|
681
681
|
numericSeparator: false,
|
|
682
682
|
});
|
|
683
|
-
return inspectConstructor(`Date(${dateSource})`, {
|
|
683
|
+
return inspectConstructor(`Date(${dateSource})`, {
|
|
684
|
+
useNew,
|
|
685
|
+
parenthesis,
|
|
686
|
+
});
|
|
684
687
|
};
|
|
685
688
|
|
|
686
689
|
const inspectFunction = (
|
|
@@ -1014,29 +1017,16 @@ const TIME_DICTIONARY_EN = {
|
|
|
1014
1017
|
second: { long: "second", plural: "seconds", short: "s" },
|
|
1015
1018
|
joinDuration: (primary, remaining) => `${primary} and ${remaining}`,
|
|
1016
1019
|
};
|
|
1017
|
-
const TIME_DICTIONARY_FR = {
|
|
1018
|
-
year: { long: "an", plural: "ans", short: "a" },
|
|
1019
|
-
month: { long: "mois", plural: "mois", short: "m" },
|
|
1020
|
-
week: { long: "semaine", plural: "semaines", short: "s" },
|
|
1021
|
-
day: { long: "jour", plural: "jours", short: "j" },
|
|
1022
|
-
hour: { long: "heure", plural: "heures", short: "h" },
|
|
1023
|
-
minute: { long: "minute", plural: "minutes", short: "m" },
|
|
1024
|
-
second: { long: "seconde", plural: "secondes", short: "s" },
|
|
1025
|
-
joinDuration: (primary, remaining) => `${primary} et ${remaining}`,
|
|
1026
|
-
};
|
|
1027
1020
|
|
|
1028
1021
|
const humanizeEllapsedTime = (
|
|
1029
1022
|
ms,
|
|
1030
|
-
{
|
|
1031
|
-
short,
|
|
1032
|
-
lang = "en",
|
|
1033
|
-
timeDictionnary = lang === "fr" ? TIME_DICTIONARY_FR : TIME_DICTIONARY_EN,
|
|
1034
|
-
} = {},
|
|
1023
|
+
{ short, timeDictionnary = TIME_DICTIONARY_EN } = {},
|
|
1035
1024
|
) => {
|
|
1036
1025
|
if (ms < 1000) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1026
|
+
if (short) {
|
|
1027
|
+
return `0${timeDictionnary.second.short}`;
|
|
1028
|
+
}
|
|
1029
|
+
return `0 ${timeDictionnary.second.long}`;
|
|
1040
1030
|
}
|
|
1041
1031
|
const { primary, remaining } = parseMs(ms);
|
|
1042
1032
|
if (!remaining) {
|
|
@@ -1068,33 +1058,52 @@ const inspectEllapsedUnit = (unit, { short, timeDictionnary }) => {
|
|
|
1068
1058
|
return `${count} ${unitText}`;
|
|
1069
1059
|
};
|
|
1070
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* Converts a duration in milliseconds into a human-readable string intended for display in
|
|
1063
|
+
* CLI output — where readability matters more than precision.
|
|
1064
|
+
*
|
|
1065
|
+
* - Values below 1ms are displayed as "0 second". Sub-millisecond durations are not
|
|
1066
|
+
* meaningful at human scale, and showing "0.0001 second" (or switching to a "millisecond"
|
|
1067
|
+
* unit) would hurt readability. The chosen trade-off is to always use "second" as the
|
|
1068
|
+
* smallest unit and accept the loss of precision for very small values.
|
|
1069
|
+
* - Values below 1s are displayed in fractional seconds (e.g. "0.05 second").
|
|
1070
|
+
* - Values are expressed using the two most significant units (e.g. "1 hour and 23 minutes").
|
|
1071
|
+
* - Rounding never causes a value to display as the next unit boundary
|
|
1072
|
+
* (e.g. 59_999ms → "59.9 seconds", never "60 seconds").
|
|
1073
|
+
*
|
|
1074
|
+
* @param {number} ms - Duration in milliseconds.
|
|
1075
|
+
* @param {object} [options]
|
|
1076
|
+
* @param {boolean} [options.short=false] - Use compact unit symbols (e.g. "1h and 23m").
|
|
1077
|
+
* @param {boolean} [options.rounded=true] - Round the last displayed digit. When false, truncates instead.
|
|
1078
|
+
* @param {number} [options.decimals] - Override the number of decimal places shown.
|
|
1079
|
+
* @returns {string}
|
|
1080
|
+
*/
|
|
1071
1081
|
const humanizeDuration = (
|
|
1072
1082
|
ms,
|
|
1073
1083
|
{
|
|
1074
1084
|
short,
|
|
1075
1085
|
rounded = true,
|
|
1076
1086
|
decimals,
|
|
1077
|
-
|
|
1078
|
-
timeDictionnary = lang === "fr" ? TIME_DICTIONARY_FR : TIME_DICTIONARY_EN,
|
|
1087
|
+
timeDictionnary = TIME_DICTIONARY_EN,
|
|
1079
1088
|
} = {},
|
|
1080
1089
|
) => {
|
|
1081
|
-
// ignore ms below meaningfulMs so that:
|
|
1082
|
-
// humanizeDuration(0.5) -> "0 second"
|
|
1083
|
-
// humanizeDuration(1.1) -> "0.001 second" (and not "0.0011 second")
|
|
1084
|
-
// This tool is meant to be read by humans and it would be barely readable to see
|
|
1085
|
-
// "0.0001 second" (stands for 0.1 millisecond)
|
|
1086
|
-
// yes we could return "0.1 millisecond" but we choosed consistency over precision
|
|
1087
|
-
// so that the prefered unit is "second" (and does not become millisecond when ms is super small)
|
|
1088
1090
|
if (ms < 1) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1091
|
+
if (short) {
|
|
1092
|
+
return `0${timeDictionnary.second.short}`;
|
|
1093
|
+
}
|
|
1094
|
+
return `0 ${timeDictionnary.second.long}`;
|
|
1092
1095
|
}
|
|
1093
1096
|
const { primary, remaining } = parseMs(ms);
|
|
1094
1097
|
if (!remaining) {
|
|
1098
|
+
const primaryUnitIndex = UNIT_KEYS.indexOf(primary.name);
|
|
1099
|
+
const nextUnitName = UNIT_KEYS[primaryUnitIndex - 1];
|
|
1100
|
+
const maxCount = nextUnitName
|
|
1101
|
+
? UNIT_MS[nextUnitName] / UNIT_MS[primary.name]
|
|
1102
|
+
: null;
|
|
1095
1103
|
return humanizeDurationUnit(primary, {
|
|
1096
1104
|
decimals:
|
|
1097
1105
|
decimals === undefined ? (primary.name === "second" ? 1 : 0) : decimals,
|
|
1106
|
+
maxCount,
|
|
1098
1107
|
short,
|
|
1099
1108
|
rounded,
|
|
1100
1109
|
timeDictionnary,
|
|
@@ -1112,15 +1121,23 @@ const humanizeDuration = (
|
|
|
1112
1121
|
rounded,
|
|
1113
1122
|
timeDictionnary,
|
|
1114
1123
|
});
|
|
1124
|
+
if (short) {
|
|
1125
|
+
return `${primaryText}${remainingText}`;
|
|
1126
|
+
}
|
|
1115
1127
|
return timeDictionnary.joinDuration(primaryText, remainingText);
|
|
1116
1128
|
};
|
|
1117
1129
|
const humanizeDurationUnit = (
|
|
1118
1130
|
unit,
|
|
1119
|
-
{ decimals, short, rounded, timeDictionnary },
|
|
1131
|
+
{ decimals, maxCount, short, rounded, timeDictionnary },
|
|
1120
1132
|
) => {
|
|
1121
|
-
|
|
1133
|
+
let count = rounded
|
|
1122
1134
|
? setRoundedPrecision(unit.count, { decimals })
|
|
1123
1135
|
: setPrecision(unit.count, { decimals });
|
|
1136
|
+
if (maxCount !== null && maxCount !== undefined && count >= maxCount) {
|
|
1137
|
+
// Prevent rounding up to the next unit boundary (e.g. 59.999s → 60s → cap to 59.9s)
|
|
1138
|
+
const factor = Math.pow(10, decimals ?? 0);
|
|
1139
|
+
count = Math.floor(unit.count * factor) / factor;
|
|
1140
|
+
}
|
|
1124
1141
|
const name = unit.name;
|
|
1125
1142
|
if (short) {
|
|
1126
1143
|
const unitText = timeDictionnary[name].short;
|
|
@@ -1173,6 +1190,17 @@ const parseMs = (ms) => {
|
|
|
1173
1190
|
},
|
|
1174
1191
|
};
|
|
1175
1192
|
}
|
|
1193
|
+
// When remaining rounds up to a full next-unit (e.g. 59.999s rounds to 60s = 1min),
|
|
1194
|
+
// drop the remaining to avoid displaying "59 minutes and 60 seconds".
|
|
1195
|
+
const remainingUnitMs = UNIT_MS[remainingUnitName];
|
|
1196
|
+
const nextUnitMs = UNIT_MS[firstUnitName];
|
|
1197
|
+
const maxRemainingCount = nextUnitMs / remainingUnitMs; // e.g. 60 for seconds-in-a-minute
|
|
1198
|
+
// Cap remaining so it never rounds up to the next unit boundary
|
|
1199
|
+
// (e.g. 59.5s stays as 59s instead of rounding to 60s = 1min)
|
|
1200
|
+
const cappedRemainingCount =
|
|
1201
|
+
remainingUnitCount >= maxRemainingCount - 1
|
|
1202
|
+
? maxRemainingCount - 1
|
|
1203
|
+
: remainingUnitCount;
|
|
1176
1204
|
// - 1 year and 1 month is great
|
|
1177
1205
|
return {
|
|
1178
1206
|
primary: {
|
|
@@ -1181,7 +1209,7 @@ const parseMs = (ms) => {
|
|
|
1181
1209
|
},
|
|
1182
1210
|
remaining: {
|
|
1183
1211
|
name: remainingUnitName,
|
|
1184
|
-
count:
|
|
1212
|
+
count: cappedRemainingCount,
|
|
1185
1213
|
},
|
|
1186
1214
|
};
|
|
1187
1215
|
};
|
|
@@ -1701,23 +1729,122 @@ const error = (...args) => console.error(...args);
|
|
|
1701
1729
|
|
|
1702
1730
|
const errorDisabled = () => {};
|
|
1703
1731
|
|
|
1732
|
+
// Whole-cluster zero-width: Default_Ignorable, Control, Format, Mark, Surrogate
|
|
1733
|
+
const zeroWidthClusterRegex =
|
|
1734
|
+
/^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+$/v;
|
|
1735
|
+
|
|
1736
|
+
// Strip leading non-printing chars to get the first visible scalar of a cluster
|
|
1737
|
+
const leadingNonPrintingRegex =
|
|
1738
|
+
/^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v;
|
|
1739
|
+
|
|
1740
|
+
// RGI emoji sequences (e.g. flag sequences, ZWJ families, keycap+VS16)
|
|
1741
|
+
const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
|
|
1742
|
+
|
|
1743
|
+
// Unqualified keycap: digit/# /* + combining enclosing keycap (no VS16)
|
|
1744
|
+
const unqualifiedKeycapRegex = /^[\d#*]\u20E3$/;
|
|
1745
|
+
const extendedPictographicRegex = /\p{Extended_Pictographic}/gv;
|
|
1746
|
+
|
|
1747
|
+
const isDoubleWidthNonRgiEmojiSequence = (segment) => {
|
|
1748
|
+
if (segment.length > 50) {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
if (unqualifiedKeycapRegex.test(segment)) {
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
// ZWJ sequences with 2+ Extended_Pictographic
|
|
1755
|
+
if (segment.includes("\u200D")) {
|
|
1756
|
+
const pictographics = segment.match(extendedPictographicRegex);
|
|
1757
|
+
return pictographics !== null && pictographics.length >= 2;
|
|
1758
|
+
}
|
|
1759
|
+
return false;
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
const baseVisible = (segment) => {
|
|
1763
|
+
return segment.replace(leadingNonPrintingRegex, "");
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
const isHangulLeadingJamo = (cp) => {
|
|
1767
|
+
return (cp >= 0x11_00 && cp <= 0x11_5f) || (cp >= 0xa9_60 && cp <= 0xa9_7c);
|
|
1768
|
+
};
|
|
1769
|
+
const isHangulVowelJamo = (cp) => {
|
|
1770
|
+
return (cp >= 0x11_60 && cp <= 0x11_a7) || (cp >= 0xd7_b0 && cp <= 0xd7_c6);
|
|
1771
|
+
};
|
|
1772
|
+
const isHangulTrailingJamo = (cp) => {
|
|
1773
|
+
return (cp >= 0x11_a8 && cp <= 0x11_ff) || (cp >= 0xd7_cb && cp <= 0xd7_fb);
|
|
1774
|
+
};
|
|
1775
|
+
const isHangulJamo = (cp) => {
|
|
1776
|
+
return (
|
|
1777
|
+
isHangulLeadingJamo(cp) || isHangulVowelJamo(cp) || isHangulTrailingJamo(cp)
|
|
1778
|
+
);
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
const hangulClusterWidth = (visibleSegment, eastAsianWidthOptions) => {
|
|
1782
|
+
const codePoints = [];
|
|
1783
|
+
for (const character of visibleSegment) {
|
|
1784
|
+
if (zeroWidthClusterRegex.test(character)) {
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
codePoints.push(character.codePointAt(0));
|
|
1788
|
+
}
|
|
1789
|
+
if (codePoints.length === 0) {
|
|
1790
|
+
return undefined;
|
|
1791
|
+
}
|
|
1792
|
+
let width = 0;
|
|
1793
|
+
for (let index = 0; index < codePoints.length; index++) {
|
|
1794
|
+
const codePoint = codePoints[index];
|
|
1795
|
+
if (!isHangulJamo(codePoint)) {
|
|
1796
|
+
if (width === 0) {
|
|
1797
|
+
return undefined;
|
|
1798
|
+
}
|
|
1799
|
+
for (let remaining = index; remaining < codePoints.length; remaining++) {
|
|
1800
|
+
width += eastAsianWidth(codePoints[remaining], eastAsianWidthOptions);
|
|
1801
|
+
}
|
|
1802
|
+
return width;
|
|
1803
|
+
}
|
|
1804
|
+
if (
|
|
1805
|
+
isHangulLeadingJamo(codePoint) &&
|
|
1806
|
+
isHangulVowelJamo(codePoints[index + 1])
|
|
1807
|
+
) {
|
|
1808
|
+
width += 2;
|
|
1809
|
+
index += isHangulTrailingJamo(codePoints[index + 2]) ? 2 : 1;
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
width += eastAsianWidth(codePoint, eastAsianWidthOptions);
|
|
1813
|
+
}
|
|
1814
|
+
return width;
|
|
1815
|
+
};
|
|
1816
|
+
|
|
1817
|
+
const trailingHalfwidthWidth = (visibleSegment, eastAsianWidthOptions) => {
|
|
1818
|
+
let extra = 0;
|
|
1819
|
+
let first = true;
|
|
1820
|
+
for (const character of visibleSegment) {
|
|
1821
|
+
if (first) {
|
|
1822
|
+
first = false;
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
if (character >= "\uFF00" && character <= "\uFFEF") {
|
|
1826
|
+
extra += eastAsianWidth(character.codePointAt(0), eastAsianWidthOptions);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return extra;
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1704
1832
|
const createMeasureTextWidth = ({ stripAnsi }) => {
|
|
1705
1833
|
const segmenter = new Intl.Segmenter();
|
|
1706
|
-
const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
1707
1834
|
|
|
1708
1835
|
const measureTextWidth = (
|
|
1709
1836
|
string,
|
|
1710
|
-
{
|
|
1711
|
-
ambiguousIsNarrow = true,
|
|
1712
|
-
countAnsiEscapeCodes = false,
|
|
1713
|
-
skipEmojis = false,
|
|
1714
|
-
} = {},
|
|
1837
|
+
{ ambiguousIsNarrow = true, countAnsiEscapeCodes = false } = {},
|
|
1715
1838
|
) => {
|
|
1716
1839
|
if (typeof string !== "string" || string.length === 0) {
|
|
1717
1840
|
return 0;
|
|
1718
1841
|
}
|
|
1719
1842
|
|
|
1720
|
-
|
|
1843
|
+
// Only strip ANSI when escape codes are actually present
|
|
1844
|
+
if (
|
|
1845
|
+
!countAnsiEscapeCodes &&
|
|
1846
|
+
(string.includes("\u001B") || string.includes("\u009B"))
|
|
1847
|
+
) {
|
|
1721
1848
|
string = stripAnsi(string);
|
|
1722
1849
|
}
|
|
1723
1850
|
|
|
@@ -1725,70 +1852,54 @@ const createMeasureTextWidth = ({ stripAnsi }) => {
|
|
|
1725
1852
|
return 0;
|
|
1726
1853
|
}
|
|
1727
1854
|
|
|
1855
|
+
// Fast path: printable ASCII needs no segmenter or EAW lookup
|
|
1856
|
+
if (/^[\u0020-\u007E]*$/.test(string)) {
|
|
1857
|
+
return string.length;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1728
1860
|
let width = 0;
|
|
1729
1861
|
const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
|
|
1730
1862
|
|
|
1731
|
-
for (const { segment
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
// Ignore control characters
|
|
1735
|
-
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
1863
|
+
for (const { segment } of segmenter.segment(string)) {
|
|
1864
|
+
if (zeroWidthClusterRegex.test(segment)) {
|
|
1736
1865
|
continue;
|
|
1737
1866
|
}
|
|
1738
1867
|
|
|
1739
|
-
//
|
|
1868
|
+
// RGI emoji + unqualified emoji sequences are double-width
|
|
1740
1869
|
if (
|
|
1741
|
-
(
|
|
1742
|
-
|
|
1870
|
+
rgiEmojiRegex.test(segment) ||
|
|
1871
|
+
isDoubleWidthNonRgiEmojiSequence(segment)
|
|
1743
1872
|
) {
|
|
1873
|
+
if (process.env.CAPTURING_SIDE_EFFECTS && segment === "✔️") {
|
|
1874
|
+
width += 2;
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
width += 2;
|
|
1744
1878
|
continue;
|
|
1745
1879
|
}
|
|
1746
1880
|
|
|
1747
|
-
|
|
1748
|
-
if (
|
|
1749
|
-
(codePoint >= 0x3_00 && codePoint <= 0x3_6f) || // Combining diacritical marks
|
|
1750
|
-
(codePoint >= 0x1a_b0 && codePoint <= 0x1a_ff) || // Combining diacritical marks extended
|
|
1751
|
-
(codePoint >= 0x1d_c0 && codePoint <= 0x1d_ff) || // Combining diacritical marks supplement
|
|
1752
|
-
(codePoint >= 0x20_d0 && codePoint <= 0x20_ff) || // Combining diacritical marks for symbols
|
|
1753
|
-
(codePoint >= 0xfe_20 && codePoint <= 0xfe_2f) // Combining half marks
|
|
1754
|
-
) {
|
|
1755
|
-
continue;
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
// Ignore surrogate pairs
|
|
1759
|
-
if (codePoint >= 0xd8_00 && codePoint <= 0xdf_ff) {
|
|
1760
|
-
continue;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// Ignore variation selectors
|
|
1764
|
-
if (codePoint >= 0xfe_00 && codePoint <= 0xfe_0f) {
|
|
1765
|
-
continue;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
// This covers some of the above cases, but we still keep them for performance reasons.
|
|
1769
|
-
if (defaultIgnorableCodePointRegex.test(character)) {
|
|
1770
|
-
continue;
|
|
1771
|
-
}
|
|
1881
|
+
const visibleSegment = baseVisible(segment);
|
|
1772
1882
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
}
|
|
1780
|
-
width += measureTextWidth(character, {
|
|
1781
|
-
skipEmojis: true,
|
|
1782
|
-
countAnsiEscapeCodes: true, // to skip call to stripAnsi
|
|
1783
|
-
});
|
|
1883
|
+
const hangulWidth = hangulClusterWidth(
|
|
1884
|
+
visibleSegment,
|
|
1885
|
+
eastAsianWidthOptions,
|
|
1886
|
+
);
|
|
1887
|
+
if (hangulWidth !== undefined) {
|
|
1888
|
+
width += hangulWidth;
|
|
1784
1889
|
continue;
|
|
1785
1890
|
}
|
|
1786
1891
|
|
|
1892
|
+
// EAW of the cluster's first visible scalar
|
|
1893
|
+
const codePoint = visibleSegment.codePointAt(0);
|
|
1787
1894
|
width += eastAsianWidth(codePoint, eastAsianWidthOptions);
|
|
1895
|
+
|
|
1896
|
+
// Add width for trailing Halfwidth/Fullwidth Forms (e.g. ゙, ゚, ー)
|
|
1897
|
+
width += trailingHalfwidthWidth(visibleSegment, eastAsianWidthOptions);
|
|
1788
1898
|
}
|
|
1789
1899
|
|
|
1790
1900
|
return width;
|
|
1791
1901
|
};
|
|
1902
|
+
|
|
1792
1903
|
return measureTextWidth;
|
|
1793
1904
|
};
|
|
1794
1905
|
|