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 @@ -16,6 +16,12 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- An image stays inside its frame on a page read without the shipped stylesheet,
rather than covering the whole page.
- A frame that names a side instead of an offset sits on that side, so a centred
image in an odt is centred. The side it names is `GraphicStyle`'s new
`horizontal_position`, carried by the python, java and objc bindings.

## v6.5.0 - 2026-08-10

- An xml file opens as xml and reads as a foldable, highlighted source view
Expand Down
2 changes: 2 additions & 0 deletions apple/include/OdrCoreObjC/ODRStyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ NS_SWIFT_NAME(GraphicStyle)
@property(nonatomic, readonly, nullable) NSNumber *verticalAlign;
/// `ODRTextWrap`, boxed.
@property(nonatomic, readonly, nullable) NSNumber *textWrap;
/// `ODRHorizontalAlign`, boxed.
@property(nonatomic, readonly, nullable) NSNumber *horizontalPosition;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
Expand Down
1 change: 1 addition & 0 deletions apple/src/ODRStyle.mm
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ + (instancetype)styleWithHandle:(const odr::GraphicStyle &)handle {
result->_fillColor = box(handle.fill_color);
result->_verticalAlign = box_enum(handle.vertical_align);
result->_textWrap = box_enum(handle.text_wrap);
result->_horizontalPosition = box_enum(handle.horizontal_position);
return result;
}

Expand Down
3 changes: 3 additions & 0 deletions apple/swift/Style+Optionals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ extension GraphicStyle {
public var fill: Color? { fillColor?.asColor }
public var vertical: VerticalAlign? { verticalAlign?.asEnum(VerticalAlign.self) }
public var wrap: TextWrap? { textWrap?.asEnum(TextWrap.self) }
public var horizontal: HorizontalAlign? {
horizontalPosition?.asEnum(HorizontalAlign.self)
}
}

extension PageLayout {
Expand Down
9 changes: 8 additions & 1 deletion jni/java/app/opendocument/core/GraphicStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ public final class GraphicStyle {
public final Color fillColor;
public final VerticalAlign verticalAlign;
public final TextWrap textWrap;
public final HorizontalAlign horizontalPosition;

GraphicStyle(
Measure strokeWidth, Color strokeColor, Color fillColor, int verticalAlign, int textWrap) {
Measure strokeWidth,
Color strokeColor,
Color fillColor,
int verticalAlign,
int textWrap,
int horizontalPosition) {
this.strokeWidth = strokeWidth;
this.strokeColor = strokeColor;
this.fillColor = fillColor;
this.verticalAlign = VerticalAlign.fromNative(verticalAlign);
this.textWrap = TextWrap.fromNative(textWrap);
this.horizontalPosition = HorizontalAlign.fromNative(horizontalPosition);
}
}
5 changes: 3 additions & 2 deletions jni/src/jni_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,11 @@ jobject make_graphic_style(JNIEnv *env, const odr::GraphicStyle &style) {
return new_object(
env, "app/opendocument/core/GraphicStyle",
"(Lapp/opendocument/core/Measure;Lapp/opendocument/core/Color;"
"Lapp/opendocument/core/Color;II)V",
"Lapp/opendocument/core/Color;III)V",
make_measure(env, style.stroke_width),
make_color(env, style.stroke_color), make_color(env, style.fill_color),
enum_code(style.vertical_align), enum_code(style.text_wrap));
enum_code(style.vertical_align), enum_code(style.text_wrap),
enum_code(style.horizontal_position));
}

