feat(materials): Фаза 4 — аннотации и рисунки
- svg-draw.js: opts.bgImage (рисунок-подложка) + exportFlatBlob() — растеризация подложки и вектора в плоский PNG. - /my-materials: кнопка «Рисунок» (создать с нуля) и «Аннотировать» на карточках доски/изображения (рисовать поверх). Модалка с SVG-рисовалкой → сохранение в «Мои материалы» как image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+41
-1
@@ -153,6 +153,18 @@
|
||||
const content = document.createElementNS(SVGNS, 'g'); // user drawing lives here
|
||||
content.setAttribute('class', 'svgd-content');
|
||||
svg.appendChild(content);
|
||||
// optional background image to draw over (annotation mode)
|
||||
const bgUrl = opts.bgImage || null;
|
||||
if (bgUrl) {
|
||||
const im = document.createElementNS(SVGNS, 'image');
|
||||
im.setAttribute('href', bgUrl);
|
||||
try { im.setAttributeNS('http://www.w3.org/1999/xlink', 'href', bgUrl); } catch (e) {}
|
||||
im.setAttribute('x', '0'); im.setAttribute('y', '0');
|
||||
im.setAttribute('width', String(W)); im.setAttribute('height', String(H));
|
||||
im.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
||||
im.style.pointerEvents = 'none';
|
||||
svg.insertBefore(im, content);
|
||||
}
|
||||
wrap.appendChild(svg);
|
||||
|
||||
root.appendChild(toolbar);
|
||||
@@ -480,13 +492,41 @@
|
||||
return '#' + [m[1], m[2], m[3]].map(function (x) { return ('0' + parseInt(x, 10).toString(16)).slice(-2); }).join('');
|
||||
}
|
||||
|
||||
/* Rasterize to a flattened PNG: optional background image + the vector
|
||||
drawing on top. Used to save an annotated image into «Мои материалы». */
|
||||
function exportFlatBlob(cb) {
|
||||
const out = document.createElement('canvas');
|
||||
out.width = W; out.height = H;
|
||||
const ctx = out.getContext('2d');
|
||||
function drawVector() {
|
||||
const v = new Image();
|
||||
v.onload = function () { ctx.drawImage(v, 0, 0, W, H); out.toBlob(cb, 'image/png'); };
|
||||
v.onerror = function () { out.toBlob(cb, 'image/png'); };
|
||||
v.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(getSVG());
|
||||
}
|
||||
ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, W, H);
|
||||
if (bgUrl) {
|
||||
const bg = new Image();
|
||||
bg.onload = function () {
|
||||
const s = Math.min(W / bg.naturalWidth, H / bg.naturalHeight) || 1;
|
||||
const dw = bg.naturalWidth * s, dh = bg.naturalHeight * s;
|
||||
ctx.drawImage(bg, (W - dw) / 2, (H - dh) / 2, dw, dh);
|
||||
drawVector();
|
||||
};
|
||||
bg.onerror = drawVector;
|
||||
bg.src = bgUrl;
|
||||
} else {
|
||||
drawVector();
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
document.removeEventListener('keydown', onKey);
|
||||
document.removeEventListener('pointerup', restoreAncestorDrag);
|
||||
if (root.parentNode) root.parentNode.removeChild(root);
|
||||
}
|
||||
|
||||
return { getSVG: getSVG, destroy: destroy, el: root };
|
||||
return { getSVG: getSVG, exportFlatBlob: exportFlatBlob, destroy: destroy, el: root };
|
||||
}
|
||||
|
||||
window.SvgDraw = { mount: mount };
|
||||
|
||||
Reference in New Issue
Block a user