imprimatur 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/Cargo.lock +153 -3
  4. data/Cargo.toml +1 -1
  5. data/README.md +139 -21
  6. data/ext/imprimatur_native/cli/Cargo.toml +3 -1
  7. data/ext/imprimatur_native/core/Cargo.toml +20 -4
  8. data/ext/imprimatur_native/core/README.md +36 -4
  9. data/ext/imprimatur_native/core/src/archive.rs +3 -16
  10. data/ext/imprimatur_native/core/src/barcode/code128.rs +162 -0
  11. data/ext/imprimatur_native/core/src/barcode/draw.rs +124 -0
  12. data/ext/imprimatur_native/core/src/barcode/ean.rs +91 -0
  13. data/ext/imprimatur_native/core/src/barcode/gs1.rs +188 -0
  14. data/ext/imprimatur_native/core/src/barcode/matrix.rs +130 -0
  15. data/ext/imprimatur_native/core/src/barcode.rs +314 -0
  16. data/ext/imprimatur_native/core/src/doc/paragraph.rs +10 -3
  17. data/ext/imprimatur_native/core/src/doc/picture.rs +192 -0
  18. data/ext/imprimatur_native/core/src/doc/symbol.rs +136 -0
  19. data/ext/imprimatur_native/core/src/doc/table.rs +21 -0
  20. data/ext/imprimatur_native/core/src/doc.rs +96 -2
  21. data/ext/imprimatur_native/core/src/docx/picture.rs +72 -0
  22. data/ext/imprimatur_native/core/src/docx.rs +103 -28
  23. data/ext/imprimatur_native/core/src/error.rs +18 -0
  24. data/ext/imprimatur_native/core/src/font.rs +231 -0
  25. data/ext/imprimatur_native/core/src/lib.rs +6 -1
  26. data/ext/imprimatur_native/core/src/pdf/layout.rs +72 -15
  27. data/ext/imprimatur_native/core/src/pdf/objects.rs +29 -5
  28. data/ext/imprimatur_native/core/src/pdf/picture.rs +197 -0
  29. data/ext/imprimatur_native/core/src/pdf/truetype/descriptor.rs +129 -0
  30. data/ext/imprimatur_native/core/src/pdf/truetype/program.rs +70 -0
  31. data/ext/imprimatur_native/core/src/pdf/truetype.rs +149 -0
  32. data/ext/imprimatur_native/core/src/pdf.rs +40 -4
  33. data/ext/imprimatur_native/core/src/read/content.rs +75 -2
  34. data/ext/imprimatur_native/core/src/read/image.rs +17 -0
  35. data/ext/imprimatur_native/core/src/read/json.rs +94 -7
  36. data/ext/imprimatur_native/core/src/read/numbers.rs +65 -42
  37. data/ext/imprimatur_native/core/src/read/ods.rs +1 -2
  38. data/ext/imprimatur_native/core/src/read/pdf/layout/bidi.rs +212 -0
  39. data/ext/imprimatur_native/core/src/read/pdf/layout.rs +25 -119
  40. data/ext/imprimatur_native/core/src/read/xls.rs +1 -1
  41. data/ext/imprimatur_native/core/src/read/xlsx/formats.rs +155 -0
  42. data/ext/imprimatur_native/core/src/read/xlsx.rs +104 -19
  43. data/ext/imprimatur_native/core/src/read.rs +5 -1
  44. data/ext/imprimatur_native/core/src/script.rs +116 -0
  45. data/ext/imprimatur_native/core/src/style.rs +32 -0
  46. data/ext/imprimatur_native/core/src/xlsx/autofit.rs +60 -0
  47. data/ext/imprimatur_native/core/src/xlsx/date.rs +7 -1
  48. data/ext/imprimatur_native/core/src/xlsx/format.rs +22 -6
  49. data/ext/imprimatur_native/core/src/xlsx/package.rs +22 -8
  50. data/ext/imprimatur_native/core/src/xlsx/styles.rs +9 -8
  51. data/ext/imprimatur_native/core/src/xlsx/worksheet.rs +35 -7
  52. data/ext/imprimatur_native/core/src/xlsx.rs +40 -3
  53. data/ext/imprimatur_native/core/src/xml/reader.rs +10 -0
  54. data/ext/imprimatur_native/ffi/Cargo.toml +3 -2
  55. data/ext/imprimatur_native/ffi/src/archive.rs +1 -3
  56. data/ext/imprimatur_native/ffi/src/barcode.rs +147 -0
  57. data/ext/imprimatur_native/ffi/src/document.rs +76 -1
  58. data/ext/imprimatur_native/ffi/src/lib.rs +40 -1
  59. data/ext/imprimatur_native/ffi/src/read.rs +8 -11
  60. data/ext/imprimatur_native/ffi/src/sheets.rs +114 -0
  61. data/lib/imprimatur/barcode.rb +78 -0
  62. data/lib/imprimatur/document.rb +46 -6
  63. data/lib/imprimatur/reader.rb +2 -2
  64. data/lib/imprimatur/spreadsheet.rb +151 -0
  65. data/lib/imprimatur/version.rb +1 -1
  66. data/lib/imprimatur/xlsx.rb +25 -4
  67. data/lib/imprimatur.rb +19 -3
  68. metadata +23 -2
  69. data/ext/imprimatur_native/core/src/test_support.rs +0 -250
