UNPKG

playable.js

Version:

A lightweight HTML5 game engine.

359 lines (314 loc) 9.29 kB
import {Layer} from './Layer'; import {Rectangle} from '../geom/Rectangle'; export class Text extends Layer { public static defaultFontSize: number = 16; protected static readonly wordRe: RegExp = /\w+/; protected static readonly boundaryRe: RegExp = /\b/; protected $text: string = ''; protected $color: string = 'black'; protected $fontSize: number = Text.defaultFontSize; protected $fontStyle: FontStyle = 'normal'; protected $fontWeight: FontWeight = 'normal'; protected $textAlign: TextAlign = 'left'; protected $verticalAlign: VerticalAlign = 'top'; protected $lineHeight: number = 1.2; protected $strokeSize: number = 0; protected $strokeColor: string = null; protected $fontFamily: string = 'Helvetica'; protected $multiline: boolean = false; protected $breakWord: boolean = false; protected $autoFitSize: boolean = false; protected $minFontSize: number = 0; protected $explicitSize: number = 0; protected $lines: Array<string> = []; public constructor(text?: string, options: TextOption = {}) { super(); this.$text = text || this.$text; this.$color = options.color || this.$color; this.$fontSize = options.fontSize || this.$fontSize; this.$fontStyle = options.fontStyle || this.$fontStyle; this.$fontWeight = options.fontWeight || this.$fontWeight; this.$textAlign = options.textAlign || this.$textAlign; this.$verticalAlign = options.verticalAlign || this.$verticalAlign; this.$lineHeight = options.lineHeight || this.$lineHeight; this.$strokeSize = options.strokeSize || this.$strokeSize; this.$strokeColor = options.strokeColor || this.$strokeColor; this.$fontFamily = options.fontFamily || this.$fontFamily; this.$multiline = options.multiline || this.$multiline; this.$breakWord = options.breakWord || this.$breakWord; this.$autoFitSize = options.autoFitSize || this.autoFitSize; this.$minFontSize = options.minFontSize || this.minFontSize; this.$resizeCanvas(); } public get text(): string { return this.$text; } public set text(text: string) { this.$text = text; this.$resizeCanvas(); } public get color(): string { return this.$color; } public set color(color: string) { this.$color = color; this.$markDirty(); } public get fontSize(): number { return this.$fontSize; } public set fontSize(fontSize: number) { this.$fontSize = fontSize; this.$resizeCanvas(); } public get fontStyle(): FontStyle { return this.$fontStyle; } public set fontStyle(fontStyle: FontStyle) { this.$fontStyle = fontStyle; this.$resizeCanvas(); } public get fontWeight(): FontWeight { return this.$fontWeight; } public set fontWeight(fontWeight: FontWeight) { this.$fontWeight = fontWeight; this.$resizeCanvas(); } public get textAlign(): TextAlign { return this.$textAlign; } public set textAlign(textAlign: TextAlign) { this.$textAlign = textAlign; this.$markDirty(); } public get verticalAlign(): VerticalAlign { return this.$verticalAlign; } public set verticalAlign(verticalAlign: VerticalAlign) { this.$verticalAlign = verticalAlign; this.$markDirty(); } public get lineHeight(): number { return this.$lineHeight; } public set lineHeight(lineHeight: number) { this.$lineHeight = lineHeight; this.$resizeCanvas(); } public get strokeSize(): number { return this.$strokeSize; } public set strokeSize(strokeSize: number) { this.$strokeSize = strokeSize; this.$markDirty(); } public get strokeColor(): string { return this.$strokeColor; } public set strokeColor(strokeColor: string) { this.$strokeColor = strokeColor; this.$markDirty(); } public get fontFamily(): string { return this.$fontFamily; } public set fontFamily(fontFamily: string) { this.$fontFamily = fontFamily; this.$resizeCanvas(); } public get multiline(): boolean { return this.$multiline; } public set multiline(multiline: boolean) { this.$multiline = multiline; this.$resizeCanvas(); } public get breakWord(): boolean { return this.$breakWord; } public set breakWord(breakWord: boolean) { this.$breakWord = breakWord; this.$resizeCanvas(); } public get autoFitSize(): boolean { return this.$autoFitSize; } public set autoFitSize(autoFitSize: boolean) { this.$autoFitSize = autoFitSize; this.$resizeCanvas(); } public get minFontSize(): number { return this.$minFontSize; } public set minFontSize(minFontSize: number) { this.$minFontSize = minFontSize; this.$resizeCanvas(); } protected $updateContext(): void { let ctx = this.$context; let fontStyle = this.$fontStyle; let fontWeight = this.$fontWeight; let pixelRatio = Layer.pixelRatio; let fontSize = this.$explicitSize || this.$fontSize; let sizeStr = fontSize * pixelRatio + 'px'; ctx.font = fontStyle + ' ' + fontWeight + ' ' + sizeStr + ' ' + this.$fontFamily; ctx.textAlign = this.$textAlign; ctx.textBaseline = 'top'; ctx.fillStyle = this.$color; ctx.lineWidth = this.$strokeSize * pixelRatio; ctx.strokeStyle = this.$strokeColor; } protected $divideUnits(): Array<string> { let units; let text = this.$text; let breakWord = this.$breakWord; let wordRe = Text.wordRe; let boundaryRe = Text.boundaryRe; if (breakWord) { units = text.split(''); } else { let words = text.split(boundaryRe); units = []; for (let unit of words) { if (wordRe.test(unit)) { units.push(unit); } else { units = units.concat(unit.split('')); } } } return units; } protected $divideLines(): void { let text = this.$text; if (!this.$multiline) { this.$lines = [text]; return; } else if (!this.$width) { this.$lines = text.split('\n'); return; } let line = ''; let ctx = this.$context; let lines = this.$lines = []; let units = this.$divideUnits(); let width = this.$width * Layer.pixelRatio; this.$updateContext(); for (let unit of units) { if (unit === '\n') { lines.push(line); line = ''; continue; } let lineWidth = ctx.measureText(line + unit).width; if (lineWidth <= width) { line += unit; } else { line && lines.push(line); line = unit; } } lines.push(line); } protected $resizeCanvas(): void { let width = this.$width; let height = this.$height; this.$divideLines(); if (this.$autoFitSize && (width || height)) { let minFontSize = this.$minFontSize || 1; this.$explicitSize = this.$fontSize; while (this.$explicitSize > minFontSize) { let bounds = this.$getContentBounds(); if ((width && bounds.width > width) || (height && bounds.height > height)) { --this.$explicitSize; } else { break; } bounds.release(); } } super.$resizeCanvas(); } protected $getContentBounds(): Rectangle { let ctx = this.$context; let bounds = super.$getContentBounds(); let lines = this.$lines; let lineHeight = this.$lineHeight; let pixelRatio = Layer.pixelRatio; let fontSize = this.$explicitSize || this.$fontSize; this.$updateContext(); for (let line of lines) { bounds.width = Math.max(bounds.width, ctx.measureText(line).width / pixelRatio); } bounds.height = Math.max(bounds.height, fontSize * lineHeight * lines.length); return bounds; } protected $render(): number { if (!this.$dirty) { return 0; } let x = 0; let y = 0; let width = this.width; let height = this.height; let anchorX = this.anchorX; let anchorY = this.anchorY; let ctx = this.$context; let lines = this.$lines; let color = this.$color; let textAlign = this.$textAlign; let verticalAlign = this.$verticalAlign; let lineHeight = this.$lineHeight; let strokeSize = this.$strokeSize; let strokeColor = this.$strokeColor; let pixelRatio = Layer.pixelRatio; let fontSize = this.$explicitSize || this.$fontSize; let drawCalls = super.$render(); this.$updateContext(); if (textAlign === 'center') { x = width * pixelRatio / 2 - anchorX * pixelRatio; } else if (textAlign === 'right') { x = width * pixelRatio - anchorX * pixelRatio; } else { x = -anchorX * pixelRatio; } if (verticalAlign === 'middle') { y = (height - fontSize * lineHeight * (lines.length - 1) - fontSize) * pixelRatio / 2 - anchorY * pixelRatio; } else if (verticalAlign === 'bottom') { y = (height - fontSize * lineHeight * (lines.length - 1) - fontSize) * pixelRatio - anchorY * pixelRatio; } else { y = -anchorY * pixelRatio; } for (let line of lines) { if (color) { ctx.fillText(line, x, y); } if (strokeSize && strokeColor) { ctx.strokeText(line, x, y); } y += fontSize * lineHeight * pixelRatio; } return drawCalls; } } export type FontStyle = 'normal' | 'italic' | 'oblique'; export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; export type TextAlign = 'left' | 'right' | 'center'; export type VerticalAlign = 'top' | 'middle' | 'bottom'; export interface TextOption { color?: string; fontSize?: number; fontWeight?: FontWeight; fontStyle?: FontStyle; textAlign?: TextAlign; verticalAlign?: VerticalAlign; lineHeight?: number; strokeSize?: number; strokeColor?: string; fontFamily?: string; multiline?: boolean; breakWord?: boolean; autoFitSize?: boolean; minFontSize?: number; }