style(src-tauri): cargo fmt
Pure mechanical rustfmt reformat (default config, no rustfmt.toml).
This commit is contained in:
+368
-90
@@ -9,7 +9,9 @@
|
||||
//! (`fvar`, `CFF2`: only the default instance would embed), files without outlines, and fonts whose OS/2 `fsType`
|
||||
//! forbids embedding. The sfnt is parsed by hand (no font crate is available offline); only the tables needed
|
||||
//! to describe the font are read.
|
||||
use super::raw::{decode_header_path, header_map, raw_body, required_header, write_atomic, Headers};
|
||||
use super::raw::{
|
||||
decode_header_path, header_map, raw_body, required_header, write_atomic, Headers,
|
||||
};
|
||||
use crate::AppState;
|
||||
use flate2::read::ZlibDecoder;
|
||||
use rusqlite::{params, Connection, OptionalExtension};
|
||||
@@ -119,7 +121,10 @@ impl<'a> Sfnt<'a> {
|
||||
let off = be32(data, rec + 8).ok_or("The font file is truncated")? as usize;
|
||||
let len = be32(data, rec + 12).ok_or("The font file is truncated")? as usize;
|
||||
if off.checked_add(len).map_or(true, |end| end > data.len()) {
|
||||
return Err(format!("Table '{}' lies outside the file", String::from_utf8_lossy(&tag)));
|
||||
return Err(format!(
|
||||
"Table '{}' lies outside the file",
|
||||
String::from_utf8_lossy(&tag)
|
||||
));
|
||||
}
|
||||
tables.push((tag, off, len));
|
||||
}
|
||||
@@ -135,7 +140,10 @@ impl<'a> Sfnt<'a> {
|
||||
}
|
||||
|
||||
fn utf16be(b: &[u8]) -> String {
|
||||
let units: Vec<u16> = b.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect();
|
||||
let units: Vec<u16> = b
|
||||
.chunks_exact(2)
|
||||
.map(|c| u16::from_be_bytes([c[0], c[1]]))
|
||||
.collect();
|
||||
String::from_utf16_lossy(&units)
|
||||
}
|
||||
|
||||
@@ -201,7 +209,11 @@ impl<'a> Cmap<'a> {
|
||||
best = Some((rank, offset, format));
|
||||
}
|
||||
}
|
||||
best.map(|(_, offset, format)| Self { table, offset, format })
|
||||
best.map(|(_, offset, format)| Self {
|
||||
table,
|
||||
offset,
|
||||
format,
|
||||
})
|
||||
}
|
||||
|
||||
fn has(&self, cp: u32) -> bool {
|
||||
@@ -283,7 +295,9 @@ fn woff_to_sfnt(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let comp = be32(data, rec + 8).ok_or_else(bad)? as usize;
|
||||
let orig = be32(data, rec + 12).ok_or_else(bad)? as usize;
|
||||
let checksum = be32(data, rec + 16).ok_or_else(bad)?;
|
||||
let stored = data.get(off..off.checked_add(comp).ok_or_else(bad)?).ok_or_else(bad)?;
|
||||
let stored = data
|
||||
.get(off..off.checked_add(comp).ok_or_else(bad)?)
|
||||
.ok_or_else(bad)?;
|
||||
total += (orig + 3) & !3;
|
||||
if orig > MAX_FONT_BYTES || total > MAX_FONT_BYTES {
|
||||
return Err("The font expands to more than the 16 MB limit".into());
|
||||
@@ -293,7 +307,10 @@ fn woff_to_sfnt(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||
} else if comp < orig {
|
||||
let mut out = Vec::with_capacity(orig);
|
||||
// One byte over the stated size detects a table that lies about it, without trusting it for allocation.
|
||||
ZlibDecoder::new(stored).take(orig as u64 + 1).read_to_end(&mut out).map_err(|_| bad())?;
|
||||
ZlibDecoder::new(stored)
|
||||
.take(orig as u64 + 1)
|
||||
.read_to_end(&mut out)
|
||||
.map_err(|_| bad())?;
|
||||
if out.len() != orig {
|
||||
return Err(bad());
|
||||
}
|
||||
@@ -301,7 +318,11 @@ fn woff_to_sfnt(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||
} else {
|
||||
return Err(bad());
|
||||
};
|
||||
entries.push(Entry { tag, checksum, bytes });
|
||||
entries.push(Entry {
|
||||
tag,
|
||||
checksum,
|
||||
bytes,
|
||||
});
|
||||
}
|
||||
entries.sort_by_key(|e| e.tag);
|
||||
|
||||
@@ -372,12 +393,18 @@ pub fn inspect_bytes(input: &[u8]) -> Result<(Vec<u8>, FontInspection), String>
|
||||
}
|
||||
let cff = font.table(b"CFF ").is_some();
|
||||
if !cff && (font.table(b"glyf").is_none() || font.table(b"loca").is_none()) {
|
||||
return Err("The font has no glyph outlines (no glyf or CFF table), so it cannot be embedded.".into());
|
||||
return Err(
|
||||
"The font has no glyph outlines (no glyf or CFF table), so it cannot be embedded."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let head = font.table(b"head").ok_or("The font has no head table")?;
|
||||
let cmap_table = font.table(b"cmap").ok_or("The font has no cmap table")?;
|
||||
let name = font.table(b"name").ok_or("The font has no name table")?;
|
||||
let num_glyphs = font.table(b"maxp").and_then(|m| be16(m, 4)).ok_or("The font has no usable maxp table")?;
|
||||
let num_glyphs = font
|
||||
.table(b"maxp")
|
||||
.and_then(|m| be16(m, 4))
|
||||
.ok_or("The font has no usable maxp table")?;
|
||||
|
||||
let os2 = font.table(b"OS/2");
|
||||
let fs_type = os2.and_then(|t| be16(t, 8)).unwrap_or(0);
|
||||
@@ -413,7 +440,11 @@ pub fn inspect_bytes(input: &[u8]) -> Result<(Vec<u8>, FontInspection), String>
|
||||
let fs_selection = os2.and_then(|t| be16(t, 62)).unwrap_or(0);
|
||||
let mac_style = be16(head, 44).unwrap_or(0);
|
||||
// fsSelection bit 0 = italic, bit 9 = oblique; head.macStyle bit 1 = italic.
|
||||
let italic = if os2.map_or(false, |t| t.len() >= 64) { fs_selection & 0x0201 != 0 } else { mac_style & 0x2 != 0 };
|
||||
let italic = if os2.map_or(false, |t| t.len() >= 64) {
|
||||
fs_selection & 0x0201 != 0
|
||||
} else {
|
||||
mac_style & 0x2 != 0
|
||||
};
|
||||
|
||||
let cmap = Cmap::new(cmap_table).ok_or("The font has no usable Unicode character map")?;
|
||||
let has_rupee = cmap.has(0x20B9);
|
||||
@@ -428,9 +459,14 @@ pub fn inspect_bytes(input: &[u8]) -> Result<(Vec<u8>, FontInspection), String>
|
||||
warnings.push("The font has no rupee sign (\u{20B9}); amounts fall back to IBM Plex Sans for that glyph.".to_string());
|
||||
}
|
||||
|
||||
let family = name_string(name, 16).or_else(|| name_string(name, 1)).unwrap_or_default();
|
||||
let subfamily = name_string(name, 17).or_else(|| name_string(name, 2)).unwrap_or_default();
|
||||
let full_name = name_string(name, 4).unwrap_or_else(|| format!("{family} {subfamily}").trim().to_string());
|
||||
let family = name_string(name, 16)
|
||||
.or_else(|| name_string(name, 1))
|
||||
.unwrap_or_default();
|
||||
let subfamily = name_string(name, 17)
|
||||
.or_else(|| name_string(name, 2))
|
||||
.unwrap_or_default();
|
||||
let full_name =
|
||||
name_string(name, 4).unwrap_or_else(|| format!("{family} {subfamily}").trim().to_string());
|
||||
let postscript_name = name_string(name, 6).unwrap_or_default();
|
||||
if family.is_empty() && full_name.is_empty() {
|
||||
return Err("The font has no readable name".into());
|
||||
@@ -460,7 +496,10 @@ pub fn inspect_bytes(input: &[u8]) -> Result<(Vec<u8>, FontInspection), String>
|
||||
// storage
|
||||
|
||||
fn font_path(local_dir: &Path, sha256: &str, format: &str) -> Result<PathBuf, String> {
|
||||
let valid = sha256.len() == 64 && sha256.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'));
|
||||
let valid = sha256.len() == 64
|
||||
&& sha256
|
||||
.bytes()
|
||||
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'));
|
||||
if !valid {
|
||||
return Err("The stored font hash is malformed".to_string());
|
||||
}
|
||||
@@ -493,19 +532,31 @@ fn map_row(r: &rusqlite::Row) -> rusqlite::Result<UserFont> {
|
||||
}
|
||||
|
||||
fn get_font(conn: &Connection, id: i64) -> Result<Option<UserFont>, String> {
|
||||
conn.query_row(&format!("SELECT {COLS} FROM user_fonts WHERE id = ?1"), params![id], map_row)
|
||||
.optional()
|
||||
.map_err(|e| e.to_string())
|
||||
conn.query_row(
|
||||
&format!("SELECT {COLS} FROM user_fonts WHERE id = ?1"),
|
||||
params![id],
|
||||
map_row,
|
||||
)
|
||||
.optional()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn list_user_fonts_impl(conn: &Connection, include_hidden: bool) -> Result<Vec<UserFont>, String> {
|
||||
pub fn list_user_fonts_impl(
|
||||
conn: &Connection,
|
||||
include_hidden: bool,
|
||||
) -> Result<Vec<UserFont>, String> {
|
||||
let sql = format!(
|
||||
"SELECT {COLS} FROM user_fonts {} ORDER BY face, weight, style, id",
|
||||
if include_hidden { "" } else { "WHERE hidden = 0" }
|
||||
if include_hidden {
|
||||
""
|
||||
} else {
|
||||
"WHERE hidden = 0"
|
||||
}
|
||||
);
|
||||
let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
|
||||
let rows = stmt.query_map([], map_row).map_err(|e| e.to_string())?;
|
||||
rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
|
||||
rows.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Whether any invoice's frozen render prefs name this font file.
|
||||
@@ -531,7 +582,11 @@ pub struct ImportRequest<'a> {
|
||||
fn clean_file_name(raw: &str) -> String {
|
||||
let base = raw.rsplit(['/', '\\']).next().unwrap_or(raw);
|
||||
let cleaned: String = base.chars().filter(|c| !c.is_control()).take(120).collect();
|
||||
if cleaned.trim().is_empty() { "font".to_string() } else { cleaned }
|
||||
if cleaned.trim().is_empty() {
|
||||
"font".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_font_impl(
|
||||
@@ -541,7 +596,10 @@ pub fn import_font_impl(
|
||||
req: &ImportRequest,
|
||||
) -> Result<UserFont, String> {
|
||||
if !FACES.contains(&req.face) {
|
||||
return Err(format!("Fonts can be imported for {} only", FACES.join(", ")));
|
||||
return Err(format!(
|
||||
"Fonts can be imported for {} only",
|
||||
FACES.join(", ")
|
||||
));
|
||||
}
|
||||
if !req.licence_acknowledged {
|
||||
return Err("Confirm that you hold a licence for this font before importing it".into());
|
||||
@@ -567,7 +625,9 @@ pub fn import_font_impl(
|
||||
let path = font_path(local_dir, &info.sha256, &info.format)?;
|
||||
std::fs::create_dir_all(path.parent().expect("font path has a parent"))
|
||||
.map_err(|e| format!("Could not create the fonts folder: {e}"))?;
|
||||
let present = std::fs::metadata(&path).map(|m| m.len() == sfnt.len() as u64).unwrap_or(false);
|
||||
let present = std::fs::metadata(&path)
|
||||
.map(|m| m.len() == sfnt.len() as u64)
|
||||
.unwrap_or(false);
|
||||
if !present {
|
||||
write_atomic(&path, &sfnt)?;
|
||||
}
|
||||
@@ -628,12 +688,21 @@ pub fn import_font_impl(
|
||||
fn retire(conn: &Connection, local_dir: &Path, id: i64) -> Result<Option<PathBuf>, String> {
|
||||
let font = get_font(conn, id)?.ok_or_else(|| "Font not found".to_string())?;
|
||||
if referenced_by_invoice(conn, &font.sha256)? {
|
||||
conn.execute("UPDATE user_fonts SET hidden = 1 WHERE id = ?1", params![id]).map_err(|e| e.to_string())?;
|
||||
conn.execute(
|
||||
"UPDATE user_fonts SET hidden = 1 WHERE id = ?1",
|
||||
params![id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
return Ok(None);
|
||||
}
|
||||
conn.execute("DELETE FROM user_fonts WHERE id = ?1", params![id]).map_err(|e| e.to_string())?;
|
||||
conn.execute("DELETE FROM user_fonts WHERE id = ?1", params![id])
|
||||
.map_err(|e| e.to_string())?;
|
||||
let shared: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM user_fonts WHERE sha256 = ?1", params![font.sha256], |r| r.get(0))
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM user_fonts WHERE sha256 = ?1",
|
||||
params![font.sha256],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
if shared > 0 {
|
||||
return Ok(None);
|
||||
@@ -641,11 +710,19 @@ fn retire(conn: &Connection, local_dir: &Path, id: i64) -> Result<Option<PathBuf
|
||||
font_path(local_dir, &font.sha256, &font.format).map(Some)
|
||||
}
|
||||
|
||||
pub fn remove_user_font_impl(conn: &mut Connection, local_dir: &Path, id: i64) -> Result<RemoveOutcome, String> {
|
||||
pub fn remove_user_font_impl(
|
||||
conn: &mut Connection,
|
||||
local_dir: &Path,
|
||||
id: i64,
|
||||
) -> Result<RemoveOutcome, String> {
|
||||
let tx = conn.transaction().map_err(|e| e.to_string())?;
|
||||
let file = retire(&tx, local_dir, id)?;
|
||||
let still_there: bool = tx
|
||||
.query_row("SELECT EXISTS(SELECT 1 FROM user_fonts WHERE id = ?1)", params![id], |r| r.get::<_, i64>(0))
|
||||
.query_row(
|
||||
"SELECT EXISTS(SELECT 1 FROM user_fonts WHERE id = ?1)",
|
||||
params![id],
|
||||
|r| r.get::<_, i64>(0),
|
||||
)
|
||||
.map_err(|e| e.to_string())?
|
||||
!= 0;
|
||||
tx.commit().map_err(|e| e.to_string())?;
|
||||
@@ -653,13 +730,24 @@ pub fn remove_user_font_impl(conn: &mut Connection, local_dir: &Path, id: i64) -
|
||||
// Best effort, as in import: the row is already gone.
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
Ok(RemoveOutcome { deleted: !still_there, hidden: still_there })
|
||||
Ok(RemoveOutcome {
|
||||
deleted: !still_there,
|
||||
hidden: still_there,
|
||||
})
|
||||
}
|
||||
|
||||
/// The stored sfnt for a font file, hidden or not (an issued invoice must still be able to render with it).
|
||||
pub fn user_font_bytes_impl(conn: &Connection, local_dir: &Path, sha256: &str) -> Result<Vec<u8>, String> {
|
||||
pub fn user_font_bytes_impl(
|
||||
conn: &Connection,
|
||||
local_dir: &Path,
|
||||
sha256: &str,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let format: Option<String> = conn
|
||||
.query_row("SELECT format FROM user_fonts WHERE sha256 = ?1 LIMIT 1", params![sha256], |r| r.get(0))
|
||||
.query_row(
|
||||
"SELECT format FROM user_fonts WHERE sha256 = ?1 LIMIT 1",
|
||||
params![sha256],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let format = format.ok_or_else(|| "This font is not in the font library".to_string())?;
|
||||
@@ -677,20 +765,43 @@ pub fn user_font_bytes_impl(conn: &Connection, local_dir: &Path, sha256: &str) -
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn parse_import_headers<'a>(headers: &'a Headers, file_name: &'a str) -> Result<ImportRequest<'a>, String> {
|
||||
fn parse_import_headers<'a>(
|
||||
headers: &'a Headers,
|
||||
file_name: &'a str,
|
||||
) -> Result<ImportRequest<'a>, String> {
|
||||
let face = required_header(headers, "x-face")?;
|
||||
let weight = match headers.get("x-weight").map(|v| v.trim()).filter(|v| !v.is_empty()) {
|
||||
let weight = match headers
|
||||
.get("x-weight")
|
||||
.map(|v| v.trim())
|
||||
.filter(|v| !v.is_empty())
|
||||
{
|
||||
None => None,
|
||||
Some(v) => Some(v.parse::<u16>().map_err(|_| "x-weight must be a number".to_string())?),
|
||||
Some(v) => Some(
|
||||
v.parse::<u16>()
|
||||
.map_err(|_| "x-weight must be a number".to_string())?,
|
||||
),
|
||||
};
|
||||
let italic = match headers.get("x-style").map(|v| v.trim()).filter(|v| !v.is_empty()) {
|
||||
let italic = match headers
|
||||
.get("x-style")
|
||||
.map(|v| v.trim())
|
||||
.filter(|v| !v.is_empty())
|
||||
{
|
||||
None => None,
|
||||
Some("normal") => Some(false),
|
||||
Some("italic") => Some(true),
|
||||
Some(_) => return Err("x-style must be normal or italic".into()),
|
||||
};
|
||||
let licence_acknowledged = headers.get("x-licence-ack").map(|v| v.trim() == "true").unwrap_or(false);
|
||||
Ok(ImportRequest { face, file_name, weight, italic, licence_acknowledged })
|
||||
let licence_acknowledged = headers
|
||||
.get("x-licence-ack")
|
||||
.map(|v| v.trim() == "true")
|
||||
.unwrap_or(false);
|
||||
Ok(ImportRequest {
|
||||
face,
|
||||
file_name,
|
||||
weight,
|
||||
italic,
|
||||
licence_acknowledged,
|
||||
})
|
||||
}
|
||||
|
||||
// The commands are async so parsing, hashing and file I/O of a multi-MB font stay off the main thread.
|
||||
@@ -701,10 +812,18 @@ pub async fn inspect_font(request: Request<'_>) -> Result<FontInspection, String
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn import_font(request: Request<'_>, state: State<'_, AppState>) -> Result<UserFont, String> {
|
||||
pub async fn import_font(
|
||||
request: Request<'_>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<UserFont, String> {
|
||||
let bytes = raw_body(&request)?;
|
||||
let headers = header_map(&request);
|
||||
let file_name = decode_header_path(headers.get("x-file-name").map(String::as_str).unwrap_or("font"))?;
|
||||
let file_name = decode_header_path(
|
||||
headers
|
||||
.get("x-file-name")
|
||||
.map(String::as_str)
|
||||
.unwrap_or("font"),
|
||||
)?;
|
||||
let face = decode_header_path(required_header(&headers, "x-face")?)?;
|
||||
let mut headers = headers;
|
||||
headers.insert("x-face".to_string(), face);
|
||||
@@ -714,19 +833,28 @@ pub async fn import_font(request: Request<'_>, state: State<'_, AppState>) -> Re
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_user_fonts(include_hidden: Option<bool>, state: State<'_, AppState>) -> Result<Vec<UserFont>, String> {
|
||||
pub async fn list_user_fonts(
|
||||
include_hidden: Option<bool>,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<UserFont>, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
list_user_fonts_impl(&conn, include_hidden.unwrap_or(false))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_user_font_bytes(sha256: String, state: State<'_, AppState>) -> Result<Response, String> {
|
||||
pub async fn get_user_font_bytes(
|
||||
sha256: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Response, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
user_font_bytes_impl(&conn, &state.local_data_dir, &sha256).map(Response::new)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_user_font(id: i64, state: State<'_, AppState>) -> Result<RemoveOutcome, String> {
|
||||
pub async fn remove_user_font(
|
||||
id: i64,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<RemoveOutcome, String> {
|
||||
let mut conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
remove_user_font_impl(&mut conn, &state.local_data_dir, id)
|
||||
}
|
||||
@@ -740,7 +868,9 @@ mod tests {
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn jost(name: &str) -> Vec<u8> {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../public/fonts/jost").join(name);
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../public/fonts/jost")
|
||||
.join(name);
|
||||
std::fs::read(path).unwrap()
|
||||
}
|
||||
fn regular() -> Vec<u8> {
|
||||
@@ -781,7 +911,11 @@ mod tests {
|
||||
let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||
enc.write_all(raw).unwrap();
|
||||
let z = enc.finish().unwrap();
|
||||
blobs.push(if z.len() < raw.len() { (*tag, z, raw.len()) } else { (*tag, raw.to_vec(), raw.len()) });
|
||||
blobs.push(if z.len() < raw.len() {
|
||||
(*tag, z, raw.len())
|
||||
} else {
|
||||
(*tag, raw.to_vec(), raw.len())
|
||||
});
|
||||
}
|
||||
let mut out = vec![0u8; 44 + 20 * n];
|
||||
out[0..4].copy_from_slice(b"wOFF");
|
||||
@@ -803,7 +937,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn request<'a>(face: &'a str, ack: bool) -> ImportRequest<'a> {
|
||||
ImportRequest { face, file_name: "C:\\fonts\\Jost-Regular.ttf", weight: None, italic: None, licence_acknowledged: ack }
|
||||
ImportRequest {
|
||||
face,
|
||||
file_name: "C:\\fonts\\Jost-Regular.ttf",
|
||||
weight: None,
|
||||
italic: None,
|
||||
licence_acknowledged: ack,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -821,9 +961,17 @@ mod tests {
|
||||
assert_eq!(info.sha256, sha256_hex(®ular()));
|
||||
assert_eq!(info.warnings.len(), 1, "{:?}", info.warnings);
|
||||
assert!(info.warnings[0].contains("rupee"));
|
||||
let poppins = std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("../public/fonts/poppins/Poppins-Regular.ttf")).unwrap();
|
||||
let poppins = std::fs::read(
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../public/fonts/poppins/Poppins-Regular.ttf"),
|
||||
)
|
||||
.unwrap();
|
||||
let (_, with_rupee) = inspect_bytes(&poppins).unwrap();
|
||||
assert!(with_rupee.has_rupee && with_rupee.warnings.is_empty(), "{:?}", with_rupee.warnings);
|
||||
assert!(
|
||||
with_rupee.has_rupee && with_rupee.warnings.is_empty(),
|
||||
"{:?}",
|
||||
with_rupee.warnings
|
||||
);
|
||||
assert_eq!(inspect_bytes(&bold()).unwrap().1.weight, 700);
|
||||
}
|
||||
|
||||
@@ -835,7 +983,9 @@ mod tests {
|
||||
let mut ttc = regular();
|
||||
ttc[0..4].copy_from_slice(b"ttcf");
|
||||
assert!(inspect_bytes(&ttc).unwrap_err().contains("collections"));
|
||||
assert!(inspect_bytes(b"<!doctype html><html></html>").unwrap_err().contains("not a TrueType"));
|
||||
assert!(inspect_bytes(b"<!doctype html><html></html>")
|
||||
.unwrap_err()
|
||||
.contains("not a TrueType"));
|
||||
assert!(inspect_bytes(b"short").is_err());
|
||||
let mut big = regular();
|
||||
big.resize(MAX_FONT_BYTES + 1, 0);
|
||||
@@ -856,7 +1006,9 @@ mod tests {
|
||||
fn fs_type_rules() {
|
||||
let mut restricted = regular();
|
||||
set_fs_type(&mut restricted, FS_RESTRICTED);
|
||||
assert!(inspect_bytes(&restricted).unwrap_err().contains("restricted"));
|
||||
assert!(inspect_bytes(&restricted)
|
||||
.unwrap_err()
|
||||
.contains("restricted"));
|
||||
|
||||
// A more permissive bit alongside wins (least restrictive).
|
||||
let mut both = regular();
|
||||
@@ -865,11 +1017,21 @@ mod tests {
|
||||
|
||||
let mut preview = regular();
|
||||
set_fs_type(&mut preview, FS_PREVIEW_PRINT);
|
||||
assert!(inspect_bytes(&preview).unwrap().1.warnings.iter().any(|w| w.contains("preview and print")));
|
||||
assert!(inspect_bytes(&preview)
|
||||
.unwrap()
|
||||
.1
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.contains("preview and print")));
|
||||
|
||||
let mut no_subset = regular();
|
||||
set_fs_type(&mut no_subset, FS_NO_SUBSETTING);
|
||||
assert!(inspect_bytes(&no_subset).unwrap().1.warnings.iter().any(|w| w.contains("subset")));
|
||||
assert!(inspect_bytes(&no_subset)
|
||||
.unwrap()
|
||||
.1
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.contains("subset")));
|
||||
|
||||
let mut bitmap = regular();
|
||||
set_fs_type(&mut bitmap, FS_BITMAP_ONLY);
|
||||
@@ -880,13 +1042,18 @@ mod tests {
|
||||
fn refuses_a_font_without_outlines() {
|
||||
let mut font = regular();
|
||||
rename_table(&mut font, b"glyf", b"XXXX");
|
||||
assert!(inspect_bytes(&font).unwrap_err().contains("no glyph outlines"));
|
||||
assert!(inspect_bytes(&font)
|
||||
.unwrap_err()
|
||||
.contains("no glyph outlines"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpacks_woff_to_the_same_font() {
|
||||
let woff = to_woff(®ular());
|
||||
assert!(woff.len() < regular().len(), "the fixture should actually compress");
|
||||
assert!(
|
||||
woff.len() < regular().len(),
|
||||
"the fixture should actually compress"
|
||||
);
|
||||
let (sfnt, info) = inspect_bytes(&woff).unwrap();
|
||||
assert_eq!(info.source_format, "woff");
|
||||
assert_eq!(info.family, "Jost");
|
||||
@@ -897,7 +1064,12 @@ mod tests {
|
||||
let unpacked = Sfnt::parse(&sfnt).unwrap();
|
||||
assert_eq!(original.tables.len(), unpacked.tables.len());
|
||||
for (tag, _, _) in &original.tables {
|
||||
assert_eq!(original.table(tag), unpacked.table(tag), "table {}", String::from_utf8_lossy(tag));
|
||||
assert_eq!(
|
||||
original.table(tag),
|
||||
unpacked.table(tag),
|
||||
"table {}",
|
||||
String::from_utf8_lossy(tag)
|
||||
);
|
||||
}
|
||||
let mut damaged = woff.clone();
|
||||
let len = damaged.len();
|
||||
@@ -909,17 +1081,28 @@ mod tests {
|
||||
fn import_stores_content_addressed_and_round_trips() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let row =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
assert_eq!(row.face, "Gotham");
|
||||
assert_eq!((row.weight, row.style.as_str(), row.format.as_str()), (400, "normal", "ttf"));
|
||||
assert_eq!(row.file_name, "Jost-Regular.ttf", "only the base name is kept");
|
||||
assert_eq!(
|
||||
(row.weight, row.style.as_str(), row.format.as_str()),
|
||||
(400, "normal", "ttf")
|
||||
);
|
||||
assert_eq!(
|
||||
row.file_name, "Jost-Regular.ttf",
|
||||
"only the base name is kept"
|
||||
);
|
||||
assert!(!row.hidden && row.licence_ack_at.len() > 10);
|
||||
let path = dir.path().join("fonts").join(format!("{}.ttf", row.sha256));
|
||||
assert_eq!(std::fs::read(&path).unwrap(), regular());
|
||||
assert_eq!(user_font_bytes_impl(&conn, dir.path(), &row.sha256).unwrap(), regular());
|
||||
assert_eq!(
|
||||
user_font_bytes_impl(&conn, dir.path(), &row.sha256).unwrap(),
|
||||
regular()
|
||||
);
|
||||
|
||||
// Importing the same file again is a no-op.
|
||||
let again = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let again =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
assert_eq!(again.id, row.id);
|
||||
assert_eq!(list_user_fonts_impl(&conn, false).unwrap().len(), 1);
|
||||
|
||||
@@ -933,18 +1116,32 @@ mod tests {
|
||||
fn import_overrides_and_guards() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
assert!(import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", false))
|
||||
.unwrap_err()
|
||||
.contains("licence"));
|
||||
assert!(import_font_impl(&mut conn, dir.path(), ®ular(), &request("Montserrat", true))
|
||||
.unwrap_err()
|
||||
.contains("Now, Gotham, Open Sauce One"));
|
||||
assert!(!dir.path().join("fonts").exists(), "a refused import writes nothing");
|
||||
assert!(
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", false))
|
||||
.unwrap_err()
|
||||
.contains("licence")
|
||||
);
|
||||
assert!(import_font_impl(
|
||||
&mut conn,
|
||||
dir.path(),
|
||||
®ular(),
|
||||
&request("Montserrat", true)
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("Now, Gotham, Open Sauce One"));
|
||||
assert!(
|
||||
!dir.path().join("fonts").exists(),
|
||||
"a refused import writes nothing"
|
||||
);
|
||||
let mut woff2 = regular();
|
||||
woff2[0..4].copy_from_slice(b"wOF2");
|
||||
assert!(import_font_impl(&mut conn, dir.path(), &woff2, &request("Now", true)).is_err());
|
||||
|
||||
let req = ImportRequest { weight: Some(300), italic: Some(true), ..request("Open Sauce One", true) };
|
||||
let req = ImportRequest {
|
||||
weight: Some(300),
|
||||
italic: Some(true),
|
||||
..request("Open Sauce One", true)
|
||||
};
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &req).unwrap();
|
||||
assert_eq!((row.weight, row.style.as_str()), (300, "italic"));
|
||||
// The table itself refuses other faces.
|
||||
@@ -962,15 +1159,23 @@ mod tests {
|
||||
fn replacing_a_slot_removes_the_old_unreferenced_font() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let first = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let first =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
// Same slot, different file (the bold file declared as 400).
|
||||
let req = ImportRequest { weight: Some(400), ..request("Gotham", true) };
|
||||
let req = ImportRequest {
|
||||
weight: Some(400),
|
||||
..request("Gotham", true)
|
||||
};
|
||||
let second = import_font_impl(&mut conn, dir.path(), &bold(), &req).unwrap();
|
||||
assert_ne!(first.sha256, second.sha256);
|
||||
let rows = list_user_fonts_impl(&conn, true).unwrap();
|
||||
assert_eq!(rows.len(), 1);
|
||||
assert_eq!(rows[0].sha256, second.sha256);
|
||||
assert!(!dir.path().join("fonts").join(format!("{}.ttf", first.sha256)).exists());
|
||||
assert!(!dir
|
||||
.path()
|
||||
.join("fonts")
|
||||
.join(format!("{}.ttf", first.sha256))
|
||||
.exists());
|
||||
}
|
||||
|
||||
fn issue_invoice_with_prefs(conn: &Connection, prefs: &str) {
|
||||
@@ -986,11 +1191,22 @@ mod tests {
|
||||
fn remove_deletes_when_unreferenced() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Now", true)).unwrap();
|
||||
let row =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Now", true)).unwrap();
|
||||
let out = remove_user_font_impl(&mut conn, dir.path(), row.id).unwrap();
|
||||
assert_eq!(out, RemoveOutcome { deleted: true, hidden: false });
|
||||
assert_eq!(
|
||||
out,
|
||||
RemoveOutcome {
|
||||
deleted: true,
|
||||
hidden: false
|
||||
}
|
||||
);
|
||||
assert!(list_user_fonts_impl(&conn, true).unwrap().is_empty());
|
||||
assert!(!dir.path().join("fonts").join(format!("{}.ttf", row.sha256)).exists());
|
||||
assert!(!dir
|
||||
.path()
|
||||
.join("fonts")
|
||||
.join(format!("{}.ttf", row.sha256))
|
||||
.exists());
|
||||
assert!(user_font_bytes_impl(&conn, dir.path(), &row.sha256).is_err());
|
||||
assert!(remove_user_font_impl(&mut conn, dir.path(), row.id).is_err());
|
||||
}
|
||||
@@ -999,20 +1215,42 @@ mod tests {
|
||||
fn remove_hides_a_font_an_issued_invoice_references() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let row =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
issue_invoice_with_prefs(
|
||||
&conn,
|
||||
&format!(r#"{{"version":1,"templateId":"purple-pop","fonts":{{"Gotham":["{}"]}}}}"#, row.sha256),
|
||||
&format!(
|
||||
r#"{{"version":1,"templateId":"purple-pop","fonts":{{"Gotham":["{}"]}}}}"#,
|
||||
row.sha256
|
||||
),
|
||||
);
|
||||
let out = remove_user_font_impl(&mut conn, dir.path(), row.id).unwrap();
|
||||
assert_eq!(out, RemoveOutcome { deleted: false, hidden: true });
|
||||
assert_eq!(
|
||||
out,
|
||||
RemoveOutcome {
|
||||
deleted: false,
|
||||
hidden: true
|
||||
}
|
||||
);
|
||||
// Gone from the visible list, but kept and still served so the invoice can re-render.
|
||||
assert!(list_user_fonts_impl(&conn, false).unwrap().is_empty());
|
||||
assert_eq!(list_user_fonts_impl(&conn, true).unwrap().len(), 1);
|
||||
assert_eq!(user_font_bytes_impl(&conn, dir.path(), &row.sha256).unwrap(), regular());
|
||||
assert_eq!(
|
||||
user_font_bytes_impl(&conn, dir.path(), &row.sha256).unwrap(),
|
||||
regular()
|
||||
);
|
||||
|
||||
// The slot is free again: importing a new font for it works, and the hidden one stays.
|
||||
let fresh = import_font_impl(&mut conn, dir.path(), &bold(), &ImportRequest { weight: Some(400), ..request("Gotham", true) }).unwrap();
|
||||
let fresh = import_font_impl(
|
||||
&mut conn,
|
||||
dir.path(),
|
||||
&bold(),
|
||||
&ImportRequest {
|
||||
weight: Some(400),
|
||||
..request("Gotham", true)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_ne!(fresh.sha256, row.sha256);
|
||||
assert_eq!(list_user_fonts_impl(&conn, true).unwrap().len(), 2);
|
||||
}
|
||||
@@ -1021,10 +1259,27 @@ mod tests {
|
||||
fn replacing_a_referenced_font_hides_it_instead_of_deleting() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
issue_invoice_with_prefs(&conn, &format!(r#"{{"fonts":{{"Gotham":["{}"]}}}}"#, row.sha256));
|
||||
import_font_impl(&mut conn, dir.path(), &bold(), &ImportRequest { weight: Some(400), ..request("Gotham", true) }).unwrap();
|
||||
assert!(dir.path().join("fonts").join(format!("{}.ttf", row.sha256)).exists());
|
||||
let row =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
issue_invoice_with_prefs(
|
||||
&conn,
|
||||
&format!(r#"{{"fonts":{{"Gotham":["{}"]}}}}"#, row.sha256),
|
||||
);
|
||||
import_font_impl(
|
||||
&mut conn,
|
||||
dir.path(),
|
||||
&bold(),
|
||||
&ImportRequest {
|
||||
weight: Some(400),
|
||||
..request("Gotham", true)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(dir
|
||||
.path()
|
||||
.join("fonts")
|
||||
.join(format!("{}.ttf", row.sha256))
|
||||
.exists());
|
||||
let all = list_user_fonts_impl(&conn, true).unwrap();
|
||||
assert_eq!(all.len(), 2);
|
||||
assert_eq!(all.iter().filter(|f| f.hidden).count(), 1);
|
||||
@@ -1034,22 +1289,38 @@ mod tests {
|
||||
fn a_file_shared_by_two_faces_survives_removing_one() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let a = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let a =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Gotham", true)).unwrap();
|
||||
let b = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Now", true)).unwrap();
|
||||
assert_eq!(a.sha256, b.sha256);
|
||||
remove_user_font_impl(&mut conn, dir.path(), a.id).unwrap();
|
||||
assert!(dir.path().join("fonts").join(format!("{}.ttf", a.sha256)).exists());
|
||||
assert!(dir
|
||||
.path()
|
||||
.join("fonts")
|
||||
.join(format!("{}.ttf", a.sha256))
|
||||
.exists());
|
||||
remove_user_font_impl(&mut conn, dir.path(), b.id).unwrap();
|
||||
assert!(!dir.path().join("fonts").join(format!("{}.ttf", a.sha256)).exists());
|
||||
assert!(!dir
|
||||
.path()
|
||||
.join("fonts")
|
||||
.join(format!("{}.ttf", a.sha256))
|
||||
.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_are_verified_and_the_hash_shape_checked() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
let row = import_font_impl(&mut conn, dir.path(), ®ular(), &request("Now", true)).unwrap();
|
||||
std::fs::write(dir.path().join("fonts").join(format!("{}.ttf", row.sha256)), b"tampered").unwrap();
|
||||
assert!(user_font_bytes_impl(&conn, dir.path(), &row.sha256).unwrap_err().contains("corrupted"));
|
||||
let row =
|
||||
import_font_impl(&mut conn, dir.path(), ®ular(), &request("Now", true)).unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join("fonts").join(format!("{}.ttf", row.sha256)),
|
||||
b"tampered",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(user_font_bytes_impl(&conn, dir.path(), &row.sha256)
|
||||
.unwrap_err()
|
||||
.contains("corrupted"));
|
||||
assert!(font_path(dir.path(), "../../etc/passwd", "ttf").is_err());
|
||||
}
|
||||
|
||||
@@ -1061,11 +1332,18 @@ mod tests {
|
||||
h.insert("x-weight".into(), "700".into());
|
||||
h.insert("x-style".into(), "italic".into());
|
||||
let req = parse_import_headers(&h, "f.ttf").unwrap();
|
||||
assert_eq!((req.weight, req.italic, req.licence_acknowledged), (Some(700), Some(true), true));
|
||||
assert_eq!(
|
||||
(req.weight, req.italic, req.licence_acknowledged),
|
||||
(Some(700), Some(true), true)
|
||||
);
|
||||
h.insert("x-style".into(), "oblique".into());
|
||||
assert!(parse_import_headers(&h, "f.ttf").is_err());
|
||||
h.remove("x-licence-ack");
|
||||
h.insert("x-style".into(), "".into());
|
||||
assert!(!parse_import_headers(&h, "f.ttf").unwrap().licence_acknowledged);
|
||||
assert!(
|
||||
!parse_import_headers(&h, "f.ttf")
|
||||
.unwrap()
|
||||
.licence_acknowledged
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user