mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 11:59:36 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54779c7cd0 | ||
|
|
de0df3e034 |
@@ -1,6 +1,8 @@
|
|||||||
//! Fonts a presentation uses that this machine doesn't have, so opening it can say so (its text
|
//! Fonts a presentation uses that this machine doesn't have, so opening it can say so (its text
|
||||||
//! draws in a stand-in; see `FontDb::document_face`).
|
//! draws in a stand-in; see `FontDb::document_face`).
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use deckcraft_fonts::FontDb;
|
use deckcraft_fonts::FontDb;
|
||||||
use deckcraft_model::resolve::{self, Ctx};
|
use deckcraft_model::resolve::{self, Ctx};
|
||||||
use deckcraft_model::text::{Bullet, TextBody};
|
use deckcraft_model::text::{Bullet, TextBody};
|
||||||
@@ -12,8 +14,13 @@ use deckcraft_model::{Presentation, Shape, ShapeKind};
|
|||||||
/// font to load and are left for the round trip.
|
/// font to load and are left for the round trip.
|
||||||
pub fn install_embedded_fonts(p: &Presentation) {
|
pub fn install_embedded_fonts(p: &Presentation) {
|
||||||
let db = FontDb::global();
|
let db = FontDb::global();
|
||||||
|
// Faces can name one media item many times; each is copied and enumerated once.
|
||||||
|
let mut seen = HashSet::new();
|
||||||
for font in &p.embedded_fonts {
|
for font in &p.embedded_fonts {
|
||||||
for id in font.faces().into_iter().flatten() {
|
for id in font.faces().into_iter().flatten() {
|
||||||
|
if !seen.insert(id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let Some(item) = p.media(id) else { continue };
|
let Some(item) = p.media(id) else { continue };
|
||||||
let Some(sfnt) = deckcraft_fonts::embedded_sfnt(&item.data) else { continue };
|
let Some(sfnt) = deckcraft_fonts::embedded_sfnt(&item.data) else { continue };
|
||||||
db.add_font(sfnt.to_vec());
|
db.add_font(sfnt.to_vec());
|
||||||
@@ -27,12 +34,8 @@ pub fn install_embedded_fonts(p: &Presentation) {
|
|||||||
/// and can't be installed, so they aren't reported.
|
/// and can't be installed, so they aren't reported.
|
||||||
pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
||||||
let db = FontDb::global();
|
let db = FontDb::global();
|
||||||
let mut out: Vec<String> = Vec::new();
|
// used_fonts is already free of case-insensitive duplicates.
|
||||||
for family in used_fonts(p) {
|
let mut out: Vec<String> = used_fonts(p).into_iter().filter(|f| !f.starts_with('.') && !db.has_family(f)).collect();
|
||||||
if !family.starts_with('.') && !db.has_family(&family) && !out.iter().any(|f| f.eq_ignore_ascii_case(&family)) {
|
|
||||||
out.push(family);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.sort_by_key(|f| f.to_lowercase());
|
out.sort_by_key(|f| f.to_lowercase());
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -40,9 +43,10 @@ pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
|||||||
/// The families the slides' text runs and bullets are drawn in.
|
/// The families the slides' text runs and bullets are drawn in.
|
||||||
pub fn used_fonts(p: &Presentation) -> Vec<String> {
|
pub fn used_fonts(p: &Presentation) -> Vec<String> {
|
||||||
let mut out: Vec<String> = Vec::new();
|
let mut out: Vec<String> = Vec::new();
|
||||||
|
let mut seen: HashSet<String> = HashSet::new();
|
||||||
let mut add = |f: String| {
|
let mut add = |f: String| {
|
||||||
let f = f.trim().to_string();
|
let f = f.trim().to_string();
|
||||||
if !f.is_empty() && !out.iter().any(|o| o.eq_ignore_ascii_case(&f)) {
|
if !f.is_empty() && seen.insert(f.to_ascii_lowercase()) {
|
||||||
out.push(f);
|
out.push(f);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6483,6 +6483,27 @@ fn master_body_placeholder_format_keeps_per_level_formatting() {
|
|||||||
assert_eq!(shown(&s), vec![Some(28.0), Some(24.0)]);
|
assert_eq!(shown(&s), vec![Some(28.0), Some(24.0)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn many_distinct_fonts_are_listed_once_each_without_a_quadratic_scan() {
|
||||||
|
// 30,000 runs in 15,000 families, each written twice in different case.
|
||||||
|
let mut p = deckcraft_model::Presentation::default();
|
||||||
|
let slide = std::sync::Arc::make_mut(&mut p.slides[0]);
|
||||||
|
let mut body = deckcraft_model::text::TextBody::from_text("x");
|
||||||
|
let run = body.paragraphs[0].runs[0].clone();
|
||||||
|
body.paragraphs[0].runs = (0..30_000)
|
||||||
|
.map(|i| {
|
||||||
|
let mut r = run.clone();
|
||||||
|
let name = format!("Qqq Font {}", i / 2);
|
||||||
|
r.props.font = Some(if i % 2 == 0 { name } else { name.to_uppercase() });
|
||||||
|
r
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
slide.shapes[0].text = Some(body);
|
||||||
|
let used = crate::fonts::used_fonts(&p);
|
||||||
|
assert_eq!(used.iter().filter(|f| f.starts_with("Qqq Font ")).count(), 15_000);
|
||||||
|
assert_eq!(crate::fonts::missing_fonts(&p).iter().filter(|f| f.to_ascii_lowercase().starts_with("qqq font ")).count(), 15_000);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn slide_master_view_rejects_invalid_indices() {
|
fn slide_master_view_rejects_invalid_indices() {
|
||||||
let mut s = Session::with_new();
|
let mut s = Session::with_new();
|
||||||
|
|||||||
Reference in New Issue
Block a user