From 5dcfbfe175ea6bc8bb90c1ee6965080a480eb3ce Mon Sep 17 00:00:00 2001 From: Elvis Mao Date: Sun, 28 Sep 2025 20:06:09 +0800 Subject: [PATCH 1/3] feat: Send to justwriteNOW --- .github/workflows/static.yml | 13 +------- .prettierignore | 5 ++- pages/fontdrawer.js | 63 ++++++++++++++++++++++++++++++++++-- pages/index.html | 15 ++++++++- pages/ja.html | 13 ++++++++ 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index a340101..84e4386 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -1,28 +1,18 @@ -# Simple workflow for deploying static content to GitHub Pages name: Deploy static content to Pages on: - # Runs on pushes targeting the default branch push: branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - # workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + workflow_dispatch: permissions: contents: read pages: write id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false jobs: - # Single deploy job since we're just deploying deploy: environment: name: github-pages @@ -36,7 +26,6 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - # Upload entire repository path: './pages' - name: Deploy to GitHub Pages id: deployment diff --git a/.prettierignore b/.prettierignore index 22e8364..b216235 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,4 @@ -/* \ No newline at end of file +glist/ +pages/cglyphlist.js +pages/jglyphlist.js +pages/potrace.js \ No newline at end of file diff --git a/pages/fontdrawer.js b/pages/fontdrawer.js index 9f784be..c6871b2 100644 --- a/pages/fontdrawer.js +++ b/pages/fontdrawer.js @@ -387,6 +387,8 @@ $(document).ready(async function () { } $('#spanDoneCount').text(await countGlyphFromDB()); + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get("export") === "true") await sendRightNOW(); }).catch((error) => { console.error('IndexedDB 起動失敗', error); }); @@ -965,7 +967,8 @@ $(document).ready(async function () { }); // 儲存字型檔 - $('#downloadFontButton').on('click', async function () { + + async function loadWriting() { // 顯示進度條 $naviContainer.hide(); $progressContainer.show(); @@ -1048,7 +1051,11 @@ $(document).ready(async function () { gidMap[glyphF.name] = glyphs.length-1; } const font = await createFont(glyphs, gidMap, verts, ccmps); - + return font; + } + // 儲存字型檔 + $("#downloadFontButton").on("click", async function () { + const font = await loadWriting(); // 建立下載連結 const link = document.createElement('a'); link.download = font.names.windows.postScriptName.en + '.otf'; //'drawing.otf'; @@ -1060,6 +1067,53 @@ $(document).ready(async function () { $progressContainer.hide(); }); + let justwritenowBuffer; + + const sendRightNOW = async () => { + $('#download-container').hide(); + $('#justwritenow-container').show(); + $('#justwriteNOWConfirmButton').prop('disabled', true); + $('#justwritenow-status').text(fdrawer.exportPreparingMsg); + const font = await loadWriting(); + justwritenowBuffer = font.toArrayBuffer(); + $('#justwriteNOWConfirmButton').prop('disabled', false); + $('#justwritenow-status').text(fdrawer.exportReadyMsg); + }; + + $("#justwriteNOWButton").on("click", sendRightNOW); + + $("#justwriteNOWConfirmButton").on("click", async function () { + const host = "https://justwritenow.zeabur.app"; + const popup = window.open(`${host}/upload?send=But`, "_blank"); + if (!popup) { + alert(fdrawer.exportFailedMsg); + return; + } + + window.addEventListener("message", event => { + console.log("Parent received message from:", event.origin, "data:", event.data); + if (event.origin !== host) return; + + if (event.data === "ready") { + console.log("Received ready signal, sending buffer to popup"); + + const uint8Array = new Uint8Array(justwritenowBuffer); + const base64String = btoa(String.fromCharCode.apply(null, Array.from(uint8Array))); + + popup.postMessage( + { + bufferBase64: base64String, + byteLength: justwritenowBuffer.byteLength, + }, + event.origin + ); + } + }); + + $naviContainer.show(); + $progressContainer.hide(); + }); + // 顯示設定畫面 $('#settingButton').on('click', async function () { $('#settings-title').text(settings.notNewFlag ? fdrawer.settingsTitle : fdrawer.welcomeTitle); @@ -1183,6 +1237,11 @@ $(document).ready(async function () { $('#closeAdsButton').on('click', function () { $('#ads-container').hide(); }); + + // 關閉匯出至手寫誌畫面 + $('#closeJustWriteNOWButton').on('click', function () { + $('#justwritenow-container').hide(); + }); // 取得滑鼠或觸控座標 diff --git a/pages/index.html b/pages/index.html index 21aee78..6bf88db 100644 --- a/pages/index.html +++ b/pages/index.html @@ -156,6 +156,7 @@

下載字型

勾選測試輸出,字型名稱會加上流水號。避免系統快取造成在電腦上無法正常安裝使用的問題。
+

斗內作者

字型檔的權利均屬於您個人,不過如果您喜歡這個工具,請考慮斗內支持作者的開發工作。您的支持將有助於未來的更新和改進!
@@ -171,6 +172,15 @@

匯出編輯中資料

+
+
+ × +

正在傳送至手寫誌

+

處理中...

+ +
+
+ - - - - - - - - - - \ No newline at end of file + + + + + + + + + + diff --git a/pages/ja.html b/pages/ja.html index 29ddad0..8f0a094 100644 --- a/pages/ja.html +++ b/pages/ja.html @@ -1,218 +1,225 @@ - + - - - - フォントを書こう! by 字嗨 - - - -
- - - 👓 - 🔍 -
- -
- - - - 👈 - 👉 -
-
- - -
- -
-
- 🖌️🚿 - - -
- -
- 🚮 - ⬅️ - ➡️ - ⬆️ - ⬇️ - ↩️ -
- - -
- - 0% -
- -
-
- × -

グリフ一覧

-
+ + + + フォントを書こう! by 字嗨 + + + +
+ + + 👓 + 🔍
-
- -
-
- × -

ヒント Ver

-
    -
  • このツールは「@buttaiwan」が作りました。
  • -
  • すべてのデータはブラウザに保存されるため、未完成のフォントは定期的にバックアップすることをおすすめします。
  • -
  • 文字を書いたら、直ちに位置の調整を行うことをおすすめします。一般的に仮名と漢字は中央揃え、欧文はベースライン揃えです(赤いラインは目安)。
  • -
  • 半角文字は自動的にプロポーショナル幅で出力されるため、水平方向の位置は無視されます。
  • -
  • 全角の数字やアルファベットなどは、半角のグリフを用いて自動的に生成されます。
  • -
  • 横書き用・縦書き用のグリフが両方書かれている場合、縦書きの設定は自動的に行われます。
  • -
  • ダウンロードしたフォントファイルはAirDropなどでパソコンに転送してご利用ください。
  • -
  • 生成されたフォントのあらゆる権利は、利用者に属します。公開も商用利用もご自由にどうぞ。
  • -
  • 技術的制限上、現在このツールで生成されたOTFファイルは、インストールして利用すること自体は可能ですが、正確なCIDフォーマットではないため、Adobeアプリケーションなどでは日本語フォントとして認識されない恐れがあります。
  • -
  • 本システムは、ユーザーの筆跡やその他の情報を収集することはありません。ただし、フォントファイルを公開する場合は、筆跡を公開するリスクはご自身でご判断ください。
  • -
  • 本サービスの利用によって、利用者および第三者に生じた損害について、サービス提供者は責任を負わないものとします。
  • -
  • 本サービスは不定期に更新します。できるだけ互換性をキープしますが、全てのブラウザ・デバイスをカバーすることが不可能です。技術サポートは可能だが内容によっては有料です。
  • -
  • プロジェクトGitHubページにもご参照ください。

  • -
+ +
+ + + + 👈 + 👉 +
+
+ + +
+ +
+
+
+ 🖌️🚿 + + +
+ +
+ 🚮 + ⬅️ + ➡️ + ⬆️ + ⬇️ + ↩️
-
- -
-
- × -

フォント設定

- まずはフォントの基本情報を設定しましょう!
アプリの組み込みブラウザを利用している場合は、データ紛失を避けるためシステムブラウザで開いてください。
- -

フォント名(英語)

- - -

フォント名(日本語)

- - -

拡大率

- - 100% - 手書きの場合、枠をはみ出さないように書くと、どうしても文字がやや小さくなりがちですが、拡大率を設定して文字枠のサイズを調整できます。 - -

小さく書きたい!

- - マジで文字が小さい人にどうぞ。 - -

背景スタイル

- - -

全角文字もプロポーショナル幅

- - 手書きフォントの性格上、固定幅よりもプロポーショナル幅の方が自然に見える場合がありますが、縦書きとしての利用に不向きです。 - -

筆圧感度(実験中)

- - このオプションは筆圧対応デバイスでのみ有効です。現在調整中であり、将来的な変更により旧バージョンとの互換性が維持されない可能性があります。 - -

旧筆圧モードの有効化(非推奨)

- - 旧筆圧モードはブラシに対応しません。 - -
-
-
-
-

フォントデータを削除する

- -
-

システムデバッグ用機能

- -
+ + +
+ + 0% +
+ +
+
+ × +

グリフ一覧

+
-

-
- -
-
- × -

ダウンロード

- -

フォントのダウンロード

- - - テスト出力モードでは、フォント名に通し番号がつけられます。パソコンのフォントキャッシュによるインストールや使用の不具合を避けることができます。 -
- -

寄付のお願い

- 作成したフォントのあらゆる権利はあなたに属しますが、もしこのツールが役に立ったと感じたら、ぜひ寄付をお願いします!
- PayPalはこちら/ - または台湾ドル建てはこちら -
-
-
-
-

バックアップデータの読み込み

- -
-

データをバックアップする

- -
-

