1use crate::binary::{Reader, Writer};
11use crate::error::{Result, bail};
12
13pub mod ids {
15 pub const RUN_FONT_SIZE: u32 = 11;
17 pub const FONT_SIZE: u32 = 32;
19 pub const BBOX: u32 = 42;
21 pub const BALLOON: u32 = 45;
25 pub const RENDER_OFFSCREEN: u32 = 50;
27 pub const FONT: u32 = 57;
29 pub const BLOCK_SIZE: u32 = 63;
31 pub const QUAD: u32 = 64;
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Record {
38 pub id: u32,
40 pub data: Vec<u8>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Default)]
46pub struct Attributes {
47 pub records: Vec<Record>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
53pub struct Font {
54 pub lead: u16,
56 pub name: String,
58 pub postscript_name: String,
60}
61
62impl Attributes {
63 pub fn parse(blob: &[u8]) -> Result<Self> {
65 let mut reader = Reader::new(blob);
66 let mut records = Vec::new();
67 while !reader.is_empty() {
68 let id = reader.le_u32()?;
69 let length = reader.le_u32()? as usize;
70 if length > reader.remaining() {
71 bail!("Text attribute {id} says {length} bytes, {} remain", reader.remaining());
72 }
73 records.push(Record { id, data: reader.bytes(length)?.to_vec() });
74 }
75 Ok(Attributes { records })
76 }
77
78 pub fn to_bytes(&self) -> Vec<u8> {
80 let mut writer = Writer::new();
81 for record in &self.records {
82 writer.le_u32(record.id).le_u32(record.data.len() as u32).bytes(&record.data);
83 }
84 writer.into_inner()
85 }
86
87 pub fn get(&self, id: u32) -> Option<&[u8]> {
89 self.records.iter().find(|record| record.id == id).map(|record| record.data.as_slice())
90 }
91
92 pub fn set(&mut self, id: u32, data: Vec<u8>) {
94 match self.records.iter_mut().find(|record| record.id == id) {
95 Some(record) => record.data = data,
96 None => self.records.push(Record { id, data }),
97 }
98 }
99
100 fn i32s(&self, id: u32) -> Option<Vec<i32>> {
101 let data = self.get(id)?;
102 Some(data.as_chunks::<4>().0.iter().map(|b| i32::from_le_bytes(*b)).collect())
103 }
104
105 fn set_i32s(&mut self, id: u32, values: &[i32]) {
106 self.set(id, values.iter().flat_map(|value| value.to_le_bytes()).collect());
107 }
108
109 pub fn font_size(&self) -> Option<f64> {
111 self.i32s(ids::FONT_SIZE)?.first().map(|value| *value as f64 / 100.0)
112 }
113
114 pub fn bbox(&self) -> Option<[i32; 4]> {
116 self.i32s(ids::BBOX)?.try_into().ok()
117 }
118
119 pub fn set_bbox(&mut self, bbox: [i32; 4]) {
121 self.set_i32s(ids::BBOX, &bbox);
122 }
123
124 pub fn block_size(&self) -> Option<[i32; 2]> {
126 self.i32s(ids::BLOCK_SIZE)?.try_into().ok()
127 }
128
129 pub fn quad(&self) -> Option<[i32; 8]> {
131 self.i32s(ids::QUAD)?.try_into().ok()
132 }
133
134 pub fn balloon(&self) -> Option<u32> {
136 self.i32s(ids::BALLOON)?.first().map(|value| *value as u32).filter(|value| *value > 0)
137 }
138
139 pub fn render_offscreen(&self) -> Option<u32> {
141 self.i32s(ids::RENDER_OFFSCREEN)?.first().map(|value| *value as u32)
142 }
143
144 pub fn clear_render_offscreen(&mut self) {
146 if self.get(ids::RENDER_OFFSCREEN).is_some() {
147 self.set_i32s(ids::RENDER_OFFSCREEN, &[0]);
148 }
149 }
150
151 pub fn font(&self) -> Option<Font> {
153 let data = self.get(ids::FONT)?;
154 let mut reader = Reader::new(data);
155 let lead = reader.le_u16().ok()?;
156 let length = reader.le_u16().ok()? as usize;
157 let name = String::from_utf8_lossy(reader.bytes(length).ok()?).into_owned();
158 let length = reader.le_u16().ok()? as usize;
159 let postscript_name = String::from_utf8_lossy(reader.bytes(length).ok()?).into_owned();
160 Some(Font { lead, name, postscript_name })
161 }
162
163 pub fn transform(&mut self, scale: f64, dx: f64, dy: f64) {
165 if let Some(b) = self.bbox() {
166 self.set_bbox([
167 (b[0] as f64 * scale + dx).floor() as i32,
168 (b[1] as f64 * scale + dy).floor() as i32,
169 (b[2] as f64 * scale + dx).ceil() as i32,
170 (b[3] as f64 * scale + dy).ceil() as i32,
171 ]);
172 }
173 if let Some(size) = self.block_size() {
174 self.set_i32s(ids::BLOCK_SIZE, &size.map(|v| (v as f64 * scale).round() as i32));
175 }
176 if let Some(quad) = self.quad() {
177 self.set_i32s(ids::QUAD, &quad.map(|v| (v as f64 * scale).round() as i32));
178 }
179 }
180}
181
182pub fn split_array(blob: &[u8]) -> Result<Vec<&[u8]>> {
184 let mut reader = Reader::new(blob);
185 let mut parts = Vec::new();
186 while !reader.is_empty() {
187 let length = reader.le_u32()? as usize;
188 if length > reader.remaining() {
189 bail!("Array entry of {length} bytes, {} remain", reader.remaining());
190 }
191 parts.push(reader.bytes(length)?);
192 }
193 Ok(parts)
194}
195
196pub fn join_array<T: AsRef<[u8]>>(parts: &[T]) -> Vec<u8> {
198 let mut writer = Writer::new();
199 for part in parts {
200 writer.le_u32(part.as_ref().len() as u32).bytes(part.as_ref());
201 }
202 writer.into_inner()
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct TextBlock {
208 pub text: String,
210 pub attributes: Attributes,
212}
213
214pub fn read_blocks(row: &crate::database::Row) -> Result<Vec<TextBlock>> {
219 let mut blocks = Vec::new();
220 let (text, attributes) = (row.blob("TextLayerString"), row.blob("TextLayerAttributes"));
221 if text.is_some() || attributes.is_some() {
222 blocks.push(block(text.unwrap_or_default(), attributes)?);
223 }
224 let strings = row.blob("TextLayerStringArray").map(split_array).transpose()?.unwrap_or_default();
225 let attributes = row.blob("TextLayerAttributesArray").map(split_array).transpose()?.unwrap_or_default();
226 for index in 0..strings.len().max(attributes.len()) {
227 blocks.push(block(strings.get(index).copied().unwrap_or_default(), attributes.get(index).copied())?);
228 }
229 Ok(blocks)
230}
231
232fn block(text: &[u8], attributes: Option<&[u8]>) -> Result<TextBlock> {
233 let attributes = match attributes {
234 Some(blob) => Attributes::parse(blob)?,
235 None => Attributes::default(),
236 };
237 Ok(TextBlock { text: String::from_utf8_lossy(text).into_owned(), attributes })
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn attributes_round_trip_and_decode() {
246 let mut attributes = Attributes::default();
247 attributes.set(ids::FONT_SIZE, 600i32.to_le_bytes().to_vec());
248 attributes.set_bbox([1, 2, 30, 40]);
249 attributes.set(99, vec![1, 2, 3]);
250 let bytes = attributes.to_bytes();
251 let parsed = Attributes::parse(&bytes).unwrap();
252 assert_eq!(parsed, attributes);
253 assert_eq!(parsed.font_size(), Some(6.0));
254 assert_eq!(parsed.bbox(), Some([1, 2, 30, 40]));
255 assert!(Attributes::parse(&bytes[..bytes.len() - 1]).is_err());
256 let array = join_array(&[bytes.clone(), vec![]]);
257 assert_eq!(split_array(&array).unwrap(), vec![bytes.as_slice(), &[][..]]);
258 }
259
260 #[test]
261 fn the_arrays_follow_the_single_block() {
262 use crate::database::{Row, SqlValue as Value};
263 let mut first = Attributes::default();
264 first.set_bbox([0, 0, 10, 10]);
265 let mut second = Attributes::default();
266 second.set_bbox([20, 0, 30, 10]);
267 let columns = ["TextLayerString", "TextLayerAttributes", "TextLayerStringArray", "TextLayerAttributesArray"];
268 let row = Row::new(
269 columns.iter().map(|c| c.to_string()).collect(),
270 vec![
271 Value::Blob(b"one".to_vec()),
272 Value::Blob(first.to_bytes()),
273 Value::Blob(join_array(&[b"two".to_vec()])),
274 Value::Blob(join_array(&[second.to_bytes()])),
275 ],
276 );
277 let blocks = read_blocks(&row).unwrap();
278 assert_eq!(blocks.iter().map(|b| b.text.as_str()).collect::<Vec<_>>(), ["one", "two"]);
279 assert_eq!(blocks[1].attributes.bbox(), Some([20, 0, 30, 10]));
280 let single = Row::new(vec!["TextLayerString".into()], vec![Value::Blob(b"only".to_vec())]);
281 assert_eq!(read_blocks(&single).unwrap().len(), 1);
282 }
283}