From d1e488625ed8c2f44de19074dd64ac576ce0e2d2 Mon Sep 17 00:00:00 2001 From: Msystech Date: Wed, 12 Aug 2026 08:57:26 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9C=A1=EC=95=88=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=A4=80=EB=B9=84=20=E2=80=94=20=EC=A0=84=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=A0=8C=EB=8D=94=20+=20=EC=A0=9C=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=B1=EC=8B=A0=20E=5FSctMst=20=EC=96=B4?= =?UTF-8?q?=EC=84=9C=EC=85=98=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [전 페이지 렌더] --db-render 가 첫 페이지만 뽑던 것을 전 페이지로 확장했다(이름_p1.png …). 운영 서식 128건이 다중 페이지라 첫 장만 봐서는 검증이 되지 않는다. [E_SctMst 어서션 정정] --db-save-smoke 가 제자리 갱신 경로에서도 sctCount == walkerCount 를 요구하고 있었는데, 이 불변식은 버저닝 경로에만 성립한다. 제자리 갱신은 레거시와 동일하게 삭제된 컨트롤의 행을 지우지 않으므로(SctKey 안정성) 과거에 지워진 컨트롤의 고아 행이 누적된다. P347 에서 실제로 걸렸다 — E_SctMst 3행(MDesignerHost1, TextBox1, TextBox2) 대 현재 XML 2개(MDesignerHost1, TextBox2)로, TextBox1 은 과거에 지워진 컨트롤의 잔존 행이었다. 기존 데이터이지 저장 경로의 결함이 아니다. 제자리 갱신은 "워커 산출 행이 전부 존재"(sctCount >= walkerCount)로 바꾸고 잔존 행 수를 리포트에 표시한다. 버저닝은 전량 재생성이므로 정확히 일치 요구를 유지한다. [검증 준비 결과] 위험도 상위 16건을 SheetMe 로 재저장하고 저장 전/후 렌더를 픽셀 비교했다 — 39페이지 전부 동일(J209 6장, S256 7장 포함). 대상 선정은 운영 활성 디자인을 전수 스캔해 위험 요소로 가중치를 매겼다: 자리표시 15건 · Spread 7건 · Binary 569건(45%) · 배경이미지 315건 · 다중페이지 128건 · 제자리갱신 36건. 레거시 뷰어 대조는 사용자가 직접 수행한다(공용 ServerInfo ini 의 CurrentServer 를 운영에서 테스트로 바꿔야 하는데, 그 파일을 레거시 EXE 187개가 공유하므로 자동화하지 않았다). 바탕화면 'SheetMe-검증패키지' 에 체크리스트·렌더 PNG 39장·자동검증 리포트를 준비했다. 검증: 테스트 70/70, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신(P163 정상/P347 잔존 1건 정상)·버저닝(S999) 양쪽 통과. Co-Authored-By: Claude Fable 5 --- src/SheetMe.Designer/App.xaml.cs | 42 ++++++++++++++------- src/SheetMe.Designer/Diagnostics/DbSmoke.cs | 22 +++++++++-- 2 files changed, 46 insertions(+), 18 deletions(-) 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 만 덮어쓴다).