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

## Unreleased

- Html views take a zoom from their host: `odr.getZoom()`, `setZoom(value,
focus)`, `adjustZoom()`, `resetZoom()`, `isZoomFitted()`, `onZoomChange`.
`HtmlConfig::initial_zoom` sets what they open at, script or no script.
- A pdf that nests parentheses inside a string opens, and keeps its document
metadata β€” `cairo` and `pdfTeX` write their `/Producer` that way.
- A jni build without a JDK fails instead of shipping a package missing
Expand Down
2 changes: 2 additions & 0 deletions apple/include/OdrCoreObjC/ODRHtml.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ NS_SWIFT_NAME(HtmlConfig)
@property(nonatomic, copy, nullable) NSString *viewportContent;
/// The width the output is shown at, in css pixels; fits paged content to it.
@property(nonatomic, strong, nullable) NSNumber *viewportWidth;
/// The zoom the view opens at, 1 being actual size; `nil` follows the fit.
@property(nonatomic, strong, nullable) NSNumber *initialZoom;

@property(nonatomic) BOOL formatHtml;
/// Repeated `htmlIndentString` per nesting level; 0 disables indentation.
Expand Down
7 changes: 7 additions & 0 deletions apple/src/ODRHtml.mm
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
_viewportWidth = config.viewport_width.has_value()
? @(static_cast<unsigned int>(*config.viewport_width))
: nil;
_initialZoom =
config.initial_zoom.has_value() ? @(*config.initial_zoom) : nil;
_formatHtml = config.format_html ? YES : NO;
_htmlIndent = config.html_indent;
_htmlIndentString = to_nsstring(config.html_indent_string);
Expand Down Expand Up @@ -178,6 +180,11 @@ - (instancetype)initWithNativeConfig:(const odr::HtmlConfig &)config {
} else {
config.viewport_width.reset();
}
if (_initialZoom != nil) {
config.initial_zoom = _initialZoom.doubleValue;
} else {
config.initial_zoom.reset();
}
config.format_html = _formatHtml == YES;
config.html_indent = _htmlIndent;
config.html_indent_string = to_string(_htmlIndentString);
Expand Down
2 changes: 2 additions & 0 deletions jni/java/app/opendocument/core/HtmlConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public final class HtmlConfig {
public String viewportContent;
/** The width the output is shown at, in css pixels; fits paged content to it. */
public Integer viewportWidth;
/** The zoom the view opens at, 1 being actual size; {@code null} follows the fit. */
public Double initialZoom;

public boolean formatHtml = false;
public int htmlIndent = 1;
Expand Down
15 changes: 15 additions & 0 deletions jni/src/jni_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ jobject html_config_to_java(JNIEnv *env, const odr::HtmlConfig &config) {
make_string_opt(env, config.viewport_content));
set_object("viewportWidth", "Ljava/lang/Integer;",
box_integer(env, config.viewport_width));
set_object("initialZoom", "Ljava/lang/Double;",
box_double(env, config.initial_zoom));
set_boolean("formatHtml", config.format_html);
set_int("htmlIndent", config.html_indent);
set_string("htmlIndentString", config.html_indent_string);
Expand Down Expand Up @@ -523,6 +525,19 @@ odr::HtmlConfig html_config_from_java(JNIEnv *env, jobject config) {
}
env->DeleteLocalRef(width);
}
{
jobject zoom = get_object("initialZoom", "Ljava/lang/Double;");
if (zoom == nullptr) {
result.initial_zoom = std::nullopt;
} else {
jclass double_cls = env->GetObjectClass(zoom);
jmethodID double_value =
env->GetMethodID(double_cls, "doubleValue", "()D");
result.initial_zoom = env->CallDoubleMethod(zoom, double_value);
env->DeleteLocalRef(double_cls);
}
env->DeleteLocalRef(zoom);
}
result.format_html = get_boolean("formatHtml");
result.html_indent = static_cast<std::uint8_t>(get_int("htmlIndent"));
result.html_indent_string = get_string("htmlIndentString");
Expand Down
3 changes: 3 additions & 0 deletions jni/tests/app/opendocument/core/HtmlTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void htmlConfigDefaults() {
assertNull(config.spreadsheetViewportMode);
assertNull(config.viewportContent);
assertNull(config.viewportWidth);
assertNull(config.initialZoom);
}

