1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! KFX fragment representation.
//!
//! A fragment is the fundamental unit of KFX content. Fragments can contain
//! either Ion data (for structured content) or raw bytes (for media like images).
use super::ion::IonValue;
use super::symbols::KfxSymbol;
/// Fragment payload - Ion for structured data, Raw for media.
#[derive(Debug, Clone)]
pub enum FragmentData {
/// Ion-encoded structured data (metadata, storylines, styles, etc.)
Ion(IonValue),
/// Raw binary data (JPEG, PNG, TTF, etc.)
Raw(Vec<u8>),
}
/// A KFX fragment - the fundamental unit of KFX content.
#[derive(Debug, Clone)]
pub struct KfxFragment {
/// Fragment type (symbol ID like $260, $145, etc.)
pub ftype: u64,
/// Fragment ID (unique identifier, or same as ftype for singletons)
pub fid: String,
/// The payload (Ion or raw bytes)
pub data: FragmentData,
}
impl KfxFragment {
/// Create a new fragment with Ion data.
pub fn new(ftype: impl Into<u64>, fid: impl Into<String>, value: IonValue) -> Self {
Self {
ftype: ftype.into(),
fid: fid.into(),
data: FragmentData::Ion(value),
}
}
/// Create a new fragment with a numeric fragment ID.
///
/// This is used when the fragment ID was pre-assigned during Pass 1.
pub fn new_with_id(
ftype: impl Into<u64>,
fragment_id: u64,
name: impl Into<String>,
value: IonValue,
) -> Self {
// Store the name as fid for debugging, but the fragment_id is what matters
// for serialization
let _ = fragment_id; // ID is embedded in the entity table during serialization
Self {
ftype: ftype.into(),
fid: name.into(),
data: FragmentData::Ion(value),
}
}
/// Create a new fragment with raw binary data.
pub fn raw(ftype: impl Into<u64>, fid: impl Into<String>, bytes: Vec<u8>) -> Self {
Self {
ftype: ftype.into(),
fid: fid.into(),
data: FragmentData::Raw(bytes),
}
}
/// Create a singleton fragment (fid equals ftype name).
/// Used for fragments where only one instance exists (e.g., metadata).
pub fn singleton(ftype: impl Into<u64>, value: IonValue) -> Self {
let ftype_val = ftype.into();
Self {
ftype: ftype_val,
fid: format!("${ftype_val}"),
data: FragmentData::Ion(value),
}
}
/// Check if this is a singleton fragment.
pub fn is_singleton(&self) -> bool {
self.fid == format!("${}", self.ftype)
}
/// Check if this fragment contains raw data.
pub fn is_raw(&self) -> bool {
matches!(self.data, FragmentData::Raw(_))
}
/// Get the Ion value if this is an Ion fragment.
pub fn as_ion(&self) -> Option<&IonValue> {
match &self.data {
FragmentData::Ion(v) => Some(v),
FragmentData::Raw(_) => None,
}
}
/// Get the raw bytes if this is a raw fragment.
pub fn as_raw(&self) -> Option<&[u8]> {
match &self.data {
FragmentData::Ion(_) => None,
FragmentData::Raw(bytes) => Some(bytes),
}
}
}
// Convenience conversions from KfxSymbol
impl From<KfxSymbol> for u64 {
fn from(sym: KfxSymbol) -> u64 {
sym as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fragment_new() {
let frag = KfxFragment::new(260u64, "section-1", IonValue::Null);
assert_eq!(frag.ftype, 260);
assert_eq!(frag.fid, "section-1");
assert!(!frag.is_singleton());
assert!(!frag.is_raw());
}
#[test]
fn test_fragment_singleton() {
let frag = KfxFragment::singleton(KfxSymbol::Metadata, IonValue::Null);
assert!(frag.is_singleton());
assert_eq!(frag.fid, "$258");
}
#[test]
fn test_fragment_raw() {
let data = vec![0xFF, 0xD8, 0xFF, 0xE0]; // JPEG header
let frag = KfxFragment::raw(KfxSymbol::Bcrawmedia, "image-1", data.clone());
assert!(frag.is_raw());
assert_eq!(frag.as_raw(), Some(data.as_slice()));
assert!(frag.as_ion().is_none());
}
#[test]
fn test_kfx_symbol_conversion() {
let frag = KfxFragment::new(KfxSymbol::Section, "sec-1", IonValue::Null);
assert_eq!(frag.ftype, 260);
}
}