pub struct Fib {
pub f_which_tbl_stm: u8,
pub fc_clx: u32,
pub lcb_clx: u32,
pub fc_plcf_papx: u32,
pub lcb_plcf_papx: u32,
pub fc_stshf: u32,
pub lcb_stshf: u32,
pub ccp_text: i32,
pub ccp_ftn: i32,
pub ccp_hdd: i32,
pub ccp_mcr: i32,
pub ccp_atn: i32,
pub ccp_edn: i32,
pub ccp_txbx: i32,
pub ccp_hdr_txbx: i32,
pub fc_plcf_bte_chpx: u32,
pub lcb_plcf_bte_chpx: u32,
pub fc_dgg_info: u32,
pub lcb_dgg_info: u32,
}
fn read_u16(data: &[u8], offset: usize) -> Result<u16, String> {
data.get(offset..offset + 2)
.map(|b| u16::from_le_bytes([b[0], b[1]]))
.ok_or_else(|| format!("FIB truncated at offset {offset}"))
}
fn read_u32(data: &[u8], offset: usize) -> Result<u32, String> {
data.get(offset..offset + 4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.ok_or_else(|| format!("FIB truncated at offset {offset}"))
}
fn read_i32(data: &[u8], offset: usize) -> Result<i32, String> {
data.get(offset..offset + 4)
.map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.ok_or_else(|| format!("FIB truncated at offset {offset}"))
}
fn read_fc_lcb_entry(data: &[u8], start: usize, index: usize) -> Result<(u32, u32), String> {
let off = start + index * 8;
let fc = read_u32(data, off)?;
let lcb = read_u32(data, off + 4)?;
Ok((fc, lcb))
}
pub fn parse_fib(word_doc: &[u8]) -> Result<Fib, String> {
if word_doc.len() < 34 {
return Err("FIB truncated at offset 0".to_string());
}
let w_ident = read_u16(word_doc, 0)?;
if w_ident == 0xA5DC {
return Err(
"Pre-Word 97 .doc files are not supported. Convert to .docx first.".to_string(),
);
}
if w_ident != 0xA5EC {
return Err(format!(
"Not a Word .doc file (unexpected magic 0x{w_ident:04X})"
));
}
let flags = read_u16(word_doc, 10)?;
let f_which_tbl_stm = ((flags >> 9) & 1) as u8;
let csw = read_u16(word_doc, 32)? as usize;
let fib_rg_w_start = 34usize;
let fib_rg_w_end = fib_rg_w_start + (2 * csw);
if fib_rg_w_end > word_doc.len() {
return Err(format!("FIB truncated at offset {fib_rg_w_end}"));
}
let cslw_off = fib_rg_w_end;
let cslw = read_u16(word_doc, cslw_off)? as usize;
let lw_array_start = cslw_off + 2;
let lw_array_end = lw_array_start + (4 * cslw);
if lw_array_end > word_doc.len() {
return Err(format!("FIB truncated at offset {lw_array_end}"));
}
if cslw < 4 {
return Err("FIB FibRgLw does not include ccpText".to_string());
}
let ccp_text = read_i32(word_doc, lw_array_start + 12)?;
let ccp_at = |i: usize| -> i32 {
if cslw > i {
read_i32(word_doc, lw_array_start + i * 4).unwrap_or(0)
} else {
0
}
};
let ccp_ftn = ccp_at(4);
let ccp_hdd = ccp_at(5);
let ccp_mcr = ccp_at(6);
let ccp_atn = ccp_at(7);
let ccp_edn = ccp_at(8);
let ccp_txbx = ccp_at(9);
let ccp_hdr_txbx = ccp_at(10);
let cfclcb_off = lw_array_end;
let cfclcb = read_u16(word_doc, cfclcb_off)? as usize;
let fclcb_start = cfclcb_off + 2;
let fclcb_end = fclcb_start + (8 * cfclcb);
if fclcb_end > word_doc.len() {
return Err(format!("FIB truncated at offset {fclcb_end}"));
}
for required in [1usize, 13usize, 33usize] {
if required >= cfclcb {
return Err(format!("FIB FibRgFcLcb missing entry index {required}"));
}
}
let (fc_stshf, lcb_stshf) = read_fc_lcb_entry(word_doc, fclcb_start, 1)?;
let (fc_plcf_papx, lcb_plcf_papx) = read_fc_lcb_entry(word_doc, fclcb_start, 13)?;
let (fc_clx, lcb_clx) = read_fc_lcb_entry(word_doc, fclcb_start, 33)?;
let read_optional = |index: usize| -> (u32, u32) {
if index < cfclcb {
read_fc_lcb_entry(word_doc, fclcb_start, index).unwrap_or((0, 0))
} else {
(0, 0)
}
};
let (fc_plcf_bte_chpx, lcb_plcf_bte_chpx) = read_optional(12);
let (fc_dgg_info, lcb_dgg_info) = read_optional(50);
Ok(Fib {
f_which_tbl_stm,
fc_clx,
lcb_clx,
fc_plcf_papx,
lcb_plcf_papx,
fc_stshf,
lcb_stshf,
ccp_text,
ccp_ftn,
ccp_hdd,
ccp_mcr,
ccp_atn,
ccp_edn,
ccp_txbx,
ccp_hdr_txbx,
fc_plcf_bte_chpx,
lcb_plcf_bte_chpx,
fc_dgg_info,
lcb_dgg_info,
})
}