FabricJS circular text

by Arjan Haverkamp

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<table>
	<tr>
		<td>Text</td>
		<td><input type="text" id="text" value="Lorem ipsum di samet"></td>
	</tr>
	<tr>
		<td>Diameter</td>
		<td><input type="range" id="diameter" min="100" max="2500" value="250" step="1"></td></tr>
	<tr>
		<td>Font size</td>
		<td><input type="range" id="fontSize" min="8" max="100" value="24" step="1"></td>
	</tr>
	<tr>
		<td>Kerning</td>
		<td><input type="range" id="kerning" min="-10" max="10" value="0" step="1"></td>
	</tr>
	<tr>
		<td>Flip</td>
		<td><input type="checkbox" id="flip" value="1"></td>
	</tr>
	<tr>
		<td></td>
		<td><button type="button" id="newObject">Add to Canvas</button></td>
	</tr>
</table>

<hr>

<div id="canvasContainer">
   <canvas id="c" width="600" height="600"></canvas>
</div>

CSS

body, input, button {
	font: 12pt Arial, Helvetica;
}

#canvasContainer {
  background-position: 0 0, 10px 10px;
  background-size: 20px 20px;
  background-image: linear-gradient(45deg, #e4ebf5 25%, transparent 25%, transparent 75%, #e4ebf5 75%, #e4ebf5 100%),linear-gradient(45deg, #e4ebf5 25%, white 25%, white 75%, #e4ebf5 75%, #e4ebf5 100%);
	border: 2px solid #ccc;
	display: inline-block;
}

input[type=range], input[type=text] {
	width: 200px;
}

JavaScript

//
// Fabric Addons
//

/**         
 * Trim a canvas.
 *          
 * @param {canvas} canvas A canvas element to trim. This element will be trimmed (reference)
 * @param {int} threshold Alpha threshold. Allows for trimming semi-opaque pixels too. Range: 0 - 255
 * @returns {Object} Width and height of trimmed canvcas and left-top coordinate of trimmed area. Example: {width:400, height:300, x:65, y:104}
 * @see https://stackoverflow.com/a/69993651/3360038
 */         
fabric.util.trimCanvas = (canvas, threshold) => {
  threshold = threshold || 0;

  const ctx = canvas.getContext('2d'),
        w = canvas.width, h = canvas.height,
        imageData = ctx.getImageData(0, 0, w, h),
        tlCorner = { x:w+1, y:h+1 }, 
        brCorner = { x:-1, y:-1 }

  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      if (imageData.data[((y * w + x) * 4) + 3] > threshold) {
        tlCorner.x = Math.min(x, tlCorner.x)
        tlCorner.y = Math.min(y, tlCorner.y)
        brCorner.x = Math.max(x, brCorner.x)
        brCorner.y = Math.max(y, brCorner.y)
      }
    }
  }

  const cut = ctx.getImageData(tlCorner.x, tlCorner.y, brCorner.x - tlCorner.x, brCorner.y - tlCorner.y)

  canvas.width = brCorner.x - tlCorner.x
  canvas.height = brCorner.y - tlCorner.y

  ctx.putImageData(cut, 0, 0)

  return {width:canvas.width, height:canvas.height, x:tlCorner.x, y:tlCorner.y}
}

//
// Textcurved object
//
fabric.Textcurved = fabric.util.createClass(fabric.IText, {
	type: 'textcurved',
	cacheProperties: fabric.IText.prototype.cacheProperties.concat(
		'diameter', 'kerning', 'flipped'	
	),

   initialize: function(text, options) {
			this.diameter = options?.diameter ?? 250
      this.kerning = options?.kerning ?? 0
      this.flipped = options?.flipped ?? false

      this.set('lockScalingFlip',true)

      this.setControlsVisibility({
         mt: false, // middle top disable
         mb: false, // middle bottom
         ml: false, // middle left
         mr: false, //...