@gct-paas/word 0.1.24 → 0.1.25

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.
@@ -60,7 +60,6 @@ export declare const DEFAULT_PAGE_SIZE: {
60
60
  export declare const DEFAULT_PAGE_PADDING: [number, number, number, number];
61
61
  export declare const DEFAULT_CELL_PADDING: [number, number, number, number];
62
62
  export declare const DEFAULT_FONT_SIZE = 16;
63
- /** 回退链在不同 OS 上可能命中不同实体字体;跨平台一致布局请配合 Web 字体加载与 TextUtil.awaitDocumentFonts() */
64
63
  export declare const DEFAULT_FONT_FAMILY = "Roboto,RobotoDraft,Helvetica,Arial,sans-serif";
65
64
  export declare const DEFAULT_TEXT_COLOR = "black";
66
65
  export declare const ZERO_WIDTH_SPACE = "\u200B";
@@ -1,5 +1,6 @@
1
1
  import { WrText } from '../../../model/document';
2
2
  import { LayoutContext } from '../../LayoutContext';
3
+ import { TextRun } from '../../../view/runs/TextRun';
3
4
  import { TextWidget } from '../../../view/runs/TextWidget';
4
5
  import { HandlerContext } from '../../types';
5
6
  type ValueType = string | number | undefined | null;
@@ -50,6 +51,7 @@ export declare class FieldBaseHandler {
50
51
  ctx: HandlerContext;
51
52
  fontSize: number;
52
53
  label: string;
54
+ layoutStyle?: Partial<TextRun>;
53
55
  extra?: Partial<TextWidget>;
54
56
  }): void;
55
57
  /**
@@ -97,6 +97,12 @@ export declare class TextRun extends LayoutNode implements ITextRun {
97
97
  descent: number;
98
98
  style?: TextStyle;
99
99
  constructor(options: TextRunOptions);
100
+ /**
101
+ * 构建与 Konva 渲染一致的度量参数。
102
+ * 须与段落 layout 使用的 TextRun 样式一致;若只传 fontSize 会按默认字体度量,
103
+ * 加粗/自定义字体后 advance 偏窄,易出现字母叠画。
104
+ */
105
+ static measurePayload(text: string, layoutStyle?: Partial<TextRun>): Konva.TextConfig;
100
106
  /**
101
107
  * 计算文字大小 度量
102
108
  * 优化版本
@@ -18,14 +18,14 @@ export declare class TextUtil {
18
18
  * - kg·m²
19
19
  * 等符号贴太近的问题
20
20
  */
21
+ /**
22
+ * 中间点类:CoreText / DirectWrite 下 advance 常偏窄,仅靠较小 ratio 仍可能与字母叠画;
23
+ * 在 getVisualSpacing 内另有 em 下限(与平台无关)。
24
+ */
25
+ private static readonly MIDDLE_DOT_EXTRA_FLOOR_EM;
21
26
  private static readonly VISUAL_SPACING_MAP;
22
27
  private static fontMetricsCache;
23
28
  private static layoutSizeCache;
24
- /**
25
- * 等待文档字体就绪后再做 Canvas 度量,可减轻 Mac/Windows 因 Web 字体未加载导致的度量差异。
26
- * 无 Font Loading API 时立即 resolve。
27
- */
28
- static awaitDocumentFonts(): Promise<void>;
29
29
  /**
30
30
  * 验证中文字符
31
31
  * @param char
@@ -56,32 +56,16 @@ export declare class TextUtil {
56
56
  * @param fontSize
57
57
  * @returns
58
58
  */
59
+ /** 数学/单位中间的点号(不含列表圆点 •) */
60
+ private static isMiddleDotSpacingChar;
59
61
  static getVisualSpacing(char: string, fontSize: number): number;
60
- /** 与度量路径一致:lineHeight 固定为 1 的样式副本 */
61
- private static measureBase;
62
62
  /**
63
- * 字体级缓存键(不含 text),与 getFontMetrics 语义一致
64
- */
65
- private static measureStyleKey;
66
- /**
67
- * 布局缓存键:样式签名 + 编码后的文本
68
- */
69
- private static layoutCacheKey;
70
- private static touchLru;
71
- private static evictOldestIfFull;
72
- /**
73
- * 单次 Konva.Text 计算 width / height(advance width 含视觉间距)
74
- */
75
- private static computeLayoutSizeUncached;
76
- /**
77
- * 一次度量:布局宽高 + 字体 ascent/descent(双缓存均未命中时只创建一个 Konva.Text)
63
+ * 生成文本度量缓存键
64
+ * @param payload
65
+ * @param includeText
66
+ * @returns
78
67
  */
79
- static measureTextComplete(payload: Konva.TextConfig): {
80
- width: number;
81
- height: number;
82
- ascent: number;
83
- descent: number;
84
- };
68
+ private static getMeasureCacheKey;
85
69
  /**
86
70
  * 计算字体度量
87
71
  * ascent / descent
package/dist/index.es.js CHANGED
@@ -26693,31 +26693,28 @@ class TextUtil {
26693
26693
  * - kg·m²
26694
26694
  * 等符号贴太近的问题
26695
26695
  */
