blob: 8456d4f7002eb9f1a38cbda92f065d0a47b6e63a [file] [log] [blame]
describe('CanvasKit\'s Font Behavior', function() {
let container = document.createElement('div');
document.body.appendChild(container);
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
beforeEach(function() {
container.innerHTML = `
<canvas width=600 height=600 id=test></canvas>
<canvas width=600 height=600 id=report></canvas>`;
});
afterEach(function() {
container.innerHTML = '';
});
let notSerifFontBuffer = null;
// This font is known to support kerning
const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
notSerifFontBuffer = buffer;
});
let bungeeFontBuffer = null;
// This font has tofu for incorrect null terminators
// see https://bugs.chromium.org/p/skia/issues/detail?id=9314
const bungeeFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
bungeeFontBuffer = buffer;
});
it('can draw shaped and unshaped text', function(done) {
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
// This is taken from example.html
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
const textPaint = new CanvasKit.SkPaint();
const textFont = new CanvasKit.SkFont(notoSerif, 20);
canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
canvas.drawText('This text is not shaped, and overflows the boundary',
35, 50, textPaint, textFont);
const shapedText = new CanvasKit.ShapedText({
font: textFont,
leftToRight: true,
text: 'This text *is* shaped, and wraps to the right width.',
width: 160,
});
const textBoxX = 35;
const textBoxY = 55;
canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
const bounds = shapedText.getBounds();
bounds.fLeft += textBoxX;
bounds.fRight += textBoxX;
bounds.fTop += textBoxY;
bounds.fBottom += textBoxY
canvas.drawRect(bounds, paint);
const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
const textFont2 = new CanvasKit.SkFont(notoSerif, 60);
const shapedText2 = new CanvasKit.ShapedText({
font: textFont2,
leftToRight: true,
text: SHAPE_TEST_TEXT,
width: 600,
});
canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
canvas.drawText(shapedText2, 10, 300, textPaint);
canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
surface.flush();
paint.delete();
notoSerif.delete();
textPaint.delete();
textFont.delete();
shapedText.delete();
textFont2.delete();
shapedText2.delete();
fontMgr.delete();
reportSurface(surface, 'text_shaping', done);
}));
});
it('can draw text following a path', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const font = new CanvasKit.SkFont(null, 24);
const fontPaint = new CanvasKit.SkPaint();
fontPaint.setAntiAlias(true);
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
const arc = new CanvasKit.SkPath();
arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
arc.lineTo(210, 140);
arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
// Only 1 dot should show up in the image, because we run out of path.
const str = 'This téxt should follow the curve across contours...';
const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font);
canvas.drawPath(arc, paint);
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
surface.flush();
textBlob.delete();
arc.delete();
paint.delete();
font.delete();
fontPaint.delete();
reportSurface(surface, 'monospace_text_on_path', done);
}));
});
it('can draw text following a path with a non-serif font', function(done) {
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const font = new CanvasKit.SkFont(notoSerif, 24);
const fontPaint = new CanvasKit.SkPaint();
fontPaint.setAntiAlias(true);
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
const arc = new CanvasKit.SkPath();
arc.arcTo(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
arc.lineTo(210, 140);
arc.arcTo(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
const str = 'This téxt should follow the curve across contours...';
const textBlob = CanvasKit.SkTextBlob.MakeOnPath(str, arc, font, 60.5);
canvas.drawPath(arc, paint);
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
surface.flush();
textBlob.delete();
arc.delete();
paint.delete();
notoSerif.delete();
font.delete();
fontPaint.delete();
fontMgr.delete();
reportSurface(surface, 'serif_text_on_path', done);
}));
});
// https://bugs.chromium.org/p/skia/issues/detail?id=9314
it('does not draw tofu for null terminators at end of text', function(done) {
Promise.all([LoadCanvasKit, bungeeFontLoaded]).then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const bungee = fontMgr.MakeTypefaceFromData(bungeeFontBuffer);
const canvas = surface.getCanvas();
// yellow, to make sure tofu is plainly visible
canvas.clear(CanvasKit.Color(255, 255, 0, 1));
const font = new CanvasKit.SkFont(bungee, 24);
const fontPaint = new CanvasKit.SkPaint();
fontPaint.setAntiAlias(true);
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
const str = 'This is téxt';
const textBlob = CanvasKit.SkTextBlob.MakeFromText(str + ' text blob', font);
canvas.drawTextBlob(textBlob, 10, 50, fontPaint);
canvas.drawText(str + ' normal', 10, 100, fontPaint, font);
canvas.drawText('null terminator ->\u0000<- on purpose', 10, 150, fontPaint, font);
surface.flush();
textBlob.delete();
bungee.delete();
font.delete();
fontPaint.delete();
fontMgr.delete();
reportSurface(surface, 'nullterminators_skbug_9314', done);
}));
});
it('can make a font mgr with passed in fonts', function(done) {
Promise.all([LoadCanvasKit, bungeeFontLoaded, notoSerifFontLoaded]).then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
// CanvasKit.SkFontMgr.FromData([bungeeFontBuffer, notSerifFontBuffer]) also works
const fontMgr = CanvasKit.SkFontMgr.FromData(bungeeFontBuffer, notSerifFontBuffer);
expect(fontMgr).toBeTruthy();
expect(fontMgr.countFamilies()).toBe(2);
// in debug mode, let's list them.
if (fontMgr.dumpFamilies) {
fontMgr.dumpFamilies();
}
fontMgr.delete();
done();
}));
});
it('can load fonts of a variety of types', function(done) {
let otfFontBuffer = null;
const otfFontLoaded = fetch('/assets/Roboto-Regular.otf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
otfFontBuffer = buffer;
});
let woffFontBuffer = null;
const woffFontLoaded = fetch('/assets/Roboto-Regular.woff').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
woffFontBuffer = buffer;
});
let woff2FontBuffer = null;
const woff2FontLoaded = fetch('/assets/Roboto-Regular.woff2').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
woff2FontBuffer = buffer;
});
let ttcFontBuffer = null;
const ttcFontLoaded = fetch('/assets/test.ttc').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
ttcFontBuffer = buffer;
});
Promise.all([LoadCanvasKit, otfFontLoaded, bungeeFontLoaded, woffFontLoaded,
woff2FontLoaded, ttcFontLoaded]).then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const canvas = surface.getCanvas();
const fontPaint = new CanvasKit.SkPaint();
fontPaint.setAntiAlias(true);
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
const inputs = [{
type: '.ttf font',
buffer: bungeeFontBuffer,
y: 60,
},{
type: '.otf font',
buffer: otfFontBuffer,
y: 90,
},{
// Not currently supported by Skia
type: '.woff font',
buffer: woffFontBuffer,
y: 120,
},{
// Not currently supported by Skia
type: '.woff2 font',
buffer: woff2FontBuffer,
y: 150,
}];
const defaultFont = new CanvasKit.SkFont(null, 24);
canvas.drawText(`The following should be ${inputs.length + 1} lines of text:`, 5, 30, fontPaint, defaultFont);
for (const fontType of inputs) {
// smoke test that the font bytes loaded.
expect(fontType.buffer).toBeTruthy(fontType.type + ' did not load');
const typeface = fontMgr.MakeTypefaceFromData(fontType.buffer);
const font = new CanvasKit.SkFont(typeface, 24);
if (font && typeface) {
canvas.drawText(fontType.type + ' loaded', 5, fontType.y, fontPaint, font);
} else {
canvas.drawText(fontType.type + ' *not* loaded', 5, fontType.y, fontPaint, defaultFont);
}
font && font.delete();
typeface && typeface.delete();
}
// The only ttc font I could find was 14 MB big, so I'm using the smaller test font,
// which doesn't have very many glyphs in it, so we just check that we got a non-zero
// typeface for it. I was able to load NotoSansCJK-Regular.ttc just fine in a
// manual test.
expect(ttcFontBuffer).toBeTruthy('.ttc font did not load');
const typeface = fontMgr.MakeTypefaceFromData(ttcFontBuffer);
expect(typeface).toBeTruthy('.ttc font');
if (typeface) {
canvas.drawText('.ttc loaded', 5, 180, fontPaint, defaultFont);
typeface.delete();
} else {
canvas.drawText('.ttc *not* loaded', 5, 180, fontPaint, defaultFont);
}
defaultFont.delete();
fontPaint.delete();
fontMgr.delete();
reportSurface(surface, 'various_font_formats', done);
}));
});
it('can measure text very precisely with proper settings', function(done) {
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const typeface = fontMgr.MakeTypefaceFromData(notSerifFontBuffer);
const fontSizes = [257, 100, 11];
// The point of these values is to let us know 1) we can measure to sub-pixel levels
// and 2) that measurements don't drastically change. If these change a little bit,
// just update them with the new values. For super-accurate readings, one could
// run a C++ snippet of code and compare the values, but that is likely unnecessary
// unless we suspect a bug with the bindings.
const expectedSizes = [1178.71143, 458.64258, 50.450683]
for (const idx in fontSizes) {
const font = new CanvasKit.SkFont(typeface, fontSizes[idx]);
font.setHinting(CanvasKit.FontHinting.None);
font.setLinearMetrics(true);
font.setSubpixel(true);
const res = font.measureText('someText');
expect(res).toBeCloseTo(expectedSizes[idx], 5);
font.delete();
}
typeface.delete();
fontMgr.delete();
done();
}));
});
});