Add PATH_NEGATIVE, simplify 1FBBD-1FBBF

This commit is contained in:
Daniel Imms
2025-12-22 16:22:06 -08:00
parent 66c1316f87
commit e8dcf46f39
4 changed files with 80 additions and 5 deletions
+1 -1
View File
@@ -514,7 +514,7 @@ export class TextureAtlas implements ITextureAtlas {
// Draw custom characters if applicable
let customGlyph = false;
if (this._config.customGlyphs !== false) {
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.fontSize, this._config.devicePixelRatio);
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css);
}
// Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -549,9 +549,9 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// TODO: Consider implementing
// Negative terminal graphic characters (1FBBD-1FBBF)
'\u{1FBBD}': { type: CustomGlyphDefinitionType.PATH, data: 'M.07,0 L.93,0 L.5,.43 Z M1,.07 L1,.93 L.57,.5 Z M.93,1 L.07,1 L.5,.57 Z M0,.93 L0,.07 L.43,.5 Z' }, // NEGATIVE DIAGONAL CROSS
'\u{1FBBE}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.43 L.43,1 L0,1 Z M1,.57 L.57,1 L1,1 Z' }, // NEGATIVE DIAGONAL MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBBF}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L.43,0 L0,.43 Z M.57,0 L1,0 L1,.43 Z M1,.57 L1,1 L.57,1 Z M0,.57 L.43,1 L0,1 Z M.5,.07 L.93,.5 L.5,.93 L.07,.5 Z' }, // NEGATIVE DIAGONAL DIAMOND
'\u{1FBBD}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1 Z', type: CustomGlyphVectorType.STROKE } }, // NEGATIVE DIAGONAL CROSS
'\u{1FBBE}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M1,.5 L.5,1', type: CustomGlyphVectorType.STROKE } }, // NEGATIVE DIAGONAL MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBBF}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.5,0 L1,.5 L.5,1 L0,.5 Z', type: CustomGlyphVectorType.STROKE } }, // NEGATIVE DIAGONAL DIAMOND
// Terminal graphic characters (1FBC0-1FBCA)
@@ -19,7 +19,8 @@ export function tryDrawCustomGlyph(
deviceCellWidth: number,
deviceCellHeight: number,
fontSize: number,
devicePixelRatio: number
devicePixelRatio: number,
backgroundColor?: string
): boolean {
const unifiedCharDefinition = customGlyphDefinitions[c];
if (unifiedCharDefinition) {
@@ -44,6 +45,9 @@ export function tryDrawCustomGlyph(
case CustomGlyphDefinitionType.PATH:
drawPathDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.PATH_NEGATIVE:
drawPathNegativeDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, backgroundColor);
return true;
case CustomGlyphDefinitionType.VECTOR_SHAPE:
drawVectorShape(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio);
return true;
@@ -111,6 +115,75 @@ function drawPathDefinitionCharacter(
ctx.fill();
}
/**
* Draws a "negative" path where the background color is used to draw the shape on top of a
* foreground-filled cell. This creates the appearance of a cutout without using actual
* transparency, which allows SPAA (subpixel anti-aliasing) to work correctly.
*
* @param ctx The canvas rendering context (fillStyle should be set to foreground color)
* @param charDefinition The vector shape definition for the negative shape
* @param xOffset The x offset to draw at
* @param yOffset The y offset to draw at
* @param deviceCellWidth The width of the cell in device pixels
* @param deviceCellHeight The height of the cell in device pixels
* @param devicePixelRatio The device pixel ratio
* @param backgroundColor The background color to use for the "cutout" portion
*/
function drawPathNegativeDefinitionCharacter(
ctx: CanvasRenderingContext2D,
charDefinition: ICustomGlyphVectorShape,
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number,
devicePixelRatio: number,
backgroundColor?: string
): void {
ctx.save();
// First, fill the entire cell with foreground color
ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
// Then draw the "negative" shape with the background color
if (backgroundColor) {
ctx.fillStyle = backgroundColor;
ctx.strokeStyle = backgroundColor;
}
ctx.lineWidth = devicePixelRatio;
ctx.lineCap = 'square';
ctx.beginPath();
for (const instruction of charDefinition.d.split(' ')) {
const type = instruction[0];
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
if (type === 'Z') {
ctx.closePath();
}
continue;
}
const translatedArgs = args.map((e, i) => {
const val = parseFloat(e);
return i % 2 === 0
? xOffset + val * deviceCellWidth
: yOffset + val * deviceCellHeight;
});
if (type === 'M') {
ctx.moveTo(translatedArgs[0], translatedArgs[1]);
} else if (type === 'L') {
ctx.lineTo(translatedArgs[0], translatedArgs[1]);
}
}
if (charDefinition.type === CustomGlyphVectorType.STROKE) {
ctx.stroke();
} else {
ctx.fill();
}
ctx.restore();
}
const cachedPatterns: Map<CustomGlyphPatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
function drawPatternChar(
@@ -39,6 +39,7 @@ export const enum CustomGlyphDefinitionType {
PATH_FUNCTION,
PATH_FUNCTION_WITH_WEIGHT,
PATH,
PATH_NEGATIVE,
VECTOR_SHAPE,
}
@@ -55,5 +56,6 @@ export type CustomGlyphCharacterDefinition = (
{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition } |
{ type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [fontWeight: number]: string | CustomGlyphPathDrawFunctionDefinition } } |
{ type: CustomGlyphDefinitionType.PATH, data: string } |
{ type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape }
);