Skip to main content

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}