Mayo commited on
Commit
1889d5b
·
unverified ·
1 Parent(s): 185e1f0

fix: bold/italic cause postcard unable reopen

Browse files
koharu-app/src/session.rs CHANGED
@@ -229,7 +229,9 @@ struct ProjectTomlFile {
229
  mod tests {
230
  use super::*;
231
  use camino::Utf8PathBuf;
232
- use koharu_core::{Op, Page, PageId};
 
 
233
  use tempfile::tempdir;
234
 
235
  fn tmp_dir() -> (tempfile::TempDir, Utf8PathBuf) {
@@ -257,6 +259,70 @@ mod tests {
257
  assert!(session.scene.read().pages.contains_key(&page_id));
258
  }
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  #[test]
261
  fn exclusive_lock_prevents_second_open() {
262
  let (_tmp, path) = tmp_dir();
 
229
  mod tests {
230
  use super::*;
231
  use camino::Utf8PathBuf;
232
+ use koharu_core::{
233
+ Node, NodeId, NodeKind, Op, Page, PageId, TextData, TextShaderEffect, TextStyle, Transform,
234
+ };
235
  use tempfile::tempdir;
236
 
237
  fn tmp_dir() -> (tempfile::TempDir, Utf8PathBuf) {
 
259
  assert!(session.scene.read().pages.contains_key(&page_id));
260
  }
261
 
262
+ #[test]
263
+ fn reopen_preserves_text_style_effects_in_scene_bin() {
264
+ let (_tmp, path) = tmp_dir();
265
+ let page_id: PageId;
266
+ let node_id: NodeId;
267
+ {
268
+ let session = ProjectSession::create(&path, "styled").unwrap();
269
+ let page = Page::new("p1", 800, 600);
270
+ page_id = page.id;
271
+ session
272
+ .apply(Op::AddPage { page, at: 0 })
273
+ .expect("apply AddPage");
274
+
275
+ node_id = NodeId::new();
276
+ let mut scene = session.scene.write();
277
+ let page = scene.pages.get_mut(&page_id).expect("page");
278
+ page.nodes.insert(
279
+ node_id,
280
+ Node {
281
+ id: node_id,
282
+ transform: Transform {
283
+ x: 0.0,
284
+ y: 0.0,
285
+ width: 100.0,
286
+ height: 40.0,
287
+ rotation_deg: 0.0,
288
+ },
289
+ visible: true,
290
+ kind: NodeKind::Text(TextData {
291
+ style: Some(TextStyle {
292
+ font_families: vec!["Arial".to_string()],
293
+ font_size: Some(20.0),
294
+ color: [0, 0, 0, 255],
295
+ effect: Some(TextShaderEffect {
296
+ italic: true,
297
+ bold: true,
298
+ }),
299
+ stroke: None,
300
+ text_align: None,
301
+ }),
302
+ ..Default::default()
303
+ }),
304
+ },
305
+ );
306
+ drop(scene);
307
+ session.compact().unwrap();
308
+ }
309
+
310
+ let session = ProjectSession::open(&path).unwrap();
311
+ let scene = session.scene.read();
312
+ let page = scene.pages.get(&page_id).expect("page");
313
+ let node = page.nodes.get(&node_id).expect("node");
314
+ let NodeKind::Text(text) = &node.kind else {
315
+ panic!("expected text node");
316
+ };
317
+ let effect = text
318
+ .style
319
+ .as_ref()
320
+ .and_then(|style| style.effect)
321
+ .expect("effect");
322
+ assert!(effect.italic);
323
+ assert!(effect.bold);
324
+ }
325
+
326
  #[test]
327
  fn exclusive_lock_prevents_second_open() {
328
  let (_tmp, path) = tmp_dir();
koharu-core/src/style.rs CHANGED
@@ -151,6 +151,13 @@ impl<'de> Deserialize<'de> for TextShaderEffect {
151
  bold: Option<bool>,
152
  }
153
 
 
 
 
 
 
 
 
154
  #[derive(Deserialize)]
155
  #[serde(untagged)]
156
  enum Repr {
@@ -158,13 +165,18 @@ impl<'de> Deserialize<'de> for TextShaderEffect {
158
  Legacy(String),
159
  }
160
 
161
- match Repr::deserialize(deserializer)? {
162
- Repr::Flags(FlagsRepr { italic, bold }) => Ok(Self {
163
- italic: italic.unwrap_or(false),
164
- bold: bold.unwrap_or(false),
165
- }),
166
- Repr::Legacy(value) => value.parse().map_err(serde::de::Error::custom),
 
 
167
  }
 
 
 
168
  }
169
  }
170
 
@@ -219,7 +231,7 @@ pub struct TextStyle {
219
 
220
  #[cfg(test)]
221
  mod tests {
222
- use super::TextShaderEffect;
223
 
224
  #[test]
225
  fn parse_combined_effects() {
@@ -240,4 +252,43 @@ mod tests {
240
  let effect: TextShaderEffect = "none".parse().expect("parse");
241
  assert_eq!(effect.to_string(), "none");
242
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
 
151
  bold: Option<bool>,
152
  }
153
 
154
+ #[derive(Deserialize)]
155
+ #[serde(deny_unknown_fields)]
156
+ struct BinaryFlagsRepr {
157
+ italic: bool,
158
+ bold: bool,
159
+ }
160
+
161
  #[derive(Deserialize)]
162
  #[serde(untagged)]
163
  enum Repr {
 
165
  Legacy(String),
166
  }
167
 
168
+ if deserializer.is_human_readable() {
169
+ return match Repr::deserialize(deserializer)? {
170
+ Repr::Flags(FlagsRepr { italic, bold }) => Ok(Self {
171
+ italic: italic.unwrap_or(false),
172
+ bold: bold.unwrap_or(false),
173
+ }),
174
+ Repr::Legacy(value) => value.parse().map_err(serde::de::Error::custom),
175
+ };
176
  }
177
+
178
+ let BinaryFlagsRepr { italic, bold } = BinaryFlagsRepr::deserialize(deserializer)?;
179
+ Ok(Self { italic, bold })
180
  }
181
  }
182
 
 
231
 
232
  #[cfg(test)]
233
  mod tests {
234
+ use super::{TextShaderEffect, TextStyle};
235
 
236
  #[test]
237
  fn parse_combined_effects() {
 
252
  let effect: TextShaderEffect = "none".parse().expect("parse");
253
  assert_eq!(effect.to_string(), "none");
254
  }
255
+
256
+ #[test]
257
+ fn json_legacy_string_deserializes() {
258
+ let effect: TextShaderEffect = serde_json::from_str("\"italic,bold\"").expect("json");
259
+ assert!(effect.italic);
260
+ assert!(effect.bold);
261
+ }
262
+
263
+ #[test]
264
+ fn postcard_text_shader_effect_round_trips() {
265
+ let effect = TextShaderEffect {
266
+ italic: true,
267
+ bold: true,
268
+ };
269
+ let bytes = postcard::to_allocvec(&effect).expect("serialize");
270
+ let decoded: TextShaderEffect = postcard::from_bytes(&bytes).expect("deserialize");
271
+ assert!(decoded.italic);
272
+ assert!(decoded.bold);
273
+ }
274
+
275
+ #[test]
276
+ fn postcard_text_style_with_effect_round_trips() {
277
+ let style = TextStyle {
278
+ font_families: vec!["Arial".to_string()],
279
+ font_size: Some(18.0),
280
+ color: [12, 34, 56, 255],
281
+ effect: Some(TextShaderEffect {
282
+ italic: true,
283
+ bold: false,
284
+ }),
285
+ stroke: None,
286
+ text_align: None,
287
+ };
288
+ let bytes = postcard::to_allocvec(&style).expect("serialize");
289
+ let decoded: TextStyle = postcard::from_bytes(&bytes).expect("deserialize");
290
+ let effect = decoded.effect.expect("effect");
291
+ assert!(effect.italic);
292
+ assert!(!effect.bold);
293
+ }
294
  }