diff --git a/ui-html/webview2/ftb/autumn_osmanthus.html b/ui-html/webview2/ftb/autumn_osmanthus.html index 17360a9d..0a454639 100644 --- a/ui-html/webview2/ftb/autumn_osmanthus.html +++ b/ui-html/webview2/ftb/autumn_osmanthus.html @@ -112,10 +112,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -180,11 +188,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -399,6 +369,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/autumn_osmanthus_light.html b/ui-html/webview2/ftb/autumn_osmanthus_light.html index 3ea183fc..41581273 100644 --- a/ui-html/webview2/ftb/autumn_osmanthus_light.html +++ b/ui-html/webview2/ftb/autumn_osmanthus_light.html @@ -111,10 +111,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -179,11 +187,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -398,6 +368,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/default.html b/ui-html/webview2/ftb/default.html index 88664224..d2cc349b 100644 --- a/ui-html/webview2/ftb/default.html +++ b/ui-html/webview2/ftb/default.html @@ -112,10 +112,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -171,11 +179,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -390,6 +360,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/default_light.html b/ui-html/webview2/ftb/default_light.html index 557a32b6..e2d2664b 100644 --- a/ui-html/webview2/ftb/default_light.html +++ b/ui-html/webview2/ftb/default_light.html @@ -111,10 +111,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -170,11 +178,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -389,6 +359,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/graphite.html b/ui-html/webview2/ftb/graphite.html index 6c09f67b..c6e105a8 100644 --- a/ui-html/webview2/ftb/graphite.html +++ b/ui-html/webview2/ftb/graphite.html @@ -112,10 +112,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -184,11 +192,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -403,6 +373,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/graphite_light.html b/ui-html/webview2/ftb/graphite_light.html index b9479d6b..922bb503 100644 --- a/ui-html/webview2/ftb/graphite_light.html +++ b/ui-html/webview2/ftb/graphite_light.html @@ -111,10 +111,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -183,11 +191,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -402,6 +372,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/wechat.html b/ui-html/webview2/ftb/wechat.html index 15dcb39f..31fcb38d 100644 --- a/ui-html/webview2/ftb/wechat.html +++ b/ui-html/webview2/ftb/wechat.html @@ -112,10 +112,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -179,11 +187,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -398,6 +368,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/wechat_light.html b/ui-html/webview2/ftb/wechat_light.html index 26b7cd84..085e6016 100644 --- a/ui-html/webview2/ftb/wechat_light.html +++ b/ui-html/webview2/ftb/wechat_light.html @@ -111,10 +111,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -178,11 +186,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -397,6 +367,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/willow_green.html b/ui-html/webview2/ftb/willow_green.html index 4d00b57c..61cd518e 100644 --- a/ui-html/webview2/ftb/willow_green.html +++ b/ui-html/webview2/ftb/willow_green.html @@ -112,10 +112,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -180,11 +188,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -399,6 +369,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/ftb/willow_green_light.html b/ui-html/webview2/ftb/willow_green_light.html index a792c7f3..2bd79134 100644 --- a/ui-html/webview2/ftb/willow_green_light.html +++ b/ui-html/webview2/ftb/willow_green_light.html @@ -111,10 +111,18 @@ color: inherit; } - .icon svg { - width: 100%; - height: 100%; - display: block; + /* 与 D2D 工具栏同一套图标字体(msimeui::ResolveIconGlyph):Segoe Fluent Icons + 优先,Segoe MDL2 Assets 兜底;两者都缺的码位由脚本换成 data-fallback 文字。 */ + .icon-glyph { + font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; + font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); + font-style: normal; + line-height: 1; + } + + .icon-glyph.is-fallback { + font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; + font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .icon:hover { @@ -179,11 +187,11 @@
- 中 +
- 英 +
- - - - - +
- - - - +
- - - - + +
- - - - - - +
- - - - +
@@ -398,6 +368,35 @@ window.chrome.webview.postMessage(serializeHostMessage({ type: 'openSettings' })); }); + // + // 图标字形回退:Segoe Fluent Icons 只随 Windows 11 提供,旧版 Windows 10 的 Segoe MDL2 Assets + // 也可能缺少部分输入法码位。Chromium 缺字时静默换字体,按钮会变成空白方框(issue #232), + // 所以和 D2D 工具栏一样,缺字形的按钮改用文字。U+10FFFD 不在任何字体里,拿它的渲染结果当 + // “缺字形”的参照。 + // + (function applyIconGlyphFallbacks() { + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const context = canvas.getContext('2d', { willReadFrequently: true }); + if (!context) return; + const render = (text) => { + context.clearRect(0, 0, 32, 32); + context.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context.textBaseline = 'top'; + context.fillText(text, 4, 4); + return context.getImageData(0, 0, 32, 32).data; + }; + const missing = render('\u{10FFFD}'); + document.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + const pixels = render(node.textContent); + if (pixels.every((value, index) => value === missing[index])) { + node.textContent = node.dataset.fallback; + node.classList.add('is-fallback'); + } + }); + })(); + // Tell the host the first frame is ready so it can stop the paint-grace show // and apply the real visibility decision (IME active / configured / etc.). function notifyToolbarReady() { diff --git a/ui-html/webview2/settings/ime-settings/src/modules/floating-toolbar.ts b/ui-html/webview2/settings/ime-settings/src/modules/floating-toolbar.ts index a5ab5bcc..04712f2e 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/floating-toolbar.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/floating-toolbar.ts @@ -2,6 +2,7 @@ import { applyDropdownValue, setupDropdownMenu, setupToggleButton } from './shar import { syncCaretStateIndicatorPreview } from './appearance'; import { updateConfig } from './config-sync'; import { syncAppearancePreviews } from './skin'; +import { applyToolbarIconGlyphFallbacks } from './toolbar-icon-glyphs'; import ftbHTML from '../../../../ftb/default.html?raw'; type FloatingToolbarItem = 'fullwidth' | 'punctuation' | 'character_set' | 'emoji' | 'screen_keyboard' | 'settings'; @@ -139,6 +140,7 @@ function mountFloatingToolbarPreview(): void { statusBar.querySelectorAll('#en, #fullwidth, #puncEn').forEach((element) => element.remove()); statusBar.querySelectorAll('[id]').forEach((element) => element.removeAttribute('id')); + applyToolbarIconGlyphFallbacks(statusBar); host.replaceChildren(statusBar); updatePreviewItems(); applyPreviewAppearance(); diff --git a/ui-html/webview2/settings/ime-settings/src/modules/skin.ts b/ui-html/webview2/settings/ime-settings/src/modules/skin.ts index c1390161..ed565b4e 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/skin.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/skin.ts @@ -4,6 +4,7 @@ let styleGeneration = 0; const styleLoads = new Map>(); import { serializeHostMessage } from '../../../../shared/messages'; import { loadHTML } from '../utils/common-utils'; +import { applyToolbarIconGlyphFallbacks } from './toolbar-icon-glyphs'; import ftbHTML from '../../../../ftb/default.html?raw'; export type SkinPreviewTheme = 'dark' | 'light'; @@ -88,6 +89,7 @@ function fillToolbar(host: HTMLElement): void { if (!statusBar) return; statusBar.querySelectorAll('#en, #fullwidth, #puncEn').forEach((element) => element.remove()); statusBar.querySelectorAll('[id]').forEach((element) => element.removeAttribute('id')); + applyToolbarIconGlyphFallbacks(statusBar); host.replaceChildren(statusBar); } @@ -137,10 +139,10 @@ function candidatePreviewCss(skin: ExternalSkin): string { const surface = skinColor(colors.surface); const border = skinColor(colors.border); const text = skinColor(colors.text); - let css = ''; - const variables = [surface && `--cand-bg: ${surface}`, border && `--cand-border: ${border}`, const number = skinColor(colors.number); const translation = skinColor(colors.translation); + let css = ''; + const variables = [surface && `--cand-bg: ${surface}`, border && `--cand-border: ${border}`, text && `--cand-text: ${text}`].filter(Boolean).join('; '); if (variables) { const theme = scope ? 'light' : 'dark'; @@ -153,11 +155,11 @@ function candidatePreviewCss(skin: ExternalSkin): string { if (surface) css += `${prefix}.container { background: ${surface}; }\n`; if (border) css += `${prefix}.container { border-color: ${border}; }\n`; if (text) css += `${prefix}.container { color: ${text}; }\n`; - if (colors.showSelectedBar === false) css += `${prefix}.first::before { display: none; }\n`; - return css; if (number) css += `${prefix}.num, ${prefix}.cand-no { color: ${number}; }\n`; // Mirrors the candidate window: an explicit translation colour is used as-is instead of the inherited text colour at opacity .62. if (translation) css += `${prefix}.cand-translation { color: ${translation}; opacity: 1; }\n`; + if (colors.showSelectedBar === false) css += `${prefix}.first::before { display: none; }\n`; + return css; }; let css = ''; if ((skin.decorationTopDip || 0) > 0) { diff --git a/ui-html/webview2/settings/ime-settings/src/modules/toolbar-icon-glyphs.ts b/ui-html/webview2/settings/ime-settings/src/modules/toolbar-icon-glyphs.ts new file mode 100644 index 00000000..715c2894 --- /dev/null +++ b/ui-html/webview2/settings/ime-settings/src/modules/toolbar-icon-glyphs.ts @@ -0,0 +1,44 @@ +// Preview counterpart of the glyph fallback script in ftb/*.html: the toolbar markup is copied in through DOMParser, so its inline script never runs here. Segoe Fluent Icons ships with Windows 11 only and older Segoe MDL2 Assets builds lack some IME codepoints; Chromium silently substitutes a font, so a missing glyph would preview as a blank box (issue #232). U+10FFFD is in no font, which makes its rendering the reference for "missing". +const missingGlyphCache = new Map(); +let renderGlyph: ((text: string) => Uint8ClampedArray) | null | undefined; + +function glyphRenderer(): ((text: string) => Uint8ClampedArray) | null { + if (renderGlyph !== undefined) return renderGlyph; + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + let context: CanvasRenderingContext2D | null = null; + try { + context = canvas.getContext('2d', { willReadFrequently: true }); + } catch { + context = null; + } + renderGlyph = context ? (text: string) => { + context!.clearRect(0, 0, 32, 32); + context!.font = '24px "Segoe Fluent Icons", "Segoe MDL2 Assets"'; + context!.textBaseline = 'top'; + context!.fillText(text, 4, 4); + return context!.getImageData(0, 0, 32, 32).data; + } : null; + return renderGlyph; +} + +function isGlyphMissing(text: string): boolean { + const cached = missingGlyphCache.get(text); + if (cached !== undefined) return cached; + const render = glyphRenderer(); + if (!render) return false; + const missing = render('\u{10FFFD}'); + const pixels = render(text); + const result = pixels.every((value, index) => value === missing[index]); + missingGlyphCache.set(text, result); + return result; +} + +export function applyToolbarIconGlyphFallbacks(root: ParentNode): void { + root.querySelectorAll('.icon-glyph[data-fallback]').forEach((node) => { + if (!isGlyphMissing(node.textContent || '')) return; + node.textContent = node.dataset.fallback || ''; + node.classList.add('is-fallback'); + }); +} diff --git a/ui-html/webview2/settings/ime-settings/src/styles/modules/floating-toolbar.css b/ui-html/webview2/settings/ime-settings/src/styles/modules/floating-toolbar.css index 767b8225..8f81d3e1 100644 --- a/ui-html/webview2/settings/ime-settings/src/styles/modules/floating-toolbar.css +++ b/ui-html/webview2/settings/ime-settings/src/styles/modules/floating-toolbar.css @@ -47,10 +47,10 @@ .ftb-preview-host .drag-handle { position: relative; width: calc(var(--ftb-handle-width) * var(--ftb-scale)); height: calc(var(--ftb-handle-height) * var(--ftb-scale)); margin-right: 0; border-radius: calc(2px * var(--ftb-scale)); background: #8e8cd8; } .ftb-preview-host .divider { align-self: stretch; width: calc(1.2px * var(--ftb-scale)); height: auto; margin: 0 0 0 calc(4px * var(--ftb-scale)); background-color: rgba(255, 255, 255, 0.15); } .ftb-preview-host .icon { display: flex; align-items: center; justify-content: center; width: calc(var(--ftb-icon-size) * var(--ftb-scale)); height: calc(var(--ftb-icon-size) * var(--ftb-scale)); color: #fff; border-radius: calc(6px * var(--ftb-scale)); font-family: "Segoe UI Symbol", "Microsoft YaHei", sans-serif; font-size: calc(var(--ftb-icon-size) * 0.833 * var(--ftb-scale)); } -.ftb-preview-host .icon svg { width: 100%; height: 100%; display: block; } +.ftb-preview-host .icon-glyph { font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; font-size: calc(var(--ftb-icon-size) * 0.82 * var(--ftb-scale)); font-style: normal; line-height: 1; } +.ftb-preview-host .icon-glyph.is-fallback { font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif; font-size: calc(var(--ftb-icon-size) * 0.8 * var(--ftb-scale)); } .ftb-preview-host .left-section { display: flex; align-items: center; gap: calc(4px * var(--ftb-scale)); } .ftb-preview-host .right-section { display: flex; align-items: center; gap: calc(var(--ftb-gap) * var(--ftb-scale)); } -.ftb-preview-host svg { display: block; } .floating-toolbar-appearance .section-header { padding: 16px 24px; gap: 16px; } .floating-toolbar-appearance .floating-toolbar-description { margin-top: 4px; } @@ -68,10 +68,6 @@ color: #1a1a1a; } -.ftb-preview-host.theme-light .icon svg { - filter: invert(1); -} - html[data-theme="light"] .floating-toolbar-preview, html[data-theme="light"] .caret-state-preview { background: rgba(0, 0, 0, 0.03); diff --git a/ui-html/webview2/settings/ime-settings/src/styles/modules/skin.css b/ui-html/webview2/settings/ime-settings/src/styles/modules/skin.css index da26801f..466da374 100644 --- a/ui-html/webview2/settings/ime-settings/src/styles/modules/skin.css +++ b/ui-html/webview2/settings/ime-settings/src/styles/modules/skin.css @@ -136,6 +136,8 @@ .ftb-preview-host.skin-graphite.theme-dark .drag-handle { background: #8993a0; } .ftb-preview-host.skin-graphite.theme-light .drag-handle { background: #5f6b7a; } .ftb-preview-host.skin-graphite .icon { border-radius: calc(3px * var(--ftb-scale)); } +.ftb-preview-host.skin-graphite.theme-dark .icon { color: #d7dce2; } +.ftb-preview-host.skin-graphite.theme-light .icon { color: #374151; } /* Preview equivalents of the standalone Willow green skin HTML files. */ .candidate.skin-willow-green.theme-dark {