@kurot/core 2.5.0 → 2.5.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/CHANGELOG.md CHANGED
@@ -4,10 +4,50 @@ All notable changes to `@kurot/core` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.5.1] — 2026-10-10
8
+
9
+ Prepared, not published. The latest verified registry release remains 2.5.0.
10
+
11
+ ### Fixed
12
+
13
+ - Decouple dynamic `verticalAlign = "middle"` from `multiline`. Single and
14
+ multiple lines center their complete glyph block, including mixed-size rich
15
+ runs and fallback glyphs. Identical one-line content has the same placement
16
+ in either line mode. Blank rows retain their nominal height and line spacing.
17
+ - Reuse cached block bounds for drawing, link hits and render-only glyph/outline
18
+ padding so nested bitmap caches and WebGL text captures retain centered ink.
19
+ - Preserve stable input row baselines, caret, selection and composition geometry
20
+ in both line modes; preserve top/bottom alignment and nominal measurements.
21
+
22
+ ### Compatibility
23
+
24
+ - No new API, dependencies, XML fields or resource format. Existing multiline
25
+ dynamic middle-aligned captions can move to their visual center; changing
26
+ glyphs can translate the block without changing its relative baselines.
27
+ - Unchanged UI, Game, DragonBones, Spine and ui-runtime SDK peers accept this
28
+ patch without version bumps. Applications adopt Core and rebuild explicitly.
29
+ Editor's exact 2.5.0 pin must be changed to adopt it; compatible game ranges
30
+ such as `^2.5.0` need an installation/lock update. CLI and headless packages
31
+ require no release. See [text layout](docs/text-layout.md).
32
+
33
+ ### Verification — 2026-10-10
34
+
35
+ - Core 2.5.1 build and all 900 unit tests pass (81 files).
36
+ - All 12 minified browser checks pass in Canvas, WebGL 1/2 at 1×/2×.
37
+ Independent Canvas references verify identical one-row placement in both
38
+ line modes, 24/40/56 px automatic-height captions, multilingual paragraphs,
39
+ wrapping, rich text, blank rows, outlines and nested caches. Input caret,
40
+ selection, composition underline and second-row character hits pass.
41
+ - Package dry-run verifies the 2.5.1 manifest, compiled block-alignment module,
42
+ declarations and updated documentation; `git diff --check` passes.
43
+ No application dependency or SDK peer changes.
44
+
7
45
  ## [2.5.0] — 2026-10-10
8
46
 
9
- Prepared for publication; not yet published. Latest verified npm release is
10
- 2.4.0. Publish Core 2.5.0 before CLI 3.6.0, whose game template uses 2× sheets.
47
+ Published; registry version metadata and latest verified on 2026-10-10.
48
+ CLI 3.6.0 is also published; its game template requires this Core release.
49
+ The published archive downloads through npm pack with matching registry integrity;
50
+ its density API, text baseline module and fractional scissor correction are present.
11
51
 
12
52
  ### Added
13
53
 
package/README.md CHANGED
@@ -1,22 +1,38 @@
1
1
  # @kurot/core
2
2
 
3
- **2.5.0 is prepared for publication, not yet published.** Latest verified npm
4
- release: 2.4.0. This release adds independent source texture density and fixes
5
- text alignment, fractional viewport edges and root WebGL target restoration.
6
- See [texture density](docs/texture-density.md) and [CHANGELOG.md](CHANGELOG.md).
3
+ **2.5.1 is prepared, not published.** It makes dynamic middle alignment
4
+ independent of single/multiline mode. The latest verified registry release is
5
+ 2.5.0 (2026-10-10), with source texture density, text baselines, fractional
6
+ viewport fixes and root WebGL target restoration. See [CHANGELOG.md](CHANGELOG.md).
7
7
 
8
8
  A modern rewrite of the Egret game engine. Maintains Egret-compatible display object and event APIs while upgrading the rendering architecture, type safety, and tooling.
9
9
 
