|
|
@@ -0,0 +1,90 @@
|
|
|
+(() => {
|
|
|
+ // Shape the COMPLETE Myanmar string first. Bending pixels afterwards keeps
|
|
|
+ // combining marks attached; SVG textPath can split these clusters on iOS.
|
|
|
+ const drawTitle = async (title) => {
|
|
|
+ const text = title.textContent.trim();
|
|
|
+ if (!/[\u1000-\u109f]/u.test(text) || !document.fonts) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const loadedFonts = await document.fonts.load('700 22px "Noto Sans Myanmar"', text);
|
|
|
+ if (!loadedFonts.length) return; // Retain the readable HTML fallback.
|
|
|
+
|
|
|
+ const daily = title.classList.contains("myanmar-daily-title");
|
|
|
+ const ribbon = title.closest(".daily-title, .confirm-ribbon");
|
|
|
+ if (!ribbon || ribbon.querySelector(".curved-myanmar-title")) return;
|
|
|
+
|
|
|
+ const density = 4;
|
|
|
+ const width = 320;
|
|
|
+ const height = 75;
|
|
|
+ const maxTextWidth = daily ? 210 : 258;
|
|
|
+ const middleY = daily ? 29.5 : 36.5;
|
|
|
+ const rise = daily ? 14.5 : 18.5;
|
|
|
+ const halfCurveWidth = daily ? 102 : 140;
|
|
|
+ const source = document.createElement("canvas");
|
|
|
+ const sourceContext = source.getContext("2d");
|
|
|
+ const canvas = document.createElement("canvas");
|
|
|
+ const context = canvas.getContext("2d");
|
|
|
+ if (!sourceContext || !context) return;
|
|
|
+
|
|
|
+ let fontSize = 22;
|
|
|
+ const setFont = () => {
|
|
|
+ sourceContext.font = `700 ${fontSize * density}px "Noto Sans Myanmar"`;
|
|
|
+ sourceContext.textBaseline = "alphabetic";
|
|
|
+ sourceContext.textAlign = "left";
|
|
|
+ sourceContext.direction = "ltr";
|
|
|
+ };
|
|
|
+ setFont();
|
|
|
+ let metrics = sourceContext.measureText(text);
|
|
|
+ const measuredWidth = Math.max(metrics.width,
|
|
|
+ metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight);
|
|
|
+ fontSize *= Math.min(1, maxTextWidth * density / measuredWidth,
|
|
|
+ (middleY - 3) * density / metrics.actualBoundingBoxAscent);
|
|
|
+ setFont();
|
|
|
+ metrics = sourceContext.measureText(text);
|
|
|
+
|
|
|
+ // Include ink bearings and tall stacked marks in the source bitmap.
|
|
|
+ const padding = 2 * density;
|
|
|
+ const left = Math.ceil(Math.max(0, metrics.actualBoundingBoxLeft));
|
|
|
+ const right = Math.ceil(Math.max(metrics.width, metrics.actualBoundingBoxRight));
|
|
|
+ const ascent = Math.ceil(metrics.actualBoundingBoxAscent);
|
|
|
+ const descent = Math.ceil(Math.max(0, metrics.actualBoundingBoxDescent));
|
|
|
+ if (![left, right, ascent, descent].every(Number.isFinite) || right <= 0) return;
|
|
|
+ source.width = left + right + padding * 2;
|
|
|
+ source.height = ascent + descent + padding * 2;
|
|
|
+ setFont(); // Resizing a canvas resets its drawing state.
|
|
|
+ sourceContext.fillStyle = "#fff";
|
|
|
+ sourceContext.fillText(text, left + padding, ascent + padding);
|
|
|
+
|
|
|
+ canvas.width = width * density;
|
|
|
+ canvas.height = height * density;
|
|
|
+ canvas.className = "curved-myanmar-title";
|
|
|
+ canvas.setAttribute("aria-hidden", "true");
|
|
|
+
|
|
|
+ const startX = Math.round((canvas.width - source.width) / 2);
|
|
|
+ // Same quadratic baselines as the original ribbon paths, expressed
|
|
|
+ // as y(x), with the middle higher than the two ends.
|
|
|
+ for (let column = 0; column < source.width; column++) {
|
|
|
+ const x = (startX + column) / density;
|
|
|
+ const baseline = middleY + rise * ((x - width / 2) / halfCurveWidth) ** 2;
|
|
|
+ const y = Math.round(baseline * density - ascent - padding);
|
|
|
+ context.drawImage(source, column, 0, 1, source.height,
|
|
|
+ startX + column, y, 1, source.height);
|
|
|
+ }
|
|
|
+
|
|
|
+ ribbon.appendChild(canvas);
|
|
|
+ title.classList.add("has-curved-rendering");
|
|
|
+ } catch {
|
|
|
+ // Font/network/canvas failures must never hide the original title.
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const initialize = () => {
|
|
|
+ document.querySelectorAll(".myanmar-ribbon-title, .myanmar-daily-title")
|
|
|
+ .forEach((title) => { void drawTitle(title); });
|
|
|
+ };
|
|
|
+ if (document.readyState === "loading") {
|
|
|
+ document.addEventListener("DOMContentLoaded", initialize, { once: true });
|
|
|
+ } else {
|
|
|
+ initialize();
|
|
|
+ }
|
|
|
+})();
|