Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- A linked image in a docx or xlsx (`embed_images = false`) is named relative
to the document, like odf, so a host serving the html under a mount point
resolves it.

## v6.10.0 - 2026-08-20

- Html views take a zoom from their host: `odr.getZoom()`, `setZoom(value,
Expand Down
9 changes: 6 additions & 3 deletions src/odr/internal/html/document_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <odr/html.hpp>
#include <odr/style.hpp>

#include <odr/internal/common/path.hpp>
#include <odr/internal/common/table_cursor.hpp>
#include <odr/internal/html/common.hpp>
#include <odr/internal/html/document_style.hpp>
Expand Down Expand Up @@ -498,9 +499,11 @@ void html::translate_image(const Element &element, const WritingState &state) {
odr::HtmlResource resource;
HtmlResourceLocation resource_location;
if (image.is_internal()) {
resource =
HtmlResource::create(HtmlResourceType::image, "image/jpg", image.href(),
image.href(), image.file(), false, false, true);
// the location is resolved against the document, so an engine naming the
// image by its absolute path in the container has to lose the root
const std::string path = Path(image.href()).make_relative().string();
resource = HtmlResource::create(HtmlResourceType::image, "image/jpg", path,
path, image.file(), false, false, true);
resource_location =
state.config().resource_locator(resource, state.config());
} else {
Expand Down
48 changes: 48 additions & 0 deletions test/src/html_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <odr/exceptions.hpp>

#include <odr/internal/common/path.hpp>

#include <internal/pdf/pdf_test_file_builder.hpp>

#include <test_util.hpp>
Expand Down Expand Up @@ -64,6 +66,52 @@ TEST(html, linked_resources_are_served) {
"document.html");
}

// Same for a document resource, which unlike a shipped one is named by the
// engine: the reference output embeds every image, so nothing else renders the
// linked form (opendocument-app/OpenDocument.droid#551).
TEST(html, linked_images_are_served) {
const auto logger = Logger::create_stdio("odr-test", LogLevel::verbose);

const std::string cache_path =
(std::filesystem::current_path() / "images").string();

const auto check = [&](const std::string &path) {
const DecodedFile file(TestData::test_file_path(path), logger);

HtmlConfig config;
config.embed_images = false;

const HtmlService service = html::translate(file, cache_path, config);

std::ostringstream out;
const HtmlResources resources = service.list_views().at(0).write_html(out);

std::size_t linked = 0;
for (const auto &[resource, location] : resources) {
if (resource.type() != HtmlResourceType::image ||
!resource.is_accessible()) {
continue;
}
ASSERT_TRUE(location.has_value()) << resource.name();
++linked;

// an absolute location would resolve against the server root rather than
// against the document
EXPECT_TRUE(Path(*location).relative()) << *location;
EXPECT_TRUE(service.exists(*location)) << *location;

std::ostringstream served;
service.write(*location, served);
EXPECT_FALSE(served.str().empty()) << *location;
}
EXPECT_GT(linked, 0) << path;
};

check("odr-public/odt/image-text-wrap.odt");
check("odr-public/docx/file-sample_100kB.docx");
check("odr-public/xlsx/sample.xlsx");
}

// The one archive the reference-output suite renders has no directory in it.
// An archive may hold a file named like the stylesheet. Forcing the collision
// through the locator saves needing such an archive in the test data.
Expand Down
Loading