feat: add first-class text layers
This commit is contained in:
44
core/text-layer.ts
Normal file
44
core/text-layer.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { BaseLayer } from "./base-layer";
|
||||
import type { Size } from "./geometry";
|
||||
|
||||
export const builtInTextFonts = ["Arial", "Georgia", "Courier New", "Trebuchet MS"] as const;
|
||||
export type TextFontFamily = typeof builtInTextFonts[number];
|
||||
export type TextAlignment = "left" | "center" | "right";
|
||||
export type TextFontStyle = "normal" | "italic";
|
||||
export type TextFontWeight = 400 | 700;
|
||||
|
||||
export type TextStyle = {
|
||||
fontFamily: TextFontFamily;
|
||||
fontSize: number;
|
||||
fontWeight: TextFontWeight;
|
||||
fontStyle: TextFontStyle;
|
||||
color: string;
|
||||
alignment: TextAlignment;
|
||||
lineHeight: number;
|
||||
};
|
||||
|
||||
export type TextLayer = BaseLayer & {
|
||||
type: "text";
|
||||
content: string;
|
||||
style: TextStyle;
|
||||
};
|
||||
|
||||
/** Deterministic layout box shared by selection, renderer and export. */
|
||||
export function measureTextLayer(layer: Pick<TextLayer, "content" | "style">): Size {
|
||||
const lines = layer.content.split("\n");
|
||||
const weightFactor = layer.style.fontWeight === 700 ? 1.04 : 1;
|
||||
const italicFactor = layer.style.fontStyle === "italic" ? 1.03 : 1;
|
||||
const familyFactor = layer.style.fontFamily === "Courier New" ? 0.62 : layer.style.fontFamily === "Georgia" ? 0.56 : 0.54;
|
||||
const longest = Math.max(1, ...lines.map((line) => [...line].length));
|
||||
return {
|
||||
w: Math.max(1, longest * layer.style.fontSize * familyFactor * weightFactor * italicFactor),
|
||||
h: Math.max(1, lines.length * layer.style.fontSize * layer.style.lineHeight),
|
||||
};
|
||||
}
|
||||
|
||||
export function isValidTextStyle(style: TextStyle): boolean {
|
||||
return builtInTextFonts.includes(style.fontFamily) && Number.isFinite(style.fontSize) && style.fontSize >= 1 && style.fontSize <= 1000
|
||||
&& (style.fontWeight === 400 || style.fontWeight === 700) && (style.fontStyle === "normal" || style.fontStyle === "italic")
|
||||
&& /^#[0-9a-f]{6}$/i.test(style.color) && (style.alignment === "left" || style.alignment === "center" || style.alignment === "right")
|
||||
&& Number.isFinite(style.lineHeight) && style.lineHeight >= 0.5 && style.lineHeight <= 5;
|
||||
}
|
||||
Reference in New Issue
Block a user