From 9eaef9e57c36e0015e830a70b6b5fdd288286e2b Mon Sep 17 00:00:00 2001 From: Jim Zhou Date: Thu, 9 Jul 2026 20:12:44 +0800 Subject: [PATCH] [724] WASM Support for STEM --- .github/workflows/ci-macos-wasm.yml | 89 ++++++++ .gitignore | 5 +- 3rdparty/libaesgm.lua | 2 +- 3rdparty/pdfhummus.lua | 2 +- 3rdparty/pdfhummus/xmake.lua | 5 +- 3rdparty/tbox/xmake.lua | 6 - bin/wasm_server.py | 62 ++++++ devel/0724.md | 21 ++ lolly/xmake.lua | 5 - src/Plugins/ImGui/im_tm_widget.cpp | 309 +++++++++++++++------------- src/Plugins/ImGui/im_tm_widget.hpp | 4 + src/System/Link/pipe_link.cpp | 6 +- src/Texmacs/Server/tm_server.cpp | 6 - xmake.lua | 56 ++--- xmake/packages/l/libpng/xmake.lua | 92 +++++++++ xmake/packages/m/mupdf/xmake.lua | 62 +++++- xmake/requires.lua | 10 +- xmake/stem.lua | 1 + xmake/targets/libmogan.lua | 23 ++- xmake/targets/stem.lua | 21 +- 20 files changed, 582 insertions(+), 205 deletions(-) create mode 100644 .github/workflows/ci-macos-wasm.yml create mode 100644 bin/wasm_server.py create mode 100644 devel/0724.md create mode 100644 xmake/packages/l/libpng/xmake.lua diff --git a/.github/workflows/ci-macos-wasm.yml b/.github/workflows/ci-macos-wasm.yml new file mode 100644 index 0000000000..eb0fe1b850 --- /dev/null +++ b/.github/workflows/ci-macos-wasm.yml @@ -0,0 +1,89 @@ +name: Build on macOS for WASM +on: + push: + branches: [main] + paths: + - "src/**" + - "!src/Plugins/Macos/**" + - "!src/Plugins/Windows/**" + - "tests/**" + - "lolly/**" + - "moebius/**" + - "xmake.lua" + - 'xmake/options.lua' + - 'xmake/requires.lua' + - 'xmake/targets/**' + - "xmake/packages.lua" + - "xmake/packages/**" + - ".github/workflows/ci-macos-wasm.yml" + - "TeXmacs/tests/*.scm" + - "3rdparty/**" + pull_request: + branches: [main] + paths: + - "src/**" + - "!src/Plugins/Macos/**" + - "!src/Plugins/Windows/**" + - "tests/**" + - "lolly/**" + - "moebius/**" + - "xmake.lua" + - 'xmake/options.lua' + - 'xmake/requires.lua' + - 'xmake/targets/**' + - "xmake/packages.lua" + - "xmake/packages/**" + - ".github/workflows/ci-macos-wasm.yml" + - "TeXmacs/tests/*.scm" + - "3rdparty/**" + workflow_dispatch: + +env: + XMAKE_ROOT: y + INSTALL_DIR: tmp/build/packages/app.mogan/ + +jobs: + build: + strategy: + matrix: + os: [macos-14] + arch: [arm64] + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + if: always() + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: cache xmake + uses: actions/cache@v4 + with: + path: | + ${{github.workspace}}/build/.build_cache + /Users/runner/.xmake + key: xmake-${{ runner.os }}-wasm-${{ hashFiles('xmake/vars.lua', 'lolly/xmake.lua', 'moebius/xmake.lua') }} + + - name: set XMAKE_GLOBALDIR + run: echo "XMAKE_GLOBALDIR=${{ runner.workspace }}/xmake-global" >> $GITHUB_ENV + + - uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: v3.0.4 + actions-cache-folder: '.xmake-cache' + + - name: xmake repo --update + run: xmake repo --update + + - name: cache packages from xrepo + uses: actions/cache@v4 + with: + path: | + ${{ env.XMAKE_GLOBALDIR }}/.xmake/packages + key: xrepo-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('xmake/vars.lua', 'lolly/xmake.lua', 'moebius/xmake.lua') }} + + - name: config + run: xmake config --policies=build.ccache -o ${{ runner.workspace }}/build -m releasedbg -p wasm --qt_frontend=no --yes + + - name: build + run: xmake build --yes -vD stem \ No newline at end of file diff --git a/.gitignore b/.gitignore index 90946f032c..076db58a69 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ docs/**/plugin_binary.md # Binary of the goldfish plugin TeXmacs/plugins/goldfish/bin/goldfish +TeXmacs/plugins/goldfish/bin/*.wasm +TeXmacs/plugins/goldfish/bin/*.js +TeXmacs/plugins/goldfish/bin/*.html goldfish.dSYM/ *.sym *goldfish.pdb @@ -53,4 +56,4 @@ goldfish.dSYM/ # font TeXmacs/fonts/opentype/noto -record \ No newline at end of file +record diff --git a/3rdparty/libaesgm.lua b/3rdparty/libaesgm.lua index 338e1a2495..f41f0506c1 100644 --- a/3rdparty/libaesgm.lua +++ b/3rdparty/libaesgm.lua @@ -10,7 +10,7 @@ package("liii-libaesgm") set_sourcedir(path.join(os.scriptdir(), "libaesgm")) - on_install("linux", "macosx", "windows", "mingw", function (package) + on_install("linux", "macosx", "windows", "mingw", "wasm", function (package) if package:is_plat("windows", "mingw") and package:is_arch("arm", "arm64") then -- Windows is always little endian io.replace("brg_endian.h", [[ diff --git a/3rdparty/pdfhummus.lua b/3rdparty/pdfhummus.lua index 7173c3827e..0e7ee515ba 100644 --- a/3rdparty/pdfhummus.lua +++ b/3rdparty/pdfhummus.lua @@ -30,7 +30,7 @@ package("liii-pdfhummus") end end end) - on_install("linux", "windows", "mingw", "macosx", function (package) + on_install("linux", "windows", "mingw", "macosx", "wasm", function (package) local configs = {} if package:config("shared") then configs.kind = "shared" diff --git a/3rdparty/pdfhummus/xmake.lua b/3rdparty/pdfhummus/xmake.lua index 80012ea999..390b261e61 100644 --- a/3rdparty/pdfhummus/xmake.lua +++ b/3rdparty/pdfhummus/xmake.lua @@ -1,5 +1,6 @@ -- originally from https://github.com/xmake-io/xmake-repo/pull/1709 +includes("../libaesgm.lua") option("libtiff", {description = "Enable libtiff", default = false}) option("libpng", {description = "Enable libpng", default = false}) option("libjpeg", {description = "Enable libjpeg", default = false}) @@ -17,14 +18,14 @@ end if has_config("openssl") then add_requires("openssl") end -add_requires("freetype", "zlib", "libaesgm") +add_requires("freetype", "zlib", "liii-libaesgm") target("pdfhummus") set_kind("$(kind)") add_files("PDFWriter/*.cpp") add_headerfiles("(PDFWriter/*.h)") add_packages("freetype") add_packages("libtiff", "libpng", "libjpeg", "openssl") - add_packages("libaesgm", "zlib") + add_packages("liii-libaesgm", "zlib") if has_package("libtiff") then add_defines("_INCLUDE_TIFF_HEADER") add_cxflags("-Wno-deprecated-declarations") diff --git a/3rdparty/tbox/xmake.lua b/3rdparty/tbox/xmake.lua index b1fe97ce51..8cb3b80578 100644 --- a/3rdparty/tbox/xmake.lua +++ b/3rdparty/tbox/xmake.lua @@ -31,12 +31,6 @@ if has_config("coroutine") then add_cxflags("gcc::-Wno-error=dangling-pointer") end --- set wasm toolchain -if is_plat("wasm") then - add_requires("emscripten") - set_toolchains("emcc@emscripten") -end - -- add build modes add_rules("mode.release", "mode.debug", "mode.profile", "mode.coverage", "mode.valgrind", "mode.asan", "mode.tsan", "mode.ubsan") if is_mode("debug") then diff --git a/bin/wasm_server.py b/bin/wasm_server.py new file mode 100644 index 0000000000..838b45b4e4 --- /dev/null +++ b/bin/wasm_server.py @@ -0,0 +1,62 @@ +from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler +import sys +import os + +root = os.path.abspath(sys.argv[1]) + +html = os.path.join(root, "stem.html") +js = os.path.join(root, "stem.js") + +#if not os.path.exists(html) and os.path.exists(js): +with open(html, "w", encoding="utf-8") as f: + f.write(""" + + + + stem + + + + + + + + + + +""") + +class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=root, **kwargs) + + def end_headers(self): + self.send_header( + "Cross-Origin-Opener-Policy", + "same-origin" + ) + self.send_header( + "Cross-Origin-Embedder-Policy", + "require-corp" + ) + super().end_headers() + +server = ThreadingHTTPServer( + ("127.0.0.1", 8000), + Handler +) + +print("Serving on http://127.0.0.1:8000/stem.html") + +server.serve_forever() \ No newline at end of file diff --git a/devel/0724.md b/devel/0724.md new file mode 100644 index 0000000000..178b340752 --- /dev/null +++ b/devel/0724.md @@ -0,0 +1,21 @@ +# [0724] WASM Support for STEM (ImGui) +## What +- 在构建脚本中添加了适配 wasm 的逻辑 +- bin 中新增 wasm_server.py +- `tm_server.cpp` `pipe_link.cpp` 中调整 WASM 条件编译选项,以通过编译(目前没有实现 WASM 的多进程接口) +- `im_tm_widget` 中添加符合 Emscripten 3.1.56 的调用(在条件编译中) + +## 如何测试 +测试默认的 Qt 构建不被改变 +``` +xmake f +xmake b stem +``` + +测试 WASM 构建 +``` +xmake f -p wasm +xmake b stem +xmake r stem +``` +浏览器打开监听的地址 diff --git a/lolly/xmake.lua b/lolly/xmake.lua index 68aaef70f4..381ac1b8a6 100644 --- a/lolly/xmake.lua +++ b/lolly/xmake.lua @@ -21,11 +21,6 @@ if is_plat("mingw") and is_host("windows") then set_toolchains("mingw@mingw-w64") end -if is_plat("wasm") then - add_requires("emscripten 3.1.25") - set_toolchains("emcc@emscripten") -end - -- Options option("malloc") set_default("default") diff --git a/src/Plugins/ImGui/im_tm_widget.cpp b/src/Plugins/ImGui/im_tm_widget.cpp index 6425832a3e..e07fa6197d 100644 --- a/src/Plugins/ImGui/im_tm_widget.cpp +++ b/src/Plugins/ImGui/im_tm_widget.cpp @@ -25,7 +25,9 @@ #include "backends/imgui_impl_glfw.h" #include "backends/imgui_impl_opengl3.h" +#ifndef __EMSCRIPTEN__ #include "backends/imgui_impl_opengl3_loader.h" +#endif #include "imgui.h" #if defined(IMGUI_IMPL_OPENGL_ES2) @@ -33,6 +35,10 @@ #endif #include // defines GLFWwindow, drags in system OpenGL headers +#ifdef __EMSCRIPTEN__ +#include +#endif + #include #include #include @@ -239,7 +245,9 @@ im_tm_widget_rep::im_tm_widget_rep (int mask, command _quit) return; } glfwMakeContextCurrent (window); - glfwSwapInterval (1); // enable vsync +#ifndef __EMSCRIPTEN__ + glfwSwapInterval (1); +#endif // Match retina factor float xscale= 1.0f, yscale= 1.0f; @@ -272,6 +280,9 @@ im_tm_widget_rep::im_tm_widget_rep (int mask, command _quit) style.FontScaleDpi= main_scale; ImGui_ImplGlfw_InitForOpenGL (window, true); +#ifdef __EMSCRIPTEN__ + ImGui_ImplGlfw_InstallEmscriptenCallbacks (window, "#canvas"); +#endif ImGui_ImplOpenGL3_Init (glsl_version); initialized= true; @@ -287,9 +298,6 @@ void im_tm_widget_rep::shutdown_context () { if (!initialized) return; initialized= false; -#ifdef __EMSCRIPTEN__ - EMSCRIPTEN_MAINLOOP_END; -#endif if (document_texture) { glDeleteTextures (1, &document_texture); document_texture= 0; @@ -329,160 +337,175 @@ im_tm_widget_rep::plain_window_widget (string name, command _quit, int b) { ******************************************************************************/ void -im_tm_widget_rep::run () { - if (!initialized || window == nullptr) return; - while (!glfwWindowShouldClose (window)) { - glfwPollEvents (); - // 创建了一个 stub 窗口(例如 Cmd+F 的查找窗口等)时 server - // 将当前窗口从我们这里切换走, 编辑器被 suspend () 挂起(got_focus=false) - // 在这里重新恢复这两种焦点状态,以确保后续的键盘和鼠标事件能够继续正常传递。 - // 此代码会在创建该占位窗口的 Scheme 处理函数返回之后执行 - // (即 glfwPollEvents 已经分发了 Cmd+F 的按键事件)。 - if (needs_refocus) { - needs_refocus= false; - if (window) glfwFocusWindow (window); - im_simple_widget_rep* ed= canvas (); - if (ed) ed->handle_keyboard_focus (true, texmacs_time ()); +im_tm_widget_rep::im_main_loop () { + glfwPollEvents (); + // 创建了一个 stub 窗口(例如 Cmd+F 的查找窗口等)时 server + // 将当前窗口从我们这里切换走, 编辑器被 suspend () 挂起(got_focus=false) + // 在这里重新恢复这两种焦点状态,以确保后续的键盘和鼠标事件能够继续正常传递。 + // 此代码会在创建该占位窗口的 Scheme 处理函数返回之后执行 + // (即 glfwPollEvents 已经分发了 Cmd+F 的按键事件)。 + if (needs_refocus) { + needs_refocus= false; + if (window) glfwFocusWindow (window); + im_simple_widget_rep* ed= canvas (); + if (ed) ed->handle_keyboard_focus (true, texmacs_time ()); + } + // Auto-scroll while dragging (left button held) near the top/bottom edge. + // ImGui 实现的拖动滚动逻辑 + if (!is_nil (main_widget) && window != nullptr && + glfwGetMouseButton (window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { + double xpos= 0, ypos= 0; + glfwGetCursorPos (window, &xpos, &ypos); + int ww= 0, wh= 0; + glfwGetWindowSize (window, &ww, &wh); + const double EDGE= 48.0; // px proximity band + const SI MAXD= (SI) (80.0 * PIXEL); // cap per-frame scroll to avoid jitter + SI sx= 0, sy= 0; + get_scroll_position (main_widget, sx, sy); + SI new_sy= sy; + if (wh > 0 && ypos < EDGE) { + SI d= (SI) ((EDGE - ypos) * PIXEL); + if (d > MAXD) d= MAXD; + new_sy= sy + d; // near/over top → up } - // Auto-scroll while dragging (left button held) near the top/bottom edge. - // ImGui 实现的拖动滚动逻辑 - if (!is_nil (main_widget) && window != nullptr && - glfwGetMouseButton (window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { - double xpos= 0, ypos= 0; - glfwGetCursorPos (window, &xpos, &ypos); - int ww= 0, wh= 0; - glfwGetWindowSize (window, &ww, &wh); - const double EDGE= 48.0; // px proximity band - const SI MAXD= - (SI) (80.0 * PIXEL); // cap per-frame scroll to avoid jitter - SI sx= 0, sy= 0; - get_scroll_position (main_widget, sx, sy); - SI new_sy= sy; - if (wh > 0 && ypos < EDGE) { - SI d= (SI) ((EDGE - ypos) * PIXEL); - if (d > MAXD) d= MAXD; - new_sy= sy + d; // near/over top → up - } - else if (wh > 0 && ypos > (double) wh - EDGE) { - SI d= (SI) ((ypos - ((double) wh - EDGE)) * PIXEL); - if (d > MAXD) d= MAXD; - new_sy= sy - d; // near/over bottom → down - } - if (new_sy != sy) { - main_widget->send (SLOT_SCROLL_POSITION, - close_box (coord2 (sx, new_sy))); - // Re-dispatch a move so the editor's drag selection tracks the - // viewport. Clamp ypos to [0, wh]. - double cy= ypos; - if (cy < 0.0) cy= 0.0; - if (wh > 0 && cy > (double) wh) cy= (double) wh; - double cx= xpos; - if (cx < 0.0) cx= 0.0; - if (ww > 0 && cx > (double) ww) cx= (double) ww; - SI msx= 0, msy= 0, nsx= 0, nsy= 0; - screen_to_si (cx, cy, msx, msy); - get_scroll_position (main_widget, nsx, nsy); - dispatch_mouse ("move", msx + nsx, msy + nsy, 1); - } + else if (wh > 0 && ypos > (double) wh - EDGE) { + SI d= (SI) ((ypos - ((double) wh - EDGE)) * PIXEL); + if (d > MAXD) d= MAXD; + new_sy= sy - d; // near/over bottom → down } - - // Drive one TeXmacs tick. 驱动编辑器。 - im_interpose (); - if (glfwGetWindowAttrib (window, GLFW_ICONIFIED) != 0) { - ImGui_ImplGlfw_Sleep (10); - continue; + if (new_sy != sy) { + main_widget->send (SLOT_SCROLL_POSITION, + close_box (coord2 (sx, new_sy))); + // Re-dispatch a move so the editor's drag selection tracks the + // viewport. Clamp ypos to [0, wh]. + double cy= ypos; + if (cy < 0.0) cy= 0.0; + if (wh > 0 && cy > (double) wh) cy= (double) wh; + double cx= xpos; + if (cx < 0.0) cx= 0.0; + if (ww > 0 && cx > (double) ww) cx= (double) ww; + SI msx= 0, msy= 0, nsx= 0, nsy= 0; + screen_to_si (cx, cy, msx, msy); + get_scroll_position (main_widget, nsx, nsy); + dispatch_mouse ("move", msx + nsx, msy + nsy, 1); } + } - ImGui_ImplOpenGL3_NewFrame (); - ImGui_ImplGlfw_NewFrame (); - ImGui::NewFrame (); - // Keep the editor canvas informed of the visible size (Qt does this via - // resize events); otherwise update_visible()/SLOT_VISIBLE_PART report a - // zero-sized region and the editor has nothing to render. - static int last_cw= 0, last_ch= 0; - if (!is_nil (main_widget)) { - int cw= 0, ch= 0; - glfwGetWindowSize (window, &cw, &ch); - if (cw > 0 && ch > 0 && (cw != last_cw || ch != last_ch)) { - last_cw= cw; - last_ch= ch; - main_widget->send ( - SLOT_SIZE, - close_box (coord2 ((SI) (cw * PIXEL), (SI) (ch * PIXEL)))); - texture_dirty= true; // force a re-rasterization at the new canvas size - } - } + // Drive one TeXmacs tick. 驱动编辑器。 + im_interpose (); + if (glfwGetWindowAttrib (window, GLFW_ICONIFIED) != 0) { + ImGui_ImplGlfw_Sleep (10); + return; + } - static SI last_sx= 0, last_sy= 0; - if (!is_nil (main_widget)) { - SI sx= 0, sy= 0; - get_scroll_position (main_widget, sx, sy); - if (sx != last_sx || sy != last_sy) { - last_sx = sx; - last_sy = sy; - texture_dirty= true; - } + ImGui_ImplOpenGL3_NewFrame (); + ImGui_ImplGlfw_NewFrame (); + ImGui::NewFrame (); + // Keep the editor canvas informed of the visible size (Qt does this via + // resize events); otherwise update_visible()/SLOT_VISIBLE_PART report a + // zero-sized region and the editor has nothing to render. + static int last_cw= 0, last_ch= 0; + if (!is_nil (main_widget)) { + int cw= 0, ch= 0; + glfwGetWindowSize (window, &cw, &ch); + if (cw > 0 && ch > 0 && (cw != last_cw || ch != last_ch)) { + last_cw= cw; + last_ch= ch; + main_widget->send (SLOT_SIZE, close_box (coord2 ( + (SI) (cw * PIXEL), (SI) (ch * PIXEL)))); + texture_dirty= true; // force a re-rasterization at the new canvas size } + } - // Re-render only when something actually changed: either the window was - // invalidated (texture_dirty, set by windows_refresh / SLOT_INVALIDATE_*), - // or the editor canvas reports invalid regions. - if (!is_nil (main_widget)) { - if (texture_dirty || query_invalid (main_widget)) render_editor (); + static SI last_sx= 0, last_sy= 0; + if (!is_nil (main_widget)) { + SI sx= 0, sy= 0; + get_scroll_position (main_widget, sx, sy); + if (sx != last_sx || sy != last_sy) { + last_sx = sx; + last_sy = sy; + texture_dirty= true; } + } + + // Re-render only when something actually changed: either the window was + // invalidated (texture_dirty, set by windows_refresh / SLOT_INVALIDATE_*), + // or the editor canvas reports invalid regions. + if (!is_nil (main_widget)) { + if (texture_dirty || query_invalid (main_widget)) render_editor (); + } #ifdef LIII_DEBUG - { - // show FPS - ImGui::Begin ("FPS"); - ImGui::Text ("Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / io->Framerate, io->Framerate); - ImGui::End (); - } + { + // show FPS + ImGui::Begin ("FPS"); + ImGui::Text ("Application average %.3f ms/frame (%.1f FPS)", + 1000.0f / io->Framerate, io->Framerate); + ImGui::End (); + } #endif - // Display the canvas - int win_w= 0, win_h= 0; - glfwGetWindowSize (window, &win_w, &win_h); - int fb_w= 0, fb_h= 0; - glfwGetFramebufferSize (window, &fb_w, &fb_h); - ImGui::SetNextWindowPos (ImVec2 (0, 0)); - ImGui::SetNextWindowSize (ImVec2 ((float) win_w, (float) win_h)); - ImGui::PushStyleVar (ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar (ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar (ImGuiStyleVar_WindowPadding, ImVec2 (0, 0)); - { - ImGui::Begin ("Mogan Canvas", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse); - ImGui::PopStyleVar (3); - if (document_texture != 0) { - ImVec2 avail= ImGui::GetContentRegionAvail (); - float img_w= (float) tex_w; - float img_h= (float) tex_h; - if (img_w > avail.x) img_w= avail.x; - if (img_h > avail.y) img_h= avail.y; - // Note: ImGui 系永远从 (0, 0) 开始,所有位移用 scroll_x, scroll_y 完成 - ImGui::Image ((ImTextureID) (intptr_t) document_texture, - ImVec2 (img_w, img_h)); - } - else { - ImGui::Text ("Editor canvas not yet wired."); - ImGui::Text ("Close this window to quit."); - } - ImGui::End (); + // Display the canvas + int win_w= 0, win_h= 0; + glfwGetWindowSize (window, &win_w, &win_h); + int fb_w= 0, fb_h= 0; + glfwGetFramebufferSize (window, &fb_w, &fb_h); + ImGui::SetNextWindowPos (ImVec2 (0, 0)); + ImGui::SetNextWindowSize (ImVec2 ((float) win_w, (float) win_h)); + ImGui::PushStyleVar (ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar (ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar (ImGuiStyleVar_WindowPadding, ImVec2 (0, 0)); + { + ImGui::Begin ("Mogan Canvas", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar (3); + if (document_texture != 0) { + ImVec2 avail= ImGui::GetContentRegionAvail (); + float img_w= (float) tex_w; + float img_h= (float) tex_h; + if (img_w > avail.x) img_w= avail.x; + if (img_h > avail.y) img_h= avail.y; + // Note: ImGui 系永远从 (0, 0) 开始,所有位移用 scroll_x, scroll_y 完成 + ImGui::Image ((ImTextureID) (intptr_t) document_texture, + ImVec2 (img_w, img_h)); + } + else { + ImGui::Text ("Editor canvas not yet wired."); + ImGui::Text ("Close this window to quit."); } + ImGui::End (); + } + + ImGui::Render (); + glViewport (0, 0, fb_w, fb_h); + glClearColor (0.45f, 0.55f, 0.60f, 1.00f); + glClear (GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData (ImGui::GetDrawData ()); + glfwSwapBuffers (window); +} + +void +im_tm_widget_rep::em_main_loop_wrapper (void* arg) { + auto* self= static_cast (arg); + self->im_main_loop (); +} - ImGui::Render (); - glViewport (0, 0, fb_w, fb_h); - glClearColor (0.45f, 0.55f, 0.60f, 1.00f); - glClear (GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData (ImGui::GetDrawData ()); - glfwSwapBuffers (window); +void +im_tm_widget_rep::run () { + if (!initialized || window == nullptr) return; +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg (im_tm_widget_rep::em_main_loop_wrapper, this, 0, + true); + emscripten_cancel_main_loop (); +#else + while (!glfwWindowShouldClose (window)) { + im_main_loop (); } +#endif shutdown_context (); } diff --git a/src/Plugins/ImGui/im_tm_widget.hpp b/src/Plugins/ImGui/im_tm_widget.hpp index e1796b2216..7c822d02b1 100644 --- a/src/Plugins/ImGui/im_tm_widget.hpp +++ b/src/Plugins/ImGui/im_tm_widget.hpp @@ -69,6 +69,8 @@ class im_tm_widget_rep : public im_widget_rep { array data= array ()); // Convert a position in GLFW screen coordinates to the renderer's SI space void screen_to_si (double xpos, double ypos, SI& sx, SI& sy); + // ImGui's main loop + void im_main_loop (); // The attached editor canvas. nullptr when no real editor is wired im_simple_widget_rep* canvas (); @@ -76,6 +78,8 @@ class im_tm_widget_rep : public im_widget_rep { im_tm_widget_rep (int mask, command quit); ~im_tm_widget_rep (); + static void em_main_loop_wrapper (void* arg); + // Provide the typeset document to display void set_box (box b, double zoom_factor); diff --git a/src/System/Link/pipe_link.cpp b/src/System/Link/pipe_link.cpp index 272f8d102a..c302eec95f 100644 --- a/src/System/Link/pipe_link.cpp +++ b/src/System/Link/pipe_link.cpp @@ -13,8 +13,8 @@ #include "tm_debug.hpp" #include "tm_link.hpp" -#if !(defined(QTTEXMACS) && (defined(OS_MINGW) || defined(QTPIPES) || \ - defined(OS_WIN) || defined(OS_WASM))) +#if !(defined(QTTEXMACS) && \ + (defined(OS_MINGW) || defined(QTPIPES) || defined(OS_WIN))) #include "hashset.hpp" #include "iterator.hpp" @@ -97,6 +97,7 @@ pipe_link_rep::~pipe_link_rep () { pipe_link_set->remove ((pointer) this); } +#ifndef OS_WASM tm_link make_pipe_link (string cmd) { return tm_new (cmd); @@ -127,6 +128,7 @@ process_all_pipes () { if (con->alive) con->apply_command (); } } +#endif /****************************************************************************** * Routines for pipe_links diff --git a/src/Texmacs/Server/tm_server.cpp b/src/Texmacs/Server/tm_server.cpp index 2af21a1232..b5e6d561b3 100644 --- a/src/Texmacs/Server/tm_server.cpp +++ b/src/Texmacs/Server/tm_server.cpp @@ -19,9 +19,7 @@ #include "dictionary.hpp" #include "file.hpp" #include "glue.hpp" -#ifndef OS_WASM #include "goldfish.hpp" -#endif #include "lolly/system/subprocess.hpp" #include "new_style.hpp" #include "s7_blackbox.hpp" @@ -154,9 +152,7 @@ tm_server_rep::tm_server_rep (app_type app) : def_zoomf (1.0) { eval_scheme_root (init_prg); initialize_smobs (initialize_scheme ()); initialize_glue (); -#ifndef OS_WASM goldfish::glue_for_community_edition (tm_s7); -#endif gui_interpose (texmacs_interpose_handler); set_wait_handler (texmacs_wait_handler); @@ -207,7 +203,6 @@ tm_server_rep::refresh () { void tm_server_rep::interpose_handler () { -#ifndef OS_WASM #ifdef QTTEXMACS // TeXmacs/Qt handles delayed messages and socket notification // in its own runloop @@ -219,7 +214,6 @@ tm_server_rep::interpose_handler () { perform_select (); exec_pending_commands (); #endif -#endif // not OS_WASM if (!headless_mode) { int i, j; diff --git a/xmake.lua b/xmake.lua index 9cb024a642..a0ff6a2046 100644 --- a/xmake.lua +++ b/xmake.lua @@ -36,7 +36,7 @@ add_repositories("liii-repo xmake") -- msvc, this project will not support windows env. -- because some package is not ported to cygwin env, this project will not -- support cygwin env. -set_allowedplats("linux", "macosx", "windows") +set_allowedplats("linux", "macosx", "windows", "wasm") -- add releasedbg, debug and release modes for different platforms. -- debug mode cannot run on mingw with qt precompiled binary @@ -73,6 +73,14 @@ else set_configvar("OS_WIN", false) end +if is_plat("wasm") then + set_configvar("OS_WASM", true) + add_requires("emscripten 3.1.56") + set_toolchains("emcc@emscripten") +else + set_configvar("OS_WASM", false) +end + includes("@builtin/check") configvar_check_cxxtypes("HAVE_INTPTR_T", "intptr_t", {includes = {"memory"}}) configvar_check_cxxincludes("HAVE_INTTYPES_H", "inttypes.h") @@ -103,34 +111,34 @@ if is_mode("release") then end if has_config("qt_frontend") then -includes("xmake/tests.lua") --- Tests in C++ -all_cpp_tests = os.files("tests/**_test.cpp") -cpp_benches = os.files("bench/**_bench.cpp") + includes("xmake/tests.lua") + -- Tests in C++ + all_cpp_tests = os.files("tests/**_test.cpp") + cpp_benches = os.files("bench/**_bench.cpp") -for _, filepath in ipairs(cpp_benches) do - add_target_cpp_bench(filepath, "libmogan") -end + for _, filepath in ipairs(cpp_benches) do + add_target_cpp_bench(filepath, "libmogan") + end -if not (is_plat("linux") and (linuxos.name () == "ubuntu" and linuxos.version():major() == 20)) then - for _, filepath in ipairs(all_cpp_tests) do - if not string.find(filepath, "tests/L3/") then - add_target_cpp_test(filepath, "libmogan", "libmoebius") + if not (is_plat("linux") and (linuxos.name () == "ubuntu" and linuxos.version():major() == 20)) then + for _, filepath in ipairs(all_cpp_tests) do + if not string.find(filepath, "tests/L3/") then + add_target_cpp_test(filepath, "libmogan", "libmoebius") + end end end -end --- Tests in Scheme -for _, filepath in ipairs(os.files("TeXmacs/progs/**/*-test.scm")) do - add_target_scheme_test(filepath, INSTALL_DIR, RUN_ENVS) -end + -- Tests in Scheme + for _, filepath in ipairs(os.files("TeXmacs/progs/**/*-test.scm")) do + add_target_scheme_test(filepath, INSTALL_DIR, RUN_ENVS) + end -for _, filepath in ipairs(os.files("TeXmacs/progs/kernel/**/*-test.scm")) do - add_target_scheme_test(filepath, INSTALL_DIR, RUN_ENVS) -end + for _, filepath in ipairs(os.files("TeXmacs/progs/kernel/**/*-test.scm")) do + add_target_scheme_test(filepath, INSTALL_DIR, RUN_ENVS) + end --- Integration tests -for _, filepath in ipairs(os.files("TeXmacs/tests/*.scm")) do - add_target_integration_test(filepath, INSTALL_DIR, RUN_ENVS) -end + -- Integration tests + for _, filepath in ipairs(os.files("TeXmacs/tests/*.scm")) do + add_target_integration_test(filepath, INSTALL_DIR, RUN_ENVS) + end end -- has_config("qt_frontend") diff --git a/xmake/packages/l/libpng/xmake.lua b/xmake/packages/l/libpng/xmake.lua new file mode 100644 index 0000000000..f6766b1f5b --- /dev/null +++ b/xmake/packages/l/libpng/xmake.lua @@ -0,0 +1,92 @@ +package("libpng") + set_homepage("https://www.libpng.org/pub/png/libpng.html") + set_description("The official PNG reference library") + set_license("libpng-2.0") + + add_urls("https://github.com/glennrp/libpng/archive/refs/tags/$(version).tar.gz", + "https://github.com/glennrp/libpng.git") + + add_versions("v1.6.58", "a9d4df463d36a6e5f9c29bd6f4967312d17e996c1854f3511f833924eb1993cf") + add_versions("v1.6.57", "4cbb7b0746edc1683c9581b373365e955133b7f1243f171b7d1535b4415dfedb") + add_versions("v1.6.56", "41d74ffe235cb7e8bab40bcad2167f7bb25edbf2231dcfff57ccf4305dc0bfae") + add_versions("v1.6.55", "71a2c5b1218f60c4c6d2f1954c7eb20132156cae90bdb90b566c24db002782a6") + add_versions("v1.6.54", "ba7efce137409079989df4667706c339bebfbb10e9f413474718012a13c8cd4c") + add_versions("v1.6.53", "b20cee717e11416d2f96ccc7d184f63730ca8cb2f03bfd0c4ed77fbc909c0bff") + add_versions("v1.6.51", "b1872484c1c5c70acc79cbb15fb366df954fa8d5dacbe7f729d858902d17933c") + add_versions("v1.6.50", "71158e53cfdf2877bc99bcab33641d78df3f48e6e0daad030afe9cb8c031aa46") + add_versions("v1.6.49", "e425762fdfb9bb30a5d2da29c0067570e96b5d41d79c659cf0dad861e9df738e") + add_versions("v1.6.48", "b17e99026055727e8cba99160c3a9a7f9af788e9f786daeadded5a42243f1dd0") + add_versions("v1.6.47", "631a4c58ea6c10c81f160c4b21fa8495b715d251698ebc2552077e8450f30454") + add_versions("v1.6.46", "767b01936f9620d4ab4cdf6ec348f6526f861f825648b610b1d604167dc738d2") + add_versions("v1.6.44", "0ef5b633d0c65f780c4fced27ff832998e71478c13b45dfb6e94f23a82f64f7c") + add_versions("v1.6.43", "fecc95b46cf05e8e3fc8a414750e0ba5aad00d89e9fdf175e94ff041caf1a03a") + add_versions("v1.6.42", "fe89de292e223829859d21990d9c4d6b7e30e295a268f6a53a022611aa98bd67") + add_versions("v1.6.40", "62d25af25e636454b005c93cae51ddcd5383c40fa14aa3dae8f6576feb5692c2") + add_versions("v1.6.37", "ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307") + add_versions("v1.6.36", "5bef5a850a9255365a2dc344671b7e9ef810de491bd479c2506ac3c337e2d84f") + add_versions("v1.6.35", "6d59d6a154ccbb772ec11772cb8f8beb0d382b61e7ccc62435bf7311c9f4b210") + add_versions("v1.6.34", "e45ce5f68b1d80e2cb9a2b601605b374bdf51e1798ef1c2c2bd62131dfcf9eef") + + if is_plat("wasm") then + add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true}) + add_deps("zlib") + else + add_deps("zlib") + end + + if is_plat("linux") then + add_syslinks("m") + end + + if is_plat("mingw") and is_subhost("msys") then + add_extsources("pacman::libpng") + elseif is_plat("linux") then + add_extsources("pacman::libpng", "apt::libpng-dev") + elseif is_plat("macosx") then + add_extsources("brew::libpng") + end + + on_install(function (package) + if package:is_plat("android") and package:is_arch("armeabi-v7a") then + io.replace("arm/filter_neon.S", ".func", ".hidden", {plain = true}) + io.replace("arm/filter_neon.S", ".endfunc", "", {plain = true}) + end + os.cp("scripts/pnglibconf.h.prebuilt", "pnglibconf.h") + io.writefile("xmake.lua", [[ + add_rules("mode.debug", "mode.release") + add_requires("zlib") + target("png") + set_kind("$(kind)") + add_files("*.c|example.c|pngtest.c") + if is_arch("x86", "x64", "i386", "x86_64") then + add_files("intel/*.c") + add_defines("PNG_INTEL_SSE_OPT=1") + add_vectorexts("sse", "sse2") + elseif is_arch("arm.*") then + add_files("arm/*.c") + if is_plat("windows") then + add_defines("PNG_ARM_NEON_OPT=1") + add_defines("PNG_ARM_NEON_IMPLEMENTATION=1") + else + add_files("arm/*.S") + add_defines("PNG_ARM_NEON_OPT=2") + end + elseif is_arch("mips.*") then + add_files("mips/*.c") + add_defines("PNG_MIPS_MSA_OPT=2") + elseif is_arch("ppc.*") then + add_files("powerpc/*.c") + add_defines("PNG_POWERPC_VSX_OPT=2") + end + add_headerfiles("*.h") + add_packages("zlib") + if is_kind("shared") and is_plat("windows") then + add_defines("PNG_BUILD_DLL") + end + ]]) + import("package.tools.xmake").install(package) + end) + + on_test(function (package) + assert(package:has_cfuncs("png_create_read_struct", {includes = "png.h"})) + end) diff --git a/xmake/packages/m/mupdf/xmake.lua b/xmake/packages/m/mupdf/xmake.lua index de092ffc61..a413b03059 100644 --- a/xmake/packages/m/mupdf/xmake.lua +++ b/xmake/packages/m/mupdf/xmake.lua @@ -27,26 +27,31 @@ package("mupdf") if is_plat("linux", "macosx") then add_deps("pkg-config", "make", "libjpeg", "freetype", "libcurl", "zlib") + elseif is_plat("wasm") then + add_deps("freetype", {configs={png=true}}) -- 保持与 liii-pdfhummus 一致,避免多次安装 + add_deps("pkg-config", "make", "libjpeg", "harfbuzz", "zlib") end on_load(function (package) if not is_plat("windows") then if is_plat("linux") then package:add("links", "mupdf", "mupdf-third", "harfbuzz") + elseif is_plat("wasm") then + package:add("links", "mupdf", "mupdf-third", "harfbuzz", "libjpeg", "freetype", "zlib") else package:add("links", "mupdf", "mupdf-third") end end end) - on_install("linux", "macosx", function (package) + on_install("linux", "macosx", "wasm", function (package) if is_plat("macosx") then io.writefile("user.make", "CFLAGS = -arch " .. package:targetarch()) -- Use pkg-config to detect system library io.replace("Makerules", "else ifeq ($(LINUX_OR_OPENBSD),yes)", "", {plain = true}) end -- Use system library from xmake to compat with other program - import("package.tools.make").build(package, { + local configs = { "install-libs", "USE_SYSTEM_LIBJPEG=yes", "USE_SYSTEM_FREETYPE=yes", @@ -55,7 +60,56 @@ package("mupdf") "tofu=yes", "tofu_cjk=yes", "prefix=" .. package:installdir() - }) + } + if not is_plat("wasm") then + import("package.tools.make").build(package, configs) + else + -- xmake seems not passing the right configs to mupdf's make + local cflags = {} + local ldflags = {} + local harfbuzz = package:dep("harfbuzz"):fetch() + local libjpeg = package:dep("libjpeg"):fetch() + local freetype = package:dep("freetype"):fetch() + local zlib = package:dep("zlib"):fetch() + local cc = package:tool("cc") + local cxx = package:tool("cxx") + local ld = package:tool("ld") + local ar = package:tool("ar") + local ranlib = package:tool("ranlib") + table.insert(configs, "CC=" .. cc) + table.insert(configs, "CXX=" .. cxx) + table.insert(configs, "LD=" .. ld) + table.insert(configs, "AR=" .. ar) + table.insert(configs, "RANLIB=" .. ranlib) + table.insert(configs, "USE_SYSTEM_HARFBUZZ=yes") + table.insert(cflags, "-matomics") + table.insert(cflags, "-mbulk-memory") + table.insert(cflags, "-pthread") + table.insert(ldflags, "-matomics") + table.insert(ldflags, "-mbulk-memory") + table.insert(ldflags, "-pthread") + table.insert(ldflags, "-sUSE_PTHREADS=1") + table.insert(ldflags, "-sSHARED_MEMORY=1") + table.insert(ldflags, "-DZ_PREFIX=OFF") + for _, pkg in ipairs({harfbuzz, libjpeg, freetype, zlib}) do + for _, dir in ipairs(pkg.sysincludedirs or {}) do + table.insert(cflags, "-I" .. dir) + end + for _, dir in ipairs(pkg.includedirs or {}) do + table.insert(cflags, "-I" .. dir) + end + for _, dir in ipairs(pkg.linkdirs or {}) do + table.insert(ldflags, "-L" .. dir) + end + end + os.execv("make verbose=yes", configs, { + envs = { + CFLAGS = table.concat(cflags, " "), + LDFLAGS = table.concat(ldflags, " ") + } + }) + os.execv("make", table.join({"install-libs"}, configs)) + end end) on_install("windows", function (package) @@ -80,4 +134,4 @@ package("mupdf") local output = format("platform/win32/%s/%s/", arch, build_type) os.cp(output .. "libmupdf.lib", package:installdir("lib")) os.cp(output .. "libthirdparty.lib", package:installdir("lib")) - end) + end) \ No newline at end of file diff --git a/xmake/requires.lua b/xmake/requires.lua index 195591bbc8..81c2b4d0f4 100644 --- a/xmake/requires.lua +++ b/xmake/requires.lua @@ -10,7 +10,9 @@ add_requires("goldfish", {system=false}) add_requires("liii-tbox", {system=false}) -add_requires("cpr", {system=false}) +if not is_plat("wasm") then + add_requires("cpr", {system=false}) +end -- QWK is built locally from 3rdparty/qwindowkitty, no external package needed if is_plat ("windows") then add_requires("libiconv "..LIBICONV_VERSION, {system=false}) @@ -26,7 +28,9 @@ elseif is_plat("linux") and (linuxos.name() == "fedora" or linuxos.name() == "rh add_requires("libcurl", {system=true}) else add_requires("libpng", {system=false}) - add_requires("libcurl", {system=false}) + if not is_plat("wasm") then + add_requires("libcurl", {system=false}) + end end add_requires("liii-pdfhummus", {system=false,configs={libpng=true,libjpeg=true}}) @@ -38,7 +42,7 @@ add_requires("argh v1.3.2") if has_config("qt_frontend") then QT6_VERSION="6.8.3" add_requires("qt6widgets "..QT6_VERSION) -else +elseif not is_plat("wasm") then -- WASM GLFW is in EMCC add_requires("glfw") end diff --git a/xmake/stem.lua b/xmake/stem.lua index 5c283e030f..49d737886d 100644 --- a/xmake/stem.lua +++ b/xmake/stem.lua @@ -17,6 +17,7 @@ stem_binary_linux = "moganstem" stem_dmg_bg_image = "mogan-background.png" stem_binary_macos = stem_binary_name stem_binary_windows = stem_binary_name .. ".exe" +stem_binary_wasm = "stem.js" stem_lab_name = "moganlab" stem_lab_big_name= "MoganLab" diff --git a/xmake/targets/libmogan.lua b/xmake/targets/libmogan.lua index 9bcf6eb1c0..b2189b9f64 100644 --- a/xmake/targets/libmogan.lua +++ b/xmake/targets/libmogan.lua @@ -75,7 +75,7 @@ target("libmogan") do add_defines("QTPIPES") set_configvar("USE_QT_PRINTER", 1) add_defines("USE_QT_PRINTER") - else + elseif not is_plat("wasm") then -- WASM GLFW is in EMCC add_packages("glfw") end @@ -83,7 +83,9 @@ target("libmogan") do add_packages("freetype") add_packages("goldfish") add_packages("liii-tbox") - add_packages("cpr") + if not is_plat("wasm") then + add_packages("cpr") + end add_packages("argh") if not is_plat("macosx") then add_packages("libiconv") @@ -148,10 +150,18 @@ target("libmogan") do set_configvar("CONFIG_OS", "") end - configvar_check_cxxsnippets( - "CONFIG_LARGE_POINTER", [[ - #include - static_assert(sizeof(void*) == 8, "");]]) + if not is_plat("wasm") then + configvar_check_cxxsnippets( + "CONFIG_LARGE_POINTER", [[ + #include + static_assert(sizeof(void*) == 8, "");]]) + else + -- WASM use 32 bit pointers + configvar_check_cxxsnippets( + "CONFIG_LARGE_POINTER", [[ + #include + static_assert(sizeof(void*) == 4, "");]]) + end add_configfiles( "$(projectdir)/src/System/tm_configure.hpp.xmake", { filename = "tm_configure.hpp", @@ -280,6 +290,7 @@ target("libmogan") do add_files("$(projectdir)/src/Plugins/Qt/moganqml.qrc") else add_files("$(projectdir)/src/Plugins/ImGui/**.cpp") + remove_files("$(projectdir)/TeXmacs/plugins/goldfish/src/liii_http.cpp") end if is_plat("macosx") then diff --git a/xmake/targets/stem.lua b/xmake/targets/stem.lua index 18d25e575f..25c45fb549 100644 --- a/xmake/targets/stem.lua +++ b/xmake/targets/stem.lua @@ -34,6 +34,8 @@ target("stem") do set_filename(stem_binary_linux) elseif is_plat("macosx") then set_filename(stem_binary_macos) + elseif is_plat("wasm") then + set_filename(stem_binary_wasm) else set_filename(stem_binary_windows) end @@ -96,6 +98,17 @@ target("stem") do set_runtimes("MT") add_ldflags("/STACK:16777216") end + + if is_plat("wasm") then + add_cxxflags("-O3") + add_cxxflags("-sUSE_GLFW=3") + add_ldflags("-O3") + add_ldflags("-sUSE_GLFW=3") + add_ldflags("-sINITIAL_MEMORY=512MB") + add_ldflags("-sALLOW_MEMORY_GROWTH=1") + add_ldflags("-sSTACK_SIZE=32MB", {force = true}) + add_ldflags("--preload-file=" .. path.join(os.projectdir(), "TeXmacs") .. "@/TeXmacs") + end if has_config("qt_frontend") then -- Qt frontend: build stem as a Qt app. @@ -110,7 +123,7 @@ target("stem") do add_packages("goldfish") add_packages("mupdf") - if not has_config("qt_frontend") then + if not has_config("qt_frontend") and not is_plat("wasm") then -- WASM GLFW is in EMCC add_packages("glfw") end add_deps("liblolly") @@ -221,6 +234,12 @@ target("stem") do os.execv(binary, params, {envs={TEXMACS_PATH= path.join(os.projectdir(), "TeXmacs")}}) elseif is_plat("windows") then os.execv(binary, params) + elseif is_plat("wasm") then + local build_dir = path.absolute(path.directory(binary)) + os.execv("python3", { + path.join(os.projectdir(), "bin/wasm_server.py"), + build_dir + }) else print("Unsupported platform: " .. tostring(os.host())) end