提交 04f02ec2 编写于 作者: A Alex Dima

Fixes #18644: Fix char width reading in IE11 and Edge

上级 e2c39c6c
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as browser from 'vs/base/browser/browser';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
export const enum CharWidthRequestType {
Regular = 0,
Italic = 1,
Bold = 2
}
export class CharWidthRequest {
public readonly chr: string;
public readonly type: CharWidthRequestType;
public width: number;
constructor(chr: string, type: CharWidthRequestType) {
this.chr = chr;
this.type = type;
this.width = 0;
}
public fulfill(width: number) {
this.width = width;
}
}
interface ICharWidthReader {
read(): void;
}
class DomCharWidthReader implements ICharWidthReader {
private readonly _bareFontInfo: BareFontInfo;
private readonly _requests: CharWidthRequest[];
private _container: HTMLElement;
private _testElements: HTMLSpanElement[];
constructor(bareFontInfo: BareFontInfo, requests: CharWidthRequest[]) {
this._bareFontInfo = bareFontInfo;
this._requests = requests;
this._container = null;
this._testElements = null;
}
public read(): void {
// Create a test container with all these test elements
this._createDomElements();
// Add the container to the DOM
document.body.appendChild(this._container);
// Read character widths
this._readFromDomElements();
// Remove the container from the DOM
document.body.removeChild(this._container);
this._container = null;
this._testElements = null;
}
private _createDomElements(): void {
let container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '-50000px';
container.style.width = '50000px';
let regularDomNode = document.createElement('div');
regularDomNode.style.fontFamily = this._bareFontInfo.fontFamily;
regularDomNode.style.fontWeight = this._bareFontInfo.fontWeight;
regularDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px';
regularDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px';
container.appendChild(regularDomNode);
let boldDomNode = document.createElement('div');
boldDomNode.style.fontFamily = this._bareFontInfo.fontFamily;
boldDomNode.style.fontWeight = 'bold';
boldDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px';
boldDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px';
container.appendChild(boldDomNode);
let italicDomNode = document.createElement('div');
italicDomNode.style.fontFamily = this._bareFontInfo.fontFamily;
italicDomNode.style.fontWeight = this._bareFontInfo.fontWeight;
italicDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px';
italicDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px';
italicDomNode.style.fontStyle = 'italic';
container.appendChild(italicDomNode);
let testElements: HTMLSpanElement[] = [];
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
let parent: HTMLElement;
if (request.type === CharWidthRequestType.Regular) {
parent = regularDomNode;
}
if (request.type === CharWidthRequestType.Bold) {
parent = boldDomNode;
}
if (request.type === CharWidthRequestType.Italic) {
parent = italicDomNode;
}
parent.appendChild(document.createElement('br'));
let testElement = document.createElement('span');
DomCharWidthReader._render(testElement, request);
parent.appendChild(testElement);
testElements[i] = testElement;
}
this._container = container;
this._testElements = testElements;
}
private static _render(testElement: HTMLElement, request: CharWidthRequest): void {
if (request.chr === ' ') {
let htmlString = '&nbsp;';
// Repeat character 256 (2^8) times
for (let i = 0; i < 8; i++) {
htmlString += htmlString;
}
testElement.innerHTML = htmlString;
} else {
let testString = request.chr;
// Repeat character 256 (2^8) times
for (let i = 0; i < 8; i++) {
testString += testString;
}
testElement.textContent = testString;
}
}
private _readFromDomElements(): void {
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
const testElement = this._testElements[i];
request.fulfill(testElement.offsetWidth / 256);
}
}
}
class CanvasCharWidthReader implements ICharWidthReader {
private readonly _bareFontInfo: BareFontInfo;
private readonly _requests: CharWidthRequest[];
constructor(bareFontInfo: BareFontInfo, requests: CharWidthRequest[]) {
this._bareFontInfo = bareFontInfo;
this._requests = requests;
}
public read(): void {
let canvasElement = <HTMLCanvasElement>document.createElement('canvas');
let context = canvasElement.getContext('2d');
context.font = CanvasCharWidthReader._createFontString(this._bareFontInfo);
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
if (request.type === CharWidthRequestType.Regular) {
request.fulfill(context.measureText(request.chr).width);
}
}
context.font = CanvasCharWidthReader._createFontString(this._bareFontInfo, undefined, 'bold');
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
if (request.type === CharWidthRequestType.Bold) {
request.fulfill(context.measureText(request.chr).width);
}
}
context.font = CanvasCharWidthReader._createFontString(this._bareFontInfo, 'italic');
for (let i = 0, len = this._requests.length; i < len; i++) {
const request = this._requests[i];
if (request.type === CharWidthRequestType.Italic) {
request.fulfill(context.measureText(request.chr).width);
}
}
}
private static _createFontString(bareFontInfo: BareFontInfo, overwriteFontStyle: string = 'normal', overwriteFontWeight: string = bareFontInfo.fontWeight): string {
return this._doCreateFontString(overwriteFontStyle, overwriteFontWeight, bareFontInfo.fontSize, bareFontInfo.lineHeight, bareFontInfo.fontFamily);
}
private static _doCreateFontString(fontStyle: string, fontWeight: string, fontSize: number, lineHeight: number, fontFamily: string): string {
// The full font syntax is:
// style | variant | weight | stretch | size/line-height | fontFamily
// (https://developer.mozilla.org/en-US/docs/Web/CSS/font)
// But it appears Edge and IE11 cannot properly parse `stretch`.
return `${fontStyle} normal ${fontWeight} ${fontSize}px / ${lineHeight}px ${fontFamily}`;
}
}
export function readCharWidths(bareFontInfo: BareFontInfo, requests: CharWidthRequest[]): void {
// In IE11, it appears that ctx.measureText() always returns integer results.
if (browser.isIE) {
let reader = new DomCharWidthReader(bareFontInfo, requests);
reader.read();
} else {
let reader = new CanvasCharWidthReader(bareFontInfo, requests);
reader.read();
}
}
......@@ -13,6 +13,7 @@ import { IDimension } from 'vs/editor/common/editorCommon';
import { FontInfo, BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import { FastDomNode } from 'vs/base/browser/styleMutator';
import { CharWidthRequest, CharWidthRequestType, readCharWidths } from 'vs/editor/browser/config/charWidthReader';
class CSSBasedConfigurationCache {
......@@ -125,85 +126,75 @@ class CSSBasedConfiguration extends Disposable {
}
}
private static createRequest(chr: string, type: CharWidthRequestType, all: CharWidthRequest[], monospace: CharWidthRequest[]): CharWidthRequest {
let result = new CharWidthRequest(chr, type);
all.push(result);
if (monospace) {
monospace.push(result);
}
return result;
}
private static _actualReadConfiguration(bareFontInfo: BareFontInfo): FontInfo {
let canvasElem = <HTMLCanvasElement>document.createElement('canvas');
let context = canvasElem.getContext('2d');
let all: CharWidthRequest[] = [];
let monospace: CharWidthRequest[] = [];
const typicalHalfwidthCharacter = this.createRequest('n', CharWidthRequestType.Regular, all, monospace);
const typicalFullwidthCharacter = this.createRequest('\uff4d', CharWidthRequestType.Regular, all, null);
const space = this.createRequest(' ', CharWidthRequestType.Regular, all, monospace);
const digit0 = this.createRequest('0', CharWidthRequestType.Regular, all, monospace);
const digit1 = this.createRequest('1', CharWidthRequestType.Regular, all, monospace);
const digit2 = this.createRequest('2', CharWidthRequestType.Regular, all, monospace);
const digit3 = this.createRequest('3', CharWidthRequestType.Regular, all, monospace);
const digit4 = this.createRequest('4', CharWidthRequestType.Regular, all, monospace);
const digit5 = this.createRequest('5', CharWidthRequestType.Regular, all, monospace);
const digit6 = this.createRequest('6', CharWidthRequestType.Regular, all, monospace);
const digit7 = this.createRequest('7', CharWidthRequestType.Regular, all, monospace);
const digit8 = this.createRequest('8', CharWidthRequestType.Regular, all, monospace);
const digit9 = this.createRequest('9', CharWidthRequestType.Regular, all, monospace);
let getCharWidth = (char: string): number => {
return context.measureText(char).width;
};
// monospace test: used for whitespace rendering
this.createRequest('', CharWidthRequestType.Regular, all, monospace);
this.createRequest('·', CharWidthRequestType.Regular, all, monospace);
context.font = `normal normal normal normal ${bareFontInfo.fontSize}px / ${bareFontInfo.lineHeight}px ${bareFontInfo.fontFamily}`;
const typicalHalfwidthCharacter = getCharWidth('n');
const typicalFullwidthCharacter = getCharWidth('\uff4d');
// monospace test: some characters
this.createRequest('|', CharWidthRequestType.Regular, all, monospace);
this.createRequest('/', CharWidthRequestType.Regular, all, monospace);
this.createRequest('-', CharWidthRequestType.Regular, all, monospace);
this.createRequest('_', CharWidthRequestType.Regular, all, monospace);
this.createRequest('i', CharWidthRequestType.Regular, all, monospace);
this.createRequest('l', CharWidthRequestType.Regular, all, monospace);
this.createRequest('m', CharWidthRequestType.Regular, all, monospace);
// monospace italic test
this.createRequest('|', CharWidthRequestType.Italic, all, monospace);
this.createRequest('_', CharWidthRequestType.Italic, all, monospace);
this.createRequest('i', CharWidthRequestType.Italic, all, monospace);
this.createRequest('l', CharWidthRequestType.Italic, all, monospace);
this.createRequest('m', CharWidthRequestType.Italic, all, monospace);
this.createRequest('n', CharWidthRequestType.Italic, all, monospace);
// monospace bold test
this.createRequest('|', CharWidthRequestType.Bold, all, monospace);
this.createRequest('_', CharWidthRequestType.Bold, all, monospace);
this.createRequest('i', CharWidthRequestType.Bold, all, monospace);
this.createRequest('l', CharWidthRequestType.Bold, all, monospace);
this.createRequest('m', CharWidthRequestType.Bold, all, monospace);
this.createRequest('n', CharWidthRequestType.Bold, all, monospace);
readCharWidths(bareFontInfo, all);
const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width);
let isMonospace = true;
let monospaceWidth = typicalHalfwidthCharacter;
let getCharWidthAndCheckMonospace = (char: string): number => {
const charWidth = getCharWidth(char);
if (isMonospace) {
const diff = typicalHalfwidthCharacter - charWidth;
if (diff < -0.001 || diff > 0.001) {
isMonospace = false;
}
}
return charWidth;
};
let checkMonospace = (char: string): void => {
if (isMonospace) {
const charWidth = getCharWidth(char);
const diff = typicalHalfwidthCharacter - charWidth;
if (diff < -0.001 || diff > 0.001) {
isMonospace = false;
}
let referenceWidth = monospace[0].width;
for (let i = 1, len = monospace.length; i < len; i++) {
const diff = referenceWidth - monospace[i].width;
if (diff < -0.001 || diff > 0.001) {
isMonospace = false;
break;
}
};
monospaceWidth = typicalHalfwidthCharacter;
const space = getCharWidthAndCheckMonospace(' ');
const digit0 = getCharWidthAndCheckMonospace('0');
const digit1 = getCharWidthAndCheckMonospace('1');
const digit2 = getCharWidthAndCheckMonospace('2');
const digit3 = getCharWidthAndCheckMonospace('3');
const digit4 = getCharWidthAndCheckMonospace('4');
const digit5 = getCharWidthAndCheckMonospace('5');
const digit6 = getCharWidthAndCheckMonospace('6');
const digit7 = getCharWidthAndCheckMonospace('7');
const digit8 = getCharWidthAndCheckMonospace('8');
const digit9 = getCharWidthAndCheckMonospace('9');
const maxDigitWidth = Math.max(digit0, digit1, digit2, digit3, digit4, digit5, digit6, digit7, digit8, digit9);
// monospace test: used for whitespace rendering
checkMonospace('');
checkMonospace('·');
// monospace test: some characters
checkMonospace('|');
checkMonospace('/');
checkMonospace('-');
checkMonospace('_');
checkMonospace('i');
checkMonospace('l');
checkMonospace('m');
context.font = `italic normal normal normal ${bareFontInfo.fontSize}px / ${bareFontInfo.lineHeight}px ${bareFontInfo.fontFamily}`;
checkMonospace('|');
checkMonospace('_');
checkMonospace('i');
checkMonospace('l');
checkMonospace('m');
checkMonospace('n');
context.font = `normal normal bold normal ${bareFontInfo.fontSize}px / ${bareFontInfo.lineHeight}px ${bareFontInfo.fontFamily}`;
checkMonospace('|');
checkMonospace('_');
checkMonospace('i');
checkMonospace('l');
checkMonospace('m');
checkMonospace('n');
}
return new FontInfo({
fontFamily: bareFontInfo.fontFamily,
......@@ -211,9 +202,9 @@ class CSSBasedConfiguration extends Disposable {
fontSize: bareFontInfo.fontSize,
lineHeight: bareFontInfo.lineHeight,
isMonospace: isMonospace,
typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter,
typicalFullwidthCharacterWidth: typicalFullwidthCharacter,
spaceWidth: space,
typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter.width,
typicalFullwidthCharacterWidth: typicalFullwidthCharacter.width,
spaceWidth: space.width,
maxDigitWidth: maxDigitWidth
});
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册