From 5dcff5db523b101d1c7ebfa52fda03e33e2fda62 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Tue, 14 May 2024 22:47:11 -0500 Subject: [PATCH] fix: [#3060] SpriteFont measureText with scale (#3062) Closes #3060 ## Changes: - Adds sandbox test with repro - Updates spritefont to include scale --- CHANGELOG.md | 32 ++++++++++-- sandbox/tests/spritefont-measuring/font.png | Bin 0 -> 898 bytes sandbox/tests/spritefont-measuring/index.html | 13 +++++ sandbox/tests/spritefont-measuring/index.ts | 46 ++++++++++++++++++ sandbox/tests/spritefont-rendering/font.png | Bin 0 -> 898 bytes sandbox/tests/spritefont-rendering/index.html | 13 +++++ sandbox/tests/spritefont-rendering/index.ts | 37 ++++++++++++++ src/engine/Graphics/SpriteFont.ts | 2 +- src/spec/TextSpec.ts | 39 +++++++++++++++ .../GraphicsTextSpec/spritefont-scaled.png | Bin 0 -> 1685 bytes 10 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 sandbox/tests/spritefont-measuring/font.png create mode 100644 sandbox/tests/spritefont-measuring/index.html create mode 100644 sandbox/tests/spritefont-measuring/index.ts create mode 100644 sandbox/tests/spritefont-rendering/font.png create mode 100644 sandbox/tests/spritefont-rendering/index.html create mode 100644 sandbox/tests/spritefont-rendering/index.ts create mode 100644 src/spec/images/GraphicsTextSpec/spritefont-scaled.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eff4a35e..05fdd443b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Breaking Changes -- `ex.Action` now requires a unique `id` property +- ### Deprecated @@ -18,14 +18,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - `actor.oldGlobalPos` returns the globalPosition from the previous frame -- Built in actions now have a unique `id` property - create development builds of excalibur that bundlers can use in dev mode - show warning in development when Entity hasn't been added to a scene after a few seconds ### Fixed -- Fixed animation glitch caused by uninitialized state in `ImageRenderer` -- Fixed issue where `ex.Loader.suppressPlayButton = true` did not work. Only using the `ex.Engine({suppressPlayButton: true})` worked +- Fixed issue where `ex.SpriteFont` did not respect scale when measuring text ### Updates @@ -33,12 +31,36 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- `ex.Vector.toAngle()` now returns angles from [0 - 2 PI) +## [v0.29.3] + +### Breaking Changes + +- `ex.Action` now requires a unique `id` property + +### Deprecated + +### Added + +- Built in actions now have a unique `id` property + +### Fixed + +- Fixed animation glitch caused by uninitialized state in `ImageRenderer` +- Fixed issue where `ex.Loader.suppressPlayButton = true` did not work. Only using the `ex.Engine({suppressPlayButton: true})` worked + +### Updates + +- + +### Changed + +- `ex.Vector.toAngle()` now returns angles from `[0 - 2 PI)` + ## [v0.29.2] ### Breaking Changes diff --git a/sandbox/tests/spritefont-measuring/font.png b/sandbox/tests/spritefont-measuring/font.png new file mode 100644 index 0000000000000000000000000000000000000000..46fded31d53e65e69a35b04fecc3cc7d859edf2e GIT binary patch literal 898 zcmV-|1AY97P)VGd000McNliru=miW9GbirIl;HpX0}n|= zK~#9!?Obh+#UKa-;}Q1$S7m=}`r`T!P^hh~O*U!XT0a;E2ed`_y9ne9^KnPSmqkPX z0La{r+{1~hgsVBVI#qWif$`!BvZKmUb6m$&J6gNQmL%@~$zmFpdB|+XmWf=*_zI#j z7&{`}Sod?L=8J_hAWmFq(tgeGRu{&aZgt^k;XKZeRz@^WX@RCUYxFlwL`46@)e2G#At}7ZuTdWlRDJzmjhd5M%9|;47<4HT zWwS+iJcTopwVCc8O~`1@G;W<)5*bYaWTPXPq?%*3ncu6SLAIhgaZ3;q2N{P_GE9oT z<)dGGxE<;HyxEX>%?B9G8v>9Sqh+Aj=QK9^ybSa4VxzDW{V#MdNg72h(L9lfjdfiHCTk(R)s7Q4AeuVOEDO&?=2?;ENOlI@+KgK6@RWGw>|KESAoG&E z*=P;`MHW`gWAVp3Qf7ddq(WS7ZzH3-6e$pQuQT*s&J5WxW*5NUw6wx*WMYuXQx(8n zaw+jUXH9Qb$A`&M+sX<6#YL0rTa4}jZ^HMHsu=`?`l8{GaF6EZ0Q?2dJD~v@Lf35h zQXP^~J!+N;+QEyJ&S;Xxz~-n=-M-p60R3s#eDqD+_;@B8;~9LEYHpqc92Q9PB25VA z%+3Ma6`6B0nnB1|79{K;7lwMX6%@c7BF*@CX6FFhr5qufq8UO$Q07*qoM6N<$f*f + + + + + + Sprite Font Rendering + + + + + + diff --git a/sandbox/tests/spritefont-measuring/index.ts b/sandbox/tests/spritefont-measuring/index.ts new file mode 100644 index 000000000..70e000c05 --- /dev/null +++ b/sandbox/tests/spritefont-measuring/index.ts @@ -0,0 +1,46 @@ +class TestFontMeasuringScene extends ex.Scene { + font1: ex.SpriteFont; + font2: ex.SpriteFont; + async onInitialize(game: ex.Engine) { + super.onInitialize(game); + let loader = new ex.DefaultLoader(); + let fontImage = new ex.ImageSource('font.png'); + loader.addResource(fontImage); + await game.start(loader); + + let fontSpriteSheet = ex.SpriteSheet.fromImageSource({ + image: fontImage, + grid: { columns: 1, rows: 45, spriteWidth: 10, spriteHeight: 10 } + }); + + this.font1 = this.createFont(fontSpriteSheet); + this.font2 = this.createFont(fontSpriteSheet); + this.font2.scale = ex.vec(2, 2); + } + + createFont(spriteSheet: ex.SpriteSheet) { + return new ex.SpriteFont({ + alphabet: '01234567890.- ©][ABCDEFGHIJKLMNOPQRSTUVWXYZ|/', + caseInsensitive: true, + spriteSheet + }); + } + onActivate(ctx: ex.SceneActivationContext) { + const text = 'brown fox jumps over the lazy dog'; + let m1 = this.font1.measureText(text); + console.log('font 1', m1.dimensions); //330,10, OK + let m2 = this.font2.measureText(text); + console.log('font 2', m2.dimensions); //330,10, should be 660,20 + } +} + +var engineSpriteFont = new ex.Engine({ + width: 600, + height: 600, + pixelArt: true, + scenes: { + start: TestFontMeasuringScene + } +}); + +engineSpriteFont.start('start'); diff --git a/sandbox/tests/spritefont-rendering/font.png b/sandbox/tests/spritefont-rendering/font.png new file mode 100644 index 0000000000000000000000000000000000000000..46fded31d53e65e69a35b04fecc3cc7d859edf2e GIT binary patch literal 898 zcmV-|1AY97P)VGd000McNliru=miW9GbirIl;HpX0}n|= zK~#9!?Obh+#UKa-;}Q1$S7m=}`r`T!P^hh~O*U!XT0a;E2ed`_y9ne9^KnPSmqkPX z0La{r+{1~hgsVBVI#qWif$`!BvZKmUb6m$&J6gNQmL%@~$zmFpdB|+XmWf=*_zI#j z7&{`}Sod?L=8J_hAWmFq(tgeGRu{&aZgt^k;XKZeRz@^WX@RCUYxFlwL`46@)e2G#At}7ZuTdWlRDJzmjhd5M%9|;47<4HT zWwS+iJcTopwVCc8O~`1@G;W<)5*bYaWTPXPq?%*3ncu6SLAIhgaZ3;q2N{P_GE9oT z<)dGGxE<;HyxEX>%?B9G8v>9Sqh+Aj=QK9^ybSa4VxzDW{V#MdNg72h(L9lfjdfiHCTk(R)s7Q4AeuVOEDO&?=2?;ENOlI@+KgK6@RWGw>|KESAoG&E z*=P;`MHW`gWAVp3Qf7ddq(WS7ZzH3-6e$pQuQT*s&J5WxW*5NUw6wx*WMYuXQx(8n zaw+jUXH9Qb$A`&M+sX<6#YL0rTa4}jZ^HMHsu=`?`l8{GaF6EZ0Q?2dJD~v@Lf35h zQXP^~J!+N;+QEyJ&S;Xxz~-n=-M-p60R3s#eDqD+_;@B8;~9LEYHpqc92Q9PB25VA z%+3Ma6`6B0nnB1|79{K;7lwMX6%@c7BF*@CX6FFhr5qufq8UO$Q07*qoM6N<$f*f + + + + + + Sprite Font Rendering + + + + + + diff --git a/sandbox/tests/spritefont-rendering/index.ts b/sandbox/tests/spritefont-rendering/index.ts new file mode 100644 index 000000000..4f4e7dc05 --- /dev/null +++ b/sandbox/tests/spritefont-rendering/index.ts @@ -0,0 +1,37 @@ +class TestFontScene extends ex.Scene { + async onInitialize(game: ex.Engine) { + super.onInitialize(game); + let loader = new ex.DefaultLoader(); + let fontImage = new ex.ImageSource('./font.png'); + loader.addResource(fontImage); + await game.start(loader); + let fontSpriteSheet = ex.SpriteSheet.fromImageSource({ + image: fontImage, + grid: { columns: 1, rows: 45, spriteWidth: 10, spriteHeight: 10 } + }); + let font = new ex.SpriteFont({ + alphabet: '01234567890.- ©][ABCDEFGHIJKLMNOPQRSTUVWXYZ|/', + caseInsensitive: true, + spriteSheet: fontSpriteSheet, + scale: ex.vec(4, 4) + }); + let lbl = new ex.Label({ + pos: ex.vec(10, 10), + anchor: ex.vec(0, 0), + text: '-brown fox jumps \nover the lazy dog', + spriteFont: font + }); + this.add(lbl); + } +} + +var engineSpriteFont = new ex.Engine({ + width: 600, + height: 600, + pixelArt: true, + scenes: { + start: TestFontScene + } +}); + +engineSpriteFont.start('start'); diff --git a/src/engine/Graphics/SpriteFont.ts b/src/engine/Graphics/SpriteFont.ts index 1189a7cc6..bd99d6a8b 100644 --- a/src/engine/Graphics/SpriteFont.ts +++ b/src/engine/Graphics/SpriteFont.ts @@ -99,7 +99,7 @@ export class SpriteFont extends Graphic implements FontRenderer { width += sprite.width + this.spacing; height = Math.max(height, sprite.height); } - return BoundingBox.fromDimension(width, height * lines.length, Vector.Zero); + return BoundingBox.fromDimension(width * this.scale.x, height * lines.length * this.scale.y, Vector.Zero); } protected _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number, maxWidth?: number): void { diff --git a/src/spec/TextSpec.ts b/src/spec/TextSpec.ts index 3b28f07df..4389e21d6 100644 --- a/src/spec/TextSpec.ts +++ b/src/spec/TextSpec.ts @@ -921,6 +921,45 @@ describe('A Text Graphic', () => { await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsTextSpec/spritefont-text.png'); }); + it('can scale a spritefont', async () => { + const spriteFontImage = new ex.ImageSource('src/spec/images/GraphicsTextSpec/spritefont.png'); + + await spriteFontImage.load(); + + const spriteFontSheet = ex.SpriteSheet.fromImageSource({ + image: spriteFontImage, + grid: { + rows: 3, + columns: 16, + spriteWidth: 16, + spriteHeight: 16 + } + }); + + const spriteFont = new ex.SpriteFont({ + alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ', + caseInsensitive: true, + spacing: -5, + spriteSheet: spriteFontSheet, + scale: ex.vec(3, 3) + }); + + const sut = new ex.Text({ + text: 'Some Sprite Text!?', + font: spriteFont + }); + + const canvasElement = document.createElement('canvas'); + canvasElement.width = 200; + canvasElement.height = 100; + const ctx = new ex.ExcaliburGraphicsContext2DCanvas({ canvasElement }); + + ctx.clear(); + sut.draw(ctx, 0, 50); + expect(spriteFont.measureText('some test')).toEqual(ex.BoundingBox.fromDimension((16 - 5) * 9 * 3, 16 * 3, ex.vec(0, 0))); + await expectAsync(canvasElement).toEqualImage('src/spec/images/GraphicsTextSpec/spritefont-scaled.png'); + }); + it('can have a custom lineHeight', async () => { const spriteFontImage = new ex.ImageSource('src/spec/images/GraphicsTextSpec/spritefont.png'); diff --git a/src/spec/images/GraphicsTextSpec/spritefont-scaled.png b/src/spec/images/GraphicsTextSpec/spritefont-scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..9a24b23fc419e54c41af62a9be472605b059bc88 GIT binary patch literal 1685 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgAGU?ZmZ`8QjEnx?oJHr&dI!FU|{q2ba4!+ zhZ?~Pt+u}_IeUG+{@eQhhvQr81sV>dbH*G}C}Cr{#bUs5m$C7` zLW5%lKSzQ<6$6tLqn|*pbf70k!UPqk;AL(M%^7z(yqp-CRRm`lnKDVZJa!8aVvo(=EGs{+nzOGq(yy*Q@l?|5 zTW>e*s9K$K_lD`(?K+>H1-zF$x%}(SX|~E6OaD#06`iB|GrHvF6)FA6VS8hFivIjr zQBzSDa?jq<-k4-$74y-)o4=wCTO&EaMqwJoytzjxOBc)R@5 zzw7d+AH4#)x@>h$Y`pAzzx(^N)brz}UAB*#GWT!(sc-+TKm9bF-|LCS_h$j!SGDI) z6Id%1ZRyT)`_#gvtxGef|9r1w`s-C=>Q9mEJ%1BkGTv19D!47@*VFg?ldt=qcUwMp zZ*}VPciXiu*sZm%D~b@iUVgr=KIiV9U?umR=I!TuRWH`eI(u`)#;Kn^+~cPS zk^9Y>b&hX%oGyQS!&Lbbjf?LDC3s)(SaV(B>yFU=#e1LqkK20Y&Yx*__Q>ei?lfzj z)0=QQ@XgoDQ=^pKcZzo`-B?{)eSLmt|CFy5U5+}r`SFk6vU~flJFD=`=&kl%2eVXx zYFGL2Z$>N2<@4ulUfci0d9@hOpC7MONhL5!RCb9n3UzLkI2-i)h42!&w>m#vwrqG~ zUv;))-W1*-dH=P`8O0PPE{~X~)_-K4c=MITcQbT#e)q>2FH%o!{*pE6goHrmCy$)< zx7_C|+-vK}c=&hC#n-z2+`4~uODy?nvS4b%)23rg2A7^pjOccJ<-Kdyy=(ukS64}I zz3%$3a*s^TSI_nH-Xu-9cDW(#)BioYOz{)>O_*Ow($2HpD0(%B*1`PMXQ!r`Nv zeOxZTW9y7LwI#0qyPfMy#dg6(74_Cziax}NT$^UuKfO))m!sT9rRhd%uRq@FADDI` zF|D~mpDVjL;@G5{=N1V5RggYn^eTd(YQJuD^|xITo1WyXyc-mLBxah-o~5&dHil%@7?VA9Fuo_(_?!XD)QSQ!Hh{F#mx0Yx)zYu gvh+wJpI^^>d*_M%_gztGz