Skip to content

Commit

Permalink
Feat:The first version of text system (#686)
Browse files Browse the repository at this point in the history
* feat(text): init TextRenderer
  • Loading branch information
singlecoder authored Mar 29, 2022
1 parent afa176a commit c0ea1a2
Show file tree
Hide file tree
Showing 15 changed files with 1,193 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@rollup/plugin-node-resolve": "^11.0.1",
"@rollup/plugin-replace": "^2.3.4",
"@types/jest": "^26.0.20",
"@types/offscreencanvas": "^2019.6.4",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"babel-jest": "^26.6.3",
Expand Down
95 changes: 95 additions & 0 deletions packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Rect } from "@oasis-engine/math";
import { Engine } from "../../Engine";
import { Texture2D } from "../../texture/Texture2D";
import { Sprite } from "../sprite/Sprite";

/**
* Dynamic atlas for text.
*/
export class DynamicTextAtlas {
private static _region: Rect = new Rect();

private _texture: Texture2D;
private _width: number;
private _height: number;

private _space: number = 1;
private _curX: number = 1;
private _curY: number = 1;
private _nextY: number = 1;

private _sprites: Record<number, Sprite> = {};

constructor(engine: Engine, width: number, height: number) {
this._width = width;
this._height = height;
this._texture = new Texture2D(engine, width, height);
this._texture._addRefCount(1);
}

/**
* Destroy atlas, it will release the texture.
*/
public destroy() {
this._sprites = {};
this._texture.destroy(true);
}

/**
* Add a sprite.
* @param sprite - the sprite to add
* @param imageSource - The source of texture
* @returns true if add sprite success, otherwise false
*/
public addSprite(sprite: Sprite, imageSource: TexImageSource | OffscreenCanvas): boolean {
const { _space: space, _texture: texture } = this;
const { width, height } = imageSource;

const endX = this._curX + width + space;
if (endX >= this._width) {
this._curX = space;
this._curY = this._nextY + space;
}

const endY = this._curY + height + space;
if (endY > this._nextY) {
this._nextY = endY;
}

if (this._nextY >= this._height) {
return false;
}

texture.setImageSource(imageSource, 0, false, false, this._curX, this._curY);
texture.generateMipmaps();

const { _width, _height } = this;
const region = DynamicTextAtlas._region;
region.setValue(this._curX / _width, this._curY / _height, width / _width, height / _height);

// destroy origin texture.
sprite.texture && sprite.texture.destroy();
// Update atlas texture.
sprite.atlasRegion = region;
sprite.texture = texture;
this._curX = endX + space;

return true;
}

/**
* Remove a sprite.
* @param sprite - the sprite to remove
* @returns true if remove sprite success, otherwise false
*/
public removeSprite(sprite: Sprite): boolean {
const id = sprite.instanceId;
const { _sprites } = this;
if (_sprites[id]) {
delete _sprites[id];
return true;
}
return false;
}
}

126 changes: 126 additions & 0 deletions packages/core/src/2d/dynamic-atlas/DynamicTextAtlasManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Sprite } from "../sprite/Sprite";
import { Engine } from "../../Engine";
import { DynamicTextAtlas } from "./DynamicTextAtlas";

