Merge pull request #5473 from Tyriar/tyriar/5466_legacy_computing

Support entire Symbols for Legacy Computing unicode block (0x1FB00-0x1FBFF), add missing powerline symbol
This commit is contained in:
Daniel Imms
2025-12-23 08:15:05 -08:00
committed by GitHub
8 changed files with 711 additions and 45 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
@@ -5,6 +5,7 @@
import { CustomGlyphDefinitionType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphPathDrawFunctionDefinition } from './Types';
/* eslint-disable max-len */
/* eslint-disable @typescript-eslint/naming-convention */
export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefinition | undefined } = {
@@ -231,10 +232,12 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
// 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 } },
// L N
// LN (Line Number)
'\u{E0A1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: CustomGlyphVectorType.FILL } },
// Lock
'\u{E0A2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82', type: CustomGlyphVectorType.FILL } },
// CN (Column Number)
'\u{E0A3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.7,.03 L.7,.1 L.355,.1 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5', type: CustomGlyphVectorType.FILL } },
// Right triangle solid
'\u{E0B0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,.5 L0,1', type: CustomGlyphVectorType.FILL, rightPadding: 2 } },
// Right triangle line
@@ -395,18 +398,18 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
'\u{1FB6F}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,1 L1,1 L0.5,0.5 Z' }, // LOWER TRIANGULAR ONE QUARTER BLOCK
// Block elements (1FB70-1FB80)
'\u{1FB70}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 1, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-2
'\u{1FB71}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-3
'\u{1FB72}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 3, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-4
'\u{1FB73}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-5
'\u{1FB74}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 5, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-6
'\u{1FB75}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 6, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-7
'\u{1FB76}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 1, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-2
'\u{1FB77}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-3
'\u{1FB78}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 3, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-4
'\u{1FB79}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-5
'\u{1FB7A}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 5, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-6
'\u{1FB7B}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 6, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-7
'\u{1FB70}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 1, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-2
'\u{1FB71}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-3
'\u{1FB72}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 3, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-4
'\u{1FB73}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-5
'\u{1FB74}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 5, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-6
'\u{1FB75}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 6, y: 0, w: 1, h: 8 }] }, // VERTICAL ONE EIGHTH BLOCK-7
'\u{1FB76}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 1, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-2
'\u{1FB77}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-3
'\u{1FB78}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 3, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-4
'\u{1FB79}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 4, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-5
'\u{1FB7A}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 5, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-6
'\u{1FB7B}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 6, w: 8, h: 1 }] }, // HORIZONTAL ONE EIGHTH BLOCK-7
'\u{1FB7C}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }] }, // LEFT AND LOWER ONE EIGHTH BLOCK
'\u{1FB7D}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }] }, // LEFT AND UPPER ONE EIGHTH BLOCK
'\u{1FB7E}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }] }, // RIGHT AND UPPER ONE EIGHTH BLOCK
@@ -489,6 +492,150 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }
] },
// Diagonal fill characters (1FB98-1FB99)
'\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L1,1 M0,.25 L.75,1 M0,.5 L.5,1 M0,.75 L.25,1 M.25,0 L1,.75 M.5,0 L1,.5 M.75,0 L1,.25' } }, // UPPER LEFT TO LOWER RIGHT FILL
'\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.25 L.25,0 M0,.5 L.5,0 M0,.75 L.75,0 M0,1 L1,0 M.25,1 L1,.25 M.5,1 L1,.5 M.75,1 L1,.75' } }, // UPPER RIGHT TO LOWER LEFT FILL
// Smooth mosaic terminal graphic characters (1FB9A-1FB9B)
'\u{1FB9A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0', type: CustomGlyphVectorType.FILL } }, // UPPER AND LOWER TRIANGULAR HALF BLOCK
'\u{1FB9B}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L1,0 L1,1 L.5,.5 L0,1', type: CustomGlyphVectorType.FILL } }, // LEFT AND RIGHT TRIANGULAR HALF BLOCK
// Triangular shade characters (1FB9C-1FB9F)
'\u{1FB9C}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [[ // UPPER LEFT TRIANGULAR MEDIUM SHADE
[1, 0],
[0, 1]
], 'M0,0 L1,0 L0,1 Z'] },
'\u{1FB9D}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [[ // UPPER RIGHT TRIANGULAR MEDIUM SHADE
[1, 0],
[0, 1]
], 'M0,0 L1,0 L1,1 Z'] },
'\u{1FB9E}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [[ // LOWER RIGHT TRIANGULAR MEDIUM SHADE
[1, 0],
[0, 1]
], 'M1,0 L1,1 L0,1 Z'] },
'\u{1FB9F}': { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [[ // LOWER LEFT TRIANGULAR MEDIUM SHADE
[1, 0],
[0, 1]
], 'M0,0 L1,1 L0,1 Z'] },
// Character cell diagonals (1FBA0-1FBAE)
'\u{1FBA0}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L0,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT
'\u{1FBA1}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT
'\u{1FBA2}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE
'\u{1FBA3}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBA4}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L0,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE
'\u{1FBA5}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBA6}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.5 L.5,1 L1,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT
'\u{1FBA7}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.5 L.5,0 L1,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT
'\u{1FBA8}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L0,.5 M1,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT AND MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBA9}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,.5 M0,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT AND MIDDLE LEFT TO LOWER CENTRE
'\u{1FBAA}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,.5 L.5,1 L0,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE TO MIDDLE LEFT
'\u{1FBAB}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L0,.5 L.5,1 L1,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE TO MIDDLE RIGHT
'\u{1FBAC}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.5 L.5,0 L1,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO UPPER CENTRE TO MIDDLE RIGHT TO LOWER CENTRE
'\u{1FBAD}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,.5 L.5,0 L0,.5 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO UPPER CENTRE TO MIDDLE LEFT TO LOWER CENTRE
'\u{1FBAE}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,.5 L.5,1 L0,.5 Z' } }, // BOX DRAWINGS LIGHT DIAGONAL DIAMOND
// Light solid line with stroke (1FBAF-1FBAF)
'\u{1FBAF}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: `${Shapes.LEFT_TO_RIGHT} M.5,.35 L.5,.65` } }, // BOX DRAWINGS LIGHT HORIZONTAL WITH VERTICAL STROKE
// Terminal graphic characters (1FBB0-1FBB3)
'\u{1FBB0}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z' }, // ARROWHEAD-SHAPED POINTER
'\u{1FBB1}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.1,.55 L.35,.85 L.9,.2', type: CustomGlyphVectorType.STROKE } }, // INVERSE CHECK MARK
'\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)
'\u{1FBB4}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.15,.6 L.5,.4 L.5,.5 L.75,.5 L.75,.2 L.85,.2 L.85,.7 L.5,.7 L.5,.8 Z', type: CustomGlyphVectorType.FILL } }, // INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS
'\u{1FBB5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.125 L0,.125 Z M0,.875 L1,.875 L1,1 L0,1 Z M.15,.5 L.5,.3 L.5,.4 L.85,.4 L.85,.6 L.5,.6 L.5,.7 Z', type: CustomGlyphVectorType.FILL } }, // LEFTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK
'\u{1FBB6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.125 L0,.125 Z M0,.875 L1,.875 L1,1 L0,1 Z M.85,.5 L.5,.3 L.5,.4 L.15,.4 L.15,.6 L.5,.6 L.5,.7 Z', type: CustomGlyphVectorType.FILL } }, // RIGHTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK
'\u{1FBB7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.875,0 L1,0 L1,1 L.875,1 Z M.5,.85 L.3,.5 L.4,.5 L.4,.15 L.6,.15 L.6,.5 L.7,.5 Z', type: CustomGlyphVectorType.FILL } }, // DOWNWARDS ARROW AND RIGHT ONE EIGHTH BLOCK
'\u{1FBB8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.875,0 L1,0 L1,1 L.875,1 Z M.5,.15 L.3,.5 L.4,.5 L.4,.85 L.6,.85 L.6,.5 L.7,.5 Z', type: CustomGlyphVectorType.FILL } }, // UPWARDS ARROW AND RIGHT ONE EIGHTH BLOCK
// Terminal graphic characters (1FBB9-1FBBC)
'\u{1FBB9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,.89 L.11,.89 L.11,.37 L.36,.12 L.74,.12 L.96,.34 L1,.34 L1,.45 L.92,.45 L.69,.22 L.41,.22 L.21,.42 L.21,.79 L1,.79 Z', type: CustomGlyphVectorType.FILL } }, // LEFT HALF FOLDER
'\u{1FBBA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,.89 L0,.79 L.78,.79 L.78,.53 L.7,.45 L0,.45 L0,.35 L.75,.35 L.88,.48 L.88,.89 Z', type: CustomGlyphVectorType.FILL } }, // RIGHT HALF FOLDER
'\u{1FBBB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.31,.05 L.44,.05 L.44,.44 L.05,.44 L.05,.31 L.31,.31 Z M.56,.05 L.69,.05 L.69,.31 L.95,.31 L.95,.44 L.56,.44 Z M.05,.56 L.44,.56 L.44,.95 L.31,.95 L.31,.69 L.05,.69 Z M.56,.56 L.95,.56 L.95,.69 L.69,.69 L.69,.95 L.56,.95 Z', type: CustomGlyphVectorType.FILL } }, // VOIDED GREEK CROSS
'\u{1FBBC}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,1 L0,1 L0,.87 L.87,.87 L.87,.13 L0,.13 Z M.67,.5 A.17,.17,0,1,1,.33,.5 A.17,.17,0,1,1,.67,.5' }, // RIGHT OPEN SQUARED DOT
// Negative terminal graphic characters (1FBBD-1FBBF)
'\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)
'\u{1FBC0}': { type: CustomGlyphDefinitionType.PATH, data: 'M.16,.39 A.02,.02,0,0,1,.39,.16 L.5,.25 L.61,.16 A.02,.02,0,0,1,.84,.39 L.75,.5 L.84,.61 A.02,.02,0,0,1,.61,.84 L.5,.75 L.39,.84 A.02,.02,0,0,1,.16,.61 L.25,.5 Z M.24,.32 L.39,.5 L.24,.68 L.32,.76 L.5,.61 L.68,.76 L.76,.68 L.61,.5 L.76,.32 L.68,.24 L.5,.39 L.32,.24 Z' }, // WHITE HEAVY SALTIRE WITH ROUNDED CORNERS
// 1FBC1-1FBC4 is dervied from the Iosevka font (SIL OFL v1.1) https://github.com/be5invis/Iosevka
'\u{1FBC1}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.142308,0.924 Q0.130770,0.924,0.120193,0.922 T0.101924,0.916 T0.089424,0.9065 T0.084616,0.895 V0.025 Q0.084616,0.019,0.089424,0.0135 T0.101924,0.004 T0.120193,-0.002 T0.142308,-0.004 Q0.150000,-0.004,0.157693,-0.003 T0.173078,0.000 L0.430770,0.085 Q0.451924,0.059,0.494232,0.041 T0.587501,0.013 T0.692309,-0.0005 T0.800001,-0.004 H1 V0.055 H0.800001 Q0.771155,0.055,0.742309,0.056 T0.685578,0.060 T0.630770,0.0685 T0.580770,0.0825 T0.543270,0.1045 T0.528847,0.133 V0.647 H0.530770 Q0.596155,0.645,0.659616,0.638 T0.782693,0.6165 T0.893270,0.5805 T1,0.531 V0.623 Q0.934616,0.644,0.880770,0.6595 T0.769232,0.685 T0.650963,0.700 T0.528847,0.707 V0.787 Q0.528847,0.802,0.543270,0.8155 T0.580770,0.8375 T0.630770,0.8515 T0.685578,0.860 T0.742309,0.864 T0.800001,0.865 H1 V0.924 H0.800001 Q0.746155,0.924,0.692309,0.9205 T0.587501,0.907 T0.494232,0.879 T0.430770,0.835 L0.173078,0.920 Q0.165386,0.922,0.157693,0.923 T0.142308,0.924 Z M0.2,0.841 L0.415385,0.770 V0.150 L0.2,0.079 V0.841 Z M1,0.698 Q0.973077,0.694,0.969231,0.6885 T0.965385,0.677 Q0.965385,0.672,0.969231,0.6665 T1,0.657 V0.698 Z' }, // LEFT THIRD WHITE RIGHT POINTING INDEX
'\u{1FBC2}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.623 V0.531 Q0.044231,0.512,0.0625,0.4905 T0.092308,0.4465 T0.108654,0.4005 T0.113462,0.354 V0.351 Q0.113462,0.345,0.117308,0.3395 T0.129808,0.3305 T0.148846,0.3245 T0.171154,0.323 H0.501923 Q0.532692,0.323,0.563462,0.3185 T0.617308,0.303 T0.651923,0.2765 T0.663462,0.2435 V0.241 Q0.663462,0.219,0.657692,0.197 T0.639423,0.1545 T0.604808,0.115 T0.549615,0.082 T0.475577,0.0615 T0.392308,0.055 H0 V-0.004 H1 V0.055 H0.667308 Q0.694231,0.071,0.713462,0.09 T0.746154,0.129 T0.766346,0.1705 T0.775,0.213 H1 V0.316 Q0.971154,0.305,0.955769,0.2965 T0.920192,0.2825 T0.876923,0.2745 T0.830769,0.272 H0.773077 Q0.767308,0.297,0.743269,0.319 T0.680769,0.355 T0.595192,0.375 T0.501923,0.381 H0.227308 Q0.223462,0.415,0.211,0.4485 T0.172692,0.5135 T0.108269,0.573 T0,0.623 Z M0.611538,0.924 H0 V0.865 H0.611538 Q0.642308,0.865,0.673077,0.8605 T0.727885,0.845 T0.762308,0.8185 T0.773077,0.787 V0.785 Q0.773077,0.769,0.762308,0.7535 T0.727885,0.727 T0.673077,0.7115 T0.611538,0.707 H0.059615 Q0.048077,0.707,0.037115,0.7045 T0,0.694 V0.653 Q0.026923,0.648,0.037115,0.646 T0.059615,0.644 H0.722692 Q0.753462,0.644,0.784231,0.6395 T0.839038,0.624 T0.873846,0.5975 T0.884615,0.566 V0.564 Q0.884615,0.548,0.873846,0.532 T0.839038,0.5055 T0.784231,0.49 T0.722692,0.4855 H0.392308 Q0.380769,0.4855,0.370192,0.483 T0.351923,0.4765 T0.339423,0.467 T0.334615,0.4555 T0.339423,0.444 T0.351923,0.4345 T0.370192,0.428 T0.392308,0.4255 H0.832692 Q0.855769,0.4255,0.878846,0.4235 T0.922115,0.416 T0.957692,0.4015 T1,0.3815 V0.4685 Q0.975,0.4705,0.966346,0.4725 T0.95,0.4765 Q0.961538,0.4835,0.969231,0.4915 T1,0.5075 V0.6205 Q0.965385,0.6455,0.926923,0.665 T0.84,0.6935 Q0.866923,0.7115,0.877596,0.7345 T0.894231,0.7855 V0.787 Q0.894231,0.815,0.876923,0.8425 T0.820192,0.8895 T0.726923,0.916 T0.611538,0.924 Z' }, // MIDDLE THIRD WHITE RIGHT POINTING INDEX
'\u{1FBC3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0.473 V0.386 Q0.025490,0.378,0.028431,0.3695 T0.031373,0.352 V0.35 Q0.031373,0.342,0.028431,0.333 T0,0.316 V0.213 H0.652941 Q0.684314,0.213,0.715686,0.2085 T0.770588,0.193 T0.804902,0.1665 T0.815686,0.135 V0.133 Q0.815686,0.117,0.804902,0.101 T0.770588,0.0745 T0.715686,0.0595 T0.652941,0.055 H0 V-0.004 H0.652941 Q0.707843,-0.004,0.761765,0.004 T0.856863,0.031 T0.915686,0.0775 T0.933333,0.133 V0.135 Q0.933333,0.163,0.915686,0.1905 T0.856863,0.237 T0.761765,0.264 T0.652941,0.272 H0.109804 Q0.131373,0.29,0.140196,0.31 T0.149020,0.35 V0.352 Q0.149020,0.37,0.142157,0.388 T0.118627,0.4225 T0.076471,0.452 T0,0.473 Z M0,0.625 V0.513 Q0.029412,0.526,0.032353,0.54 T0.035294,0.568 V0.57 Q0.035294,0.584,0.032353,0.598 T0,0.625 Z' }, // RIGHT THIRD WHITE RIGHT POINTING INDEX
'\u{1FBC4}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.019231,1.085 V-0.165 H0.980769 V1.085 H0.019231 Z M0.446154,0.527 H0.553846 Q0.553846,0.511,0.5625,0.495 T0.591346,0.466 T0.631731,0.4405 T0.666346,0.4135 T0.688462,0.3825 T0.696154,0.35 Q0.696154,0.33,0.682692,0.311 T0.641346,0.2785 T0.575,0.259 T0.498077,0.253 T0.421154,0.259 T0.356731,0.279 T0.315385,0.3125 T0.301923,0.352 V0.357 H0.409615 V0.354 Q0.409615,0.345,0.414423,0.335 T0.431731,0.318 T0.4625,0.3075 T0.498077,0.304 Q0.517308,0.304,0.534615,0.307 T0.564423,0.3165 T0.582692,0.332 T0.588462,0.35 Q0.588462,0.366,0.572115,0.3805 T0.535577,0.4075 T0.496154,0.4335 T0.465385,0.4625 T0.45,0.4945 T0.446154,0.527 Z M0.5,0.667 Q0.519231,0.667,0.5375,0.664 T0.569231,0.654 T0.588462,0.637 T0.594231,0.617 T0.588462,0.5975 T0.569231,0.581 T0.5375,0.571 T0.5,0.568 T0.4625,0.571 T0.430769,0.581 T0.411538,0.5975 T0.405769,0.617 T0.411538,0.637 T0.430769,0.654 T0.4625,0.664 T0.5,0.667 Z' }, // NEGATIVE SQUARED QUESTION MARK
'\u{1FBC5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.35 L.44,.52 L.44,.62 L.19,.87 L.26,.94 L.5,.71 L.74,.94 L.81,.87 L.56,.62 L.56,.52 L.56,.35 Z M.17,.42 L.17,.52 L.83,.52 L.83,.42 Z M.67,.2 C.67,.106,.594,.03,.5,.03 C.406,.03,.33,.106,.33,.2 C.33,.294,.406,.37,.5,.37 C.594,.37,.67,.294,.67,.2 Z M.56,.2 C.56,.233,.533,.26,.5,.26 C.467,.26,.44,.233,.44,.2 C.44,.167,.467,.14,.5,.14 C.533,.14,.56,.167,.56,.2 Z', type: CustomGlyphVectorType.FILL } }, // STICK FIGURE
'\u{1FBC6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.35 L.44,.42 L.23,.27 L.19,.36 L.44,.52 L.44,.62 L.29,.92 L.38,.97 L.5,.71 L.61,.97 L.7,.92 L.56,.62 L.56,.52 L.81,.36 L.77,.27 L.56,.42 L.56,.35 Z M.67,.2 C.67,.106,.594,.03,.5,.03 C.406,.03,.33,.106,.33,.2 C.33,.294,.406,.37,.5,.37 C.594,.37,.67,.294,.67,.2 Z M.56,.2 C.56,.233,.533,.26,.5,.26 C.467,.26,.44,.233,.44,.2 C.44,.167,.467,.14,.5,.14 C.533,.14,.56,.167,.56,.2 Z', type: CustomGlyphVectorType.FILL } }, // STICK FIGURE WITH ARMS RAISED
'\u{1FBC7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.35 L.44,.62 L.29,.92 L.38,.97 L.5,.71 L.74,.94 L.81,.87 L.56,.62 L.56,.35 Z M.18,.56 L.23,.65 L.81,.36 L.77,.27 Z M.67,.2 C.67,.106,.594,.03,.5,.03 C.406,.03,.33,.106,.33,.2 C.33,.294,.406,.37,.5,.37 C.594,.37,.67,.294,.67,.2 Z M.56,.2 C.56,.233,.533,.26,.5,.26 C.467,.26,.44,.233,.44,.2 C.44,.167,.467,.14,.5,.14 C.533,.14,.56,.167,.56,.2 Z', type: CustomGlyphVectorType.FILL } }, // STICK FIGURE LEANING LEFT
'\u{1FBC8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.35 L.44,.62 L.19,.87 L.26,.94 L.5,.71 L.62,.97 L.71,.92 L.56,.62 L.56,.35 Z M.23,.27 L.18,.36 L.77,.65 L.81,.56 Z M.67,.2 C.67,.106,.594,.03,.5,.03 C.406,.03,.33,.106,.33,.2 C.33,.294,.406,.37,.5,.37 C.594,.37,.67,.294,.67,.2 Z M.56,.2 C.56,.233,.533,.26,.5,.26 C.467,.26,.44,.233,.44,.2 C.44,.167,.467,.14,.5,.14 C.533,.14,.56,.167,.56,.2 Z', type: CustomGlyphVectorType.FILL } }, // STICK FIGURE LEANING RIGHT
'\u{1FBC9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.44,.35 L.45,.49 L.15,.79 L.34,.79 L.34,.9 L.44,.9 L.44,.79 L.56,.79 L.56,.9 L.66,.9 L.66,.79 L.84,.79 L.54,.49 L.56,.35 Z M.39,.7 L.5,.6 L.60,.7 Z M.17,.42 L.17,.52 L.83,.52 L.83,.42 Z M.67,.2 C.67,.106,.594,.03,.5,.03 C.406,.03,.33,.106,.33,.2 C.33,.294,.406,.37,.5,.37 C.594,.37,.67,.294,.67,.2 Z M.56,.2 C.56,.233,.533,.26,.5,.26 C.467,.26,.44,.233,.44,.2 C.44,.167,.467,.14,.5,.14 C.533,.14,.56,.167,.56,.2 Z', type: CustomGlyphVectorType.FILL } }, // STICK FIGURE WITH DRESS
'\u{1FBCA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.26,.25 L.5,.01 L.74,.25 L.74,.83 L.5,.6 L.26,.83 Z M.37,.29 L.37,.58 L.5,.45 L.63,.58 L.63,.29 L.5,.16 Z', type: CustomGlyphVectorType.FILL } }, // WHITE UP-POINTING CHEVRON
// Terminal graphic characters (1FBCB-1FBCD)
'\u{1FBCB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.09,.32 L.32,.09 L.5,.25 L.68,.09 L.91,.32 L.75,.5 L.91,.68 L.68,.91 L.5,.75 L.32,.91 L.09,.68 L.25,.5 Z M.24,.32 L.39,.5 L.24,.68 L.32,.76 L.5,.61 L.68,.76 L.76,.68 L.61,.5 L.76,.32 L.68,.24 L.5,.39 L.32,.24 Z', type: CustomGlyphVectorType.FILL } }, // WHITE CROSS MARK
'\u{1FBCC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.55,.11 L.88,.11 L.88,.21 L.65,.21 L.65,.44, L.88,.44 L.88,.54 L.55,.54 Z', type: CustomGlyphVectorType.FILL } }, // RAISED SMALL LEFT SQUARE BRACKET
'\u{1FBCD}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.38,.28 L.5,.16 L.62,.28 L.62,.56 L.5,.44 L.38,.56 Z', type: CustomGlyphVectorType.FILL } }, // BLACK SMALL UP-POINTING CHEVRON
// Block elements (1FBCE-1FBCF)
'\u{1FBCE}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.6667,0 L0.6667,1 L0,1 Z' }, // LEFT TWO THIRDS BLOCK
'\u{1FBCF}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L0.3333,0 L0.3333,1 L0,1 Z' }, // LEFT ONE THIRD BLOCK
// Character cell diagonals (1FBD0-1FBDF)
'\u{1FBD0}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,.5 L0,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE RIGHT TO LOWER LEFT
'\u{1FBD1}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,0 L0,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE LEFT
'\u{1FBD2}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L1,.5' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE RIGHT
'\u{1FBD3}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,.5 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL MIDDLE LEFT TO LOWER RIGHT
'\u{1FBD4}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER CENTRE
'\u{1FBD5}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO LOWER RIGHT
'\u{1FBD6}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,0 L.5,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER CENTRE
'\u{1FBD7}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M.5,0 L0,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER CENTRE TO LOWER LEFT
'\u{1FBD8}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L.5,.5 L1,0' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE CENTRE TO UPPER RIGHT
'\u{1FBD9}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,0 L.5,.5 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE CENTRE TO LOWER RIGHT
'\u{1FBDA}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,1 L.5,.5 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL LOWER LEFT TO MIDDLE CENTRE TO LOWER RIGHT
'\u{1FBDB}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L.5,.5 L0,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE CENTRE TO LOWER LEFT
'\u{1FBDC}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L.5,1 L1,0' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER CENTRE TO UPPER RIGHT
'\u{1FBDD}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M1,0 L0,.5 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO MIDDLE LEFT TO LOWER RIGHT
'\u{1FBDE}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,1 L.5,0 L1,1' } }, // BOX DRAWINGS LIGHT DIAGONAL LOWER LEFT TO UPPER CENTRE TO LOWER RIGHT
'\u{1FBDF}': { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [FontWeight.NORMAL]: 'M0,0 L1,.5 L0,1' } }, // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO MIDDLE RIGHT TO LOWER LEFT
// Geometric shapes (1FBE0-1FBEF)
'\u{1FBE0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0', type: CustomGlyphVectorType.STROKE } }, // TOP JUSTIFIED LOWER HALF WHITE CIRCLE
'\u{1FBE1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1', type: CustomGlyphVectorType.STROKE } }, // RIGHT JUSTIFIED LEFT HALF WHITE CIRCLE
'\u{1FBE2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1', type: CustomGlyphVectorType.STROKE } }, // BOTTOM JUSTIFIED UPPER HALF WHITE CIRCLE
'\u{1FBE3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1', type: CustomGlyphVectorType.STROKE } }, // LEFT JUSTIFIED RIGHT HALF WHITE CIRCLE
'\u{1FBE4}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 0, w: 4, h: 4 }] }, // UPPER CENTRE ONE QUARTER BLOCK
'\u{1FBE5}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 2, y: 4, w: 4, h: 4 }] }, // LOWER CENTRE ONE QUARTER BLOCK
'\u{1FBE6}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 0, y: 2, w: 4, h: 4 }] }, // MIDDLE LEFT ONE QUARTER BLOCK
'\u{1FBE7}': { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: [{ x: 4, y: 2, w: 4, h: 4 }] }, // MIDDLE RIGHT ONE QUARTER BLOCK
'\u{1FBE8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C0,.276,.224,.5,.5,.5 C.776,.5,1,.276,1,0 Z', type: CustomGlyphVectorType.FILL } }, // TOP JUSTIFIED LOWER HALF BLACK CIRCLE
'\u{1FBE9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 C.724,0,.5,.224,.5,.5 C.5,.776,.724,1,1,1 Z', type: CustomGlyphVectorType.FILL } }, // RIGHT JUSTIFIED LEFT HALF BLACK CIRCLE
'\u{1FBEA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 C0,.724,.224,.5,.5,.5 C.776,.5,1,.724,1,1 Z', type: CustomGlyphVectorType.FILL } }, // BOTTOM JUSTIFIED UPPER HALF BLACK CIRCLE
'\u{1FBEB}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 C.276,0,.5,.224,.5,.5 C.5,.776,.276,1,0,1 Z', type: CustomGlyphVectorType.FILL } }, // LEFT JUSTIFIED RIGHT HALF BLACK CIRCLE
'\u{1FBEC}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,0 L.5,0 C.5,.276,.724,.5,1,.5 Z', type: CustomGlyphVectorType.FILL } }, // TOP RIGHT JUSTIFIED LOWER LEFT QUARTER BLACK CIRCLE
'\u{1FBED}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,1 L.5,1 C.5,.724,.276,.5,0,.5 Z', type: CustomGlyphVectorType.FILL } }, // BOTTOM LEFT JUSTIFIED UPPER RIGHT QUARTER BLACK CIRCLE
'\u{1FBEE}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M1,1 L1,.5 C.724,.5,.5,.724,.5,1 Z', type: CustomGlyphVectorType.FILL } }, // BOTTOM RIGHT JUSTIFIED UPPER LEFT QUARTER BLACK CIRCLE
'\u{1FBEF}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L0,.5 C.276,.5,.5,.276,.5,0 Z', type: CustomGlyphVectorType.FILL } }, // TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE
// Segmented digits (1FBF0-1FBF9)
'\u{1FBF0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111110), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT ZERO (abcdef)
'\u{1FBF1}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b0110000), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT ONE (bc)
'\u{1FBF2}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1101101), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT TWO (abdeg)
'\u{1FBF3}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111001), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT THREE (abcdg)
'\u{1FBF4}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b0110011), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT FOUR (bcfg)
'\u{1FBF5}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1011011), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT FIVE (acdfg)
'\u{1FBF6}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1011111), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT SIX (acdefg)
'\u{1FBF7}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1110010), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT SEVEN (abcf)
'\u{1FBF8}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111111), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT EIGHT (abcdefg)
'\u{1FBF9}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: segmentedDigit(0b1111011), type: CustomGlyphVectorType.FILL } }, // SEGMENTED DIGIT NINE (abcdfg)
// Terminal graphic character (1FBFA-1FBFA)
'\u{1FBFA}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.5,.175 C.2,.175,.15,.305,.15,.435 L.05,.63 L.35,.63 C.35,.682,.42,.76,.5,.76 C.58,.76,.65,.682,.65,.63 L.95,.63 L.85,.435 C.85,.305,.8,.175,.5,.175 Z', type: CustomGlyphVectorType.FILL } }, // ALARM BELL SYMBOL
// #endregion
};
@@ -528,6 +675,117 @@ function sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTI
};
}
/**
* Generates SVG path data for a 7-segment display digit.
*
* Segment mapping (bit positions):
*
* - bit 6: a (top)
* - bit 5: b (upper right)
* - bit 4: c (lower right)
* - bit 3: d (bottom)
* - bit 2: e (lower left)
* - bit 1: f (upper left)
* - bit 0: g (middle)
*
* ```
* ─a─
* │ │
* f b
* ─g─
* e c
* │ │
* ─d─
* ```
*/
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
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
const padY = 0.175; // Vertical padding from edge (35% total = 65% height)
const gap = 0.015; // Gap between segments
const taperX = segW / 2; // Horizontal taper for vertical segments
const taperY = segH / 2; // Vertical taper for horizontal segments
const left = padX;
const right = 1 - padX;
const top = padY;
const bottom = 1 - padY;
const midY = 0.5;
// Segment a (top horizontal) - hexagonal with pointed left/right ends
if (pattern & 0b1000000) {
const y1 = top;
const y2 = top + segH / 2;
const y3 = top + segH;
const x1 = left + segW + gap;
const x2 = right - segW - gap;
paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);
}
// Segment b (upper right vertical) - hexagonal with pointed top/bottom ends
if (pattern & 0b0100000) {
const x1 = right - segW;
const x2 = right - segW / 2;
const x3 = right;
const y1 = top + segH + gap;
const y2 = midY - gap;
paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);
}
// Segment c (lower right vertical) - hexagonal with pointed top/bottom ends
if (pattern & 0b0010000) {
const x1 = right - segW;
const x2 = right - segW / 2;
const x3 = right;
const y1 = midY + gap;
const y2 = bottom - segH - gap;
paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);
}
// Segment d (bottom horizontal) - hexagonal with pointed left/right ends
if (pattern & 0b0001000) {
const y1 = bottom - segH;
const y2 = bottom - segH / 2;
const y3 = bottom;
const x1 = left + segW + gap;
const x2 = right - segW - gap;
paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);
}
// Segment e (lower left vertical) - hexagonal with pointed top/bottom ends
if (pattern & 0b0000100) {
const x1 = left;
const x2 = left + segW / 2;
const x3 = left + segW;
const y1 = midY + gap;
const y2 = bottom - segH - gap;
paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);
}
// Segment f (upper left vertical) - hexagonal with pointed top/bottom ends
if (pattern & 0b0000010) {
const x1 = left;
const x2 = left + segW / 2;
const x3 = left + segW;
const y1 = top + segH + gap;
const y2 = midY - gap;
paths.push(`M${x2},${y1} L${x3},${y1 + taperY} L${x3},${y2 - taperY} L${x2},${y2} L${x1},${y2 - taperY} L${x1},${y1 + taperY} Z`);
}
// Segment g (middle horizontal) - hexagonal with pointed left/right ends
if (pattern & 0b0000001) {
const y1 = midY - segH / 2;
const y2 = midY;
const y3 = midY + segH / 2;
const x1 = left + segW + gap;
const x2 = right - segW - gap;
paths.push(`M${x1},${y2} L${x1 + taperX},${y1} L${x2 - taperX},${y1} L${x2},${y2} L${x2 - taperX},${y3} L${x1 + taperX},${y3} Z`);
}
return paths.join(' ');
}
const enum Shapes {
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
/** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
@@ -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) {
@@ -37,10 +38,16 @@ export function tryDrawCustomGlyph(
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;
@@ -84,7 +91,222 @@ function drawPathDefinitionCharacter(
): void {
const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
ctx.beginPath();
let currentX = 0;
let currentY = 0;
let lastControlX = 0;
let lastControlY = 0;
let lastCommand = '';
for (const instruction of instructions.split(' ')) {
const type = instruction[0];
const args: string[] = instruction.substring(1).split(',');
if (type === 'Z') {
ctx.closePath();
lastCommand = type;
continue;
}
if (type === 'V') {
const y = yOffset + parseFloat(args[0]) * deviceCellHeight;
ctx.lineTo(currentX, y);
currentY = y;
lastControlX = currentX;
lastControlY = currentY;
lastCommand = type;
continue;
}
if (type === 'H') {
const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
ctx.lineTo(x, currentY);
currentX = x;
lastControlX = currentX;
lastControlY = currentY;
lastCommand = type;
continue;
}
if (!args[0] || !args[1]) {
continue;
}
if (type === 'A') {
// SVG arc: A rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y
const rx = parseFloat(args[0]) * deviceCellWidth;
const ry = parseFloat(args[1]) * deviceCellHeight;
const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
const largeArcFlag = parseInt(args[3]);
const sweepFlag = parseInt(args[4]);
const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
currentX = x;
currentY = y;
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]);
currentX = translatedArgs[0];
currentY = translatedArgs[1];
lastControlX = currentX;
lastControlY = currentY;
} else if (type === 'L') {
ctx.lineTo(translatedArgs[0], translatedArgs[1]);
currentX = translatedArgs[0];
currentY = translatedArgs[1];
lastControlX = currentX;
lastControlY = currentY;
} else if (type === 'Q') {
ctx.quadraticCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3]);
lastControlX = translatedArgs[0];
lastControlY = translatedArgs[1];
currentX = translatedArgs[2];
currentY = translatedArgs[3];
} else if (type === 'T') {
// T uses reflection of last control point if previous command was Q or T
let cpX: number;
let cpY: number;
if (lastCommand === 'Q' || lastCommand === 'T') {
cpX = 2 * currentX - lastControlX;
cpY = 2 * currentY - lastControlY;
} else {
cpX = currentX;
cpY = currentY;
}
ctx.quadraticCurveTo(cpX, cpY, translatedArgs[0], translatedArgs[1]);
lastControlX = cpX;
lastControlY = cpY;
currentX = translatedArgs[0];
currentY = translatedArgs[1];
} else if (type === 'C') {
ctx.bezierCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3], translatedArgs[4], translatedArgs[5]);
lastControlX = translatedArgs[2];
lastControlY = translatedArgs[3];
currentX = translatedArgs[4];
currentY = translatedArgs[5];
}
lastCommand = type;
}
ctx.fill();
}
/**
* Converts SVG arc parameters to canvas arc/ellipse calls.
* Based on the SVG spec's endpoint to center parameterization conversion.
*/
function drawSvgArc(
ctx: CanvasRenderingContext2D,
x1: number, y1: number,
rx: number, ry: number,
phi: number,
largeArcFlag: number,
sweepFlag: number,
x2: number, y2: number
): void {
// Handle degenerate cases
if (rx === 0 || ry === 0) {
ctx.lineTo(x2, y2);
return;
}
rx = Math.abs(rx);
ry = Math.abs(ry);
const cosPhi = Math.cos(phi);
const sinPhi = Math.sin(phi);
// Step 1: Compute (x1', y1')
const dx = (x1 - x2) / 2;
const dy = (y1 - y2) / 2;
const x1p = cosPhi * dx + sinPhi * dy;
const y1p = -sinPhi * dx + cosPhi * dy;
// Step 2: Compute (cx', cy')
let rxSq = rx * rx;
let rySq = ry * ry;
const x1pSq = x1p * x1p;
const y1pSq = y1p * y1p;
// Correct radii if necessary
const lambda = x1pSq / rxSq + y1pSq / rySq;
if (lambda > 1) {
const lambdaSqrt = Math.sqrt(lambda);
rx *= lambdaSqrt;
ry *= lambdaSqrt;
rxSq = rx * rx;
rySq = ry * ry;
}
let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
if (sq < 0) sq = 0;
const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
const cxp = coef * (rx * y1p / ry);
const cyp = coef * -(ry * x1p / rx);
// Step 3: Compute (cx, cy) from (cx', cy')
const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
// Step 4: Compute angles
const ux = (x1p - cxp) / rx;
const uy = (y1p - cyp) / ry;
const vx = (-x1p - cxp) / rx;
const vy = (-y1p - cyp) / ry;
const startAngle = Math.atan2(uy, ux);
let dTheta = Math.atan2(vy, vx) - startAngle;
if (sweepFlag === 0 && dTheta > 0) {
dTheta -= 2 * Math.PI;
} else if (sweepFlag === 1 && dTheta < 0) {
dTheta += 2 * Math.PI;
}
const endAngle = startAngle + dTheta;
ctx.ellipse(cx, cy, rx, ry, phi, startAngle, endAngle, sweepFlag === 0);
}
/**
* 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]) {
@@ -105,7 +327,14 @@ function drawPathDefinitionCharacter(
ctx.lineTo(translatedArgs[0], translatedArgs[1]);
}
}
ctx.fill();
if (charDefinition.type === CustomGlyphVectorType.STROKE) {
ctx.stroke();
} else {
ctx.fill();
}
ctx.restore();
}
const cachedPatterns: Map<CustomGlyphPatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
@@ -266,6 +495,10 @@ function drawPathDefinitionCharacterWithWeight(
}
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}"`);
@@ -282,6 +515,49 @@ function drawPathDefinitionCharacterWithWeight(
}
}
/**
* Draws a pattern clipped to an arbitrary path (for triangular shades, etc.)
*/
function drawBlockPatternWithClipPath(
ctx: CanvasRenderingContext2D,
definition: [pattern: CustomGlyphPatternDefinition, 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];
if (type === 'Z') {
ctx.closePath();
continue;
}
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
continue;
}
const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
const y = yOffset + parseFloat(args[1]) * deviceCellHeight;
if (type === 'M') {
ctx.moveTo(x, y);
} else if (type === 'L') {
ctx.lineTo(x, y);
}
}
ctx.clip();
// Draw the pattern
drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
ctx.restore();
}
function drawVectorShape(
ctx: CanvasRenderingContext2D,
charDefinition: ICustomGlyphVectorShape,
@@ -303,6 +579,10 @@ function drawVectorShape(
ctx.lineWidth = devicePixelRatio * cssLineWidth;
for (const instruction of charDefinition.d.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}"`);
@@ -340,7 +620,8 @@ function clamp(value: number, max: number, min: number = 0): number {
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])
'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])
};
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
@@ -35,9 +35,11 @@ export const enum CustomGlyphDefinitionType {
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,
}
@@ -50,8 +52,10 @@ export type CustomGlyphCharacterDefinition = (
// 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, data: string } |
{ type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape }
);
+43 -11
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-restricted-syntax */
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
@@ -8,6 +7,8 @@
/// <reference path="../typings/xterm.d.ts"/>
/* eslint-disable no-restricted-syntax */
// HACK: Playwright/WebKit on Windows does not support WebAssembly https://stackoverflow.com/q/62311688/1156119
import type { ImageAddon as ImageAddonType, IImageAddonOptions } from '@xterm/addon-image';
let ImageAddon: typeof ImageAddonType | undefined; // eslint-disable-line @typescript-eslint/naming-convention
@@ -29,7 +30,7 @@ import { WebglAddon } from '@xterm/addon-webgl';
import { Unicode11Addon } from '@xterm/addon-unicode11';
import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';
import { writeUnicodeTable, type UnicodeRangeDefinition } from './unicodeTable';
import { writeUnicodeTable } from './unicodeTable';
export interface IWindowWithTerminal extends Window {
term: typeof Terminal;
@@ -828,13 +829,44 @@ function customGlyphAlignmentHandler(): void {
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
term.write('Smooth mosaic terminal graphic characters alignment tests:\x1b[33m\n\r');
term.write('🭇🬼 🭈🬽 🭉🬾 🭊🬿 🭋🭀 🭁🭌 🭂🭍 🭃🭎 🭄🭏 🭅🭐 🭆🭑 🭨🭪 🭩 🭯 🭮🭬\n\r');
term.write('🭢🭗 🭣🭘 🭤🭙 🭥🭚 🭦🭛 🭒🭝 🭓🭞 🭔🭟 🭕🭠 🭖🭡 🭧🭜 🭫 🭭\n\r');
term.write(' 🭇🬼 🭉🬾 🭋🭀\n\r');
term.write('🭊🭁🭌🬿 🭈🭆🭂🭍🭑🬽 🭇🭄🭏🬼 🭃🭎 🭅🭐 🭨🭪\n\r');
term.write('🭥🭒🭝🭚 🭣🭧🭓🭞🭜🭘 🭢🭕🭠🭗 🭔🭟 🭖🭡 🭪🭨\n\r');
term.write(' 🭢🭗 🭤🭙 🭦🭛\n\r');
term.write('\x1b[0mSmooth mosaic terminal graphic characters alignment tests:\x1b[33m\n\r');
term.write(' 🭇🬼 🭈🬽 🭉🬾 🭊🬿 🭋🭀 🭁🭌 🭂🭍 🭃🭎 🭄🭏 🭅🭐 🭆🭑 🭨🭪 🭩 🭯 🭮🭬\n\r');
term.write(' 🭢🭗 🭣🭘 🭤🭙 🭥🭚 🭦🭛 🭒🭝 🭓🭞 🭔🭟 🭕🭠 🭖🭡 🭧🭜 🭫 🭭\n\r');
term.write(' 🭇🬼 🭉🬾 🭋🭀\n\r');
term.write(' 🭊🭁🭌🬿 🭈🭆🭂🭍🭑🬽 🭇🭄🭏🬼 🭃🭎 🭅🭐 🭨🭪\n\r');
term.write(' 🭥🭒🭝🭚 🭣🭧🭓🭞🭜🭘 🭢🭕🭠🭗 🭔🭟 🭖🭡 🭪🭨\n\r');
term.write(' 🭢🭗 🭤🭙 🭦🭛\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBA0-1FBAE) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBA3}\u{1FBA7}\u{1FBA2} \u{1FBA3}\u{1FBA8}\u{1FBA0} \u{1FBAD}\u{1FBA2} \u{1FBA3}\u{1FBAC} \u{1FBAE}\n\r');
term.write(' \u{1FBA3}\u{1FBA0} \u{1FBA1}\u{1FBA2} \u{1FBA1}\u{1FBA9}\u{1FBA2} \u{1FBA1}\u{1FBAA} \u{1FBAB}\u{1FBA0}\n\r');
term.write(' \u{1FBA4} \u{1FBA5}\n\r');
term.write(' \u{1FBA1}\u{1FBA2} \u{1FBA3}\u{1FBA0}\n\r');
term.write(' \u{1FBA1}\u{1FBA6}\u{1FBA0}\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBD0-1FBDF) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBD6}\u{1FBD4} \u{1FBD0}\u{1FBD1}\u{1FBD2}\u{1FBD3} \u{1FBDA} \u{1FBD9}\u{1FBDB} \u{1FBDE} \u{1FBDD}\u{1FBDF}\n\r');
term.write(' \u{1FBD7}\u{1FBD5} \u{1FBD2}\u{1FBD3}\u{1FBD0}\u{1FBD1} \u{1FBD8} \u{1FBDC}\n\r');
term.write(' \u{1FBD4}\u{1FBD6}\n\r');
term.write(' \u{1FBD5}\u{1FBD7}\n\r');
term.write('');
term.write('\x1b[0mComposite terminal graphics characters:\x1b[35m\n\r');
term.write('\u{1FBB2}\u{1FBB3} \u{1FBB9}\u{1FBBA} \u{1FBC1}\u{1FBC2}\u{1FBC3}\n\r');
term.write('\x1b[0mFill tests:\x1b[36m\n\r');
const fillChars = ['\u{2591}', '\u{2592}', '\u{2593}', '\u{1FB8C}', '\u{1FB8D}', '\u{1FB8E}', '\u{1FB8F}', '\u{1FB90}', '\u{1FB91}', '\u{1FB92}', '\u{1FB94}', '\u{1FB95}', '\u{1FB96}', '\u{1FB97}', '\u{1FB98}', '\u{1FB99}'];
while (fillChars.length > 0) {
const batch = fillChars.splice(0, 10);
for (const fillChar of batch) {
term.write(`${fillChar.codePointAt(0).toString(16).toUpperCase().padEnd(5, ' ')} `);
}
term.write('\n\r');
for (let i = 0; i < 3; i++) {
for (const fillChar of batch) {
term.write(fillChar.repeat(5));
term.write(' ');
}
term.write('\n\r');
}
}
term.write('\x1b[0m');
window.scrollTo(0, 0);
}
@@ -868,7 +900,7 @@ function customGlyphRangesHandler(): void {
// Range: E0A0E0BF
// https://github.com/ryanoasis/nerd-fonts
writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0BF, [
['Powerline Symbols', 0xE0A0, 0xE0B3],
['Powerline Symbols', 0xE0A0, 0xE0B3, [0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF]],
['Powerline Extra Symbols', 0xE0B4, 0xE0BF],
]);
// Symbols for Legacy Computing
@@ -880,7 +912,7 @@ function customGlyphRangesHandler(): void {
['Block elements', 0x1FB70, 0x1FB80],
['Window title bar', 0x1FB81, 0x1FB81],
['Block elements', 0x1FB82, 0x1FB8B],
['Rectangular shade characters', 0x1FB8C, 0x1FB94],
['Rectangular shade characters', 0x1FB8C, 0x1FB94, [0x1FB93]],
['Fill characters', 0x1FB95, 0x1FB97],
['Diagonal fill characters', 0x1FB98, 0x1FB99],
['Smooth mosaic terminal graphic characters', 0x1FB9A, 0x1FB9B],
+83 -7
View File
@@ -6,6 +6,7 @@ export type UnicodeRangeDefinition = [
label: string,
start: number,
end: number,
reserved?: number[],
];
/**
@@ -39,15 +40,23 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
const labelStartMap = new Map<number, { label: string, colorIndex: number }>();
// Build a map of codepoint -> colorIndex for all codepoints in each range
const codePointColorMap = new Map<number, number>();
// Build a set of reserved codepoints
const reservedSet = new Set<number>();
let lastDefinitionEnd = start; // Track the last definition's end to stop printing there
if (definitions) {
for (let i = 0; i < definitions.length; i++) {
const [label, labelStart, labelEnd] = definitions[i];
const [label, labelStart, labelEnd, reserved] = definitions[i];
labelStartMap.set(labelStart, { label, colorIndex: i });
// Map all codepoints in the range to this color
for (let cp = labelStart; cp <= labelEnd; cp++) {
codePointColorMap.set(cp, i);
}
// Track reserved codepoints
if (reserved) {
for (const cp of reserved) {
reservedSet.add(cp);
}
}
if (labelEnd > lastDefinitionEnd) {
lastDefinitionEnd = labelEnd;
}
@@ -60,12 +69,18 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
for (let row = startRow; row <= endRow; row++) {
// Collect labels for this row with their column positions and color
const rowLabels: { col: number, label: string, colorIndex: number }[] = [];
// Collect reserved codepoints for this row
const rowReserved: { col: number, colorIndex: number }[] = [];
for (let col = 0; col < 16; col++) {
const codePoint = row * 16 + col;
const labelInfo = labelStartMap.get(codePoint);
if (labelInfo) {
rowLabels.push({ col, label: labelInfo.label, colorIndex: labelInfo.colorIndex });
}
if (reservedSet.has(codePoint)) {
const charColorIndex = codePointColorMap.get(codePoint);
rowReserved.push({ col, colorIndex: charColorIndex ?? -1 });
}
}
// If labels exist and don't start at column 0, first output chars before first label
@@ -78,16 +93,54 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
term.write(' ');
if (codePoint >= start && codePoint <= end) {
const charColorIndex = codePointColorMap.get(codePoint);
const isReserved = reservedSet.has(codePoint);
let char = String.fromCodePoint(codePoint);
if (charColorIndex !== undefined) {
term.write(color(String.fromCodePoint(codePoint), charColorIndex));
} else {
term.write(String.fromCodePoint(codePoint));
char = color(char, charColorIndex);
}
if (isReserved) {
char = faint(char);
}
term.write(char);
} else {
term.write(' ');
}
}
term.write('\n\r');
// Render reserved labels that appear before the first label (below the first part of row)
const earlyReserved = rowReserved.filter(r => r.col < rowLabels[0].col);
if (earlyReserved.length > 0) {
for (let i = earlyReserved.length - 1; i >= 0; i--) {
const prefix = ' '.repeat(8);
let line = '';
let visualLen = 0;
for (let col = 0; col < rowLabels[0].col; col++) {
const colPos = col * 2 + 1; // +1 to align with character position
const reservedAtCol = earlyReserved.findIndex(r => r.col === col);
if (reservedAtCol === i) {
const padding = ' '.repeat(colPos - visualLen);
const reservedItem = earlyReserved[i];
if (reservedItem.colorIndex >= 0) {
line += padding + color('└<reserved>', reservedItem.colorIndex);
} else {
line += padding + '└<reserved>';
}
break;
} else if (reservedAtCol !== -1 && reservedAtCol < i) {
const padding = ' '.repeat(colPos - visualLen);
const prevReserved = earlyReserved[reservedAtCol];
if (prevReserved.colorIndex >= 0) {
line += padding + color('│', prevReserved.colorIndex);
} else {
line += padding + '│';
}
visualLen = colPos + 1;
}
}
term.write(faint(prefix + line) + '\n\r');
}
}
}
// If labels exist, render them above the row
@@ -148,16 +201,39 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
if (codePoint >= start && codePoint <= effectiveEnd) {
// Color the character if it's part of a definition range
const charColorIndex = codePointColorMap.get(codePoint);
const isReserved = reservedSet.has(codePoint);
let char = String.fromCodePoint(codePoint);
if (charColorIndex !== undefined) {
term.write(color(String.fromCodePoint(codePoint), charColorIndex));
} else {
term.write(String.fromCodePoint(codePoint));
char = color(char, charColorIndex);
}
if (isReserved) {
char = faint(char);
}
term.write(char);
} else {
term.write(' ');
}
}
term.write('\n\r');
// Render reserved labels that appear after the first label (below the row)
// Only show one label pointing to the first reserved item when there are multiple
const lateReserved = rowLabels.length > 0
? rowReserved.filter(r => r.col >= rowLabels[0].col)
: rowReserved;
if (lateReserved.length > 0) {
const prefix = ' '.repeat(8);
const firstReserved = lateReserved[0];
const colPos = firstReserved.col * 2 + 1;
const padding = ' '.repeat(colPos);
let line: string;
if (firstReserved.colorIndex >= 0) {
line = padding + color('└<reserved>', firstReserved.colorIndex);
} else {
line = padding + '└<reserved>';
}
term.write(faint(prefix + line) + '\n\r');
}
}
}
+13 -5
View File
@@ -64,13 +64,21 @@ declare module '@xterm/headless' {
cursorWidth?: number;
/**
* Whether to draw custom glyphs for block element and box drawing
* characters instead of using the font. This should typically result in
* better rendering with continuous lines, even when line height and letter
* spacing is used. Note that this doesn't work with the DOM renderer which
* renders all characters using the font. The default is true.
* Whether to draw custom glyphs instead of using the font for the following
* unicode ranges:
*
* - Box Drawing (U+2500-U+257F)
* - Box Elements (U+2580-U+259F)
* - Powerline Symbols (U+E0A0U+E0BF)
* - Symbols for Legacy Computing (U+1FB00U+1FBFF)
*
* This will typically result in better rendering with continuous lines,
* even when line height and letter spacing is used. Note that this doesn't
* work with the DOM renderer which renders all characters using the font.
* The default is true.
*/
customGlyphs?: boolean;
/**
* Whether input should be disabled.
*/
+12 -5
View File
@@ -78,11 +78,18 @@ declare module '@xterm/xterm' {
cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none';
/**
* Whether to draw custom glyphs for block element and box drawing
* characters instead of using the font. This should typically result in
* better rendering with continuous lines, even when line height and letter
* spacing is used. Note that this doesn't work with the DOM renderer which
* renders all characters using the font. The default is true.
* Whether to draw custom glyphs instead of using the font for the following
* unicode ranges:
*
* - Box Drawing (U+2500-U+257F)
* - Box Elements (U+2580-U+259F)
* - Powerline Symbols (U+E0A0U+E0BF)
* - Symbols for Legacy Computing (U+1FB00U+1FBFF)
*
* This will typically result in better rendering with continuous lines,
* even when line height and letter spacing is used. Note that this doesn't
* work with the DOM renderer which renders all characters using the font.
* The default is true.
*/
customGlyphs?: boolean;