This is an automated email from the ASF dual-hosted git repository.

leginee pushed a commit to branch bugfix-dxf-filter
in repository https://gitbox.apache.org/repos/asf/openoffice.git

commit 6d943642bf1e81cf5dfbf9a3114a12ca53e8b98e
Author: Peter Kovacs <[email protected]>
AuthorDate: Mon Jul 6 09:51:15 2026 +0200

    fix 3D DXF import — skip OCS extrusion for WCS entities (render + bbox), 
case-insensitive VPORT lookup, 3D text via glyph outlines
---
 main/filter/source/graphicfilter/idxf/dxf2mtf.cxx  | 108 ++++++++++++++++++++-
 main/filter/source/graphicfilter/idxf/dxf2mtf.hxx  |   3 +
 main/filter/source/graphicfilter/idxf/dxfentrd.cxx |  22 +++++
 main/filter/source/graphicfilter/idxf/dxfentrd.hxx |   8 ++
 main/filter/source/graphicfilter/idxf/dxfreprd.cxx |   8 +-
 main/filter/source/graphicfilter/idxf/dxftblrd.cxx |  20 +++-
 6 files changed, 164 insertions(+), 5 deletions(-)

diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx 
b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx
index cebe0b6cd1..b116a54ae3 100644
--- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx
+++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx
@@ -483,6 +483,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;
@@ -938,7 +1036,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 {
@@ -965,7 +1063,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);
@@ -1071,6 +1172,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)
diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx 
b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx
index 1321581e25..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);
diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx 
b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx
index e7c8e23671..2b063fbe92 100644
--- a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx
+++ b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx
@@ -1004,3 +1004,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 3f1c57a2d8..62f1724f05 100644
--- a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx
+++ b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx
@@ -627,6 +627,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/dxfreprd.cxx 
b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx
index 9fee186bca..baac269954 100644
--- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx
+++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx
@@ -309,9 +309,13 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities 
& rEntities,
        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.
+               // 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)
+               if (pBE->aExtrusion.fz != 1.0 && !DXFCoordsAreWCS(*pBE))
                        aE2W = DXFTransform(pBE->aExtrusion);
                switch (pBE->eType) {
                        case DXF_LINE: {
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;
 }

Reply via email to