Add multiline-comment-style lint rule and fix violations

Enable multiline-comment-style with starred-block to require JSDoc-style
block comments with content on separate lines from the opening delimiter.
Auto-fix existing violations across the codebase.

Fixes #5899

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-05-26 20:15:55 +00:00
co-authored by Daniel Imms
parent 1a67b6f4b3
commit 4ec85e67ba
138 changed files with 3040 additions and 1837 deletions
+12 -6
View File
@@ -47,8 +47,10 @@ export class CellColorResolver {
this.result.bg = cell.bg;
this.result.fg = cell.fg;
this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
// Get any foreground/background overrides, this happens on the model to avoid spreading
// override logic throughout the different sub-renderers
/*
* Get any foreground/background overrides, this happens on the model to avoid spreading
* override logic throughout the different sub-renderers
*/
// Reset overrides work variables
$bg = 0;
@@ -186,8 +188,10 @@ export class CellColorResolver {
}
});
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
// ahead of time in order to use the correct cache key
/*
* Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
* ahead of time in order to use the correct cache key
*/
if ($hasBg) {
if ($isSelected) {
// Non-RGB attributes from model + force non-dim + override + force RGB color mode
@@ -202,8 +206,10 @@ export class CellColorResolver {
$fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB;
}
// Handle case where inverse was specified by only one of bg override or fg override was set,
// resolving the other inverse color and setting the inverse flag if needed.
/*
* Handle case where inverse was specified by only one of bg override or fg override was set,
* resolving the other inverse color and setting the inverse flag if needed.
*/
if (this.result.fg & FgFlags.INVERSE) {
if ($hasBg && !$hasFg) {
// Resolve bg color type (default color has a different meaning in fg vs bg)
+4 -2
View File
@@ -13,8 +13,10 @@ import type { ILogService } from 'common/services/Services';
interface ITextureAtlasCacheEntry {
atlas: ITextureAtlas;
config: ICharAtlasConfig;
// N.B. This implementation potentially holds onto copies of the terminal forever, so
// this may cause memory leaks.
/*
* N.B. This implementation potentially holds onto copies of the terminal forever, so
* this may cause memory leaks.
*/
ownedBy: Terminal[];
}
+4 -2
View File
@@ -25,8 +25,10 @@ export function generateConfig(deviceCellWidth: number, deviceCellHeight: number
scrollbarSliderBackground: NULL_COLOR,
scrollbarSliderHoverBackground: NULL_COLOR,
scrollbarSliderActiveBackground: NULL_COLOR,
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
// dynamic character atlas.
/*
* For the static char atlas, we only use the first 16 colors, but we need all 256 for the
* dynamic character atlas.
*/
ansi: colors.ansi.slice(),
contrastCache: colors.contrastCache,
halfContrastCache: colors.halfContrastCache
+5 -3
View File
@@ -6,7 +6,9 @@
import { isFirefox, isLegacyEdge } from 'common/Platform';
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
// would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
/*
* The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
* would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
* unaligned Powerline fonts (PR 3356#issuecomment-850928179).
*/
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
@@ -87,13 +87,17 @@ export class CursorBlinkStateManager {
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
// the regular interval is setup in order to support restarting the blink
// animation in a lightweight way (without thrashing clearInterval and
// setInterval).
/*
* Setup the initial timeout which will hide the cursor, this is done before
* the regular interval is setup in order to support restarting the blink
* animation in a lightweight way (without thrashing clearInterval and
* setInterval).
*/
this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => {
// Check if another animation restart was requested while this was being
// started
/*
* Check if another animation restart was requested while this was being
* started
*/
if (this._animationTimeRestarted) {
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = undefined;
@@ -114,8 +118,10 @@ export class CursorBlinkStateManager {
this._blinkInterval = this._coreBrowserService.window.setInterval(() => {
// Adjust the animation time if it was restarted
if (this._animationTimeRestarted) {
// calc time diff
// Make restart interval do a setTimeout initially?
/*
* calc time diff
* Make restart interval do a setTimeout initially?
*/
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = undefined;
this._restartInterval(time);
@@ -6,10 +6,12 @@
import { toDisposable, IDisposable } from 'common/Lifecycle';
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
// Observe any resizes to the element and extract the actual pixel size of the element if the
// devicePixelContentBoxSize API is supported. This allows correcting rounding errors when
// converting between CSS pixels and device pixels which causes blurry rendering when device
// pixel ratio is not a round number.
/*
* Observe any resizes to the element and extract the actual pixel size of the element if the
* devicePixelContentBoxSize API is supported. This allows correcting rounding errors when
* converting between CSS pixels and device pixels which causes blurry rendering when device
* pixel ratio is not a round number.
*/
let observer: ResizeObserver | undefined = new parentWindow.ResizeObserver((entries) => {
const entry = entries.find((entry) => entry.target === element);
if (!entry) {
+37 -23
View File
@@ -148,9 +148,11 @@ export class GlyphRenderer extends Disposable {
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
// Setup the unit quad element array buffer, this points to indices in
// unitQuadVertices to allow is to draw 2 triangles from the vertices via a
// triangle strip
/*
* Setup the unit quad element array buffer, this points to indices in
* unitQuadVertices to allow is to draw 2 triangles from the vertices via a
* triangle strip
*/
const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);
const elementIndicesBuffer = gl.createBuffer();
this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));
@@ -189,8 +191,10 @@ export class GlyphRenderer extends Disposable {
gl.uniform1iv(this._textureLocation, textureUnits);
gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);
// Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures
// is ever drawn it will show characters as red rectangles.
/*
* Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures
* is ever drawn it will show characters as red rectangles.
*/
this._atlasTextures = [];
for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {
const glTexture = new GLTexture(throwIfFalsy(gl.createTexture()));
@@ -216,18 +220,22 @@ export class GlyphRenderer extends Disposable {
}
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
// Since this function is called for every cell (`rows*cols`), it must be very optimized. It
// should not instantiate any variables unless a new glyph is drawn to the cache where the
// slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
// each glyph.
/*
* Since this function is called for every cell (`rows*cols`), it must be very optimized. It
* should not instantiate any variables unless a new glyph is drawn to the cache where the
* slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
* each glyph.
*/
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);
}
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
$i = (y * this._terminal.cols + x) * Constants.INDICES_PER_CELL;
// Exit early if this is a null character, allow space character to continue as it may have
// underline/strikethrough styles
/*
* Exit early if this is a null character, allow space character to continue as it may have
* underline/strikethrough styles
*/
if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
array.fill(0, $i, $i + Constants.INDICES_PER_CELL - 1 - Constants.CELL_POSITION_INDICES);
return;
@@ -279,8 +287,10 @@ export class GlyphRenderer extends Disposable {
}
// a_cellpos only changes on resize
// Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
// following cell (ie. the width is not 2).
/*
* Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
* following cell (ie. the width is not 2).
*/
if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {
if (allowRescaling(code, width, $glyph.size.x, this._dimensions.device.cell.width)) {
array[$i + 2] = (this._dimensions.device.cell.width - 1) / this._dimensions.device.canvas.width; // - 1 to improve readability
@@ -339,13 +349,15 @@ export class GlyphRenderer extends Disposable {
this._activeBuffer = (this._activeBuffer + 1) % 2;
const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer];
// Copy data for each cell of each line up to its line length (the last non-whitespace cell)
// from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
// The reasons for this are as follows:
// - So the active buffer can be alternated so we don't get blocked on rendering finishing
// - To copy either the normal attributes buffer or the selection attributes buffer when there
// is a selection
// - So we don't send vertices for all the line-ending whitespace to the GPU
/*
* Copy data for each cell of each line up to its line length (the last non-whitespace cell)
* from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
* The reasons for this are as follows:
* - So the active buffer can be alternated so we don't get blocked on rendering finishing
* - To copy either the normal attributes buffer or the selection attributes buffer when there
* is a selection
* - So we don't send vertices for all the line-ending whitespace to the GPU
*/
let bufferLength = 0;
for (let y = 0; y < renderModel.lineLengths.length; y++) {
const si = y * this._terminal.cols * Constants.INDICES_PER_CELL;
@@ -358,9 +370,11 @@ export class GlyphRenderer extends Disposable {
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
// monotonic, so a page object swap at the same index (which happens after a page merge)
// is detected by the same comparison.
/*
* Bind the atlas page texture if they have changed. AtlasPage.version is globally
* monotonic, so a page object swap at the same index (which happens after a page merge)
* is detected by the same comparison.
*/
for (let i = 0; i < this._atlas.pages.length; i++) {
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
this._bindAtlasPageTexture(gl, this._atlas, i);
+5 -3
View File
@@ -114,9 +114,11 @@ export class RectangleRenderer extends Disposable {
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
// Setup the unit quad element array buffer, this points to indices in
// unitQuadVertices to allow is to draw 2 triangles from the vertices via a
// triangle strip
/*
* Setup the unit quad element array buffer, this points to indices in
* unitQuadVertices to allow is to draw 2 triangles from the vertices via a
* triangle strip
*/
const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);
const elementIndicesBuffer = gl.createBuffer();
this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));
+129 -77
View File
@@ -151,13 +151,17 @@ export class TextureAtlas implements ITextureAtlas {
}
private _createNewPage(): AtlasPage {
// Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
// microtask to ensure it does not interrupt textures that will be rendered in the current
// animation frame which would result in blank rendered areas. This is actually not that
// expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
/*
* Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
* microtask to ensure it does not interrupt textures that will be rendered in the current
* animation frame which would result in blank rendered areas. This is actually not that
* expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
*/
if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) {
// Find the set of the largest 4 images, below the maximum size, with the highest
// percentages used
/*
* Find the set of the largest 4 images, below the maximum size, with the highest
* percentages used
*/
const pagesBySize = this._pages.filter(e => {
return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE);
}).sort((a, b) => {
@@ -180,8 +184,10 @@ export class TextureAtlas implements ITextureAtlas {
// Gather details of the merge
const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
// Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
// effectively reduce page count and merging would cause issues.
/*
* Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
* effectively reduce page count and merging would cause issues.
*/
if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
const newPage = new AtlasPage(this._document, this._textureSize);
this._pages.push(newPage);
@@ -294,9 +300,11 @@ export class TextureAtlas implements ITextureAtlas {
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean, dim: boolean): IColor {
if (this._config.allowTransparency) {
// The background color might have some transparency, so we need to render it as fully
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
// around the anti-aliased edges of the glyph, and it would look too dark.
/*
* The background color might have some transparency, so we need to render it as fully
* transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
* around the anti-aliased edges of the glyph, and it would look too dark.
*/
return NULL_COLOR;
}
@@ -418,8 +426,10 @@ export class TextureAtlas implements ITextureAtlas {
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
// Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
// non-dim cells
/*
* Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
* non-dim cells
*/
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio / (dim ? 2 : 1));
if (!result) {
@@ -447,20 +457,26 @@ export class TextureAtlas implements ITextureAtlas {
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
// Uncomment for debugging
// console.log(`draw to cache "${chars}"`, bg, fg, ext);
/*
* Uncomment for debugging
* console.log(`draw to cache "${chars}"`, bg, fg, ext);
*/
// Attach the canvas to the DOM in order to inherit font-feature-settings
// from the parent elements. This is necessary for ligatures and variants to
// work.
/*
* Attach the canvas to the DOM in order to inherit font-feature-settings
* from the parent elements. This is necessary for ligatures and variants to
* work.
*/
if (domContainer && this._tmpCanvas.parentElement !== domContainer) {
this._tmpCanvas.style.display = 'none';
domContainer.append(this._tmpCanvas);
}
// Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
// to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
// giant ligatures (eg. =====>) don't impact overall performance.
/*
* Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
* to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
* giant ligatures (eg. =====>) don't impact overall performance.
*/
const allowedWidth = Math.min(this._config.deviceCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceMaxTextureSize);
if (this._tmpCanvas.width < allowedWidth) {
this._tmpCanvas.width = allowedWidth;
@@ -503,8 +519,10 @@ export class TextureAtlas implements ITextureAtlas {
// draw the background
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse, dim);
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,
// regardless of transparency in backgroundColor
/*
* Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,
* regardless of transparency in backgroundColor
*/
this._tmpCtx.globalCompositeOperation = 'copy';
this._tmpCtx.fillStyle = backgroundColor.css;
this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
@@ -532,9 +550,11 @@ export class TextureAtlas implements ITextureAtlas {
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
}
// Whether to clear pixels based on a threshold difference between the glyph color and the
// background color. This should be disabled when the glyph contains multiple colors such as
// underline colors to prevent important colors could get cleared.
/*
* Whether to clear pixels based on a threshold difference between the glyph color and the
* background color. This should be disabled when the glyph contains multiple colors such as
* underline colors to prevent important colors could get cleared.
*/
let enableClearThresholdCheck = !powerlineGlyph;
let chWidth: number;
@@ -599,8 +619,10 @@ export class TextureAtlas implements ITextureAtlas {
clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
this._tmpCtx.clip(clipRegion);
// Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
// style. The viewbox is 6x3 so scale it using that.
/*
* Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
* style. The viewbox is 6x3 so scale it using that.
*/
const cellW = this._config.deviceCellWidth;
const curlyH = (yBot - yTop);
const scaleX = cellW / 6;
@@ -671,15 +693,21 @@ export class TextureAtlas implements ITextureAtlas {
}
this._tmpCtx.restore();
// Draw stroke in the background color for non custom characters in order to give an outline
// between the text and the underline. Only do this when font size is >= 12 as the underline
// looks odd when the font size is too small
/*
* Draw stroke in the background color for non custom characters in order to give an outline
* between the text and the underline. Only do this when font size is >= 12 as the underline
* looks odd when the font size is too small
*/
if (!customGlyph && this._config.fontSize >= 12) {
// This only works when transparency is disabled because it's not clear how to clear stroked
// text
/*
* This only works when transparency is disabled because it's not clear how to clear stroked
* text
*/
if (!this._config.allowTransparency && chars !== ' ') {
// Measure the text, only draw the stroke if there is a descent beyond an alphabetic text
// baseline
/*
* Measure the text, only draw the stroke if there is a descent beyond an alphabetic text
* baseline
*/
this._tmpCtx.save();
this._tmpCtx.textBaseline = 'alphabetic';
const metrics = this._tmpCtx.measureText(chars);
@@ -687,9 +715,11 @@ export class TextureAtlas implements ITextureAtlas {
if ('actualBoundingBoxDescent' in metrics && metrics.actualBoundingBoxDescent > 0) {
// This translates to 1/2 the line width in either direction
this._tmpCtx.save();
// Clip the region to only draw in valid pixels near the underline to avoid a slight
// outline around the whole glyph, as well as additional pixels in the glyph at the top
// which would increase GPU memory demands
/*
* Clip the region to only draw in valid pixels near the underline to avoid a slight
* outline around the whole glyph, as well as additional pixels in the glyph at the top
* which would increase GPU memory demands
*/
const clipRegion = new Path2D();
clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth * chWidth, yBot - yTop + Math.ceil(lineWidth / 2));
this._tmpCtx.clip(clipRegion);
@@ -719,8 +749,10 @@ export class TextureAtlas implements ITextureAtlas {
this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight);
}
// If this character is underscore and beyond the cell bounds, shift it up until it is visible
// even on the bottom row, try for a maximum of 5 pixels.
/*
* If this character is underscore and beyond the cell bounds, shift it up until it is visible
* even on the bottom row, try for a maximum of 5 pixels.
*/
if (chars === '_' && !this._config.allowTransparency) {
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck);
if (isBeyondCellBounds) {
@@ -752,8 +784,10 @@ export class TextureAtlas implements ITextureAtlas {
this._tmpCtx.restore();
// clear the background from the character to avoid issues with drawing over the previous
// character if it extends past it's bounds
/*
* clear the background from the character to avoid issues with drawing over the previous
* character if it extends past it's bounds
*/
const imageData = this._tmpCtx.getImageData(
0, 0, this._tmpCanvas.width, this._tmpCanvas.height
);
@@ -796,12 +830,16 @@ export class TextureAtlas implements ITextureAtlas {
}
}
// TODO: This algorithm could be simplified:
// - Search for the page with ROW_PIXEL_THRESHOLD in mind
// - Keep track of current/fixed rows in a Map
/*
* TODO: This algorithm could be simplified:
* - Search for the page with ROW_PIXEL_THRESHOLD in mind
* - Keep track of current/fixed rows in a Map
*/
// Replace the best current row with a fixed row if there is one at least as good as the
// current row. Search in reverse to prioritize filling in older pages.
/*
* Replace the best current row with a fixed row if there is one at least as good as the
* current row. Search in reverse to prioritize filling in older pages.
*/
for (let i = this._activePages.length - 1; i >= 0; i--) {
for (const row of this._activePages[i].fixedRows) {
if (row.height <= activeRow.height && rasterizedGlyph.size.y <= row.height) {
@@ -832,12 +870,16 @@ export class TextureAtlas implements ITextureAtlas {
break;
}
// Create a new page if too much vertical space would be wasted or there is not enough room
// left in the page. The previous active row will become fixed in the process as it now has a
// fixed height
/*
* Create a new page if too much vertical space would be wasted or there is not enough room
* left in the page. The previous active row will become fixed in the process as it now has a
* fixed height
*/
if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) {
// Create the new fixed height row, creating a new page if there isn't enough room on the
// current page
/*
* Create the new fixed height row, creating a new page if there isn't enough room on the
* current page
*/
let wasPageAndRowFound = false;
if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) {
// Find the first page with room to create the new row on
@@ -852,10 +894,12 @@ export class TextureAtlas implements ITextureAtlas {
if (candidatePage) {
activePage = candidatePage;
} else {
// Before creating a new atlas page that would trigger a page merge, check if the
// current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will
// improve texture utilization by using the available space before the page is merged
// and becomes static.
/*
* Before creating a new atlas page that would trigger a page merge, check if the
* current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will
* improve texture utilization by using the available space before the page is merged
* and becomes static.
*/
if (
TextureAtlas.maxAtlasPages &&
this._pages.length >= TextureAtlas.maxAtlasPages &&
@@ -923,8 +967,10 @@ export class TextureAtlas implements ITextureAtlas {
rasterizedGlyph.sizeClipSpace.x /= activePage.canvas.width;
rasterizedGlyph.sizeClipSpace.y /= activePage.canvas.height;
// Update atlas current row, for fixed rows the glyph height will never be larger than the row
// height
/*
* Update atlas current row, for fixed rows the glyph height will never be larger than the row
* height
*/
activeRow.height = Math.max(activeRow.height, rasterizedGlyph.size.y);
activeRow.x += rasterizedGlyph.size.x;
@@ -1057,16 +1103,18 @@ class AtlasPage {
public static nextVersion: number = 0;
public version = ++AtlasPage.nextVersion;
// Texture atlas current positioning data. The texture packing strategy used is to fill from
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
// row's height, the following happens:
//
// - The current row becomes the fixed height row A
// - A new fixed height row B the exact size of the glyph is created below the current row
// - A new dynamic height current row is created below B
//
// This strategy does a good job preventing space being wasted for very short glyphs such as
// underscores, hyphens etc. or those with underlines rendered.
/*
* Texture atlas current positioning data. The texture packing strategy used is to fill from
* left-to-right and top-to-bottom. When the glyph being written is less than half of the current
* row's height, the following happens:
*
* - The current row becomes the fixed height row A
* - A new fixed height row B the exact size of the glyph is created below the current row
* - A new dynamic height current row is created below B
*
* This strategy does a good job preventing space being wasted for very short glyphs such as
* underscores, hyphens etc. or those with underlines rendered.
*/
public currentRow: ICharAtlasActiveRow = {
x: 0,
y: 0,
@@ -1086,9 +1134,11 @@ class AtlasPage {
}
}
this.canvas = createCanvas(document, size, size);
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
// It might also contain some characters with transparent backgrounds if allowTransparency is
// set.
/*
* The canvas needs alpha because we use clearColor to convert the background color to alpha.
* It might also contain some characters with transparent backgrounds if allowTransparency is
* set.
*/
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
}
@@ -1116,12 +1166,14 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
const fgG = fg.rgba >>> 16 & 0xFF;
const fgB = fg.rgba >>> 8 & 0xFF;
// Calculate a threshold that when below a color will be treated as transpart when the sum of
// channel value differs. This helps improve rendering when glyphs overlap with others. This
// threshold is calculated relative to the difference between the background and foreground to
// ensure important details of the glyph are always shown, even when the contrast ratio is low.
// The number 12 is largely arbitrary to ensure the pixels that escape the cell in the test case
// were covered (fg=#8ae234, bg=#c4a000).
/*
* Calculate a threshold that when below a color will be treated as transpart when the sum of
* channel value differs. This helps improve rendering when glyphs overlap with others. This
* threshold is calculated relative to the difference between the background and foreground to
* ensure important details of the glyph are always shown, even when the contrast ratio is low.
* The number 12 is largely arbitrary to ensure the pixels that escape the cell in the test case
* were covered (fg=#8ae234, bg=#c4a000).
*/
const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
// Set alpha channel of relevent pixels to 0
+106 -58
View File
@@ -84,9 +84,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
) {
super();
// IMPORTANT: Canvas initialization and fetching of the context must be first in order to
// prevent possible listeners leaking and continuing to operate after the WebglRenderer has been
// discarded.
/*
* IMPORTANT: Canvas initialization and fetching of the context must be first in order to
* prevent possible listeners leaking and continuing to operate after the WebglRenderer has been
* discarded.
*/
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
const contextAttributes = {
antialias: false,
@@ -125,8 +127,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
console.log('webglcontextlost event received');
// Prevent the default behavior in order to enable WebGL context restoration.
e.preventDefault();
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
// If not, dispatch the onContextLoss notification to observers.
/*
* Wait a few seconds to see if the 'webglcontextrestored' event is fired.
* If not, dispatch the onContextLoss notification to observers.
*/
this._contextRestorationTimeout = setTimeout(() => {
this._contextRestorationTimeout = undefined;
console.warn('webgl context not restored; firing onContextLoss');
@@ -137,8 +141,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
console.warn('webglcontextrestored event received');
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
// The texture atlas and glyph renderer must be fully reinitialized
// because their contents have been lost.
/*
* The texture atlas and glyph renderer must be fully reinitialized
* because their contents have been lost.
*/
removeTerminalFromCache(this._terminal);
this._initializeWebGLState();
this._requestRedrawViewport();
@@ -178,8 +184,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public handleDevicePixelRatioChange(): void {
// If the device pixel ratio changed, the char atlas needs to be regenerated
// and the terminal needs to refreshed
/*
* If the device pixel ratio changed, the char atlas needs to be regenerated
* and the terminal needs to refreshed
*/
if (this._devicePixelRatio !== this._coreBrowserService.dpr) {
this._devicePixelRatio = this._coreBrowserService.dpr;
this.handleResize(this._terminal.cols, this._terminal.rows);
@@ -215,8 +223,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._refreshCharAtlas();
// Force a full refresh. Resizing `_glyphRenderer` should clear it already,
// so there is no need to clear it again here.
/*
* Force a full refresh. Resizing `_glyphRenderer` should clear it already,
* so there is no need to clear it again here.
*/
this._clearModel(false);
// Render synchronously to avoid flicker when the canvas is cleared
@@ -368,9 +378,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
return;
}
// Tell renderer the frame is beginning
// upon a model clear also refresh the full viewport model
// (also triggered by an atlas page merge, part of #4480)
/*
* Tell renderer the frame is beginning
* upon a model clear also refresh the full viewport model
* (also triggered by an atlas page merge, part of #4480)
*/
if (this._glyphRenderer.value.beginFrame()) {
this._clearModel(true);
this._updateModel(0, this._terminal.rows - 1);
@@ -379,8 +391,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._updateModel(start, end);
}
// A mid-update atlas page merge invalidates vertex data and may not bump the host
// page's version, so re-run the update and force a full texture rebind.
/*
* A mid-update atlas page merge invalidates vertex data and may not bump the host
* page's version, so re-run the update and force a full texture rebind.
*/
let merged = false;
let mergeRetries = 0;
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
@@ -408,8 +422,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
} else {
this._cursorBlinkStateManager.clear();
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
/*
* Request a refresh from the terminal as management of rendering is being
* moved back to the terminal
*/
this._requestRedrawCursor();
}
@@ -470,20 +486,26 @@ export class WebglRenderer extends Disposable implements IRenderer {
// If true, indicates that the current character(s) to draw were joined.
isJoined = false;
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
// be rendered entirely, like the selection state differs across the range.
/*
* Indicates whether this cell is part of a joined range that should be ignored as it cannot
* be rendered entirely, like the selection state differs across the range.
*/
isValidJoinRange = (x >= skipJoinedCheckUntilX);
lastCharX = x;
// Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters
// and attributes of our input.
/*
* Process any joined character ranges as needed. Because of how the
* ranges are produced, we know that they are valid for the characters
* and attributes of our input.
*/
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
range = joinedRanges.shift()!;
// If the ligature's selection state is not consistent, don't join it. This helps the
// selection render correctly regardless whether they should be joined.
/*
* If the ligature's selection state is not consistent, don't join it. This helps the
* selection render correctly regardless whether they should be joined.
*/
const firstSelectionState = this._model.selection.isCellSelected(this._terminal, range[0], row);
for (i = range[0] + 1; i < range[1]; i++) {
isValidJoinRange &&= (firstSelectionState === this._model.selection.isCellSelected(this._terminal, i, row));
@@ -495,8 +517,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
} else {
isJoined = true;
// We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly.
/*
* We already know the exact start and end column of the joined range,
* so we get the string and width representing it directly.
*/
cell = new JoinedCellData(
cell,
line!.translateToString(true, range[0], range[1]),
@@ -586,8 +610,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
this._model.cells[j] = NULL_CELL_CODE;
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
// supported
/*
* Don't re-resolve the cell color since multi-colored ligature backgrounds are not
* supported
*/
this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
@@ -631,48 +657,64 @@ export class WebglRenderer extends Disposable implements IRenderer {
return;
}
// Calculate the device character width. Width is floored as it must be drawn to an integer grid
// in order for the char atlas glyphs to not be blurry.
/*
* Calculate the device character width. Width is floored as it must be drawn to an integer grid
* in order for the char atlas glyphs to not be blurry.
*/
this.dimensions.device.char.width = Math.floor(this._charSizeService.width * this._devicePixelRatio);
// Calculate the device character height. Height is ceiled in case devicePixelRatio is a
// floating point number in order to ensure there is enough space to draw the character to the
// cell.
/*
* Calculate the device character height. Height is ceiled in case devicePixelRatio is a
* floating point number in order to ensure there is enough space to draw the character to the
* cell.
*/
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * this._devicePixelRatio);
// Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be
// floored since lineHeight can never be lower then 1, this guarentees the device cell height
// will always be larger than device char height.
/*
* Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be
* floored since lineHeight can never be lower then 1, this guarentees the device cell height
* will always be larger than device char height.
*/
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
// Calculate the y offset within a cell that glyph should draw at in order for it to be centered
// correctly within the cell.
/*
* Calculate the y offset within a cell that glyph should draw at in order for it to be centered
* correctly within the cell.
*/
this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);
// Calculate the device cell width, taking the letterSpacing into account.
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
// Calculate the x offset with a cell that text should draw from in order for it to be centered
// correctly within the cell.
/*
* Calculate the x offset with a cell that text should draw from in order for it to be centered
* correctly within the cell.
*/
this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);
// Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in
// the canvas
/*
* Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in
* the canvas
*/
this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
// The the size of the canvas on the page. It's important that this rounds to nearest integer
// and not ceils as browsers often have floating point precision issues where
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
// it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
// large for the canvas element size.
/*
* The the size of the canvas on the page. It's important that this rounds to nearest integer
* and not ceils as browsers often have floating point precision issues where
* `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
* it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
* large for the canvas element size.
*/
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);
// Get the CSS dimensions of an individual cell. This needs to be derived from the calculated
// device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the
// page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char
// size on the canvas can differ.
/*
* Get the CSS dimensions of an individual cell. This needs to be derived from the calculated
* device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the
* page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char
* size on the canvas can differ.
*/
this.dimensions.css.cell.height = this.dimensions.device.cell.height / this._devicePixelRatio;
this.dimensions.css.cell.width = this.dimensions.device.cell.width / this._devicePixelRatio;
}
@@ -681,8 +723,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
if (this._canvas.width === width && this._canvas.height === height) {
return;
}
// While the actual canvas size has changed, keep device canvas dimensions as the value before
// the change as it's an exact multiple of the cell sizes.
/*
* While the actual canvas size has changed, keep device canvas dimensions as the value before
* the change as it's an exact multiple of the cell sizes.
*/
this._canvas.width = width;
this._canvas.height = height;
// Render synchronously to avoid flicker when the canvas is cleared
@@ -702,8 +746,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
// TODO: Share impl with core
export class JoinedCellData extends AttributeData implements ICellData {
private _width: number;
// .content carries no meaning for joined CellData, simply nullify it
// thus we have to overload all other .content accessors
/*
* .content carries no meaning for joined CellData, simply nullify it
* thus we have to overload all other .content accessors
*/
public content: number = 0;
public fg: number;
public bg: number;
@@ -731,8 +777,10 @@ export class JoinedCellData extends AttributeData implements ICellData {
}
public getCode(): number {
// code always gets the highest possible fake codepoint (read as -1)
// this is needed as code is used by caches as identifier
/*
* code always gets the highest possible fake codepoint (read as -1)
* this is needed as code is used by caches as identifier
*/
return 0x1FFFFF;
}
@@ -274,15 +274,17 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// #region Powerline Symbols (E0A0-E0BF)
// This contains the definitions of the primarily used box drawing characters as vector shapes.
// The reason these characters are defined specially is to avoid common problems if a user's font
// has not been patched with powerline characters and also to get pixel perfect rendering as
// rendering issues can occur around AA/SPAA.
//
// The line variants draw beyond the cell and get clipped to ensure the end of the line is not
// visible.
//
// Original symbols defined in https://github.com/powerline/fontpatcher
/*
* This contains the definitions of the primarily used box drawing characters as vector shapes.
* The reason these characters are defined specially is to avoid common problems if a user's font
* has not been patched with powerline characters and also to get pixel perfect rendering as
* rendering issues can occur around AA/SPAA.
*
* The line variants draw beyond the cell and get clipped to ensure the end of the line is not
* visible.
*
* Original symbols defined in https://github.com/powerline/fontpatcher
*/
// Git branch
'\u{E0A0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655', type: CustomGlyphVectorType.FILL } },
@@ -355,8 +357,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// #region Progress Indicators (EE00-EE0B)
// initially added in Fira Code and later Nerd Fonts
// https://github.com/ryanoasis/nerd-fonts/pull/1733
/*
* initially added in Fira Code and later Nerd Fonts
* https://github.com/ryanoasis/nerd-fonts/pull/1733
*/
// Progress bars (EE00-EE05)
'\u{EE00}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY START
@@ -378,8 +382,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// #region Git Branch Symbols (F5D0-F60D)
// Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
// then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).
/*
* Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
* then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).
*/
// Straight lines (F5D0-F5D9)
'\u{F5D0}': GitBranchSymbolsParts.LINE_H, // Same as 2500
@@ -433,8 +439,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
'\u{F5FA}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
'\u{F5FB}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
// Extended Nodes (F5FC-F60D)
// These were added a little later https://github.com/kovidgoyal/kitty/pull/7805
/*
* Extended Nodes (F5FC-F60D)
* These were added a little later https://github.com/kovidgoyal/kitty/pull/7805
*/
'\u{F5FC}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
'\u{F5FD}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
'\u{F5FE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
@@ -460,8 +468,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// https://www.unicode.org/charts/PDF/U1FB00.pdf
// Block mosaic terminal graphic characters (1FB00-1FB3B)
// The term "sextant" refers to block mosaics divided into six parts.
/*
* Block mosaic terminal graphic characters (1FB00-1FB3B)
* The term "sextant" refers to block mosaics divided into six parts.
*/
'\u{1FB00}': sextant(0b000001), // BLOCK SEXTANT-1
'\u{1FB01}': sextant(0b000010), // BLOCK SEXTANT-2
'\u{1FB02}': sextant(0b000011), // BLOCK SEXTANT-12 (upper one third block)
@@ -724,8 +734,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
'\u{1FBB2}': { type: CustomGlyphDefinitionType.PATH, data: 'M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13' }, // LEFT HALF RUNNING MAN
'\u{1FBB3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6' }, // RIGHT HALF RUNNING MAN
// Arrows (1FBB4-1FBB8)
// TODO: Improve all arrows, use hybrid approach
/*
* Arrows (1FBB4-1FBB8)
* TODO: Improve all arrows, use hybrid approach
*/
'\u{1FBB4}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS
'\u{1FBB5}': [ // LEFTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z', type: CustomGlyphVectorType.FILL } },
@@ -880,9 +892,11 @@ function sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTI
return {
type: CustomGlyphDefinitionType.PATH_FUNCTION,
data: () => {
// Sextant grid: 2 columns, 3 rows
// Row heights in 8ths: top=3, middle=2, bottom=3
// Column widths: left=4, right=4
/*
* Sextant grid: 2 columns, 3 rows
* Row heights in 8ths: top=3, middle=2, bottom=3
* Column widths: left=4, right=4
*/
const rects: string[] = [];
const colW = 0.5; // Each column is half width
const rowH = [3 / 8, 2 / 8, 3 / 8]; // Row heights as fractions
@@ -932,10 +946,12 @@ function sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTI
function segmentedDigit(pattern: number): string {
const paths: string[] = [];
// Each segment should have approximately the same stroke width, this is somewhat difficult to be
// precise since coordinates are 0-1 of the whole cell (percentage-based). To handle this, the
// fact that terminal cells are typically sized at ~2:1 (height:width) is leveraged.
// for horizontal vs vertical to make segments appear the same thickness
/*
* Each segment should have approximately the same stroke width, this is somewhat difficult to be
* precise since coordinates are 0-1 of the whole cell (percentage-based). To handle this, the
* fact that terminal cells are typically sized at ~2:1 (height:width) is leveraged.
* for horizontal vs vertical to make segments appear the same thickness
*/
const segW = 0.15; // Width of vertical segments (fraction of cell width)
const segH = 0.075; // Height of horizontal segments (fraction of cell height, ~half of segW for 2:1 cells)
const padX = 0.05; // Horizontal padding from edge
@@ -489,8 +489,10 @@ function drawPatternChar(
pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
patternSet.set(fillStyle, pattern);
}
// Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
// variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
/*
* Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
* variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
*/
const dx = (variantOffset >> 1) & 1;
const dy = variantOffset & 1;
if (dx !== 0 || dy !== 0) {
@@ -713,8 +715,10 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
for (let x = 0; x < result.length; x += 2) {
// Translate from 0-1 to 0-cellWidth
result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);
// Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
// clamp to the cell bounds.
/*
* Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
* clamp to the cell bounds.
*/
if (doClamp && result[x] !== 0) {
const rounded = Math.round(result[x] + 0.5) - 0.5;
result[x] = clampToCell ? clamp(rounded, cellWidth, 0) : rounded;
@@ -726,8 +730,10 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
for (let y = 1; y < result.length; y += 2) {
// Translate from 0-1 to 0-cellHeight
result[y] *= cellHeight;
// Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
// clamp to the cell bounds.
/*
* Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
* clamp to the cell bounds.
*/
if (doClamp && result[y] !== 0) {
const rounded = Math.round(result[y] + 0.5) - 0.5;
result[y] = clampToCell ? clamp(rounded, cellHeight, 0) : rounded;
@@ -24,9 +24,11 @@ test.beforeAll(async ({ browser }) => {
test.afterAll(async () => await ctx.page.close());
test.describe('WebGL Renderer Integration Tests', async () => {
// HACK: webgl2 is often not supported in headless firefox on Linux
// https://github.com/microsoft/playwright/issues/11566
// also disable safari due to #5852
/*
* HACK: webgl2 is often not supported in headless firefox on Linux
* https://github.com/microsoft/playwright/issues/11566
* also disable safari due to #5852
*/
if (platform() === 'linux') {
test.skip(({ browserName }) => browserName === 'firefox' || browserName === 'webkit');
}