Resources (§18.3, §18.4, §18.5)
Day apps bundle three kinds of resource, all looked up by name, all routed through each platform’s native resource machinery so they get the platform’s optimizations and load paths for free. Day never rewrites your pixels or bytes itself. It hands the raw files to the native build system, which optionally optimizes them (actool re-encodes/dedupes, aapt2 crunches, …). Data is stored uncompressed wherever the platform allows, so the runtime can return a zero-copy view.
| Project dir | Kind | API (typed, §18.5) | Native store |
|---|---|---|---|
resource/images/ | processed images | image(res::images::logo) | SwiftPM .process → Assets.car (iOS) · bundle file (macOS) · res/drawable → R (Android) · GResource (GTK) · .qrc (Qt) · MRT / loose (XAML) · rawfile (ArkUI) |
resource/assets/ | arbitrary data | resource(res::assets::stations_json) | bundle file + mmap (Apple) · AAssetManager (Android) · g_resources_lookup_data (GTK) · QResource (Qt) · loose file (XAML) · rawfile fd (ArkUI) |
resource/fonts/ | custom fonts | Font::custom(res::fonts::family, pt) | CoreText registration (Apple) · res/font → R.font (Android) · fontconfig/CoreText (GTK) · QFontDatabase (Qt) · XAML ms-appx:///fonts/<file>#family (XAML) · rawfile + ArkTS registerFont (ArkUI) |
Referencing resources: typed constants (§18.5)
You don’t reference bundled resources by bare string. An app’s build.rs (one line, wired into the
day new scaffold) generates a typed constant for every file under resource/:
// build.rs
fn main() { day_build::generate_resources().expect("day-build: resource codegen"); }
// src/lib.rs — surface the generated constants once
pub mod res { include!(concat!(env!("OUT_DIR"), "/day_resources.rs")); }
image(res::images::logo) // ImageName ← resource/images/logo.png
resource(res::assets::stations_json) // AssetName ← resource/assets/stations.json
Font::custom(res::fonts::pacifico, 24.0) // FontFamily ← resource/fonts/*.ttf (family "Pacifico")
Referencing a resource that isn’t bundled is a compile error, the available names autocomplete,
and dropping a file into resource/ makes its constant appear on the next build (cargo:rerun-if-changed).
Symbols are lowercase [a-z0-9_]: the file stem for images, the full file name (sanitized, e.g.
numbers.bin → numbers_bin) for assets, and the parsed font family ("Special Elite" →
special_elite) for fonts.
For a name known only at runtime, opt in explicitly; this bypasses the presence guarantee:
image(ImageName::dynamic(user_choice))
resource(AssetName::dynamic(format!("page-{n}.json")))
day-build also fails the build with a fix hint if an image stem isn’t portable across toolkits
(e.g. Logo.png resolves verbatim on Apple/GTK/Qt but as logo on Android/ArkUI; rename to
logo.png), or if two files collide on one symbol. The same crate is the single source of the
name→identifier rule the CLI stagers use, so a constant’s string is exactly the name staged natively.
Images: image(res::images::name)
Drop resource/images/logo.png (optionally logo@2x.png, logo@3x.png) in the project. day build stages
each image into the target’s native image pipeline; image(res::images::logo) (the piece) then
resolves the name through the native by-name API. Nothing about the piece API changes, only how
the backend resolves the name.
- iOS (UIKit): a generated
Media.xcassetsis placed in theDayResourcesSwiftPM package withresources: [.process(...)]. xcodebuild runsactool→ an optimized, deduplicatedAssets.carinDayResources_DayResources.bundle; the backend loads viaUIImage(named:in:compatibleWith:). - macOS (AppKit): the app is a plain cargo binary (no xcodebuild/actool), so the image is a file
in the
.appbundle, loaded withNSImage(contentsOfFile:). (Optimization is whatever the source already is; there is no actool step off the Xcode build.) - Android: staged into
res/drawable*/(density buckets from@Nxvariants); aapt2 crunches and assigns anR.drawableid. Runtime resolves the name withResources.getIdentifier(name, "drawable", pkg)(cached) →getDrawable. - GTK / Qt: compiled into the binary as a GResource /
.qrcand loaded by a stable virtual path (/dev/<appid>/logo.png,:/logo.png). - XAML: staged as
logo.scale-{100,200,400}.png; MRT auto-selects by DPI when packaged, a WIC/DPI resolver picks the file when unpackaged. - ArkUI: staged into
resources/rawfile/day/; the native NodeAPI image node is set toresource://RAWFILE/day/logo.png(rawfile is the only store the OpenHarmony NDK can reach).
Data: resource(res::assets::name)
day::resource(res::assets::stations_json) returns a Resource with efficient random read-only
access, backed directly by the native store (zero-copy where the platform exposes a stable pointer):
let res = day::resource(res::assets::stations_json).expect("bundled");
let all: &[u8] = res.as_slice(); // zero-copy view
let n = res.len(); // byte length
let mut hdr = [0u8; 16];
res.read_at(0, &mut hdr); // random access, no allocation
let owned: Vec<u8> = res.to_vec(); // copy out if you need ownership
Backing per platform: Apple = mmap of the bundle file (the “plain file handle”); Android = NDK
AAssetManager (AAsset_getBuffer on an uncompressed asset, zero copy); GTK =
g_resources_lookup_data; Qt = QResource::data; ArkUI = OH_ResourceManager_GetRawFileDescriptor
- mmap; desktop dev / host tests = mmap of
DAY_ASSET_ROOT/<name>. The active backend registers its opener once viaday_core::set_resource_opener; absent that, the default mmap-file opener is used (which is exactly the Apple path). Seecrates/day-core/src/resource.rs.
Fonts: Font::custom(res::fonts::family, pt) (§18.4)
resource/fonts/*.{ttf,otf} are referenced by the family name embedded in the file’s sfnt name
table, never by file name. The single invariant that makes the name resolve everywhere with no
side table: day build parses the name table (day_spec::fonts::parse_font_names, a ~100-line
bounds-checked sfnt reader shared by the CLI and the runtimes) and derives every staged name from
the family via font_ident (“Special Elite” → special_elite), so a runtime can re-derive it
from the requested name. The size scales with the platform accessibility text scale exactly like
Font::System (UIFontMetrics on iOS, sp on Android, text-scaling-factor on GTK, …).
Per platform:
- macOS (AppKit): files register with
CTFontManagerRegisterFontsForURL(process scope) inrun();NSFont(name:size:)then resolves family/full/PostScript names. Dev launch readsDAY_FONT_ROOT(set byday launchto the project’sresource/fonts/); packed apps readContents/Resources/fonts(copied byday pack). - iOS (UIKit): fonts ride the DayPieces SwiftPM bundle as a
.copy("fonts")resource (DayPieces_DayPieces.bundle/fonts/…):.copy, not.process, so the bytes land verbatim.day buildALSO syncs aUIAppFontsarray intoplatform/ios/Runner/Info.plist(managed key, rewritten each build), and day-uikit registers the bundle dir with CoreText at launch; the registration covers dev loops and any path iOS declines to load from the plist. - Android: staged as
res/font/<ident>.<ext>; aapt2 assignsR.font.<ident>.DayBridge.setLabelFonttakes the family string, re-derives<ident>with the same sanitization, resolves viaResources.getIdentifier(…, "font", pkg)→getFont(API 26+; older devices log and fall back), caches the Typeface, and buildsTypeface.create(base, weight, italic)on API 28+. - GTK:
FcConfigAppFontAddFileon Linux; on macOS BOTH CoreText and fontconfig (Homebrew Pango may sit on either fontmap);AddFontResourceExW(FR_PRIVATE)best-effort on Windows. The label carries a PangoAttrString::new_familyattribute. - Qt:
QFontDatabase::addApplicationFontper file at startup (shimday_qt_register_font); labels getQFont::setFamilyon top of the size/weight/italic font. - XAML: unpackaged Win32 XAML has no registration API and rejects
file:///absolute font locations (likeBitmapImage). The one location system XAML resolves isms-appx:///, mapped to the executable directory and its subtree, sorun()stages every bundled font into<exe>/fonts/(a no-op when packed;day packalready ships them there) and the shim setsFontFamily("ms-appx:///fonts/<file>#<family>"). The family→file mapping is resolved (and cached) throughday_spec::fonts::resolve_font_fileagainstDAY_FONT_ROOT/ the exe-relativefonts/dir. - ArkUI: staged into rawfile
day/fonts/plus aday/fonts.jsonmanifest ([{family, file}]); the platform/ohos scaffold’s EntryAbility feeds it to ArkTSfont.registerFont(building the rawfileResourceobject by hand;$rawfile()only takes literals) before the native UI loads, and day-arkui setsNODE_FONT_FAMILY.
Validation (crates/day-cli/src/resources/mod.rs::scan_fonts) is hard-error at build time: only
.ttf/.otf (Android’s res/font accepts nothing else; the strictest platform sets the rule),
a parseable name table, and no two families colliding on the same sanitized ident. At runtime an
unknown family logs day: unknown font family … and falls back to the system font; a missing
font is a visual bug, never a crash. Weight overrides on custom fonts map to synthesized bold
(>= Semibold) where the family has no such face.
Scaling: image(res::images::logo).content_mode(…) / .aspect_ratio(…)
Images scale with ContentMode::Fit by default (preserve aspect, letterbox, never stretch). Tune
with .content_mode(ContentMode::Fill) (preserve aspect, crop), .stretch(), or the shorthands
.fit()/.fill(); constrain the frame to a ratio with .aspect_ratio(16.0/9.0). Each maps to the
native scaler: NSImageView imageScaling, UIImageView contentMode, GtkPicture content-fit, a
Qt aspect-painting label, Android ImageView.ScaleType, XAML Image.Stretch, ArkUI
NODE_IMAGE_OBJECT_FIT.
Build-time staging
crates/day-cli/src/resources/ scans images/, assets/, and fonts/ and, before the platform
build, dispatches to a per-toolkit stager. GTK compiles a .gresource blob (glib-compile-resources) and
Qt a .rcc blob (rcc -binary -no-compress); both are registered at startup and loaded natively
(g_resources_lookup_data / gtk_picture_new_for_resource, QResource::data / QPixmap(":/…")).
Android copies images into res/drawable*/ and iOS into a .process Media.xcassets; ArkUI copies
into rawfile/. Day performs no image/SVG processing of its own; the native build system optionally
optimizes.
Notes / limits
- ArkUI uses
rawfile(native-reachable, uncompressed) with no per-density auto-selection yet; amedia/+ ArkTS-bridge path for density is a future enhancement. - macOS AppKit does not run actool (cargo build), so its images are unoptimized bundle files.
- XAML is built unpackaged, so images/data load as loose files via WIC/the file opener (the
recommended unpackaged path); MRT
.pri(ms-appx:///) applies only to MSIX-packaged apps. - XAML unpackaged uses loose scale-suffixed files + a DPI resolver (MRT/
.prineeds MSIX).