refactor(serialization): 重构颜色模块的序列化实现

将手动实现的序列化逻辑替换为派生宏实现
添加foreign_serializer模块处理特殊序列化需求
优化代码结构并减少重复代码
This commit is contained in:
徐涛 2025-07-20 07:25:13 +08:00
parent f82575c49b
commit edc2a0546e
6 changed files with 64 additions and 91 deletions

View File

@ -1,10 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
use palette::{ use palette::{
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
cam16::{Cam16Jch, Parameters}, cam16::{Cam16Jch, Parameters},
color_difference::Wcag21RelativeContrast, color_difference::Wcag21RelativeContrast,
convert::FromColorUnclamped, convert::FromColorUnclamped,
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
}; };
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -14,6 +14,7 @@ mod color_differ;
mod color_shifting; mod color_shifting;
mod convert; mod convert;
mod errors; mod errors;
mod foreign_serializer;
mod palettes; mod palettes;
mod reversing; mod reversing;
mod schemes; mod schemes;
@ -139,10 +140,6 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result<f32, err
#[macro_export] #[macro_export]
macro_rules! cond { macro_rules! cond {
($s: expr, $a: expr, $b: expr) => { ($s: expr, $a: expr, $b: expr) => {
if $s { if $s { $a } else { $b }
$a
} else {
$b
}
}; };
} }

View File

@ -1,11 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use internment::Intern;
use material_design_2::MaterialDesign2Scheme; use material_design_2::MaterialDesign2Scheme;
use material_design_3::MaterialDesign3Scheme; use material_design_3::MaterialDesign3Scheme;
use material_design_3_dynamic::{build_baseline, build_dynamic_scheme, build_swatches, Variant}; use material_design_3_dynamic::{Variant, build_baseline, build_dynamic_scheme, build_swatches};
use q_style::{QScheme, SchemeSetting}; use q_style::{QScheme, SchemeSetting};
use swatch_style::{SwatchEntry, SwatchSchemeSetting}; use swatch_style::{SwatchEntry, SwatchSchemeSetting};
use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
use crate::{errors, schemes::q_style_2::QScheme2}; use crate::{errors, schemes::q_style_2::QScheme2};
@ -23,6 +24,10 @@ pub trait SchemeExport {
fn output_javascript_object(&self) -> String; fn output_javascript_object(&self) -> String;
} }
pub fn get_static_str(s: &str) -> &'static str {
Intern::new(s.to_string()).as_ref()
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn generate_material_design_3_scheme( pub fn generate_material_design_3_scheme(
source_color: &str, source_color: &str,

View File

@ -5,7 +5,7 @@ use palette::{
Oklch, ShiftHue, Oklch, ShiftHue,
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic}, color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
}; };
use serde::{Serialize, ser::SerializeStruct}; use serde::Serialize;
use crate::{ use crate::{
convert::map_oklch_to_srgb_hex, convert::map_oklch_to_srgb_hex,
@ -16,7 +16,8 @@ use crate::{
}, },
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ColorUnit { pub struct ColorUnit {
pub root: ColorSet, pub root: ColorSet,
pub surface: ColorSet, pub surface: ColorSet,
@ -82,20 +83,8 @@ impl ColorUnit {
} }
} }
impl Serialize for ColorUnit { #[derive(Debug, Clone, Serialize)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> #[serde(rename_all = "camelCase")]
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("ColorUnit", 3)?;
state.serialize_field("root", &self.root)?;
state.serialize_field("surface", &self.surface)?;
state.serialize_field("swatch", &self.swatch)?;
state.end()
}
}
#[derive(Debug, Clone)]
pub struct Baseline { pub struct Baseline {
pub primary: ColorUnit, pub primary: ColorUnit,
pub secondary: Option<ColorUnit>, pub secondary: Option<ColorUnit>,
@ -105,18 +94,26 @@ pub struct Baseline {
pub neutral_variant: ColorSet, pub neutral_variant: ColorSet,
pub surface: ColorSet, pub surface: ColorSet,
pub surface_variant: ColorSet, pub surface_variant: ColorSet,
#[serde(serialize_with = "crate::schemes::q_style_2::swatch::serialize_neutral_swatch")]
pub neutral_swatch: Arc<NeutralSwatch>, pub neutral_swatch: Arc<NeutralSwatch>,
pub danger: ColorUnit, pub danger: ColorUnit,
pub success: ColorUnit, pub success: ColorUnit,
pub warn: ColorUnit, pub warn: ColorUnit,
pub info: ColorUnit, pub info: ColorUnit,
pub custom_colors: HashMap<String, ColorUnit>, pub custom_colors: HashMap<String, ColorUnit>,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub shadow: Oklch, pub shadow: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub overlay: Oklch, pub overlay: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub outline: Oklch, pub outline: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub outline_variant: Oklch, pub outline_variant: Oklch,
#[serde(skip)]
pub neutral_lightness: f32, pub neutral_lightness: f32,
#[serde(skip)]
pub scheme_settings: Arc<SchemeSetting>, pub scheme_settings: Arc<SchemeSetting>,
#[serde(skip)]
pub is_dark: bool, pub is_dark: bool,
} }
@ -514,35 +511,3 @@ impl Baseline {
javascript_fields javascript_fields
} }
} }
impl Serialize for Baseline {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("Baseline", 17)?;
state.serialize_field("primary", &self.primary)?;
state.serialize_field("secondary", &self.secondary)?;
state.serialize_field("tertiary", &self.tertiary)?;
state.serialize_field("accent", &self.accent)?;
state.serialize_field("neutral", &self.neutral)?;
state.serialize_field("neutralVariant", &self.neutral_variant)?;
state.serialize_field("surface", &self.surface)?;
state.serialize_field("surfaceVariant", &self.surface_variant)?;
state.serialize_field("danger", &self.danger)?;
state.serialize_field("success", &self.success)?;
state.serialize_field("warn", &self.warn)?;
state.serialize_field("info", &self.info)?;
state.serialize_field("shadow", &map_oklch_to_srgb_hex(&self.shadow))?;
state.serialize_field("overlay", &map_oklch_to_srgb_hex(&self.overlay))?;
state.serialize_field("outline", &map_oklch_to_srgb_hex(&self.outline))?;
state.serialize_field(
"outlineVariant",
&map_oklch_to_srgb_hex(&self.outline_variant),
)?;
state.serialize_field("custom", &self.custom_colors)?;
state.end()
}
}

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use linked_hash_map::LinkedHashMap; use linked_hash_map::LinkedHashMap;
use palette::{Oklch, color_difference::Wcag21RelativeContrast, luma::Luma}; use palette::{Oklch, color_difference::Wcag21RelativeContrast, luma::Luma};
use serde::{Serialize, ser::SerializeStruct}; use serde::Serialize;
use crate::{ use crate::{
convert::{map_oklch_to_luma, map_oklch_to_srgb_hex}, convert::{map_oklch_to_luma, map_oklch_to_srgb_hex},
@ -13,17 +13,28 @@ use crate::{
}, },
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ColorSet { pub struct ColorSet {
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub root: Oklch, pub root: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub active: Oklch, pub active: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub focus: Oklch, pub focus: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub hover: Oklch, pub hover: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub disabled: Oklch, pub disabled: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub on_root: Oklch, pub on_root: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub on_disabled: Oklch, pub on_disabled: Oklch,
#[serde(skip)]
pub neutral_swatch: Arc<NeutralSwatch>, pub neutral_swatch: Arc<NeutralSwatch>,
#[serde(skip)]
pub neutral_lightness: f32, pub neutral_lightness: f32,
#[serde(skip)]
pub scheme_settings: Arc<SchemeSetting>, pub scheme_settings: Arc<SchemeSetting>,
} }
@ -266,28 +277,3 @@ impl ColorSet {
variables variables
} }
} }
impl Serialize for ColorSet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let root = map_oklch_to_srgb_hex(&self.root);
let hover = map_oklch_to_srgb_hex(&self.hover);
let active = map_oklch_to_srgb_hex(&self.active);
let focus = map_oklch_to_srgb_hex(&self.focus);
let disabled = map_oklch_to_srgb_hex(&self.disabled);
let on_root = map_oklch_to_srgb_hex(&self.on_root);
let on_disabled = map_oklch_to_srgb_hex(&self.on_disabled);
let mut state = serializer.serialize_struct("ColorSet", 6)?;
state.serialize_field("root", &root)?;
state.serialize_field("hover", &hover)?;
state.serialize_field("active", &active)?;
state.serialize_field("focus", &focus)?;
state.serialize_field("disabled", &disabled)?;
state.serialize_field("onRoot", &on_root)?;
state.serialize_field("onDisabled", &on_disabled)?;
state.end()
}
}

