Forráskód Böngészése

fix chữ uốn theo banner

student 1 hete
szülő
commit
0e15a9ef32

+ 1 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/Views/Shared/_Layout.cshtml

@@ -202,6 +202,7 @@
 
     <script src="~/kitty/lib/jquery/jquery.min.js" asp-append-version="true"></script>
     <script src="~/kitty/js/all.min.js" asp-append-version="true"></script>
+    <script src="~/kitty/js/curved-titles.js" asp-append-version="true"></script>
     <script>
         function changeLang(lang) {
             $.ajax({

+ 14 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/css/all.min.css

@@ -3364,6 +3364,20 @@ html[lang="my"] .package-action button {
 }
 
 /* Native text shaping preserves Myanmar combining marks on iOS. */
+.curved-myanmar-title {
+    position: absolute;
+    inset: 0;
+    width: 100%;
+    height: 100%;
+    pointer-events: none;
+    filter: drop-shadow(0 2px 1px rgba(143, 40, 86, 0.35));
+}
+
+/* Keep the accessible HTML text and its layout after the canvas is ready. */
+.has-curved-rendering {
+    opacity: 0;
+}
+
 .myanmar-ribbon-title {
     display: none;
 }

+ 14 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/css/input.css

@@ -3364,6 +3364,20 @@ html[lang="my"] .package-action button {
 }
 
 /* Native text shaping preserves Myanmar combining marks on iOS. */
+.curved-myanmar-title {
+    position: absolute;
+    inset: 0;
+    width: 100%;
+    height: 100%;
+    pointer-events: none;
+    filter: drop-shadow(0 2px 1px rgba(143, 40, 86, 0.35));
+}
+
+/* Keep the accessible HTML text and its layout after the canvas is ready. */
+.has-curved-rendering {
+    opacity: 0;
+}
+
 .myanmar-ribbon-title {
     display: none;
 }

+ 90 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Website/wwwroot/kitty/js/curved-titles.js

@@ -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();
+    }
+})();