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

## Unreleased

- A file can be read in the dark: `HtmlConfig::color_scheme` is `light`, `dark`
or `system`, following the reader's `prefers-color-scheme`. Every view honors
it but the pdf one, and the colors the file authored give way to it. Bound in
the python, wasm, jni and apple bindings as `HtmlColorScheme`.
- `FileTypeCapabilities::color_scheme` says whether a type's view honors it β€”
every type that renders but pdf, audio and video. Bound in the bindings too.
- A text document reflowed to the viewport is inset 3mm from the screen edge
rather than starting at the first pixel of it.
- Prose is no longer read as a csv. A separator that every field follows with a
space, in fields long enough to be sentences, is punctuation; `a, b, c` with
short values is still a csv. One record is a line, not a table.
Expand Down
2 changes: 2 additions & 0 deletions apple/include/OdrCoreObjC/ODRFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ NS_SWIFT_NAME(FileTypeCapabilities)
@property(nonatomic, readonly) BOOL decrypt;
/// `ODRHtml` produces output for it.
@property(nonatomic, readonly) BOOL translateHtml;
/// The view it renders as honors `ODRHtmlConfig.colorScheme`.
@property(nonatomic, readonly) BOOL colorScheme;
@property(nonatomic, readonly) BOOL edit;
@property(nonatomic, readonly) BOOL save;
/// Saving with a password is supported.
Expand Down
13 changes: 13 additions & 0 deletions apple/include/OdrCoreObjC/ODRHtml.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ typedef NS_ENUM(NSInteger, ODRHtmlTableGridlines) {
ODRHtmlTableGridlinesHard,
} NS_SWIFT_NAME(HtmlTableGridlines);

/// The colors the emitted HTML renders against. `ODRFileTypeCapabilities`
/// says which views honor it.
typedef NS_ENUM(NSInteger, ODRHtmlColorScheme) {
/// A white page, carrying the colors the document gives its content.
ODRHtmlColorSchemeLight = 0,
/// A dark page, which the document's own text and fill colors give way to.
ODRHtmlColorSchemeDark,
/// Light or dark, by the reader's `prefers-color-scheme`.
ODRHtmlColorSchemeSystem,
} NS_SWIFT_NAME(HtmlColorScheme);

