UNPKG

tav-media

Version:

Cross platform media editing framework

128 lines (127 loc) 5.1 kB
import { measureText } from '../utils/measure-text'; import { defaultFontNames, getFontFamilies } from '../utils/font-family'; import { getCanvas2D } from '../utils/canvas'; import { NativeImage } from './native-image'; export class ScalerContext { constructor(fontName, fontStyle, size, fauxBold = false, fauxItalic = false) { this.fontBoundingBoxMap = []; this.fontName = fontName; this.fontStyle = fontStyle; this.size = size; this.fauxBold = fauxBold; this.fauxItalic = fauxItalic; this.loadCanvas(); } static setCanvas(canvas) { ScalerContext.canvas = canvas; } static setContext(context) { ScalerContext.context = context; } static isEmoji(text) { const emojiRegExp = /\p{Extended_Pictographic}|[#*0-9]\uFE0F?\u20E3|[\uD83C\uDDE6-\uD83C\uDDFF]/u; return emojiRegExp.test(text); } fontString() { const attributes = []; // css font-style if (this.fauxItalic) { attributes.push('italic'); } // css font-weight if (this.fauxBold) { attributes.push('bold'); } // css font-size attributes.push(`${this.size}px`); // css font-family const fallbackFontNames = defaultFontNames.concat(); fallbackFontNames.unshift(...getFontFamilies(this.fontName, this.fontStyle)); attributes.push(`${fallbackFontNames.join(',')}`); return attributes.join(' '); } getTextAdvance(text) { const { context } = ScalerContext; context.font = this.fontString(); return context.measureText(text).width; } getTextBounds(text) { const { context } = ScalerContext; context.font = this.fontString(); const metrics = this.measureText(context, text); const bounds = { left: Math.floor(-metrics.actualBoundingBoxLeft), top: Math.floor(-metrics.actualBoundingBoxAscent), right: Math.ceil(metrics.actualBoundingBoxRight), bottom: Math.ceil(metrics.actualBoundingBoxDescent), }; return bounds; } generateFontMetrics() { const { context } = ScalerContext; context.font = this.fontString(); const metrics = this.measureText(context, '中'); const capHeight = metrics.actualBoundingBoxAscent; const xMetrics = this.measureText(context, 'x'); const xHeight = xMetrics.actualBoundingBoxAscent; return { ascent: -metrics.fontBoundingBoxAscent, descent: metrics.fontBoundingBoxDescent, xHeight, capHeight, }; } generateImage(text, bounds) { const canvas = getCanvas2D(); canvas.width = bounds.right - bounds.left; canvas.height = bounds.bottom - bounds.top; const context = canvas.getContext('2d'); context.font = this.fontString(); context.fillText(text, -bounds.left, -bounds.top); return new NativeImage(canvas, true); } loadCanvas() { if (!ScalerContext.canvas) { ScalerContext.setCanvas(getCanvas2D()); ScalerContext.canvas.width = 10; ScalerContext.canvas.height = 10; ScalerContext.setContext(ScalerContext.canvas.getContext('2d')); } } measureText(ctx, text) { const metrics = ctx.measureText(text); if (metrics === null || metrics === void 0 ? void 0 : metrics.actualBoundingBoxAscent) return metrics; ctx.canvas.width = this.size * 1.5; ctx.canvas.height = this.size * 1.5; const pos = [0, this.size]; ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.font = this.fontString(); ctx.fillText(text, pos[0], pos[1]); const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); const { left, top, right, bottom } = measureText(imageData); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); let fontMeasure; const fontBoundingBox = this.fontBoundingBoxMap.find((item) => item.key === this.fontName); if (fontBoundingBox) { fontMeasure = fontBoundingBox.value; } else { ctx.font = this.fontString(); ctx.fillText('测', pos[0], pos[1]); const fontImageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); fontMeasure = measureText(fontImageData); this.fontBoundingBoxMap.push({ key: this.fontName, value: fontMeasure }); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); } return { actualBoundingBoxAscent: pos[1] - top, actualBoundingBoxRight: right - pos[0], actualBoundingBoxDescent: bottom - pos[1], actualBoundingBoxLeft: pos[0] - left, fontBoundingBoxAscent: fontMeasure.bottom - fontMeasure.top, fontBoundingBoxDescent: 0, width: fontMeasure.right - fontMeasure.left, }; } }