• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14840900305

05 May 2025 04:07PM UTC coverage: 87.864% (-1.5%) from 89.319%
14840900305

push

github

web-flow
chore: migrate tests to vitest (#3381)

managed to migrate the engine leak & memory reporters, although it took a bit of rigamarole. Mainly because, as opposed to karma, the reporter runs in node and not the browser. So I have to track the data needed separately in some global hooks _within_ the browser environment, which the reporter then reads and creates the logs if needed.

However, it seems the memory tracking is regularly reporting >1 mb, so I'm not sure if it's tracking properly or if things have changed with the new browsers. My only theory is that because the timing where memory is read is during an `afterEach` _before_ the test's `afterEach`, it's running before any potential cleanup. The timing of this is not something that's easy to control, unfortunately. I did try to prove this theory by doing the memory analysis on the next test's `beforeEach`, but it didnt seem to change the results, so it may not be an issue.

Implementation is done in `src/spec/vitest/__reporters__/memory.ts` and `src/spec/vitest/__reporters/memory.setup.ts`

---

I noticed CouroutineSpec was an offender for >10mb memory, which spins up additional engines. I added a short 100ms wait after the engine.dispose and the memory usage did drop, so it does seem like this is prone to scheduled garbage collecting.

---

I was able to run the garbage collector (if exposed, currently only on chrome) and this makes the reports more accurate

4998 of 6942 branches covered (72.0%)

13655 of 15541 relevant lines covered (87.86%)

25165.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.52
/src/engine/Graphics/Font.ts
1
import type { Vector } from '../Math/vector';
2
import { BoundingBox } from '../Collision/Index';
3
import { Color } from '../Color';
4
import type { ExcaliburGraphicsContext } from './Context/ExcaliburGraphicsContext';
5
import type { FontOptions, FontRenderer } from './FontCommon';
6
import { BaseAlign, Direction, FontStyle, FontUnit, TextAlign } from './FontCommon';
7
import type { GraphicOptions } from './Graphic';
8
import { Graphic } from './Graphic';
9
import type { RasterOptions } from './Raster';
10
import { ImageFiltering } from './Filtering';
11
import { FontTextInstance } from './FontTextInstance';
12
import { FontCache } from './FontCache';
13
/**
14
 * Represents a system or web font in Excalibur
15
 *
16
 * If no options specified, the system sans-serif 10 pixel is used
17
 *
18
 * If loading a custom web font be sure to have the font loaded before you use it https://erikonarheim.com/posts/dont-test-fonts/
19
 */
20
export class Font extends Graphic implements FontRenderer {
21
  /**
22
   * Set the font filtering mode, by default set to {@apilink ImageFiltering.Blended} regardless of the engine default smoothing
23
   *
24
   * If you have a pixel style font that may be a reason to switch this to {@apilink ImageFiltering.Pixel}
25
   */
26
  public filtering: ImageFiltering = ImageFiltering.Blended;
9✔
27
  constructor(options: FontOptions & GraphicOptions & RasterOptions = {}) {
5✔
28
    super(options); // <- Graphics properties
9✔
29

30
    // Raster properties
31
    this.smoothing = options?.smoothing ?? this.smoothing;
9!
32
    this.padding = options?.padding ?? this.padding;
9!
33
    this.color = options?.color ?? this.color;
9!
34
    this.strokeColor = options?.strokeColor ?? this.strokeColor;
9!
35
    this.lineDash = options?.lineDash ?? this.lineDash;
9!
36
    this.lineWidth = options?.lineWidth ?? this.lineWidth;
9!
37
    this.filtering = options?.filtering ?? this.filtering;
9!
38

39
    // Font specific properties
40
    this.family = options?.family ?? this.family;
9!
41
    this.style = options?.style ?? this.style;
9!
42
    this.bold = options?.bold ?? this.bold;
9!
43
    this.size = options?.size ?? this.size;
9!
44
    this.unit = options?.unit ?? this.unit;
9!
45
    this.textAlign = options?.textAlign ?? this.textAlign;
9!
46
    this.baseAlign = options?.baseAlign ?? this.baseAlign;
9!
47
    this.direction = options?.direction ?? this.direction;
9!
48
    this.lineHeight = options?.lineHeight ?? this.lineHeight;
9!
49
    this.quality = options?.quality ?? this.quality;
9!
50
    if (options?.shadow) {
9!
51
      this.shadow = {};
×
52
      this.shadow.blur = options.shadow.blur ?? this.shadow.blur;
×
53
      this.shadow.offset = options.shadow.offset ?? this.shadow.offset;
×
54
      this.shadow.color = options.shadow.color ?? this.shadow.color;
×
55
    }
56
  }
57

58
  public clone() {
59
    return new Font({
×
60
      ...this.cloneGraphicOptions(),
61
      size: this.size,
62
      unit: this.unit,
63
      family: this.family,
64
      style: this.style,
65
      bold: this.bold,
66
      textAlign: this.textAlign,
67
      baseAlign: this.baseAlign,
68
      direction: this.direction,
69
      shadow: this.shadow
×
70
        ? {
71
            blur: this.shadow.blur,
72
            offset: this.shadow.offset,
73
            color: this.shadow.color
74
          }
75
        : undefined
76
    });
77
  }
78

79
  /**
80
   * Font quality determines the size of the underlying raster text, higher quality means less jagged edges.
81
   * If quality is set to 1, then just enough raster bitmap is generated to render the text.
82
   *
83
   * You can think of quality as how zoomed in to the text you can get before seeing jagged edges.
84
   *
85
   * (Default 2)
86
   */
87
  public quality = 2;
9✔
88

89
  // Raster properties for fonts
90
  public padding = 2;
9✔
91
  public smoothing = false;
9✔
92
  public lineWidth = 1;
9✔
93
  public lineDash: number[] = [];
9✔
94
  public color: Color = Color.Black;
9✔
95
  public strokeColor?: Color;
96

97
  public family: string = 'sans-serif';
9✔
98
  public style: FontStyle = FontStyle.Normal;
9✔
99
  public bold: boolean = false;
9✔
100
  public unit: FontUnit = FontUnit.Px;
9✔
101
  public textAlign: TextAlign = TextAlign.Left;
9✔
102
  public baseAlign: BaseAlign = BaseAlign.Top;
9✔
103
  public direction: Direction = Direction.LeftToRight;
9✔
104
  /**
105
   * Font line height in pixels, default line height if unset
106
   */
107
  public lineHeight: number | undefined = undefined;
9✔
108
  public size: number = 10;
9✔
109
  public shadow?: { blur?: number; offset?: Vector; color?: Color };
110

111
  public get fontString() {
112
    return `${this.style} ${this.bold ? 'bold' : ''} ${this.size}${this.unit} ${this.family}`;
39!
113
  }
114

115
  private _textBounds: BoundingBox = new BoundingBox();
9✔
116

117
  public get localBounds(): BoundingBox {
118
    return this._textBounds;
×
119
  }
120

121
  protected _drawImage(_ex: ExcaliburGraphicsContext, _x: number, _y: number) {
122
    // TODO weird vestigial drawimage
123
  }
124

125
  protected _rotate(ex: ExcaliburGraphicsContext) {
126
    // TODO this needs to change depending on the bounding box...
127
    const origin = this.origin ?? this._textBounds.center;
×
128
    ex.translate(origin.x, origin.y);
×
129
    ex.rotate(this.rotation);
×
130
    ex.translate(-origin.x, -origin.y);
×
131
  }
132

133
  protected _flip(ex: ExcaliburGraphicsContext) {
134
    if (this.flipHorizontal) {
×
135
      ex.translate(this._textBounds.width / this.scale.x, 0);
×
136
      ex.scale(-1, 1);
×
137
    }
138

139
    if (this.flipVertical) {
×
140
      ex.translate(0, -this._textBounds.height / 2 / this.scale.y);
×
141
      ex.scale(1, -1);
×
142
    }
143
  }
144

145
  private _textMeasurement = new FontTextInstance(this, '', Color.Black);
9✔
146

147
  public measureTextWithoutCache(text: string, maxWidth?: number) {
148
    return this._textMeasurement.measureText(text, maxWidth);
3✔
149
  }
150

151
  /**
152
   * Returns a BoundingBox that is the total size of the text including multiple lines
153
   *
154
   * Does not include any padding or adjustment
155
   * @param text
156
   * @returns BoundingBox
157
   */
158
  public measureText(text: string, maxWidth?: number): BoundingBox {
159
    return FontCache.measureText(text, this, maxWidth);
18✔
160
  }
161

162
  protected _postDraw(ex: ExcaliburGraphicsContext): void {
163
    ex.restore();
×
164
  }
165

166
  public render(ex: ExcaliburGraphicsContext, text: string, colorOverride: Color, x: number, y: number, maxWidth?: number) {
167
    const textInstance = FontCache.getTextInstance(text, this, colorOverride);
×
168

169
    // Apply affine transformations
170
    this._textBounds = textInstance.dimensions;
×
171
    this._preDraw(ex, x, y);
×
172

173
    textInstance.render(ex, x, y, maxWidth);
×
174

175
    this._postDraw(ex);
×
176
  }
177
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc