mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-09 11:50:20 -06:00
ODT: text can turn off what its style turns on (gap 19) (#5724)
* ODT gap 19: text can turn off what its style turns on A word not bold in a bold heading showed bold on screen, and came back bold in LibreOffice after a save: an "off" on a run was dropped because the run's own formatting said nothing, so the paragraph's or character style's showed through. Keep it on the run for bold, italic, underline, strike-through, capitals and small capitals, and write it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: an Asian or complex normal says nothing of Latin text (gap 19) With "off" now kept on the text, `style:font-weight-complex="normal"` alone took a word's bold in a bold heading, and saved it as `fo:font-weight="normal"`. Those attributes stay fallbacks only to turn it on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: strike-through turned off over a double one stays off (gap 19) A style or span saying line-through none over a double strike still showed the double strike, on screen and after a save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Docs: a missing full stop in the ODT revision row (gap 19) Docs only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: a span's character style wins over its paragraph turning off (gap 19) A paragraph saying not bold made a span in a bold character style not bold too, on screen and after a save. The span's style now wins, for bold, italic, underline, strike-through and capitals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Gap 19: docs status lines are main's, with this change in front Merging main had kept this branch's older status lines in ROADMAP.md and docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped main's newer entries and the parity doc's CLI path note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <FredApps@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
FredApps
parent
29a5abf3e7
commit
92eac2aa7c
+3
-1
File diff suppressed because one or more lines are too long
@@ -116,11 +116,15 @@ fn run_fmt(doc: &Document, props: &CharProps, in_code: bool, opts: FlowOpts) ->
|
||||
fn extra_of(e: &CharProps) -> Option<Box<CharProps>> {
|
||||
let set = |v: Option<bool>| v.filter(|b| *b);
|
||||
let x = CharProps {
|
||||
underline: e.underline.filter(|u| !matches!(u, Underline::None | Underline::Single)),
|
||||
// What it turns off, over what the style of its paragraph or span turns on, too.
|
||||
bold: e.bold.filter(|b| !*b),
|
||||
italic: e.italic.filter(|b| !*b),
|
||||
strike: e.strike.filter(|b| !*b),
|
||||
underline: e.underline.filter(|u| *u != Underline::Single),
|
||||
underline_color: e.underline_color,
|
||||
double_strike: set(e.double_strike),
|
||||
caps: set(e.caps),
|
||||
small_caps: set(e.small_caps),
|
||||
caps: e.caps,
|
||||
small_caps: e.small_caps,
|
||||
hidden: set(e.hidden),
|
||||
spacing: e.spacing.filter(|v| *v != 0.0 && v.is_finite()),
|
||||
scale: e.scale.filter(|v| *v != 100.0 && v.is_finite()),
|
||||
|
||||
@@ -50,6 +50,20 @@ impl WithExtra for CharProps {
|
||||
if f.underline {
|
||||
self.underline_color = x.underline_color;
|
||||
}
|
||||
// Turned off on the text, over what the style of its paragraph or span turns on.
|
||||
if !f.bold && x.bold == Some(false) {
|
||||
self.bold = Some(false);
|
||||
}
|
||||
if !f.italic && x.italic == Some(false) {
|
||||
self.italic = Some(false);
|
||||
}
|
||||
if !f.strike && x.strike == Some(false) {
|
||||
// A double strike under it goes too.
|
||||
(self.strike, self.double_strike) = (Some(false), Some(false));
|
||||
}
|
||||
if !f.underline && x.underline == Some(Underline::None) {
|
||||
self.underline = Some(Underline::None);
|
||||
}
|
||||
if x.double_strike == Some(true) {
|
||||
(self.strike, self.double_strike) = (None, Some(true));
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ fn turned_off(mut chr: wordcraft_doc::props::CharProps, off: [bool; 4]) -> wordc
|
||||
chr.underline = Some(wordcraft_doc::props::Underline::None);
|
||||
}
|
||||
if strike {
|
||||
chr.strike = Some(false);
|
||||
(chr.strike, chr.double_strike) = (Some(false), Some(false));
|
||||
}
|
||||
chr
|
||||
}
|
||||
|
||||
@@ -39,6 +39,26 @@ impl FmtDelta {
|
||||
($($k:ident),*) => { $( if let Some(v) = self.$k.clone() { f.$k = v; } )* };
|
||||
}
|
||||
set!(bold, italic, underline, strike, sup, sub, code);
|
||||
// What this turns off stays said on the text, or the style of its paragraph or span would
|
||||
// show through; turned back on, it no longer is.
|
||||
if self.bold.is_some() || self.italic.is_some() || self.underline.is_some() || self.strike.is_some() {
|
||||
use wordcraft_doc::props::Underline;
|
||||
let mut x = f.extra.take().map(|b| *b).unwrap_or_default();
|
||||
if self.strike == Some(false) {
|
||||
x.double_strike = None;
|
||||
}
|
||||
for (state, said) in [(self.bold, &mut x.bold), (self.italic, &mut x.italic), (self.strike, &mut x.strike)] {
|
||||
if let Some(on) = state {
|
||||
*said = (!on).then_some(false);
|
||||
}
|
||||
}
|
||||
match self.underline {
|
||||
Some(false) => x.underline = Some(Underline::None),
|
||||
Some(true) if x.underline == Some(Underline::None) => x.underline = None,
|
||||
_ => {}
|
||||
}
|
||||
f.extra = (x != wordcraft_doc::props::CharProps::default()).then(|| Box::new(x));
|
||||
}
|
||||
if self.color.is_some() {
|
||||
f.color = self.color;
|
||||
}
|
||||
@@ -61,10 +81,12 @@ impl FmtDelta {
|
||||
let size = f.size.unwrap_or(12.0);
|
||||
x.position = Some((r / 100.0 * size).clamp(-1584.0, 1584.0)).filter(|p| *p != 0.0);
|
||||
}
|
||||
// An explicit "off" only cancels what a parent style set.
|
||||
for b in
|
||||
[&mut x.double_strike, &mut x.caps, &mut x.small_caps, &mut x.hidden, &mut x.outline, &mut x.shadow, &mut x.emboss, &mut x.engrave]
|
||||
{
|
||||
// An explicit "off" only cancels what a parent style set (capitals are kept: a
|
||||
// paragraph's or span's style may set them too).
|
||||
if self.strike == Some(false) {
|
||||
x.double_strike = None;
|
||||
}
|
||||
for b in [&mut x.double_strike, &mut x.hidden, &mut x.outline, &mut x.shadow, &mut x.emboss, &mut x.engrave] {
|
||||
if *b == Some(false) {
|
||||
*b = None;
|
||||
}
|
||||
@@ -812,13 +834,18 @@ fn text_properties(a: &Attrs, style: &mut StyleInfo, faces: &HashMap<String, Str
|
||||
let weight = weight.trim();
|
||||
weight.eq_ignore_ascii_case("bold") || weight.parse::<u32>().is_ok_and(|n| n >= 600)
|
||||
};
|
||||
// Latin attributes take precedence; Asian and complex-script attributes are fallbacks.
|
||||
if let Some(weight) = get(a, "fo:font-weight").or_else(|| get(a, "style:font-weight-asian")).or_else(|| get(a, "style:font-weight-complex")) {
|
||||
// Latin attributes take precedence; Asian and complex-script attributes are fallbacks, to turn
|
||||
// it on: one of those saying normal says nothing of Latin text.
|
||||
if let Some(weight) = get(a, "fo:font-weight") {
|
||||
fmt.bold = Some(is_bold(weight));
|
||||
} else if get(a, "style:font-weight-asian").or_else(|| get(a, "style:font-weight-complex")).is_some_and(is_bold) {
|
||||
fmt.bold = Some(true);
|
||||
}
|
||||
let is_italic = |value: &str| value.trim().eq_ignore_ascii_case("italic") || value.trim().eq_ignore_ascii_case("oblique");
|
||||
if let Some(value) = get(a, "fo:font-style").or_else(|| get(a, "style:font-style-asian")).or_else(|| get(a, "style:font-style-complex")) {
|
||||
if let Some(value) = get(a, "fo:font-style") {
|
||||
fmt.italic = Some(is_italic(value));
|
||||
} else if get(a, "style:font-style-asian").or_else(|| get(a, "style:font-style-complex")).is_some_and(is_italic) {
|
||||
fmt.italic = Some(true);
|
||||
}
|
||||
let is_not_none = |value: &str| !value.trim().eq_ignore_ascii_case("none");
|
||||
if let Some(value) = get(a, "style:text-underline-style").or_else(|| get(a, "style:text-underline-type")) {
|
||||
@@ -1326,6 +1353,32 @@ impl Styles {
|
||||
let chain = self.chain(name);
|
||||
let Some(at) = chain.iter().position(|(n, _)| self.is_named_text(n)) else { return self.text_fmt(name, base) };
|
||||
let mut f = base.clone();
|
||||
// What the span's character style says is its own: its paragraph turning that off doesn't
|
||||
// reach past it (ODF: the span's style wins).
|
||||
if let Some(x) = f.extra.as_deref_mut() {
|
||||
use wordcraft_doc::props::Underline;
|
||||
for (_, s) in chain.iter().skip(at) {
|
||||
let (d, e) = (&s.fmt, &s.fmt.extra);
|
||||
if d.bold.is_some() && x.bold == Some(false) {
|
||||
x.bold = None;
|
||||
}
|
||||
if d.italic.is_some() && x.italic == Some(false) {
|
||||
x.italic = None;
|
||||
}
|
||||
if d.underline.is_some() && x.underline == Some(Underline::None) {
|
||||
x.underline = None;
|
||||
}
|
||||
if (d.strike.is_some() || e.double_strike.is_some()) && x.strike == Some(false) {
|
||||
(x.strike, x.double_strike) = (None, None);
|
||||
}
|
||||
if e.caps.is_some() && x.caps == Some(false) {
|
||||
x.caps = None;
|
||||
}
|
||||
if e.small_caps.is_some() && x.small_caps == Some(false) {
|
||||
x.small_caps = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (_, s) in chain.iter().take(at).rev() {
|
||||
s.fmt.apply(&mut f);
|
||||
}
|
||||
|
||||
@@ -285,6 +285,128 @@ fn objects_in_hidden_text_are_kept() {
|
||||
assert!(p.objects.iter().any(|o| matches!(o, wordcraft_doc::para::InlineObject::BookmarkStart { name } if name == "Secret")), "{:?}", p.objects);
|
||||
}
|
||||
|
||||
/// A paragraph in style `style` holding `span`, with `auto` among its automatic styles.
|
||||
fn off_doc(styles: &str, style: &str, auto: &str, span: &str) -> Document {
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}><office:automatic-styles>{auto}</office:automatic-styles><office:body><office:text><text:p text:style-name="{style}">{span}</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let styles = format!("<office:document-styles {NS_T}><office:styles>{styles}</office:styles></office:document-styles>");
|
||||
import(&crate::odt::tests::package(&content, Some(&styles))).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_turning_off_what_its_style_turns_on_stays_off() {
|
||||
// A word not bold in a bold heading: it showed bold, on screen and after a save.
|
||||
let on = r#"fo:font-weight="bold" fo:font-style="italic" style:text-underline-style="solid" style:text-line-through-style="solid" fo:font-variant="small-caps" fo:text-transform="uppercase""#;
|
||||
let off = r#"fo:font-weight="normal" fo:font-style="normal" style:text-underline-style="none" style:text-line-through-style="none" fo:font-variant="normal" fo:text-transform="none""#;
|
||||
let styles = format!(r#"<style:style style:name="Laut" style:family="paragraph"><style:text-properties {on}/></style:style>"#);
|
||||
let auto = format!(r#"<style:style style:name="T1" style:family="text"><style:text-properties {off}/></style:style>"#);
|
||||
let check = |d: &Document, what: &str| {
|
||||
let p = d.body[0].as_para().unwrap();
|
||||
let shown = |run: usize| d.styles.resolve_char(p.props.style.as_deref(), &p.runs[run].props);
|
||||
assert_eq!(p.text, "onoff", "{what}");
|
||||
let (a, b) = (shown(0), shown(1));
|
||||
assert_eq!((a.bold, a.italic, a.strike, a.small_caps, a.caps), (true, true, true, true, true), "{what}: the style's");
|
||||
assert!(a.underline != Underline::None, "{what}");
|
||||
assert_eq!((b.bold, b.italic, b.strike, b.small_caps, b.caps), (false, false, false, false, false), "{what}: turned off");
|
||||
assert_eq!(b.underline, Underline::None, "{what}");
|
||||
};
|
||||
let d = off_doc(&styles, "Laut", &auto, r#"on<text:span text:style-name="T1">off</text:span>"#);
|
||||
check(&d, "read");
|
||||
check(&import(&crate::odt::tests::export_model(&d).unwrap()).unwrap(), "written from the model");
|
||||
check(&import(&export(&d).unwrap()).unwrap(), "written as read");
|
||||
// Turned back on inside it: on.
|
||||
let auto = format!(
|
||||
r#"{auto}<style:style style:name="T2" style:family="text"><style:text-properties fo:font-weight="bold" style:text-underline-style="solid"/></style:style>"#
|
||||
);
|
||||
let d = off_doc(&styles, "Laut", &auto, r#"<text:span text:style-name="T1"><text:span text:style-name="T2">x</text:span></text:span>"#);
|
||||
let p = d.body[0].as_para().unwrap();
|
||||
let x = d.styles.resolve_char(p.props.style.as_deref(), &p.runs[0].props);
|
||||
assert!(x.bold);
|
||||
assert!(x.underline != Underline::None, "{x:?}");
|
||||
assert!(!x.italic, "still off");
|
||||
// A character style's: off in the automatic style based on it.
|
||||
let styles = r#"<style:style style:name="Zeichen" style:family="text"><style:text-properties fo:font-weight="bold" style:text-underline-style="solid"/></style:style>"#;
|
||||
let auto = r#"<style:style style:name="T3" style:family="text" style:parent-style-name="Zeichen"><style:text-properties fo:font-weight="normal" style:text-underline-style="none"/></style:style>"#;
|
||||
let d = off_doc(styles, "Standard", auto, r#"<text:span text:style-name="T3">x</text:span>"#);
|
||||
let p = d.body[0].as_para().unwrap();
|
||||
let x = d.styles.resolve_char(p.props.style.as_deref(), &p.runs[0].props);
|
||||
assert_eq!((x.bold, x.underline), (false, Underline::None), "{x:?}");
|
||||
// Text nothing turned on says nothing.
|
||||
let d = off_doc("", "Standard", "", "plain");
|
||||
let c = crate::odt::tests::content_of(&crate::odt::tests::export_model(&d).unwrap());
|
||||
assert!(!c.contains(r#"fo:font-weight="normal""#) && !c.contains(r#"style:text-underline-style="none""#), "{c}");
|
||||
}
|
||||
|
||||
/// A weight or slant only for Asian or complex-script text says nothing of Latin text when it is
|
||||
/// normal: the heading's bold stays, on screen and saved. Latin text's own "normal" still counts.
|
||||
#[test]
|
||||
fn an_asian_or_complex_normal_doesnt_turn_latin_text_off() {
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {NS_T}><office:styles><style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:default-outline-level="1"><style:text-properties fo:font-weight="bold" fo:font-style="italic"/></style:style></office:styles></office:document-styles>"#
|
||||
);
|
||||
let open = |attr: &str| {
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}><office:automatic-styles><style:style style:name="T1" style:family="text"><style:text-properties {attr}/></style:style></office:automatic-styles><office:body><office:text><text:h text:style-name="Heading_20_1" text:outline-level="1">Bold <text:span text:style-name="T1">word</text:span></text:h></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
import(&package(&content, Some(&styles))).unwrap()
|
||||
};
|
||||
let run = |d: &Document| d.body[0].as_para().unwrap().runs.last().unwrap().props.clone();
|
||||
for attr in [
|
||||
r#"style:font-weight-complex="normal""#,
|
||||
r#"style:font-weight-asian="normal""#,
|
||||
r#"style:font-style-complex="normal""#,
|
||||
r#"style:font-style-asian="normal""#,
|
||||
] {
|
||||
let d = open(attr);
|
||||
assert_eq!((run(&d).bold, run(&d).italic), (None, None), "{attr}");
|
||||
let saved = crate::odt::tests::content_of(&crate::odt::tests::export_model(&d).unwrap());
|
||||
assert!(!saved.contains(r#"fo:font-weight="normal""#) && !saved.contains(r#"fo:font-style="normal""#), "{attr}: {saved}");
|
||||
}
|
||||
assert_eq!(run(&open(r#"fo:font-weight="normal""#)).bold, Some(false));
|
||||
assert_eq!(run(&open(r#"fo:font-style="normal""#)).italic, Some(false));
|
||||
}
|
||||
|
||||
/// Strike-through turned off over a double one is off, not the double one again.
|
||||
#[test]
|
||||
fn strike_turned_off_over_a_double_strike_stays_off() {
|
||||
let double = r#"<style:text-properties style:text-line-through-style="solid" style:text-line-through-type="double"/>"#;
|
||||
let none = r#"<style:text-properties style:text-line-through-style="none"/>"#;
|
||||
let check = |d: &Document, what: &str| {
|
||||
let p = d.body[0].as_para().unwrap();
|
||||
let c = d.styles.resolve_char(p.props.style.as_deref(), &p.runs.last().unwrap().props);
|
||||
assert_eq!((c.strike, c.double_strike), (false, false), "{what}");
|
||||
};
|
||||
// A style over its parent.
|
||||
let styles = format!(
|
||||
r#"<style:style style:name="Parent" style:family="paragraph">{double}</style:style><style:style style:name="Kid" style:family="paragraph" style:parent-style-name="Parent">{none}</style:style>"#
|
||||
);
|
||||
let d = off_doc(&styles, "Kid", "", "One");
|
||||
check(&d, "a style, read");
|
||||
check(&import(&crate::odt::tests::export_model(&d).unwrap()).unwrap(), "a style, written from the model");
|
||||
// A span over its paragraph's style.
|
||||
let styles = format!(r#"<style:style style:name="Parent" style:family="paragraph">{double}</style:style>"#);
|
||||
let auto = format!(r#"<style:style style:name="T1" style:family="text">{none}</style:style>"#);
|
||||
let d = off_doc(&styles, "Parent", &auto, r#"<text:span text:style-name="T1">One</text:span>"#);
|
||||
check(&d, "a span, read");
|
||||
check(&import(&crate::odt::tests::export_model(&d).unwrap()).unwrap(), "a span, written from the model");
|
||||
}
|
||||
|
||||
/// A span's character style is its own: a paragraph turning bold or italic off doesn't reach past it.
|
||||
#[test]
|
||||
fn a_spans_character_style_wins_over_its_paragraph_turning_off() {
|
||||
let styles = r#"<style:style style:name="Standard" style:family="paragraph"/><style:style style:name="Strong_20_Emphasis" style:display-name="Strong Emphasis" style:family="text"><style:text-properties fo:font-weight="bold" fo:font-style="italic"/></style:style>"#;
|
||||
let auto = r#"<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"><style:text-properties fo:font-weight="normal" fo:font-style="normal"/></style:style>"#;
|
||||
let d = off_doc(styles, "P1", auto, r#"<text:span text:style-name="Strong_20_Emphasis">One</text:span>"#);
|
||||
let check = |d: &Document, what: &str| {
|
||||
let p = d.body[0].as_para().unwrap();
|
||||
let c = d.styles.resolve_char(p.props.style.as_deref(), &p.runs.last().unwrap().props);
|
||||
assert_eq!((c.bold, c.italic), (true, true), "{what}: {:?}", p.runs.last().unwrap().props);
|
||||
};
|
||||
check(&d, "read");
|
||||
check(&import(&crate::odt::tests::export_model(&d).unwrap()).unwrap(), "written from the model");
|
||||
}
|
||||
|
||||
/// `default` as a document's default paragraph style's text properties, with `standard` as its
|
||||
/// Standard style's.
|
||||
fn with_default_text(default: &str, standard: &str) -> Vec<u8> {
|
||||
|
||||
@@ -10,9 +10,13 @@ pub(in crate::odt) fn extra_props(x: &wordcraft_doc::props::CharProps, size: Opt
|
||||
let on = |v: Option<bool>| v == Some(true);
|
||||
if on(x.caps) {
|
||||
p.push_str(" fo:text-transform=\"uppercase\"");
|
||||
} else if x.caps == Some(false) {
|
||||
p.push_str(" fo:text-transform=\"none\"");
|
||||
}
|
||||
if on(x.small_caps) {
|
||||
p.push_str(" fo:font-variant=\"small-caps\"");
|
||||
} else if x.small_caps == Some(false) {
|
||||
p.push_str(" fo:font-variant=\"normal\"");
|
||||
}
|
||||
if on(x.hidden) {
|
||||
p.push_str(" text:display=\"none\"");
|
||||
@@ -78,28 +82,7 @@ pub(in crate::odt) fn props_text(cp: &wordcraft_doc::props::CharProps) -> String
|
||||
extra: Some(Box::new(cp.clone())),
|
||||
..Default::default()
|
||||
};
|
||||
let mut p = text_props(&f);
|
||||
// What a style turns off that the style it is based on may turn on: said so, or the parent's
|
||||
// bold, italic, underline or strike-through would show through.
|
||||
if cp.bold == Some(false) {
|
||||
p.push_str(" fo:font-weight=\"normal\" style:font-weight-asian=\"normal\"");
|
||||
if cp.bold_cs.is_none() {
|
||||
p.push_str(" style:font-weight-complex=\"normal\"");
|
||||
}
|
||||
}
|
||||
if cp.italic == Some(false) {
|
||||
p.push_str(" fo:font-style=\"normal\" style:font-style-asian=\"normal\"");
|
||||
if cp.italic_cs.is_none() {
|
||||
p.push_str(" style:font-style-complex=\"normal\"");
|
||||
}
|
||||
}
|
||||
if cp.underline == Some(Underline::None) {
|
||||
p.push_str(" style:text-underline-style=\"none\"");
|
||||
}
|
||||
if cp.strike == Some(false) && cp.double_strike != Some(true) {
|
||||
p.push_str(" style:text-line-through-style=\"none\"");
|
||||
}
|
||||
p
|
||||
text_props(&f)
|
||||
}
|
||||
|
||||
/// `fo:text-align` for `a` (ODF 1.3 §20.223).
|
||||
@@ -171,19 +154,32 @@ pub(in crate::odt) fn text_props(f: &Fmt) -> String {
|
||||
let mut p = String::new();
|
||||
// Complex-script weight and posture of their own are written with the other script extras.
|
||||
let own_cs = |pick: fn(&wordcraft_doc::props::CharProps) -> Option<bool>| f.extra.as_deref().and_then(pick).is_some();
|
||||
let extra = f.extra.as_deref();
|
||||
// What it turns off that its style, or the style that is based on, may turn on: said so, or
|
||||
// that bold, italic, underline or strike-through would show through.
|
||||
let off = |pick: fn(&wordcraft_doc::props::CharProps) -> Option<bool>| extra.and_then(pick) == Some(false);
|
||||
if f.bold {
|
||||
p.push_str(" fo:font-weight=\"bold\" style:font-weight-asian=\"bold\"");
|
||||
if !own_cs(|x| x.bold_cs) {
|
||||
p.push_str(" style:font-weight-complex=\"bold\"");
|
||||
}
|
||||
} else if off(|x| x.bold) {
|
||||
p.push_str(" fo:font-weight=\"normal\" style:font-weight-asian=\"normal\"");
|
||||
if !own_cs(|x| x.bold_cs) {
|
||||
p.push_str(" style:font-weight-complex=\"normal\"");
|
||||
}
|
||||
}
|
||||
if f.italic {
|
||||
p.push_str(" fo:font-style=\"italic\" style:font-style-asian=\"italic\"");
|
||||
if !own_cs(|x| x.italic_cs) {
|
||||
p.push_str(" style:font-style-complex=\"italic\"");
|
||||
}
|
||||
} else if off(|x| x.italic) {
|
||||
p.push_str(" fo:font-style=\"normal\" style:font-style-asian=\"normal\"");
|
||||
if !own_cs(|x| x.italic_cs) {
|
||||
p.push_str(" style:font-style-complex=\"normal\"");
|
||||
}
|
||||
}
|
||||
let extra = f.extra.as_deref();
|
||||
if f.underline {
|
||||
use wordcraft_doc::props::Underline as U;
|
||||
let (style, kind, width, mode) = match extra.and_then(|e| e.underline) {
|
||||
@@ -204,11 +200,16 @@ pub(in crate::odt) fn text_props(f: &Fmt) -> String {
|
||||
" style:text-underline-style=\"{style}\" style:text-underline-type=\"{kind}\" style:text-underline-width=\"{width}\" style:text-underline-color=\"{color}\"{mode}"
|
||||
));
|
||||
}
|
||||
if !f.underline && extra.is_some_and(|e| e.underline == Some(wordcraft_doc::props::Underline::None)) {
|
||||
p.push_str(" style:text-underline-style=\"none\"");
|
||||
}
|
||||
if f.strike || extra.is_some_and(|e| e.double_strike == Some(true)) {
|
||||
p.push_str(" style:text-line-through-style=\"solid\"");
|
||||
if extra.is_some_and(|e| e.double_strike == Some(true)) {
|
||||
p.push_str(" style:text-line-through-type=\"double\"");
|
||||
}
|
||||
} else if off(|x| x.strike) {
|
||||
p.push_str(" style:text-line-through-style=\"none\"");
|
||||
}
|
||||
if let Some(e) = extra {
|
||||
if (f.sup || f.sub) && e.position.is_some() {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user