Skip to content

Commit

Permalink
Fix the rounded and shaded box character rendering
Browse files Browse the repository at this point in the history
fixes #296
  • Loading branch information
sedwards2009 committed Oct 17, 2020
1 parent 9d18ceb commit 54e24ec
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 5 deletions.
146 changes: 146 additions & 0 deletions packages/extraterm-char-render-canvas/src/ColorUtilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2020 Simon Edwards <[email protected]>
*
* This source code is licensed under the MIT license which is detailed in the LICENSE.txt file.
*/

//-------------------------------------------------------------------------
/**
* Utility class for handling CSS colors
*/
export class Color {

_red: number;
_green: number;
_blue: number;
_opacity: number; // 0-255
_hexString: string = null;
_rgbaString: string = null;

/**
* Creates a color object.
*
* @param {string | number} redOrString [description]
* @param {number} green [description]
* @param {number} blue [description]
* @param {number} opacity [description]
* @return {[type]} [description]
*/
constructor(redOrString: string | number, green?: number, blue?: number, opacity?: number) {
if (typeof redOrString === "string") {
const stringColor = <string> redOrString;
if (stringColor.startsWith("#")) {
if (stringColor.length === 4) {
// Parse the 4bit colour values and expand then to 8bit.
this._red = parseInt(stringColor.slice(1, 2), 16) * 17;
this._green = parseInt(stringColor.slice(2, 3), 16) * 17;
this._blue = parseInt(stringColor.slice(3, 4), 16) * 17;
this._opacity = 255;
} else if (stringColor.length === 4) {
// Parse the 4bit colour values and expand then to 8bit.
this._red = parseInt(stringColor.slice(1, 2), 16) * 17;
this._green = parseInt(stringColor.slice(2, 3), 16) * 17;
this._blue = parseInt(stringColor.slice(3, 4), 16) * 17;
this._opacity = parseInt(stringColor.slice(4, 5), 16) * 17;

} else if (stringColor.length === 7) {
this._red = parseInt(stringColor.slice(1, 3), 16);
this._green = parseInt(stringColor.slice(3, 5), 16);
this._blue = parseInt(stringColor.slice(5, 7), 16);
this._opacity = 255;

} else if (stringColor.length === 9) {
this._red = parseInt(stringColor.slice(1, 3), 16);
this._green = parseInt(stringColor.slice(3, 5), 16);
this._blue = parseInt(stringColor.slice(5, 7), 16);
this._opacity = parseInt(stringColor.slice(7, 9), 16);
} else {
// Malformed hex colour.

}

} else {
// What now?!
}
} else {
// Assume numbers.
const red = <number> redOrString;
this._red = red;
this._green = green !== undefined ? green : 0;
this._blue = blue !== undefined ? blue : 0;
this._opacity = opacity !== undefined ? opacity : 255;
}
}
/**
* Returns the color as a 6 digit hex string.
*
* @return the color as a CSS style hex string.
*/
toHexString(): string {
if (this._hexString === null) {
this._hexString = "#" + to2DigitHex(this._red) + to2DigitHex(this._green) + to2DigitHex(this._blue);
}
return this._hexString;
}

/**
* Returns the color as a CSS rgba() value.
*
* @return the color as a CSS rgba() value.
*/
toRGBAString(): string {
if (this._rgbaString === null) {
this._rgbaString = "rgba(" + this._red + "," + this._green + "," + this._blue + "," + (this._opacity/255) + ")";
}
return this._rgbaString;
}

toRGBA(): number {
return (this._red << 24) | (this._green << 16) | (this._blue << 8) | this._opacity;
}

/**
* Returns the color as a CSS string.
*
* @return the color as a CSS formatted string.
*/
toString(): string {
if (this._opacity === 255) {
// Use a hex representation.
return this.toHexString();
} else {
return this.toRGBAString();
}
}

/**
* Creates a new color with the given opacity value.
*
* @param newOpacity A number from 0 to 1.
* @return the new color.
*/
opacity(newOpacity: number): Color {
return new Color(this._red, this._green, this._blue, newOpacity);
}

mix(otherColor: Color, fraction=0.5): Color {
const rightFraction = fraction;

const red = Math.min(255, Math.round(fraction * this._red + rightFraction * otherColor._red));
const green = Math.min(255, Math.round(fraction * this._green + rightFraction * otherColor._green));
const blue = Math.min(255, Math.round(fraction * this._blue + rightFraction * otherColor._blue));
const opacity = Math.min(255, Math.round(fraction* (this._opacity/255) + rightFraction * (otherColor._opacity/255)));
return new Color(red, green, blue, opacity);
}
}

