display_types/manufacture.rs
1/// Manufacture date or model year, decoded from EDID base block bytes 16–17.
2///
3/// | Byte 16 | Meaning |
4/// |---------|------------------------------------------------------|
5/// | `0x00` | Week unspecified; byte 17 is the manufacture year. |
6/// | `0x01`–`0x36` | Week of manufacture (1–54). |
7/// | `0xFF` | Byte 17 is a model year, not a manufacture year. |
8///
9/// Year is encoded as `byte_17 + 1990`.
10#[non_exhaustive]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ManufactureDate {
14 /// The display was manufactured in the given year.
15 /// `week` is `None` if byte 16 was `0x00` (week unspecified).
16 Manufactured {
17 /// Week of manufacture (1–54), if specified.
18 week: Option<u8>,
19 /// Year of manufacture.
20 year: u16,
21 },
22 /// The year identifies a model year rather than a manufacture date.
23 ModelYear(u16),
24}
25
26/// A three-character PNP manufacturer identifier, decoded from EDID base block bytes `0x08`–`0x09`.
27///
28/// Each character is an ASCII uppercase letter (A–Z). Valid IDs are registered with the IANA
29/// PNP registry. Well-known examples: `GSM` (LG), `SAM` (Samsung), `DEL` (Dell).
30///
31/// # Invariant
32///
33/// All three bytes must be ASCII uppercase letters (`b'A'`–`b'Z'`, i.e. `0x41`–`0x5A`).
34/// The library only constructs this type after validating that constraint. If you construct
35/// one manually via the public field, you are responsible for maintaining the invariant;
36/// methods on this type will panic in debug builds if it is violated.
37///
38/// Use [`ManufacturerId::from_ascii`] for a checked construction path.
39///
40/// Available in all build configurations including bare `no_std`. The `Display` impl renders
41/// the three-character string directly, so `format!("{}", id)` and `id.to_string()` both work
42/// wherever a `Display` bound is satisfied.
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct ManufacturerId(pub [u8; 3]);
46
47impl ManufacturerId {
48 /// Constructs a `ManufacturerId` from three raw bytes, returning `None` if any byte is
49 /// not an ASCII uppercase letter (`A`–`Z`).
50 pub fn from_ascii(bytes: [u8; 3]) -> Option<Self> {
51 if bytes.iter().all(|&b| b.is_ascii_uppercase()) {
52 Some(Self(bytes))
53 } else {
54 None
55 }
56 }
57
58 /// Returns the ID as a `&str` slice.
59 ///
60 /// Panics in debug builds if the stored bytes are not ASCII uppercase letters, which
61 /// would indicate the type invariant was violated at construction time.
62 pub fn as_str(&self) -> &str {
63 debug_assert!(
64 self.0.iter().all(|&b| b.is_ascii_uppercase()),
65 "ManufacturerId invariant violated: bytes must be ASCII uppercase A-Z, got {:?}",
66 self.0
67 );
68 // Safety: ASCII uppercase bytes are always valid UTF-8.
69 core::str::from_utf8(&self.0).expect("ManufacturerId bytes must be ASCII uppercase A-Z")
70 }
71}
72
73impl core::fmt::Display for ManufacturerId {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 f.write_str(self.as_str())
76 }
77}
78
79/// A monitor descriptor string, decoded from one of the 18-byte descriptor slots in the
80/// EDID base block (`0xFC` monitor name, `0xFF` serial number, `0xFE` unspecified text).
81///
82/// The text payload occupies bytes 5–17 of the descriptor (13 bytes), terminated by `0x0A`
83/// and padded with spaces. The `as_str()` method strips both the terminator and trailing
84/// spaces, returning a clean `&str`.
85///
86/// Available in all build configurations including bare `no_std`. `Deref<Target = str>`
87/// is implemented so `Option<MonitorString>::as_deref()` returns `Option<&str>` directly.
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct MonitorString(pub [u8; 13]);
91
92impl MonitorString {
93 /// Returns the string content with the `0x0A` terminator and trailing spaces stripped.
94 ///
95 /// Returns an empty `&str` if the payload is all padding or not valid UTF-8.
96 pub fn as_str(&self) -> &str {
97 let bytes = &self.0;
98 let end = bytes.iter().position(|&b| b == 0x0A).unwrap_or(bytes.len());
99 let trimmed = match bytes[..end].iter().rposition(|&b| b != b' ') {
100 Some(i) => &bytes[..=i],
101 None => &[][..],
102 };
103 core::str::from_utf8(trimmed).unwrap_or("")
104 }
105}
106
107impl core::fmt::Display for MonitorString {
108 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109 f.write_str(self.as_str())
110 }
111}
112
113impl core::ops::Deref for MonitorString {
114 type Target = str;
115 fn deref(&self) -> &str {
116 self.as_str()
117 }
118}