data/lib/imprimatur.rb CHANGED
@@ -6,9 +6,11 @@ require_relative 'imprimatur/formula'
6
6
  require_relative 'imprimatur/native'
7
7
  require_relative 'imprimatur/style'
8
8
  require_relative 'imprimatur/validation'
9
+ require_relative 'imprimatur/barcode'
9
10
  require_relative 'imprimatur/xlsx'
10
11
  require_relative 'imprimatur/document'
11
12
  require_relative 'imprimatur/reader'
13
+ require_relative 'imprimatur/spreadsheet'
12
14
  require_relative 'imprimatur/zip'
13
15
 
14
16
  # Document authoring with a native core: XLSX workbooks, and PDF and DOCX from
@@ -25,7 +27,7 @@ require_relative 'imprimatur/zip'
25
27
  module Imprimatur
26
28
  class << self
27
29
  # @see Xlsx.build
28
- def xlsx(path = nil, &) = Xlsx.build(path, &)
30
+ def xlsx(path = nil, **, &) = Xlsx.build(path, **, &)
29
31
 
30
32
  # @param expression [String] such as +"SUM(A1:A3)"+ or +"=A1*2"+
31
33
  # @return [Formula]
@@ -37,6 +39,9 @@ module Imprimatur
37
39
  # @see Validation.build
38
40
  def validation(**) = Validation.build(**)
39
41
 
42
+ # @see Barcode.encode
43
+ def barcode(symbology, data, **) = Barcode.encode(symbology, data, **)
44
+
40
45
  # Builds a {Document} and renders it as PDF. With a path, saves it there
41
46
  # and returns the path; without, returns the bytes.
42
47
  #
@@ -78,18 +83,29 @@ module Imprimatur
78
83
  # (+heading+, +paragraph+) with their +:bounds+ in points from the
79
84
  # page's top-left corner; a workbook's +:sheets+ of +:rows+ of typed
80
85
  # +:cells+ (+number+, +date+, +bool+, +text+, +error+, +formula+) and
81
- # +:comments+; a Word document's +:blocks+ (+heading+, +paragraph+,
86
+ # +:comments+, and from an +.xlsx+ its +:columns+ widths and data
87
+ # +:validations+; a Word document's +:blocks+ (+heading+, +paragraph+,
82
88
  # +list_item+, +table+), their bold, italic and linked +:marks+ as byte
83
89
  # ranges. Dates are ISO 8601 strings: Excel's have no time zone.
84
90
  #
85
91
  # sheet = Imprimatur.content('sales.xlsx')[:sheets].first
86
92
  # sheet[:rows][1][:cells] # => [{cell: "A2", type: "text", value: "North"}, ...]
87
93
  #
88
- # Takes the arguments of {.markdown}, and raises as it does.
94
+ # Takes the arguments of {.markdown}, and raises as it does. With
95
+ # +styles: true+ each sheet also lists its formatted cells' +:styles+:
96
+ # font, size, bold, italic, underline, ARGB color and fill, alignment,
97
+ # wrap, border and number format.
89
98
  #
90
99
  # @return [Hash] with symbol keys; +:format+ is +"pdf"+, +"xlsx"+ or +"docx"+
91
100
  def content(document, ...) = Reader.content(document, ...)
92
101
 
102
+ # A workbook's sheets and their values, read from the same documents
103
+ # {.markdown} takes (+.xlsx+, +.xls+, +.ods+); see {Spreadsheet}.
104
+ #
105
+ # @return [Spreadsheet]
106
+ # @raise [UnsupportedDocument] a PDF, a Word document or another format
107
+ def spreadsheet(document, ...) = Spreadsheet.open(document, ...)
108
+
93
109
  # A document's images, as files: a PDF's, once each, from the first page
