@retikz/core 0.2.0-rc.1 → 0.3.0-alpha.1

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 ADDED
@@ -0,0 +1,41 @@
1
+ # @retikz/core
2
+
3
+ Framework-agnostic core of [retikz](https://pionpill.github.io/retikz/) — a TikZ-inspired diagramming library. Provides the zod-typed **IR**, the **`compileToScene`** compiler, pure **parsers**, geometry helpers, and the shape / arrow / pattern / path-generator registries.
4
+
5
+ 零框架核心:retikz 的中间表示(IR)、`compileToScene` 编译器、纯解析器、几何工具与形状 / 箭头 / 图案 / 路径生成器注册面。**零 React、零 DOM**,运行时依赖只有 `zod`,IR 100% 可 JSON 序列化。
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @retikz/core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ `@retikz/core` is renderer-agnostic: it turns an IR into a serializable `Scene`. A backend (`@retikz/render`) or runtime (`@retikz/react` / `@retikz/vanilla`) then renders that Scene.
16
+
17
+ ```ts
18
+ import { compileToScene } from '@retikz/core';
19
+
20
+ const scene = compileToScene(ir);
21
+ // optional: compileToScene(ir, { measureText, shapes, arrows, padding, ... })
22
+ // hand `scene` to @retikz/render/svg, @retikz/render/canvas, or a runtime
23
+ ```
24
+
25
+ Most users consume core indirectly through [`@retikz/react`](https://www.npmjs.com/package/@retikz/react) or [`@retikz/vanilla`](https://www.npmjs.com/package/@retikz/vanilla). Use core directly when you build IR programmatically, persist/transport scenes, or write a custom renderer.
26
+
27
+ ## Exports
28
+
29
+ - `compileToScene` / `computeLayout` / `fallbackMeasurer` — IR → `Scene`
30
+ - IR & `Scene` zod schemas + inferred types
31
+ - `parseWay` / `parseNodeTarget` / `parseTargetSugar` — pure parsers
32
+ - `point` / `rect` / `circle` / `ellipse` / `diamond` / `polar` — geometry
33
+ - `BUILTIN_SHAPES` / `BUILTIN_ARROWS` / `BUILTIN_PATTERNS` + `ShapeDefinition` / `ArrowDefinition` / `PatternDefinition` / `definePathGenerator` — registries
34
+
35
+ ## Docs
36
+
37
+ <https://pionpill.github.io/retikz/>
38
+
39
+ ## License
40
+
41
+ MIT
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAc,MAAM,EAAe,WAAW,EAAE,MAAM,OAAO,CAAC;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAa,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEnF,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,WAAW,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,oBAAoB,EAAmB,MAAM,YAAY,CAAC;AAExE,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgG7D,MAAM,MAAM,UAAU,GAAG;IACvB,qBAAqB;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;OAGG;IACH,IAAI,EAAE,IAAI,CAAC;IACX,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAClC,iBAAiB;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS;IACT,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS;IACT,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC5C,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,gBAAgB;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACjC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC5C,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IAChD,+BAA+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,GAAG,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC;CACxF,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,KAAG,QACS,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,UAAU,EAAE,MAAM,MAAM,KAAG,QAM3D,CAAC;AA8FF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,UAAU,EAAE,UAAU,MAAM,KAAG,QActE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,aAAa,YAAY,EACzB,WAAW,SAAS,EACpB,eAAe,MAAM,EACrB,aAAY,aAAa,CAAC,SAAS,CAAM,EACzC,eAAe,cAAc,EAC7B,SAAQ,MAAM,CAAC,MAAM,EAAE,eAAe,CAAkB,EACxD,uBAAuB,oBAAoB,KAC1C,UA4KF,CAAC;AAcF;;;;GAIG;AACH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,UAAU,KAAG,KAAK,CAAC,QAAQ,CA6BpE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,aAAa,aAAa,KACzB,KAAK,CAAC,cAAc,CA4GtB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { BUILTIN_SHAPES } from "../shapes/index.js";
2
2
  import { resolvePosition } from "./position.js";
3
+ import { toAlphabeticBaselineY } from "./text-baseline.js";
3
4
  //#region src/compile/node.ts
4
5
  var DEFAULT_FONT_SIZE = 14;
5
6
  var DEFAULT_PADDING = 8;
@@ -383,18 +384,19 @@ var emitNodePrimitives = (layout, round, resolveFill) => {
383
384
  if (layout.lines) {
384
385
  const halfBlockW = layout.textWidth / 2;
385
386
  const xOffset = layout.align === "start" ? -halfBlockW : layout.align === "end" ? halfBlockW : 0;
387
+ const lineHeight = round(layout.lineHeight);
386
388
  inner.push({
387
389
  type: "text",
388
390
  x: round(layout.rect.x + xOffset),
389
- y: round(layout.rect.y),
391
+ y: round(toAlphabeticBaselineY(layout.rect.y, "middle", layout.lines.length, lineHeight, layout.fontSize)),
390
392
  lines: layout.lines,
391
393
  fontSize: layout.fontSize,
392
394
  fontFamily: layout.fontFamily,
393
395
  fontWeight: layout.fontWeight,
394
396
  fontStyle: layout.fontStyle,
395
397
  align: layout.align,
396
- baseline: "middle",
397
- lineHeight: round(layout.lineHeight),
398
+ baseline: "alphabetic",
399
+ lineHeight,
398
400
  fill: layout.textColor ?? "currentColor",
399
401
  opacity: layout.opacity,
400
402
  measuredWidth: round(layout.textWidth),
@@ -426,18 +428,19 @@ var emitNodePrimitives = (layout, round, resolveFill) => {
426
428
  opacity: lab.opacity ?? layout.opacity
427
429
  });
428
430
  }
431
+ const labLineHeight = round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR);
429
432
  const textPrim = {
430
433
  type: "text",
431
434
  x: round(lx),
432
- y: round(ly),
435
+ y: round(toAlphabeticBaselineY(ly, "middle", 1, labLineHeight, lab.fontSize)),
433
436
  lines: [{ text: lab.text }],
434
437
  fontSize: lab.fontSize,
435
438
  fontFamily: lab.fontFamily,
436
439
  fontWeight: lab.fontWeight,
437
440
  fontStyle: lab.fontStyle,
438
441
  align: "middle",
439
- baseline: "middle",
440
- lineHeight: round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR),
442
+ baseline: "alphabetic",
443
+ lineHeight: labLineHeight,
441
444
  fill: lab.textColor ?? "currentColor",
442
445
  opacity: lab.opacity ?? layout.opacity,
443
446
  measuredWidth: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,WAAW,CAAC,UAAU,CAAC,KAAG,MAIhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,OAAO,WAAW,EAClB,QAAQ,aAAa,EACrB,aAAa,YAAY,EACzB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAc,MAAM,KACnB;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAkGxD,CAAC"}
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,iBAAiB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,WAAW,CAAC,UAAU,CAAC,KAAG,MAIhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,OAAO,WAAW,EAClB,QAAQ,aAAa,EACrB,aAAa,YAAY,EACzB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAc,MAAM,KACnB;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAmGxD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { toAlphabeticBaselineY } from "../text-baseline.js";
1
2
  //#region src/compile/path/label.ts
2
3
  /** 边标注默认字号 / 偏移量 */
3
4
  var LABEL_FONT_SIZE = 14;
@@ -60,15 +61,16 @@ var emitLabelPrimitive = (label, sample, measureText, round, hostOpacity) => {
60
61
  x += LABEL_SIDE_OFFSET;
61
62
  align = "start";
62
63
  } else baseline = "bottom";
64
+ const emittedLineHeight = round(lineHeight);
63
65
  const text = {
64
66
  type: "text",
65
67
  x: round(x),
66
- y: round(y),
68
+ y: round(toAlphabeticBaselineY(y, baseline, 1, emittedLineHeight, fontSize)),
67
69
  lines: [{ text: label.text }],
68
70
  fontSize,
69
71
  align,
70
- baseline,
71
- lineHeight: round(lineHeight),
72
+ baseline: "alphabetic",
73
+ lineHeight: emittedLineHeight,
72
74
  measuredWidth: round(measuredWidth),
73
75
  measuredHeight: round(measuredHeight),
74
76
  fill: label.textColor ?? "currentColor"
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 文本垂直定位归一:任意 baseline 锚点 → 首行 alphabetic 基线 y
3
+ * @description canvas 的 `textBaseline` 与 SVG 的 `dominant-baseline` 是两套同名异义的关键字
4
+ * (`top/middle/bottom` 各按「em 方块」与「字体 ascent/descent」两种不同参照线解释),
5
+ * 只有 `alphabetic`(拉丁字母坐的底线)在两套模型、各浏览器里定义一致。故 core 在编译期把
6
+ * 所有文本的垂直锚点统一折算成首行 alphabetic 基线、emit `baseline: 'alphabetic'`,让 adapter
7
+ * 只渲染这一条无歧义的基线 —— 跨后端像素一致,垂直定位逻辑收口到 renderer-agnostic 的 core。
8
+ *
9
+ * 无字体测量器时用下方 ascent/descent 近似(占一个 em,asc 8 : desc 2 拆分),renderer-agnostic
10
+ * 且两后端一致;接入精确测量器后只需替换 asc/desc 来源,折算公式不变。
11
+ */
12
+ /** ascent 近似占 fontSize 比例(基线之上) */
13
+ export declare const ASCENT_FACTOR = 0.8;
14
+ /** descent 近似占 fontSize 比例(基线之下) */
15
+ export declare const DESCENT_FACTOR = 0.2;
16
+ /**
17
+ * 把按 `baseline` 解释的垂直锚点 `y` 折算成首行 alphabetic 基线 y
18
+ * @description 多行文本首行基线在上、后续行按 lineHeight 向下堆叠;折算保持「关键字所指的块边界
19
+ * (top=块顶 ascent 线 / bottom=块底 descent 线 / middle=视觉中心 / alphabetic=首行基线)落在 y」。
20
+ * @param y - 原垂直锚点(含义由 baseline 决定)
21
+ * @param baseline - 锚点语义
22
+ * @param lineCount - 文本行数(≥1)
23
+ * @param lineHeight - 行高(相邻行基线间距)
24
+ * @param fontSize - 字号(算 ascent/descent 用)
25
+ * @returns 首行 alphabetic 基线 y
26
+ */
27
+ export declare const toAlphabeticBaselineY: (y: number, baseline: "top" | "middle" | "bottom" | "alphabetic", lineCount: number, lineHeight: number, fontSize: number) => number;
28
+ //# sourceMappingURL=text-baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-baseline.d.ts","sourceRoot":"","sources":["../../../src/compile/text-baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,mCAAmC;AACnC,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,oCAAoC;AACpC,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAChC,GAAG,MAAM,EACT,UAAU,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,EACpD,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,UAAU,MAAM,KACf,MAcF,CAAC"}
@@ -0,0 +1,40 @@
1
+ //#region src/compile/text-baseline.ts
2
+ /**
3
+ * 文本垂直定位归一:任意 baseline 锚点 → 首行 alphabetic 基线 y
4
+ * @description canvas 的 `textBaseline` 与 SVG 的 `dominant-baseline` 是两套同名异义的关键字
5
+ * (`top/middle/bottom` 各按「em 方块」与「字体 ascent/descent」两种不同参照线解释),
6
+ * 只有 `alphabetic`(拉丁字母坐的底线)在两套模型、各浏览器里定义一致。故 core 在编译期把
7
+ * 所有文本的垂直锚点统一折算成首行 alphabetic 基线、emit `baseline: 'alphabetic'`,让 adapter
8
+ * 只渲染这一条无歧义的基线 —— 跨后端像素一致,垂直定位逻辑收口到 renderer-agnostic 的 core。
9
+ *
10
+ * 无字体测量器时用下方 ascent/descent 近似(占一个 em,asc 8 : desc 2 拆分),renderer-agnostic
11
+ * 且两后端一致;接入精确测量器后只需替换 asc/desc 来源,折算公式不变。
12
+ */
13
+ /** ascent 近似占 fontSize 比例(基线之上) */
14
+ var ASCENT_FACTOR = .8;
15
+ /** descent 近似占 fontSize 比例(基线之下) */
16
+ var DESCENT_FACTOR = .2;
17
+ /**
18
+ * 把按 `baseline` 解释的垂直锚点 `y` 折算成首行 alphabetic 基线 y
19
+ * @description 多行文本首行基线在上、后续行按 lineHeight 向下堆叠;折算保持「关键字所指的块边界
20
+ * (top=块顶 ascent 线 / bottom=块底 descent 线 / middle=视觉中心 / alphabetic=首行基线)落在 y」。
21
+ * @param y - 原垂直锚点(含义由 baseline 决定)
22
+ * @param baseline - 锚点语义
23
+ * @param lineCount - 文本行数(≥1)
24
+ * @param lineHeight - 行高(相邻行基线间距)
25
+ * @param fontSize - 字号(算 ascent/descent 用)
26
+ * @returns 首行 alphabetic 基线 y
27
+ */
28
+ var toAlphabeticBaselineY = (y, baseline, lineCount, lineHeight, fontSize) => {
29
+ const asc = fontSize * ASCENT_FACTOR;
30
+ const desc = fontSize * DESCENT_FACTOR;
31
+ const span = (lineCount - 1) * lineHeight;
32
+ switch (baseline) {
33
+ case "top": return y + asc;
34
+ case "bottom": return y - span - desc;
35
+ case "middle": return y - span / 2 + (asc - desc) / 2;
36
+ case "alphabetic": return y;
37
+ }
38
+ };
39
+ //#endregion
40
+ export { toAlphabeticBaselineY };
@@ -22,7 +22,7 @@ export type TextMetrics = {
22
22
  };
23
23
  /**
24
24
  * 文字度量函数接口(编译期由 adapter 注入)
25
- * @description @retikz/react: canvas measureText;@retikz/ssr: opentype.js/fontkit;@retikz/canvas: ctx.measureText
25
+ * @description @retikz/react: canvas measureText;@retikz/ssr: opentype.js/fontkit;@retikz/render/canvas: ctx.measureText
26
26
  */
27
27
  export type TextMeasurer = (text: string, font: FontSpec) => TextMetrics;
28
28
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @retikz/core 公开 API
3
- * @description 任何 framework adapter(@retikz/react、@retikz/vue、@retikz/canvas、@retikz/ssr)只能 import 本文件导出内容,不准走子路径。本包零 React/零 DOM 依赖
3
+ * @description 任何 framework adapter(@retikz/react、@retikz/vue、@retikz/render/canvas、@retikz/ssr)只能 import 本文件导出内容,不准走子路径。本包零 React/零 DOM 依赖
4
4
  */
5
5
  export { PositionSchema, PolarPositionSchema, AtPositionSchema, OffsetPositionSchema, AT_DIRECTIONS, TargetSchema, AnchorRefSchema, NodeTargetSchema, RelativeTargetSchema, RelativeAccumulateTargetSchema, AbsoluteTargetSchema, BetweenPositionSchema, MoveStepSchema, LineStepSchema, FoldStepSchema, CycleStepSchema, CurveStepSchema, CubicStepSchema, BendStepSchema, ArcStepSchema, CirclePathStepSchema, EllipsePathStepSchema, RectangleStepSchema, GeneratorStepSchema, ControlPointSchema, StepLabelSchema, StepSchema, NodeSchema, NodeLabelSchema, CoordinateSchema, FontSchema, TextBlockSchema, LineSpecSchema, PathSchema, ArrowDetailSchema, ArrowEndDetailSchema, ScopeSchema, NodeDefaultSchema, PathDefaultSchema, LabelDefaultSchema, ArrowDefaultSchema, TransformSchema, ChildSchema, SceneSchema, CURRENT_IR_VERSION, PaintSpecSchema, GradientStopSchema, JsonValueSchema, JsonObjectSchema, ClipSpecSchema, ViewBoxSchema, } from './ir';
6
6
  export type { IRPosition, IRAtPosition, IROffsetPosition, AtDirection, IRTarget, IRAnchorRef, IRNodeTarget, IRRelativeTarget, IRRelativeAccumulateTarget, IRAbsoluteTarget, IRBetweenPosition, IRMoveStep, IRLineStep, IRFoldStep, IRCycleStep, IRCurveStep, IRCubicStep, IRBendStep, IRArcStep, IRCirclePathStep, IREllipsePathStep, IRRectangleStep, IRGeneratorStep, IRControlPoint, IRStepLabel, IRStep, IRNode, IRNodeLabel, IRCoordinate, IRFont, IRLineSpec, IRTextBlock, IRPath, IRScope, IRNodeDefault, IRPathDefault, IRLabelDefault, IRArrowDefault, StyleChannel, IRTransform, IRTranslateTransform, IRPolarTranslateTransform, IRAtTranslateTransform, IROffsetTranslateTransform, IRRotateTransform, IRScaleTransform, IRChild, IR, ArrowShape, BuiltinArrowName, ArrowShapeName, IRArrowDetail, IRArrowEndDetail, NodeShape, BuiltinShapeName, NodeTextAlign, PatternShapeName, BuiltinPatternName, IRPaintSpec, IRGradientStop, JsonValue, IRJsonObject, IRClipSpec, IRViewBox, } from './ir';
@@ -1,5 +1,6 @@
1
1
  const require_index = require("../shapes/index.cjs");
2
2
  const require_position = require("./position.cjs");
3
+ const require_text_baseline = require("./text-baseline.cjs");
3
4
  //#region src/compile/node.ts
4
5
  var DEFAULT_FONT_SIZE = 14;
5
6
  var DEFAULT_PADDING = 8;
@@ -383,18 +384,19 @@ var emitNodePrimitives = (layout, round, resolveFill) => {
383
384
  if (layout.lines) {
384
385
  const halfBlockW = layout.textWidth / 2;
385
386
  const xOffset = layout.align === "start" ? -halfBlockW : layout.align === "end" ? halfBlockW : 0;
387
+ const lineHeight = round(layout.lineHeight);
386
388
  inner.push({
387
389
  type: "text",
388
390
  x: round(layout.rect.x + xOffset),
389
- y: round(layout.rect.y),
391
+ y: round(require_text_baseline.toAlphabeticBaselineY(layout.rect.y, "middle", layout.lines.length, lineHeight, layout.fontSize)),
390
392
  lines: layout.lines,
391
393
  fontSize: layout.fontSize,
392
394
  fontFamily: layout.fontFamily,
393
395
  fontWeight: layout.fontWeight,
394
396
  fontStyle: layout.fontStyle,
395
397
  align: layout.align,
396
- baseline: "middle",
397
- lineHeight: round(layout.lineHeight),
398
+ baseline: "alphabetic",
399
+ lineHeight,
398
400
  fill: layout.textColor ?? "currentColor",
399
401
  opacity: layout.opacity,
400
402
  measuredWidth: round(layout.textWidth),
@@ -426,18 +428,19 @@ var emitNodePrimitives = (layout, round, resolveFill) => {
426
428
  opacity: lab.opacity ?? layout.opacity
427
429
  });
428
430
  }
431
+ const labLineHeight = round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR);
429
432
  const textPrim = {
430
433
  type: "text",
431
434
  x: round(lx),
432
- y: round(ly),
435
+ y: round(require_text_baseline.toAlphabeticBaselineY(ly, "middle", 1, labLineHeight, lab.fontSize)),
433
436
  lines: [{ text: lab.text }],
434
437
  fontSize: lab.fontSize,
435
438
  fontFamily: lab.fontFamily,
436
439
  fontWeight: lab.fontWeight,
437
440
  fontStyle: lab.fontStyle,
438
441
  align: "middle",
439
- baseline: "middle",
440
- lineHeight: round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR),
442
+ baseline: "alphabetic",
443
+ lineHeight: labLineHeight,
441
444
  fill: lab.textColor ?? "currentColor",
442
445
  opacity: lab.opacity ?? layout.opacity,
443
446
  measuredWidth: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ const require_text_baseline = require("../text-baseline.cjs");
1
2
  //#region src/compile/path/label.ts
2
3
  /** 边标注默认字号 / 偏移量 */
3
4
  var LABEL_FONT_SIZE = 14;
@@ -60,15 +61,16 @@ var emitLabelPrimitive = (label, sample, measureText, round, hostOpacity) => {
60
61
  x += LABEL_SIDE_OFFSET;
61
62
  align = "start";
62
63
  } else baseline = "bottom";
64
+ const emittedLineHeight = round(lineHeight);
63
65
  const text = {
64
66
  type: "text",
65
67
  x: round(x),
66
- y: round(y),
68
+ y: round(require_text_baseline.toAlphabeticBaselineY(y, baseline, 1, emittedLineHeight, fontSize)),
67
69
  lines: [{ text: label.text }],
68
70
  fontSize,
69
71
  align,
70
- baseline,
71
- lineHeight: round(lineHeight),
72
+ baseline: "alphabetic",
73
+ lineHeight: emittedLineHeight,
72
74
  measuredWidth: round(measuredWidth),
73
75
  measuredHeight: round(measuredHeight),
74
76
  fill: label.textColor ?? "currentColor"
@@ -1 +1 @@
1
- {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,WAAW,CAAC,UAAU,CAAC,KAAG,MAIhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,OAAO,WAAW,EAClB,QAAQ,aAAa,EACrB,aAAa,YAAY,EACzB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAc,MAAM,KACnB;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAkGxD,CAAC"}
1
+ {"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,iBAAiB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,WAAW,CAAC,UAAU,CAAC,KAAG,MAIhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,OAAO,WAAW,EAClB,QAAQ,aAAa,EACrB,aAAa,YAAY,EACzB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAc,MAAM,KACnB;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAmGxD,CAAC"}
@@ -0,0 +1,40 @@
1
+ //#region src/compile/text-baseline.ts
2
+ /**
3
+ * 文本垂直定位归一:任意 baseline 锚点 → 首行 alphabetic 基线 y
4
+ * @description canvas 的 `textBaseline` 与 SVG 的 `dominant-baseline` 是两套同名异义的关键字
5
+ * (`top/middle/bottom` 各按「em 方块」与「字体 ascent/descent」两种不同参照线解释),
6
+ * 只有 `alphabetic`(拉丁字母坐的底线)在两套模型、各浏览器里定义一致。故 core 在编译期把
7
+ * 所有文本的垂直锚点统一折算成首行 alphabetic 基线、emit `baseline: 'alphabetic'`,让 adapter
8
+ * 只渲染这一条无歧义的基线 —— 跨后端像素一致,垂直定位逻辑收口到 renderer-agnostic 的 core。
9
+ *
10
+ * 无字体测量器时用下方 ascent/descent 近似(占一个 em,asc 8 : desc 2 拆分),renderer-agnostic
11
+ * 且两后端一致;接入精确测量器后只需替换 asc/desc 来源,折算公式不变。
12
+ */
13
+ /** ascent 近似占 fontSize 比例(基线之上) */
14
+ var ASCENT_FACTOR = .8;
15
+ /** descent 近似占 fontSize 比例(基线之下) */
16
+ var DESCENT_FACTOR = .2;
17
+ /**
18
+ * 把按 `baseline` 解释的垂直锚点 `y` 折算成首行 alphabetic 基线 y
19
+ * @description 多行文本首行基线在上、后续行按 lineHeight 向下堆叠;折算保持「关键字所指的块边界
20
+ * (top=块顶 ascent 线 / bottom=块底 descent 线 / middle=视觉中心 / alphabetic=首行基线)落在 y」。
21
+ * @param y - 原垂直锚点(含义由 baseline 决定)
22
+ * @param baseline - 锚点语义
23
+ * @param lineCount - 文本行数(≥1)
24
+ * @param lineHeight - 行高(相邻行基线间距)
25
+ * @param fontSize - 字号(算 ascent/descent 用)
26
+ * @returns 首行 alphabetic 基线 y
27
+ */
28
+ var toAlphabeticBaselineY = (y, baseline, lineCount, lineHeight, fontSize) => {
29
+ const asc = fontSize * ASCENT_FACTOR;
30
+ const desc = fontSize * DESCENT_FACTOR;
31
+ const span = (lineCount - 1) * lineHeight;
32
+ switch (baseline) {
33
+ case "top": return y + asc;
34
+ case "bottom": return y - span - desc;
35
+ case "middle": return y - span / 2 + (asc - desc) / 2;
36
+ case "alphabetic": return y;
37
+ }
38
+ };
39
+ //#endregion
40
+ exports.toAlphabeticBaselineY = toAlphabeticBaselineY;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 文本垂直定位归一:任意 baseline 锚点 → 首行 alphabetic 基线 y
3
+ * @description canvas 的 `textBaseline` 与 SVG 的 `dominant-baseline` 是两套同名异义的关键字
4
+ * (`top/middle/bottom` 各按「em 方块」与「字体 ascent/descent」两种不同参照线解释),
5
+ * 只有 `alphabetic`(拉丁字母坐的底线)在两套模型、各浏览器里定义一致。故 core 在编译期把
6
+ * 所有文本的垂直锚点统一折算成首行 alphabetic 基线、emit `baseline: 'alphabetic'`,让 adapter
7
+ * 只渲染这一条无歧义的基线 —— 跨后端像素一致,垂直定位逻辑收口到 renderer-agnostic 的 core。
8
+ *
9
+ * 无字体测量器时用下方 ascent/descent 近似(占一个 em,asc 8 : desc 2 拆分),renderer-agnostic
10
+ * 且两后端一致;接入精确测量器后只需替换 asc/desc 来源,折算公式不变。
11
+ */
12
+ /** ascent 近似占 fontSize 比例(基线之上) */
13
+ export declare const ASCENT_FACTOR = 0.8;
14
+ /** descent 近似占 fontSize 比例(基线之下) */
15
+ export declare const DESCENT_FACTOR = 0.2;
16
+ /**
17
+ * 把按 `baseline` 解释的垂直锚点 `y` 折算成首行 alphabetic 基线 y
18
+ * @description 多行文本首行基线在上、后续行按 lineHeight 向下堆叠;折算保持「关键字所指的块边界
19
+ * (top=块顶 ascent 线 / bottom=块底 descent 线 / middle=视觉中心 / alphabetic=首行基线)落在 y」。
20
+ * @param y - 原垂直锚点(含义由 baseline 决定)
21
+ * @param baseline - 锚点语义
22
+ * @param lineCount - 文本行数(≥1)
23
+ * @param lineHeight - 行高(相邻行基线间距)
24
+ * @param fontSize - 字号(算 ascent/descent 用)
25
+ * @returns 首行 alphabetic 基线 y
26
+ */
27
+ export declare const toAlphabeticBaselineY: (y: number, baseline: "top" | "middle" | "bottom" | "alphabetic", lineCount: number, lineHeight: number, fontSize: number) => number;
28
+ //# sourceMappingURL=text-baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-baseline.d.ts","sourceRoot":"","sources":["../../../src/compile/text-baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,mCAAmC;AACnC,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,oCAAoC;AACpC,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAChC,GAAG,MAAM,EACT,UAAU,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,EACpD,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,UAAU,MAAM,KACf,MAcF,CAAC"}
@@ -22,7 +22,7 @@ export type TextMetrics = {
22
22
  };
23
23
  /**
24
24
  * 文字度量函数接口(编译期由 adapter 注入)
25
- * @description @retikz/react: canvas measureText;@retikz/ssr: opentype.js/fontkit;@retikz/canvas: ctx.measureText
25
+ * @description @retikz/react: canvas measureText;@retikz/ssr: opentype.js/fontkit;@retikz/render/canvas: ctx.measureText
26
26
  */
27
27
  export type TextMeasurer = (text: string, font: FontSpec) => TextMetrics;
28
28
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @retikz/core 公开 API
3
- * @description 任何 framework adapter(@retikz/react、@retikz/vue、@retikz/canvas、@retikz/ssr)只能 import 本文件导出内容,不准走子路径。本包零 React/零 DOM 依赖
3
+ * @description 任何 framework adapter(@retikz/react、@retikz/vue、@retikz/render/canvas、@retikz/ssr)只能 import 本文件导出内容,不准走子路径。本包零 React/零 DOM 依赖
4
4
  */
5
5
  export { PositionSchema, PolarPositionSchema, AtPositionSchema, OffsetPositionSchema, AT_DIRECTIONS, TargetSchema, AnchorRefSchema, NodeTargetSchema, RelativeTargetSchema, RelativeAccumulateTargetSchema, AbsoluteTargetSchema, BetweenPositionSchema, MoveStepSchema, LineStepSchema, FoldStepSchema, CycleStepSchema, CurveStepSchema, CubicStepSchema, BendStepSchema, ArcStepSchema, CirclePathStepSchema, EllipsePathStepSchema, RectangleStepSchema, GeneratorStepSchema, ControlPointSchema, StepLabelSchema, StepSchema, NodeSchema, NodeLabelSchema, CoordinateSchema, FontSchema, TextBlockSchema, LineSpecSchema, PathSchema, ArrowDetailSchema, ArrowEndDetailSchema, ScopeSchema, NodeDefaultSchema, PathDefaultSchema, LabelDefaultSchema, ArrowDefaultSchema, TransformSchema, ChildSchema, SceneSchema, CURRENT_IR_VERSION, PaintSpecSchema, GradientStopSchema, JsonValueSchema, JsonObjectSchema, ClipSpecSchema, ViewBoxSchema, } from './ir';
6
6
  export type { IRPosition, IRAtPosition, IROffsetPosition, AtDirection, IRTarget, IRAnchorRef, IRNodeTarget, IRRelativeTarget, IRRelativeAccumulateTarget, IRAbsoluteTarget, IRBetweenPosition, IRMoveStep, IRLineStep, IRFoldStep, IRCycleStep, IRCurveStep, IRCubicStep, IRBendStep, IRArcStep, IRCirclePathStep, IREllipsePathStep, IRRectangleStep, IRGeneratorStep, IRControlPoint, IRStepLabel, IRStep, IRNode, IRNodeLabel, IRCoordinate, IRFont, IRLineSpec, IRTextBlock, IRPath, IRScope, IRNodeDefault, IRPathDefault, IRLabelDefault, IRArrowDefault, StyleChannel, IRTransform, IRTranslateTransform, IRPolarTranslateTransform, IRAtTranslateTransform, IROffsetTranslateTransform, IRRotateTransform, IRScaleTransform, IRChild, IR, ArrowShape, BuiltinArrowName, ArrowShapeName, IRArrowDetail, IRArrowEndDetail, NodeShape, BuiltinShapeName, NodeTextAlign, PatternShapeName, BuiltinPatternName, IRPaintSpec, IRGradientStop, JsonValue, IRJsonObject, IRClipSpec, IRViewBox, } from './ir';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retikz/core",
3
- "version": "0.2.0-rc.1",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "retikz v0.1 core: framework-agnostic IR, scene compiler, and pure parsers.",
5
5
  "type": "module",
6
6
  "author": "Pionpill",