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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ The release run heads these entries with the version and opens a fresh
`https`, `mailto`, `ftp`, `ftps`, `tel` or a relative reference β€” the
allowlist a PDF `/URI` action already went through. `Link::href()` is
unchanged.
- `HtmlView::sheet_cut` reports the extent a sheet's cells span against the
extent the rendered markup carries, or nothing where the limits cut nothing.
Bound in python, jni, wasm and apple as `sheet_cut` / `sheetCut`.
- New `HtmlConfig::spreadsheet_cell_limit`, 500000 cells for one sheet, bounds
the rows a sheet keeps by its width; `spreadsheet_limit` rises from 10000 to
100000 rows. wasm gains both.
- A saved document opens in LibreOffice again: every zip entry's size goes into
its local header instead of a trailing data descriptor, which LibreOffice
rejects on a stored entry β€” an odf package always stores `mimetype`.
Expand Down
19 changes: 19 additions & 0 deletions apple/include/OdrCoreObjC/ODRHtml.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ NS_SWIFT_NAME(HtmlConfig)

/// `nil` for no limit.
@property(nonatomic, strong, nullable) NSValue *spreadsheetLimit;
/// Most cells written for one sheet; bounds the rows by the sheet's width.
/// `nil` for no budget.
@property(nonatomic, strong, nullable) NSNumber *spreadsheetCellLimit;
@property(nonatomic) BOOL spreadsheetLimitByContent;
@property(nonatomic) ODRHtmlTableGridlines spreadsheetGridlines;

Expand Down Expand Up @@ -175,6 +178,18 @@ NS_SWIFT_NAME(Html)
+ (instancetype)new NS_UNAVAILABLE;
@end

/// What a view leaves out of the sheet it renders. `odr::HtmlSheetCut`.
NS_SWIFT_NAME(HtmlSheetCut)
@interface ODRHtmlSheetCut : NSObject
/// The extent the sheet's cells span.
@property(nonatomic, readonly) ODRTableDimensions content;
/// The extent the markup carries.
@property(nonatomic, readonly) ODRTableDimensions rendered;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end

/// One renderable view of a document β€” a slide, a sheet, a page, or the whole
/// thing. `odr::HtmlView`.
NS_SWIFT_NAME(HtmlView)
Expand All @@ -184,6 +199,10 @@ NS_SWIFT_NAME(HtmlView)
/// The path this view is served at.
@property(nonatomic, readonly, copy) NSString *path;

/// The sheet this view cuts down to `spreadsheetLimit` and
/// `spreadsheetCellLimit`, or `nil` where it writes every cell.
@property(nonatomic, readonly, nullable) ODRHtmlSheetCut *sheetCut;

/// Renders the view. The resources it refers to come back alongside it.
- (nullable NSString *)writeHtmlWithResources:
(NSArray<ODRHtmlResource *> *_Nullable *_Nullable)
Expand Down
32 changes: 32 additions & 0 deletions apple/src/ODRHtml.mm
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
_spreadsheetLimit = [NSValue valueWithBytes:&limit
objCType:@encode(ODRTableDimensions)];
}
_spreadsheetCellLimit =
config.spreadsheet_cell_limit.has_value()
? @(static_cast<unsigned long long>(*config.spreadsheet_cell_limit))
: nil;
_spreadsheetLimitByContent = config.spreadsheet_limit_by_content ? YES : NO;
_spreadsheetGridlines =
static_cast<ODRHtmlTableGridlines>(config.spreadsheet_gridlines);
Expand Down Expand Up @@ -163,6 +167,12 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
} else {
config.spreadsheet_limit.reset();
}
if (_spreadsheetCellLimit != nil) {
config.spreadsheet_cell_limit =
static_cast<std::uint64_t>(_spreadsheetCellLimit.unsignedLongLongValue);
} else {
config.spreadsheet_cell_limit.reset();
}
config.spreadsheet_limit_by_content = _spreadsheetLimitByContent == YES;
config.spreadsheet_gridlines =
static_cast<odr::HtmlTableGridlines>(_spreadsheetGridlines);
Expand Down Expand Up @@ -336,6 +346,19 @@ + (instancetype)htmlWithHandle:(const odr::Html &)handle {

} // namespace

@implementation ODRHtmlSheetCut

+ (instancetype)cutWithHandle:(const odr::HtmlSheetCut &)handle {
ODRHtmlSheetCut *const result = [[ODRHtmlSheetCut alloc] init];
result->_content =
ODRTableDimensionsMake(handle.content.rows, handle.content.columns);
result->_rendered =
ODRTableDimensionsMake(handle.rendered.rows, handle.rendered.columns);
return result;
}

