Fonts: a new asset type, picked from the server or dropped in, worn by text objects (#74)
game-maker / suite (push) Successful in 9m10s
game-maker / suite (push) Successful in 9m10s
A font asset holds a file and nothing else, the twin of a texture: the server keeps it
content-addressed under data/fonts/{sha256}.{ext}, with the format read from the file's own
four-byte signature (finfo reports fonts inconsistently across libmagic versions). A text
wears one through its ordinary Font property, so overrides, reset, undo and the Properties
node come for free; no font means the editor's own typeface.
The picker lists everything the server holds and renders each row in the face it offers.
The family name is gm-font-<hash16>, never the name inside the file, so two "Regular" cannot
collide. WorldViewport.syncText is the single place that draws it, which covers scene, game
preview and play at once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
28c0789b19
commit
86b5363a01
35 files changed
+1451
-85
No files matched your search
+12
-1
@@ -100,6 +100,16 @@ export interface ShaderAsset extends AssetBase {
|
||||
graph: Graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` names the typeface in `data/fonts/` the way a texture names its image: content-addressed
|
||||
* `{sha256}.{ext}`, empty when the upload failed. A text object wears it through its `font`
|
||||
* property; the family the browser knows it under is derived from the file name, see `model/font`.
|
||||
*/
|
||||
export interface FontAsset extends AssetBase {
|
||||
type: 'font';
|
||||
file: string;
|
||||
}
|
||||
|
||||
export type Asset =
|
||||
| SpriteAsset
|
||||
| CameraAsset
|
||||
@@ -111,7 +121,8 @@ export type Asset =
|
||||
| TextureAsset
|
||||
| AnimationAsset
|
||||
| StateMachineAsset
|
||||
| ShaderAsset;
|
||||
| ShaderAsset
|
||||
| FontAsset;
|
||||
|
||||
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
|
||||
export type GraphAsset = BehaviorAsset | StateMachineAsset | ShaderAsset;
|
||||
|
||||
@@ -10,6 +10,7 @@ export const ASSET_TYPES = [
|
||||
'animation',
|
||||
'statemachine',
|
||||
'shader',
|
||||
'font',
|
||||
] as const;
|
||||
|
||||
export type AssetType = (typeof ASSET_TYPES)[number];
|
||||
@@ -26,15 +27,17 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
animation: 'Animation',
|
||||
statemachine: 'State machine',
|
||||
shader: 'Shader',
|
||||
font: 'Font',
|
||||
};
|
||||
|
||||
/**
|
||||
* Textures and animations are not placeable: they are pictures a sprite wears through `texture`,
|
||||
* as a shader is worn through `shader`. A state machine is not either: a behavior refers to it by id.
|
||||
* as a shader is worn through `shader` and a font through `font`. A state machine is not either:
|
||||
* a behavior refers to it by id.
|
||||
*/
|
||||
export type PlaceableAssetType = Exclude<
|
||||
AssetType,
|
||||
'scene' | 'texture' | 'animation' | 'statemachine' | 'shader'
|
||||
'scene' | 'texture' | 'animation' | 'statemachine' | 'shader' | 'font'
|
||||
>;
|
||||
|
||||
const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
@@ -43,6 +46,7 @@ const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
'animation',
|
||||
'statemachine',
|
||||
'shader',
|
||||
'font',
|
||||
]);
|
||||
|
||||
export function isPlaceableType(type: AssetType): type is PlaceableAssetType {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { FontAsset } from './Asset';
|
||||
import { findAsset, type Project } from './Project';
|
||||
|
||||
/** Mirrors `FontStore::isValidName` on the server, where the name is derived from the bytes. */
|
||||
const FILE_PATTERN = /^[a-f0-9]{64}\.(ttf|otf|woff|woff2)$/;
|
||||
|
||||
export function isFontFile(name: string): boolean {
|
||||
return FILE_PATTERN.test(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* The CSS family a file is registered under. Derived from the content-addressed name, so the same
|
||||
* bytes are always one family and two uploads of one typeface cannot fight over a name – the name
|
||||
* inside the font is never used for this, only for the asset's label.
|
||||
*/
|
||||
export function fontFamilyOf(file: string): string {
|
||||
return `gm-font-${file.slice(0, 16)}`;
|
||||
}
|
||||
|
||||
/** The file a `font` property points at, empty when the id is unset or no longer resolves. */
|
||||
export function fontFileOf(project: Project, id: string): string {
|
||||
if (id === '') return '';
|
||||
const asset = findAsset(project, id);
|
||||
return asset?.type === 'font' ? asset.file : '';
|
||||
}
|
||||
|
||||
export function isFontAsset(asset: { type: string }): asset is FontAsset {
|
||||
return asset.type === 'font';
|
||||
}
|
||||
Reference in new issue
Block a user