tdf#154546 skip dispatch when presenter controller is not set
[LibreOffice.git] / emfio / source / reader / emfreader.cxx
blobaf676e7de6691ef77725c45dc116f3c23e15ffce
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 .
19 #include <basegfx/polygon/b2dpolygontools.hxx>
20 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
21 #include <emfreader.hxx>
22 #include <sal/log.hxx>
23 #include <osl/diagnose.h>
24 #include <vcl/dibtools.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <o3tl/sprintf.hxx>
27 #include <tools/stream.hxx>
28 #include <memory>
29 #include <unotools/configmgr.hxx>
30 #include <vcl/graph.hxx>
31 #include <vcl/pdfread.hxx>
32 #include <rtl/bootstrap.hxx>
34 #ifdef DBG_UTIL
35 #include <vcl/filter/PngImageWriter.hxx>
36 #endif
38 // GDI-Array
40 #define EMR_HEADER 1
41 #define EMR_POLYBEZIER 2
42 #define EMR_POLYGON 3
43 #define EMR_POLYLINE 4
44 #define EMR_POLYBEZIERTO 5
45 #define EMR_POLYLINETO 6
46 #define EMR_POLYPOLYLINE 7
47 #define EMR_POLYPOLYGON 8
48 #define EMR_SETWINDOWEXTEX 9
49 #define EMR_SETWINDOWORGEX 10
50 #define EMR_SETVIEWPORTEXTEX 11
51 #define EMR_SETVIEWPORTORGEX 12
52 #define EMR_SETBRUSHORGEX 13
53 #define EMR_EOF 14
54 #define EMR_SETPIXELV 15
55 #define EMR_SETMAPPERFLAGS 16
56 #define EMR_SETMAPMODE 17
57 #define EMR_SETBKMODE 18
58 #define EMR_SETPOLYFILLMODE 19
59 #define EMR_SETROP2 20
60 #define EMR_SETSTRETCHBLTMODE 21
61 #define EMR_SETTEXTALIGN 22
62 #define EMR_SETCOLORADJUSTMENT 23
63 #define EMR_SETTEXTCOLOR 24
64 #define EMR_SETBKCOLOR 25
65 #define EMR_OFFSETCLIPRGN 26
66 #define EMR_MOVETOEX 27
67 #define EMR_SETMETARGN 28
68 #define EMR_EXCLUDECLIPRECT 29
69 #define EMR_INTERSECTCLIPRECT 30
70 #define EMR_SCALEVIEWPORTEXTEX 31
71 #define EMR_SCALEWINDOWEXTEX 32
72 #define EMR_SAVEDC 33
73 #define EMR_RESTOREDC 34
74 #define EMR_SETWORLDTRANSFORM 35
75 #define EMR_MODIFYWORLDTRANSFORM 36
76 #define EMR_SELECTOBJECT 37
77 #define EMR_CREATEPEN 38
78 #define EMR_CREATEBRUSHINDIRECT 39
79 #define EMR_DELETEOBJECT 40
80 #define EMR_ANGLEARC 41
81 #define EMR_ELLIPSE 42
82 #define EMR_RECTANGLE 43
83 #define EMR_ROUNDRECT 44
84 #define EMR_ARC 45
85 #define EMR_CHORD 46
86 #define EMR_PIE 47
87 #define EMR_SELECTPALETTE 48
88 #define EMR_CREATEPALETTE 49
89 #define EMR_SETPALETTEENTRIES 50
90 #define EMR_RESIZEPALETTE 51
91 #define EMR_REALIZEPALETTE 52
92 #define EMR_EXTFLOODFILL 53
93 #define EMR_LINETO 54
94 #define EMR_ARCTO 55
95 #define EMR_POLYDRAW 56
96 #define EMR_SETARCDIRECTION 57
97 #define EMR_SETMITERLIMIT 58
98 #define EMR_BEGINPATH 59
99 #define EMR_ENDPATH 60
100 #define EMR_CLOSEFIGURE 61
101 #define EMR_FILLPATH 62
102 #define EMR_STROKEANDFILLPATH 63
103 #define EMR_STROKEPATH 64
104 #define EMR_FLATTENPATH 65
105 #define EMR_WIDENPATH 66
106 #define EMR_SELECTCLIPPATH 67
107 #define EMR_ABORTPATH 68
109 #define EMR_COMMENT 70 // Contains arbitrary private data.
110 // Comment Identifiers:
111 #define EMR_COMMENT_EMFPLUS 0x2B464D45 // Contains embedded EMF+ records.
112 #define EMR_COMMENT_EMFSPOOL 0x00000000 // Contains embedded EMFSPOOL records.
113 #define EMR_COMMENT_PUBLIC 0x43494447 // Specify extensions to EMF processing.
115 #define EMR_FILLRGN 71
116 #define EMR_FRAMERGN 72
117 #define EMR_INVERTRGN 73
118 #define EMR_PAINTRGN 74
119 #define EMR_EXTSELECTCLIPRGN 75
120 #define EMR_BITBLT 76
121 #define EMR_STRETCHBLT 77
122 #define EMR_MASKBLT 78
123 #define EMR_PLGBLT 79
124 #define EMR_SETDIBITSTODEVICE 80
125 #define EMR_STRETCHDIBITS 81
126 #define EMR_EXTCREATEFONTINDIRECTW 82
127 #define EMR_EXTTEXTOUTA 83
128 #define EMR_EXTTEXTOUTW 84
129 #define EMR_POLYBEZIER16 85
130 #define EMR_POLYGON16 86
131 #define EMR_POLYLINE16 87
132 #define EMR_POLYBEZIERTO16 88
133 #define EMR_POLYLINETO16 89
134 #define EMR_POLYPOLYLINE16 90
135 #define EMR_POLYPOLYGON16 91
136 #define EMR_POLYDRAW16 92
137 #define EMR_CREATEMONOBRUSH 93
138 #define EMR_CREATEDIBPATTERNBRUSHPT 94
139 #define EMR_EXTCREATEPEN 95
140 #define EMR_POLYTEXTOUTA 96
141 #define EMR_POLYTEXTOUTW 97
143 // WINDOWS VERSION >= 0x400
144 #define EMR_SETICMMODE 98
145 #define EMR_CREATECOLORSPACE 99
146 #define EMR_SETCOLORSPACE 100
147 #define EMR_DELETECOLORSPACE 101
148 #define EMR_GLSRECORD 102
149 #define EMR_GLSBOUNDEDRECORD 103
150 #define EMR_PIXELFORMAT 104
152 // WINDOWS VERSION >= 0x500
153 #define EMR_DRAWESCAPE 105
154 #define EMR_EXTESCAPE 106
155 #define EMR_STARTDOC 107
156 #define EMR_SMALLTEXTOUT 108
157 #define EMR_FORCEUFIMAPPING 109
158 #define EMR_NAMEDESCAPE 110
159 #define EMR_COLORCORRECTPALETTE 111
160 #define EMR_SETICMPROFILEA 112
161 #define EMR_SETICMPROFILEW 113
162 #define EMR_ALPHABLEND 114
163 #define EMR_ALPHADIBBLEND 115
164 #define EMR_TRANSPARENTBLT 116
165 #define EMR_TRANSPARENTDIB 117
166 #define EMR_GRADIENTFILL 118
167 #define EMR_SETLINKEDUFIS 119
168 #define EMR_SETTEXTJUSTIFICATION 120
170 #define PDF_SIGNATURE 0x50444620 // "PDF "
172 /* [MS-EMF] - v20210625 - page 28 */
173 constexpr sal_Int32 ARCDIRECTION_CLOCKWISE = 0x00000002;
175 namespace
178 const char *
179 record_type_name(sal_uInt32 nRecType)
181 #ifndef SAL_LOG_INFO
182 (void) nRecType;
183 return "";
184 #else
185 switch( nRecType )
187 case EMR_HEADER: return "HEADER";
188 case EMR_POLYBEZIER: return "POLYBEZIER";
189 case EMR_POLYGON: return "POLYGON";
190 case EMR_POLYLINE: return "POLYLINE";
191 case EMR_POLYBEZIERTO: return "POLYBEZIERTO";
192 case EMR_POLYLINETO: return "POLYLINETO";
193 case EMR_POLYPOLYLINE: return "POLYPOLYLINE";
194 case EMR_POLYPOLYGON: return "POLYPOLYGON";
195 case EMR_SETWINDOWEXTEX: return "SETWINDOWEXTEX";
196 case EMR_SETWINDOWORGEX: return "SETWINDOWORGEX";
197 case EMR_SETVIEWPORTEXTEX: return "SETVIEWPORTEXTEX";
198 case EMR_SETVIEWPORTORGEX: return "SETVIEWPORTORGEX";
199 case EMR_SETBRUSHORGEX: return "SETBRUSHORGEX";
200 case EMR_EOF: return "EOF";
201 case EMR_SETPIXELV: return "SETPIXELV";
202 case EMR_SETMAPPERFLAGS: return "SETMAPPERFLAGS";
203 case EMR_SETMAPMODE: return "SETMAPMODE";
204 case EMR_SETBKMODE: return "SETBKMODE";
205 case EMR_SETPOLYFILLMODE: return "SETPOLYFILLMODE";
206 case EMR_SETROP2: return "SETROP2";
207 case EMR_SETSTRETCHBLTMODE: return "SETSTRETCHBLTMODE";
208 case EMR_SETTEXTALIGN: return "SETTEXTALIGN";
209 case EMR_SETCOLORADJUSTMENT: return "SETCOLORADJUSTMENT";
210 case EMR_SETTEXTCOLOR: return "SETTEXTCOLOR";
211 case EMR_SETBKCOLOR: return "SETBKCOLOR";
212 case EMR_OFFSETCLIPRGN: return "OFFSETCLIPRGN";
213 case EMR_MOVETOEX: return "MOVETOEX";
214 case EMR_SETMETARGN: return "SETMETARGN";
215 case EMR_EXCLUDECLIPRECT: return "EXCLUDECLIPRECT";
216 case EMR_INTERSECTCLIPRECT: return "INTERSECTCLIPRECT";
217 case EMR_SCALEVIEWPORTEXTEX: return "SCALEVIEWPORTEXTEX";
218 case EMR_SCALEWINDOWEXTEX: return "SCALEWINDOWEXTEX";
219 case EMR_SAVEDC: return "SAVEDC";
220 case EMR_RESTOREDC: return "RESTOREDC";
221 case EMR_SETWORLDTRANSFORM: return "SETWORLDTRANSFORM";
222 case EMR_MODIFYWORLDTRANSFORM: return "MODIFYWORLDTRANSFORM";
223 case EMR_SELECTOBJECT: return "SELECTOBJECT";
224 case EMR_CREATEPEN: return "CREATEPEN";
225 case EMR_CREATEBRUSHINDIRECT: return "CREATEBRUSHINDIRECT";
226 case EMR_DELETEOBJECT: return "DELETEOBJECT";
227 case EMR_ANGLEARC: return "ANGLEARC";
228 case EMR_ELLIPSE: return "ELLIPSE";
229 case EMR_RECTANGLE: return "RECTANGLE";
230 case EMR_ROUNDRECT: return "ROUNDRECT";
231 case EMR_ARC: return "ARC";
232 case EMR_CHORD: return "CHORD";
233 case EMR_PIE: return "PIE";
234 case EMR_SELECTPALETTE: return "SELECTPALETTE";
235 case EMR_CREATEPALETTE: return "CREATEPALETTE";
236 case EMR_SETPALETTEENTRIES: return "SETPALETTEENTRIES";
237 case EMR_RESIZEPALETTE: return "RESIZEPALETTE";
238 case EMR_REALIZEPALETTE: return "REALIZEPALETTE";
239 case EMR_EXTFLOODFILL: return "EXTFLOODFILL";
240 case EMR_LINETO: return "LINETO";
241 case EMR_ARCTO: return "ARCTO";
242 case EMR_POLYDRAW: return "POLYDRAW";
243 case EMR_SETARCDIRECTION: return "SETARCDIRECTION";
244 case EMR_SETMITERLIMIT: return "SETMITERLIMIT";
245 case EMR_BEGINPATH: return "BEGINPATH";
246 case EMR_ENDPATH: return "ENDPATH";
247 case EMR_CLOSEFIGURE: return "CLOSEFIGURE";
248 case EMR_FILLPATH: return "FILLPATH";
249 case EMR_STROKEANDFILLPATH: return "STROKEANDFILLPATH";
250 case EMR_STROKEPATH: return "STROKEPATH";
251 case EMR_FLATTENPATH: return "FLATTENPATH";
252 case EMR_WIDENPATH: return "WIDENPATH";
253 case EMR_SELECTCLIPPATH: return "SELECTCLIPPATH";
254 case EMR_ABORTPATH: return "ABORTPATH";
255 case EMR_COMMENT: return "COMMENT";
256 case EMR_FILLRGN: return "FILLRGN";
257 case EMR_FRAMERGN: return "FRAMERGN";
258 case EMR_INVERTRGN: return "INVERTRGN";
259 case EMR_PAINTRGN: return "PAINTRGN";
260 case EMR_EXTSELECTCLIPRGN: return "EXTSELECTCLIPRGN";
261 case EMR_BITBLT: return "BITBLT";
262 case EMR_STRETCHBLT: return "STRETCHBLT";
263 case EMR_MASKBLT: return "MASKBLT";
264 case EMR_PLGBLT: return "PLGBLT";
265 case EMR_SETDIBITSTODEVICE: return "SETDIBITSTODEVICE";
266 case EMR_STRETCHDIBITS: return "STRETCHDIBITS";
267 case EMR_EXTCREATEFONTINDIRECTW: return "EXTCREATEFONTINDIRECTW";
268 case EMR_EXTTEXTOUTA: return "EXTTEXTOUTA";
269 case EMR_EXTTEXTOUTW: return "EXTTEXTOUTW";
270 case EMR_POLYBEZIER16: return "POLYBEZIER16";
271 case EMR_POLYGON16: return "POLYGON16";
272 case EMR_POLYLINE16: return "POLYLINE16";
273 case EMR_POLYBEZIERTO16: return "POLYBEZIERTO16";
274 case EMR_POLYLINETO16: return "POLYLINETO16";
275 case EMR_POLYPOLYLINE16: return "POLYPOLYLINE16";
276 case EMR_POLYPOLYGON16: return "POLYPOLYGON16";
277 case EMR_POLYDRAW16: return "POLYDRAW16";
278 case EMR_CREATEMONOBRUSH: return "CREATEMONOBRUSH";
279 case EMR_CREATEDIBPATTERNBRUSHPT: return "CREATEDIBPATTERNBRUSHPT";
280 case EMR_EXTCREATEPEN: return "EXTCREATEPEN";
281 case EMR_POLYTEXTOUTA: return "POLYTEXTOUTA";
282 case EMR_POLYTEXTOUTW: return "POLYTEXTOUTW";
283 case EMR_SETICMMODE: return "SETICMMODE";
284 case EMR_CREATECOLORSPACE: return "CREATECOLORSPACE";
285 case EMR_SETCOLORSPACE: return "SETCOLORSPACE";
286 case EMR_DELETECOLORSPACE: return "DELETECOLORSPACE";
287 case EMR_GLSRECORD: return "GLSRECORD";
288 case EMR_GLSBOUNDEDRECORD: return "GLSBOUNDEDRECORD";
289 case EMR_PIXELFORMAT: return "PIXELFORMAT";
290 case EMR_DRAWESCAPE: return "DRAWESCAPE";
291 case EMR_EXTESCAPE: return "EXTESCAPE";
292 case EMR_STARTDOC: return "STARTDOC";
293 case EMR_SMALLTEXTOUT: return "SMALLTEXTOUT";
294 case EMR_FORCEUFIMAPPING: return "FORCEUFIMAPPING";
295 case EMR_NAMEDESCAPE: return "NAMEDESCAPE";
296 case EMR_COLORCORRECTPALETTE: return "COLORCORRECTPALETTE";
297 case EMR_SETICMPROFILEA: return "SETICMPROFILEA";
298 case EMR_SETICMPROFILEW: return "SETICMPROFILEW";
299 case EMR_ALPHABLEND: return "ALPHABLEND";
300 case EMR_ALPHADIBBLEND: return "ALPHADIBBLEND";
301 case EMR_TRANSPARENTBLT: return "TRANSPARENTBLT";
302 case EMR_TRANSPARENTDIB: return "TRANSPARENTDIB";
303 case EMR_GRADIENTFILL: return "GRADIENTFILL";
304 case EMR_SETLINKEDUFIS: return "SETLINKEDUFIS";
305 case EMR_SETTEXTJUSTIFICATION: return "SETTEXTJUSTIFICATION";
306 default:
307 // Yes, return a pointer to a static buffer. This is a very
308 // local debugging output function, so no big deal.
309 static char buffer[11];
310 o3tl::sprintf(buffer, "0x%08" SAL_PRIxUINT32, nRecType);
311 return buffer;
313 #endif
316 struct BLENDFUNCTION
318 unsigned char aBlendOperation;
319 unsigned char aBlendFlags;
320 unsigned char aSrcConstantAlpha;
321 unsigned char aAlphaFormat;
323 friend SvStream& operator>>(SvStream& rInStream, BLENDFUNCTION& rBlendFun);
326 SvStream& operator>>(SvStream& rInStream, BLENDFUNCTION& rBlendFun)
328 rInStream.ReadUChar(rBlendFun.aBlendOperation);
329 rInStream.ReadUChar(rBlendFun.aBlendFlags);
330 rInStream.ReadUChar(rBlendFun.aSrcConstantAlpha);
331 rInStream.ReadUChar(rBlendFun.aAlphaFormat);
332 return rInStream;
335 bool ImplReadRegion( basegfx::B2DPolyPolygon& rPolyPoly, SvStream& rStream, sal_uInt32 nLen, Point aWinOrg )
337 if (nLen < 32) // 32 bytes - Size of RegionDataHeader
338 return false;
340 sal_uInt32 nHdSize, nType, nCountRects, nRgnSize;
341 rStream.ReadUInt32(nHdSize);
342 rStream.ReadUInt32(nType);
343 rStream.ReadUInt32(nCountRects);
344 rStream.ReadUInt32(nRgnSize);
346 sal_Int32 nLeft, nTop, nRight, nBottom;
347 //bounds of the region
348 rStream.ReadInt32(nLeft);
349 rStream.ReadInt32(nTop);
350 rStream.ReadInt32(nRight);
351 rStream.ReadInt32(nBottom);
353 if (!rStream.good() || nCountRects == 0 || nType != emfio::RDH_RECTANGLES)
354 return false;
356 SAL_INFO("emfio", "\t\tBounds Left: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom);
358 nLen -= 32;
360 sal_uInt32 nSize;
361 if (o3tl::checked_multiply<sal_uInt32>(nCountRects, 16, nSize))
362 return false;
363 if (nLen < nSize)
364 return false;
366 for (sal_uInt32 i = 0; i < nCountRects; ++i)
368 rStream.ReadInt32(nLeft);
369 rStream.ReadInt32(nTop);
370 rStream.ReadInt32(nRight);
371 rStream.ReadInt32(nBottom);
372 nLeft += aWinOrg.X();
373 nRight += aWinOrg.X();
374 nTop += aWinOrg.Y();
375 nBottom += aWinOrg.Y();
376 rPolyPoly.append( basegfx::utils::createPolygonFromRect( ::basegfx::B2DRectangle( nLeft, nTop, nRight, nBottom ) ) );
377 SAL_INFO("emfio", "\t\t" << i << " Left: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom);
379 if (!utl::ConfigManager::IsFuzzing())
381 rPolyPoly = basegfx::utils::solveCrossovers(rPolyPoly);
382 rPolyPoly = basegfx::utils::stripNeutralPolygons(rPolyPoly);
383 rPolyPoly = basegfx::utils::stripDispensablePolygons(rPolyPoly);
385 return true;
388 } // anonymous namespace
390 namespace emfio
392 EmfReader::EmfReader(SvStream& rStream,GDIMetaFile& rGDIMetaFile)
393 : MtfTools(rGDIMetaFile, rStream)
394 , mnRecordCount(0)
395 , mbRecordPath(false)
396 , mbEMFPlus(false)
397 , mbEMFPlusDualMode(false)
401 EmfReader::~EmfReader()
405 const sal_uInt32 EMR_COMMENT_BEGINGROUP = 0x00000002;
406 const sal_uInt32 EMR_COMMENT_ENDGROUP = 0x00000003;
407 const sal_uInt32 EMR_COMMENT_MULTIFORMATS = 0x40000004;
408 const sal_uInt32 EMR_COMMENT_WINDOWS_METAFILE = 0x80000001;
410 void EmfReader::ReadGDIComment(sal_uInt32 nCommentId)
412 sal_uInt32 nPublicCommentIdentifier(0);
413 mpInputStream->ReadUInt32(nPublicCommentIdentifier);
415 SAL_INFO("emfio", "\t\tEMR_COMMENT_PUBLIC, id: 0x" << std::hex << nCommentId << std::dec);
416 switch (nPublicCommentIdentifier)
418 case EMR_COMMENT_BEGINGROUP:
420 SAL_INFO("emfio", "\t\t\tEMR_COMMENT_BEGINGROUP");
421 sal_uInt32 left, top, right, bottom;
422 mpInputStream->ReadUInt32(left).ReadUInt32(top).ReadUInt32(right).ReadUInt32(bottom);
424 SAL_INFO("emfio", "\t\t\t\tBounding rect");
425 SAL_INFO("emfio", "\t\t\t\t\tLeft: " << left);
426 SAL_INFO("emfio", "\t\t\t\t\tTop: " << top);
427 SAL_INFO("emfio", "\t\t\t\t\tRight: " << right);
428 SAL_INFO("emfio", "\t\t\t\t\tBottom: " << bottom);
430 sal_uInt32 nDescChars(0);
431 mpInputStream->ReadUInt32(nDescChars);
433 OUString aDesc;
434 for (sal_uInt32 i=0; i < nDescChars; i++)
436 sal_uInt16 cChar(0);
437 mpInputStream->ReadUInt16(cChar);
438 if (cChar == '\0')
439 break;
441 sal_Unicode cUniChar = static_cast<sal_Unicode>(cChar);
442 aDesc = aDesc + OUStringChar(cUniChar);
445 SAL_INFO("emfio", "\t\tDescription: " << aDesc);
447 break;
449 case EMR_COMMENT_ENDGROUP:
450 SAL_INFO("emfio", "\t\t\tEMR_COMMENT_ENDGROUP");
451 break;
453 case EMR_COMMENT_MULTIFORMATS:
454 ReadMultiformatsComment();
455 break;
457 case EMR_COMMENT_WINDOWS_METAFILE:
458 SAL_WARN("emfio", "\t\tEMR_COMMENT_WINDOWS_METAFILE not implemented");
459 break;
461 default:
462 SAL_WARN("emfio", "\t\tEMR_COMMENT_PUBLIC not implemented, id: 0x" << std::hex << nCommentId << std::dec);
463 break;
467 void EmfReader::ReadMultiformatsComment()
469 tools::Rectangle aOutputRect = EmfReader::ReadRectangle();
471 sal_uInt32 nCountFormats(0);
472 mpInputStream->ReadUInt32(nCountFormats);
473 if (nCountFormats < 1)
475 return;
478 // Read the first EmrFormat.
479 sal_uInt32 nSignature(0);
480 mpInputStream->ReadUInt32(nSignature);
481 if (nSignature != PDF_SIGNATURE)
483 return;
486 sal_uInt32 nVersion(0);
487 mpInputStream->ReadUInt32(nVersion);
488 if (nVersion != 1)
490 return;
493 sal_uInt32 nSizeData(0);
494 mpInputStream->ReadUInt32(nSizeData);
495 if (!nSizeData || nSizeData > mpInputStream->remainingSize())
497 return;
500 sal_uInt32 nOffData(0);
501 mpInputStream->ReadUInt32(nOffData);
502 if (!nOffData)
504 return;
507 std::vector<char> aPdfData(nSizeData);
508 mpInputStream->ReadBytes(aPdfData.data(), aPdfData.size());
509 if (!mpInputStream->good())
511 return;
514 SvMemoryStream aPdfStream;
515 aPdfStream.WriteBytes(aPdfData.data(), aPdfData.size());
516 aPdfStream.Seek(0);
517 Graphic aGraphic;
518 if (!vcl::ImportPDF(aPdfStream, aGraphic))
520 return;
523 // aGraphic will be the only output of the EMF parser, so its size hint can be the same as
524 // ours.
525 aGraphic.getVectorGraphicData()->setSizeHint(maSizeHint);
527 maBmpSaveList.emplace_back(
528 aGraphic.GetBitmapEx(), aOutputRect, SRCCOPY, /*bForceAlpha=*/true);
529 const std::shared_ptr<VectorGraphicData> pVectorGraphicData
530 = aGraphic.getVectorGraphicData();
531 if (!pVectorGraphicData)
533 return;
536 if (pVectorGraphicData->getType() != VectorGraphicDataType::Pdf)
538 return;
541 mbReadOtherGraphicFormat = true;
544 void EmfReader::ReadEMFPlusComment(sal_uInt32 length, bool& bHaveDC)
546 if (!mbEMFPlus)
548 PassEMFPlusHeaderInfo();
550 #if OSL_DEBUG_LEVEL > 1
551 // debug code - write the stream to debug file /tmp/emf-stream.emf
552 sal_uInt64 const pos = mpInputStream->Tell();
553 mpInputStream->Seek(0);
554 SvFileStream file( OUString( "/tmp/emf-stream.emf" ), StreamMode::WRITE | StreamMode::TRUNC );
556 mpInputStream->WriteStream(file);
557 file.Flush();
558 file.Close();
560 mpInputStream->Seek( pos );
561 #endif
565 mbEMFPlus = true;
566 sal_uInt64 const pos = mpInputStream->Tell();
567 auto buffer = std::make_unique<char[]>( length );
568 PassEMFPlus( buffer.get(), mpInputStream->ReadBytes(buffer.get(), length) );
569 buffer.reset();
570 mpInputStream->Seek( pos );
572 bHaveDC = false;
574 // skip in SeekRel if impossibly unavailable
575 sal_uInt32 nRemainder = length;
577 const size_t nRequiredHeaderSize = 12;
578 while (nRemainder >= nRequiredHeaderSize)
580 sal_uInt16 type(0), flags(0);
581 sal_uInt32 size(0), dataSize(0);
583 mpInputStream->ReadUInt16( type ).ReadUInt16( flags ).ReadUInt32( size ).ReadUInt32( dataSize );
584 nRemainder -= nRequiredHeaderSize;
586 SAL_INFO("emfio", "\t\tEMF+ record type: 0x" << std::hex << type << std::dec);
588 // Get Device Context
589 // TODO We should use EmfPlusRecordType::GetDC instead
590 if( type == 0x4004 )
592 bHaveDC = true;
593 SAL_INFO("emfio", "\t\tEMF+ lock DC (device context)");
596 // look for the "dual mode" in header
597 // it indicates that either EMF or EMF+ records should be processed
598 // 0x4001 = EMF+ header
599 // flags & 1 = dual mode active
600 if ( type == 0x4001 && flags & 1 )
602 mbEMFPlusDualMode = true;
603 SAL_INFO ("emfio", "\t\tEMF+ dual mode detected");
606 // Get the length of the remaining data of this record based
607 // on the alleged size
608 sal_uInt32 nRemainingRecordData = size >= nRequiredHeaderSize ?
609 size-nRequiredHeaderSize : 0;
610 // clip to available size
611 nRemainingRecordData = std::min(nRemainingRecordData, nRemainder);
612 mpInputStream->SeekRel(nRemainingRecordData);
613 nRemainder -= nRemainingRecordData;
615 mpInputStream->SeekRel(nRemainder);
618 // these are referenced from inside the templates
619 static SvStream& operator >> (SvStream& rStream, sal_Int16 &n)
621 return rStream.ReadInt16(n);
624 static SvStream& operator >> (SvStream& rStream, sal_Int32 &n)
626 return rStream.ReadInt32(n);
630 * Reads polygons from the stream.
631 * The \<class T> parameter is for the type of the points (sal_uInt32 or sal_uInt16).
632 * skipFirst: if the first point read is the 0th point or the 1st point in the array.
633 * */
634 template <class T>
635 tools::Polygon EmfReader::ReadPolygonWithSkip(const bool skipFirst, sal_uInt32 nNextPos)
637 sal_uInt32 nPoints(0), nStartIndex(0);
638 mpInputStream->SeekRel( 16 );
639 mpInputStream->ReadUInt32( nPoints );
640 if (skipFirst)
642 nPoints ++;
643 nStartIndex ++;
646 return ReadPolygon<T>(nStartIndex, nPoints, nNextPos);
650 * Reads polygons from the stream.
651 * The \<class T> parameter is for the type of the points
652 * nStartIndex: which is the starting index in the polygon of the first point read
653 * nPoints: number of points
654 * mpInputStream: the stream containing the polygons
655 * */
656 template <class T>
657 tools::Polygon EmfReader::ReadPolygon(sal_uInt32 nStartIndex, sal_uInt32 nPoints, sal_uInt32 nNextPos)
659 SAL_INFO ("emfio", "\t\tPolygon:");
661 bool bRecordOk = nPoints <= SAL_MAX_UINT16;
662 SAL_WARN_IF(!bRecordOk, "emfio", "polygon record has more polygons than we can handle");
663 if (!bRecordOk || !nPoints)
664 return tools::Polygon();
666 auto nRemainingSize = std::min(nNextPos - mpInputStream->Tell(), mpInputStream->remainingSize());
667 auto nMaxPossiblePoints = nRemainingSize / (sizeof(T) * 2);
668 auto nPointCount = nPoints - nStartIndex;
669 if (nPointCount > nMaxPossiblePoints)
671 SAL_WARN("emfio", "polygon claims more points than record can provide, truncating");
672 nPoints = nMaxPossiblePoints + nStartIndex;
675 tools::Polygon aPolygon(nPoints);
676 for (sal_uInt32 i = nStartIndex ; i < nPoints && mpInputStream->good(); i++ )
678 T nX, nY;
679 *mpInputStream >> nX >> nY;
681 SAL_INFO("emfio", "\t\t\tPoint " << i << " of " << nPoints - 1 << ": " << nX << ", " << nY);
683 if (!mpInputStream->good())
685 SAL_WARN("emfio", "short read on polygon, truncating");
686 aPolygon.SetSize(i);
687 break;
689 aPolygon[ i ] = Point( nX, nY );
692 return aPolygon;
696 * Reads a polyline from the WMF file and draws it
697 * The \<class T> parameter refers to the type of the points. (e.g. sal_uInt16 or sal_uInt32)
698 * */
699 template <class T>
700 void EmfReader::ReadAndDrawPolyLine(sal_uInt32 nNextPos)
702 SAL_INFO("emfio", "\t\tPolyline: ");
704 mpInputStream->SeekRel( 0x10 ); // TODO Skipping Bounds. A 128-bit WMF RectL object (specifies the bounding rectangle in device units.)
706 sal_uInt32 nNumberOfPolylines = 0;
707 mpInputStream->ReadUInt32( nNumberOfPolylines );
708 SAL_INFO("emfio", "\t\t\tPolylines: " << nNumberOfPolylines);
710 sal_uInt32 nCount = 0;
711 mpInputStream->ReadUInt32( nCount ); // total number of points in all polylines
712 SAL_INFO("emfio", "\t\t\tPoints: " << nCount);
714 const auto nEndPos = std::min(nNextPos, mnEndPos);
715 if (mpInputStream->Tell() >= nEndPos)
716 return;
718 // taking the amount of points of each polygon, retrieving the total number of points
719 if ( !(mpInputStream->good() &&
720 ( nNumberOfPolylines < SAL_MAX_UINT32 / sizeof( sal_uInt16 ) ) &&
721 ( nNumberOfPolylines * sizeof( sal_uInt16 ) ) <= ( nEndPos - mpInputStream->Tell() ))
723 return;
725 std::unique_ptr< sal_uInt32[] > pnPolylinePointCount( new sal_uInt32[ nNumberOfPolylines ] );
726 for ( sal_uInt32 i = 0; i < nNumberOfPolylines && mpInputStream->good(); i++ )
728 sal_uInt32 nPoints;
729 mpInputStream->ReadUInt32( nPoints );
730 SAL_INFO("emfio", "\t\t\tPoint " << i << " of " << nNumberOfPolylines << ": " << nPoints);
731 pnPolylinePointCount[ i ] = nPoints;
734 // Get polyline points:
735 for ( sal_uInt32 i = 0; ( i < nNumberOfPolylines ) && mpInputStream->good(); i++ )
737 tools::Polygon aPolygon = ReadPolygon<T>(0, pnPolylinePointCount[i], nNextPos);
738 DrawPolyLine(std::move(aPolygon), false, mbRecordPath);
743 * Reads a poly polygon from the WMF file and draws it.
744 * The \<class T> parameter refers to the type of the points. (e.g. sal_uInt16 or sal_uInt32)
745 * */
746 template <class T>
747 void EmfReader::ReadAndDrawPolyPolygon(sal_uInt32 nNextPos)
749 SAL_INFO("emfio", "\t\tPolygon: ");
750 mpInputStream->SeekRel( 0x10 ); // RectL bounds
752 sal_uInt32 nPoly(0), nGesPoints(0), nReadPoints(0);
753 // Number of polygons
754 mpInputStream->ReadUInt32( nPoly ).ReadUInt32( nGesPoints );
755 SAL_INFO("emfio", "\t\t\tPolygons: " << nPoly);
756 SAL_INFO("emfio", "\t\t\tPoints: " << nGesPoints);
758 const auto nEndPos = std::min(nNextPos, mnEndPos);
759 if (mpInputStream->Tell() >= nEndPos)
760 return;
761 if (!mpInputStream->good())
762 return;
763 //check against numeric overflowing
764 if (nGesPoints >= SAL_MAX_UINT32 / sizeof(Point))
765 return;
766 if (nPoly >= SAL_MAX_UINT32 / sizeof(sal_uInt16))
767 return;
768 if (nPoly * sizeof(sal_uInt16) > nEndPos - mpInputStream->Tell())
769 return;
771 // Get number of points in each polygon
772 std::vector<sal_uInt16> aPoints(nPoly);
773 for (sal_uInt32 i = 0; i < nPoly && mpInputStream->good(); ++i)
775 sal_uInt32 nPoints(0);
776 mpInputStream->ReadUInt32( nPoints );
778 SAL_INFO("emfio", "\t\t\t\tPolygon " << i << " points: " << nPoints);
780 aPoints[i] = static_cast<sal_uInt16>(nPoints);
783 if ( mpInputStream->good() && ( nGesPoints * (sizeof(T)+sizeof(T)) ) <= ( nEndPos - mpInputStream->Tell() ) )
785 // Get polygon points
786 tools::PolyPolygon aPolyPoly(nPoly);
787 for (sal_uInt32 i = 0; i < nPoly && mpInputStream->good(); ++i)
789 const sal_uInt16 nPointCount(aPoints[i]);
790 std::vector<Point> aPtAry(nPointCount);
791 for (sal_uInt16 j = 0; j < nPointCount && mpInputStream->good(); ++j)
793 T nX(0), nY(0);
794 *mpInputStream >> nX >> nY;
795 aPtAry[j] = Point( nX, nY );
796 ++nReadPoints;
799 aPolyPoly.Insert(tools::Polygon(aPtAry.size(), aPtAry.data()));
802 DrawPolyPolygon(aPolyPoly, mbRecordPath);
805 OSL_ENSURE(nReadPoints == nGesPoints, "The number Points processed from EMR_POLYPOLYGON is unequal imported number (!)");
808 bool EmfReader::ReadEnhWMF()
810 sal_uInt32 nStretchBltMode = 0;
811 sal_uInt32 nNextPos(0),
812 nW(0), nH(0), nColor(0), nIndex(0),
813 nDat32(0), nNom1(0), nDen1(0), nNom2(0), nDen2(0);
814 sal_Int32 nX32(0), nY32(0), nx32(0), ny32(0);
816 bool bStatus = ReadHeader();
817 bool bHaveDC = false;
819 OUString aEMFPlusDisable;
820 rtl::Bootstrap::get("EMF_PLUS_DISABLE", aEMFPlusDisable);
821 bool bEnableEMFPlus = aEMFPlusDisable.isEmpty();
822 if (!mbEnableEMFPlus)
824 // EMF+ is enabled if neither the bootstrap variable, not the member variable disables
825 // it.
826 bEnableEMFPlus = mbEnableEMFPlus;
829 SAL_INFO("emfio", "EMF+ reading is " << (bEnableEMFPlus ? "enabled" : "disabled"));
831 while (bStatus && mnRecordCount-- && mpInputStream->good() && !mbReadOtherGraphicFormat)
833 sal_uInt32 nRecType(0), nRecSize(0);
834 mpInputStream->ReadUInt32(nRecType).ReadUInt32(nRecSize);
836 if ( !mpInputStream->good() || ( nRecSize < 8 ) || ( nRecSize & 3 ) ) // Parameters are always divisible by 4
838 bStatus = false;
839 break;
842 auto nCurPos = mpInputStream->Tell();
844 if (mnEndPos < nCurPos - 8)
846 bStatus = false;
847 break;
850 const sal_uInt32 nMaxPossibleRecSize = mnEndPos - (nCurPos - 8);
851 if (nRecSize > nMaxPossibleRecSize)
853 bStatus = false;
854 break;
857 nNextPos = nCurPos + (nRecSize - 8);
859 if( !maBmpSaveList.empty()
860 && ( nRecType != EMR_STRETCHBLT )
861 && ( nRecType != EMR_STRETCHDIBITS )
863 ResolveBitmapActions( maBmpSaveList );
866 bool bFlag = false;
868 SAL_INFO("emfio", "0x" << std::hex << (nNextPos - nRecSize) << "-0x" << nNextPos << " " << record_type_name(nRecType) << " size: "
869 << std::dec << nRecSize);
871 if( bEnableEMFPlus && nRecType == EMR_COMMENT )
873 sal_uInt32 length;
875 mpInputStream->ReadUInt32( length );
877 SAL_INFO("emfio", "\tGDI comment, length: " << length);
879 if( mpInputStream->good() && length >= 4 && length <= mpInputStream->remainingSize() ) {
880 sal_uInt32 nCommentId;
882 mpInputStream->ReadUInt32( nCommentId );
884 SAL_INFO("emfio", "\t\tbegin " << static_cast<char>(nCommentId & 0xff) << static_cast<char>((nCommentId & 0xff00) >> 8) << static_cast<char>((nCommentId & 0xff0000) >> 16) << static_cast<char>((nCommentId & 0xff000000) >> 24) << " id: 0x" << std::hex << nCommentId << std::dec);
886 if( nCommentId == EMR_COMMENT_EMFPLUS && nRecSize >= 12 )
888 // [MS-EMF] 2.3.3: DataSize includes both CommentIdentifier and CommentRecordParm fields.
889 // We have already read 4-byte CommentIdentifier, so reduce length appropriately
890 ReadEMFPlusComment( length-4, bHaveDC );
892 else if( nCommentId == EMR_COMMENT_PUBLIC && nRecSize >= 12 )
894 ReadGDIComment(nCommentId);
896 else if( nCommentId == EMR_COMMENT_EMFSPOOL && nRecSize >= 12 )
898 SAL_WARN("emfio", "\t\tEMFSPOOL not implemented, id: 0x" << std::hex << nCommentId << std::dec);
899 // TODO Implement reading EMFSPOOL comment
902 else
904 SAL_WARN("emfio", "\t\tunknown id: 0x" << std::hex << nCommentId << std::dec);
908 else if ( !bHaveDC && mbEMFPlusDualMode && nRecType != EMR_HEADER && nRecType != EMR_EOF )
910 // skip content (EMF record) in dual mode
911 // we process only EMR_COMMENT (see above) to access EMF+ data
912 // with 2 exceptions, according to EMF+ specification:
913 // EMR_HEADER and EMR_EOF
914 // if a device context is given (bHaveDC) process the following EMF record, too.
916 else if( !mbEMFPlus || bHaveDC || nRecType == EMR_EOF )
918 switch( nRecType )
920 case EMR_POLYBEZIERTO :
921 DrawPolyBezier(ReadPolygonWithSkip<sal_Int32>(true, nNextPos), true, mbRecordPath);
922 break;
923 case EMR_POLYBEZIER :
924 DrawPolyBezier(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), false, mbRecordPath);
925 break;
927 case EMR_POLYGON :
928 DrawPolygon(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), mbRecordPath);
929 break;
931 case EMR_POLYLINETO :
932 DrawPolyLine(ReadPolygonWithSkip<sal_Int32>(true, nNextPos), true, mbRecordPath);
933 break;
935 case EMR_POLYLINE :
936 DrawPolyLine(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), false, mbRecordPath);
937 break;
939 case EMR_POLYPOLYLINE :
940 ReadAndDrawPolyLine<sal_Int32>(nNextPos);
941 break;
943 case EMR_POLYPOLYGON :
944 ReadAndDrawPolyPolygon<sal_Int32>(nNextPos);
945 break;
947 case EMR_SETWINDOWEXTEX :
949 sal_Int32 w = 0, h = 0;
950 mpInputStream->ReadInt32( w ).ReadInt32( h );
951 SAL_INFO("emfio", "\t\tWidth: " << w);
952 SAL_INFO("emfio", "\t\tHeight: " << h);
953 SetWinExt( Size( w, h ), true);
955 break;
957 case EMR_SETWINDOWORGEX :
959 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
960 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
961 SetWinOrg( Point( nX32, nY32 ), true);
963 break;
965 case EMR_SCALEWINDOWEXTEX :
967 mpInputStream->ReadUInt32( nNom1 ).ReadUInt32( nDen1 ).ReadUInt32( nNom2 ).ReadUInt32( nDen2 );
968 SAL_INFO("emfio", "\t\tHorizontal scale: " << nNom1 << " / " << nDen1);
969 SAL_INFO("emfio", "\t\tVertical scale: " << nNom2 << " / " << nDen2);
971 if (nDen1 != 0 && nDen2 != 0)
972 ScaleWinExt( static_cast<double>(nNom1) / nDen1, static_cast<double>(nNom2) / nDen2 );
973 else
974 SAL_WARN("vcl.emf", "ignoring bogus divide by zero");
976 break;
978 case EMR_SETVIEWPORTORGEX :
980 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
981 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
982 SetDevOrg( Point( nX32, nY32 ) );
984 break;
986 case EMR_SCALEVIEWPORTEXTEX :
988 mpInputStream->ReadUInt32( nNom1 ).ReadUInt32( nDen1 ).ReadUInt32( nNom2 ).ReadUInt32( nDen2 );
989 SAL_INFO("emfio", "\t\tHorizontal scale: " << nNom1 << " / " << nDen1);
990 SAL_INFO("emfio", "\t\tVertical scale: " << nNom2 << " / " << nDen2);
992 if (nDen1 != 0 && nDen2 != 0)
993 ScaleDevExt( static_cast<double>(nNom1) / nDen1, static_cast<double>(nNom2) / nDen2 );
994 else
995 SAL_WARN("vcl.emf", "ignoring bogus divide by zero");
997 break;
999 case EMR_SETVIEWPORTEXTEX :
1001 sal_Int32 w = 0, h = 0;
1002 mpInputStream->ReadInt32( w ).ReadInt32( h );
1003 SAL_INFO("emfio", "\t\tWidth: " << w);
1004 SAL_INFO("emfio", "\t\tHeight: " << h);
1006 SetDevExt( Size( w, h ) );
1008 break;
1010 case EMR_EOF :
1011 mnRecordCount = 0;
1012 break;
1014 case EMR_SETPIXELV :
1016 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
1017 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
1018 DrawPixel( Point( nX32, nY32 ), ReadColor() );
1020 break;
1022 case EMR_SETMAPMODE :
1024 sal_uInt32 nMapMode(0);
1025 mpInputStream->ReadUInt32( nMapMode );
1026 SAL_INFO("emfio", "\t\tMapMode: 0x" << std::hex << nMapMode << std::dec);
1027 SetMapMode( static_cast<MappingMode>(nMapMode) );
1029 break;
1031 case EMR_SETBKMODE :
1033 mpInputStream->ReadUInt32( nDat32 );
1034 SAL_INFO("emfio", "\t\tBkMode: 0x" << std::hex << nDat32 << std::dec);
1035 SetBkMode( static_cast<BackgroundMode>(nDat32) );
1037 break;
1039 case EMR_SETPOLYFILLMODE :
1040 break;
1042 case EMR_SETROP2 :
1044 mpInputStream->ReadUInt32( nDat32 );
1045 SAL_INFO("emfio", "\t\tROP2: 0x" << std::hex << nDat32 << std::dec);
1046 SetRasterOp( static_cast<WMFRasterOp>(nDat32) );
1048 break;
1050 case EMR_SETSTRETCHBLTMODE :
1052 mpInputStream->ReadUInt32( nStretchBltMode );
1053 SAL_INFO("emfio", "\t\tStretchBltMode: 0x" << std::hex << nDat32 << std::dec);
1055 break;
1057 case EMR_SETTEXTALIGN :
1059 mpInputStream->ReadUInt32( nDat32 );
1060 SAL_INFO("emfio", "\t\tTextAlign: 0x" << std::hex << nDat32 << std::dec);
1061 SetTextAlign( nDat32 );
1063 break;
1065 case EMR_SETTEXTCOLOR :
1067 SetTextColor( ReadColor() );
1069 break;
1071 case EMR_SETARCDIRECTION:
1073 mpInputStream->ReadUInt32(nIndex);
1074 SetArcDirection(nIndex == ARCDIRECTION_CLOCKWISE);
1076 break;
1078 case EMR_SETBKCOLOR :
1080 SetBkColor( ReadColor() );
1082 break;
1084 case EMR_OFFSETCLIPRGN :
1086 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
1087 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
1088 MoveClipRegion( Size( nX32, nY32 ) );
1090 break;
1092 case EMR_MOVETOEX :
1094 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
1095 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
1096 MoveTo( Point( nX32, nY32 ), mbRecordPath);
1098 break;
1100 case EMR_INTERSECTCLIPRECT :
1102 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 );
1103 IntersectClipRect( ReadRectangle( nX32, nY32, nx32, ny32 ) );
1104 SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")");
1105 SAL_INFO("emfio", "\t\tPoint: (" << nx32 << ", " << ny32 << ")");
1107 break;
1109 case EMR_SAVEDC :
1111 Push();
1113 break;
1115 case EMR_RESTOREDC :
1117 sal_Int32 nSavedDC(0);
1118 mpInputStream->ReadInt32( nSavedDC );
1119 SAL_INFO( "emfio", "\t\t SavedDC Index: " << nSavedDC );
1120 if ( nSavedDC < 0 )
1121 Pop( nSavedDC );
1122 else
1123 Pop( -1 ); // For RestoreDC values above -1, treat as get last element
1125 break;
1127 case EMR_SETWORLDTRANSFORM :
1129 XForm aTempXForm;
1130 *mpInputStream >> aTempXForm;
1131 SetWorldTransform( aTempXForm );
1133 break;
1135 case EMR_MODIFYWORLDTRANSFORM :
1137 sal_uInt32 nMode(0);
1138 XForm aTempXForm;
1139 *mpInputStream >> aTempXForm;
1140 mpInputStream->ReadUInt32( nMode );
1141 ModifyWorldTransform( aTempXForm, static_cast<ModifyWorldTransformMode>(nMode) );
1143 break;
1145 case EMR_SELECTOBJECT :
1147 mpInputStream->ReadUInt32( nIndex );
1148 SelectObject( nIndex );
1150 break;
1152 case EMR_CREATEPEN:
1154 mpInputStream->ReadUInt32(nIndex);
1155 if ((nIndex & ENHMETA_STOCK_OBJECT) == 0)
1157 sal_uInt32 nPenStyle(0);
1158 sal_Int32 nPenWidth(0), nIgnored;
1159 mpInputStream->ReadUInt32(nPenStyle).ReadInt32(nPenWidth).ReadInt32(nIgnored);
1160 SAL_INFO("emfio", "\t\tIndex: " << nIndex << " Style: 0x" << std::hex
1161 << nPenStyle << std::dec
1162 << " PenWidth: " << nPenWidth);
1163 if ((nPenStyle & PS_STYLE_MASK) > PS_INSIDEFRAME)
1164 nPenStyle = PS_COSMETIC;
1165 if ((nPenStyle & PS_GEOMETRIC) == 0)
1166 nPenWidth = 0;
1167 CreateObjectIndexed(nIndex, std::make_unique<WinMtfLineStyle>(ReadColor(), nPenStyle, nPenWidth));
1170 break;
1172 case EMR_EXTCREATEPEN:
1174 mpInputStream->ReadUInt32(nIndex);
1175 if ((nIndex & ENHMETA_STOCK_OBJECT) == 0)
1177 sal_uInt32 offBmi, cbBmi, offBits, cbBits, nPenStyle, nWidth, nBrushStyle, elpNumEntries;
1178 sal_Int32 elpHatch;
1179 mpInputStream->ReadUInt32(offBmi).ReadUInt32(cbBmi).ReadUInt32(offBits).ReadUInt32(cbBits);
1180 mpInputStream->ReadUInt32(nPenStyle).ReadUInt32(nWidth).ReadUInt32(nBrushStyle);
1181 Color aColorRef = ReadColor();
1182 mpInputStream->ReadInt32(elpHatch).ReadUInt32(elpNumEntries);
1184 if (!mpInputStream->good())
1185 bStatus = false;
1186 else
1188 SAL_INFO("emfio", "\t\tStyle: 0x" << std::hex << nPenStyle << std::dec);
1189 if ((nPenStyle & PS_STYLE_MASK) > PS_INSIDEFRAME)
1190 nPenStyle = PS_COSMETIC;
1191 if ((nPenStyle & PS_GEOMETRIC) == 0)
1192 nWidth = 0;
1193 SAL_INFO("emfio", "\t\tWidth: " << nWidth);
1194 CreateObjectIndexed(nIndex, std::make_unique<WinMtfLineStyle>(aColorRef, nPenStyle, nWidth));
1198 break;
1200 case EMR_CREATEBRUSHINDIRECT :
1202 mpInputStream->ReadUInt32( nIndex );
1203 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
1205 sal_uInt32 nStyle;
1206 mpInputStream->ReadUInt32( nStyle );
1207 BrushStyle eStyle = static_cast<BrushStyle>(nStyle);
1208 CreateObjectIndexed(nIndex, std::make_unique<WinMtfFillStyle>( ReadColor(), ( eStyle == BrushStyle::BS_HOLLOW ) ));
1211 break;
1213 case EMR_DELETEOBJECT :
1215 mpInputStream->ReadUInt32( nIndex );
1216 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
1217 DeleteObject( nIndex );
1219 break;
1221 case EMR_ELLIPSE :
1223 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 );
1224 SAL_INFO("emfio", "\t\t Rectangle, left: " << nX32 << ", top: " << nY32 << ", right: " << nx32 << ", bottom: " << ny32);
1226 sal_Int32 w(0), h(0);
1227 if (o3tl::checked_sub(nx32, nX32, w) || o3tl::checked_sub(ny32, nY32, h))
1228 SAL_WARN("emfio", "broken ellipse");
1229 else
1231 tools::Long dw = w / 2;
1232 tools::Long dh = h / 2;
1233 Point aCenter( nX32 + dw, nY32 + dh );
1234 tools::Polygon aPoly( aCenter, dw, dh );
1235 DrawPolygon( std::move(aPoly), mbRecordPath );
1238 break;
1240 case EMR_RECTANGLE :
1242 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 );
1243 SAL_INFO("emfio", "\t\t Rectangle, left: " << nX32 << ", top: " << nY32 << ", right: " << nx32 << ", bottom: " << ny32);
1244 Point aPoints[] { Point(nX32, nY32),
1245 Point(nx32, nY32),
1246 Point(nx32, ny32),
1247 Point(nX32, ny32) };
1248 tools::Polygon aPoly(4, aPoints);
1249 aPoly.Optimize( PolyOptimizeFlags::CLOSE );
1250 DrawPolygon( std::move(aPoly), mbRecordPath );
1252 break;
1254 case EMR_ROUNDRECT :
1256 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadUInt32( nW ).ReadUInt32( nH );
1257 tools::Polygon aRoundRectPoly( ReadRectangle( nX32, nY32, nx32, ny32 ), nW, nH );
1258 DrawPolygon( std::move(aRoundRectPoly), mbRecordPath );
1260 break;
1262 case EMR_ARC :
1263 case EMR_ARCTO :
1264 case EMR_CHORD :
1266 sal_Int32 nStartX, nStartY, nEndX, nEndY;
1267 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadInt32( nStartX ).ReadInt32( nStartY ).ReadInt32( nEndX ).ReadInt32( nEndY );
1268 if (!mpInputStream->good())
1269 bStatus = false;
1270 else
1272 SAL_INFO( "emfio", "\t\t Bounds: " << nX32 << ":" << nY32 << ", " << nx32 << ":" << ny32 << ", Start: " << nStartX << ":" << nStartY << ", End: " << nEndX << ":" << nEndY );
1273 tools::Polygon aPoly(ReadRectangle(nX32, nY32, nx32, ny32), Point(nStartX, nStartY), Point(nEndX, nEndY), PolyStyle::Arc, IsArcDirectionClockWise());
1275 if ( nRecType == EMR_CHORD )
1276 DrawPolygon( std::move(aPoly), mbRecordPath );
1277 else
1278 DrawPolyLine( std::move(aPoly), nRecType == EMR_ARCTO, mbRecordPath );
1281 break;
1283 case EMR_PIE :
1285 sal_Int32 nStartX, nStartY, nEndX, nEndY;
1286 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadInt32( nStartX ).ReadInt32( nStartY ).ReadInt32( nEndX ).ReadInt32( nEndY );
1287 if (!mpInputStream->good())
1288 bStatus = false;
1289 else
1291 tools::Polygon aPoly(ReadRectangle(nX32, nY32, nx32, ny32), Point(nStartX, nStartY), Point(nEndX, nEndY), PolyStyle::Pie, IsArcDirectionClockWise());
1292 DrawPolygon( std::move(aPoly), mbRecordPath );
1295 break;
1297 case EMR_LINETO :
1299 mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 );
1300 LineTo( Point( nX32, nY32 ), mbRecordPath);
1302 break;
1304 case EMR_BEGINPATH :
1306 ClearPath();
1307 mbRecordPath = true;
1309 break;
1311 case EMR_ABORTPATH :
1312 ClearPath();
1313 [[fallthrough]];
1314 case EMR_ENDPATH :
1315 mbRecordPath = false;
1316 break;
1318 case EMR_CLOSEFIGURE :
1319 ClosePath();
1320 break;
1322 case EMR_FILLPATH :
1323 StrokeAndFillPath( false, true );
1324 break;
1326 case EMR_STROKEANDFILLPATH :
1327 StrokeAndFillPath( true, true );
1328 break;
1330 case EMR_STROKEPATH :
1331 StrokeAndFillPath( true, false );
1332 break;
1334 case EMR_SELECTCLIPPATH :
1336 sal_Int32 nClippingMode(0);
1337 mpInputStream->ReadInt32(nClippingMode);
1338 SetClipPath(GetPathObj(), static_cast<RegionMode>(nClippingMode), true);
1340 break;
1342 case EMR_EXTSELECTCLIPRGN :
1344 sal_uInt32 nRemainingRecSize = nRecSize - 8;
1345 if (nRemainingRecSize < 8)
1346 bStatus = false;
1347 else
1349 sal_Int32 nClippingMode(0), cbRgnData(0);
1350 mpInputStream->ReadInt32(cbRgnData);
1351 mpInputStream->ReadInt32(nClippingMode);
1352 nRemainingRecSize -= 8;
1354 // This record's region data should be ignored if mode
1355 // is RGN_COPY - see EMF spec section 2.3.2.2
1356 if (static_cast<RegionMode>(nClippingMode) == RegionMode::RGN_COPY)
1358 SetDefaultClipPath();
1360 else
1362 basegfx::B2DPolyPolygon aPolyPoly;
1363 if (cbRgnData)
1364 ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize, GetWinOrg());
1365 const tools::PolyPolygon aPolyPolygon(aPolyPoly);
1366 SetClipPath(aPolyPolygon, static_cast<RegionMode>(nClippingMode), false);
1370 break;
1372 case EMR_ALPHABLEND:
1374 sal_Int32 xDest(0), yDest(0), cxDest(0), cyDest(0);
1376 BLENDFUNCTION aFunc;
1377 sal_Int32 xSrc(0), ySrc(0), cxSrc(0), cySrc(0);
1378 XForm xformSrc;
1379 sal_uInt32 BkColorSrc(0), iUsageSrc(0), offBmiSrc(0);
1380 sal_uInt32 cbBmiSrc(0), offBitsSrc(0), cbBitsSrc(0);
1382 sal_uInt32 nStart = mpInputStream->Tell() - 8;
1383 mpInputStream->SeekRel( 0x10 );
1385 mpInputStream->ReadInt32( xDest ).ReadInt32( yDest ).ReadInt32( cxDest ).ReadInt32( cyDest );
1386 *mpInputStream >> aFunc;
1387 mpInputStream->ReadInt32( xSrc ).ReadInt32( ySrc );
1388 *mpInputStream >> xformSrc;
1389 mpInputStream->ReadUInt32( BkColorSrc ).ReadUInt32( iUsageSrc ).ReadUInt32( offBmiSrc ).ReadUInt32( cbBmiSrc )
1390 .ReadUInt32( offBitsSrc ).ReadUInt32( cbBitsSrc ).ReadInt32( cxSrc ).ReadInt32( cySrc ) ;
1392 if ( !mpInputStream->good() ||
1393 (cbBitsSrc > (SAL_MAX_UINT32 - 14)) ||
1394 ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc) ||
1395 cxDest == SAL_MAX_INT32 || cyDest == SAL_MAX_INT32 )
1397 bStatus = false;
1399 else
1401 tools::Rectangle aRect(Point(xDest, yDest), Size(cxDest + 1, cyDest + 1));
1403 const sal_uInt32 nSourceSize = cbBmiSrc + cbBitsSrc + 14;
1404 bool bSafeRead = nSourceSize <= (mnEndPos - mnStartPos);
1405 sal_uInt32 nDeltaToDIB5HeaderSize(0);
1406 const bool bReadAlpha(0x01 == aFunc.aAlphaFormat);
1407 if (bSafeRead && bReadAlpha)
1409 // we need to read alpha channel data if AlphaFormat of BLENDFUNCTION is
1410 // AC_SRC_ALPHA (==0x01). To read it, create a temp DIB-File which is ready
1411 // for DIB-5 format
1412 const sal_uInt32 nHeaderSize = getDIBV5HeaderSize();
1413 if (cbBmiSrc > nHeaderSize)
1414 bSafeRead = false;
1415 else
1416 nDeltaToDIB5HeaderSize = nHeaderSize - cbBmiSrc;
1418 if (bSafeRead)
1420 const sal_uInt32 nTargetSize(cbBmiSrc + nDeltaToDIB5HeaderSize + cbBitsSrc + 14);
1421 char* pBuf = new char[ nTargetSize ];
1422 SvMemoryStream aTmp( pBuf, nTargetSize, StreamMode::READ | StreamMode::WRITE );
1424 aTmp.ObjectOwnsMemory( true );
1426 // write BM-Header (14 bytes)
1427 aTmp.WriteUChar( 'B' )
1428 .WriteUChar( 'M' )
1429 .WriteUInt32( cbBitsSrc )
1430 .WriteUInt16( 0 )
1431 .WriteUInt16( 0 )
1432 .WriteUInt32( cbBmiSrc + nDeltaToDIB5HeaderSize + 14 );
1434 // copy DIBInfoHeader from source (cbBmiSrc bytes)
1435 mpInputStream->Seek( nStart + offBmiSrc );
1436 char* pWritePos = pBuf + 14;
1437 auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc);
1438 if (nRead != cbBmiSrc)
1440 // zero remainder if short read
1441 memset(pWritePos + nRead, 0, cbBmiSrc - nRead);
1444 if (bReadAlpha)
1446 // need to add values for all stuff that DIBV5Header is bigger
1447 // than DIBInfoHeader, all values are correctly initialized to zero,
1448 // so we can use memset here
1449 memset(pBuf + cbBmiSrc + 14, 0, nDeltaToDIB5HeaderSize);
1452 // copy bitmap data from source (offBitsSrc bytes)
1453 mpInputStream->Seek( nStart + offBitsSrc );
1454 pWritePos = pBuf + 14 + nDeltaToDIB5HeaderSize + cbBmiSrc;
1455 nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc);
1456 if (nRead != cbBitsSrc)
1458 // zero remainder if short read
1459 memset(pWritePos + nRead, 0, cbBitsSrc - nRead);
1462 aTmp.Seek( 0 );
1464 // prepare to read and fill BitmapEx
1465 BitmapEx aBitmapEx;
1467 if(bReadAlpha)
1469 Bitmap aBitmap;
1470 AlphaMask aAlpha;
1472 if(ReadDIBV5(aBitmap, aAlpha, aTmp))
1474 aBitmapEx = BitmapEx(aBitmap, aAlpha);
1477 else
1479 Bitmap aBitmap;
1481 if(ReadDIB(aBitmap, aTmp, true))
1483 if(0xff != aFunc.aSrcConstantAlpha)
1485 // add const alpha channel
1486 aBitmapEx = BitmapEx(
1487 aBitmap,
1488 AlphaMask(aBitmap.GetSizePixel(), &aFunc.aSrcConstantAlpha));
1490 else
1492 // just use Bitmap
1493 aBitmapEx = BitmapEx(aBitmap);
1498 if(!aBitmapEx.IsEmpty())
1500 // test if it is sensible to crop
1501 if (cxSrc > 0 && cySrc > 0 && xSrc >= 0 && ySrc >= 0)
1503 sal_Int32 xEndSrc;
1504 sal_Int32 yEndSrc;
1505 if (!o3tl::checked_add(xSrc, cxSrc, xEndSrc) && xEndSrc < aBitmapEx.GetSizePixel().Width() &&
1506 !o3tl::checked_add(ySrc, cySrc, yEndSrc) && yEndSrc < aBitmapEx.GetSizePixel().Height())
1508 const tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) );
1509 aBitmapEx.Crop( aCropRect );
1513 #ifdef DBG_UTIL
1514 static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
1516 if(bDoSaveForVisualControl)
1518 // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
1519 static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
1520 if(!sDumpPath.isEmpty())
1522 SvFileStream aNew(sDumpPath + "metafile_content.png",
1523 StreamMode::WRITE | StreamMode::TRUNC);
1524 vcl::PngImageWriter aPNGWriter(aNew);
1525 aPNGWriter.write(aBitmapEx);
1528 #endif
1529 maBmpSaveList.emplace_back(aBitmapEx, aRect, SRCAND|SRCINVERT);
1534 break;
1536 case EMR_BITBLT : // PASSTHROUGH INTENDED
1537 case EMR_STRETCHBLT :
1539 sal_Int32 xDest, yDest, cxDest, cyDest, xSrc, ySrc, cxSrc, cySrc;
1540 sal_uInt32 dwRop, iUsageSrc, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc;
1541 XForm xformSrc;
1543 sal_uInt32 nStart = mpInputStream->Tell() - 8;
1545 mpInputStream->SeekRel( 0x10 );
1546 mpInputStream->ReadInt32( xDest ).ReadInt32( yDest ).ReadInt32( cxDest ).ReadInt32( cyDest ).ReadUInt32( dwRop ).ReadInt32( xSrc ).ReadInt32( ySrc )
1547 >> xformSrc;
1548 mpInputStream->ReadUInt32( nColor ).ReadUInt32( iUsageSrc ).ReadUInt32( offBmiSrc ).ReadUInt32( cbBmiSrc )
1549 .ReadUInt32( offBitsSrc ).ReadUInt32( cbBitsSrc );
1551 if ( nRecType == EMR_STRETCHBLT )
1552 mpInputStream->ReadInt32( cxSrc ).ReadInt32( cySrc );
1553 else
1554 cxSrc = cySrc = 0;
1556 if (!mpInputStream->good() || (cbBitsSrc > (SAL_MAX_UINT32 - 14)) || ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc))
1557 bStatus = false;
1558 else
1560 Bitmap aBitmap;
1561 tools::Rectangle aRect(Point(xDest, yDest), Size(cxDest, cyDest));
1563 sal_uInt32 nSize = cbBmiSrc + cbBitsSrc + 14;
1564 if ( nSize <= ( mnEndPos - mnStartPos ) )
1566 char* pBuf = new char[ nSize ];
1567 SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE );
1568 aTmp.ObjectOwnsMemory( true );
1569 aTmp.WriteUChar( 'B' )
1570 .WriteUChar( 'M' )
1571 .WriteUInt32( cbBitsSrc )
1572 .WriteUInt16( 0 )
1573 .WriteUInt16( 0 )
1574 .WriteUInt32( cbBmiSrc + 14 );
1576 mpInputStream->Seek( nStart + offBmiSrc );
1577 char* pWritePos = pBuf + 14;
1578 auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc);
1579 if (nRead != cbBmiSrc)
1581 // zero remainder if short read
1582 memset(pWritePos + nRead, 0, cbBmiSrc - nRead);
1585 mpInputStream->Seek( nStart + offBitsSrc );
1586 pWritePos = pBuf + 14 + cbBmiSrc;
1587 nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc);
1588 if (nRead != cbBitsSrc)
1590 // zero remainder if short read
1591 memset(pWritePos + nRead, 0, cbBitsSrc - nRead);
1594 aTmp.Seek( 0 );
1595 ReadDIB(aBitmap, aTmp, true);
1597 // test if it is sensible to crop
1598 if ( (cxSrc > 0) && (cySrc > 0) &&
1599 (xSrc >= 0) && (ySrc >= 0) &&
1600 (aBitmap.GetSizePixel().Width() >= cxSrc) &&
1601 (xSrc <= aBitmap.GetSizePixel().Width() - cxSrc) &&
1602 (aBitmap.GetSizePixel().Height() >= cySrc) &&
1603 (ySrc <= aBitmap.GetSizePixel().Height() - cySrc) )
1605 tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) );
1606 aBitmap.Crop( aCropRect );
1609 maBmpSaveList.emplace_back(aBitmap, aRect, dwRop);
1613 break;
1615 case EMR_STRETCHDIBITS :
1617 sal_Int32 xDest, yDest, xSrc, ySrc, cxSrc, cySrc, cxDest, cyDest;
1618 sal_uInt32 offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc, iUsageSrc, dwRop;
1619 sal_uInt32 nStart = mpInputStream->Tell() - 8;
1621 mpInputStream->SeekRel( 0x10 );
1622 mpInputStream->ReadInt32( xDest )
1623 .ReadInt32( yDest )
1624 .ReadInt32( xSrc )
1625 .ReadInt32( ySrc )
1626 .ReadInt32( cxSrc )
1627 .ReadInt32( cySrc )
1628 .ReadUInt32( offBmiSrc )
1629 .ReadUInt32( cbBmiSrc )
1630 .ReadUInt32( offBitsSrc )
1631 .ReadUInt32( cbBitsSrc )
1632 .ReadUInt32( iUsageSrc )
1633 .ReadUInt32( dwRop )
1634 .ReadInt32( cxDest )
1635 .ReadInt32( cyDest );
1637 if (!mpInputStream->good() ||
1638 ((SAL_MAX_UINT32 - 14) < cbBitsSrc) ||
1639 ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc))
1641 bStatus = false;
1643 else
1645 Bitmap aBitmap;
1646 tools::Rectangle aRect(xDest, yDest);
1647 aRect.SaturatingSetSize(Size(cxDest, cyDest));
1649 sal_uInt32 nSize = cbBmiSrc + cbBitsSrc + 14;
1650 if ( nSize <= ( mnEndPos - mnStartPos ) )
1652 char* pBuf = new char[ nSize ];
1653 SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE );
1654 aTmp.ObjectOwnsMemory( true );
1655 aTmp.WriteUChar( 'B' )
1656 .WriteUChar( 'M' )
1657 .WriteUInt32( cbBitsSrc )
1658 .WriteUInt16( 0 )
1659 .WriteUInt16( 0 )
1660 .WriteUInt32( cbBmiSrc + 14 );
1662 mpInputStream->Seek( nStart + offBmiSrc );
1663 char* pWritePos = pBuf + 14;
1664 auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc);
1665 if (nRead != cbBmiSrc)
1667 // zero remainder if short read
1668 memset(pWritePos + nRead, 0, cbBmiSrc - nRead);
1671 mpInputStream->Seek( nStart + offBitsSrc );
1672 pWritePos = pBuf + 14 + cbBmiSrc;
1673 nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc);
1674 if (nRead != cbBitsSrc)
1676 // zero remainder if short read
1677 memset(pWritePos + nRead, 0, cbBitsSrc - nRead);
1680 aTmp.Seek( 0 );
1681 ReadDIB(aBitmap, aTmp, true);
1683 const tools::Long nWidthDiff = aBitmap.GetSizePixel().Width() - cxSrc;
1684 const tools::Long nHeightDiff = aBitmap.GetSizePixel().Height() - cySrc;
1686 // test if it is sensible to crop
1687 if ( (cxSrc > 0) && (cySrc > 0) &&
1688 (xSrc >= 0) && (ySrc >= 0) &&
1689 (xSrc <= nWidthDiff) && (ySrc <= nHeightDiff) )
1691 tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) );
1692 aBitmap.Crop( aCropRect );
1694 maBmpSaveList.emplace_back(aBitmap, aRect, dwRop);
1698 break;
1700 case EMR_EXTCREATEFONTINDIRECTW :
1702 mpInputStream->ReadUInt32( nIndex );
1703 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
1705 LOGFONTW aLogFont;
1706 mpInputStream->ReadInt32( aLogFont.lfHeight )
1707 .ReadInt32( aLogFont.lfWidth )
1708 .ReadInt32( aLogFont.lfEscapement )
1709 .ReadInt32( aLogFont.lfOrientation )
1710 .ReadInt32( aLogFont.lfWeight )
1711 .ReadUChar( aLogFont.lfItalic )
1712 .ReadUChar( aLogFont.lfUnderline )
1713 .ReadUChar( aLogFont.lfStrikeOut )
1714 .ReadUChar( aLogFont.lfCharSet )
1715 .ReadUChar( aLogFont.lfOutPrecision )
1716 .ReadUChar( aLogFont.lfClipPrecision )
1717 .ReadUChar( aLogFont.lfQuality )
1718 .ReadUChar( aLogFont.lfPitchAndFamily );
1720 sal_Unicode lfFaceName[LF_FACESIZE+1];
1721 lfFaceName[LF_FACESIZE] = 0;
1722 for (int i = 0; i < LF_FACESIZE; ++i)
1724 sal_uInt16 nChar(0);
1725 mpInputStream->ReadUInt16(nChar);
1726 lfFaceName[i] = nChar;
1728 aLogFont.alfFaceName = OUString( lfFaceName );
1730 // #i123216# Not used in the test case of #121382# (always identity in XForm), also
1731 // no hints in ms docu if FontSize should be scaled with WT. Using with the example
1732 // from #i123216# creates errors, so removing.
1734 // // #i121382# Need to apply WorldTransform to FontHeight/Width; this should be completely
1735 // // changed to basegfx::B2DHomMatrix instead of 'struct XForm', but not now due to time
1736 // // constraints and dangers
1737 // const XForm& rXF = GetWorldTransform();
1738 // const basegfx::B2DHomMatrix aWT(rXF.eM11, rXF.eM21, rXF.eDx, rXF.eM12, rXF.eM22, rXF.eDy);
1739 // const basegfx::B2DVector aTransVec(aWT * basegfx::B2DVector(aLogFont.lfWidth, aLogFont.lfHeight));
1740 // aLogFont.lfWidth = aTransVec.getX();
1741 // aLogFont.lfHeight = aTransVec.getY();
1742 if (mpInputStream->good() && aLogFont.lfHeight != SAL_MIN_INT32 && aLogFont.lfWidth != SAL_MIN_INT32)
1744 CreateObjectIndexed(nIndex, std::make_unique<WinMtfFontStyle>( aLogFont ));
1748 break;
1750 case EMR_POLYTEXTOUTA :
1751 case EMR_EXTTEXTOUTA :
1752 bFlag = true;
1753 [[fallthrough]];
1754 case EMR_POLYTEXTOUTW :
1755 case EMR_EXTTEXTOUTW :
1757 sal_Int32 nLeft, nTop, nRight, nBottom;
1758 sal_uInt32 nGfxMode;
1759 float nXScale, nYScale;
1760 sal_uInt32 ncStrings( 1 );
1761 sal_Int32 ptlReferenceX, ptlReferenceY;
1762 sal_uInt32 nLen, nOffString, nOptions, offDx;
1763 sal_Int32 nLeftRect, nTopRect, nRightRect, nBottomRect;
1765 nCurPos = mpInputStream->Tell() - 8;
1767 mpInputStream->ReadInt32( nLeft ).ReadInt32( nTop ).ReadInt32( nRight ).ReadInt32( nBottom )
1768 .ReadUInt32( nGfxMode ).ReadFloat( nXScale ).ReadFloat( nYScale );
1769 SAL_INFO("emfio", "\t\tBounds: " << nLeft << ", " << nTop << ", " << nRight << ", " << nBottom);
1770 SAL_INFO("emfio", "\t\tiGraphicsMode: 0x" << std::hex << nGfxMode << std::dec);
1771 SAL_INFO("emfio", "\t\t Scale: " << nXScale << " x " << nYScale);
1772 if ( ( nRecType == EMR_POLYTEXTOUTA ) || ( nRecType == EMR_POLYTEXTOUTW ) )
1774 mpInputStream->ReadUInt32( ncStrings );
1775 SAL_INFO("emfio", "\t\t Number of Text objects: " << ncStrings);
1776 if ( ncStrings == 0 )
1777 break;
1779 mpInputStream->ReadInt32( ptlReferenceX ).ReadInt32( ptlReferenceY ).ReadUInt32( nLen ).ReadUInt32( nOffString ).ReadUInt32( nOptions );
1780 SAL_INFO("emfio", "\t\tReference: (" << ptlReferenceX << ", " << ptlReferenceY << ")");
1782 mpInputStream->ReadInt32( nLeftRect ).ReadInt32( nTopRect ).ReadInt32( nRightRect ).ReadInt32( nBottomRect );
1783 mpInputStream->ReadUInt32( offDx );
1785 if (!mpInputStream->good())
1786 bStatus = false;
1787 else
1789 const tools::Rectangle aRect( nLeftRect, nTopRect, nRightRect, nBottomRect );
1790 const BackgroundMode mnBkModeBackup = mnBkMode;
1791 if ( nOptions & ETO_NO_RECT ) // Don't draw the background rectangle and text background
1792 mnBkMode = BackgroundMode::Transparent;
1793 else if ( nOptions & ETO_OPAQUE )
1794 DrawRectWithBGColor( aRect );
1796 // ETO_RTLREADING indicates that the characters are laid from right to left
1797 vcl::text::ComplexTextLayoutFlags nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
1798 if ( nOptions & ETO_RTLREADING )
1799 nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
1800 SetTextLayoutMode( nTextLayoutMode );
1801 SAL_WARN_IF( ( nOptions & ( ETO_PDY | ETO_GLYPH_INDEX ) ) != 0, "emfio", "SJ: ETO_PDY || ETO_GLYPH_INDEX in EMF" );
1803 Point aPos( ptlReferenceX, ptlReferenceY );
1804 bool bOffStringSane = nOffString <= mnEndPos - nCurPos;
1805 if ( bOffStringSane )
1807 mpInputStream->Seek( nCurPos + nOffString );
1808 OUString aText;
1809 if ( bFlag )
1811 if ( nLen <= ( mnEndPos - mpInputStream->Tell() ) )
1813 std::vector<char> pBuf( nLen );
1814 mpInputStream->ReadBytes(pBuf.data(), nLen);
1815 aText = OUString(pBuf.data(), nLen, GetCharSet());
1818 else
1820 if ( ( nLen * sizeof(sal_Unicode) ) <= ( mnEndPos - mpInputStream->Tell() ) )
1822 aText = read_uInt16s_ToOUString(*mpInputStream, nLen);
1826 SAL_INFO("emfio", "\t\tText: " << aText);
1827 SAL_INFO("emfio", "\t\tDxBuffer:");
1829 KernArray aDXAry;
1830 std::unique_ptr<tools::Long[]> pDYAry;
1832 sal_Int32 nDxSize;
1833 sal_Int32 nBytesEach;
1835 // Reading OutputDx
1836 // ETO_PDY flag indicates that we should read twice values
1837 // compared to the number of characters in the output string.
1838 // Values are stored in an array of 32-bit unsigned integers
1839 // named OutputDx, so there will be either 8 bytes or 4 bytes
1840 // each depending on ETO_PDY is set or not.
1841 if (nOptions & ETO_PDY)
1842 nBytesEach = 8;
1843 else
1844 nBytesEach = 4;
1846 bool bOverflow = o3tl::checked_multiply<sal_Int32>(nLen, nBytesEach, nDxSize);
1847 if (!bOverflow && offDx && ((nCurPos + offDx + nDxSize) <= nNextPos ) && nNextPos <= mnEndPos)
1849 mpInputStream->Seek( nCurPos + offDx );
1850 aDXAry.resize(aText.getLength());
1851 if (nOptions & ETO_PDY)
1853 pDYAry.reset( new tools::Long[aText.getLength()] );
1856 for (sal_Int32 i = 0; i < aText.getLength(); ++i)
1858 sal_Int32 nDxCount = 1;
1859 if ( static_cast<sal_uInt32>( aText.getLength() ) != nLen )
1861 sal_Unicode cUniChar = aText[i];
1862 OString aTmp(&cUniChar, 1, GetCharSet());
1863 if (aTmp.getLength() > 1)
1865 nDxCount = aTmp.getLength();
1869 aDXAry.set(i, 0);
1870 if (nOptions & ETO_PDY)
1872 pDYAry[i] = 0;
1875 while (nDxCount--)
1877 sal_Int32 nDxTmp = 0;
1878 mpInputStream->ReadInt32(nDxTmp);
1879 aDXAry.set(i, o3tl::saturating_add(aDXAry[i], nDxTmp));
1880 if (nOptions & ETO_PDY)
1882 sal_Int32 nDyTmp = 0;
1883 mpInputStream->ReadInt32(nDyTmp);
1884 pDYAry[i] += nDyTmp;
1888 SAL_INFO("emfio", "\t\t\tSpacing " << i << ": " << aDXAry[i]);
1891 if ( nOptions & ETO_CLIPPED )
1893 Push(); // Save the current clip. It will be restored after text drawing
1894 IntersectClipRect( aRect );
1896 DrawText(aPos, aText, aDXAry.empty() ? nullptr : &aDXAry, pDYAry.get(), mbRecordPath, static_cast<GraphicsMode>(nGfxMode));
1897 if ( nOptions & ETO_CLIPPED )
1898 Pop();
1900 mnBkMode = mnBkModeBackup;
1903 break;
1905 case EMR_POLYBEZIERTO16 :
1906 DrawPolyBezier(ReadPolygonWithSkip<sal_Int16>(true, nNextPos), true, mbRecordPath);
1907 break;
1909 case EMR_POLYBEZIER16 :
1910 DrawPolyBezier(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), false, mbRecordPath);
1911 break;
1913 case EMR_POLYGON16 :
1914 DrawPolygon(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), mbRecordPath);
1915 break;
1917 case EMR_POLYLINETO16 :
1918 DrawPolyLine(ReadPolygonWithSkip<sal_Int16>(true, nNextPos), true, mbRecordPath);
1919 break;
1921 case EMR_POLYLINE16 :
1922 DrawPolyLine(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), false, mbRecordPath);
1923 break;
1925 case EMR_POLYPOLYLINE16 :
1926 ReadAndDrawPolyLine<sal_Int16>(nNextPos);
1927 break;
1929 case EMR_POLYPOLYGON16 :
1930 ReadAndDrawPolyPolygon<sal_Int16>(nNextPos);
1931 break;
1933 case EMR_FILLRGN :
1935 sal_uInt32 nRemainingRecSize = nRecSize - 8;
1936 if (nRemainingRecSize < 24)
1937 bStatus = false;
1938 else
1940 sal_uInt32 nRgnDataSize;
1941 basegfx::B2DPolyPolygon aPolyPoly;
1942 mpInputStream->SeekRel(16); // RectL bounds
1943 mpInputStream->ReadUInt32( nRgnDataSize ).ReadUInt32( nIndex );
1944 nRemainingRecSize -= 24;
1946 if (ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize, GetWinOrg()))
1948 Push();
1949 SelectObject( nIndex );
1950 tools::PolyPolygon aPolyPolygon(aPolyPoly);
1951 DrawPolyPolygon( aPolyPolygon );
1952 Pop();
1956 break;
1958 case EMR_PAINTRGN :
1960 sal_uInt32 nRemainingRecSize = nRecSize - 8;
1961 if (nRemainingRecSize < 20)
1962 bStatus = false;
1963 else
1965 sal_uInt32 nRgnDataSize;
1966 basegfx::B2DPolyPolygon aPolyPoly;
1967 mpInputStream->SeekRel(16); // Skipping RectL bounds
1968 mpInputStream->ReadUInt32( nRgnDataSize );
1969 nRemainingRecSize -= 20;
1971 if (ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize, GetWinOrg()))
1973 tools::PolyPolygon aPolyPolygon(aPolyPoly);
1974 DrawPolyPolygon( aPolyPolygon );
1978 break;
1980 case EMR_CREATEDIBPATTERNBRUSHPT :
1982 sal_uInt32 nStart = mpInputStream->Tell() - 8;
1983 Bitmap aBitmap;
1985 mpInputStream->ReadUInt32( nIndex );
1987 if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
1989 sal_uInt32 usage, offBmi, cbBmi, offBits, cbBits;
1991 mpInputStream->ReadUInt32( usage );
1992 mpInputStream->ReadUInt32( offBmi );
1993 mpInputStream->ReadUInt32( cbBmi );
1994 mpInputStream->ReadUInt32( offBits );
1995 mpInputStream->ReadUInt32( cbBits );
1997 if ( !mpInputStream->good() || (cbBits > (SAL_MAX_UINT32 - 14)) || ((SAL_MAX_UINT32 - 14) - cbBits < cbBmi) )
1998 bStatus = false;
1999 else if ( offBmi )
2001 sal_uInt32 nSize = cbBmi + cbBits + 14;
2002 if ( nSize <= ( mnEndPos - mnStartPos ) )
2004 char* pBuf = new char[ nSize ];
2006 SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE );
2007 aTmp.ObjectOwnsMemory( true );
2008 aTmp.WriteUChar( 'B' )
2009 .WriteUChar( 'M' )
2010 .WriteUInt32( cbBits )
2011 .WriteUInt16( 0 )
2012 .WriteUInt16( 0 )
2013 .WriteUInt32( cbBmi + 14 );
2015 mpInputStream->Seek( nStart + offBmi );
2016 char* pWritePos = pBuf + 14;
2017 auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmi);
2018 if (nRead != cbBmi)
2020 // zero remainder if short read
2021 memset(pWritePos + nRead, 0, cbBmi - nRead);
2024 mpInputStream->Seek( nStart + offBits );
2025 pWritePos = pBuf + 14 + cbBmi;
2026 nRead = mpInputStream->ReadBytes(pWritePos, cbBits);
2027 if (nRead != cbBits)
2029 // zero remainder if short read
2030 memset(pWritePos + nRead, 0, cbBits - nRead);
2033 aTmp.Seek( 0 );
2034 ReadDIB(aBitmap, aTmp, true);
2039 CreateObjectIndexed(nIndex, std::make_unique<WinMtfFillStyle>( aBitmap ));
2041 break;
2043 case EMR_MASKBLT :
2044 case EMR_PLGBLT :
2045 case EMR_SETDIBITSTODEVICE :
2046 case EMR_FRAMERGN :
2047 case EMR_INVERTRGN :
2048 case EMR_FLATTENPATH :
2049 case EMR_WIDENPATH :
2050 case EMR_POLYDRAW :
2051 case EMR_SETPALETTEENTRIES :
2052 case EMR_RESIZEPALETTE :
2053 case EMR_EXTFLOODFILL :
2054 case EMR_ANGLEARC :
2055 case EMR_SETCOLORADJUSTMENT :
2056 case EMR_POLYDRAW16 :
2057 case EMR_CREATECOLORSPACE :
2058 case EMR_SETCOLORSPACE :
2059 case EMR_DELETECOLORSPACE :
2060 case EMR_GLSRECORD :
2061 case EMR_GLSBOUNDEDRECORD :
2062 case EMR_PIXELFORMAT :
2063 case EMR_DRAWESCAPE :
2064 case EMR_EXTESCAPE :
2065 case EMR_STARTDOC :
2066 case EMR_SMALLTEXTOUT :
2067 case EMR_FORCEUFIMAPPING :
2068 case EMR_NAMEDESCAPE :
2069 case EMR_COLORCORRECTPALETTE :
2070 case EMR_SETICMPROFILEA :
2071 case EMR_SETICMPROFILEW :
2072 case EMR_TRANSPARENTBLT :
2073 case EMR_TRANSPARENTDIB :
2074 case EMR_GRADIENTFILL :
2075 case EMR_SETLINKEDUFIS :
2076 case EMR_SETMAPPERFLAGS :
2077 case EMR_SETICMMODE :
2078 case EMR_CREATEMONOBRUSH :
2079 case EMR_SETBRUSHORGEX :
2080 case EMR_SETMETARGN :
2081 case EMR_SETMITERLIMIT :
2082 case EMR_EXCLUDECLIPRECT :
2083 case EMR_REALIZEPALETTE :
2084 case EMR_SELECTPALETTE :
2085 case EMR_CREATEPALETTE :
2086 case EMR_ALPHADIBBLEND :
2087 case EMR_SETTEXTJUSTIFICATION :
2089 SAL_WARN("emfio", "TODO: EMF record not implemented: " << record_type_name(nRecType));
2091 break;
2093 case EMR_COMMENT :
2094 case EMR_HEADER : // has already been read at ReadHeader()
2095 break;
2097 default : SAL_INFO("emfio", "Unknown Meta Action"); break;
2100 mpInputStream->Seek( nNextPos );
2103 // tdf#127471
2104 maScaledFontHelper.applyAlternativeFontScale();
2106 if( !maBmpSaveList.empty() )
2107 ResolveBitmapActions( maBmpSaveList );
2109 if ( bStatus )
2110 mpInputStream->Seek(mnEndPos);
2112 return bStatus;
2115 bool EmfReader::ReadHeader()
2117 // Spare me the METAFILEHEADER here
2118 // Reading the METAHEADER - EMR_HEADER ([MS-EMF] section 2.3.4.2 EMR_HEADER Record Types)
2119 sal_uInt32 nType(0), nHeaderSize(0);
2120 mpInputStream->ReadUInt32(nType).ReadUInt32(nHeaderSize);
2121 SAL_INFO ("emfio", "0x0-0x" << std::hex << nHeaderSize << " " << record_type_name(nType) << " size: " << std::dec << nHeaderSize);
2122 if (nType != 0x00000001)
2124 // per [MS-EMF] 2.3.4.2 EMF Header Record Types, type MUST be 0x00000001
2125 SAL_WARN("emfio", "EMF header type is not set to 0x00000001 - possibly corrupted file?");
2126 return false;
2129 // Start reading the EMR_HEADER Header object
2131 // bound size (RectL object, see [MS-WMF] section 2.2.2.19)
2132 SAL_INFO("emfio", "\tBounding rectangle");
2133 tools::Rectangle rclBounds = ReadRectangle(); // rectangle in logical units
2135 // picture frame size (RectL object)
2136 SAL_INFO("emfio", "\tPicture frame");
2137 tools::Rectangle rclFrame = ReadRectangle(); // rectangle in device units 1/100th mm
2139 sal_uInt32 nSignature(0);
2140 mpInputStream->ReadUInt32(nSignature);
2141 SAL_INFO("emfio", "\tSignature: 0x" << std::hex << nSignature << std::dec);
2143 // nSignature MUST be the ASCII characters "FME", see [WS-EMF] 2.2.9 Header Object
2144 // and 2.1.14 FormatSignature Enumeration
2145 if (nSignature != 0x464d4520)
2147 SAL_WARN("emfio", "EMF\t\tSignature is not 0x464d4520 (\"FME\") - possibly corrupted file?");
2148 return false;
2151 sal_uInt32 nVersion(0);
2152 mpInputStream->ReadUInt32(nVersion); // according to [WS-EMF] 2.2.9, this SHOULD be 0x0001000, however
2153 // Microsoft note that not even Windows checks this...
2154 SAL_INFO("emfio", "\tVersion: 0x" << std::hex << nVersion << std::dec);
2155 if (nVersion != 0x00010000)
2157 SAL_WARN("emfio", "EMF\t\tThis really should be 0x00010000, though not absolutely essential...");
2160 mpInputStream->ReadUInt32(mnEndPos); // size of metafile
2161 SAL_INFO("emfio", "\tMetafile size: " << mnEndPos);
2162 mnEndPos += mnStartPos;
2164 sal_uInt32 nStrmPos = mpInputStream->Tell(); // checking if mnEndPos is valid
2165 sal_uInt32 nActualFileSize = nStrmPos + mpInputStream->remainingSize();
2167 if ( nActualFileSize < mnEndPos )
2169 SAL_WARN("emfio", "EMF\t\tEMF Header object records number of bytes as " << mnEndPos
2170 << ", however the file size is actually " << nActualFileSize
2171 << " bytes. Possible file corruption?");
2172 mnEndPos = nActualFileSize;
2175 mpInputStream->ReadUInt32(mnRecordCount);
2176 SAL_INFO("emfio", "\tRecords: " << mnRecordCount);
2178 // the number of "handles", or graphics objects used in the metafile
2180 sal_uInt16 nHandlesCount;
2181 mpInputStream->ReadUInt16(nHandlesCount);
2182 SAL_INFO("emfio", "\tGraphics: " << nHandlesCount);
2184 // the next 2 bytes are reserved, but according to [MS-EMF] section 2.2.9
2185 // it MUST be 0x000 and MUST be ignored... the thing is, having such a specific
2186 // value is actually pretty useful in checking if there is possible corruption
2188 sal_uInt16 nReserved(0);
2189 mpInputStream->ReadUInt16(nReserved);
2190 SAL_INFO("emfio", "\tReserved: 0x" << std::hex << nReserved << std::dec);
2192 if ( nReserved != 0x0000 )
2194 SAL_WARN("emfio", "EMF\t\tEMF Header object's reserved field is NOT 0x0000... possible "
2195 "corruption?");
2198 // The next 4 bytes specifies the number of characters in the metafile description.
2199 // The 4 bytes after that specific the offset from this record that contains the
2200 // metafile description... zero means no description string.
2201 // For now, we ignore it.
2203 mpInputStream->SeekRel(0x8);
2205 sal_uInt32 nPalEntries(0);
2206 mpInputStream->ReadUInt32(nPalEntries);
2207 SAL_INFO("emfio", "\tPalette entries: " << nPalEntries);
2208 sal_Int32 nPixX(0), nPixY(0), nMillX(0), nMillY(0);
2209 mpInputStream->ReadInt32(nPixX);
2210 mpInputStream->ReadInt32(nPixY);
2211 SAL_INFO("emfio", "\tRef (pixels): " << nPixX << ", " << nPixY);
2212 mpInputStream->ReadInt32(nMillX);
2213 mpInputStream->ReadInt32(nMillY);
2214 SAL_INFO("emfio", "\tRef (mm): " << nMillX << ", " << nMillY);
2216 SetrclFrame(rclFrame);
2217 SetrclBounds(rclBounds);
2218 SetRefPix(Size( nPixX, nPixY ) );
2219 SetRefMill(Size( nMillX, nMillY ) );
2221 return checkSeek(*mpInputStream, mnStartPos + nHeaderSize);
2224 tools::Rectangle EmfReader::ReadRectangle()
2226 sal_Int32 nLeft(0), nTop(0), nRight(0), nBottom(0);
2227 mpInputStream->ReadInt32(nLeft);
2228 mpInputStream->ReadInt32(nTop);
2229 mpInputStream->ReadInt32(nRight);
2230 mpInputStream->ReadInt32(nBottom);
2232 SAL_INFO("emfio", "\t\tLeft: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom);
2233 if (nLeft > nRight || nTop > nBottom)
2235 SAL_WARN("emfio", "broken rectangle");
2236 return tools::Rectangle::Normalize(Point(nLeft, nTop), Point(nRight, nBottom));
2239 return tools::Rectangle(nLeft, nTop, nRight, nBottom);
2242 tools::Rectangle EmfReader::ReadRectangle( sal_Int32 x1, sal_Int32 y1, sal_Int32 x2, sal_Int32 y2 )
2244 Point aTL(x1, y1);
2245 Point aBR(o3tl::saturating_add<sal_Int32>(x2, -1), o3tl::saturating_add<sal_Int32>(y2, -1));
2246 return tools::Rectangle(aTL, aBR);
2250 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */