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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ producer's layout recorded — odf's `text:soft-page-break` — are not parsed.
| `src/odr/internal/magic.*`, `open_strategy.*` | File-type detection + open/dispatch. |
| `src/odr/internal/file_type_table.*` | **The** per-`FileType` table: extensions, MIME types, category, document type, `FileTypeCapabilities`. Every public lookup in `odr.hpp` is a thin forward into it — extend the table, not the lookups. |
| `src/odr/internal/html/` | Generic HTML renderer. |
| `src/odr/internal/html/frontend/` | The stylesheets and scripts the renderer writes into the page, as the files a browser reads. `cmake/frontend_assets.cmake` embeds them into the library; `frontend.cpp` names them and decides which view writes which. |
| `src/odr/internal/cfb/`, `zip/` | Container formats (CFB, ZIP). |
| `src/odr/internal/odf/` | OpenDocument (odt/ods/odp/odg); see [`odf/AGENTS.md`](src/odr/internal/odf/AGENTS.md). |
| `src/odr/internal/ooxml/` | OOXML (docx/pptx/xlsx); see [`ooxml/AGENTS.md`](src/odr/internal/ooxml/AGENTS.md) + per-format docs. |
Expand Down
53 changes: 52 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,55 @@ else ()
configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY)
endif ()