jobject make_page_layout(JNIEnv *env, const odr::PageLayout &layout) {
Expand Down
4 changes: 3 additions & 1 deletion python/src/bind_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ void odr_python::bind_style(py::module_ &m) {
.def_readwrite("stroke_color", &odr::GraphicStyle::stroke_color)
.def_readwrite("fill_color", &odr::GraphicStyle::fill_color)
.def_readwrite("vertical_align", &odr::GraphicStyle::vertical_align)
.def_readwrite("text_wrap", &odr::GraphicStyle::text_wrap);
.def_readwrite("text_wrap", &odr::GraphicStyle::text_wrap)
.def_readwrite("horizontal_position",
&odr::GraphicStyle::horizontal_position);

py::class_<odr::PageLayout>(m, "PageLayout")
.def(py::init<>())
Expand Down
38 changes: 32 additions & 6 deletions src/odr/internal/html/document_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,16 +394,28 @@ std::string html::translate_drawing_style(const GraphicStyle &graphic_style) {
}

std::string html::translate_frame_properties(const Frame &frame) {
const GraphicStyle style = frame.style();

auto text_wrap = TextWrap::run_through;
if (const GraphicStyle style = frame.style(); style.text_wrap.has_value()) {
if (style.text_wrap.has_value()) {
text_wrap = *style.text_wrap;
}

// a side, but only where no offset already places the frame
auto horizontal_position = HorizontalAlign::left;
if (!frame.x().has_value() && style.horizontal_position.has_value()) {
horizontal_position = *style.horizontal_position;
}

// The frame says it positions itself: read without the stylesheet's
// `*{position:relative}`, its image would fill the viewport instead.
std::string result;
if (const AnchorType anchor_type = frame.anchor_type();
anchor_type == AnchorType::as_char) {
result += "position:relative;";
result += "display:inline-block;";
} else if (text_wrap == TextWrap::before) {
result += "position:relative;";
result += "display:block;";
result += "float:right;clear:both;";
result += "shape-outside:content-box;";
Expand All @@ -413,12 +425,15 @@ std::string html::translate_frame_properties(const Frame &frame) {
if (const std::optional<Measure> y = frame.y(); y.has_value()) {
result += "margin-top:" + y->to_string() + ";";
}
result += "margin-right:calc(100% - ";
result += frame.x().value_or(Measure(0, DynamicUnit("in"))).to_string();
result += " - ";
result += frame.width()->to_string();
result += ");";
if (const std::optional<Measure> width = frame.width(); width.has_value()) {
result += "margin-right:calc(100% - ";
result += frame.x().value_or(Measure(0, DynamicUnit("in"))).to_string();
result += " - ";
result += width->to_string();
result += ");";
}
} else if (text_wrap == TextWrap::after) {
result += "position:relative;";
result += "display:block;";
result += "float:left;clear:both;";
result += "shape-outside:content-box;";
Expand All @@ -429,10 +444,16 @@ std::string html::translate_frame_properties(const Frame &frame) {
result += "margin-top:" + y->to_string() + ";";
}
} else if (text_wrap == TextWrap::none) {
result += "position:relative;";
result += "display:block;";
if (const std::optional<Measure> x = frame.x(); x.has_value()) {
result += "margin-left:" + x->to_string() + ";";
}
if (horizontal_position == HorizontalAlign::center) {
result += "margin-left:auto;margin-right:auto;";
} else if (horizontal_position == HorizontalAlign::right) {
result += "margin-left:auto;";
}
if (const std::optional<Measure> y = frame.y(); y.has_value()) {
result += "margin-top:" + y->to_string() + ";";
}
Expand All @@ -442,6 +463,11 @@ std::string html::translate_frame_properties(const Frame &frame) {
if (const std::optional<Measure> x = frame.x(); x.has_value()) {
result += "left:" + x->to_string() + ";";
}
if (horizontal_position == HorizontalAlign::center) {
result += "left:0;right:0;margin-left:auto;margin-right:auto;";
} else if (horizontal_position == HorizontalAlign::right) {
result += "right:0;";
}
if (const std::optional<Measure> y = frame.y(); y.has_value()) {
result += "top:" + y->to_string() + ";";
}
Expand Down
25 changes: 25 additions & 0 deletions src/odr/internal/odf/odf_style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ std::optional<TextWrap> read_text_wrap(const pugi::xml_attribute attribute) {
return {};
}

/// `from-*` names no side - the offset places those frames.
std::optional<HorizontalAlign>
read_horizontal_position(const pugi::xml_attribute attribute) {
if (!attribute) {
return {};
}
const char *value = attribute.value();
if (std::strcmp("left", value) == 0 || std::strcmp("inside", value) == 0) {
return HorizontalAlign::left;
}
if (std::strcmp("center", value) == 0) {
return HorizontalAlign::center;
}
if (std::strcmp("right", value) == 0 || std::strcmp("outside", value) == 0) {
return HorizontalAlign::right;
}
return {};
}

std::optional<PrintOrientation>
read_print_orientation(const pugi::xml_attribute attribute) {
if (!attribute) {
Expand Down Expand Up @@ -510,6 +529,12 @@ void Style::resolve_graphic_style_(const pugi::xml_node node,
read_text_wrap(graphic_properties.attribute("style:wrap"))) {
result.text_wrap = text_wrap;
}
// assigned even when it reads as none, so a `from-*` overrides an inherited
// side rather than keeping it
if (const pugi::xml_attribute attribute =
graphic_properties.attribute("style:horizontal-pos")) {
result.horizontal_position = read_horizontal_position(attribute);
}
}

StyleRegistry::StyleRegistry() = default;
Expand Down
1 change: 1 addition & 0 deletions src/odr/style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ void GraphicStyle::override(const GraphicStyle &other) {
override_if_set(fill_color, other.fill_color);
override_if_set(vertical_align, other.vertical_align);
override_if_set(text_wrap, other.text_wrap);
override_if_set(horizontal_position, other.horizontal_position);
}

} // namespace odr
2 changes: 2 additions & 0 deletions src/odr/style.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ struct GraphicStyle final {
std::optional<Color> fill_color;
std::optional<VerticalAlign> vertical_align;
std::optional<TextWrap> text_wrap;
/// The side a frame sits on; unset where its offset decides instead.
std::optional<HorizontalAlign> horizontal_position;
Comment thread
andiwand marked this conversation as resolved.

void override(const GraphicStyle &other);
};
Expand Down
4 changes: 2 additions & 2 deletions test/data.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ odr_test_data(
odr_test_data(
PATH "reference-output/odr-public"
URL "https://github.com/opendocument-app/OpenDocument.test.output.git"
REVISION "40c457831835a8e9437bfe6494cb96b4c6159fab")
REVISION "26c71049d2cbdc32ca3ea982fb814bddbc90c5fe")

odr_test_data(
PATH "reference-output/odr-private"
URL "https://github.com/opendocument-app/OpenDocument.test-private.output.git"
REVISION "d1fdace052b45f7744f24a7c6bb84a82332d37bb")
REVISION "372fb6ed9733047835ff17d6056518e6482eea3c")
Loading