C14.14 r2: links judged as browsers read them; only a folder this export made is removed; formula text outlined or refused (library 3bbbd05)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
88b0f8f5fc
commit
bfd507ae2e
10 files changed
+87
-16
No files matched your search
Generated
+1
-1
@@ -1922,7 +1922,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ely-gpui-component"
|
||||
version = "0.1.1"
|
||||
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=ff9c0f88d3b7975a316f6869b5d250200178885a#ff9c0f88d3b7975a316f6869b5d250200178885a"
|
||||
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=3bbbd05f381178b92fe7395eab9436647f69f9b4#3bbbd05f381178b92fe7395eab9436647f69f9b4"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ jiff = "0.2"
|
||||
anyhow = "1"
|
||||
directories = "6"
|
||||
dunce = "1"
|
||||
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "ff9c0f88d3b7975a316f6869b5d250200178885a" }
|
||||
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "3bbbd05f381178b92fe7395eab9436647f69f9b4" }
|
||||
env_logger = "0.11"
|
||||
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false, features = ["stacker"] }
|
||||
gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", features = ["font-kit", "wayland", "x11"] }
|
||||
|
||||
@@ -40,7 +40,7 @@ site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木
|
||||
|
||||
## 组件映射
|
||||
|
||||
依赖 `ely-gpui-component`(pin ff9c0f8,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
|
||||
依赖 `ely-gpui-component`(pin 3bbbd05,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
|
||||
|
||||
| 需要 | 用 |
|
||||
| --- | --- |
|
||||
@@ -283,4 +283,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-11:Linux 包的实测(L1.6)。本机(Omarchy,Hyprland 0.56)装上六个 Arch 包与六个 AppImage,`linux-installed.sh` 两种都过;deb、rpm、Arch 包在 Ubuntu 26.04、Fedora 44、Arch 容器里装上、全部 ELF 找得到库;deb 与 rpm 的六个应用在各自容器的 Xvfb 下画出窗口(`linux-distros.sh`)。截图按窗口(`grim -T` 加 Hyprland 的 `stableId`):按区域截时用户切换工作区或别的窗口盖住即截错。ElyMark 的文件监视略去读取后不会自激:重读前先比指纹(长度与修改时间),不打开文件。
|
||||
- 2026-10-11(用户定:替换 v0.1.3 的 Linux 包):v0.1.3 发布里原有的六个 AppImage(无头,打不开窗口)已删去,换成本机容器里建出的六个应用各四种包(共 24 个),出自 L1.7 的提交(晚于该标签);上传后逐个核对大小,ElyTree 的 AppImage 下载回来散列相同。`package.sh` 把每个 RUNPATH 按所在 ELF 解析(`$ORIGIN` 或 `${ORIGIN}` 开头,规范化后须在 AppDir 内),否则拒绝(如未修正的 cairo):CI 用的是别处建好的引擎,旧构建会在这里报错而不是带进包里。替换的 Linux 包对应的源码随之上传到同一发布(`source.sh`,文件名带构建提交号),发布说明写明 Linux 包出自哪个提交。
|
||||
- 2026-10-11:`ci.sh` 用 `ELY_SHARE` 时,CI 克隆里的 `engines`、`reference` 须是指向共享克隆的链接(Windows 为目录联接,不查);是本克隆自建的目录或指向别处即报错,不混用。`.gitignore` 的 `/engines`、`/reference` 不带斜杠,链接也被忽略(`source.sh` 要求工作区干净)。
|
||||
- 2026-10-11(用户定:ElyMark 不用 CEF,不用 Electron):ElyMark 改为原生。界面用组件库的 Markdown 组件(`documents::MarkdownEditor` 一族,缺的在组件库补并推到 Gitea),解析用 markdown-rs(`markdown` 1.0.0),公式用 RaTeX,Mermaid 用 mermaid-rs-renderer,PDF 用 MuPDF Story;CEF 随之删去,ElyMail 的正文也不用 CEF 与 Electron。进度在 `tasks/TASKS.md` 的 C14。组件库 fcad0ff 与 508cc55:`MarkdownRenderer`、`MarkdownEditor`、Markdown 着色、块范围与链接查找改读 markdown-rs 的语法树(GitHub 风格加公式),引用式链接与图片按定义解析,图片把所在段落分开,单行 `$$…$$` 为块公式,软换行(LF、CRLF、CR)读作空格,嵌套在引用与列表里的定义同样解析,强调与链接里的图片仍是图片(链接里的图片可按);`BlockEditor` 的导入导出不属 ElyMark,仍用 pulldown-cmark。markdown-rs 读嵌套图片时只留外层的说明。组件库 d5ecc5a:块公式经 `typography::Latex` 由 RaTeX 排出(字形为轮廓,按窗口缩放画),```` ```mermaid ```` 块经 `documents::Diagram` 由 mermaid-rs-renderer 画出(Mermaid 自己的深浅主题,底色与字色取 Ely 色板),都作为 gpui 资源在后台排版,RaTeX 或 Mermaid 对已解析的输入 panic 时显示为错误;`MarkdownRenderer::base` 给出文档所在文件夹,相对与 file 地址的图片从中读取并按自身大小、不超过栏宽显示,`data:` 地址解码,网络图片不载入(写明)。组件库 ab9a14c:每个公式、图与图片元素经 `primitives::held` 持有其资源,按资源计数持有者(8b9820b;画出的图存在共享的记录里,9036c92 起由最后离开的持有者释放,哪怕它自己还没收到图),最后一个持有者换了内容或离开窗口才从 gpui 资源缓存移除、在本帧之后从图集释放,共用同一内容的元素(含 gpui 缓存的视图)不受影响;公式按字形实际范围取画框,字形不被裁;公式失败显示原因、RaTeX 给的位置与原文,日志不含公式;`file:` 地址去掉查询与片段,`localhost` 为本机,Windows 上别的主机为共享路径,他处拒绝;Mermaid 的节点与饼图文字取 Ely 的前景色。组件库 2b6f551:`documents::MarkdownDocument` 是文件级的 Markdown 文档:全文与撤销在一个 `CodeEditor` 里(`CodeEditor::highlighter` 以 `markdown_highlights` 着色,无行号);可视以 gpui `list` 逐块画出文件的顶层块(链接定义、HTML、front matter 照原文显示,每个字节都在块里或块间),按下的块开成源码(`TextInput`),其键入即刻算作文档的文字,写回为一步撤销,上下键越过块边进入相邻块,Esc 写回;Cmd(他处 Ctrl)按下交给链接;预览只读;源码为编辑器本身;128 KiB 以内当场解析,更长的在后台解析,解析中按下块不打开(日志);新解析保留两端相同的块,滚动不跳。`CodeEditor` 的撤销历史最多留 64 MiB 文字,超出先丢最旧的步;`CodeEditor::reset` 换文字且撤销重新开始;`MarkdownMode` 加 `Preview`。组件库 fc181b4:一个插入点通用于各模式(编辑器的选区,写回的块移动它);`set_caret` 与 `set_top_line` 分别设插入点与视图,换模式保持顶行;按下的块在前一块写回后仍是所按的块(按偏移换算,结构变了即不开并记日志);块是 Tab 停靠点,Enter 或空格打开;预览不收按下、撤销与重做;撤销与重做每次记录、输入法组字、撤销、重做后都限在 64 MiB 内(先丢最旧的撤销,再丢最远的重做)。组件库 433c38f:只有对当下文字的解析才能打开块(解析的文字与编辑器相同),否则拒绝;要求的顶行在其文字解析后才落定:先到所在块,下一帧按块高的比例到块内位置,再有新的要求即接着放下一个;源码与列表之间切换保持顶行,可视与预览共用一个列表,切换不动滚动。组件库 abac8b4:顶行的要求带编号,迟到的帧回调只完成自己的那个;文字一换即按新文字重新放,放时按当下文字的行数夹住;`reveal` 是同样的要求(插入点设在该行,该行放到顶上),新文字解析后才落定。组件库 f59c9f7:文字超过读取上限(2 MiB)即丢掉旧的读取,大纲与块为空。ElyMark(C14.4):视图即一个 `MarkdownDocument`,文本即文件原文(BOM 与 CRLF 照 `text.rs` 存回),新建、打开、保存、另存、重命名经外壳照旧;视图在建成后的下一轮更新向外壳报一次载入(文档在 `new` 里当场解析,外壳此时还未订阅);文字超过 2 MiB 即只用源码并转去源码(随当前文字,不只看打开时);网页实现的查找替换、表格、附件、书写视图的应用、导出与打印Line truncated
|
||||
- 2026-10-11(用户定:ElyMark 不用 CEF,不用 Electron):ElyMark 改为原生。界面用组件库的 Markdown 组件(`documents::MarkdownEditor` 一族,缺的在组件库补并推到 Gitea),解析用 markdown-rs(`markdown` 1.0.0),公式用 RaTeX,Mermaid 用 mermaid-rs-renderer,PDF 用 MuPDF Story;CEF 随之删去,ElyMail 的正文也不用 CEF 与 Electron。进度在 `tasks/TASKS.md` 的 C14。组件库 fcad0ff 与 508cc55:`MarkdownRenderer`、`MarkdownEditor`、Markdown 着色、块范围与链接查找改读 markdown-rs 的语法树(GitHub 风格加公式),引用式链接与图片按定义解析,图片把所在段落分开,单行 `$$…$$` 为块公式,软换行(LF、CRLF、CR)读作空格,嵌套在引用与列表里的定义同样解析,强调与链接里的图片仍是图片(链接里的图片可按);`BlockEditor` 的导入导出不属 ElyMark,仍用 pulldown-cmark。markdown-rs 读嵌套图片时只留外层的说明。组件库 d5ecc5a:块公式经 `typography::Latex` 由 RaTeX 排出(字形为轮廓,按窗口缩放画),```` ```mermaid ```` 块经 `documents::Diagram` 由 mermaid-rs-renderer 画出(Mermaid 自己的深浅主题,底色与字色取 Ely 色板),都作为 gpui 资源在后台排版,RaTeX 或 Mermaid 对已解析的输入 panic 时显示为错误;`MarkdownRenderer::base` 给出文档所在文件夹,相对与 file 地址的图片从中读取并按自身大小、不超过栏宽显示,`data:` 地址解码,网络图片不载入(写明)。组件库 ab9a14c:每个公式、图与图片元素经 `primitives::held` 持有其资源,按资源计数持有者(8b9820b;画出的图存在共享的记录里,9036c92 起由最后离开的持有者释放,哪怕它自己还没收到图),最后一个持有者换了内容或离开窗口才从 gpui 资源缓存移除、在本帧之后从图集释放,共用同一内容的元素(含 gpui 缓存的视图)不受影响;公式按字形实际范围取画框,字形不被裁;公式失败显示原因、RaTeX 给的位置与原文,日志不含公式;`file:` 地址去掉查询与片段,`localhost` 为本机,Windows 上别的主机为共享路径,他处拒绝;Mermaid 的节点与饼图文字取 Ely 的前景色。组件库 2b6f551:`documents::MarkdownDocument` 是文件级的 Markdown 文档:全文与撤销在一个 `CodeEditor` 里(`CodeEditor::highlighter` 以 `markdown_highlights` 着色,无行号);可视以 gpui `list` 逐块画出文件的顶层块(链接定义、HTML、front matter 照原文显示,每个字节都在块里或块间),按下的块开成源码(`TextInput`),其键入即刻算作文档的文字,写回为一步撤销,上下键越过块边进入相邻块,Esc 写回;Cmd(他处 Ctrl)按下交给链接;预览只读;源码为编辑器本身;128 KiB 以内当场解析,更长的在后台解析,解析中按下块不打开(日志);新解析保留两端相同的块,滚动不跳。`CodeEditor` 的撤销历史最多留 64 MiB 文字,超出先丢最旧的步;`CodeEditor::reset` 换文字且撤销重新开始;`MarkdownMode` 加 `Preview`。组件库 fc181b4:一个插入点通用于各模式(编辑器的选区,写回的块移动它);`set_caret` 与 `set_top_line` 分别设插入点与视图,换模式保持顶行;按下的块在前一块写回后仍是所按的块(按偏移换算,结构变了即不开并记日志);块是 Tab 停靠点,Enter 或空格打开;预览不收按下、撤销与重做;撤销与重做每次记录、输入法组字、撤销、重做后都限在 64 MiB 内(先丢最旧的撤销,再丢最远的重做)。组件库 433c38f:只有对当下文字的解析才能打开块(解析的文字与编辑器相同),否则拒绝;要求的顶行在其文字解析后才落定:先到所在块,下一帧按块高的比例到块内位置,再有新的要求即接着放下一个;源码与列表之间切换保持顶行,可视与预览共用一个列表,切换不动滚动。组件库 abac8b4:顶行的要求带编号,迟到的帧回调只完成自己的那个;文字一换即按新文字重新放,放时按当下文字的行数夹住;`reveal` 是同样的要求(插入点设在该行,该行放到顶上),新文字解析后才落定。组件库 f59c9f7:文字超过读取上限(2 MiB)即丢掉旧的读取,大纲与块为空。ElyMark(C14.4):视图即一个 `MarkdownDocument`,文本即文件原文(BOM 与 CRLF 照 `text.rs` 存回),新建、打开、保存、另存、重命名经外壳照旧;视图在建成后的下一轮更新向外壳报一次载入(文档在 `new` 里当场解析,外壳此时还未订阅);文字超过 2 MiB 即只用源码并转去源码(随当前文字,不只看打开时);网页实现的查找替换、表格、附件、书写视图的应用、导出与打印Line truncated
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use super::{Page, Pictures, body, document, plain};
|
||||
use super::{Page, Pictures, body, document, page_of, pictures::Placed, plain};
|
||||
|
||||
/// What an export writes.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -64,13 +64,13 @@ fn export(target: &Target, bytes: &[u8]) -> io::Result<()> {
|
||||
/// The page beside a new folder of its pictures.
|
||||
fn linked(text: &str, beside: Option<&Path>, target: &Target, title: &str) -> io::Result<Page> {
|
||||
let folder = target.path.with_file_name(format!("{title}_files"));
|
||||
let found = folder.exists();
|
||||
let written = body(text, beside, &Pictures::Folder(folder.clone()))
|
||||
let pictures = Pictures::Folder(folder.clone());
|
||||
let mut placed = Placed::new(beside, &pictures);
|
||||
let written = page_of(text, &mut placed)
|
||||
.and_then(|page| export(target, document(title, &page.html).as_bytes()).map(|()| page));
|
||||
// A failure takes only the folder it made.
|
||||
if written.is_err()
|
||||
&& !found
|
||||
&& folder.exists()
|
||||
&& placed.made
|
||||
&& let Err(left) = std::fs::remove_dir_all(&folder)
|
||||
{
|
||||
log::error!("mark: the half-made picture folder stays: {left}");
|
||||
|
||||
@@ -258,9 +258,15 @@ fn bare(svg: &str) -> &str {
|
||||
svg.find("<svg").map_or(svg, |at| &svg[at..])
|
||||
}
|
||||
|
||||
/// A plain, web or mail address.
|
||||
/// A plain, web or mail address, as a browser reads it.
|
||||
fn safe(url: &str) -> bool {
|
||||
match scheme(url) {
|
||||
// Browsers trim spaces and controls, then drop tabs and newlines.
|
||||
let read: String = url
|
||||
.trim_matches(|ch: char| ch <= ' ')
|
||||
.chars()
|
||||
.filter(|ch| !matches!(ch, '\t' | '\n' | '\r'))
|
||||
.collect();
|
||||
match scheme(&read) {
|
||||
None => true,
|
||||
Some(scheme) => ["http", "https", "mailto"].contains(&scheme.to_lowercase().as_str()),
|
||||
}
|
||||
|
||||
@@ -37,11 +37,15 @@ img,svg{max-width:100%}div.math{text-align:center}span.math svg{vertical-align:m
|
||||
|
||||
/// `text`'s body as HTML; pictures go as `pictures` says.
|
||||
pub fn body(text: &str, beside: Option<&Path>, pictures: &Pictures) -> io::Result<Page> {
|
||||
page_of(text, &mut pictures::Placed::new(beside, pictures))
|
||||
}
|
||||
|
||||
/// `text`'s body; `placed` keeps what it copied.
|
||||
fn page_of(text: &str, placed: &mut pictures::Placed) -> io::Result<Page> {
|
||||
let tree = markdown::to_mdast(text, &markdown_options())
|
||||
.map_err(|error| io::Error::other(error.to_string()))?;
|
||||
let mut page = Page::default();
|
||||
let mut placed = pictures::Placed::new(beside, pictures);
|
||||
html::write(&tree, &mut placed, &mut page)?;
|
||||
html::write(&tree, placed, &mut page)?;
|
||||
log::info!(
|
||||
"mark: a page of {} bytes, {} pictures copied, {} missing, {} diagrams and {} formulas as code",
|
||||
page.html.len(),
|
||||
|
||||
@@ -36,6 +36,8 @@ pub(super) struct Placed<'a> {
|
||||
pictures: &'a Pictures,
|
||||
/// Pictures copied so far, and their names.
|
||||
pub copied: Vec<(PathBuf, String)>,
|
||||
/// This export made the picture folder.
|
||||
pub made: bool,
|
||||
}
|
||||
|
||||
impl<'a> Placed<'a> {
|
||||
@@ -44,6 +46,7 @@ impl<'a> Placed<'a> {
|
||||
beside,
|
||||
pictures,
|
||||
copied: Vec::new(),
|
||||
made: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,8 +120,8 @@ impl<'a> Placed<'a> {
|
||||
}
|
||||
|
||||
/// Copies `source` under a name still free.
|
||||
fn copy(&self, source: &mut File, path: &Path, folder: &Path) -> io::Result<String> {
|
||||
if self.copied.is_empty() {
|
||||
fn copy(&mut self, source: &mut File, path: &Path, folder: &Path) -> io::Result<String> {
|
||||
if !self.made {
|
||||
// A folder already there is someone's: refused.
|
||||
std::fs::create_dir(folder).map_err(|error| match error.kind() {
|
||||
io::ErrorKind::AlreadyExists => {
|
||||
@@ -126,6 +129,7 @@ impl<'a> Placed<'a> {
|
||||
}
|
||||
_ => error,
|
||||
})?;
|
||||
self.made = true;
|
||||
}
|
||||
let file = path
|
||||
.file_name()
|
||||
|
||||
@@ -403,3 +403,53 @@ mod files {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_disguised_script_link_does_not_follow() {
|
||||
let text = "[a](< javascript:alert(1)>) [b](java	script:alert(1)) [c](java script:alert(1)) [d](< https://x.org>)\n";
|
||||
let html = page(text, None, &Pictures::Inline).html;
|
||||
assert_eq!(html.matches("<a>").count(), 3, "{html}");
|
||||
assert!(html.contains("href=\" https://x.org\""), "{html}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_linked_page_takes_the_folder_it_made() {
|
||||
let dir = tempfile::tempdir().expect("a dir");
|
||||
std::fs::write(dir.path().join("dot.png"), b"png").expect("writes");
|
||||
// A folder where the page goes: the page cannot be written.
|
||||
let out = dir.path().join("out.html");
|
||||
std::fs::create_dir(&out).expect("makes");
|
||||
let target = super::Target {
|
||||
path: &out,
|
||||
session: None,
|
||||
revision: 1,
|
||||
work: dir.path(),
|
||||
};
|
||||
super::write(
|
||||
super::Format::Linked,
|
||||
"\n",
|
||||
Some(dir.path()),
|
||||
&target,
|
||||
)
|
||||
.expect_err("fails");
|
||||
assert!(
|
||||
!dir.path().join("out_files").exists(),
|
||||
"the folder it made is gone"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_formula_letter_no_font_has_stays_as_tex() {
|
||||
let page = page(
|
||||
"Before\n\n$\text{\u{F0000}}$ after.\n",
|
||||
None,
|
||||
&Pictures::Inline,
|
||||
);
|
||||
let lines: Vec<usize> = page.formulas.iter().map(|(line, _)| *line).collect();
|
||||
assert_eq!(lines, [3]);
|
||||
assert!(
|
||||
page.html.contains("<code>\text{\u{F0000}}</code>"),
|
||||
"{}",
|
||||
page.html
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ fetch() { # name url rev [sparse paths...]
|
||||
echo "fetch $name $rev"
|
||||
}
|
||||
|
||||
fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git ff9c0f88d3b7975a316f6869b5d250200178885a
|
||||
fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git 3bbbd05f381178b92fe7395eab9436647f69f9b4
|
||||
fetch zed https://github.com/zed-industries/zed.git 1a28cff4b409169bac058bca40dfbfeb7621d19b
|
||||
fetch libreoffice-core https://github.com/LibreOffice/core.git 8221e31b3ac356a1623c672912a3d2b492f7e3d1 \
|
||||
include/LibreOfficeKit libreofficekit desktop/source/lib desktop/inc \
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
FAIL
|
||||
|
||||
- apps/ely-mark/src/output/html.rs:270 — Scheme detection treats whitespace in a scheme as absence of a scheme, which `safe` allows. `[run](< javascript:alert(1)>)` exports a live JavaScript link; `java	script:` and `java script:` also bypass the filter because browsers remove those controls. The probes produce these hrefs without diagnostics, and URL normalization identifies all three as `javascript:`. Validate the browser-normalized address against the allowed schemes, rejecting executable and other disallowed schemes even when obfuscated. Evidence: `.work/C14.14-r2/windows-probe.log`, `linux-probe.log`, `linux-url-probe.log`.
|
||||
|
||||
- apps/ely-mark/src/output/files.rs:71 — Cleanup infers folder ownership from an earlier `exists()` check rather than successful creation. Start a linked export with many paragraphs before its first image, then export a short document to the same destination while the first is parsing. Both initially see no resource folder; the short export succeeds, but the first later gets `AlreadyExists` and deletes the successful export's folder. Native Windows and WSL both reproduce `folder_before_loser=true`, then `folder_after_loser=false` with the HTML still present. Track which operation actually created the resource folder and only let that operation clean it up. Evidence: `.work/C14.14-r2/windows-probe.log`, `linux-probe.log`.
|
||||
|
||||
- reference/Ely-GPUI-Components/src/typography/math.rs:120 (ff9c0f8) — `formula_svg` assumes RaTeX's `embed_glyphs` guarantees outlines, but RaTeX silently falls back to SVG text for unsupported glyphs. Exporting `Before $\text{𓀀}$ after.` succeeds with no formula diagnostic while the rendered PDF contains a missing-glyph box instead of the formula character. The returned SVG contains a `KaTeX_Main` text element rather than that glyph's outline. Convert remaining text to outlines with checked glyph coverage, or return `MathError` so ElyMark preserves the TeX and reports its line. Cover this fallback in export tests. Evidence: `.work/C14.14-r2/linux-formula.log`, `linux-formula-output.log`, `raster-verification.log`.
|
||||
Reference in new issue
Block a user