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; }
