This is an automated email from the ASF dual-hosted git repository. leginee pushed a commit to branch bazel-migration in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit c772a5afb7c8d8b0df33796fa2511c1b51d4a248 Author: Peter Kovacs <[email protected]> AuthorDate: Wed Jul 29 19:05:43 2026 +0200 fix(idxf): DXF import filter — scaling, geometry, hatch, 3D and encoding Back-port of apache/openoffice#491 (merge c082241a9b, trunk, 2026-07-28), squashed. The work originated on the local branch dxf-filter-fix, which used Bazel only as a fast iteration harness; the deliverable was always a source-only C++ fix, so it went upstream first and returns here. The tree is byte-identical to trunk's post-PR state for all 11 files. Twelve defects, all root-caused in main/filter/source/graphicfilter/idxf/Readme.md (added here as the permanent developer doc: pipeline, the coordinate systems that cause most DXF bugs, and the root cause per Bugzilla id): - 58347 / 70274 stale header $EXTMIN/$EXTMAX used as the scaling box; always measure geometry instead, plus a 1% page-fit margin. - 16564 negative-Z extrusion arcs drawn as their complement (Mirror() tested the 3D, not the 2D projected, determinant); R13 curves are ELLIPSE/SPLINE, which were unsupported — now implemented. - 122565 three bugs: detector rejected leading 999 comments; ReadI rejected float-formatted int flags; ARC bbox unioned the full circle instead of the swept extent. Also an OOB write. - 112320 LWPOLYLINE per-vertex bulge (group 42) dropped -> circle drawn as a bar; now tessellated. - 70273 case-sensitive SearchVPort missed *Active -> flat drop-Z. - 70275 zero-length dot in a linetype pattern rendered invisible. - 99893 OCS extrusion transform wrongly applied to WCS entities -> scattered 3D; DXFCoordsAreWCS gate + 3D text via outlines. - (no BZ) HATCH pattern fills flood-filled as solid blobs; now render real section lines via VCL DrawHatch. - (no BZ) oversized LWPOLYLINE truncated; now chunked. - 99892 filter half only: $DWGCODEPAGE resolution, system-encoding default with a UTF-8 guard, and a CharacterSet override seam. The UI chooser that would feed it is still open. makefile.mk gains $(SVTOOLLIB) because idxf.cxx now reads FilterConfigItem; under Bazel the graphic_filter macro already supplies it, so //main/filter:idx needed no change. The PR's .gitignore hunk is deliberately NOT taken. Squashed from these 11 upstream trunk commits (kept here so they are identifiable despite the lost patch-id parity — see CLAUDE.md, BACKPORT #2): 999d0e5d3c 22079cd995 65c7e1576f e345bd9383 030749d53e 82ba6d6560 c4b51fde1e 5ed424c6ce 3931960203 05e2156ffd 96b0524be2 The 13.5 MB Bugzilla sample corpus and the still-open cases stay out of this tree, on the local branch bugfix-dxf-filter-open. --- CLAUDE.md | 21 + main/filter/source/graphicfilter/idxf/Readme.md | 128 +++++ main/filter/source/graphicfilter/idxf/dxf2mtf.cxx | 553 +++++++++++++++++---- main/filter/source/graphicfilter/idxf/dxf2mtf.hxx | 7 + main/filter/source/graphicfilter/idxf/dxfentrd.cxx | 151 +++++- main/filter/source/graphicfilter/idxf/dxfentrd.hxx | 70 ++- main/filter/source/graphicfilter/idxf/dxfgrprd.cxx | 14 +- main/filter/source/graphicfilter/idxf/dxfreprd.cxx | 354 ++++++++++--- main/filter/source/graphicfilter/idxf/dxftblrd.cxx | 20 +- main/filter/source/graphicfilter/idxf/dxfvec.cxx | 13 +- main/filter/source/graphicfilter/idxf/idxf.cxx | 19 +- main/filter/source/graphicfilter/idxf/makefile.mk | 2 +- main/svtools/source/filter/filter.cxx | 28 +- 13 files changed, 1200 insertions(+), 180 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7ce2ba1c64..3d4a3f8dae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -149,6 +149,27 @@ GREEN per user build). Two groups: (depends on upstream 11db77d7e7 macOS x86 bridge rework, not carried here); only its extensions/update files were taken. Noted in that commit's body. +BACKPORT #2 LANDED 2026-07-29 (idxf DXF import filter, SQUASHED to one commit). +Unlike #1 this work ORIGINATED here: it was developed on the local branch +dxf-filter-fix using Bazel purely as the fast iteration harness, went upstream as +apache/openoffice#491 (merge c082241a9b, 2026-07-28), and was then brought back from +trunk. LANDMINE for the `git cherry` method above: because it was squashed, the 11 +upstream commits have NO patch-id match here and WILL show as `+` candidates +(999d0e5d3c 22079cd995 65c7e1576f e345bd9383 030749d53e 82ba6d6560 c4b51fde1e +5ed424c6ce 3931960203 05e2156ffd 96b0524be2). Do NOT re-apply them — they are +already in, verified tree-identical to trunk's post-PR state; the list also lives in +the squash commit's body. Source-only, 11 files: +main/filter/source/graphicfilter/idxf/* (scaling/extents, arc+ellipse+spline geometry, +LWPOLYLINE bulge, linetype dots, HATCH patterns, 3D OCS/WCS + VPORT, $DWGCODEPAGE +encoding) + main/svtools/source/filter/filter.cxx (DXF detector tolerates leading 999 +comments). Root causes per defect: main/filter/source/graphicfilter/idxf/Readme.md. + DEVIATION: the PR's .gitignore hunk was DROPPED — bazel-migration's .gitignore + already covers bazel-*/user.bazelrc/.vscode, and trunk's `*.lock` + whole-dir + `.claude/` are wrong here (MODULE.bazel.lock is tracked). + NOT carried to AOO42X/AOO41X yet. The 13.5 MB Bugzilla sample corpus and the + still-open cases (99892 Phase B encoding-chooser UI, text-dense thumbnail) live on + the local branch bugfix-dxf-filter-open, deliberately out of this tree. + DO NOT PORT (VC9 island landmines — see the trunk drift audit above): boost 1.84 (2e60be5056), pyuno→Python 3 (27dee00ef8), C++11 floor (7ce5b5df31), autoconf 2.72, the macOS/Apple-Silicon batch, GTK/iODBC/lld/icc-Clang16. diff --git a/main/filter/source/graphicfilter/idxf/Readme.md b/main/filter/source/graphicfilter/idxf/Readme.md new file mode 100644 index 0000000000..72ed1bb686 --- /dev/null +++ b/main/filter/source/graphicfilter/idxf/Readme.md @@ -0,0 +1,128 @@ +# idxf — DXF import filter (developer notes) + +Imports AutoCAD DXF drawings into AOO as a GDI metafile (vector graphic). This +document is the developer entry point for the filter: the pipeline, the +coordinate systems that cause most of its bugs, and the root cause of every +defect fixed so far. + +> **Status.** The fixes described below are source-only C++ and landed upstream on +> **trunk** via [PR #491](https://github.com/apache/openoffice/pull/491) +> (merge `c082241a9b`, 2026-07-28). They were back-ported onto `bazel-migration` +> with `cherry-pick -x` so the two stay patch-id identical. Not yet ported to +> AOO42X / AOO41X. +> +> **Regression corpus.** The DXF samples, reference renders and per-case analyses +> (`cases/`, ~13.5 MB of Bugzilla attachments) are deliberately **not in this +> tree** — they live on the local branch `bugfix-dxf-filter-open`, together with +> the cases that are still unsolved. The comment in `idxf.cxx` that points at +> `idxf/cases/case-99892` refers to that branch. + +## Pipeline / architecture + +DXF is a tagged ASCII (or binary) format: a flat stream of (group-code, value) +pairs. Import runs in three stages: + +1. **Tokenize** — `DXFGroupReader` (dxfgrprd.cxx) reads the raw group/value + pairs and exposes typed accessors (`GetG/GetS/GetI/GetF`). +2. **Model** — `DXFRepresentation` (dxfreprd.cxx) parses the SECTIONs into an + in-memory model: + - HEADER vars (`$EXTMIN/$EXTMAX`, `$DWGCODEPAGE`, `$LTSCALE`) — dxfreprd.cxx. + - TABLES (layers, linetypes, VPORTs) — dxftblrd.cxx. + - BLOCKS (reusable geometry for INSERT) — dxfblkrd.cxx. + - ENTITIES (the drawing itself) — dxfentrd.cxx. Each entity subclass has an + `EvaluateGroup()`; unknown groups fall through to `DXFBasicEntity`, which + handles the shared ones (layer 8, color 62, thickness 39, extrusion + 210/220/230, …). +3. **Render** — `DXF2GDIMetaFile` (dxf2mtf.cxx) walks the entities and emits + VCL primitives into a `VirtualDevice`, producing the metafile. + +`idxf.cxx` is the thin UNO/GraphicFilter entry point; `dxfvec.cxx/.hxx` provides +the `DXFVector` (3D double vector) and `DXFTransform` (the transform matrix) +math used throughout rendering. + +Note that the filter is **not** self-contained at link time: `idxf.cxx` reads the +caller's `FilterConfigItem`, so the library links svtools (`$(SVTOOLLIB)` in +makefile.mk; under Bazel the `graphic_filter` macro already supplies it, so +`//main/filter:idx` needed no change). + +## Coordinate systems (the source of most bugs) + +- **OCS / extrusion (ECS→WCS):** each entity may carry an extrusion direction + (groups 210/220/230, default (0,0,1)). Non-default extrusion means the entity + is defined in its own object plane; `DXFTransform(rExtrusion)` builds the + ECS→WCS matrix via Autodesk's *Arbitrary Axis Algorithm* (dxfvec.cxx:89-102). + A negative-Z extrusion yields a **reflection** — `DXFTransform::Mirror()` is + the left-handedness test (dxfvec.cxx:246); it must test the **2D projected** + determinant, not the 3D one, or negative-Z arcs come out as their complement + (BZ 16564). Equally important: the transform must only be applied to entities + whose coordinates are actually in OCS — `DXFCoordsAreWCS` gates the WCS types + (LINE/POINT/3DFACE/3D polyline), which otherwise scatter (BZ 99893). +- **World → page (scaling):** the drawing is fit to the page using + `aBoundingBox`. This used to be a trap: if the HEADER supplied + `$EXTMIN/$EXTMAX` those were used directly and the geometry was never + measured — but AutoCAD's header extents are stale/zoom-affected, which broke + scaling (BZ 58347). `Read` now always measures the geometry with + `CalcBoundingBox` and prefers it, keeping the header only as an empty-box + fallback. Rules that fell out of the follow-up cases: + - measure what the renderer **draws**: every entity type `DrawEntities` + handles (HATCH included), and *only* those (SHAPE/ATTDEF are deliberately + excluded because the renderer never draws them); + - measure it the way the renderer draws it: through the entity's ECS→WCS + transform (`UnionOCS`), else negative-Z geometry inflates the box; and by an + ARC's **swept** extent, not its full circle, else a large-radius arc with an + off-sheet centre collapses the drawing (BZ 122565); + - page-fit leaves a 1% symmetric margin, so geometry sitting exactly on the + fit boundary isn't edge-clipped (BZ 58347 / 70274). +- **3D:** viewport projection IS wired in — `Convert()` (dxf2mtf.cxx:1069-1134) + honors the `*ACTIVE` VPORT and, for a non-top view direction, projects via + `DXFTransform(rViewDir,rViewTarget)` (dxfvec.cxx:105, used at 1126). Only when + there is no usable (non-top) viewport does it fall back to the flat drop-Z + projection (`Transform(...,Point)` drops Z, dxfvec.cxx:158). The VPORT lookup + is case-insensitive — files naming their viewport `*Active` used to miss + `strcmp("*ACTIVE",…)` and silently render flat (BZ 70273). TEXT in a 3D plane + goes through `Draw3DTextEntity`, which transforms glyph outlines like ordinary + geometry. +- **Text encoding:** `$DWGCODEPAGE` → rtl encoding via `DXFCodePageToTextEncoding` + (dxfreprd.cxx: extracts the code-page number from `ANSI_<cp>`/`DOS<cp>` and + resolves through `rtl_getTextEncodingFromWindowsCodePage`). Precedence is + **file > caller > default**: no codepage in the file → the caller's + `"CharacterSet"` `FilterConfigItem` option (read in idxf.cxx before `Read`, so + a codepage in the file still wins) → else the system encoding + (`gsl_getSystemTextEncoding`), but if that is UTF-8/UTF-7/UCS2/UCS4/unknown it + falls back to MS_1252, because a UTF-8 host would otherwise mis-decode legacy + single-byte DXF bytes into tofu. + +## Defects fixed + +| BZ | Title | Area | Root cause / fix | +|----|-------|------|------------------| +| [58347](https://bz.apache.org/ooo/show_bug.cgi?id=58347) | Wrong scaling after import | scaling / extents | Stale header `$EXTMIN/$EXTMAX` used as the bounding box. dxfreprd.cxx: always measure geometry (`CalcBoundingBox`, WCS/extrusion-aware) and prefer it; HATCH measurement gap closed. dxf2mtf.cxx: 1% page-fit margin so a tight box doesn't edge-clip the frame. | +| [16564](https://bz.apache.org/ooo/show_bug.cgi?id=16564) | Arcs imported incorrectly | geometry / arcs | **(a)** Negative-Z extrusion arcs drawn as their complement: `Mirror()` tested the 3D determinant, but the Arbitrary-Axis result for ext (0,0,-1) is a proper rotation that flips only the *in-plane* orientation → now tests the 2D projected determinant, plus an extrusion-aware bbox. **(b)** The R13 file's "arcs" are 14 ELLIPSE + 8 SPLINE that idxf did not support at all → added ELLIPS [...] +| [70274](https://bz.apache.org/ooo/show_bug.cgi?id=70274) | Cutting edges on import | scaling / clip | Duplicate of 58347: width 180 > height 90 put the far edge exactly on the fit boundary → right-edge clip. Resolved by the page-fit margin. | +| [14993](https://bz.apache.org/ooo/show_bug.cgi?id=14993) | Objects lost / incorrect | import (old) | No fix needed. Plain 2D LINE/ARC/POINT/CIRCLE, no extrusion, all supported; an old Linux-only 1.0.2/1.1beta defect, reporter confirms fixed since 3.0.0. Verified by spot-check. | +| [122565](https://bz.apache.org/ooo/show_bug.cgi?id=122565) | Import fails: "unknown graphics format" | detection + reader + scaling | Pro/ENGINEER file, **three** independent bugs: (1) it opens with `999` comment groups but the detector (`ImpPeekGraphicFormat`, svtools/source/filter/filter.cxx) demanded first char `0` + `SECTION` → now skips leading `999`; (2) DIMSTYLE flags `71`/`72` written as floats (`"1.0"`) made `DXFGroupReader::ReadI` reject the value and abort the import → now t [...] +| [112320](https://bz.apache.org/ooo/show_bug.cgi?id=112320) | Circle rendered as a bar | geometry / LWPOLYLINE bulge | Eagle stores the circle as a *bulged* LWPOLYLINE, not a CIRCLE; the per-vertex bulge (group 42) was dropped, so straight chords were drawn = a bar. Read the bulge and tessellate the arcs in `DrawLWPolyLineEntity` (dxfentrd.hxx/.cxx, dxf2mtf.cxx). | +| [70273](https://bz.apache.org/ooo/show_bug.cgi?id=70273) | Partial import (misaligned) | viewport lookup / 3D | Not missing entities — all imported. The file names its viewport `*Active`, which the case-sensitive `SearchVPort` missed → flat drop-Z projection. Fixed in dxftblrd.cxx. (This file's saved view genuinely *is* top (0,0,1), so top-view rendering is correct by design.) | +| [70275](https://bz.apache.org/ooo/show_bug.cgi?id=70275) | Wrong line type | linetype | A dotted line (`DOT2` = `[0.0,-3.175]`) drew solid/invisible: a `0.0` pattern element is a *dot* (zero-length pen-down), so `fDotLen=0` clamped only to 1/100mm. `LTypeToDXFLineInfo` (dxf2mtf.cxx) now gives a zero-length dot a model-unit length of 0.05·gap so it scales and reads as a dot; real dashes untouched. VCL has no round-dot line style (native Draw is dashed too), so true round dots remain fut [...] +| _(no BZ)_ | HATCH pattern fills render as solid blobs | area fill / hatch | Found via 122565: `DrawHatchEntity` ignored `nFlags`, so pattern fills (group 70 = 0) were flood-filled in the entity colour. Now gates solid fill on `nFlags & 1`; for a pattern fill it parses the pattern-definition line (angle 53, offset 45/46 — new fields in dxfentrd.hxx/.cxx) and draws the section lines via VCL `DrawHatch` (angle+spacing mapped through `rTransform`, style from group 78); the boundary tessell [...] +| [99893](https://bz.apache.org/ooo/show_bug.cgi?id=99893) | 3D DXF imported flat / scattered | 3D / extrusion + projection | Viewport projection was already correct; the real cause was applying the OCS/extrusion transform to WCS entities (LINE/POINT/3DFACE/3D polyline) → the pyramid scattered. `DXFCoordsAreWCS` skips extrusion for those types; VPORT lookup made case-insensitive; new `Draw3DTextEntity` puts TEXT in the 3D plane via transformed glyph outlines. | +| [99892](https://bz.apache.org/ooo/show_bug.cgi?id=99892) | Cyrillic / accented text garbled | text / encoding | **Partial — the filter half only.** `$DWGCODEPAGE` now resolves through `DXFCodePageToTextEncoding`; the default is the system encoding with a UTF-8→MS_1252 guard; and a `"CharacterSet"` `FilterConfigItem` override seam lets a higher layer supply the encoding. Nothing populates that option yet — see *Open* below. | +| _(no BZ)_ | Oversized LWPOLYLINE truncated | reader robustness | A vertex count exceeding the reader's limit truncated the polyline; it is now chunked instead. | + +## Open / not fixed + +Carried on the local branch `bugfix-dxf-filter-open` with the full sample corpus +and per-case analyses: + +- **99892 Phase B** — the UI half of the encoding fix. The filter seam exists; + what is missing is detect-and-prompt (an `SvxTextEncodingBox` chooser) to fill + the `"CharacterSet"` option. That lives above idxf, in the import/dialog layer. +- **Thumbnail of a text-dense DXF scrambles** — full-size import is correct; only + the small slide/graphic-selector preview overlaps, because at thumbnail scale + geometry shrinks while text hits VCL's minimum font size. Fix, if any, belongs + to the app/VCL thumbnail renderer, not this filter. +- **Confirmed outside idxf** (no work planned here): 75928 and 75922 (metafile + break/paste behaviour in Draw), 60138 (graphic-link persistence), 97993 + (metafile→PDF export). +- Of 22 collected Bugzilla reports, 14 were analysed; the remainder are + unexported on that branch. diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 7f2706117d..ac27b0a3cb 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -27,6 +27,7 @@ #include <string.h> #include <vcl/gdimtf.hxx> #include <vcl/virdev.hxx> +#include <vcl/hatch.hxx> #include <tools/poly.hxx> #include "dxf2mtf.hxx" @@ -132,15 +133,23 @@ DXFLineInfo DXF2GDIMetaFile::LTypeToDXFLineInfo(const char * sLineType) } } -#if 0 - if (aDXFLineInfo.DashCount > 0 && aDXFLineInfo.DashLen == 0.0) - aDXFLineInfo.DashLen ( 1 ); - if (aDXFLineInfo.DotCount > 0 && aDXFLineInfo.DotLen() == 0.0) - aDXFLineInfo.SetDotLen( 1 ); - if (aDXFLineInfo.GetDashCount > 0 || aDXFLineInfo.GetDotCount > 0) - if (aDXFLineInfo.GetDistance() == 0) - aDXFLineInfo.SetDistance( 1 ); -#endif + // A DXF pattern element of exactly 0.0 is a *dot* (a point), distinct from a + // positive-length dash (e.g. DOT2 = 0,-3.175). AutoCAD paints it as a small + // dot, but VCL has no round dot: LineInfo strokes every "on" run as a short + // segment, so a dot is just a very short dash. Rendered verbatim the length + // collapses to a sub-unit mark (Transform() only clamps it to 1/100mm) and the + // line came out invisible (issue 70275). Give a zero-length dot/dash a small + // model-unit length so it survives scaling; keep it a SMALL fraction of the + // gap (0.05) so it reads as a dot, not a dash. Done here in model units, not + // after scaling, so it tracks the drawing size. Only true 0.0 elements are + // adjusted — real authored dashes keep their length. + const double fDotOnFraction = 0.05; // dot mark length as a fraction of the gap + if (aDXFLineInfo.fDistance != 0.0) { + if (aDXFLineInfo.nDotCount > 0 && aDXFLineInfo.fDotLen == 0.0) + aDXFLineInfo.fDotLen = aDXFLineInfo.fDistance * fDotOnFraction; + if (aDXFLineInfo.nDashCount > 0 && aDXFLineInfo.fDashLen == 0.0) + aDXFLineInfo.fDashLen = aDXFLineInfo.fDistance * fDotOnFraction; + } return aDXFLineInfo; } @@ -475,6 +484,104 @@ void DXF2GDIMetaFile::DrawTextEntity(const DXFTextEntity & rE, const DXFTransfor } +// Draw a TEXT entity projected into the (3D) view, used when the drawing is +// rendered through a non-top viewport. VCL's DrawText can only place text with a +// position + uniform height + single rotation, so it cannot follow the shear / +// foreshortening a 3D projection produces. Instead we take the glyph OUTLINES and +// push them through the full transform like any other geometry, so the text lies +// in its projected plane. The flat 2D path (DrawTextEntity) is left untouched. +void DXF2GDIMetaFile::Draw3DTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform) +{ + ByteString aStr( rE.sText ); + String aUString( aStr, pDXF->getTextEncoding() ); + if ( aUString.Len()==0 ) return; + + long nColor = GetEntityColor(rE); + if ( nColor<0 ) return; + Color aColor = ConvertColor((sal_uInt8)nColor); + + // Extract the glyph outlines from a SCRATCH device, never from pVirDev: the + // recording device has output disabled, so its GetTextOutlines falls back to a + // bitmap path that fails ("could not create system bitmap"). A clean scalable + // font on a plain VirtualDevice takes the vector path. The nominal height is + // arbitrary — the extent is self-calibrated below. + const long nNominal = 1000; + Font aFont; + aFont.SetFamily(FAMILY_SWISS); + aFont.SetSize(Size(0,nNominal)); + aFont.SetAlign(ALIGN_BASELINE); + VirtualDevice aTextDev; + aTextDev.SetFont(aFont); + PolyPolyVector aGlyphs; + sal_Bool bOK = aTextDev.GetTextOutlines( aGlyphs, aUString ); + if ( !bOK || aGlyphs.empty() ) { + DrawTextEntity(rE,rTransform); // no outlines available -> flat fallback + return; + } + + // GetTextOutlines returns the glyphs in an opaque internal scale (it toggles + // the map mode and rescales by a pixel factor), so we can't assume the height + // is nNominal. Self-calibrate: measure the real outline extent and normalise by + // its height, so one text-height maps to 1.0 regardless of the device scale. + double fMinY=0.0, fMaxY=0.0; + sal_Bool bFirst = sal_True; + sal_uInt32 g; + for ( g=0; g<aGlyphs.size(); g++ ) { + const PolyPolygon & rGlyph = aGlyphs[ g ]; + sal_uInt16 c; + for ( c=0; c<rGlyph.Count(); c++ ) { + const Polygon & rContour = rGlyph[ c ]; + sal_uInt16 i, nPts = rContour.GetSize(); + for ( i=0; i<nPts; i++ ) { + const double y = (double)rContour[ i ].Y(); + if ( bFirst ) { fMinY=fMaxY=y; bFirst=sal_False; } + else { if (y<fMinY) fMinY=y; if (y>fMaxY) fMaxY=y; } + } + } + } + const double fHeight = fMaxY - fMinY; + if ( fHeight <= 0.0 ) { // degenerate outlines -> flat fallback + DrawTextEntity(rE,rTransform); + return; + } + const double fInv = 1.0/fHeight; + + // Text-local coords (baseline at Y=0, one text-height == 1.0, Y up) -> DXF + // text placement (width/height/rotation/insertion) -> the incoming extrusion+ + // view transform. This is the same chain the renderer uses for geometry. + // The outlines are normalised to text-height units in BOTH axes, so X and Y + // must both scale by fHeight; fXScale (DXF group 41 width factor) is only an + // extra horizontal stretch on top (default 1.0). Scaling X by fXScale alone — + // as DrawTextEntity can, because it re-derives width from the font — would + // collapse the glyphs to a vertical sliver here. + const double fWScale = rE.fHeight * ( rE.fXScale>0.0 ? rE.fXScale : 1.0 ); + DXFTransform aT( DXFTransform(fWScale,rE.fHeight,1.0,rE.fRotAngle,rE.aP0), rTransform ); + + if ( aActFillColor!=aColor ) pVirDev->SetFillColor( aActFillColor = aColor ); + if ( aActLineColor!=aColor ) pVirDev->SetLineColor( aActLineColor = aColor ); + + for ( g=0; g<aGlyphs.size(); g++ ) { + const PolyPolygon & rGlyph = aGlyphs[ g ]; + PolyPolygon aDevGlyph; + sal_uInt16 c; + for ( c=0; c<rGlyph.Count(); c++ ) { + const Polygon & rContour = rGlyph[ c ]; + sal_uInt16 nPts = rContour.GetSize(); + Polygon aDev( nPts ); + sal_uInt16 i; + for ( i=0; i<nPts; i++ ) { + const Point & rP = rContour[ i ]; + aT.Transform( DXFVector( (double)rP.X()*fInv, + -(double)rP.Y()*fInv, 0.0 ), + aDev[ i ] ); + } + aDevGlyph.Insert( aDev ); + } + pVirDev->DrawPolyPolygon( aDevGlyph ); + } +} + + void DXF2GDIMetaFile::DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform) { const DXFBlock * pB; @@ -586,126 +693,331 @@ void DXF2GDIMetaFile::DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXF void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform ) { - sal_Int32 i, nPolySize = rE.nCount; - if ( nPolySize && rE.pP ) + const sal_Int32 nVtxCount = rE.nCount; + if ( nVtxCount <= 0 || rE.pP == NULL ) + return; + + double fW = rE.fConstantWidth; + if ( !SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) + return; + + const sal_Bool bClosed = ( rE.nFlags & 1 ) != 0; + + // Build the outline in device space, expanding any segment that carries a + // non-zero bulge into an arc. In DXF a bulge is tan(sweep/4), so bulge 1.0 + // is a 180 degree arc; a circle is commonly stored as two bulged segments + // (e.g. Eagle CAD). Straight segments (bulge 0) contribute just the vertex. + DXFPointArray aPtAry; + Point aPt; + for ( sal_Int32 i = 0; i < nVtxCount; i++ ) { - Polygon aPoly( (sal_uInt16)nPolySize); - for ( i = 0; i < nPolySize; i++ ) + rTransform.Transform( rE.pP[ i ], aPt ); + aPtAry.push_back( aPt ); + + sal_Int32 iNext = i + 1; + if ( iNext >= nVtxCount ) { - rTransform.Transform( rE.pP[ (sal_uInt16)i ], aPoly[ (sal_uInt16)i ] ); + if ( !bClosed ) break; // open: no segment after the last vertex + iNext = 0; // closed: last vertex arcs back to the first } - double fW = rE.fConstantWidth; - if ( SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) + + const double fBulge = ( rE.pBulge != NULL ) ? rE.pBulge[ i ] : 0.0; + if ( fBulge != 0.0 ) { - if ( ( rE.nFlags & 1 ) != 0 ) - pVirDev->DrawPolygon( aPoly ); - else - pVirDev->DrawPolyLine( aPoly ); - // #### - //pVirDev->DrawPolyLine( aPoly, aDXFLineInfo ); + const DXFVector & rS = rE.pP[ i ]; + const DXFVector & rEnd = rE.pP[ iNext ]; + // Arc centre from the bulge (cotangent form): the centre lies on the + // chord's perpendicular bisector, offset by cot(sweep/2)/2 * chord. + const double fCot = 0.5 * ( 1.0 / fBulge - fBulge ); + const double fCx = 0.5 * ( ( rS.fx + rEnd.fx ) + fCot * ( rS.fy - rEnd.fy ) ); + const double fCy = 0.5 * ( ( rS.fy + rEnd.fy ) + fCot * ( rEnd.fx - rS.fx ) ); + const double fRx = rS.fx - fCx; + const double fRy = rS.fy - fCy; + const double fRadius = sqrt( fRx * fRx + fRy * fRy ); + const double fStartAng = atan2( fRy, fRx ); + const double fSweep = 4.0 * atan( fBulge ); // signed: >0 = CCW + sal_Int32 nArcPoints = (sal_Int32)( fabs( fSweep ) / ( 2 * 3.14159265359 ) + * (double)OptPointsPerCircle + 0.5 ); + // Emit the interior arc samples; the start vertex is already pushed + // and the end vertex is pushed as the next loop's leading vertex. + for ( sal_Int32 k = 1; k < nArcPoints; k++ ) + { + const double fAng = fStartAng + fSweep * (double)k / (double)nArcPoints; + rTransform.Transform( + DXFVector( fCx + fRadius * cos( fAng ), + fCy + fRadius * sin( fAng ), + rS.fz ), + aPt ); + aPtAry.push_back( aPt ); + } } } + + const sal_Int32 nPolySize = (sal_Int32)aPtAry.size(); + if ( nPolySize < 2 ) + return; + + if ( nPolySize <= 0xFFFF ) + { + // Fits in a single tools Polygon (which is indexed by sal_uInt16). + Polygon aPoly( (sal_uInt16)nPolySize ); + for ( sal_Int32 i = 0; i < nPolySize; i++ ) + aPoly[ (sal_uInt16)i ] = aPtAry[ i ]; + if ( bClosed ) + pVirDev->DrawPolygon( aPoly ); + else + pVirDev->DrawPolyLine( aPoly ); + return; + } + + // The tessellated outline can exceed the 0xFFFF points a tools Polygon holds. + // Render it as a sequence of Polygon segments that overlap by one point, so + // the whole line is drawn without truncation and the stroke stays continuous. + const sal_Int32 nMaxChunk = 0xFFFF; + sal_Int32 nStart = 0; + while ( nStart + 1 < nPolySize ) + { + sal_Int32 nEnd = nStart + nMaxChunk; + if ( nEnd > nPolySize ) + nEnd = nPolySize; + const sal_uInt16 nChunk = (sal_uInt16)( nEnd - nStart ); + Polygon aChunk( nChunk ); + for ( sal_uInt16 j = 0; j < nChunk; j++ ) + aChunk[ j ] = aPtAry[ nStart + j ]; + pVirDev->DrawPolyLine( aChunk ); + nStart = nEnd - 1; // next segment shares this segment's last point + } + if ( bClosed ) + { + // Close the outline: last point back to the first. + Polygon aClose( 2 ); + aClose[ 0 ] = aPtAry[ nPolySize - 1 ]; + aClose[ 1 ] = aPtAry[ 0 ]; + pVirDev->DrawPolyLine( aClose ); + } } void DXF2GDIMetaFile::DrawHatchEntity(const DXFHatchEntity & rE, const DXFTransform & rTransform ) { - if ( rE.nBoundaryPathCount ) + if ( !rE.nBoundaryPathCount ) + return; + + // Build the boundary as a PolyPolygon, tessellating curved edges so the + // area is measured correctly for both the solid fill and the pattern clip. + sal_Int32 j; + PolyPolygon aPolyPoly; + for ( j = 0; j < rE.nBoundaryPathCount; j++ ) { - SetAreaAttribute( rE ); - sal_Int32 j = 0; - PolyPolygon aPolyPoly; - for ( j = 0; j < rE.nBoundaryPathCount; j++ ) + DXFPointArray aPtAry; + const DXFBoundaryPathData& rPathData = rE.pBoundaryPathData[ j ]; + if ( rPathData.bIsPolyLine ) { - DXFPointArray aPtAry; - const DXFBoundaryPathData& rPathData = rE.pBoundaryPathData[ j ]; - if ( rPathData.bIsPolyLine ) + sal_Int32 i; + for( i = 0; i < rPathData.nPointCount; i++ ) { - sal_Int32 i; - for( i = 0; i < rPathData.nPointCount; i++ ) - { - Point aPt; - rTransform.Transform( rPathData.pP[ i ], aPt ); - aPtAry.push_back( aPt ); - } + Point aPt; + rTransform.Transform( rPathData.pP[ i ], aPt ); + aPtAry.push_back( aPt ); } - else + } + else + { + sal_uInt32 i; + for ( i = 0; i < rPathData.aEdges.size(); i++ ) { - sal_uInt32 i; - for ( i = 0; i < rPathData.aEdges.size(); i++ ) + const DXFEdgeType* pEdge = rPathData.aEdges[ i ]; + switch( pEdge->nEdgeType ) { - const DXFEdgeType* pEdge = rPathData.aEdges[ i ]; - switch( pEdge->nEdgeType ) + case 1 : { - case 1 : + Point aPt; + rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aStartPoint, aPt ); + aPtAry.push_back( aPt ); + rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aEndPoint, aPt ); + aPtAry.push_back( aPt ); + } + break; + case 2 : + { + // Circular arc edge: sample the sweep and push the points. + // Angles are in degrees; direction follows the CCW flag. + const DXFEdgeTypeCircularArc* pArc = (const DXFEdgeTypeCircularArc*)pEdge; + double fSweep = pArc->fEndAngle - pArc->fStartAngle; + if ( pArc->nIsCounterClockwiseFlag ) + { while ( fSweep <= 0.0 ) fSweep += 360.0; } + else + { while ( fSweep >= 0.0 ) fSweep -= 360.0; } + sal_uInt16 nPts = (sal_uInt16)( fabs(fSweep)/360.0*(double)OptPointsPerCircle + 0.5 ); + if ( nPts < 2 ) nPts = 2; + for ( sal_uInt16 k = 0; k < nPts; k++ ) { + double a = ( pArc->fStartAngle + fSweep*(double)k/(double)(nPts-1) ) + * (3.14159265359/180.0); Point aPt; - rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aStartPoint, aPt ); - aPtAry.push_back( aPt ); - rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aEndPoint, aPt ); + rTransform.Transform( + DXFVector( pArc->aCenter.fx + pArc->fRadius*cos(a), + pArc->aCenter.fy + pArc->fRadius*sin(a), 0.0 ), aPt ); aPtAry.push_back( aPt ); } - break; - case 2 : - { -/* - double frx,fry,fA1,fdA,fAng; - sal_uInt16 nPoints,i; - DXFVector aC; - Point aPS,aPE; - fA1=((DXFEdgeTypeCircularArc*)pEdge)->fStartAngle; - fdA=((DXFEdgeTypeCircularArc*)pEdge)->fEndAngle - fA1; - while ( fdA >= 360.0 ) - fdA -= 360.0; - while ( fdA <= 0 ) - fdA += 360.0; - rTransform.Transform(((DXFEdgeTypeCircularArc*)pEdge)->aCenter, aC); - if ( fdA > 5.0 && rTransform.TransCircleToEllipse(((DXFEdgeTypeCircularArc*)pEdge)->fRadius,frx,fry ) == sal_True ) - { - DXFVector aVS(cos(fA1/180.0*3.14159265359),sin(fA1/180.0*3.14159265359),0.0); - aVS*=((DXFEdgeTypeCircularArc*)pEdge)->fRadius; - aVS+=((DXFEdgeTypeCircularArc*)pEdge)->aCenter; - DXFVector aVE(cos((fA1+fdA)/180.0*3.14159265359),sin((fA1+fdA)/180.0*3.14159265359),0.0); - aVE*=((DXFEdgeTypeCircularArc*)pEdge)->fRadius; - aVE+=((DXFEdgeTypeCircularArc*)pEdge)->aCenter; - if ( rTransform.Mirror() == sal_True ) - { - rTransform.Transform(aVS,aPS); - rTransform.Transform(aVE,aPE); - } - else - { - rTransform.Transform(aVS,aPE); - rTransform.Transform(aVE,aPS); - } - pVirDev->DrawArc( - Rectangle((long)(aC.fx-frx+0.5),(long)(aC.fy-fry+0.5), - (long)(aC.fx+frx+0.5),(long)(aC.fy+fry+0.5)), - aPS,aPE - ); - } -*/ - } - break; - case 3 : - case 4 : - break; } + break; + case 3 : // elliptical arc: not tessellated (not seen in + case 4 : // practice here); spline edges store no control + break; // points, so both fall through as before. } } - sal_uInt16 i, nSize = (sal_uInt16)aPtAry.size(); - if ( nSize ) - { - Polygon aPoly( nSize ); - for ( i = 0; i < nSize; i++ ) - aPoly[ i ] = aPtAry[ i ]; - aPolyPoly.Insert( aPoly, POLYPOLY_APPEND ); - } } - if ( aPolyPoly.Count() ) + sal_uInt16 i, nSize = (sal_uInt16)aPtAry.size(); + if ( nSize ) + { + Polygon aPoly( nSize ); + for ( i = 0; i < nSize; i++ ) + aPoly[ i ] = aPtAry[ i ]; + aPolyPoly.Insert( aPoly, POLYPOLY_APPEND ); + } + } + if ( !aPolyPoly.Count() ) + return; + + if ( rE.nFlags & 1 ) + { + // Solid fill (group 70 bit 0): flood-fill the boundary in the entity colour. + SetAreaAttribute( rE ); + pVirDev->DrawPolyPolygon( aPolyPoly ); + } + else if ( rE.bHasPatternLine ) + { + // Pattern fill: render the section lines with VCL's DrawHatch, which + // clips a line family to the boundary and records a MetaHatchAction. + // The DXF pattern is collapsed to a single/double/triple Hatch; the + // on-page angle and spacing come from mapping the pattern-definition + // line's direction and inter-line offset through rTransform, so any + // scale / rotation / block-insert nesting is accounted for. + long nColor = GetEntityColor( rE ); + if ( nColor < 0 ) + return; + Color aColor = ConvertColor( (sal_uInt8)nColor ); + + const double fPi = 3.14159265359; + double fA = rE.fPatternLineAngle * (fPi/180.0); + DXFVector aDir, aOff; + rTransform.TransDir( DXFVector( cos(fA), sin(fA), 0.0 ), aDir ); + rTransform.TransDir( DXFVector( rE.fPatternLineOffsetX, rE.fPatternLineOffsetY, 0.0 ), aOff ); + + double fDirLen = sqrt( aDir.fx*aDir.fx + aDir.fy*aDir.fy ); + if ( fDirLen < 1e-9 ) + { + // degenerate direction: fall back to a bare outline (no blob). + SetLineAttribute( rE ); pVirDev->DrawPolyPolygon( aPolyPoly ); + return; + } + + // Perpendicular spacing between adjacent lines in device space, and the + // device line angle. VCL's angle is measured in the recorded coordinate + // system with its positive direction as (cos,-sin), hence -aDir.fy. + double fDist = fabs( aOff.fx*aDir.fy - aOff.fy*aDir.fx ) / fDirLen; + long nDistance = (long)( fDist + 0.5 ); + if ( nDistance < 1 ) nDistance = 1; + + double fAngle = atan2( -aDir.fy, aDir.fx ) * 1800.0 / fPi; + long nAngle10 = (long)( fAngle + (fAngle < 0.0 ? -0.5 : 0.5) ); + nAngle10 %= 1800; + if ( nAngle10 < 0 ) + nAngle10 += 1800; + + HatchStyle eStyle = HATCH_SINGLE; + if ( rE.nHatchPatternDefinitionLines == 2 ) + eStyle = HATCH_DOUBLE; + else if ( rE.nHatchPatternDefinitionLines >= 3 ) + eStyle = HATCH_TRIPLE; + + Hatch aHatch( eStyle, aColor, nDistance, (sal_uInt16)nAngle10 ); + pVirDev->DrawHatch( aPolyPoly, aHatch ); + } + else + { + // Pattern fill but no pattern-definition line was parsed: stroke the + // boundary outline only (transparent fill), never a solid blob. + SetLineAttribute( rE ); + pVirDev->DrawPolyPolygon( aPolyPoly ); + } +} + +void DXF2GDIMetaFile::DrawEllipseEntity(const DXFEllipseEntity & rE, const DXFTransform & rTransform) +{ + if (SetLineAttribute(rE)==sal_False) return; + + // Major axis vector (relative to centre) and the minor axis, which for the + // common planar case (extrusion 0,0,1) is ratio*(normal x major) = + // ratio*(-major.y, major.x, 0). A non-default extrusion is applied through + // rTransform by DrawEntities, so compute in the entity's own frame. + const DXFVector & aU = rE.aP1; + DXFVector aV(-aU.fy * rE.fRatio, aU.fx * rE.fRatio, 0.0); + + double fSweep = rE.fEnd - rE.fStart; + while (fSweep <= 0.0) fSweep += 2*3.14159265359; + while (fSweep > 2*3.14159265359) fSweep -= 2*3.14159265359; + + sal_uInt16 nPoints = (sal_uInt16)( fSweep/(2*3.14159265359)*(double)OptPointsPerCircle + 0.5 ); + if (nPoints<2) nPoints=2; + + Polygon aPoly(nPoints); + for (sal_uInt16 i=0; i<nPoints; i++) { + double t = rE.fStart + fSweep*(double)i/(double)(nPoints-1); + rTransform.Transform( rE.aP0 + aU*cos(t) + aV*sin(t), aPoly[i] ); } + pVirDev->DrawPolyLine(aPoly); } + +void DXF2GDIMetaFile::DrawSplineEntity(const DXFSplineEntity & rE, const DXFTransform & rTransform) +{ + // Non-uniform B-spline, evaluated with de Boor's algorithm and drawn as a + // polyline. Rational (weighted) splines are treated as non-rational (the + // weights are ignored) — sufficient for the planar curves this targets. + if ( rE.pControlPts==NULL || rE.pfKnots==NULL ) return; + const long p = rE.nDegree; + const long n = rE.nCtrlCount - 1; + if ( p < 1 || n < p ) return; + if ( rE.nKnotCount != rE.nCtrlCount + p + 1 ) return; // not a clamped B-spline + if ( SetLineAttribute(rE)==sal_False ) return; + + const double * U = rE.pfKnots; + const double u0 = U[p]; + const double u1 = U[n+1]; + if ( u1 <= u0 ) return; + + long nPoints = rE.nCtrlCount * 8; + if (nPoints < 2) nPoints = 2; + if (nPoints > 2000) nPoints = 2000; + + Polygon aPoly((sal_uInt16)nPoints); + DXFVector * d = new DXFVector[p+1]; + for (long i=0; i<nPoints; i++) { + double u = u0 + (u1-u0)*(double)i/(double)(nPoints-1); + if (u >= u1) u = u1 - (u1-u0)*1e-9; // stay inside the last knot span + + // knot span k with U[k] <= u < U[k+1], clamped to [p, n] + long k = p; + while (k < n && u >= U[k+1]) k++; + + long j, r; + for (j=0; j<=p; j++) d[j] = rE.pControlPts[k-p+j]; + for (r=1; r<=p; r++) { + for (j=p; j>=r; j--) { + double fDenom = U[k+1+j-r] - U[k-p+j]; + double a = (fDenom!=0.0) ? (u - U[k-p+j])/fDenom : 0.0; + d[j] = d[j-1]*(1.0-a) + d[j]*a; + } + } + rTransform.Transform(d[p], aPoly[(sal_uInt16)i]); + } + delete[] d; + pVirDev->DrawPolyLine(aPoly); +} + + void DXF2GDIMetaFile::Draw3DFaceEntity(const DXF3DFaceEntity & rE, const DXFTransform & rTransform) { sal_uInt16 nN,i; @@ -773,7 +1085,7 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, while (pE!=NULL && bStatus==sal_True) { if (pE->nSpace==0) { - if (pE->aExtrusion.fz==1.0) { + if (pE->aExtrusion.fz==1.0 || DXFCoordsAreWCS(*pE)) { pT=&rTransform; } else { @@ -800,7 +1112,10 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, DrawSolidEntity((DXFSolidEntity&)*pE,*pT); break; case DXF_TEXT: - DrawTextEntity((DXFTextEntity&)*pE,*pT); + if (b3DText) + Draw3DTextEntity((DXFTextEntity&)*pE,*pT); + else + DrawTextEntity((DXFTextEntity&)*pE,*pT); break; case DXF_INSERT: DrawInsertEntity((DXFInsertEntity&)*pE,*pT); @@ -817,6 +1132,12 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, case DXF_HATCH : DrawHatchEntity((DXFHatchEntity&)*pE, *pT); break; + case DXF_ELLIPSE: + DrawEllipseEntity((DXFEllipseEntity&)*pE,*pT); + break; + case DXF_SPLINE: + DrawSplineEntity((DXFSplineEntity&)*pE,*pT); + break; case DXF_3DFACE: Draw3DFaceEntity((DXF3DFaceEntity&)*pE,*pT); break; @@ -900,6 +1221,9 @@ sal_Bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & if (pVPort->aDirection.fx==0 && pVPort->aDirection.fy==0) pVPort=NULL; } + // A non-top viewport means the drawing is projected in 3D; text then has to be + // laid into that projection (Draw3DTextEntity) rather than drawn flat. + b3DText = (pVPort!=NULL) ? sal_True : sal_False; if (pVPort==NULL) { if (pDXF->aBoundingBox.bEmpty==sal_True) @@ -912,21 +1236,32 @@ sal_Bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & fScale = 0; // -Wall added this... } else { + // Leave a small margin around the drawing. The larger extent is + // scaled to fill the 10000-unit target exactly, so geometry that + // lies right on the extent (e.g. a drawing frame's outer line) + // would otherwise sit precisely on the fit boundary and be + // clipped. This surfaced once issue 58347 made the bounding box + // a tight fit to the geometry; the margin keeps every edge inside + // the fitted area (symmetric, so all four sides get breathing room). + const double fMargin = + (fWidth>fHeight ? fWidth : fHeight) * 0.01; + const double fFitWidth = fWidth + 2.0*fMargin; + const double fFitHeight = fHeight + 2.0*fMargin; // if (fWidth<500.0 || fHeight<500.0 || fWidth>32767.0 || fHeight>32767.0) { - if (fWidth>fHeight) - fScale=10000.0/fWidth; + if (fFitWidth>fFitHeight) + fScale=10000.0/fFitWidth; else - fScale=10000.0/fHeight; + fScale=10000.0/fFitHeight; // } // else // fScale=1.0; aTransform=DXFTransform(fScale,-fScale,fScale, - DXFVector(-pDXF->aBoundingBox.fMinX*fScale, - pDXF->aBoundingBox.fMaxY*fScale, + DXFVector((fMargin-pDXF->aBoundingBox.fMinX)*fScale, + (pDXF->aBoundingBox.fMaxY+fMargin)*fScale, -pDXF->aBoundingBox.fMinZ*fScale)); + aPrefSize.Width() =(long)(fFitWidth*fScale+1.5); + aPrefSize.Height()=(long)(fFitHeight*fScale+1.5); } - aPrefSize.Width() =(long)(fWidth*fScale+1.5); - aPrefSize.Height()=(long)(fHeight*fScale+1.5); } } else { diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx index a2578e6aed..3b864db623 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx @@ -57,6 +57,7 @@ private: Color aActLineColor; Color aActFillColor; Font aActFont; + sal_Bool b3DText; // draw TEXT projected into the 3D view (Draw3DTextEntity) sal_uLong CountEntities(const DXFEntities & rEntities); @@ -91,6 +92,8 @@ private: void DrawTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform); + void Draw3DTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform); + void DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform); void DrawAttribEntity(const DXFAttribEntity & rE, const DXFTransform & rTransform); @@ -105,6 +108,10 @@ private: void DrawHatchEntity( const DXFHatchEntity & rE, const DXFTransform & rTransform ); + void DrawEllipseEntity( const DXFEllipseEntity & rE, const DXFTransform & rTransform ); + + void DrawSplineEntity( const DXFSplineEntity & rE, const DXFTransform & rTransform ); + void DrawEntities(const DXFEntities & rEntities, const DXFTransform & rTransform, sal_Bool bTopEntities); diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx index e7cce8cc12..320b96c0f0 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx @@ -422,7 +422,8 @@ DXFLWPolyLineEntity::DXFLWPolyLineEntity() : fConstantWidth( 0.0 ), fStartWidth( 0.0 ), fEndWidth( 0.0 ), - pP( NULL ) + pP( NULL ), + pBulge( NULL ) { } @@ -438,6 +439,9 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) try { pP = new DXFVector[ nCount ]; + pBulge = new double[ nCount ]; + for ( sal_Int32 i = 0; i < nCount; i++ ) + pBulge[ i ] = 0.0; } catch (::std::bad_alloc) { @@ -464,6 +468,14 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) pP[ nIndex++ ].fy = rDGR.GetF(); } break; + case 42: + { + // per-vertex bulge; follows the 10/20 pair of the vertex it + // belongs to (nIndex has already advanced past that vertex). + if ( pBulge && ( nIndex > 0 ) && ( nIndex <= nCount ) ) + pBulge[ nIndex - 1 ] = rDGR.GetF(); + } + break; default: DXFBasicEntity::EvaluateGroup(rDGR); } } @@ -471,6 +483,7 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) DXFLWPolyLineEntity::~DXFLWPolyLineEntity() { delete[] pP; + delete[] pBulge; } //--------------------------DXFHatchEntity------------------------------------- @@ -684,6 +697,7 @@ DXFHatchEntity::DXFHatchEntity() : DXFBasicEntity( DXF_HATCH ), bIsInBoundaryPathContext( sal_False ), nCurrentBoundaryPathIndex( -1 ), + bPatternLineOffsetSet( sal_False ), nFlags( 0 ), nAssociativityFlag( 0 ), nBoundaryPathCount( 0 ), @@ -695,6 +709,10 @@ DXFHatchEntity::DXFHatchEntity() : nHatchPatternDefinitionLines( 0 ), fPixelSize( 1.0 ), nNumberOfSeedPoints( 0 ), + bHasPatternLine( sal_False ), + fPatternLineAngle( 0.0 ), + fPatternLineOffsetX( 0.0 ), + fPatternLineOffsetY( 0.0 ), pBoundaryPathData( NULL ) { } @@ -741,6 +759,21 @@ void DXFHatchEntity::EvaluateGroup( DXFGroupReader & rDGR ) case 47 : fPixelSize = rDGR.GetF(); break; case 98 : nNumberOfSeedPoints = rDGR.GetI(); break; + // Pattern-definition lines (only present for a pattern fill, after 75). + // Capture the FIRST line's angle (53) and inter-line offset (45/46); + // 53/45/46 appear nowhere else in a HATCH, so intercepting them here is + // safe. Codes 43/44 (base point) are not needed for a VCL Hatch. + case 53 : + if ( !bHasPatternLine ) { fPatternLineAngle = rDGR.GetF(); bHasPatternLine = sal_True; } + break; + case 45 : + if ( bHasPatternLine && !bPatternLineOffsetSet ) fPatternLineOffsetX = rDGR.GetF(); + break; + case 46 : + if ( bHasPatternLine && !bPatternLineOffsetSet ) + { fPatternLineOffsetY = rDGR.GetF(); bPatternLineOffsetSet = sal_True; } + break; + //!! passthrough !! case 92 : nCurrentBoundaryPathIndex++; default: @@ -842,6 +875,98 @@ void DXFDimensionEntity::EvaluateGroup(DXFGroupReader & rDGR) //---------------------------- DXFEntites -------------------------------------- +//--------------------------DXFEllipseEntity------------------------------------ + +DXFEllipseEntity::DXFEllipseEntity() : DXFBasicEntity(DXF_ELLIPSE) +{ + fRatio = 1.0; + fStart = 0.0; + fEnd = 2*3.14159265359; +} + +void DXFEllipseEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + case 40: fRatio=rDGR.GetF(); break; + case 41: fStart=rDGR.GetF(); break; + case 42: fEnd=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFSplineEntity------------------------------------- + +DXFSplineEntity::DXFSplineEntity() : + DXFBasicEntity(DXF_SPLINE), + nFlags(0), + nDegree(0), + nKnotCount(0), + nCtrlCount(0), + nFitCount(0), + pfKnots(NULL), + pControlPts(NULL), + nKnotIndex(0), + nCtrlIndex(0) +{ +} + +DXFSplineEntity::~DXFSplineEntity() +{ + delete[] pfKnots; + delete[] pControlPts; +} + +void DXFSplineEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 70: nFlags = rDGR.GetI(); break; + case 71: nDegree = rDGR.GetI(); break; + case 72: + nKnotCount = rDGR.GetI(); + if ( rDGR.GetStatus() && nKnotCount > 0 ) { + try { pfKnots = new double[nKnotCount]; } + catch (::std::bad_alloc) { rDGR.SetError(); } + } + else if ( nKnotCount < 0 ) + rDGR.SetError(); + break; + case 73: + nCtrlCount = rDGR.GetI(); + if ( rDGR.GetStatus() && nCtrlCount > 0 ) { + try { pControlPts = new DXFVector[nCtrlCount]; } + catch (::std::bad_alloc) { rDGR.SetError(); } + } + else if ( nCtrlCount < 0 ) + rDGR.SetError(); + break; + case 74: nFitCount = rDGR.GetI(); break; + case 40: + if ( pfKnots && nKnotIndex < nKnotCount ) + pfKnots[nKnotIndex++] = rDGR.GetF(); + break; + // control points are 3D (10/20/30); advance on the Z group + case 10: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex].fx = rDGR.GetF(); + break; + case 20: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex].fy = rDGR.GetF(); + break; + case 30: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex++].fz = rDGR.GetF(); + break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + void DXFEntities::Read(DXFGroupReader & rDGR) { DXFBasicEntity * pE, * * ppSucc; @@ -874,6 +999,8 @@ void DXFEntities::Read(DXFGroupReader & rDGR) else if (strcmp(rDGR.GetS(),"3DFACE" )==0) pE=new DXF3DFaceEntity; else if (strcmp(rDGR.GetS(),"DIMENSION" )==0) pE=new DXFDimensionEntity; else if (strcmp(rDGR.GetS(),"HATCH" )==0) pE=new DXFHatchEntity; + else if (strcmp(rDGR.GetS(),"ELLIPSE" )==0) pE=new DXFEllipseEntity; + else if (strcmp(rDGR.GetS(),"SPLINE" )==0) pE=new DXFSplineEntity; else { do { @@ -897,3 +1024,25 @@ void DXFEntities::Clear() delete ptmp; } } + +sal_Bool DXFCoordsAreWCS(const DXFBasicEntity & rE) +{ + // See the header comment. For a LINE the extrusion only defines a thickness + // direction, never the endpoint positions; applying it scatters 3D wireframes + // (issue 99893) and inflates the bounding box (both must agree). ELLIPSE/SPLINE + // are intentionally NOT WCS here — DrawEllipseEntity/DrawSplineEntity compute in + // the entity's own frame and rely on the extrusion being applied. + switch (rE.eType) { + case DXF_LINE: + case DXF_POINT: + case DXF_3DFACE: + return sal_True; + case DXF_POLYLINE: + // 3D polyline / 3D mesh / polyface mesh store WCS vertices; a plain + // 2D polyline is in OCS. + return ( ((const DXFPolyLineEntity &)rE).nFlags & (8|16|64) ) != 0 + ? sal_True : sal_False; + default: + return sal_False; + } +} diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx index 035082d72a..3c08f08a39 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx @@ -53,7 +53,9 @@ enum DXFEntityType { DXF_3DFACE, DXF_DIMENSION, DXF_LWPOLYLINE, - DXF_HATCH + DXF_HATCH, + DXF_ELLIPSE, + DXF_SPLINE }; //------------------------------------------------------------------------------ @@ -369,6 +371,7 @@ class DXFLWPolyLineEntity : public DXFBasicEntity double fEndWidth; // 41 DXFVector* pP; + double* pBulge; // 42 (per vertex; parallel to pP, 0.0 = straight segment) DXFLWPolyLineEntity(); ~DXFLWPolyLineEntity(); @@ -465,6 +468,7 @@ class DXFHatchEntity : public DXFBasicEntity { sal_Bool bIsInBoundaryPathContext; sal_Int32 nCurrentBoundaryPathIndex; + sal_Bool bPatternLineOffsetSet; // transient parse state (first 45/46 captured) public : @@ -481,6 +485,15 @@ class DXFHatchEntity : public DXFBasicEntity double fPixelSize; // 47 sal_Int32 nNumberOfSeedPoints; // 98 + // First pattern-definition line (78 block). Enough to drive a VCL Hatch + // (single/double/triple): the line angle (53) and the inter-line offset + // (45/46) give on-page angle + spacing; multi-line patterns collapse to + // this line's geometry plus the style from nHatchPatternDefinitionLines. + sal_Bool bHasPatternLine; // a 53 line angle was parsed + double fPatternLineAngle; // 53 + double fPatternLineOffsetX; // 45 + double fPatternLineOffsetY; // 46 + DXFBoundaryPathData* pBoundaryPathData; DXFHatchEntity(); @@ -492,6 +505,53 @@ class DXFHatchEntity : public DXFBasicEntity }; +//--------------------------Ellipse--------------------------------------------- + +class DXFEllipseEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 center + DXFVector aP1; // 11,21,31 endpoint of the major axis, relative to center + double fRatio; // 40 ratio of minor axis to major axis + double fStart; // 41 start parameter (radians; 0 for a full ellipse) + double fEnd; // 42 end parameter (radians; 2*pi for a full ellipse) + + DXFEllipseEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR); +}; + +//--------------------------Spline---------------------------------------------- + +class DXFSplineEntity : public DXFBasicEntity { + +public: + + long nFlags; // 70 bit 1=closed, 2=periodic, 4=rational, 8=planar + long nDegree; // 71 + long nKnotCount; // 72 + long nCtrlCount; // 73 + long nFitCount; // 74 + + double * pfKnots; // 40 (nKnotCount entries) + DXFVector * pControlPts; // 10,20,30 (nCtrlCount entries) + + DXFSplineEntity(); + ~DXFSplineEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR); + +private: + + long nKnotIndex; + long nCtrlIndex; +}; + //--------------------------Vertex---------------------------------------------- class DXFVertexEntity : public DXFBasicEntity { @@ -577,6 +637,14 @@ public: // Loescht alle Entities }; +// True when an entity's stored coordinates are already in WCS, so the OCS +// "arbitrary axis" (extrusion) transform must NOT be applied to them. Used by +// BOTH the renderer (DrawEntities) and the bounding-box pass (CalcBoundingBox) so +// they stay consistent: flat/planar entities (CIRCLE, ARC, TEXT, 2D POLYLINE, +// LWPOLYLINE, INSERT, ...) are OCS and DO need the extrusion; inherently-3D +// entities (LINE, POINT, 3DFACE, and a 3D polyline / mesh) carry WCS coordinates. +sal_Bool DXFCoordsAreWCS(const DXFBasicEntity & rE); + //------------------------------------------------------------------------------ //--------------------------------- inlines ------------------------------------ //------------------------------------------------------------------------------ diff --git a/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx b/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx index 57d362a33c..895469ece9 100644 --- a/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx @@ -151,7 +151,7 @@ sal_uInt16 DXFGroupReader::Read() else if (nG< 140) ReadS( aTmp ); else if (nG< 148) F140_147[nG-140]=ReadF(); else if (nG< 170) ReadS( aTmp ); - else if (nG< 176) I170_175[nG-175]=ReadI(); + else if (nG< 176) I170_175[nG-170]=ReadI(); else if (nG< 180) ReadI(); else if (nG< 210) ReadS( aTmp ); else if (nG< 240) F210_239[nG-210]=ReadF(); @@ -330,6 +330,18 @@ long DXFGroupReader::ReadI() } while (*p==0x20) p++; + + // Tolerate an integer group value written in floating-point notation + // (e.g. "1.0"): some CAD applications (Pro/ENGINEER) emit integer group + // codes — such as the DIMSTYLE flags 71/72 — that way. Skip a trailing + // decimal fraction and keep the integer part; AutoCAD accepts this, whereas + // a strict reject aborts the whole import (issue 122565). + if (*p=='.') { + p++; + while (*p>='0' && *p<='9') p++; + while (*p==0x20) p++; + } + if (*p!=0) { bStatus=sal_False; return 0; diff --git a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index 205537a9a1..6cc60e1aa6 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -25,6 +25,10 @@ #include "precompiled_filter.hxx" #include <string.h> +#include <math.h> +#include <tools/string.hxx> // gsl_getSystemTextEncoding +#include <rtl/tencinfo.h> // rtl_getTextEncodingFromWindowsCodePage +#include <rtl/string.h> // rtl_str_compareIgnoreAsciiCase #include <dxfreprd.hxx> @@ -135,7 +139,32 @@ void DXFPalette::SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sa DXFRepresentation::DXFRepresentation() { - setTextEncoding(RTL_TEXTENCODING_IBM_437); + // Default text encoding for a DXF that neither declares a $DWGCODEPAGE nor + // receives an override from the caller (see GraphicImport / FilterConfigItem). + // The historic hard-coded IBM_437 (DOS OEM) is almost never right for a modern + // DXF and garbles any high-bit text (issue 99892). Prefer the system encoding, + // which usually matches the locale the file came from (e.g. a Russian machine + // -> MS_1251). + // + // BUT the system encoding is not necessarily a legacy 8-bit code page any more: + // on Windows with the "Use Unicode UTF-8 for worldwide language support" option + // GetACP() returns 65001, and on modern Linux the locale is UTF-8 as well, so + // the system encoding is RTL_TEXTENCODING_UTF8. Legacy DXF text predates UTF-8 + // and is never encoded that way (test13.dxf: windows-1252 Ä/ü/ß on a UTF-8 host + // came out as artefacts), so decoding those bytes as UTF-8 fails. When the + // system encoding is Unicode/unknown, fall back to windows-1252 — the most + // common encoding for untagged DXF and AOO's usual Western default (cf. the + // msfilter legacy importers); other scripts come from an explicit $DWGCODEPAGE + // or the Phase B chooser. + // Precedence: file $DWGCODEPAGE > caller override > this default. + rtl_TextEncoding eDefault = gsl_getSystemTextEncoding(); + if ( eDefault == RTL_TEXTENCODING_UTF8 || + eDefault == RTL_TEXTENCODING_UTF7 || + eDefault == RTL_TEXTENCODING_UCS2 || + eDefault == RTL_TEXTENCODING_UCS4 || + eDefault == RTL_TEXTENCODING_DONTKNOW ) + eDefault = RTL_TEXTENCODING_MS_1252; + setTextEncoding(eDefault); setGlobalLineTypeScale(1.0); } @@ -176,13 +205,67 @@ sal_Bool DXFRepresentation::Read( SvStream & rIStream, sal_uInt16 nMinPercent, s delete pDGR; - if (bRes==sal_True && aBoundingBox.bEmpty==sal_True) - CalcBoundingBox(aEntities,aBoundingBox); + if (bRes==sal_True) + { + // The header $EXTMIN/$EXTMAX that ReadHeader collected into aBoundingBox + // are written by AutoCAD from the current view/regen state, not from a + // tight geometry fit, so they drift with the zoom level in effect when + // the file was saved (issue 58347: same drawing, different zoom, wrong + // import scale). Measure the real geometry extent and use that for the + // page-fit scaling instead. CalcBoundingBox now measures every entity + // type DrawEntities actually renders (HATCH included), so a non-empty + // geometry box is authoritative. The header extents are kept only as a + // last-resort fallback for the degenerate case where nothing measurable + // was drawn at all (empty geometry box). + DXFBoundingBox aGeometryBox; + CalcBoundingBox(aEntities,aGeometryBox); + if (aGeometryBox.bEmpty==sal_False) + aBoundingBox=aGeometryBox; + } return bRes; } +// Map a DXF $DWGCODEPAGE value to an rtl text encoding. +// The values AutoCAD writes are "ANSI_<cp>" (e.g. ANSI_1252, ANSI_1251), +// "DOS<cp>" (e.g. DOS932, DOS850) or a few named ones ("MACINTOSH", "UTF8"). +// For the ANSI_/DOS_ forms the trailing number IS the Windows code page, so we +// extract it and let rtl resolve it (covers the whole family in one line instead +// of the old single ANSI_932 special case). Returns RTL_TEXTENCODING_DONTKNOW +// when the value is empty/unrecognised so the caller can keep its current +// (default or caller-supplied) encoding. +static rtl_TextEncoding DXFCodePageToTextEncoding(const char * pCodePage) +{ + if (pCodePage==NULL || *pCodePage==0) + return RTL_TEXTENCODING_DONTKNOW; + + // find the first digit (start of the code-page number, if any) + const char * p = pCodePage; + while (*p!=0 && (*p<'0' || *p>'9')) + p++; + if (*p!=0) { + sal_uInt32 nCodePage = 0; + while (*p>='0' && *p<='9') { + nCodePage = nCodePage*10 + (sal_uInt32)(*p-'0'); + p++; + } + rtl_TextEncoding eEnc = rtl_getTextEncodingFromWindowsCodePage(nCodePage); + if (eEnc!=RTL_TEXTENCODING_DONTKNOW) + return eEnc; + } + + // named values without a code-page number + if (rtl_str_compareIgnoreAsciiCase(pCodePage,"MACINTOSH")==0) + return RTL_TEXTENCODING_APPLE_ROMAN; + if (rtl_str_compareIgnoreAsciiCase(pCodePage,"UTF8")==0 || + rtl_str_compareIgnoreAsciiCase(pCodePage,"UTF-8")==0) + return RTL_TEXTENCODING_UTF8; + + return RTL_TEXTENCODING_DONTKNOW; +} + + void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) { @@ -208,15 +291,13 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) { rDGR.Read(); - // FIXME: we really need a whole table of - // $DWGCODEPAGE to encodings mappings - if ( (strcmp(rDGR.GetS(),"ANSI_932")==0) || - (strcmp(rDGR.GetS(),"ansi_932")==0) || - (strcmp(rDGR.GetS(),"DOS932")==0) || - (strcmp(rDGR.GetS(),"dos932")==0) ) - { - setTextEncoding(RTL_TEXTENCODING_MS_932); - } + // The file declares its encoding: this is + // authoritative and overrides both the + // default and any caller-supplied override. + rtl_TextEncoding eEnc = + DXFCodePageToTextEncoding(rDGR.GetS()); + if (eEnc!=RTL_TEXTENCODING_DONTKNOW) + setTextEncoding(eEnc); } else if (strcmp(rDGR.GetS(),"$LTSCALE")==0) { @@ -231,73 +312,150 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) } +// Map a single OCS point of an entity to WCS via its extrusion, then add it to +// the bounding box. The renderer (DXF2GDIMetaFile::DrawEntities) applies the +// same ECS->WCS transform per entity, so the extent has to be measured in WCS +// as well — otherwise drawings that use a non-default (e.g. negative-Z) +// extrusion come out mis-scaled and offset (issue 16564: PL1CAFE.dxf reaches +// OCS X=-10.88 on mirrored polylines that actually render near WCS X=+10.88). +static inline void UnionOCS(DXFBoundingBox & rBox, const DXFTransform & rE2W, + const DXFVector & rP) +{ + DXFVector aW; + rE2W.Transform(rP, aW); + rBox.Union(aW); +} + + +// Approximate the world-space extent of a single line of text and add it to +// the bounding box, so labels sitting near the drawing edge are not cropped. +// AOO renders TEXT/ATTRIB left-aligned on the baseline at the insertion point, +// growing up and to the right (the justification codes are ignored by the +// renderer, see DXF2GDIMetaFile::DrawTextEntity), so bound that rectangle, +// rotated by the text angle. The per-character advance and descent are +// deliberately generous fractions of the text height so the label never clips. +// rE2W maps the (OCS) text rectangle to WCS so text on an extruded entity is +// measured where it is drawn. +static void UnionTextExtent(DXFBoundingBox & rBox, const DXFTransform & rE2W, + const DXFVector & rInsert, + double fHeight, double fXScale, double fRotAngle, + const char * pText) +{ + if (fHeight <= 0.0) { + UnionOCS(rBox, rE2W, rInsert); + return; + } + if (fXScale <= 0.0) + fXScale = 1.0; + + const size_t nLen = (pText != NULL) ? strlen(pText) : 0; + const double fWidth = (double)nLen * 0.85 * fHeight * fXScale; + const double fAscent = fHeight; + const double fDescent = 0.25 * fHeight; + + const double fRad = fRotAngle * 3.14159265359 / 180.0; + const DXFVector aDir( cos(fRad), sin(fRad), 0.0); // text direction + const DXFVector aUp (-sin(fRad), cos(fRad), 0.0); // baseline normal (up) + + for (int a = 0; a < 2; a++) { + const double fAlong = a ? fWidth : 0.0; + for (int b = 0; b < 2; b++) { + const double fPerp = b ? fAscent : -fDescent; + UnionOCS(rBox, rE2W, rInsert + aDir * fAlong + aUp * fPerp); + } + } +} + + void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, DXFBoundingBox & rBox) { DXFBasicEntity * pBE=rEntities.pFirst; while (pBE!=NULL) { + // The renderer builds an ECS->WCS transform from the entity's extrusion + // whenever it is not the default (DrawEntities checks fz != 1.0); measure + // through the SAME transform so the box is in WCS, not raw OCS. Use the same + // WCS-vs-OCS rule as DrawEntities (DXFCoordsAreWCS): entities whose coords + // are already WCS (LINE/POINT/3DFACE/3D-polyline) must NOT be extruded, or + // the box is measured over scattered positions and comes out far too large + // (issue 99893/70273 — geometry then sits in a corner of an inflated page). + DXFTransform aE2W; // identity by default + if (pBE->aExtrusion.fz != 1.0 && !DXFCoordsAreWCS(*pBE)) + aE2W = DXFTransform(pBE->aExtrusion); switch (pBE->eType) { case DXF_LINE: { const DXFLineEntity * pE = (DXFLineEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); break; } case DXF_POINT: { const DXFPointEntity * pE = (DXFPointEntity*)pBE; - rBox.Union(pE->aP0); + UnionOCS(rBox, aE2W, pE->aP0); break; } case DXF_CIRCLE: { const DXFCircleEntity * pE = (DXFCircleEntity*)pBE; - DXFVector aP; - aP=pE->aP0; - aP.fx-=pE->fRadius; - aP.fy-=pE->fRadius; - rBox.Union(aP); - aP=pE->aP0; - aP.fx+=pE->fRadius; - aP.fy+=pE->fRadius; - rBox.Union(aP); + // radius is invariant under the extrusion (a rotation/reflection), + // so map the centre to WCS and take +/-radius there. + DXFVector aC; + aE2W.Transform(pE->aP0, aC); + DXFVector aP=aC; aP.fx-=pE->fRadius; aP.fy-=pE->fRadius; rBox.Union(aP); + aP=aC; aP.fx+=pE->fRadius; aP.fy+=pE->fRadius; rBox.Union(aP); break; } case DXF_ARC: { const DXFArcEntity * pE = (DXFArcEntity*)pBE; - DXFVector aP; - aP=pE->aP0; - aP.fx-=pE->fRadius; - aP.fy-=pE->fRadius; - rBox.Union(aP); - aP=pE->aP0; - aP.fx+=pE->fRadius; - aP.fy+=pE->fRadius; - rBox.Union(aP); + // Measure the arc's SWEPT extent, not its full circle. A + // large-radius arc (nearly straight, common in CAD) has its + // centre far outside the drawing; unioning centre +/- radius + // blew the box up enormously and collapsed the real geometry + // (issue 122565). Sample along the sweep exactly as the renderer + // does (CCW from fStart by fdA, normalised to (0,360]) and map + // each point through the same ECS->WCS transform. + double fA1 = pE->fStart; + double fdA = pE->fEnd - fA1; + while (fdA>=360.0) fdA-=360.0; + while (fdA<=0.0) fdA+=360.0; + sal_uInt16 nSeg = (sal_uInt16)(fdA/4.0 + 0.5); + if (nSeg<1) nSeg=1; + for (sal_uInt16 nA=0; nA<=nSeg; nA++) { + double fAng = 3.14159265359/180.0 * + (fA1 + fdA*(double)nA/(double)nSeg); + UnionOCS(rBox, aE2W, pE->aP0 + + DXFVector(pE->fRadius*cos(fAng), + pE->fRadius*sin(fAng), 0.0)); + } break; } case DXF_TRACE: { const DXFTraceEntity * pE = (DXFTraceEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_SOLID: { const DXFSolidEntity * pE = (DXFSolidEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_TEXT: { - //const DXFTextEntity * pE = (DXFTextEntity*)pBE; - //??? + const DXFTextEntity * pE = (DXFTextEntity*)pBE; + UnionTextExtent(rBox, aE2W, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_SHAPE: { - //const DXFShapeEntity * pE = (DXFShapeEntity*)pBE; - //??? + // SHAPE is not rendered (DrawEntities' dispatch has no case for + // it, so DrawEntities falls through to default:break), therefore + // it deliberately does not contribute to the extent — measuring + // invisible geometry would only reserve empty margin and shrink + // the visible drawing on the page. break; } case DXF_INSERT: { @@ -312,34 +470,37 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, aP.fx=(aBox.fMinX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; aP.fy=(aBox.fMinY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; aP.fz=(aBox.fMinZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); aP.fx=(aBox.fMaxX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; aP.fy=(aBox.fMaxY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; aP.fz=(aBox.fMaxZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); break; } case DXF_ATTDEF: { - //const DXFAttDefEntity * pE = (DXFAttDefEntity*)pBE; - //??? + // ATTDEF (attribute definition) is likewise not rendered by + // DrawEntities, so — like SHAPE — it is intentionally left out + // of the extent to stay consistent with what actually gets + // drawn. break; } case DXF_ATTRIB: { - //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - //??? + const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + UnionTextExtent(rBox, aE2W, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_VERTEX: { const DXFVertexEntity * pE = (DXFVertexEntity*)pBE; - rBox.Union(pE->aP0); + UnionOCS(rBox, aE2W, pE->aP0); break; } case DXF_3DFACE: { const DXF3DFaceEntity * pE = (DXF3DFaceEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_DIMENSION: { @@ -354,16 +515,18 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, aP.fx=aBox.fMinX-pB->aBasePoint.fx; aP.fy=aBox.fMinY-pB->aBasePoint.fy; aP.fz=aBox.fMinZ-pB->aBasePoint.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); aP.fx=aBox.fMaxX-pB->aBasePoint.fx; aP.fy=aBox.fMaxY-pB->aBasePoint.fy; aP.fz=aBox.fMaxZ-pB->aBasePoint.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); break; } case DXF_POLYLINE: { - //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - //??? + // The old-style POLYLINE header carries no coordinates itself; + // its geometry lives in the VERTEX entities that follow it in + // the entity list (each handled by the DXF_VERTEX case above), + // so there is nothing to union here. break; } case DXF_SEQEND: { @@ -371,10 +534,77 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, //??? break; } - case DXF_HATCH : + case DXF_HATCH : { + // HATCH is rendered (DrawHatchEntity), so its extent has to be + // measured or a hatch reaching past the rest of the drawing gets + // cropped. Union exactly the boundary geometry the renderer + // actually draws: the points of a polyline boundary path, and + // the endpoints of straight (type 1) edges. Circular-arc, + // elliptical-arc and spline edges are NOT drawn by + // DrawHatchEntity (their code is commented out / empty), so they + // are intentionally not unioned here — matching the render. + const DXFHatchEntity * pE = (DXFHatchEntity*)pBE; + for (sal_Int32 nPath = 0; + pE->pBoundaryPathData != NULL && nPath < pE->nBoundaryPathCount; + nPath++) + { + const DXFBoundaryPathData & rPath = pE->pBoundaryPathData[nPath]; + if (rPath.bIsPolyLine == sal_True) { + if (rPath.pP != NULL) { + for (sal_Int32 i = 0; i < rPath.nPointCount; i++) + UnionOCS(rBox, aE2W, rPath.pP[i]); + } + } + else { + for (size_t i = 0; i < rPath.aEdges.size(); i++) { + const DXFEdgeType * pEdge = rPath.aEdges[i]; + if (pEdge != NULL && pEdge->nEdgeType == 1) { + const DXFEdgeTypeLine * pLine = (DXFEdgeTypeLine*)pEdge; + UnionOCS(rBox, aE2W, pLine->aStartPoint); + UnionOCS(rBox, aE2W, pLine->aEndPoint); + } + } + } + } break; - case DXF_LWPOLYLINE : + } + case DXF_LWPOLYLINE : { + // LWPOLYLINE keeps its vertices inline in pP[] (not as + // separate VERTEX entities), so they must be unioned here + // or the drawing extent misses the polyline and the graphic + // is cropped. Walk the same [0,nCount) range that + // DrawLWPolyLineEntity renders. + const DXFLWPolyLineEntity * pE = (DXFLWPolyLineEntity*)pBE; + if (pE->pP != NULL) { + for (sal_Int32 i = 0; i < pE->nCount; i++) + UnionOCS(rBox, aE2W, pE->pP[i]); + } break; + } + case DXF_ELLIPSE: { + // Conservative full-ellipse AABB (like ARC uses the full-circle + // box): centre +/- the axis-projected radii. Over-approximates a + // partial ellipse, which is fine for a bounding box. + const DXFEllipseEntity * pE = (DXFEllipseEntity*)pBE; + const DXFVector & aU = pE->aP1; + DXFVector aV(-aU.fy*pE->fRatio, aU.fx*pE->fRatio, 0.0); + double fRx = sqrt(aU.fx*aU.fx + aV.fx*aV.fx); + double fRy = sqrt(aU.fy*aU.fy + aV.fy*aV.fy); + DXFVector aC; + aE2W.Transform(pE->aP0, aC); + DXFVector aP=aC; aP.fx-=fRx; aP.fy-=fRy; rBox.Union(aP); + aP=aC; aP.fx+=fRx; aP.fy+=fRy; rBox.Union(aP); + break; + } + case DXF_SPLINE: { + // The control polygon's convex hull contains the B-spline curve, + // so unioning the control points bounds it (conservatively). + const DXFSplineEntity * pE = (DXFSplineEntity*)pBE; + if (pE->pControlPts != NULL) + for (long i = 0; i < pE->nCtrlCount; i++) + UnionOCS(rBox, aE2W, pE->pControlPts[i]); + break; + } } pBE=pBE->pSucc; } diff --git a/main/filter/source/graphicfilter/idxf/dxftblrd.cxx b/main/filter/source/graphicfilter/idxf/dxftblrd.cxx index ceab0b7c37..afbcd2107e 100644 --- a/main/filter/source/graphicfilter/idxf/dxftblrd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxftblrd.cxx @@ -332,11 +332,29 @@ DXFLayer * DXFTables::SearchLayer(const char * pName) const } +// DXF symbol-table names (e.g. the "*ACTIVE" viewport) are case-insensitive, but +// applications write them in different case ("*ACTIVE" vs "*Active"). A plain +// strcmp then misses the active viewport, so idxf discards the saved view and +// flattens a 3D drawing to a top-view. Compare ASCII-case-insensitively (names +// are ASCII; this avoids <ctype.h>/locale and cherry-picks cleanly). +static sal_Bool DXFNameEqualIgnoreCase(const char * p1, const char * p2) +{ + while (*p1!=0 && *p2!=0) { + char c1=*p1, c2=*p2; + if (c1>='A' && c1<='Z') c1 = (char)(c1 - 'A' + 'a'); + if (c2>='A' && c2<='Z') c2 = (char)(c2 - 'A' + 'a'); + if (c1!=c2) return sal_False; + p1++; p2++; + } + return (*p1==0 && *p2==0) ? sal_True : sal_False; +} + + DXFVPort * DXFTables::SearchVPort(const char * pName) const { DXFVPort * p; for (p=pVPorts; p!=NULL; p=p->pSucc) { - if (strcmp(pName,p->sName)==0) break; + if (DXFNameEqualIgnoreCase(pName,p->sName)) break; } return p; } diff --git a/main/filter/source/graphicfilter/idxf/dxfvec.cxx b/main/filter/source/graphicfilter/idxf/dxfvec.cxx index 61b4ad7c73..201f06095e 100644 --- a/main/filter/source/graphicfilter/idxf/dxfvec.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfvec.cxx @@ -245,5 +245,16 @@ double DXFTransform::CalcRotAngle() const sal_Bool DXFTransform::Mirror() const { - if (aMZ.SProd(aMX*aMY)<0) return sal_True; else return sal_False; + // True when this transform reverses orientation *in the projected XY plane* — + // the plane the 2D metafile is actually drawn in. Arc rendering uses this to + // choose the sweep direction handed to DrawArc. + // + // The 3D handedness aMZ.(aMX x aMY) is the wrong test here: a negative-Z + // extrusion feeds the Arbitrary Axis Algorithm a 180-degrees rotation about Y + // (a proper rotation), which flips the in-plane orientation yet leaves the 3D + // determinant sign unchanged (the flipped aMZ.z masks it). That drew arcs with + // negative-Z extrusion as their complement (e.g. a quarter arc as three + // quarters — issue 16564). Use the sign of the 2D projected determinant, which + // is what actually governs clockwise vs counter-clockwise on the page. + if (aMX.fx*aMY.fy - aMX.fy*aMY.fx < 0) return sal_True; else return sal_False; } diff --git a/main/filter/source/graphicfilter/idxf/idxf.cxx b/main/filter/source/graphicfilter/idxf/idxf.cxx index a1fd94a11c..f44cf85278 100644 --- a/main/filter/source/graphicfilter/idxf/idxf.cxx +++ b/main/filter/source/graphicfilter/idxf/idxf.cxx @@ -28,17 +28,34 @@ #include <vcl/gdimtf.hxx> #include <vcl/graph.hxx> #include <vcl/virdev.hxx> +#include <svtools/FilterConfigItem.hxx> #include "dxf2mtf.hxx" #include <math.h> //================== GraphicImport - die exportierte Funktion ================ -extern "C" sal_Bool __LOADONCALLAPI GraphicImport(SvStream & rStream, Graphic & rGraphic, FilterConfigItem*, sal_Bool ) +extern "C" sal_Bool __LOADONCALLAPI GraphicImport(SvStream & rStream, Graphic & rGraphic, FilterConfigItem* pConfigItem, sal_Bool ) { DXFRepresentation aDXF; DXF2GDIMetaFile aConverter; GDIMetaFile aMTF; + // A DXF may not declare its text encoding ($DWGCODEPAGE). In that case the + // caller (a higher layer that knows the user's intent) can supply a fallback + // encoding as a "CharacterSet" filter option; it is applied before reading so + // that a $DWGCODEPAGE actually present in the file still wins over it. + // See idxf/cases/case-99892 (Phase B concept) for how this option is meant to + // be populated. When neither the file nor the caller provides one, + // DXFRepresentation defaults to the system ANSI encoding. + if ( pConfigItem ) + { + sal_Int32 nEncoding = pConfigItem->ReadInt32( + ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "CharacterSet" ) ), + RTL_TEXTENCODING_DONTKNOW ); + if ( nEncoding != RTL_TEXTENCODING_DONTKNOW ) + aDXF.setTextEncoding( (rtl_TextEncoding)nEncoding ); + } + if ( aDXF.Read( rStream, 0, 60 ) == sal_False ) return sal_False; if ( aConverter.Convert( aDXF, aMTF, 60, 100 ) == sal_False ) diff --git a/main/filter/source/graphicfilter/idxf/makefile.mk b/main/filter/source/graphicfilter/idxf/makefile.mk index ba69c76043..71a2d76637 100644 --- a/main/filter/source/graphicfilter/idxf/makefile.mk +++ b/main/filter/source/graphicfilter/idxf/makefile.mk @@ -54,7 +54,7 @@ SLOFILES = $(SLO)$/dxfgrprd.obj \ SHL1TARGET= idx$(DLLPOSTFIX) SHL1IMPLIB= idxf -SHL1STDLIBS= $(VCLLIB) $(TOOLSLIB) $(SALLIB) +SHL1STDLIBS= $(VCLLIB) $(TOOLSLIB) $(SALLIB) $(SVTOOLLIB) .IF "$(GUI)" == "OS2" SHL1STDLIBS+= $(CPPULIB) .ENDIF diff --git a/main/svtools/source/filter/filter.cxx b/main/svtools/source/filter/filter.cxx index 6558b7cf38..bf1ed23db0 100644 --- a/main/svtools/source/filter/filter.cxx +++ b/main/svtools/source/filter/filter.cxx @@ -514,8 +514,32 @@ static sal_Bool ImpPeekGraphicFormat( SvStream& rStream, String& rFormatExtensio bSomethingTested=sal_True; i=0; - while (i<256 && sFirstBytes[i]<=32) - i++; + + // A DXF file may begin with one or more 999 comment groups (each is a + // "999" code line followed by a comment value line), e.g. files written + // by Pro/ENGINEER. Skip them before looking for the first real group, + // otherwise such valid DXF files are rejected as an unknown format + // (issue 122565). + for (;;) + { + while (i<256 && sFirstBytes[i]<=32) + i++; + if ( i+3<256 && sFirstBytes[i]=='9' && sFirstBytes[i+1]=='9' && + sFirstBytes[i+2]=='9' && sFirstBytes[i+3]<=32 ) + { + // skip the "999" code line and the comment value line after it + int nLine; + for ( nLine=0; nLine<2 && i<256; nLine++ ) + { + while (i<256 && sFirstBytes[i]!='\n' && sFirstBytes[i]!='\r') + i++; + while (i<256 && (sFirstBytes[i]=='\n' || sFirstBytes[i]=='\r')) + i++; + } + continue; + } + break; + } if (i<256) {