View File

@ -6,7 +6,7 @@ use serde::Serialize;
use crate::{ use crate::{
errors, parse_option_to_oklch, parse_to_oklch, errors, parse_option_to_oklch, parse_to_oklch,
schemes::{q_style::SchemeSetting, q_style_2::baseline::Baseline, SchemeExport}, schemes::{SchemeExport, q_style::SchemeSetting, q_style_2::baseline::Baseline},
}; };
mod baseline; mod baseline;

View File

@ -1,9 +1,13 @@
use internment::Intern; use std::sync::Arc;
use linked_hash_map::LinkedHashMap; use linked_hash_map::LinkedHashMap;
use palette::Oklch; use palette::Oklch;
use serde::{Serialize, ser::SerializeStruct}; use serde::{Serialize, Serializer, ser::SerializeStruct};
use crate::convert::map_oklch_to_srgb_hex; use crate::{
convert::map_oklch_to_srgb_hex,
schemes::{get_static_str, q_style::NeutralSwatch},
};
static SWATCH_LIGHTINGS: [u8; 16] = [ static SWATCH_LIGHTINGS: [u8; 16] = [
10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 75, 80, 85, 90, 95, 98, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 75, 80, 85, 90, 95, 98,
@ -77,10 +81,6 @@ impl Swatch {
} }
} }
fn get_static_str(s: &str) -> &'static str {
Intern::new(s.to_string()).as_ref()
}
impl Serialize for Swatch { impl Serialize for Swatch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
@ -97,3 +97,23 @@ impl Serialize for Swatch {
state.end() state.end()
} }
} }
pub fn serialize_neutral_swatch<S>(
swatch: &Arc<NeutralSwatch>,
serailizer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let swatch = swatch.clone();
let mut swatch_struct = serailizer.serialize_struct("NeutralSwatch", SWATCH_LIGHTINGS.len())?;
for l in SWATCH_LIGHTINGS {
let color = swatch.get((l as f32) / 100.0);
let color = map_oklch_to_srgb_hex(&color);
let key: &'static str = get_static_str(&format!("{l:02}"));
swatch_struct.serialize_field(key, &color)?;
}
swatch_struct.end()
}