UNPKG

modern-text

Version:

Measure and render text in a way that describes the DOM.

564 lines (561 loc) 18.6 kB
import { Vector2 } from 'modern-path2d'; import { d as defineDeformation } from '../shared/modern-text.C0w4dLp7.mjs'; import { H as HeartCurve, R as RectangularCurve, E as EllipseCurve, C as CircleCurve, P as PolygonCurve } from '../shared/modern-text.fT17R5HY.mjs'; import 'modern-idoc'; const BY_WORD = [50, 5, 0, 100]; const PI_DIV_3 = Math.PI / 3; function random(value) { const v = Math.sin(value) * 43758.5453123; return v - Math.trunc(v); } function translatePoint(point, arg) { return [point.x + arg.translateVec.x, point.y + arg.translateVec.y]; } function scalePoint(point, arg) { const { centerX, centerY, t, center } = arg; return [(point.x - center.x) * t + centerX, (point.y - center.y) * t + centerY]; } function rotatePoint(point, arg) { const { cos, sin, center } = arg; const dx = point.x - center.x; const dy = point.y - center.y; return [center.x + dx * cos - dy * sin, center.y + dx * sin + dy * cos]; } const deformationPresets = { // ── Bend(整体弯曲)─────────────────────────────────────────────────── "bend": { engine: "bend", vertical: false, transform: (ctx) => { const { lineHeight, size, center, centerDist, centerDistAngle, width, height } = ctx; return (point) => { const p = lineHeight + Math.sign(size) * height / 2 - (point.y - center.y); const deg = centerDistAngle + (point.x - centerDist.x) / width * 2 * size; return [centerDist.x + p * Math.cos(deg), centerDist.y + p * Math.sin(deg)]; }; } }, "bend-vertical": { engine: "bend", vertical: true, transform: (ctx) => { const { lineHeight, size, center, centerDist, centerDistAngle, width, height } = ctx; return (point) => { const p = lineHeight + Math.sign(size) * width / 2 + (point.x - center.x); const deg = centerDistAngle + (point.y - centerDist.y) / height * 2 * size; return [centerDist.x + p * Math.cos(deg), centerDist.y + p * Math.sin(deg)]; }; } }, "arch-curve": { engine: "bend", vertical: "auto", transform: (ctx) => { const { lineHeight, size, center, centerDist, centerDistAngle, width, height, isHorizontal } = ctx; return isHorizontal ? (point) => { const p = centerDistAngle + (point.x - centerDist.x) / width * 2 * size; const x = centerDist.x + lineHeight * Math.cos(p); const y = centerDist.y + lineHeight * Math.sin(p) - Math.sign(size) * height / 2 + (point.y - center.y); return [x, y]; } : (point) => { const deg = centerDistAngle + (point.y - centerDist.y) / height * 2 * size; const x = centerDist.x + lineHeight * Math.cos(deg) + Math.sign(size) * width / 2 + (point.x - center.x); const y = centerDist.y + lineHeight * Math.sin(deg); return [x, y]; }; } }, // ── FFD(自由变形)───────────────────────────────────────────────────── "concave-curve": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, lineToQuad: true, build: (p, { a, baseHeight, adjust }) => { const k = baseHeight * a; adjust(p[0], 0, -k); adjust(p[2], 0, 2 * k); adjust(p[4], 0, -k); adjust(p[1], 0, k); adjust(p[3], 0, -2 * k); adjust(p[5], 0, k); } }, "upper-arch-curve": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, lineToQuad: true, build: (p, { a, baseWidth, baseHeight, adjust }) => { const k = (a > 0 ? 0.5 * baseWidth : 2 * baseHeight) * a; adjust(p[2], 0, -k); } }, "lower-arch-curve": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, lineToQuad: true, build: (p, { a, baseWidth, baseHeight, adjust }) => { const k = (a > 0 ? 0.5 * baseWidth : 2 * baseHeight) * a; adjust(p[3], 0, +k); } }, "bulb-curve": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, lineToQuad: true, build: (p, { a, baseHeight, adjust }) => { const k = -baseHeight * a; adjust(p[0], 0, -0.5 * k); adjust(p[2], 0, 2 * k); adjust(p[4], 0, -0.5 * k); adjust(p[1], 0, 0.5 * k); adjust(p[3], 0, -2 * k); adjust(p[5], 0, 0.5 * k); } }, "skew": { engine: "ffd", bezier: false, hBlocks: 0, vBlocks: 0, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = baseHeight * Math.tan(a * PI_DIV_3); const k2 = baseWidth * Math.tan(b * PI_DIV_3); adjust(p[0], k1, 0); adjust(p[2], k1, -k2); adjust(p[3], 0, -k2); } }, "flag-curve": { engine: "ffd", bezier: true, hBlocks: 3, vBlocks: 0, breakLine: true, lineToQuad: true, build: (p, { a, baseWidth, adjust }) => { const k = 0.5 * baseWidth * a; adjust(p[2], 0, -k); adjust(p[3], 0, -k); adjust(p[6], 0, k); adjust(p[7], 0, k); } }, "trapezoid": { engine: "ffd", bezier: true, hBlocks: 0, vBlocks: 1, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.25 * baseWidth * a; const k2 = 0.5 * baseHeight * b; const t = 0.5 * (a + 1); adjust(p[0], +k1, -k2); adjust(p[2], -k1, +k2); adjust(p[3], -k1, -k2); adjust(p[5], +k1, +k2); p[1] = Vector2.lerp(p[2], p[0], t); p[4] = Vector2.lerp(p[5], p[3], t); } }, "lower-trapezoid": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = baseWidth * Math.tan(a * PI_DIV_3); const k2 = baseHeight * b; const t = 0.5 * (a + 1); adjust(p[0], 0, -k2); adjust(p[4], 0, -k2); adjust(p[a > 0 ? 1 : 5], 0, +Math.abs(k1)); p[2] = Vector2.lerp(p[0], p[4], t); p[3] = Vector2.lerp(p[1], p[5], t); } }, "top-trapezoid": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = baseWidth * Math.tan(a * PI_DIV_3); const k2 = baseHeight * b; const t = 0.5 * (a + 1); adjust(p[1], 0, +k2); adjust(p[5], 0, +k2); adjust(p[a > 0 ? 4 : 0], 0, -Math.abs(k1)); p[2] = Vector2.lerp(p[4], p[0], t); p[3] = Vector2.lerp(p[5], p[1], t); } }, "horizontal-trapezoid": { engine: "ffd", bezier: true, hBlocks: 1, vBlocks: 0, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * PI_DIV_3); const k2 = 0.5 * baseHeight * b; const t = 0.5 + a * 0.5; adjust(p[a > 0 ? 0 : 4], 0, -Math.sign(a) * k1); adjust(p[a > 0 ? 1 : 5], 0, +Math.sign(a) * k1); adjust(p[0], 0, -k2); adjust(p[1], 0, +k2); adjust(p[4], 0, -k2); adjust(p[5], 0, +k2); p[2] = Vector2.lerp(p[0], p[4], t); p[3] = Vector2.lerp(p[1], p[5], t); } }, "bevel": { engine: "ffd", bezier: false, hBlocks: 1, vBlocks: 0, breakLine: true, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * PI_DIV_3); const k2 = 0.5 * baseHeight * b; adjust(p[0], 0, -k2); adjust(p[2], 0, -k1 - k2); adjust(p[4], 0, -k2); adjust(p[1], 0, +k2); adjust(p[3], 0, -k1 + k2); adjust(p[5], 0, +k2); } }, "upper-roof": { engine: "ffd", bezier: false, hBlocks: 1, vBlocks: 0, breakLine: true, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * PI_DIV_3); const k2 = baseHeight * b; adjust(p[0], 0, +a * baseHeight - k2); adjust(p[2], 0, -k1 - k2); adjust(p[4], 0, +a * baseHeight - k2); } }, "lower-roof": { engine: "ffd", bezier: false, hBlocks: 1, vBlocks: 0, breakLine: true, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * PI_DIV_3); const k2 = baseHeight * b; adjust(p[1], 0, -a * baseHeight + k2); adjust(p[3], 0, +k1 + k2); adjust(p[5], 0, -a * baseHeight + k2); } }, "angled-projection": { engine: "ffd", bezier: false, hBlocks: 1, vBlocks: 0, breakLine: true, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * 0.5 * PI_DIV_3); const k2 = 0.5 * baseHeight * b; adjust(p[0], 0, 0.5 * k1 - k2); adjust(p[2], 0, -2 * k1 - k2); adjust(p[4], 0, 0.5 * k1 - k2); adjust(p[1], 0, -0.5 * k1 + k2); adjust(p[3], 0, 2 * k1 + k2); adjust(p[5], 0, -0.5 * k1 + k2); } }, "folded-corner": { engine: "ffd", bezier: false, hBlocks: 1, vBlocks: 0, breakLine: true, build: (p, { a, b, baseWidth, baseHeight, adjust }) => { const k1 = 0.5 * baseWidth * Math.tan(a * 0.5 * PI_DIV_3); const k2 = 0.5 * baseHeight * b; adjust(p[0], 0, -k1 - k2); adjust(p[2], 0, 0.5 * k1 - k2); adjust(p[4], 0, -k1 - k2); adjust(p[1], 0, +k1 + k2); adjust(p[3], 0, -0.5 * k1 + k2); adjust(p[5], 0, +k1 + k2); } }, "lateral-stretching": { engine: "ffd", bezier: false, hBlocks: 0, vBlocks: 0, build: (p, { a, baseWidth, adjust }) => { const k = 0.5 * baseWidth * a; adjust(p[0], -k, 0); adjust(p[2], k, 0); adjust(p[1], -k, 0); adjust(p[3], k, 0); } }, "vertical-stretching": { engine: "ffd", bezier: false, hBlocks: 0, vBlocks: 0, build: (p, { a, baseHeight, adjust }) => { const k = 0.5 * baseHeight * a; adjust(p[0], 0, -k); adjust(p[2], 0, -k); adjust(p[1], 0, +k); adjust(p[3], 0, +k); } }, // ── 逐字 · 位移/旋转(offset)────────────────────────────────────────── "patchwork-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: translatePoint, perChar: (ctx, { character }) => { const { index, fontSize } = character; const n = 0.5 * (-1) ** (index + 1) * ctx.intensities[0] * fontSize; return { translateVec: ctx.isHorizontal ? new Vector2(0, -n) : new Vector2(n, 0) }; } }, "step-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: translatePoint, perChar: (ctx, { character }) => { const { left, top } = ctx.boundingBox; const { center } = character; const translateVec = ctx.isHorizontal ? new Vector2(0, -((center.x - left) * ctx.intensities[0])) : new Vector2((center.y - top) * ctx.intensities[0], 0); return { translateVec }; } }, "arch2-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: translatePoint, perChar: (ctx, { character }) => { const { left, top } = ctx.boundingBox; const i = ctx.intensities[0]; const { center } = character; let o; let translateVec; if (ctx.isHorizontal) { o = (center.x - left) / ctx.baseWidth; translateVec = new Vector2(0, -(o * (1 - o) * i * ctx.baseWidth)); } else { o = (center.y - top) / ctx.baseWidth; translateVec = new Vector2(o * (1 - o) * i * ctx.baseWidth, 0); } return { translateVec }; } }, "wave-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: translatePoint, perChar: (ctx, { character }) => { const { width, height, left, top } = ctx.boundingBox; const i = ctx.intensities[0]; const { center } = character; let translateVec; if (ctx.isHorizontal) { const l = (center.x - left) / width * 2 * Math.PI; translateVec = new Vector2(0, -(0.25 * Math.sin(l) * i * width)); } else { const l = (center.y - top) / height * 2 * Math.PI; translateVec = new Vector2(0.25 * Math.sin(l) * i * height, 0); } return { translateVec }; } }, "step-far-and-near-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: scalePoint, perChar: (ctx, { character }) => { const { left, top, width, height } = ctx.boundingBox; const { center } = character; const s = ctx.intensities[0]; let l; let centerX; let centerY; if (ctx.isHorizontal) { l = (center.x - left) / width; centerX = (center.x - left) * (s * l + 1 - s) + left; centerY = center.y; } else { l = (center.y - top) / height; centerX = center.x; centerY = (center.y - top) * (s * l + 1 - s) + top; } const t = 2 * (l - 0.5) * s + 1; return { centerX, centerY, t, center }; } }, "arch-far-and-near-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: scalePoint, perChar: (ctx, { character }) => { const { left, top, width, height } = ctx.boundingBox; const boxCenter = new Vector2(left + 0.5 * width, top + 0.5 * height); const n = ctx.intensities[0]; const { center } = character; let h; let centerX; let centerY; if (ctx.isHorizontal) { h = 2 * Math.abs(center.x - boxCenter.x) / width; centerX = (center.x - boxCenter.x) * (-n * h + 1 + n) + boxCenter.x; centerY = center.y; } else { h = 2 * Math.abs(center.y - boxCenter.y) / height; centerX = center.x; centerY = (center.y - boxCenter.y) * (-n * h + 1 + n) + boxCenter.y; } const t = -2 * (h - 0.5) * n + 1; return { centerX, centerY, t, center }; } }, "horizontal-rotate-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: rotatePoint, perChar: (ctx, { character }) => { const { center } = character; const s = 0.333 * ctx.intensities[0] * Math.PI; return { cos: Math.cos(s), sin: Math.sin(s), center }; } }, "arbitrary-offset-rotate-by-word": { engine: "offset", defaultIntensities: BY_WORD, point: (point, arg) => { const { cos, sin, diviate, center, isHorizontal } = arg; const dx = point.x - center.x; const dy = point.y - center.y; if (isHorizontal) { return [center.x + dx * cos - dy * sin, center.y + diviate + dx * sin + dy * cos]; } return [center.x + diviate + dx * cos - dy * sin, center.y + dx * sin + dy * cos]; }, perChar: (ctx, { characterIndex, character }) => { const { fontSize, center } = character; let o = random(characterIndex * Math.E); const e = 0.5 * ctx.intensities[1] * Math.PI * o; o = random(characterIndex * characterIndex * Math.E); const diviate = 0.5 * ctx.intensities[0] * o * fontSize; return { cos: Math.cos(e), sin: Math.sin(e), diviate, center, isHorizontal: ctx.isHorizontal }; } }, // ── 逐字 · 沿曲线排布(curve)────────────────────────────────────────── "horizontal-curved-rotate-by-word": { engine: "curve", followTangent: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => { const { width, height, left, top } = ctx.boundingBox; const center = new Vector2(left + width / 2, top + height / 2); const radius = ctx.lineHeight * 2 / Math.sin(ctx.intensities[0] * Math.PI * 0.5); const e = 0.5 * ctx.baseWidth / radius; const l = ctx.isHorizontal ? 0 : 0.5 * Math.PI; return new CircleCurve(center, radius, l - e, l + e); } }, "ellipse-by-word": { engine: "curve", followTangent: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => { const { width, height, left, top } = ctx.boundingBox; const n = ctx.intensities[3] || 0; let aX; let aY; let xRadius; let yRadius; if (ctx.isHorizontal) { aX = left + 0.5 * width; aY = top; xRadius = 0.5 * width * ctx.intensities[0]; yRadius = Math.sqrt(1 - n * n) * xRadius; } else { aX = left; aY = top + 0.5 * height; yRadius = 0.5 * height * ctx.intensities[0]; xRadius = Math.sqrt(1 - n * n) * yRadius; } const startAngle = (ctx.intensities[1] / 1.8 + (ctx.isHorizontal ? 1 : 1.5)) * Math.PI; const endAngle = startAngle + ctx.intensities[2] / 1.8 * Math.PI; return new EllipseCurve(new Vector2(aX, aY), xRadius, yRadius, 0, startAngle, endAngle, false); } }, "triangle-by-word": { engine: "curve", followTangent: true, expandAlongNormal: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => makePolygonCurve(ctx) }, "pentagon-by-word": { engine: "curve", followTangent: true, expandAlongNormal: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => makePolygonCurve(ctx) }, "rectangular-by-word": { engine: "curve", followTangent: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => { const { center, extent } = shapeBase(ctx); const e = ctx.intensities[1]; const l = ctx.intensities[2]; const c = l + ctx.intensities[3]; return new RectangularCurve(center, extent, e, l, c); } }, "heart-by-word": { engine: "curve", followTangent: true, defaultIntensities: BY_WORD, makeCurve: (ctx) => { const { center, extent } = shapeBase(ctx); const ratio = ctx.intensities[1]; const startNum = ratio + ctx.intensities[2]; return new HeartCurve(center, extent, ratio, startNum); } } }; function shapeBase(ctx) { const { width, height, left, top } = ctx.boundingBox; const i0 = ctx.intensities[0]; return ctx.isHorizontal ? { center: new Vector2(left + 0.5 * width, top), extent: 0.5 * i0 * width } : { center: new Vector2(left, top + 0.5 * height), extent: 0.5 * i0 * height }; } function makePolygonCurve(ctx) { const { center, extent } = shapeBase(ctx); const num = ctx.intensities[1] * 100; const startNum = ctx.intensities[2]; const endNum = startNum + ctx.intensities[3]; return new PolygonCurve(center, extent, num, startNum, endNum); } function registerDeformations() { for (const name in deformationPresets) { defineDeformation(name, deformationPresets[name]); } } export { deformationPresets, registerDeformations };