diff --git a/src/SheetMe.Designer/App.xaml.cs b/src/SheetMe.Designer/App.xaml.cs
index 0ec30cf..1147fb7 100644
--- a/src/SheetMe.Designer/App.xaml.cs
+++ b/src/SheetMe.Designer/App.xaml.cs
@@ -201,7 +201,11 @@ public partial class App : Application
}
}
- /// DB 서식 첫 페이지 오프스크린 렌더 → PNG (Spread 격자 주입 경로 포함 검증)
+ ///
+ /// DB 서식 오프스크린 렌더 → PNG (Spread 격자 주입 경로 포함 검증).
+ /// 페이지가 여럿이면 이름_p1.png, 이름_p2.png … 로 전부 뽑는다 —
+ /// 운영 서식 128건이 다중 페이지라 첫 장만 봐서는 검증이 되지 않는다.
+ ///
private static int RunDbRenderSmoke(string shtCod, string pngPath)
{
try
@@ -215,22 +219,32 @@ public partial class App : Application
}
var designer = new ViewModels.DesignerViewModel(document, dataBusiness.LoadSpreadGrids(shtCod));
- var page = designer.Pages[0];
+ var directory = Path.GetDirectoryName(pngPath) ?? ".";
+ var stem = Path.GetFileNameWithoutExtension(pngPath);
- var view = new PageView { DataContext = page };
- var size = new Size(page.WidthDip, page.HeightDip);
- view.Measure(size);
- view.Arrange(new Rect(size));
- view.UpdateLayout();
+ for (var i = 0; i < designer.Pages.Count; i++)
+ {
+ var page = designer.Pages[i];
+ var view = new PageView { DataContext = page };
+ var size = new Size(page.WidthDip, page.HeightDip);
+ view.Measure(size);
+ view.Arrange(new Rect(size));
+ view.UpdateLayout();
- var bitmap = new RenderTargetBitmap(
- (int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height), 96, 96, PixelFormats.Pbgra32);
- bitmap.Render(view);
+ var bitmap = new RenderTargetBitmap(
+ (int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height), 96, 96, PixelFormats.Pbgra32);
+ bitmap.Render(view);
- var encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(bitmap));
- using var stream = File.Create(pngPath);
- encoder.Save(stream);
+ var encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(bitmap));
+ var target = designer.Pages.Count == 1
+ ? pngPath
+ : Path.Combine(directory, $"{stem}_p{i + 1}.png");
+ using var stream = File.Create(target);
+ encoder.Save(stream);
+ }
+
+ Console.WriteLine($"{shtCod}: 페이지 {designer.Pages.Count} 렌더 완료");
return 0;
}
catch (Exception ex)
diff --git a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs
index fce8df5..315b3f6 100644
--- a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs
+++ b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs
@@ -519,15 +519,29 @@ public static class DbSmoke
var sctCount = store.CountSctRows(savedKey);
var walkerCount = SctMstXmlWalker.Walk(after.Xml).Count;
- lines.Add($"[E_SctMst] 저장본 행 {sctCount}건 / 워커 기대 {walkerCount}건 → {(sctCount == walkerCount ? "일치" : "불일치")}");
-
- if (cneYon != "Y")
+ bool sctOk;
+ if (cneYon == "Y")
{
+ // 제자리 갱신은 레거시와 동일하게 삭제된 컨트롤의 행을 지우지 않는다(SctKey 안정성).
+ // 따라서 과거에 지워진 컨트롤의 고아 행이 누적될 수 있고, 행 수 동일은 성립하지 않는다.
+ // 옳은 불변식은 "워커가 낸 행이 전부 존재하고, 재저장으로 늘지 않는다" 이다.
+ sctOk = sctCount >= walkerCount;
+ var orphans = sctCount - walkerCount;
+ lines.Add($"[E_SctMst] 저장본 행 {sctCount}건 / 워커 기대 {walkerCount}건 → " +
+ (sctOk ? $"정상{(orphans > 0 ? $" (과거 삭제 컨트롤의 잔존 행 {orphans}건 — 레거시도 지우지 않음)" : "")}"
+ : "부족! 워커 산출 행이 DB 에 없음"));
+ }
+ else
+ {
+ // 버저닝은 E_SctMst 를 새 키로 전량 재생성하므로 정확히 일치해야 한다
+ sctOk = sctCount == walkerCount;
+ lines.Add($"[E_SctMst] 저장본 행 {sctCount}건 / 워커 기대 {walkerCount}건 → {(sctOk ? "일치" : "불일치")}");
+
var oldVersion = versionsAfter.FirstOrDefault(v => v.SdgKey == before.SdgKey);
lines.Add($"[이력] 이전 버전 SdgKey={before.SdgKey} SdgDelYon='Y' 처리: {(oldVersion?.Deleted == true ? "확인" : "미확인!")}");
}
- var ok = diffs.Count == 0 && sctCount == walkerCount;
+ var ok = diffs.Count == 0 && sctOk;
// 제자리 갱신 경로에서만 의미가 있는 검사 — 무변경 재저장으로는 SctObjTxt 갱신을 증명할 수 없으므로
// 문구를 실제로 바꿔 E_SctMst 에 반영되는지 확인하고 원복한다(레거시는 SctObjTxt 만 덮어쓴다).