<canvas id="canvas"></canvas>

<div class="menu">
  <label>
    <span>radius: </span>
    <input id="radius" type="range" min="0" max="180" step="0.01" value="50">
  </label>

  <label>
    <span>smooth: </span>
    <input id="smooth" type="range" min="0" max="1" step="0.01" value="0.5">
  </label>
</div>
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: monospace, monospace;
}

#canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

.menu {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  
  label {
    display: flex;
    align-items: center;
  }
}
// https://habr.com/ru/post/353082/

const getType = o => Object.prototype.toString.call(o).slice(8, -1);

class Point {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }

  setXY(x, y) {
    this.x = x;
    this.y = y;
    return this;
  }

  copy() {
    return new Point(this.x, this.y);
  }

  scale(sx = 1, sy = 1) {
    this.x *= sx;
    this.y *= sy;
    return this;
  }

  add(dx, dy) {
    this.x += dx;
    this.y += dy;
    return this;
  }

  addPoint(p) {
    this.x += p.x;
    this.y += p.y;
    return this;
  }

  subPoint(p) {
    this.x -= p.x;
    this.y -= p.y;
    return this;
  }

  rotate(rad) {
    const sinC = Math.sin(rad);
    const cosC = Math.cos(rad);
    const x = this.x * cosC - this.y * sinC;
    const y = this.x * sinC + this.y * cosC;
    this.x = x;
    this.y = y;
    return this;
  }

  setLength(l = 1) {
    const len = Math.hypot(this.x, this.y);
    if (len > 0) {
      const invLen = l / len;
      this.scale(invLen, invLen);
    }
    return this;
  }
}

class CubicBezier {
  constructor(p0, p1, p2, p3) {
    this.p0 = p0;
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
  }

  copy() {
    return new CubicBezier(
      this.p0.copy(),
      this.p1.copy(),
      this.p2.copy(),
      this.p3.copy()
    );
  }

  scale(sx = 1, sy = 1) {
    this.p0.scale(sx, sy);
    this.p1.scale(sx, sy);
    this.p2.scale(sx, sy);
    this.p3.scale(sx, sy);
    return this;
  }

  add(dx, dy) {
    this.p0.add(dx, dy);
    this.p1.add(dx, dy);
    this.p2.add(dx, dy);
    this.p3.add(dx, dy);
    return this;
  }

  addPoint(p) {
    this.p0.addPoint(p);
    this.p1.addPoint(p);
    this.p2.addPoint(p);
    this.p3.addPoint(p);
    return this;
  }

  subPoint(p) {
    this.p0.subPoint(p);
    this.p1.subPoint(p);
    this.p2.subPoint(p);
    this.p3.subPoint(p);
    return this;
  }

  rotate(a) {
    this.p0.rotate(a);
    this.p1.rotate(a);
    this.p2.rotate(a);
    this.p3.rotate(a);
    return this;
  }
}

class Line {
  constructor(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
  }
}

class Path {
  constructor() {
    this.segments = [];
  }
  
  push(...seg) {
    this.segments.push(...seg);
  }
  
  toString() {
    throw new Error("NotImplementedException");
  }
  
  [Symbol.iterator]() {
    return this.segments.values();
  }
}

