webfont_generator/memory/
updates.rs1use std::collections::{HashMap, HashSet};
2#[cfg(feature = "async")]
3use std::io::Error;
4use std::io::Result;
5use std::sync::OnceLock;
6
7use super::options::{invalid, validate_name};
8use super::{GenerateWebfontsFromSourcesResult, SvgSource, build};
9use crate::incremental::sources::{
10 MembershipChange, SourceUpdate, change_membership, ordered_sources, same_sources,
11 updated_sources,
12};
13use crate::input::{LoadedSvgSource, validate_glyph_names};
14use crate::rendering::css::{TemplateDependencies, template_dependencies};
15
16#[derive(serde::Deserialize)]
19#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
20pub enum SourceChange {
21 Added(SvgSource),
23 Changed {
25 id: String,
27 svg: String,
29 name: Option<String>,
31 },
32 Removed {
34 id: String,
36 },
37}
38
39#[derive(serde::Deserialize)]
41#[serde(rename_all = "camelCase", deny_unknown_fields)]
42pub struct VariantSourceSet {
43 pub name: String,
45 pub ids: Vec<String>,
47}
48
49#[derive(serde::Deserialize)]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53pub enum SourceMembership {
54 Sources(Vec<String>),
56 Variants(Vec<VariantSourceSet>),
58}
59
60#[cfg(feature = "async")]
62pub struct SourceRegenerateError {
63 error: Error,
64 result: Option<Box<GenerateWebfontsFromSourcesResult>>,
65}
66
67#[cfg(feature = "async")]
68impl SourceRegenerateError {
69 pub(super) fn worker(error: tokio::task::JoinError) -> Self {
70 Self {
71 error: Error::other(format!("Source update task failed: {error}")),
72 result: None,
73 }
74 }
75 pub fn error(&self) -> &Error {
77 &self.error
78 }
79 pub fn into_result(self) -> Option<GenerateWebfontsFromSourcesResult> {
81 self.result.map(|result| *result)
82 }
83 pub fn into_parts(self) -> (Error, Option<GenerateWebfontsFromSourcesResult>) {
85 (self.error, self.result.map(|result| *result))
86 }
87}
88
89#[cfg(feature = "async")]
90impl std::fmt::Debug for SourceRegenerateError {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("SourceRegenerateError")
93 .field("error", &self.error)
94 .field("recoverable", &self.result.is_some())
95 .finish()
96 }
97}
98#[cfg(feature = "async")]
99impl std::fmt::Display for SourceRegenerateError {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 self.error.fmt(f)
102 }
103}
104#[cfg(feature = "async")]
105impl std::error::Error for SourceRegenerateError {
106 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
107 Some(&self.error)
108 }
109}
110
111impl GenerateWebfontsFromSourcesResult {
112 fn membership_for_change(
113 &self,
114 id: &str,
115 change: MembershipChange<'_>,
116 ) -> Result<SourceMembership> {
117 let names = self.options.variants.as_ref().map(|family| {
118 family
119 .variants
120 .iter()
121 .map(|variant| variant.name.clone())
122 .collect::<Vec<_>>()
123 });
124 let mut sets = self
125 .options
126 .sources
127 .iter()
128 .map(|sources| sources.iter().map(|source| source.id.clone()).collect())
129 .collect::<Vec<_>>();
130 change_membership(&mut sets, names.as_deref(), id, change)?;
131 Ok(match names {
132 Some(names) => SourceMembership::Variants(
133 names
134 .into_iter()
135 .zip(sets)
136 .map(|(name, ids)| VariantSourceSet { name, ids })
137 .collect(),
138 ),
139 None => SourceMembership::Sources(sets.remove(0)),
140 })
141 }
142
143 pub fn add_sync(&mut self, source: SvgSource, variant: Option<&str>) -> Result<()> {
146 let membership = self.membership_for_change(&source.id, MembershipChange::Add(variant))?;
147 self.regenerate_sync(membership, vec![SourceChange::Added(source)])
148 }
149
150 pub fn update_sync(&mut self, id: String, svg: String, name: Option<String>) -> Result<()> {
153 let membership = self.membership_for_change(&id, MembershipChange::Update)?;
154 self.regenerate_sync(membership, vec![SourceChange::Changed { id, svg, name }])
155 }
156
157 pub fn remove_sync(&mut self, id: String) -> Result<()> {
160 let membership = self.membership_for_change(&id, MembershipChange::Remove)?;
161 self.regenerate_sync(membership, vec![SourceChange::Removed { id }])
162 }
163
164 #[cfg(feature = "async")]
166 pub async fn add(
167 self,
168 source: SvgSource,
169 variant: Option<String>,
170 ) -> std::result::Result<Self, SourceRegenerateError> {
171 let membership = match self
172 .membership_for_change(&source.id, MembershipChange::Add(variant.as_deref()))
173 {
174 Ok(membership) => membership,
175 Err(error) => {
176 return Err(SourceRegenerateError {
177 error,
178 result: Some(Box::new(self)),
179 });
180 }
181 };
182 self.regenerate(membership, vec![SourceChange::Added(source)])
183 .await
184 }
185
186 #[cfg(feature = "async")]
188 pub async fn update(
189 self,
190 id: String,
191 svg: String,
192 name: Option<String>,
193 ) -> std::result::Result<Self, SourceRegenerateError> {
194 let membership = match self.membership_for_change(&id, MembershipChange::Update) {
195 Ok(membership) => membership,
196 Err(error) => {
197 return Err(SourceRegenerateError {
198 error,
199 result: Some(Box::new(self)),
200 });
201 }
202 };
203 self.regenerate(membership, vec![SourceChange::Changed { id, svg, name }])
204 .await
205 }
206
207 #[cfg(feature = "async")]
209 pub async fn remove(self, id: String) -> std::result::Result<Self, SourceRegenerateError> {
210 let membership = match self.membership_for_change(&id, MembershipChange::Remove) {
211 Ok(membership) => membership,
212 Err(error) => {
213 return Err(SourceRegenerateError {
214 error,
215 result: Some(Box::new(self)),
216 });
217 }
218 };
219 self.regenerate(membership, vec![SourceChange::Removed { id }])
220 .await
221 }
222
223 pub fn regenerate_sync(
227 &mut self,
228 membership: SourceMembership,
229 changes: Vec<SourceChange>,
230 ) -> Result<()> {
231 let threads = self.options.common.threads;
232 crate::threads::install(threads, || self.regenerate_in_pool(membership, changes))
233 }
234
235 fn regenerate_in_pool(
236 &mut self,
237 membership: SourceMembership,
238 changes: Vec<SourceChange>,
239 ) -> Result<()> {
240 if self.caches.is_none() {
241 return Err(invalid("Source updates require incremental generation"));
242 }
243 let memberships = match (&self.options.variants, membership) {
244 (None, SourceMembership::Sources(ids)) => vec![ids],
245 (Some(variants), SourceMembership::Variants(sets)) => {
246 if sets.len() != variants.variants.len()
247 || sets
248 .iter()
249 .zip(&variants.variants)
250 .any(|(set, variant)| set.name != variant.name)
251 {
252 return Err(invalid("Supply every variant in its original order"));
253 }
254 sets.into_iter().map(|set| set.ids).collect()
255 }
256 _ => {
257 return Err(invalid(
258 "Source membership does not match the generation mode",
259 ));
260 }
261 };
262 if changes.is_empty()
263 && memberships
264 .iter()
265 .zip(&self.options.sources)
266 .all(|(ids, sources)| {
267 ids.iter()
268 .map(String::as_str)
269 .eq(sources.iter().map(|source| source.id.as_str()))
270 })
271 {
272 return Ok(());
273 }
274 let mut staged = self.options.clone();
275 if let Some(variants) = &mut staged.variants {
276 for (variant, ids) in variants.variants.iter_mut().zip(&memberships) {
277 variant.source_ids = ids.clone();
278 }
279 }
280 let previous = staged
281 .sources
282 .iter()
283 .flatten()
284 .map(|source| (source.id.clone(), source.clone()))
285 .collect::<HashMap<_, _>>();
286 let mut changed_ids = HashSet::new();
287 let mut supplied_ids = HashSet::new();
288 let mut updates = Vec::with_capacity(changes.len());
289 for change in changes {
290 let id = match &change {
291 SourceChange::Added(source) => &source.id,
292 SourceChange::Changed { id, .. } | SourceChange::Removed { id } => id,
293 };
294 if id.is_empty() || !changed_ids.insert(id.clone()) {
295 return Err(invalid("Change IDs must be nonempty and unique"));
296 }
297 match change {
298 SourceChange::Added(source) => {
299 if previous.contains_key(&source.id) {
300 return Err(invalid(format!("Source {:?} already exists", source.id)));
301 }
302 supplied_ids.insert(source.id.clone());
303 updates.push(SourceUpdate::Set(LoadedSvgSource {
304 id: source.id,
305 glyph_name: source.name,
306 contents: source.svg.into(),
307 }));
308 }
309 SourceChange::Changed { id, svg, name } => {
310 let source = previous
311 .get(&id)
312 .ok_or_else(|| invalid(format!("Unknown source {id:?}")))?;
313 supplied_ids.insert(id.clone());
314 updates.push(SourceUpdate::Set(LoadedSvgSource {
315 id,
316 contents: svg.into(),
317 glyph_name: name.unwrap_or_else(|| source.glyph_name.clone()),
318 }));
319 }
320 SourceChange::Removed { id } => {
321 if !previous.contains_key(&id) {
322 return Err(invalid(format!("Unknown source {id:?}")));
323 }
324 updates.push(SourceUpdate::Remove(id));
325 }
326 }
327 }
328 let previous = previous.into_values().collect::<Vec<_>>();
329 let definitions = updated_sources(&previous, &updates);
330 let mut used = HashSet::new();
331 staged.sources = memberships
332 .into_iter()
333 .map(|ids| {
334 if ids.is_empty() {
335 return Err(invalid("Each design must contain at least one source"));
336 }
337 let mut seen = HashSet::new();
338 for id in &ids {
339 if !seen.insert(id.clone()) {
340 return Err(invalid(format!("Duplicate source ID {id:?}")));
341 }
342 used.insert(id.clone());
343 }
344 let sources = ordered_sources(&definitions, &ids)
345 .map_err(|id| invalid(format!("Unknown membership ID {id:?}")))?;
346 for source in &sources {
347 validate_name(&source.glyph_name)?;
348 }
349 validate_glyph_names(&sources)?;
350 Ok(sources)
351 })
352 .collect::<Result<Vec<_>>>()?;
353 if supplied_ids.iter().any(|id| !used.contains(id)) {
354 return Err(invalid(
355 "Added or changed sources must appear in final membership",
356 ));
357 }
358 let same = staged
359 .sources
360 .iter()
361 .zip(&self.options.sources)
362 .all(|(new, old)| same_sources(old, new));
363 if same {
364 return Ok(());
365 }
366
367 let built = build(&mut staged, self.caches.as_mut(), &updates);
368 if let Some(persistent) = self.persistent.as_mut() {
369 persistent.caches_reset = built.is_err();
370 }
371 let fonts = built?;
372 let names_unchanged = self
373 .options
374 .sources
375 .iter()
376 .flatten()
377 .map(|source| &source.glyph_name)
378 .eq(staged
379 .sources
380 .iter()
381 .flatten()
382 .map(|source| &source.glyph_name));
383 let codepoints_unchanged = self.options.common.codepoints == staged.common.codepoints;
384 let keep_css = matches!(self.css.get(), Some(Ok(_)))
385 && self
386 .options
387 .css_template
388 .as_deref()
389 .map(template_dependencies)
390 .unwrap_or_else(TemplateDependencies::css_default)
391 .can_reuse_css_no_urls(names_unchanged, codepoints_unchanged);
392 let html_dependencies = self
393 .options
394 .html_template
395 .as_deref()
396 .map(template_dependencies)
397 .unwrap_or_else(TemplateDependencies::html_default);
398 let keep_html =
399 html_dependencies.can_reuse_html(names_unchanged, codepoints_unchanged, keep_css)
400 && matches!(self.html.get(), Some(Ok(_)));
401 self.options = staged;
402 if !names_unchanged {
403 self.ts = OnceLock::new();
404 }
405 self.fonts = fonts;
406 self.shared = OnceLock::new();
407 if !keep_css {
408 self.css = OnceLock::new();
409 }
410 if !keep_html {
411 self.html = OnceLock::new();
412 }
413 Ok(())
414 }
415
416 #[cfg(feature = "async")]
419 pub async fn regenerate(
420 mut self,
421 membership: SourceMembership,
422 changes: Vec<SourceChange>,
423 ) -> std::result::Result<Self, SourceRegenerateError> {
424 let runtime = match tokio::runtime::Handle::try_current() {
425 Ok(runtime) => runtime,
426 Err(error) => {
427 return Err(SourceRegenerateError {
428 error: Error::other(format!("Source update requires a Tokio runtime: {error}")),
429 result: Some(Box::new(self)),
430 });
431 }
432 };
433 runtime
434 .spawn_blocking(move || match self.regenerate_sync(membership, changes) {
435 Ok(()) => Ok(self),
436 Err(error) => Err(SourceRegenerateError {
437 error,
438 result: Some(Box::new(self)),
439 }),
440 })
441 .await
442 .map_err(SourceRegenerateError::worker)?
443 }
444}