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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90d5145594d31e77fcde4acfeeecfb69156a5aa449740a54a6435e4f36d34595
4
- data.tar.gz: '093e50e8ddca274d55841ee9a4b74f21b4061a6f16fc470cfd0985900eb96ec6'
3
+ metadata.gz: 4b0c00d27618298889f1419aec02a69a9de179fb9f3caaa5f33e57db05a68a9b
4
+ data.tar.gz: f8c77e28a3f3ada14611260a7f1ffffc830ba3f6e0ec6d0675b4c8177b391f72
5
5
  SHA512:
6
- metadata.gz: 6b9c5b392e20dc7d35c3e10de40f44af8b2664d7df6b7a152e0f3740c58354ed337ee012f675bde18c217e26c0586a3d952bc159970f29871b67a64366728610
7
- data.tar.gz: bf43e139d43a48ed9910e152d9f98d5542a990da7131a61628bb320be40a7d10bee79992f11544735fddc54253c08b0031f4ab0bada9c397248f9b5c7eaf6f17
6
+ metadata.gz: 4780e0a1754d6b1df1562910bf63f1cf70b2d7c5750c1d04e6518898408eded1d13ced430564a966f3980fa1b5db0af131aba34db66de50ca0ec19cd4db1c827
7
+ data.tar.gz: a4a2dde579ad49d425a2e22e39907cf35041061d245b41c8cde6f1abd3c2abdcda1c0e3c28bafcbdfe6a2bf55eab5a5adc7e30750af5fcbb1739216d5c2708d6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/seuros/imprimatur/compare/v0.3.0...v0.4.0) (2026-10-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * barcodes: QR Code, Data Matrix, EAN-13, EAN-8, UPC-A and Code 128, GS1 forms included, as SVG, PNG and in documents ([ade7b68](https://github.com/seuros/imprimatur/commit/ade7b68f4c5adcab9d2ee0b935288eea7ec2eea6))
9
+ * **pdf:** add embedded TrueType font resources ([c0e3be4](https://github.com/seuros/imprimatur/commit/c0e3be4ef46ede94fcc840840e82d8ccb6679f3e))
10
+ * **pdf:** add embedded TrueType font resources ([313075f](https://github.com/seuros/imprimatur/commit/313075fe8ba1bedc9f4156060aeedcbb0e0a8bef))
11
+
12
+ ## [0.3.0](https://github.com/seuros/imprimatur/compare/v0.2.0...v0.3.0) (2026-10-17)
13
+
14
+
15
+ ### Features
16
+
17
+ * a spreadsheet's sheets give their comments, columns, validations and styles ([1797632](https://github.com/seuros/imprimatur/commit/1797632aad2948c1c0a493501de3684187062246))
18
+ * draw PNG and JPEG pictures, on pages of any size ([419524a](https://github.com/seuros/imprimatur/commit/419524afe36bbb06e3ef0ecb48a4fab4f80fb989))
19
+ * **font:** load static TrueType fonts and metrics ([fd85e3d](https://github.com/seuros/imprimatur/commit/fd85e3d78ece99506b2dde2c61bab6997cb20d21))
20
+ * read column widths, data validations and cell styles ([2930964](https://github.com/seuros/imprimatur/commit/2930964cde82cc998113aa83336a547cda8bd9f8))
21
+ * read whether a list validation shows its drop-down ([b75ef53](https://github.com/seuros/imprimatur/commit/b75ef530ef790876c58e40bb02dfc7257a48aab5))
22
+ * read workbooks as typed rows with Imprimatur::Spreadsheet ([927033b](https://github.com/seuros/imprimatur/commit/927033bb5ef9725c38726bc0785bbd90be8f1dc8))
23
+ * size columns to their cells, and give workbooks a font ([c88a058](https://github.com/seuros/imprimatur/commit/c88a0580a7ba052c4994faf4f53b42e75f48f587))
24
+ * write right-to-left text in its own direction ([137206e](https://github.com/seuros/imprimatur/commit/137206e08622f33ae21b6e52013e3ec7b071bb6b))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * build the gem with every feature of the crate ([7b48599](https://github.com/seuros/imprimatur/commit/7b485998abdb8b14b59e22a6ff97ca7dec143986))
30
+ * **font:** put the TrueType loader behind a `fonts` feature ([f9d1915](https://github.com/seuros/imprimatur/commit/f9d1915f3c98c0c7351477c6e8f21799d01e02f9))
31
+ * read right-to-left PDF text in its page's direction ([ca173ef](https://github.com/seuros/imprimatur/commit/ca173efd019513f25df004feafeda0e8551e64c0))
32
+ * show numbers with their format's decimals and grouping ([d0089ee](https://github.com/seuros/imprimatur/commit/d0089ee2dcf364ac32c7f92dc6c3af9aa6a9e9cb))
33
+
3
34
  ## [0.2.0](https://github.com/seuros/imprimatur/compare/v0.1.0...v0.2.0) (2026-10-16)
4
35
 
5
36
 
data/Cargo.lock CHANGED
@@ -28,6 +28,12 @@ dependencies = [
28
28
  "memchr",
29
29
  ]
30
30
 
31
+ [[package]]
32
+ name = "arrayvec"
33
+ version = "0.7.8"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
36
+
31
37
  [[package]]
32
38
  name = "autocfg"
33
39
  version = "1.5.1"
@@ -67,6 +73,26 @@ dependencies = [
67
73
  "hybrid-array",
68
74
  ]
69
75
 
76
+ [[package]]
77
+ name = "bytemuck"
78
+ version = "1.25.2"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
81
+ dependencies = [
82
+ "bytemuck_derive",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "bytemuck_derive"
87
+ version = "1.12.1"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
90
+ dependencies = [
91
+ "proc-macro2",
92
+ "quote",
93
+ "syn 3.0.9",
94
+ ]
95
+
70
96
  [[package]]
71
97
  name = "cexpr"
72
98
  version = "0.6.0"
@@ -148,6 +174,16 @@ dependencies = [
148
174
  "hybrid-array",
149
175
  ]
150
176
 
177
+ [[package]]
178
+ name = "datamatrix"
179
+ version = "0.3.3"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "b2d3882a013aca40e2e24f0e454ef7a581a50c603d67eded130013ab641a289d"
182
+ dependencies = [
183
+ "arrayvec",
184
+ "flagset",
185
+ ]
186
+
151
187
  [[package]]
152
188
  name = "digest"
153
189
  version = "0.11.3"
@@ -187,6 +223,12 @@ version = "2.5.0"
187
223
  source = "registry+https://github.com/rust-lang/crates.io-index"
188
224
  checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
189
225
 
226
+ [[package]]
227
+ name = "flagset"
228
+ version = "0.4.7"
229
+ source = "registry+https://github.com/rust-lang/crates.io-index"
230
+ checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
231
+
190
232
  [[package]]
191
233
  name = "flate2"
192
234
  version = "1.1.10"
@@ -196,6 +238,30 @@ dependencies = [
196
238
  "zlib-rs",
197
239
  ]
198
240
 
241
+ [[package]]
242
+ name = "foldhash"
243
+ version = "0.2.0"
244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
245
+ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
246
+
247
+ [[package]]
248
+ name = "font-test-data"
249
+ version = "0.10.0"
250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
251
+ checksum = "9b7088ff167a44404649ff2221f3ba67c9eeff076cfe026c1308f54e25702a81"
252
+ dependencies = [
253
+ "font-types",
254
+ ]
255
+
256
+ [[package]]
257
+ name = "font-types"
258
+ version = "0.12.7"
259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
260
+ checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d"
261
+ dependencies = [
262
+ "bytemuck",
263
+ ]
264
+
199
265
  [[package]]
200
266
  name = "getrandom"
201
267
  version = "0.4.3"
@@ -230,12 +296,16 @@ dependencies = [
230
296
 
231
297
  [[package]]
232
298
  name = "imprimatur"
233
- version = "0.2.0"
299
+ version = "0.4.0"
234
300
  dependencies = [
235
301
  "aes",
236
302
  "crc32fast",
303
+ "datamatrix",
304
+ "font-test-data",
237
305
  "md-5",
238
306
  "miniz_oxide",
307
+ "qrcode",
308
+ "read-fonts",
239
309
  "serde_json",
240
310
  "sha2",
241
311
  "tempfile",
@@ -245,7 +315,7 @@ dependencies = [
245
315
 
246
316
  [[package]]
247
317
  name = "imprimatur-cli"
248
- version = "0.2.0"
318
+ version = "0.4.0"
249
319
  dependencies = [
250
320
  "imprimatur",
251
321
  "regex",
@@ -257,7 +327,7 @@ dependencies = [
257
327
 
258
328
  [[package]]
259
329
  name = "imprimatur_native"
260
- version = "0.2.0"
330
+ version = "0.4.0"
261
331
  dependencies = [
262
332
  "imprimatur",
263
333
  "magnus",
@@ -455,6 +525,12 @@ dependencies = [
455
525
  "unicase",
456
526
  ]
457
527
 
528
+ [[package]]
529
+ name = "qrcode"
530
+ version = "0.14.1"
531
+ source = "registry+https://github.com/rust-lang/crates.io-index"
532
+ checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
533
+
458
534
  [[package]]
459
535
  name = "quote"
460
536
  version = "1.0.47"
@@ -500,6 +576,19 @@ version = "0.2.3"
500
576
  source = "registry+https://github.com/rust-lang/crates.io-index"
501
577
  checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
502
578
 
579
+ [[package]]
580
+ name = "read-fonts"
581
+ version = "0.45.1"
582
+ source = "registry+https://github.com/rust-lang/crates.io-index"
583
+ checksum = "4edc8db0d972de88e53a085b1f01fb3fbdae7bb0cb7a0ce2b2f8285a412fe3ef"
584
+ dependencies = [
585
+ "bytemuck",
586
+ "foldhash",
587
+ "font-types",
588
+ "once_cell",
589
+ "yoke",
590
+ ]
591
+
503
592
  [[package]]
504
593
  name = "regex"
505
594
  version = "1.13.1"
@@ -632,6 +721,12 @@ version = "1.3.0"
632
721
  source = "registry+https://github.com/rust-lang/crates.io-index"
633
722
  checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
634
723
 
724
+ [[package]]
725
+ name = "stable_deref_trait"
726
+ version = "1.2.1"
727
+ source = "registry+https://github.com/rust-lang/crates.io-index"
728
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
729
+
635
730
  [[package]]
636
731
  name = "syn"
637
732
  version = "2.0.119"
@@ -654,6 +749,17 @@ dependencies = [
654
749
  "unicode-ident",
655
750
  ]
656
751
 
752
+ [[package]]
753
+ name = "synstructure"
754
+ version = "0.14.0"
755
+ source = "registry+https://github.com/rust-lang/crates.io-index"
756
+ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
757
+ dependencies = [
758
+ "proc-macro2",
759
+ "quote",
760
+ "syn 3.0.9",
761
+ ]
762
+
657
763
  [[package]]
658
764
  name = "tempfile"
659
765
  version = "3.27.0"
@@ -809,6 +915,50 @@ dependencies = [
809
915
  "memchr",
810
916
  ]
811
917
 
918
+ [[package]]
919
+ name = "yoke"
920
+ version = "0.8.3"
921
+ source = "registry+https://github.com/rust-lang/crates.io-index"
922
+ checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
923
+ dependencies = [
924
+ "stable_deref_trait",
925
+ "yoke-derive",
926
+ "zerofrom",
927
+ ]
928
+
929
+ [[package]]
930
+ name = "yoke-derive"
931
+ version = "0.8.4"
932
+ source = "registry+https://github.com/rust-lang/crates.io-index"
933
+ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
934
+ dependencies = [
935
+ "proc-macro2",
936
+ "quote",
937
+ "syn 3.0.9",
938
+ "synstructure",
939
+ ]
940
+
941
+ [[package]]
942
+ name = "zerofrom"
943
+ version = "0.1.8"
944
+ source = "registry+https://github.com/rust-lang/crates.io-index"
945
+ checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
946
+ dependencies = [
947
+ "zerofrom-derive",
948
+ ]
949
+
950
+ [[package]]
951
+ name = "zerofrom-derive"
952
+ version = "0.1.8"
953
+ source = "registry+https://github.com/rust-lang/crates.io-index"
954
+ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
955
+ dependencies = [
956
+ "proc-macro2",
957
+ "quote",
958
+ "syn 3.0.9",
959
+ "synstructure",
960
+ ]
961
+
812
962
  [[package]]
813
963
  name = "zip"
814
964
  version = "9.0.3"
data/Cargo.toml CHANGED
@@ -10,7 +10,7 @@ license = "BSD-3-Clause"
10
10
  repository = "https://github.com/seuros/imprimatur"
11
11
 
12
12
  [workspace.dependencies]
13
- imprimatur = { path = "ext/imprimatur_native/core", version = "0.2.0" }
13
+ imprimatur = { path = "ext/imprimatur_native/core", version = "0.4.0" }
14
14
  magnus = { version = "0.9.2", default-features = false }
15
15
  # For rb_thread_call_without_gvl2, which magnus does not wrap; magnus's own
16
16
  # rb-sys, features and all.
data/README.md CHANGED
@@ -9,7 +9,8 @@ a 40,000-character cell is refused at the door, not discovered by Excel's
9
9
  as `imprimatur` on crates.io; the gem wraps it.
10
10
  - **No third-party format stack.** The XML writer and the zip container are
11
11
  written here, from ECMA-376 and the zip specification; only deflate
12
- (`miniz_oxide`) and CRC-32 (`crc32fast`) come from crates.
12
+ (`miniz_oxide`) and CRC-32 (`crc32fast`) come from crates, and QR Code and
13
+ Data Matrix encoding (`qrcode`, `datamatrix`) for barcodes.
13
14
  - **Deterministic.** The same document always gives the same bytes.
14
15
  - **Native only.** CRuby 4.0+. No pure Ruby fallback, no JRuby, no TruffleRuby.
15
16
 
@@ -102,6 +103,21 @@ columns past its end are left unstyled:
102
103
  sheet.row "", "", "", "Notes", style: [yellow] * 3
103
104
  ```
104
105
 
106
+ The workbook can have a font of its own, for unstyled cells and for styles
107
+ that name no font or size, and size its columns to their cells:
108
+
109
+ ```ruby
110
+ Imprimatur.xlsx("export.xlsx", font: "Arial", size: 10, autofit: true) do |book|
111
+ book.sheet("Orders") { it.row "Supplier", "Amount", style: header }
112
+ book.sheet("Notes").autofit!(false) # this one keeps Excel's widths
113
+ end
114
+ ```
115
+
116
+ `autofit` (or `sheet.autofit!`) sizes each column without a width of its own
117
+ to its widest cell, estimated as `(characters + 3) × points / 10`,
118
+ bold cells one and a half times wider, at most 255. A text counts its longest
119
+ line, a date `yyyy-mm-dd`; formulas are left out, their results unknown.
120
+
105
121
  ## Validation and comments
106
122
 
107
123
  ```ruby
@@ -137,22 +153,23 @@ shown all the time with `visible: true`. The cell need not hold a value.
137
153
  ```ruby
138
154
  rtl = Imprimatur.style(direction: :rtl)
139
155
 
140
- book.sheet("مبيعات") do |sheet| # Arabic
141
- sheet.right_to_left! # column A on the right
142
- sheet.row "المنطقة", "الشمال", style: rtl
156
+ book.sheet("مبيعات") do |sheet| # Arabic: right to left
157
+ sheet.row "المنطقة", "الشمال", style: rtl # column A on the right
143
158
  end
144
159
 
145
- book.sheet("מכירות") do |sheet| # Hebrew
146
- sheet.right_to_left!
147
- sheet.row "אזור", "צפון", style: rtl
160
+ book.sheet("Suppliers") do |sheet|
161
+ sheet.right_to_left! # told so: a Hebrew layout
162
+ sheet.row "אזור", "Region"
148
163
  end
149
164
  ```
150
165
 
151
166
  Text in any right-to-left language needs nothing special: Hebrew, Yiddish,
152
167
  Arabic, Persian, Urdu, Pashto, Sindhi, Kurdish (Sorani), Uyghur, Syriac,
153
- Dhivehi (Thaana), N'Ko, Adlam and the rest. `right_to_left!` lays the sheet
154
- out the way Excel does for them, and `direction: :rtl` fixes a cell's reading
155
- order instead of letting Excel infer it from its first letter.
168
+ Dhivehi (Thaana), N'Ko, Adlam and the rest. A sheet whose letters (its name
169
+ and text cells) are mostly right to left is laid out the way Excel does for
170
+ them, column A on the right; `right_to_left!` and `right_to_left!(false)`
171
+ decide instead. `direction: :rtl` fixes a cell's reading order instead of
172
+ letting Excel infer it from its first letter.
156
173
 
157
174
  Not yet: merged cells, frozen panes, streaming for very large sheets.
158
175
 
@@ -171,6 +188,7 @@ doc.paragraph do |p|
171
188
  end
172
189
  doc.paragraph "Figures are unaudited.", italic: true, align: :center
173
190
  doc.table [["Region", "Q1"], ["North", 12_500.5]], header: true
191
+ doc.image "chart.png", width: 300, align: :center
174
192
  doc.page_break
175
193
  doc.paragraph "Appendix."
176
194
 
@@ -178,15 +196,26 @@ doc.save_pdf("report.pdf")
178
196
  doc.save_docx("report.docx")
179
197
 
180
198
  Imprimatur.pdf("memo.pdf") { it.paragraph "Or in one go." } # Imprimatur.docx alike
199
+ Imprimatur.pdf(page_size: [200, 200], margin: 10) { it.image qr_png, width: 180 }
181
200
  ```
182
201
 
183
202
  - Headings 1 to 3 (real Word heading styles in DOCX), paragraphs of runs
184
203
  (bold, italic, underline, color), alignment, `\n` line breaks and tabs.
185
204
  - Tables: equal columns, borders, a shaded header row that repeats on every
186
205
  page; cells are converted with `to_s`.
187
- - Page breaks, A4 or Letter, `1 / N` page numbers.
188
- - `direction: :rtl` makes a right-to-left paragraph (right aligned, and
189
- `w:bidi` in DOCX).
206
+ - Pictures: PNG or JPEG (a path, the bytes or an IO), at `width:` or
207
+ `height:` points (the other follows), or else at 96 pixels an inch, never
208
+ larger than the page; `align:` like a paragraph. In PDF a JPEG and an
209
+ opaque PNG are stored as they are, a PNG with alpha gets a soft mask.
210
+ Interlaced PNGs are refused.
211
+ - Page breaks; A4, Letter or `page_size: [width, height]` in points (36 to
212
+ 1584); `margin:` in points (one inch unless set); `1 / N` page numbers.
213
+ - Right to left without being told: a paragraph, heading or table cell
214
+ reads in the direction of its first letter (right aligned, and `w:bidi` in
215
+ DOCX), figures alone in their table's or document's, each run in its own;
216
+ a mostly right-to-left table has its first column on the right, and a
217
+ mostly right-to-left document its pages laid out right to left.
218
+ `direction: :rtl` or `:ltr` decides for a paragraph.
190
219
 
191
220
  **PDF text is Latin only for now.** PDFs use the standard Helvetica fonts,
192
221
  which carry Windows-1252: English and Western European text, curly quotes,
@@ -195,6 +224,45 @@ dashes, the euro sign. Anything else (Arabic, Hebrew, Greek, CJK, emoji) raises
195
224
  script. Embedded TrueType fonts, and with them every script and proper RTL in
196
225
  PDF, are the next step.
197
226
 
227
+ ## Barcodes
228
+
229
+ QR Code, Data Matrix, EAN-13, EAN-8, UPC-A and Code 128, and the GS1 forms of
230
+ QR Code, Data Matrix and Code 128 (GS1-128):
231
+
232
+ ```ruby
233
+ qr = Imprimatur.barcode(:qr, "https://id.gs1.org/01/09506000134352", level: :m)
234
+ File.write("qr.svg", qr.svg(module_size: 4))
235
+ File.binwrite("qr.png", qr.png(module_size: 8, background: nil)) # transparent
236
+
237
+ ean = Imprimatur.barcode(:ean13, "950600013435")
238
+ ean.text # => "9506000134352", the check digit added (or checked when given)
239
+
240
+ label = Imprimatur.barcode(:code128, "(00)106141412345678908", gs1: true) # GS1-128
241
+ dpp = Imprimatur.barcode(:datamatrix, "(01)09506000134352(21)ABC123", gs1: true)
242
+
243
+ Imprimatur.pdf("label.pdf", page_size: [300, 200], margin: 10) do |doc|
244
+ doc.barcode label, width: 280
245
+ doc.barcode dpp, width: 72, align: :right
246
+ end
247
+ ```
248
+
249
+ - `:qr` (`level:` `:l`, `:m`, `:q` or `:h`), `:datamatrix` (square),
250
+ `:ean13`, `:ean8`, `:upca` and `:code128`; `gs1: true` for the GS1 forms of
251
+ `:qr`, `:datamatrix` and `:code128`.
252
+ - GS1 element strings are written as people write them, AIs in parentheses,
253
+ and checked: AI syntax, GS1's character set, the AIs whose lengths are
254
+ predefined, and the check digits of SSCCs, GTINs and GLNs. Separators go in
255
+ only where a variable-length field needs one.
256
+ - `svg` and `png` take `module_size:`, `color:`, `background:` (nil for
257
+ none), `quiet_zone:` and, for linear codes, `bar_height:` in modules. PNGs
258
+ are one bit a pixel.
259
+ - In documents, barcodes are vector shapes in a PDF and a 300 dpi picture in
260
+ DOCX, at `width:` or `height:` points or else at their usual module size
261
+ (0.33 mm for EAN and UPC, 0.495 mm for GS1-128, 0.7 mm for QR Code and Data
262
+ Matrix), quiet zone included.
263
+ - `text`, `symbology`, `gs1?`, `width`, `height` (in modules), and `modules`
264
+ (rows of booleans) for drawing them some other way.
265
+
198
266
  ## Reading: documents as Markdown
199
267
 
200
268
  The other direction: a PDF, a workbook (XLSX, legacy XLS, ODS) or a DOCX
@@ -213,14 +281,22 @@ end
213
281
  ```
214
282
 
215
283
  - **PDF**: the text layer, placed glyph by glyph (fonts, encodings and
216
- ToUnicode maps, forms, rotated pages), in reading order (right-to-left
217
- lines too: Hebrew, Arabic, Persian, Urdu, Syriac, Dhivehi, N'Ko, Adlam,
218
- their vowel marks kept on their letters), with headings by font size,
219
- hyphenated line breaks rejoined, presentation forms (`fi`, Arabic contextual forms) as plain letters, and
220
- running headers and footers left out. Pages after the first start with
221
- `<!-- page N -->`. Encrypted PDFs open when the user password is empty, as
222
- it is for documents that only restrict printing or copying (RC4, AES-128,
223
- AES-256).
284
+ ToUnicode maps, forms, rotated pages), in reading order, with headings by
285
+ font size, hyphenated line breaks rejoined, presentation forms (`fi`,
286
+ Arabic contextual forms) as plain letters, and running headers and
287
+ footers left out. Pages after the first start with `<!-- page N -->`.
288
+ Encrypted PDFs open when the user password is empty, as it is for
289
+ documents that only restrict printing or copying (RC4, AES-128, AES-256).
290
+ - **Right to left in PDFs** (Hebrew, Arabic, Persian, Urdu, Syriac,
291
+ Dhivehi, N'Ko, Adlam): a PDF records where glyphs go, not the order they
292
+ read in, so each line is put back in order the way the Unicode
293
+ bidirectional algorithm laid it out, with its page's direction for
294
+ context. An Arabic page's table rows read from the right, figures
295
+ included; an Arabic or Hebrew name in an English line reads forwards
296
+ inside that line; numbers stay whole (`2024-03-15`, `1,250.50`, `50%`,
297
+ `+98 21 5555 1234`, Arabic-Indic and Persian digits); vowel marks stay on
298
+ their letters. Not yet: pages set in several columns, whose lines run
299
+ together whatever their direction.
224
300
  - **XLSX**: each sheet as a table under its name, values as Excel shows them
225
301
  (dates as dates, percentages, 15 significant digits), formulas without a
226
302
  cached result as `=FORMULA`, then the sheet's comments.
@@ -261,6 +337,15 @@ Imprimatur.content("audit.pdf")[:pages].first[:blocks].first
261
337
  # bounds: {left: 72.0, top: 61.4, right: 290.1, bottom: 85.0}}
262
338
  ```
263
339
 
340
+ An `.xlsx` sheet also gives its `columns` widths and data `validations`, and
341
+ with `styles: true` the look of each formatted cell, empty ones included:
342
+
343
+ ```ruby
344
+ sheet = Imprimatur.content("template.xlsx", styles: true)[:sheets].first
345
+ sheet[:validations].first # => {range: "G2:G1000", type: "list", formula1: "\"Yes,No\"", ...}
346
+ sheet[:styles].first # => {cell: "A1", font: "Arial", size: 12.0, bold: true, fill: "FF808080", ...}
347
+ ```
348
+
264
349
  `Imprimatur.images` hands over every image as a file: a PDF's JPEG and JPEG
265
350
  2000 as stored, fax scans as TIFF, raw pixels as PNG (soft masks as alpha),
266
351
  with the page and place each is drawn at; a Word or Excel document's media,
@@ -273,6 +358,39 @@ Imprimatur.images("scan.pdf") do |image|
273
358
  end
274
359
  ```
275
360
 
361
+ ### Workbooks as rows
362
+
363
+ For importing uploads, `Imprimatur.spreadsheet` reads an `.xlsx`, `.xls` or
364
+ `.ods` into rows of Ruby values: Strings, Integers for whole numbers shown
365
+ without a decimal point or a percent sign, Floats, `true`/`false`, Dates,
366
+ DateTimes for a time of day, and `nil` for an empty cell or a formula never
367
+ computed.
368
+
369
+ ```ruby
370
+ book = Imprimatur.spreadsheet(upload.download) # a path, the bytes or an IO
371
+ book.names # => ["Suppliers", "Countries"]
372
+ book.sheet.rows.first # => ["Instructions: one a row"]
373
+
374
+ book.sheet("Suppliers").parse(clean: true, name: "Company Name", since: "Since")
375
+ # => [{ name: "Acme", since: #<Date: 2024-03-15> }, ...]
376
+ ```
377
+
378
+ `parse` finds the header row among the first 100 (`header_rows:`), the first
379
+ holding every header, and maps the rows under it. `clean: true` strips control
380
+ characters and the whitespace at both ends of every String. Without such a row
381
+ it raises `Imprimatur::HeaderRowNotFound`, whose `missing` lists the headers the
382
+ closest row lacks.
383
+
384
+ Each sheet also has its `comments`, its `columns` (runs of widths, and whether
385
+ hidden) and its `validations`, as `Imprimatur.content` gives them. Opened with
386
+ `styles: true`, it has every formatted cell's look too:
387
+
388
+ ```ruby
389
+ sheet = Imprimatur.spreadsheet("template.xlsx", styles: true).sheet("BOM")
390
+ sheet.validation("G2:G1000") # => { range: "G2:G1000", type: "list", formula1: "Lists!$A$1:$A$9", ... }
391
+ sheet.style("A1") # => { cell: "A1", font: "Arial", bold: true, fill: "FFDDEEFF", ... }
392
+ ```
393
+
276
394
  ## Zip archives
277
395
 
278
396
  Archives of any files, for what is not a document (an export of an order's
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "imprimatur-cli"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "Read PDF, XLSX, XLS, ODS and DOCX documents as Markdown or JSON, search them, extract their images."
5
5
  keywords = ["pdf", "xlsx", "docx", "markdown", "cli"]
6
6
  categories = ["command-line-utilities", "text-processing"]
@@ -10,6 +10,8 @@ rust-version.workspace = true
10
10
  authors.workspace = true
11
11
  license.workspace = true
12
12
  repository.workspace = true
13
+ # Tests and their fixtures build nothing a published crate needs.
14
+ exclude = ["tests/**", "src/**/tests.rs"]
13
15
 
14
16
  [[bin]]
15
17
  name = "imprimatur"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "imprimatur"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "XLSX, PDF and DOCX authoring, and PDF, XLSX, XLS, ODS and DOCX reading, with its own XML, zip and PDF code."
5
5
  keywords = ["xlsx", "excel", "pdf", "docx", "spreadsheet"]
6
6
  categories = ["encoding", "text-processing"]
@@ -10,7 +10,16 @@ rust-version.workspace = true
10
10
  authors.workspace = true
11
11
  license.workspace = true
12
12
  repository.workspace = true
13
- include = ["src/**/*.rs", "src/**/*.txt", "src/**/tests/*.pdf", "src/**/tests/*.xml", "src/**/tests/*.rels", "README.md", "LICENSE*"]
13
+ include = [
14
+ "src/**/*.rs",
15
+ "src/**/*.txt",
16
+ "README.md",
17
+ "LICENSE*",
18
+ # Tests and their fixtures build nothing a published crate needs.
19
+ "!src/**/tests.rs",
20
+ "!src/**/tests/**",
21
+ "!src/**/test_support.rs",
22
+ ]
14
23
 
15
24
  [lints]
16
25
  workspace = true
@@ -18,9 +27,12 @@ workspace = true
18
27
  [dependencies]
19
28
  aes = "0.9.3"
20
29
  crc32fast = "1.5.2"
30
+ datamatrix = "0.3.3"
21
31
  md-5 = "0.11.0"
22
32
  miniz_oxide = "0.9.1"
23
- serde_json = { version = "1.0.154", optional = true }
33
+ qrcode = { version = "0.14.1", default-features = false }
34
+ read-fonts = { version = "0.45.1", optional = true }
35
+ serde_json = { workspace = true, optional = true }
24
36
  sha2 = "0.11.1"
25
37
  thiserror = "2.0.21"
26
38
  zip = { version = "9.0.3", default-features = false, features = ["deflate-flate2-zlib-rs"], optional = true }
@@ -32,7 +44,11 @@ json = ["dep:serde_json"]
32
44
  # `archive`: zip archives of any files (written with their times and
33
45
  # comments, ZIP64 when large; read within limits), through the `zip` crate.
34
46
  archive = ["dep:zip"]
47
+ # `font`: static TrueType font data (glyphs, advances, line metrics),
48
+ # through `read-fonts`.
49
+ fonts = ["dep:read-fonts"]
35
50
 
36
51
  [dev-dependencies]
37
- tempfile = "3"
52
+ font-test-data = "0.10.0"
53
+ tempfile.workspace = true
38
54
  zip = { version = "9.0.3", default-features = false, features = ["deflate-flate2-zlib-rs"] }
@@ -50,6 +50,20 @@ sheet.add_validation("A2:A1000".parse()?, short)?;
50
50
  # Ok::<(), imprimatur::Error>(())
51
51
  ```
52
52
 
53
+ ```rust
54
+ use imprimatur::barcode::{Barcode, Level, Look};
55
+ use imprimatur::doc::{Document, Symbol};
56
+
57
+ let qr = Barcode::qr("https://id.gs1.org/01/09506000134352", Level::Medium)?;
58
+ let svg = qr.svg(4.0, &Look::default())?; // or png(scale, &look)
59
+ let label = Barcode::gs1_128("(00)106141412345678908")?; // GS1 element strings, checked
60
+ let mut doc = Document::new();
61
+ doc.barcode(Symbol::new(label).width(250.0)?); // vector shapes in PDF
62
+ doc.barcode(Barcode::ean13("950600013435")?); // the check digit, added
63
+ let pdf = doc.to_pdf()?;
64
+ # Ok::<(), imprimatur::Error>(())
65
+ ```
66
+
53
67
  Everything Excel would refuse is an `Error` before a byte is written: invalid
54
68
  or duplicate sheet names, positions beyond 1,048,576 rows or 16,384 columns,
55
69
  text over 32,767 characters, formulas over 8,192, NaN and infinity, font
@@ -97,13 +111,31 @@ assert!(found.is_empty(), "no pictures in this one");
97
111
  # Ok::<(), imprimatur::Error>(())
98
112
  ```
99
113
 
114
+ With the `fonts` feature, `font::TrueTypeFont::from_bytes` loads borrowed
115
+ static TrueType data and exposes Unicode glyph IDs, horizontal advances and
116
+ raw `hhea` line metrics in font units. It checks the required tables and
117
+ outline offsets, but is not a full font sanitizer. Collections, WOFF, CFF and
118
+ variable fonts are not supported. This is a Rust font-data API only. The
119
+ internal PDF writer can serialize TrueType font resources with every glyph
120
+ and Unicode text mappings; document font selection, shaping and bidirectional
121
+ layout still need to connect to them. Document PDF output uses Helvetica.
122
+
123
+ Barcodes: QR Code and Data Matrix (their GS1 forms too), EAN-13, EAN-8, UPC-A,
124
+ Code 128 and GS1-128, drawn as SVG, as PNG, or into a document (shapes in a
125
+ PDF, a 300 dpi picture in DOCX). GS1 element strings are checked: AI syntax,
126
+ GS1's character set, predefined lengths, and the check digits of SSCCs, GTINs
127
+ and GLNs.
128
+
100
129
  Dependencies: `miniz_oxide`, `crc32fast` and `thiserror`; reading encrypted
101
- PDFs adds `md-5`, `sha2` and `aes`.
130
+ PDFs adds `md-5`, `sha2` and `aes`; barcodes add `qrcode` and `datamatrix`
131
+ (for their error correction and placement; the linear codes, GS1 and the
132
+ drawing are ours).
102
133
 
103
- Two features add what the core does not need: `json` (`read::json`, the
104
- schema the command line and the Ruby gem print, through `serde_json`) and
134
+ Three features add what the core does not need: `json` (`read::json`, the
135
+ schema the command line and the Ruby gem print, through `serde_json`),
105
136
  `archive` (`archive::{Writer, Archive}`: zip archives of any files, with
106
- times, comments and ZIP64, through the `zip` crate).
137
+ times, comments and ZIP64, through the `zip` crate) and `fonts`
138
+ (`font::TrueTypeFont`: static TrueType font data, through `read-fonts`).
107
139
 
108
140
  ## License
109
141