Skip to content

Commit

Permalink
improve text metrics measurement and compatibility with other browsers
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengyao-lin committed Nov 28, 2019
1 parent ca25be0 commit 8528aee
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 10 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"version": "0.0.1",
"description": "",
"scripts": {
"build": "npx webpack",
"build": "npx webpack --mode production",
"build-dev": "npx webpack --mode development",
"test": "npx tsc --noEmit && npx mocha -r ts-node/register tests/**/*.test.ts",
"coverage": "npx nyc -r text -r lcov -n src -e .ts npm run test",
"demo": "npx ts-node demo/server.ts"
Expand Down
23 changes: 14 additions & 9 deletions src/yterm/canvas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assert } from "./utils";
import { Renderer, Block, Intensity, TextStyle } from "./renderer";
import { ColorScheme, TangoColorScheme } from "./schemes";
import { TextMetrics } from "./metrics";

/**
* Abstraction for font
Expand All @@ -25,6 +26,10 @@ export class Font {
getContextFont (style = "normal", weight = "normal"): string {
return `${style} ${weight} ${this.getSize()}px ${this.getFamily()}`;
}

measure (): TextMetrics {
return new TextMetrics(this.family, this.size);
}
}

/**
Expand Down Expand Up @@ -306,7 +311,7 @@ class TextSelector {
const { fontWidth, fontHeight } = this.canvasRenderer.getFontDimensioin();
const { columns } = this.canvasRenderer.getGridSize();

assert(startRow <= endRow, "startRow > endRow");
assert(startRow <= endRow, `invalid start and end row ${startRow}, ${endRow}`);

this.selectionLayerContext.save();

Expand Down Expand Up @@ -686,17 +691,17 @@ export class CanvasRenderer extends Renderer {

/**
* Set font and measure the dimension of the current font
* TODO: invetigate compatibility
*/
private setFont (font: Font) {
this.textLayerContext.save();
this.textLayerContext.font = font.getContextFont();
const metrics = this.textLayerContext.measureText("█");
this.textLayerContext.restore();

this.font = font;
this.fontWidth = metrics.width;
this.fontHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
this.fontDescent = metrics.actualBoundingBoxDescent;
const metrics = this.font.measure();

this.fontDescent = metrics.measureMaxDescent();
this.fontWidth = metrics.measureMaxWidth() + 1;
this.fontHeight = metrics.measureMaxHeight() + this.fontDescent;

console.log(this.fontWidth, this.fontHeight, this.fontDescent);
}

/**
Expand Down
95 changes: 95 additions & 0 deletions src/yterm/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Utility class for measruing text metrics
* measuring tricks from https://github.com/soulwire/FontMetrics
*/
export class TextMetrics {
private fontFamily: string;
private fontSize: number;

private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;

constructor (fontFamily: string, fontSize: number) {
this.fontFamily = fontFamily;
this.fontSize = fontSize;

this.canvas = document.createElement("canvas");

// setting the dimension to 2 * fontSize in case of any oferflow
this.canvas.width = fontSize * 2;
this.canvas.height = fontSize * 2;

this.ctx = this.canvas.getContext("2d")!;
}

/**
* max distance from any ascender to any descender
*/
measureMaxHeight (): number {
return this.getDimension("h").height -
this.getDimension("x").height +
this.getDimension("g").height;
}

measureMaxWidth (): number {
return this.getDimension("W").width;
}

measureMaxDescent (): number {
return this.getDimension("g").height -
this.getDimension("x").height;
}

private getCanvasFont (): string {
return `${this.fontSize}px ${this.fontFamily}`;
}

private clearCanvas () {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}

private pixelIndexToPosition (index: number): {x: number, y: number} {
index = Math.floor(index / 4);
return {
x: index % this.canvas.width,
y: Math.floor(index / this.canvas.width)
};
}

private getDimension (text: string): { width: number, height: number } {
this.clearCanvas();

this.ctx.font = this.getCanvasFont();
this.ctx.fillStyle = "rgba(255, 255, 255, 255)";
this.ctx.fillText(text, this.fontSize, this.fontSize);

const pixels = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data;

console.log(pixels);

let minX = this.canvas.width, maxX = 0;
let minY = this.canvas.height, maxY = 0;

for (let i = 0; i < pixels.length; i++) {
if (pixels[i] != 0) {
const {x, y} = this.pixelIndexToPosition(i);

if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}

console.log({
text: text,
width: maxX - minX,
height: maxY - minY
});

return {
width: maxX - minX,
height: maxY - minY
};
}
};

0 comments on commit 8528aee

Please sign in to comment.