diff --git a/crates/tinymist/src/resource/mod.rs b/crates/tinymist/src/resource/mod.rs index 550d3adb..0a542562 100644 --- a/crates/tinymist/src/resource/mod.rs +++ b/crates/tinymist/src/resource/mod.rs @@ -5,7 +5,7 @@ mod prelude { pub use std::collections::HashMap; - pub use reflexo_vec2svg::ir::{GlyphItem, GlyphRef}; + pub use reflexo_vec2svg::ir::GlyphItem; pub use serde::{Deserialize, Serialize}; pub use serde_json::Value as JsonValue; pub use sync_ls::*; diff --git a/crates/tinymist/src/resource/symbols.rs b/crates/tinymist/src/resource/symbols.rs index ed322e20..ebaa9894 100644 --- a/crates/tinymist/src/resource/symbols.rs +++ b/crates/tinymist/src/resource/symbols.rs @@ -5,7 +5,6 @@ use reflexo_typst::TypstPagedDocument; use reflexo_typst::{vector::font::GlyphId, TypstFont}; use reflexo_vec2svg::SvgGlyphBuilder; use sync_ls::LspResult; -use tinymist_std::typst::TypstDocument; use typst::foundations::Bytes; use typst::{syntax::VirtualPath, World}; @@ -16,16 +15,23 @@ use crate::world::{base::ShadowApi, EntryState, TaskInputs}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct ResourceSymbolResponse { - symbols: BTreeMap, - font_selects: Vec, - glyph_defs: HashMap, + symbols: Vec, } #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] struct ResourceSymbolItem { + id: String, + category: SymCategory, + unicode: u32, + #[serde(skip_serializing_if = "Option::is_none")] + glyph: Option, +} + +#[derive(Debug)] +struct SymbolItem { category: SymCategory, unicode: u32, - glyphs: Vec, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -60,32 +66,7 @@ enum SymCategory { DoubleStruck, } -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResourceGlyphDesc { - font_index: u32, - x_advance: Option, - y_advance: Option, - x_min: Option, - x_max: Option, - y_min: Option, - y_max: Option, - name: Option, - shape: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct FontItem { - family: String, - cap_height: f32, - ascender: f32, - descender: f32, - units_per_em: f32, - // vertical: bool, -} - -type ResourceSymbolMap = BTreeMap; +type ResourceSymbolMap = BTreeMap; static CAT_MAP: LazyLock> = LazyLock::new(|| { use SymCategory::*; @@ -951,155 +932,13 @@ static CAT_MAP: LazyLock> = LazyLock::new(|| { impl ServerState { /// Get the all valid symbols pub async fn get_symbol_resources(snap: LspComputeGraph) -> LspResult { - let mut symbols = ResourceSymbolMap::new(); + let symbols = collect_symbols(&snap)?; - let std = snap - .library() - .std - .read() - .scope() - .ok_or_else(|| internal_error("cannot get std scope"))?; - let sym = std - .get("sym") - .ok_or_else(|| internal_error("cannot get sym"))?; + let glyph_mapping = render_symbols(&snap, &symbols)?; - if let Some(scope) = sym.read().scope() { - populate_scope(scope, "sym", SymCategory::Misc, &mut symbols); - } - // todo: disabling emoji module, as there is performant issue on emojis - // let _ = emoji; - // populate_scope(emoji().scope(), "emoji", SymCategory::Emoji, &mut symbols); + let symbols = render_glyphs(&symbols, &glyph_mapping)?; - const PRELUDE: &str = r#"#show math.equation: set text(font: ( - "New Computer Modern Math", - "Latin Modern Math", - "STIX Two Math", - "Cambria Math", - "New Computer Modern", - "Cambria", -)) -"#; - - let math_shaping_text = symbols.iter().fold(PRELUDE.to_owned(), |mut o, (k, e)| { - use std::fmt::Write; - writeln!(o, "$#{k}$/* {} */#pagebreak()", e.unicode).ok(); - o - }); - log::debug!("math shaping text: {math_shaping_text}"); - - let symbols_ref = symbols.keys().cloned().collect::>(); - - let font = { - let entry_path: Arc = Path::new("/._sym_.typ").into(); - - let new_entry = EntryState::new_rootless(VirtualPath::new(&entry_path)); - - let mut forked = snap.world().task(TaskInputs { - entry: Some(new_entry), - ..TaskInputs::default() - }); - forked - .map_shadow_by_id(forked.main(), Bytes::from_string(math_shaping_text)) - .map_err(|e| error_once!("cannot map shadow", err: e)) - .map_err(internal_error)?; - - let sym_doc = typst::compile::(&forked) - .output - .map_err(|e| error_once!("cannot compile symbols", err: format!("{e:?}"))) - .map_err(internal_error)?; - - log::debug!("sym doc: {sym_doc:?}"); - Some(trait_symbol_fonts( - &TypstDocument::Paged(Arc::new(sym_doc)), - &symbols_ref, - )) - }; - - let mut glyph_defs = HashMap::new(); - - let mut collected_fonts = None; - - if let Some(glyph_mapping) = font.clone() { - let glyph_provider = reflexo_vec2svg::GlyphProvider::default(); - let glyph_pass = - reflexo_typst::vector::pass::ConvertInnerImpl::new(glyph_provider, false); - - let mut glyphs = vec![]; - - let font_collected = glyph_mapping - .values() - .map(|e| e.0.clone()) - .collect::>() - .into_iter() - .collect::>(); - - let mut render_sym = |u| { - let (font, id) = glyph_mapping.get(u)?.clone(); - let font_index = font_collected.iter().position(|e| e == &font).unwrap() as u32; - - let width = font.ttf().glyph_hor_advance(id); - let height = font.ttf().glyph_ver_advance(id); - let bbox = font.ttf().glyph_bounding_box(id); - - let glyph = glyph_pass.must_flat_glyph(&GlyphItem::Raw(font.clone(), id))?; - - let g_ref = GlyphRef { - font_hash: font_index, - glyph_idx: id.0 as u32, - }; - - glyphs.push((g_ref, glyph)); - - Some(ResourceGlyphDesc { - font_index, - x_advance: width, - y_advance: height, - x_min: bbox.map(|e| e.x_min), - x_max: bbox.map(|e| e.x_max), - y_min: bbox.map(|e| e.y_min), - y_max: bbox.map(|e| e.y_max), - name: font.ttf().glyph_name(id).map(|e| e.to_owned()), - shape: Some(g_ref.as_svg_id("g")), - }) - }; - - for (k, v) in symbols.iter_mut() { - let Some(desc) = render_sym(k) else { - continue; - }; - - v.glyphs.push(desc); - } - - let mut builder = SvgGlyphBuilder::new(); - glyph_defs = glyphs - .iter() - .map(|(id, item)| { - let glyph_id = id.as_svg_id("g"); - let rendered = builder.render_glyph("", item).unwrap_or_default(); - (glyph_id, rendered) - }) - .collect(); - - collected_fonts = Some(font_collected); - } - - let resp = ResourceSymbolResponse { - symbols, - font_selects: collected_fonts - .map(|e| e.into_iter()) - .into_iter() - .flatten() - .map(|e| FontItem { - family: e.info().family.clone(), - cap_height: e.metrics().cap_height.get() as f32, - ascender: e.metrics().ascender.get() as f32, - descender: e.metrics().descender.get() as f32, - units_per_em: e.metrics().units_per_em as f32, - }) - .collect::>(), - glyph_defs, - }; + let resp = ResourceSymbolResponse { symbols }; serde_json::to_value(resp) .context("cannot serialize response") @@ -1107,82 +946,42 @@ impl ServerState { } } -fn trait_symbol_fonts( - doc: &TypstDocument, - symbols: &[String], -) -> HashMap { - use typst::layout::Frame; - use typst::layout::FrameItem; +fn collect_symbols(snap: &LspComputeGraph) -> LspResult> { + let mut symbols = ResourceSymbolMap::new(); - let mut worker = Worker { - symbols, - active: "", - res: HashMap::new(), - }; - worker.work(doc); - let res = worker.res; + let std = snap + .library() + .std + .read() + .scope() + .ok_or_else(|| internal_error("cannot get std scope"))?; + let sym = std + .get("sym") + .ok_or_else(|| internal_error("cannot get sym"))?; - struct Worker<'a> { - symbols: &'a [String], - active: &'a str, - res: HashMap, + if let Some(scope) = sym.read().scope() { + populate_scope(scope, "sym", SymCategory::Misc, &mut symbols); } + // todo: disabling emoji module, as there is performant issue on emojis + // let _ = emoji; + // populate_scope(emoji().scope(), "emoji", SymCategory::Emoji, &mut symbols); - impl Worker<'_> { - fn work(&mut self, doc: &TypstDocument) { - match doc { - TypstDocument::Paged(paged_doc) => { - for (pg, s) in paged_doc.pages.iter().zip(self.symbols.iter()) { - self.active = s; - self.work_frame(&pg.frame); - } - } - // todo: handle html - TypstDocument::Html(..) => {} - } - } + Ok(symbols) +} - fn work_frame(&mut self, k: &Frame) { - for (_, item) in k.items() { - let text = match item { - FrameItem::Group(g) => { - self.work_frame(&g.frame); - continue; - } - FrameItem::Text(text) => text, - FrameItem::Shape(_, _) - | FrameItem::Image(_, _, _) - | FrameItem::Link(_, _) - | FrameItem::Tag(_) => continue, - }; +fn populate_scope( + sym: &Scope, + mod_name: &str, + fallback_cat: SymCategory, + out: &mut ResourceSymbolMap, +) { + for (k, b) in sym.iter() { + let Value::Symbol(sym) = b.read() else { + continue; + }; - let font = text.font.clone(); - for g in &text.glyphs { - let g_text = &text.text[g.range()]; - let chars_count = g_text.chars().count(); - if chars_count > 1 { - log::warn!("multi char glyph: {g_text}"); - continue; - } - let Some(ch) = g_text.chars().next() else { - continue; - }; - if ch.is_whitespace() { - continue; - } - log::debug!( - "glyph: {active} => {ch} ({chc:x})", - active = self.active, - chc = ch as u32 - ); - self.res - .insert(self.active.to_owned(), (font.clone(), GlyphId(g.id))); - } - } - } + populate(sym, mod_name, k, fallback_cat, out) } - - res } fn populate( @@ -1208,26 +1007,188 @@ fn populate( let category = CAT_MAP.get(name.as_str()).cloned().unwrap_or(fallback_cat); out.insert( name, - ResourceSymbolItem { + SymbolItem { category, unicode: ch as u32, - glyphs: vec![], }, ); } } -fn populate_scope( - sym: &Scope, - mod_name: &str, - fallback_cat: SymCategory, - out: &mut ResourceSymbolMap, -) { - for (k, b) in sym.iter() { - let Value::Symbol(sym) = b.read() else { - continue; - }; +fn render_symbols( + snap: &LspComputeGraph, + symbols: &BTreeMap, +) -> LspResult> { + const PRELUDE: &str = r#"#show math.equation: set text(font: ( + "New Computer Modern Math", + "Latin Modern Math", + "STIX Two Math", + "Cambria Math", + "New Computer Modern", + "Cambria", +)) +"#; - populate(sym, mod_name, k, fallback_cat, out) - } + let math_shaping_text = symbols.iter().fold(PRELUDE.to_owned(), |mut o, (k, e)| { + use std::fmt::Write; + writeln!(o, "$#{k}$/* {} */#pagebreak()", e.unicode).ok(); + o + }); + log::debug!("math shaping text: {math_shaping_text}"); + + let entry_path: Arc = Path::new("/._sym_.typ").into(); + + let new_entry = EntryState::new_rootless(VirtualPath::new(&entry_path)); + + let mut forked = snap.world().task(TaskInputs { + entry: Some(new_entry), + ..TaskInputs::default() + }); + forked + .map_shadow_by_id(forked.main(), Bytes::from_string(math_shaping_text)) + .map_err(|e| error_once!("cannot map shadow", err: e)) + .map_err(internal_error)?; + + let sym_doc = typst::compile::(&forked) + .output + .map_err(|e| error_once!("cannot compile symbols", err: format!("{e:?}"))) + .map_err(internal_error)?; + + log::debug!("sym doc: {sym_doc:?}"); + + let res = extract_rendered_symbols(&sym_doc, symbols.keys()); + + Ok(res) +} + +fn extract_rendered_symbols<'a>( + doc: &TypstPagedDocument, + symbols: impl Iterator, +) -> HashMap { + use typst::layout::Frame; + use typst::layout::FrameItem; + + struct Worker { + res: HashMap, + } + + impl Worker { + fn work<'a>( + &mut self, + paged_doc: &TypstPagedDocument, + symbols: impl Iterator, + ) { + for (pg, s) in paged_doc.pages.iter().zip(symbols) { + self.work_frame(&pg.frame, s); + } + } + + fn work_frame(&mut self, k: &Frame, active: &str) { + for (_, item) in k.items() { + let text = match item { + FrameItem::Group(g) => { + self.work_frame(&g.frame, active); + continue; + } + FrameItem::Text(text) => text, + _ => continue, + }; + + let font = text.font.clone(); + for g in &text.glyphs { + let g_text = &text.text[g.range()]; + let chars_count = g_text.chars().count(); + if chars_count > 1 { + log::warn!("multi char glyph: {g_text}"); + continue; + } + let Some(ch) = g_text.chars().next() else { + continue; + }; + if ch.is_whitespace() { + continue; + } + log::debug!("glyph: {active} => {ch} ({chc:x})", chc = ch as u32); + self.res + .insert(active.to_owned(), (font.clone(), GlyphId(g.id))); + } + } + } + } + + let mut worker = Worker { + res: HashMap::new(), + }; + worker.work(doc, symbols); + worker.res +} + +fn render_glyphs( + symbols: &BTreeMap, + glyph_mapping: &HashMap, +) -> LspResult> { + let glyph_provider = reflexo_vec2svg::GlyphProvider::default(); + let glyph_pass = reflexo_typst::vector::pass::ConvertInnerImpl::new(glyph_provider, false); + + let mut builder = SvgGlyphBuilder::new(); + + let mut render_sym = |u| { + let (font, id) = glyph_mapping.get(u)?.clone(); + + let glyph = glyph_pass.must_flat_glyph(&GlyphItem::Raw(font.clone(), id))?; + + let rendered = builder.render_glyph("", &glyph)?; // the glyph_id does not matter here + + Some(create_display_svg(&font, id, &rendered)) + }; + + let rendered_symbols = symbols + .iter() + .map(|(k, v)| ResourceSymbolItem { + id: k.clone(), + category: v.category, + unicode: v.unicode, + glyph: render_sym(k), + }) + .collect(); + + Ok(rendered_symbols) +} + +fn create_display_svg(font: &TypstFont, gid: GlyphId, svg_path: &str) -> String { + let face = font.ttf(); + + let (x_min, x_max) = face + .glyph_bounding_box(gid) + .map(|bbox| (bbox.x_min as f32, bbox.x_max as f32)) + .unwrap_or_default(); + + // Font-wide metrics + let units_per_em = font.metrics().units_per_em as f32; + let ascender = font.metrics().ascender.get() as f32 * units_per_em; + let descender = font.metrics().descender.get() as f32 * units_per_em; // usually negative + + // Horizontal advance (fallback to em) + let x_advance = face + .glyph_hor_advance(gid) + .map(f32::from) + .unwrap_or(units_per_em); + + // Start viewBox.x at left-most ink or 0, whichever is smaller (to include left overhang) + let view_x = x_min.min(0.0); + + // Start view width as the advance; enlarge if ink extends past that + let view_w = x_advance.max(x_max - view_x); + + // Vertical viewBox uses font ascender/descent so baseline is at y=0 + let view_y = -ascender; + let view_h = ascender - descender; // ascender - (negative descender) -> total height + + let svg_content = format!( + r#" +{svg_path} +"# + ); + + svg_content } diff --git a/tools/editor-tools/src/features/symbol-view/components/symbol-picker.ts b/tools/editor-tools/src/features/symbol-view/components/symbol-picker.ts index 1523c51a..60be21e3 100644 --- a/tools/editor-tools/src/features/symbol-view/components/symbol-picker.ts +++ b/tools/editor-tools/src/features/symbol-view/components/symbol-picker.ts @@ -38,7 +38,7 @@ export const SymbolCell = (sym: SymbolItem) => { const fallback = () => { const key = stripSymPrefix(sym.id); - return span(NOPRINT_SYMBOLS[key] ?? key); + return span({ class: "symbol-glyph" }, NOPRINT_SYMBOLS[key] ?? key); }; const symbolName = stripSymPrefix(sym.id); @@ -50,7 +50,7 @@ export const SymbolCell = (sym: SymbolItem) => { title: `Click to insert: ${symbolName}`, onclick: handleClick, }, - div({ class: "symbol-glyph" }, sym.rendered ?? fallback()), + (sym.glyph && div({ class: "symbol-glyph", innerHTML: sym.glyph })) ?? fallback(), div( { class: "symbol-details" }, div( diff --git a/tools/editor-tools/src/features/symbol-view/index.ts b/tools/editor-tools/src/features/symbol-view/index.ts index 9adf2494..7edf70fd 100644 --- a/tools/editor-tools/src/features/symbol-view/index.ts +++ b/tools/editor-tools/src/features/symbol-view/index.ts @@ -6,7 +6,6 @@ import { CanvasPanel } from "./components/canvas-panel"; import { SymbolPicker } from "./components/symbol-picker"; import { SearchBar, ViewModeToggle } from "./components/toolbox"; import { useDetypifyFilter } from "./detypify-filter"; -import { prerenderSymbols } from "./render"; import { useSymbolSearch } from "./search-filter"; import type { SymbolItem, SymbolResource } from "./symbols"; @@ -18,15 +17,14 @@ function useSymbolResource() { const symbols = van.state( symbolInformationData.startsWith(":") ? [] - : prerenderSymbols(JSON.parse(base64Decode(symbolInformationData))), + : (JSON.parse(base64Decode(symbolInformationData)) as SymbolResource).symbols, ); if (import.meta.env.DEV) { // Dynamically import mock data in development mode if no real data is present import("./mock-data.json").then((json) => { const symbolResource = json as SymbolResource; - symbols.val = prerenderSymbols(symbolResource); - console.log("symbols", symbols.val); + symbols.val = symbolResource.symbols; }); } diff --git a/tools/editor-tools/src/features/symbol-view/mock-data.json b/tools/editor-tools/src/features/symbol-view/mock-data.json index 14a157c2..fc30ca6e 100644 --- a/tools/editor-tools/src/features/symbol-view/mock-data.json +++ b/tools/editor-tools/src/features/symbol-view/mock-data.json @@ -1,148 +1,117 @@ { - "fontSelects": [ + "symbols": [ { - "ascender": 0.72802734375, - "capHeight": 0.7001953125, - "descender": -0.21044921875, - "family": "Segoe UI Symbol", - "unitsPerEm": 2048 - }, - { - "ascender": 0.8059999942779541, - "capHeight": 0.6830000281333923, - "descender": -0.1940000057220459, - "family": "New Computer Modern Math", - "unitsPerEm": 1000 - } - ], - "glyphDefs": { - "gAQAAAHU": "", - "gAQAAABQD": "", - "gAQAAAGw": "" - }, - "symbols": { - "sym.acute": { "category": "accent", - "glyphs": [ - { - "fontIndex": 1, - "name": "acute", - "shape": "gAQAAAHU", - "xAdvance": 500, - "xMax": 374, - "xMin": 187, - "yAdvance": null, - "yMax": 698, - "yMin": 509 - } - ], + "glyph": "\n\n", + "id": "sym.acute", "unicode": 180 }, - "sym.Im": { + { "category": "misc", - "glyphs": [ - { - "fontIndex": 1, - "name": "uni2111", - "shape": "gAQAAABQD", - "xAdvance": 554, - "xMax": 534, - "xMin": 27, - "yAdvance": null, - "yMax": 686, - "yMin": -27 - } - ], + "glyph": "\n\n", + "id": "sym.Im", "unicode": 8465 }, - "sym.quote.angle.l.double": { + { "category": "quote", - "glyphs": [ - { - "fontIndex": 1, - "name": "guillemotleft", - "shape": "gAQAAAGw", - "xAdvance": 556, - "xMax": 444, - "xMin": 111, - "yAdvance": null, - "yMax": 483, - "yMin": 0 - } - ], + "glyph": "\n\n", + "id": "sym.quote.angle.l.double", "unicode": 171 }, - - "sym.lrm": { + { "category": "control", - "glyphs": [], + "id": "sym.lrm", "unicode": 8206 }, - "sym.rlm": { + { "category": "control", - "glyphs": [], + "id": "sym.rlm", "unicode": 8207 }, - "sym.wj": { + { "category": "control", - "glyphs": [], + "id": "sym.wj", "unicode": 8288 }, - "sym.zwj": { + { "category": "control", - "glyphs": [], + "id": "sym.zwj", "unicode": 8205 }, - "sym.zwnj": { + { "category": "control", - "glyphs": [], + "id": "sym.zwnj", "unicode": 8204 }, - "sym.zws": { + { "category": "control", - "glyphs": [], + "id": "sym.zws", "unicode": 8203 }, - - "sym.space": { + { "category": "space", - "glyphs": [], + "id": "sym.space", "unicode": 32 }, - "sym.space.en": { + { "category": "space", - "glyphs": [], + "id": "sym.space.en", "unicode": 8194 }, - "sym.space.med": { + { "category": "space", - "glyphs": [], + "id": "sym.space.fig", + "unicode": 8199 + }, + { + "category": "space", + "id": "sym.space.hair", + "unicode": 8202 + }, + { + "category": "space", + "id": "sym.space.med", "unicode": 8287 }, - "sym.space.nobreak": { + { "category": "space", - "glyphs": [], + "id": "sym.space.nobreak", "unicode": 160 }, - "sym.space.nobreak.narrow": { + { "category": "space", - "glyphs": [], + "id": "sym.space.nobreak.narrow", "unicode": 8239 }, - "sym.space.quad": { + { "category": "space", - "glyphs": [], + "id": "sym.space.punct", + "unicode": 8200 + }, + { + "category": "space", + "id": "sym.space.quad", "unicode": 8195 }, - "sym.space.quarter": { + { "category": "space", - "glyphs": [], + "id": "sym.space.quarter", "unicode": 8197 }, - "sym.space.thin": { + { "category": "space", - "glyphs": [], + "id": "sym.space.sixth", + "unicode": 8198 + }, + { + "category": "space", + "id": "sym.space.thin", "unicode": 8201 + }, + { + "category": "space", + "id": "sym.space.third", + "unicode": 8196 } - } + ] } diff --git a/tools/editor-tools/src/features/symbol-view/render.ts b/tools/editor-tools/src/features/symbol-view/render.ts deleted file mode 100644 index 785924d0..00000000 --- a/tools/editor-tools/src/features/symbol-view/render.ts +++ /dev/null @@ -1,64 +0,0 @@ -import van from "vanjs-core"; -import type { FontItem, GlyphDesc, SymbolItem, SymbolResource } from "./symbols"; - -const { div } = van.tags; - -function renderSymbol( - mask: HTMLElement, - primaryGlyph: GlyphDesc, - fontSelected: FontItem, - path: string, -) { - const diff = (min?: number | null, max?: number | null) => { - return Math.abs((max ?? 0) - (min ?? 0)); - }; - - const bboxXWidth = diff(primaryGlyph.xMin, primaryGlyph.xMax); - const xWidth = Math.max(bboxXWidth, primaryGlyph.xAdvance ?? fontSelected.unitsPerEm); - - const yReal = diff(primaryGlyph.yMin, primaryGlyph.yMax); - const yGlobal = primaryGlyph.yAdvance ?? fontSelected.unitsPerEm; - const yWidth = Math.max(yReal, yGlobal); - - // keep viewBox in glyph units - const viewBox = `0 0 ${xWidth} ${yWidth}`; - - const yShift = - yReal >= yGlobal - ? Math.abs(primaryGlyph.yMax ?? 0) - : (Math.abs(primaryGlyph.yMax ?? 0) + yWidth) / 2; - - // Center the symbol horizontally - const xShift = -(primaryGlyph.xMin ?? 0) + (xWidth - bboxXWidth) / 2; - - const imageData = ` -${path} -`; - - mask.style.maskImage = `url('data:image/svg+xml;utf8,${encodeURIComponent(imageData)}')`; - - return mask; -} - -export function prerenderSymbols(symRes: SymbolResource): SymbolItem[] { - return Object.entries(symRes.symbols).map(([id, sym]) => { - const primaryGlyph = sym.glyphs[0]; - const mask = div(); - const renderedSym: SymbolItem = { - id, - category: sym.category, - unicode: sym.unicode, - rendered: primaryGlyph ? mask : undefined, - }; - if (primaryGlyph?.fontIndex && primaryGlyph?.shape) { - const fontSelected = symRes.fontSelects[primaryGlyph.fontIndex]; - if (fontSelected) { - const glyphPath = (primaryGlyph.shape && symRes.glyphDefs[primaryGlyph.shape]) ?? ""; - setTimeout(() => { - renderSymbol(mask, sym.glyphs[0], fontSelected, glyphPath); - }); - } - } - return renderedSym; - }); -} diff --git a/tools/editor-tools/src/features/symbol-view/styles.css b/tools/editor-tools/src/features/symbol-view/styles.css index 0b43b966..5376623d 100644 --- a/tools/editor-tools/src/features/symbol-view/styles.css +++ b/tools/editor-tools/src/features/symbol-view/styles.css @@ -25,8 +25,8 @@ flex-direction: column; justify-content: center; align-items: center; - width: 2rem; - height: 2rem; + width: 2.5rem; + height: 2.5rem; border: 1px solid transparent; border-radius: 0.25rem; background: var(--vscode-list-inactiveSelectionBackground, rgba(201, 209, 217, 0.15)); @@ -35,6 +35,10 @@ transition: all 0.2s ease; } +.symbol-cell:hover { + overflow: visible +} + .symbol-picker.detailed .symbol-cell { width: 4rem; height: 4rem; @@ -62,21 +66,28 @@ } /* Symbol glyph mask */ -.symbol-glyph > div { - width: 1.75rem; - height: 1.75rem; - mask-repeat: no-repeat; - mask-position: center; - background-color: currentColor; +div.symbol-glyph svg { + width: 2rem; + height: 2rem; + fill: currentColor; + transition: fill 200ms; +} + +.symbol-cell:hover div.symbol-glyph svg { + fill: var(--vscode-button-hoverBackground, #1177bb); } /* Symbol glyph fallback text */ -.symbol-glyph > span { +span.symbol-glyph { font-size: 0.75rem; font-style: italic; font-weight: 500; } +.symbol-cell:hover span.symbol-glyph { + color: var(--vscode-button-hoverBackground, #1177bb); +} + /* Symbol details */ .symbol-details { display: flex; diff --git a/tools/editor-tools/src/features/symbol-view/symbols.ts b/tools/editor-tools/src/features/symbol-view/symbols.ts index 04e22e29..6dda1c9f 100644 --- a/tools/editor-tools/src/features/symbol-view/symbols.ts +++ b/tools/editor-tools/src/features/symbol-view/symbols.ts @@ -33,7 +33,7 @@ export const CATEGORY_NAMES = { export type SymbolCategory = keyof typeof CATEGORY_NAMES; -export const NOPRINT_SYMBOLS: { [key: string]: string } = { +export const NOPRINT_SYMBOLS: Record = { space: "␣", "space.en": "ensp", "space.quad": "emsp", @@ -50,45 +50,17 @@ export const NOPRINT_SYMBOLS: { [key: string]: string } = { zws: "zwsp", }; -export interface GlyphDesc { - fontIndex: number | null; - xAdvance?: number | null; - yAdvance?: number | null; - xMin?: number | null; - yMin?: number | null; - xMax?: number | null; - yMax?: number | null; - name?: string | null; - shape?: string | null; -} - export type SymbolId = string; export interface SymbolItem { id: SymbolId; category: SymbolCategory; unicode: number; - rendered?: HTMLElement; -} - -export interface FontItem { - family: string; - capHeight: number; - ascender: number; - descender: number; - unitsPerEm: number; -} - -export interface RawSymbolItem { - category: SymbolCategory; - unicode: number; - glyphs: GlyphDesc[]; + glyph?: string; } export interface SymbolResource { - symbols: Record; - fontSelects: FontItem[]; - glyphDefs: Record; + symbols: SymbolItem[]; } export function stripSymPrefix(name: string): string {