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/dxf2mtf.cxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.cxx index 7f2706117d8..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" @@ -132,15 +133,23 @@ DXFLineInfo DXF2GDIMetaFile::LTypeToDXFLineInfo(const char * sLineType) } } -#if 0 - if (aDXFLineInfo.DashCount > 0 && aDXFLineInfo.DashLen == 0.0) - aDXFLineInfo.DashLen ( 1 ); - if (aDXFLineInfo.DotCount > 0 && aDXFLineInfo.DotLen() == 0.0) - aDXFLineInfo.SetDotLen( 1 ); - if (aDXFLineInfo.GetDashCount > 0 || aDXFLineInfo.GetDotCount > 0) - if (aDXFLineInfo.GetDistance() == 0) - aDXFLineInfo.SetDistance( 1 ); -#endif + // A DXF pattern element of exactly 0.0 is a *dot* (a point), distinct from a + // positive-length dash (e.g. DOT2 = 0,-3.175). AutoCAD paints it as a small + // dot, but VCL has no round dot: LineInfo strokes every "on" run as a short + // segment, so a dot is just a very short dash. Rendered verbatim the length + // collapses to a sub-unit mark (Transform() only clamps it to 1/100mm) and the + // line came out invisible (issue 70275). Give a zero-length dot/dash a small + // model-unit length so it survives scaling; keep it a SMALL fraction of the + // gap (0.05) so it reads as a dot, not a dash. Done here in model units, not + // after scaling, so it tracks the drawing size. Only true 0.0 elements are + // adjusted — real authored dashes keep their length. + const double fDotOnFraction = 0.05; // dot mark length as a fraction of the gap + if (aDXFLineInfo.fDistance != 0.0) { + if (aDXFLineInfo.nDotCount > 0 && aDXFLineInfo.fDotLen == 0.0) + aDXFLineInfo.fDotLen = aDXFLineInfo.fDistance * fDotOnFraction; + if (aDXFLineInfo.nDashCount > 0 && aDXFLineInfo.fDashLen == 0.0) + aDXFLineInfo.fDashLen = aDXFLineInfo.fDistance * fDotOnFraction; + } return aDXFLineInfo; } @@ -475,6 +484,104 @@ void DXF2GDIMetaFile::DrawTextEntity(const DXFTextEntity & rE, const DXFTransfor } +// Draw a TEXT entity projected into the (3D) view, used when the drawing is +// rendered through a non-top viewport. VCL's DrawText can only place text with a +// position + uniform height + single rotation, so it cannot follow the shear / +// foreshortening a 3D projection produces. Instead we take the glyph OUTLINES and +// push them through the full transform like any other geometry, so the text lies +// in its projected plane. The flat 2D path (DrawTextEntity) is left untouched. +void DXF2GDIMetaFile::Draw3DTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform) +{ + ByteString aStr( rE.sText ); + String aUString( aStr, pDXF->getTextEncoding() ); + if ( aUString.Len()==0 ) return; + + long nColor = GetEntityColor(rE); + if ( nColor<0 ) return; + Color aColor = ConvertColor((sal_uInt8)nColor); + + // Extract the glyph outlines from a SCRATCH device, never from pVirDev: the + // recording device has output disabled, so its GetTextOutlines falls back to a + // bitmap path that fails ("could not create system bitmap"). A clean scalable + // font on a plain VirtualDevice takes the vector path. The nominal height is + // arbitrary — the extent is self-calibrated below. + const long nNominal = 1000; + Font aFont; + aFont.SetFamily(FAMILY_SWISS); + aFont.SetSize(Size(0,nNominal)); + aFont.SetAlign(ALIGN_BASELINE); + VirtualDevice aTextDev; + aTextDev.SetFont(aFont); + PolyPolyVector aGlyphs; + sal_Bool bOK = aTextDev.GetTextOutlines( aGlyphs, aUString ); + if ( !bOK || aGlyphs.empty() ) { + DrawTextEntity(rE,rTransform); // no outlines available -> flat fallback + return; + } + + // GetTextOutlines returns the glyphs in an opaque internal scale (it toggles + // the map mode and rescales by a pixel factor), so we can't assume the height + // is nNominal. Self-calibrate: measure the real outline extent and normalise by + // its height, so one text-height maps to 1.0 regardless of the device scale. + double fMinY=0.0, fMaxY=0.0; + sal_Bool bFirst = sal_True; + sal_uInt32 g; + for ( g=0; 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; @@ -586,126 +693,331 @@ void DXF2GDIMetaFile::DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXF void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform ) { - sal_Int32 i, nPolySize = rE.nCount; - if ( nPolySize && rE.pP ) + const sal_Int32 nVtxCount = rE.nCount; + if ( nVtxCount <= 0 || rE.pP == NULL ) + return; + + double fW = rE.fConstantWidth; + if ( !SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) + return; + + const sal_Bool bClosed = ( rE.nFlags & 1 ) != 0; + + // Build the outline in device space, expanding any segment that carries a + // non-zero bulge into an arc. In DXF a bulge is tan(sweep/4), so bulge 1.0 + // is a 180 degree arc; a circle is commonly stored as two bulged segments + // (e.g. Eagle CAD). Straight segments (bulge 0) contribute just the vertex. + DXFPointArray aPtAry; + Point aPt; + for ( sal_Int32 i = 0; i < nVtxCount; i++ ) { - Polygon aPoly( (sal_uInt16)nPolySize); - for ( i = 0; i < nPolySize; i++ ) + rTransform.Transform( rE.pP[ i ], aPt ); + aPtAry.push_back( aPt ); + + sal_Int32 iNext = i + 1; + if ( iNext >= nVtxCount ) { - rTransform.Transform( rE.pP[ (sal_uInt16)i ], aPoly[ (sal_uInt16)i ] ); + if ( !bClosed ) break; // open: no segment after the last vertex + iNext = 0; // closed: last vertex arcs back to the first } - double fW = rE.fConstantWidth; - if ( SetLineAttribute( rE, rTransform.TransLineWidth( fW ) ) ) + + const double fBulge = ( rE.pBulge != NULL ) ? rE.pBulge[ i ] : 0.0; + if ( fBulge != 0.0 ) { - if ( ( rE.nFlags & 1 ) != 0 ) - pVirDev->DrawPolygon( aPoly ); - else - pVirDev->DrawPolyLine( aPoly ); - // #### - //pVirDev->DrawPolyLine( aPoly, aDXFLineInfo ); + const DXFVector & rS = rE.pP[ i ]; + const DXFVector & rEnd = rE.pP[ iNext ]; + // Arc centre from the bulge (cotangent form): the centre lies on the + // chord's perpendicular bisector, offset by cot(sweep/2)/2 * chord. + const double fCot = 0.5 * ( 1.0 / fBulge - fBulge ); + const double fCx = 0.5 * ( ( rS.fx + rEnd.fx ) + fCot * ( rS.fy - rEnd.fy ) ); + const double fCy = 0.5 * ( ( rS.fy + rEnd.fy ) + fCot * ( rEnd.fx - rS.fx ) ); + const double fRx = rS.fx - fCx; + const double fRy = rS.fy - fCy; + const double fRadius = sqrt( fRx * fRx + fRy * fRy ); + const double fStartAng = atan2( fRy, fRx ); + const double fSweep = 4.0 * atan( fBulge ); // signed: >0 = CCW + sal_Int32 nArcPoints = (sal_Int32)( fabs( fSweep ) / ( 2 * 3.14159265359 ) + * (double)OptPointsPerCircle + 0.5 ); + // Emit the interior arc samples; the start vertex is already pushed + // and the end vertex is pushed as the next loop's leading vertex. + for ( sal_Int32 k = 1; k < nArcPoints; k++ ) + { + const double fAng = fStartAng + fSweep * (double)k / (double)nArcPoints; + rTransform.Transform( + DXFVector( fCx + fRadius * cos( fAng ), + fCy + fRadius * sin( fAng ), + rS.fz ), + aPt ); + aPtAry.push_back( aPt ); + } } } + + const sal_Int32 nPolySize = (sal_Int32)aPtAry.size(); + if ( nPolySize < 2 ) + return; + + if ( nPolySize <= 0xFFFF ) + { + // Fits in a single tools Polygon (which is indexed by sal_uInt16). + Polygon aPoly( (sal_uInt16)nPolySize ); + for ( sal_Int32 i = 0; i < nPolySize; i++ ) + aPoly[ (sal_uInt16)i ] = aPtAry[ i ]; + if ( bClosed ) + pVirDev->DrawPolygon( aPoly ); + else + pVirDev->DrawPolyLine( aPoly ); + return; + } + + // The tessellated outline can exceed the 0xFFFF points a tools Polygon holds. + // Render it as a sequence of Polygon segments that overlap by one point, so + // the whole line is drawn without truncation and the stroke stays continuous. + const sal_Int32 nMaxChunk = 0xFFFF; + sal_Int32 nStart = 0; + while ( nStart + 1 < nPolySize ) + { + sal_Int32 nEnd = nStart + nMaxChunk; + if ( nEnd > nPolySize ) + nEnd = nPolySize; + const sal_uInt16 nChunk = (sal_uInt16)( nEnd - nStart ); + Polygon aChunk( nChunk ); + for ( sal_uInt16 j = 0; j < nChunk; j++ ) + aChunk[ j ] = aPtAry[ nStart + j ]; + pVirDev->DrawPolyLine( aChunk ); + nStart = nEnd - 1; // next segment shares this segment's last point + } + if ( bClosed ) + { + // Close the outline: last point back to the first. + Polygon aClose( 2 ); + aClose[ 0 ] = aPtAry[ nPolySize - 1 ]; + aClose[ 1 ] = aPtAry[ 0 ]; + pVirDev->DrawPolyLine( aClose ); + } } void DXF2GDIMetaFile::DrawHatchEntity(const DXFHatchEntity & rE, const DXFTransform & rTransform ) { - if ( rE.nBoundaryPathCount ) + if ( !rE.nBoundaryPathCount ) + return; + + // Build the boundary as a PolyPolygon, tessellating curved edges so the + // area is measured correctly for both the solid fill and the pattern clip. + sal_Int32 j; + PolyPolygon aPolyPoly; + for ( j = 0; j < rE.nBoundaryPathCount; j++ ) { - SetAreaAttribute( rE ); - sal_Int32 j = 0; - PolyPolygon aPolyPoly; - for ( j = 0; j < rE.nBoundaryPathCount; j++ ) + DXFPointArray aPtAry; + const DXFBoundaryPathData& rPathData = rE.pBoundaryPathData[ j ]; + if ( rPathData.bIsPolyLine ) { - DXFPointArray aPtAry; - const DXFBoundaryPathData& rPathData = rE.pBoundaryPathData[ j ]; - if ( rPathData.bIsPolyLine ) + sal_Int32 i; + for( i = 0; i < rPathData.nPointCount; i++ ) { - sal_Int32 i; - for( i = 0; i < rPathData.nPointCount; i++ ) - { - Point aPt; - rTransform.Transform( rPathData.pP[ i ], aPt ); - aPtAry.push_back( aPt ); - } + Point aPt; + rTransform.Transform( rPathData.pP[ i ], aPt ); + aPtAry.push_back( aPt ); } - else + } + else + { + sal_uInt32 i; + for ( i = 0; i < rPathData.aEdges.size(); i++ ) { - sal_uInt32 i; - for ( i = 0; i < rPathData.aEdges.size(); i++ ) + const DXFEdgeType* pEdge = rPathData.aEdges[ i ]; + switch( pEdge->nEdgeType ) { - const DXFEdgeType* pEdge = rPathData.aEdges[ i ]; - switch( pEdge->nEdgeType ) + case 1 : { - case 1 : + Point aPt; + rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aStartPoint, aPt ); + aPtAry.push_back( aPt ); + rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aEndPoint, aPt ); + aPtAry.push_back( aPt ); + } + break; + case 2 : + { + // Circular arc edge: sample the sweep and push the points. + // Angles are in degrees; direction follows the CCW flag. + const DXFEdgeTypeCircularArc* pArc = (const DXFEdgeTypeCircularArc*)pEdge; + double fSweep = pArc->fEndAngle - pArc->fStartAngle; + if ( pArc->nIsCounterClockwiseFlag ) + { while ( fSweep <= 0.0 ) fSweep += 360.0; } + else + { while ( fSweep >= 0.0 ) fSweep -= 360.0; } + sal_uInt16 nPts = (sal_uInt16)( fabs(fSweep)/360.0*(double)OptPointsPerCircle + 0.5 ); + if ( nPts < 2 ) nPts = 2; + for ( sal_uInt16 k = 0; k < nPts; k++ ) { + double a = ( pArc->fStartAngle + fSweep*(double)k/(double)(nPts-1) ) + * (3.14159265359/180.0); Point aPt; - rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aStartPoint, aPt ); - aPtAry.push_back( aPt ); - rTransform.Transform( ((DXFEdgeTypeLine*)pEdge)->aEndPoint, aPt ); + rTransform.Transform( + DXFVector( pArc->aCenter.fx + pArc->fRadius*cos(a), + pArc->aCenter.fy + pArc->fRadius*sin(a), 0.0 ), aPt ); aPtAry.push_back( aPt ); } - break; - case 2 : - { -/* - double frx,fry,fA1,fdA,fAng; - sal_uInt16 nPoints,i; - DXFVector aC; - Point aPS,aPE; - fA1=((DXFEdgeTypeCircularArc*)pEdge)->fStartAngle; - fdA=((DXFEdgeTypeCircularArc*)pEdge)->fEndAngle - fA1; - while ( fdA >= 360.0 ) - fdA -= 360.0; - while ( fdA <= 0 ) - fdA += 360.0; - rTransform.Transform(((DXFEdgeTypeCircularArc*)pEdge)->aCenter, aC); - if ( fdA > 5.0 && rTransform.TransCircleToEllipse(((DXFEdgeTypeCircularArc*)pEdge)->fRadius,frx,fry ) == sal_True ) - { - DXFVector aVS(cos(fA1/180.0*3.14159265359),sin(fA1/180.0*3.14159265359),0.0); - aVS*=((DXFEdgeTypeCircularArc*)pEdge)->fRadius; - aVS+=((DXFEdgeTypeCircularArc*)pEdge)->aCenter; - DXFVector aVE(cos((fA1+fdA)/180.0*3.14159265359),sin((fA1+fdA)/180.0*3.14159265359),0.0); - aVE*=((DXFEdgeTypeCircularArc*)pEdge)->fRadius; - aVE+=((DXFEdgeTypeCircularArc*)pEdge)->aCenter; - if ( rTransform.Mirror() == sal_True ) - { - rTransform.Transform(aVS,aPS); - rTransform.Transform(aVE,aPE); - } - else - { - rTransform.Transform(aVS,aPE); - rTransform.Transform(aVE,aPS); - } - pVirDev->DrawArc( - Rectangle((long)(aC.fx-frx+0.5),(long)(aC.fy-fry+0.5), - (long)(aC.fx+frx+0.5),(long)(aC.fy+fry+0.5)), - aPS,aPE - ); - } -*/ - } - break; - case 3 : - case 4 : - break; } + break; + case 3 : // elliptical arc: not tessellated (not seen in + case 4 : // practice here); spline edges store no control + break; // points, so both fall through as before. } } - sal_uInt16 i, nSize = (sal_uInt16)aPtAry.size(); - if ( nSize ) - { - Polygon aPoly( nSize ); - for ( i = 0; i < nSize; i++ ) - aPoly[ i ] = aPtAry[ i ]; - aPolyPoly.Insert( aPoly, POLYPOLY_APPEND ); - } } - if ( aPolyPoly.Count() ) + sal_uInt16 i, nSize = (sal_uInt16)aPtAry.size(); + if ( nSize ) + { + Polygon aPoly( nSize ); + for ( i = 0; i < nSize; i++ ) + aPoly[ i ] = aPtAry[ i ]; + aPolyPoly.Insert( aPoly, POLYPOLY_APPEND ); + } + } + if ( !aPolyPoly.Count() ) + return; + + if ( rE.nFlags & 1 ) + { + // Solid fill (group 70 bit 0): flood-fill the boundary in the entity colour. + SetAreaAttribute( rE ); + pVirDev->DrawPolyPolygon( aPolyPoly ); + } + else if ( rE.bHasPatternLine ) + { + // Pattern fill: render the section lines with VCL's DrawHatch, which + // clips a line family to the boundary and records a MetaHatchAction. + // The DXF pattern is collapsed to a single/double/triple Hatch; the + // on-page angle and spacing come from mapping the pattern-definition + // line's direction and inter-line offset through rTransform, so any + // scale / rotation / block-insert nesting is accounted for. + long nColor = GetEntityColor( rE ); + if ( nColor < 0 ) + return; + Color aColor = ConvertColor( (sal_uInt8)nColor ); + + const double fPi = 3.14159265359; + double fA = rE.fPatternLineAngle * (fPi/180.0); + DXFVector aDir, aOff; + rTransform.TransDir( DXFVector( cos(fA), sin(fA), 0.0 ), aDir ); + rTransform.TransDir( DXFVector( rE.fPatternLineOffsetX, rE.fPatternLineOffsetY, 0.0 ), aOff ); + + double fDirLen = sqrt( aDir.fx*aDir.fx + aDir.fy*aDir.fy ); + if ( fDirLen < 1e-9 ) + { + // degenerate direction: fall back to a bare outline (no blob). + SetLineAttribute( rE ); pVirDev->DrawPolyPolygon( aPolyPoly ); + return; + } + + // Perpendicular spacing between adjacent lines in device space, and the + // device line angle. VCL's angle is measured in the recorded coordinate + // system with its positive direction as (cos,-sin), hence -aDir.fy. + double fDist = fabs( aOff.fx*aDir.fy - aOff.fy*aDir.fx ) / fDirLen; + long nDistance = (long)( fDist + 0.5 ); + if ( nDistance < 1 ) nDistance = 1; + + double fAngle = atan2( -aDir.fy, aDir.fx ) * 1800.0 / fPi; + long nAngle10 = (long)( fAngle + (fAngle < 0.0 ? -0.5 : 0.5) ); + nAngle10 %= 1800; + if ( nAngle10 < 0 ) + nAngle10 += 1800; + + HatchStyle eStyle = HATCH_SINGLE; + if ( rE.nHatchPatternDefinitionLines == 2 ) + eStyle = HATCH_DOUBLE; + else if ( rE.nHatchPatternDefinitionLines >= 3 ) + eStyle = HATCH_TRIPLE; + + Hatch aHatch( eStyle, aColor, nDistance, (sal_uInt16)nAngle10 ); + pVirDev->DrawHatch( aPolyPoly, aHatch ); + } + else + { + // Pattern fill but no pattern-definition line was parsed: stroke the + // boundary outline only (transparent fill), never a solid blob. + SetLineAttribute( rE ); + pVirDev->DrawPolyPolygon( aPolyPoly ); + } +} + +void DXF2GDIMetaFile::DrawEllipseEntity(const DXFEllipseEntity & rE, const DXFTransform & rTransform) +{ + if (SetLineAttribute(rE)==sal_False) return; + + // Major axis vector (relative to centre) and the minor axis, which for the + // common planar case (extrusion 0,0,1) is ratio*(normal x major) = + // ratio*(-major.y, major.x, 0). A non-default extrusion is applied through + // rTransform by DrawEntities, so compute in the entity's own frame. + const DXFVector & aU = rE.aP1; + DXFVector aV(-aU.fy * rE.fRatio, aU.fx * rE.fRatio, 0.0); + + double fSweep = rE.fEnd - rE.fStart; + while (fSweep <= 0.0) fSweep += 2*3.14159265359; + while (fSweep > 2*3.14159265359) fSweep -= 2*3.14159265359; + + sal_uInt16 nPoints = (sal_uInt16)( fSweep/(2*3.14159265359)*(double)OptPointsPerCircle + 0.5 ); + if (nPoints<2) nPoints=2; + + Polygon aPoly(nPoints); + for (sal_uInt16 i=0; 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; @@ -773,7 +1085,7 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, while (pE!=NULL && bStatus==sal_True) { if (pE->nSpace==0) { - if (pE->aExtrusion.fz==1.0) { + if (pE->aExtrusion.fz==1.0 || DXFCoordsAreWCS(*pE)) { pT=&rTransform; } else { @@ -800,7 +1112,10 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, DrawSolidEntity((DXFSolidEntity&)*pE,*pT); break; case DXF_TEXT: - DrawTextEntity((DXFTextEntity&)*pE,*pT); + if (b3DText) + Draw3DTextEntity((DXFTextEntity&)*pE,*pT); + else + DrawTextEntity((DXFTextEntity&)*pE,*pT); break; case DXF_INSERT: DrawInsertEntity((DXFInsertEntity&)*pE,*pT); @@ -817,6 +1132,12 @@ void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, case DXF_HATCH : DrawHatchEntity((DXFHatchEntity&)*pE, *pT); break; + case DXF_ELLIPSE: + DrawEllipseEntity((DXFEllipseEntity&)*pE,*pT); + break; + case DXF_SPLINE: + DrawSplineEntity((DXFSplineEntity&)*pE,*pT); + break; case DXF_3DFACE: Draw3DFaceEntity((DXF3DFaceEntity&)*pE,*pT); break; @@ -900,6 +1221,9 @@ sal_Bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & if (pVPort->aDirection.fx==0 && pVPort->aDirection.fy==0) pVPort=NULL; } + // A non-top viewport means the drawing is projected in 3D; text then has to be + // laid into that projection (Draw3DTextEntity) rather than drawn flat. + b3DText = (pVPort!=NULL) ? sal_True : sal_False; if (pVPort==NULL) { if (pDXF->aBoundingBox.bEmpty==sal_True) @@ -912,21 +1236,32 @@ sal_Bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & fScale = 0; // -Wall added this... } else { + // Leave a small margin around the drawing. The larger extent is + // scaled to fill the 10000-unit target exactly, so geometry that + // lies right on the extent (e.g. a drawing frame's outer line) + // would otherwise sit precisely on the fit boundary and be + // clipped. This surfaced once issue 58347 made the bounding box + // a tight fit to the geometry; the margin keeps every edge inside + // the fitted area (symmetric, so all four sides get breathing room). + const double fMargin = + (fWidth>fHeight ? fWidth : fHeight) * 0.01; + const double fFitWidth = fWidth + 2.0*fMargin; + const double fFitHeight = fHeight + 2.0*fMargin; // if (fWidth<500.0 || fHeight<500.0 || fWidth>32767.0 || fHeight>32767.0) { - if (fWidth>fHeight) - fScale=10000.0/fWidth; + if (fFitWidth>fFitHeight) + fScale=10000.0/fFitWidth; else - fScale=10000.0/fHeight; + fScale=10000.0/fFitHeight; // } // else // fScale=1.0; aTransform=DXFTransform(fScale,-fScale,fScale, - DXFVector(-pDXF->aBoundingBox.fMinX*fScale, - pDXF->aBoundingBox.fMaxY*fScale, + DXFVector((fMargin-pDXF->aBoundingBox.fMinX)*fScale, + (pDXF->aBoundingBox.fMaxY+fMargin)*fScale, -pDXF->aBoundingBox.fMinZ*fScale)); + aPrefSize.Width() =(long)(fFitWidth*fScale+1.5); + aPrefSize.Height()=(long)(fFitHeight*fScale+1.5); } - aPrefSize.Width() =(long)(fWidth*fScale+1.5); - aPrefSize.Height()=(long)(fHeight*fScale+1.5); } } else { diff --git a/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx b/main/filter/source/graphicfilter/idxf/dxf2mtf.hxx index a2578e6aed0..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); @@ -105,6 +108,10 @@ private: void DrawHatchEntity( const DXFHatchEntity & rE, const DXFTransform & rTransform ); + void DrawEllipseEntity( const DXFEllipseEntity & rE, const DXFTransform & rTransform ); + + void DrawSplineEntity( const DXFSplineEntity & rE, const DXFTransform & rTransform ); + void DrawEntities(const DXFEntities & rEntities, const DXFTransform & rTransform, sal_Bool bTopEntities); diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx index e7cce8cc124..320b96c0f03 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.cxx @@ -422,7 +422,8 @@ DXFLWPolyLineEntity::DXFLWPolyLineEntity() : fConstantWidth( 0.0 ), fStartWidth( 0.0 ), fEndWidth( 0.0 ), - pP( NULL ) + pP( NULL ), + pBulge( NULL ) { } @@ -438,6 +439,9 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) try { pP = new DXFVector[ nCount ]; + pBulge = new double[ nCount ]; + for ( sal_Int32 i = 0; i < nCount; i++ ) + pBulge[ i ] = 0.0; } catch (::std::bad_alloc) { @@ -464,6 +468,14 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) pP[ nIndex++ ].fy = rDGR.GetF(); } break; + case 42: + { + // per-vertex bulge; follows the 10/20 pair of the vertex it + // belongs to (nIndex has already advanced past that vertex). + if ( pBulge && ( nIndex > 0 ) && ( nIndex <= nCount ) ) + pBulge[ nIndex - 1 ] = rDGR.GetF(); + } + break; default: DXFBasicEntity::EvaluateGroup(rDGR); } } @@ -471,6 +483,7 @@ void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) DXFLWPolyLineEntity::~DXFLWPolyLineEntity() { delete[] pP; + delete[] pBulge; } //--------------------------DXFHatchEntity------------------------------------- @@ -684,6 +697,7 @@ DXFHatchEntity::DXFHatchEntity() : DXFBasicEntity( DXF_HATCH ), bIsInBoundaryPathContext( sal_False ), nCurrentBoundaryPathIndex( -1 ), + bPatternLineOffsetSet( sal_False ), nFlags( 0 ), nAssociativityFlag( 0 ), nBoundaryPathCount( 0 ), @@ -695,6 +709,10 @@ DXFHatchEntity::DXFHatchEntity() : nHatchPatternDefinitionLines( 0 ), fPixelSize( 1.0 ), nNumberOfSeedPoints( 0 ), + bHasPatternLine( sal_False ), + fPatternLineAngle( 0.0 ), + fPatternLineOffsetX( 0.0 ), + fPatternLineOffsetY( 0.0 ), pBoundaryPathData( NULL ) { } @@ -741,6 +759,21 @@ void DXFHatchEntity::EvaluateGroup( DXFGroupReader & rDGR ) case 47 : fPixelSize = rDGR.GetF(); break; case 98 : nNumberOfSeedPoints = rDGR.GetI(); break; + // Pattern-definition lines (only present for a pattern fill, after 75). + // Capture the FIRST line's angle (53) and inter-line offset (45/46); + // 53/45/46 appear nowhere else in a HATCH, so intercepting them here is + // safe. Codes 43/44 (base point) are not needed for a VCL Hatch. + case 53 : + if ( !bHasPatternLine ) { fPatternLineAngle = rDGR.GetF(); bHasPatternLine = sal_True; } + break; + case 45 : + if ( bHasPatternLine && !bPatternLineOffsetSet ) fPatternLineOffsetX = rDGR.GetF(); + break; + case 46 : + if ( bHasPatternLine && !bPatternLineOffsetSet ) + { fPatternLineOffsetY = rDGR.GetF(); bPatternLineOffsetSet = sal_True; } + break; + //!! passthrough !! case 92 : nCurrentBoundaryPathIndex++; default: @@ -842,6 +875,98 @@ void DXFDimensionEntity::EvaluateGroup(DXFGroupReader & rDGR) //---------------------------- DXFEntites -------------------------------------- +//--------------------------DXFEllipseEntity------------------------------------ + +DXFEllipseEntity::DXFEllipseEntity() : DXFBasicEntity(DXF_ELLIPSE) +{ + fRatio = 1.0; + fStart = 0.0; + fEnd = 2*3.14159265359; +} + +void DXFEllipseEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + case 40: fRatio=rDGR.GetF(); break; + case 41: fStart=rDGR.GetF(); break; + case 42: fEnd=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFSplineEntity------------------------------------- + +DXFSplineEntity::DXFSplineEntity() : + DXFBasicEntity(DXF_SPLINE), + nFlags(0), + nDegree(0), + nKnotCount(0), + nCtrlCount(0), + nFitCount(0), + pfKnots(NULL), + pControlPts(NULL), + nKnotIndex(0), + nCtrlIndex(0) +{ +} + +DXFSplineEntity::~DXFSplineEntity() +{ + delete[] pfKnots; + delete[] pControlPts; +} + +void DXFSplineEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 70: nFlags = rDGR.GetI(); break; + case 71: nDegree = rDGR.GetI(); break; + case 72: + nKnotCount = rDGR.GetI(); + if ( rDGR.GetStatus() && nKnotCount > 0 ) { + try { pfKnots = new double[nKnotCount]; } + catch (::std::bad_alloc) { rDGR.SetError(); } + } + else if ( nKnotCount < 0 ) + rDGR.SetError(); + break; + case 73: + nCtrlCount = rDGR.GetI(); + if ( rDGR.GetStatus() && nCtrlCount > 0 ) { + try { pControlPts = new DXFVector[nCtrlCount]; } + catch (::std::bad_alloc) { rDGR.SetError(); } + } + else if ( nCtrlCount < 0 ) + rDGR.SetError(); + break; + case 74: nFitCount = rDGR.GetI(); break; + case 40: + if ( pfKnots && nKnotIndex < nKnotCount ) + pfKnots[nKnotIndex++] = rDGR.GetF(); + break; + // control points are 3D (10/20/30); advance on the Z group + case 10: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex].fx = rDGR.GetF(); + break; + case 20: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex].fy = rDGR.GetF(); + break; + case 30: + if ( pControlPts && nCtrlIndex < nCtrlCount ) + pControlPts[nCtrlIndex++].fz = rDGR.GetF(); + break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + void DXFEntities::Read(DXFGroupReader & rDGR) { DXFBasicEntity * pE, * * ppSucc; @@ -874,6 +999,8 @@ void DXFEntities::Read(DXFGroupReader & rDGR) else if (strcmp(rDGR.GetS(),"3DFACE" )==0) pE=new DXF3DFaceEntity; else if (strcmp(rDGR.GetS(),"DIMENSION" )==0) pE=new DXFDimensionEntity; else if (strcmp(rDGR.GetS(),"HATCH" )==0) pE=new DXFHatchEntity; + else if (strcmp(rDGR.GetS(),"ELLIPSE" )==0) pE=new DXFEllipseEntity; + else if (strcmp(rDGR.GetS(),"SPLINE" )==0) pE=new DXFSplineEntity; else { do { @@ -897,3 +1024,25 @@ void DXFEntities::Clear() delete ptmp; } } + +sal_Bool DXFCoordsAreWCS(const DXFBasicEntity & rE) +{ + // See the header comment. For a LINE the extrusion only defines a thickness + // direction, never the endpoint positions; applying it scatters 3D wireframes + // (issue 99893) and inflates the bounding box (both must agree). ELLIPSE/SPLINE + // are intentionally NOT WCS here — DrawEllipseEntity/DrawSplineEntity compute in + // the entity's own frame and rely on the extrusion being applied. + switch (rE.eType) { + case DXF_LINE: + case DXF_POINT: + case DXF_3DFACE: + return sal_True; + case DXF_POLYLINE: + // 3D polyline / 3D mesh / polyface mesh store WCS vertices; a plain + // 2D polyline is in OCS. + return ( ((const DXFPolyLineEntity &)rE).nFlags & (8|16|64) ) != 0 + ? sal_True : sal_False; + default: + return sal_False; + } +} diff --git a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx index 035082d72a4..3c08f08a39c 100644 --- a/main/filter/source/graphicfilter/idxf/dxfentrd.hxx +++ b/main/filter/source/graphicfilter/idxf/dxfentrd.hxx @@ -53,7 +53,9 @@ enum DXFEntityType { DXF_3DFACE, DXF_DIMENSION, DXF_LWPOLYLINE, - DXF_HATCH + DXF_HATCH, + DXF_ELLIPSE, + DXF_SPLINE }; //------------------------------------------------------------------------------ @@ -369,6 +371,7 @@ class DXFLWPolyLineEntity : public DXFBasicEntity double fEndWidth; // 41 DXFVector* pP; + double* pBulge; // 42 (per vertex; parallel to pP, 0.0 = straight segment) DXFLWPolyLineEntity(); ~DXFLWPolyLineEntity(); @@ -465,6 +468,7 @@ class DXFHatchEntity : public DXFBasicEntity { sal_Bool bIsInBoundaryPathContext; sal_Int32 nCurrentBoundaryPathIndex; + sal_Bool bPatternLineOffsetSet; // transient parse state (first 45/46 captured) public : @@ -481,6 +485,15 @@ class DXFHatchEntity : public DXFBasicEntity double fPixelSize; // 47 sal_Int32 nNumberOfSeedPoints; // 98 + // First pattern-definition line (78 block). Enough to drive a VCL Hatch + // (single/double/triple): the line angle (53) and the inter-line offset + // (45/46) give on-page angle + spacing; multi-line patterns collapse to + // this line's geometry plus the style from nHatchPatternDefinitionLines. + sal_Bool bHasPatternLine; // a 53 line angle was parsed + double fPatternLineAngle; // 53 + double fPatternLineOffsetX; // 45 + double fPatternLineOffsetY; // 46 + DXFBoundaryPathData* pBoundaryPathData; DXFHatchEntity(); @@ -492,6 +505,53 @@ class DXFHatchEntity : public DXFBasicEntity }; +//--------------------------Ellipse--------------------------------------------- + +class DXFEllipseEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 center + DXFVector aP1; // 11,21,31 endpoint of the major axis, relative to center + double fRatio; // 40 ratio of minor axis to major axis + double fStart; // 41 start parameter (radians; 0 for a full ellipse) + double fEnd; // 42 end parameter (radians; 2*pi for a full ellipse) + + DXFEllipseEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR); +}; + +//--------------------------Spline---------------------------------------------- + +class DXFSplineEntity : public DXFBasicEntity { + +public: + + long nFlags; // 70 bit 1=closed, 2=periodic, 4=rational, 8=planar + long nDegree; // 71 + long nKnotCount; // 72 + long nCtrlCount; // 73 + long nFitCount; // 74 + + double * pfKnots; // 40 (nKnotCount entries) + DXFVector * pControlPts; // 10,20,30 (nCtrlCount entries) + + DXFSplineEntity(); + ~DXFSplineEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR); + +private: + + long nKnotIndex; + long nCtrlIndex; +}; + //--------------------------Vertex---------------------------------------------- class DXFVertexEntity : public DXFBasicEntity { @@ -577,6 +637,14 @@ public: // Loescht alle Entities }; +// True when an entity's stored coordinates are already in WCS, so the OCS +// "arbitrary axis" (extrusion) transform must NOT be applied to them. Used by +// BOTH the renderer (DrawEntities) and the bounding-box pass (CalcBoundingBox) so +// they stay consistent: flat/planar entities (CIRCLE, ARC, TEXT, 2D POLYLINE, +// LWPOLYLINE, INSERT, ...) are OCS and DO need the extrusion; inherently-3D +// entities (LINE, POINT, 3DFACE, and a 3D polyline / mesh) carry WCS coordinates. +sal_Bool DXFCoordsAreWCS(const DXFBasicEntity & rE); + //------------------------------------------------------------------------------ //--------------------------------- inlines ------------------------------------ //------------------------------------------------------------------------------ diff --git a/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx b/main/filter/source/graphicfilter/idxf/dxfgrprd.cxx index 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 205537a9a1c..6cc60e1aa61 100644 --- a/main/filter/source/graphicfilter/idxf/dxfreprd.cxx +++ b/main/filter/source/graphicfilter/idxf/dxfreprd.cxx @@ -25,6 +25,10 @@ #include "precompiled_filter.hxx" #include +#include +#include // gsl_getSystemTextEncoding +#include // rtl_getTextEncodingFromWindowsCodePage +#include // rtl_str_compareIgnoreAsciiCase #include @@ -135,7 +139,32 @@ void DXFPalette::SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sa DXFRepresentation::DXFRepresentation() { - setTextEncoding(RTL_TEXTENCODING_IBM_437); + // Default text encoding for a DXF that neither declares a $DWGCODEPAGE nor + // receives an override from the caller (see GraphicImport / FilterConfigItem). + // The historic hard-coded IBM_437 (DOS OEM) is almost never right for a modern + // DXF and garbles any high-bit text (issue 99892). Prefer the system encoding, + // which usually matches the locale the file came from (e.g. a Russian machine + // -> MS_1251). + // + // BUT the system encoding is not necessarily a legacy 8-bit code page any more: + // on Windows with the "Use Unicode UTF-8 for worldwide language support" option + // GetACP() returns 65001, and on modern Linux the locale is UTF-8 as well, so + // the system encoding is RTL_TEXTENCODING_UTF8. Legacy DXF text predates UTF-8 + // and is never encoded that way (test13.dxf: windows-1252 Ä/ü/ß on a UTF-8 host + // came out as artefacts), so decoding those bytes as UTF-8 fails. When the + // system encoding is Unicode/unknown, fall back to windows-1252 — the most + // common encoding for untagged DXF and AOO's usual Western default (cf. the + // msfilter legacy importers); other scripts come from an explicit $DWGCODEPAGE + // or the Phase B chooser. + // Precedence: file $DWGCODEPAGE > caller override > this default. + rtl_TextEncoding eDefault = gsl_getSystemTextEncoding(); + if ( eDefault == RTL_TEXTENCODING_UTF8 || + eDefault == RTL_TEXTENCODING_UTF7 || + eDefault == RTL_TEXTENCODING_UCS2 || + eDefault == RTL_TEXTENCODING_UCS4 || + eDefault == RTL_TEXTENCODING_DONTKNOW ) + eDefault = RTL_TEXTENCODING_MS_1252; + setTextEncoding(eDefault); setGlobalLineTypeScale(1.0); } @@ -176,13 +205,67 @@ sal_Bool DXFRepresentation::Read( SvStream & rIStream, sal_uInt16 nMinPercent, s delete pDGR; - if (bRes==sal_True && aBoundingBox.bEmpty==sal_True) - CalcBoundingBox(aEntities,aBoundingBox); + if (bRes==sal_True) + { + // The header $EXTMIN/$EXTMAX that ReadHeader collected into aBoundingBox + // are written by AutoCAD from the current view/regen state, not from a + // tight geometry fit, so they drift with the zoom level in effect when + // the file was saved (issue 58347: same drawing, different zoom, wrong + // import scale). Measure the real geometry extent and use that for the + // page-fit scaling instead. CalcBoundingBox now measures every entity + // type DrawEntities actually renders (HATCH included), so a non-empty + // geometry box is authoritative. The header extents are kept only as a + // last-resort fallback for the degenerate case where nothing measurable + // was drawn at all (empty geometry box). + DXFBoundingBox aGeometryBox; + CalcBoundingBox(aEntities,aGeometryBox); + if (aGeometryBox.bEmpty==sal_False) + aBoundingBox=aGeometryBox; + } return bRes; } +// Map a DXF $DWGCODEPAGE value to an rtl text encoding. +// The values AutoCAD writes are "ANSI_" (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) { @@ -208,15 +291,13 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) { rDGR.Read(); - // FIXME: we really need a whole table of - // $DWGCODEPAGE to encodings mappings - if ( (strcmp(rDGR.GetS(),"ANSI_932")==0) || - (strcmp(rDGR.GetS(),"ansi_932")==0) || - (strcmp(rDGR.GetS(),"DOS932")==0) || - (strcmp(rDGR.GetS(),"dos932")==0) ) - { - setTextEncoding(RTL_TEXTENCODING_MS_932); - } + // The file declares its encoding: this is + // authoritative and overrides both the + // default and any caller-supplied override. + rtl_TextEncoding eEnc = + DXFCodePageToTextEncoding(rDGR.GetS()); + if (eEnc!=RTL_TEXTENCODING_DONTKNOW) + setTextEncoding(eEnc); } else if (strcmp(rDGR.GetS(),"$LTSCALE")==0) { @@ -231,73 +312,150 @@ void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) } +// Map a single OCS point of an entity to WCS via its extrusion, then add it to +// the bounding box. The renderer (DXF2GDIMetaFile::DrawEntities) applies the +// same ECS->WCS transform per entity, so the extent has to be measured in WCS +// as well — otherwise drawings that use a non-default (e.g. negative-Z) +// extrusion come out mis-scaled and offset (issue 16564: PL1CAFE.dxf reaches +// OCS X=-10.88 on mirrored polylines that actually render near WCS X=+10.88). +static inline void UnionOCS(DXFBoundingBox & rBox, const DXFTransform & rE2W, + const DXFVector & rP) +{ + DXFVector aW; + rE2W.Transform(rP, aW); + rBox.Union(aW); +} + + +// Approximate the world-space extent of a single line of text and add it to +// the bounding box, so labels sitting near the drawing edge are not cropped. +// AOO renders TEXT/ATTRIB left-aligned on the baseline at the insertion point, +// growing up and to the right (the justification codes are ignored by the +// renderer, see DXF2GDIMetaFile::DrawTextEntity), so bound that rectangle, +// rotated by the text angle. The per-character advance and descent are +// deliberately generous fractions of the text height so the label never clips. +// rE2W maps the (OCS) text rectangle to WCS so text on an extruded entity is +// measured where it is drawn. +static void UnionTextExtent(DXFBoundingBox & rBox, const DXFTransform & rE2W, + const DXFVector & rInsert, + double fHeight, double fXScale, double fRotAngle, + const char * pText) +{ + if (fHeight <= 0.0) { + UnionOCS(rBox, rE2W, rInsert); + return; + } + if (fXScale <= 0.0) + fXScale = 1.0; + + const size_t nLen = (pText != NULL) ? strlen(pText) : 0; + const double fWidth = (double)nLen * 0.85 * fHeight * fXScale; + const double fAscent = fHeight; + const double fDescent = 0.25 * fHeight; + + const double fRad = fRotAngle * 3.14159265359 / 180.0; + const DXFVector aDir( cos(fRad), sin(fRad), 0.0); // text direction + const DXFVector aUp (-sin(fRad), cos(fRad), 0.0); // baseline normal (up) + + for (int a = 0; a < 2; a++) { + const double fAlong = a ? fWidth : 0.0; + for (int b = 0; b < 2; b++) { + const double fPerp = b ? fAscent : -fDescent; + UnionOCS(rBox, rE2W, rInsert + aDir * fAlong + aUp * fPerp); + } + } +} + + void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, DXFBoundingBox & rBox) { DXFBasicEntity * pBE=rEntities.pFirst; while (pBE!=NULL) { + // The renderer builds an ECS->WCS transform from the entity's extrusion + // whenever it is not the default (DrawEntities checks fz != 1.0); measure + // through the SAME transform so the box is in WCS, not raw OCS. Use the same + // WCS-vs-OCS rule as DrawEntities (DXFCoordsAreWCS): entities whose coords + // are already WCS (LINE/POINT/3DFACE/3D-polyline) must NOT be extruded, or + // the box is measured over scattered positions and comes out far too large + // (issue 99893/70273 — geometry then sits in a corner of an inflated page). + DXFTransform aE2W; // identity by default + if (pBE->aExtrusion.fz != 1.0 && !DXFCoordsAreWCS(*pBE)) + aE2W = DXFTransform(pBE->aExtrusion); switch (pBE->eType) { case DXF_LINE: { const DXFLineEntity * pE = (DXFLineEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); break; } case DXF_POINT: { const DXFPointEntity * pE = (DXFPointEntity*)pBE; - rBox.Union(pE->aP0); + UnionOCS(rBox, aE2W, pE->aP0); break; } case DXF_CIRCLE: { const DXFCircleEntity * pE = (DXFCircleEntity*)pBE; - DXFVector aP; - aP=pE->aP0; - aP.fx-=pE->fRadius; - aP.fy-=pE->fRadius; - rBox.Union(aP); - aP=pE->aP0; - aP.fx+=pE->fRadius; - aP.fy+=pE->fRadius; - rBox.Union(aP); + // radius is invariant under the extrusion (a rotation/reflection), + // so map the centre to WCS and take +/-radius there. + DXFVector aC; + aE2W.Transform(pE->aP0, aC); + DXFVector aP=aC; aP.fx-=pE->fRadius; aP.fy-=pE->fRadius; rBox.Union(aP); + aP=aC; aP.fx+=pE->fRadius; aP.fy+=pE->fRadius; rBox.Union(aP); break; } case DXF_ARC: { const DXFArcEntity * pE = (DXFArcEntity*)pBE; - DXFVector aP; - aP=pE->aP0; - aP.fx-=pE->fRadius; - aP.fy-=pE->fRadius; - rBox.Union(aP); - aP=pE->aP0; - aP.fx+=pE->fRadius; - aP.fy+=pE->fRadius; - rBox.Union(aP); + // Measure the arc's SWEPT extent, not its full circle. A + // large-radius arc (nearly straight, common in CAD) has its + // centre far outside the drawing; unioning centre +/- radius + // blew the box up enormously and collapsed the real geometry + // (issue 122565). Sample along the sweep exactly as the renderer + // does (CCW from fStart by fdA, normalised to (0,360]) and map + // each point through the same ECS->WCS transform. + double fA1 = pE->fStart; + double fdA = pE->fEnd - fA1; + while (fdA>=360.0) fdA-=360.0; + while (fdA<=0.0) fdA+=360.0; + sal_uInt16 nSeg = (sal_uInt16)(fdA/4.0 + 0.5); + if (nSeg<1) nSeg=1; + for (sal_uInt16 nA=0; nA<=nSeg; nA++) { + double fAng = 3.14159265359/180.0 * + (fA1 + fdA*(double)nA/(double)nSeg); + UnionOCS(rBox, aE2W, pE->aP0 + + DXFVector(pE->fRadius*cos(fAng), + pE->fRadius*sin(fAng), 0.0)); + } break; } case DXF_TRACE: { const DXFTraceEntity * pE = (DXFTraceEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_SOLID: { const DXFSolidEntity * pE = (DXFSolidEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_TEXT: { - //const DXFTextEntity * pE = (DXFTextEntity*)pBE; - //??? + const DXFTextEntity * pE = (DXFTextEntity*)pBE; + UnionTextExtent(rBox, aE2W, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_SHAPE: { - //const DXFShapeEntity * pE = (DXFShapeEntity*)pBE; - //??? + // SHAPE is not rendered (DrawEntities' dispatch has no case for + // it, so DrawEntities falls through to default:break), therefore + // it deliberately does not contribute to the extent — measuring + // invisible geometry would only reserve empty margin and shrink + // the visible drawing on the page. break; } case DXF_INSERT: { @@ -312,34 +470,37 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, aP.fx=(aBox.fMinX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; aP.fy=(aBox.fMinY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; aP.fz=(aBox.fMinZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); aP.fx=(aBox.fMaxX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; aP.fy=(aBox.fMaxY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; aP.fz=(aBox.fMaxZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); break; } case DXF_ATTDEF: { - //const DXFAttDefEntity * pE = (DXFAttDefEntity*)pBE; - //??? + // ATTDEF (attribute definition) is likewise not rendered by + // DrawEntities, so — like SHAPE — it is intentionally left out + // of the extent to stay consistent with what actually gets + // drawn. break; } case DXF_ATTRIB: { - //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - //??? + const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + UnionTextExtent(rBox, aE2W, pE->aP0, pE->fHeight, pE->fXScale, + pE->fRotAngle, pE->sText); break; } case DXF_VERTEX: { const DXFVertexEntity * pE = (DXFVertexEntity*)pBE; - rBox.Union(pE->aP0); + UnionOCS(rBox, aE2W, pE->aP0); break; } case DXF_3DFACE: { const DXF3DFaceEntity * pE = (DXF3DFaceEntity*)pBE; - rBox.Union(pE->aP0); - rBox.Union(pE->aP1); - rBox.Union(pE->aP2); - rBox.Union(pE->aP3); + UnionOCS(rBox, aE2W, pE->aP0); + UnionOCS(rBox, aE2W, pE->aP1); + UnionOCS(rBox, aE2W, pE->aP2); + UnionOCS(rBox, aE2W, pE->aP3); break; } case DXF_DIMENSION: { @@ -354,16 +515,18 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, aP.fx=aBox.fMinX-pB->aBasePoint.fx; aP.fy=aBox.fMinY-pB->aBasePoint.fy; aP.fz=aBox.fMinZ-pB->aBasePoint.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); aP.fx=aBox.fMaxX-pB->aBasePoint.fx; aP.fy=aBox.fMaxY-pB->aBasePoint.fy; aP.fz=aBox.fMaxZ-pB->aBasePoint.fz; - rBox.Union(aP); + UnionOCS(rBox, aE2W, aP); break; } case DXF_POLYLINE: { - //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; - //??? + // The old-style POLYLINE header carries no coordinates itself; + // its geometry lives in the VERTEX entities that follow it in + // the entity list (each handled by the DXF_VERTEX case above), + // so there is nothing to union here. break; } case DXF_SEQEND: { @@ -371,10 +534,77 @@ void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, //??? break; } - case DXF_HATCH : + case DXF_HATCH : { + // HATCH is rendered (DrawHatchEntity), so its extent has to be + // measured or a hatch reaching past the rest of the drawing gets + // cropped. Union exactly the boundary geometry the renderer + // actually draws: the points of a polyline boundary path, and + // the endpoints of straight (type 1) edges. Circular-arc, + // elliptical-arc and spline edges are NOT drawn by + // DrawHatchEntity (their code is commented out / empty), so they + // are intentionally not unioned here — matching the render. + const DXFHatchEntity * pE = (DXFHatchEntity*)pBE; + for (sal_Int32 nPath = 0; + pE->pBoundaryPathData != NULL && nPath < pE->nBoundaryPathCount; + nPath++) + { + const DXFBoundaryPathData & rPath = pE->pBoundaryPathData[nPath]; + if (rPath.bIsPolyLine == sal_True) { + if (rPath.pP != NULL) { + for (sal_Int32 i = 0; i < rPath.nPointCount; i++) + UnionOCS(rBox, aE2W, rPath.pP[i]); + } + } + else { + for (size_t i = 0; i < rPath.aEdges.size(); i++) { + const DXFEdgeType * pEdge = rPath.aEdges[i]; + if (pEdge != NULL && pEdge->nEdgeType == 1) { + const DXFEdgeTypeLine * pLine = (DXFEdgeTypeLine*)pEdge; + UnionOCS(rBox, aE2W, pLine->aStartPoint); + UnionOCS(rBox, aE2W, pLine->aEndPoint); + } + } + } + } break; - case DXF_LWPOLYLINE : + } + case DXF_LWPOLYLINE : { + // LWPOLYLINE keeps its vertices inline in pP[] (not as + // separate VERTEX entities), so they must be unioned here + // or the drawing extent misses the polyline and the graphic + // is cropped. Walk the same [0,nCount) range that + // DrawLWPolyLineEntity renders. + const DXFLWPolyLineEntity * pE = (DXFLWPolyLineEntity*)pBE; + if (pE->pP != NULL) { + for (sal_Int32 i = 0; i < pE->nCount; i++) + UnionOCS(rBox, aE2W, pE->pP[i]); + } break; + } + case DXF_ELLIPSE: { + // Conservative full-ellipse AABB (like ARC uses the full-circle + // box): centre +/- the axis-projected radii. Over-approximates a + // partial ellipse, which is fine for a bounding box. + const DXFEllipseEntity * pE = (DXFEllipseEntity*)pBE; + const DXFVector & aU = pE->aP1; + DXFVector aV(-aU.fy*pE->fRatio, aU.fx*pE->fRatio, 0.0); + double fRx = sqrt(aU.fx*aU.fx + aV.fx*aV.fx); + double fRy = sqrt(aU.fy*aU.fy + aV.fy*aV.fy); + DXFVector aC; + aE2W.Transform(pE->aP0, aC); + DXFVector aP=aC; aP.fx-=fRx; aP.fy-=fRy; rBox.Union(aP); + aP=aC; aP.fx+=fRx; aP.fy+=fRy; rBox.Union(aP); + break; + } + case DXF_SPLINE: { + // The control polygon's convex hull contains the B-spline curve, + // so unioning the control points bounds it (conservatively). + const DXFSplineEntity * pE = (DXFSplineEntity*)pBE; + if (pE->pControlPts != NULL) + for (long i = 0; i < pE->nCtrlCount; i++) + UnionOCS(rBox, aE2W, pE->pControlPts[i]); + break; + } } pBE=pBE->pSucc; } diff --git a/main/filter/source/graphicfilter/idxf/dxftblrd.cxx b/main/filter/source/graphicfilter/idxf/dxftblrd.cxx index 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; } 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; } 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 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) {