10
- > **Package version: 2.5.0 (pending publication).** Targets ES2022 and evergreen browsers (Chrome / Edge / Firefox / Safari). No IE / old-Android / pre-2022 Safari support shims.
10
+ > **Package version: 2.5.1 (prepared, not published).** Targets ES2022 and evergreen browsers (Chrome / Edge / Firefox / Safari). No IE / old-Android / pre-2022 Safari support shims.
11
+
12
+ ## Unified middle alignment in 2.5.1
13
+
14
+ `multiline` controls hard breaks and wrapping. Dynamic `verticalAlign = "middle"`
15
+ centers the complete glyph block in either line mode, including rich text and
16
+ fallback fonts. Blank rows retain their space; relative baselines and line
17
+ spacing remain unchanged. Identical single-row content centers identically
18
+ with either `multiline` value. Input keeps stable editing rows, including its
19
+ caret, selection and composition underline. Top/bottom alignment and nominal
20
+ measurement sizes retain their contracts.
21
+
22
+ Existing multiline middle-aligned captions may move. After publication,
23
+ adopt Core 2.5.1 and rebuild the application; existing SDK peers accept the patch
24
+ without new releases. This source change does not update installed Editors or
25
+ games. See [text alignment](docs/text-layout.md#middle-alignment-independent-of-line-mode-core-251).
11
26
 
12
27
  ## Text alignment correction in 2.5.0
13
28
 
14
- Single-line dynamic middle alignment centers measured glyph ink, including
15
- fallback fonts and accents. Multiline text and input keep stable font baselines;
16
- rich runs share an alphabetic baseline. Layout sizes and wrapping stay nominal.
29
+ Published 2.5.0 centers single-line dynamic middle alignment by measured glyph
30
+ ink, including fallback fonts and accents. Its multiline text and input retain
31
+ font-frame alignment; rich runs share an alphabetic baseline. Version 2.5.1
32
+ extends visual middle alignment to multiline dynamic text. Layout sizes and wrapping stay nominal.
17
33
  The existing font-readiness invalidation refreshes these cached metrics.
18
34
  See [text alignment](docs/text-layout.md#visual-centering-and-stable-baselines-core-250).
19
- Install Core 2.5.0 after publication and rebuild to adopt it; published 2.4.0
35
+ Install Core 2.5.0 and rebuild to adopt it; published 2.4.0
20
36
  does not include the correction. Existing SDK peer ranges require no change.
21
37
 
22
38
  ## Bitmap fill correction in 2.3.3
@@ -123,7 +139,6 @@ matching UI release; Core does not expose `textFit` or `minFontSize`.
123
139
  ## Installation
124
140
 
125
141
  ```bash
126
- # After Core 2.5.0 is published:
127
142
  pnpm add @kurot/core@^2.5.0
128
143
  ```
129
144
 
@@ -0,0 +1,12 @@
1
+ import type { TextField } from './TextField.js';
2
+ interface TextBlockBounds {
3
+ readonly top: number;
4
+ readonly bottom: number;
5
+ }
6
+ /**
7
+ * Measures the complete glyph block, retaining nominal rows for blank lines.
8
+ * Outlines belong to render padding and do not change alignment.
9
+ */
10
+ export declare function getTextBlockBounds(field: TextField): TextBlockBounds;
11
+ export {};
12
+ //# sourceMappingURL=TextBlockBounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextBlockBounds.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/TextBlockBounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,UAAU,eAAe;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAKD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,eAAe,CAkBpE"}
@@ -0,0 +1,25 @@
1
+ // Font, wrapping and line-spacing invalidation replace the field's line array.
2
+ const boundsCache = new WeakMap();
3
+ /**
4
+ * Measures the complete glyph block, retaining nominal rows for blank lines.
5
+ * Outlines belong to render padding and do not change alignment.
6
+ */
7
+ export function getTextBlockBounds(field) {
8
+ const lines = field.getLinesArr();
9
+ const cached = boundsCache.get(lines);
10
+ if (cached)
11
+ return cached;
12
+ let top = Infinity;
13
+ let bottom = -Infinity;
14
+ let lineTop = 0;
15
+ for (const line of lines) {
16
+ const hasInk = line.inkAscent + line.inkDescent > 0;
17
+ top = Math.min(top, lineTop + (hasInk ? line.baseline - line.inkAscent : 0));
18
+ bottom = Math.max(bottom, lineTop + (hasInk ? line.baseline + line.inkDescent : line.height));
19
+ lineTop += line.height + field.lineSpacing;
20
+ }
21
+ const bounds = { top: top === Infinity ? 0 : top, bottom: bottom === -Infinity ? 0 : bottom };
22
+ boundsCache.set(lines, bounds);
23
+ return bounds;
24
+ }
25
+ //# sourceMappingURL=TextBlockBounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextBlockBounds.js","sourceRoot":"","sources":["../../../src/kurot/text/TextBlockBounds.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,MAAM,WAAW,GAAG,IAAI,OAAO,EAAmC,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAgB;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,IAAI,MAAM,GAAG,CAAC,QAAQ,CAAC;IACvB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpD,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9F,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9F,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TextRenderBounds.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/TextRenderBounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAqBjE"}
1
+ {"version":3,"file":"TextRenderBounds.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/TextRenderBounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKhD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAuBjE"}
@@ -1,3 +1,4 @@
1
+ import { getTextBlockBounds } from './TextBlockBounds.js';
1
2
  import { TextFieldType } from './enums/TextFieldType.js';
2
3
  import { VerticalAlign } from './enums/VerticalAlign.js';
3
4
  /**
@@ -9,10 +10,12 @@ export function getTextRenderPadding(textField) {
9
10
  return 0;
10
11
  let stroke = 0;
11
12
  let overhang = 0;
13
+ const bounds = getTextBlockBounds(textField);
14
+ const baselineShift = textField.verticalAlign === VerticalAlign.MIDDLE
15
+ ? (textField.textHeight - bounds.top - bounds.bottom) / 2 : 0;
12
16
  for (const line of textField.getLinesArr()) {
13
17
  if (line.inkAscent + line.inkDescent > 0) {
14
- const baseline = !textField.multiline && textField.verticalAlign === VerticalAlign.MIDDLE
15
- ? (line.height + line.inkAscent - line.inkDescent) / 2 : line.baseline;
18
+ const baseline = line.baseline + baselineShift;
16
19
  overhang = Math.max(overhang, line.inkAscent - baseline, baseline + line.inkDescent - line.height);
17
20
  }
18
21
  for (const element of line.elements) {
@@ -1 +1 @@
1
- {"version":3,"file":"TextRenderBounds.js","sourceRoot":"","sources":["../../../src/kurot/text/TextRenderBounds.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAoB;IACxD,IAAI,SAAS,CAAC,IAAI,KAAK,aAAa,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IAErD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,QAAQ,GAAG,CAAC,SAAS,CAAC,SAAS,IAAI,SAAS,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM;gBACxF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;YACxE,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,GAAG,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACpG,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC;YACxD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,MAAM,EAAE,CAAC;gBAC9C,MAAM,GAAG,KAAK,CAAC;YAChB,CAAC;QACF,CAAC;IACF,CAAC;IAED,yEAAyE;IACzE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC"}
1
+ {"version":3,"file":"TextRenderBounds.js","sourceRoot":"","sources":["../../../src/kurot/text/TextRenderBounds.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAoB;IACxD,IAAI,SAAS,CAAC,IAAI,KAAK,aAAa,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IAErD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM;QACrE,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAC5C,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,aAAa,CAAC;YAC/C,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,GAAG,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACpG,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC;YACxD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,MAAM,EAAE,CAAC;gBAC9C,MAAM,GAAG,KAAK,CAAC;YAChB,CAAC;QACF,CAAC;IACF,CAAC;IAED,yEAAyE;IACzE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { TextField } from './TextField.js';
2
2
  /**
3
- * Single-line dynamic middle alignment centers visible ink. Multiline and input
4
- * retain nominal line boxes so changing glyphs cannot move their baselines.
3
+ * Dynamic middle alignment centers the complete glyph block independently of
4
+ * multiline. Input retains nominal rows for stable editing geometry.
5
5
  */
6
6
  export declare function getTextVerticalOffset(field: TextField): number;
7
7
  //# sourceMappingURL=TextVerticalLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TextVerticalLayout.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/TextVerticalLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAe9D"}
1
+ {"version":3,"file":"TextVerticalLayout.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/TextVerticalLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKhD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAa9D"}
@@ -1,17 +1,16 @@
1
+ import { getTextBlockBounds } from './TextBlockBounds.js';
1
2
  import { TextFieldType } from './enums/TextFieldType.js';
2
3
  import { VerticalAlign } from './enums/VerticalAlign.js';
3
4
  /**
4
- * Single-line dynamic middle alignment centers visible ink. Multiline and input
5
- * retain nominal line boxes so changing glyphs cannot move their baselines.
5
+ * Dynamic middle alignment centers the complete glyph block independently of
6
+ * multiline. Input retains nominal rows for stable editing geometry.
6
7
  */
7
8
  export function getTextVerticalOffset(field) {
8
- const lines = field.getLinesArr();
9
9
  const height = !isNaN(field.$explicitHeight) ? field.$explicitHeight : field.textHeight;
10
- const line = lines[0];
11
10
  if (field.verticalAlign === VerticalAlign.MIDDLE) {
12
- if (field.type === TextFieldType.DYNAMIC && !field.multiline && line
13
- && line.inkAscent + line.inkDescent > 0) {
14
- return (height - line.inkAscent - line.inkDescent) / 2 + line.inkAscent - line.baseline;
11
+ if (field.type === TextFieldType.DYNAMIC) {
12
+ const bounds = getTextBlockBounds(field);
13
+ return (height - bounds.top - bounds.bottom) / 2;
15
14
  }
16
15
  return Math.max(0, (height - field.textHeight) / 2);
17
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TextVerticalLayout.js","sourceRoot":"","sources":["../../../src/kurot/text/TextVerticalLayout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAgB;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;IACxF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,KAAK,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI;eAChE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QACzF,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,KAAK,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC"}
1
+ {"version":3,"file":"TextVerticalLayout.js","sourceRoot":"","sources":["../../../src/kurot/text/TextVerticalLayout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAgB;IACrD,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;IACxF,IAAI,KAAK,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,OAAO,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACzC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,KAAK,CAAC,aAAa,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC"}
@@ -1,7 +1,8 @@
1
1
  # @kurot/core — AI context map
2
2
 
3
- Core 2.5.0 is prepared for publication, not yet published. Latest verified npm
4
- release is 2.4.0. Read
3
+ Core 2.5.1 is prepared, not published, with dynamic middle alignment independent
4
+ of multiline. Core 2.5.0 remains the latest verified registry release
5
+ (2026-10-10). Read
5
6
  [centered flips](../../../docs/centered-flips.md) before changing transforms,
6
7
  reflection frames, authored flags or dependency adoption.
7
8
 
@@ -15,7 +16,7 @@ agent unfamiliar with Kurot does not need to re-derive the architecture from
15
16
  scratch on every session. Treat the package source and its `src/index.ts`
16
17
  barrel as the authority for current behavior and exports.
17
18
 
18
- Package identity: `@kurot/core@2.5.0`, pending publication. It provides Kurot's scene graph,
19
+ Package identity: `@kurot/core@2.5.1`, prepared, not published. It provides Kurot's scene graph,
19
20
  events, rendering, text, resource, network and media runtime. Rendering uses a
20
21
  flat `InstructionSet + RenderPipe` pipeline. ES2022 / evergreen browsers only
21
22
  with `strict: true`. Two
@@ -53,7 +54,7 @@ src/kurot/
53
54
  │ push/pop scene integration in player/pipes/.
54
55
  ├── text/ TextField, BitmapText/BitmapFont, StageText (DOM overlay, INPUT mode only),
55
56
  │ HtmlTextParser, InputController, TextMeasurer, TextSegmentation,
56
- │ LineBreaks, TextLineLayout, WordWrap.
57
+ │ LineBreaks, TextLineLayout, TextBlockBounds, TextVerticalLayout, WordWrap.
57
58
  ├── resource/ Resource class + `resource` singleton, ResourceLoader, analyzers/
58
59
  │ (Image/Json/Text/Sound/Sheet/Font). Async, resource.json-driven (RES-compatible).
59
60
  ├── net/ HttpRequest, ImageLoader. Low-level; resource/analyzers build on these.
@@ -227,7 +228,7 @@ Re-export order: `events`, `geom`, `utils`, `display`, `net`, `filters`,
227
228
  | Change how instructions are built/executed | `player/webgl/WebGLRenderer.ts`, `player/InstructionSet.ts` |
228
229
  | Add a new resource type/parser | `resource/analyzers/`, register in `Resource.ts` |
229
230
  | Debug a texture-batching issue | `player/webgl/MultiTextureBatcher.ts`, `player/webgl/WebGLDrawCmdManager.ts` |
230
- | Change text layout/wrapping | `text/LineBreaks.ts`, `text/TextLineLayout.ts`, `text/TextSegmentation.ts`, `text/TextMeasurer.ts`; `docs/text-layout.md` |
231
+ | Change text layout/wrapping | `text/LineBreaks.ts`, `text/TextLineLayout.ts`, `text/TextBlockBounds.ts`, `text/TextVerticalLayout.ts`, `text/TextSegmentation.ts`, `text/TextMeasurer.ts`; `docs/text-layout.md` |
231
232
  | Understand dirty-flag propagation | `display/DisplayObject.ts` (`$markDirty`, `$cacheDirtyUp`, `$renderDirtyUp`) |
232
233
  | Run perf tests | `examples/benchmark/`, `pnpm benchmark`; automated Kurot/PixiJS/Egret comparison via `pnpm benchmark:compare` |
233
234
 
@@ -240,17 +241,21 @@ vertices, UVs, or indices, call updateVertices(). WebGL keeps Mesh indices
240
241
  separate from quad indices and splits oversized meshes into ordered batches
241
242
  with local index remapping (player/webgl/split-mesh.ts).
242
243
 
243
- ## Text alignment in 2.5.0
244
+ ## Text alignment in 2.5.1
244
245
 
245
246
  `TextLineLayout.ts` caches alphabetic `baseline` and visible `inkAscent` /
246
- `inkDescent` per line. `TextVerticalLayout.ts` centers ink only for dynamic,
247
- single-line middle alignment. Multiline/INPUT/top/bottom retain nominal rows
248
- with content-independent font frames, including blank lines. Canvas, WebGL
247
+ `inkDescent` per line. `TextBlockBounds.ts` caches the union of row glyph bounds,
248
+ using nominal rows for blank lines. `TextVerticalLayout.ts` centers the entire
249
+ dynamic block in either line mode. Multiline only controls line generation;
250
+ input/top/bottom retain nominal alignment with content-independent font frames.
251
+ Relative row baselines and spacing stay stable, but a dynamic middle block can
252
+ translate when its glyph extents change. Canvas, WebGL
249
253
  rasterization and link hit testing share the offset. Render bounds add glyph
250
254
  margin without changing measurement, input or external clip boundaries. See
251
- [text layout](text-layout.md#visual-centering-and-stable-baselines-core-250).
252
- Core 2.4.0 on npm does not include this correction; existing consumer peer ranges
253
- accept 2.5.0 without SDK bumps. Adoption updates the Core installation/lock.
255
+ [text layout](text-layout.md#middle-alignment-independent-of-line-mode-core-251).
256
+ Published Core 2.5.0 centers only single-line dynamic ink. Version 2.5.1 is
257
+ prepared, not published; existing consumer peers accept it without SDK bumps.
258
+ Adoption requires an explicit installation/lock update and rebuild after release.
254
259
 
255
260
  ## Text layout in 2.0.1
256
261
 
@@ -1,6 +1,6 @@
1
1
  # @kurot/core architecture
2
2
 
3
- > Package version: 2.5.0 (pending publication; latest verified npm release: 2.4.0). See [CHANGELOG.md](../CHANGELOG.md) for release notes.
3
+ > Package version: 2.5.0 (published; registry version metadata verified on 2026-10-10). See [CHANGELOG.md](../CHANGELOG.md) for release notes.
4
4
  > The [AI context map](./ai-context.md) covers directories, non-obvious behavior,
5
5
  > terminology and task-to-file references. This document explains design choices
6
6
  > and internal mechanisms for contributors; the two documents complement each other.
@@ -40,8 +40,60 @@ Dictionary and grapheme segmentation follow the browser's `Intl` data. The
40
40
  pinned Unicode 17.0 tables govern the line-break profile; they do not replace
41
41
  the browser's grapheme implementation.
42
42
 
43
+ ## Middle alignment independent of line mode (Core 2.5.1)
44
+
45
+ Core 2.5.1 is prepared, not published. The current verified registry release is
46
+ 2.5.0. Its single-line-only visual centering is described in the following section.
47
+
48
+ `multiline` controls hard breaks and width wrapping. Dynamic
49
+ `verticalAlign = "middle"` centers the complete laid-out glyph block in either
50
+ line mode. Identical single-row content, font and bounds produce identical
51
+ placement with `multiline = true` or `false`, including automatic height.
52
+ Multiple rows receive one shared translation; their relative alphabetic baselines
53
+ and line spacing are preserved. Rich runs and fallback glyphs participate in the
54
+ same block bounds. Each blank/whitespace-only row contributes its nominal height,
55
+ including leading and trailing blank rows. An entirely blank block uses those
56
+ nominal rows. Outlines remain render-only and do not affect alignment.
57
+
58
+ ```ts
59
+ textField.multiline = true;
60
+ textField.verticalAlign = 'middle';
61
+ textField.text = 'First line\nSecond line';
62
+ ```
63
+
64
+ The visible block is centered within the explicit height, or the nominal content
65
+ height when height is automatic. An overflowing dynamic block remains centered;
66
+ ordinary viewport clipping and scrolling still apply. Changing the outer glyph
67
+ extents can translate the complete block, without changing its row baselines or
68
+ distances. The cache reuses block bounds until font/content/wrapping/spacing
69
+ invalidation replaces the line layout.
70
+
71
+ INPUT continues to align its stable editing rows in either line mode. Caret,
72
+ selection, composition underline and pointer-to-character mapping use those rows,
73
+ so changing glyphs cannot move the editing frame. `top` and `bottom` retain their
74
+ nominal-row semantics. `textHeight`, `measureText()`, automatic dimensions, font
75
+ fitting and wrapping measurements retain their existing contracts.
76
+
77
+ Canvas, WebGL text rasterization, link hit tests and glyph/outline capture margins
78
+ share the block alignment. External masks/scrollRect and input viewports keep
79
+ their authored clip boundaries. No new public property, runtime dependency or
80
+ XML/resource format is introduced. Existing multiline dynamic middle-aligned
81
+ captions can move compared with 2.5.0; adopt the released Core patch and rebuild
82
+ the application to receive the change. Existing SDK peers accept the patch.
83
+
84
+ Unit checks cover both line modes, complete paragraphs, mixed-size rich text,
85
+ blank rows, font/spacing invalidation, top/bottom, short viewports, links and
86
+ single/multiline input caret/selection. The minified browser checks compare
87
+ independent Canvas measurements and actual pixels in Canvas and WebGL 1/2 at
88
+ 1×/2×, including multilingual text, wrapping, caches, outlines, caret, selection,
89
+ composition decoration and input character hits.
90
+
43
91
  ## Visual centering and stable baselines (Core 2.5.0)
44
92
 
93
+ This section records the published 2.5.0 behavior. Version 2.5.1 extends dynamic
94
+ middle alignment to multiple rows as described above; input and top/bottom retain
95
+ the contracts below.
96
+
45
97
  Canvas and WebGL use the same alphabetic baseline and logical-pixel Canvas
46
98
  `TextMetrics`. Each cached line records `baseline`, `inkAscent` and `inkDescent`;
47
99
  rich runs share that baseline, even when their sizes or font families differ.
@@ -66,11 +118,10 @@ or operating-system offset table is used.
66
118
  the actual selected glyphs rather than promising identical pixels.
67
119
 
68
120
  See the [Canvas TextMetrics contract](https://html.spec.whatwg.org/multipage/canvas.html#textmetrics).
69
- The correction is included in Core 2.5.0, pending publication; it is not in
121
+ The correction is included in published Core 2.5.0; it is not in
70
122
  published Core 2.4.0. There is no new alignment
71
123
  property, runtime dependency or XML format. Default KUI single-line control
72
- labels use the existing flags with symmetric padding. Adopt Core 2.5.0 after
73
- publication and rebuild to receive this behavior; SDK peers need no
124
+ labels use the existing flags with symmetric padding. Adopt Core 2.5.0 and rebuild to receive this behavior; SDK peers need no
74
125
  incidental change.
75
126
 
76
127
  `test/TextVerticalLayout.test.ts` covers ink centering, mixed sizes, stable input
@@ -1,7 +1,7 @@
1
1
  # Texture density
2
2
 
3
- This API is included in Core 2.5.0, prepared for publication but not yet
4
- published. Published Core 2.4.0 and earlier do not interpret sheet `resolution`.
3
+ This API is included in published Core 2.5.0, with registry version metadata
4
+ verified on 2026-10-10. Published Core 2.4.0 and earlier do not interpret sheet `resolution`.
5
5
 
6
6
  `new Texture(resolution)` describes source pixels per logical unit. The default
7
7
  is 1; the value must be finite and positive, is immutable, and is inherited by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurot/core",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Modern rewrite of the Egret game engine — WebGL multi-texture batching, instruction-driven render pipeline, strict TypeScript, Egret-compatible API",
5
5
  "author": "Kelvin <kyia.x52@gmail.com>",
6
6
  "license": "MIT",