The Feuerwerk wordmark is set with -0.03em tracking. Text in the engine had no letter spacing, so the wordmark came out about 3 % wider than the logo. - Text has a new property Letter spacing, default 0, in the same unit as the font size. It goes between each two characters, so ten characters get it nine times. Negative values pull them together. - TextPicture hands the value to Pixi's text style. Both rasters, the smooth and the sharp one, use it, and it is part of the raster key. - The estimated text width in bounds.ts adds the spacing too, so selection, picking and "Add collider" fit the drawn text. - Like every number property it can be animated and set from a graph. That is not tried in the browser. Tests: - New unit test for the footprint with spacing. 102 tests in the three text files green. - e2e 30f-ii measures the width of "Handgloves" on screen: 237 without spacing, 282 at +5 (expected +45) and 210 at -3 (expected -27). e2e 30 on melektaus-atx: ALL PASSED, 25 checks. - Gate on melektaus-atx: CHECK EXIT 0, 307 files, 4757 tests. - No counter-check run for 30f-ii. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
3.0 KiB
TypeScript
62 lines
3.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
|
||
import { AddColliderCommand } from '../../src/core/commands/colliderCommands';
|
||
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
|
||
import { footprintOf, textSize } from '../../src/core/properties/bounds';
|
||
import { makeSpriteScene, place, startWorld } from '../helpers';
|
||
|
||
describe('text objects', () => {
|
||
it('measures a text footprint from its content and font size', () => {
|
||
expect(textSize({ text: 'ab', fontSize: 100 }).w).toBeCloseTo(110, 5);
|
||
expect(textSize({ text: 'ab', fontSize: 100 }).h).toBeCloseTo(125, 5);
|
||
expect(footprintOf('text', { text: 'abcd', fontSize: 100 }).w).toBeCloseTo(220, 5);
|
||
});
|
||
|
||
it('widens or narrows the footprint by the letter spacing between its characters', () => {
|
||
expect(textSize({ text: 'abcd\nab', fontSize: 100, letterSpacing: 10 }).w).toBeCloseTo(250, 5);
|
||
expect(textSize({ text: 'abcd', fontSize: 100, letterSpacing: -6 }).w).toBeCloseTo(202, 5);
|
||
expect(textSize({ text: 'a', fontSize: 100, letterSpacing: 10 }).w).toBeCloseTo(55, 5);
|
||
});
|
||
});
|
||
|
||
describe('the mouse', () => {
|
||
it('hit-tests an object through its colliders, else through its sprites and texts', () => {
|
||
const f = makeSpriteScene('Button');
|
||
const { stack, sceneId } = f;
|
||
const button = place(f, f.spriteId, { x: 0, y: 0 });
|
||
const label = new CreateAssetCommand('text', 'Label');
|
||
stack.execute(label);
|
||
place(f, label.asset.id, { x: 0, y: 200 }, button);
|
||
|
||
const { world } = startWorld(f);
|
||
const entity = world.findByObjectId(button)!;
|
||
expect(world.hitTest(entity, 10, 10)).toBe(true); // inside the 100×100 sprite
|
||
expect(world.hitTest(entity, 10, 200)).toBe(true); // on the label parented below it
|
||
expect(world.hitTest(entity, 400, 400)).toBe(false);
|
||
|
||
// A collider takes over the hit test for the whole subtree.
|
||
const collider = new AddColliderCommand(sceneId, button);
|
||
stack.execute(collider);
|
||
stack.execute(new SetOverrideCommand(sceneId, collider.objectId, 'width', 20));
|
||
world.syncFromModel();
|
||
expect(world.hitTest(entity, 5, 5)).toBe(true);
|
||
expect(world.hitTest(entity, 40, 0)).toBe(false); // outside the 20-wide collider
|
||
expect(world.hitTest(entity, 10, 200)).toBe(false); // the label no longer answers
|
||
|
||
// Switched off, nothing of it can be hit.
|
||
entity.props.enabled = false;
|
||
expect(world.hitTest(entity, 5, 5)).toBe(false);
|
||
});
|
||
|
||
it('reports pointer edges for exactly one tick', () => {
|
||
const { world } = startWorld(makeSpriteScene());
|
||
world.movePointer(12, 34, true);
|
||
world.setPointerDown(true);
|
||
expect(world.pointer).toMatchObject({ x: 12, y: 34, inside: true, down: true, pressed: true });
|
||
world.tick(0.1);
|
||
expect(world.pointer).toMatchObject({ down: true, pressed: false, released: false });
|
||
world.setPointerDown(false);
|
||
expect(world.pointer).toMatchObject({ down: false, released: true });
|
||
});
|
||
});
|