/**
* Converts an 8bit number to a 2 digit hexadecimal string.
*
* @param {number} value An integer in the range 0-255 inclusive.
* @return {string} the converted number.
*/
export function to2DigitHex(value: number): string {
const h = value.toString(16);
return h.length === 1 ? "0" + h : h;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright 2019 Simon Edwards <[email protected]>
*/
import { Logger, getLogger, log } from "extraterm-logging";
import { Color } from "../ColorUtilities";


const _log = getLogger("BoxDrawingCharacters");
Expand Down Expand Up @@ -98,7 +99,7 @@ export function drawBoxCharacter(ctx: CanvasRenderingContext2D, codePoint: numbe
case GlyphRenderer.ARC_DOWN_AND_LEFT:
case GlyphRenderer.ARC_UP_AND_LEFT:
case GlyphRenderer.ARC_UP_AND_RIGHT:
drawArcDownAndRight(ctx, thisGlyphData.glyphRenderer, dx, dy, width, height);
drawRoundArc(ctx, thisGlyphData.glyphRenderer, dx, dy, width, height);
break;
}
}
Expand Down Expand Up @@ -301,7 +302,10 @@ function compute8x5GlyphGrid(width: number, height: number): GlyphGridMetrics {

function drawShade(ctx: CanvasRenderingContext2D, dx: number, dy: number, width: number, height: number, alpha: number): void {
ctx.save();
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;

const fillColor = new Color(<string> ctx.fillStyle);
ctx.fillStyle = fillColor.opacity(alpha * 255).toRGBAString();

ctx.fillRect(dx, dy, width, height);
ctx.restore();
}
Expand Down Expand Up @@ -343,7 +347,7 @@ interface ArcStartEndPoint {
endPointY: number;
}

const arcStartEndPoints = {
const arcStartEndPoints: { [index: number]: ArcStartEndPoint } = {
[GlyphRenderer.ARC_DOWN_AND_RIGHT]: {
startPointX: 0.5,
startPointY: 1,
Expand All @@ -370,7 +374,7 @@ const arcStartEndPoints = {
},
};

function drawArcDownAndRight(ctx: CanvasRenderingContext2D, renderer: GlyphRenderer, dx: number, dy: number,
function drawRoundArc(ctx: CanvasRenderingContext2D, renderer: GlyphRenderer, dx: number, dy: number,
width: number, height: number): void {

const metrics = compute5x5GlyphGrid(width, height);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ export abstract class FontAtlasBase<CG extends CachedGlyph> {
ctx.fillRect(xPx, yPx, widthInCells * this._metrics.widthPx, this._metrics.heightPx);

ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = RGBAToCss(fgRGBA);
const fgCSS = RGBAToCss(fgRGBA);
ctx.fillStyle = fgCSS;
ctx.strokeStyle = fgCSS;

if (isBoxCharacter(codePoint)) {
drawBoxCharacter(ctx, codePoint, xPx, yPx, this._metrics.widthPx, this._metrics.heightPx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class FontMeasurement {
ctx.fillRect(0, 0, this._canvasWidthPx, this._canvasHeightPx);
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "#ffffffff";
ctx.strokeStyle = "#ffffffff";

const textXY = Math.ceil(fontSizePx);
ctx.fillText(text, textXY, textXY);
Expand Down

0 comments on commit 54e24ec

Please sign in to comment.