mbed/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # mbed
4//!
5//! Embed and transform assets into your Rust crate.
6//!
7//! [`mbed`](crate) provides macros for turning files and generated outputs into [`Artifact`]
8//! values. Artifacts carry their bytes, MIME type, and [`Id`], and can be grouped
9//! into a [`Manifest`] for lookup by identifier.
10//!
11//! ## Basic usage
12//!
13//! Requires the `macros` feature.
14//!
15//! ```rust
16//! # #[cfg(any())]
17//! # fn __no_run() {
18//! pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
19//! pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
20//!
21//! let id = LOGO.id();
22//! assert_eq!(LOGO.mime(), "image/svg+xml");
23//! # }
24//! ```
25//!
26//! ## Collections and manifests
27//!
28//! `collect!` groups artifacts inside a module. `manifest!` can then reference
29//! individual artifacts or whole collections with `::*`.
30//!
31//! Requires the `macros` feature.
32//!
33//! ```rust
34//! # #[cfg(any())]
35//! # fn __no_run() {
36//! pub mod pages {
37//! pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
38//! pub const ABOUT: Artifact<str> = mbed::include_str!["../pages/about.html"];
39//!
40//! mbed::collect![INDEX, ABOUT];
41//! }
42//!
43//! pub mod images {
44//! pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
45//!
46//! mbed::collect![LOGO];
47//! }
48//!
49//! pub const ASSETS: Manifest = mbed::manifest![pages::*, images::*];
50//!
51//! let artifact = ASSETS.get(pages::INDEX.id()).unwrap();
52//! assert_eq!(artifact.mime(), pages::INDEX.mime());
53//! # }
54//! ```
55//!
56//! ## Images
57//!
58//! Requires the `image` feature.
59//!
60//! ```rust
61//! # #[cfg(any())]
62//! # fn __no_run() {
63//! pub const LOGO: Artifact<Image> = mbed::image::include! {
64//! resize: Scale(0.5),
65//! path: "../assets/logo.png",
66//! format: WebP,
67//! };
68//!
69//! assert_eq!(LOGO.format(), mbed::image::Format::WebP);
70//! let bytes = LOGO.as_bytes();
71//! # }
72//! ```
73//!
74//! ## JavaScript bundles
75//!
76//! Requires the `bundle` feature.
77//!
78//! ```rust
79//! # #[cfg(any())]
80//! # fn __no_run() {
81//! pub const APP: Artifact<Bundle> = mbed::js::bundle!["../frontend/app.js"];
82//!
83//! let code = APP.code();
84//! let sourcemap = APP.sourcemap();
85//! # }
86//! ```
87//!
88//! ## Tailwind CSS
89//!
90//! Requires the `tailwindcss` feature.
91//!
92//! ```rust
93//! # #[cfg(any())]
94//! # fn __no_run() {
95//! pub const STYLES: Artifact<str> = mbed::css::tailwindcss!("../tailwind.css");
96//! # }
97//! ```
98//!
99//! ## Feature flags
100//!
101//! - `macros` enables [`include_bytes!`], [`include_str!`], [`collect!`], and
102//! [`manifest!`].
103//! - `image` enables [`image::include!`] and image metadata support.
104//! - `bundle` enables [`js::bundle!`].
105//! - `tailwindcss` enables [`css::tailwindcss!`].
106//! - `serde` enables serialization support for public data types.
107//! - `cargo-progress` shows bundling progress in Cargo build output. This feature
108//! is only supported on Linux.
109
110use std::{iter::FusedIterator, ops::Deref, str::FromStr};
111
112/// A collection of embedded artifacts indexed by [`Id`].
113///
114/// Manifests are usually generated with [`manifest!`]. They store artifacts as
115/// byte artifacts, regardless of whether the original source artifact was bytes,
116/// text, an image, or a JavaScript bundle.
117///
118/// # Examples
119///
120/// Requires the `macros` feature.
121///
122/// ```rust
123/// # #[cfg(any())]
124/// # fn __no_run() {
125/// pub mod assets {
126/// pub const ROBOTS: Artifact<str> = mbed::include_str!["../public/robots.txt"];
127/// pub const FAVICON: Artifact<[u8]> = mbed::include_bytes!["../public/favicon.ico"];
128///
129/// mbed::collect![ROBOTS, FAVICON];
130/// }
131///
132/// pub const MANIFEST: Manifest = mbed::manifest![assets::*];
133///
134/// let robots = MANIFEST.get(assets::ROBOTS.id()).unwrap();
135/// assert_eq!(robots.as_bytes(), assets::ROBOTS.as_bytes());
136/// # }
137/// ```
138#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
139pub struct Manifest {
140 artifacts: &'static [Artifact<[u8]>],
141 buckets: &'static [Bucket],
142}
143
144impl std::fmt::Debug for Manifest {
145 #[inline]
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_tuple("Manifest").field(&self.artifacts).finish()
148 }
149}
150
151impl Manifest {
152 /// Returns the artifact with the given [`Id`].
153 ///
154 /// Returns `None` when the id is not present in this manifest.
155 #[inline]
156 pub const fn get(&self, id: Id) -> Option<Artifact> {
157 let bucket_index = id.bucket_index(self.buckets.len());
158
159 match self.buckets[bucket_index].find(id) {
160 Some(index) => Some(self.artifacts[index]),
161 None => None,
162 }
163 }
164
165 /// Returns all artifacts in this manifest.
166 ///
167 /// The order matches the order generated by [`manifest!`].
168 #[inline]
169 pub const fn artifacts(&self) -> &'static [Artifact] {
170 self.artifacts
171 }
172}
173
174impl IntoIterator for Manifest {
175 type Item = Artifact;
176
177 type IntoIter = Iter<[u8]>;
178
179 #[inline]
180 fn into_iter(self) -> Self::IntoIter {
181 Iter {
182 artifacts: self.artifacts(),
183 index: 0,
184 }
185 }
186}
187
188#[cfg(feature = "serde")]
189impl serde::Serialize for Manifest {
190 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: serde::Serializer,
193 {
194 <[_] as serde::Serialize>::serialize(self.artifacts(), serializer)
195 }
196}
197
198/// An iterator over embedded artifacts.
199#[derive(Debug, Clone)]
200pub struct Iter<T>
201where
202 T: ?Sized + 'static,
203{
204 artifacts: &'static [Artifact<T>],
205 index: usize,
206}
207
208impl<T> Iterator for Iter<T>
209where
210 T: ?Sized + 'static,
211{
212 type Item = Artifact<T>;
213
214 #[inline]
215 fn next(&mut self) -> Option<Self::Item> {
216 if self.index < self.artifacts.len() {
217 let artifact = self.artifacts[self.index];
218 self.index += 1;
219 Some(artifact)
220 } else {
221 None
222 }
223 }
224}
225
226impl<T> FusedIterator for Iter<T> where T: ?Sized + 'static {}
227
228/// An embedded asset and its metadata.
229///
230/// `Artifact` values are produced by the crate's embedding macros. The default
231/// artifact type is `Artifact<[u8]>`, but text artifacts use `Artifact<str>`,
232/// image artifacts use `Artifact<image::Image>`, and JavaScript bundles use
233/// `Artifact<js::Bundle>`.
234///
235/// Each artifact has:
236///
237/// - an [`Id`], generated from the artifact contents;
238/// - a MIME type;
239/// - a reference to the embedded value.
240///
241/// # Examples
242///
243/// Requires the `macros` feature.
244///
245/// ```rust
246/// # #[cfg(any())]
247/// # fn __no_run() {
248/// pub const CONFIG: Artifact<str> = mbed::include_str!["../config/default.toml"];
249///
250/// let id = CONFIG.id();
251/// let mime = CONFIG.mime();
252/// let text = CONFIG.as_str();
253/// # }
254/// ```
255#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize))]
257pub struct Artifact<T = [u8]>
258where
259 T: ?Sized + 'static,
260{
261 mime: &'static str,
262 id: Id,
263 value: &'static T,
264}
265
266impl<T> Clone for Artifact<T>
267where
268 T: ?Sized,
269{
270 #[inline]
271 fn clone(&self) -> Self {
272 *self
273 }
274}
275
276impl<T> Copy for Artifact<T> where T: ?Sized {}
277
278impl<T> Artifact<T>
279where
280 T: ?Sized,
281{
282 /// Returns this artifact's SHA-224 identifier.
283 #[inline]
284 pub const fn id(&self) -> Id {
285 self.id
286 }
287
288 /// Returns this artifact's MIME type.
289 #[inline]
290 pub const fn mime(&self) -> &'static str {
291 self.mime
292 }
293}
294
295impl Artifact<[u8]> {
296 /// Returns this artifact's embedded bytes.
297 #[inline]
298 pub const fn as_bytes(&self) -> &'static [u8] {
299 self.value
300 }
301
302 #[doc(hidden)]
303 #[inline]
304 pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
305 self
306 }
307}
308
309impl Artifact<str> {
310 /// Returns this artifact's UTF-8 contents as bytes.
311 #[inline]
312 pub const fn as_bytes(&self) -> &'static [u8] {
313 self.value.as_bytes()
314 }
315
316 /// Returns this artifact's UTF-8 contents.
317 #[inline]
318 pub const fn as_str(&self) -> &'static str {
319 self.value
320 }
321
322 /// Converts this text artifact into a byte artifact.
323 #[inline]
324 pub const fn into_bytes_artifact(self) -> Artifact<[u8]> {
325 Artifact {
326 id: self.id,
327 mime: self.mime,
328 value: self.value.as_bytes(),
329 }
330 }
331
332 #[doc(hidden)]
333 #[inline]
334 pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
335 self.into_bytes_artifact()
336 }
337}
338
339impl AsRef<[u8]> for Artifact<[u8]> {
340 #[inline]
341 fn as_ref(&self) -> &[u8] {
342 self.as_bytes()
343 }
344}
345
346impl AsRef<[u8]> for Artifact<str> {
347 #[inline]
348 fn as_ref(&self) -> &[u8] {
349 self.as_bytes()
350 }
351}
352
353impl AsRef<str> for Artifact<str> {
354 #[inline]
355 fn as_ref(&self) -> &str {
356 self.as_str()
357 }
358}
359
360impl Deref for Artifact<[u8]> {
361 type Target = [u8];
362
363 #[inline]
364 fn deref(&self) -> &Self::Target {
365 self.as_bytes()
366 }
367}
368
369impl Deref for Artifact<str> {
370 type Target = str;
371
372 #[inline]
373 fn deref(&self) -> &Self::Target {
374 self.as_str()
375 }
376}
377
378/// A SHA-224 artifact identifier.
379///
380/// `Id` is a 28-byte digest. Its [`Display`](std::fmt::Display)
381/// representation is lowercase hexadecimal, and [`FromStr`] parses the same
382/// representation.
383///
384/// # Examples
385///
386/// ```rust
387/// # #[cfg(any())]
388/// # fn __no_run() {
389/// let id = mbed::Id::from_str(
390/// "00000000000000000000000000000000000000000000000000000000",
391/// ).unwrap();
392///
393/// assert_eq!(id.to_string().len(), 56);
394/// assert_eq!(id.as_bytes().len(), 28);
395/// # }
396/// ```
397#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord, Hash)]
398#[repr(transparent)]
399#[allow(clippy::derived_hash_with_manual_eq)]
400pub struct Id([u8; 28]);
401
402impl std::fmt::Debug for Id {
403 #[inline]
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 f.debug_tuple("Id")
406 .field(&const_hex::Buffer::<_, false>::new().const_format(self.as_bytes()))
407 .finish()
408 }
409}
410
411impl std::fmt::Display for Id {
412 #[inline]
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 f.write_str(
415 const_hex::Buffer::<_, false>::new()
416 .const_format(self.as_bytes())
417 .as_str(),
418 )
419 }
420}
421
422impl Id {
423 /// The smallest possible identifier.
424 pub const MIN: Self = Self([u8::MIN; _]);
425
426 /// The largest possible identifier.
427 pub const MAX: Self = Self([u8::MAX; _]);
428
429 /// Compares two identifiers byte by byte.
430 #[inline]
431 pub const fn eq(&self, other: &Self) -> bool {
432 let mut x = true;
433 let mut i = 0;
434
435 while i < self.0.len() {
436 x = x && self.0[i] == other.0[i];
437 i += 1;
438 }
439
440 x
441 }
442
443 /// Returns this identifier as a 28-byte array reference.
444 #[inline]
445 pub const fn as_bytes(&self) -> &[u8; 28] {
446 &self.0
447 }
448
449 /// Returns this identifier as a 28-byte array.
450 #[inline]
451 pub const fn to_bytes(self) -> [u8; 28] {
452 self.0
453 }
454
455 /// Creates an identifier from a 28-byte array.
456 #[inline]
457 pub const fn from_bytes(x: [u8; 28]) -> Self {
458 Self(x)
459 }
460
461 #[inline]
462 const fn bucket_index(&self, n: usize) -> usize {
463 usize::from_ne_bytes(*self.0.first_chunk().unwrap()) & !(usize::MAX << n.ilog2())
464 }
465}
466
467impl PartialEq for Id {
468 #[inline]
469 fn eq(&self, other: &Self) -> bool {
470 self.eq(other)
471 }
472}
473
474impl AsRef<[u8]> for Id {
475 #[inline]
476 fn as_ref(&self) -> &[u8] {
477 self.as_bytes()
478 }
479}
480
481impl Deref for Id {
482 type Target = [u8];
483
484 #[inline]
485 fn deref(&self) -> &Self::Target {
486 self.as_bytes()
487 }
488}
489
490impl FromStr for Id {
491 type Err = const_hex::FromHexError;
492
493 #[inline]
494 fn from_str(s: &str) -> Result<Self, Self::Err> {
495 const_hex::decode_to_array(s).map(Self::from_bytes)
496 }
497}
498
499#[cfg(feature = "serde")]
500impl serde::Serialize for Id {
501 #[inline]
502 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
503 where
504 S: serde::Serializer,
505 {
506 serializer.serialize_str(
507 const_hex::Buffer::<_, false>::new()
508 .const_format(self.as_bytes())
509 .as_str(),
510 )
511 }
512}
513
514#[cfg(feature = "serde")]
515impl<'de> serde::Deserialize<'de> for Id {
516 #[inline]
517 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
518 where
519 D: serde::Deserializer<'de>,
520 {
521 <&str as serde::Deserialize>::deserialize(deserializer)
522 .and_then(|x| Self::from_str(x).map_err(<D::Error as serde::de::Error>::custom))
523 }
524}
525
526#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
527#[doc(hidden)]
528#[repr(transparent)]
529pub struct Bucket(&'static [Entry]);
530
531impl Bucket {
532 const fn find(&self, id: Id) -> Option<usize> {
533 let mut i = 0;
534
535 while i < self.0.len() {
536 if self.0[i].id.eq(&id) {
537 return Some(self.0[i].index as usize);
538 }
539
540 i += 1;
541 }
542
543 None
544 }
545}
546
547#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
548#[doc(hidden)]
549pub struct Entry {
550 id: Id,
551 index: u32,
552}
553
554/// Groups artifacts into a module-local collection.
555///
556/// `collect!` creates a hidden `__ARTIFACTS` item that can be consumed by
557/// [`manifest!`] or by another `collect!` invocation using `module::*`.
558///
559/// This macro accepts artifact names and collection globs.
560///
561/// Requires the `macros` feature.
562///
563/// # Examples
564///
565/// ```rust
566/// # #[cfg(any())]
567/// # fn __no_run() {
568/// pub mod icons {
569/// pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../icons/logo.svg"];
570/// pub const MENU: Artifact<[u8]> = mbed::include_bytes!["../icons/menu.svg"];
571///
572/// mbed::collect![LOGO, MENU];
573/// }
574///
575/// pub mod pages {
576/// pub const INDEX: Artifact<str> = mbed::include_str!["../pages/index.html"];
577///
578/// mbed::collect![INDEX];
579/// }
580///
581/// pub mod public {
582/// mbed::collect![super::icons::*, super::pages::*];
583/// }
584/// # }
585/// ```
586#[cfg(feature = "macros")]
587#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
588#[macro_export]
589macro_rules! collect {
590 (@impl $($x:ident)::+ ::*) => {
591 &$($x)::+ ::__ARTIFACTS
592 };
593
594 (@impl $($x:ident)::+) => {
595 &[($($x)::+).__into_bytes_artifact()]
596 };
597
598
599 ($($($tt:tt)::+),* $(,)?) => {
600 #[allow(unused)]
601 #[doc(hidden)]
602 pub const __ARTIFACTS: &'static [$crate::__macro::Artifact]
603 = $crate::__macro::concat_slices!([$crate::__macro::Artifact]: $($crate::collect!(@impl $($tt)::+)),*);
604 };
605}
606
607/// Builds a [`Manifest`] from artifacts and collections.
608///
609/// `manifest!` accepts the same input shape as [`collect!`]: artifact names and
610/// module globs such as `assets::*`.
611///
612/// Requires the `macros` feature.
613///
614/// # Examples
615///
616/// ```rust
617/// # #[cfg(any())]
618/// # fn __no_run() {
619/// pub mod assets {
620/// pub const README: Artifact<str> = mbed::include_str!["../README.md"];
621/// pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
622///
623/// mbed::collect![README, LOGO];
624/// }
625///
626/// pub const MANIFEST: Manifest = mbed::manifest![assets::*];
627///
628/// let artifact = MANIFEST.get(assets::README.id()).unwrap();
629/// assert_eq!(artifact.mime(), assets::README.mime());
630/// # }
631/// ```
632#[cfg(feature = "macros")]
633#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
634#[macro_export]
635macro_rules! manifest {
636 ($($tt:tt)*) => {{
637 $crate::collect!($($tt)*);
638
639 const MANIFEST: $crate::__macro::Manifest = {
640 const BUCKET_LEN: usize = 1 << __ARTIFACTS.len().ilog2() as usize;
641
642 const ENTRIES_LEN: usize = {
643 let mut entries_len = [0usize; BUCKET_LEN];
644 let mut entries_max = 0;
645
646 let mut i = 0;
647
648 while i < __ARTIFACTS.len() {
649 let x = &mut entries_len
650 [$crate::__macro::bucket_index(&__ARTIFACTS[i].id(), BUCKET_LEN)];
651
652 *x += 1;
653
654 if *x > entries_max {
655 entries_max = *x;
656 }
657
658 i += 1;
659 }
660
661 entries_max
662 };
663
664 const BUCKETS_RAW: [([$crate::__macro::Entry; ENTRIES_LEN], usize); BUCKET_LEN] = {
665 let mut buckets =
666 [([$crate::__macro::entry($crate::__macro::Id::MIN, 0); ENTRIES_LEN], 0usize); BUCKET_LEN];
667
668 let mut i = 0;
669
670 while i < __ARTIFACTS.len() {
671 let x =
672 &mut buckets[$crate::__macro::bucket_index(&__ARTIFACTS[i].id(), BUCKET_LEN)];
673
674 let k = x.1;
675
676 x.0[k] = $crate::__macro::entry(__ARTIFACTS[i].id(), i as u32);
677 x.1 += 1;
678
679 i += 1;
680 }
681
682 buckets
683 };
684
685 const BUCKETS: [$crate::__macro::Bucket; BUCKET_LEN] = {
686 let mut buckets = [$crate::__macro::bucket(&[]); BUCKET_LEN];
687
688 let mut i = 0;
689
690 while i < BUCKET_LEN {
691 buckets[i] = $crate::__macro::bucket(BUCKETS_RAW[i].0.split_at(BUCKETS_RAW[i].1).0);
692 i += 1;
693 }
694
695 buckets
696 };
697
698 $crate::__macro::manifest(__ARTIFACTS, &BUCKETS)
699 };
700
701 MANIFEST
702 }};
703}
704
705/// Embeds a file as an [`Artifact<[u8]>`].
706///
707/// This is the mbed equivalent of Rust's `include_bytes!`, with artifact
708/// metadata attached.
709///
710/// Requires the `macros` feature.
711///
712/// # Examples
713///
714/// ```rust
715/// # #[cfg(any())]
716/// # fn __no_run() {
717/// pub const LOGO: Artifact<[u8]> = mbed::include_bytes!["../assets/logo.svg"];
718///
719/// let bytes = LOGO.as_bytes();
720/// let id = LOGO.id();
721/// # }
722/// ```
723#[cfg(feature = "macros")]
724#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
725pub use mbed_core::include_str;
726
727/// Embeds a UTF-8 file as an [`Artifact<str>`].
728///
729/// This is the mbed equivalent of Rust's `include_str!`, with artifact metadata
730/// attached.
731///
732/// Requires the `macros` feature.
733///
734/// # Examples
735///
736/// ```rust
737/// # #[cfg(any())]
738/// # fn __no_run() {
739/// pub const TEMPLATE: Artifact<str> = mbed::include_str!["../templates/page.html"];
740///
741/// let text = TEMPLATE.as_str();
742/// let bytes = TEMPLATE.as_bytes();
743/// # }
744/// ```
745#[cfg(feature = "macros")]
746#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
747pub use mbed_core::include_bytes;
748
749/// CSS asset support.
750#[cfg(feature = "css")]
751#[cfg_attr(docsrs, doc(cfg(feature = "css")))]
752pub mod css {
753 /// Compiles Tailwind CSS into an embedded text artifact.
754 ///
755 /// Requires the `tailwindcss` feature.
756 ///
757 /// # Examples
758 ///
759 /// ```rust
760 /// # #[cfg(any())]
761 /// # fn __no_run() {
762 /// pub const APP_CSS: Artifact<str> = mbed::css::tailwindcss!("../tailwind.css");
763 ///
764 /// let css = APP_CSS.as_str();
765 /// # }
766 /// ```
767 #[cfg(feature = "tailwindcss")]
768 #[cfg_attr(docsrs, doc(cfg(feature = "tailwindcss")))]
769 pub use mbed_css::tailwindcss;
770}
771
772/// Image asset support.
773///
774/// Requires the `image` feature.
775///
776/// Optional image features:
777///
778/// - `image-avif` enables AVIF support and requires `libdav1d`.
779/// - `image-asm` enables assembly optimizations and requires `nasm`.
780#[cfg(feature = "image")]
781#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
782pub mod image {
783 use std::ops::Deref;
784
785 use crate::Artifact;
786
787 /// Embeds an image as an [`Artifact<Image>`].
788 ///
789 /// The embedded artifact includes the encoded image bytes, image dimensions,
790 /// and detected output format.
791 ///
792 /// Requires the `image` feature.
793 ///
794 /// Optional image features:
795 ///
796 /// - `image-avif` enables AVIF support and requires `libdav1d`.
797 /// - `image-asm` enables assembly optimizations and requires `nasm`.
798 ///
799 /// # Syntax
800 ///
801 /// ```rust
802 /// # #[cfg(any())]
803 /// # fn __no_run() {
804 /// mbed::image::include! {
805 /// path: "../assets/logo.png",
806 /// format: WebP,
807 /// compression: Fast,
808 /// resize: Scale(0.75),
809 /// }
810 /// # }
811 /// ```
812 ///
813 /// The macro accepts a braced list of named fields. The `path` field is
814 /// required; all other fields are optional.
815 ///
816 /// ## Fields
817 ///
818 /// - `path`: Path to the source image file.
819 /// - `format`: Output image format. If omitted, the original image format is
820 /// preserved.
821 /// - `compression`: Best-effort hint for how the image should be re-encoded.
822 /// - `resize`: Optional resize operation to apply before embedding.
823 ///
824 /// ## Supported formats
825 ///
826 /// `Png`, `Jpeg`, `Gif`, `WebP`, `Pnm`, `Tiff`, `Tga`, `Bmp`, `Ico`, `Hdr`,
827 /// `OpenExr`, `Farbfeld`, `Avif`, and `Qoi`.
828 ///
829 /// AVIF support requires the `image-avif` feature.
830 ///
831 /// ## Supported compression modes
832 ///
833 /// `Fast`, `Best`, `Uncompressed`, and `Balanced`.
834 ///
835 /// Compression is a best-effort hint. Some formats may ignore it or only
836 /// support a subset of compression behavior.
837 ///
838 /// ## Resize syntax
839 ///
840 /// Images can be resized by scale factor:
841 ///
842 /// ```rust
843 /// # #[cfg(any())]
844 /// # fn __no_run() {
845 /// # __ {
846 /// resize: Scale(0.75)
847 /// # }}
848 /// ```
849 ///
850 /// Or to exact dimensions:
851 ///
852 /// ```rust
853 /// # #[cfg(any())]
854 /// # fn __no_run() {
855 /// # __ {
856 /// resize: Exact {
857 /// width: 1024,
858 /// height: 1024,
859 /// keep_aspect_ratio: false,
860 /// }
861 /// # }}
862 /// ```
863 ///
864 /// When `keep_aspect_ratio` is enabled, the image is resized to fit within the
865 /// requested dimensions without changing its aspect ratio.
866 ///
867 /// # Examples
868 ///
869 /// Embed an image using its original format:
870 ///
871 /// ```rust
872 /// # #[cfg(any())]
873 /// # fn __no_run() {
874 /// pub const LOGO: Artifact<Image> = mbed::image::include! {
875 /// path: "../assets/logo.png",
876 /// };
877 ///
878 /// assert_eq!(LOGO.width(), 512);
879 /// assert_eq!(LOGO.height(), 512);
880 /// let bytes = LOGO.as_bytes();
881 /// # }
882 /// ```
883 ///
884 /// Re-encode, compress, and resize an image:
885 ///
886 /// ```rust
887 /// # #[cfg(any())]
888 /// # fn __no_run() {
889 /// pub const LOGO_WEBP: Artifact<Image> = mbed::image::include! {
890 /// path: "../assets/logo.png",
891 /// format: WebP,
892 /// compression: Fast,
893 /// resize: Scale(0.75),
894 /// };
895 /// # }
896 /// ```
897 pub use mbed_image::include;
898
899 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
900 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
901 pub struct Image {
902 format: Format,
903 width: u32,
904 height: u32,
905 bytes: &'static [u8],
906 }
907
908 impl Artifact<Image> {
909 /// Returns the encoded image format.
910 #[inline]
911 pub const fn format(&self) -> Format {
912 self.value.format
913 }
914
915 /// Returns the image width in pixels.
916 #[inline]
917 pub const fn width(&self) -> u32 {
918 self.value.width
919 }
920
921 /// Returns the image height in pixels.
922 #[inline]
923 pub const fn height(&self) -> u32 {
924 self.value.height
925 }
926
927 /// Returns the encoded image bytes.
928 #[inline]
929 pub const fn as_bytes(&self) -> &'static [u8] {
930 self.value.bytes
931 }
932
933 #[doc(hidden)]
934 #[inline]
935 pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
936 Artifact {
937 mime: self.mime(),
938 id: self.id(),
939 value: self.as_bytes(),
940 }
941 }
942 }
943
944 impl AsRef<[u8]> for Artifact<Image> {
945 #[inline]
946 fn as_ref(&self) -> &[u8] {
947 self.as_bytes()
948 }
949 }
950
951 impl Deref for Artifact<Image> {
952 type Target = [u8];
953
954 #[inline]
955 fn deref(&self) -> &Self::Target {
956 self.as_bytes()
957 }
958 }
959
960 /// An encoded image format.
961 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
962 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
963 pub enum Format {
964 /// PNG image data.
965 Png,
966 /// JPEG image data.
967 Jpeg,
968 /// GIF image data.
969 Gif,
970 /// WebP image data.
971 WebP,
972 /// PNM image data.
973 Pnm,
974 /// TIFF image data.
975 Tiff,
976 /// TGA image data.
977 Tga,
978 /// BMP image data.
979 Bmp,
980 /// ICO image data.
981 Ico,
982 /// Radiance HDR image data.
983 Hdr,
984 /// OpenEXR image data.
985 OpenExr,
986 /// Farbfeld image data.
987 Farbfeld,
988 /// AVIF image data.
989 #[cfg(any(feature = "image-avif", doc, docsrs))]
990 #[cfg_attr(docsrs, doc(cfg(feature = "image-avif")))]
991 Avif,
992 /// QOI image data.
993 Qoi,
994 }
995
996 #[cfg(feature = "macros")]
997 #[doc(hidden)]
998 pub mod __macro {
999 pub use super::{Format, Image};
1000
1001 #[inline]
1002 pub const fn image(format: Format, width: u32, height: u32, bytes: &'static [u8]) -> Image {
1003 Image {
1004 format,
1005 width,
1006 height,
1007 bytes,
1008 }
1009 }
1010 }
1011}
1012
1013/// JavaScript asset support.
1014#[cfg(feature = "js")]
1015#[cfg_attr(docsrs, doc(cfg(feature = "js")))]
1016pub mod js {
1017 use std::ops::Deref;
1018
1019 use crate::Artifact;
1020
1021 /// Bundles and minifies JavaScript into an embedded [`Artifact<Bundle>`].
1022 ///
1023 /// The embedded artifact includes the bundled JavaScript code and source map.
1024 ///
1025 /// Bundling and minification are performed using `oxc`.
1026 ///
1027 /// Requires the `bundle` feature.
1028 ///
1029 /// # Syntax
1030 ///
1031 /// The macro accepts either a single JavaScript file path:
1032 ///
1033 /// ```rust
1034 /// # #[cfg(any())]
1035 /// # fn __no_run() {
1036 /// mbed::js::bundle!("../frontend/app.js")
1037 /// # }
1038 /// ```
1039 ///
1040 /// Or a list of JavaScript file paths:
1041 ///
1042 /// ```rust
1043 /// # #[cfg(any())]
1044 /// # fn __no_run() {
1045 /// mbed::js::bundle![
1046 /// "../frontend/vendor.js",
1047 /// "../frontend/app.js",
1048 /// ]
1049 /// # }
1050 /// ```
1051 ///
1052 /// Paths are resolved relative to the source file containing the macro
1053 /// invocation.
1054 ///
1055 /// Only JavaScript files are currently supported. TypeScript files cannot be
1056 /// bundled by this macro at this time.
1057 ///
1058 /// # Examples
1059 ///
1060 /// Bundle a single JavaScript entry point:
1061 ///
1062 /// ```rust
1063 /// # #[cfg(any())]
1064 /// # fn __no_run() {
1065 /// pub const APP: Artifact<Bundle> = mbed::js::bundle!("../frontend/app.js");
1066 ///
1067 /// let code = APP.code();
1068 /// let sourcemap = APP.sourcemap();
1069 /// # }
1070 /// ```
1071 ///
1072 /// Bundle multiple JavaScript files:
1073 ///
1074 /// ```rust
1075 /// # #[cfg(any())]
1076 /// # fn __no_run() {
1077 /// pub const APP: Artifact<Bundle> = mbed::js::bundle![
1078 /// "../frontend/vendor.js",
1079 /// "../frontend/app.js",
1080 /// ];
1081 ///
1082 /// let code = APP.code();
1083 /// let sourcemap = APP.sourcemap();
1084 /// # }
1085 /// ```
1086 #[cfg(feature = "bundle")]
1087 #[cfg_attr(docsrs, doc(cfg(feature = "bundle")))]
1088 pub use mbed_js::bundle;
1089
1090 /// Embedded JavaScript bundle output.
1091 ///
1092 /// A bundle contains generated JavaScript code and its source map.
1093 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1094 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1095 pub struct Bundle {
1096 code: &'static str,
1097 sourcemap: &'static str,
1098 }
1099
1100 impl Artifact<Bundle> {
1101 /// Returns the bundle code as UTF-8 bytes.
1102 #[inline]
1103 pub const fn as_bytes(&self) -> &'static [u8] {
1104 self.as_str().as_bytes()
1105 }
1106
1107 /// Returns the bundle code.
1108 #[inline]
1109 pub const fn as_str(&self) -> &'static str {
1110 self.code()
1111 }
1112
1113 /// Returns the generated JavaScript code.
1114 #[inline]
1115 pub const fn code(&self) -> &'static str {
1116 self.value.code
1117 }
1118
1119 /// Returns the generated source map.
1120 #[inline]
1121 pub const fn sourcemap(&self) -> &'static str {
1122 self.value.sourcemap
1123 }
1124
1125 #[doc(hidden)]
1126 #[inline]
1127 pub const fn __into_bytes_artifact(self) -> Artifact<[u8]> {
1128 Artifact {
1129 mime: self.mime(),
1130 id: self.id(),
1131 value: self.code().as_bytes(),
1132 }
1133 }
1134 }
1135
1136 impl AsRef<[u8]> for Artifact<Bundle> {
1137 #[inline]
1138 fn as_ref(&self) -> &[u8] {
1139 self.as_bytes()
1140 }
1141 }
1142
1143 impl AsRef<str> for Artifact<Bundle> {
1144 #[inline]
1145 fn as_ref(&self) -> &str {
1146 self.as_str()
1147 }
1148 }
1149
1150 impl Deref for Artifact<Bundle> {
1151 type Target = str;
1152
1153 #[inline]
1154 fn deref(&self) -> &Self::Target {
1155 self.as_str()
1156 }
1157 }
1158
1159 #[cfg(feature = "macros")]
1160 #[doc(hidden)]
1161 pub mod __macro {
1162 pub use super::Bundle;
1163
1164 #[inline]
1165 pub const fn bundle(code: &'static str, sourcemap: &'static str) -> Bundle {
1166 Bundle { code, sourcemap }
1167 }
1168 }
1169}
1170
1171#[cfg(feature = "macros")]
1172#[doc(hidden)]
1173pub mod __macro {
1174 pub use crate::{Artifact, Bucket, Entry, Id, Manifest};
1175 pub use constcat::concat_slices;
1176
1177 #[inline]
1178 pub const fn manifest(
1179 artifacts: &'static [Artifact<[u8]>],
1180 buckets: &'static [Bucket],
1181 ) -> Manifest {
1182 Manifest { artifacts, buckets }
1183 }
1184
1185 #[inline]
1186 pub const fn artifact<T>(mime: &'static str, id: Id, value: &'static T) -> Artifact<T>
1187 where
1188 T: ?Sized + 'static,
1189 {
1190 Artifact { mime, id, value }
1191 }
1192
1193 #[inline]
1194 pub const fn bucket(x: &'static [Entry]) -> Bucket {
1195 Bucket(x)
1196 }
1197
1198 #[inline]
1199 pub const fn entry(id: Id, index: u32) -> Entry {
1200 Entry { id, index }
1201 }
1202
1203 #[inline]
1204 pub const fn bucket_index(x: &Id, n: usize) -> usize {
1205 x.bucket_index(n)
1206 }
1207}