@end

@implementation ODRHtmlView {
std::optional<odr::HtmlView> _handle;
// The view's impl holds a bare pointer to its service, so the view has to
Expand Down Expand Up @@ -367,6 +390,15 @@ - (NSString *)path {
return guarded_value([&] { return to_nsstring(_handle->path()); }, @"");
}

- (ODRHtmlSheetCut *)sheetCut {
return guarded_value(
[&]() -> ODRHtmlSheetCut * {
const std::optional<odr::HtmlSheetCut> &cut = _handle->sheet_cut();
return cut.has_value() ? [ODRHtmlSheetCut cutWithHandle:*cut] : nil;
},
static_cast<ODRHtmlSheetCut *>(nil));
}

- (nullable NSString *)writeHtmlWithResources:
(NSArray<ODRHtmlResource *> **)resources
error:(NSError **)error {
Expand Down
4 changes: 4 additions & 0 deletions apple/src/ODRPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ ODRMeasure *_Nullable box(const std::optional<odr::Measure> &measure);
+ (instancetype)htmlWithHandle:(const odr::Html &)handle;
@end

@interface ODRHtmlSheetCut (Private)
+ (instancetype)cutWithHandle:(const odr::HtmlSheetCut &)handle;
@end

@interface ODRHtmlView (Private)
+ (instancetype)viewWithHandle:(odr::HtmlView)handle owner:(id)owner;
- (const odr::HtmlView &)handle;
Expand Down
1 change: 1 addition & 0 deletions jni/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ add_jar(odr_java
"java/app/opendocument/core/HtmlResource.java"
"java/app/opendocument/core/HtmlResourceType.java"
"java/app/opendocument/core/HtmlService.java"
"java/app/opendocument/core/HtmlSheetCut.java"
"java/app/opendocument/core/HtmlTableGridlines.java"
"java/app/opendocument/core/HtmlView.java"
"java/app/opendocument/core/HtmlViewportMode.java"
Expand Down
9 changes: 8 additions & 1 deletion jni/java/app/opendocument/core/HtmlConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ public final class HtmlConfig {
public HtmlColorScheme colorScheme = HtmlColorScheme.LIGHT;

/** {@code null} disables the spreadsheet limit. */
public TableDimensions spreadsheetLimit = new TableDimensions(10000, 500);
public TableDimensions spreadsheetLimit = new TableDimensions(100000, 500);

/**
* Most cells written for one sheet; bounds the rows by the sheet's width. {@code null} disables
* the budget.
*/
public Long spreadsheetCellLimit = 500000L;

public boolean spreadsheetLimitByContent = true;
public HtmlTableGridlines spreadsheetGridlines = HtmlTableGridlines.SOFT;

Expand Down
26 changes: 26 additions & 0 deletions jni/java/app/opendocument/core/HtmlSheetCut.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package app.opendocument.core;

/**
* What a view leaves out of the sheet it renders. Mirrors {@code odr::HtmlSheetCut}.
*
* @see HtmlView#sheetCut()
*/
public final class HtmlSheetCut {
/** The extent the sheet's cells span. */
public TableDimensions content;

/** The extent the markup carries. */
public TableDimensions rendered;

public HtmlSheetCut() {}

public HtmlSheetCut(TableDimensions content, TableDimensions rendered) {
this.content = content;
this.rendered = rendered;
}

@Override
public String toString() {
return "HtmlSheetCut(content=" + content + ", rendered=" + rendered + ")";
}
}
10 changes: 10 additions & 0 deletions jni/java/app/opendocument/core/HtmlView.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public HtmlConfig config() {
return configNative(handle());
}

/**
* The sheet this view cuts down to {@link HtmlConfig#spreadsheetLimit} and {@link
* HtmlConfig#spreadsheetCellLimit}, or {@code null} where it writes every cell.
*/
public HtmlSheetCut sheetCut() {
return sheetCutNative(handle());
}

/** Renders this view; returns the HTML and its resources. */
public Html.Content writeHtml() {
return writeHtmlNative(handle());
Expand All @@ -42,6 +50,8 @@ public Html bringOffline(String outputPath) {

private native HtmlConfig configNative(long handle);

private native HtmlSheetCut sheetCutNative(long handle);

private native Html.Content writeHtmlNative(long handle);

private native Html bringOfflineNative(long handle, String outputPath);
Expand Down
3 changes: 3 additions & 0 deletions jni/src/jni_convert.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobject make_page_layout(JNIEnv *env, const odr::PageLayout &layout);
jobject make_table_dimensions(JNIEnv *env,
const odr::TableDimensions &dimensions);
jobject make_table_position(JNIEnv *env, const odr::TablePosition &position);
/// `nullptr` where nothing was cut.
jobject make_html_sheet_cut(JNIEnv *env,
const std::optional<odr::HtmlSheetCut> &cut);
jobject make_file_meta(JNIEnv *env, const odr::FileMeta &meta);
jobject make_file_type_capabilities(JNIEnv *env,
const odr::FileTypeCapabilities &);
Expand Down
8 changes: 8 additions & 0 deletions jni/src/jni_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,14 @@ Java_app_opendocument_core_HtmlView_configNative(JNIEnv *env, jobject,
});
}

extern "C" JNIEXPORT jobject JNICALL
Java_app_opendocument_core_HtmlView_sheetCutNative(JNIEnv *env, jobject,
jlong handle) {
return guarded(env, [&] {
return odr_jni::make_html_sheet_cut(env, view(handle).sheet_cut());
});
}

extern "C" JNIEXPORT jobject JNICALL
Java_app_opendocument_core_HtmlView_writeHtmlNative(JNIEnv *env, jobject,
jlong handle) {
Expand Down
34 changes: 34 additions & 0 deletions jni/src/jni_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ jobject box_integer(JNIEnv *env, const std::optional<std::uint32_t> &value) {
static_cast<jint>(*value));
}

jobject box_long(JNIEnv *env, const std::optional<std::uint64_t> &value) {
if (!value.has_value()) {
return nullptr;
}
return call_static_object(env, "java/lang/Long", "valueOf",
"(J)Ljava/lang/Long;", static_cast<jlong>(*value));
}

/// Looks up an enum constant by its C++ code (= Java ordinal).
jobject enum_from_code(JNIEnv *env, const char *class_name, const jint code) {
if (code < 0) {
Expand Down Expand Up @@ -309,6 +317,18 @@ jobject make_table_dimensions(JNIEnv *env,
static_cast<jint>(dimensions.columns));
}

jobject make_html_sheet_cut(JNIEnv *env,
const std::optional<odr::HtmlSheetCut> &cut) {
if (!cut.has_value()) {
return nullptr;
}
return new_object(env, "app/opendocument/core/HtmlSheetCut",
"(Lapp/opendocument/core/TableDimensions;Lapp/opendocument/"
"core/TableDimensions;)V",
make_table_dimensions(env, cut->content),
make_table_dimensions(env, cut->rendered));
}

jobject make_table_position(JNIEnv *env, const odr::TablePosition &position) {
return new_object(env, "app/opendocument/core/TablePosition", "(II)V",
static_cast<jint>(position.column),
Expand Down Expand Up @@ -392,6 +412,8 @@ jobject html_config_to_java(JNIEnv *env, const odr::HtmlConfig &config) {
config.spreadsheet_limit.has_value()
? make_table_dimensions(env, *config.spreadsheet_limit)
: nullptr);
set_object("spreadsheetCellLimit", "Ljava/lang/Long;",
box_long(env, config.spreadsheet_cell_limit));
set_boolean("spreadsheetLimitByContent", config.spreadsheet_limit_by_content);
set_object("spreadsheetGridlines",
"Lapp/opendocument/core/HtmlTableGridlines;",
Expand Down Expand Up @@ -527,6 +549,18 @@ odr::HtmlConfig html_config_from_java(JNIEnv *env, jobject config) {
env->DeleteLocalRef(dimensions_cls);
}
}
{
jobject cell_limit = get_object("spreadsheetCellLimit", "Ljava/lang/Long;");
if (cell_limit == nullptr) {
result.spreadsheet_cell_limit = std::nullopt;
} else {
jclass long_cls = env->GetObjectClass(cell_limit);
jmethodID long_value = env->GetMethodID(long_cls, "longValue", "()J");
result.spreadsheet_cell_limit = static_cast<std::uint64_t>(
env->CallLongMethod(cell_limit, long_value));
env->DeleteLocalRef(long_cls);
}
}
result.spreadsheet_limit_by_content =
get_boolean("spreadsheetLimitByContent");
{
Expand Down
28 changes: 28 additions & 0 deletions jni/tests/app/opendocument/core/HtmlTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app.opendocument.core;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -160,6 +161,33 @@ void translateDocument() throws IOException {
assertTrue(content.contains(TestFiles.ODT_WORD));
}

/** The C++ suite covers where the limits land; this proves the pair crosses JNI. */
@Test
void spreadsheetCutReachesTheView() throws IOException {
assertEquals(Long.valueOf(500000L), new HtmlConfig().spreadsheetCellLimit);

Path cache = Files.createDirectories(tempDir.resolve("cache"));
DecodedFile file = Odr.open(TestFiles.csvFile(tempDir).toString());

HtmlConfig full = new HtmlConfig();
for (HtmlView view : Html.translate(file, cache.toString(), full).listViews()) {
assertNull(view.sheetCut());
}

HtmlConfig cut = new HtmlConfig();
cut.spreadsheetLimit = new TableDimensions(2, 1);
cut.spreadsheetCellLimit = null;
HtmlService service = Html.translate(file, cache.toString(), cut);

assertNull(service.config().spreadsheetCellLimit);
HtmlSheetCut sheetCut = service.listViews().get(1).sheetCut();
assertNotNull(sheetCut);
assertEquals(3, sheetCut.content.rows);
assertEquals(2, sheetCut.content.columns);
assertEquals(2, sheetCut.rendered.rows);
assertEquals(1, sheetCut.rendered.columns);
}

@Test
void htmlServiceViews() throws IOException {
Path cache = Files.createDirectories(tempDir.resolve("cache"));
Expand Down
10 changes: 10 additions & 0 deletions python/src/bind_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ void odr_python::bind_html(py::module_ &m) {
&odr::HtmlConfig::text_document_margin)
.def_readwrite("color_scheme", &odr::HtmlConfig::color_scheme)
.def_readwrite("spreadsheet_limit", &odr::HtmlConfig::spreadsheet_limit)
.def_readwrite("spreadsheet_cell_limit",
&odr::HtmlConfig::spreadsheet_cell_limit)
.def_readwrite("spreadsheet_limit_by_content",
&odr::HtmlConfig::spreadsheet_limit_by_content)
.def_readwrite("spreadsheet_gridlines",
Expand Down Expand Up @@ -117,6 +119,10 @@ void odr_python::bind_html(py::module_ &m) {
.def_readwrite("output_path", &odr::HtmlConfig::output_path)
.def_readwrite("resource_locator", &odr::HtmlConfig::resource_locator);

py::class_<odr::HtmlSheetCut>(m, "HtmlSheetCut")
.def_readonly("content", &odr::HtmlSheetCut::content)
.def_readonly("rendered", &odr::HtmlSheetCut::rendered);

py::class_<odr::HtmlPage>(m, "HtmlPage")
.def_readonly("name", &odr::HtmlPage::name)
.def_readonly("path", &odr::HtmlPage::path)
Expand All @@ -135,6 +141,10 @@ void odr_python::bind_html(py::module_ &m) {
.def("index", &odr::HtmlView::index)
.def("path", &odr::HtmlView::path)
.def("config", &odr::HtmlView::config)
.def(
"sheet_cut",
[](const odr::HtmlView &view) { return view.sheet_cut(); },
"The sheet this view cut down to the spreadsheet limits, or None.")
.def(
"write_html",
[](const odr::HtmlView &view) {
Expand Down
22 changes: 22 additions & 0 deletions python/tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ def test_html_config_defaults():
assert config.editable
assert config.spreadsheet_limit.rows == 100

assert config.spreadsheet_cell_limit == 500000
config.spreadsheet_cell_limit = None
assert config.spreadsheet_cell_limit is None


def test_html_view_sheet_cut(csv_path, tmp_path):
cache = tmp_path / "cache"
file = pyodr.open(str(csv_path))

config = pyodr.HtmlConfig()
service = pyodr.html.translate(file, str(cache), config)
assert all(view.sheet_cut() is None for view in service.list_views())

config.spreadsheet_limit = pyodr.TableDimensions(2, 1)
config.spreadsheet_cell_limit = None
service = pyodr.html.translate(file, str(cache), config)

cut = service.list_views()[1].sheet_cut()
assert cut is not None
assert (cut.content.rows, cut.content.columns) == (3, 2)
assert (cut.rendered.rows, cut.rendered.columns) == (2, 1)


def test_html_config_color_scheme_defaults():
config = pyodr.HtmlConfig()
Expand Down
4 changes: 4 additions & 0 deletions src/odr/html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ const std::string &HtmlView::path() const { return m_impl->path(); }

const HtmlConfig &HtmlView::config() const { return m_impl->config(); }

const std::optional<HtmlSheetCut> &HtmlView::sheet_cut() const {
return m_impl->sheet_cut();
}

HtmlResources HtmlView::write_html(std::ostream &out) const {
internal::html::HtmlWriter writer(out, config());
return m_impl->write_html(writer);
Expand Down
Loading
Loading