Merge remote-tracking branch 'origin/master' into lint

This commit is contained in:
Daniel Imms
2025-12-24 11:16:01 -08:00
19 changed files with 1060 additions and 484 deletions
+3 -2
View File
@@ -32,9 +32,10 @@ export function acquireTextureAtlas(
deviceCharWidth: number,
deviceCharHeight: number,
devicePixelRatio: number,
deviceMaxTextureSize: number
deviceMaxTextureSize: number,
customGlyphs: boolean = true
): ITextureAtlas {
const newConfig = generateConfig(deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, options, colors, devicePixelRatio, deviceMaxTextureSize);
const newConfig = generateConfig(deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, options, colors, devicePixelRatio, deviceMaxTextureSize, customGlyphs);
// Check to see if the terminal already owns this config
for (let i = 0; i < charAtlasCache.length; i++) {
+2 -2
View File
@@ -9,7 +9,7 @@ import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number): ICharAtlasConfig {
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors: IColorSet = {
foreground: colors.foreground,
@@ -32,7 +32,7 @@ export function generateConfig(deviceCellWidth: number, deviceCellHeight: number
halfContrastCache: colors.halfContrastCache
};
return {
customGlyphs: options.customGlyphs,
customGlyphs,
devicePixelRatio,
deviceMaxTextureSize,
letterSpacing: options.letterSpacing,
+8 -4
View File
@@ -4,7 +4,7 @@
*/
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -28,9 +28,10 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
private readonly _onContextLoss = this._register(new Emitter<void>());
public readonly onContextLoss = this._onContextLoss.event;
constructor(
private _preserveDrawingBuffer?: boolean
) {
private readonly _customGlyphs: boolean;
private readonly _preserveDrawingBuffer?: boolean;
constructor(options?: IWebglAddonOptions) {
if (isSafari && getSafariVersion() < 16) {
// Perform an extra check to determine if Webgl2 is manually enabled in developer settings
const contextAttributes = {
@@ -44,6 +45,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
}
}
super();
this._customGlyphs = options?.customGlyphs ?? true;
this._preserveDrawingBuffer = options?.preserveDrawingBuffer;
}
public activate(terminal: Terminal): void {
@@ -79,6 +82,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
decorationService,
optionsService,
themeService,
this._customGlyphs,
this._preserveDrawingBuffer
));
this._register(Event.forward(this._renderer.onContextLoss, this._onContextLoss));
+3 -1
View File
@@ -72,6 +72,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _decorationService: IDecorationService,
private readonly _optionsService: IOptionsService,
private readonly _themeService: IThemeService,
private readonly _customGlyphs: boolean = true,
preserveDrawingBuffer?: boolean
) {
super();
@@ -278,7 +279,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this.dimensions.device.char.width,
this.dimensions.device.char.height,
this._coreBrowserService.dpr,
this._deviceMaxTextureSize
this._deviceMaxTextureSize,
this._customGlyphs
);
if (this._charAtlas !== atlas) {
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
import { CustomGlyphDefinitionType, CustomGlyphVectorType, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type CustomGlyphRegionDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
import { CustomGlyphDefinitionType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
/**
* Try drawing a custom block element or box drawing character, returning whether it was
@@ -24,42 +24,63 @@ export function tryDrawCustomGlyph(
): boolean {
const unifiedCharDefinition = customGlyphDefinitions[c];
if (unifiedCharDefinition) {
switch (unifiedCharDefinition.type) {
case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:
drawBlockVectorChar(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.BLOCK_PATTERN:
drawPatternChar(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION:
drawBlockPatternWithRegion(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR:
drawBlockPatternWithRegion(ctx, unifiedCharDefinition.data.pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
drawBlockVectorChar(ctx, unifiedCharDefinition.data.vectors, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH:
drawBlockPatternWithClipPath(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
return true;
case CustomGlyphDefinitionType.PATH_FUNCTION:
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;
case CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT:
drawPathDefinitionCharacterWithWeight(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio);
return true;
// Normalize to array for uniform handling
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
for (const part of parts) {
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio, backgroundColor);
}
return true;
}
return false;
}
function drawDefinitionPart(
ctx: CanvasRenderingContext2D,
part: CustomGlyphDefinitionPart,
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number,
fontSize: number,
devicePixelRatio: number,
backgroundColor?: string
): void {
// Handle clipPath generically for any definition type
if (part.clipPath) {
ctx.save();
applyClipPath(ctx, part.clipPath, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
}
switch (part.type) {
case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:
drawBlockVectorChar(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
break;
case CustomGlyphDefinitionType.BLOCK_PATTERN:
drawPatternChar(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
break;
case CustomGlyphDefinitionType.PATH_FUNCTION:
drawPathFunctionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, part.strokeWidth);
break;
case CustomGlyphDefinitionType.PATH:
drawPathDefinitionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
break;
case CustomGlyphDefinitionType.PATH_NEGATIVE:
drawPathNegativeDefinitionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, backgroundColor);
break;
case CustomGlyphDefinitionType.VECTOR_SHAPE:
drawVectorShape(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio);
break;
case CustomGlyphDefinitionType.BRAILLE:
drawBrailleCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
break;
}
if (part.clipPath) {
ctx.restore();
}
}
function drawBlockVectorChar(
ctx: CanvasRenderingContext2D,
charDefinition: ICustomGlyphSolidOctantBlockVector[],
@@ -81,6 +102,52 @@ function drawBlockVectorChar(
}
}
/**
* Braille dot positions in octant coordinates (x, y for center of each dot area)
* Columns: left=1-2, right=5-6 (leaving 0 and 7 as margins, 3-4 as gap)
* Rows: 0-1, 2-3, 4-5, 6-7 for the 4 rows
*/
const brailleDotPositions = new Uint8Array([
1, 0, // dot 1 - bit 0
1, 2, // dot 2 - bit 1
1, 4, // dot 3 - bit 2
5, 0, // dot 4 - bit 3
5, 2, // dot 5 - bit 4
5, 4, // dot 6 - bit 5
1, 6, // dot 7 - bit 6
5, 6, // dot 8 - bit 7
]);
/**
* Draws a braille pattern
*/
function drawBrailleCharacter(
ctx: CanvasRenderingContext2D,
pattern: number,
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number
): void {
const xEighth = deviceCellWidth / 8;
const paddingY = deviceCellHeight * 0.1;
const usableHeight = deviceCellHeight * 0.8;
const yEighth = usableHeight / 8;
const radius = Math.min(xEighth, yEighth);
for (let bit = 0; bit < 8; bit++) {
if (pattern & (1 << bit)) {
const x = brailleDotPositions[bit * 2];
const y = brailleDotPositions[bit * 2 + 1];
const cx = xOffset + (x + 1) * xEighth;
const cy = yOffset + paddingY + (y + 1) * yEighth;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
}
}
}
function drawPathDefinitionCharacter(
ctx: CanvasRenderingContext2D,
charDefinition: CustomGlyphPathDrawFunctionDefinition | string,
@@ -398,139 +465,66 @@ function drawPatternChar(
ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
}
/**
* Draws rectangular shade characters - medium shade pattern clipped to a region.
* Uses a checkerboard pattern that shifts 1px each row (same as medium shade U+2592).
*/
function drawBlockPatternWithRegion(
function drawPathFunctionCharacter(
ctx: CanvasRenderingContext2D,
definition: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition],
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number
): void {
const [pattern, region] = definition;
const [rx, ry, rw, rh] = region;
const regionX = Math.round(xOffset + rx * deviceCellWidth);
const regionY = Math.round(yOffset + ry * deviceCellHeight);
const regionW = Math.round(rw * deviceCellWidth);
const regionH = Math.round(rh * deviceCellHeight);
// Save context state
ctx.save();
// Clip to the region
ctx.beginPath();
ctx.rect(regionX, regionY, regionW, regionH);
ctx.clip();
// Draw the pattern
drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
// Restore context state
ctx.restore();
}
/**
* Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to
* canvas draw calls.
*
* Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐
* ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘
* ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤
* └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘
*
* Other:
* ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈
* │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉
* ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋
*
* All box drawing characters:
* ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
* ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
* ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
* ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
* ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
* ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
* ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
* ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
*
* ---
*
* Box drawing alignment tests: █
* ▉
* ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
* ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
* ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
* ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
* ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
* ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
* ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
*
* Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html
*/
function drawPathDefinitionCharacterWithWeight(
ctx: CanvasRenderingContext2D,
charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
charDefinition: string | ((xp: number, yp: number) => string),
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number,
devicePixelRatio: number
devicePixelRatio: number,
strokeWidth?: number
): void {
ctx.strokeStyle = ctx.fillStyle;
for (const [fontWeight, instructions] of Object.entries(charDefinition)) {
ctx.beginPath();
ctx.lineWidth = devicePixelRatio * Number.parseInt(fontWeight);
let actualInstructions: string;
if (typeof instructions === 'function') {
const xp = .15;
const yp = .15 / deviceCellHeight * deviceCellWidth;
actualInstructions = instructions(xp, yp);
} else {
actualInstructions = instructions;
}
for (const instruction of actualInstructions.split(' ')) {
const type = instruction[0];
if (type === 'Z') {
ctx.closePath();
continue;
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio));
}
ctx.stroke();
ctx.closePath();
ctx.beginPath();
let actualInstructions: string;
if (typeof charDefinition === 'function') {
const xp = .15;
const yp = .15 / deviceCellHeight * deviceCellWidth;
actualInstructions = charDefinition(xp, yp);
} else {
actualInstructions = charDefinition;
}
const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
for (const instruction of actualInstructions.split(' ')) {
const type = instruction[0];
if (type === 'Z') {
ctx.closePath();
state.lastCommand = type;
continue;
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio), state);
state.lastCommand = type;
}
if (strokeWidth !== undefined) {
ctx.strokeStyle = ctx.fillStyle;
ctx.lineWidth = devicePixelRatio * strokeWidth;
ctx.stroke();
} else {
ctx.fill();
}
ctx.closePath();
}
/**
* Draws a pattern clipped to an arbitrary path (for triangular shades, etc.)
* Applies a clip path to the canvas context from SVG-like path instructions.
*/
function drawBlockPatternWithClipPath(
function applyClipPath(
ctx: CanvasRenderingContext2D,
definition: [pattern: CustomGlyphPatternDefinition, clipPath: string],
clipPath: string,
xOffset: number,
yOffset: number,
deviceCellWidth: number,
deviceCellHeight: number
): void {
const [pattern, clipPath] = definition;
ctx.save();
// Build clip path from SVG-like instructions
ctx.beginPath();
for (const instruction of clipPath.split(' ')) {
const type = instruction[0];
@@ -551,11 +545,6 @@ function drawBlockPatternWithClipPath(
}
}
ctx.clip();
// Draw the pattern
drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
ctx.restore();
}
function drawVectorShape(
@@ -577,10 +566,12 @@ function drawVectorShape(
// Scale the stroke with DPR and font size
const cssLineWidth = fontSize / 12;
ctx.lineWidth = devicePixelRatio * cssLineWidth;
const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
for (const instruction of charDefinition.d.split(' ')) {
const type = instruction[0];
if (type === 'Z') {
ctx.closePath();
state.lastCommand = type;
continue;
}
const f = svgToCanvasInstructionMap[type];
@@ -602,7 +593,8 @@ function drawVectorShape(
devicePixelRatio,
(charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),
(charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)
));
), state);
state.lastCommand = type;
}
if (charDefinition.type === CustomGlyphVectorType.STROKE) {
ctx.strokeStyle = ctx.fillStyle;
@@ -617,11 +609,55 @@ function clamp(value: number, max: number, min: number = 0): number {
return Math.max(Math.min(value, max), min);
}
const svgToCanvasInstructionMap: { [index: string]: any } = {
'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]),
'Q': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.quadraticCurveTo(args[0], args[1], args[2], args[3])
interface ISvgPathState {
currentX: number;
currentY: number;
lastControlX: number;
lastControlY: number;
lastCommand: string;
}
const svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext2D, args: number[], state: ISvgPathState) => void } = {
'C': (ctx, args, state) => {
ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
state.lastControlX = args[2];
state.lastControlY = args[3];
state.currentX = args[4];
state.currentY = args[5];
},
'L': (ctx, args, state) => {
ctx.lineTo(args[0], args[1]);
state.lastControlX = state.currentX = args[0];
state.lastControlY = state.currentY = args[1];
},
'M': (ctx, args, state) => {
ctx.moveTo(args[0], args[1]);
state.lastControlX = state.currentX = args[0];
state.lastControlY = state.currentY = args[1];
},
'Q': (ctx, args, state) => {
ctx.quadraticCurveTo(args[0], args[1], args[2], args[3]);
state.lastControlX = args[0];
state.lastControlY = args[1];
state.currentX = args[2];
state.currentY = args[3];
},
'T': (ctx, args, state) => {
let cpX: number;
let cpY: number;
if (state.lastCommand === 'Q' || state.lastCommand === 'T') {
cpX = 2 * state.currentX - state.lastControlX;
cpY = 2 * state.currentY - state.lastControlY;
} else {
cpX = state.currentX;
cpY = state.currentY;
}
ctx.quadraticCurveTo(cpX, cpY, args[0], args[1]);
state.lastControlX = cpX;
state.lastControlY = cpY;
state.currentX = args[0];
state.currentY = args[1];
}
};
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
+23 -15
View File
@@ -33,29 +33,37 @@ export type CustomGlyphPatternDefinition = number[][];
export const enum CustomGlyphDefinitionType {
SOLID_OCTANT_BLOCK_VECTOR,
BLOCK_PATTERN,
BLOCK_PATTERN_WITH_REGION,
BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR,
BLOCK_PATTERN_WITH_CLIP_PATH,
PATH_FUNCTION,
PATH_FUNCTION_WITH_WEIGHT,
PATH,
PATH_NEGATIVE,
VECTOR_SHAPE,
BRAILLE,
}
export type CustomGlyphRegionDefinition = [x: number, y: number, w: number, h: number];
export type CustomGlyphCharacterDefinition = (
export type CustomGlyphDefinitionPartRaw = (
{ type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: ICustomGlyphSolidOctantBlockVector[] } |
{ type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: CustomGlyphPatternDefinition } |
{ type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION, data: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition] } |
// TODO: Consolidate these, draws should be possible via regions/clipping instead of special
// casing
{ type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR, data: { pattern: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition], vectors: ICustomGlyphSolidOctantBlockVector[] } } |
{ type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [pattern: CustomGlyphPatternDefinition, clipPath: string] } |
{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition } |
{ type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [fontWeight: number]: string | CustomGlyphPathDrawFunctionDefinition } } |
{ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition | string } |
{ type: CustomGlyphDefinitionType.PATH, data: string } |
{ type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape }
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape} |
{ type: CustomGlyphDefinitionType.BRAILLE, data: number }
);
export interface ICustomGlyphDefinitionCommon {
/**
* A custom clip path for the draw definition, restricting the area it can draw to.
*/
clipPath?: string;
/**
* The stroke width to use when drawing the path. Defaults to 1.
*/
strokeWidth?: number;
}
export type CustomGlyphDefinitionPart = CustomGlyphDefinitionPartRaw & ICustomGlyphDefinitionCommon;
/**
* A character definition that can be a single part or an array of parts drawn in sequence.
*/
export type CustomGlyphCharacterDefinition = CustomGlyphDefinitionPart | CustomGlyphDefinitionPart[];