Incorporate extra padding for raised text on canvas

I have a project in progress where I am working on implementing live text engraving on a bracelet using a canvas overlay.

Here is the link to my code snippet:

var first = true;
startIt();


function startIt()
{
    const canvasDiv = document.getElementById('canvasDiv');
    canvasDiv.innerHTML = '<canvas id="layer0" width="400" height="400"></canvas>'; //for IE
    const canvas = document.getElementById('layer0');
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = "green";
    ctx.font = "20px Courier New";
    const curve = document.getElementById('curve');
    const curveText = document.getElementById('text');
    $(curve).keyup(function(e) {changeCurve();});
    $(curveText).keyup(function(e) {changeCurve();});
    
    
    
       
    if (first)
    {
        changeCurve();
        first = false;
    }
    
}

function changeCurve()
{
const points = curve.value.split(',');  
    if (points.length == 8)
        drawStack();
  
}
...
.product-images-container {
  
  width: 400px;
  height: 400px; 
  position:relative;
}
canvas {
  position: absolute;
  top: 0;
}
.productImage {
  
  width: 100%
  
}
.engravingArea {
    position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="product-images-container">
    <input size="80" type="text" id="curve" name="curve" value="40,228,100,260,270,260,330,240">
    <input size="80" type="text" id="text" name="text" value="testing 1234567890">
<div class="engravingArea">
<div id="canvasDiv"></div>
</div>
    
<img class="productImage" src="https://i.imgur.com/BaoChJT.jpeg"> 
</div>

  1. I am aiming to center the text on the cuff by adding some letter padding to ensure proper alignment.

  2. The text appears slightly blurry. Is there a way to enhance its quality for better clarity?

  3. I want to dynamically scale the canvas and the text inside it when its container resizes. The container has a percentage-based width, and we need the canvas to adjust according to either the container size or the image dimensions. I attempted to set canvas.width = $(".product-images-container").width(), but it didn't work as expected in this scenario.

Looking forward to your valuable insights!

Answer №1

Consider implementing the following approach: https://jsfiddle.net/uajfh78p/

var first = true;

var productImage = document.getElementsByClassName('productImage')[0];
var curveOffset = document.getElementById('offset');
var fontSize = document.getElementById('fontsize');

$(productImage).on('load', function() {
    startIt();
});


function startIt()
{
    canvasDiv = document.getElementById('canvasDiv');
    canvasDiv.innerHTML = '<canvas id="layer0" width="' + productImage.naturalWidth + '" height="' + productImage.naturalHeight + '"></canvas>'; //for IE
    canvas = document.getElementById('layer0');
    ctx = canvas.getContext('2d');
    ctx.fillStyle = "green";
    curve = document.getElementById('curve');
    curveText = document.getElementById('text');
    scale = productImage.naturalWidth/400;
    $(curve).keyup(function(e) {changeCurve();});
    $(curveText).keyup(function(e) {changeCurve();});
    
    
    $(curveOffset).on('change', changeCurve);
    $(fontSize).on('change', changeCurve);
    
       
    if (first)
    {
        changeCurve();
        first = false;
    }
    
}

function changeCurve()
{
points = curve.value.split(',');  
offset = (parseInt(curveOffset.value) || 0);
ctx.font = (parseInt(fontSize.value) || 20)*scale + "px Courier New";

    if (points.length == 8)
        drawStack();
        
    
  
}

function drawStack()
{
    Ribbon = {maxChar: 50, startX: points[0]*scale, startY: (points[1] - offset)*scale, 
              control1X: points[2]*scale, control1Y: (points[3] - offset)*scale, 
              control2X: points[4]*scale, control2Y: (points[5] - offset)*scale, 
              endX: points[6]*scale, endY: (points[7] - offset)*scale};
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.beginPath();
    
    ctx.moveTo(Ribbon.startX,Ribbon.startY);
    ctx.bezierCurveTo(Ribbon.control1X,Ribbon.control1Y,
                   Ribbon.control2X,Ribbon.control2Y,
                   Ribbon.endX,Ribbon.endY);
    
    ctx.stroke();
    ctx.restore();
    
   FillRibbon(curveText.value,Ribbon); 
}

function FillRibbon(text,Ribbon)
{

var textCurve = [];
var ribbon = text.substring(0,Ribbon.maxChar);
var curveSample = 1000;


xDist = 0;
var i = 0;
for (i = 0; i < curveSample; i++)
    {
        a = new bezier2(i/curveSample,Ribbon.startX,Ribbon.startY,Ribbon.control1X,Ribbon.control1Y,Ribbon.control2X,Ribbon.control2Y,Ribbon.endX,Ribbon.endY);
        b = new bezier2((i+1)/curveSample,Ribbon.startX,Ribbon.startY,Ribbon.control1X,Ribbon.control1Y,Ribbon.control2X,Ribbon.control2Y,Ribbon.endX,Ribbon.endY);
        c = new bezier(a,b);
        textCurve.push({bezier: a, curve: c.curve});
    }

letterPadding = ctx.measureText("").width / 4; 
w = ribbon.length;
ww = Math.round(ctx.measureText(ribbon).width);


totalPadding = (w-1) * letterPadding;
totalLength = ww + totalPadding;
p = 0;

cDist = textCurve[curveSample-1].curve.cDist;

z = (cDist / 2) - (totalLength / 2);

for (i=0;i<curveSample;i++)
    {
        if (textCurve[i].curve.cDist >= z)
        {
            p = i;
            break;
        }
    }

for (i = 0; i < w ; i++)
    {
        ctx.save();
        ctx.translate(textCurve[p].bezier.point.x,textCurve[p].bezier.point.y);
        ctx.rotate(textCurve[p].curve.rad);
        ctx.fillText(ribbon[i],0,0);
        ctx.restore();
        
        x1 = ctx.measureText(ribbon[i]).width + letterPadding;
        x2 = 0; 
        for (j=p;j<curveSample;j++)
        {
        x2 = x2 + textCurve[j].curve.dist;
        if (x2 >= x1)
            {
                p = j;
                break;
            }
        }
        



    }
} //end FillRibon

function bezier(b1, b2)
{
//Final stage which takes p, p+1 and calculates the rotation, distance on the path and accumulates the total distance
this.rad = Math.atan(b1.point.mY/b1.point.mX);
this.b2 = b2;
this.b1 = b1;
dx = (b2.x - b1.x);
dx2 = (b2.x - b1.x) * (b2.x - b1.x);
this.dist = Math.sqrt( ((b2.x - b1.x) * (b2.x - b1.x)) + ((b2.y - b1.y) * (b2.y - b1.y)) );
xDist = xDist + this.dist;
this.curve = {rad: this.rad, dist: this.dist, cDist: xDist};
}

function bezierT(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY)
{
//calculates the tangent line to a point in the curve; later used to calculate the degrees of rotation at this point.
this.mx = (3*(1-t)*(1-t) * (control1X - startX)) + ((6 * (1-t) * t) * (control2X - control1X)) + (3 * t * t * (endX - control2X));
this.my = (3*(1-t)*(1-t) * (control1Y - startY)) + ((6 * (1-t) * t) * (control2Y - control1Y)) + (3 * t * t * (endY - control2Y));
}

function bezier2(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY)
{
//Quadratic bezier curve plotter
this.Bezier1 = new bezier1(t,startX,startY,control1X,control1Y,control2X,control2Y);
this.Bezier2 = new bezier1(t,control1X,control1Y,control2X,control2Y,endX,endY);
this.x = ((1 - t) * this.Bezier1.x) + (t * this.Bezier2.x);
this.y = ((1 - t) * this.Bezier1.y) + (t * this.Bezier2.y);
this.slope = new bezierT(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY);

this.point = {t: t, x: this.x, y: this.y, mX: this.slope.mx, mY: this.slope.my};
}
function bezier1(t,startX, startY,control1X,control1Y,control2X,control2Y)
{
//linear bezier curve plotter; used recursivly in the quadratic bezier curve calculation
this.x = (( 1 - t) * (1 - t) * startX) + (2 * (1 - t) * t * control1X) + (t * t * control2X);
this.y = (( 1 - t) * (1 - t) * startY) + (2 * (1 - t) * t * control1Y) + (t * t * control2Y);

}
.product-images-container {
  
    width:100%;
  position:relative;
}
canvas {
  position: absolute;
  top: 0;
}
.productImage {
  
  width: 100%
  
}
.engravingArea {
    position: relative;
}

#canvasDiv > canvas {
    width:100%;
    height: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="product-images-container">
    <label for="curve">Curve</label><input size="80" type="text" id="curve" name="curve" value="40,228,100,260,270,260,330,240">
    <label for="text">Text</label><input size="80" type="text" id="text" name="text" value="testing 1234567890">
    <label for="offset">Y offset</label><input size="80" type="number" id="offset" name="offset" value="5">
    <label for="fontsize">Font size</label><input size="80" type="number" id="fontsize" name="fontsize" value="20">
<div class="engravingArea">
<div id="canvasDiv"></div>
</div>
    
<img class="productImage" src="https://i.imgur.com/BaoChJT.jpeg"> 
</div>

  1. An additional field named offset has been included to specify the y offset from the bottom. This offset is integrated into the curve values before rendering on the canvas. By adjusting this value along with the font size, you can easily match the image (changeCurve() is triggered upon any changes).

  2. For optimal sharpness, it's recommended to set the canvas pixel size equal to the image dimensions. The code now dynamically determines the image width to create the canvas at that size using a scaling factor for proportional adjustments.

  3. To ensure uniform scaling, both the canvas and image should fill their container width through CSS modifications, enabling synchronized scaling.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Determine the distinct elements in an array using JavaScript/jQuery

I have incorporated Gridster widgets into my webpage, each with a button that turns the widget's color to red when clicked. Upon clicking the button, the parent element is also added to an array. My main goal I aim to have the parent element added t ...

Empty POST request detected in select2 form submission

Looking for some assistance to help steer me in the right direction. My professor is unable to provide guidance. I'm currently struggling with handling POST requests in my form for select2 multiple fields. The issue arises from a Yes/No flag that dete ...

What is the reason for the absence of a shorthand property in CSS that combines width and height measurements?

Have you ever thought about using a shorter property for specifying an item's width and height in CSS? Currently, we have to write: selector {width: 100px; height: 250px;} While this is already pretty fast, wouldn't it be even quicker if we c ...

When using flexgrid, be aware that adjacent divs on the same row may shift position when applying floats

Struggling with aligning the divs in a row when floating one of them? Here's a snippet to illustrate the issue: .flex-grid { display: flex; justify-content:space-between; } .flex-grid .rcorners1 { border-radius: 25px; background: green; ...

Modification of encapsulated class in Angular is not permitted

Within Angular, a div element with the class "container" is automatically created and inserted into my component's HTML. Is there a way to modify this class to "container-fluid"? I understand that Angular utilizes encapsulation, but I am unsure of how ...

Error: The property 'classList' of null is unreachable for HTMLImageElement

I'm facing an issue on my website that's causing the following error message to pop up: Uncaught TypeError: Cannot read property 'classList' of null at HTMLImageElement. Here's the code I've been using: https://jsfiddle. ...

The navigation buttons on the Bootstrap carousel will only respond to keyboard commands after the arrow on the

Is there a way to change the default behavior of the bootstrap carousel to allow keyboard navigation without having to click the arrows on the screen first? In my modal, I have a carousel that I want users to be able to navigate using the keyboard arrows ...

Steps to have the initial link redirect to a designated webpage

I am working with tables that have links defined in the HTML. I want to modify the behavior so that the first link opens a different URL, for example: john.com. Is it possible to achieve this? Specifically, I want the link associated with the first name ( ...

Labeling src library files with namespaces

I have developed a ReactJS website that interacts with a library called analyzejs which was created in another programming language. While I am able to call functions from this library, I do not have much flexibility to modify its contents. Up until now, ...

The interaction between jQuery Masonry and Bootstrap results in unexpected grid behavior

I've spent hours trying to solve this problem, but I can't seem to get it to work as intended. I want the layout of my boxes to be like this using Bootstrap grids: However, after implementing the jQuery Masonry plugin (which I used to manage va ...

Discovering the RGB color of a pixel while hovering the mouse in MapboxGL Js

Looking to retrieve the RGB color value of the pixel under the mouse hover in MapboxGL Js. What is the most effective method to accomplish this task? ...

Explore nested arrays and retrieve objects that have corresponding categories

Working with react+ nextJS and fetching static objects, specifically "posts". The aim is to develop a "related posts" feature for each post that retrieves three posts containing at least one of the categories. Below is an excerpt simplified for clarity: co ...

The Challenge with jQuery Nano Scroll

I recently added the jQuery Nano Scroll plugin to my HTML page. However, I am facing an issue where the scrollpane is not visible. When I inspect the div using Chrome, the scrollpane appears. Any suggestions on how to resolve this? Here's a snippet ...

Retrieving Data from Vue JS Store

I am fetching value from the store import {store} from '../../store/store' Here is the Variable:- let Data = { textType: '', textData: null }; Upon using console.log(store.state.testData) We see the following result in the cons ...

What is the best way to retrieve an attribute from an object dynamically using JavaScript?

Inside my object, I have the following values: obj.resposta1 obj.resposta2 obj.resposta3 obj.resposta4 How can I access each value inside a loop like this: for ( var int = 1; int < 5; int++) Thank you, Celso ...

Is there a way to mix up the sequence of information when replying?

There have been numerous instances where this question has been raised and addressed, such as here, here, and here. However, those discussions pertained to Bootstrap 4, which is approaching its End of Life as of today, 2021-10-20. Given that Bootstrap 5 i ...

Increasing the boundary width beyond a specified limit with CSS

Need help extending the border-top of an element beyond a container width set to 1024px, while maintaining center alignment on the page with left alignment. Thank you! Here is the code snippet: HTML <main> <div> <span>Hello& ...

Build a new shop using a section of data from a JSON database

Let's say I have a JSON store that is set up as shown below var subAccountStore = new Ext.data.JsonStore({ autoLoad: true, proxy: { type:'ajax', url : '/opUI/json/subaccount.action?name="ABC"' }, fields: ['acc ...

Adjust the positioning of axisLeft labels to the left side

I have incorporated a @nivo/bar chart in my project as shown below: <ResponsiveBar height="400" data={barData} keys={["value"]} indexBy="label" layout="horizontal" axisLeft={{ width ...

Identify the CSS Framework being used in conjunction with Selenium

I have developed a program that crawls through various web pages and conducts tests using Selenium. My current task is to identify which CSS Frameworks are utilized on these websites for statistical analysis. Currently, I am using the FireFox Webdriver to ...