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 만 덮어쓴다).