Skip to content

PDF/SVG/DXF exporters duplicate drawing-collection logic instead of sharing DrawingDispatch.swift; DXF duplicates its own formatTolerance #795

Description

@gsdali

What

The single largest finding from the #784 duplication rescan's Swift-side pass
(Scripts/repro/784-duplication-rescan/detect-duplicate-logic.py --swift). PDFExporter.swift,
SVGExporter.swift and DXFExporter.swift each independently reimplement drawing-collection logic
that DrawingDispatch.swift already exists to share, and DXFExporter additionally reimplements
one of DrawingDispatch.swift's own functions verbatim.

1. PDF and SVG duplicate their drawing-collection pipeline almost verbatim

  • primitiveOps() (PDFExporter.swift:160, SVGExporter.swift:142): score 1.00, 170 shared
    shingles.
  • collectFromDrawing(_:...) (PDFExporter.swift:119, SVGExporter.swift:102): score 1.00, 143
    shared shingles.
  • collectProjectedEdges(...) (DXFExporter.swift:135, PDFExporter.swift:144,
    SVGExporter.swift:127): all three pairwise combinations score 1.00, 99 shared shingles each.
    DXF shares this one too, even though it does not use primitiveOps()/DrawingPrimitiveOps for
    everything else (see below).
  • strokeWidth(for:) (PDFExporter.swift:273, SVGExporter.swift:261): score 1.00, 48 shared
    shingles.

Both PDFWriter and SVGWriter are independent final class ... : @unchecked Sendable with no
shared base or protocol, so none of this is structurally forced: it is copy-paste between the two
newer exporters.

2. DXF's dimension pipeline bypasses the shared one and duplicates part of it anyway

DrawingDispatch.swift already provides a shared emitDimension(_:into:) (using the shared
DrawingPrimitiveOps) that both PDFExporter and SVGExporter call
(emitDimension(d, into: primitiveOps())). DXFExporter does not use it at all: it has its own,
completely separate addDimension(_:) / collectDimensions(_:translate:scale:) pipeline
(DXFExporter.swift:101/:298).

Worse, DXF's own formatTolerance(base:tolerance:) + TolerancedLabel
(DXFExporter.swift:322-358) is byte-identical to DrawingDispatch.swift's own
formatTolerance/TolerancedLabel (DrawingDispatch.swift:159-191, private, used by
DrawingDispatch.swift's own emitLinear/emitRadial/etc.). Both switches handle
.none/.symmetric/.fitClass/.bilateral/.unilateral/.limits identically, including the
%.3f formatting and the ±/+/- sign conventions. This is the shared-dispatch file
duplicating its own logic in a sibling file: the file that exists specifically to be the one
place this logic lives has a second, independent copy sitting right next to it.

DXF's own addDimension/collectDimensions (DXFExporter.swift:101/:298) also duplicate each
other internally: collectDimensions loops an array and (optionally) transforms each element, then
runs the identical 5-case switch (.linear/.radial/.diameter/.angular/.ordinate calling
emitLinear/emitRadial/emitDiameter/emitAngular/emitOrdinate) that addDimension also
runs on a single value; addDimension does not call collectDimensions([d]).

Why this matters

If a tolerance-formatting bug is ever found (a locale issue, a rounding change, a new
DrawingTolerance case), fixing DrawingDispatch.swift's formatTolerance, the obviously
"shared" one, would silently miss DXF entirely, because DXF never calls it. This is the exact
mechanism #784 exists to catch, at the largest scale found in this rescan.

Whether DXF's whole dimension-emission pipeline can/should route through the shared
emitDimension/DrawingPrimitiveOps abstraction is a real open question (DXF entities may need a
different intermediate representation than PDF/SVG's line/text primitives) rather than an assumed
yes, but the pure string-formatting formatTolerance/TolerancedLabel piece has no dependency on
output format and has no reason to differ at all.

Fix (not applied here, filed per #784's scope, and large enough that a fix needs its own review)

  • Minimum: DXFExporter.swift deletes its own formatTolerance/TolerancedLabel and uses
    DrawingDispatch.swift's (make it internal instead of private if needed).
    addDimension/collectDimensions factor their shared 5-case switch into one private dispatcher.
  • Larger, needs investigation first: whether collectFromDrawing/collectProjectedEdges/
    primitiveOps/strokeWidth can move into DrawingDispatch.swift as shared functions
    PDFExporter/SVGExporter/DXFExporter all call, parameterized on whatever differs
    per format (likely little, given the near-100% shared-shingle scores).

Likely non-breaking (all touched symbols are private/internal), but should be confirmed per
function before implementing, since this issue's investigation stopped at the duplication finding
rather than a full fix design.

Evidence

Detected by Scripts/repro/784-duplication-rescan/detect-duplicate-logic.py --swift at
shingle_k=15, boilerplate_min_count=4, min_distinctive=25, min_shared=25, containment_threshold=0.85. Confirmed by direct source reading of all four functions in both
files named above, including verifying PDFWriter/SVGWriter share no base class or protocol
(so none of the duplication is structurally required) and that DrawingDispatch.swift's
emitDimension is called by PDF/SVG but not DXF.

Part of #784 (Pass 1a/1b duplication rescan). See Scripts/repro/784-duplication-rescan/README.md.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cluster:kernelOCCTSwift core geometry/meshing/IO librariespriority:P2Normaltype:choreMaintenance / tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions