2 * Copyright (c) 2016 The xterm.js authors. All rights reserved.
6 import { IOptionsService } from 'common/services/Services';
7 import { IEvent, EventEmitter } from 'common/EventEmitter';
8 import { ICharSizeService } from 'browser/services/Services';
10 export class CharSizeService implements ICharSizeService {
13 public width: number = 0;
14 public height: number = 0;
15 private _measureStrategy: IMeasureStrategy;
17 public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
19 private _onCharSizeChange = new EventEmitter<void>();
20 public get onCharSizeChange(): IEvent<void> { return this._onCharSizeChange.event; }
23 readonly document: Document,
24 readonly parentElement: HTMLElement,
25 @IOptionsService private readonly _optionsService: IOptionsService
27 this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
30 public measure(): void {
31 const result = this._measureStrategy.measure();
32 if (result.width !== this.width || result.height !== this.height) {
33 this.width = result.width;
34 this.height = result.height;
35 this._onCharSizeChange.fire();
40 interface IMeasureStrategy {
41 measure(): IReadonlyMeasureResult;
44 interface IReadonlyMeasureResult {
45 readonly width: number;
46 readonly height: number;
49 interface IMeasureResult {
54 // TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText
55 class DomMeasureStrategy implements IMeasureStrategy {
56 private _result: IMeasureResult = { width: 0, height: 0 };
57 private _measureElement: HTMLElement;
60 private _document: Document,
61 private _parentElement: HTMLElement,
62 private _optionsService: IOptionsService
64 this._measureElement = this._document.createElement('span');
65 this._measureElement.classList.add('xterm-char-measure-element');
66 this._measureElement.textContent = 'W';
67 this._measureElement.setAttribute('aria-hidden', 'true');
68 this._parentElement.appendChild(this._measureElement);
71 public measure(): IReadonlyMeasureResult {
72 this._measureElement.style.fontFamily = this._optionsService.options.fontFamily;
73 this._measureElement.style.fontSize = `${this._optionsService.options.fontSize}px`;
75 // Note that this triggers a synchronous layout
76 const geometry = this._measureElement.getBoundingClientRect();
78 // If values are 0 then the element is likely currently display:none, in which case we should
79 // retain the previous value.
80 if (geometry.width !== 0 && geometry.height !== 0) {
81 this._result.width = geometry.width;
82 this._result.height = Math.ceil(geometry.height);