Skip to content

Commit 474b70c

Browse files
authored
Adopting variable fonts (microsoft#252308)
Allowing to define variable fonts in the editor
1 parent 8d856e5 commit 474b70c

File tree

25 files changed

+818
-421
lines changed

25 files changed

+818
-421
lines changed

src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ export class TextAreaEditContext extends AbstractEditContext {
743743

744744
// Try to render the textarea with the color/font style to match the text under it
745745
const lineHeight = this._context.viewLayout.getLineHeightForLineNumber(startPosition.lineNumber);
746+
const fontSize = this._context.viewModel.getFontSizeAtPosition(this._primaryCursorPosition);
746747
const viewLineData = this._context.viewModel.getViewLineData(startPosition.lineNumber);
747748
const startTokenIndex = viewLineData.tokens.findTokenIndexAtOffset(startPosition.column - 1);
748749
const endTokenIndex = viewLineData.tokens.findTokenIndexAtOffset(endPosition.column - 1);
@@ -765,7 +766,8 @@ export class TextAreaEditContext extends AbstractEditContext {
765766
italic: presentation.italic,
766767
bold: presentation.bold,
767768
underline: presentation.underline,
768-
strikethrough: presentation.strikethrough
769+
strikethrough: presentation.strikethrough,
770+
fontSize
769771
});
770772
}
771773
return;
@@ -850,6 +852,7 @@ export class TextAreaEditContext extends AbstractEditContext {
850852
ta.setHeight(renderData.height);
851853
ta.setLineHeight(renderData.height);
852854

855+
ta.setFontSize(renderData.fontSize ?? this._fontInfo.fontSize);
853856
ta.setColor(renderData.color ? Color.Format.CSS.formatHex(renderData.color) : '');
854857
ta.setFontStyle(renderData.italic ? 'italic' : '');
855858
if (renderData.bold) {
@@ -885,6 +888,7 @@ interface IRenderData {
885888
height: number;
886889
useCover: boolean;
887890

891+
fontSize?: string | null;
888892
color?: Color | null;
889893
italic?: boolean;
890894
bold?: boolean;

src/vs/editor/browser/editorBrowser.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { IDiffComputationResult, ILineChange } from '../common/diff/legacyLinesD
1919
import * as editorCommon from '../common/editorCommon.js';
2020
import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../common/model.js';
2121
import { InjectedText } from '../common/modelLineProjectionData.js';
22-
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelLineHeightChangedEvent } from '../common/textModelEvents.js';
22+
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelFontChangedEvent, ModelLineHeightChangedEvent } from '../common/textModelEvents.js';
2323
import { IEditorWhitespace, IViewModel } from '../common/viewModel.js';
2424
import { OverviewRulerZone } from '../common/viewModel/overviewZoneManager.js';
2525
import { MenuId } from '../../platform/actions/common/actions.js';
@@ -900,6 +900,13 @@ export interface ICodeEditor extends editorCommon.IEditor {
900900
*/
901901
onDidChangeLineHeight: Event<ModelLineHeightChangedEvent>;
902902

903+
/**
904+
* An event emitted when the font of the editor has changed.
905+
* @internal
906+
* @event
907+
*/
908+
onDidChangeFont: Event<ModelFontChangedEvent>;
909+
903910
/**
904911
* Get value of the current model attached to this editor.
905912
* @see {@link ITextModel.getValue}
@@ -1020,6 +1027,12 @@ export interface ICodeEditor extends editorCommon.IEditor {
10201027
*/
10211028
getDecorationsInRange(range: Range): IModelDecoration[] | null;
10221029

1030+
/**
1031+
* Get the font size at a given position
1032+
* @param position the position for which to fetch the font size
1033+
*/
1034+
getFontSizeAtPosition(position: IPosition): string | null;
1035+
10231036
/**
10241037
* All decorations added through this call will get the ownerId of this editor.
10251038
* @deprecated Use `createDecorationsCollection`

src/vs/editor/browser/services/abstractCodeEditorService.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
461461
public glyphMarginClassName: string | undefined;
462462
public isWholeLine: boolean;
463463
public lineHeight: number | undefined;
464+
public fontSize: string | undefined;
465+
public fontFamily: string | undefined;
466+
public fontWeight: string | undefined;
467+
public fontStyle: string | undefined;
464468
public overviewRuler: IModelDecorationOverviewRulerOptions | undefined;
465469
public stickiness: TrackedRangeStickiness | undefined;
466470
public beforeInjectedText: InjectedTextOptions | undefined;
@@ -522,6 +526,10 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
522526
const options = providerArgs.options;
523527
this.isWholeLine = Boolean(options.isWholeLine);
524528
this.lineHeight = options.lineHeight;
529+
this.fontFamily = options.fontFamily;
530+
this.fontSize = options.fontSize;
531+
this.fontWeight = options.fontWeight;
532+
this.fontStyle = options.fontStyle;
525533
this.stickiness = options.rangeBehavior;
526534

527535
const lightOverviewRulerColor = options.light && options.light.overviewRulerColor || options.overviewRulerColor;
@@ -552,6 +560,10 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
552560
glyphMarginClassName: this.glyphMarginClassName,
553561
isWholeLine: this.isWholeLine,
554562
lineHeight: this.lineHeight,
563+
fontFamily: this.fontFamily,
564+
fontSize: this.fontSize,
565+
fontWeight: this.fontWeight,
566+
fontStyle: this.fontStyle,
555567
overviewRuler: this.overviewRuler,
556568
stickiness: this.stickiness,
557569
before: this.beforeInjectedText,
@@ -759,7 +771,7 @@ class DecorationCSSRules {
759771
return '';
760772
}
761773
const cssTextArr: string[] = [];
762-
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'cursor', 'color', 'opacity', 'letterSpacing'], cssTextArr);
774+
this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'fontFamily', 'fontSize', 'textDecoration', 'cursor', 'color', 'opacity', 'letterSpacing'], cssTextArr);
763775
if (opts.letterSpacing) {
764776
this._hasLetterSpacing = true;
765777
}

src/vs/editor/browser/viewParts/viewLines/viewLine.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { RangeUtil } from './rangeUtil.js';
1111
import { StringBuilder } from '../../../common/core/stringBuilder.js';
1212
import { FloatHorizontalRange, VisibleRanges } from '../../view/renderingContext.js';
1313
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
14-
import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, DomPosition } from '../../../common/viewLayout/viewLineRenderer.js';
14+
import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, DomPosition, RenderWhitespace } from '../../../common/viewLayout/viewLineRenderer.js';
1515
import { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';
1616
import { InlineDecorationType } from '../../../common/viewModel.js';
1717
import { isHighContrast } from '../../../../platform/theme/common/theme.js';
@@ -91,7 +91,7 @@ export class ViewLine implements IVisibleLine {
9191
this._options = newOptions;
9292
}
9393
public onSelectionChanged(): boolean {
94-
if (isHighContrast(this._options.themeType) || this._options.renderWhitespace === 'selection') {
94+
if (isHighContrast(this._options.themeType) || this._renderedViewLine?.getRenderWhitespace() === RenderWhitespace.Selection) {
9595
this._isMaybeInvalid = true;
9696
return true;
9797
}
@@ -115,10 +115,12 @@ export class ViewLine implements IVisibleLine {
115115
const lineData = viewportData.getViewLineRenderingData(lineNumber);
116116
const options = this._options;
117117
const actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn);
118+
const renderWhitespace = (lineData.hasVariableFonts || options.experimentalWhitespaceRendering === 'off') ? options.renderWhitespace : 'none';
119+
const allowFastRendering = !lineData.hasVariableFonts;
118120

119121
// Only send selection information when needed for rendering whitespace
120122
let selectionsOnLine: OffsetRange[] | null = null;
121-
if (isHighContrast(options.themeType) || this._options.renderWhitespace === 'selection') {
123+
if (isHighContrast(options.themeType) || renderWhitespace === 'selection') {
122124
const selections = viewportData.selections;
123125
for (const selection of selections) {
124126

@@ -134,7 +136,7 @@ export class ViewLine implements IVisibleLine {
134136
if (isHighContrast(options.themeType)) {
135137
actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular));
136138
}
137-
if (this._options.renderWhitespace === 'selection') {
139+
if (renderWhitespace === 'selection') {
138140
if (!selectionsOnLine) {
139141
selectionsOnLine = [];
140142
}
@@ -161,7 +163,7 @@ export class ViewLine implements IVisibleLine {
161163
options.middotWidth,
162164
options.wsmiddotWidth,
163165
options.stopRenderingLineAfter,
164-
options.renderWhitespace,
166+
renderWhitespace,
165167
options.renderControlCharacters,
166168
options.fontLigatures !== EditorFontLigatures.OFF,
167169
selectionsOnLine
@@ -187,7 +189,7 @@ export class ViewLine implements IVisibleLine {
187189
sb.appendString('</div>');
188190

189191
let renderedViewLine: IRenderedViewLine | null = null;
190-
if (monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) {
192+
if (allowFastRendering && monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) {
191193
renderedViewLine = new FastRenderedViewLine(
192194
this._renderedViewLine ? this._renderedViewLine.domNode : null,
193195
renderLineInput,
@@ -301,6 +303,7 @@ interface IRenderedViewLine {
301303
readonly input: RenderLineInput;
302304
getWidth(context: DomReadingContext | null): number;
303305
getWidthIsFast(): boolean;
306+
getRenderWhitespace(): RenderWhitespace;
304307
getVisibleRangesForRange(lineNumber: number, startColumn: number, endColumn: number, context: DomReadingContext): FloatHorizontalRange[] | null;
305308
getColumnOfNodeOffset(spanNode: HTMLElement, offset: number): number;
306309
}
@@ -346,6 +349,10 @@ class FastRenderedViewLine implements IRenderedViewLine {
346349
this._charWidth = renderLineInput.spaceWidth;
347350
}
348351

352+
public getRenderWhitespace(): RenderWhitespace {
353+
return this.input.renderWhitespace;
354+
}
355+
349356
public getWidth(context: DomReadingContext | null): number {
350357
if (!this.domNode || this.input.lineContent.length < Constants.MaxMonospaceDistance) {
351358
const horizontalOffset = this._characterMapping.getHorizontalOffset(this._characterMapping.length);
@@ -478,6 +485,13 @@ class RenderedViewLine implements IRenderedViewLine {
478485
return <HTMLSpanElement>myDomNode.domNode.firstChild;
479486
}
480487

488+
/**
489+
* The render whitespace setting for this line
490+
*/
491+
public getRenderWhitespace(): RenderWhitespace {
492+
return this.input.renderWhitespace;
493+
}
494+
481495
/**
482496
* Width of the line in pixels
483497
*/

src/vs/editor/browser/viewParts/viewLines/viewLineOptions.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { EditorOption } from '../../../common/config/editorOptions.js';
1010
export class ViewLineOptions {
1111
public readonly themeType: ColorScheme;
1212
public readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all';
13+
public readonly experimentalWhitespaceRendering: 'svg' | 'font' | 'off';
1314
public readonly renderControlCharacters: boolean;
1415
public readonly spaceWidth: number;
1516
public readonly middotWidth: number;
@@ -25,13 +26,8 @@ export class ViewLineOptions {
2526
this.themeType = themeType;
2627
const options = config.options;
2728
const fontInfo = options.get(EditorOption.fontInfo);
28-
const experimentalWhitespaceRendering = options.get(EditorOption.experimentalWhitespaceRendering);
29-
if (experimentalWhitespaceRendering === 'off') {
30-
this.renderWhitespace = options.get(EditorOption.renderWhitespace);
31-
} else {
32-
// whitespace is rendered in a different layer
33-
this.renderWhitespace = 'none';
34-
}
29+
this.renderWhitespace = options.get(EditorOption.renderWhitespace);
30+
this.experimentalWhitespaceRendering = options.get(EditorOption.experimentalWhitespaceRendering);
3531
this.renderControlCharacters = options.get(EditorOption.renderControlCharacters);
3632
this.spaceWidth = fontInfo.spaceWidth;
3733
this.middotWidth = fontInfo.middotWidth;
@@ -51,6 +47,7 @@ export class ViewLineOptions {
5147
return (
5248
this.themeType === other.themeType
5349
&& this.renderWhitespace === other.renderWhitespace
50+
&& this.experimentalWhitespaceRendering === other.experimentalWhitespaceRendering
5451
&& this.renderControlCharacters === other.renderControlCharacters
5552
&& this.spaceWidth === other.spaceWidth
5653
&& this.middotWidth === other.middotWidth

src/vs/editor/browser/viewParts/whitespace/whitespace.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Selection } from '../../../common/core/selection.js';
99
import { RenderingContext } from '../../view/renderingContext.js';
1010
import { ViewContext } from '../../../common/viewModel/viewContext.js';
1111
import * as viewEvents from '../../../common/viewEvents.js';
12-
import { ViewLineData } from '../../../common/viewModel.js';
12+
import { ViewLineRenderingData } from '../../../common/viewModel.js';
1313
import { EditorOption } from '../../../common/config/editorOptions.js';
1414
import { IEditorConfiguration } from '../../../common/config/editorConfiguration.js';
1515
import * as strings from '../../../../base/common/strings.js';
@@ -97,12 +97,11 @@ export class WhitespaceOverlay extends DynamicViewOverlay {
9797
for (let i = 0; i < lineCount; i++) {
9898
needed[i] = true;
9999
}
100-
const viewportData = this._context.viewModel.getMinimapLinesRenderingData(ctx.viewportData.startLineNumber, ctx.viewportData.endLineNumber, needed);
101100

102101
this._renderResult = [];
103102
for (let lineNumber = ctx.viewportData.startLineNumber; lineNumber <= ctx.viewportData.endLineNumber; lineNumber++) {
104103
const lineIndex = lineNumber - ctx.viewportData.startLineNumber;
105-
const lineData = viewportData.data[lineIndex]!;
104+
const lineData = this._context.viewModel.getViewLineRenderingData(lineNumber);
106105

107106
let selectionsOnLine: OffsetRange[] | null = null;
108107
if (this._options.renderWhitespace === 'selection') {
@@ -130,7 +129,10 @@ export class WhitespaceOverlay extends DynamicViewOverlay {
130129
}
131130
}
132131

133-
private _applyRenderWhitespace(ctx: RenderingContext, lineNumber: number, selections: OffsetRange[] | null, lineData: ViewLineData): string {
132+
private _applyRenderWhitespace(ctx: RenderingContext, lineNumber: number, selections: OffsetRange[] | null, lineData: ViewLineRenderingData): string {
133+
if (lineData.hasVariableFonts) {
134+
return '';
135+
}
134136
if (this._options.renderWhitespace === 'selection' && !selections) {
135137
return '';
136138
}

src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { ICommandDelegate } from '../../view/viewController.js';
2626
import { ViewUserInputEvents } from '../../view/viewUserInputEvents.js';
2727
import { CodeEditorContributions } from './codeEditorContributions.js';
2828
import { IEditorConfiguration } from '../../../common/config/editorConfiguration.js';
29-
import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterValidationDecorations } from '../../../common/config/editorOptions.js';
29+
import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterFontDecorations, filterValidationDecorations } from '../../../common/config/editorOptions.js';
3030
import { CursorColumns } from '../../../common/core/cursorColumns.js';
3131
import { IDimension } from '../../../common/core/2d/dimension.js';
3232
import { editorUnnecessaryCodeOpacity } from '../../../common/core/editorColorRegistry.js';
@@ -44,7 +44,7 @@ import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSi
4444
import { ClassName } from '../../../common/model/intervalTree.js';
4545
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
4646
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
47-
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelLineHeightChangedEvent } from '../../../common/textModelEvents.js';
47+
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelFontChangedEvent, ModelLineHeightChangedEvent } from '../../../common/textModelEvents.js';
4848
import { VerticalRevealType } from '../../../common/viewEvents.js';
4949
import { IEditorWhitespace, IViewModel } from '../../../common/viewModel.js';
5050
import { MonospaceLineBreaksComputerFactory } from '../../../common/viewModel/monospaceLineBreaksComputer.js';
@@ -96,6 +96,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
9696
private readonly _onDidChangeLineHeight: Emitter<ModelLineHeightChangedEvent> = this._register(new Emitter<ModelLineHeightChangedEvent>({ deliveryQueue: this._deliveryQueue }));
9797
public readonly onDidChangeLineHeight: Event<ModelLineHeightChangedEvent> = this._onDidChangeLineHeight.event;
9898

99+
private readonly _onDidChangeFont: Emitter<ModelFontChangedEvent> = this._register(new Emitter<ModelFontChangedEvent>({ deliveryQueue: this._deliveryQueue }));
100+
public readonly onDidChangeFont: Event<ModelFontChangedEvent> = this._onDidChangeFont.event;
101+
99102
private readonly _onDidChangeModelTokens: Emitter<IModelTokensChangedEvent> = this._register(new Emitter<IModelTokensChangedEvent>({ deliveryQueue: this._deliveryQueue }));
100103
public readonly onDidChangeModelTokens: Event<IModelTokensChangedEvent> = this._onDidChangeModelTokens.event;
101104

@@ -1309,14 +1312,23 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
13091312
if (!this._modelData) {
13101313
return null;
13111314
}
1312-
return this._modelData.model.getLineDecorations(lineNumber, this._id, filterValidationDecorations(this._configuration.options));
1315+
const options = this._configuration.options;
1316+
return this._modelData.model.getLineDecorations(lineNumber, this._id, filterValidationDecorations(options), filterFontDecorations(options));
13131317
}
13141318

13151319
public getDecorationsInRange(range: Range): IModelDecoration[] | null {
13161320
if (!this._modelData) {
13171321
return null;
13181322
}
1319-
return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(this._configuration.options));
1323+
const options = this._configuration.options;
1324+
return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(options), filterFontDecorations(options));
1325+
}
1326+
1327+
public getFontSizeAtPosition(position: IPosition): string | null {
1328+
if (!this._modelData) {
1329+
return null;
1330+
}
1331+
return this._modelData.viewModel.getFontSizeAtPosition(position);
13201332
}
13211333

13221334
/**
@@ -1809,7 +1821,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
18091821
case OutgoingViewModelEventKind.ModelLineHeightChanged:
18101822
this._onDidChangeLineHeight.fire(e.event);
18111823
break;
1812-
1824+
case OutgoingViewModelEventKind.ModelFontChangedEvent:
1825+
this._onDidChangeFont.fire(e.event);
1826+
break;
18131827
}
18141828
}));
18151829

src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ export class DiffEditorEditors extends Disposable {
184184
clonedOptions.inDiffEditor = true;
185185
clonedOptions.automaticLayout = false;
186186
clonedOptions.allowVariableLineHeights = false;
187+
clonedOptions.allowVariableFonts = false;
188+
clonedOptions.allowVariableFontsInAccessibilityMode = false;
187189

188190
// Clone scrollbar options before changing them
189191
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };

0 commit comments

Comments
 (0)