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.
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">