{"version":3,"file":"panZoom.geometry.mjs","names":[],"sources":["../../../../package/common/panZoom/panZoom.geometry.ts"],"sourcesContent":["/** CSS `translate(x, y) scale(scale)` with `transform-origin: 0 0`. */\nexport interface PanZoomTransform {\n  x: number;\n  y: number;\n  scale: number;\n}\n\nexport interface Point {\n  x: number;\n  y: number;\n}\n\nexport interface Size {\n  width: number;\n  height: number;\n}\n\n/** A box in the viewport's own coordinates, before any transform. */\nexport interface Box {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\nexport const IDENTITY: PanZoomTransform = { x: 0, y: 0, scale: 1 };\n\n/** Below this, a scale counts as the fitted one. */\nexport const EPSILON = 0.001;\n\n/** Scales to `scale` while the content point under `focus` stays where it is on screen. */\nexport function zoomAt(transform: PanZoomTransform, scale: number, focus: Point): PanZoomTransform {\n  const ratio = scale / transform.scale;\n  return {\n    x: focus.x - (focus.x - transform.x) * ratio,\n    y: focus.y - (focus.y - transform.y) * ratio,\n    scale,\n  };\n}\n\n/** Larger than the viewport on an axis, its edges stay outside the viewport's; smaller, it is centred. */\nexport function clampTransform(transform: PanZoomTransform, viewport: Size, content: Box): PanZoomTransform {\n  return {\n    x: clampAxis(transform.x, transform.scale, viewport.width, content.left, content.width),\n    y: clampAxis(transform.y, transform.scale, viewport.height, content.top, content.height),\n    scale: transform.scale,\n  };\n}\n\n/**\n * At the fitted size the layout has centred the content already. Centring it again from measured\n * rects only adds their rounding: Chromium left 1/128 px, which counted as zoomed.\n */\nexport function settleTransform(transform: PanZoomTransform, viewport: Size, content: Box): PanZoomTransform {\n  if (transform.scale <= 1 + EPSILON && !canPan(1, viewport, content)) return IDENTITY;\n  return clampTransform(transform, viewport, content);\n}\n\nfunction clampAxis(offset: number, scale: number, viewport: number, start: number, size: number): number {\n  const scaledStart = start * scale;\n  const scaledSize = size * scale;\n  if (scaledSize <= viewport) return (viewport - scaledSize) / 2 - scaledStart;\n  return Math.min(-scaledStart, Math.max(viewport - scaledStart - scaledSize, offset));\n}\n\n/** Whether the content is larger than the viewport on either axis, so a drag can move it. */\nexport function canPan(scale: number, viewport: Size, content: Box): boolean {\n  // Half a pixel of slack, so rounding at the fitted size does not read as room to pan.\n  return content.width * scale > viewport.width + 0.5 || content.height * scale > viewport.height + 0.5;\n}\n"],"mappings":"aAyBA,IAAa,EAA6B,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,CAAE,EAGpD,EAAU,KAGvB,SAAgB,EAAO,EAA6B,EAAe,EAAgC,CACjG,IAAM,EAAQ,EAAQ,EAAU,MAChC,MAAO,CACL,EAAG,EAAM,GAAK,EAAM,EAAI,EAAU,GAAK,EACvC,EAAG,EAAM,GAAK,EAAM,EAAI,EAAU,GAAK,EACvC,OACF,CACF,CAGA,SAAgB,EAAe,EAA6B,EAAgB,EAAgC,CAC1G,MAAO,CACL,EAAG,EAAU,EAAU,EAAG,EAAU,MAAO,EAAS,MAAO,EAAQ,KAAM,EAAQ,KAAK,EACtF,EAAG,EAAU,EAAU,EAAG,EAAU,MAAO,EAAS,OAAQ,EAAQ,IAAK,EAAQ,MAAM,EACvF,MAAO,EAAU,KACnB,CACF,CAMA,SAAgB,EAAgB,EAA6B,EAAgB,EAAgC,CAE3G,OADI,EAAU,OAAS,OAAe,CAAC,EAAO,EAAG,EAAU,CAAO,EAAU,EACrE,EAAe,EAAW,EAAU,CAAO,CACpD,CAEA,SAAS,EAAU,EAAgB,EAAe,EAAkB,EAAe,EAAsB,CACvG,IAAM,EAAc,EAAQ,EACtB,EAAa,EAAO,EAE1B,OADI,GAAc,GAAkB,EAAW,GAAc,EAAI,EAC1D,KAAK,IAAI,CAAC,EAAa,KAAK,IAAI,EAAW,EAAc,EAAY,CAAM,CAAC,CACrF,CAGA,SAAgB,EAAO,EAAe,EAAgB,EAAuB,CAE3E,OAAO,EAAQ,MAAQ,EAAQ,EAAS,MAAQ,IAAO,EAAQ,OAAS,EAAQ,EAAS,OAAS,EACpG"}