Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Font Glyph Extractor</title>
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet">
- <style>
- /* === SCROLLBAR === */
- ::-webkit-scrollbar { width:10px; height:10px; }
- ::-webkit-scrollbar-track { background:#1a1a1a; }
- ::-webkit-scrollbar-thumb { background:#444; border-radius:5px; }
- ::-webkit-scrollbar-thumb:hover { background:#666; }
- /* === PAGE LAYOUT === */
- html,body{ margin:0; padding:0; background:#111; color:#ddd;
- font-family:'JetBrains Mono', monospace; overflow-y:scroll; }
- h1{ margin:1.2em 0 0.6em; text-align:center; }
- /* === DROP-ZONE === */
- #dropzone{
- width:calc(100vw - 4em); max-width:1600px; margin:0 auto 1.6em;
- padding:2em; box-sizing:border-box;
- border:2px dashed #555; border-radius:8px; background:#1a1a1a;
- text-align:center; cursor:pointer; transition:background .25s;
- }
- #dropzone.hover{ background:#222; }
- #fileInput{ display:none; }
- /* === TOP CONTROLS === */
- .controls{ width:100%; display:flex; justify-content:center; gap:1em; margin-bottom:1em; }
- .controls button, .copy-btn{
- background:#444; color:#fff; border:none; border-radius:4px;
- padding:0.6em 1.2em; font-size:1em; cursor:pointer; transition:background .25s;
- }
- .controls button:hover, .copy-btn:hover{ background:#666; }
- /* === OUTPUT AREA === */
- #outputs{ width:100%; }
- .output-box{
- position:relative; width:calc(100vw - 4em); max-width:1600px;
- margin:0.8em auto; padding:1em 2em 2em; box-sizing:border-box;
- background:#222; border:1px solid #333; border-radius:8px;
- }
- .output-box.dragging{ opacity:0.35; }
- .output-box.dragover{ outline:2px dashed #888; }
- .output-box pre{ margin:0; overflow-x:auto; white-space:pre; }
- .copy-btn{
- position:absolute; top:8px; right:8px;
- padding:0.35em 0.7em; font-size:0.85em;
- }
- </style>
- </head>
- <body>
- <h1>Font Glyph Extractor</h1>
- <div id="dropzone">
- <p>Drop <code>.ttf</code> or <code>.svg</code> files (multi-select OK) or click to browse</p>
- <input type="file" id="fileInput" multiple>
- </div>
- <div class="controls">
- <button id="mergeAllBtn">Merge All</button>
- <button id="clearBtn">Clear All</button>
- </div>
- <div id="outputs"></div>
- <script src="https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js"></script>
- <script>
- /* ───────────────────────────────────────────────────────── helpers */
- const INDENT = ' ';
- /* full CSS-3/SVG colour keyword list → rgb triplets */
- const NAMED = {
- aliceblue:'#f0f8ff', antiquewhite:'#faebd7', aqua:'#00ffff', aquamarine:'#7fffd4',
- azure:'#f0ffff', beige:'#f5f5dc', bisque:'#ffe4c4', black:'#000000',
- blanchedalmond:'#ffebcd', blue:'#0000ff', blueviolet:'#8a2be2', brown:'#a52a2a',
- burlywood:'#deb887', cadetblue:'#5f9ea0', chartreuse:'#7fff00', chocolate:'#d2691e',
- coral:'#ff7f50', cornflowerblue:'#6495ed', cornsilk:'#fff8dc', crimson:'#dc143c',
- cyan:'#00ffff', darkblue:'#00008b', darkcyan:'#008b8b', darkgoldenrod:'#b8860b',
- darkgray:'#a9a9a9', darkgreen:'#006400', darkgrey:'#a9a9a9', darkkhaki:'#bdb76b',
- darkmagenta:'#8b008b', darkolivegreen:'#556b2f', darkorange:'#ff8c00',
- darkorchid:'#9932cc', darkred:'#8b0000', darksalmon:'#e9967a',
- darkseagreen:'#8fbc8f', darkslateblue:'#483d8b', darkslategray:'#2f4f4f',
- darkslategrey:'#2f4f4f', darkturquoise:'#00ced1', darkviolet:'#9400d3',
- deeppink:'#ff1493', deepskyblue:'#00bfff', dimgray:'#696969', dimgrey:'#696969',
- dodgerblue:'#1e90ff', firebrick:'#b22222', floralwhite:'#fffaf0', forestgreen:'#228b22',
- fuchsia:'#ff00ff', gainsboro:'#dcdcdc', ghostwhite:'#f8f8ff', gold:'#ffd700',
- goldenrod:'#daa520', gray:'#808080', green:'#008000', greenyellow:'#adff2f',
- grey:'#808080', honeydew:'#f0fff0', hotpink:'#ff69b4', indianred:'#cd5c5c',
- indigo:'#4b0082', ivory:'#fffff0', khaki:'#f0e68c', lavender:'#e6e6fa',
- lavenderblush:'#fff0f5', lawngreen:'#7cfc00', lemonchiffon:'#fffacd',
- lightblue:'#add8e6', lightcoral:'#f08080', lightcyan:'#e0ffff',
- lightgoldenrodyellow:'#fafad2', lightgray:'#d3d3d3', lightgreen:'#90ee90',
- lightgrey:'#d3d3d3', lightpink:'#ffb6c1', lightsalmon:'#ffa07a',
- lightseagreen:'#20b2aa', lightskyblue:'#87cefa', lightslategray:'#778899',
- lightslategrey:'#778899', lightsteelblue:'#b0c4de', lightyellow:'#ffffe0',
- lime:'#00ff00', limegreen:'#32cd32', linen:'#faf0e6', magenta:'#ff00ff',
- maroon:'#800000', mediumaquamarine:'#66cdaa', mediumblue:'#0000cd',
- mediumorchid:'#ba55d3', mediumpurple:'#9370db', mediumseagreen:'#3cb371',
- mediumslateblue:'#7b68ee', mediumspringgreen:'#00fa9a',
- mediumturquoise:'#48d1cc', mediumvioletred:'#c71585', midnightblue:'#191970',
- mintcream:'#f5fffa', mistyrose:'#ffe4e1', moccasin:'#ffe4b5', navajowhite:'#ffdead',
- navy:'#000080', oldlace:'#fdf5e6', olive:'#808000', olivedrab:'#6b8e23',
- orange:'#ffa500', orangered:'#ff4500', orchid:'#da70d6', palegoldenrod:'#eee8aa',
- palegreen:'#98fb98', paleturquoise:'#afeeee', palevioletred:'#db7093',
- papayawhip:'#ffefd5', peachpuff:'#ffdab9', peru:'#cd853f', pink:'#ffc0cb',
- plum:'#dda0dd', powderblue:'#b0e0e6', purple:'#800080', rebeccapurple:'#663399',
- red:'#ff0000', rosybrown:'#bc8f8f', royalblue:'#4169e1', saddlebrown:'#8b4513',
- salmon:'#fa8072', sandybrown:'#f4a460', seagreen:'#2e8b57', seashell:'#fff5ee',
- sienna:'#a0522d', silver:'#c0c0c0', skyblue:'#87ceeb', slateblue:'#6a5acd',
- slategray:'#708090', slategrey:'#708090', snow:'#fffafa', springgreen:'#00ff7f',
- steelblue:'#4682b4', tan:'#d2b48c', teal:'#008080', thistle:'#d8bfd8',
- tomato:'#ff6347', turquoise:'#40e0d0', violet:'#ee82ee', wheat:'#f5deb3',
- white:'#ffffff', whitesmoke:'#f5f5f5', yellow:'#ffff00', yellowgreen:'#9acd32'
- };
- function hexToRgb(h){
- if(h.length===4) h='#'+[h[1],h[1],h[2],h[2],h[3],h[3]].join('');
- const n=parseInt(h.slice(1),16);
- return`rgb(${(n>>16)&255},${(n>>8)&255},${n&255})`;
- }
- function cssToRGB(str){
- if(!str) return 'rgb(unknown)';
- str=str.trim().toLowerCase();
- if(str.startsWith('#')) return hexToRgb(str);
- if(str.startsWith('rgb')) return str.replace(/\s+/g,''); // keep as-is
- if(NAMED[str]) return hexToRgb(NAMED[str]);
- return 'rgb(unknown)'; // unsupported pattern e.g. currentColor
- }
- /* climb element → root for fill attr / inline style */
- function findFill(el){
- while(el && el.nodeType===1){
- let f=el.getAttribute('fill');
- if(f && f!=='none') return f;
- const style=el.getAttribute('style');
- if(style){
- const m=style.match(/fill\s*:\s*([^;]+)/i);
- if(m && m[1] && m[1]!=='none') return m[1];
- }
- el=el.parentElement;
- }
- return null;
- }
- /* ─────────────────────────────────────────────── DOM shortcuts */
- const dropzone=document.getElementById('dropzone');
- const fileInput=document.getElementById('fileInput');
- const outputs=document.getElementById('outputs');
- /* ────────────────────────────── SVG → single-entry C-array */
- function svgToEntry(txt,name){
- const key=name.replace(/\.[^/.]+$/,'');
- const doc=new DOMParser().parseFromString(txt,'image/svg+xml');
- const parts=[];
- doc.querySelectorAll('path').forEach(p=>{
- const raw=findFill(p) || 'black';
- const col=cssToRGB(raw);
- parts.push(`${p.getAttribute('d')?esc(p.getAttribute('d')):''}[{${col}}]`);
- });
- return `${INDENT}{"${key}", "${parts.join('')}"}`;
- }
- /* ────────────────────────────── TTF → full C-array */
- function ttfToArray(buf){
- const font=opentype.parse(buf);
- const upm=font.unitsPerEm||1000;
- const scale=1024/upm;
- const seen=new Set(),lines=[];
- for(const codeStr of Object.keys(font.tables.cmap.glyphIndexMap)){
- const code=parseInt(codeStr);
- if(seen.has(code)) continue;
- seen.add(code);
- const ch=String.fromCodePoint(code);
- const glyph=font.charToGlyph(ch);
- const path=glyph.getPath(0,0,upm*scale);
- lines.push(`${INDENT}{"${esc(ch)}", "${esc(path.toPathData())}[{rgb(unknown)}]"}`);
- }
- return `{\n${lines.join(',\n')}\n}`;
- }
- /* ────────────────────────────── UI: build output box */
- function addBox(text){
- const box=document.createElement('div');
- box.className='output-box'; box.setAttribute('draggable',true);
- const pre=document.createElement('pre'); pre.textContent=text; box.appendChild(pre);
- const btn=document.createElement('button');
- btn.className='copy-btn'; btn.textContent='Copy';
- btn.onclick=(()=>{ let t; return ()=>{
- navigator.clipboard.writeText(pre.textContent);
- clearTimeout(t); btn.textContent='Copied!';
- t=setTimeout(()=>btn.textContent='Copy',600);
- };})();
- box.appendChild(btn);
- outputs.appendChild(box);
- /* drag-merge */
- box.addEventListener('dragstart',e=>{
- e.dataTransfer.setData('text/plain',pre.textContent);
- box.classList.add('dragging');
- });
- box.addEventListener('dragend',()=>box.classList.remove('dragging'));
- box.addEventListener('dragover',e=>{e.preventDefault();box.classList.add('dragover');});
- box.addEventListener('dragleave',()=>box.classList.remove('dragover'));
- box.addEventListener('drop',e=>{
- e.preventDefault(); box.classList.remove('dragover');
- const other=e.dataTransfer.getData('text/plain');
- if(other && other!==pre.textContent){
- pre.textContent=mergeArrays(pre.textContent,other);
- document.querySelectorAll('.output-box.dragging').forEach(b=>b.remove());
- }
- });
- }
- /* ────────────────────────────── merge helpers */
- const stripLines=s=>s.replace(/^\s*\{|\}\s*$/g,'')
- .split(/\n/)
- .map(l=>l.trim().replace(/,$/,''))
- .filter(Boolean);
- function mergeArrays(a,b){
- const seen=new Set(),out=[];
- [...stripLines(a),...stripLines(b)].forEach(l=>{
- if(!seen.has(l)){seen.add(l);out.push(l);}
- });
- return '{\n'+out.map(l=>INDENT+l).join(',\n')+'\n}';
- }
- function mergeAll(){
- const pres=[...document.querySelectorAll('.output-box pre')];
- if(pres.length<2) return;
- let merged=pres[0].textContent;
- for(let i=1;i<pres.length;i++) merged=mergeArrays(merged,pres[i].textContent);
- outputs.innerHTML=''; addBox(merged);
- }
- /* ────────────────────────────── file handlers */
- function esc(s){ return s.replace(/\\/g,'\\\\').replace(/"/g,'\\"'); }
- function handleFile(f){
- const r=new FileReader();
- r.onload=()=>{
- if(f.name.endsWith('.svg')) addBox('{\n'+svgToEntry(r.result,f.name)+'\n}');
- else if(f.name.endsWith('.ttf')) addBox(ttfToArray(r.result));
- else addBox('Unsupported file: '+f.name);
- };
- f.name.endsWith('.svg') ? r.readAsText(f) : r.readAsArrayBuffer(f);
- }
- /* ────────────────────────────── UI wiring */
- dropzone.addEventListener('click',()=>fileInput.click());
- dropzone.addEventListener('dragover',e=>{e.preventDefault();dropzone.classList.add('hover');});
- ['dragleave','drop'].forEach(ev=>dropzone.addEventListener(ev,()=>dropzone.classList.remove('hover')));
- dropzone.addEventListener('drop',e=>{
- e.preventDefault();
- [...e.dataTransfer.files].forEach(handleFile);
- });
- fileInput.addEventListener('change',e=>[...e.target.files].forEach(handleFile));
- document.getElementById('mergeAllBtn').addEventListener('click',mergeAll);
- document.getElementById('clearBtn').addEventListener('click',()=>outputs.innerHTML='');
- </script>
- </body>
- </html>
Add Comment
Please, Sign In to add comment