-
Notifications
You must be signed in to change notification settings - Fork 4
Add local font checking and loading functionality for improved font m… #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -233,43 +233,72 @@ | |
| if (!_fontLoaded) | ||
| { | ||
| isFontLoading = true; | ||
| fontStatus = L.T("LoadingFont"); | ||
| fontStatus = L.T("CheckingLocalFont"); | ||
| StateHasChanged(); | ||
| await Task.Yield(); | ||
|
|
||
| try | ||
| { | ||
| var request = new HttpRequestMessage(HttpMethod.Get, "fonts/NotoSansSC-Regular.ttf"); | ||
| request.SetBrowserResponseStreamingEnabled(true); | ||
| using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); | ||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| var totalBytes = response.Content.Headers.ContentLength ?? -1; | ||
| using var contentStream = await response.Content.ReadAsStreamAsync(); | ||
| using var fontMs = new MemoryStream(); | ||
| var buffer = new byte[81920]; | ||
| long totalRead = 0; | ||
| int read; | ||
|
|
||
| while ((read = await contentStream.ReadAsync(buffer)) > 0) | ||
| // Try Local Font Access API first (Chrome 103+, requires user permission) | ||
| string? localFontFamily = null; | ||
| try { localFontFamily = await JS.InvokeAsync<string?>("tryLoadLocalFontMeta"); } catch { } | ||
|
|
||
| if (localFontFamily is not null) | ||
| { | ||
| fontMs.Write(buffer, 0, read); | ||
| totalRead += read; | ||
| if (totalBytes > 0) | ||
| var jsStreamRef = await JS.InvokeAsync<IJSStreamReference?>("getLocalFontStream"); | ||
| if (jsStreamRef is not null) | ||
| { | ||
| fontProgress = (int)(totalRead * 100 / totalBytes); | ||
| fontStatus = string.Format(L.T("FontDownloadProgress"), fontProgress, FormatSize(totalRead), FormatSize(totalBytes)); | ||
| await using var fontStream = await jsStreamRef.OpenReadStreamAsync(maxAllowedSize: 50 * 1024 * 1024); | ||
| using var fontMs = new MemoryStream(); | ||
| await fontStream.CopyToAsync(fontMs); | ||
| MiniSoftware.MiniPdf.RegisterFont(localFontFamily, fontMs.ToArray()); | ||
| fontLoadSuccess = true; | ||
|
Comment on lines
+251
to
+255
|
||
| fontStatus = "✓ " + string.Format(L.T("LocalFontUsed"), localFontFamily); | ||
| } | ||
| else | ||
| { | ||
| fontStatus = $"{L.T("LoadingFont")} ({FormatSize(totalRead)})"; | ||
| localFontFamily = null; // stream unexpectedly null, fall through to download | ||
| } | ||
| StateHasChanged(); | ||
| } | ||
|
|
||
| MiniSoftware.MiniPdf.RegisterFont("NotoSansSC", fontMs.ToArray()); | ||
| fontLoadSuccess = true; | ||
| fontStatus = "✓ " + L.T("FontLoaded"); | ||
| if (localFontFamily is null) | ||
| { | ||
| // Fall back to downloading NotoSansSC | ||
| fontStatus = L.T("LoadingFont"); | ||
| StateHasChanged(); | ||
|
|
||
| var request = new HttpRequestMessage(HttpMethod.Get, "fonts/NotoSansSC-Regular.ttf"); | ||
| request.SetBrowserResponseStreamingEnabled(true); | ||
| using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); | ||
| response.EnsureSuccessStatusCode(); | ||
|
|
||
| var totalBytes = response.Content.Headers.ContentLength ?? -1; | ||
| using var contentStream = await response.Content.ReadAsStreamAsync(); | ||
| using var fontMs = new MemoryStream(); | ||
| var buffer = new byte[81920]; | ||
| long totalRead = 0; | ||
| int read; | ||
|
|
||
| while ((read = await contentStream.ReadAsync(buffer)) > 0) | ||
| { | ||
| fontMs.Write(buffer, 0, read); | ||
| totalRead += read; | ||
| if (totalBytes > 0) | ||
| { | ||
| fontProgress = (int)(totalRead * 100 / totalBytes); | ||
| fontStatus = string.Format(L.T("FontDownloadProgress"), fontProgress, FormatSize(totalRead), FormatSize(totalBytes)); | ||
| } | ||
| else | ||
| { | ||
| fontStatus = $"{L.T("LoadingFont")} ({FormatSize(totalRead)})"; | ||
| } | ||
| StateHasChanged(); | ||
| } | ||
|
|
||
| MiniSoftware.MiniPdf.RegisterFont("NotoSansSC", fontMs.ToArray()); | ||
| fontLoadSuccess = true; | ||
| fontStatus = "✓ " + L.T("FontLoaded"); | ||
| } | ||
| } | ||
|
Comment on lines
240
to
302
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The font loading logic within this |
||
| catch | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,46 @@ window.downloadFile = function (fileName, contentType, byteArray) { | |
| document.body.removeChild(a); | ||
| URL.revokeObjectURL(url); | ||
| }; | ||
|
|
||
| // Local Font Access API support (Chrome 103+) | ||
| // Returns the font-family name of the first matching CJK font, or null. | ||
| // The font blob is cached so getLocalFontStream() can return it immediately after. | ||
| let _localFontBlob = null; | ||
|
|
||
| window.tryLoadLocalFontMeta = async function () { | ||
| if (!('queryLocalFonts' in window)) return null; | ||
| try { | ||
| const fonts = await window.queryLocalFonts(); | ||
| const preferred = [ | ||
| // Windows | ||
| 'Microsoft YaHei', 'Microsoft YaHei UI', | ||
| 'SimSun', 'SimHei', 'FangSong', 'KaiTi', | ||
| // macOS / iOS | ||
| 'PingFang SC', 'Heiti SC', 'STHeiti', 'STSong', | ||
| // Japanese (Windows / macOS) | ||
| 'Meiryo', 'Yu Gothic', 'MS Gothic', 'Hiragino Sans', | ||
| // Korean (Windows / macOS) | ||
| 'Malgun Gothic', 'Apple SD Gothic Neo', 'Gulim', 'Dotum', | ||
| // Google Noto / open-source | ||
| 'Noto Sans CJK SC', 'Noto Sans SC', 'Source Han Sans SC', | ||
| ]; | ||
| let found = null; | ||
| for (const family of preferred) { | ||
| found = fonts.find(f => f.family === family && (f.style === 'Regular' || f.style === 'Normal')); | ||
| if (!found) found = fonts.find(f => f.family === family); | ||
| if (found) break; | ||
| } | ||
|
Comment on lines
+35
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation for finding a preferred font can be inefficient. It iterates over the entire const familyMap = new Map();
for (const font of fonts) {
if (!familyMap.has(font.family)) {
familyMap.set(font.family, []);
}
familyMap.get(font.family).push(font);
}
let found = null;
for (const family of preferred) {
const familyFonts = familyMap.get(family);
if (familyFonts) {
found = familyFonts.find(f => f.style === 'Regular' || f.style === 'Normal') || familyFonts[0];
if (found) {
break;
}
}
} |
||
| if (!found) return null; | ||
| _localFontBlob = await found.blob(); | ||
| return found.family; | ||
| } catch { | ||
| return null; | ||
| } | ||
| }; | ||
|
|
||
| // Returns a ReadableStream for the cached font blob (call after tryLoadLocalFontMeta succeeds). | ||
| window.getLocalFontStream = function () { | ||
| const blob = _localFontBlob; | ||
| _localFontBlob = null; // release reference | ||
| return blob ? blob.stream() : null; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swallowing exceptions with an empty
catchblock can hide bugs and make debugging difficult. If the JavaScript interop call fails for an unexpected reason, it will fail silently. It's better to log the exception to the developer console to aid in debugging, while still allowing the code to fall back to the font download behavior.