From 999d0e5d3c8f710c68c553b185539e922d0809c9 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Thu, 2 Jul 2026 22:15:55 +0200 Subject: [PATCH 01/11] filter: idxf: chunk oversized LWPOLYLINE instead of truncating the count A DXF LWPOLYLINE vertex count (group code 90) is 32-bit, but tools::Polygon is indexed by sal_uInt16. Render polylines with more than 65535 points as consecutive Polygon segments that overlap by one point, walking the source array with the full 32-bit index, so the whole line is drawn without narrowing it to 16 bits. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 2f0de83e3857a766280165977d6304354eb43806) (cherry picked from commit ac9bccefb1d87a770f60d12a2e6523cc8ef496a9) --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 7f2706117d8..3ee67f6797f 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -586,24 +586,55 @@ 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 nPolySize = rE.nCount; + if ( nPolySize <= 0 || rE.pP == NULL ) + return; + + double fW = rE.fConstantWidth; + if ( !SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) + return; + + const sal_Bool bClosed = ( rE.nFlags & 1 ) != 0; + + if ( nPolySize <= 0xFFFF ) { - Polygon aPoly( (sal_uInt16)nPolySize); - for ( i = 0; i < nPolySize; i++ ) - { - rTransform.Transform( rE.pP[ (sal_uInt16)i ], aPoly[ (sal_uInt16)i ] ); - } - double fW = rE.fConstantWidth; - if ( SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) - { - if ( ( rE.nFlags & 1 ) != 0 ) - pVirDev->DrawPolygon( aPoly ); - else - pVirDev->DrawPolyLine( aPoly ); - // #### - //pVirDev->DrawPolyLine( aPoly, aDXFLineInfo ); - } + // 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++ ) + rTransform.Transform( rE.pP[ i ], aPoly[ (sal_uInt16)i ] ); + if ( bClosed ) + pVirDev->DrawPolygon( aPoly ); + else + pVirDev->DrawPolyLine( aPoly ); + return; + } + + // The DXF vertex count (group code 90) is a 32-bit value, but a tools Polygon + // holds at most 0xFFFF points. Render a larger polyline as a sequence of + // Polygon segments that overlap by one point, so the whole line is drawn + // without truncation and the stroke stays continuous. The source array pP is + // walked with the full 32-bit position; only the per-segment index is 16-bit. + 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++ ) + rTransform.Transform( rE.pP[ nStart + j ], aChunk[ j ] ); + pVirDev->DrawPolyLine( aChunk ); + nStart = nEnd - 1; // next segment shares this segment's last point + } + if ( bClosed ) + { + // Close the outline: last vertex back to the first. + Polygon aClose( 2 ); + rTransform.Transform( rE.pP[ nPolySize - 1 ], aClose[ 0 ] ); + rTransform.Transform( rE.pP[ 0 ], aClose[ 1 ] ); + pVirDev->DrawPolyLine( aClose ); } } From 22079cd99557dff80c71ceac553b439f8a80b6c5 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Fri, 3 Jul 2026 00:22:13 +0200 Subject: [PATCH 02/11] dxt files labels and text are evaluated when deciding on the points where to corp the picture. We have to estimate font size and width, since we have no renderer at that point. so we estimate the size. (cherry picked from commit 2c10eb18ea27ba6eba6a25520a663b8636044f74) --- .gitignore | 15 +++++ .../source/graphicfilter/idxf/dxfreprd.cxx | 61 +++++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index a1c136d1937..55f009dbbce 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,18 @@ main/winaccessibility/source/UAccCOMIDL/dlldata.c # Python .venv + +# Visual Studio Code +.vscode/ + +# JetBrains IDEs +.idea/ + +# claude +.claude/ + +# bazel +bazel-* +user.bazelrc +*.lock + diff --git a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index 205537a9a1c..f35b544c6f3 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -25,6 +25,7 @@ #include "precompiled_filter.hxx" #include +#include #include @@ -231,6 +232,43 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) } +// 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. +static void UnionTextExtent(DXFBoundingBox & rBox, const DXFVector & rInsert, + double fHeight, double fXScale, double fRotAngle, + const char * pText) +{ + if (fHeight <= 0.0) { + rBox.Union(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; + rBox.Union(rInsert + aDir * fAlong + aUp * fPerp); + } + } +} + + void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, DXFBoundingBox & rBox) { @@ -291,8 +329,9 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, break; } case DXF_TEXT: { - //const DXFTextEntity * pE = (DXFTextEntity*)pBE; - //??? + const DXFTextEntity * pE = (DXFTextEntity*)pBE; + UnionTextExtent(rBox, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_SHAPE: { @@ -325,8 +364,9 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, break; } case DXF_ATTRIB: { - //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - //??? + const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + UnionTextExtent(rBox, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_VERTEX: { @@ -373,8 +413,19 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, } case DXF_HATCH : 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++) + rBox.Union(pE->pP[i]); + } break; + } } pBE=pBE->pSucc; } From 65c7e1576f90d3faefbecf5bad626a760d990dd0 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 09:41:51 +0200 Subject: [PATCH 03/11] fix zoom by ignoring the metadata. The correctness of the metadata depends on creators actions and may be stale. Better to calculate it by ourselfs. --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 25 +++++-- .../source/graphicfilter/idxf/dxfreprd.cxx | 72 ++++++++++++++++--- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 3ee67f6797f..477b2c1b0af 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -943,21 +943,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/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index f35b544c6f3..4fd7c620390 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -177,8 +177,23 @@ 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; } @@ -335,8 +350,11 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, 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: { @@ -359,8 +377,10 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, 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: { @@ -402,8 +422,10 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, 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: { @@ -411,8 +433,40 @@ 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++) + rBox.Union(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; + rBox.Union(pLine->aStartPoint); + rBox.Union(pLine->aEndPoint); + } + } + } + } break; + } case DXF_LWPOLYLINE : { // LWPOLYLINE keeps its vertices inline in pP[] (not as // separate VERTEX entities), so they must be unioned here From e345bd9383e623f5a68fcb7b67f200c5a74eb7e8 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 10:38:40 +0200 Subject: [PATCH 04/11] for the boundary we need to measure as the renderer does. fix correct rendering for arcs. --- .../source/graphicfilter/idxf/dxfreprd.cxx | 110 ++++++++++-------- .../source/graphicfilter/idxf/dxfvec.cxx | 13 ++- 2 files changed, 75 insertions(+), 48 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index 4fd7c620390..3a1b1c44cf2 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -247,6 +247,21 @@ 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, @@ -254,12 +269,15 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) // 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. -static void UnionTextExtent(DXFBoundingBox & rBox, const DXFVector & rInsert, +// 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) { - rBox.Union(rInsert); + UnionOCS(rBox, rE2W, rInsert); return; } if (fXScale <= 0.0) @@ -278,7 +296,7 @@ static void UnionTextExtent(DXFBoundingBox & rBox, const DXFVector & rInsert, const double fAlong = a ? fWidth : 0.0; for (int b = 0; b < 2; b++) { const double fPerp = b ? fAscent : -fDescent; - rBox.Union(rInsert + aDir * fAlong + aUp * fPerp); + UnionOCS(rBox, rE2W, rInsert + aDir * fAlong + aUp * fPerp); } } } @@ -289,63 +307,61 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, { 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. + DXFTransform aE2W; // identity by default + if (pBE->aExtrusion.fz != 1.0) + 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); + 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_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; - UnionTextExtent(rBox, pE->aP0, pE->fHeight, pE->fXScale, + UnionTextExtent(rBox, aE2W, pE->aP0, pE->fHeight, pE->fXScale, pE->fRotAngle, pE->sText); break; } @@ -369,11 +385,11 @@ 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: { @@ -385,21 +401,21 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, } case DXF_ATTRIB: { const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - UnionTextExtent(rBox, pE->aP0, pE->fHeight, pE->fXScale, + 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: { @@ -414,11 +430,11 @@ 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: { @@ -451,7 +467,7 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, if (rPath.bIsPolyLine == sal_True) { if (rPath.pP != NULL) { for (sal_Int32 i = 0; i < rPath.nPointCount; i++) - rBox.Union(rPath.pP[i]); + UnionOCS(rBox, aE2W, rPath.pP[i]); } } else { @@ -459,8 +475,8 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, const DXFEdgeType * pEdge = rPath.aEdges[i]; if (pEdge != NULL && pEdge->nEdgeType == 1) { const DXFEdgeTypeLine * pLine = (DXFEdgeTypeLine*)pEdge; - rBox.Union(pLine->aStartPoint); - rBox.Union(pLine->aEndPoint); + UnionOCS(rBox, aE2W, pLine->aStartPoint); + UnionOCS(rBox, aE2W, pLine->aEndPoint); } } } @@ -476,7 +492,7 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, const DXFLWPolyLineEntity * pE = (DXFLWPolyLineEntity*)pBE; if (pE->pP != NULL) { for (sal_Int32 i = 0; i < pE->nCount; i++) - rBox.Union(pE->pP[i]); + UnionOCS(rBox, aE2W, pE->pP[i]); } break; } diff --git a/main/filter/source/graphicfilter/idxf/dxfvec.cxx b/main/filter/source/graphicfilter/idxf/dxfvec.cxx index 61b4ad7c73f..201f06095eb 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; } From 030749d53e5513266bad4de246db4f34bcfa5f05 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 14:05:24 +0200 Subject: [PATCH 05/11] Adding Ellipse and spine support fixed negative-z extrusion arcs --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 80 ++++++++++++++++ .../source/graphicfilter/idxf/dxf2mtf.hxx | 4 + .../source/graphicfilter/idxf/dxfentrd.cxx | 94 +++++++++++++++++++ .../source/graphicfilter/idxf/dxfentrd.hxx | 51 +++++++++- .../source/graphicfilter/idxf/dxfreprd.cxx | 24 +++++ 5 files changed, 252 insertions(+), 1 deletion(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 477b2c1b0af..96ab298f1cc 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -737,6 +737,80 @@ void DXF2GDIMetaFile::DrawHatchEntity(const DXFHatchEntity & rE, const DXFTransf } } +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; iDrawPolyLine(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= 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; @@ -848,6 +922,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; diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx index a2578e6aed0..1321581e25e 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx @@ -105,6 +105,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 e7cce8cc124..d3af99be5f2 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx @@ -842,6 +842,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 +966,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 { diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx index 035082d72a4..00475c0a9d9 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 }; //------------------------------------------------------------------------------ @@ -492,6 +494,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 { diff --git a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index 3a1b1c44cf2..6bcb2d49fd6 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -496,6 +496,30 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, } 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; } From 82ba6d6560ff4df4fee01e2c4daf841c51f09632 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 15:34:25 +0200 Subject: [PATCH 06/11] the file detector now ignores comments at the start of the file to identifiy DXF files. On import we tollerate now 1.0 as 1. Before it broke the read Measuring the arc's swept extent, not the full circle, so large-radius arcs don't blow up the scaling box ond collapse the drawing. --- .../source/graphicfilter/idxf/dxfgrprd.cxx | 14 +++++++++- .../source/graphicfilter/idxf/dxfreprd.cxx | 24 +++++++++++++--- main/svtools/source/filter/filter.cxx | 28 +++++++++++++++++-- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx b/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx index 57d362a33c2..895469ece92 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 6bcb2d49fd6..9fee186bca8 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -337,10 +337,26 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, } case DXF_ARC: { const DXFArcEntity * pE = (DXFArcEntity*)pBE; - 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); + // 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: { diff --git a/main/svtools/source/filter/filter.cxx b/main/svtools/source/filter/filter.cxx index 6558b7cf387..bf1ed23db06 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) { From c4b51fde1e475e553f010285a15e778ae2c0fc66 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 15:55:45 +0200 Subject: [PATCH 07/11] added support for dashed lines. --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 96ab298f1cc..658a6520f3e 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -132,15 +132,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; } From 5ed424c6cedf5d64d4bf6b1dda491df7050d84df Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Sun, 5 Jul 2026 22:49:30 +0200 Subject: [PATCH 08/11] support LWPOLYLINE bulge (group 42) --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 76 ++++++++++++++++--- .../source/graphicfilter/idxf/dxfentrd.cxx | 15 +++- .../source/graphicfilter/idxf/dxfentrd.hxx | 1 + 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 658a6520f3e..cebe0b6cd17 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -594,8 +594,8 @@ void DXF2GDIMetaFile::DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXF void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform ) { - const sal_Int32 nPolySize = rE.nCount; - if ( nPolySize <= 0 || rE.pP == NULL ) + const sal_Int32 nVtxCount = rE.nCount; + if ( nVtxCount <= 0 || rE.pP == NULL ) return; double fW = rE.fConstantWidth; @@ -604,12 +604,66 @@ void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const 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++ ) + { + rTransform.Transform( rE.pP[ i ], aPt ); + aPtAry.push_back( aPt ); + + sal_Int32 iNext = i + 1; + if ( iNext >= nVtxCount ) + { + if ( !bClosed ) break; // open: no segment after the last vertex + iNext = 0; // closed: last vertex arcs back to the first + } + + const double fBulge = ( rE.pBulge != NULL ) ? rE.pBulge[ i ] : 0.0; + if ( fBulge != 0.0 ) + { + 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++ ) - rTransform.Transform( rE.pP[ i ], aPoly[ (sal_uInt16)i ] ); + aPoly[ (sal_uInt16)i ] = aPtAry[ i ]; if ( bClosed ) pVirDev->DrawPolygon( aPoly ); else @@ -617,11 +671,9 @@ void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const return; } - // The DXF vertex count (group code 90) is a 32-bit value, but a tools Polygon - // holds at most 0xFFFF points. Render a larger polyline as a sequence of - // Polygon segments that overlap by one point, so the whole line is drawn - // without truncation and the stroke stays continuous. The source array pP is - // walked with the full 32-bit position; only the per-segment index is 16-bit. + // 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 ) @@ -632,16 +684,16 @@ void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const const sal_uInt16 nChunk = (sal_uInt16)( nEnd - nStart ); Polygon aChunk( nChunk ); for ( sal_uInt16 j = 0; j < nChunk; j++ ) - rTransform.Transform( rE.pP[ nStart + j ], aChunk[ 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 vertex back to the first. + // Close the outline: last point back to the first. Polygon aClose( 2 ); - rTransform.Transform( rE.pP[ nPolySize - 1 ], aClose[ 0 ] ); - rTransform.Transform( rE.pP[ 0 ], aClose[ 1 ] ); + aClose[ 0 ] = aPtAry[ nPolySize - 1 ]; + aClose[ 1 ] = aPtAry[ 0 ]; pVirDev->DrawPolyLine( aClose ); } } diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx index d3af99be5f2..e7c8e236711 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------------------------------------- diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx index 00475c0a9d9..3f1c57a2d89 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx @@ -371,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(); From 393196020362fc2c8d0783b91254644fb848c4d1 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Mon, 6 Jul 2026 09:51:15 +0200 Subject: [PATCH 09/11] =?UTF-8?q?fix=203D=20DXF=20import=20=E2=80=94=20ski?= =?UTF-8?q?p=20OCS=20extrusion=20for=20WCS=20entities=20(render=20+=20bbox?= =?UTF-8?q?),=20case-insensitive=20VPORT=20lookup,=203D=20text=20via=20gly?= =?UTF-8?q?ph=20outlines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 108 +++++++++++++++++- .../source/graphicfilter/idxf/dxf2mtf.hxx | 3 + .../source/graphicfilter/idxf/dxfentrd.cxx | 22 ++++ .../source/graphicfilter/idxf/dxfentrd.hxx | 8 ++ .../source/graphicfilter/idxf/dxfreprd.cxx | 8 +- .../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 cebe0b6cd17..b116a54ae3e 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; gfMaxY) 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; gDrawPolyPolygon( 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 1321581e25e..3b864db6236 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 e7c8e236711..2b063fbe927 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 3f1c57a2d89..62f1724f055 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 9fee186bca8..baac2699549 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 ceab0b7c37f..afbcd2107e0 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 /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; } From 05e2156ffdbb5657da2fe959ce0efb68c7a7bfbc Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Mon, 6 Jul 2026 11:01:12 +0200 Subject: [PATCH 10/11] pattern hatches render as proper section lines instead of solid blobs --- .../source/graphicfilter/idxf/dxf2mtf.cxx | 205 +++++++++++------- .../source/graphicfilter/idxf/dxfentrd.cxx | 20 ++ .../source/graphicfilter/idxf/dxfentrd.hxx | 10 + 3 files changed, 157 insertions(+), 78 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index b116a54ae3e..ac27b0a3cb3 100644 --- a/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx +++ b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "dxf2mtf.hxx" @@ -798,100 +799,148 @@ void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const 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 : + { + 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 : { - case 1 : + // 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 ); } } diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx index 2b063fbe927..320b96c0f03 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx @@ -697,6 +697,7 @@ DXFHatchEntity::DXFHatchEntity() : DXFBasicEntity( DXF_HATCH ), bIsInBoundaryPathContext( sal_False ), nCurrentBoundaryPathIndex( -1 ), + bPatternLineOffsetSet( sal_False ), nFlags( 0 ), nAssociativityFlag( 0 ), nBoundaryPathCount( 0 ), @@ -708,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 ) { } @@ -754,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: diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx index 62f1724f055..3c08f08a39c 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx @@ -468,6 +468,7 @@ class DXFHatchEntity : public DXFBasicEntity { sal_Bool bIsInBoundaryPathContext; sal_Int32 nCurrentBoundaryPathIndex; + sal_Bool bPatternLineOffsetSet; // transient parse state (first 45/46 captured) public : @@ -484,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(); From 96b0524be2f2db4f8bd766f53466e5765b393f52 Mon Sep 17 00:00:00 2001 From: Peter Kovacs Date: Mon, 6 Jul 2026 22:35:44 +0200 Subject: [PATCH 11/11] encoding is now based on RTL_TEXTENCODING_MS_1252 as a default. The dxf filter is now prepared to receive a overide which encoding is to be used. With this fix no higher level has been included to use this method. no test have been done on the code. All test are done in a compilde AOO version, by loading test files. --- .../source/graphicfilter/idxf/dxfreprd.cxx | 85 ++++++++++++++++--- .../filter/source/graphicfilter/idxf/idxf.cxx | 19 ++++- .../source/graphicfilter/idxf/makefile.mk | 2 +- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx index baac2699549..6cc60e1aa61 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -26,6 +26,9 @@ #include #include +#include // gsl_getSystemTextEncoding +#include // rtl_getTextEncodingFromWindowsCodePage +#include // rtl_str_compareIgnoreAsciiCase #include @@ -136,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); } @@ -199,6 +227,45 @@ sal_Bool DXFRepresentation::Read( SvStream & rIStream, sal_uInt16 nMinPercent, s } +// Map a DXF $DWGCODEPAGE value to an rtl text encoding. +// The values AutoCAD writes are "ANSI_" (e.g. ANSI_1252, ANSI_1251), +// "DOS" (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) { @@ -224,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) { diff --git a/main/filter/source/graphicfilter/idxf/idxf.cxx b/main/filter/source/graphicfilter/idxf/idxf.cxx index a1fd94a11cd..f44cf85278b 100644 --- a/main/filter/source/graphicfilter/idxf/idxf.cxx +++ b/main/filter/source/graphicfilter/idxf/idxf.cxx @@ -28,17 +28,34 @@ #include #include #include +#include #include "dxf2mtf.hxx" #include //================== 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 ba69c760437..71a2d766370 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