tav-media
Version:
Cross platform media editing framework
128 lines (127 loc) • 5.1 kB
JavaScript
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,
};
}
}