refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,342 @@
/**
* TextLayout
*
* Text layout engine for MSDF text rendering.
* Handles line breaking, alignment, and glyph positioning.
*
* MSDF 文本渲染的文本布局引擎
* 处理换行、对齐和字形定位
*/
import type { MSDFFont, IMSDFGlyph } from './MSDFFont';
import { EAlignType, EVertAlignType } from '../core/FieldTypes';
/**
* Positioned glyph for rendering
* 用于渲染的定位字形
*/
export interface IPositionedGlyph {
/** Glyph data | 字形数据 */
glyph: IMSDFGlyph;
/** X position in pixels | X 位置(像素) */
x: number;
/** Y position in pixels | Y 位置(像素) */
y: number;
/** Glyph width in pixels | 字形宽度(像素) */
width: number;
/** Glyph height in pixels | 字形高度(像素) */
height: number;
/** UV coordinates [u0, v0, u1, v1] | UV 坐标 */
uv: [number, number, number, number];
}
/**
* Layout line
* 布局行
*/
interface ILayoutLine {
/** Glyphs in this line | 此行中的字形 */
glyphs: IPositionedGlyph[];
/** Line width in pixels | 行宽(像素) */
width: number;
/** Line start Y position | 行起始 Y 位置 */
y: number;
}
/**
* Text layout options
* 文本布局选项
*/
export interface ITextLayoutOptions {
/** Font to use | 使用的字体 */
font: MSDFFont;
/** Text content | 文本内容 */
text: string;
/** Font size in pixels | 字体大小(像素) */
fontSize: number;
/** Maximum width (for word wrap) | 最大宽度(用于换行) */
maxWidth?: number;
/** Maximum height | 最大高度 */
maxHeight?: number;
/** Horizontal alignment | 水平对齐 */
align?: EAlignType;
/** Vertical alignment | 垂直对齐 */
valign?: EVertAlignType;
/** Line height multiplier | 行高倍数 */
lineHeight?: number;
/** Letter spacing in pixels | 字间距(像素) */
letterSpacing?: number;
/** Word wrap enabled | 是否启用换行 */
wordWrap?: boolean;
/** Single line mode | 单行模式 */
singleLine?: boolean;
}
/**
* Text layout result
* 文本布局结果
*/
export interface ITextLayoutResult {
/** Positioned glyphs ready for rendering | 准备渲染的定位字形 */
glyphs: IPositionedGlyph[];
/** Total width of laid out text | 布局文本的总宽度 */
width: number;
/** Total height of laid out text | 布局文本的总高度 */
height: number;
/** Number of lines | 行数 */
lineCount: number;
}
/**
* Layout text into positioned glyphs
* 将文本布局为定位字形
*/
export function layoutText(options: ITextLayoutOptions): ITextLayoutResult {
const {
font,
text,
fontSize,
maxWidth = Infinity,
maxHeight = Infinity,
align = EAlignType.Left,
valign = EVertAlignType.Top,
lineHeight = 1.2,
letterSpacing = 0,
wordWrap = false,
singleLine = false
} = options;
if (!text || !font) {
return { glyphs: [], width: 0, height: 0, lineCount: 0 };
}
const metrics = font.metrics;
const atlas = font.atlas;
// Calculate scale from em units to pixels
const scale = fontSize / metrics.emSize;
const lineHeightPx = fontSize * lineHeight;
// Atlas dimensions for UV calculation
const atlasWidth = atlas.width;
const atlasHeight = atlas.height;
const yFlip = atlas.yOrigin === 'bottom';
const lines: ILayoutLine[] = [];
let currentLine: IPositionedGlyph[] = [];
let currentX = 0;
let currentY = 0;
let maxLineWidth = 0;
let prevCharCode = 0;
// Process each character
for (let i = 0; i < text.length; i++) {
const char = text[i];
const charCode = char.charCodeAt(0);
// Handle newline
if (char === '\n') {
if (singleLine) continue;
lines.push({
glyphs: currentLine,
width: currentX,
y: currentY
});
maxLineWidth = Math.max(maxLineWidth, currentX);
currentLine = [];
currentX = 0;
currentY += lineHeightPx;
prevCharCode = 0;
continue;
}
// Handle carriage return
if (char === '\r') continue;
// Get glyph
const glyph = font.getGlyph(charCode);
if (!glyph) {
// Try space as fallback
const spaceGlyph = font.getGlyph(32);
if (spaceGlyph) {
currentX += spaceGlyph.advance * scale + letterSpacing;
}
prevCharCode = charCode;
continue;
}
// Apply kerning
if (prevCharCode) {
currentX += font.getKerning(prevCharCode, charCode) * scale;
}
// Check word wrap
const glyphAdvance = glyph.advance * scale + letterSpacing;
if (wordWrap && !singleLine && currentX + glyphAdvance > maxWidth && currentLine.length > 0) {
// Word wrap - start new line
lines.push({
glyphs: currentLine,
width: currentX,
y: currentY
});
maxLineWidth = Math.max(maxLineWidth, currentX);
currentLine = [];
currentX = 0;
currentY += lineHeightPx;
// Check max height
if (currentY + lineHeightPx > maxHeight) {
break;
}
}
// Position glyph if it has atlas bounds
if (glyph.planeBounds && glyph.atlasBounds) {
const pb = glyph.planeBounds;
const ab = glyph.atlasBounds;
// Calculate glyph position and size
const glyphX = currentX + pb.left * scale;
const glyphY = currentY + (metrics.ascender - pb.top) * scale;
const glyphWidth = (pb.right - pb.left) * scale;
const glyphHeight = (pb.top - pb.bottom) * scale;
// Calculate UV coordinates
let u0 = ab.left / atlasWidth;
let v0 = ab.bottom / atlasHeight;
let u1 = ab.right / atlasWidth;
let v1 = ab.top / atlasHeight;
// Flip V if Y origin is top
if (!yFlip) {
v0 = 1 - v0;
v1 = 1 - v1;
[v0, v1] = [v1, v0];
}
currentLine.push({
glyph,
x: glyphX,
y: glyphY,
width: glyphWidth,
height: glyphHeight,
uv: [u0, v0, u1, v1]
});
}
currentX += glyphAdvance;
prevCharCode = charCode;
}
// Add last line
if (currentLine.length > 0 || lines.length === 0) {
lines.push({
glyphs: currentLine,
width: currentX,
y: currentY
});
maxLineWidth = Math.max(maxLineWidth, currentX);
}
const totalHeight = currentY + lineHeightPx;
const lineCount = lines.length;
// Apply horizontal alignment
for (const line of lines) {
let offsetX = 0;
if (align === EAlignType.Center) {
offsetX = (maxWidth === Infinity ? 0 : (maxWidth - line.width) / 2);
} else if (align === EAlignType.Right) {
offsetX = maxWidth === Infinity ? 0 : (maxWidth - line.width);
}
for (const glyph of line.glyphs) {
glyph.x += offsetX;
}
}
// Apply vertical alignment
let offsetY = 0;
if (valign === EVertAlignType.Middle) {
offsetY = (maxHeight === Infinity ? 0 : (maxHeight - totalHeight) / 2);
} else if (valign === EVertAlignType.Bottom) {
offsetY = maxHeight === Infinity ? 0 : (maxHeight - totalHeight);
}
if (offsetY !== 0) {
for (const line of lines) {
for (const glyph of line.glyphs) {
glyph.y += offsetY;
}
}
}
// Flatten glyphs
const allGlyphs: IPositionedGlyph[] = [];
for (const line of lines) {
allGlyphs.push(...line.glyphs);
}
return {
glyphs: allGlyphs,
width: maxLineWidth,
height: totalHeight,
lineCount
};
}
/**
* Measure text dimensions without full layout
* 测量文本尺寸(不进行完整布局)
*/
export function measureText(font: MSDFFont, text: string, fontSize: number, letterSpacing: number = 0): { width: number; height: number } {
if (!text || !font) {
return { width: 0, height: 0 };
}
const metrics = font.metrics;
const scale = fontSize / metrics.emSize;
let width = 0;
let prevCharCode = 0;
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i);
const glyph = font.getGlyph(charCode);
if (glyph) {
if (prevCharCode) {
width += font.getKerning(prevCharCode, charCode) * scale;
}
width += glyph.advance * scale + letterSpacing;
}
prevCharCode = charCode;
}
return {
width,
height: fontSize * 1.2
};
}