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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- New `Sheet::page_layout()`: the paper an ods states for a sheet, read from
the master page its table style names. Mirrored in the Python, JNI and Apple
bindings. Empty for xlsx, xls, numbers and csv.

- A printed sheet is fitted to the paper the file states, at the file's own
column proportions. Towards #816.

- A right-to-left document renders right-to-left. `style:writing-mode`,
`w:bidi` and `a:pPr@rtl` are read into a new `TextDirection` on
`ParagraphStyle` and `PageLayout`, and the view's root carries it as
Expand Down
2 changes: 2 additions & 0 deletions apple/include/OdrCoreObjC/ODRDocumentElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ NS_SWIFT_NAME(Slide)
NS_SWIFT_NAME(Sheet)
@interface ODRSheet : ODRElement
@property(nonatomic, readonly, copy) NSString *name;
/// The paper the sheet is printed on, where the file states one.
@property(nonatomic, readonly) ODRPageLayout *pageLayout;
@property(nonatomic, readonly) ODRTableDimensions dimensions;
/// The dimensions actually filled with content, optionally within `range`.
- (ODRTableDimensions)contentDimensions NS_SWIFT_NAME(contentDimensions());
Expand Down
9 changes: 9 additions & 0 deletions apple/src/ODRDocumentElement.mm
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@ - (NSString *)name {
[&] { return to_nsstring(self.handle.as_sheet().name()); }, @"");
}

- (ODRPageLayout *)pageLayout {
return guarded_value(
[&]() -> ODRPageLayout * {
return [ODRPageLayout
layoutWithHandle:self.handle.as_sheet().page_layout()];
},
nil);
}

