computeViewport function

CanvasViewportTransform computeViewport({
  1. required double artboardW,
  2. required double artboardH,
  3. required double targetW,
  4. required double targetH,
  5. Rect2D? bounds,
  6. double bleed = 0,
  7. CanvasFit fit = CanvasFit.contain,
  8. double? minUniformScale,
  9. double? maxUniformScale,
})

Fits either the artboard (bounds=null) or a world-space bounds rect into a target, with optional "bleed" (outer margin).

Implementation

CanvasViewportTransform computeViewport({
  required double artboardW,
  required double artboardH,
  required double targetW,
  required double targetH,
  Rect2D? bounds, // if set: fit this rect in artboard coordinates
  double bleed = 0,
  CanvasFit fit = CanvasFit.contain,

  // Optional clamp used by editor camera (export doesn’t need this).
  double? minUniformScale,
  double? maxUniformScale,
}) {
  // Source rect
  final double srcW, srcH, srcLeft, srcTop;

  if (bounds != null) {
    srcW = (bounds.right - bounds.left).abs();
    srcH = (bounds.bottom - bounds.top).abs();
    srcLeft = bounds.left;
    srcTop = bounds.top;
  } else {
    srcW = artboardW;
    srcH = artboardH;
    srcLeft = 0;
    srcTop = 0;
  }

  if (srcW <= 0 || srcH <= 0) {
    return CanvasViewportTransform(
      scaleX: 1,
      scaleY: 1,
      translateX: bleed,
      translateY: bleed,
      recordingW: targetW + bleed * 2,
      recordingH: targetH + bleed * 2,
    );
  }

  final sx = targetW / srcW;
  final sy = targetH / srcH;

  final base = switch (fit) {
    CanvasFit.contain => sx < sy ? sx : sy,
    CanvasFit.cover => sx > sy ? sx : sy,
    CanvasFit.stretch => 1.0,
  };

  var scaleX = fit == CanvasFit.stretch ? sx : base;
  var scaleY = fit == CanvasFit.stretch ? sy : base;

  // Editor clamp (uniform only; stretch shouldn’t clamp like this)
  if (fit != CanvasFit.stretch &&
      (minUniformScale != null || maxUniformScale != null)) {
    final lo = minUniformScale ?? double.negativeInfinity;
    final hi = maxUniformScale ?? double.infinity;
    final clamped = base.clamp(lo, hi).toDouble();
    scaleX = clamped;
    scaleY = clamped;
  }

  final scaledW = srcW * scaleX;
  final scaledH = srcH * scaleY;

  final dx = bleed + (targetW - scaledW) / 2.0 - srcLeft * scaleX;
  final dy = bleed + (targetH - scaledH) / 2.0 - srcTop * scaleY;

  return CanvasViewportTransform(
    scaleX: scaleX,
    scaleY: scaleY,
    translateX: dx,
    translateY: dy,
    recordingW: targetW + bleed * 2,
    recordingH: targetH + bleed * 2,
  );
}