Fix SVG font inconsistency and wave animation timing desync

Co-authored-by: Corgiking0127 <54504179+Corgiking0127@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]andCorgiking0127 committed 2026-03-07 07:16:16 +00:00
1 parent 04a6253561
commit 317a9efb4f
2 files changed
+46 -14

No files matched your search

+8 -7
View File
@@ -23,11 +23,12 @@ async function loadFont(k){
cache[k]={css:`@import url('${f.url.replace(/&/g,"&amp;")}');`,m:"fb"};
try{
const css=await get(f.url); if(!css.ok) throw 0;
const m=css.buf.toString().match(/url\((https:\/\/fonts\.gstatic\.com\/[^\)]+\.woff2)\)/);
if(!m) throw 0;
const w=await get(m[1]); if(!w.ok) throw 0;
const ur=css.buf.toString().match(/unicode-range:\s*([^;}\n]+)/);
cache[k]={css:`@font-face{font-family:'${f.family}';font-style:${f.style};font-weight:${f.weight};src:url(data:font/woff2;base64,${w.buf.toString("base64")}) format('woff2');${ur?"unicode-range:"+ur[1].trim()+";":""}}`,m:"ok"};
// Use the last woff2 URL (Google Fonts lists Latin last, which covers most signatures)
const all=[...css.buf.toString().matchAll(/url\((https:\/\/fonts\.gstatic\.com\/[^\)]+\.woff2)\)/g)];
if(!all.length) throw 0;
const w=await get(all[all.length-1][1]); if(!w.ok) throw 0;
// Omit unicode-range so the embedded font applies to all characters
cache[k]={css:`@font-face{font-family:'${f.family}';font-style:${f.style};font-weight:${f.weight};src:url(data:font/woff2;base64,${w.buf.toString("base64")}) format('woff2');}`,m:"ok"};
}catch(e){/* keep fallback */}
}
@@ -40,7 +41,7 @@ function dots(W,H,f){let d="";for(let i=0;i<50;i++)d+=`<circle cx="${(i*137+29)%
function fl(f,t,W,H){const e=f.size*.48*t.length,s=W/2-e/2,y=H/2+f.size*.38;let d=`M ${s} ${y}`;for(let x=0;x<=e;x+=4){const p=x/e;d+=` L ${(s+x).toFixed(1)} ${(y+Math.sin(p*Math.PI*2.5)*6*(1-p*.7)).toFixed(1)}`}return{d,l:(e*1.05).toFixed(0)}}
function buildSVG(text,font,fk,color,speed,bgC,animated){
const W=600,H=200,b=bi(bgC),dur=(2.4/speed).toFixed(2),fDl=(dur*.78).toFixed(2),fDr=(dur*.28).toFixed(2);
const W=600,H=200,b=bi(bgC),dur=(2.4/speed).toFixed(2);
const sk=font.skewX?`skewX(${font.skewX})`:"",f=fl(font,text,W,H),dt=b.t?"":dots(W,H,b.gr);
const txtEl=`<text x="${W/2}" y="${H/2+font.size*.08+font.yo}" font-family="'${font.family}',cursive,serif" font-size="${font.size}" font-weight="${font.weight}" font-style="${font.style}" fill="${color}" text-anchor="middle" dominant-baseline="middle" letter-spacing="${font.ls}" transform="translate(0,0) ${sk}" transform-origin="${W/2} ${H/2}" ${font.sw?`stroke="${color}" stroke-width="${font.sw}"`:""}>${esc(text)}</text>`;
if(!animated) return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">
@@ -55,7 +56,7 @@ ${txtEl}
${b.t?"":`<rect width="${W}" height="${H}" rx="4" fill="${b.bg}"/>`}${dt}
<g clip-path="url(#r)">${txtEl}</g>
<g opacity="1"><animateTransform attributeName="transform" type="translate" from="0 0" to="${W} 0" dur="${dur}s" fill="freeze" calcMode="spline" keySplines="0.25 0.1 0.25 1" keyTimes="0;1" repeatCount="indefinite"/><g transform="translate(0,${H/2-8}) rotate(22)"><rect x="-1.5" y="-28" width="3" height="26" rx="1" fill="${b.pn}"/><polygon points="0,1 -1.8,-5 1.8,-5" fill="${color}"/></g><animate attributeName="opacity" values="1;1;0" keyTimes="0;0.92;1" dur="${dur}s" fill="freeze" repeatCount="indefinite"/></g>
<path d="${f.d}" fill="none" stroke="${color}" stroke-width="1.2" stroke-linecap="round" opacity=".45" stroke-dasharray="${f.l}" stroke-dashoffset="${f.l}"><animate attributeName="stroke-dashoffset" from="${f.l}" to="0" begin="${fDl}s" dur="${fDr}s" fill="freeze" calcMode="spline" keySplines="0.4 0 0.2 1" keyTimes="0;1" repeatCount="indefinite"/></path>
<path d="${f.d}" fill="none" stroke="${color}" stroke-width="1.2" stroke-linecap="round" opacity=".45" stroke-dasharray="${f.l}" stroke-dashoffset="${f.l}"><animate attributeName="stroke-dashoffset" values="${f.l};${f.l};0;0" keyTimes="0;0.72;0.95;1" dur="${dur}s" calcMode="spline" keySplines="0 0 1 1;0.4 0 0.2 1;0 0 1 1" fill="freeze" repeatCount="indefinite"/></path>
</svg>`;
}
+38 -7
View File
@@ -206,9 +206,26 @@ function defaultBase(){return location.origin+"/api"}
// ─── SVG Builder ─────────────────────────────────────────────────
function escXml(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}
async function fetchFontBase64(font){
try{
const cssRes=await fetch(font.url);if(!cssRes.ok)return null;
const css=await cssRes.text();
// Use the last woff2 URL — Google Fonts lists Latin last, covering most signatures
const all=[...css.matchAll(/url\((https:\/\/fonts\.gstatic\.com\/[^)]+\.woff2)\)/g)];
if(!all.length)return null;
const woffRes=await fetch(all[all.length-1][1]);if(!woffRes.ok)return null;
const buf=await woffRes.arrayBuffer();
const bytes=new Uint8Array(buf);
const bin=Array.from(bytes,b=>String.fromCharCode(b)).join("");
// Extract primary font-family name, e.g. "'Dancing Script', cursive" → "Dancing Script"
const familyName=font.family.replace(/^['"]([^'"]+)['"].*$/,"$1");
return{base64:btoa(bin),familyName};
}catch(e){return null}
}
function buildSVG(text,fontFamily,inkColor,bgColor,speed,fontUrl){
const W=600,H=180,bg=bgColor||"#faf8f3",isT=bg==="transparent";
const dur=(2.4/speed).toFixed(2),fDel=(dur*.78).toFixed(2),fDr=(dur*.28).toFixed(2);
const dur=(2.4/speed).toFixed(2);
const estW=text.length*28,fsx=W/2-estW/2,fBy=H/2+36;
let fD=`M ${fsx} ${fBy}`;
for(let x=0;x<=estW;x+=4){const t=x/estW;fD+=` L ${(fsx+x).toFixed(1)} ${(fBy+Math.sin(t*Math.PI*2.5)*6*(1-t*.7)).toFixed(1)}`}
@@ -224,7 +241,7 @@ function buildSVG(text,fontFamily,inkColor,bgColor,speed,fontUrl){
${isT?"":`<rect width="${W}" height="${H}" rx="6" fill="${bg}"/>`}${dots}
<g clip-path="url(#reveal)"><text x="${W/2}" y="${H/2+4}" font-family="${fontFamily}" font-size="60" fill="${inkColor}" text-anchor="middle" dominant-baseline="middle" letter-spacing="1">${escXml(text)}</text></g>
<g opacity="1"><animateTransform attributeName="transform" type="translate" from="0 0" to="${W} 0" dur="${dur}s" fill="freeze" calcMode="spline" keySplines="0.25 0.1 0.25 1" keyTimes="0;1" repeatCount="indefinite"/><g transform="translate(0,${H/2-22}) rotate(22)"><rect x="-1.8" y="-28" width="3.6" height="26" rx="1.2" fill="${pen}"/><polygon points="0,1 -2,-5 2,-5" fill="${inkColor}"/></g><animate attributeName="opacity" values="1;1;0" keyTimes="0;0.92;1" dur="${dur}s" fill="freeze" repeatCount="indefinite"/></g>
<path d="${fD}" fill="none" stroke="${inkColor}" stroke-width="1.4" stroke-linecap="round" opacity=".5" stroke-dasharray="${fLen}" stroke-dashoffset="${fLen}"><animate attributeName="stroke-dashoffset" from="${fLen}" to="0" begin="${fDel}s" dur="${fDr}s" fill="freeze" calcMode="spline" keySplines="0.4 0 0.2 1" keyTimes="0;1" repeatCount="indefinite"/></path>
<path d="${fD}" fill="none" stroke="${inkColor}" stroke-width="1.4" stroke-linecap="round" opacity=".5" stroke-dasharray="${fLen}" stroke-dashoffset="${fLen}"><animate attributeName="stroke-dashoffset" values="${fLen};${fLen};0;0" keyTimes="0;0.72;0.95;1" dur="${dur}s" calcMode="spline" keySplines="0 0 1 1;0.4 0 0.2 1;0 0 1 1" fill="freeze" repeatCount="indefinite"/></path>
</svg>`;
}
@@ -252,13 +269,27 @@ function switchTab(t){
document.getElementById("tab-api").style.display=t==="api"?"":"none";
}
function downloadSvg(){
if(!state.svgStr)return;
const a=document.createElement("a");a.href=URL.createObjectURL(new Blob([state.svgStr],{type:"image/svg+xml"}));
async function svgWithEmbeddedFont(){
if(!state.svgStr)return null;
let svg=state.svgStr;
if(state.font.url){
const fd=await fetchFontBase64(state.font);
if(fd){
const imp=`@import url('${state.font.url.replace(/&/g,"&amp;")}');`;
const face=`@font-face{font-family:'${fd.familyName}';src:url(data:font/woff2;base64,${fd.base64}) format('woff2');}`;
svg=svg.replace(imp,face);
}
}
return svg;
}
async function downloadSvg(){
const svg=await svgWithEmbeddedFont();if(!svg)return;
const a=document.createElement("a");a.href=URL.createObjectURL(new Blob([svg],{type:"image/svg+xml"}));
a.download=`signature_${document.getElementById("nameInput").value.replace(/\s+/g,"_")}.svg`;a.click();URL.revokeObjectURL(a.href);
}
function copySvgSrc(){
navigator.clipboard.writeText(state.svgStr).catch(()=>{const t=document.createElement("textarea");t.value=state.svgStr;document.body.appendChild(t);t.select();document.execCommand("copy");document.body.removeChild(t)});
async function copySvgSrc(){
const svg=await svgWithEmbeddedFont();if(!svg)return;
navigator.clipboard.writeText(svg).catch(()=>{const t=document.createElement("textarea");t.value=svg;document.body.appendChild(t);t.select();document.execCommand("copy");document.body.removeChild(t)});
const b=document.getElementById("copySrcBtn");b.classList.add("ok");b.textContent="✓ Copied!";setTimeout(()=>{b.classList.remove("ok");b.textContent="Copy SVG Source Code"},2000);
}
function copyUrl(btn,text){