word/edit: fix (vertical) scale with different aspect ratios
[sheet.git] / word / editor.js
index 0e54c62fe349a571727d1266af945bf12bddcbc2..461bab0aa15a2b13ec83d57c7f58c8a43bedb4a7 100644 (file)
@@ -120,26 +120,107 @@ document.addEventListener('DOMContentLoaded', () => {
        let thumbpreview = document.getElementById('convertpreview');
        if (thumbpreview && imgpreview) {
                thumbpreview.onclick = e => {
+                       thumbpreview.onclick = null; // setup once
                        const cropinput = document.getElementById('crop32');
-                       let border = { width: 600, height: 400 };
-                       let crop = new Croppie(thumbpreview, {
-                               boundary: { width: border.width * 1.3, height: border.height * 1.2 },
-                               viewport: border,
-                               update: e => {
-                                       cropinput.value = e.points.map((pos, axis) => {
-                                               Math.round(1000 * pos / (axis % 2 ? border.height : border.width))
-                                       }).join(',');
-                               },
-                       });
-                       crop.bind({
-                               url: imgpreview.src,
-                               points: cropinput.value.split(/[^0-9]/).map((rel, axis) => {
-                                       return rel * (axis % 2 ? border.height : border.width) / 1000
-                               }),
-                       });
-                       crop.elements.overlay.addEventListener('dblclick', e => {
-                               crop.destroy();
-                       });
+                       const imgselect = thumbpreview.children[0];
+                       const canvas = [thumbpreview.clientWidth, thumbpreview.clientHeight];
+                       const border = [canvas[0], canvas[0] * imgpreview.height / imgpreview.width];
+                       const minscale = Math.max(1, canvas[1] / border[1]); // 100% or fit width
+                       let crop = cropinput.value.split(/[^0-9]/).map(pos => pos / 1000);
+                       let scale = 1 / (crop[2] - crop[0]) || minscale;
+                       crop.push(0); // defined y dimension
+                       crop.splice(2); // end coordinates applied to zoom
+                       crop = crop.map((rel, axis) => rel * border[axis % 2] * scale);
+
+                       let drag, pinch;
+                       function applydrag(e) {
+                               const touch = e.touches ? e.touches[0] : e;
+                               let pos = [touch.pageX, touch.pageY];
+                               if (e.type === 'touchmove' && e.touches.length > 1) {
+                                       // distance to second point
+                                       pos[0] -= e.touches[1].pageX;
+                                       pos[1] -= e.touches[1].pageY;
+                                       const span = Math.sqrt(pos[0]**2 + pos[1]**2);
+                                       if (pinch) {
+                                               cropzoom(.01 * (span - pinch));
+                                       }
+                                       pinch = span;
+                                       return;
+                               }
+                               if (drag) {
+                                       // apply drag delta to crop position
+                                       crop[0] += drag[0] - pos[0];
+                                       crop[1] += drag[1] - pos[1];
+                                       recrop();
+                               }
+                               drag = pos;
+                       }
+
+                       function recrop() {
+                               [0, 1].forEach(axis => {
+                                       if (crop[axis] > border[axis] * scale - canvas[axis]) {
+                                               crop[axis] = border[axis] * scale - canvas[axis]; // max bound
+                                       }
+                                       if (crop[axis] < 0) {
+                                               crop[axis] = 0; // min bound
+                                       }
+                               });
+                               imgselect.style.left = -crop[0]+'px';
+                               imgselect.style.top  = -crop[1]+'px';
+                               imgselect.style.width = (scale * 100)+'%';
+                               cropinput.value = [
+                                       crop[0] / border[0],
+                                       crop[1] / border[1],
+                                       (crop[0] + canvas[0]) / border[0],
+                                       (crop[1] + canvas[1]) / border[1],
+                               ].map(pos => Math.round(1000 * pos / scale));
+                       }
+
+                       function cropzoom(delta) {
+                               if (scale + delta < minscale) {
+                                       delta = minscale - scale; // scale = 1
+                               }
+                               [0, 1].forEach(axis => {
+                                       // same area center at altered scale
+                                       crop[axis] += (crop[axis] + border[axis] / 2) / scale * delta;
+                               });
+                               scale += delta;
+                               recrop();
+                       }
+
+                       imgselect.src = imgpreview.src;
+                       imgselect.style.cursor = 'grab';
+                       imgselect.style.position = 'absolute';
+                       recrop();
+
+                       imgselect.ontouchstart =
+                       imgselect.onmousedown = e => {
+                               e.preventDefault();
+                               drag = pinch = false;
+                               applydrag(e);
+                               imgselect.style.cursor = 'grabbing';
+                               window.ontouchmove =
+                               window.onmousemove = e => {
+                                       e.preventDefault();
+                                       applydrag(e);
+                               };
+                               window.ontouchend =
+                               window.onmouseup = e => {
+                                       e.preventDefault();
+                                       imgselect.style.cursor = 'grab';
+                                       window.ontouchmove = window.ontouchend =
+                                       window.onmousemove = window.onmouseup = null;
+                               };
+                       };
+
+                       imgselect.onwheel = e => {
+                               e.preventDefault();
+                               let delta = (-e.deltaY || e.wheelDelta) * .001 * scale;
+                               if (e.deltaMode == 1) { // DOM_DELTA_LINE
+                                       delta *= 18; // convert number of lines to pixels
+                               }
+                               cropzoom(delta);
+                       };
                };
        }