94
110
  # that draws them (JPEG and JPEG 2000 as stored, fax scans as TIFF, raw
95
111
  # pixels as PNG); a Word or Excel document's media. A scanned PDF gives
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imprimatur
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -52,11 +52,21 @@ files:
52
52
  - ext/imprimatur_native/core/LICENSE
53
53
  - ext/imprimatur_native/core/README.md
54
54
  - ext/imprimatur_native/core/src/archive.rs
55
+ - ext/imprimatur_native/core/src/barcode.rs
56
+ - ext/imprimatur_native/core/src/barcode/code128.rs
57
+ - ext/imprimatur_native/core/src/barcode/draw.rs
58
+ - ext/imprimatur_native/core/src/barcode/ean.rs
59
+ - ext/imprimatur_native/core/src/barcode/gs1.rs
60
+ - ext/imprimatur_native/core/src/barcode/matrix.rs
55
61
  - ext/imprimatur_native/core/src/doc.rs
56
62
  - ext/imprimatur_native/core/src/doc/paragraph.rs
63
+ - ext/imprimatur_native/core/src/doc/picture.rs
64
+ - ext/imprimatur_native/core/src/doc/symbol.rs
57
65
  - ext/imprimatur_native/core/src/doc/table.rs
58
66
  - ext/imprimatur_native/core/src/docx.rs
67
+ - ext/imprimatur_native/core/src/docx/picture.rs
59
68
  - ext/imprimatur_native/core/src/error.rs
69
+ - ext/imprimatur_native/core/src/font.rs
60
70
  - ext/imprimatur_native/core/src/lib.rs
61
71
  - ext/imprimatur_native/core/src/opc.rs
62
72
  - ext/imprimatur_native/core/src/pdf.rs
@@ -65,6 +75,10 @@ files:
65
75
  - ext/imprimatur_native/core/src/pdf/layout/wrap.rs
66
76
  - ext/imprimatur_native/core/src/pdf/metrics.rs
67
77
  - ext/imprimatur_native/core/src/pdf/objects.rs
78
+ - ext/imprimatur_native/core/src/pdf/picture.rs
79
+ - ext/imprimatur_native/core/src/pdf/truetype.rs
80
+ - ext/imprimatur_native/core/src/pdf/truetype/descriptor.rs
81
+ - ext/imprimatur_native/core/src/pdf/truetype/program.rs
68
82
  - ext/imprimatur_native/core/src/read.rs
69
83
  - ext/imprimatur_native/core/src/read/budget.rs
70
84
  - ext/imprimatur_native/core/src/read/content.rs
@@ -92,6 +106,7 @@ files:
92
106
  - ext/imprimatur_native/core/src/read/pdf/glyphs.txt
93
107
  - ext/imprimatur_native/core/src/read/pdf/images.rs
94
108
  - ext/imprimatur_native/core/src/read/pdf/layout.rs
109
+ - ext/imprimatur_native/core/src/read/pdf/layout/bidi.rs
95
110
  - ext/imprimatur_native/core/src/read/pdf/lexer.rs
96
111
  - ext/imprimatur_native/core/src/read/pdf/object.rs
97
112
  - ext/imprimatur_native/core/src/read/pdf/object/name.rs
@@ -100,9 +115,11 @@ files:
100
115
  - ext/imprimatur_native/core/src/read/xls.rs
101
116
  - ext/imprimatur_native/core/src/read/xls/strings.rs
102
117
  - ext/imprimatur_native/core/src/read/xlsx.rs
118
+ - ext/imprimatur_native/core/src/read/xlsx/formats.rs
119
+ - ext/imprimatur_native/core/src/script.rs
103
120
  - ext/imprimatur_native/core/src/style.rs
104
- - ext/imprimatur_native/core/src/test_support.rs
105
121
  - ext/imprimatur_native/core/src/xlsx.rs
122
+ - ext/imprimatur_native/core/src/xlsx/autofit.rs
106
123
  - ext/imprimatur_native/core/src/xlsx/cell.rs
107
124
  - ext/imprimatur_native/core/src/xlsx/comment.rs
108
125
  - ext/imprimatur_native/core/src/xlsx/date.rs
@@ -120,19 +137,23 @@ files:
120
137
  - ext/imprimatur_native/extconf.rb
121
138
  - ext/imprimatur_native/ffi/Cargo.toml
