tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / emfio / source / reader / mtftools.cxx
blob6d920a2ce5a43f91543962171720a4f4aeae6e57
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <mtftools.hxx>
22 #include <cstdlib>
23 #include <memory>
24 #include <basegfx/matrix/b2dhommatrix.hxx>
25 #include <basegfx/polygon/b2dpolypolygontools.hxx>
26 #include <vcl/metric.hxx>
27 #include <vcl/graphictools.hxx>
28 #include <vcl/BitmapTools.hxx>
29 #include <vcl/metaact.hxx>
30 #include <vcl/canvastools.hxx>
31 #include <vcl/svapp.hxx>
32 #include <tools/stream.hxx>
33 #include <rtl/tencinfo.h>
34 #include <sal/log.hxx>
35 #include <osl/diagnose.h>
36 #include <vcl/virdev.hxx>
37 #include <o3tl/safeint.hxx>
38 #include <comphelper/configuration.hxx>
39 #include <unotools/defaultencoding.hxx>
40 #include <unotools/wincodepage.hxx>
42 #if OSL_DEBUG_LEVEL > 1
43 #define EMFP_DEBUG(x) x
44 #else
45 #define EMFP_DEBUG(x)
46 #endif
48 namespace emfio
50 SvStream& operator >> (SvStream& rInStream, XForm& rXForm)
52 if (sizeof(float) != 4)
54 OSL_FAIL("EmfReader::sizeof( float ) != 4");
55 rXForm = XForm();
57 else
59 rInStream.ReadFloat(rXForm.eM11);
60 rInStream.ReadFloat(rXForm.eM12);
61 rInStream.ReadFloat(rXForm.eM21);
62 rInStream.ReadFloat(rXForm.eM22);
63 rInStream.ReadFloat(rXForm.eDx);
64 rInStream.ReadFloat(rXForm.eDy);
65 if (std::isnan(rXForm.eM11) ||
66 std::isnan(rXForm.eM12) ||
67 std::isnan(rXForm.eM21) ||
68 std::isnan(rXForm.eM22) ||
69 std::isnan(rXForm.eDx) ||
70 std::isnan(rXForm.eDy))
72 SAL_WARN("emfio", "XForm member isnan, ignoring");
73 rXForm = XForm();
76 return rInStream;
79 void WinMtfClipPath::intersectClip( const basegfx::B2DPolyPolygon& rPolyPolygon )
81 maClip.intersectPolyPolygon(rPolyPolygon);
84 void WinMtfClipPath::excludeClip( const basegfx::B2DPolyPolygon& rPolyPolygon )
86 maClip.subtractPolyPolygon(rPolyPolygon);
89 void WinMtfClipPath::setClipPath( const basegfx::B2DPolyPolygon& rB2DPoly, RegionMode nClippingMode )
91 switch ( nClippingMode )
93 case RegionMode::RGN_OR :
94 maClip.unionPolyPolygon(rB2DPoly);
95 break;
96 case RegionMode::RGN_XOR :
97 maClip.xorPolyPolygon(rB2DPoly);
98 break;
99 case RegionMode::RGN_DIFF :
100 maClip.subtractPolyPolygon(rB2DPoly);
101 break;
102 case RegionMode::RGN_AND :
103 maClip.intersectPolyPolygon(rB2DPoly);
104 break;
105 case RegionMode::RGN_COPY :
106 maClip = basegfx::utils::B2DClipState(rB2DPoly);
107 break;
111 void WinMtfClipPath::moveClipRegion( const Size& rSize )
113 basegfx::B2DHomMatrix aTranslate;
114 aTranslate.translate(rSize.Width(), rSize.Height());
115 maClip.transform(aTranslate);
118 void WinMtfClipPath::setDefaultClipPath()
120 // Empty clip region - everything visible
121 maClip = basegfx::utils::B2DClipState();
124 basegfx::B2DPolyPolygon const & WinMtfClipPath::getClipPath() const
126 return maClip.getClipPoly();
129 void WinMtfPathObj::AddPoint( const Point& rPoint )
131 if ( bClosed )
132 Insert( tools::Polygon() );
133 tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
134 rPoly.Insert( rPoly.GetSize(), rPoint );
135 bClosed = false;
138 void WinMtfPathObj::AddPolyLine( const tools::Polygon& rPolyLine )
140 if ( bClosed )
141 Insert( tools::Polygon() );
142 tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
143 rPoly.Insert( rPoly.GetSize(), rPolyLine );
144 bClosed = false;
147 void WinMtfPathObj::AddPolygon( const tools::Polygon& rPoly )
149 Insert( rPoly );
150 bClosed = true;
153 void WinMtfPathObj::AddPolyPolygon( const tools::PolyPolygon& rPolyPoly )
155 sal_uInt16 i, nCount = rPolyPoly.Count();
156 for ( i = 0; i < nCount; i++ )
157 Insert( rPolyPoly[ i ] );
158 bClosed = true;
161 void WinMtfPathObj::ClosePath()
163 if ( Count() )
165 tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
166 if ( rPoly.GetSize() > 2 )
168 Point aFirst( rPoly[ 0 ] );
169 if ( aFirst != rPoly[ rPoly.GetSize() - 1 ] )
170 rPoly.Insert( rPoly.GetSize(), aFirst );
173 bClosed = true;
176 WinMtfFontStyle::WinMtfFontStyle( LOGFONTW const & rFont )
178 rtl_TextEncoding eCharSet;
179 if ((rFont.alfFaceName == "Symbol")
180 || (rFont.alfFaceName == "MT Extra"))
181 eCharSet = RTL_TEXTENCODING_SYMBOL;
182 else if ((rFont.lfCharSet == DEFAULT_CHARSET) || (rFont.lfCharSet == OEM_CHARSET))
183 eCharSet = utl_getWinTextEncodingFromLangStr(utl_getLocaleForGlobalDefaultEncoding(),
184 rFont.lfCharSet == OEM_CHARSET);
185 else
186 eCharSet = rtl_getTextEncodingFromWindowsCharset( rFont.lfCharSet );
187 if ( eCharSet == RTL_TEXTENCODING_DONTKNOW )
188 eCharSet = RTL_TEXTENCODING_MS_1252;
189 aFont.SetCharSet( eCharSet );
190 aFont.SetFamilyName( rFont.alfFaceName );
191 FontFamily eFamily;
192 switch ( rFont.lfPitchAndFamily >> 4 & 0x0f )
194 case FamilyFont::FF_ROMAN:
195 eFamily = FAMILY_ROMAN;
196 break;
198 case FamilyFont::FF_SWISS:
199 eFamily = FAMILY_SWISS;
200 break;
202 case FamilyFont::FF_MODERN:
203 eFamily = FAMILY_MODERN;
204 break;
206 case FamilyFont::FF_SCRIPT:
207 eFamily = FAMILY_SCRIPT;
208 break;
210 case FamilyFont::FF_DECORATIVE:
211 eFamily = FAMILY_DECORATIVE;
212 break;
214 default:
215 eFamily = FAMILY_DONTKNOW;
216 break;
218 aFont.SetFamily( eFamily );
220 FontPitch ePitch;
221 switch ( rFont.lfPitchAndFamily & 0x0f )
223 case FIXED_PITCH:
224 ePitch = PITCH_FIXED;
225 break;
227 case DEFAULT_PITCH:
228 case VARIABLE_PITCH:
229 default:
230 ePitch = PITCH_VARIABLE;
231 break;
233 aFont.SetPitch( ePitch );
235 FontWeight eWeight;
236 if (rFont.lfWeight == 0) // default weight SHOULD be used
237 eWeight = WEIGHT_DONTKNOW;
238 else if (rFont.lfWeight <= FW_THIN)
239 eWeight = WEIGHT_THIN;
240 else if( rFont.lfWeight <= FW_ULTRALIGHT )
241 eWeight = WEIGHT_ULTRALIGHT;
242 else if( rFont.lfWeight <= FW_LIGHT )
243 eWeight = WEIGHT_LIGHT;
244 else if( rFont.lfWeight < FW_MEDIUM )
245 eWeight = WEIGHT_NORMAL;
246 else if( rFont.lfWeight == FW_MEDIUM )
247 eWeight = WEIGHT_MEDIUM;
248 else if( rFont.lfWeight <= FW_SEMIBOLD )
249 eWeight = WEIGHT_SEMIBOLD;
250 else if( rFont.lfWeight <= FW_BOLD )
251 eWeight = WEIGHT_BOLD;
252 else if( rFont.lfWeight <= FW_ULTRABOLD )
253 eWeight = WEIGHT_ULTRABOLD;
254 else
255 eWeight = WEIGHT_BLACK;
256 aFont.SetWeight( eWeight );
258 if( rFont.lfItalic )
259 aFont.SetItalic( ITALIC_NORMAL );
261 if( rFont.lfUnderline )
262 aFont.SetUnderline( LINESTYLE_SINGLE );
264 if( rFont.lfStrikeOut )
265 aFont.SetStrikeout( STRIKEOUT_SINGLE );
267 aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rFont.lfEscapement)) );
269 Size aFontSize( rFont.lfWidth, rFont.lfHeight );
270 if ( rFont.lfHeight > 0 )
272 // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
273 SolarMutexGuard aGuard;
274 ScopedVclPtrInstance< VirtualDevice > pVDev;
275 // converting the cell height into a font height
276 aFont.SetFontSize( aFontSize );
277 pVDev->SetFont( aFont );
278 FontMetric aMetric( pVDev->GetFontMetric() );
279 tools::Long nHeight = aMetric.GetAscent() + aMetric.GetDescent();
280 if (nHeight)
282 double fHeight = (static_cast<double>(aFontSize.Height()) * rFont.lfHeight ) / nHeight;
283 aFontSize.setHeight( static_cast<sal_Int32>( fHeight + 0.5 ) );
287 // Convert height to positive
288 aFontSize.setHeight( std::abs(aFontSize.Height()) );
289 aFont.SetFontSize(aFontSize);
291 // tdf#127471 adapt nFontWidth from Windows-like notation to
292 // NormedFontScaling if used for text scaling
293 #ifndef _WIN32
294 const bool bFontScaledHorizontally(aFontSize.Width() != 0 && aFontSize.Width() != aFontSize.Height());
296 if(bFontScaledHorizontally)
298 // tdf#127471 nFontWidth is the Windows FontScaling, need to convert to
299 // Non-Windowslike notation relative to FontHeight.
300 const tools::Long nAverageFontWidth(aFont.GetOrCalculateAverageFontWidth());
302 if(nAverageFontWidth > 0)
304 const double fScaleFactor(static_cast<double>(aFontSize.Height()) / static_cast<double>(nAverageFontWidth));
305 aFont.SetAverageFontWidth(static_cast<tools::Long>(static_cast<double>(aFontSize.Width()) * fScaleFactor));
308 #endif
311 WinMtfFontStyle::~WinMtfFontStyle() = default;
313 // tdf#127471
314 ScaledFontDetectCorrectHelper::ScaledFontDetectCorrectHelper()
318 void ScaledFontDetectCorrectHelper::endCurrentMetaFontAction()
320 if(maCurrentMetaFontAction.is() && !maAlternativeFontScales.empty())
322 // create average corrected FontScale value and count
323 // positive/negative hits
324 sal_uInt32 nPositive(0);
325 sal_uInt32 nNegative(0);
326 double fAverage(0.0);
328 for(double fPart : maAlternativeFontScales)
330 if(fPart < 0.0)
332 nNegative++;
333 fAverage += -fPart;
335 else
337 nPositive++;
338 fAverage += fPart;
342 fAverage /= static_cast<double>(maAlternativeFontScales.size());
344 if(nPositive >= nNegative)
346 // correction intended, it is probably an old imported file
347 maPositiveIdentifiedCases.emplace_back(maCurrentMetaFontAction, fAverage);
349 else
351 // correction not favorable in the majority of cases for this Font, still
352 // remember to have a weight in the last decision for correction
353 maNegativeIdentifiedCases.emplace_back(maCurrentMetaFontAction, fAverage);
357 maCurrentMetaFontAction.clear();
358 maAlternativeFontScales.clear();
361 void ScaledFontDetectCorrectHelper::newCurrentMetaFontAction(const rtl::Reference<MetaFontAction>& rNewMetaFontAction)
363 maCurrentMetaFontAction.clear();
364 maAlternativeFontScales.clear();
366 if(!rNewMetaFontAction.is())
367 return;
369 // check 1st criteria for FontScale active. We usually write this,
370 // so this will already sort out most situations
371 const vcl::Font& rCandidate(rNewMetaFontAction->GetFont());
373 if(0 != rCandidate.GetAverageFontWidth())
375 const tools::Long nUnscaledAverageFontWidth(rCandidate.GetOrCalculateAverageFontWidth());
377 // check 2nd (system-dependent) criteria for FontScale
378 if(nUnscaledAverageFontWidth != rCandidate.GetFontHeight())
380 // FontScale is active, remember and use as current
381 maCurrentMetaFontAction = rNewMetaFontAction;
386 void ScaledFontDetectCorrectHelper::evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength)
388 if(!maCurrentMetaFontAction.is())
389 return;
391 SolarMutexGuard aGuard; // VirtualDevice is not thread-safe
392 ScopedVclPtrInstance< VirtualDevice > pTempVirtualDevice;
394 // calculate measured TextLength
395 const vcl::Font& rFontCandidate(maCurrentMetaFontAction->GetFont());
396 pTempVirtualDevice->SetFont(rFontCandidate);
397 tools::Long nMeasuredTextLength(pTempVirtualDevice->GetTextWidth(rText));
398 // on failure, use original length
399 if (!nMeasuredTextLength)
400 nMeasuredTextLength = nImportedTextLength;
402 // compare expected and imported TextLengths
403 if (nImportedTextLength == nMeasuredTextLength)
404 return;
406 const double fFactorText(static_cast<double>(nImportedTextLength) / static_cast<double>(nMeasuredTextLength));
407 const double fFactorTextPercent(fabs(1.0 - fFactorText) * 100.0);
409 // if we assume that loaded file was written on old linux, we have to
410 // back-convert the scale value depending on which system we run
411 #ifdef _WIN32
412 // When running on Windows the value was not adapted at font import (see WinMtfFontStyle
413 // constructor), so it is still NormedFontScaling and we need to convert to Windows-style
414 // scaling
415 #else
416 // When running on unx (non-Windows) the value was already adapted at font import (see WinMtfFontStyle
417 // constructor). It was wrongly assumed to be Windows-style FontScaling, so we need to revert that
418 // to get back to the needed unx-style FontScale
419 #endif
420 // Interestingly this leads to the *same* correction, so no need to make this
421 // system-dependent (!)
422 const tools::Long nUnscaledAverageFontWidth(rFontCandidate.GetOrCalculateAverageFontWidth());
423 const tools::Long nScaledAverageFontWidth(rFontCandidate.GetAverageFontWidth());
424 const double fScaleFactor(static_cast<double>(nUnscaledAverageFontWidth) / static_cast<double>(rFontCandidate.GetFontHeight()));
425 const double fCorrectedAverageFontWidth(static_cast<double>(nScaledAverageFontWidth) * fScaleFactor);
426 tools::Long nCorrectedTextLength(0);
428 { // do in own scope, only need nUnscaledAverageFontWidth
429 vcl::Font rFontCandidate2(rFontCandidate);
430 rFontCandidate2.SetAverageFontWidth(static_cast<tools::Long>(fCorrectedAverageFontWidth));
431 pTempVirtualDevice->SetFont(rFontCandidate2);
432 nCorrectedTextLength = pTempVirtualDevice->GetTextWidth(rText);
433 // on failure, use original length
434 if (!nCorrectedTextLength)
435 nCorrectedTextLength = nImportedTextLength;
438 const double fFactorCorrectedText(static_cast<double>(nImportedTextLength) / static_cast<double>(nCorrectedTextLength));
439 const double fFactorCorrectedTextPercent(fabs(1.0 - fFactorCorrectedText) * 100.0);
441 // If FactorCorrectedText fits better than FactorText this is probably
442 // an import of an old EMF/WMF written by LibreOffice on a non-Windows (unx) system
443 // and should be corrected.
444 // Usually in tested cases this lies inside 5% of range, so detecting this just using
445 // fFactorTextPercent inside 5% -> no old file
446 // fFactorCorrectedTextPercent inside 5% -> is old file
447 // works not too bad, but there are some strange not so often used fonts where that
448 // values do deviate, so better just compare if old corrected would fit better than
449 // the uncorrected case, that is usually safe.
450 if(fFactorCorrectedTextPercent < fFactorTextPercent)
452 maAlternativeFontScales.push_back(fCorrectedAverageFontWidth);
454 else
456 // also push, but negative to remember non-fitting case
457 maAlternativeFontScales.push_back(-fCorrectedAverageFontWidth);
461 void ScaledFontDetectCorrectHelper::applyAlternativeFontScale()
463 // make sure last evtl. detected current FontAction gets added to identified cases
464 endCurrentMetaFontAction();
466 // Take final decision to correct FontScaling for this imported Metafile or not.
467 // It is possible to weight positive against negative cases, so to only finally
468 // correct when more positive cases were detected.
469 // But that would be inconsequent and wrong. *If* the detected case is an old import
470 // the whole file was written with wrong FontScale values and all Font actions
471 // need to be corrected. Thus, for now, correct all when there are/is positive
472 // cases detected.
473 // On the other hand it *may* be that for some strange fonts there is a false-positive
474 // in the positive cases, so at least insist on positive cases being more than negative.
475 // Still, do then correct *all* cases.
476 if(!maPositiveIdentifiedCases.empty()
477 && maPositiveIdentifiedCases.size() >= maNegativeIdentifiedCases.size())
479 for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maPositiveIdentifiedCases)
481 rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second));
483 for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maNegativeIdentifiedCases)
485 rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second));
489 maPositiveIdentifiedCases.clear();
490 maNegativeIdentifiedCases.clear();
493 Color MtfTools::ReadColor()
495 sal_uInt32 nColor(0);
496 mpInputStream->ReadUInt32( nColor );
497 Color aColor( COL_BLACK );
498 if ( ( nColor & 0xFFFF0000 ) == 0x01000000 )
500 size_t index = nColor & 0x0000FFFF;
501 if ( index < maPalette.aPaletteColors.size() )
502 aColor = maPalette.aPaletteColors[ index ];
503 else
504 SAL_INFO( "emfio", "\t\t Palette index out of range: " << index );
506 else
507 aColor = Color( static_cast<sal_uInt8>( nColor ), static_cast<sal_uInt8>( nColor >> 8 ), static_cast<sal_uInt8>( nColor >> 16 ) );
509 SAL_INFO("emfio", "\t\tColor: " << aColor);
510 return aColor;
513 Point MtfTools::ImplScale(const Point& rPoint) // Hack to set varying defaults for incompletely defined files.
515 if (!mbIsMapDevSet)
516 return Point(rPoint.X() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Left(),
517 rPoint.Y() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Top());
518 else
519 return rPoint;
522 Point MtfTools::ImplMap( const Point& rPt )
524 if ( mnWinExtX && mnWinExtY )
526 double fX = rPt.X();
527 double fY = rPt.Y();
529 double fX2 = fX * maXForm.eM11 + fY * maXForm.eM21 + maXForm.eDx;
530 double fY2 = fX * maXForm.eM12 + fY * maXForm.eM22 + maXForm.eDy;
532 if ( meGfxMode == GraphicsMode::GM_COMPATIBLE )
534 fX2 -= mnWinOrgX;
535 fY2 -= mnWinOrgY;
537 switch( meMapMode )
539 case MappingMode::MM_LOENGLISH :
541 fX2 = o3tl::convert(fX2, o3tl::Length::in100, o3tl::Length::mm100);
542 fY2 = o3tl::convert(-fY2, o3tl::Length::in100, o3tl::Length::mm100);
544 break;
545 case MappingMode::MM_HIENGLISH :
547 fX2 = o3tl::convert(fX2, o3tl::Length::in1000, o3tl::Length::mm100);
548 fY2 = o3tl::convert(-fY2, o3tl::Length::in1000, o3tl::Length::mm100);
550 break;
551 case MappingMode::MM_TWIPS:
553 fX2 = o3tl::convert(fX2, o3tl::Length::twip, o3tl::Length::mm100);
554 fY2 = o3tl::convert(-fY2, o3tl::Length::twip, o3tl::Length::mm100);
556 break;
557 case MappingMode::MM_LOMETRIC :
559 fX2 = o3tl::convert(fX2, o3tl::Length::mm10, o3tl::Length::mm100);
560 fY2 = o3tl::convert(-fY2, o3tl::Length::mm10, o3tl::Length::mm100);
562 break;
563 case MappingMode::MM_HIMETRIC : // in hundredth of a millimeter
565 fY2 *= -1;
567 break;
568 default :
570 if (mnPixX == 0 || mnPixY == 0)
572 SAL_WARN("emfio", "invalid scaling factor");
573 return Point();
575 else
577 if ( meMapMode != MappingMode::MM_TEXT )
579 fX2 /= mnWinExtX;
580 fY2 /= mnWinExtY;
581 fX2 *= mnDevWidth;
582 fY2 *= mnDevHeight;
584 fX2 *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
585 fY2 *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
588 break;
591 double nDevOrgX = mnDevOrgX;
592 if (mnPixX)
593 nDevOrgX *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
594 fX2 += nDevOrgX;
595 double nDevOrgY = mnDevOrgY;
596 if (mnPixY)
597 nDevOrgY *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
598 fY2 += nDevOrgY;
600 fX2 -= mrclFrame.Left();
601 fY2 -= mrclFrame.Top();
603 return Point(basegfx::fround<tools::Long>(fX2), basegfx::fround<tools::Long>(fY2));
605 else
606 return Point();
609 Size MtfTools::ImplMap(const Size& rSz, bool bDoWorldTransform)
611 if ( mnWinExtX && mnWinExtY )
613 // #i121382# apply the whole WorldTransform, else a rotation will be misinterpreted
614 double fWidth, fHeight;
615 if (bDoWorldTransform)
617 fWidth = rSz.Width() * maXForm.eM11 + rSz.Height() * maXForm.eM21;
618 fHeight = rSz.Width() * maXForm.eM12 + rSz.Height() * maXForm.eM22;
620 else
622 //take the scale, but not the rotation
623 basegfx::B2DHomMatrix aMatrix(maXForm.eM11, maXForm.eM12, 0,
624 maXForm.eM21, maXForm.eM22, 0);
625 basegfx::B2DTuple aScale, aTranslate;
626 double fRotate, fShearX;
627 if (!aMatrix.decompose(aScale, aTranslate, fRotate, fShearX))
629 aScale.setX(1.0);
630 aScale.setY(1.0);
632 fWidth = rSz.Width() * aScale.getX();
633 fHeight = rSz.Height() * aScale.getY();
636 if ( meGfxMode == GraphicsMode::GM_COMPATIBLE )
638 switch( meMapMode )
640 case MappingMode::MM_LOENGLISH :
642 fWidth = o3tl::convert(fWidth, o3tl::Length::in100, o3tl::Length::mm100);
643 fHeight = o3tl::convert(-fHeight, o3tl::Length::in100, o3tl::Length::mm100);
645 break;
646 case MappingMode::MM_HIENGLISH :
648 fWidth = o3tl::convert(fWidth, o3tl::Length::in1000, o3tl::Length::mm100);
649 fHeight = o3tl::convert(-fHeight, o3tl::Length::in1000, o3tl::Length::mm100);
651 break;
652 case MappingMode::MM_LOMETRIC :
654 fWidth = o3tl::convert(fWidth, o3tl::Length::mm10, o3tl::Length::mm100);
655 fHeight = o3tl::convert(-fHeight, o3tl::Length::mm10, o3tl::Length::mm100);
657 break;
658 case MappingMode::MM_HIMETRIC : // in hundredth of millimeters
660 fHeight *= -1;
662 break;
663 case MappingMode::MM_TWIPS:
665 fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::mm100);
666 fHeight = o3tl::convert(-fHeight, o3tl::Length::twip, o3tl::Length::mm100);
668 break;
669 default :
671 if (mnPixX == 0 || mnPixY == 0)
673 SAL_WARN("emfio", "invalid scaling factor");
674 return Size();
676 else
678 if ( meMapMode != MappingMode::MM_TEXT )
680 fWidth /= mnWinExtX;
681 fHeight /= mnWinExtY;
682 fWidth *= mnDevWidth;
683 fHeight *= mnDevHeight;
685 fWidth *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
686 fHeight *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
689 break;
692 return Size(basegfx::fround<tools::Long>(fWidth), basegfx::fround<tools::Long>(fHeight));
694 else
695 return Size();
698 tools::Rectangle MtfTools::ImplMap( const tools::Rectangle& rRect )
700 tools::Rectangle aRect;
701 aRect.SetPos(ImplMap(rRect.TopLeft()));
702 aRect.SaturatingSetSize(ImplMap(rRect.GetSize()));
703 return aRect;
706 void MtfTools::ImplMap( vcl::Font& rFont )
708 // !!! HACK: we now always set the width to zero because the OS width is interpreted differently;
709 // must later be made portable in SV (KA 1996-02-08)
710 Size aFontSize = ImplMap (rFont.GetFontSize(), false);
712 const auto nHeight = aFontSize.Height();
713 if (nHeight < 0)
714 aFontSize.setHeight( o3tl::saturating_toggle_sign(nHeight) );
716 rFont.SetFontSize( aFontSize );
718 sal_Int32 nResult;
719 const bool bFail = o3tl::checked_multiply(mnWinExtX, mnWinExtY, nResult);
720 if (!bFail && nResult < 0)
721 rFont.SetOrientation( 3600_deg10 - rFont.GetOrientation() );
724 tools::Polygon& MtfTools::ImplMap( tools::Polygon& rPolygon )
726 sal_uInt16 nPoints = rPolygon.GetSize();
727 for ( sal_uInt16 i = 0; i < nPoints; i++ )
729 rPolygon[ i ] = ImplMap( rPolygon[ i ] );
731 return rPolygon;
734 void MtfTools::ImplScale( tools::Polygon& rPolygon )
736 sal_uInt16 nPoints = rPolygon.GetSize();
737 for ( sal_uInt16 i = 0; i < nPoints; i++ )
739 rPolygon[ i ] = ImplScale( rPolygon[ i ] );
743 tools::PolyPolygon& MtfTools::ImplScale( tools::PolyPolygon& rPolyPolygon )
745 sal_uInt16 nPolys = rPolyPolygon.Count();
746 for (sal_uInt16 i = 0; i < nPolys; ++i)
748 ImplScale(rPolyPolygon[i]);
750 return rPolyPolygon;
753 tools::PolyPolygon& MtfTools::ImplMap( tools::PolyPolygon& rPolyPolygon )
755 sal_uInt16 nPolys = rPolyPolygon.Count();
756 for ( sal_uInt16 i = 0; i < nPolys; ImplMap( rPolyPolygon[ i++ ] ) ) ;
757 return rPolyPolygon;
760 void MtfTools::SelectObject( sal_uInt32 nIndex )
762 if ( nIndex & ENHMETA_STOCK_OBJECT )
764 SAL_INFO ( "emfio", "\t\t ENHMETA_STOCK_OBJECT, StockObject Enumeration: 0x" << std::hex << nIndex );
765 StockObject nStockId = static_cast<StockObject>(nIndex & 0xFF);
766 switch( nStockId )
768 case StockObject::WHITE_BRUSH :
770 maFillStyle = WinMtfFillStyle( COL_WHITE );
771 mbFillStyleSelected = true;
773 break;
774 case StockObject::LTGRAY_BRUSH :
776 maFillStyle = WinMtfFillStyle( COL_LIGHTGRAY );
777 mbFillStyleSelected = true;
779 break;
780 case StockObject::GRAY_BRUSH :
782 maFillStyle = WinMtfFillStyle( COL_GRAY );
783 mbFillStyleSelected = true;
785 break;
786 case StockObject::DKGRAY_BRUSH :
788 maFillStyle = WinMtfFillStyle( COL_GRAY7 );
789 mbFillStyleSelected = true;
791 break;
792 case StockObject::BLACK_BRUSH :
794 maFillStyle = WinMtfFillStyle( COL_BLACK );
795 mbFillStyleSelected = true;
797 break;
798 case StockObject::NULL_BRUSH :
800 maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true );
801 mbFillStyleSelected = true;
803 break;
804 case StockObject::WHITE_PEN :
806 maLineStyle = WinMtfLineStyle(COL_WHITE, PS_COSMETIC, 0);
808 break;
809 case StockObject::BLACK_PEN :
811 maLineStyle = WinMtfLineStyle(COL_BLACK, PS_COSMETIC, 0);
813 break;
814 case StockObject::NULL_PEN :
816 maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true );
818 break;
819 default:
820 break;
823 else
825 nIndex &= 0xffff; // safety check: don't allow index to be > 65535
827 GDIObj *pGDIObj = nullptr;
829 if ( nIndex < mvGDIObj.size() )
830 pGDIObj = mvGDIObj[ nIndex ].get();
832 if ( pGDIObj )
835 SAL_INFO ( "emfio", "\t\t Index: " << nIndex );
836 if (const auto pen = dynamic_cast<WinMtfLineStyle*>(pGDIObj))
838 maLineStyle = *pen;
839 SAL_INFO ( "emfio", "\t Line Style, Color: 0x" << std::hex << maLineStyle.aLineColor
840 << ", Weight: " << maLineStyle.aLineInfo.GetWidth() );
842 else if (const auto brush = dynamic_cast<WinMtfFillStyle*>(
843 pGDIObj))
845 maFillStyle = *brush;
846 mbFillStyleSelected = true;
847 SAL_INFO("emfio", "\t\tBrush Object, Index: " << nIndex << ", Color: " << maFillStyle.aFillColor);
849 else if (const auto font = dynamic_cast<WinMtfFontStyle*>(
850 pGDIObj))
852 maFont = font->aFont;
853 SAL_INFO("emfio", "\t\tFont Object, Index: " << nIndex << ", Font: " << maFont.GetFamilyName() << " " << maFont.GetStyleName());
855 else if (const auto palette = dynamic_cast<WinMtfPalette*>(
856 pGDIObj))
858 maPalette = palette->aPaletteColors;
859 SAL_INFO("emfio", "\t\tPalette Object, Index: " << nIndex << ", Number of colours: " << maPalette.aPaletteColors.size() );
862 else
864 SAL_WARN("emfio", "Warning: Unable to find Object with index:" << nIndex);
869 void MtfTools::SetTextLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
871 mnTextLayoutMode = nTextLayoutMode;
874 void MtfTools::SetArcDirection(bool bClockWise)
876 SAL_INFO("emfio", "\t\t Arc direction: " << (bClockWise ? "ClockWise" : "CounterClockWise"));
877 mbClockWiseArcDirection = bClockWise;
880 void MtfTools::SetBkMode( BackgroundMode nMode )
882 mnBkMode = nMode;
885 void MtfTools::SetBkColor( const Color& rColor )
887 maBkColor = rColor;
890 void MtfTools::SetTextColor( const Color& rColor )
892 maTextColor = rColor;
895 void MtfTools::SetTextAlign( sal_uInt32 nAlign )
897 mnTextAlign = nAlign;
900 void MtfTools::ImplResizeObjectArry( sal_uInt32 nNewEntrys )
902 mvGDIObj.resize(nNewEntrys);
905 void MtfTools::ImplDrawClippedPolyPolygon( const tools::PolyPolygon& rPolyPoly )
907 if ( !rPolyPoly.Count() )
908 return;
910 ImplSetNonPersistentLineColorTransparenz();
911 if ( rPolyPoly.Count() == 1 )
913 if ( rPolyPoly.IsRect() )
914 mpGDIMetaFile->AddAction( new MetaRectAction( rPolyPoly.GetBoundRect() ) );
915 else
917 tools::Polygon aPoly( rPolyPoly[ 0 ] );
918 sal_uInt16 nCount = aPoly.GetSize();
919 if ( nCount )
921 if ( aPoly[ nCount - 1 ] != aPoly[ 0 ] )
923 Point aPoint( aPoly[ 0 ] );
924 aPoly.Insert( nCount, aPoint );
926 mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(aPoly) ) );
930 else
931 mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
934 void MtfTools::CreateObject( std::unique_ptr<GDIObj> pObject )
936 if ( pObject )
938 const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get());
939 const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get());
941 if ( pFontStyle )
943 if (pFontStyle->aFont.GetFontHeight() == 0)
944 pFontStyle->aFont.SetFontHeight(423);
945 ImplMap(pFontStyle->aFont); // defaulting to 12pt
947 else if ( pLineStyle )
949 Size aSize(pLineStyle->aLineInfo.GetWidth(), 0);
950 aSize = ImplMap(aSize);
951 pLineStyle->aLineInfo.SetWidth(aSize.Width());
954 std::vector<std::unique_ptr<GDIObj>>::size_type nIndex;
955 for ( nIndex = 0; nIndex < mvGDIObj.size(); nIndex++ )
957 if ( !mvGDIObj[ nIndex ] )
958 break;
960 if ( nIndex == mvGDIObj.size() )
961 ImplResizeObjectArry( mvGDIObj.size() + 16 );
963 mvGDIObj[ nIndex ] = std::move(pObject);
966 void MtfTools::CreateObjectIndexed( sal_uInt32 nIndex, std::unique_ptr<GDIObj> pObject )
968 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) != 0 )
969 return;
971 nIndex &= 0xffff; // safety check: do not allow index to be > 65535
972 if ( pObject )
974 const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get());
975 const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get());
976 if ( pFontStyle )
978 if (pFontStyle->aFont.GetFontHeight() == 0)
979 pFontStyle->aFont.SetFontHeight(423);
980 ImplMap(pFontStyle->aFont);
982 else if ( pLineStyle )
984 Size aSize(pLineStyle->aLineInfo.GetWidth(), 0);
985 pLineStyle->aLineInfo.SetWidth( ImplMap(aSize).Width() );
987 if ( pLineStyle->aLineInfo.GetStyle() == LineStyle::Dash )
989 aSize.AdjustWidth(1 );
990 tools::Long nDashLen, nDotLen = ImplMap( aSize ).Width();
991 const bool bFail = o3tl::checked_multiply<tools::Long>(nDotLen, 3, nDashLen);
992 if (!bFail)
994 pLineStyle->aLineInfo.SetDistance( nDotLen );
995 pLineStyle->aLineInfo.SetDotLen( nDotLen );
996 pLineStyle->aLineInfo.SetDashLen( nDotLen * 3 );
998 else
1000 SAL_WARN("emfio", "DotLen too long: " << nDotLen);
1005 if ( nIndex >= mvGDIObj.size() )
1006 ImplResizeObjectArry( nIndex + 16 );
1008 mvGDIObj[ nIndex ] = std::move(pObject);
1011 void MtfTools::CreateObject()
1013 CreateObject(std::make_unique<GDIObj>());
1016 void MtfTools::DeleteObject( sal_uInt32 nIndex )
1018 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
1020 if ( nIndex < mvGDIObj.size() )
1022 mvGDIObj[ nIndex ].reset();
1027 void MtfTools::IntersectClipRect( const tools::Rectangle& rRect )
1029 if (comphelper::IsFuzzing())
1030 return;
1031 mbClipNeedsUpdate=true;
1032 if ((rRect.Left()-rRect.Right()==0) && (rRect.Top()-rRect.Bottom()==0))
1034 return; // empty rectangles cause trouble
1036 tools::Polygon aPoly( rRect );
1037 const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) );
1038 maClipPath.intersectClip( aPolyPolyRect.getB2DPolyPolygon() );
1041 void MtfTools::ExcludeClipRect( const tools::Rectangle& rRect )
1043 if (comphelper::IsFuzzing())
1044 return;
1045 mbClipNeedsUpdate=true;
1046 tools::Polygon aPoly( rRect );
1047 const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) );
1048 maClipPath.excludeClip( aPolyPolyRect.getB2DPolyPolygon() );
1051 void MtfTools::MoveClipRegion( const Size& rSize )
1053 if (comphelper::IsFuzzing())
1054 return;
1055 mbClipNeedsUpdate=true;
1056 maClipPath.moveClipRegion( ImplMap( rSize ) );
1059 void MtfTools::SetClipPath( const tools::PolyPolygon& rPolyPolygon, RegionMode eClippingMode, bool bIsMapped )
1061 if (comphelper::IsFuzzing())
1062 return;
1063 mbClipNeedsUpdate = true;
1064 tools::PolyPolygon aPolyPolygon(rPolyPolygon);
1066 if (!bIsMapped)
1068 if (!mbIsMapDevSet && (meMapMode == MappingMode::MM_ISOTROPIC || meMapMode == MappingMode::MM_ANISOTROPIC))
1069 aPolyPolygon = ImplScale(aPolyPolygon);
1070 else
1071 aPolyPolygon = ImplMap(aPolyPolygon);
1073 maClipPath.setClipPath(aPolyPolygon.getB2DPolyPolygon(), eClippingMode);
1076 void MtfTools::SetDefaultClipPath()
1078 mbClipNeedsUpdate = true;
1079 maClipPath.setDefaultClipPath();
1082 MtfTools::MtfTools( GDIMetaFile& rGDIMetaFile, SvStream& rStreamWMF)
1083 : mnLatestTextAlign(90),
1084 mnTextAlign(TextAlignmentMode::TA_LEFT | TextAlignmentMode::TA_TOP | TextAlignmentMode::TA_NOUPDATECP),
1085 maLatestBkColor(ColorTransparency, 0x12345678),
1086 maBkColor(COL_WHITE),
1087 mnLatestTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
1088 mnTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
1089 mnLatestBkMode(BackgroundMode::NONE),
1090 mnBkMode(BackgroundMode::OPAQUE),
1091 meLatestRasterOp(RasterOp::Invert),
1092 meRasterOp(RasterOp::OverPaint),
1093 mnRop(),
1094 meGfxMode(GraphicsMode::GM_COMPATIBLE),
1095 meMapMode(MappingMode::MM_TEXT),
1096 mnDevOrgX(0),
1097 mnDevOrgY(0),
1098 mnDevWidth(1),
1099 mnDevHeight(1),
1100 mnWinOrgX(0),
1101 mnWinOrgY(0),
1102 mnWinExtX(1),
1103 mnWinExtY(1),
1104 mnPixX(100),
1105 mnPixY(100),
1106 mnMillX(1),
1107 mnMillY(1),
1108 mpGDIMetaFile(&rGDIMetaFile),
1109 mpInputStream(&rStreamWMF),
1110 mnStartPos(0),
1111 mnEndPos(0),
1112 mbNopMode(false),
1113 mbClockWiseArcDirection(false),
1114 mbFillStyleSelected(false),
1115 mbClipNeedsUpdate(true),
1116 mbComplexClip(false),
1117 mbIsMapWinSet(false),
1118 mbIsMapDevSet(false)
1120 SvLockBytes *pLB = mpInputStream->GetLockBytes();
1122 if (pLB)
1124 pLB->SetSynchronMode();
1127 mnStartPos = mpInputStream->Tell();
1128 SetDevOrg(Point());
1130 mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) ); // The original clipregion has to be on top
1131 // of the stack so it can always be restored
1132 // this is necessary to be able to support
1133 // SetClipRgn( NULL ) and similar ClipRgn actions (SJ)
1135 maFont.SetFamilyName( u"Arial"_ustr ); // sj: #i57205#, we do have some scaling problems if using
1136 maFont.SetCharSet( RTL_TEXTENCODING_MS_1252 ); // the default font then most times a x11 font is used, we
1137 maFont.SetFontHeight( 423 ); // will prevent this defining a font
1139 maLatestLineStyle.aLineColor = Color( 0x12, 0x34, 0x56 );
1140 maLatestFillStyle.aFillColor = Color( 0x12, 0x34, 0x56 );
1142 mnRop = WMFRasterOp::Black;
1143 meRasterOp = RasterOp::OverPaint;
1144 mpGDIMetaFile->AddAction( new MetaRasterOpAction( RasterOp::OverPaint ) );
1147 MtfTools::~MtfTools() COVERITY_NOEXCEPT_FALSE
1149 mpGDIMetaFile->AddAction( new MetaPopAction() );
1150 mpGDIMetaFile->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
1151 if ( mrclFrame.IsEmpty() )
1152 mpGDIMetaFile->SetPrefSize( Size( mnDevWidth, mnDevHeight ) );
1153 else
1154 mpGDIMetaFile->SetPrefSize( mrclFrame.GetSize() );
1157 void MtfTools::UpdateClipRegion()
1159 if (!mbClipNeedsUpdate)
1160 return;
1162 mbClipNeedsUpdate = false;
1163 mbComplexClip = false;
1165 mpGDIMetaFile->AddAction( new MetaPopAction() ); // taking the original clipregion
1166 mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) );
1168 // skip for 'no clipping at all' case
1169 if( maClipPath.isEmpty() )
1170 return;
1172 const basegfx::B2DPolyPolygon& rClipPoly( maClipPath.getClipPath() );
1174 mbComplexClip = rClipPoly.count() > 1
1175 || !basegfx::utils::isRectangle(rClipPoly);
1177 // This makes cases like tdf#45820 work in reasonable time.
1178 if (mbComplexClip)
1180 mpGDIMetaFile->AddAction(
1181 new MetaISectRegionClipRegionAction(
1182 vcl::Region(rClipPoly)));
1183 mbComplexClip = false;
1185 else
1187 mpGDIMetaFile->AddAction(
1188 new MetaISectRectClipRegionAction(
1189 vcl::unotools::rectangleFromB2DRectangle(
1190 rClipPoly.getB2DRange())));
1194 void MtfTools::ImplSetNonPersistentLineColorTransparenz()
1196 WinMtfLineStyle aTransparentLine( COL_TRANSPARENT, true );
1197 if ( ! ( maLatestLineStyle == aTransparentLine ) )
1199 maLatestLineStyle = aTransparentLine;
1200 mpGDIMetaFile->AddAction( new MetaLineColorAction( aTransparentLine.aLineColor, !aTransparentLine.bTransparent ) );
1204 void MtfTools::UpdateLineStyle()
1206 if (!( maLatestLineStyle == maLineStyle ) )
1208 maLatestLineStyle = maLineStyle;
1209 mpGDIMetaFile->AddAction( new MetaLineColorAction( maLineStyle.aLineColor, !maLineStyle.bTransparent ) );
1213 void MtfTools::UpdateFillStyle()
1215 if ( !mbFillStyleSelected ) // SJ: #i57205# taking care of bkcolor if no brush is selected
1216 maFillStyle = WinMtfFillStyle( maBkColor, mnBkMode == BackgroundMode::Transparent );
1217 if (!( maLatestFillStyle == maFillStyle ) )
1219 maLatestFillStyle = maFillStyle;
1220 if (maFillStyle.aType == WinMtfFillStyleType::Solid)
1221 mpGDIMetaFile->AddAction( new MetaFillColorAction( maFillStyle.aFillColor, !maFillStyle.bTransparent ) );
1225 WMFRasterOp MtfTools::SetRasterOp( WMFRasterOp nRasterOp )
1227 WMFRasterOp nRetROP = mnRop;
1228 if ( nRasterOp != mnRop )
1230 mnRop = nRasterOp;
1232 if ( mbNopMode && ( nRasterOp != WMFRasterOp::Nop ) )
1233 { // changing modes from WMFRasterOp::Nop so set pen and brush
1234 maFillStyle = maNopFillStyle;
1235 maLineStyle = maNopLineStyle;
1236 mbNopMode = false;
1238 switch( nRasterOp )
1240 case WMFRasterOp::Not:
1241 meRasterOp = RasterOp::Invert;
1242 break;
1244 case WMFRasterOp::XorPen:
1245 meRasterOp = RasterOp::Xor;
1246 break;
1248 case WMFRasterOp::Nop:
1250 meRasterOp = RasterOp::OverPaint;
1251 if( !mbNopMode )
1253 maNopFillStyle = maFillStyle;
1254 maNopLineStyle = maLineStyle;
1255 maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true );
1256 maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true );
1257 mbNopMode = true;
1260 break;
1262 default:
1263 meRasterOp = RasterOp::OverPaint;
1264 break;
1267 if ( nRetROP != nRasterOp )
1268 mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) );
1269 return nRetROP;
1272 void MtfTools::StrokeAndFillPath( bool bStroke, bool bFill )
1274 if ( !maPathObj.Count() )
1275 return;
1277 UpdateClipRegion();
1278 UpdateLineStyle();
1279 UpdateFillStyle();
1280 if ( bFill )
1282 if ( !bStroke )
1284 mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
1285 mpGDIMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
1287 if ( maPathObj.Count() == 1 )
1288 mpGDIMetaFile->AddAction( new MetaPolygonAction( maPathObj.GetObject( 0 ) ) );
1289 else
1290 mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( maPathObj ) );
1292 if ( !bStroke )
1293 mpGDIMetaFile->AddAction( new MetaPopAction() );
1295 // tdf#142014 By default the stroke is made with hairline. If width is bigger, we need to use PolyLineAction
1296 if ( bStroke )
1298 // bFill is drawing hairstyle line. So we need to draw it only when the width is different than 0
1299 if ( !bFill || maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1301 sal_uInt16 i, nCount = maPathObj.Count();
1302 for ( i = 0; i < nCount; i++ )
1303 mpGDIMetaFile->AddAction( new MetaPolyLineAction( maPathObj[ i ], maLineStyle.aLineInfo ) );
1306 ClearPath();
1309 void MtfTools::DrawPixel( const Point& rSource, const Color& rColor )
1311 mpGDIMetaFile->AddAction( new MetaPixelAction( ImplMap( rSource), rColor ) );
1314 void MtfTools::MoveTo( const Point& rPoint, bool bRecordPath )
1316 Point aDest( ImplMap( rPoint ) );
1317 if ( bRecordPath )
1319 // fdo#57353 create new subpath for subsequent moves
1320 if ( maPathObj.Count() )
1321 if ( maPathObj[ maPathObj.Count() - 1 ].GetSize() )
1322 maPathObj.Insert( tools::Polygon() );
1323 maPathObj.AddPoint( aDest );
1325 maActPos = aDest;
1328 void MtfTools::LineTo( const Point& rPoint, bool bRecordPath )
1330 UpdateClipRegion();
1331 Point aDest( ImplMap( rPoint ) );
1332 if ( bRecordPath )
1333 maPathObj.AddPoint( aDest );
1334 else
1336 UpdateLineStyle();
1337 mpGDIMetaFile->AddAction( new MetaLineAction( maActPos, aDest, maLineStyle.aLineInfo ) );
1339 maActPos = aDest;
1342 void MtfTools::DrawRectWithBGColor(const tools::Rectangle& rRect)
1344 WinMtfFillStyle aFillStyleBackup = maFillStyle;
1345 bool bTransparentBackup = maLineStyle.bTransparent;
1346 BackgroundMode mnBkModeBackup = mnBkMode;
1348 const tools::Polygon aPoly( rRect );
1349 maLineStyle.bTransparent = true;
1350 maFillStyle = maBkColor;
1351 mnBkMode = BackgroundMode::OPAQUE;
1352 ImplSetNonPersistentLineColorTransparenz();
1353 DrawPolygon(aPoly, false);
1354 mnBkMode = mnBkModeBackup; // The rectangle needs to be always drawned even if mode is transparent
1355 maFillStyle = std::move(aFillStyleBackup);
1356 maLineStyle.bTransparent = bTransparentBackup;
1359 void MtfTools::DrawRect( const tools::Rectangle& rRect, bool bEdge )
1361 UpdateClipRegion();
1362 UpdateFillStyle();
1364 if ( mbComplexClip )
1366 tools::Polygon aPoly( ImplMap( rRect ) );
1367 tools::PolyPolygon aPolyPolyRect( aPoly );
1368 tools::PolyPolygon aDest;
1369 tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( aPolyPolyRect, aDest );
1370 ImplDrawClippedPolyPolygon( aDest );
1372 else
1374 if ( bEdge )
1376 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1378 ImplSetNonPersistentLineColorTransparenz();
1379 mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
1380 UpdateLineStyle();
1381 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( ImplMap( rRect ) ),maLineStyle.aLineInfo ) );
1383 else
1385 UpdateLineStyle();
1386 mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
1389 else
1391 ImplSetNonPersistentLineColorTransparenz();
1392 mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
1397 void MtfTools::DrawRoundRect( const tools::Rectangle& rRect, const Size& rSize )
1399 UpdateClipRegion();
1400 UpdateLineStyle();
1401 UpdateFillStyle();
1402 mpGDIMetaFile->AddAction( new MetaRoundRectAction( ImplMap( rRect ), std::abs( ImplMap( rSize ).Width() ), std::abs( ImplMap( rSize ).Height() ) ) );
1403 // tdf#142139 Wrong line width during WMF import
1404 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1406 tools::Polygon aRoundRectPoly( rRect, rSize.Width(), rSize.Height() );
1407 mpGDIMetaFile->AddAction( new MetaPolyLineAction( ImplMap( aRoundRectPoly ), maLineStyle.aLineInfo ) );
1411 void MtfTools::DrawEllipse( const tools::Rectangle& rRect )
1413 UpdateClipRegion();
1414 UpdateFillStyle();
1416 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1418 Point aCenter( ImplMap( rRect.Center() ) );
1419 Size aRad( ImplMap( Size( rRect.GetWidth() / 2, rRect.GetHeight() / 2 ) ) );
1421 ImplSetNonPersistentLineColorTransparenz();
1422 mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) );
1423 UpdateLineStyle();
1424 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( std::move(aCenter), aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) );
1426 else
1428 UpdateLineStyle();
1429 mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) );
1433 void MtfTools::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd, bool bTo )
1435 UpdateClipRegion();
1436 UpdateLineStyle();
1437 UpdateFillStyle();
1439 tools::Rectangle aRect( ImplMap( rRect ) );
1440 Point aStart( ImplMap( rStart ) );
1441 Point aEnd( ImplMap( rEnd ) );
1443 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1445 if ( aStart == aEnd )
1446 { // SJ: #i53768# if start & end is identical, then we have to draw a full ellipse
1447 Point aCenter( aRect.Center() );
1448 Size aRad( aRect.GetWidth() / 2, aRect.GetHeight() / 2 );
1450 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( std::move(aCenter), aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) );
1452 else
1453 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Arc ), maLineStyle.aLineInfo ) );
1455 else
1456 mpGDIMetaFile->AddAction( new MetaArcAction( aRect, aStart, aEnd ) );
1458 if ( bTo )
1459 maActPos = aEnd;
1462 void MtfTools::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd )
1464 UpdateClipRegion();
1465 UpdateFillStyle();
1467 tools::Rectangle aRect( ImplMap( rRect ) );
1468 Point aStart( ImplMap( rStart ) );
1469 Point aEnd( ImplMap( rEnd ) );
1471 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1473 ImplSetNonPersistentLineColorTransparenz();
1474 mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) );
1475 UpdateLineStyle();
1476 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Pie ), maLineStyle.aLineInfo ) );
1478 else
1480 UpdateLineStyle();
1481 mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) );
1485 void MtfTools::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd )
1487 UpdateClipRegion();
1488 UpdateFillStyle();
1490 tools::Rectangle aRect( ImplMap( rRect ) );
1491 Point aStart( ImplMap( rStart ) );
1492 Point aEnd( ImplMap( rEnd ) );
1494 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1496 ImplSetNonPersistentLineColorTransparenz();
1497 mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) );
1498 UpdateLineStyle();
1499 mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Chord ), maLineStyle.aLineInfo ) );
1501 else
1503 UpdateLineStyle();
1504 mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) );
1508 void MtfTools::DrawPolygon( tools::Polygon rPolygon, bool bRecordPath )
1510 UpdateClipRegion();
1511 ImplMap( rPolygon );
1512 if ( bRecordPath )
1513 maPathObj.AddPolygon( rPolygon );
1514 else
1516 UpdateFillStyle();
1518 if ( mbComplexClip )
1520 tools::PolyPolygon aPolyPoly( rPolygon );
1521 auto tmp = maClipPath.getClip();
1522 tmp.intersectPolyPolygon(aPolyPoly.getB2DPolyPolygon());
1523 tools::PolyPolygon aDest(tmp.getClipPoly());
1524 ImplDrawClippedPolyPolygon( aDest );
1526 else
1528 if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
1530 sal_uInt16 nCount = rPolygon.GetSize();
1531 if ( nCount )
1533 if ( rPolygon[ nCount - 1 ] != rPolygon[ 0 ] )
1535 Point aPoint( rPolygon[ 0 ] );
1536 rPolygon.Insert( nCount, aPoint );
1539 ImplSetNonPersistentLineColorTransparenz();
1540 mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) );
1541 UpdateLineStyle();
1542 mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
1544 else
1546 UpdateLineStyle();
1548 if (maLatestFillStyle.aType != WinMtfFillStyleType::Pattern)
1549 mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(rPolygon) ) );
1550 else {
1551 SvtGraphicFill aFill( tools::PolyPolygon( rPolygon ),
1552 Color(),
1553 0.0,
1554 SvtGraphicFill::fillNonZero,
1555 SvtGraphicFill::fillTexture,
1556 SvtGraphicFill::Transform(),
1557 true,
1558 SvtGraphicFill::hatchSingle,
1559 Color(),
1560 SvtGraphicFill::GradientType::Linear,
1561 Color(),
1562 Color(),
1564 Graphic (BitmapEx(maLatestFillStyle.aBmp)));
1566 SvMemoryStream aMemStm;
1568 WriteSvtGraphicFill( aMemStm, aFill );
1570 mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN"_ostr, 0,
1571 static_cast<const sal_uInt8*>(aMemStm.GetData()),
1572 aMemStm.TellEnd() ) );
1573 mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_END"_ostr ) );
1581 void MtfTools::DrawPolyPolygon( tools::PolyPolygon& rPolyPolygon, bool bRecordPath )
1583 UpdateClipRegion();
1585 ImplMap( rPolyPolygon );
1587 if ( bRecordPath )
1588 maPathObj.AddPolyPolygon( rPolyPolygon );
1589 else
1591 UpdateFillStyle();
1593 if ( mbComplexClip )
1595 tools::PolyPolygon aDest;
1596 tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( rPolyPolygon, aDest );
1597 ImplDrawClippedPolyPolygon( aDest );
1599 else
1601 UpdateLineStyle();
1602 mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPolygon ) );
1603 if (maLineStyle.aLineInfo.GetWidth() > 0 || maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash)
1605 for (sal_uInt16 nPoly = 0; nPoly < rPolyPolygon.Count(); ++nPoly)
1607 mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolyPolygon[nPoly], maLineStyle.aLineInfo));
1614 void MtfTools::DrawPolyLine( tools::Polygon rPolygon, bool bTo, bool bRecordPath )
1616 UpdateClipRegion();
1618 sal_uInt16 nPoints = rPolygon.GetSize();
1619 if (nPoints < 1)
1620 return;
1622 ImplMap( rPolygon );
1623 if ( bTo )
1625 rPolygon[ 0 ] = maActPos;
1626 maActPos = rPolygon[ rPolygon.GetSize() - 1 ];
1628 if ( bRecordPath )
1629 maPathObj.AddPolyLine( rPolygon );
1630 else
1632 UpdateLineStyle();
1633 mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
1637 void MtfTools::DrawPolyBezier( tools::Polygon rPolygon, bool bTo, bool bRecordPath )
1639 sal_uInt16 nPoints = rPolygon.GetSize();
1640 if ( ( nPoints < 4 ) || ( ( ( nPoints - 4 ) % 3 ) != 0 ) )
1642 SAL_WARN("emfio",
1643 "EMF file error: Number of Bezier points is not set of three");
1644 return;
1646 UpdateClipRegion();
1647 ImplMap( rPolygon );
1648 if ( bTo )
1650 rPolygon[ 0 ] = maActPos;
1651 maActPos = rPolygon[ nPoints - 1 ];
1653 sal_uInt16 i;
1654 for ( i = 0; ( i + 2 ) < nPoints; )
1656 rPolygon.SetFlags( i++, PolyFlags::Normal );
1657 rPolygon.SetFlags( i++, PolyFlags::Control );
1658 rPolygon.SetFlags( i++, PolyFlags::Control );
1660 if ( bRecordPath )
1661 maPathObj.AddPolyLine( rPolygon );
1662 else
1664 UpdateLineStyle();
1665 mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
1669 void MtfTools::DrawText( Point& rPosition, OUString const & rText, KernArray* pDXArry, tools::Long* pDYArry, bool bRecordPath, GraphicsMode nGfxMode )
1671 UpdateClipRegion();
1672 rPosition = ImplMap( rPosition );
1673 GraphicsMode nOldGfxMode = GetGfxMode();
1674 SetGfxMode( GraphicsMode::GM_COMPATIBLE );
1676 if (pDXArry)
1678 sal_Int64 nSumX = 0, nSumY = 0;
1679 for (sal_Int32 i = 0; i < rText.getLength(); i++ )
1681 nSumX += (*pDXArry)[i];
1683 // #i121382# Map DXArray using WorldTransform
1684 const Size aSizeX(ImplMap(Size(nSumX, 0)));
1685 const basegfx::B2DVector aVectorX(aSizeX.Width(), aSizeX.Height());
1686 (*pDXArry)[i] = aVectorX.getLength() * (nSumX >= 0 ? 1 : -1);
1688 if (pDYArry)
1690 nSumY += pDYArry[i];
1692 const Size aSizeY(ImplMap(Size(0, nSumY)));
1693 const basegfx::B2DVector aVectorY(aSizeY.Width(), aSizeY.Height());
1694 // Reverse Y
1695 pDYArry[i] = basegfx::fround<tools::Long>(aVectorY.getLength());
1696 pDYArry[i] *= (nSumY >= 0 ? -1 : 1);
1700 if ( mnLatestTextLayoutMode != mnTextLayoutMode )
1702 mnLatestTextLayoutMode = mnTextLayoutMode;
1703 mpGDIMetaFile->AddAction( new MetaLayoutModeAction( mnTextLayoutMode ) );
1705 SetGfxMode(nGfxMode);
1706 TextAlign eTextAlign;
1707 if (mnTextAlign & TA_BASELINE)
1708 eTextAlign = ALIGN_BASELINE;
1709 else if (mnTextAlign & TA_BOTTOM)
1710 eTextAlign = ALIGN_BOTTOM;
1711 else
1712 eTextAlign = ALIGN_TOP;
1713 bool bChangeFont = false;
1714 if ( mnLatestTextAlign != mnTextAlign )
1716 bChangeFont = true;
1718 if ((mnLatestTextAlign & TA_RTLREADING) != (mnTextAlign & TA_RTLREADING))
1720 auto nFlags = vcl::text::ComplexTextLayoutFlags::Default;
1721 if (mnTextAlign & TA_RTLREADING)
1723 nFlags = vcl::text::ComplexTextLayoutFlags::BiDiRtl
1724 | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
1727 mpGDIMetaFile->AddAction(new MetaLayoutModeAction(nFlags));
1730 mnLatestTextAlign = mnTextAlign;
1731 mpGDIMetaFile->AddAction( new MetaTextAlignAction( eTextAlign ) );
1733 if ( maLatestTextColor != maTextColor )
1735 bChangeFont = true;
1736 maLatestTextColor = maTextColor;
1737 mpGDIMetaFile->AddAction( new MetaTextColorAction( maTextColor ) );
1739 bool bChangeFillColor = false;
1740 if ( maLatestBkColor != maBkColor )
1742 bChangeFillColor = true;
1743 maLatestBkColor = maBkColor;
1745 if ( mnLatestBkMode != mnBkMode )
1747 bChangeFillColor = true;
1748 mnLatestBkMode = mnBkMode;
1750 if ( bChangeFillColor )
1752 bChangeFont = true;
1753 mpGDIMetaFile->AddAction( new MetaTextFillColorAction( maFont.GetFillColor(), !maFont.IsTransparent() ) );
1755 vcl::Font aTmp( maFont );
1756 aTmp.SetColor( maTextColor );
1757 aTmp.SetFillColor( maBkColor );
1759 if( mnBkMode == BackgroundMode::Transparent )
1760 aTmp.SetTransparent( true );
1761 else
1762 aTmp.SetTransparent( false );
1764 aTmp.SetAlignment( eTextAlign );
1766 if ( nGfxMode == GraphicsMode::GM_ADVANCED )
1768 // check whether there is a font rotation applied via transformation
1769 Point aP1( ImplMap( Point() ) );
1770 Point aP2( ImplMap( Point( 0, 100 ) ) );
1771 aP2.setX(o3tl::saturating_sub(aP2.X(), aP1.X()));
1772 aP2.setY(o3tl::saturating_sub(aP2.Y(), aP1.Y()));
1773 double fX = aP2.X();
1774 double fY = aP2.Y();
1775 if ( fX )
1777 double fOrientation = basegfx::rad2deg(acos(fX / std::hypot(fX, fY)));
1778 if ( fY > 0 )
1779 fOrientation = 360 - fOrientation;
1780 fOrientation += 90;
1781 fOrientation *= 10;
1782 aTmp.SetOrientation( aTmp.GetOrientation() + Degree10( static_cast<sal_Int16>(fOrientation) ) );
1786 if( mnTextAlign & ( TA_UPDATECP | TA_RIGHT_CENTER ) )
1788 // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
1789 SolarMutexGuard aGuard;
1790 ScopedVclPtrInstance< VirtualDevice > pVDev;
1791 sal_Int32 nTextWidth;
1792 Point aActPosDelta;
1793 pVDev->SetMapMode( MapMode( MapUnit::Map100thMM ) );
1794 pVDev->SetFont( maFont );
1795 const sal_uInt32 nLen = pDXArry ? rText.getLength() : 0;
1796 if (nLen)
1798 nTextWidth = pVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) );
1799 if( nLen > 1 )
1800 nTextWidth += (*pDXArry)[ nLen - 2 ];
1801 // tdf#39894: We should consider the distance to next character cell origin
1802 aActPosDelta.setX( (*pDXArry)[ nLen - 1 ] );
1803 if ( pDYArry )
1805 aActPosDelta.setY( pDYArry[ nLen - 1 ] );
1808 else
1810 nTextWidth = pVDev->GetTextWidth( rText );
1811 aActPosDelta.setX( nTextWidth );
1814 if( mnTextAlign & TA_UPDATECP )
1815 rPosition = maActPos;
1817 if (mnTextAlign & TA_RIGHT_CENTER)
1819 Point aDisplacement(((mnTextAlign & TA_RIGHT_CENTER) == TA_CENTER) ? nTextWidth >> 1: nTextWidth, 0);
1820 Point().RotateAround(aDisplacement, maFont.GetOrientation());
1821 rPosition -= aDisplacement;
1824 if( mnTextAlign & TA_UPDATECP )
1826 Point().RotateAround(aActPosDelta, maFont.GetOrientation());
1827 maActPos = rPosition + aActPosDelta;
1831 if(bChangeFont || (maLatestFont != aTmp))
1833 maLatestFont = aTmp;
1834 rtl::Reference<MetaFontAction> aNewMetaFontAction(new MetaFontAction(aTmp));
1836 // tdf#127471 end evtl active MetaFontAction scale corrector detector/collector
1837 maScaledFontHelper.endCurrentMetaFontAction();
1839 // !bRecordPath: else no MetaTextArrayAction will be created
1840 // nullptr != pDXArry: detection only possible when text size is given
1841 // rText.getLength(): no useful check without text
1842 if(!bRecordPath && nullptr != pDXArry && 0 != rText.getLength())
1844 maScaledFontHelper.newCurrentMetaFontAction(aNewMetaFontAction);
1847 mpGDIMetaFile->AddAction( aNewMetaFontAction );
1848 mpGDIMetaFile->AddAction( new MetaTextAlignAction( aTmp.GetAlignment() ) );
1849 mpGDIMetaFile->AddAction( new MetaTextColorAction( aTmp.GetColor() ) );
1850 mpGDIMetaFile->AddAction( new MetaTextFillColorAction( aTmp.GetFillColor(), !aTmp.IsTransparent() ) );
1853 if ( bRecordPath )
1855 // TODO
1857 else
1859 if ( pDXArry && pDYArry )
1861 for (sal_Int32 i = 0; i < rText.getLength(); ++i)
1863 Point aCharDisplacement( i ? (*pDXArry)[i-1] : 0, i ? pDYArry[i-1] : 0 );
1864 Point().RotateAround(aCharDisplacement, maFont.GetOrientation());
1865 mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), KernArraySpan(), {}, 0, 1 ) );
1868 else
1870 /* because text without dx array is badly scaled, we
1871 will create such an array if necessary */
1872 KernArraySpan pDX;
1873 KernArray aMyDXArray;
1874 if (pDXArry)
1876 pDX = *pDXArry;
1877 // only useful when we have an imported DXArray
1878 if(!rText.isEmpty())
1880 maScaledFontHelper.evaluateAlternativeFontScale(
1881 rText,
1882 (*pDXArry)[rText.getLength() - 1] // extract imported TextLength
1886 else
1888 // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
1889 SolarMutexGuard aGuard;
1890 ScopedVclPtrInstance< VirtualDevice > pVDev;
1891 pVDev->SetMapMode(MapMode(MapUnit::Map100thMM));
1892 pVDev->SetFont( maLatestFont );
1893 pVDev->GetTextArray( rText, &aMyDXArray, 0, rText.getLength());
1894 pDX = aMyDXArray;
1896 mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, {}, 0, rText.getLength() ) );
1899 SetGfxMode( nOldGfxMode );
1902 void MtfTools::ImplDrawBitmap( const Point& rPos, const Size& rSize, const BitmapEx& rBitmap )
1904 BitmapEx aBmpEx( rBitmap );
1905 if ( mbComplexClip )
1907 vcl::bitmap::DrawAndClipBitmap(rPos, rSize, rBitmap, aBmpEx, maClipPath.getClipPath());
1910 if ( aBmpEx.IsAlpha() )
1911 mpGDIMetaFile->AddAction( new MetaBmpExScaleAction( rPos, rSize, aBmpEx ) );
1912 else
1913 mpGDIMetaFile->AddAction( new MetaBmpScaleAction( rPos, rSize, aBmpEx.GetBitmap() ) );
1916 void MtfTools::ResolveBitmapActions( std::vector<BSaveStruct>& rSaveList )
1918 UpdateClipRegion();
1920 size_t nObjects = rSaveList.size();
1921 size_t nObjectsLeft = nObjects;
1923 while ( nObjectsLeft )
1925 size_t i;
1926 size_t nObjectsOfSameSize = 0;
1927 size_t nObjectStartIndex = nObjects - nObjectsLeft;
1929 BSaveStruct* pSave = &rSaveList[nObjectStartIndex];
1930 tools::Rectangle aRect( pSave->aOutRect );
1932 for ( i = nObjectStartIndex; i < nObjects; )
1934 nObjectsOfSameSize++;
1935 if ( ++i < nObjects )
1937 pSave = &rSaveList[i];
1938 if ( pSave->aOutRect != aRect )
1939 break;
1942 Point aPos( ImplMap( aRect.TopLeft() ) );
1943 Size aSize( ImplMap( aRect.GetSize() ) );
1945 for ( i = nObjectStartIndex; i < ( nObjectStartIndex + nObjectsOfSameSize ); i++ )
1947 pSave = &rSaveList[i];
1949 sal_uInt32 nWinRop = pSave->nWinRop;
1950 sal_uInt8 nRasterOperation = static_cast<sal_uInt8>( nWinRop >> 16 );
1952 sal_uInt32 nUsed = 0;
1953 if ( ( nRasterOperation & 0xf ) != ( nRasterOperation >> 4 ) )
1954 nUsed |= 1; // pattern is used
1955 if ( ( nRasterOperation & 0x33 ) != ( ( nRasterOperation & 0xcc ) >> 2 ) )
1956 nUsed |= 2; // source is used
1957 if ( ( nRasterOperation & 0xaa ) != ( ( nRasterOperation & 0x55 ) << 1 ) )
1958 nUsed |= 4; // destination is used
1960 if ( (nUsed & 1) && (( nUsed & 2 ) == 0) && nWinRop != PATINVERT )
1961 { // patterns aren't well supported yet
1962 WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::NONE ); // in this case nRasterOperation is either 0 or 0xff
1963 UpdateFillStyle();
1964 DrawRect( aRect, false );
1965 SetRasterOp( nOldRop );
1967 else
1969 bool bDrawn = false;
1971 if ( i == nObjectStartIndex ) // optimizing, sometimes it is possible to create just one transparent bitmap
1973 if ( nObjectsOfSameSize == 2 )
1975 BSaveStruct* pSave2 = &rSaveList[i + 1];
1976 if ( ( pSave->aBmpEx.GetPrefSize() == pSave2->aBmpEx.GetPrefSize() ) &&
1977 ( pSave->aBmpEx.GetPrefMapMode() == pSave2->aBmpEx.GetPrefMapMode() ) )
1979 // TODO: Strictly speaking, we should
1980 // check whether mask is monochrome, and
1981 // whether image is black (upper branch)
1982 // or white (lower branch). Otherwise, the
1983 // effect is not the same as a masked
1984 // bitmap.
1985 if ( ( nWinRop == SRCPAINT ) && ( pSave2->nWinRop == SRCAND ) )
1987 Bitmap aMask( pSave->aBmpEx.GetBitmap() ); aMask.Invert();
1988 BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), aMask );
1989 ImplDrawBitmap( aPos, aSize, aBmpEx );
1990 bDrawn = true;
1991 i++;
1993 // #i20085# This is just the other way
1994 // around as above. Only difference: mask
1995 // is inverted
1996 else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCPAINT ) )
1998 const Bitmap & rMask( pSave->aBmpEx.GetBitmap() );
1999 BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask );
2000 ImplDrawBitmap( aPos, aSize, aBmpEx );
2001 bDrawn = true;
2002 i++;
2004 // tdf#90539
2005 else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCINVERT ) )
2007 const Bitmap & rMask( pSave->aBmpEx.GetBitmap() );
2008 BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask );
2009 ImplDrawBitmap( aPos, aSize, aBmpEx );
2010 bDrawn = true;
2011 i++;
2017 if ( !bDrawn )
2019 Push();
2020 WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::CopyPen );
2021 Bitmap aBitmap( pSave->aBmpEx.GetBitmap() );
2022 sal_uInt32 nOperation = ( nRasterOperation & 0xf );
2023 switch( nOperation )
2025 case 0x1 :
2026 case 0xe :
2028 if(pSave->aBmpEx.IsAlpha())
2030 ImplDrawBitmap( aPos, aSize, pSave->aBmpEx );
2032 else
2034 SetRasterOp( WMFRasterOp::XorPen );
2035 ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
2036 SetRasterOp( WMFRasterOp::CopyPen );
2037 Bitmap aMask( aBitmap );
2038 aMask.Invert();
2039 BitmapEx aBmpEx( aBitmap, aMask );
2040 ImplDrawBitmap( aPos, aSize, aBmpEx );
2041 if ( nOperation == 0x1 )
2043 SetRasterOp( WMFRasterOp::Not );
2044 DrawRect( aRect, false );
2048 break;
2049 case 0x7 :
2050 case 0x8 :
2052 Bitmap aMask( aBitmap );
2053 if ( ( nUsed & 1 ) && ( nRasterOperation & 0xb0 ) == 0xb0 ) // pattern used
2055 aBitmap.Convert( BmpConversion::N24Bit );
2056 aBitmap.Erase( maFillStyle.aFillColor );
2058 BitmapEx aBmpEx( aBitmap, aMask );
2059 ImplDrawBitmap( aPos, aSize, aBmpEx );
2060 if ( nOperation == 0x7 )
2062 SetRasterOp( WMFRasterOp::Not );
2063 DrawRect( aRect, false );
2066 break;
2068 case 0x4 :
2069 case 0xb :
2071 SetRasterOp( WMFRasterOp::Not );
2072 DrawRect( aRect, false );
2073 SetRasterOp( WMFRasterOp::CopyPen );
2074 Bitmap aMask( aBitmap );
2075 aBitmap.Invert();
2076 BitmapEx aBmpEx( aBitmap, aMask );
2077 ImplDrawBitmap( aPos, aSize, aBmpEx );
2078 SetRasterOp( WMFRasterOp::XorPen );
2079 ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
2080 if ( nOperation == 0xb )
2082 SetRasterOp( WMFRasterOp::Not );
2083 DrawRect( aRect, false );
2086 break;
2088 case 0x2 :
2089 case 0xd :
2091 Bitmap aMask( aBitmap );
2092 aMask.Invert();
2093 BitmapEx aBmpEx( aBitmap, aMask );
2094 ImplDrawBitmap( aPos, aSize, aBmpEx );
2095 SetRasterOp( WMFRasterOp::XorPen );
2096 ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
2097 if ( nOperation == 0xd )
2099 SetRasterOp( WMFRasterOp::Not );
2100 DrawRect( aRect, false );
2103 break;
2104 case 0x6 :
2105 case 0x9 :
2107 SetRasterOp( WMFRasterOp::XorPen );
2108 ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
2109 if ( nOperation == 0x9 )
2111 SetRasterOp( WMFRasterOp::Not );
2112 DrawRect( aRect, false );
2115 break;
2117 case 0x0 : // WHITENESS
2118 case 0xf : // BLACKNESS
2119 { // in this case nRasterOperation is either 0 or 0xff
2120 maFillStyle = WinMtfFillStyle( Color( nRasterOperation, nRasterOperation, nRasterOperation ) );
2121 UpdateFillStyle();
2122 DrawRect( aRect, false );
2124 break;
2126 case 0x3 : // only source is used
2127 case 0xc :
2129 if ( nRasterOperation == 0x33 )
2130 aBitmap.Invert();
2131 if (pSave->m_bForceAlpha)
2133 ImplDrawBitmap(aPos, aSize, pSave->aBmpEx);
2135 else
2137 ImplDrawBitmap(aPos, aSize, BitmapEx(aBitmap));
2140 break;
2142 case 0x5 : // only destination is used
2144 SetRasterOp( WMFRasterOp::Not );
2145 DrawRect( aRect, false );
2147 break;
2149 case 0xa : // no operation
2150 break;
2152 SetRasterOp( nOldRop );
2153 Pop();
2157 nObjectsLeft -= nObjectsOfSameSize;
2160 rSaveList.clear();
2163 void MtfTools::SetDevOrg( const Point& rPoint )
2165 mnDevOrgX = rPoint.X();
2166 mnDevOrgY = rPoint.Y();
2169 void MtfTools::SetDevOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd )
2171 mnDevOrgX += nXAdd;
2172 mnDevOrgY += nYAdd;
2175 void MtfTools::SetDevExt( const Size& rSize ,bool regular)
2177 if ( !(rSize.Width() && rSize.Height()) )
2178 return;
2180 switch( meMapMode )
2182 case MappingMode::MM_ISOTROPIC :
2183 case MappingMode::MM_ANISOTROPIC :
2185 mnDevWidth = rSize.Width();
2186 mnDevHeight = rSize.Height();
2187 break;
2190 //do nothing
2191 default:
2192 break;
2194 if (regular)
2196 mbIsMapDevSet=true;
2200 void MtfTools::ScaleDevExt(double fX, double fY)
2202 mnDevWidth = basegfx::fround(mnDevWidth * fX);
2203 mnDevHeight = basegfx::fround(mnDevHeight * fY);
2206 void MtfTools::SetWinOrg( const Point& rPoint , bool bIsEMF)
2208 mnWinOrgX = rPoint.X();
2209 mnWinOrgY = rPoint.Y();
2210 if (bIsEMF)
2212 SetDevByWin();
2214 mbIsMapWinSet=true;
2217 void MtfTools::SetWinOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd )
2219 mnWinOrgX += nXAdd;
2220 mnWinOrgY += nYAdd;
2223 void MtfTools::SetDevByWin() //mnWinExt...-stuff has to be assigned before.
2225 if (!mbIsMapDevSet)
2227 if ( meMapMode == MappingMode::MM_ISOTROPIC ) //TODO: WHAT ABOUT ANISOTROPIC???
2229 sal_Int32 nX, nY;
2230 if (o3tl::checked_add(mnWinExtX, mnWinOrgX, nX) || o3tl::checked_sub(mnWinExtY, mnWinOrgY, nY))
2231 return;
2232 Size aSize(nX >> MS_FIXPOINT_BITCOUNT_28_4, -(nY >> MS_FIXPOINT_BITCOUNT_28_4));
2233 SetDevExt(aSize, false);
2238 void MtfTools::SetWinExt(const Size& rSize, bool bIsEMF)
2240 if (!(rSize.Width() && rSize.Height()))
2241 return;
2243 switch( meMapMode )
2245 case MappingMode::MM_ISOTROPIC :
2246 case MappingMode::MM_ANISOTROPIC :
2248 mnWinExtX = rSize.Width();
2249 mnWinExtY = rSize.Height();
2250 if (bIsEMF)
2252 SetDevByWin();
2254 mbIsMapWinSet = true;
2255 break;
2258 default:
2259 //do nothing
2260 break;
2264 void MtfTools::ScaleWinExt(double fX, double fY)
2266 mnWinExtX = basegfx::fround(mnWinExtX * fX);
2267 mnWinExtY = basegfx::fround(mnWinExtY * fY);
2270 void MtfTools::SetrclBounds( const tools::Rectangle& rRect )
2272 mrclBounds = rRect;
2275 void MtfTools::SetrclFrame( const tools::Rectangle& rRect )
2277 mrclFrame = rRect;
2280 void MtfTools::SetRefPix( const Size& rSize )
2282 mnPixX = rSize.Width();
2283 mnPixY = rSize.Height();
2286 void MtfTools::SetRefMill( const Size& rSize )
2288 mnMillX = rSize.Width();
2289 mnMillY = rSize.Height();
2292 void MtfTools::SetMapMode( MappingMode nMapMode )
2294 meMapMode = nMapMode;
2295 if ( nMapMode == MappingMode::MM_TEXT && !mbIsMapWinSet )
2297 mnWinExtX = mnDevWidth;
2298 mnWinExtY = mnDevHeight;
2300 else if ( meMapMode == MappingMode::MM_HIMETRIC )
2302 sal_Int32 nWinExtX, nWinExtY;
2303 if (o3tl::checked_multiply<sal_Int32>(mnMillX, 100, nWinExtX) ||
2304 o3tl::checked_multiply<sal_Int32>(mnMillY, 100, nWinExtY))
2306 return;
2308 mnWinExtX = nWinExtX;
2309 mnWinExtY = nWinExtY;
2313 void MtfTools::SetWorldTransform( const XForm& rXForm )
2315 maXForm.eM11 = rXForm.eM11;
2316 maXForm.eM12 = rXForm.eM12;
2317 maXForm.eM21 = rXForm.eM21;
2318 maXForm.eM22 = rXForm.eM22;
2319 maXForm.eDx = rXForm.eDx;
2320 maXForm.eDy = rXForm.eDy;
2323 void MtfTools::ModifyWorldTransform( const XForm& rXForm, ModifyWorldTransformMode nMode )
2325 switch( nMode )
2327 case ModifyWorldTransformMode::MWT_IDENTITY :
2329 maXForm.eM11 = maXForm.eM22 = 1.0f;
2330 maXForm.eM12 = maXForm.eM21 = maXForm.eDx = maXForm.eDy = 0.0f;
2331 break;
2334 case ModifyWorldTransformMode::MWT_RIGHTMULTIPLY :
2335 case ModifyWorldTransformMode::MWT_LEFTMULTIPLY :
2337 const XForm* pLeft;
2338 const XForm* pRight;
2340 if ( nMode == ModifyWorldTransformMode::MWT_LEFTMULTIPLY )
2342 pLeft = &rXForm;
2343 pRight = &maXForm;
2345 else
2347 pLeft = &maXForm;
2348 pRight = &rXForm;
2351 float aF[3][3];
2352 float bF[3][3];
2353 float cF[3][3];
2355 aF[0][0] = pLeft->eM11;
2356 aF[0][1] = pLeft->eM12;
2357 aF[0][2] = 0;
2358 aF[1][0] = pLeft->eM21;
2359 aF[1][1] = pLeft->eM22;
2360 aF[1][2] = 0;
2361 aF[2][0] = pLeft->eDx;
2362 aF[2][1] = pLeft->eDy;
2363 aF[2][2] = 1;
2365 bF[0][0] = pRight->eM11;
2366 bF[0][1] = pRight->eM12;
2367 bF[0][2] = 0;
2368 bF[1][0] = pRight->eM21;
2369 bF[1][1] = pRight->eM22;
2370 bF[1][2] = 0;
2371 bF[2][0] = pRight->eDx;
2372 bF[2][1] = pRight->eDy;
2373 bF[2][2] = 1;
2375 int i, j, k;
2376 for ( i = 0; i < 3; i++ )
2378 for ( j = 0; j < 3; j++ )
2380 cF[i][j] = 0;
2381 for ( k = 0; k < 3; k++ )
2382 cF[i][j] += aF[i][k] * bF[k][j];
2385 maXForm.eM11 = cF[0][0];
2386 maXForm.eM12 = cF[0][1];
2387 maXForm.eM21 = cF[1][0];
2388 maXForm.eM22 = cF[1][1];
2389 maXForm.eDx = cF[2][0];
2390 maXForm.eDy = cF[2][1];
2391 break;
2393 case ModifyWorldTransformMode::MWT_SET:
2395 SetWorldTransform(rXForm);
2396 break;
2401 void MtfTools::Push() // !! to be able to access the original ClipRegion it
2402 { // is not allowed to use the MetaPushAction()
2403 UpdateClipRegion(); // (the original clip region is on top of the stack) (SJ)
2404 auto pSave = std::make_shared<SaveStruct>();
2406 pSave->aLineStyle = maLineStyle;
2407 pSave->aFillStyle = maFillStyle;
2409 pSave->aFont = maFont;
2410 pSave->aTextColor = maTextColor;
2411 pSave->nTextAlign = mnTextAlign;
2412 pSave->nTextLayoutMode = mnTextLayoutMode;
2413 pSave->eMapMode = meMapMode;
2414 pSave->eGfxMode = meGfxMode;
2415 pSave->nBkMode = mnBkMode;
2416 pSave->aBkColor = maBkColor;
2417 pSave->bClockWiseArcDirection = mbClockWiseArcDirection;
2418 pSave->bFillStyleSelected = mbFillStyleSelected;
2420 pSave->aActPos = maActPos;
2421 pSave->aXForm = maXForm;
2422 pSave->eRasterOp = meRasterOp;
2424 pSave->nWinOrgX = mnWinOrgX;
2425 pSave->nWinOrgY = mnWinOrgY;
2426 pSave->nWinExtX = mnWinExtX;
2427 pSave->nWinExtY = mnWinExtY;
2428 pSave->nDevOrgX = mnDevOrgX;
2429 pSave->nDevOrgY = mnDevOrgY;
2430 pSave->nDevWidth = mnDevWidth;
2431 pSave->nDevHeight = mnDevHeight;
2433 pSave->maPathObj = maPathObj;
2434 pSave->maClipPath = maClipPath;
2436 SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode));
2437 SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode));
2438 SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY);
2439 SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY);
2440 SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY);
2441 SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight);
2442 SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor );
2443 mvSaveStack.push_back( pSave );
2446 void MtfTools::Pop( const sal_Int32 nSavedDC )
2448 if ( nSavedDC == 0 )
2449 return;
2451 sal_Int32 aIndex;
2452 if ( nSavedDC < 0 ) // WMF/EMF, if negative, nSavedDC represents an instance relative to the current state.
2453 aIndex = static_cast< sal_Int32 >( mvSaveStack.size() ) + nSavedDC;
2454 else
2455 aIndex = nSavedDC; // WMF, if positive, nSavedDC represents a specific instance of the state to be restored.
2456 if( aIndex < 0 )
2458 mvSaveStack.clear();
2459 return;
2461 if( mvSaveStack.empty() || ( aIndex >= static_cast< sal_Int32 >( mvSaveStack.size() ) ) )
2462 return;
2464 mvSaveStack.resize( aIndex + 1 );
2465 // Backup the current data on the stack
2466 std::shared_ptr<SaveStruct>& pSave( mvSaveStack.back() );
2468 maLineStyle = pSave->aLineStyle;
2469 maFillStyle = pSave->aFillStyle;
2471 maFont = pSave->aFont;
2472 maTextColor = pSave->aTextColor;
2473 mnTextAlign = pSave->nTextAlign;
2474 mnTextLayoutMode = pSave->nTextLayoutMode;
2475 mnBkMode = pSave->nBkMode;
2476 meGfxMode = pSave->eGfxMode;
2477 meMapMode = pSave->eMapMode;
2478 maBkColor = pSave->aBkColor;
2479 mbClockWiseArcDirection = pSave->bClockWiseArcDirection;
2480 mbFillStyleSelected = pSave->bFillStyleSelected;
2482 maActPos = pSave->aActPos;
2483 maXForm = pSave->aXForm;
2484 meRasterOp = pSave->eRasterOp;
2486 mnWinOrgX = pSave->nWinOrgX;
2487 mnWinOrgY = pSave->nWinOrgY;
2488 mnWinExtX = pSave->nWinExtX;
2489 mnWinExtY = pSave->nWinExtY;
2490 mnDevOrgX = pSave->nDevOrgX;
2491 mnDevOrgY = pSave->nDevOrgY;
2492 mnDevWidth = pSave->nDevWidth;
2493 mnDevHeight = pSave->nDevHeight;
2495 maPathObj = pSave->maPathObj;
2496 if ( ! ( maClipPath == pSave->maClipPath ) )
2498 maClipPath = pSave->maClipPath;
2499 mbClipNeedsUpdate = true;
2501 if ( meLatestRasterOp != meRasterOp )
2503 mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) );
2504 meLatestRasterOp = meRasterOp;
2507 SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode));
2508 SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode));
2509 SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY);
2510 SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY);
2511 SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY);
2512 SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight);
2513 SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor );
2514 mvSaveStack.pop_back();
2517 void MtfTools::AddFromGDIMetaFile( GDIMetaFile& rGDIMetaFile )
2519 rGDIMetaFile.Play( *mpGDIMetaFile );
2522 void MtfTools::PassEMFPlusHeaderInfo()
2524 EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS header info\n"));
2526 SvMemoryStream mem;
2527 sal_Int32 nLeft, nRight, nTop, nBottom;
2529 nLeft = mrclFrame.Left();
2530 nTop = mrclFrame.Top();
2531 nRight = mrclFrame.Right();
2532 nBottom = mrclFrame.Bottom();
2534 // emf header info
2535 mem.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom );
2536 mem.WriteInt32( mnPixX ).WriteInt32( mnPixY ).WriteInt32( mnMillX ).WriteInt32( mnMillY );
2538 float one, zero;
2540 one = 1;
2541 zero = 0;
2543 // add transformation matrix to be used in vcl's metaact.cxx for
2544 // rotate and scale operations
2545 mem.WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero ).WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero );
2547 // need to flush the stream, otherwise GetEndOfData will return 0
2548 // on windows where the function parameters are probably resolved in reverse order
2549 mem.Flush();
2551 mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS_HEADER_INFO"_ostr, 0, static_cast<const sal_uInt8*>(mem.GetData()), mem.GetEndOfData() ) );
2552 mpGDIMetaFile->UseCanvas( true );
2555 void MtfTools::PassEMFPlus( void const * pBuffer, sal_uInt32 nLength )
2557 EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS comment length %04x\n",(unsigned int) nLength));
2558 mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS"_ostr, 0, static_cast<const sal_uInt8*>(pBuffer), nLength ) );
2562 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */