function applayFingers(svg, canvasData, scaleFn, transformFn) {
	/** @type { Pointer } */
	let firstPointer;

	/** @type { Pointer} */
	let secondPointer;

	/** @type {number} */
	let distance;

	/** @type {Point} */
	let center;

	/** @param {PointerEvent} evt */
	function cancel(evt) {
		distance = null;
		center = null;
		if (firstPointer?.id === evt.pointerId) { firstPointer = null; }
		if (secondPointer?.id === evt.pointerId) { secondPointer = null; }

		if (!firstPointer && !secondPointer) {
			listenDel(svg, 'pointermove', move);
			listenDel(svg, 'pointercancel', cancel);
			listenDel(svg, 'pointerup', cancel);
		}
	};

	/** @param {PointerEvent} evt */
	function move(evt) {
		if (evt[ProcessedSmbl]) { return; }

		if ((firstPointer && !secondPointer) || (!firstPointer && secondPointer)) {
			// move with one pointer
			canvasData.position.x = evt.clientX + (firstPointer || secondPointer).shift.x;
			canvasData.position.y = evt.clientY + (firstPointer || secondPointer).shift.y;
			transformFn();
			return;
		}

		if (!secondPointer || !firstPointer || (secondPointer?.id !== evt.pointerId && firstPointer?.id !== evt.pointerId)) { return; }

		const distanceNew = Math.hypot(firstPointer.pos.x - secondPointer.pos.x, firstPointer.pos.y - secondPointer.pos.y);
		const centerNew = {
			x: (firstPointer.pos.x + secondPointer.pos.x) / 2,
			y: (firstPointer.pos.y + secondPointer.pos.y) / 2
		};

		// not first move
		if (distance) {
			canvasData.position.x = canvasData.position.x + centerNew.x - center.x;
			canvasData.position.y = canvasData.position.y + centerNew.y - center.y;

			scaleFn(
				canvasData.scale / distance * distanceNew,
				centerNew);
		}

		distance = distanceNew;
		center = centerNew;

		if (firstPointer.id === evt.pointerId) { firstPointer = evtPointer(evt, canvasData); }
		if (secondPointer.id === evt.pointerId) { secondPointer = evtPointer(evt, canvasData); }
	}

	listen(svg, 'pointerdown', /** @param {PointerEvent} evt */ evt => {
		if (evt[ProcessedSmbl] || (!firstPointer && !evt.isPrimary) || (firstPointer && secondPointer)) {
			return;
		}

		svg.setPointerCapture(evt.pointerId);
		if (!firstPointer) {
			listen(svg, 'pointermove', move);
			listen(svg, 'pointercancel', cancel);
			listen(svg, 'pointerup', cancel);
		}

		if (!firstPointer) { firstPointer = evtPointer(evt, canvasData); return; }
		if (!secondPointer) { secondPointer = evtPointer(evt, canvasData); }
	});
}
@param { SVGSVGElement } svg
@param { {position:Point, scale:number} } canvasData
@param { {(nextScale:number, originPoint:Point):void} } scaleFn
@param { {():void} } transformFn
@return