mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-10 04:09:53 -06:00
ODT: pictures keep the size their file gives (gap 19) (#5721)
* ODT gap 19: pictures keep the size their file gives Opening a file fitted a picture wider than the text to the text, and shrank any picture wider than 6.5 in to that, even with its size stated. So a save with no edit to it took its paragraph off being written as read, and LibreOffice laid it out differently. Pictures in OpenDocument files keep the size they state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: a picture keeps a size stated on one side, in proportion (gap 19) Only a frame stating both sides kept them. On one side alone a picture past a column's width was still fitted to it, and past 1,584 pt each side was cut separately, which distorted it. A size past any page can no longer overflow the sizing either. Found by CodeRabbit on this PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: an inline picture with no stated size is fitted to the text (gap 19) Skipping the fit for OpenDocument files left one wider than the text on a small page, which `main` fits. A size the file states still stands, and a test now fails without that. The fit happens when it is read, so its paragraph stays as read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: an object's picture is fitted too, and one side follows the picture's own shape (gap 19) An inline object with no stated size kept its picture's width on a small page, which main fitted. A picture stated on one side took the other from the size already capped to the page: 10 by 1000 pixels at 500 pt wide came out 468 by 1584 pt, not 15.84 by 1584. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: a frame stating a size of nothing is fitted like one stating none (gap 19) svg:width="0pt" counted as a stated size for fitting, though the picture then came at its own size, so it could run past the text on a small page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ODT: a picture in line that is raised, turned or mirrored is fitted too (gap 19) Such a picture has a float record that is still in line, which the reader's fit skipped, so with no size stated it stayed 468 pt wide on a small page. main fitted it. 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> * Order the parity header's change notes newest first --------- 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
15b608660f
commit
bd9e726cda
+3
-1
File diff suppressed because one or more lines are too long
@@ -10,10 +10,15 @@ pub fn open(ext: &str, bytes: &[u8], page: &wordcraft_doc::SectionProps) -> Opti
|
||||
"doc" | "dot" => Some(wordcraft_docbin::read(bytes).map_err(|e| e.to_string())),
|
||||
// Word XML Document (Flat OPC) or Word 2003 XML, told apart by the root element.
|
||||
"xml" => Some(wordcraft_docx::read_xml(bytes).map_err(|e| e.to_string())),
|
||||
// HTML, Markdown, ODT, RTF…: pictures fit the text width (see `io::fit_pictures`).
|
||||
// HTML, Markdown, RTF…: pictures fit the text width (see `io::fit_pictures`). An
|
||||
// OpenDocument file says how big they are, and one wider than the text is meant to be;
|
||||
// changing it would take its paragraph off being written as read. Its reader fits those
|
||||
// that state no size.
|
||||
other => wordcraft_formats::import_on(other, bytes, page).map(|r| {
|
||||
r.map(|mut doc| {
|
||||
crate::io::fit_pictures(&mut doc);
|
||||
if !matches!(other, "odt" | "ott" | "fodt") {
|
||||
crate::io::fit_pictures(&mut doc);
|
||||
}
|
||||
draw_charts(&mut doc);
|
||||
doc
|
||||
})
|
||||
|
||||
@@ -12724,6 +12724,44 @@ fn an_odt_charts_paragraph_is_written_as_read_after_drawing_it() {
|
||||
assert!(content.contains(r#"draw:name="Chart1""#) && !content.contains("wcChart"), "{content}");
|
||||
}
|
||||
|
||||
/// A picture an OpenDocument file makes wider than the text (a rule across the whole page) stays
|
||||
/// that wide, and its paragraph is written as read: opening a file changes nothing in it.
|
||||
#[test]
|
||||
fn an_odts_picture_wider_than_the_text_keeps_its_size() {
|
||||
let ns = r#"xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink""#;
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {ns}><office:automatic-styles><style:page-layout style:name="pm"><style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm" fo:margin-left="3cm" fo:margin-right="3cm" fo:margin-top="2cm" fo:margin-bottom="2cm"/></style:page-layout></office:automatic-styles><office:master-styles><style:master-page style:name="Standard" style:page-layout-name="pm"/></office:master-styles></office:document-styles>"#
|
||||
);
|
||||
let content = format!(
|
||||
r#"<office:document-content {ns}><office:body><office:text><text:p><draw:frame draw:name="Rule" text:anchor-type="paragraph" svg:x="-1cm" svg:width="18cm" svg:height="0.2cm"><draw:image xlink:href="Pictures/r.png"/></draw:frame>Text</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let png = [b"\x89PNG\r\n\x1a\n".as_slice(), &[7u8; 64]].concat();
|
||||
let mut z = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
|
||||
let o = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
for (name, data) in [
|
||||
("mimetype", b"application/vnd.oasis.opendocument.text".as_slice()),
|
||||
("content.xml", content.as_bytes()),
|
||||
("styles.xml", styles.as_bytes()),
|
||||
("Pictures/r.png", &png),
|
||||
] {
|
||||
z.start_file(name, o).unwrap();
|
||||
std::io::Write::write_all(&mut z, data).unwrap();
|
||||
}
|
||||
let doc = crate::io::open_bytes("t.odt", &z.finish().unwrap().into_inner()).unwrap();
|
||||
let width = doc.body[0]
|
||||
.as_para()
|
||||
.unwrap()
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|o| if let wordcraft_doc::para::InlineObject::Image { w, .. } = o { Some(*w) } else { None });
|
||||
let want = 18.0 * 72.0 / 2.54;
|
||||
assert!(width.is_some_and(|w| (w - want).abs() < 0.1), "{width:?}, not fitted to the text");
|
||||
let mut z = zip::ZipArchive::new(std::io::Cursor::new(wordcraft_formats::odt::export(&doc).unwrap())).unwrap();
|
||||
let mut saved = String::new();
|
||||
std::io::Read::read_to_string(&mut z.by_name("content.xml").unwrap(), &mut saved).unwrap();
|
||||
assert!(saved.contains(r#"draw:name="Rule""#) && saved.contains(r#"svg:width="18cm""#), "written as read: {saved}");
|
||||
}
|
||||
|
||||
/// Numbering or a new style makes the styles be written from the model on save: a document in
|
||||
/// German stays in German, rather than taking the default language of what opens it next.
|
||||
#[test]
|
||||
@@ -12997,6 +13035,47 @@ fn page_down_extends_a_selection_that_ends_in_collapsed_outline_text() {
|
||||
assert!(s.layout().caret(&s.sel.focus).is_some(), "to a place the outline shows");
|
||||
}
|
||||
|
||||
/// A picture an OpenDocument file gives no size is shown at its own, fitted to the text as other
|
||||
/// formats' are; one with a stated size keeps it, in line too.
|
||||
#[test]
|
||||
fn an_odts_picture_is_fitted_only_when_it_states_no_size() {
|
||||
let ns = r#"xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink""#;
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {ns}><office:automatic-styles><style:page-layout style:name="pm"><style:page-layout-properties fo:page-width="14.8cm" fo:page-height="21cm" fo:margin-left="2cm" fo:margin-right="2cm" fo:margin-top="2cm" fo:margin-bottom="2cm"/></style:page-layout></office:automatic-styles><office:master-styles><style:master-page style:name="Standard" style:page-layout-name="pm"/></office:master-styles></office:document-styles>"#
|
||||
);
|
||||
let mut png = std::io::Cursor::new(Vec::new());
|
||||
image::RgbImage::new(1000, 500).write_to(&mut png, image::ImageFormat::Png).unwrap();
|
||||
let png = png.into_inner();
|
||||
let open = |attrs: &str| {
|
||||
let content = format!(
|
||||
r#"<office:document-content {ns}><office:body><office:text><text:p><draw:frame text:anchor-type="as-char"{attrs}><draw:image xlink:href="Pictures/a.png"/></draw:frame></text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let mut z = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
|
||||
let o = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
for (name, data) in [
|
||||
("mimetype", b"application/vnd.oasis.opendocument.text".as_slice()),
|
||||
("content.xml", content.as_bytes()),
|
||||
("styles.xml", styles.as_bytes()),
|
||||
("Pictures/a.png", &png),
|
||||
] {
|
||||
z.start_file(name, o).unwrap();
|
||||
std::io::Write::write_all(&mut z, data).unwrap();
|
||||
}
|
||||
let doc = crate::io::open_bytes("t.odt", &z.finish().unwrap().into_inner()).unwrap();
|
||||
doc.body[0]
|
||||
.as_para()
|
||||
.unwrap()
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|o| if let wordcraft_doc::para::InlineObject::Image { w, h, .. } = o { Some((*w, *h)) } else { None })
|
||||
.unwrap()
|
||||
};
|
||||
let (w, h) = open("");
|
||||
assert!((w - 306.14).abs() < 0.5 && (h - 153.07).abs() < 0.5, "fitted to the text: {w} {h}");
|
||||
let (w, _) = open(r#" svg:width="18cm" svg:height="9cm""#);
|
||||
assert!((w - 18.0 * 72.0 / 2.54).abs() < 0.5, "as the file says: {w}");
|
||||
}
|
||||
|
||||
/// Issue #5667's document: `Intro`, then an inserted `Appendix` paragraph whose mark carries a
|
||||
/// section break (Normal, indented), then `Body` in Heading 1. The whole of `Appendix` is
|
||||
/// tracked as an insertion by `rev`, or as a deletion when `delete`.
|
||||
|
||||
@@ -111,6 +111,13 @@ impl Body<'_> {
|
||||
self.frame = None;
|
||||
return;
|
||||
}
|
||||
// In line with no stated size, a picture comes at its own: fitted to the text, as
|
||||
// other formats' pictures are. A stated size is the file's.
|
||||
let room = self.sec.text_width();
|
||||
let says = |v: Option<f32>| v.is_some_and(|v| v.is_finite() && v > 0.0);
|
||||
// (Still in line when raised, turned or mirrored.)
|
||||
let in_line = float.as_ref().is_none_or(|f| f.wrap == wordcraft_doc::para::Wrap::Inline);
|
||||
let fit = (!says(w) && !says(h) && in_line && room.is_finite() && room > 0.0).then_some(room);
|
||||
let inline = match (boxed, eq) {
|
||||
(Some(blocks), _) => {
|
||||
let mut look = std::mem::take(&mut self.frame_look);
|
||||
@@ -149,12 +156,14 @@ impl Body<'_> {
|
||||
.and_then(|data| picture(data, w, h, alt.trim()))
|
||||
.or_else(|| crate::odt::objects::placeholder(&o.mime).and_then(|p| make_img(p, w, h, alt.trim())));
|
||||
shown.map(|mut img| {
|
||||
fit_to(&mut img, fit);
|
||||
img.float = float.map(Box::new);
|
||||
img.object = Some(std::sync::Arc::new(o));
|
||||
Inline::Image(img)
|
||||
})
|
||||
}
|
||||
(None, None) => img.and_then(|data| picture(data, w, h, alt.trim())).map(|mut img| {
|
||||
fit_to(&mut img, fit);
|
||||
img.float = float.map(Box::new);
|
||||
if let Some(clip) = self.frame_clip.take() {
|
||||
img.crop = crop_fractions(clip, &img.data);
|
||||
@@ -399,14 +408,50 @@ pub(in crate::odt) fn readable_picture(data: &[u8]) -> bool {
|
||||
/// A picture from a package's bytes: a raster image, or a Windows metafile (WMF, EMF: LibreOffice
|
||||
/// keeps clip art and logos so), sized `w` × `h` (points) when the frame says.
|
||||
pub(in crate::odt) fn picture(data: Vec<u8>, w: Option<f32>, h: Option<f32>, alt: &str) -> Option<model::Img> {
|
||||
if wordcraft_metafile::is_metafile(&data) {
|
||||
// What the frame states, if it could be a size (far past any page, kept from overflowing).
|
||||
let stated = |v: Option<f32>| v.filter(|v| v.is_finite() && *v > 0.0).map(|v| v.min(1e6));
|
||||
let (w, h) = (stated(w), stated(h));
|
||||
let mut img = if wordcraft_metafile::is_metafile(&data) {
|
||||
// An EMF starts with its header record, type 1.
|
||||
let ext = if data.get(..4) == Some(&[1, 0, 0, 0]) { "emf" } else { "wmf" };
|
||||
// Its own size, when the frame gives none: a placeable WMF's or an EMF's frame, points.
|
||||
let natural = if w.is_some() && h.is_some() { None } else { metafile_size(&data) };
|
||||
return Some(model::sized_img(std::sync::Arc::new(data), ext, natural, w, h, alt));
|
||||
model::sized_img(std::sync::Arc::new(data), ext, natural, w, h, alt)
|
||||
} else {
|
||||
make_img(data, w, h, alt)?
|
||||
};
|
||||
// A size the frame states is the file's, however large (a picture across a whole page): only
|
||||
// a picture with none is shrunk to a column. Stated on one side, the other follows the
|
||||
// picture's proportions.
|
||||
let (w, h) = match (w, h) {
|
||||
(Some(w), Some(h)) => (w, h),
|
||||
(Some(w), None) => (w, w * height_per_width(&img.data)),
|
||||
(None, Some(h)) => (h / height_per_width(&img.data), h),
|
||||
(None, None) => return Some(img),
|
||||
};
|
||||
// Past what a document holds, it shrinks in proportion.
|
||||
let (w, h) = match w.max(h) {
|
||||
most if most <= 1584.0 => (w, h),
|
||||
_ if w >= h => (1584.0, h * 1584.0 / w),
|
||||
_ => (w * 1584.0 / h, 1584.0),
|
||||
};
|
||||
(img.w, img.h) = (w.max(1.0), h.max(1.0));
|
||||
Some(img)
|
||||
}
|
||||
|
||||
/// `img` shrunk in proportion to `room` points wide, when it's given and the picture is wider.
|
||||
fn fit_to(img: &mut model::Img, room: Option<f32>) {
|
||||
if let Some(room) = room.filter(|room| img.w > *room) {
|
||||
img.h = (img.h * room / img.w).max(1.0);
|
||||
img.w = room.max(1.0);
|
||||
}
|
||||
make_img(data, w, h, alt)
|
||||
}
|
||||
|
||||
/// A picture's height for each point of its width, by its own size, before anything caps it (1
|
||||
/// when it doesn't say).
|
||||
fn height_per_width(data: &[u8]) -> f32 {
|
||||
let own = if wordcraft_metafile::is_metafile(data) { metafile_size(data) } else { model::image_px(data).map(|(w, h)| (w as f32, h as f32)) };
|
||||
own.filter(|(w, h)| w.is_finite() && h.is_finite() && *w > 0.0 && *h > 0.0).map_or(1.0, |(w, h)| h / w)
|
||||
}
|
||||
|
||||
/// A metafile's own size, points, from its header: a placeable WMF's bounding box in its units
|
||||
|
||||
@@ -205,6 +205,81 @@ fn object_without_mathml_falls_back_to_its_replacement_image() {
|
||||
assert!(imgs[0].data.as_slice() == png.as_slice());
|
||||
}
|
||||
|
||||
/// A picture of `w` by `h` pixels, as a PNG.
|
||||
fn png_of(w: u32, h: u32) -> Vec<u8> {
|
||||
let mut png = Cursor::new(Vec::new());
|
||||
image::RgbImage::new(w, h).write_to(&mut png, image::ImageFormat::Png).unwrap();
|
||||
png.into_inner()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_picture_stated_on_one_side_keeps_its_own_proportions() {
|
||||
// A hundred times as tall as wide: only the cap on a page's size may shrink it, in proportion.
|
||||
let frame = r#"<draw:frame text:anchor-type="as-char" svg:width="500pt"><draw:image xlink:href="Pictures/a.png"/></draw:frame>"#;
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}><office:body><office:text><text:p>{frame}</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let pkg = zip_of(&[("content.xml", content.as_bytes()), ("Pictures/a.png", &png_of(10, 1000))]);
|
||||
let flow = parse(&pkg).unwrap();
|
||||
let img = paras(&flow).into_iter().flat_map(|p| &p.inlines).find_map(|i| if let Inline::Image(img) = i { Some(img) } else { None }).unwrap();
|
||||
assert!((img.w - 15.84).abs() < 0.01 && (img.h - 1584.0).abs() < 0.01, "{} {}", img.w, img.h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_objects_picture_with_no_stated_size_is_fitted_to_the_text() {
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {NS_T}><office:automatic-styles><style:page-layout style:name="pm"><style:page-layout-properties fo:page-width="14.8cm" fo:page-height="21cm" fo:margin-left="2cm" fo:margin-right="2cm" fo:margin-top="2cm" fo:margin-bottom="2cm"/></style:page-layout></office:automatic-styles><office:master-styles><style:master-page style:name="Standard" style:page-layout-name="pm"/></office:master-styles></office:document-styles>"#
|
||||
);
|
||||
let frame =
|
||||
r#"<draw:frame text:anchor-type="as-char"><draw:object xlink:href="./Object 1"/><draw:image xlink:href="Pictures/a.png"/></draw:frame>"#;
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}><office:body><office:text><text:p>{frame}</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let pkg = zip_of(&[
|
||||
("content.xml", content.as_bytes()),
|
||||
("styles.xml", styles.as_bytes()),
|
||||
("Object 1/content.xml", b"<office:spreadsheet/>"),
|
||||
("Pictures/a.png", &png_of(1000, 500)),
|
||||
]);
|
||||
let flow = parse(&pkg).unwrap();
|
||||
let img = paras(&flow).into_iter().flat_map(|p| &p.inlines).find_map(|i| if let Inline::Image(img) = i { Some(img) } else { None }).unwrap();
|
||||
assert!(img.object.is_some(), "still the object");
|
||||
assert!((img.w - 306.14).abs() < 0.5 && (img.h - 153.07).abs() < 0.5, "{} {}", img.w, img.h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_frame_stating_a_size_of_nothing_is_fitted_to_the_text() {
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {NS_T}><office:automatic-styles><style:page-layout style:name="pm"><style:page-layout-properties fo:page-width="14.8cm" fo:page-height="21cm" fo:margin-left="2cm" fo:margin-right="2cm" fo:margin-top="2cm" fo:margin-bottom="2cm"/></style:page-layout></office:automatic-styles><office:master-styles><style:master-page style:name="Standard" style:page-layout-name="pm"/></office:master-styles></office:document-styles>"#
|
||||
);
|
||||
let frame = r#"<draw:frame text:anchor-type="as-char" svg:width="0pt"><draw:image xlink:href="Pictures/a.png"/></draw:frame>"#;
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}><office:body><office:text><text:p>{frame}</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let pkg = zip_of(&[("content.xml", content.as_bytes()), ("styles.xml", styles.as_bytes()), ("Pictures/a.png", &png_of(1000, 500))]);
|
||||
let flow = parse(&pkg).unwrap();
|
||||
let img = paras(&flow).into_iter().flat_map(|p| &p.inlines).find_map(|i| if let Inline::Image(img) = i { Some(img) } else { None }).unwrap();
|
||||
assert!((img.w - 306.14).abs() < 0.5 && (img.h - 153.07).abs() < 0.5, "{} {}", img.w, img.h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_picture_in_line_raised_or_turned_with_no_size_is_fitted_to_the_text() {
|
||||
let styles = format!(
|
||||
r#"<office:document-styles {NS_T}><office:automatic-styles><style:page-layout style:name="pm"><style:page-layout-properties fo:page-width="14.8cm" fo:page-height="21cm" fo:margin-left="2cm" fo:margin-right="2cm" fo:margin-top="2cm" fo:margin-bottom="2cm"/></style:page-layout></office:automatic-styles><office:master-styles><style:master-page style:name="Standard" style:page-layout-name="pm"/></office:master-styles></office:document-styles>"#
|
||||
);
|
||||
let auto = r#"<office:automatic-styles><style:style style:name="fr1" style:family="graphic"><style:graphic-properties style:vertical-pos="middle" style:vertical-rel="char"/></style:style></office:automatic-styles>"#;
|
||||
for attrs in [r#" draw:style-name="fr1""#, r#" draw:transform="rotate (0.5) translate (0cm 0cm)""#] {
|
||||
let frame = format!(r#"<draw:frame text:anchor-type="as-char"{attrs}><draw:image xlink:href="Pictures/a.png"/></draw:frame>"#);
|
||||
let content = format!(
|
||||
r#"<office:document-content {NS_T}>{auto}<office:body><office:text><text:p>{frame}</text:p></office:text></office:body></office:document-content>"#
|
||||
);
|
||||
let pkg = zip_of(&[("content.xml", content.as_bytes()), ("styles.xml", styles.as_bytes()), ("Pictures/a.png", &png_of(1000, 500))]);
|
||||
let flow = parse(&pkg).unwrap();
|
||||
let img = paras(&flow).into_iter().flat_map(|p| &p.inlines).find_map(|i| if let Inline::Image(img) = i { Some(img) } else { None }).unwrap();
|
||||
assert!(img.w <= 306.5, "{attrs}: {} {}", img.w, img.h);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const NS_T: &str = r#"xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0""#;
|
||||
|
||||
/// A package with `content.xml` and, when given, `styles.xml`.
|
||||
|
||||
@@ -168,6 +168,38 @@ fn an_object_in_a_header_is_kept_too() {
|
||||
assert!(entry(&saved, "styles.xml").contains("<draw:object xlink:href=\"./Object M"));
|
||||
}
|
||||
|
||||
/// A frame's stated size goes before fitting a column: on either side alone the other follows the
|
||||
/// picture's proportions, and only past what a document holds does it shrink, in proportion.
|
||||
#[test]
|
||||
fn a_picture_keeps_the_size_its_frame_states_on_either_side() {
|
||||
let png = |w: u32, h: u32| {
|
||||
let mut out = std::io::Cursor::new(Vec::new());
|
||||
image::RgbImage::new(w, h).write_to(&mut out, image::ImageFormat::Png).unwrap();
|
||||
out.into_inner()
|
||||
};
|
||||
let size = |w: Option<f32>, h: Option<f32>| {
|
||||
let img = crate::odt::body::drawing::picture(png(200, 100), w, h, "").unwrap();
|
||||
(img.w, img.h)
|
||||
};
|
||||
let near = |got: (f32, f32), want: (f32, f32)| assert!((got.0 - want.0).abs() < 0.01 && (got.1 - want.1).abs() < 0.01, "{got:?}, not {want:?}");
|
||||
// Both, one or the other, past a column's 468 pt.
|
||||
near(size(Some(600.0), Some(100.0)), (600.0, 100.0));
|
||||
near(size(Some(600.0), None), (600.0, 300.0));
|
||||
near(size(None, Some(400.0)), (800.0, 400.0));
|
||||
// Past what a document holds: in proportion, whichever side is stated.
|
||||
near(size(Some(2000.0), Some(1000.0)), (1584.0, 792.0));
|
||||
near(size(Some(2000.0), None), (1584.0, 792.0));
|
||||
near(size(None, Some(1000.0)), (1584.0, 792.0));
|
||||
near(size(Some(f32::MAX), None), (1584.0, 792.0));
|
||||
// None stated, or nothing a size could be: its own, fitted to a column.
|
||||
near(size(None, None), (150.0, 75.0));
|
||||
for junk in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.0, -5.0] {
|
||||
near(size(Some(junk), Some(junk)), (150.0, 75.0));
|
||||
}
|
||||
let wide = crate::odt::body::drawing::picture(png(1000, 500), None, None, "").unwrap();
|
||||
near((wide.w, wide.h), (468.0, 234.0));
|
||||
}
|
||||
|
||||
/// A replacement picture may come before its object. It is kept whatever it is, though WordCraft
|
||||
/// can't show it, and a save from the model writes it back.
|
||||
#[test]
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user