122
139
  - ext/imprimatur_native/ffi/src/archive.rs
140
+ - ext/imprimatur_native/ffi/src/barcode.rs
123
141
  - ext/imprimatur_native/ffi/src/blocking.rs
124
142
  - ext/imprimatur_native/ffi/src/convert.rs
125
143
  - ext/imprimatur_native/ffi/src/document.rs
126
144
  - ext/imprimatur_native/ffi/src/lib.rs
127
145
  - ext/imprimatur_native/ffi/src/read.rs
146
+ - ext/imprimatur_native/ffi/src/sheets.rs
128
147
  - ext/imprimatur_native/ffi/src/style.rs
129
148
  - ext/imprimatur_native/ffi/src/validation.rs
130
149
  - lib/imprimatur.rb
150
+ - lib/imprimatur/barcode.rb
131
151
  - lib/imprimatur/document.rb
132
152
  - lib/imprimatur/error.rb
133
153
  - lib/imprimatur/formula.rb
134
154
  - lib/imprimatur/native.rb
135
155
  - lib/imprimatur/reader.rb
156
+ - lib/imprimatur/spreadsheet.rb
136
157
  - lib/imprimatur/style.rb
137
158
  - lib/imprimatur/validation.rb
138
159
  - lib/imprimatur/version.rb
