157 lines
3.5 KiB
TypeScript
157 lines
3.5 KiB
TypeScript
|
|
import type { TextSettings } from '@/types/text';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render text on canvas with specified settings
|
||
|
|
*/
|
||
|
|
export function renderText(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
x: number,
|
||
|
|
y: number,
|
||
|
|
settings: TextSettings
|
||
|
|
): void {
|
||
|
|
if (!settings.text) return;
|
||
|
|
|
||
|
|
ctx.save();
|
||
|
|
|
||
|
|
// Build font string
|
||
|
|
const font = `${settings.fontStyle} ${settings.fontWeight} ${settings.fontSize}px ${settings.fontFamily}`;
|
||
|
|
ctx.font = font;
|
||
|
|
ctx.fillStyle = settings.color;
|
||
|
|
ctx.textAlign = settings.align;
|
||
|
|
ctx.textBaseline = settings.baseline;
|
||
|
|
|
||
|
|
// Handle multi-line text
|
||
|
|
const lines = settings.text.split('\n');
|
||
|
|
const lineHeightPx = settings.fontSize * settings.lineHeight;
|
||
|
|
|
||
|
|
lines.forEach((line, index) => {
|
||
|
|
const lineY = y + index * lineHeightPx;
|
||
|
|
|
||
|
|
// Apply letter spacing if needed
|
||
|
|
if (settings.letterSpacing !== 0) {
|
||
|
|
renderTextWithLetterSpacing(ctx, line, x, lineY, settings.letterSpacing);
|
||
|
|
} else {
|
||
|
|
ctx.fillText(line, x, lineY);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render text with custom letter spacing
|
||
|
|
*/
|
||
|
|
function renderTextWithLetterSpacing(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
text: string,
|
||
|
|
x: number,
|
||
|
|
y: number,
|
||
|
|
letterSpacing: number
|
||
|
|
): void {
|
||
|
|
const chars = text.split('');
|
||
|
|
let currentX = x;
|
||
|
|
|
||
|
|
// Adjust starting position based on text align
|
||
|
|
if (ctx.textAlign === 'center') {
|
||
|
|
const totalWidth = measureTextWidth(ctx, text, letterSpacing);
|
||
|
|
currentX = x - totalWidth / 2;
|
||
|
|
} else if (ctx.textAlign === 'right') {
|
||
|
|
const totalWidth = measureTextWidth(ctx, text, letterSpacing);
|
||
|
|
currentX = x - totalWidth;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Draw each character with spacing
|
||
|
|
chars.forEach((char) => {
|
||
|
|
ctx.fillText(char, currentX, y);
|
||
|
|
currentX += ctx.measureText(char).width + letterSpacing;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Measure text width including letter spacing
|
||
|
|
*/
|
||
|
|
function measureTextWidth(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
text: string,
|
||
|
|
letterSpacing: number
|
||
|
|
): number {
|
||
|
|
const chars = text.split('');
|
||
|
|
let width = 0;
|
||
|
|
|
||
|
|
chars.forEach((char) => {
|
||
|
|
width += ctx.measureText(char).width + letterSpacing;
|
||
|
|
});
|
||
|
|
|
||
|
|
return width - letterSpacing; // Remove last letter spacing
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Common web-safe fonts
|
||
|
|
*/
|
||
|
|
export const WEB_SAFE_FONTS = [
|
||
|
|
'Arial',
|
||
|
|
'Arial Black',
|
||
|
|
'Comic Sans MS',
|
||
|
|
'Courier New',
|
||
|
|
'Georgia',
|
||
|
|
'Impact',
|
||
|
|
'Lucida Console',
|
||
|
|
'Lucida Sans Unicode',
|
||
|
|
'Palatino Linotype',
|
||
|
|
'Tahoma',
|
||
|
|
'Times New Roman',
|
||
|
|
'Trebuchet MS',
|
||
|
|
'Verdana',
|
||
|
|
] as const;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Popular Google Fonts (will be loaded dynamically)
|
||
|
|
*/
|
||
|
|
export const GOOGLE_FONTS = [
|
||
|
|
'Roboto',
|
||
|
|
'Open Sans',
|
||
|
|
'Lato',
|
||
|
|
'Montserrat',
|
||
|
|
'Oswald',
|
||
|
|
'Source Sans Pro',
|
||
|
|
'Raleway',
|
||
|
|
'PT Sans',
|
||
|
|
'Merriweather',
|
||
|
|
'Playfair Display',
|
||
|
|
'Ubuntu',
|
||
|
|
'Noto Sans',
|
||
|
|
'Poppins',
|
||
|
|
'Inter',
|
||
|
|
] as const;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load a Google Font dynamically
|
||
|
|
*/
|
||
|
|
export function loadGoogleFont(fontFamily: string): Promise<void> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
// Check if font is already loaded
|
||
|
|
if (document.fonts.check(`16px "${fontFamily}"`)) {
|
||
|
|
resolve();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create link element to load font
|
||
|
|
const link = document.createElement('link');
|
||
|
|
link.rel = 'stylesheet';
|
||
|
|
link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}:wght@100;300;400;500;700;900&display=swap`;
|
||
|
|
|
||
|
|
link.onload = () => {
|
||
|
|
// Wait for font to be ready
|
||
|
|
document.fonts.load(`16px "${fontFamily}"`).then(() => {
|
||
|
|
resolve();
|
||
|
|
}).catch(reject);
|
||
|
|
};
|
||
|
|
|
||
|
|
link.onerror = () => {
|
||
|
|
reject(new Error(`Failed to load font: ${fontFamily}`));
|
||
|
|
};
|
||
|
|
|
||
|
|
document.head.appendChild(link);
|
||
|
|
});
|
||
|
|
}
|