- (ODRTableDimensions)dimensions {
return guarded_value(
[&] { return to_dimensions(self.handle.as_sheet().dimensions()); },
Expand Down
7 changes: 7 additions & 0 deletions jni/java/app/opendocument/core/Sheet.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public String name() {
return nameNative(handle());
}

/** The paper the sheet is printed on, where the file states one. */
public PageLayout pageLayout() {
return pageLayoutNative(handle());
}

public TableDimensions dimensions() {
return dimensionsNative(handle());
}
Expand Down Expand Up @@ -51,6 +56,8 @@ public TableCellStyle cellStyle(int column, int row) {

private native String nameNative(long handle);

private native PageLayout pageLayoutNative(long handle);

private native TableDimensions dimensionsNative(long handle);

private native TableDimensions contentNative(long handle, int rows, int columns);
Expand Down
9 changes: 9 additions & 0 deletions jni/src/jni_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@ Java_app_opendocument_core_Sheet_nameNative(JNIEnv *env, jobject,
env, [&] { return to_jstring(env, element(handle).as_sheet().name()); });
}

extern "C" JNIEXPORT jobject JNICALL
Java_app_opendocument_core_Sheet_pageLayoutNative(JNIEnv *env, jobject,
jlong handle) {
return guarded(env, [&] {
return odr_jni::make_page_layout(env,
element(handle).as_sheet().page_layout());
});
}

extern "C" JNIEXPORT jobject JNICALL
Java_app_opendocument_core_Sheet_dimensionsNative(JNIEnv *env, jobject,
jlong handle) {
Expand Down
1 change: 1 addition & 0 deletions python/src/bind_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ void odr_python::bind_document(py::module_ &m) {

bind_element<odr::Sheet>(m, "Sheet")
.def("name", &odr::Sheet::name)
.def("page_layout", &odr::Sheet::page_layout)
.def("dimensions", &odr::Sheet::dimensions)
.def("content", &odr::Sheet::content, py::arg("range"))
.def("cell", &odr::Sheet::cell, py::arg("column"), py::arg("row"),
Expand Down
4 changes: 4 additions & 0 deletions src/odr/document_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ std::string Sheet::name() const {
return exists_() ? m_adapter2->sheet_name(m_identifier) : "";
}

PageLayout Sheet::page_layout() const {
return exists_() ? m_adapter2->sheet_page_layout(m_identifier) : PageLayout();
}

TableDimensions Sheet::dimensions() const {
return exists_() ? m_adapter2->sheet_dimensions(m_identifier)
: TableDimensions();
Expand Down
3 changes: 3 additions & 0 deletions src/odr/document_element.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ class Sheet final : public ElementBase<internal::abstract::SheetAdapter> {

[[nodiscard]] std::string name() const;

/// The paper the sheet is meant to be printed on, where the file states one.
[[nodiscard]] PageLayout page_layout() const;

[[nodiscard]] TableDimensions dimensions() const;
[[nodiscard]] TableDimensions
content(std::optional<TableDimensions> range) const;
Expand Down
4 changes: 4 additions & 0 deletions src/odr/internal/abstract/document.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ class SheetAdapter {
[[nodiscard]] virtual std::string
sheet_name(ElementIdentifier element_id) const = 0;

/// Empty where the file states no paper.
[[nodiscard]] virtual PageLayout
sheet_page_layout(ElementIdentifier element_id) const = 0;

[[nodiscard]] virtual TableDimensions
sheet_dimensions(ElementIdentifier element_id) const = 0;
[[nodiscard]] virtual TableDimensions
Expand Down
4 changes: 4 additions & 0 deletions src/odr/internal/csv/csv_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ class ElementAdapter final : public abstract::ElementAdapter,
[[maybe_unused]] const ElementIdentifier element_id) const override {
return "csv";
}
[[nodiscard]] PageLayout sheet_page_layout(
[[maybe_unused]] const ElementIdentifier element_id) const override {
return {};
}
[[nodiscard]] TableDimensions sheet_dimensions(
[[maybe_unused]] const ElementIdentifier element_id) const override {
return m_document->dimensions();
Expand Down
52 changes: 49 additions & 3 deletions src/odr/internal/html/document_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,40 @@ TableDimensions html::sheet_rendered_extent(const Sheet &sheet,
return {end_row, end_column};
}

namespace {

/// How far a sheet has to shrink to fit the paper the file states; nothing
/// where it fits already, or where no width is stated.
std::optional<double> sheet_print_fit(const Sheet &sheet,
const std::uint32_t end_column) {
const PageLayout page_layout = sheet.page_layout();
const std::optional<double> page = html::css_pixels(page_layout.width);
if (!page.has_value()) {
return {};
}
const double printable =
*page - html::css_pixels(page_layout.margin.left).value_or(0) -
html::css_pixels(page_layout.margin.right).value_or(0);

// the ruler does not print
double content = 0;
for (std::uint32_t column = 0; column < end_column; ++column) {
const std::optional<double> width =
html::css_pixels(sheet.column_style(column).width);
if (!width.has_value()) {
return {};
}
content += *width;
}

if (printable <= 0 || content <= printable) {
return {};
}
return printable / content;
}

} // namespace

std::optional<HtmlSheetCut> html::sheet_cut(const Sheet &sheet,
const HtmlConfig &config) {
const TableDimensions rendered = sheet_rendered_extent(sheet, config);
Expand All @@ -199,13 +233,25 @@ std::optional<HtmlSheetCut> html::sheet_cut(const Sheet &sheet,
}

void html::translate_sheet(const Sheet &sheet, const WritingState &state) {
state.out().write_element_begin("table",
HtmlElementOptions().set_class("odr-sheet"));

const TableDimensions rendered = sheet_rendered_extent(sheet, state.config());
const std::uint32_t end_column = rendered.columns;
const std::uint32_t end_row = rendered.rows;

const std::optional<double> print_fit = sheet_print_fit(sheet, end_column);

state.out().write_element_begin(
"table", HtmlElementOptions()
.set_class("odr-sheet")
.set_style([&]() -> std::optional<HtmlWritable> {
if (!print_fit.has_value()) {
return std::nullopt;
}
// `Measure` renders no exponent form
return "--odr-print-fit:" +
Measure(*print_fit, DynamicUnit()).to_string() +
";";
}()));

state.out().write_element_begin("col",
HtmlElementOptions()
.set_close_type(HtmlCloseType::none)
Expand Down
10 changes: 5 additions & 5 deletions src/odr/internal/html/frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ body{margin:0;background:var(--odr-sheet-canvas)}
.odr-sheet-sort:hover{background:var(--odr-sheet-wash-ruler)}
.odr-sheet-sort::after{content:"\25BE";font-size:15px;line-height:1}
.odr-sheet-sort-asc::after{content:"\25B4"}
/* Paper cannot scroll, so a sheet wider than the page is cut off; only
`!important` beats the inline column width. The ruler collapses rather than
`display:none`, which would take the cells out of their rows and shift every
column left. */
/* `--odr-print-fit` is written per sheet by `translate_sheet`; `zoom` keeps
the rows breaking across pages. Only `!important` beats the inline column
width. The ruler collapses rather than `display:none`, which would take the
cells out of their rows and shift every column left. */
@media print{
.odr-sheet thead{display:none}
.odr-sheet-gutter{visibility:collapse;width:0}
.odr-sheet-row-header{visibility:collapse;padding:0;box-shadow:none}
.odr-sheet{max-width:100%}
.odr-sheet{zoom:var(--odr-print-fit,1);max-width:100%}
.odr-sheet col{min-width:0!important}
}
)css";
Expand Down
5 changes: 5 additions & 0 deletions src/odr/internal/iwork/iwork_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ class ElementAdapter final : public abstract::ElementAdapter,
sheet_name(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).name;
}
/// TODO the print setup of a `.numbers` sheet is not read.
[[nodiscard]] PageLayout sheet_page_layout(
[[maybe_unused]] const ElementIdentifier element_id) const override {
return {};
}
[[nodiscard]] TableDimensions
sheet_dimensions(const ElementIdentifier element_id) const override {
const ElementRegistry::Sheet &sheet =
Expand Down
15 changes: 15 additions & 0 deletions src/odr/internal/odf/odf_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,21 @@ class ElementAdapter final : public abstract::ElementAdapter,
sheet_name(const ElementIdentifier element_id) const override {
return get_node(element_id).attribute("table:name").value();
}
[[nodiscard]] PageLayout
sheet_page_layout(const ElementIdentifier element_id) const override {
// The table style names the master page (20.383); a sheet that names none
// takes the first.
ElementIdentifier master_page_id =
m_document->style_registry().master_page_of_style(
get_node(element_id).attribute("table:style-name").value());
if (master_page_id == null_element_id) {
master_page_id = m_document->style_registry().first_master_page();
}
if (master_page_id == null_element_id) {
return {};
}
return master_page_page_layout(master_page_id);
}
[[nodiscard]] TableDimensions
sheet_dimensions(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).dimensions;
Expand Down
5 changes: 5 additions & 0 deletions src/odr/internal/oldms/spreadsheet/xls_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ class ElementAdapter final : public abstract::ElementAdapter,
sheet_name(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).name;
}
/// TODO `SETUP` ([MS-XLS] 2.4.257) is not read.
[[nodiscard]] PageLayout sheet_page_layout(
[[maybe_unused]] const ElementIdentifier element_id) const override {
return {};
}
[[nodiscard]] TableDimensions
sheet_dimensions(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).dimensions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ class ElementAdapter final : public abstract::ElementAdapter,
sheet_name(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).name;
}
/// TODO `pageSetup` is not read; a column width here is a `ch` either way.
[[nodiscard]] PageLayout sheet_page_layout(
[[maybe_unused]] const ElementIdentifier element_id) const override {
return {};
}
[[nodiscard]] TableDimensions
sheet_dimensions(const ElementIdentifier element_id) const override {
return m_registry->sheet_element_at(element_id).dimensions;
Expand Down
37 changes: 37 additions & 0 deletions test/src/document_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,43 @@ TEST(Document, docx_page_layout) {
EXPECT_EQ(Measure("1in"), page_layout.margin.left);
}

// #816
TEST(Document, ods_sheet_page_layout) {
const Logger logger = Logger::create_stdio("odr-test", LogLevel::verbose);

const DocumentFile document_file(
TestData::test_file_path("odr-public/ods/file_example_ODS_100.ods"),
logger);

const Document document = document_file.document();
const PageLayout page_layout =
(*document.root_element().children().begin()).as_sheet().page_layout();
EXPECT_EQ(Measure("8.2681in"), page_layout.width);
EXPECT_EQ(Measure("11.6929in"), page_layout.height);
EXPECT_EQ(PrintOrientation::portrait, page_layout.print_orientation);
EXPECT_EQ(Measure("0.7874in"), page_layout.margin.top);
EXPECT_EQ(Measure("0.7874in"), page_layout.margin.right);
EXPECT_EQ(Measure("0.7874in"), page_layout.margin.bottom);
EXPECT_EQ(Measure("0.7874in"), page_layout.margin.left);
}

// Producers commonly state margins only and leave the paper to the printer.
TEST(Document, ods_sheet_page_layout_without_a_paper_size) {
const Logger logger = Logger::create_stdio("odr-test", LogLevel::verbose);

const DocumentFile document_file(
TestData::test_file_path("odr-public/ods/file_example_ODS_10.ods"),
logger);

const Document document = document_file.document();
const PageLayout page_layout =
(*document.root_element().children().begin()).as_sheet().page_layout();

EXPECT_FALSE(page_layout.width.has_value());
EXPECT_FALSE(page_layout.height.has_value());
EXPECT_EQ(Measure("0.7874in"), page_layout.margin.left);
}

TEST(Document, xlsx_sheet_names) {
const Logger logger = Logger::create_stdio("odr-test", LogLevel::verbose);

Expand Down
Loading
Loading