26696
+ /**
26697
+ * 中间点类:CoreText / DirectWrite 下 advance 常偏窄,仅靠较小 ratio 仍可能与字母叠画;
26698
+ * 在 getVisualSpacing 内另有 em 下限(与平台无关)。
26699
+ */
26700
+ static MIDDLE_DOT_EXTRA_FLOOR_EM = 0.24;
26696
26701
  static VISUAL_SPACING_MAP = {
26697
26702
  "·": 0.24,
26698
26703
  "•": 0.24,
26699
- "/": 0.16,
26700
- "/": 0.16,
26701
- "|": 0.12,
26702
- ":": 0.06,
26703
- "": 0.08
26704
+ "": 0.24,
26705
+ // ⋅ DOT OPERATOR(常见于 N⋅m 等)
26706
+ "": 0.24,
26707
+ // ∙ BULLET OPERATOR
26708
+ "·": 0.24,
26709
+ // · GREEK ANO TELEIA,易与 · 混用
26710
+ "/": 0.2,
26711
+ "/": 0.2,
26712
+ "|": 0.16,
26713
+ ":": 0.12,
26714
+ ":": 0.12
26704
26715
  };
26705
26716
  static fontMetricsCache = /* @__PURE__ */ new Map();
26706
26717
  static layoutSizeCache = /* @__PURE__ */ new Map();
26707
- /**
26708
- * 等待文档字体就绪后再做 Canvas 度量,可减轻 Mac/Windows 因 Web 字体未加载导致的度量差异。
26709
- * 无 Font Loading API 时立即 resolve。
26710
- */
26711
- static awaitDocumentFonts() {
26712
- if (typeof document === "undefined") {
26713
- return Promise.resolve();
26714
- }
26715
- const fonts = document.fonts;
26716
- if (!fonts || typeof fonts.ready?.then !== "function") {
26717
- return Promise.resolve();
26718
- }
26719
- return fonts.ready;
26720
- }
26721
26718
  /**
26722
26719
  * 验证中文字符
26723
26720
  * @param char
@@ -26755,119 +26752,41 @@ class TextUtil {
26755
26752
  * @param fontSize
26756
26753
  * @returns
26757
26754
  */
