diff --git a/TeXmacs/plugins/goldfish/src/goldfish.hpp b/TeXmacs/plugins/goldfish/src/goldfish.hpp index 29c6be6afe..5769aae5cf 100644 --- a/TeXmacs/plugins/goldfish/src/goldfish.hpp +++ b/TeXmacs/plugins/goldfish/src/goldfish.hpp @@ -2473,6 +2473,28 @@ static s7_pointer f_os_temp_dir (s7_scheme* sc, s7_pointer args) { tb_char_t path[GOLDFISH_PATH_MAXN]; tb_directory_temporary (path, GOLDFISH_PATH_MAXN); +#ifdef TB_CONFIG_OS_WINDOWS + tb_wchar_t path_w[GOLDFISH_PATH_MAXN] = {0}; + tb_wchar_t path_long_w[GOLDFISH_PATH_MAXN]= {0}; + if (tb_atow (path_w, path, GOLDFISH_PATH_MAXN) != -1) { + tb_size_t path_len= tb_wcslen (path_w); + tb_bool_t had_sep = path_len > 0 + && (path_w[path_len - 1] == L'\\' || path_w[path_len - 1] == L'/'); + if (had_sep) path_w[path_len - 1]= L'\0'; + + DWORD long_len= GetLongPathNameW (path_w, path_long_w, GOLDFISH_PATH_MAXN); + if (long_len > 0 && long_len < GOLDFISH_PATH_MAXN) { + if (had_sep) { + path_long_w[long_len++] = L'\\'; + path_long_w[long_len] = L'\0'; + } + + tb_char_t path_long[GOLDFISH_PATH_MAXN]; + tb_size_t size= tb_wtoa (path_long, path_long_w, GOLDFISH_PATH_MAXN); + if (size != -1 && size > 0) return s7_make_string (sc, path_long); + } + } +#endif return s7_make_string (sc, path); } diff --git a/TeXmacs/plugins/tikz/goldfish/tm-tikz.scm b/TeXmacs/plugins/tikz/goldfish/tm-tikz.scm index 4f32b071a5..11933803df 100644 --- a/TeXmacs/plugins/tikz/goldfish/tm-tikz.scm +++ b/TeXmacs/plugins/tikz/goldfish/tm-tikz.scm @@ -22,6 +22,9 @@ (define (goldfish-quote s) (string-append "\"" (escape-string s) "\"")) +(define (windows-quote s) + (string-append "\"" s "\"")) + (define (tikz-welcome) (flush-prompt "tikz] ") (flush-verbatim "Liii STEM interface to TikZ")) @@ -35,10 +38,10 @@ (read-code "")) (define (gen-temp-path) - (let ((tikz-tmpdir (string-append (os-temp-dir) "/tikz"))) + (let ((tikz-tmpdir (string-append (os-temp-dir) (string (os-sep)) "tikz"))) (when (not (file-exists? tikz-tmpdir)) (mkdir tikz-tmpdir)) - (string-append tikz-tmpdir "/" (uuid4)))) + (string-append tikz-tmpdir (string (os-sep)) (uuid4)))) (define (wrap-tikz-code code) (let ((trimmed (string-trim-left code))) @@ -109,19 +112,24 @@ (lambda () (display code)))) (define (tikz-temp-dir) - (string-append (os-temp-dir) "/tikz")) + (string-append (os-temp-dir) (string (os-sep)) "tikz")) (define (run-pdflatex tex-path pdflatex-bin) - (let* ((inner-cmd (string-append (goldfish-quote pdflatex-bin) + (let* ((redirect (if (os-windows?) " > NUL 2>&1" "")) + (quote-fn (if (os-windows?) windows-quote goldfish-quote)) + (inner-cmd (string-append (quote-fn pdflatex-bin) " --interaction=errorstopmode -halt-on-error " - (goldfish-quote tex-path) - " > /dev/null 2>&1")) - (cmd (string-append "sh -c " (goldfish-quote inner-cmd))) + (quote-fn tex-path) + redirect)) + (cmd (if (os-windows?) + (string-append "cmd.exe /c call " inner-cmd) + (string-append "sh -c " (goldfish-quote inner-cmd)))) (orig-dir (getcwd))) - (unsetenv "DYLD_LIBRARY_PATH") - (unsetenv "DYLD_FRAMEWORK_PATH") - (unsetenv "DYLD_FALLBACK_LIBRARY_PATH") - (unsetenv "DYLD_FALLBACK_FRAMEWORK_PATH") + (when (os-macos?) + (unsetenv "DYLD_LIBRARY_PATH") + (unsetenv "DYLD_FRAMEWORK_PATH") + (unsetenv "DYLD_FALLBACK_LIBRARY_PATH") + (unsetenv "DYLD_FALLBACK_FRAMEWORK_PATH")) (chdir (tikz-temp-dir)) (let ((result (os-call cmd))) (chdir orig-dir) diff --git a/TeXmacs/plugins/tikz/tests/tm-tikz-test.scm b/TeXmacs/plugins/tikz/tests/tm-tikz-test.scm index dd113ec60e..644a95336b 100644 --- a/TeXmacs/plugins/tikz/tests/tm-tikz-test.scm +++ b/TeXmacs/plugins/tikz/tests/tm-tikz-test.scm @@ -15,6 +15,7 @@ (liii string) (liii list) (liii path) + (liii os) ) ; Simulate the wrap-tikz-code logic from tm-tikz.scm @@ -210,6 +211,14 @@ (check (parse-magic-line "% -width 0.8par -height") => (list "0.8par" "0px")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(check + (if (and (os-windows?) (string-contains? (getenv "TEMP" "") "~")) + (not (string-contains? (os-temp-dir) "~")) + #t) + => + #t) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; image-valid? helper ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -220,13 +229,13 @@ ;; Tests for image-valid? ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(define test-empty-path "/tmp/tikz-test-empty.txt") +(define test-empty-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-empty.txt")) (with-output-to-file test-empty-path (lambda () (display ""))) -(check (image-valid? "/tmp/nonexistent-file-12345.txt") => #f) +(check (image-valid? (string-append (os-temp-dir) (string (os-sep)) "nonexistent-file-12345.txt")) => #f) (check (image-valid? test-empty-path) => #f) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -265,11 +274,11 @@ ;; Tests for pdf-page-empty? ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(define test-log-empty-path "/tmp/tikz-test-log-empty.log") -(define test-log-valid-path "/tmp/tikz-test-log-valid.log") -(define test-log-vertical-path "/tmp/tikz-test-log-vertical.log") -(define test-log-horizontal-path "/tmp/tikz-test-log-horizontal.log") -(define test-log-point-path "/tmp/tikz-test-log-point.log") +(define test-log-empty-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-log-empty.log")) +(define test-log-valid-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-log-valid.log")) +(define test-log-vertical-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-log-vertical.log")) +(define test-log-horizontal-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-log-horizontal.log")) +(define test-log-point-path (string-append (os-temp-dir) (string (os-sep)) "tikz-test-log-point.log")) (with-output-to-file test-log-empty-path (lambda () diff --git a/devel/0309.md b/devel/0309.md new file mode 100644 index 0000000000..c068d3e1c7 --- /dev/null +++ b/devel/0309.md @@ -0,0 +1,110 @@ +# [0309] 修复 Windows 下 TikZ 图片无法回传到文档 + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `TeXmacs/plugins/goldfish/src/goldfish.hpp` +- `TeXmacs/plugins/tikz/goldfish/tm-tikz.scm` +- `TeXmacs/plugins/tikz/tests/tm-tikz-test.scm` +- `tests/System/Files/input_file_flush_test.cpp` + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +```bash +xmake b goldfish +xmake b input_file_flush_test +xmake r input_file_flush_test +TeXmacs/plugins/goldfish/bin/goldfish.exe TeXmacs/plugins/tikz/tests/tm-tikz-test.scm +``` + +### 3.2 非确定性测试(文档验证) +```bash +xmake b stem +xmake r stem +``` + +在 Windows + MiKTeX 环境中验证: + +1. 打开 TikZ 插件,输入一段不含中文的 TikZ 代码,确认图片能正常插入当前文档。 +2. 若系统 `TEMP`/`TMP` 环境变量为 `C:\Users\NAME~1\AppData\Local\Temp` 这类 8.3 短路径,确认插图流程仍然成功。 +3. 执行以下命令,确认 `os-temp-dir` 返回值不再包含 `~`: + +```bash +TeXmacs/plugins/goldfish/bin/goldfish.exe eval "(begin (import (liii os)) (display (os-temp-dir)) (newline))" +``` + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +xmake b goldfish +xmake b input_file_flush_test +xmake r input_file_flush_test +TeXmacs/plugins/goldfish/bin/goldfish.exe TeXmacs/plugins/tikz/tests/tm-tikz-test.scm +``` + +## 5 What + +修复 Windows 下 TikZ 插件生成 PDF 后无法把图片返回到文档的问题,根因是临时目录路径使用了 8.3 短路径表示,导致 `pdflatex` 无法正确打开输入文件。 + +1. 在 Goldfish 层规范化 Windows 临时目录,避免返回带 `~` 的 8.3 短路径。 +2. 调整 TikZ 插件在 Windows 下的路径拼接和 `pdflatex` 调用方式。 +3. 新增 `file:` 协议回归测试,确认 host 侧可以把 PDF 文件转换成内部 `IMAGE` 节点。 +4. 补充 TikZ Scheme 测试,覆盖 Windows 下 `TEMP` 含 `~` 的场景。 + +## 6 Why + +TikZ 插件的返回链路是: + +1. Goldfish 生成临时 `.tex` +2. 调用外部 `pdflatex` 生成 `.pdf` +3. 通过 `flush-file` 发送 `file:?width=...&height=...` +4. host 侧 `file_flush` 读取 PDF 字节并构造内部 `IMAGE` + +实际排查发现,host 侧的 `file:` 协议解析在 Windows 上对正常绝对路径是可用的,问题出在更前面: + +- Windows 上 `os-temp-dir` 可能返回 `C:\Users\LUOYIN~1\AppData\Local\Temp` 这种 8.3 短路径。 +- TikZ 插件把该路径拼进 `pdflatex` 命令行后,MiKTeX 会把 `~` 前的路径片段错误识别为文件名,导致 `.pdf` 根本没有生成。 +- 既然没有 PDF 产物,后续 `flush-file -> file_flush -> IMAGE` 这条回传链路自然也不会发生。 + +因此修复重点不是 `file_flush`,而是保证 TikZ 插件传给 `pdflatex` 的临时路径在 Windows 上是稳定可读的长路径。 + +## 7 How + +### 7.1 Goldfish 层修复 temp 路径 + +在 `g_os-temp-dir` 中保留原有“取系统 temp 目录”的语义,但在 Windows 分支额外做一次规范化: + +- 先调用 `tb_directory_temporary()` 取 temp 路径。 +- 将结果转成 `WCHAR`。 +- 使用 `GetLongPathNameW()` 把 `C:\Users\NAME~1\...` 展开为长路径。 +- 若转换成功,再转回 UTF-8 字符串返回给 Scheme;失败则回退到原值。 + +这样不会改变 temp 目录的选择逻辑,只是修正其字符串表示。 + +### 7.2 TikZ 插件适配 Windows 命令行 + +在 `tm-tikz.scm` 中同步做了两类整理: + +- 临时路径拼接从硬编码 `/` 改为 `os-sep`,确保 Windows 下生成 `...\tikz\UUID.tex`。 +- `run-pdflatex` 在 Windows 下改为: + - 使用简单的双引号包装路径和可执行文件 + - 使用 `cmd.exe /c call ... > NUL 2>&1` + +Linux/macOS 仍保留原来的 `sh -c` 分支。 + +### 7.3 测试策略 + +本任务分成两层验证: + +- `input_file_flush_test.cpp`:直接向 `texmacs_input` 注入 `file:` 协议,确认 Windows host 侧对正常 PDF 路径能构造出 `IMAGE`,从而排除 `file_flush` 本身的问题。 +- `tm-tikz-test.scm`:在 Scheme 层检查 `os-temp-dir` 在 Windows + `TEMP` 含 `~` 时不再返回短路径,并把原先写死 `/tmp` 的辅助测试改成跨平台 temp 路径。 + +### 7.4 当前范围 + +本任务修复的是 Windows 下 temp 目录 8.3 短路径导致的 TikZ 失败问题。 + +它不覆盖另一个更大的 Windows Unicode 路径问题:当前 `g_os-call` 仍通过 `std::system` 执行命令,因此“中文用户名/中文路径是否完全可用”不在本次修复范围内。 diff --git a/tests/System/Files/input_file_flush_test.cpp b/tests/System/Files/input_file_flush_test.cpp new file mode 100644 index 0000000000..e3aa7b8802 --- /dev/null +++ b/tests/System/Files/input_file_flush_test.cpp @@ -0,0 +1,65 @@ +/****************************************************************************** + * MODULE : input_file_flush_test.cpp + * COPYRIGHT : (C) 2026 OpenAI + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#include "Generic/input.hpp" +#include "analyze.hpp" +#include "base.hpp" +#include "file.hpp" +#include "tm_link.hpp" +#include "tm_url.hpp" +#include "tree.hpp" +#include "tree_helper.hpp" +#include "url.hpp" +#include + +class TestInputFileFlush : public QObject { + Q_OBJECT + +private slots: + void init () { init_lolly (); } + void test_file_protocol_loads_pdf_as_image (); +}; + +void +TestInputFileFlush::test_file_protocol_loads_pdf_as_image () { + url pdf_url= + resolve (url_system ("$TEXMACS_PATH/tests/PDF/pdf_1_4_sample.pdf"), "r"); + QVERIFY (exists (pdf_url)); + + string pdf_path = concretize (pdf_url); + string pdf_bytes= string_load (pdf_url); + url reparsed = url_system (pdf_path); + QVERIFY (exists (reparsed)); + qcompare (suffix (reparsed), "pdf"); + + string protocol_s; + protocol_s << DATA_BEGIN << "file:" << pdf_path << "?width=0.3par&height=0px" + << DATA_END; + + texmacs_input in ("output"); + for (int i= 0; i < N (protocol_s); ++i) + (void) in->put (protocol_s[i]); + + tree doc= in->get ("output"); + QVERIFY (is_document (doc)); + QCOMPARE (N (doc), 1); + + tree image= doc[0]; + QVERIFY (is_func (image, moebius::IMAGE, 5)); + QVERIFY (is_func (image[0], moebius::TUPLE, 2)); + QVERIFY (is_func (image[0][0], moebius::RAW_DATA, 1)); + + qcompare (as_string (image[0][1]), "pdf"); + qcompare (as_string (image[1]), "0.3par"); + qcompare (as_string (image[2]), ""); + QCOMPARE (N (as_string (image[0][0][0])), N (pdf_bytes)); +} + +QTEST_MAIN (TestInputFileFlush) +#include "input_file_flush_test.moc"