Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

BaseRenderLayer.ts 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /**
  2. * Copyright (c) 2017 The xterm.js authors. All rights reserved.
  3. * @license MIT
  4. */
  5. import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
  6. import { ICellData } from 'common/Types';
  7. import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
  8. import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
  9. import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
  10. import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
  11. import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
  12. import { AttributeData } from 'common/buffer/AttributeData';
  13. import { IColorSet } from 'browser/Types';
  14. import { CellData } from 'common/buffer/CellData';
  15. import { IBufferService, IOptionsService } from 'common/services/Services';
  16. import { throwIfFalsy } from 'browser/renderer/RendererUtils';
  17. export abstract class BaseRenderLayer implements IRenderLayer {
  18. private _canvas: HTMLCanvasElement;
  19. protected _ctx!: CanvasRenderingContext2D;
  20. private _scaledCharWidth: number = 0;
  21. private _scaledCharHeight: number = 0;
  22. private _scaledCellWidth: number = 0;
  23. private _scaledCellHeight: number = 0;
  24. private _scaledCharLeft: number = 0;
  25. private _scaledCharTop: number = 0;
  26. protected _charAtlas: BaseCharAtlas | undefined;
  27. /**
  28. * An object that's reused when drawing glyphs in order to reduce GC.
  29. */
  30. private _currentGlyphIdentifier: IGlyphIdentifier = {
  31. chars: '',
  32. code: 0,
  33. bg: 0,
  34. fg: 0,
  35. bold: false,
  36. dim: false,
  37. italic: false
  38. };
  39. constructor(
  40. private _container: HTMLElement,
  41. id: string,
  42. zIndex: number,
  43. private _alpha: boolean,
  44. protected _colors: IColorSet,
  45. private _rendererId: number,
  46. protected readonly _bufferService: IBufferService,
  47. protected readonly _optionsService: IOptionsService
  48. ) {
  49. this._canvas = document.createElement('canvas');
  50. this._canvas.classList.add(`xterm-${id}-layer`);
  51. this._canvas.style.zIndex = zIndex.toString();
  52. this._initCanvas();
  53. this._container.appendChild(this._canvas);
  54. }
  55. public dispose(): void {
  56. this._container.removeChild(this._canvas);
  57. if (this._charAtlas) {
  58. this._charAtlas.dispose();
  59. }
  60. }
  61. private _initCanvas(): void {
  62. this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
  63. // Draw the background if this is an opaque layer
  64. if (!this._alpha) {
  65. this._clearAll();
  66. }
  67. }
  68. public onOptionsChanged(): void {}
  69. public onBlur(): void {}
  70. public onFocus(): void {}
  71. public onCursorMove(): void {}
  72. public onGridChanged(startRow: number, endRow: number): void {}
  73. public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
  74. public setColors(colorSet: IColorSet): void {
  75. this._refreshCharAtlas(colorSet);
  76. }
  77. protected _setTransparency(alpha: boolean): void {
  78. // Do nothing when alpha doesn't change
  79. if (alpha === this._alpha) {
  80. return;
  81. }
  82. // Create new canvas and replace old one
  83. const oldCanvas = this._canvas;
  84. this._alpha = alpha;
  85. // Cloning preserves properties
  86. this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
  87. this._initCanvas();
  88. this._container.replaceChild(this._canvas, oldCanvas);
  89. // Regenerate char atlas and force a full redraw
  90. this._refreshCharAtlas(this._colors);
  91. this.onGridChanged(0, this._bufferService.rows - 1);
  92. }
  93. /**
  94. * Refreshes the char atlas, aquiring a new one if necessary.
  95. * @param colorSet The color set to use for the char atlas.
  96. */
  97. private _refreshCharAtlas(colorSet: IColorSet): void {
  98. if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
  99. return;
  100. }
  101. this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
  102. this._charAtlas.warmUp();
  103. }
  104. public resize(dim: IRenderDimensions): void {
  105. this._scaledCellWidth = dim.scaledCellWidth;
  106. this._scaledCellHeight = dim.scaledCellHeight;
  107. this._scaledCharWidth = dim.scaledCharWidth;
  108. this._scaledCharHeight = dim.scaledCharHeight;
  109. this._scaledCharLeft = dim.scaledCharLeft;
  110. this._scaledCharTop = dim.scaledCharTop;
  111. this._canvas.width = dim.scaledCanvasWidth;
  112. this._canvas.height = dim.scaledCanvasHeight;
  113. this._canvas.style.width = `${dim.canvasWidth}px`;
  114. this._canvas.style.height = `${dim.canvasHeight}px`;
  115. // Draw the background if this is an opaque layer
  116. if (!this._alpha) {
  117. this._clearAll();
  118. }
  119. this._refreshCharAtlas(this._colors);
  120. }
  121. public abstract reset(): void;
  122. /**
  123. * Fills 1+ cells completely. This uses the existing fillStyle on the context.
  124. * @param x The column to start at.
  125. * @param y The row to start at
  126. * @param width The number of columns to fill.
  127. * @param height The number of rows to fill.
  128. */
  129. protected _fillCells(x: number, y: number, width: number, height: number): void {
  130. this._ctx.fillRect(
  131. x * this._scaledCellWidth,
  132. y * this._scaledCellHeight,
  133. width * this._scaledCellWidth,
  134. height * this._scaledCellHeight);
  135. }
  136. /**
  137. * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
  138. * existing fillStyle on the context.
  139. * @param x The column to fill.
  140. * @param y The row to fill.
  141. */
  142. protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
  143. this._ctx.fillRect(
  144. x * this._scaledCellWidth,
  145. (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
  146. width * this._scaledCellWidth,
  147. window.devicePixelRatio);
  148. }
  149. /**
  150. * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
  151. * existing fillStyle on the context.
  152. * @param x The column to fill.
  153. * @param y The row to fill.
  154. */
  155. protected _fillLeftLineAtCell(x: number, y: number): void {
  156. this._ctx.fillRect(
  157. x * this._scaledCellWidth,
  158. y * this._scaledCellHeight,
  159. window.devicePixelRatio,
  160. this._scaledCellHeight);
  161. }
  162. /**
  163. * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
  164. * strokeStyle on the context.
  165. * @param x The column to fill.
  166. * @param y The row to fill.
  167. */
  168. protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
  169. this._ctx.lineWidth = window.devicePixelRatio;
  170. this._ctx.strokeRect(
  171. x * this._scaledCellWidth + window.devicePixelRatio / 2,
  172. y * this._scaledCellHeight + (window.devicePixelRatio / 2),
  173. width * this._scaledCellWidth - window.devicePixelRatio,
  174. (height * this._scaledCellHeight) - window.devicePixelRatio);
  175. }
  176. /**
  177. * Clears the entire canvas.
  178. */
  179. protected _clearAll(): void {
  180. if (this._alpha) {
  181. this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
  182. } else {
  183. this._ctx.fillStyle = this._colors.background.css;
  184. this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
  185. }
  186. }
  187. /**
  188. * Clears 1+ cells completely.
  189. * @param x The column to start at.
  190. * @param y The row to start at.
  191. * @param width The number of columns to clear.
  192. * @param height The number of rows to clear.
  193. */
  194. protected _clearCells(x: number, y: number, width: number, height: number): void {
  195. if (this._alpha) {
  196. this._ctx.clearRect(
  197. x * this._scaledCellWidth,
  198. y * this._scaledCellHeight,
  199. width * this._scaledCellWidth,
  200. height * this._scaledCellHeight);
  201. } else {
  202. this._ctx.fillStyle = this._colors.background.css;
  203. this._ctx.fillRect(
  204. x * this._scaledCellWidth,
  205. y * this._scaledCellHeight,
  206. width * this._scaledCellWidth,
  207. height * this._scaledCellHeight);
  208. }
  209. }
  210. /**
  211. * Draws a truecolor character at the cell. The character will be clipped to
  212. * ensure that it fits with the cell, including the cell to the right if it's
  213. * a wide character. This uses the existing fillStyle on the context.
  214. * @param cell The cell data for the character to draw.
  215. * @param x The column to draw at.
  216. * @param y The row to draw at.
  217. * @param color The color of the character.
  218. */
  219. protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
  220. this._ctx.font = this._getFont(false, false);
  221. this._ctx.textBaseline = 'middle';
  222. this._clipRow(y);
  223. this._ctx.fillText(
  224. cell.getChars(),
  225. x * this._scaledCellWidth + this._scaledCharLeft,
  226. y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
  227. }
  228. /**
  229. * Draws one or more characters at a cell. If possible this will draw using
  230. * the character atlas to reduce draw time.
  231. * @param chars The character or characters.
  232. * @param code The character code.
  233. * @param width The width of the characters.
  234. * @param x The column to draw at.
  235. * @param y The row to draw at.
  236. * @param fg The foreground color, in the format stored within the attributes.
  237. * @param bg The background color, in the format stored within the attributes.
  238. * This is used to validate whether a cached image can be used.
  239. * @param bold Whether the text is bold.
  240. */
  241. protected _drawChars(cell: ICellData, x: number, y: number): void {
  242. // skip cache right away if we draw in RGB
  243. // Note: to avoid bad runtime JoinedCellData will be skipped
  244. // in the cache handler itself (atlasDidDraw == false) and
  245. // fall through to uncached later down below
  246. if (cell.isFgRGB() || cell.isBgRGB()) {
  247. this._drawUncachedChars(cell, x, y);
  248. return;
  249. }
  250. let fg;
  251. let bg;
  252. if (cell.isInverse()) {
  253. fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor();
  254. bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor();
  255. } else {
  256. bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor();
  257. fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
  258. }
  259. const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
  260. fg += drawInBrightColor ? 8 : 0;
  261. this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
  262. this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE;
  263. this._currentGlyphIdentifier.bg = bg;
  264. this._currentGlyphIdentifier.fg = fg;
  265. this._currentGlyphIdentifier.bold = !!cell.isBold();
  266. this._currentGlyphIdentifier.dim = !!cell.isDim();
  267. this._currentGlyphIdentifier.italic = !!cell.isItalic();
  268. const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
  269. this._ctx,
  270. this._currentGlyphIdentifier,
  271. x * this._scaledCellWidth + this._scaledCharLeft,
  272. y * this._scaledCellHeight + this._scaledCharTop
  273. );
  274. if (!atlasDidDraw) {
  275. this._drawUncachedChars(cell, x, y);
  276. }
  277. }
  278. /**
  279. * Draws one or more characters at one or more cells. The character(s) will be
  280. * clipped to ensure that they fit with the cell(s), including the cell to the
  281. * right if the last character is a wide character.
  282. * @param chars The character.
  283. * @param width The width of the character.
  284. * @param fg The foreground color, in the format stored within the attributes.
  285. * @param x The column to draw at.
  286. * @param y The row to draw at.
  287. */
  288. private _drawUncachedChars(cell: ICellData, x: number, y: number): void {
  289. this._ctx.save();
  290. this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
  291. this._ctx.textBaseline = 'middle';
  292. if (cell.isInverse()) {
  293. if (cell.isBgDefault()) {
  294. this._ctx.fillStyle = this._colors.background.css;
  295. } else if (cell.isBgRGB()) {
  296. this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
  297. } else {
  298. this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
  299. }
  300. } else {
  301. if (cell.isFgDefault()) {
  302. this._ctx.fillStyle = this._colors.foreground.css;
  303. } else if (cell.isFgRGB()) {
  304. this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
  305. } else {
  306. let fg = cell.getFgColor();
  307. if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
  308. fg += 8;
  309. }
  310. this._ctx.fillStyle = this._colors.ansi[fg].css;
  311. }
  312. }
  313. this._clipRow(y);
  314. // Apply alpha to dim the character
  315. if (cell.isDim()) {
  316. this._ctx.globalAlpha = DIM_OPACITY;
  317. }
  318. // Draw the character
  319. this._ctx.fillText(
  320. cell.getChars(),
  321. x * this._scaledCellWidth + this._scaledCharLeft,
  322. y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
  323. this._ctx.restore();
  324. }
  325. /**
  326. * Clips a row to ensure no pixels will be drawn outside the cells in the row.
  327. * @param y The row to clip.
  328. */
  329. private _clipRow(y: number): void {
  330. this._ctx.beginPath();
  331. this._ctx.rect(
  332. 0,
  333. y * this._scaledCellHeight,
  334. this._bufferService.cols * this._scaledCellWidth,
  335. this._scaledCellHeight);
  336. this._ctx.clip();
  337. }
  338. /**
  339. * Gets the current font.
  340. * @param isBold If we should use the bold fontWeight.
  341. */
  342. protected _getFont(isBold: boolean, isItalic: boolean): string {
  343. const fontWeight = isBold ? this._optionsService.options.fontWeightBold : this._optionsService.options.fontWeight;
  344. const fontStyle = isItalic ? 'italic' : '';
  345. return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;
  346. }
  347. }