perf: auto one page position

This commit is contained in:
JOYCEQL
2025-11-23 19:58:53 +08:00
parent c231610a3f
commit d19fd76882
+114 -155
View File
@@ -14,12 +14,12 @@ interface OptimizationParams {
const FONT_CONSTRAINTS = { const FONT_CONSTRAINTS = {
minBaseFontSize: 14, minBaseFontSize: 14,
minHeaderSize: 16, minHeaderSize: 14,
minSubheaderSize: 16, minSubheaderSize: 14,
headerRatio: 1.25, headerRatio: 1.25,
subheaderRatio: 1.0, subheaderRatio: 1.0,
minHeaderDiff: 2, minHeaderDiff: 2,
minSubheaderDiff: 2 minSubheaderDiff: 2,
}; };
interface OptimizationResult { interface OptimizationResult {
@@ -50,6 +50,7 @@ export const useAutoFitOnePage = () => {
const rect = resumeContent.getBoundingClientRect(); const rect = resumeContent.getBoundingClientRect();
return Math.max(0, rect.height); return Math.max(0, rect.height);
}; };
const calculateCoordinatedFontSizes = useCallback( const calculateCoordinatedFontSizes = useCallback(
(baseFontSize: number): { headerSize: number; subheaderSize: number } => { (baseFontSize: number): { headerSize: number; subheaderSize: number } => {
let headerSize = Math.round(baseFontSize * FONT_CONSTRAINTS.headerRatio); let headerSize = Math.round(baseFontSize * FONT_CONSTRAINTS.headerRatio);
@@ -77,29 +78,6 @@ export const useAutoFitOnePage = () => {
[] []
); );
const adjustFontSizesCoordinated = useCallback(
(
currentParams: OptimizationParams,
scaleFactor: number
): OptimizationParams => {
const newBaseFontSize = Math.max(
FONT_CONSTRAINTS.minBaseFontSize,
Math.round(currentParams.baseFontSize * scaleFactor)
);
const { headerSize, subheaderSize } =
calculateCoordinatedFontSizes(newBaseFontSize);
return {
...currentParams,
baseFontSize: newBaseFontSize,
headerSize,
subheaderSize
};
},
[calculateCoordinatedFontSizes]
);
const applyOptimizationParams = useCallback( const applyOptimizationParams = useCallback(
(params: OptimizationParams): void => { (params: OptimizationParams): void => {
updateGlobalSettings({ updateGlobalSettings({
@@ -109,7 +87,7 @@ export const useAutoFitOnePage = () => {
lineHeight: params.lineHeight, lineHeight: params.lineHeight,
paragraphSpacing: params.paragraphSpacing, paragraphSpacing: params.paragraphSpacing,
sectionSpacing: params.sectionSpacing, sectionSpacing: params.sectionSpacing,
pagePadding: params.pagePadding pagePadding: params.pagePadding,
}); });
}, },
[updateGlobalSettings] [updateGlobalSettings]
@@ -118,18 +96,64 @@ export const useAutoFitOnePage = () => {
const waitForDOMUpdate = (): Promise<void> => { const waitForDOMUpdate = (): Promise<void> => {
return new Promise((resolve) => { return new Promise((resolve) => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
setTimeout(resolve, 100); setTimeout(resolve, 50);
}); });
}); });
}; };
const calculateParamsFromScale = useCallback(
(scale: number, initialParams: OptimizationParams): OptimizationParams => {
const lerp = (start: number, end: number, t: number) => {
return start + (end - start) * t;
};
const minBase = FONT_CONSTRAINTS.minBaseFontSize;
const currentBase = initialParams.baseFontSize;
const targetBase = Math.max(minBase, currentBase);
const newBaseFontSize = Math.round(lerp(minBase, targetBase, scale));
const { headerSize, subheaderSize } =
calculateCoordinatedFontSizes(newBaseFontSize);
const minLineHeight = 1.2;
const newLineHeight = Number(
lerp(minLineHeight, initialParams.lineHeight, scale).toFixed(2)
);
const minParaSpacing = 4;
const minSectionSpacing = 12;
const newParaSpacing = Math.round(
lerp(minParaSpacing, initialParams.paragraphSpacing, scale)
);
const newSectionSpacing = Math.round(
lerp(minSectionSpacing, initialParams.sectionSpacing, scale)
);
const minPadding = 20;
const newPadding = Math.round(
lerp(minPadding, initialParams.pagePadding, scale)
);
return {
baseFontSize: newBaseFontSize,
headerSize,
subheaderSize,
lineHeight: newLineHeight,
paragraphSpacing: newParaSpacing,
sectionSpacing: newSectionSpacing,
pagePadding: newPadding,
};
},
[calculateCoordinatedFontSizes]
);
const autoFitOnePage = useCallback(async (): Promise<OptimizationResult> => { const autoFitOnePage = useCallback(async (): Promise<OptimizationResult> => {
if (!activeResume) { if (!activeResume) {
throw new Error("没有活跃的简历"); throw new Error("没有活跃的简历");
} }
const { globalSettings = {} } = activeResume; const { globalSettings = {} } = activeResume;
const baseFontSize = globalSettings.baseFontSize || 16; const baseFontSize = globalSettings.baseFontSize || 16;
const { headerSize, subheaderSize } = const { headerSize, subheaderSize } =
calculateCoordinatedFontSizes(baseFontSize); calculateCoordinatedFontSizes(baseFontSize);
@@ -141,161 +165,96 @@ export const useAutoFitOnePage = () => {
lineHeight: globalSettings.lineHeight || 1.5, lineHeight: globalSettings.lineHeight || 1.5,
paragraphSpacing: globalSettings.paragraphSpacing || 12, paragraphSpacing: globalSettings.paragraphSpacing || 12,
sectionSpacing: globalSettings.sectionSpacing || 24, sectionSpacing: globalSettings.sectionSpacing || 24,
pagePadding: globalSettings.pagePadding || 32 pagePadding: globalSettings.pagePadding || 32,
}; };
let currentParams = { ...initialParams };
let iterations = 0;
const maxIterations = 20;
const tolerance = 10;
const targetHeight = calculateTargetHeight(currentParams.pagePadding);
const initialHeight = getCurrentContentHeight(); const initialHeight = getCurrentContentHeight();
const initialTargetHeight = calculateTargetHeight(
initialParams.pagePadding
);
console.log("开始自动一页纸优化", { console.log("开始自动一页纸优化 (Binary Search)", {
targetHeight,
initialHeight, initialHeight,
initialParams, targetHeight: initialTargetHeight,
needsReduction: initialHeight > targetHeight
}); });
if (initialHeight <= targetHeight + tolerance) {
if (initialHeight <= initialTargetHeight) {
return { return {
success: true, success: true,
finalParams: currentParams, finalParams: initialParams,
iterations: 0, iterations: 0,
finalHeight: initialHeight, finalHeight: initialHeight,
targetHeight, targetHeight: initialTargetHeight,
initialHeight, initialHeight,
reductionPercentage: 0 reductionPercentage: 0,
}; };
} }
while (iterations < maxIterations) { let low = 0.0;
iterations++; let high = 1.0;
let bestParams = initialParams;
let bestHeight = initialHeight;
let bestScale = 1.0;
let success = false;
applyOptimizationParams(currentParams); const iterations = 8;
for (let i = 0; i < iterations; i++) {
const mid = (low + high) / 2;
const params = calculateParamsFromScale(mid, initialParams);
applyOptimizationParams(params);
await waitForDOMUpdate(); await waitForDOMUpdate();
const currentHeight = getCurrentContentHeight(); const currentHeight = getCurrentContentHeight();
const currentTargetHeight = calculateTargetHeight(params.pagePadding);
console.log(`${iterations}次迭代`, { console.log(
currentHeight, `Iteration ${i + 1}: scale=${mid.toFixed(
targetHeight, 3
difference: currentHeight - targetHeight, )}, height=${currentHeight}, target=${currentTargetHeight}`
params: currentParams );
});
if (currentHeight <= targetHeight + tolerance) { if (currentHeight <= currentTargetHeight) {
const reductionPercentage = success = true;
((initialHeight - currentHeight) / initialHeight) * 100; bestParams = params;
return { bestHeight = currentHeight;
success: true, bestScale = mid;
finalParams: currentParams, low = mid;
iterations,
finalHeight: currentHeight,
targetHeight,
initialHeight,
reductionPercentage
};
}
const heightRatio = targetHeight / currentHeight;
const reductionNeeded = (currentHeight - targetHeight) / currentHeight;
const isEarlyIteration = iterations <= 5;
const isLateIteration = iterations > 15;
let fontScaleFactor = 1.0;
let spacingScaleFactor = 1.0;
let lineHeightScaleFactor = 1.0;
if (reductionNeeded > 0.4) {
fontScaleFactor = 0.85;
spacingScaleFactor = 0.7;
lineHeightScaleFactor = 0.92;
} else if (reductionNeeded > 0.25) {
fontScaleFactor = 0.9;
spacingScaleFactor = 0.8;
lineHeightScaleFactor = 0.95;
} else if (reductionNeeded > 0.15) {
fontScaleFactor = 0.95;
spacingScaleFactor = 0.9;
lineHeightScaleFactor = 0.97;
} else if (reductionNeeded > 0.05) {
fontScaleFactor = 0.98;
spacingScaleFactor = 0.95;
lineHeightScaleFactor = 0.99;
} else { } else {
fontScaleFactor = 0.99; high = mid;
spacingScaleFactor = 0.98;
lineHeightScaleFactor = 0.995;
}
currentParams = adjustFontSizesCoordinated(
currentParams,
fontScaleFactor
);
currentParams.lineHeight = Math.max(
1.3,
currentParams.lineHeight * lineHeightScaleFactor
);
currentParams.paragraphSpacing = Math.max(
2,
Math.round(currentParams.paragraphSpacing * spacingScaleFactor)
);
currentParams.sectionSpacing = Math.max(
6,
Math.round(currentParams.sectionSpacing * spacingScaleFactor)
);
if (isLateIteration && currentParams.pagePadding > 20) {
currentParams.pagePadding = Math.max(
16,
currentParams.pagePadding * 0.9
);
}
if (
currentParams.baseFontSize <= FONT_CONSTRAINTS.minBaseFontSize &&
currentParams.pagePadding > 16
) {
currentParams.pagePadding = Math.max(
16,
currentParams.pagePadding * 0.9
);
const newTargetHeight = calculateTargetHeight(
currentParams.pagePadding
);
if (currentHeight <= newTargetHeight + tolerance) {
const reductionPercentage =
((initialHeight - currentHeight) / initialHeight) * 100;
return {
success: true,
finalParams: currentParams,
iterations,
finalHeight: currentHeight,
targetHeight: newTargetHeight,
initialHeight,
reductionPercentage
};
}
} }
} }
const finalHeight = getCurrentContentHeight(); if (success) {
applyOptimizationParams(bestParams);
await waitForDOMUpdate();
} else {
const minParams = calculateParamsFromScale(0, initialParams);
applyOptimizationParams(minParams);
await waitForDOMUpdate();
bestParams = minParams;
bestHeight = getCurrentContentHeight();
}
const finalTargetHeight = calculateTargetHeight(bestParams.pagePadding);
const reductionPercentage = const reductionPercentage =
((initialHeight - finalHeight) / initialHeight) * 100; ((initialHeight - bestHeight) / initialHeight) * 100;
return { return {
success: finalHeight <= targetHeight + tolerance, success,
finalParams: currentParams, finalParams: bestParams,
iterations, iterations,
finalHeight, finalHeight: bestHeight,
targetHeight, targetHeight: finalTargetHeight,
initialHeight, initialHeight,
reductionPercentage reductionPercentage,
}; };
}, [ }, [
activeResume, activeResume,
adjustFontSizesCoordinated,
applyOptimizationParams, applyOptimizationParams,
calculateCoordinatedFontSizes calculateCoordinatedFontSizes,
calculateParamsFromScale,
]); ]);
const resetToDefaults = useCallback(() => { const resetToDefaults = useCallback(() => {
@@ -312,7 +271,7 @@ export const useAutoFitOnePage = () => {
lineHeight: 1.5, lineHeight: 1.5,
paragraphSpacing: 12, paragraphSpacing: 12,
sectionSpacing: 24, sectionSpacing: 24,
pagePadding: 32 pagePadding: 32,
}; };
updateGlobalSettings(defaultParams); updateGlobalSettings(defaultParams);
@@ -322,6 +281,6 @@ export const useAutoFitOnePage = () => {
autoFitOnePage, autoFitOnePage,
resetToDefaults, resetToDefaults,
getCurrentContentHeight, getCurrentContentHeight,
calculateTargetHeight calculateTargetHeight,
}; };
}; };