rakata_formats/twoda/
writer.rs1use std::collections::HashMap;
4use std::io::{Cursor, Write};
5
6use rakata_core::{encode_text, TextEncoding};
7
8use super::{
9 binary::{write_u16, write_u32},
10 validate_twoda, TwoDa, TwoDaBinaryError, TwoDaBinaryOptions, TWODA_MAGIC, TWODA_VERSION_V2B,
11};
12
13#[cfg_attr(
15 feature = "tracing",
16 tracing::instrument(level = "debug", skip(writer, twoda))
17)]
18pub fn write_twoda<W: Write>(writer: &mut W, twoda: &TwoDa) -> Result<(), TwoDaBinaryError> {
19 write_twoda_with_options(writer, twoda, TwoDaBinaryOptions::default())
20}
21
22#[cfg_attr(
24 feature = "tracing",
25 tracing::instrument(level = "debug", skip(writer, twoda, options))
26)]
27pub fn write_twoda_with_options<W: Write>(
28 writer: &mut W,
29 twoda: &TwoDa,
30 options: TwoDaBinaryOptions,
31) -> Result<(), TwoDaBinaryError> {
32 validate_twoda(twoda)?;
33
34 writer.write_all(&TWODA_MAGIC)?;
35 writer.write_all(&TWODA_VERSION_V2B)?;
36 writer.write_all(b"\n")?;
37
38 for (index, header) in twoda.headers.iter().enumerate() {
39 let bytes =
40 encode_with_encoding(header, format!("header[{index}]"), options.text_encoding)?;
41 writer.write_all(&bytes)?;
42 writer.write_all(b"\t")?;
43 }
44 writer.write_all(&[0])?;
45
46 let row_count = u32::try_from(twoda.rows.len())
47 .map_err(|_| TwoDaBinaryError::ValueOverflow("row_count"))?;
48 write_u32(writer, row_count)?;
49
50 for (index, row) in twoda.rows.iter().enumerate() {
51 let bytes = encode_with_encoding(
52 &row.label,
53 format!("row label[{index}]"),
54 options.text_encoding,
55 )?;
56 writer.write_all(&bytes)?;
57 writer.write_all(b"\t")?;
58 }
59
60 let mut unique_values = Vec::<Vec<u8>>::new();
61 let mut offset_by_value = HashMap::<Vec<u8>, u16>::new();
62 let mut cell_offsets = Vec::<u16>::with_capacity(twoda.rows.len() * twoda.headers.len());
63 let mut data_size: usize = 0;
64
65 for (row_index, row) in twoda.rows.iter().enumerate() {
66 for (column_index, value) in row.cells.iter().enumerate() {
67 let mut encoded = encode_with_encoding(
68 value,
69 format!("cell[{row_index}][{column_index}]"),
70 options.text_encoding,
71 )?;
72 encoded.push(0);
73
74 let offset = if let Some(existing) = offset_by_value.get(&encoded) {
75 *existing
76 } else {
77 let offset = u16::try_from(data_size)
78 .map_err(|_| TwoDaBinaryError::ValueOverflow("cell_offset"))?;
79 data_size = data_size
80 .checked_add(encoded.len())
81 .ok_or(TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
82 if data_size > usize::from(u16::MAX) {
83 return Err(TwoDaBinaryError::ValueOverflow("cell_data_size"));
84 }
85 offset_by_value.insert(encoded.clone(), offset);
86 unique_values.push(encoded);
87 offset
88 };
89 cell_offsets.push(offset);
90 }
91 }
92
93 for offset in &cell_offsets {
94 write_u16(writer, *offset)?;
95 }
96 let data_size_u16 =
97 u16::try_from(data_size).map_err(|_| TwoDaBinaryError::ValueOverflow("cell_data_size"))?;
98 write_u16(writer, data_size_u16)?;
99 for value in &unique_values {
100 writer.write_all(value)?;
101 }
102
103 Ok(())
104}
105
106#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(twoda)))]
108pub fn write_twoda_to_vec(twoda: &TwoDa) -> Result<Vec<u8>, TwoDaBinaryError> {
109 write_twoda_to_vec_with_options(twoda, TwoDaBinaryOptions::default())
110}
111
112#[cfg_attr(
114 feature = "tracing",
115 tracing::instrument(level = "debug", skip(twoda, options))
116)]
117pub fn write_twoda_to_vec_with_options(
118 twoda: &TwoDa,
119 options: TwoDaBinaryOptions,
120) -> Result<Vec<u8>, TwoDaBinaryError> {
121 let mut cursor = Cursor::new(Vec::new());
122 write_twoda_with_options(&mut cursor, twoda, options)?;
123 Ok(cursor.into_inner())
124}
125
126fn encode_with_encoding(
127 value: &str,
128 context: String,
129 encoding: TextEncoding,
130) -> Result<Vec<u8>, TwoDaBinaryError> {
131 encode_text(value, encoding)
132 .map_err(|source| TwoDaBinaryError::TextEncoding { context, source })
133}