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
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/android.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ arch={{arch}}
build_type=Release
compiler=clang
compiler.version=17
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=c++_shared

[conf]
Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/apple.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ os.version={{os_version}}
build_type=Release
compiler=apple-clang
compiler.version=17
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=libc++

[conf]
Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/emscripten-wasm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ build_type=Release
compiler=emcc
compiler.version={{emsdk_version}}
compiler.libcxx=libc++
compiler.cppstd=20
compiler.cppstd=23

[options]
# No sockets and no threads in a browser; the CLI has no meaning here either.
Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/macos-15-armv8-clang-14
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ arch=armv8
build_type=Release
compiler=apple-clang
compiler.version=14
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=libc++
os=Macos

Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/macos-26-armv8-clang-14
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ arch=armv8
build_type=Release
compiler=apple-clang
compiler.version=14
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=libc++
os=Macos

Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/ubuntu-24.04-clang-18
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ arch=x86_64
build_type=Release
compiler=clang
compiler.version=18
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=libstdc++11
os=Linux

Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/ubuntu-24.04-gcc-14
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ arch=x86_64
build_type=Release
compiler=gcc
compiler.version=14
compiler.cppstd=20
compiler.cppstd=23
compiler.libcxx=libstdc++11
os=Linux

Expand Down
2 changes: 1 addition & 1 deletion .github/config/conan/profiles/windows-2022-msvc-1940
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ arch=x86_64
build_type=Release
compiler=msvc
compiler.version=194
compiler.cppstd=20
compiler.cppstd=23
compiler.runtime=dynamic
os=Windows

Expand Down
26 changes: 24 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ user-facing docs see [`README.md`](README.md) and [`docs/`](docs/README.md).

## What this is

`odr` (a.k.a. `odrcore`) is a **C++20 library that decodes documents and renders
`odr` (a.k.a. `odrcore`) is a **C++23 library that decodes documents and renders
them to HTML**. It reads many formats (ODF, OOXML, legacy MS binary, PDF, CSV, …)
behind one abstract document model and a generic HTML renderer. It is the backend
for OpenDocument.droid / .ios. Build: **CMake + Conan**; standard: **C++20**.
for OpenDocument.droid / .ios. Build: **CMake + Conan**; standard: **C++23**.

## Big picture: how a file becomes HTML

Expand Down Expand Up @@ -202,6 +202,28 @@ Dispatch `release.yml` against main, publish the draft that appears β€”
- **Formatting**: clang-format (LLVM-based, `.clang-format`); run `scripts/format`
or use the `scripts/setup` git hook. `clang-tidy` per `.clang-tidy`. CI enforces
both.
- **C++23, but three separate ceilings sit under it.** Check a facility against
all three before reaching for it, and check it by building an **object file**
β€” `-fsyntax-only` misses the codegen bugs.
- **The standard library is capped by `emsdk 3.1.73`'s libc++ 18.1**, the
oldest in the profile matrix and the newest emsdk conan-center packages.
`std::ranges::to`, `std::expected`, `std::string::resize_and_overwrite`,
`views::zip`, `ranges::fold_left` and the monadic `std::optional` are there;
**`views::enumerate`, `std::generator`, `std::move_only_function` and
`std::flat_map` are not**. `std::mdspan` fails from the other side β€”
libstdc++ has no `<mdspan>`, so the gcc-14 job would not build it.
- **`std::format` is unusable, on every slice** β€” the apple deployment target
gates it, and `fmt` is what we format with instead. See *Format numbers*
below.
- **Deducing `this` is fine on accessors, not on a capturing recursive
lambda.** NDK 28.1's clang 19 segfaults on `[&](this auto self, …)` that
recurses (`cannot compile this l-value expression yet`). The Y-combinator
form β€” pass the lambda to itself β€” is what `ooxml_text_list` still uses.
- **The public headers stay C++20**: nothing propagates the standard to a
consumer β€” no `target_compile_features(odr PUBLIC …)`, no `cppstd` in
`package_info` β€” so `src/odr/*.hpp` must keep compiling under C++20. C++23 is
for `internal/`, `cli/` and the bindings. `conanfile.py`'s `check_min_cppstd`
sits in `validate_build`, which constrains building `odr` and not using it.
- **Fail fast**: where the spec dictates what to expect, **throw** on unexpected
input (`std::runtime_error` or the typed exceptions in `src/odr/exceptions.hpp`)
rather than silently degrading. Only pass through (return empty / skip) values
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- **Breaking**: building `odr` from source now needs a **C++23** compiler. Using
it does not β€” the public headers still compile as C++20, and the package
forces no standard on a consumer.

