UNPKG

autokerning

Version:

autokerning computes suggested kerning values for glyph pairs from TrueType/OpenType fonts by rendering glyph bitmaps, applying a small Gaussian blur, and measuring pixel overlap across horizontal offsets. It can be used programmatically (as an imported E

68 lines (67 loc) 2.67 kB
import ndarray from "ndarray"; import config from "./config.js"; import { logger } from "./log.js"; /** Gaussian blur using a separable kernel */ export function gaussianBlur(input, // Use factor from config so it is centrally adjustable sigma = config.BLUR_SIGMA_FACTOR * input.shape[1], // Optional: override sigma with fixed kernel width (for adaptive calibration) kernelWidth) { // If kernelWidth is provided, use it to calculate sigma (like Python's adaptive calibration) if (kernelWidth !== undefined) { sigma = kernelWidth / 4; // Matches Python: c = KERNEL_WIDTH / 4 } logger.debug(`[GAUSSIAN] enter: kernelWidth=${kernelWidth ?? "(auto)"}, sigma=${sigma.toFixed(2)}`); const width = input.shape[1]; const height = input.shape[0]; const radius = Math.round(sigma); // Cache kernel arrays by radius to avoid recomputing for same sigma const kernelCache = gaussianBlur._kernelCache || {}; gaussianBlur._kernelCache = kernelCache; let kernel = kernelCache[radius]; if (!kernel) { const klen = radius * 2 + 1; const tmp = new Float64Array(klen); let s = 0; for (let i = 0; i < klen; i++) { const x = i - radius; const v = Math.exp(-(x * x) / (2 * sigma * sigma)); tmp[i] = v; s += v; } for (let i = 0; i < klen; i++) tmp[i] /= s; kernel = tmp; kernelCache[radius] = kernel; } const inData = input.data; const tempData = new Float32Array(width * height); const outData = new Float32Array(width * height); // horizontal pass (direct indexing) for (let y = 0; y < height; y++) { const rowOff = y * width; for (let x = 0; x < width; x++) { let acc = 0; for (let i = -radius; i <= radius; i++) { const xi = Math.min(width - 1, Math.max(0, x + i)); acc += inData[rowOff + xi] * kernel[i + radius]; } tempData[rowOff + x] = acc; } } // vertical pass for (let y = 0; y < height; y++) { const rowOff = y * width; for (let x = 0; x < width; x++) { let acc = 0; for (let i = -radius; i <= radius; i++) { const yi = Math.min(height - 1, Math.max(0, y + i)); acc += tempData[yi * width + x] * kernel[i + radius]; } outData[rowOff + x] = acc; } } const out = ndarray(outData, [height, width]); logger.debug(`[GAUSSIAN] exit: kernelWidth=${kernelWidth ?? "(auto)"}, radius=${radius}, width=${width}, height=${height}`); return out; }