mirror of https://github.com/onivim/oni.git
WebGL Renderer Ligatures (#2560)
* Tokenize rendered cells based on style and whitespace * Manually evaluate system font files to determine character sequences to render together * Render ligatures as one single token in the WebGL Atlas * Use font weight setting in the WebGL Renderer * Prefill atlas with the visible ASCII characters for optimization * Simplify lookup of rendered glyph information * Code style adjustments and refactorings for readability
This commit is contained in:
parent
6bbb5418cb
commit
aecf5c6de0
|
@ -0,0 +1,31 @@
|
|||
type ColorInput =
|
||||
| string
|
||||
| Int8Array
|
||||
| Int16Array
|
||||
| Int32Array
|
||||
| Uint8Array
|
||||
| Uint16Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| Array
|
||||
| Uint8ClampedArray
|
||||
|
||||
declare function colorNormalize(color: ColorInput, type: "float"): float[]
|
||||
declare function colorNormalize(color: ColorInput, type: "array"): float[]
|
||||
declare function colorNormalize(color: ColorInput, type: "int8"): Int8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "int16"): Int8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "int32"): Int8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "uint"): Uint8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "uint8"): Uint8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "uint16"): Uint8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "uint32"): Uint8Array
|
||||
declare function colorNormalize(color: ColorInput, type: "float32"): Float32Array
|
||||
declare function colorNormalize(color: ColorInput, type: "float64"): Float64Array
|
||||
declare function colorNormalize(color: ColorInput, type: "uint_clamped"): Uint8ClampedArray
|
||||
declare function colorNormalize(color: ColorInput, type: "uint8_clamped"): Uint8ClampedArray
|
||||
declare function colorNormalize(color: ColorInput): float[]
|
||||
|
||||
declare module "color-normalize" {
|
||||
export default colorNormalize
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
||||
type FontWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
||||
|
||||
interface QueryFontDescriptor {
|
||||
postscriptName?: string
|
||||
family?: string
|
||||
style?: string
|
||||
weight?: FontWeight
|
||||
width?: FontWidth
|
||||
italic?: boolean
|
||||
monospace?: boolean
|
||||
}
|
||||
|
||||
interface ResultFontDescriptor {
|
||||
path: string
|
||||
postscriptName: string
|
||||
family: string
|
||||
style: string
|
||||
weight: FontWeight
|
||||
width: FontWidth
|
||||
italic: boolean
|
||||
monospace: boolean
|
||||
}
|
||||
|
||||
interface FontManager {
|
||||
getAvailableFonts: (callback: (availableFonts: ResultFontDescriptor[]) => void) => void
|
||||
getAvailableFontsSync: () => ResultFontDescriptor[]
|
||||
|
||||
findFonts: (
|
||||
fontDescriptor: QueryFontDescriptor,
|
||||
callback: (foundFonts: ResultFontDescriptor[]) => void,
|
||||
) => void
|
||||
findFontsSync: (fontDescriptor: QueryFontDescriptor) => ResultFontDescriptor[]
|
||||
|
||||
findFont: (
|
||||
fontDescriptor: QueryFontDescriptor,
|
||||
callback: (foundFont: ResultFontDescriptor | null) => void,
|
||||
) => void
|
||||
findFontSync: (fontDescriptor: QueryFontDescriptor) => ResultFontDescriptor | null
|
||||
|
||||
substituteFont: (
|
||||
postscriptName: string,
|
||||
text: string,
|
||||
callback: (replacement: ResultFontDescriptor[]) => void,
|
||||
) => void
|
||||
substituteFontSync: (postscriptName: string, text: string) => ResultFontDescriptor[]
|
||||
}
|
||||
|
||||
declare module "font-manager" {
|
||||
declare const fontManager: FontManager
|
||||
|
||||
export default fontManager
|
||||
}
|
|
@ -94,8 +94,7 @@ import CommandLine from "./../../UI/components/CommandLine"
|
|||
import ExternalMenus from "./../../UI/components/ExternalMenus"
|
||||
import WildMenu from "./../../UI/components/WildMenu"
|
||||
|
||||
import { CanvasRenderer } from "../../Renderer/CanvasRenderer"
|
||||
import { WebGLRenderer } from "../../Renderer/WebGL/WebGLRenderer"
|
||||
import { CanvasRenderer, WebGLRenderer } from "../../Renderer"
|
||||
import { getInstance as getNotificationsInstance } from "./../../Services/Notifications"
|
||||
|
||||
type NeovimError = [number, string]
|
||||
|
@ -297,9 +296,10 @@ export class NeovimEditor extends Editor implements Oni.Editor {
|
|||
initVimNotification.show()
|
||||
}
|
||||
|
||||
const ligaturesEnabled = this._configuration.getValue("editor.fontLigatures")
|
||||
this._renderer =
|
||||
this._configuration.getValue("editor.renderer") === "webgl"
|
||||
? new WebGLRenderer()
|
||||
? new WebGLRenderer(ligaturesEnabled)
|
||||
: new CanvasRenderer()
|
||||
|
||||
this._rename = new Rename(
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import * as normalizeColor from "color-normalize"
|
||||
import { IColorNormalizer } from "./IColorNormalizer"
|
||||
|
||||
export class CachedColorNormalizer implements IColorNormalizer {
|
||||
private _cache = new Map<string, Float32Array>()
|
||||
|
||||
public normalizeColor(cssColor: string): Float32Array {
|
||||
const cachedRgba = this._cache.get(cssColor)
|
||||
|
||||
if (cachedRgba) {
|
||||
return cachedRgba
|
||||
} else {
|
||||
const rgba = normalizeColor.call(null, cssColor, "float32")
|
||||
this._cache.set(cssColor, rgba)
|
||||
return rgba
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export interface IColorNormalizer {
|
||||
normalizeColor(cssColor: string): Float32Array
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { ICell } from "../../neovim"
|
||||
import { IColorNormalizer } from "./IColorNormalizer"
|
||||
import { normalizeColor } from "./normalizeColor"
|
||||
import {
|
||||
createProgram,
|
||||
createUnitQuadElementIndicesBuffer,
|
||||
|
@ -48,7 +48,7 @@ const fragmentShaderSource = `
|
|||
}
|
||||
`.trim()
|
||||
|
||||
export class WebGLSolidRenderer {
|
||||
export class SolidRenderer {
|
||||
private _program: WebGLProgram
|
||||
private _viewportScaleLocation: WebGLUniformLocation
|
||||
private _unitQuadVerticesBuffer: WebGLBuffer
|
||||
|
@ -57,16 +57,12 @@ export class WebGLSolidRenderer {
|
|||
private _solidInstancesBuffer: WebGLBuffer
|
||||
private _vertexArrayObject: WebGLVertexArrayObject
|
||||
|
||||
constructor(
|
||||
private _gl: WebGL2RenderingContext,
|
||||
private _colorNormalizer: IColorNormalizer,
|
||||
private _devicePixelRatio: number,
|
||||
) {
|
||||
constructor(private _gl: WebGL2RenderingContext, private _devicePixelRatio: number) {
|
||||
this._program = createProgram(this._gl, vertexShaderSource, fragmentShaderSource)
|
||||
this._viewportScaleLocation = this._gl.getUniformLocation(this._program, "viewportScale")
|
||||
|
||||
this.createBuffers()
|
||||
this.createVertexArrayObject()
|
||||
this._createBuffers()
|
||||
this._createVertexArrayObject()
|
||||
}
|
||||
|
||||
public draw(
|
||||
|
@ -80,8 +76,8 @@ export class WebGLSolidRenderer {
|
|||
viewportScaleY: number,
|
||||
) {
|
||||
const cellCount = columnCount * rowCount
|
||||
this.recreateSolidInstancesArrayIfRequired(cellCount)
|
||||
const solidInstanceCount = this.populateSolidInstances(
|
||||
this._recreateSolidInstancesArrayIfRequired(cellCount)
|
||||
const solidInstanceCount = this._populateSolidInstances(
|
||||
columnCount,
|
||||
rowCount,
|
||||
getCell,
|
||||
|
@ -89,16 +85,16 @@ export class WebGLSolidRenderer {
|
|||
fontHeightInPixels,
|
||||
defaultBackgroundColor,
|
||||
)
|
||||
this.drawSolidInstances(solidInstanceCount, viewportScaleX, viewportScaleY)
|
||||
this._drawSolidInstances(solidInstanceCount, viewportScaleX, viewportScaleY)
|
||||
}
|
||||
|
||||
private createBuffers() {
|
||||
private _createBuffers() {
|
||||
this._unitQuadVerticesBuffer = createUnitQuadVerticesBuffer(this._gl)
|
||||
this._unitQuadElementIndicesBuffer = createUnitQuadElementIndicesBuffer(this._gl)
|
||||
this._solidInstancesBuffer = this._gl.createBuffer()
|
||||
}
|
||||
|
||||
private createVertexArrayObject() {
|
||||
private _createVertexArrayObject() {
|
||||
this._vertexArrayObject = this._gl.createVertexArray()
|
||||
this._gl.bindVertexArray(this._vertexArrayObject)
|
||||
|
||||
|
@ -151,14 +147,14 @@ export class WebGLSolidRenderer {
|
|||
this._gl.vertexAttribDivisor(vertexShaderAttributes.colorRGBA, 1)
|
||||
}
|
||||
|
||||
private recreateSolidInstancesArrayIfRequired(cellCount: number) {
|
||||
private _recreateSolidInstancesArrayIfRequired(cellCount: number) {
|
||||
const requiredArrayLength = cellCount * solidInstanceFieldCount
|
||||
if (!this._solidInstances || this._solidInstances.length < requiredArrayLength) {
|
||||
this._solidInstances = new Float32Array(requiredArrayLength)
|
||||
}
|
||||
}
|
||||
|
||||
private populateSolidInstances(
|
||||
private _populateSolidInstances(
|
||||
columnCount: number,
|
||||
rowCount: number,
|
||||
getCell: (columnIndex: number, rowIndex: number) => ICell,
|
||||
|
@ -172,7 +168,6 @@ export class WebGLSolidRenderer {
|
|||
let solidCellCount = 0
|
||||
let y = 0
|
||||
|
||||
// TODO refactor this to not be as reliant on mutations
|
||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
||||
let x = 0
|
||||
|
||||
|
@ -181,11 +176,9 @@ export class WebGLSolidRenderer {
|
|||
|
||||
if (cell.backgroundColor && cell.backgroundColor !== defaultBackgroundColor) {
|
||||
const colorToUse = cell.backgroundColor || defaultBackgroundColor || "black"
|
||||
const normalizedBackgroundColor = this._colorNormalizer.normalizeColor(
|
||||
colorToUse,
|
||||
)
|
||||
const normalizedBackgroundColor = normalizeColor(colorToUse)
|
||||
|
||||
this.updateSolidInstance(
|
||||
this._updateSolidInstance(
|
||||
solidCellCount,
|
||||
x,
|
||||
y,
|
||||
|
@ -205,7 +198,11 @@ export class WebGLSolidRenderer {
|
|||
return solidCellCount
|
||||
}
|
||||
|
||||
private drawSolidInstances(solidCount: number, viewportScaleX: number, viewportScaleY: number) {
|
||||
private _drawSolidInstances(
|
||||
solidCount: number,
|
||||
viewportScaleX: number,
|
||||
viewportScaleY: number,
|
||||
) {
|
||||
this._gl.bindVertexArray(this._vertexArrayObject)
|
||||
this._gl.disable(this._gl.BLEND)
|
||||
this._gl.useProgram(this._program)
|
||||
|
@ -215,7 +212,7 @@ export class WebGLSolidRenderer {
|
|||
this._gl.drawElementsInstanced(this._gl.TRIANGLES, 6, this._gl.UNSIGNED_BYTE, 0, solidCount)
|
||||
}
|
||||
|
||||
private updateSolidInstance(
|
||||
private _updateSolidInstance(
|
||||
index: number,
|
||||
x: number,
|
||||
y: number,
|
|
@ -1,9 +1,12 @@
|
|||
import { IRasterizedGlyph } from "./IRasterizedGlyph"
|
||||
|
||||
const backgroundColor = "black"
|
||||
const foregroundColor = "white"
|
||||
|
||||
export interface IWebGLAtlasOptions {
|
||||
export interface IGlyphAtlasOptions {
|
||||
fontFamily: string
|
||||
fontSize: string
|
||||
fontWeight: number | string
|
||||
lineHeightInPixels: number
|
||||
linePaddingInPixels: number
|
||||
glyphPaddingInPixels: number
|
||||
|
@ -13,40 +16,29 @@ export interface IWebGLAtlasOptions {
|
|||
textureLayerCount: number
|
||||
}
|
||||
|
||||
export interface WebGLGlyph {
|
||||
width: number
|
||||
height: number
|
||||
textureLayerIndex: number
|
||||
textureWidth: number
|
||||
textureHeight: number
|
||||
textureU: number
|
||||
textureV: number
|
||||
variantOffset: number
|
||||
subpixelWidth: number // TODO remove this as it is unused
|
||||
}
|
||||
|
||||
export class WebGLTextureSpaceExceededError extends Error {}
|
||||
|
||||
export class WebGLAtlas {
|
||||
private _glyphContext: CanvasRenderingContext2D
|
||||
private _glyphs = new Map<string, WebGLGlyph[][]>()
|
||||
export class GlyphAtlas {
|
||||
private _rasterizingContext: CanvasRenderingContext2D
|
||||
private _rasterizedGlyphs = new Map<string, IRasterizedGlyph>()
|
||||
private _texture: WebGLTexture
|
||||
private _currentTextureLayerIndex = 0
|
||||
private _currentTextureLayerChangedSinceLastUpload = false
|
||||
private _nextX = 0
|
||||
private _nextY = 0
|
||||
|
||||
constructor(private _gl: WebGL2RenderingContext, private _options: IWebGLAtlasOptions) {
|
||||
const glyphCanvas = document.createElement("canvas")
|
||||
glyphCanvas.width = this._options.textureSizeInPixels
|
||||
glyphCanvas.height = this._options.textureSizeInPixels
|
||||
this._glyphContext = glyphCanvas.getContext("2d", { alpha: false })
|
||||
this._glyphContext.fillStyle = foregroundColor
|
||||
this._glyphContext.textBaseline = "top"
|
||||
this._glyphContext.scale(_options.devicePixelRatio, _options.devicePixelRatio)
|
||||
this._glyphContext.imageSmoothingEnabled = false
|
||||
constructor(private _gl: WebGL2RenderingContext, private _options: IGlyphAtlasOptions) {
|
||||
// TODO we should share at least the rasterizingCanvas and maybe even the texture among different buffers
|
||||
const rasterizingCanvas = document.createElement("canvas")
|
||||
rasterizingCanvas.width = this._options.textureSizeInPixels
|
||||
rasterizingCanvas.height = this._options.textureSizeInPixels
|
||||
this._rasterizingContext = rasterizingCanvas.getContext("2d", { alpha: false })
|
||||
this._rasterizingContext.fillStyle = foregroundColor
|
||||
this._rasterizingContext.textBaseline = "top"
|
||||
this._rasterizingContext.scale(_options.devicePixelRatio, _options.devicePixelRatio)
|
||||
this._rasterizingContext.imageSmoothingEnabled = false
|
||||
|
||||
document.body.appendChild(glyphCanvas)
|
||||
document.body.appendChild(rasterizingCanvas)
|
||||
|
||||
this._texture = this._gl.createTexture()
|
||||
this._gl.bindTexture(this._gl.TEXTURE_2D_ARRAY, this._texture)
|
||||
|
@ -84,29 +76,19 @@ export class WebGLAtlas {
|
|||
)
|
||||
}
|
||||
|
||||
public getGlyph(text: string, isBold: boolean, isItalic: boolean, variantIndex: number) {
|
||||
// The mapping goes from character to styles (bold etc.) to subpixel-offset variant,
|
||||
// e.g. this._glyphs.get("a")[0][0] is the regular "a" with 0 offset,
|
||||
// while this._glyphs.get("a")[3][1] is the bold italic "a" with 1/offsetGlyphVariantCount px offset
|
||||
let glyphStyleVariants = this._glyphs.get(text)
|
||||
if (!glyphStyleVariants) {
|
||||
glyphStyleVariants = new Array<WebGLGlyph[]>(glyphStyles.length)
|
||||
this._glyphs.set(text, glyphStyleVariants)
|
||||
public getRasterizedGlyph(
|
||||
text: string,
|
||||
isBold: boolean,
|
||||
isItalic: boolean,
|
||||
variantIndex: number,
|
||||
) {
|
||||
const glyphKey = `${text} ${isBold ? "b" : ""}${isItalic ? "i" : ""}${variantIndex}`
|
||||
let rasterizedGlyph = this._rasterizedGlyphs.get(glyphKey)
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._rasterizeGlyph(text, isBold, isItalic, variantIndex)
|
||||
this._rasterizedGlyphs.set(glyphKey, rasterizedGlyph)
|
||||
}
|
||||
const glyphStyleIndex = getGlyphStyleIndex(isBold, isItalic)
|
||||
let glyphOffsetVariants = glyphStyleVariants[glyphStyleIndex]
|
||||
if (!glyphOffsetVariants) {
|
||||
glyphOffsetVariants = new Array<WebGLGlyph>(this._options.offsetGlyphVariantCount)
|
||||
glyphStyleVariants[glyphStyleIndex] = glyphOffsetVariants
|
||||
}
|
||||
|
||||
let glyph = glyphOffsetVariants[variantIndex]
|
||||
if (!glyph) {
|
||||
glyph = this._rasterizeGlyph(text, isBold, isItalic, variantIndex)
|
||||
glyphOffsetVariants[variantIndex] = glyph
|
||||
}
|
||||
|
||||
return glyph
|
||||
return rasterizedGlyph
|
||||
}
|
||||
|
||||
public uploadTexture() {
|
||||
|
@ -123,7 +105,7 @@ export class WebGLAtlas {
|
|||
1,
|
||||
this._gl.RGBA,
|
||||
this._gl.UNSIGNED_BYTE,
|
||||
this._glyphContext.canvas,
|
||||
this._rasterizingContext.canvas,
|
||||
)
|
||||
this._currentTextureLayerChangedSinceLastUpload = false
|
||||
}
|
||||
|
@ -138,18 +120,21 @@ export class WebGLAtlas {
|
|||
this._currentTextureLayerChangedSinceLastUpload = true
|
||||
|
||||
const {
|
||||
fontWeight,
|
||||
devicePixelRatio,
|
||||
lineHeightInPixels,
|
||||
linePaddingInPixels,
|
||||
glyphPaddingInPixels,
|
||||
offsetGlyphVariantCount,
|
||||
} = this._options
|
||||
const style = getGlyphStyleString(isBold, isItalic)
|
||||
this._glyphContext.font = `${style} ${this._options.fontSize} ${this._options.fontFamily}`
|
||||
const style = getGlyphStyleString(fontWeight, isBold, isItalic)
|
||||
this._rasterizingContext.font = `${style} ${this._options.fontSize} ${
|
||||
this._options.fontFamily
|
||||
}`
|
||||
const variantOffset = variantIndex / offsetGlyphVariantCount
|
||||
|
||||
const height = lineHeightInPixels + 2 * glyphPaddingInPixels
|
||||
const { width: measuredGlyphWidth } = this._glyphContext.measureText(text)
|
||||
const { width: measuredGlyphWidth } = this._rasterizingContext.measureText(text)
|
||||
const width =
|
||||
Math.ceil(variantOffset) + Math.ceil(measuredGlyphWidth) + 2 * glyphPaddingInPixels
|
||||
|
||||
|
@ -164,14 +149,14 @@ export class WebGLAtlas {
|
|||
|
||||
const x = this._nextX
|
||||
const y = this._nextY
|
||||
this._glyphContext.fillText(
|
||||
this._rasterizingContext.fillText(
|
||||
text,
|
||||
x + glyphPaddingInPixels + variantOffset,
|
||||
y + glyphPaddingInPixels + linePaddingInPixels / 2,
|
||||
)
|
||||
this._nextX += width
|
||||
|
||||
return {
|
||||
const rasterizedGlyph: IRasterizedGlyph = {
|
||||
width: width * devicePixelRatio,
|
||||
height: height * devicePixelRatio,
|
||||
textureLayerIndex: this._currentTextureLayerIndex,
|
||||
|
@ -179,9 +164,9 @@ export class WebGLAtlas {
|
|||
textureV: y * devicePixelRatio / this._options.textureSizeInPixels,
|
||||
textureWidth: width * devicePixelRatio / this._options.textureSizeInPixels,
|
||||
textureHeight: height * devicePixelRatio / this._options.textureSizeInPixels,
|
||||
subpixelWidth: measuredGlyphWidth * devicePixelRatio,
|
||||
variantOffset,
|
||||
} as WebGLGlyph
|
||||
}
|
||||
return rasterizedGlyph
|
||||
}
|
||||
|
||||
private _switchToNextLayer() {
|
||||
|
@ -194,14 +179,14 @@ export class WebGLAtlas {
|
|||
|
||||
this.uploadTexture()
|
||||
|
||||
this._glyphContext.fillStyle = backgroundColor
|
||||
this._glyphContext.fillRect(
|
||||
this._rasterizingContext.fillStyle = backgroundColor
|
||||
this._rasterizingContext.fillRect(
|
||||
0,
|
||||
0,
|
||||
this._glyphContext.canvas.width,
|
||||
this._glyphContext.canvas.width,
|
||||
this._rasterizingContext.canvas.width,
|
||||
this._rasterizingContext.canvas.width,
|
||||
)
|
||||
this._glyphContext.fillStyle = foregroundColor
|
||||
this._rasterizingContext.fillStyle = foregroundColor
|
||||
this._currentTextureLayerIndex++
|
||||
this._nextX = 0
|
||||
this._nextY = 0
|
||||
|
@ -209,14 +194,40 @@ export class WebGLAtlas {
|
|||
}
|
||||
}
|
||||
|
||||
const getGlyphStyleIndex = (isBold: boolean, isItalic: boolean) =>
|
||||
isBold ? (isItalic ? 3 : 1) : isItalic ? 2 : 0
|
||||
const defaultFontWeight = 400
|
||||
|
||||
const glyphStyles = [
|
||||
"", // regular, 0
|
||||
"bold", // 1
|
||||
"italic", // 2
|
||||
"bold italic", // 3
|
||||
]
|
||||
const getGlyphStyleString = (isBold: boolean, isItalic: boolean) =>
|
||||
glyphStyles[getGlyphStyleIndex(isBold, isItalic)]
|
||||
const getGlyphStyleString = (
|
||||
baseFontWeight: number | string = defaultFontWeight,
|
||||
isBold: boolean,
|
||||
isItalic: boolean,
|
||||
) => {
|
||||
const fontWeight = isBold
|
||||
? getIncreasedFontWeightForBoldText(baseFontWeight)
|
||||
: getNumericFontWeight(baseFontWeight) || defaultFontWeight
|
||||
return "" + fontWeight + (isItalic ? " italic" : "")
|
||||
}
|
||||
|
||||
const addedFontWeightForBoldText = 300
|
||||
const maxFontWeight = 900
|
||||
|
||||
const getIncreasedFontWeightForBoldText = (baseFontWeight: number | string) => {
|
||||
const numericBaseFontWeight = getNumericFontWeight(baseFontWeight)
|
||||
return Math.min(numericBaseFontWeight + addedFontWeightForBoldText, maxFontWeight)
|
||||
}
|
||||
|
||||
const getNumericFontWeight = (fontWeight: number | string) => {
|
||||
if (typeof fontWeight === "number") {
|
||||
return fontWeight
|
||||
} else {
|
||||
return numericFontWeightMap[fontWeight] || defaultFontWeight
|
||||
}
|
||||
}
|
||||
|
||||
const numericFontWeightMap = {
|
||||
normal: 400,
|
||||
bold: 700,
|
||||
// The following two should in fact be dynamic based on the weight of other elements
|
||||
// but this is too complex and not relevant enough to warrant respecting this logic
|
||||
bolder: 700,
|
||||
lighter: 300,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
export interface IRasterizedGlyph {
|
||||
width: number
|
||||
height: number
|
||||
textureLayerIndex: number
|
||||
textureWidth: number
|
||||
textureHeight: number
|
||||
textureU: number
|
||||
textureV: number
|
||||
variantOffset: number
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./GlyphAtlas"
|
||||
export * from "./IRasterizedGlyph"
|
|
@ -0,0 +1,9 @@
|
|||
export interface ICellGroup {
|
||||
startColumnIndex: number
|
||||
characters: string[]
|
||||
foregroundColor?: string
|
||||
backgroundColor?: string
|
||||
italic?: boolean
|
||||
bold?: boolean
|
||||
underline?: boolean
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface ILigatureGrouper {
|
||||
getLigatureGroups(characters: string[]): string[]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { ILigatureGrouper } from "./ILigatureGrouper"
|
||||
|
||||
export class NoopLigatureGrouper implements ILigatureGrouper {
|
||||
public getLigatureGroups(characters: string[]) {
|
||||
return characters
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import fontManager from "font-manager"
|
||||
import * as fs from "fs"
|
||||
import * as Log from "oni-core-logging"
|
||||
import oniFontkit, { Font } from "oni-fontkit"
|
||||
|
||||
import { ILigatureGrouper } from "./ILigatureGrouper"
|
||||
|
||||
const ligatureFeatures = ["calt", "rclt", "liga", "dlig", "clig"]
|
||||
|
||||
export class OpenTypeLigatureGrouper implements ILigatureGrouper {
|
||||
private readonly _font = loadFont(this._fontFamily)
|
||||
private readonly _fontHasLigatures = this._font && checkIfFontHasLigatures(this._font)
|
||||
private readonly _cache = new Map<string, string[]>()
|
||||
|
||||
constructor(private _fontFamily: string) {}
|
||||
|
||||
public getLigatureGroups(characters: string[]) {
|
||||
if (!this._fontHasLigatures) {
|
||||
return characters
|
||||
}
|
||||
|
||||
const concatenatedCharacters = characters.join("")
|
||||
|
||||
const cachedLigatureGroups = this._cache.get(concatenatedCharacters)
|
||||
if (cachedLigatureGroups) {
|
||||
return cachedLigatureGroups
|
||||
}
|
||||
|
||||
const fontGlyphs = this._font.glyphsForString(concatenatedCharacters)
|
||||
// Apply ligatures and get the contextGroup metadata in the Glyphs where context-based replacements happened
|
||||
const contextGroupArray = this._font.applySubstitutionFeatures(fontGlyphs, ligatureFeatures)
|
||||
const ligatureGroups: string[] = []
|
||||
let currentContextGroupId: number = null
|
||||
contextGroupArray.forEach((contextGroupId, index) => {
|
||||
if (contextGroupId !== currentContextGroupId) {
|
||||
currentContextGroupId = contextGroupId
|
||||
ligatureGroups.push("")
|
||||
}
|
||||
ligatureGroups[ligatureGroups.length - 1] += concatenatedCharacters[index]
|
||||
})
|
||||
|
||||
this._cache.set(concatenatedCharacters, ligatureGroups)
|
||||
return [...ligatureGroups]
|
||||
}
|
||||
}
|
||||
|
||||
const loadFont = (fontFamily: string) => {
|
||||
try {
|
||||
const fontDescriptor = findMatchingFont(fontFamily)
|
||||
if (!fontDescriptor) {
|
||||
Log.warn(
|
||||
`[OpenTypeLigatureGrouper] Could not find installed font for font family '${fontFamily}'. Ligatures won't be available.`,
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const fontFileBuffer = fs.readFileSync(fontDescriptor.path)
|
||||
const font = oniFontkit.create(fontFileBuffer)
|
||||
Log.verbose(
|
||||
`[OpenTypeLigatureGrouper] Using font ${fontDescriptor.postscriptName} located at ${
|
||||
fontDescriptor.path
|
||||
} for finding ligatures in '${fontFamily}'`,
|
||||
)
|
||||
return font
|
||||
} catch (error) {
|
||||
Log.warn(
|
||||
`[OpenTypeLigatureGrouper] Error loading font file for font family '${fontFamily}': ${error} Ligatures won't be available.`,
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// This is a platform-independent reimplementation of the matching logic within font-manager's
|
||||
// findFont* methods.
|
||||
// We reimplemented it here because we encountered inconsistencies with matching on Windows.
|
||||
const findMatchingFont = (fontFamily: string) => {
|
||||
const availableFonts = fontManager.getAvailableFontsSync()
|
||||
const fontWithMatchingFamily = availableFonts.find(font => font.family === fontFamily)
|
||||
if (fontWithMatchingFamily) {
|
||||
return fontWithMatchingFamily
|
||||
} else {
|
||||
// Chromium allows to use the postscript name of the font as well, so we do the same
|
||||
// for compatibility
|
||||
const fontWithMatchingPostscriptName = availableFonts.find(
|
||||
font => font.postscriptName === fontFamily,
|
||||
)
|
||||
return fontWithMatchingPostscriptName
|
||||
}
|
||||
}
|
||||
|
||||
const checkIfFontHasLigatures = (font: Font) => {
|
||||
const fontHasLigatures = ligatureFeatures.some(
|
||||
ligatureFeature =>
|
||||
font && font.availableFeatures && font.availableFeatures.includes(ligatureFeature),
|
||||
)
|
||||
if (fontHasLigatures) {
|
||||
Log.verbose(
|
||||
`[OpenTypeLigatureGrouper] Found ligatures in '${
|
||||
font.postscriptName
|
||||
}'. Ligatures will be available.`,
|
||||
)
|
||||
return true
|
||||
} else {
|
||||
Log.verbose(
|
||||
`[OpenTypeLigatureGrouper] Could not find ligatures in '${
|
||||
font.postscriptName
|
||||
}'. Ligatures won't be available.`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./ILigatureGrouper"
|
||||
export * from "./OpenTypeLigatureGrouper"
|
||||
export * from "./NoopLigatureGrouper"
|
|
@ -1,11 +1,13 @@
|
|||
import { ICell } from "../../neovim"
|
||||
import { IColorNormalizer } from "./IColorNormalizer"
|
||||
import { IWebGLAtlasOptions, WebGLAtlas, WebGLGlyph } from "./WebGLAtlas"
|
||||
import { ICell } from "../../../neovim"
|
||||
import { normalizeColor } from "../normalizeColor"
|
||||
import {
|
||||
createProgram,
|
||||
createUnitQuadElementIndicesBuffer,
|
||||
createUnitQuadVerticesBuffer,
|
||||
} from "./WebGLUtilities"
|
||||
} from "../WebGLUtilities"
|
||||
import { GlyphAtlas, IGlyphAtlasOptions, IRasterizedGlyph } from "./GlyphAtlas"
|
||||
import { groupCells } from "./groupCells"
|
||||
import { ILigatureGrouper } from "./LigatureGrouper"
|
||||
|
||||
const glyphInstanceFieldCount = 13
|
||||
const glyphInstanceSizeInBytes = glyphInstanceFieldCount * Float32Array.BYTES_PER_ELEMENT
|
||||
|
@ -87,10 +89,8 @@ const secondPassFragmentShaderSource = `
|
|||
}
|
||||
`.trim()
|
||||
|
||||
const isWhiteSpace = (text: string) => text === null || text === "" || text === " "
|
||||
|
||||
export class WebGlTextRenderer {
|
||||
private _atlas: WebGLAtlas
|
||||
export class TextRenderer {
|
||||
private _atlas: GlyphAtlas
|
||||
private _glyphOverlapInPixels: number
|
||||
private _subpixelDivisor: number
|
||||
private _devicePixelRatio: number
|
||||
|
@ -109,13 +109,13 @@ export class WebGlTextRenderer {
|
|||
|
||||
constructor(
|
||||
private _gl: WebGL2RenderingContext,
|
||||
private _colorNormalizer: IColorNormalizer,
|
||||
atlasOptions: IWebGLAtlasOptions,
|
||||
private _ligatureGrouper: ILigatureGrouper,
|
||||
atlasOptions: IGlyphAtlasOptions,
|
||||
) {
|
||||
this._glyphOverlapInPixels = atlasOptions.glyphPaddingInPixels
|
||||
this._subpixelDivisor = atlasOptions.offsetGlyphVariantCount
|
||||
this._devicePixelRatio = atlasOptions.devicePixelRatio
|
||||
this._atlas = new WebGLAtlas(this._gl, atlasOptions)
|
||||
this._atlas = new GlyphAtlas(this._gl, atlasOptions)
|
||||
|
||||
this._firstPassProgram = createProgram(
|
||||
this._gl,
|
||||
|
@ -146,8 +146,21 @@ export class WebGlTextRenderer {
|
|||
"atlasTextures",
|
||||
)
|
||||
|
||||
this.createBuffers()
|
||||
this.createVertexArrayObject()
|
||||
this._createBuffers()
|
||||
this._createVertexArrayObject()
|
||||
}
|
||||
|
||||
public prefillAtlasWithCommonGlyphs() {
|
||||
for (let asciiCode = 33; asciiCode <= 126; asciiCode++) {
|
||||
const character = String.fromCharCode(asciiCode)
|
||||
|
||||
for (let variantIndex = 0; variantIndex < this._subpixelDivisor; variantIndex++) {
|
||||
this._atlas.getRasterizedGlyph(character, false, false, variantIndex)
|
||||
this._atlas.getRasterizedGlyph(character, true, false, variantIndex)
|
||||
this._atlas.getRasterizedGlyph(character, false, true, variantIndex)
|
||||
this._atlas.getRasterizedGlyph(character, true, true, variantIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public draw(
|
||||
|
@ -161,8 +174,8 @@ export class WebGlTextRenderer {
|
|||
viewportScaleY: number,
|
||||
) {
|
||||
const cellCount = columnCount * rowCount
|
||||
this.recreateGlyphInstancesArrayIfRequired(cellCount)
|
||||
const glyphInstanceCount = this.populateGlyphInstances(
|
||||
this._recreateGlyphInstancesArrayIfRequired(cellCount)
|
||||
const glyphInstanceCount = this._populateGlyphInstances(
|
||||
columnCount,
|
||||
rowCount,
|
||||
getCell,
|
||||
|
@ -170,16 +183,16 @@ export class WebGlTextRenderer {
|
|||
fontHeightInPixels,
|
||||
defaultForegroundColor,
|
||||
)
|
||||
this.drawGlyphInstances(glyphInstanceCount, viewportScaleX, viewportScaleY)
|
||||
this._drawGlyphInstances(glyphInstanceCount, viewportScaleX, viewportScaleY)
|
||||
}
|
||||
|
||||
private createBuffers() {
|
||||
private _createBuffers() {
|
||||
this._unitQuadVerticesBuffer = createUnitQuadVerticesBuffer(this._gl)
|
||||
this._unitQuadElementIndicesBuffer = createUnitQuadElementIndicesBuffer(this._gl)
|
||||
this._glyphInstancesBuffer = this._gl.createBuffer()
|
||||
}
|
||||
|
||||
private createVertexArrayObject() {
|
||||
private _createVertexArrayObject() {
|
||||
this._vertexArrayObject = this._gl.createVertexArray()
|
||||
this._gl.bindVertexArray(this._vertexArrayObject)
|
||||
|
||||
|
@ -265,14 +278,14 @@ export class WebGlTextRenderer {
|
|||
this._gl.vertexAttribDivisor(vertexShaderAttributes.atlasSize, 1)
|
||||
}
|
||||
|
||||
private recreateGlyphInstancesArrayIfRequired(cellCount: number) {
|
||||
private _recreateGlyphInstancesArrayIfRequired(cellCount: number) {
|
||||
const requiredArrayLength = cellCount * glyphInstanceFieldCount
|
||||
if (!this._glyphInstances || this._glyphInstances.length < requiredArrayLength) {
|
||||
this._glyphInstances = new Float32Array(requiredArrayLength)
|
||||
}
|
||||
}
|
||||
|
||||
private populateGlyphInstances(
|
||||
private _populateGlyphInstances(
|
||||
columnCount: number,
|
||||
rowCount: number,
|
||||
getCell: (columnIndex: number, rowIndex: number) => ICell,
|
||||
|
@ -285,23 +298,31 @@ export class WebGlTextRenderer {
|
|||
const pixelRatioAdaptedGlyphOverlap = this._glyphOverlapInPixels * this._devicePixelRatio
|
||||
|
||||
let glyphCount = 0
|
||||
let y = 0
|
||||
|
||||
// TODO refactor this to not be as reliant on mutations
|
||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
||||
let x = 0
|
||||
const cellGroups = groupCells(columnCount, rowIndex, getCell)
|
||||
|
||||
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
||||
const cell = getCell(columnIndex, rowIndex)
|
||||
const char = cell.character
|
||||
if (!isWhiteSpace(char)) {
|
||||
cellGroups.forEach(cellGroup => {
|
||||
const { startColumnIndex, characters, bold, italic, foregroundColor } = cellGroup
|
||||
|
||||
const ligatureGroups = this._ligatureGrouper.getLigatureGroups(characters)
|
||||
|
||||
let offsetWithinCellGroup = 0
|
||||
ligatureGroups.forEach(ligatureGroup => {
|
||||
const columnIndex = startColumnIndex + offsetWithinCellGroup
|
||||
const x = pixelRatioAdaptedFontWidth * columnIndex
|
||||
const y = pixelRatioAdaptedFontHeight * rowIndex
|
||||
const variantIndex =
|
||||
Math.round(x * this._subpixelDivisor) % this._subpixelDivisor
|
||||
const glyph = this._atlas.getGlyph(char, cell.bold, cell.italic, variantIndex)
|
||||
const colorToUse = cell.foregroundColor || defaultForegroundColor || "white"
|
||||
const normalizedTextColor = this._colorNormalizer.normalizeColor(colorToUse)
|
||||
const glyph = this._atlas.getRasterizedGlyph(
|
||||
ligatureGroup,
|
||||
bold,
|
||||
italic,
|
||||
variantIndex,
|
||||
)
|
||||
const colorToUse = foregroundColor || defaultForegroundColor || "white"
|
||||
const normalizedTextColor = normalizeColor(colorToUse)
|
||||
|
||||
this.updateGlyphInstance(
|
||||
this._updateGlyphInstance(
|
||||
glyphCount,
|
||||
Math.round(x - glyph.variantOffset) - pixelRatioAdaptedGlyphOverlap,
|
||||
y - pixelRatioAdaptedGlyphOverlap,
|
||||
|
@ -309,19 +330,22 @@ export class WebGlTextRenderer {
|
|||
normalizedTextColor,
|
||||
)
|
||||
|
||||
offsetWithinCellGroup += ligatureGroup.length
|
||||
glyphCount++
|
||||
}
|
||||
x += pixelRatioAdaptedFontWidth
|
||||
}
|
||||
|
||||
y += pixelRatioAdaptedFontHeight
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this._atlas.uploadTexture()
|
||||
|
||||
return glyphCount
|
||||
}
|
||||
|
||||
private drawGlyphInstances(glyphCount: number, viewportScaleX: number, viewportScaleY: number) {
|
||||
private _drawGlyphInstances(
|
||||
glyphCount: number,
|
||||
viewportScaleX: number,
|
||||
viewportScaleY: number,
|
||||
) {
|
||||
this._gl.bindVertexArray(this._vertexArrayObject)
|
||||
this._gl.enable(this._gl.BLEND)
|
||||
|
||||
|
@ -356,11 +380,11 @@ export class WebGlTextRenderer {
|
|||
this._gl.drawElementsInstanced(this._gl.TRIANGLES, 6, this._gl.UNSIGNED_BYTE, 0, glyphCount)
|
||||
}
|
||||
|
||||
private updateGlyphInstance(
|
||||
private _updateGlyphInstance(
|
||||
index: number,
|
||||
x: number,
|
||||
y: number,
|
||||
glyph: WebGLGlyph,
|
||||
glyph: IRasterizedGlyph,
|
||||
color: Float32Array,
|
||||
) {
|
||||
const startOffset = glyphInstanceFieldCount * index
|
|
@ -0,0 +1,52 @@
|
|||
import { ICell } from "../../../neovim"
|
||||
import { ICellGroup } from "./ICellGroup"
|
||||
|
||||
export const groupCells = (
|
||||
columnCount: number,
|
||||
rowIndex: number,
|
||||
getCell: (columnIndex: number, rowIndex: number) => ICell,
|
||||
) => {
|
||||
const cellGroups: ICellGroup[] = []
|
||||
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
||||
const currentCell = getCell(columnIndex, rowIndex)
|
||||
const currentCharacter = currentCell.character
|
||||
const currentCellGroup = cellGroups.length && cellGroups[cellGroups.length - 1]
|
||||
|
||||
if (!currentCharacter || currentCharacter === " ") {
|
||||
continue
|
||||
} else if (
|
||||
currentCellGroup &&
|
||||
cellStyleMatchesCellGroup(currentCell, currentCellGroup) &&
|
||||
columnComesDirectlyAfterCellGroup(columnIndex, currentCellGroup)
|
||||
) {
|
||||
currentCellGroup.characters.push(currentCharacter)
|
||||
} else {
|
||||
const newCellGroup = createNewCellGroup(columnIndex, currentCell)
|
||||
cellGroups.push(newCellGroup)
|
||||
}
|
||||
}
|
||||
return cellGroups
|
||||
}
|
||||
|
||||
const cellStyleMatchesCellGroup = (cell: ICell, cellGroup: ICellGroup) =>
|
||||
cellGroup.foregroundColor === cell.foregroundColor &&
|
||||
cellGroup.backgroundColor === cell.backgroundColor && // Maybe this isn't necessary; should we still group different backgrounds?
|
||||
cellGroup.bold === cell.bold &&
|
||||
cellGroup.italic === cell.italic &&
|
||||
cellGroup.underline === cell.underline
|
||||
|
||||
const columnComesDirectlyAfterCellGroup = (columnIndex: number, cellGroup: ICellGroup) =>
|
||||
columnIndex === cellGroup.startColumnIndex + cellGroup.characters.length
|
||||
|
||||
const createNewCellGroup = (startColumnIndex: number, startingCell: ICell) => {
|
||||
const { character, foregroundColor, backgroundColor, bold, italic, underline } = startingCell
|
||||
return {
|
||||
startColumnIndex,
|
||||
characters: [character],
|
||||
foregroundColor,
|
||||
backgroundColor,
|
||||
bold,
|
||||
italic,
|
||||
underline,
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./TextRenderer"
|
|
@ -1,25 +1,30 @@
|
|||
import { INeovimRenderer } from ".."
|
||||
import { MinimalScreenForRendering } from "../../neovim"
|
||||
import { CachedColorNormalizer } from "./CachedColorNormalizer"
|
||||
import { IColorNormalizer } from "./IColorNormalizer"
|
||||
import { IWebGLAtlasOptions, WebGLTextureSpaceExceededError } from "./WebGLAtlas"
|
||||
import { WebGLSolidRenderer } from "./WebGLSolidRenderer"
|
||||
import { WebGlTextRenderer } from "./WebGLTextRenderer"
|
||||
import { normalizeColor } from "./normalizeColor"
|
||||
import { SolidRenderer } from "./SolidRenderer"
|
||||
import { TextRenderer } from "./TextRenderer"
|
||||
import { IGlyphAtlasOptions, WebGLTextureSpaceExceededError } from "./TextRenderer/GlyphAtlas"
|
||||
import {
|
||||
ILigatureGrouper,
|
||||
NoopLigatureGrouper,
|
||||
OpenTypeLigatureGrouper,
|
||||
} from "./TextRenderer/LigatureGrouper"
|
||||
|
||||
export class WebGLRenderer implements INeovimRenderer {
|
||||
private _editorElement: HTMLElement
|
||||
private _colorNormalizer: IColorNormalizer
|
||||
private _previousAtlasOptions: IWebGLAtlasOptions
|
||||
private _ligatureGrouper: ILigatureGrouper = new NoopLigatureGrouper()
|
||||
private _previousAtlasOptions: IGlyphAtlasOptions
|
||||
private _textureSizeInPixels = 1024
|
||||
private _textureLayerCount = 2
|
||||
|
||||
private _gl: WebGL2RenderingContext
|
||||
private _solidRenderer: WebGLSolidRenderer
|
||||
private _textRenderer: WebGlTextRenderer
|
||||
private _solidRenderer: SolidRenderer
|
||||
private _textRenderer: TextRenderer
|
||||
|
||||
public constructor(private _ligaturesEnabled: boolean) {}
|
||||
|
||||
public start(editorElement: HTMLElement): void {
|
||||
this._editorElement = editorElement
|
||||
this._colorNormalizer = new CachedColorNormalizer()
|
||||
|
||||
const canvasElement = document.createElement("canvas")
|
||||
this._editorElement.innerHTML = ""
|
||||
|
@ -66,20 +71,20 @@ export class WebGLRenderer implements INeovimRenderer {
|
|||
canvas.style.height = `${canvas.height / devicePixelRatio}px`
|
||||
}
|
||||
|
||||
private _createNewRendererIfRequired({
|
||||
width: columnCount,
|
||||
height: rowCount,
|
||||
fontWidthInPixels,
|
||||
fontHeightInPixels,
|
||||
linePaddingInPixels,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
}: MinimalScreenForRendering) {
|
||||
private _createNewRendererIfRequired(screenInfo: MinimalScreenForRendering) {
|
||||
const {
|
||||
fontHeightInPixels,
|
||||
linePaddingInPixels,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
} = screenInfo
|
||||
const devicePixelRatio = window.devicePixelRatio
|
||||
const offsetGlyphVariantCount = Math.max(Math.ceil(4 / devicePixelRatio), 1)
|
||||
const atlasOptions = {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
lineHeightInPixels: fontHeightInPixels,
|
||||
linePaddingInPixels,
|
||||
glyphPaddingInPixels: Math.ceil(fontHeightInPixels / 4),
|
||||
|
@ -87,7 +92,7 @@ export class WebGLRenderer implements INeovimRenderer {
|
|||
offsetGlyphVariantCount,
|
||||
textureSizeInPixels: this._textureSizeInPixels,
|
||||
textureLayerCount: this._textureLayerCount,
|
||||
} as IWebGLAtlasOptions
|
||||
} as IGlyphAtlasOptions
|
||||
|
||||
if (
|
||||
!this._solidRenderer ||
|
||||
|
@ -95,23 +100,32 @@ export class WebGLRenderer implements INeovimRenderer {
|
|||
!this._previousAtlasOptions ||
|
||||
!isShallowEqual(this._previousAtlasOptions, atlasOptions)
|
||||
) {
|
||||
this._solidRenderer = new WebGLSolidRenderer(
|
||||
this._gl,
|
||||
this._colorNormalizer,
|
||||
atlasOptions.devicePixelRatio,
|
||||
)
|
||||
this._textRenderer = new WebGlTextRenderer(
|
||||
this._gl,
|
||||
this._colorNormalizer,
|
||||
atlasOptions,
|
||||
)
|
||||
if (
|
||||
(!this._previousAtlasOptions ||
|
||||
this._previousAtlasOptions.fontFamily !== fontFamily) &&
|
||||
this._ligaturesEnabled
|
||||
) {
|
||||
this._ligatureGrouper = new OpenTypeLigatureGrouper(fontFamily)
|
||||
}
|
||||
|
||||
this._solidRenderer = new SolidRenderer(this._gl, atlasOptions.devicePixelRatio)
|
||||
this._textRenderer = new TextRenderer(this._gl, this._ligatureGrouper, atlasOptions)
|
||||
try {
|
||||
this._textRenderer.prefillAtlasWithCommonGlyphs()
|
||||
} catch (error) {
|
||||
if (error instanceof WebGLTextureSpaceExceededError) {
|
||||
this._textureLayerCount *= 2
|
||||
this._createNewRendererIfRequired(screenInfo)
|
||||
}
|
||||
}
|
||||
|
||||
this._previousAtlasOptions = atlasOptions
|
||||
}
|
||||
}
|
||||
|
||||
private _clear(backgroundColor: string) {
|
||||
const backgroundColorToUse = backgroundColor || "black"
|
||||
const normalizedBackgroundColor = this._colorNormalizer.normalizeColor(backgroundColorToUse)
|
||||
const normalizedBackgroundColor = normalizeColor(backgroundColorToUse)
|
||||
this._gl.clearColor(
|
||||
normalizedBackgroundColor[0],
|
||||
normalizedBackgroundColor[1],
|
|
@ -0,0 +1 @@
|
|||
export * from "./WebGLRenderer"
|
|
@ -0,0 +1,15 @@
|
|||
import colorNormalize from "color-normalize"
|
||||
|
||||
const cache = new Map<string, Float32Array>()
|
||||
|
||||
export const normalizeColor = (cssColor: string) => {
|
||||
const cachedRgba = cache.get(cssColor)
|
||||
|
||||
if (cachedRgba) {
|
||||
return cachedRgba
|
||||
} else {
|
||||
const rgba = colorNormalize(cssColor, "float32")
|
||||
cache.set(cssColor, rgba)
|
||||
return rgba
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./CanvasRenderer"
|
||||
export * from "./WebGLRenderer"
|
||||
export * from "./INeovimRenderer"
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
type ColorInput =
|
||||
| string
|
||||
| Int8Array
|
||||
| Int16Array
|
||||
| Int32Array
|
||||
| Uint8Array
|
||||
| Uint16Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| Array
|
||||
| Uint8ClampedArray
|
||||
|
||||
declare module "color-normalize" {
|
||||
export function call(thisArg: any, color: ColorInput, type: "float"): float[]
|
||||
export function call(thisArg: any, color: ColorInput, type: "array"): float[]
|
||||
export function call(thisArg: any, color: ColorInput, type: "int8"): Int8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "int16"): Int8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "int32"): Int8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint"): Uint8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint8"): Uint8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint16"): Uint8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint32"): Uint8Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "float32"): Float32Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "float64"): Float64Array
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint_clamped"): Uint8ClampedArray
|
||||
export function call(thisArg: any, color: ColorInput, type: "uint8_clamped"): Uint8ClampedArray
|
||||
export function call(thisArg: any, color: ColorInput): float[]
|
||||
}
|
|
@ -18,6 +18,7 @@ module.exports = {
|
|||
"simple-git/promise": "require('simple-git/promise')",
|
||||
"styled-components": "require('styled-components')",
|
||||
fsevents: "require('fsevents')",
|
||||
"font-manager": "require('font-manager')",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js", ".less"],
|
||||
|
|
|
@ -160,7 +160,7 @@ export function createWindow(
|
|||
const iconImage = process.platform === "win32" ? "oni.ico" : "256x256.png"
|
||||
const iconPath = path.join(rootPath, "images", iconImage)
|
||||
|
||||
const indexFileName = process.env.ONI_WEBPACK_LOAD ? "index.dev.html" : "index.html"
|
||||
const indexFileName = isDevelopment ? "index.dev.html" : "index.html"
|
||||
const indexPath = path.join(rootPath, indexFileName + "?react_perf")
|
||||
// Create the browser window.
|
||||
// TODO: Do we need to use non-ico for other platforms?
|
||||
|
|
|
@ -863,6 +863,7 @@
|
|||
"dompurify": "^1.0.3",
|
||||
"electron-settings": "^3.1.4",
|
||||
"find-up": "2.1.0",
|
||||
"font-manager": "^0.3.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"highlight.js": "^9.12.0",
|
||||
"json5": "^1.0.1",
|
||||
|
@ -872,6 +873,7 @@
|
|||
"msgpack-lite": "0.1.26",
|
||||
"ocaml-language-server": "^1.0.27",
|
||||
"oni-api": "0.0.49",
|
||||
"oni-fontkit": "^0.0.4",
|
||||
"oni-neovim-binaries": "0.1.3",
|
||||
"oni-ripgrep": "0.0.4",
|
||||
"oni-types": "^0.0.8",
|
||||
|
|
105
yarn.lock
105
yarn.lock
|
@ -705,6 +705,14 @@ assign-symbols@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
|
||||
ast-transform@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ast-transform/-/ast-transform-0.0.0.tgz#74944058887d8283e189d954600947bc98fe0062"
|
||||
dependencies:
|
||||
escodegen "~1.2.0"
|
||||
esprima "~1.0.4"
|
||||
through "~2.3.4"
|
||||
|
||||
ast-types@0.10.1:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd"
|
||||
|
@ -717,6 +725,10 @@ ast-types@0.9.6:
|
|||
version "0.9.6"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
||||
|
||||
ast-types@^0.7.0:
|
||||
version "0.7.8"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.7.8.tgz#902d2e0d60d071bdcd46dc115e1809ed11c138a9"
|
||||
|
||||
astral-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||
|
@ -1589,7 +1601,7 @@ babel-register@^6.26.0, babel-register@^6.9.0:
|
|||
mkdirp "^0.5.1"
|
||||
source-map-support "^0.4.15"
|
||||
|
||||
babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
|
||||
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||
dependencies:
|
||||
|
@ -1660,6 +1672,10 @@ base64-js@^1.0.2:
|
|||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
||||
|
||||
base64-js@^1.1.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||
|
||||
base@^0.11.1:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||
|
@ -1826,11 +1842,17 @@ brorand@^1.0.1:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
|
||||
brotli@^1.2.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.2.tgz#525a9cad4fcba96475d7d388f6aecb13eed52f46"
|
||||
dependencies:
|
||||
base64-js "^1.1.2"
|
||||
|
||||
browser-process-hrtime@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e"
|
||||
|
||||
browser-resolve@^1.11.3:
|
||||
browser-resolve@^1.11.3, browser-resolve@^1.8.1:
|
||||
version "1.11.3"
|
||||
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
|
||||
dependencies:
|
||||
|
@ -1871,6 +1893,14 @@ browserify-mime@~1.2.9:
|
|||
version "1.2.9"
|
||||
resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f"
|
||||
|
||||
browserify-optional@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-optional/-/browserify-optional-1.0.1.tgz#1e13722cfde0d85f121676c2a72ced533a018869"
|
||||
dependencies:
|
||||
ast-transform "0.0.0"
|
||||
ast-types "^0.7.0"
|
||||
browser-resolve "^1.8.1"
|
||||
|
||||
browserify-rsa@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
|
||||
|
@ -2366,7 +2396,7 @@ clone-stats@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
|
||||
|
||||
clone@^1.0.0:
|
||||
clone@^1.0.0, clone@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
|
||||
|
@ -3071,7 +3101,7 @@ decompress-response@^3.2.0, decompress-response@^3.3.0:
|
|||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
deep-equal@^1.0.0, deep-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
|
||||
|
@ -3782,6 +3812,16 @@ escodegen@^1.6.1, escodegen@^1.9.0:
|
|||
optionalDependencies:
|
||||
source-map "~0.5.6"
|
||||
|
||||
escodegen@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.2.0.tgz#09de7967791cc958b7f89a2ddb6d23451af327e1"
|
||||
dependencies:
|
||||
esprima "~1.0.4"
|
||||
estraverse "~1.5.0"
|
||||
esutils "~1.0.0"
|
||||
optionalDependencies:
|
||||
source-map "~0.1.30"
|
||||
|
||||
eslint-scope@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
|
||||
|
@ -3801,6 +3841,10 @@ esprima@^4.0.0, esprima@~4.0.0:
|
|||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
||||
|
||||
esprima@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad"
|
||||
|
||||
esrecurse@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
|
||||
|
@ -3816,10 +3860,18 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
|||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
|
||||
estraverse@~1.5.0:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.5.1.tgz#867a3e8e58a9f84618afb6c2ddbcd916b7cbaf71"
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
|
||||
esutils@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.0.0.tgz#8151d358e20c8acc7fb745e7472c0025fe496570"
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
|
@ -4269,6 +4321,12 @@ flush-write-stream@^1.0.0:
|
|||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.4"
|
||||
|
||||
font-manager@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/font-manager/-/font-manager-0.3.0.tgz#9efdc13e521a3d8752e7ab56c3938818043a311f"
|
||||
dependencies:
|
||||
nan ">=2.10.0"
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
@ -6663,6 +6721,10 @@ lodash.flattendeep@^4.4.0:
|
|||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
|
||||
lodash.isarguments@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||
|
@ -6687,6 +6749,10 @@ lodash.memoize@^4.1.2:
|
|||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
||||
lodash.set@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||
|
||||
lodash.some@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
|
@ -7199,6 +7265,10 @@ mute-stream@0.0.7:
|
|||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||
|
||||
nan@>=2.10.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099"
|
||||
|
||||
nan@^2.0.0, nan@^2.0.9, nan@^2.3.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||
|
@ -7655,6 +7725,17 @@ oni-core-logging@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/oni-core-logging/-/oni-core-logging-1.0.0.tgz#7ad6c0ad8b06c23255202f97e229c2b0947dcf0b"
|
||||
|
||||
oni-fontkit@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/oni-fontkit/-/oni-fontkit-0.0.4.tgz#4bc91a2c9802c0910fcd93b409ba56e45fc202cf"
|
||||
dependencies:
|
||||
babel-runtime "^6.11.6"
|
||||
brotli "^1.2.0"
|
||||
clone "^1.0.1"
|
||||
deep-equal "^1.0.0"
|
||||
oni-restructure "^0.0.1"
|
||||
tiny-inflate "^1.0.2"
|
||||
|
||||
oni-neovim-binaries@0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/oni-neovim-binaries/-/oni-neovim-binaries-0.1.3.tgz#4855d11e9c1a6da586801bbb9996f61808c8b5b9"
|
||||
|
@ -7669,6 +7750,14 @@ oni-release-downloader@^0.0.10:
|
|||
mkdirp "^0.5.1"
|
||||
rimraf "^2.6.2"
|
||||
|
||||
oni-restructure@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/oni-restructure/-/oni-restructure-0.0.1.tgz#9938eedafc8afdada554fe9f1771a7eb80eeee05"
|
||||
dependencies:
|
||||
browserify-optional "^1.0.0"
|
||||
lodash.get "^4.4.2"
|
||||
lodash.set "^4.3.2"
|
||||
|
||||
oni-ripgrep@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/oni-ripgrep/-/oni-ripgrep-0.0.4.tgz#8eb52383f4a3f92b8b5d8fe29d52e9d728a46b50"
|
||||
|
@ -9764,7 +9853,7 @@ source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, sourc
|
|||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
source-map@^0.1.38:
|
||||
source-map@^0.1.38, source-map@~0.1.30:
|
||||
version "0.1.43"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
|
||||
dependencies:
|
||||
|
@ -10327,7 +10416,7 @@ through2@~0.2.3:
|
|||
readable-stream "~1.1.9"
|
||||
xtend "~2.1.1"
|
||||
|
||||
through@2, through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.6:
|
||||
through@2, through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.4, through@~2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
|
@ -10345,6 +10434,10 @@ timers-browserify@^2.0.4:
|
|||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
tiny-inflate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
|
|
Loading…
Reference in New Issue