perf(性能提升): 优化字体加载与页面渲染调度

This commit is contained in:
xiaoqidun committed 2026-10-26 14:17:22 +08:00
1 parent 1061b07942
commit 15a532a1ca
2 files changed
+55 -31

No files matched your search

+14 -1
View File
@@ -64,6 +64,12 @@ class BrowserScheduler {
if (data.id == null || data.type) return;
const key = requestKey(worker, data);
const started = worker.requests.get(key);
const operation = worker.operations.get(key), owner = this.owner(worker, data);
if (started !== undefined && data.ok && owner) {
const elapsed = performance.now() - started;
if (operation?.name === "ofdgoOpen") owner.openTime = elapsed;
if (operation?.name === "ofdgoRenderPage") owner.pageTime = owner.pageTime ? owner.pageTime * 0.75 + elapsed * 0.25 : elapsed;
}
worker.requests.delete(key);
worker.operations.delete(key);
if (this.memory > this.budget) this.limit = Math.max(1, this.limit - 1);
@@ -343,6 +349,8 @@ class BrowserEngine {
this.source = null;
this.pageSource = null;
this.parallelism = 1;
this.startupTime = 0;
this.primary.pageTime = 0;
for (const slot of this.slots) this.closeSlot(slot);
}
@@ -355,6 +363,7 @@ class BrowserEngine {
}
createSlot(source) {
const started = performance.now();
const worker = createEngineWorker({page:true, url:this.workerURL});
worker.setForeground(this.foreground);
worker.pausePages(this.pagesPaused);
@@ -382,6 +391,7 @@ class BrowserEngine {
let opened = await this.callSlot(slot, "ofdgoOpen", [source.file, source.fonts, source.annotations]);
if ((source.document || 0) !== (opened.data.documentIndex || 0)) opened = await this.callSlot(slot, "ofdgoSelectDocument", [source.document]);
if ((source.version || "") !== (opened.data.documentVersion || "")) await this.callSlot(slot, "ofdgoSelectVersion", [source.version || ""]);
if (this.slots.has(slot)) this.startupTime = performance.now() - started;
});
slot.ready.catch(() => {});
return slot;
@@ -428,7 +438,10 @@ class BrowserEngine {
}
let slot = preferred || [...this.slots].filter(slot => !slot.busy).sort((a,b) => Math.abs(a.lastPage-data.args[0])-Math.abs(b.lastPage-data.args[0]))[0];
const memory = engineScheduler.memory;
if (!slot && this.foreground && !this.pagesPaused && this.slots.size < this.parallelism - 1 && memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source);
const pending = [...this.requests.values()].filter(slot => slot === null).length;
const worthwhile = (this.primary.pageTime || 0) * pending >= Math.max(50, this.startupTime || this.primary.openTime || 0);
if (!slot && worthwhile && this.foreground && !this.pagesPaused
&& this.slots.size < this.parallelism - 1 && memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source);
if (!slot) {
this.requests.set(data.id, null);
this.primary.postMessage(data, transfer);
+41 -30
View File
@@ -529,33 +529,33 @@ export function createPageView(context) {
},
async loadSVGFonts(fonts, openSeq) {
const state = context.state;
for (const font of fonts) {
if (openSeq !== context.state.openSeq) {
return;
}
if (state.svgFonts.has(font.name)) {
continue;
}
const key = `${openSeq}:${font.name}`;
let loading = state.svgFontLoads.get(key);
if (!loading) {
loading = pageView.loadSVGFont(font, openSeq);
state.svgFontLoads.set(key, loading);
}
try { await loading; }
finally { if (state.svgFontLoads.get(key) === loading) state.svgFontLoads.delete(key); }
const state = context.state, loadSeq = state.pageLoadSeq;
const current = () => state === context.state && openSeq === state.openSeq && loadSeq === state.pageLoadSeq;
for (let offset = 0; offset < fonts.length && current(); offset += 4) {
const results = await Promise.allSettled(fonts.slice(offset, offset + 4).map(async font => {
if (state.svgFonts.has(font.name)) return;
const key = `${openSeq}:${loadSeq}:${font.name}`;
let loading = state.svgFontLoads.get(key);
if (!loading) {
loading = pageView.loadSVGFont(font, openSeq, loadSeq);
state.svgFontLoads.set(key, loading);
}
try { await loading; }
finally { if (state.svgFontLoads.get(key) === loading) state.svgFontLoads.delete(key); }
}));
const failed = results.find(result => result.status === "rejected");
if (failed) throw failed.reason;
}
},
async loadSVGFont(font, openSeq) {
async loadSVGFont(font, openSeq, loadSeq = context.state.pageLoadSeq) {
const data = font.data ? { bytes: font.data } : await context.callWASM("ofdgoSVGFontData", font.name);
if (openSeq !== context.state.openSeq) {
if (openSeq !== context.state.openSeq || loadSeq !== context.state.pageLoadSeq) {
return;
}
const face = new FontFace(font.name, data.bytes, { weight: String(font.weight), style: font.style });
await face.load();
if (openSeq !== context.state.openSeq) {
if (openSeq !== context.state.openSeq || loadSeq !== context.state.pageLoadSeq) {
return;
}
document.fonts.add(face);
@@ -610,23 +610,26 @@ export function createPageView(context) {
const started = performance.now();
do {
const task = [...state.pagePresentQueue.values()].sort(pageView.comparePageRenderTask)[0];
state.pagePresentQueue.delete(task.index);
try {
if (state.pageCache.get(task.index) === task.page) {
if (task.shell && pageView.pageShell(task.index) === task.shell && !task.shell.classList.contains("rendered")
&& (!state.pageObserver || state.visiblePages.has(task.index) || task.index === state.pageIndex)) {
pageView.mountPageSVG(task.index, task.page, task.openSeq);
task.text ||= pageView.createTextLayerParts(task.page.text);
const text = task.text.next();
if (!text.done) continue;
pageView.mountPageSVG(task.index, task.page, task.openSeq, text.value);
}
if (task.thumb && context.el.pageList.querySelector(`[data-page-index="${task.index}"] .thumb-paper`) === task.thumb) pageView.updateThumbnail(task.index, task.openSeq);
}
task.resolve(null);
} catch (err) { task.reject(err); }
state.pagePresentQueue.delete(task.index);
} while (state.pagePresentQueue.size && performance.now() - started < 6);
pageView.schedulePagePresentation();
});
},
mountPageSVG(index, page, openSeq = context.state.openSeq) {
mountPageSVG(index, page, openSeq = context.state.openSeq, textLayer = null) {
if (openSeq !== context.state.openSeq) {
return;
}
@@ -638,7 +641,7 @@ export function createPageView(context) {
const svg = pageView.parseSVG(page.svg, `p${openSeq}-${index}`);
svg.classList.add("ofd-svg");
surface.replaceChildren(svg);
surface.append(pageView.createTextLayer(page.text));
surface.append(textLayer || pageView.createTextLayer(page.text));
if (context.state.editorInfo && pageView.displayMode() === "svg") {
context.mountEditorObjects(index, page, surface);
}
@@ -1621,12 +1624,11 @@ export function createPageView(context) {
return next;
};
const attrs = ["clip-path", "fill", "filter", "href", "marker-end", "marker-mid", "marker-start", "mask", "stroke", "style", "xlink:href"];
for (const attr of attrs) {
for (const node of svg.querySelectorAll(`[${attr === "xlink:href" ? "*|href" : attr}*="#"]`)) {
const selector = attrs.map(attr => `[${attr === "xlink:href" ? "*|href" : attr}*="#"]`).join(",");
for (const node of svg.querySelectorAll(selector)) {
for (const attr of attrs) {
const value = node.getAttribute(attr);
if (!value) {
continue;
}
if (!value?.includes("#")) continue;
const next = replaceRef(value);
if (next !== value) {
node.setAttribute(attr, next);
@@ -1636,11 +1638,19 @@ export function createPageView(context) {
},
createTextLayer(text) {
const parts = pageView.createTextLayerParts(text);
let next;
do { next = parts.next(); } while (!next.done);
return next.value;
},
*createTextLayerParts(text) {
const layer = document.createElement("div");
layer.className = "text-layer";
const measure = textMeasure ||= document.createElement("canvas").getContext("2d");
measure.font = "100px sans-serif";
const widths = new Map();
let count = 0;
for (const run of text.runs || []) {
const line = document.createElement("span");
line.className = "text-run";
@@ -1658,10 +1668,11 @@ export function createPageView(context) {
width = measure.measureText(span.textContent).width || 1;
widths.set(span.textContent, width);
}
const [a, b, c, d, e, f] = item.matrix.map((value) => value * MM_TO_PX);
span.style.transform = `matrix(${a / width}, ${b / width}, ${c / 100}, ${d / 100}, ${e}, ${f})`;
const [a, b, c, d, e, f] = item.matrix;
span.style.transform = `matrix(${a * MM_TO_PX / width}, ${b * MM_TO_PX / width}, ${c * MM_TO_PX / 100}, ${d * MM_TO_PX / 100}, ${e * MM_TO_PX}, ${f * MM_TO_PX})`;
line.append(span);
end = item.end;
if (++count % 128 === 0) yield;
}
const breaks = chars.slice(end).filter((char) => char === "\n").join("");
if (breaks) {