1use std::collections::HashSet;
20use std::ops::Range;
21
22use super::{
23 Kind, Refused, is_inline_container, is_paragraph, normalize, parent_of, segments, set_count,
24 text,
25};
26use crate::style::{Family, Styles, TextProperties};
27use crate::xml::{Attribute, Element, Name, Node, Ns};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Mark {
32 Bold,
34 Italic,
36 Underline,
38 Strike,
40}
41
42impl Mark {
43 fn of(self, properties: &TextProperties) -> Option<bool> {
44 match self {
45 Self::Bold => properties.bold,
46 Self::Italic => properties.italic,
47 Self::Underline => properties.underline,
48 Self::Strike => properties.strike,
49 }
50 }
51
52 fn attributes(self) -> Vec<(Ns, &'static str)> {
55 let style = |names: &[&'static str]| names.iter().map(|n| (Ns::Style, *n)).collect();
56 match self {
57 Self::Bold => vec![
58 (Ns::Fo, "font-weight"),
59 (Ns::Style, "font-weight-asian"),
60 (Ns::Style, "font-weight-complex"),
61 ],
62 Self::Italic => vec![
63 (Ns::Fo, "font-style"),
64 (Ns::Style, "font-style-asian"),
65 (Ns::Style, "font-style-complex"),
66 ],
67 Self::Underline => style(&[
68 "text-underline-type",
69 "text-underline-style",
70 "text-underline-width",
71 "text-underline-color",
72 "text-underline-mode",
73 ]),
74 Self::Strike => style(&[
75 "text-line-through-type",
76 "text-line-through-style",
77 "text-line-through-width",
78 "text-line-through-color",
79 "text-line-through-text",
80 "text-line-through-text-style",
81 "text-line-through-mode",
82 ]),
83 }
84 }
85
86 fn written(self, on: bool) -> Vec<(Ns, &'static str, &'static str)> {
90 match self {
91 Self::Bold => {
92 let weight = if on { "bold" } else { "normal" };
93 vec![
94 (Ns::Fo, "font-weight", weight),
95 (Ns::Style, "font-weight-asian", weight),
96 (Ns::Style, "font-weight-complex", weight),
97 ]
98 }
99 Self::Italic => {
100 let style = if on { "italic" } else { "normal" };
101 vec![
102 (Ns::Fo, "font-style", style),
103 (Ns::Style, "font-style-asian", style),
104 (Ns::Style, "font-style-complex", style),
105 ]
106 }
107 Self::Underline if on => vec![
108 (Ns::Style, "text-underline-style", "solid"),
109 (Ns::Style, "text-underline-width", "auto"),
110 (Ns::Style, "text-underline-color", "font-color"),
111 ],
112 Self::Underline => vec![(Ns::Style, "text-underline-style", "none")],
113 Self::Strike => vec![(
114 Ns::Style,
115 "text-line-through-style",
116 if on { "solid" } else { "none" },
117 )],
118 }
119 }
120}
121
122pub fn marked(
128 paragraph: &Element,
129 range: Range<usize>,
130 mark: Mark,
131 styles: &Styles,
132) -> Option<bool> {
133 let base = paragraph_mark(paragraph, mark, styles);
134 let (start, end) = if range.start < range.end {
135 (range.start, range.end)
136 } else if range.start > 0 {
137 (range.start - 1, range.start)
138 } else {
139 (0, 1)
140 };
141 let mut answer = None;
142 for segment in segments(paragraph) {
143 let segment_end = segment.start + segment.kind.len();
144 if segment.kind.len() == 0 || segment_end <= start || end <= segment.start {
145 continue;
146 }
147 let value = mark_at(paragraph, &segment.path, base, mark, styles);
148 if answer.is_some_and(|a| a != value) {
149 return None;
150 }
151 answer = Some(value);
152 }
153 Some(answer.unwrap_or(base))
154}
155
156pub fn format(
170 content: &mut Element,
171 path: &[usize],
172 range: Range<usize>,
173 mark: Mark,
174 on: bool,
175 styles: &mut Styles,
176) -> Result<(), Refused> {
177 let paragraph = content
178 .at(path)
179 .filter(|e| is_paragraph(e))
180 .ok_or(Refused::NotFound)?;
181 let needs_fo = matches!(mark, Mark::Bold | Mark::Italic);
182 if !content.declares(&Ns::Style) || needs_fo && !content.declares(&Ns::Fo) {
183 return Err(Refused::Namespace);
184 }
185 let len = text(paragraph).chars().count();
186 let end = range.end.min(len);
187 let start = range.start.min(end);
188 if start == end {
189 return Ok(());
190 }
191
192 let mut edited = paragraph.clone();
193 cut_at(&mut edited, end);
194 cut_at(&mut edited, start);
195 let selected = segments(&edited)
198 .into_iter()
199 .filter(|s| match s.kind.len() {
200 0 => start < s.start && s.start < end,
201 n => start <= s.start && s.start + n <= end,
202 })
203 .map(|s| s.path)
204 .collect();
205 let base = paragraph_mark(&edited, mark, styles);
206 let root = Element {
207 attrs: content.attrs.clone(),
208 ..element(content.name.clone())
209 };
210 let mut formatter = Formatter {
211 mark,
212 on,
213 selected,
214 automatic: automatic_text_styles(content),
215 text: edited.name.prefix.as_deref().unwrap_or("text").to_owned(),
216 root,
217 styles,
218 minted: Vec::new(),
219 };
220 formatter.within(&mut edited, &mut Vec::new(), base);
221 normalize(&mut edited);
222 let minted = formatter.minted;
223
224 *content.at_mut(path).ok_or(Refused::NotFound)? = edited;
227 if !minted.is_empty() {
228 let container = automatic_styles(content);
229 container
230 .children
231 .extend(minted.into_iter().map(Node::Element));
232 container.self_closing = false;
233 }
234 Ok(())
235}
236
237fn style_name(element: &Element) -> &str {
240 element.attr(&Ns::Text, "style-name").unwrap_or("Standard")
241}
242
243fn paragraph_mark(paragraph: &Element, mark: Mark, styles: &Styles) -> bool {
246 mark.of(&styles
247 .resolve(&Family::Paragraph, style_name(paragraph))
248 .text)
249 .unwrap_or(false)
250}
251
252fn own(element: &Element, mark: Mark, styles: &Styles) -> Option<bool> {
255 if element.is(&Ns::Text, "span") || element.is(&Ns::Text, "a") {
256 mark.of(&styles.resolve(&Family::Text, style_name(element)).text)
257 } else {
258 None
259 }
260}
261
262fn mark_at(paragraph: &Element, path: &[usize], base: bool, mark: Mark, styles: &Styles) -> bool {
265 (1..path.len())
266 .filter_map(|depth| paragraph.at(&path[..depth]))
267 .fold(base, |value, e| own(e, mark, styles).unwrap_or(value))
268}
269
270fn cut_at(paragraph: &mut Element, at: usize) {
273 let Some(segment) = segments(paragraph)
274 .into_iter()
275 .find(|s| s.start < at && at < s.start + s.kind.len())
276 else {
277 return;
278 };
279 let offset = at - segment.start;
280 let Some((parent, index)) = parent_of(paragraph, &segment.path) else {
281 return;
282 };
283 let byte = |t: &str| t.char_indices().nth(offset).map_or(t.len(), |(b, _)| b);
284 let second = match (&mut parent.children[index], segment.kind) {
285 (Node::Text(t), _) => Node::Text(t.split_off(byte(t))),
286 (Node::CData(t), _) => Node::CData(t.split_off(byte(t))),
287 (Node::Element(space), Kind::Spaces(count)) => {
288 let mut second = space.clone();
289 set_count(space, offset);
290 set_count(&mut second, count - offset);
291 Node::Element(second)
292 }
293 _ => return,
294 };
295 parent.children.insert(index + 1, second);
296}
297
298fn automatic_text_styles(content: &Element) -> Vec<Element> {
300 content
301 .child(&Ns::Office, "automatic-styles")
302 .into_iter()
303 .flat_map(Element::elements)
304 .filter(|e| e.is(&Ns::Style, "style") && e.attr(&Ns::Style, "family") == Some("text"))
305 .cloned()
306 .collect()
307}
308
309pub(super) fn automatic_styles(content: &mut Element) -> &mut Element {
312 let found = |local: &'static str| move |n: &Node| matches!(n, Node::Element(e) if e.is(&Ns::Office, local));
313 let at = if let Some(at) = content.children.iter().position(found("automatic-styles")) {
314 at
315 } else {
316 let before = content
317 .children
318 .iter()
319 .position(|n| found("master-styles")(n) || found("body")(n))
320 .unwrap_or(content.children.len());
321 let container = element(content.name_for(&Ns::Office, "automatic-styles"));
322 content.children.insert(before, Node::Element(container));
323 content.self_closing = false;
324 before
325 };
326 let Node::Element(container) = &mut content.children[at] else {
327 unreachable!("found or written as an element");
328 };
329 container
330}
331
332pub(super) fn element(name: Name) -> Element {
333 Element {
334 name,
335 attrs: Vec::new(),
336 children: Vec::new(),
337 self_closing: true,
338 }
339}
340
341#[derive(Clone, Copy, PartialEq, Eq)]
343enum Held {
344 Whole,
346 Part,
348 Outside,
350 Neutral,
352}
353
354struct Formatter<'a> {
356 mark: Mark,
357 on: bool,
358 selected: HashSet<Vec<usize>>,
360 automatic: Vec<Element>,
362 text: String,
364 root: Element,
367 styles: &'a mut Styles,
368 minted: Vec<Element>,
370}
371
372impl Formatter<'_> {
373 fn held(&self, node: &Node, path: &mut Vec<usize>) -> Held {
374 match node {
375 Node::Comment(_) | Node::ProcessingInstruction(_) => Held::Neutral,
376 Node::Element(e) if is_inline_container(e) => {
377 let (mut whole, mut some) = (true, false);
378 for (index, child) in e.children.iter().enumerate() {
379 path.push(index);
380 match self.held(child, path) {
381 Held::Whole => some = true,
382 Held::Part => (some, whole) = (true, false),
383 Held::Outside => whole = false,
384 Held::Neutral => {}
385 }
386 path.pop();
387 }
388 match (some, whole) {
389 (true, true) => Held::Whole,
390 (true, false) => Held::Part,
391 _ => Held::Outside,
392 }
393 }
394 _ if self.selected.contains(path) => Held::Whole,
395 _ => Held::Outside,
396 }
397 }
398
399 fn within(&mut self, element: &mut Element, path: &mut Vec<usize>, context: bool) {
404 let held: Vec<Held> = (0..element.children.len())
405 .map(|index| {
406 path.push(index);
407 let held = self.held(&element.children[index], path);
408 path.pop();
409 held
410 })
411 .collect();
412 let mut out = Vec::with_capacity(element.children.len());
413 let mut run = Vec::new();
414 let mut waiting = Vec::new();
416 for (index, (node, held)) in std::mem::take(&mut element.children)
417 .into_iter()
418 .zip(held)
419 .enumerate()
420 {
421 path.push(index);
422 let contrary = matches!(&node, Node::Element(e)
423 if is_inline_container(e) && own(e, self.mark, self.styles) == Some(!self.on));
424 match (held, node) {
425 (Held::Neutral, node) if !run.is_empty() => waiting.push(node),
426 (Held::Whole, mut node) if !contrary => {
427 if let Node::Element(e) = &mut node
430 && is_inline_container(e)
431 {
432 self.within(e, path, self.on);
433 }
434 run.append(&mut waiting);
435 run.push(node);
436 }
437 (held, node) => {
438 self.close(&mut run, &mut out, context);
439 out.append(&mut waiting);
440 match (held, node) {
441 (Held::Whole, Node::Element(mut e)) if e.is(&Ns::Text, "span") => {
442 self.within(&mut e, path, self.on);
443 if self.restyle(&mut e, context) {
444 out.extend(e.children);
445 } else {
446 out.push(Node::Element(e));
447 }
448 }
449 (Held::Whole, Node::Element(mut e)) => {
452 self.within(&mut e, path, !self.on);
453 out.push(Node::Element(e));
454 }
455 (Held::Part, Node::Element(mut e)) => {
456 let inner = own(&e, self.mark, self.styles).unwrap_or(context);
457 self.within(&mut e, path, inner);
458 out.push(Node::Element(e));
459 }
460 (_, node) => out.push(node),
461 }
462 }
463 }
464 path.pop();
465 }
466 self.close(&mut run, &mut out, context);
467 out.append(&mut waiting);
468 element.children = out;
469 }
470
471 fn close(&mut self, run: &mut Vec<Node>, out: &mut Vec<Node>, context: bool) {
474 if run.is_empty() {
475 return;
476 }
477 if context != self.on {
478 if let [Node::Element(span)] = run.as_mut_slice()
479 && span.is(&Ns::Text, "span")
480 {
481 if own(span, self.mark, self.styles) != Some(self.on) {
482 self.restyle(span, context);
484 }
485 } else {
486 let mut style = self.blank(None);
487 self.write_mark(&mut style);
488 let name = self.name(style);
489 let mut span = element(Name::new(&self.text, "span", Ns::Text));
490 span.set_attr(Name::new(&self.text, "style-name", Ns::Text), name);
491 span.children = std::mem::take(run);
492 span.self_closing = false;
493 out.push(Node::Element(span));
494 return;
495 }
496 }
497 out.append(run);
498 }
499
500 fn restyle(&mut self, span: &mut Element, context: bool) -> bool {
510 let current = span.attr(&Ns::Text, "style-name").map(ToOwned::to_owned);
511 let style = match current.as_deref().and_then(|n| self.automatic(n)) {
512 Some(existing) => existing.clone(),
513 None => self.blank(current.as_deref()),
514 };
515 let mut written = style.clone();
516 self.write_mark(&mut written);
517 let mut bare = style;
518 if let Some(properties) = bare.child_mut(&Ns::Style, "text-properties") {
519 for (ns, local) in self.mark.attributes() {
520 properties.remove_attr(&ns, local);
521 }
522 }
523 bare.children.retain(|n| {
524 !matches!(n, Node::Element(e)
525 if e.is(&Ns::Style, "text-properties") && e.attrs.is_empty() && e.children.is_empty())
526 });
527 let parent = bare.attr(&Ns::Style, "parent-style-name");
531 let bare_reads = self
532 .mark
533 .of(&self
534 .styles
535 .resolve(&Family::Text, parent.unwrap_or(""))
536 .text)
537 .unwrap_or(context);
538
539 let style = if bare_reads != self.on {
540 written
541 } else if parent.is_none() && bare.elements().next().is_none() {
542 span.remove_attr(&Ns::Text, "style-name");
543 return span.attrs.is_empty();
544 } else if self.existing(&bare).is_none() && self.existing(&written).is_some() {
545 written
546 } else {
547 bare
548 };
549 let name = self.name(style);
550 match span
551 .attrs
552 .iter_mut()
553 .find(|a| a.name.is(&Ns::Text, "style-name"))
554 {
555 Some(attr) => attr.value = name,
556 None => span.set_attr(Name::new(&self.text, "style-name", Ns::Text), name),
557 }
558 false
559 }
560
561 fn write_mark(&self, style: &mut Element) {
565 if style.child(&Ns::Style, "text-properties").is_none() {
566 let properties = element(self.root.name_for(&Ns::Style, "text-properties"));
567 style.children.push(Node::Element(properties));
568 style.self_closing = false;
569 }
570 let Some(properties) = style.child_mut(&Ns::Style, "text-properties") else {
571 return;
572 };
573 let written = self.mark.written(self.on);
574 for (ns, local) in self.mark.attributes() {
575 if !written.iter().any(|(n, l, _)| *n == ns && *l == local) {
576 properties.remove_attr(&ns, local);
577 }
578 }
579 for (ns, local, value) in written {
580 match properties.attrs.iter_mut().find(|a| a.name.is(&ns, local)) {
581 Some(attr) => value.clone_into(&mut attr.value),
582 None => properties.set_attr(self.root.name_for(&ns, local), value),
583 }
584 }
585 }
586
587 fn blank(&self, parent: Option<&str>) -> Element {
589 let mut style = element(self.root.name_for(&Ns::Style, "style"));
590 style.set_attr(self.root.name_for(&Ns::Style, "family"), "text");
591 if let Some(parent) = parent {
592 style.set_attr(self.root.name_for(&Ns::Style, "parent-style-name"), parent);
593 }
594 style
595 }
596
597 fn automatic(&self, name: &str) -> Option<&Element> {
598 self.automatic
599 .iter()
600 .find(|s| s.attr(&Ns::Style, "name") == Some(name))
601 }
602
603 fn existing(&self, style: &Element) -> Option<&str> {
605 let wanted = unnamed(style);
606 self.automatic
607 .iter()
608 .find(|s| unnamed(s) == wanted)
609 .and_then(|s| s.attr(&Ns::Style, "name"))
610 }
611
612 fn name(&mut self, mut style: Element) -> String {
615 if let Some(name) = self.existing(&style) {
616 return name.to_owned();
617 }
618 let taken =
619 |n: &str| self.styles.style(&Family::Text, n).is_some() || self.automatic(n).is_some();
620 let mut number = 1;
621 while taken(&format!("T{number}")) {
622 number += 1;
623 }
624 let name = format!("T{number}");
625 style.remove_attr(&Ns::Style, "name");
626 style.attrs.insert(
627 0,
628 Attribute {
629 name: self.root.name_for(&Ns::Style, "name"),
630 value: name.clone(),
631 },
632 );
633 self.styles.add(&style);
634 self.automatic.push(style.clone());
635 self.minted.push(style);
636 name
637 }
638}
639
640pub(super) fn unnamed(style: &Element) -> Element {
642 let mut style = style.clone();
643 style.remove_attr(&Ns::Style, "name");
644 style
645}