syscycle/svg-text-to-path
0
1<!DOCTYPE html>2<html lang="tr" class="h-full bg-slate-900 text-slate-100">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Android Text-to-SVG Path Generator</title>7 <script src="https://cdn.tailwindcss.com"></script>8 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">9 <script src="https://cdnjs.cloudflare.com/ajax/libs/opentype.js/1.3.4/opentype.min.js"></script>10 11 <style>12 ::-webkit-scrollbar {13 width: 6px;14 height: 6px;15 }16 ::-webkit-scrollbar-track {17 background: #0f172a;18 }19 ::-webkit-scrollbar-thumb {20 background: #334155;21 border-radius: 3px;22 }23 ::-webkit-scrollbar-thumb:hover {24 background: #475569;25 }26 </style>27</head>28<body class="h-full flex flex-col font-sans antialiased overflow-x-hidden">29 30 <header class="border-b border-slate-800 bg-slate-950/90 backdrop-blur-md sticky top-0 z-50 px-4 py-3">31 <div class="max-w-7xl mx-auto flex items-center justify-between">32 <div class="flex items-center space-x-3">33 <div class="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400">34 <i class="fa-brands fa-android text-2xl"></i>35 </div>36 <div>37 <h1 class="text-lg font-bold text-slate-100 flex items-center gap-2">38 Android Text-to-Path SVG39 <span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">40 Real <path d="..."> Outlines41 </span>42 </h1>43 <p class="text-xs text-slate-400">Android fontlarıyla saf vektör yoluna (vector curve) dönüştürme motoru</p>44 </div>45 </div>46 47 <div class="flex items-center gap-2">48 <button id="btn-export-svg" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium rounded-lg shadow-lg shadow-emerald-900/30 transition flex items-center gap-2">49 <i class="fa-solid fa-download"></i>50 <span>SVG Path İndir</span>51 </button>52 <button id="btn-copy-code" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 text-sm font-medium rounded-lg border border-slate-700 transition flex items-center gap-2">53 <i class="fa-regular fa-copy"></i>54 <span>Kodu Kopyala</span>55 </button>56 </div>57 </div>58 </header>59 60 <main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 grid grid-cols-1 lg:grid-cols-12 gap-6 overflow-hidden">61 <div class="lg:col-span-5 flex flex-col gap-5 overflow-y-auto pr-1 max-h-[calc(100vh-100px)]">62 <div class="p-3.5 bg-emerald-950/40 border border-emerald-500/30 rounded-xl text-xs text-emerald-200 flex items-start gap-3">63 <i class="fa-solid fa-bezier-curve text-emerald-400 text-base shrink-0 mt-0.5"></i>64 <div>65 <strong>Gerçek Vektör Dönüştürücü:</strong> Metniniz matematiksel çizgi ve eğri komutlarına (<code><path d="M..." /></code>) dönüştürülür. Hedef cihazda font kurulu olmasına gerek kalmaz; Illustrator/Figma'da açılabilir ve düzenlenebilir.66 </div>67 </div>68 <div class="bg-slate-800/40 border border-slate-700/80 rounded-xl p-4 space-y-3">69 <label class="block text-xs font-semibold text-slate-300 uppercase tracking-wider flex justify-between">70 <span>Metin İçeriği</span>71 <span class="text-slate-500 font-normal">Çoklu satır desteklenir</span>72 </label>73 <textarea id="input-text" rows="3" class="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm text-slate-100 placeholder-slate-500 focus:outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition resize-y" placeholder="Vektör path yapmak istediğiniz metni girin...">Android OS74System Vector</textarea>75 </div>76 <div class="bg-slate-800/40 border border-slate-700/80 rounded-xl p-4 space-y-4">77 <h3 class="text-xs font-semibold text-slate-300 uppercase tracking-wider border-b border-slate-700/60 pb-2 flex items-center justify-between">78 <span>Android Font Seçimi</span>79 <i class="fa-solid fa-font text-slate-500"></i>80 </h3>81 <div class="space-y-1">82 <label class="text-xs text-slate-400">Android Sistem Fontu (TTF)</label>83 <select id="select-font-preset" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:border-emerald-500">84 <option value="roboto-regular">Roboto Regular (Android Standart)</option>85 <option value="roboto-bold">Roboto Bold (Android Kalın)</option>86 <option value="roboto-light">Roboto Light (Android İnce)</option>87 <option value="roboto-italic">Roboto Italic (Android Eğik)</option>88 <option value="noto-sans">Noto Sans (Android Global / Türkçe)</option>89 <option value="roboto-mono">Roboto Mono (Android Monospace)</option>90 <option value="custom">Özel Font Yükle (.ttf / .otf)</option>91 </select>92 </div>93 94 <!-- Custom Font Upload (Hidden by default) -->95 <div id="section-custom-font" class="hidden space-y-2 p-3 bg-slate-900/60 rounded-lg border border-slate-700/50">96 <label class="block text-xs text-slate-300 font-medium">Font Dosyası Yükleyin (.ttf veya .otf)</label>97 <input id="file-custom-font" type="file" accept=".ttf,.otf,.woff" class="block w-full text-xs text-slate-400 file:mr-3 file:py-1.5 file:px-3 file:rounded-lg file:border-0 file:text-xs file:font-medium file:bg-emerald-600 file:text-white hover:file:bg-emerald-500 cursor-pointer">98 </div>99 <div id="font-loading-status" class="flex items-center gap-2 text-xs text-emerald-400 font-mono">100 <i class="fa-solid fa-spinner fa-spin text-slate-400"></i>101 <span>Font yükleniyor...</span>102 </div>103 </div>104 <div class="bg-slate-800/40 border border-slate-700/80 rounded-xl p-4 space-y-4">105 <h3 class="text-xs font-semibold text-slate-300 uppercase tracking-wider border-b border-slate-700/60 pb-2 flex items-center justify-between">106 <span>Boyut & Düzen</span>107 <i class="fa-solid fa-sliders text-slate-500"></i>108 </h3>109 <div>110 <div class="flex justify-between text-xs text-slate-400 mb-1">111 <span>Font Boyutu (px)</span>112 <span id="val-font-size" class="text-emerald-400 font-mono">56</span>113 </div>114 <input id="slider-font-size" type="range" min="16" max="200" value="56" class="w-full accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">115 </div>116 <div>117 <div class="flex justify-between text-xs text-slate-400 mb-1">118 <span>Satır Yüksekliği Çarpanı</span>119 <span id="val-line-height" class="text-emerald-400 font-mono">1.2</span>120 </div>121 <input id="slider-line-height" type="range" min="0.8" max="2.5" step="0.1" value="1.2" class="w-full accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">122 </div>123 <div>124 <div class="flex justify-between text-xs text-slate-400 mb-1">125 <span>Harf Aralığı (px)</span>126 <span id="val-letter-spacing" class="text-emerald-400 font-mono">0</span>127 </div>128 <input id="slider-letter-spacing" type="range" min="-10" max="40" value="0" class="w-full accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">129 </div>130 <div>131 <label class="text-xs text-slate-400 block mb-1">Path Hizalama</label>132 <div class="grid grid-cols-3 gap-2">133 <button type="button" data-align="left" class="btn-align py-1.5 bg-slate-800 text-slate-300 hover:bg-slate-700 rounded border border-slate-700 text-xs font-medium flex items-center justify-center gap-1">134 <i class="fa-solid fa-align-left"></i> Sol135 </button>136 <button type="button" data-align="center" class="btn-align py-1.5 bg-emerald-600 text-white rounded border border-emerald-500 text-xs font-medium flex items-center justify-center gap-1">137 <i class="fa-solid fa-align-center"></i> Orta138 </button>139 <button type="button" data-align="end" class="btn-align py-1.5 bg-slate-800 text-slate-300 hover:bg-slate-700 rounded border border-slate-700 text-xs font-medium flex items-center justify-center gap-1">140 <i class="fa-solid fa-align-right"></i> Sağ141 </button>142 </div>143 </div>144 </div>145 146 <!-- Colors & Path Effects -->147 <div class="bg-slate-800/40 border border-slate-700/80 rounded-xl p-4 space-y-4">148 <h3 class="text-xs font-semibold text-slate-300 uppercase tracking-wider border-b border-slate-700/60 pb-2 flex items-center justify-between">149 <span>Path Renk & Stil</span>150 <i class="fa-solid fa-palette text-slate-500"></i>151 </h3>152 <div class="grid grid-cols-2 gap-2">153 <button id="tab-fill-solid" class="py-1.5 text-xs rounded-lg bg-emerald-600 text-white border border-emerald-500 font-medium">Düz Dolgu</button>154 <button id="tab-fill-gradient" class="py-1.5 text-xs rounded-lg bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700 font-medium">Gradyan Dolgu</button>155 </div>156 <div id="section-fill-solid" class="flex items-center justify-between">157 <span class="text-xs text-slate-400">Path Dolgu Rengi</span>158 <div class="flex items-center gap-2">159 <input id="color-text" type="color" value="#38bdf8" class="w-8 h-8 rounded bg-transparent cursor-pointer border-0">160 <input id="hex-text" type="text" value="#38bdf8" class="w-20 bg-slate-900 border border-slate-700 rounded text-xs px-2 py-1 text-slate-200 font-mono uppercase">161 </div>162 </div>163 <div id="section-fill-gradient" class="hidden space-y-3">164 <div class="flex items-center justify-between">165 <span class="text-xs text-slate-400">Başlangıç Renk</span>166 <input id="color-grad-1" type="color" value="#38bdf8" class="w-8 h-8 rounded bg-transparent cursor-pointer border-0">167 </div>168 <div class="flex items-center justify-between">169 <span class="text-xs text-slate-400">Bitiş Renk</span>170 <input id="color-grad-2" type="color" value="#34d399" class="w-8 h-8 rounded bg-transparent cursor-pointer border-0">171 </div>172 <div class="flex items-center justify-between">173 <span class="text-xs text-slate-400">Açı</span>174 <select id="select-grad-angle" class="bg-slate-900 border border-slate-700 rounded text-xs px-2 py-1 text-slate-200">175 <option value="horizontal">Yatay (0°)</option>176 <option value="vertical">Dikey (90°)</option>177 <option value="diagonal">Diyagonal (45°)</option>178 </select>179 </div>180 </div>181 <div class="border-t border-slate-700/60 pt-3 space-y-2">182 <div class="flex items-center justify-between">183 <label class="text-xs text-slate-400 flex items-center gap-2">184 <input id="chk-stroke" type="checkbox" class="rounded accent-emerald-500">185 <span>Vektör Dış Çizgisi (Stroke)</span>186 </label>187 <input id="color-stroke" type="color" value="#0f172a" class="w-7 h-7 rounded bg-transparent cursor-pointer border-0">188 </div>189 <div id="section-stroke-width" class="hidden flex items-center justify-between gap-3 pt-1">190 <span class="text-xs text-slate-500">Çizgi Kalınlığı (px)</span>191 <input id="slider-stroke-width" type="range" min="1" max="15" value="2" class="w-32 accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">192 <span id="val-stroke-width" class="text-xs font-mono text-emerald-400">2</span>193 </div>194 </div>195 <div class="border-t border-slate-700/60 pt-3 space-y-2">196 <div class="flex items-center justify-between">197 <label class="text-xs text-slate-400 flex items-center gap-2">198 <input id="chk-shadow" type="checkbox" class="rounded accent-emerald-500">199 <span>Vektör Gölgesi (Drop Shadow)</span>200 </label>201 <input id="color-shadow" type="color" value="#000000" class="w-7 h-7 rounded bg-transparent cursor-pointer border-0">202 </div>203 <div id="section-shadow-controls" class="hidden space-y-2 pt-1">204 <div class="flex items-center justify-between gap-2">205 <span class="text-xs text-slate-500">Bulanıklık</span>206 <input id="slider-shadow-blur" type="range" min="0" max="25" value="6" class="w-28 accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">207 <span id="val-shadow-blur" class="text-xs font-mono text-emerald-400">6</span>208 </div>209 <div class="flex items-center justify-between gap-2">210 <span class="text-xs text-slate-500">Öteleme X</span>211 <input id="slider-shadow-x" type="range" min="-30" max="30" value="2" class="w-28 accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">212 <span id="val-shadow-x" class="text-xs font-mono text-emerald-400">2</span>213 </div>214 <div class="flex items-center justify-between gap-2">215 <span class="text-xs text-slate-500">Öteleme Y</span>216 <input id="slider-shadow-y" type="range" min="-30" max="30" value="4" class="w-28 accent-emerald-500 bg-slate-700 h-1.5 rounded-lg appearance-none cursor-pointer">217 <span id="val-shadow-y" class="text-xs font-mono text-emerald-400">4</span>218 </div>219 </div>220 </div>221 </div>222 <div class="bg-slate-800/40 border border-slate-700/80 rounded-xl p-4 space-y-3">223 <h3 class="text-xs font-semibold text-slate-300 uppercase tracking-wider border-b border-slate-700/60 pb-2 flex items-center justify-between">224 <span>Kanvas & Arka Plan</span>225 <i class="fa-solid fa-expand text-slate-500"></i>226 </h3>227 228 <div class="grid grid-cols-2 gap-3">229 <div>230 <label class="text-xs text-slate-400 block mb-1">Genişlik (px)</label>231 <input id="canvas-width" type="number" value="650" min="100" max="3000" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-1.5 text-xs text-slate-200">232 </div>233 <div>234 <label class="text-xs text-slate-400 block mb-1">Yükseklik (px)</label>235 <input id="canvas-height" type="number" value="320" min="100" max="3000" class="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-1.5 text-xs text-slate-200">236 </div>237 </div>238 239 <div class="flex items-center justify-between pt-2">240 <label class="text-xs text-slate-400 flex items-center gap-2">241 <input id="chk-bg" type="checkbox" checked class="rounded accent-emerald-500">242 <span>Arka Plan Ekle</span>243 </label>244 <div id="wrapper-bg-color" class="flex items-center gap-2">245 <input id="color-bg" type="color" value="#0f172a" class="w-7 h-7 rounded bg-transparent cursor-pointer border-0">246 <input id="slider-bg-radius" type="number" value="16" min="0" max="100" title="Köşe Yuvarlama (rx)" class="w-14 bg-slate-900 border border-slate-700 rounded text-xs px-2 py-1 text-slate-200">247 <span class="text-xs text-slate-500">px rx</span>248 </div>249 </div>250 </div>251 252 </div>253 <div class="lg:col-span-7 flex flex-col gap-4 h-full">254 <div class="flex items-center justify-between bg-slate-800/60 p-2 rounded-xl border border-slate-700/60">255 <div class="flex items-center gap-1">256 <button id="btn-view-preview" class="px-3 py-1.5 rounded-lg text-xs font-medium bg-emerald-600 text-white shadow-sm flex items-center gap-1.5">257 <i class="fa-solid fa-eye"></i> Vektör Önizleme258 </button>259 <button id="btn-view-code" class="px-3 py-1.5 rounded-lg text-xs font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-800 flex items-center gap-1.5">260 <i class="fa-solid fa-code"></i> SVG Path Kaynak Kodu261 </button>262 </div>263 264 <div class="text-xs text-slate-400 font-mono flex items-center gap-3 px-2">265 <span id="canvas-size-indicator">650 x 320 px</span>266 <span class="text-slate-600">|</span>267 <span id="svg-file-size" class="text-emerald-400">~2.5 KB</span>268 </div>269 </div>270 <div id="container-preview" class="flex-1 bg-slate-950/80 border border-slate-800 rounded-2xl p-6 flex items-center justify-center relative overflow-hidden min-h-[380px] shadow-inner">271 <div class="absolute inset-0 opacity-10 bg-[radial-gradient(#334155_1px,transparent_1px)] [background-size:16px_16px] pointer-events-none"></div>272 <div id="svg-output-wrapper" class="relative max-w-full max-h-full flex items-center justify-center transition-all duration-200">273 </div>274 </div>275 <div id="container-code" class="hidden flex-1 bg-slate-950 border border-slate-800 rounded-2xl p-4 flex flex-col min-h-[380px] relative">276 <div class="flex justify-between items-center mb-2 px-1">277 <span class="text-xs font-mono text-emerald-400 flex items-center gap-2">278 <i class="fa-solid fa-code"></i> Saf <path d="M..." /> Çıktısı (Zero Font Dependency)279 </span>280 <button id="btn-inner-copy" class="text-xs text-emerald-400 hover:text-emerald-300 flex items-center gap-1 bg-emerald-500/10 px-2 py-1 rounded border border-emerald-500/20">281 <i class="fa-regular fa-copy"></i> Kopyala282 </button>283 </div>284 <textarea id="code-output" readonly class="w-full flex-1 bg-slate-900/90 border border-slate-800 rounded-xl p-4 font-mono text-xs text-emerald-300 focus:outline-none resize-none leading-relaxed overflow-y-auto selection:bg-emerald-500/30"></textarea>285 </div>286 <div id="toast-message" class="fixed bottom-6 right-6 bg-emerald-500 text-slate-950 px-4 py-2.5 rounded-xl shadow-2xl font-medium text-xs flex items-center gap-2 transform translate-y-20 opacity-0 transition-all duration-300 pointer-events-none z-50">287 <i class="fa-solid fa-circle-check text-base"></i>288 <span id="toast-text">SVG Path kodu kopyalandı!</span>289 </div>290 291 </div>292 293 </main>294 295 <script>296 document.addEventListener('DOMContentLoaded', () => {297 const FONT_URLS = {298 'roboto-regular': 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.ttf',299 'roboto-bold': 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-700-normal.ttf',300 'roboto-light': 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-300-normal.ttf',301 'roboto-italic': 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-italic.ttf',302 'noto-sans': 'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans@latest/latin-400-normal.ttf',303 'roboto-mono': 'https://cdn.jsdelivr.net/fontsource/fonts/roboto-mono@latest/latin-400-normal.ttf'304 };305 306 const state = {307 text: "Android OS\nSystem Vector",308 fontPreset: "roboto-regular",309 currentFont: null,310 isLoadingFont: true,311 fontSize: 56,312 lineHeight: 1.2,313 letterSpacing: 0,314 textAlign: "center",315 fillType: "solid",316 colorText: "#38bdf8",317 colorGrad1: "#38bdf8",318 colorGrad2: "#34d399",319 gradAngle: "horizontal",320 hasStroke: false,321 colorStroke: "#0f172a",322 strokeWidth: 2,323 hasShadow: false,324 colorShadow: "#000000",325 shadowBlur: 6,326 shadowX: 2,327 shadowY: 4,328 width: 650,329 height: 320,330 hasBg: true,331 colorBg: "#0f172a",332 bgRadius: 16333 };334 const inputText = document.getElementById('input-text');335 const selectFontPreset = document.getElementById('select-font-preset');336 const sectionCustomFont = document.getElementById('section-custom-font');337 const fileCustomFont = document.getElementById('file-custom-font');338 const fontLoadingStatus = document.getElementById('font-loading-status');339 340 const sliderFontSize = document.getElementById('slider-font-size');341 const valFontSize = document.getElementById('val-font-size');342 const sliderLineHeight = document.getElementById('slider-line-height');343 const valLineHeight = document.getElementById('val-line-height');344 const sliderLetterSpacing = document.getElementById('slider-letter-spacing');345 const valLetterSpacing = document.getElementById('val-letter-spacing');346 const btnAligns = document.querySelectorAll('.btn-align');347 348 const tabSolid = document.getElementById('tab-fill-solid');349 const tabGradient = document.getElementById('tab-fill-gradient');350 const sectionSolid = document.getElementById('section-fill-solid');351 const sectionGradient = document.getElementById('section-fill-gradient');352 353 const colorText = document.getElementById('color-text');354 const hexText = document.getElementById('hex-text');355 const colorGrad1 = document.getElementById('color-grad-1');356 const colorGrad2 = document.getElementById('color-grad-2');357 const selectGradAngle = document.getElementById('select-grad-angle');358 359 const chkStroke = document.getElementById('chk-stroke');360 const sectionStrokeWidth = document.getElementById('section-stroke-width');361 const colorStroke = document.getElementById('color-stroke');362 const sliderStrokeWidth = document.getElementById('slider-stroke-width');363 const valStrokeWidth = document.getElementById('val-stroke-width');364 365 const chkShadow = document.getElementById('chk-shadow');366 const sectionShadowControls = document.getElementById('section-shadow-controls');367 const colorShadow = document.getElementById('color-shadow');368 const sliderShadowBlur = document.getElementById('slider-shadow-blur');369 const valShadowBlur = document.getElementById('val-shadow-blur');370 const sliderShadowX = document.getElementById('slider-shadow-x');371 const valShadowX = document.getElementById('val-shadow-x');372 const sliderShadowY = document.getElementById('slider-shadow-y');373 const valShadowY = document.getElementById('val-shadow-y');374 375 const inputWidth = document.getElementById('canvas-width');376 const inputHeight = document.getElementById('canvas-height');377 const chkBg = document.getElementById('chk-bg');378 const colorBg = document.getElementById('color-bg');379 const sliderBgRadius = document.getElementById('slider-bg-radius');380 const wrapperBgColor = document.getElementById('wrapper-bg-color');381 382 const svgOutputWrapper = document.getElementById('svg-output-wrapper');383 const codeOutput = document.getElementById('code-output');384 const canvasSizeIndicator = document.getElementById('canvas-size-indicator');385 const svgFileSize = document.getElementById('svg-file-size');386 387 const btnViewPreview = document.getElementById('btn-view-preview');388 const btnViewCode = document.getElementById('btn-view-code');389 const containerPreview = document.getElementById('container-preview');390 const containerCode = document.getElementById('container-code');391 392 const btnExportSvg = document.getElementById('btn-export-svg');393 const btnCopyCode = document.getElementById('btn-copy-code');394 const btnInnerCopy = document.getElementById('btn-inner-copy');395 const toastMessage = document.getElementById('toast-message');396 const toastText = document.getElementById('toast-text');397 function loadPresetFont(key) {398 const url = FONT_URLS[key];399 if (!url) return;400 401 state.isLoadingFont = true;402 updateStatusUI("loading", "Font indiriliyor & işleniyor...");403 404 opentype.load(url, (err, font) => {405 if (err) {406 console.error("Font yükleme hatası:", err);407 updateStatusUI("error", "Font yüklenemedi. Lütfen tekrar deneyin.");408 state.isLoadingFont = false;409 return;410 }411 state.currentFont = font;412 state.isLoadingFont = false;413 updateStatusUI("success", `Font hazır: ${font.names.fontFamily?.en || key}`);414 renderSVG();415 });416 }417 418 function updateStatusUI(type, message) {419 if (type === "loading") {420 fontLoadingStatus.className = "flex items-center gap-2 text-xs text-amber-400 font-mono";421 fontLoadingStatus.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> <span>${message}</span>`;422 } else if (type === "success") {423 fontLoadingStatus.className = "flex items-center gap-2 text-xs text-emerald-400 font-mono";424 fontLoadingStatus.innerHTML = `<i class="fa-solid fa-check"></i> <span>${message}</span>`;425 } else if (type === "error") {426 fontLoadingStatus.className = "flex items-center gap-2 text-xs text-rose-400 font-mono";427 fontLoadingStatus.innerHTML = `<i class="fa-solid fa-triangle-exclamation"></i> <span>${message}</span>`;428 }429 }430 function generateCombinedPathData() {431 if (!state.currentFont) return "";432 433 const font = state.currentFont;434 const lines = state.text.split('\n');435 const fontSize = state.fontSize;436 const lineGap = fontSize * state.lineHeight;437 const letterSpacing = state.letterSpacing;438 const totalTextHeight = lines.length * lineGap;439 const startY = (state.height / 2) - (totalTextHeight / 2) + (fontSize * 0.7);440 441 let combinedPathCommands = "";442 443 lines.forEach((lineText, lineIdx) => {444 if (!lineText) lineText = " ";445 const yPos = startY + (lineIdx * lineGap);446 let lineWidth = 0;447 const glyphs = font.stringToGlyphs(lineText);448 449 for (let i = 0; i < glyphs.length; i++) {450 const glyph = glyphs[i];451 if (glyph.advanceWidth) {452 lineWidth += (glyph.advanceWidth / font.unitsPerEm) * fontSize;453 }454 if (i < glyphs.length - 1) {455 lineWidth += letterSpacing;456 }457 }458 459 let xPos = 0;460 if (state.textAlign === 'left') {461 xPos = state.width * 0.08;462 } else if (state.textAlign === 'center') {463 xPos = (state.width - lineWidth) / 2;464 } else if (state.textAlign === 'right') {465 xPos = state.width * 0.92 - lineWidth;466 }467 468 let currentX = xPos;469 for (let i = 0; i < glyphs.length; i++) {470 const glyph = glyphs[i];471 const glyphPath = glyph.getPath(currentX, yPos, fontSize);472 combinedPathCommands += glyphPath.toPathData() + " ";473 474 if (glyph.advanceWidth) {475 currentX += (glyph.advanceWidth / font.unitsPerEm) * fontSize;476 }477 currentX += letterSpacing;478 }479 });480 481 return combinedPathCommands.trim();482 }483 function buildSVGString() {484 if (state.isLoadingFont || !state.currentFont) {485 return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${state.width} ${state.height}" width="${state.width}" height="${state.height}">486 <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#38bdf8" font-family="sans-serif" font-size="16">Font Yükleniyor...</text>487</svg>`;488 }489 490 const pathData = generateCombinedPathData();491 let defsMarkup = "";492 let fillAttr = state.colorText;493 494 if (state.fillType === 'gradient') {495 let x1 = "0%", y1 = "0%", x2 = "100%", y2 = "0%";496 if (state.gradAngle === 'vertical') {497 x1 = "0%"; y1 = "0%"; x2 = "0%"; y2 = "100%";498 } else if (state.gradAngle === 'diagonal') {499 x1 = "0%"; y1 = "0%"; x2 = "100%"; y2 = "100%";500 }501 502 defsMarkup += `503 <linearGradient id="androidPathGrad" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">504 <stop offset="0%" stop-color="${state.colorGrad1}" />505 <stop offset="100%" stop-color="${state.colorGrad2}" />506 </linearGradient>`;507 fillAttr = "url(#androidPathGrad)";508 }509 510 if (state.hasShadow) {511 defsMarkup += `512 <filter id="pathDropShadow" x="-30%" y="-30%" width="160%" height="160%">513 <feDropShadow dx="${state.shadowX}" dy="${state.shadowY}" stdDeviation="${state.shadowBlur / 2}" flood-color="${state.colorShadow}" flood-opacity="0.8"/>514 </filter>`;515 }516 517 const defsBlock = defsMarkup ? `\n <defs>${defsMarkup}\n </defs>\n` : "";518 let bgRect = "";519 if (state.hasBg) {520 bgRect = `\n <!-- Background Rect -->\n <rect width="100%" height="100%" fill="${state.colorBg}" rx="${state.bgRadius}" ry="${state.bgRadius}" />\n`;521 }522 523 let strokeAttrs = "";524 if (state.hasStroke) {525 strokeAttrs = ` stroke="${state.colorStroke}" stroke-width="${state.strokeWidth}" stroke-linejoin="round" stroke-linecap="round"`;526 }527 528 let filterAttr = "";529 if (state.hasShadow) {530 filterAttr = ` filter="url(#pathDropShadow)"`;531 }532 533 const svgString = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${state.width} ${state.height}" width="${state.width}" height="${state.height}">534 <!-- Converted to Real SVG Path curves via Opentype.js -->${defsBlock}${bgRect}535 <path d="${pathData}"536 fill="${fillAttr}"${strokeAttrs}${filterAttr} />537</svg>`;538 539 return svgString;540 }541 542 function renderSVG() {543 const svgCode = buildSVGString();544 svgOutputWrapper.innerHTML = svgCode;545 codeOutput.value = svgCode;546 547 canvasSizeIndicator.textContent = `${state.width} x ${state.height} px`;548 const bytes = new Blob([svgCode]).size;549 svgFileSize.textContent = bytes > 1024 ? `~${(bytes / 1024).toFixed(1)} KB` : `~${bytes} B`;550 }551 552 function showToast(msg) {553 toastText.textContent = msg;554 toastMessage.classList.remove('translate-y-20', 'opacity-0');555 toastMessage.classList.add('translate-y-0', 'opacity-100');556 setTimeout(() => {557 toastMessage.classList.remove('translate-y-0', 'opacity-100');558 toastMessage.classList.add('translate-y-20', 'opacity-0');559 }, 2500);560 }561 562 inputText.addEventListener('input', (e) => {563 state.text = e.target.value;564 renderSVG();565 });566 567 selectFontPreset.addEventListener('change', (e) => {568 const val = e.target.value;569 state.fontPreset = val;570 571 if (val === 'custom') {572 sectionCustomFont.classList.remove('hidden');573 } else {574 sectionCustomFont.classList.add('hidden');575 loadPresetFont(val);576 }577 });578 579 fileCustomFont.addEventListener('change', (e) => {580 const file = e.target.files[0];581 if (!file) return;582 583 const reader = new FileReader();584 updateStatusUI("loading", "Özel font okunuyor...");585 586 reader.onload = function (evt) {587 try {588 const font = opentype.parse(evt.target.result);589 state.currentFont = font;590 state.isLoadingFont = false;591 updateStatusUI("success", `Özel Font: ${font.names.fontFamily?.en || file.name}`);592 renderSVG();593 } catch (err) {594 console.error(err);595 updateStatusUI("error", "Geçersiz font dosyası!");596 }597 };598 599 reader.readAsArrayBuffer(file);600 });601 602 sliderFontSize.addEventListener('input', (e) => {603 state.fontSize = parseInt(e.target.value, 10);604 valFontSize.textContent = state.fontSize;605 renderSVG();606 });607 608 sliderLineHeight.addEventListener('input', (e) => {609 state.lineHeight = parseFloat(e.target.value);610 valLineHeight.textContent = state.lineHeight;611 renderSVG();612 });613 614 sliderLetterSpacing.addEventListener('input', (e) => {615 state.letterSpacing = parseInt(e.target.value, 10);616 valLetterSpacing.textContent = state.letterSpacing;617 renderSVG();618 });619 620 btnAligns.forEach(btn => {621 btn.addEventListener('click', () => {622 btnAligns.forEach(b => {623 b.classList.remove('bg-emerald-600', 'text-white', 'border-emerald-500');624 b.classList.add('bg-slate-800', 'text-slate-300', 'border-slate-700');625 });626 btn.classList.remove('bg-slate-800', 'text-slate-300', 'border-slate-700');627 btn.classList.add('bg-emerald-600', 'text-white', 'border-emerald-500');628 629 state.textAlign = btn.getAttribute('data-align');630 renderSVG();631 });632 });633 634 tabSolid.addEventListener('click', () => {635 tabSolid.className = "py-1.5 text-xs rounded-lg bg-emerald-600 text-white border border-emerald-500 font-medium";636 tabGradient.className = "py-1.5 text-xs rounded-lg bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700 font-medium";637 sectionSolid.classList.remove('hidden');638 sectionGradient.classList.add('hidden');639 state.fillType = 'solid';640 renderSVG();641 });642 643 tabGradient.addEventListener('click', () => {644 tabGradient.className = "py-1.5 text-xs rounded-lg bg-emerald-600 text-white border border-emerald-500 font-medium";645 tabSolid.className = "py-1.5 text-xs rounded-lg bg-slate-800 text-slate-300 hover:bg-slate-700 border border-slate-700 font-medium";646 sectionGradient.classList.remove('hidden');647 sectionSolid.classList.add('hidden');648 state.fillType = 'gradient';649 renderSVG();650 });651 652 colorText.addEventListener('input', (e) => {653 state.colorText = e.target.value;654 hexText.value = e.target.value;655 renderSVG();656 });657 658 hexText.addEventListener('input', (e) => {659 if (/^#[0-9A-F]{6}$/i.test(e.target.value)) {660 state.colorText = e.target.value;661 colorText.value = e.target.value;662 renderSVG();663 }664 });665 666 colorGrad1.addEventListener('input', (e) => {667 state.colorGrad1 = e.target.value;668 renderSVG();669 });670 671 colorGrad2.addEventListener('input', (e) => {672 state.colorGrad2 = e.target.value;673 renderSVG();674 });675 676 selectGradAngle.addEventListener('change', (e) => {677 state.gradAngle = e.target.value;678 renderSVG();679 });680 681 chkStroke.addEventListener('change', (e) => {682 state.hasStroke = e.target.checked;683 sectionStrokeWidth.classList.toggle('hidden', !e.target.checked);684 renderSVG();685 });686 687 colorStroke.addEventListener('input', (e) => {688 state.colorStroke = e.target.value;689 renderSVG();690 });691 692 sliderStrokeWidth.addEventListener('input', (e) => {693 state.strokeWidth = parseInt(e.target.value, 10);694 valStrokeWidth.textContent = state.strokeWidth;695 renderSVG();696 });697 698 chkShadow.addEventListener('change', (e) => {699 state.hasShadow = e.target.checked;700 sectionShadowControls.classList.toggle('hidden', !e.target.checked);701 renderSVG();702 });703 704 colorShadow.addEventListener('input', (e) => {705 state.colorShadow = e.target.value;706 renderSVG();707 });708 709 sliderShadowBlur.addEventListener('input', (e) => {710 state.shadowBlur = parseInt(e.target.value, 10);711 valShadowBlur.textContent = state.shadowBlur;712 renderSVG();713 });714 715 sliderShadowX.addEventListener('input', (e) => {716 state.shadowX = parseInt(e.target.value, 10);717 valShadowX.textContent = state.shadowX;718 renderSVG();719 });720 721 sliderShadowY.addEventListener('input', (e) => {722 state.shadowY = parseInt(e.target.value, 10);723 valShadowY.textContent = state.shadowY;724 renderSVG();725 });726 727 // Canvas Dimensions728 inputWidth.addEventListener('input', (e) => {729 state.width = Math.max(100, parseInt(e.target.value, 10) || 100);730 renderSVG();731 });732 733 inputHeight.addEventListener('input', (e) => {734 state.height = Math.max(100, parseInt(e.target.value, 10) || 100);735 renderSVG();736 });737 738 chkBg.addEventListener('change', (e) => {739 state.hasBg = e.target.checked;740 wrapperBgColor.classList.toggle('opacity-40', !e.target.checked);741 wrapperBgColor.classList.toggle('pointer-events-none', !e.target.checked);742 renderSVG();743 });744 745 colorBg.addEventListener('input', (e) => {746 state.colorBg = e.target.value;747 renderSVG();748 });749 750 sliderBgRadius.addEventListener('input', (e) => {751 state.bgRadius = Math.max(0, parseInt(e.target.value, 10) || 0);752 renderSVG();753 });754 755 btnViewPreview.addEventListener('click', () => {756 btnViewPreview.className = "px-3 py-1.5 rounded-lg text-xs font-medium bg-emerald-600 text-white shadow-sm flex items-center gap-1.5";757 btnViewCode.className = "px-3 py-1.5 rounded-lg text-xs font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-800 flex items-center gap-1.5";758 containerPreview.classList.remove('hidden');759 containerCode.classList.add('hidden');760 });761 762 btnViewCode.addEventListener('click', () => {763 btnViewCode.className = "px-3 py-1.5 rounded-lg text-xs font-medium bg-emerald-600 text-white shadow-sm flex items-center gap-1.5";764 btnViewPreview.className = "px-3 py-1.5 rounded-lg text-xs font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-800 flex items-center gap-1.5";765 containerCode.classList.remove('hidden');766 containerPreview.classList.add('hidden');767 });768 769 function copyToClipboard(text) {770 const textarea = document.createElement('textarea');771 textarea.value = text;772 textarea.style.position = 'fixed';773 textarea.style.left = '-9999px';774 document.body.appendChild(textarea);775 textarea.select();776 try {777 document.execCommand('copy');778 showToast("SVG Path kodu panoya kopyalandı!");779 } catch (err) {780 showToast("Kopyalama başarısız oldu.");781 }782 document.body.removeChild(textarea);783 }784 785 btnCopyCode.addEventListener('click', () => copyToClipboard(buildSVGString()));786 btnInnerCopy.addEventListener('click', () => copyToClipboard(buildSVGString()));787 788 btnExportSvg.addEventListener('click', () => {789 const svgContent = buildSVGString();790 const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' });791 const url = URL.createObjectURL(blob);792 793 const link = document.createElement('a');794 link.href = url;795 link.download = `android-vector-path-${Date.now()}.svg`;796 document.body.appendChild(link);797 link.click();798 document.body.removeChild(link);799 URL.revokeObjectURL(url);800 801 showToast("SVG dosyası başarıyla indirildi!");802 });803 loadPresetFont('roboto-regular');804 });805 </script>806</body>807</html>