is it possible to draw text decoration (underline, etc.) with HTML5 Canvas Text API?
I am using the HTML5
canvas API to display some string (canvas.fillText), and I was wondering whether text-decoration (like underline, strikethrough, etc.) was something possible with the canvas API. Unfortunately, I found nothing about this.
The only solution I found was to manually do开发者_开发知识库 the decoration using the canvas drawing API (I mean, explicitly drawing a horizontal line, for example, to mimic the 'underline' decoration).
Is this possible using the canvas text API?
It won't work with a built-in method, but here is a simplified function I used successfully based on reading, "HTML5 Canvas: Text underline workaround" on the ScriptStock website.
var underline = function(ctx, text, x, y, size, color, thickness ,offset){
var width = ctx.measureText(text).width;
switch(ctx.textAlign){
case "center":
x -= (width/2); break;
case "right":
x -= width; break;
}
y += size+offset;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = thickness;
ctx.moveTo(x,y);
ctx.lineTo(x+width,y);
ctx.stroke();
}
You can do this by using measureText
and fillRect
like so:
ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText("Hello World");
ctx.fillRect(xPos, yPos, width, 2);
The only difficult part about this approach is there is no way to obtain the height use measureText. Otherwise, you could use that as your Y coordinate when drawing your fillRect.
Your Y position will only depend on the height of your text and how close you'd like the underline.
Demo in Stack Snippets
// get canvas / context
var can = document.getElementById('my-canvas');
var ctx = can.getContext('2d')
let xPos=10, yPos=15;
let text = "Hello World"
ctx.fillText(text, xPos, yPos);
let { width } = ctx.measureText("Hello World");
ctx.fillRect(xPos, yPos, width, 2);
<canvas id="my-canvas" width="250" height="150"></canvas>
I created an alternative version of Mulhoon's code. I also take into account the text baseline.
const underline = (ctx, text, x, y) => {
let metrics = measureText(ctx, text)
let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height
switch (ctx.textAlign) {
case "center" : x -= (metrics.width / 2) ; break
case "right" : x -= metrics.width ; break
}
switch (ctx.textBaseline) {
case "top" : y += (fontSize) ; break
case "middle" : y += (fontSize / 2) ; break
}
ctx.save()
ctx.beginPath()
ctx.strokeStyle = ctx.fillStyle
ctx.lineWidth = Math.ceil(fontSize * 0.08)
ctx.moveTo(x, y)
ctx.lineTo(x + metrics.width, y)
ctx.stroke()
ctx.restore()
}
Full Example
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents')
event.initEvent(eventName, true, false)
el.dispatchEvent(event)
}
const measureText = (ctx, text) => {
let metrics = ctx.measureText(text)
return {
width: Math.floor(metrics.width),
height: Math.floor(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent),
actualHeight: Math.floor(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)
}
}
const underline = (ctx, text, x, y) => {
let metrics = measureText(ctx, text)
let fontSize = Math.floor(metrics.actualHeight * 1.4) // 140% the height
switch (ctx.textAlign) {
case "center" : x -= (metrics.width / 2) ; break
case "right" : x -= metrics.width ; break
}
switch (ctx.textBaseline) {
case "top" : y += (fontSize) ; break
case "middle" : y += (fontSize / 2) ; break
}
ctx.save()
ctx.beginPath()
ctx.strokeStyle = ctx.fillStyle
ctx.lineWidth = Math.ceil(fontSize * 0.08)
ctx.moveTo(x, y)
ctx.lineTo(x + metrics.width, y)
ctx.stroke()
ctx.restore()
}
const getOrigin = (ctx) => ({
x : Math.floor(ctx.canvas.width / 2),
y : Math.floor(ctx.canvas.height / 2)
})
const redraw = (ctx, sampleText, fontSize) => {
let origin = getOrigin(ctx)
ctx.font = fontSize + 'px Arial'
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
renderText(ctx, sampleText, origin.x, origin.y, 'Yellow', 'left', 'top')
renderText(ctx, sampleText, origin.x, origin.y, 'SkyBlue ', 'right', 'bottom')
renderText(ctx, sampleText, origin.x, origin.y, 'Tomato', 'left', 'bottom')
renderText(ctx, sampleText, origin.x, origin.y, 'Chartreuse ', 'right', 'top')
renderText(ctx, sampleText, origin.x, origin.y, 'Black', 'center', 'middle')
}
const renderText = (ctx, text, x, y, fillStyle, textAlign, textBaseLine) => {
ctx.fillStyle = fillStyle
ctx.textAlign = textAlign
ctx.textBaseline = textBaseLine
ctx.fillText(text, x, y)
underline(ctx, text, x, y)
}
const sampleText = 'Hello World'
const fontSizes = [ 8, 12, 16, 24, 32 ]
document.addEventListener('DOMContentLoaded', () => {
let ctx = document.querySelector('#demo').getContext('2d')
let sel = document.querySelector('select[name="font-size"]')
fontSizes.forEach(fontSize => sel.appendChild(new Option(fontSize, fontSize)))
sel.addEventListener('change', (e) => redraw(ctx, sampleText, sel.value))
sel.value = fontSizes[fontSizes.length - 1]
triggerEvent(sel, 'change')
})
canvas { border: thin solid grey }
label { font-weight: bold }
label::after { content: ": " }
<canvas id="demo" width="360" height="120"></canvas>
<form>
<label for="font-size-select">Font Size</label>
<select id="font-size-select" name="font-size"></select>
</form>
To add an underline to your canvas text, simply add underline characters at the same (x,y) position as your text. e.g. you want to underline abc
context.fillText("abc",x,y);
context.fillText ("___",x,y);
Similarly for strike through, you would use the "-" character rather than underline.
I'm sorry to say that the answer is 'no'. There are no 'text-decoration' or similar styles available in the text methods of the HTML Canvas Context.
精彩评论