From 9774786391ccf1d05b10ccf95fb5acbdabfd0d53 Mon Sep 17 00:00:00 2001 From: Kostya Farber Date: Sat, 30 May 2026 14:53:15 +0100 Subject: [PATCH] feat(store): add font info tables and tests --- crates/shift-store/src/font.rs | 134 +++++++++++++++++++++++++ crates/shift-store/src/lib.rs | 2 + crates/shift-store/src/schema.rs | 19 ++++ crates/shift-store/tests/store_test.rs | 108 +++++++++++++++++++- 4 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 crates/shift-store/src/font.rs diff --git a/crates/shift-store/src/font.rs b/crates/shift-store/src/font.rs new file mode 100644 index 00000000..160412dc --- /dev/null +++ b/crates/shift-store/src/font.rs @@ -0,0 +1,134 @@ +use crate::{ShiftStore, StoreError}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FontInfo { + pub family_name: Option, + pub copyright: Option, + pub trademark: Option, + pub description: Option, + pub sample_text: Option, + pub designer: Option, + pub designer_url: Option, + pub manufacturer: Option, + pub manufacturer_url: Option, + pub license_description: Option, + pub license_info_url: Option, + pub vendor_id: Option, + pub version_major: Option, + pub version_minor: Option, + pub units_per_em: i64, +} + +impl ShiftStore { + pub fn set_font_info(&mut self, font_info: FontInfo) -> Result<(), StoreError> { + self.conn.execute( + " + INSERT INTO font_info ( + id, + family_name, + copyright, + trademark, + description, + sample_text, + designer, + designer_url, + manufacturer, + manufacturer_url, + license_description, + license_info_url, + vendor_id, + version_major, + version_minor, + units_per_em + ) + VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15) + ON CONFLICT(id) DO UPDATE SET + family_name = excluded.family_name, + copyright = excluded.copyright, + trademark = excluded.trademark, + description = excluded.description, + sample_text = excluded.sample_text, + designer = excluded.designer, + designer_url = excluded.designer_url, + manufacturer = excluded.manufacturer, + manufacturer_url = excluded.manufacturer_url, + license_description = excluded.license_description, + license_info_url = excluded.license_info_url, + vendor_id = excluded.vendor_id, + version_major = excluded.version_major, + version_minor = excluded.version_minor, + units_per_em = excluded.units_per_em + ", + rusqlite::params![ + font_info.family_name, + font_info.copyright, + font_info.trademark, + font_info.description, + font_info.sample_text, + font_info.designer, + font_info.designer_url, + font_info.manufacturer, + font_info.manufacturer_url, + font_info.license_description, + font_info.license_info_url, + font_info.vendor_id, + font_info.version_major, + font_info.version_minor, + font_info.units_per_em, + ], + )?; + + Ok(()) + } + + pub fn get_font_info(&self) -> Result, StoreError> { + let mut stmt = self.conn.prepare( + " + SELECT + family_name, + copyright, + trademark, + description, + sample_text, + designer, + designer_url, + manufacturer, + manufacturer_url, + license_description, + license_info_url, + vendor_id, + version_major, + version_minor, + units_per_em + FROM font_info + WHERE id = 1 + ", + )?; + + match stmt.query_row([], map_font_info_row) { + Ok(font_info) => Ok(Some(font_info)), + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +fn map_font_info_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { + Ok(FontInfo { + family_name: row.get(0)?, + copyright: row.get(1)?, + trademark: row.get(2)?, + description: row.get(3)?, + sample_text: row.get(4)?, + designer: row.get(5)?, + designer_url: row.get(6)?, + manufacturer: row.get(7)?, + manufacturer_url: row.get(8)?, + license_description: row.get(9)?, + license_info_url: row.get(10)?, + vendor_id: row.get(11)?, + version_major: row.get(12)?, + version_minor: row.get(13)?, + units_per_em: row.get(14)?, + }) +} diff --git a/crates/shift-store/src/lib.rs b/crates/shift-store/src/lib.rs index 4fbeaae2..1cf5ffb8 100644 --- a/crates/shift-store/src/lib.rs +++ b/crates/shift-store/src/lib.rs @@ -1,6 +1,7 @@ mod component; mod connection; mod error; +mod font; mod glyph; mod layer; mod schema; @@ -10,6 +11,7 @@ mod types; pub use component::{GlyphComponentRecord, NewGlyphComponent}; pub use error::StoreError; +pub use font::FontInfo; pub use glyph::{GlyphRecord, NewGlyph}; pub use layer::{GlyphLayerRecord, NewGlyphLayer}; pub use source::{AxisRecord, NewAxis, NewSource, SourceAxisLocation, SourceKind, SourceRecord}; diff --git a/crates/shift-store/src/schema.rs b/crates/shift-store/src/schema.rs index 84ec6100..f8cf4917 100644 --- a/crates/shift-store/src/schema.rs +++ b/crates/shift-store/src/schema.rs @@ -1,6 +1,25 @@ use crate::StoreError; pub(crate) const SCHEMA_V1: &str = r#" +CREATE TABLE IF NOT EXISTS font_info ( + id INTEGER PRIMARY KEY CHECK (id = 1), + family_name TEXT, + copyright TEXT, + trademark TEXT, + description TEXT, + sample_text TEXT, + designer TEXT, + designer_url TEXT, + manufacturer TEXT, + manufacturer_url TEXT, + license_description TEXT, + license_info_url TEXT, + vendor_id TEXT, + version_major INTEGER CHECK (version_major IS NULL OR version_major >= 0), + version_minor INTEGER CHECK (version_minor IS NULL OR version_minor >= 0), + units_per_em INTEGER NOT NULL CHECK (units_per_em > 0) +); + CREATE TABLE IF NOT EXISTS axes ( id TEXT PRIMARY KEY, tag TEXT NOT NULL, diff --git a/crates/shift-store/tests/store_test.rs b/crates/shift-store/tests/store_test.rs index 0b08de1c..5544316f 100644 --- a/crates/shift-store/tests/store_test.rs +++ b/crates/shift-store/tests/store_test.rs @@ -1,6 +1,6 @@ use shift_store::{ - AxisId, ComponentId, GlyphId, LayerId, NewAxis, NewGlyph, NewGlyphComponent, NewGlyphLayer, - NewSource, ShiftStore, SourceId, SourceKind, + AxisId, ComponentId, FontInfo, GlyphId, LayerId, NewAxis, NewGlyph, NewGlyphComponent, + NewGlyphLayer, NewSource, ShiftStore, SourceId, SourceKind, }; #[test] @@ -8,6 +8,61 @@ fn opens_memory_store() { ShiftStore::open_memory_for_test().expect("memory store should open"); } +#[test] +fn writes_and_reads_font_info() { + let mut store = ShiftStore::open_memory_for_test().expect("memory store should open"); + let font_info = open_sans_font_info(); + + store + .set_font_info(font_info.clone()) + .expect("font info should be written"); + + let loaded = store + .get_font_info() + .expect("font info query should succeed") + .expect("font info should exist"); + + assert_eq!(loaded, font_info); +} + +#[test] +fn overwrites_font_info() { + let mut store = ShiftStore::open_memory_for_test().expect("memory store should open"); + + store + .set_font_info(open_sans_font_info()) + .expect("font info should be written"); + + store + .set_font_info(FontInfo { + family_name: Some("Shift Sans".to_string()), + units_per_em: 1000, + ..empty_font_info() + }) + .expect("font info should be overwritten"); + + let loaded = store + .get_font_info() + .expect("font info query should succeed") + .expect("font info should exist"); + + assert_eq!(loaded.family_name.as_deref(), Some("Shift Sans")); + assert_eq!(loaded.units_per_em, 1000); + assert_eq!(loaded.copyright, None); +} + +#[test] +fn font_info_requires_positive_units_per_em() { + let mut store = ShiftStore::open_memory_for_test().expect("memory store should open"); + + let result = store.set_font_info(FontInfo { + units_per_em: 0, + ..empty_font_info() + }); + + assert!(result.is_err()); +} + #[test] fn creates_and_reads_glyph() { let mut store = ShiftStore::open_memory_for_test().expect("memory store should open"); @@ -215,6 +270,55 @@ fn create_glyph_a(store: &mut ShiftStore) -> GlyphId { glyph_id } +fn open_sans_font_info() -> FontInfo { + FontInfo { + family_name: Some("Open Sans".to_string()), + copyright: Some( + "Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)" + .to_string(), + ), + trademark: Some( + "Open Sans is a trademark of Google and may be registered in certain jurisdictions." + .to_string(), + ), + description: Some("Designed by Monotype design team.".to_string()), + sample_text: None, + designer: Some("Monotype Design Team".to_string()), + designer_url: Some("http://www.monotype.com/studio".to_string()), + manufacturer: Some("Monotype Imaging Inc.".to_string()), + manufacturer_url: Some("http://www.google.com/get/noto/".to_string()), + license_description: Some( + "This Font Software is licensed under the SIL Open Font License, Version 1.1." + .to_string(), + ), + license_info_url: Some("http://scripts.sil.org/OFL".to_string()), + vendor_id: None, + version_major: Some(3), + version_minor: Some(3), + units_per_em: 2048, + } +} + +fn empty_font_info() -> FontInfo { + FontInfo { + family_name: None, + copyright: None, + trademark: None, + description: None, + sample_text: None, + designer: None, + designer_url: None, + manufacturer: None, + manufacturer_url: None, + license_description: None, + license_info_url: None, + vendor_id: None, + version_major: None, + version_minor: None, + units_per_em: 1000, + } +} + fn create_glyph_b(store: &mut ShiftStore) -> GlyphId { let glyph_id = GlyphId::new("glyph-B");