chat-layout 1.1.0-4 → 1.1.0
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/README.md +9 -4
- package/example/chat.ts +12 -12
- package/example/test.ts +16 -5
- package/index.d.mts +8 -8
- package/index.mjs +452 -302
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { layoutNextLine, layoutWithLines, measureLineStats, measureNaturalWidth, prepareWithSegments } from "@chenglou/pretext";
|
|
2
|
-
import { materializeRichInlineLineRange, measureRichInlineStats, prepareRichInline, walkRichInlineLineRanges } from "@chenglou/pretext/rich-inline";
|
|
2
|
+
import { layoutNextRichInlineLineRange, materializeRichInlineLineRange, measureRichInlineStats, prepareRichInline, walkRichInlineLineRanges } from "@chenglou/pretext/rich-inline";
|
|
3
3
|
//#region src/internal/node-registry.ts
|
|
4
4
|
const registry = /* @__PURE__ */ new WeakMap();
|
|
5
5
|
const revisions = /* @__PURE__ */ new WeakMap();
|
|
@@ -816,23 +816,10 @@ var Place = class extends Wrapper {
|
|
|
816
816
|
});
|
|
817
817
|
}
|
|
818
818
|
};
|
|
819
|
-
//#endregion
|
|
820
|
-
//#region src/text.ts
|
|
821
|
-
const FONT_SHIFT_PROBE = "M";
|
|
822
|
-
const ELLIPSIS_GLYPH = "…";
|
|
823
819
|
const INTRINSIC_MAX_WIDTH = Number.POSITIVE_INFINITY;
|
|
824
|
-
const PREPARED_TEXT_CACHE_CAPACITY = 512;
|
|
825
|
-
const FONT_SHIFT_CACHE_CAPACITY = 64;
|
|
826
|
-
const ELLIPSIS_WIDTH_CACHE_CAPACITY = 64;
|
|
827
|
-
const LINE_START_CURSOR = {
|
|
828
|
-
segmentIndex: 0,
|
|
829
|
-
graphemeIndex: 0
|
|
830
|
-
};
|
|
831
820
|
const MIN_CONTENT_WIDTH_EPSILON = .001;
|
|
832
|
-
const preparedTextCache = /* @__PURE__ */ new Map();
|
|
833
821
|
const fontShiftCache = /* @__PURE__ */ new Map();
|
|
834
822
|
const ellipsisWidthCache = /* @__PURE__ */ new Map();
|
|
835
|
-
const preparedUnitCache = /* @__PURE__ */ new WeakMap();
|
|
836
823
|
let sharedGraphemeSegmenter;
|
|
837
824
|
function readLruValue(cache, key) {
|
|
838
825
|
const cached = cache.get(key);
|
|
@@ -850,6 +837,106 @@ function writeLruValue(cache, key, value, capacity) {
|
|
|
850
837
|
cache.set(key, value);
|
|
851
838
|
return value;
|
|
852
839
|
}
|
|
840
|
+
function measureFontShift(ctx) {
|
|
841
|
+
const font = ctx.graphics.font;
|
|
842
|
+
const cached = readLruValue(fontShiftCache, font);
|
|
843
|
+
if (cached != null) return cached;
|
|
844
|
+
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText("M");
|
|
845
|
+
return writeLruValue(fontShiftCache, font, ascent - descent, 64);
|
|
846
|
+
}
|
|
847
|
+
function measureEllipsisWidth(ctx) {
|
|
848
|
+
const font = ctx.graphics.font;
|
|
849
|
+
const cached = readLruValue(ellipsisWidthCache, font);
|
|
850
|
+
if (cached != null) return cached;
|
|
851
|
+
return writeLruValue(ellipsisWidthCache, font, ctx.graphics.measureText("…").width, 64);
|
|
852
|
+
}
|
|
853
|
+
function getGraphemeSegmenter() {
|
|
854
|
+
if (sharedGraphemeSegmenter !== void 0) return sharedGraphemeSegmenter;
|
|
855
|
+
sharedGraphemeSegmenter = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
856
|
+
return sharedGraphemeSegmenter;
|
|
857
|
+
}
|
|
858
|
+
function splitGraphemes(text) {
|
|
859
|
+
const segmenter = getGraphemeSegmenter();
|
|
860
|
+
if (segmenter == null) return Array.from(text);
|
|
861
|
+
const graphemes = [];
|
|
862
|
+
for (const part of segmenter.segment(text)) graphemes.push(part.segment);
|
|
863
|
+
return graphemes;
|
|
864
|
+
}
|
|
865
|
+
function buildPrefixWidths(widths) {
|
|
866
|
+
const cumulativeWidths = [0];
|
|
867
|
+
let total = 0;
|
|
868
|
+
for (const width of widths) {
|
|
869
|
+
total += width;
|
|
870
|
+
cumulativeWidths.push(total);
|
|
871
|
+
}
|
|
872
|
+
return cumulativeWidths;
|
|
873
|
+
}
|
|
874
|
+
function buildSuffixWidths(widths) {
|
|
875
|
+
const cumulativeWidths = [0];
|
|
876
|
+
let total = 0;
|
|
877
|
+
for (let index = widths.length - 1; index >= 0; index -= 1) {
|
|
878
|
+
total += widths[index] ?? 0;
|
|
879
|
+
cumulativeWidths.push(total);
|
|
880
|
+
}
|
|
881
|
+
return cumulativeWidths;
|
|
882
|
+
}
|
|
883
|
+
function findMaxFittingCount(cumulativeWidths, maxWidth) {
|
|
884
|
+
if (maxWidth <= 0) return 0;
|
|
885
|
+
let low = 0;
|
|
886
|
+
let high = cumulativeWidths.length - 1;
|
|
887
|
+
while (low < high) {
|
|
888
|
+
const mid = Math.floor((low + high + 1) / 2);
|
|
889
|
+
if ((cumulativeWidths[mid] ?? 0) <= maxWidth) low = mid;
|
|
890
|
+
else high = mid - 1;
|
|
891
|
+
}
|
|
892
|
+
return low;
|
|
893
|
+
}
|
|
894
|
+
function normalizeMaxLines(maxLines) {
|
|
895
|
+
if (maxLines == null || !Number.isFinite(maxLines)) return;
|
|
896
|
+
return Math.max(1, Math.trunc(maxLines));
|
|
897
|
+
}
|
|
898
|
+
function selectEllipsisUnitCounts({ position, prefixWidths, suffixWidths, unitCount, availableWidth, getMaxSuffixCount = (prefixCount) => unitCount - prefixCount }) {
|
|
899
|
+
let prefixCount = 0;
|
|
900
|
+
let suffixCount = 0;
|
|
901
|
+
switch (position) {
|
|
902
|
+
case "start":
|
|
903
|
+
suffixCount = Math.min(unitCount, findMaxFittingCount(suffixWidths, availableWidth));
|
|
904
|
+
break;
|
|
905
|
+
case "middle": {
|
|
906
|
+
let bestVisibleUnits = -1;
|
|
907
|
+
let bestBalanceScore = Number.NEGATIVE_INFINITY;
|
|
908
|
+
for (let nextPrefixCount = 0; nextPrefixCount <= unitCount; nextPrefixCount += 1) {
|
|
909
|
+
const prefixWidth = prefixWidths[nextPrefixCount] ?? 0;
|
|
910
|
+
if (prefixWidth > availableWidth) break;
|
|
911
|
+
const remainingWidth = availableWidth - prefixWidth;
|
|
912
|
+
const maxSuffixCount = Math.max(0, getMaxSuffixCount(nextPrefixCount));
|
|
913
|
+
const nextSuffixCount = Math.min(maxSuffixCount, findMaxFittingCount(suffixWidths, remainingWidth));
|
|
914
|
+
const visibleUnits = nextPrefixCount + nextSuffixCount;
|
|
915
|
+
const balanceScore = -Math.abs(nextPrefixCount - nextSuffixCount);
|
|
916
|
+
if (visibleUnits > bestVisibleUnits || visibleUnits === bestVisibleUnits && balanceScore > bestBalanceScore || visibleUnits === bestVisibleUnits && balanceScore === bestBalanceScore && nextPrefixCount > prefixCount) {
|
|
917
|
+
prefixCount = nextPrefixCount;
|
|
918
|
+
suffixCount = nextSuffixCount;
|
|
919
|
+
bestVisibleUnits = visibleUnits;
|
|
920
|
+
bestBalanceScore = balanceScore;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
case "end":
|
|
926
|
+
prefixCount = Math.min(unitCount, findMaxFittingCount(prefixWidths, availableWidth));
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
return {
|
|
930
|
+
prefixCount,
|
|
931
|
+
suffixCount
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const LINE_START_CURSOR$1 = {
|
|
935
|
+
segmentIndex: 0,
|
|
936
|
+
graphemeIndex: 0
|
|
937
|
+
};
|
|
938
|
+
const preparedTextCache = /* @__PURE__ */ new Map();
|
|
939
|
+
const preparedUnitCache = /* @__PURE__ */ new WeakMap();
|
|
853
940
|
function getPreparedTextCacheKey(text, font, whiteSpace, wordBreak) {
|
|
854
941
|
return `${font}\u0000${whiteSpace}\u0000${wordBreak}\u0000${text}`;
|
|
855
942
|
}
|
|
@@ -860,64 +947,45 @@ function readPreparedText(text, font, whiteSpace, wordBreak) {
|
|
|
860
947
|
return writeLruValue(preparedTextCache, key, prepareWithSegments(text, font, {
|
|
861
948
|
whiteSpace,
|
|
862
949
|
wordBreak
|
|
863
|
-
}),
|
|
950
|
+
}), 512);
|
|
864
951
|
}
|
|
865
952
|
function readPreparedFirstLine(ctx, text, whiteSpace, wordBreak) {
|
|
866
|
-
const line = layoutNextLine(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), LINE_START_CURSOR, INTRINSIC_MAX_WIDTH);
|
|
953
|
+
const line = layoutNextLine(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), LINE_START_CURSOR$1, INTRINSIC_MAX_WIDTH);
|
|
867
954
|
if (line == null) return;
|
|
868
955
|
return {
|
|
869
956
|
text: line.text,
|
|
870
957
|
prepared: readPreparedText(line.text, ctx.graphics.font, whiteSpace, wordBreak)
|
|
871
958
|
};
|
|
872
959
|
}
|
|
873
|
-
function measureFontShift(ctx) {
|
|
874
|
-
const font = ctx.graphics.font;
|
|
875
|
-
const cached = readLruValue(fontShiftCache, font);
|
|
876
|
-
if (cached != null) return cached;
|
|
877
|
-
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(FONT_SHIFT_PROBE);
|
|
878
|
-
return writeLruValue(fontShiftCache, font, ascent - descent, FONT_SHIFT_CACHE_CAPACITY);
|
|
879
|
-
}
|
|
880
960
|
function measurePreparedMinContentWidth(prepared, overflowWrap = "break-word") {
|
|
881
961
|
let maxWidth = 0;
|
|
882
962
|
let maxAnyWidth = 0;
|
|
883
|
-
for (let
|
|
884
|
-
const segmentWidth = prepared.widths[
|
|
963
|
+
for (let index = 0; index < prepared.widths.length; index += 1) {
|
|
964
|
+
const segmentWidth = prepared.widths[index] ?? 0;
|
|
885
965
|
maxAnyWidth = Math.max(maxAnyWidth, segmentWidth);
|
|
886
|
-
const segment = prepared.segments[
|
|
966
|
+
const segment = prepared.segments[index];
|
|
887
967
|
if (segment != null && segment.trim().length > 0) {
|
|
888
|
-
const breakableWidths = prepared.breakableFitAdvances[
|
|
968
|
+
const breakableWidths = prepared.breakableFitAdvances[index];
|
|
889
969
|
const minContentWidth = overflowWrap === "anywhere" && breakableWidths != null && breakableWidths.length > 0 ? breakableWidths.reduce((widest, width) => Math.max(widest, width), 0) : segmentWidth;
|
|
890
970
|
maxWidth = Math.max(maxWidth, minContentWidth);
|
|
891
971
|
}
|
|
892
972
|
}
|
|
893
973
|
return maxWidth > 0 ? maxWidth : maxAnyWidth;
|
|
894
974
|
}
|
|
895
|
-
function getGraphemeSegmenter() {
|
|
896
|
-
if (sharedGraphemeSegmenter !== void 0) return sharedGraphemeSegmenter;
|
|
897
|
-
sharedGraphemeSegmenter = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
898
|
-
return sharedGraphemeSegmenter;
|
|
899
|
-
}
|
|
900
|
-
function splitGraphemes(text) {
|
|
901
|
-
const segmenter = getGraphemeSegmenter();
|
|
902
|
-
if (segmenter == null) return Array.from(text);
|
|
903
|
-
const graphemes = [];
|
|
904
|
-
for (const part of segmenter.segment(text)) graphemes.push(part.segment);
|
|
905
|
-
return graphemes;
|
|
906
|
-
}
|
|
907
975
|
function getPreparedUnits(prepared) {
|
|
908
976
|
const cached = preparedUnitCache.get(prepared);
|
|
909
977
|
if (cached != null) return cached;
|
|
910
978
|
const units = [];
|
|
911
|
-
for (let
|
|
912
|
-
const segment = prepared.segments[
|
|
913
|
-
const segmentWidth = prepared.widths[
|
|
914
|
-
const breakableWidths = prepared.breakableFitAdvances[
|
|
979
|
+
for (let index = 0; index < prepared.segments.length; index += 1) {
|
|
980
|
+
const segment = prepared.segments[index] ?? "";
|
|
981
|
+
const segmentWidth = prepared.widths[index] ?? 0;
|
|
982
|
+
const breakableWidths = prepared.breakableFitAdvances[index];
|
|
915
983
|
if (breakableWidths != null && segment.length > 0) {
|
|
916
984
|
const graphemes = splitGraphemes(segment);
|
|
917
985
|
if (graphemes.length === breakableWidths.length) {
|
|
918
|
-
for (let
|
|
919
|
-
text: graphemes[
|
|
920
|
-
width: breakableWidths[
|
|
986
|
+
for (let graphemeIndex = 0; graphemeIndex < graphemes.length; graphemeIndex += 1) units.push({
|
|
987
|
+
text: graphemes[graphemeIndex] ?? "",
|
|
988
|
+
width: breakableWidths[graphemeIndex] ?? 0
|
|
921
989
|
});
|
|
922
990
|
continue;
|
|
923
991
|
}
|
|
@@ -930,44 +998,18 @@ function getPreparedUnits(prepared) {
|
|
|
930
998
|
preparedUnitCache.set(prepared, units);
|
|
931
999
|
return units;
|
|
932
1000
|
}
|
|
933
|
-
function buildUnitPrefixWidths(units) {
|
|
934
|
-
const widths = [0];
|
|
935
|
-
let total = 0;
|
|
936
|
-
for (const unit of units) {
|
|
937
|
-
total += unit.width;
|
|
938
|
-
widths.push(total);
|
|
939
|
-
}
|
|
940
|
-
return widths;
|
|
941
|
-
}
|
|
942
|
-
function buildUnitSuffixWidths(units) {
|
|
943
|
-
const widths = [0];
|
|
944
|
-
let total = 0;
|
|
945
|
-
for (let i = units.length - 1; i >= 0; i -= 1) {
|
|
946
|
-
total += units[i]?.width ?? 0;
|
|
947
|
-
widths.push(total);
|
|
948
|
-
}
|
|
949
|
-
return widths;
|
|
950
|
-
}
|
|
951
|
-
function findMaxFittingCount(cumulativeWidths, maxWidth) {
|
|
952
|
-
if (maxWidth <= 0) return 0;
|
|
953
|
-
let low = 0;
|
|
954
|
-
let high = cumulativeWidths.length - 1;
|
|
955
|
-
while (low < high) {
|
|
956
|
-
const mid = Math.floor((low + high + 1) / 2);
|
|
957
|
-
if ((cumulativeWidths[mid] ?? 0) <= maxWidth) low = mid;
|
|
958
|
-
else high = mid - 1;
|
|
959
|
-
}
|
|
960
|
-
return low;
|
|
961
|
-
}
|
|
962
1001
|
function joinUnitText(units, start, end) {
|
|
963
1002
|
if (start >= end) return "";
|
|
964
1003
|
return units.slice(start, end).map((unit) => unit.text).join("");
|
|
965
1004
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/text/plain.ts
|
|
1007
|
+
const LINE_START_CURSOR = {
|
|
1008
|
+
segmentIndex: 0,
|
|
1009
|
+
graphemeIndex: 0
|
|
1010
|
+
};
|
|
1011
|
+
function clampMaxWidth(maxWidth) {
|
|
1012
|
+
return Math.max(0, maxWidth);
|
|
971
1013
|
}
|
|
972
1014
|
function createEllipsisOnlyLayout(ctx, maxWidth, shift) {
|
|
973
1015
|
const ellipsisWidth = measureEllipsisWidth(ctx);
|
|
@@ -979,11 +1021,22 @@ function createEllipsisOnlyLayout(ctx, maxWidth, shift) {
|
|
|
979
1021
|
};
|
|
980
1022
|
return {
|
|
981
1023
|
width: ellipsisWidth,
|
|
982
|
-
text:
|
|
1024
|
+
text: "…",
|
|
983
1025
|
shift,
|
|
984
1026
|
overflowed: true
|
|
985
1027
|
};
|
|
986
1028
|
}
|
|
1029
|
+
function toTextBlockLayout(lines, shift) {
|
|
1030
|
+
const mappedLines = lines.map((line) => ({
|
|
1031
|
+
width: line.width,
|
|
1032
|
+
text: line.text,
|
|
1033
|
+
shift
|
|
1034
|
+
}));
|
|
1035
|
+
return {
|
|
1036
|
+
width: mappedLines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1037
|
+
lines: mappedLines
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
987
1040
|
function layoutPreparedEllipsis(ctx, prepared, text, maxWidth, shift, position, forceEllipsis = false) {
|
|
988
1041
|
const intrinsicWidth = measureNaturalWidth(prepared);
|
|
989
1042
|
if (!forceEllipsis && intrinsicWidth <= maxWidth) return {
|
|
@@ -1002,52 +1055,29 @@ function layoutPreparedEllipsis(ctx, prepared, text, maxWidth, shift, position,
|
|
|
1002
1055
|
const units = getPreparedUnits(prepared);
|
|
1003
1056
|
if (units.length === 0) return createEllipsisOnlyLayout(ctx, maxWidth, shift);
|
|
1004
1057
|
const availableWidth = Math.max(0, maxWidth - ellipsisWidth);
|
|
1005
|
-
const prefixWidths =
|
|
1006
|
-
const suffixWidths =
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
let bestVisibleUnits = -1;
|
|
1015
|
-
let bestBalanceScore = Number.NEGATIVE_INFINITY;
|
|
1016
|
-
for (let nextPrefixCount = 0; nextPrefixCount <= units.length; nextPrefixCount += 1) {
|
|
1017
|
-
const prefixWidth = prefixWidths[nextPrefixCount] ?? 0;
|
|
1018
|
-
if (prefixWidth > availableWidth) break;
|
|
1019
|
-
const remainingWidth = availableWidth - prefixWidth;
|
|
1020
|
-
const maxSuffixCount = units.length - nextPrefixCount;
|
|
1021
|
-
const nextSuffixCount = Math.min(maxSuffixCount, findMaxFittingCount(suffixWidths, remainingWidth));
|
|
1022
|
-
const visibleUnits = nextPrefixCount + nextSuffixCount;
|
|
1023
|
-
const balanceScore = -Math.abs(nextPrefixCount - nextSuffixCount);
|
|
1024
|
-
if (visibleUnits > bestVisibleUnits || visibleUnits === bestVisibleUnits && balanceScore > bestBalanceScore || visibleUnits === bestVisibleUnits && balanceScore === bestBalanceScore && nextPrefixCount > prefixCount) {
|
|
1025
|
-
prefixCount = nextPrefixCount;
|
|
1026
|
-
suffixCount = nextSuffixCount;
|
|
1027
|
-
bestVisibleUnits = visibleUnits;
|
|
1028
|
-
bestBalanceScore = balanceScore;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
break;
|
|
1032
|
-
}
|
|
1033
|
-
case "end":
|
|
1034
|
-
prefixCount = Math.min(units.length, findMaxFittingCount(prefixWidths, availableWidth));
|
|
1035
|
-
break;
|
|
1036
|
-
}
|
|
1058
|
+
const prefixWidths = buildPrefixWidths(units.map((unit) => unit.width));
|
|
1059
|
+
const suffixWidths = buildSuffixWidths(units.map((unit) => unit.width));
|
|
1060
|
+
const { prefixCount, suffixCount } = selectEllipsisUnitCounts({
|
|
1061
|
+
position,
|
|
1062
|
+
prefixWidths,
|
|
1063
|
+
suffixWidths,
|
|
1064
|
+
unitCount: units.length,
|
|
1065
|
+
availableWidth
|
|
1066
|
+
});
|
|
1037
1067
|
const prefixWidth = prefixWidths[prefixCount] ?? 0;
|
|
1038
1068
|
const suffixWidth = suffixWidths[suffixCount] ?? 0;
|
|
1039
1069
|
const prefixText = joinUnitText(units, 0, prefixCount);
|
|
1040
1070
|
const suffixText = joinUnitText(units, units.length - suffixCount, units.length);
|
|
1041
1071
|
return {
|
|
1042
1072
|
width: prefixWidth + ellipsisWidth + suffixWidth,
|
|
1043
|
-
text: `${prefixText}
|
|
1073
|
+
text: `${prefixText}…${suffixText}`,
|
|
1044
1074
|
shift,
|
|
1045
1075
|
overflowed: true
|
|
1046
1076
|
};
|
|
1047
1077
|
}
|
|
1048
|
-
function
|
|
1049
|
-
if (
|
|
1050
|
-
return
|
|
1078
|
+
function layoutForcedEllipsizedLine(ctx, text, maxWidth, shift, whiteSpace = "normal", wordBreak = "normal") {
|
|
1079
|
+
if (text.length === 0) return createEllipsisOnlyLayout(ctx, maxWidth, shift);
|
|
1080
|
+
return layoutPreparedEllipsis(ctx, readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), text, maxWidth, shift, "end", true);
|
|
1051
1081
|
}
|
|
1052
1082
|
function layoutFirstLineIntrinsic(ctx, text, whiteSpace = "normal", wordBreak = "normal") {
|
|
1053
1083
|
const firstLine = readPreparedFirstLine(ctx, text, whiteSpace, wordBreak);
|
|
@@ -1080,26 +1110,17 @@ function layoutTextIntrinsic(ctx, text, whiteSpace = "normal", wordBreak = "norm
|
|
|
1080
1110
|
width: 0,
|
|
1081
1111
|
lines: []
|
|
1082
1112
|
};
|
|
1083
|
-
|
|
1084
|
-
const lines = intrinsic.lines.map((line) => ({
|
|
1085
|
-
width: line.width,
|
|
1086
|
-
text: line.text,
|
|
1087
|
-
shift
|
|
1088
|
-
}));
|
|
1089
|
-
return {
|
|
1090
|
-
width: lines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1091
|
-
lines
|
|
1092
|
-
};
|
|
1113
|
+
return toTextBlockLayout(intrinsic.lines, measureFontShift(ctx));
|
|
1093
1114
|
}
|
|
1094
1115
|
function layoutFirstLine(ctx, text, maxWidth, whiteSpace = "normal", wordBreak = "normal") {
|
|
1095
|
-
|
|
1116
|
+
const clampedMaxWidth = clampMaxWidth(maxWidth);
|
|
1096
1117
|
const shift = measureFontShift(ctx);
|
|
1097
|
-
if (
|
|
1118
|
+
if (clampedMaxWidth === 0) return {
|
|
1098
1119
|
width: 0,
|
|
1099
1120
|
text: "",
|
|
1100
1121
|
shift
|
|
1101
1122
|
};
|
|
1102
|
-
const line = layoutNextLine(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), LINE_START_CURSOR,
|
|
1123
|
+
const line = layoutNextLine(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), LINE_START_CURSOR, clampedMaxWidth);
|
|
1103
1124
|
if (line == null) return {
|
|
1104
1125
|
width: 0,
|
|
1105
1126
|
text: "",
|
|
@@ -1112,7 +1133,7 @@ function layoutFirstLine(ctx, text, maxWidth, whiteSpace = "normal", wordBreak =
|
|
|
1112
1133
|
};
|
|
1113
1134
|
}
|
|
1114
1135
|
function layoutEllipsizedFirstLine(ctx, text, maxWidth, ellipsisPosition = "end", whiteSpace = "normal", wordBreak = "normal") {
|
|
1115
|
-
|
|
1136
|
+
const clampedMaxWidth = clampMaxWidth(maxWidth);
|
|
1116
1137
|
const firstLine = readPreparedFirstLine(ctx, text, whiteSpace, wordBreak);
|
|
1117
1138
|
if (firstLine == null) return {
|
|
1118
1139
|
width: 0,
|
|
@@ -1121,25 +1142,21 @@ function layoutEllipsizedFirstLine(ctx, text, maxWidth, ellipsisPosition = "end"
|
|
|
1121
1142
|
overflowed: false
|
|
1122
1143
|
};
|
|
1123
1144
|
const shift = measureFontShift(ctx);
|
|
1124
|
-
if (
|
|
1145
|
+
if (clampedMaxWidth === 0) return {
|
|
1125
1146
|
width: 0,
|
|
1126
1147
|
text: "",
|
|
1127
1148
|
shift,
|
|
1128
1149
|
overflowed: true
|
|
1129
1150
|
};
|
|
1130
|
-
return layoutPreparedEllipsis(ctx, firstLine.prepared, firstLine.text,
|
|
1131
|
-
}
|
|
1132
|
-
function layoutForcedEllipsizedLine(ctx, text, maxWidth, shift, whiteSpace = "normal", wordBreak = "normal") {
|
|
1133
|
-
if (text.length === 0) return createEllipsisOnlyLayout(ctx, maxWidth, shift);
|
|
1134
|
-
return layoutPreparedEllipsis(ctx, readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), text, maxWidth, shift, "end", true);
|
|
1151
|
+
return layoutPreparedEllipsis(ctx, firstLine.prepared, firstLine.text, clampedMaxWidth, shift, ellipsisPosition);
|
|
1135
1152
|
}
|
|
1136
1153
|
function measureText(ctx, text, maxWidth, whiteSpace = "normal", wordBreak = "normal") {
|
|
1137
|
-
|
|
1138
|
-
if (
|
|
1154
|
+
const clampedMaxWidth = clampMaxWidth(maxWidth);
|
|
1155
|
+
if (clampedMaxWidth === 0) return {
|
|
1139
1156
|
width: 0,
|
|
1140
1157
|
lineCount: 0
|
|
1141
1158
|
};
|
|
1142
|
-
const { maxLineWidth: width, lineCount } = measureLineStats(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak),
|
|
1159
|
+
const { maxLineWidth: width, lineCount } = measureLineStats(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), clampedMaxWidth);
|
|
1143
1160
|
return {
|
|
1144
1161
|
width,
|
|
1145
1162
|
lineCount
|
|
@@ -1159,33 +1176,25 @@ function measureTextMinContent(ctx, text, whiteSpace = "normal", wordBreak = "no
|
|
|
1159
1176
|
};
|
|
1160
1177
|
}
|
|
1161
1178
|
function layoutText(ctx, text, maxWidth, whiteSpace = "normal", wordBreak = "normal") {
|
|
1162
|
-
|
|
1163
|
-
if (
|
|
1179
|
+
const clampedMaxWidth = clampMaxWidth(maxWidth);
|
|
1180
|
+
if (clampedMaxWidth === 0) return {
|
|
1164
1181
|
width: 0,
|
|
1165
1182
|
lines: []
|
|
1166
1183
|
};
|
|
1167
|
-
const layout = layoutWithLines(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak),
|
|
1184
|
+
const layout = layoutWithLines(readPreparedText(text, ctx.graphics.font, whiteSpace, wordBreak), clampedMaxWidth, 0);
|
|
1168
1185
|
if (layout.lines.length === 0) return {
|
|
1169
1186
|
width: 0,
|
|
1170
1187
|
lines: []
|
|
1171
1188
|
};
|
|
1172
|
-
|
|
1173
|
-
const lines = layout.lines.map((line) => ({
|
|
1174
|
-
width: line.width,
|
|
1175
|
-
text: line.text,
|
|
1176
|
-
shift
|
|
1177
|
-
}));
|
|
1178
|
-
return {
|
|
1179
|
-
width: lines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1180
|
-
lines
|
|
1181
|
-
};
|
|
1189
|
+
return toTextBlockLayout(layout.lines, measureFontShift(ctx));
|
|
1182
1190
|
}
|
|
1183
1191
|
function layoutTextWithOverflow(ctx, text, maxWidth, options = {}) {
|
|
1192
|
+
const clampedMaxWidth = clampMaxWidth(maxWidth);
|
|
1184
1193
|
const whiteSpace = options.whiteSpace ?? "normal";
|
|
1185
1194
|
const wordBreak = options.wordBreak ?? "normal";
|
|
1186
1195
|
const overflow = options.overflow ?? "clip";
|
|
1187
1196
|
const normalizedMaxLines = normalizeMaxLines(options.maxLines);
|
|
1188
|
-
const layout = layoutText(ctx, text,
|
|
1197
|
+
const layout = layoutText(ctx, text, clampedMaxWidth, whiteSpace, wordBreak);
|
|
1189
1198
|
if (normalizedMaxLines == null || layout.lines.length <= normalizedMaxLines) return {
|
|
1190
1199
|
width: layout.width,
|
|
1191
1200
|
lines: layout.lines.map((line) => ({
|
|
@@ -1205,7 +1214,7 @@ function layoutTextWithOverflow(ctx, text, maxWidth, options = {}) {
|
|
|
1205
1214
|
};
|
|
1206
1215
|
const shift = visibleLines[visibleLines.length - 1]?.shift ?? measureFontShift(ctx);
|
|
1207
1216
|
const lastVisibleLine = visibleLines[visibleLines.length - 1];
|
|
1208
|
-
const ellipsizedLastLine = lastVisibleLine == null || lastVisibleLine.text.length === 0 ? createEllipsisOnlyLayout(ctx,
|
|
1217
|
+
const ellipsizedLastLine = lastVisibleLine == null || lastVisibleLine.text.length === 0 ? createEllipsisOnlyLayout(ctx, clampedMaxWidth, shift) : layoutForcedEllipsizedLine(ctx, lastVisibleLine.text, clampedMaxWidth, shift, whiteSpace, wordBreak);
|
|
1209
1218
|
const lines = [...visibleLines.slice(0, -1).map((line) => ({
|
|
1210
1219
|
...line,
|
|
1211
1220
|
overflowed: false
|
|
@@ -1219,40 +1228,50 @@ function layoutTextWithOverflow(ctx, text, maxWidth, options = {}) {
|
|
|
1219
1228
|
overflowed: true
|
|
1220
1229
|
};
|
|
1221
1230
|
}
|
|
1231
|
+
//#endregion
|
|
1232
|
+
//#region src/text/rich.ts
|
|
1222
1233
|
const RICH_PREPARED_CACHE_CAPACITY = 256;
|
|
1223
1234
|
const richPreparedCache = /* @__PURE__ */ new Map();
|
|
1235
|
+
function withFont(ctx, font, cb) {
|
|
1236
|
+
const previousFont = ctx.graphics.font;
|
|
1237
|
+
ctx.graphics.font = font;
|
|
1238
|
+
try {
|
|
1239
|
+
return cb();
|
|
1240
|
+
} finally {
|
|
1241
|
+
ctx.graphics.font = previousFont;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1224
1244
|
function getRichPreparedCacheKey(spans, defaultFont) {
|
|
1225
|
-
return spans.map((
|
|
1245
|
+
return spans.map((span) => `${span.font ?? defaultFont}\u0000${span.text}\u0000${span.break ?? ""}\u0000${span.extraWidth ?? 0}`).join("");
|
|
1226
1246
|
}
|
|
1227
1247
|
function readRichPrepared(spans, defaultFont) {
|
|
1228
1248
|
const key = getRichPreparedCacheKey(spans, defaultFont);
|
|
1229
1249
|
const cached = readLruValue(richPreparedCache, key);
|
|
1230
1250
|
if (cached != null) return cached;
|
|
1231
|
-
return writeLruValue(richPreparedCache, key, prepareRichInline(spans.map((
|
|
1232
|
-
text:
|
|
1233
|
-
font:
|
|
1234
|
-
break:
|
|
1235
|
-
extraWidth:
|
|
1251
|
+
return writeLruValue(richPreparedCache, key, prepareRichInline(spans.map((span) => ({
|
|
1252
|
+
text: span.text,
|
|
1253
|
+
font: span.font ?? defaultFont,
|
|
1254
|
+
break: span.break,
|
|
1255
|
+
extraWidth: span.extraWidth
|
|
1236
1256
|
}))), RICH_PREPARED_CACHE_CAPACITY);
|
|
1237
1257
|
}
|
|
1238
|
-
function
|
|
1258
|
+
function measureRichFragmentShift(ctx, font) {
|
|
1259
|
+
return withFont(ctx, font, () => measureFontShift(ctx));
|
|
1260
|
+
}
|
|
1261
|
+
function materializeRichLine(ctx, spans, defaultFont, defaultColor, lineRange, overflowed) {
|
|
1239
1262
|
const richLine = materializeRichInlineLineRange(readRichPrepared(spans, defaultFont), lineRange);
|
|
1240
|
-
const fragments = richLine.fragments.map((
|
|
1241
|
-
const span = spans[
|
|
1242
|
-
const
|
|
1243
|
-
const
|
|
1244
|
-
const prevFont = ctx.graphics.font;
|
|
1245
|
-
ctx.graphics.font = fragFont;
|
|
1246
|
-
const shift = measureFontShift(ctx);
|
|
1247
|
-
ctx.graphics.font = prevFont;
|
|
1263
|
+
const fragments = richLine.fragments.map((fragment) => {
|
|
1264
|
+
const span = spans[fragment.itemIndex];
|
|
1265
|
+
const font = span?.font ?? defaultFont;
|
|
1266
|
+
const color = span?.color ?? defaultColor;
|
|
1248
1267
|
return {
|
|
1249
|
-
itemIndex:
|
|
1250
|
-
text:
|
|
1251
|
-
font
|
|
1252
|
-
|
|
1253
|
-
gapBefore:
|
|
1254
|
-
occupiedWidth:
|
|
1255
|
-
shift
|
|
1268
|
+
itemIndex: fragment.itemIndex,
|
|
1269
|
+
text: fragment.text,
|
|
1270
|
+
font,
|
|
1271
|
+
color,
|
|
1272
|
+
gapBefore: fragment.gapBefore,
|
|
1273
|
+
occupiedWidth: fragment.occupiedWidth,
|
|
1274
|
+
shift: measureRichFragmentShift(ctx, font)
|
|
1256
1275
|
};
|
|
1257
1276
|
});
|
|
1258
1277
|
return {
|
|
@@ -1261,7 +1280,176 @@ function materializeRichLine(ctx, spans, defaultFont, defaultStyle, lineRange, o
|
|
|
1261
1280
|
overflowed
|
|
1262
1281
|
};
|
|
1263
1282
|
}
|
|
1264
|
-
function
|
|
1283
|
+
function flattenRichLineUnits(line) {
|
|
1284
|
+
const units = [];
|
|
1285
|
+
for (let fragmentIndex = 0; fragmentIndex < line.fragments.length; fragmentIndex += 1) {
|
|
1286
|
+
const fragment = line.fragments[fragmentIndex];
|
|
1287
|
+
const fragmentUnits = getPreparedUnits(readPreparedText(fragment.text, fragment.font, "normal", "normal"));
|
|
1288
|
+
if (fragmentUnits.length === 0) continue;
|
|
1289
|
+
const textWidth = fragmentUnits.reduce((total, unit) => total + unit.width, 0);
|
|
1290
|
+
const trailingExtraWidth = Math.max(0, fragment.occupiedWidth - textWidth);
|
|
1291
|
+
for (let unitIndex = 0; unitIndex < fragmentUnits.length; unitIndex += 1) {
|
|
1292
|
+
const unit = fragmentUnits[unitIndex];
|
|
1293
|
+
units.push({
|
|
1294
|
+
fragmentIndex,
|
|
1295
|
+
itemIndex: fragment.itemIndex,
|
|
1296
|
+
text: unit.text,
|
|
1297
|
+
width: unit.width + (unitIndex === fragmentUnits.length - 1 ? trailingExtraWidth : 0),
|
|
1298
|
+
font: fragment.font,
|
|
1299
|
+
color: fragment.color,
|
|
1300
|
+
leadingGap: unitIndex === 0 ? fragment.gapBefore : 0
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return units;
|
|
1305
|
+
}
|
|
1306
|
+
function buildRichPrefixWidths(units) {
|
|
1307
|
+
return buildPrefixWidths(units.map((unit) => unit.leadingGap + unit.width));
|
|
1308
|
+
}
|
|
1309
|
+
function buildRichSuffixWidths(units) {
|
|
1310
|
+
const widths = [0];
|
|
1311
|
+
let total = 0;
|
|
1312
|
+
for (let index = units.length - 1; index >= 0; index -= 1) {
|
|
1313
|
+
const unit = units[index];
|
|
1314
|
+
total += unit.width;
|
|
1315
|
+
if (widths.length > 1) total += unit.leadingGap;
|
|
1316
|
+
widths.push(total);
|
|
1317
|
+
}
|
|
1318
|
+
return widths;
|
|
1319
|
+
}
|
|
1320
|
+
function materializeRichFragmentsFromUnits(units, start, end, suppressLeadingGap) {
|
|
1321
|
+
const fragments = [];
|
|
1322
|
+
for (let index = start; index < end; index += 1) {
|
|
1323
|
+
const unit = units[index];
|
|
1324
|
+
const previous = fragments[fragments.length - 1];
|
|
1325
|
+
const previousUnit = units[index - 1];
|
|
1326
|
+
if (previous != null && previousUnit != null && previousUnit.fragmentIndex === unit.fragmentIndex) {
|
|
1327
|
+
previous.text += unit.text;
|
|
1328
|
+
previous.occupiedWidth += unit.width;
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
fragments.push({
|
|
1332
|
+
itemIndex: unit.itemIndex,
|
|
1333
|
+
text: unit.text,
|
|
1334
|
+
font: unit.font,
|
|
1335
|
+
color: unit.color,
|
|
1336
|
+
gapBefore: fragments.length === 0 && suppressLeadingGap ? 0 : unit.leadingGap,
|
|
1337
|
+
occupiedWidth: unit.width,
|
|
1338
|
+
shift: 0
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
return fragments;
|
|
1342
|
+
}
|
|
1343
|
+
function measureRichFragmentsShift(ctx, fragments) {
|
|
1344
|
+
return fragments.map((fragment) => ({
|
|
1345
|
+
...fragment,
|
|
1346
|
+
shift: measureRichFragmentShift(ctx, fragment.font)
|
|
1347
|
+
}));
|
|
1348
|
+
}
|
|
1349
|
+
function createRichEllipsisFragment(ctx, font, color) {
|
|
1350
|
+
return withFont(ctx, font, () => ({
|
|
1351
|
+
itemIndex: -1,
|
|
1352
|
+
text: "…",
|
|
1353
|
+
font,
|
|
1354
|
+
color,
|
|
1355
|
+
gapBefore: 0,
|
|
1356
|
+
occupiedWidth: measureEllipsisWidth(ctx),
|
|
1357
|
+
shift: measureFontShift(ctx)
|
|
1358
|
+
}));
|
|
1359
|
+
}
|
|
1360
|
+
function createRichEllipsisOnlyLayout(ctx, maxWidth, font, color) {
|
|
1361
|
+
const ellipsis = createRichEllipsisFragment(ctx, font, color);
|
|
1362
|
+
if (ellipsis.occupiedWidth > maxWidth) return {
|
|
1363
|
+
width: 0,
|
|
1364
|
+
fragments: [],
|
|
1365
|
+
overflowed: true
|
|
1366
|
+
};
|
|
1367
|
+
return {
|
|
1368
|
+
width: ellipsis.occupiedWidth,
|
|
1369
|
+
fragments: [ellipsis],
|
|
1370
|
+
overflowed: true
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function layoutPreparedRichEllipsis(ctx, line, maxWidth, defaultFont, defaultColor, position) {
|
|
1374
|
+
if (!line.overflowed && line.width <= maxWidth) return {
|
|
1375
|
+
...line,
|
|
1376
|
+
overflowed: false
|
|
1377
|
+
};
|
|
1378
|
+
const units = flattenRichLineUnits(line);
|
|
1379
|
+
const fallbackFragment = line.fragments[0];
|
|
1380
|
+
const ellipsisOnly = createRichEllipsisOnlyLayout(ctx, maxWidth, fallbackFragment?.font ?? defaultFont, fallbackFragment?.color ?? defaultColor);
|
|
1381
|
+
if (ellipsisOnly.fragments.length === 0 || units.length === 0) return ellipsisOnly;
|
|
1382
|
+
const ellipsisWidth = ellipsisOnly.width;
|
|
1383
|
+
const availableWidth = Math.max(0, maxWidth - ellipsisWidth);
|
|
1384
|
+
const prefixWidths = buildRichPrefixWidths(units);
|
|
1385
|
+
const suffixWidths = buildRichSuffixWidths(units);
|
|
1386
|
+
const { prefixCount, suffixCount } = selectEllipsisUnitCounts({
|
|
1387
|
+
position,
|
|
1388
|
+
prefixWidths,
|
|
1389
|
+
suffixWidths,
|
|
1390
|
+
unitCount: units.length,
|
|
1391
|
+
availableWidth,
|
|
1392
|
+
getMaxSuffixCount: position === "middle" ? (nextPrefixCount) => Math.max(0, units.length - nextPrefixCount - 1) : void 0
|
|
1393
|
+
});
|
|
1394
|
+
const prefixFragments = measureRichFragmentsShift(ctx, materializeRichFragmentsFromUnits(units, 0, prefixCount, false));
|
|
1395
|
+
const suffixFragments = measureRichFragmentsShift(ctx, materializeRichFragmentsFromUnits(units, units.length - suffixCount, units.length, true));
|
|
1396
|
+
const ellipsisSource = position === "start" ? suffixFragments[0] ?? line.fragments[0] : position === "middle" ? prefixFragments[prefixFragments.length - 1] ?? suffixFragments[0] ?? line.fragments[line.fragments.length - 1] : prefixFragments[prefixFragments.length - 1] ?? line.fragments[line.fragments.length - 1];
|
|
1397
|
+
const ellipsis = createRichEllipsisFragment(ctx, ellipsisSource?.font ?? defaultFont, ellipsisSource?.color ?? defaultColor);
|
|
1398
|
+
const fragments = position === "start" ? [ellipsis, ...suffixFragments] : position === "middle" ? [
|
|
1399
|
+
...prefixFragments,
|
|
1400
|
+
ellipsis,
|
|
1401
|
+
...suffixFragments
|
|
1402
|
+
] : [...prefixFragments, ellipsis];
|
|
1403
|
+
return {
|
|
1404
|
+
width: position === "start" ? ellipsis.occupiedWidth + (suffixWidths[suffixCount] ?? 0) : position === "middle" ? (prefixWidths[prefixCount] ?? 0) + ellipsis.occupiedWidth + (suffixWidths[suffixCount] ?? 0) : (prefixWidths[prefixCount] ?? 0) + ellipsis.occupiedWidth,
|
|
1405
|
+
fragments,
|
|
1406
|
+
overflowed: true
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
function layoutRichFirstLineIntrinsic(ctx, spans, defaultFont, defaultColor) {
|
|
1410
|
+
if (spans.length === 0) return {
|
|
1411
|
+
width: 0,
|
|
1412
|
+
fragments: [],
|
|
1413
|
+
overflowed: false
|
|
1414
|
+
};
|
|
1415
|
+
const lineRange = layoutNextRichInlineLineRange(readRichPrepared(spans, defaultFont), INTRINSIC_MAX_WIDTH);
|
|
1416
|
+
if (lineRange == null) return {
|
|
1417
|
+
width: 0,
|
|
1418
|
+
fragments: [],
|
|
1419
|
+
overflowed: false
|
|
1420
|
+
};
|
|
1421
|
+
return materializeRichLine(ctx, spans, defaultFont, defaultColor, lineRange, false);
|
|
1422
|
+
}
|
|
1423
|
+
function layoutRichFirstLine(ctx, spans, maxWidth, defaultFont, defaultColor) {
|
|
1424
|
+
const clampedMaxWidth = Math.max(0, maxWidth);
|
|
1425
|
+
if (spans.length === 0 || clampedMaxWidth === 0) return {
|
|
1426
|
+
width: 0,
|
|
1427
|
+
fragments: [],
|
|
1428
|
+
overflowed: false
|
|
1429
|
+
};
|
|
1430
|
+
const lineRange = layoutNextRichInlineLineRange(readRichPrepared(spans, defaultFont), clampedMaxWidth);
|
|
1431
|
+
if (lineRange == null) return {
|
|
1432
|
+
width: 0,
|
|
1433
|
+
fragments: [],
|
|
1434
|
+
overflowed: false
|
|
1435
|
+
};
|
|
1436
|
+
return materializeRichLine(ctx, spans, defaultFont, defaultColor, lineRange, false);
|
|
1437
|
+
}
|
|
1438
|
+
function layoutRichEllipsizedFirstLine(ctx, spans, maxWidth, defaultFont, defaultColor, ellipsisPosition = "end") {
|
|
1439
|
+
const clampedMaxWidth = Math.max(0, maxWidth);
|
|
1440
|
+
const intrinsicLine = layoutRichFirstLineIntrinsic(ctx, spans, defaultFont, defaultColor);
|
|
1441
|
+
if (intrinsicLine.fragments.length === 0) return {
|
|
1442
|
+
...intrinsicLine,
|
|
1443
|
+
overflowed: false
|
|
1444
|
+
};
|
|
1445
|
+
if (clampedMaxWidth === 0) return {
|
|
1446
|
+
width: 0,
|
|
1447
|
+
fragments: [],
|
|
1448
|
+
overflowed: true
|
|
1449
|
+
};
|
|
1450
|
+
return layoutPreparedRichEllipsis(ctx, intrinsicLine, clampedMaxWidth, defaultFont, defaultColor, ellipsisPosition);
|
|
1451
|
+
}
|
|
1452
|
+
function measureRichText(_ctx, spans, maxWidth, defaultFont) {
|
|
1265
1453
|
if (spans.length === 0) return {
|
|
1266
1454
|
width: 0,
|
|
1267
1455
|
lineCount: 0
|
|
@@ -1272,7 +1460,7 @@ function measureRichText(ctx, spans, maxWidth, defaultFont) {
|
|
|
1272
1460
|
lineCount
|
|
1273
1461
|
};
|
|
1274
1462
|
}
|
|
1275
|
-
function measureRichTextIntrinsic(
|
|
1463
|
+
function measureRichTextIntrinsic(_ctx, spans, defaultFont) {
|
|
1276
1464
|
if (spans.length === 0) return {
|
|
1277
1465
|
width: 0,
|
|
1278
1466
|
lineCount: 0
|
|
@@ -1283,7 +1471,7 @@ function measureRichTextIntrinsic(ctx, spans, defaultFont) {
|
|
|
1283
1471
|
lineCount
|
|
1284
1472
|
};
|
|
1285
1473
|
}
|
|
1286
|
-
function measureRichTextMinContent(
|
|
1474
|
+
function measureRichTextMinContent(_ctx, spans, defaultFont, overflowWrap = "break-word") {
|
|
1287
1475
|
if (spans.length === 0) return {
|
|
1288
1476
|
width: 0,
|
|
1289
1477
|
lineCount: 0
|
|
@@ -1292,8 +1480,8 @@ function measureRichTextMinContent(ctx, spans, defaultFont, overflowWrap = "brea
|
|
|
1292
1480
|
for (const span of spans) {
|
|
1293
1481
|
if (span.text.trim().length === 0) continue;
|
|
1294
1482
|
const font = span.font ?? defaultFont;
|
|
1295
|
-
const
|
|
1296
|
-
if (
|
|
1483
|
+
const spanMinWidth = measurePreparedMinContentWidth(readPreparedText(span.text, font, "normal", "normal"), overflowWrap) + (span.extraWidth ?? 0);
|
|
1484
|
+
if (spanMinWidth > maxWidth) maxWidth = spanMinWidth;
|
|
1297
1485
|
}
|
|
1298
1486
|
if (maxWidth === 0) return {
|
|
1299
1487
|
width: 0,
|
|
@@ -1305,7 +1493,7 @@ function measureRichTextMinContent(ctx, spans, defaultFont, overflowWrap = "brea
|
|
|
1305
1493
|
lineCount
|
|
1306
1494
|
};
|
|
1307
1495
|
}
|
|
1308
|
-
function layoutRichText(ctx, spans, maxWidth, defaultFont,
|
|
1496
|
+
function layoutRichText(ctx, spans, maxWidth, defaultFont, defaultColor) {
|
|
1309
1497
|
if (spans.length === 0) return {
|
|
1310
1498
|
width: 0,
|
|
1311
1499
|
lines: [],
|
|
@@ -1313,124 +1501,50 @@ function layoutRichText(ctx, spans, maxWidth, defaultFont, defaultStyle) {
|
|
|
1313
1501
|
};
|
|
1314
1502
|
const prepared = readRichPrepared(spans, defaultFont);
|
|
1315
1503
|
const lineRanges = [];
|
|
1316
|
-
walkRichInlineLineRanges(prepared, maxWidth, (
|
|
1504
|
+
walkRichInlineLineRanges(prepared, maxWidth, (lineRange) => lineRanges.push(lineRange));
|
|
1317
1505
|
if (lineRanges.length === 0) return {
|
|
1318
1506
|
width: 0,
|
|
1319
1507
|
lines: [],
|
|
1320
1508
|
overflowed: false
|
|
1321
1509
|
};
|
|
1322
|
-
const lines = lineRanges.map((
|
|
1510
|
+
const lines = lineRanges.map((lineRange) => materializeRichLine(ctx, spans, defaultFont, defaultColor, lineRange, false));
|
|
1323
1511
|
return {
|
|
1324
|
-
width: lines.reduce((
|
|
1512
|
+
width: lines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1325
1513
|
lines,
|
|
1326
1514
|
overflowed: false
|
|
1327
1515
|
};
|
|
1328
1516
|
}
|
|
1329
|
-
function layoutRichTextIntrinsic(ctx, spans, defaultFont,
|
|
1330
|
-
return layoutRichText(ctx, spans, INTRINSIC_MAX_WIDTH, defaultFont,
|
|
1517
|
+
function layoutRichTextIntrinsic(ctx, spans, defaultFont, defaultColor) {
|
|
1518
|
+
return layoutRichText(ctx, spans, INTRINSIC_MAX_WIDTH, defaultFont, defaultColor);
|
|
1331
1519
|
}
|
|
1332
|
-
function layoutRichTextWithOverflow(ctx, spans, maxWidth, defaultFont,
|
|
1520
|
+
function layoutRichTextWithOverflow(ctx, spans, maxWidth, defaultFont, defaultColor, maxLines, overflow = "clip") {
|
|
1333
1521
|
if (spans.length === 0) return {
|
|
1334
1522
|
width: 0,
|
|
1335
1523
|
lines: [],
|
|
1336
1524
|
overflowed: false
|
|
1337
1525
|
};
|
|
1338
1526
|
const normalizedMaxLines = normalizeMaxLines(maxLines);
|
|
1339
|
-
const layout = layoutRichText(ctx, spans, maxWidth, defaultFont,
|
|
1527
|
+
const layout = layoutRichText(ctx, spans, maxWidth, defaultFont, defaultColor);
|
|
1340
1528
|
if (normalizedMaxLines == null || layout.lines.length <= normalizedMaxLines) return layout;
|
|
1341
1529
|
const visibleLines = layout.lines.slice(0, normalizedMaxLines);
|
|
1342
1530
|
if (overflow !== "ellipsis") return {
|
|
1343
|
-
width: visibleLines.reduce((
|
|
1531
|
+
width: visibleLines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1344
1532
|
lines: visibleLines,
|
|
1345
1533
|
overflowed: true
|
|
1346
1534
|
};
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
width:
|
|
1350
|
-
|
|
1535
|
+
const lastVisibleLine = visibleLines[visibleLines.length - 1];
|
|
1536
|
+
const ellipsizedLastLine = lastVisibleLine == null ? {
|
|
1537
|
+
width: 0,
|
|
1538
|
+
fragments: [],
|
|
1351
1539
|
overflowed: true
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
const
|
|
1357
|
-
ctx.graphics.font = prevFont1;
|
|
1358
|
-
if (maxWidth <= 0 || ellipsisWidth > maxWidth) {
|
|
1359
|
-
const truncatedLine = {
|
|
1360
|
-
width: 0,
|
|
1361
|
-
fragments: [],
|
|
1362
|
-
overflowed: true
|
|
1363
|
-
};
|
|
1364
|
-
return {
|
|
1365
|
-
width: visibleLines.slice(0, -1).reduce((max, line) => Math.max(max, line.width), 0),
|
|
1366
|
-
lines: [...visibleLines.slice(0, -1), truncatedLine],
|
|
1367
|
-
overflowed: true
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
const budget = maxWidth - ellipsisWidth;
|
|
1371
|
-
const resultFragments = [];
|
|
1372
|
-
let usedWidth = 0;
|
|
1373
|
-
let ellipsisFont = lastFrag.font;
|
|
1374
|
-
let ellipsisStyle = lastFrag.style;
|
|
1375
|
-
let truncated = false;
|
|
1376
|
-
for (let fi = 0; fi < lastLine.fragments.length; fi++) {
|
|
1377
|
-
const frag = lastLine.fragments[fi];
|
|
1378
|
-
const neededGap = fi === 0 ? 0 : frag.gapBefore;
|
|
1379
|
-
const fragTotal = neededGap + frag.occupiedWidth;
|
|
1380
|
-
if (usedWidth + fragTotal <= budget) {
|
|
1381
|
-
resultFragments.push({
|
|
1382
|
-
...frag,
|
|
1383
|
-
gapBefore: fi === 0 ? 0 : frag.gapBefore
|
|
1384
|
-
});
|
|
1385
|
-
usedWidth += fragTotal;
|
|
1386
|
-
} else {
|
|
1387
|
-
ellipsisFont = frag.font;
|
|
1388
|
-
ellipsisStyle = frag.style;
|
|
1389
|
-
const remaining = budget - usedWidth - neededGap;
|
|
1390
|
-
if (remaining > 0 && frag.text.length > 0) {
|
|
1391
|
-
const units = getPreparedUnits(readPreparedText(frag.text, frag.font, "normal", "normal"));
|
|
1392
|
-
let charWidth = 0;
|
|
1393
|
-
let charText = "";
|
|
1394
|
-
for (const unit of units) {
|
|
1395
|
-
if (charWidth + unit.width > remaining) break;
|
|
1396
|
-
charWidth += unit.width;
|
|
1397
|
-
charText += unit.text;
|
|
1398
|
-
}
|
|
1399
|
-
if (charText.length > 0) {
|
|
1400
|
-
resultFragments.push({
|
|
1401
|
-
...frag,
|
|
1402
|
-
text: charText,
|
|
1403
|
-
occupiedWidth: charWidth,
|
|
1404
|
-
gapBefore: fi === 0 ? 0 : frag.gapBefore
|
|
1405
|
-
});
|
|
1406
|
-
usedWidth += neededGap + charWidth;
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
truncated = true;
|
|
1410
|
-
break;
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
const prevFont2 = ctx.graphics.font;
|
|
1414
|
-
ctx.graphics.font = ellipsisFont;
|
|
1415
|
-
const ellipsisShift = measureFontShift(ctx);
|
|
1416
|
-
ctx.graphics.font = prevFont2;
|
|
1417
|
-
resultFragments.push({
|
|
1418
|
-
itemIndex: -1,
|
|
1419
|
-
text: ELLIPSIS_GLYPH,
|
|
1420
|
-
font: ellipsisFont,
|
|
1421
|
-
style: ellipsisStyle,
|
|
1422
|
-
gapBefore: 0,
|
|
1423
|
-
occupiedWidth: ellipsisWidth,
|
|
1424
|
-
shift: ellipsisShift
|
|
1425
|
-
});
|
|
1426
|
-
const lastLineResult = {
|
|
1427
|
-
width: usedWidth + ellipsisWidth,
|
|
1428
|
-
fragments: resultFragments,
|
|
1429
|
-
overflowed: truncated || layout.lines.length > normalizedMaxLines
|
|
1430
|
-
};
|
|
1540
|
+
} : layoutPreparedRichEllipsis(ctx, {
|
|
1541
|
+
...lastVisibleLine,
|
|
1542
|
+
overflowed: true
|
|
1543
|
+
}, maxWidth, defaultFont, defaultColor, "end");
|
|
1544
|
+
const lines = [...visibleLines.slice(0, -1), ellipsizedLastLine];
|
|
1431
1545
|
return {
|
|
1432
|
-
width:
|
|
1433
|
-
lines
|
|
1546
|
+
width: lines.reduce((maxLineWidth, line) => Math.max(maxLineWidth, line.width), 0),
|
|
1547
|
+
lines,
|
|
1434
1548
|
overflowed: true
|
|
1435
1549
|
};
|
|
1436
1550
|
}
|
|
@@ -1484,6 +1598,15 @@ function getRichMultiLineOverflowLayoutKey(maxWidth) {
|
|
|
1484
1598
|
function shouldUseMultilineOverflowLayout(options) {
|
|
1485
1599
|
return options.maxLines != null;
|
|
1486
1600
|
}
|
|
1601
|
+
function shouldReadConstrainedOverflowLayout(maxWidth, options) {
|
|
1602
|
+
return maxWidth != null && shouldUseMultilineOverflowLayout(options);
|
|
1603
|
+
}
|
|
1604
|
+
function measureBlockLayout(layout) {
|
|
1605
|
+
return {
|
|
1606
|
+
width: layout.width,
|
|
1607
|
+
lineCount: layout.lines.length
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1487
1610
|
function getSingleLineMinContentLayoutKey() {
|
|
1488
1611
|
return "single:min-content";
|
|
1489
1612
|
}
|
|
@@ -1497,6 +1620,10 @@ function getSingleLineLayout(node, ctx, text, options) {
|
|
|
1497
1620
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1498
1621
|
return readCachedTextLayout(node, ctx, getSingleLineLayoutKey(maxWidth), () => maxWidth == null ? layoutFirstLineIntrinsic(ctx, text, options.whiteSpace, options.wordBreak) : options.overflow === "ellipsis" ? layoutEllipsizedFirstLine(ctx, text, maxWidth, options.ellipsisPosition ?? "end", options.whiteSpace, options.wordBreak) : layoutFirstLine(ctx, text, maxWidth, options.whiteSpace, options.wordBreak));
|
|
1499
1622
|
}
|
|
1623
|
+
function getRichSingleLineLayout(node, ctx, spans, options) {
|
|
1624
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1625
|
+
return readCachedTextLayout(node, ctx, getSingleLineLayoutKey(maxWidth), () => maxWidth == null ? layoutRichFirstLineIntrinsic(ctx, spans, options.font, options.color) : options.overflow === "ellipsis" ? layoutRichEllipsizedFirstLine(ctx, spans, maxWidth, options.font, options.color, options.ellipsisPosition ?? "end") : layoutRichFirstLine(ctx, spans, maxWidth, options.font, options.color));
|
|
1626
|
+
}
|
|
1500
1627
|
function getMultiLineOverflowLayout(node, ctx, text, options) {
|
|
1501
1628
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1502
1629
|
return readCachedTextLayout(node, ctx, getMultiLineOverflowLayoutKey(maxWidth), () => layoutTextWithOverflow(ctx, text, maxWidth ?? 0, {
|
|
@@ -1508,18 +1635,12 @@ function getMultiLineOverflowLayout(node, ctx, text, options) {
|
|
|
1508
1635
|
}
|
|
1509
1636
|
function getMultiLineMeasureLayout(node, ctx, text, options) {
|
|
1510
1637
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1511
|
-
if (maxWidth
|
|
1512
|
-
const layout = getMultiLineOverflowLayout(node, ctx, text, options);
|
|
1513
|
-
return {
|
|
1514
|
-
width: layout.width,
|
|
1515
|
-
lineCount: layout.lines.length
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1638
|
+
if (shouldReadConstrainedOverflowLayout(maxWidth, options)) return measureBlockLayout(getMultiLineOverflowLayout(node, ctx, text, options));
|
|
1518
1639
|
return readCachedTextLayout(node, ctx, getMultiLineMeasureLayoutKey(maxWidth), () => maxWidth == null ? measureTextIntrinsic(ctx, text, options.whiteSpace, options.wordBreak) : measureText(ctx, text, maxWidth, options.whiteSpace, options.wordBreak));
|
|
1519
1640
|
}
|
|
1520
1641
|
function getMultiLineDrawLayout(node, ctx, text, options) {
|
|
1521
1642
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1522
|
-
if (maxWidth
|
|
1643
|
+
if (shouldReadConstrainedOverflowLayout(maxWidth, options)) return getMultiLineOverflowLayout(node, ctx, text, options);
|
|
1523
1644
|
return readCachedTextLayout(node, ctx, getMultiLineDrawLayoutKey(maxWidth), () => maxWidth == null ? layoutTextIntrinsic(ctx, text, options.whiteSpace, options.wordBreak) : layoutText(ctx, text, maxWidth, options.whiteSpace, options.wordBreak));
|
|
1524
1645
|
}
|
|
1525
1646
|
function getSingleLineMinContentLayout(node, ctx, text, options) {
|
|
@@ -1533,28 +1654,39 @@ function getSingleLineMinContentLayout(node, ctx, text, options) {
|
|
|
1533
1654
|
};
|
|
1534
1655
|
});
|
|
1535
1656
|
}
|
|
1657
|
+
function getRichSingleLineMinContentWidth(node, ctx, spans, options) {
|
|
1658
|
+
return readCachedTextLayout(node, ctx, getSingleLineMinContentLayoutKey(), () => measureRichTextMinContent(ctx, spans, options.font, options.overflowWrap).width);
|
|
1659
|
+
}
|
|
1660
|
+
function drawRichLine(ctx, line, fallbackColor, x, y, lineHeight) {
|
|
1661
|
+
let cursorX = x;
|
|
1662
|
+
for (let fragmentIndex = 0; fragmentIndex < line.fragments.length; fragmentIndex += 1) {
|
|
1663
|
+
const fragment = line.fragments[fragmentIndex];
|
|
1664
|
+
cursorX += fragment.gapBefore;
|
|
1665
|
+
ctx.with((g) => {
|
|
1666
|
+
g.font = fragment.font;
|
|
1667
|
+
g.fillStyle = ctx.resolveDynValue(fragment.color ?? fallbackColor);
|
|
1668
|
+
g.textAlign = "left";
|
|
1669
|
+
g.fillText(fragment.text, cursorX, y + (lineHeight + fragment.shift) / 2);
|
|
1670
|
+
});
|
|
1671
|
+
cursorX += fragment.occupiedWidth;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1536
1674
|
function getMultiLineMinContentLayout(node, ctx, text, whiteSpace, wordBreak, overflowWrap) {
|
|
1537
1675
|
return readCachedTextLayout(node, ctx, getMultiLineMinContentLayoutKey(), () => measureTextMinContent(ctx, text, whiteSpace, wordBreak, overflowWrap));
|
|
1538
1676
|
}
|
|
1539
1677
|
function getRichMultiLineMeasureLayout(node, ctx, spans, options) {
|
|
1540
1678
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1541
|
-
if (maxWidth
|
|
1542
|
-
const layout = getRichMultiLineOverflowLayout(node, ctx, spans, options);
|
|
1543
|
-
return {
|
|
1544
|
-
width: layout.width,
|
|
1545
|
-
lineCount: layout.lines.length
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1679
|
+
if (shouldReadConstrainedOverflowLayout(maxWidth, options)) return measureBlockLayout(getRichMultiLineOverflowLayout(node, ctx, spans, options));
|
|
1548
1680
|
return readCachedTextLayout(node, ctx, getRichMultiLineMeasureLayoutKey(maxWidth), () => maxWidth == null ? measureRichTextIntrinsic(ctx, spans, options.font) : measureRichText(ctx, spans, maxWidth, options.font));
|
|
1549
1681
|
}
|
|
1550
1682
|
function getRichMultiLineOverflowLayout(node, ctx, spans, options) {
|
|
1551
1683
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1552
|
-
return readCachedTextLayout(node, ctx, getRichMultiLineOverflowLayoutKey(maxWidth), () => layoutRichTextWithOverflow(ctx, spans, maxWidth ?? 0, options.font, options.
|
|
1684
|
+
return readCachedTextLayout(node, ctx, getRichMultiLineOverflowLayoutKey(maxWidth), () => layoutRichTextWithOverflow(ctx, spans, maxWidth ?? 0, options.font, options.color, options.maxLines, options.overflow));
|
|
1553
1685
|
}
|
|
1554
1686
|
function getRichMultiLineDrawLayout(node, ctx, spans, options) {
|
|
1555
1687
|
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1556
|
-
if (maxWidth
|
|
1557
|
-
return readCachedTextLayout(node, ctx, getRichMultiLineDrawLayoutKey(maxWidth), () => maxWidth == null ? layoutRichTextIntrinsic(ctx, spans, options.font, options.
|
|
1688
|
+
if (shouldReadConstrainedOverflowLayout(maxWidth, options)) return getRichMultiLineOverflowLayout(node, ctx, spans, options);
|
|
1689
|
+
return readCachedTextLayout(node, ctx, getRichMultiLineDrawLayoutKey(maxWidth), () => maxWidth == null ? layoutRichTextIntrinsic(ctx, spans, options.font, options.color) : layoutRichText(ctx, spans, maxWidth, options.font, options.color));
|
|
1558
1690
|
}
|
|
1559
1691
|
function getRichMultiLineMinContentLayout(node, ctx, spans, options) {
|
|
1560
1692
|
return readCachedTextLayout(node, ctx, getRichMultiLineMinContentLayoutKey(), () => measureRichTextMinContent(ctx, spans, options.font, options.overflowWrap));
|
|
@@ -1621,7 +1753,7 @@ var MultilineText = class {
|
|
|
1621
1753
|
cursorX += frag.gapBefore;
|
|
1622
1754
|
ctx.with((g) => {
|
|
1623
1755
|
g.font = frag.font;
|
|
1624
|
-
g.fillStyle = ctx.resolveDynValue(frag.
|
|
1756
|
+
g.fillStyle = ctx.resolveDynValue(frag.color ?? this.options.color);
|
|
1625
1757
|
if (align === "right") g.textAlign = "right";
|
|
1626
1758
|
else if (align === "center") g.textAlign = "center";
|
|
1627
1759
|
else g.textAlign = "left";
|
|
@@ -1635,7 +1767,7 @@ var MultilineText = class {
|
|
|
1635
1767
|
}
|
|
1636
1768
|
return ctx.with((g) => {
|
|
1637
1769
|
g.font = this.options.font;
|
|
1638
|
-
g.fillStyle = ctx.resolveDynValue(this.options.
|
|
1770
|
+
g.fillStyle = ctx.resolveDynValue(this.options.color);
|
|
1639
1771
|
const { width, lines } = getMultiLineDrawLayout(this, ctx, this.text, this.options);
|
|
1640
1772
|
switch (resolvePhysicalTextAlign(this.options)) {
|
|
1641
1773
|
case "left":
|
|
@@ -1673,7 +1805,7 @@ var MultilineText = class {
|
|
|
1673
1805
|
*/
|
|
1674
1806
|
var Text = class {
|
|
1675
1807
|
/**
|
|
1676
|
-
* @param text Source text to measure and draw.
|
|
1808
|
+
* @param text Source text to measure and draw. Pass an `InlineSpan[]` for mixed inline styles.
|
|
1677
1809
|
* @param options Text layout and drawing options.
|
|
1678
1810
|
*/
|
|
1679
1811
|
constructor(text, options) {
|
|
@@ -1681,9 +1813,17 @@ var Text = class {
|
|
|
1681
1813
|
this.options = options;
|
|
1682
1814
|
}
|
|
1683
1815
|
measure(ctx) {
|
|
1816
|
+
if (typeof this.text !== "string") {
|
|
1817
|
+
const { width } = getRichSingleLineLayout(this, ctx, this.text, this.options);
|
|
1818
|
+
return {
|
|
1819
|
+
width,
|
|
1820
|
+
height: this.options.lineHeight
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
const text = this.text;
|
|
1684
1824
|
return ctx.with((g) => {
|
|
1685
1825
|
g.font = this.options.font;
|
|
1686
|
-
const { width } = getSingleLineLayout(this, ctx,
|
|
1826
|
+
const { width } = getSingleLineLayout(this, ctx, text, this.options);
|
|
1687
1827
|
return {
|
|
1688
1828
|
width,
|
|
1689
1829
|
height: this.options.lineHeight
|
|
@@ -1691,9 +1831,14 @@ var Text = class {
|
|
|
1691
1831
|
});
|
|
1692
1832
|
}
|
|
1693
1833
|
measureMinContent(ctx) {
|
|
1834
|
+
if (typeof this.text !== "string") return {
|
|
1835
|
+
width: getRichSingleLineMinContentWidth(this, ctx, this.text, this.options),
|
|
1836
|
+
height: this.options.lineHeight
|
|
1837
|
+
};
|
|
1838
|
+
const text = this.text;
|
|
1694
1839
|
return ctx.with((g) => {
|
|
1695
1840
|
g.font = this.options.font;
|
|
1696
|
-
const { width } = getSingleLineMinContentLayout(this, ctx,
|
|
1841
|
+
const { width } = getSingleLineMinContentLayout(this, ctx, text, this.options);
|
|
1697
1842
|
return {
|
|
1698
1843
|
width,
|
|
1699
1844
|
height: this.options.lineHeight
|
|
@@ -1701,11 +1846,16 @@ var Text = class {
|
|
|
1701
1846
|
});
|
|
1702
1847
|
}
|
|
1703
1848
|
draw(ctx, x, y) {
|
|
1849
|
+
if (typeof this.text !== "string") {
|
|
1850
|
+
drawRichLine(ctx, getRichSingleLineLayout(this, ctx, this.text, this.options), this.options.color, x, y, this.options.lineHeight);
|
|
1851
|
+
return false;
|
|
1852
|
+
}
|
|
1853
|
+
const text = this.text;
|
|
1704
1854
|
return ctx.with((g) => {
|
|
1705
1855
|
g.font = this.options.font;
|
|
1706
|
-
g.fillStyle = ctx.resolveDynValue(this.options.
|
|
1707
|
-
const
|
|
1708
|
-
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
1856
|
+
g.fillStyle = ctx.resolveDynValue(this.options.color);
|
|
1857
|
+
const layout = getSingleLineLayout(this, ctx, text, this.options);
|
|
1858
|
+
g.fillText(layout.text, x, y + (this.options.lineHeight + layout.shift) / 2);
|
|
1709
1859
|
return false;
|
|
1710
1860
|
});
|
|
1711
1861
|
}
|