/// Initial zoom of the emitted HTML on mobile (the viewport meta tag). Desktop
/// browsers ignore it entirely.
typedef NS_ENUM(NSInteger, ODRHtmlViewportMode) {
Expand Down Expand Up @@ -71,6 +82,8 @@ NS_SWIFT_NAME(HtmlConfig)
@property(nonatomic) BOOL editable;
@property(nonatomic) BOOL textDocumentMargin;

@property(nonatomic) ODRHtmlColorScheme colorScheme;

/// `nil` for no limit.
@property(nonatomic, strong, nullable) NSValue *spreadsheetLimit;
@property(nonatomic) BOOL spreadsheetLimitByContent;
Expand Down
1 change: 1 addition & 0 deletions apple/src/ODRFile.mm
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ + (instancetype)capabilitiesWithHandle:
result->_open = handle.open ? YES : NO;
result->_decrypt = handle.decrypt ? YES : NO;
result->_translateHtml = handle.translate_html ? YES : NO;
result->_colorScheme = handle.color_scheme ? YES : NO;
result->_edit = handle.edit ? YES : NO;
result->_save = handle.save ? YES : NO;
result->_encrypt = handle.encrypt ? YES : NO;
Expand Down
6 changes: 6 additions & 0 deletions apple/src/ODRHtml.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
ODR_SAME_ENUM(ODRHtmlTableGridlinesSoft, odr::HtmlTableGridlines::soft);
ODR_SAME_ENUM(ODRHtmlTableGridlinesHard, odr::HtmlTableGridlines::hard);

ODR_SAME_ENUM(ODRHtmlColorSchemeLight, odr::HtmlColorScheme::light);
ODR_SAME_ENUM(ODRHtmlColorSchemeDark, odr::HtmlColorScheme::dark);
ODR_SAME_ENUM(ODRHtmlColorSchemeSystem, odr::HtmlColorScheme::system);

ODR_SAME_ENUM(ODRHtmlViewportModeAutomatic, odr::HtmlViewportMode::automatic);
ODR_SAME_ENUM(ODRHtmlViewportModeFitWidth, odr::HtmlViewportMode::fit_width);
ODR_SAME_ENUM(ODRHtmlViewportModeActualSize,
Expand Down Expand Up @@ -84,6 +88,7 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
_relativeResourcePaths = config.relative_resource_paths ? YES : NO;
_editable = config.editable ? YES : NO;
_textDocumentMargin = config.text_document_margin ? YES : NO;
_colorScheme = static_cast<ODRHtmlColorScheme>(config.color_scheme);
if (config.spreadsheet_limit.has_value()) {
const ODRTableDimensions limit = ODRTableDimensionsMake(
config.spreadsheet_limit->rows, config.spreadsheet_limit->columns);
Expand Down Expand Up @@ -141,6 +146,7 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
config.relative_resource_paths = _relativeResourcePaths == YES;
config.editable = _editable == YES;
config.text_document_margin = _textDocumentMargin == YES;
config.color_scheme = static_cast<odr::HtmlColorScheme>(_colorScheme);
if (_spreadsheetLimit != nil) {
ODRTableDimensions limit{};
[_spreadsheetLimit getValue:&limit size:sizeof(limit)];
Expand Down
1 change: 1 addition & 0 deletions jni/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ add_jar(odr_java
"java/app/opendocument/core/GuardedNativeResource.java"
"java/app/opendocument/core/HorizontalAlign.java"
"java/app/opendocument/core/Html.java"
"java/app/opendocument/core/HtmlColorScheme.java"
"java/app/opendocument/core/HtmlConfig.java"
"java/app/opendocument/core/HtmlPage.java"
"java/app/opendocument/core/HtmlResource.java"
Expand Down
5 changes: 5 additions & 0 deletions jni/java/app/opendocument/core/FileTypeCapabilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public final class FileTypeCapabilities {
/** {@link Html#translate} produces output. */
public final boolean translateHtml;

/** The view this type renders as honors {@link HtmlConfig#colorScheme}. */
public final boolean colorScheme;

/** {@link Document#isEditable} can be {@code true}. */
public final boolean edit;

Expand All @@ -36,13 +39,15 @@ public final class FileTypeCapabilities {
boolean open,
boolean decrypt,
boolean translateHtml,
boolean colorScheme,
boolean edit,
boolean save,
boolean encrypt) {
this.detectByContent = detectByContent;
this.open = open;
this.decrypt = decrypt;
this.translateHtml = translateHtml;
this.colorScheme = colorScheme;
this.edit = edit;
this.save = save;
this.encrypt = encrypt;
Expand Down
14 changes: 14 additions & 0 deletions jni/java/app/opendocument/core/HtmlColorScheme.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.opendocument.core;

/** Mirrors {@code odr::HtmlColorScheme}; constant order must match the C++ declaration. */
public enum HtmlColorScheme {
LIGHT, DARK, SYSTEM;

static HtmlColorScheme fromNative(int code) {
return code < 0 ? null : values()[code];
}

int toNative() {
return ordinal();
}
}
3 changes: 3 additions & 0 deletions jni/java/app/opendocument/core/HtmlConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public final class HtmlConfig {

public boolean textDocumentMargin = false;

/** The colors a document renders against. */
public HtmlColorScheme colorScheme = HtmlColorScheme.LIGHT;

/** {@code null} disables the spreadsheet limit. */
public TableDimensions spreadsheetLimit = new TableDimensions(10000, 500);
public boolean spreadsheetLimitByContent = true;
Expand Down
14 changes: 13 additions & 1 deletion jni/src/jni_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,12 @@ jobject
make_file_type_capabilities(JNIEnv *env,
const odr::FileTypeCapabilities &capabilities) {
return new_object(env, "app/opendocument/core/FileTypeCapabilities",
"(ZZZZZZZ)V",
"(ZZZZZZZZ)V",
static_cast<jboolean>(capabilities.detect_by_content),
static_cast<jboolean>(capabilities.open),
static_cast<jboolean>(capabilities.decrypt),
static_cast<jboolean>(capabilities.translate_html),
static_cast<jboolean>(capabilities.color_scheme),
static_cast<jboolean>(capabilities.edit),
static_cast<jboolean>(capabilities.save),
static_cast<jboolean>(capabilities.encrypt));
Expand Down Expand Up @@ -344,6 +345,9 @@ jobject html_config_to_java(JNIEnv *env, const odr::HtmlConfig &config) {
set_boolean("relativeResourcePaths", config.relative_resource_paths);
set_boolean("editable", config.editable);
set_boolean("textDocumentMargin", config.text_document_margin);
set_object("colorScheme", "Lapp/opendocument/core/HtmlColorScheme;",
enum_from_code(env, "app/opendocument/core/HtmlColorScheme",
static_cast<jint>(config.color_scheme)));
set_object("spreadsheetLimit", "Lapp/opendocument/core/TableDimensions;",
config.spreadsheet_limit.has_value()
? make_table_dimensions(env, *config.spreadsheet_limit)
Expand Down Expand Up @@ -452,6 +456,14 @@ odr::HtmlConfig html_config_from_java(JNIEnv *env, jobject config) {
result.relative_resource_paths = get_boolean("relativeResourcePaths");
result.editable = get_boolean("editable");
result.text_document_margin = get_boolean("textDocumentMargin");
{
const jint code = enum_ordinal(
env,
get_object("colorScheme", "Lapp/opendocument/core/HtmlColorScheme;"));
if (code >= 0) {
result.color_scheme = static_cast<odr::HtmlColorScheme>(code);
}
}
{
jobject limit = get_object("spreadsheetLimit",
"Lapp/opendocument/core/TableDimensions;");
Expand Down
16 changes: 16 additions & 0 deletions jni/tests/app/opendocument/core/HtmlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ void viewportModeReachesTheHtml() throws IOException {
assertTrue(renderOdt(raw).contains("<meta name=\"viewport\" content=\"width=420\"/>"));
}

/** The C++ suite covers what the scheme paints; this only proves it crosses JNI. */
@Test
void colorSchemeReachesTheHtml() throws IOException {
assertEquals(HtmlColorScheme.LIGHT, new HtmlConfig().colorScheme);
assertTrue(!renderOdt(new HtmlConfig()).contains("prefers-color-scheme"));

HtmlConfig system = new HtmlConfig();
system.colorScheme = HtmlColorScheme.SYSTEM;
assertTrue(renderOdt(system).contains("media=\"(prefers-color-scheme: dark)\""));

Path cache = Files.createDirectories(tempDir.resolve("scheme"));
DecodedFile file = Odr.open(TestFiles.odtFile(tempDir).toString());
HtmlConfig readBack = Html.translate(file, cache.toString(), system).config();
assertEquals(HtmlColorScheme.SYSTEM, readBack.colorScheme);
}

@Test
void translateText() throws IOException {
Html html = translateOffline(TestFiles.txtFile(tempDir));
Expand Down
6 changes: 6 additions & 0 deletions jni/tests/app/opendocument/core/MetaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ void capabilitiesByFileType() {
FileTypeCapabilities odt = Odr.capabilitiesByFileType(FileType.OPENDOCUMENT_TEXT);
assertTrue(odt.open);
assertTrue(odt.translateHtml);
assertTrue(odt.colorScheme);
assertTrue(odt.edit);

// detected and named, but there is no decoder behind it
Expand All @@ -82,6 +83,11 @@ void capabilitiesByFileType() {

// spreadsheet editing is force-disabled
assertFalse(Odr.capabilitiesByFileType(FileType.OPENDOCUMENT_SPREADSHEET).edit);

// a pdf renders, but paints its own page backgrounds
FileTypeCapabilities pdf = Odr.capabilitiesByFileType(FileType.PORTABLE_DOCUMENT_FORMAT);
assertTrue(pdf.translateHtml);
assertFalse(pdf.colorScheme);
}

@Test
Expand Down
1 change: 1 addition & 0 deletions python/src/bind_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ void odr_python::bind_file(py::module_ &m) {
.def_readwrite("decrypt", &odr::FileTypeCapabilities::decrypt)
.def_readwrite("translate_html",
&odr::FileTypeCapabilities::translate_html)
.def_readwrite("color_scheme", &odr::FileTypeCapabilities::color_scheme)
.def_readwrite("edit", &odr::FileTypeCapabilities::edit)
.def_readwrite("save", &odr::FileTypeCapabilities::save)
.def_readwrite("encrypt", &odr::FileTypeCapabilities::encrypt);
Expand Down
6 changes: 6 additions & 0 deletions python/src/bind_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ void odr_python::bind_html(py::module_ &m) {
.value("soft", odr::HtmlTableGridlines::soft)
.value("hard", odr::HtmlTableGridlines::hard);

py::enum_<odr::HtmlColorScheme>(m, "HtmlColorScheme")
.value("light", odr::HtmlColorScheme::light)
.value("dark", odr::HtmlColorScheme::dark)
.value("system", odr::HtmlColorScheme::system);

py::enum_<odr::HtmlViewportMode>(m, "HtmlViewportMode")
.value("automatic", odr::HtmlViewportMode::automatic)
.value("fit_width", odr::HtmlViewportMode::fit_width)
Expand Down Expand Up @@ -75,6 +80,7 @@ void odr_python::bind_html(py::module_ &m) {
.def_readwrite("editable", &odr::HtmlConfig::editable)
.def_readwrite("text_document_margin",
&odr::HtmlConfig::text_document_margin)
.def_readwrite("color_scheme", &odr::HtmlConfig::color_scheme)
.def_readwrite("spreadsheet_limit", &odr::HtmlConfig::spreadsheet_limit)
.def_readwrite("spreadsheet_limit_by_content",
&odr::HtmlConfig::spreadsheet_limit_by_content)
Expand Down
8 changes: 8 additions & 0 deletions python/tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ def test_html_config_defaults():
assert config.spreadsheet_limit.rows == 100


def test_html_config_color_scheme_defaults():
config = pyodr.HtmlConfig()
assert config.color_scheme == pyodr.HtmlColorScheme.light

config.color_scheme = pyodr.HtmlColorScheme.system
assert config.color_scheme == pyodr.HtmlColorScheme.system


def test_html_config_viewport_defaults():
config = pyodr.HtmlConfig()
assert config.viewport_mode == pyodr.HtmlViewportMode.automatic
Expand Down
6 changes: 6 additions & 0 deletions python/tests/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def test_capabilities_by_file_type():
odt = pyodr.capabilities_by_file_type(pyodr.FileType.opendocument_text)
assert odt.open
assert odt.translate_html
assert odt.color_scheme
assert odt.edit

# detected and named, but there is no decoder behind it
Expand All @@ -109,6 +110,11 @@ def test_capabilities_by_file_type():
pyodr.FileType.opendocument_spreadsheet
).edit

# a pdf renders, but paints its own page backgrounds
pdf = pyodr.capabilities_by_file_type(pyodr.FileType.portable_document_format)
assert pdf.translate_html
assert not pdf.color_scheme


def test_decoded_file_capabilities(odt_path):
capabilities = pyodr.open(str(odt_path)).capabilities()
Expand Down
2 changes: 2 additions & 0 deletions src/odr/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ FileTypeCapabilities DecodedFile::capabilities() const {
// an encrypted file has to be decrypted before it renders
result.translate_html =
result.translate_html && encryption_state() != EncryptionState::encrypted;
// there is no scheme without html
result.color_scheme = result.color_scheme && result.translate_html;

// `edit`/`save`/`encrypt` stay as declared β€” resolving them would mean
// decoding the document; ask `Document` for the precise answer
Expand Down
1 change: 1 addition & 0 deletions src/odr/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ struct FileTypeCapabilities final {
bool open{}; ///< a decoder exists; @ref odr::open can decode it
bool decrypt{}; ///< encrypted instances can be decrypted
bool translate_html{}; ///< @ref html::translate produces output
bool color_scheme{}; ///< the view honors @ref HtmlConfig::color_scheme
bool edit{}; ///< @ref Document::is_editable can be `true`
bool save{}; ///< @ref Document::save is supported
bool encrypt{}; ///< @ref Document::save with a password is supported
Expand Down
11 changes: 11 additions & 0 deletions src/odr/html.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ enum class HtmlTableGridlines {
hard,
};

/// @brief The colors the emitted HTML renders against. @ref
/// FileTypeCapabilities::color_scheme says which views honor it.
enum class HtmlColorScheme {
light, ///< a white page, carrying the colors the document gives its content
dark, ///< a dark page, which the document's own colors give way to
system, ///< `light` or `dark`, by the reader's `prefers-color-scheme`
};

/// @brief Initial zoom of the emitted HTML on mobile (viewport meta tag).
/// Desktop browsers ignore the tag entirely.
enum class HtmlViewportMode {
Expand Down Expand Up @@ -124,6 +132,9 @@ struct HtmlConfig {
// text document margin
bool text_document_margin{false};

// colors the output renders against
HtmlColorScheme color_scheme{HtmlColorScheme::light};

// spreadsheet table limit
std::optional<TableDimensions> spreadsheet_limit{TableDimensions(10000, 500)};
bool spreadsheet_limit_by_content{true};
Expand Down
Loading
Loading