UNPKG

export-kerning

Version:
130 lines (112 loc) 4.06 kB
// Pure functions for extracting and formatting font kerning pairs // Works in both Node.js and browser environments export function parseUnicodeRanges(ranges) { const chars = new Set(); ranges.split(',').forEach(range => { range = range.trim(); if (range.includes('-')) { // Handle range like U+0000-007F const [start, end] = range.replace('U+', '').split('-').map(hex => parseInt(hex, 16)); for (let i = start; i <= end; i++) { chars.add(String.fromCharCode(i)); } } else { // Handle single character like U+0153 const code = parseInt(range.replace('U+', ''), 16); chars.add(String.fromCharCode(code)); } }); return Array.from(chars); } export function extractKerningPairs(font, charsOrRanges) { const pairs = {}; const chars = typeof charsOrRanges === 'string' ? parseUnicodeRanges(charsOrRanges) : charsOrRanges; for (let i = 0; i < chars.length; i++) { for (let j = 0; j < chars.length; j++) { const left = font.charToGlyph(chars[i]); const right = font.charToGlyph(chars[j]); const kern = font.getKerningValue(left, right); if (kern !== 0) { pairs[chars[i] + chars[j]] = kern; } } } return pairs; } export function extractUsedKerningPairs(text, font) { const pairs = {}; const words = text.split(/\s+/); for (const word of words) { for (let i = 0; i < word.length - 1; i++) { const left = word[i]; const right = word[i + 1]; const leftGlyph = font.charToGlyph(left); const rightGlyph = font.charToGlyph(right); const kern = font.getKerningValue(leftGlyph, rightGlyph); if (kern !== 0) { pairs[left + right] = kern; } } } return pairs; } export function getSupportedChars(font) { const cmap = font.tables.cmap.glyphIndexMap; return Object.keys(cmap).map(code => String.fromCharCode(code)); } export function convertToSVGStyleOptimizedFormat(kerningPairs) { // Step 1: Group by value const valueGroups = {}; for (const [pair, value] of Object.entries(kerningPairs)) { if (!valueGroups[value]) valueGroups[value] = []; valueGroups[value].push(pair); } // Step 2: For each value, group by left and right glyphs const result = []; for (const [value, pairs] of Object.entries(valueGroups)) { // Map: left glyph -> set of right glyphs const leftToRights = {}; for (const pair of pairs) { const left = pair[0]; const right = pair[1]; if (!leftToRights[left]) leftToRights[left] = new Set(); leftToRights[left].add(right); } // Now, try to group lefts that share the same set of rights // Map: stringified rights set -> set of lefts const rightsToLefts = {}; for (const [left, rightsSet] of Object.entries(leftToRights)) { const rightsArr = Array.from(rightsSet).sort(); const rightsKey = rightsArr.join(','); if (!rightsToLefts[rightsKey]) rightsToLefts[rightsKey] = { lefts: [], rights: rightsArr }; rightsToLefts[rightsKey].lefts.push(left); } // Add to result for (const group of Object.values(rightsToLefts)) { result.push({ left: group.lefts.sort(), right: group.rights, value: Number(value) }); } } return result; } // High-level export function that combines all the functionality export function exportKerning(font, options = {}) { const { ranges, text } = options; // Get supported chars or use ranges const supportedChars = getSupportedChars(font); const charsToUse = ranges || supportedChars; // Extract all kerning pairs const allKerningPairs = extractKerningPairs(font, charsToUse); const result = { unitsPerEm: font.unitsPerEm, kerningPairs: convertToSVGStyleOptimizedFormat(allKerningPairs) }; // If text is provided, also generate subset if (text) { const usedKerningPairs = extractUsedKerningPairs(text, font); result.subset = { unitsPerEm: font.unitsPerEm, kerningPairs: convertToSVGStyleOptimizedFormat(usedKerningPairs) }; } return result; }