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:
Manuel Hornung 2018-09-24 05:02:07 -07:00 committed by GitHub
parent 6bbb5418cb
commit aecf5c6de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 616 additions and 225 deletions

31
@types/color-normalize/index.d.ts vendored Normal file
View File

@ -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
}

53
@types/font-manager/index.d.ts vendored Normal file
View File

@ -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
}

View File

@ -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(

View File

@ -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
}
}
}

View File

@ -1,3 +0,0 @@
export interface IColorNormalizer {
normalizeColor(cssColor: string): Float32Array
}

View File

@ -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,

View File

@ -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,
}

View File

@ -0,0 +1,10 @@
export interface IRasterizedGlyph {
width: number
height: number
textureLayerIndex: number
textureWidth: number
textureHeight: number
textureU: number
textureV: number
variantOffset: number
}

View File

@ -0,0 +1,2 @@
export * from "./GlyphAtlas"
export * from "./IRasterizedGlyph"

View File

@ -0,0 +1,9 @@
export interface ICellGroup {
startColumnIndex: number
characters: string[]
foregroundColor?: string
backgroundColor?: string
italic?: boolean
bold?: boolean
underline?: boolean
}

View File

@ -0,0 +1,3 @@
export interface ILigatureGrouper {
getLigatureGroups(characters: string[]): string[]
}

View File

@ -0,0 +1,7 @@
import { ILigatureGrouper } from "./ILigatureGrouper"
export class NoopLigatureGrouper implements ILigatureGrouper {
public getLigatureGroups(characters: string[]) {
return characters
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,3 @@
export * from "./ILigatureGrouper"
export * from "./OpenTypeLigatureGrouper"
export * from "./NoopLigatureGrouper"

View File

@ -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

View File

@ -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,
}
}

View File

@ -0,0 +1 @@
export * from "./TextRenderer"

View File

@ -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],

View File

@ -0,0 +1 @@
export * from "./WebGLRenderer"

View File

@ -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
}
}

View File

@ -1,2 +1,3 @@
export * from "./CanvasRenderer"
export * from "./WebGLRenderer"
export * from "./INeovimRenderer"

View File

@ -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[]
}

View File

@ -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"],

View File

@ -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?

View File

@ -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
View File

@ -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"