+ +
+
+ × +

ヒント Ver

+
    +
  • このツールは「@buttaiwan」が作りました。
  • +
  • すべてのデータはブラウザに保存されるため、未完成のフォントは定期的にバックアップすることをおすすめします。
  • +
  • 文字を書いたら、直ちに位置の調整を行うことをおすすめします。一般的に仮名と漢字は中央揃え、欧文はベースライン揃えです(赤いラインは目安)。
  • +
  • 半角文字は自動的にプロポーショナル幅で出力されるため、水平方向の位置は無視されます。
  • +
  • 全角の数字やアルファベットなどは、半角のグリフを用いて自動的に生成されます。
  • +
  • 横書き用・縦書き用のグリフが両方書かれている場合、縦書きの設定は自動的に行われます。
  • +
  • ダウンロードしたフォントファイルはAirDropなどでパソコンに転送してご利用ください。
  • +
  • 生成されたフォントのあらゆる権利は、利用者に属します。公開も商用利用もご自由にどうぞ。
  • +
  • + 技術的制限上、現在このツールで生成されたOTFファイルは、インストールして利用すること自体は可能ですが、正確なCIDフォーマットではないため、Adobeアプリケーションなどでは日本語フォントとして認識されない恐れがあります。 +
  • +
  • 本システムは、ユーザーの筆跡やその他の情報を収集することはありません。ただし、フォントファイルを公開する場合は、筆跡を公開するリスクはご自身でご判断ください。
  • +
  • 本サービスの利用によって、利用者および第三者に生じた損害について、サービス提供者は責任を負わないものとします。
  • +
  • 本サービスは不定期に更新します。できるだけ互換性をキープしますが、全てのブラウザ・デバイスをカバーすることが不可能です。技術サポートは可能だが内容によっては有料です。
  • +
  • + プロジェクトGitHubページにもご参照ください。 +

    +
  • +
+
+
+ +
+
+ × +

フォント設定

+ まずはフォントの基本情報を設定しましょう!
アプリの組み込みブラウザを利用している場合は、データ紛失を避けるためシステムブラウザで開いてください。
+ +

フォント名(英語)

+ + +

フォント名(日本語)

+ + +

拡大率

+ + 100% + 手書きの場合、枠をはみ出さないように書くと、どうしても文字がやや小さくなりがちですが、拡大率を設定して文字枠のサイズを調整できます。 + +

小さく書きたい!

+ + マジで文字が小さい人にどうぞ。 + +

背景スタイル

+ + +

全角文字もプロポーショナル幅

+ + 手書きフォントの性格上、固定幅よりもプロポーショナル幅の方が自然に見える場合がありますが、縦書きとしての利用に不向きです。 + +

筆圧感度(実験中)

+ + このオプションは筆圧対応デバイスでのみ有効です。現在調整中であり、将来的な変更により旧バージョンとの互換性が維持されない可能性があります。 + +

旧筆圧モードの有効化(非推奨)

+ + 旧筆圧モードはブラシに対応しません。 + +
+
+
+
+

フォントデータを削除する

+ +
+

システムデバッグ用機能

+ +
+
+

+
+
+ +
+
+ × +

ダウンロード

+ +

フォントのダウンロード

+ + + テスト出力モードでは、フォント名に通し番号がつけられます。パソコンのフォントキャッシュによるインストールや使用の不具合を避けることができます。 +
+ +

寄付のお願い

+ 作成したフォントのあらゆる権利はあなたに属しますが、もしこのツールが役に立ったと感じたら、ぜひ寄付をお願いします!
+ PayPalはこちら/ または台湾ドル建てはこちら +
+
+
+
+

バックアップデータの読み込み

+ +
+

データをバックアップする

+ +
+

+
-
- -
-
- × -

手書き誌へ送信中

-

処理中...

- + +
+
+ × +

手書き誌へ送信中

+

処理中...