- **Breaking**: the drawing elements `Rect`, `Line`, `Circle` and `CustomShape`
are gone, with their `ElementType` values and `Element::as_rect`/`as_line`/
`as_circle`/`as_custom_shape`. Every shape is a `Frame` now, naming itself
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
endif ()

project(odr LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

Expand Down
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def build_requirements(self):

def validate_build(self):
if self.settings.get_safe("compiler.cppstd"):
check_min_cppstd(self, 20)
check_min_cppstd(self, 23)

def configure(self):
if self.options.shared:
Expand Down
87 changes: 27 additions & 60 deletions src/odr/internal/common/element_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,21 @@ template <typename T> class SideTable final {
return m_entries.insert_or_assign(id, std::move(value)).first->second;
}

[[nodiscard]] T *find(const ElementIdentifier id) {
return find_(m_entries, id);
}
[[nodiscard]] const T *find(const ElementIdentifier id) const {
return find_(m_entries, id);
}

[[nodiscard]] T &at(const ElementIdentifier id) { return at_(*this, id); }
[[nodiscard]] const T &at(const ElementIdentifier id) const {
return at_(*this, id);
}

private:
std::unordered_map<ElementIdentifier, T> m_entries;

/// One body for both constnesses: the argument carries the const and the
/// deduced return takes it on.
template <typename Entries>
static auto *find_(Entries &entries, const ElementIdentifier id) {
const auto it = entries.find(id);
return it != std::end(entries) ? &it->second : nullptr;
[[nodiscard]] auto *find(this auto &self, const ElementIdentifier id) {
const auto it = self.m_entries.find(id);
return it != std::end(self.m_entries) ? &it->second : nullptr;
}

template <typename Self>
static auto &at_(Self &self, const ElementIdentifier id) {
[[nodiscard]] auto &at(this auto &self, const ElementIdentifier id) {
auto *entry = self.find(id);
if (entry == nullptr) {
throw std::out_of_range("SideTable::at: identifier not found");
}
return *entry;
}

private:
std::unordered_map<ElementIdentifier, T> m_entries;
};

/// A per-type payload appended as its elements are created, so the ids only
Expand All @@ -80,44 +64,32 @@ class SortedSideTable final {
return m_entries.emplace_back(static_cast<Id>(id), std::move(value)).second;
}

[[nodiscard]] T *find(const ElementIdentifier id) {
return find_(m_entries, id);
}
[[nodiscard]] const T *find(const ElementIdentifier id) const {
return find_(m_entries, id);
[[nodiscard]] auto *find(this auto &self, const ElementIdentifier id) {
const auto it =
std::ranges::lower_bound(self.m_entries, id, {}, &Entry::first);
return it != std::end(self.m_entries) && it->first == id ? &it->second
: nullptr;
}

[[nodiscard]] T &at(const ElementIdentifier id) { return at_(*this, id); }
[[nodiscard]] const T &at(const ElementIdentifier id) const {
return at_(*this, id);
[[nodiscard]] auto &at(this auto &self, const ElementIdentifier id) {
auto *entry = self.find(id);
if (entry == nullptr) {
throw std::out_of_range("SortedSideTable::at: identifier not found");
}
return *entry;
}

[[nodiscard]] auto begin() const noexcept { return m_entries.begin(); }
[[nodiscard]] auto end() const noexcept { return m_entries.end(); }
[[nodiscard]] auto begin() noexcept { return m_entries.begin(); }
[[nodiscard]] auto end() noexcept { return m_entries.end(); }
[[nodiscard]] auto begin(this auto &self) noexcept {
return self.m_entries.begin();
}
[[nodiscard]] auto end(this auto &self) noexcept {
return self.m_entries.end();
}

private:
using Entry = std::pair<Id, T>;

std::deque<Entry> m_entries;

/// One body for both constnesses: the argument carries the const and the
/// deduced return takes it on.
template <typename Entries>
static auto *find_(Entries &entries, const ElementIdentifier id) {
const auto it = std::ranges::lower_bound(entries, id, {}, &Entry::first);
return it != std::end(entries) && it->first == id ? &it->second : nullptr;
}

template <typename Self>
static auto &at_(Self &self, const ElementIdentifier id) {
auto *entry = self.find(id);
if (entry == nullptr) {
throw std::out_of_range("SortedSideTable::at: identifier not found");
}
return *entry;
}
};

/// The flat store an engine builds its element tree in: an id is the index
Expand All @@ -132,14 +104,9 @@ class ElementRegistry {

[[nodiscard]] std::size_t size() const noexcept { return m_elements.size(); }

[[nodiscard]] Element &element_at(const ElementIdentifier id) {
check_element_id(id);
return m_elements[id - 1];
}

[[nodiscard]] const Element &element_at(const ElementIdentifier id) const {
check_element_id(id);
return m_elements[id - 1];
[[nodiscard]] auto &element_at(this auto &self, const ElementIdentifier id) {
self.check_element_id(id);
return self.m_elements[id - 1];
}

void append_child(const ElementIdentifier parent_id,
Expand Down
10 changes: 5 additions & 5 deletions src/odr/internal/font/cff_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <odr/internal/util/byte_string.hpp>

#include <cstdint>
#include <ranges>
#include <string>
#include <vector>

Expand Down Expand Up @@ -108,11 +109,10 @@ std::string cff::build_cff(const std::string_view name,
// String INDEX: every glyph name gets a custom SID (391 + position). Glyph 0
// is the implicit `.notdef` (SID 0), so its name is not stored; the charset
// lists SIDs for glyphs 1..n-1.
std::vector<std::string> strings;
for (std::size_t i = 1; i < glyphs.size(); ++i) {
strings.push_back(glyphs[i].name);
}
const std::string string_index = build_index(strings);
const std::string string_index =
build_index(glyphs | std::views::drop(1) |
std::views::transform(&BuilderGlyph::name) |
std::ranges::to<std::vector<std::string>>());

// Format-0 charset: SID per glyph 1..n-1.
std::string charset;
Expand Down
53 changes: 18 additions & 35 deletions src/odr/internal/iwork/iwork_element_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,46 +90,29 @@ class ElementRegistry final
std::tuple<ElementIdentifier, Element &, Cell &>
create_cell_element(ElementType type);

[[nodiscard]] Text &text_element_at(const ElementIdentifier id) {
return m_texts.at(id);
[[nodiscard]] auto &text_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_texts.at(id);
}
[[nodiscard]] Frame &frame_element_at(const ElementIdentifier id) {
return m_frames.at(id);
[[nodiscard]] auto &frame_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_frames.at(id);
}
[[nodiscard]] Slide &slide_element_at(const ElementIdentifier id) {
return m_slides.at(id);
[[nodiscard]] auto &slide_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_slides.at(id);
}
[[nodiscard]] Table &table_element_at(const ElementIdentifier id) {
return m_tables.at(id);
[[nodiscard]] auto &table_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_tables.at(id);
}
[[nodiscard]] Sheet &sheet_element_at(const ElementIdentifier id) {
return m_sheets.at(id);
[[nodiscard]] auto &sheet_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_sheets.at(id);
}
[[nodiscard]] Cell &cell_element_at(const ElementIdentifier id) {
return m_cells.at(id);
}

[[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const {
return m_texts.at(id);
}
[[nodiscard]] const Frame &
frame_element_at(const ElementIdentifier id) const {
return m_frames.at(id);
}
[[nodiscard]] const Slide &
slide_element_at(const ElementIdentifier id) const {
return m_slides.at(id);
}
[[nodiscard]] const Table &
table_element_at(const ElementIdentifier id) const {
return m_tables.at(id);
}
[[nodiscard]] const Sheet &
sheet_element_at(const ElementIdentifier id) const {
return m_sheets.at(id);
}
[[nodiscard]] const Cell &cell_element_at(const ElementIdentifier id) const {
return m_cells.at(id);
[[nodiscard]] auto &cell_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_cells.at(id);
}

/// Links @p column_id into @p table_id's column chain. Columns are not
Expand Down
17 changes: 6 additions & 11 deletions src/odr/internal/markdown/markdown_element_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,15 @@ class ElementRegistry final
std::tuple<ElementIdentifier, Element &, TableCell &>
create_table_cell_element();

[[nodiscard]] Text &text_element_at(const ElementIdentifier id) {
return m_texts.at(id);
[[nodiscard]] auto &text_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_texts.at(id);
}
[[nodiscard]] Table &table_element_at(const ElementIdentifier id) {
return m_tables.at(id);
[[nodiscard]] auto &table_element_at(this auto &self,
const ElementIdentifier id) {
return self.m_tables.at(id);
}

[[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const {
return m_texts.at(id);
}
[[nodiscard]] const Link &link_element_at(const ElementIdentifier id) const {
return m_links.at(id);
}
Expand All @@ -77,10 +76,6 @@ class ElementRegistry final
list_item_element_at(const ElementIdentifier id) const {
return m_list_items.at(id);
}
[[nodiscard]] const Table &
table_element_at(const ElementIdentifier id) const {
return m_tables.at(id);
}
[[nodiscard]] const TableCell &
table_cell_element_at(const ElementIdentifier id) const {
return m_table_cells.at(id);
Expand Down
Loading
Loading