common/parser_tools/djot_options.rs
1//! Per-feature selection for djot import/export.
2//!
3//! The djot importer and exporter round-trip a set of *optional* block-level
4//! attributes — paragraph alignment, line height, text direction, non-breakable
5//! lines and background color — through djot's native `{key=value}` block
6//! attribute syntax (e.g. `{alignment=center}` on the line before a paragraph).
7//! These two option structs let a caller choose which of those attributes are
8//! carried. Everything else (headings, lists, tables, blockquotes, code blocks
9//! and all inline formatting) is always imported/exported and is not gated.
10//!
11//! Both structs default to **all enabled** — the fully lossless round-trip. Use
12//! [`DjotImportOptions::none`] / [`DjotExportOptions::none`] to restrict the
13//! round-trip to the core structural and inline feature set only.
14//!
15//! The attribute keys used on the wire are the model field names:
16//! `alignment`, `line_height`, `direction`, `non_breakable_lines`,
17//! `page_break_before`, `background_color`, `top_margin`, `text_indent` and
18//! `semantic_role`. Block attributes are only emitted/read for standalone
19//! paragraphs and headings; list items, code blocks and table cells normalise
20//! their block styling away (the same boundary the other targets observe).
21
22use serde::{Deserialize, Serialize};
23
24/// Selects which optional block attributes the djot **importer** applies to the
25/// document model. An attribute present in the source but disabled here is
26/// parsed and discarded, exactly like an unrepresentable construct.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub struct DjotImportOptions {
29 /// Apply paragraph alignment from `{alignment=left|right|center|justify}`.
30 pub alignment: bool,
31 /// Apply line height from `{line_height=<int>}`.
32 pub line_height: bool,
33 /// Apply text direction from `{direction=ltr|rtl}`.
34 pub direction: bool,
35 /// Apply non-breakable lines from `{non_breakable_lines=true|false}`.
36 pub non_breakable_lines: bool,
37 /// Apply "start on a new page" from `{page_break_before=true|false}`.
38 pub page_break_before: bool,
39 /// Apply block background color from `{background_color="<value>"}`.
40 pub background_color: bool,
41 /// Apply the block's own space-above from `{top_margin=<int>}`, overriding
42 /// the document-wide paragraph spacing for that one block.
43 pub top_margin: bool,
44 /// Apply the block's own first-line indent from `{text_indent=<int>}`,
45 /// overriding the document-wide first-line indent for that one block.
46 pub text_indent: bool,
47 /// Carry the frame's semantic role from `{semantic_role=epigraph}`. Unlike its
48 /// neighbours this one describes the enclosing **blockquote**, not the paragraph it
49 /// is written on: it rides the quote's first block because that is the only channel
50 /// djot block attributes have, and the importer moves it onto the frame.
51 pub semantic_role: bool,
52}
53
54impl DjotImportOptions {
55 /// Every optional block attribute enabled — the lossless default.
56 pub const fn all() -> Self {
57 Self {
58 alignment: true,
59 line_height: true,
60 direction: true,
61 non_breakable_lines: true,
62 page_break_before: true,
63 background_color: true,
64 top_margin: true,
65 text_indent: true,
66 semantic_role: true,
67 }
68 }
69
70 /// No optional block attributes — import only the core structural and
71 /// inline feature set, discarding any block-attribute styling.
72 pub const fn none() -> Self {
73 Self {
74 alignment: false,
75 line_height: false,
76 direction: false,
77 non_breakable_lines: false,
78 page_break_before: false,
79 background_color: false,
80 top_margin: false,
81 text_indent: false,
82 semantic_role: false,
83 }
84 }
85}
86
87impl Default for DjotImportOptions {
88 fn default() -> Self {
89 Self::all()
90 }
91}
92
93/// Selects which optional block attributes the djot **exporter** emits as
94/// `{key=value}` block attributes. A disabled attribute is omitted from the
95/// output even when the model carries a value for it.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97pub struct DjotExportOptions {
98 /// Emit paragraph alignment as `{alignment=…}`.
99 pub alignment: bool,
100 /// Emit line height as `{line_height=…}`.
101 pub line_height: bool,
102 /// Emit text direction as `{direction=…}`.
103 pub direction: bool,
104 /// Emit non-breakable lines as `{non_breakable_lines=…}`.
105 pub non_breakable_lines: bool,
106 /// Emit "start on a new page" as `{page_break_before=…}`.
107 pub page_break_before: bool,
108 /// Emit block background color as `{background_color=…}`.
109 pub background_color: bool,
110 /// Emit the block's own space-above as `{top_margin=…}`.
111 pub top_margin: bool,
112 /// Emit the block's own first-line indent as `{text_indent=…}`.
113 pub text_indent: bool,
114 /// Emit the enclosing blockquote's semantic role as `{semantic_role=…}` on the
115 /// quote's first block, so the model→djot→model round trip stays a fixpoint.
116 pub semantic_role: bool,
117 /// Drop inline images instead of emitting `{width=… height=…}`.
118 ///
119 /// Unlike every other field here this one is not a block attribute and is
120 /// **not** governed by [`Self::all`]/[`Self::none`]: an image is content, not
121 /// styling, so "emit no optional attributes" must not quietly delete it.
122 /// Only a caller that has decided not to place the image files beside the
123 /// output turns it on.
124 #[serde(default)]
125 pub omit_images: bool,
126}
127
128impl DjotExportOptions {
129 /// Every optional block attribute emitted — the lossless default.
130 pub const fn all() -> Self {
131 Self {
132 alignment: true,
133 line_height: true,
134 direction: true,
135 non_breakable_lines: true,
136 page_break_before: true,
137 background_color: true,
138 top_margin: true,
139 text_indent: true,
140 semantic_role: true,
141 omit_images: false,
142 }
143 }
144
145 /// No optional block attributes — emit only the core structural and inline
146 /// feature set, dropping any block-attribute styling.
147 pub const fn none() -> Self {
148 Self {
149 alignment: false,
150 line_height: false,
151 direction: false,
152 non_breakable_lines: false,
153 page_break_before: false,
154 background_color: false,
155 top_margin: false,
156 text_indent: false,
157 semantic_role: false,
158 // Content, not styling — see the field's own doc.
159 omit_images: false,
160 }
161 }
162}
163
164impl Default for DjotExportOptions {
165 fn default() -> Self {
166 Self::all()
167 }
168}