/**
* Dynamic atlas manager for text.
*/
export class DynamicTextAtlasManager {
private _maxAtlasCount: number = 2;
private _textureSize: number = 1024;
private _atlases: Array<DynamicTextAtlas> = [];
private _atlasIndex: number = -1;
private _spritesInAtlasIndex: Record<number, number> = {};

/**
* Indicates how many atlases should be created.
*/
get maxAtlasCount(): number {
return this._maxAtlasCount;
}

set maxAtlasCount(val: number) {
this._maxAtlasCount = val;
}

/**
* Indicates the size of the texture.
*/
get textureSize(): number {
return this._textureSize;
}

set textureSize(val: number) {
this._textureSize = Math.min(val, 2048);
}

/**
* @internal
*/
constructor(public readonly engine: Engine) {}

/**
* Add a sprite to atlas.
* @param sprite - the sprite to add
* @param imageSource - The source of texture
* @returns true if add sprite success, otherwise false
*/
public addSprite(sprite: Sprite, imageSource: TexImageSource | OffscreenCanvas): boolean {
// Remove sprite if the sprite has been add.
const { _spritesInAtlasIndex, _atlases } = this;
const id = sprite.instanceId;
const atlasIndex = _spritesInAtlasIndex[id];
if (atlasIndex) {
_atlases[atlasIndex].removeSprite(sprite);
delete _spritesInAtlasIndex[id];
}

if (this._atlasIndex >= this._maxAtlasCount) {
return false;
}

let atlas = _atlases[this._atlasIndex];
if (!atlas) {
atlas = this._createAtlas();
}

if (atlas.addSprite(sprite, imageSource)) {
_spritesInAtlasIndex[id] = this._atlasIndex;
return true;
}

if (this._atlasIndex + 1 >= this._maxAtlasCount) {
this._atlasIndex = this._maxAtlasCount;
return false;
}

atlas = this._createAtlas();
if (atlas.addSprite(sprite, imageSource)) {
_spritesInAtlasIndex[id] = this._atlasIndex;
return true;
}
return false;
}

/**
* Remove a sprite from atlas.
* @param sprite - the sprite to remove
* @returns true if remove sprite success, otherwise false
*/
public removeSprite(sprite: Sprite): boolean {
if (!sprite) return false;

const { _atlases } = this;
for (let i = _atlases.length - 1; i >= 0; --i) {
const atlas = _atlases[i];
if(atlas.removeSprite(sprite)) {
delete this._spritesInAtlasIndex[i];
return true;
}
}

return false;
}

/**
* Reset all atlases.
*/
public reset() {
const { _atlases } = this;
for (let i = 0, l = _atlases.length; i < l; ++i) {
_atlases[i].destroy();
}

_atlases.length = 0;
this._atlasIndex = -1;
this._spritesInAtlasIndex = {};
}

private _createAtlas(): DynamicTextAtlas {
this._atlasIndex++;
const { _textureSize } = this;
const atlas = new DynamicTextAtlas(this.engine, _textureSize, _textureSize);
this._atlases.push(atlas);
return atlas;
}
}
11 changes: 11 additions & 0 deletions packages/core/src/2d/enums/FontStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* The style of the font.
*/
export enum FontStyle {
/** Set font without style */
None = 0x0,
/** Set font bold */
Bold = 0x1,
/** Set font italic */
Italic = 0x2
}
23 changes: 23 additions & 0 deletions packages/core/src/2d/enums/TextAlignment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* The horizontal alignment of the text.
*/
export enum TextHorizontalAlignment {
/** Align left horizontally */
Left = 0,
/** Align center horizontally */
Center = 1,
/** Align right horizontally */
Right = 2
}

/**
* The vertical alignment of the text.
*/
export enum TextVerticalAlignment {
/** Align top vertically */
Top = 0,
/** Align center vertically */
Center = 1,
/** Align bottom vertically */
Bottom = 2
}
9 changes: 9 additions & 0 deletions packages/core/src/2d/enums/TextOverflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* The way to handle the situation where wrapped text is too tall to fit in the height.
*/
export enum OverflowMode {
/** Overflow when the text is too tall */
Overflow = 0,
/** Truncate with height when the text is too tall */
Truncate = 1
}
4 changes: 4 additions & 0 deletions packages/core/src/2d/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction";
export { SpriteMaskLayer } from "./enums/SpriteMaskLayer";
export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlignment";
export { OverflowMode } from "./enums/TextOverflow";
export { FontStyle } from "./enums/FontStyle";
export { SpriteAtlas } from "./atlas/SpriteAtlas";
export * from "./sprite/index";
export * from "./text/index";
45 changes: 45 additions & 0 deletions packages/core/src/2d/text/Font.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { RefObject } from "../../asset/RefObject";
import { Engine } from "../../Engine";

/**
* Font.
*/
export class Font extends RefObject {
private static _fontMap: Record<string, Font> = {};

/**
* Create a font from OS.
* @param engine - Engine to which the font belongs
* @param fontName - The name of font
* @returns The font object has been create
*/
static createFromOS(engine: Engine, fontName: string = ""): Font {
const fontMap = Font._fontMap;
let font = fontMap[fontName];
if (font) {
return font;
}
font = new Font(engine, fontName);
fontMap[fontName] = font;
return font;
}

private _name: string = "";

/**
* The name of the font object.
*/
get name(): string {
return this._name;
}

private constructor(engine: Engine, name: string = "") {
super(engine);
this._name = name;
}

/**
* @override
*/
protected _onDestroy(): void {}
}
Loading

0 comments on commit c0ea1a2

Please sign in to comment.