diff --git a/.github/config/conan/profiles/android.jinja b/.github/config/conan/profiles/android.jinja index 7c851aa89..a5cc98e69 100644 --- a/.github/config/conan/profiles/android.jinja +++ b/.github/config/conan/profiles/android.jinja @@ -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] diff --git a/.github/config/conan/profiles/apple.jinja b/.github/config/conan/profiles/apple.jinja index 550ac77de..89e7fd3bf 100644 --- a/.github/config/conan/profiles/apple.jinja +++ b/.github/config/conan/profiles/apple.jinja @@ -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] diff --git a/.github/config/conan/profiles/emscripten-wasm b/.github/config/conan/profiles/emscripten-wasm index 7854be44c..5367b470c 100644 --- a/.github/config/conan/profiles/emscripten-wasm +++ b/.github/config/conan/profiles/emscripten-wasm @@ -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. diff --git a/.github/config/conan/profiles/macos-15-armv8-clang-14 b/.github/config/conan/profiles/macos-15-armv8-clang-14 index 64b597da4..bb64de343 100644 --- a/.github/config/conan/profiles/macos-15-armv8-clang-14 +++ b/.github/config/conan/profiles/macos-15-armv8-clang-14 @@ -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 diff --git a/.github/config/conan/profiles/macos-26-armv8-clang-14 b/.github/config/conan/profiles/macos-26-armv8-clang-14 index 64b597da4..bb64de343 100644 --- a/.github/config/conan/profiles/macos-26-armv8-clang-14 +++ b/.github/config/conan/profiles/macos-26-armv8-clang-14 @@ -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 diff --git a/.github/config/conan/profiles/ubuntu-24.04-clang-18 b/.github/config/conan/profiles/ubuntu-24.04-clang-18 index fafeefc8c..ae904e043 100644 --- a/.github/config/conan/profiles/ubuntu-24.04-clang-18 +++ b/.github/config/conan/profiles/ubuntu-24.04-clang-18 @@ -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 diff --git a/.github/config/conan/profiles/ubuntu-24.04-gcc-14 b/.github/config/conan/profiles/ubuntu-24.04-gcc-14 index c7df6917c..47a08c060 100644 --- a/.github/config/conan/profiles/ubuntu-24.04-gcc-14 +++ b/.github/config/conan/profiles/ubuntu-24.04-gcc-14 @@ -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 diff --git a/.github/config/conan/profiles/windows-2022-msvc-1940 b/.github/config/conan/profiles/windows-2022-msvc-1940 index 4a74ff921..32d5eed36 100644 --- a/.github/config/conan/profiles/windows-2022-msvc-1940 +++ b/.github/config/conan/profiles/windows-2022-msvc-1940 @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 73a5c4601..a6762bc23 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 @@ -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 ``, 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index f8f9a1e93..1f6a4d2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 750555b87..6d9fb9759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/conanfile.py b/conanfile.py index bbd418fcb..5c8fd6ae1 100644 --- a/conanfile.py +++ b/conanfile.py @@ -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: diff --git a/src/odr/internal/common/element_registry.hpp b/src/odr/internal/common/element_registry.hpp index cc07aaada..0b428ead7 100644 --- a/src/odr/internal/common/element_registry.hpp +++ b/src/odr/internal/common/element_registry.hpp @@ -33,37 +33,21 @@ template 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 m_entries; - - /// One body for both constnesses: the argument carries the const and the - /// deduced return takes it on. - template - 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 - 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 m_entries; }; /// A per-type payload appended as its elements are created, so the ids only @@ -80,44 +64,32 @@ class SortedSideTable final { return m_entries.emplace_back(static_cast(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; std::deque m_entries; - - /// One body for both constnesses: the argument carries the const and the - /// deduced return takes it on. - template - 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 - 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 @@ -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, diff --git a/src/odr/internal/font/cff_builder.cpp b/src/odr/internal/font/cff_builder.cpp index 278b63b18..fb45997ac 100644 --- a/src/odr/internal/font/cff_builder.cpp +++ b/src/odr/internal/font/cff_builder.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -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 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>()); // Format-0 charset: SID per glyph 1..n-1. std::string charset; diff --git a/src/odr/internal/iwork/iwork_element_registry.hpp b/src/odr/internal/iwork/iwork_element_registry.hpp index aed11335e..769601c3b 100644 --- a/src/odr/internal/iwork/iwork_element_registry.hpp +++ b/src/odr/internal/iwork/iwork_element_registry.hpp @@ -90,46 +90,29 @@ class ElementRegistry final std::tuple 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 diff --git a/src/odr/internal/markdown/markdown_element_registry.hpp b/src/odr/internal/markdown/markdown_element_registry.hpp index 3a97f1bc4..c093f024c 100644 --- a/src/odr/internal/markdown/markdown_element_registry.hpp +++ b/src/odr/internal/markdown/markdown_element_registry.hpp @@ -57,16 +57,15 @@ class ElementRegistry final std::tuple 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); } @@ -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); diff --git a/src/odr/internal/odf/odf_chart.cpp b/src/odr/internal/odf/odf_chart.cpp index 92b99f1ea..94a8663ca 100644 --- a/src/odr/internal/odf/odf_chart.cpp +++ b/src/odr/internal/odf/odf_chart.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -282,11 +283,8 @@ class ChartWriter { [[nodiscard]] static std::vector read_row_text(const pugi::xml_node row) { - std::vector result; - for (const pugi::xml_node cell : read_row(row)) { - result.emplace_back(read_text(cell)); - } - return result; + return read_row(row) | std::views::transform(read_text) | + std::ranges::to>(); } /// A missing data point is written `office:value="NaN"`. diff --git a/src/odr/internal/odf/odf_element_registry.hpp b/src/odr/internal/odf/odf_element_registry.hpp index db1794229..e638ed5d4 100644 --- a/src/odr/internal/odf/odf_element_registry.hpp +++ b/src/odr/internal/odf/odf_element_registry.hpp @@ -112,27 +112,19 @@ class ElementRegistry final create_sheet_cell_element(pugi::xml_node node, const TablePosition &position, bool is_repeated); - [[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]] 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]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.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 SheetCell & sheet_cell_element_at(const ElementIdentifier id) const { return m_sheet_cells.at(id); diff --git a/src/odr/internal/oldms/presentation/ppt_element_registry.hpp b/src/odr/internal/oldms/presentation/ppt_element_registry.hpp index db4000267..e4d49b820 100644 --- a/src/odr/internal/oldms/presentation/ppt_element_registry.hpp +++ b/src/odr/internal/oldms/presentation/ppt_element_registry.hpp @@ -36,26 +36,17 @@ class ElementRegistry final std::tuple create_frame_element(); std::tuple create_image_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]] 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]] Image &image_element_at(const ElementIdentifier id) { - return m_images.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 Image & - image_element_at(const ElementIdentifier id) const { - return m_images.at(id); + [[nodiscard]] auto &image_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_images.at(id); } /// Character style of a span or paragraph element, as an index into the diff --git a/src/odr/internal/oldms/presentation/ppt_io.cpp b/src/odr/internal/oldms/presentation/ppt_io.cpp index c69708931..cb41d61fc 100644 --- a/src/odr/internal/oldms/presentation/ppt_io.cpp +++ b/src/odr/internal/oldms/presentation/ppt_io.cpp @@ -72,9 +72,11 @@ std::u16string presentation::read_raw_text_chars(std::istream &in, std::string presentation::read_raw_text_bytes(std::istream &in, const std::uint32_t rec_len) { std::string buffer; - buffer.resize(rec_len); - in.read(buffer.data(), static_cast(rec_len)); - buffer.resize(static_cast(in.gcount())); + // The record length is only a claim, so the buffer is cut to what was read. + buffer.resize_and_overwrite(rec_len, [&](char *out, const std::size_t size) { + in.read(out, static_cast(size)); + return static_cast(in.gcount()); + }); return buffer; } diff --git a/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp b/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp index b4d477e4f..4f515ee7b 100644 --- a/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp +++ b/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp @@ -47,26 +47,17 @@ class ElementRegistry final std::tuple create_sheet_cell_element(const TablePosition &position); - [[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]] 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]] SheetCell &sheet_cell_element_at(const ElementIdentifier id) { - return m_sheet_cells.at(id); - } - - [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.at(id); - } - [[nodiscard]] const Sheet & - sheet_element_at(const ElementIdentifier id) const { - return m_sheets.at(id); - } - [[nodiscard]] const SheetCell & - sheet_cell_element_at(const ElementIdentifier id) const { - return m_sheet_cells.at(id); + [[nodiscard]] auto &sheet_cell_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_sheet_cells.at(id); } /// Registers a cell with its sheet: sets the cell's parent and adds it to diff --git a/src/odr/internal/oldms/text/doc_element_registry.hpp b/src/odr/internal/oldms/text/doc_element_registry.hpp index d1e34ca20..e4ae925ff 100644 --- a/src/odr/internal/oldms/text/doc_element_registry.hpp +++ b/src/odr/internal/oldms/text/doc_element_registry.hpp @@ -21,11 +21,9 @@ class ElementRegistry final std::tuple create_element(ElementType type); std::tuple create_text_element(); - [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { - return m_texts.at(id); - } - [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.at(id); + [[nodiscard]] auto &text_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_texts.at(id); } /// Character style of a span or paragraph element, as an index into the diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp index 43f0aea7e..1b921fc7c 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp @@ -34,19 +34,13 @@ class ElementRegistry final std::tuple create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); - [[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]] Text &text_element_at(const ElementIdentifier id) { - return m_texts.at(id); - } - - [[nodiscard]] const Table & - table_element_at(const ElementIdentifier id) const { - return m_tables.at(id); - } - [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.at(id); + [[nodiscard]] auto &text_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_texts.at(id); } void append_column(ElementIdentifier table_id, ElementIdentifier column_id); diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp index 1d04c4860..7126b2737 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp @@ -97,24 +97,18 @@ class ElementRegistry final const Relations &relations, const AbsPath &origin); - [[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]] SheetCell &sheet_cell_element_at(const ElementIdentifier id) { - return m_sheet_cells.at(id); + [[nodiscard]] auto &sheet_cell_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_sheet_cells.at(id); } [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { return m_texts.at(id); } - [[nodiscard]] const Sheet & - sheet_element_at(const ElementIdentifier id) const { - return m_sheets.at(id); - } - [[nodiscard]] const SheetCell & - sheet_cell_element_at(const ElementIdentifier id) const { - return m_sheet_cells.at(id); - } [[nodiscard]] const ElementRelations * element_relations(const ElementIdentifier id) const { diff --git a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp index 3743699b2..7e0fc3db8 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp @@ -35,19 +35,13 @@ class ElementRegistry final std::tuple create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); - [[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]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.at(id); - } - [[nodiscard]] const Table & - table_element_at(const ElementIdentifier id) const { - return m_tables.at(id); + [[nodiscard]] auto &table_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_tables.at(id); } void append_column(ElementIdentifier table_id, ElementIdentifier column_id); diff --git a/src/odr/internal/ooxml/text/ooxml_text_list.cpp b/src/odr/internal/ooxml/text/ooxml_text_list.cpp index e2735882a..eac47733a 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_list.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_list.cpp @@ -204,6 +204,8 @@ void resolve_list_numbering(ElementRegistry ®istry, return node.child("w:pPr").child("w:numPr"); }; + // Takes itself as a parameter rather than `this auto self`: NDK 28.1's + // clang 19 segfaults on a capturing lambda that recurses through one. const auto walk = [&](auto &self, const ElementIdentifier id) -> void { for (ElementIdentifier child_id = id; child_id != null_element_id; child_id = registry.element_at(child_id).next_sibling_id) { diff --git a/src/odr/internal/pdf/pdf_object.hpp b/src/odr/internal/pdf/pdf_object.hpp index 4e8f70bc8..e822f3667 100644 --- a/src/odr/internal/pdf/pdf_object.hpp +++ b/src/odr/internal/pdf/pdf_object.hpp @@ -257,25 +257,19 @@ class Array final { Array &operator=(const Array &) = default; Array &operator=(Array &&) = default; - [[nodiscard]] Holder &holder() { return m_holder; } - [[nodiscard]] const Holder &holder() const { return m_holder; } + [[nodiscard]] auto &holder(this auto &self) { return self.m_holder; } [[nodiscard]] std::size_t size() const { return m_holder.size(); } [[nodiscard]] bool empty() const { return m_holder.empty(); } - [[nodiscard]] Holder::iterator begin() { return m_holder.begin(); } - [[nodiscard]] Holder::iterator end() { return m_holder.end(); } - [[nodiscard]] Holder::const_iterator begin() const { - return m_holder.cbegin(); - } - [[nodiscard]] Holder::const_iterator end() const { return m_holder.cend(); } + [[nodiscard]] auto begin(this auto &self) { return self.m_holder.begin(); } + [[nodiscard]] auto end(this auto &self) { return self.m_holder.end(); } - Object &operator[](const std::size_t i) { return m_holder.at(i); } - const Object &operator[](const std::size_t i) const { return m_holder.at(i); } + [[nodiscard]] auto &operator[](this auto &self, const std::size_t i) { + return self.m_holder.at(i); + } - Object &front() { return m_holder.front(); } - [[nodiscard]] const Object &front() const { return m_holder.front(); } - Object &back() { return m_holder.back(); } - [[nodiscard]] const Object &back() const { return m_holder.back(); } + [[nodiscard]] auto &front(this auto &self) { return self.m_holder.front(); } + [[nodiscard]] auto &back(this auto &self) { return self.m_holder.back(); } void to_stream(std::ostream &) const; [[nodiscard]] std::string to_string() const; @@ -291,36 +285,25 @@ class Dictionary final { Dictionary() = default; explicit Dictionary(Holder holder) : m_holder{std::move(holder)} {} - Holder &holder() { return m_holder; } - [[nodiscard]] const Holder &holder() const { return m_holder; } + [[nodiscard]] auto &holder(this auto &self) { return self.m_holder; } [[nodiscard]] std::size_t size() const { return m_holder.size(); } - using iterator = Holder::iterator; - using const_iterator = Holder::const_iterator; - - [[nodiscard]] iterator begin() { return m_holder.begin(); } - [[nodiscard]] iterator end() { return m_holder.end(); } - [[nodiscard]] const_iterator begin() const { return m_holder.cbegin(); } - [[nodiscard]] const_iterator end() const { return m_holder.cend(); } + [[nodiscard]] auto begin(this auto &self) { return self.m_holder.begin(); } + [[nodiscard]] auto end(this auto &self) { return self.m_holder.end(); } + /// Inserts a null when the key is absent — `at` is the one that throws. Object &operator[](const std::string &name) { return m_holder[name]; } const Object &operator[](const std::string &name) const { return m_holder.at(name); } - [[nodiscard]] Object &at(const std::string &name) { - return m_holder.at(name); - } - [[nodiscard]] const Object &at(const std::string &name) const { - return m_holder.at(name); + [[nodiscard]] auto &at(this auto &self, const std::string &name) { + return self.m_holder.at(name); } - [[nodiscard]] Holder::iterator find(const std::string &name) { - return m_holder.find(name); - } - [[nodiscard]] Holder::const_iterator find(const std::string &name) const { - return m_holder.find(name); + [[nodiscard]] auto find(this auto &self, const std::string &name) { + return self.m_holder.find(name); } [[nodiscard]] bool has_key(const std::string &name) const { diff --git a/src/odr/internal/rtf/rtf_element_registry.hpp b/src/odr/internal/rtf/rtf_element_registry.hpp index c59c12226..032e3d25a 100644 --- a/src/odr/internal/rtf/rtf_element_registry.hpp +++ b/src/odr/internal/rtf/rtf_element_registry.hpp @@ -20,11 +20,9 @@ class ElementRegistry final std::tuple create_element(ElementType type); std::tuple create_text_element(); - [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { - return m_texts.at(id); - } - [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { - return m_texts.at(id); + [[nodiscard]] auto &text_element_at(this auto &self, + const ElementIdentifier id) { + return self.m_texts.at(id); } private: diff --git a/src/odr/internal/rtf/rtf_tokenizer.cpp b/src/odr/internal/rtf/rtf_tokenizer.cpp index a929ee5df..268bd6d8d 100644 --- a/src/odr/internal/rtf/rtf_tokenizer.cpp +++ b/src/odr/internal/rtf/rtf_tokenizer.cpp @@ -73,8 +73,15 @@ std::string Tokenizer::bumpnc(const std::size_t n) { const std::size_t offset = result.size(); const auto m = static_cast(std::min(chunk_size, n - offset)); - result.resize(offset + static_cast(m)); - if (m_sb->sgetn(result.data() + offset, m) != m) { + // The callback must not throw, so a short read reports itself by + // shrinking the string back. + result.resize_and_overwrite(offset + static_cast(m), + [&](char *out, const std::size_t size) { + return m_sb->sgetn(out + offset, m) == m + ? size + : offset; + }); + if (result.size() == offset) { m_in->setstate(std::ios::eofbit); throw std::runtime_error("unexpected stream exhaust"); } diff --git a/src/odr/internal/util/byte_stream_util.cpp b/src/odr/internal/util/byte_stream_util.cpp index fba64f92a..c39f09db0 100644 --- a/src/odr/internal/util/byte_stream_util.cpp +++ b/src/odr/internal/util/byte_stream_util.cpp @@ -53,8 +53,15 @@ std::string byte_stream::read_u8s(std::istream &in, const std::uint64_t n) { const std::size_t offset = result.size(); const auto step = static_cast(std::min(chunk_size, n - offset)); - result.resize(offset + step); - read(in, result.data() + offset, step); + // The callback must not throw, so a short read reports itself by + // shrinking the string back. + result.resize_and_overwrite( + offset + step, [&](char *out, const std::size_t size) { + return try_read(in, out + offset, step) ? size : offset; + }); + if (result.size() == offset) { + throw_exhausted(); + } } return result; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 45577d5f3..959b00176 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -163,3 +163,11 @@ target_link_libraries(odr_test ) gtest_add_tests(TARGET odr_test) + +# Holds the public headers to C++20. Not a gtest: it compiles or it does not. +add_library(odr_public_headers_cpp20 OBJECT "public_headers_cpp20.cpp") +target_link_libraries(odr_public_headers_cpp20 PRIVATE odr) +set_target_properties(odr_public_headers_cpp20 PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF) diff --git a/test/public_headers_cpp20.cpp b/test/public_headers_cpp20.cpp new file mode 100644 index 000000000..d85c538cb --- /dev/null +++ b/test/public_headers_cpp20.cpp @@ -0,0 +1,19 @@ +// Every public header, compiled as a C++20 consumer sees them. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include