@@ -1,250 +0,0 @@
1
- //! Helpers shared by the unit tests.
2
-
3
- use std::io::{Cursor, Read};
4
-
5
- /// Every entry of a zip archive as `(name, bytes)`, read with the `zip`
6
- /// crate: an implementation independent of ours, which verifies each CRC.
7
- pub fn read_zip(bytes: Vec<u8>) -> Vec<(String, Vec<u8>)> {
8
- let mut archive = ::zip::ZipArchive::new(Cursor::new(bytes)).unwrap();
9
- (0..archive.len())
10
- .map(|index| {
11
- let mut file = archive.by_index(index).unwrap();
12
- let mut data = Vec::new();
13
- file.read_to_end(&mut data).unwrap();
14
- (file.name().unwrap().into_owned(), data)
15
- })
16
- .collect()
17
- }
18
-
19
- /// Every entry of a zip archive as text, by name, read as [`read_zip`] does.
20
- pub fn text_parts(bytes: Vec<u8>) -> Vec<(String, String)> {
21
- read_zip(bytes)
22
- .into_iter()
23
- .map(|(name, data)| (name, String::from_utf8(data).unwrap()))
24
- .collect()
25
- }
26
-
27
- /// The text of the part `name` among `parts`.
28
- pub fn part<'a>(parts: &'a [(String, String)], name: &str) -> &'a str {
29
- parts
30
- .iter()
31
- .find(|(part, _)| part == name)
32
- .map_or_else(|| panic!("no part {name}"), |(_, xml)| xml)
33
- }
34
-
35
- /// A zip of `(name, text)` parts, written with our own writer.
36
- pub fn zip_of(parts: &[(&str, &str)]) -> Vec<u8> {
37
- let mut zip = crate::zip::ZipWriter::new(Vec::new());
38
- for (name, text) in parts {
39
- zip.add(name, text.as_bytes()).unwrap();
40
- }
41
- zip.finish().unwrap()
42
- }
43
-
44
- /// A PDF of `objects`, numbered from 1 (the first is the catalog), with a
45
- /// classic cross-reference table.
46
- pub fn pdf_of(objects: &[Vec<u8>]) -> Vec<u8> {
47
- use std::io::Write as _;
48
- let mut out = b"%PDF-1.7\n%\xE2\xE3\xCF\xD3\n".to_vec();
49
- let mut offsets = Vec::new();
50
- for (number, body) in (1..).zip(objects) {
51
- offsets.push(out.len());
52
- writeln!(out, "{number} 0 obj").unwrap();
53
- out.extend_from_slice(body);
54
- out.extend_from_slice(b"\nendobj\n");
55
- }
56
- let xref = out.len();
57
- write!(out, "xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).unwrap();
58
- for offset in offsets {
59
- writeln!(out, "{offset:010} 00000 n ").unwrap();
60
- }
61
- write!(
62
- out,
63
- "trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n",
64
- objects.len() + 1
65
- )
66
- .unwrap();
67
- out
68
- }
69
-
70
- /// A stream object: `dict` holds the entries besides `/Length`.
71
- pub fn stream_object(dict: &str, data: &[u8]) -> Vec<u8> {
72
- let mut out = format!("<< /Length {} {dict} >>\nstream\n", data.len()).into_bytes();
73
- out.extend_from_slice(data);
74
- out.extend_from_slice(b"\nendstream");
75
- out
76
- }
77
-
78
- /// One page of `content` with Helvetica as `/F1` and a 612 by 792 page.
79
- pub fn page_pdf(content: &str, page_extra: &str) -> Vec<u8> {
80
- pdf_of(&[
81
- b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
82
- b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
83
- format!(
84
- "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] \
85
- /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R {page_extra} >>"
86
- )
87
- .into_bytes(),
88
- b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>"
89
- .to_vec(),
90
- stream_object("", content.as_bytes()),
91
- ])
92
- }
93
-
94
- /// A PNG as `read::image::png` writes it, read back.
95
- #[derive(Debug)]
96
- pub struct Png {
97
- pub width: u32,
98
- pub height: u32,
99
- pub bits: u8,
100
- pub color: u8,
101
- pub palette: Option<Vec<u8>>,
102
- /// The rows, their filter bytes (always 0, none) taken out.
103
- pub rows: Vec<u8>,
104
- }
105
-
106
- /// Reads a PNG, checking each chunk's CRC and that every row is unfiltered.
107
- pub fn png_of(data: &[u8]) -> Png {
108
- let be = |at: usize| u32::from_be_bytes(data[at..at + 4].try_into().unwrap());
109
- assert!(data.starts_with(b"\x89PNG\r\n\x1a\n"), "{data:?}");
110
- let (mut header, mut palette, mut compressed) = (Vec::new(), None, Vec::new());
111
- let mut at = 8;
112
- while at < data.len() {
113
- let length = be(at) as usize;
114
- let (kind, body) = (&data[at + 4..at + 8], &data[at + 8..at + 8 + length]);
115
- assert_eq!(
116
- be(at + 8 + length),
117
- crc32fast::hash(&data[at + 4..at + 8 + length]),
118
- "{kind:?}"
119
- );
120
- match kind {
121
- b"IHDR" => header = body.to_vec(),
122
- b"PLTE" => palette = Some(body.to_vec()),
123
- b"IDAT" => compressed.extend_from_slice(body),
124
- _ => {}
125
- }
126
- at += 12 + length;
127
- }
128
- let (width, height) = (be(16), be(20));
129
- let (bits, color) = (header[8], header[9]);
130
- let channels = match color {
131
- 0 | 3 => 1,
132
- 4 => 2,
133
- 2 => 3,
134
- _ => 4,
135
- };
136
- let stride = (width as usize * channels * usize::from(bits)).div_ceil(8);
137
- let filtered = miniz_oxide::inflate::decompress_to_vec_zlib(&compressed).unwrap();
138
- assert_eq!(filtered.len(), (stride + 1) * height as usize);
139
- let mut rows = Vec::new();
140
- for row in filtered.chunks(stride + 1) {
141
- assert_eq!(row[0], 0, "filter type");
142
- rows.extend_from_slice(&row[1..]);
143
- }
144
- Png { width, height, bits, color, palette, rows }
145
- }
146
-
147
- /// A one-strip little-endian TIFF's tags, by number, and its strip.
148
- pub fn tiff_of(data: &[u8]) -> (std::collections::BTreeMap<u16, u32>, &[u8]) {
149
- let le16 = |at: usize| u16::from_le_bytes(data[at..at + 2].try_into().unwrap());
150
- let le32 = |at: usize| u32::from_le_bytes(data[at..at + 4].try_into().unwrap());
151
- assert!(data.starts_with(b"II*\0"));
152
- let directory = le32(4) as usize;
153
- let tags: std::collections::BTreeMap<u16, u32> = (0..usize::from(le16(directory)))
154
- .map(|index| {
155
- let entry = directory + 2 + index * 12;
156
- let value =
157
- if le16(entry + 2) == 3 { u32::from(le16(entry + 8)) } else { le32(entry + 8) };
158
- (le16(entry), value)
159
- })
160
- .collect();
161
- let (offset, length) = (tags[&273] as usize, tags[&279] as usize);
162
- (tags, &data[offset..offset + length])
163
- }
164
-
165
- /// A compound file (MS-CFB, version 3) holding one stream named `name` in
166
- /// its root storage; a stream under 4096 bytes goes to the mini stream, as
167
- /// writers put it.
168
- pub fn ole_of(name: &str, stream: &[u8]) -> Vec<u8> {
169
- const SECTOR: usize = 512;
170
- const END: u32 = 0xFFFF_FFFE;
171
- let small = stream.len() < 4096;
172
- let pad = |data: &[u8], size: usize| {
173
- let mut data = data.to_vec();
174
- data.resize(data.len().div_ceil(size).max(1) * size, 0);
175
- data
176
- };
177
- // Sectors: FAT, directory, mini FAT (when small), then the data: the
178
- // stream itself, or the mini stream holding it.
179
- let data = if small { pad(stream, 64) } else { stream.to_vec() };
180
- let data_sectors = data.len().div_ceil(SECTOR).max(1);
181
- let first_data = if small { 3 } else { 2 };
182
- let mut fat = vec![0xFFFF_FFFD_u32, END];
183
- if small {
184
- fat.push(END);
185
- }
186
- fat.extend((0..data_sectors).map(|index| {
187
- if index + 1 == data_sectors { END } else { u32::try_from(first_data + index + 1).unwrap() }
188
- }));
189
- fat.resize(SECTOR / 4, 0xFFFF_FFFF);
190
- let mut header = vec![0_u8; SECTOR];
191
- header[..8].copy_from_slice(&[0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1]);
192
- let put = |bytes: &mut Vec<u8>, at: usize, value: u32| {
193
- bytes[at..at + 4].copy_from_slice(&value.to_le_bytes());
194
- };
195
- header[0x18..0x1A].copy_from_slice(&0x3E_u16.to_le_bytes());
196
- header[0x1A..0x1C].copy_from_slice(&3_u16.to_le_bytes());
197
- header[0x1C..0x1E].copy_from_slice(&0xFFFE_u16.to_le_bytes());
198
- header[0x1E..0x20].copy_from_slice(&9_u16.to_le_bytes());
199
- header[0x20..0x22].copy_from_slice(&6_u16.to_le_bytes());
200
- put(&mut header, 0x2C, 1);
201
- put(&mut header, 0x30, 1);
202
- put(&mut header, 0x38, 4096);
203
- put(&mut header, 0x3C, if small { 2 } else { END });
204
- put(&mut header, 0x40, u32::from(small));
205
- put(&mut header, 0x44, END);
206
- put(&mut header, 0x4C, 0);
207
- for index in 1..109 {
208
- put(&mut header, 0x4C + index * 4, 0xFFFF_FFFF);
209
- }
210
- let entry = |name: &str, kind: u8, child: u32, start: u32, size: usize| {
211
- let mut bytes = vec![0_u8; 128];
212
- let units: Vec<u16> = name.encode_utf16().collect();
213
- for (index, unit) in units.iter().enumerate() {
214
- bytes[index * 2..index * 2 + 2].copy_from_slice(&unit.to_le_bytes());
215
- }
216
- bytes[0x40..0x42]
217
- .copy_from_slice(&u16::try_from(units.len() * 2 + 2).unwrap().to_le_bytes());
218
- bytes[0x42] = kind;
219
- for at in [0x44, 0x48, 0x4C] {
220
- put(&mut bytes, at, 0xFFFF_FFFF);
221
- }
222
- put(&mut bytes, 0x4C, child);
223
- put(&mut bytes, 0x74, start);
224
- put(&mut bytes, 0x78, u32::try_from(size).unwrap());
225
- bytes
226
- };
227
- let first = u32::try_from(first_data).unwrap();
228
- let mut directory = if small {
229
- let mut directory = entry("Root Entry", 5, 1, first, data.len());
230
- directory.extend(entry(name, 2, 0xFFFF_FFFF, 0, stream.len()));
231
- directory
232
- } else {
233
- let mut directory = entry("Root Entry", 5, 1, END, 0);
234
- directory.extend(entry(name, 2, 0xFFFF_FFFF, first, stream.len()));
235
- directory
236
- };
237
- directory.resize(SECTOR, 0);
238
- let mut file = header;
239
- file.extend(fat.iter().flat_map(|word| word.to_le_bytes()));
240
- file.extend(directory);
241
- if small {
242
- let mut mini_fat: Vec<u32> =
243
- (1..data.len() / 64).map(|next| u32::try_from(next).unwrap()).collect();
244
- mini_fat.push(END);
245
- mini_fat.resize(SECTOR / 4, 0xFFFF_FFFF);
246
- file.extend(mini_fat.iter().flat_map(|word| word.to_le_bytes()));
247
- }
248
- file.extend(pad(&data, SECTOR));
249
- file
250
- }