Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

75 строки
3.9KB

  1. namespace EnvelopeRenderer.Cli.Render;
  2. /// <summary>
  3. /// Pure geometry for Sprint 4's "Collapse blank optional address lines consistently": given a
  4. /// set of text lines (each with an X/Y position and whether it should collapse for this
  5. /// particular record), decides which lines are visible and where the visible ones land after
  6. /// closing the gaps left by any collapsed lines above them.
  7. ///
  8. /// <para><b>Grouping rule (a Development Team design decision — the template format has no
  9. /// explicit "address block" concept):</b> lines that share the same X position (rounded to 2
  10. /// decimal places, matching the precision the designer's properties panel and XML persistence
  11. /// already use) are treated as one vertical stack — i.e. one address block — ordered top-to-
  12. /// bottom by descending Y (PDF points increase upward, so the top line of a block has the
  13. /// highest Y). Lines at a different X never affect each other's collapsing math. This mirrors
  14. /// how a real address block is authored today: every line in it shares the same left margin.</para>
  15. ///
  16. /// <para><b>Shift math:</b> for a stack ordered top-to-bottom, line k's "row height" is defined
  17. /// as the gap between it and the line immediately below it, i.e. <c>Y[k] - Y[k+1]</c> (using the
  18. /// stack's original, undisturbed Y values — never an already-shifted one, so consecutive
  19. /// collapsed lines don't compound rounding error). When line k collapses, every line below it in
  20. /// the same stack shifts upward by that row height. The last line in a stack has no line below
  21. /// it, so its own row height is irrelevant (nothing shifts if it collapses) — a deliberate
  22. /// no-op, not a bug.</para>
  23. /// </summary>
  24. public static class AddressLineCollapser
  25. {
  26. /// <summary>One input line: its original template position and whether this particular
  27. /// record's resolved value makes it collapse (i.e. the caller has already combined "is this
  28. /// element configured as collapsible" with "is its resolved text blank for this record").</summary>
  29. public readonly record struct Line(double X, double Y, bool ShouldCollapse);
  30. /// <summary>One resolved line, same order/count as the input. <see cref="Visible"/> is
  31. /// <c>false</c> exactly for lines whose own <see cref="Line.ShouldCollapse"/> was true — the
  32. /// caller should skip drawing those. <see cref="EffectiveY"/> is the Y every line (visible or
  33. /// not) should be treated as occupying after any collapsed lines above it in its stack have
  34. /// closed their gaps.</summary>
  35. public readonly record struct Resolved(bool Visible, double EffectiveY);
  36. /// <summary>Rounding precision (decimal places) used to decide whether two lines share the
  37. /// same X position and therefore belong to the same address-line stack. 2 decimal places
  38. /// matches the precision already used by the designer's XML persistence and properties
  39. /// panel, so two lines placed at "the same" X via the UI reliably group together even after a
  40. /// save/reload round trip through floating point.</summary>
  41. private const int GroupingPrecision = 2;
  42. public static IReadOnlyList<Resolved> Resolve(IReadOnlyList<Line> lines)
  43. {
  44. var result = new Resolved[lines.Count];
  45. var groups = lines
  46. .Select((line, index) => (Line: line, Index: index))
  47. .GroupBy(t => Math.Round(t.Line.X, GroupingPrecision));
  48. foreach (var group in groups)
  49. {
  50. var stack = group.OrderByDescending(t => t.Line.Y).ToList();
  51. var cumulativeShift = 0.0;
  52. for (var i = 0; i < stack.Count; i++)
  53. {
  54. var (line, index) = stack[i];
  55. result[index] = new Resolved(!line.ShouldCollapse, line.Y + cumulativeShift);
  56. if (line.ShouldCollapse && i < stack.Count - 1)
  57. {
  58. var nextY = stack[i + 1].Line.Y;
  59. cumulativeShift += line.Y - nextY;
  60. }
  61. }
  62. }
  63. return result;
  64. }
  65. }

Powered by TurnKey Linux.