26755
+ /** 数学/单位中间的点号(不含列表圆点 •) */
26756
+ static isMiddleDotSpacingChar(char) {
26757
+ return char === "·" || // · MIDDLE DOT
26758
+ char === "⋅" || // ⋅ DOT OPERATOR
26759
+ char === "∙" || // ∙ BULLET OPERATOR
26760
+ char === "·";
26761
+ }
26758
26762
  static getVisualSpacing(char, fontSize2) {
26759
26763
  const ratio = this.VISUAL_SPACING_MAP[char];
26760
26764
  if (!ratio) {
26761
26765
  return 0;
26762
26766
  }
26763
- return fontSize2 * ratio;
26764
- }
26765
- /** 与度量路径一致:lineHeight 固定为 1 的样式副本 */
26766
- static measureBase(payload) {
26767
- return Object.assign({}, payload, { lineHeight: 1 });
26768
- }
26769
- /**
26770
- * 字体级缓存键(不含 text),与 getFontMetrics 语义一致
26771
- */
26772
- static measureStyleKey(payload) {
26773
- const c2 = this.measureBase(payload);
26774
- return [
26775
- c2.fontSize ?? "",
26776
- c2.fontFamily ?? "",
26777
- c2.fontStyle ?? "",
26778
- c2.fontVariant ?? "",
26779
- c2.lineHeight ?? "",
26780
- c2.padding ?? "",
26781
- c2.letterSpacing ?? ""
26782
- ].join("");
26783
- }
26784
- /**
26785
- * 布局缓存键:样式签名 + 编码后的文本
26786
- */
26787
- static layoutCacheKey(payload) {
26788
- const c2 = this.measureBase(payload);
26789
- return `${this.measureStyleKey(payload)}${encodeURIComponent(c2.text ?? "")}`;
26790
- }
26791
- static touchLru(map2, key, value) {
26792
- map2.delete(key);
26793
- map2.set(key, value);
26794
- }
26795
- static evictOldestIfFull(map2, limit) {
26796
- if (map2.size >= limit) {
26797
- const firstKey = map2.keys().next().value;
26798
- map2.delete(firstKey);
26767
+ let space = fontSize2 * ratio;
26768
+ if (this.isMiddleDotSpacingChar(char)) {
26769
+ space = Math.max(space, fontSize2 * this.MIDDLE_DOT_EXTRA_FLOOR_EM);
26799
26770
  }
26771
+ return space;
26800
26772
  }
26801
26773
  /**
26802
- * 单次 Konva.Text 计算 width / height(advance width 含视觉间距)
26803
- */
26804
- static computeLayoutSizeUncached(payload) {
26805
- const config = this.measureBase(payload);
26806
- const text = new Konva.Text(config);
26807
- const char = payload.text ?? "";
26808
- const fontSize2 = payload.fontSize ?? 0;
26809
- const width = this.isFullWidthChar(char) ? fontSize2 : text.width() + this.getVisualSpacing(char, fontSize2);
26810
- return {
26811
- width,
26812
- height: text.height()
26813
- };
26814
- }
26815
- /**
26816
- * 一次度量:布局宽高 + 字体 ascent/descent(双缓存均未命中时只创建一个 Konva.Text)
26774
+ * 生成文本度量缓存键
26775
+ * @param payload
26776
+ * @param includeText
26777
+ * @returns
26817
26778
  */
26818
- static measureTextComplete(payload) {
26819
- const layoutKey = this.layoutCacheKey(payload);
26820
- const fontKey = this.measureStyleKey(payload);
26821
- const layoutCached = this.layoutSizeCache.get(layoutKey);
26822
- const fontCached = this.fontMetricsCache.get(fontKey);
26823
- if (layoutCached && fontCached) {
26824
- this.touchLru(this.layoutSizeCache, layoutKey, layoutCached);
26825
- this.touchLru(this.fontMetricsCache, fontKey, fontCached);
26826
- return {
26827
- width: layoutCached.width,
26828
- height: layoutCached.height,
26829
- ascent: fontCached.ascent,
26830
- descent: fontCached.descent
26831
- };
26832
- }
26833
- if (layoutCached && !fontCached) {
26834
- this.touchLru(this.layoutSizeCache, layoutKey, layoutCached);
26835
- const { ascent: ascent2, descent: descent2 } = this.getFontMetrics(payload);
26836
- return {
26837
- width: layoutCached.width,
26838
- height: layoutCached.height,
26839
- ascent: ascent2,
26840
- descent: descent2
26841
- };
26842
- }
26843
- if (!layoutCached && fontCached) {
26844
- const layout2 = this.computeLayoutSizeUncached(payload);
26845
- this.evictOldestIfFull(this.layoutSizeCache, this.LAYOUT_SIZE_CACHE_LIMIT);
26846
- this.layoutSizeCache.set(layoutKey, layout2);
26847
- this.touchLru(this.fontMetricsCache, fontKey, fontCached);
26848
- return {
26849
- width: layout2.width,
26850
- height: layout2.height,
26851
- ascent: fontCached.ascent,
26852
- descent: fontCached.descent
26853
- };
26854
- }
26855
- const config = this.measureBase(payload);
26856
- const text = new Konva.Text(config);
26857
- const char = payload.text ?? "";
26858
- const fontSize2 = payload.fontSize ?? 0;
26859
- const width = this.isFullWidthChar(char) ? fontSize2 : text.width() + this.getVisualSpacing(char, fontSize2);
26860
- const height = text.height();
26861
- const sizeHg = text.measureSize("Hg");
26862
- const ascent = sizeHg.actualBoundingBoxAscent;
26863
- const descent = sizeHg.actualBoundingBoxDescent;
26864
- const layout = { width, height };
26865
- const font = { ascent, descent };
26866
- this.evictOldestIfFull(this.layoutSizeCache, this.LAYOUT_SIZE_CACHE_LIMIT);
26867
- this.evictOldestIfFull(this.fontMetricsCache, this.FONT_METRICS_CACHE_LIMIT);
26868
- this.layoutSizeCache.set(layoutKey, layout);
26869
- this.fontMetricsCache.set(fontKey, font);
26870
- return { width, height, ascent, descent };
26779
+ static getMeasureCacheKey(payload, includeText = true) {
26780
+ return JSON.stringify({
26781
+ text: includeText ? payload.text ?? "" : "",
26782
+ fontSize: payload.fontSize ?? "",
26783
+ fontFamily: payload.fontFamily ?? "",
26784
+ fontStyle: payload.fontStyle ?? "",
26785
+ fontVariant: payload.fontVariant ?? "",
26786
+ lineHeight: payload.lineHeight ?? "",
26787
+ padding: payload.padding ?? "",
26788
+ letterSpacing: payload.letterSpacing ?? ""
26789
+ });
26871
26790
  }
26872
26791
  /**
26873
26792
  * 计算字体度量
@@ -26882,10 +26801,11 @@ class TextUtil {
26882
26801
  text: targetText,
26883
26802
  lineHeight: 1
26884
26803
  });
26885
- const cacheKey = this.measureStyleKey(config);
26804
+ const cacheKey = this.getMeasureCacheKey(config, false);
26886
26805
  if (this.fontMetricsCache.has(cacheKey)) {
26887
26806
  const result2 = this.fontMetricsCache.get(cacheKey);
26888
- this.touchLru(this.fontMetricsCache, cacheKey, result2);
26807
+ this.fontMetricsCache.delete(cacheKey);
26808
+ this.fontMetricsCache.set(cacheKey, result2);
26889
26809
  return result2;
26890
26810
  }
26891
26811
  const text = new Konva.Text(config);
@@ -26894,7 +26814,10 @@ class TextUtil {
26894
26814
  ascent: size.actualBoundingBoxAscent,
26895
26815
  descent: size.actualBoundingBoxDescent
26896
26816
  };
26897
- this.evictOldestIfFull(this.fontMetricsCache, this.FONT_METRICS_CACHE_LIMIT);
26817
+ if (this.fontMetricsCache.size >= this.FONT_METRICS_CACHE_LIMIT) {
26818
+ const firstKey = this.fontMetricsCache.keys().next().value;
26819
+ this.fontMetricsCache.delete(firstKey);
26820
+ }
26898
26821
  this.fontMetricsCache.set(cacheKey, result);
26899
26822
  return result;
26900
26823
  }
@@ -26923,7 +26846,13 @@ class TextUtil {
26923
26846
  if (this.isFullWidthChar(char)) {
26924
26847
  return fontSize2;
26925
26848
  }
26926
- return this.getLayoutSize(payload).width;
26849
+ const config = Object.assign({}, payload, {
26850
+ lineHeight: 1
26851
+ });
26852
+ const text = new Konva.Text(config);
26853
+ const glyphWidth = text.width();
26854
+ const visualSpacing = this.getVisualSpacing(char, fontSize2);
26855
+ return glyphWidth + visualSpacing;
26927
26856
  }
26928
26857
  /**
26929
26858
  * 计算单字符布局大小
@@ -26935,16 +26864,27 @@ class TextUtil {
26935
26864
  * @returns
26936
26865
  */
26937
26866
  static getLayoutSize(payload) {
26938
- const cacheKey = this.layoutCacheKey(payload);
26867
+ const config = Object.assign({}, payload, {
26868
+ lineHeight: 1
26869
+ });
26870
+ const cacheKey = this.getMeasureCacheKey(config);
26939
26871
  if (this.layoutSizeCache.has(cacheKey)) {
26940
26872
  const result2 = this.layoutSizeCache.get(cacheKey);
26941
- this.touchLru(this.layoutSizeCache, cacheKey, result2);
26942
- return { width: result2.width, height: result2.height };
26873
+ this.layoutSizeCache.delete(cacheKey);
26874
+ this.layoutSizeCache.set(cacheKey, result2);
26875
+ return result2;
26876
+ }
26877
+ const text = new Konva.Text(config);
26878
+ const result = {
26879
+ width: this.getAdvanceWidth(payload),
26880
+ height: text.height()
26881
+ };
26882
+ if (this.layoutSizeCache.size >= this.LAYOUT_SIZE_CACHE_LIMIT) {
26883
+ const firstKey = this.layoutSizeCache.keys().next().value;
26884
+ this.layoutSizeCache.delete(firstKey);
26943
26885
  }
26944
- const result = this.computeLayoutSizeUncached(payload);
26945
- this.evictOldestIfFull(this.layoutSizeCache, this.LAYOUT_SIZE_CACHE_LIMIT);
26946
26886
  this.layoutSizeCache.set(cacheKey, result);
26947
- return { width: result.width, height: result.height };
26887
+ return result;
26948
26888
  }
26949
26889
  /**
26950
26890
  * 清除度量缓存
@@ -27017,6 +26957,37 @@ class TextRun extends LayoutNode {
27017
26957
  Object.assign(this, options.style);
27018
26958
  }
27019
26959
  }
26960
+ /**
26961
+ * 构建与 Konva 渲染一致的度量参数。
26962
+ * 须与段落 layout 使用的 TextRun 样式一致;若只传 fontSize 会按默认字体度量,
26963
+ * 加粗/自定义字体后 advance 偏窄,易出现字母叠画。
26964
+ */
26965
+ static measurePayload(text, layoutStyle) {
26966
+ const ls = layoutStyle ?? {};
26967
+ const fontSize2 = ls.fontSize ?? DEFAULT_FONT_SIZE;
26968
+ const fontFamily = ls.fontFamily ?? DEFAULT_FONT_FAMILY;
26969
+ let fontStyle;
26970
+ if (ls.bold && ls.italic) {
26971
+ fontStyle = "bold italic";
26972
+ } else if (ls.bold) {
26973
+ fontStyle = "bold";
26974
+ } else if (ls.italic) {
26975
+ fontStyle = "italic";
26976
+ }
26977
+ const payload = {
26978
+ text,
26979
+ fontSize: fontSize2,
26980
+ fontFamily,
26981
+ lineHeight: 1
26982
+ };
26983
+ if (fontStyle) {
26984
+ payload.fontStyle = fontStyle;
26985
+ }
26986
+ if (ls.letterSpacing !== void 0 && ls.letterSpacing !== null) {
26987
+ payload.letterSpacing = ls.letterSpacing;
26988
+ }
26989
+ return payload;
26990
+ }
27020
26991
  /**
27021
26992
  * 计算文字大小 度量
27022
26993
  * 优化版本
@@ -27024,7 +26995,14 @@ class TextRun extends LayoutNode {
27024
26995
  * @returns
27025
26996
  */
27026
26997
  static measureText(payload) {
27027
- return TextUtil.measureTextComplete(payload);
26998
+ const { width, height } = TextUtil.getLayoutSize(payload);
26999
+ const { ascent, descent } = TextUtil.getFontMetrics(payload);
27000
+ return {
27001
+ width,
27002
+ height,
27003
+ ascent,
27004
+ descent
27005
+ };
27028
27006
  }
27029
27007
  /**
27030
27008
  * 计算文字大小 度量
@@ -27048,10 +27026,7 @@ class TextRun extends LayoutNode {
27048
27026
  }
27049
27027
  static createEmptyRun(doc) {
27050
27028
  const fontSize2 = DEFAULT_FONT_SIZE;
27051
- const { height, ascent, descent } = TextRun.measureText({
27052
- text: "0",
27053
- fontSize: fontSize2
27054
- });
27029
+ const { height, ascent, descent } = TextRun.measureText(TextRun.measurePayload("0", { fontSize: fontSize2 }));
27055
27030
  const run = new TextRun({
27056
27031
  doc,
27057
27032
  width: 6,
@@ -40374,14 +40349,11 @@ class TextHandler {
40374
40349
  let text = wr.text;
40375
40350
  const textStyle = TextStyleResolver.resolve(wr.rPr, context.doc.model?.styles);
40376
40351
  const layoutStyle = TextRun.textStyle2LayoutStyle(textStyle);
40377
- const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
40378
40352
  const chars = text.split("");
40379
40353
  let doc = context.doc;
40380
40354
  let charMetrics = [];
40381
40355
  let remainingWidth = context.getParagraphRemainingSize();
40382
- const { ascent, descent } = TextUtil.getFontMetrics({
40383
- fontSize: fontSize2
40384
- });
40356
+ const { ascent, descent } = TextUtil.getFontMetrics(TextRun.measurePayload("M", layoutStyle));
40385
40357
  const complete = () => {
40386
40358
  if (!charMetrics.length) return;
40387
40359
  const width = charMetrics.reduce((total, item) => total + item.width, 0);
@@ -40408,10 +40380,7 @@ class TextHandler {
40408
40380
  context.addRun(run);
40409
40381
  };
40410
40382
  chars.forEach((char, charIndex) => {
40411
- const { width, height } = TextRun.measureText({
40412
- text: char,
40413
- fontSize: fontSize2
40414
- });
40383
+ const { width, height } = TextRun.measureText(TextRun.measurePayload(char, layoutStyle));
40415
40384
  if (width <= remainingWidth) {
40416
40385
  charMetrics.push({
40417
40386
  char,
@@ -40445,14 +40414,10 @@ class TextHandler {
40445
40414
  let text = wr.text;
40446
40415
  const textStyle = TextStyleResolver.resolve(wr.rPr, context.doc.model?.styles);
40447
40416
  const layoutStyle = TextRun.textStyle2LayoutStyle(textStyle);
40448
- const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
40449
40417
  const chars = text.split("");
40450
40418
  let doc = context.doc;
40451
40419
  chars.forEach((char, charIndex) => {
40452
- const { width, height, ascent, descent } = TextRun.measureText({
40453
- text: char,
40454
- fontSize: fontSize2
40455
- });
40420
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(char, layoutStyle));
40456
40421
  const run = new TextRun({
40457
40422
  doc,
40458
40423
  width,
@@ -40656,10 +40621,7 @@ class FieldBaseHandler {
40656
40621
  position: "left"
40657
40622
  });
40658
40623
  chars.forEach((char, charIndex) => {
40659
- const { width, height, ascent, descent } = TextRun.measureText({
40660
- text: char,
40661
- fontSize: fontSize2
40662
- });
40624
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(char, layoutStyle));
40663
40625
  const run = new TextWidget({
40664
40626
  doc,
40665
40627
  width,
@@ -40693,6 +40655,9 @@ class FieldBaseHandler {
40693
40655
  static layoutNoValueLabel(ctx) {
40694
40656
  const { context, wr } = ctx;
40695
40657
  let doc = context.doc;
40658
+ const textStyle = TextStyleResolver.resolve(wr.rPr, context.doc.model?.styles);
40659
+ const layoutStyle = TextRun.textStyle2LayoutStyle(textStyle);
40660
+ const measureStyle = { ...layoutStyle, fontSize: 16 };
40696
40661
  const { label, type: type4 } = this.getNoValueLabel(ctx);
40697
40662
  const chars = label.split("");
40698
40663
  this.addFieldMarker({
@@ -40701,10 +40666,7 @@ class FieldBaseHandler {
40701
40666
  position: "left"
40702
40667
  });
40703
40668
  chars.forEach((char, charIndex) => {
40704
- const { width, height, ascent, descent } = TextRun.measureText({
40705
- text: char,
40706
- fontSize: 16
40707
- });
40669
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(char, measureStyle));
40708
40670
  const run = new TextWidget({
40709
40671
  doc,
40710
40672
  width,
@@ -40773,13 +40735,12 @@ class FieldBaseHandler {
40773
40735
  ctx: { context, wr },
40774
40736
  fontSize: fontSize2,
40775
40737
  extra,
40776
- label
40738
+ label,
40739
+ layoutStyle
40777
40740
  } = payload;
40741
+ const style = { ...layoutStyle, fontSize: fontSize2 };
40778
40742
  label.split("").forEach((char, charIndex) => {
40779
- const { width, height, ascent, descent } = TextRun.measureText({
40780
- text: char,
40781
- fontSize: fontSize2
40782
- });
40743
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(char, style));
40783
40744
  const run = new TextWidget({
40784
40745
  doc: context.doc,
40785
40746
  width,
@@ -40848,16 +40809,16 @@ class FieldBaseHandler {
40848
40809
  if (context.doc.mode === DocModeTypeConst.Print) {
40849
40810
  return;
40850
40811
  }
40812
+ const textStyle = TextStyleResolver.resolve(wr.rPr, context.doc.model?.styles);
40813
+ const layoutStyle = TextRun.textStyle2LayoutStyle(textStyle);
40814
+ const measureStyle = { ...layoutStyle, fontSize: fontSize2 };
40851
40815
  const label = position === "left" ? "[" : "]";
40852
40816
  const options = position === "left" ? {
40853
40817
  widgetFieldLeftMarker: true
40854
40818
  } : {
40855
40819
  widgetFieldRightMarker: true
40856
40820
  };
40857
- const { width, height, ascent, descent } = TextRun.measureText({
40858
- text: label,
40859
- fontSize: fontSize2
40860
- });
40821
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(label, measureStyle));
40861
40822
  const _width = position === "left" ? width + 2 : width + 3;
40862
40823
  const run = new TextWidget({
40863
40824
  doc: context.doc,
@@ -40924,10 +40885,9 @@ class OptionHandler extends FieldBaseHandler {
40924
40885
  });
40925
40886
  }
40926
40887
  static layoutOptionItem(context, wr, option, optionIndex, layoutStyle) {
40927
- const { width, height, ascent, descent } = TextRun.measureText({
40928
- text: "0",
40929
- fontSize: 16
40930
- });
40888
+ const { width, height, ascent, descent } = TextRun.measureText(
40889
+ TextRun.measurePayload("0", { ...layoutStyle, fontSize: 16 })
40890
+ );
40931
40891
  const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
40932
40892
  const { iconLabelSpace, labelPosition, controlEnumSpace } = wr.widgetMeta?.props;
40933
40893
  const isVertical = this.isVerticalLayout(wr);
@@ -40948,6 +40908,7 @@ class OptionHandler extends FieldBaseHandler {
40948
40908
  ctx: { context, wr },
40949
40909
  fontSize: fontSize2,
40950
40910
  label: option.label,
40911
+ layoutStyle,
40951
40912
  extra: {
40952
40913
  widgetOption
40953
40914
  }
@@ -41059,10 +41020,12 @@ class SignatureHandler extends FieldBaseHandler {
41059
41020
  const format2 = signatureType === SignatureTypeConst.SIGNATURE_DATETIME ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD";
41060
41021
  const timeStr = dayjs(time).format(format2);
41061
41022
  let position = signDisplayStyle === SignDisplayTypeConst.VERTICAL ? "bottom" : "right";
41062
- const { width: textWidth, height: textHeight } = TextRun.measureText({
41063
- text: timeStr,
41064
- fontSize: DEFAULT_FONT_SIZE
41065
- });
41023
+ const textStyle = TextStyleResolver.resolve(wr.rPr, ctx.context.doc.model?.styles);
41024
+ const layoutStyle = TextRun.textStyle2LayoutStyle(textStyle);
41025
+ const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
41026
+ const { width: textWidth, height: textHeight } = TextRun.measureText(
41027
+ TextRun.measurePayload(timeStr, { ...layoutStyle, fontSize: fontSize2 })
41028
+ );
41066
41029
  return {
41067
41030
  render: render2,
41068
41031
  rectWidth: width + (position === "right" ? textWidth : 0),
@@ -41070,7 +41033,7 @@ class SignatureHandler extends FieldBaseHandler {
41070
41033
  decorations: [
41071
41034
  {
41072
41035
  text: timeStr,
41073
- fontSize: DEFAULT_FONT_SIZE,
41036
+ fontSize: fontSize2,
41074
41037
  x: position === "right" ? width : 0,
41075
41038
  y: position === "right" ? (height - textHeight) / 2 : height,
41076
41039
  position,
@@ -41154,10 +41117,7 @@ class InputHandler extends FieldBaseHandler {
41154
41117
  position: "left"
41155
41118
  });
41156
41119
  chars.forEach((char, charIndex) => {
41157
- const { width, height, ascent, descent } = TextRun.measureText({
41158
- text: char,
41159
- fontSize: fontSize2
41160
- });
41120
+ const { width, height, ascent, descent } = TextRun.measureText(TextRun.measurePayload(char, layoutStyle));
41161
41121
  const run = new TextWidget({
41162
41122
  doc,
41163
41123
  width,
@@ -41225,10 +41185,9 @@ class AttachmentHandler extends FieldBaseHandler {
41225
41185
  const { ctx, item, itemIndex, layoutStyle, itemIsLast } = payload;
41226
41186
  const name = getFileName(item);
41227
41187
  const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
41228
- const { width, height, ascent, descent } = TextRun.measureText({
41229
- text: "0",
41230
- fontSize: 16
41231
- });
41188
+ const { width, height, ascent, descent } = TextRun.measureText(
41189
+ TextRun.measurePayload("0", { ...layoutStyle, fontSize: 16 })
41190
+ );
41232
41191
  const widgetFileItem = {
41233
41192
  index: itemIndex,
41234
41193
  value: item
@@ -41251,6 +41210,7 @@ class AttachmentHandler extends FieldBaseHandler {
41251
41210
  ctx,
41252
41211
  fontSize: fontSize2,
41253
41212
  label: name,
41213
+ layoutStyle,
41254
41214
  extra: {
41255
41215
  style: layoutStyle,
41256
41216
  widgetFileItem
@@ -41345,10 +41305,9 @@ class SerialNumberHandler extends WidgetBaseHandler {
41345
41305
  const layoutStyle = this.getLayoutStyle();
41346
41306
  const label = (context.cell?.subRenderer?.dataIndex ?? 0) + 1 + "";
41347
41307
  label.split("").forEach((char, charIndex) => {
41348
- const { width, height, ascent, descent } = TextRun.measureText({
41349
- text: char,
41350
- fontSize: layoutStyle.fontSize
41351
- });
41308
+ const { width, height, ascent, descent } = TextRun.measureText(
41309
+ TextRun.measurePayload(char, layoutStyle)
41310
+ );
41352
41311
  const run = new TextWidget({
41353
41312
  doc: context.doc,
41354
41313
  width,
@@ -41384,10 +41343,9 @@ class DefaultHandler extends WidgetBaseHandler {
41384
41343
  const layoutStyle = this.getLayoutStyle();
41385
41344
  const label = wr.pageWidgetMeta?.type ?? "default";
41386
41345
  label.split("").forEach((char, charIndex) => {
41387
- const { width, height, ascent, descent } = TextRun.measureText({
41388
- text: char,
41389
- fontSize: layoutStyle.fontSize
41390
- });
41346
+ const { width, height, ascent, descent } = TextRun.measureText(
41347
+ TextRun.measurePayload(char, layoutStyle)
41348
+ );
41391
41349
  const run = new TextWidget({
41392
41350
  doc: context.doc,
41393
41351
  width,
@@ -41446,11 +41404,11 @@ class BarcodeHandler extends WidgetBaseHandler {
41446
41404
  const render2 = showValue;
41447
41405
  if (render2) {
41448
41406
  let codeLabel = label ?? "";
41449
- let fontSize2 = DEFAULT_FONT_SIZE;
41450
- const { width: textWidth, height: textHeight } = TextRun.measureText({
41451
- text: codeLabel,
41452
- fontSize: fontSize2
41453
- });
41407
+ const layoutStyle = this.getLayoutStyle();
41408
+ const fontSize2 = layoutStyle.fontSize ?? DEFAULT_FONT_SIZE;
41409
+ const { width: textWidth, height: textHeight } = TextRun.measureText(
41410
+ TextRun.measurePayload(codeLabel, layoutStyle)
41411
+ );
41454
41412
  return {
41455
41413
  render: render2,
41456
41414
  rectWidth: width,
@@ -41573,10 +41531,9 @@ class PaginationHandler extends WidgetBaseHandler {
41573
41531
  total: context.doc.pages.length
41574
41532
  });
41575
41533
  label.split("").forEach((char, charIndex) => {
41576
- const { width, height, ascent, descent } = TextRun.measureText({
41577
- text: char,
41578
- fontSize: layoutStyle.fontSize
41579
- });
41534
+ const { width, height, ascent, descent } = TextRun.measureText(
41535
+ TextRun.measurePayload(char, layoutStyle)
41536
+ );
41580
41537
  const run = new TextWidget({
41581
41538
  doc: context.doc,
41582
41539
  width,
@@ -51332,8 +51289,6 @@ async function initializeDocumentEngine(props, payload, result) {
51332
51289
  initDocModelJson: JSON.stringify(docModel.toXmlJson())
51333
51290
  }
51334
51291
  };
51335
- console.log("rawData", rawData);
51336
- await TextUtil.awaitDocumentFonts();
51337
51292
  const doc = new Doc({
51338
51293
  model: docModel,
51339
51294
  mode: fillModeType,
@@ -51348,6 +51303,7 @@ async function initializeDocumentEngine(props, payload, result) {
51348
51303
  ...defaultDataMap,
51349
51304
  ...dataInitMap
51350
51305
  });
51306
+ doc.docRuntimeMeta.handleInfo.initRawDataSnapshot = cloneDeep(doc.dataManager.getRawData());
51351
51307
  const docInfo = snapshotDocInfo(doc);
51352
51308
  return {
51353
51309
  doc,
@@ -51407,6 +51363,7 @@ function useDocumentFactory(props, payload) {
51407
51363
  docInfo.value = snapshotDocInfo(ins);
51408
51364
  };
51409
51365
  docInfo.value = initialized.docInfo;
51366
+ payload.onDocumentLoadSuccess?.(result);
51410
51367
  }
51411
51368
  const hasData = computed(() => docIns.value !== null);
51412
51369
  function clear() {
@@ -57003,7 +56960,7 @@ function useDocOperations(docRef) {
57003
56960
  focus: ids.length ? `root::${firstPage.id}` : null
57004
56961
  });
57005
56962
  }
57006
- function getUnsavedChanges() {
56963
+ function getUnsavedChanges(options) {
57007
56964
  const currentCheckpoint = buildSaveCheckpoint();
57008
56965
  if (!currentCheckpoint) {
57009
56966
  return { dirty: false };
@@ -57012,10 +56969,26 @@ function useDocOperations(docRef) {
57012
56969
  if (!isEqual(currentCheckpoint.currentModel, currentCheckpoint.interfaceModel)) {
57013
56970
  changedScopes.push("model");
57014
56971
  }
56972
+ if (options?.includeRawData) {
56973
+ const doc = getDoc();
56974
+ const baselineRaw = doc?.docRuntimeMeta.handleInfo.initRawDataSnapshot;
56975
+ if (baselineRaw !== void 0 && doc) {
56976
+ const currentRaw = doc.dataManager.getRawData();
56977
+ if (!isEqual(currentRaw, baselineRaw)) {
56978
+ changedScopes.push("rawData");
56979
+ }
56980
+ }
56981
+ }
57015
56982
  return {
57016
56983
  dirty: changedScopes.length > 0
57017
56984
  };
57018
56985
  }
56986
+ function markAsSaved() {
56987
+ const doc = getDoc();
56988
+ if (!doc) return;
56989
+ doc.docRuntimeMeta.handleInfo.initDocModelJson = JSON.stringify(exportModel());
56990
+ doc.docRuntimeMeta.handleInfo.initRawDataSnapshot = cloneDeep(doc.dataManager.getRawData());
56991
+ }
57019
56992
  return {
57020
56993
  validate,
57021
56994
  exportModel,
@@ -57023,7 +56996,8 @@ function useDocOperations(docRef) {
57023
56996
  enterBaseline,
57024
56997
  computeBaselineChanges,
57025
56998
  setAnnotation,
57026
- getUnsavedChanges
56999
+ getUnsavedChanges,
57000
+ markAsSaved
57027
57001
  };
57028
57002
  }
57029
57003
  function useDocController(factory2, ops) {
@@ -57099,8 +57073,11 @@ function useDocController(factory2, ops) {
57099
57073
  computeBaselineChanges(ctx) {
57100
57074
  return ops.computeBaselineChanges(ctx);
57101
57075
  },
57102
- getUnsavedChanges() {
57103
- return ops.getUnsavedChanges();
57076
+ getUnsavedChanges(options) {
57077
+ return ops.getUnsavedChanges(options);
57078
+ },
57079
+ markAsSaved() {
57080
+ ops.markAsSaved();
57104
57081
  },
57105
57082
  setAnnotation(ids, list) {
57106
57083
  ops.setAnnotation(ids, list);
@@ -10,6 +10,11 @@ export interface DocUnsavedChanges {
10
10
  /** 是否存在未保存改动 */
11
11
  dirty: boolean;
12
12
  }
13
+ /** getUnsavedChanges 可选参数 */
14
+ export interface DocGetUnsavedChangesOptions {
15
+ /** 为 true 时除文档模型外,同时与初始化时的 rawData 快照比对 */
16
+ includeRawData?: boolean;
17
+ }
13
18
  export interface DocOperations {
14
19
  /** 校验所有字段,返回错误映射或 null(通过) */
15
20
  validate(): Promise<Record<string, string[]> | null>;
@@ -27,7 +32,9 @@ export interface DocOperations {
27
32
  /** 设置变更批注 */
28
33
  setAnnotation(ids: string[], list: any[]): void;
29
34
  /** 获取当前未保存改动状态,供外部在退出前做拦截 */
30
- getUnsavedChanges(): DocUnsavedChanges;
35
+ getUnsavedChanges(options?: DocGetUnsavedChangesOptions): DocUnsavedChanges;
36
+ /** 标记当前文档为已保存 */
37
+ markAsSaved(): void;
31
38
  }
32
39
  /**
33
40
  * 文档操作组合式函数
@@ -43,6 +43,11 @@ export interface IDocPayload extends IDocTemplatePayload, IDocInstancePayload {
43
43
  /** 是否是预览模式 */
44
44
  isPreview: boolean;
45
45
  ctx: DesignSuiteContext;
46
+ /**
47
+ * 文档接口请求成功且引擎挂载完成后的回调(每轮成功加载触发一次)。
48
+ * 不会在 requestId 为空、请求被丢弃、或初始化抛错时调用。
49
+ */
50
+ onDocumentLoadSuccess?: (result: Execute) => void;
46
51
  }
47
52
  /** 文档加载工厂 — 只负责响应式加载与生命周期,不包含业务操作 */
48
53
  export interface DocumentFactory {
@@ -4,7 +4,8 @@
4
4
  */
5
5
  export { DocModeTypeConst, PageSizeEnumConst, BuiltinComponentTypeConst } from '../../core';
6
6
  export type { DocModeType, CompleteComponentType, BuiltinComponentType } from '../../core';
7
- export type { DocController, DocQueryAPI, WordRuntime, DocInfo, DocRuntimeMeta, DocRuntimeMetaHandleInfo, DocUnsavedChanges, UseWordProps, UseWordOptions, FieldModelQuery, } from '../types';
7
+ export type { DocController, DocQueryAPI, WordRuntime, DocInfo, DocRuntimeMeta, DocRuntimeMetaHandleInfo, DocUnsavedChanges, DocGetUnsavedChangesOptions, UseWordProps, UseWordOptions, FieldModelQuery, } from '../types';
8
+ export type { Execute } from '../doc-runtime/factories/useDocumentFactory';
8
9
  export { useWord } from '../doc-runtime/useWord';
9
10
  export { useDocSuite } from '../doc-runtime/composables/useDocSuite';
10
11
  export { useDocumentFactory } from '../doc-runtime/factories/useDocumentFactory';
@@ -2,11 +2,12 @@ import { ComputedRef, Ref, ShallowRef } from 'vue';
2
2
  import { DocModeType } from '../../core/constants';
3
3
  import { DocModel } from '../../core/model';
4
4
  import { Page } from '../../core';
5
- import { DocBaselineContext, DocUnsavedChanges, DocOperations } from '../doc-runtime/composables/useDocOperations';
5
+ import { DocBaselineContext, DocUnsavedChanges, DocOperations, DocGetUnsavedChangesOptions } from '../doc-runtime/composables/useDocOperations';
6
6
  import { OnlineFormInstanceResponse, OnlineFormTmplResponse } from '@gct-paas/api/apaas';
7
7
  import { FieldChangeItem } from '../../runtime/interface/change-diff';
8
8
  import { FormTmplConfigController } from '../../suites/edhr/panel-schema/data-load/hooks/form-tmpl-config';
9
- export type { DocUnsavedChanges } from '../doc-runtime/composables/useDocOperations';
9
+ import { Execute } from '../doc-runtime/factories/useDocumentFactory';
10
+ export type { DocUnsavedChanges, DocGetUnsavedChangesOptions } from '../doc-runtime/composables/useDocOperations';
10
11
  export type { FieldModelQuery } from './field-model-query';
11
12
  /** 字段权限信息 */
12
13
  export interface FieldPermissionInfo {
@@ -78,8 +79,10 @@ export interface DocControllerMethods extends DocOperations {
78
79
  enterBaseline(uniqueId: string): DocBaselineContext | null;
79
80
  /** 与基线做差异计算,返回字段变更列表 */
80
81
  computeBaselineChanges(ctx: DocBaselineContext): Promise<FieldChangeItem[]>;
81
- /** 获取当前未保存改动状态,适合外部在退出前做校验 */
82
- getUnsavedChanges(): DocUnsavedChanges;
82
+ /** 获取当前未保存改动状态,适合外部在退出前做校验;特定场景可传入 includeRawData 同时比对填报数据 */
83
+ getUnsavedChanges(options?: DocGetUnsavedChangesOptions): DocUnsavedChanges;
84
+ /** 标记当前文档为已保存 */
85
+ markAsSaved(): void;
83
86
  /** 向指定字段/组件设置批注信息 */
84
87
  setAnnotation(ids: string[], list: any[]): void;
85
88
  }
@@ -123,6 +126,8 @@ export interface DocRuntimeMetaHandleInfo {
123
126
  bpmnFieldAuthMap?: Record<string, any>;
124
127
  /** 初始化的doc model 用来比对用 */
125
128
  initDocModelJson: string;
129
+ /** 初始化后的填报原始数据快照,供 getUnsavedChanges({ includeRawData: true }) 比对 */
130
+ initRawDataSnapshot?: Record<string, any>;
126
131
  }
127
132
  /** 文档运行时元信息(接口返回字段 + 业务拼接字段) */
128
133
  export interface DocRuntimeMeta extends OnlineFormInstanceResponse, OnlineFormTmplResponse {
@@ -178,6 +183,11 @@ export interface UseWordOptions {
178
183
  isDetailPage?: boolean | (() => boolean);
179
184
  /** 指定渲染模式 */
180
185
  renderModeType?: DocModeType;
186
+ /**
187
+ * 文档接口请求成功且引擎挂载完成后的回调(每轮成功加载触发一次)。
188
+ * 不会在 requestId 为空、请求被丢弃、或初始化抛错时调用。
189
+ */
190
+ onDocumentLoadSuccess?: (result: Execute) => void;
181
191
  /** 扩展字段 - 兼容其他未明确定义的负载参数 */
182
192
  [key: string]: any;
183
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gct-paas/word",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "GCT 在线 word",
5
5
  "keywords": [
6
6
  "vue",