blitz-pdf 0.1.0 → 0.1.2

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 (114) hide show
  1. package/README.md +300 -10
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,733 @@
1
+ //! Format the tag tree in a human readable way, in form of a YAML document.
2
+ //! YAML is a good fit to represent the hierarchy of the tag tree, and many
3
+ //! editors allow folding the sub-trees.
4
+
5
+ use std::fmt::Display;
6
+
7
+ use crate::tagging::{
8
+ Attr, BBox, BlockAlign, BorderStyle, ColumnDimensions, GlyphOrientationVertical, Identifier,
9
+ IdentifierInner, IdentifierType, InlineAlign, LayoutAttr, LineHeight, ListAttr, ListNumbering,
10
+ NaiveRgbColor, Node, Placement, Sides, StructAttr, TableAttr, TableHeaderScope, TagGroup,
11
+ TagId, TagKind, TagTree, TextAlign, TextDecorationType, WritingMode,
12
+ };
13
+
14
+ /// Helper trait for indented output.
15
+ pub trait Output {
16
+ /// Wrapper around [`Output::output`] with a zero indent;
17
+ fn output(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
18
+ self.output_indent(f, Indent(0))
19
+ }
20
+
21
+ /// Output data with an indent.
22
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result;
23
+
24
+ /// Create a [`Display`]able wrapper.
25
+ fn display<'a>(&'a self) -> Wrapper<'a, Self> {
26
+ Wrapper {
27
+ inner: self,
28
+ indent: Indent(0),
29
+ }
30
+ }
31
+
32
+ /// Create a [`Display`]able wrapper with a specific indent.
33
+ fn display_indent<'a>(&'a self, indent: Indent) -> Wrapper<'a, Self> {
34
+ Wrapper {
35
+ inner: self,
36
+ indent,
37
+ }
38
+ }
39
+ }
40
+
41
+ /// A [`Display`]able wrapper struct around an [`Output`].
42
+ pub struct Wrapper<'a, T: Output + ?Sized> {
43
+ inner: &'a T,
44
+ indent: Indent,
45
+ }
46
+
47
+ impl<T: Output> Display for Wrapper<'_, T> {
48
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49
+ self.inner.output_indent(f, self.indent)
50
+ }
51
+ }
52
+
53
+ /// A [`Display`]able indentation.
54
+ #[derive(Clone, Copy)]
55
+ pub struct Indent(pub usize);
56
+
57
+ impl Indent {
58
+ fn inc(self) -> Self {
59
+ Self(self.0 + 1)
60
+ }
61
+ }
62
+
63
+ impl Display for Indent {
64
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65
+ write!(f, "{:indent$}", "", indent = 2 * self.0)
66
+ }
67
+ }
68
+
69
+ impl Output for TagTree {
70
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
71
+ for child in self.children.iter() {
72
+ child.output_indent(f, indent)?;
73
+ }
74
+ Ok(())
75
+ }
76
+ }
77
+
78
+ impl Output for Node {
79
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
80
+ match self {
81
+ Node::Group(group) => group.output_indent(f, indent),
82
+ Node::Leaf(Identifier(IdentifierInner::Real(IdentifierType::PageIdentifier(pi)))) => {
83
+ writeln!(
84
+ f,
85
+ "{indent}- Content: page={} mcid={}",
86
+ pi.page_index, pi.mcid
87
+ )
88
+ }
89
+ Node::Leaf(Identifier(IdentifierInner::Real(
90
+ IdentifierType::AnnotationIdentifier(ai),
91
+ ))) => {
92
+ writeln!(
93
+ f,
94
+ "{indent}- Annotation: page={} index={}",
95
+ ai.page_index, ai.annot_index
96
+ )
97
+ }
98
+ Node::Leaf(Identifier(IdentifierInner::Dummy)) => writeln!(f, "{indent}- Artifact"),
99
+ }
100
+ }
101
+ }
102
+
103
+ impl Output for TagGroup {
104
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
105
+ self.tag.output_indent(f, indent)?;
106
+ let indent = indent.inc();
107
+ if !self.children.is_empty() {
108
+ writeln!(f, "{indent}/K:")?;
109
+ let indent = indent.inc();
110
+ for node in self.children.iter() {
111
+ node.output_indent(f, indent)?;
112
+ }
113
+ }
114
+ Ok(())
115
+ }
116
+ }
117
+
118
+ impl Output for TagKind {
119
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
120
+ write!(f, "{indent}- Tag: ")?;
121
+ match self {
122
+ TagKind::Part(_) => write!(f, "Part"),
123
+ TagKind::Article(_) => write!(f, "Article"),
124
+ TagKind::Section(_) => write!(f, "Section"),
125
+ TagKind::Div(_) => write!(f, "Div"),
126
+ TagKind::BlockQuote(_) => write!(f, "BlockQuote"),
127
+ TagKind::Caption(_) => write!(f, "Caption"),
128
+ TagKind::TOC(_) => write!(f, "TOC"),
129
+ TagKind::TOCI(_) => write!(f, "TOCI"),
130
+ TagKind::Index(_) => write!(f, "Index"),
131
+ TagKind::P(_) => write!(f, "P"),
132
+ TagKind::Hn(tag) => write!(f, "H{}", tag.level().get()),
133
+ TagKind::L(_) => write!(f, "L"),
134
+ TagKind::LI(_) => write!(f, "LI"),
135
+ TagKind::Lbl(_) => write!(f, "Lbl"),
136
+ TagKind::LBody(_) => write!(f, "LBody"),
137
+ TagKind::Table(_) => write!(f, "Table"),
138
+ TagKind::TR(_) => write!(f, "TR"),
139
+ TagKind::TH(_) => write!(f, "TH"),
140
+ TagKind::TD(_) => write!(f, "TD"),
141
+ TagKind::THead(_) => write!(f, "THead"),
142
+ TagKind::TBody(_) => write!(f, "TBody"),
143
+ TagKind::TFoot(_) => write!(f, "TFoot"),
144
+ TagKind::Span(_) => write!(f, "Span"),
145
+ TagKind::InlineQuote(_) => write!(f, "InlineQuote"),
146
+ TagKind::Note(_) => write!(f, "Note"),
147
+ TagKind::Reference(_) => write!(f, "Reference"),
148
+ TagKind::BibEntry(_) => write!(f, "BibEntry"),
149
+ TagKind::Code(_) => write!(f, "Code"),
150
+ TagKind::Link(_) => write!(f, "Link"),
151
+ TagKind::Annot(_) => write!(f, "Annot"),
152
+ TagKind::Figure(_) => write!(f, "Figure"),
153
+ TagKind::Formula(_) => write!(f, "Formula"),
154
+ TagKind::Form(_) => write!(f, "Form"),
155
+ TagKind::NonStruct(_) => write!(f, "NonStruct"),
156
+ TagKind::Datetime(_) => write!(f, "Datetime"),
157
+ TagKind::Terms(_) => write!(f, "Terms"),
158
+ TagKind::Title(_) => write!(f, "Title"),
159
+ TagKind::Strong(_) => write!(f, "Strong"),
160
+ TagKind::Em(_) => write!(f, "Em"),
161
+ }?;
162
+ writeln!(f)?;
163
+
164
+ let indent = indent.inc();
165
+ for attr in self.as_any().attrs.iter() {
166
+ attr.output_indent(f, indent)?;
167
+ }
168
+
169
+ Ok(())
170
+ }
171
+ }
172
+
173
+ impl Output for Attr {
174
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
175
+ use LayoutAttr::*;
176
+ use ListAttr::*;
177
+ use StructAttr::*;
178
+ use TableAttr::*;
179
+
180
+ if let Attr::Struct(StructAttr::HeadingLevel(_)) = self {
181
+ return Ok(());
182
+ }
183
+
184
+ write!(f, "{indent}")?;
185
+ match self {
186
+ Attr::Struct(struct_attr) => match struct_attr {
187
+ Id(id) => writeln!(f, "/Id: {}", id.display()),
188
+ Lang(lang) => writeln!(f, "/Lang: {lang:?}"),
189
+ AltText(alt) => writeln!(f, "/Alt: {alt:?}"),
190
+ Expanded(e) => writeln!(f, "/E: {e:?}"),
191
+ ActualText(actual) => writeln!(f, "/ActualText: {actual:?}"),
192
+ Title(title) => writeln!(f, "/T: {title:?}"),
193
+
194
+ // Not a real attribute, is already displayed in tag kind.
195
+ HeadingLevel(_) => Ok(()),
196
+ },
197
+ Attr::List(list_attr) => match list_attr {
198
+ Numbering(n) => writeln!(f, "/Numbering: {}", n.display()),
199
+ },
200
+ Attr::Table(table_attr) => match table_attr {
201
+ Summary(summary) => writeln!(f, "/Summary: {summary:?}"),
202
+ HeaderScope(scope) => writeln!(f, "/Scope: {}", scope.display()),
203
+ CellHeaders(headers) => {
204
+ write!(f, "/Headers: [")?;
205
+ if let Some((first, remainder)) = headers.split_first() {
206
+ first.output(f)?;
207
+ for id in remainder.iter() {
208
+ write!(f, ", {}", id.display())?;
209
+ }
210
+ }
211
+ writeln!(f, "]")
212
+ }
213
+ RowSpan(rowspan) => writeln!(f, "/RowSpan: {}", rowspan.get()),
214
+ ColSpan(colspan) => writeln!(f, "/ColSpan: {}", colspan.get()),
215
+ },
216
+ Attr::Layout(layout_attr) => match layout_attr {
217
+ Placement(placement) => writeln!(f, "/Placement: {}", placement.display()),
218
+ WritingMode(mode) => writeln!(f, "/WritingMode: {}", mode.display()),
219
+ BBox(bbox) => writeln!(f, "/BBox:{}", bbox.display_indent(indent.inc())),
220
+ Width(width) => writeln!(f, "/Width: {}", width.display()),
221
+ Height(height) => writeln!(f, "/Height: {}", height.display()),
222
+ BackgroundColor(color) => writeln!(f, "/BackgroundColor: {}", color.display()),
223
+ BorderColor(sides) => {
224
+ let space = omit_if(" ", sides.is_multiline());
225
+ let sides = sides.display_indent(indent.inc());
226
+ writeln!(f, "/BorderColor:{space}{sides}")
227
+ }
228
+ BorderStyle(sides) => {
229
+ let space = omit_if(" ", sides.is_multiline());
230
+ let sides = sides.display_indent(indent.inc());
231
+ writeln!(f, "/BorderStyle:{space}{sides}")
232
+ }
233
+ BorderThickness(sides) => {
234
+ let space = omit_if(" ", sides.is_multiline());
235
+ let sides = sides.display_indent(indent.inc());
236
+ writeln!(f, "/BorderThickness:{space}{sides}")
237
+ }
238
+ Padding(sides) => {
239
+ writeln!(f, "/Padding: {}", sides.display_indent(indent.inc()))
240
+ }
241
+ Color(color) => writeln!(f, "/Color: {}", color.display()),
242
+ SpaceBefore(space) => writeln!(f, "/SpaceBefore: {}", space.display()),
243
+ SpaceAfter(space) => writeln!(f, "/SpaceAfter: {}", space.display()),
244
+ StartIndent(indent) => writeln!(f, "/StartIndent: {}", indent.display()),
245
+ EndIndent(indent) => writeln!(f, "/EndIndent: {}", indent.display()),
246
+ TextIndent(indent) => writeln!(f, "/TextIndent: {}", indent.display()),
247
+ TextAlign(align) => writeln!(f, "/TextAlign: {}", align.display()),
248
+ BlockAlign(align) => writeln!(f, "/BlockAlign: {}", align.display()),
249
+ InlineAlign(align) => writeln!(f, "/InlineAlign: {}", align.display()),
250
+ TableBorderStyle(sides) => {
251
+ let space = omit_if(" ", sides.is_multiline());
252
+ let style = sides.display_indent(indent.inc());
253
+ writeln!(f, "/TableBorderStyle:{space}{style}")
254
+ }
255
+ TablePadding(sides) => {
256
+ let space = omit_if(" ", sides.is_multiline());
257
+ let sides = sides.display_indent(indent.inc());
258
+ writeln!(f, "/TablePadding:{space}{sides}")
259
+ }
260
+ BaselineShift(shift) => writeln!(f, "/BaselineShift: {}", shift.display()),
261
+ LineHeight(line_height) => {
262
+ writeln!(f, "/LineHeight: {}", line_height.display())
263
+ }
264
+ TextDecorationColor(color) => {
265
+ writeln!(f, "/TextDecorationColor: {}", color.display())
266
+ }
267
+ TextDecorationThickness(thickness) => {
268
+ writeln!(f, "/TextDecorationThickness: {}", thickness.display())
269
+ }
270
+ TextDecorationType(deco_type) => {
271
+ writeln!(f, "/TextDecorationType: {}", deco_type.display())
272
+ }
273
+ GlyphOrientationVertical(orientation) => {
274
+ writeln!(f, "/GlyphOrientationVertical: {}", orientation.display())
275
+ }
276
+ ColumnCount(column_count) => writeln!(f, "/ColumnCount: {}", column_count.get()),
277
+ ColumnGap(column_gap) => {
278
+ let space = omit_if(" ", column_gap.is_multiline());
279
+ let column_gap = column_gap.display_indent(indent.inc());
280
+ writeln!(f, "/ColumnGap:{space}{column_gap}")
281
+ }
282
+ ColumnWidths(column_width) => {
283
+ let space = omit_if(" ", column_width.is_multiline());
284
+ let column_width = column_width.display_indent(indent.inc());
285
+ writeln!(f, "/ColumnWidths:{space}{column_width}")
286
+ }
287
+ },
288
+ }
289
+ }
290
+ }
291
+
292
+ trait ValueOutput: Output {
293
+ fn is_multiline(&self) -> bool {
294
+ false
295
+ }
296
+ }
297
+
298
+ #[derive(Clone, Copy)]
299
+ struct OmitText {
300
+ text: &'static str,
301
+ omit: bool,
302
+ }
303
+
304
+ fn omit_if(text: &'static str, omit: bool) -> OmitText {
305
+ OmitText { text, omit }
306
+ }
307
+
308
+ impl Display for OmitText {
309
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310
+ if !self.omit {
311
+ f.write_str(self.text)?;
312
+ }
313
+ Ok(())
314
+ }
315
+ }
316
+
317
+ impl ValueOutput for TagId {}
318
+ impl Output for TagId {
319
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
320
+ if self
321
+ .as_bytes()
322
+ .iter()
323
+ .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
324
+ {
325
+ let str = std::str::from_utf8(self.as_bytes()).unwrap();
326
+ write!(f, "\"{str}\"")?;
327
+ } else {
328
+ for b in self.as_bytes() {
329
+ write!(f, "0x{b:02x}")?;
330
+ }
331
+ }
332
+ Ok(())
333
+ }
334
+ }
335
+
336
+ impl ValueOutput for ListNumbering {}
337
+ impl Output for ListNumbering {
338
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
339
+ match self {
340
+ ListNumbering::None => write!(f, "None"),
341
+ ListNumbering::Disc => write!(f, "Disc"),
342
+ ListNumbering::Circle => write!(f, "Circle"),
343
+ ListNumbering::Square => write!(f, "Square"),
344
+ ListNumbering::Decimal => write!(f, "Decimal"),
345
+ ListNumbering::LowerRoman => write!(f, "LowerRoman"),
346
+ ListNumbering::UpperRoman => write!(f, "UpperRoman"),
347
+ ListNumbering::LowerAlpha => write!(f, "LowerAlpha"),
348
+ ListNumbering::UpperAlpha => write!(f, "UpperAlpha"),
349
+ }
350
+ }
351
+ }
352
+
353
+ impl ValueOutput for TableHeaderScope {}
354
+ impl Output for TableHeaderScope {
355
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
356
+ match self {
357
+ TableHeaderScope::Row => write!(f, "Row"),
358
+ TableHeaderScope::Column => write!(f, "Column"),
359
+ TableHeaderScope::Both => write!(f, "Both"),
360
+ }
361
+ }
362
+ }
363
+
364
+ impl ValueOutput for Placement {}
365
+ impl Output for Placement {
366
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
367
+ match self {
368
+ Placement::Block => write!(f, "Block"),
369
+ Placement::Inline => write!(f, "Inline"),
370
+ Placement::Before => write!(f, "Before"),
371
+ Placement::Start => write!(f, "Start"),
372
+ Placement::End => write!(f, "End"),
373
+ }
374
+ }
375
+ }
376
+
377
+ impl ValueOutput for WritingMode {}
378
+ impl Output for WritingMode {
379
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
380
+ match self {
381
+ WritingMode::LrTb => write!(f, "LrTb"),
382
+ WritingMode::RlTb => write!(f, "RlTb"),
383
+ WritingMode::TbRl => write!(f, "TbRl"),
384
+ }
385
+ }
386
+ }
387
+
388
+ impl ValueOutput for BBox {
389
+ fn is_multiline(&self) -> bool {
390
+ true
391
+ }
392
+ }
393
+ impl Output for BBox {
394
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
395
+ writeln!(f)?;
396
+ writeln!(f, "{indent}page: {}", self.page_idx)?;
397
+ writeln!(
398
+ f,
399
+ "{indent}left: {}",
400
+ self.rect.left().display_indent(indent)
401
+ )?;
402
+ writeln!(
403
+ f,
404
+ "{indent}top: {}",
405
+ self.rect.top().display_indent(indent)
406
+ )?;
407
+ writeln!(
408
+ f,
409
+ "{indent}right: {}",
410
+ self.rect.right().display_indent(indent)
411
+ )?;
412
+ write!(
413
+ f,
414
+ "{indent}bottom: {}",
415
+ self.rect.bottom().display_indent(indent)
416
+ )?;
417
+ Ok(())
418
+ }
419
+ }
420
+
421
+ impl<T: PartialEq + ValueOutput> ValueOutput for Sides<T> {
422
+ fn is_multiline(&self) -> bool {
423
+ !self.is_uniform() || self.before.is_multiline()
424
+ }
425
+ }
426
+ impl<T: PartialEq + ValueOutput> Output for Sides<T> {
427
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
428
+ if self.is_uniform() {
429
+ self.before.output_indent(f, indent)?;
430
+ } else {
431
+ writeln!(f)?;
432
+
433
+ let space = omit_if(" ", self.before.is_multiline());
434
+ let before = self.before.display_indent(indent.inc());
435
+ writeln!(f, "{indent}before:{space}{before}")?;
436
+
437
+ let space = omit_if(" ", self.after.is_multiline());
438
+ let after = self.after.display_indent(indent.inc());
439
+ writeln!(f, "{indent}after:{space}{after}")?;
440
+
441
+ let space = omit_if(" ", self.start.is_multiline());
442
+ let start = self.start.display_indent(indent.inc());
443
+ writeln!(f, "{indent}start:{space}{start}")?;
444
+
445
+ let space = omit_if(" ", self.end.is_multiline());
446
+ let end = self.end.display_indent(indent.inc());
447
+ write!(f, "{indent}end:{space}{end}")?;
448
+ }
449
+ Ok(())
450
+ }
451
+ }
452
+
453
+ impl ValueOutput for NaiveRgbColor {}
454
+ impl Output for NaiveRgbColor {
455
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
456
+ let r = self.red;
457
+ let g = self.green;
458
+ let b = self.blue;
459
+ write!(f, "#{r:02x}{g:02x}{b:02x}")
460
+ }
461
+ }
462
+
463
+ impl ValueOutput for BorderStyle {}
464
+ impl Output for BorderStyle {
465
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
466
+ match self {
467
+ BorderStyle::None => write!(f, "None"),
468
+ BorderStyle::Hidden => write!(f, "Hidden"),
469
+ BorderStyle::Solid => write!(f, "Solid"),
470
+ BorderStyle::Dashed => write!(f, "Dashed"),
471
+ BorderStyle::Dotted => write!(f, "Dotted"),
472
+ BorderStyle::Double => write!(f, "Double"),
473
+ BorderStyle::Groove => write!(f, "Groove"),
474
+ BorderStyle::Ridge => write!(f, "Ridge"),
475
+ BorderStyle::Inset => write!(f, "Inset"),
476
+ BorderStyle::Outset => write!(f, "Outset"),
477
+ }
478
+ }
479
+ }
480
+
481
+ impl ValueOutput for TextAlign {}
482
+ impl Output for TextAlign {
483
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
484
+ match self {
485
+ TextAlign::Start => write!(f, "Start"),
486
+ TextAlign::Center => write!(f, "Center"),
487
+ TextAlign::End => write!(f, "End"),
488
+ TextAlign::Justify => write!(f, "Justify"),
489
+ }
490
+ }
491
+ }
492
+
493
+ impl ValueOutput for BlockAlign {}
494
+ impl Output for BlockAlign {
495
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
496
+ match self {
497
+ BlockAlign::Begin => write!(f, "Begin"),
498
+ BlockAlign::Middle => write!(f, "Middle"),
499
+ BlockAlign::After => write!(f, "After"),
500
+ BlockAlign::Justify => write!(f, "Justify"),
501
+ }
502
+ }
503
+ }
504
+
505
+ impl ValueOutput for InlineAlign {}
506
+ impl Output for InlineAlign {
507
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
508
+ match self {
509
+ InlineAlign::Start => write!(f, "Start"),
510
+ InlineAlign::Center => write!(f, "Center"),
511
+ InlineAlign::End => write!(f, "End"),
512
+ }
513
+ }
514
+ }
515
+
516
+ impl ValueOutput for LineHeight {}
517
+ impl Output for LineHeight {
518
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
519
+ match self {
520
+ LineHeight::Normal => write!(f, "Normal"),
521
+ LineHeight::Auto => write!(f, "Auto"),
522
+ LineHeight::Custom(custom) => custom.output(f),
523
+ }
524
+ }
525
+ }
526
+
527
+ impl ValueOutput for TextDecorationType {}
528
+ impl Output for TextDecorationType {
529
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
530
+ match self {
531
+ TextDecorationType::None => write!(f, "None"),
532
+ TextDecorationType::Underline => write!(f, "Underline"),
533
+ TextDecorationType::Overline => write!(f, "Overline"),
534
+ TextDecorationType::LineThrough => write!(f, "LineThrough"),
535
+ }
536
+ }
537
+ }
538
+
539
+ impl ValueOutput for GlyphOrientationVertical {}
540
+ impl Output for GlyphOrientationVertical {
541
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
542
+ match self {
543
+ GlyphOrientationVertical::Auto => write!(f, "Auto"),
544
+ GlyphOrientationVertical::None => write!(f, "0"),
545
+ GlyphOrientationVertical::Clockwise90 => write!(f, "90"),
546
+ GlyphOrientationVertical::CounterClockwise90 => write!(f, "-90"),
547
+ GlyphOrientationVertical::Clockwise180 => write!(f, "180"),
548
+ GlyphOrientationVertical::CounterClockwise180 => write!(f, "-180"),
549
+ GlyphOrientationVertical::Clockwise270 => write!(f, "270"),
550
+ }
551
+ }
552
+ }
553
+
554
+ impl ValueOutput for ColumnDimensions {
555
+ fn is_multiline(&self) -> bool {
556
+ matches!(self, Self::Specific(_))
557
+ }
558
+ }
559
+ impl Output for ColumnDimensions {
560
+ fn output_indent(&self, f: &mut impl std::fmt::Write, indent: Indent) -> std::fmt::Result {
561
+ match self {
562
+ ColumnDimensions::All(all) => all.output(f),
563
+ ColumnDimensions::Specific(list) => {
564
+ let Some((last, remainder)) = list.split_last() else {
565
+ return Ok(());
566
+ };
567
+ writeln!(f)?;
568
+ for dim in remainder.iter() {
569
+ writeln!(f, "{indent}- {}", dim.display())?;
570
+ }
571
+ write!(f, "{indent}- {}", last.display())
572
+ }
573
+ }
574
+ }
575
+ }
576
+
577
+ impl ValueOutput for f32 {}
578
+ impl Output for f32 {
579
+ fn output_indent(&self, f: &mut impl std::fmt::Write, _: Indent) -> std::fmt::Result {
580
+ write!(f, "{self:7.3}")
581
+ }
582
+ }
583
+
584
+ #[cfg(test)]
585
+ mod tests {
586
+ use std::num::NonZeroU16;
587
+
588
+ use pretty_assertions::assert_eq;
589
+
590
+ use crate::action::{Action, LinkAction};
591
+ use crate::annotation::{LinkAnnotation, Target};
592
+ use crate::geom::Rect;
593
+ use crate::tagging::fmt::{Indent, Output};
594
+ use crate::tagging::{
595
+ BBox, ColumnDimensions, ContentTag, LineHeight, NaiveRgbColor, Sides, Tag, TagGroup,
596
+ TagTree,
597
+ };
598
+ use crate::Document;
599
+
600
+ #[test]
601
+ fn display_empty_tag_tree() {
602
+ assert_eq!("", TagTree::new().display().to_string());
603
+ }
604
+
605
+ #[test]
606
+ fn display_tag_tree() {
607
+ let mut document = Document::new();
608
+ let mut page = document.start_page();
609
+ let mut tree = TagTree::new();
610
+
611
+ let sec = Tag::Section
612
+ .with_lang(Some("de".into()))
613
+ .with_column_widths(Some(ColumnDimensions::Specific(vec![17.0, 23.0, 34.0])))
614
+ .with_column_gap(Some(ColumnDimensions::Specific(vec![3.0, 4.0])));
615
+ let mut sec = TagGroup::new(sec);
616
+
617
+ let figure_rect = Rect::from_ltrb(12.1, 12.342, 24.789877, 32.0).unwrap();
618
+ let figure = Tag::Figure(Some("figure alt text".into()))
619
+ .with_actual_text(Some("THE ACTUAL TEXT".into()))
620
+ .with_bbox(Some(BBox::new(0, figure_rect)))
621
+ .with_line_height(Some(LineHeight::Normal));
622
+ let mut figure = TagGroup::new(figure);
623
+
624
+ let link_rect = Rect::from_ltrb(12.0, 12.0, 24.0, 32.32).unwrap();
625
+ let link_target =
626
+ Target::Action(Action::Link(LinkAction::new("https://github.com".into())));
627
+ let link_id =
628
+ page.add_tagged_annotation(LinkAnnotation::new(link_rect, link_target).into());
629
+ figure.push(link_id);
630
+ sec.push(figure);
631
+
632
+ let border_color = Sides::new(
633
+ NaiveRgbColor::new(0x1A, 0x66, 0xFF),
634
+ NaiveRgbColor::new(0x4D, 0x80, 0x33),
635
+ NaiveRgbColor::new(0x4D, 0x66, 0x4D),
636
+ NaiveRgbColor::new(0x00, 0xB3, 0x33),
637
+ );
638
+ let table = Tag::Table
639
+ .with_border_color(Some(border_color))
640
+ .with_line_height(Some(LineHeight::Custom(23.0)));
641
+ let table = TagGroup::new(table);
642
+ sec.push(table);
643
+
644
+ tree.push(sec);
645
+
646
+ let yaml = tree.display().to_string();
647
+ let expected = "\
648
+ - Tag: Section
649
+ /Lang: \"de\"
650
+ /ColumnGap:
651
+ - 3.000
652
+ - 4.000
653
+ /ColumnWidths:
654
+ - 17.000
655
+ - 23.000
656
+ - 34.000
657
+ /K:
658
+ - Tag: Figure
659
+ /Alt: \"figure alt text\"
660
+ /ActualText: \"THE ACTUAL TEXT\"
661
+ /BBox:
662
+ page: 0
663
+ left: 12.100
664
+ top: 12.342
665
+ right: 24.790
666
+ bottom: 32.000
667
+ /LineHeight: Normal
668
+ /K:
669
+ - Annotation: page=0 index=0
670
+ - Tag: Table
671
+ /BorderColor:
672
+ before: #1a66ff
673
+ after: #4d8033
674
+ start: #4d664d
675
+ end: #00b333
676
+ /LineHeight: 23.000
677
+ ";
678
+ assert_eq!(expected, yaml)
679
+ }
680
+
681
+ #[test]
682
+ fn display_sides_mutliline() {
683
+ let sides = Sides::new(
684
+ ColumnDimensions::specific(vec![1.0, 5.0]),
685
+ ColumnDimensions::specific(vec![2.0, 6.0]),
686
+ ColumnDimensions::specific(vec![3.0, 7.0]),
687
+ ColumnDimensions::specific(vec![4.0, 8.0]),
688
+ );
689
+ let yaml = format!("val:{}\n", sides.display_indent(Indent(1)));
690
+ let expected = "\
691
+ val:
692
+ before:
693
+ - 1.000
694
+ - 5.000
695
+ after:
696
+ - 2.000
697
+ - 6.000
698
+ start:
699
+ - 3.000
700
+ - 7.000
701
+ end:
702
+ - 4.000
703
+ - 8.000
704
+ ";
705
+ assert_eq!(expected, yaml);
706
+ }
707
+
708
+ #[test]
709
+ fn display_heading_with_children() {
710
+ let mut document = Document::new();
711
+ let mut page = document.start_page();
712
+ let mut tree = TagTree::new();
713
+
714
+ let heading = Tag::Hn(NonZeroU16::new(1).unwrap(), None);
715
+ let mut heading = TagGroup::new(heading);
716
+
717
+ let mut surface = page.surface();
718
+
719
+ let id1 = surface.start_tagged(ContentTag::Other);
720
+ surface.end_tagged();
721
+ heading.push(id1);
722
+
723
+ tree.push(heading);
724
+
725
+ let yaml = tree.display().to_string();
726
+ let expected = "\
727
+ - Tag: H1
728
+ /K:
729
+ - Content: page=0 mcid=0
730
+ ";
731
+ assert_eq!(expected, yaml);
732
+ }
733
+ }