@Test
Expand All @@ -48,6 +49,7 @@ void viewportConfigRoundTrips() throws IOException {
config.spreadsheetViewportMode = HtmlViewportMode.ACTUAL_SIZE;
config.viewportContent = "width=420";
config.viewportWidth = 420;
config.initialZoom = 1.5;

Path cache = Files.createDirectories(tempDir.resolve("cache"));
DecodedFile file = Odr.open(TestFiles.odtFile(tempDir).toString());
Expand All @@ -57,6 +59,7 @@ void viewportConfigRoundTrips() throws IOException {
assertEquals(HtmlViewportMode.ACTUAL_SIZE, readBack.spreadsheetViewportMode);
assertEquals("width=420", readBack.viewportContent);
assertEquals(Integer.valueOf(420), readBack.viewportWidth);
assertEquals(Double.valueOf(1.5), readBack.initialZoom);
}

/** The C++ suite covers the mode matrix; this only proves the config crosses JNI. */
Expand Down
1 change: 1 addition & 0 deletions python/src/bind_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void odr_python::bind_html(py::module_ &m) {
&odr::HtmlConfig::spreadsheet_viewport_mode)
.def_readwrite("viewport_content", &odr::HtmlConfig::viewport_content)
.def_readwrite("viewport_width", &odr::HtmlConfig::viewport_width)
.def_readwrite("initial_zoom", &odr::HtmlConfig::initial_zoom)
.def_readwrite("format_html", &odr::HtmlConfig::format_html)
.def_readwrite("html_indent", &odr::HtmlConfig::html_indent)
.def_readwrite("html_indent_string", &odr::HtmlConfig::html_indent_string)
Expand Down
3 changes: 3 additions & 0 deletions python/tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ def test_html_config_viewport_defaults():
assert config.spreadsheet_viewport_mode is None
assert config.viewport_content is None
assert config.viewport_width is None
assert config.initial_zoom is None

config.viewport_mode = pyodr.HtmlViewportMode.fit_width
config.spreadsheet_viewport_mode = pyodr.HtmlViewportMode.actual_size
config.viewport_content = "width=420"
config.viewport_width = 420
config.initial_zoom = 1.5
assert config.viewport_mode == pyodr.HtmlViewportMode.fit_width
assert config.spreadsheet_viewport_mode == pyodr.HtmlViewportMode.actual_size
assert config.viewport_content == "width=420"
assert config.viewport_width == 420
assert config.initial_zoom == 1.5


def test_viewport_mode_reaches_the_html(odt_path, tmp_path):
Expand Down
2 changes: 2 additions & 0 deletions src/odr/html.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ struct HtmlConfig {
std::optional<std::string> viewport_content;
/// The width the output is shown at, in css pixels; fits paged content to it.
std::optional<std::uint32_t> viewport_width;
/// The zoom the view opens at, 1 being actual size; unset follows the fit.
std::optional<double> initial_zoom;

/// Indent and break the output into lines rather than writing one stream.
bool format_html{false};
Expand Down
74 changes: 56 additions & 18 deletions src/odr/internal/html/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <odr/quantity.hpp>
#include <odr/style.hpp>

#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
Expand Down Expand Up @@ -80,29 +81,66 @@ std::optional<double> html::css_pixels(const std::optional<Measure> &measure) {
return pixels > 0 ? std::optional(pixels) : std::nullopt;
}

bool html::write_viewport_fit_style(
HtmlWriter &out, const HtmlConfig &config, const bool fits,
const std::optional<double> content_pixels) {
if (!fits || !config.viewport_width.has_value() ||
!content_pixels.has_value()) {
return false;
void html::write_zoom_style(HtmlWriter &out, const HtmlConfig &config,
const bool fits,
const std::optional<double> content_pixels) {
std::optional<double> fit = 1;
if (fits) {
if (config.viewport_width.has_value() && content_pixels.has_value()) {
// only ever down: a page narrower than the viewport is shown at its size
fit = std::min(1.0, static_cast<double>(config.viewport_width.value()) /
*content_pixels);
} else {
// only the view can measure this one
fit.reset();
}
}

const double factor =
static_cast<double>(config.viewport_width.value()) / *content_pixels;
// only ever down: a page narrower than the viewport is shown at its size
if (factor >= 1) {
return true;
}
const std::optional<double> zoom =
config.initial_zoom.has_value() ? config.initial_zoom : fit;

const auto number = [](const double value) {
// `Measure` renders no exponent form
return Measure(value, DynamicUnit()).to_string();
};

const bool writes_fit = !fit.has_value() || *fit != 1;
// stated because it was set: `1` is actual size asked for, not the fit
const bool writes_pin = config.initial_zoom.has_value();
const bool writes_body_zoom = zoom.has_value() && *zoom != 1;

out.write_header_style_begin();
// `zoom` scales the layout, so the page scrolls against the scaled size
// instead of overflowing beside it; `Measure` renders no exponent form
out.out() << "body{zoom:" << Measure(factor, DynamicUnit()).to_string()
<< "}";
out.write_header_style_end();

return true;
if (writes_fit || writes_pin) {
out.out() << ":root{";
if (writes_fit) {
out.out() << "--odr-fit:";
if (fit.has_value()) {
out.out() << number(*fit);
} else {
out.out() << "auto";
}
if (writes_pin) {
out.out() << ";";
}
}
if (writes_pin) {
out.out() << "--odr-zoom:" << number(*config.initial_zoom);
}
out.out() << "}";
}

if (writes_body_zoom) {
// `zoom` scales the layout, so the page scrolls against the scaled size
// instead of overflowing beside it
out.out() << "body{zoom:" << number(*zoom) << "}";
}

// paper has its own geometry; beats the script's inline zoom
out.out() << "@media print{:root{--odr-zoom:1!important}"
"body{zoom:1!important}}";

out.write_header_style_end();
}

std::string html::escape_text(std::string text) {
Expand Down
9 changes: 5 additions & 4 deletions src/odr/internal/html/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ css_pixels(const std::optional<Measure> &measure);
/// The side gutters the page column puts around its pages, in css pixels.
constexpr double page_column_gutter_pixels = 32;

/// Scales the body so @p content_pixels fits `config.viewport_width`. Writes
/// nothing unless @p fits and both widths are known.
bool write_viewport_fit_style(HtmlWriter &out, const HtmlConfig &config,
bool fits, std::optional<double> content_pixels);
/// The zoom the view opens at: `--odr-fit` fits @p content_pixels into
/// `config.viewport_width` (`auto` where only the view can measure it),
/// `--odr-zoom` pins it, `body{zoom}` applies the winner.
void write_zoom_style(HtmlWriter &out, const HtmlConfig &config, bool fits,
std::optional<double> content_pixels);

std::string escape_text(std::string text);

Expand Down
34 changes: 8 additions & 26 deletions src/odr/internal/html/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,6 @@ viewport_mode_override(const Document &document, const HtmlConfig &config) {
: std::nullopt;
}

/// True where the view should fit but no css factor could be written.
bool fits_at_load_time(const Document &document, const HtmlConfig &config,
const bool paged_content,
const std::optional<double> content_pixels) {
if (!paged_content || !fits_width(config, paged_content,
viewport_mode_override(document, config))) {
return false;
}
return !config.viewport_width.has_value() || !content_pixels.has_value();
}

/// @p name titles the view; empty when the whole document is written as one
/// file, which no one view names.
void front(const Document &document, const WritingState &state,
Expand All @@ -127,12 +116,10 @@ void front(const Document &document, const WritingState &state,
const std::optional<HtmlViewportMode> mode_override =
viewport_mode_override(document, state.config());
write_viewport_meta(out, state.config(), paged_content, mode_override);
if (paged_content) {
write_viewport_fit_style(
out, state.config(),
fits_width(state.config(), paged_content, mode_override),
content_pixels);
}
write_zoom_style(out, state.config(),
paged_content &&
fits_width(state.config(), paged_content, mode_override),
content_pixels);

write_document_style(state);
write_document_dark_style(state);
Expand Down Expand Up @@ -171,8 +158,7 @@ void front(const Document &document, const WritingState &state,
}
}

void back(const Document &document, const WritingState &state,
const std::optional<double> content_pixels) {
void back(const Document &document, const WritingState &state) {
HtmlWriter &out = state.out();

if (is_paged_content(document, state.config())) {
Expand All @@ -184,11 +170,7 @@ void back(const Document &document, const WritingState &state,
if (document.document_type() == DocumentType::spreadsheet) {
write_spreadsheet_script(state);
}
if (fits_at_load_time(document, state.config(),
is_paged_content(document, state.config()),
content_pixels)) {
write_viewport_script(state);
}
write_viewport_script(state);

out.write_body_end();
out.write_end();
Expand Down Expand Up @@ -216,7 +198,7 @@ class HtmlFragmentBase {
const std::optional<double> content = content_pixels();
front(m_document, state, m_name, content);
write_fragment(out, state);
back(m_document, state, content);
back(m_document, state);
}

protected:
Expand Down Expand Up @@ -369,7 +351,7 @@ class HtmlServiceImpl final : public HtmlService {
for (const auto &fragment : m_fragments) {
fragment->write_fragment(out, state);
}
back(m_document, state, content);
back(m_document, state);

return resources;
}
Expand Down
2 changes: 2 additions & 0 deletions src/odr/internal/html/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class HtmlServiceImpl final : public HtmlService {
out.write_header_target("_blank");
out.write_header_title("odr");
write_viewport_meta(out, config(), false);
write_zoom_style(out, config(), false, {});
write_filesystem_style(state);
write_filesystem_dark_style(state);
write_search_style(state);
Expand Down Expand Up @@ -267,6 +268,7 @@ class HtmlServiceImpl final : public HtmlService {
out.write_element_end("table");

write_search_script(state);
write_viewport_script(state);

out.write_body_end();

Expand Down
Loading
Loading