0

I'm trying to align text in a way that canvas context textBaseline property set to "alphabetic" does. I can't get exactly same effect with kineticjs.

var letters = [
    { symbol: "A", x: 3.0, size: 20 },
    { symbol: "B", x: 36.3, size: 30 },
    { symbol: "C", x: 86.3, size: 40 },
    { symbol: "D", x: 158.6, size: 50 },
    { symbol: "E", x: 248.9, size: 40 },
    { symbol: "F", x: 315.5, size: 30 },
    { symbol: "G", x: 361.3, size: 20 } ];
// How kineticjs renders the text
(function actual() {
    var stage = new Kinetic.Stage({ container: "mycontainer",  width: 400,  height: 100 }),
  layer = new Kinetic.Layer(),
        baseline = 60;
 letters.forEach(function(letter) { 
     layer.add(new Kinetic.Text({
         x: letter.x,
         y: baseline - letter.size,
         text: letter.symbol,
         fontSize: letter.size,         
          fill: 'black',
      }));     
  });
    // Baseline visualization
  layer.add(new Kinetic.Line({
     points: [0, baseline, 400, baseline ],
     stroke: "red"
  }));
  stage.add(layer);
})();
// How I would like it to render the text
(function expected() {
    var c = document.getElementById("mycanvas"),
     ctx = c.getContext("2d"),
        baseline = 60;
   ctx.textBaseline = "alphabetic"; // redundant as it's actually default behaviour  
  letters.forEach(function(letter) {
        ctx.font = letter.size + "px Arial";
        ctx.fillText(letter.symbol, letter.x, baseline); 
    });    
    // Baseline visualization
    ctx.strokeStyle = "red";
  ctx.moveTo(0, baseline);
  ctx.lineTo(400, baseline);
  ctx.stroke();
})();
<script src="https://cdn.lukej.me/kineticjs/5.1.0/kinetic.min.js"></script>

<div id="mycontainer"></div>

<canvas id="mycanvas" width="400" height="150" style="position: absolute; left: 10px; top: 100px">

Same code on jsfiddle.

I'm aware of this question however I haven't found the right way of calculating the offset for alphabetic baseline.

Community
  • 1
  • 1
eugenesqr
  • 589
  • 6
  • 19

1 Answers1

0

The proper "y" position can be calculated as follows:

y = baseline - fontSize + descent;

where "descent" can be obtained using "getTextHeight" function from this answer

The code below gives pixel perfect results in Firefox and decent ones in Chrome.

var letters = [
    { symbol: "A", x: 3.0, size: 20 },
    { symbol: "B", x: 36.3, size: 30 },
    { symbol: "C", x: 86.3, size: 40 },
    { symbol: "D", x: 158.6, size: 50 },
    { symbol: "E", x: 248.9, size: 40 },
    { symbol: "F", x: 315.5, size: 30 },
    { symbol: "G", x: 361.3, size: 20 } ];
// How kineticjs renders the text
(function actual() {
    var stage = new Kinetic.Stage({ container: "mycontainer",  width: 400,  height: 100 }),
  layer = new Kinetic.Layer(),
        baseline = 60;
 letters.forEach(function(letter) { 
     layer.add(new Kinetic.Text({
         x: letter.x,
         y: baseline - letter.size + getMetrics("Arial", letter.size).descent,
         text: letter.symbol,
         fontSize: letter.size,         
          fill: 'black',
      }));     
  });
    // Baseline visualization
  layer.add(new Kinetic.Line({
     points: [0, baseline, 400, baseline ],
     stroke: "red"
  }));
  stage.add(layer);
    function getMetrics(fontFamily, fontSize) { 
        var $text = $("<span>Hg</span>").css({  
            "fontFamily": fontFamily,  
            "font-size": fontSize + "px", 
            "line-height": "normal" 
        }); 
        var $block = $("<div></div>").css({ 
            "display": "inline-block", 
            "width": "1px", 
            "height": "0px" 
        }); 
        var $div = $("<div></div>"); 
        $div.append($text, $block); 
        $("body").append($div); 
        try { 
            var result = {}; 
            $block.css({ verticalAlign: "baseline" }); 
            result.ascent = $block.offset().top - $text.offset().top; 
            $block.css({ verticalAlign: "bottom" }); 
            result.height = $block.offset().top - $text.offset().top; 
            result.descent = result.height - result.ascent; 
        } finally { 
            $div.remove(); 
        } 
        return result; 
 }; 
})();
// How I would like it to render the text
(function expected() {
    var c = document.getElementById("mycanvas"),
     ctx = c.getContext("2d"),
        baseline = 60;
   ctx.textBaseline = "alphabetic"; // redundant as it's actually default behaviour  
  letters.forEach(function(letter) {
        ctx.font = letter.size + "px Arial";
        ctx.fillText(letter.symbol, letter.x, baseline); 
    });    
    // Baseline visualization
    ctx.strokeStyle = "red";
  ctx.moveTo(0, baseline);
  ctx.lineTo(400, baseline);
  ctx.stroke();
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.lukej.me/kineticjs/5.1.0/kinetic.min.js"></script>

<div id="mycontainer"></div>

<canvas id="mycanvas" width="400" height="150" style="position: absolute; left: 10px; top: 100px">
Community
  • 1
  • 1
eugenesqr
  • 589
  • 6
  • 19