hwpforge_core/style_lookup.rs
1//! Format-agnostic style querying trait.
2//!
3//! [`StyleLookup`] provides a uniform interface for retrieving character,
4//! paragraph, and style properties by index. Each format-specific style
5//! store (e.g. `HwpxStyleStore`) implements this trait so that downstream
6//! consumers (e.g. the Markdown encoder) can query styles without knowing
7//! the underlying format.
8//!
9//! All methods have default implementations returning `None`, so
10//! implementors only need to override the methods they can support.
11
12use hwpforge_foundation::{
13 Alignment, CharShapeIndex, Color, HwpUnit, ParaShapeIndex, StyleIndex, UnderlineType,
14};
15
16/// Trait for querying resolved style properties by index.
17///
18/// This is the bridge between format-specific style stores and
19/// format-independent consumers (like the Markdown encoder). Each method
20/// takes a branded index and returns `Option<T>`, where `None` means the
21/// property is unavailable or unsupported.
22///
23/// # Default Implementations
24///
25/// Every method defaults to `None`, so an empty implementation is valid:
26///
27/// ```
28/// use hwpforge_core::StyleLookup;
29/// use hwpforge_foundation::CharShapeIndex;
30///
31/// struct NoopStore;
32/// impl StyleLookup for NoopStore {}
33///
34/// let store = NoopStore;
35/// assert!(store.char_bold(CharShapeIndex::new(0)).is_none());
36/// ```
37pub trait StyleLookup {
38 /// Returns whether the character shape at `id` is bold.
39 fn char_bold(&self, _id: CharShapeIndex) -> Option<bool> {
40 None
41 }
42
43 /// Returns whether the character shape at `id` is italic.
44 fn char_italic(&self, _id: CharShapeIndex) -> Option<bool> {
45 None
46 }
47
48 /// Returns the underline type of the character shape at `id`.
49 fn char_underline(&self, _id: CharShapeIndex) -> Option<UnderlineType> {
50 None
51 }
52
53 /// Returns whether the character shape at `id` has strikeout.
54 fn char_strikeout(&self, _id: CharShapeIndex) -> Option<bool> {
55 None
56 }
57
58 /// Returns whether the character shape at `id` is superscript.
59 fn char_superscript(&self, _id: CharShapeIndex) -> Option<bool> {
60 None
61 }
62
63 /// Returns whether the character shape at `id` is subscript.
64 fn char_subscript(&self, _id: CharShapeIndex) -> Option<bool> {
65 None
66 }
67
68 /// Returns the font name of the character shape at `id`.
69 fn char_font_name(&self, _id: CharShapeIndex) -> Option<&str> {
70 None
71 }
72
73 /// Returns the font size (in [`HwpUnit`]) of the character shape at `id`.
74 fn char_font_size(&self, _id: CharShapeIndex) -> Option<HwpUnit> {
75 None
76 }
77
78 /// Returns the text color of the character shape at `id`.
79 fn char_text_color(&self, _id: CharShapeIndex) -> Option<Color> {
80 None
81 }
82
83 /// Returns the horizontal alignment of the paragraph shape at `id`.
84 fn para_alignment(&self, _id: ParaShapeIndex) -> Option<Alignment> {
85 None
86 }
87
88 /// Returns the list type for a paragraph shape: `"BULLET"`, `"NUMBER"`, or `None`.
89 ///
90 /// Returns `None` if the paragraph has no list heading or if the heading
91 /// type is `NONE` / `OUTLINE`.
92 fn para_list_type(&self, _id: ParaShapeIndex) -> Option<&str> {
93 None
94 }
95
96 /// Returns the Korean name of the style at `id`.
97 fn style_name(&self, _id: StyleIndex) -> Option<&str> {
98 None
99 }
100
101 /// Returns the heading level (1–6) of the style at `id`, if it is
102 /// a heading style. Returns `None` for non-heading styles.
103 fn style_heading_level(&self, _id: StyleIndex) -> Option<u8> {
104 None
105 }
106
107 /// Resolves a `binaryItemIDRef` (e.g. `"BinData/image1"`) to the actual
108 /// filename with extension (e.g. `"image1.png"`).
109 ///
110 /// Returns `None` if no matching image is found.
111 fn image_resolve_filename(&self, _key: &str) -> Option<&str> {
112 None
113 }
114
115 /// Returns the raw binary data for the image identified by `key`.
116 ///
117 /// `key` is typically a path like `"image1.jpg"`. Returns `None` if
118 /// the image is not available or if the implementor does not store
119 /// image data.
120 fn image_data(&self, _key: &str) -> Option<&[u8]> {
121 None
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use hwpforge_foundation::{ParaShapeIndex, StyleIndex};
129
130 struct NoopStore;
131 impl StyleLookup for NoopStore {}
132
133 #[test]
134 fn noop_store_returns_none_for_all_methods() {
135 let store = NoopStore;
136 let cs = CharShapeIndex::new(0);
137 let ps = ParaShapeIndex::new(0);
138 let si = StyleIndex::new(0);
139
140 assert!(store.char_bold(cs).is_none());
141 assert!(store.char_italic(cs).is_none());
142 assert!(store.char_underline(cs).is_none());
143 assert!(store.char_strikeout(cs).is_none());
144 assert!(store.char_superscript(cs).is_none());
145 assert!(store.char_subscript(cs).is_none());
146 assert!(store.char_font_name(cs).is_none());
147 assert!(store.char_font_size(cs).is_none());
148 assert!(store.char_text_color(cs).is_none());
149 assert!(store.para_alignment(ps).is_none());
150 assert!(store.para_list_type(ps).is_none());
151 assert!(store.style_name(si).is_none());
152 assert!(store.style_heading_level(si).is_none());
153 assert!(store.image_data("image1.jpg").is_none());
154 }
155
156 #[test]
157 fn partial_impl_returns_some_for_overridden_methods() {
158 struct BoldOnly;
159 impl StyleLookup for BoldOnly {
160 fn char_bold(&self, _id: CharShapeIndex) -> Option<bool> {
161 Some(true)
162 }
163 }
164
165 let store = BoldOnly;
166 assert_eq!(store.char_bold(CharShapeIndex::new(0)), Some(true));
167 // Non-overridden methods still return None
168 assert!(store.char_italic(CharShapeIndex::new(0)).is_none());
169 }
170
171 #[test]
172 fn trait_object_works() {
173 let store: &dyn StyleLookup = &NoopStore;
174 assert!(store.char_bold(CharShapeIndex::new(0)).is_none());
175 }
176}