Skip to content

Commit a108cf5

Browse files
andiwandclaude
andcommitted
refactor(doc): make the style registry immutable after construction
StyleRegistry now takes its font names and resolved styles in the constructor (mirroring the xls registry); set_font_names/add_style are gone, read_character_runs collects styles into a plain vector and no longer depends on the registry, and the default style moved into a named default_character_style() factory. Also review cleanups across the branch: - emplace_back for aggregate push_backs (C++20 parenthesized init) - intern cache: single try_emplace instead of find + emplace with a second std::string(grpprl) allocation - fixed-size integer types instead of plain int - drop the local read_u16 in favor of util::byte::from_little_endian - shared append_u16/append_u32 test helpers in oldms_test_util.hpp - doc_helper.cpp: definitions moved into the namespace block Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJRDZcksAtKnXyhDTeMkMK
1 parent f22459e commit a108cf5

10 files changed

Lines changed: 146 additions & 139 deletions

File tree

src/odr/internal/oldms/spreadsheet/xls_parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void parse_globals(BiffReader &reader, std::vector<BoundSheet> &sheets,
6767
case biff_font: {
6868
const auto font = reader.read<FontFixed>();
6969
std::string name = reader.read_short_xl_unicode_string();
70-
styles.fonts.push_back({font, std::move(name)});
70+
styles.fonts.emplace_back(font, std::move(name));
7171
} break;
7272
case biff_xf: {
7373
styles.xfs.push_back(reader.read<XfBody>());

src/odr/internal/oldms/text/doc_helper.cpp

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,11 @@
1313
#include <string_view>
1414
#include <unordered_map>
1515

16-
namespace odr::internal::oldms {
17-
18-
text::CharacterIndex text::read_character_index(std::istream &in) {
19-
CharacterIndex result;
20-
21-
read_Clx(in, skip_Prc, [&](std::istream &) {
22-
if (const int c = in.get(); c != 0x2) {
23-
throw std::runtime_error("Unexpected input: " + std::to_string(c));
24-
}
25-
const std::uint32_t lcb = util::byte_stream::read<std::uint32_t>(in);
26-
std::string plcPcd = util::stream::read(in, lcb);
27-
const PlcPcdMap plc_pcd_map(plcPcd.data(), plcPcd.size());
28-
29-
for (std::uint32_t i = 0; i < plc_pcd_map.n(); ++i) {
30-
const bool is_compressed = plc_pcd_map.aData(i).fc.fCompressed != 0;
31-
const std::size_t data_offset = is_compressed
32-
? plc_pcd_map.aData(i).fc.fc / 2
33-
: plc_pcd_map.aData(i).fc.fc;
34-
const std::size_t length_cp = plc_pcd_map.aCP(i + 1) - plc_pcd_map.aCP(i);
35-
result.append(plc_pcd_map.aCP(i), length_cp, data_offset, is_compressed);
36-
}
37-
});
38-
39-
return result;
40-
}
16+
namespace odr::internal::oldms::text {
4117

42-
void text::CharacterRuns::append_run(const std::uint32_t begin_fc,
43-
const std::uint32_t end_fc,
44-
const std::uint32_t style_index) {
18+
void CharacterRuns::append_run(const std::uint32_t begin_fc,
19+
const std::uint32_t end_fc,
20+
const std::uint32_t style_index) {
4521
if (begin_fc >= end_fc ||
4622
(!m_runs.empty() && begin_fc < m_runs.back().end_fc)) {
4723
throw std::runtime_error("doc: character runs must be ascending");
@@ -51,10 +27,10 @@ void text::CharacterRuns::append_run(const std::uint32_t begin_fc,
5127
m_runs.back().end_fc = end_fc;
5228
return;
5329
}
54-
m_runs.push_back({begin_fc, end_fc, style_index});
30+
m_runs.emplace_back(begin_fc, end_fc, style_index);
5531
}
5632

57-
std::uint32_t text::CharacterRuns::index_at(const std::uint32_t fc) const {
33+
std::uint32_t CharacterRuns::index_at(const std::uint32_t fc) const {
5834
const auto it = std::ranges::upper_bound(m_runs, fc, {}, &Run::begin_fc);
5935
if (it == m_runs.begin()) {
6036
return 0;
@@ -63,7 +39,7 @@ std::uint32_t text::CharacterRuns::index_at(const std::uint32_t fc) const {
6339
return fc < run.end_fc ? run.style_index : 0;
6440
}
6541

66-
std::uint32_t text::CharacterRuns::chunk_end(const std::uint32_t fc) const {
42+
std::uint32_t CharacterRuns::chunk_end(const std::uint32_t fc) const {
6743
const auto it = std::ranges::upper_bound(m_runs, fc, {}, &Run::begin_fc);
6844
if (it != m_runs.begin() && fc < std::prev(it)->end_fc) {
6945
return std::prev(it)->end_fc;
@@ -74,17 +50,46 @@ std::uint32_t text::CharacterRuns::chunk_end(const std::uint32_t fc) const {
7450
return std::numeric_limits<std::uint32_t>::max();
7551
}
7652

77-
text::CharacterRuns text::read_character_runs(std::istream &document_stream,
78-
std::istream &table_stream,
79-
const FcLcb plcf_bte_chpx,
80-
StyleRegistry &style_registry) {
53+
} // namespace odr::internal::oldms::text
54+
55+
namespace odr::internal::oldms {
56+
57+
text::CharacterIndex text::read_character_index(std::istream &in) {
58+
CharacterIndex result;
59+
60+
read_Clx(in, skip_Prc, [&](std::istream &) {
61+
if (const int c = in.get(); c != 0x2) {
62+
throw std::runtime_error("Unexpected input: " + std::to_string(c));
63+
}
64+
const std::uint32_t lcb = util::byte_stream::read<std::uint32_t>(in);
65+
std::string plcPcd = util::stream::read(in, lcb);
66+
const PlcPcdMap plc_pcd_map(plcPcd.data(), plcPcd.size());
67+
68+
for (std::uint32_t i = 0; i < plc_pcd_map.n(); ++i) {
69+
const bool is_compressed = plc_pcd_map.aData(i).fc.fCompressed != 0;
70+
const std::size_t data_offset = is_compressed
71+
? plc_pcd_map.aData(i).fc.fc / 2
72+
: plc_pcd_map.aData(i).fc.fc;
73+
const std::size_t length_cp = plc_pcd_map.aCP(i + 1) - plc_pcd_map.aCP(i);
74+
result.append(plc_pcd_map.aCP(i), length_cp, data_offset, is_compressed);
75+
}
76+
});
77+
78+
return result;
79+
}
80+
81+
text::CharacterRuns
82+
text::read_character_runs(std::istream &document_stream,
83+
std::istream &table_stream, const FcLcb plcf_bte_chpx,
84+
std::vector<TextStyle> &styles,
85+
const std::span<const std::string> font_names) {
8186
CharacterRuns result;
8287
if (plcf_bte_chpx.lcb == 0) {
8388
return result;
8489
}
8590

86-
// Copy: `add_style` may reallocate the registry's style storage.
87-
const TextStyle default_style = style_registry.text_style(0);
91+
// Copy: appending to `styles` may reallocate.
92+
const TextStyle default_style = styles.at(0);
8893

8994
table_stream.seekg(plcf_bte_chpx.fc);
9095
std::string plc_bytes = util::stream::read(table_stream, plcf_bte_chpx.lcb);
@@ -96,14 +101,13 @@ text::CharacterRuns text::read_character_runs(std::istream &document_stream,
96101
if (grpprl.empty()) {
97102
return 0;
98103
}
99-
const auto it = style_cache.find(std::string(grpprl));
100-
if (it != style_cache.end()) {
101-
return it->second;
104+
const auto [it, inserted] = style_cache.try_emplace(std::string(grpprl));
105+
if (inserted) {
106+
styles.push_back(
107+
apply_character_sprms(default_style, grpprl, font_names));
108+
it->second = static_cast<std::uint32_t>(styles.size() - 1);
102109
}
103-
const std::uint32_t index = style_registry.add_style(apply_character_sprms(
104-
default_style, grpprl, style_registry.font_names()));
105-
style_cache.emplace(std::string(grpprl), index);
106-
return index;
110+
return it->second;
107111
};
108112

109113
for (std::uint32_t i = 0; i < plc.n(); ++i) {

src/odr/internal/oldms/text/doc_helper.hpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#pragma once
22

3+
#include <odr/style.hpp>
4+
35
#include <odr/internal/oldms/text/doc_structs.hpp>
46

57
#include <algorithm>
68
#include <cstdint>
79
#include <iosfwd>
10+
#include <span>
811
#include <stdexcept>
12+
#include <string>
913
#include <vector>
1014

1115
namespace odr::internal::oldms::text {
1216

13-
class StyleRegistry;
14-
1517
class CharacterIndex {
1618
public:
1719
[[nodiscard]] bool empty() const { return m_entries.empty(); }
@@ -111,8 +113,8 @@ class CharacterIndex {
111113
CharacterIndex read_character_index(std::istream &in);
112114

113115
/// The document's character-formatting runs, keyed by WordDocument-stream
114-
/// offset (fc), each referencing a `StyleRegistry` style index. Index 0 is
115-
/// the default style; offsets outside any run resolve to it.
116+
/// offset (fc), each referencing a style index. Index 0 is the default style;
117+
/// offsets outside any run resolve to it.
116118
class CharacterRuns final {
117119
public:
118120
/// Appends a run; runs must be ascending and non-overlapping. Adjacent runs
@@ -138,12 +140,13 @@ class CharacterRuns final {
138140

139141
/// Reads the PlcBteChpx at `fc` in the table stream and every referenced
140142
/// ChpxFkp page ([MS-DOC] 2.9.33) from the WordDocument stream, resolving
141-
/// each run's Chpx on top of the default style (index 0) into
142-
/// `style_registry` (fonts via its font names). Equal Chpx bytes share one
143-
/// style.
143+
/// each run's Chpx on top of `styles[0]` (the default style) and appending
144+
/// each distinct resolved style to `styles` (fonts via `font_names`). Equal
145+
/// Chpx bytes share one style.
144146
CharacterRuns read_character_runs(std::istream &document_stream,
145147
std::istream &table_stream,
146148
FcLcb plcf_bte_chpx,
147-
StyleRegistry &style_registry);
149+
std::vector<TextStyle> &styles,
150+
std::span<const std::string> font_names);
148151

149152
} // namespace odr::internal::oldms::text

src/odr/internal/oldms/text/doc_parser.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class TextCleaner {
9696
// Open fields; entry is true while still in that field's instruction part.
9797
// m_instruction_depth counts those; text is hidden whenever it is > 0.
9898
std::vector<bool> m_field_in_instruction;
99-
int m_instruction_depth{0};
99+
std::int32_t m_instruction_depth{0};
100100
};
101101

102102
/// A maximal piece of body text with uniform character formatting.
@@ -143,7 +143,7 @@ std::vector<StyledRun> decode_styled_runs(
143143
if (!runs.empty() && runs.back().style_index == style_index) {
144144
runs.back().text += chunk_text;
145145
} else {
146-
runs.push_back({std::move(chunk_text), style_index});
146+
runs.emplace_back(std::move(chunk_text), style_index);
147147
}
148148
cp += chunk_cp;
149149
}
@@ -169,18 +169,18 @@ ElementIdentifier text::parse_tree(ElementRegistry &registry,
169169
fib.base.fWhichTblStm == 1 ? "/1Table" : "/0Table";
170170
const auto table_stream = files.open(AbsPath(tableStreamPath))->stream();
171171

172-
// Font table; TextStyle::font_name points into the registry's copy.
173-
style_registry.set_font_names(
174-
read_font_names(*table_stream, fib.fibRgFcLcb->sttbfFfn));
172+
// Font table; TextStyle::font_name points into the strings, which keep
173+
// their buffers when the vector is moved into the registry below.
174+
std::vector<std::string> font_names =
175+
read_font_names(*table_stream, fib.fibRgFcLcb->sttbfFfn);
175176

176177
// Direct character formatting ([MS-DOC] 2.4.6.2); Pcd.Prm modifications are
177-
// not modelled. Without sprmCHps the size defaults to 20 half-points.
178-
TextStyle default_style;
179-
default_style.font_size = Measure(10.0, DynamicUnit("pt"));
180-
style_registry.add_style(std::move(default_style)); // index 0
178+
// not modelled.
179+
std::vector<TextStyle> styles{default_character_style()}; // index 0
181180
const CharacterRuns character_runs =
182181
read_character_runs(*document_stream, *table_stream,
183-
fib.fibRgFcLcb->plcfBteChpx, style_registry);
182+
fib.fibRgFcLcb->plcfBteChpx, styles, font_names);
183+
style_registry = StyleRegistry(std::move(font_names), std::move(styles));
184184

185185
table_stream->seekg(fib.fibRgFcLcb->clx.fc);
186186
const CharacterIndex character_index = read_character_index(*table_stream);

src/odr/internal/oldms/text/doc_structs.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ struct Sprm {
308308
std::uint16_t sgc : 3;
309309
std::uint16_t spra : 3;
310310

311-
[[nodiscard]] int operand_size() const {
311+
[[nodiscard]] std::int32_t operand_size() const {
312312
switch (spra) {
313313
case 0:
314314
case 1:

src/odr/internal/oldms/text/doc_style.cpp

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
#include <odr/internal/oldms/text/doc_io.hpp>
44
#include <odr/internal/util/byte_stream_util.hpp>
5+
#include <odr/internal/util/byte_util.hpp>
56
#include <odr/internal/util/string_util.hpp>
67

78
#include <array>
89
#include <bit>
10+
#include <cstdint>
911
#include <cstring>
1012
#include <istream>
1113
#include <stdexcept>
@@ -46,31 +48,22 @@ bool toggle_on(const std::uint8_t value) {
4648
}
4749
}
4850

49-
std::uint16_t read_u16(const std::string_view bytes, const std::size_t at) {
50-
std::uint16_t value;
51-
std::memcpy(&value, bytes.data() + at, sizeof(value));
52-
return value;
53-
}
54-
5551
} // namespace
5652

57-
void StyleRegistry::set_font_names(std::vector<std::string> names) {
58-
m_font_names = std::move(names);
59-
}
60-
61-
std::span<const std::string> StyleRegistry::font_names() const {
62-
return m_font_names;
63-
}
64-
65-
std::uint32_t StyleRegistry::add_style(TextStyle style) {
66-
m_styles.push_back(std::move(style));
67-
return static_cast<std::uint32_t>(m_styles.size() - 1);
68-
}
53+
StyleRegistry::StyleRegistry(std::vector<std::string> font_names,
54+
std::vector<TextStyle> styles)
55+
: m_font_names(std::move(font_names)), m_styles(std::move(styles)) {}
6956

7057
const TextStyle &StyleRegistry::text_style(const std::uint32_t index) const {
7158
return m_styles.at(index);
7259
}
7360

61+
TextStyle default_character_style() {
62+
TextStyle style;
63+
style.font_size = Measure(10.0, DynamicUnit("pt"));
64+
return style;
65+
}
66+
7467
} // namespace odr::internal::oldms::text
7568

7669
namespace odr::internal::oldms {
@@ -86,7 +79,7 @@ text::apply_character_sprms(TextStyle style, const std::string_view grpprl,
8679
at += sizeof(sprm);
8780

8881
std::size_t operand_size;
89-
if (const int fixed_size = sprm.operand_size(); fixed_size >= 0) {
82+
if (const std::int32_t fixed_size = sprm.operand_size(); fixed_size >= 0) {
9083
operand_size = static_cast<std::size_t>(fixed_size);
9184
} else {
9285
// spra == 6: the first operand byte is the size of the remainder. The
@@ -132,7 +125,8 @@ text::apply_character_sprms(TextStyle style, const std::string_view grpprl,
132125
style.font_color = ico_color(static_cast<std::uint8_t>(operand[0]));
133126
break;
134127
case sprmCHps: {
135-
const std::uint16_t half_points = read_u16(operand, 0);
128+
const auto half_points =
129+
util::byte::from_little_endian<std::uint16_t>(operand);
136130
if (half_points < 2 || half_points > 3276) {
137131
throw std::runtime_error("doc: sprmCHps value out of range");
138132
}
@@ -141,7 +135,8 @@ text::apply_character_sprms(TextStyle style, const std::string_view grpprl,
141135
case sprmCRgFtc0: {
142136
// SttbfFfn index ([MS-DOC] 2.6.1); with 0 entries the value MUST be 0
143137
// and the (unmodelled) style-sheet default font applies — leave unset.
144-
const auto ftc = static_cast<std::int16_t>(read_u16(operand, 0));
138+
const auto ftc = static_cast<std::int16_t>(
139+
util::byte::from_little_endian<std::uint16_t>(operand));
145140
if (ftc < 0 || (font_names.empty() ? ftc != 0
146141
: static_cast<std::size_t>(ftc) >=
147142
font_names.size())) {

src/odr/internal/oldms/text/doc_style.hpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,31 @@ namespace odr::internal::oldms::text {
1515

1616
/// Owns the document's resolved character styles — indexed by the style index
1717
/// stored on paragraph/span elements, 0 being the default style — and the
18-
/// font names `TextStyle::font_name` points into.
18+
/// font names `TextStyle::font_name` points into. Immutable after
19+
/// construction.
1920
class StyleRegistry final {
2021
public:
21-
/// Stores the font table (SttbfFfn names); must be set before any style
22-
/// referencing a font is added.
23-
void set_font_names(std::vector<std::string> names);
24-
[[nodiscard]] std::span<const std::string> font_names() const;
22+
StyleRegistry() = default;
23+
/// `font_names` are the SttbfFfn names the styles' `font_name` point into;
24+
/// `styles` are the resolved character styles, index 0 the default style.
25+
StyleRegistry(std::vector<std::string> font_names,
26+
std::vector<TextStyle> styles);
2527

26-
std::uint32_t add_style(TextStyle style);
2728
/// Throws if the index has no style.
2829
[[nodiscard]] const TextStyle &text_style(std::uint32_t index) const;
2930

3031
private:
3132
/// Owns the font names: `TextStyle::font_name` (`const char *`) points into
32-
/// them. Never modified after `set_font_names` (moving the registry is fine
33-
/// the strings themselves do not move).
33+
/// them. Never modified after construction (moving the registry is fine
34+
/// the strings themselves do not move).
3435
std::vector<std::string> m_font_names;
3536
std::vector<TextStyle> m_styles;
3637
};
3738

39+
/// The base character style Chpx grpprls apply on top of; without sprmCHps
40+
/// the font size defaults to 20 half-points ([MS-DOC] 2.6.1 sprmCHps).
41+
[[nodiscard]] TextStyle default_character_style();
42+
3843
/// Applies the character SPRMs of a Chpx grpprl ([MS-DOC] 2.6.1) on top of
3944
/// `style`; non-character SPRMs are skipped via their operand size.
4045
/// `font_names` resolves sprmCRgFtc0 (the strings must outlive the style —

0 commit comments

Comments
 (0)