+ +
-
- - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/pages/pressure-drawing.js b/pages/pressure-drawing.js index db29fd6..54a3033 100644 --- a/pages/pressure-drawing.js +++ b/pages/pressure-drawing.js @@ -4,570 +4,572 @@ */ class PressureDrawing { - constructor() { - this.perfectFreehandModule = null; - this.currentStroke = []; - this.isDrawing = false; - this.lastPoint = null; - this.hasPressureSupport = false; // 檢測是否支援筆壓 - this.pressureCheckCount = 0; // 用於檢測筆壓支援 - this.delayedStart = false; // 是否在延遲繪製狀態 - this.startPoint = null; // 起筆點 - this.moveThreshold = 5; // 移動閾值(像素) - } - - // Initialize the perfect-freehand module - async initialize() { - try { - this.perfectFreehandModule = await import('https://unpkg.com/perfect-freehand@1.2.2/dist/esm/index.mjs'); - return true; - } catch (error) { - return false; - } - } - - // Start a new stroke - startStroke(x, y, pressure = 0.5) { - this.isDrawing = true; - this.currentStroke = []; - this.lastPoint = { x, y, pressure }; - this.delayedStart = true; // 進入延遲繪製狀態 - this.startPoint = { x, y, pressure }; - this.currentStroke.push([x, y, pressure]); - } - - // Add a point to the current stroke - addPoint(x, y, pressure = 0.5) { - if (!this.isDrawing) return; - - // 檢查是否在延遲繪製狀態 - if (this.delayedStart && this.startPoint) { - const dx = x - this.startPoint.x; - const dy = y - this.startPoint.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - if (distance < this.moveThreshold) { - // 還沒有足夠的移動,只更新起始點的壓力 - this.startPoint.pressure = Math.max(this.startPoint.pressure, pressure); - this.currentStroke[0] = [this.startPoint.x, this.startPoint.y, this.startPoint.pressure]; - this.lastPoint = { x, y, pressure }; - return; - } else { - // 開始真正的繪製 - this.delayedStart = false; - } - } - - let isSimulatedPressure = false; - - // 如果沒有真實壓力支持且檢測計數足夠,才進行速度模擬 - if (pressure === 0.5 && this.lastPoint && !this.hasPressureSupport && this.pressureCheckCount > 3) { - const dx = x - this.lastPoint.x; - const dy = y - this.lastPoint.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - // Adjust pressure based on drawing speed (slower = more pressure) - const speedFactor = Math.min(1, 10 / Math.max(distance, 1)); - pressure = 0.4 + speedFactor * 0.4; // Range from 0.4 to 0.8,範圍較小避免極端值 - isSimulatedPressure = true; - } - - // 只對模擬的壓力值增強對比,讓效果更明顯 - if (isSimulatedPressure) { - if (pressure < 0.5) { - pressure = pressure * 0.9; // 低壓力稍微降低 - } else { - pressure = 0.4 + (pressure - 0.5) * 1.1; // 高壓力稍微增加 - } - } - - this.currentStroke.push([x, y, pressure]); - this.lastPoint = { x, y, pressure }; - } - - // Finish the current stroke and return the path - finishStroke(options = {}) { - if (!this.isDrawing) { - this.delayedStart = false; - this.startPoint = null; - return null; - } - - // 如果還在延遲繪製狀態,說明沒有足夠的移動,直接生成圓形點 - if (this.delayedStart && this.startPoint) { - this.isDrawing = false; - this.delayedStart = false; - const strokePoints = [[this.startPoint.x, this.startPoint.y, this.startPoint.pressure]]; - this.startPoint = null; - return this.generateCircularDot(strokePoints, options); - } - - // 如果筆跡太短(只有起始點),也生成圓形點 - if (this.currentStroke.length < 2) { - this.isDrawing = false; - this.delayedStart = false; - const strokePoints = [...this.currentStroke]; - this.startPoint = null; - return this.generateCircularDot(strokePoints, options); - } - - // 動態決定是否模擬壓力 - const shouldSimulatePressure = !this.hasPressureSupport && this.pressureCheckCount > 3; - - const defaultOptions = { - size: 12, - thinning: 0.8, // 增加壓力對粗細的影響 - smoothing: 0.5, - streamline: 0.3, // 減少流線化,讓壓力變化更明顯 - simulatePressure: shouldSimulatePressure, // 動態決定是否模擬壓力 - easing: (t) => t, - start: { - taper: 0, - easing: (t) => t, - cap: true - }, - end: { - taper: 25, // 大幅增加結尾的漸減效果 - easing: (t) => Math.sin((t * Math.PI) / 2), // 使用正弦緩動,更平滑 - cap: true // 使用圓頭來避免尖銳結尾 - } - }; - - const finalOptions = { ...defaultOptions, ...options }; - - try { - // 複製 stroke 點,避免修改原始資料 - let strokePoints = [...this.currentStroke]; - - // 壓力平滑處理 - if (strokePoints.length > 5) { - strokePoints = this.smoothPressureValues(strokePoints); - } - - - - // 檢查是否為靜止點或極短筆跡 - if (strokePoints.length <= 3) { - this.isDrawing = false; - this.currentStroke = []; - this.delayedStart = false; - this.startPoint = null; - return this.generateCircularDot(strokePoints, finalOptions); - } - - // 檢查筆跡是否在很小的範圍內 - const bounds = this.calculateBounds(strokePoints); - const maxDistance = Math.max(bounds.width, bounds.height); - - if (maxDistance < 8) { // 如果筆跡範圍小於 8 像素 - this.isDrawing = false; - this.currentStroke = []; - this.delayedStart = false; - this.startPoint = null; - return this.generateCircularDot(strokePoints, finalOptions); - } - - // Get stroke outline from perfect-freehand - const outlinePoints = this.perfectFreehandModule.getStroke(strokePoints, finalOptions); - - this.isDrawing = false; - this.currentStroke = []; - this.delayedStart = false; - this.startPoint = null; - - return outlinePoints; - } catch (error) { - this.isDrawing = false; - this.currentStroke = []; - this.delayedStart = false; - this.startPoint = null; - return null; - } - } - - // Smooth pressure values to create natural light-heavy-light curve - smoothPressureValues(strokePoints) { - if (!strokePoints || strokePoints.length < 5) return strokePoints; - - const points = [...strokePoints]; - const len = points.length; - - // Step 1: 移除開始和結尾的不穩定區域 - let startIndex = 0; - let endIndex = len; - - // 找到壓力開始穩定的位置 - for (let i = 2; i < Math.min(len - 2, 15); i++) { - const pressureVariance = this.calculatePressureVariance(points, i - 2, i + 2); - if (pressureVariance < 0.15) { // 放寬穩定標準 - startIndex = i; - break; - } - } - - // 找到壓力結束穩定的位置 - for (let i = len - 3; i >= Math.max(startIndex + 2, len - 15); i--) { - const pressureVariance = this.calculatePressureVariance(points, i - 2, i + 2); - if (pressureVariance < 0.15) { // 放寬穩定標準 - endIndex = i + 1; - break; - } - } - - // Step 2: 截取穩定區域 - const stablePoints = points.slice(startIndex, endIndex); - - if (stablePoints.length < 3) return points; // 如果穩定區域太小,返回原始數據 - - // Step 3: 對穩定區域進行移動平均平滑 - const smoothedPoints = this.applyMovingAverage(stablePoints); - - // Step 4: 創建自然的起筆和收筆 - const naturalCurve = this.createNaturalPressureCurve(smoothedPoints); - - return naturalCurve; - } - - // Calculate pressure variance in a range - calculatePressureVariance(points, start, end) { - if (start < 0 || end >= points.length || end - start < 2) return 999; - - const pressures = points.slice(start, end + 1).map(p => p[2]); - const mean = pressures.reduce((a, b) => a + b) / pressures.length; - const variance = pressures.reduce((acc, p) => acc + Math.pow(p - mean, 2), 0) / pressures.length; - - return Math.sqrt(variance); - } - - // Apply moving average to smooth pressure values - applyMovingAverage(points) { - if (points.length < 3) return points; - - const smoothed = []; - const windowSize = 5; // 增加窗口大小,讓平滑效果更明顯 - - for (let i = 0; i < points.length; i++) { - const start = Math.max(0, i - Math.floor(windowSize / 2)); - const end = Math.min(points.length - 1, i + Math.floor(windowSize / 2)); - - let sumPressure = 0; - let count = 0; - - for (let j = start; j <= end; j++) { - sumPressure += points[j][2]; - count++; - } - - const avgPressure = sumPressure / count; - smoothed.push([points[i][0], points[i][1], avgPressure]); - } - - return smoothed; - } - - // Create natural pressure curve with light start and end - createNaturalPressureCurve(points) { - if (points.length < 3) return points; - - const result = [...points]; - const len = result.length; - - // 找到壓力的峰值位置 - let maxPressure = 0; - let maxIndex = Math.floor(len / 2); - - for (let i = 0; i < len; i++) { - if (result[i][2] > maxPressure) { - maxPressure = result[i][2]; - maxIndex = i; - } - } - - // 創建自然的壓力曲線:輕 -> 重 -> 輕 - for (let i = 0; i < len; i++) { - let factor = 1.0; - - if (i < maxIndex) { - // 起筆段:從 0.3 漸增到 1.0 - factor = 0.3 + 0.7 * (i / maxIndex); - } else { - // 收筆段:從 1.0 漸減到 0.2 - factor = 1.0 - 0.8 * ((i - maxIndex) / (len - 1 - maxIndex)); - factor = Math.max(0.2, factor); - } - - // 應用漸變係數,但保持原始壓力的相對變化 - result[i][2] = result[i][2] * factor; - } - - return result; - } - - // Calculate bounds of stroke points - calculateBounds(strokePoints) { - if (!strokePoints || strokePoints.length === 0) { - return { width: 0, height: 0, minX: 0, maxX: 0, minY: 0, maxY: 0 }; - } - - let minX = strokePoints[0][0]; - let maxX = strokePoints[0][0]; - let minY = strokePoints[0][1]; - let maxY = strokePoints[0][1]; - - for (let i = 1; i < strokePoints.length; i++) { - const [x, y] = strokePoints[i]; - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - - return { - width: maxX - minX, - height: maxY - minY, - minX, maxX, minY, maxY - }; - } - - // Generate circular dot based on pressure - generateCircularDot(strokePoints, options) { - if (!strokePoints || strokePoints.length === 0) return []; - - // 計算中心點和平均壓力 - let centerX = 0; - let centerY = 0; - let totalPressure = 0; - - for (const point of strokePoints) { - centerX += point[0]; - centerY += point[1]; - totalPressure += point[2]; - } - - centerX /= strokePoints.length; - centerY /= strokePoints.length; - const avgPressure = totalPressure / strokePoints.length; - - // 根據壓力計算半徑 - const baseRadius = (options.size || 12) * 0.6; // 增加基礎半徑 - const radius = baseRadius * (0.4 + avgPressure * 0.8); // 增加最小和最大半徑 - - // 生成圓形的點 - const circlePoints = []; - const segments = 16; // 圓形分段數 - - for (let i = 0; i < segments; i++) { - const angle = (i * 2 * Math.PI) / segments; - const x = centerX + Math.cos(angle) * radius; - const y = centerY + Math.sin(angle) * radius; - circlePoints.push([x, y]); - } - - return circlePoints; - } - - // Convert stroke outline to SVG path - outlineToSVGPath(outlinePoints) { - if (!outlinePoints || outlinePoints.length < 2) return ''; - - const path = outlinePoints.reduce((acc, point, index) => { - const [x, y] = point; - if (index === 0) { - return `M${x},${y}`; - } - return `${acc}L${x},${y}`; - }, ''); - - return `${path}Z`; - } - - // Draw stroke outline on canvas - drawStrokeOnCanvas(ctx, outlinePoints, eraseMode = false) { - if (!outlinePoints || outlinePoints.length < 2) return; - - ctx.save(); - - // Set composite operation for erasing - ctx.globalCompositeOperation = eraseMode ? "destination-out" : "source-over"; - - // Create path from outline points - ctx.beginPath(); - outlinePoints.forEach((point, index) => { - const [x, y] = point; - if (index === 0) { - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - ctx.closePath(); - - // Fill the path - ctx.fillStyle = eraseMode ? 'rgba(0,0,0,1)' : 'black'; - ctx.fill(); - - ctx.restore(); - } - - // Get current stroke points (for preview) - getCurrentStrokePoints() { - return [...this.currentStroke]; - } - - // Check if currently drawing - getIsDrawing() { - return this.isDrawing; - } - - // Cancel current stroke - cancelStroke() { - this.isDrawing = false; - this.currentStroke = []; - this.lastPoint = null; - this.delayedStart = false; - this.startPoint = null; - } - - // Reset pressure detection (useful when switching characters) - resetPressureDetection() { - this.hasPressureSupport = false; - this.pressureCheckCount = 0; - this.delayedStart = false; - this.startPoint = null; - } - - // Create a preview stroke (for real-time drawing feedback) - createPreviewStroke(options = {}) { - if (!this.isDrawing || this.currentStroke.length < 8) return null; - - // 在延遲繪製狀態下不生成預覽筆跡 - if (this.delayedStart) return null; - - // 動態決定是否模擬壓力 - const shouldSimulatePressure = !this.hasPressureSupport && this.pressureCheckCount > 3; - - const defaultOptions = { - size: 12, - thinning: 0.8, // 增加壓力對粗細的影響 - smoothing: 0.5, - streamline: 0.3, // 減少流線化,讓壓力變化更明顯 - simulatePressure: shouldSimulatePressure, // 動態決定是否模擬壓力 - easing: (t) => t, - start: { - taper: 0, - easing: (t) => t, - cap: true - }, - end: { - taper: 25, // 大幅增加結尾的漸減效果 - easing: (t) => Math.sin((t * Math.PI) / 2), // 使用正弦緩動,更平滑 - cap: true // 使用圓頭來避免尖銳結尾 - } - }; - - const finalOptions = { ...defaultOptions, ...options }; - - try { - // 對預覽筆跡也應用壓力平滑 - let previewPoints = [...this.currentStroke]; - if (previewPoints.length > 5) { - previewPoints = this.smoothPressureValues(previewPoints); - } - - return this.perfectFreehandModule.getStroke(previewPoints, finalOptions); - } catch (error) { - return null; - } - } - - // Simulate pressure from pointer events - simulatePressure(event, eventType = 'move') { - - - // 提筆事件特殊處理 - 使用較低的壓力值 - if (eventType === 'end' && this.lastPoint) { - return Math.max(0.05, this.lastPoint.pressure * 0.3); // 提筆時壓力大幅減少 - } - - // Try to get pressure from pointer event (works with Apple Pencil) - if (event && event.pressure !== undefined && event.pressure > 0.1 && event.pointerType === 'pen') { - // 只有筆類型的 pointer event 且壓力值 > 0.1 才算真實筆壓支援 - this.hasPressureSupport = true; - - // 提筆事件時限制最大壓力值 - let pressure = event.pressure; - if (eventType === 'end') { - pressure = Math.min(pressure, 0.6); // 提筆時限制最大壓力 - } - - return Math.max(0.1, Math.min(1.0, pressure)); - } - - // 非筆類型的 pointer events(如手指)使用模擬壓力 - if (event && event.pointerType && event.pointerType !== 'pen') { - this.pressureCheckCount++; - return 0.5; // 返回預設值,讓速度模擬邏輯處理 - } - - // Try to get pressure from touch event - if (event && event.touches && event.touches.length > 0) { - const touch = event.touches[0]; - - // Apple Pencil support through force property - if (touch.force !== undefined && touch.force > 0.1 && touch.touchType === 'stylus') { - // 只有觸控筆類型且 force > 0.1 才算真實筆壓支援 - this.hasPressureSupport = true; - - // 提筆事件時限制最大壓力值 - let force = touch.force; - if (eventType === 'end') { - force = Math.min(force, 0.6); // 提筆時限制最大壓力 - } - - return Math.max(0.1, Math.min(1.0, force)); - } - - // 其他觸控事件(手指觸控或沒有 touchType)不提供真實壓力,使用模擬壓力 - if (!touch.touchType || touch.touchType !== 'stylus') { - this.pressureCheckCount++; - return 0.5; // 返回預設值,讓速度模擬邏輯處理 - } - } - - // Try to get pressure from mouse/pointer events - if (event && event.buttons !== undefined && event.type && event.type.includes('mouse')) { - // For mouse events, don't claim pressure support and use default simulation - // 滑鼠事件不提供真實壓力,應該使用模擬壓力 - this.pressureCheckCount++; - return 0.5; // 返回預設值,讓 addPoint 中的速度模擬邏輯處理 - } - - // Check for webkitForce (Safari specific for Force Touch) - if (event && event.webkitForce !== undefined && event.webkitForce > 1.0) { - // 只有 webkitForce > 1.0 才算真實壓力支援(正常值為 1.0,有壓力時會超過) - this.hasPressureSupport = true; - - // 提筆事件時限制最大壓力值 - let force = event.webkitForce; - if (eventType === 'end') { - force = Math.min(force, 2.0); // 提筆時限制最大壓力 - } - - // webkitForce 的範圍通常是 1.0-3.0,需要映射到 0.1-1.0 - const normalizedForce = Math.max(0.1, Math.min(1.0, (force - 1.0) / 2.0 + 0.5)); - return normalizedForce; - } - - // 增加檢測計數 - this.pressureCheckCount++; - - // Default pressure simulation with slight randomization - if (eventType === 'end') { - return 0.3; // 提筆時使用固定的低壓力值 - } - return 0.5 + Math.random() * 0.3; // Random pressure between 0.5 and 0.8 - } + constructor() { + this.perfectFreehandModule = null; + this.currentStroke = []; + this.isDrawing = false; + this.lastPoint = null; + this.hasPressureSupport = false; // 檢測是否支援筆壓 + this.pressureCheckCount = 0; // 用於檢測筆壓支援 + this.delayedStart = false; // 是否在延遲繪製狀態 + this.startPoint = null; // 起筆點 + this.moveThreshold = 5; // 移動閾值(像素) + } + + // Initialize the perfect-freehand module + async initialize() { + try { + this.perfectFreehandModule = await import('https://unpkg.com/perfect-freehand@1.2.2/dist/esm/index.mjs'); + return true; + } catch (error) { + return false; + } + } + + // Start a new stroke + startStroke(x, y, pressure = 0.5) { + this.isDrawing = true; + this.currentStroke = []; + this.lastPoint = { x, y, pressure }; + this.delayedStart = true; // 進入延遲繪製狀態 + this.startPoint = { x, y, pressure }; + this.currentStroke.push([x, y, pressure]); + } + + // Add a point to the current stroke + addPoint(x, y, pressure = 0.5) { + if (!this.isDrawing) return; + + // 檢查是否在延遲繪製狀態 + if (this.delayedStart && this.startPoint) { + const dx = x - this.startPoint.x; + const dy = y - this.startPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.moveThreshold) { + // 還沒有足夠的移動,只更新起始點的壓力 + this.startPoint.pressure = Math.max(this.startPoint.pressure, pressure); + this.currentStroke[0] = [this.startPoint.x, this.startPoint.y, this.startPoint.pressure]; + this.lastPoint = { x, y, pressure }; + return; + } else { + // 開始真正的繪製 + this.delayedStart = false; + } + } + + let isSimulatedPressure = false; + + // 如果沒有真實壓力支持且檢測計數足夠,才進行速度模擬 + if (pressure === 0.5 && this.lastPoint && !this.hasPressureSupport && this.pressureCheckCount > 3) { + const dx = x - this.lastPoint.x; + const dy = y - this.lastPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Adjust pressure based on drawing speed (slower = more pressure) + const speedFactor = Math.min(1, 10 / Math.max(distance, 1)); + pressure = 0.4 + speedFactor * 0.4; // Range from 0.4 to 0.8,範圍較小避免極端值 + isSimulatedPressure = true; + } + + // 只對模擬的壓力值增強對比,讓效果更明顯 + if (isSimulatedPressure) { + if (pressure < 0.5) { + pressure = pressure * 0.9; // 低壓力稍微降低 + } else { + pressure = 0.4 + (pressure - 0.5) * 1.1; // 高壓力稍微增加 + } + } + + this.currentStroke.push([x, y, pressure]); + this.lastPoint = { x, y, pressure }; + } + + // Finish the current stroke and return the path + finishStroke(options = {}) { + if (!this.isDrawing) { + this.delayedStart = false; + this.startPoint = null; + return null; + } + + // 如果還在延遲繪製狀態,說明沒有足夠的移動,直接生成圓形點 + if (this.delayedStart && this.startPoint) { + this.isDrawing = false; + this.delayedStart = false; + const strokePoints = [[this.startPoint.x, this.startPoint.y, this.startPoint.pressure]]; + this.startPoint = null; + return this.generateCircularDot(strokePoints, options); + } + + // 如果筆跡太短(只有起始點),也生成圓形點 + if (this.currentStroke.length < 2) { + this.isDrawing = false; + this.delayedStart = false; + const strokePoints = [...this.currentStroke]; + this.startPoint = null; + return this.generateCircularDot(strokePoints, options); + } + + // 動態決定是否模擬壓力 + const shouldSimulatePressure = !this.hasPressureSupport && this.pressureCheckCount > 3; + + const defaultOptions = { + size: 12, + thinning: 0.8, // 增加壓力對粗細的影響 + smoothing: 0.5, + streamline: 0.3, // 減少流線化,讓壓力變化更明顯 + simulatePressure: shouldSimulatePressure, // 動態決定是否模擬壓力 + easing: (t) => t, + start: { + taper: 0, + easing: (t) => t, + cap: true, + }, + end: { + taper: 25, // 大幅增加結尾的漸減效果 + easing: (t) => Math.sin((t * Math.PI) / 2), // 使用正弦緩動,更平滑 + cap: true, // 使用圓頭來避免尖銳結尾 + }, + }; + + const finalOptions = { ...defaultOptions, ...options }; + + try { + // 複製 stroke 點,避免修改原始資料 + let strokePoints = [...this.currentStroke]; + + // 壓力平滑處理 + if (strokePoints.length > 5) { + strokePoints = this.smoothPressureValues(strokePoints); + } + + // 檢查是否為靜止點或極短筆跡 + if (strokePoints.length <= 3) { + this.isDrawing = false; + this.currentStroke = []; + this.delayedStart = false; + this.startPoint = null; + return this.generateCircularDot(strokePoints, finalOptions); + } + + // 檢查筆跡是否在很小的範圍內 + const bounds = this.calculateBounds(strokePoints); + const maxDistance = Math.max(bounds.width, bounds.height); + + if (maxDistance < 8) { + // 如果筆跡範圍小於 8 像素 + this.isDrawing = false; + this.currentStroke = []; + this.delayedStart = false; + this.startPoint = null; + return this.generateCircularDot(strokePoints, finalOptions); + } + + // Get stroke outline from perfect-freehand + const outlinePoints = this.perfectFreehandModule.getStroke(strokePoints, finalOptions); + + this.isDrawing = false; + this.currentStroke = []; + this.delayedStart = false; + this.startPoint = null; + + return outlinePoints; + } catch (error) { + this.isDrawing = false; + this.currentStroke = []; + this.delayedStart = false; + this.startPoint = null; + return null; + } + } + + // Smooth pressure values to create natural light-heavy-light curve + smoothPressureValues(strokePoints) { + if (!strokePoints || strokePoints.length < 5) return strokePoints; + + const points = [...strokePoints]; + const len = points.length; + + // Step 1: 移除開始和結尾的不穩定區域 + let startIndex = 0; + let endIndex = len; + + // 找到壓力開始穩定的位置 + for (let i = 2; i < Math.min(len - 2, 15); i++) { + const pressureVariance = this.calculatePressureVariance(points, i - 2, i + 2); + if (pressureVariance < 0.15) { + // 放寬穩定標準 + startIndex = i; + break; + } + } + + // 找到壓力結束穩定的位置 + for (let i = len - 3; i >= Math.max(startIndex + 2, len - 15); i--) { + const pressureVariance = this.calculatePressureVariance(points, i - 2, i + 2); + if (pressureVariance < 0.15) { + // 放寬穩定標準 + endIndex = i + 1; + break; + } + } + + // Step 2: 截取穩定區域 + const stablePoints = points.slice(startIndex, endIndex); + + if (stablePoints.length < 3) return points; // 如果穩定區域太小,返回原始數據 + + // Step 3: 對穩定區域進行移動平均平滑 + const smoothedPoints = this.applyMovingAverage(stablePoints); + + // Step 4: 創建自然的起筆和收筆 + const naturalCurve = this.createNaturalPressureCurve(smoothedPoints); + + return naturalCurve; + } + + // Calculate pressure variance in a range + calculatePressureVariance(points, start, end) { + if (start < 0 || end >= points.length || end - start < 2) return 999; + + const pressures = points.slice(start, end + 1).map((p) => p[2]); + const mean = pressures.reduce((a, b) => a + b) / pressures.length; + const variance = pressures.reduce((acc, p) => acc + Math.pow(p - mean, 2), 0) / pressures.length; + + return Math.sqrt(variance); + } + + // Apply moving average to smooth pressure values + applyMovingAverage(points) { + if (points.length < 3) return points; + + const smoothed = []; + const windowSize = 5; // 增加窗口大小,讓平滑效果更明顯 + + for (let i = 0; i < points.length; i++) { + const start = Math.max(0, i - Math.floor(windowSize / 2)); + const end = Math.min(points.length - 1, i + Math.floor(windowSize / 2)); + + let sumPressure = 0; + let count = 0; + + for (let j = start; j <= end; j++) { + sumPressure += points[j][2]; + count++; + } + + const avgPressure = sumPressure / count; + smoothed.push([points[i][0], points[i][1], avgPressure]); + } + + return smoothed; + } + + // Create natural pressure curve with light start and end + createNaturalPressureCurve(points) { + if (points.length < 3) return points; + + const result = [...points]; + const len = result.length; + + // 找到壓力的峰值位置 + let maxPressure = 0; + let maxIndex = Math.floor(len / 2); + + for (let i = 0; i < len; i++) { + if (result[i][2] > maxPressure) { + maxPressure = result[i][2]; + maxIndex = i; + } + } + + // 創建自然的壓力曲線:輕 -> 重 -> 輕 + for (let i = 0; i < len; i++) { + let factor = 1.0; + + if (i < maxIndex) { + // 起筆段:從 0.3 漸增到 1.0 + factor = 0.3 + 0.7 * (i / maxIndex); + } else { + // 收筆段:從 1.0 漸減到 0.2 + factor = 1.0 - 0.8 * ((i - maxIndex) / (len - 1 - maxIndex)); + factor = Math.max(0.2, factor); + } + + // 應用漸變係數,但保持原始壓力的相對變化 + result[i][2] = result[i][2] * factor; + } + + return result; + } + + // Calculate bounds of stroke points + calculateBounds(strokePoints) { + if (!strokePoints || strokePoints.length === 0) { + return { width: 0, height: 0, minX: 0, maxX: 0, minY: 0, maxY: 0 }; + } + + let minX = strokePoints[0][0]; + let maxX = strokePoints[0][0]; + let minY = strokePoints[0][1]; + let maxY = strokePoints[0][1]; + + for (let i = 1; i < strokePoints.length; i++) { + const [x, y] = strokePoints[i]; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + + return { + width: maxX - minX, + height: maxY - minY, + minX, + maxX, + minY, + maxY, + }; + } + + // Generate circular dot based on pressure + generateCircularDot(strokePoints, options) { + if (!strokePoints || strokePoints.length === 0) return []; + + // 計算中心點和平均壓力 + let centerX = 0; + let centerY = 0; + let totalPressure = 0; + + for (const point of strokePoints) { + centerX += point[0]; + centerY += point[1]; + totalPressure += point[2]; + } + + centerX /= strokePoints.length; + centerY /= strokePoints.length; + const avgPressure = totalPressure / strokePoints.length; + + // 根據壓力計算半徑 + const baseRadius = (options.size || 12) * 0.6; // 增加基礎半徑 + const radius = baseRadius * (0.4 + avgPressure * 0.8); // 增加最小和最大半徑 + + // 生成圓形的點 + const circlePoints = []; + const segments = 16; // 圓形分段數 + + for (let i = 0; i < segments; i++) { + const angle = (i * 2 * Math.PI) / segments; + const x = centerX + Math.cos(angle) * radius; + const y = centerY + Math.sin(angle) * radius; + circlePoints.push([x, y]); + } + + return circlePoints; + } + + // Convert stroke outline to SVG path + outlineToSVGPath(outlinePoints) { + if (!outlinePoints || outlinePoints.length < 2) return ''; + + const path = outlinePoints.reduce((acc, point, index) => { + const [x, y] = point; + if (index === 0) { + return `M${x},${y}`; + } + return `${acc}L${x},${y}`; + }, ''); + + return `${path}Z`; + } + + // Draw stroke outline on canvas + drawStrokeOnCanvas(ctx, outlinePoints, eraseMode = false) { + if (!outlinePoints || outlinePoints.length < 2) return; + + ctx.save(); + + // Set composite operation for erasing + ctx.globalCompositeOperation = eraseMode ? 'destination-out' : 'source-over'; + + // Create path from outline points + ctx.beginPath(); + outlinePoints.forEach((point, index) => { + const [x, y] = point; + if (index === 0) { + ctx.moveTo(x, y); + } else { + ctx.lineTo(x, y); + } + }); + ctx.closePath(); + + // Fill the path + ctx.fillStyle = eraseMode ? 'rgba(0,0,0,1)' : 'black'; + ctx.fill(); + + ctx.restore(); + } + + // Get current stroke points (for preview) + getCurrentStrokePoints() { + return [...this.currentStroke]; + } + + // Check if currently drawing + getIsDrawing() { + return this.isDrawing; + } + + // Cancel current stroke + cancelStroke() { + this.isDrawing = false; + this.currentStroke = []; + this.lastPoint = null; + this.delayedStart = false; + this.startPoint = null; + } + + // Reset pressure detection (useful when switching characters) + resetPressureDetection() { + this.hasPressureSupport = false; + this.pressureCheckCount = 0; + this.delayedStart = false; + this.startPoint = null; + } + + // Create a preview stroke (for real-time drawing feedback) + createPreviewStroke(options = {}) { + if (!this.isDrawing || this.currentStroke.length < 8) return null; + + // 在延遲繪製狀態下不生成預覽筆跡 + if (this.delayedStart) return null; + + // 動態決定是否模擬壓力 + const shouldSimulatePressure = !this.hasPressureSupport && this.pressureCheckCount > 3; + + const defaultOptions = { + size: 12, + thinning: 0.8, // 增加壓力對粗細的影響 + smoothing: 0.5, + streamline: 0.3, // 減少流線化,讓壓力變化更明顯 + simulatePressure: shouldSimulatePressure, // 動態決定是否模擬壓力 + easing: (t) => t, + start: { + taper: 0, + easing: (t) => t, + cap: true, + }, + end: { + taper: 25, // 大幅增加結尾的漸減效果 + easing: (t) => Math.sin((t * Math.PI) / 2), // 使用正弦緩動,更平滑 + cap: true, // 使用圓頭來避免尖銳結尾 + }, + }; + + const finalOptions = { ...defaultOptions, ...options }; + + try { + // 對預覽筆跡也應用壓力平滑 + let previewPoints = [...this.currentStroke]; + if (previewPoints.length > 5) { + previewPoints = this.smoothPressureValues(previewPoints); + } + + return this.perfectFreehandModule.getStroke(previewPoints, finalOptions); + } catch (error) { + return null; + } + } + + // Simulate pressure from pointer events + simulatePressure(event, eventType = 'move') { + // 提筆事件特殊處理 - 使用較低的壓力值 + if (eventType === 'end' && this.lastPoint) { + return Math.max(0.05, this.lastPoint.pressure * 0.3); // 提筆時壓力大幅減少 + } + + // Try to get pressure from pointer event (works with Apple Pencil) + if (event && event.pressure !== undefined && event.pressure > 0.1 && event.pointerType === 'pen') { + // 只有筆類型的 pointer event 且壓力值 > 0.1 才算真實筆壓支援 + this.hasPressureSupport = true; + + // 提筆事件時限制最大壓力值 + let pressure = event.pressure; + if (eventType === 'end') { + pressure = Math.min(pressure, 0.6); // 提筆時限制最大壓力 + } + + return Math.max(0.1, Math.min(1.0, pressure)); + } + + // 非筆類型的 pointer events(如手指)使用模擬壓力 + if (event && event.pointerType && event.pointerType !== 'pen') { + this.pressureCheckCount++; + return 0.5; // 返回預設值,讓速度模擬邏輯處理 + } + + // Try to get pressure from touch event + if (event && event.touches && event.touches.length > 0) { + const touch = event.touches[0]; + + // Apple Pencil support through force property + if (touch.force !== undefined && touch.force > 0.1 && touch.touchType === 'stylus') { + // 只有觸控筆類型且 force > 0.1 才算真實筆壓支援 + this.hasPressureSupport = true; + + // 提筆事件時限制最大壓力值 + let force = touch.force; + if (eventType === 'end') { + force = Math.min(force, 0.6); // 提筆時限制最大壓力 + } + + return Math.max(0.1, Math.min(1.0, force)); + } + + // 其他觸控事件(手指觸控或沒有 touchType)不提供真實壓力,使用模擬壓力 + if (!touch.touchType || touch.touchType !== 'stylus') { + this.pressureCheckCount++; + return 0.5; // 返回預設值,讓速度模擬邏輯處理 + } + } + + // Try to get pressure from mouse/pointer events + if (event && event.buttons !== undefined && event.type && event.type.includes('mouse')) { + // For mouse events, don't claim pressure support and use default simulation + // 滑鼠事件不提供真實壓力,應該使用模擬壓力 + this.pressureCheckCount++; + return 0.5; // 返回預設值,讓 addPoint 中的速度模擬邏輯處理 + } + + // Check for webkitForce (Safari specific for Force Touch) + if (event && event.webkitForce !== undefined && event.webkitForce > 1.0) { + // 只有 webkitForce > 1.0 才算真實壓力支援(正常值為 1.0,有壓力時會超過) + this.hasPressureSupport = true; + + // 提筆事件時限制最大壓力值 + let force = event.webkitForce; + if (eventType === 'end') { + force = Math.min(force, 2.0); // 提筆時限制最大壓力 + } + + // webkitForce 的範圍通常是 1.0-3.0,需要映射到 0.1-1.0 + const normalizedForce = Math.max(0.1, Math.min(1.0, (force - 1.0) / 2.0 + 0.5)); + return normalizedForce; + } + + // 增加檢測計數 + this.pressureCheckCount++; + + // Default pressure simulation with slight randomization + if (eventType === 'end') { + return 0.3; // 提筆時使用固定的低壓力值 + } + return 0.5 + Math.random() * 0.3; // Random pressure between 0.5 and 0.8 + } } // Export for use in other modules -window.PressureDrawing = PressureDrawing; \ No newline at end of file +window.PressureDrawing = PressureDrawing; diff --git a/pages/style.css b/pages/style.css index 96017ca..d4f0564 100644 --- a/pages/style.css +++ b/pages/style.css @@ -11,76 +11,386 @@ body { user-select: none; /* 禁用文字選擇 */ -webkit-touch-callout: none; /* 禁用長按彈出選單 */ -webkit-user-select: none; /* 禁用文字選擇 */ - -webkit-tap-highlight-color: transparent; /* 移除點擊高亮效果 */ + -webkit-tap-highlight-color: transparent; /* 移除點擊高亮效果 */ +} +button { + display: inline-block; + margin: 2px 0; + padding: 4px 6px; + font-size: 20px; +} +select { + margin: 2px 0; + padding: 4px 6px; + font-size: 16px; +} +span.butt { + display: inline-block; + margin: 2px 1px; + padding: 4px 6px; + font-size: 18px; + border: 1px solid #666; + border-radius: 10px; + cursor: pointer; + background-color: #eee; + color: #333; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -button { display: inline-block; margin: 2px 0; padding: 4px 6px; font-size: 20px } -select { margin: 2px 0; padding: 4px 6px; font-size: 16px } -span.butt { display: inline-block; margin: 2px 1px; padding: 4px 6px; font-size: 18px; border: 1px solid #666; border-radius: 10px; cursor: pointer; background-color: #eee; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1) } -@font-face { font-family: LessonOne; src: url('LessonOne-Regular.woff'); font-weight: normal; font-style: normal; } -@font-face { font-family: GenYoExt; src: url('GenYoExt3-R.woff'); font-weight: normal; font-style: normal; } +@font-face { + font-family: LessonOne; + src: url('LessonOne-Regular.woff'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: GenYoExt; + src: url('GenYoExt3-R.woff'); + font-weight: normal; + font-style: normal; +} -#demo-container { margin-bottom: 5px; width: 360px; position: relative; color: #1123c4; text-align: center; font-family: sans-serif; height: 5.6em; margin: 10px auto } -#demo-container span { display: block; line-height: 1.2; } -#glyphName { font-family: Consolas, monospace; color: #aaa } -#demo-container #charSeq { font-size: 2.6em; line-height: 1; margin: 0 auto 10px auto; width: 1em; font-family: GenYoExt, LessonOne, sans-serif; border: 1px solid #aaa; } -#demo-container #charSeq.vert { writing-mode: vertical-rl; text-orientation: mixed; font-feature-settings: "vert" } -#prevButton { font-size: 3.2em; position: absolute; left: 0; top: 0; border: 0; background: transparent; box-shadow: none; } -#nextButton { font-size: 3.2em; position: absolute; right: 0; top: 0; border: 0; background: transparent; box-shadow: none } +#demo-container { + margin-bottom: 5px; + width: 360px; + position: relative; + color: #1123c4; + text-align: center; + font-family: sans-serif; + height: 5.6em; + margin: 10px auto; +} +#demo-container span { + display: block; + line-height: 1.2; +} +#glyphName { + font-family: Consolas, monospace; + color: #aaa; +} +#demo-container #charSeq { + font-size: 2.6em; + line-height: 1; + margin: 0 auto 10px auto; + width: 1em; + font-family: GenYoExt, LessonOne, sans-serif; + border: 1px solid #aaa; +} +#demo-container #charSeq.vert { + writing-mode: vertical-rl; + text-orientation: mixed; + font-feature-settings: 'vert'; +} +#prevButton { + font-size: 3.2em; + position: absolute; + left: 0; + top: 0; + border: 0; + background: transparent; + box-shadow: none; +} +#nextButton { + font-size: 3.2em; + position: absolute; + right: 0; + top: 0; + border: 0; + background: transparent; + box-shadow: none; +} -#canvas-container { position: relative; width: 360px; height: 360px; border: 1px solid #888; background-color: #fff;} -canvas { position: absolute; top: 0; left: 0; width: 360px; height: 360px; touch-action: none; } +#canvas-container { + position: relative; + width: 360px; + height: 360px; + border: 1px solid #888; + background-color: #fff; +} +canvas { + position: absolute; + top: 0; + left: 0; + width: 360px; + height: 360px; + touch-action: none; +} @media screen and (max-height: 750px), screen and (max-width: 380px) { - #canvas-container { width: 300px; height: 300px } - canvas { width: 300px; height: 300px; touch-action: none; } + #canvas-container { + width: 300px; + height: 300px; + } + canvas { + width: 300px; + height: 300px; + touch-action: none; + } +} +.smallmode { + width: 200px !important; + height: 200px !important; +} +.smallmode canvas { + width: 200px; + height: 200px; + touch-action: none; } -.smallmode { width: 200px !important; height: 200px !important} -.smallmode canvas { width: 200px; height: 200px; touch-action: none; } -#gridCanvas { z-index: 0 } /* 九宮格底圖在下方 */ -#drawingCanvas { position: absolute; top: 0; left: 0; z-index: 1 } /* 繪圖畫布在上方 */ +#gridCanvas { + z-index: 0; +} /* 九宮格底圖在下方 */ +#drawingCanvas { + position: absolute; + top: 0; + left: 0; + z-index: 1; +} /* 繪圖畫布在上方 */ -#slider-container { text-align: center; margin: 10px 0; font-family: sans-serif } -#slider-container .use { background-color: #ffc ;} -#lineWidthSlider { margin-left: 15px; width: 120px; display: inline-block; vertical-align: middle; } -#slider-container #lineWidthValue { display: inline-block; width: 1.2em; text-align: right; } -#brushSelector { display: inline-block; width: 32px; height: 32px; background-color: #fff; border: 1px solid #777; vertical-align: bottom; margin: 2px 0 2px 2px; padding: 0; cursor: pointer; border-radius: 10px 0 0 10px; text-align: center; } -#brushSelector img { width: 28px; height: 28px; line-height: 32px; margin-top: 2px} -#pressureButton { display: inline-block; width: 32px; height: 32px; border: 1px solid #777; margin: 2px 4px 2px 0; border-radius: 0 10px 10px 0; vertical-align: bottom; } -#slider-container .off { background: center no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyNS0wNy0xOVQxMToyNToxMSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ODQ0M2E4LTM5MzMtYjI0Yi04MDk1LTFlNzM4ZGNmNWUxNiIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjI5MmQzODI5LWNiZGEtZjE0NS05ZjVkLTRlOTM0Y2VhZjU4MCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmE0ZTBhZjA2LTcyMTUtMWY0Ni1hODZkLWU3NzU4MzNmNmMwZCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTRlMGFmMDYtNzIxNS0xZjQ2LWE4NmQtZTc3NTgzM2Y2YzBkIiBzdEV2dDp3aGVuPSIyMDI1LTA3LTE5VDExOjI1OjExKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMCAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjMxZDQ3YTdhLWUwNzEtOGI0Zi1hMTYwLWQzZDE3ZmYxNTQ5MyIgc3RFdnQ6d2hlbj0iMjAyNS0wNy0xOVQxNTozNjo0MCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2NDg0NDNhOC0zOTMzLWIyNGItODA5NS0xZTczOGRjZjVlMTYiIHN0RXZ0OndoZW49IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4wIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5CPmYRAAAMs0lEQVR42u1deVBV1xk/4AOBxyIgLiwqqCCLCBQEd8SKFVyC45K6JDbRVhONOk2sNZ3WGNP+YbVLWsVmZKxKWwuEqWjAGEdckM0h4vDYZBNRFkHAJ8oi0N93h8sgvnd5wHtg4Hwzd3g8zr33nO/3nd/3+849V/Xa29sZt8Ezfe4CDgAHgBsHgAPAjQPAAeDGAeAAcOMAcAC4cQA4ANw4ABwAbhwADgA3DgAHgBsHYMiaTPxw4sQJZmhoyBITE1lBQQFrbGxk+vr6zNnZmeXm5rJRo0axKVOmMIVCwWxsbFh5eTkbOXIks7OzY/X19aytrY09ePCA2draMj09PVZZWclMTExYc3Mzc3V1ZaWlpUL7+/fvC+esWbOGxcbGCveRyWTMyMhIuKafn59wLv00MDCQ7LyxsTGLiIhgo0ePZqampkypVArfm5mZCdenaw+20aaHjIwMGpN/QkLCNisrq8fo26dHjx5teQWAoWYUBG9KVxBY71+8ePHLlpYWo+fPn7OUlBQrfL9lyAJAs7Guro5ZWlqywd528+zZs7Hx8fFHyfnid/n5+UGvUdBQMqK96OhoNnv2bIH2CJDBMLr3tWvXftnU1GTWLUAshzQAZDTV09LSmJOTkzALBnomEAXinpMuXbr0sYoZ2jrkASABQVZcXCxQkZgQBxAAw+vXr/9b1T0xM+qGPABiFIKDWWtrK5s8ebKglAYKhNraWi8onx91zIRX/iaXy8uHBQBkI0aMYEiA7MmTJywoKIi9ePFC5/c0Nzdn+/bt+xhUo1JHe3h4fDNsABBBqKmpYampqWz+/Pk6BYFqqaKiIsd79+6tUddm6dKlx4YVAGQvX75kmZmZbMyYMczBwYFBmegk74BuZOHh4f9U18bHx+cCAqJ+WC5FUDV/7tw5lpyc3JkjtHkQyMeOHTtcWFg4T9X9Udk/CwsL29e1QpcNJwAoGdKyBy2nLFy4UPisraRMyyZQXE5JSUm71bUJDg7ej7ygIDoclgCIZmFhwY4fP85WrFghOI7oqD9LF0Q9UFpGR44ciVbXxszMrMrf3/9LKhK72rAEgJIyLfyhShUAoIU/Ukp9lboA0PDMmTORZWVl3urarVu3bou7u/trAmBYAkC0Q44HV3cC0NcZQOdB8SwrKChYpe52UD2/nzZtWlxFRcVrlDcsARBBoLUaWjJHxcqWLVsmLIn3JifQrImOjvbBTDqtro2VlVUxqO4QAa1qTWrYAtA1gkmViI4XlzA0sadPnzog6caBVuTq2mzevPndCRMmNKqrPYY9AGLxFBcXJyTjxYsXk2Ml29NMoTaxsbF/VyqVtt2XGzrorDUwMPAXq1evvklt6SERB0A6moUVVDKiCqIXStb0mZxLn2lNiaQrOTgnJ+cjcP9ykc6605urq+t5yM4ItJFcDucAdFFGdJSUlLDHjx8ze3t7RkkTHC58T2tJVMjRY9f79+9/COo5LFETNK9cufJnqLrbaTFQyjgA3Sw/P194bm1tbU3qhnl6egrJ+tGjR8KydnZ29qyMjIy/SdQErTt37nwb9FNPlXFPxgFQI1EpGVPki/IU1KMHEJbk5eX9S+p85JD33NzcYul8TRI6B0DDZYaGhobply5dikEla6JKSRFwzs7OZ/fs2XOaFI+mOzL4viANckN1dbVPamrqFVXOFyfOxIkTz4WFhW2l9pSoNbUBnwEULTQ1ScqJiuJNpSLqZ01NTcD58+fjEdWjJNZ5MqB63gHnN4tj1HRcMl07W9TZ+KyHAdlAyk2ura01z8zMtIHaqMN3j01NTbMARsObBAbRDkCwjYdJOd/FxSUrNDQ0WC6XN5PWf/jwYa92Ych05fiOwxid8c3KyloEdREC5zuAG8eRxqbdYh3JrR0qo8zOzu4GBnEQA897E/4JHThy0d27dyNQH6h1/ujRo3M+++yzRXV1dU/E+qGnIk7nABCtwMkOiJqlpaWlH9bX13tSAaPOMG31cDgAoPVw/koTE5MIW1vbjwYDBAoa4nDcOwiV8bfgfKkc+XLu3LlhCJ4qWmIWFU9vljK0BgB1vCPxuCNy3snNzd2OTpn19jqYGXIUODtnzJih7+vru6On/TzapizcywARvK+oqGi/lPNRkFU7Ojq+jTyWJxVcAwIAolYPnXUsKyvbXVBQsA1ONOjvNRUKxc8x/ZO9vb0jKSIlANPKEy0CEuMYWV5e/meMY5sUh6Pts1WrVs1HoOX01/n9AoAcQ85HtXggPT19L2jESFuRiGsZREZGnnV3d8/08PDIUvewhJ7talJtSikdGgdocjqC5xwKLVep9mPHji1Brlprbm6eQ9WyVii7L50mukHiWQ7n74eSCejtNYgncY02OFZfKoJPnjz5FQqbWeIiWfdr0C6H/iodKLIFoMzzuIe5VFvQTs727dsXXr16tbI/oPcLABo0Om0NVfMrdPqT3pyLAZTh3PTp06fHI3qKjY2NnyLZGpWUlLyLBPyeqnPwNx9EpRfa3eme6Ol9A3pvgTZB9cXxGItZRUVFOK6zBlRiIDHm9qlTp15uampaP27cuBptb/TVGADS8oiSAMjHr5VK5XgNz3mBzn8HZ58bP378+aqqKiUqRoafAu/iO3pZI8nS0tIWleZPup+PwRreunXr/Xnz5u1q6xi5qFQSEhKEYq63AYRzR8Dx64qLi38D6nHt6RwIggh7e/vtmG0tfX1u3O+lCHR8JJTBgaioqKuaOB+Ob0TUxrm5uc1ZvXr1ChRakfhaSUlLTJx00JrJpEmTWteuXbsVkVyjiu4SExN3IDm60rKueNAjRCp4eiP5MGv0EUAzMI6oGzduRPbkfIyh1cLC4q9BQUFbEP3a97ymANAgoQxOgiZ+B4cZ9aQmEOHfIeq98HMlzv2eHK5q2tLrRbRhlnaqET2FhIT8Ud11b968+Tm9FkWbaxsaGoS1eU3WW8QVTZhNYWHhP65cuXIbOSusJ+UE5yuDYVZWVru1yfe9piBw5fjLly8fq6ysfKunC5EygGrZBscnQUK2ik+SVA2WviP6IEfSw4+OHHEYDvtDd7AIVHB9GH7aoeh5SOv1Us4XKYraIOKnIod8ghm0EsCN0QCwNuj7K4j8PU5OTgqMQ+fFn77ElDWFyrnVk/PR7vmSJUt+DcoJQGl+HRHT2lOEUVSBWwUnUW6hA2C3AsAYVWCRxcTEnAVgY2lXWXfq6VBV5HiqzMYgx6wDaAnIH3cgL7dq4nzcv8nf3/8Q8k0wrqXQBd9rPAMQRU5IUlHV1dWTpE4GJZR7eXm9BSpJoxchiOM1cT7tUKatgd3XzAHikYMHDy4B1Zl2Pw/JP/DBgwe3kLh34tfv0cdGOJ4cboz2U8HpU5AXlgOkWeBsm96oFdz3BoTAtlmzZmXTk6+BXAaRqYgkM8jMKxiQpPMhJ09MmzbtACikovt2OykjkGbOnNn5SmlXw7RP9vHxOZ6UlKRS4uJeTgiKCwBeCSBr0UcZrmeanZ1t0XW2aGq4To2Li8tX4PpPca223oxDJwBQ1KSkpPynB+c3Ypp+YW1tfYg2NdE5vVmTIX6mKFM1xek6CxYs2A9JugsONlRTCOoBPBL/5n3mXX39Zii0ryEvdyG5V0FkDNrblJ0AYNrKTp8+/RdEU4gET9ahHF/s7e19m4ogdXtdeiqCSPmoW0dBon25cePGDadOnYrS9mBx7xY4/QIS7X7I31xy/GBEvcokrFAoHNPS0j5QpyzQ6aKAgIDFoKjb1On+rERSwlR30IxydXX9H2TpbzFbWrUSZTJZi7Ozczjoxm/OnDmroHJyRccP9kMg/S5l/w51mhdcmb1hwwZ/uVx+WxuluChP1R2YHS2BgYGf+/n5hWCW1fQx2l/i3GyIhL1wvpenp+d2AJFJYxys94YlKSg9Pf2nqhrA6S1QBz+GU6oHsuO0TRB09C2SvTuq3/WlpaUfgPsd0YcRqviaIhm1xWNwehY+X0OUx+H3bNBdI2220tbStc4AwJS0UTGoutDQ0LUoaMppABQ9XTey6to6ZkMlirQ/YRaGoz+TUVTNtrOzs0dBJgdFtfv6+iqTk5NLIIVLIQwUcHzVnTt3OulM15Ws1gBAcvomLy/vlQS8adOmLR4eHpdpOZhWHTEb6CWzAZ3CFNmUsOHIF3BuFsDIov38VJBRAUf1BGSrsLRBO9iI28Uq/IdgnQBAm+/FVPXHQK1JKoI//4ukG0ODov2RNCh6tYdUDJTSoHRWdKzoZAJGLOZ+SE5/JcD4f+IzuMZ3xnEAOADcOAAcAG4cAA4ANw4AB4AbB4ADwI0DwAHgxgHgAHDjAHAAuHEAOADcOAAcAG4cAA4ANw4AB4AbB2Bo2f8BtnaEPGGUcf0AAAAASUVORK5CYII='); background-size: 28px 28px; } -#slider-container .on { background: center no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyNS0wNy0xOVQxMToyNToxMSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjYyYjgyODhlLWQ1NDktM2E0OS1iNzEwLThkZGYyNTFhZDAxMSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmVkMGNjMTc1LTA1ODQtMmI0OS1hNWMyLTgwNTg0ZWViNDc2YyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmE0ZTBhZjA2LTcyMTUtMWY0Ni1hODZkLWU3NzU4MzNmNmMwZCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTRlMGFmMDYtNzIxNS0xZjQ2LWE4NmQtZTc3NTgzM2Y2YzBkIiBzdEV2dDp3aGVuPSIyMDI1LTA3LTE5VDExOjI1OjExKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMCAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjc0OTU2YTBlLWRiNDItNzg0MS04YmU4LWRiNmU3MGZkMTA0YSIgc3RFdnQ6d2hlbj0iMjAyNS0wNy0xOVQxNTozNzozNSswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MmI4Mjg4ZS1kNTQ5LTNhNDktYjcxMC04ZGRmMjUxYWQwMTEiIHN0RXZ0OndoZW49IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4wIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6vtbYKAAAReklEQVR42u1dC1BU5xX+wRUfMSKCyIILIiCIwReCj1jBGoP1Ga1DTCzGREdr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'); background-size: 28px 28px; } -#penButton { padding-right: 2px; border-radius: 10px 0 0 10px; margin-right: 0 } -#eraserButton { padding-left: 2px; border-radius: 0 10px 10px 0; margin-left: 0; border-left: 0 } +#slider-container { + text-align: center; + margin: 10px 0; + font-family: sans-serif; +} +#slider-container .use { + background-color: #ffc; +} +#lineWidthSlider { + margin-left: 15px; + width: 120px; + display: inline-block; + vertical-align: middle; +} +#slider-container #lineWidthValue { + display: inline-block; + width: 1.2em; + text-align: right; +} +#brushSelector { + display: inline-block; + width: 32px; + height: 32px; + background-color: #fff; + border: 1px solid #777; + vertical-align: bottom; + margin: 2px 0 2px 2px; + padding: 0; + cursor: pointer; + border-radius: 10px 0 0 10px; + text-align: center; +} +#brushSelector img { + width: 28px; + height: 28px; + line-height: 32px; + margin-top: 2px; +} +#pressureButton { + display: inline-block; + width: 32px; + height: 32px; + border: 1px solid #777; + margin: 2px 4px 2px 0; + border-radius: 0 10px 10px 0; + vertical-align: bottom; +} +#slider-container .off { + background: center no-repeat + url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyNS0wNy0xOVQxMToyNToxMSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY0ODQ0M2E4LTM5MzMtYjI0Yi04MDk1LTFlNzM4ZGNmNWUxNiIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjI5MmQzODI5LWNiZGEtZjE0NS05ZjVkLTRlOTM0Y2VhZjU4MCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmE0ZTBhZjA2LTcyMTUtMWY0Ni1hODZkLWU3NzU4MzNmNmMwZCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTRlMGFmMDYtNzIxNS0xZjQ2LWE4NmQtZTc3NTgzM2Y2YzBkIiBzdEV2dDp3aGVuPSIyMDI1LTA3LTE5VDExOjI1OjExKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMCAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjMxZDQ3YTdhLWUwNzEtOGI0Zi1hMTYwLWQzZDE3ZmYxNTQ5MyIgc3RFdnQ6d2hlbj0iMjAyNS0wNy0xOVQxNTozNjo0MCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2NDg0NDNhOC0zOTMzLWIyNGItODA5NS0xZTczOGRjZjVlMTYiIHN0RXZ0OndoZW49IjIwMjUtMDctMTlUMTU6MzY6NDArMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4wIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5CPmYRAAAMs0lEQVR42u1deVBV1xk/4AOBxyIgLiwqqCCLCBQEd8SKFVyC45K6JDbRVhON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'); + background-size: 28px 28px; +} +#slider-container .on { + background: center no-repeat + url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyNS0wNy0xOVQxMToyNToxMSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjYyYjgyODhlLWQ1NDktM2E0OS1iNzEwLThkZGYyNTFhZDAxMSIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmVkMGNjMTc1LTA1ODQtMmI0OS1hNWMyLTgwNTg0ZWViNDc2YyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmE0ZTBhZjA2LTcyMTUtMWY0Ni1hODZkLWU3NzU4MzNmNmMwZCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YTRlMGFmMDYtNzIxNS0xZjQ2LWE4NmQtZTc3NTgzM2Y2YzBkIiBzdEV2dDp3aGVuPSIyMDI1LTA3LTE5VDExOjI1OjExKzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMCAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjc0OTU2YTBlLWRiNDItNzg0MS04YmU4LWRiNmU3MGZkMTA0YSIgc3RFdnQ6d2hlbj0iMjAyNS0wNy0xOVQxNTozNzozNSswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjAgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MmI4Mjg4ZS1kNTQ5LTNhNDktYjcxMC04ZGRmMjUxYWQwMTEiIHN0RXZ0OndoZW49IjIwMjUtMDctMTlUMTU6Mzc6MzUrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4wIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6vtbYKAAAReklEQVR42u1dC1BU5xX+wRUfMSKCyIILIiCIwReCj1jBGoP1Ga1DTCzGREdr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'); + background-size: 28px 28px; +} +#penButton { + padding-right: 2px; + border-radius: 10px 0 0 10px; + margin-right: 0; +} +#eraserButton { + padding-left: 2px; + border-radius: 0 10px 10px 0; + margin-left: 0; + border-left: 0; +} -#button-container { display: flex; width: 320px; gap: 10px; justify-content: space-between } -#button-container span.butt { padding: 0; border: 0; font-size: 32px; box-shadow: none; line-height: 1.1; } +#button-container { + display: flex; + width: 320px; + gap: 10px; + justify-content: space-between; +} +#button-container span.butt { + padding: 0; + border: 0; + font-size: 32px; + box-shadow: none; + line-height: 1.1; +} -#list-container { display: block; position: fixed; top: 0; left: 0; width: 100%; justify-content: center; background-color: #fff; padding: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); text-align: center; z-index: 1; } +#list-container { + display: block; + position: fixed; + top: 0; + left: 0; + width: 100%; + justify-content: center; + background-color: #fff; + padding: 10px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + text-align: center; + z-index: 1; +} -#navi-container { display: flex; gap: 10px; position: fixed; bottom: 0; left: 0; width: 100%; justify-content: center; background-color: #fff; padding: 10px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1); } -#navi-container #spanDoneCount { padding-top: 12px; color: #999; width: 3em; text-align: center; } -#navi-container #label-tester { text-align: center; font-size: 0.8em } -#navi-container #label-tester input { width: 1.2em; height: 1.2em } -#progress-container { display: none; position: fixed; bottom: 0; left: 0; width: 100%; background-color: #fff; padding: 10px; text-align: center; box-shadow: 0 -2px 5px rgba(0,0,0,0.1); z-index: 1000; } -#progress-bar { width: 80%; height: 20px; display: block; margin: 0 auto; } +#navi-container { + display: flex; + gap: 10px; + position: fixed; + bottom: 0; + left: 0; + width: 100%; + justify-content: center; + background-color: #fff; + padding: 10px; + box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); +} +#navi-container #spanDoneCount { + padding-top: 12px; + color: #999; + width: 3em; + text-align: center; +} +#navi-container #label-tester { + text-align: center; + font-size: 0.8em; +} +#navi-container #label-tester input { + width: 1.2em; + height: 1.2em; +} +#progress-container { + display: none; + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: #fff; + padding: 10px; + text-align: center; + box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); + z-index: 1000; +} +#progress-bar { + width: 80%; + height: 20px; + display: block; + margin: 0 auto; +} -.dialog { display: none; position: fixed; top: 0; left: 0; width: 90%; height: 100%; background-color: rgba(0, 0, 0, 0.8); color: #fff; z-index: 999; text-align: left; padding: 0 5% } -.dialog h2 { margin-top: 1em } -.dialog h3 { display: block; font-size: 1.2em; margin: 0.8em 0 0.2em 0; } -.dialog a { color: #ff0 !important; text-decoration: underline; } -.dialog input[type="text"] { width: 80%; padding: 3px; font-size: 1.2em; } -.dialog input[type="file"] { width: 90%; padding: 3px; font-size: 1.2em; } -.dialog input[type="range"] { width: 80%; vertical-align: middle; } -.dialog input[type="checkbox"] { width: 2em; height: 2em; vertical-align: middle } -.dialog .note { display: block; color: #ccc } +.dialog { + display: none; + position: fixed; + top: 0; + left: 0; + width: 90%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + color: #fff; + z-index: 999; + text-align: left; + padding: 0 5%; +} +.dialog h2 { + margin-top: 1em; +} +.dialog h3 { + display: block; + font-size: 1.2em; + margin: 0.8em 0 0.2em 0; +} +.dialog a { + color: #ff0 !important; + text-decoration: underline; +} +.dialog input[type='text'] { + width: 80%; + padding: 3px; + font-size: 1.2em; +} +.dialog input[type='file'] { + width: 90%; + padding: 3px; + font-size: 1.2em; +} +.dialog input[type='range'] { + width: 80%; + vertical-align: middle; +} +.dialog input[type='checkbox'] { + width: 2em; + height: 2em; + vertical-align: middle; +} +.dialog .note { + display: block; + color: #ccc; +} -.close { position: absolute; display: block; right: 20px; top: 15px; font: normal 3em/1 sans-serif} -.dialog .dummy { display: block; clear: both; height: 3em } -.dialog-body { height: 100%; overflow: scroll; max-width: 720px; margin: 0 auto; position: relative; } +.close { + position: absolute; + display: block; + right: 20px; + top: 15px; + font: normal 3em/1 sans-serif; +} +.dialog .dummy { + display: block; + clear: both; + height: 3em; +} +.dialog-body { + height: 100%; + overflow: scroll; + max-width: 720px; + margin: 0 auto; + position: relative; +} -#listup-body svg { display: block; background-color: #fff; width: 50px; height: 50px; float: left; border: 1px solid #ccc; margin: 5px } -#listup-body span { display: block; background-color: #666; color: #ddd; width: 50px; height: 50px; float: left; border: 1px solid #ccc; line-height: 50px; text-align: center; font-size: 2em; margin: 5px; font-family: GenYoExt, LessonOne, sans-serif; overflow: hidden; } -#listup-body span.vert { writing-mode: vertical-rl; text-orientation: mixed; font-feature-settings: "vert" } +#listup-body svg { + display: block; + background-color: #fff; + width: 50px; + height: 50px; + float: left; + border: 1px solid #ccc; + margin: 5px; +} +#listup-body span { + display: block; + background-color: #666; + color: #ddd; + width: 50px; + height: 50px; + float: left; + border: 1px solid #ccc; + line-height: 50px; + text-align: center; + font-size: 2em; + margin: 5px; + font-family: GenYoExt, LessonOne, sans-serif; + overflow: hidden; +} +#listup-body span.vert { + writing-mode: vertical-rl; + text-orientation: mixed; + font-feature-settings: 'vert'; +} -#hintContent { padding: 0 1.2em } -#hintContent li { margin: 10px 0; line-height: 1.6; } +#hintContent { + padding: 0 1.2em; +} +#hintContent li { + margin: 10px 0; + line-height: 1.6; +}