Fix: CAD-Skizzen Baustellenschild zurueck, Key-Gen wieder in TOOL_DEFS, QR-Code browser-kompatibel

This commit is contained in:
2026-06-05 00:48:37 +02:00
parent fa1eca6867
commit b79e32fc20
2 changed files with 71 additions and 43 deletions

View File

@@ -68,8 +68,8 @@ export const TOOLS = [
{
id: 'skizze',
Icon: SkizzeIcon,
label: 'CAD-Skizzen',
navLabel: 'CAD-Skizzen',
label: 'CAD-Skizzen 🚧',
navLabel: 'CAD-Skizzen 🚧',
group: 'Werkzeuge',
component: Skizze,
},

View File

@@ -2701,60 +2701,78 @@ const CAP_POS = [
function drawQrToCanvas(canvas, data, opts) {
const { size, fgColor, bgColor, margin, dotStyle, cornerStyle, caption, captionPos, captionColor, captionSize } = opts;
const capH = caption && captionPos !== 'none' ? (captionSize + 16) : 0;
const capH = caption && captionPos !== 'none' ? (Number(captionSize) + 16) : 0;
const totalH = size + capH;
canvas.width = size;
canvas.height = totalH;
const ctx = canvas.getContext('2d');
// Step 1: generate QR to temporary canvas (black on white, no margin)
const tmp = document.createElement('canvas');
return QRCode.toCanvas(tmp, data, {
width: size,
margin: 0,
color: { dark: '#000000ff', light: '#ffffffff' },
errorCorrectionLevel: 'M',
}).then(() => {
// Step 2: read pixel data to detect module grid
const tctx = tmp.getContext('2d');
const pixels = tctx.getImageData(0, 0, size, size).data;
// Find module size by scanning first dark run
let firstDark = -1, runEnd = -1;
for (let x = 0; x < size; x++) {
const idx = x * 4;
const dark = pixels[idx] < 128;
if (dark && firstDark < 0) firstDark = x;
if (!dark && firstDark >= 0 && runEnd < 0) { runEnd = x; break; }
}
const moduleSize = (runEnd > 0 && firstDark >= 0) ? (runEnd - firstDark) : Math.round(size / 25);
const n = Math.round(size / moduleSize);
// Step 3: draw background
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, size, totalH);
return QRCode.create(data, { errorCorrectionLevel: 'M' }).then(qr => {
const modules = qr.modules;
const n = modules.size;
const cellSize = (size - margin * 2) / n;
const qrTop = captionPos === 'top' ? capH : 0;
const pad = Math.round(margin * moduleSize);
const cellSize = (size - pad * 2) / n;
ctx.fillStyle = fgColor;
for (let row = 0; row < n; row++) {
for (let col = 0; col < n; col++) {
if (!modules.get(row, col)) continue;
const x = margin + col * cellSize;
const y = qrTop + margin + row * cellSize;
// Sample the center pixel of this module from the temp canvas
const px = Math.round((col + 0.5) * moduleSize);
const py = Math.round((row + 0.5) * moduleSize);
const idx = (py * size + px) * 4;
if (pixels[idx] > 128) continue; // light module
const x = pad + col * cellSize;
const y = qrTop + pad + row * cellSize;
const w = cellSize;
// Corner squares (finder patterns): rows/cols 0-7 and last 7
// Finder pattern corners: rows/cols 0-6 and last 6
const isCorner = (row < 7 && col < 7) || (row < 7 && col >= n - 7) || (row >= n - 7 && col < 7);
if (isCorner) {
if (cornerStyle === 'dot') {
const r = w * 0.5;
ctx.beginPath(); ctx.arc(x+r, y+r, r, 0, Math.PI*2); ctx.fill();
} else if (cornerStyle === 'rounded') {
const r = w * 0.35;
ctx.beginPath(); ctx.roundRect(x, y, w, w, r); ctx.fill();
} else {
ctx.fillRect(x, y, w, w);
}
} else {
if (dotStyle === 'dots') {
const style = isCorner ? cornerStyle : dotStyle;
if (style === 'dots') {
const r = w * 0.45;
ctx.beginPath(); ctx.arc(x+r, y+r, r, 0, Math.PI*2); ctx.fill();
} else if (dotStyle === 'rounded') {
const r = w * 0.3;
ctx.beginPath(); ctx.roundRect(x, y, w, w, r); ctx.fill();
} else if (dotStyle === 'classy') {
const r = w * 0.4;
ctx.beginPath(); ctx.roundRect(x+1, y+1, w-2, w-2, r); ctx.fill();
ctx.beginPath(); ctx.arc(x + w/2, y + w/2, r, 0, Math.PI * 2); ctx.fill();
} else if (style === 'rounded' || style === 'classy') {
const r = style === 'classy' ? w * 0.42 : w * 0.32;
ctx.beginPath(); ctx.roundRect(x + 0.5, y + 0.5, w - 1, w - 1, r); ctx.fill();
} else if (style === 'dot') {
const r = w * 0.45;
ctx.beginPath(); ctx.arc(x + w/2, y + w/2, r, 0, Math.PI * 2); ctx.fill();
} else {
ctx.fillRect(x, y, w, w);
}
}
}
}
// Caption
// Step 4: caption
if (caption && captionPos !== 'none') {
ctx.fillStyle = captionColor;
ctx.font = `${captionSize}px sans-serif`;
@@ -2763,7 +2781,16 @@ function drawQrToCanvas(canvas, data, opts) {
const cy = captionPos === 'top' ? capH / 2 : size + capH / 2;
ctx.fillText(caption, size / 2, cy, size - 16);
}
}).catch(() => {});
}).catch(err => {
// Show error message on canvas
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, size, totalH);
ctx.fillStyle = '#ff6b9d';
ctx.font = '13px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Ungültige URL', size/2, size/2);
});
}
function QrGenerator({ toast, mobile }) {
@@ -3086,6 +3113,7 @@ const TOOL_DEFS = [
{id:'netzwerk', label:'🌐 Netzwerk', desc:'IP, Subnetz, Geschwindigkeit, CIDR', ready:true, component:NetzwerkRechner},
{id:'datetime', label:'📅 Datetime', desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner},
{id:'farben', label:'🎨 Farben', desc:'HEX, RGB, HSL, OKLCH umrechnen', ready:true, component:FarbRechner},
{id:'keygen', label:'🔐 Key-Gen', desc:'Sichere Keys & Secrets generieren', ready:true, component:KeyGen},
{id:'qrcode', label:'◻ QR-Code', desc:'QR-Codes erstellen & verwalten', ready:true, component:QrGenerator},
];