# The renderer's own stylesheets and scripts. They live as the files a browser,
# a formatter and `test/browser` can read, and are embedded into the library so
# that nothing has to be shipped beside it.
set(ODR_FRONTEND_ASSETS
"document.css"
"document-dark.css"
"spreadsheet.css"
"spreadsheet-dark.css"
"text.css"
"text-dark.css"
"xml.css"
"xml-dark.css"
"filesystem.css"
"filesystem-dark.css"
"media.css"
"search.css"
"search-dark.css"
"document.js"
"search.js"
"spreadsheet.js"
"sheet-editing.js"
"text.js"
"viewport.js"
"pdf-annotation.css"
"pdf-annotation.js"
)
set(ODR_FRONTEND_ASSET_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/odr/internal/html/frontend")
# Generated rather than installed: it is an implementation detail of one source
# file, and a consumer of the installed headers must not see it.
set(ODR_FRONTEND_ASSETS_HEADER "${CMAKE_CURRENT_BINARY_DIR}/generated/odr/internal/html/frontend_assets.hpp")
set(ODR_FRONTEND_ASSET_PATHS "")
foreach (ODR_FRONTEND_ASSET IN LISTS ODR_FRONTEND_ASSETS)
list(APPEND ODR_FRONTEND_ASSET_PATHS "${ODR_FRONTEND_ASSET_DIRECTORY}/${ODR_FRONTEND_ASSET}")
endforeach ()
# A `;` in a `-D` argument would split it into several, so the list travels
# joined.
string(JOIN "|" ODR_FRONTEND_ASSETS_ARGUMENT ${ODR_FRONTEND_ASSETS})
add_custom_command(
OUTPUT "${ODR_FRONTEND_ASSETS_HEADER}"
COMMAND "${CMAKE_COMMAND}"
"-DASSET_DIR=${ODR_FRONTEND_ASSET_DIRECTORY}"
"-DASSETS=${ODR_FRONTEND_ASSETS_ARGUMENT}"
"-DOUTPUT=${ODR_FRONTEND_ASSETS_HEADER}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/frontend_assets.cmake"
DEPENDS ${ODR_FRONTEND_ASSET_PATHS} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/frontend_assets.cmake"
COMMENT "Embedding the html frontend assets"
VERBATIM
)

set(ODR_SOURCE_FILES
"src/odr/archive.cpp"
"src/odr/document.cpp"
Expand Down Expand Up @@ -302,12 +351,14 @@ set(ODR_SOURCE_FILES
"src/odr/internal/zip/zip_util.cpp"
)

add_library(odr ${ODR_SOURCE_FILES})
add_library(odr ${ODR_SOURCE_FILES} "${ODR_FRONTEND_ASSETS_HEADER}")
set_target_properties(odr PROPERTIES OUTPUT_NAME odr)
target_include_directories(odr
PUBLIC
src
${CMAKE_CURRENT_BINARY_DIR}/src
PRIVATE
"${CMAKE_CURRENT_BINARY_DIR}/generated"
)
target_link_libraries(odr
PRIVATE
Expand Down
71 changes: 71 additions & 0 deletions cmake/frontend_assets.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Generates the header that holds the renderer's stylesheets and scripts as
# `std::string_view`s, one per file in `src/odr/internal/html/frontend/`.
#
# Run in script mode:
# cmake -DASSET_DIR=<dir> -DASSETS=<name|name|…> -DOUTPUT=<header> \
# -P cmake/frontend_assets.cmake
#
# The bytes become a `char` array rather than a string literal: msvc caps a
# literal at 16380 bytes and an array at nothing.

cmake_minimum_required(VERSION 3.15)

if (NOT DEFINED ASSET_DIR OR NOT DEFINED ASSETS OR NOT DEFINED OUTPUT)
message(FATAL_ERROR "ASSET_DIR, ASSETS and OUTPUT are all required")
endif ()

string(REPLACE "|" ";" ASSETS "${ASSETS}")

# Sixteen `'\x..',` in a row: cmake's regex has no `{n}` repetition, and `.`
# stands in for the backslash, which two levels of escaping would eat.
set(_row_pattern "")
foreach (_ RANGE 15)
string(APPEND _row_pattern "'....',")
endforeach ()

set(_storage "")
set(_views "")

foreach (_asset IN LISTS ASSETS)
set(_path "${ASSET_DIR}/${_asset}")
if (NOT EXISTS "${_path}")
message(FATAL_ERROR "no such frontend asset: ${_path}")
endif ()

file(READ "${_path}" _hex HEX)
if (_hex STREQUAL "")
message(FATAL_ERROR "empty frontend asset: ${_path}")
endif ()

string(REGEX REPLACE "\\.|-" "_" _name "${_asset}")

# A character literal, not an integer: a byte over 0x7f narrows on a
# platform whose `char` is signed, and a negative one where it is unsigned.
string(REGEX REPLACE "(..)" "'\\\\x\\1'," _bytes "${_hex}")
string(REGEX REPLACE "(${_row_pattern})" "\\1\n " _bytes "${_bytes}")

string(APPEND _storage
"inline constexpr char ${_name}[]{\n ${_bytes}\n};\n\n")
string(APPEND _views
"inline constexpr std::string_view ${_name}{\n"
" storage::${_name}, sizeof(storage::${_name})};\n")
endforeach ()

set(_content "// Generated from src/odr/internal/html/frontend/ by\n\
// cmake/frontend_assets.cmake. Do not edit.\n\
\n\
#pragma once\n\
\n\
#include <string_view>\n\
\n\
namespace odr::internal::html::frontend_assets {\n\
\n\
/// The bytes themselves, which nothing outside this header names.\n\
namespace storage {\n\
\n\
${_storage}} // namespace storage\n\
\n\
${_views}\n\
} // namespace odr::internal::html::frontend_assets\n")

file(WRITE "${OUTPUT}" "${_content}")
9 changes: 4 additions & 5 deletions docs/design/spreadsheet-editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ results go stale the moment an input changes.
| Cell value | `SheetCellAdapter` | `sheet_cell_value` reads the number and the formula (step 0.1, landed); `sheet_cell_value_type` stays the cheap question the renderer asks. Dates, booleans and errors still report `string` |
| Number formats | — | Not parsed in either engine. ODS shows the producer's cached `text:p`; XLSX shows the raw `<v>` (a date is its serial) |
| Formulas | `sheet_cell_value` | The expression is read and handed out as a string (step 0.1, landed); nothing parses or evaluates it. XLSX shows the cached `<v>`, ODS the cached `text:p`. `xls` and `numbers` drop the expression at parse time |
| Browser: sheet script | `frontend.cpp::spreadsheet_js` | Hover/pin, raise a clipped cell over its neighbours, sort rows in the DOM. Sorting reorders `<tr>`s, so a row's identity is its `<th>` label, not its index. Publishes `odr.sheet` (step 1.1, landed), and the value and reflow half of it (steps 1.2/1.3, landed) |
| Browser: editing script | `frontend.cpp::document_js` | A `MutationObserver` over `contenteditable` runs keyed by `data-odr-path`; `odr.generateDiff()` emits the envelope |
| Browser: sheet editor | `frontend.cpp::sheet_editing_js` | `odr.editing` with the mode, the locks and the refusals (step 1.1, landed), and the overlay that types into a cell (steps 1.2/1.3, landed). Undo/redo and `committed()` are step 1.4 |
| Browser: sheet script | `html/frontend/spreadsheet.js` | Hover/pin, raise a clipped cell over its neighbours, sort rows in the DOM. Sorting reorders `<tr>`s, so a row's identity is its `<th>` label, not its index. Publishes `odr.sheet` (step 1.1, landed), and the value and reflow half of it (steps 1.2/1.3, landed) |
| Browser: editing script | `html/frontend/document.js` | A `MutationObserver` over `contenteditable` runs keyed by `data-odr-path`; `odr.generateDiff()` emits the envelope |
| Browser: sheet editor | `html/frontend/sheet-editing.js` | `odr.editing` with the mode, the locks and the refusals (step 1.1, landed), and the overlay that types into a cell (steps 1.2/1.3, landed). Undo/redo and `committed()` are step 1.4 |
| Wire format | `document.cpp::Document::edit` | The op envelope, `setCell` and `setText` (step 0.4, landed) |
| Addressing | `DocumentPath` | Already spells a cell by position: `/child:0/cell:A1/...` |
| Capabilities | `file_type_table.cpp` | `ods` and `xlsx` declare `edit` and `save` (step 0.2, landed); `csv` declares neither. `odr_test` checks the declaration against `Document::is_editable` |
Expand Down Expand Up @@ -285,8 +285,7 @@ wrapper — an editor whose overlay is open while the other script lowers the
cell underneath it.

**Why not one script instead:** the read-only view would carry the editor it
never runs, and a raw string literal caps at 16380 bytes on msvc
(`fits_a_literal`), which the two together would reach during step 1.
never runs.

**The coordinates are the ones an op names** (decision 1), never a DOM index.
The wash paints through `nth-child`, so the ruler's index stays private to the
Expand Down
Loading
Loading