/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // MS Windows defines enum WMFRecords { W_META_EOF = 0x0000, W_META_SETBKCOLOR = 0x0201, W_META_SETBKMODE = 0x0102, W_META_SETMAPMODE = 0x0103, W_META_SETROP2 = 0x0104, W_META_SETRELABS = 0x0105, W_META_SETPOLYFILLMODE = 0x0106, W_META_SETSTRETCHBLTMODE = 0x0107, W_META_SETTEXTCHAREXTRA = 0x0108, W_META_SETTEXTCOLOR = 0x0209, W_META_SETTEXTJUSTIFICATION = 0x020A, W_META_SETWINDOWORG = 0x020B, W_META_SETWINDOWEXT = 0x020C, W_META_SETVIEWPORTORG = 0x020D, W_META_SETVIEWPORTEXT = 0x020E, W_META_OFFSETWINDOWORG = 0x020F, W_META_SCALEWINDOWEXT = 0x0410, W_META_OFFSETVIEWPORTORG = 0x0211, W_META_SCALEVIEWPORTEXT = 0x0412, W_META_LINETO = 0x0213, W_META_MOVETO = 0x0214, W_META_EXCLUDECLIPRECT = 0x0415, W_META_INTERSECTCLIPRECT = 0x0416, W_META_ARC = 0x0817, W_META_ELLIPSE = 0x0418, W_META_FLOODFILL = 0x0419, W_META_PIE = 0x081A, W_META_RECTANGLE = 0x041B, W_META_ROUNDRECT = 0x061C, W_META_PATBLT = 0x061D, W_META_SAVEDC = 0x001E, W_META_SETPIXEL = 0x041F, W_META_OFFSETCLIPRGN = 0x0220, W_META_TEXTOUT = 0x0521, W_META_BITBLT = 0x0922, W_META_STRETCHBLT = 0x0B23, W_META_POLYGON = 0x0324, W_META_POLYLINE = 0x0325, W_META_ESCAPE = 0x0626, W_META_RESTOREDC = 0x0127, W_META_FILLREGION = 0x0228, W_META_FRAMEREGION = 0x0429, W_META_INVERTREGION = 0x012A, W_META_PAINTREGION = 0x012B, W_META_SELECTCLIPREGION = 0x012C, W_META_SELECTOBJECT = 0x012D, W_META_SETTEXTALIGN = 0x012E, W_META_DRAWTEXT = 0x062F, W_META_CHORD = 0x0830, W_META_SETMAPPERFLAGS = 0x0231, W_META_EXTTEXTOUT = 0x0a32, W_META_SETDIBTODEV = 0x0d33, W_META_SELECTPALETTE = 0x0234, W_META_REALIZEPALETTE = 0x0035, W_META_ANIMATEPALETTE = 0x0436, W_META_SETPALENTRIES = 0x0037, W_META_POLYPOLYGON = 0x0538, W_META_RESIZEPALETTE = 0x0139, W_META_DIBBITBLT = 0x0940, W_META_DIBSTRETCHBLT = 0x0b41, W_META_DIBCREATEPATTERNBRUSH = 0x0142, W_META_STRETCHDIB = 0x0f43, W_META_EXTFLOODFILL = 0x0548, W_META_RESETDC = 0x014C, W_META_STARTDOC = 0x014D, W_META_STARTPAGE = 0x004F, W_META_ENDPAGE = 0x0050, W_META_ABORTDOC = 0x0052, W_META_ENDDOC = 0x005E, W_META_DELETEOBJECT = 0x01f0, W_META_CREATEPALETTE = 0x00f7, W_META_CREATEBRUSH = 0x00F8, W_META_CREATEPATTERNBRUSH = 0x01F9, W_META_CREATEPENINDIRECT = 0x02FA, W_META_CREATEFONTINDIRECT = 0x02FB, W_META_CREATEBRUSHINDIRECT = 0x02FC, W_META_CREATEBITMAPINDIRECT = 0x02FD, W_META_CREATEBITMAP = 0x06FE, W_META_CREATEREGION = 0x06FF }; void GetWinExtMax(const Point& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode eMapMode) { Point aSource(rSource); if (eMapMode == emfio::MappingMode::MM_HIMETRIC) aSource.setY( -rSource.Y() ); if (aSource.X() < rPlaceableBound.Left()) rPlaceableBound.SetLeft( aSource.X() ); if (aSource.X() > rPlaceableBound.Right()) rPlaceableBound.SetRight( aSource.X() ); if (aSource.Y() < rPlaceableBound.Top()) rPlaceableBound.SetTop( aSource.Y() ); if (aSource.Y() > rPlaceableBound.Bottom()) rPlaceableBound.SetBottom( aSource.Y() ); } void GetWinExtMax(const tools::Rectangle& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode nMapMode) { GetWinExtMax(rSource.TopLeft(), rPlaceableBound, nMapMode); GetWinExtMax(rSource.BottomRight(), rPlaceableBound, nMapMode); } const char * record_type_name(sal_uInt16 nRecType) { #ifndef SAL_LOG_INFO (void) nRecType; return ""; #else switch( nRecType ) { case W_META_EOF: return "W_META_EOF"; case W_META_SETBKCOLOR: return "META_SETBKCOLOR"; case W_META_SETBKMODE: return "META_SETBKMODE"; case W_META_SETMAPMODE: return "META_SETMAPMODE"; case W_META_SETROP2: return "META_SETROP2"; case W_META_SETRELABS: return "META_SETRELABS"; case W_META_SETPOLYFILLMODE: return "META_SETPOLYFILLMODE"; case W_META_SETSTRETCHBLTMODE: return "META_SETSTRETCHBLTMODE"; case W_META_SETTEXTCHAREXTRA: return "META_SETTEXTCHAREXTRA"; case W_META_SETTEXTCOLOR: return "META_SETTEXTCOLOR"; case W_META_SETTEXTJUSTIFICATION: return "META_SETTEXTJUSTIFICATION"; case W_META_SETWINDOWORG: return "META_SETWINDOWORG"; case W_META_SETWINDOWEXT: return "META_SETWINDOWEXT"; case W_META_SETVIEWPORTORG: return "META_SETVIEWPORTORG"; case W_META_SETVIEWPORTEXT: return "META_SETVIEWPORTEXT"; case W_META_OFFSETWINDOWORG: return "META_OFFSETWINDOWORG"; case W_META_SCALEWINDOWEXT: return "META_SCALEWINDOWEXT"; case W_META_OFFSETVIEWPORTORG: return "META_OFFSETVIEWPORTORG"; case W_META_SCALEVIEWPORTEXT: return "META_SCALEVIEWPORTEXT"; case W_META_LINETO: return "META_LINETO"; case W_META_MOVETO: return "META_MOVETO"; case W_META_EXCLUDECLIPRECT: return "META_EXCLUDECLIPRECT"; case W_META_INTERSECTCLIPRECT: return "META_INTERSECTCLIPRECT"; case W_META_ARC: return "META_ARC"; case W_META_ELLIPSE: return "META_ELLIPSE"; case W_META_FLOODFILL: return "META_FLOODFILL"; case W_META_PIE: return "META_PIE"; case W_META_RECTANGLE: return "META_RECTANGLE"; case W_META_ROUNDRECT: return "META_ROUNDRECT"; case W_META_PATBLT: return "META_PATBLT"; case W_META_SAVEDC: return "META_SAVEDC"; case W_META_SETPIXEL: return "META_SETPIXEL"; case W_META_OFFSETCLIPRGN: return "META_OFFSETCLIPRGN"; case W_META_TEXTOUT: return "META_TEXTOUT"; case W_META_BITBLT: return "META_BITBLT"; case W_META_STRETCHBLT: return "META_STRETCHBLT"; case W_META_POLYGON: return "META_POLYGON"; case W_META_POLYLINE: return "META_POLYLINE"; case W_META_ESCAPE: return "META_ESCAPE"; case W_META_RESTOREDC: return "META_RESTOREDC"; case W_META_FILLREGION: return "META_FILLREGION"; case W_META_FRAMEREGION: return "META_FRAMEREGION"; case W_META_INVERTREGION: return "META_INVERTREGION"; case W_META_PAINTREGION: return "META_PAINTREGION"; case W_META_SELECTCLIPREGION: return "META_SELECTCLIPREGION"; case W_META_SELECTOBJECT: return "META_SELECTOBJECT"; case W_META_SETTEXTALIGN: return "META_SETTEXTALIGN"; case W_META_DRAWTEXT: return "META_DRAWTEXT"; case W_META_CHORD: return "META_CHORD"; case W_META_SETMAPPERFLAGS: return "META_SETMAPPERFLAGS"; case W_META_EXTTEXTOUT: return "META_EXTTEXTOUT"; case W_META_SETDIBTODEV: return "META_SETDIBTODEV"; case W_META_SELECTPALETTE: return "META_SELECTPALETTE"; case W_META_REALIZEPALETTE: return "META_REALIZEPALETTE"; case W_META_ANIMATEPALETTE: return "META_ANIMATEPALETTE"; case W_META_SETPALENTRIES: return "META_SETPALENTRIES"; case W_META_POLYPOLYGON: return "META_POLYPOLYGON"; case W_META_RESIZEPALETTE: return "META_RESIZEPALETTE"; case W_META_DIBBITBLT: return "META_DIBBITBLT"; case W_META_DIBSTRETCHBLT: return "META_DIBSTRETCHBLT"; case W_META_DIBCREATEPATTERNBRUSH: return "META_DIBCREATEPATTERNBRUSH"; case W_META_STRETCHDIB: return "META_STRETCHDIB"; case W_META_EXTFLOODFILL: return "META_EXTFLOODFILL"; case W_META_RESETDC: return "META_RESETDC"; case W_META_STARTDOC: return "META_STARTDOC"; case W_META_STARTPAGE: return "META_STARTPAGE"; case W_META_ENDPAGE: return "META_ENDPAGE"; case W_META_ABORTDOC: return "META_ABORTDOC"; case W_META_ENDDOC: return "META_ENDDOC"; case W_META_DELETEOBJECT: return "META_DELETEOBJECT"; case W_META_CREATEPALETTE: return "META_CREATEPALETTE"; case W_META_CREATEBRUSH: return "META_CREATEBRUSH"; case W_META_CREATEPATTERNBRUSH: return "META_CREATEPATTERNBRUSH"; case W_META_CREATEPENINDIRECT: return "META_CREATEPENINDIRECT"; case W_META_CREATEFONTINDIRECT: return "META_CREATEFONTINDIRECT"; case W_META_CREATEBRUSHINDIRECT: return "META_CREATEBRUSHINDIRECT"; case W_META_CREATEBITMAPINDIRECT: return "META_CREATEBITMAPINDIRECT"; case W_META_CREATEBITMAP: return "META_CREATEBITMAP"; case W_META_CREATEREGION: return "META_CREATEREGION"; default: // Yes, return a pointer to a static buffer. This is a very // local debugging output function, so no big deal. static char buffer[11]; o3tl::sprintf(buffer, "0x%08" SAL_PRIxUINT32, sal_uInt32(nRecType)); return buffer; } #endif } } namespace emfio { inline Point WmfReader::ReadPoint() { short nX = 0, nY = 0; mpInputStream->ReadInt16( nX ).ReadInt16( nY ); return Point( nX, nY ); } inline Point WmfReader::ReadYX() { short nX = 0, nY = 0; mpInputStream->ReadInt16( nY ).ReadInt16( nX ); return Point( nX, nY ); } tools::Rectangle WmfReader::ReadRectangle() { Point aBR, aTL; aBR = ReadYX(); aTL = ReadYX(); aBR.AdjustX( -1 ); aBR.AdjustY( -1 ); if (aTL.X() > aBR.X() || aTL.Y() > aBR.Y()) { SAL_WARN("emfio", "broken rectangle"); return tools::Rectangle::Normalize(aTL, aBR); } return tools::Rectangle( aTL, aBR ); } Size WmfReader::ReadYXExt() { short nW=0, nH=0; mpInputStream->ReadInt16( nH ).ReadInt16( nW ); return Size( nW, nH ); } void WmfReader::ReadRecordParams( sal_uInt32 nRecordSize, sal_uInt16 nFunc ) { bool bRecordOk = true; SAL_INFO("emfio", "\t" << record_type_name(nFunc)); switch(nFunc) { case W_META_SETBKCOLOR: { if (nRecordSize != 5) bRecordOk = false; SetBkColor(ReadColor()); } break; case W_META_SETBKMODE: { // It could have Reserved values. Both 4 and 5 sizes are allowed if ((nRecordSize != 4) && (nRecordSize != 5)) bRecordOk = false; sal_uInt16 nDat = 0; mpInputStream->ReadUInt16( nDat ); SetBkMode( static_cast(nDat) ); } break; case W_META_SETMAPMODE: { if (nRecordSize != 4) bRecordOk = false; sal_uInt16 nMapMode = 0; mpInputStream->ReadUInt16(nMapMode); SetMapMode(static_cast(nMapMode)); } break; case W_META_SETROP2: { // It could have Reserved values. Both 4 and 5 sizes are allowed if ((nRecordSize != 4) && (nRecordSize != 5)) bRecordOk = false; sal_uInt16 nROP2 = 0; mpInputStream->ReadUInt16(nROP2); SetRasterOp(static_cast(nROP2)); mpInputStream->SeekRel(2); // reserved data } break; case W_META_SETTEXTCOLOR: { if (nRecordSize != 5) bRecordOk = false; SetTextColor( ReadColor() ); } break; case W_META_SETWINDOWORG: { if (nRecordSize != 5) bRecordOk = false; SetWinOrg( ReadYX() ); } break; case W_META_SETWINDOWEXT: { if (nRecordSize != 5) bRecordOk = false; short nWidth = 0, nHeight = 0; mpInputStream->ReadInt16( nHeight ).ReadInt16( nWidth ); SetWinExt( Size( nWidth, nHeight ) ); } break; case W_META_OFFSETWINDOWORG: { if (nRecordSize != 5) bRecordOk = false; short nXAdd = 0, nYAdd = 0; mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd ); SetWinOrgOffset( nXAdd, nYAdd ); } break; case W_META_SCALEWINDOWEXT: { if (nRecordSize != 7) bRecordOk = false; short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0; mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum ); if (!nYDenom || !nXDenom) { mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); break; } ScaleWinExt( static_cast(nXNum) / nXDenom, static_cast(nYNum) / nYDenom ); } break; case W_META_SETVIEWPORTORG: case W_META_SETVIEWPORTEXT: { if (nRecordSize != 5) bRecordOk = false; } break; case W_META_OFFSETVIEWPORTORG: { if (nRecordSize != 5) bRecordOk = false; short nXAdd = 0, nYAdd = 0; mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd ); SetDevOrgOffset( nXAdd, nYAdd ); } break; case W_META_SCALEVIEWPORTEXT: { if (nRecordSize != 7) bRecordOk = false; short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0; mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum ); if (!nYDenom || !nXDenom) { mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); break; } ScaleDevExt( static_cast(nXNum) / nXDenom, static_cast(nYNum) / nYDenom ); } break; case W_META_LINETO: { if (nRecordSize != 5) bRecordOk = false; LineTo( ReadYX() ); } break; case W_META_MOVETO: { if (nRecordSize != 5) bRecordOk = false; MoveTo( ReadYX() ); } break; case W_META_INTERSECTCLIPRECT: { if (nRecordSize != 7) bRecordOk = false; IntersectClipRect(ReadRectangle()); } break; case W_META_RECTANGLE: { if (nRecordSize != 7) bRecordOk = false; DrawRect(ReadRectangle()); } break; case W_META_ROUNDRECT: { if (nRecordSize != 9) bRecordOk = false; Size aSize( ReadYXExt() ); DrawRoundRect( ReadRectangle(), Size( aSize.Width() / 2, aSize.Height() / 2 ) ); } break; case W_META_ELLIPSE: { if (nRecordSize != 7) bRecordOk = false; DrawEllipse(ReadRectangle()); } break; case W_META_ARC: { if (nRecordSize != 11) bRecordOk = false; Point aEnd( ReadYX() ); Point aStart( ReadYX() ); tools::Rectangle aRect( ReadRectangle() ); aRect.Normalize(); DrawArc( aRect, aStart, aEnd ); } break; case W_META_PIE: { if (nRecordSize != 11) bRecordOk = false; Point aEnd( ReadYX() ); Point aStart( ReadYX() ); tools::Rectangle aRect( ReadRectangle() ); aRect.Normalize(); // #i73608# OutputDevice deviates from WMF // semantics. start==end means full ellipse here. if( aStart == aEnd ) DrawEllipse( aRect ); else DrawPie( aRect, aStart, aEnd ); } break; case W_META_CHORD: { if (nRecordSize != 11) bRecordOk = false; Point aEnd( ReadYX() ); Point aStart( ReadYX() ); tools::Rectangle aRect( ReadRectangle() ); aRect.Normalize(); DrawChord( aRect, aStart, aEnd ); } break; case W_META_POLYGON: { sal_uInt16 nPoints(0); mpInputStream->ReadUInt16(nPoints); if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; } else { tools::Polygon aPoly(nPoints); for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i) aPoly[ i ] = ReadPoint(); DrawPolygon(std::move(aPoly), false/*bRecordPath*/); } SAL_WARN_IF(!bRecordOk, "emfio", "polygon record has more points than we can handle"); bRecordOk &= mpInputStream->good(); } break; case W_META_POLYPOLYGON: { sal_uInt16 nPolyCount(0); // Number of polygons: mpInputStream->ReadUInt16( nPolyCount ); if (nPolyCount && mpInputStream->good()) { if (nPolyCount > mpInputStream->remainingSize() / sizeof(sal_uInt16)) break; // Number of points of each polygon. Determine total number of points std::unique_ptr xPolygonPointCounts(new sal_uInt16[nPolyCount]); sal_uInt16* pnPoints = xPolygonPointCounts.get(); tools::PolyPolygon aPolyPoly(nPolyCount); sal_uInt16 nPoints = 0; for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a) { mpInputStream->ReadUInt16( pnPoints[a] ); if (pnPoints[a] > SAL_MAX_UINT16 - nPoints) { bRecordOk = false; break; } nPoints += pnPoints[a]; } SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle"); bRecordOk &= mpInputStream->good(); if (!bRecordOk) break; // Polygon points are: for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a) { const sal_uInt16 nPointCount(pnPoints[a]); if (nPointCount > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; break; } std::unique_ptr xPolygonPoints(new Point[nPointCount]); Point* pPtAry = xPolygonPoints.get(); for(sal_uInt16 b(0); b < nPointCount && mpInputStream->good(); ++b) { pPtAry[b] = ReadPoint(); } aPolyPoly.Insert( tools::Polygon(nPointCount, pPtAry) ); } bRecordOk &= mpInputStream->good(); if (!bRecordOk) break; DrawPolyPolygon( aPolyPoly ); } } break; case W_META_POLYLINE: { sal_uInt16 nPoints(0); mpInputStream->ReadUInt16(nPoints); if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; } else { tools::Polygon aPoly(nPoints); for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i) aPoly[ i ] = ReadPoint(); DrawPolyLine( std::move(aPoly) ); } SAL_WARN_IF(!bRecordOk, "emfio", "polyline record has more points than we can handle"); bRecordOk &= mpInputStream->good(); } break; case W_META_SAVEDC: { if (nRecordSize != 3) bRecordOk = false; Push(); } break; case W_META_RESTOREDC: { sal_Int16 nSavedDC(0); if (nRecordSize != 4) bRecordOk = false; mpInputStream->ReadInt16(nSavedDC); SAL_INFO("emfio", "\t\t SavedDC: " << nSavedDC); SAL_WARN_IF(nSavedDC < 0, "emfio", "TODO implement relative to the current state"); Pop(nSavedDC); } break; case W_META_SETPIXEL: { if (nRecordSize != 7) bRecordOk = false; const Color aColor = ReadColor(); DrawPixel( ReadYX(), aColor ); } break; case W_META_OFFSETCLIPRGN: { if (nRecordSize != 5) bRecordOk = false; MoveClipRegion( ReadYXExt() ); } break; case W_META_TEXTOUT: { //record is Recordsize, RecordFunction, StringLength, , YStart, XStart const sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 4 * sizeof(sal_uInt16); const sal_uInt32 nRecSize = nRecordSize * 2; if (nRecSize < nNonStringLen) { bRecordOk = false; break; } sal_uInt16 nLength = 0; mpInputStream->ReadUInt16(nLength); sal_uInt16 nStoredLength = (nLength + 1) &~ 1; if (nRecSize - nNonStringLen < nStoredLength) { SAL_WARN("emfio", "W_META_TEXTOUT too short, truncating string"); nLength = nStoredLength = nRecSize - nNonStringLen; } if (nLength) { std::vector aChars(nStoredLength); nLength = std::min(nLength, mpInputStream->ReadBytes(aChars.data(), aChars.size())); OUString aText(aChars.data(), nLength, GetCharSet()); Point aPosition( ReadYX() ); DrawText( aPosition, aText ); } } break; case W_META_EXTTEXTOUT: { //record is Recordsize, RecordFunction, Y, X, StringLength, options, maybe rectangle, sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 5 * sizeof(sal_uInt16); const sal_uInt32 nRecSize = nRecordSize * 2; if (nRecSize < nNonStringLen) { bRecordOk = false; break; } auto nRecordPos = mpInputStream->Tell() - 6; Point aPosition = ReadYX(); sal_uInt16 nLen = 0, nOptions = 0; mpInputStream->ReadUInt16( nLen ).ReadUInt16( nOptions ); SAL_INFO( "emfio", "\t\t\t Pos: " << aPosition.getX() << ":" << aPosition.getY() << " String Length: " << nLen << " Options: " << nOptions ); tools::Rectangle aRect; if ( ( nOptions & ETO_CLIPPED ) || ( nOptions & ETO_OPAQUE ) ) { nNonStringLen += 2 * sizeof(sal_uInt16); if (nRecSize < nNonStringLen) { bRecordOk = false; break; } const Point aTopLeft = ReadPoint(); const Point aBottomRight = ReadPoint(); aRect = tools::Rectangle( aTopLeft, aBottomRight ); if ( nOptions & ETO_OPAQUE ) DrawRectWithBGColor( aRect ); SAL_INFO( "emfio", "\t\t\t Rectangle : " << aTopLeft.getX() << ":" << aTopLeft.getY() << ", " << aBottomRight.getX() << ":" << aBottomRight.getY() ); } vcl::text::ComplexTextLayoutFlags nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; if ( nOptions & ETO_RTLREADING ) nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; SetTextLayoutMode( nTextLayoutMode ); SAL_WARN_IF( ( nOptions & ( ETO_PDY | ETO_GLYPH_INDEX ) ) != 0, "emfio", "SJ: ETO_PDY || ETO_GLYPH_INDEX in WMF" ); // output only makes sense if the text contains characters if (nLen) { sal_Int32 nOriginalTextLen = nLen; sal_Int32 nOriginalBlockLen = ( nOriginalTextLen + 1 ) &~ 1; auto nMaxStreamPos = nRecordPos + nRecSize; auto nRemainingSize = std::min(mpInputStream->remainingSize(), nMaxStreamPos - mpInputStream->Tell()); if (nRemainingSize < o3tl::make_unsigned(nOriginalBlockLen)) { SAL_WARN("emfio", "exttextout record claimed more data than the stream can provide"); nOriginalTextLen = nOriginalBlockLen = nRemainingSize; } std::vector pChar(nOriginalBlockLen); mpInputStream->ReadBytes(pChar.data(), nOriginalBlockLen); OUString aText(pChar.data(), nOriginalTextLen, GetCharSet()); // after this conversion the text may contain sal_Int32 nNewTextLen = aText.getLength(); // less character (japanese version), so the // dxAry will not fit if ( nNewTextLen ) { if ( nOptions & ETO_CLIPPED ) { Push(); // Save the current clip. It will be restored after text drawing IntersectClipRect( aRect ); } SAL_INFO( "emfio", "\t\t\t Text : " << aText ); KernArray aDXAry; std::unique_ptr pDYAry; auto nDxArySize = nMaxStreamPos - mpInputStream->Tell(); auto nDxAryEntries = nDxArySize >> 1; bool bUseDXAry = false; if ( ( ( nDxAryEntries % nOriginalTextLen ) == 0 ) && ( nNewTextLen <= nOriginalTextLen ) ) { sal_Int32 i; // needed just outside the for aDXAry.resize( nNewTextLen ); if ( nOptions & ETO_PDY ) { pDYAry.reset(new tools::Long[ nNewTextLen ]); } for (i = 0; i < nNewTextLen; i++ ) { if ( mpInputStream->Tell() >= nMaxStreamPos ) break; sal_Int32 nDxCount = 1; if ( nNewTextLen != nOriginalTextLen ) { sal_Unicode cUniChar = aText[i]; OString aTmp(&cUniChar, 1, GetCharSet()); if ( aTmp.getLength() > 1 ) { nDxCount = aTmp.getLength(); } } sal_Int16 nDx = 0, nDy = 0; while ( nDxCount-- ) { if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos ) break; sal_Int16 nDxTmp = 0; mpInputStream->ReadInt16(nDxTmp); nDx += nDxTmp; if ( nOptions & ETO_PDY ) { if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos ) break; sal_Int16 nDyTmp = 0; mpInputStream->ReadInt16(nDyTmp); nDy += nDyTmp; } } aDXAry.set(i, nDx); if ( nOptions & ETO_PDY ) { pDYAry[i] = nDy; } } if ( i == nNewTextLen ) bUseDXAry = true; } if ( bUseDXAry ) DrawText( aPosition, aText, &aDXAry, pDYAry.get() ); else DrawText( aPosition, aText ); if ( nOptions & ETO_CLIPPED ) Pop(); } } } break; case W_META_SELECTOBJECT: case W_META_SELECTPALETTE: { if (nRecordSize != 4) bRecordOk = false; sal_uInt16 nObjIndex = 0; mpInputStream->ReadUInt16( nObjIndex ); SelectObject( nObjIndex ); } break; case W_META_SETTEXTALIGN: { // It could have Reserved values. Both 4 and 5 sizes are allowed if ((nRecordSize != 4) && (nRecordSize != 5)) bRecordOk = false; sal_uInt16 nAlign = 0; mpInputStream->ReadUInt16( nAlign ); SetTextAlign( nAlign ); } break; case W_META_BITBLT: case W_META_STRETCHBLT: { sal_uInt32 nRasterOperation = 0; sal_Int16 nSrcHeight = 0, nSrcWidth = 0, nYSrc, nXSrc, nSye, nSxe, nBitmapType, nWidth, nHeight, nBytesPerScan; sal_uInt8 nPlanes, nBitCount; const bool bNoSourceBitmap = ( nRecordSize == ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 ); mpInputStream->ReadUInt32( nRasterOperation ); SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap); if (nFunc == W_META_STRETCHBLT) { if (nRecordSize < 18) bRecordOk = false; mpInputStream->ReadInt16(nSrcHeight).ReadInt16(nSrcWidth); } else if (nRecordSize < 16) bRecordOk = false; mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc ); if ( bNoSourceBitmap ) mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) mpInputStream->ReadInt16( nSye ).ReadInt16( nSxe ); Point aPoint( ReadYX() ); // The upper-left corner of the destination rectangle. mpInputStream->ReadInt16( nBitmapType ).ReadInt16( nWidth ).ReadInt16( nHeight ).ReadInt16( nBytesPerScan ).ReadUChar( nPlanes ).ReadUChar( nBitCount ); SAL_INFO("emfio", "\t\t Bitmap type:" << nBitmapType << " Width:" << nWidth << " Height:" << nHeight << " WidthBytes:" << nBytesPerScan << " Planes: " << static_cast< sal_uInt16 >( nPlanes ) << " BitCount: " << static_cast< sal_uInt16 >( nBitCount ) ); if (!mpInputStream->good() || bNoSourceBitmap || nBitCount == 4 || nBitCount == 8 || nPlanes != 1) { SAL_WARN("emfio", "\t\t TODO The unsupported Bitmap record. Please fill a bug."); break; } bool bOk = nWidth > 0 && nHeight > 0 && nBytesPerScan > 0 && (nBitCount == 1 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32); if (bOk) { // must be enough data to fulfil the request bOk = o3tl::make_unsigned( nBytesPerScan ) <= mpInputStream->remainingSize() / nHeight; } if (bOk) { // scanline must be large enough to provide all pixels bOk = nBytesPerScan >= nWidth * nBitCount / 8; } if (bOk) { std::unique_ptr< sal_uInt8[] > pData; pData.reset( new sal_uInt8[ nHeight * nBytesPerScan ] ); mpInputStream->ReadBytes( pData.get(), nHeight * nBytesPerScan ); BitmapEx aBitmap = vcl::bitmap::CreateFromData( pData.get(), nWidth, nHeight, nBytesPerScan, nBitCount, true ); if ( nSye && nSxe && ( nXSrc + nSxe <= nWidth ) && ( nYSrc + nSye <= nHeight ) ) { tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSxe, nSye ) ); aBitmap.Crop( aCropRect ); } tools::Rectangle aDestRect( aPoint, Size( nSxe, nSye ) ); maBmpSaveList.emplace_back(aBitmap, aDestRect, nRasterOperation); } } break; case W_META_DIBBITBLT: case W_META_DIBSTRETCHBLT: case W_META_STRETCHDIB: { sal_uInt32 nRasterOperation = 0; sal_uInt16 nColorUsage = 0; sal_Int16 nSrcHeight = 0, nSrcWidth = 0, nYSrc = 0, nXSrc = 0; Bitmap aBmp; const bool bNoSourceBitmap = ( nFunc != W_META_STRETCHDIB ) && ( nRecordSize == ( ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 ) ); if (nRecordSize < 12) bRecordOk = false; mpInputStream->ReadUInt32( nRasterOperation ); SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap); if (nFunc == W_META_STRETCHDIB) { if (nRecordSize < 15) bRecordOk = false; mpInputStream->ReadUInt16(nColorUsage); } // nSrcHeight and nSrcWidth is the number of pixels that has to been used // If they are set to zero, it is as indicator not to scale the bitmap later if( nFunc == W_META_DIBSTRETCHBLT || nFunc == W_META_STRETCHDIB ) mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth ); // nYSrc and nXSrc is the offset of the first pixel mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc ); if ( bNoSourceBitmap ) mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) Size aDestSize( ReadYXExt() ); if ( aDestSize.Width() && aDestSize.Height() ) // #92623# do not try to read buggy bitmaps { tools::Rectangle aDestRect( ReadYX(), aDestSize ); if ( !bNoSourceBitmap ) { // tdf#142625 Read the DIBHeader and check if bitmap is supported // If bitmap is not supported don't run ReadDIB, as it will interrupt image processing const auto nOldPos(mpInputStream->Tell()); sal_uInt32 nHeaderSize(0); mpInputStream->ReadUInt32( nHeaderSize ); if ( nHeaderSize == 0xC ) // BitmapCoreHeader mpInputStream->SeekRel( 6 ); // skip Width (16), Height (16), Planes (16) else mpInputStream->SeekRel( 10 ); // skip Width (32), Height (32), Planes (16) sal_uInt16 nBitCount(0); mpInputStream->ReadUInt16( nBitCount ); if ( nBitCount == 0 ) // TODO Undefined BitCount (JPEG/PNG), which are not supported break; mpInputStream->Seek(nOldPos); if ( !ReadDIB( aBmp, *mpInputStream, false ) ) SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." ); } // test if it is sensible to crop if ( nSrcHeight && nSrcWidth && ( nXSrc + nSrcWidth <= aBmp.GetSizePixel().Width() ) && ( nYSrc + nSrcHeight <= aBmp.GetSizePixel().Height() ) ) { tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSrcWidth, nSrcHeight ) ); aBmp.Crop( aCropRect ); } maBmpSaveList.emplace_back(aBmp, aDestRect, nRasterOperation); } } break; case W_META_DIBCREATEPATTERNBRUSH: { Bitmap aBmp; sal_uInt32 nRed(0), nGreen(0), nBlue(0), nCount(1); sal_uInt16 nStyle(0), nColorUsage(0); if (nRecordSize < 5) bRecordOk = false; mpInputStream->ReadUInt16( nStyle ).ReadUInt16( nColorUsage ); BrushStyle eStyle = static_cast(nStyle); SAL_INFO( "emfio", "\t\t Style:" << nStyle << ", ColorUsage: " << nColorUsage ); if ( eStyle == BrushStyle::BS_PATTERN ) // TODO tdf#142625 Add support for pattern { SAL_WARN( "emfio", "\tTODO: Pattern brush style is not supported." ); CreateObject(); break; } if ( !ReadDIB( aBmp, *mpInputStream, false ) ) SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." ); if ( !aBmp.IsEmpty() ) { BitmapScopedReadAccess pBmp(aBmp); for ( tools::Long y = 0; y < pBmp->Height(); y++ ) { for ( tools::Long x = 0; x < pBmp->Width(); x++ ) { const BitmapColor aColor( pBmp->GetColor( y, x ) ); nRed += aColor.GetRed(); nGreen += aColor.GetGreen(); nBlue += aColor.GetBlue(); } } nCount = pBmp->Height() * pBmp->Width(); if ( !nCount ) nCount++; } Color aColor( static_cast( nRed / nCount ), static_cast( nGreen / nCount ), static_cast( nBlue / nCount ) ); CreateObject(std::make_unique( aColor, false )); } break; case W_META_DELETEOBJECT: { if (nRecordSize != 4) bRecordOk = false; sal_uInt16 nIndex = 0; mpInputStream->ReadUInt16( nIndex ); DeleteObject( nIndex ); } break; case W_META_CREATEPALETTE: { sal_uInt16 nStart = 0; sal_uInt16 nNumberOfEntries = 0; mpInputStream->ReadUInt16(nStart); mpInputStream->ReadUInt16(nNumberOfEntries); if (nRecordSize != 2u * nNumberOfEntries + 5u) bRecordOk = false; SAL_INFO("emfio", "\t\t Start 0x" << std::hex << nStart << std::dec << ", Number of entries: " << nNumberOfEntries); sal_uInt32 nPalleteEntry; std::vector< Color > aPaletteColors; for (sal_uInt16 i = 0; i < nNumberOfEntries; ++i) { //PALETTEENTRY: Values, Blue, Green, Red mpInputStream->ReadUInt32( nPalleteEntry ); SAL_INFO("emfio", "\t\t " << i << ". Palette entry: " << std::setw(10) << std::showbase <(nPalleteEntry), static_cast(nPalleteEntry >> 8), static_cast(nPalleteEntry >> 16)); } CreateObject(std::make_unique( aPaletteColors )); } break; case W_META_CREATEBRUSH: { SAL_WARN( "emfio", "TODO: Not implemented. Please fill the bug report" ); CreateObject(std::make_unique( COL_WHITE, false )); } break; case W_META_CREATEPATTERNBRUSH: { SAL_WARN( "emfio", "TODO: Not implemented. Please fill the bug report" ); CreateObject(std::make_unique( COL_WHITE, false )); } break; case W_META_CREATEPENINDIRECT: { // FIXME For some WMF correct size is 8 and for some 9 if ((nRecordSize != 8) && (nRecordSize != 9)) bRecordOk = false; LineInfo aLineInfo; sal_uInt16 nStyle = 0; sal_uInt16 nWidth = 0; sal_uInt16 nHeight = 0; mpInputStream->ReadUInt16(nStyle); mpInputStream->ReadUInt16(nWidth); mpInputStream->ReadUInt16(nHeight); CreateObject(std::make_unique(ReadColor(), nStyle, nWidth)); } break; case W_META_CREATEBRUSHINDIRECT: { if (nRecordSize != 7) bRecordOk = false; sal_uInt16 nBrushStyle = 0; mpInputStream->ReadUInt16( nBrushStyle ); BrushStyle eBrushStyle = static_cast(nBrushStyle); CreateObject(std::make_unique( ReadColor(), ( eBrushStyle == BrushStyle::BS_NULL ) )); SAL_WARN_IF( (eBrushStyle != BrushStyle::BS_SOLID) && (eBrushStyle != BrushStyle::BS_NULL), "emfio", "TODO: Brush style not implemented. Please fill the bug report" ); } break; case W_META_CREATEFONTINDIRECT: { Size aFontSize; char lfFaceName[LF_FACESIZE+1]; sal_Int16 lfEscapement = 0; sal_Int16 lfOrientation = 0; sal_Int16 lfWeight = 0; LOGFONTW aLogFont; aFontSize = ReadYXExt(); mpInputStream->ReadInt16( lfEscapement ); mpInputStream->ReadInt16( lfOrientation ); mpInputStream->ReadInt16( lfWeight ); mpInputStream->ReadUChar( aLogFont.lfItalic ); mpInputStream->ReadUChar( aLogFont.lfUnderline ); mpInputStream->ReadUChar( aLogFont.lfStrikeOut ); mpInputStream->ReadUChar( aLogFont.lfCharSet ); mpInputStream->ReadUChar( aLogFont.lfOutPrecision ); mpInputStream->ReadUChar( aLogFont.lfClipPrecision ); mpInputStream->ReadUChar( aLogFont.lfQuality ); mpInputStream->ReadUChar( aLogFont.lfPitchAndFamily ); size_t nRet = mpInputStream->ReadBytes( lfFaceName, LF_FACESIZE ); lfFaceName[nRet] = 0; aLogFont.lfWidth = aFontSize.Width(); aLogFont.lfHeight = aFontSize.Height(); aLogFont.lfEscapement = lfEscapement; aLogFont.lfOrientation = lfOrientation; aLogFont.lfWeight = lfWeight; rtl_TextEncoding eCharSet; if ( ( aLogFont.lfCharSet == OEM_CHARSET ) || ( aLogFont.lfCharSet == DEFAULT_CHARSET ) ) eCharSet = osl_getThreadTextEncoding(); else eCharSet = rtl_getTextEncodingFromWindowsCharset( aLogFont.lfCharSet ); if ( eCharSet == RTL_TEXTENCODING_DONTKNOW ) eCharSet = osl_getThreadTextEncoding(); if ( eCharSet == RTL_TEXTENCODING_SYMBOL ) eCharSet = RTL_TEXTENCODING_MS_1252; size_t nLength = strlen(lfFaceName); aLogFont.alfFaceName = OUString( lfFaceName, nLength, eCharSet ); SAL_INFO("emfio", "\tFacename: " << lfFaceName); if ((nRecordSize < 12) || (nRecordSize > 28)) bRecordOk = false; else CreateObject(std::make_unique(aLogFont)); } break; case W_META_CREATEBITMAPINDIRECT: { SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAPINDIRECT is not implemented. Please fill the bug report" ); CreateObject(); } break; case W_META_CREATEBITMAP: { SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAP is not implemented. Please fill the bug report" ); CreateObject(); } break; case W_META_CREATEREGION: { SAL_WARN( "emfio", "TODO: W_META_CREATEREGION is not implemented. Please fill the bug report" ); CreateObject(); } break; case W_META_EXCLUDECLIPRECT: { if (nRecordSize != 7) bRecordOk = false; SAL_WARN("emfio", "TODO: Not working correctly. Please fill the bug report"); ExcludeClipRect(ReadRectangle()); } break; case W_META_PATBLT: { if (nRecordSize != 9) bRecordOk = false; sal_uInt32 nROP = 0; mpInputStream->ReadUInt32( nROP ); Size aSize = ReadYXExt(); WMFRasterOp nOldROP = SetRasterOp( static_cast(nROP) ); DrawRect( tools::Rectangle( ReadYX(), aSize ), false ); SetRasterOp( nOldROP ); } break; case W_META_SELECTCLIPREGION: { if (nRecordSize != 4) bRecordOk = false; sal_uInt16 nObjIndex = 0; mpInputStream->ReadUInt16( nObjIndex ); SAL_WARN( "emfio", "TODO: W_META_SELECTCLIPREGION is not implemented. Please fill the bug report" ); if ( !nObjIndex ) { tools::PolyPolygon aEmptyPolyPoly; SetClipPath( aEmptyPolyPoly, RegionMode::RGN_COPY, true ); } } break; case W_META_ESCAPE: { sal_uInt64 nMetaRecSize = static_cast(nRecordSize - 2) * 2; sal_uInt64 nMetaRecEndPos = mpInputStream->Tell() + nMetaRecSize; // taking care that nRecordSize does not exceed the maximal stream position if (nMetaRecEndPos > mnEndPos) { mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); break; } sal_uInt16 nMode = 0, nLen = 0; mpInputStream->ReadUInt16(nMode).ReadUInt16(nLen); if (nRecordSize != ((nLen + 1u) >> 1u) + 5u) { bRecordOk = false; break; } if ((nMode == W_MFCOMMENT) && (nLen >= 4)) { sal_uInt32 nNewMagic = 0; // we have to read int32 for // META_ESCAPE_ENHANCED_METAFILE CommentIdentifier mpInputStream->ReadUInt32(nNewMagic); if (nNewMagic == 0x2c2a4f4f && nLen >= 14) { sal_uInt16 nMagic2 = 0; mpInputStream->ReadUInt16(nMagic2); if (nMagic2 == 0x0a) // 2nd half of magic { // continue with private escape sal_uInt32 nCheck = 0, nEsc = 0; mpInputStream->ReadUInt32(nCheck).ReadUInt32(nEsc); sal_uInt32 nEscLen = nLen - 14; if (nEscLen <= (nRecordSize * 2)) { #ifdef OSL_BIGENDIAN sal_uInt32 nTmp = OSL_SWAPDWORD(nEsc); sal_uInt32 nCheckSum = rtl_crc32(0, &nTmp, 4); #else sal_uInt32 nCheckSum = rtl_crc32(0, &nEsc, 4); #endif std::unique_ptr pData; if ((static_cast(nEscLen) + mpInputStream->Tell()) > nMetaRecEndPos) { mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); break; } if (nEscLen > 0) { pData.reset(new sal_Int8[nEscLen]); mpInputStream->ReadBytes(pData.get(), nEscLen); nCheckSum = rtl_crc32(nCheckSum, pData.get(), nEscLen); } if (nCheck == nCheckSum) { switch (nEsc) { case PRIVATE_ESCAPE_UNICODE: { // we will use text instead of polygons only if we have the correct font if (Application::GetDefaultDevice()->IsFontAvailable( GetFont().GetFamilyName())) { Point aPt; sal_uInt32 nStringLen, nDXCount; KernArray aDXAry; SvMemoryStream aMemoryStream(nEscLen); aMemoryStream.WriteBytes(pData.get(), nEscLen); aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN); sal_Int32 nTmpX(0), nTmpY(0); aMemoryStream.ReadInt32(nTmpX) .ReadInt32(nTmpY) .ReadUInt32(nStringLen); aPt.setX(nTmpX); aPt.setY(nTmpY); if ((static_cast(nStringLen) * sizeof(sal_Unicode)) < (nEscLen - aMemoryStream.Tell())) { OUString aString = read_uInt16s_ToOUString( aMemoryStream, nStringLen); aMemoryStream.ReadUInt32(nDXCount); if ((static_cast(nDXCount) * sizeof(sal_Int32)) >= (nEscLen - aMemoryStream.Tell())) nDXCount = 0; if (nDXCount) aDXAry.resize(nDXCount); for (sal_uInt32 i = 0; i < nDXCount; i++) { sal_Int32 val; aMemoryStream.ReadInt32(val); aDXAry.set(i, val); } aMemoryStream.ReadUInt32(mnSkipActions); DrawText(aPt, aString, aDXAry.empty() ? nullptr : &aDXAry); } } } break; } } } } } else if ((nNewMagic == static_cast(0x43464D57)) && (nLen >= 34) && (static_cast(nLen + 10) <= static_cast(nRecordSize * 2))) { sal_uInt32 nComType = 0, nVersion = 0, nFlags = 0, nComRecCount = 0, nCurRecSize = 0, nRemainingSize = 0, nEMFTotalSize = 0; sal_uInt16 nCheck = 0; mpInputStream->ReadUInt32(nComType) .ReadUInt32(nVersion) .ReadUInt16(nCheck) .ReadUInt32(nFlags) .ReadUInt32(nComRecCount) .ReadUInt32(nCurRecSize) .ReadUInt32(nRemainingSize) .ReadUInt32( nEMFTotalSize); // the nRemainingSize is not mentioned in MSDN documentation // but it seems to be required to read in data produced by OLE if (nComType == 0x01 && nVersion == 0x10000 && nComRecCount) { if (!mnEMFRec) { // first EMF comment mnEMFRecCount = nComRecCount; mnEMFSize = nEMFTotalSize; if (mnEMFSize > mpInputStream->remainingSize()) { SAL_WARN("emfio", "emf size claims to be larger than remaining data"); mpEMFStream.reset(); } else mpEMFStream = std::vector(); } else if ((mnEMFRecCount != nComRecCount) || (mnEMFSize != nEMFTotalSize)) // add additional checks here { // total records should be the same as in previous comments mnEMFRecCount = 0xFFFFFFFF; mpEMFStream.reset(); } mnEMFRec++; if (mpEMFStream && nCurRecSize + 34 > nLen) { mnEMFRecCount = 0xFFFFFFFF; mpEMFStream.reset(); } if (mpEMFStream && nCurRecSize > mpInputStream->remainingSize()) { SAL_WARN("emfio", "emf record size claims to be larger than remaining data"); mnEMFRecCount = 0xFFFFFFFF; mpEMFStream.reset(); } if (mpEMFStream) { std::vector aBuf(nCurRecSize); sal_uInt32 nCount = mpInputStream->ReadBytes(aBuf.data(), nCurRecSize); if (nCount == nCurRecSize) { mpEMFStream->insert(mpEMFStream->end(), aBuf.begin(), aBuf.end()); } } } } } } break; case W_META_SETRELABS: case W_META_SETPOLYFILLMODE: case W_META_SETSTRETCHBLTMODE: case W_META_SETTEXTCHAREXTRA: case W_META_SETTEXTJUSTIFICATION: case W_META_FLOODFILL: case W_META_FILLREGION: case W_META_FRAMEREGION: case W_META_INVERTREGION: case W_META_PAINTREGION: case W_META_DRAWTEXT: case W_META_SETMAPPERFLAGS: case W_META_SETDIBTODEV: case W_META_REALIZEPALETTE: case W_META_ANIMATEPALETTE: case W_META_SETPALENTRIES: case W_META_RESIZEPALETTE: case W_META_EXTFLOODFILL: case W_META_RESETDC: case W_META_STARTDOC: case W_META_STARTPAGE: case W_META_ENDPAGE: case W_META_ABORTDOC: case W_META_ENDDOC: { SAL_WARN("emfio", "TODO: WMF record not implemented: " << record_type_name(nFunc)); } break; default: { SAL_WARN("emfio", "Unknown Meta Action: 0x" << std::hex << nFunc << std::dec); } } if (!bRecordOk) { SAL_WARN("emfio", "WMF validation failed, record: " << record_type_name(nFunc) << ", size: " << nRecordSize); mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); } // tdf#127471 maScaledFontHelper.applyAlternativeFontScale(); } const tools::Long aMaxWidth = 1024; bool WmfReader::ReadHeader() { sal_uInt64 const nStrmPos = mpInputStream->Tell(); sal_uInt32 nPlaceableMetaKey(0); // if available read the METAFILEHEADER mpInputStream->ReadUInt32( nPlaceableMetaKey ); if (!mpInputStream->good()) return false; tools::Rectangle aPlaceableBound; mbPlaceable = nPlaceableMetaKey == 0x9ac6cdd7L; SAL_INFO("emfio", "Placeable: \"" << (mbPlaceable ? "yes" : "no") << "\""); if (mbPlaceable) { //TODO do some real error handling here sal_Int16 nVal(0); // Skip reserved bytes mpInputStream->SeekRel(2); // BoundRect // These are simply ignored for now mpInputStream->ReadInt16( nVal ); aPlaceableBound.SetLeft( nVal ); mpInputStream->ReadInt16( nVal ); aPlaceableBound.SetTop( nVal ); mpInputStream->ReadInt16( nVal ); aPlaceableBound.SetRight( nVal ); mpInputStream->ReadInt16( nVal ); aPlaceableBound.SetBottom( nVal ); // inch mpInputStream->ReadUInt16( mnUnitsPerInch ); // reserved mpInputStream->SeekRel( 4 ); // Skip and don't check the checksum mpInputStream->SeekRel( 2 ); // Skip wmf header mpInputStream->Seek( nStrmPos + 40 ); // set the streampos to the start of the metaactions GetPlaceableBound( aPlaceableBound, mpInputStream ); // Go back to the place after placeable header mpInputStream->Seek( nStrmPos + 22); } else { // Default is 1440, but it is set to 96 to show the wmf larger mnUnitsPerInch = 96; if (mpExternalHeader != nullptr && mpExternalHeader->xExt > 0 && mpExternalHeader->yExt > 0 && (mpExternalHeader->mapMode == MappingMode::MM_ISOTROPIC || mpExternalHeader->mapMode == MappingMode::MM_ANISOTROPIC)) { // #n417818#: If we have an external header then overwrite the bounds! tools::Rectangle aExtRect(0, 0, o3tl::convert(mpExternalHeader->xExt, o3tl::Length::mm100, o3tl::Length::px), o3tl::convert(mpExternalHeader->yExt, o3tl::Length::mm100, o3tl::Length::px)); aPlaceableBound = aExtRect; SAL_INFO("emfio", "External header size " " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top() << " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom()); SetMapMode(static_cast(mpExternalHeader->mapMode));; } else { mpInputStream->Seek(nStrmPos + 18); // set the streampos to the start of the metaactions GetPlaceableBound(aPlaceableBound, mpInputStream); // The image size is not known so normalize the calculated bounds so that the // resulting image is not too big if (aPlaceableBound.GetWidth() > aMaxWidth) { const double fMaxWidth = static_cast(aMaxWidth); double fRatio = aPlaceableBound.GetWidth() / fMaxWidth; // changing mnUnitsPerInch as a tool to scale wmf mnUnitsPerInch *= fRatio; } SAL_INFO("emfio", "Placeable bounds " " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top() << " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom()); } mpInputStream->Seek( nStrmPos ); } SetWinOrg( aPlaceableBound.TopLeft() ); Size aWMFSize( std::abs( aPlaceableBound.GetWidth() ), std::abs( aPlaceableBound.GetHeight() ) ); SetWinExt( aWMFSize ); SAL_INFO("emfio", "WMF size w: " << aWMFSize.Width() << " h: " << aWMFSize.Height()); Size aDevExt( 10000, 10000 ); if( ( std::abs( aWMFSize.Width() ) > 1 ) && ( std::abs( aWMFSize.Height() ) > 1 ) ) { const Fraction aFrac( 1, mnUnitsPerInch); MapMode aWMFMap( MapUnit::MapInch, Point(), aFrac, aFrac ); Size aSize100(OutputDevice::LogicToLogic(aWMFSize, aWMFMap, MapMode(MapUnit::Map100thMM))); aDevExt = Size( std::abs( aSize100.Width() ), std::abs( aSize100.Height() ) ); } SetDevExt( aDevExt ); SAL_INFO("emfio", "Dev size w: " << aDevExt.Width() << " h: " << aDevExt.Height()); // read the METAHEADER sal_uInt32 nMetaKey(0); mpInputStream->ReadUInt32( nMetaKey ); // type and headersize if (!mpInputStream->good()) return false; if (nMetaKey != 0x00090001) { sal_uInt16 aNextWord(0); mpInputStream->ReadUInt16( aNextWord ); if (nMetaKey != 0x10000 || aNextWord != 0x09) { mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); return false; } } mpInputStream->SeekRel( 2 ); // Version (of Windows) mpInputStream->SeekRel( 4 ); // Size (of file in words) mpInputStream->SeekRel( 2 ); // NoObjects (maximum number of simultaneous objects) mpInputStream->SeekRel( 4 ); // MaxRecord (size of largest record in words) mpInputStream->SeekRel( 2 ); // NoParameters (Unused return mpInputStream->good(); } void WmfReader::ReadWMF() { sal_uInt16 nFunction; mnSkipActions = 0; mpEMFStream.reset(); mnEMFRecCount = 0; mnEMFRec = 0; mnEMFSize = 0; SetMapMode( MappingMode::MM_ANISOTROPIC ); SetWinOrg( Point() ); SetWinExt( Size( 1, 1 ) ); SetDevExt( Size( 10000, 10000 ) ); mnEndPos=mpInputStream->TellEnd(); mpInputStream->Seek( mnStartPos ); if ( ReadHeader( ) ) { auto nPos = mpInputStream->Tell(); if( mnEndPos - mnStartPos ) { bool bEMFAvailable = false; while( !mpInputStream->eof() ) { mpInputStream->ReadUInt32(mnRecSize).ReadUInt16( nFunction ); if (!mpInputStream->good() || (mnRecSize < 3) || (nFunction == W_META_EOF)) { if (mpInputStream->eof()) mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); break; } const sal_uInt32 nAvailableBytes = mnEndPos - nPos; const sal_uInt32 nMaxPossibleRecordSize = nAvailableBytes/2; if (mnRecSize > nMaxPossibleRecordSize) { mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); break; } if ( !bEMFAvailable ) { if( !maBmpSaveList.empty() && ( nFunction != W_META_STRETCHDIB ) && ( nFunction != W_META_DIBBITBLT ) && ( nFunction != W_META_DIBSTRETCHBLT ) ) { ResolveBitmapActions( maBmpSaveList ); } if ( !mnSkipActions) ReadRecordParams( mnRecSize, nFunction ); else mnSkipActions--; if(mpEMFStream && mnEMFRecCount == mnEMFRec) { GDIMetaFile aMeta; SvMemoryStream aStream(mpEMFStream->data(), mpEMFStream->size(), StreamMode::STD_READ); std::unique_ptr pEMFReader(std::make_unique(aStream, aMeta)); pEMFReader->SetEnableEMFPlus(mbEnableEMFPlus); bEMFAvailable = pEMFReader->ReadEnhWMF(); pEMFReader.reset(); // destroy first!!! if( bEMFAvailable ) { AddFromGDIMetaFile( aMeta ); SetrclFrame( tools::Rectangle( Point(0, 0), aMeta.GetPrefSize())); // the stream needs to be set to the wmf end position, // otherwise the GfxLink that is created will be incorrect // (leading to graphic loss after swapout/swapin). // so we will proceed normally, but are ignoring further wmf // records } else { // something went wrong // continue with WMF, don't try this again mpEMFStream.reset(); } } } nPos += mnRecSize * 2; mpInputStream->Seek(nPos); } } else mpInputStream->SetError( SVSTREAM_GENERALERROR ); if( !mpInputStream->GetError() && !maBmpSaveList.empty() ) ResolveBitmapActions( maBmpSaveList ); } if ( mpInputStream->GetError() ) mpInputStream->Seek( mnStartPos ); } void WmfReader::GetPlaceableBound( tools::Rectangle& rPlaceableBound, SvStream* pStm ) { bool bRet = true; tools::Rectangle aBound; aBound.SetLeft( RECT_MAX ); aBound.SetTop( RECT_MAX ); aBound.SetRight( RECT_MIN ); aBound.SetBottom( RECT_MIN ); bool bBoundsDetermined = false; auto nPos = pStm->Tell(); auto nEnd = nPos + pStm->remainingSize(); Point aWinOrg(0,0); std::optional aWinExt; Point aViewportOrg(0,0); std::optional aViewportExt; MappingMode eMapMode = MappingMode::MM_ANISOTROPIC; if (nEnd - nPos) { sal_uInt16 nFunction; sal_uInt32 nRSize; while( bRet ) { pStm->ReadUInt32( nRSize ).ReadUInt16( nFunction ); if( pStm->GetError() ) { bRet = false; break; } else if (pStm->eof() || nRSize < 3) { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; break; } else if (nFunction == W_META_EOF) { break; } switch( nFunction ) { case W_META_SETWINDOWORG: { aWinOrg = ReadYX(); } break; case W_META_SETWINDOWEXT: { sal_Int16 nWidth(0), nHeight(0); pStm->ReadInt16(nHeight); pStm->ReadInt16(nWidth); aWinExt = Size(nWidth, nHeight); } break; case W_META_SETVIEWPORTORG: { aViewportOrg = ReadYX(); } break; case W_META_SETVIEWPORTEXT: { sal_Int16 nWidth(0), nHeight(0); pStm->ReadInt16(nHeight); pStm->ReadInt16(nWidth); aViewportExt = Size(nWidth, nHeight); } break; case W_META_SETMAPMODE: { sal_uInt16 nMapMode(0); pStm->ReadUInt16(nMapMode); eMapMode = static_cast(nMapMode); } break; case W_META_MOVETO: case W_META_LINETO: { GetWinExtMax( ReadYX(), aBound, eMapMode ); bBoundsDetermined = true; } break; case W_META_RECTANGLE: case W_META_INTERSECTCLIPRECT: case W_META_EXCLUDECLIPRECT: case W_META_ELLIPSE: { GetWinExtMax( ReadRectangle(), aBound, eMapMode ); bBoundsDetermined = true; } break; case W_META_ROUNDRECT: { ReadYXExt(); // size GetWinExtMax( ReadRectangle(), aBound, eMapMode ); bBoundsDetermined = true; } break; case W_META_ARC: case W_META_PIE: case W_META_CHORD: { ReadYX(); // end ReadYX(); // start GetWinExtMax( ReadRectangle(), aBound, eMapMode ); bBoundsDetermined = true; } break; case W_META_POLYGON: { bool bRecordOk = true; sal_uInt16 nPoints(0); pStm->ReadUInt16( nPoints ); if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; } else { for(sal_uInt16 i = 0; i < nPoints; i++ ) { GetWinExtMax( ReadPoint(), aBound, eMapMode ); bBoundsDetermined = true; } } bRecordOk &= pStm->good(); SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide"); if (!bRecordOk) { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; break; } } break; case W_META_POLYPOLYGON: { bool bRecordOk = true; sal_uInt16 nPoly(0), nPoints(0); pStm->ReadUInt16(nPoly); if (nPoly > pStm->remainingSize() / sizeof(sal_uInt16)) { bRecordOk = false; } else { for(sal_uInt16 i = 0; i < nPoly; i++ ) { sal_uInt16 nP = 0; pStm->ReadUInt16( nP ); if (nP > SAL_MAX_UINT16 - nPoints) { bRecordOk = false; break; } nPoints += nP; } } SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle"); bRecordOk &= pStm->good(); if (!bRecordOk) { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; break; } if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; } else { for (sal_uInt16 i = 0; i < nPoints; i++ ) { GetWinExtMax( ReadPoint(), aBound, eMapMode ); bBoundsDetermined = true; } } SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record claimed more points than the stream can provide"); bRecordOk &= pStm->good(); if (!bRecordOk) { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; break; } } break; case W_META_POLYLINE: { bool bRecordOk = true; sal_uInt16 nPoints(0); pStm->ReadUInt16(nPoints); if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) { bRecordOk = false; } else { for (sal_uInt16 i = 0; i < nPoints; ++i) { GetWinExtMax( ReadPoint(), aBound, eMapMode ); bBoundsDetermined = true; } } SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide"); bRecordOk &= pStm->good(); if (!bRecordOk) { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; break; } } break; case W_META_SETPIXEL: { ReadColor(); GetWinExtMax( ReadYX(), aBound, eMapMode ); bBoundsDetermined = true; } break; case W_META_TEXTOUT: { sal_uInt16 nLength(0); pStm->ReadUInt16( nLength ); // todo: we also have to take care of the text width if ( nLength ) { pStm->SeekRel( ( nLength + 1 ) &~ 1 ); GetWinExtMax( ReadYX(), aBound, eMapMode ); bBoundsDetermined = true; } } break; case W_META_EXTTEXTOUT: { sal_uInt16 nLen(0), nOptions; Point aPosition = ReadYX(); pStm->ReadUInt16( nLen ).ReadUInt16( nOptions ); // todo: we also have to take care of the text width if( nLen ) { GetWinExtMax( aPosition, aBound, eMapMode ); bBoundsDetermined = true; } } break; case W_META_BITBLT: case W_META_DIBBITBLT: case W_META_DIBSTRETCHBLT: case W_META_STRETCHBLT: case W_META_STRETCHDIB: { sal_uInt32 nRasterOperation; sal_Int16 nYSrc, nXSrc; sal_uInt16 nColorUsage; pStm->ReadUInt32( nRasterOperation ); if( nFunction == W_META_STRETCHDIB ) pStm->ReadUInt16( nColorUsage ); if( nFunction == W_META_DIBSTRETCHBLT || nFunction == W_META_STRETCHBLT || nFunction == W_META_STRETCHDIB ) { sal_Int16 nSrcHeight, nSrcWidth; pStm->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth ); } // nYSrc and nXSrc is the offset of the first pixel pStm->ReadInt16( nYSrc ).ReadInt16( nXSrc ); const bool bNoSourceBitmap = ( nFunction != W_META_STRETCHDIB ) && ( nRSize == ( ( static_cast< sal_uInt32 >( nFunction ) >> 8 ) + 3 ) ); if ( bNoSourceBitmap ) mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) Size aDestSize( ReadYXExt() ); if ( aDestSize.Width() && aDestSize.Height() ) // #92623# do not try to read buggy bitmaps { tools::Rectangle aDestRect( ReadYX(), aDestSize ); GetWinExtMax( aDestRect, aBound, eMapMode ); bBoundsDetermined = true; } } break; case W_META_PATBLT: { sal_uInt32 nROP(0); pStm->ReadUInt32( nROP ); Size aSize = ReadYXExt(); GetWinExtMax( tools::Rectangle( ReadYX(), aSize ), aBound, eMapMode ); bBoundsDetermined = true; } break; } const auto nAvailableBytes = nEnd - nPos; const auto nMaxPossibleRecordSize = nAvailableBytes/2; if (nRSize <= nMaxPossibleRecordSize) { nPos += nRSize * 2; pStm->Seek(nPos); } else { pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; } } } else { pStm->SetError( SVSTREAM_GENERALERROR ); bRet = false; } if (!bRet) { SAL_WARN("emfio", "Unable to calculate Placeable Bounds"); return; } if (aWinExt) { rPlaceableBound = tools::Rectangle(aWinOrg, *aWinExt); if (mbPlaceable && eMapMode == MM_ANISOTROPIC) { // It seems that (in MM_ANISOTROPIC WMFs) the "inch" field (PPI) in META_PLACEABLE is // ignored and instead competitor office suites decide what it should be arbitrarily // Could have to do with MM_ANISOTROPICs definition: // Logical units are mapped to arbitrary units with arbitrarily scaled axes. // The issue is that when PPI is bigger than the window size, the image appears // tiny (smaller than an inch squared). // A solution is to scale PPI down in such images to an arbitrary amount that makes // the image visible: auto nWidth = rPlaceableBound.GetWidth(); auto nHeight = rPlaceableBound.GetHeight(); if (mnUnitsPerInch > nWidth && mnUnitsPerInch > nHeight) mnUnitsPerInch = std::max(nWidth, nHeight); } SAL_INFO("emfio", "Window dimension " " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); } else if (aViewportExt) { rPlaceableBound = tools::Rectangle(aViewportOrg, *aViewportExt); SAL_INFO("emfio", "Viewport dimension " " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); } else if (bBoundsDetermined) { rPlaceableBound = aBound; SAL_INFO("emfio", "Determined dimension " " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); } else { rPlaceableBound.SetLeft( 0 ); rPlaceableBound.SetTop( 0 ); rPlaceableBound.SetRight( aMaxWidth ); rPlaceableBound.SetBottom( aMaxWidth ); SAL_INFO("emfio", "Default dimension " " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); } } WmfReader::WmfReader(SvStream& rStreamWMF, GDIMetaFile& rGDIMetaFile, const WmfExternal* pExternalHeader) : MtfTools(rGDIMetaFile, rStreamWMF) , mnUnitsPerInch(96) , mnRecSize(0) , mbPlaceable(false) , mnEMFRecCount(0) , mnEMFRec(0) , mnEMFSize(0) , mnSkipActions(0) , mpExternalHeader(pExternalHeader) { } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */