diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /emfio | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'emfio')
76 files changed, 10577 insertions, 0 deletions
diff --git a/emfio/CppunitTest_emfio_emf.mk b/emfio/CppunitTest_emfio_emf.mk new file mode 100644 index 000000000..38a1c9825 --- /dev/null +++ b/emfio/CppunitTest_emfio_emf.mk @@ -0,0 +1,48 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,emfio_emf)) + +$(eval $(call gb_CppunitTest_use_externals,emfio_emf,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,emfio_emf,\ + emfio/qa/cppunit/emf/EmfImportTest \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,emfio_emf)) + +$(eval $(call gb_CppunitTest_use_libraries,emfio_emf,\ + basegfx \ + drawinglayer \ + drawinglayercore \ + cppu \ + cppuhelper \ + comphelper \ + sal \ + svt \ + test \ + unotest \ + tl \ + vcl \ + utl \ +)) + +$(eval $(call gb_CppunitTest_use_ure,emfio_emf)) +$(eval $(call gb_CppunitTest_use_vcl,emfio_emf)) + +$(eval $(call gb_CppunitTest_use_rdb,emfio_emf,services)) + +$(eval $(call gb_CppunitTest_use_more_fonts,emfio_emf)) + +$(eval $(call gb_CppunitTest_use_configuration,emfio_emf)) + +# vim: set noet sw=4 ts=4: diff --git a/emfio/CppunitTest_emfio_wmf.mk b/emfio/CppunitTest_emfio_wmf.mk new file mode 100644 index 000000000..8ab4a9064 --- /dev/null +++ b/emfio/CppunitTest_emfio_wmf.mk @@ -0,0 +1,42 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,emfio_wmf)) + +$(eval $(call gb_CppunitTest_set_include,emfio_wmf,\ + $$(INCLUDE) \ + -I$(SRCDIR)/emfio/inc \ +)) + +$(eval $(call gb_CppunitTest_use_externals,emfio_wmf,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,emfio_wmf, \ + emfio/qa/cppunit/wmf/wmfimporttest \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,emfio_wmf,\ + emfio \ + sal \ + test \ + tl \ + unotest \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_more_fonts,emfio_wmf)) +$(eval $(call gb_CppunitTest_use_configuration,emfio_wmf)) +$(eval $(call gb_CppunitTest_use_sdk_api,emfio_wmf)) +$(eval $(call gb_CppunitTest_use_ure,emfio_wmf)) +$(eval $(call gb_CppunitTest_use_vcl,emfio_wmf)) +$(eval $(call gb_CppunitTest_use_rdb,emfio_wmf,services)) + +# vim: set noet sw=4 ts=4: diff --git a/emfio/IwyuFilter_emfio.yaml b/emfio/IwyuFilter_emfio.yaml new file mode 100644 index 000000000..3a2074b75 --- /dev/null +++ b/emfio/IwyuFilter_emfio.yaml @@ -0,0 +1,6 @@ +--- +assumeFilename: emfio/source/emfuno/xemfparser.cxx +excludelist: + emfio/source/reader/wmfreader.cxx: + # OSL_BIGENDIAN is being checked + - osl/endian.h diff --git a/emfio/Library_emfio.mk b/emfio/Library_emfio.mk new file mode 100644 index 000000000..5895b79cf --- /dev/null +++ b/emfio/Library_emfio.mk @@ -0,0 +1,64 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +$(eval $(call gb_Library_Library,emfio)) + +$(eval $(call gb_Library_set_componentfile,emfio,emfio/emfio,services)) + +$(eval $(call gb_Library_set_include,emfio,\ + $$(INCLUDE) \ + -I$(SRCDIR)/emfio/inc \ +)) + +$(eval $(call gb_Library_add_defs,emfio,\ + -DEMFIO_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_use_custom_headers,emfio,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_external,emfio,boost_headers)) + +$(eval $(call gb_Library_set_precompiled_header,emfio,emfio/inc/pch/precompiled_emfio)) + +$(eval $(call gb_Library_use_sdk_api,emfio)) + +$(eval $(call gb_Library_use_libraries,emfio,\ + basegfx \ + drawinglayercore \ + drawinglayer \ + cppu \ + cppuhelper \ + sal \ + comphelper \ + tl \ + salhelper \ + vcl \ + svt \ + utl \ +)) + +$(eval $(call gb_Library_add_exception_objects,emfio,\ + emfio/source/emfuno/xemfparser \ + emfio/source/reader/mtftools \ + emfio/source/reader/wmfreader \ + emfio/source/reader/emfreader \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/emfio/Makefile b/emfio/Makefile new file mode 100644 index 000000000..0997e6284 --- /dev/null +++ b/emfio/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/emfio/Module_emfio.mk b/emfio/Module_emfio.mk new file mode 100644 index 000000000..96b69d1cb --- /dev/null +++ b/emfio/Module_emfio.mk @@ -0,0 +1,30 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +$(eval $(call gb_Module_Module,emfio)) + +$(eval $(call gb_Module_add_targets,emfio,\ + Library_emfio \ +)) + +$(eval $(call gb_Module_add_check_targets,emfio,\ + CppunitTest_emfio_emf \ + $(if $(MERGELIBS),,CppunitTest_emfio_wmf) \ +)) + +# vim: set noet ts=4 sw=4: diff --git a/emfio/README.md b/emfio/README.md new file mode 100644 index 000000000..f9f33a2ba --- /dev/null +++ b/emfio/README.md @@ -0,0 +1,133 @@ +# WMF/EMF/EMF+ Reader + +## Introduction +The **emfio** module is used to read **WMF** (Windows Metafile), **EMF** +(Enhanced Metafiles) and also **EMF+** (enhanced EMF) files [1], which are +binary formats for vector images from Microsoft. These files can contain vector +graphics, bitmap components and text. + +This folder contains `emfio/source/reader` which is used for reading the +WMF/EMF/EMF+ files, either directly, or inside a document. For embedding Windows +Metafiles inside a document, one can use "Insert -> Picture -> From File" to put +such a file into the document. It is possible to export the Windows Metafile +by using right click and choose "save". + +Most of the records of WMF/EMF formats come from the Windows Graphics Device. +For Interface (GDI) API and EMF+ they come from the newer Windows GDI+. + +More information about rendering Windows Metafiles can be found in the +[Visual Class Library (VCL)](../vcl) and also in the +[GDIMetaFile](../vcl/README.GDIMetaFile) documentation. + +An example demo that renders a metafile using `vcl` be seen by invoking: + + ./bin/run mtfdemo odk/examples/basic/forms_and_controls/burger.wmf + +This opens the burger.wmf file from the ODK examples. + +It is also possible to dump metaactions created as the intermediary format +before rendering the metafile using: + + ./bin/run mtfdemo -d odk/examples/basic/forms_and_controls/burger.wmf + +If the command is successful, this message will be shown, and metadump.xml will +be put in the current folder: + +"Dumped metaactions as metadump.xml" + +The demo code structure is described in [GDIMetaFile](../vcl/README.GDIMetaFile) +documentation. + +[1] [Windows Meta File](https://en.wikipedia.org/wiki/Windows\_Metafile), +Wikipedia + +## EMF+ Specifics +Handling EMF+ is to some extent different from handling WMF/EMF. More +information can be found in the [VCL](../vcl) +documentation. + +## How does it work? +`emfio` module takes a byte array and turns it into a `drawinglayer` primitive container. The rendering is done via `drawinglayer` primitives. For more information, you should refer to [VCL](../vcl) documentation. + +The drawinglayer primitives created to draw the emf/wmf files can be dumped as +xml for debugging purposes. For more information, please refer to the +[drawinglayer](../drawyinglayer) documentation. + +## Limitations +Not all the WMF/EMF/EMF+ records are supported by this module. Unsupported +records are marked as "not implemented", and a warning message will printed +if they are actually read within a file. You can file a bug report for +implementing these records. + +Currently, these records are not implemented for WMF (specified in +`wmfreader.cxx`): + +``` +W_META_SETRELABS W_META_SETPOLYFILLMODE W_META_SETSTRETCHBLTMODE +W_META_SETTEXTCHAREXTRA W_META_SETTEXTJUSTIFICATION W_META_FLOODFILL +W_META_FILLREGION W_META_FRAMEREGION W_META_INVERTREGION +W_META_PAINTREGION W_META_DRAWTEXT W_META_SETMAPPERFLAGS +W_META_SETDIBTODEV W_META_REALIZEPALETTE W_META_ANIMATEPALETTE +W_META_SETPALENTRIES W_META_RESIZEPALETTE W_META_EXTFLOODFILL +W_META_RESETDC W_META_STARTDOC W_META_STARTPAGE W_META_ENDPAGE +W_META_ABORTDOC W_META_ENDDOC +``` + +And these records are not implemented for EMF/EMF+ (specified in `emfreader.cxx`): + +``` +EMR_MASKBLT EMR_PLGBLT EMR_SETDIBITSTODEVICE EMR_FRAMERGN +EMR_INVERTRGN EMR_FLATTENPATH EMR_WIDENPATH EMR_POLYDRAW +EMR_SETPALETTEENTRIES EMR_RESIZEPALETTE +EMR_EXTFLOODFILL EMR_ANGLEARC EMR_SETCOLORADJUSTMENT EMR_POLYDRAW16 +EMR_CREATECOLORSPACE EMR_SETCOLORSPACE EMR_DELETECOLORSPACE +EMR_GLSRECORD EMR_GLSBOUNDEDRECORD EMR_PIXELFORMAT EMR_DRAWESCAPE +EMR_EXTESCAPE EMR_STARTDOC EMR_SMALLTEXTOUT EMR_FORCEUFIMAPPING +EMR_NAMEDESCAPE EMR_COLORCORRECTPALETTE EMR_SETICMPROFILEA +EMR_SETICMPROFILEW EMR_TRANSPARENTBLT EMR_TRANSPARENTDIB +EMR_GRADIENTFILL EMR_SETLINKEDUFIS EMR_SETMAPPERFLAGS EMR_SETICMMODE +EMR_CREATEMONOBRUSH EMR_SETBRUSHORGEX EMR_SETMETARGN EMR_SETMITERLIMIT +EMR_EXCLUDECLIPRECT EMR_REALIZEPALETTE EMR_SELECTPALETTE +EMR_CREATEPALETTE EMR_ALPHADIBBLEND EMR_SETTEXTJUSTIFICATION +``` + +Due to the difference on the fonts available on various platforms, the outcome +of text rendering can be different on Linux, Windows, macOS and elsewhere. + +## Known Bugs +Known remaining bugs for this module is gathered here: + +* [Bug 103859 \[META\] EMF/WMF (Enhanced/Windows Metafile) bugs and +enhancements](https://bugs.documentfoundation.org/show\_bug.cgi?id=103859) + +## Dependencies +The direct dependency for **emfio** is [**drawinglayer**](../drawinglayer). The +complete list of dependencies including the indirect dependencies is as below: +``` +basegfx drawinglayer cppu cppuhelper sal comphelper tl salhelper vcl svt utl +``` + +## Tools +Several tools are available for inspecting WMF/EMF/EMF+ files, which are binary +formats. Some of them are: + +* [mso-dumper](https://git.libreoffice.org/mso-dumper/): Reads and dumps various + binary formats from Microsoft including WMF/EMF/EMF+. The output is in a + custom XML format. emf-dump.py and wmf-dump.py are usable. +* [RE-lab (Formerly OLEToy)](https://github.com/renyxa/re-lab): Reads, dumps and +modifies several binary formats from Microsoft including WMF/EMF/EMF+, and also +other companies. +* [EMF+ diagnostics reporting tool](https://github.com/chrissherlock/emfplus-decoder) +* [limerest](https://gitlab.com/re-lab-project/limerest): A new gui tool based +on OLEToy for working with various binary formats + +## Related Software +* [libemf](http://libemf.sourceforge.net/) +* [libwmf](https://github.com/caolanm/libwmf) + +## References +Documentation for WMF/EMF/EMF+ formats are available on Microsoft website: + +* [\[MS-WMF\]: Windows Metafile Format](https://docs.microsoft.com/en-us/openspecs/windows\_protocols/ms-wmf/4813e7fd-52d0-4f42-965f-228c8b7488d2) +* [\[MS-EMF\]: Enhanced Metafile Format](https://docs.microsoft.com/en-us/openspecs/windows\_protocols/ms-emf/91c257d7-c39d-4a36-9b1f-63e3f73d30ca) +* [\[MS-EMFPLUS\]: Enhanced Metafile Format Plus Extensions](https://docs.microsoft.com/en-us/openspecs/windows\_protocols/ms-emfplus/5f92c789-64f2-46b5-9ed4-15a9bb0946c6) diff --git a/emfio/emfio.component b/emfio/emfio.component new file mode 100644 index 000000000..80e242792 --- /dev/null +++ b/emfio/emfio.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="emfio::emfreader::XEmfParser" + constructor="emfio_emfreader_XEmfParser_get_implementation"> + <service name="com.sun.star.graphic.EmfTools"/> + </implementation> +</component> diff --git a/emfio/inc/emfiodllapi.h b/emfio/inc/emfiodllapi.h new file mode 100644 index 000000000..af672949d --- /dev/null +++ b/emfio/inc/emfiodllapi.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_EMFIO_INC_EMFIODLLAPI_H +#define INCLUDED_EMFIO_INC_EMFIODLLAPI_H + +#include <sal/types.h> + +#if defined(EMFIO_DLLIMPLEMENTATION) +#define EMFIO_DLLPUBLIC SAL_DLLPUBLIC_EXPORT +#else +#define EMFIO_DLLPUBLIC SAL_DLLPUBLIC_IMPORT +#endif + +#endif // INCLUDED_EMFIO_INC_EMFIODLLAPI_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/inc/emfreader.hxx b/emfio/inc/emfreader.hxx new file mode 100644 index 000000000..a09ad6fa2 --- /dev/null +++ b/emfio/inc/emfreader.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_EMFIO_INC_EMFREADER_HXX +#define INCLUDED_EMFIO_INC_EMFREADER_HXX + +#include "mtftools.hxx" + +namespace emfio +{ + class EmfReader : public MtfTools + { + private: + sal_uInt32 mnRecordCount; + + bool mbRecordPath : 1; + bool mbEMFPlus : 1; + bool mbEMFPlusDualMode : 1; + /// Another format is read already, can ignore actual EMF data. + bool mbReadOtherGraphicFormat = false; + basegfx::B2DTuple maSizeHint; + bool mbEnableEMFPlus = true; + + bool ReadHeader(); + // reads and converts the rectangle + static tools::Rectangle ReadRectangle(sal_Int32, sal_Int32, sal_Int32, sal_Int32); + + public: + EmfReader(SvStream& rStreamWMF, GDIMetaFile& rGDIMetaFile); + ~EmfReader(); + + bool ReadEnhWMF(); + void ReadGDIComment(sal_uInt32 nCommentId); + /// Parses EMR_COMMENT_MULTIFORMATS. + void ReadMultiformatsComment(); + void SetSizeHint(const basegfx::B2DTuple& rSizeHint) { maSizeHint = rSizeHint; } + void SetEnableEMFPlus(bool bEnableEMFPlus) { mbEnableEMFPlus = bEnableEMFPlus; } + + private: + template <class T> void ReadAndDrawPolyPolygon(sal_uInt32 nNextPos); + template <class T> void ReadAndDrawPolyLine(sal_uInt32 nNextPos); + template <class T> tools::Polygon ReadPolygon(sal_uInt32 nStartIndex, sal_uInt32 nPoints, sal_uInt32 nNextPos); + template <class T> tools::Polygon ReadPolygonWithSkip(const bool skipFirst, sal_uInt32 nNextPos); + + tools::Rectangle ReadRectangle(); + void ReadEMFPlusComment(sal_uInt32 length, bool& bHaveDC); + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/inc/mtftools.hxx b/emfio/inc/mtftools.hxx new file mode 100644 index 000000000..997f2287f --- /dev/null +++ b/emfio/inc/mtftools.hxx @@ -0,0 +1,842 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_EMFIO_INC_MTFTOOLS_HXX +#define INCLUDED_EMFIO_INC_MTFTOOLS_HXX + +#include <config_options.h> +#include <basegfx/utils/b2dclipstate.hxx> +#include <tools/poly.hxx> +#include <vcl/font.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/rendercontext/State.hxx> +#include <vcl/metaact.hxx> +#include <rtl/ref.hxx> + +#include "emfiodllapi.h" + +namespace emfio +{ + /* [MS-EMF] - v20210625 - pages 43, 107 */ + enum class RegionMode : sal_uInt32 + { + RGN_AND = 0x01, + RGN_OR = 0x02, + RGN_XOR = 0x03, + RGN_DIFF = 0x04, + RGN_COPY = 0x05 + }; + + /* [MS-EMF] - v20210625 - pages 40, 198 */ + enum class BackgroundMode : sal_uInt32 + { + NONE = 0, + Transparent = 1, + OPAQUE = 2, + }; + + /* [MS-EMF] - v20210625 - pages 40, 210 */ + /* xform stuff */ + enum class ModifyWorldTransformMode : sal_uInt32 + { + MWT_IDENTITY = 0x01, + MWT_LEFTMULTIPLY = 0x02, + MWT_RIGHTMULTIPLY = 0x03, + MWT_SET = 0x04 + }; + + constexpr sal_uInt32 ENHMETA_STOCK_OBJECT = 0x80000000; + + /* [MS-EMF] - v20210625 - pages 44, 45, 182 */ + /* Stock Logical Objects */ + enum class StockObject : sal_uInt32 + { + WHITE_BRUSH = 0, + LTGRAY_BRUSH = 1, + GRAY_BRUSH = 2, + DKGRAY_BRUSH = 3, + BLACK_BRUSH = 4, + NULL_BRUSH = 5, + WHITE_PEN = 6, + BLACK_PEN = 7, + NULL_PEN = 8, + ANSI_FIXED_FONT = 11, + ANSI_VAR_FONT = 12, + SYSTEM_FIXED_FONT = 16 + }; + + /* Note: This enum is incomplete compared to the specification */ + /* [MS-WMF] - v20210625 - pages 25-26 */ + enum class WMFRasterOp : sal_uInt16 + { + NONE = 0, + Black = 1, + Not = 6, + XorPen = 7, + Nop = 11, + CopyPen = 13 + }; + + /* Note: We have MapMode elsewhere, so we use MappingMode instead */ + /* [MS-EMF] - v20210625 - pages 38-50, 202 */ + /* Mapping modes */ + enum MappingMode : sal_uInt32 + { + MM_TEXT = 0x01, + MM_LOMETRIC = 0x02, + MM_HIMETRIC = 0x03, + MM_LOENGLISH = 0x04, + MM_HIENGLISH = 0x05, + MM_TWIPS = 0x06, + MM_ISOTROPIC = 0x07, + MM_ANISOTROPIC = 0x08 + }; + + /* Note: No type is specified, but 32-bit unsigned is used to provide + * to match the number of bits in the binary literal, and also the + * previous definition of SetGfxMode() and GetGfxMode() functions */ + /* [MS-EMF] - v20210625 - pages 35 */ + /* Graphics modes */ + enum class GraphicsMode : sal_uInt32 + { + GM_COMPATIBLE = 0x00000001, + GM_ADVANCED = 0x00000002 + } ; + + /* [MS-WMF] - v20210625 - pages 46 */ + /* StretchBlt() modes */ + enum class StretchMode : sal_uInt16 + { + BLACKONWHITE = 0x0001, + WHITEONBLACK = 0x0002, + COLORONCOLOR = 0x0003, + HALFTONE = 0x0004, + STRETCH_ANDSCANS = BLACKONWHITE, + STRETCH_ORSCANS = WHITEONBLACK, + STRETCH_DELETESCANS = COLORONCOLOR + }; + + constexpr sal_Int32 LF_FACESIZE = 32; + + struct LOGFONTW + { + sal_Int32 lfHeight; + sal_Int32 lfWidth; + sal_Int32 lfEscapement; + sal_Int32 lfOrientation; + sal_Int32 lfWeight; + sal_uInt8 lfItalic; + sal_uInt8 lfUnderline; + sal_uInt8 lfStrikeOut; + sal_uInt8 lfCharSet; + sal_uInt8 lfOutPrecision; + sal_uInt8 lfClipPrecision; + sal_uInt8 lfQuality; + sal_uInt8 lfPitchAndFamily; + OUString alfFaceName; + LOGFONTW() + : lfHeight(0) + , lfWidth(0) + , lfEscapement(0) + , lfOrientation(0) + , lfWeight(0) + , lfItalic(0) + , lfUnderline(0) + , lfStrikeOut(0) + , lfCharSet(0) + , lfOutPrecision(0) + , lfClipPrecision(0) + , lfQuality(0) + , lfPitchAndFamily(0) + { + } + }; + + /* [MS-WMF] - v20210625 - pages 153 */ + enum TextAlignmentMode : sal_uInt16 + { + TA_NOUPDATECP = 0x0000, + TA_UPDATECP = 0x0001, + TA_LEFT = 0x0000, + TA_RIGHT = 0x0002, + TA_CENTER = 0x0006, + TA_RIGHT_CENTER = (TA_RIGHT | TA_CENTER), + TA_TOP = 0x0000, + TA_BOTTOM = 0x0008, + TA_BASELINE = 0x0018, + TA_RTLREADING = 0x0100 + }; + + /* Note: This enum is incomplete compared to the specification */ + /* [MS-EMF] - v20210625 - pages 47-50, 126 */ + /* Ternary raster operations */ + enum TernaryRasterOperation : sal_uInt32 + { + SRCCOPY = 0x00CC0020L, + SRCPAINT = 0x00EE0086L, + SRCAND = 0x008800C6L, + SRCINVERT = 0x00660046L, + SRCERASE = 0x00440328L, + PATCOPY = 0x00F00021L, + PATINVERT = 0x005A0049L, + BLACKNESS = 0x00000042L, + WHITENESS = 0x00FF0062L + }; + + /* [MS-EMF] - v20210625 - pages 40, 41, 65 */ + enum PenStyle : sal_uInt32 + { + PS_COSMETIC = 0x00000000, + PS_SOLID = 0x00000000, + PS_DASH = 0x00000001, + PS_DOT = 0x00000002, + PS_DASHDOT = 0x00000003, + PS_DASHDOTDOT = 0x00000004, + PS_NULL = 0x00000005, + PS_INSIDEFRAME = 0x00000006, + PS_USERSTYLE = 0x00000007, + PS_ALTERNATE = 0x00000008, + PS_STYLE_MASK = 0x0000000F, + + PS_ENDCAP_ROUND = 0x00000000, + PS_ENDCAP_SQUARE = 0x00000100, + PS_ENDCAP_FLAT = 0x00000200, + PS_ENDCAP_STYLE_MASK = 0x00000F00, + + PS_JOIN_ROUND = 0x00000000, + PS_JOIN_BEVEL = 0x00001000, + PS_JOIN_MITER = 0x00002000, + PS_JOIN_STYLE_MASK = 0x0000F000, + + PS_GEOMETRIC = 0x00010000 + }; + + /* [MS-WMF] - v20210625 - pages 30, 82 */ + /* Character Sets */ + enum CharacterSet : sal_uInt8 + { + ANSI_CHARSET = 0x00000000, + DEFAULT_CHARSET = 0x00000001, + SYMBOL_CHARSET = 0x00000002, + SHIFTJIS_CHARSET = 0x00000080, + HANGUL_CHARSET = 0x00000081, + GB2312_CHARSET = 0x00000086, + CHINESEBIG5_CHARSET = 0x00000088, + OEM_CHARSET = 0x000000FF, + /* WINVER >= 0x0400 */ + MAC_CHARSET = 0x0000004D, + JOHAB_CHARSET = 0x00000082, + GREEK_CHARSET = 0x000000A1, + TURKISH_CHARSET = 0x000000A2, + VIETNAMESE_CHARSET = 0x000000A3, + HEBREW_CHARSET = 0x000000B1, + ARABIC_CHARSET = 0x000000B2, + BALTIC_CHARSET = 0x000000BA, + RUSSIAN_CHARSET = 0x000000CC, + THAI_CHARSET = 0x000000DE, + EASTEUROPE_CHARSET = 0x000000EE + }; + + /* Note: This enum is incomplete compared to the specification */ + /* [MS-EMF] - v20210625 - pages 32, 283 */ + enum ExtTextOutOptions : sal_uInt32 + { + ETO_OPAQUE = 0x0002, + ETO_CLIPPED = 0x0004, + /* WINVER >= 0x0400 */ + ETO_GLYPH_INDEX = 0x0010, + ETO_RTLREADING = 0x0080, + /* _WIN32_WINNT >= 0x0500 */ + ETO_NO_RECT = 0x0100, + ETO_PDY = 0x2000 + }; + + /* [MS-WMF] - v20210625 - pages 44, 96 */ + /* This is packed into a byte as 2 bits */ + enum PitchFont : sal_uInt8 + { + DEFAULT_PITCH = 0, + FIXED_PITCH = 1, + VARIABLE_PITCH = 2 + }; + + /* [MS-WMF] - v20210625 - pages 33, */ + enum FamilyFont : sal_uInt8 + { + FF_DONTCARE = 0x00, + FF_ROMAN = 0x01, + FF_SWISS = 0x02, + FF_MODERN = 0x03, + FF_SCRIPT = 0x04, + FF_DECORATIVE = 0x05 + }; + + enum WeightFont + { + FW_THIN = 100, + FW_EXTRALIGHT = 200, + FW_LIGHT = 300, + FW_NORMAL = 400, + FW_MEDIUM = 500, + FW_SEMIBOLD = 600, + FW_BOLD = 700, + FW_EXTRABOLD = 800, + FW_ULTRALIGHT = 200, + FW_ULTRABOLD = 800, + FW_BLACK = 900 + }; + + /* [MS-WMF] - v20210625 - pages 29, 30, 182 */ + enum class BrushStyle : sal_uInt16 + { + BS_SOLID = 0, + BS_NULL = 1, + BS_HOLLOW = 1, + BS_HATCHED = 2, + BS_PATTERN = 3, + BS_INDEXED = 4, + BS_DIBPATTERN = 5, + BS_DIBPATTERNPT = 6, + BS_PATTERN8X8 = 7, + BS_DIBPATTERN8X8 = 8, + BS_MONOPATTERN = 9 + }; + + constexpr sal_Int32 RDH_RECTANGLES = 1; + constexpr sal_Int32 W_MFCOMMENT = 15; + constexpr sal_Int32 PRIVATE_ESCAPE_UNICODE = 2; + + //Scalar constants + constexpr sal_Int32 UNDOCUMENTED_WIN_RCL_RELATION = 32; + constexpr sal_Int32 MS_FIXPOINT_BITCOUNT_28_4 = 4; +} + +//============================ WmfReader ================================== + +namespace emfio +{ + class WinMtfClipPath + { + basegfx::utils::B2DClipState maClip; + + public: + WinMtfClipPath() : maClip() {}; + + void setClipPath(const basegfx::B2DPolyPolygon&, RegionMode nClippingMode); + void intersectClip(const basegfx::B2DPolyPolygon& rPolyPolygon); + void excludeClip(const basegfx::B2DPolyPolygon& rPolyPolygon); + void moveClipRegion(const Size& rSize); + void setDefaultClipPath(); + + bool isEmpty() const { return maClip.isCleared(); } + + basegfx::utils::B2DClipState const & getClip() const { return maClip; } + basegfx::B2DPolyPolygon const & getClipPath() const; + + bool operator==(const WinMtfClipPath& rPath) const + { + return maClip == rPath.maClip; + }; + }; + + class WinMtfPathObj : public tools::PolyPolygon + { + bool bClosed; + + public: + + WinMtfPathObj() : + bClosed(true) + {} + + void Init() + { + Clear(); + bClosed = true; + } + + void ClosePath(); + void AddPoint(const Point& rPoint); + void AddPolygon(const tools::Polygon& rPoly); + void AddPolyLine(const tools::Polygon& rPoly); + void AddPolyPolygon(const tools::PolyPolygon& rPolyPolygon); + }; + + struct EMFIO_DLLPUBLIC GDIObj + { + GDIObj() = default; + GDIObj(GDIObj const &) = default; + virtual ~GDIObj() = default; // Polymorphic base class + GDIObj & operator =(GDIObj const &) = default; + }; + + struct UNLESS_MERGELIBS(EMFIO_DLLPUBLIC) WinMtfFontStyle final : GDIObj + { + vcl::Font aFont; + + explicit WinMtfFontStyle(LOGFONTW const & rLogFont); + }; + + enum class WinMtfFillStyleType + { + Solid, Pattern + }; + + struct WinMtfFillStyle final : GDIObj + { + Color aFillColor; + bool bTransparent; + WinMtfFillStyleType aType; + Bitmap aBmp; + + WinMtfFillStyle() + : aFillColor(COL_BLACK) + , bTransparent(false) + , aType(WinMtfFillStyleType::Solid) + {} + + WinMtfFillStyle(const Color& rColor, bool bTrans = false) + : aFillColor(rColor) + , bTransparent(bTrans) + , aType(WinMtfFillStyleType::Solid) + {} + + explicit WinMtfFillStyle(Bitmap const & rBmp) + : bTransparent(false) + , aType(WinMtfFillStyleType::Pattern) + , aBmp(rBmp) + {} + + bool operator==(const WinMtfFillStyle& rStyle) const + { + return aFillColor == rStyle.aFillColor + && bTransparent == rStyle.bTransparent + && aType == rStyle.aType; + } + }; + + + struct WinMtfPalette final : GDIObj + { + std::vector< Color > aPaletteColors; + + WinMtfPalette() + : aPaletteColors(std::vector< Color >{}) + {} + + WinMtfPalette(const std::vector< Color > rPaletteColors) + : aPaletteColors(rPaletteColors) + {} + + }; + + + struct WinMtfLineStyle final : GDIObj + { + Color aLineColor; + LineInfo aLineInfo; + bool bTransparent; + + WinMtfLineStyle() + : aLineColor(COL_BLACK) + , bTransparent(false) + {} + + WinMtfLineStyle(const Color& rColor, bool bTrans = false) + : aLineColor(rColor) + , bTransparent(bTrans) + {} + + WinMtfLineStyle(const Color& rColor, const sal_uInt32 nStyle, const sal_Int32 nPenWidth) + : aLineColor(rColor) + { + // According to documentation: nStyle = PS_COSMETIC = 0x0 - line with a width of one logical unit and a style that is a solid color + // tdf#140271 Based on observed behaviour the line width is not constant with PS_COSMETIC + + // Width 0 means default width for LineInfo (HairLine) with 1 pixel wide + aLineInfo.SetWidth(nPenWidth); + switch (nStyle & PS_STYLE_MASK) + { + case PS_DASHDOTDOT: + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(1); + aLineInfo.SetDotCount(2); + break; + case PS_DASHDOT: + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(1); + aLineInfo.SetDotCount(1); + break; + case PS_DOT: + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(0); + aLineInfo.SetDotCount(1); + break; + case PS_DASH: + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(1); + aLineInfo.SetDotCount(0); + break; + case PS_NULL: + aLineInfo.SetStyle(LineStyle::NONE); + break; + case PS_INSIDEFRAME: // TODO Implement PS_INSIDEFRAME + case PS_SOLID: + default: + aLineInfo.SetStyle(LineStyle::Solid); + } + if (nPenWidth) + switch (nStyle & PS_ENDCAP_STYLE_MASK) + { + case PS_ENDCAP_ROUND: + aLineInfo.SetLineCap(css::drawing::LineCap_ROUND); + break; + case PS_ENDCAP_SQUARE: + aLineInfo.SetLineCap(css::drawing::LineCap_SQUARE); + break; + case PS_ENDCAP_FLAT: + default: + aLineInfo.SetLineCap(css::drawing::LineCap_BUTT); + } + else + aLineInfo.SetLineCap(css::drawing::LineCap_BUTT); + switch (nStyle & PS_JOIN_STYLE_MASK) + { + case PS_JOIN_ROUND: + aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Round); + break; + case PS_JOIN_BEVEL: + aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel); + break; + // Undocumented but based on experiments with MS Paint and MS Word, + // the default Join Style is PS_JOIN_MITER + case PS_JOIN_MITER: + default: + aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Miter); + } + bTransparent = aLineInfo.GetStyle() == LineStyle::NONE; + } + + bool operator==(const WinMtfLineStyle& rStyle) const + { + return aLineColor == rStyle.aLineColor + && bTransparent == rStyle.bTransparent + && aLineInfo == rStyle.aLineInfo; + } + }; + + struct XForm + { + float eM11; + float eM12; + float eM21; + float eM22; + float eDx; + float eDy; + + XForm() + : eM11(1.0f) + , eM12(0.0f) + , eM21(0.0f) + , eM22(1.0f) + , eDx(0.0f) + , eDy(0.0f) + {} + }; + + SvStream& operator >> (SvStream& rInStream, XForm& rXForm); + + struct SaveStruct + { + BackgroundMode nBkMode; + MappingMode eMapMode; + GraphicsMode eGfxMode; + vcl::text::ComplexTextLayoutFlags nTextLayoutMode; + sal_Int32 nWinOrgX, nWinOrgY, nWinExtX, nWinExtY; + sal_Int32 nDevOrgX, nDevOrgY, nDevWidth, nDevHeight; + + WinMtfLineStyle aLineStyle; + WinMtfFillStyle aFillStyle; + + vcl::Font aFont; + Color aBkColor; + Color aTextColor; + sal_uInt32 nTextAlign; + RasterOp eRasterOp; + + Point aActPos; + WinMtfPathObj maPathObj; + WinMtfClipPath maClipPath; + XForm aXForm; + + bool bClockWiseArcDirection; + bool bFillStyleSelected; + }; + + struct BSaveStruct + { + BitmapEx aBmpEx; + tools::Rectangle aOutRect; + sal_uInt32 nWinRop; + bool m_bForceAlpha = false; + + BSaveStruct(const Bitmap& rBmp, const tools::Rectangle& rOutRect, sal_uInt32 nRop) + : aBmpEx(rBmp) + , aOutRect(rOutRect) + , nWinRop(nRop) + {} + + BSaveStruct(const BitmapEx& rBmpEx, const tools::Rectangle& rOutRect, sal_uInt32 nRop, + bool bForceAlpha = false) + : aBmpEx(rBmpEx) + , aOutRect(rOutRect) + , nWinRop(nRop) + , m_bForceAlpha(bForceAlpha) + {} + }; + + // tdf#127471 implement detection and correction of wrongly scaled + // fonts in own-written, old (before this fix) EMF/WMF files + class ScaledFontDetectCorrectHelper + { + private: + rtl::Reference<MetaFontAction> maCurrentMetaFontAction; + std::vector<double> maAlternativeFontScales; + std::vector<std::pair<rtl::Reference<MetaFontAction>, double>> maPositiveIdentifiedCases; + std::vector<std::pair<rtl::Reference<MetaFontAction>, double>> maNegativeIdentifiedCases; + + public: + ScaledFontDetectCorrectHelper(); + void endCurrentMetaFontAction(); + void newCurrentMetaFontAction(const rtl::Reference<MetaFontAction>& rNewMetaFontAction); + void evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength); + void applyAlternativeFontScale(); + }; + + class MtfTools + { + MtfTools(MtfTools const &) = delete; + MtfTools& operator =(MtfTools const &) = delete; + + protected: + WinMtfPathObj maPathObj; + WinMtfClipPath maClipPath; + + WinMtfLineStyle maLatestLineStyle; + WinMtfLineStyle maLineStyle; + WinMtfLineStyle maNopLineStyle; + WinMtfFillStyle maLatestFillStyle; + WinMtfFillStyle maFillStyle; + WinMtfFillStyle maNopFillStyle; + WinMtfPalette maPalette; + + vcl::Font maLatestFont; + vcl::Font maFont; + sal_uInt32 mnLatestTextAlign; + sal_uInt32 mnTextAlign; + Color maLatestTextColor; + Color maTextColor; + Color maLatestBkColor; + Color maBkColor; + vcl::text::ComplexTextLayoutFlags mnLatestTextLayoutMode; + vcl::text::ComplexTextLayoutFlags mnTextLayoutMode; + BackgroundMode mnLatestBkMode; + BackgroundMode mnBkMode; + RasterOp meLatestRasterOp; + RasterOp meRasterOp; + + std::vector< std::unique_ptr<GDIObj> > mvGDIObj; + Point maActPos; + WMFRasterOp mnRop; + std::vector< std::shared_ptr<SaveStruct> > mvSaveStack; + + GraphicsMode meGfxMode; + MappingMode meMapMode; + + XForm maXForm; + sal_Int32 mnDevOrgX; + sal_Int32 mnDevOrgY; + sal_Int32 mnDevWidth; + sal_Int32 mnDevHeight; + sal_Int32 mnWinOrgX; + sal_Int32 mnWinOrgY; + sal_Int32 mnWinExtX; + sal_Int32 mnWinExtY; + + sal_Int32 mnPixX; // Reference Device in pixel + sal_Int32 mnPixY; // Reference Device in pixel + sal_Int32 mnMillX; // Reference Device in Mill + sal_Int32 mnMillY; // Reference Device in Mill + tools::Rectangle mrclFrame; + tools::Rectangle mrclBounds; + + GDIMetaFile* mpGDIMetaFile; + + SvStream* mpInputStream; // the WMF/EMF file to be read + sal_uInt32 mnStartPos; + sal_uInt32 mnEndPos; + std::vector<BSaveStruct> maBmpSaveList; + + // tdf#127471 always try to detect - only used with ScaledText + ScaledFontDetectCorrectHelper maScaledFontHelper; + + bool mbNopMode : 1; + bool mbClockWiseArcDirection : 1; + bool mbFillStyleSelected : 1; + bool mbClipNeedsUpdate : 1; + bool mbComplexClip : 1; + bool mbIsMapWinSet : 1; + bool mbIsMapDevSet : 1; + + void UpdateLineStyle(); + void UpdateFillStyle(); + + Point ImplMap(const Point& rPt); + Point ImplScale(const Point& rPt); + Size ImplMap(const Size& rSize, bool bDoWorldTransform = true); + tools::Rectangle ImplMap(const tools::Rectangle& rRectangle); + void ImplMap(vcl::Font& rFont); + tools::Polygon& ImplMap(tools::Polygon& rPolygon); + tools::PolyPolygon& ImplMap(tools::PolyPolygon& rPolyPolygon); + void ImplScale(tools::Polygon& rPolygon); + tools::PolyPolygon& ImplScale(tools::PolyPolygon& rPolyPolygon); + void ImplResizeObjectArry(sal_uInt32 nNewEntry); + void ImplSetNonPersistentLineColorTransparenz(); + void ImplDrawClippedPolyPolygon(const tools::PolyPolygon& rPolyPoly); + void ImplDrawBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBitmap); + + public: + + void SetDevByWin(); //Hack to set varying defaults for incompletely defined files. + void SetDevOrg(const Point& rPoint); + void SetDevOrgOffset(sal_Int32 nXAdd, sal_Int32 nYAdd); + void SetDevExt(const Size& rSize, bool regular = true); + void ScaleDevExt(double fX, double fY); + + void SetWinOrg(const Point& rPoint, bool bIsEMF = false); + void SetWinOrgOffset(sal_Int32 nX, sal_Int32 nY); + void SetWinExt(const Size& rSize, bool bIsEMF = false); + void ScaleWinExt(double fX, double fY); + + void SetrclBounds(const tools::Rectangle& rRect); + void SetrclFrame(const tools::Rectangle& rRect); + void SetRefPix(const Size& rSize); + void SetRefMill(const Size& rSize); + + void SetMapMode(MappingMode mnMapMode); + void SetWorldTransform(const XForm& rXForm); + void ModifyWorldTransform(const XForm& rXForm, ModifyWorldTransformMode nMode); + + void Push(); + void Pop( const sal_Int32 nSavedDC = -1 ); + + WMFRasterOp SetRasterOp(WMFRasterOp nRasterOp); + void StrokeAndFillPath(bool bStroke, bool bFill); + + void SetArcDirection(bool bCounterClockWise); + bool IsArcDirectionClockWise() { return mbClockWiseArcDirection; }; + void SetGfxMode(GraphicsMode nGfxMode) { meGfxMode = nGfxMode; }; + GraphicsMode GetGfxMode() const { return meGfxMode; }; + void SetBkMode(BackgroundMode nMode); + void SetBkColor(const Color& rColor); + void SetTextColor(const Color& rColor); + void SetTextAlign(sal_uInt32 nAlign); + + void CreateObject(std::unique_ptr<GDIObj> pObject); + void CreateObjectIndexed(sal_uInt32 nIndex, std::unique_ptr<GDIObj> pObject); + void CreateObject(); + + void DeleteObject(sal_uInt32 nIndex); + void SelectObject(sal_uInt32 nIndex); + rtl_TextEncoding GetCharSet() const { return maFont.GetCharSet(); }; + const vcl::Font& GetFont() const { return maFont; } + void SetTextLayoutMode(vcl::text::ComplexTextLayoutFlags nLayoutMode); + + void ClearPath() { maPathObj.Init(); }; + void ClosePath() { maPathObj.ClosePath(); }; + const tools::PolyPolygon& GetPathObj() const { return maPathObj; }; + + void MoveTo(const Point& rPoint, bool bRecordPath = false); + void LineTo(const Point& rPoint, bool bRecordPath = false); + void DrawPixel(const Point& rSource, const Color& rColor); + void DrawRect(const tools::Rectangle& rRect, bool bEdge = true); + void DrawRectWithBGColor(const tools::Rectangle& rRect); + void DrawRoundRect(const tools::Rectangle& rRect, const Size& rSize); + void DrawEllipse(const tools::Rectangle& rRect); + void DrawArc( + const tools::Rectangle& rRect, + const Point& rStartAngle, + const Point& rEndAngle, + bool bDrawTo = false + ); + void DrawPie( + const tools::Rectangle& rRect, + const Point& rStartAngle, + const Point& rEndAngle + ); + void DrawChord( + const tools::Rectangle& rRect, + const Point& rStartAngle, + const Point& rEndAngle + ); + void DrawPolygon(tools::Polygon rPolygon, bool bRecordPath); + void DrawPolyPolygon(tools::PolyPolygon& rPolyPolygon, bool bRecordPath = false); + void DrawPolyLine(tools::Polygon rPolygon, + bool bDrawTo = false, + bool bRecordPath = false + ); + void DrawPolyBezier(tools::Polygon rPolygon, + bool bDrawTo, + bool bRecordPath + ); + void DrawText(Point& rPosition, + OUString const & rString, + std::vector<sal_Int32>* pDXArry = nullptr, + tools::Long* pDYArry = nullptr, + bool bRecordPath = false, + GraphicsMode nGraphicsMode = GraphicsMode::GM_COMPATIBLE); + + void ResolveBitmapActions(std::vector<BSaveStruct>& rSaveList); + + void IntersectClipRect(const tools::Rectangle& rRect); + void ExcludeClipRect(const tools::Rectangle& rRect); + void MoveClipRegion(const Size& rSize); + void SetClipPath( + const tools::PolyPolygon& rPolyPoly, + RegionMode nClippingMode, + bool bIsMapped + ); + void SetDefaultClipPath(); + void UpdateClipRegion(); + void AddFromGDIMetaFile(GDIMetaFile& rGDIMetaFile); + + void PassEMFPlus(void const * pBuffer, sal_uInt32 nLength); + void PassEMFPlusHeaderInfo(); + + Color ReadColor(); + + explicit MtfTools(GDIMetaFile& rGDIMetaFile, SvStream& rStreamWMF); + ~MtfTools() COVERITY_NOEXCEPT_FALSE; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/inc/pch/precompiled_emfio.cxx b/emfio/inc/pch/precompiled_emfio.cxx new file mode 100644 index 000000000..d87142820 --- /dev/null +++ b/emfio/inc/pch/precompiled_emfio.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "precompiled_emfio.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/inc/pch/precompiled_emfio.hxx b/emfio/inc/pch/precompiled_emfio.hxx new file mode 100644 index 000000000..4c258670a --- /dev/null +++ b/emfio/inc/pch/precompiled_emfio.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + This file has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-03-08 13:13:45 using: + ./bin/update_pch emfio emfio --cutoff=8 --exclude:system --exclude:module --include:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./emfio/inc/pch/precompiled_emfio.hxx "make emfio.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <memory> +#include <vector> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/endian.h> +#include <osl/mutex.hxx> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/crc.h> +#include <rtl/instance.hxx> +#include <rtl/ref.hxx> +#include <rtl/string.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/types.h> +#include <vcl/bitmapex.hxx> +#include <vcl/dllapi.h> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <salhelper/simplereferenceobject.hxx> +#include <tools/gen.hxx> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/inc/wmfreader.hxx b/emfio/inc/wmfreader.hxx new file mode 100644 index 000000000..5c6979550 --- /dev/null +++ b/emfio/inc/wmfreader.hxx @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_EMFIO_INC_WMFREADER_HXX +#define INCLUDED_EMFIO_INC_WMFREADER_HXX + +#include "mtftools.hxx" +#include <tools/stream.hxx> + +// predefines +struct WmfExternal; + +namespace emfio +{ + class WmfReader : public MtfTools + { + private: + sal_uInt16 mnUnitsPerInch; + sal_uInt32 mnRecSize; + bool mbPlaceable; + + // embedded EMF data + std::optional<std::vector<sal_uInt8>> mpEMFStream; + + // total number of comment records containing EMF data + sal_uInt32 mnEMFRecCount; + + // number of EMF records read + sal_uInt32 mnEMFRec; + + // total size of embedded EMF data + sal_uInt32 mnEMFSize; + + sal_uInt32 mnSkipActions; + + // eventually handed over external header + const WmfExternal* mpExternalHeader; + + bool mbEnableEMFPlus = true; + + // reads header of the WMF-Datei + bool ReadHeader(); + + // reads parameters of the record with the functionnumber nFunction. + void ReadRecordParams(sal_uInt32 nRecordSize, sal_uInt16 nFunction); + + Point ReadPoint(); // reads and converts a point (first X then Y) + Point ReadYX(); // reads and converts a point (first Y then X) + tools::Rectangle ReadRectangle(); // reads and converts a rectangle + Size ReadYXExt(); + void GetPlaceableBound(tools::Rectangle& rSize, SvStream* pStrm); + + public: + WmfReader(SvStream& rStreamWMF, GDIMetaFile& rGDIMetaFile, const WmfExternal* pExternalHeader); + + // read WMF file from stream and fill the GDIMetaFile + void ReadWMF(); + + // Allows disabling EMF+ if EMF is embedded in this WMF. + void SetEnableEMFPlus(bool bEnableEMFPlus) { mbEnableEMFPlus = bEnableEMFPlus; } + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx new file mode 100644 index 000000000..5cdfac8a3 --- /dev/null +++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx @@ -0,0 +1,1675 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <config_fonts.h> + +#include <test/bootstrapfixture.hxx> +#include <test/xmltesttools.hxx> +#include <unotest/macros_test.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> + +#include <comphelper/seqstream.hxx> + +#include <com/sun/star/graphic/EmfTools.hpp> + +#include <drawinglayer/primitive2d/Primitive2DContainer.hxx> +#include <drawinglayer/tools/primitive2dxmldump.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> + +#include <memory> +#include <string_view> + +namespace +{ +using namespace css; +using namespace css::uno; +using namespace css::io; +using namespace css::graphic; +using drawinglayer::primitive2d::Primitive2DSequence; +using drawinglayer::primitive2d::Primitive2DContainer; + +class Test : public test::BootstrapFixture, public XmlTestTools, public unotest::MacrosTest +{ + uno::Reference<lang::XComponent> mxComponent; + const OString aXPathPrefix = "/primitive2D/metafile/transform/"; + + void testPolyPolygon(); + void TestDrawImagePointsTypeBitmap(); + void TestDrawString(); + void TestDrawStringAlign(); + void TestDrawStringTransparent(); + void TestDrawStringWithBrush(); + void TestEmfPlusDrawBeziers(); + void TestDrawLine(); + void TestDrawLineWithCaps(); + void TestDrawLineWithDash(); + void TestLinearGradient(); + void TestTextMapMode(); + void TestEnglishMapMode(); + void TestRectangleWithModifyWorldTransform(); + void TestArcStartPointEqualEndPoint(); + void TestArcInsideWronglyDefinedRectangle(); + void TestChordWithModifyWorldTransform(); + void TestEllipseWithSelectClipPath(); + void TestEllipseXformIntersectClipRect(); + void TestSetArcDirection(); + void TestDrawPolyLine16WithClip(); + void TestFillRegion(); + void TestEmfPlusBrushPathGradientWithBlendColors(); + void TestEmfPlusGetDC(); + void TestEmfPlusSave(); + void TestEmfPlusDrawPathWithCustomCap(); + void TestEmfPlusDrawPathWithMiterLimit(); + void TestEmfPlusFillClosedCurve(); + void TestExtTextOutOpaqueAndClipTransform(); + + void TestBitBltStretchBltWMF(); + void TestExtTextOutOpaqueAndClipWMF(); + void TestPaletteWMF(); + void TestRestoreDCWMF(); + void TestRoundrectWMF(); + void TestStretchDIBWMF(); + void TestMoveToLineToWMF(); + void TestPolylinetoCloseStroke(); + void TestPolyLineWidth(); + + void TestRestoreDC(); + void TestRoundRect(); + void TestCreatePen(); + void TestPdfInEmf(); + + Primitive2DSequence parseEmf(std::u16string_view aSource); + +public: + void setUp() override; + void tearDown() override; + uno::Reference<lang::XComponent>& getComponent() { return mxComponent; } + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testPolyPolygon); + CPPUNIT_TEST(TestDrawImagePointsTypeBitmap); + CPPUNIT_TEST(TestDrawString); + CPPUNIT_TEST(TestDrawStringAlign); + CPPUNIT_TEST(TestDrawStringTransparent); + CPPUNIT_TEST(TestDrawStringWithBrush); + CPPUNIT_TEST(TestEmfPlusDrawBeziers); + CPPUNIT_TEST(TestDrawLine); + CPPUNIT_TEST(TestDrawLineWithCaps); + CPPUNIT_TEST(TestDrawLineWithDash); + CPPUNIT_TEST(TestLinearGradient); + CPPUNIT_TEST(TestTextMapMode); + CPPUNIT_TEST(TestEnglishMapMode); + CPPUNIT_TEST(TestRectangleWithModifyWorldTransform); + CPPUNIT_TEST(TestArcStartPointEqualEndPoint); + CPPUNIT_TEST(TestArcInsideWronglyDefinedRectangle); + CPPUNIT_TEST(TestChordWithModifyWorldTransform); + CPPUNIT_TEST(TestEllipseWithSelectClipPath); + CPPUNIT_TEST(TestEllipseXformIntersectClipRect); + CPPUNIT_TEST(TestSetArcDirection); + CPPUNIT_TEST(TestDrawPolyLine16WithClip); + CPPUNIT_TEST(TestFillRegion); + CPPUNIT_TEST(TestEmfPlusBrushPathGradientWithBlendColors); + CPPUNIT_TEST(TestEmfPlusGetDC); + CPPUNIT_TEST(TestEmfPlusSave); + CPPUNIT_TEST(TestEmfPlusDrawPathWithCustomCap); + CPPUNIT_TEST(TestEmfPlusDrawPathWithMiterLimit); + CPPUNIT_TEST(TestEmfPlusFillClosedCurve); + CPPUNIT_TEST(TestExtTextOutOpaqueAndClipTransform); + + CPPUNIT_TEST(TestBitBltStretchBltWMF); + CPPUNIT_TEST(TestExtTextOutOpaqueAndClipWMF); + CPPUNIT_TEST(TestPaletteWMF); + CPPUNIT_TEST(TestRestoreDCWMF); + CPPUNIT_TEST(TestRoundrectWMF); + CPPUNIT_TEST(TestStretchDIBWMF); + CPPUNIT_TEST(TestMoveToLineToWMF); + CPPUNIT_TEST(TestPolylinetoCloseStroke); + CPPUNIT_TEST(TestPolyLineWidth); + CPPUNIT_TEST(TestRestoreDC); + CPPUNIT_TEST(TestRoundRect); + CPPUNIT_TEST(TestCreatePen); + CPPUNIT_TEST(TestPdfInEmf); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::setUp() +{ + test::BootstrapFixture::setUp(); + + mxDesktop.set(frame::Desktop::create(mxComponentContext)); +} + +void Test::tearDown() +{ + if (mxComponent.is()) + mxComponent->dispose(); + + test::BootstrapFixture::tearDown(); +} + +Primitive2DSequence Test::parseEmf(std::u16string_view aSource) +{ + const Reference<XEmfParser> xEmfParser = EmfTools::create(m_xContext); + + OUString aUrl = m_directories.getURLFromSrc(aSource); + OUString aPath = m_directories.getPathFromSrc(aSource); + + SvFileStream aFileStream(aUrl, StreamMode::READ); + std::size_t nSize = aFileStream.remainingSize(); + CPPUNIT_ASSERT_MESSAGE("Unable to open file", nSize); + std::unique_ptr<sal_Int8[]> pBuffer(new sal_Int8[nSize + 1]); + aFileStream.ReadBytes(pBuffer.get(), nSize); + pBuffer[nSize] = 0; + + Sequence<sal_Int8> aData(pBuffer.get(), nSize + 1); + Reference<XInputStream> aInputStream(new comphelper::SequenceInputStream(aData)); + css::uno::Sequence<css::beans::PropertyValue> aEmptyValues; + + return xEmfParser->getDecomposition(aInputStream, aPath, aEmptyValues); +} + +void Test::testPolyPolygon() +{ + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/fdo79679-2.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + + CPPUNIT_ASSERT(pDocument); + + // Chart axis + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h19746v14817h-19746z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 2); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#ffffff"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m0 0h19781v14852h-19781z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]/polypolygon", "path", + "m2574 13194v-12065h15303v12065z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke", 116); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[1]/polygon", + "2574,13194 2574,1129 17877,1129 17877,13194"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", "color", "#ffffff"); + + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[10]/polygon", + "8674,13194 8674,1129"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[10]/line", "color", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion", 28); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[6]", "width", "459"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[6]", "x", "9908"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[6]", "text", "0.5"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[6]", "fontcolor", "#000000"); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray", 98); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]/point", "x", "2574"); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]/point", "y", "1129"); +} + +void Test::TestDrawImagePointsTypeBitmap() +{ + // tdf#142941 EMF+ file with ObjectTypeImage, FillRects, DrawImagePoints ,records + // The test is checking the position of displaying bitmap with too large SrcRect + + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawImagePointsTypeBitmap.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#0080ff"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy11", "5346"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy12", "0"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy13", "5558"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy21", "0"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy22", "4716"); + assertXPath(pDocument, aXPathPrefix + "bitmap", "xy23", "5564"); + assertXPath( + pDocument, aXPathPrefix + "bitmap/data[2]", "row", + "020202,ffffff,ffffff,ffffff,fefefe,ffffff,ffffff,fefefe,ffffff,ffffff,f8f8f8,ffffff," + "fdfdfd,ffffff,ffffff,fdfdfd,ffffff,ffffff,ffffff,fbfbfb,010101,ffffff,fefefe,ffffff," + "ffffff,fbfbfb,ffffff,fdfdfd,fcfcfc,fdfdfd,ffffff,ffffff,ffffff,ffffff,ffffff,ffffff," + "ffffff,ffffff,ffffff,ffffff,020202,fdfdfd,ffffff,ffffff,fefefe,ffffff,ffffff,ffffff," + "ffffff,fbfbfb,fefefe,ffffff,fcfcfc,ffffff,fdfdfd,ffffff,ffffff,ffffff,ffffff,fbfbfb," + "010101,ffffff,fefefe,ffffff,ffffff,ffffff,fcfcfc,ffffff,fafafa,ffffff,ffffff,fefefe," + "ffffff,fdfdfd,fefefe,fefefe,ffffff,ffffff,fdfdfd,fffbfb,1e0000,8f4347,b13a3e,b82d32," + "bb3438,b73237,b63338,b33035,b63338"); +} + +void Test::TestDrawString() +{ +#if HAVE_MORE_FONTS + // EMF+ file with only one DrawString Record + // Since the text is undecorated the optimal choice is a simpletextportion primitive + + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawString.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + // check correct import of the DrawString: height, position, text, color and font + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "height", "120"); + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "x", "817"); + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "y", "1137"); + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "text", "TEST"); + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "fontcolor", "#000000"); + assertXPath(pDocument, aXPathPrefix + "transform/textsimpleportion", "familyname", "CALIBRI"); +#endif +} + +void Test::TestDrawStringAlign() +{ +#if HAVE_MORE_FONTS + // EMF+ DrawString with alignment (StringAlignmentNear, StringAlignmentFar, StringAlignmentCenter) + // It seems Arial font is replaced with Liberation Sans. These numbers are valid for Liberation Sans. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawStringAlign.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/transform", 9); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "width", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "height", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "x", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "y", "22"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "text", "HLVT"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "fontcolor", + "#000000"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[1]/textsimpleportion", "familyname", + "ARIAL"); + + assertXPath(pDocument, aXPathPrefix + "mask/transform[2]/textsimpleportion", "width", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[2]/textsimpleportion", "height", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[2]/textsimpleportion", "x", "143"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[2]/textsimpleportion", "y", "22"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[2]/textsimpleportion", "text", "HCVT"); + + // TODO Make the position of the text the same across the platforms (Arial vs Liberation Sans). + // This is usually 276, but can be 275 as well; depends on what fonts are installed? + sal_Int32 nX + = getXPath(pDocument, aXPathPrefix + "mask/transform[3]/textsimpleportion", "x").toInt32(); + CPPUNIT_ASSERT(nX >= 275); + CPPUNIT_ASSERT(nX <= 276); + assertXPath(pDocument, aXPathPrefix + "mask/transform[3]/textsimpleportion", "y", "22"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[3]/textsimpleportion", "text", "HRVT"); + + assertXPath(pDocument, aXPathPrefix + "mask/transform[4]/textsimpleportion", "x", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[4]/textsimpleportion", "y", "66"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[4]/textsimpleportion", "text", "HLVC"); + + assertXPath(pDocument, aXPathPrefix + "mask/transform[5]/textsimpleportion", "x", "142"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[5]/textsimpleportion", "y", "66"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[5]/textsimpleportion", "text", "HCVC"); + + // This is usually 274, but can be 273 as well; depends on what fonts are installed? + nX = getXPath(pDocument, aXPathPrefix + "mask/transform[6]/textsimpleportion", "x").toInt32(); + CPPUNIT_ASSERT(nX >= 273); + CPPUNIT_ASSERT(nX <= 274); + assertXPath(pDocument, aXPathPrefix + "mask/transform[6]/textsimpleportion", "y", "66"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[6]/textsimpleportion", "text", "HRVC"); + + assertXPath(pDocument, aXPathPrefix + "mask/transform[7]/textsimpleportion", "x", "12"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[7]/textsimpleportion", "y", "110"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[7]/textsimpleportion", "text", "HLVB"); + + assertXPath(pDocument, aXPathPrefix + "mask/transform[8]/textsimpleportion", "x", "143"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[8]/textsimpleportion", "y", "110"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[8]/textsimpleportion", "text", "HCVB"); + + // This is usually 275, but can be 274 as well; depends on what fonts are installed? + nX = getXPath(pDocument, aXPathPrefix + "mask/transform[9]/textsimpleportion", "x").toInt32(); + CPPUNIT_ASSERT(nX >= 274); + CPPUNIT_ASSERT(nX <= 275); + assertXPath(pDocument, aXPathPrefix + "mask/transform[9]/textsimpleportion", "y", "110"); + assertXPath(pDocument, aXPathPrefix + "mask/transform[9]/textsimpleportion", "text", "HRVB"); +#endif +} + +void Test::TestDrawStringTransparent() +{ +#if HAVE_MORE_FONTS + // EMF+ file with one DrawString Record with transparency + + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawStringTransparent.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence", "transparence", + "50"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "height", "24"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "x", "66"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "y", "74"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "text", "Transparent Text"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "fontcolor", "#0000ff"); + assertXPath(pDocument, aXPathPrefix + "mask/transform/unifiedtransparence/textsimpleportion", + "familyname", "ARIAL"); +#endif +} + +void Test::TestDrawStringWithBrush() +{ + // tdf#142975 EMF+ with records: DrawString, Brush and Font + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawStringWithBrush.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "xy11", "20"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "xy13", "16"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "xy22", "20"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "xy33", "1"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "text", + "0123456789ABCDEF"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "fontcolor", "#a50021"); + assertXPath(pDocument, aXPathPrefix + "transform/textdecoratedportion", "familyname", + "TIMES NEW ROMAN"); +} + +void Test::TestEmfPlusDrawBeziers() +{ + // tdf#107019 tdf#154789 EMF+ records: DrawBeziers + // Check if DrawBeziers is displayed correctly and text is rotated + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawBeziers.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 4); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow", 9); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[9]/line", "color", "#00ff00"); + + assertXPath(pDocument, aXPathPrefix + "transform", 5); + assertXPath(pDocument, aXPathPrefix + "transform[1]/textsimpleportion", "fontcolor", "#000000"); + assertXPath(pDocument, aXPathPrefix + "transform[1]/textsimpleportion", "text", "% Efficiency"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy11", "0"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy12", "4"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy13", "800"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy21", "-4"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy22", "0"); + assertXPath(pDocument, aXPathPrefix + "transform[1]", "xy23", "3195"); +} + +void Test::TestDrawLine() +{ + // EMF+ with records: DrawLine + // The line is colored and has a specified width, therefore a polypolygonstroke primitive is the optimal choice + Primitive2DSequence aSequence = parseEmf(u"emfio/qa/cppunit/emf/data/TestDrawLine.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + // check correct import of the DrawLine: color and width of the line + assertXPath(pDocument, aXPathPrefix + "mask/unifiedtransparence", "transparence", "14"); + assertXPath(pDocument, aXPathPrefix + "mask/unifiedtransparence/polypolygonstroke/line", + "color", "#c01002"); + assertXPath(pDocument, aXPathPrefix + "mask/unifiedtransparence/polypolygonstroke/line", + "width", "115"); + assertXPath(pDocument, aXPathPrefix + "mask/unifiedtransparence/polypolygonstroke/line", + "linecap", "BUTT"); + assertXPath(pDocument, aXPathPrefix + "mask/unifiedtransparence/polypolygonstroke/polypolygon", + "path", "m55.5192348773662 403.573503917507 874.352660545936-345.821325648415"); +} + +void Test::TestDrawLineWithCaps() +{ + // EMF+ with records: DrawLine + // Test lines with different caps styles and arrows + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithCaps.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow", 3); + + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[1]/line", "width", "211"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[1]/stroke", 0); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[1]/linestartattribute/polypolygon", + "path", "m0-1 1 2h-2z"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[1]/lineendattribute", 0); + + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[2]/line", "width", "211"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[2]/stroke", 0); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[2]/linestartattribute/polypolygon", + "path", "m0-1 1 2h-2z"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[2]/lineendattribute", 0); + + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[3]/line", "width", "423"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[3]/stroke", 0); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[3]/linestartattribute", 0); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow[3]/lineendattribute/polypolygon", + "path", "m-1-1h2v2h-2z"); + + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence", 3); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]", "transparence", "39"); + assertXPath(pDocument, + aXPathPrefix + + "unifiedtransparence[1]/polygonstrokearrow/linestartattribute/polypolygon", + "path", + "m-1 1h2v-1l-0.0764-0.3827-0.2165-0.3244-0.3244-0.2165-0.3827-0.0764-0.3827 " + "0.0764-0.3244 0.2165-0.2165 0.3244-0.0764 0.3827z"); + assertXPath(pDocument, + aXPathPrefix + + "unifiedtransparence[1]/polygonstrokearrow/lineendattribute/polypolygon", + "path", "m-1 1h2v-1l-1-1-1 1z"); + assertXPath(pDocument, + aXPathPrefix + "unifiedtransparence[2]/polygonstrokearrow/linestartattribute", 0); + assertXPath(pDocument, + aXPathPrefix + + "unifiedtransparence[2]/polygonstrokearrow/lineendattribute/polypolygon", + "path", "m-1-1h2v2h-2z"); + assertXPath(pDocument, + aXPathPrefix + + "unifiedtransparence[3]/polygonstrokearrow/lineendattribute/polypolygon", + "path", "m0-1 1 1-0.5 0.5v0.5h-1v-0.5l-0.5-0.5z"); +} + +void Test::TestDrawLineWithDash() +{ + // EMF+ with records: DrawLine, ScaleWorldTransform, RotateWorldTransform + // Test lines with different dash styles, different line arrows and different World Rotation + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + // check correct import of the DrawLine: color and width of the line + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke", 10); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[1]/line", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[1]/line", "width", "185"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[1]/stroke", 0); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[2]/line", "width", "185"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[2]/stroke", "dotDashArray", + "185 185 "); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[3]/line", "width", "185"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[3]/stroke", "dotDashArray", + "556 185 "); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[4]/line", "width", "185"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[4]/stroke", "dotDashArray", + "556 185 185 185 "); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[5]/line", "width", "370"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygonstroke[5]/stroke", "dotDashArray", + "556 185 185 185 185 185 "); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow", 2); + //TODO polypolygonstroke[6-9]/stroke add support for PenDataDashedLineOffset + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[1]/line", "width", "370"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[1]/stroke", "dotDashArray", + "1851 741 5554 1481 "); + // Arrows on both ends + assertXPath(pDocument, + aXPathPrefix + "mask/polygonstrokearrow[1]/linestartattribute/polypolygon", "path", + "m0-1 1 2h-2z"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[1]/lineendattribute/polypolygon", + "path", "m0-1 1 2h-2z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[2]/line", "width", "370"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[2]/stroke", "dotDashArray", + "1852 741 5555 1481 "); + assertXPath(pDocument, + aXPathPrefix + "mask/polygonstrokearrow[2]/linestartattribute/polypolygon", "path", + "m-1 1h2v-1l-0.0764-0.3827-0.2165-0.3244-0.3244-0.2165-0.3827-0.0764-0.3827 " + "0.0764-0.3244 0.2165-0.2165 0.3244-0.0764 0.3827z"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow[2]/lineendattribute/polypolygon", + "path", + "m-1 1h2v-1l-0.0764-0.3827-0.2165-0.3244-0.3244-0.2165-0.3827-0.0764-0.3827 " + "0.0764-0.3244 0.2165-0.2165 0.3244-0.0764 0.3827z"); +} + +void Test::TestLinearGradient() +{ + // EMF+ file with LinearGradient brush + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestLinearGradient.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy11", "1"); + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy12", "0"); + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy13", "0"); + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy21", "0"); + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy22", "1"); + assertXPath(pDocument, "/primitive2D/metafile/transform", "xy23", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "height", "7610"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "width", "15232"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h15232v7610h-15232z"); + + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "spreadmethod", "repeat"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "startx", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "starty", "-1"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "endx", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "endy", "-1"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "opacity", + "0.392156862745098"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]/polypolygon", "path", + "m0 0.216110019646294h7615.75822989746v7610.21611001965h-7615.75822989746z"); + + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "spreadmethod", "repeat"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "startx", "-1"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "starty", "-1"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "endx", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "endy", "-1"); + assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "opacity", "1"); + assertXPath( + pDocument, aXPathPrefix + "mask/svglineargradient[2]/polypolygon", "path", + "m7615.75822989746 0.216110019646294h7615.75822989746v7610.21611001965h-7615.75822989746z"); +} + +void Test::TestTextMapMode() +{ + // EMF with records: SETMAPMODE with MM_TEXT MapMode, POLYLINE16, EXTCREATEPEN, EXTTEXTOUTW + // MM_TEXT is mapped to one device pixel. Positive x is to the right; positive y is down. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TextMapMode.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 2); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#ffffff"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m0 0h3542v4647h-3542z"); + + assertXPath(pDocument, aXPathPrefix + "textsimpleportion", 20); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "text", "N"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "fontcolor", "#4a70e3"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "x", "2099"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "y", "1859"); + + assertXPath(pDocument, aXPathPrefix + "polygonstroke", 138); + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke[1]/polygon", "2142,1638 2142,1489"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[1]/line", "color", "#4a70e3"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[1]/line", "width", "11"); + + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke[10]/polygon", "1967,1029 1869,952"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[10]/line", "color", "#4a70e3"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[10]/line", "width", "11"); + + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke[20]/polygon", + "2710,1113 2873,1330"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[20]/line", "color", "#666666"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[20]/line", "width", "11"); +} + +void Test::TestEnglishMapMode() +{ + // EMF image with records: SETMAPMODE with MM_ENGLISH MapMode, STROKEANDFILLPATH, EXTTEXTOUTW, SETTEXTALIGN, STRETCHDIBITS + // MM_LOENGLISH is mapped to 0.01 inch. Positive x is to the right; positive y is up.M + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/test_mm_hienglish_ref.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", 1); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon[1]", "path", + "m0 0h29699v20999h-29699z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 3); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#ffffad"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m-1-1h29700v21001h-29700z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]/polypolygon", "path", + "m1058 7937v5293h3175v-1059h-2118v-4234z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[3]/polypolygon", "path", + "m12699 1058h4234v1060h-1587v4231h-1059v-4231h-1588z"); + + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion", 4); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "text", "UL"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "fontcolor", "#000000"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "x", "106"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "y", "459"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "width", "424"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "height", "424"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 3); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[1]/polygon", + "-1,-1 29699,-1 29699,21000 -1,21000"); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[2]/polygon", + "1058,7937 1058,13230 4233,13230 4233,12171 2115,12171 2115,7937"); + assertXPathContent( + pDocument, aXPathPrefix + "mask/polygonhairline[3]/polygon", + "12699,1058 16933,1058 16933,2118 15346,2118 15346,6349 14287,6349 14287,2118 12699,2118"); +} + +void Test::TestRectangleWithModifyWorldTransform() +{ + // EMF image with records: EXTCREATEPEN, SELECTOBJECT, MODIFYWORLDTRANSFORM, RECTANGLE + + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestRectangleWithModifyWorldTransform.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 1); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#ffffff"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m1042 417 918 529 353 610-918-528z"); + + assertXPath(pDocument, aXPathPrefix + "polygonstroke", 1); + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke[1]/polygon", + "1042,417 1960,946 2313,1556 1395,1028"); +} + +void Test::TestChordWithModifyWorldTransform() +{ + // EMF import test with records: CHORD, MODIFYWORLDTRANSFORM, EXTCREATEPEN, SELECTOBJECT + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestChordWithModifyWorldTransform.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#ffffff"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor/polypolygon", "path", + "m590 448-21 45-66 24-34 12-33 12-21 45-33 12 12 33-33 12 12 33 12 34 33-12 12 33 " + "34-12 33-12 45 21 33-12 33-12 46 21 66-25 33-12 66-24 34-12 66-24z"); + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke/polygon", + "590,448 569,493 503,517 469,529 436,541 415,586 382,598 394,631 361,643 " + "361,643 373,676 373,676 385,710 418,698 430,731 464,719 497,707 542,728 " + "575,716 608,704 654,725 720,700 753,688 819,664 853,652 919,628"); +} + +void Test::TestArcStartPointEqualEndPoint() +{ + // i73608 EMF import test where StartPoint == EndPoint. It should draw full circle + // Records: SETMAPMODE, SETWINDOWEXTEX, SETWINDOWORGEX, EXTSELECTCLIPRGN, INTERSECTCLIPRECT, MOVETOEX, ARC + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestArcStartPointEqualEndPoint.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/group/mask/polygonhairline", "color", "#000000"); + assertXPathContent( + pDocument, aXPathPrefix + "mask/group/mask/polygonhairline/polygon", + "11886,23133 11970,23223 12051,23316 12131,23410 12208,23506 12282,23604 12354,23704 " + "12424,23805 12491,23909 12556,24014 12618,24120 12677,24228 12734,24337 12788,24448 " + "12839,24560 12888,24673 12933,24788 12976,24903 13016,25020 13053,25137 13087,25256 " + "13119,25375 13147,25495 13172,25615 13195,25736 13214,25858 13230,25980 13244,26103 " + "13254,26225 13261,26348 13266,26472 13267,26595 13265,26718 13260,26841 13253,26964 " + "13242,27087 13228,27209 13211,27331 13191,27453 13168,27574 13142,27694 13113,27814 " + "13082,27933 13047,28051 13009,28169 12969,28285 12926,28400 12879,28514 12830,28628 " + "12779,28739 12724,28850 12667,28959 12607,29067 12545,29173 12480,29277 12412,29380 " + "12342,29482 12269,29581 12194,29679 12117,29775 12037,29869 11955,29960 11871,30050 " + "11784,30138 11696,30224 11605,30307 11512,30388 11418,30467 11321,30543 11223,30617 " + "11122,30689 11020,30758 10917,30825 10811,30888 10705,30950 10596,31009 10487,31065 " + "10376,31118 10263,31168 10150,31216 10035,31261 9919,31303 9803,31343 9685,31379 " + "9566,31412 9447,31443 9327,31471 9206,31495 9085,31517 8963,31535 8841,31551 8719,31564 " + "8596,31573 8473,31580 8350,31583 8226,31584 8103,31581 7980,31576 7857,31567 7735,31555 " + "7612,31541 7491,31523 7369,31503 7248,31479 7128,31452 7008,31423 6890,31390 6772,31355 " + "6655,31316 6538,31275 6423,31231 6310,31184 6197,31135 6085,31082 5975,31027 5866,30969 " + "5759,30909 5653,30846 5549,30780 5447,30712 5346,30641 5247,30568 5150,30492 5054,30414 " + "4961,30334 4870,30251 4780,30166 4693,30079 4608,29990 4525,29899 4445,29805 4367,29710 " + "4291,29613 4217,29514 4146,29414 4078,29311 4012,29207 3949,29101 3888,28994 3830,28885 " + "3775,28775 3722,28664 3672,28551 3625,28438 3581,28323 3540,28207 3501,28090 3465,27972 " + "3433,27853 3403,27733 3376,27613 3352,27492 3331,27371 3313,27249 3299,27127 3287,27004 " + "3278,26881 3272,26758 3269,26635 3270,26512 3273,26388 3279,26265 3289,26143 3301,26020 " + "3316,25898 3335,25776 3356,25655 3380,25534 3408,25414 3438,25294 3471,25176 3508,25058 " + "3547,24941 3588,24825 3633,24711 3681,24597 3731,24484 3784,24373 3840,24263 3899,24155 " + "3960,24048 4023,23943 4090,23839 4159,23737 4230,23636 4304,23538 4380,23441 4459,23346 " + "4540,23253 4623,23162 4708,23074 4796,22987 4885,22902 4977,22820 5071,22740 5166,22663 " + "5264,22587 5363,22515 5465,22444 5567,22376 5672,22311 5778,22249 5885,22188 5994,22131 " + "6105,22076 6216,22024 6329,21975 6443,21929 6559,21885 6675,21845 6792,21807 6910,21772 " + "7029,21740 7149,21711 7269,21685 7390,21662 7512,21642 7634,21624 7756,21610 7879,21599 " + "8002,21591 8125,21586 8248,21584 8371,21585 8494,21589 8617,21596 8740,21606 8862,21619 " + "8985,21636 9106,21655 9227,21677 9348,21702 9468,21730 9587,21761 9705,21795 9823,21832 " + "9940,21872 10055,21914 10170,21960 10283,22008 10395,22059 10506,22113 10615,22169 " + "10723,22229 10830,22291 10935,22355 11038,22422 11140,22491 11240,22563 11338,22638 " + "11434,22715 11529,22794 11621,22875 11711,22959 11800,23045"); +} + +void Test::TestArcInsideWronglyDefinedRectangle() +{ + // tdf#142268 EMF import test with records: ARC + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestArcInsideWronglyDefinedRectangle.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polygonhairline", "color", "#000000"); + assertXPathContent( + pDocument, aXPathPrefix + "polygonhairline/polygon", + "1630,1460 1650,1470 1670,1480 1700,1490 1720,1500 1750,1510 1770,1520 1800,1530 1820,1530 " + "1850,1540 1870,1540 1900,1540 1930,1540 1950,1540 1980,1540 2000,1530 2030,1530 2050,1520 " + "2080,1510 2100,1500 2130,1490 2150,1480 2170,1470 2200,1450 2220,1440 2240,1420 2260,1400 " + "2280,1390 2290,1370 2310,1350 2330,1330 2340,1300 2360,1280 2370,1260 2380,1240 2390,1210 " + "2400,1190 2410,1160 2420,1140 2420,1110 2420,1080 2430,1060 2430,1030 2430,1000 2430,980 " + "2430,950 2420,930 2420,900 2410,870 2410,850 2400,820 2390,800 2380,770 2360,750 2350,730 " + "2340,710 2320,680 2300,660 2290,640 2270,630 2250,610 2230,590 2210,580 2190,560 2160,550 " + "2140,540 2120,520 2090,510 2070,510 2040,500 2020,490 1990,490 1970,480 1940,480 1920,480 " + "1890,480 1860,480 1840,490 1810,490 1790,500 1760,500 1740,510 1710,520 1690,530 1670,540 " + "1640,560 1620,570 1600,580 1580,600 1560,620 1540,640 1520,660 1510,680 1490,700 1480,720 " + "1460,740 1450,760"); +} + +void Test::TestEllipseWithSelectClipPath() +{ + // EMF import test with records: RECTANGLE, BEGINPATH, ENDPATH, ELLIPSE + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestEllipseWithSelectClipPath.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygon", 1); + assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygon[1]", "path", + "m2790 " + "776v-36-35h-36v-35l-35-35-35-36h-36l-35-35-35-35h-35-36l-35-35h-35-36l-35-36h-35-" + "36l-35-35h-35-71-35l-36-35h-70-35-36-70l-36-35h-35-71-35-71-35-71-35-35-71-35-71-" + "35-71-35l-35 35h-71-35-36-70l-35 35h-36-70-36l-35 35h-35-36l-35 36h-35-36l-35 " + "35h-35-36l-35 35-35 35h-35l-36 36-35 35v35h-35v35 36 35 35h35v35l35 36 36 " + "35h35l35 35 35 35h36 35l35 36h36 35l35 35h36 35l35 35h36 70 36l35 35h70 36 35 " + "71l35 36h35 71 35 71 35 71 35 35 71 35 71 35 71 35l36-36h70 36 35 70l36-35h35 71 " + "35l35-35h36 35l35-35h36 35l35-36h36 35l35-35 35-35h36l35-35 35-36v-35h36v-35z"); + + assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygoncolor", 1); + assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygoncolor[1]", "color", "#ffff00"); + assertXPath(pDocument, aXPathPrefix + "group/mask/polypolygoncolor[1]/polypolygon[1]", "path", + "m353 353h2472v1057h-2472z"); + + assertXPath(pDocument, aXPathPrefix + "group/mask/polygonstroke", 1); + assertXPathContent(pDocument, aXPathPrefix + "group/mask/polygonstroke[1]/polygon", + "353,353 2825,353 2825,1410 353,1410"); +} + +void Test::TestEllipseXformIntersectClipRect() +{ + // EMF import test with records: EXTCREATEPEN, CREATEBRUSHINDIRECT, MODIFYWORLDTRANSFORM, INTERSECTCLIPRECT, ELLIPSE + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestEllipseXformIntersectClipRect.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h3000v2000h-3000z"); + assertXPath(pDocument, aXPathPrefix + "mask/group/mask/polypolygon", "path", + "m370 152 1128-409 592 1623-1128 410z"); + assertXPath( + pDocument, aXPathPrefix + "mask/group/mask/polypolygoncolor/polypolygon", "path", + "m3613 287-12-33-12-33-12-33-12-33-33 12-12-34-13-33-45-21-12-33-33 " + "12-12-33-45-21-46-21-12-33-33 12-12-33-45-21-33 12-46-21-45-21-33 12-45-21-34 12-45-21-33 " + "12-45-21-34 12-45-21-33 12-45-21-33 12-34 12-45-21-33 12-33 12-45-21-34 12-66 24-45-21-33 " + "12-34 12-66 24-33 12-45-21-34 12-66 24-33 12-33 12-34 12-66 24-33 12-33 12-67 25-33 12-33 " + "12-33 12-67 24-33 12-21 45-33 12-66 24-34 12-33 12-21 46-66 24-33 12-22 45-33 12-33 12-21 " + "45-33 12-33 12-21 46-34 12-21 45-33 12-21 45-33 12-21 45-34 12-21 45-33 13-21 45-21 45-33 " + "12-21 45 12 33-33 12 12 33-21 46-22 45 13 33-34 12 12 33-21 45 12 33 12 34-33 12 12 33 12 " + "33 13 33 12 33 12 33 12 33 12 33 12 34 33-12 12 33 12 33 46 21 12 33 33-12 12 33 45 21 45 " + "21 12 33 34-12 12 33 45 21 33-12 45 21 46 22 33-13 45 22 33-13 46 22 33-13 45 22 33-13 45 " + "22 34-13 45 22 33-12 33-13 46 22 33-13 33-12 45 21 33-12 67-24 45 21 33-12 33-12 67-24 " + "33-12 45 21 33-12 67-24 33-12 33-12 33-12 67-24 33-12 33-12 66-25 34-12 33-12 33-12 66-24 " + "33-12 22-45 33-12 66-24 33-12 33-12 22-45 66-25 33-12 21-45 33-12 34-12 21-45 33-12 33-12 " + "21-45 33-12 21-46 34-12 21-45 33-12 21-45 33-12 21-45 33-12 22-46 21-45 33-12 21-45-12-33 " + "33-12-12-33 21-46 21-45-12-33 33-12-12-33 21-45-12-33-12-33 33-12-12-34-12-33-12-33z"); + assertXPathContent( + pDocument, aXPathPrefix + "mask/group/mask/polygonstroke/polygon", + "3613,287 3601,254 3601,254 3589,221 3577,188 3565,155 3532,167 3520,133 3507,100 3507,100 " + "3462,79 3450,46 3417,58 3405,25 3360,4 3360,4 3314,-17 3302,-50 3269,-38 3257,-71 " + "3212,-92 3179,-80 3133,-101 3133,-101 3088,-122 3055,-110 3010,-131 2976,-119 2931,-140 " + "2898,-128 2853,-149 2819,-137 2774,-158 2741,-146 2696,-167 2663,-155 2629,-143 2584,-164 " + "2551,-152 2518,-140 2473,-161 2439,-149 2373,-125 2328,-146 2295,-134 2261,-122 2195,-98 " + "2162,-86 2117,-107 2083,-95 2017,-71 1984,-59 1951,-47 1917,-35 1851,-11 1818,1 1818,1 " + "1785,13 1718,38 1685,50 1652,62 1619,74 1552,98 1519,110 1498,155 1465,167 1399,191 " + "1365,203 1332,215 1311,261 1245,285 1212,297 1190,342 1157,354 1124,366 1103,411 1070,423 " + "1037,435 1016,481 982,493 961,538 928,550 907,595 874,607 853,652 819,664 798,709 765,722 " + "744,767 744,767 723,812 690,824 669,869 681,902 648,914 660,947 639,993 639,993 617,1038 " + "630,1071 596,1083 608,1116 587,1161 587,1161 599,1194 611,1228 578,1240 590,1273 602,1306 " + "615,1339 615,1339 627,1372 627,1372 639,1405 639,1405 651,1438 663,1471 675,1505 708,1493 " + "720,1526 732,1559 732,1559 778,1580 790,1613 823,1601 835,1634 880,1655 880,1655 925,1676 " + "937,1709 971,1697 983,1730 1028,1751 1061,1739 1106,1760 1106,1760 1152,1782 1185,1769 " + "1230,1791 1263,1778 1309,1800 1342,1787 1387,1809 1420,1796 1465,1818 1499,1805 1544,1827 " + "1577,1815 1610,1802 1656,1824 1689,1811 1722,1799 1767,1820 1800,1808 1867,1784 1912,1805 " + "1945,1793 1978,1781 2045,1757 2078,1745 2123,1766 2156,1754 2223,1730 2256,1718 2289,1706 " + "2322,1694 2389,1670 2422,1658 2422,1658 2455,1646 2521,1621 2555,1609 2588,1597 2621,1585 " + "2687,1561 2720,1549 2742,1504 2775,1492 2841,1468 2874,1456 2907,1444 2929,1399 2995,1374 " + "3028,1362 3049,1317 3082,1305 3116,1293 3137,1248 3170,1236 3203,1224 3224,1179 3257,1167 " + "3278,1121 3312,1109 3333,1064 3366,1052 3387,1007 3420,995 3441,950 3474,938 3496,892 " + "3496,892 3517,847 3550,835 3571,790 3559,757 3592,745 3580,712 3601,666 3601,666 3622,621 " + "3610,588 3643,576 3631,543 3652,498 3652,498 3640,465 3628,432 3661,420 3649,386 3637,353 " + "3625,320 3625,320"); +} + +void Test::TestSetArcDirection() +{ + // EMF import test with records: SETARCDIRECTION, ARC, PIE + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestSetArcDirection.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#ffffff"); + assertXPath( + pDocument, aXPathPrefix + "polypolygoncolor/polypolygon", "path", + "m1640 1570-1000-950 50-50 50-50 50-50 50-40 60-40 50-40 60-30 60-40 60-20 60-30 70-20 " + "60-20 70-10 60-20h70l70-10h60 70l70 10 60 10 70 10 70 20 60 20 60 20 70 30 60 30 60 30 50 " + "40 60 40 50 40 50 40 50 50 50 50 50 50 40 60 40 60 40 60 30 60 30 60 30 60 20 70 30 70 10 " + "60 20 70 10 70 10 70 10 70v80 70l-10 70v70l-10 70-20 70-20 70z"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline", 2); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[1]", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[2]", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline", 2); + assertXPathContent( + pDocument, aXPathPrefix + "polygonhairline[1]/polygon", + "1070,1570 1110,1560 1160,1540 1200,1530 1250,1520 1300,1510 1350,1510 1400,1500 1440,1500 " + "1490,1500 1540,1500 1590,1500 1640,1510 1690,1510 1740,1520 1780,1530 1830,1540 1880,1560 " + "1920,1570 1960,1590 2010,1610 2050,1630 2090,1650 2130,1670 2160,1700 2200,1720 2230,1750 " + "2260,1780 2290,1810 2320,1840 2350,1870 2370,1900 2390,1930 2410,1970 2430,2000 2440,2030 " + "2450,2070 2460,2110 2470,2140 2480,2180 2480,2220 2480,2250 2480,2290 2470,2320 2470,2360 " + "2460,2400 2450,2430 2430,2470 2420,2500 2400,2540 2380,2570 2350,2600 2330,2630 2300,2660 " + "2270,2690 2240,2720 2210,2750 2170,2770 2140,2800 2100,2820 2060,2840 2020,2860 1980,2880 " + "1940,2900 1890,2920 1850,2930 1800,2940 1750,2950 1700,2960 1660,2970 1610,2970 1560,2980 " + "1510,2980 1460,2980 1410,2980 1360,2970 1320,2970 1270,2960 1220,2950 1170,2940 1130,2930 " + "1080,2910 1040,2900 1000,2880 950,2860 910,2840 870,2820 840,2800 800,2770 770,2740 " + "730,2720 700,2690 670,2660 650,2630 620,2600 600,2560 580,2530 560,2500 550,2460 530,2430 " + "520,2390 510,2360 510,2320 500,2280 500,2250 500,2210 500,2170 510,2140 520,2100 530,2070 " + "540,2030 560,1990 570,1960 590,1930 610,1890 640,1860 660,1830 690,1800 720,1770 750,1740 " + "790,1720 820,1690 860,1670 900,1650 940,1630 980,1610 1020,1590"); + assertXPathContent( + pDocument, aXPathPrefix + "polygonhairline[2]/polygon", + "1640,1570 640,620 690,570 740,520 790,470 840,430 900,390 950,350 1010,320 1070,280 " + "1130,260 1190,230 1260,210 1320,190 1390,180 1450,160 1520,160 1590,150 1650,150 1720,150 " + "1790,160 1850,170 1920,180 1990,200 2050,220 2110,240 2180,270 2240,300 2300,330 2350,370 " + "2410,410 2460,450 2510,490 2560,540 2610,590 2660,640 2700,700 2740,760 2780,820 2810,880 " + "2840,940 2870,1000 2890,1070 2920,1140 2930,1200 2950,1270 2960,1340 2970,1410 2980,1480 " + "2980,1560 2980,1630 2970,1700 2970,1770 2960,1840 2940,1910 2920,1980"); +} + +void Test::TestDrawPolyLine16WithClip() +{ + // EMF image with records: + // CREATEBRUSHINDIRECT, FILLRGN, BEGINPATH, POLYGON16, SELECTCLIPPATH, MODIFYWORLDTRANSFORM, SELECTOBJECT + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestDrawPolyLine16WithClip.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h3943v3939h-3943z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 1); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m1323 0h1323v1322h1323v1322h-1323v1322h-1323v-1322h-1323v-1322h1323z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#b4ffff"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 1); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[1]/polygon", + "1323,0 2646,0 2646,1322 3969,1322 3969,2644 2646,2644 2646,3966 1323,3966 " + "1323,2644 0,2644 0,1322 1323,1322"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline[1]", "color", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/group[1]/mask/polypolygon", "path", + "m2646 0v1322h1323v1322h-1323v1322h-1323v-1322h-1323v-1322h1323v-1322"); + assertXPathContent(pDocument, aXPathPrefix + "mask/group[1]/mask/polygonstroke/polygon", + "0,793 3969,4230"); +} + +void Test::TestFillRegion() +{ + // EMF import with records: CREATEBRUSHINDIRECT, FILLRGN. The SETICMMODE is also used. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestFillRegion.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h3943v3939h-3943z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 1); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m1323 0h1323v1322h1323v1322h-1323v1322h-1323v-1322h-1323v-1322h1323z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#ff0000"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 1); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[1]/polygon", + "1323,0 2646,0 2646,1322 3969,1322 3969,2644 2646,2644 2646,3966 1323,3966 " + "1323,2644 0,2644 0,1322 1323,1322"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline[1]", "color", "#000000"); +} + +void Test::TestPolylinetoCloseStroke() +{ + // EMF import with records: BEGINPATH, ARC, ENDPATH, STROKEPATH, EXTCREATEPEN. + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestPolylinetoCloseStroke.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polygonhairline", 2); + assertXPathContent( + pDocument, aXPathPrefix + "polygonhairline[1]/polygon", + "1080,150 1010,170 940,190 870,210 810,230 750,260 690,280 630,310 570,340 520,380 470,410 " + "420,450 370,490 330,530 290,570 260,610 230,660 200,700 170,750 150,790 130,840 120,890 " + "110,930 100,980 100,1030 100,1080 110,1130 120,1180 130,1220 140,1270 160,1320 190,1360 " + "210,1410 250,1450 280,1490 320,1540 360,1580 400,1620 450,1650 500,1690"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[1]", "color", "#000000"); + assertXPathContent( + pDocument, aXPathPrefix + "polygonhairline[2]/polygon", + "1760,1120 1710,1130 1670,1140 1620,1150 1580,1160 1540,1170 1500,1180 1460,1200 1420,1210 " + "1380,1230 1350,1240 1320,1260 1290,1280 1260,1300 1230,1310 1210,1330 1190,1360 1170,1380 " + "1150,1400 1140,1420 1120,1440 1110,1460 1110,1490 1100,1510 1100,1530 1100,1550 1100,1580 " + "1110,1600 1120,1620 1130,1650 1140,1670 1160,1690 1170,1710 1190,1730"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[2]", "color", "#000000"); +} + +void Test::TestEmfPlusBrushPathGradientWithBlendColors() +{ + // tdf#131506 EMF+ records: FillRects, Brush with PathGradient and BlendColor, FillRects + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "radius", "0.7"); + assertXPath(pDocument, aXPathPrefix + "svgradialgradient/focalx", 0); + assertXPath(pDocument, aXPathPrefix + "svgradialgradient/focaly", 0); + assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "startx", "0"); + assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "starty", "0"); + assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "spreadmethod", "pad"); +} + +void Test::TestEmfPlusGetDC() +{ + // tdf#147818 EMF+ records: GetDC, DrawPath, FillRects + Primitive2DSequence aSequence = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusGetDC.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "textsimpleportion", "text", "sd CCCCCCCCCCCCCCC"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "group", 5); + assertXPath( + pDocument, aXPathPrefix + "group[4]/textsimpleportion", "text", + "Getttttttttttttttttttttttttttttt, uuuu: \"eeeeeeeeeeeeeeeeeeeeeee-7acd04a3953b\")"); + assertXPath(pDocument, aXPathPrefix + "group[5]/textsimpleportion", "text", + "TTTTTTTTTTTTTTTTTTTTTTTTTTTTT, trackId: 55)"); + assertXPath(pDocument, aXPathPrefix + "group[5]/textsimpleportion", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 6); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m105.78125 " + "776.111111111111h3878.64583333333l458.385416666667-493.888888888889v-176." + "388888888889h-4337.03125v670.277777777778"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#ffffff"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]/polypolygon", "path", + "m2291.92708333333 4550.83333333333h317.34375v-317.5h-317.34375z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]", "color", "#fcf2e3"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[6]/polypolygon", "path", + "m19428.4895833333 6632.22222222222h317.34375v-2398.88888888889h-317.34375z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[6]", "color", "#fcf2e3"); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 4); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow", 13); +} + +void Test::TestEmfPlusSave() +{ + // tdf#147818 EMF+ records: Save, Restore, SetWorldTransform, FillRects, SetClipRegion + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusSave.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h33544v21311h-33544z"); + + assertXPath(pDocument, aXPathPrefix + "mask/group/mask/polypolygoncolor/polypolygon", "path", + "m327.458333333333 638.222222222222h437007.1875v295555.555555556h-437007.1875z"); + assertXPath(pDocument, aXPathPrefix + "mask/group/mask/polypolygoncolor", "color", "#ff0cad"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor/polypolygon", "path", + "m10853.4145539602 7321.41354709201h41952690v29630720h-41952690z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", "color", "#00ffad"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstrokearrow/line", "color", "#000000"); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstrokearrow/polygon", + "10853.4145539602,7321.41354709201 10853.4145539602,4907.54325697157 " + "12832.6557236512,4907.54325697157"); +} + +void Test::TestEmfPlusDrawPathWithCustomCap() +{ + // tdf#142261 EMF+ records: DrawPath, SetWorldTransform, Object (Brush, Pen, Path) + // Check if CustomEndCap is displayed correctly + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPathContent(pDocument, aXPathPrefix + "polygonstrokearrow/polygon", + "1423.297394625,1268.98481214025 705.717657763014,1304.88786195939"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "color", "#cc0000"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "width", "96"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "linecap", "BUTT"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/stroke", 0); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/linestartattribute", 0); + + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/lineendattribute", "centered", "0"); + assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/lineendattribute/polypolygon", "path", + "m-1.5 3 1.5-3 1.5 3z"); +} + +void Test::TestEmfPlusDrawPathWithMiterLimit() +{ + // tdf#142261 EMF+ records: DrawPath, TranslateWorldTransform, Object (Brush, Pen, Path) + // Check if Miter is correctly set for Lines + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithMiterLimit.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 3); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "color", "#c800c8"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "width", "1057"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "miterangle", "5"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/stroke", 0); + + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence", 3); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]", "transparence", "85"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]/polypolygonstroke/line", "color", + "#6400c8"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]/polypolygonstroke/line", "width", + "1057"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]/polypolygonstroke/line", + "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[1]/polypolygonstroke/line", + "miterangle", "19"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[2]", "transparence", "69"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[2]/polypolygonstroke/line", + "miterangle", "19"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[3]", "transparence", "53"); + assertXPath(pDocument, aXPathPrefix + "unifiedtransparence[3]/polypolygonstroke/line", + "miterangle", "19"); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "color", "#0000ff"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "width", "1057"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "miterangle", "60"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/stroke", 0); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/line", "color", "#0000ff"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/line", "width", "1057"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/line", "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/line", "miterangle", "60"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/stroke", 0); +} + +void Test::TestEmfPlusFillClosedCurve() +{ + // tdf#143876 EMF+ records: SetWorldTransform, FillClosedCurve, DrawClosedCurve + Primitive2DSequence aSequence + = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 2); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#808080"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m18202.841744243 13758.4401790456 1269.96570308672 " + "3175.02465670283-2539.93140617345-2116.68310446856h2539.93140617345l-2539." + "93140617345 2116.68310446856z"); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 2); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "width", "10"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "miterangle", "3"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "linecap", "BUTT"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/polypolygon", "path", + "m18202.841744243 13758.4401790456 1269.96570308672 " + "3175.02465670283-2539.93140617345-2116.68310446856h2539.93140617345l-2539." + "93140617345 2116.68310446856z"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]", "color", "#808080"); + //TODO Check path with implemented Winding + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]/polypolygon", "path", + "m22012.7388535032 13758.4401790456 1269.96570308672 " + "3175.02465670283-2539.93140617344-2116.68310446856h2539.93140617344l-2539." + "93140617344 2116.68310446856z"); + + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "width", "10"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "linejoin", "Miter"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "miterangle", "3"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "linecap", "BUTT"); + assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/polypolygon", "path", + "m22012.7388535032 13758.4401790456 1269.96570308672 " + "3175.02465670283-2539.93140617344-2116.68310446856h2539.93140617344l-2539." + "93140617344 2116.68310446856z"); +} + +void Test::TestExtTextOutOpaqueAndClipTransform() +{ + // tdf#142495 EMF records: SETBKCOLOR, SELECTOBJECT, EXTTEXTOUTW, MODIFYWORLDTRANSFORM, CREATEFONTINDIRECT. + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/emf/data/TestExtTextOutOpaqueAndClipTransform.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "textsimpleportion", 2); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "text", "No_rect- DLP-"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[1]", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[2]", "text", "OpaqueTranspa"); + assertXPath(pDocument, aXPathPrefix + "textsimpleportion[2]", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 3); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m966 490-477-275-84 147 476 275z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#ff0000"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]/polypolygon", "path", + "m251 713 623 361-148 257-623-361z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]", "color", "#0080ff"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]/polypolygon", "path", + "m972 1326-476-275-148 257 476 276z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]", "color", "#800080"); + + assertXPath(pDocument, aXPathPrefix + "group", 3); + assertXPath(pDocument, aXPathPrefix + "group[1]/polypolygoncolor", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "group[1]/textsimpleportion", "text", "Opaque - DLP-"); + assertXPath(pDocument, aXPathPrefix + "group[1]/textsimpleportion", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "group[2]/mask/group/polypolygoncolor", "color", + "#00ff00"); + assertXPath(pDocument, aXPathPrefix + "group[2]/mask/polypolygon", "path", + "m320 508 586 340-169 293-586-339z"); + assertXPath(pDocument, aXPathPrefix + "group[2]/mask/group/textsimpleportion", "text", + "Clip - DLP-"); + assertXPath(pDocument, aXPathPrefix + "group[2]/mask/group/textsimpleportion", "fontcolor", + "#000000"); + + assertXPath(pDocument, aXPathPrefix + "group[3]/mask/group/polypolygoncolor", "color", + "#0080ff"); + assertXPath(pDocument, aXPathPrefix + "group[3]/mask/polypolygon", "path", + "m251 713 623 361-148 257-623-361z"); + assertXPath(pDocument, aXPathPrefix + "group[3]/mask/group/textsimpleportion", "text", + "Opaque ClipP-"); + assertXPath(pDocument, aXPathPrefix + "group[3]/mask/group/textsimpleportion", "fontcolor", + "#000000"); +} + +void Test::TestBitBltStretchBltWMF() +{ + // tdf#55058 tdf#142722 WMF records: BITBLT, STRETCHBLT. + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", 2); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy11", "508"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy12", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy13", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy21", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy22", "508"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "xy23", "406"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "height", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]", "width", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]/data", 10); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]/data[1]", "row", + "000000,000000,000000,000000,000000,000000,000000,000000,000000,000000"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]/data[4]", "row", + "000000,ffffff,000000,ffffff,000000,ffffff,000000,ffffff,000000,ffffff"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[1]/data[5]", "row", + "ffffff,000000,ffffff,ffffff,000000,000000,000000,ffffff,ffffff,000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy11", "1524"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy12", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy13", "813"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy21", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy22", "1016"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "xy23", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "height", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]", "width", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]/data", 10); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]/data[1]", "row", + "000000,00001c,000038,000055,000071,00008d,0000aa,0000c6,0000e2,0000ff"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap[2]/data[5]", "row", + "720000,721c1c,723838,725555,727171,72728d,55728d,39728d,1d728d,00728d"); +} + +void Test::TestExtTextOutOpaqueAndClipWMF() +{ + // tdf#53004 WMF records: SETBKCOLOR, SELECTOBJECT, EXTTEXTOUT, CREATEBRUSHINDIRECT. + Primitive2DSequence aSequence + = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestExtTextOutOpaqueAndClip.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + +#ifdef MACOSX + // On some operating systems (Linux on LO Jenkins CI), the `/mask/` string is not added to XPath + // As a result tests are failing. On my Ubuntu 20.04 the `/mask/` string was added + // I would leave this test case for macOS to make sure there is no regression at least on one platform. + + // These values come from the fix for tdf#88163 + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 5); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m7257 1836h320v3628h-320z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#ff0000"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]/polypolygon", "path", + "m7257 5976h320v321h-320z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]", "color", "#00ff00"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[3]/polypolygon", "path", + "m10203 5976h320v321h-320z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[3]", "color", "#8080ff"); + + assertXPath(pDocument, aXPathPrefix + "mask/group", 5); + assertXPath(pDocument, aXPathPrefix + "mask/group[1]/polypolygoncolor", "color", "#00ff00"); + assertXPath(pDocument, aXPathPrefix + "mask/group[1]/textsimpleportion", "text", "ABCD"); + assertXPath(pDocument, aXPathPrefix + "mask/group[1]/textsimpleportion", "fontcolor", + "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/group[2]/polypolygoncolor", "color", "#8080ff"); + assertXPath(pDocument, aXPathPrefix + "mask/group[2]/textsimpleportion", "text", "MMMM"); + assertXPath(pDocument, aXPathPrefix + "mask/group[2]/textsimpleportion", "fontcolor", + "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/group[3]/mask/group/polypolygoncolor", "color", + "#ff8000"); + assertXPath(pDocument, aXPathPrefix + "mask/group[3]/mask/group/polypolygoncolor/polypolygon", + "path", "m1067 1067h1270v473h-1270z"); + assertXPath(pDocument, aXPathPrefix + "mask/group[3]/mask/group/textsimpleportion", "text", + "OOOO"); + assertXPath(pDocument, aXPathPrefix + "mask/group[3]/mask/group/textsimpleportion", "fontcolor", + "#000000"); +#endif +} + +void Test::TestPaletteWMF() +{ + // WMF import with records: CREATEPALETTE, SELECTOBJECT, CREATEPENINDIRECT, CREATEBRUSHINDIRECT, ELLIPSE. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestPalette.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor", 2); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]/polypolygon", "path", + "m0 0h3015v3015h-3015z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[1]", "color", "#ffff00"); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]/polypolygon", "path", + "m2222 2222h2698v2698h-2698z"); + assertXPath(pDocument, aXPathPrefix + "mask/polypolygoncolor[2]", "color", "#0080ff"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke", 2); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[1]/polygon", + "0,0 3015,0 3015,3015 0,3015"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", "width", "132"); + + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[2]/polygon", + "2222,2222 4920,2222 4920,4920 2222,4920"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", "width", "132"); +} + +void Test::TestRestoreDCWMF() +{ + // WMF records: RESTOREDC, SAVEDC, CREATEBRUSHINDIRECT, RECTANGLE. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestRestoreDC.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 3); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#0000ff"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m238 2884h1640v1110h-1640z"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[1]", "color", "#000000"); + assertXPathContent(pDocument, aXPathPrefix + "polygonhairline[1]/polygon", + "238,2884 1878,2884 1878,3994 238,3994"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]/polypolygon", "path", + "m238 238h1640v1110h-1640z"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[2]", "color", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[3]/polypolygon", "path", + "m238 5530h1640v1110h-1640z"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline[3]", "color", "#000000"); +} + +void Test::TestRoundrectWMF() +{ + // WMF records: ROUNDRECT, SETBKCOLOR, CREATEBRUSHINDIRECT + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestRoundRect.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#ffffff"); + + assertXPathContent( + pDocument, aXPathPrefix + "polygonstroke/polygon", + "2858,659 2858,651 2858,643 2858,635 2858,619 2858,611 2858,603 2850,595 2850,587 2850,580 " + "2850,564 2850,556 2842,548 2842,540 2842,532 2834,524 2834,516 2834,508 2826,500 2826,492 " + "2818,484 2818,476 2810,468 2810,460 2802,452 2802,445 2794,437 2794,429 2786,421 2786,421 " + "2778,413 2770,405 2770,397 2762,389 2754,389 2754,381 2746,373 2738,373 2731,365 2731,365 " + "2723,357 2715,349 2707,349 2707,341 2699,341 2691,341 2683,333 2675,333 2675,333 2667,325 " + "2659,325 2651,325 2643,325 2635,318 2627,318 2627,318 2619,318 2611,318 2604,318 572,318 " + "564,318 556,318 548,318 548,318 540,318 532,325 524,325 516,325 508,325 500,333 500,333 " + "492,333 484,341 476,341 468,341 468,349 460,349 452,357 445,365 445,365 437,373 429,373 " + "421,381 421,389 413,389 405,397 405,405 397,413 389,421 389,421 381,429 381,437 373,445 " + "373,452 365,460 365,468 357,476 357,484 349,492 349,500 341,508 341,516 341,524 333,532 " + "333,540 333,548 325,556 325,564 325,580 325,587 325,595 318,603 318,611 318,619 318,635 " + "318,643 318,651 318,659 318,1667 318,1675 318,1683 318,1691 318,1707 318,1715 318,1723 " + "325,1731 325,1739 325,1746 325,1762 325,1770 333,1778 333,1786 333,1794 341,1802 341,1810 " + "341,1818 349,1826 349,1834 357,1842 357,1850 365,1858 365,1866 373,1874 373,1881 381,1889 " + "381,1897 389,1905 389,1905 397,1913 405,1921 405,1929 413,1937 421,1937 421,1945 429,1953 " + "437,1953 445,1961 445,1961 452,1969 460,1977 468,1977 468,1985 476,1985 484,1985 492,1993 " + "500,1993 500,1993 508,2001 516,2001 524,2001 532,2001 540,2008 548,2008 548,2008 556,2008 " + "564,2008 572,2008 2604,2008 2611,2008 2619,2008 2627,2008 2627,2008 2635,2008 2643,2001 " + "2651,2001 2659,2001 2667,2001 2675,1993 2675,1993 2683,1993 2691,1985 2699,1985 2707,1985 " + "2707,1977 2715,1977 2723,1969 2731,1961 2731,1961 2738,1953 2746,1953 2754,1945 2754,1937 " + "2762,1937 2770,1929 2770,1921 2778,1913 2786,1905 2786,1905 2794,1897 2794,1889 2802,1881 " + "2802,1874 2810,1866 2810,1858 2818,1850 2818,1842 2826,1834 2826,1826 2834,1818 2834,1810 " + "2834,1802 2842,1794 2842,1786 2842,1778 2850,1770 2850,1762 2850,1746 2850,1739 2850,1731 " + "2858,1723 2858,1715 2858,1707 2858,1691 2858,1683 2858,1675 2858,1667"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "width", "143"); +} + +void Test::TestStretchDIBWMF() +{ + // WMF records: STRETCHDIB + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestStretchDIB.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy11", "12065"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy12", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy13", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy21", "0"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy22", "12065"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "xy23", "0"); + + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "height", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap", "width", "10"); + assertXPath(pDocument, aXPathPrefix + "mask/bitmap/data", 10); + assertXPath(pDocument, aXPathPrefix + "/mask/bitmap/data[1]", "row", + "000000,00001c,000038,000055,000071,00008d,0000aa,0000c6,0000e2,0000ff"); + assertXPath(pDocument, aXPathPrefix + "/mask/bitmap/data[5]", "row", + "720000,721c1c,723838,725555,727171,72728d,55728d,39728d,1d728d,00728d"); +} + +void Test::TestMoveToLineToWMF() +{ + // tdf#89331 WMF records: MOTETO, LINETO, CREATEPENINDIRECT. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestLineTo.wmf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke/polygon", + "5856,3586 7167,621 8625,3586"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "color", "#800000"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "width", "310"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "linejoin", "Bevel"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "linecap", "ROUND"); +} + +void Test::TestPolyLineWidth() +{ + // EMF import with records: CREATEPEN, ROUNDRECT. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestPolyLineWidth.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor/polypolygon", "path", + "m530 529 1236-176-707 352z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#ffff00"); + + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke/polygon", + "530,529 530,529 1766,353 1059,705"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "color", "#000000"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke/line", "width", "71"); +} + +void Test::TestRestoreDC() +{ + // EMF records: SAVEDC, RESTOREDC, POLYGON16, MODIFYWORLDTRANSFORM + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestRestoreDC.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor/polypolygon", "path", + "m1148 4354v1481h4943v-1481z"); + assertXPath(pDocument, aXPathPrefix + "polygonhairline", "color", "#000000"); + assertXPathContent(pDocument, aXPathPrefix + "polygonhairline/polygon", + "1148,4354 1148,5835 6091,5835 6091,4354"); +} + +void Test::TestRoundRect() +{ + // EMF import with records: CREATEPEN, ROUNDRECT. + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestRoundRect.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 2); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path", + "m100 100h4000v2000h-4000z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#ffffff"); + + assertXPath(pDocument, aXPathPrefix + "polygonstroke", 2); + assertXPathContent(pDocument, aXPathPrefix + "polygonstroke[1]/polygon", + "100,100 4100,100 4100,2100 100,2100"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[1]/line", "color", "#ff0000"); + + assertXPath( + pDocument, aXPathPrefix + "polypolygoncolor[2]/polypolygon", "path", + "m4090 " + "2700v-30-20l-10-30v-20l-10-30-10-20-10-20-20-30-10-20-20-20-20-20-20-30-20-20-20-20-20-10-" + "30-20-20-20-30-20-30-10-30-10-30-20-30-10-30-10-40-10-30-10h-30l-40-10h-30l-40-10h-30-40-" + "2590-40-30l-40 10h-30l-40 10h-30l-30 10-40 10-30 10-30 10-30 20-30 10-30 10-30 20-20 " + "20-30 20-20 10-20 20-20 20-20 30-20 20-20 20-10 20-20 30-10 20-10 20-10 30v20l-10 30v20 " + "30 990 30 20l10 30v20l10 30 10 20 10 20 20 30 10 20 20 20 20 20 20 30 20 20 20 20 20 10 " + "30 20 20 20 30 20 30 10 30 10 30 20 30 10 30 10 40 10 30 10h30l40 10h30l40 10h30 40 2590 " + "40 30l40-10h30l40-10h30l30-10 40-10 30-10 30-10 30-20 30-10 30-10 30-20 20-20 30-20 20-10 " + "20-20 20-20 20-30 20-20 20-20 10-20 20-30 10-20 10-20 10-30v-20l10-30v-20-30z"); + assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]", "color", "#ffffff"); + + assertXPathContent( + pDocument, aXPathPrefix + "polygonstroke[2]/polygon", + "4090,2700 4090,2670 4090,2650 4080,2620 4080,2600 4070,2570 4060,2550 4050,2530 4030,2500 " + "4020,2480 4000,2460 3980,2440 3960,2410 3940,2390 3920,2370 3900,2360 3870,2340 3850,2320 " + "3820,2300 3790,2290 3760,2280 3730,2260 3700,2250 3670,2240 3630,2230 3600,2220 3570,2220 " + "3530,2210 3500,2210 3460,2200 3430,2200 3390,2200 800,2200 760,2200 730,2200 690,2210 " + "660,2210 620,2220 590,2220 560,2230 520,2240 490,2250 460,2260 430,2280 400,2290 370,2300 " + "340,2320 320,2340 290,2360 270,2370 250,2390 230,2410 210,2440 190,2460 170,2480 160,2500 " + "140,2530 130,2550 120,2570 110,2600 110,2620 100,2650 100,2670 100,2700 100,3690 100,3720 " + "100,3740 110,3770 110,3790 120,3820 130,3840 140,3860 160,3890 170,3910 190,3930 210,3950 " + "230,3980 250,4000 270,4020 290,4030 320,4050 340,4070 370,4090 400,4100 430,4110 460,4130 " + "490,4140 520,4150 560,4160 590,4170 620,4170 660,4180 690,4180 730,4190 760,4190 800,4190 " + "3390,4190 3430,4190 3460,4190 3500,4180 3530,4180 3570,4170 3600,4170 3630,4160 3670,4150 " + "3700,4140 3730,4130 3760,4110 3790,4100 3820,4090 3850,4070 3870,4050 3900,4030 3920,4020 " + "3940,4000 3960,3980 3980,3950 4000,3930 4020,3910 4030,3890 4050,3860 4060,3840 4070,3820 " + "4080,3790 4080,3770 4090,3740 4090,3720 4090,3690"); + assertXPath(pDocument, aXPathPrefix + "polygonstroke[2]/line", "color", "#ff0000"); +} + +void Test::TestCreatePen() +{ + // Check import of EMF image with records: RESTOREDC, SAVEDC, MOVETOEX, LINETO, POLYLINE16, EXTTEXTOUTW with DxBuffer + // The CREATEPEN record is used with PS_COSMETIC line style, which sometimes will be displayed as solid hairline + Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/emf/data/TestCreatePen.emf"); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength())); + drawinglayer::Primitive2dXmlDump dumper; + xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence)); + CPPUNIT_ASSERT(pDocument); + + assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h31250v18192h-31250z"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke", 748); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[1]/polygon", + "27875,16523 27875,1453"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", "width", "6"); + + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[2]/polygon", + "27975,16453 27875,16453"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", "width", "6"); + + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonstroke[3]/polygon", + "27925,16078 27875,16078"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[3]/line", "color", "#ff0000"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[3]/line", "width", "6"); + + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 10); + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline[5]", "color", "#008000"); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[5]/polygon", + "25850,2179 25844,1958"); + assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline[10]", "color", "#000080"); + assertXPathContent(pDocument, aXPathPrefix + "mask/polygonhairline[10]/polygon", + "2025,1642 2025,1501"); + + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion", 69); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "width", "374"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "x", "28124"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "y", "16581"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "text", "0.0"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[10]", "width", "266"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[10]", "x", "28000"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[10]", "y", "428"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[10]", "text", "-6"); + assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[10]", "fontcolor", "#000000"); + + assertXPath(pDocument, aXPathPrefix + "mask/pointarray", 8); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]", "color", "#008000"); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]/point", "x", "25844"); + assertXPath(pDocument, aXPathPrefix + "mask/pointarray[1]/point", "y", "8918"); +} + +void Test::TestPdfInEmf() +{ + if (!vcl::pdf::PDFiumLibrary::get()) + { + return; + } + + // Load a PPTX file, which has a shape, with a bitmap fill, which is an EMF, containing a PDF. + OUString aURL = m_directories.getURLFromSrc(u"emfio/qa/cppunit/emf/data/pdf-in-emf.pptx"); + getComponent() = loadFromDesktop(aURL); + + // Get the EMF. + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); + uno::Reference<graphic::XGraphic> xGraphic; + xShape->getPropertyValue("FillBitmap") >>= xGraphic; + Graphic aGraphic(xGraphic); + + // Check the size hint of the EMF, which influences the bitmap generated from the PDF. + const std::shared_ptr<VectorGraphicData>& pVectorGraphicData = aGraphic.getVectorGraphicData(); + + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 14321 + // - Actual : 0 + // i.e. there was no size hint, the shape with 14cm height had a bitmap-from-PDF fill, the PDF + // height was only 5cm, so it looked blurry. + // Tolerance was added later based on results on different systems. + CPPUNIT_ASSERT_LESSEQUAL(1.0, abs(14321.0 - pVectorGraphicData->getSizeHint().getY())); + + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 0 + // - Actual : 255 + // i.e. the pixel in the center was entirely opaque, while it should be transparent. + BitmapEx aBitmapEx = aGraphic.GetBitmapEx(); + Size size = aBitmapEx.GetSizePixel(); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>(0), + aBitmapEx.GetAlpha(size.Width() / 2, size.Height() / 2)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/qa/cppunit/emf/data/TestArcInsideWronglyDefinedRectangle.emf b/emfio/qa/cppunit/emf/data/TestArcInsideWronglyDefinedRectangle.emf Binary files differnew file mode 100644 index 000000000..3a785fba6 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestArcInsideWronglyDefinedRectangle.emf diff --git a/emfio/qa/cppunit/emf/data/TestArcStartPointEqualEndPoint.emf b/emfio/qa/cppunit/emf/data/TestArcStartPointEqualEndPoint.emf Binary files differnew file mode 100644 index 000000000..da8805443 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestArcStartPointEqualEndPoint.emf diff --git a/emfio/qa/cppunit/emf/data/TestChordWithModifyWorldTransform.emf b/emfio/qa/cppunit/emf/data/TestChordWithModifyWorldTransform.emf Binary files differnew file mode 100644 index 000000000..991a1f802 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestChordWithModifyWorldTransform.emf diff --git a/emfio/qa/cppunit/emf/data/TestCreatePen.emf b/emfio/qa/cppunit/emf/data/TestCreatePen.emf Binary files differnew file mode 100644 index 000000000..5a13910ec --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestCreatePen.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawImagePointsTypeBitmap.emf b/emfio/qa/cppunit/emf/data/TestDrawImagePointsTypeBitmap.emf Binary files differnew file mode 100644 index 000000000..291052a16 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawImagePointsTypeBitmap.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawLine.emf b/emfio/qa/cppunit/emf/data/TestDrawLine.emf Binary files differnew file mode 100644 index 000000000..8d8c620b2 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawLine.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawPolyLine16WithClip.emf b/emfio/qa/cppunit/emf/data/TestDrawPolyLine16WithClip.emf Binary files differnew file mode 100644 index 000000000..acb69cc34 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawPolyLine16WithClip.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawString.emf b/emfio/qa/cppunit/emf/data/TestDrawString.emf Binary files differnew file mode 100644 index 000000000..c7976f53b --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawString.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawStringAlign.emf b/emfio/qa/cppunit/emf/data/TestDrawStringAlign.emf Binary files differnew file mode 100644 index 000000000..bc3a33c61 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawStringAlign.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawStringTransparent.emf b/emfio/qa/cppunit/emf/data/TestDrawStringTransparent.emf Binary files differnew file mode 100644 index 000000000..73954c490 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawStringTransparent.emf diff --git a/emfio/qa/cppunit/emf/data/TestDrawStringWithBrush.emf b/emfio/qa/cppunit/emf/data/TestDrawStringWithBrush.emf Binary files differnew file mode 100644 index 000000000..3afb53ed5 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestDrawStringWithBrush.emf diff --git a/emfio/qa/cppunit/emf/data/TestEllipseWithSelectClipPath.emf b/emfio/qa/cppunit/emf/data/TestEllipseWithSelectClipPath.emf Binary files differnew file mode 100644 index 000000000..ed0d52401 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEllipseWithSelectClipPath.emf diff --git a/emfio/qa/cppunit/emf/data/TestEllipseXformIntersectClipRect.emf b/emfio/qa/cppunit/emf/data/TestEllipseXformIntersectClipRect.emf Binary files differnew file mode 100644 index 000000000..bda2ad233 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEllipseXformIntersectClipRect.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf Binary files differnew file mode 100644 index 000000000..caa9876bd --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawBeziers.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawBeziers.emf Binary files differnew file mode 100644 index 000000000..11f074121 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawBeziers.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithCaps.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithCaps.emf Binary files differnew file mode 100644 index 000000000..1b4cedb63 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithCaps.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf Binary files differnew file mode 100644 index 000000000..547cfca3f --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf Binary files differnew file mode 100644 index 000000000..df83e6551 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithMiterLimit.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithMiterLimit.emf Binary files differnew file mode 100644 index 000000000..e8e464863 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithMiterLimit.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf Binary files differnew file mode 100644 index 000000000..ad9140a77 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusGetDC.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusGetDC.emf Binary files differnew file mode 100644 index 000000000..665364abe --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusGetDC.emf diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusSave.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusSave.emf Binary files differnew file mode 100644 index 000000000..24662233b --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestEmfPlusSave.emf diff --git a/emfio/qa/cppunit/emf/data/TestExtTextOutOpaqueAndClipTransform.emf b/emfio/qa/cppunit/emf/data/TestExtTextOutOpaqueAndClipTransform.emf Binary files differnew file mode 100644 index 000000000..0b7be5ab3 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestExtTextOutOpaqueAndClipTransform.emf diff --git a/emfio/qa/cppunit/emf/data/TestFillRegion.emf b/emfio/qa/cppunit/emf/data/TestFillRegion.emf Binary files differnew file mode 100644 index 000000000..da7d4cd65 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestFillRegion.emf diff --git a/emfio/qa/cppunit/emf/data/TestLinearGradient.emf b/emfio/qa/cppunit/emf/data/TestLinearGradient.emf Binary files differnew file mode 100644 index 000000000..fb6e953dc --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestLinearGradient.emf diff --git a/emfio/qa/cppunit/emf/data/TestPolyLineWidth.emf b/emfio/qa/cppunit/emf/data/TestPolyLineWidth.emf Binary files differnew file mode 100644 index 000000000..792694fa8 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestPolyLineWidth.emf diff --git a/emfio/qa/cppunit/emf/data/TestPolylinetoCloseStroke.emf b/emfio/qa/cppunit/emf/data/TestPolylinetoCloseStroke.emf Binary files differnew file mode 100644 index 000000000..e89e92272 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestPolylinetoCloseStroke.emf diff --git a/emfio/qa/cppunit/emf/data/TestRectangleWithModifyWorldTransform.emf b/emfio/qa/cppunit/emf/data/TestRectangleWithModifyWorldTransform.emf Binary files differnew file mode 100644 index 000000000..8f8889284 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestRectangleWithModifyWorldTransform.emf diff --git a/emfio/qa/cppunit/emf/data/TestRestoreDC.emf b/emfio/qa/cppunit/emf/data/TestRestoreDC.emf Binary files differnew file mode 100644 index 000000000..b65b48d6b --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestRestoreDC.emf diff --git a/emfio/qa/cppunit/emf/data/TestRoundRect.emf b/emfio/qa/cppunit/emf/data/TestRoundRect.emf Binary files differnew file mode 100644 index 000000000..6e13cda83 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestRoundRect.emf diff --git a/emfio/qa/cppunit/emf/data/TestSetArcDirection.emf b/emfio/qa/cppunit/emf/data/TestSetArcDirection.emf Binary files differnew file mode 100644 index 000000000..589d12dc8 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TestSetArcDirection.emf diff --git a/emfio/qa/cppunit/emf/data/TextMapMode.emf b/emfio/qa/cppunit/emf/data/TextMapMode.emf Binary files differnew file mode 100644 index 000000000..e0bbf73e2 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/TextMapMode.emf diff --git a/emfio/qa/cppunit/emf/data/fdo79679-2.emf b/emfio/qa/cppunit/emf/data/fdo79679-2.emf Binary files differnew file mode 100644 index 000000000..0962dc122 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/fdo79679-2.emf diff --git a/emfio/qa/cppunit/emf/data/pdf-in-emf.pptx b/emfio/qa/cppunit/emf/data/pdf-in-emf.pptx Binary files differnew file mode 100644 index 000000000..61b2af28c --- /dev/null +++ b/emfio/qa/cppunit/emf/data/pdf-in-emf.pptx diff --git a/emfio/qa/cppunit/emf/data/test_mm_hienglish_ref.emf b/emfio/qa/cppunit/emf/data/test_mm_hienglish_ref.emf Binary files differnew file mode 100644 index 000000000..28f4f1fc7 --- /dev/null +++ b/emfio/qa/cppunit/emf/data/test_mm_hienglish_ref.emf diff --git a/emfio/qa/cppunit/wmf/data/ETO_PDY.emf b/emfio/qa/cppunit/wmf/data/ETO_PDY.emf Binary files differnew file mode 100644 index 000000000..065698eaf --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/ETO_PDY.emf diff --git a/emfio/qa/cppunit/wmf/data/ETO_PDY.wmf b/emfio/qa/cppunit/wmf/data/ETO_PDY.wmf Binary files differnew file mode 100644 index 000000000..bd9774069 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/ETO_PDY.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestBigPPI.wmf b/emfio/qa/cppunit/wmf/data/TestBigPPI.wmf Binary files differnew file mode 100644 index 000000000..e120af279 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestBigPPI.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf b/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf Binary files differnew file mode 100644 index 000000000..030027c75 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestExtTextOutOpaqueAndClip.wmf b/emfio/qa/cppunit/wmf/data/TestExtTextOutOpaqueAndClip.wmf Binary files differnew file mode 100644 index 000000000..fb6fd20f0 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestExtTextOutOpaqueAndClip.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestLineTo.wmf b/emfio/qa/cppunit/wmf/data/TestLineTo.wmf Binary files differnew file mode 100644 index 000000000..14ba0fc6e --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestLineTo.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestPalette.wmf b/emfio/qa/cppunit/wmf/data/TestPalette.wmf Binary files differnew file mode 100644 index 000000000..079a7f64f --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestPalette.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestRestoreDC.wmf b/emfio/qa/cppunit/wmf/data/TestRestoreDC.wmf Binary files differnew file mode 100644 index 000000000..c81244f6b --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestRestoreDC.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestRoundRect.wmf b/emfio/qa/cppunit/wmf/data/TestRoundRect.wmf Binary files differnew file mode 100644 index 000000000..cf4dee52b --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestRoundRect.wmf diff --git a/emfio/qa/cppunit/wmf/data/TestStretchDIB.wmf b/emfio/qa/cppunit/wmf/data/TestStretchDIB.wmf Binary files differnew file mode 100644 index 000000000..c828debbc --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/TestStretchDIB.wmf diff --git a/emfio/qa/cppunit/wmf/data/computer_mail.emf b/emfio/qa/cppunit/wmf/data/computer_mail.emf Binary files differnew file mode 100644 index 000000000..0dbf23f7f --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/computer_mail.emf diff --git a/emfio/qa/cppunit/wmf/data/image1.emf b/emfio/qa/cppunit/wmf/data/image1.emf Binary files differnew file mode 100644 index 000000000..2dcc32a4c --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/image1.emf diff --git a/emfio/qa/cppunit/wmf/data/line_styles.emf b/emfio/qa/cppunit/wmf/data/line_styles.emf Binary files differnew file mode 100644 index 000000000..07b78327d --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/line_styles.emf diff --git a/emfio/qa/cppunit/wmf/data/sine_wave.emf b/emfio/qa/cppunit/wmf/data/sine_wave.emf Binary files differnew file mode 100644 index 000000000..e5a4fa675 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/sine_wave.emf diff --git a/emfio/qa/cppunit/wmf/data/stockobject.emf b/emfio/qa/cppunit/wmf/data/stockobject.emf Binary files differnew file mode 100644 index 000000000..8de7c1ae5 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/stockobject.emf diff --git a/emfio/qa/cppunit/wmf/data/tdf39894.emf b/emfio/qa/cppunit/wmf/data/tdf39894.emf Binary files differnew file mode 100644 index 000000000..c9d5b957b --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/tdf39894.emf diff --git a/emfio/qa/cppunit/wmf/data/tdf39894.wmf b/emfio/qa/cppunit/wmf/data/tdf39894.wmf Binary files differnew file mode 100644 index 000000000..32e41dee9 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/tdf39894.wmf diff --git a/emfio/qa/cppunit/wmf/data/tdf88163-non-placeable.wmf b/emfio/qa/cppunit/wmf/data/tdf88163-non-placeable.wmf Binary files differnew file mode 100644 index 000000000..6d27691f0 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/tdf88163-non-placeable.wmf diff --git a/emfio/qa/cppunit/wmf/data/tdf88163-wrong-font-size.wmf b/emfio/qa/cppunit/wmf/data/tdf88163-wrong-font-size.wmf Binary files differnew file mode 100644 index 000000000..4902ba18d --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/tdf88163-wrong-font-size.wmf diff --git a/emfio/qa/cppunit/wmf/data/tdf93750.emf b/emfio/qa/cppunit/wmf/data/tdf93750.emf Binary files differnew file mode 100644 index 000000000..3c4c41592 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/tdf93750.emf diff --git a/emfio/qa/cppunit/wmf/data/visio_import_source.wmf b/emfio/qa/cppunit/wmf/data/visio_import_source.wmf Binary files differnew file mode 100644 index 000000000..88deac9d2 --- /dev/null +++ b/emfio/qa/cppunit/wmf/data/visio_import_source.wmf diff --git a/emfio/qa/cppunit/wmf/wmfimporttest.cxx b/emfio/qa/cppunit/wmf/wmfimporttest.cxx new file mode 100644 index 000000000..c5b2ddbc3 --- /dev/null +++ b/emfio/qa/cppunit/wmf/wmfimporttest.cxx @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <test/xmltesttools.hxx> +#include <test/bootstrapfixture.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/wmf.hxx> +#include <mtftools.hxx> + +using namespace css; + +class WmfTest : public test::BootstrapFixture, public XmlTestTools +{ + OUString maDataUrl; + + OUString getFullUrl(std::u16string_view sFileName) + { + return m_directories.getURLFromSrc(maDataUrl) + sFileName; + } + +public: + WmfTest() + : BootstrapFixture(true, false) + , maDataUrl("/emfio/qa/cppunit/wmf/data/") + { + } + + void testNonPlaceableWmf(); + void testTdf88163NonPlaceableWmf(); + void testTdf88163PlaceableWmf(); + void testSine(); + void testEmfProblem(); + void testEmfLineStyles(); + void testWorldTransformFontSize(); + void testBigPPI(); + void testTdf93750(); + void testTdf99402(); + void testTdf39894(); + void testETO_PDY(); + void testStockObject(); + + CPPUNIT_TEST_SUITE(WmfTest); + CPPUNIT_TEST(testNonPlaceableWmf); + CPPUNIT_TEST(testTdf88163NonPlaceableWmf); + CPPUNIT_TEST(testTdf88163PlaceableWmf); + CPPUNIT_TEST(testSine); + CPPUNIT_TEST(testEmfProblem); + CPPUNIT_TEST(testEmfLineStyles); + CPPUNIT_TEST(testWorldTransformFontSize); + CPPUNIT_TEST(testBigPPI); + CPPUNIT_TEST(testTdf93750); + CPPUNIT_TEST(testTdf99402); + CPPUNIT_TEST(testTdf39894); + CPPUNIT_TEST(testETO_PDY); + CPPUNIT_TEST(testStockObject); + CPPUNIT_TEST_SUITE_END(); +}; + +void WmfTest::testNonPlaceableWmf() +{ + SvFileStream aFileStream(getFullUrl(u"visio_import_source.wmf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::POLYLINE, false); + + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // These values come from changes done in tdf#88163 + assertXPath(pDoc, "/metafile/polyline[1]/point[1]", "x", "16813"); + assertXPath(pDoc, "/metafile/polyline[1]/point[1]", "y", "1004"); + + assertXPath(pDoc, "/metafile/polyline[1]/point[2]", "x", "16813"); + assertXPath(pDoc, "/metafile/polyline[1]/point[2]", "y", "7514"); + + assertXPath(pDoc, "/metafile/polyline[1]/point[3]", "x", "26112"); + assertXPath(pDoc, "/metafile/polyline[1]/point[3]", "y", "7514"); + + assertXPath(pDoc, "/metafile/polyline[1]/point[4]", "x", "26112"); + assertXPath(pDoc, "/metafile/polyline[1]/point[4]", "y", "1004"); + + assertXPath(pDoc, "/metafile/polyline[1]/point[5]", "x", "16813"); + assertXPath(pDoc, "/metafile/polyline[1]/point[5]", "y", "1004"); +} + +void WmfTest::testTdf88163NonPlaceableWmf() +{ + OUString fileName(u"tdf88163-non-placeable.wmf"); + SvFileStream aFileStream(getFullUrl(fileName), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // These values come from the fix for tdf#88163 + + // Fails without the fix + // With fix: 3272, without fix: ~ 8000 + auto x = getXPath(pDoc, "/metafile/push[2]/font[1]", "height"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3272), x.toInt32()); + + // Fails without the fix: Expected: 7359, Actual: 7336 + assertXPath(pDoc, "/metafile/push[2]/textarray[1]", "x", "7359"); + // Fails without the fix: Expected: 4118, Actual: 4104 + assertXPath(pDoc, "/metafile/push[2]/textarray[1]", "y", "4118"); + + // Fails without the fix: Expected: 5989, Actual: 5971 + assertXPath(pDoc, "/metafile/push[2]/textarray[2]", "x", "5989"); + // Fails without the fix: Expected: 16264, Actual: 16208 + assertXPath(pDoc, "/metafile/push[2]/textarray[2]", "y", "16264"); + + // Fails without the fix: Expected: 20769, Actual: 20705 + assertXPath(pDoc, "/metafile/push[2]/textarray[3]", "x", "20769"); + // Fails without the fix: Expected: 4077, Actual: 4062 + assertXPath(pDoc, "/metafile/push[2]/textarray[3]", "y", "4077"); +} + +void WmfTest::testTdf88163PlaceableWmf() +{ + OUString fileName(u"tdf88163-wrong-font-size.wmf"); + SvFileStream aFileStream(getFullUrl(fileName), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // These values come from the fix for tdf#88163 + + // The fix does not affect the font size + auto x = getXPath(pDoc, "/metafile/push[2]/font[1]", "height"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(313), x.toInt32()); + + // Fails without the fix: Expected: 1900, Actual: 19818 + assertXPath(pDoc, "/metafile", "height", "1900"); + + // Fails without the fix: Expected: 704, Actual: 7336 + assertXPath(pDoc, "/metafile/push[2]/textarray[1]", "x", "704"); + // Fails without the fix: Expected: 394, Actual: 4110 + assertXPath(pDoc, "/metafile/push[2]/textarray[1]", "y", "394"); + + // Fails without the fix: Expected: 573, Actual: 5971 + assertXPath(pDoc, "/metafile/push[2]/textarray[2]", "x", "573"); + // Fails without the fix: Expected: 1556, Actual: 16230 + assertXPath(pDoc, "/metafile/push[2]/textarray[2]", "y", "1556"); + + // Fails without the fix: Expected: 1987, Actual: 20706 + assertXPath(pDoc, "/metafile/push[2]/textarray[3]", "x", "1987"); + // Fails without the fix: Expected: 390, Actual: 4068 + assertXPath(pDoc, "/metafile/push[2]/textarray[3]", "y", "390"); +} + +void WmfTest::testSine() +{ + SvFileStream aFileStream(getFullUrl(u"sine_wave.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::ISECTRECTCLIPREGION, false); + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/metafile/sectrectclipregion", 0); +} + +void WmfTest::testEmfProblem() +{ + SvFileStream aFileStream(getFullUrl(u"computer_mail.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::ISECTRECTCLIPREGION, false); + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/metafile/sectrectclipregion", 2); + assertXPath(pDoc, "/metafile/sectrectclipregion[1]", "top", "2125"); + assertXPath(pDoc, "/metafile/sectrectclipregion[1]", "left", "1084"); + assertXPath(pDoc, "/metafile/sectrectclipregion[1]", "bottom", "2927"); + assertXPath(pDoc, "/metafile/sectrectclipregion[1]", "right", "2376"); +} + +void WmfTest::testEmfLineStyles() +{ + SvFileStream aFileStream(getFullUrl(u"line_styles.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::LINE, false); + dumper.filterActionType(MetaActionType::LINECOLOR, false); + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/metafile/line", 4); + assertXPath(pDoc, "/metafile/linecolor", 5); + + assertXPath(pDoc, "/metafile/linecolor[1]", "color", "#ffffff"); + assertXPath(pDoc, "/metafile/linecolor[2]", "color", "#00ff00"); + assertXPath(pDoc, "/metafile/linecolor[3]", "color", "#408080"); + assertXPath(pDoc, "/metafile/linecolor[4]", "color", "#ff0000"); + assertXPath(pDoc, "/metafile/linecolor[5]", "color", "#0000ff"); + + assertXPath(pDoc, "/metafile/line[1]", "style", "dash"); + assertXPath(pDoc, "/metafile/line[1]", "dashlen", "528"); + assertXPath(pDoc, "/metafile/line[1]", "dashcount", "1"); + assertXPath(pDoc, "/metafile/line[1]", "dotlen", "176"); + assertXPath(pDoc, "/metafile/line[1]", "dotcount", "0"); + assertXPath(pDoc, "/metafile/line[1]", "distance", "176"); + assertXPath(pDoc, "/metafile/line[1]", "join", "miter"); + assertXPath(pDoc, "/metafile/line[1]", "cap", "butt"); + + assertXPath(pDoc, "/metafile/line[2]", "style", "dash"); + assertXPath(pDoc, "/metafile/line[2]", "dashlen", "528"); + assertXPath(pDoc, "/metafile/line[2]", "dashcount", "0"); + assertXPath(pDoc, "/metafile/line[2]", "dotlen", "176"); + assertXPath(pDoc, "/metafile/line[2]", "dotcount", "1"); + assertXPath(pDoc, "/metafile/line[2]", "distance", "176"); + assertXPath(pDoc, "/metafile/line[2]", "join", "miter"); + assertXPath(pDoc, "/metafile/line[2]", "cap", "butt"); + + assertXPath(pDoc, "/metafile/line[3]", "style", "dash"); + assertXPath(pDoc, "/metafile/line[3]", "dashlen", "528"); + assertXPath(pDoc, "/metafile/line[3]", "dashcount", "1"); + assertXPath(pDoc, "/metafile/line[3]", "dotlen", "176"); + assertXPath(pDoc, "/metafile/line[3]", "dotcount", "1"); + assertXPath(pDoc, "/metafile/line[3]", "distance", "176"); + assertXPath(pDoc, "/metafile/line[3]", "join", "miter"); + assertXPath(pDoc, "/metafile/line[3]", "cap", "butt"); + + assertXPath(pDoc, "/metafile/line[4]", "style", "dash"); + assertXPath(pDoc, "/metafile/line[4]", "dashlen", "528"); + assertXPath(pDoc, "/metafile/line[4]", "dashcount", "1"); + assertXPath(pDoc, "/metafile/line[4]", "dotlen", "176"); + assertXPath(pDoc, "/metafile/line[4]", "dotcount", "2"); + assertXPath(pDoc, "/metafile/line[4]", "distance", "176"); + assertXPath(pDoc, "/metafile/line[4]", "join", "miter"); + assertXPath(pDoc, "/metafile/line[4]", "cap", "butt"); +}; + +void WmfTest::testWorldTransformFontSize() +{ + SvFileStream aFileStream(getFullUrl(u"image1.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::FONT, false); + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/metafile/font", 9); + + assertXPath(pDoc, "/metafile/font[1]", "color", "#595959"); + assertXPath(pDoc, "/metafile/font[1]", "width", "0"); + assertXPath(pDoc, "/metafile/font[1]", "height", "389"); + assertXPath(pDoc, "/metafile/font[1]", "orientation", "0"); + assertXPath(pDoc, "/metafile/font[1]", "weight", "bold"); + + assertXPath(pDoc, "/metafile/font[3]", "color", "#000000"); + assertXPath(pDoc, "/metafile/font[3]", "width", "0"); + assertXPath(pDoc, "/metafile/font[3]", "height", "389"); + assertXPath(pDoc, "/metafile/font[3]", "orientation", "0"); + assertXPath(pDoc, "/metafile/font[3]", "weight", "bold"); + + // World transform should not affect font size. Rotating text for 90 degrees + // should not exchange font width and height. + assertXPath(pDoc, "/metafile/font[4]", "color", "#000000"); + assertXPath(pDoc, "/metafile/font[4]", "width", "0"); + assertXPath(pDoc, "/metafile/font[4]", "height", "530"); + assertXPath(pDoc, "/metafile/font[4]", "orientation", "900"); + assertXPath(pDoc, "/metafile/font[4]", "weight", "normal"); +} + +void WmfTest::testBigPPI() +{ + // Test that PPI is reduced from 2540 to 96 (width from META_SETWINDOWEXT) to make the graphic + // bigger + SvFileStream aFileStream(getFullUrl(u"TestBigPPI.wmf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + dumper.filterAllActionTypes(); + dumper.filterActionType(MetaActionType::FONT, false); + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // If the PPI was not reduced the width and height would be <100 which is too small + // Related: tdf#150888 + assertXPath(pDoc, "/metafile", "width", "2540"); + assertXPath(pDoc, "/metafile", "height", "2143"); +} + +void WmfTest::testTdf93750() +{ + SvFileStream aFileStream(getFullUrl(u"tdf93750.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + assertXPath(pDoc, "/metafile/push[1]/comment[2]", "datasize", "28"); + assertXPath(pDoc, "/metafile/push[1]/comment[3]", "datasize", "72"); +} + +void WmfTest::testTdf99402() +{ + // Symbol font should arrive with RTL_TEXTENCODING_SYMBOL encoding, + // even if charset is OEM_CHARSET/DEFAULT_CHARSET in WMF + emfio::LOGFONTW logfontw; + logfontw.lfHeight = 0; + logfontw.lfWidth = 0; + logfontw.lfEscapement = 0; + logfontw.lfWeight = 0; + logfontw.lfItalic = 0; + logfontw.lfUnderline = 0; + logfontw.lfStrikeOut = 0; + logfontw.lfCharSet = emfio::CharacterSet::OEM_CHARSET; + logfontw.lfPitchAndFamily = emfio::FamilyFont::FF_ROMAN << 4 | emfio::PitchFont::DEFAULT_PITCH; + logfontw.alfFaceName = "Symbol"; + + emfio::WinMtfFontStyle fontStyle(logfontw); + + CPPUNIT_ASSERT_EQUAL(RTL_TEXTENCODING_SYMBOL, fontStyle.aFont.GetCharSet()); +} + +void WmfTest::testTdf39894() +{ + OUString files[] = { "tdf39894.wmf", "tdf39894.emf" }; + for (const auto& file : files) + { + SvFileStream aFileStream(getFullUrl(file), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // The x position of the second text must take into account + // the previous text's last Dx (previously was ~300) + auto x = getXPath(pDoc, "/metafile/push[2]/textarray[2]", "x"); + CPPUNIT_ASSERT_GREATER(sal_Int32(2700), x.toInt32()); + } +} + +void WmfTest::testETO_PDY() +{ + OUString files[] = { "ETO_PDY.wmf", "ETO_PDY.emf" }; + for (const auto& file : files) + { + SvFileStream aFileStream(getFullUrl(file), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // The y position of following text + // must be smaller than that of previous + auto y1 = getXPath(pDoc, "/metafile/push[2]/textarray[1]", "y"); + auto y2 = getXPath(pDoc, "/metafile/push[2]/textarray[2]", "y"); + auto y3 = getXPath(pDoc, "/metafile/push[2]/textarray[3]", "y"); + CPPUNIT_ASSERT_MESSAGE(file.toUtf8().getStr(), y2.toInt32() < y1.toInt32()); + CPPUNIT_ASSERT_MESSAGE(file.toUtf8().getStr(), y3.toInt32() < y2.toInt32()); + } +} + +void WmfTest::testStockObject() +{ + SvFileStream aFileStream(getFullUrl(u"stockobject.emf"), StreamMode::READ); + GDIMetaFile aGDIMetaFile; + ReadWindowMetafile(aFileStream, aGDIMetaFile); + + MetafileXmlDump dumper; + xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile); + + CPPUNIT_ASSERT(pDoc); + + // Without the fix in place, this test would have failed with + // - Expected: 1 + // - Actual : 0 + // - In <>, XPath '/metafile/push[2]/fillcolor[2]' number of nodes is incorrect + assertXPath(pDoc, "/metafile/push[2]/fillcolor[2]", "color", "#000000"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(WmfTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/source/emfuno/xemfparser.cxx b/emfio/source/emfuno/xemfparser.cxx new file mode 100644 index 000000000..d5342edf7 --- /dev/null +++ b/emfio/source/emfuno/xemfparser.cxx @@ -0,0 +1,238 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/graphic/XEmfParser.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wmfexternal.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <sal/log.hxx> +#include <comphelper/sequenceashashmap.hxx> + +#include <wmfreader.hxx> +#include <emfreader.hxx> + +using namespace ::com::sun::star; + +namespace emfio::emfreader +{ + namespace { + + class XEmfParser : public ::cppu::WeakAggImplHelper2< graphic::XEmfParser, lang::XServiceInfo > + { + private: + uno::Reference< uno::XComponentContext > context_; + basegfx::B2DTuple maSizeHint; + + public: + explicit XEmfParser( + uno::Reference< uno::XComponentContext > const & context); + XEmfParser(const XEmfParser&) = delete; + XEmfParser& operator=(const XEmfParser&) = delete; + + // XEmfParser + virtual uno::Sequence< uno::Reference< ::graphic::XPrimitive2D > > SAL_CALL getDecomposition( + const uno::Reference< ::io::XInputStream >& xEmfStream, + const OUString& aAbsolutePath, + const uno::Sequence< ::beans::PropertyValue >& rProperties) override; + void SAL_CALL setSizeHint(const geometry::RealPoint2D& rSize) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString&) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + }; + + } + + XEmfParser::XEmfParser( + uno::Reference< uno::XComponentContext > const & context): + context_(context) + { + } + + uno::Sequence< uno::Reference< ::graphic::XPrimitive2D > > XEmfParser::getDecomposition( + const uno::Reference< ::io::XInputStream >& xEmfStream, + const OUString& /*aAbsolutePath*/, + const uno::Sequence< ::beans::PropertyValue >& rProperties) + { + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + if (xEmfStream.is()) + { + WmfExternal aExternalHeader; + const bool bExternalHeaderUsed(aExternalHeader.setSequence(rProperties)); + bool bEnableEMFPlus = true; + comphelper::SequenceAsHashMap aMap(rProperties); + auto it = aMap.find("EMFPlusEnable"); + if (it != aMap.end()) + { + bool bValue; + if (it->second >>= bValue) + { + bEnableEMFPlus = bValue; + } + } + + // rough check - import and conv to primitive + GDIMetaFile aMtf; + std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream(xEmfStream)); + sal_uInt32 nOrgPos = pStream->Tell(); + + SvStreamEndian nOrigNumberFormat = pStream->GetEndian(); + pStream->SetEndian(SvStreamEndian::LITTLE); + + sal_uInt32 nMetaType(0); + if (checkSeek(*pStream, 0x28)) + pStream->ReadUInt32(nMetaType); + pStream->Seek(nOrgPos); + + bool bReadError(false); + + try + { + if (nMetaType == 0x464d4520) + { + // read and get possible failure/error, ReadEnhWMF returns success + emfio::EmfReader aReader(*pStream, aMtf); + aReader.SetSizeHint(maSizeHint); + if (!bEnableEMFPlus) + { + aReader.SetEnableEMFPlus(bEnableEMFPlus); + } + bReadError = !aReader.ReadEnhWMF(); + } + else + { + emfio::WmfReader aReader(*pStream, aMtf, bExternalHeaderUsed ? &aExternalHeader : nullptr); + if (!bEnableEMFPlus) + aReader.SetEnableEMFPlus(bEnableEMFPlus); + aReader.ReadWMF(); + + // Need to check for ErrCode at stream to not lose former work. + // This may contain important information and will behave the + // same as before. When we have an error, do not create content + ErrCode aErrCode(pStream->GetError()); + + bReadError = aErrCode.IsError(); + } + } + catch (...) + { + bReadError = true; + } + + pStream->SetEndian(nOrigNumberFormat); + + if (!bReadError) + { + Size aSize(aMtf.GetPrefSize()); + + if (aMtf.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel) + { + aSize = Application::GetDefaultDevice()->PixelToLogic(aSize, MapMode(MapUnit::Map100thMM)); + } + else + { + aSize = OutputDevice::LogicToLogic(aSize, aMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)); + } + + // use size + const basegfx::B2DHomMatrix aMetafileTransform( + basegfx::utils::createScaleB2DHomMatrix( + aSize.Width(), + aSize.Height())); + + // ...and create a single MetafilePrimitive2D containing the Metafile. + // CAUTION: Currently, ReadWindowMetafile uses the local VectorGraphicData + // and a MetafileAccessor hook at the MetafilePrimitive2D inside of + // ImpGraphic::ImplGetGDIMetaFile to get the Metafile. Thus, the first + // and only primitive in this case *has to be* a MetafilePrimitive2D. + aRetval.push_back( + new drawinglayer::primitive2d::MetafilePrimitive2D( + aMetafileTransform, + aMtf)); + + // // force to use decomposition directly to get rid of the metafile + // const css::uno::Sequence< css::beans::PropertyValue > aViewParameters; + // drawinglayer::primitive2d::MetafilePrimitive2D aMetafilePrimitive2D( + // aMetafileTransform, + // aMtf); + // aRetval.append(aMetafilePrimitive2D.getDecomposition(aViewParameters)); + + // if (aRetval.empty()) + // { + // // for test, just create some graphic data that will get visualized + // const basegfx::B2DRange aRange(1000, 1000, 5000, 5000); + // const basegfx::BColor aColor(1.0, 0.0, 0.0); + // const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + // + // aRetval.push_back(new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aOutline), aColor)); + // } + } + } + else + { + SAL_WARN("emfio", "Invalid stream (!)"); + } + + return aRetval.toSequence(); + } + + void XEmfParser::setSizeHint(const geometry::RealPoint2D& rSize) + { + maSizeHint.setX(rSize.X); + maSizeHint.setY(rSize.Y); + } + + OUString SAL_CALL XEmfParser::getImplementationName() + { + return "emfio::emfreader::XEmfParser"; + } + + sal_Bool SAL_CALL XEmfParser::supportsService(const OUString& rServiceName) + { + return cppu::supportsService(this, rServiceName); + } + + uno::Sequence< OUString > SAL_CALL XEmfParser::getSupportedServiceNames() + { + return { "com.sun.star.graphic.EmfTools" }; + } + +} // end of namespace emfio::emfreader + + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +emfio_emfreader_XEmfParser_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new emfio::emfreader::XEmfParser(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/source/reader/emfreader.cxx b/emfio/source/reader/emfreader.cxx new file mode 100644 index 000000000..1613fd859 --- /dev/null +++ b/emfio/source/reader/emfreader.cxx @@ -0,0 +1,2230 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <emfreader.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <vcl/dibtools.hxx> +#include <o3tl/safeint.hxx> +#include <tools/stream.hxx> +#include <memory> +#include <unotools/configmgr.hxx> +#include <vcl/graph.hxx> +#include <vcl/pdfread.hxx> +#include <rtl/bootstrap.hxx> + +#ifdef DBG_UTIL +#include <vcl/pngwrite.hxx> +#endif + +// GDI-Array + +#define EMR_HEADER 1 +#define EMR_POLYBEZIER 2 +#define EMR_POLYGON 3 +#define EMR_POLYLINE 4 +#define EMR_POLYBEZIERTO 5 +#define EMR_POLYLINETO 6 +#define EMR_POLYPOLYLINE 7 +#define EMR_POLYPOLYGON 8 +#define EMR_SETWINDOWEXTEX 9 +#define EMR_SETWINDOWORGEX 10 +#define EMR_SETVIEWPORTEXTEX 11 +#define EMR_SETVIEWPORTORGEX 12 +#define EMR_SETBRUSHORGEX 13 +#define EMR_EOF 14 +#define EMR_SETPIXELV 15 +#define EMR_SETMAPPERFLAGS 16 +#define EMR_SETMAPMODE 17 +#define EMR_SETBKMODE 18 +#define EMR_SETPOLYFILLMODE 19 +#define EMR_SETROP2 20 +#define EMR_SETSTRETCHBLTMODE 21 +#define EMR_SETTEXTALIGN 22 +#define EMR_SETCOLORADJUSTMENT 23 +#define EMR_SETTEXTCOLOR 24 +#define EMR_SETBKCOLOR 25 +#define EMR_OFFSETCLIPRGN 26 +#define EMR_MOVETOEX 27 +#define EMR_SETMETARGN 28 +#define EMR_EXCLUDECLIPRECT 29 +#define EMR_INTERSECTCLIPRECT 30 +#define EMR_SCALEVIEWPORTEXTEX 31 +#define EMR_SCALEWINDOWEXTEX 32 +#define EMR_SAVEDC 33 +#define EMR_RESTOREDC 34 +#define EMR_SETWORLDTRANSFORM 35 +#define EMR_MODIFYWORLDTRANSFORM 36 +#define EMR_SELECTOBJECT 37 +#define EMR_CREATEPEN 38 +#define EMR_CREATEBRUSHINDIRECT 39 +#define EMR_DELETEOBJECT 40 +#define EMR_ANGLEARC 41 +#define EMR_ELLIPSE 42 +#define EMR_RECTANGLE 43 +#define EMR_ROUNDRECT 44 +#define EMR_ARC 45 +#define EMR_CHORD 46 +#define EMR_PIE 47 +#define EMR_SELECTPALETTE 48 +#define EMR_CREATEPALETTE 49 +#define EMR_SETPALETTEENTRIES 50 +#define EMR_RESIZEPALETTE 51 +#define EMR_REALIZEPALETTE 52 +#define EMR_EXTFLOODFILL 53 +#define EMR_LINETO 54 +#define EMR_ARCTO 55 +#define EMR_POLYDRAW 56 +#define EMR_SETARCDIRECTION 57 +#define EMR_SETMITERLIMIT 58 +#define EMR_BEGINPATH 59 +#define EMR_ENDPATH 60 +#define EMR_CLOSEFIGURE 61 +#define EMR_FILLPATH 62 +#define EMR_STROKEANDFILLPATH 63 +#define EMR_STROKEPATH 64 +#define EMR_FLATTENPATH 65 +#define EMR_WIDENPATH 66 +#define EMR_SELECTCLIPPATH 67 +#define EMR_ABORTPATH 68 + +#define EMR_COMMENT 70 // Contains arbitrary private data. +// Comment Identifiers: +#define EMR_COMMENT_EMFPLUS 0x2B464D45 // Contains embedded EMF+ records. +#define EMR_COMMENT_EMFSPOOL 0x00000000 // Contains embedded EMFSPOOL records. +#define EMR_COMMENT_PUBLIC 0x43494447 // Specify extensions to EMF processing. + +#define EMR_FILLRGN 71 +#define EMR_FRAMERGN 72 +#define EMR_INVERTRGN 73 +#define EMR_PAINTRGN 74 +#define EMR_EXTSELECTCLIPRGN 75 +#define EMR_BITBLT 76 +#define EMR_STRETCHBLT 77 +#define EMR_MASKBLT 78 +#define EMR_PLGBLT 79 +#define EMR_SETDIBITSTODEVICE 80 +#define EMR_STRETCHDIBITS 81 +#define EMR_EXTCREATEFONTINDIRECTW 82 +#define EMR_EXTTEXTOUTA 83 +#define EMR_EXTTEXTOUTW 84 +#define EMR_POLYBEZIER16 85 +#define EMR_POLYGON16 86 +#define EMR_POLYLINE16 87 +#define EMR_POLYBEZIERTO16 88 +#define EMR_POLYLINETO16 89 +#define EMR_POLYPOLYLINE16 90 +#define EMR_POLYPOLYGON16 91 +#define EMR_POLYDRAW16 92 +#define EMR_CREATEMONOBRUSH 93 +#define EMR_CREATEDIBPATTERNBRUSHPT 94 +#define EMR_EXTCREATEPEN 95 +#define EMR_POLYTEXTOUTA 96 +#define EMR_POLYTEXTOUTW 97 + +// WINDOWS VERSION >= 0x400 +#define EMR_SETICMMODE 98 +#define EMR_CREATECOLORSPACE 99 +#define EMR_SETCOLORSPACE 100 +#define EMR_DELETECOLORSPACE 101 +#define EMR_GLSRECORD 102 +#define EMR_GLSBOUNDEDRECORD 103 +#define EMR_PIXELFORMAT 104 + +// WINDOWS VERSION >= 0x500 +#define EMR_DRAWESCAPE 105 +#define EMR_EXTESCAPE 106 +#define EMR_STARTDOC 107 +#define EMR_SMALLTEXTOUT 108 +#define EMR_FORCEUFIMAPPING 109 +#define EMR_NAMEDESCAPE 110 +#define EMR_COLORCORRECTPALETTE 111 +#define EMR_SETICMPROFILEA 112 +#define EMR_SETICMPROFILEW 113 +#define EMR_ALPHABLEND 114 +#define EMR_ALPHADIBBLEND 115 +#define EMR_TRANSPARENTBLT 116 +#define EMR_TRANSPARENTDIB 117 +#define EMR_GRADIENTFILL 118 +#define EMR_SETLINKEDUFIS 119 +#define EMR_SETTEXTJUSTIFICATION 120 + +#define PDF_SIGNATURE 0x50444620 // "PDF " + +/* [MS-EMF] - v20210625 - page 28 */ +constexpr sal_Int32 ARCDIRECTION_CLOCKWISE = 0x00000002; + +namespace +{ + +const char * +record_type_name(sal_uInt32 nRecType) +{ +#ifndef SAL_LOG_INFO + (void) nRecType; + return ""; +#else + switch( nRecType ) + { + case EMR_HEADER: return "HEADER"; + case EMR_POLYBEZIER: return "POLYBEZIER"; + case EMR_POLYGON: return "POLYGON"; + case EMR_POLYLINE: return "POLYLINE"; + case EMR_POLYBEZIERTO: return "POLYBEZIERTO"; + case EMR_POLYLINETO: return "POLYLINETO"; + case EMR_POLYPOLYLINE: return "POLYPOLYLINE"; + case EMR_POLYPOLYGON: return "POLYPOLYGON"; + case EMR_SETWINDOWEXTEX: return "SETWINDOWEXTEX"; + case EMR_SETWINDOWORGEX: return "SETWINDOWORGEX"; + case EMR_SETVIEWPORTEXTEX: return "SETVIEWPORTEXTEX"; + case EMR_SETVIEWPORTORGEX: return "SETVIEWPORTORGEX"; + case EMR_SETBRUSHORGEX: return "SETBRUSHORGEX"; + case EMR_EOF: return "EOF"; + case EMR_SETPIXELV: return "SETPIXELV"; + case EMR_SETMAPPERFLAGS: return "SETMAPPERFLAGS"; + case EMR_SETMAPMODE: return "SETMAPMODE"; + case EMR_SETBKMODE: return "SETBKMODE"; + case EMR_SETPOLYFILLMODE: return "SETPOLYFILLMODE"; + case EMR_SETROP2: return "SETROP2"; + case EMR_SETSTRETCHBLTMODE: return "SETSTRETCHBLTMODE"; + case EMR_SETTEXTALIGN: return "SETTEXTALIGN"; + case EMR_SETCOLORADJUSTMENT: return "SETCOLORADJUSTMENT"; + case EMR_SETTEXTCOLOR: return "SETTEXTCOLOR"; + case EMR_SETBKCOLOR: return "SETBKCOLOR"; + case EMR_OFFSETCLIPRGN: return "OFFSETCLIPRGN"; + case EMR_MOVETOEX: return "MOVETOEX"; + case EMR_SETMETARGN: return "SETMETARGN"; + case EMR_EXCLUDECLIPRECT: return "EXCLUDECLIPRECT"; + case EMR_INTERSECTCLIPRECT: return "INTERSECTCLIPRECT"; + case EMR_SCALEVIEWPORTEXTEX: return "SCALEVIEWPORTEXTEX"; + case EMR_SCALEWINDOWEXTEX: return "SCALEWINDOWEXTEX"; + case EMR_SAVEDC: return "SAVEDC"; + case EMR_RESTOREDC: return "RESTOREDC"; + case EMR_SETWORLDTRANSFORM: return "SETWORLDTRANSFORM"; + case EMR_MODIFYWORLDTRANSFORM: return "MODIFYWORLDTRANSFORM"; + case EMR_SELECTOBJECT: return "SELECTOBJECT"; + case EMR_CREATEPEN: return "CREATEPEN"; + case EMR_CREATEBRUSHINDIRECT: return "CREATEBRUSHINDIRECT"; + case EMR_DELETEOBJECT: return "DELETEOBJECT"; + case EMR_ANGLEARC: return "ANGLEARC"; + case EMR_ELLIPSE: return "ELLIPSE"; + case EMR_RECTANGLE: return "RECTANGLE"; + case EMR_ROUNDRECT: return "ROUNDRECT"; + case EMR_ARC: return "ARC"; + case EMR_CHORD: return "CHORD"; + case EMR_PIE: return "PIE"; + case EMR_SELECTPALETTE: return "SELECTPALETTE"; + case EMR_CREATEPALETTE: return "CREATEPALETTE"; + case EMR_SETPALETTEENTRIES: return "SETPALETTEENTRIES"; + case EMR_RESIZEPALETTE: return "RESIZEPALETTE"; + case EMR_REALIZEPALETTE: return "REALIZEPALETTE"; + case EMR_EXTFLOODFILL: return "EXTFLOODFILL"; + case EMR_LINETO: return "LINETO"; + case EMR_ARCTO: return "ARCTO"; + case EMR_POLYDRAW: return "POLYDRAW"; + case EMR_SETARCDIRECTION: return "SETARCDIRECTION"; + case EMR_SETMITERLIMIT: return "SETMITERLIMIT"; + case EMR_BEGINPATH: return "BEGINPATH"; + case EMR_ENDPATH: return "ENDPATH"; + case EMR_CLOSEFIGURE: return "CLOSEFIGURE"; + case EMR_FILLPATH: return "FILLPATH"; + case EMR_STROKEANDFILLPATH: return "STROKEANDFILLPATH"; + case EMR_STROKEPATH: return "STROKEPATH"; + case EMR_FLATTENPATH: return "FLATTENPATH"; + case EMR_WIDENPATH: return "WIDENPATH"; + case EMR_SELECTCLIPPATH: return "SELECTCLIPPATH"; + case EMR_ABORTPATH: return "ABORTPATH"; + case EMR_COMMENT: return "COMMENT"; + case EMR_FILLRGN: return "FILLRGN"; + case EMR_FRAMERGN: return "FRAMERGN"; + case EMR_INVERTRGN: return "INVERTRGN"; + case EMR_PAINTRGN: return "PAINTRGN"; + case EMR_EXTSELECTCLIPRGN: return "EXTSELECTCLIPRGN"; + case EMR_BITBLT: return "BITBLT"; + case EMR_STRETCHBLT: return "STRETCHBLT"; + case EMR_MASKBLT: return "MASKBLT"; + case EMR_PLGBLT: return "PLGBLT"; + case EMR_SETDIBITSTODEVICE: return "SETDIBITSTODEVICE"; + case EMR_STRETCHDIBITS: return "STRETCHDIBITS"; + case EMR_EXTCREATEFONTINDIRECTW: return "EXTCREATEFONTINDIRECTW"; + case EMR_EXTTEXTOUTA: return "EXTTEXTOUTA"; + case EMR_EXTTEXTOUTW: return "EXTTEXTOUTW"; + case EMR_POLYBEZIER16: return "POLYBEZIER16"; + case EMR_POLYGON16: return "POLYGON16"; + case EMR_POLYLINE16: return "POLYLINE16"; + case EMR_POLYBEZIERTO16: return "POLYBEZIERTO16"; + case EMR_POLYLINETO16: return "POLYLINETO16"; + case EMR_POLYPOLYLINE16: return "POLYPOLYLINE16"; + case EMR_POLYPOLYGON16: return "POLYPOLYGON16"; + case EMR_POLYDRAW16: return "POLYDRAW16"; + case EMR_CREATEMONOBRUSH: return "CREATEMONOBRUSH"; + case EMR_CREATEDIBPATTERNBRUSHPT: return "CREATEDIBPATTERNBRUSHPT"; + case EMR_EXTCREATEPEN: return "EXTCREATEPEN"; + case EMR_POLYTEXTOUTA: return "POLYTEXTOUTA"; + case EMR_POLYTEXTOUTW: return "POLYTEXTOUTW"; + case EMR_SETICMMODE: return "SETICMMODE"; + case EMR_CREATECOLORSPACE: return "CREATECOLORSPACE"; + case EMR_SETCOLORSPACE: return "SETCOLORSPACE"; + case EMR_DELETECOLORSPACE: return "DELETECOLORSPACE"; + case EMR_GLSRECORD: return "GLSRECORD"; + case EMR_GLSBOUNDEDRECORD: return "GLSBOUNDEDRECORD"; + case EMR_PIXELFORMAT: return "PIXELFORMAT"; + case EMR_DRAWESCAPE: return "DRAWESCAPE"; + case EMR_EXTESCAPE: return "EXTESCAPE"; + case EMR_STARTDOC: return "STARTDOC"; + case EMR_SMALLTEXTOUT: return "SMALLTEXTOUT"; + case EMR_FORCEUFIMAPPING: return "FORCEUFIMAPPING"; + case EMR_NAMEDESCAPE: return "NAMEDESCAPE"; + case EMR_COLORCORRECTPALETTE: return "COLORCORRECTPALETTE"; + case EMR_SETICMPROFILEA: return "SETICMPROFILEA"; + case EMR_SETICMPROFILEW: return "SETICMPROFILEW"; + case EMR_ALPHABLEND: return "ALPHABLEND"; + case EMR_ALPHADIBBLEND: return "ALPHADIBBLEND"; + case EMR_TRANSPARENTBLT: return "TRANSPARENTBLT"; + case EMR_TRANSPARENTDIB: return "TRANSPARENTDIB"; + case EMR_GRADIENTFILL: return "GRADIENTFILL"; + case EMR_SETLINKEDUFIS: return "SETLINKEDUFIS"; + case EMR_SETTEXTJUSTIFICATION: return "SETTEXTJUSTIFICATION"; + default: + // Yes, return a pointer to a static buffer. This is a very + // local debugging output function, so no big deal. + static char buffer[11]; + sprintf(buffer, "0x%08" SAL_PRIxUINT32, nRecType); + return buffer; + } +#endif +} + +struct BLENDFUNCTION +{ + unsigned char aBlendOperation; + unsigned char aBlendFlags; + unsigned char aSrcConstantAlpha; + unsigned char aAlphaFormat; + + friend SvStream& operator>>(SvStream& rInStream, BLENDFUNCTION& rBlendFun); +}; + +SvStream& operator>>(SvStream& rInStream, BLENDFUNCTION& rBlendFun) +{ + rInStream.ReadUChar(rBlendFun.aBlendOperation); + rInStream.ReadUChar(rBlendFun.aBlendFlags); + rInStream.ReadUChar(rBlendFun.aSrcConstantAlpha); + rInStream.ReadUChar(rBlendFun.aAlphaFormat); + return rInStream; +} + +bool ImplReadRegion( basegfx::B2DPolyPolygon& rPolyPoly, SvStream& rStream, sal_uInt32 nLen ) +{ + if (nLen < 32) // 32 bytes - Size of RegionDataHeader + return false; + + sal_uInt32 nHdSize, nType, nCountRects, nRgnSize; + rStream.ReadUInt32(nHdSize); + rStream.ReadUInt32(nType); + rStream.ReadUInt32(nCountRects); + rStream.ReadUInt32(nRgnSize); + + sal_Int32 nLeft, nTop, nRight, nBottom; + //bounds of the region + rStream.ReadInt32(nLeft); + rStream.ReadInt32(nTop); + rStream.ReadInt32(nRight); + rStream.ReadInt32(nBottom); + + if (!rStream.good() || nCountRects == 0 || nType != emfio::RDH_RECTANGLES) + return false; + + SAL_INFO("emfio", "\t\tBounds Left: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom); + + nLen -= 32; + + sal_uInt32 nSize; + if (o3tl::checked_multiply<sal_uInt32>(nCountRects, 16, nSize)) + return false; + if (nLen < nSize) + return false; + + for (sal_uInt32 i = 0; i < nCountRects; ++i) + { + rStream.ReadInt32(nLeft); + rStream.ReadInt32(nTop); + rStream.ReadInt32(nRight); + rStream.ReadInt32(nBottom); + rPolyPoly.append( basegfx::utils::createPolygonFromRect( ::basegfx::B2DRectangle( nLeft, nTop, nRight, nBottom ) ) ); + SAL_INFO("emfio", "\t\t" << i << " Left: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom); + } + if (!utl::ConfigManager::IsFuzzing()) + { + rPolyPoly = basegfx::utils::solveCrossovers(rPolyPoly); + rPolyPoly = basegfx::utils::stripNeutralPolygons(rPolyPoly); + rPolyPoly = basegfx::utils::stripDispensablePolygons(rPolyPoly); + } + return true; +} + +} // anonymous namespace + +namespace emfio +{ + EmfReader::EmfReader(SvStream& rStream,GDIMetaFile& rGDIMetaFile) + : MtfTools(rGDIMetaFile, rStream) + , mnRecordCount(0) + , mbRecordPath(false) + , mbEMFPlus(false) + , mbEMFPlusDualMode(false) + { + } + + EmfReader::~EmfReader() + { + } + + const sal_uInt32 EMR_COMMENT_BEGINGROUP = 0x00000002; + const sal_uInt32 EMR_COMMENT_ENDGROUP = 0x00000003; + const sal_uInt32 EMR_COMMENT_MULTIFORMATS = 0x40000004; + const sal_uInt32 EMR_COMMENT_WINDOWS_METAFILE = 0x80000001; + + void EmfReader::ReadGDIComment(sal_uInt32 nCommentId) + { + sal_uInt32 nPublicCommentIdentifier(0); + mpInputStream->ReadUInt32(nPublicCommentIdentifier); + + SAL_INFO("emfio", "\t\tEMR_COMMENT_PUBLIC, id: 0x" << std::hex << nCommentId << std::dec); + switch (nPublicCommentIdentifier) + { + case EMR_COMMENT_BEGINGROUP: + { + SAL_INFO("emfio", "\t\t\tEMR_COMMENT_BEGINGROUP"); + sal_uInt32 left, top, right, bottom; + mpInputStream->ReadUInt32(left).ReadUInt32(top).ReadUInt32(right).ReadUInt32(bottom); + + SAL_INFO("emfio", "\t\t\t\tBounding rect"); + SAL_INFO("emfio", "\t\t\t\t\tLeft: " << left); + SAL_INFO("emfio", "\t\t\t\t\tTop: " << top); + SAL_INFO("emfio", "\t\t\t\t\tRight: " << right); + SAL_INFO("emfio", "\t\t\t\t\tBottom: " << bottom); + + sal_uInt32 nDescChars(0); + mpInputStream->ReadUInt32(nDescChars); + + OUString aDesc; + for (sal_uInt32 i=0; i < nDescChars; i++) + { + sal_uInt16 cChar(0); + mpInputStream->ReadUInt16(cChar); + if (cChar == '\0') + break; + + sal_Unicode cUniChar = static_cast<sal_Unicode>(cChar); + aDesc = aDesc + OUStringChar(cUniChar); + } + + SAL_INFO("emfio", "\t\tDescription: " << aDesc); + } + break; + + case EMR_COMMENT_ENDGROUP: + SAL_INFO("emfio", "\t\t\tEMR_COMMENT_ENDGROUP"); + break; + + case EMR_COMMENT_MULTIFORMATS: + ReadMultiformatsComment(); + break; + + case EMR_COMMENT_WINDOWS_METAFILE: + SAL_WARN("emfio", "\t\tEMR_COMMENT_WINDOWS_METAFILE not implemented"); + break; + + default: + SAL_WARN("emfio", "\t\tEMR_COMMENT_PUBLIC not implemented, id: 0x" << std::hex << nCommentId << std::dec); + break; + } + } + + void EmfReader::ReadMultiformatsComment() + { + tools::Rectangle aOutputRect = EmfReader::ReadRectangle(); + + sal_uInt32 nCountFormats(0); + mpInputStream->ReadUInt32(nCountFormats); + if (nCountFormats < 1) + { + return; + } + + // Read the first EmrFormat. + sal_uInt32 nSignature(0); + mpInputStream->ReadUInt32(nSignature); + if (nSignature != PDF_SIGNATURE) + { + return; + } + + sal_uInt32 nVersion(0); + mpInputStream->ReadUInt32(nVersion); + if (nVersion != 1) + { + return; + } + + sal_uInt32 nSizeData(0); + mpInputStream->ReadUInt32(nSizeData); + if (!nSizeData || nSizeData > mpInputStream->remainingSize()) + { + return; + } + + sal_uInt32 nOffData(0); + mpInputStream->ReadUInt32(nOffData); + if (!nOffData) + { + return; + } + + std::vector<char> aPdfData(nSizeData); + mpInputStream->ReadBytes(aPdfData.data(), aPdfData.size()); + if (!mpInputStream->good()) + { + return; + } + + SvMemoryStream aPdfStream; + aPdfStream.WriteBytes(aPdfData.data(), aPdfData.size()); + aPdfStream.Seek(0); + Graphic aGraphic; + if (!vcl::ImportPDF(aPdfStream, aGraphic)) + { + return; + } + + // aGraphic will be the only output of the EMF parser, so its size hint can be the same as + // ours. + aGraphic.getVectorGraphicData()->setSizeHint(maSizeHint); + + maBmpSaveList.emplace_back( + aGraphic.GetBitmapEx(), aOutputRect, SRCCOPY, /*bForceAlpha=*/true); + const std::shared_ptr<VectorGraphicData> pVectorGraphicData + = aGraphic.getVectorGraphicData(); + if (!pVectorGraphicData) + { + return; + } + + if (pVectorGraphicData->getType() != VectorGraphicDataType::Pdf) + { + return; + } + + mbReadOtherGraphicFormat = true; + } + + void EmfReader::ReadEMFPlusComment(sal_uInt32 length, bool& bHaveDC) + { + if (!mbEMFPlus) + { + PassEMFPlusHeaderInfo(); + + #if OSL_DEBUG_LEVEL > 1 + // debug code - write the stream to debug file /tmp/emf-stream.emf + sal_uInt64 const pos = mpInputStream->Tell(); + mpInputStream->Seek(0); + SvFileStream file( OUString( "/tmp/emf-stream.emf" ), StreamMode::WRITE | StreamMode::TRUNC ); + + mpInputStream->WriteStream(file); + file.Flush(); + file.Close(); + + mpInputStream->Seek( pos ); + #endif + + } + + mbEMFPlus = true; + sal_uInt64 const pos = mpInputStream->Tell(); + auto buffer = std::make_unique<char[]>( length ); + PassEMFPlus( buffer.get(), mpInputStream->ReadBytes(buffer.get(), length) ); + buffer.reset(); + mpInputStream->Seek( pos ); + + bHaveDC = false; + + // skip in SeekRel if impossibly unavailable + sal_uInt32 nRemainder = length; + + const size_t nRequiredHeaderSize = 12; + while (nRemainder >= nRequiredHeaderSize) + { + sal_uInt16 type(0), flags(0); + sal_uInt32 size(0), dataSize(0); + + mpInputStream->ReadUInt16( type ).ReadUInt16( flags ).ReadUInt32( size ).ReadUInt32( dataSize ); + nRemainder -= nRequiredHeaderSize; + + SAL_INFO("emfio", "\t\tEMF+ record type: 0x" << std::hex << type << std::dec); + + // Get Device Context + // TODO We should use EmfPlusRecordType::GetDC instead + if( type == 0x4004 ) + { + bHaveDC = true; + SAL_INFO("emfio", "\t\tEMF+ lock DC (device context)"); + } + + // look for the "dual mode" in header + // it indicates that either EMF or EMF+ records should be processed + // 0x4001 = EMF+ header + // flags & 1 = dual mode active + if ( type == 0x4001 && flags & 1 ) + { + mbEMFPlusDualMode = true; + SAL_INFO ("emfio", "\t\tEMF+ dual mode detected"); + } + + // Get the length of the remaining data of this record based + // on the alleged size + sal_uInt32 nRemainingRecordData = size >= nRequiredHeaderSize ? + size-nRequiredHeaderSize : 0; + // clip to available size + nRemainingRecordData = std::min(nRemainingRecordData, nRemainder); + mpInputStream->SeekRel(nRemainingRecordData); + nRemainder -= nRemainingRecordData; + } + mpInputStream->SeekRel(nRemainder); + } + + // these are referenced from inside the templates + static SvStream& operator >> (SvStream& rStream, sal_Int16 &n) + { + return rStream.ReadInt16(n); + } + + static SvStream& operator >> (SvStream& rStream, sal_Int32 &n) + { + return rStream.ReadInt32(n); + } + + /** + * Reads polygons from the stream. + * The \<class T> parameter is for the type of the points (sal_uInt32 or sal_uInt16). + * skipFirst: if the first point read is the 0th point or the 1st point in the array. + * */ + template <class T> + tools::Polygon EmfReader::ReadPolygonWithSkip(const bool skipFirst, sal_uInt32 nNextPos) + { + sal_uInt32 nPoints(0), nStartIndex(0); + mpInputStream->SeekRel( 16 ); + mpInputStream->ReadUInt32( nPoints ); + if (skipFirst) + { + nPoints ++; + nStartIndex ++; + } + + return ReadPolygon<T>(nStartIndex, nPoints, nNextPos); + } + + /** + * Reads polygons from the stream. + * The \<class T> parameter is for the type of the points + * nStartIndex: which is the starting index in the polygon of the first point read + * nPoints: number of points + * mpInputStream: the stream containing the polygons + * */ + template <class T> + tools::Polygon EmfReader::ReadPolygon(sal_uInt32 nStartIndex, sal_uInt32 nPoints, sal_uInt32 nNextPos) + { + SAL_INFO ("emfio", "\t\tPolygon:"); + + bool bRecordOk = nPoints <= SAL_MAX_UINT16; + SAL_WARN_IF(!bRecordOk, "emfio", "polygon record has more polygons than we can handle"); + if (!bRecordOk || !nPoints) + return tools::Polygon(); + + auto nRemainingSize = std::min(nNextPos - mpInputStream->Tell(), mpInputStream->remainingSize()); + auto nMaxPossiblePoints = nRemainingSize / (sizeof(T) * 2); + auto nPointCount = nPoints - nStartIndex; + if (nPointCount > nMaxPossiblePoints) + { + SAL_WARN("emfio", "polygon claims more points than record can provide, truncating"); + nPoints = nMaxPossiblePoints + nStartIndex; + } + + tools::Polygon aPolygon(nPoints); + for (sal_uInt32 i = nStartIndex ; i < nPoints && mpInputStream->good(); i++ ) + { + T nX, nY; + *mpInputStream >> nX >> nY; + + SAL_INFO("emfio", "\t\t\tPoint " << i << " of " << nPoints - 1 << ": " << nX << ", " << nY); + + if (!mpInputStream->good()) + { + SAL_WARN("emfio", "short read on polygon, truncating"); + aPolygon.SetSize(i); + break; + } + aPolygon[ i ] = Point( nX, nY ); + } + + return aPolygon; + } + + /** + * Reads a polyline from the WMF file and draws it + * The \<class T> parameter refers to the type of the points. (e.g. sal_uInt16 or sal_uInt32) + * */ + template <class T> + void EmfReader::ReadAndDrawPolyLine(sal_uInt32 nNextPos) + { + SAL_INFO("emfio", "\t\tPolyline: "); + + mpInputStream->SeekRel( 0x10 ); // TODO Skipping Bounds. A 128-bit WMF RectL object (specifies the bounding rectangle in device units.) + + sal_uInt32 nNumberOfPolylines = 0; + mpInputStream->ReadUInt32( nNumberOfPolylines ); + SAL_INFO("emfio", "\t\t\tPolylines: " << nNumberOfPolylines); + + sal_uInt32 nCount = 0; + mpInputStream->ReadUInt32( nCount ); // total number of points in all polylines + SAL_INFO("emfio", "\t\t\tPoints: " << nCount); + + const auto nEndPos = std::min(nNextPos, mnEndPos); + if (mpInputStream->Tell() >= nEndPos) + return; + + // taking the amount of points of each polygon, retrieving the total number of points + if ( !(mpInputStream->good() && + ( nNumberOfPolylines < SAL_MAX_UINT32 / sizeof( sal_uInt16 ) ) && + ( nNumberOfPolylines * sizeof( sal_uInt16 ) ) <= ( nEndPos - mpInputStream->Tell() )) + ) + return; + + std::unique_ptr< sal_uInt32[] > pnPolylinePointCount( new sal_uInt32[ nNumberOfPolylines ] ); + for ( sal_uInt32 i = 0; i < nNumberOfPolylines && mpInputStream->good(); i++ ) + { + sal_uInt32 nPoints; + mpInputStream->ReadUInt32( nPoints ); + SAL_INFO("emfio", "\t\t\tPoint " << i << " of " << nNumberOfPolylines << ": " << nPoints); + pnPolylinePointCount[ i ] = nPoints; + } + + // Get polyline points: + for ( sal_uInt32 i = 0; ( i < nNumberOfPolylines ) && mpInputStream->good(); i++ ) + { + tools::Polygon aPolygon = ReadPolygon<T>(0, pnPolylinePointCount[i], nNextPos); + DrawPolyLine(std::move(aPolygon), false, mbRecordPath); + } + } + + /** + * Reads a poly polygon from the WMF file and draws it. + * The \<class T> parameter refers to the type of the points. (e.g. sal_uInt16 or sal_uInt32) + * */ + template <class T> + void EmfReader::ReadAndDrawPolyPolygon(sal_uInt32 nNextPos) + { + SAL_INFO("emfio", "\t\tPolygon: "); + mpInputStream->SeekRel( 0x10 ); // RectL bounds + + sal_uInt32 nPoly(0), nGesPoints(0), nReadPoints(0); + // Number of polygons + mpInputStream->ReadUInt32( nPoly ).ReadUInt32( nGesPoints ); + SAL_INFO("emfio", "\t\t\tPolygons: " << nPoly); + SAL_INFO("emfio", "\t\t\tPoints: " << nGesPoints); + + const auto nEndPos = std::min(nNextPos, mnEndPos); + if (mpInputStream->Tell() >= nEndPos) + return; + if (!mpInputStream->good()) + return; + //check against numeric overflowing + if (nGesPoints >= SAL_MAX_UINT32 / sizeof(Point)) + return; + if (nPoly >= SAL_MAX_UINT32 / sizeof(sal_uInt16)) + return; + if (nPoly * sizeof(sal_uInt16) > nEndPos - mpInputStream->Tell()) + return; + + // Get number of points in each polygon + std::vector<sal_uInt16> aPoints(nPoly); + for (sal_uInt32 i = 0; i < nPoly && mpInputStream->good(); ++i) + { + sal_uInt32 nPoints(0); + mpInputStream->ReadUInt32( nPoints ); + + SAL_INFO("emfio", "\t\t\t\tPolygon " << i << " points: " << nPoints); + + aPoints[i] = static_cast<sal_uInt16>(nPoints); + } + + if ( mpInputStream->good() && ( nGesPoints * (sizeof(T)+sizeof(T)) ) <= ( nEndPos - mpInputStream->Tell() ) ) + { + // Get polygon points + tools::PolyPolygon aPolyPoly(nPoly); + for (sal_uInt32 i = 0; i < nPoly && mpInputStream->good(); ++i) + { + const sal_uInt16 nPointCount(aPoints[i]); + std::vector<Point> aPtAry(nPointCount); + for (sal_uInt16 j = 0; j < nPointCount && mpInputStream->good(); ++j) + { + T nX(0), nY(0); + *mpInputStream >> nX >> nY; + aPtAry[j] = Point( nX, nY ); + ++nReadPoints; + } + + aPolyPoly.Insert(tools::Polygon(aPtAry.size(), aPtAry.data())); + } + + DrawPolyPolygon(aPolyPoly, mbRecordPath); + } + + OSL_ENSURE(nReadPoints == nGesPoints, "The number Points processed from EMR_POLYPOLYGON is unequal imported number (!)"); + } + + bool EmfReader::ReadEnhWMF() + { + sal_uInt32 nStretchBltMode = 0; + sal_uInt32 nNextPos(0), + nW(0), nH(0), nColor(0), nIndex(0), + nDat32(0), nNom1(0), nDen1(0), nNom2(0), nDen2(0); + sal_Int32 nX32(0), nY32(0), nx32(0), ny32(0); + + bool bStatus = ReadHeader(); + bool bHaveDC = false; + + OUString aEMFPlusDisable; + rtl::Bootstrap::get("EMF_PLUS_DISABLE", aEMFPlusDisable); + bool bEnableEMFPlus = aEMFPlusDisable.isEmpty(); + if (!mbEnableEMFPlus) + { + // EMF+ is enabled if neither the bootstrap variable, not the member variable disables + // it. + bEnableEMFPlus = mbEnableEMFPlus; + } + + SAL_INFO("emfio", "EMF+ reading is " << (bEnableEMFPlus ? "enabled" : "disabled")); + + while (bStatus && mnRecordCount-- && mpInputStream->good() && !mbReadOtherGraphicFormat) + { + sal_uInt32 nRecType(0), nRecSize(0); + mpInputStream->ReadUInt32(nRecType).ReadUInt32(nRecSize); + + if ( !mpInputStream->good() || ( nRecSize < 8 ) || ( nRecSize & 3 ) ) // Parameters are always divisible by 4 + { + bStatus = false; + break; + } + + auto nCurPos = mpInputStream->Tell(); + + if (mnEndPos < nCurPos - 8) + { + bStatus = false; + break; + } + + const sal_uInt32 nMaxPossibleRecSize = mnEndPos - (nCurPos - 8); + if (nRecSize > nMaxPossibleRecSize) + { + bStatus = false; + break; + } + + nNextPos = nCurPos + (nRecSize - 8); + + if( !maBmpSaveList.empty() + && ( nRecType != EMR_STRETCHBLT ) + && ( nRecType != EMR_STRETCHDIBITS ) + ) { + ResolveBitmapActions( maBmpSaveList ); + } + + bool bFlag = false; + + SAL_INFO("emfio", "0x" << std::hex << (nNextPos - nRecSize) << "-0x" << nNextPos << " " << record_type_name(nRecType) << " size: " + << std::dec << nRecSize); + + if( bEnableEMFPlus && nRecType == EMR_COMMENT ) + { + sal_uInt32 length; + + mpInputStream->ReadUInt32( length ); + + SAL_INFO("emfio", "\tGDI comment, length: " << length); + + if( mpInputStream->good() && length >= 4 && length <= mpInputStream->remainingSize() ) { + sal_uInt32 nCommentId; + + mpInputStream->ReadUInt32( nCommentId ); + + 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); + + if( nCommentId == EMR_COMMENT_EMFPLUS && nRecSize >= 12 ) + { + // [MS-EMF] 2.3.3: DataSize includes both CommentIdentifier and CommentRecordParm fields. + // We have already read 4-byte CommentIdentifier, so reduce length appropriately + ReadEMFPlusComment( length-4, bHaveDC ); + } + else if( nCommentId == EMR_COMMENT_PUBLIC && nRecSize >= 12 ) + { + ReadGDIComment(nCommentId); + } + else if( nCommentId == EMR_COMMENT_EMFSPOOL && nRecSize >= 12 ) + { + SAL_WARN("emfio", "\t\tEMFSPOOL not implemented, id: 0x" << std::hex << nCommentId << std::dec); + // TODO Implement reading EMFSPOOL comment + + } + else + { + SAL_WARN("emfio", "\t\tunknown id: 0x" << std::hex << nCommentId << std::dec); + } + } + } + else if ( !bHaveDC && mbEMFPlusDualMode && nRecType != EMR_HEADER && nRecType != EMR_EOF ) + { + // skip content (EMF record) in dual mode + // we process only EMR_COMMENT (see above) to access EMF+ data + // with 2 exceptions, according to EMF+ specification: + // EMR_HEADER and EMR_EOF + // if a device context is given (bHaveDC) process the following EMF record, too. + } + else if( !mbEMFPlus || bHaveDC || nRecType == EMR_EOF ) + { + switch( nRecType ) + { + case EMR_POLYBEZIERTO : + DrawPolyBezier(ReadPolygonWithSkip<sal_Int32>(true, nNextPos), true, mbRecordPath); + break; + case EMR_POLYBEZIER : + DrawPolyBezier(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), false, mbRecordPath); + break; + + case EMR_POLYGON : + DrawPolygon(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), mbRecordPath); + break; + + case EMR_POLYLINETO : + DrawPolyLine(ReadPolygonWithSkip<sal_Int32>(true, nNextPos), true, mbRecordPath); + break; + + case EMR_POLYLINE : + DrawPolyLine(ReadPolygonWithSkip<sal_Int32>(false, nNextPos), false, mbRecordPath); + break; + + case EMR_POLYPOLYLINE : + ReadAndDrawPolyLine<sal_Int32>(nNextPos); + break; + + case EMR_POLYPOLYGON : + ReadAndDrawPolyPolygon<sal_Int32>(nNextPos); + break; + + case EMR_SETWINDOWEXTEX : + { + sal_Int32 w = 0, h = 0; + mpInputStream->ReadInt32( w ).ReadInt32( h ); + SAL_INFO("emfio", "\t\tWidth: " << w); + SAL_INFO("emfio", "\t\tHeight: " << h); + SetWinExt( Size( w, h ), true); + } + break; + + case EMR_SETWINDOWORGEX : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + SetWinOrg( Point( nX32, nY32 ), true); + } + break; + + case EMR_SCALEWINDOWEXTEX : + { + mpInputStream->ReadUInt32( nNom1 ).ReadUInt32( nDen1 ).ReadUInt32( nNom2 ).ReadUInt32( nDen2 ); + SAL_INFO("emfio", "\t\tHorizontal scale: " << nNom1 << " / " << nDen1); + SAL_INFO("emfio", "\t\tVertical scale: " << nNom2 << " / " << nDen2); + + if (nDen1 != 0 && nDen2 != 0) + ScaleWinExt( static_cast<double>(nNom1) / nDen1, static_cast<double>(nNom2) / nDen2 ); + else + SAL_WARN("vcl.emf", "ignoring bogus divide by zero"); + } + break; + + case EMR_SETVIEWPORTORGEX : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + SetDevOrg( Point( nX32, nY32 ) ); + } + break; + + case EMR_SCALEVIEWPORTEXTEX : + { + mpInputStream->ReadUInt32( nNom1 ).ReadUInt32( nDen1 ).ReadUInt32( nNom2 ).ReadUInt32( nDen2 ); + SAL_INFO("emfio", "\t\tHorizontal scale: " << nNom1 << " / " << nDen1); + SAL_INFO("emfio", "\t\tVertical scale: " << nNom2 << " / " << nDen2); + + if (nDen1 != 0 && nDen2 != 0) + ScaleDevExt( static_cast<double>(nNom1) / nDen1, static_cast<double>(nNom2) / nDen2 ); + else + SAL_WARN("vcl.emf", "ignoring bogus divide by zero"); + } + break; + + case EMR_SETVIEWPORTEXTEX : + { + sal_Int32 w = 0, h = 0; + mpInputStream->ReadInt32( w ).ReadInt32( h ); + SAL_INFO("emfio", "\t\tWidth: " << w); + SAL_INFO("emfio", "\t\tHeight: " << h); + + SetDevExt( Size( w, h ) ); + } + break; + + case EMR_EOF : + mnRecordCount = 0; + break; + + case EMR_SETPIXELV : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + DrawPixel( Point( nX32, nY32 ), ReadColor() ); + } + break; + + case EMR_SETMAPMODE : + { + sal_uInt32 nMapMode(0); + mpInputStream->ReadUInt32( nMapMode ); + SAL_INFO("emfio", "\t\tMapMode: 0x" << std::hex << nMapMode << std::dec); + SetMapMode( static_cast<MappingMode>(nMapMode) ); + } + break; + + case EMR_SETBKMODE : + { + mpInputStream->ReadUInt32( nDat32 ); + SAL_INFO("emfio", "\t\tBkMode: 0x" << std::hex << nDat32 << std::dec); + SetBkMode( static_cast<BackgroundMode>(nDat32) ); + } + break; + + case EMR_SETPOLYFILLMODE : + break; + + case EMR_SETROP2 : + { + mpInputStream->ReadUInt32( nDat32 ); + SAL_INFO("emfio", "\t\tROP2: 0x" << std::hex << nDat32 << std::dec); + SetRasterOp( static_cast<WMFRasterOp>(nDat32) ); + } + break; + + case EMR_SETSTRETCHBLTMODE : + { + mpInputStream->ReadUInt32( nStretchBltMode ); + SAL_INFO("emfio", "\t\tStretchBltMode: 0x" << std::hex << nDat32 << std::dec); + } + break; + + case EMR_SETTEXTALIGN : + { + mpInputStream->ReadUInt32( nDat32 ); + SAL_INFO("emfio", "\t\tTextAlign: 0x" << std::hex << nDat32 << std::dec); + SetTextAlign( nDat32 ); + } + break; + + case EMR_SETTEXTCOLOR : + { + SetTextColor( ReadColor() ); + } + break; + + case EMR_SETARCDIRECTION: + { + mpInputStream->ReadUInt32(nIndex); + SetArcDirection(nIndex == ARCDIRECTION_CLOCKWISE); + } + break; + + case EMR_SETBKCOLOR : + { + SetBkColor( ReadColor() ); + } + break; + + case EMR_OFFSETCLIPRGN : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + MoveClipRegion( Size( nX32, nY32 ) ); + } + break; + + case EMR_MOVETOEX : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + MoveTo( Point( nX32, nY32 ), mbRecordPath); + } + break; + + case EMR_INTERSECTCLIPRECT : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ); + IntersectClipRect( ReadRectangle( nX32, nY32, nx32, ny32 ) ); + SAL_INFO("emfio", "\t\tPoint: (" << nX32 << ", " << nY32 << ")"); + SAL_INFO("emfio", "\t\tPoint: (" << nx32 << ", " << ny32 << ")"); + } + break; + + case EMR_SAVEDC : + { + Push(); + } + break; + + case EMR_RESTOREDC : + { + sal_Int32 nSavedDC(0); + mpInputStream->ReadInt32( nSavedDC ); + SAL_INFO( "emfio", "\t\t SavedDC Index: " << nSavedDC ); + if ( nSavedDC < 0 ) + Pop( nSavedDC ); + else + Pop( -1 ); // For RestoreDC values above -1, treat as get last element + } + break; + + case EMR_SETWORLDTRANSFORM : + { + XForm aTempXForm; + *mpInputStream >> aTempXForm; + SetWorldTransform( aTempXForm ); + } + break; + + case EMR_MODIFYWORLDTRANSFORM : + { + sal_uInt32 nMode(0); + XForm aTempXForm; + *mpInputStream >> aTempXForm; + mpInputStream->ReadUInt32( nMode ); + ModifyWorldTransform( aTempXForm, static_cast<ModifyWorldTransformMode>(nMode) ); + } + break; + + case EMR_SELECTOBJECT : + { + mpInputStream->ReadUInt32( nIndex ); + SelectObject( nIndex ); + } + break; + + case EMR_CREATEPEN: + { + mpInputStream->ReadUInt32(nIndex); + if ((nIndex & ENHMETA_STOCK_OBJECT) == 0) + { + sal_uInt32 nStyle(0); + sal_Int32 nPenWidth(0), nIgnored; + mpInputStream->ReadUInt32(nStyle).ReadInt32(nPenWidth).ReadInt32(nIgnored); + SAL_INFO("emfio", "\t\tIndex: " << nIndex << " Style: 0x" << std::hex + << nStyle << std::dec + << " PenWidth: " << nPenWidth); + CreateObjectIndexed(nIndex, std::make_unique<WinMtfLineStyle>(ReadColor(), nStyle, nPenWidth)); + } + } + break; + + case EMR_EXTCREATEPEN: + { + mpInputStream->ReadUInt32(nIndex); + if ((nIndex & ENHMETA_STOCK_OBJECT) == 0) + { + sal_uInt32 offBmi, cbBmi, offBits, cbBits, nStyle, nWidth, nBrushStyle, elpNumEntries; + sal_Int32 elpHatch; + mpInputStream->ReadUInt32(offBmi).ReadUInt32(cbBmi).ReadUInt32(offBits).ReadUInt32(cbBits); + mpInputStream->ReadUInt32(nStyle).ReadUInt32(nWidth).ReadUInt32(nBrushStyle); + + SAL_INFO("emfio", "\t\tStyle: 0x" << std::hex << nStyle << std::dec); + SAL_INFO("emfio", "\t\tWidth: " << nWidth); + Color aColorRef = ReadColor(); + mpInputStream->ReadInt32(elpHatch).ReadUInt32(elpNumEntries); + + if (!mpInputStream->good()) + bStatus = false; + else + CreateObjectIndexed(nIndex, std::make_unique<WinMtfLineStyle>(aColorRef, nStyle, nWidth)); + } + } + break; + + case EMR_CREATEBRUSHINDIRECT : + { + mpInputStream->ReadUInt32( nIndex ); + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 ) + { + sal_uInt32 nStyle; + mpInputStream->ReadUInt32( nStyle ); + BrushStyle eStyle = static_cast<BrushStyle>(nStyle); + CreateObjectIndexed(nIndex, std::make_unique<WinMtfFillStyle>( ReadColor(), ( eStyle == BrushStyle::BS_HOLLOW ) )); + } + } + break; + + case EMR_DELETEOBJECT : + { + mpInputStream->ReadUInt32( nIndex ); + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 ) + DeleteObject( nIndex ); + } + break; + + case EMR_ELLIPSE : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ); + SAL_INFO("emfio", "\t\t Rectangle, left: " << nX32 << ", top: " << nY32 << ", right: " << nx32 << ", bottom: " << ny32); + + sal_Int32 w(0), h(0); + if (o3tl::checked_sub(nx32, nX32, w) || o3tl::checked_sub(ny32, nY32, h)) + SAL_WARN("emfio", "broken ellipse"); + else + { + tools::Long dw = w / 2; + tools::Long dh = h / 2; + Point aCenter( nX32 + dw, nY32 + dh ); + tools::Polygon aPoly( aCenter, dw, dh ); + DrawPolygon( std::move(aPoly), mbRecordPath ); + } + } + break; + + case EMR_RECTANGLE : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ); + SAL_INFO("emfio", "\t\t Rectangle, left: " << nX32 << ", top: " << nY32 << ", right: " << nx32 << ", bottom: " << ny32); + Point aPoints[] { Point(nX32, nY32), + Point(nx32, nY32), + Point(nx32, ny32), + Point(nX32, ny32) }; + tools::Polygon aPoly(4, aPoints); + aPoly.Optimize( PolyOptimizeFlags::CLOSE ); + DrawPolygon( std::move(aPoly), mbRecordPath ); + } + break; + + case EMR_ROUNDRECT : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadUInt32( nW ).ReadUInt32( nH ); + tools::Polygon aRoundRectPoly( ReadRectangle( nX32, nY32, nx32, ny32 ), nW, nH ); + DrawPolygon( std::move(aRoundRectPoly), mbRecordPath ); + } + break; + + case EMR_ARC : + case EMR_ARCTO : + case EMR_CHORD : + { + sal_Int32 nStartX, nStartY, nEndX, nEndY; + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadInt32( nStartX ).ReadInt32( nStartY ).ReadInt32( nEndX ).ReadInt32( nEndY ); + if (!mpInputStream->good()) + bStatus = false; + else + { + SAL_INFO( "emfio", "\t\t Bounds: " << nX32 << ":" << nY32 << ", " << nx32 << ":" << ny32 << ", Start: " << nStartX << ":" << nStartY << ", End: " << nEndX << ":" << nEndY ); + tools::Polygon aPoly(ReadRectangle(nX32, nY32, nx32, ny32), Point(nStartX, nStartY), Point(nEndX, nEndY), PolyStyle::Arc, IsArcDirectionClockWise()); + + if ( nRecType == EMR_CHORD ) + DrawPolygon( std::move(aPoly), mbRecordPath ); + else + DrawPolyLine( std::move(aPoly), nRecType == EMR_ARCTO, mbRecordPath ); + } + } + break; + + case EMR_PIE : + { + sal_Int32 nStartX, nStartY, nEndX, nEndY; + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ).ReadInt32( nx32 ).ReadInt32( ny32 ).ReadInt32( nStartX ).ReadInt32( nStartY ).ReadInt32( nEndX ).ReadInt32( nEndY ); + if (!mpInputStream->good()) + bStatus = false; + else + { + tools::Polygon aPoly(ReadRectangle(nX32, nY32, nx32, ny32), Point(nStartX, nStartY), Point(nEndX, nEndY), PolyStyle::Pie, IsArcDirectionClockWise()); + DrawPolygon( std::move(aPoly), mbRecordPath ); + } + } + break; + + case EMR_LINETO : + { + mpInputStream->ReadInt32( nX32 ).ReadInt32( nY32 ); + LineTo( Point( nX32, nY32 ), mbRecordPath); + } + break; + + case EMR_BEGINPATH : + { + ClearPath(); + mbRecordPath = true; + } + break; + + case EMR_ABORTPATH : + ClearPath(); + [[fallthrough]]; + case EMR_ENDPATH : + mbRecordPath = false; + break; + + case EMR_CLOSEFIGURE : + ClosePath(); + break; + + case EMR_FILLPATH : + StrokeAndFillPath( false, true ); + break; + + case EMR_STROKEANDFILLPATH : + StrokeAndFillPath( true, true ); + break; + + case EMR_STROKEPATH : + StrokeAndFillPath( true, false ); + break; + + case EMR_SELECTCLIPPATH : + { + sal_Int32 nClippingMode(0); + mpInputStream->ReadInt32(nClippingMode); + SetClipPath(GetPathObj(), static_cast<RegionMode>(nClippingMode), true); + } + break; + + case EMR_EXTSELECTCLIPRGN : + { + sal_uInt32 nRemainingRecSize = nRecSize - 8; + if (nRemainingRecSize < 8) + bStatus = false; + else + { + sal_Int32 nClippingMode(0), cbRgnData(0); + mpInputStream->ReadInt32(cbRgnData); + mpInputStream->ReadInt32(nClippingMode); + nRemainingRecSize -= 8; + + // This record's region data should be ignored if mode + // is RGN_COPY - see EMF spec section 2.3.2.2 + if (static_cast<RegionMode>(nClippingMode) == RegionMode::RGN_COPY) + { + SetDefaultClipPath(); + } + else + { + basegfx::B2DPolyPolygon aPolyPoly; + if (cbRgnData) + ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize); + const tools::PolyPolygon aPolyPolygon(aPolyPoly); + SetClipPath(aPolyPolygon, static_cast<RegionMode>(nClippingMode), false); + } + } + } + break; + + case EMR_ALPHABLEND: + { + sal_Int32 xDest(0), yDest(0), cxDest(0), cyDest(0); + + BLENDFUNCTION aFunc; + sal_Int32 xSrc(0), ySrc(0), cxSrc(0), cySrc(0); + XForm xformSrc; + sal_uInt32 BkColorSrc(0), iUsageSrc(0), offBmiSrc(0); + sal_uInt32 cbBmiSrc(0), offBitsSrc(0), cbBitsSrc(0); + + sal_uInt32 nStart = mpInputStream->Tell() - 8; + mpInputStream->SeekRel( 0x10 ); + + mpInputStream->ReadInt32( xDest ).ReadInt32( yDest ).ReadInt32( cxDest ).ReadInt32( cyDest ); + *mpInputStream >> aFunc; + mpInputStream->ReadInt32( xSrc ).ReadInt32( ySrc ); + *mpInputStream >> xformSrc; + mpInputStream->ReadUInt32( BkColorSrc ).ReadUInt32( iUsageSrc ).ReadUInt32( offBmiSrc ).ReadUInt32( cbBmiSrc ) + .ReadUInt32( offBitsSrc ).ReadUInt32( cbBitsSrc ).ReadInt32( cxSrc ).ReadInt32( cySrc ) ; + + if ( !mpInputStream->good() || + (cbBitsSrc > (SAL_MAX_UINT32 - 14)) || + ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc) || + cxDest == SAL_MAX_INT32 || cyDest == SAL_MAX_INT32 ) + { + bStatus = false; + } + else + { + tools::Rectangle aRect(Point(xDest, yDest), Size(cxDest + 1, cyDest + 1)); + + const sal_uInt32 nSourceSize = cbBmiSrc + cbBitsSrc + 14; + bool bSafeRead = nSourceSize <= (mnEndPos - mnStartPos); + sal_uInt32 nDeltaToDIB5HeaderSize(0); + const bool bReadAlpha(0x01 == aFunc.aAlphaFormat); + if (bSafeRead && bReadAlpha) + { + // we need to read alpha channel data if AlphaFormat of BLENDFUNCTION is + // AC_SRC_ALPHA (==0x01). To read it, create a temp DIB-File which is ready + // for DIB-5 format + const sal_uInt32 nHeaderSize = getDIBV5HeaderSize(); + if (cbBmiSrc > nHeaderSize) + bSafeRead = false; + else + nDeltaToDIB5HeaderSize = nHeaderSize - cbBmiSrc; + } + if (bSafeRead) + { + const sal_uInt32 nTargetSize(cbBmiSrc + nDeltaToDIB5HeaderSize + cbBitsSrc + 14); + char* pBuf = new char[ nTargetSize ]; + SvMemoryStream aTmp( pBuf, nTargetSize, StreamMode::READ | StreamMode::WRITE ); + + aTmp.ObjectOwnsMemory( true ); + + // write BM-Header (14 bytes) + aTmp.WriteUChar( 'B' ) + .WriteUChar( 'M' ) + .WriteUInt32( cbBitsSrc ) + .WriteUInt16( 0 ) + .WriteUInt16( 0 ) + .WriteUInt32( cbBmiSrc + nDeltaToDIB5HeaderSize + 14 ); + + // copy DIBInfoHeader from source (cbBmiSrc bytes) + mpInputStream->Seek( nStart + offBmiSrc ); + char* pWritePos = pBuf + 14; + auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc); + if (nRead != cbBmiSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBmiSrc - nRead); + } + + if (bReadAlpha) + { + // need to add values for all stuff that DIBV5Header is bigger + // than DIBInfoHeader, all values are correctly initialized to zero, + // so we can use memset here + memset(pBuf + cbBmiSrc + 14, 0, nDeltaToDIB5HeaderSize); + } + + // copy bitmap data from source (offBitsSrc bytes) + mpInputStream->Seek( nStart + offBitsSrc ); + pWritePos = pBuf + 14 + nDeltaToDIB5HeaderSize + cbBmiSrc; + nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc); + if (nRead != cbBitsSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBitsSrc - nRead); + } + + aTmp.Seek( 0 ); + + // prepare to read and fill BitmapEx + BitmapEx aBitmapEx; + + if(bReadAlpha) + { + Bitmap aBitmap; + AlphaMask aAlpha; + + if(ReadDIBV5(aBitmap, aAlpha, aTmp)) + { + aBitmapEx = BitmapEx(aBitmap, aAlpha); + } + } + else + { + Bitmap aBitmap; + + if(ReadDIB(aBitmap, aTmp, true)) + { + if(0xff != aFunc.aSrcConstantAlpha) + { + // add const alpha channel + aBitmapEx = BitmapEx( + aBitmap, + AlphaMask(aBitmap.GetSizePixel(), &aFunc.aSrcConstantAlpha)); + } + else + { + // just use Bitmap + aBitmapEx = BitmapEx(aBitmap); + } + } + } + + if(!aBitmapEx.IsEmpty()) + { + // test if it is sensible to crop + if (cxSrc > 0 && cySrc > 0 && xSrc >= 0 && ySrc >= 0) + { + sal_Int32 xEndSrc; + sal_Int32 yEndSrc; + if (!o3tl::checked_add(xSrc, cxSrc, xEndSrc) && xEndSrc < aBitmapEx.GetSizePixel().Width() && + !o3tl::checked_add(ySrc, cySrc, yEndSrc) && yEndSrc < aBitmapEx.GetSizePixel().Height()) + { + const tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) ); + aBitmapEx.Crop( aCropRect ); + } + } + + #ifdef DBG_UTIL + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + + if(bDoSaveForVisualControl) + { + SvFileStream aNew("c:\\metafile_content.png", StreamMode::WRITE|StreamMode::TRUNC); + vcl::PNGWriter aPNGWriter(aBitmapEx); + aPNGWriter.Write(aNew); + } + #endif + maBmpSaveList.emplace_back(aBitmapEx, aRect, SRCAND|SRCINVERT); + } + } + } + } + break; + + case EMR_BITBLT : // PASSTHROUGH INTENDED + case EMR_STRETCHBLT : + { + sal_Int32 xDest, yDest, cxDest, cyDest, xSrc, ySrc, cxSrc, cySrc; + sal_uInt32 dwRop, iUsageSrc, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc; + XForm xformSrc; + + sal_uInt32 nStart = mpInputStream->Tell() - 8; + + mpInputStream->SeekRel( 0x10 ); + mpInputStream->ReadInt32( xDest ).ReadInt32( yDest ).ReadInt32( cxDest ).ReadInt32( cyDest ).ReadUInt32( dwRop ).ReadInt32( xSrc ).ReadInt32( ySrc ) + >> xformSrc; + mpInputStream->ReadUInt32( nColor ).ReadUInt32( iUsageSrc ).ReadUInt32( offBmiSrc ).ReadUInt32( cbBmiSrc ) + .ReadUInt32( offBitsSrc ).ReadUInt32( cbBitsSrc ); + + if ( nRecType == EMR_STRETCHBLT ) + mpInputStream->ReadInt32( cxSrc ).ReadInt32( cySrc ); + else + cxSrc = cySrc = 0; + + if (!mpInputStream->good() || (cbBitsSrc > (SAL_MAX_UINT32 - 14)) || ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc)) + bStatus = false; + else + { + Bitmap aBitmap; + tools::Rectangle aRect(Point(xDest, yDest), Size(cxDest, cyDest)); + + sal_uInt32 nSize = cbBmiSrc + cbBitsSrc + 14; + if ( nSize <= ( mnEndPos - mnStartPos ) ) + { + char* pBuf = new char[ nSize ]; + SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE ); + aTmp.ObjectOwnsMemory( true ); + aTmp.WriteUChar( 'B' ) + .WriteUChar( 'M' ) + .WriteUInt32( cbBitsSrc ) + .WriteUInt16( 0 ) + .WriteUInt16( 0 ) + .WriteUInt32( cbBmiSrc + 14 ); + + mpInputStream->Seek( nStart + offBmiSrc ); + char* pWritePos = pBuf + 14; + auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc); + if (nRead != cbBmiSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBmiSrc - nRead); + } + + mpInputStream->Seek( nStart + offBitsSrc ); + pWritePos = pBuf + 14 + cbBmiSrc; + nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc); + if (nRead != cbBitsSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBitsSrc - nRead); + } + + aTmp.Seek( 0 ); + ReadDIB(aBitmap, aTmp, true); + + // test if it is sensible to crop + if ( (cxSrc > 0) && (cySrc > 0) && + (xSrc >= 0) && (ySrc >= 0) && + (aBitmap.GetSizePixel().Width() >= cxSrc) && + (xSrc <= aBitmap.GetSizePixel().Width() - cxSrc) && + (aBitmap.GetSizePixel().Height() >= cySrc) && + (ySrc <= aBitmap.GetSizePixel().Height() - cySrc) ) + { + tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) ); + aBitmap.Crop( aCropRect ); + } + + maBmpSaveList.emplace_back(aBitmap, aRect, dwRop); + } + } + } + break; + + case EMR_STRETCHDIBITS : + { + sal_Int32 xDest, yDest, xSrc, ySrc, cxSrc, cySrc, cxDest, cyDest; + sal_uInt32 offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc, iUsageSrc, dwRop; + sal_uInt32 nStart = mpInputStream->Tell() - 8; + + mpInputStream->SeekRel( 0x10 ); + mpInputStream->ReadInt32( xDest ) + .ReadInt32( yDest ) + .ReadInt32( xSrc ) + .ReadInt32( ySrc ) + .ReadInt32( cxSrc ) + .ReadInt32( cySrc ) + .ReadUInt32( offBmiSrc ) + .ReadUInt32( cbBmiSrc ) + .ReadUInt32( offBitsSrc ) + .ReadUInt32( cbBitsSrc ) + .ReadUInt32( iUsageSrc ) + .ReadUInt32( dwRop ) + .ReadInt32( cxDest ) + .ReadInt32( cyDest ); + + if (!mpInputStream->good() || + ((SAL_MAX_UINT32 - 14) < cbBitsSrc) || + ((SAL_MAX_UINT32 - 14) - cbBitsSrc < cbBmiSrc)) + { + bStatus = false; + } + else + { + Bitmap aBitmap; + tools::Rectangle aRect(xDest, yDest); + aRect.SaturatingSetSize(Size(cxDest, cyDest)); + + sal_uInt32 nSize = cbBmiSrc + cbBitsSrc + 14; + if ( nSize <= ( mnEndPos - mnStartPos ) ) + { + char* pBuf = new char[ nSize ]; + SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE ); + aTmp.ObjectOwnsMemory( true ); + aTmp.WriteUChar( 'B' ) + .WriteUChar( 'M' ) + .WriteUInt32( cbBitsSrc ) + .WriteUInt16( 0 ) + .WriteUInt16( 0 ) + .WriteUInt32( cbBmiSrc + 14 ); + + mpInputStream->Seek( nStart + offBmiSrc ); + char* pWritePos = pBuf + 14; + auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmiSrc); + if (nRead != cbBmiSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBmiSrc - nRead); + } + + mpInputStream->Seek( nStart + offBitsSrc ); + pWritePos = pBuf + 14 + cbBmiSrc; + nRead = mpInputStream->ReadBytes(pWritePos, cbBitsSrc); + if (nRead != cbBitsSrc) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBitsSrc - nRead); + } + + aTmp.Seek( 0 ); + ReadDIB(aBitmap, aTmp, true); + + const tools::Long nWidthDiff = aBitmap.GetSizePixel().Width() - cxSrc; + const tools::Long nHeightDiff = aBitmap.GetSizePixel().Height() - cySrc; + + // test if it is sensible to crop + if ( (cxSrc > 0) && (cySrc > 0) && + (xSrc >= 0) && (ySrc >= 0) && + (xSrc <= nWidthDiff) && (ySrc <= nHeightDiff) ) + { + tools::Rectangle aCropRect( Point( xSrc, ySrc ), Size( cxSrc, cySrc ) ); + aBitmap.Crop( aCropRect ); + } + maBmpSaveList.emplace_back(aBitmap, aRect, dwRop); + } + } + } + break; + + case EMR_EXTCREATEFONTINDIRECTW : + { + mpInputStream->ReadUInt32( nIndex ); + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 ) + { + LOGFONTW aLogFont; + mpInputStream->ReadInt32( aLogFont.lfHeight ) + .ReadInt32( aLogFont.lfWidth ) + .ReadInt32( aLogFont.lfEscapement ) + .ReadInt32( aLogFont.lfOrientation ) + .ReadInt32( aLogFont.lfWeight ) + .ReadUChar( aLogFont.lfItalic ) + .ReadUChar( aLogFont.lfUnderline ) + .ReadUChar( aLogFont.lfStrikeOut ) + .ReadUChar( aLogFont.lfCharSet ) + .ReadUChar( aLogFont.lfOutPrecision ) + .ReadUChar( aLogFont.lfClipPrecision ) + .ReadUChar( aLogFont.lfQuality ) + .ReadUChar( aLogFont.lfPitchAndFamily ); + + sal_Unicode lfFaceName[LF_FACESIZE+1]; + lfFaceName[LF_FACESIZE] = 0; + for (int i = 0; i < LF_FACESIZE; ++i) + { + sal_uInt16 nChar(0); + mpInputStream->ReadUInt16(nChar); + lfFaceName[i] = nChar; + } + aLogFont.alfFaceName = OUString( lfFaceName ); + + // #i123216# Not used in the test case of #121382# (always identity in XForm), also + // no hints in ms docu if FontSize should be scaled with WT. Using with the example + // from #i123216# creates errors, so removing. + + // // #i121382# Need to apply WorldTransform to FontHeight/Width; this should be completely + // // changed to basegfx::B2DHomMatrix instead of 'struct XForm', but not now due to time + // // constraints and dangers + // const XForm& rXF = GetWorldTransform(); + // const basegfx::B2DHomMatrix aWT(rXF.eM11, rXF.eM21, rXF.eDx, rXF.eM12, rXF.eM22, rXF.eDy); + // const basegfx::B2DVector aTransVec(aWT * basegfx::B2DVector(aLogFont.lfWidth, aLogFont.lfHeight)); + // aLogFont.lfWidth = aTransVec.getX(); + // aLogFont.lfHeight = aTransVec.getY(); + if (mpInputStream->good() && aLogFont.lfHeight != SAL_MIN_INT32 && aLogFont.lfWidth != SAL_MIN_INT32) + { + CreateObjectIndexed(nIndex, std::make_unique<WinMtfFontStyle>( aLogFont )); + } + } + } + break; + + case EMR_POLYTEXTOUTA : + case EMR_EXTTEXTOUTA : + bFlag = true; + [[fallthrough]]; + case EMR_POLYTEXTOUTW : + case EMR_EXTTEXTOUTW : + { + sal_Int32 nLeft, nTop, nRight, nBottom; + sal_uInt32 nGfxMode; + float nXScale, nYScale; + sal_uInt32 ncStrings( 1 ); + sal_Int32 ptlReferenceX, ptlReferenceY; + sal_uInt32 nLen, nOffString, nOptions, offDx; + sal_Int32 nLeftRect, nTopRect, nRightRect, nBottomRect; + + nCurPos = mpInputStream->Tell() - 8; + + mpInputStream->ReadInt32( nLeft ).ReadInt32( nTop ).ReadInt32( nRight ).ReadInt32( nBottom ) + .ReadUInt32( nGfxMode ).ReadFloat( nXScale ).ReadFloat( nYScale ); + SAL_INFO("emfio", "\t\tBounds: " << nLeft << ", " << nTop << ", " << nRight << ", " << nBottom); + SAL_INFO("emfio", "\t\tiGraphicsMode: 0x" << std::hex << nGfxMode << std::dec); + SAL_INFO("emfio", "\t\t Scale: " << nXScale << " x " << nYScale); + if ( ( nRecType == EMR_POLYTEXTOUTA ) || ( nRecType == EMR_POLYTEXTOUTW ) ) + { + mpInputStream->ReadUInt32( ncStrings ); + SAL_INFO("emfio", "\t\t Number of Text objects: " << ncStrings); + if ( ncStrings == 0 ) + break; + } + mpInputStream->ReadInt32( ptlReferenceX ).ReadInt32( ptlReferenceY ).ReadUInt32( nLen ).ReadUInt32( nOffString ).ReadUInt32( nOptions ); + SAL_INFO("emfio", "\t\tReference: (" << ptlReferenceX << ", " << ptlReferenceY << ")"); + + mpInputStream->ReadInt32( nLeftRect ).ReadInt32( nTopRect ).ReadInt32( nRightRect ).ReadInt32( nBottomRect ); + mpInputStream->ReadUInt32( offDx ); + + if (!mpInputStream->good()) + bStatus = false; + else + { + const tools::Rectangle aRect( nLeftRect, nTopRect, nRightRect, nBottomRect ); + const BackgroundMode mnBkModeBackup = mnBkMode; + if ( nOptions & ETO_NO_RECT ) // Don't draw the background rectangle and text background + mnBkMode = BackgroundMode::Transparent; + else if ( nOptions & ETO_OPAQUE ) + DrawRectWithBGColor( aRect ); + + // ETO_RTLREADING indicates that the characters are laid from right to left + vcl::text::ComplexTextLayoutFlags nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + if ( nOptions & ETO_RTLREADING ) + nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + SetTextLayoutMode( nTextLayoutMode ); + SAL_WARN_IF( ( nOptions & ( ETO_PDY | ETO_GLYPH_INDEX ) ) != 0, "emfio", "SJ: ETO_PDY || ETO_GLYPH_INDEX in EMF" ); + + Point aPos( ptlReferenceX, ptlReferenceY ); + bool bOffStringSane = nOffString <= mnEndPos - nCurPos; + if ( bOffStringSane ) + { + mpInputStream->Seek( nCurPos + nOffString ); + OUString aText; + if ( bFlag ) + { + if ( nLen <= ( mnEndPos - mpInputStream->Tell() ) ) + { + std::vector<char> pBuf( nLen ); + mpInputStream->ReadBytes(pBuf.data(), nLen); + aText = OUString(pBuf.data(), nLen, GetCharSet()); + } + } + else + { + if ( ( nLen * sizeof(sal_Unicode) ) <= ( mnEndPos - mpInputStream->Tell() ) ) + { + aText = read_uInt16s_ToOUString(*mpInputStream, nLen); + } + } + + SAL_INFO("emfio", "\t\tText: " << aText); + SAL_INFO("emfio", "\t\tDxBuffer:"); + + std::vector<sal_Int32> aDXAry; + std::unique_ptr<tools::Long[]> pDYAry; + + sal_Int32 nDxSize; + sal_Int32 nBytesEach; + + // Reading OutputDx + // ETO_PDY flag indicates that we should read twice values + // compared to the number of characters in the output string. + // Values are stored in an array of 32-bit unsigned integers + // named OutputDx, so there will be either 8 bytes or 4 bytes + // each depending on ETO_PDY is set or not. + if (nOptions & ETO_PDY) + nBytesEach = 8; + else + nBytesEach = 4; + + bool bOverflow = o3tl::checked_multiply<sal_Int32>(nLen, nBytesEach, nDxSize); + if (!bOverflow && offDx && ((nCurPos + offDx + nDxSize) <= nNextPos ) && nNextPos <= mnEndPos) + { + mpInputStream->Seek( nCurPos + offDx ); + aDXAry.resize(aText.getLength()); + if (nOptions & ETO_PDY) + { + pDYAry.reset( new tools::Long[aText.getLength()] ); + } + + for (sal_Int32 i = 0; i < aText.getLength(); ++i) + { + sal_Int32 nDxCount = 1; + if ( static_cast<sal_uInt32>( aText.getLength() ) != nLen ) + { + sal_Unicode cUniChar = aText[i]; + OString aTmp(&cUniChar, 1, GetCharSet()); + if (aTmp.getLength() > 1) + { + nDxCount = aTmp.getLength(); + } + } + + aDXAry[i] = 0; + if (nOptions & ETO_PDY) + { + pDYAry[i] = 0; + } + + while (nDxCount--) + { + sal_Int32 nDxTmp = 0; + mpInputStream->ReadInt32(nDxTmp); + aDXAry[i] = o3tl::saturating_add(aDXAry[i], nDxTmp); + if (nOptions & ETO_PDY) + { + sal_Int32 nDyTmp = 0; + mpInputStream->ReadInt32(nDyTmp); + pDYAry[i] += nDyTmp; + } + } + + SAL_INFO("emfio", "\t\t\tSpacing " << i << ": " << aDXAry[i]); + } + } + if ( nOptions & ETO_CLIPPED ) + { + Push(); // Save the current clip. It will be restored after text drawing + IntersectClipRect( aRect ); + } + DrawText(aPos, aText, aDXAry.empty() ? nullptr : &aDXAry, pDYAry.get(), mbRecordPath, static_cast<GraphicsMode>(nGfxMode)); + if ( nOptions & ETO_CLIPPED ) + Pop(); + } + mnBkMode = mnBkModeBackup; + } + } + break; + + case EMR_POLYBEZIERTO16 : + DrawPolyBezier(ReadPolygonWithSkip<sal_Int16>(true, nNextPos), true, mbRecordPath); + break; + + case EMR_POLYBEZIER16 : + DrawPolyBezier(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), false, mbRecordPath); + break; + + case EMR_POLYGON16 : + DrawPolygon(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), mbRecordPath); + break; + + case EMR_POLYLINETO16 : + DrawPolyLine(ReadPolygonWithSkip<sal_Int16>(true, nNextPos), true, mbRecordPath); + break; + + case EMR_POLYLINE16 : + DrawPolyLine(ReadPolygonWithSkip<sal_Int16>(false, nNextPos), false, mbRecordPath); + break; + + case EMR_POLYPOLYLINE16 : + ReadAndDrawPolyLine<sal_Int16>(nNextPos); + break; + + case EMR_POLYPOLYGON16 : + ReadAndDrawPolyPolygon<sal_Int16>(nNextPos); + break; + + case EMR_FILLRGN : + { + sal_uInt32 nRemainingRecSize = nRecSize - 8; + if (nRemainingRecSize < 24) + bStatus = false; + else + { + sal_uInt32 nRgnDataSize; + basegfx::B2DPolyPolygon aPolyPoly; + mpInputStream->SeekRel(16); // RectL bounds + mpInputStream->ReadUInt32( nRgnDataSize ).ReadUInt32( nIndex ); + nRemainingRecSize -= 24; + + if (ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize)) + { + Push(); + SelectObject( nIndex ); + tools::PolyPolygon aPolyPolygon(aPolyPoly); + DrawPolyPolygon( aPolyPolygon ); + Pop(); + } + } + } + break; + + case EMR_PAINTRGN : + { + sal_uInt32 nRemainingRecSize = nRecSize - 8; + if (nRemainingRecSize < 20) + bStatus = false; + else + { + sal_uInt32 nRgnDataSize; + basegfx::B2DPolyPolygon aPolyPoly; + mpInputStream->SeekRel(16); // Skipping RectL bounds + mpInputStream->ReadUInt32( nRgnDataSize ); + nRemainingRecSize -= 20; + + if (ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize)) + { + tools::PolyPolygon aPolyPolygon(aPolyPoly); + DrawPolyPolygon( aPolyPolygon ); + } + } + } + break; + + case EMR_CREATEDIBPATTERNBRUSHPT : + { + sal_uInt32 nStart = mpInputStream->Tell() - 8; + Bitmap aBitmap; + + mpInputStream->ReadUInt32( nIndex ); + + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 ) + { + sal_uInt32 usage, offBmi, cbBmi, offBits, cbBits; + + mpInputStream->ReadUInt32( usage ); + mpInputStream->ReadUInt32( offBmi ); + mpInputStream->ReadUInt32( cbBmi ); + mpInputStream->ReadUInt32( offBits ); + mpInputStream->ReadUInt32( cbBits ); + + if ( !mpInputStream->good() || (cbBits > (SAL_MAX_UINT32 - 14)) || ((SAL_MAX_UINT32 - 14) - cbBits < cbBmi) ) + bStatus = false; + else if ( offBmi ) + { + sal_uInt32 nSize = cbBmi + cbBits + 14; + if ( nSize <= ( mnEndPos - mnStartPos ) ) + { + char* pBuf = new char[ nSize ]; + + SvMemoryStream aTmp( pBuf, nSize, StreamMode::READ | StreamMode::WRITE ); + aTmp.ObjectOwnsMemory( true ); + aTmp.WriteUChar( 'B' ) + .WriteUChar( 'M' ) + .WriteUInt32( cbBits ) + .WriteUInt16( 0 ) + .WriteUInt16( 0 ) + .WriteUInt32( cbBmi + 14 ); + + mpInputStream->Seek( nStart + offBmi ); + char* pWritePos = pBuf + 14; + auto nRead = mpInputStream->ReadBytes(pWritePos, cbBmi); + if (nRead != cbBmi) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBmi - nRead); + } + + mpInputStream->Seek( nStart + offBits ); + pWritePos = pBuf + 14 + cbBmi; + nRead = mpInputStream->ReadBytes(pWritePos, cbBits); + if (nRead != cbBits) + { + // zero remainder if short read + memset(pWritePos + nRead, 0, cbBits - nRead); + } + + aTmp.Seek( 0 ); + ReadDIB(aBitmap, aTmp, true); + } + } + } + + CreateObjectIndexed(nIndex, std::make_unique<WinMtfFillStyle>( aBitmap )); + } + break; + + case EMR_MASKBLT : + case EMR_PLGBLT : + case EMR_SETDIBITSTODEVICE : + case EMR_FRAMERGN : + case EMR_INVERTRGN : + case EMR_FLATTENPATH : + case EMR_WIDENPATH : + case EMR_POLYDRAW : + case EMR_SETPALETTEENTRIES : + case EMR_RESIZEPALETTE : + case EMR_EXTFLOODFILL : + case EMR_ANGLEARC : + case EMR_SETCOLORADJUSTMENT : + case EMR_POLYDRAW16 : + case EMR_CREATECOLORSPACE : + case EMR_SETCOLORSPACE : + case EMR_DELETECOLORSPACE : + case EMR_GLSRECORD : + case EMR_GLSBOUNDEDRECORD : + case EMR_PIXELFORMAT : + case EMR_DRAWESCAPE : + case EMR_EXTESCAPE : + case EMR_STARTDOC : + case EMR_SMALLTEXTOUT : + case EMR_FORCEUFIMAPPING : + case EMR_NAMEDESCAPE : + case EMR_COLORCORRECTPALETTE : + case EMR_SETICMPROFILEA : + case EMR_SETICMPROFILEW : + case EMR_TRANSPARENTBLT : + case EMR_TRANSPARENTDIB : + case EMR_GRADIENTFILL : + case EMR_SETLINKEDUFIS : + case EMR_SETMAPPERFLAGS : + case EMR_SETICMMODE : + case EMR_CREATEMONOBRUSH : + case EMR_SETBRUSHORGEX : + case EMR_SETMETARGN : + case EMR_SETMITERLIMIT : + case EMR_EXCLUDECLIPRECT : + case EMR_REALIZEPALETTE : + case EMR_SELECTPALETTE : + case EMR_CREATEPALETTE : + case EMR_ALPHADIBBLEND : + case EMR_SETTEXTJUSTIFICATION : + { + SAL_WARN("emfio", "TODO: EMF record not implemented: " << record_type_name(nRecType)); + } + break; + + case EMR_COMMENT : + case EMR_HEADER : // has already been read at ReadHeader() + break; + + default : SAL_INFO("emfio", "Unknown Meta Action"); break; + } + } + mpInputStream->Seek( nNextPos ); + } + + // tdf#127471 + maScaledFontHelper.applyAlternativeFontScale(); + + if( !maBmpSaveList.empty() ) + ResolveBitmapActions( maBmpSaveList ); + + if ( bStatus ) + mpInputStream->Seek(mnEndPos); + + return bStatus; + }; + + bool EmfReader::ReadHeader() + { + // Spare me the METAFILEHEADER here + // Reading the METAHEADER - EMR_HEADER ([MS-EMF] section 2.3.4.2 EMR_HEADER Record Types) + sal_uInt32 nType(0), nHeaderSize(0); + mpInputStream->ReadUInt32(nType).ReadUInt32(nHeaderSize); + SAL_INFO ("emfio", "0x0-0x" << std::hex << nHeaderSize << " " << record_type_name(nType) << " size: " << std::dec << nHeaderSize); + if (nType != 0x00000001) + { + // per [MS-EMF] 2.3.4.2 EMF Header Record Types, type MUST be 0x00000001 + SAL_WARN("emfio", "EMF header type is not set to 0x00000001 - possibly corrupted file?"); + return false; + } + + // Start reading the EMR_HEADER Header object + + // bound size (RectL object, see [MS-WMF] section 2.2.2.19) + SAL_INFO("emfio", "\tBounding rectangle"); + tools::Rectangle rclBounds = ReadRectangle(); // rectangle in logical units + + // picture frame size (RectL object) + SAL_INFO("emfio", "\tPicture frame"); + tools::Rectangle rclFrame = ReadRectangle(); // rectangle in device units 1/100th mm + + sal_uInt32 nSignature(0); + mpInputStream->ReadUInt32(nSignature); + SAL_INFO("emfio", "\tSignature: 0x" << std::hex << nSignature << std::dec); + + // nSignature MUST be the ASCII characters "FME", see [WS-EMF] 2.2.9 Header Object + // and 2.1.14 FormatSignature Enumeration + if (nSignature != 0x464d4520) + { + SAL_WARN("emfio", "EMF\t\tSignature is not 0x464d4520 (\"FME\") - possibly corrupted file?"); + return false; + } + + sal_uInt32 nVersion(0); + mpInputStream->ReadUInt32(nVersion); // according to [WS-EMF] 2.2.9, this SHOULD be 0x0001000, however + // Microsoft note that not even Windows checks this... + SAL_INFO("emfio", "\tVersion: 0x" << std::hex << nVersion << std::dec); + if (nVersion != 0x00010000) + { + SAL_WARN("emfio", "EMF\t\tThis really should be 0x00010000, though not absolutely essential..."); + } + + mpInputStream->ReadUInt32(mnEndPos); // size of metafile + SAL_INFO("emfio", "\tMetafile size: " << mnEndPos); + mnEndPos += mnStartPos; + + sal_uInt32 nStrmPos = mpInputStream->Tell(); // checking if mnEndPos is valid + sal_uInt32 nActualFileSize = nStrmPos + mpInputStream->remainingSize(); + + if ( nActualFileSize < mnEndPos ) + { + SAL_WARN("emfio", "EMF\t\tEMF Header object records number of bytes as " << mnEndPos + << ", however the file size is actually " << nActualFileSize + << " bytes. Possible file corruption?"); + mnEndPos = nActualFileSize; + } + + mpInputStream->ReadUInt32(mnRecordCount); + SAL_INFO("emfio", "\tRecords: " << mnRecordCount); + + // the number of "handles", or graphics objects used in the metafile + + sal_uInt16 nHandlesCount; + mpInputStream->ReadUInt16(nHandlesCount); + SAL_INFO("emfio", "\tGraphics: " << nHandlesCount); + + // the next 2 bytes are reserved, but according to [MS-EMF] section 2.2.9 + // it MUST be 0x000 and MUST be ignored... the thing is, having such a specific + // value is actually pretty useful in checking if there is possible corruption + + sal_uInt16 nReserved(0); + mpInputStream->ReadUInt16(nReserved); + SAL_INFO("emfio", "\tReserved: 0x" << std::hex << nReserved << std::dec); + + if ( nReserved != 0x0000 ) + { + SAL_WARN("emfio", "EMF\t\tEMF Header object's reserved field is NOT 0x0000... possible " + "corruption?"); + } + + // The next 4 bytes specifies the number of characters in the metafile description. + // The 4 bytes after that specific the offset from this record that contains the + // metafile description... zero means no description string. + // For now, we ignore it. + + mpInputStream->SeekRel(0x8); + + sal_uInt32 nPalEntries(0); + mpInputStream->ReadUInt32(nPalEntries); + SAL_INFO("emfio", "\tPalette entries: " << nPalEntries); + sal_Int32 nPixX(0), nPixY(0), nMillX(0), nMillY(0); + mpInputStream->ReadInt32(nPixX); + mpInputStream->ReadInt32(nPixY); + SAL_INFO("emfio", "\tRef (pixels): " << nPixX << ", " << nPixY); + mpInputStream->ReadInt32(nMillX); + mpInputStream->ReadInt32(nMillY); + SAL_INFO("emfio", "\tRef (mm): " << nMillX << ", " << nMillY); + + SetrclFrame(rclFrame); + SetrclBounds(rclBounds); + SetRefPix(Size( nPixX, nPixY ) ); + SetRefMill(Size( nMillX, nMillY ) ); + + return checkSeek(*mpInputStream, mnStartPos + nHeaderSize); + } + + tools::Rectangle EmfReader::ReadRectangle() + { + sal_Int32 nLeft(0), nTop(0), nRight(0), nBottom(0); + mpInputStream->ReadInt32(nLeft); + mpInputStream->ReadInt32(nTop); + mpInputStream->ReadInt32(nRight); + mpInputStream->ReadInt32(nBottom); + + SAL_INFO("emfio", "\t\tLeft: " << nLeft << ", top: " << nTop << ", right: " << nRight << ", bottom: " << nBottom); + if (nLeft > nRight || nTop > nBottom) + { + SAL_WARN("emfio", "broken rectangle"); + return tools::Rectangle::Justify(Point(nLeft, nTop), Point(nRight, nBottom)); + } + + return tools::Rectangle(nLeft, nTop, nRight, nBottom); + } + + tools::Rectangle EmfReader::ReadRectangle( sal_Int32 x1, sal_Int32 y1, sal_Int32 x2, sal_Int32 y2 ) + { + Point aTL(x1, y1); + Point aBR(o3tl::saturating_add<sal_Int32>(x2, -1), o3tl::saturating_add<sal_Int32>(y2, -1)); + return tools::Rectangle(aTL, aBR); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/source/reader/mtftools.cxx b/emfio/source/reader/mtftools.cxx new file mode 100644 index 000000000..85429a501 --- /dev/null +++ b/emfio/source/reader/mtftools.cxx @@ -0,0 +1,2528 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <mtftools.hxx> + +#include <cstdlib> +#include <memory> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <vcl/metric.hxx> +#include <vcl/graphictools.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/svapp.hxx> +#include <tools/stream.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <vcl/virdev.hxx> +#include <o3tl/safeint.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/defaultencoding.hxx> +#include <unotools/wincodepage.hxx> + +#if OSL_DEBUG_LEVEL > 1 +#define EMFP_DEBUG(x) x +#else +#define EMFP_DEBUG(x) +#endif + +namespace emfio +{ + SvStream& operator >> (SvStream& rInStream, XForm& rXForm) + { + if (sizeof(float) != 4) + { + OSL_FAIL("EmfReader::sizeof( float ) != 4"); + rXForm = XForm(); + } + else + { + rInStream.ReadFloat(rXForm.eM11); + rInStream.ReadFloat(rXForm.eM12); + rInStream.ReadFloat(rXForm.eM21); + rInStream.ReadFloat(rXForm.eM22); + rInStream.ReadFloat(rXForm.eDx); + rInStream.ReadFloat(rXForm.eDy); + } + return rInStream; + } + + void WinMtfClipPath::intersectClip( const basegfx::B2DPolyPolygon& rPolyPolygon ) + { + maClip.intersectPolyPolygon(rPolyPolygon); + } + + void WinMtfClipPath::excludeClip( const basegfx::B2DPolyPolygon& rPolyPolygon ) + { + maClip.subtractPolyPolygon(rPolyPolygon); + } + + void WinMtfClipPath::setClipPath( const basegfx::B2DPolyPolygon& rB2DPoly, RegionMode nClippingMode ) + { + switch ( nClippingMode ) + { + case RegionMode::RGN_OR : + maClip.unionPolyPolygon(rB2DPoly); + break; + case RegionMode::RGN_XOR : + maClip.xorPolyPolygon(rB2DPoly); + break; + case RegionMode::RGN_DIFF : + maClip.subtractPolyPolygon(rB2DPoly); + break; + case RegionMode::RGN_AND : + maClip.intersectPolyPolygon(rB2DPoly); + break; + case RegionMode::RGN_COPY : + maClip = basegfx::utils::B2DClipState(rB2DPoly); + break; + } + } + + void WinMtfClipPath::moveClipRegion( const Size& rSize ) + { + basegfx::B2DHomMatrix aTranslate; + aTranslate.translate(rSize.Width(), rSize.Height()); + maClip.transform(aTranslate); + } + + void WinMtfClipPath::setDefaultClipPath() + { + // Empty clip region - everything visible + maClip = basegfx::utils::B2DClipState(); + } + + basegfx::B2DPolyPolygon const & WinMtfClipPath::getClipPath() const + { + return maClip.getClipPoly(); + } + + void WinMtfPathObj::AddPoint( const Point& rPoint ) + { + if ( bClosed ) + Insert( tools::Polygon() ); + tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ]; + rPoly.Insert( rPoly.GetSize(), rPoint ); + bClosed = false; + } + + void WinMtfPathObj::AddPolyLine( const tools::Polygon& rPolyLine ) + { + if ( bClosed ) + Insert( tools::Polygon() ); + tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ]; + rPoly.Insert( rPoly.GetSize(), rPolyLine ); + bClosed = false; + } + + void WinMtfPathObj::AddPolygon( const tools::Polygon& rPoly ) + { + Insert( rPoly ); + bClosed = true; + } + + void WinMtfPathObj::AddPolyPolygon( const tools::PolyPolygon& rPolyPoly ) + { + sal_uInt16 i, nCount = rPolyPoly.Count(); + for ( i = 0; i < nCount; i++ ) + Insert( rPolyPoly[ i ] ); + bClosed = true; + } + + void WinMtfPathObj::ClosePath() + { + if ( Count() ) + { + tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ]; + if ( rPoly.GetSize() > 2 ) + { + Point aFirst( rPoly[ 0 ] ); + if ( aFirst != rPoly[ rPoly.GetSize() - 1 ] ) + rPoly.Insert( rPoly.GetSize(), aFirst ); + } + } + bClosed = true; + } + + WinMtfFontStyle::WinMtfFontStyle( LOGFONTW const & rFont ) + { + rtl_TextEncoding eCharSet; + if ((rFont.alfFaceName == "Symbol") + || (rFont.alfFaceName == "MT Extra")) + eCharSet = RTL_TEXTENCODING_SYMBOL; + else if ((rFont.lfCharSet == DEFAULT_CHARSET) || (rFont.lfCharSet == OEM_CHARSET)) + eCharSet = utl_getWinTextEncodingFromLangStr(utl_getLocaleForGlobalDefaultEncoding(), + rFont.lfCharSet == OEM_CHARSET); + else + eCharSet = rtl_getTextEncodingFromWindowsCharset( rFont.lfCharSet ); + if ( eCharSet == RTL_TEXTENCODING_DONTKNOW ) + eCharSet = RTL_TEXTENCODING_MS_1252; + aFont.SetCharSet( eCharSet ); + aFont.SetFamilyName( rFont.alfFaceName ); + FontFamily eFamily; + switch ( rFont.lfPitchAndFamily >> 4 & 0x0f ) + { + case FamilyFont::FF_ROMAN: + eFamily = FAMILY_ROMAN; + break; + + case FamilyFont::FF_SWISS: + eFamily = FAMILY_SWISS; + break; + + case FamilyFont::FF_MODERN: + eFamily = FAMILY_MODERN; + break; + + case FamilyFont::FF_SCRIPT: + eFamily = FAMILY_SCRIPT; + break; + + case FamilyFont::FF_DECORATIVE: + eFamily = FAMILY_DECORATIVE; + break; + + default: + eFamily = FAMILY_DONTKNOW; + break; + } + aFont.SetFamily( eFamily ); + + FontPitch ePitch; + switch ( rFont.lfPitchAndFamily & 0x0f ) + { + case FIXED_PITCH: + ePitch = PITCH_FIXED; + break; + + case DEFAULT_PITCH: + case VARIABLE_PITCH: + default: + ePitch = PITCH_VARIABLE; + break; + } + aFont.SetPitch( ePitch ); + + FontWeight eWeight; + if (rFont.lfWeight == 0) // default weight SHOULD be used + eWeight = WEIGHT_DONTKNOW; + else if (rFont.lfWeight <= FW_THIN) + eWeight = WEIGHT_THIN; + else if( rFont.lfWeight <= FW_ULTRALIGHT ) + eWeight = WEIGHT_ULTRALIGHT; + else if( rFont.lfWeight <= FW_LIGHT ) + eWeight = WEIGHT_LIGHT; + else if( rFont.lfWeight < FW_MEDIUM ) + eWeight = WEIGHT_NORMAL; + else if( rFont.lfWeight == FW_MEDIUM ) + eWeight = WEIGHT_MEDIUM; + else if( rFont.lfWeight <= FW_SEMIBOLD ) + eWeight = WEIGHT_SEMIBOLD; + else if( rFont.lfWeight <= FW_BOLD ) + eWeight = WEIGHT_BOLD; + else if( rFont.lfWeight <= FW_ULTRABOLD ) + eWeight = WEIGHT_ULTRABOLD; + else + eWeight = WEIGHT_BLACK; + aFont.SetWeight( eWeight ); + + if( rFont.lfItalic ) + aFont.SetItalic( ITALIC_NORMAL ); + + if( rFont.lfUnderline ) + aFont.SetUnderline( LINESTYLE_SINGLE ); + + if( rFont.lfStrikeOut ) + aFont.SetStrikeout( STRIKEOUT_SINGLE ); + + aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rFont.lfEscapement)) ); + + Size aFontSize( Size( rFont.lfWidth, rFont.lfHeight ) ); + if ( rFont.lfHeight > 0 ) + { + // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading + SolarMutexGuard aGuard; + ScopedVclPtrInstance< VirtualDevice > pVDev; + // converting the cell height into a font height + aFont.SetFontSize( aFontSize ); + pVDev->SetFont( aFont ); + FontMetric aMetric( pVDev->GetFontMetric() ); + tools::Long nHeight = aMetric.GetAscent() + aMetric.GetDescent(); + if (nHeight) + { + double fHeight = (static_cast<double>(aFontSize.Height()) * rFont.lfHeight ) / nHeight; + aFontSize.setHeight( static_cast<sal_Int32>( fHeight + 0.5 ) ); + } + } + + // Convert height to positive + aFontSize.setHeight( std::abs(aFontSize.Height()) ); + aFont.SetFontSize(aFontSize); + + // tdf#127471 adapt nFontWidth from Windows-like notation to + // NormedFontScaling if used for text scaling +#ifndef _WIN32 + const bool bFontScaledHorizontally(aFontSize.Width() != 0 && aFontSize.Width() != aFontSize.Height()); + + if(bFontScaledHorizontally) + { + // tdf#127471 nFontWidth is the Windows FontScaling, need to convert to + // Non-Windowslike notation relative to FontHeight. + const tools::Long nAverageFontWidth(aFont.GetOrCalculateAverageFontWidth()); + + if(nAverageFontWidth > 0) + { + const double fScaleFactor(static_cast<double>(aFontSize.Height()) / static_cast<double>(nAverageFontWidth)); + aFont.SetAverageFontWidth(static_cast<tools::Long>(static_cast<double>(aFontSize.Width()) * fScaleFactor)); + } + } +#endif + }; + + // tdf#127471 + ScaledFontDetectCorrectHelper::ScaledFontDetectCorrectHelper() + { + } + + void ScaledFontDetectCorrectHelper::endCurrentMetaFontAction() + { + if(maCurrentMetaFontAction.is() && !maAlternativeFontScales.empty()) + { + // create average corrected FontScale value and count + // positive/negative hits + sal_uInt32 nPositive(0); + sal_uInt32 nNegative(0); + double fAverage(0.0); + + for(double fPart : maAlternativeFontScales) + { + if(fPart < 0.0) + { + nNegative++; + fAverage += -fPart; + } + else + { + nPositive++; + fAverage += fPart; + } + } + + fAverage /= static_cast<double>(maAlternativeFontScales.size()); + + if(nPositive >= nNegative) + { + // correction intended, it is probably an old imported file + maPositiveIdentifiedCases.push_back(std::pair<rtl::Reference<MetaFontAction>, double>(maCurrentMetaFontAction, fAverage)); + } + else + { + // correction not favorable in the majority of cases for this Font, still + // remember to have a weight in the last decision for correction + maNegativeIdentifiedCases.push_back(std::pair<rtl::Reference<MetaFontAction>, double>(maCurrentMetaFontAction, fAverage)); + } + } + + maCurrentMetaFontAction.clear(); + maAlternativeFontScales.clear(); + } + + void ScaledFontDetectCorrectHelper::newCurrentMetaFontAction(const rtl::Reference<MetaFontAction>& rNewMetaFontAction) + { + maCurrentMetaFontAction.clear(); + maAlternativeFontScales.clear(); + + if(!rNewMetaFontAction.is()) + return; + + // check 1st criteria for FontScale active. We usually write this, + // so this will already sort out most situations + const vcl::Font& rCandidate(rNewMetaFontAction->GetFont()); + + if(0 != rCandidate.GetAverageFontWidth()) + { + const tools::Long nUnscaledAverageFontWidth(rCandidate.GetOrCalculateAverageFontWidth()); + + // check 2nd (system-dependent) criteria for FontScale + if(nUnscaledAverageFontWidth != rCandidate.GetFontHeight()) + { + // FontScale is active, remember and use as current + maCurrentMetaFontAction = rNewMetaFontAction; + } + } + } + + void ScaledFontDetectCorrectHelper::evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength) + { + if(!maCurrentMetaFontAction.is()) + return; + + SolarMutexGuard aGuard; // VirtualDevice is not thread-safe + ScopedVclPtrInstance< VirtualDevice > pTempVirtualDevice; + + // calculate measured TextLength + const vcl::Font& rFontCandidate(maCurrentMetaFontAction->GetFont()); + pTempVirtualDevice->SetFont(rFontCandidate); + tools::Long nMeasuredTextLength(pTempVirtualDevice->GetTextWidth(rText)); + // on failure, use original length + if (!nMeasuredTextLength) + nMeasuredTextLength = nImportedTextLength; + + // compare expected and imported TextLengths + if (nImportedTextLength == nMeasuredTextLength) + return; + + const double fFactorText(static_cast<double>(nImportedTextLength) / static_cast<double>(nMeasuredTextLength)); + const double fFactorTextPercent(fabs(1.0 - fFactorText) * 100.0); + + // if we assume that loaded file was written on old linux, we have to + // back-convert the scale value depending on which system we run +#ifdef _WIN32 + // When running on Windows the value was not adapted at font import (see WinMtfFontStyle + // constructor), so it is still NormedFontScaling and we need to convert to Windows-style + // scaling +#else + // When running on unx (non-Windows) the value was already adapted at font import (see WinMtfFontStyle + // constructor). It was wrongly assumed to be Windows-style FontScaling, so we need to revert that + // to get back to the needed unx-style FontScale +#endif + // Interestingly this leads to the *same* correction, so no need to make this + // system-dependent (!) + const tools::Long nUnscaledAverageFontWidth(rFontCandidate.GetOrCalculateAverageFontWidth()); + const tools::Long nScaledAverageFontWidth(rFontCandidate.GetAverageFontWidth()); + const double fScaleFactor(static_cast<double>(nUnscaledAverageFontWidth) / static_cast<double>(rFontCandidate.GetFontHeight())); + const double fCorrectedAverageFontWidth(static_cast<double>(nScaledAverageFontWidth) * fScaleFactor); + tools::Long nCorrectedTextLength(0); + + { // do in own scope, only need nUnscaledAverageFontWidth + vcl::Font rFontCandidate2(rFontCandidate); + rFontCandidate2.SetAverageFontWidth(static_cast<tools::Long>(fCorrectedAverageFontWidth)); + pTempVirtualDevice->SetFont(rFontCandidate2); + nCorrectedTextLength = pTempVirtualDevice->GetTextWidth(rText); + // on failure, use original length + if (!nCorrectedTextLength) + nCorrectedTextLength = nImportedTextLength; + } + + const double fFactorCorrectedText(static_cast<double>(nImportedTextLength) / static_cast<double>(nCorrectedTextLength)); + const double fFactorCorrectedTextPercent(fabs(1.0 - fFactorCorrectedText) * 100.0); + + // If FactorCorrectedText fits better than FactorText this is probably + // an import of an old EMF/WMF written by LibreOffice on a non-Windows (unx) system + // and should be corrected. + // Usually in tested cases this lies inside 5% of range, so detecting this just using + // fFactorTextPercent inside 5% -> no old file + // fFactorCorrectedTextPercent inside 5% -> is old file + // works not too bad, but there are some strange not so often used fonts where that + // values do deviate, so better just compare if old corrected would fit better than + // the uncorrected case, that is usually safe. + if(fFactorCorrectedTextPercent < fFactorTextPercent) + { + maAlternativeFontScales.push_back(fCorrectedAverageFontWidth); + } + else + { + // also push, but negative to remember non-fitting case + maAlternativeFontScales.push_back(-fCorrectedAverageFontWidth); + } + } + + void ScaledFontDetectCorrectHelper::applyAlternativeFontScale() + { + // make sure last evtl. detected current FontAction gets added to identified cases + endCurrentMetaFontAction(); + + // Take final decision to correct FontScaling for this imported Metafile or not. + // It is possible to weight positive against negative cases, so to only finally + // correct when more positive cases were detected. + // But that would be inconsequent and wrong. *If* the detected case is an old import + // the whole file was written with wrong FontScale values and all Font actions + // need to be corrected. Thus, for now, correct all when there are/is positive + // cases detected. + // On the other hand it *may* be that for some strange fonts there is a false-positive + // in the positive cases, so at least insist on positive cases being more than negative. + // Still, do then correct *all* cases. + if(!maPositiveIdentifiedCases.empty() + && maPositiveIdentifiedCases.size() >= maNegativeIdentifiedCases.size()) + { + for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maPositiveIdentifiedCases) + { + rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second)); + } + for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maNegativeIdentifiedCases) + { + rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second)); + } + } + + maPositiveIdentifiedCases.clear(); + maNegativeIdentifiedCases.clear(); + } + + Color MtfTools::ReadColor() + { + sal_uInt32 nColor(0); + mpInputStream->ReadUInt32( nColor ); + Color aColor( COL_BLACK ); + if ( ( nColor & 0xFFFF0000 ) == 0x01000000 ) + { + size_t index = nColor & 0x0000FFFF; + if ( index < maPalette.aPaletteColors.size() ) + aColor = maPalette.aPaletteColors[ index ]; + else + SAL_INFO( "emfio", "\t\t Palette index out of range: " << index ); + } + else + aColor = Color( static_cast<sal_uInt8>( nColor ), static_cast<sal_uInt8>( nColor >> 8 ), static_cast<sal_uInt8>( nColor >> 16 ) ); + + SAL_INFO("emfio", "\t\tColor: " << aColor); + return aColor; + }; + + Point MtfTools::ImplScale(const Point& rPoint) // Hack to set varying defaults for incompletely defined files. + { + if (!mbIsMapDevSet) + return Point(rPoint.X() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Left(), + rPoint.Y() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Top()); + else + return rPoint; + } + + Point MtfTools::ImplMap( const Point& rPt ) + { + if ( mnWinExtX && mnWinExtY ) + { + double fX = rPt.X(); + double fY = rPt.Y(); + + double fX2 = fX * maXForm.eM11 + fY * maXForm.eM21 + maXForm.eDx; + double fY2 = fX * maXForm.eM12 + fY * maXForm.eM22 + maXForm.eDy; + + if ( meGfxMode == GraphicsMode::GM_COMPATIBLE ) + { + fX2 -= mnWinOrgX; + fY2 -= mnWinOrgY; + + switch( meMapMode ) + { + case MappingMode::MM_LOENGLISH : + { + fX2 = o3tl::convert(fX2, o3tl::Length::in100, o3tl::Length::mm100); + fY2 = o3tl::convert(-fY2, o3tl::Length::in100, o3tl::Length::mm100); + } + break; + case MappingMode::MM_HIENGLISH : + { + fX2 = o3tl::convert(fX2, o3tl::Length::in1000, o3tl::Length::mm100); + fY2 = o3tl::convert(-fY2, o3tl::Length::in1000, o3tl::Length::mm100); + } + break; + case MappingMode::MM_TWIPS: + { + fX2 = o3tl::convert(fX2, o3tl::Length::twip, o3tl::Length::mm100); + fY2 = o3tl::convert(-fY2, o3tl::Length::twip, o3tl::Length::mm100); + } + break; + case MappingMode::MM_LOMETRIC : + { + fX2 = o3tl::convert(fX2, o3tl::Length::mm10, o3tl::Length::mm100); + fY2 = o3tl::convert(-fY2, o3tl::Length::mm10, o3tl::Length::mm100); + } + break; + case MappingMode::MM_HIMETRIC : // in hundredth of a millimeter + { + fY2 *= -1; + } + break; + default : + { + if (mnPixX == 0 || mnPixY == 0) + { + SAL_WARN("emfio", "invalid scaling factor"); + return Point(); + } + else + { + if ( meMapMode != MappingMode::MM_TEXT ) + { + fX2 /= mnWinExtX; + fY2 /= mnWinExtY; + fX2 *= mnDevWidth; + fY2 *= mnDevHeight; + } + fX2 *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX); + fY2 *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY); + } + } + break; + } + + double nDevOrgX = mnDevOrgX; + if (mnPixX) + nDevOrgX *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX); + fX2 += nDevOrgX; + double nDevOrgY = mnDevOrgY; + if (mnPixY) + nDevOrgY *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY); + fY2 += nDevOrgY; + + fX2 -= mrclFrame.Left(); + fY2 -= mrclFrame.Top(); + } + return Point(basegfx::fround(fX2), basegfx::fround(fY2)); + } + else + return Point(); + }; + + Size MtfTools::ImplMap(const Size& rSz, bool bDoWorldTransform) + { + if ( mnWinExtX && mnWinExtY ) + { + // #i121382# apply the whole WorldTransform, else a rotation will be misinterpreted + double fWidth, fHeight; + if (bDoWorldTransform) + { + fWidth = rSz.Width() * maXForm.eM11 + rSz.Height() * maXForm.eM21; + fHeight = rSz.Width() * maXForm.eM12 + rSz.Height() * maXForm.eM22; + } + else + { + //take the scale, but not the rotation + basegfx::B2DHomMatrix aMatrix(maXForm.eM11, maXForm.eM12, 0, + maXForm.eM21, maXForm.eM22, 0); + basegfx::B2DTuple aScale, aTranslate; + double fRotate, fShearX; + if (!aMatrix.decompose(aScale, aTranslate, fRotate, fShearX)) + { + aScale.setX(1.0); + aScale.setY(1.0); + } + fWidth = rSz.Width() * aScale.getX(); + fHeight = rSz.Height() * aScale.getY(); + } + + if ( meGfxMode == GraphicsMode::GM_COMPATIBLE ) + { + switch( meMapMode ) + { + case MappingMode::MM_LOENGLISH : + { + fWidth = o3tl::convert(fWidth, o3tl::Length::in100, o3tl::Length::mm100); + fHeight = o3tl::convert(-fHeight, o3tl::Length::in100, o3tl::Length::mm100); + } + break; + case MappingMode::MM_HIENGLISH : + { + fWidth = o3tl::convert(fWidth, o3tl::Length::in1000, o3tl::Length::mm100); + fHeight = o3tl::convert(-fHeight, o3tl::Length::in1000, o3tl::Length::mm100); + } + break; + case MappingMode::MM_LOMETRIC : + { + fWidth = o3tl::convert(fWidth, o3tl::Length::mm10, o3tl::Length::mm100); + fHeight = o3tl::convert(-fHeight, o3tl::Length::mm10, o3tl::Length::mm100); + } + break; + case MappingMode::MM_HIMETRIC : // in hundredth of millimeters + { + fHeight *= -1; + } + break; + case MappingMode::MM_TWIPS: + { + fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::mm100); + fHeight = o3tl::convert(-fHeight, o3tl::Length::twip, o3tl::Length::mm100); + } + break; + default : + { + if (mnPixX == 0 || mnPixY == 0) + { + SAL_WARN("emfio", "invalid scaling factor"); + return Size(); + } + else + { + if ( meMapMode != MappingMode::MM_TEXT ) + { + fWidth /= mnWinExtX; + fHeight /= mnWinExtY; + fWidth *= mnDevWidth; + fHeight *= mnDevHeight; + } + fWidth *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX); + fHeight *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY); + } + } + break; + } + } + return Size(basegfx::fround(fWidth), basegfx::fround(fHeight)); + } + else + return Size(); + } + + tools::Rectangle MtfTools::ImplMap( const tools::Rectangle& rRect ) + { + tools::Rectangle aRect; + aRect.SetPos(ImplMap(rRect.TopLeft())); + aRect.SaturatingSetSize(ImplMap(rRect.GetSize())); + return aRect; + } + + void MtfTools::ImplMap( vcl::Font& rFont ) + { + // !!! HACK: we now always set the width to zero because the OS width is interpreted differently; + // must later be made portable in SV (KA 1996-02-08) + Size aFontSize = ImplMap (rFont.GetFontSize(), false); + + const auto nHeight = aFontSize.Height(); + if (nHeight < 0) + aFontSize.setHeight( o3tl::saturating_toggle_sign(nHeight) ); + + rFont.SetFontSize( aFontSize ); + + sal_Int32 nResult; + const bool bFail = o3tl::checked_multiply(mnWinExtX, mnWinExtY, nResult); + if (!bFail && nResult < 0) + rFont.SetOrientation( 3600_deg10 - rFont.GetOrientation() ); + } + + tools::Polygon& MtfTools::ImplMap( tools::Polygon& rPolygon ) + { + sal_uInt16 nPoints = rPolygon.GetSize(); + for ( sal_uInt16 i = 0; i < nPoints; i++ ) + { + rPolygon[ i ] = ImplMap( rPolygon[ i ] ); + } + return rPolygon; + } + + void MtfTools::ImplScale( tools::Polygon& rPolygon ) + { + sal_uInt16 nPoints = rPolygon.GetSize(); + for ( sal_uInt16 i = 0; i < nPoints; i++ ) + { + rPolygon[ i ] = ImplScale( rPolygon[ i ] ); + } + } + + tools::PolyPolygon& MtfTools::ImplScale( tools::PolyPolygon& rPolyPolygon ) + { + sal_uInt16 nPolys = rPolyPolygon.Count(); + for (sal_uInt16 i = 0; i < nPolys; ++i) + { + ImplScale(rPolyPolygon[i]); + } + return rPolyPolygon; + } + + tools::PolyPolygon& MtfTools::ImplMap( tools::PolyPolygon& rPolyPolygon ) + { + sal_uInt16 nPolys = rPolyPolygon.Count(); + for ( sal_uInt16 i = 0; i < nPolys; ImplMap( rPolyPolygon[ i++ ] ) ) ; + return rPolyPolygon; + } + + void MtfTools::SelectObject( sal_uInt32 nIndex ) + { + if ( nIndex & ENHMETA_STOCK_OBJECT ) + { + SAL_INFO ( "emfio", "\t\t ENHMETA_STOCK_OBJECT, StockObject Enumeration: 0x" << std::hex << nIndex ); + StockObject nStockId = static_cast<StockObject>(nIndex & 0xFF); + switch( nStockId ) + { + case StockObject::WHITE_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_WHITE ); + mbFillStyleSelected = true; + } + break; + case StockObject::LTGRAY_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_LIGHTGRAY ); + mbFillStyleSelected = true; + } + break; + case StockObject::GRAY_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_GRAY ); + mbFillStyleSelected = true; + } + break; + case StockObject::DKGRAY_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_GRAY7 ); + mbFillStyleSelected = true; + } + break; + case StockObject::BLACK_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_BLACK ); + mbFillStyleSelected = true; + } + break; + case StockObject::NULL_BRUSH : + { + maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true ); + mbFillStyleSelected = true; + } + break; + case StockObject::WHITE_PEN : + { + maLineStyle = WinMtfLineStyle( COL_WHITE ); + } + break; + case StockObject::BLACK_PEN : + { + maLineStyle = WinMtfLineStyle( COL_BLACK ); + } + break; + case StockObject::NULL_PEN : + { + maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true ); + } + break; + default: + break; + } + } + else + { + nIndex &= 0xffff; // safety check: don't allow index to be > 65535 + + GDIObj *pGDIObj = nullptr; + + if ( nIndex < mvGDIObj.size() ) + pGDIObj = mvGDIObj[ nIndex ].get(); + + if ( pGDIObj ) + { + + SAL_INFO ( "emfio", "\t\t Index: " << nIndex ); + if (const auto pen = dynamic_cast<WinMtfLineStyle*>(pGDIObj)) + { + maLineStyle = *pen; + SAL_INFO ( "emfio", "\t Line Style, Color: 0x" << std::hex << maLineStyle.aLineColor + << ", Weight: " << maLineStyle.aLineInfo.GetWidth() ); + } + else if (const auto brush = dynamic_cast<WinMtfFillStyle*>( + pGDIObj)) + { + maFillStyle = *brush; + mbFillStyleSelected = true; + SAL_INFO("emfio", "\t\tBrush Object, Index: " << nIndex << ", Color: " << maFillStyle.aFillColor); + } + else if (const auto font = dynamic_cast<WinMtfFontStyle*>( + pGDIObj)) + { + maFont = font->aFont; + SAL_INFO("emfio", "\t\tFont Object, Index: " << nIndex << ", Font: " << maFont.GetFamilyName() << " " << maFont.GetStyleName()); + } + else if (const auto palette = dynamic_cast<WinMtfPalette*>( + pGDIObj)) + { + maPalette = palette->aPaletteColors; + SAL_INFO("emfio", "\t\tPalette Object, Index: " << nIndex << ", Number of colours: " << maPalette.aPaletteColors.size() ); + } + } + else + { + SAL_WARN("emfio", "Warning: Unable to find Object with index:" << nIndex); + } + } + } + + void MtfTools::SetTextLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode ) + { + mnTextLayoutMode = nTextLayoutMode; + } + + void MtfTools::SetArcDirection(bool bClockWise) + { + SAL_INFO("emfio", "\t\t Arc direction: " << (bClockWise ? "ClockWise" : "CounterClockWise")); + mbClockWiseArcDirection = bClockWise; + } + + void MtfTools::SetBkMode( BackgroundMode nMode ) + { + mnBkMode = nMode; + } + + void MtfTools::SetBkColor( const Color& rColor ) + { + maBkColor = rColor; + } + + void MtfTools::SetTextColor( const Color& rColor ) + { + maTextColor = rColor; + } + + void MtfTools::SetTextAlign( sal_uInt32 nAlign ) + { + mnTextAlign = nAlign; + } + + void MtfTools::ImplResizeObjectArry( sal_uInt32 nNewEntrys ) + { + mvGDIObj.resize(nNewEntrys); + } + + void MtfTools::ImplDrawClippedPolyPolygon( const tools::PolyPolygon& rPolyPoly ) + { + if ( !rPolyPoly.Count() ) + return; + + ImplSetNonPersistentLineColorTransparenz(); + if ( rPolyPoly.Count() == 1 ) + { + if ( rPolyPoly.IsRect() ) + mpGDIMetaFile->AddAction( new MetaRectAction( rPolyPoly.GetBoundRect() ) ); + else + { + tools::Polygon aPoly( rPolyPoly[ 0 ] ); + sal_uInt16 nCount = aPoly.GetSize(); + if ( nCount ) + { + if ( aPoly[ nCount - 1 ] != aPoly[ 0 ] ) + { + Point aPoint( aPoly[ 0 ] ); + aPoly.Insert( nCount, aPoint ); + } + mpGDIMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + } + } + } + else + mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) ); + } + + void MtfTools::CreateObject( std::unique_ptr<GDIObj> pObject ) + { + if ( pObject ) + { + const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get()); + const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get()); + + if ( pFontStyle ) + { + if (pFontStyle->aFont.GetFontHeight() == 0) + pFontStyle->aFont.SetFontHeight(423); + ImplMap(pFontStyle->aFont); // defaulting to 12pt + } + else if ( pLineStyle ) + { + Size aSize(pLineStyle->aLineInfo.GetWidth(), 0); + aSize = ImplMap(aSize); + pLineStyle->aLineInfo.SetWidth(aSize.Width()); + } + } + std::vector<std::unique_ptr<GDIObj>>::size_type nIndex; + for ( nIndex = 0; nIndex < mvGDIObj.size(); nIndex++ ) + { + if ( !mvGDIObj[ nIndex ] ) + break; + } + if ( nIndex == mvGDIObj.size() ) + ImplResizeObjectArry( mvGDIObj.size() + 16 ); + + mvGDIObj[ nIndex ] = std::move(pObject); + } + + void MtfTools::CreateObjectIndexed( sal_uInt32 nIndex, std::unique_ptr<GDIObj> pObject ) + { + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) != 0 ) + return; + + nIndex &= 0xffff; // safety check: do not allow index to be > 65535 + if ( pObject ) + { + const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get()); + const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get()); + if ( pFontStyle ) + { + if (pFontStyle->aFont.GetFontHeight() == 0) + pFontStyle->aFont.SetFontHeight(423); + ImplMap(pFontStyle->aFont); + } + else if ( pLineStyle ) + { + Size aSize(pLineStyle->aLineInfo.GetWidth(), 0); + pLineStyle->aLineInfo.SetWidth( ImplMap(aSize).Width() ); + + if ( pLineStyle->aLineInfo.GetStyle() == LineStyle::Dash ) + { + aSize.AdjustWidth(1 ); + tools::Long nDotLen = ImplMap( aSize ).Width(); + pLineStyle->aLineInfo.SetDistance( nDotLen ); + pLineStyle->aLineInfo.SetDotLen( nDotLen ); + pLineStyle->aLineInfo.SetDashLen( nDotLen * 3 ); + } + } + } + if ( nIndex >= mvGDIObj.size() ) + ImplResizeObjectArry( nIndex + 16 ); + + mvGDIObj[ nIndex ] = std::move(pObject); + } + + void MtfTools::CreateObject() + { + CreateObject(std::make_unique<GDIObj>()); + } + + void MtfTools::DeleteObject( sal_uInt32 nIndex ) + { + if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 ) + { + if ( nIndex < mvGDIObj.size() ) + { + mvGDIObj[ nIndex ].reset(); + } + } + } + + void MtfTools::IntersectClipRect( const tools::Rectangle& rRect ) + { + if (utl::ConfigManager::IsFuzzing()) + return; + mbClipNeedsUpdate=true; + if ((rRect.Left()-rRect.Right()==0) && (rRect.Top()-rRect.Bottom()==0)) + { + return; // empty rectangles cause trouble + } + tools::Polygon aPoly( rRect ); + const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) ); + maClipPath.intersectClip( aPolyPolyRect.getB2DPolyPolygon() ); + } + + void MtfTools::ExcludeClipRect( const tools::Rectangle& rRect ) + { + if (utl::ConfigManager::IsFuzzing()) + return; + mbClipNeedsUpdate=true; + tools::Polygon aPoly( rRect ); + const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) ); + maClipPath.excludeClip( aPolyPolyRect.getB2DPolyPolygon() ); + } + + void MtfTools::MoveClipRegion( const Size& rSize ) + { + if (utl::ConfigManager::IsFuzzing()) + return; + mbClipNeedsUpdate=true; + maClipPath.moveClipRegion( ImplMap( rSize ) ); + } + + void MtfTools::SetClipPath( const tools::PolyPolygon& rPolyPolygon, RegionMode eClippingMode, bool bIsMapped ) + { + if (utl::ConfigManager::IsFuzzing()) + return; + mbClipNeedsUpdate = true; + tools::PolyPolygon aPolyPolygon(rPolyPolygon); + + if (!bIsMapped) + { + if (!mbIsMapDevSet && (meMapMode == MappingMode::MM_ISOTROPIC || meMapMode == MappingMode::MM_ANISOTROPIC)) + aPolyPolygon = ImplScale(aPolyPolygon); + else + aPolyPolygon = ImplMap(aPolyPolygon); + } + maClipPath.setClipPath(aPolyPolygon.getB2DPolyPolygon(), eClippingMode); + } + + void MtfTools::SetDefaultClipPath() + { + mbClipNeedsUpdate = true; + maClipPath.setDefaultClipPath(); + } + + MtfTools::MtfTools( GDIMetaFile& rGDIMetaFile, SvStream& rStreamWMF) + : mnLatestTextAlign(90), + mnTextAlign(TextAlignmentMode::TA_LEFT | TextAlignmentMode::TA_TOP | TextAlignmentMode::TA_NOUPDATECP), + maLatestBkColor(ColorTransparency, 0x12345678), + maBkColor(COL_WHITE), + mnLatestTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default), + mnTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default), + mnLatestBkMode(BackgroundMode::NONE), + mnBkMode(BackgroundMode::OPAQUE), + meLatestRasterOp(RasterOp::Invert), + meRasterOp(RasterOp::OverPaint), + mnRop(), + meGfxMode(GraphicsMode::GM_COMPATIBLE), + meMapMode(MappingMode::MM_TEXT), + mnDevOrgX(0), + mnDevOrgY(0), + mnDevWidth(1), + mnDevHeight(1), + mnWinOrgX(0), + mnWinOrgY(0), + mnWinExtX(1), + mnWinExtY(1), + mnPixX(100), + mnPixY(100), + mnMillX(1), + mnMillY(1), + mpGDIMetaFile(&rGDIMetaFile), + mpInputStream(&rStreamWMF), + mnStartPos(0), + mnEndPos(0), + mbNopMode(false), + mbClockWiseArcDirection(false), + mbFillStyleSelected(false), + mbClipNeedsUpdate(true), + mbComplexClip(false), + mbIsMapWinSet(false), + mbIsMapDevSet(false) + { + SvLockBytes *pLB = mpInputStream->GetLockBytes(); + + if (pLB) + { + pLB->SetSynchronMode(); + } + + mnStartPos = mpInputStream->Tell(); + SetDevOrg(Point()); + + mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) ); // The original clipregion has to be on top + // of the stack so it can always be restored + // this is necessary to be able to support + // SetClipRgn( NULL ) and similar ClipRgn actions (SJ) + + maFont.SetFamilyName( "Arial" ); // sj: #i57205#, we do have some scaling problems if using + maFont.SetCharSet( RTL_TEXTENCODING_MS_1252 ); // the default font then most times a x11 font is used, we + maFont.SetFontHeight( 423 ); // will prevent this defining a font + + maLatestLineStyle.aLineColor = Color( 0x12, 0x34, 0x56 ); + maLatestFillStyle.aFillColor = Color( 0x12, 0x34, 0x56 ); + + mnRop = WMFRasterOp::Black; + meRasterOp = RasterOp::OverPaint; + mpGDIMetaFile->AddAction( new MetaRasterOpAction( RasterOp::OverPaint ) ); + } + + MtfTools::~MtfTools() COVERITY_NOEXCEPT_FALSE + { + mpGDIMetaFile->AddAction( new MetaPopAction() ); + mpGDIMetaFile->SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + if ( mrclFrame.IsEmpty() ) + mpGDIMetaFile->SetPrefSize( Size( mnDevWidth, mnDevHeight ) ); + else + mpGDIMetaFile->SetPrefSize( mrclFrame.GetSize() ); + } + + void MtfTools::UpdateClipRegion() + { + if (!mbClipNeedsUpdate) + return; + + mbClipNeedsUpdate = false; + mbComplexClip = false; + + mpGDIMetaFile->AddAction( new MetaPopAction() ); // taking the original clipregion + mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) ); + + // skip for 'no clipping at all' case + if( maClipPath.isEmpty() ) + return; + + const basegfx::B2DPolyPolygon& rClipPoly( maClipPath.getClipPath() ); + + mbComplexClip = rClipPoly.count() > 1 + || !basegfx::utils::isRectangle(rClipPoly); + + // This makes cases like tdf#45820 work in reasonable time. + if (mbComplexClip) + { + mpGDIMetaFile->AddAction( + new MetaISectRegionClipRegionAction( + vcl::Region(rClipPoly))); + mbComplexClip = false; + } + else + { + mpGDIMetaFile->AddAction( + new MetaISectRectClipRegionAction( + vcl::unotools::rectangleFromB2DRectangle( + rClipPoly.getB2DRange()))); + } + } + + void MtfTools::ImplSetNonPersistentLineColorTransparenz() + { + WinMtfLineStyle aTransparentLine( COL_TRANSPARENT, true ); + if ( ! ( maLatestLineStyle == aTransparentLine ) ) + { + maLatestLineStyle = aTransparentLine; + mpGDIMetaFile->AddAction( new MetaLineColorAction( aTransparentLine.aLineColor, !aTransparentLine.bTransparent ) ); + } + } + + void MtfTools::UpdateLineStyle() + { + if (!( maLatestLineStyle == maLineStyle ) ) + { + maLatestLineStyle = maLineStyle; + mpGDIMetaFile->AddAction( new MetaLineColorAction( maLineStyle.aLineColor, !maLineStyle.bTransparent ) ); + } + } + + void MtfTools::UpdateFillStyle() + { + if ( !mbFillStyleSelected ) // SJ: #i57205# taking care of bkcolor if no brush is selected + maFillStyle = WinMtfFillStyle( maBkColor, mnBkMode == BackgroundMode::Transparent ); + if (!( maLatestFillStyle == maFillStyle ) ) + { + maLatestFillStyle = maFillStyle; + if (maFillStyle.aType == WinMtfFillStyleType::Solid) + mpGDIMetaFile->AddAction( new MetaFillColorAction( maFillStyle.aFillColor, !maFillStyle.bTransparent ) ); + } + } + + WMFRasterOp MtfTools::SetRasterOp( WMFRasterOp nRasterOp ) + { + WMFRasterOp nRetROP = mnRop; + if ( nRasterOp != mnRop ) + { + mnRop = nRasterOp; + + if ( mbNopMode && ( nRasterOp != WMFRasterOp::Nop ) ) + { // changing modes from WMFRasterOp::Nop so set pen and brush + maFillStyle = maNopFillStyle; + maLineStyle = maNopLineStyle; + mbNopMode = false; + } + switch( nRasterOp ) + { + case WMFRasterOp::Not: + meRasterOp = RasterOp::Invert; + break; + + case WMFRasterOp::XorPen: + meRasterOp = RasterOp::Xor; + break; + + case WMFRasterOp::Nop: + { + meRasterOp = RasterOp::OverPaint; + if( !mbNopMode ) + { + maNopFillStyle = maFillStyle; + maNopLineStyle = maLineStyle; + maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true ); + maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true ); + mbNopMode = true; + } + } + break; + + default: + meRasterOp = RasterOp::OverPaint; + break; + } + } + if ( nRetROP != nRasterOp ) + mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) ); + return nRetROP; + }; + + void MtfTools::StrokeAndFillPath( bool bStroke, bool bFill ) + { + if ( !maPathObj.Count() ) + return; + + UpdateClipRegion(); + UpdateLineStyle(); + UpdateFillStyle(); + if ( bFill ) + { + if ( !bStroke ) + { + mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + mpGDIMetaFile->AddAction( new MetaLineColorAction( Color(), false ) ); + } + if ( maPathObj.Count() == 1 ) + mpGDIMetaFile->AddAction( new MetaPolygonAction( maPathObj.GetObject( 0 ) ) ); + else + mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( maPathObj ) ); + + if ( !bStroke ) + mpGDIMetaFile->AddAction( new MetaPopAction() ); + } + // tdf#142014 By default the stroke is made with hairline. If width is bigger, we need to use PolyLineAction + if ( bStroke ) + { + // bFill is drawing hairstyle line. So we need to draw it only when the width is different than 0 + if ( !bFill || maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + sal_uInt16 i, nCount = maPathObj.Count(); + for ( i = 0; i < nCount; i++ ) + mpGDIMetaFile->AddAction( new MetaPolyLineAction( maPathObj[ i ], maLineStyle.aLineInfo ) ); + } + } + ClearPath(); + } + + void MtfTools::DrawPixel( const Point& rSource, const Color& rColor ) + { + mpGDIMetaFile->AddAction( new MetaPixelAction( ImplMap( rSource), rColor ) ); + } + + void MtfTools::MoveTo( const Point& rPoint, bool bRecordPath ) + { + Point aDest( ImplMap( rPoint ) ); + if ( bRecordPath ) + { + // fdo#57353 create new subpath for subsequent moves + if ( maPathObj.Count() ) + if ( maPathObj[ maPathObj.Count() - 1 ].GetSize() ) + maPathObj.Insert( tools::Polygon() ); + maPathObj.AddPoint( aDest ); + } + maActPos = aDest; + } + + void MtfTools::LineTo( const Point& rPoint, bool bRecordPath ) + { + UpdateClipRegion(); + Point aDest( ImplMap( rPoint ) ); + if ( bRecordPath ) + maPathObj.AddPoint( aDest ); + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaLineAction( maActPos, aDest, maLineStyle.aLineInfo ) ); + } + maActPos = aDest; + } + + void MtfTools::DrawRectWithBGColor(const tools::Rectangle& rRect) + { + WinMtfFillStyle aFillStyleBackup = maFillStyle; + bool aTransparentBackup = maLineStyle.bTransparent; + BackgroundMode mnBkModeBackup = mnBkMode; + + const tools::Polygon aPoly( rRect ); + maLineStyle.bTransparent = true; + maFillStyle = maBkColor; + mnBkMode = BackgroundMode::OPAQUE; + ImplSetNonPersistentLineColorTransparenz(); + DrawPolygon(std::move(aPoly), false); + mnBkMode = mnBkModeBackup; // The rectangle needs to be always drawned even if mode is transparent + maFillStyle = aFillStyleBackup; + maLineStyle.bTransparent = aTransparentBackup; + } + + void MtfTools::DrawRect( const tools::Rectangle& rRect, bool bEdge ) + { + UpdateClipRegion(); + UpdateFillStyle(); + + if ( mbComplexClip ) + { + tools::Polygon aPoly( ImplMap( rRect ) ); + tools::PolyPolygon aPolyPolyRect( aPoly ); + tools::PolyPolygon aDest; + tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( aPolyPolyRect, aDest ); + ImplDrawClippedPolyPolygon( aDest ); + } + else + { + if ( bEdge ) + { + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) ); + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( ImplMap( rRect ) ),maLineStyle.aLineInfo ) ); + } + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) ); + } + } + else + { + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) ); + } + } + } + + void MtfTools::DrawRoundRect( const tools::Rectangle& rRect, const Size& rSize ) + { + UpdateClipRegion(); + UpdateLineStyle(); + UpdateFillStyle(); + mpGDIMetaFile->AddAction( new MetaRoundRectAction( ImplMap( rRect ), std::abs( ImplMap( rSize ).Width() ), std::abs( ImplMap( rSize ).Height() ) ) ); + // tdf#142139 Wrong line width during WMF import + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + tools::Polygon aRoundRectPoly( rRect, rSize.Width(), rSize.Height() ); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( ImplMap( aRoundRectPoly ), maLineStyle.aLineInfo ) ); + } + } + + void MtfTools::DrawEllipse( const tools::Rectangle& rRect ) + { + UpdateClipRegion(); + UpdateFillStyle(); + + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + Point aCenter( ImplMap( rRect.Center() ) ); + Size aRad( ImplMap( Size( rRect.GetWidth() / 2, rRect.GetHeight() / 2 ) ) ); + + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) ); + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aCenter, aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) ); + } + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) ); + } + } + + void MtfTools::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd, bool bTo ) + { + UpdateClipRegion(); + UpdateLineStyle(); + UpdateFillStyle(); + + tools::Rectangle aRect( ImplMap( rRect ) ); + Point aStart( ImplMap( rStart ) ); + Point aEnd( ImplMap( rEnd ) ); + + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + if ( aStart == aEnd ) + { // SJ: #i53768# if start & end is identical, then we have to draw a full ellipse + Point aCenter( aRect.Center() ); + Size aRad( aRect.GetWidth() / 2, aRect.GetHeight() / 2 ); + + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aCenter, aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) ); + } + else + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Arc ), maLineStyle.aLineInfo ) ); + } + else + mpGDIMetaFile->AddAction( new MetaArcAction( aRect, aStart, aEnd ) ); + + if ( bTo ) + maActPos = aEnd; + } + + void MtfTools::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd ) + { + UpdateClipRegion(); + UpdateFillStyle(); + + tools::Rectangle aRect( ImplMap( rRect ) ); + Point aStart( ImplMap( rStart ) ); + Point aEnd( ImplMap( rEnd ) ); + + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) ); + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Pie ), maLineStyle.aLineInfo ) ); + } + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) ); + } + } + + void MtfTools::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd ) + { + UpdateClipRegion(); + UpdateFillStyle(); + + tools::Rectangle aRect( ImplMap( rRect ) ); + Point aStart( ImplMap( rStart ) ); + Point aEnd( ImplMap( rEnd ) ); + + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) ); + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Chord ), maLineStyle.aLineInfo ) ); + } + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) ); + } + } + + void MtfTools::DrawPolygon( tools::Polygon rPolygon, bool bRecordPath ) + { + UpdateClipRegion(); + ImplMap( rPolygon ); + if ( bRecordPath ) + maPathObj.AddPolygon( rPolygon ); + else + { + UpdateFillStyle(); + + if ( mbComplexClip ) + { + tools::PolyPolygon aPolyPoly( rPolygon ); + auto tmp = maClipPath.getClip(); + tmp.intersectPolyPolygon(aPolyPoly.getB2DPolyPolygon()); + tools::PolyPolygon aDest(tmp.getClipPoly()); + ImplDrawClippedPolyPolygon( aDest ); + } + else + { + if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) ) + { + sal_uInt16 nCount = rPolygon.GetSize(); + if ( nCount ) + { + if ( rPolygon[ nCount - 1 ] != rPolygon[ 0 ] ) + { + Point aPoint( rPolygon[ 0 ] ); + rPolygon.Insert( nCount, aPoint ); + } + } + ImplSetNonPersistentLineColorTransparenz(); + mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) ); + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( rPolygon, maLineStyle.aLineInfo ) ); + } + else + { + UpdateLineStyle(); + + if (maLatestFillStyle.aType != WinMtfFillStyleType::Pattern) + mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) ); + else { + SvtGraphicFill aFill( tools::PolyPolygon( rPolygon ), + Color(), + 0.0, + SvtGraphicFill::fillNonZero, + SvtGraphicFill::fillTexture, + SvtGraphicFill::Transform(), + true, + SvtGraphicFill::hatchSingle, + Color(), + SvtGraphicFill::GradientType::Linear, + Color(), + Color(), + 0, + Graphic (BitmapEx(maLatestFillStyle.aBmp))); + + SvMemoryStream aMemStm; + + WriteSvtGraphicFill( aMemStm, aFill ); + + mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN", 0, + static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd() ) ); + mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_END" ) ); + } + + } + } + } + } + + void MtfTools::DrawPolyPolygon( tools::PolyPolygon& rPolyPolygon, bool bRecordPath ) + { + UpdateClipRegion(); + + ImplMap( rPolyPolygon ); + + if ( bRecordPath ) + maPathObj.AddPolyPolygon( rPolyPolygon ); + else + { + UpdateFillStyle(); + + if ( mbComplexClip ) + { + tools::PolyPolygon aDest; + tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( rPolyPolygon, aDest ); + ImplDrawClippedPolyPolygon( aDest ); + } + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPolygon ) ); + if (maLineStyle.aLineInfo.GetWidth() > 0 || maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash) + { + for (sal_uInt16 nPoly = 0; nPoly < rPolyPolygon.Count(); ++nPoly) + { + mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolyPolygon[nPoly], maLineStyle.aLineInfo)); + } + } + } + } + } + + void MtfTools::DrawPolyLine( tools::Polygon rPolygon, bool bTo, bool bRecordPath ) + { + UpdateClipRegion(); + + sal_uInt16 nPoints = rPolygon.GetSize(); + if (nPoints < 1) + return; + + ImplMap( rPolygon ); + if ( bTo ) + { + rPolygon[ 0 ] = maActPos; + maActPos = rPolygon[ rPolygon.GetSize() - 1 ]; + } + if ( bRecordPath ) + maPathObj.AddPolyLine( rPolygon ); + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( rPolygon, maLineStyle.aLineInfo ) ); + } + } + + void MtfTools::DrawPolyBezier( tools::Polygon rPolygon, bool bTo, bool bRecordPath ) + { + sal_uInt16 nPoints = rPolygon.GetSize(); + if ( ( nPoints < 4 ) || ( ( ( nPoints - 4 ) % 3 ) != 0 ) ) + return; + + UpdateClipRegion(); + + ImplMap( rPolygon ); + if ( bTo ) + { + rPolygon[ 0 ] = maActPos; + maActPos = rPolygon[ nPoints - 1 ]; + } + sal_uInt16 i; + for ( i = 0; ( i + 2 ) < nPoints; ) + { + rPolygon.SetFlags( i++, PolyFlags::Normal ); + rPolygon.SetFlags( i++, PolyFlags::Control ); + rPolygon.SetFlags( i++, PolyFlags::Control ); + } + if ( bRecordPath ) + maPathObj.AddPolyLine( rPolygon ); + else + { + UpdateLineStyle(); + mpGDIMetaFile->AddAction( new MetaPolyLineAction( rPolygon, maLineStyle.aLineInfo ) ); + } + } + + void MtfTools::DrawText( Point& rPosition, OUString const & rText, std::vector<sal_Int32>* pDXArry, tools::Long* pDYArry, bool bRecordPath, GraphicsMode nGfxMode ) + { + UpdateClipRegion(); + rPosition = ImplMap( rPosition ); + GraphicsMode nOldGfxMode = GetGfxMode(); + SetGfxMode( GraphicsMode::GM_COMPATIBLE ); + + if (pDXArry) + { + sal_Int64 nSumX = 0, nSumY = 0; + for (sal_Int32 i = 0; i < rText.getLength(); i++ ) + { + nSumX += (*pDXArry)[i]; + + // #i121382# Map DXArray using WorldTransform + const Size aSizeX(ImplMap(Size(nSumX, 0))); + const basegfx::B2DVector aVectorX(aSizeX.Width(), aSizeX.Height()); + (*pDXArry)[i] = basegfx::fround(aVectorX.getLength()); + (*pDXArry)[i] *= (nSumX >= 0 ? 1 : -1); + + if (pDYArry) + { + nSumY += pDYArry[i]; + + const Size aSizeY(ImplMap(Size(0, nSumY))); + const basegfx::B2DVector aVectorY(aSizeY.Width(), aSizeY.Height()); + // Reverse Y + pDYArry[i] = basegfx::fround(aVectorY.getLength()); + pDYArry[i] *= (nSumY >= 0 ? -1 : 1); + } + } + } + if ( mnLatestTextLayoutMode != mnTextLayoutMode ) + { + mnLatestTextLayoutMode = mnTextLayoutMode; + mpGDIMetaFile->AddAction( new MetaLayoutModeAction( mnTextLayoutMode ) ); + } + SetGfxMode( nGfxMode ); + TextAlign eTextAlign; + if ( ( mnTextAlign & TA_BASELINE) == TA_BASELINE ) + eTextAlign = ALIGN_BASELINE; + else if( ( mnTextAlign & TA_BOTTOM) == TA_BOTTOM ) + eTextAlign = ALIGN_BOTTOM; + else + eTextAlign = ALIGN_TOP; + bool bChangeFont = false; + if ( mnLatestTextAlign != mnTextAlign ) + { + bChangeFont = true; + mnLatestTextAlign = mnTextAlign; + mpGDIMetaFile->AddAction( new MetaTextAlignAction( eTextAlign ) ); + } + if ( maLatestTextColor != maTextColor ) + { + bChangeFont = true; + maLatestTextColor = maTextColor; + mpGDIMetaFile->AddAction( new MetaTextColorAction( maTextColor ) ); + } + bool bChangeFillColor = false; + if ( maLatestBkColor != maBkColor ) + { + bChangeFillColor = true; + maLatestBkColor = maBkColor; + } + if ( mnLatestBkMode != mnBkMode ) + { + bChangeFillColor = true; + mnLatestBkMode = mnBkMode; + } + if ( bChangeFillColor ) + { + bChangeFont = true; + mpGDIMetaFile->AddAction( new MetaTextFillColorAction( maFont.GetFillColor(), !maFont.IsTransparent() ) ); + } + vcl::Font aTmp( maFont ); + aTmp.SetColor( maTextColor ); + aTmp.SetFillColor( maBkColor ); + + if( mnBkMode == BackgroundMode::Transparent ) + aTmp.SetTransparent( true ); + else + aTmp.SetTransparent( false ); + + aTmp.SetAlignment( eTextAlign ); + + if ( nGfxMode == GraphicsMode::GM_ADVANCED ) + { + // check whether there is a font rotation applied via transformation + Point aP1( ImplMap( Point() ) ); + Point aP2( ImplMap( Point( 0, 100 ) ) ); + aP2.AdjustX( -(aP1.X()) ); + aP2.AdjustY( -(aP1.Y()) ); + double fX = aP2.X(); + double fY = aP2.Y(); + if ( fX ) + { + double fOrientation = basegfx::rad2deg(acos(fX / std::hypot(fX, fY))); + if ( fY > 0 ) + fOrientation = 360 - fOrientation; + fOrientation += 90; + fOrientation *= 10; + aTmp.SetOrientation( aTmp.GetOrientation() + Degree10( static_cast<sal_Int16>(fOrientation) ) ); + } + } + + if( mnTextAlign & ( TA_UPDATECP | TA_RIGHT_CENTER ) ) + { + // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading + SolarMutexGuard aGuard; + ScopedVclPtrInstance< VirtualDevice > pVDev; + sal_Int32 nTextWidth; + Point aActPosDelta; + pVDev->SetMapMode( MapMode( MapUnit::Map100thMM ) ); + pVDev->SetFont( maFont ); + const sal_uInt32 nLen = pDXArry ? rText.getLength() : 0; + if (nLen) + { + nTextWidth = pVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) ); + if( nLen > 1 ) + nTextWidth += (*pDXArry)[ nLen - 2 ]; + // tdf#39894: We should consider the distance to next character cell origin + aActPosDelta.setX( (*pDXArry)[ nLen - 1 ] ); + if ( pDYArry ) + { + aActPosDelta.setY( pDYArry[ nLen - 1 ] ); + } + } + else + { + nTextWidth = pVDev->GetTextWidth( rText ); + aActPosDelta.setX( nTextWidth ); + } + + if( mnTextAlign & TA_UPDATECP ) + rPosition = maActPos; + + if ( mnTextAlign & TA_RIGHT_CENTER ) + { + Point aDisplacement( ( ( mnTextAlign & TA_RIGHT_CENTER ) == TA_RIGHT ) ? nTextWidth : nTextWidth >> 1, 0 ); + Point().RotateAround(aDisplacement, maFont.GetOrientation()); + rPosition -= aDisplacement; + } + + if( mnTextAlign & TA_UPDATECP ) + { + Point().RotateAround(aActPosDelta, maFont.GetOrientation()); + maActPos = rPosition + aActPosDelta; + } + } + + if(bChangeFont || (maLatestFont != aTmp)) + { + maLatestFont = aTmp; + rtl::Reference<MetaFontAction> aNewMetaFontAction(new MetaFontAction(aTmp)); + + // tdf#127471 end evtl active MetaFontAction scale corrector detector/collector + maScaledFontHelper.endCurrentMetaFontAction(); + + // !bRecordPath: else no MetaTextArrayAction will be created + // nullptr != pDXArry: detection only possible when text size is given + // rText.getLength(): no useful check without text + if(!bRecordPath && nullptr != pDXArry && 0 != rText.getLength()) + { + maScaledFontHelper.newCurrentMetaFontAction(aNewMetaFontAction); + } + + mpGDIMetaFile->AddAction( aNewMetaFontAction ); + mpGDIMetaFile->AddAction( new MetaTextAlignAction( aTmp.GetAlignment() ) ); + mpGDIMetaFile->AddAction( new MetaTextColorAction( aTmp.GetColor() ) ); + mpGDIMetaFile->AddAction( new MetaTextFillColorAction( aTmp.GetFillColor(), !aTmp.IsTransparent() ) ); + } + + if ( bRecordPath ) + { + // TODO + } + else + { + if ( pDXArry && pDYArry ) + { + for (sal_Int32 i = 0; i < rText.getLength(); ++i) + { + Point aCharDisplacement( i ? (*pDXArry)[i-1] : 0, i ? pDYArry[i-1] : 0 ); + Point().RotateAround(aCharDisplacement, maFont.GetOrientation()); + mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), o3tl::span<const sal_Int32>{}, 0, 1 ) ); + } + } + else + { + /* because text without dx array is badly scaled, we + will create such an array if necessary */ + o3tl::span<const sal_Int32> pDX; + std::vector<sal_Int32> aMyDXArray; + if (pDXArry) + { + pDX = { pDXArry->data(), pDXArry->size() }; + // only useful when we have an imported DXArray + if(!rText.isEmpty()) + { + maScaledFontHelper.evaluateAlternativeFontScale( + rText, + (*pDXArry)[rText.getLength() - 1] // extract imported TextLength + ); + } + } + else + { + // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading + SolarMutexGuard aGuard; + ScopedVclPtrInstance< VirtualDevice > pVDev; + pVDev->SetMapMode(MapMode(MapUnit::Map100thMM)); + pVDev->SetFont( maLatestFont ); + pVDev->GetTextArray( rText, &aMyDXArray, 0, rText.getLength()); + pDX = aMyDXArray; + } + mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, 0, rText.getLength() ) ); + } + } + SetGfxMode( nOldGfxMode ); + } + + void MtfTools::ImplDrawBitmap( const Point& rPos, const Size& rSize, const BitmapEx& rBitmap ) + { + BitmapEx aBmpEx( rBitmap ); + if ( mbComplexClip ) + { + vcl::bitmap::DrawAndClipBitmap(rPos, rSize, rBitmap, aBmpEx, maClipPath.getClipPath()); + } + + if ( aBmpEx.IsAlpha() ) + mpGDIMetaFile->AddAction( new MetaBmpExScaleAction( rPos, rSize, aBmpEx ) ); + else + mpGDIMetaFile->AddAction( new MetaBmpScaleAction( rPos, rSize, aBmpEx.GetBitmap() ) ); + } + + void MtfTools::ResolveBitmapActions( std::vector<BSaveStruct>& rSaveList ) + { + UpdateClipRegion(); + + size_t nObjects = rSaveList.size(); + size_t nObjectsLeft = nObjects; + + while ( nObjectsLeft ) + { + size_t i; + size_t nObjectsOfSameSize = 0; + size_t nObjectStartIndex = nObjects - nObjectsLeft; + + BSaveStruct* pSave = &rSaveList[nObjectStartIndex]; + tools::Rectangle aRect( pSave->aOutRect ); + + for ( i = nObjectStartIndex; i < nObjects; ) + { + nObjectsOfSameSize++; + if ( ++i < nObjects ) + { + pSave = &rSaveList[i]; + if ( pSave->aOutRect != aRect ) + break; + } + } + Point aPos( ImplMap( aRect.TopLeft() ) ); + Size aSize( ImplMap( aRect.GetSize() ) ); + + for ( i = nObjectStartIndex; i < ( nObjectStartIndex + nObjectsOfSameSize ); i++ ) + { + pSave = &rSaveList[i]; + + sal_uInt32 nWinRop = pSave->nWinRop; + sal_uInt8 nRasterOperation = static_cast<sal_uInt8>( nWinRop >> 16 ); + + sal_uInt32 nUsed = 0; + if ( ( nRasterOperation & 0xf ) != ( nRasterOperation >> 4 ) ) + nUsed |= 1; // pattern is used + if ( ( nRasterOperation & 0x33 ) != ( ( nRasterOperation & 0xcc ) >> 2 ) ) + nUsed |= 2; // source is used + if ( ( nRasterOperation & 0xaa ) != ( ( nRasterOperation & 0x55 ) << 1 ) ) + nUsed |= 4; // destination is used + + if ( (nUsed & 1) && (( nUsed & 2 ) == 0) && nWinRop != PATINVERT ) + { // patterns aren't well supported yet + WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::NONE ); // in this case nRasterOperation is either 0 or 0xff + UpdateFillStyle(); + DrawRect( aRect, false ); + SetRasterOp( nOldRop ); + } + else + { + bool bDrawn = false; + + if ( i == nObjectStartIndex ) // optimizing, sometimes it is possible to create just one transparent bitmap + { + if ( nObjectsOfSameSize == 2 ) + { + BSaveStruct* pSave2 = &rSaveList[i + 1]; + if ( ( pSave->aBmpEx.GetPrefSize() == pSave2->aBmpEx.GetPrefSize() ) && + ( pSave->aBmpEx.GetPrefMapMode() == pSave2->aBmpEx.GetPrefMapMode() ) ) + { + // TODO: Strictly speaking, we should + // check whether mask is monochrome, and + // whether image is black (upper branch) + // or white (lower branch). Otherwise, the + // effect is not the same as a masked + // bitmap. + if ( ( nWinRop == SRCPAINT ) && ( pSave2->nWinRop == SRCAND ) ) + { + Bitmap aMask( pSave->aBmpEx.GetBitmap() ); aMask.Invert(); + BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), aMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + bDrawn = true; + i++; + } + // #i20085# This is just the other way + // around as above. Only difference: mask + // is inverted + else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCPAINT ) ) + { + const Bitmap & rMask( pSave->aBmpEx.GetBitmap() ); + BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + bDrawn = true; + i++; + } + // tdf#90539 + else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCINVERT ) ) + { + const Bitmap & rMask( pSave->aBmpEx.GetBitmap() ); + BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + bDrawn = true; + i++; + } + } + } + } + + if ( !bDrawn ) + { + Push(); + WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::CopyPen ); + Bitmap aBitmap( pSave->aBmpEx.GetBitmap() ); + sal_uInt32 nOperation = ( nRasterOperation & 0xf ); + switch( nOperation ) + { + case 0x1 : + case 0xe : + { + if(pSave->aBmpEx.IsAlpha()) + { + ImplDrawBitmap( aPos, aSize, pSave->aBmpEx ); + } + else + { + SetRasterOp( WMFRasterOp::XorPen ); + ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) ); + SetRasterOp( WMFRasterOp::CopyPen ); + Bitmap aMask( aBitmap ); + aMask.Invert(); + BitmapEx aBmpEx( aBitmap, aMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + if ( nOperation == 0x1 ) + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + } + } + break; + case 0x7 : + case 0x8 : + { + Bitmap aMask( aBitmap ); + if ( ( nUsed & 1 ) && ( nRasterOperation & 0xb0 ) == 0xb0 ) // pattern used + { + aBitmap.Convert( BmpConversion::N24Bit ); + aBitmap.Erase( maFillStyle.aFillColor ); + } + BitmapEx aBmpEx( aBitmap, aMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + if ( nOperation == 0x7 ) + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + } + break; + + case 0x4 : + case 0xb : + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + SetRasterOp( WMFRasterOp::CopyPen ); + Bitmap aMask( aBitmap ); + aBitmap.Invert(); + BitmapEx aBmpEx( aBitmap, aMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + SetRasterOp( WMFRasterOp::XorPen ); + ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) ); + if ( nOperation == 0xb ) + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + } + break; + + case 0x2 : + case 0xd : + { + Bitmap aMask( aBitmap ); + aMask.Invert(); + BitmapEx aBmpEx( aBitmap, aMask ); + ImplDrawBitmap( aPos, aSize, aBmpEx ); + SetRasterOp( WMFRasterOp::XorPen ); + ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) ); + if ( nOperation == 0xd ) + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + } + break; + case 0x6 : + case 0x9 : + { + SetRasterOp( WMFRasterOp::XorPen ); + ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) ); + if ( nOperation == 0x9 ) + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + } + break; + + case 0x0 : // WHITENESS + case 0xf : // BLACKNESS + { // in this case nRasterOperation is either 0 or 0xff + maFillStyle = WinMtfFillStyle( Color( nRasterOperation, nRasterOperation, nRasterOperation ) ); + UpdateFillStyle(); + DrawRect( aRect, false ); + } + break; + + case 0x3 : // only source is used + case 0xc : + { + if ( nRasterOperation == 0x33 ) + aBitmap.Invert(); + if (pSave->m_bForceAlpha) + { + ImplDrawBitmap(aPos, aSize, pSave->aBmpEx); + } + else + { + ImplDrawBitmap(aPos, aSize, BitmapEx(aBitmap)); + } + } + break; + + case 0x5 : // only destination is used + { + SetRasterOp( WMFRasterOp::Not ); + DrawRect( aRect, false ); + } + break; + + case 0xa : // no operation + break; + } + SetRasterOp( nOldRop ); + Pop(); + } + } + } + nObjectsLeft -= nObjectsOfSameSize; + } + + rSaveList.clear(); + } + + void MtfTools::SetDevOrg( const Point& rPoint ) + { + mnDevOrgX = rPoint.X(); + mnDevOrgY = rPoint.Y(); + } + + void MtfTools::SetDevOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd ) + { + mnDevOrgX += nXAdd; + mnDevOrgY += nYAdd; + } + + void MtfTools::SetDevExt( const Size& rSize ,bool regular) + { + if ( !(rSize.Width() && rSize.Height()) ) + return; + + switch( meMapMode ) + { + case MappingMode::MM_ISOTROPIC : + case MappingMode::MM_ANISOTROPIC : + { + mnDevWidth = rSize.Width(); + mnDevHeight = rSize.Height(); + break; + } + + //do nothing + default: + break; + } + if (regular) + { + mbIsMapDevSet=true; + } + } + + void MtfTools::ScaleDevExt(double fX, double fY) + { + mnDevWidth = basegfx::fround(mnDevWidth * fX); + mnDevHeight = basegfx::fround(mnDevHeight * fY); + } + + void MtfTools::SetWinOrg( const Point& rPoint , bool bIsEMF) + { + mnWinOrgX = rPoint.X(); + mnWinOrgY = rPoint.Y(); + if (bIsEMF) + { + SetDevByWin(); + } + mbIsMapWinSet=true; + } + + void MtfTools::SetWinOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd ) + { + mnWinOrgX += nXAdd; + mnWinOrgY += nYAdd; + } + + void MtfTools::SetDevByWin() //mnWinExt...-stuff has to be assigned before. + { + if (!mbIsMapDevSet) + { + if ( meMapMode == MappingMode::MM_ISOTROPIC ) //TODO: WHAT ABOUT ANISOTROPIC??? + { + sal_Int32 nX, nY; + if (o3tl::checked_add(mnWinExtX, mnWinOrgX, nX) || o3tl::checked_sub(mnWinExtY, mnWinOrgY, nY)) + return; + Size aSize(nX >> MS_FIXPOINT_BITCOUNT_28_4, -(nY >> MS_FIXPOINT_BITCOUNT_28_4)); + SetDevExt(aSize, false); + } + } + } + + void MtfTools::SetWinExt(const Size& rSize, bool bIsEMF) + { + if (!(rSize.Width() && rSize.Height())) + return; + + switch( meMapMode ) + { + case MappingMode::MM_ISOTROPIC : + case MappingMode::MM_ANISOTROPIC : + { + mnWinExtX = rSize.Width(); + mnWinExtY = rSize.Height(); + if (bIsEMF) + { + SetDevByWin(); + } + mbIsMapWinSet = true; + break; + } + + default: + //do nothing + break; + } + } + + void MtfTools::ScaleWinExt(double fX, double fY) + { + mnWinExtX = basegfx::fround(mnWinExtX * fX); + mnWinExtY = basegfx::fround(mnWinExtY * fY); + } + + void MtfTools::SetrclBounds( const tools::Rectangle& rRect ) + { + mrclBounds = rRect; + } + + void MtfTools::SetrclFrame( const tools::Rectangle& rRect ) + { + mrclFrame = rRect; + } + + void MtfTools::SetRefPix( const Size& rSize ) + { + mnPixX = rSize.Width(); + mnPixY = rSize.Height(); + } + + void MtfTools::SetRefMill( const Size& rSize ) + { + mnMillX = rSize.Width(); + mnMillY = rSize.Height(); + } + + void MtfTools::SetMapMode( MappingMode nMapMode ) + { + meMapMode = nMapMode; + if ( nMapMode == MappingMode::MM_TEXT && !mbIsMapWinSet ) + { + mnWinExtX = mnDevWidth; + mnWinExtY = mnDevHeight; + } + else if ( meMapMode == MappingMode::MM_HIMETRIC ) + { + sal_Int32 nWinExtX, nWinExtY; + if (o3tl::checked_multiply<sal_Int32>(mnMillX, 100, nWinExtX) || + o3tl::checked_multiply<sal_Int32>(mnMillY, 100, nWinExtY)) + { + return; + } + mnWinExtX = nWinExtX; + mnWinExtY = nWinExtY; + } + } + + void MtfTools::SetWorldTransform( const XForm& rXForm ) + { + maXForm.eM11 = rXForm.eM11; + maXForm.eM12 = rXForm.eM12; + maXForm.eM21 = rXForm.eM21; + maXForm.eM22 = rXForm.eM22; + maXForm.eDx = rXForm.eDx; + maXForm.eDy = rXForm.eDy; + } + + void MtfTools::ModifyWorldTransform( const XForm& rXForm, ModifyWorldTransformMode nMode ) + { + switch( nMode ) + { + case ModifyWorldTransformMode::MWT_IDENTITY : + { + maXForm.eM11 = maXForm.eM22 = 1.0f; + maXForm.eM12 = maXForm.eM21 = maXForm.eDx = maXForm.eDy = 0.0f; + break; + } + + case ModifyWorldTransformMode::MWT_RIGHTMULTIPLY : + case ModifyWorldTransformMode::MWT_LEFTMULTIPLY : + { + const XForm* pLeft; + const XForm* pRight; + + if ( nMode == ModifyWorldTransformMode::MWT_LEFTMULTIPLY ) + { + pLeft = &rXForm; + pRight = &maXForm; + } + else + { + pLeft = &maXForm; + pRight = &rXForm; + } + + float aF[3][3]; + float bF[3][3]; + float cF[3][3]; + + aF[0][0] = pLeft->eM11; + aF[0][1] = pLeft->eM12; + aF[0][2] = 0; + aF[1][0] = pLeft->eM21; + aF[1][1] = pLeft->eM22; + aF[1][2] = 0; + aF[2][0] = pLeft->eDx; + aF[2][1] = pLeft->eDy; + aF[2][2] = 1; + + bF[0][0] = pRight->eM11; + bF[0][1] = pRight->eM12; + bF[0][2] = 0; + bF[1][0] = pRight->eM21; + bF[1][1] = pRight->eM22; + bF[1][2] = 0; + bF[2][0] = pRight->eDx; + bF[2][1] = pRight->eDy; + bF[2][2] = 1; + + int i, j, k; + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + cF[i][j] = 0; + for ( k = 0; k < 3; k++ ) + cF[i][j] += aF[i][k] * bF[k][j]; + } + } + maXForm.eM11 = cF[0][0]; + maXForm.eM12 = cF[0][1]; + maXForm.eM21 = cF[1][0]; + maXForm.eM22 = cF[1][1]; + maXForm.eDx = cF[2][0]; + maXForm.eDy = cF[2][1]; + break; + } + case ModifyWorldTransformMode::MWT_SET: + { + SetWorldTransform(rXForm); + break; + } + } + } + + void MtfTools::Push() // !! to be able to access the original ClipRegion it + { // is not allowed to use the MetaPushAction() + UpdateClipRegion(); // (the original clip region is on top of the stack) (SJ) + auto pSave = std::make_shared<SaveStruct>(); + + pSave->aLineStyle = maLineStyle; + pSave->aFillStyle = maFillStyle; + + pSave->aFont = maFont; + pSave->aTextColor = maTextColor; + pSave->nTextAlign = mnTextAlign; + pSave->nTextLayoutMode = mnTextLayoutMode; + pSave->eMapMode = meMapMode; + pSave->eGfxMode = meGfxMode; + pSave->nBkMode = mnBkMode; + pSave->aBkColor = maBkColor; + pSave->bClockWiseArcDirection = mbClockWiseArcDirection; + pSave->bFillStyleSelected = mbFillStyleSelected; + + pSave->aActPos = maActPos; + pSave->aXForm = maXForm; + pSave->eRasterOp = meRasterOp; + + pSave->nWinOrgX = mnWinOrgX; + pSave->nWinOrgY = mnWinOrgY; + pSave->nWinExtX = mnWinExtX; + pSave->nWinExtY = mnWinExtY; + pSave->nDevOrgX = mnDevOrgX; + pSave->nDevOrgY = mnDevOrgY; + pSave->nDevWidth = mnDevWidth; + pSave->nDevHeight = mnDevHeight; + + pSave->maPathObj = maPathObj; + pSave->maClipPath = maClipPath; + + SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode)); + SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode)); + SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY); + SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY); + SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY); + SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight); + SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor ); + mvSaveStack.push_back( pSave ); + } + + void MtfTools::Pop( const sal_Int32 nSavedDC ) + { + if ( nSavedDC == 0 ) + return; + + sal_Int32 aIndex; + if ( nSavedDC < 0 ) // WMF/EMF, if negative, nSavedDC represents an instance relative to the current state. + aIndex = static_cast< sal_Int32 >( mvSaveStack.size() ) + nSavedDC; + else + aIndex = nSavedDC; // WMF, if positive, nSavedDC represents a specific instance of the state to be restored. + if( aIndex < 0 ) + { + mvSaveStack.clear(); + return; + } + if( mvSaveStack.empty() || ( aIndex >= static_cast< sal_Int32 >( mvSaveStack.size() ) ) ) + return; + + mvSaveStack.resize( aIndex + 1 ); + // Backup the current data on the stack + std::shared_ptr<SaveStruct>& pSave( mvSaveStack.back() ); + + maLineStyle = pSave->aLineStyle; + maFillStyle = pSave->aFillStyle; + + maFont = pSave->aFont; + maTextColor = pSave->aTextColor; + mnTextAlign = pSave->nTextAlign; + mnTextLayoutMode = pSave->nTextLayoutMode; + mnBkMode = pSave->nBkMode; + meGfxMode = pSave->eGfxMode; + meMapMode = pSave->eMapMode; + maBkColor = pSave->aBkColor; + mbClockWiseArcDirection = pSave->bClockWiseArcDirection; + mbFillStyleSelected = pSave->bFillStyleSelected; + + maActPos = pSave->aActPos; + maXForm = pSave->aXForm; + meRasterOp = pSave->eRasterOp; + + mnWinOrgX = pSave->nWinOrgX; + mnWinOrgY = pSave->nWinOrgY; + mnWinExtX = pSave->nWinExtX; + mnWinExtY = pSave->nWinExtY; + mnDevOrgX = pSave->nDevOrgX; + mnDevOrgY = pSave->nDevOrgY; + mnDevWidth = pSave->nDevWidth; + mnDevHeight = pSave->nDevHeight; + + maPathObj = pSave->maPathObj; + if ( ! ( maClipPath == pSave->maClipPath ) ) + { + maClipPath = pSave->maClipPath; + mbClipNeedsUpdate = true; + } + if ( meLatestRasterOp != meRasterOp ) + { + mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) ); + meLatestRasterOp = meRasterOp; + } + + SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode)); + SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode)); + SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY); + SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY); + SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY); + SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight); + SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor ); + mvSaveStack.pop_back(); + } + + void MtfTools::AddFromGDIMetaFile( GDIMetaFile& rGDIMetaFile ) + { + rGDIMetaFile.Play( *mpGDIMetaFile ); + } + + void MtfTools::PassEMFPlusHeaderInfo() + { + EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS header info\n")); + + SvMemoryStream mem; + sal_Int32 nLeft, nRight, nTop, nBottom; + + nLeft = mrclFrame.Left(); + nTop = mrclFrame.Top(); + nRight = mrclFrame.Right(); + nBottom = mrclFrame.Bottom(); + + // emf header info + mem.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom ); + mem.WriteInt32( mnPixX ).WriteInt32( mnPixY ).WriteInt32( mnMillX ).WriteInt32( mnMillY ); + + float one, zero; + + one = 1; + zero = 0; + + // add transformation matrix to be used in vcl's metaact.cxx for + // rotate and scale operations + mem.WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero ).WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero ); + + // need to flush the stream, otherwise GetEndOfData will return 0 + // on windows where the function parameters are probably resolved in reverse order + mem.Flush(); + + mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS_HEADER_INFO", 0, static_cast<const sal_uInt8*>(mem.GetData()), mem.GetEndOfData() ) ); + mpGDIMetaFile->UseCanvas( true ); + } + + void MtfTools::PassEMFPlus( void const * pBuffer, sal_uInt32 nLength ) + { + EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS comment length %04x\n",(unsigned int) nLength)); + mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS", 0, static_cast<const sal_uInt8*>(pBuffer), nLength ) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/emfio/source/reader/wmfreader.cxx b/emfio/source/reader/wmfreader.cxx new file mode 100644 index 000000000..bb79033a3 --- /dev/null +++ b/emfio/source/reader/wmfreader.cxx @@ -0,0 +1,2015 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <wmfreader.hxx> +#include <emfreader.hxx> + +#include <cstdlib> +#include <memory> +#include <optional> +#include <o3tl/safeint.hxx> +#include <o3tl/unit_conversion.hxx> +#include <rtl/crc.h> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <osl/endian.h> +#include <vcl/gdimtf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/outdev.hxx> +#include <vcl/wmfexternal.hxx> +#include <tools/fract.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/BitmapTools.hxx> +#include <osl/thread.h> + +namespace +{ + // MS Windows defines + enum WMFRecords + { + W_META_EOF = 0x0000, + W_META_SETBKCOLOR = 0x0201, + W_META_SETBKMODE = 0x0102, + W_META_SETMAPMODE = 0x0103, + W_META_SETROP2 = 0x0104, + W_META_SETRELABS = 0x0105, + W_META_SETPOLYFILLMODE = 0x0106, + W_META_SETSTRETCHBLTMODE = 0x0107, + W_META_SETTEXTCHAREXTRA = 0x0108, + W_META_SETTEXTCOLOR = 0x0209, + W_META_SETTEXTJUSTIFICATION = 0x020A, + W_META_SETWINDOWORG = 0x020B, + W_META_SETWINDOWEXT = 0x020C, + W_META_SETVIEWPORTORG = 0x020D, + W_META_SETVIEWPORTEXT = 0x020E, + W_META_OFFSETWINDOWORG = 0x020F, + W_META_SCALEWINDOWEXT = 0x0410, + W_META_OFFSETVIEWPORTORG = 0x0211, + W_META_SCALEVIEWPORTEXT = 0x0412, + W_META_LINETO = 0x0213, + W_META_MOVETO = 0x0214, + W_META_EXCLUDECLIPRECT = 0x0415, + W_META_INTERSECTCLIPRECT = 0x0416, + W_META_ARC = 0x0817, + W_META_ELLIPSE = 0x0418, + W_META_FLOODFILL = 0x0419, + W_META_PIE = 0x081A, + W_META_RECTANGLE = 0x041B, + W_META_ROUNDRECT = 0x061C, + W_META_PATBLT = 0x061D, + W_META_SAVEDC = 0x001E, + W_META_SETPIXEL = 0x041F, + W_META_OFFSETCLIPRGN = 0x0220, + W_META_TEXTOUT = 0x0521, + W_META_BITBLT = 0x0922, + W_META_STRETCHBLT = 0x0B23, + W_META_POLYGON = 0x0324, + W_META_POLYLINE = 0x0325, + W_META_ESCAPE = 0x0626, + W_META_RESTOREDC = 0x0127, + W_META_FILLREGION = 0x0228, + W_META_FRAMEREGION = 0x0429, + W_META_INVERTREGION = 0x012A, + W_META_PAINTREGION = 0x012B, + W_META_SELECTCLIPREGION = 0x012C, + W_META_SELECTOBJECT = 0x012D, + W_META_SETTEXTALIGN = 0x012E, + W_META_DRAWTEXT = 0x062F, + W_META_CHORD = 0x0830, + W_META_SETMAPPERFLAGS = 0x0231, + W_META_EXTTEXTOUT = 0x0a32, + W_META_SETDIBTODEV = 0x0d33, + W_META_SELECTPALETTE = 0x0234, + W_META_REALIZEPALETTE = 0x0035, + W_META_ANIMATEPALETTE = 0x0436, + W_META_SETPALENTRIES = 0x0037, + W_META_POLYPOLYGON = 0x0538, + W_META_RESIZEPALETTE = 0x0139, + W_META_DIBBITBLT = 0x0940, + W_META_DIBSTRETCHBLT = 0x0b41, + W_META_DIBCREATEPATTERNBRUSH = 0x0142, + W_META_STRETCHDIB = 0x0f43, + W_META_EXTFLOODFILL = 0x0548, + W_META_RESETDC = 0x014C, + W_META_STARTDOC = 0x014D, + W_META_STARTPAGE = 0x004F, + W_META_ENDPAGE = 0x0050, + W_META_ABORTDOC = 0x0052, + W_META_ENDDOC = 0x005E, + W_META_DELETEOBJECT = 0x01f0, + W_META_CREATEPALETTE = 0x00f7, + W_META_CREATEBRUSH = 0x00F8, + W_META_CREATEPATTERNBRUSH = 0x01F9, + W_META_CREATEPENINDIRECT = 0x02FA, + W_META_CREATEFONTINDIRECT = 0x02FB, + W_META_CREATEBRUSHINDIRECT = 0x02FC, + W_META_CREATEBITMAPINDIRECT = 0x02FD, + W_META_CREATEBITMAP = 0x06FE, + W_META_CREATEREGION = 0x06FF + }; + + void GetWinExtMax(const Point& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode eMapMode) + { + Point aSource(rSource); + if (eMapMode == emfio::MappingMode::MM_HIMETRIC) + aSource.setY( -rSource.Y() ); + if (aSource.X() < rPlaceableBound.Left()) + rPlaceableBound.SetLeft( aSource.X() ); + if (aSource.X() > rPlaceableBound.Right()) + rPlaceableBound.SetRight( aSource.X() ); + if (aSource.Y() < rPlaceableBound.Top()) + rPlaceableBound.SetTop( aSource.Y() ); + if (aSource.Y() > rPlaceableBound.Bottom()) + rPlaceableBound.SetBottom( aSource.Y() ); + } + + void GetWinExtMax(const tools::Rectangle& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode nMapMode) + { + GetWinExtMax(rSource.TopLeft(), rPlaceableBound, nMapMode); + GetWinExtMax(rSource.BottomRight(), rPlaceableBound, nMapMode); + } + + const char * + record_type_name(sal_uInt16 nRecType) + { + #ifndef SAL_LOG_INFO + (void) nRecType; + return ""; + #else + switch( nRecType ) + { + case W_META_SETBKCOLOR: return "META_SETBKCOLOR"; + case W_META_SETBKMODE: return "META_SETBKMODE"; + case W_META_SETMAPMODE: return "META_SETMAPMODE"; + case W_META_SETROP2: return "META_SETROP2"; + case W_META_SETRELABS: return "META_SETRELABS"; + case W_META_SETPOLYFILLMODE: return "META_SETPOLYFILLMODE"; + case W_META_SETSTRETCHBLTMODE: return "META_SETSTRETCHBLTMODE"; + case W_META_SETTEXTCHAREXTRA: return "META_SETTEXTCHAREXTRA"; + case W_META_SETTEXTCOLOR: return "META_SETTEXTCOLOR"; + case W_META_SETTEXTJUSTIFICATION: return "META_SETTEXTJUSTIFICATION"; + case W_META_SETWINDOWORG: return "META_SETWINDOWORG"; + case W_META_SETWINDOWEXT: return "META_SETWINDOWEXT"; + case W_META_SETVIEWPORTORG: return "META_SETVIEWPORTORG"; + case W_META_SETVIEWPORTEXT: return "META_SETVIEWPORTEXT"; + case W_META_OFFSETWINDOWORG: return "META_OFFSETWINDOWORG"; + case W_META_SCALEWINDOWEXT: return "META_SCALEWINDOWEXT"; + case W_META_OFFSETVIEWPORTORG: return "META_OFFSETVIEWPORTORG"; + case W_META_SCALEVIEWPORTEXT: return "META_SCALEVIEWPORTEXT"; + case W_META_LINETO: return "META_LINETO"; + case W_META_MOVETO: return "META_MOVETO"; + case W_META_EXCLUDECLIPRECT: return "META_EXCLUDECLIPRECT"; + case W_META_INTERSECTCLIPRECT: return "META_INTERSECTCLIPRECT"; + case W_META_ARC: return "META_ARC"; + case W_META_ELLIPSE: return "META_ELLIPSE"; + case W_META_FLOODFILL: return "META_FLOODFILL"; + case W_META_PIE: return "META_PIE"; + case W_META_RECTANGLE: return "META_RECTANGLE"; + case W_META_ROUNDRECT: return "META_ROUNDRECT"; + case W_META_PATBLT: return "META_PATBLT"; + case W_META_SAVEDC: return "META_SAVEDC"; + case W_META_SETPIXEL: return "META_SETPIXEL"; + case W_META_OFFSETCLIPRGN: return "META_OFFSETCLIPRGN"; + case W_META_TEXTOUT: return "META_TEXTOUT"; + case W_META_BITBLT: return "META_BITBLT"; + case W_META_STRETCHBLT: return "META_STRETCHBLT"; + case W_META_POLYGON: return "META_POLYGON"; + case W_META_POLYLINE: return "META_POLYLINE"; + case W_META_ESCAPE: return "META_ESCAPE"; + case W_META_RESTOREDC: return "META_RESTOREDC"; + case W_META_FILLREGION: return "META_FILLREGION"; + case W_META_FRAMEREGION: return "META_FRAMEREGION"; + case W_META_INVERTREGION: return "META_INVERTREGION"; + case W_META_PAINTREGION: return "META_PAINTREGION"; + case W_META_SELECTCLIPREGION: return "META_SELECTCLIPREGION"; + case W_META_SELECTOBJECT: return "META_SELECTOBJECT"; + case W_META_SETTEXTALIGN: return "META_SETTEXTALIGN"; + case W_META_DRAWTEXT: return "META_DRAWTEXT"; + case W_META_CHORD: return "META_CHORD"; + case W_META_SETMAPPERFLAGS: return "META_SETMAPPERFLAGS"; + case W_META_EXTTEXTOUT: return "META_EXTTEXTOUT"; + case W_META_SETDIBTODEV: return "META_SETDIBTODEV"; + case W_META_SELECTPALETTE: return "META_SELECTPALETTE"; + case W_META_REALIZEPALETTE: return "META_REALIZEPALETTE"; + case W_META_ANIMATEPALETTE: return "META_ANIMATEPALETTE"; + case W_META_SETPALENTRIES: return "META_SETPALENTRIES"; + case W_META_POLYPOLYGON: return "META_POLYPOLYGON"; + case W_META_RESIZEPALETTE: return "META_RESIZEPALETTE"; + case W_META_DIBBITBLT: return "META_DIBBITBLT"; + case W_META_DIBSTRETCHBLT: return "META_DIBSTRETCHBLT"; + case W_META_DIBCREATEPATTERNBRUSH: return "META_DIBCREATEPATTERNBRUSH"; + case W_META_STRETCHDIB: return "META_STRETCHDIB"; + case W_META_EXTFLOODFILL: return "META_EXTFLOODFILL"; + case W_META_RESETDC: return "META_RESETDC"; + case W_META_STARTDOC: return "META_STARTDOC"; + case W_META_STARTPAGE: return "META_STARTPAGE"; + case W_META_ENDPAGE: return "META_ENDPAGE"; + case W_META_ABORTDOC: return "META_ABORTDOC"; + case W_META_ENDDOC: return "META_ENDDOC"; + case W_META_DELETEOBJECT: return "META_DELETEOBJECT"; + case W_META_CREATEPALETTE: return "META_CREATEPALETTE"; + case W_META_CREATEBRUSH: return "META_CREATEBRUSH"; + case W_META_CREATEPATTERNBRUSH: return "META_CREATEPATTERNBRUSH"; + case W_META_CREATEPENINDIRECT: return "META_CREATEPENINDIRECT"; + case W_META_CREATEFONTINDIRECT: return "META_CREATEFONTINDIRECT"; + case W_META_CREATEBRUSHINDIRECT: return "META_CREATEBRUSHINDIRECT"; + case W_META_CREATEBITMAPINDIRECT: return "META_CREATEBITMAPINDIRECT"; + case W_META_CREATEBITMAP: return "META_CREATEBITMAP"; + case W_META_CREATEREGION: return "META_CREATEREGION"; + default: + // Yes, return a pointer to a static buffer. This is a very + // local debugging output function, so no big deal. + static char buffer[11]; + sprintf(buffer, "0x%08" SAL_PRIxUINT32, sal_uInt32(nRecType)); + return buffer; + } + #endif + } + +} + +namespace emfio +{ + inline Point WmfReader::ReadPoint() + { + short nX = 0, nY = 0; + mpInputStream->ReadInt16( nX ).ReadInt16( nY ); + return Point( nX, nY ); + } + + inline Point WmfReader::ReadYX() + { + short nX = 0, nY = 0; + mpInputStream->ReadInt16( nY ).ReadInt16( nX ); + return Point( nX, nY ); + } + + tools::Rectangle WmfReader::ReadRectangle() + { + Point aBR, aTL; + aBR = ReadYX(); + aTL = ReadYX(); + aBR.AdjustX( -1 ); + aBR.AdjustY( -1 ); + if (aTL.X() > aBR.X() || aTL.Y() > aBR.Y()) + { + SAL_WARN("emfio", "broken rectangle"); + return tools::Rectangle::Justify(aTL, aBR); + } + return tools::Rectangle( aTL, aBR ); + } + + Size WmfReader::ReadYXExt() + { + short nW=0, nH=0; + mpInputStream->ReadInt16( nH ).ReadInt16( nW ); + return Size( nW, nH ); + } + + void WmfReader::ReadRecordParams( sal_uInt32 nRecordSize, sal_uInt16 nFunc ) + { + SAL_INFO("emfio", "\t" << record_type_name(nFunc)); + switch( nFunc ) + { + case W_META_SETBKCOLOR: + { + SetBkColor( ReadColor() ); + } + break; + + case W_META_SETBKMODE: + { + sal_uInt16 nDat = 0; + mpInputStream->ReadUInt16( nDat ); + SetBkMode( static_cast<BackgroundMode>(nDat) ); + } + break; + + // !!! + case W_META_SETMAPMODE: + { + sal_Int16 nMapMode = 0; + mpInputStream->ReadInt16( nMapMode ); + SetMapMode( static_cast<MappingMode>(nMapMode) ); + } + break; + + case W_META_SETROP2: + { + sal_uInt16 nROP2 = 0; + mpInputStream->ReadUInt16( nROP2 ); + SetRasterOp( static_cast<WMFRasterOp>(nROP2) ); + } + break; + + case W_META_SETTEXTCOLOR: + { + SetTextColor( ReadColor() ); + } + break; + + case W_META_SETWINDOWORG: + { + SetWinOrg( ReadYX() ); + } + break; + + case W_META_SETWINDOWEXT: + { + short nWidth = 0, nHeight = 0; + mpInputStream->ReadInt16( nHeight ).ReadInt16( nWidth ); + SetWinExt( Size( nWidth, nHeight ) ); + } + break; + + case W_META_OFFSETWINDOWORG: + { + short nXAdd = 0, nYAdd = 0; + mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd ); + SetWinOrgOffset( nXAdd, nYAdd ); + } + break; + + case W_META_SCALEWINDOWEXT: + { + short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0; + mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum ); + if (!nYDenom || !nXDenom) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + ScaleWinExt( static_cast<double>(nXNum) / nXDenom, static_cast<double>(nYNum) / nYDenom ); + } + break; + + case W_META_SETVIEWPORTORG: + case W_META_SETVIEWPORTEXT: + break; + + case W_META_OFFSETVIEWPORTORG: + { + short nXAdd = 0, nYAdd = 0; + mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd ); + SetDevOrgOffset( nXAdd, nYAdd ); + } + break; + + case W_META_SCALEVIEWPORTEXT: + { + short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0; + mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum ); + if (!nYDenom || !nXDenom) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + ScaleDevExt( static_cast<double>(nXNum) / nXDenom, static_cast<double>(nYNum) / nYDenom ); + } + break; + + case W_META_LINETO: + { + LineTo( ReadYX() ); + } + break; + + case W_META_MOVETO: + { + MoveTo( ReadYX() ); + } + break; + + case W_META_INTERSECTCLIPRECT: + { + IntersectClipRect( ReadRectangle() ); + } + break; + + case W_META_RECTANGLE: + { + DrawRect( ReadRectangle() ); + } + break; + + case W_META_ROUNDRECT: + { + Size aSize( ReadYXExt() ); + DrawRoundRect( ReadRectangle(), Size( aSize.Width() / 2, aSize.Height() / 2 ) ); + } + break; + + case W_META_ELLIPSE: + { + DrawEllipse( ReadRectangle() ); + } + break; + + case W_META_ARC: + { + Point aEnd( ReadYX() ); + Point aStart( ReadYX() ); + tools::Rectangle aRect( ReadRectangle() ); + aRect.Justify(); + DrawArc( aRect, aStart, aEnd ); + } + break; + + case W_META_PIE: + { + Point aEnd( ReadYX() ); + Point aStart( ReadYX() ); + tools::Rectangle aRect( ReadRectangle() ); + aRect.Justify(); + + // #i73608# OutputDevice deviates from WMF + // semantics. start==end means full ellipse here. + if( aStart == aEnd ) + DrawEllipse( aRect ); + else + DrawPie( aRect, aStart, aEnd ); + } + break; + + case W_META_CHORD: + { + Point aEnd( ReadYX() ); + Point aStart( ReadYX() ); + tools::Rectangle aRect( ReadRectangle() ); + aRect.Justify(); + DrawChord( aRect, aStart, aEnd ); + } + break; + + case W_META_POLYGON: + { + bool bRecordOk = true; + + sal_uInt16 nPoints(0); + mpInputStream->ReadUInt16(nPoints); + + if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + } + else + { + tools::Polygon aPoly(nPoints); + for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i) + aPoly[ i ] = ReadPoint(); + DrawPolygon(std::move(aPoly), false/*bRecordPath*/); + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polygon record has more points than we can handle"); + + bRecordOk &= mpInputStream->good(); + + if (!bRecordOk) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + } + break; + + case W_META_POLYPOLYGON: + { + sal_uInt16 nPolyCount(0); + // Number of polygons: + mpInputStream->ReadUInt16( nPolyCount ); + if (nPolyCount && mpInputStream->good()) + { + bool bRecordOk = true; + if (nPolyCount > mpInputStream->remainingSize() / sizeof(sal_uInt16)) + { + break; + } + + // Number of points of each polygon. Determine total number of points + std::unique_ptr<sal_uInt16[]> xPolygonPointCounts(new sal_uInt16[nPolyCount]); + sal_uInt16* pnPoints = xPolygonPointCounts.get(); + tools::PolyPolygon aPolyPoly(nPolyCount); + sal_uInt16 nPoints = 0; + for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a) + { + mpInputStream->ReadUInt16( pnPoints[a] ); + + if (pnPoints[a] > SAL_MAX_UINT16 - nPoints) + { + bRecordOk = false; + break; + } + + nPoints += pnPoints[a]; + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle"); + + bRecordOk &= mpInputStream->good(); + + if (!bRecordOk) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + + // Polygon points are: + for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a) + { + const sal_uInt16 nPointCount(pnPoints[a]); + + if (nPointCount > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + break; + } + + std::unique_ptr<Point[]> xPolygonPoints(new Point[nPointCount]); + Point* pPtAry = xPolygonPoints.get(); + + for(sal_uInt16 b(0); b < nPointCount && mpInputStream->good(); ++b) + { + pPtAry[b] = ReadPoint(); + } + + aPolyPoly.Insert( tools::Polygon(nPointCount, pPtAry) ); + } + + bRecordOk &= mpInputStream->good(); + + if (!bRecordOk) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + + DrawPolyPolygon( aPolyPoly ); + } + } + break; + + case W_META_POLYLINE: + { + bool bRecordOk = true; + + sal_uInt16 nPoints(0); + mpInputStream->ReadUInt16(nPoints); + + if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + } + else + { + tools::Polygon aPoly(nPoints); + for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i) + aPoly[ i ] = ReadPoint(); + DrawPolyLine( std::move(aPoly) ); + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polyline record has more points than we can handle"); + + bRecordOk &= mpInputStream->good(); + + if (!bRecordOk) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + } + break; + + case W_META_SAVEDC: + { + Push(); + } + break; + + case W_META_RESTOREDC: + { + sal_Int16 nSavedDC(0); + mpInputStream->ReadInt16( nSavedDC ); + SAL_INFO( "emfio", "\t\t SavedDC: " << nSavedDC ); + Pop( nSavedDC ); + } + break; + + case W_META_SETPIXEL: + { + const Color aColor = ReadColor(); + DrawPixel( ReadYX(), aColor ); + } + break; + + case W_META_OFFSETCLIPRGN: + { + MoveClipRegion( ReadYXExt() ); + } + break; + + case W_META_TEXTOUT: + { + //record is Recordsize, RecordFunction, StringLength, <String>, YStart, XStart + const sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 4 * sizeof(sal_uInt16); + const sal_uInt32 nRecSize = mnRecSize * 2; + + if (nRecSize < nNonStringLen) + { + SAL_WARN("emfio", "W_META_TEXTOUT too short"); + break; + } + + sal_uInt16 nLength = 0; + mpInputStream->ReadUInt16(nLength); + sal_uInt16 nStoredLength = (nLength + 1) &~ 1; + + if (nRecSize - nNonStringLen < nStoredLength) + { + SAL_WARN("emfio", "W_META_TEXTOUT too short, truncating string"); + nLength = nStoredLength = nRecSize - nNonStringLen; + } + + if (nLength) + { + std::vector<char> aChars(nStoredLength); + nLength = std::min<sal_uInt16>(nLength, mpInputStream->ReadBytes(aChars.data(), aChars.size())); + OUString aText(aChars.data(), nLength, GetCharSet()); + Point aPosition( ReadYX() ); + DrawText( aPosition, aText ); + } + } + break; + + case W_META_EXTTEXTOUT: + { + //record is Recordsize, RecordFunction, Y, X, StringLength, options, maybe rectangle, <String> + sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 5 * sizeof(sal_uInt16); + const sal_uInt32 nRecSize = mnRecSize * 2; + + if (nRecSize < nNonStringLen) + { + SAL_WARN("emfio", "W_META_EXTTEXTOUT too short"); + break; + } + + auto nRecordPos = mpInputStream->Tell() - 6; + Point aPosition = ReadYX(); + sal_uInt16 nLen = 0, nOptions = 0; + mpInputStream->ReadUInt16( nLen ).ReadUInt16( nOptions ); + SAL_INFO( "emfio", "\t\t\t Pos: " << aPosition.getX() << ":" << aPosition.getY() << " String Length: " << nLen << " Options: " << nOptions ); + tools::Rectangle aRect; + if ( ( nOptions & ETO_CLIPPED ) || ( nOptions & ETO_OPAQUE ) ) + { + nNonStringLen += 2 * sizeof(sal_uInt16); + + if (nRecSize < nNonStringLen) + { + SAL_WARN("emfio", "W_META_TEXTOUT too short"); + break; + } + const Point aTopLeft = ReadPoint(); + const Point aBottomRight = ReadPoint(); + aRect = tools::Rectangle( aTopLeft, aBottomRight ); + if ( nOptions & ETO_OPAQUE ) + DrawRectWithBGColor( aRect ); + SAL_INFO( "emfio", "\t\t\t Rectangle : " << aTopLeft.getX() << ":" << aTopLeft.getY() << ", " << aBottomRight.getX() << ":" << aBottomRight.getY() ); + } + + vcl::text::ComplexTextLayoutFlags nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + if ( nOptions & ETO_RTLREADING ) + nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + SetTextLayoutMode( nTextLayoutMode ); + SAL_WARN_IF( ( nOptions & ( ETO_PDY | ETO_GLYPH_INDEX ) ) != 0, "emfio", "SJ: ETO_PDY || ETO_GLYPH_INDEX in WMF" ); + + // output only makes sense if the text contains characters + if (nLen) + { + sal_Int32 nOriginalTextLen = nLen; + sal_Int32 nOriginalBlockLen = ( nOriginalTextLen + 1 ) &~ 1; + + auto nMaxStreamPos = nRecordPos + nRecSize; + auto nRemainingSize = std::min(mpInputStream->remainingSize(), nMaxStreamPos - mpInputStream->Tell()); + if (nRemainingSize < o3tl::make_unsigned(nOriginalBlockLen)) + { + SAL_WARN("emfio", "exttextout record claimed more data than the stream can provide"); + nOriginalTextLen = nOriginalBlockLen = nRemainingSize; + } + + std::vector<char> pChar(nOriginalBlockLen); + mpInputStream->ReadBytes(pChar.data(), nOriginalBlockLen); + OUString aText(pChar.data(), nOriginalTextLen, GetCharSet()); // after this conversion the text may contain + sal_Int32 nNewTextLen = aText.getLength(); // less character (japanese version), so the + // dxAry will not fit + if ( nNewTextLen ) + { + if ( nOptions & ETO_CLIPPED ) + { + Push(); // Save the current clip. It will be restored after text drawing + IntersectClipRect( aRect ); + } + SAL_INFO( "emfio", "\t\t\t Text : " << aText ); + std::vector<sal_Int32> aDXAry; + std::unique_ptr<tools::Long[]> pDYAry; + auto nDxArySize = nMaxStreamPos - mpInputStream->Tell(); + auto nDxAryEntries = nDxArySize >> 1; + bool bUseDXAry = false; + + if ( ( ( nDxAryEntries % nOriginalTextLen ) == 0 ) && ( nNewTextLen <= nOriginalTextLen ) ) + { + sal_Int32 i; // needed just outside the for + aDXAry.resize( nNewTextLen ); + if ( nOptions & ETO_PDY ) + { + pDYAry.reset(new tools::Long[ nNewTextLen ]); + } + for (i = 0; i < nNewTextLen; i++ ) + { + if ( mpInputStream->Tell() >= nMaxStreamPos ) + break; + sal_Int32 nDxCount = 1; + if ( nNewTextLen != nOriginalTextLen ) + { + sal_Unicode cUniChar = aText[i]; + OString aTmp(&cUniChar, 1, GetCharSet()); + if ( aTmp.getLength() > 1 ) + { + nDxCount = aTmp.getLength(); + } + } + + sal_Int16 nDx = 0, nDy = 0; + while ( nDxCount-- ) + { + if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos ) + break; + sal_Int16 nDxTmp = 0; + mpInputStream->ReadInt16(nDxTmp); + nDx += nDxTmp; + if ( nOptions & ETO_PDY ) + { + if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos ) + break; + sal_Int16 nDyTmp = 0; + mpInputStream->ReadInt16(nDyTmp); + nDy += nDyTmp; + } + } + + aDXAry[ i ] = nDx; + if ( nOptions & ETO_PDY ) + { + pDYAry[i] = nDy; + } + } + if ( i == nNewTextLen ) + bUseDXAry = true; + } + if ( bUseDXAry ) + DrawText( aPosition, aText, &aDXAry, pDYAry.get() ); + else + DrawText( aPosition, aText ); + if ( nOptions & ETO_CLIPPED ) + Pop(); + } + } + } + break; + + case W_META_SELECTOBJECT: + case W_META_SELECTPALETTE: + { + sal_uInt16 nObjIndex = 0; + mpInputStream->ReadUInt16( nObjIndex ); + SelectObject( nObjIndex ); + } + break; + + case W_META_SETTEXTALIGN: + { + sal_uInt16 nAlign = 0; + mpInputStream->ReadUInt16( nAlign ); + SetTextAlign( nAlign ); + } + break; + + case W_META_BITBLT: + case W_META_STRETCHBLT: + { + sal_uInt32 nRasterOperation = 0; + sal_Int16 nSrcHeight = 0, nSrcWidth = 0, nYSrc, nXSrc, nSye, nSxe, nBitmapType, nWidth, nHeight, nBytesPerScan; + sal_uInt8 nPlanes, nBitCount; + const bool bNoSourceBitmap = ( nRecordSize == ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 ); + + mpInputStream->ReadUInt32( nRasterOperation ); + SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap); + + if( nFunc == W_META_STRETCHBLT ) + mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth ); + + mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc ); + if ( bNoSourceBitmap ) + mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) + mpInputStream->ReadInt16( nSye ).ReadInt16( nSxe ); + Point aPoint( ReadYX() ); // The upper-left corner of the destination rectangle. + mpInputStream->ReadInt16( nBitmapType ).ReadInt16( nWidth ).ReadInt16( nHeight ).ReadInt16( nBytesPerScan ).ReadUChar( nPlanes ).ReadUChar( nBitCount ); + + SAL_INFO("emfio", "\t\t Bitmap type:" << nBitmapType << " Width:" << nWidth << " Height:" << nHeight << " WidthBytes:" << nBytesPerScan << " Planes: " << static_cast< sal_uInt16 >( nPlanes ) << " BitCount: " << static_cast< sal_uInt16 >( nBitCount ) ); + if (!mpInputStream->good() || bNoSourceBitmap || nBitCount == 4 || nBitCount == 8 || nPlanes != 1) + { + SAL_WARN("emfio", "\t\t TODO The unsupported Bitmap record. Please fill a bug."); + break; + } + const vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat( nBitCount ); + bool bOk = nWidth > 0 && nHeight > 0 && nBytesPerScan > 0 && ePixelFormat != vcl::PixelFormat::INVALID; + if (bOk) + { + // must be enough data to fulfil the request + bOk = o3tl::make_unsigned( nBytesPerScan ) <= mpInputStream->remainingSize() / nHeight; + } + if (bOk) + { + // scanline must be large enough to provide all pixels + bOk = nBytesPerScan >= nWidth * nBitCount / 8; + } + if (bOk) + { + std::unique_ptr< sal_uInt8[] > pData; + pData.reset( new sal_uInt8[ nHeight * nBytesPerScan ] ); + mpInputStream->ReadBytes( pData.get(), nHeight * nBytesPerScan ); + BitmapEx aBitmap = vcl::bitmap::CreateFromData( pData.get(), nWidth, nHeight, nBytesPerScan, ePixelFormat, true ); + if ( nSye && nSxe && + ( nXSrc + nSxe <= nWidth ) && + ( nYSrc + nSye <= nHeight ) ) + { + tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSxe, nSye ) ); + aBitmap.Crop( aCropRect ); + } + tools::Rectangle aDestRect( aPoint, Size( nSxe, nSye ) ); + maBmpSaveList.emplace_back(aBitmap, aDestRect, nRasterOperation); + } + } + break; + + case W_META_DIBBITBLT: + case W_META_DIBSTRETCHBLT: + case W_META_STRETCHDIB: + { + sal_uInt32 nRasterOperation = 0; + sal_uInt16 nColorUsage = 0; + sal_Int16 nSrcHeight = 0, nSrcWidth = 0, nYSrc = 0, nXSrc = 0; + Bitmap aBmp; + const bool bNoSourceBitmap = ( nFunc != W_META_STRETCHDIB ) && ( nRecordSize == ( ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 ) ); + + mpInputStream->ReadUInt32( nRasterOperation ); + SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap); + if( nFunc == W_META_STRETCHDIB ) + mpInputStream->ReadUInt16( nColorUsage ); + + // nSrcHeight and nSrcWidth is the number of pixels that has to been used + // If they are set to zero, it is as indicator not to scale the bitmap later + if( nFunc == W_META_DIBSTRETCHBLT || + nFunc == W_META_STRETCHDIB ) + mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth ); + + // nYSrc and nXSrc is the offset of the first pixel + mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc ); + + if ( bNoSourceBitmap ) + mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) + + Size aDestSize( ReadYXExt() ); + if ( aDestSize.Width() && aDestSize.Height() ) // #92623# do not try to read buggy bitmaps + { + tools::Rectangle aDestRect( ReadYX(), aDestSize ); + if ( !bNoSourceBitmap ) + { + // tdf#142625 Read the DIBHeader and check if bitmap is supported + // If bitmap is not supported don't run ReadDIB, as it will interrupt image processing + const auto nOldPos(mpInputStream->Tell()); + sal_uInt32 nHeaderSize(0); + mpInputStream->ReadUInt32( nHeaderSize ); + if ( nHeaderSize == 0xC ) // BitmapCoreHeader + mpInputStream->SeekRel( 6 ); // skip Width (16), Height (16), Planes (16) + else + mpInputStream->SeekRel( 10 ); // skip Width (32), Height (32), Planes (16) + sal_uInt16 nBitCount(0); + mpInputStream->ReadUInt16( nBitCount ); + if ( nBitCount == 0 ) // TODO Undefined BitCount (JPEG/PNG), which are not supported + break; + mpInputStream->Seek(nOldPos); + + if ( !ReadDIB( aBmp, *mpInputStream, false ) ) + SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." ); + } + // test if it is sensible to crop + if ( nSrcHeight && nSrcWidth && + ( nXSrc + nSrcWidth <= aBmp.GetSizePixel().Width() ) && + ( nYSrc + nSrcHeight <= aBmp.GetSizePixel().Height() ) ) + { + tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSrcWidth, nSrcHeight ) ); + aBmp.Crop( aCropRect ); + } + + maBmpSaveList.emplace_back(aBmp, aDestRect, nRasterOperation); + } + } + break; + + case W_META_DIBCREATEPATTERNBRUSH: + { + Bitmap aBmp; + sal_uInt32 nRed(0), nGreen(0), nBlue(0), nCount(1); + sal_uInt16 nStyle(0), nColorUsage(0); + + mpInputStream->ReadUInt16( nStyle ).ReadUInt16( nColorUsage ); + BrushStyle eStyle = static_cast<BrushStyle>(nStyle); + SAL_INFO( "emfio", "\t\t Style:" << nStyle << ", ColorUsage: " << nColorUsage ); + if ( eStyle == BrushStyle::BS_PATTERN ) // TODO tdf#142625 Add support for pattern + { + SAL_WARN( "emfio", "\tTODO: Pattern brush style is not supported." ); + CreateObject(); + break; + } + if ( !ReadDIB( aBmp, *mpInputStream, false ) ) + SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." ); + if ( !aBmp.IsEmpty() ) + { + Bitmap::ScopedReadAccess pBmp(aBmp); + for ( tools::Long y = 0; y < pBmp->Height(); y++ ) + { + for ( tools::Long x = 0; x < pBmp->Width(); x++ ) + { + const BitmapColor aColor( pBmp->GetColor( y, x ) ); + + nRed += aColor.GetRed(); + nGreen += aColor.GetGreen(); + nBlue += aColor.GetBlue(); + } + } + nCount = pBmp->Height() * pBmp->Width(); + if ( !nCount ) + nCount++; + } + Color aColor( static_cast<sal_uInt8>( nRed / nCount ), static_cast<sal_uInt8>( nGreen / nCount ), static_cast<sal_uInt8>( nBlue / nCount ) ); + CreateObject(std::make_unique<WinMtfFillStyle>( aColor, false )); + } + break; + + case W_META_DELETEOBJECT: + { + sal_uInt16 nIndex = 0; + mpInputStream->ReadUInt16( nIndex ); + DeleteObject( nIndex ); + } + break; + + case W_META_CREATEPALETTE: + { + sal_uInt16 nStart = 0; + sal_uInt16 nNumberOfEntries = 0; + mpInputStream->ReadUInt16( nStart ); + mpInputStream->ReadUInt16( nNumberOfEntries ); + + SAL_INFO("emfio", "\t\t Start 0x" << std::hex << nStart << std::dec << ", Number of entries: " << nNumberOfEntries); + sal_uInt32 nPalleteEntry; + std::vector< Color > aPaletteColors; + for (sal_uInt16 i = 0; i < nNumberOfEntries; ++i) + { + //PALETTEENTRY: Values, Blue, Green, Red + mpInputStream->ReadUInt32( nPalleteEntry ); + SAL_INFO("emfio", "\t\t " << i << ". Palette entry: " << std::setw(10) << std::showbase <<std::hex << nPalleteEntry << std::dec ); + aPaletteColors.push_back(Color(static_cast<sal_uInt8>(nPalleteEntry), static_cast<sal_uInt8>(nPalleteEntry >> 8), static_cast<sal_uInt8>(nPalleteEntry >> 16))); + } + CreateObject(std::make_unique<WinMtfPalette>( aPaletteColors )); + } + break; + + case W_META_CREATEBRUSH: + { + SAL_WARN( "emfio", "TODO: Not implemented. Please fill the bug report" ); + CreateObject(std::make_unique<WinMtfFillStyle>( COL_WHITE, false )); + } + break; + + case W_META_CREATEPATTERNBRUSH: + { + SAL_WARN( "emfio", "TODO: Not implemented. Please fill the bug report" ); + CreateObject(std::make_unique<WinMtfFillStyle>( COL_WHITE, false )); + } + break; + + case W_META_CREATEPENINDIRECT: + { + LineInfo aLineInfo; + sal_uInt16 nStyle = 0; + sal_uInt16 nWidth = 0; + sal_uInt16 nHeight = 0; + + mpInputStream->ReadUInt16(nStyle); + mpInputStream->ReadUInt16(nWidth); + mpInputStream->ReadUInt16(nHeight); + CreateObject(std::make_unique<WinMtfLineStyle>(ReadColor(), nStyle, nWidth)); + } + break; + + case W_META_CREATEBRUSHINDIRECT: + { + sal_uInt16 nBrushStyle = 0; + mpInputStream->ReadUInt16( nBrushStyle ); + BrushStyle eBrushStyle = static_cast<BrushStyle>(nBrushStyle); + CreateObject(std::make_unique<WinMtfFillStyle>( ReadColor(), ( eBrushStyle == BrushStyle::BS_NULL ) )); + SAL_WARN_IF( (eBrushStyle != BrushStyle::BS_SOLID) && (eBrushStyle != BrushStyle::BS_NULL), "emfio", "TODO: Brush style not implemented. Please fill the bug report" ); + } + break; + + case W_META_CREATEFONTINDIRECT: + { + Size aFontSize; + char lfFaceName[LF_FACESIZE+1]; + sal_Int16 lfEscapement = 0; + sal_Int16 lfOrientation = 0; + sal_Int16 lfWeight = 0; + + LOGFONTW aLogFont; + aFontSize = ReadYXExt(); + mpInputStream->ReadInt16( lfEscapement ); + mpInputStream->ReadInt16( lfOrientation ); + mpInputStream->ReadInt16( lfWeight ); + mpInputStream->ReadUChar( aLogFont.lfItalic ); + mpInputStream->ReadUChar( aLogFont.lfUnderline ); + mpInputStream->ReadUChar( aLogFont.lfStrikeOut ); + mpInputStream->ReadUChar( aLogFont.lfCharSet ); + mpInputStream->ReadUChar( aLogFont.lfOutPrecision ); + mpInputStream->ReadUChar( aLogFont.lfClipPrecision ); + mpInputStream->ReadUChar( aLogFont.lfQuality ); + mpInputStream->ReadUChar( aLogFont.lfPitchAndFamily ); + size_t nRet = mpInputStream->ReadBytes( lfFaceName, LF_FACESIZE ); + lfFaceName[nRet] = 0; + aLogFont.lfWidth = aFontSize.Width(); + aLogFont.lfHeight = aFontSize.Height(); + aLogFont.lfEscapement = lfEscapement; + aLogFont.lfOrientation = lfOrientation; + aLogFont.lfWeight = lfWeight; + + rtl_TextEncoding eCharSet; + if ( ( aLogFont.lfCharSet == OEM_CHARSET ) || ( aLogFont.lfCharSet == DEFAULT_CHARSET ) ) + eCharSet = osl_getThreadTextEncoding(); + else + eCharSet = rtl_getTextEncodingFromWindowsCharset( aLogFont.lfCharSet ); + if ( eCharSet == RTL_TEXTENCODING_DONTKNOW ) + eCharSet = osl_getThreadTextEncoding(); + if ( eCharSet == RTL_TEXTENCODING_SYMBOL ) + eCharSet = RTL_TEXTENCODING_MS_1252; + aLogFont.alfFaceName = OUString( lfFaceName, strlen(lfFaceName), eCharSet ); + + CreateObject(std::make_unique<WinMtfFontStyle>( aLogFont )); + } + break; + + case W_META_CREATEBITMAPINDIRECT: + { + SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAPINDIRECT is not implemented. Please fill the bug report" ); + CreateObject(); + } + break; + + case W_META_CREATEBITMAP: + { + SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAP is not implemented. Please fill the bug report" ); + CreateObject(); + } + break; + + case W_META_CREATEREGION: + { + SAL_WARN( "emfio", "TODO: W_META_CREATEREGION is not implemented. Please fill the bug report" ); + CreateObject(); + } + break; + + case W_META_EXCLUDECLIPRECT : + { + SAL_WARN( "emfio", "TODO: Not working correctly. Please fill the bug report" ); + ExcludeClipRect( ReadRectangle() ); + } + break; + + case W_META_PATBLT: + { + sal_uInt32 nROP = 0; + mpInputStream->ReadUInt32( nROP ); + Size aSize = ReadYXExt(); + WMFRasterOp nOldROP = SetRasterOp( static_cast<WMFRasterOp>(nROP) ); + DrawRect( tools::Rectangle( ReadYX(), aSize ), false ); + SetRasterOp( nOldROP ); + } + break; + + case W_META_SELECTCLIPREGION: + { + sal_uInt16 nObjIndex = 0; + mpInputStream->ReadUInt16( nObjIndex ); + SAL_WARN( "emfio", "TODO: W_META_SELECTCLIPREGION is not implemented. Please fill the bug report" ); + if ( !nObjIndex ) + { + tools::PolyPolygon aEmptyPolyPoly; + SetClipPath( aEmptyPolyPoly, RegionMode::RGN_COPY, true ); + } + } + break; + + case W_META_ESCAPE : + { + // mnRecSize has been checked previously to be greater than 3 + sal_uInt64 nMetaRecSize = static_cast< sal_uInt64 >(mnRecSize - 2 ) * 2; + sal_uInt64 nMetaRecEndPos = mpInputStream->Tell() + nMetaRecSize; + + // taking care that mnRecSize does not exceed the maximal stream position + if ( nMetaRecEndPos > mnEndPos ) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + if (mnRecSize >= 4 ) // minimal escape length + { + sal_uInt16 nMode = 0, nLen = 0; + mpInputStream->ReadUInt16( nMode ) + .ReadUInt16( nLen ); + if ( ( nMode == W_MFCOMMENT ) && ( nLen >= 4 ) ) + { + sal_uInt32 nNewMagic = 0; // we have to read int32 for + mpInputStream->ReadUInt32( nNewMagic ); // META_ESCAPE_ENHANCED_METAFILE CommentIdentifier + + if( nNewMagic == 0x2c2a4f4f && nLen >= 14 ) + { + sal_uInt16 nMagic2 = 0; + mpInputStream->ReadUInt16( nMagic2 ); + if( nMagic2 == 0x0a ) // 2nd half of magic + { // continue with private escape + sal_uInt32 nCheck = 0, nEsc = 0; + mpInputStream->ReadUInt32( nCheck ) + .ReadUInt32( nEsc ); + + sal_uInt32 nEscLen = nLen - 14; + if ( nEscLen <= (mnRecSize * 2 ) ) + { + #ifdef OSL_BIGENDIAN + sal_uInt32 nTmp = OSL_SWAPDWORD( nEsc ); + sal_uInt32 nCheckSum = rtl_crc32( 0, &nTmp, 4 ); + #else + sal_uInt32 nCheckSum = rtl_crc32( 0, &nEsc, 4 ); + #endif + std::unique_ptr<sal_Int8[]> pData; + + if ( ( static_cast< sal_uInt64 >( nEscLen ) + mpInputStream->Tell() ) > nMetaRecEndPos ) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + break; + } + if ( nEscLen > 0 ) + { + pData.reset(new sal_Int8[ nEscLen ]); + mpInputStream->ReadBytes(pData.get(), nEscLen); + nCheckSum = rtl_crc32( nCheckSum, pData.get(), nEscLen ); + } + if ( nCheck == nCheckSum ) + { + switch( nEsc ) + { + case PRIVATE_ESCAPE_UNICODE : + { + // we will use text instead of polygons only if we have the correct font + if ( Application::GetDefaultDevice()->IsFontAvailable( GetFont().GetFamilyName() ) ) + { + Point aPt; + sal_uInt32 nStringLen, nDXCount; + std::vector<sal_Int32> aDXAry; + SvMemoryStream aMemoryStream( nEscLen ); + aMemoryStream.WriteBytes(pData.get(), nEscLen); + aMemoryStream.Seek( STREAM_SEEK_TO_BEGIN ); + sal_Int32 nTmpX(0), nTmpY(0); + aMemoryStream.ReadInt32( nTmpX ) + .ReadInt32( nTmpY ) + .ReadUInt32( nStringLen ); + aPt.setX( nTmpX ); + aPt.setY( nTmpY ); + + if ( ( static_cast< sal_uInt64 >( nStringLen ) * sizeof( sal_Unicode ) ) < ( nEscLen - aMemoryStream.Tell() ) ) + { + OUString aString = read_uInt16s_ToOUString(aMemoryStream, nStringLen); + aMemoryStream.ReadUInt32( nDXCount ); + if ( ( static_cast< sal_uInt64 >( nDXCount ) * sizeof( sal_Int32 ) ) >= ( nEscLen - aMemoryStream.Tell() ) ) + nDXCount = 0; + if ( nDXCount ) + aDXAry.resize(nDXCount); + for (sal_uInt32 i = 0; i < nDXCount; i++ ) + { + sal_Int32 val; + aMemoryStream.ReadInt32( val); + aDXAry[ i ] = val; + } + aMemoryStream.ReadUInt32(mnSkipActions); + DrawText( aPt, aString, aDXAry.empty() ? nullptr : &aDXAry ); + } + } + } + break; + } + } + } + } + } + else if ( (nNewMagic == static_cast< sal_uInt32 >(0x43464D57)) && (nLen >= 34) && ( static_cast<sal_Int32>(nLen + 10) <= static_cast<sal_Int32>(mnRecSize * 2) )) + { + sal_uInt32 nComType = 0, nVersion = 0, nFlags = 0, nComRecCount = 0, + nCurRecSize = 0, nRemainingSize = 0, nEMFTotalSize = 0; + sal_uInt16 nCheck = 0; + + mpInputStream->ReadUInt32( nComType ).ReadUInt32( nVersion ).ReadUInt16( nCheck ).ReadUInt32( nFlags ) + .ReadUInt32( nComRecCount ).ReadUInt32( nCurRecSize ) + .ReadUInt32( nRemainingSize ).ReadUInt32( nEMFTotalSize ); // the nRemainingSize is not mentioned in MSDN documentation + // but it seems to be required to read in data produced by OLE + + if( nComType == 0x01 && nVersion == 0x10000 && nComRecCount ) + { + if( !mnEMFRec) + { // first EMF comment + mnEMFRecCount = nComRecCount; + mnEMFSize = nEMFTotalSize; + if (mnEMFSize > mpInputStream->remainingSize()) + { + SAL_WARN("emfio", "emf size claims to be larger than remaining data"); + mpEMFStream.reset(); + } + else + mpEMFStream = std::vector<sal_uInt8>(); + } + else if( (mnEMFRecCount != nComRecCount ) || (mnEMFSize != nEMFTotalSize ) ) // add additional checks here + { + // total records should be the same as in previous comments + mnEMFRecCount = 0xFFFFFFFF; + mpEMFStream.reset(); + } + mnEMFRec++; + + if (mpEMFStream && nCurRecSize + 34 > nLen) + { + mnEMFRecCount = 0xFFFFFFFF; + mpEMFStream.reset(); + } + + if (mpEMFStream && nCurRecSize > mpInputStream->remainingSize()) + { + SAL_WARN("emfio", "emf record size claims to be larger than remaining data"); + mnEMFRecCount = 0xFFFFFFFF; + mpEMFStream.reset(); + } + + if (mpEMFStream) + { + std::vector<sal_Int8> aBuf(nCurRecSize); + sal_uInt32 nCount = mpInputStream->ReadBytes(aBuf.data(), nCurRecSize); + if( nCount == nCurRecSize ) + { + mpEMFStream->insert(mpEMFStream->end(), aBuf.begin(), aBuf.end()); + } + } + } + } + } + } + } + break; + + case W_META_SETRELABS: + case W_META_SETPOLYFILLMODE: + case W_META_SETSTRETCHBLTMODE: + case W_META_SETTEXTCHAREXTRA: + case W_META_SETTEXTJUSTIFICATION: + case W_META_FLOODFILL : + case W_META_FILLREGION: + case W_META_FRAMEREGION: + case W_META_INVERTREGION: + case W_META_PAINTREGION: + case W_META_DRAWTEXT: + case W_META_SETMAPPERFLAGS: + case W_META_SETDIBTODEV: + case W_META_REALIZEPALETTE: + case W_META_ANIMATEPALETTE: + case W_META_SETPALENTRIES: + case W_META_RESIZEPALETTE: + case W_META_EXTFLOODFILL: + case W_META_RESETDC: + case W_META_STARTDOC: + case W_META_STARTPAGE: + case W_META_ENDPAGE: + case W_META_ABORTDOC: + case W_META_ENDDOC: + { + SAL_WARN("emfio", "TODO: WMF record not implemented: " << record_type_name(nFunc)); + } + break; + + default: + { + SAL_WARN("emfio", "Unknown Meta Action: 0x" << std::hex << nFunc << std::dec); + } + } + + // tdf#127471 + maScaledFontHelper.applyAlternativeFontScale(); + } + + const tools::Long aMaxWidth = 1024; + + bool WmfReader::ReadHeader() + { + sal_uInt64 const nStrmPos = mpInputStream->Tell(); + + sal_uInt32 nPlaceableMetaKey(0); + // if available read the METAFILEHEADER + mpInputStream->ReadUInt32( nPlaceableMetaKey ); + if (!mpInputStream->good()) + return false; + + tools::Rectangle aPlaceableBound; + + mbPlaceable = nPlaceableMetaKey == 0x9ac6cdd7L; + + SAL_INFO("emfio", "Placeable: \"" << (mbPlaceable ? "yes" : "no") << "\""); + + if (mbPlaceable) + { + //TODO do some real error handling here + sal_Int16 nVal(0); + + // Skip reserved bytes + mpInputStream->SeekRel(2); + + // BoundRect + // These are simply ignored for now + mpInputStream->ReadInt16( nVal ); + aPlaceableBound.SetLeft( nVal ); + mpInputStream->ReadInt16( nVal ); + aPlaceableBound.SetTop( nVal ); + mpInputStream->ReadInt16( nVal ); + aPlaceableBound.SetRight( nVal ); + mpInputStream->ReadInt16( nVal ); + aPlaceableBound.SetBottom( nVal ); + + // inch + mpInputStream->ReadUInt16( mnUnitsPerInch ); + + // reserved + mpInputStream->SeekRel( 4 ); + + // Skip and don't check the checksum + mpInputStream->SeekRel( 2 ); + + // Skip wmf header + mpInputStream->Seek( nStrmPos + 40 ); // set the streampos to the start of the metaactions + GetPlaceableBound( aPlaceableBound, mpInputStream ); + // Go back to the place after placeable header + mpInputStream->Seek( nStrmPos + 22); + } + else + { + // Default is 1440, but it is set to 96 to show the wmf larger + mnUnitsPerInch = 96; + + if (mpExternalHeader != nullptr + && mpExternalHeader->xExt > 0 + && mpExternalHeader->yExt > 0 + && (mpExternalHeader->mapMode == MappingMode::MM_ISOTROPIC || mpExternalHeader->mapMode == MappingMode::MM_ANISOTROPIC)) + { + // #n417818#: If we have an external header then overwrite the bounds! + tools::Rectangle aExtRect(0, 0, + o3tl::convert(mpExternalHeader->xExt, o3tl::Length::mm100, o3tl::Length::px), + o3tl::convert(mpExternalHeader->yExt, o3tl::Length::mm100, o3tl::Length::px)); + aPlaceableBound = aExtRect; + + SAL_INFO("emfio", "External header size " + " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top() + << " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom()); + + SetMapMode(static_cast<MappingMode>(mpExternalHeader->mapMode));; + } + else + { + mpInputStream->Seek(nStrmPos + 18); // set the streampos to the start of the metaactions + GetPlaceableBound(aPlaceableBound, mpInputStream); + + // The image size is not known so normalize the calculated bounds so that the + // resulting image is not too big + if (aPlaceableBound.GetWidth() > aMaxWidth) + { + const double fMaxWidth = static_cast<double>(aMaxWidth); + double fRatio = aPlaceableBound.GetWidth() / fMaxWidth; + + // changing mnUnitsPerInch as a tool to scale wmf + mnUnitsPerInch *= fRatio; + + SAL_INFO("emfio", "Placeable bounds " + " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top() + << " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom()); + } + } + + mpInputStream->Seek( nStrmPos ); + } + + SetWinOrg( aPlaceableBound.TopLeft() ); + Size aWMFSize( + std::abs( aPlaceableBound.GetWidth() ), std::abs( aPlaceableBound.GetHeight() ) ); + SetWinExt( aWMFSize ); + + SAL_INFO("emfio", "WMF size w: " << aWMFSize.Width() << " h: " << aWMFSize.Height()); + + Size aDevExt( 10000, 10000 ); + if( ( std::abs( aWMFSize.Width() ) > 1 ) && ( std::abs( aWMFSize.Height() ) > 1 ) ) + { + const Fraction aFrac( 1, mnUnitsPerInch); + MapMode aWMFMap( MapUnit::MapInch, Point(), aFrac, aFrac ); + Size aSize100(OutputDevice::LogicToLogic(aWMFSize, aWMFMap, MapMode(MapUnit::Map100thMM))); + aDevExt = Size( std::abs( aSize100.Width() ), std::abs( aSize100.Height() ) ); + } + SetDevExt( aDevExt ); + + SAL_INFO("emfio", "Dev size w: " << aDevExt.Width() << " h: " << aDevExt.Height()); + + // read the METAHEADER + sal_uInt32 nMetaKey(0); + mpInputStream->ReadUInt32( nMetaKey ); // type and headersize + if (!mpInputStream->good()) + return false; + if (nMetaKey != 0x00090001) + { + sal_uInt16 aNextWord(0); + mpInputStream->ReadUInt16( aNextWord ); + if (nMetaKey != 0x10000 || aNextWord != 0x09) + { + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + return false; + } + } + + mpInputStream->SeekRel( 2 ); // Version (of Windows) + mpInputStream->SeekRel( 4 ); // Size (of file in words) + mpInputStream->SeekRel( 2 ); // NoObjects (maximum number of simultaneous objects) + mpInputStream->SeekRel( 4 ); // MaxRecord (size of largest record in words) + mpInputStream->SeekRel( 2 ); // NoParameters (Unused + + return mpInputStream->good(); + } + + void WmfReader::ReadWMF() + { + sal_uInt16 nFunction; + + mnSkipActions = 0; + + mpEMFStream.reset(); + mnEMFRecCount = 0; + mnEMFRec = 0; + mnEMFSize = 0; + + SetMapMode( MappingMode::MM_ANISOTROPIC ); + SetWinOrg( Point() ); + SetWinExt( Size( 1, 1 ) ); + SetDevExt( Size( 10000, 10000 ) ); + + mnEndPos=mpInputStream->TellEnd(); + mpInputStream->Seek( mnStartPos ); + + if ( ReadHeader( ) ) + { + auto nPos = mpInputStream->Tell(); + + if( mnEndPos - mnStartPos ) + { + bool bEMFAvailable = false; + while( !mpInputStream->eof() ) + { + mpInputStream->ReadUInt32(mnRecSize).ReadUInt16( nFunction ); + + if ( + !mpInputStream->good() || + (mnRecSize < 3) || + (mnRecSize == 3 && nFunction == W_META_EOF) + ) + { + if( mpInputStream->eof() ) + mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR ); + + break; + } + + const sal_uInt32 nAvailableBytes = mnEndPos - nPos; + const sal_uInt32 nMaxPossibleRecordSize = nAvailableBytes/2; + if (mnRecSize > nMaxPossibleRecordSize) + { + mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR); + break; + } + + if ( !bEMFAvailable ) + { + if( !maBmpSaveList.empty() + && ( nFunction != W_META_STRETCHDIB ) + && ( nFunction != W_META_DIBBITBLT ) + && ( nFunction != W_META_DIBSTRETCHBLT ) + ) + { + ResolveBitmapActions( maBmpSaveList ); + } + + if ( !mnSkipActions) + ReadRecordParams( mnRecSize, nFunction ); + else + mnSkipActions--; + + if(mpEMFStream && mnEMFRecCount == mnEMFRec) + { + GDIMetaFile aMeta; + SvMemoryStream aStream(mpEMFStream->data(), mpEMFStream->size(), StreamMode::STD_READ); + std::unique_ptr<EmfReader> pEMFReader(std::make_unique<EmfReader>(aStream, aMeta)); + pEMFReader->SetEnableEMFPlus(mbEnableEMFPlus); + bEMFAvailable = pEMFReader->ReadEnhWMF(); + pEMFReader.reset(); // destroy first!!! + + if( bEMFAvailable ) + { + AddFromGDIMetaFile( aMeta ); + SetrclFrame( tools::Rectangle( Point(0, 0), aMeta.GetPrefSize())); + + // the stream needs to be set to the wmf end position, + // otherwise the GfxLink that is created will be incorrect + // (leading to graphic loss after swapout/swapin). + // so we will proceed normally, but are ignoring further wmf + // records + } + else + { + // something went wrong + // continue with WMF, don't try this again + mpEMFStream.reset(); + } + } + } + + nPos += mnRecSize * 2; + mpInputStream->Seek(nPos); + } + } + else + mpInputStream->SetError( SVSTREAM_GENERALERROR ); + + if( !mpInputStream->GetError() && !maBmpSaveList.empty() ) + ResolveBitmapActions( maBmpSaveList ); + } + if ( mpInputStream->GetError() ) + mpInputStream->Seek( mnStartPos ); + } + + void WmfReader::GetPlaceableBound( tools::Rectangle& rPlaceableBound, SvStream* pStm ) + { + bool bRet = true; + + tools::Rectangle aBound; + aBound.SetLeft( RECT_MAX ); + aBound.SetTop( RECT_MAX ); + aBound.SetRight( RECT_MIN ); + aBound.SetBottom( RECT_MIN ); + bool bBoundsDetermined = false; + + auto nPos = pStm->Tell(); + auto nEnd = nPos + pStm->remainingSize(); + + Point aWinOrg(0,0); + std::optional<Size> aWinExt; + + Point aViewportOrg(0,0); + std::optional<Size> aViewportExt; + + MappingMode eMapMode = MappingMode::MM_ANISOTROPIC; + + if (nEnd - nPos) + { + sal_uInt16 nFunction; + sal_uInt32 nRSize; + + while( bRet ) + { + pStm->ReadUInt32( nRSize ).ReadUInt16( nFunction ); + + if( pStm->GetError() ) + { + bRet = false; + break; + } + else if (pStm->eof() || nRSize < 3) + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + break; + } + else if ( nRSize == 3 && nFunction == W_META_EOF ) + { + break; + } + switch( nFunction ) + { + case W_META_EOF: + { + return; + } + + case W_META_SETWINDOWORG: + { + aWinOrg = ReadYX(); + } + break; + + case W_META_SETWINDOWEXT: + { + sal_Int16 nWidth(0), nHeight(0); + pStm->ReadInt16(nHeight); + pStm->ReadInt16(nWidth); + aWinExt = Size(nWidth, nHeight); + } + break; + + case W_META_SETVIEWPORTORG: + { + aViewportOrg = ReadYX(); + } + break; + + case W_META_SETVIEWPORTEXT: + { + sal_Int16 nWidth(0), nHeight(0); + pStm->ReadInt16(nHeight); + pStm->ReadInt16(nWidth); + aViewportExt = Size(nWidth, nHeight); + } + break; + + case W_META_SETMAPMODE : + { + sal_Int16 nMapMode(0); + pStm->ReadInt16( nMapMode ); + eMapMode = static_cast<MappingMode>(nMapMode); + } + break; + + case W_META_MOVETO: + case W_META_LINETO: + { + GetWinExtMax( ReadYX(), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + + case W_META_RECTANGLE: + case W_META_INTERSECTCLIPRECT: + case W_META_EXCLUDECLIPRECT : + case W_META_ELLIPSE: + { + GetWinExtMax( ReadRectangle(), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + + case W_META_ROUNDRECT: + { + ReadYXExt(); // size + GetWinExtMax( ReadRectangle(), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + + case W_META_ARC: + case W_META_PIE: + case W_META_CHORD: + { + ReadYX(); // end + ReadYX(); // start + GetWinExtMax( ReadRectangle(), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + + case W_META_POLYGON: + { + bool bRecordOk = true; + + sal_uInt16 nPoints(0); + pStm->ReadUInt16( nPoints ); + + if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + } + else + { + for(sal_uInt16 i = 0; i < nPoints; i++ ) + { + GetWinExtMax( ReadPoint(), aBound, eMapMode ); + bBoundsDetermined = true; + } + } + + bRecordOk &= pStm->good(); + + SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide"); + + if (!bRecordOk) + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + break; + } + } + break; + + case W_META_POLYPOLYGON: + { + bool bRecordOk = true; + sal_uInt16 nPoly(0), nPoints(0); + pStm->ReadUInt16(nPoly); + if (nPoly > pStm->remainingSize() / sizeof(sal_uInt16)) + { + bRecordOk = false; + } + else + { + for(sal_uInt16 i = 0; i < nPoly; i++ ) + { + sal_uInt16 nP = 0; + pStm->ReadUInt16( nP ); + if (nP > SAL_MAX_UINT16 - nPoints) + { + bRecordOk = false; + break; + } + nPoints += nP; + } + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle"); + + bRecordOk &= pStm->good(); + + if (!bRecordOk) + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + break; + } + + if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + } + else + { + for (sal_uInt16 i = 0; i < nPoints; i++ ) + { + GetWinExtMax( ReadPoint(), aBound, eMapMode ); + bBoundsDetermined = true; + } + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record claimed more points than the stream can provide"); + + bRecordOk &= pStm->good(); + + if (!bRecordOk) + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + break; + } + } + break; + + case W_META_POLYLINE: + { + bool bRecordOk = true; + + sal_uInt16 nPoints(0); + pStm->ReadUInt16(nPoints); + if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16))) + { + bRecordOk = false; + } + else + { + for (sal_uInt16 i = 0; i < nPoints; ++i) + { + GetWinExtMax( ReadPoint(), aBound, eMapMode ); + bBoundsDetermined = true; + } + } + + SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide"); + + bRecordOk &= pStm->good(); + + if (!bRecordOk) + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + break; + } + } + break; + + case W_META_SETPIXEL: + { + ReadColor(); + GetWinExtMax( ReadYX(), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + + case W_META_TEXTOUT: + { + sal_uInt16 nLength(0); + pStm->ReadUInt16( nLength ); + // todo: we also have to take care of the text width + if ( nLength ) + { + pStm->SeekRel( ( nLength + 1 ) &~ 1 ); + GetWinExtMax( ReadYX(), aBound, eMapMode ); + bBoundsDetermined = true; + } + } + break; + + case W_META_EXTTEXTOUT: + { + sal_uInt16 nLen(0), nOptions; + Point aPosition = ReadYX(); + pStm->ReadUInt16( nLen ).ReadUInt16( nOptions ); + // todo: we also have to take care of the text width + if( nLen ) + { + GetWinExtMax( aPosition, aBound, eMapMode ); + bBoundsDetermined = true; + } + } + break; + case W_META_BITBLT: + case W_META_DIBBITBLT: + case W_META_DIBSTRETCHBLT: + case W_META_STRETCHBLT: + case W_META_STRETCHDIB: + { + sal_uInt32 nRasterOperation; + sal_Int16 nYSrc, nXSrc; + sal_uInt16 nColorUsage; + pStm->ReadUInt32( nRasterOperation ); + + if( nFunction == W_META_STRETCHDIB ) + pStm->ReadUInt16( nColorUsage ); + + if( nFunction == W_META_DIBSTRETCHBLT || + nFunction == W_META_STRETCHBLT || + nFunction == W_META_STRETCHDIB ) + { + sal_Int16 nSrcHeight, nSrcWidth; + pStm->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth ); + } + + // nYSrc and nXSrc is the offset of the first pixel + pStm->ReadInt16( nYSrc ).ReadInt16( nXSrc ); + + const bool bNoSourceBitmap = ( nFunction != W_META_STRETCHDIB ) && ( nRSize == ( ( static_cast< sal_uInt32 >( nFunction ) >> 8 ) + 3 ) ); + if ( bNoSourceBitmap ) + mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored) + + Size aDestSize( ReadYXExt() ); + if ( aDestSize.Width() && aDestSize.Height() ) // #92623# do not try to read buggy bitmaps + { + tools::Rectangle aDestRect( ReadYX(), aDestSize ); + GetWinExtMax( aDestRect, aBound, eMapMode ); + bBoundsDetermined = true; + } + } + break; + + case W_META_PATBLT: + { + sal_uInt32 nROP(0); + pStm->ReadUInt32( nROP ); + Size aSize = ReadYXExt(); + GetWinExtMax( tools::Rectangle( ReadYX(), aSize ), aBound, eMapMode ); + bBoundsDetermined = true; + } + break; + } + + const auto nAvailableBytes = nEnd - nPos; + const auto nMaxPossibleRecordSize = nAvailableBytes/2; + if (nRSize <= nMaxPossibleRecordSize) + { + nPos += nRSize * 2; + pStm->Seek(nPos); + } + else + { + pStm->SetError( SVSTREAM_FILEFORMAT_ERROR ); + bRet = false; + } + } + } + else + { + pStm->SetError( SVSTREAM_GENERALERROR ); + bRet = false; + } + + if (!bRet) + return; + + if (aWinExt) + { + rPlaceableBound = tools::Rectangle(aWinOrg, *aWinExt); + if (mbPlaceable && eMapMode == MM_ANISOTROPIC) + { + // It seems that (in MM_ANISOTROPIC WMFs) the "inch" field (PPI) in META_PLACEABLE is + // ignored and instead competitor office suites decide what it should be arbitrarily + // Could have to do with MM_ANISOTROPICs definition: + // Logical units are mapped to arbitrary units with arbitrarily scaled axes. + // The issue is that when PPI is bigger than the window size, the image appears + // tiny (smaller than an inch squared). + // A solution is to scale PPI down in such images to an arbitrary amount that makes + // the image visible: + auto nWidth = rPlaceableBound.GetWidth(); + auto nHeight = rPlaceableBound.GetHeight(); + if (mnUnitsPerInch > nWidth && mnUnitsPerInch > nHeight) + mnUnitsPerInch = std::max(nWidth, nHeight); + } + SAL_INFO("emfio", "Window dimension " + " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() + << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); + } + else if (aViewportExt) + { + rPlaceableBound = tools::Rectangle(aViewportOrg, *aViewportExt); + SAL_INFO("emfio", "Viewport dimension " + " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() + << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); + } + else if (bBoundsDetermined) + { + rPlaceableBound = aBound; + SAL_INFO("emfio", "Determined dimension " + " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() + << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); + } + else + { + rPlaceableBound.SetLeft( 0 ); + rPlaceableBound.SetTop( 0 ); + rPlaceableBound.SetRight( aMaxWidth ); + rPlaceableBound.SetBottom( aMaxWidth ); + SAL_INFO("emfio", "Default dimension " + " left: " << rPlaceableBound.Left() << " top: " << rPlaceableBound.Top() + << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom()); + } + } + + WmfReader::WmfReader(SvStream& rStreamWMF, GDIMetaFile& rGDIMetaFile, const WmfExternal* pExternalHeader) + : MtfTools(rGDIMetaFile, rStreamWMF) + , mnUnitsPerInch(96) + , mnRecSize(0) + , mbPlaceable(false) + , mnEMFRecCount(0) + , mnEMFRec(0) + , mnEMFSize(0) + , mnSkipActions(0) + , mpExternalHeader(pExternalHeader) + { + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |