feat: vsetky sablony cenoviek z novych SVG (zakladne, appkaci, vernostna karta)

- 22 sablon: Super cena / Super vyhodne / Zakladna v 3 farbach so SK aj bez,
  Appkaci (SK / bez), Vernostna karta (so zlavou / bez); orezane na viditelny
  obsah, rovnaka mierka (sirka = viewBox x 130 / 80,1)
- nove premenne {zlava_app}, {jednotkova_cena_app}, {cena_bez_karty},
  {zlava_bez_karty}, {jednotkova_cena_s_kartou}, {jednotkova_cena_bez_karty};
  zvisle natiahnutie textu (scaleY), viac nazvov pisma, napis zlava len pri zlave
- Vernostna karta je predvolena cenovka sekcie Vernostne zlavy
- produkty pri nacitani dostanu aktualnu verziu svojej sablony
- import: zlava pre appkacov a s vernostnou kartou z poznamky, jednotkova cena
  pre appkacov prepocitana, sirka cenovky podla sablony
- okraj stitkov predajni podla mierky cenovky
- postup prevodu v scripts/badges

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-22 10:36:37 +02:00
1 parent 50af94f954
commit af35334688
13 files changed
+300 -107

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
# Šablóny cenoviek z Illustratora
1. **Prevod SVG** – `python scripts/badges/convert-illustrator-svg.py <priečinok so SVG> public/__svg/conv.json`
(vyžaduje `pip install fonttools` a nainštalovaný Gilroy-Heavy; súbory a ich tagy sú v `ASSETS`).
Statické nápisy v Gilroy sa prevedú na krivky, texty v Aptly / Korolev sa vyberú na vrstvy, triedy a id
dostanú jedinečnú predponu.
2. **Meranie v prehliadači** – skopírovať SVG a `measure.js` do `public/__svg/`, otvoriť stránku na dev serveri
a zavolať `await measureBadges()`: orezanie na viditeľný obsah (bez priehľadného okraja) a presné polohy
textov s písmom Aptly z Adobe Fonts. Z výsledku vznikne `layers.json` (cena + centy → `{cena…}`,
„%“ → `{zlava…}`, jednotková cena → `{jednotkova_cena…}`, ostatné texty ostanú pevné).
3. **Zostavenie** – `npx tsx --tsconfig tsconfig.json scripts/badges/build-templates.ts` → `public/__svg/rows.json`
a `compare.html` (porovnanie s originálmi), potom zápis riadkov do tabuľky `badges` (podľa názvu: existujúce
sa aktualizujú, nové pribudnú).
Šírka šablóny v px = šírka orezaného viewBoxu × 130 / 80,1, takže všetky cenovky majú rovnakú mierku.
+48
View File
@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-explicit-any -- pomocný skript nad JSON výstupmi z prevodu */
// Zostavenie šablón cenoviek z výstupu convert-illustrator-svg.py (public/__svg/conv.json) a vrstiev
// zmeraných v prehliadači (scripts/badges/layers.json, vytvára measure.js). Zapíše public/__svg/rows.json
// (riadky tabuľky badges) a public/__svg/compare.html (porovnanie s originálmi).
// Spustenie: npx tsx --tsconfig tsconfig.json scripts/badges/build-templates.ts [filter názvu] [mierka]
import fs from 'fs';
import { renderCleanBadgeSvg, badgeFromRow } from '../../src/lib/badgeUtils';
const conv = JSON.parse(fs.readFileSync('public/__svg/conv.json', 'utf8'));
const L = JSON.parse(fs.readFileSync('scripts/badges/layers.json', 'utf8'));
const rows = L.map(([file, name, tags, crop, width, layers]: any) => {
const c = conv.find((x: any) => x.file === file);
return {
file, name,
svg_raw: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="${crop.join(' ')}">${c.inner}</svg>`,
layers_config: {
width, align: 'center', paddingTop: 4, paddingBottom: 4, importTags: tags,
layers: layers.map(([text, al, x, y, fontSize, fill, font, ifs, ioy, iox, ls, sy]: any, i: number) => ({
id: `l-${c.key}-${i}`, type: 'text', text, align: { c: 'center', l: 'left', r: 'right' }[al as 'c'], x, y, fontSize,
fill: fill === 'w' ? '#ffffff' : fill,
fontFamily: font === 'A' ? 'Aptly' : 'korolev-condensed, Korolev Condensed', fontSource: 'adobe', fontWeight: font === 'A' ? '700' : font.slice(1),
...(ifs !== '' ? { indexFontSize: ifs, indexOffsetY: ioy, indexOffsetX: iox, longPriceScale: 0.8 } : {}),
...(ls !== '' ? { letterSpacing: ls } : {}), ...(sy !== '' ? { scaleY: sy } : {})
})).map((l: any, _i: number, all: any[]) => {
// nápis „zľava“ len pri vyplnenej zľave – patrí k najbližšej premennej {zlava…}
if (l.text !== 'zľava') return l;
const d = all.filter((x: any) => /^\{zlava/.test(x.text)).sort((a: any, b: any) => Math.abs(a.y - l.y) - Math.abs(b.y - l.y))[0];
return d ? { ...l, showIfFilled: d.text } : l;
})
}
};
});
fs.writeFileSync('public/__svg/rows.json', JSON.stringify(rows));
const sample = { priceMain: '0', priceSub: '00', discountText: '00%', unitPrice: 'jednotková cena 000 EUR/kg',
appPriceMain: '0', appPriceSub: '00', appDiscount: '00%', appUnitPrice: 'jednotková cena 000 EUR/kg',
cardPriceMain: '0', cardPriceSub: '00', cardDiscount: '00%', cardUnitPrice: 'jednotková cena 000 EUR/kg',
noCardPriceMain: '0', noCardPriceSub: '00', noCardDiscount: '00%', noCardUnitPrice: 'jednotková cena 000 EUR/kg' };
const only = process.argv[2] ? new RegExp(process.argv[2]) : null;
const scale = Number(process.argv[3] || 1);
let html = '<!doctype html><html><head><link rel="stylesheet" href="https://use.typekit.net/loe7muj.css"></head><body style="background:#333;color:#fff;font:11px sans-serif;margin:8px"><div style="display:flex;flex-wrap:wrap;gap:12px">';
for (const r of rows) {
if (only && !only.test(r.name)) continue;
const b = badgeFromRow({ id: r.file, name: r.name, svg_raw: r.svg_raw, layers_config: r.layers_config });
const [x, y, w, h] = r.svg_raw.match(/viewBox="([^"]+)"/)![1].split(' ').map(Number);
const px = Math.round(r.layers_config.width * scale);
html += `<figure style="margin:0"><div style="display:flex;gap:4px;align-items:flex-start"><img src="/__svg/${r.file.replace(' ', '_')}#svgView(viewBox(${[x, y, w, h].join(',')}))" width="${px}" height="${Math.round(px * h / w)}" style="background:#555"><div style="width:${px}px;background:#555">${renderCleanBadgeSvg(b, sample as any, px)}</div></div><figcaption>${r.name} · ${r.layers_config.width}px</figcaption></figure>`;
}
fs.writeFileSync('public/__svg/compare.html', html + '</div></body></html>');
console.log(rows.length, 'ok');
+60 -67
View File
@@ -1,12 +1,12 @@
# Prevod testovacích SVG cenoviek z Illustratora na pozadia šablón:
# - ceny, zľava % a „zľava“ sa odstránia (budú z nich vrstvy editora),
# - statické nápisy (SUPER / CENA / VÝHODNÉ, písmo Gilroy-Heavy) sa prevedú na krivky,
# - triedy a id dostanú jedinečnú predponu (inak by sa viaceré cenovky na stránke prebíjali),
# - obsah sa posunie tak, aby telo cenovky bolo vo všetkých šablónach na rovnakom mieste.
# Prevod SVG cenoviek z Illustratora na pozadia šablón cenoviek:
# - texty v písmach Aptly / Korolev (ceny, zľavy, „zľava“, jednotkové ceny, popisy) sa z pozadia odstránia
# a zapíšu sa do výstupu – vznikne z nich vrstva editora (premenná alebo pevný text),
# - statické nápisy v písme Gilroy (SUPER / CENA / VÝHODNÉ) sa prevedú na krivky (Gilroy je nainštalovaný),
# - triedy a id dostanú jedinečnú predponu (inak by sa viaceré cenovky na jednej stránke prebíjali).
# Orezanie na viditeľný obsah (bez priehľadného okraja) a meranie polôh textov sa robí v prehliadači
# (scripts/badges/README.md), lebo potrebuje vykreslenie s písmom Aptly.
#
# Použitie: pip install fonttools; python convert-illustrator-svg.py <priečinok so SVG> <výstup.json>
# (Gilroy-Heavy musí byť nainštalovaný; priradenie súborov k typu/farbe/pôvodu je v ASSETS nižšie.)
# Výstup obsahuje vyčistené SVG a namerané polohy textov – z nich sa vyrobili šablóny v tabuľke badges.
import json
import re
import sys
@@ -24,20 +24,30 @@ glyphs = font.getGlyphSet()
cmap = font.getBestCmap()
upem = font['head'].unitsPerEm
# referenčná poloha tela cenovky (zhora ľavý roh rámika s cenou)
REF_X = 27.98
# súbor → (názov šablóny, tagy pre import)
ASSETS = {
'Asset 2.svg': ('SUPER CENA', 'cervena', 'SK'),
'Asset 3.svg': ('SUPER VÝHODNÉ', 'cervena', 'SK'),
'Asset 4.svg': ('SUPER CENA', 'oranzova', 'SK'),
'Asset 5.svg': ('SUPER CENA', 'zelena', 'SK'),
'Asset 6.svg': ('SUPER CENA', 'cervena', ''),
'Asset 7.svg': ('SUPER VÝHODNÉ', 'cervena', ''),
'Asset 8.svg': ('SUPER CENA', 'oranzova', ''),
'Asset 9.svg': ('SUPER VÝHODNÉ', 'zelena', ''),
'Asset 10.svg': ('SUPER VÝHODNÉ', 'oranzova', 'SK'),
'Asset 11.svg': ('SUPER VÝHODNÉ', 'oranzova', ''),
'Asset 12.svg': ('Super cena | Červená | SK', {'type': 'SUPER CENA', 'color': 'cervena', 'origin': 'SK'}),
'Asset 18.svg': ('Super cena | Červená', {'type': 'SUPER CENA', 'color': 'cervena', 'origin': ''}),
'Asset 14.svg': ('Super cena | Oranžová | SK', {'type': 'SUPER CENA', 'color': 'oranzova', 'origin': 'SK'}),
'Asset 20.svg': ('Super cena | Oranžová', {'type': 'SUPER CENA', 'color': 'oranzova', 'origin': ''}),
'Asset 16.svg': ('Super cena | Zelená | SK', {'type': 'SUPER CENA', 'color': 'zelena', 'origin': 'SK'}),
'Asset 22.svg': ('Super cena | Zelená', {'type': 'SUPER CENA', 'color': 'zelena', 'origin': ''}),
'Asset 13.svg': ('Super výhodné | Červená | SK', {'type': 'SUPER VÝHODNÉ', 'color': 'cervena', 'origin': 'SK'}),
'Asset 19.svg': ('Super výhodné | Červená', {'type': 'SUPER VÝHODNÉ', 'color': 'cervena', 'origin': ''}),
'Asset 15.svg': ('Super výhodné | Oranžová | SK', {'type': 'SUPER VÝHODNÉ', 'color': 'oranzova', 'origin': 'SK'}),
'Asset 21.svg': ('Super výhodné | Oranžová', {'type': 'SUPER VÝHODNÉ', 'color': 'oranzova', 'origin': ''}),
'Asset 17.svg': ('Super výhodné | Zelená | SK', {'type': 'SUPER VÝHODNÉ', 'color': 'zelena', 'origin': 'SK'}),
'Asset 23.svg': ('Super výhodné | Zelená', {'type': 'SUPER VÝHODNÉ', 'color': 'zelena', 'origin': ''}),
'Asset 27.svg': ('Základná | Červená | SK', {'type': 'ZÁKLADNÁ', 'color': 'cervena', 'origin': 'SK'}),
'Asset 28.svg': ('Základná | Červená', {'type': 'ZÁKLADNÁ', 'color': 'cervena', 'origin': ''}),
'Asset 30.svg': ('Základná | Oranžová | SK', {'type': 'ZÁKLADNÁ', 'color': 'oranzova', 'origin': 'SK'}),
'Asset 29.svg': ('Základná | Oranžová', {'type': 'ZÁKLADNÁ', 'color': 'oranzova', 'origin': ''}),
'Asset 32.svg': ('Základná | Zelená | SK', {'type': 'ZÁKLADNÁ', 'color': 'zelena', 'origin': 'SK'}),
'Asset 31.svg': ('Základná | Zelená', {'type': 'ZÁKLADNÁ', 'color': 'zelena', 'origin': ''}),
'Asset 33.svg': ('Appkáči | SK', {'type': 'ZÁKLADNÁ', 'origin': 'SK', 'app': True}),
'Asset 36.svg': ('Appkáči', {'type': 'ZÁKLADNÁ', 'origin': '', 'app': True}),
'Asset 34.svg': ('Vernostná karta', {'loyalty': True}),
'Asset 35.svg': ('Vernostná karta | bez zľavy', {'loyalty': True}),
}
@@ -45,9 +55,7 @@ def parse_css(svg):
style = re.search(r'<style>(.*?)</style>', svg, re.S).group(1)
props = {}
for sel, body in re.findall(r'([^{}]+)\{([^}]*)\}', style):
decl = dict(
(k.strip(), v.strip()) for k, v in (d.split(':', 1) for d in body.split(';') if ':' in d)
)
decl = dict((k.strip(), v.strip()) for k, v in (d.split(':', 1) for d in body.split(';') if ':' in d))
for cls in sel.split(','):
props.setdefault(cls.strip().lstrip('.'), {}).update(decl)
return props
@@ -70,70 +78,55 @@ def text_to_path(transform, runs, size, fill):
result = []
for fname, (btype, color, origin) in ASSETS.items():
for fname, (name, tags) in ASSETS.items():
svg = open(f'{SRC}/{fname}', encoding='utf-8').read()
key = 'b' + re.sub(r'\D', '', fname)
key = 'k' + re.sub(r'\D', '', fname)
css = parse_css(svg)
texts = []
texts = {} # rola → údaje
def replace_text(m):
classes = m.group(1).split()
transform = m.group(2)
body = m.group(3)
p = {}
for c in classes:
for c in m.group(1).split():
p.update(css.get(c, {}))
transform, body = m.group(2), m.group(3)
size = float(p.get('font-size', '0').replace('px', ''))
family = p.get('font-family', '')
fill = p.get('fill', '#000')
runs = []
for tcls, x, txt in re.findall(r'<tspan(?: class="([^"]*)")? x="([\d.\-]+)" y="[\d.\-]+">([^<]*)</tspan>', body):
ls = css.get(tcls, {}).get('letter-spacing', '0em').replace('em', '') if tcls else '0'
runs.append((float(x), txt, float(ls or 0)))
if not runs:
runs = [(0.0, re.sub(r'<[^>]+>', '', body), 0.0)]
content = ''.join(r[1] for r in runs)
tr = re.match(r'translate\(([\d.\-]+) ([\d.\-]+)\)', transform)
tx, ty = float(tr.group(1)), float(tr.group(2))
if not content.strip():
return ''
if 'Gilroy' in family:
return text_to_path(transform, runs, size, fill)
# Aptly: ceny, zľava % a „zľava“ → vrstvy editora
if '%' in content:
role = 'discount'
elif content.isdigit() and size >= 25:
role = 'main'
elif content.isdigit():
role = 'cents'
else:
role = 'label'
texts[role] = {'text': content, 'x': tx, 'y': ty, 'size': size, 'fill': fill, 'runs': runs}
return text_to_path(transform, runs, size, p.get('fill', '#000'))
tr = re.match(r'translate\(([\d.\-]+) ([\d.\-]+)\)(?: scale\(([\d.]+) ([\d.]+)\))?', transform)
texts.append({
'text': content, 'x': float(tr.group(1)), 'y': float(tr.group(2)),
'scaleY': float(tr.group(4)) if tr.group(4) else 1,
'size': size, 'fill': p.get('fill', '#000'), 'family': family,
'weight': p.get('font-weight', ''), 'transform': transform, 'markup': m.group(0),
})
return ''
svg = re.sub(r'<text class="([^"]+)" transform="([^"]+)">(.*?)</text>', replace_text, svg, flags=re.S)
clean = re.sub(r'<text class="([^"]+)" transform="([^"]+)">(.*?)</text>', replace_text, svg, flags=re.S)
original = svg
# posun obsahu: rámik s cenou vždy na REF_X a jeho horná hrana na rovnakej výške v rámci druhu cenovky
rects = re.findall(r'<rect class="(cls-\d+)" x="([\d.]+)" y="([\d.]+)" width="48.53" height="41.94"/>', svg)
box_x, box_y = float(rects[0][1]), float(rects[0][2])
dx = REF_X - box_x
dy = (92.67 if btype == 'SUPER CENA' else 44.98) - box_y
# jedinečné triedy a id (vo vyčistenom aj v pôvodnom SVG – pôvodné slúži na meranie textov)
def uniq(s):
s = re.sub(r'\bcls-(\d+)\b', rf'{key}-c\1', s)
for ident in set(re.findall(r'id="([^"]+)"', s)):
s = re.sub(rf'(id="|url\(#|xlink:href="#){re.escape(ident)}(["\)])', rf'\1{key}-{ident}\2', s)
return s
# jedinečné triedy a id
svg = re.sub(r'\bcls-(\d+)\b', rf'{key}-c\1', svg)
for ident in set(re.findall(r'id="([^"]+)"', svg)):
svg = re.sub(rf'(id="|url\(#|xlink:href="#){re.escape(ident)}(["\)])', rf'\1{key}-{ident}\2', svg)
inner = re.sub(r'^.*?<svg[^>]*>', '', svg, flags=re.S)
inner = re.sub(r'</svg>\s*$', '', inner)
defs = re.search(r'<defs>.*?</defs>', inner, re.S).group(0)
body = inner.replace(defs, '')
clean, original = uniq(clean), uniq(original)
inner = re.sub(r'</svg>\s*$', '', re.sub(r'^.*?<svg[^>]*>', '', clean, flags=re.S))
vb = [float(v) for v in re.search(r'viewBox="([^"]+)"', svg).group(1).split()]
result.append({'file': fname, 'key': key, 'name': name, 'tags': tags, 'vb': vb, 'texts': texts,
'inner': inner.strip(), 'original': original})
result.append({
'file': fname, 'key': key, 'type': btype, 'color': color, 'origin': origin,
'dx': dx, 'dy': dy, 'boxY': box_y, 'vb': vb, 'texts': texts,
'svg': f'{defs}<g transform="translate({dx:.2f} {dy:.2f})">{body.strip()}</g>',
})
json.dump(result, open(OUT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
json.dump(result, open(OUT, 'w', encoding='utf-8'), ensure_ascii=False)
for r in result:
print(r['file'], r['type'], r['color'], r['origin'], 'dx', round(r['dx'], 2), 'dy', round(r['dy'], 2), 'boxY', r['boxY'], 'texts', {k: v['text'] for k, v in r['texts'].items()}, len(r['svg']))
print(r['file'], r['name'], [t['text'] for t in r['texts']])
+1
View File
@@ -0,0 +1 @@
[["Asset 12.svg","Super cena | Červená | SK",{"type":"SUPER CENA","color":"cervena","origin":"SK"},[11.6,41.9,79.7,104.2],129,[["{cena}","c",52.26,115.4,38,"w","A",22.15,-5.66,-0.3,"",""],["{zlava}","r",75.09,53.3,14.5,"w","A","","","","",""],["zľava","l",30.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 18.svg","Super cena | Červená",{"type":"SUPER CENA","color":"cervena","origin":""},[11.6,41.9,79.7,95.6],129,[["{cena}","c",52.23,115.4,38,"w","A",22.15,-5.66,-0.23,"",""],["{zlava}","r",75.11,53.3,14.5,"w","A","","","","",""],["zľava","l",30.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 14.svg","Super cena | Oranžová | SK",{"type":"SUPER CENA","color":"oranzova","origin":"SK"},[11.6,41.9,79.7,104.2],129,[["{cena}","c",52.25,115.4,38,"w","A",22.15,-5.66,-0.02,"",""],["{zlava}","r",75.09,53.3,14.5,"w","A","","","","",""],["zľava","l",30.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 20.svg","Super cena | Oranžová",{"type":"SUPER CENA","color":"oranzova","origin":""},[11.6,41.9,79.7,95.8],129,[["{cena}","c",52.26,115.4,38,"w","A",22.15,-5.66,-0.3,"",""],["{zlava}","r",75.1,53.3,14.5,"w","A","","","","",""],["zľava","l",30.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 16.svg","Super cena | Zelená | SK",{"type":"SUPER CENA","color":"zelena","origin":"SK"},[19.6,41.9,79.7,104.1],129,[["{cena}","c",60.26,115.4,38,"w","A",22.15,-5.66,-0.3,"",""],["{zlava}","r",83.09,53.3,14.5,"w","A","","","","",""],["zľava","l",38.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 22.svg","Super cena | Zelená",{"type":"SUPER CENA","color":"zelena","origin":""},[11.6,41.9,79.7,95.7],129,[["{cena}","c",52.25,115.4,38,"w","A",22.15,-5.66,-0.33,"",""],["{zlava}","r",75.1,53.3,14.5,"w","A","","","","",""],["zľava","l",30.17,56.57,8,"w","A","","","",-0.03,""]]],["Asset 13.svg","Super výhodné | Červená | SK",{"type":"SUPER VÝHODNÉ","color":"cervena","origin":"SK"},[11.6,11.5,79.7,86.7],129,[["{cena}","c",52.25,67.7,38,"w","A",22.15,-5.65,-0.33,"",""]]],["Asset 19.svg","Super výhodné | Červená",{"type":"SUPER VÝHODNÉ","color":"cervena","origin":""},[3.6,4.5,79.7,78.2],129,[["{cena}","c",44.23,60.7,38,"w","A",22.15,-5.65,-0.23,"",""]]],["Asset 15.svg","Super výhodné | Oranžová | SK",{"type":"SUPER VÝHODNÉ","color":"oranzova","origin":"SK"},[11.6,11.5,79.7,86.6],129,[["{cena}","c",52.26,67.7,38,"w","A",22.15,-5.65,-0.27,"",""]]],["Asset 21.svg","Super výhodné | Oranžová",{"type":"SUPER VÝHODNÉ","color":"oranzova","origin":""},[3.6,4.5,79.7,78.3],129,[["{cena}","c",44.24,60.7,38,"w","A",22.15,-5.65,-0.13,"",""]]],["Asset 17.svg","Super výhodné | Zelená | SK",{"type":"SUPER VÝHODNÉ","color":"zelena","origin":"SK"},[11.6,11.5,79.7,86.9],129,[["{cena}","c",52.25,67.7,38,"w","A",22.15,-5.65,-0.33,"",""]]],["Asset 23.svg","Super výhodné | Zelená",{"type":"SUPER VÝHODNÉ","color":"zelena","origin":""},[3.6,4.5,79.7,78.6],129,[["{cena}","c",44.25,60.7,38,"w","A",22.15,-5.65,-0.3,"",""]]],["Asset 27.svg","Základná | Červená | SK",{"type":"ZÁKLADNÁ","color":"cervena","origin":"SK"},[16.7,34.9,54.5,68.2],88,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 28.svg","Základná | Červená",{"type":"ZÁKLADNÁ","color":"cervena","origin":""},[16.7,34.9,54.8,59.5],89,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 30.svg","Základná | Oranžová | SK",{"type":"ZÁKLADNÁ","color":"oranzova","origin":"SK"},[16.7,34.9,54.6,68.2],89,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 29.svg","Základná | Oranžová",{"type":"ZÁKLADNÁ","color":"oranzova","origin":""},[16.7,34.9,54.7,59.4],89,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 32.svg","Základná | Zelená | SK",{"type":"ZÁKLADNÁ","color":"zelena","origin":"SK"},[16.7,34.7,54.6,68.4],89,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 31.svg","Základná | Zelená",{"type":"ZÁKLADNÁ","color":"zelena","origin":""},[16.7,34.7,54.8,59.8],89,[["{cena}","c",44.23,72.56,38,"w","A",22.15,-5.65,-0.33,"",""],["{zlava}","r",67.05,46.29,14.5,"#1d1d1b","A","","","","",""],["zľava","l",22.15,49.55,8,"#1d1d1b","A","","","",-0.03,""]]],["Asset 33.svg","Appkáči | SK",{"type":"ZÁKLADNÁ","origin":"SK","app":true},[4.8,1,136.4,100.5],221,[["{cena_app}","l",34.25,22.47,46,"w","A",26.82,-7.31,-0.49,"",""],["{cena}","l",52.46,7Line truncated
+57
View File
@@ -0,0 +1,57 @@
// Meranie šablón cenoviek v prehliadači (dočasný súbor): orezanie na viditeľný obsah a vrstvy z textov.
window.measureBadges = async function () {
await document.fonts.load('700 20px aptly');
const C = await (await fetch('/__svg/conv.json?' + Date.now())).json();
const NS = 'http://www.w3.org/2000/svg';
const out = [];
for (const c of C) {
// 1) viditeľný obsah pozadia (alfa kanál) → orezaný viewBox
const [vx, vy, vw, vh] = c.vb;
const S = 10;
const bg = `<svg xmlns="${NS}" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="${vx} ${vy} ${vw} ${vh}" width="${vw * S}" height="${vh * S}">${c.inner}</svg>`;
const img = new Image();
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(bg);
await img.decode();
const cv = document.createElement('canvas');
cv.width = Math.ceil(vw * S);
cv.height = Math.ceil(vh * S);
const ctx = cv.getContext('2d');
ctx.drawImage(img, 0, 0);
const a = ctx.getImageData(0, 0, cv.width, cv.height).data;
let x0 = 1e9, y0 = 1e9, x1 = -1, y1 = -1;
for (let y = 0; y < cv.height; y++)
for (let x = 0; x < cv.width; x++)
if (a[(y * cv.width + x) * 4 + 3] > 8) {
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
const pad = 0.3;
const crop = [vx + x0 / S - pad, vy + y0 / S - pad, (x1 + 1 - x0) / S + 2 * pad, (y1 + 1 - y0) / S + 2 * pad].map((v) => Math.round(v * 100) / 100);
// 2) texty z pôvodného SVG zmerané s písmom Aptly
const host = document.createElement('div');
host.style.cssText = 'position:absolute;left:-5000px;top:0';
host.innerHTML = c.original.replace(/^[\s\S]*?(<svg)/, '$1');
document.body.appendChild(host);
const els = [...host.querySelectorAll('text')];
const m = c.texts.map((info) => {
const t = els.find((x) => x.getAttribute('transform') === info.transform && x.textContent === info.text);
const tr = t.transform.baseVal.consolidate()?.matrix || { e: 0, f: 0 };
const n = t.getNumberOfChars();
const e0 = t.getExtentOfChar(0);
return {
...info,
start: tr.e + t.getStartPositionOfChar(0).x,
end: tr.e + t.getEndPositionOfChar(n - 1).x,
top: tr.f + e0.y,
h: e0.height,
aptly: /aptly/i.test(info.family)
};
});
host.remove();
out.push({ ...c, crop, m });
}
return out;
};