diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5cb4553 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: Chromium +SortIncludes: false diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d3afd9b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.DS_Store +.vscode +.idea +.rollup.cache +.vercel +tsconfig.tsbuildinfo + +node_modules +build +dist diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..e19bebf --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,122 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/strict" + ], + "parserOptions": { + "ecmaVersion": "latest", + "project": "tsconfig.json" + }, + "plugins": [ + "@typescript-eslint", + "import", + "react", + "jsx-a11y", + "react-refresh", + "no-array-concat" + ], + "ignorePatterns": [ + "node_modules", + "boost", + "librime", + "schema", + "public", + "build", + "dist" + ], + "settings": { + "import/core-modules": ["bun", "react", "react-dom"] + }, + "rules": { + "default-case-last": "error", + "eqeqeq": "error", + "func-style": ["error", "declaration"], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "logical-assignment-operators": "error", + "no-array-concat/no-array-concat": "error", + "no-constant-binary-expression": "error", + "no-control-regex": "off", + "no-irregular-whitespace": ["error", { + "skipStrings": true, + "skipComments": true, + "skipRegExps": true, + "skipTemplates": true, + "skipJSXText": true + }], + "no-negated-condition": "warn", + "no-self-compare": "error", + "no-template-curly-in-string": "warn", + "no-undef-init": "error", + "no-unmodified-loop-condition": "warn", + "no-unneeded-ternary": "error", + "no-unreachable-loop": "warn", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-return": "error", + "no-var": "error", + "operator-assignment": "error", + "prefer-const": "error", + "prefer-exponentiation-operator": "error", + "prefer-object-spread": "error", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-template": "warn", + + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/default-param-last": "error", + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/no-loop-func": "warn", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-useless-empty-export": "error", + "@typescript-eslint/parameter-properties": ["error", { + "prefer": "parameter-property" + }], + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/prefer-regexp-exec": "error", + "@typescript-eslint/prefer-string-starts-ends-with": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-plus-operands": ["error", { + "allowAny": true, + "allowBoolean": true, + "allowNumberAndString": true + }], + "@typescript-eslint/restrict-template-expressions": ["error", { + "allowAny": true, + "allowBoolean": true, + "allowNumber": true, + "allowNever": true + }], + + "import/consistent-type-specifier-style": ["error", "prefer-top-level"], + "import/extensions": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-anonymous-default-export": ["warn", { "allowCallExpression": false }], + "import/no-extraneous-dependencies": ["error", { "includeInternal": true, "includeTypes": true }], + "import/no-named-default": "error", + "import/no-self-import": "error", + "import/no-useless-path-segments": ["error", { "noUselessIndex": true }], + "import/order": ["error", { + "groups": ["builtin", "external", "internal", ["parent", "sibling", "index"], "type", "unknown", "object"], + "newlines-between": "always", + "alphabetize": { "order": "asc", "orderImportKind": "asc", "caseInsensitive": true }, + "warnOnUnassignedImports": true + }], + + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + + "react-refresh/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c26c29f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,89 @@ +name: Build +on: + workflow_call: + pull_request: +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, macos-14, windows-latest] + steps: + - name: Checkout Latest Commit + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install Ubuntu Dependencies + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt update + sudo apt upgrade -y + sudo apt install -y \ + ninja-build \ + libboost-dev \ + libboost-regex-dev \ + libyaml-cpp-dev \ + libleveldb-dev \ + libmarisa-dev \ + libopencc-dev + echo "CC=/usr/bin/clang" >> $GITHUB_ENV + echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV + - name: Install macOS Dependencies + if: ${{ startsWith(matrix.os, 'macos') }} + run: | + brew install ninja + - name: Install Windows Dependencies + if: ${{ matrix.os == 'windows-latest' }} + run: | + choco upgrade -y llvm + pip install ninja + echo "$env:ProgramFiles\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Setup Emscripten SDK + uses: mymindstorm/setup-emsdk@v14 + - name: Install Package Dependencies + run: | + bun i + - name: Prepare Boost + run: | + bun run boost + - name: Build Native + run: | + bun run native + - name: Build Schema + run: | + bun run schema + - name: Build Library + run: | + bun run lib + - name: Build WebAssembly + run: | + bun run wasm + - name: Build App + run: | + bun run build + docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout Latest Commit + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build App + run: | + docker build -t typeduck-web --no-cache . + - name: Compress Files + run: | + mv dist TypeDuck-Web + tar -cvf TypeDuck-Web.tar TypeDuck-Web + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: TypeDuck-Web + path: TypeDuck-Web.tar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..607bdd8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release +on: + push: + branches: + - main +jobs: + build: + uses: ./.github/workflows/build.yml + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + name: TypeDuck-Web + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: | + tar -xvf TypeDuck-Web.tar + zip -r TypeDuck-Web.zip TypeDuck-Web + gh release upload latest TypeDuck-Web.zip --clobber diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36878c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +.vscode +!.vscode/extensions.json +.idea +.rollup.cache +.vercel +tsconfig.tsbuildinfo + +node_modules +boost-*.tar.xz +boost +build +public +dist diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0591590 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "librime"] + path = librime + url = https://github.com/TypeDuck-HK/librime.git +[submodule "schema"] + path = schema + url = https://github.com/TypeDuck-HK/schema.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9e9ce3d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM oven/bun as builder +FROM emscripten/emsdk +FROM nginx:stable-alpine-slim + +ARG ENABLE_LOGGING=ON +ENV ENABLE_LOGGING ${ENABLE_LOGGING} + +RUN apt update +RUN apt upgrade -y +RUN apt install -y \ + git \ + cmake \ + ninja-build \ + libboost-dev \ + libboost-regex-dev \ + libyaml-cpp-dev \ + libleveldb-dev \ + libmarisa-dev \ + libopencc-dev + +COPY / /TypeDuck-Web +WORKDIR /TypeDuck-Web + +RUN bun i +RUN bun run boost +RUN bun run native +RUN bun run schema +RUN bun run lib +RUN bun run wasm +RUN bun run build + +COPY --from=builder /TypeDuck-Web/dist /usr/share/nginx/html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..364fef3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, TypeDuck Team + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..2d94fcb Binary files /dev/null and b/bun.lockb differ diff --git a/dprint.jsonc b/dprint.jsonc new file mode 100644 index 0000000..1a00b37 --- /dev/null +++ b/dprint.jsonc @@ -0,0 +1,57 @@ +// Adopt from https://github.com/microsoft/TypeScript with minor changes +{ + "indentWidth": 4, + "lineWidth": 1000, + "newLineKind": "crlf", + "useTabs": true, + "typescript": { + "semiColons": "prefer", + "quoteStyle": "preferDouble", + "quoteProps": "consistent", + "useBraces": "whenNotSingleLine", + "bracePosition": "sameLineUnlessHanging", + "singleBodyPosition": "sameLine", + "nextControlFlowPosition": "nextLine", + "trailingCommas": "onlyMultiLine", + "operatorPosition": "nextLine", + "preferHanging": false, + + "arrowFunction.useParentheses": "preferNone", + "jsx.bracketPosition": "sameLine", + "jsx.forceNewLinesSurroundingContent": false, + "jsx.multiLineParens": "never", + "functionExpression.spaceAfterFunctionKeyword": true, + "constructorType.spaceAfterNewKeyword": true, + "constructSignature.spaceAfterNewKeyword": true, + + // Let ESLint handle this. + "module.sortImportDeclarations": "maintain", + "module.sortExportDeclarations": "maintain", + "exportDeclaration.sortNamedExports": "maintain", + "importDeclaration.sortNamedImports": "maintain" + }, + "json": { + "trailingCommas": "never" + }, + "markdown": {}, + "prettier": { + "associations": [ + "**/*.{html,htm,xhtml,css,scss,sass,less,graphql,graphqls,gql,yaml,yml}" + ] + }, + "excludes": [ + "node_modules", + "boost", + "librime", + "schema", + "public", + "build", + "dist" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.89.3.wasm", + "https://plugins.dprint.dev/json-0.19.2.wasm", + "https://plugins.dprint.dev/markdown-0.16.4.wasm", + "https://plugins.dprint.dev/prettier-0.39.0.json@896b70f29ef8213c1b0ba81a93cee9c2d4f39ac2194040313cd433906db7bc7c" + ] +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..1f23d40 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + TypeDuck 網頁版 + + + + + + + + + + + + + + +
+ + + diff --git a/librime b/librime new file mode 160000 index 0000000..10bd5ad --- /dev/null +++ b/librime @@ -0,0 +1 @@ +Subproject commit 10bd5ade99fb7626ef9fd6551b2f1cf8166f17fa diff --git a/package.json b/package.json new file mode 100644 index 0000000..1d473de --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "typeduck-web", + "version": "0.0.0", + "description": "TypeDuck 網頁版", + "type": "module", + "scripts": { + "boost": "bun scripts/prepare_boost.ts", + "native": "bun scripts/build_native.ts", + "schema": "bun scripts/build_schema.ts", + "lib": "bun scripts/build_lib.ts", + "wasm": "bun scripts/build_wasm.ts", + "start": "vite --host", + "build": "NODE_ENV=production bun run worker && vite build", + "worker": "rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript", + "preview": "vite preview --host" + }, + "dependencies": { + "chiron-hei-hk-webfont": "^2.5.10", + "chiron-sung-hk-webfont": "^1.0.11", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-toastify": "^10.0.5", + "react-use": "^17.5.0", + "textarea-caret": "^3.1.0" + }, + "devDependencies": { + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-typescript": "^11.1.6", + "@types/bun": "^1.1.2", + "@types/emscripten": "^1.39.12", + "@types/react": "^18.3.2", + "@types/react-dom": "^18.3.0", + "@types/textarea-caret": "^3.0.3", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", + "@vitejs/plugin-react-swc": "^3.6.0", + "autoprefixer": "^10.4.19", + "daisyui": "^3.9.4", + "esbuild": "^0.21.3", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-no-array-concat": "^0.1.2", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "postcss": "^8.4.38", + "rollup": "^4.17.2", + "rollup-plugin-esbuild": "^6.1.1", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.5", + "vite": "^5.2.11" + }, + "license": "BSD-3-Clause" +} diff --git a/patches/glog.patch b/patches/glog.patch new file mode 100644 index 0000000..56dc21c --- /dev/null +++ b/patches/glog.patch @@ -0,0 +1,272 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b787631..d8bda34 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -43,6 +43,8 @@ option (WITH_TLS "Enable Thread Local Storage (TLS) support" ON) + set (WITH_UNWIND libunwind CACHE STRING "unwind driver") + set_property (CACHE WITH_UNWIND PROPERTY STRINGS none unwind libunwind) + ++add_definitions(-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.) ++ + cmake_dependent_option (WITH_GMOCK "Use Google Mock" ON WITH_GTEST OFF) + + set (WITH_FUZZING none CACHE STRING "Fuzzing engine") +diff --git a/src/glog/logging.h b/src/glog/logging.h +index 9ab897e..a7b2564 100644 +--- a/src/glog/logging.h ++++ b/src/glog/logging.h +@@ -358,9 +358,9 @@ struct [[deprecated("Use LogMessage instead.")]] LogMessageInfo { + // better to have compact code for these operations. + + #if GOOGLE_STRIP_LOG == 0 +-# define COMPACT_GOOGLE_LOG_INFO google::LogMessage(__FILE__, __LINE__) ++# define COMPACT_GOOGLE_LOG_INFO google::LogMessage(__FILE_NAME__, __LINE__) + # define LOG_TO_STRING_INFO(message) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_INFO, message) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_INFO, message) + #else + # define COMPACT_GOOGLE_LOG_INFO google::NullStream() + # define LOG_TO_STRING_INFO(message) google::NullStream() +@@ -368,9 +368,9 @@ struct [[deprecated("Use LogMessage instead.")]] LogMessageInfo { + + #if GOOGLE_STRIP_LOG <= 1 + # define COMPACT_GOOGLE_LOG_WARNING \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_WARNING) + # define LOG_TO_STRING_WARNING(message) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING, message) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_WARNING, message) + #else + # define COMPACT_GOOGLE_LOG_WARNING google::NullStream() + # define LOG_TO_STRING_WARNING(message) google::NullStream() +@@ -378,18 +378,18 @@ struct [[deprecated("Use LogMessage instead.")]] LogMessageInfo { + + #if GOOGLE_STRIP_LOG <= 2 + # define COMPACT_GOOGLE_LOG_ERROR \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_ERROR) + # define LOG_TO_STRING_ERROR(message) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, message) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_ERROR, message) + #else + # define COMPACT_GOOGLE_LOG_ERROR google::NullStream() + # define LOG_TO_STRING_ERROR(message) google::NullStream() + #endif + + #if GOOGLE_STRIP_LOG <= 3 +-# define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal(__FILE__, __LINE__) ++# define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal(__FILE_NAME__, __LINE__) + # define LOG_TO_STRING_FATAL(message) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL, message) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_FATAL, message) + #else + # define COMPACT_GOOGLE_LOG_FATAL google::NullStreamFatal() + # define LOG_TO_STRING_FATAL(message) google::NullStreamFatal() +@@ -407,40 +407,40 @@ struct [[deprecated("Use LogMessage instead.")]] LogMessageInfo { + # define COMPACT_GOOGLE_LOG_DFATAL COMPACT_GOOGLE_LOG_ERROR + #elif GOOGLE_STRIP_LOG <= 3 + # define COMPACT_GOOGLE_LOG_DFATAL \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL) ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_FATAL) + #else + # define COMPACT_GOOGLE_LOG_DFATAL google::NullStreamFatal() + #endif + + #define GOOGLE_LOG_INFO(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_INFO, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_INFO, counter, \ + &google::LogMessage::SendToLog) + #define SYSLOG_INFO(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_INFO, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_INFO, counter, \ + &google::LogMessage::SendToSyslogAndLog) + #define GOOGLE_LOG_WARNING(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_WARNING, counter, \ + &google::LogMessage::SendToLog) + #define SYSLOG_WARNING(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_WARNING, counter, \ + &google::LogMessage::SendToSyslogAndLog) + #define GOOGLE_LOG_ERROR(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_ERROR, counter, \ + &google::LogMessage::SendToLog) + #define SYSLOG_ERROR(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_ERROR, counter, \ + &google::LogMessage::SendToSyslogAndLog) + #define GOOGLE_LOG_FATAL(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_FATAL, counter, \ + &google::LogMessage::SendToLog) + #define SYSLOG_FATAL(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_FATAL, counter, \ + &google::LogMessage::SendToSyslogAndLog) + #define GOOGLE_LOG_DFATAL(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::DFATAL_LEVEL, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::DFATAL_LEVEL, counter, \ + &google::LogMessage::SendToLog) + #define SYSLOG_DFATAL(counter) \ +- google::LogMessage(__FILE__, __LINE__, google::DFATAL_LEVEL, counter, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::DFATAL_LEVEL, counter, \ + &google::LogMessage::SendToSyslogAndLog) + + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ +@@ -457,7 +457,7 @@ struct [[deprecated("Use LogMessage instead.")]] LogMessageInfo { + std::unique_ptr release{message, \ + &LocalFree}; \ + if (message_length > 0) { \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, 0, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_ERROR, 0, \ + &google::LogMessage::SendToLog) \ + .stream() \ + << reinterpret_cast(message); \ +@@ -546,11 +546,11 @@ class LogSink; // defined below + // LogSeverity severity; + // The cast is to disambiguate nullptr arguments. + #define LOG_TO_SINK(sink, severity) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + static_cast(sink), true) \ + .stream() + #define LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink, severity) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + static_cast(sink), false) \ + .stream() + +@@ -766,7 +766,7 @@ using _Check_string = std::string; + google::logging::internal::GetReferenceableValue(val1), \ + google::logging::internal::GetReferenceableValue(val2), \ + #val1 " " #op " " #val2)) \ +- log(__FILE__, __LINE__, \ ++ log(__FILE_NAME__, __LINE__, \ + google::logging::internal::CheckOpString(std::move(_result))) \ + .stream() + #else +@@ -778,7 +778,7 @@ using _Check_string = std::string; + google::logging::internal::GetReferenceableValue(val1), \ + google::logging::internal::GetReferenceableValue(val2), \ + #val1 " " #op " " #val2)) \ +- log(__FILE__, __LINE__, _result).stream() ++ log(__FILE_NAME__, __LINE__, _result).stream() + #endif // STATIC_ANALYSIS, DCHECK_IS_ON() + + #if GOOGLE_STRIP_LOG <= 3 +@@ -819,7 +819,7 @@ using _Check_string = std::string; + + #define CHECK_NOTNULL(val) \ + google::logging::internal::CheckNotNull( \ +- __FILE__, __LINE__, "'" #val "' Must be non nullptr", (val)) ++ __FILE_NAME__, __LINE__, "'" #val "' Must be non nullptr", (val)) + + // Helper functions for string comparisons. + // To avoid bloat, the definitions are in logging.cc. +@@ -881,7 +881,7 @@ DECLARE_CHECK_STROP_IMPL(strcasecmp, false) + #define PLOG(severity) GOOGLE_PLOG(severity, 0).stream() + + #define GOOGLE_PLOG(severity, counter) \ +- google::ErrnoLogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::ErrnoLogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + counter, &google::LogMessage::SendToLog) + + #define PLOG_IF(severity, condition) \ +@@ -933,9 +933,9 @@ namespace google { + std::chrono::duration(seconds)); \ + static std::atomic LOG_PREVIOUS_TIME_RAW; \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_TIME_PERIOD, sizeof(google::int64), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_TIME_PERIOD, sizeof(google::int64), "")); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_PREVIOUS_TIME_RAW, sizeof(google::int64), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_PREVIOUS_TIME_RAW, sizeof(google::int64), "")); \ + const auto LOG_CURRENT_TIME = \ + std::chrono::duration_cast( \ + std::chrono::steady_clock::now().time_since_epoch()); \ +@@ -949,54 +949,54 @@ namespace google { + .count(), \ + std::memory_order_relaxed); \ + if (LOG_TIME_DELTA > LOG_TIME_PERIOD) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity).stream() ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity).stream() + + #define SOME_KIND_OF_LOG_EVERY_N(severity, n, what_to_do) \ + static std::atomic LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ + ++LOG_OCCURRENCES; \ + if (++LOG_OCCURRENCES_MOD_N > n) LOG_OCCURRENCES_MOD_N -= n; \ + if (LOG_OCCURRENCES_MOD_N == 1) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + LOG_OCCURRENCES, &what_to_do) \ + .stream() + + #define SOME_KIND_OF_LOG_IF_EVERY_N(severity, condition, n, what_to_do) \ + static std::atomic LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ + ++LOG_OCCURRENCES; \ + if ((condition) && \ + ((LOG_OCCURRENCES_MOD_N = (LOG_OCCURRENCES_MOD_N + 1) % n) == (1 % n))) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + LOG_OCCURRENCES, &what_to_do) \ + .stream() + + #define SOME_KIND_OF_PLOG_EVERY_N(severity, n, what_to_do) \ + static std::atomic LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \ + ++LOG_OCCURRENCES; \ + if (++LOG_OCCURRENCES_MOD_N > n) LOG_OCCURRENCES_MOD_N -= n; \ + if (LOG_OCCURRENCES_MOD_N == 1) \ +- google::ErrnoLogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::ErrnoLogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + LOG_OCCURRENCES, &what_to_do) \ + .stream() + + #define SOME_KIND_OF_LOG_FIRST_N(severity, n, what_to_do) \ + static std::atomic LOG_OCCURRENCES(0); \ + GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized( \ +- __FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ ++ __FILE_NAME__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \ + if (LOG_OCCURRENCES <= n) ++LOG_OCCURRENCES; \ + if (LOG_OCCURRENCES <= n) \ +- google::LogMessage(__FILE__, __LINE__, google::GLOG_##severity, \ ++ google::LogMessage(__FILE_NAME__, __LINE__, google::GLOG_##severity, \ + LOG_OCCURRENCES, &what_to_do) \ + .stream() + +@@ -1418,7 +1418,7 @@ class GLOG_EXPORT LogMessageFatal : public LogMessage { + // A non-macro interface to the log facility; (useful + // when the logging level is not a compile-time constant). + inline void LogAtLevel(LogSeverity severity, std::string const& msg) { +- LogMessage(__FILE__, __LINE__, severity).stream() << msg; ++ LogMessage(__FILE_NAME__, __LINE__, severity).stream() << msg; + } + + // A macro alternative of LogAtLevel. New code may want to use this +@@ -1426,7 +1426,7 @@ inline void LogAtLevel(LogSeverity severity, std::string const& msg) { + // file name and the line number where this macro is put like other + // LOG macros, 2. this macro can be used as C++ stream. + #define LOG_AT_LEVEL(severity) \ +- google::LogMessage(__FILE__, __LINE__, severity).stream() ++ google::LogMessage(__FILE_NAME__, __LINE__, severity).stream() + + // Allow folks to put a counter in the LOG_EVERY_X()'ed messages. This + // only works if ostream is a LogStream. If the ostream is not a diff --git a/patches/leveldb.patch b/patches/leveldb.patch new file mode 100644 index 0000000..8084e5e --- /dev/null +++ b/patches/leveldb.patch @@ -0,0 +1,12 @@ +diff --git a/util/env_posix.cc b/util/env_posix.cc +index d84cd1e..773c8cd 100644 +--- a/util/env_posix.cc ++++ b/util/env_posix.cc +@@ -781,6 +781,7 @@ PosixEnv::PosixEnv() + void PosixEnv::Schedule( + void (*background_work_function)(void* background_work_arg), + void* background_work_arg) { ++ return background_work_function(background_work_arg); + background_work_mutex_.Lock(); + + // Start the background thread, if we haven't done so already. diff --git a/patches/librime.patch b/patches/librime.patch new file mode 100644 index 0000000..7bcdc4e --- /dev/null +++ b/patches/librime.patch @@ -0,0 +1,13 @@ +diff --git a/include/darts.h b/include/darts.h +index dd73cf1b..69a7328a 100644 +--- a/include/darts.h ++++ b/include/darts.h +@@ -16,7 +16,7 @@ + #define DARTS_LINE_TO_STR(line) DARTS_INT_TO_STR(line) + #define DARTS_LINE_STR DARTS_LINE_TO_STR(__LINE__) + #define DARTS_THROW(msg) throw Darts::Details::Exception( \ +- __FILE__ ":" DARTS_LINE_STR ": exception: " msg) ++ __FILE_NAME__ ":" DARTS_LINE_STR ": exception: " msg) + + namespace Darts { + diff --git a/patches/marisa.patch b/patches/marisa.patch new file mode 100644 index 0000000..93052db --- /dev/null +++ b/patches/marisa.patch @@ -0,0 +1,15 @@ +diff --git a/include/marisa/exception.h b/include/marisa/exception.h +index 508c6b8..7f17c0a 100644 +--- a/include/marisa/exception.h ++++ b/include/marisa/exception.h +@@ -62,8 +62,8 @@ class Exception : public std::exception { + // code and an error message. The message format is as follows: + // "__FILE__:__LINE__: error_code: error_message" + #define MARISA_THROW(error_code, error_message) \ +- (throw marisa::Exception(__FILE__, __LINE__, error_code, \ +- __FILE__ ":" MARISA_LINE_STR ": " #error_code ": " error_message)) ++ (throw marisa::Exception(__FILE_NAME__, __LINE__, error_code, \ ++ __FILE_NAME__ ":" MARISA_LINE_STR ": " #error_code ": " error_message)) + + // MARISA_THROW_IF throws an exception if `condition' is true. + #define MARISA_THROW_IF(condition, error_code) \ diff --git a/patches/opencc.patch b/patches/opencc.patch new file mode 100644 index 0000000..9d61e1d --- /dev/null +++ b/patches/opencc.patch @@ -0,0 +1,50 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1acb75a..3faa727 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -152,12 +152,22 @@ add_definitions( + -DPACKAGE_NAME="${PACKAGE_NAME}" + ) + ++add_definitions(-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.) ++ ++if (EMSCRIPTEN) ++ add_definitions(-I"${CMAKE_CURRENT_SOURCE_DIR}/../../../build/sysroot/usr/include") ++else() ++ add_definitions(-I"${CMAKE_CURRENT_SOURCE_DIR}/../../include") ++endif() ++ + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_definitions( + -std=c++14 + -Wall + ) +- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") ++ if (NOT EMSCRIPTEN) ++ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") ++ endif () + if (CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-O0 -g3) + endif () +@@ -221,9 +231,6 @@ endif() + ######## Subdirectories + + add_subdirectory(src) +-add_subdirectory(doc) +-add_subdirectory(data) +-add_subdirectory(test) + + ######## Testing + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 75eda02..759fa85 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -202,7 +202,3 @@ endif() + if (ENABLE_BENCHMARK) + add_subdirectory(benchmark) + endif() +- +-# Subdir +- +-add_subdirectory(tools) diff --git a/rollup.config.ts b/rollup.config.ts new file mode 100644 index 0000000..3006992 --- /dev/null +++ b/rollup.config.ts @@ -0,0 +1,25 @@ +import json from "@rollup/plugin-json"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import esbuild from "rollup-plugin-esbuild"; + +import type { RollupOptions } from "rollup"; + +const isProduction = process.env.NODE_ENV === "production"; + +export default { + input: "src/worker.ts", + output: { + dir: "public", + sourcemap: !isProduction, + format: "iife", + }, + plugins: [ + json(), + nodeResolve(), + esbuild({ + target: "es2017", + sourceMap: !isProduction, + minify: isProduction, + }), + ], +} satisfies RollupOptions; diff --git a/schema b/schema new file mode 160000 index 0000000..a8efc16 --- /dev/null +++ b/schema @@ -0,0 +1 @@ +Subproject commit a8efc163e3947b6c319928aa69d6c1ff53d89a84 diff --git a/scripts/build_lib.ts b/scripts/build_lib.ts new file mode 100644 index 0000000..094286b --- /dev/null +++ b/scripts/build_lib.ts @@ -0,0 +1,132 @@ +import { $ } from "bun"; +import { argv, cwd } from "process"; + +import { patch } from "./utils"; + +const root = cwd(); +const ENABLE_LOGGING = import.meta.env["ENABLE_LOGGING"] ?? "ON"; +const BUILD_TYPE = import.meta.env["BUILD_TYPE"] ?? "Release"; +const CXXFLAGS = "-fexceptions -DBOOST_DISABLE_CURRENT_LOCATION"; +const DESTDIR = `${root}/build/sysroot`; +const CMAKE_FIND_ROOT_PATH = `${DESTDIR}/usr`; +const CMAKE_DEF = { + raw: `\ + -G Ninja \ + -DCMAKE_INSTALL_PREFIX:PATH=/usr \ + -DCMAKE_BUILD_TYPE:STRING=${BUILD_TYPE} \ + -DBUILD_SHARED_LIBS:BOOL=OFF \ + `, +}; + +$.env({ ...import.meta.env, CXXFLAGS, DESTDIR }); + +const targetHandlers = { + async "yaml-cpp"() { + console.log("Building yaml-cpp"); + const src = "librime/deps/yaml-cpp"; + const dst = "build/yaml-cpp"; + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} \ + ${CMAKE_DEF} \ + -DYAML_CPP_BUILD_CONTRIB:BOOL=OFF \ + -DYAML_CPP_BUILD_TESTS:BOOL=OFF \ + -DYAML_CPP_BUILD_TOOLS:BOOL=OFF \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "leveldb"() { + console.log("Building leveldb"); + const src = "librime/deps/leveldb"; + const dst = "build/leveldb"; + await patch("leveldb.patch", src); + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} \ + ${CMAKE_DEF} \ + -DLEVELDB_BUILD_BENCHMARKS:BOOL=OFF \ + -DLEVELDB_BUILD_TESTS:BOOL=OFF \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "marisa"() { + console.log("Building marisa-trie"); + const src = "librime/deps/marisa-trie"; + const dst = "build/marisa-trie"; + await patch("marisa.patch", src); + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} ${CMAKE_DEF}`; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "opencc"() { + console.log("Building opencc"); + const src = "librime/deps/opencc"; + const dst = "build/opencc"; + await patch("opencc.patch", src); + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} \ + ${CMAKE_DEF} \ + -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} \ + -DSHARE_INSTALL_PREFIX:PATH=/usr/share/rime-data/ \ + -DENABLE_DARTS:BOOL=OFF \ + -DUSE_SYSTEM_MARISA:BOOL=ON \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "glog"() { + if (ENABLE_LOGGING !== "ON") { + console.log("Skip glog"); + return; + } + console.log("Building glog"); + const src = "librime/deps/glog"; + const dst = "build/glog"; + await patch("glog.patch", src); + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} \ + ${CMAKE_DEF} \ + -DWITH_GFLAGS:BOOL=OFF \ + -DBUILD_TESTING:BOOL=OFF \ + -DWITH_UNWIND:BOOL=OFF \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "rime"() { + console.log("Building librime"); + const src = "librime"; + const dst = "build/librime_wasm"; + await patch("librime.patch", src); + await $`rm -rf ${dst}`; + await $`emcmake cmake ${src} -B ${dst} \ + ${CMAKE_DEF} \ + -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} \ + -DBUILD_TEST:BOOL=OFF \ + -DBUILD_STATIC:BOOL=ON \ + -DENABLE_THREADING:BOOL=OFF \ + -DENABLE_TIMESTAMP:BOOL=OFF \ + -DENABLE_LOGGING:BOOL=${ENABLE_LOGGING} \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, +}; + +const buildTargets = new Set(argv.slice(2)); +const unknownTargets = buildTargets.difference(new Set(Object.keys(targetHandlers))); +if (unknownTargets.size) { + throw new Error(`Unknown targets: '${Array.from(unknownTargets).join("', '")}'`); +} + +for (const [target, handler] of Object.entries(targetHandlers)) { + if (!buildTargets.size || buildTargets.has(target)) { + await handler(); + } +} diff --git a/scripts/build_native.ts b/scripts/build_native.ts new file mode 100644 index 0000000..176832b --- /dev/null +++ b/scripts/build_native.ts @@ -0,0 +1,157 @@ +import { $ } from "bun"; +import { platform } from "os"; +import { argv, cwd, exit } from "process"; + +import { patch } from "./utils"; + +const root = cwd(); +const PLATFORM = platform(); + +const dst = "build"; +const dstRime = "build/librime_native"; + +const CMAKE_INSTALL_PREFIX = `"${root}/librime"`; +const CMAKE_DEF_COMMON = `\ + -G Ninja \ + -DCMAKE_BUILD_TYPE:STRING=Release ${ + PLATFORM === "win32" + ? `\ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_USER_MAKE_RULES_OVERRIDE:PATH="${root}/librime/cmake/c_flag_overrides.cmake" \ + -DCMAKE_USER_MAKE_RULES_OVERRIDE_CXX:PATH="${root}/librime/cmake/cxx_flag_overrides.cmake" \ + -DCMAKE_EXE_LINKER_FLAGS_INIT:STRING=-llibcmt \ + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=MultiThreaded` + : "" +}`; +const CMAKE_DEF = { + raw: `\ + -B ${dst} \ + ${CMAKE_DEF_COMMON} \ + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} \ + -DBUILD_SHARED_LIBS:BOOL=OFF \ + `, +}; +const CMAKE_DEF_RIME = { + raw: `\ + -B ${dstRime} \ + ${CMAKE_DEF_COMMON} \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + ${PLATFORM === "linux" ? "" : "-DBUILD_STATIC:BOOL=ON"} \ + -DBUILD_TEST:BOOL=OFF \ + -DBoost_INCLUDE_DIR:PATH="${root}/build/sysroot/usr/include" \ + -DENABLE_TIMESTAMP:BOOL=OFF \ + -DENABLE_LOGGING:BOOL=OFF \ + `, +}; + +if (PLATFORM !== "linux") { + $.env({ ...import.meta.env, BOOST_ROOT: `${root}/boost` }); +} + +let hasError = false; + +const targetHandlers = { + async "yaml-cpp"() { + console.log("Building yaml-cpp"); + $.cwd("librime/deps/yaml-cpp"); + await $`rm -rf ${dst}`; + await $`cmake . \ + ${CMAKE_DEF} \ + -DYAML_CPP_BUILD_CONTRIB:BOOL=OFF \ + -DYAML_CPP_BUILD_TESTS:BOOL=OFF \ + -DYAML_CPP_BUILD_TOOLS:BOOL=OFF \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "leveldb"() { + console.log("Building leveldb"); + $.cwd("librime/deps/leveldb"); + await $`rm -rf ${dst}`; + await $`cmake . \ + ${CMAKE_DEF} \ + -DCMAKE_CXX_FLAGS:STRING=-Wno-error=deprecated-declarations \ + -DLEVELDB_BUILD_BENCHMARKS:BOOL=OFF \ + -DLEVELDB_BUILD_TESTS:BOOL=OFF \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "marisa"() { + console.log("Building marisa-trie"); + $.cwd("librime/deps/marisa-trie"); + await patch("marisa.patch"); + await $`rm -rf ${dst}`; + await $`cmake . ${CMAKE_DEF}`; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "opencc"() { + console.log("Building opencc"); + $.cwd("librime/deps/opencc"); + await patch("opencc.patch"); + await $`rm -rf ${dst}`; + await $`cmake . \ + ${CMAKE_DEF} \ + -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_INSTALL_PREFIX} \ + -DENABLE_DARTS:BOOL=OFF \ + -DUSE_SYSTEM_MARISA:BOOL=ON \ + `; + await $`cmake --build ${dst}`; + await $`cmake --install ${dst}`; + }, + + async "glog"() { + console.error(`'glog' need not be built in phase 'native'`); + hasError = true; + }, + + async "rime"() { + console.log("Building librime"); + $.cwd(); + await patch("librime.patch", "librime"); + await $`rm -rf ${dstRime}`; + await $`cmake librime ${CMAKE_DEF_RIME}`; + await $`cmake --build ${dstRime}`; + }, +}; + +function needNotBeBuilt(target: string) { + return async () => { + console.error(`'${target}' need not be built in Linux in phase 'native'`); + hasError = true; + }; +} + +const nonPosixTargets: (keyof typeof targetHandlers)[] = ["yaml-cpp", "leveldb", "marisa", "opencc"]; +if (PLATFORM === "linux") { + for (const target of nonPosixTargets) { + targetHandlers[target] = needNotBeBuilt(target); + } +} + +const buildTargets = new Set(argv.slice(2)); +const unknownTargets = buildTargets.difference(new Set(Object.keys(targetHandlers))); +if (unknownTargets.size) { + throw new Error(`Unknown targets: '${Array.from(unknownTargets).join("', '")}'`); +} + +for (const [target, handler] of Object.entries(targetHandlers)) { + if (buildTargets.has(target)) { + await handler(); + } +} +if (hasError) { + exit(1); +} + +if (!buildTargets.size) { + const allTargets: (keyof typeof targetHandlers)[] = [...(PLATFORM === "linux" ? [] : nonPosixTargets), "rime"]; + for (const handler of allTargets.map(target => targetHandlers[target])) { + await handler(); + } +} diff --git a/scripts/build_schema.ts b/scripts/build_schema.ts new file mode 100644 index 0000000..8e2c280 --- /dev/null +++ b/scripts/build_schema.ts @@ -0,0 +1,7 @@ +import { $ } from "bun"; +import { cwd } from "process"; + +const root = cwd(); +$.cwd("schema"); +await $`rm -rf build default.custom.yaml`; +await $`${root}/build/librime_native/bin/rime_api_console --build`; diff --git a/scripts/build_wasm.ts b/scripts/build_wasm.ts new file mode 100644 index 0000000..eb63b03 --- /dev/null +++ b/scripts/build_wasm.ts @@ -0,0 +1,44 @@ +import { $ } from "bun"; + +const libPath = "build/sysroot/usr/lib"; +const exportedFunctions = [ + "_init", + "_set_option", + "_process_key", + "_select_candidate", + "_delete_candidate", + "_flip_page", + "_customize", + "_deploy", +].join(); + +const compileArgs = { + raw: `\ + -std=c++17 \ + ${import.meta.env["BUILD_TYPE"] === "Debug" ? "-g" : "-O2 -DBOOST_DISABLE_ASSERTS -DBOOST_DISABLE_CURRENT_LOCATION"} \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s MAXIMUM_MEMORY=4GB \ + -s EXPORTED_FUNCTIONS=${exportedFunctions} \ + -s EXPORTED_RUNTIME_METHODS=["ccall","FS"] \ + --preload-file schema@/usr/share/rime-data \ + -I build/sysroot/usr/include \ + -o public/rime.js \ + `, +}; + +const linkArgs = { + raw: `\ + -fexceptions \ + -l idbfs.js \ + -L ${libPath} \ + -Wl,--whole-archive -l rime -Wl,--no-whole-archive \ + -l yaml-cpp \ + -l leveldb \ + -l marisa \ + -l opencc \ + ${(await Bun.file(`${libPath}/librime.a`).text()).includes("LogMessage") ? "-l glog" : ""} \ + `, +}; + +await $`mkdir -p public`; +await $`em++ -v ${compileArgs} wasm/api.cpp ${linkArgs}`; // --emit-tsd ${root}/src/rime.d.ts diff --git a/scripts/prepare_boost.ts b/scripts/prepare_boost.ts new file mode 100644 index 0000000..d98678a --- /dev/null +++ b/scripts/prepare_boost.ts @@ -0,0 +1,25 @@ +import { $ } from "bun"; +import { exists } from "fs/promises"; +import { platform } from "os"; + +const PLATFORM = platform(); +function bash(command: string) { + return PLATFORM === "win32" ? $`bash -c ${command}` : $`${{ raw: command }}`; +} + +const includeDir = "build/sysroot/usr/include"; +const boostVersion = "1.85.0"; +const archiveName = `boost-${boostVersion}-cmake.tar.xz`; + +if (!await exists(archiveName)) { + await Bun.write(archiveName, await fetch(`https://github.com/boostorg/boost/releases/download/boost-${boostVersion}/${archiveName}`)); + await $`rm -rf boost`; +} +if (!await exists("boost")) { + await bash(`tar -xvf ${archiveName}`); + await $`mv boost-${boostVersion} boost`; +} +await $`rm -rf ${includeDir}/boost`; +await $`mkdir -p ${includeDir}/boost`; +await bash(`cp -rf boost/libs/*/include/boost ${includeDir}`); +await bash(`cp -rf boost/libs/numeric/*/include/boost ${includeDir}`); diff --git a/scripts/utils.ts b/scripts/utils.ts new file mode 100644 index 0000000..d64397a --- /dev/null +++ b/scripts/utils.ts @@ -0,0 +1,11 @@ +import { $ } from "bun"; +import { cwd } from "process"; + +const root = cwd(); +export async function patch(patchFile: string, path?: string) { + if (path) $.cwd(path); + if (!await $`git status --porcelain -uno --ignore-submodules`.text()) { + await $`git apply ${root}/patches/${patchFile}`; + } + if (path) $.cwd(); +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..2d9f971 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,88 @@ +import { useEffect, useReducer, useState } from "react"; + +import { ToastContainer } from "react-toastify"; + +import CandidatePanel from "./CandidatePanel"; +import { NO_AUTO_FILL } from "./consts"; +import { usePreferences } from "./hooks"; +import Preferences from "./Preferences"; +import Rime, { subscribe } from "./rime"; +import ThemeSwitcher from "./ThemeSwitcher"; +import Toolbar from "./Toolbar"; +import { notify } from "./utils"; + +export default function App() { + const [textArea, setTextArea] = useState(null); + const [loading, setLoading] = useState(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(subscribe("initialized", success => { + if (!success) { + notify("error", "啟動輸入法引擎", "initializing the input method engine"); + } + setLoading(false); + })); + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(subscribe("deployStatusChanged", status => { + switch (status) { + case "start": + setLoading(true); + break; + case "failure": + notify("warning", "重新整理", "refreshing"); + // falls through + case "success": + setLoading(false); + break; + } + })); + + const [deployStatus, updateDeployStatus] = useReducer((n: number) => n + 1, 0); + const preferences = usePreferences(); + const { pageSize, enableCompletion, enableCorrection, enableSentence, enableLearning, isCangjie5 } = preferences; + useEffect(() => { + async function updateRimePreferences() { + setLoading(true); + let type: "warning" | "error" | undefined; + try { + const success = await Rime.customize({ pageSize, enableCompletion, enableCorrection, enableSentence, enableLearning, isCangjie5 }); + if (!(await Rime.deploy() && success)) { + type = "warning"; + } + } + catch { + type = "error"; + } + if (type) { + notify(type, "套用設定", "applying the settings"); + } + setLoading(false); + updateDeployStatus(); + } + void updateRimePreferences(); + }, [pageSize, enableCompletion, enableCorrection, enableSentence, enableLearning, isCangjie5, updateDeployStatus]); + + return <> +
+ + TypeDuck Logo + +

TypeDuck 網頁版

+ +
+
+ +