cz-typography
Version:
Universal Czech typography fixer for JavaScript, React, Next.js, and any SSR framework. Non-breaking spaces after one-letter prepositions, units, dates, ordinals and more.
337 lines (324 loc) • 9.61 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
// src/rules/dates.js
var FULL_DATE_REGEX = /(\d{1,2})\.[ \t]+(\d{1,2})\.[ \t]+(\d{2,4})\b/g;
var DAY_MONTH_REGEX = /(\d{1,2})\.[ \t]+(\d{1,2})\.(?=[^\p{L}\p{N}]|$)/gu;
function applyDates(text) {
return text.replace(FULL_DATE_REGEX, "$1.\xA0$2.\xA0$3").replace(DAY_MONTH_REGEX, "$1.\xA0$2.");
}
// src/rules/initials.js
var INITIAL_REGEX = new RegExp("(\\b\\p{Lu})\\.[ \\t]+(?=\\p{Lu})", "gu");
function applyInitials(text) {
let previous;
let current = text;
do {
previous = current;
current = current.replace(INITIAL_REGEX, "$1.\xA0");
} while (current !== previous);
return current;
}
// src/rules/ordinals.js
var ORDINAL_REGEX = new RegExp("(\\d{1,2})\\.[ \\t]+(?=\\p{Ll})", "gu");
function applyOrdinals(text) {
return text.replace(ORDINAL_REGEX, "$1.\xA0");
}
// src/data/prepositions.js
var PREPOSITIONS = Object.freeze([
"a",
"i",
"k",
"o",
"s",
"u",
"v",
"z"
]);
// src/rules/prepositions.js
var PREPOSITION_REGEX = new RegExp(
`(?<=^|[\\s(\\["'\xA0\u2018\u201C\xBB\u2014\u2013-])(${PREPOSITIONS.join("|")})[ \\t]+(?=\\S)`,
"giu"
);
function applyPrepositions(text) {
return text.replace(PREPOSITION_REGEX, "$1\xA0");
}
// src/rules/roman.js
var ROMAN_NUMERAL = "(?:(?=[IVXLCDM])M{0,3}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3}))";
var ROMAN_REGEX = new RegExp(
`(\\b\\p{Lu}\\p{L}+)[ \\t]+(?=${ROMAN_NUMERAL}\\.(?=[^\\p{L}\\p{N}]|$))`,
"gu"
);
function applyRoman(text) {
return text.replace(ROMAN_REGEX, "$1\xA0");
}
// src/rules/thousands.js
var THOUSANDS_REGEX = /(\d)[ \t](\d{3})(?=\D|$)/g;
function applyThousands(text) {
let previous;
let current = text;
do {
previous = current;
current = current.replace(THOUSANDS_REGEX, "$1\xA0$2");
} while (current !== previous);
return current;
}
// src/data/units.js
var SAFE_UNITS = Object.freeze(
[
"km/h",
"m/s",
"kWh",
"mAh",
"mbar",
"kbps",
"Mbps",
"Gbit",
"kPa",
"bar",
"kg",
"mg",
"cm",
"mm",
"\xB5m",
"nm",
"km",
"ml",
"cl",
"dl",
"kJ",
"Nm",
"Wb",
"lm",
"lx",
"cd",
"mol",
"min",
"ms",
"ns",
"Hz",
"Pa",
"kW",
"MW",
"Ohm",
"\xB0C",
"\xB0F",
"\xB0",
"\u2030",
"%",
"px",
"pt",
"dpi",
"bpm",
"EUR",
"USD",
"CZK",
"K\u010D",
"mg/m\xB3",
"str",
"osob",
"ks",
"MJ",
"TB",
"GB",
"MB",
"kB",
"bit",
"GBit",
"mol",
"\u03A9",
"\u20AC",
"$",
"\xA3"
].sort((a, b) => b.length - a.length)
);
var STRICT_UNITS = Object.freeze(
["m", "s", "l", "t", "g", "V", "A", "W", "J", "K", "B"].sort(
(a, b) => b.length - a.length
)
);
// src/rules/units.js
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
var SAFE_PATTERN = SAFE_UNITS.map(escapeRegex).join("|");
var STRICT_PATTERN = STRICT_UNITS.map(escapeRegex).join("|");
var SAFE_UNIT_REGEX = new RegExp(
`(\\d)[ \\t]+(?=(?:${SAFE_PATTERN})(?![\\p{L}\\p{N}]))`,
"gu"
);
var STRICT_UNIT_REGEX = new RegExp(
`(\\d)[ \\t]+(?=(?:${STRICT_PATTERN})(?:[.,;:!?)\\]/\\u00A0]|$))`,
"gu"
);
function applyUnits(text) {
return text.replace(SAFE_UNIT_REGEX, "$1\xA0").replace(STRICT_UNIT_REGEX, "$1\xA0");
}
// src/fixCzech.js
var DEFAULT_OPTIONS = Object.freeze({
prepositions: true,
units: true,
initials: true,
dates: true,
ordinals: true,
roman: true,
thousands: true
});
function fixCzech(text, options) {
if (typeof text !== "string" || text.length === 0) {
return text;
}
const opts = options ? { ...DEFAULT_OPTIONS, ...options } : DEFAULT_OPTIONS;
let result = text;
if (opts.thousands) result = applyThousands(result);
if (opts.dates) result = applyDates(result);
if (opts.ordinals) result = applyOrdinals(result);
if (opts.units) result = applyUnits(result);
if (opts.initials) result = applyInitials(result);
if (opts.roman) result = applyRoman(result);
if (opts.prepositions) result = applyPrepositions(result);
return result;
}
var fixCzech_default = fixCzech;
// src/fixCzechHtml.js
var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style", "pre", "code", "textarea"]);
var TOKEN_REGEX = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<![A-Za-z][^>]*>|<\?[\s\S]*?\?>|<\/?([A-Za-z][A-Za-z0-9-]*)\b[^>]*>|[^<]+/g;
var SENTINEL = "";
function isTextToken(match) {
return match.charCodeAt(0) !== 60;
}
function isTagToken(match) {
return match.charCodeAt(0) === 60 && !match.startsWith("<!--") && !match.startsWith("<![") && !match.startsWith("<!") && !match.startsWith("<?");
}
function fixWithLookahead(text, options, nextChar) {
if (nextChar && /\S/.test(nextChar)) {
const padded = text + nextChar + SENTINEL;
const fixed = fixCzech(padded, options);
if (fixed.endsWith(nextChar + SENTINEL)) {
return fixed.slice(0, -(nextChar.length + SENTINEL.length));
}
return fixed.endsWith(SENTINEL) ? fixed.slice(0, -SENTINEL.length) : fixed;
}
return fixCzech(text, options);
}
function transformOne(match, tagName, rawTextStack, options, nextChar) {
if (!isTagToken(match) && match.charCodeAt(0) === 60) {
return match;
}
if (isTagToken(match)) {
const isClosing = match.charCodeAt(1) === 47;
const tag = tagName ? tagName.toLowerCase() : "";
if (isClosing) {
const top = rawTextStack[rawTextStack.length - 1];
if (top && top === tag) rawTextStack.pop();
} else if (RAW_TEXT_TAGS.has(tag) && !/\/\s*>$/.test(match)) {
rawTextStack.push(tag);
}
return match;
}
if (rawTextStack.length > 0) return match;
return fixWithLookahead(match, options, nextChar);
}
function tokenize(html) {
const tokens = [];
TOKEN_REGEX.lastIndex = 0;
let m;
while (m = TOKEN_REGEX.exec(html)) {
tokens.push({ match: m[0], tagName: m[1], end: m.index + m[0].length });
}
return tokens;
}
function lookaheadOf(tokens, index, fallback) {
const next = tokens[index + 1];
if (!next) return fallback || "";
if (isTextToken(next.match)) return next.match.charAt(0);
return "<";
}
function transformTokens(tokens, rawTextStack, options, trailingChar) {
let output = "";
for (let i = 0; i < tokens.length; i++) {
const { match, tagName } = tokens[i];
const nextChar = lookaheadOf(tokens, i, trailingChar);
output += transformOne(match, tagName, rawTextStack, options, nextChar);
}
return output;
}
function fixCzechHtml(html, options) {
if (typeof html !== "string" || html.length === 0) return html;
return transformTokens(tokenize(html), [], options, "");
}
var STREAM_TAIL = 64;
function createFixCzechProcessor(options) {
let buffer = "";
const rawTextStack = [];
function flushUpTo(cutPoint, trailingChar) {
if (cutPoint === 0) return "";
const processable = buffer.slice(0, cutPoint);
const tokens = tokenize(processable);
const output = transformTokens(tokens, rawTextStack, options, trailingChar);
buffer = buffer.slice(cutPoint);
return output;
}
return {
feed(chunk) {
if (typeof chunk !== "string") chunk = String(chunk);
buffer += chunk;
if (buffer.length === 0) return "";
let cutPoint = Math.max(0, buffer.length - STREAM_TAIL);
const lastLt = buffer.lastIndexOf("<");
const lastGt = buffer.lastIndexOf(">");
if (lastLt > lastGt) cutPoint = Math.min(cutPoint, lastLt);
while (cutPoint > 0 && !/\s/.test(buffer[cutPoint - 1])) cutPoint--;
while (cutPoint > 0 && /\s/.test(buffer[cutPoint - 1])) cutPoint--;
while (cutPoint > 0 && !/\s/.test(buffer[cutPoint - 1])) cutPoint--;
if (cutPoint === 0) return "";
const trailingChar = buffer.charAt(cutPoint);
return flushUpTo(cutPoint, trailingChar);
},
flush() {
if (buffer.length === 0) return "";
return flushUpTo(buffer.length, "");
}
};
}
function createFixCzechStream(options) {
if (typeof TransformStream === "undefined") {
throw new Error(
"createFixCzechStream requires the Web Streams API. Use Node 18+, Deno, Bun, or run in a browser/Edge runtime."
);
}
const decoder = new TextDecoder("utf-8");
const encoder = new TextEncoder();
const processor = createFixCzechProcessor(options);
return new TransformStream({
transform(chunk, controller) {
const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
const out = processor.feed(text);
if (out.length > 0) controller.enqueue(encoder.encode(out));
},
flush(controller) {
const tail = decoder.decode();
if (tail.length > 0) {
const out = processor.feed(tail);
if (out.length > 0) controller.enqueue(encoder.encode(out));
}
const final = processor.flush();
if (final.length > 0) controller.enqueue(encoder.encode(final));
}
});
}
exports.PREPOSITIONS = PREPOSITIONS;
exports.SAFE_UNITS = SAFE_UNITS;
exports.STRICT_UNITS = STRICT_UNITS;
exports.applyDates = applyDates;
exports.applyInitials = applyInitials;
exports.applyOrdinals = applyOrdinals;
exports.applyPrepositions = applyPrepositions;
exports.applyRoman = applyRoman;
exports.applyThousands = applyThousands;
exports.applyUnits = applyUnits;
exports.createFixCzechProcessor = createFixCzechProcessor;
exports.createFixCzechStream = createFixCzechStream;
exports.default = fixCzech_default;
exports.fixCzech = fixCzech;
exports.fixCzechHtml = fixCzechHtml;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map