function smoothRect(width, height, R, smooth = 0.5) {
  const theta = Math.PI / 2;
  const sw = R * (1 + smooth);
  const alpha = theta * (1 - smooth);
  const beta = (theta * smooth) / 2;
  const ad = sw - (R - Math.abs(Math.sin(beta)) * R);
  const dh = R - Math.abs(Math.cos(beta)) * R;
  const cd = Math.abs((dh * Math.sin(alpha + beta)) / Math.sin(beta));
  const ac = ad - cd;
  const ab = (2 * ac) / 3;

  const ahl = (R * 4 * Math.tan(alpha / 4)) / 3;

  const p10 = new Point(-R, sw - R);
  const p11 = p10.copy().add(0, -ab);
  const p12 = p10.copy().add(0, -ac);
  const p13 = p10.copy().add(dh, -ad);

  const p20 = new Point(sw - R, -R);
  const p21 = p20.copy().add(-ab, 0);
  const p22 = p20.copy().add(-ac, 0);
  const p23 = p20.copy().add(-ad, dh);

  const arcH1 = p13.copy().subPoint(p12).setLength(ahl).addPoint(p13);
  const arcH2 = p23.copy().subPoint(p22).setLength(ahl).addPoint(p23);

  const cornerBezierIn = new CubicBezier(p10, p11, p12, p13);
  const cornerBezierArc = new CubicBezier(p13, arcH1, arcH2, p23);
  const cornerBezierOut = new CubicBezier(p23, p22, p21, p20);

  const circleOrigin = new Point();
  const segments = new Path();

  let cornerAngle = 0;
  
  //
  // topLeft
  //
  circleOrigin.setXY(R, R);
  const tlBezierIn = cornerBezierIn.copy().addPoint(circleOrigin);
  const tlBezierArc = cornerBezierArc.copy().addPoint(circleOrigin);
  const tlBezierOut = cornerBezierOut.copy().addPoint(circleOrigin);

  segments.push(tlBezierIn, tlBezierArc, tlBezierOut);

  //
  // topRight
  //
  cornerAngle = (90 * Math.PI) / 180;
  circleOrigin.setXY(width - R, R);

  const trBezierIn = cornerBezierIn
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const trBezierArc = cornerBezierArc
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const trBezierOut = cornerBezierOut
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);

  const topLine = new Line(
    tlBezierOut.p3.x,
    tlBezierOut.p3.y,
    trBezierIn.p0.x,
    trBezierIn.p0.y
  );

  segments.push(topLine, trBezierIn, trBezierArc, trBezierOut);

  //
  // bottomRight
  //
  cornerAngle = (180 * Math.PI) / 180;
  circleOrigin.setXY(width - R, height - R);

  const brBezierIn = cornerBezierIn
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const brBezierArc = cornerBezierArc
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const brBezierOut = cornerBezierOut
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);

  const rightLine = new Line(
    trBezierOut.p3.x,
    trBezierOut.p3.y,
    brBezierIn.p0.x,
    brBezierIn.p0.y
  );
  segments.push(rightLine, brBezierIn, brBezierArc, brBezierOut);

  //
  // bottomLeft
  //
  cornerAngle = (270 * Math.PI) / 180;
  circleOrigin.setXY(R, height - R);

  const blBezierIn = cornerBezierIn
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const blBezierArc = cornerBezierArc
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);
  const blBezierOut = cornerBezierOut
    .copy()
    .rotate(cornerAngle)
    .addPoint(circleOrigin);

  const bottomLine = new Line(
    brBezierOut.p3.x,
    brBezierOut.p3.y,
    blBezierIn.p0.x,
    blBezierIn.p0.y
  );
  segments.push(bottomLine, blBezierIn, blBezierArc, blBezierOut);

  const leftLine = new Line(
    blBezierOut.p3.x,
    blBezierOut.p3.y,
    tlBezierIn.p0.x,
    tlBezierIn.p0.y
  );
  segments.push(leftLine);
  return segments;
}

function drawSegments(ctx, segments) {
  ctx.strokeStyle = "#7B8794";
  ctx.lineWidth = 4;

  for (seg of segments) {
    if (seg instanceof CubicBezier) {
      ctx.beginPath();
      ctx.moveTo(seg.p0.x, seg.p0.y);
      ctx.bezierCurveTo(seg.p1.x, seg.p1.y, seg.p2.x, seg.p2.y, seg.p3.x, seg.p3.y);
    } else if (seg instanceof Line) {
      ctx.beginPath();
      ctx.moveTo(seg.x1, seg.y1);
      ctx.lineTo(seg.x2, seg.y2);
    }
    
    ctx.stroke();    
  }
}

function update(ctx, r, smooth) {
  const rw = 240;
  const rh = 240;
  const segments = smoothRect(rw, rh, r, smooth);

  canvas.width = canvas.offsetWidth;
  canvas.height = canvas.offsetHeight;
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.save();
  ctx.translate((canvas.width - rw) / 2, (canvas.height - rh) / 2);
  drawSegments(ctx, segments);
  ctx.restore();
}

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

const radiusInput = document.querySelector("#radius");
const smoothInput = document.querySelector("#smooth");

let smooth = +smoothInput.value;
let radius = +radiusInput.value;

update(ctx, radius, smooth);

radiusInput.addEventListener("input", (e) => {
  radius = +e.target.value;
  update(ctx, radius, smooth);
});

smoothInput.addEventListener("input", (e) => {
  smooth = +e.target.value;
  update(ctx, radius, smooth);
});

window.addEventListener("resize", (e) => {
  update(ctx, radius, smooth);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.