-
-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feature request] loading custom font support #17
Comments
[edit]
|
oh if this is already supported thats fantastic. I am looking at the code now https://github.com/DjDeveloperr/deno-canvas/blob/master/src/types.ts#L1065, what should the descriptors field be? |
ah, I figured it out: canvas.loadFont(fontBuffer, {
family: 'Comic Sans',
style: 'normal',
weight: 'normal',
variant: 'normal'
}) it probably wouldnt hurt to make the type signatures more specific than type FontDescriptors = {
/* identifying name of font */
family: string
style: 'normal' | 'italic'
variant: 'normal' | ...
weight: 'normal' | 'bold' | ...
}
loadFont(
bytes: ArrayBuffer | Uint8Array,
descriptors: FontDescriptors,
): void; |
seems like the const text = "Hello There"
const family = './fonts/stick-no-bills/StickNoBills-VariableFont_wght.ttf'
// load a font
const font = await Deno.readFile(family)
const font_identifier = new Date().toString()
canvas.loadFont(font, {
family: font_identifier
})
context.font = `${size}px ${font_identifier}`
// get the font measurements
const metrics = context.measureText(text)
// draw a rect around it
context.fillStyle = 'white'
context.fillRect(0, 0, metrics.width, metrics.fontBoundingBoxAscent + metrics.actualBoundingBoxDescent)
// draw the text
context.fillStyle = "black"
context.fillText(text_chunk, 0, 0) here they are for one font https://fonts.google.com/specimen/Stick+No+Bills {
width: 352,
actualBoundingBoxAscent: 38,
actualBoundingBoxDescent: 2,
actualBoundingBoxLeft: 5,
actualBoundingBoxRight: 357,
fontBoundingBoxAscent: 47,
fontBoundingBoxDescent: 15.600000381469727
} here they are for another https://www.fontspace.com/category/comic-sans {
width: 198,
actualBoundingBoxAscent: 33,
actualBoundingBoxDescent: 1,
actualBoundingBoxLeft: 0,
actualBoundingBoxRight: 198,
fontBoundingBoxAscent: 43.75,
fontBoundingBoxDescent: 13.600000381469727
} |
if this looks like a real issue I can create a separate issue or change the title on this one |
Here are some repros. First a jsfiddle which correctly measures the width of text: and second, deno-canvas incorrectly measuring the same text: import { createCanvas } from 'https://deno.land/x/[email protected]/mod.ts'
const canvas = createCanvas(500, 200)
const context = canvas.getContext('2d')
function draw_text(text: string, x: number, y: number) {
const metrics = context.measureText(text)
console.log(metrics)
context.fillStyle = 'red'
context.fillRect(
x,
y,
metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight,
metrics.fontBoundingBoxAscent,
)
context.fillStyle = 'black'
context.fillText(
text,
x + metrics.actualBoundingBoxLeft,
y + metrics.fontBoundingBoxAscent,
)
}
context.fillStyle = 'white'
context.fillRect(0, 0, canvas.width, canvas.height)
const font_buffer = await Deno.readFile('./fonts/tangerine/Tangerine-Regular.ttf')
canvas.loadFont(font_buffer, {family: 'Tangerine'})
context.font = '50px Tangerine'
draw_text("Hello World", 50, 50)
await Deno.writeFile('canvas.png', canvas.toBuffer()) both examples use this font https://fonts.google.com/specimen/Tangerine?query=tangerine |
All values in |
Got it. Well it is unfortunate that measuring text can't be done with deno canvas. I wanted to build out text captions with borders. I suppose there's an extremely hacky workaround for me where I dump the image data and find the max & min x & y |
Right.. text rendering as a whole is not-so-good with |
It would definitely be cool to see an ffi bridge to skia. For what I need though, if the paragraph api is exposed that would probably get me everything I need. Would you mind showing an example of how to use it in deno canvas (I have seen canvaskits docs but I'm not sure it's the same api here) |
Skia related APIs are all exposed in a namespace that is default exported from import Skia from "https://deno.land/x/[email protected]/mod.ts";
// Use Skia.Paragraph, Skia.ParagraphBuilder, etc.
// API should be same as canvaskit. Under the hood, canvaskit polyills HTML Canvas using Skia WASM API. If you don't mind, I'd appreciate a PR to polyfill measureText properly using Paragraph API. |
@DjDeveloperr could you give me a primer on contributing to this library? Would it be this file https://github.com/DjDeveloperr/deno-canvas/blob/master/src/lib.js#L2750? Its hard to tell if this is a source file because some of this code looks minified. Could you show where another skia specific method is used as an example? |
Yeah, in |
@DjDeveloperr its been a minute since I worked on this. Heres a first attempt at measuring text. I can properly measure ascent and descent of text from the baseline. I cant seem to measure left/right properly though. Especially with this font which is extra italicized (this is the "Regular") font. I know if there is a part of the paragraph api I am just missing or what import CanvasKit, { createCanvas } from "https://deno.land/x/[email protected]/mod.ts";
function measureText(text: string, fontInfo: { fontData: Uint8Array, fontSize: number }) {
// I assume I can find fontInfo somewhere else inside the context class
const { fontData, fontSize } = fontInfo
const fontMgr = CanvasKit.FontMgr.FromData(fontData);
if (fontMgr === null) throw new Error('idk why but fontMgr is null')
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: [fontMgr.getFamilyName(0)],
fontSize,
},
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
paragraph.layout(Infinity);
const left = Math.max(...paragraph.getLineMetrics().map(l => l.left))
const right = paragraph.getLongestLine() + left
const ascent = Math.max(...paragraph.getLineMetrics().map(l => l.ascent))
const descent = Math.max(...paragraph.getLineMetrics().map(l => l.descent))
const height = ascent + descent
const width = right
const metrics = { ascent, descent, left, right, width, height }
paragraph.delete()
fontMgr.delete()
return metrics
}
function draw_text(text: string, x: number, y: number, fontInfo: { fontData: Uint8Array, fontSize: number }) {
const metrics = measureText(text, fontInfo)
console.log(metrics)
context.fillStyle = 'red'
context.fillRect(
x - metrics.left,
y - metrics.ascent,
metrics.width,
metrics.height,
)
context.fillStyle = 'black'
context.fillText(
text,
x,
y,
)
}
const fontSize = 50
const canvas = createCanvas(500, 200)
const context = canvas.getContext('2d')
context.fillStyle = 'white'
context.fillRect(0, 0, canvas.width, canvas.height)
canvas.loadFont(fontData, {family: 'Tangerine'})
context.font = `${fontSize}px Tangerine`
// context.textBaseline = 'top'
draw_text("Hello go World", 50, 50, { fontSize, fontData })
await Deno.writeFile('canvas.png', canvas.toBuffer()) |
fwiw I created an issue with skia https://bugs.chromium.org/p/skia/issues/detail?id=12586 |
@andykais your first attempt at the implementation looks great. I think it would be good enough for first pass (better than current inaccurate implementation) And yes, we do have access to So I think you can PR this function (but slightly modified to accept |
whats the current CanvasKit version? |
It is 0.32.0 |
I think I've figure out how to measure text correctly. // Make sure to set the font size first!
context2d.font = `16px sans-serif`;
// The text to display
const text: string = `Hello World`;
// Measure the width of a single character
const charWidth: number = Math.floor(context2d.measureText("X").width);
// Get the length of the text string
const textLength: number = text.length;
// textWidth at selected fontSize
const textWidth: number = charWidth * textLength; |
@webdev3301 I believe there is still an issue with measuring custom loaded fonts. See this message above: #17 (comment) |
deno-canvas supports writing text to a canvas:
this is currently only limited to fonts your system knows about (it might even be more limited than that). Canvas kit has an api for loading custom fonts https://skia.org/docs/user/modules/quickstart/#text-shaping. Itd be great if deno-canvas supported loading custom fonts. I think pulling in the whole paragraph builder api might be substantial, but all I would personally be interested in is mirroring the browser's canvas apis with the addition of being able to load custom fonts. E.g.
the
registerFont
method is very similar to the css interface for loading fonts:The text was updated successfully, but these errors were encountered: