diff options
Diffstat (limited to '')
85 files changed, 44419 insertions, 0 deletions
diff --git a/vcl/source/filter/FilterConfigCache.cxx b/vcl/source/filter/FilterConfigCache.cxx new file mode 100644 index 000000000..9f3668ccc --- /dev/null +++ b/vcl/source/filter/FilterConfigCache.cxx @@ -0,0 +1,489 @@ +/* -*- 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 "FilterConfigCache.hxx" + +#include <o3tl/safeint.hxx> +#include <vcl/graphicfilter.hxx> +#include <unotools/configmgr.hxx> +#include <tools/svlibrary.h> +#include <com/sun/star/uno/Any.h> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> + +using namespace ::com::sun::star::lang ; // XMultiServiceFactory +using namespace ::com::sun::star::container ; // XNameAccess +using namespace ::com::sun::star::uno ; // Reference +using namespace ::com::sun::star::beans ; // PropertyValue +using namespace ::com::sun::star::configuration ; + +const char* FilterConfigCache::FilterConfigCacheEntry::InternalPixelFilterNameList[] = +{ + IMP_BMP, IMP_GIF, IMP_PNG, IMP_JPEG, IMP_TIFF, IMP_WEBP, + IMP_XBM, IMP_XPM, IMP_TGA, IMP_PICT, IMP_MET, IMP_RAS, + IMP_PCX, IMP_MOV, IMP_PSD, IMP_PCD, IMP_PBM, IMP_DXF, + EXP_BMP, EXP_GIF, EXP_PNG, EXP_JPEG, EXP_TIFF, EXP_WEBP, + nullptr +}; + +void FilterConfigCache::FilterConfigCacheEntry::CreateFilterName( const OUString& rUserDataEntry ) +{ + bIsPixelFormat = false; + sFilterName = rUserDataEntry; + const char** pPtr; + for ( pPtr = InternalPixelFilterNameList; *pPtr; pPtr++ ) + { + if ( sFilterName.equalsIgnoreAsciiCaseAscii( *pPtr ) ) + { + bIsPixelFormat = true; + } + } +} + +OUString FilterConfigCache::FilterConfigCacheEntry::GetShortName() +{ + OUString aShortName; + if ( !lExtensionList.empty() ) + { + aShortName = lExtensionList[ 0 ]; + if ( aShortName.startsWith( "*." ) ) + aShortName = aShortName.replaceAt( 0, 2, u"" ); + } + return aShortName; +} + +/** helper to open the configuration root of the underlying + config package + + @param sPackage + specify, which config package should be opened. + Must be one of "types" or "filters" + + @return A valid object if open was successful. The access on opened + data will be readonly. It returns NULL in case open failed. + + @throws It let pass RuntimeExceptions only. + */ +static Reference< XInterface > openConfig(const char* sPackage) +{ + Reference< XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + Reference< XInterface > xCfg; + try + { + // get access to config API (not to file!) + Reference< XMultiServiceFactory > xConfigProvider = theDefaultProvider::get( xContext ); + + PropertyValue aParam ; + + // define cfg path for open + aParam.Name = "nodepath"; + if (rtl_str_compareIgnoreAsciiCase(sPackage, "types") == 0) + aParam.Value <<= OUString( "/org.openoffice.TypeDetection.Types/Types" ); + if (rtl_str_compareIgnoreAsciiCase(sPackage, "filters") == 0) + aParam.Value <<= OUString( "/org.openoffice.TypeDetection.GraphicFilter/Filters" ); + Sequence< Any > lParams{ Any(aParam) }; + + // get access to file + xCfg = xConfigProvider->createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", lParams); + } + catch(const RuntimeException&) + { throw; } + catch(const Exception&) + { xCfg.clear(); } + + return xCfg; +} + +void FilterConfigCache::ImplInit() +{ + OUString const STYPE ( "Type" ); + OUString const SUINAME ( "UIName" ); + OUString const SFLAGS ( "Flags" ); + OUString const SMEDIATYPE ( "MediaType" ); + OUString const SEXTENSIONS ( "Extensions" ); + OUString const SFORMATNAME ( "FormatName" ); + OUString const SREALFILTERNAME ( "RealFilterName" ); + + // get access to config + Reference< XNameAccess > xTypeAccess ( openConfig("types" ), UNO_QUERY ); + Reference< XNameAccess > xFilterAccess( openConfig("filters"), UNO_QUERY ); + + if ( !(xTypeAccess.is() && xFilterAccess.is()) ) + return; + + const Sequence< OUString > lAllFilter = xFilterAccess->getElementNames(); + + for ( const OUString& sInternalFilterName : lAllFilter ) + { + Reference< XPropertySet > xFilterSet; + xFilterAccess->getByName( sInternalFilterName ) >>= xFilterSet; + if (!xFilterSet.is()) + continue; + + FilterConfigCacheEntry aEntry; + + aEntry.sInternalFilterName = sInternalFilterName; + xFilterSet->getPropertyValue(STYPE) >>= aEntry.sType; + xFilterSet->getPropertyValue(SUINAME) >>= aEntry.sUIName; + xFilterSet->getPropertyValue(SREALFILTERNAME) >>= aEntry.sFilterType; + Sequence< OUString > lFlags; + xFilterSet->getPropertyValue(SFLAGS) >>= lFlags; + if (lFlags.getLength()!=1 || lFlags[0].isEmpty()) + continue; + if (lFlags[0].equalsIgnoreAsciiCase("import")) + aEntry.nFlags = 1; + else if (lFlags[0].equalsIgnoreAsciiCase("export")) + aEntry.nFlags = 2; + else + aEntry.nFlags = 0; + + OUString sFormatName; + xFilterSet->getPropertyValue(SFORMATNAME) >>= sFormatName; + aEntry.CreateFilterName( sFormatName ); + + Reference< XPropertySet > xTypeSet; + xTypeAccess->getByName( aEntry.sType ) >>= xTypeSet; + if (!xTypeSet.is()) + continue; + + xTypeSet->getPropertyValue(SMEDIATYPE) >>= aEntry.sMediaType; + css::uno::Sequence<OUString> tmp; + if (xTypeSet->getPropertyValue(SEXTENSIONS) >>= tmp) + aEntry.lExtensionList = comphelper::sequenceToContainer<std::vector<OUString>>(tmp); + + // The first extension will be used + // to generate our internal FilterType ( BMP, WMF ... ) + OUString aExtension( aEntry.GetShortName() ); + if (aExtension.isEmpty()) + continue; + + if ( aEntry.nFlags & 1 ) + aImport.push_back( aEntry ); + if ( aEntry.nFlags & 2 ) + aExport.push_back( aEntry ); + + // bFilterEntryCreated!? + if (!( aEntry.nFlags & 3 )) + continue; //? Entry was already inserted ... but following code will be suppressed?! + } +}; + +const char* FilterConfigCache::InternalFilterListForSvxLight[] = +{ + "bmp","1","SVBMP", + "bmp","2","SVBMP", + "dxf","1","SVDXF", + "eps","1","SVIEPS", + "eps","2","SVEEPS", + "gif","1","SVIGIF", + "gif","2","SVEGIF", + "jpg","1","SVIJPEG", + "jpg","2","SVEJPEG", + "mov","1","SVMOV", + "mov","2","SVMOV", + "met","1","SVMET", + "png","1","SVIPNG", + "png","2","SVEPNG", + "pct","1","SVPICT", + "pcd","1","SVPCD", + "psd","1","SVPSD", + "pcx","1","SVPCX", + "pbm","1","SVPBM", + "pgm","1","SVPBM", + "ppm","1","SVPBM", + "ras","1","SVRAS", + "svm","1","SVMETAFILE", + "svm","2","SVMETAFILE", + "tga","1","SVTGA", + "tif","1","SVTIFF", + "tif","2","SVTIFF", + "emf","1","SVEMF", + "emf","2","SVEMF", + "wmf","1","SVWMF", + "wmf","2","SVWMF", + "xbm","1","SVIXBM", + "xpm","1","SVIXPM", + "svg","1","SVISVG", + "svg","2","SVESVG", + "webp","1","SVIWEBP", + "webp","2","SVEWEBP", + nullptr +}; + +void FilterConfigCache::ImplInitSmart() +{ + const char** pPtr; + for ( pPtr = InternalFilterListForSvxLight; *pPtr; pPtr++ ) + { + FilterConfigCacheEntry aEntry; + + OUString sExtension( OUString::createFromAscii( *pPtr++ ) ); + + aEntry.lExtensionList.push_back(sExtension); + + aEntry.sType = sExtension; + aEntry.sUIName = sExtension; + + OString sFlags( *pPtr++ ); + aEntry.nFlags = sFlags.toInt32(); + + OUString sUserData( OUString::createFromAscii( *pPtr ) ); + aEntry.CreateFilterName( sUserData ); + + if ( aEntry.nFlags & 1 ) + aImport.push_back( aEntry ); + if ( aEntry.nFlags & 2 ) + aExport.push_back( aEntry ); + } +} + +FilterConfigCache::FilterConfigCache(bool bConfig) +{ + if (bConfig) + bConfig = !utl::ConfigManager::IsFuzzing(); + if (bConfig) + ImplInit(); + else + ImplInitSmart(); +} + +FilterConfigCache::~FilterConfigCache() +{ +} + +OUString FilterConfigCache::GetImportFilterName( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].sFilterName; + return OUString(); +} + +sal_uInt16 FilterConfigCache::GetImportFormatNumber( std::u16string_view rFormatName ) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aImport) + { + if ( elem.sUIName.equalsIgnoreAsciiCase( rFormatName ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +/// get the index of the filter that matches this extension +sal_uInt16 FilterConfigCache::GetImportFormatNumberForExtension( std::u16string_view rExt ) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aImport) + { + for ( OUString const & s : elem.lExtensionList ) + { + if ( s.equalsIgnoreAsciiCase( rExt ) ) + return nPos; + } + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +sal_uInt16 FilterConfigCache::GetImportFormatNumberForShortName( std::u16string_view rShortName ) +{ + sal_uInt16 nPos = 0; + for (auto & elem : aImport) + { + if ( elem.GetShortName().equalsIgnoreAsciiCase( rShortName ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +sal_uInt16 FilterConfigCache::GetImportFormatNumberForTypeName( std::u16string_view rType ) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aImport) + { + if ( elem.sType.equalsIgnoreAsciiCase( rType ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +OUString FilterConfigCache::GetImportFormatName( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].sUIName; + return OUString(); +} + +OUString FilterConfigCache::GetImportFormatMediaType( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].sMediaType; + return OUString(); +} + +OUString FilterConfigCache::GetImportFormatShortName( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].GetShortName(); + return OUString(); +} + +OUString FilterConfigCache::GetImportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry ) +{ + if ( (nFormat < aImport.size()) && (o3tl::make_unsigned(nEntry) < aImport[ nFormat ].lExtensionList.size()) ) + return aImport[ nFormat ].lExtensionList[ nEntry ]; + return OUString(); +} + +OUString FilterConfigCache::GetImportFilterType( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].sType; + return OUString(); +} + +OUString FilterConfigCache::GetImportFilterTypeName( sal_uInt16 nFormat ) +{ + if( nFormat < aImport.size() ) + return aImport[ nFormat ].sFilterType; + return OUString(); +} + +OUString FilterConfigCache::GetImportWildcard(sal_uInt16 nFormat, sal_Int32 nEntry) +{ + OUString aWildcard( GetImportFormatExtension( nFormat, nEntry ) ); + if ( !aWildcard.isEmpty() ) + aWildcard = aWildcard.replaceAt( 0, 0, u"*." ); + return aWildcard; +} + +OUString FilterConfigCache::GetExportFilterName( sal_uInt16 nFormat ) +{ + if( nFormat < aExport.size() ) + return aExport[ nFormat ].sFilterName; + return OUString(); +} + +sal_uInt16 FilterConfigCache::GetExportFormatNumber(std::u16string_view rFormatName) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aExport) + { + if ( elem.sUIName.equalsIgnoreAsciiCase( rFormatName ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +sal_uInt16 FilterConfigCache::GetExportFormatNumberForMediaType( std::u16string_view rMediaType ) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aExport) + { + if ( elem.sMediaType.equalsIgnoreAsciiCase( rMediaType ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +sal_uInt16 FilterConfigCache::GetExportFormatNumberForShortName( std::u16string_view rShortName ) +{ + sal_uInt16 nPos = 0; + for (auto & elem : aExport) + { + if ( elem.GetShortName().equalsIgnoreAsciiCase( rShortName ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +sal_uInt16 FilterConfigCache::GetExportFormatNumberForTypeName( std::u16string_view rType ) +{ + sal_uInt16 nPos = 0; + for (auto const& elem : aExport) + { + if ( elem.sType.equalsIgnoreAsciiCase( rType ) ) + return nPos; + ++nPos; + } + return GRFILTER_FORMAT_NOTFOUND; +} + +OUString FilterConfigCache::GetExportFormatName( sal_uInt16 nFormat ) +{ + if( nFormat < aExport.size() ) + return aExport[ nFormat ].sUIName; + return OUString(); +} + +OUString FilterConfigCache::GetExportFormatMediaType( sal_uInt16 nFormat ) +{ + if( nFormat < aExport.size() ) + return aExport[ nFormat ].sMediaType; + return OUString(); +} + +OUString FilterConfigCache::GetExportFormatShortName( sal_uInt16 nFormat ) +{ + if( nFormat < aExport.size() ) + return aExport[ nFormat ].GetShortName(); + return OUString(); +} + +OUString FilterConfigCache::GetExportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry ) +{ + if ( (nFormat < aExport.size()) && (o3tl::make_unsigned(nEntry) < aExport[ nFormat ].lExtensionList.size()) ) + return aExport[ nFormat ].lExtensionList[ nEntry ]; + return OUString(); +} + +OUString FilterConfigCache::GetExportInternalFilterName( sal_uInt16 nFormat ) +{ + if( nFormat < aExport.size() ) + return aExport[ nFormat ].sInternalFilterName; + return OUString(); +} + +OUString FilterConfigCache::GetExportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry ) +{ + OUString aWildcard( GetExportFormatExtension( nFormat, nEntry ) ); + if ( !aWildcard.isEmpty() ) + aWildcard = aWildcard.replaceAt( 0, 0, u"*." ); + return aWildcard; +} + +bool FilterConfigCache::IsExportPixelFormat( sal_uInt16 nFormat ) +{ + return (nFormat < aExport.size()) && aExport[ nFormat ].bIsPixelFormat; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/FilterConfigCache.hxx b/vcl/source/filter/FilterConfigCache.hxx new file mode 100644 index 000000000..f398a58ff --- /dev/null +++ b/vcl/source/filter/FilterConfigCache.hxx @@ -0,0 +1,100 @@ +/* -*- 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_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX +#define INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX + +#include <rtl/ustring.hxx> +#include <vector> + +/** Cache to keep list of graphic filters + the filters themselves. */ +class FilterConfigCache +{ + struct FilterConfigCacheEntry + { + OUString sInternalFilterName; + OUString sType; + std::vector< OUString > lExtensionList; + OUString sUIName; + + OUString sMediaType; + OUString sFilterType; + + sal_Int32 nFlags; + + // user data + OUString sFilterName; + bool bIsPixelFormat : 1; + + void CreateFilterName( const OUString& rUserDataEntry ); + OUString GetShortName( ); + + static const char* InternalPixelFilterNameList[]; + }; + + + std::vector< FilterConfigCacheEntry > aImport; + std::vector< FilterConfigCacheEntry > aExport; + + static const char* InternalFilterListForSvxLight[]; + + void ImplInit(); + void ImplInitSmart(); + +public: + + sal_uInt16 GetImportFormatCount() const + { return sal::static_int_cast< sal_uInt16 >(aImport.size()); }; + sal_uInt16 GetImportFormatNumber( std::u16string_view rFormatName ); + sal_uInt16 GetImportFormatNumberForShortName( std::u16string_view rShortName ); + sal_uInt16 GetImportFormatNumberForTypeName( std::u16string_view rType ); + sal_uInt16 GetImportFormatNumberForExtension( std::u16string_view rExt ); + OUString GetImportFilterName( sal_uInt16 nFormat ); + OUString GetImportFormatName( sal_uInt16 nFormat ); + OUString GetImportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry = 0); + OUString GetImportFormatMediaType( sal_uInt16 nFormat ); + OUString GetImportFormatShortName( sal_uInt16 nFormat ); + OUString GetImportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry ); + OUString GetImportFilterType( sal_uInt16 nFormat ); + OUString GetImportFilterTypeName( sal_uInt16 nFormat ); + + + sal_uInt16 GetExportFormatCount() const + { return sal::static_int_cast< sal_uInt16 >(aExport.size()); }; + sal_uInt16 GetExportFormatNumber( std::u16string_view rFormatName ); + sal_uInt16 GetExportFormatNumberForMediaType( std::u16string_view rMediaType ); + sal_uInt16 GetExportFormatNumberForShortName( std::u16string_view rShortName ); + sal_uInt16 GetExportFormatNumberForTypeName( std::u16string_view rType ); + OUString GetExportFilterName( sal_uInt16 nFormat ); + OUString GetExportFormatName( sal_uInt16 nFormat ); + OUString GetExportFormatExtension( sal_uInt16 nFormat, sal_Int32 nEntry = 0 ); + OUString GetExportFormatMediaType( sal_uInt16 nFormat ); + OUString GetExportFormatShortName( sal_uInt16 nFormat ); + OUString GetExportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry ); + OUString GetExportInternalFilterName( sal_uInt16 nFormat ); + + bool IsExportPixelFormat( sal_uInt16 nFormat ); + + explicit FilterConfigCache( bool bUseConfig ); + ~FilterConfigCache(); +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_FILTERCONFIGCACHE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/FilterConfigItem.cxx b/vcl/source/filter/FilterConfigItem.cxx new file mode 100644 index 000000000..962d970bf --- /dev/null +++ b/vcl/source/filter/FilterConfigItem.cxx @@ -0,0 +1,389 @@ +/* -*- 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 <vcl/FilterConfigItem.hxx> + +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> + +using namespace ::com::sun::star::lang ; // XMultiServiceFactory +using namespace ::com::sun::star::beans ; // PropertyValue +using namespace ::com::sun::star::uno ; // Reference +using namespace ::com::sun::star::util ; // XChangesBatch +using namespace ::com::sun::star::awt ; // Size +using namespace ::com::sun::star::container ; +using namespace ::com::sun::star::configuration; +using namespace ::com::sun::star::task ; // XStatusIndicator + +static bool ImpIsTreeAvailable( Reference< XMultiServiceFactory > const & rXCfgProv, const OUString& rTree ) +{ + bool bAvailable = !rTree.isEmpty(); + if ( bAvailable ) + { + sal_Int32 nIdx{0}; + if ( rTree[0] == '/' ) + ++nIdx; + + // creation arguments: nodepath + PropertyValue aPathArgument = comphelper::makePropertyValue("nodepath", + rTree.getToken(0, '/', nIdx)); + Sequence< Any > aArguments{ Any(aPathArgument) }; + + Reference< XInterface > xReadAccess; + try + { + xReadAccess = rXCfgProv->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArguments ); + } + catch (const css::uno::Exception&) + { + bAvailable = false; + } + if ( xReadAccess.is() ) + { + const sal_Int32 nEnd {rTree.getLength()}; + while (bAvailable && nIdx>=0 && nIdx<nEnd) + { + Reference< XHierarchicalNameAccess > xHierarchicalNameAccess + ( xReadAccess, UNO_QUERY ); + + if ( !xHierarchicalNameAccess.is() ) + bAvailable = false; + else + { + const OUString aNode( rTree.getToken(0, '/', nIdx) ); + if ( !xHierarchicalNameAccess->hasByHierarchicalName( aNode ) ) + bAvailable = false; + else + { + Any a( xHierarchicalNameAccess->getByHierarchicalName( aNode ) ); + bAvailable = (a >>= xReadAccess); + } + } + } + } + } + return bAvailable; +} + +void FilterConfigItem::ImpInitTree( std::u16string_view rSubTree ) +{ + bModified = false; + + Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() ); + + Reference< XMultiServiceFactory > xCfgProv = theDefaultProvider::get( xContext ); + + OUString sTree = OUString::Concat("/org.openoffice.") + rSubTree; + if ( !ImpIsTreeAvailable(xCfgProv, sTree) ) + return; + + // creation arguments: nodepath + PropertyValue aPathArgument; + aPathArgument.Name = "nodepath"; + aPathArgument.Value <<= sTree; + + Sequence< Any > aArguments{ Any(aPathArgument) }; + + try + { + xUpdatableView = xCfgProv->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", + aArguments ); + if ( xUpdatableView.is() ) + xPropSet.set( xUpdatableView, UNO_QUERY ); + } + catch ( css::uno::Exception& ) + { + OSL_FAIL( "FilterConfigItem::FilterConfigItem - Could not access configuration Key" ); + } +} + +FilterConfigItem::FilterConfigItem( std::u16string_view rSubTree ) +{ + ImpInitTree( rSubTree ); +} + +FilterConfigItem::FilterConfigItem( css::uno::Sequence< css::beans::PropertyValue > const * pFilterData ) + : bModified(false) +{ + if ( pFilterData ) + aFilterData = *pFilterData; +} + +FilterConfigItem::FilterConfigItem( std::u16string_view rSubTree, + css::uno::Sequence< css::beans::PropertyValue > const * pFilterData ) +{ + ImpInitTree( rSubTree ); + + if ( pFilterData ) + aFilterData = *pFilterData; +}; + +FilterConfigItem::~FilterConfigItem() +{ + WriteModifiedConfig(); +} + +void FilterConfigItem::WriteModifiedConfig() +{ + if ( !xUpdatableView.is() ) + return; + + if ( !(xPropSet.is() && bModified) ) + return; + + Reference< XChangesBatch > xUpdateControl( xUpdatableView, UNO_QUERY ); + if ( xUpdateControl.is() ) + { + try + { + xUpdateControl->commitChanges(); + bModified = false; + } + catch ( css::uno::Exception& ) + { + OSL_FAIL( "FilterConfigItem::FilterConfigItem - Could not update configuration data" ); + } + } +} + +bool FilterConfigItem::ImplGetPropertyValue( Any& rAny, const Reference< XPropertySet >& rXPropSet, const OUString& rString ) +{ + bool bRetValue = true; + + if ( rXPropSet.is() ) + { + bRetValue = false; + try + { + Reference< XPropertySetInfo > + aXPropSetInfo( rXPropSet->getPropertySetInfo() ); + if ( aXPropSetInfo.is() ) + bRetValue = aXPropSetInfo->hasPropertyByName( rString ); + } + catch( css::uno::Exception& ) + { + } + if ( bRetValue ) + { + try + { + rAny = rXPropSet->getPropertyValue( rString ); + if ( !rAny.hasValue() ) + bRetValue = false; + } + catch( css::uno::Exception& ) + { + bRetValue = false; + } + } + } + else + bRetValue = false; + return bRetValue; +} + +// if property is available it returns a pointer, +// otherwise the result is null +const PropertyValue* FilterConfigItem::GetPropertyValue( const Sequence< PropertyValue >& rPropSeq, const OUString& rName ) +{ + auto pProp = std::find_if(rPropSeq.begin(), rPropSeq.end(), + [&rName](const PropertyValue& rProp) { return rProp.Name == rName; }); + if (pProp != rPropSeq.end()) + return pProp; + return nullptr; +} + +/* if PropertySequence already includes a PropertyValue using the same name, the + corresponding PropertyValue is replaced, otherwise the given PropertyValue + will be appended */ + +bool FilterConfigItem::WritePropertyValue( Sequence< PropertyValue >& rPropSeq, const PropertyValue& rPropValue ) +{ + bool bRet = false; + if ( !rPropValue.Name.isEmpty() ) + { + auto pProp = std::find_if(std::cbegin(rPropSeq), std::cend(rPropSeq), + [&rPropValue](const PropertyValue& rProp) { return rProp.Name == rPropValue.Name; }); + sal_Int32 i = std::distance(std::cbegin(rPropSeq), pProp); + sal_Int32 nCount = rPropSeq.getLength(); + if ( i == nCount ) + rPropSeq.realloc( ++nCount ); + + rPropSeq.getArray()[ i ] = rPropValue; + + bRet = true; + } + return bRet; +} + +bool FilterConfigItem::ReadBool( const OUString& rKey, bool bDefault ) +{ + Any aAny; + bool bRetValue = bDefault; + const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey ); + if ( pPropVal ) + { + pPropVal->Value >>= bRetValue; + } + else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) ) + { + aAny >>= bRetValue; + } + PropertyValue aBool; + aBool.Name = rKey; + aBool.Value <<= bRetValue; + WritePropertyValue( aFilterData, aBool ); + return bRetValue; +} + +sal_Int32 FilterConfigItem::ReadInt32( const OUString& rKey, sal_Int32 nDefault ) +{ + Any aAny; + sal_Int32 nRetValue = nDefault; + const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey ); + if ( pPropVal ) + { + pPropVal->Value >>= nRetValue; + } + else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) ) + { + aAny >>= nRetValue; + } + PropertyValue aInt32; + aInt32.Name = rKey; + aInt32.Value <<= nRetValue; + WritePropertyValue( aFilterData, aInt32 ); + return nRetValue; +} + +OUString FilterConfigItem::ReadString( const OUString& rKey, const OUString& rDefault ) +{ + Any aAny; + OUString aRetValue( rDefault ); + const PropertyValue* pPropVal = GetPropertyValue( aFilterData, rKey ); + if ( pPropVal ) + { + pPropVal->Value >>= aRetValue; + } + else if ( ImplGetPropertyValue( aAny, xPropSet, rKey ) ) + { + aAny >>= aRetValue; + } + PropertyValue aString; + aString.Name = rKey; + aString.Value <<= aRetValue; + WritePropertyValue( aFilterData, aString ); + return aRetValue; +} + +void FilterConfigItem::WriteBool( const OUString& rKey, bool bNewValue ) +{ + PropertyValue aBool; + aBool.Name = rKey; + aBool.Value <<= bNewValue; + WritePropertyValue( aFilterData, aBool ); + + if ( !xPropSet.is() ) + return; + + Any aAny; + if ( !ImplGetPropertyValue( aAny, xPropSet, rKey ) ) + return; + + bool bOldValue(true); + if ( !(aAny >>= bOldValue) ) + return; + + if ( bOldValue != bNewValue ) + { + try + { + xPropSet->setPropertyValue( rKey, Any(bNewValue) ); + bModified = true; + } + catch ( css::uno::Exception& ) + { + OSL_FAIL( "FilterConfigItem::WriteBool - could not set PropertyValue" ); + } + } +} + +void FilterConfigItem::WriteInt32( const OUString& rKey, sal_Int32 nNewValue ) +{ + PropertyValue aInt32; + aInt32.Name = rKey; + aInt32.Value <<= nNewValue; + WritePropertyValue( aFilterData, aInt32 ); + + if ( !xPropSet.is() ) + return; + + Any aAny; + + if ( !ImplGetPropertyValue( aAny, xPropSet, rKey ) ) + return; + + sal_Int32 nOldValue = 0; + if ( !(aAny >>= nOldValue) ) + return; + + if ( nOldValue != nNewValue ) + { + try + { + xPropSet->setPropertyValue( rKey, Any(nNewValue) ); + bModified = true; + } + catch ( css::uno::Exception& ) + { + OSL_FAIL( "FilterConfigItem::WriteInt32 - could not set PropertyValue" ); + } + } +} + + +Reference< XStatusIndicator > FilterConfigItem::GetStatusIndicator() const +{ + Reference< XStatusIndicator > xStatusIndicator; + + auto pPropVal = std::find_if(aFilterData.begin(), aFilterData.end(), + [](const css::beans::PropertyValue& rPropVal) { + return rPropVal.Name == "StatusIndicator"; }); + if (pPropVal != aFilterData.end()) + { + pPropVal->Value >>= xStatusIndicator; + } + return xStatusIndicator; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/GraphicFormatDetector.cxx b/vcl/source/filter/GraphicFormatDetector.cxx new file mode 100644 index 000000000..6ef3d3015 --- /dev/null +++ b/vcl/source/filter/GraphicFormatDetector.cxx @@ -0,0 +1,859 @@ +/* -*- 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 <algorithm> + +#include <graphic/GraphicFormatDetector.hxx> +#include <graphic/DetectorTools.hxx> +#include <tools/solar.h> +#include <tools/zcodec.hxx> + +constexpr sal_uInt32 SVG_CHECK_SIZE = 2048; +constexpr sal_uInt32 WMF_EMF_CHECK_SIZE = 44; + +namespace vcl +{ +bool peekGraphicFormat(SvStream& rStream, OUString& rFormatExtension, bool bTest) +{ + vcl::GraphicFormatDetector aDetector(rStream, rFormatExtension); + if (!aDetector.detect()) + return false; + + // The following variable is used when bTest == true. It remains false + // if the format (rFormatExtension) has not yet been set. + bool bSomethingTested = false; + + // Now the different formats are checked. The order *does* matter. e.g. a MET file + // could also go through the BMP test, however, a BMP file can hardly go through the MET test. + // So MET should be tested prior to BMP. However, theoretically a BMP file could conceivably + // go through the MET test. These problems are of course not only in MET and BMP. + // Therefore, in the case of a format check (bTest == true) we only test *exactly* this + // format. Everything else could have fatal consequences, for example if the user says it is + // a BMP file (and it is a BMP) file, and the file would go through the MET test ... + + if (!bTest || rFormatExtension.startsWith("MET")) + { + bSomethingTested = true; + if (aDetector.checkMET()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("BMP")) + { + bSomethingTested = true; + if (aDetector.checkBMP()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("WMF") || rFormatExtension.startsWith("EMF") + || rFormatExtension.startsWith("WMZ") || rFormatExtension.startsWith("EMZ")) + { + bSomethingTested = true; + if (aDetector.checkWMForEMF()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PCX")) + { + bSomethingTested = true; + if (aDetector.checkPCX()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("TIF")) + { + bSomethingTested = true; + if (aDetector.checkTIF()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("GIF")) + { + bSomethingTested = true; + if (aDetector.checkGIF()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PNG")) + { + bSomethingTested = true; + if (aDetector.checkPNG()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("JPG")) + { + bSomethingTested = true; + if (aDetector.checkJPG()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("SVM")) + { + bSomethingTested = true; + if (aDetector.checkSVM()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PCD")) + { + bSomethingTested = true; + if (aDetector.checkPCD()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PSD")) + { + bSomethingTested = true; + if (aDetector.checkPSD()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("EPS")) + { + bSomethingTested = true; + if (aDetector.checkEPS()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("DXF")) + { + if (aDetector.checkDXF()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PCT")) + { + bSomethingTested = true; + if (aDetector.checkPCT()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PBM") || rFormatExtension.startsWith("PGM") + || rFormatExtension.startsWith("PPM")) + { + bSomethingTested = true; + if (aDetector.checkPBMorPGMorPPM()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("RAS")) + { + bSomethingTested = true; + if (aDetector.checkRAS()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest) + { + bSomethingTested = true; + if (aDetector.checkXPM()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + else if (rFormatExtension.startsWith("XPM")) + { + return true; + } + + if (!bTest) + { + if (aDetector.checkXBM()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + else if (rFormatExtension.startsWith("XBM")) + { + return true; + } + + if (!bTest) + { + if (aDetector.checkSVG()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + else if (rFormatExtension.startsWith("SVG")) + { + return true; + } + + if (!bTest || rFormatExtension.startsWith("TGA")) + { + bSomethingTested = true; + if (aDetector.checkTGA()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("MOV")) + { + if (aDetector.checkMOV()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("PDF")) + { + if (aDetector.checkPDF()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + if (!bTest || rFormatExtension.startsWith("WEBP")) + { + bSomethingTested = true; + if (aDetector.checkWEBP()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + + return bTest && !bSomethingTested; +} + +namespace +{ +bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen) +{ + sal_uInt8 sBuf[3]; + // store number format + SvStreamEndian oldNumberFormat = rStream.GetEndian(); + sal_uInt32 nOffset; // in MS documents the pict format is used without the first 512 bytes + for (nOffset = 0; (nOffset <= 512) && ((nStreamPos + nOffset + 14) <= nStreamLen); + nOffset += 512) + { + short y1, x1, y2, x2; + bool bdBoxOk = true; + + rStream.Seek(nStreamPos + nOffset); + // size of the pict in version 1 pict ( 2bytes) : ignored + rStream.SeekRel(2); + // bounding box (bytes 2 -> 9) + rStream.SetEndian(SvStreamEndian::BIG); + rStream.ReadInt16(y1).ReadInt16(x1).ReadInt16(y2).ReadInt16(x2); + rStream.SetEndian(oldNumberFormat); // reset format + + // read version op + rStream.ReadBytes(sBuf, 3); + + if (!rStream.good()) + break; + + if (x1 > x2 || y1 > y2 || // bad bdbox + (x1 == x2 && y1 == y2) || // 1 pixel picture + x2 - x1 > 2048 || y2 - y1 > 2048) // picture abnormally big + bdBoxOk = false; + + // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf + // normal version 2 - page A23 and A24 + if (sBuf[0] == 0x00 && sBuf[1] == 0x11 && sBuf[2] == 0x02) + return true; + // normal version 1 - page A25 + else if (sBuf[0] == 0x11 && sBuf[1] == 0x01 && bdBoxOk) + return true; + } + return false; +} + +} // end anonymous namespace + +GraphicFormatDetector::GraphicFormatDetector(SvStream& rStream, OUString const& rFormatExtension) + : mrStream(rStream) + , maExtension(rFormatExtension) + , mnFirstLong(0) + , mnSecondLong(0) + , mnStreamPosition(0) + , mnStreamLength(0) +{ +} + +bool GraphicFormatDetector::detect() +{ + maFirstBytes.clear(); + maFirstBytes.resize(256, 0); + + mnFirstLong = 0; + mnSecondLong = 0; + + mnStreamPosition = mrStream.Tell(); + mnStreamLength = mrStream.remainingSize(); + + if (!mnStreamLength) + { + SvLockBytes* pLockBytes = mrStream.GetLockBytes(); + if (pLockBytes) + pLockBytes->SetSynchronMode(); + mnStreamLength = mrStream.remainingSize(); + } + + if (mnStreamLength == 0) + { + return false; // this prevents at least a STL assertion + } + else if (mnStreamLength >= maFirstBytes.size()) + { + // load first 256 bytes into a buffer + sal_uInt64 nRead = mrStream.ReadBytes(maFirstBytes.data(), maFirstBytes.size()); + if (nRead < maFirstBytes.size()) + mnStreamLength = nRead; + } + else + { + mnStreamLength = mrStream.ReadBytes(maFirstBytes.data(), mnStreamLength); + } + + if (mrStream.GetError()) + return false; + + for (int i = 0; i < 4; ++i) + { + mnFirstLong = (mnFirstLong << 8) | sal_uInt32(maFirstBytes[i]); + mnSecondLong = (mnSecondLong << 8) | sal_uInt32(maFirstBytes[i + 4]); + } + return true; +} + +bool GraphicFormatDetector::checkMET() +{ + if (maFirstBytes[2] != 0xd3) + return false; + mrStream.SetEndian(SvStreamEndian::BIG); + mrStream.Seek(mnStreamPosition); + sal_uInt16 nFieldSize; + sal_uInt8 nMagic; + + mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic); + for (int i = 0; i < 3; i++) + { + if (nFieldSize < 6) + return false; + if (mnStreamLength < mrStream.Tell() + nFieldSize) + return false; + mrStream.SeekRel(nFieldSize - 3); + mrStream.ReadUInt16(nFieldSize).ReadUChar(nMagic); + if (nMagic != 0xd3) + return false; + } + mrStream.SetEndian(SvStreamEndian::LITTLE); + + if (mrStream.GetError()) + return false; + + msDetectedFormat = "MET"; + return true; +} + +bool GraphicFormatDetector::checkBMP() +{ + sal_uInt8 nOffset; + + // We're possibly also able to read an OS/2 bitmap array + // ('BA'), therefore we must adjust the offset to discover the + // first bitmap in the array + if (maFirstBytes[0] == 0x42 && maFirstBytes[1] == 0x41) + nOffset = 14; + else + nOffset = 0; + + // Now we initially test on 'BM' + if (maFirstBytes[0 + nOffset] == 0x42 && maFirstBytes[1 + nOffset] == 0x4d) + { + // OS/2 can set the Reserved flags to a value other than 0 + // (which they really should not do...); + // In this case we test the size of the BmpInfoHeaders + if ((maFirstBytes[6 + nOffset] == 0x00 && maFirstBytes[7 + nOffset] == 0x00 + && maFirstBytes[8 + nOffset] == 0x00 && maFirstBytes[9 + nOffset] == 0x00) + || maFirstBytes[14 + nOffset] == 0x28 || maFirstBytes[14 + nOffset] == 0x0c) + { + msDetectedFormat = "BMP"; + return true; + } + } + return false; +} + +bool GraphicFormatDetector::checkWMForEMF() +{ + sal_uInt64 nCheckSize = std::min<sal_uInt64>(mnStreamLength, 256); + sal_uInt8 sExtendedOrDecompressedFirstBytes[WMF_EMF_CHECK_SIZE]; + sal_uInt64 nDecompressedSize = nCheckSize; + // check if it is gzipped -> wmz/emz + sal_uInt8* pCheckArray = checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes, + WMF_EMF_CHECK_SIZE, nDecompressedSize); + if (mnFirstLong == 0xd7cdc69a || mnFirstLong == 0x01000900) + { + msDetectedFormat = "WMF"; + return true; + } + else if (mnFirstLong == 0x01000000 && pCheckArray[40] == 0x20 && pCheckArray[41] == 0x45 + && pCheckArray[42] == 0x4d && pCheckArray[43] == 0x46) + { + msDetectedFormat = "EMF"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPCX() +{ + if (maFirstBytes[0] != 0x0a) + return false; + + sal_uInt8 nVersion = maFirstBytes[1]; + sal_uInt8 nEncoding = maFirstBytes[2]; + if ((nVersion == 0 || nVersion == 2 || nVersion == 3 || nVersion == 5) && nEncoding <= 1) + { + msDetectedFormat = "PCX"; + return true; + } + + return false; +} + +bool GraphicFormatDetector::checkTIF() +{ + if (mnFirstLong == 0x49492a00 || mnFirstLong == 0x4d4d002a) + { + msDetectedFormat = "TIF"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkGIF() +{ + if (mnFirstLong == 0x47494638 && (maFirstBytes[4] == 0x37 || maFirstBytes[4] == 0x39) + && maFirstBytes[5] == 0x61) + { + msDetectedFormat = "GIF"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPNG() +{ + if (mnFirstLong == 0x89504e47 && mnSecondLong == 0x0d0a1a0a) + { + msDetectedFormat = "PNG"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkJPG() +{ + if ((mnFirstLong == 0xffd8ffe0 && maFirstBytes[6] == 0x4a && maFirstBytes[7] == 0x46 + && maFirstBytes[8] == 0x49 && maFirstBytes[9] == 0x46) + || (mnFirstLong == 0xffd8fffe) || (0xffd8ff00 == (mnFirstLong & 0xffffff00))) + { + msDetectedFormat = "JPG"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkSVM() +{ + if (mnFirstLong == 0x53564744 && maFirstBytes[4] == 0x49) + { + msDetectedFormat = "SVM"; + return true; + } + else if (maFirstBytes[0] == 0x56 && maFirstBytes[1] == 0x43 && maFirstBytes[2] == 0x4C + && maFirstBytes[3] == 0x4D && maFirstBytes[4] == 0x54 && maFirstBytes[5] == 0x46) + { + msDetectedFormat = "SVM"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPCD() +{ + if (mnStreamLength < 2055) + return false; + char sBuffer[8]; + mrStream.Seek(mnStreamPosition + 2048); + sBuffer[mrStream.ReadBytes(sBuffer, 7)] = 0; + + if (strncmp(sBuffer, "PCD_IPI", 7) == 0) + { + msDetectedFormat = "PCD"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPSD() +{ + if ((mnFirstLong == 0x38425053) && ((mnSecondLong >> 16) == 1)) + { + msDetectedFormat = "PSD"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkEPS() +{ + const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data()); + + if (mnFirstLong == 0xC5D0D3C6) + { + msDetectedFormat = "EPS"; + return true; + } + else if (checkArrayForMatchingStrings(pFirstBytesAsCharArray, 30, { "%!PS-Adobe", " EPS" })) + { + msDetectedFormat = "EPS"; + return true; + } + + return false; +} + +bool GraphicFormatDetector::checkDXF() +{ + if (strncmp(reinterpret_cast<char*>(maFirstBytes.data()), "AutoCAD Binary DXF", 18) == 0) + { + msDetectedFormat = "DXF"; + return true; + } + + // ASCII DXF File Format + int i = 0; + while (i < 256 && maFirstBytes[i] <= 32) + { + ++i; + } + + if (i < 256 && maFirstBytes[i] == '0') + { + ++i; + + // only now do we have sufficient data to make a judgement + // based on a '0' + 'SECTION' == DXF argument + + while (i < 256 && maFirstBytes[i] <= 32) + { + ++i; + } + + if (i + 7 < 256 + && (strncmp(reinterpret_cast<char*>(maFirstBytes.data() + i), "SECTION", 7) == 0)) + { + msDetectedFormat = "DXF"; + return true; + } + } + return false; +} + +bool GraphicFormatDetector::checkPCT() +{ + if (isPCT(mrStream, mnStreamPosition, mnStreamLength)) + { + msDetectedFormat = "PCT"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPBMorPGMorPPM() +{ + if (maFirstBytes[0] == 'P') + { + switch (maFirstBytes[1]) + { + case '1': + case '4': + msDetectedFormat = "PBM"; + return true; + + case '2': + case '5': + msDetectedFormat = "PGM"; + return true; + + case '3': + case '6': + msDetectedFormat = "PPM"; + return true; + } + } + return false; +} + +bool GraphicFormatDetector::checkRAS() +{ + if (mnFirstLong == 0x59a66a95) + { + msDetectedFormat = "RAS"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkXPM() +{ + const char* pFirstBytesAsCharArray = reinterpret_cast<char*>(maFirstBytes.data()); + if (matchArrayWithString(pFirstBytesAsCharArray, 256, "/* XPM */")) + { + msDetectedFormat = "XPM"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkXBM() +{ + sal_uInt64 nSize = std::min<sal_uInt64>(mnStreamLength, 2048); + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize]); + + mrStream.Seek(mnStreamPosition); + nSize = mrStream.ReadBytes(pBuffer.get(), nSize); + + const char* pBufferAsCharArray = reinterpret_cast<char*>(pBuffer.get()); + + if (checkArrayForMatchingStrings(pBufferAsCharArray, nSize, { "#define", "_width" })) + { + msDetectedFormat = "XBM"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkSVG() +{ + sal_uInt64 nCheckSize = std::min<sal_uInt64>(mnStreamLength, 256); + sal_uInt8 sExtendedOrDecompressedFirstBytes[SVG_CHECK_SIZE]; + sal_uInt64 nDecompressedSize = nCheckSize; + // check if it is gzipped -> svgz + sal_uInt8* pCheckArray = checkAndUncompressBuffer(sExtendedOrDecompressedFirstBytes, + SVG_CHECK_SIZE, nDecompressedSize); + nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 256); + bool bIsSvg(false); + bool bIsGZip = (nDecompressedSize > 0); + const char* pCheckArrayAsCharArray = reinterpret_cast<char*>(pCheckArray); + // check for XML + // #119176# SVG files which have no xml header at all have shown up this is optional + // check for "xml" then "version" then "DOCTYPE" and "svg" tags + if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, + { "<?xml", "version", "DOCTYPE", "svg" })) + { + bIsSvg = true; + } + + // check for svg element in 1st 256 bytes + // search for '<svg' + if (!bIsSvg && checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" })) + { + bIsSvg = true; + } + + // extended search for svg element + if (!bIsSvg) + { + // it's a xml, look for '<svg' in full file. Should not happen too + // often since the tests above will handle most cases, but can happen + // with Svg files containing big comment headers or Svg as the host + // language + + pCheckArrayAsCharArray = reinterpret_cast<char*>(sExtendedOrDecompressedFirstBytes); + + if (bIsGZip) + { + nCheckSize = std::min<sal_uInt64>(nDecompressedSize, 2048); + } + else + { + nCheckSize = std::min<sal_uInt64>(mnStreamLength, 2048); + mrStream.Seek(mnStreamPosition); + nCheckSize = mrStream.ReadBytes(sExtendedOrDecompressedFirstBytes, nCheckSize); + } + + // search for '<svg' + if (checkArrayForMatchingStrings(pCheckArrayAsCharArray, nCheckSize, { "<svg" })) + { + bIsSvg = true; + } + } + + if (bIsSvg) + { + msDetectedFormat = "SVG"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkTGA() +{ + // Check TGA ver.2 footer bytes + if (mnStreamLength > 18) + { + char sFooterBytes[18]; + + mrStream.Seek(STREAM_SEEK_TO_END); + mrStream.SeekRel(-18); + if (mrStream.ReadBytes(sFooterBytes, 18) == 18 + && memcmp(sFooterBytes, "TRUEVISION-XFILE.", SAL_N_ELEMENTS(sFooterBytes)) == 0) + { + msDetectedFormat = "TGA"; + return true; + } + } + + // Fallback to file extension check + if (maExtension.startsWith("TGA")) + { + msDetectedFormat = "TGA"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkMOV() +{ + if ((maFirstBytes[4] == 'f' && maFirstBytes[5] == 't' && maFirstBytes[6] == 'y' + && maFirstBytes[7] == 'p' && maFirstBytes[8] == 'q' && maFirstBytes[9] == 't') + || (maFirstBytes[4] == 'm' && maFirstBytes[5] == 'o' && maFirstBytes[6] == 'o' + && maFirstBytes[7] == 'v' && maFirstBytes[11] == 'l' && maFirstBytes[12] == 'm')) + { + msDetectedFormat = "MOV"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkPDF() +{ + if (maFirstBytes[0] == '%' && maFirstBytes[1] == 'P' && maFirstBytes[2] == 'D' + && maFirstBytes[3] == 'F' && maFirstBytes[4] == '-') + { + msDetectedFormat = "PDF"; + return true; + } + return false; +} + +bool GraphicFormatDetector::checkWEBP() +{ + if (maFirstBytes[0] == 'R' && maFirstBytes[1] == 'I' && maFirstBytes[2] == 'F' + && maFirstBytes[3] == 'F' && maFirstBytes[8] == 'W' && maFirstBytes[9] == 'E' + && maFirstBytes[10] == 'B' && maFirstBytes[11] == 'P') + { + msDetectedFormat = "WEBP"; + return true; + } + return false; +} + +sal_uInt8* GraphicFormatDetector::checkAndUncompressBuffer(sal_uInt8* aUncompressedBuffer, + sal_uInt32 nSize, sal_uInt64& nRetSize) +{ + if (ZCodec::IsZCompressed(mrStream)) + { + ZCodec aCodec; + mrStream.Seek(mnStreamPosition); + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/ true); + auto nDecompressedOut = aCodec.Read(mrStream, aUncompressedBuffer, nSize); + // ZCodec::Decompress returns -1 on failure + nRetSize = nDecompressedOut < 0 ? 0 : nDecompressedOut; + aCodec.EndCompression(); + // Recalculate first/second long + for (int i = 0; i < 4; ++i) + { + mnFirstLong = (mnFirstLong << 8) | sal_uInt32(aUncompressedBuffer[i]); + mnSecondLong = (mnSecondLong << 8) | sal_uInt32(aUncompressedBuffer[i + 4]); + } + return aUncompressedBuffer; + } + nRetSize = 0; + return maFirstBytes.data(); +} + +} // vcl namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/GraphicNativeMetadata.cxx b/vcl/source/filter/GraphicNativeMetadata.cxx new file mode 100644 index 000000000..5b11fcc78 --- /dev/null +++ b/vcl/source/filter/GraphicNativeMetadata.cxx @@ -0,0 +1,59 @@ +/* -*- 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 <vcl/GraphicNativeMetadata.hxx> +#include <vcl/gfxlink.hxx> +#include "jpeg/Exif.hxx" +#include <memory> + +GraphicNativeMetadata::GraphicNativeMetadata() + : mRotation(0) +{ +} + +bool GraphicNativeMetadata::read(Graphic const& rGraphic) +{ + GfxLink aLink = rGraphic.GetGfxLink(); + if (aLink.GetType() != GfxLinkType::NativeJpg) + return false; + + sal_uInt32 aDataSize = aLink.GetDataSize(); + if (!aDataSize) + return false; + + std::unique_ptr<sal_uInt8[]> aBuffer(new sal_uInt8[aDataSize]); + + memcpy(aBuffer.get(), aLink.GetData(), aDataSize); + SvMemoryStream aMemoryStream(aBuffer.get(), aDataSize, StreamMode::READ); + + read(aMemoryStream); + + return true; +} + +bool GraphicNativeMetadata::read(SvStream& rStream) +{ + Exif aExif; + aExif.read(rStream); + mRotation = aExif.getRotation(); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/GraphicNativeTransform.cxx b/vcl/source/filter/GraphicNativeTransform.cxx new file mode 100644 index 000000000..792dd6a93 --- /dev/null +++ b/vcl/source/filter/GraphicNativeTransform.cxx @@ -0,0 +1,164 @@ +/* -*- 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 <vcl/GraphicNativeTransform.hxx> + +#include <vcl/gfxlink.hxx> +#include <vcl/graphicfilter.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <tools/stream.hxx> +#include <comphelper/propertyvalue.hxx> + +#include "jpeg/Exif.hxx" +#include "jpeg/JpegTransform.hxx" + +GraphicNativeTransform::GraphicNativeTransform(Graphic& rGraphic) + : mrGraphic(rGraphic) +{ +} + +void GraphicNativeTransform::rotate(Degree10 aInputRotation) +{ + // Rotation can be between 0 and 3600 + Degree10 aRotation = aInputRotation % 3600_deg10; + + if (aRotation == 0_deg10) + { + return; // No rotation is needed + } + else if (aRotation != 900_deg10 && aRotation != 1800_deg10 && aRotation != 2700_deg10) + { + return; + } + + GfxLink aLink = mrGraphic.GetGfxLink(); + if (aLink.GetType() == GfxLinkType::NativeJpg) + { + rotateJPEG(aRotation); + } + else if (aLink.GetType() == GfxLinkType::NativePng) + { + rotateGeneric(aRotation, u"png"); + } + else if (aLink.GetType() == GfxLinkType::NativeGif) + { + rotateGeneric(aRotation, u"gif"); + } + else if (aLink.GetType() == GfxLinkType::NONE) + { + rotateBitmapOnly(aRotation); + } +} + +bool GraphicNativeTransform::rotateBitmapOnly(Degree10 aRotation) +{ + if (mrGraphic.IsAnimated()) + { + return false; + } + + BitmapEx aBitmap = mrGraphic.GetBitmapEx(); + aBitmap.Rotate(aRotation, COL_BLACK); + mrGraphic = aBitmap; + + return true; +} + +bool GraphicNativeTransform::rotateGeneric(Degree10 aRotation, std::u16string_view aType) +{ + // Can't rotate animations yet + if (mrGraphic.IsAnimated()) + { + return false; + } + + SvMemoryStream aStream; + + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + + css::uno::Sequence<css::beans::PropertyValue> aFilterData{ + comphelper::makePropertyValue("Interlaced", sal_Int32(0)), + comphelper::makePropertyValue("Compression", sal_Int32(9)), + comphelper::makePropertyValue("Quality", sal_Int32(90)) + }; + + sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(aType); + + BitmapEx aBitmap = mrGraphic.GetBitmapEx(); + aBitmap.Rotate(aRotation, COL_BLACK); + rFilter.ExportGraphic(aBitmap, u"none", aStream, nFilterFormat, &aFilterData); + + aStream.Seek(STREAM_SEEK_TO_BEGIN); + + Graphic aGraphic; + rFilter.ImportGraphic(aGraphic, u"import", aStream); + + mrGraphic = aGraphic; + return true; +} + +void GraphicNativeTransform::rotateJPEG(Degree10 aRotation) +{ + BitmapEx aBitmap = mrGraphic.GetBitmapEx(); + + if (aBitmap.GetSizePixel().Width() % 16 != 0 || aBitmap.GetSizePixel().Height() % 16 != 0) + { + rotateGeneric(aRotation, u"png"); + } + else + { + GfxLink aLink = mrGraphic.GetGfxLink(); + + SvMemoryStream aSourceStream; + aSourceStream.WriteBytes(aLink.GetData(), aLink.GetDataSize()); + aSourceStream.Seek(STREAM_SEEK_TO_BEGIN); + + exif::Orientation aOrientation = exif::TOP_LEFT; + + Exif exif; + if (exif.read(aSourceStream)) + { + aOrientation = exif.getOrientation(); + } + + SvMemoryStream aTargetStream; + JpegTransform transform(aSourceStream, aTargetStream); + transform.setRotate(aRotation); + transform.perform(); + + aTargetStream.Seek(STREAM_SEEK_TO_BEGIN); + + // Reset orientation in exif if needed + if (exif.hasExif() && aOrientation != exif::TOP_LEFT) + { + exif.setOrientation(exif::TOP_LEFT); + exif.write(aTargetStream); + } + + aTargetStream.Seek(STREAM_SEEK_TO_BEGIN); + + Graphic aGraphic; + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + rFilter.ImportGraphic(aGraphic, u"import", aTargetStream); + mrGraphic = aGraphic; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/bmp/BmpReader.cxx b/vcl/source/filter/bmp/BmpReader.cxx new file mode 100644 index 000000000..3d2b6a463 --- /dev/null +++ b/vcl/source/filter/bmp/BmpReader.cxx @@ -0,0 +1,30 @@ +/* -*- 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 <filter/BmpReader.hxx> +#include <vcl/TypeSerializer.hxx> + +bool BmpReader(SvStream& rStream, Graphic& rGraphic) +{ + TypeSerializer aSerializer(rStream); + aSerializer.readGraphic(rGraphic); + return !rStream.GetError(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/bmp/BmpWriter.cxx b/vcl/source/filter/bmp/BmpWriter.cxx new file mode 100644 index 000000000..a5fcedec2 --- /dev/null +++ b/vcl/source/filter/bmp/BmpWriter.cxx @@ -0,0 +1,40 @@ +/* -*- 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 <filter/BmpWriter.hxx> +#include <vcl/dibtools.hxx> + +bool BmpWriter(SvStream& rStream, const Graphic& rGraphic, FilterConfigItem* pFilterConfigItem) +{ + BitmapEx aBitmap = rGraphic.GetBitmapEx(); + sal_Int32 nColor = pFilterConfigItem->ReadInt32("Color", 0); + + auto nColorRes = static_cast<BmpConversion>(nColor); + if (nColorRes != BmpConversion::NNONE && nColorRes <= BmpConversion::N24Bit) + { + if (!aBitmap.Convert(nColorRes)) + aBitmap = rGraphic.GetBitmapEx(); + } + bool bRleCoding = pFilterConfigItem->ReadBool("RLE_Coding", true); + WriteDIB(aBitmap, rStream, bRleCoding); + + return rStream.good(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/egif/egif.cxx b/vcl/source/filter/egif/egif.cxx new file mode 100644 index 000000000..699b7f05d --- /dev/null +++ b/vcl/source/filter/egif/egif.cxx @@ -0,0 +1,549 @@ +/* -*- 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 <tools/stream.hxx> +#include <tools/debug.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include "giflzwc.hxx" +#include <memory> +#include <filter/GifWriter.hxx> + +namespace { + +class GIFWriter +{ + Bitmap aAccBmp; + SvStream& m_rGIF; + BitmapReadAccess* m_pAcc; + sal_uInt32 nMinPercent; + sal_uInt32 nMaxPercent; + sal_uInt32 nLastPercent; + tools::Long nActX; + tools::Long nActY; + sal_Int32 nInterlaced; + bool bStatus; + bool bTransparent; + + void MayCallback(sal_uInt32 nPercent); + void WriteSignature( bool bGIF89a ); + void WriteGlobalHeader( const Size& rSize ); + void WriteLoopExtension( const Animation& rAnimation ); + void WriteLogSizeExtension( const Size& rSize100 ); + void WriteImageExtension( tools::Long nTimer, Disposal eDisposal ); + void WriteLocalHeader(); + void WritePalette(); + void WriteAccess(); + void WriteTerminator(); + + bool CreateAccess( const BitmapEx& rBmpEx ); + void DestroyAccess(); + + void WriteAnimation( const Animation& rAnimation ); + void WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint, bool bExtended, + tools::Long nTimer = 0, Disposal eDisposal = Disposal::Not ); + + css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator; + +public: + + explicit GIFWriter(SvStream &rStream); + + bool WriteGIF( const Graphic& rGraphic, FilterConfigItem* pConfigItem ); +}; + +} + +GIFWriter::GIFWriter(SvStream &rStream) + : m_rGIF(rStream) + , m_pAcc(nullptr) + , nMinPercent(0) + , nMaxPercent(0) + , nLastPercent(0) + , nActX(0) + , nActY(0) + , nInterlaced(0) + , bStatus(false) + , bTransparent(false) +{ +} + + +bool GIFWriter::WriteGIF(const Graphic& rGraphic, FilterConfigItem* pFilterConfigItem) +{ + if ( pFilterConfigItem ) + { + xStatusIndicator = pFilterConfigItem->GetStatusIndicator(); + if ( xStatusIndicator.is() ) + { + xStatusIndicator->start( OUString(), 100 ); + } + } + + Size aSize100; + const MapMode aMap( rGraphic.GetPrefMapMode() ); + bool bLogSize = ( aMap.GetMapUnit() != MapUnit::MapPixel ); + + if( bLogSize ) + aSize100 = OutputDevice::LogicToLogic(rGraphic.GetPrefSize(), aMap, MapMode(MapUnit::Map100thMM)); + + bStatus = true; + nLastPercent = 0; + nInterlaced = 0; + m_pAcc = nullptr; + + if ( pFilterConfigItem ) + nInterlaced = pFilterConfigItem->ReadInt32( "Interlaced", 0 ); + + m_rGIF.SetEndian( SvStreamEndian::LITTLE ); + + if( rGraphic.IsAnimated() ) + { + const Animation& rAnimation = rGraphic.GetAnimation(); + + WriteSignature( true ); + + if ( bStatus ) + { + WriteGlobalHeader( rAnimation.GetDisplaySizePixel() ); + + if( bStatus ) + { + WriteLoopExtension( rAnimation ); + + if( bStatus ) + WriteAnimation( rAnimation ); + } + } + } + else + { + const bool bGrafTrans = rGraphic.IsTransparent(); + + BitmapEx aBmpEx = rGraphic.GetBitmapEx(); + + nMinPercent = 0; + nMaxPercent = 100; + + WriteSignature( bGrafTrans || bLogSize ); + + if( bStatus ) + { + WriteGlobalHeader( aBmpEx.GetSizePixel() ); + + if( bStatus ) + WriteBitmapEx( aBmpEx, Point(), bGrafTrans ); + } + } + + if( bStatus ) + { + if( bLogSize ) + WriteLogSizeExtension( aSize100 ); + + WriteTerminator(); + } + + if ( xStatusIndicator.is() ) + xStatusIndicator->end(); + + return bStatus; +} + + +void GIFWriter::WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint, + bool bExtended, tools::Long nTimer, Disposal eDisposal ) +{ + if( !CreateAccess( rBmpEx ) ) + return; + + nActX = rPoint.X(); + nActY = rPoint.Y(); + + if( bExtended ) + WriteImageExtension( nTimer, eDisposal ); + + if( bStatus ) + { + WriteLocalHeader(); + + if( bStatus ) + { + WritePalette(); + + if( bStatus ) + WriteAccess(); + } + } + + DestroyAccess(); +} + + +void GIFWriter::WriteAnimation( const Animation& rAnimation ) +{ + const sal_uInt16 nCount = rAnimation.Count(); + + if( !nCount ) + return; + + const double fStep = 100. / nCount; + + nMinPercent = 0; + nMaxPercent = static_cast<sal_uInt32>(fStep); + + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + const AnimationBitmap& rAnimationBitmap = rAnimation.Get( i ); + + WriteBitmapEx(rAnimationBitmap.maBitmapEx, rAnimationBitmap.maPositionPixel, true, + rAnimationBitmap.mnWait, rAnimationBitmap.meDisposal ); + nMinPercent = nMaxPercent; + nMaxPercent = static_cast<sal_uInt32>(nMaxPercent + fStep); + } +} + + +void GIFWriter::MayCallback(sal_uInt32 nPercent) +{ + if ( xStatusIndicator.is() ) + { + if( nPercent >= nLastPercent + 3 ) + { + nLastPercent = nPercent; + if ( nPercent <= 100 ) + xStatusIndicator->setValue( nPercent ); + } + } +} + + +bool GIFWriter::CreateAccess( const BitmapEx& rBmpEx ) +{ + if( bStatus ) + { + Bitmap aMask( rBmpEx.GetAlpha() ); + + aAccBmp = rBmpEx.GetBitmap(); + bTransparent = false; + + if( !aMask.IsEmpty() ) + { + if( aAccBmp.Convert( BmpConversion::N8BitTrans ) ) + { + aMask.Convert( BmpConversion::N1BitThreshold ); + aAccBmp.Replace( aMask, BMP_COL_TRANS ); + bTransparent = true; + } + else + aAccBmp.Convert( BmpConversion::N8BitColors ); + } + else + aAccBmp.Convert( BmpConversion::N8BitColors ); + + m_pAcc = aAccBmp.AcquireReadAccess(); + + if( !m_pAcc ) + bStatus = false; + } + + return bStatus; +} + + +void GIFWriter::DestroyAccess() +{ + Bitmap::ReleaseAccess( m_pAcc ); + m_pAcc = nullptr; +} + + +void GIFWriter::WriteSignature( bool bGIF89a ) +{ + if( bStatus ) + { + m_rGIF.WriteBytes(bGIF89a ? "GIF89a" : "GIF87a" , 6); + + if( m_rGIF.GetError() ) + bStatus = false; + } +} + + +void GIFWriter::WriteGlobalHeader( const Size& rSize ) +{ + if( !bStatus ) + return; + + // 256 colors + const sal_uInt16 nWidth = static_cast<sal_uInt16>(rSize.Width()); + const sal_uInt16 nHeight = static_cast<sal_uInt16>(rSize.Height()); + const sal_uInt8 cFlags = 128 | ( 7 << 4 ); + + // write values + m_rGIF.WriteUInt16( nWidth ); + m_rGIF.WriteUInt16( nHeight ); + m_rGIF.WriteUChar( cFlags ); + m_rGIF.WriteUChar( 0x00 ); + m_rGIF.WriteUChar( 0x00 ); + + // write dummy palette with two entries (black/white); + // we do this only because of a bug in Photoshop, since those can't + // read pictures without a global color palette + m_rGIF.WriteUInt16( 0 ); + m_rGIF.WriteUInt16( 255 ); + m_rGIF.WriteUInt16( 65535 ); + + if( m_rGIF.GetError() ) + bStatus = false; +} + + +void GIFWriter::WriteLoopExtension( const Animation& rAnimation ) +{ + DBG_ASSERT( rAnimation.Count() > 0, "Animation has no bitmaps!" ); + + sal_uInt16 nLoopCount = static_cast<sal_uInt16>(rAnimation.GetLoopCount()); + + // if only one run should take place + // the LoopExtension won't be written + // The default in this case is a single run + if( nLoopCount == 1 ) + return; + + // Netscape interprets the LoopCount + // as the sole number of _repetitions_ + if( nLoopCount ) + nLoopCount--; + + const sal_uInt8 cLoByte = static_cast<sal_uInt8>(nLoopCount); + const sal_uInt8 cHiByte = static_cast<sal_uInt8>( nLoopCount >> 8 ); + + m_rGIF.WriteUChar( 0x21 ); + m_rGIF.WriteUChar( 0xff ); + m_rGIF.WriteUChar( 0x0b ); + m_rGIF.WriteBytes( "NETSCAPE2.0", 11 ); + m_rGIF.WriteUChar( 0x03 ); + m_rGIF.WriteUChar( 0x01 ); + m_rGIF.WriteUChar( cLoByte ); + m_rGIF.WriteUChar( cHiByte ); + m_rGIF.WriteUChar( 0x00 ); +} + + +void GIFWriter::WriteLogSizeExtension( const Size& rSize100 ) +{ + // writer PrefSize in 100th-mm as ApplicationExtension + if( rSize100.Width() && rSize100.Height() ) + { + m_rGIF.WriteUChar( 0x21 ); + m_rGIF.WriteUChar( 0xff ); + m_rGIF.WriteUChar( 0x0b ); + m_rGIF.WriteBytes( "STARDIV 5.0", 11 ); + m_rGIF.WriteUChar( 0x09 ); + m_rGIF.WriteUChar( 0x01 ); + m_rGIF.WriteUInt32( rSize100.Width() ); + m_rGIF.WriteUInt32( rSize100.Height() ); + m_rGIF.WriteUChar( 0x00 ); + } +} + + +void GIFWriter::WriteImageExtension( tools::Long nTimer, Disposal eDisposal ) +{ + if( !bStatus ) + return; + + const sal_uInt16 nDelay = static_cast<sal_uInt16>(nTimer); + sal_uInt8 cFlags = 0; + + // set Transparency-Flag + if( bTransparent ) + cFlags |= 1; + + // set Disposal-value + if( eDisposal == Disposal::Back ) + cFlags |= ( 2 << 2 ); + else if( eDisposal == Disposal::Previous ) + cFlags |= ( 3 << 2 ); + + m_rGIF.WriteUChar( 0x21 ); + m_rGIF.WriteUChar( 0xf9 ); + m_rGIF.WriteUChar( 0x04 ); + m_rGIF.WriteUChar( cFlags ); + m_rGIF.WriteUInt16( nDelay ); + m_rGIF.WriteUChar( m_pAcc->GetBestPaletteIndex( BMP_COL_TRANS ) ); + m_rGIF.WriteUChar( 0x00 ); + + if( m_rGIF.GetError() ) + bStatus = false; +} + + +void GIFWriter::WriteLocalHeader() +{ + if( !bStatus ) + return; + + const sal_uInt16 nPosX = static_cast<sal_uInt16>(nActX); + const sal_uInt16 nPosY = static_cast<sal_uInt16>(nActY); + const sal_uInt16 nWidth = static_cast<sal_uInt16>(m_pAcc->Width()); + const sal_uInt16 nHeight = static_cast<sal_uInt16>(m_pAcc->Height()); + sal_uInt8 cFlags = static_cast<sal_uInt8>( m_pAcc->GetBitCount() - 1 ); + + // set Interlaced-Flag + if( nInterlaced ) + cFlags |= 0x40; + + // set Flag for the local color palette + cFlags |= 0x80; + + m_rGIF.WriteUChar( 0x2c ); + m_rGIF.WriteUInt16( nPosX ); + m_rGIF.WriteUInt16( nPosY ); + m_rGIF.WriteUInt16( nWidth ); + m_rGIF.WriteUInt16( nHeight ); + m_rGIF.WriteUChar( cFlags ); + + if( m_rGIF.GetError() ) + bStatus = false; +} + + +void GIFWriter::WritePalette() +{ + if( !(bStatus && m_pAcc->HasPalette()) ) + return; + + const sal_uInt16 nCount = m_pAcc->GetPaletteEntryCount(); + const sal_uInt16 nMaxCount = ( 1 << m_pAcc->GetBitCount() ); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + const BitmapColor& rColor = m_pAcc->GetPaletteColor( i ); + + m_rGIF.WriteUChar( rColor.GetRed() ); + m_rGIF.WriteUChar( rColor.GetGreen() ); + m_rGIF.WriteUChar( rColor.GetBlue() ); + } + + // fill up the rest with 0 + if( nCount < nMaxCount ) + m_rGIF.SeekRel( ( nMaxCount - nCount ) * 3 ); + + if( m_rGIF.GetError() ) + bStatus = false; +} + + +void GIFWriter::WriteAccess() +{ + GIFLZWCompressor aCompressor; + const tools::Long nWidth = m_pAcc->Width(); + const tools::Long nHeight = m_pAcc->Height(); + std::unique_ptr<sal_uInt8[]> pBuffer; + bool bNative = m_pAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal; + + if( !bNative ) + pBuffer.reset(new sal_uInt8[ nWidth ]); + + if( !(bStatus && ( 8 == m_pAcc->GetBitCount() ) && m_pAcc->HasPalette()) ) + return; + + aCompressor.StartCompression( m_rGIF, m_pAcc->GetBitCount() ); + + tools::Long nY, nT; + + for( tools::Long i = 0; i < nHeight; ++i ) + { + if( nInterlaced ) + { + nY = i << 3; + + if( nY >= nHeight ) + { + nT = i - ( ( nHeight + 7 ) >> 3 ); + nY= ( nT << 3 ) + 4; + + if( nY >= nHeight ) + { + nT -= ( nHeight + 3 ) >> 3; + nY = ( nT << 2 ) + 2; + + if ( nY >= nHeight ) + { + nT -= ( ( nHeight + 1 ) >> 2 ); + nY = ( nT << 1 ) + 1; + } + } + } + } + else + nY = i; + + if( bNative ) + aCompressor.Compress( m_pAcc->GetScanline( nY ), nWidth ); + else + { + Scanline pScanline = m_pAcc->GetScanline( nY ); + for( tools::Long nX = 0; nX < nWidth; nX++ ) + pBuffer[ nX ] = m_pAcc->GetIndexFromData( pScanline, nX ); + + aCompressor.Compress( pBuffer.get(), nWidth ); + } + + if ( m_rGIF.GetError() ) + bStatus = false; + + MayCallback( nMinPercent + ( nMaxPercent - nMinPercent ) * i / nHeight ); + + if( !bStatus ) + break; + } + + aCompressor.EndCompression(); + + if ( m_rGIF.GetError() ) + bStatus = false; +} + + +void GIFWriter::WriteTerminator() +{ + if( bStatus ) + { + m_rGIF.WriteUChar( 0x3b ); + + if( m_rGIF.GetError() ) + bStatus = false; + } +} + + +bool ExportGifGraphic(SvStream& rStream, const Graphic& rGraphic, FilterConfigItem* pConfigItem) +{ + GIFWriter aWriter(rStream); + return aWriter.WriteGIF(rGraphic, pConfigItem); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/egif/giflzwc.cxx b/vcl/source/filter/egif/giflzwc.cxx new file mode 100644 index 000000000..41c65d2da --- /dev/null +++ b/vcl/source/filter/egif/giflzwc.cxx @@ -0,0 +1,225 @@ +/* -*- 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 <tools/stream.hxx> +#include "giflzwc.hxx" +#include <array> + + +class GIFImageDataOutputStream +{ +private: + + void FlushBlockBuf(); + inline void FlushBitsBufsFullBytes(); + + SvStream& rStream; + std::array<sal_uInt8, 255> + pBlockBuf; + sal_uInt8 nBlockBufSize; + sal_uInt32 nBitsBuf; + sal_uInt16 nBitsBufSize; + +public: + + GIFImageDataOutputStream( SvStream & rGIF, sal_uInt8 nLZWDataSize ); + ~GIFImageDataOutputStream(); + + inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ); +}; + + +inline void GIFImageDataOutputStream::FlushBitsBufsFullBytes() +{ + while (nBitsBufSize>=8) + { + if( nBlockBufSize==255 ) + FlushBlockBuf(); + + pBlockBuf[nBlockBufSize++] = static_cast<sal_uInt8>(nBitsBuf); + nBitsBuf >>= 8; + nBitsBufSize -= 8; + } +} + + +inline void GIFImageDataOutputStream::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ) +{ + if( nBitsBufSize+nCodeLen>32 ) + FlushBitsBufsFullBytes(); + + nBitsBuf |= static_cast<sal_uInt32>(nCode) << nBitsBufSize; + nBitsBufSize = nBitsBufSize + nCodeLen; +} + + +GIFImageDataOutputStream::GIFImageDataOutputStream( SvStream & rGIF, sal_uInt8 nLZWDataSize ) : + rStream(rGIF), nBlockBufSize(0), nBitsBuf(0), nBitsBufSize(0) +{ + rStream.WriteUChar( nLZWDataSize ); +} + + +GIFImageDataOutputStream::~GIFImageDataOutputStream() +{ + WriteBits(0,7); + FlushBitsBufsFullBytes(); + FlushBlockBuf(); + rStream.WriteUChar( 0 ); +} + + +void GIFImageDataOutputStream::FlushBlockBuf() +{ + if( nBlockBufSize ) + { + rStream.WriteUChar( nBlockBufSize ); + rStream.WriteBytes(pBlockBuf.data(), nBlockBufSize); + nBlockBufSize = 0; + } +} + + +struct GIFLZWCTreeNode +{ + + GIFLZWCTreeNode* pBrother; // next node which has the same father + GIFLZWCTreeNode* pFirstChild; // first + sal_uInt16 nCode; // the code for the string of pixel values which comes about + sal_uInt16 nValue; // the pixel value +}; + + +GIFLZWCompressor::GIFLZWCompressor() + : pPrefix(nullptr), nDataSize(0), nClearCode(0), + nEOICode(0), nTableSize(0), nCodeSize(0) +{ +} + + +GIFLZWCompressor::~GIFLZWCompressor() +{ + if (pIDOS!=nullptr) EndCompression(); +} + + +void GIFLZWCompressor::StartCompression( SvStream& rGIF, sal_uInt16 nPixelSize ) +{ + if( pIDOS ) + return; + + sal_uInt16 i; + + nDataSize = nPixelSize; + + if( nDataSize < 2 ) + nDataSize=2; + + nClearCode=1<<nDataSize; + nEOICode=nClearCode+1; + nTableSize=nEOICode+1; + nCodeSize=nDataSize+1; + + pIDOS.reset(new GIFImageDataOutputStream(rGIF,static_cast<sal_uInt8>(nDataSize))); + pTable.reset(new GIFLZWCTreeNode[4096]); + + for (i=0; i<4096; i++) + { + pTable[i].pBrother = pTable[i].pFirstChild = nullptr; + pTable[i].nCode = i; + pTable[i].nValue = static_cast<sal_uInt8>( i ); + } + + pPrefix = nullptr; + pIDOS->WriteBits( nClearCode,nCodeSize ); +} + +void GIFLZWCompressor::Compress(sal_uInt8* pSrc, sal_uInt32 nSize) +{ + if( !pIDOS ) + return; + + GIFLZWCTreeNode* p; + sal_uInt16 i; + sal_uInt8 nV; + + if( !pPrefix && nSize ) + { + pPrefix=&pTable[*pSrc++]; + nSize--; + } + + while( nSize ) + { + nSize--; + nV=*pSrc++; + for( p=pPrefix->pFirstChild; p!=nullptr; p=p->pBrother ) + { + if (p->nValue==nV) + break; + } + + if( p) + pPrefix=p; + else + { + pIDOS->WriteBits(pPrefix->nCode,nCodeSize); + + if (nTableSize==4096) + { + pIDOS->WriteBits(nClearCode,nCodeSize); + + for (i=0; i<nClearCode; i++) + pTable[i].pFirstChild=nullptr; + + nCodeSize=nDataSize+1; + nTableSize=nEOICode+1; + } + else + { + if(nTableSize==static_cast<sal_uInt16>(1<<nCodeSize)) + nCodeSize++; + + p=&pTable[nTableSize++]; + p->pBrother=pPrefix->pFirstChild; + pPrefix->pFirstChild=p; + p->nValue=nV; + p->pFirstChild=nullptr; + } + + pPrefix=&pTable[nV]; + } + } +} + +void GIFLZWCompressor::EndCompression() +{ + if( pIDOS ) + { + if( pPrefix ) + pIDOS->WriteBits(pPrefix->nCode,nCodeSize); + + pIDOS->WriteBits( nEOICode,nCodeSize ); + pTable.reset(); + pIDOS.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/egif/giflzwc.hxx b/vcl/source/filter/egif/giflzwc.hxx new file mode 100644 index 000000000..057710c85 --- /dev/null +++ b/vcl/source/filter/egif/giflzwc.hxx @@ -0,0 +1,55 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX + +#include <vcl/mapmod.hxx> + + +class GIFImageDataOutputStream; +struct GIFLZWCTreeNode; + + +class GIFLZWCompressor +{ +private: + + std::unique_ptr<GIFImageDataOutputStream> pIDOS; + std::unique_ptr<GIFLZWCTreeNode[]> pTable; + GIFLZWCTreeNode* pPrefix; + sal_uInt16 nDataSize; + sal_uInt16 nClearCode; + sal_uInt16 nEOICode; + sal_uInt16 nTableSize; + sal_uInt16 nCodeSize; + +public: + + GIFLZWCompressor(); + ~GIFLZWCompressor(); + + void StartCompression( SvStream& rGIF, sal_uInt16 nPixelSize ); + void Compress(sal_uInt8* pSrc, sal_uInt32 nSize); + void EndCompression(); +}; + +#endif // INCLUDED_FILTER_SOURCE_GRAPHICFILTER_EGIF_GIFLZWC_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/eps/eps.cxx b/vcl/source/filter/eps/eps.cxx new file mode 100644 index 000000000..bb25bdccb --- /dev/null +++ b/vcl/source/filter/eps/eps.cxx @@ -0,0 +1,2667 @@ +/* -*- 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 <filter/EpsWriter.hxx> +#include <tools/stream.hxx> +#include <tools/poly.hxx> +#include <tools/fract.hxx> +#include <tools/helpers.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/svapp.hxx> +#include <vcl/metaact.hxx> +#include <vcl/graph.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/region.hxx> +#include <vcl/font.hxx> +#include <vcl/virdev.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/gradient.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <vcl/graphictools.hxx> +#include <vcl/weld.hxx> +#include <strings.hrc> +#include <osl/diagnose.h> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <officecfg/Office/Common.hxx> + +#include <cstdlib> +#include <memory> + +using namespace ::com::sun::star::uno; + +#define POSTSCRIPT_BOUNDINGSEARCH 0x1000 // we only try to get the BoundingBox + // in the first 4096 bytes + +#define EPS_PREVIEW_TIFF 1 +#define EPS_PREVIEW_EPSI 2 + +#define PS_LINESIZE 70 // maximum number of characters a line in the output + +// -----------------------------field-types------------------------------ + +namespace { + +struct StackMember +{ + struct StackMember * pSucc; + Color aGlobalCol; + bool bLineCol; + Color aLineCol; + bool bFillCol; + Color aFillCol; + Color aTextCol; + bool bTextFillCol; + Color aTextFillCol; + Color aBackgroundCol; + vcl::Font aFont; + TextAlign eTextAlign; + + double fLineWidth; + double fMiterLimit; + SvtGraphicStroke::CapType eLineCap; + SvtGraphicStroke::JoinType eJoinType; + SvtGraphicStroke::DashArray aDashArray; +}; + +struct PSLZWCTreeNode +{ + + PSLZWCTreeNode* pBrother; // next node who has the same father + PSLZWCTreeNode* pFirstChild; // first son + sal_uInt16 nCode; // The code for the string of pixel values, which arises if... <missing comment> + sal_uInt16 nValue; // the pixel value +}; + +enum NMode {PS_NONE = 0x00, PS_SPACE = 0x01, PS_RET = 0x02, PS_WRAP = 0x04}; // formatting mode: action which is inserted behind the output +inline NMode operator|(NMode a, NMode b) +{ + return static_cast<NMode>(static_cast<sal_uInt8>(a) | static_cast<sal_uInt8>(b)); +} + +class PSWriter +{ +private: + bool mbStatus; + bool mbLevelWarning; // if there any embedded eps file which was not exported + sal_uInt32 mnLatestPush; // offset to streamposition, where last push was done + + tools::Long mnLevel; // dialog options + bool mbGrayScale; + bool mbCompression; + sal_Int32 mnPreview; + sal_Int32 mnTextMode; + + SvStream* mpPS; + const GDIMetaFile* pMTF; + std::unique_ptr<GDIMetaFile> + pAMTF; // only created if Graphics is not a Metafile + ScopedVclPtrInstance<VirtualDevice> + pVDev; + + double nBoundingX2; // this represents the bounding box + double nBoundingY2; + + StackMember* pGDIStack; + sal_uInt32 mnCursorPos; // current cursor position in output + Color aColor; // current color which is used for output + bool bLineColor; + Color aLineColor; // current GDIMetafile color settings + bool bFillColor; + Color aFillColor; + Color aTextColor; + bool bTextFillColor; + Color aTextFillColor; + Color aBackgroundColor; + TextAlign eTextAlign; + + double fLineWidth; + double fMiterLimit; + SvtGraphicStroke::CapType eLineCap; + SvtGraphicStroke::JoinType eJoinType; + SvtGraphicStroke::DashArray aDashArray; + + vcl::Font maFont; + vcl::Font maLastFont; + + std::unique_ptr<PSLZWCTreeNode[]> pTable; // LZW compression data + PSLZWCTreeNode* pPrefix; // the compression is as same as the TIFF compression + sal_uInt16 nDataSize; + sal_uInt16 nClearCode; + sal_uInt16 nEOICode; + sal_uInt16 nTableSize; + sal_uInt16 nCodeSize; + sal_uInt32 nOffset; + sal_uInt32 dwShift; + + css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator; + + void ImplWriteProlog( const Graphic* pPreviewEPSI ); + void ImplWriteEpilog(); + void ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev ); + + // this method makes LF's, space inserting and word wrapping as used in all nMode + // parameters + inline void ImplExecMode( NMode nMode ); + + // writes char[] + LF to stream + inline void ImplWriteLine( const char*, NMode nMode = PS_RET ); + + // writes ( nNumb / 10^nCount ) in ASCII format to stream + void ImplWriteF( sal_Int32 nNumb, sal_uInt8 nCount = 3, NMode nMode = PS_SPACE ); + + // writes a double in ASCII format to stream + void ImplWriteDouble( double ); + + // writes a long in ASCII format to stream + void ImplWriteLong( sal_Int32 nNumb, NMode nMode = PS_SPACE ); + + // writes a byte in ASCII format to stream + void ImplWriteByte( sal_uInt8 nNumb, NMode nMode = PS_SPACE ); + + // writes a byte in ASCII (hex) format to stream + void ImplWriteHexByte( sal_uInt8 nNumb, NMode nMode = PS_WRAP ); + + // writes nNumb as number from 0.000 till 1.000 in ASCII format to stream + void ImplWriteB1( sal_uInt8 nNumb ); + + inline void ImplWritePoint( const Point& ); + void ImplMoveTo( const Point& ); + void ImplLineTo( const Point&, NMode nMode = PS_SPACE ); + void ImplCurveTo( const Point& rP1, const Point& rP2, const Point& rP3, NMode nMode ); + void ImplTranslate( const double& fX, const double& fY ); + void ImplScale( const double& fX, const double& fY ); + + void ImplAddPath( const tools::Polygon & rPolygon ); + void ImplWriteLineInfo( double fLineWidth, double fMiterLimit, SvtGraphicStroke::CapType eLineCap, + SvtGraphicStroke::JoinType eJoinType, SvtGraphicStroke::DashArray && rDashArray ); + void ImplWriteLineInfo( const LineInfo& rLineInfo ); + void ImplRect( const tools::Rectangle & rRectangle ); + void ImplRectFill ( const tools::Rectangle & rRectangle ); + void ImplWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, VirtualDevice& rVDev ); + void ImplIntersect( const tools::PolyPolygon& rPolyPoly ); + void ImplPolyPoly( const tools::PolyPolygon & rPolyPolygon, bool bTextOutline = false ); + void ImplPolyLine( const tools::Polygon & rPolygon ); + + void ImplSetClipRegion( vcl::Region const & rRegion ); + void ImplBmp( Bitmap const *, Bitmap const *, const Point &, double nWidth, double nHeight ); + void ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev ); + void ImplSetAttrForText( const Point & rPoint ); + void ImplWriteCharacter( char ); + void ImplWriteString( const OString&, VirtualDevice const & rVDev, o3tl::span<const sal_Int32> pDXArry, bool bStretch ); + void ImplDefineFont( const char*, const char* ); + + void ImplClosePathDraw(); + void ImplPathDraw(); + + inline void ImplWriteLineColor( NMode nMode ); + inline void ImplWriteFillColor( NMode nMode ); + inline void ImplWriteTextColor( NMode nMode ); + void ImplWriteColor( NMode nMode ); + + static double ImplGetScaling( const MapMode& ); + void ImplGetMapMode( const MapMode& ); + static bool ImplGetBoundingBox( double* nNumb, sal_uInt8* pSource, sal_uInt32 nSize ); + static sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uInt32 nComp, sal_uInt32 nSize ); + // LZW methods + void StartCompression(); + void Compress( sal_uInt8 nSrc ); + void EndCompression(); + inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ); + +public: + bool WritePS( const Graphic& rGraphic, SvStream& rTargetStream, FilterConfigItem* ); + PSWriter(); +}; + +} + +//========================== methods from PSWriter ========================== + + +PSWriter::PSWriter() + : mbStatus(false) + , mbLevelWarning(false) + , mnLatestPush(0) + , mnLevel(0) + , mbGrayScale(false) + , mbCompression(false) + , mnPreview(0) + , mnTextMode(0) + , mpPS(nullptr) + , pMTF(nullptr) + , nBoundingX2(0) + , nBoundingY2(0) + , pGDIStack(nullptr) + , mnCursorPos(0) + , bLineColor(false) + , bFillColor(false) + , bTextFillColor(false) + , eTextAlign() + , fLineWidth(0) + , fMiterLimit(0) + , eLineCap() + , eJoinType() + , pPrefix(nullptr) + , nDataSize(0) + , nClearCode(0) + , nEOICode(0) + , nTableSize(0) + , nCodeSize(0) + , nOffset(0) + , dwShift(0) +{ +} + +bool PSWriter::WritePS( const Graphic& rGraphic, SvStream& rTargetStream, FilterConfigItem* pFilterConfigItem ) +{ + sal_uInt32 nStreamPosition = 0, nPSPosition = 0; // -Wall warning, unset, check + + mbStatus = true; + mnPreview = 0; + mbLevelWarning = false; + mnLatestPush = 0xEFFFFFFE; + + if ( pFilterConfigItem ) + { + xStatusIndicator = pFilterConfigItem->GetStatusIndicator(); + if ( xStatusIndicator.is() ) + { + xStatusIndicator->start( OUString(), 100 ); + } + } + + mpPS = &rTargetStream; + mpPS->SetEndian( SvStreamEndian::LITTLE ); + + // default values for the dialog options + mnLevel = 2; + mbGrayScale = false; +#ifdef UNX // don't compress by default on unix as ghostscript is unable to read LZW compressed eps + mbCompression = false; +#else + mbCompression = true; +#endif + mnTextMode = 0; // default0 : export glyph outlines + + // try to get the dialog selection + if ( pFilterConfigItem ) + { +#ifdef UNX // don't put binary tiff preview ahead of postscript code by default on unix as ghostscript is unable to read it + mnPreview = pFilterConfigItem->ReadInt32( "Preview", 0 ); +#else + mnPreview = pFilterConfigItem->ReadInt32( "Preview", 1 ); +#endif + mnLevel = pFilterConfigItem->ReadInt32( "Version", 2 ); + if ( mnLevel != 1 ) + mnLevel = 2; + mbGrayScale = pFilterConfigItem->ReadInt32( "ColorFormat", 1 ) == 2; +#ifdef UNX // don't compress by default on unix as ghostscript is unable to read LZW compressed eps + mbCompression = pFilterConfigItem->ReadInt32( "CompressionMode", 0 ) != 0; +#else + mbCompression = pFilterConfigItem->ReadInt32( "CompressionMode", 1 ) == 1; +#endif + mnTextMode = pFilterConfigItem->ReadInt32( "TextMode", 0 ); + if ( mnTextMode > 2 ) + mnTextMode = 0; + } + + // compression is not available for Level 1 + if ( mnLevel == 1 ) + { + mbGrayScale = true; + mbCompression = false; + } + + if ( mnPreview & EPS_PREVIEW_TIFF ) + { + rTargetStream.WriteUInt32( 0xC6D3D0C5 ); + nStreamPosition = rTargetStream.Tell(); + rTargetStream.WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ) + .WriteUInt32( nStreamPosition + 26 ).WriteUInt32( 0 ).WriteUInt16( 0xffff ); + + ErrCode nErrCode; + if ( mbGrayScale ) + { + BitmapEx aTempBitmapEx( rGraphic.GetBitmapEx() ); + aTempBitmapEx.Convert( BmpConversion::N8BitGreys ); + nErrCode = GraphicConverter::Export( rTargetStream, aTempBitmapEx, ConvertDataFormat::TIF ) ; + } + else + nErrCode = GraphicConverter::Export( rTargetStream, rGraphic, ConvertDataFormat::TIF ) ; + + if ( nErrCode == ERRCODE_NONE ) + { + nPSPosition = rTargetStream.TellEnd(); + rTargetStream.Seek( nStreamPosition + 20 ); + rTargetStream.WriteUInt32( nPSPosition - 30 ); // size of tiff gfx + rTargetStream.Seek( nPSPosition ); + } + else + { + mnPreview &=~ EPS_PREVIEW_TIFF; + rTargetStream.Seek( nStreamPosition - 4 ); + } + } + + // global default value setting + StackMember* pGS; + + if (rGraphic.GetType() == GraphicType::GdiMetafile) + pMTF = &rGraphic.GetGDIMetaFile(); + else if (rGraphic.GetGDIMetaFile().GetActionSize()) + { + pAMTF.reset( new GDIMetaFile( rGraphic.GetGDIMetaFile() ) ); + pMTF = pAMTF.get(); + } + else + { + BitmapEx aBmp( rGraphic.GetBitmapEx() ); + pAMTF.reset( new GDIMetaFile ); + ScopedVclPtrInstance< VirtualDevice > pTmpVDev; + pAMTF->Record( pTmpVDev ); + pTmpVDev->DrawBitmapEx( Point(), aBmp ); + pAMTF->Stop(); + pAMTF->SetPrefSize( aBmp.GetSizePixel() ); + pMTF = pAMTF.get(); + } + pVDev->SetMapMode( pMTF->GetPrefMapMode() ); + nBoundingX2 = pMTF->GetPrefSize().Width(); + nBoundingY2 = pMTF->GetPrefSize().Height(); + + pGDIStack = nullptr; + aColor = COL_TRANSPARENT; + bLineColor = true; + aLineColor = COL_BLACK; + bFillColor = true; + aFillColor = COL_WHITE; + bTextFillColor = true; + aTextFillColor = COL_BLACK; + fLineWidth = 1; + fMiterLimit = 15; // use same limit as most graphic systems and basegfx + eLineCap = SvtGraphicStroke::capButt; + eJoinType = SvtGraphicStroke::joinMiter; + aBackgroundColor = COL_WHITE; + eTextAlign = ALIGN_BASELINE; + + if( pMTF->GetActionSize() ) + { + ImplWriteProlog( ( mnPreview & EPS_PREVIEW_EPSI ) ? &rGraphic : nullptr ); + mnCursorPos = 0; + ImplWriteActions( *pMTF, *pVDev ); + ImplWriteEpilog(); + if ( mnPreview & EPS_PREVIEW_TIFF ) + { + sal_uInt32 nPosition = rTargetStream.Tell(); + rTargetStream.Seek( nStreamPosition ); + rTargetStream.WriteUInt32( nPSPosition ); + rTargetStream.WriteUInt32( nPosition - nPSPosition ); + rTargetStream.Seek( nPosition ); + } + while( pGDIStack ) + { + pGS=pGDIStack; + pGDIStack=pGS->pSucc; + delete pGS; + } + } + else + mbStatus = false; + + if ( mbStatus && mbLevelWarning && pFilterConfigItem ) + { + std::locale loc = Translate::Create("flt"); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + Translate::get(KEY_VERSION_CHECK, loc))); + xInfoBox->run(); + } + + if ( xStatusIndicator.is() ) + xStatusIndicator->end(); + + return mbStatus; +} + +void PSWriter::ImplWriteProlog( const Graphic* pPreview ) +{ + ImplWriteLine( "%!PS-Adobe-3.0 EPSF-3.0 " ); + mpPS->WriteCharPtr( "%%BoundingBox: " ); // BoundingBox + ImplWriteLong( 0 ); + ImplWriteLong( 0 ); + Size aSizePoint = OutputDevice::LogicToLogic( pMTF->GetPrefSize(), + pMTF->GetPrefMapMode(), MapMode(MapUnit::MapPoint)); + ImplWriteLong( aSizePoint.Width() ); + ImplWriteLong( aSizePoint.Height() ,PS_RET ); + ImplWriteLine( "%%Pages: 0" ); + OUString aCreator; + OUString aCreatorOverride = officecfg::Office::Common::Save::Document::GeneratorOverride::get(); + if( !aCreatorOverride.isEmpty()) + aCreator = aCreatorOverride; + else + aCreator = "%%Creator: " + utl::ConfigManager::getProductName() + " " + + utl::ConfigManager::getProductVersion(); + ImplWriteLine( OUStringToOString( aCreator, RTL_TEXTENCODING_UTF8 ).getStr() ); + ImplWriteLine( "%%Title: none" ); + ImplWriteLine( "%%CreationDate: none" ); + +// defaults + + mpPS->WriteCharPtr( "%%LanguageLevel: " ); // Language level + ImplWriteLong( mnLevel, PS_RET ); + if ( !mbGrayScale && mnLevel == 1 ) + ImplWriteLine( "%%Extensions: CMYK" ); // CMYK extension is to set in color mode in level 1 + ImplWriteLine( "%%EndComments" ); + if ( pPreview && aSizePoint.Width() && aSizePoint.Height() ) + { + Size aSizeBitmap( ( aSizePoint.Width() + 7 ) & ~7, aSizePoint.Height() ); + Bitmap aTmpBitmap( pPreview->GetBitmapEx().GetBitmap() ); + aTmpBitmap.Scale( aSizeBitmap, BmpScaleFlag::BestQuality ); + aTmpBitmap.Convert( BmpConversion::N1BitThreshold ); + BitmapReadAccess* pAcc = aTmpBitmap.AcquireReadAccess(); + if ( pAcc ) + { + mpPS->WriteCharPtr( "%%BeginPreview: " ); // BoundingBox + ImplWriteLong( aSizeBitmap.Width() ); + ImplWriteLong( aSizeBitmap.Height() ); + mpPS->WriteCharPtr( "1 " ); + sal_Int32 nLines = aSizeBitmap.Width() / 312; + if ( ( nLines * 312 ) != aSizeBitmap.Width() ) + nLines++; + nLines *= aSizeBitmap.Height(); + ImplWriteLong( nLines ); + sal_Int32 nCount2, nCount = 4; + const BitmapColor aBlack( pAcc->GetBestMatchingColor( COL_BLACK ) ); + for ( tools::Long nY = 0; nY < aSizeBitmap.Height(); nY++ ) + { + nCount2 = 0; + char nVal = 0; + Scanline pScanline = pAcc->GetScanline( nY ); + for ( tools::Long nX = 0; nX < aSizeBitmap.Width(); nX++ ) + { + if ( !nCount2 ) + { + ImplExecMode( PS_RET ); + mpPS->WriteCharPtr( "%" ); + nCount2 = 312; + } + nVal <<= 1; + if ( pAcc->GetPixelFromData( pScanline, nX ) == aBlack ) + nVal |= 1; + if ( ! ( --nCount ) ) + { + if ( nVal > 9 ) + nVal += 'A' - 10; + else + nVal += '0'; + mpPS->WriteChar( nVal ); + nVal = 0; + nCount += 4; + } + nCount2--; + } + } + Bitmap::ReleaseAccess( pAcc ); + ImplExecMode( PS_RET ); + ImplWriteLine( "%%EndPreview" ); + } + } + ImplWriteLine( "%%BeginProlog" ); + ImplWriteLine( "%%BeginResource: procset SDRes-Prolog 1.0 0" ); + +// BEGIN EPSF + ImplWriteLine( "/b4_inc_state save def\n/dict_count countdictstack def\n/op_count count 1 sub def\nuserdict begin" ); + ImplWriteLine( "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit[] 0 setdash newpath" ); + ImplWriteLine( "/languagelevel where {pop languagelevel 1 ne {false setstrokeadjust false setoverprint} if} if" ); + + ImplWriteLine( "/bdef {bind def} bind def" ); // the new operator bdef is created + if ( mbGrayScale ) + ImplWriteLine( "/c {setgray} bdef" ); + else + ImplWriteLine( "/c {setrgbcolor} bdef" ); + ImplWriteLine( "/l {neg lineto} bdef" ); + ImplWriteLine( "/rl {neg rlineto} bdef" ); + ImplWriteLine( "/lc {setlinecap} bdef" ); + ImplWriteLine( "/lj {setlinejoin} bdef" ); + ImplWriteLine( "/lw {setlinewidth} bdef" ); + ImplWriteLine( "/ml {setmiterlimit} bdef" ); + ImplWriteLine( "/ld {setdash} bdef" ); + ImplWriteLine( "/m {neg moveto} bdef" ); + ImplWriteLine( "/ct {6 2 roll neg 6 2 roll neg 6 2 roll neg curveto} bdef" ); + ImplWriteLine( "/r {rotate} bdef" ); + ImplWriteLine( "/t {neg translate} bdef" ); + ImplWriteLine( "/s {scale} bdef" ); + ImplWriteLine( "/sw {show} bdef" ); + ImplWriteLine( "/gs {gsave} bdef" ); + ImplWriteLine( "/gr {grestore} bdef" ); + + ImplWriteLine( "/f {findfont dup length dict begin" ); // Setfont + ImplWriteLine( "{1 index /FID ne {def} {pop pop} ifelse} forall /Encoding ISOLatin1Encoding def" ); + ImplWriteLine( "currentdict end /NFont exch definefont pop /NFont findfont} bdef" ); + + ImplWriteLine( "/p {closepath} bdef" ); + ImplWriteLine( "/sf {scalefont setfont} bdef" ); + + ImplWriteLine( "/ef {eofill}bdef" ); // close path and fill + ImplWriteLine( "/pc {closepath stroke}bdef" ); // close path and draw + ImplWriteLine( "/ps {stroke}bdef" ); // draw current path + ImplWriteLine( "/pum {matrix currentmatrix}bdef" ); // pushes the current matrix + ImplWriteLine( "/pom {setmatrix}bdef" ); // pops the matrix + ImplWriteLine( "/bs {/aString exch def /nXOfs exch def /nWidth exch def currentpoint nXOfs 0 rmoveto pum nWidth aString stringwidth pop div 1 scale aString show pom moveto} bdef" ); + ImplWriteLine( "%%EndResource" ); + ImplWriteLine( "%%EndProlog" ); + ImplWriteLine( "%%BeginSetup" ); + ImplWriteLine( "%%EndSetup" ); + ImplWriteLine( "%%Page: 1 1" ); + ImplWriteLine( "%%BeginPageSetup" ); + ImplWriteLine( "%%EndPageSetup" ); + ImplWriteLine( "pum" ); + ImplScale( static_cast<double>(aSizePoint.Width()) / static_cast<double>(pMTF->GetPrefSize().Width()), static_cast<double>(aSizePoint.Height()) / static_cast<double>(pMTF->GetPrefSize().Height()) ); + ImplWriteDouble( 0 ); + ImplWriteDouble( -pMTF->GetPrefSize().Height() ); + ImplWriteLine( "t" ); + ImplWriteLine( "/tm matrix currentmatrix def" ); +} + +void PSWriter::ImplWriteEpilog() +{ + ImplTranslate( 0, nBoundingY2 ); + ImplWriteLine( "pom" ); + ImplWriteLine( "count op_count sub {pop} repeat countdictstack dict_count sub {end} repeat b4_inc_state restore" ); + + ImplWriteLine( "%%PageTrailer" ); + ImplWriteLine( "%%Trailer" ); + + ImplWriteLine( "%%EOF" ); +} + +void PSWriter::ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev ) +{ + tools::PolyPolygon aFillPath; + + for( size_t nCurAction = 0, nCount = rMtf.GetActionSize(); nCurAction < nCount; nCurAction++ ) + { + MetaAction* pMA = rMtf.GetAction( nCurAction ); + + switch( pMA->GetType() ) + { + case MetaActionType::NONE : + break; + + case MetaActionType::PIXEL : + { + Color aOldLineColor( aLineColor ); + aLineColor = static_cast<const MetaPixelAction*>(pMA)->GetColor(); + ImplWriteLineColor( PS_SPACE ); + ImplMoveTo( static_cast<const MetaPixelAction*>(pMA)->GetPoint() ); + ImplLineTo( static_cast<const MetaPixelAction*>(pMA)->GetPoint() ); + ImplPathDraw(); + aLineColor = aOldLineColor; + } + break; + + case MetaActionType::POINT : + { + ImplWriteLineColor( PS_SPACE ); + ImplMoveTo( static_cast<const MetaPointAction*>(pMA)->GetPoint() ); + ImplLineTo( static_cast<const MetaPointAction*>(pMA)->GetPoint() ); + ImplPathDraw(); + } + break; + + case MetaActionType::LINE : + { + const LineInfo& rLineInfo = static_cast<const MetaLineAction*>(pMA)->GetLineInfo(); + ImplWriteLineInfo( rLineInfo ); + if ( bLineColor ) + { + ImplWriteLineColor( PS_SPACE ); + ImplMoveTo( static_cast<const MetaLineAction*>(pMA)->GetStartPoint() ); + ImplLineTo( static_cast<const MetaLineAction*>(pMA )->GetEndPoint() ); + ImplPathDraw(); + } + } + break; + + case MetaActionType::RECT : + { + ImplRect( static_cast<const MetaRectAction*>(pMA)->GetRect() ); + } + break; + + case MetaActionType::ROUNDRECT : + ImplRect( static_cast<const MetaRoundRectAction*>(pMA)->GetRect() ); + break; + + case MetaActionType::ELLIPSE : + { + tools::Rectangle aRect = static_cast<const MetaEllipseAction*>(pMA)->GetRect(); + Point aCenter = aRect.Center(); + tools::Polygon aPoly( aCenter, aRect.GetWidth() / 2, aRect.GetHeight() / 2 ); + tools::PolyPolygon aPolyPoly( aPoly ); + ImplPolyPoly( aPolyPoly ); + } + break; + + case MetaActionType::ARC : + { + tools::Polygon aPoly( static_cast<const MetaArcAction*>(pMA)->GetRect(), static_cast<const MetaArcAction*>(pMA)->GetStartPoint(), + static_cast<const MetaArcAction*>(pMA)->GetEndPoint(), PolyStyle::Arc ); + tools::PolyPolygon aPolyPoly( aPoly ); + ImplPolyPoly( aPolyPoly ); + } + break; + + case MetaActionType::PIE : + { + tools::Polygon aPoly( static_cast<const MetaPieAction*>(pMA)->GetRect(), static_cast<const MetaPieAction*>(pMA)->GetStartPoint(), + static_cast<const MetaPieAction*>(pMA)->GetEndPoint(), PolyStyle::Pie ); + tools::PolyPolygon aPolyPoly( aPoly ); + ImplPolyPoly( aPolyPoly ); + } + break; + + case MetaActionType::CHORD : + { + tools::Polygon aPoly( static_cast<const MetaChordAction*>(pMA)->GetRect(), static_cast<const MetaChordAction*>(pMA)->GetStartPoint(), + static_cast<const MetaChordAction*>(pMA)->GetEndPoint(), PolyStyle::Chord ); + tools::PolyPolygon aPolyPoly( aPoly ); + ImplPolyPoly( aPolyPoly ); + } + break; + + case MetaActionType::POLYLINE : + { + tools::Polygon aPoly( static_cast<const MetaPolyLineAction*>(pMA)->GetPolygon() ); + const LineInfo& rLineInfo = static_cast<const MetaPolyLineAction*>(pMA)->GetLineInfo(); + ImplWriteLineInfo( rLineInfo ); + + if(basegfx::B2DLineJoin::NONE == rLineInfo.GetLineJoin() + && rLineInfo.GetWidth() > 1) + { + // emulate B2DLineJoin::NONE by creating single edges + const sal_uInt16 nPoints(aPoly.GetSize()); + const bool bCurve(aPoly.HasFlags()); + + for(sal_uInt16 a(0); a + 1 < nPoints; a++) + { + if(bCurve + && PolyFlags::Normal != aPoly.GetFlags(a + 1) + && a + 2 < nPoints + && PolyFlags::Normal != aPoly.GetFlags(a + 2) + && a + 3 < nPoints) + { + const tools::Polygon aSnippet(4, + aPoly.GetConstPointAry() + a, + aPoly.GetConstFlagAry() + a); + ImplPolyLine(aSnippet); + a += 2; + } + else + { + const tools::Polygon aSnippet(2, + aPoly.GetConstPointAry() + a); + ImplPolyLine(aSnippet); + } + } + } + else + { + ImplPolyLine( aPoly ); + } + } + break; + + case MetaActionType::POLYGON : + { + tools::PolyPolygon aPolyPoly( static_cast<const MetaPolygonAction*>(pMA)->GetPolygon() ); + ImplPolyPoly( aPolyPoly ); + } + break; + + case MetaActionType::POLYPOLYGON : + { + ImplPolyPoly( static_cast<const MetaPolyPolygonAction*>(pMA)->GetPolyPolygon() ); + } + break; + + case MetaActionType::TEXT: + { + const MetaTextAction * pA = static_cast<const MetaTextAction*>(pMA); + + OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() ); + Point aPoint( pA->GetPoint() ); + + ImplText( aUniStr, aPoint, {}, 0, rVDev ); + } + break; + + case MetaActionType::TEXTRECT: + { + OSL_FAIL( "Unsupported action: TextRect...Action!" ); + } + break; + + case MetaActionType::STRETCHTEXT : + { + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pMA); + OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() ); + Point aPoint( pA->GetPoint() ); + + ImplText( aUniStr, aPoint, {}, pA->GetWidth(), rVDev ); + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pMA); + OUString aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() ); + Point aPoint( pA->GetPoint() ); + + ImplText( aUniStr, aPoint, pA->GetDXArray(), 0, rVDev ); + } + break; + + case MetaActionType::BMP : + { + Bitmap aBitmap = static_cast<const MetaBmpAction*>(pMA)->GetBitmap(); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Point aPoint = static_cast<const MetaBmpAction*>(pMA)->GetPoint(); + Size aSize( rVDev.PixelToLogic( aBitmap.GetSizePixel() ) ); + ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + case MetaActionType::BMPSCALE : + { + Bitmap aBitmap = static_cast<const MetaBmpScaleAction*>(pMA)->GetBitmap(); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Point aPoint = static_cast<const MetaBmpScaleAction*>(pMA)->GetPoint(); + Size aSize = static_cast<const MetaBmpScaleAction*>(pMA)->GetSize(); + ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + case MetaActionType::BMPSCALEPART : + { + Bitmap aBitmap( static_cast<const MetaBmpScalePartAction*>(pMA)->GetBitmap() ); + aBitmap.Crop( tools::Rectangle( static_cast<const MetaBmpScalePartAction*>(pMA)->GetSrcPoint(), + static_cast<const MetaBmpScalePartAction*>(pMA)->GetSrcSize() ) ); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Point aPoint = static_cast<const MetaBmpScalePartAction*>(pMA)->GetDestPoint(); + Size aSize = static_cast<const MetaBmpScalePartAction*>(pMA)->GetDestSize(); + ImplBmp( &aBitmap, nullptr, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + case MetaActionType::BMPEX : + { + BitmapEx aBitmapEx( static_cast<MetaBmpExAction*>(pMA)->GetBitmapEx() ); + Bitmap aBitmap( aBitmapEx.GetBitmap() ); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Bitmap aMask( aBitmapEx.GetAlpha() ); + Point aPoint( static_cast<const MetaBmpExAction*>(pMA)->GetPoint() ); + Size aSize( rVDev.PixelToLogic( aBitmap.GetSizePixel() ) ); + ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + case MetaActionType::BMPEXSCALE : + { + BitmapEx aBitmapEx( static_cast<MetaBmpExScaleAction*>(pMA)->GetBitmapEx() ); + Bitmap aBitmap( aBitmapEx.GetBitmap() ); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Bitmap aMask( aBitmapEx.GetAlpha() ); + Point aPoint = static_cast<const MetaBmpExScaleAction*>(pMA)->GetPoint(); + Size aSize( static_cast<const MetaBmpExScaleAction*>(pMA)->GetSize() ); + ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + case MetaActionType::BMPEXSCALEPART : + { + BitmapEx aBitmapEx( static_cast<const MetaBmpExScalePartAction*>(pMA)->GetBitmapEx() ); + aBitmapEx.Crop( tools::Rectangle( static_cast<const MetaBmpExScalePartAction*>(pMA)->GetSrcPoint(), + static_cast<const MetaBmpExScalePartAction*>(pMA)->GetSrcSize() ) ); + Bitmap aBitmap( aBitmapEx.GetBitmap() ); + if ( mbGrayScale ) + aBitmap.Convert( BmpConversion::N8BitGreys ); + Bitmap aMask( aBitmapEx.GetAlpha() ); + Point aPoint = static_cast<const MetaBmpExScalePartAction*>(pMA)->GetDestPoint(); + Size aSize = static_cast<const MetaBmpExScalePartAction*>(pMA)->GetDestSize(); + ImplBmp( &aBitmap, &aMask, aPoint, aSize.Width(), aSize.Height() ); + } + break; + + // Unsupported Actions + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + { + OSL_FAIL( "Unsupported action: MetaMask...Action!" ); + } + break; + + case MetaActionType::GRADIENT : + { + tools::PolyPolygon aPolyPoly( static_cast<const MetaGradientAction*>(pMA)->GetRect() ); + ImplWriteGradient( aPolyPoly, static_cast<const MetaGradientAction*>(pMA)->GetGradient(), rVDev ); + } + break; + + case MetaActionType::GRADIENTEX : + { + tools::PolyPolygon aPolyPoly( static_cast<const MetaGradientExAction*>(pMA)->GetPolyPolygon() ); + ImplWriteGradient( aPolyPoly, static_cast<const MetaGradientExAction*>(pMA)->GetGradient(), rVDev ); + } + break; + + case MetaActionType::HATCH : + { + ScopedVclPtrInstance< VirtualDevice > l_pVirDev; + GDIMetaFile aTmpMtf; + + l_pVirDev->SetMapMode( rVDev.GetMapMode() ); + l_pVirDev->AddHatchActions( static_cast<const MetaHatchAction*>(pMA)->GetPolyPolygon(), + static_cast<const MetaHatchAction*>(pMA)->GetHatch(), aTmpMtf ); + ImplWriteActions( aTmpMtf, rVDev ); + } + break; + + case MetaActionType::WALLPAPER : + { + const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pMA); + tools::Rectangle aRect = pA->GetRect(); + const Wallpaper& aWallpaper = pA->GetWallpaper(); + + if ( aWallpaper.IsBitmap() ) + { + BitmapEx aBitmapEx = aWallpaper.GetBitmap(); + Bitmap aBitmap( aBitmapEx.GetBitmap() ); + if ( aBitmapEx.IsAlpha() ) + { + if ( aWallpaper.IsGradient() ) + { + + // gradient action + + } + Bitmap aMask( aBitmapEx.GetAlpha() ); + ImplBmp( &aBitmap, &aMask, Point( aRect.Left(), aRect.Top() ), aRect.GetWidth(), aRect.GetHeight() ); + } + else + ImplBmp( &aBitmap, nullptr, Point( aRect.Left(), aRect.Top() ), aRect.GetWidth(), aRect.GetHeight() ); + + // wallpaper Style + + } + else if ( aWallpaper.IsGradient() ) + { + + // gradient action + + } + else + { + aColor = aWallpaper.GetColor(); + ImplRectFill( aRect ); + } + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pMA); + vcl::Region aRegion( pA->GetRect() ); + ImplSetClipRegion( aRegion ); + } + break; + + case MetaActionType::CLIPREGION: + { + const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pMA); + const vcl::Region& aRegion( pA->GetRegion() ); + ImplSetClipRegion( aRegion ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pMA); + const vcl::Region& aRegion( pA->GetRegion() ); + ImplSetClipRegion( aRegion ); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + // TODO: Implement! + } + break; + + case MetaActionType::LINECOLOR : + { + if ( static_cast<const MetaLineColorAction*>(pMA)->IsSetting() ) + { + bLineColor = true; + aLineColor = static_cast<const MetaLineColorAction*>(pMA)->GetColor(); + } + else + bLineColor = false; + } + break; + + case MetaActionType::FILLCOLOR : + { + if ( static_cast<const MetaFillColorAction*>(pMA)->IsSetting() ) + { + bFillColor = true; + aFillColor = static_cast<const MetaFillColorAction*>(pMA)->GetColor(); + } + else + bFillColor = false; + } + break; + + case MetaActionType::TEXTCOLOR : + { + aTextColor = static_cast<const MetaTextColorAction*>(pMA)->GetColor(); + } + break; + + case MetaActionType::TEXTFILLCOLOR : + { + if ( static_cast<const MetaTextFillColorAction*>(pMA)->IsSetting() ) + { + bTextFillColor = true; + aTextFillColor = static_cast<const MetaTextFillColorAction*>(pMA)->GetColor(); + } + else + bTextFillColor = false; + } + break; + + case MetaActionType::TEXTALIGN : + { + eTextAlign = static_cast<const MetaTextAlignAction*>(pMA)->GetTextAlign(); + } + break; + + case MetaActionType::MAPMODE : + { + pMA->Execute( &rVDev ); + ImplGetMapMode( rVDev.GetMapMode() ); + } + break; + + case MetaActionType::FONT : + { + maFont = static_cast<const MetaFontAction*>(pMA)->GetFont(); + rVDev.SetFont( maFont ); + } + break; + + case MetaActionType::PUSH : + { + rVDev.Push(static_cast<const MetaPushAction*>(pMA)->GetFlags() ); + StackMember* pGS = new StackMember; + pGS->pSucc = pGDIStack; + pGDIStack = pGS; + pGS->aDashArray = aDashArray; + pGS->eJoinType = eJoinType; + pGS->eLineCap = eLineCap; + pGS->fLineWidth = fLineWidth; + pGS->fMiterLimit = fMiterLimit; + pGS->eTextAlign = eTextAlign; + pGS->aGlobalCol = aColor; + pGS->bLineCol = bLineColor; + pGS->aLineCol = aLineColor; + pGS->bFillCol = bFillColor; + pGS->aFillCol = aFillColor; + pGS->aTextCol = aTextColor; + pGS->bTextFillCol = bTextFillColor; + pGS->aTextFillCol = aTextFillColor; + pGS->aBackgroundCol = aBackgroundColor; + pGS->aFont = maFont; + mnLatestPush = mpPS->Tell(); + ImplWriteLine( "gs" ); + } + break; + + case MetaActionType::POP : + { + rVDev.Pop(); + if( pGDIStack ) + { + StackMember* pGS = pGDIStack; + pGDIStack = pGS->pSucc; + aDashArray = pGS->aDashArray; + eJoinType = pGS->eJoinType; + eLineCap = pGS->eLineCap; + fLineWidth = pGS->fLineWidth; + fMiterLimit = pGS->fMiterLimit; + eTextAlign = pGS->eTextAlign; + aColor = pGS->aGlobalCol; + bLineColor = pGS->bLineCol; + aLineColor = pGS->aLineCol; + bFillColor = pGS->bFillCol; + aFillColor = pGS->aFillCol; + aTextColor = pGS->aTextCol; + bTextFillColor = pGS->bTextFillCol; + aTextFillColor = pGS->aTextFillCol; + aBackgroundColor = pGS->aBackgroundCol; + maFont = pGS->aFont; + maLastFont = vcl::Font(); // set maLastFont != maFont -> so that + delete pGS; + sal_uInt32 nCurrentPos = mpPS->Tell(); + if ( nCurrentPos - 3 == mnLatestPush ) + { + mpPS->Seek( mnLatestPush ); + ImplWriteLine( " " ); + mpPS->Seek( mnLatestPush ); + } + else + ImplWriteLine( "gr" ); + } + } + break; + + case MetaActionType::EPS : + { + GfxLink aGfxLink = static_cast<const MetaEPSAction*>(pMA)->GetLink(); + const GDIMetaFile aSubstitute( static_cast<const MetaEPSAction*>(pMA)->GetSubstitute() ); + + bool bLevelConflict = false; + sal_uInt8* pSource = const_cast<sal_uInt8*>(aGfxLink.GetData()); + sal_uInt32 nSize = aGfxLink.GetDataSize(); + sal_uInt32 nParseThis = POSTSCRIPT_BOUNDINGSEARCH; + if ( nSize < 64 ) // assuming eps is larger than 64 bytes + pSource = nullptr; + if ( nParseThis > nSize ) + nParseThis = nSize; + + if ( pSource && ( mnLevel == 1 ) ) + { + sal_uInt8* pFound = ImplSearchEntry( pSource, reinterpret_cast<sal_uInt8 const *>("%%LanguageLevel:"), nParseThis - 10, 16 ); + if ( pFound ) + { + sal_uInt8 k, i = 10; + pFound += 16; + while ( --i ) + { + k = *pFound++; + if ( ( k > '0' ) && ( k <= '9' ) ) + { + if ( k != '1' ) + { + bLevelConflict = true; + mbLevelWarning = true; + } + break; + } + } + } + } + if ( !bLevelConflict ) + { + double nBoundingBox[4]; + if ( pSource && ImplGetBoundingBox( nBoundingBox, pSource, nParseThis ) ) + { + Point aPoint = static_cast<const MetaEPSAction*>(pMA)->GetPoint(); + Size aSize = static_cast<const MetaEPSAction*>(pMA)->GetSize(); + + MapMode aMapMode( aSubstitute.GetPrefMapMode() ); + Size aOutSize( OutputDevice::LogicToLogic( aSize, rVDev.GetMapMode(), aMapMode ) ); + Point aOrigin( OutputDevice::LogicToLogic( aPoint, rVDev.GetMapMode(), aMapMode ) ); + aOrigin.AdjustY(aOutSize.Height() ); + aMapMode.SetOrigin( aOrigin ); + aMapMode.SetScaleX( Fraction(aOutSize.Width() / ( nBoundingBox[ 2 ] - nBoundingBox[ 0 ] )) ); + aMapMode.SetScaleY( Fraction(aOutSize.Height() / ( nBoundingBox[ 3 ] - nBoundingBox[ 1 ] )) ); + ImplWriteLine( "gs" ); + ImplGetMapMode( aMapMode ); + ImplWriteLine( "%%BeginDocument:" ); + mpPS->WriteBytes(pSource, aGfxLink.GetDataSize()); + ImplWriteLine( "%%EndDocument\ngr" ); + } + } + } + break; + + case MetaActionType::Transparent: + { + // TODO: implement! + } + break; + + case MetaActionType::RASTEROP: + { + pMA->Execute( &rVDev ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pMA); + + GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); + Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() ); + const Size aSrcSize( aTmpMtf.GetPrefSize() ); + const Point aDestPt( pA->GetPoint() ); + const Size aDestSize( pA->GetSize() ); + const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0; + const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0; + tools::Long nMoveX, nMoveY; + + if( fScaleX != 1.0 || fScaleY != 1.0 ) + { + aTmpMtf.Scale( fScaleX, fScaleY ); + aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) ); + aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) ); + } + + nMoveX = aDestPt.X() - aSrcPt.X(); + nMoveY = aDestPt.Y() - aSrcPt.Y(); + + if( nMoveX || nMoveY ) + aTmpMtf.Move( nMoveX, nMoveY ); + + ImplWriteActions( aTmpMtf, rVDev ); + } + break; + + case MetaActionType::COMMENT: + { + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pMA); + if ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) + { + const MetaGradientExAction* pGradAction = nullptr; + while( ++nCurAction < nCount ) + { + MetaAction* pAction = rMtf.GetAction( nCurAction ); + if( pAction->GetType() == MetaActionType::GRADIENTEX ) + pGradAction = static_cast<const MetaGradientExAction*>(pAction); + else if( ( pAction->GetType() == MetaActionType::COMMENT ) && + ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) ) + { + break; + } + } + + if( pGradAction ) + ImplWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), rVDev ); + } + else if ( pA->GetComment() == "XPATHFILL_SEQ_END" ) + { + if ( aFillPath.Count() ) + { + aFillPath = tools::PolyPolygon(); + ImplWriteLine( "gr" ); + } + } + else + { + const sal_uInt8* pData = pA->GetData(); + if ( pData ) + { + SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ ); + bool bSkipSequence = false; + OString sSeqEnd; + + if( pA->GetComment() == "XPATHSTROKE_SEQ_BEGIN" ) + { + sSeqEnd = "XPATHSTROKE_SEQ_END"; + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + + tools::Polygon aPath; + aStroke.getPath( aPath ); + + tools::PolyPolygon aStartArrow; + tools::PolyPolygon aEndArrow; + double fStrokeWidth( aStroke.getStrokeWidth() ); + SvtGraphicStroke::JoinType eJT( aStroke.getJoinType() ); + SvtGraphicStroke::DashArray l_aDashArray; + + aStroke.getStartArrow( aStartArrow ); + aStroke.getEndArrow( aEndArrow ); + aStroke.getDashArray( l_aDashArray ); + + bSkipSequence = true; + if ( l_aDashArray.size() > 11 ) // ps dasharray limit is 11 + bSkipSequence = false; + if ( aStartArrow.Count() || aEndArrow.Count() ) + bSkipSequence = false; + if ( static_cast<sal_uInt32>(eJT) > 2 ) + bSkipSequence = false; + if ( !l_aDashArray.empty() && ( fStrokeWidth != 0.0 ) ) + bSkipSequence = false; + if ( bSkipSequence ) + { + ImplWriteLineInfo( fStrokeWidth, aStroke.getMiterLimit(), + aStroke.getCapType(), eJT, std::move(l_aDashArray) ); + ImplPolyLine( aPath ); + } + } + else if (pA->GetComment() == "XPATHFILL_SEQ_BEGIN") + { + sSeqEnd = "XPATHFILL_SEQ_END"; + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + switch( aFill.getFillType() ) + { + case SvtGraphicFill::fillSolid : + { + bSkipSequence = true; + tools::PolyPolygon aPolyPoly; + aFill.getPath( aPolyPoly ); + sal_uInt16 i, nPolyCount = aPolyPoly.Count(); + if ( nPolyCount ) + { + aFillColor = aFill.getFillColor(); + ImplWriteFillColor( PS_SPACE ); + for ( i = 0; i < nPolyCount; ) + { + ImplAddPath( aPolyPoly.GetObject( i ) ); + if ( ++i < nPolyCount ) + { + mpPS->WriteCharPtr( "p" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); + } + } + mpPS->WriteCharPtr( "p ef" ); + mnCursorPos += 4; + ImplExecMode( PS_RET ); + } + } + break; + + case SvtGraphicFill::fillTexture : + { + aFill.getPath( aFillPath ); + + /* normally an object filling is consisting of three MetaActions: + MetaBitmapAction using RasterOp xor, + MetaPolyPolygonAction using RasterOp rop_0 + MetaBitmapAction using RasterOp xor + + Because RasterOps cannot been used in Postscript, we have to + replace these actions. The MetaComment "XPATHFILL_SEQ_BEGIN" is + providing the clippath of the object. The following loop is + trying to find the bitmap that is matching the clippath, so that + only one bitmap is exported, otherwise if the bitmap is not + locatable, all metaactions are played normally. + */ + sal_uInt32 nCommentStartAction = nCurAction; + sal_uInt32 nBitmapCount = 0; + sal_uInt32 nBitmapAction = 0; + + bool bOk = true; + while( bOk && ( ++nCurAction < nCount ) ) + { + MetaAction* pAction = rMtf.GetAction( nCurAction ); + switch( pAction->GetType() ) + { + case MetaActionType::BMPSCALE : + case MetaActionType::BMPSCALEPART : + case MetaActionType::BMPEXSCALE : + case MetaActionType::BMPEXSCALEPART : + { + nBitmapCount++; + nBitmapAction = nCurAction; + } + break; + case MetaActionType::COMMENT : + { + if (static_cast<const MetaCommentAction*>(pAction)->GetComment() == "XPATHFILL_SEQ_END") + bOk = false; + } + break; + default: break; + } + } + if( nBitmapCount == 2 ) + { + ImplWriteLine( "gs" ); + ImplIntersect( aFillPath ); + GDIMetaFile aTempMtf; + aTempMtf.AddAction( rMtf.GetAction( nBitmapAction )->Clone() ); + ImplWriteActions( aTempMtf, rVDev ); + ImplWriteLine( "gr" ); + aFillPath = tools::PolyPolygon(); + } + else + nCurAction = nCommentStartAction + 1; + } + break; + + case SvtGraphicFill::fillGradient : + aFill.getPath( aFillPath ); + break; + + case SvtGraphicFill::fillHatch : + break; + } + if ( aFillPath.Count() ) + { + ImplWriteLine( "gs" ); + ImplIntersect( aFillPath ); + } + } + if ( bSkipSequence ) + { + while( ++nCurAction < nCount ) + { + pMA = rMtf.GetAction( nCurAction ); + if ( pMA->GetType() == MetaActionType::COMMENT ) + { + OString sComment( static_cast<MetaCommentAction*>(pMA)->GetComment() ); + if ( sComment == sSeqEnd ) + break; + } + } + } + } + } + } + break; + default: break; + } + } +} + +inline void PSWriter::ImplWritePoint( const Point& rPoint ) +{ + ImplWriteDouble( rPoint.X() ); + ImplWriteDouble( rPoint.Y() ); +} + +void PSWriter::ImplMoveTo( const Point& rPoint ) +{ + ImplWritePoint( rPoint ); + ImplWriteByte( 'm' ); + ImplExecMode( PS_SPACE ); +} + +void PSWriter::ImplLineTo( const Point& rPoint, NMode nMode ) +{ + ImplWritePoint( rPoint ); + ImplWriteByte( 'l' ); + ImplExecMode( nMode ); +} + +void PSWriter::ImplCurveTo( const Point& rP1, const Point& rP2, const Point& rP3, NMode nMode ) +{ + ImplWritePoint( rP1 ); + ImplWritePoint( rP2 ); + ImplWritePoint( rP3 ); + mpPS->WriteCharPtr( "ct " ); + ImplExecMode( nMode ); +} + +void PSWriter::ImplTranslate( const double& fX, const double& fY ) +{ + ImplWriteDouble( fX ); + ImplWriteDouble( fY ); + ImplWriteByte( 't' ); + ImplExecMode( PS_RET ); +} + +void PSWriter::ImplScale( const double& fX, const double& fY ) +{ + ImplWriteDouble( fX ); + ImplWriteDouble( fY ); + ImplWriteByte( 's' ); + ImplExecMode( PS_RET ); +} + +void PSWriter::ImplRect( const tools::Rectangle & rRect ) +{ + if ( bFillColor ) + ImplRectFill( rRect ); + if ( bLineColor ) + { + double nWidth = rRect.GetWidth(); + double nHeight = rRect.GetHeight(); + + ImplWriteLineColor( PS_SPACE ); + ImplMoveTo( rRect.TopLeft() ); + ImplWriteDouble( nWidth ); + mpPS->WriteCharPtr( "0 rl 0 " ); + ImplWriteDouble( nHeight ); + mpPS->WriteCharPtr( "rl " ); + ImplWriteDouble( nWidth ); + mpPS->WriteCharPtr( "neg 0 rl " ); + ImplClosePathDraw(); + } + mpPS->WriteUChar( 10 ); + mnCursorPos = 0; +} + +void PSWriter::ImplRectFill( const tools::Rectangle & rRect ) +{ + double nWidth = rRect.GetWidth(); + double nHeight = rRect.GetHeight(); + + ImplWriteFillColor( PS_SPACE ); + ImplMoveTo( rRect.TopLeft() ); + ImplWriteDouble( nWidth ); + mpPS->WriteCharPtr( "0 rl 0 " ); + ImplWriteDouble( nHeight ); + mpPS->WriteCharPtr( "rl " ); + ImplWriteDouble( nWidth ); + mpPS->WriteCharPtr( "neg 0 rl ef " ); + mpPS->WriteCharPtr( "p ef" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); +} + +void PSWriter::ImplAddPath( const tools::Polygon & rPolygon ) +{ + sal_uInt16 nPointCount = rPolygon.GetSize(); + if ( nPointCount <= 1 ) + return; + + sal_uInt16 i = 1; + ImplMoveTo( rPolygon.GetPoint( 0 ) ); + while ( i < nPointCount ) + { + if ( ( rPolygon.GetFlags( i ) == PolyFlags::Control ) + && ( ( i + 2 ) < nPointCount ) + && ( rPolygon.GetFlags( i + 1 ) == PolyFlags::Control ) + && ( rPolygon.GetFlags( i + 2 ) != PolyFlags::Control ) ) + { + ImplCurveTo( rPolygon[ i ], rPolygon[ i + 1 ], rPolygon[ i + 2 ], PS_WRAP ); + i += 3; + } + else + ImplLineTo( rPolygon.GetPoint( i++ ), PS_SPACE | PS_WRAP ); + } +} + +void PSWriter::ImplIntersect( const tools::PolyPolygon& rPolyPoly ) +{ + sal_uInt16 i, nPolyCount = rPolyPoly.Count(); + for ( i = 0; i < nPolyCount; ) + { + ImplAddPath( rPolyPoly.GetObject( i ) ); + if ( ++i < nPolyCount ) + { + mpPS->WriteCharPtr( "p" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); + } + } + ImplWriteLine( "eoclip newpath" ); +} + +void PSWriter::ImplWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, VirtualDevice& rVDev ) +{ + ScopedVclPtrInstance< VirtualDevice > l_pVDev; + GDIMetaFile aTmpMtf; + l_pVDev->SetMapMode( rVDev.GetMapMode() ); + Gradient aGradient(rGradient); + aGradient.AddGradientActions( rPolyPoly.GetBoundRect(), aTmpMtf ); + ImplWriteActions( aTmpMtf, rVDev ); +} + +void PSWriter::ImplPolyPoly( const tools::PolyPolygon & rPolyPoly, bool bTextOutline ) +{ + sal_uInt16 i, nPolyCount = rPolyPoly.Count(); + if ( !nPolyCount ) + return; + + if ( bFillColor || bTextOutline ) + { + if ( bTextOutline ) + ImplWriteTextColor( PS_SPACE ); + else + ImplWriteFillColor( PS_SPACE ); + for ( i = 0; i < nPolyCount; ) + { + ImplAddPath( rPolyPoly.GetObject( i ) ); + if ( ++i < nPolyCount ) + { + mpPS->WriteCharPtr( "p" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); + } + } + mpPS->WriteCharPtr( "p ef" ); + mnCursorPos += 4; + ImplExecMode( PS_RET ); + } + if ( bLineColor ) + { + ImplWriteLineColor( PS_SPACE ); + for ( i = 0; i < nPolyCount; i++ ) + ImplAddPath( rPolyPoly.GetObject( i ) ); + ImplClosePathDraw(); + } +} + +void PSWriter::ImplPolyLine( const tools::Polygon & rPoly ) +{ + if ( !bLineColor ) + return; + + ImplWriteLineColor( PS_SPACE ); + sal_uInt16 i, nPointCount = rPoly.GetSize(); + if ( !nPointCount ) + return; + + if ( nPointCount > 1 ) + { + ImplMoveTo( rPoly.GetPoint( 0 ) ); + i = 1; + while ( i < nPointCount ) + { + if ( ( rPoly.GetFlags( i ) == PolyFlags::Control ) + && ( ( i + 2 ) < nPointCount ) + && ( rPoly.GetFlags( i + 1 ) == PolyFlags::Control ) + && ( rPoly.GetFlags( i + 2 ) != PolyFlags::Control ) ) + { + ImplCurveTo( rPoly[ i ], rPoly[ i + 1 ], rPoly[ i + 2 ], PS_WRAP ); + i += 3; + } + else + ImplLineTo( rPoly.GetPoint( i++ ), PS_SPACE | PS_WRAP ); + } + } + + // #104645# explicitly close path if polygon is closed + if( rPoly[ 0 ] == rPoly[ nPointCount-1 ] ) + ImplClosePathDraw(); + else + ImplPathDraw(); +} + +void PSWriter::ImplSetClipRegion( vcl::Region const & rClipRegion ) +{ + if ( rClipRegion.IsEmpty() ) + return; + + RectangleVector aRectangles; + rClipRegion.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + double nX1(rectangle.Left()); + double nY1(rectangle.Top()); + double nX2(rectangle.Right()); + double nY2(rectangle.Bottom()); + + ImplWriteDouble( nX1 ); + ImplWriteDouble( nY1 ); + ImplWriteByte( 'm' ); + ImplWriteDouble( nX2 ); + ImplWriteDouble( nY1 ); + ImplWriteByte( 'l' ); + ImplWriteDouble( nX2 ); + ImplWriteDouble( nY2 ); + ImplWriteByte( 'l' ); + ImplWriteDouble( nX1 ); + ImplWriteDouble( nY2 ); + ImplWriteByte( 'l' ); + ImplWriteDouble( nX1 ); + ImplWriteDouble( nY1 ); + ImplWriteByte( 'l', PS_SPACE | PS_WRAP ); + } + + ImplWriteLine( "eoclip newpath" ); +} + +// possible gfx formats: +// +// level 1: grayscale 8 bit +// color 24 bit +// +// level 2: grayscale 8 bit +// color 1(pal), 4(pal), 8(pal), 24 Bit +// + +void PSWriter::ImplBmp( Bitmap const * pBitmap, Bitmap const * pMaskBitmap, const Point & rPoint, double nXWidth, double nYHeightOrg ) +{ + if ( !pBitmap ) + return; + + sal_Int32 nHeightOrg = pBitmap->GetSizePixel().Height(); + sal_Int32 nHeightLeft = nHeightOrg; + tools::Long nWidth = pBitmap->GetSizePixel().Width(); + Point aSourcePos( rPoint ); + + while ( nHeightLeft ) + { + Bitmap aTileBitmap( *pBitmap ); + tools::Long nHeight = nHeightLeft; + double nYHeight = nYHeightOrg; + + bool bDoTrans = false; + + tools::Rectangle aRect; + vcl::Region aRegion; + + if ( pMaskBitmap ) + { + bDoTrans = true; + while (true) + { + if ( mnLevel == 1 && nHeight > 10 ) + nHeight = 8; + aRect = tools::Rectangle( Point( 0, nHeightOrg - nHeightLeft ), Size( nWidth, nHeight ) ); + aRegion = pMaskBitmap->CreateRegion( COL_BLACK, aRect ); + + if( mnLevel == 1 ) + { + RectangleVector aRectangleVector; + aRegion.GetRegionRectangles(aRectangleVector); + + if ( aRectangleVector.size() * 5 > 1000 ) + { + nHeight >>= 1; + if ( nHeight < 2 ) + return; + continue; + } + } + break; + } + } + if ( nHeight != nHeightOrg ) + { + nYHeight = nYHeightOrg * nHeight / nHeightOrg; + aTileBitmap.Crop( tools::Rectangle( Point( 0, nHeightOrg - nHeightLeft ), Size( nWidth, nHeight ) ) ); + } + if ( bDoTrans ) + { + ImplWriteLine( "gs\npum" ); + ImplTranslate( aSourcePos.X(), aSourcePos.Y() ); + ImplScale( nXWidth / nWidth, nYHeight / nHeight ); + + RectangleVector aRectangles; + aRegion.GetRegionRectangles(aRectangles); + const tools::Long nMoveVertical(nHeightLeft - nHeightOrg); + + for (auto & rectangle : aRectangles) + { + rectangle.Move(0, nMoveVertical); + + ImplWriteLong( rectangle.Left() ); + ImplWriteLong( rectangle.Top() ); + ImplWriteByte( 'm' ); + ImplWriteLong( rectangle.Right() + 1 ); + ImplWriteLong( rectangle.Top() ); + ImplWriteByte( 'l' ); + ImplWriteLong( rectangle.Right() + 1 ); + ImplWriteLong( rectangle.Bottom() + 1 ); + ImplWriteByte( 'l' ); + ImplWriteLong( rectangle.Left() ); + ImplWriteLong( rectangle.Bottom() + 1 ); + ImplWriteByte( 'l' ); + ImplWriteByte( 'p', PS_SPACE | PS_WRAP ); + } + + ImplWriteLine( "eoclip newpath" ); + ImplWriteLine( "pom" ); + } + BitmapReadAccess* pAcc = aTileBitmap.AcquireReadAccess(); + + if (!bDoTrans ) + ImplWriteLine( "pum" ); + + ImplTranslate( aSourcePos.X(), aSourcePos.Y() + nYHeight ); + ImplScale( nXWidth, nYHeight ); + if ( mnLevel == 1 ) // level 1 is always grayscale !!! + { + ImplWriteLong( nWidth ); + ImplWriteLong( nHeight ); + mpPS->WriteCharPtr( "8 [" ); + ImplWriteLong( nWidth ); + mpPS->WriteCharPtr( "0 0 " ); + ImplWriteLong( -nHeight ); + ImplWriteLong( 0 ); + ImplWriteLong( nHeight ); + ImplWriteLine( "]" ); + mpPS->WriteCharPtr( "{currentfile " ); + ImplWriteLong( nWidth ); + ImplWriteLine( "string readhexstring pop}" ); + ImplWriteLine( "image" ); + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) ); + } + } + mpPS->WriteUChar( 10 ); + } + else // Level 2 + { + if ( mbGrayScale ) + { + ImplWriteLine( "/DeviceGray setcolorspace" ); + ImplWriteLine( "<<" ); + ImplWriteLine( "/ImageType 1" ); + mpPS->WriteCharPtr( "/Width " ); + ImplWriteLong( nWidth, PS_RET ); + mpPS->WriteCharPtr( "/Height " ); + ImplWriteLong( nHeight, PS_RET ); + ImplWriteLine( "/BitsPerComponent 8" ); + ImplWriteLine( "/Decode[0 1]" ); + mpPS->WriteCharPtr( "/ImageMatrix[" ); + ImplWriteLong( nWidth ); + mpPS->WriteCharPtr( "0 0 " ); + ImplWriteLong( -nHeight ); + ImplWriteLong( 0 ); + ImplWriteLong( nHeight, PS_NONE ); + ImplWriteByte( ']', PS_RET ); + ImplWriteLine( "/DataSource currentfile" ); + ImplWriteLine( "/ASCIIHexDecode filter" ); + if ( mbCompression ) + ImplWriteLine( "/LZWDecode filter" ); + ImplWriteLine( ">>" ); + ImplWriteLine( "image" ); + if ( mbCompression ) + { + StartCompression(); + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + Compress( pAcc->GetIndexFromData( pScanlineRead, x ) ); + } + } + EndCompression(); + } + else + { + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) ); + } + } + } + } + else + { + // have we to write a palette ? + + if ( pAcc->HasPalette() ) + { + ImplWriteLine( "[/Indexed /DeviceRGB " ); + ImplWriteLong( pAcc->GetPaletteEntryCount() - 1, PS_RET ); + ImplWriteByte( '<', PS_NONE ); + for ( sal_uInt16 i = 0; i < pAcc->GetPaletteEntryCount(); i++ ) + { + BitmapColor aBitmapColor = pAcc->GetPaletteColor( i ); + ImplWriteHexByte( aBitmapColor.GetRed(), PS_NONE ); + ImplWriteHexByte( aBitmapColor.GetGreen(), PS_NONE ); + ImplWriteHexByte( aBitmapColor.GetBlue(), PS_SPACE | PS_WRAP ); + } + ImplWriteByte( '>', PS_RET ); + + ImplWriteLine( "] setcolorspace" ); + ImplWriteLine( "<<" ); + ImplWriteLine( "/ImageType 1" ); + mpPS->WriteCharPtr( "/Width " ); + ImplWriteLong( nWidth, PS_RET ); + mpPS->WriteCharPtr( "/Height " ); + ImplWriteLong( nHeight, PS_RET ); + ImplWriteLine( "/BitsPerComponent 8" ); + ImplWriteLine( "/Decode[0 255]" ); + mpPS->WriteCharPtr( "/ImageMatrix[" ); + ImplWriteLong( nWidth ); + mpPS->WriteCharPtr( "0 0 " ); + ImplWriteLong( -nHeight ); + ImplWriteLong( 0); + ImplWriteLong( nHeight, PS_NONE ); + ImplWriteByte( ']', PS_RET ); + ImplWriteLine( "/DataSource currentfile" ); + ImplWriteLine( "/ASCIIHexDecode filter" ); + if ( mbCompression ) + ImplWriteLine( "/LZWDecode filter" ); + ImplWriteLine( ">>" ); + ImplWriteLine( "image" ); + if ( mbCompression ) + { + StartCompression(); + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + Compress( pAcc->GetIndexFromData( pScanlineRead, x ) ); + } + } + EndCompression(); + } + else + { + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + ImplWriteHexByte( pAcc->GetIndexFromData( pScanlineRead, x ) ); + } + } + } + } + else // 24 bit color + { + ImplWriteLine( "/DeviceRGB setcolorspace" ); + ImplWriteLine( "<<" ); + ImplWriteLine( "/ImageType 1" ); + mpPS->WriteCharPtr( "/Width " ); + ImplWriteLong( nWidth, PS_RET ); + mpPS->WriteCharPtr( "/Height " ); + ImplWriteLong( nHeight, PS_RET ); + ImplWriteLine( "/BitsPerComponent 8" ); + ImplWriteLine( "/Decode[0 1 0 1 0 1]" ); + mpPS->WriteCharPtr( "/ImageMatrix[" ); + ImplWriteLong( nWidth ); + mpPS->WriteCharPtr( "0 0 " ); + ImplWriteLong( -nHeight ); + ImplWriteLong( 0 ); + ImplWriteLong( nHeight, PS_NONE ); + ImplWriteByte( ']', PS_RET ); + ImplWriteLine( "/DataSource currentfile" ); + ImplWriteLine( "/ASCIIHexDecode filter" ); + if ( mbCompression ) + ImplWriteLine( "/LZWDecode filter" ); + ImplWriteLine( ">>" ); + ImplWriteLine( "image" ); + if ( mbCompression ) + { + StartCompression(); + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanlineRead = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + const BitmapColor aBitmapColor( pAcc->GetPixelFromData( pScanlineRead, x ) ); + Compress( aBitmapColor.GetRed() ); + Compress( aBitmapColor.GetGreen() ); + Compress( aBitmapColor.GetBlue() ); + } + } + EndCompression(); + } + else + { + for ( tools::Long y = 0; y < nHeight; y++ ) + { + Scanline pScanline = pAcc->GetScanline( y ); + for ( tools::Long x = 0; x < nWidth; x++ ) + { + const BitmapColor aBitmapColor( pAcc->GetPixelFromData( pScanline, x ) ); + ImplWriteHexByte( aBitmapColor.GetRed() ); + ImplWriteHexByte( aBitmapColor.GetGreen() ); + ImplWriteHexByte( aBitmapColor.GetBlue() ); + } + } + } + } + } + ImplWriteLine( ">" ); // in Level 2 the dictionary needs to be closed (eod) + } + if ( bDoTrans ) + ImplWriteLine( "gr" ); + else + ImplWriteLine( "pom" ); + + Bitmap::ReleaseAccess( pAcc ); + nHeightLeft -= nHeight; + if ( nHeightLeft ) + { + nHeightLeft++; + aSourcePos.setY( static_cast<tools::Long>( rPoint.Y() + ( nYHeightOrg * ( nHeightOrg - nHeightLeft ) ) / nHeightOrg ) ); + } + } +} + +void PSWriter::ImplWriteCharacter( char nChar ) +{ + switch( nChar ) + { + case '(' : + case ')' : + case '\\' : + ImplWriteByte( sal_uInt8('\\'), PS_NONE ); + } + ImplWriteByte( static_cast<sal_uInt8>(nChar), PS_NONE ); +} + +void PSWriter::ImplWriteString( const OString& rString, VirtualDevice const & rVDev, o3tl::span<const sal_Int32> pDXArry, bool bStretch ) +{ + sal_Int32 nLen = rString.getLength(); + if ( !nLen ) + return; + + if ( !pDXArry.empty() ) + { + double nx = 0; + + for (sal_Int32 i = 0; i < nLen; ++i) + { + if ( i > 0 ) + nx = pDXArry[ i - 1 ]; + ImplWriteDouble( bStretch ? nx : rVDev.GetTextWidth( OUString(rString[i]) ) ); + ImplWriteDouble( nx ); + ImplWriteLine( "(", PS_NONE ); + ImplWriteCharacter( rString[i] ); + ImplWriteLine( ") bs" ); + } + } + else + { + ImplWriteByte( '(', PS_NONE ); + for (sal_Int32 i = 0; i < nLen; ++i) + ImplWriteCharacter( rString[i] ); + ImplWriteLine( ") sw" ); + } +} + +void PSWriter::ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev ) +{ + if ( rUniString.isEmpty() ) + return; + if ( mnTextMode == 0 ) // using glyph outlines + { + vcl::Font aNotRotatedFont( maFont ); + aNotRotatedFont.SetOrientation( 0_deg10 ); + + ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::DEFAULT); + pVirDev->SetMapMode( rVDev.GetMapMode() ); + pVirDev->SetFont( aNotRotatedFont ); + pVirDev->SetTextAlign( eTextAlign ); + + Degree10 nRotation = maFont.GetOrientation(); + tools::Polygon aPolyDummy( 1 ); + + Point aPos( rPos ); + if ( nRotation ) + { + aPolyDummy.SetPoint( aPos, 0 ); + aPolyDummy.Rotate( rPos, nRotation ); + aPos = aPolyDummy.GetPoint( 0 ); + } + bool bOldLineColor = bLineColor; + bLineColor = false; + std::vector<tools::PolyPolygon> aPolyPolyVec; + if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniString, 0, 0, -1, nWidth, pDXArry ) ) + { + // always adjust text position to match baseline alignment + ImplWriteLine( "pum" ); + ImplWriteDouble( aPos.X() ); + ImplWriteDouble( aPos.Y() ); + ImplWriteLine( "t" ); + if ( nRotation ) + { + ImplWriteF( nRotation.get(), 1 ); + mpPS->WriteCharPtr( "r " ); + } + for (auto const& elem : aPolyPolyVec) + ImplPolyPoly( elem, true ); + ImplWriteLine( "pom" ); + } + bLineColor = bOldLineColor; + } + else if ( ( mnTextMode == 1 ) || ( mnTextMode == 2 ) ) // normal text output + { + if ( mnTextMode == 2 ) // forcing output one complete text packet, by + pDXArry = {}; // ignoring the kerning array + ImplSetAttrForText( rPos ); + OString aStr(OUStringToOString(rUniString, + maFont.GetCharSet())); + ImplWriteString( aStr, rVDev, pDXArry, nWidth != 0 ); + if ( maFont.GetOrientation() ) + ImplWriteLine( "gr" ); + } +} + +void PSWriter::ImplSetAttrForText( const Point& rPoint ) +{ + Point aPoint( rPoint ); + + Degree10 nRotation = maFont.GetOrientation(); + ImplWriteTextColor(PS_RET); + + Size aSize = maFont.GetFontSize(); + + if ( maLastFont != maFont ) + { + if ( maFont.GetPitch() == PITCH_FIXED ) // a little bit font selection + ImplDefineFont( "Courier", "Oblique" ); + else if ( maFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL ) + ImplWriteLine( "/Symbol findfont" ); + else if ( maFont.GetFamilyType() == FAMILY_SWISS ) + ImplDefineFont( "Helvetica", "Oblique" ); + else + ImplDefineFont( "Times", "Italic" ); + + maLastFont = maFont; + aSize = maFont.GetFontSize(); + ImplWriteDouble( aSize.Height() ); + mpPS->WriteCharPtr( "sf " ); + } + if ( eTextAlign != ALIGN_BASELINE ) + { // PostScript does not know about FontAlignment + if ( eTextAlign == ALIGN_TOP ) // -> so I assume that + aPoint.AdjustY( aSize.Height() * 4 / 5 ); // the area under the baseline + else if ( eTextAlign == ALIGN_BOTTOM ) // is about 20% of the font size + aPoint.AdjustY( -( aSize.Height() / 5 ) ); + } + ImplMoveTo( aPoint ); + if ( nRotation ) + { + mpPS->WriteCharPtr( "gs " ); + ImplWriteF( nRotation.get(), 1 ); + mpPS->WriteCharPtr( "r " ); + } +} + +void PSWriter::ImplDefineFont( const char* pOriginalName, const char* pItalic ) +{ + mpPS->WriteUChar( '/' ); //convert the font pOriginalName using ISOLatin1Encoding + mpPS->WriteCharPtr( pOriginalName ); + switch ( maFont.GetWeight() ) + { + case WEIGHT_SEMIBOLD : + case WEIGHT_BOLD : + case WEIGHT_ULTRABOLD : + case WEIGHT_BLACK : + mpPS->WriteCharPtr( "-Bold" ); + if ( maFont.GetItalic() != ITALIC_NONE ) + mpPS->WriteCharPtr( pItalic ); + break; + default: + if ( maFont.GetItalic() != ITALIC_NONE ) + mpPS->WriteCharPtr( pItalic ); + break; + } + ImplWriteLine( " f" ); +} + +void PSWriter::ImplClosePathDraw() +{ + mpPS->WriteCharPtr( "pc" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); +} + +void PSWriter::ImplPathDraw() +{ + mpPS->WriteCharPtr( "ps" ); + mnCursorPos += 2; + ImplExecMode( PS_RET ); +} + + +inline void PSWriter::ImplWriteLineColor( NMode nMode ) +{ + if ( aColor != aLineColor ) + { + aColor = aLineColor; + ImplWriteColor( nMode ); + } +} + +inline void PSWriter::ImplWriteFillColor( NMode nMode ) +{ + if ( aColor != aFillColor ) + { + aColor = aFillColor; + ImplWriteColor( nMode ); + } +} + +inline void PSWriter::ImplWriteTextColor( NMode nMode ) +{ + if ( aColor != aTextColor ) + { + aColor = aTextColor; + ImplWriteColor( nMode ); + } +} + +void PSWriter::ImplWriteColor( NMode nMode ) +{ + if ( mbGrayScale ) + { + // writes the Color (grayscale) as a Number from 0.000 up to 1.000 + + ImplWriteF( 1000 * ( aColor.GetRed() * 77 + aColor.GetGreen() * 151 + + aColor.GetBlue() * 28 + 1 ) / 65536, 3, nMode ); + } + else + { + ImplWriteB1 ( aColor.GetRed() ); + ImplWriteB1 ( aColor.GetGreen() ); + ImplWriteB1 ( aColor.GetBlue() ); + } + mpPS->WriteCharPtr( "c" ); // ( c is defined as setrgbcolor or setgray ) + ImplExecMode( nMode ); +} + +void PSWriter::ImplGetMapMode( const MapMode& rMapMode ) +{ + ImplWriteLine( "tm setmatrix" ); + double fMul = ImplGetScaling(rMapMode); + double fScaleX = static_cast<double>(rMapMode.GetScaleX()) * fMul; + double fScaleY = static_cast<double>(rMapMode.GetScaleY()) * fMul; + ImplTranslate( rMapMode.GetOrigin().X() * fScaleX, rMapMode.GetOrigin().Y() * fScaleY ); + ImplScale( fScaleX, fScaleY ); +} + +inline void PSWriter::ImplExecMode( NMode nMode ) +{ + if ( nMode & PS_WRAP ) + { + if ( mnCursorPos >= PS_LINESIZE ) + { + mnCursorPos = 0; + mpPS->WriteUChar( 0xa ); + return; + } + } + if ( nMode & PS_SPACE ) + { + mpPS->WriteUChar( 32 ); + mnCursorPos++; + } + if ( nMode & PS_RET ) + { + mpPS->WriteUChar( 0xa ); + mnCursorPos = 0; + } +} + +inline void PSWriter::ImplWriteLine( const char* pString, NMode nMode ) +{ + sal_uInt32 i = 0; + while ( pString[ i ] ) + { + mpPS->WriteUChar( pString[ i++ ] ); + } + mnCursorPos += i; + ImplExecMode( nMode ); +} + +double PSWriter::ImplGetScaling( const MapMode& rMapMode ) +{ + double nMul; + switch (rMapMode.GetMapUnit()) + { + case MapUnit::MapPixel : + case MapUnit::MapSysFont : + case MapUnit::MapAppFont : + + case MapUnit::Map100thMM : + nMul = 1; + break; + case MapUnit::Map10thMM : + nMul = 10; + break; + case MapUnit::MapMM : + nMul = 100; + break; + case MapUnit::MapCM : + nMul = 1000; + break; + case MapUnit::Map1000thInch : + nMul = 2.54; + break; + case MapUnit::Map100thInch : + nMul = 25.4; + break; + case MapUnit::Map10thInch : + nMul = 254; + break; + case MapUnit::MapInch : + nMul = 2540; + break; + case MapUnit::MapTwip : + nMul = 1.76388889; + break; + case MapUnit::MapPoint : + nMul = 35.27777778; + break; + default: + nMul = 1.0; + break; + } + return nMul; +} + + +void PSWriter::ImplWriteLineInfo( double fLWidth, double fMLimit, + SvtGraphicStroke::CapType eLCap, + SvtGraphicStroke::JoinType eJoin, + SvtGraphicStroke::DashArray && rLDash ) +{ + if ( fLineWidth != fLWidth ) + { + fLineWidth = fLWidth; + ImplWriteDouble( fLineWidth ); + ImplWriteLine( "lw", PS_SPACE ); + } + if ( eLineCap != eLCap ) + { + eLineCap = eLCap; + ImplWriteLong( static_cast<sal_Int32>(eLineCap) ); + ImplWriteLine( "lc", PS_SPACE ); + } + if ( eJoinType != eJoin ) + { + eJoinType = eJoin; + ImplWriteLong( static_cast<sal_Int32>(eJoinType) ); + ImplWriteLine( "lj", PS_SPACE ); + } + if ( eJoinType == SvtGraphicStroke::joinMiter ) + { + if ( fMiterLimit != fMLimit ) + { + fMiterLimit = fMLimit; + ImplWriteDouble( fMiterLimit ); + ImplWriteLine( "ml", PS_SPACE ); + } + } + if ( aDashArray != rLDash ) + { + aDashArray = std::move(rLDash); + sal_uInt32 j, i = aDashArray.size(); + ImplWriteLine( "[", PS_SPACE ); + for ( j = 0; j < i; j++ ) + ImplWriteDouble( aDashArray[ j ] ); + ImplWriteLine( "] 0 ld" ); + } +} + +void PSWriter::ImplWriteLineInfo( const LineInfo& rLineInfo ) +{ + std::vector< double > l_aDashArray; + if ( rLineInfo.GetStyle() == LineStyle::Dash ) + l_aDashArray = rLineInfo.GetDotDashArray(); + const double fLWidth(( ( rLineInfo.GetWidth() + 1 ) + ( rLineInfo.GetWidth() + 1 ) ) * 0.5); + SvtGraphicStroke::JoinType aJoinType(SvtGraphicStroke::joinMiter); + SvtGraphicStroke::CapType aCapType(SvtGraphicStroke::capButt); + + switch(rLineInfo.GetLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + // do NOT use SvtGraphicStroke::joinNone here + // since it will be written as numerical value directly + // and is NOT a valid EPS value + break; + case basegfx::B2DLineJoin::Miter: + aJoinType = SvtGraphicStroke::joinMiter; + break; + case basegfx::B2DLineJoin::Bevel: + aJoinType = SvtGraphicStroke::joinBevel; + break; + case basegfx::B2DLineJoin::Round: + aJoinType = SvtGraphicStroke::joinRound; + break; + } + switch(rLineInfo.GetLineCap()) + { + default: /* css::drawing::LineCap_BUTT */ + { + aCapType = SvtGraphicStroke::capButt; + break; + } + case css::drawing::LineCap_ROUND: + { + aCapType = SvtGraphicStroke::capRound; + break; + } + case css::drawing::LineCap_SQUARE: + { + aCapType = SvtGraphicStroke::capSquare; + break; + } + } + + ImplWriteLineInfo( fLWidth, fMiterLimit, aCapType, aJoinType, std::move(l_aDashArray) ); +} + +void PSWriter::ImplWriteLong(sal_Int32 nNumber, NMode nMode) +{ + const OString aNumber(OString::number(nNumber)); + mnCursorPos += aNumber.getLength(); + mpPS->WriteOString( aNumber ); + ImplExecMode(nMode); +} + +void PSWriter::ImplWriteDouble( double fNumber ) +{ + sal_Int32 nPTemp = static_cast<sal_Int32>(fNumber); + sal_Int32 nATemp = std::abs( static_cast<sal_Int32>( ( fNumber - nPTemp ) * 100000 ) ); + + if ( !nPTemp && nATemp && ( fNumber < 0.0 ) ) + mpPS->WriteChar( '-' ); + + const OString aNumber1(OString::number(nPTemp)); + mpPS->WriteOString( aNumber1 ); + mnCursorPos += aNumber1.getLength(); + + if ( nATemp ) + { + int zCount = 0; + mpPS->WriteUChar( '.' ); + mnCursorPos++; + const OString aNumber2(OString::number(nATemp)); + + sal_Int16 n, nLen = aNumber2.getLength(); + if ( nLen < 8 ) + { + mnCursorPos += 6 - nLen; + for ( n = 0; n < ( 5 - nLen ); n++ ) + { + mpPS->WriteUChar( '0' ); + } + } + mnCursorPos += nLen; + for ( n = 0; n < nLen; n++ ) + { + mpPS->WriteChar( aNumber2[n] ); + zCount--; + if ( aNumber2[n] != '0' ) + zCount = 0; + } + if ( zCount ) + mpPS->SeekRel( zCount ); + } + ImplExecMode( PS_SPACE ); +} + +/// Writes the number to stream: nNumber / ( 10^nCount ) +void PSWriter::ImplWriteF( sal_Int32 nNumber, sal_uInt8 nCount, NMode nMode ) +{ + if ( nNumber < 0 ) + { + mpPS->WriteUChar( '-' ); + nNumber = -nNumber; + mnCursorPos++; + } + const OString aScaleFactor(OString::number(nNumber)); + sal_uInt32 nLen = aScaleFactor.getLength(); + sal_Int32 const nStSize = (nCount + 1) - nLen; + static_assert(sizeof(nStSize) == sizeof((nCount + 1) - nLen)); // tdf#134667 + if ( nStSize >= 1 ) + { + mpPS->WriteUChar( '0' ); + mnCursorPos++; + } + if ( nStSize >= 2 ) + { + mpPS->WriteUChar( '.' ); + for (sal_Int32 i = 1; i < nStSize; ++i) + { + mpPS->WriteUChar( '0' ); + mnCursorPos++; + } + } + mnCursorPos += nLen; + for( sal_uInt32 n = 0; n < nLen; n++ ) + { + if ( n == nLen - nCount ) + { + mpPS->WriteUChar( '.' ); + mnCursorPos++; + } + mpPS->WriteChar( aScaleFactor[n] ); + } + ImplExecMode( nMode ); +} + +void PSWriter::ImplWriteByte( sal_uInt8 nNumb, NMode nMode ) +{ + mpPS->WriteUChar( nNumb ); + mnCursorPos++; + ImplExecMode( nMode ); +} + +void PSWriter::ImplWriteHexByte( sal_uInt8 nNumb, NMode nMode ) +{ + if ( ( nNumb >> 4 ) > 9 ) + mpPS->WriteUChar( ( nNumb >> 4 ) + 'A' - 10 ); + else + mpPS->WriteUChar( ( nNumb >> 4 ) + '0' ); + + if ( ( nNumb & 0xf ) > 9 ) + mpPS->WriteUChar( ( nNumb & 0xf ) + 'A' - 10 ); + else + mpPS->WriteUChar( ( nNumb & 0xf ) + '0' ); + mnCursorPos += 2; + ImplExecMode( nMode ); +} + +// writes the sal_uInt8 nNumb as a Number from 0.000 up to 1.000 + +void PSWriter::ImplWriteB1( sal_uInt8 nNumb ) +{ + ImplWriteF( 1000 * ( nNumb + 1 ) / 256 ); +} + +inline void PSWriter::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ) +{ + dwShift |= ( nCode << ( nOffset - nCodeLen ) ); + nOffset -= nCodeLen; + while ( nOffset < 24 ) + { + ImplWriteHexByte( static_cast<sal_uInt8>( dwShift >> 24 ) ); + dwShift <<= 8; + nOffset += 8; + } + if ( nCode == 257 && nOffset != 32 ) + ImplWriteHexByte( static_cast<sal_uInt8>( dwShift >> 24 ) ); +} + +void PSWriter::StartCompression() +{ + sal_uInt16 i; + nDataSize = 8; + + nClearCode = 1 << nDataSize; + nEOICode = nClearCode + 1; + nTableSize = nEOICode + 1; + nCodeSize = nDataSize + 1; + + nOffset = 32; // number of free unused in dwShift + dwShift = 0; + + pTable.reset(new PSLZWCTreeNode[ 4096 ]); + + for ( i = 0; i < 4096; i++ ) + { + pTable[ i ].pBrother = pTable[ i ].pFirstChild = nullptr; + pTable[ i ].nCode = i; + pTable[ i ].nValue = static_cast<sal_uInt8>( i ); + } + pPrefix = nullptr; + WriteBits( nClearCode, nCodeSize ); +} + +void PSWriter::Compress( sal_uInt8 nCompThis ) +{ + PSLZWCTreeNode* p; + sal_uInt16 i; + sal_uInt8 nV; + + if( !pPrefix ) + { + pPrefix = pTable.get() + nCompThis; + } + else + { + nV = nCompThis; + for( p = pPrefix->pFirstChild; p != nullptr; p = p->pBrother ) + { + if ( p->nValue == nV ) + break; + } + + if( p ) + pPrefix = p; + else + { + WriteBits( pPrefix->nCode, nCodeSize ); + + if ( nTableSize == 409 ) + { + WriteBits( nClearCode, nCodeSize ); + + for ( i = 0; i < nClearCode; i++ ) + pTable[ i ].pFirstChild = nullptr; + + nCodeSize = nDataSize + 1; + nTableSize = nEOICode + 1; + } + else + { + if( nTableSize == static_cast<sal_uInt16>( ( 1 << nCodeSize ) - 1 ) ) + nCodeSize++; + + p = pTable.get() + ( nTableSize++ ); + p->pBrother = pPrefix->pFirstChild; + pPrefix->pFirstChild = p; + p->nValue = nV; + p->pFirstChild = nullptr; + } + + pPrefix = pTable.get() + nV; + } + } +} + +void PSWriter::EndCompression() +{ + if( pPrefix ) + WriteBits( pPrefix->nCode, nCodeSize ); + + WriteBits( nEOICode, nCodeSize ); + pTable.reset(); +} + +sal_uInt8* PSWriter::ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uInt32 nComp, sal_uInt32 nSize ) +{ + while ( nComp-- >= nSize ) + { + sal_uInt64 i; + for ( i = 0; i < nSize; i++ ) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + break; + } + if ( i == nSize ) + return pSource; + pSource++; + } + return nullptr; +} + +bool PSWriter::ImplGetBoundingBox( double* nNumb, sal_uInt8* pSource, sal_uInt32 nSize ) +{ + bool bRetValue = false; + sal_uInt32 nBytesRead; + + if ( nSize < 256 ) // we assume that the file is greater than 256 bytes + return false; + + if ( nSize < POSTSCRIPT_BOUNDINGSEARCH ) + nBytesRead = nSize; + else + nBytesRead = POSTSCRIPT_BOUNDINGSEARCH; + + sal_uInt8* pDest = ImplSearchEntry( pSource, reinterpret_cast<sal_uInt8 const *>("%%BoundingBox:"), nBytesRead, 14 ); + if ( pDest ) + { + int nSecurityCount = 100; // only 100 bytes following the bounding box will be checked + nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0; + pDest += 14; + for ( int i = 0; ( i < 4 ) && nSecurityCount; i++ ) + { + int nDivision = 1; + bool bDivision = false; + bool bNegative = false; + bool bValid = true; + + while ( ( --nSecurityCount ) && ( ( *pDest == ' ' ) || ( *pDest == 0x9 ) ) ) + pDest++; + sal_uInt8 nByte = *pDest; + while ( nSecurityCount && ( nByte != ' ' ) && ( nByte != 0x9 ) && ( nByte != 0xd ) && ( nByte != 0xa ) ) + { + switch ( nByte ) + { + case '.' : + if ( bDivision ) + bValid = false; + else + bDivision = true; + break; + case '-' : + bNegative = true; + break; + default : + if ( ( nByte < '0' ) || ( nByte > '9' ) ) + nSecurityCount = 1; // error parsing the bounding box values + else if ( bValid ) + { + if ( bDivision ) + nDivision*=10; + nNumb[i] *= 10; + nNumb[i] += nByte - '0'; + } + break; + } + nSecurityCount--; + nByte = *(++pDest); + } + if ( bNegative ) + nNumb[i] = -nNumb[i]; + if ( bDivision && ( nDivision != 1 ) ) + nNumb[i] /= nDivision; + } + if ( nSecurityCount) + bRetValue = true; + } + return bRetValue; +} + +//================== GraphicExport - the exported function =================== + +bool ExportEpsGraphic(SvStream & rStream, const Graphic & rGraphic, FilterConfigItem* pFilterConfigItem) +{ + PSWriter aPSWriter; + return aPSWriter.WritePS(rGraphic, rStream, pFilterConfigItem); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/etiff/etiff.cxx b/vcl/source/filter/etiff/etiff.cxx new file mode 100644 index 000000000..b34accab8 --- /dev/null +++ b/vcl/source/filter/etiff/etiff.cxx @@ -0,0 +1,586 @@ +/* -*- 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 <tools/stream.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <filter/TiffWriter.hxx> + +#define NewSubfileType 254 +#define ImageWidth 256 +#define ImageLength 257 +#define BitsPerSample 258 +#define Compression 259 +#define PhotometricInterpretation 262 +#define StripOffsets 273 +#define SamplesPerPixel 277 +#define RowsPerStrip 278 +#define StripByteCounts 279 +#define XResolution 282 +#define YResolution 283 +#define PlanarConfiguration 284 +#define ResolutionUnit 296 +#define ColorMap 320 + +namespace { + +struct TIFFLZWCTreeNode +{ + + TIFFLZWCTreeNode* pBrother; // next node with the same father + TIFFLZWCTreeNode* pFirstChild; // first son + sal_uInt16 nCode; // The code for the string of pixel values, which arises if... <missing comment> + sal_uInt16 nValue; // pixel value +}; + + +class TIFFWriter +{ +private: + + SvStream& m_rOStm; + sal_uInt32 mnStreamOfs; + + bool mbStatus; + BitmapReadAccess* mpAcc; + + sal_uInt32 mnWidth, mnHeight, mnColors; + sal_uInt32 mnCurAllPictHeight; + sal_uInt32 mnSumOfAllPictHeight; + sal_uInt32 mnBitsPerPixel; + sal_uInt32 mnLastPercent; + + sal_uInt32 mnLatestIfdPos; + sal_uInt16 mnTagCount; // number of tags already written + sal_uInt32 mnCurrentTagCountPos; // offset to the position where the current + // tag count is to insert + + sal_uInt32 mnXResPos; // if != 0 this DWORDs stores the + sal_uInt32 mnYResPos; // actual streamposition of the + sal_uInt32 mnPalPos; // Tag Entry + sal_uInt32 mnBitmapPos; + sal_uInt32 mnStripByteCountPos; + + std::unique_ptr<TIFFLZWCTreeNode[]> pTable; + TIFFLZWCTreeNode* pPrefix; + sal_uInt16 nDataSize; + sal_uInt16 nClearCode; + sal_uInt16 nEOICode; + sal_uInt16 nTableSize; + sal_uInt16 nCodeSize; + sal_uInt32 nOffset; + sal_uInt32 dwShift; + + css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator; + + void ImplCallback( sal_uInt32 nPercent ); + bool ImplWriteHeader( bool bMultiPage ); + void ImplWritePalette(); + void ImplWriteBody(); + void ImplWriteTag( sal_uInt16 TagID, sal_uInt16 DataType, sal_uInt32 NumberOfItems, sal_uInt32 Value); + void ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit ); + void StartCompression(); + void Compress( sal_uInt8 nSrc ); + void EndCompression(); + inline void WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ); + +public: + + explicit TIFFWriter(SvStream &rStream); + + bool WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem ); +}; + +} + +TIFFWriter::TIFFWriter(SvStream &rStream) + : m_rOStm(rStream) + , mnStreamOfs(0) + , mbStatus(true) + , mpAcc(nullptr) + , mnWidth(0) + , mnHeight(0) + , mnColors(0) + , mnCurAllPictHeight(0) + , mnSumOfAllPictHeight(0) + , mnBitsPerPixel(0) + , mnLastPercent(0) + , mnLatestIfdPos(0) + , mnTagCount(0) + , mnCurrentTagCountPos(0) + , mnXResPos(0) + , mnYResPos(0) + , mnPalPos(0) + , mnBitmapPos(0) + , mnStripByteCountPos(0) + , pPrefix(nullptr) + , nDataSize(0) + , nClearCode(0) + , nEOICode(0) + , nTableSize(0) + , nCodeSize(0) + , nOffset(0) + , dwShift(0) +{ +} + + +bool TIFFWriter::WriteTIFF( const Graphic& rGraphic, FilterConfigItem const * pFilterConfigItem) +{ + if ( pFilterConfigItem ) + { + xStatusIndicator = pFilterConfigItem->GetStatusIndicator(); + if ( xStatusIndicator.is() ) + { + xStatusIndicator->start( OUString(), 100 ); + } + } + + const SvStreamEndian nOldFormat = m_rOStm.GetEndian(); + mnStreamOfs = m_rOStm.Tell(); + + // we will use the BIG Endian Mode + // TIFF header + m_rOStm.SetEndian( SvStreamEndian::BIG ); + m_rOStm.WriteUInt32( 0x4d4d002a ); // TIFF identifier + mnLatestIfdPos = m_rOStm.Tell(); + m_rOStm.WriteUInt32( 0 ); + + if( mbStatus ) + { + Animation aAnimation = rGraphic.IsAnimated() ? rGraphic.GetAnimation() : Animation(); + if (!rGraphic.IsAnimated()) + aAnimation.Insert(AnimationBitmap(rGraphic.GetBitmapEx(), Point(), Size())); + + for (size_t i = 0; i < aAnimation.Count(); ++i) + mnSumOfAllPictHeight += aAnimation.Get(i).maBitmapEx.GetSizePixel().Height(); + + for (size_t i = 0; mbStatus && i < aAnimation.Count(); ++i) + { + mnPalPos = 0; + const AnimationBitmap& rAnimationBitmap = aAnimation.Get( i ); + Bitmap aBmp = rAnimationBitmap.maBitmapEx.GetBitmap(); + mpAcc = aBmp.AcquireReadAccess(); + if ( mpAcc ) + { + mnBitsPerPixel = vcl::pixelFormatBitCount(aBmp.getPixelFormat()); + + // export code below only handles four discrete cases + mnBitsPerPixel = + mnBitsPerPixel <= 1 ? 1 : mnBitsPerPixel <= 4 ? 4 : mnBitsPerPixel <= 8 ? 8 : 24; + + if ( ImplWriteHeader( aAnimation.Count() > 0 ) ) + { + Size aDestMapSize( 300, 300 ); + const MapMode& aMapMode( aBmp.GetPrefMapMode() ); + if ( aMapMode.GetMapUnit() != MapUnit::MapPixel ) + { + const Size aPrefSize( rGraphic.GetPrefSize() ); + aDestMapSize = OutputDevice::LogicToLogic(aPrefSize, aMapMode, MapMode(MapUnit::MapInch)); + } + ImplWriteResolution( mnXResPos, aDestMapSize.Width() ); + ImplWriteResolution( mnYResPos, aDestMapSize.Height() ); + if ( mnPalPos ) + ImplWritePalette(); + ImplWriteBody(); + } + sal_uInt32 nCurPos = m_rOStm.Tell(); + m_rOStm.Seek( mnCurrentTagCountPos ); + m_rOStm.WriteUInt16( mnTagCount ); + m_rOStm.Seek( nCurPos ); + + Bitmap::ReleaseAccess( mpAcc ); + } + else + mbStatus = false; + } + } + m_rOStm.SetEndian( nOldFormat ); + + if ( xStatusIndicator.is() ) + xStatusIndicator->end(); + + return mbStatus; +} + + +void TIFFWriter::ImplCallback( sal_uInt32 nPercent ) +{ + if ( xStatusIndicator.is() ) + { + if( nPercent >= mnLastPercent + 3 ) + { + mnLastPercent = nPercent; + if ( nPercent <= 100 ) + xStatusIndicator->setValue( nPercent ); + } + } +} + + +bool TIFFWriter::ImplWriteHeader( bool bMultiPage ) +{ + mnTagCount = 0; + mnWidth = mpAcc->Width(); + mnHeight = mpAcc->Height(); + + if ( mnWidth && mnHeight && mnBitsPerPixel && mbStatus ) + { + sal_uInt32 nCurrentPos = m_rOStm.Tell(); + m_rOStm.Seek( mnLatestIfdPos ); + m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs ); // offset to the IFD + m_rOStm.Seek( nCurrentPos ); + + // (OFS8) TIFF image file directory (IFD) + mnCurrentTagCountPos = m_rOStm.Tell(); + m_rOStm.WriteUInt16( 0 ); // the number of tangents to insert later + + sal_uInt32 nSubFileFlags = 0; + if ( bMultiPage ) + nSubFileFlags |= 2; + ImplWriteTag( NewSubfileType, 4, 1, nSubFileFlags ); + ImplWriteTag( ImageWidth, 4, 1, mnWidth ); + ImplWriteTag( ImageLength, 4, 1, mnHeight); + ImplWriteTag( BitsPerSample, 3, 1, ( mnBitsPerPixel == 24 ) ? 8 : mnBitsPerPixel ); + ImplWriteTag( Compression, 3, 1, 5 ); + sal_uInt8 nTemp; + switch ( mnBitsPerPixel ) + { + case 1 : + nTemp = 1; + break; + case 4 : + case 8 : + nTemp = 3; + break; + case 24: + nTemp = 2; + break; + default: + nTemp = 0; // -Wall set a default... + break; + } + ImplWriteTag( PhotometricInterpretation, 3, 1, nTemp ); + mnBitmapPos = m_rOStm.Tell(); + ImplWriteTag( StripOffsets, 4, 1, 0 ); + ImplWriteTag( SamplesPerPixel, 3, 1, ( mnBitsPerPixel == 24 ) ? 3 : 1 ); + ImplWriteTag( RowsPerStrip, 4, 1, mnHeight ); + mnStripByteCountPos = m_rOStm.Tell(); + ImplWriteTag( StripByteCounts, 4, 1, ( ( mnWidth * mnBitsPerPixel * mnHeight ) + 7 ) >> 3 ); + mnXResPos = m_rOStm.Tell(); + ImplWriteTag( XResolution, 5, 1, 0 ); + mnYResPos = m_rOStm.Tell(); + ImplWriteTag( YResolution, 5, 1, 0 ); + if ( mnBitsPerPixel != 1 ) + ImplWriteTag( PlanarConfiguration, 3, 1, 1 ); // ( RGB ORDER ) + ImplWriteTag( ResolutionUnit, 3, 1, 2); // Resolution Unit is Inch + if ( ( mnBitsPerPixel == 4 ) || ( mnBitsPerPixel == 8 ) ) + { + mnColors = mpAcc->GetPaletteEntryCount(); + mnPalPos = m_rOStm.Tell(); + ImplWriteTag( ColorMap, 3, 3 * mnColors, 0 ); + } + + // and last we write zero to close the num dir entries list + mnLatestIfdPos = m_rOStm.Tell(); + m_rOStm.WriteUInt32( 0 ); // there are no more IFD + } + else + mbStatus = false; + + return mbStatus; +} + + +void TIFFWriter::ImplWritePalette() +{ + sal_uInt64 nCurrentPos = m_rOStm.Tell(); + m_rOStm.Seek( mnPalPos + 8 ); // the palette tag entry needs the offset + m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs ); // to the palette colors + m_rOStm.Seek( nCurrentPos ); + + for ( sal_uInt32 i = 0; i < mnColors; i++ ) + { + const BitmapColor& rColor = mpAcc->GetPaletteColor( i ); + m_rOStm.WriteUInt16( rColor.GetRed() << 8 ); + } + for ( sal_uInt32 i = 0; i < mnColors; i++ ) + { + const BitmapColor& rColor = mpAcc->GetPaletteColor( i ); + m_rOStm.WriteUInt16( rColor.GetGreen() << 8 ); + } + for ( sal_uInt32 i = 0; i < mnColors; i++ ) + { + const BitmapColor& rColor = mpAcc->GetPaletteColor( i ); + m_rOStm.WriteUInt16( rColor.GetBlue() << 8 ); + } +} + + +void TIFFWriter::ImplWriteBody() +{ + sal_uInt8 nTemp = 0; + sal_uInt8 nShift; + sal_uInt32 j, x, y; + + sal_uInt64 nGfxBegin = m_rOStm.Tell(); + m_rOStm.Seek( mnBitmapPos + 8 ); // the strip offset tag entry needs the offset + m_rOStm.WriteUInt32( nGfxBegin - mnStreamOfs ); // to the bitmap data + m_rOStm.Seek( nGfxBegin ); + + StartCompression(); + + switch( mnBitsPerPixel ) + { + case 24 : + { + for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ ) + { + ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight ); + Scanline pScanline = mpAcc->GetScanline( y ); + for ( x = 0; x < mnWidth; x++ ) + { + const BitmapColor& rColor = mpAcc->GetPixelFromData( pScanline, x ); + Compress( rColor.GetRed() ); + Compress( rColor.GetGreen() ); + Compress( rColor.GetBlue() ); + } + } + } + break; + + case 8 : + { + for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ ) + { + ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight ); + Scanline pScanline = mpAcc->GetScanline( y ); + for ( x = 0; x < mnWidth; x++ ) + { + Compress( mpAcc->GetIndexFromData( pScanline, x ) ); + } + } + } + break; + + case 4 : + { + for ( nShift = 0, y = 0; y < mnHeight; y++, mnCurAllPictHeight++ ) + { + ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight ); + Scanline pScanline = mpAcc->GetScanline( y ); + for ( x = 0; x < mnWidth; x++, nShift++ ) + { + if (!( nShift & 1 )) + nTemp = ( mpAcc->GetIndexFromData( pScanline, x ) << 4 ); + else + Compress( static_cast<sal_uInt8>( nTemp | ( mpAcc->GetIndexFromData( pScanline, x ) & 0xf ) ) ); + } + if ( nShift & 1 ) + Compress( nTemp ); + } + } + break; + + case 1 : + { + j = 1; + for ( y = 0; y < mnHeight; y++, mnCurAllPictHeight++ ) + { + ImplCallback( 100 * mnCurAllPictHeight / mnSumOfAllPictHeight ); + Scanline pScanline = mpAcc->GetScanline( y ); + for ( x = 0; x < mnWidth; x++) + { + j <<= 1; + j |= ( ( ~mpAcc->GetIndexFromData( pScanline, x ) ) & 1 ); + if ( j & 0x100 ) + { + Compress( static_cast<sal_uInt8>(j) ); + j = 1; + } + } + if ( j != 1 ) + { + Compress( static_cast<sal_uInt8>(j << ( ( ( x & 7) ^ 7 ) + 1 ) ) ); + j = 1; + } + } + } + break; + + default: + { + mbStatus = false; + } + break; + } + + EndCompression(); + + if ( mnStripByteCountPos && mbStatus ) + { + sal_uInt64 nGfxEnd = m_rOStm.Tell(); + m_rOStm.Seek( mnStripByteCountPos + 8 ); + m_rOStm.WriteUInt32( nGfxEnd - nGfxBegin ); // mnStripByteCountPos needs the size of the compression data + m_rOStm.Seek( nGfxEnd ); + } +} + + +void TIFFWriter::ImplWriteResolution( sal_uInt64 nStreamPos, sal_uInt32 nResolutionUnit ) +{ + sal_uInt64 nCurrentPos = m_rOStm.Tell(); + m_rOStm.Seek( nStreamPos + 8 ); + m_rOStm.WriteUInt32( nCurrentPos - mnStreamOfs ); + m_rOStm.Seek( nCurrentPos ); + m_rOStm.WriteUInt32( 1 ); + m_rOStm.WriteUInt32( nResolutionUnit ); +} + + +void TIFFWriter::ImplWriteTag( sal_uInt16 nTagID, sal_uInt16 nDataType, sal_uInt32 nNumberOfItems, sal_uInt32 nValue) +{ + mnTagCount++; + + m_rOStm.WriteUInt16( nTagID ); + m_rOStm.WriteUInt16( nDataType ); + m_rOStm.WriteUInt32( nNumberOfItems ); + if ( nDataType == 3 ) + nValue <<=16; // in Big Endian Mode WORDS needed to be shifted to a DWORD + m_rOStm.WriteUInt32( nValue ); +} + + +inline void TIFFWriter::WriteBits( sal_uInt16 nCode, sal_uInt16 nCodeLen ) +{ + dwShift |= ( nCode << ( nOffset - nCodeLen ) ); + nOffset -= nCodeLen; + while ( nOffset < 24 ) + { + m_rOStm.WriteUChar( dwShift >> 24 ); + dwShift <<= 8; + nOffset += 8; + } + if ( nCode == 257 && nOffset != 32 ) + { + m_rOStm.WriteUChar( dwShift >> 24 ); + } +} + + +void TIFFWriter::StartCompression() +{ + sal_uInt16 i; + nDataSize = 8; + + nClearCode = 1 << nDataSize; + nEOICode = nClearCode + 1; + nTableSize = nEOICode + 1; + nCodeSize = nDataSize + 1; + + nOffset = 32; // number of free bits in dwShift + dwShift = 0; + + pTable.reset(new TIFFLZWCTreeNode[ 4096 ]); + + for ( i = 0; i < 4096; i++) + { + pTable[ i ].pBrother = pTable[ i ].pFirstChild = nullptr; + pTable[ i ].nCode = i; + pTable[ i ].nValue = static_cast<sal_uInt8>( i ); + } + + pPrefix = nullptr; + WriteBits( nClearCode, nCodeSize ); +} + + +void TIFFWriter::Compress( sal_uInt8 nCompThis ) +{ + TIFFLZWCTreeNode* p; + sal_uInt16 i; + sal_uInt8 nV; + + if( !pPrefix ) + { + pPrefix = &pTable[nCompThis]; + } + else + { + nV = nCompThis; + for( p = pPrefix->pFirstChild; p != nullptr; p = p->pBrother ) + { + if ( p->nValue == nV ) + break; + } + + if( p ) + pPrefix = p; + else + { + WriteBits( pPrefix->nCode, nCodeSize ); + + if ( nTableSize == 409 ) + { + WriteBits( nClearCode, nCodeSize ); + + for ( i = 0; i < nClearCode; i++ ) + pTable[ i ].pFirstChild = nullptr; + + nCodeSize = nDataSize + 1; + nTableSize = nEOICode + 1; + } + else + { + if( nTableSize == static_cast<sal_uInt16>( ( 1 << nCodeSize ) - 1 ) ) + nCodeSize++; + + p = &pTable[ nTableSize++ ]; + p->pBrother = pPrefix->pFirstChild; + pPrefix->pFirstChild = p; + p->nValue = nV; + p->pFirstChild = nullptr; + } + + pPrefix = &pTable[nV]; + } + } +} + + +void TIFFWriter::EndCompression() +{ + if( pPrefix ) + WriteBits( pPrefix->nCode, nCodeSize ); + + WriteBits( nEOICode, nCodeSize ); + pTable.reset(); +} + +bool ExportTiffGraphicImport(SvStream & rStream, const Graphic & rGraphic, const FilterConfigItem* pFilterConfigItem) +{ + TIFFWriter aWriter(rStream); + return aWriter.WriteTIFF( rGraphic, pFilterConfigItem ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx new file mode 100644 index 000000000..d208c7d81 --- /dev/null +++ b/vcl/source/filter/graphicfilter.cxx @@ -0,0 +1,2056 @@ +/* -*- 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 <config_folders.h> + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/threadpool.hxx> +#include <cppuhelper/implbase.hxx> +#include <tools/fract.hxx> +#include <unotools/configmgr.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <tools/zcodec.hxx> +#include <fltcall.hxx> +#include <vcl/salctype.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/pngwrite.hxx> +#include <vcl/vectorgraphicdata.hxx> +#include <vcl/virdev.hxx> +#include <impgraph.hxx> +#include <vcl/svapp.hxx> +#include <osl/file.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <vcl/wmf.hxx> +#include "igif/gifread.hxx" +#include <vcl/pdfread.hxx> +#include "jpeg/jpeg.hxx" +#include "png/png.hxx" +#include "ixbm/xbmread.hxx" +#include <filter/XpmReader.hxx> +#include <filter/TiffReader.hxx> +#include <filter/TiffWriter.hxx> +#include <filter/TgaReader.hxx> +#include <filter/PictReader.hxx> +#include <filter/MetReader.hxx> +#include <filter/RasReader.hxx> +#include <filter/PcxReader.hxx> +#include <filter/EpsReader.hxx> +#include <filter/EpsWriter.hxx> +#include <filter/PsdReader.hxx> +#include <filter/PcdReader.hxx> +#include <filter/PbmReader.hxx> +#include <filter/DxfReader.hxx> +#include <filter/GifWriter.hxx> +#include <filter/BmpReader.hxx> +#include <filter/BmpWriter.hxx> +#include <filter/WebpReader.hxx> +#include <filter/WebpWriter.hxx> +#include <osl/module.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/io/XActiveDataSource.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/svg/XSVGWriter.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <unotools/ucbstreamhelper.hxx> +#include <rtl/bootstrap.hxx> +#include <tools/svlibrary.h> +#include <comphelper/string.hxx> +#include <unotools/ucbhelper.hxx> +#include <vector> +#include <memory> +#include <mutex> +#include <string_view> +#include <o3tl/string_view.hxx> +#include <vcl/TypeSerializer.hxx> + +#include "FilterConfigCache.hxx" +#include "graphicfilter_internal.hxx" + +#include <graphic/GraphicFormatDetector.hxx> +#include <graphic/GraphicReader.hxx> + +// Support for GfxLinkType::NativeWebp is so far disabled, +// as enabling it would write .webp images e.g. to .odt documents, +// making those images unreadable for older readers. So for now +// disable the support so that .webp images will be written out as .png, +// and somewhen later enable the support unconditionally. +static bool supportNativeWebp() +{ + const char* const testname = getenv("LO_TESTNAME"); + if(testname == nullptr) + return false; + // Enable support only for those unittests that test it. + if( std::string_view("_anonymous_namespace___GraphicTest__testUnloadedGraphicLoading_") == testname + || std::string_view("VclFiltersTest__testExportImport_") == testname + || o3tl::starts_with(std::string_view(testname), "WebpFilterTest__")) + return true; + return false; +} + +static std::vector< GraphicFilter* > gaFilterHdlList; + +static std::mutex& getListMutex() +{ + static std::mutex s_aListProtection; + return s_aListProtection; +} + +namespace { + +class ImpFilterOutputStream : public ::cppu::WeakImplHelper< css::io::XOutputStream > +{ + SvStream& mrStm; + + virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& rData ) override + { mrStm.WriteBytes(rData.getConstArray(), rData.getLength()); } + virtual void SAL_CALL flush() override + { mrStm.FlushBuffer(); } + virtual void SAL_CALL closeOutput() override {} + +public: + + explicit ImpFilterOutputStream( SvStream& rStm ) : mrStm( rStm ) {} +}; + +} + +// Helper functions + +sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, sal_uLong nComp, sal_uLong nSize ) +{ + while ( nComp-- >= nSize ) + { + sal_uLong i; + for ( i = 0; i < nSize; i++ ) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + break; + } + if ( i == nSize ) + return pSource; + pSource++; + } + return nullptr; +} + +static OUString ImpGetExtension( std::u16string_view rPath ) +{ + OUString aExt; + INetURLObject aURL( rPath ); + aExt = aURL.GetFileExtension().toAsciiUpperCase(); + return aExt; +} + +bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen) +{ + sal_uInt8 sBuf[3]; + // store number format + SvStreamEndian oldNumberFormat = rStream.GetEndian(); + sal_uInt32 nOffset; // in MS documents the pict format is used without the first 512 bytes + for ( nOffset = 0; ( nOffset <= 512 ) && ( ( nStreamPos + nOffset + 14 ) <= nStreamLen ); nOffset += 512 ) + { + short y1,x1,y2,x2; + bool bdBoxOk = true; + + rStream.Seek( nStreamPos + nOffset); + // size of the pict in version 1 pict ( 2bytes) : ignored + rStream.SeekRel(2); + // bounding box (bytes 2 -> 9) + rStream.SetEndian(SvStreamEndian::BIG); + rStream.ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 ); + rStream.SetEndian(oldNumberFormat); // reset format + + if (x1 > x2 || y1 > y2 || // bad bdbox + (x1 == x2 && y1 == y2) || // 1 pixel picture + x2-x1 > 2048 || y2-y1 > 2048 ) // picture abnormally big + bdBoxOk = false; + + // read version op + rStream.ReadBytes(sBuf, 3); + // see http://developer.apple.com/legacy/mac/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_A.pdf + // normal version 2 - page A23 and A24 + if ( sBuf[ 0 ] == 0x00 && sBuf[ 1 ] == 0x11 && sBuf[ 2 ] == 0x02) + return true; + // normal version 1 - page A25 + else if (sBuf[ 0 ] == 0x11 && sBuf[ 1 ] == 0x01 && bdBoxOk) + return true; + } + return false; +} + +ErrCode GraphicFilter::ImpTestOrFindFormat( std::u16string_view rPath, SvStream& rStream, sal_uInt16& rFormat ) +{ + // determine or check the filter/format by reading into it + if( rFormat == GRFILTER_FORMAT_DONTKNOW ) + { + OUString aFormatExt; + if (vcl::peekGraphicFormat(rStream, aFormatExt, false)) + { + rFormat = pConfig->GetImportFormatNumberForExtension( aFormatExt ); + if( rFormat != GRFILTER_FORMAT_DONTKNOW ) + return ERRCODE_NONE; + } + // determine filter by file extension + if( !rPath.empty() ) + { + OUString aExt( ImpGetExtension( rPath ) ); + rFormat = pConfig->GetImportFormatNumberForExtension( aExt ); + if( rFormat != GRFILTER_FORMAT_DONTKNOW ) + return ERRCODE_NONE; + } + return ERRCODE_GRFILTER_FORMATERROR; + } + else + { + OUString aTmpStr( pConfig->GetImportFormatExtension( rFormat ) ); + aTmpStr = aTmpStr.toAsciiUpperCase(); + if (!vcl::peekGraphicFormat(rStream, aTmpStr, true)) + return ERRCODE_GRFILTER_FORMATERROR; + if ( pConfig->GetImportFormatExtension( rFormat ).equalsIgnoreAsciiCase( "pcd" ) ) + { + sal_Int32 nBase = 2; // default Base0 + if ( pConfig->GetImportFilterType( rFormat ).equalsIgnoreAsciiCase( "pcd_Photo_CD_Base4" ) ) + nBase = 1; + else if ( pConfig->GetImportFilterType( rFormat ).equalsIgnoreAsciiCase( "pcd_Photo_CD_Base16" ) ) + nBase = 0; + FilterConfigItem aFilterConfigItem( u"Office.Common/Filter/Graphic/Import/PCD" ); + aFilterConfigItem.WriteInt32( "Resolution", nBase ); + } + } + + return ERRCODE_NONE; +} + +static Graphic ImpGetScaledGraphic( const Graphic& rGraphic, FilterConfigItem& rConfigItem ) +{ + Graphic aGraphic; + + sal_Int32 nLogicalWidth = rConfigItem.ReadInt32( "LogicalWidth", 0 ); + sal_Int32 nLogicalHeight = rConfigItem.ReadInt32( "LogicalHeight", 0 ); + + if ( rGraphic.GetType() != GraphicType::NONE ) + { + sal_Int32 nMode = rConfigItem.ReadInt32( "ExportMode", -1 ); + + if ( nMode == -1 ) // the property is not there, this is possible, if the graphic filter + { // is called via UnoGraphicExporter and not from a graphic export Dialog + nMode = 0; // then we are defaulting this mode to 0 + if ( nLogicalWidth || nLogicalHeight ) + nMode = 2; + } + + Size aOriginalSize; + Size aPrefSize( rGraphic.GetPrefSize() ); + MapMode aPrefMapMode( rGraphic.GetPrefMapMode() ); + if (aPrefMapMode.GetMapUnit() == MapUnit::MapPixel) + aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aPrefSize, MapMode(MapUnit::Map100thMM)); + else + aOriginalSize = OutputDevice::LogicToLogic(aPrefSize, aPrefMapMode, MapMode(MapUnit::Map100thMM)); + if ( !nLogicalWidth ) + nLogicalWidth = aOriginalSize.Width(); + if ( !nLogicalHeight ) + nLogicalHeight = aOriginalSize.Height(); + if( rGraphic.GetType() == GraphicType::Bitmap ) + { + + // Resolution is set + if( nMode == 1 ) + { + BitmapEx aBitmap( rGraphic.GetBitmapEx() ); + MapMode aMap( MapUnit::Map100thInch ); + + sal_Int32 nDPI = rConfigItem.ReadInt32( "Resolution", 75 ); + Fraction aFrac( 1, std::clamp( nDPI, sal_Int32(75), sal_Int32(600) ) ); + + aMap.SetScaleX( aFrac ); + aMap.SetScaleY( aFrac ); + + Size aOldSize = aBitmap.GetSizePixel(); + aGraphic = rGraphic; + aGraphic.SetPrefMapMode( aMap ); + aGraphic.SetPrefSize( Size( aOldSize.Width() * 100, + aOldSize.Height() * 100 ) ); + } + // Size is set + else if( nMode == 2 ) + { + aGraphic = rGraphic; + aGraphic.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) ); + aGraphic.SetPrefSize( Size( nLogicalWidth, nLogicalHeight ) ); + } + else + aGraphic = rGraphic; + + sal_Int32 nColors = rConfigItem.ReadInt32( "Color", 0 ); + if ( nColors ) // graphic conversion necessary ? + { + BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); + aBmpEx.Convert( static_cast<BmpConversion>(nColors) ); // the entries in the xml section have the same meaning as + aGraphic = aBmpEx; // they have in the BmpConversion enum, so it should be + } // allowed to cast them + } + else + { + if( ( nMode == 1 ) || ( nMode == 2 ) ) + { + GDIMetaFile aMtf( rGraphic.GetGDIMetaFile() ); + Size aNewSize( OutputDevice::LogicToLogic(Size(nLogicalWidth, nLogicalHeight), MapMode(MapUnit::Map100thMM), aMtf.GetPrefMapMode()) ); + + if( aNewSize.Width() && aNewSize.Height() ) + { + const Size aPreferredSize( aMtf.GetPrefSize() ); + aMtf.Scale( Fraction( aNewSize.Width(), aPreferredSize.Width() ), + Fraction( aNewSize.Height(), aPreferredSize.Height() ) ); + } + aGraphic = Graphic( aMtf ); + } + else + aGraphic = rGraphic; + } + + } + else + aGraphic = rGraphic; + + return aGraphic; +} + +GraphicFilter::GraphicFilter( bool bConfig ) + : bUseConfig(bConfig) +{ + ImplInit(); +} + +GraphicFilter::~GraphicFilter() +{ + { + std::scoped_lock aGuard( getListMutex() ); + auto it = std::find(gaFilterHdlList.begin(), gaFilterHdlList.end(), this); + if( it != gaFilterHdlList.end() ) + gaFilterHdlList.erase( it ); + + if( gaFilterHdlList.empty() ) + delete pConfig; + } + + mxErrorEx.reset(); +} + +void GraphicFilter::ImplInit() +{ + { + std::scoped_lock aGuard( getListMutex() ); + + if ( gaFilterHdlList.empty() ) + pConfig = new FilterConfigCache( bUseConfig ); + else + pConfig = gaFilterHdlList.front()->pConfig; + + gaFilterHdlList.push_back( this ); + } + + if( bUseConfig ) + { + OUString url("$BRAND_BASE_DIR/" LIBO_LIB_FOLDER); + rtl::Bootstrap::expandMacros(url); //TODO: detect failure + osl::FileBase::getSystemPathFromFileURL(url, aFilterPath); + } + + mxErrorEx = ERRCODE_NONE; +} + +ErrCode GraphicFilter::ImplSetError( ErrCode nError, const SvStream* pStm ) +{ + mxErrorEx = pStm ? pStm->GetError() : ERRCODE_NONE; + return nError; +} + +sal_uInt16 GraphicFilter::GetImportFormatCount() const +{ + return pConfig->GetImportFormatCount(); +} + +sal_uInt16 GraphicFilter::GetImportFormatNumber( std::u16string_view rFormatName ) +{ + return pConfig->GetImportFormatNumber( rFormatName ); +} + +sal_uInt16 GraphicFilter::GetImportFormatNumberForShortName( std::u16string_view rShortName ) +{ + return pConfig->GetImportFormatNumberForShortName( rShortName ); +} + +sal_uInt16 GraphicFilter::GetImportFormatNumberForTypeName( std::u16string_view rType ) +{ + return pConfig->GetImportFormatNumberForTypeName( rType ); +} + +OUString GraphicFilter::GetImportFormatName( sal_uInt16 nFormat ) +{ + return pConfig->GetImportFormatName( nFormat ); +} + +OUString GraphicFilter::GetImportFormatTypeName( sal_uInt16 nFormat ) +{ + return pConfig->GetImportFilterTypeName( nFormat ); +} + +#ifdef _WIN32 +OUString GraphicFilter::GetImportFormatMediaType( sal_uInt16 nFormat ) +{ + return pConfig->GetImportFormatMediaType( nFormat ); +} +#endif + +OUString GraphicFilter::GetImportFormatShortName( sal_uInt16 nFormat ) +{ + return pConfig->GetImportFormatShortName( nFormat ); +} + +OUString GraphicFilter::GetImportWildcard( sal_uInt16 nFormat, sal_Int32 nEntry ) +{ + return pConfig->GetImportWildcard( nFormat, nEntry ); +} + +sal_uInt16 GraphicFilter::GetExportFormatCount() const +{ + return pConfig->GetExportFormatCount(); +} + +sal_uInt16 GraphicFilter::GetExportFormatNumber( std::u16string_view rFormatName ) +{ + return pConfig->GetExportFormatNumber( rFormatName ); +} + +sal_uInt16 GraphicFilter::GetExportFormatNumberForMediaType( std::u16string_view rMediaType ) +{ + return pConfig->GetExportFormatNumberForMediaType( rMediaType ); +} + +sal_uInt16 GraphicFilter::GetExportFormatNumberForShortName( std::u16string_view rShortName ) +{ + return pConfig->GetExportFormatNumberForShortName( rShortName ); +} + +OUString GraphicFilter::GetExportInternalFilterName( sal_uInt16 nFormat ) +{ + return pConfig->GetExportInternalFilterName( nFormat ); +} + +sal_uInt16 GraphicFilter::GetExportFormatNumberForTypeName( std::u16string_view rType ) +{ + return pConfig->GetExportFormatNumberForTypeName( rType ); +} + +OUString GraphicFilter::GetExportFormatName( sal_uInt16 nFormat ) +{ + return pConfig->GetExportFormatName( nFormat ); +} + +OUString GraphicFilter::GetExportFormatMediaType( sal_uInt16 nFormat ) +{ + return pConfig->GetExportFormatMediaType( nFormat ); +} + +OUString GraphicFilter::GetExportFormatShortName( sal_uInt16 nFormat ) +{ + return pConfig->GetExportFormatShortName( nFormat ); +} + +OUString GraphicFilter::GetExportWildcard( sal_uInt16 nFormat ) +{ + return pConfig->GetExportWildcard( nFormat, 0 ); +} + +bool GraphicFilter::IsExportPixelFormat( sal_uInt16 nFormat ) +{ + return pConfig->IsExportPixelFormat( nFormat ); +} + +ErrCode GraphicFilter::CanImportGraphic( const INetURLObject& rPath, + sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat ) +{ + ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR; + SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::CanImportGraphic() : ProtType == INetProtocol::NotValid" ); + + OUString aMainUrl( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::READ | StreamMode::SHARE_DENYNONE )); + if (xStream) + { + nRetValue = CanImportGraphic( aMainUrl, *xStream, nFormat, pDeterminedFormat ); + } + return nRetValue; +} + +ErrCode GraphicFilter::CanImportGraphic( std::u16string_view rMainUrl, SvStream& rIStream, + sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat ) +{ + sal_uInt64 nStreamPos = rIStream.Tell(); + ErrCode nRes = ImpTestOrFindFormat( rMainUrl, rIStream, nFormat ); + + rIStream.Seek(nStreamPos); + + if( nRes==ERRCODE_NONE && pDeterminedFormat!=nullptr ) + *pDeterminedFormat = nFormat; + + return ImplSetError( nRes, &rIStream ); +} + +//SJ: TODO, we need to create a GraphicImporter component +ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const INetURLObject& rPath, + sal_uInt16 nFormat, sal_uInt16 * pDeterminedFormat, GraphicFilterImportFlags nImportFlags ) +{ + ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR; + SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::ImportGraphic() : ProtType == INetProtocol::NotValid" ); + + OUString aMainUrl( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::READ | StreamMode::SHARE_DENYNONE )); + if (xStream) + { + nRetValue = ImportGraphic( rGraphic, aMainUrl, *xStream, nFormat, pDeterminedFormat, nImportFlags ); + } + return nRetValue; +} + +namespace { + +/// Contains a stream and other associated data to import pixels into a +/// Graphic. +struct GraphicImportContext +{ + /// Pixel data is read from this stream. + std::unique_ptr<SvStream> m_pStream; + /// The Graphic the import filter gets. + std::shared_ptr<Graphic> m_pGraphic; + /// Write pixel data using this access. + std::unique_ptr<BitmapScopedWriteAccess> m_pAccess; + std::unique_ptr<AlphaScopedWriteAccess> m_pAlphaAccess; + // Need to have an AlphaMask instance to keep its lifetime. + AlphaMask mAlphaMask; + /// Signals if import finished correctly. + ErrCode m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + /// Original graphic format. + GfxLinkType m_eLinkType = GfxLinkType::NONE; + /// Position of the stream before reading the data. + sal_uInt64 m_nStreamBegin = 0; + /// Flags for the import filter. + GraphicFilterImportFlags m_nImportFlags = GraphicFilterImportFlags::NONE; +}; + +/// Graphic import worker that gets executed on a thread. +class GraphicImportTask : public comphelper::ThreadTask +{ + GraphicImportContext& m_rContext; +public: + GraphicImportTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, GraphicImportContext& rContext); + void doWork() override; + /// Shared code between threaded and non-threaded version. + static void doImport(GraphicImportContext& rContext); +}; + +} + +GraphicImportTask::GraphicImportTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, GraphicImportContext& rContext) + : comphelper::ThreadTask(pTag), + m_rContext(rContext) +{ +} + +void GraphicImportTask::doWork() +{ + GraphicImportTask::doImport(m_rContext); +} + +void GraphicImportTask::doImport(GraphicImportContext& rContext) +{ + if(rContext.m_eLinkType == GfxLinkType::NativeJpg) + { + if (!ImportJPEG(*rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, rContext.m_pAccess.get())) + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else if(rContext.m_eLinkType == GfxLinkType::NativePng) + { + if (!vcl::ImportPNG(*rContext.m_pStream, *rContext.m_pGraphic, + rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, + rContext.m_pAccess.get(), rContext.m_pAlphaAccess.get())) + { + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } +} + +void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGraphics, std::vector< std::unique_ptr<SvStream> > vStreams) +{ + static bool bThreads = !getenv("VCL_NO_THREAD_IMPORT"); + std::vector<GraphicImportContext> aContexts; + aContexts.reserve(vStreams.size()); + comphelper::ThreadPool& rSharedPool = comphelper::ThreadPool::getSharedOptimalPool(); + std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag(); + + for (auto& pStream : vStreams) + { + aContexts.emplace_back(); + GraphicImportContext& rContext = aContexts.back(); + + if (pStream) + { + rContext.m_pStream = std::move(pStream); + rContext.m_pGraphic = std::make_shared<Graphic>(); + rContext.m_nStatus = ERRCODE_NONE; + + // Detect the format. + ResetLastError(); + rContext.m_nStreamBegin = rContext.m_pStream->Tell(); + sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW; + rContext.m_nStatus = ImpTestOrFindFormat(u"", *rContext.m_pStream, nFormat); + rContext.m_pStream->Seek(rContext.m_nStreamBegin); + + // Import the graphic. + if (rContext.m_nStatus == ERRCODE_NONE && !rContext.m_pStream->GetError()) + { + OUString aFilterName = pConfig->GetImportFilterName(nFormat); + + if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG)) + { + rContext.m_eLinkType = GfxLinkType::NativeJpg; + rContext.m_nImportFlags = GraphicFilterImportFlags::SetLogsizeForJpeg; + + if (ImportJPEG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr)) + { + Bitmap& rBitmap = const_cast<Bitmap&>(rContext.m_pGraphic->GetBitmapExRef().GetBitmap()); + rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap); + rContext.m_pStream->Seek(rContext.m_nStreamBegin); + if (bThreads) + rSharedPool.pushTask(std::make_unique<GraphicImportTask>(pTag, rContext)); + else + GraphicImportTask::doImport(rContext); + } + else + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG)) + { + rContext.m_eLinkType = GfxLinkType::NativePng; + + if (vcl::ImportPNG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr, nullptr)) + { + const BitmapEx& rBitmapEx = rContext.m_pGraphic->GetBitmapExRef(); + Bitmap& rBitmap = const_cast<Bitmap&>(rBitmapEx.GetBitmap()); + rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap); + if(rBitmapEx.IsAlpha()) + { + // The separate alpha bitmap causes a number of complications. Not only + // we need to have an extra bitmap access for it, but we also need + // to keep an AlphaMask instance in the context. This is because + // BitmapEx internally keeps Bitmap and not AlphaMask (because the Bitmap + // may be also a mask, not alpha). So BitmapEx::GetAlpha() returns + // a temporary, and direct access to the Bitmap wouldn't work + // with AlphaScopedBitmapAccess. *sigh* + rContext.mAlphaMask = rBitmapEx.GetAlpha(); + rContext.m_pAlphaAccess = std::make_unique<AlphaScopedWriteAccess>(rContext.mAlphaMask); + } + rContext.m_pStream->Seek(rContext.m_nStreamBegin); + if (bThreads) + rSharedPool.pushTask(std::make_unique<GraphicImportTask>(pTag, rContext)); + else + GraphicImportTask::doImport(rContext); + } + else + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } + } + + rSharedPool.waitUntilDone(pTag); + + // Process data after import. + for (auto& rContext : aContexts) + { + if(rContext.m_pAlphaAccess) // Need to move the AlphaMask back to the BitmapEx. + *rContext.m_pGraphic = BitmapEx( rContext.m_pGraphic->GetBitmapExRef().GetBitmap(), rContext.mAlphaMask ); + rContext.m_pAccess.reset(); + rContext.m_pAlphaAccess.reset(); + + if (rContext.m_nStatus == ERRCODE_NONE && (rContext.m_eLinkType != GfxLinkType::NONE) && !rContext.m_pGraphic->GetReaderContext()) + { + std::unique_ptr<sal_uInt8[]> pGraphicContent; + + const sal_uInt64 nStreamEnd = rContext.m_pStream->Tell(); + sal_Int32 nGraphicContentSize = nStreamEnd - rContext.m_nStreamBegin; + + if (nGraphicContentSize > 0) + { + try + { + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + } + catch (const std::bad_alloc&) + { + rContext.m_nStatus = ERRCODE_GRFILTER_TOOBIG; + } + + if (rContext.m_nStatus == ERRCODE_NONE) + { + rContext.m_pStream->Seek(rContext.m_nStreamBegin); + rContext.m_pStream->ReadBytes(pGraphicContent.get(), nGraphicContentSize); + } + } + + if (rContext.m_nStatus == ERRCODE_NONE) + rContext.m_pGraphic->SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, rContext.m_eLinkType)); + } + + if (rContext.m_nStatus != ERRCODE_NONE) + rContext.m_pGraphic = nullptr; + + rGraphics.push_back(rContext.m_pGraphic); + } +} + +void GraphicFilter::MakeGraphicsAvailableThreaded(std::vector<Graphic*>& graphics) +{ + // Graphic::makeAvailable() is not thread-safe. Only the jpeg and png loaders are, so here + // we process only jpeg and png images that also have their stream data, load new Graphic's + // from them and then update the passed objects using them. + std::vector< Graphic* > toLoad; + for(auto graphic : graphics) + { + // Need to use GetSharedGfxLink, to access the pointer without copying. + if(!graphic->isAvailable() && graphic->IsGfxLink() + && graphic->GetSharedGfxLink()->GetDataSize() != 0 + && (graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativeJpg + || graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativePng)) + { + // Graphic objects share internal ImpGraphic, do not process any of those twice. + const auto predicate = [graphic](Graphic* item) { return item->ImplGetImpGraphic() == graphic->ImplGetImpGraphic(); }; + if( std::find_if(toLoad.begin(), toLoad.end(), predicate ) == toLoad.end()) + toLoad.push_back( graphic ); + } + } + if( toLoad.empty()) + return; + std::vector< std::unique_ptr<SvStream>> streams; + for( auto graphic : toLoad ) + { + streams.push_back( std::make_unique<SvMemoryStream>( const_cast<sal_uInt8*>(graphic->GetSharedGfxLink()->GetData()), + graphic->GetSharedGfxLink()->GetDataSize(), StreamMode::READ | StreamMode::WRITE)); + } + std::vector< std::shared_ptr<Graphic>> loadedGraphics; + ImportGraphics(loadedGraphics, std::move(streams)); + assert(loadedGraphics.size() == toLoad.size()); + for( size_t i = 0; i < toLoad.size(); ++i ) + { + if(loadedGraphics[ i ] != nullptr) + toLoad[ i ]->ImplGetImpGraphic()->updateFromLoadedGraphic(loadedGraphics[ i ]->ImplGetImpGraphic()); + } +} + +Graphic GraphicFilter::ImportUnloadedGraphic(SvStream& rIStream, sal_uInt64 sizeLimit, + const Size* pSizeHint) +{ + Graphic aGraphic; + sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW; + GfxLinkType eLinkType = GfxLinkType::NONE; + + ResetLastError(); + + const sal_uInt64 nStreamBegin = rIStream.Tell(); + + rIStream.Seek(nStreamBegin); + + ErrCode nStatus = ImpTestOrFindFormat(u"", rIStream, nFormat); + + rIStream.Seek(nStreamBegin); + sal_uInt32 nStreamLength(rIStream.remainingSize()); + if (sizeLimit && sizeLimit < nStreamLength) + nStreamLength = sizeLimit; + + OUString aFilterName = pConfig->GetImportFilterName(nFormat); + + std::unique_ptr<sal_uInt8[]> pGraphicContent; + sal_Int32 nGraphicContentSize = 0; + + // read graphic + { + if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF)) + { + eLinkType = GfxLinkType::NativeGif; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG)) + { + // check if this PNG contains a GIF chunk! + pGraphicContent = vcl::PngImageReader::getMicrosoftGifChunk(rIStream, &nGraphicContentSize); + if( pGraphicContent ) + eLinkType = GfxLinkType::NativeGif; + else + eLinkType = GfxLinkType::NativePng; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG)) + { + eLinkType = GfxLinkType::NativeJpg; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVG)) + { + bool bOkay(false); + + if (nStreamLength > 0) + { + std::vector<sal_uInt8> aTwoBytes(2); + rIStream.ReadBytes(aTwoBytes.data(), 2); + rIStream.Seek(nStreamBegin); + + if (aTwoBytes[0] == 0x1F && aTwoBytes[1] == 0x8B) + { + SvMemoryStream aMemStream; + ZCodec aCodec; + tools::Long nMemoryLength; + + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true); + nMemoryLength = aCodec.Decompress(rIStream, aMemStream); + aCodec.EndCompression(); + + if (!rIStream.GetError() && nMemoryLength >= 0) + { + nGraphicContentSize = nMemoryLength; + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + + aMemStream.Seek(STREAM_SEEK_TO_BEGIN); + aMemStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize); + + bOkay = true; + } + } + else + { + nGraphicContentSize = nStreamLength; + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + rIStream.ReadBytes(pGraphicContent.get(), nStreamLength); + + bOkay = true; + } + } + + if (bOkay) + { + eLinkType = GfxLinkType::NativeSvg; + } + else + { + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP)) + { + eLinkType = GfxLinkType::NativeBmp; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_MOV)) + { + eLinkType = GfxLinkType::NativeMov; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WMF) || + aFilterName.equalsIgnoreAsciiCase(IMP_EMF)) + { + nGraphicContentSize = nStreamLength; + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + rIStream.Seek(nStreamBegin); + if (ZCodec::IsZCompressed(rIStream)) + { + ZCodec aCodec; + SvMemoryStream aMemStream; + tools::Long nMemoryLength; + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true); + nMemoryLength = aCodec.Decompress(rIStream, aMemStream); + aCodec.EndCompression(); + + if (!rIStream.GetError() && nMemoryLength >= 0) + { + nGraphicContentSize = nMemoryLength; + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + + aMemStream.Seek(STREAM_SEEK_TO_BEGIN); + aMemStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize); + } + } + else + { + rIStream.ReadBytes(pGraphicContent.get(), nStreamLength); + } + if (!rIStream.GetError()) + { + eLinkType = GfxLinkType::NativeWmf; + } + else + { + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } + else if (aFilterName == IMP_PDF) + { + eLinkType = GfxLinkType::NativePdf; + } + else if (aFilterName == IMP_TIFF) + { + eLinkType = GfxLinkType::NativeTif; + } + else if (aFilterName == IMP_PICT) + { + eLinkType = GfxLinkType::NativePct; + } + else if (aFilterName == IMP_MET) + { + eLinkType = GfxLinkType::NativeMet; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP)) + { + if(supportNativeWebp()) + eLinkType = GfxLinkType::NativeWebp; + else + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else + { + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } + + if (nStatus == ERRCODE_NONE && eLinkType != GfxLinkType::NONE) + { + if (!pGraphicContent) + { + nGraphicContentSize = nStreamLength; + + if (nGraphicContentSize > 0) + { + try + { + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + } + catch (const std::bad_alloc&) + { + nStatus = ERRCODE_GRFILTER_TOOBIG; + } + + if (nStatus == ERRCODE_NONE) + { + rIStream.Seek(nStreamBegin); + nGraphicContentSize = rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize); + } + } + } + + if( nStatus == ERRCODE_NONE ) + { + bool bAnimated = false; + Size aLogicSize; + if (eLinkType == GfxLinkType::NativeGif) + { + SvMemoryStream aMemoryStream(pGraphicContent.get(), nGraphicContentSize, StreamMode::READ); + bAnimated = IsGIFAnimated(aMemoryStream, aLogicSize); + if (!pSizeHint && aLogicSize.getWidth() && aLogicSize.getHeight()) + { + pSizeHint = &aLogicSize; + } + } + aGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType)); + aGraphic.ImplGetImpGraphic()->setPrepared(bAnimated, pSizeHint); + } + } + + // Set error code or try to set native buffer + if (nStatus != ERRCODE_NONE) + ImplSetError(nStatus, &rIStream); + if (nStatus != ERRCODE_NONE || eLinkType == GfxLinkType::NONE) + rIStream.Seek(nStreamBegin); + + return aGraphic; +} + +ErrCode GraphicFilter::readGIF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportGIF(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativeGif; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPNG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::unique_ptr<sal_uInt8[]> & rpGraphicContent, + sal_Int32& rGraphicContentSize) +{ + ErrCode aReturnCode = ERRCODE_NONE; + + // check if this PNG contains a GIF chunk! + rpGraphicContent = vcl::PngImageReader::getMicrosoftGifChunk(rStream, &rGraphicContentSize); + if( rpGraphicContent ) + { + SvMemoryStream aIStrm(rpGraphicContent.get(), rGraphicContentSize, StreamMode::READ); + ImportGIF(aIStrm, rGraphic); + rLinkType = GfxLinkType::NativeGif; + return aReturnCode; + } + + // PNG has no GIF chunk + vcl::PngImageReader aPNGReader(rStream); + BitmapEx aBitmapEx(aPNGReader.read()); + if (!aBitmapEx.IsEmpty()) + { + rGraphic = aBitmapEx; + rLinkType = GfxLinkType::NativePng; + } + else + aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + + return aReturnCode; +} + +ErrCode GraphicFilter::readJPEG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, GraphicFilterImportFlags nImportFlags) +{ + ErrCode aReturnCode = ERRCODE_NONE; + + // set LOGSIZE flag always, if not explicitly disabled + // (see #90508 and #106763) + if (!(nImportFlags & GraphicFilterImportFlags::DontSetLogsizeForJpeg)) + { + nImportFlags |= GraphicFilterImportFlags::SetLogsizeForJpeg; + } + + sal_uInt64 nPosition = rStream.Tell(); + if (!ImportJPEG(rStream, rGraphic, nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr)) + aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + else + { + Bitmap& rBitmap = const_cast<Bitmap&>(rGraphic.GetBitmapExRef().GetBitmap()); + BitmapScopedWriteAccess pWriteAccess(rBitmap); + rStream.Seek(nPosition); + if (!ImportJPEG(rStream, rGraphic, nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, &pWriteAccess)) + aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + else + rLinkType = GfxLinkType::NativeJpg; + } + + return aReturnCode; +} + +ErrCode GraphicFilter::readSVG(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::unique_ptr<sal_uInt8[]> & rpGraphicContent, + sal_Int32& rGraphicContentSize) +{ + ErrCode aReturnCode = ERRCODE_NONE; + + const sal_uInt64 nStreamPosition(rStream.Tell()); + const sal_uInt64 nStreamLength(rStream.remainingSize()); + + bool bOkay(false); + + if (nStreamLength > 0) + { + std::vector<sal_uInt8> aTwoBytes(2); + rStream.ReadBytes(aTwoBytes.data(), 2); + rStream.Seek(nStreamPosition); + + if (aTwoBytes[0] == 0x1F && aTwoBytes[1] == 0x8B) + { + SvMemoryStream aMemStream; + ZCodec aCodec; + tools::Long nMemoryLength; + + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true); + nMemoryLength = aCodec.Decompress(rStream, aMemStream); + aCodec.EndCompression(); + + if (!rStream.GetError() && nMemoryLength >= 0) + { + VectorGraphicDataArray aNewData(nMemoryLength); + aMemStream.Seek(STREAM_SEEK_TO_BEGIN); + aMemStream.ReadBytes(aNewData.getArray(), nMemoryLength); + + // Make a uncompressed copy for GfxLink + rGraphicContentSize = nMemoryLength; + rpGraphicContent.reset(new sal_uInt8[rGraphicContentSize]); + std::copy(std::cbegin(aNewData), std::cend(aNewData), rpGraphicContent.get()); + + if (!aMemStream.GetError()) + { + BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength()); + auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Svg); + rGraphic = Graphic(aVectorGraphicDataPtr); + bOkay = true; + } + } + } + else + { + VectorGraphicDataArray aNewData(nStreamLength); + rStream.ReadBytes(aNewData.getArray(), nStreamLength); + + if (!rStream.GetError()) + { + BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength()); + auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Svg); + rGraphic = Graphic(aVectorGraphicDataPtr); + bOkay = true; + } + } + } + + if (bOkay) + { + rLinkType = GfxLinkType::NativeSvg; + } + else + { + aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + } + + return aReturnCode; +} + +ErrCode GraphicFilter::readXBM(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportXBM(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readXPM(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportXPM(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readWMF_EMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, VectorGraphicDataType eType) +{ + // use new UNO API service, do not directly import but create a + // Graphic that contains the original data and decomposes to + // primitives on demand + sal_uInt32 nStreamLength(rStream.remainingSize()); + SvStream* aNewStream = &rStream; + ErrCode aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + SvMemoryStream aMemStream; + if (ZCodec::IsZCompressed(rStream)) + { + ZCodec aCodec; + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true); + auto nDecompressLength = aCodec.Decompress(rStream, aMemStream); + aCodec.EndCompression(); + aMemStream.Seek(STREAM_SEEK_TO_BEGIN); + if (nDecompressLength >= 0) + { + nStreamLength = nDecompressLength; + aNewStream = &aMemStream; + } + } + VectorGraphicDataArray aNewData(nStreamLength); + aNewStream->ReadBytes(aNewData.getArray(), nStreamLength); + if (!aNewStream->GetError()) + { + const VectorGraphicDataType aDataType(eType); + BinaryDataContainer aDataContainer(reinterpret_cast<const sal_uInt8*>(aNewData.getConstArray()), aNewData.getLength()); + + auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType); + + rGraphic = Graphic(aVectorGraphicDataPtr); + rLinkType = GfxLinkType::NativeWmf; + aReturnCode = ERRCODE_NONE; + } + + return aReturnCode; +} + +ErrCode GraphicFilter::readWMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + return readWMF_EMF(rStream, rGraphic, rLinkType,VectorGraphicDataType::Wmf); +} + +ErrCode GraphicFilter::readEMF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + return readWMF_EMF(rStream, rGraphic, rLinkType, VectorGraphicDataType::Emf); +} + +ErrCode GraphicFilter::readPDF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (vcl::ImportPDF(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativePdf; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readTIFF(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportTiffGraphicImport(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativeTif; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readWithTypeSerializer(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType, std::u16string_view aFilterName) +{ + ErrCode aReturnCode = ERRCODE_GRFILTER_FILTERERROR; + + // SV internal filters for import bitmaps and MetaFiles + TypeSerializer aSerializer(rStream); + aSerializer.readGraphic(rGraphic); + + if (!rStream.GetError()) + { + if (o3tl::equalsIgnoreAsciiCase(aFilterName, u"" IMP_MOV)) + { + rGraphic.SetDefaultType(); + rStream.Seek(STREAM_SEEK_TO_END); + rLinkType = GfxLinkType::NativeMov; + } + aReturnCode = ERRCODE_NONE; + } + return aReturnCode; +} + +ErrCode GraphicFilter::readBMP(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (BmpReader(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativeBmp; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readTGA(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportTgaGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPICT(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportPictGraphic(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativePct; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readMET(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportMetGraphic(rStream, rGraphic)) + { + rLinkType = GfxLinkType::NativeMet; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readRAS(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportRasGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPCX(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportPcxGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readEPS(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportEpsGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPSD(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportPsdGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPCD(SvStream & rStream, Graphic & rGraphic) +{ + std::unique_ptr<FilterConfigItem> pFilterConfigItem; + if (!utl::ConfigManager::IsFuzzing()) + { + OUString aFilterConfigPath( "Office.Common/Filter/Graphic/Import/PCD" ); + pFilterConfigItem = std::make_unique<FilterConfigItem>(aFilterConfigPath); + } + + if (ImportPcdGraphic(rStream, rGraphic, pFilterConfigItem.get())) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readPBM(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportPbmGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readDXF(SvStream & rStream, Graphic & rGraphic) +{ + if (ImportDxfGraphic(rStream, rGraphic)) + return ERRCODE_NONE; + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::readWEBP(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportWebpGraphic(rStream, rGraphic)) + { + if(supportNativeWebp()) + rLinkType = GfxLinkType::NativeWebp; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + +ErrCode GraphicFilter::ImportGraphic(Graphic& rGraphic, std::u16string_view rPath, SvStream& rIStream, + sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat, GraphicFilterImportFlags nImportFlags) +{ + OUString aFilterName; + sal_uInt64 nStreamBegin; + ErrCode nStatus; + GfxLinkType eLinkType = GfxLinkType::NONE; + const bool bLinkSet = rGraphic.IsGfxLink(); + + std::unique_ptr<sal_uInt8[]> pGraphicContent; + sal_Int32 nGraphicContentSize = 0; + + ResetLastError(); + + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + bool bDummyContext = rGraphic.IsDummyContext(); + if( !pContext || bDummyContext ) + { + if( bDummyContext ) + { + rGraphic.SetDummyContext( false ); + nStreamBegin = 0; + } + else + nStreamBegin = rIStream.Tell(); + + nStatus = ImpTestOrFindFormat( rPath, rIStream, nFormat ); + // if pending, return ERRCODE_NONE in order to request more bytes + if( rIStream.GetError() == ERRCODE_IO_PENDING ) + { + rGraphic.SetDummyContext(true); + rIStream.ResetError(); + rIStream.Seek( nStreamBegin ); + return ImplSetError( ERRCODE_NONE ); + } + + rIStream.Seek( nStreamBegin ); + + if( ( nStatus != ERRCODE_NONE ) || rIStream.GetError() ) + return ImplSetError( ( nStatus != ERRCODE_NONE ) ? nStatus : ERRCODE_GRFILTER_OPENERROR, &rIStream ); + + if( pDeterminedFormat ) + *pDeterminedFormat = nFormat; + + aFilterName = pConfig->GetImportFilterName( nFormat ); + } + else + { + aFilterName = pContext->GetUpperFilterName(); + + nStreamBegin = 0; + nStatus = ERRCODE_NONE; + } + + // read graphic + { + if (aFilterName.equalsIgnoreAsciiCase(IMP_GIF)) + { + nStatus = readGIF(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG)) + { + nStatus = readPNG(rIStream, rGraphic, eLinkType, pGraphicContent, nGraphicContentSize); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG)) + { + nStatus = readJPEG(rIStream, rGraphic, eLinkType, nImportFlags); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVG)) + { + nStatus = readSVG(rIStream, rGraphic, eLinkType, pGraphicContent, nGraphicContentSize); + } + else if( aFilterName.equalsIgnoreAsciiCase( IMP_XBM ) ) + { + nStatus = readXBM(rIStream, rGraphic); + } + else if( aFilterName.equalsIgnoreAsciiCase( IMP_XPM ) ) + { + nStatus = readXPM(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_BMP)) + { + nStatus = readBMP(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_SVMETAFILE)) + { + nStatus = readWithTypeSerializer(rIStream, rGraphic, eLinkType, aFilterName); + } + else if( aFilterName.equalsIgnoreAsciiCase(IMP_MOV)) + { + nStatus = readWithTypeSerializer(rIStream, rGraphic, eLinkType, aFilterName); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WMF)) + { + nStatus = readWMF(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_EMF)) + { + nStatus = readEMF(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PDF)) + { + nStatus = readPDF(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_TIFF) ) + { + nStatus = readTIFF(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_TGA) ) + { + nStatus = readTGA(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PICT)) + { + nStatus = readPICT(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_MET)) + { + nStatus = readMET(rIStream, rGraphic, eLinkType); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_RAS)) + { + nStatus = readRAS(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PCX)) + { + nStatus = readPCX(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_EPS)) + { + nStatus = readEPS(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PSD)) + { + nStatus = readPSD(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PCD)) + { + nStatus = readPCD(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PBM)) + { + nStatus = readPBM(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_DXF)) + { + nStatus = readDXF(rIStream, rGraphic); + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP)) + { + nStatus = readWEBP(rIStream, rGraphic, eLinkType); + } + else + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + + if( nStatus == ERRCODE_NONE && ( eLinkType != GfxLinkType::NONE ) && !rGraphic.GetReaderContext() && !bLinkSet ) + { + if (!pGraphicContent) + { + const sal_uInt64 nStreamEnd = rIStream.Tell(); + nGraphicContentSize = nStreamEnd - nStreamBegin; + + if (nGraphicContentSize > 0) + { + try + { + pGraphicContent.reset(new sal_uInt8[nGraphicContentSize]); + } + catch (const std::bad_alloc&) + { + nStatus = ERRCODE_GRFILTER_TOOBIG; + } + + if( nStatus == ERRCODE_NONE ) + { + rIStream.Seek(nStreamBegin); + rIStream.ReadBytes(pGraphicContent.get(), nGraphicContentSize); + } + } + } + if( nStatus == ERRCODE_NONE ) + { + rGraphic.SetGfxLink(std::make_shared<GfxLink>(std::move(pGraphicContent), nGraphicContentSize, eLinkType)); + } + } + + // Set error code or try to set native buffer + if( nStatus != ERRCODE_NONE ) + { + ImplSetError( nStatus, &rIStream ); + rIStream.Seek( nStreamBegin ); + rGraphic.Clear(); + } + + return nStatus; +} + +ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, const INetURLObject& rPath, + sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData ) +{ + SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" ); + ErrCode nRetValue = ERRCODE_GRFILTER_FORMATERROR; + SAL_WARN_IF( rPath.GetProtocol() == INetProtocol::NotValid, "vcl.filter", "GraphicFilter::ExportGraphic() : ProtType == INetProtocol::NotValid" ); + + OUString aMainUrl(rPath.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + bool bAlreadyExists = utl::UCBContentHelper::IsDocument(aMainUrl); + + std::unique_ptr<SvStream> xStream(::utl::UcbStreamHelper::CreateStream( aMainUrl, StreamMode::WRITE | StreamMode::TRUNC )); + if (xStream) + { + nRetValue = ExportGraphic( rGraphic, aMainUrl, *xStream, nFormat, pFilterData ); + xStream.reset(); + + if( ( ERRCODE_NONE != nRetValue ) && !bAlreadyExists ) + utl::UCBContentHelper::Kill(aMainUrl); + } + return nRetValue; +} + +ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, std::u16string_view rPath, + SvStream& rOStm, sal_uInt16 nFormat, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData ) +{ + SAL_INFO( "vcl.filter", "GraphicFilter::ExportGraphic() (thb)" ); + sal_uInt16 nFormatCount = GetExportFormatCount(); + + ResetLastError(); + + if( nFormat == GRFILTER_FORMAT_DONTKNOW ) + { + INetURLObject aURL( rPath ); + OUString aExt( aURL.GetFileExtension().toAsciiUpperCase() ); + + for( sal_uInt16 i = 0; i < nFormatCount; i++ ) + { + if ( pConfig->GetExportFormatExtension( i ).equalsIgnoreAsciiCase( aExt ) ) + { + nFormat=i; + break; + } + } + } + if( nFormat >= nFormatCount ) + return ImplSetError( ERRCODE_GRFILTER_FORMATERROR ); + + FilterConfigItem aConfigItem( pFilterData ); + OUString aFilterName( pConfig->GetExportFilterName( nFormat ) ); + ErrCode nStatus = ERRCODE_NONE; + GraphicType eType; + Graphic aGraphic = ImpGetScaledGraphic( rGraphic, aConfigItem ); + eType = aGraphic.GetType(); + + if( pConfig->IsExportPixelFormat( nFormat ) ) + { + if( eType != GraphicType::Bitmap ) + { + Size aSizePixel; + sal_uLong nBitsPerPixel,nNeededMem,nMaxMem; + ScopedVclPtrInstance< VirtualDevice > aVirDev; + + nMaxMem = 1024; + nMaxMem *= 1024; // In Bytes + + // Calculate how big the image would normally be: + aSizePixel=aVirDev->LogicToPixel(aGraphic.GetPrefSize(),aGraphic.GetPrefMapMode()); + + // Calculate how much memory the image will take up + nBitsPerPixel=aVirDev->GetBitCount(); + nNeededMem=(static_cast<sal_uLong>(aSizePixel.Width())*static_cast<sal_uLong>(aSizePixel.Height())*nBitsPerPixel+7)/8; + + // is the image larger than available memory? + if (nMaxMem<nNeededMem) + { + double fFak=sqrt(static_cast<double>(nMaxMem)/static_cast<double>(nNeededMem)); + aSizePixel.setWidth(static_cast<sal_uLong>(static_cast<double>(aSizePixel.Width())*fFak) ); + aSizePixel.setHeight(static_cast<sal_uLong>(static_cast<double>(aSizePixel.Height())*fFak) ); + } + + aVirDev->SetMapMode(MapMode(MapUnit::MapPixel)); + aVirDev->SetOutputSizePixel(aSizePixel); + Graphic aGraphic2=aGraphic; + aGraphic2.Draw(*aVirDev, Point(0, 0), aSizePixel); // this changes the MapMode + aVirDev->SetMapMode(MapMode(MapUnit::MapPixel)); + aGraphic=Graphic(aVirDev->GetBitmapEx(Point(0,0),aSizePixel)); + } + } + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + if( ERRCODE_NONE == nStatus ) + { + if( aFilterName.equalsIgnoreAsciiCase( EXP_BMP ) ) + { + if (!BmpWriter(rOStm, aGraphic, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + if (rOStm.GetError()) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if (aFilterName.equalsIgnoreAsciiCase(EXP_TIFF)) + { + if (!ExportTiffGraphicImport(rOStm, aGraphic, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if (aFilterName.equalsIgnoreAsciiCase(EXP_GIF)) + { + if (!ExportGifGraphic(rOStm, aGraphic, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVMETAFILE ) ) + { + sal_Int32 nVersion = aConfigItem.ReadInt32( "Version", 0 ) ; + if ( nVersion ) + rOStm.SetVersion( nVersion ); + + // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically + GDIMetaFile aMTF(aGraphic.GetGDIMetaFile()); + + SvmWriter aWriter( rOStm ); + aWriter.Write( aMTF ); + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if ( aFilterName.equalsIgnoreAsciiCase( EXP_WMF ) ) + { + bool bDone(false); + + // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly? + auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData()); + + bool bIsEMF = rGraphic.GetGfxLink().IsEMF(); + + // VectorGraphicDataType::Wmf means WMF or EMF, allow direct write in the WMF case + // only. + if (rVectorGraphicDataPtr + && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Wmf + && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty() + && !bIsEMF) + { + auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer(); + rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize()); + + if (rOStm.GetError()) + { + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else + { + bDone = true; + } + } + + if (!bDone) + { + // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically + if (!ConvertGraphicToWMF(aGraphic, rOStm, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if (rOStm.GetError()) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + } + else if ( aFilterName.equalsIgnoreAsciiCase( EXP_EMF ) ) + { + bool bDone(false); + + // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly? + auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData()); + + if (rVectorGraphicDataPtr + && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Emf + && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty()) + { + auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer(); + rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize()); + + if (rOStm.GetError()) + { + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else + { + bDone = true; + } + } + + if (!bDone) + { + // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically + if (!ConvertGDIMetaFileToEMF(aGraphic.GetGDIMetaFile(), rOStm)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if (rOStm.GetError()) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + } + else if( aFilterName.equalsIgnoreAsciiCase( EXP_JPEG ) ) + { + bool bExportedGrayJPEG = false; + if( !ExportJPEG( rOStm, aGraphic, pFilterData, &bExportedGrayJPEG ) ) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if (aFilterName.equalsIgnoreAsciiCase(EXP_EPS)) + { + if (!ExportEpsGraphic(rOStm, aGraphic, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if (rOStm.GetError()) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if ( aFilterName.equalsIgnoreAsciiCase( EXP_PNG ) ) + { + vcl::PNGWriter aPNGWriter( aGraphic.GetBitmapEx(), pFilterData ); + if ( pFilterData ) + { + for ( const auto& rPropVal : *pFilterData ) + { + if ( rPropVal.Name == "AdditionalChunks" ) + { + css::uno::Sequence< css::beans::PropertyValue > aAdditionalChunkSequence; + if ( rPropVal.Value >>= aAdditionalChunkSequence ) + { + for ( const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence) ) + { + if ( rAdditionalChunk.Name.getLength() == 4 ) + { + sal_uInt32 nChunkType = 0; + for ( sal_Int32 k = 0; k < 4; k++ ) + { + nChunkType <<= 8; + nChunkType |= static_cast<sal_uInt8>(rAdditionalChunk.Name[ k ]); + } + css::uno::Sequence< sal_Int8 > aByteSeq; + if ( rAdditionalChunk.Value >>= aByteSeq ) + { + std::vector< vcl::PNGWriter::ChunkData >& rChunkData = aPNGWriter.GetChunks(); + if ( !rChunkData.empty() ) + { + sal_uInt32 nChunkLen = aByteSeq.getLength(); + + vcl::PNGWriter::ChunkData aChunkData; + aChunkData.nType = nChunkType; + if ( nChunkLen ) + { + aChunkData.aData.resize( nChunkLen ); + memcpy( aChunkData.aData.data(), aByteSeq.getConstArray(), nChunkLen ); + } + std::vector< vcl::PNGWriter::ChunkData >::iterator aIter = rChunkData.end() - 1; + rChunkData.insert( aIter, aChunkData ); + } + } + } + } + } + } + } + } + aPNGWriter.Write( rOStm ); + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else if( aFilterName.equalsIgnoreAsciiCase( EXP_SVG ) ) + { + bool bDone(false); + + // do we have a native Vector Graphic Data RenderGraphic, whose data can be written directly? + auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData()); + + if (rVectorGraphicDataPtr + && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg + && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty()) + { + auto & aDataContainer = rVectorGraphicDataPtr->getBinaryDataContainer(); + rOStm.WriteBytes(aDataContainer.getData(), aDataContainer.getSize()); + + if( rOStm.GetError() ) + { + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else + { + bDone = true; + } + } + + if( !bDone ) + { + // do the normal GDIMetaFile export instead + try + { + css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + + css::uno::Reference< css::xml::sax::XDocumentHandler > xSaxWriter( + css::xml::sax::Writer::create( xContext ), css::uno::UNO_QUERY_THROW); + css::uno::Sequence< css::uno::Any > aArguments{ css::uno::Any( + aConfigItem.GetFilterData()) }; + css::uno::Reference< css::svg::XSVGWriter > xSVGWriter( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext( "com.sun.star.svg.SVGWriter", aArguments, xContext), + css::uno::UNO_QUERY ); + if( xSaxWriter.is() && xSVGWriter.is() ) + { + css::uno::Reference< css::io::XActiveDataSource > xActiveDataSource( + xSaxWriter, css::uno::UNO_QUERY ); + + if( xActiveDataSource.is() ) + { + const css::uno::Reference< css::uno::XInterface > xStmIf( + static_cast< ::cppu::OWeakObject* >( new ImpFilterOutputStream( rOStm ) ) ); + + SvMemoryStream aMemStm( 65535, 65535 ); + + // #i119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically + SvmWriter aWriter( aMemStm ); + aWriter.Write( aGraphic.GetGDIMetaFile() ); + + xActiveDataSource->setOutputStream( css::uno::Reference< css::io::XOutputStream >( + xStmIf, css::uno::UNO_QUERY ) ); + css::uno::Sequence< sal_Int8 > aMtfSeq( static_cast<sal_Int8 const *>(aMemStm.GetData()), aMemStm.Tell() ); + xSVGWriter->write( xSaxWriter, aMtfSeq ); + } + } + } + catch(const css::uno::Exception&) + { + nStatus = ERRCODE_GRFILTER_IOERROR; + } + } + } + else if (aFilterName.equalsIgnoreAsciiCase(EXP_WEBP)) + { + if (!ExportWebpGraphic(rOStm, aGraphic, &aConfigItem)) + nStatus = ERRCODE_GRFILTER_FORMATERROR; + + if( rOStm.GetError() ) + nStatus = ERRCODE_GRFILTER_IOERROR; + } + else + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + if( nStatus != ERRCODE_NONE ) + { + ImplSetError( nStatus, &rOStm ); + } + return nStatus; +} + + +void GraphicFilter::ResetLastError() +{ + mxErrorEx = ERRCODE_NONE; +} + +Link<ConvertData&,bool> GraphicFilter::GetFilterCallback() const +{ + Link<ConvertData&,bool> aLink( LINK( const_cast<GraphicFilter*>(this), GraphicFilter, FilterCallback ) ); + return aLink; +} + +IMPL_LINK( GraphicFilter, FilterCallback, ConvertData&, rData, bool ) +{ + bool bRet = false; + + sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW; + OUString aShortName; + css::uno::Sequence< css::beans::PropertyValue > aFilterData; + switch( rData.mnFormat ) + { + case ConvertDataFormat::BMP: aShortName = BMP_SHORTNAME; break; + case ConvertDataFormat::GIF: aShortName = GIF_SHORTNAME; break; + case ConvertDataFormat::JPG: aShortName = JPG_SHORTNAME; break; + case ConvertDataFormat::MET: aShortName = MET_SHORTNAME; break; + case ConvertDataFormat::PCT: aShortName = PCT_SHORTNAME; break; + case ConvertDataFormat::PNG: aShortName = PNG_SHORTNAME; break; + case ConvertDataFormat::SVM: aShortName = SVM_SHORTNAME; break; + case ConvertDataFormat::TIF: aShortName = TIF_SHORTNAME; break; + case ConvertDataFormat::WMF: aShortName = WMF_SHORTNAME; break; + case ConvertDataFormat::EMF: aShortName = EMF_SHORTNAME; break; + case ConvertDataFormat::SVG: aShortName = SVG_SHORTNAME; break; + case ConvertDataFormat::WEBP: aShortName = WEBP_SHORTNAME; break; + + default: + break; + } + if( GraphicType::NONE == rData.maGraphic.GetType() || rData.maGraphic.GetReaderContext() ) // Import + { + // Import + nFormat = GetImportFormatNumberForShortName( aShortName ); + bRet = ImportGraphic( rData.maGraphic, u"", rData.mrStm, nFormat ) == ERRCODE_NONE; + } + else if( !aShortName.isEmpty() ) + { + // Export +#if defined(IOS) || defined(ANDROID) + if (aShortName == PNG_SHORTNAME) + { + aFilterData.realloc(aFilterData.getLength() + 1); + auto pFilterData = aFilterData.getArray(); + pFilterData[aFilterData.getLength() - 1].Name = "Compression"; + // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed. + pFilterData[aFilterData.getLength() - 1].Value <<= static_cast<sal_Int32>(1); + } +#endif + nFormat = GetExportFormatNumberForShortName( aShortName ); + bRet = ExportGraphic( rData.maGraphic, u"", rData.mrStm, nFormat, &aFilterData ) == ERRCODE_NONE; + } + + return bRet; +} + +namespace +{ + class StandardGraphicFilter + { + public: + StandardGraphicFilter() + { + m_aFilter.GetImportFormatCount(); + } + GraphicFilter m_aFilter; + }; +} + +GraphicFilter& GraphicFilter::GetGraphicFilter() +{ + static StandardGraphicFilter gStandardFilter; + return gStandardFilter.m_aFilter; +} + +ErrCode GraphicFilter::LoadGraphic( const OUString &rPath, const OUString &rFilterName, + Graphic& rGraphic, GraphicFilter* pFilter, + sal_uInt16* pDeterminedFormat ) +{ + if ( !pFilter ) + pFilter = &GetGraphicFilter(); + + const sal_uInt16 nFilter = !rFilterName.isEmpty() && pFilter->GetImportFormatCount() + ? pFilter->GetImportFormatNumber( rFilterName ) + : GRFILTER_FORMAT_DONTKNOW; + + INetURLObject aURL( rPath ); + if ( aURL.HasError() ) + { + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetSmartURL( rPath ); + } + + std::unique_ptr<SvStream> pStream; + if ( INetProtocol::File != aURL.GetProtocol() ) + pStream = ::utl::UcbStreamHelper::CreateStream( rPath, StreamMode::READ ); + + ErrCode nRes = ERRCODE_NONE; + if ( !pStream ) + nRes = pFilter->ImportGraphic( rGraphic, aURL, nFilter, pDeterminedFormat ); + else + nRes = pFilter->ImportGraphic( rGraphic, rPath, *pStream, nFilter, pDeterminedFormat ); + +#ifdef DBG_UTIL + OUString aReturnString; + + if (nRes == ERRCODE_GRFILTER_OPENERROR) + aReturnString="open error"; + else if (nRes == ERRCODE_GRFILTER_IOERROR) + aReturnString="IO error"; + else if (nRes == ERRCODE_GRFILTER_FORMATERROR) + aReturnString="format error"; + else if (nRes == ERRCODE_GRFILTER_VERSIONERROR) + aReturnString="version error"; + else if (nRes == ERRCODE_GRFILTER_FILTERERROR) + aReturnString="filter error"; + else if (nRes == ERRCODE_GRFILTER_TOOBIG) + aReturnString="graphic is too big"; + + SAL_INFO_IF( nRes, "vcl.filter", "Problem importing graphic " << rPath << ". Reason: " << aReturnString ); +#endif + + return nRes; +} + +ErrCode GraphicFilter::compressAsPNG(const Graphic& rGraphic, SvStream& rOutputStream) +{ + css::uno::Sequence< css::beans::PropertyValue > aFilterData{ comphelper::makePropertyValue( + "Compression", sal_uInt32(9)) }; + + sal_uInt16 nFilterFormat = GetExportFormatNumberForShortName(u"PNG"); + return ExportGraphic(rGraphic, u"", rOutputStream, nFilterFormat, &aFilterData); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/graphicfilter2.cxx b/vcl/source/filter/graphicfilter2.cxx new file mode 100644 index 000000000..c02e9d557 --- /dev/null +++ b/vcl/source/filter/graphicfilter2.cxx @@ -0,0 +1,1229 @@ +/* -*- 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 <string.h> +#include <tools/stream.hxx> +#include <tools/fract.hxx> +#include <tools/urlobj.hxx> +#include <tools/zcodec.hxx> +#include <vcl/TypeSerializer.hxx> +#include <vcl/outdev.hxx> +#include <vcl/graphicfilter.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <filter/WebpReader.hxx> +#include "graphicfilter_internal.hxx" + +#define DATA_SIZE 640 +constexpr sal_uInt32 EMF_CHECK_SIZE = 44; +constexpr sal_uInt32 EMR_HEADER = 0x00000001; +constexpr sal_uInt32 ENHMETA_SIGNATURE = 0x464d4520; + +GraphicDescriptor::GraphicDescriptor( const INetURLObject& rPath ) : + pFileStm( ::utl::UcbStreamHelper::CreateStream( rPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::READ ).release() ), + aPathExt( rPath.GetFileExtension().toAsciiLowerCase() ), + bOwnStream( true ) +{ + ImpConstruct(); +} + +GraphicDescriptor::GraphicDescriptor( SvStream& rInStream, const OUString* pPath) : + pFileStm ( &rInStream ), + bOwnStream ( false ) +{ + ImpConstruct(); + + if ( pPath ) + { + INetURLObject aURL( *pPath ); + aPathExt = aURL.GetFileExtension().toAsciiLowerCase(); + } +} + +GraphicDescriptor::~GraphicDescriptor() +{ + if ( bOwnStream ) + delete pFileStm; +} + +bool GraphicDescriptor::Detect( bool bExtendedInfo ) +{ + bool bRet = false; + if ( pFileStm && !pFileStm->GetError() ) + { + SvStream& rStm = *pFileStm; + SvStreamEndian nOldFormat = rStm.GetEndian(); + + if ( ImpDetectGIF( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectJPG( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectBMP( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPNG( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectTIF( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPCX( rStm ) ) bRet = true; + else if ( ImpDetectDXF( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectMET( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectSVM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectWMF( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectEMF( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectSVG( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPCT( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectXBM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectXPM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPBM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPGM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPPM( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectRAS( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectTGA( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPSD( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectEPS( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectPCD( rStm, bExtendedInfo ) ) bRet = true; + else if ( ImpDetectWEBP( rStm, bExtendedInfo ) ) bRet = true; + + rStm.SetEndian( nOldFormat ); + } + return bRet; +} + +void GraphicDescriptor::ImpConstruct() +{ + nFormat = GraphicFileFormat::NOT; + nBitsPerPixel = 0; + nPlanes = 0; + mnNumberOfImageComponents = 0; + bIsTransparent = false; + bIsAlpha = false; +} + +bool GraphicDescriptor::ImpDetectBMP( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt16 nTemp16 = 0; + bool bRet = false; + sal_Int32 nStmPos = rStm.Tell(); + + rStm.SetEndian( SvStreamEndian::LITTLE ); + rStm.ReadUInt16( nTemp16 ); + + // OS/2-BitmapArray + if ( nTemp16 == 0x4142 ) + { + rStm.SeekRel( 0x0c ); + rStm.ReadUInt16( nTemp16 ); + } + + // Bitmap + if ( nTemp16 == 0x4d42 ) + { + nFormat = GraphicFileFormat::BMP; + bRet = true; + + if ( bExtendedInfo ) + { + sal_uInt32 nTemp32; + sal_uInt32 nCompression; + + // up to first info + rStm.SeekRel( 0x10 ); + + // Pixel width + rStm.ReadUInt32( nTemp32 ); + aPixSize.setWidth( nTemp32 ); + + // Pixel height + rStm.ReadUInt32( nTemp32 ); + aPixSize.setHeight( nTemp32 ); + + // Planes + rStm.ReadUInt16( nTemp16 ); + nPlanes = nTemp16; + + // BitCount + rStm.ReadUInt16( nTemp16 ); + nBitsPerPixel = nTemp16; + + // Compression + rStm.ReadUInt32( nTemp32 ); + nCompression = nTemp32; + + // logical width + rStm.SeekRel( 4 ); + rStm.ReadUInt32( nTemp32 ); + sal_uInt32 nXPelsPerMeter = 0; + if ( nTemp32 ) + { + aLogSize.setWidth( ( aPixSize.Width() * 100000 ) / nTemp32 ); + nXPelsPerMeter = nTemp32; + } + + // logical height + rStm.ReadUInt32( nTemp32 ); + sal_uInt32 nYPelsPerMeter = 0; + if ( nTemp32 ) + { + aLogSize.setHeight( ( aPixSize.Height() * 100000 ) / nTemp32 ); + nYPelsPerMeter = nTemp32; + } + + // further validation, check for rational values + if ( ( nBitsPerPixel > 24 ) || ( nCompression > 3 ) ) + { + nFormat = GraphicFileFormat::NOT; + bRet = false; + } + + if (bRet && nXPelsPerMeter && nYPelsPerMeter) + { + maPreferredMapMode + = MapMode(MapUnit::MapMM, Point(), Fraction(1000, nXPelsPerMeter), + Fraction(1000, nYPelsPerMeter)); + + maPreferredLogSize = Size(aPixSize.getWidth(), aPixSize.getHeight()); + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectGIF( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt32 n32 = 0; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + rStm.ReadUInt32( n32 ); + + if ( n32 == 0x38464947 ) + { + sal_uInt16 n16 = 0; + rStm.ReadUInt16( n16 ); + if ( ( n16 == 0x6137 ) || ( n16 == 0x6139 ) ) + { + nFormat = GraphicFileFormat::GIF; + bRet = true; + + if ( bExtendedInfo ) + { + sal_uInt16 nTemp16 = 0; + sal_uInt8 cByte = 0; + + // Pixel width + rStm.ReadUInt16( nTemp16 ); + aPixSize.setWidth( nTemp16 ); + + // Pixel height + rStm.ReadUInt16( nTemp16 ); + aPixSize.setHeight( nTemp16 ); + + // Bits/Pixel + rStm.ReadUChar( cByte ); + nBitsPerPixel = ( ( cByte & 112 ) >> 4 ) + 1; + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +// returns the next jpeg marker, a return value of 0 represents an error +static sal_uInt8 ImpDetectJPG_GetNextMarker( SvStream& rStm ) +{ + sal_uInt8 nByte; + do + { + do + { + rStm.ReadUChar( nByte ); + if (!rStm.good()) // as 0 is not allowed as marker, + return 0; // we can use it as errorcode + } + while ( nByte != 0xff ); + do + { + rStm.ReadUChar( nByte ); + if (!rStm.good()) + return 0; + } + while( nByte == 0xff ); + } + while( nByte == 0 ); // 0xff00 represents 0xff and not a marker, + // the marker detection has to be restarted. + return nByte; +} + +bool GraphicDescriptor::ImpDetectJPG( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt32 nTemp32 = 0; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nTemp32 ); + + // compare upper 24 bits + if( 0xffd8ff00 == ( nTemp32 & 0xffffff00 ) ) + { + nFormat = GraphicFileFormat::JPG; + bRet = true; + + if ( bExtendedInfo ) + { + rStm.SeekRel( -2 ); + + ErrCode nError( rStm.GetError() ); + + bool bScanFailure = false; + bool bScanFinished = false; + MapMode aMap; + + while (!bScanFailure && !bScanFinished && rStm.good()) + { + sal_uInt8 nMarker = ImpDetectJPG_GetNextMarker( rStm ); + switch( nMarker ) + { + // fixed size marker, not having a two byte length parameter + case 0xd0 : // RST0 + case 0xd1 : + case 0xd2 : + case 0xd3 : + case 0xd4 : + case 0xd5 : + case 0xd6 : + case 0xd7 : // RST7 + case 0x01 : // TEM + break; + + case 0xd8 : // SOI (has already been checked, there should not be a second one) + case 0x00 : // marker is invalid, we should stop now + bScanFailure = true; + break; + + case 0xd9 : // EOI + bScanFinished = true; + break; + + // per default we assume marker segments containing a length parameter + default : + { + sal_uInt16 nLength = 0; + rStm.ReadUInt16( nLength ); + + if ( nLength < 2 ) + bScanFailure = true; + else + { + sal_uInt32 nNextMarkerPos = rStm.Tell() + nLength - 2; + switch( nMarker ) + { + case 0xe0 : // APP0 Marker + { + if ( nLength == 16 ) + { + sal_Int32 nIdentifier = 0; + rStm.ReadInt32( nIdentifier ); + if ( nIdentifier == 0x4a464946 ) // JFIF Identifier + { + sal_uInt8 nStringTerminator = 0; + sal_uInt8 nMajorRevision = 0; + sal_uInt8 nMinorRevision = 0; + sal_uInt8 nUnits = 0; + sal_uInt16 nHorizontalResolution = 0; + sal_uInt16 nVerticalResolution = 0; + sal_uInt8 nHorzThumbnailPixelCount = 0; + sal_uInt8 nVertThumbnailPixelCount = 0; + + rStm.ReadUChar( nStringTerminator ) + .ReadUChar( nMajorRevision ) + .ReadUChar( nMinorRevision ) + .ReadUChar( nUnits ) + .ReadUInt16( nHorizontalResolution ) + .ReadUInt16( nVerticalResolution ) + .ReadUChar( nHorzThumbnailPixelCount ) + .ReadUChar( nVertThumbnailPixelCount ); + + // setting the logical size + if ( nUnits && nHorizontalResolution && nVerticalResolution ) + { + aMap.SetMapUnit( nUnits == 1 ? MapUnit::MapInch : MapUnit::MapCM ); + aMap.SetScaleX( Fraction( 1, nHorizontalResolution ) ); + aMap.SetScaleY( Fraction( 1, nVerticalResolution ) ); + aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap, MapMode( MapUnit::Map100thMM ) ); + } + } + } + } + break; + + // Start of Frame Markers + case 0xc0 : // SOF0 + case 0xc1 : // SOF1 + case 0xc2 : // SOF2 + case 0xc3 : // SOF3 + case 0xc5 : // SOF5 + case 0xc6 : // SOF6 + case 0xc7 : // SOF7 + case 0xc9 : // SOF9 + case 0xca : // SOF10 + case 0xcb : // SOF11 + case 0xcd : // SOF13 + case 0xce : // SOF14 + case 0xcf : // SOF15 + { + sal_uInt8 nSamplePrecision = 0; + sal_uInt16 nNumberOfLines = 0; + sal_uInt16 nSamplesPerLine = 0; + sal_uInt8 nNumberOfImageComponents = 0; + sal_uInt8 nComponentsIdentifier = 0; + sal_uInt8 nSamplingFactor = 0; + sal_uInt8 nQuantizationTableDestinationSelector = 0; + rStm.ReadUChar( nSamplePrecision ) + .ReadUInt16( nNumberOfLines ) + .ReadUInt16( nSamplesPerLine ) + .ReadUChar( nNumberOfImageComponents ) + .ReadUChar( nComponentsIdentifier ) + .ReadUChar( nSamplingFactor ) + .ReadUChar( nQuantizationTableDestinationSelector ); + mnNumberOfImageComponents = nNumberOfImageComponents; + + // nSamplingFactor (lower nibble: vertical, + // upper nibble: horizontal) is unused + + aPixSize.setHeight( nNumberOfLines ); + aPixSize.setWidth( nSamplesPerLine ); + nBitsPerPixel = ( nNumberOfImageComponents == 3 ? 24 : nNumberOfImageComponents == 1 ? 8 : 0 ); + nPlanes = 1; + + if (aMap.GetMapUnit() != MapUnit::MapPixel) + // We already know the DPI, but the + // pixel size arrived later, so do the + // conversion again. + aLogSize = OutputDevice::LogicToLogic( + aPixSize, aMap, MapMode(MapUnit::Map100thMM)); + + bScanFinished = true; + } + break; + } + rStm.Seek( nNextMarkerPos ); + } + } + break; + } + } + rStm.SetError( nError ); + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectPCD( SvStream& rStm, bool ) +{ + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + + sal_uInt32 nTemp32 = 0; + sal_uInt16 nTemp16 = 0; + sal_uInt8 cByte = 0; + + rStm.SeekRel( 2048 ); + rStm.ReadUInt32( nTemp32 ); + rStm.ReadUInt16( nTemp16 ); + rStm.ReadUChar( cByte ); + + if ( ( nTemp32 == 0x5f444350 ) && + ( nTemp16 == 0x5049 ) && + ( cByte == 0x49 ) ) + { + nFormat = GraphicFileFormat::PCD; + bRet = true; + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectPCX( SvStream& rStm ) +{ + // ! Because 0x0a can be interpreted as LF too ... + // we can't be sure that this special sign represent a PCX file only. + // Every Ascii file is possible here :-( + // We must detect the whole header. + + bool bRet = false; + sal_uInt8 cByte = 0; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + rStm.ReadUChar( cByte ); + + if ( cByte == 0x0a ) + { + nFormat = GraphicFileFormat::PCX; + + rStm.SeekRel( 1 ); + + // compression + rStm.ReadUChar( cByte ); + + bRet = (cByte==0 || cByte ==1); + if (bRet) + { + sal_uInt16 nTemp16; + sal_uInt16 nXmin; + sal_uInt16 nXmax; + sal_uInt16 nYmin; + sal_uInt16 nYmax; + sal_uInt16 nDPIx; + sal_uInt16 nDPIy; + + // Bits/Pixel + rStm.ReadUChar( cByte ); + nBitsPerPixel = cByte; + + // image dimensions + rStm.ReadUInt16( nTemp16 ); + nXmin = nTemp16; + rStm.ReadUInt16( nTemp16 ); + nYmin = nTemp16; + rStm.ReadUInt16( nTemp16 ); + nXmax = nTemp16; + rStm.ReadUInt16( nTemp16 ); + nYmax = nTemp16; + + aPixSize.setWidth( nXmax - nXmin + 1 ); + aPixSize.setHeight( nYmax - nYmin + 1 ); + + // resolution + rStm.ReadUInt16( nTemp16 ); + nDPIx = nTemp16; + rStm.ReadUInt16( nTemp16 ); + nDPIy = nTemp16; + + // set logical size + MapMode aMap( MapUnit::MapInch, Point(), + Fraction( 1, nDPIx ), Fraction( 1, nDPIy ) ); + aLogSize = OutputDevice::LogicToLogic( aPixSize, aMap, + MapMode( MapUnit::Map100thMM ) ); + + // number of color planes + cByte = 5; // Illegal value in case of EOF. + rStm.SeekRel( 49 ); + rStm.ReadUChar( cByte ); + nPlanes = cByte; + + bRet = (nPlanes<=4); + } + } + + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectPNG( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt32 nTemp32 = 0; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nTemp32 ); + + if ( nTemp32 == 0x89504e47 ) + { + rStm.ReadUInt32( nTemp32 ); + if ( nTemp32 == 0x0d0a1a0a ) + { + nFormat = GraphicFileFormat::PNG; + bRet = true; + + if ( bExtendedInfo ) + { + do { + sal_uInt8 cByte = 0; + + // IHDR-Chunk + rStm.SeekRel( 8 ); + + // width + rStm.ReadUInt32( nTemp32 ); + if (!rStm.good()) + break; + aPixSize.setWidth( nTemp32 ); + + // height + rStm.ReadUInt32( nTemp32 ); + if (!rStm.good()) + break; + aPixSize.setHeight( nTemp32 ); + + // Bits/Pixel + rStm.ReadUChar( cByte ); + if (!rStm.good()) + break; + nBitsPerPixel = cByte; + + // Colour type - check whether it supports alpha values + sal_uInt8 cColType = 0; + rStm.ReadUChar( cColType ); + if (!rStm.good()) + break; + bIsAlpha = bIsTransparent = ( cColType == 4 || cColType == 6 ); + + // Planes always 1; + // compression always + nPlanes = 1; + + sal_uInt32 nLen32 = 0; + nTemp32 = 0; + + rStm.SeekRel( 7 ); + + // read up to the start of the image + rStm.ReadUInt32( nLen32 ); + rStm.ReadUInt32( nTemp32 ); + while (rStm.good() && nTemp32 != 0x49444154) + { + if ( nTemp32 == 0x70485973 ) // physical pixel dimensions + { + sal_uLong nXRes; + sal_uLong nYRes; + + // horizontal resolution + nTemp32 = 0; + rStm.ReadUInt32( nTemp32 ); + nXRes = nTemp32; + + // vertical resolution + nTemp32 = 0; + rStm.ReadUInt32( nTemp32 ); + nYRes = nTemp32; + + // unit + cByte = 0; + rStm.ReadUChar( cByte ); + + if ( cByte ) + { + if ( nXRes ) + aLogSize.setWidth( (aPixSize.Width() * 100000) / nXRes ); + + if ( nYRes ) + aLogSize.setHeight( (aPixSize.Height() * 100000) / nYRes ); + } + + nLen32 -= 9; + } + else if ( nTemp32 == 0x74524e53 ) // transparency + { + bIsTransparent = true; + bIsAlpha = ( cColType != 0 && cColType != 2 ); + } + + // skip forward to next chunk + rStm.SeekRel( 4 + nLen32 ); + rStm.ReadUInt32( nLen32 ); + rStm.ReadUInt32( nTemp32 ); + } + } while (false); + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectTIF( SvStream& rStm, bool bExtendedInfo ) +{ + bool bRet = false; + sal_uInt8 cByte1 = 0; + sal_uInt8 cByte2 = 1; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.ReadUChar( cByte1 ); + rStm.ReadUChar( cByte2 ); + if ( cByte1 == cByte2 ) + { + bool bDetectOk = false; + + if ( cByte1 == 0x49 ) + { + rStm.SetEndian( SvStreamEndian::LITTLE ); + bDetectOk = true; + } + else if ( cByte1 == 0x4d ) + { + rStm.SetEndian( SvStreamEndian::BIG ); + bDetectOk = true; + } + + if ( bDetectOk ) + { + sal_uInt16 nTemp16 = 0; + + rStm.ReadUInt16( nTemp16 ); + if ( nTemp16 == 0x2a ) + { + nFormat = GraphicFileFormat::TIF; + bRet = true; + + if ( bExtendedInfo ) + { + sal_uLong nCount; + sal_uLong nMax = DATA_SIZE - 48; + sal_uInt32 nTemp32 = 0; + + // Offset of the first IFD + rStm.ReadUInt32( nTemp32 ); + nCount = nTemp32 + 2; + rStm.SeekRel( nCount - 0x08 ); + + if ( nCount < nMax ) + { + bool bOk = false; + + // read tags till we find Tag256 ( Width ) + // do not read more bytes than DATA_SIZE + rStm.ReadUInt16( nTemp16 ); + while ( nTemp16 != 256 ) + { + bOk = nCount < nMax; + if ( !bOk ) + { + break; + } + rStm.SeekRel( 10 ); + rStm.ReadUInt16( nTemp16 ); + nCount += 12; + } + + if ( bOk ) + { + // width + rStm.ReadUInt16( nTemp16 ); + rStm.SeekRel( 4 ); + if ( nTemp16 == 3 ) + { + rStm.ReadUInt16( nTemp16 ); + aPixSize.setWidth( nTemp16 ); + rStm.SeekRel( 2 ); + } + else + { + rStm.ReadUInt32( nTemp32 ); + aPixSize.setWidth( nTemp32 ); + } + + // height + rStm.SeekRel( 2 ); + rStm.ReadUInt16( nTemp16 ); + rStm.SeekRel( 4 ); + if ( nTemp16 == 3 ) + { + rStm.ReadUInt16( nTemp16 ); + aPixSize.setHeight( nTemp16 ); + rStm.SeekRel( 2 ); + } + else + { + rStm.ReadUInt32( nTemp32 ); + aPixSize.setHeight( nTemp32 ); + } + + // Bits/Pixel + rStm.ReadUInt16( nTemp16 ); + if ( nTemp16 == 258 ) + { + rStm.SeekRel( 6 ); + rStm.ReadUInt16( nTemp16 ); + nBitsPerPixel = nTemp16; + rStm.SeekRel( 2 ); + } + else + rStm.SeekRel( -2 ); + + // compression + rStm.ReadUInt16( nTemp16 ); + if ( nTemp16 == 259 ) + { + rStm.SeekRel( 6 ); + rStm.ReadUInt16( nTemp16 ); // compression + rStm.SeekRel( 2 ); + } + else + rStm.SeekRel( -2 ); + } + } + } + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectXBM( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "xbm" ); + if (bRet) + nFormat = GraphicFileFormat::XBM; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectXPM( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "xpm" ); + if (bRet) + nFormat = GraphicFileFormat::XPM; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectPBM( SvStream& rStm, bool ) +{ + bool bRet = false; + + // check file extension first, as this trumps the 2 ID bytes + if ( aPathExt.startsWith( "pbm" ) ) + bRet = true; + else + { + sal_Int32 nStmPos = rStm.Tell(); + sal_uInt8 nFirst = 0, nSecond = 0; + rStm.ReadUChar( nFirst ).ReadUChar( nSecond ); + if ( nFirst == 'P' && ( ( nSecond == '1' ) || ( nSecond == '4' ) ) ) + bRet = true; + rStm.Seek( nStmPos ); + } + + if ( bRet ) + nFormat = GraphicFileFormat::PBM; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectPGM( SvStream& rStm, bool ) +{ + bool bRet = false; + + if ( aPathExt.startsWith( "pgm" ) ) + bRet = true; + else + { + sal_uInt8 nFirst = 0, nSecond = 0; + sal_Int32 nStmPos = rStm.Tell(); + rStm.ReadUChar( nFirst ).ReadUChar( nSecond ); + if ( nFirst == 'P' && ( ( nSecond == '2' ) || ( nSecond == '5' ) ) ) + bRet = true; + rStm.Seek( nStmPos ); + } + + if ( bRet ) + nFormat = GraphicFileFormat::PGM; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectPPM( SvStream& rStm, bool ) +{ + bool bRet = false; + + if ( aPathExt.startsWith( "ppm" ) ) + bRet = true; + else + { + sal_uInt8 nFirst = 0, nSecond = 0; + sal_Int32 nStmPos = rStm.Tell(); + rStm.ReadUChar( nFirst ).ReadUChar( nSecond ); + if ( nFirst == 'P' && ( ( nSecond == '3' ) || ( nSecond == '6' ) ) ) + bRet = true; + rStm.Seek( nStmPos ); + } + + if ( bRet ) + nFormat = GraphicFileFormat::PPM; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectRAS( SvStream& rStm, bool ) +{ + sal_uInt32 nMagicNumber = 0; + bool bRet = false; + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nMagicNumber ); + if ( nMagicNumber == 0x59a66a95 ) + { + nFormat = GraphicFileFormat::RAS; + bRet = true; + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectTGA( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "tga" ); + if (bRet) + nFormat = GraphicFileFormat::TGA; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectPSD( SvStream& rStm, bool bExtendedInfo ) +{ + bool bRet = false; + + sal_uInt32 nMagicNumber = 0; + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nMagicNumber ); + if ( nMagicNumber == 0x38425053 ) + { + sal_uInt16 nVersion = 0; + rStm.ReadUInt16( nVersion ); + if ( nVersion == 1 ) + { + bRet = true; + if ( bExtendedInfo ) + { + sal_uInt16 nChannels = 0; + sal_uInt32 nRows = 0; + sal_uInt32 nColumns = 0; + sal_uInt16 nDepth = 0; + sal_uInt16 nMode = 0; + rStm.SeekRel( 6 ); // Pad + rStm.ReadUInt16( nChannels ).ReadUInt32( nRows ).ReadUInt32( nColumns ).ReadUInt16( nDepth ).ReadUInt16( nMode ); + if ( ( nDepth == 1 ) || ( nDepth == 8 ) || ( nDepth == 16 ) ) + { + nBitsPerPixel = ( nDepth == 16 ) ? 8 : nDepth; + switch ( nChannels ) + { + case 4 : + case 3 : + nBitsPerPixel = 24; + [[fallthrough]]; + case 2 : + case 1 : + aPixSize.setWidth( nColumns ); + aPixSize.setHeight( nRows ); + break; + default: + bRet = false; + } + } + else + bRet = false; + } + } + } + + if ( bRet ) + nFormat = GraphicFileFormat::PSD; + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectEPS( SvStream& rStm, bool ) +{ + // check the EPS preview and the file extension + sal_uInt32 nFirstLong = 0; + sal_uInt8 nFirstBytes[20] = {}; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nFirstLong ); + rStm.SeekRel( -4 ); + rStm.ReadBytes( &nFirstBytes, 20 ); + + if ( ( nFirstLong == 0xC5D0D3C6 ) || aPathExt.startsWith( "eps" ) || + ( ImplSearchEntry( nFirstBytes, reinterpret_cast<sal_uInt8 const *>("%!PS-Adobe"), 10, 10 ) + && ImplSearchEntry( &nFirstBytes[15], reinterpret_cast<sal_uInt8 const *>("EPS"), 3, 3 ) ) ) + { + nFormat = GraphicFileFormat::EPS; + bRet = true; + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectDXF( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "dxf" ); + if (bRet) + nFormat = GraphicFileFormat::DXF; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectMET( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "met" ); + if (bRet) + nFormat = GraphicFileFormat::MET; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectPCT( SvStream& rStm, bool ) +{ + bool bRet = aPathExt.startsWith( "pct" ); + if (bRet) + nFormat = GraphicFileFormat::PCT; + else + { + sal_uInt64 const nStreamPos = rStm.Tell(); + sal_uInt64 const nStreamLen = rStm.remainingSize(); + if (isPCT(rStm, nStreamPos, nStreamLen)) + { + bRet = true; + nFormat = GraphicFileFormat::PCT; + } + rStm.Seek(nStreamPos); + } + + return bRet; +} + +bool GraphicDescriptor::ImpDetectSVM( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt32 n32 = 0; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + rStm.ReadUInt32( n32 ); + if ( n32 == 0x44475653 ) + { + sal_uInt8 cByte = 0; + rStm.ReadUChar( cByte ); + if ( cByte == 0x49 ) + { + nFormat = GraphicFileFormat::SVM; + bRet = true; + + if ( bExtendedInfo ) + { + sal_uInt32 nTemp32; + sal_uInt16 nTemp16; + + rStm.SeekRel( 0x04 ); + + // width + nTemp32 = 0; + rStm.ReadUInt32( nTemp32 ); + aLogSize.setWidth( nTemp32 ); + + // height + nTemp32 = 0; + rStm.ReadUInt32( nTemp32 ); + aLogSize.setHeight( nTemp32 ); + + // read MapUnit and determine PrefSize + nTemp16 = 0; + rStm.ReadUInt16( nTemp16 ); + aLogSize = OutputDevice::LogicToLogic( aLogSize, + MapMode( static_cast<MapUnit>(nTemp16) ), + MapMode( MapUnit::Map100thMM ) ); + } + } + } + else + { + rStm.SeekRel( -4 ); + n32 = 0; + rStm.ReadUInt32( n32 ); + + if( n32 == 0x4D4C4356 ) + { + sal_uInt16 nTmp16 = 0; + + rStm.ReadUInt16( nTmp16 ); + + if( nTmp16 == 0x4654 ) + { + nFormat = GraphicFileFormat::SVM; + bRet = true; + + if( bExtendedInfo ) + { + MapMode aMapMode; + rStm.SeekRel( 0x06 ); + TypeSerializer aSerializer(rStm); + aSerializer.readMapMode(aMapMode); + aSerializer.readSize(aLogSize); + aLogSize = OutputDevice::LogicToLogic( aLogSize, aMapMode, MapMode( MapUnit::Map100thMM ) ); + } + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +bool GraphicDescriptor::ImpDetectWMF( SvStream&, bool ) +{ + bool bRet = aPathExt.startsWith( "wmf" ) || aPathExt.startsWith( "wmz" ); + if (bRet) + nFormat = GraphicFileFormat::WMF; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectEMF(SvStream& rStm, bool bExtendedInfo) +{ + SvStream* aNewStream = &rStm; + SvMemoryStream aMemStream; + sal_uInt8 aUncompressedBuffer[EMF_CHECK_SIZE]; + if (ZCodec::IsZCompressed(rStm)) + { + ZCodec aCodec; + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/ true); + auto nDecompressLength = aCodec.Read(rStm, aUncompressedBuffer, EMF_CHECK_SIZE); + aCodec.EndCompression(); + if (nDecompressLength != EMF_CHECK_SIZE) + return false; + aMemStream.SetBuffer(aUncompressedBuffer, EMF_CHECK_SIZE, EMF_CHECK_SIZE); + aNewStream = &aMemStream; + } + + sal_uInt32 nRecordType = 0; + bool bRet = false; + sal_Int32 nStmPos = aNewStream->Tell(); + aNewStream->SetEndian(SvStreamEndian::LITTLE); + aNewStream->ReadUInt32(nRecordType); + if (nRecordType == EMR_HEADER) + { + sal_Int32 nBoundLeft = 0, nBoundTop = 0, nBoundRight = 0, nBoundBottom = 0; + sal_Int32 nFrameLeft = 0, nFrameTop = 0, nFrameRight = 0, nFrameBottom = 0; + sal_uInt32 nSignature = 0; + + aNewStream->SeekRel(4); + aNewStream->ReadInt32(nBoundLeft); + aNewStream->ReadInt32(nBoundTop); + aNewStream->ReadInt32(nBoundRight); + aNewStream->ReadInt32(nBoundBottom); + aNewStream->ReadInt32(nFrameLeft); + aNewStream->ReadInt32(nFrameTop); + aNewStream->ReadInt32(nFrameRight); + aNewStream->ReadInt32(nFrameBottom); + aNewStream->ReadUInt32(nSignature); + + if (nSignature == ENHMETA_SIGNATURE) + { + nFormat = GraphicFileFormat::EMF; + bRet = true; + + if (bExtendedInfo) + { + // size in pixels + aPixSize.setWidth(nBoundRight - nBoundLeft + 1); + aPixSize.setHeight(nBoundBottom - nBoundTop + 1); + + // size in 0.01mm units + aLogSize.setWidth(nFrameRight - nFrameLeft + 1); + aLogSize.setHeight(nFrameBottom - nFrameTop + 1); + } + } + } + + rStm.Seek(nStmPos); + return bRet; +} + +bool GraphicDescriptor::ImpDetectSVG( SvStream& /*rStm*/, bool /*bExtendedInfo*/ ) +{ + bool bRet = aPathExt.startsWith( "svg" ); + if (bRet) + nFormat = GraphicFileFormat::SVG; + + return bRet; +} + +bool GraphicDescriptor::ImpDetectWEBP( SvStream& rStm, bool bExtendedInfo ) +{ + sal_uInt32 nTemp32 = 0; + bool bRet = false; + + sal_Int32 nStmPos = rStm.Tell(); + rStm.SetEndian( SvStreamEndian::BIG ); + rStm.ReadUInt32( nTemp32 ); + + if ( nTemp32 == 0x52494646 ) + { + rStm.ReadUInt32( nTemp32 ); // skip + rStm.ReadUInt32( nTemp32 ); + if ( nTemp32 == 0x57454250 ) + { + nFormat = GraphicFileFormat::WEBP; + bRet = true; + + if ( bExtendedInfo ) + { + rStm.Seek(nStmPos); + ReadWebpInfo(rStm, aPixSize, nBitsPerPixel, bIsAlpha ); + bIsTransparent = bIsAlpha; + } + } + } + rStm.Seek( nStmPos ); + return bRet; +} + +OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat ) +{ + const char *pKeyName = nullptr; + + switch( nFormat ) + { + case GraphicFileFormat::BMP : pKeyName = "bmp"; break; + case GraphicFileFormat::GIF : pKeyName = "gif"; break; + case GraphicFileFormat::JPG : pKeyName = "jpg"; break; + case GraphicFileFormat::PCD : pKeyName = "pcd"; break; + case GraphicFileFormat::PCX : pKeyName = "pcx"; break; + case GraphicFileFormat::PNG : pKeyName = "png"; break; + case GraphicFileFormat::XBM : pKeyName = "xbm"; break; + case GraphicFileFormat::XPM : pKeyName = "xpm"; break; + case GraphicFileFormat::PBM : pKeyName = "pbm"; break; + case GraphicFileFormat::PGM : pKeyName = "pgm"; break; + case GraphicFileFormat::PPM : pKeyName = "ppm"; break; + case GraphicFileFormat::RAS : pKeyName = "ras"; break; + case GraphicFileFormat::TGA : pKeyName = "tga"; break; + case GraphicFileFormat::PSD : pKeyName = "psd"; break; + case GraphicFileFormat::EPS : pKeyName = "eps"; break; + case GraphicFileFormat::TIF : pKeyName = "tif"; break; + case GraphicFileFormat::DXF : pKeyName = "dxf"; break; + case GraphicFileFormat::MET : pKeyName = "met"; break; + case GraphicFileFormat::PCT : pKeyName = "pct"; break; + case GraphicFileFormat::SVM : pKeyName = "svm"; break; + case GraphicFileFormat::WMF : pKeyName = "wmf"; break; + case GraphicFileFormat::EMF : pKeyName = "emf"; break; + case GraphicFileFormat::SVG : pKeyName = "svg"; break; + case GraphicFileFormat::WEBP : pKeyName = "webp"; break; + default: assert(false); + } + + return OUString::createFromAscii(pKeyName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/graphicfilter_internal.hxx b/vcl/source/filter/graphicfilter_internal.hxx new file mode 100644 index 000000000..87f328fce --- /dev/null +++ b/vcl/source/filter/graphicfilter_internal.hxx @@ -0,0 +1,32 @@ +/* -*- 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_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX +#define INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX + +#include <tools/solar.h> +#include <tools/stream.hxx> + +sal_uInt8* ImplSearchEntry(sal_uInt8*, sal_uInt8 const*, sal_uLong, sal_uLong); + +extern bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen); + +#endif // INCLUDED_VCL_SOURCE_FILTER_GRAPHICFILTER_INTERNAL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxf2mtf.cxx b/vcl/source/filter/idxf/dxf2mtf.cxx new file mode 100644 index 000000000..2b26abffd --- /dev/null +++ b/vcl/source/filter/idxf/dxf2mtf.cxx @@ -0,0 +1,902 @@ +/* -*- 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 <unotools/configmgr.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <tools/poly.hxx> +#include "dxf2mtf.hxx" + +#include <math.h> + + +sal_uInt64 DXF2GDIMetaFile::CountEntities(const DXFEntities & rEntities) +{ + const DXFBasicEntity * pBE; + sal_uInt64 nRes; + + nRes=0; + for (pBE=rEntities.pFirst; pBE!=nullptr; pBE=pBE->pSucc) nRes++; + return nRes; +} + +Color DXF2GDIMetaFile::ConvertColor(sal_uInt8 nColor) const +{ + return Color( + pDXF->aPalette.GetRed( nColor ), + pDXF->aPalette.GetGreen( nColor ), + pDXF->aPalette.GetBlue( nColor ) ); +} + +tools::Long DXF2GDIMetaFile::GetEntityColor(const DXFBasicEntity & rE) const +{ + tools::Long nColor; + + nColor=rE.nColor; + if (nColor==256) { + if (rE.m_sLayer.getLength() < 2) { + nColor=nParentLayerColor; + } else { + const DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer); + if (pLayer!=nullptr) nColor=pLayer->nColor; + else nColor=nParentLayerColor; + } + } + else if (nColor==0) nColor=nBlockColor; + return nColor; +} + +DXFLineInfo DXF2GDIMetaFile::LTypeToDXFLineInfo(std::string_view rLineType) const +{ + const DXFLType * pLT; + DXFLineInfo aDXFLineInfo; + + pLT = pDXF->aTables.SearchLType(rLineType); + if (pLT==nullptr || pLT->nDashCount == 0) { + aDXFLineInfo.eStyle = LineStyle::Solid; + } + else { + aDXFLineInfo.eStyle = LineStyle::Dash; + for (tools::Long i=0; i < (pLT->nDashCount); i++) { + const double x = pLT->fDash[i] * pDXF->getGlobalLineTypeScale(); + if ( x >= 0.0 ) { + if ( aDXFLineInfo.nDotCount == 0 ) { + aDXFLineInfo.nDotCount ++; + aDXFLineInfo.fDotLen = x; + } + else if ( aDXFLineInfo.fDotLen == x ) { + aDXFLineInfo.nDotCount ++; + } + else if ( aDXFLineInfo.nDashCount == 0 ) { + aDXFLineInfo.nDashCount ++; + aDXFLineInfo.fDashLen = x; + } + else if ( aDXFLineInfo.fDashLen == x ) { + aDXFLineInfo.nDashCount ++; + } + else { + // It is impossible to be converted. + } + } + else { + if ( aDXFLineInfo.fDistance == 0 ) { + aDXFLineInfo.fDistance = -1 * x; + } + else { + // It is impossible to be converted. + } + } + + } + } + + return aDXFLineInfo; +} + +DXFLineInfo DXF2GDIMetaFile::GetEntityDXFLineInfo(const DXFBasicEntity & rE) +{ + DXFLineInfo aDXFLineInfo; + + aDXFLineInfo.eStyle = LineStyle::Solid; + aDXFLineInfo.nDashCount = 0; + aDXFLineInfo.fDashLen = 0; + aDXFLineInfo.nDotCount = 0; + aDXFLineInfo.fDotLen = 0; + aDXFLineInfo.fDistance = 0; + + if (rE.m_sLineType == "BYLAYER") { + if (rE.m_sLayer.getLength() < 2) { + aDXFLineInfo=aParentLayerDXFLineInfo; + } else { + const DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer); + if (pLayer!=nullptr) { + aDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType); + } + else aDXFLineInfo=aParentLayerDXFLineInfo; + } + } + else if (rE.m_sLineType == "BYBLOCK") { + aDXFLineInfo=aBlockDXFLineInfo; + } + else { + aDXFLineInfo = LTypeToDXFLineInfo(rE.m_sLineType); + } + return aDXFLineInfo; +} + + +bool DXF2GDIMetaFile::SetLineAttribute(const DXFBasicEntity & rE) +{ + tools::Long nColor; + Color aColor; + + nColor=GetEntityColor(rE); + if (nColor<0) return false; + aColor=ConvertColor(static_cast<sal_uInt8>(nColor)); + + if (aActLineColor!=aColor) { + aActLineColor = aColor; + pVirDev->SetLineColor( aActLineColor ); + } + + if (aActFillColor!=COL_TRANSPARENT) { + aActFillColor = COL_TRANSPARENT; + pVirDev->SetFillColor(aActFillColor); + } + return true; +} + + +bool DXF2GDIMetaFile::SetAreaAttribute(const DXFBasicEntity & rE) +{ + tools::Long nColor; + Color aColor; + + nColor=GetEntityColor(rE); + if (nColor<0) return false; + aColor=ConvertColor(static_cast<sal_uInt8>(nColor)); + + if (aActLineColor!=aColor) { + aActLineColor = aColor; + pVirDev->SetLineColor( aActLineColor ); + } + + if ( aActFillColor == COL_TRANSPARENT || aActFillColor != aColor) { + aActFillColor = aColor; + pVirDev->SetFillColor( aActFillColor ); + } + return true; +} + + +bool DXF2GDIMetaFile::SetFontAttribute(const DXFBasicEntity & rE, short nAngle, sal_uInt16 nHeight) +{ + tools::Long nColor; + Color aColor; + vcl::Font aFont; + + nAngle=-nAngle; + while (nAngle>=3600) nAngle-=3600; + while (nAngle<0) nAngle+=3600; + + nColor=GetEntityColor(rE); + if (nColor<0) return false; + aColor=ConvertColor(static_cast<sal_uInt8>(nColor)); + + aFont.SetColor(aColor); + aFont.SetTransparent(true); + aFont.SetFamily(FAMILY_SWISS); + aFont.SetFontSize(Size(0,nHeight)); + aFont.SetAlignment(ALIGN_BASELINE); + aFont.SetOrientation(Degree10(nAngle)); + if (aActFont!=aFont) { + aActFont=aFont; + pVirDev->SetFont(aActFont); + } + + return true; +} + + +void DXF2GDIMetaFile::DrawLineEntity(const DXFLineEntity & rE, const DXFTransform & rTransform) +{ + if (!SetLineAttribute(rE)) + return; + + Point aP0,aP1; + rTransform.Transform(rE.aP0,aP0); + rTransform.Transform(rE.aP1,aP1); + + DXFLineInfo aDXFLineInfo=GetEntityDXFLineInfo(rE); + LineInfo aLineInfo; + aLineInfo = rTransform.Transform(aDXFLineInfo); + + pVirDev->DrawLine(aP0,aP1,aLineInfo); + if (rE.fThickness!=0) { + Point aP2,aP3; + rTransform.Transform(rE.aP0+DXFVector(0,0,rE.fThickness),aP2); + rTransform.Transform(rE.aP1+DXFVector(0,0,rE.fThickness),aP3); + DrawLine(aP2,aP3); + DrawLine(aP0,aP2); + DrawLine(aP1,aP3); + } +} + + +void DXF2GDIMetaFile::DrawPointEntity(const DXFPointEntity & rE, const DXFTransform & rTransform) +{ + + if (SetLineAttribute(rE)) { + Point aP0; + rTransform.Transform(rE.aP0,aP0); + if (rE.fThickness==0) pVirDev->DrawPixel(aP0); + else { + Point aP1; + rTransform.Transform(rE.aP0+DXFVector(0,0,rE.fThickness),aP1); + DrawLine(aP0,aP1); + } + } +} + + +void DXF2GDIMetaFile::DrawCircleEntity(const DXFCircleEntity & rE, const DXFTransform & rTransform) +{ + double frx,fry; + sal_uInt16 nPoints,i; + DXFVector aC; + + if (!SetLineAttribute(rE)) return; + rTransform.Transform(rE.aP0,aC); + if (rE.fThickness==0 && rTransform.TransCircleToEllipse(rE.fRadius,frx,fry)) { + pVirDev->DrawEllipse( + tools::Rectangle(static_cast<tools::Long>(aC.fx-frx+0.5),static_cast<tools::Long>(aC.fy-fry+0.5), + static_cast<tools::Long>(aC.fx+frx+0.5),static_cast<tools::Long>(aC.fy+fry+0.5))); + } + else { + double fAng; + nPoints=OptPointsPerCircle; + tools::Polygon aPoly(nPoints); + for (i=0; i<nPoints; i++) { + fAng=2*M_PI/static_cast<double>(nPoints-1)*static_cast<double>(i); + rTransform.Transform( + rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),0), + aPoly[i] + ); + } + pVirDev->DrawPolyLine(aPoly); + if (rE.fThickness!=0) { + tools::Polygon aPoly2(nPoints); + for (i=0; i<nPoints; i++) { + fAng=2*M_PI/static_cast<double>(nPoints-1)*static_cast<double>(i); + rTransform.Transform( + rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),rE.fThickness), + aPoly2[i] + ); + + } + pVirDev->DrawPolyLine(aPoly2); + for (i=0; i<nPoints-1; i++) DrawLine(aPoly[i],aPoly2[i]); + } + } +} + +void DXF2GDIMetaFile::DrawLine(const Point& rA, const Point& rB) +{ + if (utl::ConfigManager::IsFuzzing()) + return; + GDIMetaFile* pMetaFile = pVirDev->GetConnectMetaFile(); + assert(pMetaFile); + //use AddAction instead of OutputDevice::DrawLine so that we can explicitly share + //the aDefaultLineInfo between the MetaLineActions to reduce memory use + pMetaFile->AddAction(new MetaLineAction(rA, rB, aDefaultLineInfo)); +} + +void DXF2GDIMetaFile::DrawArcEntity(const DXFArcEntity & rE, const DXFTransform & rTransform) +{ + double frx,fry; + sal_uInt16 nPoints,i; + DXFVector aC; + + if (!SetLineAttribute(rE)) return; + double fA1=rE.fStart; + double fdA=rE.fEnd-fA1; + fdA = fmod(fdA, 360.0); + if (fdA<=0) fdA+=360.0; + rTransform.Transform(rE.aP0,aC); + if (rE.fThickness==0 && fdA>5.0 && rTransform.TransCircleToEllipse(rE.fRadius,frx,fry)) { + DXFVector aVS(cos(basegfx::deg2rad(fA1)),sin(basegfx::deg2rad(fA1)),0.0); + aVS*=rE.fRadius; + aVS+=rE.aP0; + DXFVector aVE(cos(basegfx::deg2rad(fA1+fdA)),sin(basegfx::deg2rad(fA1+fdA)),0.0); + aVE*=rE.fRadius; + aVE+=rE.aP0; + Point aPS,aPE; + if (rTransform.Mirror()) { + rTransform.Transform(aVS,aPS); + rTransform.Transform(aVE,aPE); + } + else { + rTransform.Transform(aVS,aPE); + rTransform.Transform(aVE,aPS); + } + pVirDev->DrawArc( + tools::Rectangle(static_cast<tools::Long>(aC.fx-frx+0.5),static_cast<tools::Long>(aC.fy-fry+0.5), + static_cast<tools::Long>(aC.fx+frx+0.5),static_cast<tools::Long>(aC.fy+fry+0.5)), + aPS,aPE + ); + } + else { + double fAng; + nPoints=static_cast<sal_uInt16>(fdA/360.0*static_cast<double>(OptPointsPerCircle)+0.5); + if (nPoints<2) nPoints=2; + tools::Polygon aPoly(nPoints); + for (i=0; i<nPoints; i++) { + fAng=basegfx::deg2rad( fA1 + fdA/static_cast<double>(nPoints-1)*static_cast<double>(i) ); + rTransform.Transform( + rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),0), + aPoly[i] + ); + } + pVirDev->DrawPolyLine(aPoly); + if (rE.fThickness!=0) { + tools::Polygon aPoly2(nPoints); + for (i=0; i<nPoints; i++) { + fAng=basegfx::deg2rad( fA1 + fdA/static_cast<double>(nPoints-1)*static_cast<double>(i) ); + rTransform.Transform( + rE.aP0+DXFVector(rE.fRadius*cos(fAng),rE.fRadius*sin(fAng),rE.fThickness), + aPoly2[i] + ); + } + pVirDev->DrawPolyLine(aPoly2); + for (i=0; i<nPoints; i++) + DrawLine(aPoly[i], aPoly2[i]); + } + } +} + +void DXF2GDIMetaFile::DrawTraceEntity(const DXFTraceEntity & rE, const DXFTransform & rTransform) +{ + if (!SetLineAttribute(rE)) + return; + + tools::Polygon aPoly(4); + rTransform.Transform(rE.aP0,aPoly[0]); + rTransform.Transform(rE.aP1,aPoly[1]); + rTransform.Transform(rE.aP3,aPoly[2]); + rTransform.Transform(rE.aP2,aPoly[3]); + pVirDev->DrawPolygon(aPoly); + if (rE.fThickness!=0) { + sal_uInt16 i; + tools::Polygon aPoly2(4); + DXFVector aVAdd(0,0,rE.fThickness); + rTransform.Transform(rE.aP0+aVAdd,aPoly2[0]); + rTransform.Transform(rE.aP1+aVAdd,aPoly2[1]); + rTransform.Transform(rE.aP3+aVAdd,aPoly2[2]); + rTransform.Transform(rE.aP2+aVAdd,aPoly2[3]); + pVirDev->DrawPolygon(aPoly2); + for (i=0; i<4; i++) DrawLine(aPoly[i],aPoly2[i]); + } +} + + +void DXF2GDIMetaFile::DrawSolidEntity(const DXFSolidEntity & rE, const DXFTransform & rTransform) +{ + if (!SetAreaAttribute(rE)) + return; + + sal_uInt16 nN; + if (rE.aP2==rE.aP3) nN=3; else nN=4; + tools::Polygon aPoly(nN); + rTransform.Transform(rE.aP0,aPoly[0]); + rTransform.Transform(rE.aP1,aPoly[1]); + rTransform.Transform(rE.aP3,aPoly[2]); + if (nN>3) rTransform.Transform(rE.aP2,aPoly[3]); + pVirDev->DrawPolygon(aPoly); + if (rE.fThickness==0) return; + + tools::Polygon aPoly2(nN); + DXFVector aVAdd(0,0,rE.fThickness); + rTransform.Transform(rE.aP0+aVAdd,aPoly2[0]); + rTransform.Transform(rE.aP1+aVAdd,aPoly2[1]); + rTransform.Transform(rE.aP3+aVAdd,aPoly2[2]); + if (nN>3) rTransform.Transform(rE.aP2+aVAdd,aPoly2[3]); + pVirDev->DrawPolygon(aPoly2); + if (SetLineAttribute(rE)) { + sal_uInt16 i; + for (i=0; i<nN; i++) DrawLine(aPoly[i],aPoly2[i]); + } +} + + +void DXF2GDIMetaFile::DrawTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform) +{ + DXFVector aV; + double fA; + sal_uInt16 nHeight; + short nAng; + DXFTransform aT( DXFTransform(rE.fXScale,rE.fHeight,1.0,rE.fRotAngle,rE.aP0), rTransform ); + aT.TransDir(DXFVector(0,1,0),aV); + nHeight=static_cast<sal_uInt16>(aV.Abs()+0.5); + fA=aT.CalcRotAngle(); + nAng=static_cast<short>(fA*10.0+0.5); + aT.TransDir(DXFVector(1,0,0),aV); + if ( SetFontAttribute( rE,nAng, nHeight ) ) + { + OUString const aUString(pDXF->ToOUString(rE.m_sText)); + Point aPt; + aT.Transform( DXFVector( 0, 0, 0 ), aPt ); + pVirDev->DrawText( aPt, aUString ); + } +} + + +void DXF2GDIMetaFile::DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform) +{ + const DXFBlock * pB; + pB=pDXF->aBlocks.Search(rE.m_sName); + if (pB==nullptr) + return; + + DXFTransform aDXFTransform1(1.0,1.0,1.0,DXFVector(0.0,0.0,0.0)-pB->aBasePoint); + DXFTransform aDXFTransform2(rE.fXScale,rE.fYScale,rE.fZScale,rE.fRotAngle,rE.aP0); + DXFTransform aT( + DXFTransform( aDXFTransform1, aDXFTransform2 ), + rTransform + ); + tools::Long nSavedBlockColor, nSavedParentLayerColor; + DXFLineInfo aSavedBlockDXFLineInfo, aSavedParentLayerDXFLineInfo; + nSavedBlockColor=nBlockColor; + nSavedParentLayerColor=nParentLayerColor; + aSavedBlockDXFLineInfo=aBlockDXFLineInfo; + aSavedParentLayerDXFLineInfo=aParentLayerDXFLineInfo; + nBlockColor=GetEntityColor(rE); + aBlockDXFLineInfo=GetEntityDXFLineInfo(rE); + if (rE.m_sLayer.getLength() > 1) { + DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer); + if (pLayer!=nullptr) { + nParentLayerColor=pLayer->nColor; + aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType); + } + } + DrawEntities(*pB,aT); + aBlockDXFLineInfo=aSavedBlockDXFLineInfo; + aParentLayerDXFLineInfo=aSavedParentLayerDXFLineInfo; + nBlockColor=nSavedBlockColor; + nParentLayerColor=nSavedParentLayerColor; +} + + +void DXF2GDIMetaFile::DrawAttribEntity(const DXFAttribEntity & rE, const DXFTransform & rTransform) +{ + if ((rE.nAttrFlags&1)!=0) + return; + + DXFVector aV; + double fA; + sal_uInt16 nHeight; + short nAng; + DXFTransform aT( DXFTransform( rE.fXScale, rE.fHeight, 1.0, rE.fRotAngle, rE.aP0 ), rTransform ); + aT.TransDir(DXFVector(0,1,0),aV); + nHeight=static_cast<sal_uInt16>(aV.Abs()+0.5); + fA=aT.CalcRotAngle(); + nAng=static_cast<short>(fA*10.0+0.5); + aT.TransDir(DXFVector(1,0,0),aV); + if (SetFontAttribute(rE,nAng,nHeight)) + { + OUString const aUString(pDXF->ToOUString(rE.m_sText)); + Point aPt; + aT.Transform( DXFVector( 0, 0, 0 ), aPt ); + pVirDev->DrawText( aPt, aUString ); + } +} + + +void DXF2GDIMetaFile::DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXFTransform & rTransform) +{ + sal_uInt16 i,nPolySize; + const DXFBasicEntity * pBE; + + nPolySize=0; + pBE=rE.pSucc; + while (pBE!=nullptr && pBE->eType==DXF_VERTEX) { + nPolySize++; + pBE=pBE->pSucc; + } + if (nPolySize<2) + return; + tools::Polygon aPoly(nPolySize); + pBE=rE.pSucc; + for (i=0; i<nPolySize; i++) { + rTransform.Transform(static_cast<const DXFVertexEntity*>(pBE)->aP0,aPoly[i]); + pBE=pBE->pSucc; + } + + if (!SetLineAttribute(rE)) + return; + + if ((rE.nFlags&1)!=0) pVirDev->DrawPolygon(aPoly); + else pVirDev->DrawPolyLine(aPoly); + if (rE.fThickness==0) + return; + + tools::Polygon aPoly2(nPolySize); + pBE=rE.pSucc; + for (i=0; i<nPolySize; i++) { + rTransform.Transform( + (static_cast<const DXFVertexEntity*>(pBE)->aP0)+DXFVector(0,0,rE.fThickness), + aPoly2[i] + ); + pBE=pBE->pSucc; + } + if ((rE.nFlags&1)!=0) pVirDev->DrawPolygon(aPoly2); + else pVirDev->DrawPolyLine(aPoly2); + for (i=0; i<nPolySize; i++) DrawLine(aPoly[i],aPoly2[i]); +} + +void DXF2GDIMetaFile::DrawLWPolyLineEntity(const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform ) +{ + sal_Int32 nPolySize = rE.aP.size(); + if (!nPolySize) + return; + + tools::Polygon aPoly( static_cast<sal_uInt16>(nPolySize)); + for (sal_Int32 i = 0; i < nPolySize; ++i) + { + rTransform.Transform( rE.aP[ static_cast<sal_uInt16>(i) ], aPoly[ static_cast<sal_uInt16>(i) ] ); + } + if ( SetLineAttribute( rE ) ) + { + if ( ( rE.nFlags & 1 ) != 0 ) + pVirDev->DrawPolygon( aPoly ); + else + pVirDev->DrawPolyLine( aPoly ); + } +} + +void DXF2GDIMetaFile::DrawHatchEntity(const DXFHatchEntity & rE, const DXFTransform & rTransform ) +{ + if (rE.aBoundaryPathData.empty()) + return; + + SetAreaAttribute( rE ); + tools::PolyPolygon aPolyPoly; + for (const DXFBoundaryPathData& rPathData : rE.aBoundaryPathData) + { + std::vector< Point > aPtAry; + if ( rPathData.bIsPolyLine ) + { + for (const auto& a : rPathData.aP) + { + Point aPt; + rTransform.Transform(a, aPt); + aPtAry.push_back( aPt ); + } + } + else + { + for ( auto& rEdge : rPathData.aEdges ) + { + const DXFEdgeType* pEdge = rEdge.get(); + switch( pEdge->nEdgeType ) + { + case 1 : + { + Point aPt; + rTransform.Transform( static_cast<const DXFEdgeTypeLine*>(pEdge)->aStartPoint, aPt ); + aPtAry.push_back( aPt ); + rTransform.Transform( static_cast<const DXFEdgeTypeLine*>(pEdge)->aEndPoint, aPt ); + aPtAry.push_back( aPt ); + } + break; + case 2 : + case 3 : + case 4 : + break; + } + } + } + sal_uInt16 i, nSize = static_cast<sal_uInt16>(aPtAry.size()); + if ( nSize ) + { + tools::Polygon aPoly( nSize ); + for ( i = 0; i < nSize; i++ ) + aPoly[ i ] = aPtAry[ i ]; + aPolyPoly.Insert( aPoly ); + } + } + if ( aPolyPoly.Count() ) + pVirDev->DrawPolyPolygon( aPolyPoly ); +} + +void DXF2GDIMetaFile::Draw3DFaceEntity(const DXF3DFaceEntity & rE, const DXFTransform & rTransform) +{ + sal_uInt16 nN,i; + if (!SetLineAttribute(rE)) + return; + + if (rE.aP2==rE.aP3) nN=3; else nN=4; + tools::Polygon aPoly(nN); + rTransform.Transform(rE.aP0,aPoly[0]); + rTransform.Transform(rE.aP1,aPoly[1]); + rTransform.Transform(rE.aP2,aPoly[2]); + if (nN>3) rTransform.Transform(rE.aP3,aPoly[3]); + if ((rE.nIEFlags&0x0f)==0) pVirDev->DrawPolygon(aPoly); + else { + for (i=0; i<nN; i++) { + if ( (rE.nIEFlags & (static_cast<tools::Long>(1)<<i)) == 0 ) { + DrawLine(aPoly[i],aPoly[(i+1)%nN]); + } + } + } +} + +void DXF2GDIMetaFile::DrawDimensionEntity(const DXFDimensionEntity & rE, const DXFTransform & rTransform) +{ + const DXFBlock * pB; + pB=pDXF->aBlocks.Search(rE.m_sPseudoBlock); + if (pB==nullptr) + return; + + DXFTransform aT( + DXFTransform(1.0,1.0,1.0,DXFVector(0.0,0.0,0.0)-pB->aBasePoint), + rTransform + ); + tools::Long nSavedBlockColor, nSavedParentLayerColor; + DXFLineInfo aSavedBlockDXFLineInfo, aSavedParentLayerDXFLineInfo; + nSavedBlockColor=nBlockColor; + nSavedParentLayerColor=nParentLayerColor; + aSavedBlockDXFLineInfo=aBlockDXFLineInfo; + aSavedParentLayerDXFLineInfo=aParentLayerDXFLineInfo; + nBlockColor=GetEntityColor(rE); + aBlockDXFLineInfo=GetEntityDXFLineInfo(rE); + if (rE.m_sLayer.getLength() > 1) { + DXFLayer * pLayer=pDXF->aTables.SearchLayer(rE.m_sLayer); + if (pLayer!=nullptr) { + nParentLayerColor=pLayer->nColor; + aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType); + } + } + DrawEntities(*pB,aT); + aBlockDXFLineInfo=aSavedBlockDXFLineInfo; + aParentLayerDXFLineInfo=aSavedParentLayerDXFLineInfo; + nBlockColor=nSavedBlockColor; + nParentLayerColor=nSavedParentLayerColor; +} + + +void DXF2GDIMetaFile::DrawEntities(const DXFEntities & rEntities, + const DXFTransform & rTransform) +{ + if (rEntities.mbBeingDrawn) + return; + rEntities.mbBeingDrawn = true; + + DXFTransform aET; + const DXFTransform * pT; + + const DXFBasicEntity * pE=rEntities.pFirst; + + while (pE!=nullptr && bStatus) { + if (pE->nSpace==0) { + if (pE->aExtrusion.fz==1.0) { + pT=&rTransform; + } + else { + aET=DXFTransform(DXFTransform(pE->aExtrusion),rTransform); + pT=&aET; + } + switch (pE->eType) { + case DXF_LINE: + DrawLineEntity(static_cast<const DXFLineEntity&>(*pE),*pT); + break; + case DXF_POINT: + DrawPointEntity(static_cast<const DXFPointEntity&>(*pE),*pT); + break; + case DXF_CIRCLE: + DrawCircleEntity(static_cast<const DXFCircleEntity&>(*pE),*pT); + break; + case DXF_ARC: + DrawArcEntity(static_cast<const DXFArcEntity&>(*pE),*pT); + break; + case DXF_TRACE: + DrawTraceEntity(static_cast<const DXFTraceEntity&>(*pE),*pT); + break; + case DXF_SOLID: + DrawSolidEntity(static_cast<const DXFSolidEntity&>(*pE),*pT); + break; + case DXF_TEXT: + DrawTextEntity(static_cast<const DXFTextEntity&>(*pE),*pT); + break; + case DXF_INSERT: + DrawInsertEntity(static_cast<const DXFInsertEntity&>(*pE),*pT); + break; + case DXF_ATTRIB: + DrawAttribEntity(static_cast<const DXFAttribEntity&>(*pE),*pT); + break; + case DXF_POLYLINE: + DrawPolyLineEntity(static_cast<const DXFPolyLineEntity&>(*pE),*pT); + break; + case DXF_LWPOLYLINE : + DrawLWPolyLineEntity(static_cast<const DXFLWPolyLineEntity&>(*pE), *pT); + break; + case DXF_HATCH : + DrawHatchEntity(static_cast<const DXFHatchEntity&>(*pE), *pT); + break; + case DXF_3DFACE: + Draw3DFaceEntity(static_cast<const DXF3DFaceEntity&>(*pE),*pT); + break; + case DXF_DIMENSION: + DrawDimensionEntity(static_cast<const DXFDimensionEntity&>(*pE),*pT); + break; + default: + break; // four other values not handled -Wall + } + } + pE=pE->pSucc; + } + + rEntities.mbBeingDrawn = false; +} + + +DXF2GDIMetaFile::DXF2GDIMetaFile() + : pVirDev(nullptr) + , pDXF(nullptr) + , bStatus(false) + , OptPointsPerCircle(0) + , nMinPercent(0) + , nMaxPercent(0) + , nLastPercent(0) + , nMainEntitiesCount(0) + , nBlockColor(0) + , nParentLayerColor(0) +{ +} + + +DXF2GDIMetaFile::~DXF2GDIMetaFile() +{ +} + + +bool DXF2GDIMetaFile::Convert(const DXFRepresentation & rDXF, GDIMetaFile & rMTF, sal_uInt16 nminpercent, sal_uInt16 nmaxpercent) +{ + double fWidth,fHeight,fScale(0.0); + DXFTransform aTransform; + Size aPrefSize; + const DXFLayer * pLayer; + const DXFVPort * pVPort; + + pVirDev = VclPtr<VirtualDevice>::Create(); + pDXF = &rDXF; + bStatus = true; + + OptPointsPerCircle=50; + + nMinPercent=nminpercent; + nMaxPercent=nmaxpercent; + nLastPercent=nMinPercent; + nMainEntitiesCount=CountEntities(pDXF->aEntities); + + nBlockColor=7; + aBlockDXFLineInfo.eStyle = LineStyle::Solid; + aBlockDXFLineInfo.nDashCount = 0; + aBlockDXFLineInfo.fDashLen = 0; + aBlockDXFLineInfo.nDotCount = 0; + aBlockDXFLineInfo.fDotLen = 0; + aBlockDXFLineInfo.fDistance = 0; + + pLayer=pDXF->aTables.SearchLayer("0"); + if (pLayer!=nullptr) { + nParentLayerColor=pLayer->nColor & 0xff; + aParentLayerDXFLineInfo = LTypeToDXFLineInfo(pLayer->m_sLineType); + } + else { + nParentLayerColor=7; + aParentLayerDXFLineInfo.eStyle = LineStyle::Solid; + aParentLayerDXFLineInfo.nDashCount = 0; + aParentLayerDXFLineInfo.fDashLen = 0; + aParentLayerDXFLineInfo.nDotCount = 0; + aParentLayerDXFLineInfo.fDotLen = 0; + aParentLayerDXFLineInfo.fDistance = 0; + } + + pVirDev->EnableOutput(false); + if (!utl::ConfigManager::IsFuzzing()) // for fuzzing don't bother recording the drawing + rMTF.Record(pVirDev); + + aActLineColor = pVirDev->GetLineColor(); + aActFillColor = pVirDev->GetFillColor(); + aActFont = pVirDev->GetFont(); + + pVPort=pDXF->aTables.SearchVPort("*ACTIVE"); + if (pVPort!=nullptr) { + if (pVPort->aDirection.fx==0 && pVPort->aDirection.fy==0) + pVPort=nullptr; + } + + if (pVPort==nullptr) { + if (pDXF->aBoundingBox.bEmpty) + bStatus=false; + else { + fWidth=pDXF->aBoundingBox.fMaxX-pDXF->aBoundingBox.fMinX; + fHeight=pDXF->aBoundingBox.fMaxY-pDXF->aBoundingBox.fMinY; + if (fWidth<=0 || fHeight<=0) { + bStatus=false; + } + else { + if (fWidth>fHeight) + fScale=10000.0/fWidth; + else + fScale=10000.0/fHeight; + aTransform=DXFTransform(fScale,-fScale,fScale, + DXFVector(-pDXF->aBoundingBox.fMinX*fScale, + pDXF->aBoundingBox.fMaxY*fScale, + -pDXF->aBoundingBox.fMinZ*fScale)); + } + aPrefSize.setWidth(static_cast<tools::Long>(fWidth*fScale+1.5) ); + aPrefSize.setHeight(static_cast<tools::Long>(fHeight*fScale+1.5) ); + } + } + else { + fHeight=pVPort->fHeight; + fWidth=fHeight*pVPort->fAspectRatio; + if (fWidth<=0 || fHeight<=0) { + bStatus=false; + } else { + if (fWidth>fHeight) + fScale=10000.0/fWidth; + else + fScale=10000.0/fHeight; + aTransform=DXFTransform( + DXFTransform(pVPort->aDirection,pVPort->aTarget), + DXFTransform( + DXFTransform(1.0,-1.0,1.0,DXFVector(fWidth/2-pVPort->fCenterX,fHeight/2+pVPort->fCenterY,0)), + DXFTransform(fScale,fScale,fScale,DXFVector(0,0,0)) + ) + ); + } + aPrefSize.setWidth(static_cast<tools::Long>(fWidth*fScale+1.5) ); + aPrefSize.setHeight(static_cast<tools::Long>(fHeight*fScale+1.5) ); + } + + if (bStatus) + DrawEntities(pDXF->aEntities,aTransform); + + rMTF.Stop(); + + if ( bStatus ) + { + rMTF.SetPrefSize( aPrefSize ); + // simply set map mode to 1/100-mm (1/10-mm) if the graphic + // does not get not too small (<0.5cm) + if( ( aPrefSize.Width() < 500 ) && ( aPrefSize.Height() < 500 ) ) + rMTF.SetPrefMapMode( MapMode( MapUnit::Map10thMM ) ); + else + rMTF.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) ); + } + + pVirDev.disposeAndClear(); + return bStatus; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxf2mtf.hxx b/vcl/source/filter/idxf/dxf2mtf.hxx new file mode 100644 index 000000000..d8a935b51 --- /dev/null +++ b/vcl/source/filter/idxf/dxf2mtf.hxx @@ -0,0 +1,119 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXF2MTF_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXF2MTF_HXX + +#include <sal/config.h> + +#include <string_view> + +#include "dxfreprd.hxx" +#include <vcl/font.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/vclptr.hxx> +#include <vcl/virdev.hxx> + +class DXF2GDIMetaFile { +private: + + VclPtr<VirtualDevice> pVirDev; + const DXFRepresentation * pDXF; + bool bStatus; + + sal_uInt16 OptPointsPerCircle; + + sal_uInt16 nMinPercent; + sal_uInt16 nMaxPercent; + sal_uInt16 nLastPercent; + sal_uInt16 nMainEntitiesCount; + + tools::Long nBlockColor; + DXFLineInfo aBlockDXFLineInfo; + tools::Long nParentLayerColor; + DXFLineInfo aParentLayerDXFLineInfo; + Color aActLineColor; + Color aActFillColor; + vcl::Font aActFont; + const LineInfo aDefaultLineInfo; // to share between lines to reduce memory + + static sal_uInt64 CountEntities(const DXFEntities & rEntities); + + Color ConvertColor(sal_uInt8 nColor) const; + + tools::Long GetEntityColor(const DXFBasicEntity & rE) const; + + DXFLineInfo LTypeToDXFLineInfo(std::string_view rLineType) const; + + DXFLineInfo GetEntityDXFLineInfo(const DXFBasicEntity & rE); + + bool SetLineAttribute(const DXFBasicEntity & rE); + + bool SetAreaAttribute(const DXFBasicEntity & rE); + + bool SetFontAttribute(const DXFBasicEntity & rE, short nAngle, + sal_uInt16 nHeight); + + void DrawLineEntity(const DXFLineEntity & rE, const DXFTransform & rTransform); + + void DrawPointEntity(const DXFPointEntity & rE, const DXFTransform & rTransform); + + void DrawCircleEntity(const DXFCircleEntity & rE, const DXFTransform & rTransform); + + void DrawArcEntity(const DXFArcEntity & rE, const DXFTransform & rTransform); + + void DrawTraceEntity(const DXFTraceEntity & rE, const DXFTransform & rTransform); + + void DrawSolidEntity(const DXFSolidEntity & rE, const DXFTransform & rTransform); + + void DrawTextEntity(const DXFTextEntity & rE, const DXFTransform & rTransform); + + void DrawInsertEntity(const DXFInsertEntity & rE, const DXFTransform & rTransform); + + void DrawAttribEntity(const DXFAttribEntity & rE, const DXFTransform & rTransform); + + void DrawPolyLineEntity(const DXFPolyLineEntity & rE, const DXFTransform & rTransform); + + void Draw3DFaceEntity(const DXF3DFaceEntity & rE, const DXFTransform & rTransform); + + void DrawDimensionEntity(const DXFDimensionEntity & rE, const DXFTransform & rTransform); + + void DrawLWPolyLineEntity( const DXFLWPolyLineEntity & rE, const DXFTransform & rTransform ); + + void DrawHatchEntity( const DXFHatchEntity & rE, const DXFTransform & rTransform ); + + void DrawEntities(const DXFEntities & rEntities, + const DXFTransform & rTransform); + + void DrawLine(const Point& rA, const Point& rB); + +public: + + DXF2GDIMetaFile(); + ~DXF2GDIMetaFile(); + + bool Convert( const DXFRepresentation & rDXF, GDIMetaFile & rMTF, sal_uInt16 nMinPercent, sal_uInt16 nMaxPercent); + +}; + + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfblkrd.cxx b/vcl/source/filter/idxf/dxfblkrd.cxx new file mode 100644 index 000000000..b5a96b93e --- /dev/null +++ b/vcl/source/filter/idxf/dxfblkrd.cxx @@ -0,0 +1,125 @@ +/* -*- 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 "dxfblkrd.hxx" + + +//---------------- DXFBlock -------------------------------------------------- + + +DXFBlock::DXFBlock() + : pSucc(nullptr) + , nFlags(0) +{ +} + + +DXFBlock::~DXFBlock() +{ +} + + +void DXFBlock::Read(DXFGroupReader & rDGR) +{ + m_sName = ""; + m_sAlsoName = ""; + aBasePoint.fx=0.0; + aBasePoint.fy=0.0; + aBasePoint.fz=0.0; + nFlags=0; + m_sXRef = ""; + + while (rDGR.Read()!=0) + { + switch (rDGR.GetG()) + { + case 2: m_sName = rDGR.GetS(); break; + case 3: m_sAlsoName = rDGR.GetS(); break; + case 70: nFlags=rDGR.GetI(); break; + case 10: aBasePoint.fx=rDGR.GetF(); break; + case 20: aBasePoint.fy=rDGR.GetF(); break; + case 30: aBasePoint.fz=rDGR.GetF(); break; + case 1: m_sXRef = rDGR.GetS(); break; + } + } + DXFEntities::Read(rDGR); +} + + +//---------------- DXFBlocks ------------------------------------------------- + + +DXFBlocks::DXFBlocks() +{ + pFirst=nullptr; +} + + +DXFBlocks::~DXFBlocks() +{ + Clear(); +} + + +void DXFBlocks::Read(DXFGroupReader & rDGR) +{ + DXFBlock * pB, * * ppSucc; + + ppSucc=&pFirst; + while (*ppSucc!=nullptr) ppSucc=&((*ppSucc)->pSucc); + + for (;;) { + while (rDGR.GetG()!=0) rDGR.Read(); + if (rDGR.GetS() == "ENDSEC" || + rDGR.GetS() == "EOF") break; + if (rDGR.GetS() == "BLOCK") { + pB=new DXFBlock; + pB->Read(rDGR); + *ppSucc=pB; + ppSucc=&(pB->pSucc); + } + else rDGR.Read(); + } +} + + +DXFBlock * DXFBlocks::Search(std::string_view rName) const +{ + DXFBlock * pB; + for (pB=pFirst; pB!=nullptr; pB=pB->pSucc) { + if (rName == pB->m_sName) break; + } + return pB; +} + + +void DXFBlocks::Clear() +{ + DXFBlock * ptmp; + + while (pFirst!=nullptr) { + ptmp=pFirst; + pFirst=ptmp->pSucc; + delete ptmp; + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfblkrd.hxx b/vcl/source/filter/idxf/dxfblkrd.hxx new file mode 100644 index 000000000..ca0a0e68a --- /dev/null +++ b/vcl/source/filter/idxf/dxfblkrd.hxx @@ -0,0 +1,83 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFBLKRD_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFBLKRD_HXX + +#include <sal/config.h> + +#include <string_view> + +#include "dxfentrd.hxx" + + +//---------------- A Block (= Set of Entities) -------------------------- + + +class DXFBlock : public DXFEntities { + +public: + + DXFBlock * pSucc; + // pointer to the next block in the list DXFBlocks::pFirst + + // properties of blocks; commented with group codes: + OString m_sName; // 2 + OString m_sAlsoName; // 3 + tools::Long nFlags; // 70 + DXFVector aBasePoint; // 10,20,30 + OString m_sXRef; // 1 + + DXFBlock(); + ~DXFBlock(); + + void Read(DXFGroupReader & rDGR); + // reads the block (including entities) from a dxf file + // by rGDR until an ENDBLK, ENDSEC or EOF. +}; + + +//---------------- A set of blocks ----------------------------------- + + +class DXFBlocks { + + DXFBlock * pFirst; + // list of blocks, READ ONLY! + +public: + + DXFBlocks(); + ~DXFBlocks(); + + void Read(DXFGroupReader & rDGR); + // reads all block per rDGR until an ENDSEC or EOF. + + DXFBlock * Search(std::string_view rName) const; + // looks for a block with the name, return NULL if not successful + + void Clear(); + // deletes all blocks + +}; + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfentrd.cxx b/vcl/source/filter/idxf/dxfentrd.cxx new file mode 100644 index 000000000..b4915c657 --- /dev/null +++ b/vcl/source/filter/idxf/dxfentrd.cxx @@ -0,0 +1,848 @@ +/* -*- 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 <o3tl/safeint.hxx> + +#include "dxfentrd.hxx" + +//--------------------------DXFBasicEntity-------------------------------------- + +DXFBasicEntity::DXFBasicEntity(DXFEntityType eThisType) + : m_sLayer("0") + , m_sLineType("BYLAYER") +{ + eType=eThisType; + pSucc=nullptr; + fThickness=0; + nColor=256; + nSpace=0; + aExtrusion.fx=0.0; + aExtrusion.fy=0.0; + aExtrusion.fz=1.0; +} + +void DXFBasicEntity::Read(DXFGroupReader & rDGR) +{ + while (rDGR.Read()!=0) EvaluateGroup(rDGR); +} + +void DXFBasicEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) + { + case 8: m_sLayer = rDGR.GetS(); break; + case 6: m_sLineType = rDGR.GetS(); break; + case 39: fThickness=rDGR.GetF(); break; + case 62: nColor=rDGR.GetI(); break; + case 67: nSpace=rDGR.GetI(); break; + case 210: aExtrusion.fx=rDGR.GetF(); break; + case 220: aExtrusion.fy=rDGR.GetF(); break; + case 230: aExtrusion.fz=rDGR.GetF(); break; + } +} + +DXFBasicEntity::~DXFBasicEntity() +{ +} + +//--------------------------DXFLineEntity--------------------------------------- + +DXFLineEntity::DXFLineEntity() : DXFBasicEntity(DXF_LINE) +{ +} + +void DXFLineEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFPointEntity-------------------------------------- + +DXFPointEntity::DXFPointEntity() : DXFBasicEntity(DXF_POINT) +{ +} + +void DXFPointEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFCircleEntity------------------------------------- + +DXFCircleEntity::DXFCircleEntity() : DXFBasicEntity(DXF_CIRCLE) +{ + fRadius=1.0; +} + +void DXFCircleEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fRadius=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFArcEntity---------------------------------------- + +DXFArcEntity::DXFArcEntity() : DXFBasicEntity(DXF_ARC) +{ + fRadius=1.0; + fStart=0; + fEnd=360.0; +} + +void DXFArcEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fRadius=rDGR.GetF(); break; + case 50: fStart=rDGR.GetF(); break; + case 51: fEnd=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFTraceEntity-------------------------------------- + +DXFTraceEntity::DXFTraceEntity() : DXFBasicEntity(DXF_TRACE) +{ +} + +void DXFTraceEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + case 12: aP2.fx=rDGR.GetF(); break; + case 22: aP2.fy=rDGR.GetF(); break; + case 32: aP2.fz=rDGR.GetF(); break; + case 13: aP3.fx=rDGR.GetF(); break; + case 23: aP3.fy=rDGR.GetF(); break; + case 33: aP3.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFSolidEntity-------------------------------------- + +DXFSolidEntity::DXFSolidEntity() : DXFBasicEntity(DXF_SOLID) +{ +} + +void DXFSolidEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + case 12: aP2.fx=rDGR.GetF(); break; + case 22: aP2.fy=rDGR.GetF(); break; + case 32: aP2.fz=rDGR.GetF(); break; + case 13: aP3.fx=rDGR.GetF(); break; + case 23: aP3.fy=rDGR.GetF(); break; + case 33: aP3.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFTextEntity--------------------------------------- + +DXFTextEntity::DXFTextEntity() + : DXFBasicEntity(DXF_TEXT) + , m_sStyle("STANDARD") +{ + fHeight=1.0; + fRotAngle=0.0; + fXScale=1.0; + fOblAngle=0.0; + nGenFlags=0; + nHorzJust=0; + nVertJust=0; +} + +void DXFTextEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fHeight=rDGR.GetF(); break; + case 1: m_sText = rDGR.GetS(); break; + case 50: fRotAngle=rDGR.GetF(); break; + case 41: fXScale=rDGR.GetF(); break; + case 42: fOblAngle=rDGR.GetF(); break; + case 7: m_sStyle = rDGR.GetS(); break; + case 71: nGenFlags=rDGR.GetI(); break; + case 72: nHorzJust=rDGR.GetI(); break; + case 73: nVertJust=rDGR.GetI(); break; + case 11: aAlign.fx=rDGR.GetF(); break; + case 21: aAlign.fy=rDGR.GetF(); break; + case 31: aAlign.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFShapeEntity-------------------------------------- + +DXFShapeEntity::DXFShapeEntity() : DXFBasicEntity(DXF_SHAPE) +{ + fSize=1.0; + fRotAngle=0; + fXScale=1.0; + fOblAngle=0; +} + +void DXFShapeEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fSize=rDGR.GetF(); break; + case 2: m_sName = rDGR.GetS(); break; + case 50: fRotAngle=rDGR.GetF(); break; + case 41: fXScale=rDGR.GetF(); break; + case 51: fOblAngle=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFInsertEntity------------------------------------- + +DXFInsertEntity::DXFInsertEntity() : DXFBasicEntity(DXF_INSERT) +{ + nAttrFlag=0; + fXScale=1.0; + fYScale=1.0; + fZScale=1.0; + fRotAngle=0.0; + nColCount=1; + nRowCount=1; + fColSpace=0.0; + fRowSpace=0.0; +} + +void DXFInsertEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 66: nAttrFlag=rDGR.GetI(); break; + case 2: m_sName = rDGR.GetS(); break; + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 41: fXScale=rDGR.GetF(); break; + case 42: fYScale=rDGR.GetF(); break; + case 43: fZScale=rDGR.GetF(); break; + case 50: fRotAngle=rDGR.GetF(); break; + case 70: nColCount=rDGR.GetI(); break; + case 71: nRowCount=rDGR.GetI(); break; + case 44: fColSpace=rDGR.GetF(); break; + case 45: fRowSpace=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFAttDefEntity------------------------------------- + +DXFAttDefEntity::DXFAttDefEntity() + : DXFBasicEntity(DXF_ATTDEF) + , m_sStyle("STANDARD") +{ + fHeight=1.0; + nAttrFlags=0; + nFieldLen=0; + fRotAngle=0.0; + fXScale=1.0; + fOblAngle=0.0; + nGenFlags=0; + nHorzJust=0; + nVertJust=0; +} + +void DXFAttDefEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fHeight=rDGR.GetF(); break; + case 1: m_sDefVal = rDGR.GetS(); break; + case 3: m_sPrompt = rDGR.GetS(); break; + case 2: m_sTagStr = rDGR.GetS(); break; + case 70: nAttrFlags=rDGR.GetI(); break; + case 73: nFieldLen=rDGR.GetI(); break; + case 50: fRotAngle=rDGR.GetF(); break; + case 41: fXScale=rDGR.GetF(); break; + case 51: fOblAngle=rDGR.GetF(); break; + case 7: m_sStyle = rDGR.GetS(); break; + case 71: nGenFlags=rDGR.GetI(); break; + case 72: nHorzJust=rDGR.GetI(); break; + case 74: nVertJust=rDGR.GetI(); break; + case 11: aAlign.fx=rDGR.GetF(); break; + case 21: aAlign.fy=rDGR.GetF(); break; + case 31: aAlign.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFAttribEntity------------------------------------- + +DXFAttribEntity::DXFAttribEntity() + : DXFBasicEntity(DXF_ATTRIB) + , m_sStyle("STANDARD") +{ + fHeight=1.0; + nAttrFlags=0; + nFieldLen=0; + fRotAngle=0.0; + fXScale=1.0; + fOblAngle=0.0; + nGenFlags=0; + nHorzJust=0; + nVertJust=0; +} + +void DXFAttribEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fHeight=rDGR.GetF(); break; + case 1: m_sText = rDGR.GetS(); break; + case 2: m_sTagStr = rDGR.GetS(); break; + case 70: nAttrFlags=rDGR.GetI(); break; + case 73: nFieldLen=rDGR.GetI(); break; + case 50: fRotAngle=rDGR.GetF(); break; + case 41: fXScale=rDGR.GetF(); break; + case 51: fOblAngle=rDGR.GetF(); break; + case 7: m_sStyle = rDGR.GetS(); break; + case 71: nGenFlags=rDGR.GetI(); break; + case 72: nHorzJust=rDGR.GetI(); break; + case 74: nVertJust=rDGR.GetI(); break; + case 11: aAlign.fx=rDGR.GetF(); break; + case 21: aAlign.fy=rDGR.GetF(); break; + case 31: aAlign.fz=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFPolyLine----------------------------------------- + +DXFPolyLineEntity::DXFPolyLineEntity() : DXFBasicEntity(DXF_POLYLINE) +{ + nFlags=0; + fSWidth=0.0; + fEWidth=0.0; + nMeshMCount=0; + nMeshNCount=0; + nMDensity=0; + nNDensity=0; + nCSSType=0; +} + +void DXFPolyLineEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 70: nFlags=rDGR.GetI(); break; + case 40: fSWidth=rDGR.GetF(); break; + case 41: fEWidth=rDGR.GetF(); break; + case 71: nMeshMCount=rDGR.GetI(); break; + case 72: nMeshNCount=rDGR.GetI(); break; + case 73: nMDensity=rDGR.GetI(); break; + case 74: nNDensity=rDGR.GetI(); break; + case 75: nCSSType=rDGR.GetI(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFLWPolyLine--------------------------------------- + +DXFLWPolyLineEntity::DXFLWPolyLineEntity() : + DXFBasicEntity( DXF_LWPOLYLINE ), + nIndex( 0 ), + nCount( 0 ), + nFlags( 0 ), + fConstantWidth( 0.0 ), + fStartWidth( 0.0 ), + fEndWidth( 0.0 ) +{ +} + +void DXFLWPolyLineEntity::EvaluateGroup( DXFGroupReader & rDGR ) +{ + switch ( rDGR.GetG() ) + { + case 90 : + { + nCount = rDGR.GetI(); + // limit alloc to max reasonable size based on remaining data in stream + if (nCount > 0 && o3tl::make_unsigned(nCount) <= rDGR.remainingSize()) + aP.reserve(nCount); + else + nCount = 0; + } + break; + case 70: nFlags = rDGR.GetI(); break; + case 43: fConstantWidth = rDGR.GetF(); break; + case 40: fStartWidth = rDGR.GetF(); break; + case 41: fEndWidth = rDGR.GetF(); break; + case 10: + { + if (nIndex < nCount) + { + aP.resize(nIndex+1); + aP[nIndex].fx = rDGR.GetF(); + } + } + break; + case 20: + { + if (nIndex < nCount) + { + aP.resize(nIndex+1); + aP[nIndex].fy = rDGR.GetF(); + ++nIndex; + } + } + break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFHatchEntity------------------------------------- + +DXFEdgeTypeLine::DXFEdgeTypeLine() : + DXFEdgeType( 1 ) +{ + +} + +bool DXFEdgeTypeLine::EvaluateGroup( DXFGroupReader & rDGR ) +{ + bool bExecutingGroupCode = true; + switch ( rDGR.GetG() ) + { + case 10 : aStartPoint.fx = rDGR.GetF(); break; + case 20 : aStartPoint.fy = rDGR.GetF(); break; + case 11 : aEndPoint.fx = rDGR.GetF(); break; + case 21 : aEndPoint.fy = rDGR.GetF(); break; + default : bExecutingGroupCode = false; break; + } + return bExecutingGroupCode; +} + +DXFEdgeTypeCircularArc::DXFEdgeTypeCircularArc() : + DXFEdgeType( 2 ), + fRadius( 0.0 ), + fStartAngle( 0.0 ), + fEndAngle( 0.0 ), + nIsCounterClockwiseFlag( 0 ) +{ +} + +bool DXFEdgeTypeCircularArc::EvaluateGroup( DXFGroupReader & rDGR ) +{ + bool bExecutingGroupCode = true; + switch ( rDGR.GetG() ) + { + case 10 : aCenter.fx = rDGR.GetF(); break; + case 20 : aCenter.fy = rDGR.GetF(); break; + case 40 : fRadius = rDGR.GetF(); break; + case 50 : fStartAngle = rDGR.GetF(); break; + case 51 : fEndAngle = rDGR.GetF(); break; + case 73 : nIsCounterClockwiseFlag = rDGR.GetI(); break; + default : bExecutingGroupCode = false; break; + } + return bExecutingGroupCode; +} + +DXFEdgeTypeEllipticalArc::DXFEdgeTypeEllipticalArc() : + DXFEdgeType( 3 ), + fLength( 0.0 ), + fStartAngle( 0.0 ), + fEndAngle( 0.0 ), + nIsCounterClockwiseFlag( 0 ) +{ +} + +bool DXFEdgeTypeEllipticalArc::EvaluateGroup( DXFGroupReader & rDGR ) +{ + bool bExecutingGroupCode = true; + switch( rDGR.GetG() ) + { + case 10 : aCenter.fx = rDGR.GetF(); break; + case 20 : aCenter.fy = rDGR.GetF(); break; + case 11 : aEndPoint.fx = rDGR.GetF(); break; + case 21 : aEndPoint.fy = rDGR.GetF(); break; + case 40 : fLength = rDGR.GetF(); break; + case 50 : fStartAngle = rDGR.GetF(); break; + case 51 : fEndAngle = rDGR.GetF(); break; + case 73 : nIsCounterClockwiseFlag = rDGR.GetI(); break; + default : bExecutingGroupCode = false; break; + } + return bExecutingGroupCode; +} + +DXFEdgeTypeSpline::DXFEdgeTypeSpline() : + DXFEdgeType( 4 ), + nDegree( 0 ), + nRational( 0 ), + nPeriodic( 0 ), + nKnotCount( 0 ), + nControlCount( 0 ) +{ +} + +bool DXFEdgeTypeSpline::EvaluateGroup( DXFGroupReader & rDGR ) +{ + bool bExecutingGroupCode = true; + switch ( rDGR.GetG() ) + { + case 94 : nDegree = rDGR.GetI(); break; + case 73 : nRational = rDGR.GetI(); break; + case 74 : nPeriodic = rDGR.GetI(); break; + case 95 : nKnotCount = rDGR.GetI(); break; + case 96 : nControlCount = rDGR.GetI(); break; + default : bExecutingGroupCode = false; break; + } + return bExecutingGroupCode; +} + +DXFBoundaryPathData::DXFBoundaryPathData() : + nPointCount( 0 ), + nFlags( 0 ), + nHasBulgeFlag( 0 ), + nIsClosedFlag( 0 ), + fBulge( 0.0 ), + nSourceBoundaryObjects( 0 ), + nEdgeCount( 0 ), + bIsPolyLine( true ), + nPointIndex( 0 ) +{ +} + +bool DXFBoundaryPathData::EvaluateGroup( DXFGroupReader & rDGR ) +{ + bool bExecutingGroupCode = true; + if ( bIsPolyLine ) + { + switch( rDGR.GetG() ) + { + case 92 : + { + nFlags = rDGR.GetI(); + if ( ( nFlags & 2 ) == 0 ) + bIsPolyLine = false; + } + break; + case 93 : + { + nPointCount = rDGR.GetI(); + // limit alloc to max reasonable size based on remaining data in stream + if (nPointCount > 0 && o3tl::make_unsigned(nPointCount) <= rDGR.remainingSize()) + aP.reserve(nPointCount); + else + nPointCount = 0; + } + break; + case 72 : nHasBulgeFlag = rDGR.GetI(); break; + case 73 : nIsClosedFlag = rDGR.GetI(); break; + case 97 : nSourceBoundaryObjects = rDGR.GetI(); break; + case 42 : fBulge = rDGR.GetF(); break; + case 10: + { + if (nPointIndex < nPointCount) + { + aP.resize(nPointIndex+1); + aP[nPointIndex].fx = rDGR.GetF(); + } + } + break; + case 20: + { + if (nPointIndex < nPointCount) + { + aP.resize(nPointIndex+1); + aP[nPointIndex].fy = rDGR.GetF(); + ++nPointIndex; + } + } + break; + + default : bExecutingGroupCode = false; break; + } + } + else + { + if ( rDGR.GetG() == 93 ) + nEdgeCount = rDGR.GetI(); + else if ( rDGR.GetG() == 72 ) + { + sal_Int32 nEdgeType = rDGR.GetI(); + switch( nEdgeType ) + { + case 1 : aEdges.emplace_back( new DXFEdgeTypeLine() ); break; + case 2 : aEdges.emplace_back( new DXFEdgeTypeCircularArc() ); break; + case 3 : aEdges.emplace_back( new DXFEdgeTypeEllipticalArc() ); break; + case 4 : aEdges.emplace_back( new DXFEdgeTypeSpline() ); break; + } + } + else if ( !aEdges.empty() ) + aEdges.back()->EvaluateGroup( rDGR ); + else + bExecutingGroupCode = false; + } + return bExecutingGroupCode; +} + +DXFHatchEntity::DXFHatchEntity() : + DXFBasicEntity( DXF_HATCH ), + bIsInBoundaryPathContext( false ), + nCurrentBoundaryPathIndex( -1 ), + nFlags( 0 ), + nAssociativityFlag( 0 ), + nMaxBoundaryPathCount( 0 ), + nHatchStyle( 0 ), + nHatchPatternType( 0 ), + fHatchPatternAngle( 0.0 ), + fHatchPatternScale( 1.0 ), + nHatchDoubleFlag( 0 ), + nHatchPatternDefinitionLines( 0 ), + fPixelSize( 1.0 ), + nNumberOfSeedPoints( 0 ) +{ +} + +void DXFHatchEntity::EvaluateGroup( DXFGroupReader & rDGR ) +{ + switch ( rDGR.GetG() ) + { +// case 10 : aElevationPoint.fx = rDGR.GetF(); break; +// case 20 : aElevationPoint.fy = rDGR.GetF(); break; +// case 30 : aElevationPoint.fz = rDGR.GetF(); break; + case 70 : nFlags = rDGR.GetI(); break; + case 71 : nAssociativityFlag = rDGR.GetI(); break; + case 91 : + { + bIsInBoundaryPathContext = true; + nMaxBoundaryPathCount = rDGR.GetI(); + // limit alloc to max reasonable size based on remaining data in stream + if (nMaxBoundaryPathCount > 0 && o3tl::make_unsigned(nMaxBoundaryPathCount) <= rDGR.remainingSize()) + aBoundaryPathData.reserve(nMaxBoundaryPathCount); + else + nMaxBoundaryPathCount = 0; + } + break; + case 75 : + { + nHatchStyle = rDGR.GetI(); + bIsInBoundaryPathContext = false; + } + break; + case 76 : nHatchPatternType = rDGR.GetI(); break; + case 52 : fHatchPatternAngle = rDGR.GetF(); break; + case 41 : fHatchPatternScale = rDGR.GetF(); break; + case 77 : nHatchDoubleFlag = rDGR.GetI(); break; + case 78 : nHatchPatternDefinitionLines = rDGR.GetI(); break; + case 47 : fPixelSize = rDGR.GetF(); break; + case 98 : nNumberOfSeedPoints = rDGR.GetI(); break; + + case 92: + nCurrentBoundaryPathIndex++; + [[fallthrough]]; + default: + { + bool bExecutingGroupCode = false; + if ( bIsInBoundaryPathContext ) + { + if (nCurrentBoundaryPathIndex >= 0 && nCurrentBoundaryPathIndex < nMaxBoundaryPathCount) + { + aBoundaryPathData.resize(nCurrentBoundaryPathIndex + 1); + bExecutingGroupCode = aBoundaryPathData.back().EvaluateGroup(rDGR); + } + } + if ( !bExecutingGroupCode ) + DXFBasicEntity::EvaluateGroup(rDGR); + } + break; + } +} + +//--------------------------DXFVertexEntity------------------------------------- + +DXFVertexEntity::DXFVertexEntity() : DXFBasicEntity(DXF_VERTEX) +{ + fSWidth=-1.0; + fEWidth=-1.0; + fBulge=0.0; + nFlags=0; + fCFTDir=0.0; + +} + +void DXFVertexEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 40: fSWidth=rDGR.GetF(); break; + case 41: fEWidth=rDGR.GetF(); break; + case 42: fBulge=rDGR.GetF(); break; + case 70: nFlags=rDGR.GetI(); break; + case 50: fCFTDir=rDGR.GetF(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//--------------------------DXFSeqEndEntity------------------------------------- + +DXFSeqEndEntity::DXFSeqEndEntity() : DXFBasicEntity(DXF_SEQEND) +{ +} + +//--------------------------DXF3DFace------------------------------------------- + +DXF3DFaceEntity::DXF3DFaceEntity() : DXFBasicEntity(DXF_3DFACE) +{ + nIEFlags=0; +} + +void DXF3DFaceEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 10: aP0.fx=rDGR.GetF(); break; + case 20: aP0.fy=rDGR.GetF(); break; + case 30: aP0.fz=rDGR.GetF(); break; + case 11: aP1.fx=rDGR.GetF(); break; + case 21: aP1.fy=rDGR.GetF(); break; + case 31: aP1.fz=rDGR.GetF(); break; + case 12: aP2.fx=rDGR.GetF(); break; + case 22: aP2.fy=rDGR.GetF(); break; + case 32: aP2.fz=rDGR.GetF(); break; + case 13: aP3.fx=rDGR.GetF(); break; + case 23: aP3.fy=rDGR.GetF(); break; + case 33: aP3.fz=rDGR.GetF(); break; + case 70: nIEFlags=rDGR.GetI(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + + +//--------------------------DXFDimensionEntity---------------------------------- + +DXFDimensionEntity::DXFDimensionEntity() : DXFBasicEntity(DXF_DIMENSION) +{ +} + +void DXFDimensionEntity::EvaluateGroup(DXFGroupReader & rDGR) +{ + switch (rDGR.GetG()) { + case 2: m_sPseudoBlock = rDGR.GetS(); break; + default: DXFBasicEntity::EvaluateGroup(rDGR); + } +} + +//---------------------------- DXFEntities -------------------------------------- + +void DXFEntities::Read(DXFGroupReader & rDGR) +{ + DXFBasicEntity * pE, * * ppSucc; + + ppSucc=&pFirst; + while (*ppSucc!=nullptr) ppSucc=&((*ppSucc)->pSucc); + + while (rDGR.GetG()!=0) rDGR.Read(); + + while (rDGR.GetS()!="ENDBLK" && + rDGR.GetS()!="ENDSEC" && + rDGR.GetS()!="EOF" ) + { + + if (rDGR.GetS() == "LINE" ) pE=new DXFLineEntity; + else if (rDGR.GetS() == "POINT" ) pE=new DXFPointEntity; + else if (rDGR.GetS() == "CIRCLE" ) pE=new DXFCircleEntity; + else if (rDGR.GetS() == "ARC" ) pE=new DXFArcEntity; + else if (rDGR.GetS() == "TRACE" ) pE=new DXFTraceEntity; + else if (rDGR.GetS() == "SOLID" ) pE=new DXFSolidEntity; + else if (rDGR.GetS() == "TEXT" ) pE=new DXFTextEntity; + else if (rDGR.GetS() == "SHAPE" ) pE=new DXFShapeEntity; + else if (rDGR.GetS() == "INSERT" ) pE=new DXFInsertEntity; + else if (rDGR.GetS() == "ATTDEF" ) pE=new DXFAttDefEntity; + else if (rDGR.GetS() == "ATTRIB" ) pE=new DXFAttribEntity; + else if (rDGR.GetS() == "POLYLINE" ) pE=new DXFPolyLineEntity; + else if (rDGR.GetS() == "LWPOLYLINE") pE=new DXFLWPolyLineEntity; + else if (rDGR.GetS() == "VERTEX" ) pE=new DXFVertexEntity; + else if (rDGR.GetS() == "SEQEND" ) pE=new DXFSeqEndEntity; + else if (rDGR.GetS() == "3DFACE" ) pE=new DXF3DFaceEntity; + else if (rDGR.GetS() == "DIMENSION" ) pE=new DXFDimensionEntity; + else if (rDGR.GetS() == "HATCH" ) pE=new DXFHatchEntity; + else + { + do { + rDGR.Read(); + } while (rDGR.GetG()!=0); + continue; + } + *ppSucc=pE; + ppSucc=&(pE->pSucc); + pE->Read(rDGR); + } +} + +void DXFEntities::Clear() +{ + DXFBasicEntity * ptmp; + + while (pFirst!=nullptr) { + ptmp=pFirst; + pFirst=ptmp->pSucc; + delete ptmp; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfentrd.hxx b/vcl/source/filter/idxf/dxfentrd.hxx new file mode 100644 index 000000000..85dbf7dd0 --- /dev/null +++ b/vcl/source/filter/idxf/dxfentrd.hxx @@ -0,0 +1,538 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFENTRD_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFENTRD_HXX + +#include "dxfgrprd.hxx" +#include "dxfvec.hxx" +#include <tools/long.hxx> + +#include <memory> +#include <vector> + +enum DXFEntityType { + DXF_LINE, + DXF_POINT, + DXF_CIRCLE, + DXF_ARC, + DXF_TRACE, + DXF_SOLID, + DXF_TEXT, + DXF_SHAPE, + DXF_INSERT, + DXF_ATTDEF, + DXF_ATTRIB, + DXF_POLYLINE, + DXF_VERTEX, + DXF_SEQEND, + DXF_3DFACE, + DXF_DIMENSION, + DXF_LWPOLYLINE, + DXF_HATCH +}; + +// base class of an entity + +class DXFBasicEntity { + +public: + + DXFBasicEntity * pSucc; + // pointer to next entity (in the list of DXFEntities.pFirst) + + DXFEntityType eType; + // entity kind (line or circle or what) + + // properties that all entities have, each + // commented with group codes: + OString m_sLayer; // 8 + OString m_sLineType; // 6 + double fThickness; // 39 + tools::Long nColor; // 62 + tools::Long nSpace; // 67 + DXFVector aExtrusion; // 210,220,230 + +protected: + + DXFBasicEntity(DXFEntityType eThisType); + // always initialize the constructors of entities with default values + +public: + + virtual ~DXFBasicEntity(); + void Read(DXFGroupReader & rDGR); + // Reads a parameter till the next 0-group + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR); + // This method will be called by Read() for every parameter (respectively + // for every group). + // As far as the group code of the entity is known, the corresponding + // parameter is fetched. + +}; + + +// the different kinds of entities + +class DXFLineEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + DXFVector aP1; // 11,21,31 + + DXFLineEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFPointEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + + DXFPointEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFCircleEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + double fRadius; // 40 + + DXFCircleEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFArcEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + double fRadius; // 40 + double fStart; // 50 + double fEnd; // 51 + + DXFArcEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFTraceEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + DXFVector aP1; // 11,21,31 + DXFVector aP2; // 12,22,32 + DXFVector aP3; // 13,23,33 + + DXFTraceEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFSolidEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + DXFVector aP1; // 11,21,31 + DXFVector aP2; // 12,22,32 + DXFVector aP3; // 13,23,33 + + DXFSolidEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFTextEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + double fHeight; // 40 + OString m_sText; // 1 + double fRotAngle; // 50 + double fXScale; // 41 + double fOblAngle; // 42 + OString m_sStyle; // 7 + tools::Long nGenFlags; // 71 + tools::Long nHorzJust; // 72 + tools::Long nVertJust; // 73 + DXFVector aAlign; // 11,21,31 + + DXFTextEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFShapeEntity : public DXFBasicEntity { + + DXFVector aP0; // 10,20,30 + double fSize; // 40 + OString m_sName; // 2 + double fRotAngle; // 50 + double fXScale; // 41 + double fOblAngle; // 51 + +public: + + DXFShapeEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFInsertEntity : public DXFBasicEntity { + +public: + + tools::Long nAttrFlag; // 66 + OString m_sName; // 2 + DXFVector aP0; // 10,20,30 + double fXScale; // 41 + double fYScale; // 42 + double fZScale; // 43 + double fRotAngle; // 50 + tools::Long nColCount; // 70 + tools::Long nRowCount; // 71 + double fColSpace; // 44 + double fRowSpace; // 45 + + DXFInsertEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFAttDefEntity : public DXFBasicEntity { + + DXFVector aP0; // 10,20,30 + double fHeight; // 40 + OString m_sDefVal; // 1 + OString m_sPrompt; // 3 + OString m_sTagStr; // 2 + tools::Long nAttrFlags; // 70 + tools::Long nFieldLen; // 73 + double fRotAngle; // 50 + double fXScale; // 41 + double fOblAngle; // 51 + OString m_sStyle; // 7 + tools::Long nGenFlags; // 71 + tools::Long nHorzJust; // 72 + tools::Long nVertJust; // 74 + DXFVector aAlign; // 11,21,31 + +public: + + DXFAttDefEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFAttribEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + double fHeight; // 40 + OString m_sText; // 1 + OString m_sTagStr; // 2 + tools::Long nAttrFlags; // 70 + tools::Long nFieldLen; // 73 + double fRotAngle; // 50 + double fXScale; // 41 + double fOblAngle; // 51 + OString m_sStyle; // 7 + tools::Long nGenFlags; // 71 + tools::Long nHorzJust; // 72 + tools::Long nVertJust; // 74 + DXFVector aAlign; // 11,21,31 + + DXFAttribEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFPolyLineEntity : public DXFBasicEntity { + +public: + + tools::Long nFlags; // 70 + double fSWidth; // 40 + double fEWidth; // 41 + tools::Long nMeshMCount; // 71 + tools::Long nMeshNCount; // 72 + tools::Long nMDensity; // 73 + tools::Long nNDensity; // 74 + tools::Long nCSSType; // 75 + + DXFPolyLineEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFLWPolyLineEntity : public DXFBasicEntity +{ + sal_Int32 nIndex; + sal_Int32 nCount; // 90 + + public: + + sal_Int32 nFlags; // 70 1 = closed, 128 = plinegen + double fConstantWidth; // 43 (optional - default: 0, not used if fStartWidth and/or fEndWidth is used) + double fStartWidth; // 40 + double fEndWidth; // 41 + + std::vector<DXFVector> aP; + + DXFLWPolyLineEntity(); + + protected: + + virtual void EvaluateGroup( DXFGroupReader & rDGR ) override; + +}; + +struct DXFEdgeType +{ + sal_Int32 nEdgeType; + + virtual ~DXFEdgeType(){}; + virtual bool EvaluateGroup( DXFGroupReader & /*rDGR*/ ){ return true; }; + + protected: + + DXFEdgeType( sal_Int32 EdgeType ):nEdgeType(EdgeType){}; +}; + +struct DXFEdgeTypeLine : public DXFEdgeType +{ + DXFVector aStartPoint; // 10,20 + DXFVector aEndPoint; // 11,21 + DXFEdgeTypeLine(); + virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override; +}; + +struct DXFEdgeTypeCircularArc : public DXFEdgeType +{ + DXFVector aCenter; // 10,20 + double fRadius; // 40 + double fStartAngle; // 50 + double fEndAngle; // 51 + sal_Int32 nIsCounterClockwiseFlag; // 73 + DXFEdgeTypeCircularArc(); + virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override; +}; + +struct DXFEdgeTypeEllipticalArc : public DXFEdgeType +{ + DXFVector aCenter; // 10,20 + DXFVector aEndPoint; // 11,21 + double fLength; // 40 + double fStartAngle; // 50 + double fEndAngle; // 51 + sal_Int32 nIsCounterClockwiseFlag; // 73 + + DXFEdgeTypeEllipticalArc(); + virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override; +}; + +struct DXFEdgeTypeSpline : public DXFEdgeType +{ + sal_Int32 nDegree; // 94 + sal_Int32 nRational; // 73 + sal_Int32 nPeriodic; // 74 + sal_Int32 nKnotCount; // 75 + sal_Int32 nControlCount; // 76 + + DXFEdgeTypeSpline(); + virtual bool EvaluateGroup( DXFGroupReader & rDGR ) override; +}; + +struct DXFBoundaryPathData +{ +private: + sal_Int32 nPointCount; // 93 +public: + sal_Int32 nFlags; // 92 + sal_Int32 nHasBulgeFlag; // 72 + sal_Int32 nIsClosedFlag; // 73 + double fBulge; // 42 + sal_Int32 nSourceBoundaryObjects; // 97 + sal_Int32 nEdgeCount; // 93 + + bool bIsPolyLine; + sal_Int32 nPointIndex; + + std::vector<DXFVector> aP; + std::vector<std::unique_ptr<DXFEdgeType>> aEdges; + + DXFBoundaryPathData(); + + bool EvaluateGroup( DXFGroupReader & rDGR ); +}; + +class DXFHatchEntity : public DXFBasicEntity +{ + bool bIsInBoundaryPathContext; + sal_Int32 nCurrentBoundaryPathIndex; + + public: + + sal_Int32 nFlags; // 70 (solid fill = 1, pattern fill = 0) + sal_Int32 nAssociativityFlag; // 71 (associative = 1, non-associative = 0) + sal_Int32 nMaxBoundaryPathCount; // 91 + sal_Int32 nHatchStyle; // 75 (odd parity = 0, outmost area = 1, entire area = 2 ) + sal_Int32 nHatchPatternType; // 76 (user defined = 0, predefined = 1, custom = 2) + double fHatchPatternAngle; // 52 (pattern fill only) + double fHatchPatternScale; // 41 (pattern fill only:scale or spacing) + sal_Int32 nHatchDoubleFlag; // 77 (pattern fill only:double = 1, not double = 0) + sal_Int32 nHatchPatternDefinitionLines; // 78 + double fPixelSize; // 47 + sal_Int32 nNumberOfSeedPoints; // 98 + + std::vector<DXFBoundaryPathData> aBoundaryPathData; + + DXFHatchEntity(); + + protected: + + virtual void EvaluateGroup( DXFGroupReader & rDGR ) override; +}; + +class DXFVertexEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + double fSWidth; // 40 (if <0.0, then one has DXFPolyLine::fSWidth) + double fEWidth; // 41 (if <0.0, then one has DXFPolyLine::fEWidth) + double fBulge; // 42 + tools::Long nFlags; // 70 + double fCFTDir; // 50 + + DXFVertexEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFSeqEndEntity : public DXFBasicEntity { + +public: + + DXFSeqEndEntity(); +}; + +class DXF3DFaceEntity : public DXFBasicEntity { + +public: + + DXFVector aP0; // 10,20,30 + DXFVector aP1; // 11,21,31 + DXFVector aP2; // 12,22,32 + DXFVector aP3; // 13,23,33 + tools::Long nIEFlags; // 70 + + DXF3DFaceEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + +class DXFDimensionEntity : public DXFBasicEntity { + +public: + + OString m_sPseudoBlock; // 2 + + DXFDimensionEntity(); + +protected: + + virtual void EvaluateGroup(DXFGroupReader & rDGR) override; +}; + + +// read and represent the set of entities +class DXFEntities { + +public: + + DXFEntities() + : pFirst(nullptr) + , mbBeingDrawn(false) + { + } + + ~DXFEntities() + { + Clear(); + } + + DXFBasicEntity * pFirst; // list of entities, READ ONLY! + mutable bool mbBeingDrawn; // guard for loop in entity parsing + + void Read(DXFGroupReader & rDGR); + // read entities by rGDR of a DXF file until a + // ENDBLK, ENDSEC or EOF (of group 0). + // (all unknown thing will be skipped) + + void Clear(); + // deletes all entities +}; + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfgrprd.cxx b/vcl/source/filter/idxf/dxfgrprd.cxx new file mode 100644 index 000000000..48cdb3660 --- /dev/null +++ b/vcl/source/filter/idxf/dxfgrprd.cxx @@ -0,0 +1,213 @@ +/* -*- 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 <stdlib.h> +#include <rtl/strbuf.hxx> +#include <tools/stream.hxx> +#include <o3tl/string_view.hxx> +#include "dxfgrprd.hxx" + +// we use an own ReadLine function, because Stream::ReadLine stops if +// a 0-sign occurs; this function converts 0-signs to blanks and reads +// a complete line until a cr/lf is found + +static OString DXFReadLine(SvStream& rIStm) +{ + char buf[256 + 1]; + bool bEnd = false; + sal_uInt64 nOldFilePos = rIStm.Tell(); + char c = 0; + + OStringBuffer aBuf(512); + + while( !bEnd && !rIStm.GetError() ) // !!! do not check for EOF + // !!! because we read blockwise + { + sal_uInt16 nLen = static_cast<sal_uInt16>(rIStm.ReadBytes(buf, sizeof(buf)-1)); + if( !nLen ) + { + if( aBuf.isEmpty() ) + return OString(); + else + break; + } + + for( sal_uInt16 n = 0; n < nLen ; n++ ) + { + c = buf[n]; + if( c != '\n' && c != '\r' ) + { + if( !c ) + c = ' '; + aBuf.append(c); + } + else + { + bEnd = true; + break; + } + } + } + + if( !bEnd && !rIStm.GetError() && !aBuf.isEmpty() ) + bEnd = true; + + nOldFilePos += aBuf.getLength(); + if( rIStm.Tell() > nOldFilePos ) + nOldFilePos++; + rIStm.Seek( nOldFilePos ); // seek because of BlockRead above! + + if( bEnd && (c=='\r' || c=='\n')) // special treatment of DOS files + { + char cTemp(0); + rIStm.ReadBytes(&cTemp, 1); + if( cTemp == c || (cTemp != '\n' && cTemp != '\r') ) + rIStm.Seek( nOldFilePos ); + } + + return aBuf.makeStringAndClear(); +} + +static void DXFSkipLine(SvStream& rIStm) +{ + while (rIStm.good()) + { + char buf[256 + 1]; + sal_uInt16 nLen = static_cast<sal_uInt16>(rIStm.ReadBytes(buf, sizeof(buf) - 1)); + for (sal_uInt16 n = 0; n < nLen; n++) + { + char c = buf[n]; + if ((c == '\n') || (c == '\r')) + { + rIStm.SeekRel(n-nLen+1); // return stream to next to current position + char c1 = 0; + rIStm.ReadBytes(&c1, 1); + if (c1 == c || (c1 != '\n' && c1!= '\r')) + rIStm.SeekRel(-1); + return; + } + } + } +} + +DXFGroupReader::DXFGroupReader(SvStream & rIStream) + : rIS(rIStream) + , bStatus(true) + , nLastG(0) + , I(0) +{ + rIS.Seek(0); +} + +sal_uInt16 DXFGroupReader::Read() +{ + sal_uInt16 nG = 0; + if ( bStatus ) + { + nG = static_cast<sal_uInt16>(ReadI()); + if ( bStatus ) + { + if (nG< 10) ReadS(); + else if (nG< 60) F = ReadF(); + else if (nG< 80) I = ReadI(); + else if (nG< 90) DXFSkipLine(rIS); + else if (nG< 99) I = ReadI(); + else if (nG==100) ReadS(); + else if (nG==102) ReadS(); + else if (nG==105) DXFSkipLine(rIS); + else if (nG< 140) DXFSkipLine(rIS); + else if (nG< 148) F = ReadF(); + else if (nG< 170) DXFSkipLine(rIS); + else if (nG< 176) I = ReadI(); + else if (nG< 180) DXFSkipLine(rIS); // ReadI(); + else if (nG< 210) DXFSkipLine(rIS); + else if (nG< 240) F = ReadF(); + else if (nG<=369) DXFSkipLine(rIS); + else if (nG< 999) DXFSkipLine(rIS); + else if (nG<1010) ReadS(); + else if (nG<1060) F = ReadF(); + else if (nG<1072) I = ReadI(); + else bStatus = false; + } + } + if ( !bStatus ) + { + nG = 0; + S = "EOF"; + } + nLastG = nG; + return nG; +} + +tools::Long DXFGroupReader::ReadI() +{ + OString s = DXFReadLine(rIS); + char *p=s.pData->buffer; + const char *end = s.pData->buffer + s.pData->length; + + while((p != end) && (*p==0x20)) p++; + + if ((p == end) || ((*p<'0' || *p>'9') && *p!='-')) { + bStatus=false; + return 0; + } + + OStringBuffer aNumber; + if (*p == '-') { + aNumber.append(*p++); + } + + while ((p != end) && *p >= '0' && *p <= '9') { + aNumber.append(*p++); + } + + while ((p != end) && (*p==0x20)) p++; + if (p != end) { + bStatus=false; + return 0; + } + + return o3tl::toInt32(aNumber); +} + +double DXFGroupReader::ReadF() +{ + OString s = DXFReadLine(rIS); + char *p = s.pData->buffer; + const char *end = s.pData->buffer + s.pData->length; + + while((p != end) && (*p==0x20)) p++; + if ((p == end) || ((*p<'0' || *p>'9') && *p!='.' && *p!='-')) { + bStatus=false; + return 0.0; + } + return atof(p); +} + +void DXFGroupReader::ReadS() +{ + S = DXFReadLine(rIS); +} + +sal_uInt64 DXFGroupReader::remainingSize() const +{ + return rIS.remainingSize(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfgrprd.hxx b/vcl/source/filter/idxf/dxfgrprd.hxx new file mode 100644 index 000000000..4d20ae2bf --- /dev/null +++ b/vcl/source/filter/idxf/dxfgrprd.hxx @@ -0,0 +1,115 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFGRPRD_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFGRPRD_HXX + +#include <rtl/string.hxx> +#include <sal/types.h> +#include <tools/long.hxx> + +class SvStream; + +class DXFGroupReader +{ +public: + explicit DXFGroupReader( SvStream & rIStream ); + + bool GetStatus() const; + + void SetError(); + + sal_uInt16 Read(); + // Reads next group and returns the group code. + // In case of an error GetStatus() returns sal_False, group code will be set + // to 0 and SetS(0,"EOF") will be executed. + bool Read(sal_uInt16 nExpectedG) { return Read() == nExpectedG; } + + sal_uInt16 GetG() const; + // Return the last group code (the one the last Read() did return). + + tools::Long GetI() const; + // Returns the integer value of the group which was read earlier with Read(). + // This read must have returned a group code for datatype Integer. + // If not 0 is returned + + double GetF() const; + // Returns the floating point value of the group which was read earlier with Read(). + // This read must have returned a group code for datatype Floatingpoint. + // If not 0 is returned + + const OString& GetS() const; + // Returns the string of the group which was read earlier with Read(). + // This read must have returned a group code for datatype String. + // If not NULL is returned + + sal_uInt64 remainingSize() const; +private: + + tools::Long ReadI(); + double ReadF(); + void ReadS(); + + SvStream & rIS; + bool bStatus; + sal_uInt16 nLastG; + + OString S; + union { + double F; + tools::Long I; + }; +}; + + +inline bool DXFGroupReader::GetStatus() const +{ + return bStatus; +} + + +inline void DXFGroupReader::SetError() +{ + bStatus=false; +} + +inline sal_uInt16 DXFGroupReader::GetG() const +{ + return nLastG; +} + +inline tools::Long DXFGroupReader::GetI() const +{ + return I; +} + +inline double DXFGroupReader::GetF() const +{ + return F; +} + +inline const OString& DXFGroupReader::GetS() const +{ + return S; +} + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfreprd.cxx b/vcl/source/filter/idxf/dxfreprd.cxx new file mode 100644 index 000000000..7124e296e --- /dev/null +++ b/vcl/source/filter/idxf/dxfreprd.cxx @@ -0,0 +1,480 @@ +/* -*- 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 "dxfreprd.hxx" +#include <osl/nlsupport.h> +#include <unotools/defaultencoding.hxx> +#include <unotools/wincodepage.hxx> + +//------------------DXFBoundingBox-------------------------------------------- + + +void DXFBoundingBox::Union(const DXFVector & rVector) +{ + if (bEmpty) { + fMinX=rVector.fx; + fMinY=rVector.fy; + fMinZ=rVector.fz; + fMaxX=rVector.fx; + fMaxY=rVector.fy; + fMaxZ=rVector.fz; + bEmpty=false; + } + else { + if (fMinX>rVector.fx) fMinX=rVector.fx; + if (fMinY>rVector.fy) fMinY=rVector.fy; + if (fMinZ>rVector.fz) fMinZ=rVector.fz; + if (fMaxX<rVector.fx) fMaxX=rVector.fx; + if (fMaxY<rVector.fy) fMaxY=rVector.fy; + if (fMaxZ<rVector.fz) fMaxZ=rVector.fz; + } +} + + +//------------------DXFPalette------------------------------------------------ + + +DXFPalette::DXFPalette() +{ + short i,j,nHue,nNSat,nVal,nC[3],nmax,nmed,nmin; + sal_uInt8 nV; + + // colors 0 - 9 (normal colors) + SetColor(0, 0x00, 0x00, 0x00); // actually never being used + SetColor(1, 0xff, 0x00, 0x00); + SetColor(2, 0xff, 0xff, 0x00); + SetColor(3, 0x00, 0xff, 0x00); + SetColor(4, 0x00, 0xff, 0xff); + SetColor(5, 0x00, 0x00, 0xff); + SetColor(6, 0xff, 0x00, 0xff); + SetColor(7, 0x0f, 0x0f, 0x0f); // actually white??? + SetColor(8, 0x80, 0x80, 0x80); + SetColor(9, 0xc0, 0xc0, 0xc0); + + // colors 10 - 249 + // (Universal-Palette: 24 hues * 5 lightnesses * 2 saturations ) + i=10; + for (nHue=0; nHue<24; nHue++) { + for (nVal=5; nVal>=1; nVal--) { + for (nNSat=0; nNSat<2; nNSat++) { + nmax=((nHue+3)>>3)%3; + j=nHue-(nmax<<3); if (j>4) j=j-24; + if (j>=0) { + nmed=(nmax+1)%3; + nmin=(nmax+2)%3; + } + else { + nmed=(nmax+2)%3; + nmin=(nmax+1)%3; + j=-j; + } + nC[nmin]=0; + nC[nmed]=255*j/4; + nC[nmax]=255; + if (nNSat!=0) { + for (j=0; j<3; j++) nC[j]=(nC[j]>>1)+128; + } + for (j=0; j<3; j++) nC[j]=nC[j]*nVal/5; + SetColor(static_cast<sal_uInt8>(i++),static_cast<sal_uInt8>(nC[0]),static_cast<sal_uInt8>(nC[1]),static_cast<sal_uInt8>(nC[2])); + } + } + } + + // Farben 250 - 255 (shades of gray) + for (i=0; i<6; i++) { + nV=static_cast<sal_uInt8>(i*38+65); + SetColor(static_cast<sal_uInt8>(250+i),nV,nV,nV); + } +} + + +void DXFPalette::SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sal_uInt8 nBlue) +{ + pRed[nIndex]=nRed; + pGreen[nIndex]=nGreen; + pBlue[nIndex]=nBlue; +} + + +//------------------DXFRepresentation----------------------------------------- + + +DXFRepresentation::DXFRepresentation() + : mEnc(RTL_TEXTENCODING_DONTKNOW) + , mbInCalc(false) +{ + setGlobalLineTypeScale(1.0); +} + +DXFRepresentation::~DXFRepresentation() +{ +} + +rtl_TextEncoding DXFRepresentation::getTextEncoding() const +{ + return (isTextEncodingSet()) ? + mEnc : + osl_getTextEncodingFromLocale(nullptr); // Use default encoding if none specified +} + +bool DXFRepresentation::Read( SvStream & rIStream ) +{ + bool bRes; + + aTables.Clear(); + aBlocks.Clear(); + aEntities.Clear(); + + DXFGroupReader DGR( rIStream ); + + DGR.Read(); + while (DGR.GetG()!=0 || (DGR.GetS() != "EOF")) { + if (DGR.GetG()==0 && DGR.GetS() == "SECTION") { + if (DGR.Read()!=2) { + DGR.SetError(); + break; + } + if (DGR.GetS() == "HEADER") ReadHeader(DGR); + else if (DGR.GetS() == "TABLES") aTables.Read(DGR); + else if (DGR.GetS() == "BLOCKS") aBlocks.Read(DGR); + else if (DGR.GetS() == "ENTITIES") aEntities.Read(DGR); + else DGR.Read(); + } + else DGR.Read(); + } + + bRes=DGR.GetStatus(); + + if (bRes && aBoundingBox.bEmpty) + CalcBoundingBox(aEntities,aBoundingBox); + + return bRes; +} + +void DXFRepresentation::ReadHeader(DXFGroupReader & rDGR) +{ + while (rDGR.GetG()!=0 || (rDGR.GetS() != "EOF" && rDGR.GetS() != "ENDSEC") ) + { + if (rDGR.GetG()==9) { + if (rDGR.GetS() == "$EXTMIN" || + rDGR.GetS() == "$EXTMAX") + { + DXFVector aVector; + while (rDGR.Read()!=9 && rDGR.GetG()!=0) { + switch (rDGR.GetG()) { + case 10: aVector.fx = rDGR.GetF(); break; + case 20: aVector.fy = rDGR.GetF(); break; + case 30: aVector.fz = rDGR.GetF(); break; + } + } + aBoundingBox.Union(aVector); + } + else if (rDGR.GetS() == "$ACADVER") + { + if (!rDGR.Read(1)) + continue; + // Versions of AutoCAD up to Release 12 (inclusive, AC1009) + // were DOS software and used OEM encoding for storing strings. + // Release 13 (AC1012) had both DOS and Windows variants. + // Its Windows variant, and later releases used ANSI encodings for + // strings (up to version 2006, which was the last one to do so). + // Later versions (2007+, AC1021+) use UTF-8 for that. + // Other (non-Autodesk) implementations may have used different + // encodings for storing to corresponding formats, but there's + // no way to know that. + // See http://autodesk.blogs.com/between_the_lines/autocad-release-history.html + if ((rDGR.GetS() <= std::string_view("AC1009")) || (rDGR.GetS() == "AC2.22") || (rDGR.GetS() == "AC2.21") || (rDGR.GetS() == "AC2.10") || + (rDGR.GetS() == "AC1.50") || (rDGR.GetS() == "AC1.40") || (rDGR.GetS() == "AC1.2") || (rDGR.GetS() == "MC0.0")) + { + // Set OEM encoding for old DOS formats + // only if the encoding is not set yet + // e.g. by previous $DWGCODEPAGE + if (!isTextEncodingSet()) + setTextEncoding(utl_getWinTextEncodingFromLangStr( + utl_getLocaleForGlobalDefaultEncoding(), true)); + } + else if (rDGR.GetS() >= std::string_view("AC1021")) + setTextEncoding(RTL_TEXTENCODING_UTF8); + else + { + // Set ANSI encoding for old Windows formats + // only if the encoding is not set yet + // e.g. by previous $DWGCODEPAGE + if (!isTextEncodingSet()) + setTextEncoding(utl_getWinTextEncodingFromLangStr( + utl_getLocaleForGlobalDefaultEncoding())); + } + } + else if (rDGR.GetS() == "$DWGCODEPAGE") + { + if (!rDGR.Read(3)) + continue; + + // If we already use UTF8, then don't update encoding anymore + if (mEnc == RTL_TEXTENCODING_UTF8) + continue; + // FIXME: we really need a whole table of + // $DWGCODEPAGE to encodings mappings + else if ( (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_932")) || + (rDGR.GetS().equalsIgnoreAsciiCase("DOS932")) ) + { + setTextEncoding(RTL_TEXTENCODING_MS_932); + } + else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_936")) + { + setTextEncoding(RTL_TEXTENCODING_MS_936); + } + else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_949")) + { + setTextEncoding(RTL_TEXTENCODING_MS_949); + } + else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_950")) + { + setTextEncoding(RTL_TEXTENCODING_MS_950); + } + else if (rDGR.GetS().equalsIgnoreAsciiCase("ANSI_1251")) + { + setTextEncoding(RTL_TEXTENCODING_MS_1251); + } + } + else if (rDGR.GetS() == "$LTSCALE") + { + if (!rDGR.Read(40)) + continue; + setGlobalLineTypeScale(getGlobalLineTypeScale() * rDGR.GetF()); + } + else rDGR.Read(); + } + else rDGR.Read(); + } +} + +void DXFRepresentation::CalcBoundingBox(const DXFEntities & rEntities, + DXFBoundingBox & rBox) +{ + if (mbInCalc) + return; + mbInCalc = true; + + DXFBasicEntity * pBE=rEntities.pFirst; + while (pBE!=nullptr) { + switch (pBE->eType) { + case DXF_LINE: { + const DXFLineEntity * pE = static_cast<const DXFLineEntity*>(pBE); + rBox.Union(pE->aP0); + rBox.Union(pE->aP1); + break; + } + case DXF_POINT: { + const DXFPointEntity * pE = static_cast<const DXFPointEntity*>(pBE); + rBox.Union(pE->aP0); + break; + } + case DXF_CIRCLE: { + const DXFCircleEntity * pE = static_cast<const DXFCircleEntity*>(pBE); + DXFVector aP; + aP=pE->aP0; + aP.fx-=pE->fRadius; + aP.fy-=pE->fRadius; + rBox.Union(aP); + aP=pE->aP0; + aP.fx+=pE->fRadius; + aP.fy+=pE->fRadius; + rBox.Union(aP); + break; + } + case DXF_ARC: { + const DXFArcEntity * pE = static_cast<const DXFArcEntity*>(pBE); + DXFVector aP; + aP=pE->aP0; + aP.fx-=pE->fRadius; + aP.fy-=pE->fRadius; + rBox.Union(aP); + aP=pE->aP0; + aP.fx+=pE->fRadius; + aP.fy+=pE->fRadius; + rBox.Union(aP); + break; + } + case DXF_TRACE: { + const DXFTraceEntity * pE = static_cast<const DXFTraceEntity*>(pBE); + rBox.Union(pE->aP0); + rBox.Union(pE->aP1); + rBox.Union(pE->aP2); + rBox.Union(pE->aP3); + break; + } + case DXF_SOLID: { + const DXFSolidEntity * pE = static_cast<const DXFSolidEntity*>(pBE); + rBox.Union(pE->aP0); + rBox.Union(pE->aP1); + rBox.Union(pE->aP2); + rBox.Union(pE->aP3); + break; + } + case DXF_TEXT: { + //const DXFTextEntity * pE = (DXFTextEntity*)pBE; + //??? + break; + } + case DXF_SHAPE: { + //const DXFShapeEntity * pE = (DXFShapeEntity*)pBE; + //??? + break; + } + case DXF_INSERT: { + const DXFInsertEntity * pE = static_cast<const DXFInsertEntity*>(pBE); + DXFBlock * pB; + DXFBoundingBox aBox; + DXFVector aP; + pB=aBlocks.Search(pE->m_sName); + if (pB==nullptr) break; + CalcBoundingBox(*pB,aBox); + if (aBox.bEmpty) break; + aP.fx=(aBox.fMinX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; + aP.fy=(aBox.fMinY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; + aP.fz=(aBox.fMinZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; + rBox.Union(aP); + aP.fx=(aBox.fMaxX-pB->aBasePoint.fx)*pE->fXScale+pE->aP0.fx; + aP.fy=(aBox.fMaxY-pB->aBasePoint.fy)*pE->fYScale+pE->aP0.fy; + aP.fz=(aBox.fMaxZ-pB->aBasePoint.fz)*pE->fZScale+pE->aP0.fz; + rBox.Union(aP); + break; + } + case DXF_ATTDEF: { + //const DXFAttDefEntity * pE = (DXFAttDefEntity*)pBE; + //??? + break; + } + case DXF_ATTRIB: { + //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + //??? + break; + } + case DXF_VERTEX: { + const DXFVertexEntity * pE = static_cast<const DXFVertexEntity*>(pBE); + rBox.Union(pE->aP0); + break; + } + case DXF_3DFACE: { + const DXF3DFaceEntity * pE = static_cast<const DXF3DFaceEntity*>(pBE); + rBox.Union(pE->aP0); + rBox.Union(pE->aP1); + rBox.Union(pE->aP2); + rBox.Union(pE->aP3); + break; + } + case DXF_DIMENSION: { + const DXFDimensionEntity * pE = static_cast<const DXFDimensionEntity*>(pBE); + DXFBlock * pB; + DXFBoundingBox aBox; + DXFVector aP; + pB = aBlocks.Search(pE->m_sPseudoBlock); + if (pB==nullptr) break; + CalcBoundingBox(*pB,aBox); + if (aBox.bEmpty) break; + aP.fx=aBox.fMinX-pB->aBasePoint.fx; + aP.fy=aBox.fMinY-pB->aBasePoint.fy; + aP.fz=aBox.fMinZ-pB->aBasePoint.fz; + rBox.Union(aP); + aP.fx=aBox.fMaxX-pB->aBasePoint.fx; + aP.fy=aBox.fMaxY-pB->aBasePoint.fy; + aP.fz=aBox.fMaxZ-pB->aBasePoint.fz; + rBox.Union(aP); + break; + } + case DXF_POLYLINE: { + //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + //??? + break; + } + case DXF_SEQEND: { + //const DXFAttribEntity * pE = (DXFAttribEntity*)pBE; + //??? + break; + } + case DXF_HATCH : + break; + case DXF_LWPOLYLINE : + break; + } + pBE=pBE->pSucc; + } + mbInCalc = false; +} + +namespace { + bool lcl_isDec(sal_Unicode ch) + { + return ch >= L'0' && ch <= L'9'; + } + bool lcl_isHex(sal_Unicode ch) + { + return lcl_isDec(ch) || (ch >= L'A' && ch <= L'F') || (ch >= L'a' && ch <= L'f'); + } +} + +OUString DXFRepresentation::ToOUString(std::string_view s) const +{ + OUString result = OStringToOUString(s, getTextEncoding(), + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR); + result = result.replaceAll("%%o", "") // Overscore - simply remove + .replaceAll("%%u", "") // Underscore - simply remove + .replaceAll("%%d", u"\u00B0") // Degrees symbol (°) + .replaceAll("%%p", u"\u00B1") // Tolerance symbol (±) + .replaceAll("%%c", u"\u2205") // Diameter symbol + .replaceAll("%%%", "%"); // Percent symbol + + sal_Int32 pos = result.indexOf("%%"); // %%nnn, where nnn - 3-digit decimal ASCII code + while (pos != -1 && pos <= result.getLength() - 5) { + OUString asciiNum = result.copy(pos + 2, 3); + if (lcl_isDec(asciiNum[0]) && + lcl_isDec(asciiNum[1]) && + lcl_isDec(asciiNum[2])) + { + char ch = static_cast<char>(asciiNum.toUInt32()); + OUString codePt(&ch, 1, mEnc); + result = result.replaceAll(result.subView(pos, 5), codePt, pos); + } + pos = result.indexOf("%%", pos + 1); + } + + pos = result.indexOf("\\U+"); // \U+XXXX, where XXXX - 4-digit hex unicode + while (pos != -1 && pos <= result.getLength() - 7) { + OUString codePtNum = result.copy(pos + 3, 4); + if (lcl_isHex(codePtNum[0]) && + lcl_isHex(codePtNum[1]) && + lcl_isHex(codePtNum[2]) && + lcl_isHex(codePtNum[3])) + { + OUString codePt(static_cast<sal_Unicode>(codePtNum.toUInt32(16))); + result = result.replaceAll(result.subView(pos, 7), codePt, pos); + } + pos = result.indexOf("\\U+", pos + 1); + } + return result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfreprd.hxx b/vcl/source/filter/idxf/dxfreprd.hxx new file mode 100644 index 000000000..e9c6bc653 --- /dev/null +++ b/vcl/source/filter/idxf/dxfreprd.hxx @@ -0,0 +1,129 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFREPRD_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFREPRD_HXX + +#include "dxfblkrd.hxx" +#include "dxftblrd.hxx" +#include <array> +#include <string_view> + +//--------------------Other stuff--------------------------------------------- + + +//-------------------A 3D-Min/Max-Box----------------------------------------- + +class DXFBoundingBox { +public: + bool bEmpty; + double fMinX; + double fMinY; + double fMinZ; + double fMaxX; + double fMaxY; + double fMaxZ; + + DXFBoundingBox():bEmpty(true), fMinX(0.0), fMinY(0.0), fMinZ(0.0), fMaxX(0.0), fMaxY(0.0), fMaxZ(0.0) {} + void Union(const DXFVector & rVector); +}; + + +//-------------------The (constant) palette for DXF------------------------- + +class DXFPalette { + +public: + + DXFPalette(); + + sal_uInt8 GetRed(sal_uInt8 nIndex) const; + sal_uInt8 GetGreen(sal_uInt8 nIndex) const; + sal_uInt8 GetBlue(sal_uInt8 nIndex) const; + +private: + std::array<sal_uInt8, 256> pRed; + std::array<sal_uInt8, 256> pGreen; + std::array<sal_uInt8, 256> pBlue; + void SetColor(sal_uInt8 nIndex, sal_uInt8 nRed, sal_uInt8 nGreen, sal_uInt8 nBlue); +}; + + +//-----------------read and represent DXF file-------------------------------- + + +class DXFRepresentation { + +public: + + DXFPalette aPalette; + // The always equal DXF color palette + + DXFBoundingBox aBoundingBox; + // is equal to the AutoCAD variables EXTMIN, EXTMAX if those exist + // within the DXF file. Otherwise the BoundingBox gets calculated (in Read()) + + DXFTables aTables; + // the tables of the DXF file + + DXFBlocks aBlocks; + // the blocks of the DXF file + + DXFEntities aEntities; + // the entities (from the Entities-Section) of the DXF file + + rtl_TextEncoding mEnc; // $DWGCODEPAGE + + double mfGlobalLineTypeScale; // $LTSCALE + + bool mbInCalc; // guard for self-recursive bounding box calc + + DXFRepresentation(); + ~DXFRepresentation(); + + rtl_TextEncoding getTextEncoding() const; + void setTextEncoding(rtl_TextEncoding aEnc) { mEnc = aEnc; } + OUString ToOUString(std::string_view s) const; + + double getGlobalLineTypeScale() const { return mfGlobalLineTypeScale; } + void setGlobalLineTypeScale(double fGlobalLineTypeScale) { mfGlobalLineTypeScale = fGlobalLineTypeScale; } + + bool Read( SvStream & rIStream ); + // Reads complete DXF file. + +private: + void ReadHeader(DXFGroupReader & rDGR); + void CalcBoundingBox(const DXFEntities & rEntities, + DXFBoundingBox & rBox); + + bool isTextEncodingSet() const { return mEnc != RTL_TEXTENCODING_DONTKNOW; } +}; + + +//-------------------inlines-------------------------------------------------- + + +inline sal_uInt8 DXFPalette::GetRed(sal_uInt8 nIndex) const { return pRed[nIndex]; } +inline sal_uInt8 DXFPalette::GetGreen(sal_uInt8 nIndex) const { return pGreen[nIndex]; } +inline sal_uInt8 DXFPalette::GetBlue(sal_uInt8 nIndex) const { return pBlue[nIndex]; } + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxftblrd.cxx b/vcl/source/filter/idxf/dxftblrd.cxx new file mode 100644 index 000000000..7e20e6c69 --- /dev/null +++ b/vcl/source/filter/idxf/dxftblrd.cxx @@ -0,0 +1,381 @@ +/* -*- 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 "dxftblrd.hxx" + +//----------------------------------DXFLType----------------------------------- + +DXFLType::DXFLType() + : pSucc(nullptr) + , nFlags(0) + , nDashCount(0) + , fPatternLength(0.0) + , fDash{0.0} +{ +} + +void DXFLType::Read(DXFGroupReader & rDGR) +{ + tools::Long nDashIndex=-1; + + while (rDGR.Read()!=0) + { + switch (rDGR.GetG()) + { + case 2: + m_sName = rDGR.GetS(); + break; + case 3: + m_sDescription = rDGR.GetS(); + break; + case 70: + nFlags=rDGR.GetI(); + break; + case 73: + if (nDashIndex!=-1) + { + rDGR.SetError(); + return; + } + nDashCount=rDGR.GetI(); + if (nDashCount>DXF_MAX_DASH_COUNT) + { + nDashCount=DXF_MAX_DASH_COUNT; + } + nDashIndex=0; + break; + case 40: fPatternLength=rDGR.GetF(); break; + case 49: + if (nDashCount==-1) + { + rDGR.SetError(); + return; + } + if (nDashIndex < nDashCount) + { + if (nDashIndex < 0) + { + rDGR.SetError(); + return; + } + fDash[nDashIndex++] = rDGR.GetF(); + } + break; + } + } +} + +//----------------------------------DXFLayer----------------------------------- + +DXFLayer::DXFLayer() +{ + pSucc=nullptr; + nFlags=0; + nColor=-1; +} + +void DXFLayer::Read(DXFGroupReader & rDGR) +{ + while (rDGR.Read()!=0) + { + switch(rDGR.GetG()) + { + case 2: + m_sName = rDGR.GetS(); + break; + case 6: + m_sLineType = rDGR.GetS(); + break; + case 70: + nFlags=rDGR.GetI(); + break; + case 62: + nColor=rDGR.GetI(); + break; + } + } +} + +//----------------------------------DXFStyle----------------------------------- + +DXFStyle::DXFStyle() +{ + pSucc=nullptr; + nFlags=0; + fHeight=0.0; + fWidthFak=1.0; + fOblAngle=0.0; + nTextGenFlags=0; + fLastHeightUsed=0.0; +} + +void DXFStyle::Read(DXFGroupReader & rDGR) +{ + while (rDGR.Read()!=0) + { + switch(rDGR.GetG()) + { + case 2: + m_sName = rDGR.GetS(); + break; + case 3: + m_sPrimFontFile = rDGR.GetS(); + break; + case 4: + m_sBigFontFile = rDGR.GetS(); + break; + case 70: + nFlags=rDGR.GetI(); + break; + case 40: + fHeight=rDGR.GetF(); + break; + case 41: + fWidthFak=rDGR.GetF(); + break; + case 42: + fLastHeightUsed=rDGR.GetF(); + break; + case 50: + fOblAngle=rDGR.GetF(); + break; + case 71: + nTextGenFlags=rDGR.GetI(); + break; + } + } +} + +//----------------------------------DXFVPort----------------------------------- + +DXFVPort::DXFVPort() + : pSucc(nullptr) + , nFlags(0) + , fMinX(0.0) + , fMinY(0.0) + , fMaxX(0.0) + , fMaxY(0.0) + , fCenterX(0.0) + , fCenterY(0.0) + , fSnapBaseX(0.0) + , fSnapBaseY(0.0) + , fSnapSpacingX(0.0) + , fSnapSpacingY(0.0) + , fGridX(0.0) + , fGridY(0.0) + , aDirection(DXFVector(0.0, 0.0, 1.0)) + , fHeight(0.0) + , fAspectRatio(0.0) + , fLensLength(0.0) + , fFrontClipPlane(0.0) + , fBackClipPlane(0.0) + , fTwistAngle(0.0) + , nStatus(0) + , nID(0) + , nMode(0) + , nCircleZoomPercent(0) + , nFastZoom(0) + , nUCSICON(0) + , nSnap(0) + , nGrid(0) + , nSnapStyle(0) + , nSnapIsopair(0) +{ +} + +void DXFVPort::Read(DXFGroupReader & rDGR) +{ + while (rDGR.Read()!=0) + { + switch(rDGR.GetG()) + { + case 2: + m_sName = rDGR.GetS(); + break; + case 10: fMinX=rDGR.GetF(); break; + case 11: fMaxX=rDGR.GetF(); break; + case 12: fCenterX=rDGR.GetF(); break; + case 13: fSnapBaseX=rDGR.GetF(); break; + case 14: fSnapSpacingX=rDGR.GetF(); break; + case 15: fGridX=rDGR.GetF(); break; + case 16: aDirection.fx=rDGR.GetF(); break; + case 17: aTarget.fx=rDGR.GetF(); break; + case 20: fMinY=rDGR.GetF(); break; + case 21: fMaxY=rDGR.GetF(); break; + case 22: fCenterY=rDGR.GetF(); break; + case 23: fSnapBaseY=rDGR.GetF(); break; + case 24: fSnapSpacingY=rDGR.GetF(); break; + case 25: fGridY=rDGR.GetF(); break; + case 26: aDirection.fy=rDGR.GetF(); break; + case 27: aTarget.fy=rDGR.GetF(); break; + case 36: aDirection.fz=rDGR.GetF(); break; + case 37: aTarget.fz=rDGR.GetF(); break; + case 40: fHeight=rDGR.GetF(); break; + case 41: fAspectRatio=rDGR.GetF(); break; + case 42: fLensLength=rDGR.GetF(); break; + case 43: fFrontClipPlane=rDGR.GetF(); break; + case 44: fBackClipPlane=rDGR.GetF(); break; + case 51: fTwistAngle=rDGR.GetF(); break; + case 68: nStatus=rDGR.GetI(); break; + case 69: nID=rDGR.GetI(); break; + case 70: nFlags=rDGR.GetI(); break; + case 71: nMode=rDGR.GetI(); break; + case 72: nCircleZoomPercent=rDGR.GetI(); break; + case 73: nFastZoom=rDGR.GetI(); break; + case 74: nUCSICON=rDGR.GetI(); break; + case 75: nSnap=rDGR.GetI(); break; + case 76: nGrid=rDGR.GetI(); break; + case 77: nSnapStyle=rDGR.GetI(); break; + case 78: nSnapIsopair=rDGR.GetI(); break; + } + } +} + +//----------------------------------DXFTables---------------------------------- + + +DXFTables::DXFTables() +{ + pLTypes=nullptr; + pLayers=nullptr; + pStyles=nullptr; + pVPorts=nullptr; +} + + +DXFTables::~DXFTables() +{ + Clear(); +} + + +void DXFTables::Read(DXFGroupReader & rDGR) +{ + DXFLType * * ppLT, * pLT; + DXFLayer * * ppLa, * pLa; + DXFStyle * * ppSt, * pSt; + DXFVPort * * ppVP, * pVP; + + ppLT=&pLTypes; + while(*ppLT!=nullptr) ppLT=&((*ppLT)->pSucc); + + ppLa=&pLayers; + while(*ppLa!=nullptr) ppLa=&((*ppLa)->pSucc); + + ppSt=&pStyles; + while(*ppSt!=nullptr) ppSt=&((*ppSt)->pSucc); + + ppVP=&pVPorts; + while(*ppVP!=nullptr) ppVP=&((*ppVP)->pSucc); + + for (;;) { + while (rDGR.GetG()!=0) rDGR.Read(); + if (rDGR.GetS() == "EOF" || + rDGR.GetS() == "ENDSEC") break; + else if (rDGR.GetS() == "LTYPE") { + pLT=new DXFLType; + pLT->Read(rDGR); + *ppLT=pLT; + ppLT=&(pLT->pSucc); + } + else if (rDGR.GetS() == "LAYER") { + pLa=new DXFLayer; + pLa->Read(rDGR); + *ppLa=pLa; + ppLa=&(pLa->pSucc); + } + else if (rDGR.GetS() == "STYLE") { + pSt=new DXFStyle; + pSt->Read(rDGR); + *ppSt=pSt; + ppSt=&(pSt->pSucc); + } + else if (rDGR.GetS() == "VPORT") { + pVP=new DXFVPort; + pVP->Read(rDGR); + *ppVP=pVP; + ppVP=&(pVP->pSucc); + } + else rDGR.Read(); + } +} + + +void DXFTables::Clear() +{ + DXFLType * pLT; + DXFLayer * pLa; + DXFStyle * pSt; + DXFVPort * pVP; + + while (pStyles!=nullptr) { + pSt=pStyles; + pStyles=pSt->pSucc; + delete pSt; + } + while (pLayers!=nullptr) { + pLa=pLayers; + pLayers=pLa->pSucc; + delete pLa; + } + while (pLTypes!=nullptr) { + pLT=pLTypes; + pLTypes=pLT->pSucc; + delete pLT; + } + while (pVPorts!=nullptr) { + pVP=pVPorts; + pVPorts=pVP->pSucc; + delete pVP; + } +} + + +DXFLType * DXFTables::SearchLType(std::string_view rName) const +{ + DXFLType * p; + for (p=pLTypes; p!=nullptr; p=p->pSucc) { + if (rName == p->m_sName) break; + } + return p; +} + + +DXFLayer * DXFTables::SearchLayer(std::string_view rName) const +{ + DXFLayer * p; + for (p=pLayers; p!=nullptr; p=p->pSucc) { + if (rName == p->m_sName) break; + } + return p; +} + + +DXFVPort * DXFTables::SearchVPort(std::string_view rName) const +{ + DXFVPort * p; + for (p=pVPorts; p!=nullptr; p=p->pSucc) { + if (rName == p->m_sName) break; + } + return p; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxftblrd.hxx b/vcl/source/filter/idxf/dxftblrd.hxx new file mode 100644 index 000000000..f60c0461e --- /dev/null +++ b/vcl/source/filter/idxf/dxftblrd.hxx @@ -0,0 +1,175 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFTBLRD_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFTBLRD_HXX + +#include <sal/config.h> + +#include <string_view> + +#include "dxfgrprd.hxx" +#include "dxfvec.hxx" + + +//------------------- Line Type ---------------------------------------------- + + +#define DXF_MAX_DASH_COUNT 32 + +class DXFLType { + +public: + + DXFLType * pSucc; + + OString m_sName; // 2 + tools::Long nFlags; // 70 + OString m_sDescription; // 3 + tools::Long nDashCount; // 73 + double fPatternLength; // 40 + double fDash[DXF_MAX_DASH_COUNT]; // 49,49,... + + DXFLType(); + void Read(DXFGroupReader & rDGR); +}; + + +//------------------ Layer --------------------------------------------------- + + +class DXFLayer { + +public: + + DXFLayer * pSucc; + + OString m_sName; // 2 + tools::Long nFlags; // 70 + tools::Long nColor; // 62 + OString m_sLineType; // 6 + + DXFLayer(); + void Read(DXFGroupReader & rDGR); +}; + + +//------------------ Style --------------------------------------------------- + + +class DXFStyle { + +public: + + DXFStyle * pSucc; + + OString m_sName; // 2 + tools::Long nFlags; // 70 + double fHeight; // 40 + double fWidthFak; // 41 + double fOblAngle; // 50 + tools::Long nTextGenFlags; // 71 + double fLastHeightUsed; // 42 + OString m_sPrimFontFile; // 3 + OString m_sBigFontFile; // 4 + + DXFStyle(); + void Read(DXFGroupReader & rDGR); +}; + + +//------------------ VPort --------------------------------------------------- + + +class DXFVPort { + +public: + + DXFVPort * pSucc; + + OString m_sName; // 2 + tools::Long nFlags; // 70 + double fMinX; // 10 + double fMinY; // 20 + double fMaxX; // 11 + double fMaxY; // 21 + double fCenterX; // 12 + double fCenterY; // 22 + double fSnapBaseX; // 13 + double fSnapBaseY; // 23 + double fSnapSpacingX; // 14 + double fSnapSpacingY; // 24 + double fGridX; // 15 + double fGridY; // 25 + DXFVector aDirection; // 16,26,36 + DXFVector aTarget; // 17,27,37 + double fHeight; // 40 + double fAspectRatio; // 41 + double fLensLength; // 42 + double fFrontClipPlane; // 43 + double fBackClipPlane; // 44 + double fTwistAngle; // 51 + tools::Long nStatus; // 68 + tools::Long nID; // 69 + tools::Long nMode; // 71 + tools::Long nCircleZoomPercent; // 72 + tools::Long nFastZoom; // 73 + tools::Long nUCSICON; // 74 + tools::Long nSnap; // 75 + tools::Long nGrid; // 76 + tools::Long nSnapStyle; // 77 + tools::Long nSnapIsopair; // 78 + + DXFVPort(); + void Read(DXFGroupReader & rDGR); +}; + + +//------------------ Tables -------------------------------------------------- + + +class DXFTables { + + DXFLType * pLTypes; // list of line types + DXFLayer * pLayers; // list of layers + DXFStyle * pStyles; // list of styles + DXFVPort * pVPorts; // list of viewports + +public: + + DXFTables(); + ~DXFTables(); + + void Read(DXFGroupReader & rDGR); + // Reads the table until an ENDSEC or EOF + // (Unknown things/tables will be skipped) + + void Clear(); + + // look for table entries: + DXFLType * SearchLType(std::string_view rName) const; + DXFLayer * SearchLayer(std::string_view rName) const; + DXFVPort * SearchVPort(std::string_view rName) const; + +}; + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfvec.cxx b/vcl/source/filter/idxf/dxfvec.cxx new file mode 100644 index 000000000..dc5f39835 --- /dev/null +++ b/vcl/source/filter/idxf/dxfvec.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <math.h> +#include "dxfvec.hxx" +#include <tools/gen.hxx> + + +//---------------------------- DXFVector --------------------------------------- + + +double DXFVector::Abs() const +{ + return sqrt(SProd(*this)); +} + + +DXFVector DXFVector::Unit() const +{ + double flen; + + flen=Abs(); + if (flen!=0) return (*this)*(1.0/flen); + else return DXFVector(1.0,0.0,0.0); +} + + +//---------------------------- DXFTransform ------------------------------------ + + +DXFTransform::DXFTransform() : + aMX(1.0, 0.0, 0.0), + aMY(0.0, 1.0, 0.0), + aMZ(0.0, 0.0, 1.0), + aMP(0.0, 0.0, 0.0) +{ +} + + +DXFTransform::DXFTransform(double fScaleX, double fScaleY, double fScaleZ, + const DXFVector & rShift) : + aMX(fScaleX, 0.0, 0.0), + aMY(0.0, fScaleY, 0.0), + aMZ(0.0, 0.0, fScaleZ), + aMP(rShift) +{ +} + + +DXFTransform::DXFTransform(double fScaleX, double fScaleY, double fScaleZ, + double fRotAngle, + const DXFVector & rShift) : + aMX(0.0, 0.0, 0.0), + aMY(0.0, 0.0, 0.0), + aMZ(0.0, 0.0, fScaleZ), + aMP(rShift) +{ + aMX.fx=cos(basegfx::deg2rad(fRotAngle)); + aMX.fy=sin(basegfx::deg2rad(fRotAngle)); + aMY.fx=-aMX.fy; + aMY.fy=aMX.fx; + aMX*=fScaleX; + aMY*=fScaleY; +} + + +DXFTransform::DXFTransform(const DXFVector & rExtrusion) : + aMP(0.0, 0.0, 0.0) +{ + // 'Arbitrary Axis Algorithm' (cf. DXF documentation by Autodesk) + if ( fabs(rExtrusion.fx) < 1.0/64.0 && fabs(rExtrusion.fy) < 1.0/64.0) { + aMX = DXFVector(0.0, 1.0, 0.0) * rExtrusion; + } + else { + aMX = DXFVector(0.0, 0.0, 1.0) * rExtrusion; + } + aMX=aMX.Unit(); + aMY=(rExtrusion*aMX).Unit(); + aMZ=rExtrusion.Unit(); +} + + +DXFTransform::DXFTransform(const DXFVector & rViewDir, const DXFVector & rViewTarget) +{ + DXFVector aV; + + aV=rViewDir.Unit(); + aMX.fz=aV.fx; + aMY.fz=aV.fy; + aMZ.fz=aV.fz; + + aMZ.fx=0; + if (aV.fx==0) aMY.fx=0; else aMY.fx=sqrt(1/(1+aV.fy*aV.fy/(aV.fx*aV.fx))); + aMX.fx=sqrt(1-aMY.fx*aMY.fx); + if (aV.fx*aV.fy*aMY.fx>0) aMX.fx=-aMX.fx; + + aV=aV*DXFVector(aMX.fx,aMY.fx,aMZ.fx); + aMX.fy=aV.fx; + aMY.fy=aV.fy; + aMZ.fy=aV.fz; + + if (aMZ.fy<0) { + aMX.fy=-aMX.fy; + aMY.fy=-aMY.fy; + aMZ.fy=-aMZ.fy; + aMX.fx=-aMX.fx; + aMY.fx=-aMY.fx; + } + + aV=DXFVector(0,0,0)-rViewTarget; + aMP.fx = aV.fx * aMX.fx + aV.fy * aMY.fx + aV.fz * aMZ.fx; + aMP.fy = aV.fx * aMX.fy + aV.fy * aMY.fy + aV.fz * aMZ.fy; + aMP.fz = aV.fx * aMX.fz + aV.fy * aMY.fz + aV.fz * aMZ.fz; +} + + +DXFTransform::DXFTransform(const DXFTransform & rT1, const DXFTransform & rT2) +{ + rT2.TransDir(rT1.aMX,aMX); + rT2.TransDir(rT1.aMY,aMY); + rT2.TransDir(rT1.aMZ,aMZ); + rT2.Transform(rT1.aMP,aMP); +} + + +void DXFTransform::Transform(const DXFVector & rSrc, DXFVector & rTgt) const +{ + rTgt.fx = rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx + aMP.fx; + rTgt.fy = rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy + aMP.fy; + rTgt.fz = rSrc.fx * aMX.fz + rSrc.fy * aMY.fz + rSrc.fz * aMZ.fz + aMP.fz; +} + + +void DXFTransform::Transform(const DXFVector & rSrc, Point & rTgt) const +{ + rTgt.setX(static_cast<tools::Long>( rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx + aMP.fx + 0.5 ) ); + rTgt.setY(static_cast<tools::Long>( rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy + aMP.fy + 0.5 ) ); +} + + +void DXFTransform::TransDir(const DXFVector & rSrc, DXFVector & rTgt) const +{ + rTgt.fx = rSrc.fx * aMX.fx + rSrc.fy * aMY.fx + rSrc.fz * aMZ.fx; + rTgt.fy = rSrc.fx * aMX.fy + rSrc.fy * aMY.fy + rSrc.fz * aMZ.fy; + rTgt.fz = rSrc.fx * aMX.fz + rSrc.fy * aMY.fz + rSrc.fz * aMZ.fz; +} + + +bool DXFTransform::TransCircleToEllipse(double fRadius, double & rEx, double & rEy) const +{ + double fMXAbs=aMX.Abs(); + double fMYAbs=aMY.Abs(); + double fNearNull=(fMXAbs+fMYAbs)*0.001; + + if (fabs(aMX.fy)<=fNearNull && fabs(aMX.fz)<=fNearNull && + fabs(aMY.fx)<=fNearNull && fabs(aMY.fz)<=fNearNull) + { + rEx=fabs(aMX.fx*fRadius); + rEy=fabs(aMY.fy*fRadius); + return true; + } + else if (fabs(aMX.fx)<=fNearNull && fabs(aMX.fz)<=fNearNull && + fabs(aMY.fy)<=fNearNull && fabs(aMY.fz)<=fNearNull) + { + rEx=fabs(aMY.fx*fRadius); + rEy=fabs(aMX.fy*fRadius); + return true; + } + else if (fabs(fMXAbs-fMYAbs)<=fNearNull && + fabs(aMX.fz)<=fNearNull && fabs(aMY.fz)<=fNearNull) + { + rEx=rEy=fabs(((fMXAbs+fMYAbs)/2)*fRadius); + return true; + } + else return false; +} + +LineInfo DXFTransform::Transform(const DXFLineInfo& aDXFLineInfo) const +{ + double fex,fey,scale; + + fex=sqrt(aMX.fx*aMX.fx + aMX.fy*aMX.fy); + fey=sqrt(aMY.fx*aMY.fx + aMY.fy*aMY.fy); + scale = (fex+fey)/2.0; + + LineInfo aLineInfo; + + aLineInfo.SetStyle( aDXFLineInfo.eStyle ); + aLineInfo.SetWidth( 0 ); + aLineInfo.SetDashCount( static_cast< sal_uInt16 >( aDXFLineInfo.nDashCount ) ); + aLineInfo.SetDashLen( aDXFLineInfo.fDashLen * scale ); + aLineInfo.SetDotCount( static_cast< sal_uInt16 >( aDXFLineInfo.nDotCount ) ); + aLineInfo.SetDotLen( aDXFLineInfo.fDotLen * scale ); + aLineInfo.SetDistance( aDXFLineInfo.fDistance * scale ); + + if ( aLineInfo.GetDashCount() > 0 && aLineInfo.GetDashLen() == 0 ) + aLineInfo.SetDashLen(1); + + if ( aLineInfo.GetDotCount() > 0 && aLineInfo.GetDotLen() == 0 ) + aLineInfo.SetDotLen(1); + + return aLineInfo; +} + +double DXFTransform::CalcRotAngle() const +{ + return basegfx::rad2deg(atan2(aMX.fy,aMX.fx)); +} + +bool DXFTransform::Mirror() const +{ + return aMZ.SProd(aMX*aMY)<0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/dxfvec.hxx b/vcl/source/filter/idxf/dxfvec.hxx new file mode 100644 index 000000000..59b6babc2 --- /dev/null +++ b/vcl/source/filter/idxf/dxfvec.hxx @@ -0,0 +1,218 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFVEC_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IDXF_DXFVEC_HXX + +#include <sal/types.h> +#include <vcl/lineinfo.hxx> + +class Point; + +class DXFLineInfo { +public: + LineStyle eStyle; + sal_Int32 nDashCount; + double fDashLen; + sal_Int32 nDotCount; + double fDotLen; + double fDistance; + + DXFLineInfo() : + eStyle(LineStyle::Solid), + nDashCount(0), + fDashLen(0), + nDotCount(0), + fDotLen(0), + fDistance(0) {} +}; + + +//---------------------------- DXFVector --------------------------------------- + +// common 3D vector with doubles + +class DXFVector { + +public: + + double fx,fy,fz; // public ! - why not? + + inline DXFVector(double fX=0.0, double fY=0.0, double fZ=0.0); + + // summation/subtraktion: + DXFVector & operator += (const DXFVector & rV); + DXFVector operator + (const DXFVector & rV) const; + DXFVector operator - (const DXFVector & rV) const; + + // vector product + DXFVector operator * (const DXFVector & rV) const; + + // scalar product: + double SProd(const DXFVector & rV) const; + + // multiplication with scalar: + DXFVector & operator *= (double fs); + DXFVector operator * (double fs) const; + + // length: + double Abs() const; + + // vector with same direction and a length of 1: + DXFVector Unit() const; + + // equivalence or net: + bool operator == (const DXFVector & rV) const; +}; + + +//---------------------------- DXFTransform ------------------------------------ + +// a transformation matrice specialized for our problem + +class DXFTransform { + +public: + + DXFTransform(); + // destination coordinate = source coordinate + + DXFTransform(double fScaleX, double fScaleY, double fScaleZ, + const DXFVector & rShift); + // dest coordinate = translate(scale(source coordinate)) + + DXFTransform(double fScaleX, double fScaleY, double fScaleZ, + double fRotAngle, + const DXFVector & rShift); + // dest coordinate = translate(rotate(scale(source coordinate))) + // rotation around z-axis, fRotAngle in degrees. + + DXFTransform(const DXFVector & rExtrusion); + // Transformation "ECS->WCS" via "Entity Extrusion Direction" + // ant the "Arbitrary Axis Algorithm" + // (See DXF-Docu from AutoDesk) + + DXFTransform(const DXFVector & rViewDir, const DXFVector & rViewTarget); + // Transformation object space->picture space on the basis of direction + // destination point of a viewport + // (See DXF-Docu from AutoDesk: VPORT) + + DXFTransform(const DXFTransform & rT1, const DXFTransform & rT2); + // destination coordinate = rT2(rT1(source coordinate)) + + + void Transform(const DXFVector & rSrc, DXFVector & rTgt) const; + // Transformation from DXFVector to DXFVector + + void Transform(const DXFVector & rSrc, Point & rTgt) const; + // Transformation from DXFVector to SvPoint + + void TransDir(const DXFVector & rSrc, DXFVector & rTgt) const; + // Transformation of a relative vector (so no translation) + + bool TransCircleToEllipse(double fRadius, double & rEx, double & rEy) const; + // Attempt to transform a circle (in xy plane) so that it results + // in an aligned ellipse. If the does not work because an ellipse of + // arbitrary position would be created, sal_False is returned. + // (The center point will not be transformed, use Transform(..)) + + double CalcRotAngle() const; + // Calculates the rotation angle around z-axis (in degrees) + + bool Mirror() const; + // Returns sal_True, if the matrice represents a left-handed coordinate system + + LineInfo Transform(const DXFLineInfo& aDXFLineInfo) const; + // Transform to LineInfo + +private: + DXFVector aMX; + DXFVector aMY; + DXFVector aMZ; + DXFVector aMP; +}; + + +//------------------------------- inlines -------------------------------------- + + +inline DXFVector::DXFVector(double fX, double fY, double fZ) +{ + fx=fX; fy=fY; fz=fZ; +} + + +inline DXFVector & DXFVector::operator += (const DXFVector & rV) +{ + fx+=rV.fx; fy+=rV.fy; fz+=rV.fz; + return *this; +} + + +inline DXFVector DXFVector::operator + (const DXFVector & rV) const +{ + return DXFVector(fx+rV.fx, fy+rV.fy, fz+rV.fz); +} + + +inline DXFVector DXFVector::operator - (const DXFVector & rV) const +{ + return DXFVector(fx-rV.fx, fy-rV.fy, fz-rV.fz); +} + + +inline DXFVector DXFVector::operator * (const DXFVector & rV) const +{ + return DXFVector( + fy * rV.fz - fz * rV.fy, + fz * rV.fx - fx * rV.fz, + fx * rV.fy - fy * rV.fx + ); +} + + +inline double DXFVector::SProd(const DXFVector & rV) const +{ + return fx*rV.fx + fy*rV.fy + fz*rV.fz; +} + + +inline DXFVector & DXFVector::operator *= (double fs) +{ + fx*=fs; fy*=fs; fz*=fs; + return *this; +} + + +inline DXFVector DXFVector::operator * (double fs) const +{ + return DXFVector(fx*fs,fy*fs,fz*fs); +} + + +inline bool DXFVector::operator == (const DXFVector & rV) const +{ + if (fx==rV.fx && fy==rV.fy && fz==rV.fz) return true; + else return false; +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/idxf/idxf.cxx b/vcl/source/filter/idxf/idxf.cxx new file mode 100644 index 000000000..26d42b10c --- /dev/null +++ b/vcl/source/filter/idxf/idxf.cxx @@ -0,0 +1,43 @@ +/* -*- 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 <filter/DxfReader.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> +#include "dxf2mtf.hxx" + +//================== GraphicImport - the exported function ================ + +bool ImportDxfGraphic(SvStream & rStream, Graphic & rGraphic) +{ + DXFRepresentation aDXF; + DXF2GDIMetaFile aConverter; + GDIMetaFile aMTF; + + if ( !aDXF.Read( rStream ) ) + return false; + if ( !aConverter.Convert( aDXF, aMTF, 60, 100 ) ) + return false; + rGraphic = Graphic(aMTF); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ieps/ieps.cxx b/vcl/source/filter/ieps/ieps.cxx new file mode 100644 index 000000000..9c223d6cd --- /dev/null +++ b/vcl/source/filter/ieps/ieps.cxx @@ -0,0 +1,825 @@ +/* -*- 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 <filter/EpsReader.hxx> +#include <vcl/svapp.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/BitmapTools.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/tempfile.hxx> +#include <osl/process.h> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <rtl/byteseq.hxx> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/safeint.hxx> +#include <memory> +#include <string_view> + +class FilterConfigItem; + +/************************************************************************* +|* +|* ImpSearchEntry() +|* +|* Description Checks if there is a string(pDest) of length nSize +|* inside the memory area pSource which is nComp bytes long. +|* Check is NON-CASE-SENSITIVE. The return value is the +|* address where the string is found or NULL +|* +*************************************************************************/ + +static sal_uInt8* ImplSearchEntry( sal_uInt8* pSource, sal_uInt8 const * pDest, size_t nComp, size_t nSize ) +{ + while ( nComp-- >= nSize ) + { + size_t i; + for ( i = 0; i < nSize; i++ ) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + break; + } + if ( i == nSize ) + return pSource; + pSource++; + } + return nullptr; +} + + +// SecurityCount is the buffersize of the buffer in which we will parse for a number +static tools::Long ImplGetNumber(sal_uInt8* &rBuf, sal_uInt32& nSecurityCount) +{ + bool bValid = true; + bool bNegative = false; + tools::Long nRetValue = 0; + while (nSecurityCount && (*rBuf == ' ' || *rBuf == 0x9)) + { + ++rBuf; + --nSecurityCount; + } + while ( nSecurityCount && ( *rBuf != ' ' ) && ( *rBuf != 0x9 ) && ( *rBuf != 0xd ) && ( *rBuf != 0xa ) ) + { + switch ( *rBuf ) + { + case '.' : + // we'll only use the integer format + bValid = false; + break; + case '-' : + bNegative = true; + break; + default : + if ( ( *rBuf < '0' ) || ( *rBuf > '9' ) ) + nSecurityCount = 1; // error parsing the bounding box values + else if ( bValid ) + { + const bool bFail = o3tl::checked_multiply<tools::Long>(nRetValue, 10, nRetValue) || + o3tl::checked_add<tools::Long>(nRetValue, *rBuf - '0', nRetValue); + if (bFail) + return 0; + } + break; + } + nSecurityCount--; + ++rBuf; + } + if ( bNegative ) + nRetValue = -nRetValue; + return nRetValue; +} + + +static int ImplGetLen( sal_uInt8* pBuf, int nMax ) +{ + int nLen = 0; + while( nLen != nMax ) + { + sal_uInt8 nDat = *pBuf++; + if ( nDat == 0x0a || nDat == 0x25 ) + break; + nLen++; + } + return nLen; +} + +static void MakeAsMeta(Graphic &rGraphic) +{ + ScopedVclPtrInstance< VirtualDevice > pVDev; + GDIMetaFile aMtf; + Size aSize = rGraphic.GetPrefSize(); + + if( !aSize.Width() || !aSize.Height() ) + aSize = Application::GetDefaultDevice()->PixelToLogic( + rGraphic.GetSizePixel(), MapMode(MapUnit::Map100thMM)); + else + aSize = OutputDevice::LogicToLogic( aSize, + rGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)); + + pVDev->EnableOutput( false ); + aMtf.Record( pVDev ); + pVDev->DrawBitmapEx( Point(), aSize, rGraphic.GetBitmapEx() ); + aMtf.Stop(); + aMtf.WindStart(); + aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + aMtf.SetPrefSize( aSize ); + rGraphic = aMtf; +} + +static oslProcessError runProcessWithPathSearch(const OUString &rProgName, + rtl_uString* pArgs[], sal_uInt32 nArgs, oslProcess *pProcess, + oslFileHandle *pIn, oslFileHandle *pOut, oslFileHandle *pErr) +{ + oslProcessError result = osl_Process_E_None; + oslSecurity pSecurity = osl_getCurrentSecurity(); +#ifdef _WIN32 + /* + * ooo#72096 + * On Window the underlying SearchPath searches in order of... + * The directory from which the application loaded. + * The current directory. + * The Windows system directory. + * The Windows directory. + * The directories that are listed in the PATH environment variable. + * + * Because one of our programs is called "convert" and there is a convert + * in the windows system directory, we want to explicitly search the PATH + * to avoid picking up on that one if ImageMagick's convert precedes it in + * PATH. + * + */ + OUString url; + OUString path(o3tl::toU(_wgetenv(L"PATH"))); + + oslFileError err = osl_searchFileURL(rProgName.pData, path.pData, &url.pData); + if (err != osl_File_E_None) + result = osl_Process_E_NotFound; + else + result = osl_executeProcess_WithRedirectedIO(url.pData, + pArgs, nArgs, osl_Process_HIDDEN, + pSecurity, nullptr, nullptr, 0, pProcess, pIn, pOut, pErr); +#else + result = osl_executeProcess_WithRedirectedIO(rProgName.pData, + pArgs, nArgs, osl_Process_SEARCHPATH | osl_Process_HIDDEN, + pSecurity, nullptr, nullptr, 0, pProcess, pIn, pOut, pErr); +#endif + osl_freeSecurityHandle( pSecurity ); + return result; +} + +#if defined(_WIN32) +# define EXESUFFIX ".exe" +#else +# define EXESUFFIX "" +#endif + +static bool RenderAsEMF(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, Graphic &rGraphic) +{ + utl::TempFile aTempOutput; + utl::TempFile aTempInput; + aTempOutput.EnableKillingFile(); + aTempInput.EnableKillingFile(); + OUString output; + osl::FileBase::getSystemPathFromFileURL(aTempOutput.GetURL(), output); + OUString input; + osl::FileBase::getSystemPathFromFileURL(aTempInput.GetURL(), input); + + SvStream* pInputStream = aTempInput.GetStream(StreamMode::WRITE); + sal_uInt64 nCount = pInputStream->WriteBytes(pBuf, nBytesRead); + aTempInput.CloseStream(); + + //fdo#64161 pstoedit under non-windows uses libEMF to output the EMF, but + //libEMF cannot calculate the bounding box of text, so the overall bounding + //box is not increased to include that of any text in the eps + // + //-drawbb will force pstoedit to draw a pair of pixels with the bg color to + //the topleft and bottom right of the bounding box as pstoedit sees it, + //which libEMF will then extend its bounding box to fit + // + //-usebbfrominput forces pstoedit to take the original ps bounding box + //as the bounding box as it sees it, instead of calculating its own + //which also doesn't work for this example + // + //Under Linux, positioning of letters within pstoedit is very approximate. + //Using the -nfw option delegates the positioning to the reader, and we + //will do a proper job. The option is ignored on Windows. + OUString arg1("-usebbfrominput"); //-usebbfrominput use the original ps bounding box + OUString arg2("-f"); + OUString arg3("emf:-OO -drawbb -nfw"); //-drawbb mark out the bounding box extent with bg pixels + //-nfw delegate letter placement to us + rtl_uString *args[] = + { + arg1.pData, arg2.pData, arg3.pData, input.pData, output.pData + }; + oslProcess aProcess; + oslFileHandle pIn = nullptr; + oslFileHandle pOut = nullptr; + oslFileHandle pErr = nullptr; + oslProcessError eErr = runProcessWithPathSearch( + "pstoedit" EXESUFFIX, + args, SAL_N_ELEMENTS(args), + &aProcess, &pIn, &pOut, &pErr); + + if (eErr!=osl_Process_E_None) + return false; + + bool bRet = false; + if (pIn) osl_closeFile(pIn); + osl_joinProcess(aProcess); + osl_freeProcessHandle(aProcess); + bool bEMFSupported=true; + if (pOut) + { + rtl::ByteSequence seq; + if (osl_File_E_None == osl_readLine(pOut, reinterpret_cast<sal_Sequence **>(&seq))) + { + OString line( reinterpret_cast<const char *>(seq.getConstArray()), seq.getLength() ); + if (line.startsWith("Unsupported output format")) + bEMFSupported=false; + } + osl_closeFile(pOut); + } + if (pErr) osl_closeFile(pErr); + if (nCount == nBytesRead && bEMFSupported) + { + SvFileStream aFile(output, StreamMode::READ); + if (GraphicConverter::Import(aFile, rGraphic, ConvertDataFormat::EMF) == ERRCODE_NONE) + bRet = true; + } + + return bRet; +} + +namespace { + +struct WriteData +{ + oslFileHandle m_pFile; + const sal_uInt8 *m_pBuf; + sal_uInt32 m_nBytesToWrite; +}; + +} + +extern "C" { + +static void WriteFileInThread(void *wData) +{ + sal_uInt64 nCount; + WriteData *wdata = static_cast<WriteData *>(wData); + osl_writeFile(wdata->m_pFile, wdata->m_pBuf, wdata->m_nBytesToWrite, &nCount); + // The number of bytes written does not matter. + // The helper process may close its input stream before reading it all. + // (e.g. at "showpage" in EPS) + + // File must be closed here. + // Otherwise, the helper process may wait for the next input, + // then its stdout is not closed and osl_readFile() blocks. + if (wdata->m_pFile) osl_closeFile(wdata->m_pFile); +} + +} + +static bool RenderAsBMPThroughHelper(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, + Graphic& rGraphic, + std::initializer_list<std::u16string_view> aProgNames, + rtl_uString* pArgs[], size_t nArgs) +{ + oslProcess aProcess = nullptr; + oslFileHandle pIn = nullptr; + oslFileHandle pOut = nullptr; + oslFileHandle pErr = nullptr; + oslProcessError eErr = osl_Process_E_Unknown; + for (const auto& rProgName : aProgNames) + { + eErr = runProcessWithPathSearch(OUString(rProgName), pArgs, nArgs, &aProcess, &pIn, &pOut, &pErr); + if (eErr == osl_Process_E_None) + break; + } + if (eErr!=osl_Process_E_None) + return false; + + WriteData Data; + Data.m_pFile = pIn; + Data.m_pBuf = pBuf; + Data.m_nBytesToWrite = nBytesRead; + oslThread hThread = osl_createThread(WriteFileInThread, &Data); + + bool bRet = false; + sal_uInt64 nCount; + { + SvMemoryStream aMemStm; + sal_uInt8 aBuf[32000]; + oslFileError eFileErr = osl_readFile(pOut, aBuf, 32000, &nCount); + while (eFileErr == osl_File_E_None && nCount) + { + aMemStm.WriteBytes(aBuf, sal::static_int_cast<std::size_t>(nCount)); + eFileErr = osl_readFile(pOut, aBuf, 32000, &nCount); + } + + aMemStm.Seek(0); + if ( + aMemStm.GetEndOfData() && + GraphicConverter::Import(aMemStm, rGraphic, ConvertDataFormat::BMP) == ERRCODE_NONE + ) + { + MakeAsMeta(rGraphic); + bRet = true; + } + } + if (pOut) osl_closeFile(pOut); + if (pErr) osl_closeFile(pErr); + osl_joinProcess(aProcess); + osl_freeProcessHandle(aProcess); + osl_joinWithThread(hThread); + osl_destroyThread(hThread); + return bRet; +} + +static bool RenderAsBMPThroughConvert(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, + Graphic &rGraphic) +{ + // density in pixel/inch + OUString arg1("-density"); + // since the preview is also used for PDF-Export & printing on non-PS-printers, + // use some better quality - 300x300 should allow some resizing as well + OUString arg2("300x300"); + // read eps from STDIN + OUString arg3("eps:-"); + // write bmp to STDOUT + OUString arg4("bmp:-"); + rtl_uString *args[] = + { + arg1.pData, arg2.pData, arg3.pData, arg4.pData + }; + return RenderAsBMPThroughHelper(pBuf, nBytesRead, rGraphic, + { u"convert" EXESUFFIX }, + args, + SAL_N_ELEMENTS(args)); +} + +static bool RenderAsBMPThroughGS(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, + Graphic &rGraphic) +{ + OUString arg1("-q"); + OUString arg2("-dBATCH"); + OUString arg3("-dNOPAUSE"); + OUString arg4("-dPARANOIDSAFER"); + OUString arg5("-dEPSCrop"); + OUString arg6("-dTextAlphaBits=4"); + OUString arg7("-dGraphicsAlphaBits=4"); + OUString arg8("-r300x300"); + OUString arg9("-sDEVICE=bmp16m"); + OUString arg10("-sOutputFile=-"); + OUString arg11("-"); + rtl_uString *args[] = + { + arg1.pData, arg2.pData, arg3.pData, arg4.pData, arg5.pData, + arg6.pData, arg7.pData, arg8.pData, arg9.pData, arg10.pData, + arg11.pData + }; + return RenderAsBMPThroughHelper(pBuf, nBytesRead, rGraphic, +#ifdef _WIN32 + // Try both 32-bit and 64-bit ghostscript executable name + { + u"gswin32c" EXESUFFIX, + u"gswin64c" EXESUFFIX, + }, +#else + { u"gs" EXESUFFIX }, +#endif + args, + SAL_N_ELEMENTS(args)); +} + +static bool RenderAsBMP(const sal_uInt8* pBuf, sal_uInt32 nBytesRead, Graphic &rGraphic) +{ + if (RenderAsBMPThroughGS(pBuf, nBytesRead, rGraphic)) + return true; + else + return RenderAsBMPThroughConvert(pBuf, nBytesRead, rGraphic); +} + +// this method adds a replacement action containing the original wmf or tiff replacement, +// so the original eps can be written when storing to ODF. +static void CreateMtfReplacementAction( GDIMetaFile& rMtf, SvStream& rStrm, sal_uInt32 nOrigPos, sal_uInt32 nPSSize, + sal_uInt32 nPosWMF, sal_uInt32 nSizeWMF, sal_uInt32 nPosTIFF, sal_uInt32 nSizeTIFF ) +{ + OString aComment("EPSReplacementGraphic"); + if ( nSizeWMF || nSizeTIFF ) + { + std::vector<sal_uInt8> aWMFBuf; + if (nSizeWMF && checkSeek(rStrm, nOrigPos + nPosWMF) && rStrm.remainingSize() >= nSizeWMF) + { + aWMFBuf.resize(nSizeWMF); + aWMFBuf.resize(rStrm.ReadBytes(aWMFBuf.data(), nSizeWMF)); + } + nSizeWMF = aWMFBuf.size(); + + std::vector<sal_uInt8> aTIFFBuf; + if (nSizeTIFF && checkSeek(rStrm, nOrigPos + nPosTIFF) && rStrm.remainingSize() >= nSizeTIFF) + { + aTIFFBuf.resize(nSizeTIFF); + aTIFFBuf.resize(rStrm.ReadBytes(aTIFFBuf.data(), nSizeTIFF)); + } + nSizeTIFF = aTIFFBuf.size(); + + SvMemoryStream aReplacement( nSizeWMF + nSizeTIFF + 28 ); + sal_uInt32 const nMagic = 0xc6d3d0c5; + sal_uInt32 nPPos = 28 + nSizeWMF + nSizeTIFF; + sal_uInt32 nWPos = nSizeWMF ? 28 : 0; + sal_uInt32 nTPos = nSizeTIFF ? 28 + nSizeWMF : 0; + + aReplacement.WriteUInt32( nMagic ).WriteUInt32( nPPos ).WriteUInt32( nPSSize ) + .WriteUInt32( nWPos ).WriteUInt32( nSizeWMF ) + .WriteUInt32( nTPos ).WriteUInt32( nSizeTIFF ); + + aReplacement.WriteBytes(aWMFBuf.data(), nSizeWMF); + aReplacement.WriteBytes(aTIFFBuf.data(), nSizeTIFF); + rMtf.AddAction( static_cast<MetaAction*>( new MetaCommentAction( aComment, 0, static_cast<const sal_uInt8*>(aReplacement.GetData()), aReplacement.Tell() ) ) ); + } + else + rMtf.AddAction( static_cast<MetaAction*>( new MetaCommentAction( aComment, 0, nullptr, 0 ) ) ); +} + +//there is no preview -> make a red box +static void MakePreview(sal_uInt8* pBuf, sal_uInt32 nBytesRead, + tools::Long nWidth, tools::Long nHeight, Graphic &rGraphic) +{ + GDIMetaFile aMtf; + ScopedVclPtrInstance< VirtualDevice > pVDev; + vcl::Font aFont; + + pVDev->EnableOutput( false ); + aMtf.Record( pVDev ); + pVDev->SetLineColor( COL_RED ); + pVDev->SetFillColor(); + + aFont.SetColor( COL_LIGHTRED ); + + pVDev->Push( vcl::PushFlags::FONT ); + pVDev->SetFont( aFont ); + + tools::Rectangle aRect( Point( 1, 1 ), Size( nWidth - 2, nHeight - 2 ) ); + pVDev->DrawRect( aRect ); + + OUString aString; + int nLen; + sal_uInt8* pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%Title:"), nBytesRead - 32, 8 ); + sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0; + if (nRemainingBytes >= 8) + { + pDest += 8; + nRemainingBytes -= 8; + if (nRemainingBytes && *pDest == ' ') + { + ++pDest; + --nRemainingBytes; + } + nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32)); + if (o3tl::make_unsigned(nLen) < nRemainingBytes) + { + sal_uInt8 aOldValue(pDest[ nLen ]); pDest[ nLen ] = 0; + if ( strcmp( reinterpret_cast<char*>(pDest), "none" ) != 0 ) + { + const char* pStr = reinterpret_cast<char*>(pDest); + aString += " Title:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n"; + } + pDest[ nLen ] = aOldValue; + } + } + pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%Creator:"), nBytesRead - 32, 10 ); + nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0; + if (nRemainingBytes >= 10) + { + pDest += 10; + nRemainingBytes -= 10; + if (nRemainingBytes && *pDest == ' ') + { + ++pDest; + --nRemainingBytes; + } + nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32)); + if (o3tl::make_unsigned(nLen) < nRemainingBytes) + { + sal_uInt8 aOldValue(pDest[nLen]); pDest[nLen] = 0; + const char* pStr = reinterpret_cast<char*>(pDest); + aString += " Creator:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n"; + pDest[nLen] = aOldValue; + } + } + pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%CreationDate:"), nBytesRead - 32, 15 ); + nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0; + if (nRemainingBytes >= 15) + { + pDest += 15; + nRemainingBytes -= 15; + if (nRemainingBytes && *pDest == ' ') + { + ++pDest; + --nRemainingBytes; + } + nLen = ImplGetLen(pDest, std::min<sal_uInt32>(nRemainingBytes, 32)); + if (o3tl::make_unsigned(nLen) < nRemainingBytes) + { + sal_uInt8 aOldValue(pDest[ nLen ]); pDest[ nLen ] = 0; + if ( strcmp( reinterpret_cast<char*>(pDest), "none" ) != 0 ) + { + aString += " CreationDate:" + OUString::createFromAscii( reinterpret_cast<char*>(pDest) ) + "\n"; + const char* pStr = reinterpret_cast<char*>(pDest); + aString += " CreationDate:" + OUString(pStr, strlen(pStr), RTL_TEXTENCODING_ASCII_US) + "\n"; + } + pDest[ nLen ] = aOldValue; + } + } + pDest = ImplSearchEntry( pBuf, reinterpret_cast<sal_uInt8 const *>("%%LanguageLevel:"), nBytesRead - 4, 16 ); + nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf)) : 0; + if (nRemainingBytes >= 16) + { + pDest += 16; + nRemainingBytes -= 16; + sal_uInt32 nCount = std::min<sal_uInt32>(nRemainingBytes, 4U); + sal_uInt32 nNumber = ImplGetNumber(pDest, nCount); + if (nCount && nNumber < 10) + { + aString += " LanguageLevel:" + OUString::number( nNumber ); + } + } + pVDev->DrawText( aRect, aString, DrawTextFlags::Clip | DrawTextFlags::MultiLine ); + pVDev->Pop(); + aMtf.Stop(); + aMtf.WindStart(); + aMtf.SetPrefMapMode(MapMode(MapUnit::MapPoint)); + aMtf.SetPrefSize( Size( nWidth, nHeight ) ); + rGraphic = aMtf; +} + +//================== GraphicImport - the exported function ================ + + +bool ImportEpsGraphic( SvStream & rStream, Graphic & rGraphic) +{ + if ( rStream.GetError() ) + return false; + + Graphic aGraphic; + bool bRetValue = false; + bool bHasPreview = false; + sal_uInt32 nSignature = 0, nPSStreamPos, nPSSize = 0; + sal_uInt32 nSizeWMF = 0; + sal_uInt32 nPosWMF = 0; + sal_uInt32 nSizeTIFF = 0; + sal_uInt32 nPosTIFF = 0; + + auto nOrigPos = nPSStreamPos = rStream.Tell(); + SvStreamEndian nOldFormat = rStream.GetEndian(); + + rStream.SetEndian( SvStreamEndian::LITTLE ); + rStream.ReadUInt32( nSignature ); + if ( nSignature == 0xc6d3d0c5 ) + { + rStream.ReadUInt32( nPSStreamPos ).ReadUInt32( nPSSize ).ReadUInt32( nPosWMF ).ReadUInt32( nSizeWMF ); + + // first we try to get the metafile grafix + + if ( nSizeWMF ) + { + if (nPosWMF && checkSeek(rStream, nOrigPos + nPosWMF)) + { + if (GraphicConverter::Import(rStream, aGraphic, ConvertDataFormat::WMF) == ERRCODE_NONE) + bHasPreview = bRetValue = true; + } + } + else + { + rStream.ReadUInt32( nPosTIFF ).ReadUInt32( nSizeTIFF ); + + // else we have to get the tiff grafix + + if (nPosTIFF && nSizeTIFF && checkSeek(rStream, nOrigPos + nPosTIFF)) + { + if ( GraphicConverter::Import( rStream, aGraphic, ConvertDataFormat::TIF ) == ERRCODE_NONE ) + { + MakeAsMeta(aGraphic); + rStream.Seek( nOrigPos + nPosTIFF ); + bHasPreview = bRetValue = true; + } + } + } + } + else + { + nPSStreamPos = nOrigPos; // no preview available _>so we must get the size manually + nPSSize = rStream.Seek( STREAM_SEEK_TO_END ) - nOrigPos; + } + + std::vector<sal_uInt8> aHeader(22, 0); + rStream.Seek( nPSStreamPos ); + rStream.ReadBytes(aHeader.data(), 22); // check PostScript header + sal_uInt8* pHeader = aHeader.data(); + bool bOk = ImplSearchEntry(pHeader, reinterpret_cast<sal_uInt8 const *>("%!PS-Adobe"), 10, 10) && + ImplSearchEntry(pHeader + 15, reinterpret_cast<sal_uInt8 const *>("EPS"), 3, 3); + if (bOk) + { + rStream.Seek(nPSStreamPos); + bOk = rStream.remainingSize() >= nPSSize; + SAL_WARN_IF(!bOk, "filter.eps", "eps claims to be: " << nPSSize << " in size, but only " << rStream.remainingSize() << " remains"); + } + if (bOk) + { + std::unique_ptr<sal_uInt8[]> pBuf( new sal_uInt8[ nPSSize ] ); + + sal_uInt32 nBufStartPos = rStream.Tell(); + sal_uInt32 nBytesRead = rStream.ReadBytes(pBuf.get(), nPSSize); + if ( nBytesRead == nPSSize ) + { + sal_uInt32 nSecurityCount = 32; + // if there is no tiff/wmf preview, we will parse for a preview in + // the eps prolog + if (!bHasPreview && nBytesRead >= nSecurityCount) + { + sal_uInt8* pDest = ImplSearchEntry( pBuf.get(), reinterpret_cast<sal_uInt8 const *>("%%BeginPreview:"), nBytesRead - nSecurityCount, 15 ); + sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf.get())) : 0; + if (nRemainingBytes >= 15) + { + pDest += 15; + nSecurityCount = nRemainingBytes - 15; + tools::Long nWidth = ImplGetNumber(pDest, nSecurityCount); + tools::Long nHeight = ImplGetNumber(pDest, nSecurityCount); + tools::Long nBitDepth = ImplGetNumber(pDest, nSecurityCount); + tools::Long nScanLines = ImplGetNumber(pDest, nSecurityCount); + pDest = ImplSearchEntry(pDest, reinterpret_cast<sal_uInt8 const *>("%"), nSecurityCount, 1); // go to the first Scanline + bOk = pDest && nWidth > 0 && nHeight > 0 && ( ( nBitDepth == 1 ) || ( nBitDepth == 8 ) ) && nScanLines; + if (bOk) + { + tools::Long nResult; + bOk = !o3tl::checked_multiply(nWidth, nHeight, nResult) && nResult <= SAL_MAX_INT32/2/3; + } + if (bOk) + { + rStream.Seek( nBufStartPos + ( pDest - pBuf.get() ) ); + + vcl::bitmap::RawBitmap aBitmap( Size( nWidth, nHeight ), 24 ); + { + bool bIsValid = true; + sal_uInt8 nDat = 0; + char nByte; + for (tools::Long y = 0; bIsValid && y < nHeight; ++y) + { + int nBitsLeft = 0; + for (tools::Long x = 0; x < nWidth; ++x) + { + if ( --nBitsLeft < 0 ) + { + while ( bIsValid && ( nBitsLeft != 7 ) ) + { + rStream.ReadChar(nByte); + bIsValid = rStream.good(); + if (!bIsValid) + break; + switch (nByte) + { + case 0x0a : + if ( --nScanLines < 0 ) + bIsValid = false; + break; + case 0x09 : + case 0x0d : + case 0x20 : + case 0x25 : + break; + default: + { + if ( nByte >= '0' ) + { + if ( nByte > '9' ) + { + nByte &=~0x20; // case none sensitive for hexadecimal values + nByte -= ( 'A' - 10 ); + if ( nByte > 15 ) + bIsValid = false; + } + else + nByte -= '0'; + nBitsLeft += 4; + nDat <<= 4; + nDat |= ( nByte ^ 0xf ); // in epsi a zero bit represents white color + } + else + bIsValid = false; + } + break; + } + } + } + if (!bIsValid) + break; + if ( nBitDepth == 1 ) + aBitmap.SetPixel( y, x, Color(ColorTransparency, static_cast<sal_uInt8>(nDat >> nBitsLeft) & 1) ); + else + { + aBitmap.SetPixel( y, x, nDat ? COL_WHITE : COL_BLACK ); // nBitDepth == 8 + nBitsLeft = 0; + } + } + } + if (bIsValid) + { + ScopedVclPtrInstance<VirtualDevice> pVDev; + GDIMetaFile aMtf; + Size aSize( nWidth, nHeight ); + pVDev->EnableOutput( false ); + aMtf.Record( pVDev ); + aSize = OutputDevice::LogicToLogic(aSize, MapMode(), MapMode(MapUnit::Map100thMM)); + pVDev->DrawBitmapEx( Point(), aSize, vcl::bitmap::CreateFromData(std::move(aBitmap)) ); + aMtf.Stop(); + aMtf.WindStart(); + aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + aMtf.SetPrefSize( aSize ); + aGraphic = aMtf; + bHasPreview = bRetValue = true; + } + } + } + } + } + + sal_uInt8* pDest = ImplSearchEntry( pBuf.get(), reinterpret_cast<sal_uInt8 const *>("%%BoundingBox:"), nBytesRead, 14 ); + sal_uInt32 nRemainingBytes = pDest ? (nBytesRead - (pDest - pBuf.get())) : 0; + if (nRemainingBytes >= 14) + { + pDest += 14; + nSecurityCount = std::min<sal_uInt32>(nRemainingBytes - 14, 100); + tools::Long nNumb[4]; + nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0; + for ( int i = 0; ( i < 4 ) && nSecurityCount; i++ ) + { + nNumb[ i ] = ImplGetNumber(pDest, nSecurityCount); + } + bool bFail = nSecurityCount == 0; + tools::Long nWidth(0), nHeight(0); + if (!bFail) + bFail = o3tl::checked_sub(nNumb[2], nNumb[0], nWidth) || o3tl::checked_add(nWidth, tools::Long(1), nWidth); + if (!bFail) + bFail = o3tl::checked_sub(nNumb[3], nNumb[1], nHeight) || o3tl::checked_add(nHeight, tools::Long(1), nHeight); + if (!bFail && nWidth > 0 && nHeight > 0) + { + GDIMetaFile aMtf; + + // if there is no preview -> try with gs to make one + if (!bHasPreview && !utl::ConfigManager::IsFuzzing()) + { + bHasPreview = RenderAsEMF(pBuf.get(), nBytesRead, aGraphic); + if (!bHasPreview) + bHasPreview = RenderAsBMP(pBuf.get(), nBytesRead, aGraphic); + } + + // if there is no preview -> make a red box + if( !bHasPreview ) + { + MakePreview(pBuf.get(), nBytesRead, nWidth, nHeight, + aGraphic); + } + + GfxLink aGfxLink( std::move(pBuf), nPSSize, GfxLinkType::EpsBuffer ) ; + aMtf.AddAction( static_cast<MetaAction*>( new MetaEPSAction( Point(), Size( nWidth, nHeight ), + aGfxLink, aGraphic.GetGDIMetaFile() ) ) ); + CreateMtfReplacementAction( aMtf, rStream, nOrigPos, nPSSize, nPosWMF, nSizeWMF, nPosTIFF, nSizeTIFF ); + aMtf.WindStart(); + aMtf.SetPrefMapMode(MapMode(MapUnit::MapPoint)); + aMtf.SetPrefSize( Size( nWidth, nHeight ) ); + rGraphic = aMtf; + bRetValue = true; + } + } + } + } + + rStream.SetEndian(nOldFormat); + rStream.Seek( nOrigPos ); + return bRetValue; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/igif/decode.cxx b/vcl/source/filter/igif/decode.cxx new file mode 100644 index 000000000..b062593a9 --- /dev/null +++ b/vcl/source/filter/igif/decode.cxx @@ -0,0 +1,214 @@ +/* -*- 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 "decode.hxx" + +#include <cstdlib> +#include <cstring> + +struct GIFLZWTableEntry +{ + GIFLZWTableEntry* pPrev; + GIFLZWTableEntry* pFirst; + sal_uInt8 nData; +}; + +GIFLZWDecompressor::GIFLZWDecompressor(sal_uInt8 cDataSize) + : pTable(new GIFLZWTableEntry[4098]) + , pOutBufData(pOutBuf.data() + 4096) + , pBlockBuf(nullptr) + , nInputBitsBuf(0) + , bEOIFound(false) + , nDataSize(cDataSize) + , nBlockBufSize(0) + , nBlockBufPos(0) + , nClearCode(1 << nDataSize) + , nEOICode(nClearCode + 1) + , nTableSize(nEOICode + 1) + , nCodeSize(nDataSize + 1) + , nOldCode(0xffff) + , nOutBufDataLen(0) + , nInputBitsBufSize(0) +{ + for (sal_uInt16 i = 0; i < nTableSize; ++i) + { + pTable[i].pPrev = nullptr; + pTable[i].pFirst = &pTable[i]; + pTable[i].nData = static_cast<sal_uInt8>(i); + } + + memset(pTable.get() + nTableSize, 0, sizeof(GIFLZWTableEntry) * (4098 - nTableSize)); +} + +GIFLZWDecompressor::~GIFLZWDecompressor() +{ +} + +Scanline GIFLZWDecompressor::DecompressBlock( sal_uInt8* pSrc, sal_uInt8 cBufSize, + sal_uLong& rCount, bool& rEOI ) +{ + sal_uLong nTargetSize = 4096; + sal_uLong nCount = 0; + sal_uInt8* pTarget = static_cast<sal_uInt8*>(std::malloc( nTargetSize )); + sal_uInt8* pTmpTarget = pTarget; + + nBlockBufSize = cBufSize; + nBlockBufPos = 0; + pBlockBuf = pSrc; + + while (pTarget && ProcessOneCode()) + { + nCount += nOutBufDataLen; + + if( nCount > nTargetSize ) + { + sal_uLong nNewSize = nTargetSize << 1; + sal_uLong nOffset = pTmpTarget - pTarget; + if (auto p = static_cast<sal_uInt8*>(std::realloc(pTarget, nNewSize))) + pTarget = p; + else + { + free(pTarget); + pTarget = nullptr; + break; + } + + nTargetSize = nNewSize; + pTmpTarget = pTarget + nOffset; + } + + memcpy( pTmpTarget, pOutBufData, nOutBufDataLen ); + pTmpTarget += nOutBufDataLen; + pOutBufData += nOutBufDataLen; + nOutBufDataLen = 0; + + if ( bEOIFound ) + break; + } + + rCount = nCount; + rEOI = bEOIFound; + + return pTarget; +} + +bool GIFLZWDecompressor::AddToTable( sal_uInt16 nPrevCode, sal_uInt16 nCodeFirstData ) +{ + if( nTableSize < 4096 ) + { + GIFLZWTableEntry* pE = pTable.get() + nTableSize; + pE->pPrev = pTable.get() + nPrevCode; + pE->pFirst = pE->pPrev->pFirst; + GIFLZWTableEntry *pEntry = pTable[nCodeFirstData].pFirst; + if (!pEntry) + return false; + pE->nData = pEntry->nData; + nTableSize++; + + if ( ( nTableSize == static_cast<sal_uInt16>(1 << nCodeSize) ) && ( nTableSize < 4096 ) ) + nCodeSize++; + } + return true; +} + +bool GIFLZWDecompressor::ProcessOneCode() +{ + bool bRet = false; + bool bEndOfBlock = false; + + while( nInputBitsBufSize < nCodeSize ) + { + if( nBlockBufPos >= nBlockBufSize ) + { + bEndOfBlock = true; + break; + } + + nInputBitsBuf |= static_cast<sal_uLong>(pBlockBuf[ nBlockBufPos++ ]) << nInputBitsBufSize; + nInputBitsBufSize += 8; + } + + if ( !bEndOfBlock ) + { + // fetch code from input buffer + sal_uInt16 nCode = sal::static_int_cast< sal_uInt16 >( + static_cast<sal_uInt16>(nInputBitsBuf) & ( ~( 0xffff << nCodeSize ) )); + nInputBitsBuf >>= nCodeSize; + nInputBitsBufSize = nInputBitsBufSize - nCodeSize; + + if ( nCode < nClearCode ) + { + bool bOk = true; + if ( nOldCode != 0xffff ) + bOk = AddToTable(nOldCode, nCode); + if (!bOk) + return false; + } + else if ( ( nCode > nEOICode ) && ( nCode <= nTableSize ) ) + { + if ( nOldCode != 0xffff ) + { + bool bOk; + if ( nCode == nTableSize ) + bOk = AddToTable( nOldCode, nOldCode ); + else + bOk = AddToTable( nOldCode, nCode ); + if (!bOk) + return false; + } + } + else + { + if ( nCode == nClearCode ) + { + nTableSize = nEOICode + 1; + nCodeSize = nDataSize + 1; + nOldCode = 0xffff; + nOutBufDataLen = 0; + } + else + bEOIFound = true; + + return true; + } + + nOldCode = nCode; + + if (nCode >= 4096) + return false; + + // write character(/-sequence) of code nCode in the output buffer: + GIFLZWTableEntry* pE = pTable.get() + nCode; + do + { + if (pOutBufData == pOutBuf.data()) //can't go back past start + return false; + nOutBufDataLen++; + *(--pOutBufData) = pE->nData; + pE = pE->pPrev; + } + while( pE ); + + bRet = true; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/igif/decode.hxx b/vcl/source/filter/igif/decode.hxx new file mode 100644 index 000000000..6e16730a3 --- /dev/null +++ b/vcl/source/filter/igif/decode.hxx @@ -0,0 +1,66 @@ +/* -*- 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_VCL_SOURCE_FILTER_IGIF_DECODE_HXX +#define INCLUDED_VCL_SOURCE_FILTER_IGIF_DECODE_HXX + +#include <tools/solar.h> +#include <vcl/Scanline.hxx> +#include <array> +#include <memory> + +struct GIFLZWTableEntry; + +class GIFLZWDecompressor +{ + std::unique_ptr<GIFLZWTableEntry[]> + pTable; + std::array<sal_uInt8, 4096> + pOutBuf; + sal_uInt8* pOutBufData; + sal_uInt8* pBlockBuf; + sal_uLong nInputBitsBuf; + bool bEOIFound; + sal_uInt8 nDataSize; + sal_uInt8 nBlockBufSize; + sal_uInt8 nBlockBufPos; + sal_uInt16 nClearCode; + sal_uInt16 nEOICode; + sal_uInt16 nTableSize; + sal_uInt16 nCodeSize; + sal_uInt16 nOldCode; + sal_uInt16 nOutBufDataLen; + sal_uInt16 nInputBitsBufSize; + + bool AddToTable(sal_uInt16 nPrevCode, sal_uInt16 nCodeFirstData); + bool ProcessOneCode(); + + GIFLZWDecompressor(const GIFLZWDecompressor&) = delete; + GIFLZWDecompressor& operator=(const GIFLZWDecompressor&) = delete; +public: + + explicit GIFLZWDecompressor( sal_uInt8 cDataSize ); + ~GIFLZWDecompressor(); + + Scanline DecompressBlock( sal_uInt8* pSrc, sal_uInt8 cBufSize, sal_uLong& rCount, bool& rEOI ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/igif/gifread.cxx b/vcl/source/filter/igif/gifread.cxx new file mode 100644 index 000000000..fa1270e6a --- /dev/null +++ b/vcl/source/filter/igif/gifread.cxx @@ -0,0 +1,1002 @@ +/* -*- 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/log.hxx> +#include <tools/stream.hxx> +#include "decode.hxx" +#include "gifread.hxx" +#include <memory> +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +#define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING ) + +namespace { + +enum GIFAction +{ + GLOBAL_HEADER_READING, + MARKER_READING, + EXTENSION_READING, + LOCAL_HEADER_READING, + FIRST_BLOCK_READING, + NEXT_BLOCK_READING, + ABORT_READING, + END_READING +}; + +enum ReadState +{ + GIFREAD_OK, + GIFREAD_ERROR, + GIFREAD_NEED_MORE +}; + +} + +class GIFLZWDecompressor; + +class SvStream; + +namespace { + +class GIFReader : public GraphicReader +{ + Animation aAnimation; + sal_uInt64 nAnimationByteSize; + sal_uInt64 nAnimationMinFileData; + Bitmap aBmp8; + Bitmap aBmp1; + BitmapPalette aGPalette; + BitmapPalette aLPalette; + SvStream& rIStm; + std::vector<sal_uInt8> aSrcBuf; + std::unique_ptr<GIFLZWDecompressor> pDecomp; + BitmapScopedWriteAccess pAcc8; + BitmapScopedWriteAccess pAcc1; + tools::Long nYAcc; + tools::Long nLastPos; + sal_uInt64 nMaxStreamData; + sal_uInt32 nLogWidth100; + sal_uInt32 nLogHeight100; + sal_uInt16 nTimer; + sal_uInt16 nGlobalWidth; // maximum imagewidth from header + sal_uInt16 nGlobalHeight; // maximum imageheight from header + sal_uInt16 nImageWidth; // maximum screenwidth from header + sal_uInt16 nImageHeight; // maximum screenheight from header + sal_uInt16 nImagePosX; + sal_uInt16 nImagePosY; + sal_uInt16 nImageX; // maximum screenwidth from header + sal_uInt16 nImageY; // maximum screenheight from header + sal_uInt16 nLastImageY; + sal_uInt16 nLastInterCount; + sal_uInt16 nLoops; + GIFAction eActAction; + bool bStatus; + bool bGCTransparent; // is the image transparent, if yes: + bool bInterlaced; + bool bOverreadBlock; + bool bImGraphicReady; + bool bGlobalPalette; + sal_uInt8 nBackgroundColor; // backgroundcolour + sal_uInt8 nGCTransparentIndex; // pixels of this index are transparent + sal_uInt8 nGCDisposalMethod; // 'Disposal Method' (see GIF docs) + sal_uInt8 cTransIndex1; + sal_uInt8 cNonTransIndex1; + + void ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount ); + void ClearImageExtensions(); + void CreateBitmaps( tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal, bool bWatchForBackgroundColor ); + bool ReadGlobalHeader(); + bool ReadExtension(); + bool ReadLocalHeader(); + sal_uLong ReadNextBlock(); + void FillImages( const sal_uInt8* pBytes, sal_uLong nCount ); + void CreateNewBitmaps(); + bool ProcessGIF(); + +public: + + ReadState ReadGIF( Graphic& rGraphic ); + bool ReadIsAnimated(); + void GetLogicSize(Size& rLogicSize); + Graphic GetIntermediateGraphic(); + + explicit GIFReader( SvStream& rStm ); +}; + +} + +GIFReader::GIFReader( SvStream& rStm ) + : nAnimationByteSize(0) + , nAnimationMinFileData(0) + , aGPalette ( 256 ) + , aLPalette ( 256 ) + , rIStm ( rStm ) + , nYAcc ( 0 ) + , nLastPos ( rStm.Tell() ) + , nMaxStreamData( rStm.remainingSize() ) + , nLogWidth100 ( 0 ) + , nLogHeight100 ( 0 ) + , nGlobalWidth ( 0 ) + , nGlobalHeight ( 0 ) + , nImageWidth ( 0 ) + , nImageHeight ( 0 ) + , nImagePosX ( 0 ) + , nImagePosY ( 0 ) + , nImageX ( 0 ) + , nImageY ( 0 ) + , nLastImageY ( 0 ) + , nLastInterCount ( 0 ) + , nLoops ( 1 ) + , eActAction ( GLOBAL_HEADER_READING ) + , bStatus ( false ) + , bGCTransparent ( false ) + , bInterlaced ( false) + , bOverreadBlock ( false ) + , bImGraphicReady ( false ) + , bGlobalPalette ( false ) + , nBackgroundColor ( 0 ) + , nGCTransparentIndex ( 0 ) + , cTransIndex1 ( 0 ) + , cNonTransIndex1 ( 0 ) +{ + maUpperName = "SVIGIF"; + aSrcBuf.resize(256); // Memory buffer for ReadNextBlock + ClearImageExtensions(); +} + +void GIFReader::ClearImageExtensions() +{ + nGCDisposalMethod = 0; + bGCTransparent = false; + nTimer = 0; +} + +void GIFReader::CreateBitmaps(tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal, + bool bWatchForBackgroundColor) +{ + const Size aSize(nWidth, nHeight); + + sal_uInt64 nCombinedPixSize = nWidth * nHeight; + if (bGCTransparent) + nCombinedPixSize += (nCombinedPixSize/8); + + // "Overall data compression asymptotically approaches 3839 × 8 / 12 = 2559 1/3" + // so assume compression of 1:2560 is possible + // (http://cloudinary.com/blog/a_one_color_image_is_worth_two_thousand_words suggests + // 1:1472.88 [184.11 x 8] is more realistic) + + sal_uInt64 nMinFileData = nWidth * nHeight / 2560; + + nMinFileData += nAnimationMinFileData; + nCombinedPixSize += nAnimationByteSize; + + if (nMaxStreamData < nMinFileData) + { + //there is nowhere near enough data in this stream to fill the claimed dimensions + SAL_WARN("vcl.filter", "in gif frame index " << aAnimation.Count() << " gif claims dimensions " << nWidth << " x " << nHeight << + " but filesize of " << nMaxStreamData << " is surely insufficiently large to fill all frame images"); + bStatus = false; + return; + } + + // Don't bother allocating a bitmap of a size that would fail on a + // 32-bit system. We have at least one unit tests that is expected + // to fail (loading a 65535*65535 size GIF + // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but + // which doesn't fail on 64-bit macOS at least. Why the loading + // fails on 64-bit Linux, no idea. + if (nCombinedPixSize >= SAL_MAX_INT32/3*2) + { + bStatus = false; + return; + } + + if (!aSize.Width() || !aSize.Height()) + { + bStatus = false; + return; + } + + if (bGCTransparent) + { + const Color aWhite(COL_WHITE); + + aBmp1 = Bitmap(aSize, vcl::PixelFormat::N1_BPP); + + if (!aAnimation.Count()) + aBmp1.Erase(aWhite); + + pAcc1 = BitmapScopedWriteAccess(aBmp1); + + if (pAcc1) + { + cTransIndex1 = static_cast<sal_uInt8>(pAcc1->GetBestPaletteIndex(aWhite)); + cNonTransIndex1 = cTransIndex1 ? 0 : 1; + } + else + { + bStatus = false; + } + } + + if (bStatus) + { + aBmp8 = Bitmap(aSize, vcl::PixelFormat::N8_BPP, pPal); + + if (!aBmp8.IsEmpty() && bWatchForBackgroundColor && aAnimation.Count()) + aBmp8.Erase((*pPal)[nBackgroundColor]); + else + aBmp8.Erase(COL_WHITE); + + pAcc8 = BitmapScopedWriteAccess(aBmp8); + bStatus = bool(pAcc8); + } +} + +bool GIFReader::ReadGlobalHeader() +{ + char pBuf[ 7 ]; + bool bRet = false; + + auto nRead = rIStm.ReadBytes(pBuf, 6); + if (nRead == 6 && NO_PENDING(rIStm)) + { + pBuf[ 6 ] = 0; + if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) ) + { + nRead = rIStm.ReadBytes(pBuf, 7); + if (nRead == 7 && NO_PENDING(rIStm)) + { + sal_uInt8 nAspect; + sal_uInt8 nRF; + SvMemoryStream aMemStm; + + aMemStm.SetBuffer( pBuf, 7, 7 ); + aMemStm.ReadUInt16( nGlobalWidth ); + aMemStm.ReadUInt16( nGlobalHeight ); + aMemStm.ReadUChar( nRF ); + aMemStm.ReadUChar( nBackgroundColor ); + aMemStm.ReadUChar( nAspect ); + + bGlobalPalette = ( nRF & 0x80 ); + + if( bGlobalPalette ) + ReadPaletteEntries( &aGPalette, sal_uLong(1) << ( ( nRF & 7 ) + 1 ) ); + else + nBackgroundColor = 0; + + if( NO_PENDING( rIStm ) ) + bRet = true; + } + } + else + bStatus = false; + } + + return bRet; +} + +void GIFReader::ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount ) +{ + sal_uLong nLen = 3 * nCount; + const sal_uInt64 nMaxPossible = rIStm.remainingSize(); + if (nLen > nMaxPossible) + nLen = nMaxPossible; + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nLen ]); + std::size_t nRead = rIStm.ReadBytes(pBuf.get(), nLen); + nCount = nRead/3UL; + if( !(NO_PENDING( rIStm )) ) + return; + + sal_uInt8* pTmp = pBuf.get(); + + for (sal_uLong i = 0; i < nCount; ++i) + { + BitmapColor& rColor = (*pPal)[i]; + + rColor.SetRed( *pTmp++ ); + rColor.SetGreen( *pTmp++ ); + rColor.SetBlue( *pTmp++ ); + } + + // if possible accommodate some standard colours + if( nCount < 256 ) + { + (*pPal)[ 255UL ] = COL_WHITE; + + if( nCount < 255 ) + (*pPal)[ 254UL ] = COL_BLACK; + } +} + +bool GIFReader::ReadExtension() +{ + bool bRet = false; + + // Extension-Label + sal_uInt8 cFunction(0); + rIStm.ReadUChar( cFunction ); + if( NO_PENDING( rIStm ) ) + { + bool bOverreadDataBlocks = false; + sal_uInt8 cSize(0); + // Block length + rIStm.ReadUChar( cSize ); + switch( cFunction ) + { + // 'Graphic Control Extension' + case 0xf9 : + { + sal_uInt8 cFlags(0); + rIStm.ReadUChar(cFlags); + rIStm.ReadUInt16(nTimer); + rIStm.ReadUChar(nGCTransparentIndex); + sal_uInt8 cByte(0); + rIStm.ReadUChar(cByte); + + if ( NO_PENDING( rIStm ) ) + { + nGCDisposalMethod = ( cFlags >> 2) & 7; + bGCTransparent = ( cFlags & 1 ); + bStatus = ( cSize == 4 ) && ( cByte == 0 ); + bRet = true; + } + } + break; + + // Application extension + case 0xff : + { + if ( NO_PENDING( rIStm ) ) + { + // by default overread this extension + bOverreadDataBlocks = true; + + // Appl. extension has length 11 + if ( cSize == 0x0b ) + { + OString aAppId = read_uInt8s_ToOString(rIStm, 8); + OString aAppCode = read_uInt8s_ToOString(rIStm, 3); + rIStm.ReadUChar( cSize ); + + // NetScape-Extension + if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 ) + { + sal_uInt8 cByte(0); + rIStm.ReadUChar( cByte ); + + // Loop-Extension + if ( cByte == 0x01 ) + { + rIStm.ReadUChar( cByte ); + nLoops = cByte; + rIStm.ReadUChar( cByte ); + nLoops |= ( static_cast<sal_uInt16>(cByte) << 8 ); + rIStm.ReadUChar( cByte ); + + bStatus = ( cByte == 0 ); + bRet = NO_PENDING( rIStm ); + bOverreadDataBlocks = false; + + // Netscape interprets the loop count + // as pure number of _repeats_; + // here it is the total number of loops + if( nLoops ) + nLoops++; + } + else + rIStm.SeekRel( -1 ); + } + else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 ) + { + sal_uInt8 cByte(0); + rIStm.ReadUChar( cByte ); + + // Loop extension + if ( cByte == 0x01 ) + { + rIStm.ReadUInt32( nLogWidth100 ).ReadUInt32( nLogHeight100 ); + rIStm.ReadUChar( cByte ); + bStatus = ( cByte == 0 ); + bRet = NO_PENDING( rIStm ); + bOverreadDataBlocks = false; + } + else + rIStm.SeekRel( -1 ); + } + + } + } + } + break; + + // overread everything else + default: + bOverreadDataBlocks = true; + break; + } + + // overread sub-blocks + if ( bOverreadDataBlocks ) + { + bRet = true; + while( cSize && bStatus && !rIStm.eof() ) + { + sal_uInt16 nCount = static_cast<sal_uInt16>(cSize) + 1; + const sal_uInt64 nMaxPossible = rIStm.remainingSize(); + if (nCount > nMaxPossible) + nCount = nMaxPossible; + + if (nCount) + rIStm.SeekRel( nCount - 1 ); // Skip subblock data + + bRet = false; + std::size_t nRead = rIStm.ReadBytes(&cSize, 1); + if (NO_PENDING(rIStm) && nRead == 1) + { + bRet = true; + } + else + cSize = 0; + } + } + } + + return bRet; +} + +bool GIFReader::ReadLocalHeader() +{ + sal_uInt8 pBuf[ 9 ]; + bool bRet = false; + + std::size_t nRead = rIStm.ReadBytes(pBuf, 9); + if (NO_PENDING(rIStm) && nRead == 9) + { + SvMemoryStream aMemStm; + BitmapPalette* pPal; + + aMemStm.SetBuffer( pBuf, 9, 9 ); + aMemStm.ReadUInt16( nImagePosX ); + aMemStm.ReadUInt16( nImagePosY ); + aMemStm.ReadUInt16( nImageWidth ); + aMemStm.ReadUInt16( nImageHeight ); + sal_uInt8 nFlags(0); + aMemStm.ReadUChar(nFlags); + + // if interlaced, first define startvalue + bInterlaced = ( ( nFlags & 0x40 ) == 0x40 ); + nLastInterCount = 7; + nLastImageY = 0; + + if( nFlags & 0x80 ) + { + pPal = &aLPalette; + ReadPaletteEntries( pPal, sal_uLong(1) << ( (nFlags & 7 ) + 1 ) ); + } + else + pPal = &aGPalette; + + // if we could read everything, we will create the local image; + // if the global colour table is valid for the image, we will + // consider the BackGroundColorIndex. + if( NO_PENDING( rIStm ) ) + { + CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) ); + bRet = true; + } + } + + return bRet; +} + +sal_uLong GIFReader::ReadNextBlock() +{ + sal_uLong nRet = 0; + sal_uInt8 cBlockSize; + + rIStm.ReadUChar( cBlockSize ); + + if ( rIStm.eof() ) + nRet = 4; + else if ( NO_PENDING( rIStm ) ) + { + if ( cBlockSize == 0 ) + nRet = 2; + else + { + rIStm.ReadBytes( aSrcBuf.data(), cBlockSize ); + + if( NO_PENDING( rIStm ) ) + { + if( bOverreadBlock ) + nRet = 3; + else + { + bool bEOI; + sal_uLong nRead; + sal_uInt8* pTarget = pDecomp->DecompressBlock( aSrcBuf.data(), cBlockSize, nRead, bEOI ); + + nRet = ( bEOI ? 3 : 1 ); + + if( nRead && !bOverreadBlock ) + FillImages( pTarget, nRead ); + + std::free( pTarget ); + } + } + } + } + + return nRet; +} + +void GIFReader::FillImages( const sal_uInt8* pBytes, sal_uLong nCount ) +{ + for( sal_uLong i = 0; i < nCount; i++ ) + { + if( nImageX >= nImageWidth ) + { + if( bInterlaced ) + { + tools::Long nT1; + + // lines will be copied if interlaced + if( nLastInterCount ) + { + tools::Long nMinY = std::min( static_cast<tools::Long>(nLastImageY) + 1, static_cast<tools::Long>(nImageHeight) - 1 ); + tools::Long nMaxY = std::min( static_cast<tools::Long>(nLastImageY) + nLastInterCount, static_cast<tools::Long>(nImageHeight) - 1 ); + + // copy last line read, if lines do not coincide + // ( happens at the end of the image ) + if( ( nMinY > nLastImageY ) && ( nLastImageY < ( nImageHeight - 1 ) ) ) + { + sal_uInt8* pScanline8 = pAcc8->GetScanline( nYAcc ); + sal_uInt32 nSize8 = pAcc8->GetScanlineSize(); + sal_uInt8* pScanline1 = nullptr; + sal_uInt32 nSize1 = 0; + + if( bGCTransparent ) + { + pScanline1 = pAcc1->GetScanline( nYAcc ); + nSize1 = pAcc1->GetScanlineSize(); + } + + for( tools::Long j = nMinY; j <= nMaxY; j++ ) + { + memcpy( pAcc8->GetScanline( j ), pScanline8, nSize8 ); + + if( bGCTransparent ) + memcpy( pAcc1->GetScanline( j ), pScanline1, nSize1 ); + } + } + } + + nT1 = ( ++nImageY ) << 3; + nLastInterCount = 7; + + if( nT1 >= nImageHeight ) + { + tools::Long nT2 = nImageY - ( ( nImageHeight + 7 ) >> 3 ); + nT1 = ( nT2 << 3 ) + 4; + nLastInterCount = 3; + + if( nT1 >= nImageHeight ) + { + nT2 -= ( nImageHeight + 3 ) >> 3; + nT1 = ( nT2 << 2 ) + 2; + nLastInterCount = 1; + + if( nT1 >= nImageHeight ) + { + nT2 -= ( nImageHeight + 1 ) >> 2; + nT1 = ( nT2 << 1 ) + 1; + nLastInterCount = 0; + } + } + } + + nLastImageY = static_cast<sal_uInt16>(nT1); + nYAcc = nT1; + } + else + { + nLastImageY = ++nImageY; + nYAcc = nImageY; + } + + // line starts from the beginning + nImageX = 0; + } + + if( nImageY < nImageHeight ) + { + const sal_uInt8 cTmp = pBytes[ i ]; + + if( bGCTransparent ) + { + if( cTmp == nGCTransparentIndex ) + pAcc1->SetPixelIndex( nYAcc, nImageX++, cTransIndex1 ); + else + { + pAcc8->SetPixelIndex( nYAcc, nImageX, cTmp ); + pAcc1->SetPixelIndex( nYAcc, nImageX++, cNonTransIndex1 ); + } + } + else + pAcc8->SetPixelIndex( nYAcc, nImageX++, cTmp ); + } + else + { + bOverreadBlock = true; + break; + } + } +} + +void GIFReader::CreateNewBitmaps() +{ + AnimationBitmap aAnimationBitmap; + + pAcc8.reset(); + + if( bGCTransparent ) + { + pAcc1.reset(); + aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8, aBmp1 ); + } + else + aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8 ); + + aAnimationBitmap.maPositionPixel = Point( nImagePosX, nImagePosY ); + aAnimationBitmap.maSizePixel = Size( nImageWidth, nImageHeight ); + aAnimationBitmap.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK; + aAnimationBitmap.mbUserInput = false; + + // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed. + // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same + if (aAnimationBitmap.mnWait < 2) // 20ms, specified in 100's of a second + aAnimationBitmap.mnWait = 10; + + if( nGCDisposalMethod == 2 ) + aAnimationBitmap.meDisposal = Disposal::Back; + else if( nGCDisposalMethod == 3 ) + aAnimationBitmap.meDisposal = Disposal::Previous; + else + aAnimationBitmap.meDisposal = Disposal::Not; + + nAnimationByteSize += aAnimationBitmap.maBitmapEx.GetSizeBytes(); + nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560; + aAnimation.Insert(aAnimationBitmap); + + if( aAnimation.Count() == 1 ) + { + aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) ); + aAnimation.SetLoopCount( nLoops ); + } +} + +Graphic GIFReader::GetIntermediateGraphic() +{ + Graphic aImGraphic; + + // only create intermediate graphic, if data is available + // but graphic still not completely read + if ( bImGraphicReady && !aAnimation.Count() ) + { + pAcc8.reset(); + + if ( bGCTransparent ) + { + pAcc1.reset(); + aImGraphic = BitmapEx( aBmp8, aBmp1 ); + + pAcc1 = BitmapScopedWriteAccess(aBmp1); + bStatus = bStatus && pAcc1; + } + else + aImGraphic = BitmapEx(aBmp8); + + pAcc8 = BitmapScopedWriteAccess(aBmp8); + bStatus = bStatus && pAcc8; + } + + return aImGraphic; +} + +bool GIFReader::ProcessGIF() +{ + bool bRead = false; + bool bEnd = false; + + if ( !bStatus ) + eActAction = ABORT_READING; + + // set stream to right position + rIStm.Seek( nLastPos ); + + switch( eActAction ) + { + // read next marker + case MARKER_READING: + { + sal_uInt8 cByte; + + rIStm.ReadUChar( cByte ); + + if( rIStm.eof() ) + eActAction = END_READING; + else if( NO_PENDING( rIStm ) ) + { + bRead = true; + + if( cByte == '!' ) + eActAction = EXTENSION_READING; + else if( cByte == ',' ) + eActAction = LOCAL_HEADER_READING; + else if( cByte == ';' ) + eActAction = END_READING; + else + eActAction = ABORT_READING; + } + } + break; + + // read ScreenDescriptor + case GLOBAL_HEADER_READING: + { + bRead = ReadGlobalHeader(); + if( bRead ) + { + ClearImageExtensions(); + eActAction = MARKER_READING; + } + } + break; + + // read extension + case EXTENSION_READING: + { + bRead = ReadExtension(); + if( bRead ) + eActAction = MARKER_READING; + } + break; + + // read Image-Descriptor + case LOCAL_HEADER_READING: + { + bRead = ReadLocalHeader(); + if( bRead ) + { + nYAcc = nImageX = nImageY = 0; + eActAction = FIRST_BLOCK_READING; + } + } + break; + + // read first data block + case FIRST_BLOCK_READING: + { + sal_uInt8 cDataSize; + + rIStm.ReadUChar( cDataSize ); + + if( rIStm.eof() ) + eActAction = ABORT_READING; + else if( cDataSize > 12 ) + bStatus = false; + else if( NO_PENDING( rIStm ) ) + { + bRead = true; + pDecomp = std::make_unique<GIFLZWDecompressor>( cDataSize ); + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = false; + } + else + eActAction = FIRST_BLOCK_READING; + } + break; + + // read next data block + case NEXT_BLOCK_READING: + { + sal_uInt16 nLastX = nImageX; + sal_uInt16 nLastY = nImageY; + sal_uLong nRet = ReadNextBlock(); + + // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort + if( nRet ) + { + bRead = true; + + if ( nRet == 1 ) + { + bImGraphicReady = true; + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = false; + } + else + { + if( nRet == 2 ) + { + pDecomp.reset(); + CreateNewBitmaps(); + eActAction = MARKER_READING; + ClearImageExtensions(); + } + else if( nRet == 3 ) + { + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = true; + } + else + { + pDecomp.reset(); + CreateNewBitmaps(); + eActAction = ABORT_READING; + ClearImageExtensions(); + } + } + } + else + { + nImageX = nLastX; + nImageY = nLastY; + } + } + break; + + // an error occurred + case ABORT_READING: + { + bEnd = true; + eActAction = END_READING; + } + break; + + default: + break; + } + + // set stream to right position, + // if data could be read put it at the old + // position otherwise at the actual one + if( bRead || bEnd ) + nLastPos = rIStm.Tell(); + + return bRead; +} + +bool GIFReader::ReadIsAnimated() +{ + ReadState eReadState; + + bStatus = true; + + while( ProcessGIF() && ( eActAction != END_READING ) ) {} + + if( !bStatus ) + eReadState = GIFREAD_ERROR; + else if( eActAction == END_READING ) + eReadState = GIFREAD_OK; + else + { + if ( rIStm.GetError() == ERRCODE_IO_PENDING ) + rIStm.ResetError(); + + eReadState = GIFREAD_NEED_MORE; + } + + if (eReadState == GIFREAD_OK) + return aAnimation.Count() > 1; + return false; +} + +void GIFReader::GetLogicSize(Size& rLogicSize) +{ + rLogicSize.setWidth(nLogWidth100); + rLogicSize.setHeight(nLogHeight100); +} + +ReadState GIFReader::ReadGIF( Graphic& rGraphic ) +{ + ReadState eReadState; + + bStatus = true; + + while( ProcessGIF() && ( eActAction != END_READING ) ) {} + + if( !bStatus ) + eReadState = GIFREAD_ERROR; + else if( eActAction == END_READING ) + eReadState = GIFREAD_OK; + else + { + if ( rIStm.GetError() == ERRCODE_IO_PENDING ) + rIStm.ResetError(); + + eReadState = GIFREAD_NEED_MORE; + } + + if( aAnimation.Count() == 1 ) + { + rGraphic = aAnimation.Get(0).maBitmapEx; + + if( nLogWidth100 && nLogHeight100 ) + { + rGraphic.SetPrefSize( Size( nLogWidth100, nLogHeight100 ) ); + rGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + } + } + else + rGraphic = aAnimation; + + return eReadState; +} + +bool IsGIFAnimated(SvStream & rStm, Size& rLogicSize) +{ + GIFReader aReader(rStm); + + SvStreamEndian nOldFormat = rStm.GetEndian(); + rStm.SetEndian(SvStreamEndian::LITTLE); + bool bResult = aReader.ReadIsAnimated(); + aReader.GetLogicSize(rLogicSize); + rStm.SetEndian(nOldFormat); + + return bResult; +} + +VCL_DLLPUBLIC bool ImportGIF( SvStream & rStm, Graphic& rGraphic ) +{ + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + GIFReader* pGIFReader = dynamic_cast<GIFReader*>( pContext.get() ); + if (!pGIFReader) + { + pContext = std::make_shared<GIFReader>( rStm ); + pGIFReader = static_cast<GIFReader*>( pContext.get() ); + } + + SvStreamEndian nOldFormat = rStm.GetEndian(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + + bool bRet = true; + + ReadState eReadState = pGIFReader->ReadGIF(rGraphic); + + if (eReadState == GIFREAD_ERROR) + { + bRet = false; + } + else if (eReadState == GIFREAD_NEED_MORE) + { + rGraphic = pGIFReader->GetIntermediateGraphic(); + rGraphic.SetReaderContext(pContext); + } + + rStm.SetEndian(nOldFormat); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/igif/gifread.hxx b/vcl/source/filter/igif/gifread.hxx new file mode 100644 index 000000000..de9a506f2 --- /dev/null +++ b/vcl/source/filter/igif/gifread.hxx @@ -0,0 +1,30 @@ +/* -*- 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_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX +#define INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX + +#include <vcl/graph.hxx> + +VCL_DLLPUBLIC bool ImportGIF(SvStream& rStream, Graphic& rGraphic); +bool IsGIFAnimated(SvStream& rStream, Size& rLogicSize); + +#endif // INCLUDED_VCL_SOURCE_FILTER_IGIF_GIFREAD_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/imet/ios2met.cxx b/vcl/source/filter/imet/ios2met.cxx new file mode 100644 index 000000000..fe856c1d7 --- /dev/null +++ b/vcl/source/filter/imet/ios2met.cxx @@ -0,0 +1,2883 @@ +/* -*- 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 <osl/thread.h> +#include <o3tl/safeint.hxx> +#include <tools/poly.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <sal/log.hxx> +#include <vcl/graph.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/gdimtf.hxx> +#include <filter/MetReader.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <cmath> +#include <memory> + +class FilterConfigItem; + +namespace { + +enum PenStyle { PEN_NULL, PEN_SOLID, PEN_DOT, PEN_DASH, PEN_DASHDOT }; + +} + +// -----------------------------Field Types------------------------------- + +#define BegDocumnMagic 0xA8A8 /* Begin Document */ +#define EndDocumnMagic 0xA8A9 /* End Document */ + +#define BegResGrpMagic 0xC6A8 /* Begin Resource Group */ +#define EndResGrpMagic 0xC6A9 /* End Resource Group */ + +#define BegColAtrMagic 0x77A8 /* Begin Color Attribute Table */ +#define EndColAtrMagic 0x77A9 /* End Color Attribute Table */ +#define BlkColAtrMagic 0x77B0 /* Color Attribute Table */ +#define MapColAtrMagic 0x77AB /* Map Color Attribute Table */ + +#define BegImgObjMagic 0xFBA8 /* Begin Image Object */ +#define EndImgObjMagic 0xFBA9 /* End Image Object */ +#define DscImgObjMagic 0xFBA6 /* Image Data Descriptor */ +#define DatImgObjMagic 0xFBEE /* Image Picture Data */ + +#define BegObEnv1Magic 0xC7A8 /* Begin Object Environment Group */ +#define EndObEnv1Magic 0xC7A9 /* End Object Environment Group */ + +#define BegGrfObjMagic 0xBBA8 /* Begin Graphics Object */ +#define EndGrfObjMagic 0xBBA9 /* End Graphics Object */ +#define DscGrfObjMagic 0xBBA6 /* Graphics Data Descriptor */ +#define DatGrfObjMagic 0xBBEE /* Graphics Data */ + +#define MapCodFntMagic 0x8AAB /* Map Coded Font */ +#define MapDatResMagic 0xC3AB /* Map Data Resource */ + +// -----------------------------Order Types------------------------------- + +#define GOrdGivArc 0xC6 /* 1 Arc at given position */ +#define GOrdCurArc 0x86 /* 1 Arc at current position */ +#define GOrdGivBzr 0xE5 /* 1 Beziercurve at given position */ +#define GOrdCurBzr 0xA5 /* 1 Beziercurve at current position */ +#define GOrdGivBox 0xC0 /* 1 Box at given position */ +#define GOrdCurBox 0x80 /* 1 Box at current position */ +#define GOrdGivFil 0xC5 /* 1 Fillet at given position */ +#define GOrdCurFil 0x85 /* 1 Fillet at current position */ +#define GOrdGivCrc 0xC7 /* 1 Full arc (circle) at given position */ +#define GOrdCurCrc 0x87 /* 1 Full arc (circle) at current position */ +#define GOrdGivLin 0xC1 /* 1 Line at given position */ +#define GOrdCurLin 0x81 /* 1 Line at current position */ +#define GOrdGivMrk 0xC2 /* 1 Marker at given position */ +#define GOrdCurMrk 0x82 /* 1 Marker at current position */ +#define GOrdGivArP 0xE3 /* 1 Partial arc at given position */ +#define GOrdCurArP 0xA3 /* 1 Partial arc at current position */ +#define GOrdGivRLn 0xE1 /* 1 Relative line at given position */ +#define GOrdCurRLn 0xA1 /* 1 Relative line at current position */ +#define GOrdGivSFl 0xE4 /* 1 Sharp fillet at given position */ +#define GOrdCurSFl 0xA4 /* 1 Sharp fillet at current position */ + +#define GOrdGivStM 0xF1 /* 1 Character string move at given position */ +#define GOrdCurStM 0xB1 /* 1 Character string move at current position */ +#define GOrdGivStr 0xC3 /* 1 Character string at given position */ +#define GOrdCurStr 0x83 /* 1 Character string at current position */ +#define GOrdGivStx 0xFEF0 /* 2 Character string extended at given position */ +#define GOrdCurStx 0xFEB0 /* 2 Character string extended at current position */ + +#define GOrdGivImg 0xD1 /* 1 Begin Image at given position */ +#define GOrdCurImg 0x91 /* 1 Begin Image at current position */ +#define GOrdImgDat 0x92 /* 1 Image data */ +#define GOrdEndImg 0x93 /* 1 End Image */ +#define GOrdBegAra 0x68 /* 0 1 Begin area */ +#define GOrdEndAra 0x60 /* 1 End area */ +#define GOrdBegElm 0xD2 /* 1 Begin element */ +#define GOrdEndElm 0x49 /* 0 1 End element */ + +#define GOrdBegPth 0xD0 /* 1 Begin path */ +#define GOrdEndPth 0x7F /* 0 1 End path */ +#define GOrdFilPth 0xD7 /* 1 Fill path */ +#define GOrdModPth 0xD8 /* 1 Modify path */ +#define GOrdOutPth 0xD4 /* 1 Outline path */ +#define GOrdSClPth 0xB4 /* 1 Set clip path */ + +#define GOrdNopNop 0x00 /* 0 0 No operation */ +#define GOrdRemark 0x01 /* 1 Comment */ +#define GOrdSegLab 0xD3 /* 1 Label */ +#define GOrdBitBlt 0xD6 /* 1 Bitblt */ +#define GOrdCalSeg 0x07 /* 1 Call Segment */ +#define GOrdSSgBnd 0x32 /* 1 Set segment boundary */ +#define GOrdSegChr 0x04 /* 1 Segment characteristics */ +#define GOrdCloFig 0x7D /* 0 1 Close Figure */ +#define GOrdEndSym 0xFF /* 0 0 End of symbol definition */ +#define GOrdEndPlg 0x3E /* 0 1 End prolog */ +#define GOrdEscape 0xD5 /* 1 Escape */ +#define GOrdExtEsc 0xFED5 /* 2 Extended Escape */ +#define GOrdPolygn 0xF3 /* 2 Polygons */ + +#define GOrdStkPop 0x3F /* 0 1 Pop */ + +#define GOrdSIvAtr 0x14 /* 1 Set individual attribute */ +#define GOrdPIvAtr 0x54 /* 1 Push and set individual attribute */ +#define GOrdSColor 0x0A /* 0 1 Set color */ +#define GOrdPColor 0x4A /* 0 1 Push and set color */ +#define GOrdSIxCol 0xA6 /* 1 Set indexed color */ +#define GOrdPIxCol 0xE6 /* 1 Push and set indexed color */ +#define GOrdSXtCol 0x26 /* 1 Set extended color */ +#define GOrdPXtCol 0x66 /* 1 Push and set extended color */ +#define GOrdSBgCol 0x25 /* 1 Set background color */ +#define GOrdPBgCol 0x65 /* 1 Push and set background color */ +#define GOrdSBxCol 0xA7 /* 1 Set background indexed color */ +#define GOrdPBxCol 0xE7 /* 1 Push and set background indexed color */ +#define GOrdSMixMd 0x0C /* 0 1 Set mix */ +#define GOrdPMixMd 0x4C /* 0 1 Push and set mix */ +#define GOrdSBgMix 0x0D /* 0 1 Set background mix */ +#define GOrdPBgMix 0x4D /* 0 1 Push and set background mix */ + +#define GOrdSPtSet 0x08 /* 0 1 Set pattern set */ +#define GOrdPPtSet 0x48 /* 0 1 Push and set pattern set */ +#define GOrdSPtSym 0x28 /* 0 1 Set pattern symbol */ +#define GOrdPPtSym 0x09 /* 0 1 Push and set pattern symbol */ +#define GOrdSPtRef 0xA0 /* 1 Set model pattern reference */ +#define GOrdPPtRef 0xE0 /* 1 Push and set pattern reference point */ + +#define GOrdSLnEnd 0x1A /* 0 1 Set line end */ +#define GOrdPLnEnd 0x5A /* 0 1 Push and set line end */ +#define GOrdSLnJoi 0x1B /* 0 1 Set line join */ +#define GOrdPLnJoi 0x5B /* 0 1 Push and set line join */ +#define GOrdSLnTyp 0x18 /* 0 1 Set line type */ +#define GOrdPLnTyp 0x58 /* 0 1 Push and set line type */ +#define GOrdSLnWdt 0x19 /* 0 1 Set line width */ +#define GOrdPLnWdt 0x59 /* 0 1 Push and set line width */ +#define GOrdSFrLWd 0x11 /* 1 Set fractional line width */ +#define GOrdPFrLWd 0x51 /* 1 Push and set fractional line width */ +#define GOrdSStLWd 0x15 /* 1 Set stroke line width */ +#define GOrdPStLWd 0x55 /* 1 Push and set stroke line width */ + +#define GOrdSChDir 0x3A /* 0 1 Set character direction */ +#define GOrdPChDir 0x7A /* 0 1 Push and set character direction */ +#define GOrdSChPrc 0x39 /* 0 1 Set character precision */ +#define GOrdPChPrc 0x79 /* 0 1 Push and set character precision */ +#define GOrdSChSet 0x38 /* 0 1 Set character set */ +#define GOrdPChSet 0x78 /* 0 1 Push and set character set */ +#define GOrdSChAng 0x34 /* 1 Set character angle */ +#define GOrdPChAng 0x74 /* 1 Push and set character angle */ +#define GOrdSChBrx 0x05 /* 1 Set character break extra */ +#define GOrdPChBrx 0x45 /* 1 Push and set character break extra */ +#define GOrdSChCel 0x33 /* 1 Set character cell */ +#define GOrdPChCel 0x03 /* 1 Push and set character cell */ +#define GOrdSChXtr 0x17 /* 1 Set character extra */ +#define GOrdPChXtr 0x57 /* 1 Push and set character extra */ +#define GOrdSChShr 0x35 /* 1 Set character shear */ +#define GOrdPChShr 0x75 /* 1 Push and set character shear */ +#define GOrdSTxAlg 0x36 /* 0 2 Set text allingment */ +#define GOrdPTxAlg 0x76 /* 0 2 Push and set text allingment */ + +#define GOrdSMkPrc 0x3B /* 0 1 Set marker precision */ +#define GOrdPMkPrc 0x7B /* 0 1 Push and set marker precision */ +#define GOrdSMkSet 0x3C /* 0 1 Set marker set */ +#define GOrdPMkSet 0x7C /* 0 1 Push and set marker set */ +#define GOrdSMkSym 0x29 /* 0 1 Set marker symbol */ +#define GOrdPMkSym 0x69 /* 0 1 Push and set marker symbol */ +#define GOrdSMkCel 0x37 /* 1 Set marker cell */ +#define GOrdPMkCel 0x77 /* 1 Push and set marker cell */ + +#define GOrdSArcPa 0x22 /* 1 Set arc parameters */ +#define GOrdPArcPa 0x62 /* 1 Push and set arc parameters */ + +#define GOrdSCrPos 0x21 /* 1 Set current position */ +#define GOrdPCrPos 0x61 /* 1 Push and set current position */ + +#define GOrdSMdTrn 0x24 /* 1 Set model transform */ +#define GOrdPMdTrn 0x64 /* 1 Push and set model transform */ +#define GOrdSPkIdn 0x43 /* 1 Set pick identifier */ +#define GOrdPPkIdn 0x23 /* 1 Push and set pick identifier */ +#define GOrdSVwTrn 0x31 /* 1 Set viewing transform */ +#define GOrdSVwWin 0x27 /* 1 Set viewing window */ +#define GOrdPVwWin 0x67 /* 1 Push and set viewing window */ + +//============================ OS2METReader ================================== + +namespace { + +struct OSPalette { + OSPalette * pSucc; + sal_uInt32 * p0RGB; // May be NULL! + size_t nSize; +}; + +struct OSArea { + OSArea * pSucc; + sal_uInt8 nFlags; + tools::PolyPolygon aPPoly; + bool bClosed; + Color aCol; + Color aBgCol; + RasterOp eMix; + RasterOp eBgMix; + bool bFill; + + OSArea() + : pSucc(nullptr) + , nFlags(0) + , bClosed(false) + , eMix(RasterOp::OverPaint) + , eBgMix(RasterOp::OverPaint) + , bFill(false) + { + } +}; + +struct OSPath +{ + OSPath* pSucc; + sal_uInt32 nID; + tools::PolyPolygon aPPoly; + bool bClosed; + bool bStroke; + + OSPath() + : pSucc(nullptr) + , nID(0) + , bClosed(false) + , bStroke(false) + { + } +}; + +struct OSFont { + OSFont * pSucc; + sal_uInt32 nID; + vcl::Font aFont; + + OSFont() + : pSucc(nullptr) + , nID(0) + { + } +}; + +struct OSBitmap { + OSBitmap * pSucc; + sal_uInt32 nID; + BitmapEx aBitmapEx; + + // required during reading of the bitmap: + SvStream * pBMP; // pointer to temporary Windows-BMP file or NULL + sal_uInt32 nWidth, nHeight; + sal_uInt16 nBitsPerPixel; + sal_uInt32 nMapPos; +}; + +struct OSAttr +{ + OSAttr * pSucc; + sal_uInt16 nPushOrder; + sal_uInt8 nIvAttrA, nIvAttrP; // special variables for the Order "GOrdPIvAtr" + + Color aLinCol; + Color aLinBgCol; + RasterOp eLinMix; + RasterOp eLinBgMix; + Color aChrCol; + Color aChrBgCol; + RasterOp eChrMix; + RasterOp eChrBgMix; + Color aMrkCol; + Color aMrkBgCol; + RasterOp eMrkMix; + RasterOp eMrkBgMix; + Color aPatCol; + Color aPatBgCol; + RasterOp ePatMix; + RasterOp ePatBgMix; + Color aImgCol; + Color aImgBgCol; + RasterOp eImgMix; + RasterOp eImgBgMix; + sal_Int32 nArcP, nArcQ, nArcR, nArcS; + Degree10 nChrAng; + sal_Int32 nChrCellHeight; + sal_uInt32 nChrSet; + Point aCurPos; + PenStyle eLinStyle; + sal_uInt16 nLinWidth; + Size aMrkCellSize; + sal_uInt8 nMrkPrec; + sal_uInt8 nMrkSet; + sal_uInt8 nMrkSymbol; + bool bFill; + sal_uInt16 nStrLinWidth; + + OSAttr() + : pSucc(nullptr) + , nPushOrder(0) + , nIvAttrA(0) + , nIvAttrP(0) + , eLinMix(RasterOp::OverPaint) + , eLinBgMix(RasterOp::OverPaint) + , eChrMix(RasterOp::OverPaint) + , eChrBgMix(RasterOp::OverPaint) + , eMrkMix(RasterOp::OverPaint) + , eMrkBgMix(RasterOp::OverPaint) + , ePatMix(RasterOp::OverPaint) + , ePatBgMix(RasterOp::OverPaint) + , eImgMix(RasterOp::OverPaint) + , eImgBgMix(RasterOp::OverPaint) + , nArcP(0) + , nArcQ(0) + , nArcR(0) + , nArcS(0) + , nChrAng(0) + , nChrCellHeight(0) + , nChrSet(0) + , eLinStyle(PEN_NULL) + , nLinWidth(0) + , nMrkPrec(0) + , nMrkSet(0) + , nMrkSymbol(0) + , bFill(false) + , nStrLinWidth(0) + { + } +}; + +class OS2METReader { + +private: + + int ErrorCode; + + SvStream * pOS2MET; // the OS2MET file to be read + VclPtr<VirtualDevice> pVirDev; // here the drawing methods are being called + // While doing this a recording in the GDIMetaFile + // will take place. + tools::Rectangle aBoundingRect; // bounding rectangle as stored in the file + tools::Rectangle aCalcBndRect; // bounding rectangle calculated on our own + MapMode aGlobMapMode; // resolution of the picture + bool bCoord32; + + OSPalette * pPaletteStack; + + LineInfo aLineInfo; + + OSArea * pAreaStack; // Areas that are being worked on + + OSPath * pPathStack; // Paths that are being worked on + OSPath * pPathList; // finished Paths + + OSFont * pFontList; + + OSBitmap * pBitmapList; + + OSAttr aDefAttr; + OSAttr aAttr; + OSAttr * pAttrStack; + + std::unique_ptr<SvMemoryStream> xOrdFile; + + void AddPointsToPath(const tools::Polygon & rPoly); + void AddPointsToArea(const tools::Polygon & rPoly); + void CloseFigure(); + void PushAttr(sal_uInt16 nPushOrder); + void PopAttr(); + + void ChangeBrush( const Color& rPatColor, bool bFill ); + void SetPen( const Color& rColor, sal_uInt16 nStrLinWidth = 0, PenStyle ePenStyle = PEN_SOLID ); + void SetRasterOp(RasterOp eROP); + + void SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol); + sal_uInt32 GetPalette0RGB(sal_uInt32 nIndex) const; + // gets color from palette, or, if it doesn't exist, + // interprets nIndex as immediate RGB value. + Color GetPaletteColor(sal_uInt32 nIndex) const; + + + bool IsLineInfo() const; + void DrawPolyLine( const tools::Polygon& rPolygon ); + void DrawPolygon( const tools::Polygon& rPolygon ); + void DrawPolyPolygon( const tools::PolyPolygon& rPolygon ); + sal_uInt16 ReadBigEndianWord(); + sal_uInt32 ReadBigEndian3BytesLong(); + sal_uInt32 ReadLittleEndian3BytesLong(); + sal_Int32 ReadCoord(bool b32); + Point ReadPoint( const bool bAdjustBoundRect = true ); + static RasterOp OS2MixToRasterOp(sal_uInt8 nMix); + void ReadLine(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadBox(bool bGivenPos); + void ReadBitBlt(); + void ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen); + void ReadArc(bool bGivenPos); + void ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize); + void ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize); + void ReadPolygons(); + void ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen); + void ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen); + void ReadDsc(sal_uInt16 nDscID); + void ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen); + void ReadFont(sal_uInt16 nFieldSize); + void ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize); + +public: + + OS2METReader(); + ~OS2METReader(); + + void ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile ); + // Reads from the stream an OS2MET file and fills up the GDIMetaFile + +}; + +} + +//=================== Methods of OS2METReader ============================== + +OS2METReader::OS2METReader() + : ErrorCode(0) + , pOS2MET(nullptr) + , pVirDev(VclPtr<VirtualDevice>::Create()) + , bCoord32(false) + , pPaletteStack(nullptr) + , pAreaStack(nullptr) + , pPathStack(nullptr) + , pPathList(nullptr) + , pFontList(nullptr) + , pBitmapList(nullptr) + , pAttrStack(nullptr) +{ + pVirDev->EnableOutput(false); +} + +OS2METReader::~OS2METReader() +{ + pVirDev.disposeAndClear(); + + while (pAreaStack!=nullptr) { + OSArea * p=pAreaStack; + pAreaStack=p->pSucc; + delete p; + } + + while (pPathStack!=nullptr) { + OSPath * p=pPathStack; + pPathStack=p->pSucc; + delete p; + } + + while (pPathList!=nullptr) { + OSPath * p=pPathList; + pPathList=p->pSucc; + delete p; + } + + while (pFontList!=nullptr) { + OSFont * p=pFontList; + pFontList=p->pSucc; + delete p; + } + + while (pBitmapList!=nullptr) { + OSBitmap * p=pBitmapList; + pBitmapList=p->pSucc; + delete p->pBMP; + delete p; + } + + while (pAttrStack!=nullptr) { + OSAttr * p=pAttrStack; + pAttrStack=p->pSucc; + delete p; + } + + while (pPaletteStack!=nullptr) { + OSPalette * p=pPaletteStack; + pPaletteStack=p->pSucc; + delete[] p->p0RGB; + delete p; + } +} + +bool OS2METReader::IsLineInfo() const +{ + return ( ! ( aLineInfo.IsDefault() || ( aLineInfo.GetStyle() == LineStyle::NONE ) || ( pVirDev->GetLineColor() == COL_TRANSPARENT ) ) ); +} + +void OS2METReader::DrawPolyLine( const tools::Polygon& rPolygon ) +{ + if ( aLineInfo.GetStyle() == LineStyle::Dash || ( aLineInfo.GetWidth() > 1 ) ) + pVirDev->DrawPolyLine( rPolygon, aLineInfo ); + else + pVirDev->DrawPolyLine( rPolygon ); +} + +void OS2METReader::DrawPolygon( const tools::Polygon& rPolygon ) +{ + if ( IsLineInfo() ) + { + pVirDev->Push( vcl::PushFlags::LINECOLOR ); + pVirDev->SetLineColor( COL_TRANSPARENT ); + pVirDev->DrawPolygon( rPolygon ); + pVirDev->Pop(); + pVirDev->DrawPolyLine( rPolygon, aLineInfo ); + } + else + pVirDev->DrawPolygon( rPolygon ); +} + +void OS2METReader::DrawPolyPolygon( const tools::PolyPolygon& rPolyPolygon ) +{ + if ( IsLineInfo() ) + { + pVirDev->Push( vcl::PushFlags::LINECOLOR ); + pVirDev->SetLineColor( COL_TRANSPARENT ); + pVirDev->DrawPolyPolygon( rPolyPolygon ); + pVirDev->Pop(); + for ( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i++ ) + pVirDev->DrawPolyLine( rPolyPolygon.GetObject( i ), aLineInfo ); + } + else + pVirDev->DrawPolyPolygon( rPolyPolygon ); +} + +void OS2METReader::AddPointsToArea(const tools::Polygon & rPoly) +{ + sal_uInt16 nOldSize, nNewSize,i; + + if (pAreaStack==nullptr || rPoly.GetSize()==0) return; + tools::PolyPolygon * pPP=&(pAreaStack->aPPoly); + if (pPP->Count()==0 || pAreaStack->bClosed) pPP->Insert(rPoly); + else { + tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1)); + nOldSize=aLastPoly.GetSize(); + if (nOldSize && aLastPoly.GetPoint(nOldSize-1)==rPoly.GetPoint(0)) nOldSize--; + nNewSize=nOldSize+rPoly.GetSize(); + aLastPoly.SetSize(nNewSize); + for (i=nOldSize; i<nNewSize; i++) { + aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i); + } + pPP->Replace(aLastPoly,pPP->Count()-1); + } + pAreaStack->bClosed=false; +} + +void OS2METReader::AddPointsToPath(const tools::Polygon & rPoly) +{ + sal_uInt16 nOldSize, nNewSize,i; + + if (pPathStack==nullptr || rPoly.GetSize()==0) return; + tools::PolyPolygon * pPP=&(pPathStack->aPPoly); + if (pPP->Count()==0 /*|| pPathStack->bClosed==sal_True*/) pPP->Insert(rPoly); + else { + tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1)); + nOldSize=aLastPoly.GetSize(); + if (nOldSize && aLastPoly.GetPoint(nOldSize-1)!=rPoly.GetPoint(0)) pPP->Insert(rPoly); + else { + nOldSize--; + nNewSize=nOldSize+rPoly.GetSize(); + aLastPoly.SetSize(nNewSize); + for (i=nOldSize; i<nNewSize; i++) { + aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i); + } + pPP->Replace(aLastPoly,pPP->Count()-1); + } + } + pPathStack->bClosed=false; +} + +void OS2METReader::CloseFigure() +{ + if (pAreaStack!=nullptr) pAreaStack->bClosed=true; + else if (pPathStack!=nullptr) pPathStack->bClosed=true; +} + +void OS2METReader::PushAttr(sal_uInt16 nPushOrder) +{ + OSAttr * p; + p=new OSAttr; + *p=aAttr; + p->pSucc=pAttrStack; pAttrStack=p; + p->nPushOrder=nPushOrder; +} + +void OS2METReader::PopAttr() +{ + OSAttr * p=pAttrStack; + + if (p==nullptr) return; + switch (p->nPushOrder) { + + case GOrdPIvAtr: + switch (p->nIvAttrA) { + case 1: switch (p->nIvAttrP) { + case 1: aAttr.aLinCol=p->aLinCol; break; + case 2: aAttr.aChrCol=p->aChrCol; break; + case 3: aAttr.aMrkCol=p->aMrkCol; break; + case 4: aAttr.aPatCol=p->aPatCol; break; + case 5: aAttr.aImgCol=p->aImgCol; break; + } break; + case 2: switch (p->nIvAttrP) { + case 1: aAttr.aLinBgCol=p->aLinBgCol; break; + case 2: aAttr.aChrBgCol=p->aChrBgCol; break; + case 3: aAttr.aMrkBgCol=p->aMrkBgCol; break; + case 4: aAttr.aPatBgCol=p->aPatBgCol; break; + case 5: aAttr.aImgBgCol=p->aImgBgCol; break; + } break; + case 3: switch (p->nIvAttrP) { + case 1: aAttr.eLinMix=p->eLinMix; break; + case 2: aAttr.eChrMix=p->eChrMix; break; + case 3: aAttr.eMrkMix=p->eMrkMix; break; + case 4: aAttr.ePatMix=p->ePatMix; break; + case 5: aAttr.eImgMix=p->eImgMix; break; + } break; + case 4: switch (p->nIvAttrP) { + case 1: aAttr.eLinBgMix=p->eLinBgMix; break; + case 2: aAttr.eChrBgMix=p->eChrBgMix; break; + case 3: aAttr.eMrkBgMix=p->eMrkBgMix; break; + case 4: aAttr.ePatBgMix=p->ePatBgMix; break; + case 5: aAttr.eImgBgMix=p->eImgBgMix; break; + } break; + } + break; + + case GOrdPLnTyp: aAttr.eLinStyle=p->eLinStyle; break; + + case GOrdPLnWdt: aAttr.nLinWidth=p->nLinWidth; break; + + case GOrdPStLWd: aAttr.nStrLinWidth=p->nStrLinWidth; break; + + case GOrdPChSet: aAttr.nChrSet=p->nChrSet; break; + + case GOrdPChAng: aAttr.nChrAng=p->nChrAng; break; + + case GOrdPMixMd: + aAttr.eLinMix=p->eLinMix; + aAttr.eChrMix=p->eChrMix; + aAttr.eMrkMix=p->eMrkMix; + aAttr.ePatMix=p->ePatMix; + aAttr.eImgMix=p->eImgMix; + break; + + case GOrdPBgMix: + aAttr.eLinBgMix=p->eLinBgMix; + aAttr.eChrBgMix=p->eChrBgMix; + aAttr.eMrkBgMix=p->eMrkBgMix; + aAttr.ePatBgMix=p->ePatBgMix; + aAttr.eImgBgMix=p->eImgBgMix; + break; + + case GOrdPPtSym: aAttr.bFill = p->bFill; break; + + case GOrdPColor: + case GOrdPIxCol: + case GOrdPXtCol: + aAttr.aLinCol=p->aLinCol; + aAttr.aChrCol=p->aChrCol; + aAttr.aMrkCol=p->aMrkCol; + aAttr.aPatCol=p->aPatCol; + aAttr.aImgCol=p->aImgCol; + break; + + case GOrdPBgCol: + case GOrdPBxCol: + aAttr.aLinBgCol=p->aLinBgCol; + aAttr.aChrBgCol=p->aChrBgCol; + aAttr.aMrkBgCol=p->aMrkBgCol; + aAttr.aPatBgCol=p->aPatBgCol; + aAttr.aImgBgCol=p->aImgBgCol; + break; + + case GOrdPMkPrc: aAttr.nMrkPrec=aDefAttr.nMrkPrec; break; + + case GOrdPMkSet: aAttr.nMrkSet=aDefAttr.nMrkSet; break; + + case GOrdPMkSym: aAttr.nMrkSymbol=aDefAttr.nMrkSymbol; break; + + case GOrdPMkCel: aAttr.aMrkCellSize=aDefAttr.aMrkCellSize; break; + + case GOrdPArcPa: + aAttr.nArcP=p->nArcP; aAttr.nArcQ=p->nArcQ; + aAttr.nArcR=p->nArcR; aAttr.nArcS=p->nArcS; + break; + + case GOrdPCrPos: + aAttr.aCurPos=p->aCurPos; + break; + } + pAttrStack=p->pSucc; + delete p; +} + +void OS2METReader::ChangeBrush(const Color& rPatColor, bool bFill ) +{ + Color aColor; + + if( bFill ) + aColor = rPatColor; + else + aColor = COL_TRANSPARENT; + + if( pVirDev->GetFillColor() != aColor ) + pVirDev->SetFillColor( aColor ); +} + +void OS2METReader::SetPen( const Color& rColor, sal_uInt16 nLineWidth, PenStyle ePenStyle ) +{ + LineStyle eLineStyle( LineStyle::Solid ); + + if ( pVirDev->GetLineColor() != rColor ) + pVirDev->SetLineColor( rColor ); + aLineInfo.SetWidth( nLineWidth ); + + sal_uInt16 nDotCount = 0; + sal_uInt16 nDashCount = 0; + switch ( ePenStyle ) + { + case PEN_NULL : + eLineStyle = LineStyle::NONE; + break; + case PEN_DASHDOT : + nDashCount++; + [[fallthrough]]; + case PEN_DOT : + nDotCount++; + nDashCount--; + [[fallthrough]]; + case PEN_DASH : + nDashCount++; + aLineInfo.SetDotCount( nDotCount ); + aLineInfo.SetDashCount( nDashCount ); + aLineInfo.SetDistance( nLineWidth ); + aLineInfo.SetDotLen( nLineWidth ); + aLineInfo.SetDashLen( nLineWidth << 2 ); + eLineStyle = LineStyle::Dash; + break; + case PEN_SOLID: + break; // -Wall not handled... + } + aLineInfo.SetStyle( eLineStyle ); +} + +void OS2METReader::SetRasterOp(RasterOp eROP) +{ + if (pVirDev->GetRasterOp()!=eROP) pVirDev->SetRasterOp(eROP); +} + +void OS2METReader::SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol) +{ + if (pPaletteStack==nullptr) { + pPaletteStack=new OSPalette; + pPaletteStack->pSucc=nullptr; + pPaletteStack->p0RGB=nullptr; + pPaletteStack->nSize=0; + } + if (pPaletteStack->p0RGB==nullptr || nIndex>=pPaletteStack->nSize) { + sal_uInt32 * pOld0RGB=pPaletteStack->p0RGB; + size_t nOldSize = pPaletteStack->nSize; + if (pOld0RGB==nullptr) nOldSize=0; + pPaletteStack->nSize=2*(nIndex+1); + if (pPaletteStack->nSize<256) pPaletteStack->nSize=256; + pPaletteStack->p0RGB = new sal_uInt32[pPaletteStack->nSize]; + for (size_t i=0; i < pPaletteStack->nSize; ++i) + { + if (i<nOldSize) pPaletteStack->p0RGB[i]=pOld0RGB[i]; + else if (i==0) pPaletteStack->p0RGB[i]=0x00ffffff; + else pPaletteStack->p0RGB[i]=0; + } + delete[] pOld0RGB; + } + pPaletteStack->p0RGB[nIndex]=nCol; +} + +sal_uInt32 OS2METReader::GetPalette0RGB(sal_uInt32 nIndex) const +{ + if (pPaletteStack!=nullptr && pPaletteStack->p0RGB!=nullptr && + pPaletteStack->nSize>nIndex) nIndex=pPaletteStack->p0RGB[nIndex]; + return nIndex; +} + +Color OS2METReader::GetPaletteColor(sal_uInt32 nIndex) const +{ + nIndex=GetPalette0RGB(nIndex); + return Color(sal::static_int_cast< sal_uInt8 >((nIndex>>16)&0xff), + sal::static_int_cast< sal_uInt8 >((nIndex>>8)&0xff), + sal::static_int_cast< sal_uInt8 >(nIndex&0xff)); +} + +sal_uInt16 OS2METReader::ReadBigEndianWord() +{ + sal_uInt8 nLo(0), nHi(0); + pOS2MET->ReadUChar( nHi ).ReadUChar( nLo ); + return (static_cast<sal_uInt16>(nHi)<<8)|(static_cast<sal_uInt16>(nLo)&0x00ff); +} + +sal_uInt32 OS2METReader::ReadBigEndian3BytesLong() +{ + sal_uInt8 nHi(0); + pOS2MET->ReadUChar( nHi ); + sal_uInt16 nLo = ReadBigEndianWord(); + return ((static_cast<sal_uInt32>(nHi)<<16)&0x00ff0000)|static_cast<sal_uInt32>(nLo); +} + +sal_uInt32 OS2METReader::ReadLittleEndian3BytesLong() +{ + sal_uInt8 nHi(0), nMed(0), nLo(0); + + pOS2MET->ReadUChar( nLo ).ReadUChar( nMed ).ReadUChar( nHi ); + return ((static_cast<sal_uInt32>(nHi)&0xff)<<16)|((static_cast<sal_uInt32>(nMed)&0xff)<<8)|(static_cast<sal_uInt32>(nLo)&0xff); +} + +sal_Int32 OS2METReader::ReadCoord(bool b32) +{ + sal_Int32 l(0); + + if (b32) pOS2MET->ReadInt32( l ); + else { short s(0); pOS2MET->ReadInt16( s ); l = static_cast<sal_Int32>(s); } + return l; +} + +Point OS2METReader::ReadPoint( const bool bAdjustBoundRect ) +{ + sal_Int32 x = ReadCoord(bCoord32); + sal_Int32 y = ReadCoord(bCoord32); + x=x-aBoundingRect.Left(); + y=aBoundingRect.Bottom()-y; + + if (bAdjustBoundRect) + { + if (x == SAL_MAX_INT32 || y == SAL_MAX_INT32) + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + else + aCalcBndRect.Union(tools::Rectangle(x, y, x + 1, y + 1)); + } + + return Point(x,y); +} + +RasterOp OS2METReader::OS2MixToRasterOp(sal_uInt8 nMix) +{ + switch (nMix) { + case 0x0c: return RasterOp::Invert; + case 0x04: return RasterOp::Xor; + case 0x0b: return RasterOp::Xor; + default: return RasterOp::OverPaint; + } +} + +void OS2METReader::ReadLine(bool bGivenPos, sal_uInt16 nOrderLen) +{ + sal_uInt16 i,nPolySize; + + if (bCoord32) nPolySize=nOrderLen/8; else nPolySize=nOrderLen/4; + if (!bGivenPos) nPolySize++; + if (nPolySize==0) return; + tools::Polygon aPolygon(nPolySize); + for (i=0; i<nPolySize; i++) { + if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i); + else aPolygon.SetPoint(ReadPoint(),i); + } + aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1); + if (pAreaStack!=nullptr) AddPointsToArea(aPolygon); + else if (pPathStack!=nullptr) AddPointsToPath(aPolygon); + else + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + DrawPolyLine( aPolygon ); + } +} + +void OS2METReader::ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen) +{ + sal_uInt16 i,nPolySize; + Point aP0; + + if (bGivenPos) { + aP0=ReadPoint(); + if (bCoord32) nOrderLen-=8; else nOrderLen-=4; + } + else aP0=aAttr.aCurPos; + if (nOrderLen > pOS2MET->remainingSize()) + throw css::uno::Exception("attempt to read past end of input", nullptr); + nPolySize=nOrderLen/2; + if (nPolySize==0) return; + tools::Polygon aPolygon(nPolySize); + for (i=0; i<nPolySize; i++) { + sal_Int8 nsignedbyte; + pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustX(static_cast<sal_Int32>(nsignedbyte)); + pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustY(-static_cast<sal_Int32>(nsignedbyte)); + aCalcBndRect.Union(tools::Rectangle(aP0,Size(1,1))); + aPolygon.SetPoint(aP0,i); + } + aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1); + if (pAreaStack!=nullptr) AddPointsToArea(aPolygon); + else if (pPathStack!=nullptr) AddPointsToPath(aPolygon); + else + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + DrawPolyLine( aPolygon ); + } +} + +void OS2METReader::ReadBox(bool bGivenPos) +{ + sal_uInt8 nFlags; + Point P0; + + pOS2MET->ReadUChar( nFlags ); + pOS2MET->SeekRel(1); + + if ( bGivenPos ) + P0 = ReadPoint(); + else + P0 = aAttr.aCurPos; + + aAttr.aCurPos = ReadPoint(); + sal_Int32 nHRound = ReadCoord(bCoord32); + sal_Int32 nVRound = ReadCoord(bCoord32); + + if (!pOS2MET->good()) + { + SAL_WARN("filter.os2met", "OS2METReader::ReadBox: short read"); + return; + } + + tools::Rectangle aBoxRect( P0, aAttr.aCurPos ); + + if ( pAreaStack ) + AddPointsToArea( tools::Polygon( aBoxRect ) ); + else if ( pPathStack ) + AddPointsToPath( tools::Polygon( aBoxRect ) ); + else + { + if ( nFlags & 0x20 ) + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + else + SetPen( COL_TRANSPARENT ); + + if ( nFlags & 0x40 ) + { + ChangeBrush(aAttr.aPatCol, aAttr.bFill); + SetRasterOp(aAttr.ePatMix); + } + else + { + ChangeBrush( COL_TRANSPARENT, false ); + SetRasterOp(aAttr.eLinMix); + } + + if ( IsLineInfo() ) + { + tools::Polygon aPolygon( aBoxRect, nHRound, nVRound ); + if ( nFlags & 0x40 ) + { + pVirDev->Push( vcl::PushFlags::LINECOLOR ); + pVirDev->SetLineColor( COL_TRANSPARENT ); + pVirDev->DrawRect( aBoxRect, nHRound, nVRound ); + pVirDev->Pop(); + } + pVirDev->DrawPolyLine( aPolygon, aLineInfo ); + } + else + pVirDev->DrawRect( aBoxRect, nHRound, nVRound ); + } +} + +void OS2METReader::ReadBitBlt() +{ + pOS2MET->SeekRel(4); + sal_uInt32 nID(0); + pOS2MET->ReadUInt32( nID ); + pOS2MET->SeekRel(4); + Point aP1 = ReadPoint(); + Point aP2 = ReadPoint(); + if (aP1.X() > aP2.X()) { auto nt=aP1.X(); aP1.setX(aP2.X() ); aP2.setX(nt ); } + if (aP1.Y() > aP2.Y()) { auto nt=aP1.Y(); aP1.setY(aP2.Y() ); aP2.setY(nt ); } + Size aSize(aP2.X() - aP1.X(), aP2.Y() - aP1.Y()); + + OSBitmap* pB = pBitmapList; + while (pB!=nullptr && pB->nID!=nID) pB=pB->pSucc; + if (pB!=nullptr) { + SetRasterOp(aAttr.ePatMix); + pVirDev->DrawBitmapEx(aP1,aSize,pB->aBitmapEx); + } +} + +void OS2METReader::ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen) +{ + Point aP0; + sal_uInt16 nLen; + OSFont * pF; + vcl::Font aFont; + Size aSize; + + pF = pFontList; + while (pF!=nullptr && pF->nID!=aAttr.nChrSet) pF=pF->pSucc; + if (pF!=nullptr) + aFont = pF->aFont; + aFont.SetColor(aAttr.aChrCol); + aFont.SetFontSize(Size(0,aAttr.nChrCellHeight)); + if ( aAttr.nChrAng ) + aFont.SetOrientation(aAttr.nChrAng); + + if (bGivenPos) + aP0 = ReadPoint(); + else + aP0 = aAttr.aCurPos; + if (bExtra) + { + pOS2MET->SeekRel(2); + ReadPoint( false ); + ReadPoint( false ); + pOS2MET->ReadUInt16( nLen ); + } + else + { + if ( !bGivenPos ) + nLen = nOrderLen; + else if ( bCoord32 ) + nLen = nOrderLen-8; + else + nLen = nOrderLen-4; + } + if (!pOS2MET->good() || nLen > pOS2MET->remainingSize()) + throw css::uno::Exception("attempt to read past end of input", nullptr); + std::unique_ptr<char[]> pChr(new char[nLen+1]); + for (sal_uInt16 i=0; i<nLen; i++) + pOS2MET->ReadChar( pChr[i] ); + pChr[nLen] = 0; + OUString aStr( pChr.get(), strlen(pChr.get()), osl_getThreadTextEncoding() ); + SetRasterOp(aAttr.eChrMix); + if (pVirDev->GetFont()!=aFont) + pVirDev->SetFont(aFont); + pVirDev->DrawText(aP0,aStr); + + aSize = Size( pVirDev->GetTextWidth(aStr), pVirDev->GetTextHeight() ); + if ( !aAttr.nChrAng ) + { + aCalcBndRect.Union(tools::Rectangle( Point(aP0.X(),aP0.Y()-aSize.Height()), + Size(aSize.Width(),aSize.Height()*2))); + if (bMove) + aAttr.aCurPos = Point( aP0.X() + aSize.Width(), aP0.Y()); + } + else + { + tools::Polygon aDummyPoly(4); + + aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() ), 0); // TOP LEFT + aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() - aSize.Height() ), 1); // BOTTOM LEFT + aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() ), 2); // TOP RIGHT + aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() - aSize.Height() ), 3);// BOTTOM RIGHT + aDummyPoly.Rotate( aP0, aAttr.nChrAng ); + if ( bMove ) + aAttr.aCurPos = aDummyPoly.GetPoint( 0 ); + aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 0 ), aDummyPoly.GetPoint( 3 ) ) ); + aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 1 ), aDummyPoly.GetPoint( 2 ) ) ); + } +} + +void OS2METReader::ReadArc(bool bGivenPos) +{ + Point aP1, aP2, aP3; + double x1,y1,x2,y2,x3,y3,p,q,cx,cy,ncx,ncy,r,rx,ry,w1,w3; + if (bGivenPos) aP1=ReadPoint(); else aP1=aAttr.aCurPos; + aP2=ReadPoint(); aP3=ReadPoint(); + aAttr.aCurPos=aP3; + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + // Ok, given are 3 point of the ellipse, and the relation + // of width and height (as p to q): + x1=aP1.X(); y1=aP1.Y(); + x2=aP2.X(); y2=aP2.Y(); + x3=aP3.X(); y3=aP3.Y(); + p=aAttr.nArcP;q=aAttr.nArcQ; + // Calculation of the center point cx, cy of the ellipse: + ncy=2*p*p*((y3-y1)*(x2-x1)-(y1-y2)*(x1-x3)); + ncx=2*q*q*(x2-x1); + if ( (ncx<0.001 && ncx>-0.001) || (ncy<0.001 && ncy>-0.001) ) { + // Calculation impossible, points are all on the same straight line + pVirDev->DrawLine(aP1,aP2); + pVirDev->DrawLine(aP2,aP3); + return; + } + cy=( q*q*((x3*x3-x1*x1)*(x2-x1)+(x2*x2-x1*x1)*(x1-x3)) + + p*p*((y3*y3-y1*y1)*(x2-x1)+(y2*y2-y1*y1)*(x1-x3)) ) / ncy; + cx=( q*q*(x2*x2-x1*x1)+p*p*(y2*y2-y1*y1)+cy*2*p*p*(y1-y2) ) / ncx; + // now we still need the radius in x and y direction: + r=sqrt(q*q*(x1-cx)*(x1-cx)+p*p*(y1-cy)*(y1-cy)); + rx=r/q; ry=r/p; + // We now have to find out how the starting and the end point + // have to be chosen so that point no. 2 lies inside the drawn arc: + w1=fmod((atan2(x1-cx,y1-cy)-atan2(x2-cx,y2-cy)),6.28318530718); if (w1<0) w1+=6.28318530718; + w3=fmod((atan2(x3-cx,y3-cy)-atan2(x2-cx,y2-cy)),6.28318530718); if (w3<0) w3+=6.28318530718; + if (w3<w1) { + pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry), + static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP1,aP3); + } + else { + pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry), + static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP3,aP1); + } +} + +void OS2METReader::ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize) +{ + Point aCenter; + tools::Rectangle aRect; + + if (bGivenPos) { + aCenter=ReadPoint(); + if (bCoord32) nOrderSize-=8; else nOrderSize-=4; + } + else aCenter=aAttr.aCurPos; + + sal_Int32 nP = aAttr.nArcP; + sal_Int32 nQ = aAttr.nArcQ; + if (nP < 0) + nP = o3tl::saturating_toggle_sign(nP); + if (nQ < 0) + nQ = o3tl::saturating_toggle_sign(nQ); + sal_uInt32 nMul(0); + if (nOrderSize>=4) + pOS2MET->ReadUInt32( nMul ); + else { + sal_uInt16 nMulS(0); + pOS2MET->ReadUInt16( nMulS ); + nMul=static_cast<sal_uInt32>(nMulS)<<8; + } + if (nMul!=0x00010000) { + nP=(nP*nMul)>>16; + nQ=(nQ*nMul)>>16; + } + + aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ, + aCenter.X()+nP,aCenter.Y()+nQ); + aCalcBndRect.Union(aRect); + + if (pAreaStack!=nullptr) { + ChangeBrush(aAttr.aPatCol, aAttr.bFill); + SetRasterOp(aAttr.ePatMix); + if ((pAreaStack->nFlags&0x40)!=0) + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + else + SetPen( COL_TRANSPARENT, 0, PEN_NULL ); + } + else + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + ChangeBrush(COL_TRANSPARENT, false); + SetRasterOp(aAttr.eLinMix); + } + pVirDev->DrawEllipse(aRect); +} + +void OS2METReader::ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize) +{ + Point aP0, aCenter,aPStart,aPEnd; + tools::Rectangle aRect; + + if (bGivenPos) { + aP0=ReadPoint(); + if (bCoord32) nOrderSize-=8; else nOrderSize-=4; + } + else aP0=aAttr.aCurPos; + aCenter=ReadPoint(); + + sal_Int32 nP = aAttr.nArcP; + sal_Int32 nQ = aAttr.nArcQ; + if (nP < 0) + nP = o3tl::saturating_toggle_sign(nP); + if (nQ < 0) + nQ = o3tl::saturating_toggle_sign(nQ); + sal_uInt32 nMul(0); + if (nOrderSize>=12) + pOS2MET->ReadUInt32( nMul ); + else { + sal_uInt16 nMulS(0); + pOS2MET->ReadUInt16( nMulS ); + nMul=static_cast<sal_uInt32>(nMulS)<<8; + } + if (nMul!=0x00010000) { + nP=(nP*nMul)>>16; + nQ=(nQ*nMul)>>16; + } + + sal_Int32 nStart(0), nSweep(0); + pOS2MET->ReadInt32( nStart ).ReadInt32( nSweep ); + double fStart = basegfx::deg2rad<65536>(static_cast<double>(nStart)); + double fEnd = fStart+ basegfx::deg2rad<65536>(static_cast<double>(nSweep)); + aPStart=Point(aCenter.X()+static_cast<sal_Int32>( cos(fStart)*nP), + aCenter.Y()+static_cast<sal_Int32>(-sin(fStart)*nQ)); + aPEnd= Point(aCenter.X()+static_cast<sal_Int32>( cos(fEnd)*nP), + aCenter.Y()+static_cast<sal_Int32>(-sin(fEnd)*nQ)); + + aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ, + aCenter.X()+nP,aCenter.Y()+nQ); + aCalcBndRect.Union(aRect); + + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + + pVirDev->DrawLine(aP0,aPStart); + pVirDev->DrawArc(aRect,aPStart,aPEnd); + aAttr.aCurPos=aPEnd; +} + +void OS2METReader::ReadPolygons() +{ + tools::PolyPolygon aPolyPoly; + tools::Polygon aPoly; + Point aPoint; + + sal_uInt8 nFlags(0); + sal_uInt32 nNumPolys(0); + pOS2MET->ReadUChar(nFlags).ReadUInt32(nNumPolys); + + if (!pOS2MET->good() || nNumPolys > SAL_MAX_UINT16) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=11; + return; + } + + for (sal_uInt32 i=0; i<nNumPolys; ++i) + { + sal_uInt32 nNumPoints(0); + pOS2MET->ReadUInt32(nNumPoints); + sal_uInt32 nLimit = SAL_MAX_UINT16; + if (i==0) --nLimit; + if (!pOS2MET->good() || nNumPoints > nLimit) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=11; + return; + } + if (i==0) ++nNumPoints; + aPoly.SetSize(static_cast<short>(nNumPoints)); + for (sal_uInt32 j=0; j<nNumPoints; ++j) + { + if (i==0 && j==0) aPoint=aAttr.aCurPos; + else aPoint=ReadPoint(); + aPoly.SetPoint(aPoint,static_cast<short>(j)); + if (i==nNumPolys-1 && j==nNumPoints-1) aAttr.aCurPos=aPoint; + } + aPolyPoly.Insert(aPoly); + } + + ChangeBrush(aAttr.aPatCol, aAttr.bFill); + SetRasterOp(aAttr.ePatMix); + if ((nFlags&0x01)!=0) + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + else + SetPen( COL_TRANSPARENT, 0, PEN_NULL ); + DrawPolyPolygon( aPolyPoly ); +} + +void OS2METReader::ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen) +{ + sal_uInt16 i, nNumPoints = nOrderLen / ( bCoord32 ? 8 : 4 ); + + if( !bGivenPos ) + nNumPoints++; + + if( !nNumPoints ) + return; + + tools::Polygon aPolygon( nNumPoints ); + + for( i=0; i < nNumPoints; i++ ) + { + if( i==0 && !bGivenPos) + aPolygon.SetPoint( aAttr.aCurPos, i ); + else + aPolygon.SetPoint( ReadPoint(), i ); + } + + if( !( nNumPoints % 4 ) ) + { + // create bezier polygon + const sal_uInt16 nSegPoints = 25; + const sal_uInt16 nSegments = aPolygon.GetSize() >> 2; + tools::Polygon aBezPoly( nSegments * nSegPoints ); + + sal_uInt16 nSeg, nBezPos, nStartPos; + for( nSeg = 0, nBezPos = 0, nStartPos = 0; nSeg < nSegments; nSeg++, nStartPos += 4 ) + { + const tools::Polygon aSegPoly( aPolygon[ nStartPos ], aPolygon[ nStartPos + 1 ], + aPolygon[ nStartPos + 3 ], aPolygon[ nStartPos + 2 ], + nSegPoints ); + + for( sal_uInt16 nSegPos = 0; nSegPos < nSegPoints; ) + aBezPoly[ nBezPos++ ] = aSegPoly[ nSegPos++ ]; + } + + nNumPoints = nBezPos; + + if( nNumPoints != aBezPoly.GetSize() ) + aBezPoly.SetSize( nNumPoints ); + + aPolygon = aBezPoly; + } + + aAttr.aCurPos = aPolygon[ nNumPoints - 1 ]; + + if (pAreaStack!=nullptr) + AddPointsToArea(aPolygon); + else if (pPathStack!=nullptr) + AddPointsToPath(aPolygon); + else + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + DrawPolyLine( aPolygon ); + } +} + +void OS2METReader::ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen) +{ + sal_uInt16 i,nNumPoints; + + if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4; + if (!bGivenPos) nNumPoints++; + if (nNumPoints==0) return; + tools::Polygon aPolygon(nNumPoints); + for (i=0; i<nNumPoints; i++) { + if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i); + else aPolygon.SetPoint(ReadPoint(),i); + } + aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1); + if (pAreaStack!=nullptr) AddPointsToArea(aPolygon); + else if (pPathStack!=nullptr) AddPointsToPath(aPolygon); + else { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + DrawPolyLine( aPolygon ); + } +} + +void OS2METReader::ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen) +{ + if (bGivenPos) { + aAttr.aCurPos=ReadPoint(); + if (bCoord32) nOrderLen-=8; else nOrderLen-=4; + } + + sal_uInt16 nNumPoints; + if (bCoord32) nNumPoints=1+nOrderLen/10; + else nNumPoints=1+nOrderLen/6; + + tools::Polygon aPolygon(nNumPoints); + aPolygon.SetPoint(aAttr.aCurPos, 0); + for (sal_uInt16 i = 1; i <nNumPoints; ++i) + aPolygon.SetPoint(ReadPoint(), i); + + if (!pOS2MET->good()) + { + SAL_WARN("filter.os2met", "OS2METReader::ReadFilletSharp: short read"); + return; + } + + aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1); + if (pAreaStack!=nullptr) AddPointsToArea(aPolygon); + else if (pPathStack!=nullptr) AddPointsToPath(aPolygon); + else + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + DrawPolyLine( aPolygon ); + } +} + +void OS2METReader::ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen) +{ + sal_uInt16 i,nNumPoints; + + SetPen( aAttr.aMrkCol ); + SetRasterOp(aAttr.eMrkMix); + if (aAttr.nMrkSymbol>=5 && aAttr.nMrkSymbol<=9) + { + ChangeBrush(aAttr.aMrkCol, true); + } + else + { + ChangeBrush(COL_TRANSPARENT, false); + } + if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4; + if (!bGivenPos) nNumPoints++; + for (i=0; i<nNumPoints; i++) { + if (i!=0 || bGivenPos) aAttr.aCurPos=ReadPoint(); + const auto x = aAttr.aCurPos.X(); + const auto y = aAttr.aCurPos.Y(); + aCalcBndRect.Union(tools::Rectangle(x-5,y-5,x+5,y+5)); + switch (aAttr.nMrkSymbol) { + case 2: // PLUS + pVirDev->DrawLine(Point(x-4,y),Point(x+4,y)); + pVirDev->DrawLine(Point(x,y-4),Point(x,y+4)); + break; + case 3: // DIAMOND + case 7: { // SOLIDDIAMOND + tools::Polygon aPoly(4); + aPoly.SetPoint(Point(x,y+4),0); + aPoly.SetPoint(Point(x+4,y),1); + aPoly.SetPoint(Point(x,y-4),2); + aPoly.SetPoint(Point(x-4,y),3); + pVirDev->DrawPolygon(aPoly); + break; + } + case 4: // SQUARE + case 8: { // SOLIDSUARE + tools::Polygon aPoly(4); + aPoly.SetPoint(Point(x+4,y+4),0); + aPoly.SetPoint(Point(x+4,y-4),1); + aPoly.SetPoint(Point(x-4,y-4),2); + aPoly.SetPoint(Point(x-4,y+4),3); + pVirDev->DrawPolygon(aPoly); + break; + } + case 5: { // SIXPOINTSTAR + tools::Polygon aPoly(12); + aPoly.SetPoint(Point(x ,y-4),0); + aPoly.SetPoint(Point(x+2,y-2),1); + aPoly.SetPoint(Point(x+4,y-2),2); + aPoly.SetPoint(Point(x+2,y ),3); + aPoly.SetPoint(Point(x+4,y+2),4); + aPoly.SetPoint(Point(x+2,y+2),5); + aPoly.SetPoint(Point(x ,y+4),6); + aPoly.SetPoint(Point(x-2,y+2),7); + aPoly.SetPoint(Point(x-4,y+2),8); + aPoly.SetPoint(Point(x-2,y ),9); + aPoly.SetPoint(Point(x-4,y-2),10); + aPoly.SetPoint(Point(x-2,y-2),11); + pVirDev->DrawPolygon(aPoly); + break; + } + case 6: { // EIGHTPOINTSTAR + tools::Polygon aPoly(16); + aPoly.SetPoint(Point(x ,y-4),0); + aPoly.SetPoint(Point(x+1,y-2),1); + aPoly.SetPoint(Point(x+3,y-3),2); + aPoly.SetPoint(Point(x+2,y-1),3); + aPoly.SetPoint(Point(x+4,y ),4); + aPoly.SetPoint(Point(x+2,y+1),5); + aPoly.SetPoint(Point(x+3,y+3),6); + aPoly.SetPoint(Point(x+1,y+2),7); + aPoly.SetPoint(Point(x ,y+4),8); + aPoly.SetPoint(Point(x-1,y+2),9); + aPoly.SetPoint(Point(x-3,y+3),10); + aPoly.SetPoint(Point(x-2,y+1),11); + aPoly.SetPoint(Point(x-4,y ),12); + aPoly.SetPoint(Point(x-2,y-1),13); + aPoly.SetPoint(Point(x-3,y-3),14); + aPoly.SetPoint(Point(x-1,y-2),15); + pVirDev->DrawPolygon(aPoly); + break; + } + case 9: // DOT + pVirDev->DrawEllipse(tools::Rectangle(x-1,y-1,x+1,y+1)); + break; + case 10: // SMALLCIRCLE + pVirDev->DrawEllipse(tools::Rectangle(x-2,y-2,x+2,y+2)); + break; + case 64: // BLANK + break; + default: // (=1) CROSS + pVirDev->DrawLine(Point(x-4,y-4),Point(x+4,y+4)); + pVirDev->DrawLine(Point(x-4,y+4),Point(x+4,y-4)); + break; + } + } +} + +void OS2METReader::ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen) +{ + switch (nOrderID) { + + case GOrdGivArc: ReadArc(true); break; + case GOrdCurArc: ReadArc(false); break; + + case GOrdGivBzr: ReadBezier(true,nOrderLen); break; + case GOrdCurBzr: ReadBezier(false,nOrderLen); break; + + case GOrdGivBox: ReadBox(true); break; + case GOrdCurBox: ReadBox(false); break; + + case GOrdGivFil: ReadFillet(true,nOrderLen); break; + case GOrdCurFil: ReadFillet(false,nOrderLen); break; + + case GOrdGivCrc: ReadFullArc(true,nOrderLen); break; + case GOrdCurCrc: ReadFullArc(false,nOrderLen); break; + + case GOrdGivLin: ReadLine(true, nOrderLen); break; + case GOrdCurLin: ReadLine(false, nOrderLen); break; + + case GOrdGivMrk: ReadMarker(true, nOrderLen); break; + case GOrdCurMrk: ReadMarker(false, nOrderLen); break; + + case GOrdGivArP: ReadPartialArc(true,nOrderLen); break; + case GOrdCurArP: ReadPartialArc(false,nOrderLen); break; + + case GOrdGivRLn: ReadRelLine(true,nOrderLen); break; + case GOrdCurRLn: ReadRelLine(false,nOrderLen); break; + + case GOrdGivSFl: ReadFilletSharp(true,nOrderLen); break; + case GOrdCurSFl: ReadFilletSharp(false,nOrderLen); break; + + case GOrdGivStM: ReadChrStr(true , true , false, nOrderLen); break; + case GOrdCurStM: ReadChrStr(false, true , false, nOrderLen); break; + case GOrdGivStr: ReadChrStr(true , false, false, nOrderLen); break; + case GOrdCurStr: ReadChrStr(false, false, false, nOrderLen); break; + case GOrdGivStx: ReadChrStr(true , false, true , nOrderLen); break; + case GOrdCurStx: ReadChrStr(false, false, true , nOrderLen); break; + + case GOrdGivImg: SAL_INFO("filter.os2met","GOrdGivImg"); + break; + case GOrdCurImg: SAL_INFO("filter.os2met","GOrdCurImg"); + break; + case GOrdImgDat: SAL_INFO("filter.os2met","GOrdImgDat"); + break; + case GOrdEndImg: SAL_INFO("filter.os2met","GOrdEndImg"); + break; + + case GOrdBegAra: { + OSArea * p=new OSArea; + p->bClosed=false; + p->pSucc=pAreaStack; pAreaStack=p; + pOS2MET->ReadUChar( p->nFlags ); + p->aCol=aAttr.aPatCol; + p->aBgCol=aAttr.aPatBgCol; + p->eMix=aAttr.ePatMix; + p->eBgMix=aAttr.ePatBgMix; + p->bFill=aAttr.bFill; + break; + } + case GOrdEndAra: + { + OSArea * p=pAreaStack; + if ( p ) + { + pAreaStack = p->pSucc; + if ( pPathStack ) + { + for ( sal_uInt16 i=0; i<p->aPPoly.Count(); i++ ) + { + AddPointsToPath( p->aPPoly.GetObject( i ) ); + CloseFigure(); + } + } + else + { + if ( ( p->nFlags & 0x40 ) == 0 ) + SetPen( COL_TRANSPARENT, 0, PEN_NULL ); + else + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + + ChangeBrush(p->aCol, p->bFill); + SetRasterOp(p->eMix); + DrawPolyPolygon( p->aPPoly ); + } + delete p; + } + } + break; + + case GOrdBegElm: SAL_INFO("filter.os2met","GOrdBegElm"); + break; + case GOrdEndElm: SAL_INFO("filter.os2met","GOrdEndElm"); + break; + + case GOrdBegPth: { + OSPath * p=new OSPath; + p->pSucc=pPathStack; pPathStack=p; + pOS2MET->SeekRel(2); + pOS2MET->ReadUInt32( p->nID ); + p->bClosed=false; + p->bStroke=false; + break; + } + case GOrdEndPth: { + OSPath * p, * pprev, * psucc; + if (pPathStack==nullptr) break; + p=pPathList; pprev=nullptr; + while (p!=nullptr) { + psucc=p->pSucc; + if (p->nID==pPathStack->nID) { + if (pprev==nullptr) pPathList=psucc; else pprev->pSucc=psucc; + delete p; + } + else pprev=p; + p=psucc; + } + p=pPathStack; + pPathStack=p->pSucc; + p->pSucc=pPathList; pPathList=p; + break; + } + case GOrdFilPth: + { + sal_uInt32 nID(0); + sal_uInt16 nDummy(0); + OSPath* p = pPathList; + + pOS2MET->ReadUInt16( nDummy ) + .ReadUInt32( nID ); + + if ( ! ( nDummy & 0x20 ) ) // #30933# i do not know the exact meaning of this bit, + { // but if set it seems to be better not to fill this path + while( p && p->nID != nID ) + p = p->pSucc; + + if( p ) + { + if( p->bStroke ) + { + SetPen( aAttr.aPatCol, aAttr.nStrLinWidth ); + ChangeBrush(COL_TRANSPARENT, false); + SetRasterOp( aAttr.ePatMix ); + if ( IsLineInfo() ) + { + for ( sal_uInt16 i = 0; i < p->aPPoly.Count(); i++ ) + pVirDev->DrawPolyLine( p->aPPoly.GetObject( i ), aLineInfo ); + } + else + pVirDev->DrawPolyPolygon( p->aPPoly ); + } + else + { + SetPen( COL_TRANSPARENT, 0, PEN_NULL ); + ChangeBrush( aAttr.aPatCol, aAttr.bFill ); + SetRasterOp( aAttr.ePatMix ); + pVirDev->DrawPolyPolygon( p->aPPoly ); + } + } + } + } + break; + + case GOrdModPth: + { + OSPath* p = pPathList; + + while( p && p->nID != 1 ) + p = p->pSucc; + + if( p ) + p->bStroke = true; + } + break; + + case GOrdOutPth: + { + sal_uInt32 nID; + sal_uInt16 i,nC; + OSPath* p=pPathList; + pOS2MET->SeekRel(2); + pOS2MET->ReadUInt32( nID ); + while (p && pOS2MET->good() && p->nID != nID) + p = p->pSucc; + + if (p) + { + SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle ); + SetRasterOp(aAttr.eLinMix); + ChangeBrush(COL_TRANSPARENT, false); + nC=p->aPPoly.Count(); + for (i=0; i<nC; i++) + { + if (i+1<nC || p->bClosed) + DrawPolygon( p->aPPoly.GetObject( i ) ); + else + DrawPolyLine( p->aPPoly.GetObject( i ) ); + } + } + break; + } + case GOrdSClPth: { + SAL_INFO("filter.os2met","GOrdSClPth"); + sal_uInt32 nID(0); + OSPath * p=pPathList; + pOS2MET->SeekRel(2); + pOS2MET->ReadUInt32( nID ); + if (nID==0) p=nullptr; + while (p!=nullptr && p->nID!=nID) p=p->pSucc; + if (p!=nullptr) pVirDev->SetClipRegion(vcl::Region(p->aPPoly)); + else pVirDev->SetClipRegion(); + break; + } + case GOrdNopNop: + break; + case GOrdRemark: SAL_INFO("filter.os2met","GOrdRemark"); + break; + case GOrdSegLab: SAL_INFO("filter.os2met","GOrdSegLab"); + break; + + case GOrdBitBlt: ReadBitBlt(); break; + + case GOrdCalSeg: SAL_INFO("filter.os2met","GOrdCalSeg"); + break; + case GOrdSSgBnd: SAL_INFO("filter.os2met","GOrdSSgBnd"); + break; + case GOrdSegChr: SAL_INFO("filter.os2met","GOrdSegChr"); + break; + case GOrdCloFig: + CloseFigure(); + break; + case GOrdEndSym: SAL_INFO("filter.os2met","GOrdEndSym"); + break; + case GOrdEndPlg: SAL_INFO("filter.os2met","GOrdEndPlg"); + break; + case GOrdEscape: SAL_INFO("filter.os2met","GOrdEscape"); + break; + case GOrdExtEsc: SAL_INFO("filter.os2met","GOrdExtEsc"); + break; + + case GOrdPolygn: ReadPolygons(); break; + + case GOrdStkPop: PopAttr(); break; + + case GOrdPIvAtr: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSIvAtr: { + sal_uInt8 nA(0), nP(0), nFlags(0); + Color aCol; + RasterOp eROP; + pOS2MET->ReadUChar( nA ).ReadUChar( nP ).ReadUChar( nFlags ); + if (nOrderID==GOrdPIvAtr) { + pAttrStack->nIvAttrA=nA; + pAttrStack->nIvAttrP=nP; + } + if (nA<=2) { + if ((nFlags&0x80)!=0) { + if (nA==1) switch (nP) { + case 1: aAttr.aLinCol=aDefAttr.aLinCol; break; + case 2: aAttr.aChrCol=aDefAttr.aChrCol; break; + case 3: aAttr.aMrkCol=aDefAttr.aMrkCol; break; + case 4: aAttr.aPatCol=aDefAttr.aPatCol; break; + case 5: aAttr.aImgCol=aDefAttr.aImgCol; break; + } + else switch (nP) { + case 1: aAttr.aLinBgCol=aDefAttr.aLinBgCol; break; + case 2: aAttr.aChrBgCol=aDefAttr.aChrBgCol; break; + case 3: aAttr.aMrkBgCol=aDefAttr.aMrkBgCol; break; + case 4: aAttr.aPatBgCol=aDefAttr.aPatBgCol; break; + case 5: aAttr.aImgBgCol=aDefAttr.aImgBgCol; break; + } + } + else { + const auto nVal = ReadLittleEndian3BytesLong(); + if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK; + else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK; + else aCol=GetPaletteColor(nVal); + if (nA==1) switch (nP) { + case 1: aAttr.aLinCol=aCol; break; + case 2: aAttr.aChrCol=aCol; break; + case 3: aAttr.aMrkCol=aCol; break; + case 4: aAttr.aPatCol=aCol; break; + case 5: aAttr.aImgCol=aCol; break; + } + else switch (nP) { + case 1: aAttr.aLinBgCol=aCol; break; + case 2: aAttr.aChrBgCol=aCol; break; + case 3: aAttr.aMrkBgCol=aCol; break; + case 4: aAttr.aPatBgCol=aCol; break; + case 5: aAttr.aImgBgCol=aCol; break; + } + } + } + else { + sal_uInt8 nMix(0); + pOS2MET->ReadUChar( nMix ); + if (nMix==0) { + switch (nP) { + case 1: aAttr.eLinBgMix=aDefAttr.eLinBgMix; break; + case 2: aAttr.eChrBgMix=aDefAttr.eChrBgMix; break; + case 3: aAttr.eMrkBgMix=aDefAttr.eMrkBgMix; break; + case 4: aAttr.ePatBgMix=aDefAttr.ePatBgMix; break; + case 5: aAttr.eImgBgMix=aDefAttr.eImgBgMix; break; + } + } + else { + eROP=OS2MixToRasterOp(nMix); + switch (nP) { + case 1: aAttr.eLinBgMix=eROP; break; + case 2: aAttr.eChrBgMix=eROP; break; + case 3: aAttr.eMrkBgMix=eROP; break; + case 4: aAttr.ePatBgMix=eROP; break; + case 5: aAttr.eImgBgMix=eROP; break; + } + } + } + break; + } + case GOrdPIxCol: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSIxCol: { + sal_uInt8 nFlags(0); + pOS2MET->ReadUChar( nFlags ); + if ((nFlags&0x80)!=0) { + aAttr.aLinCol=aDefAttr.aLinCol; + aAttr.aChrCol=aDefAttr.aChrCol; + aAttr.aMrkCol=aDefAttr.aMrkCol; + aAttr.aPatCol=aDefAttr.aPatCol; + aAttr.aImgCol=aDefAttr.aImgCol; + } + else { + Color aCol; + const auto nVal = ReadLittleEndian3BytesLong(); + if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK; + else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK; + else aCol=GetPaletteColor(nVal); + aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol = + aAttr.aImgCol = aCol; + } + break; + } + + case GOrdPColor: + case GOrdPXtCol: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSColor: + case GOrdSXtCol: { + sal_uInt16 nVal(0); + if (nOrderID==GOrdPColor || nOrderID==GOrdSColor) { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); nVal=static_cast<sal_uInt16>(nbyte)|0xff00; + } + else pOS2MET->ReadUInt16( nVal ); + if (nVal==0x0000 || nVal==0xff00) { + aAttr.aLinCol=aDefAttr.aLinCol; + aAttr.aChrCol=aDefAttr.aChrCol; + aAttr.aMrkCol=aDefAttr.aMrkCol; + aAttr.aPatCol=aDefAttr.aPatCol; + aAttr.aImgCol=aDefAttr.aImgCol; + } + else { + Color aCol; + if (nVal==0x0007) aCol=COL_WHITE; + else if (nVal==0x0008) aCol=COL_BLACK; + else if (nVal==0xff08) aCol=GetPaletteColor(1); + else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff); + aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol = + aAttr.aImgCol = aCol; + } + break; + } + + case GOrdPBgCol: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSBgCol: { + sal_uInt16 nVal(0); + pOS2MET->ReadUInt16( nVal ); + if (nVal==0x0000 || nVal==0xff00) { + aAttr.aLinBgCol=aDefAttr.aLinBgCol; + aAttr.aChrBgCol=aDefAttr.aChrBgCol; + aAttr.aMrkBgCol=aDefAttr.aMrkBgCol; + aAttr.aPatBgCol=aDefAttr.aPatBgCol; + aAttr.aImgBgCol=aDefAttr.aImgBgCol; + } + else { + Color aCol; + if (nVal==0x0007) aCol=COL_WHITE; + else if (nVal==0x0008) aCol=COL_BLACK; + else if (nVal==0xff08) aCol=GetPaletteColor(0); + else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff); + aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol = + aAttr.aPatBgCol = aAttr.aImgBgCol = aCol; + } + break; + } + case GOrdPBxCol: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSBxCol: { + sal_uInt8 nFlags(0); + pOS2MET->ReadUChar( nFlags ); + if ((nFlags&0x80)!=0) { + aAttr.aLinBgCol=aDefAttr.aLinBgCol; + aAttr.aChrBgCol=aDefAttr.aChrBgCol; + aAttr.aMrkBgCol=aDefAttr.aMrkBgCol; + aAttr.aPatBgCol=aDefAttr.aPatBgCol; + aAttr.aImgBgCol=aDefAttr.aImgBgCol; + } + else { + Color aCol; + const auto nVal = ReadLittleEndian3BytesLong(); + if ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK; + else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE; + else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK; + else aCol=GetPaletteColor(nVal); + aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol = + aAttr.aPatBgCol = aAttr.aImgBgCol = aCol; + } + break; + } + + case GOrdPMixMd: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMixMd: { + sal_uInt8 nMix(0); + pOS2MET->ReadUChar( nMix ); + if (nMix==0) { + aAttr.eLinMix=aDefAttr.eLinMix; + aAttr.eChrMix=aDefAttr.eChrMix; + aAttr.eMrkMix=aDefAttr.eMrkMix; + aAttr.ePatMix=aDefAttr.ePatMix; + aAttr.eImgMix=aDefAttr.eImgMix; + } + else { + aAttr.eLinMix = aAttr.eChrMix = aAttr.eMrkMix = + aAttr.ePatMix = aAttr.eImgMix = OS2MixToRasterOp(nMix); + } + break; + } + case GOrdPBgMix: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSBgMix: { + sal_uInt8 nMix(0); + pOS2MET->ReadUChar( nMix ); + if (nMix==0) { + aAttr.eLinBgMix=aDefAttr.eLinBgMix; + aAttr.eChrBgMix=aDefAttr.eChrBgMix; + aAttr.eMrkBgMix=aDefAttr.eMrkBgMix; + aAttr.ePatBgMix=aDefAttr.ePatBgMix; + aAttr.eImgBgMix=aDefAttr.eImgBgMix; + } + else { + aAttr.eLinBgMix = aAttr.eChrBgMix = aAttr.eMrkBgMix = + aAttr.ePatBgMix = aAttr.eImgBgMix = OS2MixToRasterOp(nMix); + } + break; + } + case GOrdPPtSet: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSPtSet: SAL_INFO("filter.os2met","GOrdSPtSet"); + break; + + case GOrdPPtSym: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSPtSym: { + sal_uInt8 nPatt(0); + pOS2MET->ReadUChar( nPatt ); + aAttr.bFill = ( nPatt != 0x0f ); + break; + } + + case GOrdPPtRef: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSPtRef: SAL_INFO("filter.os2met","GOrdSPtRef"); + break; + + case GOrdPLnEnd: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSLnEnd: + break; + + case GOrdPLnJoi: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSLnJoi: + break; + + case GOrdPLnTyp: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSLnTyp: { + sal_uInt8 nType(0); + pOS2MET->ReadUChar( nType ); + switch (nType) { + case 0: aAttr.eLinStyle=aDefAttr.eLinStyle; break; + case 1: case 4: aAttr.eLinStyle=PEN_DOT; break; + case 2: case 5: aAttr.eLinStyle=PEN_DASH; break; + case 3: case 6: aAttr.eLinStyle=PEN_DASHDOT; break; + case 8: aAttr.eLinStyle=PEN_NULL; break; + default: aAttr.eLinStyle=PEN_SOLID; + } + break; + } + case GOrdPLnWdt: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSLnWdt: { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0) aAttr.nLinWidth=aDefAttr.nLinWidth; + else aAttr.nLinWidth=static_cast<sal_uInt16>(nbyte)-1; + break; + } + case GOrdPFrLWd: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSFrLWd: + break; + + case GOrdPStLWd: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSStLWd : + { + sal_uInt8 nFlags(0); + + pOS2MET->ReadUChar( nFlags ); + if ( nFlags & 0x80 ) + aAttr.nStrLinWidth = aDefAttr.nStrLinWidth; + else + { + pOS2MET->SeekRel( 1 ); + sal_Int32 nWd = ReadCoord( bCoord32 ); + if (nWd < 0) + nWd = o3tl::saturating_toggle_sign(nWd); + aAttr.nStrLinWidth = static_cast<sal_uInt16>(nWd); + } + break; + } + case GOrdPChDir: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChDir: + break; + + case GOrdPChPrc: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChPrc: + break; + + case GOrdPChSet: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChSet: { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + aAttr.nChrSet=static_cast<sal_uInt32>(nbyte)&0xff; + break; + } + case GOrdPChAng: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChAng: { + sal_Int32 nX = ReadCoord(bCoord32); + sal_Int32 nY = ReadCoord(bCoord32); + if (nX>=0 && nY==0) aAttr.nChrAng=0_deg10; + else { + aAttr.nChrAng = Degree10(static_cast<short>(basegfx::rad2deg<10>(atan2(static_cast<double>(nY),static_cast<double>(nX))))); + while (aAttr.nChrAng < 0_deg10) aAttr.nChrAng += 3600_deg10; + aAttr.nChrAng %= 3600_deg10; + } + break; + } + case GOrdPChBrx: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChBrx: + break; + + case GOrdPChCel: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChCel: { + sal_uInt16 nLen=nOrderLen; + (void) ReadCoord(bCoord32); // Width, unused + auto nHeight = ReadCoord(bCoord32); + if (nHeight < 0 || nHeight > SAL_MAX_INT16) + { + SAL_WARN("filter.os2met", "ignoring out of sane range font height: " << nHeight); + aAttr.nChrCellHeight = aDefAttr.nChrCellHeight; + } + else + aAttr.nChrCellHeight = nHeight; + if (bCoord32) nLen-=8; else nLen-=4; + if (nLen>=4) { + pOS2MET->SeekRel(4); nLen-=4; + } + if (nLen>=2) { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if ((nbyte&0x80)==0 && aAttr.nChrCellHeight == 0) + aAttr.nChrCellHeight = aDefAttr.nChrCellHeight; + } + break; + } + case GOrdPChXtr: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChXtr: + break; + + case GOrdPChShr: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSChShr: + break; + + case GOrdPTxAlg: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSTxAlg: SAL_INFO("filter.os2met","GOrdSTxAlg"); + break; + + case GOrdPMkPrc: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMkPrc: { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0) aAttr.nMrkPrec=aDefAttr.nMrkPrec; + else aAttr.nMrkPrec=nbyte; + break; + } + + case GOrdPMkSet: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMkSet: { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0) aAttr.nMrkSet=aDefAttr.nMrkSet; + else aAttr.nMrkSet=nbyte; + break; + } + + case GOrdPMkSym: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMkSym: { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0) aAttr.nMrkSymbol=aDefAttr.nMrkSymbol; + else aAttr.nMrkSymbol=nbyte; + break; + } + + case GOrdPMkCel: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMkCel: { + sal_uInt16 nLen=nOrderLen; + aAttr.aMrkCellSize.setWidth(ReadCoord(bCoord32) ); + aAttr.aMrkCellSize.setHeight(ReadCoord(bCoord32) ); + if (bCoord32) nLen-=8; else nLen-=4; + if (nLen>=2) { + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if ((nbyte&0x80)==0 && aAttr.aMrkCellSize==Size(0,0)) + aAttr.aMrkCellSize=aDefAttr.aMrkCellSize; + } + break; + } + + case GOrdPArcPa: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSArcPa: + aAttr.nArcP=ReadCoord(bCoord32); + aAttr.nArcQ=ReadCoord(bCoord32); + aAttr.nArcR=ReadCoord(bCoord32); + aAttr.nArcS=ReadCoord(bCoord32); + break; + + case GOrdPCrPos: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSCrPos: + aAttr.aCurPos=ReadPoint(); + break; + + case GOrdPMdTrn: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSMdTrn: SAL_INFO("filter.os2met","GOrdSMdTrn"); + break; + + case GOrdPPkIdn: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSPkIdn: SAL_INFO("filter.os2met","GOrdSPkIdn"); + break; + + case GOrdSVwTrn: SAL_INFO("filter.os2met","GOrdSVwTrn"); + break; + + case GOrdPVwWin: PushAttr(nOrderID); + [[fallthrough]]; + case GOrdSVwWin: SAL_INFO("filter.os2met","GOrdSVwWin"); + break; + default: SAL_INFO("filter.os2met","Unknown order: " << nOrderID); + } +} + +void OS2METReader::ReadDsc(sal_uInt16 nDscID) +{ + switch (nDscID) { + case 0x00f7: { // 'Specify GVM Subset' + sal_uInt8 nbyte(0); + pOS2MET->SeekRel(6); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0x05) bCoord32=true; + else if (nbyte==0x04) bCoord32=false; + else { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=1; + } + break; + } + case 0x00f6: + { + // 'Set Picture Descriptor' + bool b32; + sal_uInt8 nbyte(0), nUnitType(0); + + pOS2MET->SeekRel(2); + pOS2MET->ReadUChar( nbyte ); + + if (nbyte==0x05) + b32=true; + else if(nbyte==0x04) + b32=false; + else + { + b32 = false; // -Wall added the case. + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=2; + } + + pOS2MET->ReadUChar( nUnitType ); + + sal_Int32 xr = ReadCoord(b32); + sal_Int32 yr = ReadCoord(b32); + + ReadCoord(b32); + + if (nUnitType==0x00 && xr>0 && yr>0) + aGlobMapMode=MapMode(MapUnit::MapInch,Point(0,0),Fraction(10,xr),Fraction(10,yr)); + else if (nUnitType==0x01 && xr>0 && yr>0) + aGlobMapMode=MapMode(MapUnit::MapCM,Point(0,0),Fraction(10,xr),Fraction(10,yr)); + else + aGlobMapMode=MapMode(); + + sal_Int32 x1 = ReadCoord(b32); + sal_Int32 x2 = ReadCoord(b32); + sal_Int32 y1 = ReadCoord(b32); + sal_Int32 y2 = ReadCoord(b32); + + if (x1>x2) + { + const auto nt = x1; + x1=x2; + x2=nt; + } + + if (y1>y2) + { + const auto nt = y1; + y1=y2; + y2=nt; + } + + aBoundingRect.SetLeft( x1 ); + aBoundingRect.SetRight( x2 ); + aBoundingRect.SetTop( y1 ); + aBoundingRect.SetBottom( y2 ); + + // no output beside this bounding rect + pVirDev->IntersectClipRegion( tools::Rectangle( Point(), aBoundingRect.GetSize() ) ); + + break; + } + case 0x0021: // 'Set Current Defaults' + break; + } +} + +void OS2METReader::ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen) +{ + OSBitmap * p=pBitmapList; if (p==nullptr) return; + + switch (nDataID) { + + case 0x0070: // Begin Segment + break; + + case 0x0091: // Begin Image Content + break; + + case 0x0094: // Image Size + pOS2MET->SeekRel(5); + p->nHeight=ReadBigEndianWord(); + p->nWidth=ReadBigEndianWord(); + break; + + case 0x0095: // Image Encoding + break; + + case 0x0096: { // Image IDE-Size + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); p->nBitsPerPixel=nbyte; + break; + } + + case 0x0097: // Image LUT-ID + break; + + case 0x009b: // IDE Structure + break; + + case 0xfe92: { // Image Data + // At the latest we now need the temporary BMP file and + // inside this file we need the header and the palette. + if (p->pBMP==nullptr) { + p->pBMP=new SvMemoryStream(); + p->pBMP->SetEndian(SvStreamEndian::LITTLE); + if (p->nWidth==0 || p->nHeight==0 || p->nBitsPerPixel==0) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=3; + return; + } + // write (Windows-)BITMAPINFOHEADER: + p->pBMP->WriteUInt32( 40 ).WriteUInt32( p->nWidth ).WriteUInt32( p->nHeight ); + p->pBMP->WriteUInt16( 1 ).WriteUInt16( p->nBitsPerPixel ); + p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ); + p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 ); + // write color table: + if (p->nBitsPerPixel<=8) { + sal_uInt16 i, nColTabSize=1<<(p->nBitsPerPixel); + for (i=0; i<nColTabSize; i++) p->pBMP->WriteUInt32( GetPalette0RGB(i) ); + } + } + // OK, now the map data is being pushed. Unfortunately OS2 and BMP + // do have a different RGB ordering when using 24-bit + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nDataLen]); + pOS2MET->ReadBytes(pBuf.get(), nDataLen); + sal_uInt32 nBytesPerLineToSwap = (p->nBitsPerPixel == 24) ? + ((p->nWidth * 3 + 3) & 0xfffffffc) : 0; + if (nBytesPerLineToSwap) { + sal_uInt32 nAlign = p->nMapPos - (p->nMapPos % nBytesPerLineToSwap); + sal_uInt32 i=0; + while (nAlign+i+2<p->nMapPos+nDataLen) { + if (nAlign+i>=p->nMapPos) { + sal_uInt32 j = nAlign + i - p->nMapPos; + std::swap(pBuf[j], pBuf[j+2]); + } + i+=3; + if (i + 2 >= nBytesPerLineToSwap) { + nAlign += nBytesPerLineToSwap; + i=0; + } + } + } + p->pBMP->WriteBytes(pBuf.get(), nDataLen); + p->nMapPos+=nDataLen; + break; + } + case 0x0093: // End Image Content + break; + + case 0x0071: // End Segment + break; + } +} + +void OS2METReader::ReadFont(sal_uInt16 nFieldSize) +{ + OSFont * pF=new OSFont; + pF->pSucc=pFontList; pFontList=pF; + pF->nID=0; + pF->aFont.SetTransparent(true); + pF->aFont.SetAlignment(ALIGN_BASELINE); + + auto nPos=pOS2MET->Tell(); + auto nMaxPos = nPos + nFieldSize; + pOS2MET->SeekRel(2); nPos+=2; + while (nPos<nMaxPos && pOS2MET->good()) { + sal_uInt8 nByte(0); + pOS2MET->ReadUChar(nByte); + sal_uInt16 nLen = static_cast<sal_uInt16>(nByte) & 0x00ff; + if (nLen == 0) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=4; + } + sal_uInt8 nTripType(0); + pOS2MET->ReadUChar( nTripType ); + switch (nTripType) { + case 0x02: + { + sal_uInt8 nTripType2(0); + pOS2MET->ReadUChar( nTripType2 ); + switch (nTripType2) { + case 0x84: // Font name + break; + case 0x08: { // Font Typeface + char str[33]; + pOS2MET->SeekRel(1); + str[pOS2MET->ReadBytes(str, 32)] = 0; + OUString aStr( str, strlen(str), osl_getThreadTextEncoding() ); + if ( aStr.equalsIgnoreAsciiCase( "Helv" ) ) + aStr = "Helvetica"; + pF->aFont.SetFamilyName( aStr ); + break; + } + } + break; + } + case 0x24: // Icid + { + sal_uInt8 nTripType2(0); + pOS2MET->ReadUChar( nTripType2 ); + switch (nTripType2) { + case 0x05: //Icid + pOS2MET->ReadUChar( nByte ); + pF->nID=static_cast<sal_uInt32>(nByte)&0xff; + break; + } + break; + } + case 0x20: // Font Binary GCID + break; + case 0x1f: { // Font Attributes + FontWeight eWeight; + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + switch (nbyte) { + case 1: eWeight=WEIGHT_THIN; break; + case 2: eWeight=WEIGHT_ULTRALIGHT; break; + case 3: eWeight=WEIGHT_LIGHT; break; + case 4: eWeight=WEIGHT_SEMILIGHT; break; + case 5: eWeight=WEIGHT_NORMAL; break; + case 6: eWeight=WEIGHT_SEMIBOLD; break; + case 7: eWeight=WEIGHT_BOLD; break; + case 8: eWeight=WEIGHT_ULTRABOLD; break; + case 9: eWeight=WEIGHT_BLACK; break; + default: eWeight=WEIGHT_DONTKNOW; + } + pF->aFont.SetWeight(eWeight); + break; + } + } + nPos+=nLen; + pOS2MET->Seek(nPos); + } +} + +void OS2METReader::ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize) +{ + switch (nFieldType) { + case BegDocumnMagic: + break; + case EndDocumnMagic: + break; + case BegResGrpMagic: + break; + case EndResGrpMagic: + break; + case BegColAtrMagic: + break; + case EndColAtrMagic: + break; + case BlkColAtrMagic: { + sal_uInt8 nbyte; + sal_uInt16 nStartIndex, nEndIndex, i, nElemLen, nBytesPerCol; + + auto nPos = pOS2MET->Tell(); + auto nMaxPos = nPos + nFieldSize; + pOS2MET->SeekRel(3); nPos+=3; + while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) { + pOS2MET->ReadUChar( nbyte ); nElemLen=static_cast<sal_uInt16>(nbyte) & 0x00ff; + if (nElemLen>11) { + pOS2MET->SeekRel(4); + nStartIndex=ReadBigEndianWord(); + pOS2MET->SeekRel(3); + pOS2MET->ReadUChar( nbyte ); + nBytesPerCol=static_cast<sal_uInt16>(nbyte) & 0x00ff; + if (nBytesPerCol == 0) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=4; + break; + } + nEndIndex=nStartIndex+(nElemLen-11)/nBytesPerCol; + for (i=nStartIndex; i<nEndIndex; i++) { + if (nBytesPerCol > 3) pOS2MET->SeekRel(nBytesPerCol-3); + auto nCol = ReadBigEndian3BytesLong(); + SetPalette0RGB(i, nCol); + } + } + else if (nElemLen<10) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=4; + } + nPos += nElemLen; + pOS2MET->Seek(nPos); + } + break; + } + case MapColAtrMagic: + break; + case BegImgObjMagic: { + // create new bitmap by now: (will be filled later) + OSBitmap * pB=new OSBitmap; + pB->pSucc=pBitmapList; pBitmapList=pB; + pB->pBMP=nullptr; pB->nWidth=0; pB->nHeight=0; pB->nBitsPerPixel=0; + pB->nMapPos=0; + // determine ID of the bitmap: + pB->nID=0; + for (sal_uInt8 i = 0; i < 4; ++i) { + sal_uInt8 nbyte(0),nbyte2(0); + pOS2MET->ReadUChar(nbyte).ReadUChar(nbyte2); + nbyte -= 0x30; + nbyte2 -= 0x30; + nbyte = (nbyte << 4) | nbyte2; + pB->nID=(pB->nID>>8)|(static_cast<sal_uInt32>(nbyte)<<24); + } + // put new palette on the palette stack: (will be filled later) + OSPalette * pP=new OSPalette; + pP->pSucc=pPaletteStack; pPaletteStack=pP; + pP->p0RGB=nullptr; pP->nSize=0; + break; + } + case EndImgObjMagic: { + // read temporary Windows BMP file: + if (pBitmapList==nullptr || pBitmapList->pBMP==nullptr || + pBitmapList->pBMP->GetError()!=ERRCODE_NONE) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=5; + return; + } + pBitmapList->pBMP->Seek(0); + + ReadDIBBitmapEx(pBitmapList->aBitmapEx, *(pBitmapList->pBMP), false); + + if (pBitmapList->pBMP->GetError()!=ERRCODE_NONE) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=6; + } + delete pBitmapList->pBMP; pBitmapList->pBMP=nullptr; + // kill palette from stack: + OSPalette * pP=pPaletteStack; + if (pP!=nullptr) { + pPaletteStack=pP->pSucc; + delete[] pP->p0RGB; + delete pP; + } + break; + } + case DscImgObjMagic: + break; + case DatImgObjMagic: { + sal_uInt16 nDataID, nDataLen; + sal_uInt8 nbyte; + + auto nPos = pOS2MET->Tell(); + auto nMaxPos = nPos + nFieldSize; + while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) { + pOS2MET->ReadUChar( nbyte ); nDataID=static_cast<sal_uInt16>(nbyte)&0x00ff; + if (nDataID==0x00fe) { + pOS2MET->ReadUChar( nbyte ); + nDataID=(nDataID<<8)|(static_cast<sal_uInt16>(nbyte)&0x00ff); + nDataLen=ReadBigEndianWord(); + nPos+=4; + } + else { + pOS2MET->ReadUChar( nbyte ); nDataLen=static_cast<sal_uInt16>(nbyte)&0x00ff; + nPos+=2; + } + ReadImageData(nDataID, nDataLen); + nPos += nDataLen; + pOS2MET->Seek(nPos); + } + break; + } + + case BegObEnv1Magic: + break; + case EndObEnv1Magic: + break; + case BegGrfObjMagic: + break; + case EndGrfObjMagic: { + if (!xOrdFile) + break; + + auto nMaxPos = xOrdFile->Tell(); + if (!nMaxPos) + break; + + // In xOrdFile all "DatGrfObj" fields were collected so that the + // therein contained "Orders" are continuous and not segmented by fields. + // To read them from the memory stream without having any trouble, + // we use a little trick: + + SvStream *pSave = pOS2MET; + pOS2MET=xOrdFile.get(); //(!) + pOS2MET->Seek(0); + + // in a sane world this block is just: pOS2MET->SetStreamSize(nMaxPos); + if (nMaxPos) + { +#ifndef NDEBUG + const sal_uInt8 nLastByte = static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1]; +#endif + pOS2MET->SetStreamSize(nMaxPos); // shrink stream to written portion + assert(pOS2MET->remainingSize() == nMaxPos || pOS2MET->remainingSize() == nMaxPos - 1); + SAL_WARN_IF(pOS2MET->remainingSize() == nMaxPos, "filter.os2met", "this SetStreamSize workaround is no longer needed"); + // The shrink case of SvMemoryStream::ReAllocateMemory, i.e. nEndOfData = nNewSize - 1, looks buggy to me, workaround + // it by using Seek to move the nEndOfData to the sane position + if (pOS2MET->remainingSize() < nMaxPos) + { + pOS2MET->Seek(nMaxPos); + pOS2MET->Seek(0); + } + + assert(nLastByte == static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1]); + } + + assert(pOS2MET->remainingSize() == nMaxPos); + + // disable stream growing past its current size + xOrdFile->SetResizeOffset(0); + + + // "Segment header": + sal_uInt8 nbyte(0); + pOS2MET->ReadUChar( nbyte ); + if (nbyte==0x70) { // header exists + pOS2MET->SeekRel(15); // but we don't need it + } + else pOS2MET->SeekRel(-1); // no header, go back one byte + + // loop through Order: + while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) { + pOS2MET->ReadUChar( nbyte ); + sal_uInt16 nOrderID = static_cast<sal_uInt16>(nbyte) & 0x00ff; + if (nOrderID==0x00fe) { + pOS2MET->ReadUChar( nbyte ); + nOrderID=(nOrderID << 8) | (static_cast<sal_uInt16>(nbyte) & 0x00ff); + } + sal_uInt16 nOrderLen; + if (nOrderID>0x00ff || nOrderID==GOrdPolygn) { + // ooo: As written in OS2 documentation, the order length should now + // be written as big endian word. (Quote: "Highorder byte precedes loworder byte"). + // In reality there are files in which the length is stored as little endian word + // (at least for nOrderID==GOrdPolygn) + // So we throw a coin or what else can we do? + pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte)&0x00ff; + pOS2MET->ReadUChar( nbyte ); if (nbyte!=0) nOrderLen=nOrderLen<<8|(static_cast<sal_uInt16>(nbyte)&0x00ff); + } + else if (nOrderID==GOrdSTxAlg || nOrderID==GOrdPTxAlg) nOrderLen=2; + else if ((nOrderID&0xff88)==0x0008) nOrderLen=1; + else if (nOrderID==0x0000 || nOrderID==0x00ff) nOrderLen=0; + else { pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte) & 0x00ff; } + auto nPos=pOS2MET->Tell(); + ReadOrder(nOrderID, nOrderLen); + if (nPos+nOrderLen < pOS2MET->Tell()) { + SAL_INFO("filter.os2met","Order is shorter than expected. OrderID: " << nOrderID << " Position: " << nPos); + } + else if (nPos+nOrderLen != pOS2MET->Tell()) { + SAL_INFO("filter.os2met","Order was not read completely. OrderID: " << nOrderID << " Position: " << nPos); + } + pOS2MET->Seek(nPos+nOrderLen); + } + + pOS2MET=pSave; + if (xOrdFile->GetError()) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=10; + } + xOrdFile.reset(); + break; + } + case DscGrfObjMagic: { + sal_uInt16 nDscID, nDscLen; + sal_uInt8 nbyte; + + auto nMaxPos = pOS2MET->Tell() + nFieldSize; + while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) { + pOS2MET->ReadUChar( nbyte ); nDscID =static_cast<sal_uInt16>(nbyte) & 0x00ff; + pOS2MET->ReadUChar( nbyte ); nDscLen=static_cast<sal_uInt16>(nbyte) & 0x00ff; + auto nPos = pOS2MET->Tell(); + ReadDsc(nDscID); + pOS2MET->Seek(nPos+nDscLen); + } + break; + } + case DatGrfObjMagic: { + if (!xOrdFile) { + xOrdFile.reset(new SvMemoryStream); + xOrdFile->SetEndian(SvStreamEndian::LITTLE); + } + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nFieldSize]); + pOS2MET->ReadBytes(pBuf.get(), nFieldSize); + xOrdFile->WriteBytes(pBuf.get(), nFieldSize); + break; + } + case MapCodFntMagic: + ReadFont(nFieldSize); + break; + + case MapDatResMagic: + break; + } +} + +void OS2METReader::ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile ) +{ + ErrorCode=0; + + pOS2MET = &rStreamOS2MET; + auto nOrigPos = pOS2MET->Tell(); + SvStreamEndian nOrigNumberFormat = pOS2MET->GetEndian(); + + bCoord32 = true; + pPaletteStack=nullptr; + pAreaStack=nullptr; + pPathStack=nullptr; + pPathList=nullptr; + pFontList=nullptr; + pBitmapList=nullptr; + pAttrStack=nullptr; + + aDefAttr.aLinCol =COL_BLACK; + aDefAttr.aLinBgCol =COL_WHITE; + aDefAttr.eLinMix =RasterOp::OverPaint; + aDefAttr.eLinBgMix =RasterOp::OverPaint; + aDefAttr.aChrCol =COL_BLACK; + aDefAttr.aChrBgCol =COL_WHITE; + aDefAttr.eChrMix =RasterOp::OverPaint; + aDefAttr.eChrBgMix =RasterOp::OverPaint; + aDefAttr.aMrkCol =COL_BLACK; + aDefAttr.aMrkBgCol =COL_WHITE; + aDefAttr.eMrkMix =RasterOp::OverPaint; + aDefAttr.eMrkBgMix =RasterOp::OverPaint; + aDefAttr.aPatCol =COL_BLACK; + aDefAttr.aPatBgCol =COL_WHITE; + aDefAttr.ePatMix =RasterOp::OverPaint; + aDefAttr.ePatBgMix =RasterOp::OverPaint; + aDefAttr.aImgCol =COL_BLACK; + aDefAttr.aImgBgCol =COL_WHITE; + aDefAttr.eImgMix =RasterOp::OverPaint; + aDefAttr.eImgBgMix =RasterOp::OverPaint; + aDefAttr.nArcP =1; + aDefAttr.nArcQ =1; + aDefAttr.nArcR =0; + aDefAttr.nArcS =0; + aDefAttr.nChrAng =0_deg10; + aDefAttr.nChrCellHeight = 12; + aDefAttr.nChrSet =0; + aDefAttr.aCurPos =Point(0,0); + aDefAttr.eLinStyle =PEN_SOLID; + aDefAttr.nLinWidth =0; + aDefAttr.aMrkCellSize=Size(10,10); + aDefAttr.nMrkPrec =0x01; + aDefAttr.nMrkSet =0xff; + aDefAttr.nMrkSymbol =0x01; + aDefAttr.bFill =true; + aDefAttr.nStrLinWidth=0; + + aAttr=aDefAttr; + + xOrdFile.reset(); + + rGDIMetaFile.Record(pVirDev); + + pOS2MET->SetEndian(SvStreamEndian::LITTLE); + + sal_uInt64 nPos = pOS2MET->Tell(); + + for (;;) { + + sal_uInt16 nFieldSize = ReadBigEndianWord(); + sal_uInt8 nMagicByte(0); + pOS2MET->ReadUChar( nMagicByte ); + if (nMagicByte!=0xd3) { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=7; + break; + } + + sal_uInt16 nFieldType(0); + pOS2MET->ReadUInt16(nFieldType); + + pOS2MET->SeekRel(3); + + if (pOS2MET->GetError()) + break; + + if (nFieldType==EndDocumnMagic) + break; + + if (pOS2MET->eof() || nFieldSize < 8) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=8; + break; + } + + nPos+=8; nFieldSize-=8; + + if (nFieldSize > pOS2MET->remainingSize()) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=8; + break; + } + + ReadField(nFieldType, nFieldSize); + nPos += nFieldSize; + + if (pOS2MET->Tell() > nPos) + { + pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR); + ErrorCode=9; + break; + } + pOS2MET->Seek(nPos); + } + + rGDIMetaFile.Stop(); + + rGDIMetaFile.SetPrefMapMode( aGlobMapMode ); + + if( aBoundingRect.GetWidth() && aBoundingRect.GetHeight() ) + rGDIMetaFile.SetPrefSize( aBoundingRect.GetSize() ); + else + { + if( aCalcBndRect.Left() || aCalcBndRect.Top() ) + rGDIMetaFile.Move( -aCalcBndRect.Left(), -aCalcBndRect.Top() ); + + rGDIMetaFile.SetPrefSize( aCalcBndRect.GetSize() ); + } + + pOS2MET->SetEndian(nOrigNumberFormat); + + if (pOS2MET->GetError()) { + SAL_INFO("filter.os2met","Error code: " << ErrorCode); + pOS2MET->Seek(nOrigPos); + } +} + +//================== GraphicImport - the exported function ================ + +bool ImportMetGraphic(SvStream & rStream, Graphic & rGraphic) +{ + OS2METReader aOS2METReader; + GDIMetaFile aMTF; + bool bRet = false; + + try + { + aOS2METReader.ReadOS2MET( rStream, aMTF ); + + if ( !rStream.GetError() ) + { + rGraphic=Graphic( aMTF ); + bRet = true; + } + } + catch (const css::uno::Exception&) + { + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipbm/ipbm.cxx b/vcl/source/filter/ipbm/ipbm.cxx new file mode 100644 index 000000000..37736a48f --- /dev/null +++ b/vcl/source/filter/ipbm/ipbm.cxx @@ -0,0 +1,541 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <tools/stream.hxx> +#include <filter/PbmReader.hxx> + +//============================ PBMReader ================================== + +namespace { + +class PBMReader { + +private: + + SvStream& mrPBM; // the PBM file to read + + bool mbStatus; + bool mbRemark; // sal_False if the stream is in a comment + bool mbRaw; // RAW/ASCII MODE + sal_uInt8 mnMode; // 0->PBM, 1->PGM, 2->PPM + std::unique_ptr<vcl::bitmap::RawBitmap> mpRawBmp; + std::vector<Color> mvPalette; + sal_Int32 mnWidth, mnHeight; // dimensions in pixel + sal_uInt16 mnCol; + sal_uInt64 mnMaxVal; // max value in the <missing comment> + bool ImplReadBody(); + bool ImplReadHeader(); + +public: + explicit PBMReader(SvStream & rPBM); + bool ReadPBM(Graphic & rGraphic ); +}; + +} + +//=================== Methods of PBMReader ============================== + +PBMReader::PBMReader(SvStream & rPBM) + : mrPBM(rPBM) + , mbStatus(true) + , mbRemark(false) + , mbRaw(true) + , mnMode(0) + , mnWidth(0) + , mnHeight(0) + , mnCol(0) + , mnMaxVal(0) +{ +} + +bool PBMReader::ReadPBM(Graphic & rGraphic ) +{ + if ( mrPBM.GetError() ) + return false; + + mrPBM.SetEndian( SvStreamEndian::LITTLE ); + + // read header: + + mbStatus = ImplReadHeader(); + if ( !mbStatus ) + return false; + + if ( ( mnMaxVal == 0 ) || ( mnWidth <= 0 ) || ( mnHeight <= 0 ) ) + return false; + + sal_uInt32 nPixelsRequired; + if (o3tl::checked_multiply<sal_uInt32>(mnWidth, mnHeight, nPixelsRequired)) + return false; + const auto nRemainingSize = mrPBM.remainingSize(); + + // 0->PBM, 1->PGM, 2->PPM + switch ( mnMode ) + { + case 0: + { + if (nRemainingSize < nPixelsRequired / 8) + return false; + + mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) ); + mvPalette.resize( 2 ); + mvPalette[0] = Color( 0xff, 0xff, 0xff ); + mvPalette[1] = Color( 0x00, 0x00, 0x00 ); + break; + } + case 1 : + if (nRemainingSize < nPixelsRequired) + return false; + + mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) ); + mnCol = static_cast<sal_uInt16>(mnMaxVal) + 1; + if ( mnCol > 256 ) + mnCol = 256; + + mvPalette.resize( 256 ); + for ( sal_uInt16 i = 0; i < mnCol; i++ ) + { + sal_uInt16 nCount = 255 * i / mnCol; + mvPalette[i] = Color( static_cast<sal_uInt8>(nCount), static_cast<sal_uInt8>(nCount), static_cast<sal_uInt8>(nCount) ); + } + break; + case 2 : + if (nRemainingSize / 3 < nPixelsRequired) + return false; + + mpRawBmp.reset( new vcl::bitmap::RawBitmap( Size( mnWidth, mnHeight ), 24 ) ); + break; + } + + // read bitmap data + mbStatus = ImplReadBody(); + + if ( mbStatus ) + rGraphic = vcl::bitmap::CreateFromData(std::move(*mpRawBmp)); + + return mbStatus; +} + +bool PBMReader::ImplReadHeader() +{ + sal_uInt8 nID[ 2 ]; + sal_uInt8 nDat; + sal_uInt8 nMax, nCount = 0; + bool bFinished = false; + + mrPBM.ReadUChar( nID[ 0 ] ).ReadUChar( nID[ 1 ] ); + if (!mrPBM.good() || nID[0] != 'P') + return false; + mnMaxVal = mnWidth = mnHeight = 0; + switch ( nID[ 1 ] ) + { + case '1' : + mbRaw = false; + [[fallthrough]]; + case '4' : + mnMode = 0; + nMax = 2; // number of parameters in Header + mnMaxVal = 1; + break; + case '2' : + mbRaw = false; + [[fallthrough]]; + case '5' : + mnMode = 1; + nMax = 3; + break; + case '3' : + mbRaw = false; + [[fallthrough]]; + case '6' : + mnMode = 2; + nMax = 3; + break; + default: + return false; + } + while ( !bFinished ) + { + mrPBM.ReadUChar( nDat ); + + if (!mrPBM.good()) + return false; + + if ( nDat == '#' ) + { + mbRemark = true; + continue; + } + else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) ) + { + mbRemark = false; + nDat = 0x20; + } + if ( mbRemark ) + continue; + + if ( ( nDat == 0x20 ) || ( nDat == 0x09 ) ) + { + if ( ( nCount == 0 ) && mnWidth ) + nCount++; + else if ( ( nCount == 1 ) && mnHeight ) + { + if ( ++nCount == nMax ) + bFinished = true; + } + else if ( ( nCount == 2 ) && mnMaxVal ) + { + bFinished = true; + } + continue; + } + if ( ( nDat >= '0' ) && ( nDat <= '9' ) ) + { + nDat -= '0'; + if ( nCount == 0 ) + { + if (mnWidth > SAL_MAX_INT32 / 10) + { + return false; + } + mnWidth *= 10; + if (nDat > SAL_MAX_INT32 - mnWidth) + { + return false; + } + mnWidth += nDat; + } + else if ( nCount == 1 ) + { + if (mnHeight > SAL_MAX_INT32 / 10) + { + return false; + } + mnHeight *= 10; + if (nDat > SAL_MAX_INT32 - mnHeight) + { + return false; + } + mnHeight += nDat; + } + else if ( nCount == 2 ) + { + if (mnMaxVal > std::numeric_limits<sal_uInt64>::max() / 10) + { + return false; + } + mnMaxVal *= 10; + if (nDat > std::numeric_limits<sal_uInt64>::max() - mnMaxVal) + { + return false; + } + mnMaxVal += nDat; + } + } + else + return false; + } + return mbStatus; +} + +bool PBMReader::ImplReadBody() +{ + sal_uInt8 nDat = 0, nCount; + sal_uInt64 nGrey, nRGB[3]; + sal_Int32 nWidth = 0; + sal_Int32 nHeight = 0; + + if ( mbRaw ) + { + signed char nShift = 0; + switch ( mnMode ) + { + + // PBM + case 0 : + while ( nHeight != mnHeight ) + { + if (!mrPBM.good()) + return false; + + if ( --nShift < 0 ) + { + mrPBM.ReadUChar( nDat ); + nShift = 7; + } + mpRawBmp->SetPixel( nHeight, nWidth, mvPalette[(nDat >> nShift) & 0x01] ); + if ( ++nWidth == mnWidth ) + { + nShift = 0; + nWidth = 0; + nHeight++; + } + } + break; + + // PGM + case 1 : + while ( nHeight != mnHeight ) + { + if (!mrPBM.good()) + return false; + + mrPBM.ReadUChar( nDat ); + mpRawBmp->SetPixel( nHeight, nWidth++, mvPalette[nDat]); + + if ( nWidth == mnWidth ) + { + nWidth = 0; + nHeight++; + } + } + break; + + // PPM + case 2 : + while ( nHeight != mnHeight ) + { + if (!mrPBM.good()) + return false; + + sal_uInt8 nR, nG, nB; + sal_uInt8 nRed, nGreen, nBlue; + mrPBM.ReadUChar( nR ).ReadUChar( nG ).ReadUChar( nB ); + nRed = 255 * nR / mnMaxVal; + nGreen = 255 * nG / mnMaxVal; + nBlue = 255 * nB / mnMaxVal; + mpRawBmp->SetPixel( nHeight, nWidth++, Color( nRed, nGreen, nBlue ) ); + if ( nWidth == mnWidth ) + { + nWidth = 0; + nHeight++; + } + } + break; + } + } + else + { + bool bPara = false; + bool bFinished = false; + + switch ( mnMode ) + { + // PBM + case 0 : + while ( !bFinished ) + { + if (!mrPBM.good()) + return false; + + mrPBM.ReadUChar( nDat ); + + if ( nDat == '#' ) + { + mbRemark = true; + continue; + } + else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) ) + { + mbRemark = false; + continue; + } + if ( mbRemark || nDat == 0x20 || nDat == 0x09 ) + continue; + + if ( nDat == '0' || nDat == '1' ) + { + mpRawBmp->SetPixel( nHeight, nWidth, mvPalette[static_cast<sal_uInt8>(nDat - '0')] ); + nWidth++; + if ( nWidth == mnWidth ) + { + nWidth = 0; + if ( ++nHeight == mnHeight ) + bFinished = true; + } + } + else + return false; + } + break; + + // PGM + case 1 : + + bPara = false; + nCount = 0; + nGrey = 0; + + while ( !bFinished ) + { + if ( nCount ) + { + nCount--; + if ( nGrey <= mnMaxVal ) + nGrey = 255 * nGrey / mnMaxVal; + mpRawBmp->SetPixel( nHeight, nWidth++, mvPalette[static_cast<sal_uInt8>(nGrey)] ); + nGrey = 0; + if ( nWidth == mnWidth ) + { + nWidth = 0; + if ( ++nHeight == mnHeight ) + bFinished = true; + } + continue; + } + + if (!mrPBM.good()) + return false; + + mrPBM.ReadUChar( nDat ); + + if ( nDat == '#' ) + { + mbRemark = true; + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) ) + { + mbRemark = false; + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + + if ( nDat == 0x20 || nDat == 0x09 ) + { + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + if ( nDat >= '0' && nDat <= '9' ) + { + bPara = true; + nGrey *= 10; + nGrey += nDat-'0'; + continue; + } + else + return false; + } + break; + + + // PPM + case 2 : + + bPara = false; + nCount = 0; + nRGB[ 0 ] = nRGB[ 1 ] = nRGB[ 2 ] = 0; + + while ( !bFinished ) + { + if ( nCount == 3 ) + { + nCount = 0; + mpRawBmp->SetPixel( nHeight, nWidth++, Color( static_cast< sal_uInt8 >( ( nRGB[ 0 ] * 255 ) / mnMaxVal ), + static_cast< sal_uInt8 >( ( nRGB[ 1 ] * 255 ) / mnMaxVal ), + static_cast< sal_uInt8 >( ( nRGB[ 2 ] * 255 ) / mnMaxVal ) ) ); + nRGB[ 0 ] = nRGB[ 1 ] = nRGB[ 2 ] = 0; + if ( nWidth == mnWidth ) + { + nWidth = 0; + if ( ++nHeight == mnHeight ) + bFinished = true; + } + continue; + } + + if (!mrPBM.good()) + return false; + + mrPBM.ReadUChar( nDat ); + + if ( nDat == '#' ) + { + mbRemark = true; + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + else if ( ( nDat == 0x0d ) || ( nDat == 0x0a ) ) + { + mbRemark = false; + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + + if ( nDat == 0x20 || nDat == 0x09 ) + { + if ( bPara ) + { + bPara = false; + nCount++; + } + continue; + } + if ( nDat >= '0' && nDat <= '9' ) + { + bPara = true; + nRGB[ nCount ] *= 10; + nRGB[ nCount ] += nDat-'0'; + continue; + } + else + return false; + } + break; + } + } + return mbStatus; +} + +//================== GraphicImport - the exported function ================ + +bool ImportPbmGraphic( SvStream & rStream, Graphic & rGraphic) +{ + PBMReader aPBMReader(rStream); + + return aPBMReader.ReadPBM(rGraphic ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipcd/ipcd.cxx b/vcl/source/filter/ipcd/ipcd.cxx new file mode 100644 index 000000000..220ac6111 --- /dev/null +++ b/vcl/source/filter/ipcd/ipcd.cxx @@ -0,0 +1,352 @@ +/* -*- 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 <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <tools/stream.hxx> +#include <filter/PcdReader.hxx> + +//============================ PCDReader ================================== + +namespace { + +// these resolutions are contained in a PCD file: +enum PCDResolution { + PCDRES_BASE16, // 192 x 128 + PCDRES_BASE4, // 384 x 256 + PCDRES_BASE, // 768 x 512 + // the following ones are compressed + // and CANNOT be read by us + PCDRES_4BASE, // 1536 x 1024 + PCDRES_16BASE // 3072 x 3072 +}; + +class PCDReader { + +private: + + bool bStatus; + + SvStream &m_rPCD; + std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap; + + sal_uInt8 nOrientation; // orientation of the picture within the PCD file: + // 0 - spire point up + // 1 - spire points to the right + // 2 - spire points down + // 3 - spire points to the left + + PCDResolution eResolution; // which resolution we want + + sal_uInt32 nWidth; // width of the PCD picture + sal_uInt32 nHeight; // height of the PCD picture + sal_uInt32 nImagePos; // position of the picture within the PCD file + + // temporary lLue-Green-Red-Bitmap + sal_uInt32 nBMPWidth; + sal_uInt32 nBMPHeight; + + void CheckPCDImagePacFile(); + // checks whether it's a Photo-CD file with 'Image Pac' + + void ReadOrientation(); + // reads the orientation and sets nOrientation + + void ReadImage(); + +public: + + explicit PCDReader(SvStream &rStream) + : bStatus(false) + , m_rPCD(rStream) + , nOrientation(0) + , eResolution(PCDRES_BASE16) + , nWidth(0) + , nHeight(0) + , nImagePos(0) + , nBMPWidth(0) + , nBMPHeight(0) + { + } + + bool ReadPCD( Graphic & rGraphic, FilterConfigItem* pConfigItem ); +}; + +} + +//=================== Methods of PCDReader ============================== + +bool PCDReader::ReadPCD( Graphic & rGraphic, FilterConfigItem* pConfigItem ) +{ + bStatus = true; + + // is it a PCD file with a picture? ( sets bStatus == sal_False, if that's not the case): + CheckPCDImagePacFile(); + + // read orientation of the picture: + ReadOrientation(); + + // which resolution do we want?: + eResolution = PCDRES_BASE; + if ( pConfigItem ) + { + sal_Int32 nResolution = pConfigItem->ReadInt32( "Resolution", 2 ); + if ( nResolution == 1 ) + eResolution = PCDRES_BASE4; + else if ( nResolution == 0 ) + eResolution = PCDRES_BASE16; + } + // determine size and position (position within the PCD file) of the picture: + switch (eResolution) + { + case PCDRES_BASE16 : + nWidth = 192; + nHeight = 128; + nImagePos = 8192; + break; + + case PCDRES_BASE4 : + nWidth = 384; + nHeight = 256; + nImagePos = 47104; + break; + + case PCDRES_BASE : + nWidth = 768; + nHeight = 512; + nImagePos = 196608; + break; + + default: + bStatus = false; + } + if ( bStatus ) + { + if ( ( nOrientation & 0x01 ) == 0 ) + { + nBMPWidth = nWidth; + nBMPHeight = nHeight; + } + else + { + nBMPWidth = nHeight; + nBMPHeight = nWidth; + } + mpBitmap.reset(new vcl::bitmap::RawBitmap( Size( nBMPWidth, nBMPHeight ), 24 )); + + ReadImage(); + + rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap)); + } + return bStatus; +} + +void PCDReader::CheckPCDImagePacFile() +{ + char Buf[ 8 ]; + + m_rPCD.Seek( 2048 ); + m_rPCD.ReadBytes(Buf, 7); + Buf[ 7 ] = 0; + if (!m_rPCD.good() || Buf != std::string_view("PCD_IPI")) + bStatus = false; +} + +void PCDReader::ReadOrientation() +{ + if ( !bStatus ) + return; + m_rPCD.Seek( 194635 ); + m_rPCD.ReadUChar( nOrientation ); + nOrientation &= 0x03; +} + +void PCDReader::ReadImage() +{ + tools::Long nL,nCb,nCr; + + if ( !bStatus ) + return; + + sal_uInt32 nW2 = nWidth>>1; + sal_uInt32 nH2 = nHeight>>1; + + // luminance for each pixel of the 1st row of the current pair of rows + std::vector<sal_uInt8> aL0(nWidth); + // luminance for each pixel of the 2nd row of the current pair of rows + std::vector<sal_uInt8> aL1(nWidth); + // blue chrominance for each 2x2 pixel of the current pair of rows + std::vector<sal_uInt8> aCb(nW2 + 1); + // red chrominance for each 2x2 pixel of the current pair of rows + std::vector<sal_uInt8> aCr(nW2 + 1); + // like above, but for the next pair of rows + std::vector<sal_uInt8> aL0N(nWidth); + std::vector<sal_uInt8> aL1N(nWidth); + std::vector<sal_uInt8> aCbN(nW2 + 1); + std::vector<sal_uInt8> aCrN(nW2 + 1); + + sal_uInt8* pL0 = aL0.data(); + sal_uInt8* pL1 = aL1.data(); + sal_uInt8* pCb = aCb.data(); + sal_uInt8* pCr = aCr.data(); + sal_uInt8* pL0N = aL0N.data(); + sal_uInt8* pL1N = aL1N.data(); + sal_uInt8* pCbN = aCbN.data(); + sal_uInt8* pCrN = aCrN.data(); + + m_rPCD.Seek( nImagePos ); + + // next pair of rows := first pair of rows: + if (m_rPCD.ReadBytes(pL0N, nWidth) != nWidth || + m_rPCD.ReadBytes(pL1N, nWidth) != nWidth || + m_rPCD.ReadBytes(pCbN, nW2) != nW2 || + m_rPCD.ReadBytes(pCrN, nW2) != nW2) + { + bStatus = false; + return; + } + pCbN[ nW2 ] = pCbN[ nW2 - 1 ]; + pCrN[ nW2 ] = pCrN[ nW2 - 1 ]; + + for (sal_uInt32 nYPair = 0; nYPair < nH2; ++nYPair) + { + sal_uInt8 * pt; + // current pair of rows := next pair of rows: + pt=pL0; pL0=pL0N; pL0N=pt; + pt=pL1; pL1=pL1N; pL1N=pt; + pt=pCb; pCb=pCbN; pCbN=pt; + pt=pCr; pCr=pCrN; pCrN=pt; + + // get the next pair of rows: + if ( nYPair < nH2 - 1 ) + { + m_rPCD.ReadBytes( pL0N, nWidth ); + m_rPCD.ReadBytes( pL1N, nWidth ); + m_rPCD.ReadBytes( pCbN, nW2 ); + m_rPCD.ReadBytes( pCrN, nW2 ); + pCbN[nW2]=pCbN[ nW2 - 1 ]; + pCrN[nW2]=pCrN[ nW2 - 1 ]; + } + else + { + for (sal_uInt32 nXPair = 0; nXPair < nW2; ++nXPair) + { + pCbN[ nXPair ] = pCb[ nXPair ]; + pCrN[ nXPair ] = pCr[ nXPair ]; + } + } + + // loop through both rows of the pair of rows: + for (sal_uInt32 ndy = 0; ndy < 2; ++ndy) + { + sal_uInt32 ny = ( nYPair << 1 ) + ndy; + + // loop through X: + for (sal_uInt32 nx = 0; nx < nWidth; ++nx) + { + // get/calculate nL,nCb,nCr for the pixel nx,ny: + sal_uInt32 nXPair = nx >> 1; + if ( ndy == 0 ) + { + nL = static_cast<tools::Long>(pL0[ nx ]); + if (( nx & 1 ) == 0 ) + { + nCb = static_cast<tools::Long>(pCb[ nXPair ]); + nCr = static_cast<tools::Long>(pCr[ nXPair ]); + } + else + { + nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCb[ nXPair + 1 ]) ) >> 1; + nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCr[ nXPair + 1 ]) ) >> 1; + } + } + else { + nL = pL1[ nx ]; + if ( ( nx & 1 ) == 0 ) + { + nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCbN[ nXPair ]) ) >> 1; + nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCrN[ nXPair ]) ) >> 1; + } + else + { + nCb = ( static_cast<tools::Long>(pCb[ nXPair ]) + static_cast<tools::Long>(pCb[ nXPair + 1 ]) + + static_cast<tools::Long>(pCbN[ nXPair ]) + static_cast<tools::Long>(pCbN[ nXPair + 1 ]) ) >> 2; + nCr = ( static_cast<tools::Long>(pCr[ nXPair ]) + static_cast<tools::Long>(pCr[ nXPair + 1]) + + static_cast<tools::Long>(pCrN[ nXPair ]) + static_cast<tools::Long>(pCrN[ nXPair + 1 ]) ) >> 2; + } + } + // conversion of nL,nCb,nCr in nRed,nGreen,nBlue: + nL *= 89024; + nCb -= 156; + nCr -= 137; + tools::Long nRed = ( nL + nCr * 119374 + 0x8000 ) >> 16; + if ( nRed < 0 ) + nRed = 0; + if ( nRed > 255) + nRed = 255; + tools::Long nGreen = ( nL - nCb * 28198 - nCr * 60761 + 0x8000 ) >> 16; + if ( nGreen < 0 ) + nGreen = 0; + if ( nGreen > 255 ) + nGreen = 255; + tools::Long nBlue = ( nL + nCb * 145352 + 0x8000 ) >> 16; + if ( nBlue < 0 ) + nBlue = 0; + if ( nBlue > 255 ) + nBlue = 255; + + // register color value in pBMPMap: + if ( nOrientation < 2 ) + { + if ( nOrientation == 0 ) + mpBitmap->SetPixel( ny, nx, Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) ); + else + mpBitmap->SetPixel( nWidth - 1 - nx, ny, Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) ); + } + else + { + if ( nOrientation == 2 ) + mpBitmap->SetPixel( nHeight - 1 - ny, ( nWidth - 1 - nx ), Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) ); + else + mpBitmap->SetPixel( nx, ( nHeight - 1 - ny ), Color( static_cast<sal_uInt8>(nRed), static_cast<sal_uInt8>(nGreen), static_cast<sal_uInt8>(nBlue) ) ); + } + } + } + + if ( m_rPCD.GetError() ) + bStatus = false; + if ( !bStatus ) + break; + } +} + +//================== GraphicImport - the exported Function ================ + +bool ImportPcdGraphic(SvStream & rStream, Graphic & rGraphic, FilterConfigItem* pConfigItem) +{ + PCDReader aPCDReader(rStream); + return aPCDReader.ReadPCD(rGraphic, pConfigItem); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipcx/ipcx.cxx b/vcl/source/filter/ipcx/ipcx.cxx new file mode 100644 index 000000000..b1162d5ec --- /dev/null +++ b/vcl/source/filter/ipcx/ipcx.cxx @@ -0,0 +1,411 @@ +/* -*- 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 <memory> +#include <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <tools/stream.hxx> +#include <filter/PcxReader.hxx> + +class FilterConfigItem; + +//============================ PCXReader ================================== + +namespace { + +class PCXReader { + +private: + + SvStream& m_rPCX; // the PCX file to read + + std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap; + std::vector<Color> mvPalette; + sal_uInt8 nVersion; // PCX-Version + sal_uInt8 nEncoding; // compression type + sal_uInt16 nBitsPerPlanePix; // bits per plane per pixel + sal_uInt16 nPlanes; // no of planes + sal_uInt16 nBytesPerPlaneLin; // bytes per plane line + + sal_uInt32 nWidth, nHeight; // dimension in pixel + sal_uInt16 nResX, nResY; // resolution in pixel per inch or 0,0 + sal_uInt16 nDestBitsPerPixel; // bits per pixel in destination bitmap 1,4,8 or 24 + std::unique_ptr<sal_uInt8[]> + pPalette; + bool bStatus; // from now on do not read status from stream ( SJ ) + + + void ImplReadBody(); + void ImplReadPalette( unsigned int nCol ); + void ImplReadHeader(); + +public: + explicit PCXReader(SvStream &rStream); + bool ReadPCX(Graphic & rGraphic ); + // Reads a PCX file from the stream and fills the GDIMetaFile +}; + +} + +//=================== methods of PCXReader ============================== + +PCXReader::PCXReader(SvStream &rStream) + : m_rPCX(rStream) + , nVersion(0) + , nEncoding(0) + , nBitsPerPlanePix(0) + , nPlanes(0) + , nBytesPerPlaneLin(0) + , nWidth(0) + , nHeight(0) + , nResX(0) + , nResY(0) + , nDestBitsPerPixel(0) + , pPalette(new sal_uInt8[ 768 ]) + , bStatus(false) +{ +} + +bool PCXReader::ReadPCX(Graphic & rGraphic) +{ + if ( m_rPCX.GetError() ) + return false; + + m_rPCX.SetEndian(SvStreamEndian::LITTLE); + + // read header: + + bStatus = true; + + ImplReadHeader(); + + // sanity check there is enough data before trying allocation + if (bStatus && nBytesPerPlaneLin > m_rPCX.remainingSize() / nPlanes) + { + bStatus = false; + } + + if (bStatus) + { + sal_uInt32 nResult; + bStatus = !o3tl::checked_multiply(nWidth, nHeight, nResult) && nResult <= SAL_MAX_INT32/2/3; + } + + // Write BMP header and conditionally (maybe invalid for now) color palette: + if (bStatus) + { + mpBitmap.reset( new vcl::bitmap::RawBitmap( Size( nWidth, nHeight ), 24 ) ); + + if ( nDestBitsPerPixel <= 8 ) + { + sal_uInt16 nColors = 1 << nDestBitsPerPixel; + sal_uInt8* pPal = pPalette.get(); + mvPalette.resize( nColors ); + for ( sal_uInt16 i = 0; i < nColors; i++, pPal += 3 ) + { + mvPalette[i] = Color( pPal[ 0 ], pPal[ 1 ], pPal[ 2 ] ); + } + } + + // read bitmap data + ImplReadBody(); + + // If an extended color palette exists at the end of the file, then read it and + // and write again in palette: + if ( nDestBitsPerPixel == 8 && bStatus ) + { + sal_uInt8* pPal = pPalette.get(); + m_rPCX.SeekRel(1); + ImplReadPalette(256); + mvPalette.resize( 256 ); + for ( sal_uInt16 i = 0; i < 256; i++, pPal += 3 ) + { + mvPalette[i] = Color( pPal[ 0 ], pPal[ 1 ], pPal[ 2 ] ); + } + } + + if ( bStatus ) + { + rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap)); + return true; + } + } + return false; +} + +void PCXReader::ImplReadHeader() +{ + sal_uInt8 nbyte(0); + m_rPCX.ReadUChar( nbyte ).ReadUChar( nVersion ).ReadUChar( nEncoding ); + if ( nbyte!=0x0a || (nVersion != 0 && nVersion != 2 && nVersion != 3 && nVersion != 5) || nEncoding > 1 ) + { + bStatus = false; + return; + } + + nbyte = 0; + m_rPCX.ReadUChar( nbyte ); nBitsPerPlanePix = static_cast<sal_uInt16>(nbyte); + sal_uInt16 nMinX(0),nMinY(0),nMaxX(0),nMaxY(0); + m_rPCX.ReadUInt16( nMinX ).ReadUInt16( nMinY ).ReadUInt16( nMaxX ).ReadUInt16( nMaxY ); + + if ((nMinX > nMaxX) || (nMinY > nMaxY)) + { + bStatus = false; + return; + } + + nWidth = nMaxX-nMinX+1; + nHeight = nMaxY-nMinY+1; + + m_rPCX.ReadUInt16( nResX ); + m_rPCX.ReadUInt16( nResY ); + if ( nResX >= nWidth || nResY >= nHeight || ( nResX != nResY ) ) + nResX = nResY = 0; + + ImplReadPalette( 16 ); + + m_rPCX.SeekRel( 1 ); + nbyte = 0; + m_rPCX.ReadUChar( nbyte ); nPlanes = static_cast<sal_uInt16>(nbyte); + sal_uInt16 nushort(0); + m_rPCX.ReadUInt16( nushort ); nBytesPerPlaneLin = nushort; + sal_uInt16 nPaletteInfo; + m_rPCX.ReadUInt16( nPaletteInfo ); + + m_rPCX.SeekRel( 58 ); + + nDestBitsPerPixel = nBitsPerPlanePix * nPlanes; + if (nDestBitsPerPixel == 2 || nDestBitsPerPixel == 3) nDestBitsPerPixel = 4; + + if ( ( nDestBitsPerPixel != 1 && nDestBitsPerPixel != 4 && nDestBitsPerPixel != 8 && nDestBitsPerPixel != 24 ) + || nPlanes > 4 || nBytesPerPlaneLin < ( ( nWidth * nBitsPerPlanePix+7 ) >> 3 ) ) + { + bStatus = false; + return; + } + + // If the bitmap has only 2 colors, the palette is most often invalid and it is always(?) + // a black and white image: + if ( nPlanes == 1 && nBitsPerPlanePix == 1 ) + { + pPalette[ 0 ] = pPalette[ 1 ] = pPalette[ 2 ] = 0x00; + pPalette[ 3 ] = pPalette[ 4 ] = pPalette[ 5 ] = 0xff; + } +} + +void PCXReader::ImplReadBody() +{ + std::unique_ptr<sal_uInt8[]> pPlane[ 4 ]; + sal_uInt8 * pDest; + sal_uInt32 i, ny, nLastPercent = 0, nPercent; + sal_uInt16 nCount, nx; + sal_uInt8 nDat = 0, nCol = 0; + + for (sal_uInt16 np = 0; np < nPlanes; ++np) + pPlane[np].reset(new sal_uInt8[nBytesPerPlaneLin]()); + + nCount = 0; + for ( ny = 0; ny < nHeight; ny++ ) + { + if (!m_rPCX.good()) + { + bStatus = false; + break; + } + nPercent = ny * 60 / nHeight + 10; + if ( ny == 0 || nLastPercent + 4 <= nPercent ) + { + nLastPercent = nPercent; + } + for (sal_uInt16 np = 0; np < nPlanes; ++np) + { + if ( nEncoding == 0) + m_rPCX.ReadBytes( static_cast<void *>(pPlane[ np ].get()), nBytesPerPlaneLin ); + else + { + pDest = pPlane[ np ].get(); + nx = nBytesPerPlaneLin; + while ( nCount > 0 && nx > 0) + { + *(pDest++) = nDat; + nx--; + nCount--; + } + while (nx > 0 && m_rPCX.good()) + { + m_rPCX.ReadUChar( nDat ); + if ( ( nDat & 0xc0 ) == 0xc0 ) + { + nCount =static_cast<sal_uInt64>(nDat) & 0x003f; + m_rPCX.ReadUChar( nDat ); + if ( nCount < nx ) + { + nx -= nCount; + while ( nCount > 0) + { + *(pDest++) = nDat; + nCount--; + } + } + else + { + nCount -= nx; + do + { + *(pDest++) = nDat; + nx--; + } + while ( nx > 0 ); + break; + } + } + else + { + *(pDest++) = nDat; + nx--; + } + } + } + } + sal_uInt8 *pSource1 = pPlane[ 0 ].get(); + sal_uInt8 *pSource2 = pPlane[ 1 ].get(); + sal_uInt8 *pSource3 = pPlane[ 2 ].get(); + sal_uInt8 *pSource4 = pPlane[ 3 ].get(); + switch ( nBitsPerPlanePix + ( nPlanes << 8 ) ) + { + // 2 colors + case 0x101 : + for ( i = 0; i < nWidth; i++ ) + { + sal_uInt32 nShift = ( i & 7 ) ^ 7; + if ( nShift == 0 ) + mpBitmap->SetPixel( ny, i, mvPalette[*(pSource1++) & 1] ); + else + mpBitmap->SetPixel( ny, i, mvPalette[(*pSource1 >> nShift ) & 1] ); + } + break; + // 4 colors + case 0x102 : + for ( i = 0; i < nWidth; i++ ) + { + switch( i & 3 ) + { + case 0 : + nCol = *pSource1 >> 6; + break; + case 1 : + nCol = ( *pSource1 >> 4 ) & 0x03 ; + break; + case 2 : + nCol = ( *pSource1 >> 2 ) & 0x03; + break; + case 3 : + nCol = ( *pSource1++ ) & 0x03; + break; + } + mpBitmap->SetPixel( ny, i, mvPalette[nCol] ); + } + break; + // 256 colors + case 0x108 : + for ( i = 0; i < nWidth; i++ ) + { + mpBitmap->SetPixel( ny, i, mvPalette[*pSource1++] ); + } + break; + // 8 colors + case 0x301 : + for ( i = 0; i < nWidth; i++ ) + { + sal_uInt32 nShift = ( i & 7 ) ^ 7; + if ( nShift == 0 ) + { + nCol = ( *pSource1++ & 1) + ( ( *pSource2++ << 1 ) & 2 ) + ( ( *pSource3++ << 2 ) & 4 ); + mpBitmap->SetPixel( ny, i, mvPalette[nCol] ); + } + else + { + nCol = sal::static_int_cast< sal_uInt8 >( + ( ( *pSource1 >> nShift ) & 1) + ( ( ( *pSource2 >> nShift ) << 1 ) & 2 ) + + ( ( ( *pSource3 >> nShift ) << 2 ) & 4 )); + mpBitmap->SetPixel( ny, i, mvPalette[nCol] ); + } + } + break; + // 16 colors + case 0x401 : + for ( i = 0; i < nWidth; i++ ) + { + sal_uInt32 nShift = ( i & 7 ) ^ 7; + if ( nShift == 0 ) + { + nCol = ( *pSource1++ & 1) + ( ( *pSource2++ << 1 ) & 2 ) + ( ( *pSource3++ << 2 ) & 4 ) + + ( ( *pSource4++ << 3 ) & 8 ); + mpBitmap->SetPixel( ny, i, mvPalette[nCol] ); + } + else + { + nCol = sal::static_int_cast< sal_uInt8 >( + ( ( *pSource1 >> nShift ) & 1) + ( ( ( *pSource2 >> nShift ) << 1 ) & 2 ) + + ( ( ( *pSource3 >> nShift ) << 2 ) & 4 ) + ( ( ( *pSource4 >> nShift ) << 3 ) & 8 )); + mpBitmap->SetPixel( ny, i, mvPalette[nCol] ); + } + } + break; + // 16m colors + case 0x308 : + for ( i = 0; i < nWidth; i++ ) + { + mpBitmap->SetPixel( ny, i, Color( *pSource1++, *pSource2++, *pSource3++ ) ); + + } + break; + default : + bStatus = false; + break; + } + } +} + +void PCXReader::ImplReadPalette( unsigned int nCol ) +{ + sal_uInt8 r, g, b; + sal_uInt8* pPtr = pPalette.get(); + for ( unsigned int i = 0; i < nCol; i++ ) + { + m_rPCX.ReadUChar( r ).ReadUChar( g ).ReadUChar( b ); + *pPtr++ = r; + *pPtr++ = g; + *pPtr++ = b; + } +} + +//================== GraphicImport - the exported function ================ + +bool ImportPcxGraphic(SvStream & rStream, Graphic & rGraphic) +{ + PCXReader aPCXReader(rStream); + bool bRetValue = aPCXReader.ReadPCX(rGraphic); + if ( !bRetValue ) + rStream.SetError( SVSTREAM_FILEFORMAT_ERROR ); + return bRetValue; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipdf/pdfcompat.cxx b/vcl/source/filter/ipdf/pdfcompat.cxx new file mode 100644 index 000000000..62413e585 --- /dev/null +++ b/vcl/source/filter/ipdf/pdfcompat.cxx @@ -0,0 +1,114 @@ +/* -*- 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 <pdf/pdfcompat.hxx> + +#include <o3tl/string_view.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> +#include <sal/log.hxx> + +namespace vcl::pdf +{ +/// Decide if PDF data is old enough to be compatible. +bool isCompatible(SvStream& rInStream, sal_uInt64 nPos, sal_uInt64 nSize) +{ + if (nSize < 8) + return false; + + // %PDF-x.y + sal_uInt8 aFirstBytes[8]; + rInStream.Seek(nPos); + sal_uLong nRead = rInStream.ReadBytes(aFirstBytes, 8); + if (nRead < 8) + return false; + + if (aFirstBytes[0] != '%' || aFirstBytes[1] != 'P' || aFirstBytes[2] != 'D' + || aFirstBytes[3] != 'F' || aFirstBytes[4] != '-') + return false; + + sal_Int32 nMajor = o3tl::toInt32(std::string_view(reinterpret_cast<char*>(&aFirstBytes[5]), 1)); + sal_Int32 nMinor = o3tl::toInt32(std::string_view(reinterpret_cast<char*>(&aFirstBytes[7]), 1)); + return !(nMajor > 1 || (nMajor == 1 && nMinor > 6)); +} + +/// Converts to highest supported format version (1.6). +/// Usually used to deal with missing referenced objects in source +/// pdf stream. +bool convertToHighestSupported(SvStream& rInStream, SvStream& rOutStream) +{ + sal_uInt64 nPos = STREAM_SEEK_TO_BEGIN; + sal_uInt64 nSize = STREAM_SEEK_TO_END; + rInStream.Seek(nPos); + // Convert to PDF-1.6. + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + return false; + + // Read input into a buffer. + SvMemoryStream aInBuffer; + aInBuffer.WriteStream(rInStream, nSize); + + SvMemoryStream aSaved; + { + // Load the buffer using pdfium. + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument + = pPdfium->openDocument(aInBuffer.GetData(), aInBuffer.GetSize(), OString()); + if (!pPdfDocument) + return false; + + // 16 means PDF-1.6. + if (!pPdfDocument->saveWithVersion(aSaved, 16)) + return false; + } + + aSaved.Seek(STREAM_SEEK_TO_BEGIN); + rOutStream.WriteStream(aSaved); + + return rOutStream.good(); +} + +/// Takes care of transparently downgrading the version of the PDF stream in +/// case it's too new for our PDF export. +bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream) +{ + sal_uInt64 nPos = STREAM_SEEK_TO_BEGIN; + sal_uInt64 nSize = STREAM_SEEK_TO_END; + bool bCompatible = isCompatible(rInStream, nPos, nSize); + rInStream.Seek(nPos); + if (bCompatible) + // Not converting. + rOutStream.WriteStream(rInStream, nSize); + else + convertToHighestSupported(rInStream, rOutStream); + + return rOutStream.good(); +} + +BinaryDataContainer createBinaryDataContainer(SvStream& rStream) +{ + // Save the original PDF stream for later use. + SvMemoryStream aMemoryStream; + if (!getCompatibleStream(rStream, aMemoryStream)) + return {}; + + const sal_uInt32 nStreamLength = aMemoryStream.TellEnd(); + + auto aPdfData = std::make_unique<std::vector<sal_uInt8>>(nStreamLength); + + aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN); + aMemoryStream.ReadBytes(aPdfData->data(), aPdfData->size()); + if (aMemoryStream.GetError()) + return {}; + + return { std::move(aPdfData) }; +} + +} // end vcl::filter::ipdf namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx new file mode 100644 index 000000000..a93083ce8 --- /dev/null +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -0,0 +1,3325 @@ +/* -*- 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 <vcl/filter/pdfdocument.hxx> +#include <pdf/pdfcompat.hxx> +#include <config_features.h> + +#include <map> +#include <memory> +#include <vector> + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/security/XCertificate.hpp> + +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/character.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <svl/cryptosign.hxx> +#include <tools/zcodec.hxx> +#include <vcl/pdfwriter.hxx> +#include <o3tl/safeint.hxx> + +#include <pdf/objectcopier.hxx> + +using namespace com::sun::star; + +namespace vcl::filter +{ +XRefEntry::XRefEntry() = default; + +PDFDocument::PDFDocument() = default; + +PDFDocument::~PDFDocument() = default; + +bool PDFDocument::RemoveSignature(size_t nPosition) +{ + std::vector<PDFObjectElement*> aSignatures = GetSignatureWidgets(); + if (nPosition >= aSignatures.size()) + { + SAL_WARN("vcl.filter", "PDFDocument::RemoveSignature: invalid nPosition"); + return false; + } + + if (aSignatures.size() != m_aEOFs.size() - 1) + { + SAL_WARN("vcl.filter", "PDFDocument::RemoveSignature: no 1:1 mapping between signatures " + "and incremental updates"); + return false; + } + + // The EOF offset is the end of the original file, without the signature at + // nPosition. + m_aEditBuffer.Seek(m_aEOFs[nPosition]); + // Drop all bytes after the current position. + m_aEditBuffer.SetStreamSize(m_aEditBuffer.Tell() + 1); + + return m_aEditBuffer.good(); +} + +sal_Int32 PDFDocument::createObject() +{ + sal_Int32 nObject = m_aXRef.size(); + m_aXRef[nObject] = XRefEntry(); + return nObject; +} + +bool PDFDocument::updateObject(sal_Int32 nObject) +{ + if (o3tl::make_unsigned(nObject) >= m_aXRef.size()) + { + SAL_WARN("vcl.filter", "PDFDocument::updateObject: invalid nObject"); + return false; + } + + XRefEntry aEntry; + aEntry.SetOffset(m_aEditBuffer.Tell()); + aEntry.SetDirty(true); + m_aXRef[nObject] = aEntry; + return true; +} + +bool PDFDocument::writeBuffer(const void* pBuffer, sal_uInt64 nBytes) +{ + std::size_t nWritten = m_aEditBuffer.WriteBytes(pBuffer, nBytes); + return nWritten == nBytes; +} + +void PDFDocument::SetSignatureLine(std::vector<sal_Int8>&& rSignatureLine) +{ + m_aSignatureLine = std::move(rSignatureLine); +} + +void PDFDocument::SetSignaturePage(size_t nPage) { m_nSignaturePage = nPage; } + +sal_uInt32 PDFDocument::GetNextSignature() +{ + sal_uInt32 nRet = 0; + for (const auto& pSignature : GetSignatureWidgets()) + { + auto pT = dynamic_cast<PDFLiteralStringElement*>(pSignature->Lookup("T")); + if (!pT) + continue; + + const OString& rValue = pT->GetValue(); + const OString aPrefix = "Signature"; + if (!rValue.startsWith(aPrefix)) + continue; + + nRet = std::max(nRet, o3tl::toUInt32(rValue.subView(aPrefix.getLength()))); + } + + return nRet + 1; +} + +sal_Int32 PDFDocument::WriteSignatureObject(const OUString& rDescription, bool bAdES, + sal_uInt64& rLastByteRangeOffset, + sal_Int64& rContentOffset) +{ + // Write signature object. + sal_Int32 nSignatureId = m_aXRef.size(); + XRefEntry aSignatureEntry; + aSignatureEntry.SetOffset(m_aEditBuffer.Tell()); + aSignatureEntry.SetDirty(true); + m_aXRef[nSignatureId] = aSignatureEntry; + OStringBuffer aSigBuffer; + aSigBuffer.append(nSignatureId); + aSigBuffer.append(" 0 obj\n"); + aSigBuffer.append("<</Contents <"); + rContentOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength(); + // Reserve space for the PKCS#7 object. + OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH); + comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0'); + aSigBuffer.append(aContentFiller); + aSigBuffer.append(">\n/Type/Sig/SubFilter"); + if (bAdES) + aSigBuffer.append("/ETSI.CAdES.detached"); + else + aSigBuffer.append("/adbe.pkcs7.detached"); + + // Time of signing. + aSigBuffer.append(" /M ("); + aSigBuffer.append(vcl::PDFWriter::GetDateTime()); + aSigBuffer.append(")"); + + // Byte range: we can write offset1-length1 and offset2 right now, will + // write length2 later. + aSigBuffer.append(" /ByteRange [ 0 "); + // -1 and +1 is the leading "<" and the trailing ">" around the hex string. + aSigBuffer.append(rContentOffset - 1); + aSigBuffer.append(" "); + aSigBuffer.append(rContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); + aSigBuffer.append(" "); + rLastByteRangeOffset = aSignatureEntry.GetOffset() + aSigBuffer.getLength(); + // We don't know how many bytes we need for the last ByteRange value, this + // should be enough. + OStringBuffer aByteRangeFiller; + comphelper::string::padToLength(aByteRangeFiller, 100, ' '); + aSigBuffer.append(aByteRangeFiller); + // Finish the Sig obj. + aSigBuffer.append(" /Filter/Adobe.PPKMS"); + + if (!rDescription.isEmpty()) + { + aSigBuffer.append("/Reason<"); + vcl::PDFWriter::AppendUnicodeTextString(rDescription, aSigBuffer); + aSigBuffer.append(">"); + } + + aSigBuffer.append(" >>\nendobj\n\n"); + m_aEditBuffer.WriteOString(aSigBuffer); + + return nSignatureId; +} + +sal_Int32 PDFDocument::WriteAppearanceObject(tools::Rectangle& rSignatureRectangle) +{ + PDFDocument aPDFDocument; + filter::PDFObjectElement* pPage = nullptr; + std::vector<filter::PDFObjectElement*> aContentStreams; + + if (!m_aSignatureLine.empty()) + { + // Parse the PDF data of signature line: we can set the signature rectangle to non-empty + // based on it. + SvMemoryStream aPDFStream; + aPDFStream.WriteBytes(m_aSignatureLine.data(), m_aSignatureLine.size()); + aPDFStream.Seek(0); + if (!aPDFDocument.Read(aPDFStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::WriteAppearanceObject: failed to read the PDF document"); + return -1; + } + + std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages(); + if (aPages.empty()) + { + SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no pages"); + return -1; + } + + pPage = aPages[0]; + if (!pPage) + { + SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no page"); + return -1; + } + + // Calculate the bounding box. + PDFElement* pMediaBox = pPage->Lookup("MediaBox"); + auto pMediaBoxArray = dynamic_cast<PDFArrayElement*>(pMediaBox); + if (!pMediaBoxArray || pMediaBoxArray->GetElements().size() < 4) + { + SAL_WARN("vcl.filter", + "PDFDocument::WriteAppearanceObject: MediaBox is not an array of 4"); + return -1; + } + const std::vector<PDFElement*>& rMediaBoxElements = pMediaBoxArray->GetElements(); + auto pWidth = dynamic_cast<PDFNumberElement*>(rMediaBoxElements[2]); + if (!pWidth) + { + SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: MediaBox has no width"); + return -1; + } + rSignatureRectangle.setWidth(pWidth->GetValue()); + auto pHeight = dynamic_cast<PDFNumberElement*>(rMediaBoxElements[3]); + if (!pHeight) + { + SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: MediaBox has no height"); + return -1; + } + rSignatureRectangle.setHeight(pHeight->GetValue()); + + if (PDFObjectElement* pContentStream = pPage->LookupObject("Contents")) + { + aContentStreams.push_back(pContentStream); + } + + if (aContentStreams.empty()) + { + SAL_WARN("vcl.filter", "PDFDocument::WriteAppearanceObject: no content stream"); + return -1; + } + } + m_aSignatureLine.clear(); + + // Write appearance object: allocate an ID. + sal_Int32 nAppearanceId = m_aXRef.size(); + m_aXRef[nAppearanceId] = XRefEntry(); + + // Write the object content. + SvMemoryStream aEditBuffer; + aEditBuffer.WriteUInt32AsString(nAppearanceId); + aEditBuffer.WriteCharPtr(" 0 obj\n"); + aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n"); + + PDFObjectCopier aCopier(*this); + if (!aContentStreams.empty()) + { + assert(pPage && "aContentStreams is only filled if there was a pPage"); + OStringBuffer aBuffer; + aCopier.copyPageResources(pPage, aBuffer); + aEditBuffer.WriteOString(aBuffer); + } + + aEditBuffer.WriteCharPtr("/BBox[0 0 "); + aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getWidth())); + aEditBuffer.WriteCharPtr(" "); + aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getHeight())); + aEditBuffer.WriteCharPtr("]\n/Length "); + + // Add the object to the doc-level edit buffer and update the offset. + SvMemoryStream aStream; + bool bCompressed = false; + sal_Int32 nLength = 0; + if (!aContentStreams.empty()) + { + nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed); + } + aEditBuffer.WriteOString(OString::number(nLength)); + if (bCompressed) + { + aEditBuffer.WriteOString(" /Filter/FlateDecode"); + } + + aEditBuffer.WriteCharPtr("\n>>\n"); + + aEditBuffer.WriteCharPtr("stream\n"); + + // Copy the original page streams to the form XObject stream. + aStream.Seek(0); + aEditBuffer.WriteStream(aStream); + + aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n"); + + aEditBuffer.Seek(0); + XRefEntry aAppearanceEntry; + aAppearanceEntry.SetOffset(m_aEditBuffer.Tell()); + aAppearanceEntry.SetDirty(true); + m_aXRef[nAppearanceId] = aAppearanceEntry; + m_aEditBuffer.WriteStream(aEditBuffer); + + return nAppearanceId; +} + +sal_Int32 PDFDocument::WriteAnnotObject(PDFObjectElement const& rFirstPage, sal_Int32 nSignatureId, + sal_Int32 nAppearanceId, + const tools::Rectangle& rSignatureRectangle) +{ + // Decide what identifier to use for the new signature. + sal_uInt32 nNextSignature = GetNextSignature(); + + // Write the Annot object, references nSignatureId and nAppearanceId. + sal_Int32 nAnnotId = m_aXRef.size(); + XRefEntry aAnnotEntry; + aAnnotEntry.SetOffset(m_aEditBuffer.Tell()); + aAnnotEntry.SetDirty(true); + m_aXRef[nAnnotId] = aAnnotEntry; + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n"); + m_aEditBuffer.WriteCharPtr("/Rect[0 0 "); + m_aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getWidth())); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteOString(OString::number(rSignatureRectangle.getHeight())); + m_aEditBuffer.WriteCharPtr("]\n"); + m_aEditBuffer.WriteCharPtr("/FT/Sig\n"); + m_aEditBuffer.WriteCharPtr("/P "); + m_aEditBuffer.WriteUInt32AsString(rFirstPage.GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" 0 R\n"); + m_aEditBuffer.WriteCharPtr("/T(Signature"); + m_aEditBuffer.WriteUInt32AsString(nNextSignature); + m_aEditBuffer.WriteCharPtr(")\n"); + m_aEditBuffer.WriteCharPtr("/V "); + m_aEditBuffer.WriteUInt32AsString(nSignatureId); + m_aEditBuffer.WriteCharPtr(" 0 R\n"); + m_aEditBuffer.WriteCharPtr("/DV "); + m_aEditBuffer.WriteUInt32AsString(nSignatureId); + m_aEditBuffer.WriteCharPtr(" 0 R\n"); + m_aEditBuffer.WriteCharPtr("/AP<<\n/N "); + m_aEditBuffer.WriteUInt32AsString(nAppearanceId); + m_aEditBuffer.WriteCharPtr(" 0 R\n>>\n"); + m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); + + return nAnnotId; +} + +bool PDFDocument::WritePageObject(PDFObjectElement& rFirstPage, sal_Int32 nAnnotId) +{ + PDFElement* pAnnots = rFirstPage.Lookup("Annots"); + auto pAnnotsReference = dynamic_cast<PDFReferenceElement*>(pAnnots); + if (pAnnotsReference) + { + // Write the updated Annots key of the Page object. + PDFObjectElement* pAnnotsObject = pAnnotsReference->LookupObject(); + if (!pAnnotsObject) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid Annots reference"); + return false; + } + + sal_uInt32 nAnnotsId = pAnnotsObject->GetObjectValue(); + m_aXRef[nAnnotsId].SetType(XRefEntryType::NOT_COMPRESSED); + m_aXRef[nAnnotsId].SetOffset(m_aEditBuffer.Tell()); + m_aXRef[nAnnotsId].SetDirty(true); + m_aEditBuffer.WriteUInt32AsString(nAnnotsId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n["); + + // Write existing references. + PDFArrayElement* pArray = pAnnotsObject->GetArray(); + if (!pArray) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: Page Annots is a reference to a non-array"); + return false; + } + + for (size_t i = 0; i < pArray->GetElements().size(); ++i) + { + auto pReference = dynamic_cast<PDFReferenceElement*>(pArray->GetElements()[i]); + if (!pReference) + continue; + + if (i) + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pReference->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" 0 R"); + } + // Write our reference. + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R"); + + m_aEditBuffer.WriteCharPtr("]\nendobj\n\n"); + } + else + { + // Write the updated first page object, references nAnnotId. + sal_uInt32 nFirstPageId = rFirstPage.GetObjectValue(); + if (nFirstPageId >= m_aXRef.size()) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid first page obj id"); + return false; + } + m_aXRef[nFirstPageId].SetOffset(m_aEditBuffer.Tell()); + m_aXRef[nFirstPageId].SetDirty(true); + m_aEditBuffer.WriteUInt32AsString(nFirstPageId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + m_aEditBuffer.WriteCharPtr("<<"); + auto pAnnotsArray = dynamic_cast<PDFArrayElement*>(pAnnots); + if (!pAnnotsArray) + { + // No Annots key, just write the key with a single reference. + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + rFirstPage.GetDictionaryOffset(), + rFirstPage.GetDictionaryLength()); + m_aEditBuffer.WriteCharPtr("/Annots["); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R]"); + } + else + { + // Annots key is already there, insert our reference at the end. + PDFDictionaryElement* pDictionary = rFirstPage.GetDictionary(); + + // Offset right before the end of the Annots array. + sal_uInt64 nAnnotsEndOffset = pDictionary->GetKeyOffset("Annots") + + pDictionary->GetKeyValueLength("Annots") - 1; + // Length of beginning of the dictionary -> Annots end. + sal_uInt64 nAnnotsBeforeEndLength = nAnnotsEndOffset - rFirstPage.GetDictionaryOffset(); + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + rFirstPage.GetDictionaryOffset(), + nAnnotsBeforeEndLength); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R"); + // Length of Annots end -> end of the dictionary. + sal_uInt64 nAnnotsAfterEndLength = rFirstPage.GetDictionaryOffset() + + rFirstPage.GetDictionaryLength() + - nAnnotsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + nAnnotsEndOffset, + nAnnotsAfterEndLength); + } + m_aEditBuffer.WriteCharPtr(">>"); + m_aEditBuffer.WriteCharPtr("\nendobj\n\n"); + } + + return true; +} + +bool PDFDocument::WriteCatalogObject(sal_Int32 nAnnotId, PDFReferenceElement*& pRoot) +{ + if (m_pXRefStream) + pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root")); + else + { + if (!m_pTrailer) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: found no trailer"); + return false; + } + pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root")); + } + if (!pRoot) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: trailer has no root reference"); + return false; + } + PDFObjectElement* pCatalog = pRoot->LookupObject(); + if (!pCatalog) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog reference"); + return false; + } + sal_uInt32 nCatalogId = pCatalog->GetObjectValue(); + if (nCatalogId >= m_aXRef.size()) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid catalog obj id"); + return false; + } + PDFElement* pAcroForm = pCatalog->Lookup("AcroForm"); + auto pAcroFormReference = dynamic_cast<PDFReferenceElement*>(pAcroForm); + if (pAcroFormReference) + { + // Write the updated AcroForm key of the Catalog object. + PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject(); + if (!pAcroFormObject) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: invalid AcroForm reference"); + return false; + } + + sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue(); + m_aXRef[nAcroFormId].SetType(XRefEntryType::NOT_COMPRESSED); + m_aXRef[nAcroFormId].SetOffset(m_aEditBuffer.Tell()); + m_aXRef[nAcroFormId].SetDirty(true); + m_aEditBuffer.WriteUInt32AsString(nAcroFormId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + + // If this is nullptr, then the AcroForm object is not in an object stream. + SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer(); + + if (!pAcroFormObject->Lookup("Fields")) + { + SAL_WARN("vcl.filter", + "PDFDocument::Sign: AcroForm object without required Fields key"); + return false; + } + + PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary(); + if (!pAcroFormDictionary) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm object has no dictionary"); + return false; + } + + // Offset right before the end of the Fields array. + sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + + pAcroFormDictionary->GetKeyValueLength("Fields") + - strlen("]"); + + // Length of beginning of the object dictionary -> Fields end. + sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset; + if (pStreamBuffer) + m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength); + else + { + nFieldsBeforeEndLength -= pAcroFormObject->GetDictionaryOffset(); + m_aEditBuffer.WriteCharPtr("<<"); + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + pAcroFormObject->GetDictionaryOffset(), + nFieldsBeforeEndLength); + } + + // Append our reference at the end of the Fields array. + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R"); + + // Length of Fields end -> end of the object dictionary. + if (pStreamBuffer) + { + sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(pStreamBuffer->GetData()) + + nFieldsEndOffset, + nFieldsAfterEndLength); + } + else + { + sal_uInt64 nFieldsAfterEndLength = pAcroFormObject->GetDictionaryOffset() + + pAcroFormObject->GetDictionaryLength() + - nFieldsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + nFieldsEndOffset, + nFieldsAfterEndLength); + m_aEditBuffer.WriteCharPtr(">>"); + } + + m_aEditBuffer.WriteCharPtr("\nendobj\n\n"); + } + else + { + // Write the updated Catalog object, references nAnnotId. + auto pAcroFormDictionary = dynamic_cast<PDFDictionaryElement*>(pAcroForm); + m_aXRef[nCatalogId].SetOffset(m_aEditBuffer.Tell()); + m_aXRef[nCatalogId].SetDirty(true); + m_aEditBuffer.WriteUInt32AsString(nCatalogId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + m_aEditBuffer.WriteCharPtr("<<"); + if (!pAcroFormDictionary) + { + // No AcroForm key, assume no signatures. + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + pCatalog->GetDictionaryOffset(), + pCatalog->GetDictionaryLength()); + m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n"); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n"); + } + else + { + // AcroForm key is already there, insert our reference at the Fields end. + auto it = pAcroFormDictionary->GetItems().find("Fields"); + if (it == pAcroFormDictionary->GetItems().end()) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm without required Fields key"); + return false; + } + + auto pFields = dynamic_cast<PDFArrayElement*>(it->second); + if (!pFields) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: AcroForm Fields is not an array"); + return false; + } + + // Offset right before the end of the Fields array. + sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + + pAcroFormDictionary->GetKeyValueLength("Fields") - 1; + // Length of beginning of the Catalog dictionary -> Fields end. + sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset(); + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + pCatalog->GetDictionaryOffset(), + nFieldsBeforeEndLength); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R"); + // Length of Fields end -> end of the Catalog dictionary. + sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + + pCatalog->GetDictionaryLength() - nFieldsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + + nFieldsEndOffset, + nFieldsAfterEndLength); + } + m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); + } + + return true; +} + +void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* pRoot) +{ + if (m_pXRefStream) + { + // Write the xref stream. + // This is a bit meta: the xref stream stores its own offset. + sal_Int32 nXRefStreamId = m_aXRef.size(); + XRefEntry aXRefStreamEntry; + aXRefStreamEntry.SetOffset(nXRefOffset); + aXRefStreamEntry.SetDirty(true); + m_aXRef[nXRefStreamId] = aXRefStreamEntry; + + // Write stream data. + SvMemoryStream aXRefStream; + const size_t nOffsetLen = 3; + // 3 additional bytes: predictor, the first and the third field. + const size_t nLineLength = nOffsetLen + 3; + // This is the line as it appears before tweaking according to the predictor. + std::vector<unsigned char> aOrigLine(nLineLength); + // This is the previous line. + std::vector<unsigned char> aPrevLine(nLineLength); + // This is the line as written to the stream. + std::vector<unsigned char> aFilteredLine(nLineLength); + for (const auto& rXRef : m_aXRef) + { + const XRefEntry& rEntry = rXRef.second; + + if (!rEntry.GetDirty()) + continue; + + // Predictor. + size_t nPos = 0; + // PNG prediction: up (on all rows). + aOrigLine[nPos++] = 2; + + // First field. + unsigned char nType = 0; + switch (rEntry.GetType()) + { + case XRefEntryType::FREE: + nType = 0; + break; + case XRefEntryType::NOT_COMPRESSED: + nType = 1; + break; + case XRefEntryType::COMPRESSED: + nType = 2; + break; + } + aOrigLine[nPos++] = nType; + + // Second field. + for (size_t i = 0; i < nOffsetLen; ++i) + { + size_t nByte = nOffsetLen - i - 1; + // Fields requiring more than one byte are stored with the + // high-order byte first. + unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8); + aOrigLine[nPos++] = nCh; + } + + // Third field. + aOrigLine[nPos++] = 0; + + // Now apply the predictor. + aFilteredLine[0] = aOrigLine[0]; + for (size_t i = 1; i < nLineLength; ++i) + { + // Count the delta vs the previous line. + aFilteredLine[i] = aOrigLine[i] - aPrevLine[i]; + // Remember the new reference. + aPrevLine[i] = aOrigLine[i]; + } + + aXRefStream.WriteBytes(aFilteredLine.data(), aFilteredLine.size()); + } + + m_aEditBuffer.WriteUInt32AsString(nXRefStreamId); + m_aEditBuffer.WriteCharPtr( + " 0 obj\n<</DecodeParms<</Columns 5/Predictor 12>>/Filter/FlateDecode"); + + // ID. + auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID")); + if (pID) + { + const std::vector<PDFElement*>& rElements = pID->GetElements(); + m_aEditBuffer.WriteCharPtr("/ID [ <"); + for (size_t i = 0; i < rElements.size(); ++i) + { + auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]); + if (!pIDString) + continue; + + m_aEditBuffer.WriteOString(pIDString->GetValue()); + if ((i + 1) < rElements.size()) + m_aEditBuffer.WriteCharPtr("> <"); + } + m_aEditBuffer.WriteCharPtr("> ] "); + } + + // Index. + m_aEditBuffer.WriteCharPtr("/Index [ "); + for (const auto& rXRef : m_aXRef) + { + if (!rXRef.second.GetDirty()) + continue; + + m_aEditBuffer.WriteUInt32AsString(rXRef.first); + m_aEditBuffer.WriteCharPtr(" 1 "); + } + m_aEditBuffer.WriteCharPtr("] "); + + // Info. + auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info")); + if (pInfo) + { + m_aEditBuffer.WriteCharPtr("/Info "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R "); + } + + // Length. + m_aEditBuffer.WriteCharPtr("/Length "); + { + ZCodec aZCodec; + aZCodec.BeginCompression(); + aXRefStream.Seek(0); + SvMemoryStream aStream; + aZCodec.Compress(aXRefStream, aStream); + aZCodec.EndCompression(); + aXRefStream.Seek(0); + aXRefStream.SetStreamSize(0); + aStream.Seek(0); + aXRefStream.WriteStream(aStream); + } + m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize()); + + if (!m_aStartXRefs.empty()) + { + // Write location of the previous cross-reference section. + m_aEditBuffer.WriteCharPtr("/Prev "); + m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); + } + + // Root. + m_aEditBuffer.WriteCharPtr("/Root "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R "); + + // Size. + m_aEditBuffer.WriteCharPtr("/Size "); + m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); + + m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n"); + aXRefStream.Seek(0); + m_aEditBuffer.WriteStream(aXRefStream); + m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n"); + } + else + { + // Write the xref table. + m_aEditBuffer.WriteCharPtr("xref\n"); + for (const auto& rXRef : m_aXRef) + { + size_t nObject = rXRef.first; + size_t nOffset = rXRef.second.GetOffset(); + if (!rXRef.second.GetDirty()) + continue; + + m_aEditBuffer.WriteUInt32AsString(nObject); + m_aEditBuffer.WriteCharPtr(" 1\n"); + OStringBuffer aBuffer; + aBuffer.append(static_cast<sal_Int32>(nOffset)); + while (aBuffer.getLength() < 10) + aBuffer.insert(0, "0"); + if (nObject == 0) + aBuffer.append(" 65535 f \n"); + else + aBuffer.append(" 00000 n \n"); + m_aEditBuffer.WriteOString(aBuffer); + } + + // Write the trailer. + m_aEditBuffer.WriteCharPtr("trailer\n<</Size "); + m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); + m_aEditBuffer.WriteCharPtr("/Root "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R\n"); + auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info")); + if (pInfo) + { + m_aEditBuffer.WriteCharPtr("/Info "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R\n"); + } + auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID")); + if (pID) + { + const std::vector<PDFElement*>& rElements = pID->GetElements(); + m_aEditBuffer.WriteCharPtr("/ID [ <"); + for (size_t i = 0; i < rElements.size(); ++i) + { + auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]); + if (!pIDString) + continue; + + m_aEditBuffer.WriteOString(pIDString->GetValue()); + if ((i + 1) < rElements.size()) + m_aEditBuffer.WriteCharPtr(">\n<"); + } + m_aEditBuffer.WriteCharPtr("> ]\n"); + } + + if (!m_aStartXRefs.empty()) + { + // Write location of the previous cross-reference section. + m_aEditBuffer.WriteCharPtr("/Prev "); + m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); + } + + m_aEditBuffer.WriteCharPtr(">>\n"); + } +} + +bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificate, + const OUString& rDescription, bool bAdES) +{ + m_aEditBuffer.Seek(STREAM_SEEK_TO_END); + m_aEditBuffer.WriteCharPtr("\n"); + + sal_uInt64 nSignatureLastByteRangeOffset = 0; + sal_Int64 nSignatureContentOffset = 0; + sal_Int32 nSignatureId = WriteSignatureObject( + rDescription, bAdES, nSignatureLastByteRangeOffset, nSignatureContentOffset); + + tools::Rectangle aSignatureRectangle; + sal_Int32 nAppearanceId = WriteAppearanceObject(aSignatureRectangle); + + std::vector<PDFObjectElement*> aPages = GetPages(); + if (aPages.empty()) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: found no pages"); + return false; + } + + size_t nPage = 0; + if (m_nSignaturePage < aPages.size()) + { + nPage = m_nSignaturePage; + } + if (!aPages[nPage]) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to find page #" << nPage); + return false; + } + + PDFObjectElement& rPage = *aPages[nPage]; + sal_Int32 nAnnotId = WriteAnnotObject(rPage, nSignatureId, nAppearanceId, aSignatureRectangle); + + if (!WritePageObject(rPage, nAnnotId)) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Page object"); + return false; + } + + PDFReferenceElement* pRoot = nullptr; + if (!WriteCatalogObject(nAnnotId, pRoot)) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Catalog object"); + return false; + } + + sal_uInt64 nXRefOffset = m_aEditBuffer.Tell(); + WriteXRef(nXRefOffset, pRoot); + + // Write startxref. + m_aEditBuffer.WriteCharPtr("startxref\n"); + m_aEditBuffer.WriteUInt32AsString(nXRefOffset); + m_aEditBuffer.WriteCharPtr("\n%%EOF\n"); + + // Finalize the signature, now that we know the total file size. + // Calculate the length of the last byte range. + sal_uInt64 nFileEnd = m_aEditBuffer.Tell(); + sal_Int64 nLastByteRangeLength + = nFileEnd - (nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); + // Write the length to the buffer. + m_aEditBuffer.Seek(nSignatureLastByteRangeOffset); + OString aByteRangeBuffer = OString::number(nLastByteRangeLength) + " ]"; + m_aEditBuffer.WriteOString(aByteRangeBuffer); + + // Create the PKCS#7 object. + css::uno::Sequence<sal_Int8> aDerEncoded = xCertificate->getEncoded(); + if (!aDerEncoded.hasElements()) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: empty certificate"); + return false; + } + + m_aEditBuffer.Seek(0); + sal_uInt64 nBufferSize1 = nSignatureContentOffset - 1; + std::unique_ptr<char[]> aBuffer1(new char[nBufferSize1]); + m_aEditBuffer.ReadBytes(aBuffer1.get(), nBufferSize1); + + m_aEditBuffer.Seek(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); + sal_uInt64 nBufferSize2 = nLastByteRangeLength; + std::unique_ptr<char[]> aBuffer2(new char[nBufferSize2]); + m_aEditBuffer.ReadBytes(aBuffer2.get(), nBufferSize2); + + OStringBuffer aCMSHexBuffer; + svl::crypto::Signing aSigning(xCertificate); + aSigning.AddDataRange(aBuffer1.get(), nBufferSize1); + aSigning.AddDataRange(aBuffer2.get(), nBufferSize2); + if (!aSigning.Sign(aCMSHexBuffer)) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: PDFWriter::Sign() failed"); + return false; + } + + assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH); + + m_aEditBuffer.Seek(nSignatureContentOffset); + m_aEditBuffer.WriteOString(aCMSHexBuffer); + + return true; +} + +bool PDFDocument::Write(SvStream& rStream) +{ + m_aEditBuffer.Seek(0); + rStream.WriteStream(m_aEditBuffer); + return rStream.good(); +} + +bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, + std::vector<std::unique_ptr<PDFElement>>& rElements, + PDFObjectElement* pObjectElement) +{ + // Last seen object token. + PDFObjectElement* pObject = pObjectElement; + PDFNameElement* pObjectKey = nullptr; + PDFObjectElement* pObjectStream = nullptr; + bool bInXRef = false; + // The next number will be an xref offset. + bool bInStartXRef = false; + // Dictionary depth, so we know when we're outside any dictionaries. + int nDepth = 0; + // Last seen array token that's outside any dictionaries. + PDFArrayElement* pArray = nullptr; + // If we're inside an obj/endobj pair. + bool bInObject = false; + + while (true) + { + char ch; + rStream.ReadChar(ch); + if (rStream.eof()) + break; + + switch (ch) + { + case '%': + { + auto pComment = new PDFCommentElement(*this); + rElements.push_back(std::unique_ptr<PDFElement>(pComment)); + rStream.SeekRel(-1); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFCommentElement::Read() failed"); + return false; + } + if (eMode == TokenizeMode::EOF_TOKEN && !m_aEOFs.empty() + && m_aEOFs.back() == rStream.Tell()) + { + // Found EOF and partial parsing requested, we're done. + return true; + } + break; + } + case '<': + { + // Dictionary or hex string. + rStream.ReadChar(ch); + rStream.SeekRel(-2); + if (ch == '<') + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement())); + ++nDepth; + } + else + rElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement)); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFDictionaryElement::Read() failed"); + return false; + } + break; + } + case '>': + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement())); + --nDepth; + rStream.SeekRel(-1); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFEndDictionaryElement::Read() failed"); + return false; + } + break; + } + case '[': + { + auto pArr = new PDFArrayElement(pObject); + rElements.push_back(std::unique_ptr<PDFElement>(pArr)); + if (nDepth == 0) + { + // The array is attached directly, inform the object. + pArray = pArr; + if (pObject) + { + pObject->SetArray(pArray); + pObject->SetArrayOffset(rStream.Tell()); + } + } + ++nDepth; + rStream.SeekRel(-1); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", "PDFDocument::Tokenize: PDFArrayElement::Read() failed"); + return false; + } + break; + } + case ']': + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement())); + --nDepth; + rStream.SeekRel(-1); + if (nDepth == 0) + { + if (pObject) + { + pObject->SetArrayLength(rStream.Tell() - pObject->GetArrayOffset()); + } + } + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFEndArrayElement::Read() failed"); + return false; + } + break; + } + case '/': + { + auto pNameElement = new PDFNameElement(); + rElements.push_back(std::unique_ptr<PDFElement>(pNameElement)); + rStream.SeekRel(-1); + if (!pNameElement->Read(rStream)) + { + SAL_WARN("vcl.filter", "PDFDocument::Tokenize: PDFNameElement::Read() failed"); + return false; + } + + if (pObject && pObjectKey && pObjectKey->GetValue() == "Type" + && pNameElement->GetValue() == "ObjStm") + pObjectStream = pObject; + else + pObjectKey = pNameElement; + break; + } + case '(': + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement)); + rStream.SeekRel(-1); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFLiteralStringElement::Read() failed"); + return false; + } + break; + } + default: + { + if (rtl::isAsciiDigit(static_cast<unsigned char>(ch)) || ch == '-' || ch == '+' + || ch == '.') + { + // Numbering object: an integer or a real. + auto pNumberElement = new PDFNumberElement(); + rElements.push_back(std::unique_ptr<PDFElement>(pNumberElement)); + rStream.SeekRel(-1); + if (!pNumberElement->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFNumberElement::Read() failed"); + return false; + } + if (bInStartXRef) + { + bInStartXRef = false; + m_aStartXRefs.push_back(pNumberElement->GetValue()); + + auto it = m_aOffsetObjects.find(pNumberElement->GetValue()); + if (it != m_aOffsetObjects.end()) + m_pXRefStream = it->second; + } + else if (bInObject && !nDepth && pObject) + // Number element inside an object, but outside a + // dictionary / array: remember it. + pObject->SetNumberElement(pNumberElement); + } + else if (rtl::isAsciiAlpha(static_cast<unsigned char>(ch))) + { + // Possible keyword, like "obj". + rStream.SeekRel(-1); + OString aKeyword = ReadKeyword(rStream); + + bool bObj = aKeyword == "obj"; + if (bObj || aKeyword == "R") + { + size_t nElements = rElements.size(); + if (nElements < 2) + { + SAL_WARN("vcl.filter", "PDFDocument::Tokenize: expected at least two " + "tokens before 'obj' or 'R' keyword"); + return false; + } + + auto pObjectNumber + = dynamic_cast<PDFNumberElement*>(rElements[nElements - 2].get()); + auto pGenerationNumber + = dynamic_cast<PDFNumberElement*>(rElements[nElements - 1].get()); + if (!pObjectNumber || !pGenerationNumber) + { + SAL_WARN("vcl.filter", "PDFDocument::Tokenize: missing object or " + "generation number before 'obj' or 'R' keyword"); + return false; + } + + if (bObj) + { + pObject = new PDFObjectElement(*this, pObjectNumber->GetValue(), + pGenerationNumber->GetValue()); + rElements.push_back(std::unique_ptr<PDFElement>(pObject)); + m_aOffsetObjects[pObjectNumber->GetLocation()] = pObject; + m_aIDObjects[pObjectNumber->GetValue()] = pObject; + bInObject = true; + } + else + { + auto pReference = new PDFReferenceElement(*this, *pObjectNumber, + *pGenerationNumber); + rElements.push_back(std::unique_ptr<PDFElement>(pReference)); + if (bInObject && nDepth > 0 && pObject) + // Inform the object about a new in-dictionary reference. + pObject->AddDictionaryReference(pReference); + } + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFElement::Read() failed"); + return false; + } + } + else if (aKeyword == "stream") + { + // Look up the length of the stream from the parent object's dictionary. + size_t nLength = 0; + for (size_t nElement = 0; nElement < rElements.size(); ++nElement) + { + // Iterate in reverse order. + size_t nIndex = rElements.size() - nElement - 1; + PDFElement* pElement = rElements[nIndex].get(); + auto pObj = dynamic_cast<PDFObjectElement*>(pElement); + if (!pObj) + continue; + + PDFElement* pLookup = pObj->Lookup("Length"); + auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup); + if (pReference) + { + // Length is provided as a reference. + nLength = pReference->LookupNumber(rStream); + break; + } + + auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup); + if (pNumber) + { + // Length is provided directly. + nLength = pNumber->GetValue(); + break; + } + + SAL_WARN( + "vcl.filter", + "PDFDocument::Tokenize: found no Length key for stream keyword"); + return false; + } + + PDFDocument::SkipLineBreaks(rStream); + auto pStreamElement = new PDFStreamElement(nLength); + if (pObject) + pObject->SetStream(pStreamElement); + rElements.push_back(std::unique_ptr<PDFElement>(pStreamElement)); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFStreamElement::Read() failed"); + return false; + } + } + else if (aKeyword == "endstream") + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement)); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFEndStreamElement::Read() failed"); + return false; + } + } + else if (aKeyword == "endobj") + { + rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement)); + if (!rElements.back()->Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: PDFEndObjectElement::Read() failed"); + return false; + } + if (eMode == TokenizeMode::END_OF_OBJECT) + { + // Found endobj and only object parsing was requested, we're done. + return true; + } + + if (pObjectStream) + { + // We're at the end of an object stream, parse the stored objects. + pObjectStream->ParseStoredObjects(); + pObjectStream = nullptr; + pObjectKey = nullptr; + } + bInObject = false; + } + else if (aKeyword == "true" || aKeyword == "false") + rElements.push_back(std::unique_ptr<PDFElement>( + new PDFBooleanElement(aKeyword.toBoolean()))); + else if (aKeyword == "null") + rElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement)); + else if (aKeyword == "xref") + // Allow 'f' and 'n' keywords. + bInXRef = true; + else if (bInXRef && (aKeyword == "f" || aKeyword == "n")) + { + } + else if (aKeyword == "trailer") + { + auto pTrailer = new PDFTrailerElement(*this); + + // Make it possible to find this trailer later by offset. + pTrailer->Read(rStream); + m_aOffsetTrailers[pTrailer->GetLocation()] = pTrailer; + + // When reading till the first EOF token only, remember + // just the first trailer token. + if (eMode != TokenizeMode::EOF_TOKEN || !m_pTrailer) + m_pTrailer = pTrailer; + rElements.push_back(std::unique_ptr<PDFElement>(pTrailer)); + } + else if (aKeyword == "startxref") + { + bInStartXRef = true; + } + else + { + SAL_WARN("vcl.filter", "PDFDocument::Tokenize: unexpected '" + << aKeyword << "' keyword at byte position " + << rStream.Tell()); + return false; + } + } + else + { + auto uChar = static_cast<unsigned char>(ch); + // Be more lenient and allow unexpected null char + if (!rtl::isAsciiWhiteSpace(uChar) && uChar != 0) + { + SAL_WARN("vcl.filter", + "PDFDocument::Tokenize: unexpected character with code " + << sal_Int32(ch) << " at byte position " << rStream.Tell()); + return false; + } + SAL_WARN_IF(uChar == 0, "vcl.filter", + "PDFDocument::Tokenize: unexpected null character at " + << rStream.Tell() << " - ignoring"); + } + break; + } + } + } + + return true; +} + +void PDFDocument::SetIDObject(size_t nID, PDFObjectElement* pObject) +{ + m_aIDObjects[nID] = pObject; +} + +bool PDFDocument::ReadWithPossibleFixup(SvStream& rStream) +{ + if (Read(rStream)) + return true; + + // Read failed, try a roundtrip through pdfium and then retry. + rStream.Seek(0); + SvMemoryStream aStandardizedStream; + vcl::pdf::convertToHighestSupported(rStream, aStandardizedStream); + return Read(aStandardizedStream); +} + +bool PDFDocument::Read(SvStream& rStream) +{ + // Check file magic. + std::vector<sal_Int8> aHeader(5); + rStream.Seek(0); + rStream.ReadBytes(aHeader.data(), aHeader.size()); + if (aHeader[0] != '%' || aHeader[1] != 'P' || aHeader[2] != 'D' || aHeader[3] != 'F' + || aHeader[4] != '-') + { + SAL_WARN("vcl.filter", "PDFDocument::Read: header mismatch"); + return false; + } + + // Allow later editing of the contents in-memory. + rStream.Seek(0); + m_aEditBuffer.WriteStream(rStream); + + // Look up the offset of the xref table. + size_t nStartXRef = FindStartXRef(rStream); + SAL_INFO("vcl.filter", "PDFDocument::Read: nStartXRef is " << nStartXRef); + if (nStartXRef == 0) + { + SAL_WARN("vcl.filter", "PDFDocument::Read: found no xref start offset"); + return false; + } + while (true) + { + rStream.Seek(nStartXRef); + OString aKeyword = ReadKeyword(rStream); + if (aKeyword.isEmpty()) + ReadXRefStream(rStream); + + else + { + if (aKeyword != "xref") + { + SAL_WARN("vcl.filter", "PDFDocument::Read: xref is not the first keyword"); + return false; + } + ReadXRef(rStream); + if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN, m_aElements, nullptr)) + { + SAL_WARN("vcl.filter", "PDFDocument::Read: failed to tokenizer trailer after xref"); + return false; + } + } + + PDFNumberElement* pPrev = nullptr; + if (m_pTrailer) + { + pPrev = dynamic_cast<PDFNumberElement*>(m_pTrailer->Lookup("Prev")); + + // Remember the offset of this trailer in the correct order. It's + // possible that newer trailers don't have a larger offset. + m_aTrailerOffsets.push_back(m_pTrailer->GetLocation()); + } + else if (m_pXRefStream) + pPrev = dynamic_cast<PDFNumberElement*>(m_pXRefStream->Lookup("Prev")); + if (pPrev) + nStartXRef = pPrev->GetValue(); + + // Reset state, except the edit buffer. + m_aElements.clear(); + m_aOffsetObjects.clear(); + m_aIDObjects.clear(); + m_aStartXRefs.clear(); + m_aEOFs.clear(); + m_pTrailer = nullptr; + m_pXRefStream = nullptr; + if (!pPrev) + break; + } + + // Then we can tokenize the stream. + rStream.Seek(0); + return Tokenize(rStream, TokenizeMode::END_OF_STREAM, m_aElements, nullptr); +} + +OString PDFDocument::ReadKeyword(SvStream& rStream) +{ + OStringBuffer aBuf; + char ch; + rStream.ReadChar(ch); + if (rStream.eof()) + return {}; + while (rtl::isAsciiAlpha(static_cast<unsigned char>(ch))) + { + aBuf.append(ch); + rStream.ReadChar(ch); + if (rStream.eof()) + return aBuf.toString(); + } + rStream.SeekRel(-1); + return aBuf.toString(); +} + +size_t PDFDocument::FindStartXRef(SvStream& rStream) +{ + // Find the "startxref" token, somewhere near the end of the document. + std::vector<char> aBuf(1024); + rStream.Seek(STREAM_SEEK_TO_END); + if (rStream.Tell() > aBuf.size()) + rStream.SeekRel(static_cast<sal_Int64>(-1) * aBuf.size()); + else + // The document is really short, then just read it from the start. + rStream.Seek(0); + size_t nBeforePeek = rStream.Tell(); + size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size()); + rStream.Seek(nBeforePeek); + if (nSize != aBuf.size()) + aBuf.resize(nSize); + OString aPrefix("startxref"); + // Find the last startxref at the end of the document. + auto itLastValid = aBuf.end(); + auto it = aBuf.begin(); + while (true) + { + it = std::search(it, aBuf.end(), aPrefix.getStr(), aPrefix.getStr() + aPrefix.getLength()); + if (it == aBuf.end()) + break; + + itLastValid = it; + ++it; + } + if (itLastValid == aBuf.end()) + { + SAL_WARN("vcl.filter", "PDFDocument::FindStartXRef: found no startxref"); + return 0; + } + + rStream.SeekRel(itLastValid - aBuf.begin() + aPrefix.getLength()); + if (rStream.eof()) + { + SAL_WARN("vcl.filter", + "PDFDocument::FindStartXRef: unexpected end of stream after startxref"); + return 0; + } + + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aNumber; + if (!aNumber.Read(rStream)) + return 0; + return aNumber.GetValue(); +} + +void PDFDocument::ReadXRefStream(SvStream& rStream) +{ + // Look up the stream length in the object dictionary. + if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT, m_aElements, nullptr)) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: failed to read object"); + return; + } + + if (m_aElements.empty()) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no tokens found"); + return; + } + + PDFObjectElement* pObject = nullptr; + for (const auto& pElement : m_aElements) + { + if (auto pObj = dynamic_cast<PDFObjectElement*>(pElement.get())) + { + pObject = pObj; + break; + } + } + if (!pObject) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no object token found"); + return; + } + + // So that the Prev key can be looked up later. + m_pXRefStream = pObject; + + PDFElement* pLookup = pObject->Lookup("Length"); + auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup); + if (!pNumber) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: stream length is not provided"); + return; + } + sal_uInt64 nLength = pNumber->GetValue(); + + // Look up the stream offset. + PDFStreamElement* pStream = nullptr; + for (const auto& pElement : m_aElements) + { + if (auto pS = dynamic_cast<PDFStreamElement*>(pElement.get())) + { + pStream = pS; + break; + } + } + if (!pStream) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no stream token found"); + return; + } + + // Read and decompress it. + rStream.Seek(pStream->GetOffset()); + std::vector<char> aBuf(nLength); + rStream.ReadBytes(aBuf.data(), aBuf.size()); + + auto pFilter = dynamic_cast<PDFNameElement*>(pObject->Lookup("Filter")); + if (!pFilter) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no Filter found"); + return; + } + + if (pFilter->GetValue() != "FlateDecode") + { + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue()); + return; + } + + int nColumns = 1; + int nPredictor = 1; + if (auto pDecodeParams = dynamic_cast<PDFDictionaryElement*>(pObject->Lookup("DecodeParms"))) + { + const std::map<OString, PDFElement*>& rItems = pDecodeParams->GetItems(); + auto it = rItems.find("Columns"); + if (it != rItems.end()) + if (auto pColumns = dynamic_cast<PDFNumberElement*>(it->second)) + nColumns = pColumns->GetValue(); + it = rItems.find("Predictor"); + if (it != rItems.end()) + if (auto pPredictor = dynamic_cast<PDFNumberElement*>(it->second)) + nPredictor = pPredictor->GetValue(); + } + + SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); + SvMemoryStream aStream; + ZCodec aZCodec; + aZCodec.BeginCompression(); + aZCodec.Decompress(aSource, aStream); + if (!aZCodec.EndCompression()) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: decompression failed"); + return; + } + + // Look up the first and the last entry we need to read. + auto pIndex = dynamic_cast<PDFArrayElement*>(pObject->Lookup("Index")); + std::vector<size_t> aFirstObjects; + std::vector<size_t> aNumberOfObjects; + if (!pIndex) + { + auto pSize = dynamic_cast<PDFNumberElement*>(pObject->Lookup("Size")); + if (pSize) + { + aFirstObjects.push_back(0); + aNumberOfObjects.push_back(pSize->GetValue()); + } + else + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: Index and Size not found"); + return; + } + } + else + { + const std::vector<PDFElement*>& rIndexElements = pIndex->GetElements(); + size_t nFirstObject = 0; + for (size_t i = 0; i < rIndexElements.size(); ++i) + { + if (i % 2 == 0) + { + auto pFirstObject = dynamic_cast<PDFNumberElement*>(rIndexElements[i]); + if (!pFirstObject) + { + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: Index has no first object"); + return; + } + nFirstObject = pFirstObject->GetValue(); + continue; + } + + auto pNumberOfObjects = dynamic_cast<PDFNumberElement*>(rIndexElements[i]); + if (!pNumberOfObjects) + { + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: Index has no number of objects"); + return; + } + aFirstObjects.push_back(nFirstObject); + aNumberOfObjects.push_back(pNumberOfObjects->GetValue()); + } + } + + // Look up the format of a single entry. + const int nWSize = 3; + auto pW = dynamic_cast<PDFArrayElement*>(pObject->Lookup("W")); + if (!pW || pW->GetElements().size() < nWSize) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W not found or has < 3 elements"); + return; + } + int aW[nWSize]; + // First character is the (kind of) repeated predictor. + int nLineLength = 1; + for (size_t i = 0; i < nWSize; ++i) + { + auto pI = dynamic_cast<PDFNumberElement*>(pW->GetElements()[i]); + if (!pI) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: W contains non-number"); + return; + } + aW[i] = pI->GetValue(); + nLineLength += aW[i]; + } + + if (nPredictor > 1 && nLineLength - 1 != nColumns) + { + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: /DecodeParms/Columns is inconsistent with /W"); + return; + } + + aStream.Seek(0); + for (size_t nSubSection = 0; nSubSection < aFirstObjects.size(); ++nSubSection) + { + size_t nFirstObject = aFirstObjects[nSubSection]; + size_t nNumberOfObjects = aNumberOfObjects[nSubSection]; + + // This is the line as read from the stream. + std::vector<unsigned char> aOrigLine(nLineLength); + // This is the line as it appears after tweaking according to nPredictor. + std::vector<unsigned char> aFilteredLine(nLineLength); + for (size_t nEntry = 0; nEntry < nNumberOfObjects; ++nEntry) + { + size_t nIndex = nFirstObject + nEntry; + + aStream.ReadBytes(aOrigLine.data(), aOrigLine.size()); + if (nPredictor > 1 && aOrigLine[0] + 10 != nPredictor) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: in-stream predictor is " + "inconsistent with /DecodeParms/Predictor for object #" + << nIndex); + return; + } + + for (int i = 0; i < nLineLength; ++i) + { + switch (nPredictor) + { + case 1: + // No prediction. + break; + case 12: + // PNG prediction: up (on all rows). + aFilteredLine[i] = aFilteredLine[i] + aOrigLine[i]; + break; + default: + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: unexpected predictor: " + << nPredictor); + return; + } + } + + // First character is already handled above. + int nPos = 1; + size_t nType = 0; + // Start of the current field in the stream data. + int nOffset = nPos; + for (; nPos < nOffset + aW[0]; ++nPos) + { + unsigned char nCh = aFilteredLine[nPos]; + nType = (nType << 8) + nCh; + } + + // Start of the object in the file stream. + size_t nStreamOffset = 0; + nOffset = nPos; + for (; nPos < nOffset + aW[1]; ++nPos) + { + unsigned char nCh = aFilteredLine[nPos]; + nStreamOffset = (nStreamOffset << 8) + nCh; + } + + // Generation number of the object. + size_t nGenerationNumber = 0; + nOffset = nPos; + for (; nPos < nOffset + aW[2]; ++nPos) + { + unsigned char nCh = aFilteredLine[nPos]; + nGenerationNumber = (nGenerationNumber << 8) + nCh; + } + + // Ignore invalid nType. + if (nType <= 2) + { + if (m_aXRef.find(nIndex) == m_aXRef.end()) + { + XRefEntry aEntry; + switch (nType) + { + case 0: + aEntry.SetType(XRefEntryType::FREE); + break; + case 1: + aEntry.SetType(XRefEntryType::NOT_COMPRESSED); + break; + case 2: + aEntry.SetType(XRefEntryType::COMPRESSED); + break; + } + aEntry.SetOffset(nStreamOffset); + m_aXRef[nIndex] = aEntry; + } + } + } + } +} + +void PDFDocument::ReadXRef(SvStream& rStream) +{ + PDFDocument::SkipWhitespace(rStream); + + while (true) + { + PDFNumberElement aFirstObject; + if (!aFirstObject.Read(rStream)) + { + // Next token is not a number, it'll be the trailer. + return; + } + + if (aFirstObject.GetValue() < 0) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected first object number >= 0"); + return; + } + + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aNumberOfEntries; + if (!aNumberOfEntries.Read(rStream)) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read number of entries"); + return; + } + + if (aNumberOfEntries.GetValue() < 0) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: expected zero or more entries"); + return; + } + + size_t nSize = aNumberOfEntries.GetValue(); + for (size_t nEntry = 0; nEntry < nSize; ++nEntry) + { + size_t nIndex = aFirstObject.GetValue() + nEntry; + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aOffset; + if (!aOffset.Read(rStream)) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read offset"); + return; + } + + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aGenerationNumber; + if (!aGenerationNumber.Read(rStream)) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: failed to read generation number"); + return; + } + + PDFDocument::SkipWhitespace(rStream); + OString aKeyword = ReadKeyword(rStream); + if (aKeyword != "f" && aKeyword != "n") + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRef: unexpected keyword"); + return; + } + // xrefs are read in reverse order, so never update an existing + // offset with an older one. + if (m_aXRef.find(nIndex) == m_aXRef.end()) + { + XRefEntry aEntry; + aEntry.SetOffset(aOffset.GetValue()); + // Initially only the first entry is dirty. + if (nIndex == 0) + aEntry.SetDirty(true); + m_aXRef[nIndex] = aEntry; + } + PDFDocument::SkipWhitespace(rStream); + } + } +} + +void PDFDocument::SkipWhitespace(SvStream& rStream) +{ + char ch = 0; + + while (true) + { + rStream.ReadChar(ch); + if (rStream.eof()) + break; + + if (!rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch))) + { + rStream.SeekRel(-1); + return; + } + } +} + +void PDFDocument::SkipLineBreaks(SvStream& rStream) +{ + char ch = 0; + + while (true) + { + rStream.ReadChar(ch); + if (rStream.eof()) + break; + + if (ch != '\n' && ch != '\r') + { + rStream.SeekRel(-1); + return; + } + } +} + +size_t PDFDocument::GetObjectOffset(size_t nIndex) const +{ + auto it = m_aXRef.find(nIndex); + if (it == m_aXRef.end() || it->second.GetType() == XRefEntryType::COMPRESSED) + { + SAL_WARN("vcl.filter", "PDFDocument::GetObjectOffset: wanted to look up index #" + << nIndex << ", but failed"); + return 0; + } + + return it->second.GetOffset(); +} + +const std::vector<std::unique_ptr<PDFElement>>& PDFDocument::GetElements() const +{ + return m_aElements; +} + +/// Visits the page tree recursively, looking for page objects. +static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet) +{ + auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids")); + if (!pKids) + { + SAL_WARN("vcl.filter", "visitPages: pages has no kids"); + return; + } + + pPages->setVisiting(true); + + for (const auto& pKid : pKids->GetElements()) + { + auto pReference = dynamic_cast<PDFReferenceElement*>(pKid); + if (!pReference) + continue; + + PDFObjectElement* pKidObject = pReference->LookupObject(); + if (!pKidObject) + continue; + + // detect if visiting reenters itself + if (pKidObject->alreadyVisiting()) + { + SAL_WARN("vcl.filter", "visitPages: loop in hierarchy"); + continue; + } + + auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type")); + if (pName && pName->GetValue() == "Pages") + // Pages inside pages: recurse. + visitPages(pKidObject, rRet); + else + // Found an actual page. + rRet.push_back(pKidObject); + } + + pPages->setVisiting(false); +} + +PDFObjectElement* PDFDocument::GetCatalog() +{ + PDFReferenceElement* pRoot = nullptr; + + PDFTrailerElement* pTrailer = nullptr; + if (!m_aTrailerOffsets.empty()) + { + // Get access to the latest trailer, and work with the keys of that + // one. + auto it = m_aOffsetTrailers.find(m_aTrailerOffsets[0]); + if (it != m_aOffsetTrailers.end()) + pTrailer = it->second; + } + + if (pTrailer) + pRoot = dynamic_cast<PDFReferenceElement*>(pTrailer->Lookup("Root")); + else if (m_pXRefStream) + pRoot = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Root")); + + if (!pRoot) + { + SAL_WARN("vcl.filter", "PDFDocument::GetCatalog: trailer has no Root key"); + return nullptr; + } + + return pRoot->LookupObject(); +} + +std::vector<PDFObjectElement*> PDFDocument::GetPages() +{ + std::vector<PDFObjectElement*> aRet; + + PDFObjectElement* pCatalog = GetCatalog(); + if (!pCatalog) + { + SAL_WARN("vcl.filter", "PDFDocument::GetPages: trailer has no catalog"); + return aRet; + } + + PDFObjectElement* pPages = pCatalog->LookupObject("Pages"); + if (!pPages) + { + SAL_WARN("vcl.filter", "PDFDocument::GetPages: catalog (obj " << pCatalog->GetObjectValue() + << ") has no pages"); + return aRet; + } + + visitPages(pPages, aRet); + + return aRet; +} + +void PDFDocument::PushBackEOF(size_t nOffset) { m_aEOFs.push_back(nOffset); } + +std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets() +{ + std::vector<PDFObjectElement*> aRet; + + std::vector<PDFObjectElement*> aPages = GetPages(); + + for (const auto& pPage : aPages) + { + if (!pPage) + continue; + + PDFElement* pAnnotsElement = pPage->Lookup("Annots"); + auto pAnnots = dynamic_cast<PDFArrayElement*>(pAnnotsElement); + if (!pAnnots) + { + // Annots is not an array, see if it's a reference to an object + // with a direct array. + auto pAnnotsRef = dynamic_cast<PDFReferenceElement*>(pAnnotsElement); + if (pAnnotsRef) + { + if (PDFObjectElement* pAnnotsObject = pAnnotsRef->LookupObject()) + { + pAnnots = pAnnotsObject->GetArray(); + } + } + } + + if (!pAnnots) + continue; + + for (const auto& pAnnot : pAnnots->GetElements()) + { + auto pReference = dynamic_cast<PDFReferenceElement*>(pAnnot); + if (!pReference) + continue; + + PDFObjectElement* pAnnotObject = pReference->LookupObject(); + if (!pAnnotObject) + continue; + + auto pFT = dynamic_cast<PDFNameElement*>(pAnnotObject->Lookup("FT")); + if (!pFT || pFT->GetValue() != "Sig") + continue; + + aRet.push_back(pAnnotObject); + } + } + + return aRet; +} + +std::vector<unsigned char> PDFDocument::DecodeHexString(PDFHexStringElement const* pElement) +{ + return svl::crypto::DecodeHexString(pElement->GetValue()); +} + +OUString PDFDocument::DecodeHexStringUTF16BE(PDFHexStringElement const& rElement) +{ + std::vector<unsigned char> const encoded(DecodeHexString(&rElement)); + // Text strings can be PDF-DocEncoding or UTF-16BE with mandatory BOM; + // only the latter supported is here + if (encoded.size() < 2 || encoded[0] != 0xFE || encoded[1] != 0xFF || (encoded.size() & 1) != 0) + { + return {}; + } + OUStringBuffer buf(encoded.size() - 2); + for (size_t i = 2; i < encoded.size(); i += 2) + { + buf.append(sal_Unicode((static_cast<sal_uInt16>(encoded[i]) << 8) | encoded[i + 1])); + } + return buf.makeStringAndClear(); +} + +PDFCommentElement::PDFCommentElement(PDFDocument& rDoc) + : m_rDoc(rDoc) +{ +} + +bool PDFCommentElement::Read(SvStream& rStream) +{ + // Read from (including) the % char till (excluding) the end of the line/stream. + OStringBuffer aBuf; + char ch; + rStream.ReadChar(ch); + while (true) + { + if (ch == '\n' || ch == '\r' || rStream.eof()) + { + m_aComment = aBuf.makeStringAndClear(); + + if (m_aComment.startsWith("%%EOF")) + { + sal_uInt64 nPos = rStream.Tell(); + if (ch == '\r') + { + rStream.ReadChar(ch); + rStream.SeekRel(-1); + // If the comment ends with a \r\n, count the \n as well to match Adobe Acrobat + // behavior. + if (ch == '\n') + { + nPos += 1; + } + } + m_rDoc.PushBackEOF(nPos); + } + + SAL_INFO("vcl.filter", "PDFCommentElement::Read: m_aComment is '" << m_aComment << "'"); + return true; + } + aBuf.append(ch); + rStream.ReadChar(ch); + } + + return false; +} + +PDFNumberElement::PDFNumberElement() = default; + +bool PDFNumberElement::Read(SvStream& rStream) +{ + OStringBuffer aBuf; + m_nOffset = rStream.Tell(); + char ch; + rStream.ReadChar(ch); + if (rStream.eof()) + { + return false; + } + if (!rtl::isAsciiDigit(static_cast<unsigned char>(ch)) && ch != '-' && ch != '+' && ch != '.') + { + rStream.SeekRel(-1); + return false; + } + while (!rStream.eof()) + { + if (!rtl::isAsciiDigit(static_cast<unsigned char>(ch)) && ch != '-' && ch != '+' + && ch != '.') + { + rStream.SeekRel(-1); + m_nLength = rStream.Tell() - m_nOffset; + m_fValue = o3tl::toDouble(aBuf); + aBuf.setLength(0); + SAL_INFO("vcl.filter", "PDFNumberElement::Read: m_fValue is '" << m_fValue << "'"); + return true; + } + aBuf.append(ch); + rStream.ReadChar(ch); + } + + return false; +} + +sal_uInt64 PDFNumberElement::GetLocation() const { return m_nOffset; } + +sal_uInt64 PDFNumberElement::GetLength() const { return m_nLength; } + +bool PDFBooleanElement::Read(SvStream& /*rStream*/) { return true; } + +bool PDFNullElement::Read(SvStream& /*rStream*/) { return true; } + +bool PDFHexStringElement::Read(SvStream& rStream) +{ + char ch; + rStream.ReadChar(ch); + if (ch != '<') + { + SAL_INFO("vcl.filter", "PDFHexStringElement::Read: expected '<' as first character"); + return false; + } + rStream.ReadChar(ch); + + OStringBuffer aBuf; + while (!rStream.eof()) + { + if (ch == '>') + { + m_aValue = aBuf.makeStringAndClear(); + SAL_INFO("vcl.filter", + "PDFHexStringElement::Read: m_aValue length is " << m_aValue.getLength()); + return true; + } + aBuf.append(ch); + rStream.ReadChar(ch); + } + + return false; +} + +const OString& PDFHexStringElement::GetValue() const { return m_aValue; } + +bool PDFLiteralStringElement::Read(SvStream& rStream) +{ + char nPrevCh = 0; + char ch = 0; + rStream.ReadChar(ch); + if (ch != '(') + { + SAL_INFO("vcl.filter", "PDFHexStringElement::Read: expected '(' as first character"); + return false; + } + nPrevCh = ch; + rStream.ReadChar(ch); + + // Start with 1 nesting level as we read a '(' above already. + int nDepth = 1; + OStringBuffer aBuf; + while (!rStream.eof()) + { + if (ch == '(' && nPrevCh != '\\') + ++nDepth; + + if (ch == ')' && nPrevCh != '\\') + --nDepth; + + if (nDepth == 0) + { + // ')' of the outermost '(' is reached. + m_aValue = aBuf.makeStringAndClear(); + SAL_INFO("vcl.filter", + "PDFLiteralStringElement::Read: m_aValue is '" << m_aValue << "'"); + return true; + } + aBuf.append(ch); + nPrevCh = ch; + rStream.ReadChar(ch); + } + + return false; +} + +const OString& PDFLiteralStringElement::GetValue() const { return m_aValue; } + +PDFTrailerElement::PDFTrailerElement(PDFDocument& rDoc) + : m_rDoc(rDoc) + , m_pDictionaryElement(nullptr) +{ +} + +bool PDFTrailerElement::Read(SvStream& rStream) +{ + m_nOffset = rStream.Tell(); + return true; +} + +PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey) +{ + if (!m_pDictionaryElement) + { + PDFObjectParser aParser(m_rDoc.GetElements()); + aParser.parse(this); + } + if (!m_pDictionaryElement) + return nullptr; + return m_pDictionaryElement->LookupElement(rDictionaryKey); +} + +sal_uInt64 PDFTrailerElement::GetLocation() const { return m_nOffset; } + +double PDFNumberElement::GetValue() const { return m_fValue; } + +PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue) + : m_rDoc(rDoc) + , m_fObjectValue(fObjectValue) + , m_fGenerationValue(fGenerationValue) + , m_pNumberElement(nullptr) + , m_nDictionaryOffset(0) + , m_nDictionaryLength(0) + , m_pDictionaryElement(nullptr) + , m_nArrayOffset(0) + , m_nArrayLength(0) + , m_pArrayElement(nullptr) + , m_pStreamElement(nullptr) + , m_bParsed(false) +{ +} + +bool PDFObjectElement::Read(SvStream& /*rStream*/) +{ + SAL_INFO("vcl.filter", + "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj"); + return true; +} + +PDFDictionaryElement::PDFDictionaryElement() = default; + +PDFElement* PDFDictionaryElement::Lookup(const std::map<OString, PDFElement*>& rDictionary, + const OString& rKey) +{ + auto it = rDictionary.find(rKey); + if (it == rDictionary.end()) + return nullptr; + + return it->second; +} + +PDFObjectElement* PDFDictionaryElement::LookupObject(const OString& rDictionaryKey) +{ + auto pKey = dynamic_cast<PDFReferenceElement*>( + PDFDictionaryElement::Lookup(m_aItems, rDictionaryKey)); + if (!pKey) + { + SAL_WARN("vcl.filter", + "PDFDictionaryElement::LookupObject: no such key with reference value: " + << rDictionaryKey); + return nullptr; + } + + return pKey->LookupObject(); +} + +PDFElement* PDFDictionaryElement::LookupElement(const OString& rDictionaryKey) +{ + return PDFDictionaryElement::Lookup(m_aItems, rDictionaryKey); +} + +void PDFObjectElement::parseIfNecessary() +{ + if (m_bParsed) + return; + + if (!m_aElements.empty()) + { + // This is a stored object in an object stream. + PDFObjectParser aParser(m_aElements); + aParser.parse(this); + } + else + { + // Normal object: elements are stored as members of the document itself. + PDFObjectParser aParser(m_rDoc.GetElements()); + aParser.parse(this); + } + m_bParsed = true; +} + +PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey) +{ + parseIfNecessary(); + if (!m_pDictionaryElement) + return nullptr; + return PDFDictionaryElement::Lookup(GetDictionaryItems(), rDictionaryKey); +} + +PDFObjectElement* PDFObjectElement::LookupObject(const OString& rDictionaryKey) +{ + auto pKey = dynamic_cast<PDFReferenceElement*>(Lookup(rDictionaryKey)); + if (!pKey) + { + SAL_WARN("vcl.filter", "PDFObjectElement::LookupObject: no such key with reference value: " + << rDictionaryKey); + return nullptr; + } + + return pKey->LookupObject(); +} + +double PDFObjectElement::GetObjectValue() const { return m_fObjectValue; } + +void PDFObjectElement::SetDictionaryOffset(sal_uInt64 nDictionaryOffset) +{ + m_nDictionaryOffset = nDictionaryOffset; +} + +sal_uInt64 PDFObjectElement::GetDictionaryOffset() +{ + parseIfNecessary(); + return m_nDictionaryOffset; +} + +void PDFObjectElement::SetArrayOffset(sal_uInt64 nArrayOffset) { m_nArrayOffset = nArrayOffset; } + +sal_uInt64 PDFObjectElement::GetArrayOffset() const { return m_nArrayOffset; } + +void PDFDictionaryElement::SetKeyOffset(const OString& rKey, sal_uInt64 nOffset) +{ + m_aDictionaryKeyOffset[rKey] = nOffset; +} + +void PDFDictionaryElement::SetKeyValueLength(const OString& rKey, sal_uInt64 nLength) +{ + m_aDictionaryKeyValueLength[rKey] = nLength; +} + +sal_uInt64 PDFDictionaryElement::GetKeyOffset(const OString& rKey) const +{ + auto it = m_aDictionaryKeyOffset.find(rKey); + if (it == m_aDictionaryKeyOffset.end()) + return 0; + + return it->second; +} + +sal_uInt64 PDFDictionaryElement::GetKeyValueLength(const OString& rKey) const +{ + auto it = m_aDictionaryKeyValueLength.find(rKey); + if (it == m_aDictionaryKeyValueLength.end()) + return 0; + + return it->second; +} + +const std::map<OString, PDFElement*>& PDFDictionaryElement::GetItems() const { return m_aItems; } + +void PDFObjectElement::SetDictionaryLength(sal_uInt64 nDictionaryLength) +{ + m_nDictionaryLength = nDictionaryLength; +} + +sal_uInt64 PDFObjectElement::GetDictionaryLength() +{ + parseIfNecessary(); + return m_nDictionaryLength; +} + +void PDFObjectElement::SetArrayLength(sal_uInt64 nArrayLength) { m_nArrayLength = nArrayLength; } + +sal_uInt64 PDFObjectElement::GetArrayLength() const { return m_nArrayLength; } + +PDFDictionaryElement* PDFObjectElement::GetDictionary() +{ + parseIfNecessary(); + return m_pDictionaryElement; +} + +void PDFObjectElement::SetDictionary(PDFDictionaryElement* pDictionaryElement) +{ + m_pDictionaryElement = pDictionaryElement; +} + +void PDFObjectElement::SetNumberElement(PDFNumberElement* pNumberElement) +{ + m_pNumberElement = pNumberElement; +} + +PDFNumberElement* PDFObjectElement::GetNumberElement() const { return m_pNumberElement; } + +const std::vector<PDFReferenceElement*>& PDFObjectElement::GetDictionaryReferences() const +{ + return m_aDictionaryReferences; +} + +void PDFObjectElement::AddDictionaryReference(PDFReferenceElement* pReference) +{ + m_aDictionaryReferences.push_back(pReference); +} + +const std::map<OString, PDFElement*>& PDFObjectElement::GetDictionaryItems() +{ + parseIfNecessary(); + return m_pDictionaryElement->GetItems(); +} + +void PDFObjectElement::SetArray(PDFArrayElement* pArrayElement) { m_pArrayElement = pArrayElement; } + +void PDFObjectElement::SetStream(PDFStreamElement* pStreamElement) +{ + m_pStreamElement = pStreamElement; +} + +PDFStreamElement* PDFObjectElement::GetStream() const { return m_pStreamElement; } + +PDFArrayElement* PDFObjectElement::GetArray() +{ + parseIfNecessary(); + return m_pArrayElement; +} + +void PDFObjectElement::ParseStoredObjects() +{ + if (!m_pStreamElement) + { + SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no stream"); + return; + } + + auto pType = dynamic_cast<PDFNameElement*>(Lookup("Type")); + if (!pType || pType->GetValue() != "ObjStm") + { + if (!pType) + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing unexpected type"); + else + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: unexpected type: " << pType->GetValue()); + return; + } + + auto pFilter = dynamic_cast<PDFNameElement*>(Lookup("Filter")); + if (!pFilter || pFilter->GetValue() != "FlateDecode") + { + if (!pFilter) + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: missing filter"); + else + SAL_WARN("vcl.filter", + "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue()); + return; + } + + auto pFirst = dynamic_cast<PDFNumberElement*>(Lookup("First")); + if (!pFirst) + { + SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no First"); + return; + } + + auto pN = dynamic_cast<PDFNumberElement*>(Lookup("N")); + if (!pN) + { + SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no N"); + return; + } + size_t nN = pN->GetValue(); + + auto pLength = dynamic_cast<PDFNumberElement*>(Lookup("Length")); + if (!pLength) + { + SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: no length"); + return; + } + size_t nLength = pLength->GetValue(); + + // Read and decompress it. + SvMemoryStream& rEditBuffer = m_rDoc.GetEditBuffer(); + rEditBuffer.Seek(m_pStreamElement->GetOffset()); + std::vector<char> aBuf(nLength); + rEditBuffer.ReadBytes(aBuf.data(), aBuf.size()); + SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); + SvMemoryStream aStream; + ZCodec aZCodec; + aZCodec.BeginCompression(); + aZCodec.Decompress(aSource, aStream); + if (!aZCodec.EndCompression()) + { + SAL_WARN("vcl.filter", "PDFObjectElement::ParseStoredObjects: decompression failed"); + return; + } + + nLength = aStream.TellEnd(); + aStream.Seek(0); + std::vector<size_t> aObjNums; + std::vector<size_t> aOffsets; + std::vector<size_t> aLengths; + // First iterate over and find out the lengths. + for (size_t nObject = 0; nObject < nN; ++nObject) + { + PDFNumberElement aObjNum; + if (!aObjNum.Read(aStream)) + { + SAL_WARN("vcl.filter", + "PDFObjectElement::ParseStoredObjects: failed to read object number"); + return; + } + aObjNums.push_back(aObjNum.GetValue()); + + PDFDocument::SkipWhitespace(aStream); + + PDFNumberElement aByteOffset; + if (!aByteOffset.Read(aStream)) + { + SAL_WARN("vcl.filter", + "PDFObjectElement::ParseStoredObjects: failed to read byte offset"); + return; + } + aOffsets.push_back(pFirst->GetValue() + aByteOffset.GetValue()); + + if (aOffsets.size() > 1) + aLengths.push_back(aOffsets.back() - aOffsets[aOffsets.size() - 2]); + if (nObject + 1 == nN) + aLengths.push_back(nLength - aOffsets.back()); + + PDFDocument::SkipWhitespace(aStream); + } + + // Now create streams with the proper length and tokenize the data. + for (size_t nObject = 0; nObject < nN; ++nObject) + { + size_t nObjNum = aObjNums[nObject]; + size_t nOffset = aOffsets[nObject]; + size_t nLen = aLengths[nObject]; + + aStream.Seek(nOffset); + m_aStoredElements.push_back(std::make_unique<PDFObjectElement>(m_rDoc, nObjNum, 0)); + PDFObjectElement* pStored = m_aStoredElements.back().get(); + + aBuf.clear(); + aBuf.resize(nLen); + aStream.ReadBytes(aBuf.data(), aBuf.size()); + SvMemoryStream aStoredStream(aBuf.data(), aBuf.size(), StreamMode::READ); + + m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(), + pStored); + // This is how references know the object is stored inside this object stream. + m_rDoc.SetIDObject(nObjNum, pStored); + + // Store the stream of the object in the object stream for later use. + std::unique_ptr<SvMemoryStream> pStreamBuffer(new SvMemoryStream()); + aStoredStream.Seek(0); + pStreamBuffer->WriteStream(aStoredStream); + pStored->SetStreamBuffer(pStreamBuffer); + } +} + +std::vector<std::unique_ptr<PDFElement>>& PDFObjectElement::GetStoredElements() +{ + return m_aElements; +} + +SvMemoryStream* PDFObjectElement::GetStreamBuffer() const { return m_pStreamBuffer.get(); } + +void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer) +{ + m_pStreamBuffer = std::move(pStreamBuffer); +} + +PDFDocument& PDFObjectElement::GetDocument() { return m_rDoc; } + +PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, PDFNumberElement& rObject, + PDFNumberElement const& rGeneration) + : m_rDoc(rDoc) + , m_fObjectValue(rObject.GetValue()) + , m_fGenerationValue(rGeneration.GetValue()) + , m_rObject(rObject) +{ +} + +PDFNumberElement& PDFReferenceElement::GetObjectElement() const { return m_rObject; } + +bool PDFReferenceElement::Read(SvStream& rStream) +{ + SAL_INFO("vcl.filter", + "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R"); + m_nOffset = rStream.Tell(); + return true; +} + +sal_uInt64 PDFReferenceElement::GetOffset() const { return m_nOffset; } + +double PDFReferenceElement::LookupNumber(SvStream& rStream) const +{ + size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue); + if (nOffset == 0) + { + SAL_WARN("vcl.filter", "PDFReferenceElement::LookupNumber: found no offset for object #" + << m_fObjectValue); + return 0; + } + + sal_uInt64 nOrigPos = rStream.Tell(); + comphelper::ScopeGuard g([&]() { rStream.Seek(nOrigPos); }); + + rStream.Seek(nOffset); + { + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aNumber; + bool bRet = aNumber.Read(rStream); + if (!bRet || aNumber.GetValue() != m_fObjectValue) + { + SAL_WARN("vcl.filter", + "PDFReferenceElement::LookupNumber: offset points to not matching object"); + return 0; + } + } + + { + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aNumber; + bool bRet = aNumber.Read(rStream); + if (!bRet || aNumber.GetValue() != m_fGenerationValue) + { + SAL_WARN("vcl.filter", + "PDFReferenceElement::LookupNumber: offset points to not matching generation"); + return 0; + } + } + + { + PDFDocument::SkipWhitespace(rStream); + OString aKeyword = PDFDocument::ReadKeyword(rStream); + if (aKeyword != "obj") + { + SAL_WARN("vcl.filter", + "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword"); + return 0; + } + } + + PDFDocument::SkipWhitespace(rStream); + PDFNumberElement aNumber; + if (!aNumber.Read(rStream)) + { + SAL_WARN("vcl.filter", + "PDFReferenceElement::LookupNumber: failed to read referenced number"); + return 0; + } + + return aNumber.GetValue(); +} + +PDFObjectElement* PDFReferenceElement::LookupObject() +{ + return m_rDoc.LookupObject(m_fObjectValue); +} + +PDFObjectElement* PDFDocument::LookupObject(size_t nObjectNumber) +{ + auto itIDObjects = m_aIDObjects.find(nObjectNumber); + + if (itIDObjects != m_aIDObjects.end()) + return itIDObjects->second; + + SAL_WARN("vcl.filter", "PDFDocument::LookupObject: can't find obj " << nObjectNumber); + return nullptr; +} + +SvMemoryStream& PDFDocument::GetEditBuffer() { return m_aEditBuffer; } + +int PDFReferenceElement::GetObjectValue() const { return m_fObjectValue; } + +int PDFReferenceElement::GetGenerationValue() const { return m_fGenerationValue; } + +bool PDFDictionaryElement::Read(SvStream& rStream) +{ + char ch; + rStream.ReadChar(ch); + if (ch != '<') + { + SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch); + return false; + } + + if (rStream.eof()) + { + SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected end of file"); + return false; + } + + rStream.ReadChar(ch); + if (ch != '<') + { + SAL_WARN("vcl.filter", "PDFDictionaryElement::Read: unexpected character: " << ch); + return false; + } + + m_nLocation = rStream.Tell(); + + SAL_INFO("vcl.filter", "PDFDictionaryElement::Read: '<<'"); + + return true; +} + +PDFEndDictionaryElement::PDFEndDictionaryElement() = default; + +sal_uInt64 PDFEndDictionaryElement::GetLocation() const { return m_nLocation; } + +bool PDFEndDictionaryElement::Read(SvStream& rStream) +{ + m_nLocation = rStream.Tell(); + char ch; + rStream.ReadChar(ch); + if (ch != '>') + { + SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch); + return false; + } + + if (rStream.eof()) + { + SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected end of file"); + return false; + } + + rStream.ReadChar(ch); + if (ch != '>') + { + SAL_WARN("vcl.filter", "PDFEndDictionaryElement::Read: unexpected character: " << ch); + return false; + } + + SAL_INFO("vcl.filter", "PDFEndDictionaryElement::Read: '>>'"); + + return true; +} + +PDFNameElement::PDFNameElement() = default; + +bool PDFNameElement::Read(SvStream& rStream) +{ + char ch; + rStream.ReadChar(ch); + if (ch != '/') + { + SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected character: " << ch); + return false; + } + m_nLocation = rStream.Tell(); + + if (rStream.eof()) + { + SAL_WARN("vcl.filter", "PDFNameElement::Read: unexpected end of file"); + return false; + } + + // Read till the first white-space. + OStringBuffer aBuf; + rStream.ReadChar(ch); + while (!rStream.eof()) + { + if (rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch)) || ch == '/' || ch == '[' + || ch == ']' || ch == '<' || ch == '>' || ch == '(') + { + rStream.SeekRel(-1); + m_aValue = aBuf.makeStringAndClear(); + SAL_INFO("vcl.filter", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'"); + return true; + } + aBuf.append(ch); + rStream.ReadChar(ch); + } + + return false; +} + +const OString& PDFNameElement::GetValue() const { return m_aValue; } + +sal_uInt64 PDFNameElement::GetLocation() const { return m_nLocation; } + +PDFStreamElement::PDFStreamElement(size_t nLength) + : m_nLength(nLength) + , m_nOffset(0) +{ +} + +bool PDFStreamElement::Read(SvStream& rStream) +{ + SAL_INFO("vcl.filter", "PDFStreamElement::Read: length is " << m_nLength); + m_nOffset = rStream.Tell(); + std::vector<unsigned char> aBytes(m_nLength); + rStream.ReadBytes(aBytes.data(), aBytes.size()); + m_aMemory.WriteBytes(aBytes.data(), aBytes.size()); + + return rStream.good(); +} + +SvMemoryStream& PDFStreamElement::GetMemory() { return m_aMemory; } + +sal_uInt64 PDFStreamElement::GetOffset() const { return m_nOffset; } + +bool PDFEndStreamElement::Read(SvStream& /*rStream*/) { return true; } + +bool PDFEndObjectElement::Read(SvStream& /*rStream*/) { return true; } + +PDFArrayElement::PDFArrayElement(PDFObjectElement* pObject) + : m_pObject(pObject) +{ +} + +bool PDFArrayElement::Read(SvStream& rStream) +{ + char ch; + rStream.ReadChar(ch); + if (ch != '[') + { + SAL_WARN("vcl.filter", "PDFArrayElement::Read: unexpected character: " << ch); + return false; + } + + SAL_INFO("vcl.filter", "PDFArrayElement::Read: '['"); + + return true; +} + +void PDFArrayElement::PushBack(PDFElement* pElement) +{ + if (m_pObject) + SAL_INFO("vcl.filter", + "PDFArrayElement::PushBack: object is " << m_pObject->GetObjectValue()); + m_aElements.push_back(pElement); +} + +const std::vector<PDFElement*>& PDFArrayElement::GetElements() const { return m_aElements; } + +PDFEndArrayElement::PDFEndArrayElement() = default; + +bool PDFEndArrayElement::Read(SvStream& rStream) +{ + m_nOffset = rStream.Tell(); + char ch; + rStream.ReadChar(ch); + if (ch != ']') + { + SAL_WARN("vcl.filter", "PDFEndArrayElement::Read: unexpected character: " << ch); + return false; + } + + SAL_INFO("vcl.filter", "PDFEndArrayElement::Read: ']'"); + + return true; +} + +sal_uInt64 PDFEndArrayElement::GetOffset() const { return m_nOffset; } + +// PDFObjectParser + +size_t PDFObjectParser::parse(PDFElement* pParsingElement, size_t nStartIndex, int nCurrentDepth) +{ + // The index of last parsed element + size_t nReturnIndex = 0; + + pParsingElement->setParsing(true); + + comphelper::ScopeGuard aGuard([pParsingElement]() { pParsingElement->setParsing(false); }); + + // Current object, if root is an object, else nullptr + auto pParsingObject = dynamic_cast<PDFObjectElement*>(pParsingElement); + auto pParsingTrailer = dynamic_cast<PDFTrailerElement*>(pParsingElement); + + // Current dictionary, if root is an dictionary, else nullptr + auto pParsingDictionary = dynamic_cast<PDFDictionaryElement*>(pParsingElement); + + // Current parsing array, if root is an array, else nullptr + auto pParsingArray = dynamic_cast<PDFArrayElement*>(pParsingElement); + + // Find out where the dictionary for this object starts. + size_t nIndex = nStartIndex; + for (size_t i = nStartIndex; i < mrElements.size(); ++i) + { + if (mrElements[i].get() == pParsingElement) + { + nIndex = i; + break; + } + } + + OString aName; + sal_uInt64 nNameOffset = 0; + std::vector<PDFNumberElement*> aNumbers; + + sal_uInt64 nDictionaryOffset = 0; + + // Current depth; 1 is current + int nDepth = 0; + + for (size_t i = nIndex; i < mrElements.size(); ++i) + { + auto* pCurrentElement = mrElements[i].get(); + + // Dictionary tokens can be nested, track enter/leave. + if (auto pCurrentDictionary = dynamic_cast<PDFDictionaryElement*>(pCurrentElement)) + { + // Handle previously stored number + if (!aNumbers.empty()) + { + if (pParsingDictionary) + { + PDFNumberElement* pNumber = aNumbers.back(); + sal_uInt64 nLength + = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset; + + pParsingDictionary->insert(aName, pNumber); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + } + else if (pParsingArray) + { + for (auto& pNumber : aNumbers) + pParsingArray->PushBack(pNumber); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + aName.clear(); + aNumbers.clear(); + } + + nDepth++; + + if (nDepth == 1) // pParsingDictionary is the current one + { + // First dictionary start, track start offset. + nDictionaryOffset = pCurrentDictionary->GetLocation(); + + if (pParsingObject) + { + // Then the toplevel dictionary of the object. + pParsingObject->SetDictionary(pCurrentDictionary); + pParsingObject->SetDictionaryOffset(nDictionaryOffset); + pParsingDictionary = pCurrentDictionary; + } + else if (pParsingTrailer) + { + pParsingTrailer->SetDictionary(pCurrentDictionary); + pParsingDictionary = pCurrentDictionary; + } + } + else if (!pCurrentDictionary->alreadyParsing()) + { + if (pParsingArray) + { + pParsingArray->PushBack(pCurrentDictionary); + } + else if (pParsingDictionary) + { + // Dictionary toplevel value. + pParsingDictionary->insert(aName, pCurrentDictionary); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + // Nested dictionary. + const size_t nNextElementIndex = parse(pCurrentDictionary, i, nCurrentDepth + 1); + i = std::max(i, nNextElementIndex - 1); + } + } + else if (auto pCurrentEndDictionary + = dynamic_cast<PDFEndDictionaryElement*>(pCurrentElement)) + { + // Handle previously stored number + if (!aNumbers.empty()) + { + if (pParsingDictionary) + { + PDFNumberElement* pNumber = aNumbers.back(); + sal_uInt64 nLength + = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset; + + pParsingDictionary->insert(aName, pNumber); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + } + else if (pParsingArray) + { + for (auto& pNumber : aNumbers) + pParsingArray->PushBack(pNumber); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + aName.clear(); + aNumbers.clear(); + } + + if (pParsingDictionary) + { + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + sal_uInt64 nLength = pCurrentEndDictionary->GetLocation() - nNameOffset + 2; + pParsingDictionary->SetKeyValueLength(aName, nLength); + aName.clear(); + } + + if (nDepth == 1) // did the parsing ended + { + // Last dictionary end, track length and stop parsing. + if (pParsingObject) + { + sal_uInt64 nDictionaryLength + = pCurrentEndDictionary->GetLocation() - nDictionaryOffset; + pParsingObject->SetDictionaryLength(nDictionaryLength); + } + nReturnIndex = i; + break; + } + + nDepth--; + } + else if (auto pCurrentArray = dynamic_cast<PDFArrayElement*>(pCurrentElement)) + { + // Handle previously stored number + if (!aNumbers.empty()) + { + if (pParsingDictionary) + { + PDFNumberElement* pNumber = aNumbers.back(); + + sal_uInt64 nLength + = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset; + pParsingDictionary->insert(aName, pNumber); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + } + else if (pParsingArray) + { + for (auto& pNumber : aNumbers) + pParsingArray->PushBack(pNumber); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + aName.clear(); + aNumbers.clear(); + } + + nDepth++; + if (nDepth == 1) // pParsingDictionary is the current one + { + if (pParsingObject) + { + pParsingObject->SetArray(pCurrentArray); + pParsingArray = pCurrentArray; + } + } + else if (!pCurrentArray->alreadyParsing()) + { + if (pParsingArray) + { + // Array is toplevel + pParsingArray->PushBack(pCurrentArray); + } + else if (pParsingDictionary) + { + // Dictionary toplevel value. + pParsingDictionary->insert(aName, pCurrentArray); + } + + const size_t nNextElementIndex = parse(pCurrentArray, i, nCurrentDepth + 1); + + // ensure we go forwards and not endlessly loop + i = std::max(i, nNextElementIndex - 1); + } + } + else if (auto pCurrentEndArray = dynamic_cast<PDFEndArrayElement*>(pCurrentElement)) + { + // Handle previously stored number + if (!aNumbers.empty()) + { + if (pParsingDictionary) + { + PDFNumberElement* pNumber = aNumbers.back(); + + sal_uInt64 nLength + = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset; + pParsingDictionary->insert(aName, pNumber); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + } + else if (pParsingArray) + { + for (auto& pNumber : aNumbers) + pParsingArray->PushBack(pNumber); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + aName.clear(); + aNumbers.clear(); + } + + if (nDepth == 1) // did the pParsing ended + { + // Last array end, track length and stop parsing. + nReturnIndex = i; + break; + } + + if (pParsingDictionary) + { + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + // Include the ending ']' in the length of the key - (array)value pair length. + sal_uInt64 nLength = pCurrentEndArray->GetOffset() - nNameOffset + 1; + pParsingDictionary->SetKeyValueLength(aName, nLength); + aName.clear(); + } + nDepth--; + } + else if (auto pCurrentName = dynamic_cast<PDFNameElement*>(pCurrentElement)) + { + // Handle previously stored number + if (!aNumbers.empty()) + { + if (pParsingDictionary) + { + PDFNumberElement* pNumber = aNumbers.back(); + + sal_uInt64 nLength + = pNumber->GetLocation() + pNumber->GetLength() - nNameOffset; + pParsingDictionary->insert(aName, pNumber); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + } + else if (pParsingArray) + { + for (auto& pNumber : aNumbers) + pParsingArray->PushBack(pNumber); + } + aName.clear(); + aNumbers.clear(); + } + + // Now handle name + if (pParsingArray) + { + // if we are in an array, just push the name to array + pParsingArray->PushBack(pCurrentName); + } + else if (pParsingDictionary) + { + // if we are in a dictionary, we need to store the name as a possible key + if (aName.isEmpty()) + { + aName = pCurrentName->GetValue(); + nNameOffset = pCurrentName->GetLocation(); + } + else + { + sal_uInt64 nKeyLength + = pCurrentName->GetLocation() + pCurrentName->GetLength() - nNameOffset; + pParsingDictionary->insert(aName, pCurrentName); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nKeyLength); + aName.clear(); + } + } + } + else if (auto pReference = dynamic_cast<PDFReferenceElement*>(pCurrentElement)) + { + if (pParsingArray) + { + pParsingArray->PushBack(pReference); + } + else if (pParsingDictionary) + { + sal_uInt64 nLength = pReference->GetOffset() - nNameOffset; + pParsingDictionary->insert(aName, pReference); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + pParsingDictionary->SetKeyValueLength(aName, nLength); + aName.clear(); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + aNumbers.clear(); + } + else if (auto pLiteralString = dynamic_cast<PDFLiteralStringElement*>(pCurrentElement)) + { + if (pParsingArray) + { + pParsingArray->PushBack(pLiteralString); + } + else if (pParsingDictionary) + { + pParsingDictionary->insert(aName, pLiteralString); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + aName.clear(); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + } + else if (auto pBoolean = dynamic_cast<PDFBooleanElement*>(pCurrentElement)) + { + if (pParsingArray) + { + pParsingArray->PushBack(pBoolean); + } + else if (pParsingDictionary) + { + pParsingDictionary->insert(aName, pBoolean); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + aName.clear(); + } + else + { + SAL_INFO("vcl.filter", "neither Dictionary nor Array available"); + } + } + else if (auto pHexString = dynamic_cast<PDFHexStringElement*>(pCurrentElement)) + { + if (pParsingArray) + { + pParsingArray->PushBack(pHexString); + } + else if (pParsingDictionary) + { + pParsingDictionary->insert(aName, pHexString); + pParsingDictionary->SetKeyOffset(aName, nNameOffset); + aName.clear(); + } + } + else if (auto pNumberElement = dynamic_cast<PDFNumberElement*>(pCurrentElement)) + { + // Just remember this, so that in case it's not a reference parameter, + // we can handle it later. + aNumbers.push_back(pNumberElement); + } + else if (dynamic_cast<PDFEndObjectElement*>(pCurrentElement)) + { + // parsing of the object is finished + break; + } + else if (dynamic_cast<PDFObjectElement*>(pCurrentElement) + || dynamic_cast<PDFTrailerElement*>(pCurrentElement)) + { + continue; + } + else + { + SAL_INFO("vcl.filter", "Unhandled element while parsing."); + } + } + + return nReturnIndex; +} + +} // namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipdf/pdfread.cxx b/vcl/source/filter/ipdf/pdfread.cxx new file mode 100644 index 000000000..c6bc4fd5b --- /dev/null +++ b/vcl/source/filter/ipdf/pdfread.cxx @@ -0,0 +1,399 @@ +/* -*- 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 <vcl/pdfread.hxx> +#include <pdf/pdfcompat.hxx> + +#include <pdf/PdfConfig.hxx> +#include <vcl/graph.hxx> +#include <bitmap/BitmapWriteAccess.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/datetime.hxx> + +#include <vcl/filter/PDFiumLibrary.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; + +namespace vcl +{ +size_t RenderPDFBitmaps(const void* pBuffer, int nSize, std::vector<BitmapEx>& rBitmaps, + const size_t nFirstPage, int nPages, const basegfx::B2DTuple* pSizeHint) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + { + return 0; + } + + // Load the buffer using pdfium. + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument + = pPdfium->openDocument(pBuffer, nSize, OString()); + if (!pPdfDocument) + return 0; + + static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi(); + + const int nPageCount = pPdfDocument->getPageCount(); + if (nPages <= 0) + nPages = nPageCount; + const size_t nLastPage = std::min<int>(nPageCount, nFirstPage + nPages) - 1; + for (size_t nPageIndex = nFirstPage; nPageIndex <= nLastPage; ++nPageIndex) + { + // Render next page. + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(nPageIndex); + if (!pPdfPage) + break; + + // Calculate the bitmap size in points. + double nPageWidthPoints = pPdfPage->getWidth(); + double nPageHeightPoints = pPdfPage->getHeight(); + if (pSizeHint && pSizeHint->getX() && pSizeHint->getY()) + { + // Have a size hint, prefer that over the logic size from the PDF. + nPageWidthPoints + = o3tl::convert(pSizeHint->getX(), o3tl::Length::mm100, o3tl::Length::pt); + nPageHeightPoints + = o3tl::convert(pSizeHint->getY(), o3tl::Length::mm100, o3tl::Length::pt); + } + + // Returned unit is points, convert that to pixel. + + const size_t nPageWidth + = std::round(vcl::pdf::pointToPixel(nPageWidthPoints, fResolutionDPI) + * PDF_INSERT_MAGIC_SCALE_FACTOR); + const size_t nPageHeight + = std::round(vcl::pdf::pointToPixel(nPageHeightPoints, fResolutionDPI) + * PDF_INSERT_MAGIC_SCALE_FACTOR); + std::unique_ptr<vcl::pdf::PDFiumBitmap> pPdfBitmap + = pPdfium->createBitmap(nPageWidth, nPageHeight, /*nAlpha=*/1); + if (!pPdfBitmap) + break; + + bool bTransparent = pPdfPage->hasTransparency(); + if (pSizeHint) + { + // This is the PDF-in-EMF case: force transparency, even in case pdfium would tell us + // the PDF is not transparent. + bTransparent = true; + } + const sal_uInt32 nColor = bTransparent ? 0x00000000 : 0xFFFFFFFF; + pPdfBitmap->fillRect(0, 0, nPageWidth, nPageHeight, nColor); + pPdfBitmap->renderPageBitmap(pPdfDocument.get(), pPdfPage.get(), /*nStartX=*/0, + /*nStartY=*/0, nPageWidth, nPageHeight); + + // Save the buffer as a bitmap. + Bitmap aBitmap(Size(nPageWidth, nPageHeight), vcl::PixelFormat::N24_BPP); + AlphaMask aMask(Size(nPageWidth, nPageHeight)); + { + BitmapScopedWriteAccess pWriteAccess(aBitmap); + AlphaScopedWriteAccess pMaskAccess(aMask); + ConstScanline pPdfBuffer = pPdfBitmap->getBuffer(); + const int nStride = pPdfBitmap->getStride(); + std::vector<sal_uInt8> aScanlineAlpha(nPageWidth); + for (size_t nRow = 0; nRow < nPageHeight; ++nRow) + { + ConstScanline pPdfLine = pPdfBuffer + (nStride * nRow); + // pdfium byte order is BGRA. + pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride); + for (size_t nCol = 0; nCol < nPageWidth; ++nCol) + { + // Invert alpha (source is alpha, target is opacity). + aScanlineAlpha[nCol] = ~pPdfLine[3]; + pPdfLine += 4; + } + pMaskAccess->CopyScanline(nRow, aScanlineAlpha.data(), ScanlineFormat::N8BitPal, + nPageWidth); + } + } + + if (bTransparent) + { + rBitmaps.emplace_back(aBitmap, aMask); + } + else + { + rBitmaps.emplace_back(std::move(aBitmap)); + } + } + + return rBitmaps.size(); +} + +bool importPdfVectorGraphicData(SvStream& rStream, + std::shared_ptr<VectorGraphicData>& rVectorGraphicData) +{ + BinaryDataContainer aDataContainer = vcl::pdf::createBinaryDataContainer(rStream); + if (aDataContainer.isEmpty()) + { + SAL_WARN("vcl.filter", "ImportPDF: empty PDF data array"); + return false; + } + + rVectorGraphicData + = std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Pdf); + + return true; +} + +bool ImportPDF(SvStream& rStream, Graphic& rGraphic) +{ + std::shared_ptr<VectorGraphicData> pVectorGraphicData; + if (!importPdfVectorGraphicData(rStream, pVectorGraphicData)) + return false; + rGraphic = Graphic(pVectorGraphicData); + return true; +} + +namespace +{ +basegfx::B2DPoint convertFromPDFInternalToHMM(basegfx::B2DSize const& rInputPoint, + basegfx::B2DSize const& rPageSize) +{ + double x = convertPointToMm100(rInputPoint.getX()); + double y = convertPointToMm100(rPageSize.getY() - rInputPoint.getY()); + return { x, y }; +} + +std::vector<PDFGraphicAnnotation> +findAnnotations(const std::unique_ptr<vcl::pdf::PDFiumPage>& pPage, basegfx::B2DSize aPageSize) +{ + std::vector<PDFGraphicAnnotation> aPDFGraphicAnnotations; + if (!pPage) + { + return aPDFGraphicAnnotations; + } + + for (int nAnnotation = 0; nAnnotation < pPage->getAnnotationCount(); nAnnotation++) + { + auto pAnnotation = pPage->getAnnotation(nAnnotation); + if (pAnnotation) + { + auto eSubtype = pAnnotation->getSubType(); + + if (eSubtype == vcl::pdf::PDFAnnotationSubType::Text + || eSubtype == vcl::pdf::PDFAnnotationSubType::Polygon + || eSubtype == vcl::pdf::PDFAnnotationSubType::Circle + || eSubtype == vcl::pdf::PDFAnnotationSubType::Square + || eSubtype == vcl::pdf::PDFAnnotationSubType::Ink + || eSubtype == vcl::pdf::PDFAnnotationSubType::Highlight + || eSubtype == vcl::pdf::PDFAnnotationSubType::Line) + { + OUString sAuthor = pAnnotation->getString(vcl::pdf::constDictionaryKeyTitle); + OUString sText = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents); + + basegfx::B2DRectangle rRectangle = pAnnotation->getRectangle(); + basegfx::B2DRectangle rRectangleHMM( + convertPointToMm100(rRectangle.getMinX()), + convertPointToMm100(aPageSize.getY() - rRectangle.getMinY()), + convertPointToMm100(rRectangle.getMaxX()), + convertPointToMm100(aPageSize.getY() - rRectangle.getMaxY())); + + OUString sDateTimeString + = pAnnotation->getString(vcl::pdf::constDictionaryKeyModificationDate); + OUString sISO8601String = vcl::pdf::convertPdfDateToISO8601(sDateTimeString); + + css::util::DateTime aDateTime; + if (!sISO8601String.isEmpty()) + { + utl::ISO8601parseDateTime(sISO8601String, aDateTime); + } + + Color aColor = pAnnotation->getColor(); + + aPDFGraphicAnnotations.emplace_back(); + + auto& rPDFGraphicAnnotation = aPDFGraphicAnnotations.back(); + rPDFGraphicAnnotation.maRectangle = rRectangleHMM; + rPDFGraphicAnnotation.maAuthor = sAuthor; + rPDFGraphicAnnotation.maText = sText; + rPDFGraphicAnnotation.maDateTime = aDateTime; + rPDFGraphicAnnotation.meSubType = eSubtype; + rPDFGraphicAnnotation.maColor = aColor; + + if (eSubtype == vcl::pdf::PDFAnnotationSubType::Polygon) + { + auto const& rVertices = pAnnotation->getVertices(); + if (!rVertices.empty()) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerPolygon>(); + rPDFGraphicAnnotation.mpMarker = pMarker; + for (auto const& rVertex : rVertices) + { + auto aPoint = convertFromPDFInternalToHMM(rVertex, aPageSize); + pMarker->maPolygon.append(aPoint); + } + pMarker->maPolygon.setClosed(true); + pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth()); + if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor)) + pMarker->maFillColor = pAnnotation->getInteriorColor(); + } + } + else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Square) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerSquare>(); + rPDFGraphicAnnotation.mpMarker = pMarker; + pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth()); + if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor)) + pMarker->maFillColor = pAnnotation->getInteriorColor(); + } + else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Circle) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerCircle>(); + rPDFGraphicAnnotation.mpMarker = pMarker; + pMarker->mnWidth = convertPointToMm100(pAnnotation->getBorderWidth()); + if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor)) + pMarker->maFillColor = pAnnotation->getInteriorColor(); + } + else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Ink) + { + auto const& rStrokesList = pAnnotation->getInkStrokes(); + if (!rStrokesList.empty()) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerInk>(); + rPDFGraphicAnnotation.mpMarker = pMarker; + for (auto const& rStrokes : rStrokesList) + { + basegfx::B2DPolygon aPolygon; + for (auto const& rVertex : rStrokes) + { + auto aPoint = convertFromPDFInternalToHMM(rVertex, aPageSize); + aPolygon.append(aPoint); + } + pMarker->maStrokes.push_back(aPolygon); + } + float fWidth = pAnnotation->getBorderWidth(); + pMarker->mnWidth = convertPointToMm100(fWidth); + if (pAnnotation->hasKey(vcl::pdf::constDictionaryKeyInteriorColor)) + pMarker->maFillColor = pAnnotation->getInteriorColor(); + } + } + else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Highlight) + { + size_t nCount = pAnnotation->getAttachmentPointsCount(); + if (nCount > 0) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerHighlight>( + vcl::pdf::PDFTextMarkerType::Highlight); + rPDFGraphicAnnotation.mpMarker = pMarker; + for (size_t i = 0; i < nCount; ++i) + { + auto aAttachmentPoints = pAnnotation->getAttachmentPoints(i); + if (!aAttachmentPoints.empty()) + { + basegfx::B2DPolygon aPolygon; + aPolygon.setClosed(true); + + auto aPoint1 + = convertFromPDFInternalToHMM(aAttachmentPoints[0], aPageSize); + aPolygon.append(aPoint1); + auto aPoint2 + = convertFromPDFInternalToHMM(aAttachmentPoints[1], aPageSize); + aPolygon.append(aPoint2); + auto aPoint3 + = convertFromPDFInternalToHMM(aAttachmentPoints[3], aPageSize); + aPolygon.append(aPoint3); + auto aPoint4 + = convertFromPDFInternalToHMM(aAttachmentPoints[2], aPageSize); + aPolygon.append(aPoint4); + + pMarker->maQuads.push_back(aPolygon); + } + } + } + } + else if (eSubtype == vcl::pdf::PDFAnnotationSubType::Line) + { + auto const& rLineGeometry = pAnnotation->getLineGeometry(); + if (!rLineGeometry.empty()) + { + auto pMarker = std::make_shared<vcl::pdf::PDFAnnotationMarkerLine>(); + rPDFGraphicAnnotation.mpMarker = pMarker; + + auto aPoint1 = convertFromPDFInternalToHMM(rLineGeometry[0], aPageSize); + pMarker->maLineStart = aPoint1; + + auto aPoint2 = convertFromPDFInternalToHMM(rLineGeometry[1], aPageSize); + pMarker->maLineEnd = aPoint2; + + float fWidth = pAnnotation->getBorderWidth(); + pMarker->mnWidth = convertPointToMm100(fWidth); + } + } + } + } + } + return aPDFGraphicAnnotations; +} + +} // end anonymous namespace + +size_t ImportPDFUnloaded(const OUString& rURL, std::vector<PDFGraphicResult>& rGraphics) +{ + std::unique_ptr<SvStream> xStream( + ::utl::UcbStreamHelper::CreateStream(rURL, StreamMode::READ | StreamMode::SHARE_DENYNONE)); + + // Save the original PDF stream for later use. + BinaryDataContainer aDataContainer = vcl::pdf::createBinaryDataContainer(*xStream); + if (aDataContainer.isEmpty()) + return 0; + + // Prepare the link with the PDF stream. + auto pGfxLink = std::make_shared<GfxLink>(aDataContainer, GfxLinkType::NativePdf); + + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + { + return 0; + } + + // Load the buffer using pdfium. + auto pPdfDocument + = pPdfium->openDocument(pGfxLink->GetData(), pGfxLink->GetDataSize(), OString()); + + if (!pPdfDocument) + return 0; + + const int nPageCount = pPdfDocument->getPageCount(); + if (nPageCount <= 0) + return 0; + + for (int nPageIndex = 0; nPageIndex < nPageCount; ++nPageIndex) + { + basegfx::B2DSize aPageSize = pPdfDocument->getPageSize(nPageIndex); + if (aPageSize.getX() <= 0.0 || aPageSize.getY() <= 0.0) + continue; + + // Returned unit is points, convert that to twip + // 1 pt = 20 twips + constexpr double pointToTwipconversionRatio = 20; + + tools::Long nPageWidth = convertTwipToMm100(aPageSize.getX() * pointToTwipconversionRatio); + tools::Long nPageHeight = convertTwipToMm100(aPageSize.getY() * pointToTwipconversionRatio); + + // Create the Graphic with the VectorGraphicDataPtr and link the original PDF stream. + // We swap out this Graphic as soon as possible, and a later swap in + // actually renders the correct Bitmap on demand. + Graphic aGraphic(pGfxLink, nPageIndex); + + auto pPage = pPdfDocument->openPage(nPageIndex); + + std::vector<PDFGraphicAnnotation> aPDFGraphicAnnotations + = findAnnotations(pPage, aPageSize); + + rGraphics.emplace_back(std::move(aGraphic), Size(nPageWidth, nPageHeight), + aPDFGraphicAnnotations); + } + + return rGraphics.size(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipict/ipict.cxx b/vcl/source/filter/ipict/ipict.cxx new file mode 100644 index 000000000..0f8fd5ba0 --- /dev/null +++ b/vcl/source/filter/ipict/ipict.cxx @@ -0,0 +1,2041 @@ +/* -*- 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 <filter/PictReader.hxx> +#include <string.h> +#include <osl/thread.h> +#include <sal/log.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/graph.hxx> +#include <vcl/gdimtf.hxx> +#include <tools/poly.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <vcl/virdev.hxx> +#include <math.h> +#include "shape.hxx" +#include <memory> + +#include <vcl/FilterConfigItem.hxx> + // complete FilterConfigItem for GraphicImport under -fsanitize=function + +namespace PictReaderInternal { + namespace { + + //! utilitary class to store a pattern, ... + class Pattern { + public: + //! constructor + Pattern() : penStyle(PEN_SOLID), + brushStyle(BRUSH_SOLID), + nBitCount(64), + isColor(false), + isRead(false) + {} + + //! reads black/white pattern from SvStream + sal_uInt8 read(SvStream &stream); + //! sets the color + void setColor(Color col) { isColor = true; color = col; } + /** returns a color which can be "used" to replace the pattern, + * created from ForeColor and BackColor, ... + * + * note: maybe, we must also use some mode PatCopy, ... to define the color + */ + Color getColor(Color bkColor, Color fgColor) const { + if (isColor) return color; + // we create a gray pattern from nBitCount + double alpha = nBitCount / 64.0; + return Color(sal_uInt8(alpha*fgColor.GetRed()+(1.0-alpha)*bkColor.GetRed()), + sal_uInt8(alpha*fgColor.GetGreen()+(1.0-alpha)*bkColor.GetGreen()), + sal_uInt8(alpha*fgColor.GetBlue()+(1.0-alpha)*bkColor.GetBlue())); + } + + //! returns true if this is the default pattern + bool isDefault() const { return !isRead; } + + enum PenStyle { PEN_NULL, PEN_SOLID, PEN_DOT, PEN_DASH, PEN_DASHDOT }; + enum BrushStyle { BRUSH_SOLID, BRUSH_HORZ, BRUSH_VERT, + BRUSH_CROSS, BRUSH_DIAGCROSS, BRUSH_UPDIAG, BRUSH_DOWNDIAG, + BRUSH_25, BRUSH_50, BRUSH_75 }; + // Data + enum PenStyle penStyle; + enum BrushStyle brushStyle; + short nBitCount; + + bool isColor; // true if it is a color pattern + Color color; + + protected: + // flag to know if the pattern came from reading the picture, or if it is the default pattern + bool isRead; + }; + + } + + sal_uInt8 Pattern::read(SvStream &stream) { + unsigned char nbyte[8] = {0}; + isColor = false; + + // count the no of bits in pattern which are set to 1: + nBitCount=0; + for (unsigned char & ny : nbyte) { + stream.ReadChar( reinterpret_cast<char&>(ny) ); + for (short nx=0; nx<8; nx++) { + if ( (ny & (1<<nx)) != 0 ) nBitCount++; + } + } + + // store pattern in 2 long words: + sal_uInt32 nHiBytes = (((((static_cast<sal_uInt32>(nbyte[0])<<8)| + static_cast<sal_uInt32>(nbyte[1]))<<8)| + static_cast<sal_uInt32>(nbyte[2]))<<8)| + static_cast<sal_uInt32>(nbyte[3]); + sal_uInt32 nLoBytes = (((((static_cast<sal_uInt32>(nbyte[4])<<8)| + static_cast<sal_uInt32>(nbyte[5]))<<8)| + static_cast<sal_uInt32>(nbyte[6]))<<8)| + static_cast<sal_uInt32>(nbyte[7]); + + // create a PenStyle: + if (nBitCount<=0) penStyle=PEN_NULL; + else if (nBitCount<=16) penStyle=PEN_DOT; + else if (nBitCount<=32) penStyle=PEN_DASHDOT; + else if (nBitCount<=48) penStyle=PEN_DASH; + else penStyle=PEN_SOLID; + + // create a BrushStyle: + if (nHiBytes==0xffffffff && nLoBytes==0xffffffff) brushStyle=BRUSH_SOLID; + else if (nHiBytes==0xff000000 && nLoBytes==0x00000000) brushStyle=BRUSH_HORZ; + else if (nHiBytes==0x80808080 && nLoBytes==0x80808080) brushStyle=BRUSH_VERT; + else if (nHiBytes==0xff808080 && nLoBytes==0x80808080) brushStyle=BRUSH_CROSS; + else if (nHiBytes==0x01824428 && nLoBytes==0x10284482) brushStyle=BRUSH_DIAGCROSS; + else if (nHiBytes==0x80402010 && nLoBytes==0x08040201) brushStyle=BRUSH_UPDIAG; + else if (nHiBytes==0x01020408 && nLoBytes==0x10204080) brushStyle=BRUSH_DOWNDIAG; + else if (nBitCount<=24) brushStyle=BRUSH_25; + else if (nBitCount<=40) brushStyle=BRUSH_50; + else if (nBitCount<=56) brushStyle=BRUSH_75; + else brushStyle=BRUSH_SOLID; + + isRead = true; + + return 8; + } +} + +//============================ PictReader ================================== + +namespace { + +enum class PictDrawingMethod { + FRAME, PAINT, ERASE, INVERT, FILL, + TEXT, UNDEFINED +}; + +class PictReader { + typedef class PictReaderInternal::Pattern Pattern; +private: + + SvStream * pPict; // The Pict file to read. + VclPtr<VirtualDevice> pVirDev; // Here the drawing method will be called. + // A recording into the GDIMetaFile will take place. + + sal_uInt64 nOrigPos; // Initial position in pPict. + bool IsVersion2; // If it is a version 2 Pictfile. + tools::Rectangle aBoundingRect; // Min/Max-Rectangle for the whole drawing. + + Point aPenPosition; + Point aTextPosition; + Color aActForeColor; + Color aActBackColor; + Pattern eActPenPattern; + Pattern eActFillPattern; + Pattern eActBackPattern; + Size nActPenSize; + // Note: Postscript mode is stored by setting eActRop to RasterOp::N1 + RasterOp eActROP; + PictDrawingMethod eActMethod; + Size aActOvalSize; + vcl::Font aActFont; + + Fraction aHRes; + Fraction aVRes; + + Point ReadPoint(); + + Point ReadDeltaH(Point aBase); + Point ReadDeltaV(Point aBase); + + Point ReadUnsignedDeltaH(Point aBase); + Point ReadUnsignedDeltaV(Point aBase); + + Size ReadSize(); + + Color ReadColor(); + + Color ReadRGBColor(); + + void ReadRectangle(tools::Rectangle & rRect); + + sal_uInt64 ReadPolygon(tools::Polygon & rPoly); + + sal_uInt64 ReadPixPattern(Pattern &pattern); + + tools::Rectangle aLastRect; + sal_uInt8 ReadAndDrawRect(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSameRect(PictDrawingMethod eMethod); + + tools::Rectangle aLastRoundRect; + sal_uInt8 ReadAndDrawRoundRect(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSameRoundRect(PictDrawingMethod eMethod); + + tools::Rectangle aLastOval; + sal_uInt8 ReadAndDrawOval(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSameOval(PictDrawingMethod eMethod); + + tools::Polygon aLastPolygon; + sal_uInt64 ReadAndDrawPolygon(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSamePolygon(PictDrawingMethod eMethod); + + tools::Rectangle aLastArcRect; + sal_uInt8 ReadAndDrawArc(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSameArc(PictDrawingMethod eMethod); + + sal_uInt64 ReadAndDrawRgn(PictDrawingMethod eMethod); + sal_uInt8 ReadAndDrawSameRgn(PictDrawingMethod eMethod); + + // returns true if there's no need to print the shape/text/frame + bool IsInvisible( PictDrawingMethod eMethod ) const { + if ( eActROP == RasterOp::N1 ) return true; + if ( eMethod == PictDrawingMethod::FRAME && nActPenSize.IsEmpty() ) return true; + return false; + } + + void DrawingMethod(PictDrawingMethod eMethod); + + sal_uInt64 ReadAndDrawText(); + + sal_uInt64 ReadPixMapEtc(BitmapEx & rBitmap, bool bBaseAddr, bool bColorTable, + tools::Rectangle * pSrcRect, tools::Rectangle * pDestRect, + bool bMode, bool bMaskRgn); + + void ReadHeader(); + // Reads the header of the Pict file, set IsVersion and aBoundingRect + + sal_uInt64 ReadData(sal_uInt16 nOpcode); + // Reads the date of anOopcode and executes the operation. + // The number of data bytes belonging to the opcode will be returned + // in any case. + + void SetLineColor( const Color& rColor ); + void SetFillColor( const Color& rColor ); + + // OSNOLA: returns the text encoding which must be used for system id + static rtl_TextEncoding GetTextEncoding (sal_uInt16 fId = 0xFFFF); + +public: + + PictReader() + : pPict(nullptr) + , pVirDev(nullptr) + , nOrigPos(0) + , IsVersion2(false) + , eActROP(RasterOp::OverPaint) + , eActMethod(PictDrawingMethod::UNDEFINED) + { + aActFont.SetCharSet(GetTextEncoding()); + } + + void ReadPict( SvStream & rStreamPict, GDIMetaFile & rGDIMetaFile ); + // reads a pict file from the stream and fills the GDIMetaFile + +}; + +} + +static void SetByte(sal_uInt16& nx, sal_uInt16 ny, vcl::bitmap::RawBitmap& rBitmap, sal_uInt16 nPixelSize, sal_uInt8 nDat, sal_uInt16 nWidth, std::vector<Color> const & rvPalette) +{ + switch (nPixelSize) + { + case 1: + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 7) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 6) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 5) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 4) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 3) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 2) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat >> 1) & 1]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 1]); + break; + case 2: + rBitmap.SetPixel(ny, nx++, rvPalette[nDat >> 6]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat>>4)&3]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[(nDat>>2)&3]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 3]); + break; + case 4: + rBitmap.SetPixel(ny, nx++, rvPalette[nDat >> 4]); + if ( nx == nWidth ) break; + rBitmap.SetPixel(ny, nx++, rvPalette[nDat & 0x0f]); + break; + case 8: + rBitmap.SetPixel(ny, nx++, rvPalette[nDat]); + break; + } +} + +//=================== methods of PictReader ============================== +rtl_TextEncoding PictReader::GetTextEncoding (sal_uInt16 fId) { + static rtl_TextEncoding enc = []() + { + rtl_TextEncoding def = osl_getThreadTextEncoding(); + // we keep osl_getThreadTextEncoding only if it is a mac encoding + switch(def) { + case RTL_TEXTENCODING_APPLE_ROMAN: + case RTL_TEXTENCODING_APPLE_ARABIC: + case RTL_TEXTENCODING_APPLE_CENTEURO: + case RTL_TEXTENCODING_APPLE_CROATIAN: + case RTL_TEXTENCODING_APPLE_CYRILLIC: + case RTL_TEXTENCODING_APPLE_DEVANAGARI: + case RTL_TEXTENCODING_APPLE_FARSI: + case RTL_TEXTENCODING_APPLE_GREEK: + case RTL_TEXTENCODING_APPLE_GUJARATI: + case RTL_TEXTENCODING_APPLE_GURMUKHI: + case RTL_TEXTENCODING_APPLE_HEBREW: + case RTL_TEXTENCODING_APPLE_ICELAND: + case RTL_TEXTENCODING_APPLE_ROMANIAN: + case RTL_TEXTENCODING_APPLE_THAI: + case RTL_TEXTENCODING_APPLE_TURKISH: + case RTL_TEXTENCODING_APPLE_UKRAINIAN: + case RTL_TEXTENCODING_APPLE_CHINSIMP: + case RTL_TEXTENCODING_APPLE_CHINTRAD: + case RTL_TEXTENCODING_APPLE_JAPANESE: + case RTL_TEXTENCODING_APPLE_KOREAN: + return def; break; + default: + break; + } + return RTL_TEXTENCODING_APPLE_ROMAN; + }(); + if (fId == 13) return RTL_TEXTENCODING_ADOBE_DINGBATS; // CHECKME + if (fId == 23) return RTL_TEXTENCODING_ADOBE_SYMBOL; + return enc; +} + +void PictReader::SetLineColor( const Color& rColor ) +{ + pVirDev->SetLineColor( rColor ); +} + +void PictReader::SetFillColor( const Color& rColor ) +{ + pVirDev->SetFillColor( rColor ); +} + +Point PictReader::ReadPoint() +{ + short nx(0), ny(0); + + pPict->ReadInt16( ny ).ReadInt16( nx ); + + Point aPoint(nx - aBoundingRect.Left(), ny - aBoundingRect.Top()); + + SAL_INFO("filter.pict", "ReadPoint: " << aPoint); + return aPoint; +} + +Point PictReader::ReadDeltaH(Point aBase) +{ + signed char ndh(0); + + pPict->ReadChar( reinterpret_cast<char&>(ndh) ); + + return Point( aBase.X() + static_cast<tools::Long>(ndh), aBase.Y() ); +} + +Point PictReader::ReadDeltaV(Point aBase) +{ + signed char ndv(0); + + pPict->ReadChar( reinterpret_cast<char&>(ndv) ); + + return Point( aBase.X(), aBase.Y() + static_cast<tools::Long>(ndv) ); +} + +Point PictReader::ReadUnsignedDeltaH(Point aBase) +{ + sal_uInt8 ndh(0); + + pPict->ReadUChar( ndh ); + + return Point( aBase.X() + static_cast<tools::Long>(ndh), aBase.Y() ); +} + +Point PictReader::ReadUnsignedDeltaV(Point aBase) +{ + sal_uInt8 ndv(0); + + pPict->ReadUChar( ndv ); + + return Point( aBase.X(), aBase.Y() + static_cast<tools::Long>(ndv) ); +} + +Size PictReader::ReadSize() +{ + short nx(0), ny(0); + + pPict->ReadInt16( ny ).ReadInt16( nx ); + + return Size(nx, ny); +} + +Color PictReader::ReadColor() +{ + Color aCol; + + sal_uInt32 nCol(0); + pPict->ReadUInt32( nCol ); + switch (nCol) + { + case 33: aCol=COL_BLACK; break; + case 30: aCol=COL_WHITE; break; + case 205: aCol=COL_LIGHTRED; break; + case 341: aCol=COL_LIGHTGREEN; break; + case 409: aCol=COL_LIGHTBLUE; break; + case 273: aCol=COL_LIGHTCYAN; break; + case 137: aCol=COL_LIGHTMAGENTA; break; + case 69: aCol=COL_YELLOW; break; + default: aCol=COL_LIGHTGRAY; + } + return aCol; +} + +Color PictReader::ReadRGBColor() +{ + sal_uInt16 nR(0), nG(0), nB(0); + + pPict->ReadUInt16( nR ).ReadUInt16( nG ).ReadUInt16( nB ); + return Color( static_cast<sal_uInt8>( nR >> 8 ), static_cast<sal_uInt8>( nG >> 8 ), static_cast<sal_uInt8>( nB >> 8 ) ); +} + +void PictReader::ReadRectangle(tools::Rectangle & rRect) +{ + Point aTopLeft = ReadPoint(); + Point aBottomRight = ReadPoint(); + if (!pPict->good() || aTopLeft.X() > aBottomRight.X() || aTopLeft.Y() > aBottomRight.Y()) + { + SAL_WARN("filter.pict", "broken rectangle"); + pPict->SetError( SVSTREAM_FILEFORMAT_ERROR ); + rRect = tools::Rectangle(); + return; + } + rRect=tools::Rectangle(aTopLeft,aBottomRight); + + SAL_INFO("filter.pict", "ReadRectangle: " << rRect); +} + +sal_uInt64 PictReader::ReadPolygon(tools::Polygon & rPoly) +{ + sal_uInt16 nSize(0); + pPict->ReadUInt16(nSize); + pPict->SeekRel(8); + sal_uInt64 nDataSize = static_cast<sal_uInt64>(nSize); + nSize=(nSize-10)/4; + const size_t nMaxPossiblePoints = pPict->remainingSize() / 2 * sizeof(sal_uInt16); + if (nSize > nMaxPossiblePoints) + { + SAL_WARN("filter.pict", "pict record claims to have: " << nSize << " points, but only " << nMaxPossiblePoints << " possible, clamping"); + nSize = nMaxPossiblePoints; + } + rPoly.SetSize(nSize); + for (sal_uInt16 i = 0; i < nSize; ++i) + { + rPoly.SetPoint(ReadPoint(), i); + if (!pPict->good()) + { + rPoly.SetSize(i); + break; + } + } + return nDataSize; +} + +sal_uInt64 PictReader::ReadPixPattern(PictReader::Pattern &pattern) +{ + // Don't know if this is correct because no picture which contains PixPatterns found. + // Here again the attempt to calculate the size of the date to create simple StarView-Styles + // from them. Luckily a PixPattern always contains a normal pattern. + + sal_uInt64 nDataSize; + + sal_uInt16 nPatType(0); + pPict->ReadUInt16(nPatType); + if (nPatType==1) { + pattern.read(*pPict); + BitmapEx aBMP; + nDataSize=ReadPixMapEtc(aBMP,false,true,nullptr,nullptr,false,false); + // CHANGEME: use average pixmap colors to update the pattern, ... + if (nDataSize!=0xffffffff) nDataSize+=10; + } + else if (nPatType==2) { + pattern.read(*pPict); + // RGBColor + sal_uInt16 nR, nG, nB; + pPict->ReadUInt16( nR ).ReadUInt16( nG ).ReadUInt16( nB ); + Color col(static_cast<sal_uInt8>( nR >> 8 ), static_cast<sal_uInt8>( nG >> 8 ), static_cast<sal_uInt8>( nB >> 8 ) ); + pattern.setColor(col); + nDataSize=16; + } + else nDataSize=0xffffffff; + + return nDataSize; +} + +sal_uInt8 PictReader::ReadAndDrawRect(PictDrawingMethod eMethod) +{ + ReadRectangle(aLastRect); + ReadAndDrawSameRect(eMethod); + return 8; +} + +sal_uInt8 PictReader::ReadAndDrawSameRect(PictDrawingMethod eMethod) +{ + if (IsInvisible(eMethod)) return 0; + DrawingMethod(eMethod); + PictReaderShape::drawRectangle( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastRect, nActPenSize ); + return 0; +} + +sal_uInt8 PictReader::ReadAndDrawRoundRect(PictDrawingMethod eMethod) +{ + ReadRectangle(aLastRoundRect); + ReadAndDrawSameRoundRect(eMethod); + return 8; +} + +sal_uInt8 PictReader::ReadAndDrawSameRoundRect(PictDrawingMethod eMethod) +{ + if (IsInvisible(eMethod)) return 0; + DrawingMethod(eMethod); + PictReaderShape::drawRoundRectangle( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastRoundRect, aActOvalSize, nActPenSize ); + return 0; +} + +sal_uInt8 PictReader::ReadAndDrawOval(PictDrawingMethod eMethod) +{ + ReadRectangle(aLastOval); + ReadAndDrawSameOval(eMethod); + return 8; +} + +sal_uInt8 PictReader::ReadAndDrawSameOval(PictDrawingMethod eMethod) +{ + if (IsInvisible(eMethod)) return 0; + DrawingMethod(eMethod); + PictReaderShape::drawEllipse( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastOval, nActPenSize ); + return 0; +} + +sal_uInt64 PictReader::ReadAndDrawPolygon(PictDrawingMethod eMethod) +{ + sal_uInt64 nDataSize; + nDataSize=ReadPolygon(aLastPolygon); + ReadAndDrawSamePolygon(eMethod); + return nDataSize; +} + +sal_uInt8 PictReader::ReadAndDrawSamePolygon(PictDrawingMethod eMethod) +{ + if (IsInvisible(eMethod)) return 0; + DrawingMethod(eMethod); + PictReaderShape::drawPolygon( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastPolygon, nActPenSize ); + return 0; +} + + +sal_uInt8 PictReader::ReadAndDrawArc(PictDrawingMethod eMethod) +{ + ReadRectangle(aLastArcRect); + ReadAndDrawSameArc(eMethod); + return 12; +} + +sal_uInt8 PictReader::ReadAndDrawSameArc(PictDrawingMethod eMethod) +{ + short nstartAngle, narcAngle; + + pPict->ReadInt16( nstartAngle ).ReadInt16( narcAngle ); + if (!pPict->good() || IsInvisible(eMethod)) return 4; + DrawingMethod(eMethod); + + if (narcAngle<0) { + nstartAngle = nstartAngle + narcAngle; + narcAngle=-narcAngle; + } + const double pi = 2 * acos(0.0); + double fAng1 = static_cast<double>(nstartAngle) * pi / 180.0; + double fAng2 = static_cast<double>(nstartAngle + narcAngle) * pi / 180.0; + PictReaderShape::drawArc( pVirDev, eMethod == PictDrawingMethod::FRAME, aLastArcRect, fAng1, fAng2, nActPenSize ); + return 4; +} + +sal_uInt64 PictReader::ReadAndDrawRgn(PictDrawingMethod eMethod) +{ + sal_uInt16 nSize(0); + pPict->ReadUInt16( nSize ); + + // read the DATA + // + // a region data is a mask and is probably coded as + // - the first 8 bytes: bdbox ( which can be read by ReadRectangle ) + // - then a list of line modifiers: y_i, a_0, b_0, a_1, b_1, ..., a_{n_i}, b_{n_i}, 0x7fff + // - 0x7fff + // where y_i is the increasing sequences of line coordinates + // and on each line: a0 < b0 < a1 < b1 < ... < a_{n_i} < b_{n_i} + + // it can be probably decoded as : + // M=an empty mask: ie. (0, 0, ... ) with (left_box-right_box+1) zeroes + // then for each line (y_i): + // - takes M and inverts all values in [a_0,b_0-1], in [a_1,b_1-1] ... + // - sets M = new y_i line mask + ReadAndDrawSameRgn(eMethod); + return static_cast<sal_uInt64>(nSize); +} + +sal_uInt8 PictReader::ReadAndDrawSameRgn(PictDrawingMethod eMethod) +{ + if (IsInvisible(eMethod)) return 0; + DrawingMethod(eMethod); + // DISPLAY: ...???... + return 0; +} + +void PictReader::DrawingMethod(PictDrawingMethod eMethod) +{ + if( eActMethod==eMethod ) return; + switch (eMethod) { + case PictDrawingMethod::FRAME: + if (eActPenPattern.isDefault()) + SetLineColor( aActForeColor ); + else + SetLineColor(eActPenPattern.getColor(aActBackColor, aActForeColor)); + SetFillColor( COL_TRANSPARENT ); + pVirDev->SetRasterOp(eActROP); + break; + case PictDrawingMethod::PAINT: + SetLineColor( COL_TRANSPARENT ); + if (eActPenPattern.isDefault()) + SetFillColor( aActForeColor ); + else + SetFillColor(eActPenPattern.getColor(aActBackColor, aActForeColor)); + pVirDev->SetRasterOp(eActROP); + break; + case PictDrawingMethod::ERASE: + SetLineColor( COL_TRANSPARENT ); + if (eActBackPattern.isDefault()) + SetFillColor( aActBackColor );// Osnola: previously aActForeColor + else // checkMe + SetFillColor(eActBackPattern.getColor(COL_BLACK, aActBackColor)); + pVirDev->SetRasterOp(RasterOp::OverPaint); + break; + case PictDrawingMethod::INVERT: // checkme + SetLineColor( COL_TRANSPARENT); + SetFillColor( COL_BLACK ); + pVirDev->SetRasterOp(RasterOp::Invert); + break; + case PictDrawingMethod::FILL: + SetLineColor( COL_TRANSPARENT ); + if (eActFillPattern.isDefault()) + SetFillColor( aActForeColor ); + else + SetFillColor(eActFillPattern.getColor(aActBackColor, aActForeColor)); + pVirDev->SetRasterOp(RasterOp::OverPaint); + break; + case PictDrawingMethod::TEXT: + aActFont.SetColor(aActForeColor); + aActFont.SetFillColor(aActBackColor); + aActFont.SetTransparent(true); + pVirDev->SetFont(aActFont); + pVirDev->SetRasterOp(RasterOp::OverPaint); + break; + default: + break; // -Wall undefined not handled... + } + eActMethod=eMethod; +} + +sal_uInt64 PictReader::ReadAndDrawText() +{ + char sText[256]; + + char nByteLen(0); + pPict->ReadChar(nByteLen); + sal_uInt32 nLen = static_cast<sal_uInt32>(nByteLen)&0x000000ff; + sal_uInt32 nDataLen = nLen + 1; + nLen = pPict->ReadBytes(&sText, nLen); + + if (IsInvisible( PictDrawingMethod::TEXT )) return nDataLen; + DrawingMethod( PictDrawingMethod::TEXT ); + + // remove annoying control characters: + while ( nLen > 0 && static_cast<unsigned char>(sText[ nLen - 1 ]) < 32 ) + nLen--; + sText[ nLen ] = 0; + OUString aString( sText, strlen(sText), aActFont.GetCharSet()); + pVirDev->DrawText( Point( aTextPosition.X(), aTextPosition.Y() ), aString ); + return nDataLen; +} + +sal_uInt64 PictReader::ReadPixMapEtc( BitmapEx &rBitmap, bool bBaseAddr, bool bColorTable, tools::Rectangle* pSrcRect, + tools::Rectangle* pDestRect, bool bMode, bool bMaskRgn ) +{ + std::unique_ptr<vcl::bitmap::RawBitmap> pBitmap; + sal_uInt16 nPackType(0), nPixelSize(0), nCmpCount(0), nCmpSize(0); + sal_uInt8 nDat(0), nRed(0), nGreen(0), nBlue(0); + + // The calculation of nDataSize is considering the size of the whole data. + size_t nDataSize = 0; + + // conditionally skip BaseAddr + if ( bBaseAddr ) + { + pPict->SeekRel( 4 ); + nDataSize += 4; + } + + // Read PixMap or Bitmap structure; + sal_uInt16 nRowBytes(0), nBndX(0), nBndY(0), nWidth(0), nHeight(0); + pPict->ReadUInt16(nRowBytes).ReadUInt16(nBndY).ReadUInt16(nBndX).ReadUInt16(nHeight).ReadUInt16(nWidth); + if (nBndY > nHeight) + return 0xffffffff; + nHeight = nHeight - nBndY; + if (nHeight == 0) + return 0xffffffff; + if (nBndX > nWidth) + return 0xffffffff; + nWidth = nWidth - nBndX; + if (nWidth == 0) + return 0xffffffff; + + std::vector<Color> aPalette; + const bool bNotMonoChrome = (nRowBytes & 0x8000) != 0; + if (bNotMonoChrome) + { // it is a PixMap + nRowBytes &= 0x3fff; + sal_uInt16 nVersion; + sal_uInt32 nPackSize; + sal_uInt16 nPixelType; + sal_uInt32 nPlaneBytes; + sal_uInt32 nHRes, nVRes; + pPict->ReadUInt16( nVersion ).ReadUInt16( nPackType ).ReadUInt32( nPackSize ).ReadUInt32( nHRes ).ReadUInt32( nVRes ).ReadUInt16( nPixelType ).ReadUInt16( nPixelSize ).ReadUInt16( nCmpCount ).ReadUInt16( nCmpSize ).ReadUInt32( nPlaneBytes ); + + pPict->SeekRel( 8 ); + nDataSize += 46; + + if ( bColorTable ) + { + pPict->SeekRel( 6 ); + sal_uInt16 nColTabSize(0); + pPict->ReadUInt16(nColTabSize); + + if (nColTabSize > 255) + return 0xffffffff; + + ++nColTabSize; + + aPalette.resize(nColTabSize); + + for (size_t i = 0; i < nColTabSize; ++i) + { + pPict->SeekRel(2); + sal_uInt8 nDummy; + pPict->ReadUChar( nRed ).ReadUChar( nDummy ).ReadUChar( nGreen ).ReadUChar( nDummy ).ReadUChar( nBlue ).ReadUChar( nDummy ); + aPalette[i] = Color(nRed, nGreen, nBlue); + } + + nDataSize += 8 + nColTabSize * 8; + } + } + else + { + nRowBytes &= 0x3fff; + nPixelSize = nCmpCount = nCmpSize = 1; + nDataSize += 10; + aPalette.resize(2); + aPalette[0] = Color(0xff, 0xff, 0xff); + aPalette[1] = Color(0, 0, 0); + } + + // conditionally read source rectangle: + if ( pSrcRect != nullptr) + { + sal_uInt16 nTop, nLeft, nBottom, nRight; + pPict->ReadUInt16( nTop ).ReadUInt16( nLeft ).ReadUInt16( nBottom ).ReadUInt16( nRight ); + *pSrcRect = tools::Rectangle(nLeft, nTop, nRight, nBottom); + nDataSize += 8; + } + + // conditionally read destination rectangle: + if ( pDestRect != nullptr ) + { + Point aTL = ReadPoint(); + Point aBR = ReadPoint(); + *pDestRect = tools::Rectangle( aTL, aBR ); + nDataSize += 8; + } + + // conditionally read mode (or skip it): + if ( bMode ) + { + pPict->SeekRel(2); + nDataSize += 2; + } + + // conditionally read region (or skip it): + if ( bMaskRgn ) + { + sal_uInt16 nSize(0); + pPict->ReadUInt16( nSize ); + pPict->SeekRel( nSize - 2 ); + nDataSize += nSize; + } + + // read and write Bitmap bits: + if ( nPixelSize == 1 || nPixelSize == 2 || nPixelSize == 4 || nPixelSize == 8 ) + { + sal_uInt16 nSrcBPL, nDestBPL; + size_t nCount; + + if ( nPixelSize == 1 ) nSrcBPL = ( nWidth + 7 ) >> 3; + else if ( nPixelSize == 2 ) nSrcBPL = ( nWidth + 3 ) >> 2; + else if ( nPixelSize == 4 ) nSrcBPL = ( nWidth + 1 ) >> 1; + else nSrcBPL = nWidth; + nDestBPL = ( nSrcBPL + 3 ) & 0xfffc; + if (!nRowBytes || nRowBytes < nSrcBPL || nRowBytes > nDestBPL) + return 0xffffffff; + + if (nRowBytes < 8 || nPackType == 1) + { + if (nHeight > pPict->remainingSize() / (sizeof(sal_uInt8) * nRowBytes)) + return 0xffffffff; + } + else + { + size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8); + if (nHeight > pPict->remainingSize() / nByteCountSize) + return 0xffffffff; + } + + pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 )); + + for (sal_uInt16 ny = 0; ny < nHeight; ++ny) + { + sal_uInt16 nx = 0; + if ( nRowBytes < 8 || nPackType == 1 ) + { + for (size_t i = 0; i < nRowBytes; ++i) + { + pPict->ReadUChar( nDat ); + if ( nx < nWidth ) + SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette); + } + nDataSize += nRowBytes; + } + else + { + sal_uInt16 nByteCount(0); + if ( nRowBytes > 250 ) + { + pPict->ReadUInt16( nByteCount ); + nDataSize += 2 + static_cast<sal_uInt32>(nByteCount); + } + else + { + sal_uInt8 nByteCountAsByte(0); + pPict->ReadUChar( nByteCountAsByte ); + nByteCount = static_cast<sal_uInt16>(nByteCountAsByte) & 0x00ff; + nDataSize += 1 + nByteCount; + } + + while (pPict->good() && nByteCount) + { + sal_uInt8 nFlagCounterByte(0); + pPict->ReadUChar(nFlagCounterByte); + if ( ( nFlagCounterByte & 0x80 ) == 0 ) + { + nCount = static_cast<sal_uInt16>(nFlagCounterByte) + 1; + for (size_t i = 0; i < nCount; ++i) + { + pPict->ReadUChar( nDat ); + if ( nx < nWidth ) + SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette); + } + nByteCount -= 1 + nCount; + } + else + { + nCount = static_cast<sal_uInt16>( 1 - sal_Int16( static_cast<sal_uInt16>(nFlagCounterByte) | 0xff00 ) ); + pPict->ReadUChar( nDat ); + for (size_t i = 0; i < nCount; ++i) + { + if ( nx < nWidth ) + SetByte(nx, ny, *pBitmap, nPixelSize, nDat, nWidth, aPalette); + } + nByteCount -= 2; + } + } + } + } + } + else if ( nPixelSize == 16 ) + { + sal_uInt8 nByteCountAsByte, nFlagCounterByte; + sal_uInt16 nByteCount, nCount, nD; + sal_uInt64 nSrcBitsPos; + + if (nWidth > nRowBytes / 2) + return 0xffffffff; + + if (nRowBytes < 8 || nPackType == 1) + { + if (nHeight > pPict->remainingSize() / (sizeof(sal_uInt8) * nRowBytes)) + return 0xffffffff; + } + else + { + size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8); + if (nHeight > pPict->remainingSize() / nByteCountSize) + return 0xffffffff; + } + + pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 )); + + for (sal_uInt16 ny = 0; ny < nHeight; ++ny) + { + sal_uInt16 nx = 0; + if ( nRowBytes < 8 || nPackType == 1 ) + { + for (size_t i = 0; i < nWidth; ++i) + { + pPict->ReadUInt16( nD ); + nRed = static_cast<sal_uInt8>( nD >> 7 ); + nGreen = static_cast<sal_uInt8>( nD >> 2 ); + nBlue = static_cast<sal_uInt8>( nD << 3 ); + pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue)); + } + nDataSize += static_cast<sal_uInt32>(nWidth) * 2; + } + else + { + nSrcBitsPos = pPict->Tell(); + if ( nRowBytes > 250 ) + { + pPict->ReadUInt16( nByteCount ); + nByteCount += 2; + } + else + { + pPict->ReadUChar( nByteCountAsByte ); + nByteCount = static_cast<sal_uInt16>(nByteCountAsByte) & 0x00ff; + nByteCount++; + } + while ( nx != nWidth ) + { + pPict->ReadUChar( nFlagCounterByte ); + if ( (nFlagCounterByte & 0x80) == 0) + { + nCount=static_cast<sal_uInt16>(nFlagCounterByte)+1; + if ( nCount + nx > nWidth) + nCount = nWidth - nx; + if (pPict->remainingSize() < sizeof(sal_uInt16) * nCount) + return 0xffffffff; + /* SJ: the RLE decoding seems not to be correct here, + I don't want to change this until I have a bugdoc for + this case. Have a look at 32bit, there I changed the + encoding, so that it is used a straight forward array + */ + for (size_t i = 0; i < nCount; ++i) + { + pPict->ReadUInt16( nD ); + nRed = static_cast<sal_uInt8>( nD >> 7 ); + nGreen = static_cast<sal_uInt8>( nD >> 2 ); + nBlue = static_cast<sal_uInt8>( nD << 3 ); + pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue)); + } + } + else + { + if (pPict->remainingSize() < sizeof(sal_uInt16)) + return 0xffffffff; + nCount=(1-sal_Int16(static_cast<sal_uInt16>(nFlagCounterByte)|0xff00)); + if ( nCount + nx > nWidth ) + nCount = nWidth - nx; + pPict->ReadUInt16( nD ); + nRed = static_cast<sal_uInt8>( nD >> 7 ); + nGreen = static_cast<sal_uInt8>( nD >> 2 ); + nBlue = static_cast<sal_uInt8>( nD << 3 ); + for (size_t i = 0; i < nCount; ++i) + { + pBitmap->SetPixel(ny, nx++, Color(nRed, nGreen, nBlue)); + } + } + } + nDataSize += nByteCount; + pPict->Seek(nSrcBitsPos+nByteCount); + } + } + } + else if ( nPixelSize == 32 ) + { + sal_uInt16 nByteCount; + size_t nCount; + sal_uInt64 nSrcBitsPos; + if ( nRowBytes != 4*nWidth ) + return 0xffffffff; + + if ( nRowBytes < 8 || nPackType == 1 ) + { + const size_t nMaxPixels = pPict->remainingSize() / 4; + const size_t nMaxRows = nMaxPixels / nWidth; + if (nHeight > nMaxRows) + return 0xffffffff; + const size_t nMaxCols = nMaxPixels / nHeight; + if (nWidth > nMaxCols) + return 0xffffffff; + + pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 )); + + for (sal_uInt16 ny = 0; ny < nHeight; ++ny) + { + for (sal_uInt16 nx = 0; nx < nWidth; ++nx) + { + sal_uInt8 nDummy; + pPict->ReadUChar( nDummy ).ReadUChar( nRed ).ReadUChar( nGreen ).ReadUChar( nBlue ); + pBitmap->SetPixel(ny, nx, Color(nRed, nGreen, nBlue)); + } + nDataSize += static_cast<sal_uInt32>(nWidth) * 4; + } + } + else if ( nPackType == 2 ) + { + const size_t nMaxPixels = pPict->remainingSize() / 3; + const size_t nMaxRows = nMaxPixels / nWidth; + if (nHeight > nMaxRows) + return 0xffffffff; + const size_t nMaxCols = nMaxPixels / nHeight; + if (nWidth > nMaxCols) + return 0xffffffff; + + pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 )); + + for (sal_uInt16 ny = 0; ny < nHeight; ++ny) + { + for (sal_uInt16 nx = 0; nx < nWidth; ++nx) + { + pPict->ReadUChar( nRed ).ReadUChar( nGreen ).ReadUChar( nBlue ); + pBitmap->SetPixel(ny, nx, Color(nRed, nGreen, nBlue)); + } + nDataSize += static_cast<sal_uInt32>(nWidth) * 3; + } + } + else + { + sal_uInt8 nByteCountAsByte; + sal_uInt8 nFlagCounterByte; + if ( ( nCmpCount == 3 ) || ( nCmpCount == 4 ) ) + { + size_t nByteCountSize = nRowBytes > 250 ? sizeof(sal_uInt16) : sizeof(sal_uInt8); + if (nHeight > pPict->remainingSize() / nByteCountSize) + return 0xffffffff; + + pBitmap.reset(new vcl::bitmap::RawBitmap( Size(nWidth, nHeight), 24 )); + + // cid#1458434 to sanitize Untrusted loop bound + nWidth = pBitmap->Width(); + + size_t nByteWidth = static_cast<size_t>(nWidth) * nCmpCount; + std::vector<sal_uInt8> aScanline(nByteWidth); + for (sal_uInt16 ny = 0; ny < nHeight; ++ny) + { + nSrcBitsPos = pPict->Tell(); + if ( nRowBytes > 250 ) + { + pPict->ReadUInt16( nByteCount ); + nByteCount += 2; + } + else + { + pPict->ReadUChar( nByteCountAsByte ); + nByteCount = nByteCountAsByte; + nByteCount++; + } + size_t i = 0; + while (i < aScanline.size()) + { + pPict->ReadUChar( nFlagCounterByte ); + if ( ( nFlagCounterByte & 0x80 ) == 0) + { + nCount = static_cast<sal_uInt16>(nFlagCounterByte) + 1; + if ((i + nCount) > aScanline.size()) + nCount = aScanline.size() - i; + if (pPict->remainingSize() < nCount) + return 0xffffffff; + while( nCount-- ) + { + pPict->ReadUChar( nDat ); + aScanline[ i++ ] = nDat; + } + } + else + { + if (pPict->remainingSize() < 1) + return 0xffffffff; + nCount = ( 1 - sal_Int16( static_cast<sal_uInt16>(nFlagCounterByte) | 0xff00 ) ); + if (( i + nCount) > aScanline.size()) + nCount = aScanline.size() - i; + pPict->ReadUChar( nDat ); + while( nCount-- ) + aScanline[ i++ ] = nDat; + } + } + sal_uInt8* pTmp = aScanline.data(); + if ( nCmpCount == 4 ) + pTmp += nWidth; + for (sal_uInt16 nx = 0; nx < nWidth; pTmp++) + pBitmap->SetPixel(ny, nx++, Color(*pTmp, pTmp[ nWidth ], pTmp[ 2 * nWidth ])); + nDataSize += nByteCount; + pPict->Seek( nSrcBitsPos + nByteCount ); + } + } + } + } + else + return 0xffffffff; + rBitmap = vcl::bitmap::CreateFromData(std::move(*pBitmap)); + return nDataSize; +} + +void PictReader::ReadHeader() +{ + short y1,x1,y2,x2; + + char sBuf[ 2 ]; + // previous code considers pPict->Tell() as the normal starting position, + // nStartPos can be != 0 f.e. a pict embedded in a microsoft word document + sal_uInt64 nStartPos = pPict->Tell(); + // Standard: + // a picture file begins by 512 bytes (reserved to the application) followed by the picture data + // while clipboard, pictures stored in a document often contain only the picture data. + + // Special cases: + // - some Pict v.1 use 0x00 0x11 0x01 ( instead of 0x11 0x01) to store the version op + // (we consider here this as another standard for Pict. v.1 ) + // - some files seem to contain extra garbage data at the beginning + // - some picture data seem to contain extra NOP opcode(0x00) between the bounding box and the version opcode + + // This code looks hard to find a picture header, ie. it looks at positions + // - nStartPos+0, nStartPos+512 with potential extra NOP codes between bdbox and version (at most 9 extra NOP) + // - 512..1024 with more strict bdbox checking and no extra NOP codes + + // Notes: + // - if the header can begin at nStartPos+0 and at nStartPos+512, we try to choose the more + // <<probable>> ( using the variable confidence) + // - svtools/source/filter.vcl/filter/{filter.cxx,filter2.cxx} only check for standard Pict, + // this may cause future problems + int st; + sal_uInt32 nOffset; + int confidence[2] = { 0, 0}; + for ( st = 0; st < 3 + 513; st++ ) + { + int actualConfid = 20; // the actual confidence + pPict->ResetError(); + if (st < 2) nOffset = nStartPos+st*512; + else if (st == 2) { + // choose nStartPos+0 or nStartPos+512 even if there are a little dubious + int actPos = -1, actConf=0; + if (confidence[0] > 0) { actPos = 0; actConf = confidence[0]; } + if (confidence[1] > 0 && confidence[1] >= actConf) actPos = 1; + if (actPos < 0) continue; + nOffset = nStartPos+actPos*512; + } + else { + nOffset = nStartPos+509+st; + // a small test to check if versionOp code exists after the bdbox ( with no extra NOP codes) + pPict->Seek(nOffset+10); + pPict->ReadBytes(sBuf, 2); + if (!pPict->good()) break; + if (sBuf[0] == 0x11 || (sBuf[0] == 0x00 && sBuf[1] == 0x11)) ; // maybe ok + else continue; + } + pPict->Seek(nOffset); + + // 2 bytes to store size ( version 1 ) ignored + pPict->SeekRel( 2 ); + pPict->ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 ); // frame rectangle of the picture + if (x1 > x2 || y1 > y2) continue; // bad bdbox + if (x1 < -2048 || x2 > 2048 || y1 < -2048 || y2 > 2048 || // origin|dest is very small|large + (x1 == x2 && y1 == y2) ) // 1 pixel pict is dubious + actualConfid-=3; + else if (x2 < x1+8 || y2 < y1+8) // a little dubious + actualConfid-=1; + if (st >= 3 && actualConfid != 20) continue; + aBoundingRect=tools::Rectangle( x1,y1, x2, y2 ); + + if (!pPict->good()) continue; + // read version + pPict->ReadBytes(sBuf, 2); + // version 1 file + if ( sBuf[ 0 ] == 0x11 && sBuf[ 1 ] == 0x01 ) { + // pict v1 must be rare and we do only few tests + if (st < 2) { confidence[st] = --actualConfid; continue; } + IsVersion2 = false; return; + } + if (sBuf[0] != 0x00) continue; // unrecoverable error + int numZero = 0; + do + { + numZero++; + pPict->SeekRel(-1); + pPict->ReadBytes(sBuf, 2); + } + while ( sBuf[0] == 0x00 && numZero < 10); + actualConfid -= (numZero-1); // extra nop are dubious + if (!pPict->good()) continue; + if (sBuf[0] != 0x11) continue; // not a version opcode + // abnormal version 1 file + if (sBuf[1] == 0x01 ) { + // pict v1 must be rare and we do only few tests + if (st < 2) { confidence[st] = --actualConfid; continue; } + IsVersion2 = false; return; + } + if (sBuf[1] != 0x02 ) continue; // not a version 2 file + + IsVersion2=true; + short nExtVer, nReserved; + // 3 Bytes ignored : end of version arg 0x02FF (ie: 0xFF), HeaderOp : 0x0C00 + pPict->SeekRel( 3 ); + pPict->ReadInt16( nExtVer ).ReadInt16( nReserved ); + if (!pPict->good()) continue; + + if ( nExtVer == -2 ) // extended version 2 picture + { + sal_Int32 nHResFixed, nVResFixed; + pPict->ReadInt32( nHResFixed ).ReadInt32( nVResFixed ); + pPict->ReadInt16( y1 ).ReadInt16( x1 ).ReadInt16( y2 ).ReadInt16( x2 ); // reading the optimal bounding rect + if (x1 > x2 || y1 > y2) continue; // bad bdbox + if (st < 2 && actualConfid != 20) { confidence[st] = actualConfid; continue; } + + double fHRes = nHResFixed; + fHRes /= 65536; + double fVRes = nVResFixed; + fVRes /= 65536; + aHRes /= fHRes; + aVRes /= fVRes; + aBoundingRect=tools::Rectangle( x1,y1, x2, y2 ); + pPict->SeekRel( 4 ); // 4 bytes reserved + return; + } + else if (nExtVer == -1 ) { // basic version 2 picture + if (st < 2 && actualConfid != 20) { confidence[st] = actualConfid; continue; } + pPict->SeekRel( 16); // bdbox(4 fixed number) + pPict->SeekRel(4); // 4 bytes reserved + return; + } + } + pPict->SetError(SVSTREAM_FILEFORMAT_ERROR); +} + +#if OSL_DEBUG_LEVEL > 0 +static const char* operationName(sal_uInt16 nOpcode) +{ + // add here whatever makes the debugging easier for you, otherwise you'll + // see only the operation's opcode + switch (nOpcode) + { + case 0x0001: return "Clip"; + case 0x0003: return "TxFont"; + case 0x0004: return "TxFace"; + case 0x0008: return "PnMode"; + case 0x0009: return "PnPat"; + case 0x000d: return "TxSize"; + case 0x001a: return "RGBFgCol"; + case 0x001d: return "HiliteColor"; + case 0x0020: return "Line"; + case 0x0022: return "ShortLine"; + case 0x0028: return "LongText"; + case 0x0029: return "DHText"; + case 0x002a: return "DVText"; + case 0x002c: return "fontName"; + case 0x002e: return "glyphState"; + case 0x0031: return "paintRect"; + case 0x0038: return "frameSameRect"; + case 0x0070: return "framePoly"; + case 0x0071: return "paintPoly"; + case 0x00a1: return "LongComment"; + default: return "?"; + } +} +#endif + +sal_uInt64 PictReader::ReadData(sal_uInt16 nOpcode) +{ + Point aPoint; + sal_uInt64 nDataSize=0; + PictDrawingMethod shapeDMethod = PictDrawingMethod::UNDEFINED; + switch (nOpcode & 7) { + case 0: shapeDMethod = PictDrawingMethod::FRAME; break; + case 1: shapeDMethod = PictDrawingMethod::PAINT; break; + case 2: shapeDMethod = PictDrawingMethod::ERASE; break; + case 3: shapeDMethod = PictDrawingMethod::INVERT; break; + case 4: shapeDMethod = PictDrawingMethod::FILL; break; + default: break; + } + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("filter.pict", "Operation: 0x" << OUString::number(nOpcode, 16) << " [" << operationName(nOpcode) << "]"); +#endif + + switch(nOpcode) { + + case 0x0000: // NOP + nDataSize=0; + break; + + case 0x0001: { // Clip + sal_uInt16 nUSHORT(0); + tools::Rectangle aRect; + pPict->ReadUInt16( nUSHORT ); + nDataSize=nUSHORT; + ReadRectangle(aRect); + // checkme: do we really want to extend the rectangle here ? + // I do that because the clipping is often used to clean a region, + // before drawing some text and also to draw this text. + // So using a too small region can lead to clip the end of the text ; + // but this can be discussable... + aRect.setWidth(aRect.getWidth()+1); + aRect.setHeight(aRect.getHeight()+1); + pVirDev->SetClipRegion( vcl::Region( aRect ) ); + break; + } + case 0x0002: // BkPat + nDataSize = eActBackPattern.read(*pPict); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x0003: // TxFont + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + if (nUSHORT <= 1) aActFont.SetFamily(FAMILY_SWISS); + else if (nUSHORT <= 12) aActFont.SetFamily(FAMILY_DECORATIVE); + else if (nUSHORT <= 20) aActFont.SetFamily(FAMILY_ROMAN); + else if (nUSHORT == 21) aActFont.SetFamily(FAMILY_SWISS); + else if (nUSHORT == 22) aActFont.SetFamily(FAMILY_MODERN); + else if (nUSHORT <= 1023) aActFont.SetFamily(FAMILY_SWISS); + else aActFont.SetFamily(FAMILY_ROMAN); + aActFont.SetCharSet(GetTextEncoding(nUSHORT)); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=2; + break; + } + case 0x0004: { // TxFace + char nFace(0); + pPict->ReadChar( nFace ); + if ( (nFace & 0x01)!=0 ) aActFont.SetWeight(WEIGHT_BOLD); + else aActFont.SetWeight(WEIGHT_NORMAL); + if ( (nFace & 0x02)!=0 ) aActFont.SetItalic(ITALIC_NORMAL); + else aActFont.SetItalic(ITALIC_NONE); + if ( (nFace & 0x04)!=0 ) aActFont.SetUnderline(LINESTYLE_SINGLE); + else aActFont.SetUnderline(LINESTYLE_NONE); + if ( (nFace & 0x08)!=0 ) aActFont.SetOutline(true); + else aActFont.SetOutline(false); + if ( (nFace & 0x10)!=0 ) aActFont.SetShadow(true); + else aActFont.SetShadow(false); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=1; + break; + } + case 0x0005: // TxMode + nDataSize=2; + break; + + case 0x0006: // SpExtra + nDataSize=4; + break; + + case 0x0007: { // PnSize + nActPenSize=ReadSize(); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=4; + break; + } + case 0x0008: // PnMode + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + // internal code for postscript command (Quickdraw Reference Drawing B-30,B-34) + if (nUSHORT==23) eActROP = RasterOp::N1; + else { + switch (nUSHORT & 0x0007) { + case 0: eActROP=RasterOp::OverPaint; break; // Copy + case 1: eActROP=RasterOp::OverPaint; break; // Or + case 2: eActROP=RasterOp::Xor; break; // Xor + case 3: eActROP=RasterOp::OverPaint; break; // Bic + case 4: eActROP=RasterOp::Invert; break; // notCopy + case 5: eActROP=RasterOp::OverPaint; break; // notOr + case 6: eActROP=RasterOp::Xor; break; // notXor + case 7: eActROP=RasterOp::OverPaint; break; // notBic + } + } + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=2; + break; + } + case 0x0009: // PnPat + nDataSize=eActPenPattern.read(*pPict); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x000a: // FillPat + nDataSize=eActFillPattern.read(*pPict); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x000b: // OvSize + aActOvalSize=ReadSize(); + nDataSize=4; + break; + + case 0x000c: // Origin + nDataSize=4; + break; + + case 0x000d: // TxSize + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + aActFont.SetFontSize( Size( 0, static_cast<tools::Long>(nUSHORT) ) ); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=2; + } + break; + + case 0x000e: // FgColor + aActForeColor=ReadColor(); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=4; + break; + + case 0x000f: // BkColor + aActBackColor=ReadColor(); + nDataSize=4; + break; + + case 0x0010: // TxRatio + nDataSize=8; + break; + + case 0x0011: // VersionOp + nDataSize=1; + break; + + case 0x0012: // BkPixPat + nDataSize=ReadPixPattern(eActBackPattern); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x0013: // PnPixPat + nDataSize=ReadPixPattern(eActPenPattern); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x0014: // FillPixPat + nDataSize=ReadPixPattern(eActFillPattern); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + + case 0x0015: // PnLocHFrac + nDataSize=2; + break; + + case 0x0016: // ChExtra + nDataSize=2; + break; + + case 0x0017: // Reserved (0 Bytes) + case 0x0018: // Reserved (0 Bytes) + case 0x0019: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x001a: // RGBFgCol + aActForeColor=ReadRGBColor(); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=6; + break; + + case 0x001b: // RGBBkCol + aActBackColor=ReadRGBColor(); + eActMethod = PictDrawingMethod::UNDEFINED; + nDataSize=6; + break; + + case 0x001c: // HiliteMode + nDataSize=0; + break; + + case 0x001d: // HiliteColor + nDataSize=6; + break; + + case 0x001e: // DefHilite + nDataSize=0; + break; + + case 0x001f: // OpColor + nDataSize=6; + break; + + case 0x0020: // Line + aPoint=ReadPoint(); aPenPosition=ReadPoint(); + nDataSize=8; + + if (!pPict->good()) + break; + + if (IsInvisible( PictDrawingMethod::FRAME )) break; + DrawingMethod( PictDrawingMethod::FRAME ); + PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize); + break; + + case 0x0021: // LineFrom + aPoint=aPenPosition; aPenPosition=ReadPoint(); + nDataSize=4; + + if (!pPict->good()) + break; + + if (IsInvisible( PictDrawingMethod::FRAME )) break; + DrawingMethod( PictDrawingMethod::FRAME ); + PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize); + break; + + case 0x0022: // ShortLine + aPoint=ReadPoint(); + aPenPosition=ReadDeltaH(aPoint); + aPenPosition=ReadDeltaV(aPenPosition); + nDataSize=6; + + if (!pPict->good()) + break; + + if ( IsInvisible(PictDrawingMethod::FRAME) ) break; + DrawingMethod( PictDrawingMethod::FRAME ); + PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize); + break; + + case 0x0023: // ShortLineFrom + aPoint=aPenPosition; + aPenPosition=ReadDeltaH(aPoint); + aPenPosition=ReadDeltaV(aPenPosition); + nDataSize=2; + + if (!pPict->good()) + break; + + if (IsInvisible( PictDrawingMethod::FRAME )) break; + DrawingMethod( PictDrawingMethod::FRAME ); + PictReaderShape::drawLine(pVirDev, aPoint,aPenPosition, nActPenSize); + break; + + case 0x0024: // Reserved (n Bytes) + case 0x0025: // Reserved (n Bytes) + case 0x0026: // Reserved (n Bytes) + case 0x0027: // Reserved (n Bytes) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + nDataSize=2+nUSHORT; + break; + } + case 0x0028: // LongText + aTextPosition=ReadPoint(); + nDataSize=4+ReadAndDrawText(); + break; + + case 0x0029: // DHText + aTextPosition=ReadUnsignedDeltaH(aTextPosition); + nDataSize=1+ReadAndDrawText(); + break; + + case 0x002a: // DVText + aTextPosition=ReadUnsignedDeltaV(aTextPosition); + nDataSize=1+ReadAndDrawText(); + break; + + case 0x002b: // DHDVText + aTextPosition=ReadUnsignedDeltaH(aTextPosition); + aTextPosition=ReadUnsignedDeltaV(aTextPosition); + nDataSize=2+ReadAndDrawText(); + break; + + case 0x002c: { // fontName + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT+2; + pPict->ReadUInt16( nUSHORT ); + if (nUSHORT <= 1) aActFont.SetFamily(FAMILY_SWISS); + else if (nUSHORT <= 12) aActFont.SetFamily(FAMILY_DECORATIVE); + else if (nUSHORT <= 20) aActFont.SetFamily(FAMILY_ROMAN); + else if (nUSHORT == 21) aActFont.SetFamily(FAMILY_SWISS); + else if (nUSHORT == 22) aActFont.SetFamily(FAMILY_MODERN); + else if (nUSHORT <= 1023) aActFont.SetFamily(FAMILY_SWISS); + else aActFont.SetFamily(FAMILY_ROMAN); + aActFont.SetCharSet(GetTextEncoding(nUSHORT)); + char nByteLen(0); + pPict->ReadChar( nByteLen ); + sal_uInt16 nLen = static_cast<sal_uInt16>(nByteLen)&0x00ff; + char sFName[ 256 ]; + sFName[pPict->ReadBytes(sFName, nLen)] = 0; + OUString aString( sFName, strlen(sFName), osl_getThreadTextEncoding() ); + aActFont.SetFamilyName( aString ); + eActMethod = PictDrawingMethod::UNDEFINED; + break; + } + case 0x002d: // lineJustify + nDataSize=10; + break; + + case 0x002e: // glyphState + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + nDataSize=2+nUSHORT; + break; + } + case 0x002f: // Reserved (n Bytes) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); + nDataSize=2+nUSHORT; + break; + } + case 0x0030: // frameRect + case 0x0031: // paintRect + case 0x0032: // eraseRect + case 0x0033: // invertRect + case 0x0034: // fillRect + nDataSize=ReadAndDrawRect(shapeDMethod); + break; + + case 0x0035: // Reserved (8 Bytes) + case 0x0036: // Reserved (8 Bytes) + case 0x0037: // Reserved (8 Bytes) + nDataSize=8; + break; + + case 0x0038: // frameSameRect + case 0x0039: // paintSameRect + case 0x003a: // eraseSameRect + case 0x003b: // invertSameRect + case 0x003c: // fillSameRect + nDataSize=ReadAndDrawSameRect(shapeDMethod); + break; + + case 0x003d: // Reserved (0 Bytes) + case 0x003e: // Reserved (0 Bytes) + case 0x003f: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x0040: // frameRRect + case 0x0041: // paintRRect + case 0x0042: // eraseRRect + case 0x0043: // invertRRect + case 0x0044: // fillRRect + nDataSize=ReadAndDrawRoundRect(shapeDMethod); + break; + + case 0x0045: // Reserved (8 Bytes) + case 0x0046: // Reserved (8 Bytes) + case 0x0047: // Reserved (8 Bytes) + nDataSize=8; + break; + + case 0x0048: // frameSameRRect + case 0x0049: // paintSameRRect + case 0x004a: // eraseSameRRect + case 0x004b: // invertSameRRect + case 0x004c: // fillSameRRect + nDataSize=ReadAndDrawSameRoundRect(shapeDMethod); + break; + + case 0x004d: // Reserved (0 Bytes) + case 0x004e: // Reserved (0 Bytes) + case 0x004f: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x0050: // frameOval + case 0x0051: // paintOval + case 0x0052: // eraseOval + case 0x0053: // invertOval + case 0x0054: // fillOval + nDataSize=ReadAndDrawOval(shapeDMethod); + break; + + case 0x0055: // Reserved (8 Bytes) + case 0x0056: // Reserved (8 Bytes) + case 0x0057: // Reserved (8 Bytes) + nDataSize=8; + break; + + case 0x0058: // frameSameOval + case 0x0059: // paintSameOval + case 0x005a: // eraseSameOval + case 0x005b: // invertSameOval + case 0x005c: // fillSameOval + nDataSize=ReadAndDrawSameOval(shapeDMethod); + break; + + case 0x005d: // Reserved (0 Bytes) + case 0x005e: // Reserved (0 Bytes) + case 0x005f: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x0060: // frameArc + case 0x0061: // paintArc + case 0x0062: // eraseArc + case 0x0063: // invertArc + case 0x0064: // fillArc + nDataSize=ReadAndDrawArc(shapeDMethod); + break; + + case 0x0065: // Reserved (12 Bytes) + case 0x0066: // Reserved (12 Bytes) + case 0x0067: // Reserved (12 Bytes) + nDataSize=12; + break; + + case 0x0068: // frameSameArc + case 0x0069: // paintSameArc + case 0x006a: // eraseSameArc + case 0x006b: // invertSameArc + case 0x006c: // fillSameArc + nDataSize=ReadAndDrawSameArc(shapeDMethod); + break; + + case 0x006d: // Reserved (4 Bytes) + case 0x006e: // Reserved (4 Bytes) + case 0x006f: // Reserved (4 Bytes) + nDataSize=4; + break; + + case 0x0070: // framePoly + case 0x0071: // paintPoly + case 0x0072: // erasePoly + case 0x0073: // invertPoly + case 0x0074: // fillPoly + nDataSize=ReadAndDrawPolygon(shapeDMethod); + break; + + case 0x0075: // Reserved (Polygon-Size) + case 0x0076: // Reserved (Polygon-Size) + case 0x0077: // Reserved (Polygon-Size) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT; + break; + } + case 0x0078: // frameSamePoly + case 0x0079: // paintSamePoly + case 0x007a: // eraseSamePoly + case 0x007b: // invertSamePoly + case 0x007c: // fillSamePoly + nDataSize=ReadAndDrawSamePolygon(shapeDMethod); + break; + + case 0x007d: // Reserved (0 Bytes) + case 0x007e: // Reserved (0 Bytes) + case 0x007f: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x0080: // frameRgn + case 0x0081: // paintRgn + case 0x0082: // eraseRgn + case 0x0083: // invertRgn + case 0x0084: // fillRgn + nDataSize=ReadAndDrawRgn(shapeDMethod); + break; + + case 0x0085: // Reserved (Region-Size) + case 0x0086: // Reserved (Region-Size) + case 0x0087: // Reserved (Region-Size) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); nDataSize=nUSHORT; + break; + } + case 0x0088: // frameSameRgn + case 0x0089: // paintSameRgn + case 0x008a: // eraseSameRgn + case 0x008b: // invertSameRgn + case 0x008c: // fillSameRgn + nDataSize=ReadAndDrawSameRgn(shapeDMethod); + break; + + case 0x008d: // Reserved (0 Bytes) + case 0x008e: // Reserved (0 Bytes) + case 0x008f: // Reserved (0 Bytes) + nDataSize=0; + break; + + case 0x0090: { // BitsRect + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, false); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x0091: { // BitsRgn + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, true); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x0092: // Reserved (n Bytes) + case 0x0093: // Reserved (n Bytes) + case 0x0094: // Reserved (n Bytes) + case 0x0095: // Reserved (n Bytes) + case 0x0096: // Reserved (n Bytes) + case 0x0097: // Reserved (n Bytes) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT; + break; + } + case 0x0098: { // PackBitsRect + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, false); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x0099: { // PackBitsRgn + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, false, true, &aSrcRect, &aDestRect, true, true); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x009a: { // DirectBitsRect + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, true, false, &aSrcRect, &aDestRect, true, false); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x009b: { // DirectBitsRgn + BitmapEx aBmp; + tools::Rectangle aSrcRect, aDestRect; + nDataSize=ReadPixMapEtc(aBmp, true, false, &aSrcRect, &aDestRect, true, true); + DrawingMethod( PictDrawingMethod::PAINT ); + pVirDev->DrawBitmapEx(aDestRect.TopLeft(),aDestRect.GetSize(),aBmp); + break; + } + case 0x009c: // Reserved (n Bytes) + case 0x009d: // Reserved (n Bytes) + case 0x009e: // Reserved (n Bytes) + case 0x009f: // Reserved (n Bytes) + { + sal_uInt16 nUSHORT(0); + pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT; + break; + } + case 0x00a0: // ShortComment + nDataSize=2; + break; + + case 0x00a1: // LongComment + { + sal_uInt16 nUSHORT(0); + pPict->SeekRel(2); pPict->ReadUInt16( nUSHORT ); nDataSize=4+nUSHORT; + break; + } + default: // 0x00a2 bis 0xffff (most times reserved) + sal_uInt16 nUSHORT(0); + if (nOpcode<=0x00af) { pPict->ReadUInt16( nUSHORT ); nDataSize=2+nUSHORT; } + else if (nOpcode<=0x00cf) { nDataSize=0; } + else if (nOpcode<=0x00fe) { sal_uInt32 nTemp(0); pPict->ReadUInt32(nTemp) ; nDataSize = nTemp; nDataSize+=4; } + // Osnola: checkme: in the Quickdraw Ref examples ( for pict v2) + // 0x00ff(EndOfPict) is also not followed by any data... + else if (nOpcode==0x00ff) { nDataSize=IsVersion2 ? 2 : 0; } // OpEndPic + else if (nOpcode<=0x01ff) { nDataSize=2; } + else if (nOpcode<=0x0bfe) { nDataSize=4; } + else if (nOpcode<=0x0bff) { nDataSize=22; } + else if (nOpcode==0x0c00) { nDataSize=24; } // HeaderOp + else if (nOpcode<=0x7eff) { nDataSize=24; } + else if (nOpcode<=0x7fff) { nDataSize=254; } + else if (nOpcode<=0x80ff) { nDataSize=0; } + else { sal_uInt32 nTemp(0); pPict->ReadUInt32(nTemp) ; nDataSize = nTemp; nDataSize+=4; } + } + + if (nDataSize==0xffffffff) { + pPict->SetError(SVSTREAM_FILEFORMAT_ERROR); + return 0; + } + return nDataSize; +} + +void PictReader::ReadPict( SvStream & rStreamPict, GDIMetaFile & rGDIMetaFile ) +{ + try { + sal_uInt16 nOpcode; + sal_uInt8 nOneByteOpcode; + sal_uInt64 nSize; + + pPict = &rStreamPict; + nOrigPos = pPict->Tell(); + SvStreamEndian nOrigNumberFormat = pPict->GetEndian(); + + aActForeColor = COL_BLACK; + aActBackColor = COL_WHITE; + nActPenSize = Size(1,1); + eActROP = RasterOp::OverPaint; + eActMethod = PictDrawingMethod::UNDEFINED; + aActOvalSize = Size(1,1); + + aActFont.SetCharSet( GetTextEncoding()); + aActFont.SetFamily(FAMILY_SWISS); + aActFont.SetFontSize(Size(0,12)); + aActFont.SetAlignment(ALIGN_BASELINE); + + aHRes = aVRes = Fraction( 1, 1 ); + + pVirDev = VclPtr<VirtualDevice>::Create(); + pVirDev->EnableOutput(false); + rGDIMetaFile.Record(pVirDev); + + pPict->SetEndian(SvStreamEndian::BIG); + + ReadHeader(); + + aPenPosition=Point(-aBoundingRect.Left(),-aBoundingRect.Top()); + aTextPosition=aPenPosition; + + sal_uInt64 nPos=pPict->Tell(); + + for (;;) { + + if (IsVersion2 ) + pPict->ReadUInt16( nOpcode ); + else + { + pPict->ReadUChar( nOneByteOpcode ); + nOpcode=static_cast<sal_uInt16>(nOneByteOpcode); + } + + if (pPict->GetError()) + break; + + if (pPict->eof()) + { + pPict->SetError(SVSTREAM_FILEFORMAT_ERROR); + break; + } + + if (nOpcode==0x00ff) + break; + + nSize=ReadData(nOpcode); + + if ( IsVersion2 ) + { + if ( nSize & 1 ) + nSize++; + + nPos+=2+nSize; + } + else + nPos+=1+nSize; + + if (!checkSeek(*pPict, nPos)) + { + pPict->SetError(SVSTREAM_FILEFORMAT_ERROR); + break; + } + } + + pVirDev->SetClipRegion(); + rGDIMetaFile.Stop(); + pVirDev.disposeAndClear(); + + rGDIMetaFile.SetPrefMapMode( MapMode( MapUnit::MapInch, Point(), aHRes, aVRes ) ); + rGDIMetaFile.SetPrefSize( aBoundingRect.GetSize() ); + + pPict->SetEndian(nOrigNumberFormat); + + if (pPict->GetError()) pPict->Seek(nOrigPos); + } catch (...) + { + rStreamPict.SetError(SVSTREAM_FILEFORMAT_ERROR); + } +} + +void ReadPictFile(SvStream &rStreamPict, GDIMetaFile& rGDIMetaFile) +{ + PictReader aPictReader; + aPictReader.ReadPict(rStreamPict, rGDIMetaFile); +} + +//================== GraphicImport - the exported function ================ + +bool ImportPictGraphic( SvStream& rIStm, Graphic & rGraphic) +{ + GDIMetaFile aMTF; + bool bRet = false; + + ReadPictFile(rIStm, aMTF); + + if ( !rIStm.GetError() ) + { + rGraphic = Graphic( aMTF ); + bRet = true; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipict/shape.cxx b/vcl/source/filter/ipict/shape.cxx new file mode 100644 index 000000000..88a62cfd2 --- /dev/null +++ b/vcl/source/filter/ipict/shape.cxx @@ -0,0 +1,259 @@ +/* -*- 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 . + */ + +/** Osnola: +IMPORTANT NOTE: some Quickdraw lines/frames can not be "quickly" drawn exactly: +for instance, when PenSize=(1,1), the line from (0,0) to (8,0) +corresponds to the rectangle (0,0)(0,1)(9,1)(9,0), which can only be drawn + by drawing a rectangle. Drawing a non horizontal/vertical will imply to draw +a polygon, ... +Similarly, drawing the frame of a rectangle (0,0)(0,1)(9,1)(9,0) when PenSize=(1,1), +will imply to draw a rectangle (0.5,0.5)(0.5,8.5)(8.5,8.5)(8.5,0.5) with linewidth=1... + +Here, we choose: +- for horizontal/vertical lines and line with length less than five to draw the real line, +- in the other case, we keep the same shape (even if this means some "bad" coordinates) +*/ + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include "shape.hxx" + +namespace PictReaderShapePrivate { + /** returns an inside rectangle knowing the penSize in order to obtain the ``correct'' position + when we draw a frame in wide length*/ + static tools::Rectangle contractRectangle(bool drawFrame, tools::Rectangle const &rect, Size const &pSize) { + if (!drawFrame) return rect; + tools::Long penSize=(pSize.Width()+pSize.Height())/2; + if (2*penSize > rect.Right()-rect.Left()) penSize = (rect.Right()-rect.Left()+1)/2; + if (2*penSize > rect.Bottom()-rect.Top()) penSize = (rect.Bottom()-rect.Top()+1)/2; + tools::Long const X[2] = { rect.Left()+penSize/2, rect.Right()-(penSize+1)/2 }; + tools::Long const Y[2] = { rect.Top()+penSize/2, rect.Bottom()-(penSize+1)/2 }; + return tools::Rectangle(Point(X[0],Y[0]), Point(X[1], Y[1])); + } +} + +namespace PictReaderShape { + //--------- draws a horizontal/vertical/small line (by creating a "rectangle/polygon") --------- + static bool drawLineHQ(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize) { + tools::Long dir[2] = { dest.X()-orig.X(), dest.Y()-orig.Y() }; + bool vertic = dir[0] == 0; + bool horiz = dir[1] == 0; + if (!horiz && !vertic && dir[0]*dir[0]+dir[1]*dir[1] > 25) return false; + + using namespace basegfx; + B2DPolygon poly; + if (horiz || vertic) { + tools::Long X[2]={ orig.X(), dest.X() }, Y[2] = { orig.Y(), dest.Y() }; + if (horiz) { + if (X[0] < X[1]) X[1]+=pSize.Width(); + else X[0]+=pSize.Width(); + Y[1] += pSize.Height(); + } + else { + if (Y[0] < Y[1]) Y[1]+=pSize.Height(); + else Y[0]+=pSize.Height(); + X[1] += pSize.Width(); + } + poly.append(B2DPoint(X[0], Y[0])); poly.append(B2DPoint(X[1], Y[0])); + poly.append(B2DPoint(X[1], Y[1])); poly.append(B2DPoint(X[0], Y[1])); + poly.append(B2DPoint(X[0], Y[0])); + } + else { + tools::Long origPt[4][2] = { { orig.X(), orig.Y() }, { orig.X()+pSize.Width(), orig.Y() }, + { orig.X()+pSize.Width(), orig.Y()+pSize.Height() }, + { orig.X(), orig.Y()+pSize.Height() }}; + int origAvoid = dir[0] > 0 ? (dir[1] > 0 ? 2 : 1) : (dir[1] > 0 ? 3 : 0); + tools::Long destPt[4][2] = { { dest.X(), dest.Y() }, { dest.X()+pSize.Width(), dest.Y() }, + { dest.X()+pSize.Width(), dest.Y()+pSize.Height() }, + { dest.X(), dest.Y()+pSize.Height() }}; + for (int w = origAvoid+1; w < origAvoid+4; w++) { + int wh = w%4; + poly.append(B2DPoint(origPt[wh][0], origPt[wh][1])); + } + for (int w = origAvoid+3; w < origAvoid+6; w++) { + int wh = w%4; + poly.append(B2DPoint(destPt[wh][0], destPt[wh][1])); + } + int wh = (origAvoid+1)%4; + poly.append(B2DPoint(origPt[wh][0], origPt[wh][1])); + } + + // HACK: here we use the line coloring when drawing the shape + // must be changed if other parameter are changed to draw + // a line/fill shape + Color oldFColor = dev->GetFillColor(), oldLColor = dev->GetLineColor(); + dev->SetFillColor(oldLColor); dev->SetLineColor(COL_TRANSPARENT); + dev->DrawPolygon(poly); + dev->SetLineColor(oldLColor); dev->SetFillColor(oldFColor); + return true; + } + + + //-------------------- draws a line -------------------- + + void drawLine(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize) { + if (drawLineHQ(dev,orig,dest,pSize)) return; + + tools::Long penSize=(pSize.Width()+pSize.Height())/2; + tools::Long decal[2] = { pSize.Width()/2, pSize.Height()/2}; + + using namespace basegfx; + B2DPolygon poly; + poly.append(B2DPoint(double(orig.X()+decal[0]), double(orig.Y()+decal[1]))); + poly.append(B2DPoint(double(dest.X()+decal[0]), double(dest.Y()+decal[1]))); + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + } + + //-------------------- draws a rectangle -------------------- + /* Note(checkme): contradictally with the QuickDraw's reference 3-23, it seems better to consider + that the frame/content of a rectangle appears inside the given rectangle. Does a conversion + appear between the pascal functions and the data stored in the file ? */ + void drawRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize) { + int penSize=(pSize.Width()+pSize.Height())/2; + tools::Rectangle rect = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize); + tools::Long const X[2] = { rect.Left(), rect.Right() }; + tools::Long const Y[2] = { rect.Top(), rect.Bottom() }; + + using namespace basegfx; + B2DPolygon poly; + poly.append(B2DPoint(X[0], Y[0])); poly.append(B2DPoint(X[1], Y[0])); + poly.append(B2DPoint(X[1], Y[1])); poly.append(B2DPoint(X[0], Y[1])); + poly.append(B2DPoint(X[0], Y[0])); + + if (drawFrame) + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + else + dev->DrawPolygon(poly); + } + + //-------------------- draws an ellipse -------------------- + void drawEllipse(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize) { + int penSize=(pSize.Width()+pSize.Height())/2; + tools::Rectangle oval = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize); + using namespace basegfx; + tools::Long const X[2] = { oval.Left(), oval.Right() }; + tools::Long const Y[2] = { oval.Top(), oval.Bottom() }; + B2DPoint center(0.5*(X[1]+X[0]), 0.5*(Y[1]+Y[0])); + B2DPolygon poly = basegfx::utils::createPolygonFromEllipse(center, 0.5*(X[1]-X[0]), 0.5*(Y[1]-Y[0])); + if (drawFrame) + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + else + dev->DrawPolygon(poly); + } + + //-------------------- draws an arc/pie -------------------- + void drawArc(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, const double& angle1, const double& angle2, Size const &pSize) { + int penSize=(pSize.Width()+pSize.Height())/2; + tools::Rectangle arc = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize); + using namespace basegfx; + + // pict angle are CW with 0 at twelve o'clock (with Y-axis inverted)... + double angl1 = angle1-M_PI_2; + double angl2 = angle2-M_PI_2; + tools::Long const X[2] = { arc.Left(), arc.Right() }; + tools::Long const Y[2] = { arc.Top(), arc.Bottom() }; + B2DPoint center(0.5*(X[1]+X[0]), 0.5*(Y[1]+Y[0])); + + // We must have angl1 between 0 and 2PI + while (angl1 < 0.0) { angl1 += 2 * M_PI; angl2 += 2 * M_PI; } + while (angl1 >= 2 * M_PI) { angl1 -= 2 * M_PI; angl2 -= 2 * M_PI; } + + // if this happen, we want a complete circle + // so we set angl2 slightly less than angl1 + if (angl2 >= angl1 + 2 * M_PI) angl2 = angl1-0.001; + + // We must have angl2 between 0 and 2PI + while (angl2 < 0.0) angl2 += 2 * M_PI; + while (angl2 >= 2 * M_PI) angl2 -= 2 * M_PI; + + B2DPolygon poly = basegfx::utils::createPolygonFromEllipseSegment(center, 0.5*(X[1]-X[0]), 0.5*(Y[1]-Y[0]), angl1, angl2); + if (drawFrame) + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + else { + // adds circle's center + poly.append(center); + dev->DrawPolygon(poly); + } + } + //-------------------- draws a rectangle with round corner -------------------- + void drawRoundRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &ovalSize, Size const &pSize) { + int penSize=(pSize.Width()+pSize.Height())/2; + tools::Rectangle oval = PictReaderShapePrivate::contractRectangle(drawFrame, orig, pSize); + int ovalW=ovalSize.Width(), ovalH=ovalSize.Height(); + using namespace basegfx; + tools::Long const X[2] = { oval.Left(), oval.Right() }; + tools::Long const Y[2] = { oval.Top(), oval.Bottom() }; + tools::Long width = X[1] - X[0]; + tools::Long height = Y[1] - Y[0]; + if (ovalW > width) ovalW = static_cast< int >( width ); + if (ovalH > height) ovalH = static_cast< int >( height ); + + B2DRectangle rect(B2DPoint(X[0],Y[0]), B2DPoint(X[1],Y[1])); + B2DPolygon poly = basegfx::utils::createPolygonFromRect(rect, (width != 0.0) ? ovalW/width : 0.0, (height != 0.0) ? ovalH/height : 0.0); + + if (drawFrame) + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + else + dev->DrawPolygon(poly); + } + + //-------------------- draws a polygon -------------------- +void drawPolygon(VirtualDevice *dev, bool drawFrame, tools::Polygon const &orig, Size const &pSize) { + int penSize=(pSize.Width()+pSize.Height())/2; + tools::Long decalTL[2] = {0, 0}, decalBR[2] = { pSize.Width(), pSize.Height()}; + if (drawFrame) { + decalTL[0] += penSize/2; decalTL[1] += penSize/2; + decalBR[0] -= (penSize+1)/2; decalBR[1] -= (penSize+1)/2; + } + // Quickdraw Drawing Reference 3-82: the pen size is only used for frame + else decalBR[0] = decalBR[1] = 0; + + + int numPt = orig.GetSize(); + if (numPt <= 1) return; + + // we compute a barycenter of the point to define the extended direction of each point + double bary[2] = { 0.0, 0.0 }; + for (int i = 0; i < numPt; i++) { + Point const &pt = orig.GetPoint(i); + bary[0] += double(pt.X()); bary[1] += double(pt.Y()); + } + bary[0]/=double(numPt); bary[1]/=double(numPt); + + using namespace basegfx; + B2DPolygon poly; + poly.reserve(numPt); + // Note: a polygon can be open, so we must not close it when we draw the frame + for (int i = 0; i < numPt; i++) { + Point const &pt = orig.GetPoint(i); + double x = (double(pt.X()) < bary[0]) ? pt.X()+decalTL[0] : pt.X()+decalBR[0]; + double y = (double(pt.Y()) < bary[1]) ? pt.Y()+decalTL[1] : pt.Y()+decalBR[1]; + poly.append(B2DPoint(x, y)); + } + if (drawFrame) + dev->DrawPolyLine(poly, double(penSize), basegfx::B2DLineJoin::NONE); + else + dev->DrawPolygon(poly); + } + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipict/shape.hxx b/vcl/source/filter/ipict/shape.hxx new file mode 100644 index 000000000..bccd39e63 --- /dev/null +++ b/vcl/source/filter/ipict/shape.hxx @@ -0,0 +1,57 @@ +/* -*- 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_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX +#define INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX + +#include <vcl/virdev.hxx> + +namespace PictReaderShape { + /** draws a line from orig to dest knowing penSize + + Attention: in order to draw horizontal/vertical/small lines, this function can instead draw a rectangle or + a polygon. In this case, we retrieve the line information from VirtualDev ( GetLineColor ) + and we use them as fill information ( SetFillColor ). We restore after the VirtualDev state. + + This implies also that this function must be modified if we use real pattern to draw these primitives. + */ + void drawLine(VirtualDevice *dev, Point const &orig, Point const &dest, Size const &pSize); + + /** draws a rectangle knowing penSize */ + void drawRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &rect, Size const &pSize); + + /** draws a polygon knowing penSize */ +void drawPolygon(VirtualDevice *dev, bool drawFrame, tools::Polygon const &rect, Size const &pSize); + + /** draws an ellipse knowing penSize */ + void drawEllipse(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &pSize); + + /** draws a rounded rectangle knowing penSize + \note ovalSize is two time the size of the corner + */ + void drawRoundRectangle(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, Size const &ovalS, Size const &pSize); + + /** draws an arc in a b2dpolygon knowing penSize + \note - it supposes that angl1 < angl2 + */ + void drawArc(VirtualDevice *dev, bool drawFrame, tools::Rectangle const &orig, const double& angle1, const double& angle2, Size const &pSize); +} + +#endif // INCLUDED_FILTER_SOURCE_GRAPHICFILTER_IPICT_SHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipsd/ipsd.cxx b/vcl/source/filter/ipsd/ipsd.cxx new file mode 100644 index 000000000..4c1277027 --- /dev/null +++ b/vcl/source/filter/ipsd/ipsd.cxx @@ -0,0 +1,776 @@ +/* -*- 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 <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/outdev.hxx> +#include <sal/log.hxx> +#include <tools/fract.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <memory> +#include <filter/PsdReader.hxx> + + +class FilterConfigItem; + +//============================ PSDReader ================================== + +#define PSD_BITMAP 0 +#define PSD_GRAYSCALE 1 +#define PSD_INDEXED 2 +#define PSD_RGB 3 +#define PSD_CMYK 4 +#define PSD_MULTICHANNEL 7 +#define PSD_DUOTONE 8 +#define PSD_LAB 9 + +namespace { + +struct PSDFileHeader +{ + sal_uInt32 nSignature; + sal_uInt16 nVersion; + sal_uInt32 nPad1; + sal_uInt16 nPad2; + sal_uInt16 nChannels; + sal_uInt32 nRows; + sal_uInt32 nColumns; + sal_uInt16 nDepth; + sal_uInt16 nMode; +}; + +class PSDReader { + +private: + + SvStream& m_rPSD; // the PSD file to be read in + std::unique_ptr<PSDFileHeader> + mpFileHeader; + + sal_uInt32 mnXResFixed; + sal_uInt32 mnYResFixed; + + bool mbStatus; + bool mbTransparent; + + std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap; + std::vector<Color> mvPalette; + sal_uInt16 mnDestBitDepth; + bool mbCompression; // RLE decoding + std::unique_ptr<sal_uInt8[]> + mpPalette; + + bool ImplReadBody(); + bool ImplReadHeader(); + +public: + explicit PSDReader(SvStream &rStream); + bool ReadPSD(Graphic & rGraphic); +}; + +} + +//=================== Methods of PSDReader ============================== + +PSDReader::PSDReader(SvStream &rStream) + : m_rPSD(rStream) + , mnXResFixed(0) + , mnYResFixed(0) + , mbStatus(true) + , mbTransparent(false) + , mnDestBitDepth(0) + , mbCompression(false) +{ +} + +bool PSDReader::ReadPSD(Graphic & rGraphic ) +{ + if (m_rPSD.GetError()) + return false; + + m_rPSD.SetEndian( SvStreamEndian::BIG ); + + // read header: + + if ( !ImplReadHeader() ) + return false; + + if (mbStatus) + { + sal_uInt32 nResult; + if (o3tl::checked_multiply(mpFileHeader->nColumns, mpFileHeader->nRows, nResult) || nResult > SAL_MAX_INT32/2/3) + return false; + } + + Size aBitmapSize( mpFileHeader->nColumns, mpFileHeader->nRows ); + mpBitmap.reset( new vcl::bitmap::RawBitmap( aBitmapSize, mbTransparent ? 32 : 24 ) ); + if ( mpPalette && mbStatus ) + { + mvPalette.resize( 256 ); + for ( sal_uInt16 i = 0; i < 256; i++ ) + { + mvPalette[i] = Color( mpPalette[ i ], mpPalette[ i + 256 ], mpPalette[ i + 512 ] ); + } + } + + if ((mnDestBitDepth == 1 || mnDestBitDepth == 8) && mvPalette.empty()) + { + SAL_WARN("vcl", "no palette, but bit depth is " << mnDestBitDepth); + mbStatus = false; + return mbStatus; + } + + // read bitmap data + if ( mbStatus && ImplReadBody() ) + { + rGraphic = Graphic( vcl::bitmap::CreateFromData( std::move(*mpBitmap) ) ); + + if ( mnXResFixed && mnYResFixed ) + { + Fraction aFractX( 1, mnXResFixed >> 16 ); + Fraction aFractY( 1, mnYResFixed >> 16 ); + MapMode aMapMode( MapUnit::MapInch, Point(), aFractX, aFractY ); + Size aPrefSize = OutputDevice::LogicToLogic(aBitmapSize, aMapMode, MapMode(MapUnit::Map100thMM)); + rGraphic.SetPrefSize( aPrefSize ); + rGraphic.SetPrefMapMode( MapMode( MapUnit::Map100thMM ) ); + } + } + else + mbStatus = false; + return mbStatus; +} + + +bool PSDReader::ImplReadHeader() +{ + mpFileHeader.reset( new PSDFileHeader ); + + m_rPSD.ReadUInt32( mpFileHeader->nSignature ).ReadUInt16( mpFileHeader->nVersion ).ReadUInt32( mpFileHeader->nPad1 ). ReadUInt16( mpFileHeader->nPad2 ).ReadUInt16( mpFileHeader->nChannels ).ReadUInt32( mpFileHeader->nRows ). ReadUInt32( mpFileHeader->nColumns ).ReadUInt16( mpFileHeader->nDepth ).ReadUInt16( mpFileHeader->nMode ); + + if (!m_rPSD.good()) + return false; + + if ( ( mpFileHeader->nSignature != 0x38425053 ) || ( mpFileHeader->nVersion != 1 ) ) + return false; + + if ( mpFileHeader->nRows == 0 || mpFileHeader->nColumns == 0 ) + return false; + + if ( ( mpFileHeader->nRows > 30000 ) || ( mpFileHeader->nColumns > 30000 ) ) + return false; + + sal_uInt16 nDepth = mpFileHeader->nDepth; + if (!( ( nDepth == 1 ) || ( nDepth == 8 ) || ( nDepth == 16 ) ) ) + return false; + + mnDestBitDepth = ( nDepth == 16 ) ? 8 : nDepth; + + sal_uInt32 nColorLength(0); + m_rPSD.ReadUInt32( nColorLength ); + if ( mpFileHeader->nMode == PSD_CMYK ) + { + switch ( mpFileHeader->nChannels ) + { + case 5 : + mbTransparent = true; + [[fallthrough]]; + case 4 : + mnDestBitDepth = 24; + break; + default : + return false; + } + } + else switch ( mpFileHeader->nChannels ) + { + case 2 : + mbTransparent = true; + break; + case 1 : + break; + case 4 : + mbTransparent = true; + [[fallthrough]]; + case 3 : + mnDestBitDepth = 24; + break; + default: + return false; + } + + switch ( mpFileHeader->nMode ) + { + case PSD_BITMAP : + { + if ( nColorLength || ( nDepth != 1 ) ) + return false; + } + break; + + case PSD_INDEXED : + { + if ( nColorLength != 768 ) // we need the color map + return false; + mpPalette.reset( new sal_uInt8[ 768 ] ); + m_rPSD.ReadBytes(mpPalette.get(), 768); + } + break; + + case PSD_DUOTONE : // we'll handle the duotone color like a normal grayscale picture + m_rPSD.SeekRel( nColorLength ); + nColorLength = 0; + [[fallthrough]]; + case PSD_GRAYSCALE : + { + if ( nColorLength ) + return false; + mpPalette.reset( new sal_uInt8[ 768 ] ); + for ( sal_uInt16 i = 0; i < 256; i++ ) + { + mpPalette[ i ] = mpPalette[ i + 256 ] = mpPalette[ i + 512 ] = static_cast<sal_uInt8>(i); + } + } + break; + + case PSD_CMYK : + case PSD_RGB : + case PSD_MULTICHANNEL : + case PSD_LAB : + { + if ( nColorLength ) // color table is not supported by the other graphic modes + return false; + } + break; + + default: + return false; + } + sal_uInt32 nResourceLength(0); + m_rPSD.ReadUInt32(nResourceLength); + if (nResourceLength > m_rPSD.remainingSize()) + return false; + sal_uInt32 nLayerPos = m_rPSD.Tell() + nResourceLength; + + // this is a loop over the resource entries to get the resolution info + while( m_rPSD.Tell() < nLayerPos ) + { + sal_uInt32 nType(0); + sal_uInt16 nUniqueID(0); + sal_uInt8 n8(0); + m_rPSD.ReadUInt32(nType).ReadUInt16(nUniqueID).ReadUChar(n8); + if (nType != 0x3842494d) + break; + sal_uInt32 nPStringLen = n8; + if ( ! ( nPStringLen & 1 ) ) + nPStringLen++; + m_rPSD.SeekRel( nPStringLen ); // skipping the pstring + sal_uInt32 nResEntryLen(0); + m_rPSD.ReadUInt32( nResEntryLen ); + if ( nResEntryLen & 1 ) + nResEntryLen++; // the resource entries are padded + sal_uInt32 nCurrentPos = m_rPSD.Tell(); + if (nCurrentPos > nLayerPos || nResEntryLen > (nLayerPos - nCurrentPos)) // check if size + break; // is possible + switch( nUniqueID ) + { + case 0x3ed : // UID for the resolution info + { + sal_Int16 nUnit; + + m_rPSD.ReadUInt32( mnXResFixed ).ReadInt16( nUnit ).ReadInt16( nUnit ) + .ReadUInt32( mnYResFixed ).ReadInt16( nUnit ).ReadInt16( nUnit ); + } + break; + } + m_rPSD.Seek( nCurrentPos + nResEntryLen ); // set the stream to the next + } // resource entry + m_rPSD.Seek( nLayerPos ); + sal_uInt32 nLayerMaskLength(0); + m_rPSD.ReadUInt32( nLayerMaskLength ); + m_rPSD.SeekRel( nLayerMaskLength ); + + sal_uInt16 nCompression(0); + m_rPSD.ReadUInt16(nCompression); + if ( nCompression == 0 ) + { + mbCompression = false; + } + else if ( nCompression == 1 ) + { + m_rPSD.SeekRel( ( mpFileHeader->nRows * mpFileHeader->nChannels ) << 1 ); + mbCompression = true; + } + else + return false; + + return true; +} + +namespace +{ + const Color& SanitizePaletteIndex(std::vector<Color> const & rvPalette, sal_uInt8 nIndex) + { + if (nIndex >= rvPalette.size()) + { + auto nSanitizedIndex = nIndex % rvPalette.size(); + SAL_WARN_IF(nIndex != nSanitizedIndex, "filter.psd", "invalid colormap index: " + << static_cast<unsigned int>(nIndex) << ", colormap len is: " + << rvPalette.size()); + nIndex = nSanitizedIndex; + } + return rvPalette[nIndex]; + } +} + +bool PSDReader::ImplReadBody() +{ + sal_uInt32 nX, nY; + signed char nRunCount = 0; + sal_uInt8 nDat = 0, nDummy, nRed, nGreen, nBlue; + BitmapColor aBitmapColor; + nX = nY = 0; + + switch ( mnDestBitDepth ) + { + case 1 : + { + signed char nBitCount = -1; + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( nBitCount == -1 ) + { + if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + } + if ( nRunCount & 0x80 ) // a run length packet + { + const sal_uInt16 nCount = -nRunCount + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + if ( nBitCount == -1 ) // bits left in nDat? + { + m_rPSD.ReadUChar( nDat ); + nDat ^= 0xff; + nBitCount = 7; + } + mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat >> nBitCount--)); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + nBitCount = -1; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + const sal_uInt16 nCount = (nRunCount & 0x7f) + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + if ( nBitCount == -1 ) // bits left in nDat ? + { + m_rPSD.ReadUChar( nDat ); + nDat ^= 0xff; + nBitCount = 7; + } + mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat >> nBitCount--)); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + nBitCount = -1; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + } + break; + + case 8 : + { + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nDat ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + const sal_uInt16 nCount = -nRunCount + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat)); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + const sal_uInt16 nCount = (nRunCount & 0x7f) + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + m_rPSD.ReadUChar( nDat ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + mpBitmap->SetPixel(nY, nX, SanitizePaletteIndex(mvPalette, nDat)); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + } + break; + + case 24 : + { + + // the psd format is in plain order (RRRR GGGG BBBB) so we have to set each pixel three times + // maybe the format is CCCC MMMM YYYY KKKK + + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nRed ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + const sal_uInt16 nCount = -nRunCount + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + mpBitmap->SetPixel( nY, nX, Color( nRed, sal_uInt8(0), sal_uInt8(0) ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + const sal_uInt16 nCount = (nRunCount & 0x7f) + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + m_rPSD.ReadUChar( nRed ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + mpBitmap->SetPixel( nY, nX, Color( nRed, sal_uInt8(0), sal_uInt8(0) ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + nY = 0; + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( mbCompression ) + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nGreen ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + const sal_uInt16 nCount = -nRunCount + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + aBitmapColor = mpBitmap->GetPixel( nY, nX ); + mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), nGreen, aBitmapColor.GetBlue() ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + const sal_uInt16 nCount = (nRunCount & 0x7f) + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + m_rPSD.ReadUChar( nGreen ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + aBitmapColor = mpBitmap->GetPixel( nY, nX ); + mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), nGreen, aBitmapColor.GetBlue() ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + nY = 0; + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( mbCompression ) + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nBlue ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + const sal_uInt16 nCount = -nRunCount + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + aBitmapColor = mpBitmap->GetPixel( nY, nX ); + mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), aBitmapColor.GetGreen(), nBlue ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + const sal_uInt16 nCount = (nRunCount & 0x7f) + 1; + for (sal_uInt16 i = 0; i < nCount && m_rPSD.good(); ++i) + { + m_rPSD.ReadUChar( nBlue ); + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + aBitmapColor = mpBitmap->GetPixel( nY, nX ); + mpBitmap->SetPixel( nY, nX, Color( aBitmapColor.GetRed(), aBitmapColor.GetGreen(), nBlue ) ); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + if (mpFileHeader->nMode == PSD_CMYK && m_rPSD.good()) + { + sal_uInt32 nBlack, nBlackMax = 0; + std::vector<sal_uInt8> aBlack(mpFileHeader->nRows * mpFileHeader->nColumns, 0); + nY = 0; + while (nY < mpFileHeader->nRows && m_rPSD.good()) + { + if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nDat ); + + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + + for ( sal_uInt16 i = 0; i < ( -nRunCount + 1 ); i++ ) + { + nBlack = mpBitmap->GetPixel( nY, nX ).GetRed() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + nBlack = mpBitmap->GetPixel( nY, nX ).GetGreen() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + nBlack = mpBitmap->GetPixel( nY, nX ).GetBlue() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + aBlack[ nX + nY * mpFileHeader->nColumns ] = nDat ^ 0xff; + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rPSD.ReadUChar( nDat ); + + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + nBlack = mpBitmap->GetPixel( nY, nX ).GetRed() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + nBlack = mpBitmap->GetPixel( nY, nX ).GetGreen() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + nBlack = mpBitmap->GetPixel( nY, nX ).GetBlue() + nDat; + if ( nBlack > nBlackMax ) + nBlackMax = nBlack; + aBlack[ nX + nY * mpFileHeader->nColumns ] = nDat ^ 0xff; + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + + for ( nY = 0; nY < mpFileHeader->nRows; nY++ ) + { + for ( nX = 0; nX < mpFileHeader->nColumns; nX++ ) + { + sal_Int32 nDAT = aBlack[ nX + nY * mpFileHeader->nColumns ] * ( nBlackMax - 256 ) / 0x1ff; + + aBitmapColor = mpBitmap->GetPixel( nY, nX ); + sal_uInt8 cR = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetRed() - nDAT, 0, 255L )); + sal_uInt8 cG = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetGreen() - nDAT, 0, 255L )); + sal_uInt8 cB = static_cast<sal_uInt8>(MinMax( aBitmapColor.GetBlue() - nDAT, 0, 255L )); + mpBitmap->SetPixel( nY, nX, Color( cR, cG, cB ) ); + } + } + } + } + break; + } + + if (mbTransparent && m_rPSD.good()) + { + // the psd is 24 or 8 bit grafix + alphachannel + + nY = nX = 0; + while ( nY < mpFileHeader->nRows ) + { + if ( mbCompression ) // else nRunCount = 0 -> so we use only single raw packets + { + char nTmp(0); + m_rPSD.ReadChar(nTmp); + nRunCount = nTmp; + } + + if ( nRunCount & 0x80 ) // a run length packet + { + m_rPSD.ReadUChar( nDat ); + if ( nDat ) + nDat = 0; + else + nDat = 1; + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + for ( sal_uInt16 i = 0; i < ( -nRunCount + 1 ); i++ ) + { + mpBitmap->SetAlpha(nY, nX, nDat ? 0 : 255); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rPSD.ReadUChar( nDat ); + if ( nDat ) + nDat = 0; + else + nDat = 1; + if ( mpFileHeader->nDepth == 16 ) // 16 bit depth is to be skipped + m_rPSD.ReadUChar( nDummy ); + mpBitmap->SetAlpha(nY, nX, nDat ? 0 : 255); + if ( ++nX == mpFileHeader->nColumns ) + { + nX = 0; + nY++; + if ( nY == mpFileHeader->nRows ) + break; + } + } + } + } + } + + return m_rPSD.good(); +} + +//================== GraphicImport - the exported function ================ + +bool ImportPsdGraphic(SvStream& rStream, Graphic& rGraphic) +{ + PSDReader aPSDReader(rStream); + return aPSDReader.ReadPSD(rGraphic); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/iras/iras.cxx b/vcl/source/filter/iras/iras.cxx new file mode 100644 index 000000000..49cfe2bef --- /dev/null +++ b/vcl/source/filter/iras/iras.cxx @@ -0,0 +1,414 @@ +/* -*- 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 <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <filter/RasReader.hxx> + +class FilterConfigItem; + +#define RAS_TYPE_OLD 0x00000000 // supported formats by this filter +#define RAS_TYPE_STANDARD 0x00000001 +#define RAS_TYPE_BYTE_ENCODED 0x00000002 +#define RAS_TYPE_RGB_FORMAT 0x00000003 + +#define RAS_COLOR_NO_MAP 0x00000000 +#define RAS_COLOR_RGB_MAP 0x00000001 +#define RAS_COLOR_RAW_MAP 0x00000002 + +#define SUNRASTER_MAGICNUMBER 0x59a66a95 + +//============================ RASReader ================================== + +namespace { + +class RASReader { + +private: + + SvStream& m_rRAS; // the RAS file to be read in + + bool mbStatus; + sal_Int32 mnWidth, mnHeight; // image dimensions in pixels + sal_uInt16 mnDstBitsPerPix; + sal_uInt16 mnDstColors; + sal_Int32 mnDepth, mnImageDatSize, mnType; + sal_Int32 mnColorMapType, mnColorMapSize; + sal_uInt8 mnRepCount, mnRepVal; // RLE Decoding + + bool ImplReadBody(vcl::bitmap::RawBitmap&, std::vector<Color> const & rvPalette); + bool ImplReadHeader(); + sal_uInt8 ImplGetByte(); + +public: + explicit RASReader(SvStream &rRAS); + bool ReadRAS(Graphic & rGraphic); +}; + +} + +//=================== Methods of RASReader ============================== + +RASReader::RASReader(SvStream &rRAS) + : m_rRAS(rRAS) + , mbStatus(true) + , mnWidth(0) + , mnHeight(0) + , mnDstBitsPerPix(0) + , mnDstColors(0) + , mnDepth(0) + , mnImageDatSize(0) + , mnType(0) + , mnColorMapType(0) + , mnColorMapSize(0) + , mnRepCount(0) + , mnRepVal(0) +{ +} + +bool RASReader::ReadRAS(Graphic & rGraphic) +{ + sal_uInt32 nMagicNumber; + + if ( m_rRAS.GetError() ) + return false; + + m_rRAS.SetEndian( SvStreamEndian::BIG ); + m_rRAS.ReadUInt32( nMagicNumber ); + if (!m_rRAS.good() || nMagicNumber != SUNRASTER_MAGICNUMBER) + return false; + + // Kopf einlesen: + + mbStatus = ImplReadHeader(); + if ( !mbStatus ) + return false; + + std::vector<Color> aPalette; + bool bOk = true; + + if ( mnDstBitsPerPix <= 8 ) // pallets pictures + { + bool bPalette(false); + + if ( mnColorMapType == RAS_COLOR_RAW_MAP ) // RAW color map is skipped + { + sal_uInt64 nCurPos = m_rRAS.Tell(); + bOk = checkSeek(m_rRAS, nCurPos + mnColorMapSize); + } + else if ( mnColorMapType == RAS_COLOR_RGB_MAP ) // we can read out the RGB + { + mnDstColors = static_cast<sal_uInt16>( mnColorMapSize / 3 ); + + if ( ( 1 << mnDstBitsPerPix ) < mnDstColors ) + return false; + + if ( ( mnDstColors >= 2 ) && ( ( mnColorMapSize % 3 ) == 0 ) ) + { + aPalette.resize(mnDstColors); + sal_uInt16 i; + sal_uInt8 nRed[256], nGreen[256], nBlue[256]; + for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nRed[ i ] ); + for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nGreen[ i ] ); + for ( i = 0; i < mnDstColors; i++ ) m_rRAS.ReadUChar( nBlue[ i ] ); + for ( i = 0; i < mnDstColors; i++ ) + { + aPalette[i] = Color(nRed[ i ], nGreen[ i ], nBlue[ i ]); + } + bPalette = true; + } + else + return false; + + } + else if ( mnColorMapType != RAS_COLOR_NO_MAP ) // everything else is not standard + return false; + + if (!bPalette) + { + mnDstColors = 1 << mnDstBitsPerPix; + aPalette.resize(mnDstColors); + for ( sal_uInt16 i = 0; i < mnDstColors; i++ ) + { + sal_uInt8 nCount = 255 - ( 255 * i / ( mnDstColors - 1 ) ); + aPalette[i] = Color(nCount, nCount, nCount); + } + } + } + else + { + if ( mnColorMapType != RAS_COLOR_NO_MAP ) // when graphic has more than 256 colors and a color map we skip + { // the colormap + sal_uInt64 nCurPos = m_rRAS.Tell(); + bOk = checkSeek(m_rRAS, nCurPos + mnColorMapSize); + } + } + + if (!bOk) + return false; + + //The RLE packets are typically three bytes in size: + //The first byte is a Flag Value indicating the type of RLE packet. + //The second byte is the Run Count. + //The third byte is the Run Value. + // + //for the sake of simplicity we'll assume that RAS_TYPE_BYTE_ENCODED can + //describe data 255 times larger than the data stored + size_t nMaxCompression = mnType != RAS_TYPE_BYTE_ENCODED ? 1 : 255; + sal_Int32 nBitSize; + if (o3tl::checked_multiply<sal_Int32>(mnWidth, mnHeight, nBitSize) || o3tl::checked_multiply<sal_Int32>(nBitSize, mnDepth, nBitSize)) + return false; + if (m_rRAS.remainingSize() * nMaxCompression < static_cast<sal_uInt32>(nBitSize) / 8) + return false; + + vcl::bitmap::RawBitmap aBmp(Size(mnWidth, mnHeight), 24); + + // read in the bitmap data + mbStatus = ImplReadBody(aBmp, aPalette); + + if ( mbStatus ) + rGraphic = vcl::bitmap::CreateFromData(std::move(aBmp)); + + return mbStatus; +} + +bool RASReader::ImplReadHeader() +{ + m_rRAS.ReadInt32(mnWidth).ReadInt32(mnHeight).ReadInt32(mnDepth).ReadInt32(mnImageDatSize).ReadInt32(mnType).ReadInt32(mnColorMapType).ReadInt32(mnColorMapSize); + + if (!m_rRAS.good() || mnWidth <= 0 || mnHeight <= 0 || mnImageDatSize <= 0) + mbStatus = false; + + switch ( mnDepth ) + { + case 24 : + case 8 : + case 1 : + mnDstBitsPerPix = static_cast<sal_uInt16>(mnDepth); + break; + case 32 : + mnDstBitsPerPix = 24; + break; + + default : + mbStatus = false; + } + + switch ( mnType ) + { + case RAS_TYPE_OLD : + case RAS_TYPE_STANDARD : + case RAS_TYPE_RGB_FORMAT : + case RAS_TYPE_BYTE_ENCODED : // this type will be supported later + break; + + default: + mbStatus = false; + } + return mbStatus; +} + +namespace +{ + const Color& SanitizePaletteIndex(std::vector<Color> const & rvPalette, sal_uInt8 nIndex) + { + if (nIndex >= rvPalette.size()) + { + auto nSanitizedIndex = nIndex % rvPalette.size(); + SAL_WARN_IF(nIndex != nSanitizedIndex, "filter.ras", "invalid colormap index: " + << static_cast<unsigned int>(nIndex) << ", colormap len is: " + << rvPalette.size()); + nIndex = nSanitizedIndex; + } + return rvPalette[nIndex]; + } +} + +bool RASReader::ImplReadBody(vcl::bitmap::RawBitmap& rBitmap, std::vector<Color> const & rvPalette) +{ + sal_Int32 x, y; + sal_uInt8 nRed, nGreen, nBlue; + switch ( mnDstBitsPerPix ) + { + case 1 : + { + sal_uInt8 nDat = 0; + for (y = 0; y < mnHeight && mbStatus; ++y) + { + for (x = 0; x < mnWidth && mbStatus; ++x) + { + if (!(x & 7)) + { + nDat = ImplGetByte(); + if (!m_rRAS.good()) + mbStatus = false; + } + rBitmap.SetPixel(y, x, SanitizePaletteIndex(rvPalette, + sal::static_int_cast< sal_uInt8 >( + nDat >> ( ( x & 7 ) ^ 7 )))); + } + if (!( ( x - 1 ) & 0x8 ) ) + { + ImplGetByte(); // WORD ALIGNMENT ??? + if (!m_rRAS.good()) + mbStatus = false; + } + } + break; + } + + case 8 : + for (y = 0; y < mnHeight && mbStatus; ++y) + { + for (x = 0; x < mnWidth && mbStatus; ++x) + { + sal_uInt8 nDat = ImplGetByte(); + rBitmap.SetPixel(y, x, SanitizePaletteIndex(rvPalette, nDat)); + if (!m_rRAS.good()) + mbStatus = false; + } + if ( x & 1 ) + { + ImplGetByte(); // WORD ALIGNMENT ??? + if (!m_rRAS.good()) + mbStatus = false; + } + } + break; + + case 24 : + switch ( mnDepth ) + { + + case 24 : + for (y = 0; y < mnHeight && mbStatus; ++y) + { + for (x = 0; x < mnWidth && mbStatus; ++x) + { + if ( mnType == RAS_TYPE_RGB_FORMAT ) + { + nRed = ImplGetByte(); + nGreen = ImplGetByte(); + nBlue = ImplGetByte(); + } + else + { + nBlue = ImplGetByte(); + nGreen = ImplGetByte(); + nRed = ImplGetByte(); + } + rBitmap.SetPixel(y, x, Color(nRed, nGreen, nBlue)); + if (!m_rRAS.good()) + mbStatus = false; + } + if ( x & 1 ) + { + ImplGetByte(); // WORD ALIGNMENT ??? + if (!m_rRAS.good()) + mbStatus = false; + } + } + break; + + case 32 : + for (y = 0; y < mnHeight && mbStatus; ++y) + { + for (x = 0; x < mnWidth && mbStatus; ++x) + { + ImplGetByte(); // pad byte > nil + if ( mnType == RAS_TYPE_RGB_FORMAT ) + { + nRed = ImplGetByte(); + nGreen = ImplGetByte(); + nBlue = ImplGetByte(); + } + else + { + nBlue = ImplGetByte(); + nGreen = ImplGetByte(); + nRed = ImplGetByte(); + } + rBitmap.SetPixel(y, x, Color(nRed, nGreen, nBlue)); + if (!m_rRAS.good()) + mbStatus = false; + } + } + break; + } + break; + + default: + mbStatus = false; + break; + } + return mbStatus; +} + +sal_uInt8 RASReader::ImplGetByte() +{ + sal_uInt8 nRetVal(0); + if ( mnType != RAS_TYPE_BYTE_ENCODED ) + { + m_rRAS.ReadUChar( nRetVal ); + return nRetVal; + } + else + { + if ( mnRepCount ) + { + mnRepCount--; + return mnRepVal; + } + else + { + m_rRAS.ReadUChar( nRetVal ); + if ( nRetVal != 0x80 ) + return nRetVal; + m_rRAS.ReadUChar( nRetVal ); + if ( nRetVal == 0 ) + return 0x80; + mnRepCount = nRetVal ; + m_rRAS.ReadUChar( mnRepVal ); + return mnRepVal; + } + } +} + +//================== GraphicImport - the exported function ================ + +bool ImportRasGraphic( SvStream & rStream, Graphic & rGraphic) +{ + bool bRet = false; + + try + { + RASReader aRASReader(rStream); + bRet = aRASReader.ReadRAS(rGraphic ); + } + catch (...) + { + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/itga/itga.cxx b/vcl/source/filter/itga/itga.cxx new file mode 100644 index 000000000..6b3b3037f --- /dev/null +++ b/vcl/source/filter/itga/itga.cxx @@ -0,0 +1,790 @@ +/* -*- 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 <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <tools/stream.hxx> +#include <memory> +#include <filter/TgaReader.hxx> + +class FilterConfigItem; + +//============================ TGAReader ================================== + +namespace { + +struct TGAFileHeader +{ + sal_uInt8 nImageIDLength; + sal_uInt8 nColorMapType; + sal_uInt8 nImageType; + sal_uInt16 nColorMapFirstEntryIndex; + sal_uInt16 nColorMapLength; + sal_uInt8 nColorMapEntrySize; + sal_uInt16 nColorMapXOrigin; + sal_uInt16 nColorMapYOrigin; + sal_uInt16 nImageWidth; + sal_uInt16 nImageHeight; + sal_uInt8 nPixelDepth; + sal_uInt8 nImageDescriptor; +}; + +#define SizeOfTGAFileFooter 26 + +struct TGAFileFooter +{ + sal_uInt32 nExtensionFileOffset; + sal_uInt32 nDeveloperDirectoryOffset; + sal_uInt32 nSignature[4]; + sal_uInt8 nPadByte; + sal_uInt8 nStringTerminator; +}; + +#define SizeOfTGAExtension 495 + +struct TGAExtension +{ + sal_uInt16 nExtensionSize; + char sAuthorName[41]; + char sAuthorComment[324]; + char sDateTimeStamp[12]; + char sJobNameID[41]; + char sSoftwareID[41]; + sal_uInt16 nSoftwareVersionNumber; + sal_uInt8 nSoftwareVersionLetter; + sal_uInt32 nKeyColor; + sal_uInt16 nPixelAspectRatioNumerator; + sal_uInt16 nPixelAspectRatioDeNumerator; + sal_uInt16 nGammaValueNumerator; + sal_uInt16 nGammaValueDeNumerator; + sal_uInt32 nColorCorrectionOffset; + sal_uInt32 nPostageStampOffset; + sal_uInt32 nScanLineOffset; + sal_uInt8 nAttributesType; +}; + +class TGAReader { + +private: + + SvStream& m_rTGA; + + std::unique_ptr<vcl::bitmap::RawBitmap> mpBitmap; + std::vector<Color> mvPalette; + std::unique_ptr<TGAFileHeader> + mpFileHeader; + std::unique_ptr<TGAFileFooter> + mpFileFooter; + std::unique_ptr<TGAExtension> + mpExtension; + std::unique_ptr<sal_uInt32[]> + mpColorMap; + + bool mbStatus; + + sal_uInt8 mnTGAVersion; // Enhanced TGA is defined as Version 2.0 + sal_uInt16 mnDestBitDepth; + bool mbIndexing; // sal_True if source contains indexing color values + bool mbEncoding; // sal_True if source is compressed + + bool ImplReadHeader(); + bool ImplReadPalette(); + bool ImplReadBody(); + +public: + explicit TGAReader(SvStream &rTGA); + bool ReadTGA(Graphic &rGraphic); +}; + +} + +//=================== Methods of TGAReader ============================== + +TGAReader::TGAReader(SvStream &rTGA) + : m_rTGA(rTGA) + , mbStatus(true) + , mnTGAVersion(1) + , mnDestBitDepth(8) + , mbIndexing(false) + , mbEncoding(false) +{ +} + +bool TGAReader::ReadTGA(Graphic & rGraphic) +{ + if ( m_rTGA.GetError() ) + return false; + + m_rTGA.SetEndian( SvStreamEndian::LITTLE ); + + // Kopf einlesen: + + if ( !m_rTGA.GetError() ) + { + mbStatus = ImplReadHeader(); + if (mbStatus) + mbStatus = mpFileHeader->nImageWidth && mpFileHeader->nImageHeight; + if (mbStatus) + { + sal_Size nSize = mpFileHeader->nImageWidth; + nSize *= mpFileHeader->nImageHeight; + if (nSize > SAL_MAX_INT32/2/3) + return false; + + mpBitmap.reset( new vcl::bitmap::RawBitmap( Size( mpFileHeader->nImageWidth, mpFileHeader->nImageHeight ), 24 ) ); + if ( mbIndexing ) + mbStatus = ImplReadPalette(); + if ( mbStatus ) + mbStatus = ImplReadBody(); + + if ( mbStatus ) + rGraphic = vcl::bitmap::CreateFromData(std::move(*mpBitmap)); + } + } + return mbStatus; +} + + +bool TGAReader::ImplReadHeader() +{ + mpFileHeader.reset( new TGAFileHeader ); + + m_rTGA.ReadUChar( mpFileHeader->nImageIDLength ).ReadUChar( mpFileHeader->nColorMapType ).ReadUChar( mpFileHeader->nImageType ). ReadUInt16( mpFileHeader->nColorMapFirstEntryIndex ).ReadUInt16( mpFileHeader->nColorMapLength ).ReadUChar( mpFileHeader->nColorMapEntrySize ). ReadUInt16( mpFileHeader->nColorMapXOrigin ).ReadUInt16( mpFileHeader->nColorMapYOrigin ).ReadUInt16( mpFileHeader->nImageWidth ). ReadUInt16( mpFileHeader->nImageHeight ).ReadUChar( mpFileHeader->nPixelDepth ).ReadUChar( mpFileHeader->nImageDescriptor ); + + if ( !m_rTGA.good()) + return false; + + if ( mpFileHeader->nColorMapType > 1 ) + return false; + if ( mpFileHeader->nColorMapType == 1 ) + mbIndexing = true; + + // first we want to get the version + mpFileFooter.reset( new TGAFileFooter ); // read the TGA-File-Footer to determine whether + // we got an old TGA format or the new one + + sal_uInt64 nCurStreamPos = m_rTGA.Tell(); + m_rTGA.Seek( STREAM_SEEK_TO_END ); + sal_uInt64 nTemp = m_rTGA.Tell(); + m_rTGA.Seek( nTemp - SizeOfTGAFileFooter ); + + m_rTGA.ReadUInt32( mpFileFooter->nExtensionFileOffset ).ReadUInt32( mpFileFooter->nDeveloperDirectoryOffset ). ReadUInt32( mpFileFooter->nSignature[0] ).ReadUInt32( mpFileFooter->nSignature[1] ).ReadUInt32( mpFileFooter->nSignature[2] ). ReadUInt32( mpFileFooter->nSignature[3] ).ReadUChar( mpFileFooter->nPadByte ).ReadUChar( mpFileFooter->nStringTerminator ); + + + if ( !m_rTGA.good()) + return false; + + // check for sal_True, VISI, ON-X, FILE in the signatures + if ( mpFileFooter->nSignature[ 0 ] == (('T'<<24)|('R'<<16)|('U'<<8)|'E') && + mpFileFooter->nSignature[ 1 ] == (('V'<<24)|('I'<<16)|('S'<<8)|'I') && + mpFileFooter->nSignature[ 2 ] == (('O'<<24)|('N'<<16)|('-'<<8)|'X') && + mpFileFooter->nSignature[ 3 ] == (('F'<<24)|('I'<<16)|('L'<<8)|'E') ) + { + mpExtension.reset( new TGAExtension ); + + m_rTGA.Seek( mpFileFooter->nExtensionFileOffset ); + m_rTGA.ReadUInt16( mpExtension->nExtensionSize ); + if ( !m_rTGA.good()) + return false; + if ( mpExtension->nExtensionSize >= SizeOfTGAExtension ) + { + mnTGAVersion = 2; + + m_rTGA.ReadBytes(mpExtension->sAuthorName, 41); + m_rTGA.ReadBytes(mpExtension->sAuthorComment, 324); + m_rTGA.ReadBytes(mpExtension->sDateTimeStamp, 12); + m_rTGA.ReadBytes(mpExtension->sJobNameID, 12); + m_rTGA.ReadChar( mpExtension->sJobNameID[ 0 ] ).ReadChar( mpExtension->sJobNameID[ 1 ] ).ReadChar( mpExtension->sJobNameID[ 2 ] ); + m_rTGA.ReadBytes(mpExtension->sSoftwareID, 41); + m_rTGA.ReadUInt16( mpExtension->nSoftwareVersionNumber ).ReadUChar( mpExtension->nSoftwareVersionLetter ) + .ReadUInt32( mpExtension->nKeyColor ).ReadUInt16( mpExtension->nPixelAspectRatioNumerator ) + .ReadUInt16( mpExtension->nPixelAspectRatioDeNumerator ).ReadUInt16( mpExtension->nGammaValueNumerator ) + .ReadUInt16( mpExtension->nGammaValueDeNumerator ).ReadUInt32( mpExtension->nColorCorrectionOffset ) + .ReadUInt32( mpExtension->nPostageStampOffset ).ReadUInt32( mpExtension->nScanLineOffset ) + .ReadUChar( mpExtension->nAttributesType ); + + if ( !m_rTGA.good()) + return false; + } + } + m_rTGA.Seek( nCurStreamPos ); + + // using the TGA file specification this was the correct form but adobe photoshop sets nImageDescriptor + // equal to nPixelDepth + // mnDestBitDepth = mpFileHeader->nPixelDepth - ( mpFileHeader->nImageDescriptor & 0xf ); + mnDestBitDepth = mpFileHeader->nPixelDepth; + + if ( mnDestBitDepth == 8 ) // this is a patch for grayscale pictures not including a palette + mbIndexing = true; + + if ( mnDestBitDepth > 32 ) // maybe the pixeldepth is invalid + return false; + else if ( mnDestBitDepth > 8 ) + mnDestBitDepth = 24; + else if ( mnDestBitDepth > 4 ) + mnDestBitDepth = 8; + else if ( mnDestBitDepth > 2 ) + mnDestBitDepth = 4; + + if ( !mbIndexing && ( mnDestBitDepth < 15 ) ) + return false; + + switch ( mpFileHeader->nImageType ) + { + case 9 : // encoding for colortype 9, 10, 11 + case 10 : + case 11 : + mbEncoding = true; + break; + } + + if ( mpFileHeader->nImageIDLength ) // skip the Image ID + m_rTGA.SeekRel( mpFileHeader->nImageIDLength ); + + return mbStatus; +} + + +bool TGAReader::ImplReadBody() +{ + + sal_uInt16 nXCount, nYCount, nRGB16; + sal_uInt8 nRed, nGreen, nBlue, nRunCount, nDummy, nDepth; + + // this four variables match the image direction + tools::Long nY, nYAdd, nX, nXAdd, nXStart; + + nX = nXStart = nY = 0; + nXCount = nYCount = 0; + nYAdd = nXAdd = 1; + + if ( mpFileHeader->nImageDescriptor & 0x10 ) + { + nX = nXStart = mpFileHeader->nImageWidth - 1; + nXAdd -= 2; + } + + if ( !(mpFileHeader->nImageDescriptor & 0x20 ) ) + { + nY = mpFileHeader->nImageHeight - 1; + nYAdd -=2; + } + + nDepth = mpFileHeader->nPixelDepth; + + if ( mbEncoding ) + { + if ( mbIndexing ) + { + switch( nDepth ) + { + // 16 bit encoding + indexing + case 16 : + while ( nYCount < mpFileHeader->nImageHeight ) + { + m_rTGA.ReadUChar( nRunCount ); + if ( !m_rTGA.good()) + return false; + if ( nRunCount & 0x80 ) // a run length packet + { + m_rTGA.ReadUInt16( nRGB16 ); + if (!m_rTGA.good()) + return false; + if ( nRGB16 >= mpFileHeader->nColorMapLength ) + return false; + nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 ); + nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 ); + nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] ); + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rTGA.ReadUInt16( nRGB16 ); + if ( !m_rTGA.good()) + return false; + if ( nRGB16 >= mpFileHeader->nColorMapLength ) + return false; + nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 ); + nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 ); + nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] ); + if ( !m_rTGA.good()) + return false; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + } + break; + + // 8 bit encoding + indexing + case 8 : + while ( nYCount < mpFileHeader->nImageHeight ) + { + m_rTGA.ReadUChar( nRunCount ); + if ( !m_rTGA.good()) + return false; + if ( nRunCount & 0x80 ) // a run length packet + { + m_rTGA.ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + if ( nDummy >= mpFileHeader->nColorMapLength ) + return false; + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + mpBitmap->SetPixel( nY, nX, mvPalette[nDummy] ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rTGA.ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + if ( nDummy >= mpFileHeader->nColorMapLength ) + return false; + mpBitmap->SetPixel( nY, nX, mvPalette[nDummy] ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + } + break; + default: + return false; + } + } + else + { + switch( nDepth ) + { + // 32 bit transparent true color encoding + case 32 : + { + while ( nYCount < mpFileHeader->nImageHeight ) + { + m_rTGA.ReadUChar( nRunCount ); + if ( !m_rTGA.good()) + return false; + if ( nRunCount & 0x80 ) // a run length packet + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + } + } + break; + + // 24 bit true color encoding + case 24 : + while ( nYCount < mpFileHeader->nImageHeight ) + { + m_rTGA.ReadUChar( nRunCount ); + if ( !m_rTGA.good()) + return false; + if ( nRunCount & 0x80 ) // a run length packet + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ); + if ( !m_rTGA.good()) + return false; + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ); + if ( !m_rTGA.good()) + return false; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + } + break; + + // 16 bit true color encoding + case 16 : + while ( nYCount < mpFileHeader->nImageHeight ) + { + m_rTGA.ReadUChar( nRunCount ); + if ( !m_rTGA.good()) + return false; + if ( nRunCount & 0x80 ) // a run length packet + { + m_rTGA.ReadUInt16( nRGB16 ); + if ( !m_rTGA.good()) + return false; + nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8; + nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8; + nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8; + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + else // a raw packet + { + for ( sal_uInt16 i = 0; i < ( ( nRunCount & 0x7f ) + 1 ); i++ ) + { + m_rTGA.ReadUInt16( nRGB16 ); + if ( !m_rTGA.good()) + return false; + nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8; + nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8; + nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + nX += nXAdd; + nXCount++; + if ( nXCount == mpFileHeader->nImageWidth ) + { + nX = nXStart; + nXCount = 0; + nY += nYAdd; + nYCount++; + + if( nYCount >= mpFileHeader->nImageHeight ) + break; + } + } + } + } + break; + + default: + return false; + } + } + } + else + { + for ( nYCount = 0; nYCount < mpFileHeader->nImageHeight; nYCount++, nY += nYAdd ) + { + nX = nXStart; + nXCount = 0; + + if ( mbIndexing ) + { + switch( nDepth ) + { + // 16 bit indexing + case 16 : + for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd ) + { + m_rTGA.ReadUInt16( nRGB16 ); + if ( !m_rTGA.good()) + return false; + if ( nRGB16 >= mpFileHeader->nColorMapLength ) + return false; + nRed = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 16 ); + nGreen = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] >> 8 ); + nBlue = static_cast<sal_uInt8>( mpColorMap[ nRGB16 ] ); + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + } + break; + + // 8 bit indexing + case 8 : + for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd ) + { + m_rTGA.ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + if ( nDummy >= mpFileHeader->nColorMapLength ) + return false; + mpBitmap->SetPixel( nY, nX, Color(ColorTransparency, nDummy) ); + } + break; + default: + return false; + } + } + else + { + switch( nDepth ) + { + // 32 bit true color + case 32 : + { + for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd ) + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ).ReadUChar( nDummy ); + if ( !m_rTGA.good()) + return false; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + } + } + break; + + // 24 bit true color + case 24 : + for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd ) + { + m_rTGA.ReadUChar( nBlue ).ReadUChar( nGreen ).ReadUChar( nRed ); + if ( !m_rTGA.good()) + return false; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + } + break; + + // 16 bit true color + case 16 : + for (;nXCount < mpFileHeader->nImageWidth; nXCount++, nX += nXAdd ) + { + m_rTGA.ReadUInt16( nRGB16 ); + if ( !m_rTGA.good()) + return false; + nRed = static_cast<sal_uInt8>( nRGB16 >> 7 ) & 0xf8; + nGreen = static_cast<sal_uInt8>( nRGB16 >> 2 ) & 0xf8; + nBlue = static_cast<sal_uInt8>( nRGB16 << 3 ) & 0xf8; + mpBitmap->SetPixel( nY, nX, Color( nRed, nGreen, nBlue ) ); + } + break; + default: + return false; + } + } + } + } + return mbStatus; +} + + +bool TGAReader::ImplReadPalette() +{ + if ( mbIndexing ) // read the colormap + { + sal_uInt16 nColors = mpFileHeader->nColorMapLength; + + if ( !nColors ) // colors == 0 ? -> we will build a grayscale palette + { + if ( mpFileHeader->nPixelDepth != 8 ) + return false; + nColors = 256; + mpFileHeader->nColorMapLength = 256; + mpFileHeader->nColorMapEntrySize = 0x3f; // patch for the following switch routine + } + mpColorMap.reset( new sal_uInt32[ nColors ] ); // we will always index dwords + + switch( mpFileHeader->nColorMapEntrySize ) + { + case 0x3f : + { + for (sal_uInt32 i = 0; i < nColors; ++i) + { + mpColorMap[ i ] = ( i << 16 ) + ( i << 8 ) + i; + } + } + break; + + case 32 : + for (sal_uInt16 i = 0; i < nColors; i++) + { + m_rTGA.ReadUInt32(mpColorMap[i]); + } + break; + + case 24 : + { + for ( sal_uInt16 i = 0; i < nColors; i++ ) + { + sal_uInt8 nBlue; + sal_uInt8 nGreen; + sal_uInt8 nRed; + m_rTGA.ReadUChar(nBlue).ReadUChar(nGreen).ReadUChar(nRed); + mpColorMap[i] = (nRed << 16) | (nGreen << 8) | nBlue; + } + } + break; + + case 15 : + case 16 : + { + for ( sal_uInt16 i = 0; i < nColors; i++ ) + { + sal_uInt16 nTemp; + m_rTGA.ReadUInt16( nTemp ); + if ( !m_rTGA.good() ) + return false; + mpColorMap[ i ] = ( ( nTemp & 0x7c00 ) << 9 ) + ( ( nTemp & 0x01e0 ) << 6 ) + + ( ( nTemp & 0x1f ) << 3 ); + } + } + break; + + default : + return false; + } + if ( mnDestBitDepth <= 8 ) + { + sal_uInt16 nDestColors = ( 1 << mnDestBitDepth ); + if ( nColors > nDestColors ) + return false; + + mvPalette.resize( nColors ); + for ( sal_uInt16 i = 0; i < nColors; i++ ) + { + mvPalette[i] = Color( static_cast<sal_uInt8>( mpColorMap[ i ] >> 16 ), + static_cast<sal_uInt8>( mpColorMap[ i ] >> 8 ), static_cast<sal_uInt8>(mpColorMap[ i ] ) ); + } + } + } + + return mbStatus; +} + +//================== GraphicImport - the exported function ================ + +bool ImportTgaGraphic(SvStream & rStream, Graphic & rGraphic) +{ + TGAReader aTGAReader(rStream); + + return aTGAReader.ReadTGA(rGraphic); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/itiff/itiff.cxx b/vcl/source/filter/itiff/itiff.cxx new file mode 100644 index 000000000..e6d243b57 --- /dev/null +++ b/vcl/source/filter/itiff/itiff.cxx @@ -0,0 +1,294 @@ +/* -*- 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 <sal/log.hxx> + +#include <comphelper/scopeguard.hxx> +#include <vcl/graph.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/animate/Animation.hxx> +#include <bitmap/BitmapWriteAccess.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <unotools/configmgr.hxx> + +#include <tiffio.h> + +#include <filter/TiffReader.hxx> + +namespace +{ + struct Context + { + SvStream& rStream; + tsize_t nSize; + int nShortReads; + Context(SvStream& rInStream, tsize_t nInSize) + : rStream(rInStream) + , nSize(nInSize) + , nShortReads(0) + { + } + }; +} + +static tsize_t tiff_read(thandle_t handle, tdata_t buf, tsize_t size) +{ + Context* pContext = static_cast<Context*>(handle); + tsize_t nRead = pContext->rStream.ReadBytes(buf, size); + // tdf#149417 allow one short read, which is similar to what + // we do for jpeg since tdf#138950 + if (nRead < size && !pContext->nShortReads) + { + memset(static_cast<char*>(buf) + nRead, 0, size - nRead); + ++pContext->nShortReads; + return size; + } + return nRead; +} + +static tsize_t tiff_write(thandle_t, tdata_t, tsize_t) +{ + return -1; +} + +static toff_t tiff_seek(thandle_t handle, toff_t offset, int whence) +{ + Context* pContext = static_cast<Context*>(handle); + + switch (whence) + { + case SEEK_SET: + pContext->rStream.Seek(offset); + break; + case SEEK_CUR: + pContext->rStream.SeekRel(offset); + break; + case SEEK_END: + pContext->rStream.Seek(STREAM_SEEK_TO_END); + pContext->rStream.SeekRel(offset); + break; + default: + assert(false && "unknown seek type"); + break; + } + + return pContext->rStream.Tell(); +} + +static int tiff_close(thandle_t) +{ + return 0; +} + +static toff_t tiff_size(thandle_t handle) +{ + Context* pContext = static_cast<Context*>(handle); + return pContext->nSize; +} + +bool ImportTiffGraphicImport(SvStream& rTIFF, Graphic& rGraphic) +{ + auto origErrorHandler = TIFFSetErrorHandler(nullptr); + auto origWarningHandler = TIFFSetWarningHandler(nullptr); + comphelper::ScopeGuard restoreDefaultHandlers([&]() { + TIFFSetErrorHandler(origErrorHandler); + TIFFSetWarningHandler(origWarningHandler); + }); + + Context aContext(rTIFF, rTIFF.remainingSize()); + TIFF* tif = TIFFClientOpen("libtiff-svstream", "r", &aContext, + tiff_read, tiff_write, + tiff_seek, tiff_close, + tiff_size, nullptr, nullptr); + + if (!tif) + return false; + + const auto nOrigPos = rTIFF.Tell(); + + Animation aAnimation; + + do + { + uint32_t w, h; + + if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w) != 1) + { + SAL_WARN("filter.tiff", "missing width"); + break; + } + + if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h) != 1) + { + SAL_WARN("filter.tiff", "missing height"); + break; + } + + if (w > SAL_MAX_INT32 / 32 || h > SAL_MAX_INT32 / 32) + { + SAL_WARN("filter.tiff", "image too large"); + break; + } + + if (utl::ConfigManager::IsFuzzing()) + { + const uint64_t MAX_SIZE = 500000000; + if (TIFFTileSize64(tif) > MAX_SIZE) + { + SAL_WARN("filter.tiff", "skipping large tiffs"); + break; + } + } + + uint32_t nPixelsRequired; + constexpr size_t nMaxPixelsAllowed = SAL_MAX_INT32/4; + // two buffers currently required, so limit further + bool bOk = !o3tl::checked_multiply(w, h, nPixelsRequired) && nPixelsRequired <= nMaxPixelsAllowed / 2; + if (!bOk) + { + SAL_WARN("filter.tiff", "skipping oversized tiff image " << w << " x " << h); + break; + } + + std::vector<uint32_t> raster(nPixelsRequired); + if (TIFFReadRGBAImageOriented(tif, w, h, raster.data(), ORIENTATION_TOPLEFT, 1)) + { + Bitmap bitmap(Size(w, h), vcl::PixelFormat::N24_BPP); + BitmapScopedWriteAccess access(bitmap); + if (!access) + { + SAL_WARN("filter.tiff", "cannot create image " << w << " x " << h); + break; + } + + AlphaMask bitmapAlpha(Size(w, h)); + AlphaScopedWriteAccess accessAlpha(bitmapAlpha); + if (!accessAlpha) + { + SAL_WARN("filter.tiff", "cannot create alpha " << w << " x " << h); + break; + } + + /* + ORIENTATION_TOPLEFT = 1 + ORIENTATION_TOPRIGHT = 2 + ORIENTATION_BOTRIGHT = 3 + ORIENTATION_BOTLEFT = 4 + ORIENTATION_LEFTTOP = 5 + ORIENTATION_RIGHTTOP = 6 + ORIENTATION_RIGHTBOT = 7 + ORIENTATION_LEFTBOT = 8 + */ + uint16_t nOrientation; + if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &nOrientation) != 1) + nOrientation = 0; + + for (uint32_t y = 0; y < h; ++y) + { + const uint32_t* src = raster.data() + w * y; + for (uint32_t x = 0; x < w; ++x) + { + sal_uInt8 r = TIFFGetR(*src); + sal_uInt8 g = TIFFGetG(*src); + sal_uInt8 b = TIFFGetB(*src); + sal_uInt8 a = TIFFGetA(*src); + + uint32_t dest; + switch (nOrientation) + { + case ORIENTATION_LEFTBOT: + dest = w - 1 - x; + break; + default: + dest = x; + break; + } + + access->SetPixel(y, dest, Color(r, g, b)); + accessAlpha->SetPixelIndex(y, dest, 255 - a); + ++src; + } + } + + raster.clear(); + + access.reset(); + accessAlpha.reset(); + + BitmapEx aBitmapEx(bitmap, bitmapAlpha); + + switch (nOrientation) + { + case ORIENTATION_LEFTBOT: + aBitmapEx.Rotate(2700_deg10, COL_BLACK); + break; + default: + break; + } + + MapMode aMapMode; + uint16_t ResolutionUnit = RESUNIT_NONE; + if (TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &ResolutionUnit) == 1 && ResolutionUnit != RESUNIT_NONE) + { + float xres = 0, yres = 0; + + if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres) == 1 && + TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres) == 1 && + xres != 0 && yres != 0) + { + if (ResolutionUnit == RESUNIT_INCH) + aMapMode = MapMode(MapUnit::MapInch, Point(0,0), Fraction(1/xres), Fraction(1/yres)); + else if (ResolutionUnit == RESUNIT_CENTIMETER) + aMapMode = MapMode(MapUnit::MapCM, Point(0,0), Fraction(1/xres), Fraction(1/yres)); + } + } + aBitmapEx.SetPrefMapMode(aMapMode); + aBitmapEx.SetPrefSize(Size(w, h)); + + AnimationBitmap aAnimationBitmap(aBitmapEx, Point(0, 0), aBitmapEx.GetSizePixel(), + ANIMATION_TIMEOUT_ON_CLICK, Disposal::Back); + aAnimation.Insert(aAnimationBitmap); + } + else + break; + } while (TIFFReadDirectory(tif)); + + TIFFClose(tif); + + const auto nImages = aAnimation.Count(); + if (nImages) + { + if (nImages == 1) + rGraphic = aAnimation.GetBitmapEx(); + else + rGraphic = aAnimation; + + // seek to end of TIFF if succeeded + rTIFF.Seek(STREAM_SEEK_TO_END); + + return true; + } + + rTIFF.Seek(nOrigPos); + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ixbm/xbmread.cxx b/vcl/source/filter/ixbm/xbmread.cxx new file mode 100644 index 000000000..9368f9f9a --- /dev/null +++ b/vcl/source/filter/ixbm/xbmread.cxx @@ -0,0 +1,396 @@ +/* -*- 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 <memory> +#include <sal/config.h> +#include <tools/stream.hxx> + +#include <rtl/character.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +#include "xbmread.hxx" + +namespace { + +enum XBMFormat +{ + XBM10, + XBM11 +}; + +enum ReadState +{ + XBMREAD_OK, + XBMREAD_ERROR, + XBMREAD_NEED_MORE +}; + +class XBMReader : public GraphicReader +{ + SvStream& rIStm; + Bitmap aBmp1; + BitmapScopedWriteAccess pAcc1; + std::unique_ptr<short[]> + pHexTable; + BitmapColor aWhite; + BitmapColor aBlack; + tools::Long nLastPos; + tools::Long nWidth; + tools::Long nHeight; + bool bStatus; + + void InitTable(); + OString FindTokenLine( SvStream* pInStm, const char* pTok1, const char* pTok2 ); + int ParseDefine( const char* pDefine ); + void ParseData( SvStream* pInStm, const OString& aLastLine, XBMFormat eFormat ); + +public: + + explicit XBMReader( SvStream& rStm ); + + ReadState ReadXBM( Graphic& rGraphic ); +}; + +} + +XBMReader::XBMReader( SvStream& rStm ) : + rIStm ( rStm ), + nLastPos ( rStm.Tell() ), + nWidth ( 0 ), + nHeight ( 0 ), + bStatus ( true ) +{ + pHexTable.reset( new short[ 256 ] ); + maUpperName = "SVIXBM"; + InitTable(); +} + +void XBMReader::InitTable() +{ + memset( pHexTable.get(), 0, sizeof( short ) * 256 ); + + pHexTable[int('0')] = 0; + pHexTable[int('1')] = 1; + pHexTable[int('2')] = 2; + pHexTable[int('3')] = 3; + pHexTable[int('4')] = 4; + pHexTable[int('5')] = 5; + pHexTable[int('6')] = 6; + pHexTable[int('7')] = 7; + pHexTable[int('8')] = 8; + pHexTable[int('9')] = 9; + pHexTable[int('A')] = 10; + pHexTable[int('B')] = 11; + pHexTable[int('C')] = 12; + pHexTable[int('D')] = 13; + pHexTable[int('E')] = 14; + pHexTable[int('F')] = 15; + pHexTable[int('X')] = 0; + pHexTable[int('a')] = 10; + pHexTable[int('b')] = 11; + pHexTable[int('c')] = 12; + pHexTable[int('d')] = 13; + pHexTable[int('e')] = 14; + pHexTable[int('f')] = 15; + pHexTable[int('x')] = 0; + pHexTable[int(' ')] = -1; + pHexTable[int(',')] = -1; + pHexTable[int('}')] = -1; + pHexTable[int('\n')] = -1; + pHexTable[int('\t')] = -1; + pHexTable[int('\0')] = -1; +} + +OString XBMReader::FindTokenLine( SvStream* pInStm, const char* pTok1, + const char* pTok2 ) +{ + OString aRet; + sal_Int32 nPos1, nPos2; + + bStatus = false; + + do + { + if( !pInStm->ReadLine( aRet ) ) + break; + + if( pTok1 ) + { + if( ( nPos1 = aRet.indexOf( pTok1 ) ) != -1 ) + { + bStatus = true; + + if( pTok2 ) + { + bStatus = false; + + nPos2 = aRet.indexOf( pTok2 ); + if( ( nPos2 != -1 ) && ( nPos2 > nPos1 ) ) + { + bStatus = true; + } + } + } + } + } + while( !bStatus ); + + return aRet; +} + +int XBMReader::ParseDefine( const char* pDefine ) +{ + sal_Int32 nRet = 0; + const char* pTmp = pDefine; + unsigned char cTmp; + + // move to end + pTmp += ( strlen( pDefine ) - 1 ); + cTmp = *pTmp--; + + // search last digit + while (pHexTable[ cTmp ] == -1 && pTmp >= pDefine) + cTmp = *pTmp--; + + // move before number + while (pHexTable[ cTmp ] != -1 && pTmp >= pDefine) + cTmp = *pTmp--; + + // move to start of number + pTmp += 2; + + // read Hex + if( ( pTmp[0] == '0' ) && ( ( pTmp[1] == 'X' ) || ( pTmp[1] == 'x' ) ) ) + { + pTmp += 2; + nRet = OString(pTmp, strlen(pTmp)).toInt32(16); + } + else // read decimal + { + nRet = OString(pTmp, strlen(pTmp)).toInt32(); + } + + return nRet; +} + +void XBMReader::ParseData( SvStream* pInStm, const OString& aLastLine, XBMFormat eFormat ) +{ + OString aLine; + tools::Long nRow = 0; + tools::Long nCol = 0; + tools::Long nBits = ( eFormat == XBM10 ) ? 16 : 8; + tools::Long nBit; + sal_uInt16 nValue; + sal_uInt16 nDigits; + bool bFirstLine = true; + + while( nRow < nHeight ) + { + if( bFirstLine ) + { + sal_Int32 nPos; + + // delete opening curly bracket + aLine = aLastLine; + nPos = aLine.indexOf('{'); + if( nPos != -1 ) + aLine = aLine.copy(nPos + 1); + + bFirstLine = false; + } + else if( !pInStm->ReadLine( aLine ) ) + break; + + if (!aLine.isEmpty()) + { + sal_Int32 nIndex = 0; + const sal_Int32 nLen {aLine.getLength()}; + while (nRow<nHeight && nIndex<nLen) + { + bool bProcessed = false; + + nBit = nDigits = nValue = 0; + + while (nIndex<nLen) + { + const unsigned char cChar = aLine[nIndex]; + + ++nIndex; + if (cChar==',') // sequence completed, ',' already skipped for next loop + break; + + const short nTable = pHexTable[ cChar ]; + + if( rtl::isAsciiHexDigit( cChar ) || !nTable ) + { + nValue = ( nValue << 4 ) + nTable; + nDigits++; + bProcessed = true; + } + else if( ( nTable < 0 ) && nDigits ) + { + bProcessed = true; + break; + } + } + + if( bProcessed ) + { + Scanline pScanline = pAcc1->GetScanline(nRow); + while( ( nCol < nWidth ) && ( nBit < nBits ) ) + pAcc1->SetPixelOnData(pScanline, nCol++, ( nValue & ( 1 << nBit++ ) ) ? aBlack : aWhite); + + if( nCol == nWidth ) + { + nCol = 0; + nRow++; + } + } + } + } + } +} + +ReadState XBMReader::ReadXBM( Graphic& rGraphic ) +{ + ReadState eReadState; + sal_uInt8 cDummy; + + // check if we can read ALL + rIStm.Seek( STREAM_SEEK_TO_END ); + rIStm.ReadUChar( cDummy ); + + // if we cannot read all + // we return and wait for new data + if ( rIStm.GetError() != ERRCODE_IO_PENDING ) + { + rIStm.Seek( nLastPos ); + bStatus = false; + OString aLine = FindTokenLine( &rIStm, "#define", "_width" ); + + if ( bStatus ) + { + int nValue; + if ( ( nValue = ParseDefine( aLine.getStr() ) ) > 0 ) + { + nWidth = nValue; + aLine = FindTokenLine( &rIStm, "#define", "_height" ); + + // if height was not received, we search again + // from start of the file + if ( !bStatus ) + { + rIStm.Seek( nLastPos ); + aLine = FindTokenLine( &rIStm, "#define", "_height" ); + } + } + else + bStatus = false; + + if ( bStatus ) + { + if ( ( nValue = ParseDefine( aLine.getStr() ) ) > 0 ) + { + nHeight = nValue; + aLine = FindTokenLine( &rIStm, "static", "_bits" ); + + if ( bStatus ) + { + XBMFormat eFormat = XBM10; + + if (aLine.indexOf("short") != -1) + eFormat = XBM10; + else if (aLine.indexOf("char") != -1) + eFormat = XBM11; + else + bStatus = false; + + //xbms are a minimum of one character per 8 pixels, so if the file isn't + //even that long, it's not all there + if (rIStm.remainingSize() < (static_cast<sal_uInt64>(nWidth) * nHeight) / 8) + bStatus = false; + + if ( bStatus && nWidth && nHeight ) + { + aBmp1 = Bitmap(Size(nWidth, nHeight), vcl::PixelFormat::N1_BPP); + pAcc1 = BitmapScopedWriteAccess(aBmp1); + + if( pAcc1 ) + { + aWhite = pAcc1->GetBestMatchingColor( COL_WHITE ); + aBlack = pAcc1->GetBestMatchingColor( COL_BLACK ); + ParseData( &rIStm, aLine, eFormat ); + } + else + bStatus = false; + } + } + } + } + } + + if (bStatus && pAcc1) + { + Bitmap aBlackBmp(Size(pAcc1->Width(), pAcc1->Height()), vcl::PixelFormat::N1_BPP); + + pAcc1.reset(); + aBlackBmp.Erase( COL_BLACK ); + rGraphic = BitmapEx( aBlackBmp, aBmp1 ); + eReadState = XBMREAD_OK; + } + else + eReadState = XBMREAD_ERROR; + } + else + { + rIStm.ResetError(); + eReadState = XBMREAD_NEED_MORE; + } + + return eReadState; +} + +VCL_DLLPUBLIC bool ImportXBM( SvStream& rStm, Graphic& rGraphic ) +{ + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + XBMReader* pXBMReader = dynamic_cast<XBMReader*>( pContext.get() ); + if (!pXBMReader) + { + pContext = std::make_shared<XBMReader>( rStm ); + pXBMReader = static_cast<XBMReader*>( pContext.get() ); + } + + bool bRet = true; + + ReadState eReadState = pXBMReader->ReadXBM( rGraphic ); + + if( eReadState == XBMREAD_ERROR ) + { + bRet = false; + } + else if( eReadState == XBMREAD_NEED_MORE ) + rGraphic.SetReaderContext( pContext ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ixbm/xbmread.hxx b/vcl/source/filter/ixbm/xbmread.hxx new file mode 100644 index 000000000..bee3eadc9 --- /dev/null +++ b/vcl/source/filter/ixbm/xbmread.hxx @@ -0,0 +1,29 @@ +/* -*- 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_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX +#define INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX + +#include <vcl/graph.hxx> + +VCL_DLLPUBLIC bool ImportXBM(SvStream& rStream, Graphic& rGraphic); + +#endif // INCLUDED_VCL_SOURCE_FILTER_IXBM_XBMREAD_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ixpm/rgbtable.hxx b/vcl/source/filter/ixpm/rgbtable.hxx new file mode 100644 index 000000000..1ded061e7 --- /dev/null +++ b/vcl/source/filter/ixpm/rgbtable.hxx @@ -0,0 +1,696 @@ +/* -*- 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_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX +#define INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX + +#include <sal/types.h> + +struct XPMRGBTab +{ + const char* name; + sal_uInt8 red; + sal_uInt8 green; + sal_uInt8 blue; +}; + +const XPMRGBTab pRGBTable[] = { +{ "white", 255, 255, 255 }, +{ "black", 0, 0, 0 }, +{ "snow", 255, 250, 250 }, +{ "GhostWhite", 248, 248, 255 }, +{ "WhiteSmoke", 245, 245, 245 }, +{ "gainsboro", 220, 220, 220 }, +{ "FloralWhite", 255, 250, 240 }, +{ "OldLace", 253, 245, 230 }, +{ "linen", 250, 240, 230 }, +{ "AntiqueWhite", 250, 235, 215 }, +{ "PapayaWhip", 255, 239, 213 }, +{ "BlanchedAlmond", 255, 235, 205 }, +{ "bisque", 255, 228, 196 }, +{ "PeachPuff", 255, 218, 185 }, +{ "NavajoWhite", 255, 222, 173 }, +{ "moccasin", 255, 228, 181 }, +{ "cornsilk", 255, 248, 220 }, +{ "ivory", 255, 255, 240 }, +{ "LemonChiffon", 255, 250, 205 }, +{ "seashell", 255, 245, 238 }, +{ "honeydew", 240, 255, 240 }, +{ "MintCream", 245, 255, 250 }, +{ "azure", 240, 255, 255 }, +{ "AliceBlue", 240, 248, 255 }, +{ "lavender", 230, 230, 250 }, +{ "LavenderBlush", 255, 240, 245 }, +{ "MistyRose", 255, 228, 225 }, +{ "DarkSlateGray", 47, 79, 79 }, +{ "DarkSlateGrey", 47, 79, 79 }, +{ "DimGray", 105, 105, 105 }, +{ "DimGrey", 105, 105, 105 }, +{ "SlateGray", 112, 128, 144 }, +{ "SlateGrey", 112, 128, 144 }, +{ "LightSlateGray", 119, 136, 153 }, +{ "LightSlateGrey", 119, 136, 153 }, +{ "gray", 190, 190, 190 }, +{ "grey", 190, 190, 190 }, +{ "LightGrey", 211, 211, 211 }, +{ "LightGray", 211, 211, 211 }, +{ "MidnightBlue", 25, 25, 112 }, +{ "navy", 0, 0, 128 }, +{ "NavyBlue", 0, 0, 128 }, +{ "CornflowerBlue", 100, 149, 237 }, +{ "DarkSlateBlue", 72, 61, 139 }, +{ "SlateBlue", 106, 90, 205 }, +{ "MediumSlateBlue", 123, 104, 238 }, +{ "LightSlateBlue", 132, 112, 255 }, +{ "MediumBlue", 0, 0, 205 }, +{ "RoyalBlue", 65, 105, 225 }, +{ "blue", 0, 0, 255 }, +{ "DodgerBlue", 30, 144, 255 }, +{ "DeepSkyBlue", 0, 191, 255 }, +{ "SkyBlue", 135, 206, 235 }, +{ "LightSkyBlue", 135, 206, 250 }, +{ "SteelBlue", 70, 130, 180 }, +{ "LightSteelBlue", 176, 196, 222 }, +{ "LightBlue", 173, 216, 230 }, +{ "PowderBlue", 176, 224, 230 }, +{ "PaleTurquoise", 175, 238, 238 }, +{ "DarkTurquoise", 0, 206, 209 }, +{ "MediumTurquoise", 72, 209, 204 }, +{ "turquoise", 64, 224, 208 }, +{ "cyan", 0, 255, 255 }, +{ "LightCyan", 224, 255, 255 }, +{ "CadetBlue", 95, 158, 160 }, +{ "MediumAquamarine", 102, 205, 170 }, +{ "aquamarine", 127, 255, 212 }, +{ "DarkGreen", 0, 100, 0 }, +{ "DarkOliveGreen", 85, 107, 47 }, +{ "DarkSeaGreen", 143, 188, 143 }, +{ "SeaGreen", 46, 139, 87 }, +{ "MediumSeaGreen", 60, 179, 113 }, +{ "LightSeaGreen", 32, 178, 170 }, +{ "PaleGreen", 152, 251, 152 }, +{ "SpringGreen", 0, 255, 127 }, +{ "LawnGreen", 124, 252, 0 }, +{ "green", 0, 255, 0 }, +{ "chartreuse", 127, 255, 0 }, +{ "MediumSpringGreen", 0, 250, 154 }, +{ "GreenYellow", 173, 255 , 47 }, +{ "LimeGreen", 50, 205, 50 }, +{ "YellowGreen", 154, 205, 50 }, +{ "ForestGreen", 34, 139, 34 }, +{ "OliveDrab", 107, 142, 35 }, +{ "DarkKhaki", 189, 183, 107 }, +{ "khaki", 240, 230, 140 }, +{ "PaleGoldenrod", 238, 232, 170 }, +{ "LightGoldenrodYellow", 250, 250, 210 }, +{ "LightYellow", 255, 255, 224 }, +{ "yellow", 255, 255, 0 }, +{ "gold", 255, 215, 0 }, +{ "LightGoldenrod", 238, 221, 130 }, +{ "goldenrod", 218, 165, 32 }, +{ "DarkGoldenrod", 184, 134, 11 }, +{ "RosyBrown", 188, 143, 143 }, +{ "IndianRed", 205, 92, 92 }, +{ "SaddleBrown", 139, 69, 19 }, +{ "sienna", 160, 82, 45 }, +{ "peru", 205, 133, 63 }, +{ "burlywood", 222, 184, 135 }, +{ "beige", 245, 245, 220 }, +{ "wheat", 245, 222, 179 }, +{ "SandyBrown", 244, 164, 96 }, +{ "tan", 210, 180, 140 }, +{ "chocolate", 210, 105, 30 }, +{ "firebrick", 178, 34, 34 }, +{ "brown", 165, 42, 42 }, +{ "DarkSalmon", 233, 150, 122 }, +{ "salmon", 250, 128, 114 }, +{ "LightSalmon", 255, 160, 122 }, +{ "orange", 255, 165, 0 }, +{ "DarkOrange", 255, 140, 0 }, +{ "coral", 255, 127, 80 }, +{ "LightCoral", 240, 128, 128 }, +{ "tomato", 255, 99, 71 }, +{ "OrangeRed", 255, 69, 0 }, +{ "red", 255, 0, 0 }, +{ "HotPink", 255, 105, 180 }, +{ "DeepPink", 255, 20, 147 }, +{ "pink", 255, 192, 203 }, +{ "LightPink", 255, 182, 193 }, +{ "PaleVioletRed", 219, 112, 147 }, +{ "maroon", 176, 48, 96 }, +{ "MediumVioletRed", 199, 21, 133 }, +{ "VioletRed", 208, 32, 144 }, +{ "magenta", 255, 0, 255 }, +{ "violet", 238, 130, 238 }, +{ "plum", 221, 160, 221 }, +{ "orchid", 218, 112, 214 }, +{ "MediumOrchid", 186, 85, 211 }, +{ "DarkOrchid", 153, 50, 204 }, +{ "DarkViolet", 148, 0, 211 }, +{ "BlueViolet", 138, 43, 226 }, +{ "purple", 160, 32, 240 }, +{ "MediumPurple", 147, 112, 219 }, +{ "thistle", 216, 191, 216 }, +{ "snow1", 255, 250, 250 }, +{ "snow2", 238, 233, 233 }, +{ "snow3", 205, 201, 201 }, +{ "snow4", 139, 137, 137 }, +{ "seashell1", 255, 245, 238 }, +{ "seashell2", 238, 229, 222 }, +{ "seashell3", 205, 197, 191 }, +{ "seashell4", 139, 134, 130 }, +{ "AntiqueWhite1", 255, 239, 219 }, +{ "AntiqueWhite2", 238, 223, 204 }, +{ "AntiqueWhite3", 205, 192, 176 }, +{ "AntiqueWhite4", 139, 131, 120 }, +{ "bisque1", 255, 228, 196 }, +{ "bisque2", 238, 213, 183 }, +{ "bisque3", 205, 183, 158 }, +{ "bisque4", 139, 125, 107 }, +{ "PeachPuff1", 255, 218, 185 }, +{ "PeachPuff2", 238, 203, 173 }, +{ "PeachPuff3", 205, 175, 149 }, +{ "PeachPuff4", 139, 119, 101 }, +{ "NavajoWhite1", 255, 222, 173 }, +{ "NavajoWhite2", 238, 207, 161 }, +{ "NavajoWhite3", 205, 179, 139 }, +{ "NavajoWhite4", 139, 121, 94 }, +{ "LemonChiffon1", 255, 250, 205 }, +{ "LemonChiffon2", 238, 233, 191 }, +{ "LemonChiffon3", 205, 201, 165 }, +{ "LemonChiffon4", 139, 137, 112 }, +{ "cornsilk1", 255, 248, 220 }, +{ "cornsilk2", 238, 232, 205 }, +{ "cornsilk3", 205, 200, 177 }, +{ "cornsilk4", 139, 136, 120 }, +{ "ivory1", 255, 255, 240 }, +{ "ivory2", 238, 238, 224 }, +{ "ivory3", 205, 205, 193 }, +{ "ivory4", 139, 139, 131 }, +{ "honeydew1", 240, 255, 240 }, +{ "honeydew2", 224, 238, 224 }, +{ "honeydew3", 193, 205, 193 }, +{ "honeydew4", 131, 139, 131 }, +{ "LavenderBlush1", 255, 240, 245 }, +{ "LavenderBlush2", 238, 224, 229 }, +{ "LavenderBlush3", 205, 193, 197 }, +{ "LavenderBlush4", 139, 131, 134 }, +{ "MistyRose1", 255, 228, 225 }, +{ "MistyRose2", 238, 213, 210 }, +{ "MistyRose3", 205, 183, 181 }, +{ "MistyRose4", 139, 125, 123 }, +{ "azure1", 240, 255, 255 }, +{ "azure2", 224, 238, 238 }, +{ "azure3", 193, 205, 205 }, +{ "azure4", 131, 139, 139 }, +{ "SlateBlue1", 131, 111, 255 }, +{ "SlateBlue2", 122, 103, 238 }, +{ "SlateBlue3", 105, 89, 205 }, +{ "SlateBlue4", 71, 60, 139 }, +{ "RoyalBlue1", 72, 118, 255 }, +{ "RoyalBlue2", 67, 110, 238 }, +{ "RoyalBlue3", 58, 95, 205 }, +{ "RoyalBlue4", 39, 64, 139 }, +{ "blue1", 0, 0, 255 }, +{ "blue2", 0, 0, 238 }, +{ "blue3", 0, 0, 205 }, +{ "blue4", 0, 0, 139 }, +{ "DodgerBlue1", 30, 144, 255 }, +{ "DodgerBlue2", 28, 134, 238 }, +{ "DodgerBlue3", 24, 116, 205 }, +{ "DodgerBlue4", 16, 78, 139 }, +{ "SteelBlue1", 99, 184, 255 }, +{ "SteelBlue2", 92, 172, 238 }, +{ "SteelBlue3", 79, 148, 205 }, +{ "SteelBlue4", 54, 100, 139 }, +{ "DeepSkyBlue1", 0, 191, 255 }, +{ "DeepSkyBlue2", 0, 178, 238 }, +{ "DeepSkyBlue3", 0, 154, 205 }, +{ "DeepSkyBlue4", 0, 104, 139 }, +{ "SkyBlue1", 135, 206, 255 }, +{ "SkyBlue2", 126, 192, 238 }, +{ "SkyBlue3", 108, 166, 205 }, +{ "SkyBlue4", 74, 112, 139 }, +{ "LightSkyBlue1", 176, 226, 255 }, +{ "LightSkyBlue2", 164, 211, 238 }, +{ "LightSkyBlue3", 141, 182, 205 }, +{ "LightSkyBlue4", 96, 123, 139 }, +{ "SlateGray1", 198, 226, 255 }, +{ "SlateGray2", 185, 211, 238 }, +{ "SlateGray3", 159, 182, 205 }, +{ "SlateGray4", 108, 123, 139 }, +{ "LightSteelBlue1", 202, 225, 255 }, +{ "LightSteelBlue2", 188, 210, 238 }, +{ "LightSteelBlue3", 162, 181, 205 }, +{ "LightSteelBlue4", 110, 123, 139 }, +{ "LightBlue1", 191, 239, 255 }, +{ "LightBlue2", 178, 223, 238 }, +{ "LightBlue3", 154, 192, 205 }, +{ "LightBlue4", 104, 131, 139 }, +{ "LightCyan1", 224, 255, 255 }, +{ "LightCyan2", 209, 238, 238 }, +{ "LightCyan3", 180, 205, 205 }, +{ "LightCyan4", 122, 139, 139 }, +{ "PaleTurquoise1", 187, 255, 255 }, +{ "PaleTurquoise2", 174, 238, 238 }, +{ "PaleTurquoise3", 150, 205, 205 }, +{ "PaleTurquoise4", 102, 139, 139 }, +{ "CadetBlue1", 152, 245, 255 }, +{ "CadetBlue2", 142, 229, 238 }, +{ "CadetBlue3", 122, 197, 205 }, +{ "CadetBlue4", 83, 134, 139 }, +{ "turquoise1", 0, 245, 255 }, +{ "turquoise2", 0, 229, 238 }, +{ "turquoise3", 0, 197, 205 }, +{ "turquoise4", 0, 134, 139 }, +{ "cyan1", 0, 255, 255 }, +{ "cyan2", 0, 238, 238 }, +{ "cyan3", 0, 205, 205 }, +{ "cyan4", 0, 139, 139 }, +{ "DarkSlateGray1", 151, 255, 255 }, +{ "DarkSlateGray2", 141, 238, 238 }, +{ "DarkSlateGray3", 121, 205, 205 }, +{ "DarkSlateGray4", 82, 139, 139 }, +{ "aquamarine1", 127, 255, 212 }, +{ "aquamarine2", 118, 238, 198 }, +{ "aquamarine3", 102, 205, 170 }, +{ "aquamarine4", 69, 139, 116 }, +{ "DarkSeaGreen1", 193, 255, 193 }, +{ "DarkSeaGreen2", 180, 238, 180 }, +{ "DarkSeaGreen3", 155, 205, 155 }, +{ "DarkSeaGreen4", 105, 139, 105 }, +{ "SeaGreen1", 84, 255, 159 }, +{ "SeaGreen2", 78, 238, 148 }, +{ "SeaGreen3", 67, 205, 128 }, +{ "SeaGreen4", 46, 139, 87 }, +{ "PaleGreen1", 154, 255, 154 }, +{ "PaleGreen2", 144, 238, 144 }, +{ "PaleGreen3", 124, 205, 124 }, +{ "PaleGreen4", 84, 139, 84 }, +{ "SpringGreen1", 0, 255, 127 }, +{ "SpringGreen2", 0, 238, 118 }, +{ "SpringGreen3", 0, 205, 102 }, +{ "SpringGreen4", 0, 139, 69 }, +{ "green1", 0, 255, 0 }, +{ "green2", 0, 238, 0 }, +{ "green3", 0, 205, 0 }, +{ "green4", 0, 139, 0 }, +{ "chartreuse1", 127, 255, 0 }, +{ "chartreuse2", 118, 238, 0 }, +{ "chartreuse3", 102, 205, 0 }, +{ "chartreuse4", 69, 139, 0 }, +{ "OliveDrab1", 192, 255, 62 }, +{ "OliveDrab2", 179, 238, 58 }, +{ "OliveDrab3", 154, 205, 50 }, +{ "OliveDrab4", 105, 139, 34 }, +{ "DarkOliveGreen1", 202, 255, 112 }, +{ "DarkOliveGreen2", 188, 238, 104 }, +{ "DarkOliveGreen3", 162, 205, 90 }, +{ "DarkOliveGreen4", 110, 139, 61 }, +{ "khaki1", 255, 246, 143 }, +{ "khaki2", 238, 230, 133 }, +{ "khaki3", 205, 198, 115 }, +{ "khaki4", 139, 134, 78 }, +{ "LightGoldenrod1", 255, 236, 139 }, +{ "LightGoldenrod2", 238, 220, 130 }, +{ "LightGoldenrod3", 205, 190, 112 }, +{ "LightGoldenrod4", 139, 129, 76 }, +{ "LightYellow1", 255, 255, 224 }, +{ "LightYellow2", 238, 238, 209 }, +{ "LightYellow3", 205, 205, 180 }, +{ "LightYellow4", 139, 139, 122 }, +{ "yellow1", 255, 255, 0 }, +{ "yellow2", 238, 238, 0 }, +{ "yellow3", 205, 205, 0 }, +{ "yellow4", 139, 139, 0 }, +{ "gold1", 255, 215, 0 }, +{ "gold2", 238, 201, 0 }, +{ "gold3", 205, 173, 0 }, +{ "gold4", 139, 117, 0 }, +{ "goldenrod1", 255, 193, 37 }, +{ "goldenrod2", 238, 180, 34 }, +{ "goldenrod3", 205, 155, 29 }, +{ "goldenrod4", 139, 105, 20 }, +{ "DarkGoldenrod1", 255, 185, 15 }, +{ "DarkGoldenrod2", 238, 173, 14 }, +{ "DarkGoldenrod3", 205, 149, 12 }, +{ "DarkGoldenrod4", 139, 101, 8 }, +{ "RosyBrown1", 255, 193, 193 }, +{ "RosyBrown2", 238, 180, 180 }, +{ "RosyBrown3", 205, 155, 155 }, +{ "RosyBrown4", 139, 105, 105 }, +{ "IndianRed1", 255, 106, 106 }, +{ "IndianRed2", 238, 99, 99 }, +{ "IndianRed3", 205, 85, 85 }, +{ "IndianRed4", 139, 58, 58 }, +{ "sienna1", 255, 130, 71 }, +{ "sienna2", 238, 121, 66 }, +{ "sienna3", 205, 104, 57 }, +{ "sienna4", 139, 71, 38 }, +{ "burlywood1", 255, 211, 155 }, +{ "burlywood2", 238, 197, 145 }, +{ "burlywood3", 205, 170, 125 }, +{ "burlywood4", 139, 115, 85 }, +{ "wheat1", 255, 231, 186 }, +{ "wheat2", 238, 216, 174 }, +{ "wheat3", 205, 186, 150 }, +{ "wheat4", 139, 126, 102 }, +{ "tan1", 255, 165, 79 }, +{ "tan2", 238, 154, 73 }, +{ "tan3", 205, 133, 63 }, +{ "tan4", 139 , 90, 43 }, +{ "chocolate1", 255, 127, 36 }, +{ "chocolate2", 238, 118, 33 }, +{ "chocolate3", 205, 102, 29 }, +{ "chocolate4", 139, 69, 19 }, +{ "firebrick1", 255, 48, 48 }, +{ "firebrick2", 238, 44, 44 }, +{ "firebrick3", 205, 38, 38 }, +{ "firebrick4", 139, 26, 26 }, +{ "brown1", 255, 64, 64 }, +{ "brown2", 238, 59, 59 }, +{ "brown3", 205, 51, 51 }, +{ "brown4", 139, 35, 35 }, +{ "salmon1", 255, 140, 105 }, +{ "salmon2", 238, 130, 98 }, +{ "salmon3", 205, 112, 84 }, +{ "salmon4", 139, 76, 57 }, +{ "LightSalmon1", 255, 160, 122 }, +{ "LightSalmon2", 238, 149, 114 }, +{ "LightSalmon3", 205, 129, 98 }, +{ "LightSalmon4", 139, 87, 66 }, +{ "orange1", 255, 165, 0 }, +{ "orange2", 238, 154, 0 }, +{ "orange3", 205, 133, 0 }, +{ "orange4", 139 , 90, 0 }, +{ "DarkOrange1", 255, 127, 0 }, +{ "DarkOrange2", 238, 118, 0 }, +{ "DarkOrange3", 205, 102, 0 }, +{ "DarkOrange4", 139 , 69, 0 }, +{ "coral1", 255, 114, 86 }, +{ "coral2", 238, 106, 80 }, +{ "coral3", 205, 91, 69 }, +{ "coral4", 139, 62, 47 }, +{ "tomato1", 255, 99, 71 }, +{ "tomato2", 238, 92, 66 }, +{ "tomato3", 205, 79, 57 }, +{ "tomato4", 139, 54, 38 }, +{ "OrangeRed1", 255, 69, 0 }, +{ "OrangeRed2", 238, 64, 0 }, +{ "OrangeRed3", 205, 55, 0 }, +{ "OrangeRed4", 139, 37, 0 }, +{ "red1", 255, 0, 0 }, +{ "red2", 238, 0, 0 }, +{ "red3", 205, 0, 0 }, +{ "red4", 139, 0, 0 }, +{ "DeepPink1", 255, 20, 147 }, +{ "DeepPink2", 238, 18, 137 }, +{ "DeepPink3", 205, 16, 118 }, +{ "DeepPink4", 139, 10, 80 }, +{ "HotPink1", 255, 110, 180 }, +{ "HotPink2", 238, 106, 167 }, +{ "HotPink3", 205, 96, 144 }, +{ "HotPink4", 139, 58, 98 }, +{ "pink1", 255, 181, 197 }, +{ "pink2", 238, 169, 184 }, +{ "pink3", 205, 145, 158 }, +{ "pink4", 139, 99, 108 }, +{ "LightPink1", 255, 174, 185 }, +{ "LightPink2", 238, 162, 173 }, +{ "LightPink3", 205, 140, 149 }, +{ "LightPink4", 139, 95, 101 }, +{ "PaleVioletRed1", 255, 130, 171 }, +{ "PaleVioletRed2", 238, 121, 159 }, +{ "PaleVioletRed3", 205, 104, 137 }, +{ "PaleVioletRed4", 139, 71, 93 }, +{ "maroon1", 255, 52, 179 }, +{ "maroon2", 238, 48, 167 }, +{ "maroon3", 205, 41, 144 }, +{ "maroon4", 139, 28, 98 }, +{ "VioletRed1", 255, 62, 150 }, +{ "VioletRed2", 238, 58, 140 }, +{ "VioletRed3", 205, 50, 120 }, +{ "VioletRed4", 139, 34, 82 }, +{ "magenta1", 255, 0, 255 }, +{ "magenta2", 238, 0, 238 }, +{ "magenta3", 205, 0, 205 }, +{ "magenta4", 139, 0, 139 }, +{ "orchid1", 255, 131, 250 }, +{ "orchid2", 238, 122, 233 }, +{ "orchid3", 205, 105, 201 }, +{ "orchid4", 139, 71, 137 }, +{ "plum1", 255, 187, 255 }, +{ "plum2", 238, 174, 238 }, +{ "plum3", 205, 150, 205 }, +{ "plum4", 139, 102, 139 }, +{ "MediumOrchid1", 224, 102, 255 }, +{ "MediumOrchid2", 209, 95, 238 }, +{ "MediumOrchid3", 180, 82, 205 }, +{ "MediumOrchid4", 122, 55, 139 }, +{ "DarkOrchid1", 191, 62, 255 }, +{ "DarkOrchid2", 178, 58, 238 }, +{ "DarkOrchid3", 154, 50, 205 }, +{ "DarkOrchid4", 104, 34, 139 }, +{ "purple1", 155, 48, 255 }, +{ "purple2", 145, 44, 238 }, +{ "purple3", 125, 38, 205 }, +{ "purple4", 85, 26, 139 }, +{ "MediumPurple1", 171, 130, 255 }, +{ "MediumPurple2", 159, 121, 238 }, +{ "MediumPurple3", 137, 104, 205 }, +{ "MediumPurple4", 93, 71, 139 }, +{ "thistle1", 255, 225, 255 }, +{ "thistle2", 238, 210, 238 }, +{ "thistle3", 205, 181, 205 }, +{ "thistle4", 139, 123, 139 }, +{ "gray0", 0, 0, 0 }, +{ "grey0", 0, 0, 0 }, +{ "gray1", 3, 3, 3 }, +{ "grey1", 3, 3, 3 }, +{ "gray2", 5, 5, 5 }, +{ "grey2", 5, 5, 5 }, +{ "gray3", 8, 8, 8 }, +{ "grey3", 8, 8, 8 }, +{ "gray4", 10, 10, 10 }, +{ "grey4", 10, 10, 10 }, +{ "gray5", 13, 13, 13 }, +{ "grey5", 13, 13, 13 }, +{ "gray6", 15, 15, 15 }, +{ "grey6", 15, 15, 15 }, +{ "gray7", 18, 18, 18 }, +{ "grey7", 18, 18, 18 }, +{ "gray8", 20, 20, 20 }, +{ "grey8", 20, 20, 20 }, +{ "gray9", 23, 23, 23 }, +{ "grey9", 23, 23, 23 }, +{ "gray10", 26, 26, 26 }, +{ "grey10", 26, 26, 26 }, +{ "gray11", 28, 28, 28 }, +{ "grey11", 28, 28, 28 }, +{ "gray12", 31, 31, 31 }, +{ "grey12", 31, 31, 31 }, +{ "gray13", 33, 33, 33 }, +{ "grey13", 33, 33, 33 }, +{ "gray14", 36, 36, 36 }, +{ "grey14", 36, 36, 36 }, +{ "gray15", 38, 38, 38 }, +{ "grey15", 38, 38, 38 }, +{ "gray16", 41, 41, 41 }, +{ "grey16", 41, 41, 41 }, +{ "gray17", 43, 43, 43 }, +{ "grey17", 43, 43, 43 }, +{ "gray18", 46, 46, 46 }, +{ "grey18", 46, 46, 46 }, +{ "gray19", 48, 48, 48 }, +{ "grey19", 48, 48, 48 }, +{ "gray20", 51, 51, 51 }, +{ "grey20", 51, 51, 51 }, +{ "gray21", 54, 54, 54 }, +{ "grey21", 54, 54, 54 }, +{ "gray22", 56, 56, 56 }, +{ "grey22", 56, 56, 56 }, +{ "gray23", 59, 59, 59 }, +{ "grey23", 59, 59, 59 }, +{ "gray24", 61, 61, 61 }, +{ "grey24", 61, 61, 61 }, +{ "gray25", 64, 64, 64 }, +{ "grey25", 64, 64, 64 }, +{ "gray26", 66, 66, 66 }, +{ "grey26", 66, 66, 66 }, +{ "gray27", 69, 69, 69 }, +{ "grey27", 69, 69, 69 }, +{ "gray28", 71, 71, 71 }, +{ "grey28", 71, 71, 71 }, +{ "gray29", 74, 74, 74 }, +{ "grey29", 74, 74, 74 }, +{ "gray30", 77, 77, 77 }, +{ "grey30", 77, 77, 77 }, +{ "gray31", 79, 79, 79 }, +{ "grey31", 79, 79, 79 }, +{ "gray32", 82, 82, 82 }, +{ "grey32", 82, 82, 82 }, +{ "gray33", 84, 84, 84 }, +{ "grey33", 84, 84, 84 }, +{ "gray34", 87, 87, 87 }, +{ "grey34", 87, 87, 87 }, +{ "gray35", 89, 89, 89 }, +{ "grey35", 89, 89, 89 }, +{ "gray36", 92, 92, 92 }, +{ "grey36", 92, 92, 92 }, +{ "gray37", 94, 94, 94 }, +{ "grey37", 94, 94, 94 }, +{ "gray38", 97, 97, 97 }, +{ "grey38", 97, 97, 97 }, +{ "gray39", 99, 99, 99 }, +{ "grey39", 99, 99, 99 }, +{ "gray40", 102, 102, 102 }, +{ "grey40", 102, 102, 102 }, +{ "gray41", 105, 105, 105 }, +{ "grey41", 105, 105, 105 }, +{ "gray42", 107, 107, 107 }, +{ "grey42", 107, 107, 107 }, +{ "gray43", 110, 110, 110 }, +{ "grey43", 110, 110, 110 }, +{ "gray44", 112, 112, 112 }, +{ "grey44", 112, 112, 112 }, +{ "gray45", 115, 115, 115 }, +{ "grey45", 115, 115, 115 }, +{ "gray46", 117, 117, 117 }, +{ "grey46", 117, 117, 117 }, +{ "gray47", 120, 120, 120 }, +{ "grey47", 120, 120, 120 }, +{ "gray48", 122, 122, 122 }, +{ "grey48", 122, 122, 122 }, +{ "gray49", 125, 125, 125 }, +{ "grey49", 125, 125, 125 }, +{ "gray50", 127, 127, 127 }, +{ "grey50", 127, 127, 127 }, +{ "gray51", 130, 130, 130 }, +{ "grey51", 130, 130, 130 }, +{ "gray52", 133, 133, 133 }, +{ "grey52", 133, 133, 133 }, +{ "gray53", 135, 135, 135 }, +{ "grey53", 135, 135, 135 }, +{ "gray54", 138, 138, 138 }, +{ "grey54", 138, 138, 138 }, +{ "gray55", 140, 140, 140 }, +{ "grey55", 140, 140, 140 }, +{ "gray56", 143, 143, 143 }, +{ "grey56", 143, 143, 143 }, +{ "gray57", 145, 145, 145 }, +{ "grey57", 145, 145, 145 }, +{ "gray58", 148, 148, 148 }, +{ "grey58", 148, 148, 148 }, +{ "gray59", 150, 150, 150 }, +{ "grey59", 150, 150, 150 }, +{ "gray60", 153, 153, 153 }, +{ "grey60", 153, 153, 153 }, +{ "gray61", 156, 156, 156 }, +{ "grey61", 156, 156, 156 }, +{ "gray62", 158, 158, 158 }, +{ "grey62", 158, 158, 158 }, +{ "gray63", 161, 161, 161 }, +{ "grey63", 161, 161, 161 }, +{ "gray64", 163, 163, 163 }, +{ "grey64", 163, 163, 163 }, +{ "gray65", 166, 166, 166 }, +{ "grey65", 166, 166, 166 }, +{ "gray66", 168, 168, 168 }, +{ "grey66", 168, 168, 168 }, +{ "gray67", 171, 171, 171 }, +{ "grey67", 171, 171, 171 }, +{ "gray68", 173, 173, 173 }, +{ "grey68", 173, 173, 173 }, +{ "gray69", 176, 176, 176 }, +{ "grey69", 176, 176, 176 }, +{ "gray70", 179, 179, 179 }, +{ "grey70", 179, 179, 179 }, +{ "gray71", 181, 181, 181 }, +{ "grey71", 181, 181, 181 }, +{ "gray72", 184, 184, 184 }, +{ "grey72", 184, 184, 184 }, +{ "gray73", 186, 186, 186 }, +{ "grey73", 186, 186, 186 }, +{ "gray74", 189, 189, 189 }, +{ "grey74", 189, 189, 189 }, +{ "gray75", 191, 191, 191 }, +{ "grey75", 191, 191, 191 }, +{ "gray76", 194, 194, 194 }, +{ "grey76", 194, 194, 194 }, +{ "gray77", 196, 196, 196 }, +{ "grey77", 196, 196, 196 }, +{ "gray78", 199, 199, 199 }, +{ "grey78", 199, 199, 199 }, +{ "gray79", 201, 201, 201 }, +{ "grey79", 201, 201, 201 }, +{ "gray80", 204, 204, 204 }, +{ "grey80", 204, 204, 204 }, +{ "gray81", 207, 207, 207 }, +{ "grey81", 207, 207, 207 }, +{ "gray82", 209, 209, 209 }, +{ "grey82", 209, 209, 209 }, +{ "gray83", 212, 212, 212 }, +{ "grey83", 212, 212, 212 }, +{ "gray84", 214, 214, 214 }, +{ "grey84", 214, 214, 214 }, +{ "gray85", 217, 217, 217 }, +{ "grey85", 217, 217, 217 }, +{ "gray86", 219, 219, 219 }, +{ "grey86", 219, 219, 219 }, +{ "gray87", 222, 222, 222 }, +{ "grey87", 222, 222, 222 }, +{ "gray88", 224, 224, 224 }, +{ "grey88", 224, 224, 224 }, +{ "gray89", 227, 227, 227 }, +{ "grey89", 227, 227, 227 }, +{ "gray90", 229, 229, 229 }, +{ "grey90", 229, 229, 229 }, +{ "gray91", 232, 232, 232 }, +{ "grey91", 232, 232, 232 }, +{ "gray92", 235, 235, 235 }, +{ "grey92", 235, 235, 235 }, +{ "gray93", 237, 237, 237 }, +{ "grey93", 237, 237, 237 }, +{ "gray94", 240, 240, 240 }, +{ "grey94", 240, 240, 240 }, +{ "gray95", 242, 242, 242 }, +{ "grey95", 242, 242, 242 }, +{ "gray96", 245, 245, 245 }, +{ "grey96", 245, 245, 245 }, +{ "gray97", 247, 247, 247 }, +{ "grey97", 247, 247, 247 }, +{ "gray98", 250, 250, 250 }, +{ "grey98", 250, 250, 250 }, +{ "gray99", 252, 252, 252 }, +{ "grey99", 252, 252, 252 }, +{ "gray100", 255, 255, 255 }, +{ "grey100", 255, 255, 255 }, +{ "DarkGrey", 169, 169, 169 }, +{ "DarkGray", 169, 169, 169 }, +{ "DarkBlue", 0, 0, 139 }, +{ "DarkCyan", 0, 139, 139 }, +{ "DarkMagenta", 139, 0, 139 }, +{ "DarkRed", 139, 0, 0 }, +{ "LightGreen", 144, 238, 144 }, +{ nullptr, 0 , 0, 0} +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_IXPM_RGBTABLE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ixpm/xpmread.cxx b/vcl/source/filter/ixpm/xpmread.cxx new file mode 100644 index 000000000..2a979d2df --- /dev/null +++ b/vcl/source/filter/ixpm/xpmread.cxx @@ -0,0 +1,697 @@ +/* -*- 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 <filter/XpmReader.hxx> + +#include <vcl/graph.hxx> +#include <tools/stream.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +#include "rgbtable.hxx" + +#include <cstring> +#include <array> +#include <map> + +#define XPMTEMPBUFSIZE 0x00008000 +#define XPMSTRINGBUF 0x00008000 + +#define XPMIDENTIFIER 0x00000001 // mnIdentifier includes one of the six phases +#define XPMDEFINITION 0x00000002 // the XPM format consists of +#define XPMVALUES 0x00000003 +#define XPMCOLORS 0x00000004 +#define XPMPIXELS 0x00000005 +#define XPMEXTENSIONS 0x00000006 +#define XPMENDEXT 0x00000007 + +#define XPMREMARK 0x00000001 // defines used by mnStatus +#define XPMDOUBLE 0x00000002 +#define XPMSTRING 0x00000004 +#define XPMFINISHED 0x00000008 + +namespace { + +enum ReadState +{ + XPMREAD_OK, + XPMREAD_ERROR, + XPMREAD_NEED_MORE +}; + +} + +class BitmapWriteAccess; +class Graphic; + +namespace { + +class XPMReader : public GraphicReader +{ +private: + + SvStream& mrIStm; + Bitmap maBmp; + BitmapScopedWriteAccess mpAcc; + Bitmap maMaskBmp; + BitmapScopedWriteAccess mpMaskAcc; + tools::Long mnLastPos; + + sal_uLong mnWidth; + sal_uLong mnHeight; + sal_uLong mnColors; + sal_uInt32 mnCpp; // characters per pix + bool mbTransparent; + bool mbStatus; + sal_uLong mnStatus; + sal_uLong mnIdentifier; + sal_uInt8 mcThisByte; + sal_uInt8 mcLastByte; + sal_uLong mnTempAvail; + sal_uInt8* mpTempBuf; + sal_uInt8* mpTempPtr; + // each key is ( mnCpp )Byte(s)-> ASCII entry assigned to the colour + // each colordata is + // 1 Byte -> 0xFF if colour is transparent + // 3 Bytes -> RGB value of the colour + typedef std::array<sal_uInt8,4> colordata; + typedef std::map<OString, colordata> colormap; + colormap maColMap; + sal_uLong mnStringSize; + sal_uInt8* mpStringBuf; + sal_uLong mnParaSize; + sal_uInt8* mpPara; + + bool ImplGetString(); + bool ImplGetColor(); + bool ImplGetScanLine( sal_uLong ); + bool ImplGetColSub(colordata &rDest); + bool ImplGetColKey( sal_uInt8 ); + void ImplGetRGBHex(colordata &rDest, sal_uLong); + bool ImplGetPara( sal_uLong numb ); + static bool ImplCompare(sal_uInt8 const *, sal_uInt8 const *, sal_uLong); + sal_uLong ImplGetULONG( sal_uLong nPara ); + +public: + explicit XPMReader( SvStream& rStm ); + + ReadState ReadXPM( Graphic& rGraphic ); +}; + +} + +XPMReader::XPMReader(SvStream& rStm) + : mrIStm(rStm) + , mnLastPos(rStm.Tell()) + , mnWidth(0) + , mnHeight(0) + , mnColors(0) + , mnCpp(0) + , mbTransparent(false) + , mbStatus(true) + , mnStatus( 0 ) + , mnIdentifier(XPMIDENTIFIER) + , mcThisByte(0) + , mcLastByte(0) + , mnTempAvail(0) + , mpTempBuf(nullptr) + , mpTempPtr(nullptr) + , mnStringSize(0) + , mpStringBuf(nullptr) + , mnParaSize(0) + , mpPara(nullptr) +{ +} + +ReadState XPMReader::ReadXPM( Graphic& rGraphic ) +{ + ReadState eReadState; + sal_uInt8 cDummy; + + // check if we can real ALL + mrIStm.Seek( STREAM_SEEK_TO_END ); + mrIStm.ReadUChar( cDummy ); + + // if we could not read all + // return and wait for new data + if ( mrIStm.GetError() != ERRCODE_IO_PENDING ) + { + mrIStm.Seek( mnLastPos ); + mbStatus = true; + + if ( mbStatus ) + { + mpStringBuf = new sal_uInt8 [ XPMSTRINGBUF ]; + mpTempBuf = new sal_uInt8 [ XPMTEMPBUFSIZE ]; + + mbStatus = ImplGetString(); + if ( mbStatus ) + { + mnIdentifier = XPMVALUES; // fetch Bitmap information + mnWidth = ImplGetULONG( 0 ); + mnHeight = ImplGetULONG( 1 ); + mnColors = ImplGetULONG( 2 ); + mnCpp = ImplGetULONG( 3 ); + } + if ( mnColors > ( SAL_MAX_UINT32 / ( 4 + mnCpp ) ) ) + mbStatus = false; + if ( ( mnWidth * mnCpp ) >= XPMSTRINGBUF ) + mbStatus = false; + //xpms are a minimum of one character (one byte) per pixel, so if the file isn't + //even that long, it's not all there + if (mrIStm.remainingSize() + mnTempAvail < static_cast<sal_uInt64>(mnWidth) * mnHeight) + mbStatus = false; + if ( mbStatus && mnWidth && mnHeight && mnColors && mnCpp ) + { + mnIdentifier = XPMCOLORS; + + for (sal_uLong i = 0; i < mnColors; ++i) + { + if (!ImplGetColor()) + { + mbStatus = false; + break; + } + } + + if ( mbStatus ) + { + // create a 24bit graphic when more as 256 colours present + auto ePixelFormat = vcl::PixelFormat::INVALID; + if ( mnColors > 256 ) + ePixelFormat = vcl::PixelFormat::N24_BPP; + else if ( mnColors > 2 ) + ePixelFormat = vcl::PixelFormat::N8_BPP; + else + ePixelFormat = vcl::PixelFormat::N1_BPP; + + maBmp = Bitmap(Size(mnWidth, mnHeight), ePixelFormat); + mpAcc = BitmapScopedWriteAccess(maBmp); + + // mbTransparent is TRUE if at least one colour is transparent + if ( mbTransparent ) + { + maMaskBmp = Bitmap(Size(mnWidth, mnHeight), vcl::PixelFormat::N1_BPP); + mpMaskAcc = BitmapScopedWriteAccess(maMaskBmp); + if ( !mpMaskAcc ) + mbStatus = false; + } + if( mpAcc && mbStatus ) + { + if (mnColors <= 256) // palette is only needed by using less than 257 + { // colors + sal_uInt8 i = 0; + for (auto& elem : maColMap) + { + mpAcc->SetPaletteColor(i, Color(elem.second[1], elem.second[2], elem.second[3])); + //reuse map entry, overwrite color with palette index + elem.second[1] = i; + i++; + } + } + + // now we get the bitmap data + mnIdentifier = XPMPIXELS; + for (sal_uLong i = 0; i < mnHeight; ++i) + { + if ( !ImplGetScanLine( i ) ) + { + mbStatus = false; + break; + } + } + mnIdentifier = XPMEXTENSIONS; + } + } + } + + delete[] mpStringBuf; + delete[] mpTempBuf; + + } + if( mbStatus ) + { + mpAcc.reset(); + if ( mpMaskAcc ) + { + mpMaskAcc.reset(); + rGraphic = Graphic( BitmapEx( maBmp, maMaskBmp ) ); + } + else + { + rGraphic = BitmapEx(maBmp); + } + eReadState = XPMREAD_OK; + } + else + { + mpMaskAcc.reset(); + mpAcc.reset(); + + eReadState = XPMREAD_ERROR; + } + } + else + { + mrIStm.ResetError(); + eReadState = XPMREAD_NEED_MORE; + } + return eReadState; +} + +// ImplGetColor returns various colour values, +// returns TRUE if various colours could be assigned +bool XPMReader::ImplGetColor() +{ + sal_uInt8* pString = mpStringBuf; + if (!ImplGetString()) + return false; + + if (mnStringSize < mnCpp) + return false; + + OString aKey(reinterpret_cast<char*>(pString), mnCpp); + colordata aValue{0}; + bool bStatus = ImplGetColSub(aValue); + if (bStatus) + { + maColMap[aKey] = aValue; + } + return bStatus; +} + +// ImpGetScanLine reads the string mpBufSize and writes the pixel in the +// Bitmap. Parameter nY is the horizontal position. +bool XPMReader::ImplGetScanLine( sal_uLong nY ) +{ + bool bStatus = ImplGetString(); + sal_uInt8* pString = mpStringBuf; + BitmapColor aWhite; + BitmapColor aBlack; + + if ( bStatus ) + { + if ( mpMaskAcc ) + { + aWhite = mpMaskAcc->GetBestMatchingColor( COL_WHITE ); + aBlack = mpMaskAcc->GetBestMatchingColor( COL_BLACK ); + } + if ( mnStringSize != ( mnWidth * mnCpp )) + bStatus = false; + else + { + Scanline pScanline = mpAcc->GetScanline(nY); + Scanline pMaskScanline = mpMaskAcc ? mpMaskAcc->GetScanline(nY) : nullptr; + for (sal_uLong i = 0; i < mnWidth; ++i) + { + OString aKey(reinterpret_cast<char*>(pString), mnCpp); + auto it = maColMap.find(aKey); + if (it != maColMap.end()) + { + if (mnColors > 256) + mpAcc->SetPixelOnData(pScanline, i, Color(it->second[1], it->second[2], it->second[3])); + else + mpAcc->SetPixelOnData(pScanline, i, BitmapColor(it->second[1])); + if (pMaskScanline) + mpMaskAcc->SetPixelOnData(pMaskScanline, i, it->second[0] ? aWhite : aBlack); + } + pString += mnCpp; + } + } + } + return bStatus; +} + +// tries to determine a colour value from mpStringBuf +// if a colour was found the RGB value is written a pDest[1]..pDest[2] +// pDest[0] contains 0xFF if the colour is transparent otherwise 0 + +bool XPMReader::ImplGetColSub(colordata &rDest) +{ + unsigned char cTransparent[] = "None"; + + bool bColStatus = false; + + if ( ImplGetColKey( 'c' ) || ImplGetColKey( 'm' ) || ImplGetColKey( 'g' ) ) + { + // hexentry for RGB or HSV color ? + if (*mpPara == '#') + { + rDest[0] = 0; + bColStatus = true; + switch ( mnParaSize ) + { + case 25 : + ImplGetRGBHex(rDest, 6); + break; + case 13 : + ImplGetRGBHex(rDest, 2); + break; + case 7 : + ImplGetRGBHex(rDest, 0); + break; + default: + bColStatus = false; + break; + } + } + // maybe pixel is transparent + else if ( ImplCompare( &cTransparent[0], mpPara, 4 )) + { + rDest[0] = 0xff; + bColStatus = true; + mbTransparent = true; + } + // last we will try to get the colorname + else if ( mnParaSize > 2 ) // name must enlarge the minimum size + { + sal_uLong i = 0; + while ( true ) + { + if ( pRGBTable[ i ].name == nullptr ) + break; + if ( std::strlen(pRGBTable[i].name) > mnParaSize && + pRGBTable[ i ].name[ mnParaSize ] == 0 ) + { + if ( ImplCompare ( reinterpret_cast<unsigned char const *>(pRGBTable[ i ].name), + mpPara, mnParaSize ) ) + { + bColStatus = true; + rDest[0] = 0; + rDest[1] = pRGBTable[i].red; + rDest[2] = pRGBTable[i].green; + rDest[3] = pRGBTable[i].blue; + break; + } + } + i++; + } + } + } + return bColStatus; +} + +// ImplGetColKey searches string mpStringBuf for a parameter 'nKey' +// and returns a boolean. (if TRUE mpPara and mnParaSize will be set) + +bool XPMReader::ImplGetColKey( sal_uInt8 nKey ) +{ + sal_uInt8 nTemp, nPrev = ' '; + + if (mnStringSize < mnCpp + 1) + return false; + + mpPara = mpStringBuf + mnCpp + 1; + mnParaSize = 0; + + while ( *mpPara != 0 ) + { + if ( *mpPara == nKey ) + { + nTemp = *( mpPara + 1 ); + if ( nTemp == ' ' || nTemp == 0x09 ) + { + if ( nPrev == ' ' || nPrev == 0x09 ) + break; + } + } + nPrev = *mpPara; + mpPara++; + } + if ( *mpPara ) + { + mpPara++; + while ( (*mpPara == ' ') || (*mpPara == 0x09) ) + { + mpPara++; + } + if ( *mpPara != 0 ) + { + while ( *(mpPara+mnParaSize) != ' ' && *(mpPara+mnParaSize) != 0x09 && + *(mpPara+mnParaSize) != 0 ) + { + mnParaSize++; + } + } + } + return mnParaSize != 0; +} + +// ImplGetRGBHex translates the ASCII-Hexadecimalvalue belonging to mpPara +// in a RGB value and writes this to rDest +// below formats should be contained in mpPara: +// if nAdd = 0 : '#12ab12' -> RGB = 0x12, 0xab, 0x12 +// 2 : '#1234abcd1234' " " " " +// 6 : '#12345678abcdefab12345678' " " " " + +void XPMReader::ImplGetRGBHex(colordata &rDest, sal_uLong nAdd) +{ + sal_uInt8* pPtr = mpPara+1; + + for (sal_uLong i = 1; i < 4; ++i) + { + sal_uInt8 nHex = (*pPtr++) - '0'; + if ( nHex > 9 ) + nHex = ((nHex - 'A' + '0') & 7) + 10; + + sal_uInt8 nTemp = (*pPtr++) - '0'; + if ( nTemp > 9 ) + nTemp = ((nTemp - 'A' + '0') & 7) + 10; + nHex = ( nHex << 4 ) + nTemp; + + pPtr += nAdd; + rDest[i] = nHex; + } +} + +// ImplGetUlong returns the value of a up to 6-digit long ASCII-decimal number. + +sal_uLong XPMReader::ImplGetULONG( sal_uLong nPara ) +{ + if ( ImplGetPara ( nPara ) ) + { + sal_uLong nRetValue = 0; + sal_uInt8* pPtr = mpPara; + + if ( ( mnParaSize > 6 ) || ( mnParaSize == 0 ) ) return 0; + for ( sal_uLong i = 0; i < mnParaSize; i++ ) + { + sal_uInt8 j = (*pPtr++) - 48; + if ( j > 9 ) return 0; // ascii is invalid + nRetValue*=10; + nRetValue+=j; + } + return nRetValue; + } + else return 0; +} + +bool XPMReader::ImplCompare(sal_uInt8 const * pSource, sal_uInt8 const * pDest, sal_uLong nSize) +{ + for (sal_uLong i = 0; i < nSize; ++i) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + { + return false; + } + } + return true; +} + +// ImplGetPara tries to retrieve nNumb (0...x) parameters from mpStringBuf. +// Parameters are separated by spaces or tabs. +// If a parameter was found then the return value is TRUE and mpPara + mnParaSize +// are set. + +bool XPMReader::ImplGetPara ( sal_uLong nNumb ) +{ + sal_uInt8 nByte; + sal_uLong nSize = 0; + sal_uInt8* pPtr = mpStringBuf; + sal_uLong nCount = 0; + + if ( ( *pPtr != ' ' ) && ( *pPtr != 0x09 ) ) + { + mpPara = pPtr; + mnParaSize = 0; + nCount = 0; + } + else + { + mpPara = nullptr; + nCount = 0xffffffff; + } + + while ( nSize < mnStringSize ) + { + nByte = *pPtr; + + if ( mpPara ) + { + if ( ( nByte == ' ' ) || ( nByte == 0x09 ) ) + { + if ( nCount == nNumb ) + break; + else + mpPara = nullptr; + } + else + mnParaSize++; + } + else + { + if ( ( nByte != ' ' ) && ( nByte != 0x09 ) ) + { + mpPara = pPtr; + mnParaSize = 1; + nCount++; + } + } + nSize++; + pPtr++; + } + return ( ( nCount == nNumb ) && mpPara ); +} + +// The next string is read and stored in mpStringBuf (terminated with 0); +// mnStringSize contains the size of the string read. +// Comments like '//' and '/*...*/' are skipped. + +bool XPMReader::ImplGetString() +{ + sal_uInt8 const sID[] = "/* XPM */"; + sal_uInt8* pString = mpStringBuf; + + mnStringSize = 0; + mpStringBuf[0] = 0; + + while( mbStatus && ( mnStatus != XPMFINISHED ) ) + { + if ( mnTempAvail == 0 ) + { + mnTempAvail = mrIStm.ReadBytes( mpTempBuf, XPMTEMPBUFSIZE ); + if ( mnTempAvail == 0 ) + break; + + mpTempPtr = mpTempBuf; + + if ( mnIdentifier == XPMIDENTIFIER ) + { + if ( mnTempAvail <= 50 ) + { + mbStatus = false; // file is too short to be a correct XPM format + break; + } + for ( int i = 0; i < 9; i++ ) // searching for "/* XPM */" + if ( *mpTempPtr++ != sID[i] ) + { + mbStatus = false; + break; + } + mnTempAvail-=9; + mnIdentifier++; + } + } + mcLastByte = mcThisByte; + mcThisByte = *mpTempPtr++; + mnTempAvail--; + + if ( mnStatus & XPMDOUBLE ) + { + if ( mcThisByte == 0x0a ) + mnStatus &=~XPMDOUBLE; + continue; + } + if ( mnStatus & XPMREMARK ) + { + if ( ( mcThisByte == '/' ) && ( mcLastByte == '*' ) ) + mnStatus &=~XPMREMARK; + continue; + } + if ( mnStatus & XPMSTRING ) // characters in string + { + if ( mcThisByte == '"' ) + { + mnStatus &=~XPMSTRING; // end of parameter by eol + break; + } + if ( mnStringSize >= ( XPMSTRINGBUF - 1 ) ) + { + mbStatus = false; + break; + } + *pString++ = mcThisByte; + pString[0] = 0; + mnStringSize++; + continue; + } + else + { // characters beside string + switch ( mcThisByte ) + { + case '*' : + if ( mcLastByte == '/' ) mnStatus |= XPMREMARK; + break; + case '/' : + if ( mcLastByte == '/' ) mnStatus |= XPMDOUBLE; + break; + case '"' : mnStatus |= XPMSTRING; + break; + case '{' : + if ( mnIdentifier == XPMDEFINITION ) + mnIdentifier++; + break; + case '}' : + if ( mnIdentifier == XPMENDEXT ) + mnStatus = XPMFINISHED; + break; + } + } + } + return mbStatus; +} + + +VCL_DLLPUBLIC bool ImportXPM( SvStream& rStm, Graphic& rGraphic ) +{ + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + XPMReader* pXPMReader = dynamic_cast<XPMReader*>( pContext.get() ); + if (!pXPMReader) + { + pContext = std::make_shared<XPMReader>( rStm ); + pXPMReader = static_cast<XPMReader*>( pContext.get() ); + } + + bool bRet = true; + + ReadState eReadState = pXPMReader->ReadXPM( rGraphic ); + + if( eReadState == XPMREAD_ERROR ) + { + bRet = false; + } + else if( eReadState == XPMREAD_NEED_MORE ) + rGraphic.SetReaderContext( pContext ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/Exif.cxx b/vcl/source/filter/jpeg/Exif.cxx new file mode 100644 index 000000000..53b55f69a --- /dev/null +++ b/vcl/source/filter/jpeg/Exif.cxx @@ -0,0 +1,288 @@ +/* -*- 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 "Exif.hxx" +#include <memory> +#include <osl/endian.h> +#include <tools/stream.hxx> + +Exif::Exif() : + maOrientation(exif::TOP_LEFT), + mbExifPresent(false) +{} + +void Exif::setOrientation(exif::Orientation aOrientation) { + maOrientation = aOrientation; +} + +exif::Orientation Exif::convertToOrientation(sal_Int32 value) +{ + switch(value) { + case 1: return exif::TOP_LEFT; + case 2: return exif::TOP_RIGHT; + case 3: return exif::BOTTOM_RIGHT; + case 4: return exif::BOTTOM_LEFT; + case 5: return exif::LEFT_TOP; + case 6: return exif::RIGHT_TOP; + case 7: return exif::RIGHT_BOTTOM; + case 8: return exif::LEFT_BOTTOM; + } + return exif::TOP_LEFT; +} + +Degree10 Exif::getRotation() const +{ + switch(maOrientation) { + case exif::TOP_LEFT: + return 0_deg10; + case exif::BOTTOM_RIGHT: + return 1800_deg10; + case exif::RIGHT_TOP: + return 2700_deg10; + case exif::LEFT_BOTTOM: + return 900_deg10; + default: + break; + } + return 0_deg10; +} + +bool Exif::read(SvStream& rStream) +{ + sal_Int32 nStreamPosition = rStream.Tell(); + bool result = processJpeg(rStream, false); + rStream.Seek( nStreamPosition ); + + return result; +} + +void Exif::write(SvStream& rStream) +{ + sal_Int32 nStreamPosition = rStream.Tell(); + processJpeg(rStream, true); + rStream.Seek( nStreamPosition ); +} + +bool Exif::processJpeg(SvStream& rStream, bool bSetValue) +{ + sal_uInt16 aMagic16; + sal_uInt16 aLength; + + sal_uInt32 aSize = rStream.TellEnd(); + rStream.Seek(STREAM_SEEK_TO_BEGIN); + + rStream.SetEndian( SvStreamEndian::BIG ); + rStream.ReadUInt16( aMagic16 ); + + // Compare JPEG magic bytes + if( 0xFFD8 != aMagic16 ) + { + return false; + } + + sal_uInt32 aPreviousPosition = STREAM_SEEK_TO_BEGIN; + + while(true) + { + sal_uInt8 aMarker = 0xD9; + sal_Int32 aCount; + + for (aCount = 0; aCount < 7; aCount++) + { + rStream.ReadUChar( aMarker ); + if (aMarker != 0xFF) + { + break; + } + if (aCount >= 6) + { + return false; + } + } + + rStream.ReadUInt16( aLength ); + + if (aLength < 8 || aLength > rStream.remainingSize()) + { + return false; + } + + if (aMarker == 0xE1) + { + return processExif(rStream, aLength, bSetValue); + } + else if (aMarker == 0xD9) + { + return false; + } + else + { + sal_uInt32 aCurrentPosition = rStream.SeekRel(aLength-1); + if (aCurrentPosition == aPreviousPosition || aCurrentPosition > aSize) + { + return false; + } + aPreviousPosition = aCurrentPosition; + } + } + return false; +} + +namespace { + +sal_uInt16 read16(sal_uInt8 const (& data)[2], bool littleEndian) { + if (littleEndian) { + return data[0] | (sal_uInt16(data[1]) << 8); + } else { + return data[1] | (sal_uInt16(data[0]) << 8); + } +} + +void write16(sal_uInt16 value, sal_uInt8 (& data)[2], bool littleEndian) { + if (littleEndian) { + data[0] = value & 0xFF; + data[1] = value >> 8; + } else { + data[1] = value & 0xFF; + data[0] = value >> 8; + } +} + +void write32(sal_uInt32 value, sal_uInt8 (& data)[4], bool littleEndian) { + if (littleEndian) { + data[0] = value & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[2] = (value >> 16) & 0xFF; + data[3] = value >> 24; + } else { + data[3] = value & 0xFF; + data[2] = (value >> 8) & 0xFF; + data[1] = (value >> 16) & 0xFF; + data[0] = value >> 24; + } +} + +} + +void Exif::processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool littleEndian) +{ + ExifIFD* ifd = nullptr; + + while (aOffset <= aLength - 12 && aNumberOfTags > 0) + { + ifd = reinterpret_cast<ExifIFD*>(&pExifData[aOffset]); + sal_uInt16 tag = read16(ifd->tag, littleEndian); + + if (tag == ORIENTATION) + { + if(bSetValue) + { + write16(3, ifd->type, littleEndian); + write32(1, ifd->count, littleEndian); + write16( + maOrientation, reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian); + } + else + { + sal_uInt16 nIfdOffset = read16( + reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian); + maOrientation = convertToOrientation(nIfdOffset); + } + } + + aNumberOfTags--; + aOffset += 12; + } +} + +bool Exif::processExif(SvStream& rStream, sal_uInt16 aSectionLength, bool bSetValue) +{ + sal_uInt32 aMagic32; + sal_uInt16 aMagic16; + + rStream.ReadUInt32( aMagic32 ); + rStream.ReadUInt16( aMagic16 ); + + // Compare EXIF magic bytes + if( 0x45786966 != aMagic32 || 0x0000 != aMagic16) + { + return false; + } + + sal_uInt16 aLength = aSectionLength - 6; // Length = Section - Header + + std::unique_ptr<sal_uInt8[]> aExifData(new sal_uInt8[aLength]); + sal_uInt32 aExifDataBeginPosition = rStream.Tell(); + + rStream.ReadBytes(aExifData.get(), aLength); + + // Exif detected + mbExifPresent = true; + + TiffHeader* aTiffHeader = reinterpret_cast<TiffHeader*>(&aExifData[0]); + + bool bIntel = aTiffHeader->byteOrder == 0x4949; //little-endian + bool bMotorola = aTiffHeader->byteOrder == 0x4D4D; //big-endian + + if (!bIntel && !bMotorola) + { + return false; + } + + bool bSwap = false; + +#ifdef OSL_BIGENDIAN + if (bIntel) + bSwap = true; +#else + if (bMotorola) + bSwap = true; +#endif + + if (bSwap) + { + aTiffHeader->tagAlign = OSL_SWAPWORD(aTiffHeader->tagAlign); + aTiffHeader->offset = OSL_SWAPDWORD(aTiffHeader->offset); + } + + if (aTiffHeader->tagAlign != 0x002A) // TIFF tag + { + return false; + } + + sal_uInt16 aOffset = aTiffHeader->offset; + + sal_uInt16 aNumberOfTags = aExifData[aOffset]; + if (bSwap) + { + aNumberOfTags = ((aExifData[aOffset] << 8) | aExifData[aOffset+1]); + } + + processIFD(aExifData.get(), aLength, aOffset+2, aNumberOfTags, bSetValue, bIntel); + + if (bSetValue) + { + rStream.Seek(aExifDataBeginPosition); + rStream.WriteBytes(aExifData.get(), aLength); + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/Exif.hxx b/vcl/source/filter/jpeg/Exif.hxx new file mode 100644 index 000000000..287b7ae43 --- /dev/null +++ b/vcl/source/filter/jpeg/Exif.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#pragma once + +#include <tools/stream.hxx> +#include <tools/degree.hxx> + +namespace exif { + +enum Orientation { + TOP_LEFT = 1, + TOP_RIGHT = 2, + BOTTOM_RIGHT = 3, + BOTTOM_LEFT = 4, + LEFT_TOP = 5, + RIGHT_TOP = 6, + RIGHT_BOTTOM = 7, + LEFT_BOTTOM = 8 +}; +}; + +enum Tag { + ORIENTATION = 0x0112 +}; + +class Exif final +{ +private: + exif::Orientation maOrientation; + bool mbExifPresent; + + bool processJpeg(SvStream& rStream, bool bSetValue); + bool processExif(SvStream& rStream, sal_uInt16 aLength, bool bSetValue); + void processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool bLittleEndian); + + struct ExifIFD { + sal_uInt8 tag[2]; + sal_uInt8 type[2]; + sal_uInt8 count[4]; + sal_uInt8 offset[4]; + }; + + struct TiffHeader { + sal_uInt16 byteOrder; + sal_uInt16 tagAlign; + sal_uInt32 offset; + }; + + static exif::Orientation convertToOrientation(sal_Int32 value); + +public: + Exif(); + + bool hasExif() const { return mbExifPresent;} + + exif::Orientation getOrientation() const { return maOrientation;} + Degree10 getRotation() const; + + void setOrientation(exif::Orientation orientation); + + bool read(SvStream& rStream); + void write(SvStream& rStream); + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegReader.cxx b/vcl/source/filter/jpeg/JpegReader.cxx new file mode 100644 index 000000000..c68ba88d7 --- /dev/null +++ b/vcl/source/filter/jpeg/JpegReader.cxx @@ -0,0 +1,331 @@ +/* -*- 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 "jpeg.h" +#include <jpeglib.h> +#include <jerror.h> + +#include "JpegReader.hxx" +#include <vcl/graphicfilter.hxx> +#include <vcl/outdev.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <memory> + +#define BUFFER_SIZE 4096 + +extern "C" { + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ +static void init_source (j_decompress_ptr cinfo) +{ + SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src); + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + source->start_of_file = TRUE; + source->no_data_available_failures = 0; +} + +} + +static tools::Long StreamRead( SvStream* pStream, void* pBuffer, tools::Long nBufferSize ) +{ + tools::Long nRead = 0; + + if( pStream->GetError() != ERRCODE_IO_PENDING ) + { + sal_uInt64 nInitialPosition = pStream->Tell(); + + nRead = static_cast<tools::Long>(pStream->ReadBytes(pBuffer, nBufferSize)); + + if( pStream->GetError() == ERRCODE_IO_PENDING ) + { + // in order to search from the old position + // we temporarily reset the error + pStream->ResetError(); + pStream->Seek( nInitialPosition ); + pStream->SetError( ERRCODE_IO_PENDING ); + } + } + + return nRead; +} + +extern "C" { + +static boolean fill_input_buffer (j_decompress_ptr cinfo) +{ + SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src); + size_t nbytes; + + nbytes = StreamRead(source->stream, source->buffer, BUFFER_SIZE); + + if (!nbytes) + { + source->no_data_available_failures++; + if (source->start_of_file) /* Treat empty input file as fatal error */ + { + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + source->buffer[0] = JOCTET(0xFF); + source->buffer[1] = JOCTET(JPEG_EOI); + nbytes = 2; + } + + source->pub.next_input_byte = source->buffer; + source->pub.bytes_in_buffer = nbytes; + source->start_of_file = FALSE; + + return TRUE; +} + +static void skip_input_data (j_decompress_ptr cinfo, long numberOfBytes) +{ + SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src); + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (numberOfBytes <= 0) + return; + + while (numberOfBytes > static_cast<tools::Long>(source->pub.bytes_in_buffer)) + { + numberOfBytes -= static_cast<tools::Long>(source->pub.bytes_in_buffer); + (void) fill_input_buffer(cinfo); + + /* note we assume that fill_input_buffer will never return false, + * so suspension need not be handled. + */ + } + source->pub.next_input_byte += static_cast<size_t>(numberOfBytes); + source->pub.bytes_in_buffer -= static_cast<size_t>(numberOfBytes); +} + +static void term_source (j_decompress_ptr) +{ + /* no work necessary here */ +} + +} + +void jpeg_svstream_src (j_decompress_ptr cinfo, void* input) +{ + SourceManagerStruct * source; + SvStream* stream = static_cast<SvStream*>(input); + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + + if (cinfo->src == nullptr) + { /* first time for this JPEG object? */ + cinfo->src = static_cast<jpeg_source_mgr *>( + (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(SourceManagerStruct))); + source = reinterpret_cast<SourceManagerStruct *>(cinfo->src); + source->buffer = static_cast<JOCTET *>( + (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, BUFFER_SIZE * sizeof(JOCTET))); + } + + source = reinterpret_cast<SourceManagerStruct*>(cinfo->src); + source->pub.init_source = init_source; + source->pub.fill_input_buffer = fill_input_buffer; + source->pub.skip_input_data = skip_input_data; + source->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + source->pub.term_source = term_source; + source->stream = stream; + source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + source->pub.next_input_byte = nullptr; /* until buffer loaded */ +} + +JPEGReader::JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ) : + mrStream ( rStream ), + mnLastPos ( rStream.Tell() ), + mnLastLines ( 0 ), + mbSetLogSize ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg ) +{ + maUpperName = "SVIJPEG"; + + if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)) + { + mpBitmap.reset(new Bitmap()); + mpIncompleteAlpha.reset(new Bitmap()); + } +} + +JPEGReader::~JPEGReader() +{ +} + +bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam const & rParam) +{ + if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8) + return false; // avoid overflows later + + if (rParam.nWidth == 0 || rParam.nHeight == 0) + return false; + + Size aSize(rParam.nWidth, rParam.nHeight); + bool bGray = rParam.bGray; + + mpBitmap.reset(new Bitmap()); + + sal_uInt64 nSize = aSize.Width() * aSize.Height(); + + if (nSize > SAL_MAX_INT32 / (bGray?1:3)) + return false; + + if( bGray ) + { + BitmapPalette aGrayPal( 256 ); + + for( sal_uInt16 n = 0; n < 256; n++ ) + { + const sal_uInt8 cGray = static_cast<sal_uInt8>(n); + aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray ); + } + + mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal)); + } + else + { + mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N24_BPP)); + } + + if (mbSetLogSize) + { + unsigned long nUnit = rParam.density_unit; + + if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density ) + { + Fraction aFractX( 1, rParam.X_density ); + Fraction aFractY( 1, rParam.Y_density ); + MapMode aMapMode( nUnit == 1 ? MapUnit::MapInch : MapUnit::MapCM, Point(), aFractX, aFractY ); + Size aPrefSize = OutputDevice::LogicToLogic(aSize, aMapMode, MapMode(MapUnit::Map100thMM)); + + mpBitmap->SetPrefSize(aPrefSize); + mpBitmap->SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + } + } + + return true; +} + +Graphic JPEGReader::CreateIntermediateGraphic(tools::Long nLines) +{ + Graphic aGraphic; + const Size aSizePixel(mpBitmap->GetSizePixel()); + + if (!mnLastLines) + { + mpIncompleteAlpha.reset(new Bitmap(aSizePixel, vcl::PixelFormat::N1_BPP)); + mpIncompleteAlpha->Erase(COL_WHITE); + } + + if (nLines && (nLines < aSizePixel.Height())) + { + const tools::Long nNewLines = nLines - mnLastLines; + + if (nNewLines > 0) + { + { + BitmapScopedWriteAccess pAccess(*mpIncompleteAlpha); + pAccess->SetFillColor(COL_BLACK); + pAccess->FillRect(tools::Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines))); + } + + aGraphic = BitmapEx(*mpBitmap, *mpIncompleteAlpha); + } + else + { + aGraphic = BitmapEx(*mpBitmap); + } + } + else + { + aGraphic = BitmapEx(*mpBitmap); + } + + mnLastLines = nLines; + + return aGraphic; +} + +ReadState JPEGReader::Read( Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess ) +{ + ReadState eReadState; + bool bRet = false; + + // seek back to the original position + mrStream.Seek( mnLastPos ); + + // read the (partial) image + tools::Long nLines; + ReadJPEG( this, &mrStream, &nLines, nImportFlags, ppAccess ); + + auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap); + if (bUseExistingBitmap || !mpBitmap->IsEmpty()) + { + if( mrStream.GetError() == ERRCODE_IO_PENDING ) + { + rGraphic = CreateIntermediateGraphic(nLines); + } + else + { + if (!bUseExistingBitmap) + rGraphic = BitmapEx(*mpBitmap); + } + + bRet = true; + } + else if( mrStream.GetError() == ERRCODE_IO_PENDING ) + { + bRet = true; + } + + // Set status ( Pending has priority ) + if (mrStream.GetError() == ERRCODE_IO_PENDING) + { + eReadState = JPEGREAD_NEED_MORE; + mrStream.ResetError(); + } + else + { + eReadState = bRet ? JPEGREAD_OK : JPEGREAD_ERROR; + } + + return eReadState; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegReader.hxx b/vcl/source/filter/jpeg/JpegReader.hxx new file mode 100644 index 000000000..f9a2eb292 --- /dev/null +++ b/vcl/source/filter/jpeg/JpegReader.hxx @@ -0,0 +1,74 @@ +/* -*- 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_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX +#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX + +#include <vcl/graph.hxx> +#include <vcl/bitmap.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +enum class GraphicFilterImportFlags; + +enum ReadState +{ + JPEGREAD_OK, + JPEGREAD_ERROR, + JPEGREAD_NEED_MORE +}; + +struct JPEGCreateBitmapParam +{ + tools::ULong nWidth; + tools::ULong nHeight; + tools::ULong density_unit; + tools::ULong X_density; + tools::ULong Y_density; + + bool bGray; +}; + +class JPEGReader : public GraphicReader +{ + SvStream& mrStream; + std::unique_ptr<Bitmap> mpBitmap; + std::unique_ptr<Bitmap> mpIncompleteAlpha; + + tools::Long mnLastPos; + tools::Long mnLastLines; + bool mbSetLogSize; + + Graphic CreateIntermediateGraphic(tools::Long nLines); + +public: + JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ); + virtual ~JPEGReader() override; + + ReadState Read(Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess); + + bool CreateBitmap(JPEGCreateBitmapParam const & param); + + Bitmap& GetBitmap() { return *mpBitmap; } +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegTransform.cxx b/vcl/source/filter/jpeg/JpegTransform.cxx new file mode 100644 index 000000000..16c0c060b --- /dev/null +++ b/vcl/source/filter/jpeg/JpegTransform.cxx @@ -0,0 +1,42 @@ +/* -*- 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 "jpeg.h" + +#include "JpegTransform.hxx" + +JpegTransform::JpegTransform(SvStream& rInputStream, SvStream& rOutputStream) : + maRotate ( 0 ), + mrInputStream ( rInputStream ), + mrOutputStream ( rOutputStream ) +{} + +void JpegTransform::perform() +{ + Transform( &mrInputStream, &mrOutputStream, maRotate ); +} + +void JpegTransform::setRotate(Degree10 aRotate) +{ + maRotate = aRotate; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegTransform.hxx b/vcl/source/filter/jpeg/JpegTransform.hxx new file mode 100644 index 000000000..2b5c6dfd6 --- /dev/null +++ b/vcl/source/filter/jpeg/JpegTransform.hxx @@ -0,0 +1,40 @@ +/* -*- 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_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX +#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX + +#include <tools/stream.hxx> + +class JpegTransform final +{ + Degree10 maRotate; + SvStream& mrInputStream; + SvStream& mrOutputStream; + +public: + JpegTransform(SvStream& rInputStream, SvStream& rOutputStream); + + void setRotate(Degree10 aRotate); + void perform(); +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegWriter.cxx b/vcl/source/filter/jpeg/JpegWriter.cxx new file mode 100644 index 000000000..026ab887b --- /dev/null +++ b/vcl/source/filter/jpeg/JpegWriter.cxx @@ -0,0 +1,265 @@ +/* -*- 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 <sal/log.hxx> + +#include "jpeg.h" +#include <jpeglib.h> +#include <jerror.h> + +#include "JpegWriter.hxx" +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> + +#define BUFFER_SIZE 4096 + +namespace { + +struct DestinationManagerStruct +{ + jpeg_destination_mgr pub; /* public fields */ + SvStream* stream; /* target stream */ + JOCTET * buffer; /* start of buffer */ +}; + +} + +extern "C" { + +static void init_destination (j_compress_ptr cinfo) +{ + DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest); + + /* Allocate the output buffer -- it will be released when done with image */ + destination->buffer = static_cast<JOCTET *>( + (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, BUFFER_SIZE * sizeof(JOCTET))); + + destination->pub.next_output_byte = destination->buffer; + destination->pub.free_in_buffer = BUFFER_SIZE; +} + +static boolean empty_output_buffer (j_compress_ptr cinfo) +{ + DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest); + + if (destination->stream->WriteBytes(destination->buffer, BUFFER_SIZE) != BUFFER_SIZE) + { + ERREXIT(cinfo, JERR_FILE_WRITE); + } + + destination->pub.next_output_byte = destination->buffer; + destination->pub.free_in_buffer = BUFFER_SIZE; + + return TRUE; +} + +static void term_destination (j_compress_ptr cinfo) +{ + DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest); + size_t datacount = BUFFER_SIZE - destination->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) + { + if (destination->stream->WriteBytes(destination->buffer, datacount) != datacount) + { + ERREXIT(cinfo, JERR_FILE_WRITE); + } + } +} + +} + +void jpeg_svstream_dest (j_compress_ptr cinfo, void* output) +{ + SvStream* stream = static_cast<SvStream*>(output); + DestinationManagerStruct * destination; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_svstream_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == nullptr) + { /* first time for this JPEG object? */ + cinfo->dest = static_cast<jpeg_destination_mgr*>( + (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(DestinationManagerStruct))); + } + + destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest); + destination->pub.init_destination = init_destination; + destination->pub.empty_output_buffer = empty_output_buffer; + destination->pub.term_destination = term_destination; + destination->stream = stream; +} + +JPEGWriter::JPEGWriter( SvStream& rStream, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, bool* pExportWasGrey ) : + mrStream ( rStream ), + mpBuffer ( nullptr ), + mbNative ( false ), + mpExpWasGrey ( pExportWasGrey ) +{ + FilterConfigItem aConfigItem( pFilterData ); + mbGreys = aConfigItem.ReadInt32( "ColorMode", 0 ) != 0; + mnQuality = aConfigItem.ReadInt32( "Quality", 75 ); + maChromaSubsampling = aConfigItem.ReadInt32( "ChromaSubsamplingMode", 0 ); + + if ( pFilterData ) + { + for( const auto& rValue : *pFilterData ) + { + if ( rValue.Name == "StatusIndicator" ) + { + rValue.Value >>= mxStatusIndicator; + } + } + } +} + +void* JPEGWriter::GetScanline( tools::Long nY ) +{ + void* pScanline = nullptr; + + if( mpReadAccess ) + { + if( mbNative ) + { + pScanline = mpReadAccess->GetScanline( nY ); + } + else if( mpBuffer ) + { + BitmapColor aColor; + tools::Long nWidth = mpReadAccess->Width(); + sal_uInt8* pTmp = mpBuffer; + + if( mpReadAccess->HasPalette() ) + { + Scanline pScanlineRead = mpReadAccess->GetScanline( nY ); + for( tools::Long nX = 0; nX < nWidth; nX++ ) + { + aColor = mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) ); + *pTmp++ = aColor.GetRed(); + if ( !mbGreys ) + { + *pTmp++ = aColor.GetGreen(); + *pTmp++ = aColor.GetBlue(); + } + } + } + else + { + Scanline pScanlineRead = mpReadAccess->GetScanline( nY ); + for( tools::Long nX = 0; nX < nWidth; nX++ ) + { + aColor = mpReadAccess->GetPixelFromData( pScanlineRead, nX ); + *pTmp++ = aColor.GetRed(); + if ( !mbGreys ) + { + *pTmp++ = aColor.GetGreen(); + *pTmp++ = aColor.GetBlue(); + } + } + } + + pScanline = mpBuffer; + } + } + + return pScanline; +} + +bool JPEGWriter::Write( const Graphic& rGraphic ) +{ + bool bRet = false; + + if ( mxStatusIndicator.is() ) + { + mxStatusIndicator->start( OUString(), 100 ); + } + + // This slightly weird logic is here to match the behaviour in ImpGraphic::ImplGetBitmap + // and is necessary to match pre-existing behaviour. We should probably pass down the expected + // background color for alpha from the higher layers. + Bitmap aGraphicBmp; + if (rGraphic.GetType() == GraphicType::Bitmap) + aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(COL_WHITE); + else + aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(); + + if ( mbGreys ) + { + if ( !aGraphicBmp.Convert( BmpConversion::N8BitGreys ) ) + aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(); + } + + mpReadAccess = Bitmap::ScopedReadAccess(aGraphicBmp); + if( mpReadAccess ) + { + if ( !mbGreys ) // bitmap was not explicitly converted into greyscale, + { // check if source is greyscale only + bool bIsGrey = true; + + tools::Long nWidth = mpReadAccess->Width(); + for ( tools::Long nY = 0; bIsGrey && ( nY < mpReadAccess->Height() ); nY++ ) + { + BitmapColor aColor; + Scanline pScanlineRead = mpReadAccess->GetScanline( nY ); + for( tools::Long nX = 0; bIsGrey && ( nX < nWidth ); nX++ ) + { + aColor = mpReadAccess->HasPalette() ? mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) ) + : mpReadAccess->GetPixelFromData( pScanlineRead, nX ); + bIsGrey = ( aColor.GetRed() == aColor.GetGreen() ) && ( aColor.GetRed() == aColor.GetBlue() ); + } + } + if ( bIsGrey ) + mbGreys = true; + } + if( mpExpWasGrey ) + *mpExpWasGrey = mbGreys; + + if ( mbGreys ) + mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal && aGraphicBmp.HasGreyPalette8Bit()); + else + mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb ); + + if( !mbNative ) + mpBuffer = new sal_uInt8[ AlignedWidth4Bytes( mbGreys ? mpReadAccess->Width() * 8L : mpReadAccess->Width() * 24L ) ]; + + SAL_INFO("vcl", "\nJPEG Export - DPI X: " << rGraphic.GetPPI().getX() << "\nJPEG Export - DPI Y: " << rGraphic.GetPPI().getY()); + + bRet = WriteJPEG( this, &mrStream, mpReadAccess->Width(), + mpReadAccess->Height(), rGraphic.GetPPI(), mbGreys, + mnQuality, maChromaSubsampling, mxStatusIndicator ); + + delete[] mpBuffer; + mpBuffer = nullptr; + + mpReadAccess.reset(); + } + if ( mxStatusIndicator.is() ) + mxStatusIndicator->end(); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/JpegWriter.hxx b/vcl/source/filter/jpeg/JpegWriter.hxx new file mode 100644 index 000000000..76666cfa0 --- /dev/null +++ b/vcl/source/filter/jpeg/JpegWriter.hxx @@ -0,0 +1,57 @@ +/* -*- 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_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX +#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX + +#include <vcl/bitmap.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graph.hxx> + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> + +class JPEGWriter final +{ + SvStream& mrStream; + Bitmap::ScopedReadAccess mpReadAccess; + sal_uInt8* mpBuffer; + bool mbNative; + bool mbGreys; + sal_Int32 mnQuality; + sal_Int32 maChromaSubsampling; + + bool* mpExpWasGrey; + + css::uno::Reference< css::task::XStatusIndicator > mxStatusIndicator; + +public: + JPEGWriter( SvStream& rStream, + const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, + bool* pExportWasGrey ); + + void* GetScanline( tools::Long nY ); + bool Write( const Graphic& rGraphic ); + +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/jinclude.h b/vcl/source/filter/jpeg/jinclude.h new file mode 100644 index 000000000..b863b11c4 --- /dev/null +++ b/vcl/source/filter/jpeg/jinclude.h @@ -0,0 +1,79 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + +/* Include auto-config file to find out which system include files we need. */ + +#include <jconfig.h> /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include <stddef.h>. + * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to + * pull in <sys/types.h> as well. + * Note that the core JPEG library does not require <stdio.h>; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without <stdio.h>. + */ + +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef NEED_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <stdio.h> + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in <string.h>. + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in <memory.h>. + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include <strings.h> +#define MEMZERO(target, size) bzero((void*)(target), (size_t)(size)) +#define MEMCOPY(dest, src, size) bcopy((const void*)(src), (void*)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include <string.h> +#define MEMZERO(target, size) memset((void*)(target), 0, (size_t)(size)) +#define MEMCOPY(dest, src, size) memcpy((void*)(dest), (const void*)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t)sizeof(object)) diff --git a/vcl/source/filter/jpeg/jpeg.cxx b/vcl/source/filter/jpeg/jpeg.cxx new file mode 100644 index 000000000..e7158b858 --- /dev/null +++ b/vcl/source/filter/jpeg/jpeg.cxx @@ -0,0 +1,62 @@ +/* -*- 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 "JpegReader.hxx" +#include "JpegWriter.hxx" +#include "jpeg.hxx" + +#include <vcl/graphicfilter.hxx> + +VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess ) +{ + bool bReturn = true; + + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + JPEGReader* pJPEGReader = dynamic_cast<JPEGReader*>( pContext.get() ); + if (!pJPEGReader) + { + pContext = std::make_shared<JPEGReader>( rInputStream, nImportFlags ); + pJPEGReader = static_cast<JPEGReader*>( pContext.get() ); + } + + ReadState eReadState = pJPEGReader->Read( rGraphic, nImportFlags, ppAccess ); + + if( eReadState == JPEGREAD_ERROR ) + { + bReturn = false; + } + else if( eReadState == JPEGREAD_NEED_MORE ) + { + rGraphic.SetReaderContext( pContext ); + } + + return bReturn; +} + +bool ExportJPEG(SvStream& rOutputStream, const Graphic& rGraphic, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData, + bool* pExportWasGrey) +{ + JPEGWriter aJPEGWriter( rOutputStream, pFilterData, pExportWasGrey ); + bool bReturn = aJPEGWriter.Write( rGraphic ); + return bReturn; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/jpeg.h b/vcl/source/filter/jpeg/jpeg.h new file mode 100644 index 000000000..a7ddcffa6 --- /dev/null +++ b/vcl/source/filter/jpeg/jpeg.h @@ -0,0 +1,67 @@ +/* -*- 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_VCL_SOURCE_FILTER_JPEG_JPEG_H +#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <bitmap/BitmapWriteAccess.hxx> + +#include <jpeglib.h> + +namespace com::sun::star::task { + class XStatusIndicator; +} +class JPEGReader; +class JPEGWriter; +class Size; +class SvStream; +enum class GraphicFilterImportFlags; + +void jpeg_svstream_src (j_decompress_ptr cinfo, void* infile); + +void jpeg_svstream_dest (j_compress_ptr cinfo, void* outfile); + +bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream, + tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & aPPI, bool bGreyScale, + tools::Long nQualityPercent, tools::Long aChromaSubsampling, + css::uno::Reference<css::task::XStatusIndicator> const & status); + +void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines, + GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* ppAccess ); + +void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle); + +/* Expanded data source object for stdio input */ + +struct SourceManagerStruct { + jpeg_source_mgr pub; /* public fields */ + SvStream* stream; /* source stream */ + JOCTET* buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ + int no_data_available_failures; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/jpeg.hxx b/vcl/source/filter/jpeg/jpeg.hxx new file mode 100644 index 000000000..96c9280c2 --- /dev/null +++ b/vcl/source/filter/jpeg/jpeg.hxx @@ -0,0 +1,39 @@ +/* -*- 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_VCL_SOURCE_FILTER_JPEG_JPEG_HXX +#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX + +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> + +#include <com/sun/star/uno/Sequence.h> + +VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess ); + +bool ExportJPEG(SvStream& rOutputStream, + const Graphic& rGraphic, + const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, + bool* pExportWasGrey); + +#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx new file mode 100644 index 000000000..8807927a8 --- /dev/null +++ b/vcl/source/filter/jpeg/jpegc.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <sal/log.hxx> +#include <o3tl/float_int_conversion.hxx> +#include <o3tl/safeint.hxx> + +#include <stdio.h> +#include <setjmp.h> +#include <jpeglib.h> + +#include <com/sun/star/task/XStatusIndicator.hpp> + +extern "C" { +#include "transupp.h" +} + +#include "jpeg.h" +#include "JpegReader.hxx" +#include "JpegWriter.hxx" +#include <memory> +#include <unotools/configmgr.hxx> +#include <vcl/graphicfilter.hxx> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */ +#endif + +namespace { + +struct ErrorManagerStruct +{ + jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +extern "C" { + +static void errorExit (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer); + ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err); + longjmp(error->setjmp_buffer, 1); +} + +static void outputMessage (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer); +} + +} + +static int GetWarningLimit() +{ + return utl::ConfigManager::IsFuzzing() ? 5 : 1000; +} + +extern "C" { + +static void emitMessage (j_common_ptr cinfo, int msg_level) +{ + if (msg_level < 0) + { + // ofz#3002 try to retain some degree of recoverability up to some + // reasonable limit (initially using ImageMagick's current limit of + // 1000), then bail. + // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf + if (++cinfo->err->num_warnings > GetWarningLimit()) + cinfo->err->error_exit(cinfo); + else + cinfo->err->output_message(cinfo); + } + else if (cinfo->err->trace_level >= msg_level) + cinfo->err->output_message(cinfo); +} + +} + +namespace { + +class JpegDecompressOwner +{ +public: + void set(jpeg_decompress_struct *cinfo) + { + m_cinfo = cinfo; + } + ~JpegDecompressOwner() + { + if (m_cinfo != nullptr) + { + jpeg_destroy_decompress(m_cinfo); + } + } +private: + jpeg_decompress_struct *m_cinfo = nullptr; +}; + +class JpegCompressOwner +{ +public: + void set(jpeg_compress_struct *cinfo) + { + m_cinfo = cinfo; + } + ~JpegCompressOwner() + { + if (m_cinfo != nullptr) + { + jpeg_destroy_compress(m_cinfo); + } + } +private: + jpeg_compress_struct *m_cinfo = nullptr; +}; + +struct JpegStuff +{ + jpeg_decompress_struct cinfo; + ErrorManagerStruct jerr; + JpegDecompressOwner aOwner; + std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess; + std::vector<sal_uInt8> pScanLineBuffer; + std::vector<sal_uInt8> pCYMKBuffer; +}; + +} + +static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines, + GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* ppAccess) +{ + if (setjmp(rContext.jerr.setjmp_buffer)) + { + return; + } + + rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub); + rContext.jerr.pub.error_exit = errorExit; + rContext.jerr.pub.output_message = outputMessage; + rContext.jerr.pub.emit_message = emitMessage; + + jpeg_create_decompress(&rContext.cinfo); + rContext.aOwner.set(&rContext.cinfo); + jpeg_svstream_src(&rContext.cinfo, pInputStream); + SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src); + jpeg_read_header(&rContext.cinfo, TRUE); + + rContext.cinfo.scale_num = 1; + rContext.cinfo.scale_denom = 1; + rContext.cinfo.output_gamma = 1.0; + rContext.cinfo.raw_data_out = FALSE; + rContext.cinfo.quantize_colors = FALSE; + + jpeg_calc_output_dimensions(&rContext.cinfo); + + tools::Long nWidth = rContext.cinfo.output_width; + tools::Long nHeight = rContext.cinfo.output_height; + + tools::Long nResult = 0; + if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000)) + return; + + bool bGray = (rContext.cinfo.output_components == 1); + + JPEGCreateBitmapParam aCreateBitmapParam; + + aCreateBitmapParam.nWidth = nWidth; + aCreateBitmapParam.nHeight = nHeight; + + aCreateBitmapParam.density_unit = rContext.cinfo.density_unit; + aCreateBitmapParam.X_density = rContext.cinfo.X_density; + aCreateBitmapParam.Y_density = rContext.cinfo.Y_density; + aCreateBitmapParam.bGray = bGray; + + const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap); + const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap); + bool bBitmapCreated = bUseExistingBitmap; + if (!bBitmapCreated) + bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam); + + if (bBitmapCreated && !bOnlyCreateBitmap) + { + if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap) + // ppAccess must be set if this flag is used. + assert(ppAccess); + else + rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap())); + + BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess; + + if (pAccess) + { + int nPixelSize = 3; + J_COLOR_SPACE best_out_color_space = JCS_RGB; + ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb; + ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat(); + + if (bGray) + { + best_out_color_space = JCS_GRAYSCALE; + eScanlineFormat = ScanlineFormat::N8BitPal; + nPixelSize = 1; + } +#if defined(JCS_EXTENSIONS) + else if (eFinalFormat == ScanlineFormat::N24BitTcBgr) + { + best_out_color_space = JCS_EXT_BGR; + eScanlineFormat = eFinalFormat; + nPixelSize = 3; + } + else if (eFinalFormat == ScanlineFormat::N32BitTcBgra) + { + best_out_color_space = JCS_EXT_BGRA; + eScanlineFormat = eFinalFormat; + nPixelSize = 4; + } + else if (eFinalFormat == ScanlineFormat::N32BitTcRgba) + { + best_out_color_space = JCS_EXT_RGBA; + eScanlineFormat = eFinalFormat; + nPixelSize = 4; + } + else if (eFinalFormat == ScanlineFormat::N32BitTcArgb) + { + best_out_color_space = JCS_EXT_ARGB; + eScanlineFormat = eFinalFormat; + nPixelSize = 4; + } +#endif + if (rContext.cinfo.jpeg_color_space == JCS_YCCK) + rContext.cinfo.out_color_space = JCS_CMYK; + + if (rContext.cinfo.out_color_space != JCS_CMYK) + rContext.cinfo.out_color_space = best_out_color_space; + + jpeg_start_decompress(&rContext.cinfo); + + JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit; + + rContext.pScanLineBuffer.resize(nWidth * nPixelSize); + + if (rContext.cinfo.out_color_space == JCS_CMYK) + { + rContext.pCYMKBuffer.resize(nWidth * 4); + } + + // tdf#138950 allow up to one short read (no_data_available_failures <= 1) to not trigger cancelling import + for (*pLines = 0; *pLines < nHeight && source->no_data_available_failures <= 1; (*pLines)++) + { + size_t yIndex = *pLines; + + sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data(); + jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1); + + if (rContext.cinfo.out_color_space == JCS_CMYK) + { + // convert CMYK to RGB + Scanline pScanline = pAccess->GetScanline(yIndex); + for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x) + { + int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0]; + int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1]; + int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2]; + int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3]; + + sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)]; + sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)]; + sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)]; + + pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue)); + } + } + else + { + pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size()); + } + + /* PENDING ??? */ + if (rContext.cinfo.err->msg_code == 113) + break; + } + + rContext.pScanLineBuffer.clear(); + rContext.pCYMKBuffer.clear(); + } + rContext.pScopedAccess.reset(); + } + + if (bBitmapCreated && !bOnlyCreateBitmap) + { + jpeg_finish_decompress(&rContext.cinfo); + } + else + { + jpeg_abort_decompress(&rContext.cinfo); + } +} + +void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines, + GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* ppAccess ) +{ + JpegStuff aContext; + ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess); +} + +bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream, + tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys, + tools::Long nQualityPercent, tools::Long aChromaSubsampling, + css::uno::Reference<css::task::XStatusIndicator> const & status ) +{ + jpeg_compress_struct cinfo; + ErrorManagerStruct jerr; + void* pScanline; + tools::Long nY; + + JpegCompressOwner aOwner; + + if ( setjmp( jerr.setjmp_buffer ) ) + { + return false; + } + + cinfo.err = jpeg_std_error( &jerr.pub ); + jerr.pub.error_exit = errorExit; + jerr.pub.output_message = outputMessage; + + jpeg_create_compress( &cinfo ); + aOwner.set(&cinfo); + jpeg_svstream_dest( &cinfo, pOutputStream ); + + cinfo.image_width = static_cast<JDIMENSION>(nWidth); + cinfo.image_height = static_cast<JDIMENSION>(nHeight); + if ( bGreys ) + { + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + } + else + { + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } + + jpeg_set_defaults( &cinfo ); + jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE ); + + if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535)) + { + cinfo.density_unit = 1; + cinfo.X_density = rPPI.getX(); + cinfo.Y_density = rPPI.getY(); + } + else + { + SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI); + } + + if ( ( nWidth > 128 ) || ( nHeight > 128 ) ) + jpeg_simple_progression( &cinfo ); + + if (aChromaSubsampling == 1) // YUV 4:4:4 + { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } + else if (aChromaSubsampling == 2) // YUV 4:2:2 + { + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + } + else if (aChromaSubsampling == 3) // YUV 4:2:0 + { + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + } + + jpeg_start_compress( &cinfo, TRUE ); + + for( nY = 0; nY < nHeight; nY++ ) + { + pScanline = pJPEGWriter->GetScanline( nY ); + + if( pScanline ) + { + jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 ); + } + + if( status.is() ) + { + status->setValue( nY * 100L / nHeight ); + } + } + + jpeg_finish_compress(&cinfo); + + return true; +} + +void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle) +{ + jpeg_transform_info aTransformOption; + JCOPY_OPTION aCopyOption = JCOPYOPT_ALL; + + jpeg_decompress_struct aSourceInfo; + jpeg_compress_struct aDestinationInfo; + ErrorManagerStruct aSourceError; + ErrorManagerStruct aDestinationError; + + jvirt_barray_ptr* aSourceCoefArrays = nullptr; + jvirt_barray_ptr* aDestinationCoefArrays = nullptr; + + aTransformOption.force_grayscale = FALSE; + aTransformOption.trim = FALSE; + aTransformOption.perfect = FALSE; + aTransformOption.crop = FALSE; + + // Angle to transform option + // 90 Clockwise = 270 Counterclockwise + switch (nAngle.get()) + { + case 2700: + aTransformOption.transform = JXFORM_ROT_90; + break; + case 1800: + aTransformOption.transform = JXFORM_ROT_180; + break; + case 900: + aTransformOption.transform = JXFORM_ROT_270; + break; + default: + aTransformOption.transform = JXFORM_NONE; + } + + // Decompression + aSourceInfo.err = jpeg_std_error(&aSourceError.pub); + aSourceInfo.err->error_exit = errorExit; + aSourceInfo.err->output_message = outputMessage; + + // Compression + aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub); + aDestinationInfo.err->error_exit = errorExit; + aDestinationInfo.err->output_message = outputMessage; + + aDestinationInfo.optimize_coding = TRUE; + + JpegDecompressOwner aDecompressOwner; + JpegCompressOwner aCompressOwner; + + if (setjmp(aSourceError.setjmp_buffer)) + { + jpeg_destroy_decompress(&aSourceInfo); + jpeg_destroy_compress(&aDestinationInfo); + return; + } + if (setjmp(aDestinationError.setjmp_buffer)) + { + jpeg_destroy_decompress(&aSourceInfo); + jpeg_destroy_compress(&aDestinationInfo); + return; + } + + jpeg_create_decompress(&aSourceInfo); + aDecompressOwner.set(&aSourceInfo); + jpeg_create_compress(&aDestinationInfo); + aCompressOwner.set(&aDestinationInfo); + + jpeg_svstream_src (&aSourceInfo, pInputStream); + + jcopy_markers_setup(&aSourceInfo, aCopyOption); + jpeg_read_header(&aSourceInfo, TRUE); + jtransform_request_workspace(&aSourceInfo, &aTransformOption); + + aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo); + jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo); + + aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption); + jpeg_svstream_dest (&aDestinationInfo, pOutputStream); + + // Compute optimal Huffman coding tables instead of precomputed tables + aDestinationInfo.optimize_coding = TRUE; + jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays); + jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption); + jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption); + + jpeg_finish_compress(&aDestinationInfo); + + jpeg_finish_decompress(&aSourceInfo); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/jpeg/jpegcomp.h b/vcl/source/filter/jpeg/jpegcomp.h new file mode 100644 index 000000000..9b3e36775 --- /dev/null +++ b/vcl/source/filter/jpeg/jpegcomp.h @@ -0,0 +1,18 @@ +/* + * jpegcomp.h + * + * Copyright (C) 2010, D. R. Commander + * For conditions of distribution and use, see the accompanying README file. + * + * JPEG compatibility macros + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + +#if JPEG_LIB_VERSION >= 70 +#define min_DCT_h_scaled_size_ min_DCT_h_scaled_size +#define min_DCT_v_scaled_size_ min_DCT_v_scaled_size +#else +#define min_DCT_h_scaled_size_ min_DCT_scaled_size +#define min_DCT_v_scaled_size_ min_DCT_scaled_size +#endif diff --git a/vcl/source/filter/jpeg/transupp.c b/vcl/source/filter/jpeg/transupp.c new file mode 100644 index 000000000..d26cb9510 --- /dev/null +++ b/vcl/source/filter/jpeg/transupp.c @@ -0,0 +1,1570 @@ +/* + * transupp.c + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * Modifications: + * Copyright (C) 2010, D. R. Commander. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +#include <sal/config.h> + +#include "jinclude.h" +#include <jerror.h> +#include <jpeglib.h> +#include "transupp.h" /* My own external interface */ +#include "jpegcomp.h" + +/* Definition of jdiv_round_up is copied here from jutils.c in jpeg-8c.tar.gz, + just as the rest of this file appears to be copied here from transupp.c in + jpeg-8c.tar.gz: */ +static long +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1) / b; +} + +#if JPEG_LIB_VERSION >= 70 +#define dstinfo_min_DCT_h_scaled_size dstinfo->min_DCT_h_scaled_size +#define dstinfo_min_DCT_v_scaled_size dstinfo->min_DCT_v_scaled_size +#else +#define dstinfo_min_DCT_h_scaled_size DCTSIZE +#define dstinfo_min_DCT_v_scaled_size DCTSIZE +#endif + + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature, + * and to Ben Jackson for introducing the cropping feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * If cropping or trimming is involved, the destination arrays may be smaller + * than the source arrays. Note it is not possible to do horizontal flip + * in-place when a nonzero Y crop offset is specified, since we'd have to move + * data from one block row to another but the virtual array manager doesn't + * guarantee we can touch more than one row at a time. So in that case, + * we have to use a separate destination array. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. When "crop" is in effect, the destination's dimensions will be the + * cropped values but the source's will be uncropped. Each transform + * routine is responsible for picking up source data starting at the + * correct X and Y offset for the crop region. (The X and Y offsets + * passed to the transform routines are measured in iMCU blocks of the + * destination.) + * 6. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + */ + +static void lcl_jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + JCOEFPTR inptr, outptr; + long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + +LOCAL(void) +do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. */ +{ + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + /* We simply have to copy the right amount of data (the destination's + * image size) starting at the given X and Y offsets in the source. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } +} + + +LOCAL(void) +do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required. + * NB: this only works when y_crop_offset is zero. + */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Do the mirroring */ + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + if (x_crop_blocks > 0) { + /* Now left-justify the portion of the data to be kept. + * We can't use a single lcl_jcopy_block_row() call because that routine + * depends on memcpy(), whose behavior is unspecified for overlapping + * source and destination areas. Sigh. + */ + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + lcl_jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks, + buffer[offset_y] + blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Horizontal flip in general cropping case */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Here we must output into a separate array because we can't touch + * different rows of a single virtual array simultaneously. Otherwise, + * this is essentially the same as the routine above. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Do the mirrorable blocks */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + } + } else { + /* Copy last partial block(s) verbatim */ + lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + src_row_ptr += x_crop_blocks; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + /* Edge blocks are transposed but not mirrored. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored both ways. */ + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } else { + /* Any remaining right-edge blocks are only mirrored vertically. */ + src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored. */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } else { + /* Any remaining right-edge blocks are only copied. */ + lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width) +{ + JDIMENSION MCU_cols; + + MCU_cols = info->output_width / info->iMCU_sample_width; + if (MCU_cols > 0 && info->x_crop_offset + MCU_cols == + full_width / info->iMCU_sample_width) + info->output_width = MCU_cols * info->iMCU_sample_width; +} + +LOCAL(void) +trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height) +{ + JDIMENSION MCU_rows; + + MCU_rows = info->output_height / info->iMCU_sample_height; + if (MCU_rows > 0 && info->y_crop_offset + MCU_rows == + full_height / info->iMCU_sample_height) + info->output_height = MCU_rows * info->iMCU_sample_height; +} + + +/* Request any required workspace. + * + * This routine figures out the size that the output image will be + * (which implies that all the transform parameters must be set before + * it is called). + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + * + * This function returns FALSE right away if -perfect is given + * and transformation is not perfect. Otherwise returns TRUE. + */ + +GLOBAL(boolean) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays; + boolean need_workspace, transpose_it; + jpeg_component_info *compptr; + JDIMENSION xoffset, yoffset; + JDIMENSION width_in_iMCUs, height_in_iMCUs; + JDIMENSION width_in_blocks, height_in_blocks; + int ci, h_samp_factor, v_samp_factor; + + /* Determine number of components in output image */ + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) + /* We'll only process the first component */ + info->num_components = 1; + else + /* Process all the components */ + info->num_components = srcinfo->num_components; + + /* Compute output image dimensions and related values. */ +#if JPEG_LIB_VERSION >= 80 + jpeg_core_output_dimensions(srcinfo); +#else + srcinfo->output_width = srcinfo->image_width; + srcinfo->output_height = srcinfo->image_height; +#endif + + /* Return right away if -perfect is given and transformation is not perfect. + */ + if (info->perfect) { + if (info->num_components == 1) { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->min_DCT_h_scaled_size_, + srcinfo->min_DCT_v_scaled_size_, + info->transform)) + return FALSE; + } else { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_, + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_, + info->transform)) + return FALSE; + } + } + + /* If there is only one output component, force the iMCU size to be 1; + * else use the source iMCU size. (This allows us to do the right thing + * when reducing color to grayscale, and also provides a handy way of + * cleaning up "funny" grayscale images whose sampling factors are not 1x1.) + */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + info->output_width = srcinfo->output_height; + info->output_height = srcinfo->output_width; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size_; + info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size_; + } else { + info->iMCU_sample_width = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_; + info->iMCU_sample_height = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_; + } + break; + default: + info->output_width = srcinfo->output_width; + info->output_height = srcinfo->output_height; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size_; + info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size_; + } else { + info->iMCU_sample_width = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_; + info->iMCU_sample_height = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_; + } + break; + } + + /* If cropping has been requested, compute the crop area's position and + * dimensions, ensuring that its upper left corner falls at an iMCU boundary. + */ + if (info->crop) { + /* Insert default values for unset crop parameters */ + if (info->crop_xoffset_set == JCROP_UNSET) + info->crop_xoffset = 0; /* default to +0 */ + if (info->crop_yoffset_set == JCROP_UNSET) + info->crop_yoffset = 0; /* default to +0 */ + if (info->crop_xoffset >= info->output_width || + info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL); + if (info->crop_width_set == JCROP_UNSET) + info->crop_width = info->output_width - info->crop_xoffset; + if (info->crop_height_set == JCROP_UNSET) + info->crop_height = info->output_height - info->crop_yoffset; + /* Ensure parameters are valid */ + if (info->crop_width <= 0 || info->crop_width > info->output_width || + info->crop_height <= 0 || info->crop_height > info->output_height || + info->crop_xoffset > info->output_width - info->crop_width || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL); + /* Convert negative crop offsets into regular offsets */ + if (info->crop_xoffset_set == JCROP_NEG) + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + else + xoffset = info->crop_xoffset; + if (info->crop_yoffset_set == JCROP_NEG) + yoffset = info->output_height - info->crop_height - info->crop_yoffset; + else + yoffset = info->crop_yoffset; + /* Now adjust so that upper left corner falls at an iMCU boundary */ + if (info->crop_width_set == JCROP_FORCE) + info->output_width = info->crop_width; + else + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + if (info->crop_height_set == JCROP_FORCE) + info->output_height = info->crop_height; + else + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + /* Save x/y offsets measured in iMCUs */ + info->x_crop_offset = xoffset / info->iMCU_sample_width; + info->y_crop_offset = yoffset / info->iMCU_sample_height; + } else { + info->x_crop_offset = 0; + info->y_crop_offset = 0; + } + + /* Figure out whether we need workspace arrays, + * and if so whether they are transposed relative to the source. + */ + need_workspace = FALSE; + transpose_it = FALSE; + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + need_workspace = TRUE; + /* No workspace needed if neither cropping nor transforming */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(info, srcinfo->output_width); + if (info->y_crop_offset != 0 || info->slow_hflip) + need_workspace = TRUE; + /* do_flip_h_no_crop doesn't need a workspace array */ + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_height); + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_TRANSPOSE: + /* transpose does NOT have to trim anything */ + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_TRANSVERSE: + if (info->trim) { + trim_right_edge(info, srcinfo->output_height); + trim_bottom_edge(info, srcinfo->output_width); + } + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_90: + if (info->trim) + trim_right_edge(info, srcinfo->output_height); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(info, srcinfo->output_width); + trim_bottom_edge(info, srcinfo->output_height); + } + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_ROT_270: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_width); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + } + + /* Allocate workspace if needed. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + if (need_workspace) { + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + width_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_width, + (long) info->iMCU_sample_width); + height_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_height, + (long) info->iMCU_sample_height); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + if (info->num_components == 1) { + /* we're going to force samp factors to 1x1 in this case */ + h_samp_factor = v_samp_factor = 1; + } else if (transpose_it) { + h_samp_factor = compptr->v_samp_factor; + v_samp_factor = compptr->h_samp_factor; + } else { + h_samp_factor = compptr->h_samp_factor; + v_samp_factor = compptr->v_samp_factor; + } + width_in_blocks = width_in_iMCUs * h_samp_factor; + height_in_blocks = height_in_iMCUs * v_samp_factor; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor); + } + info->workspace_coef_arrays = coef_arrays; + } else + info->workspace_coef_arrays = NULL; + + return TRUE; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION jtemp; + UINT16 qtemp; + + /* Transpose image dimensions */ + jtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = jtemp; +#if JPEG_LIB_VERSION >= 70 + itemp = dstinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size; + dstinfo->min_DCT_v_scaled_size = itemp; +#endif + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Adjust Exif image parameters. + * + * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible. + */ + +#if JPEG_LIB_VERSION >= 70 +LOCAL(void) +adjust_exif_parameters (JOCTET FAR * data, unsigned int length, + JDIMENSION new_width, JDIMENSION new_height) +{ + boolean is_motorola; /* Flag for byte order */ + unsigned int number_of_tags, tagnum; + unsigned int firstoffset, offset; + JDIMENSION new_value; + + if (length < 12) return; /* Length of an IFD entry */ + + /* Discover byte order */ + if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49) + is_motorola = FALSE; + else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D) + is_motorola = TRUE; + else + return; + + /* Check Tag Mark */ + if (is_motorola) { + if (GETJOCTET(data[2]) != 0) return; + if (GETJOCTET(data[3]) != 0x2A) return; + } else { + if (GETJOCTET(data[3]) != 0) return; + if (GETJOCTET(data[2]) != 0x2A) return; + } + + /* Get first IFD offset (offset to IFD0) */ + if (is_motorola) { + if (GETJOCTET(data[4]) != 0) return; + if (GETJOCTET(data[5]) != 0) return; + firstoffset = GETJOCTET(data[6]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[7]); + } else { + if (GETJOCTET(data[7]) != 0) return; + if (GETJOCTET(data[6]) != 0) return; + firstoffset = GETJOCTET(data[5]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[4]); + } + if (firstoffset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[firstoffset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset+1]); + } else { + number_of_tags = GETJOCTET(data[firstoffset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset]); + } + if (number_of_tags == 0) return; + firstoffset += 2; + + /* Search for ExifSubIFD offset Tag in IFD0 */ + for (;;) { + if (firstoffset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[firstoffset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset+1]); + } else { + tagnum = GETJOCTET(data[firstoffset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset]); + } + if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */ + if (--number_of_tags == 0) return; + firstoffset += 12; + } + + /* Get the ExifSubIFD offset */ + if (is_motorola) { + if (GETJOCTET(data[firstoffset+8]) != 0) return; + if (GETJOCTET(data[firstoffset+9]) != 0) return; + offset = GETJOCTET(data[firstoffset+10]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+11]); + } else { + if (GETJOCTET(data[firstoffset+11]) != 0) return; + if (GETJOCTET(data[firstoffset+10]) != 0) return; + offset = GETJOCTET(data[firstoffset+9]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+8]); + } + if (offset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this SubIFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[offset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset+1]); + } else { + number_of_tags = GETJOCTET(data[offset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset]); + } + if (number_of_tags < 2) return; + offset += 2; + + /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */ + do { + if (offset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[offset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset+1]); + } else { + tagnum = GETJOCTET(data[offset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset]); + } + if (tagnum == 0xA002 || tagnum == 0xA003) { + if (tagnum == 0xA002) + new_value = new_width; /* ExifImageWidth Tag */ + else + new_value = new_height; /* ExifImageHeight Tag */ + if (is_motorola) { + data[offset+2] = 0; /* Format = unsigned long (4 octets) */ + data[offset+3] = 4; + data[offset+4] = 0; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 1; + data[offset+8] = 0; + data[offset+9] = 0; + data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+11] = (JOCTET)(new_value & 0xFF); + } else { + data[offset+2] = 4; /* Format = unsigned long (4 octets) */ + data[offset+3] = 0; + data[offset+4] = 1; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 0; + data[offset+8] = (JOCTET)(new_value & 0xFF); + data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+10] = 0; + data[offset+11] = 0; + } + } + offset += 12; + } while (--number_of_tags); +} +#endif + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* First, ensure we have YCbCr or grayscale data, and that the source's + * Y channel is full resolution. (No reasonable person would make Y + * be less than full resolution, so actually copying with that case + * isn't worth extra code space. But we check it to avoid crashing.) + */ + if (((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) && + srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor && + srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, it sets the target h_samp_factor & + * v_samp_factor to 1, which typically won't match the source. + * We have to preserve the source's quantization table number, however. + */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } else if (info->num_components == 1) { + /* For a single-component source, we force the destination sampling factors + * to 1x1, with or without force_grayscale. This is useful because some + * decoders choke on grayscale images with other sampling factors. + */ + dstinfo->comp_info[0].h_samp_factor = 1; + dstinfo->comp_info[0].v_samp_factor = 1; + } + + /* Correct the destination's image dimensions as necessary + * for rotate/flip, resize, and crop operations. + */ +#if JPEG_LIB_VERSION >= 70 + dstinfo->jpeg_width = info->output_width; + dstinfo->jpeg_height = info->output_height; +#endif + + /* Transpose destination image parameters */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: +#if JPEG_LIB_VERSION < 70 + dstinfo->image_width = info->output_height; + dstinfo->image_height = info->output_width; +#endif + transpose_critical_parameters(dstinfo); + break; + default: +#if JPEG_LIB_VERSION < 70 + dstinfo->image_width = info->output_width; + dstinfo->image_height = info->output_height; +#endif + break; + } + + /* Adjust Exif properties */ + if (srcinfo->marker_list != NULL && + srcinfo->marker_list->marker == JPEG_APP0+1 && + srcinfo->marker_list->data_length >= 6 && + GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 && + GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 && + GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 && + GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 && + GETJOCTET(srcinfo->marker_list->data[4]) == 0 && + GETJOCTET(srcinfo->marker_list->data[5]) == 0) { + /* Suppress output of JFIF marker */ + dstinfo->write_JFIF_header = FALSE; +#if JPEG_LIB_VERSION >= 70 + /* Adjust Exif image parameters */ + if (dstinfo->jpeg_width != srcinfo->image_width || + dstinfo->jpeg_height != srcinfo->image_height) + /* Align data segment to start of TIFF structure for parsing */ + adjust_exif_parameters(srcinfo->marker_list->data + 6, + srcinfo->marker_list->data_length - 6, + dstinfo->jpeg_width, dstinfo->jpeg_height); +#endif + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transform (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + /* Note: conditions tested here should match those in switch statement + * in jtransform_request_workspace() + */ + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_FLIP_H: + if (info->y_crop_offset != 0 || info->slow_hflip) + do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + } +} + +/* jtransform_perfect_transform + * + * Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + * + * Inputs: + * image_width, image_height: source image dimensions. + * MCU_width, MCU_height: pixel dimensions of MCU. + * transform: transformation identifier. + * Parameter sources from initialized jpeg_struct + * (after reading source header): + * image_width = cinfo.image_width + * image_height = cinfo.image_height + * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size + * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size + * Result: + * TRUE = perfect transformation possible + * FALSE = perfect transformation not possible + * (may use custom action then) + */ + +GLOBAL(boolean) +jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform) +{ + boolean result = TRUE; /* initialize TRUE */ + + switch (transform) { + case JXFORM_FLIP_H: + case JXFORM_ROT_270: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_90: + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + case JXFORM_TRANSVERSE: + case JXFORM_ROT_180: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + default: + break; + } + + return result; +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#else + (void) srcinfo; (void) option; +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + (void)option; + + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/vcl/source/filter/jpeg/transupp.h b/vcl/source/filter/jpeg/transupp.h new file mode 100644 index 000000000..a5d403335 --- /dev/null +++ b/vcl/source/filter/jpeg/transupp.h @@ -0,0 +1,212 @@ +/* + * transupp.h + * + * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a lossless-crop option, which discards data outside a given + * image region but losslessly preserves what is inside. Like the rotate and + * flip transforms, lossless crop is restricted by the JPEG format: the upper + * left corner of the selected region must fall on an iMCU boundary. If this + * does not hold for the given crop parameters, we silently move the upper left + * corner up and/or left to make it so, simultaneously increasing the region + * dimensions to keep the lower right crop corner unchanged. (Thus, the + * output image covers at least the requested region, but may cover more.) + * The adjustment of the region dimensions may be optionally disabled. + * + * We also provide a lossless-resize option, which is kind of a lossless-crop + * operation in the DCT coefficient block domain - it discards higher-order + * coefficients and losslessly preserves lower-order coefficients of a + * sub-block. + * + * Rotate/flip transform, resize, and crop can be requested together in a + * single invocation. The crop is applied last --- that is, the crop region + * is specified in terms of the destination image after transform/resize. + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_parse_crop_spec jTrParCrop +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transform jTrExec +#define jtransform_perfect_transform jTrPerfect +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Codes for crop parameters, which can individually be unspecified, + * positive or negative for xoffset or yoffset, + * positive or forced for width or height. + */ + +typedef enum { + JCROP_UNSET, + JCROP_POS, + JCROP_NEG, + JCROP_FORCE +} JCROP_CODE; + +/* + * Transform parameters struct. + * NB: application must not change any elements of this struct after + * calling jtransform_request_workspace. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean perfect; /* if TRUE, fail if partial MCUs are requested */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + boolean crop; /* if TRUE, crop source image */ + boolean slow_hflip; /* For best performance, the JXFORM_FLIP_H transform + normally modifies the source coefficients in place. + Setting this to TRUE will instead use a slower, + double-buffered algorithm, which leaves the source + coefficients intact (necessary if other transformed + images must be generated from the same set of + coefficients. */ + + /* Crop parameters: application need not set these unless crop is TRUE. + * These can be filled in by jtransform_parse_crop_spec(). + */ + JDIMENSION crop_width; /* Width of selected region */ + JCROP_CODE crop_width_set; /* (forced disables adjustment) */ + JDIMENSION crop_height; /* Height of selected region */ + JCROP_CODE crop_height_set; /* (forced disables adjustment) */ + JDIMENSION crop_xoffset; /* X offset of selected region */ + JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ + JDIMENSION crop_yoffset; /* Y offset of selected region */ + JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ + JDIMENSION output_width; /* cropped destination dimensions */ + JDIMENSION output_height; + JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ + JDIMENSION y_crop_offset; + int iMCU_sample_width; /* destination iMCU size */ + int iMCU_sample_height; +} jpeg_transform_info; + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(boolean) jtransform_request_workspace + (j_decompress_ptr srcinfo, jpeg_transform_info *info); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transform + (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info); +/* Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + */ +EXTERN(boolean) jtransform_perfect_transform + (JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform); + +/* jtransform_execute_transform used to be called + * jtransform_execute_transformation, but some compilers complain about + * routine names that long. This macro is here to avoid breaking any + * old source code that uses the original name... + */ +#define jtransform_execute_transformation jtransform_execute_transform + +#endif /* TRANSFORMS_SUPPORTED */ + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + (j_decompress_ptr srcinfo, JCOPY_OPTION option); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option); diff --git a/vcl/source/filter/png/PngImageReader.cxx b/vcl/source/filter/png/PngImageReader.cxx new file mode 100644 index 000000000..6a3053a72 --- /dev/null +++ b/vcl/source/filter/png/PngImageReader.cxx @@ -0,0 +1,532 @@ +/* -*- 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 <vcl/filter/PngImageReader.hxx> +#include <png.h> +#include <rtl/crc.h> +#include <tools/stream.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/alpha.hxx> +#include <vcl/BitmapTools.hxx> +#include <unotools/configmgr.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <svdata.hxx> +#include <salinst.hxx> + +#include "png.hxx" + +namespace +{ +void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead) +{ + png_voidp pIO = png_get_io_ptr(pPng); + + if (pIO == nullptr) + return; + + SvStream* pStream = static_cast<SvStream*>(pIO); + + sal_Size nBytesRead = pStream->ReadBytes(pOutBytes, nBytesToRead); + + if (nBytesRead != nBytesToRead) + { + if (!nBytesRead) + png_error(pPng, "Error reading"); + else + { + // Make sure to not reuse old data (could cause infinite loop). + memset(pOutBytes + nBytesRead, 0, nBytesToRead - nBytesRead); + png_warning(pPng, "Short read"); + } + } +} + +constexpr int PNG_SIGNATURE_SIZE = 8; + +bool isPng(SvStream& rStream) +{ + // Check signature bytes + sal_uInt8 aHeader[PNG_SIGNATURE_SIZE]; + if (rStream.ReadBytes(aHeader, PNG_SIGNATURE_SIZE) != PNG_SIGNATURE_SIZE) + return false; + return png_sig_cmp(aHeader, 0, PNG_SIGNATURE_SIZE) == 0; +} + +struct PngDestructor +{ + ~PngDestructor() { png_destroy_read_struct(&pPng, &pInfo, nullptr); } + png_structp pPng; + png_infop pInfo; +}; + +#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclobbered" +#endif +bool reader(SvStream& rStream, BitmapEx& rBitmapEx, + GraphicFilterImportFlags nImportFlags = GraphicFilterImportFlags::NONE, + BitmapScopedWriteAccess* pAccess = nullptr, + AlphaScopedWriteAccess* pAlphaAccess = nullptr) +{ + if (!isPng(rStream)) + return false; + + png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!pPng) + return false; + + png_infop pInfo = png_create_info_struct(pPng); + if (!pInfo) + { + png_destroy_read_struct(&pPng, nullptr, nullptr); + return false; + } + + PngDestructor pngDestructor = { pPng, pInfo }; + + // All variables holding resources need to be declared here in order to be + // properly cleaned up in case of an error, otherwise libpng's longjmp() + // jumps over the destructor calls. + Bitmap aBitmap; + AlphaMask aBitmapAlpha; + Size prefSize; + BitmapScopedWriteAccess pWriteAccessInstance; + AlphaScopedWriteAccess pWriteAccessAlphaInstance; + const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32(); + const bool bOnlyCreateBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap); + const bool bUseExistingBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap); + + if (setjmp(png_jmpbuf(pPng))) + { + if (!bUseExistingBitmap) + { + // Set the bitmap if it contains something, even on failure. This allows + // reading images that are only partially broken. + pWriteAccessInstance.reset(); + pWriteAccessAlphaInstance.reset(); + if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else if (!aBitmap.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap); + if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + } + return false; + } + + png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); + + png_set_read_fn(pPng, &rStream, lclReadStream); + + if (!bFuzzing) + png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD); + else + png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + + png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE); + + png_read_info(pPng, pInfo); + + png_uint_32 width = 0; + png_uint_32 height = 0; + int bitDepth = 0; + int colorType = -1; + int interlace = -1; + + png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, + &interlace, nullptr, nullptr); + + if (returnValue != 1) + return false; + + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pPng); + + if (colorType == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8(pPng); + + if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pPng); + + if (bitDepth == 16) + png_set_scale_16(pPng); + + if (bitDepth < 8) + png_set_packing(pPng); + + // Convert gray+alpha to RGBA, keep gray as gray. + if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA + || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS))) + { + png_set_gray_to_rgb(pPng); + } + + // Sets the filler byte - if RGB it converts to RGBA + // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER); + + int nNumberOfPasses = png_set_interlace_handling(pPng); + + png_read_update_info(pPng, pInfo); + returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr, + nullptr, nullptr); + + if (returnValue != 1) + return false; + + if (bitDepth != 8 + || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA + && colorType != PNG_COLOR_TYPE_GRAY)) + { + return false; + } + + png_uint_32 res_x = 0; + png_uint_32 res_y = 0; + int unit_type = 0; + if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0 + && unit_type == PNG_RESOLUTION_METER && res_x && res_y) + { + // convert into MapUnit::Map100thMM + prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x), + static_cast<sal_Int32>((100000.0 * height) / res_y)); + } + + if (!bUseExistingBitmap) + { + switch (colorType) + { + case PNG_COLOR_TYPE_RGB: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + break; + case PNG_COLOR_TYPE_RGBA: + if (bSupportsBitmap32) + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + else + { + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + aBitmapAlpha = AlphaMask(Size(width, height), nullptr); + } + break; + case PNG_COLOR_TYPE_GRAY: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP, + &Bitmap::GetGreyPalette(256)); + break; + default: + abort(); + } + + if (bOnlyCreateBitmap) + { + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + return true; + } + + pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap); + if (!pWriteAccessInstance) + return false; + if (!aBitmapAlpha.IsEmpty()) + { + pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha); + if (!pWriteAccessAlphaInstance) + return false; + } + } + BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance; + AlphaScopedWriteAccess& pWriteAccessAlpha + = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance; + + if (colorType == PNG_COLOR_TYPE_RGB) + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N24BitTcBgr) + png_set_bgr(pPng); + + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } + } + else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) + { + size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo); + + if (bSupportsBitmap32) + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcBgra) + png_set_bgr(pPng); + + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } +#if !ENABLE_WASM_STRIP_PREMULTIPLY + const vcl::bitmap::lookup_table& premultiply = vcl::bitmap::get_premultiply_table(); +#endif + if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcArgb) + { // alpha first and premultiply + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + const sal_uInt8 alpha = pScanline[i + 3]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + pScanline[i + 3] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]); + pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]); + pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i]); +#else + pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]]; + pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]]; + pScanline[i + 1] = premultiply[alpha][pScanline[i]]; +#endif + pScanline[i] = alpha; + } + } + } + else + { // keep alpha last, only premultiply + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + const sal_uInt8 alpha = pScanline[i + 3]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + pScanline[i] = vcl::bitmap::premultiply(alpha, pScanline[i]); + pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]); + pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]); +#else + pScanline[i] = premultiply[alpha][pScanline[i]]; + pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]]; + pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]]; +#endif + } + } + } + } + else + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N24BitTcBgr) + png_set_bgr(pPng); + + if (nNumberOfPasses == 1) + { + // optimise the common case, where we can use a buffer of only a single row + std::vector<png_byte> aRow(aRowSizeBytes, 0); + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y); + png_bytep pRow = aRow.data(); + png_read_row(pPng, pRow, nullptr); + size_t iAlpha = 0; + size_t iColor = 0; + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + pScanline[iColor++] = pRow[i + 0]; + pScanline[iColor++] = pRow[i + 1]; + pScanline[iColor++] = pRow[i + 2]; + pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3]; + } + } + } + else + { + std::vector<std::vector<png_byte>> aRows(height); + for (auto& rRow : aRows) + rRow.resize(aRowSizeBytes, 0); + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y); + png_bytep pRow = aRows[y].data(); + png_read_row(pPng, pRow, nullptr); + size_t iAlpha = 0; + size_t iColor = 0; + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + pScanline[iColor++] = pRow[i + 0]; + pScanline[iColor++] = pRow[i + 1]; + pScanline[iColor++] = pRow[i + 2]; + pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3]; + } + } + } + } + } + } + else if (colorType == PNG_COLOR_TYPE_GRAY) + { + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } + } + + png_read_end(pPng, pInfo); + + if (!bUseExistingBitmap) + { + pWriteAccess.reset(); + pWriteAccessAlpha.reset(); + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + } + + return true; +} + +std::unique_ptr<sal_uInt8[]> getMsGifChunk(SvStream& rStream, sal_Int32* chunkSize) +{ + if (chunkSize) + *chunkSize = 0; + if (!isPng(rStream)) + return nullptr; + // It's easier to read manually the contents and find the chunk than + // try to get it using libpng. + // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format + // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc + bool ignoreCrc = utl::ConfigManager::IsFuzzing(); + for (;;) + { + sal_uInt32 length(0), type(0), crc(0); + rStream.ReadUInt32(length); + rStream.ReadUInt32(type); + if (!rStream.good()) + return nullptr; + constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF + constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0" + if (type == PNGCHUNK_msOG && length > MSGifHeaderSize) + { + // calculate chunktype CRC (swap it back to original byte order) + sal_uInt32 typeForCrc = type; +#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) + typeForCrc = OSL_SWAPDWORD(typeForCrc); +#endif + sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4); + const sal_uInt64 pos = rStream.Tell(); + if (pos + length >= rStream.TellEnd()) + return nullptr; // broken PNG + + char msHeader[MSGifHeaderSize]; + if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize) + return nullptr; + computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize); + length -= MSGifHeaderSize; + + std::unique_ptr<sal_uInt8[]> chunk(new sal_uInt8[length]); + if (rStream.ReadBytes(chunk.get(), length) != length) + return nullptr; + computedCrc = rtl_crc32(computedCrc, chunk.get(), length); + rStream.ReadUInt32(crc); + if (!ignoreCrc && crc != computedCrc) + continue; // invalid chunk, ignore + if (chunkSize) + *chunkSize = length; + return chunk; + } + if (rStream.remainingSize() < length) + return nullptr; + rStream.SeekRel(length); + rStream.ReadUInt32(crc); + constexpr sal_uInt32 PNGCHUNK_IEND = 0x49454e44; + if (type == PNGCHUNK_IEND) + return nullptr; + } +} +#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__ +#pragma GCC diagnostic pop +#endif + +} // anonymous namespace + +namespace vcl +{ +PngImageReader::PngImageReader(SvStream& rStream) + : mrStream(rStream) +{ +} + +bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); } + +BitmapEx PngImageReader::read() +{ + BitmapEx bitmap; + read(bitmap); + return bitmap; +} + +std::unique_ptr<sal_uInt8[]> PngImageReader::getMicrosoftGifChunk(SvStream& rStream, + sal_Int32* chunkSize) +{ + sal_uInt64 originalPosition = rStream.Tell(); + SvStreamEndian originalEndian = rStream.GetEndian(); + rStream.SetEndian(SvStreamEndian::BIG); + std::unique_ptr<sal_uInt8[]> chunk = getMsGifChunk(rStream, chunkSize); + rStream.SetEndian(originalEndian); + rStream.Seek(originalPosition); + return chunk; +} + +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess) +{ + // Creating empty bitmaps should be practically a no-op, and thus thread-safe. + BitmapEx bitmap; + if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess)) + { + if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)) + rGraphic = bitmap; + return true; + } + return false; +} + +} // namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/png/png.hxx b/vcl/source/filter/png/png.hxx new file mode 100644 index 000000000..b3a1b7b17 --- /dev/null +++ b/vcl/source/filter/png/png.hxx @@ -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 . + */ + +#pragma once + +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> + +namespace vcl +{ +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/png/pngwrite.cxx b/vcl/source/filter/png/pngwrite.cxx new file mode 100644 index 000000000..865fe38eb --- /dev/null +++ b/vcl/source/filter/png/pngwrite.cxx @@ -0,0 +1,660 @@ +/* -*- 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 <vcl/pngwrite.hxx> +#include <vcl/bitmapex.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <limits> +#include <rtl/crc.h> +#include <tools/solar.h> +#include <tools/zcodec.hxx> +#include <tools/stream.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/alpha.hxx> +#include <osl/endian.h> +#include <memory> +#include <vcl/BitmapTools.hxx> + +#define PNG_DEF_COMPRESSION 6 + +#define PNGCHUNK_IHDR 0x49484452 +#define PNGCHUNK_PLTE 0x504c5445 +#define PNGCHUNK_IDAT 0x49444154 +#define PNGCHUNK_IEND 0x49454e44 +#define PNGCHUNK_pHYs 0x70485973 + +namespace vcl +{ +class PNGWriterImpl +{ +public: + PNGWriterImpl(const BitmapEx& BmpEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData); + + bool Write(SvStream& rOutStream); + + std::vector<vcl::PNGWriter::ChunkData>& GetChunks() { return maChunkSeq; } + +private: + std::vector<vcl::PNGWriter::ChunkData> maChunkSeq; + + sal_Int32 mnCompLevel; + sal_Int32 mnInterlaced; + sal_uInt32 mnMaxChunkSize; + bool mbStatus; + + Bitmap::ScopedReadAccess mpAccess; + BitmapReadAccess* mpMaskAccess; + ZCodec mpZCodec; + + std::unique_ptr<sal_uInt8[]> + mpDeflateInBuf; // as big as the size of a scanline + alphachannel + 1 + std::unique_ptr<sal_uInt8[]> mpPreviousScan; // as big as mpDeflateInBuf + std::unique_ptr<sal_uInt8[]> mpCurrentScan; + sal_uLong mnDeflateInSize; + + sal_uLong mnWidth; + sal_uLong mnHeight; + sal_uInt8 mnBitsPerPixel; + sal_uInt8 mnFilterType; // 0 or 4; + sal_uLong mnBBP; // bytes per pixel ( needed for filtering ) + bool mbTrueAlpha; + + void ImplWritepHYs(const BitmapEx& rBitmapEx); + void ImplWriteIDAT(); + sal_uLong ImplGetFilter(sal_uLong nY, sal_uLong nXStart = 0, sal_uLong nXAdd = 1); + void ImplClearFirstScanline(); + bool ImplWriteHeader(); + void ImplWritePalette(); + void ImplOpenChunk(sal_uLong nChunkType); + void ImplWriteChunk(sal_uInt8 nNumb); + void ImplWriteChunk(sal_uInt32 nNumb); + void ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize); +}; + +PNGWriterImpl::PNGWriterImpl(const BitmapEx& rBitmapEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData) + : mnCompLevel(PNG_DEF_COMPRESSION) + , mnInterlaced(0) + , mnMaxChunkSize(0) + , mbStatus(true) + , mpMaskAccess(nullptr) + , mnDeflateInSize(0) + , mnWidth(0) + , mnHeight(0) + , mnBitsPerPixel(0) + , mnFilterType(0) + , mnBBP(0) + , mbTrueAlpha(false) +{ + if (rBitmapEx.IsEmpty()) + return; + + BitmapEx aBitmapEx; + + if (rBitmapEx.GetBitmap().getPixelFormat() == vcl::PixelFormat::N32_BPP) + { + if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx)) + return; + } + else + { + aBitmapEx = rBitmapEx; + } + + Bitmap aBmp(aBitmapEx.GetBitmap()); + + mnMaxChunkSize = std::numeric_limits<sal_uInt32>::max(); + bool bTranslucent = true; + + if (pFilterData) + { + for (const auto& rPropVal : *pFilterData) + { + if (rPropVal.Name == "Compression") + rPropVal.Value >>= mnCompLevel; + else if (rPropVal.Name == "Interlaced") + rPropVal.Value >>= mnInterlaced; + else if (rPropVal.Name == "Translucent") + { + tools::Long nTmp = 0; + rPropVal.Value >>= nTmp; + if (!nTmp) + bTranslucent = false; + } + else if (rPropVal.Name == "MaxChunkSize") + { + sal_Int32 nVal = 0; + if (rPropVal.Value >>= nVal) + mnMaxChunkSize = static_cast<sal_uInt32>(nVal); + } + } + } + mnBitsPerPixel = sal_uInt8(vcl::pixelFormatBitCount(aBmp.getPixelFormat())); + + if (aBitmapEx.IsAlpha() && bTranslucent) + { + if (mnBitsPerPixel <= 8) + { + aBmp.Convert(BmpConversion::N24Bit); + mnBitsPerPixel = 24; + } + + mpAccess = Bitmap::ScopedReadAccess(aBmp); // true RGB with alphachannel + if (mpAccess) + { + mbTrueAlpha = true; + AlphaMask aMask(aBitmapEx.GetAlpha()); + mpMaskAccess = aMask.AcquireReadAccess(); + if (mpMaskAccess) + { + if (ImplWriteHeader()) + { + ImplWritepHYs(aBitmapEx); + ImplWriteIDAT(); + } + aMask.ReleaseAccess(mpMaskAccess); + mpMaskAccess = nullptr; + } + else + { + mbStatus = false; + } + mpAccess.reset(); + } + else + { + mbStatus = false; + } + } + else + { + mpAccess = Bitmap::ScopedReadAccess(aBmp); // palette + RGB without alphachannel + if (mpAccess) + { + if (ImplWriteHeader()) + { + ImplWritepHYs(aBitmapEx); + if (mpAccess->HasPalette()) + ImplWritePalette(); + + ImplWriteIDAT(); + } + mpAccess.reset(); + } + else + { + mbStatus = false; + } + } + + if (mbStatus) + { + ImplOpenChunk(PNGCHUNK_IEND); // create an IEND chunk + } +} + +bool PNGWriterImpl::Write(SvStream& rOStm) +{ + /* png signature is always an array of 8 bytes */ + SvStreamEndian nOldMode = rOStm.GetEndian(); + rOStm.SetEndian(SvStreamEndian::BIG); + rOStm.WriteUInt32(0x89504e47); + rOStm.WriteUInt32(0x0d0a1a0a); + + for (auto const& chunk : maChunkSeq) + { + sal_uInt32 nType = chunk.nType; +#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) + nType = OSL_SWAPDWORD(nType); +#endif + sal_uInt32 nCRC = rtl_crc32(0, &nType, 4); + sal_uInt32 nDataSize = chunk.aData.size(); + if (nDataSize) + nCRC = rtl_crc32(nCRC, chunk.aData.data(), nDataSize); + rOStm.WriteUInt32(nDataSize); + rOStm.WriteUInt32(chunk.nType); + if (nDataSize) + rOStm.WriteBytes(chunk.aData.data(), nDataSize); + rOStm.WriteUInt32(nCRC); + } + rOStm.SetEndian(nOldMode); + return mbStatus; +} + +bool PNGWriterImpl::ImplWriteHeader() +{ + ImplOpenChunk(PNGCHUNK_IHDR); + mnWidth = mpAccess->Width(); + ImplWriteChunk(sal_uInt32(mnWidth)); + mnHeight = mpAccess->Height(); + ImplWriteChunk(sal_uInt32(mnHeight)); + + if (mnWidth && mnHeight && mnBitsPerPixel && mbStatus) + { + sal_uInt8 nBitDepth = mnBitsPerPixel; + if (mnBitsPerPixel <= 8) + mnFilterType = 0; + else + mnFilterType = 4; + + sal_uInt8 nColorType = 2; // colortype: + + // bit 0 -> palette is used + if (mpAccess->HasPalette()) // bit 1 -> color is used + nColorType |= 1; // bit 2 -> alpha channel is used + else + nBitDepth /= 3; + + if (mpMaskAccess) + nColorType |= 4; + + ImplWriteChunk(nBitDepth); + ImplWriteChunk(nColorType); // colortype + ImplWriteChunk(static_cast<sal_uInt8>(0)); // compression type + ImplWriteChunk(static_cast<sal_uInt8>(0)); // filter type - is not supported in this version + ImplWriteChunk(static_cast<sal_uInt8>(mnInterlaced)); // interlace type + } + else + { + mbStatus = false; + } + return mbStatus; +} + +void PNGWriterImpl::ImplWritePalette() +{ + const sal_uLong nCount = mpAccess->GetPaletteEntryCount(); + std::unique_ptr<sal_uInt8[]> pTempBuf(new sal_uInt8[nCount * 3]); + sal_uInt8* pTmp = pTempBuf.get(); + + ImplOpenChunk(PNGCHUNK_PLTE); + + for (sal_uLong i = 0; i < nCount; i++) + { + const BitmapColor& rColor = mpAccess->GetPaletteColor(i); + *pTmp++ = rColor.GetRed(); + *pTmp++ = rColor.GetGreen(); + *pTmp++ = rColor.GetBlue(); + } + ImplWriteChunk(pTempBuf.get(), nCount * 3); +} + +void PNGWriterImpl::ImplWritepHYs(const BitmapEx& rBmpEx) +{ + if (rBmpEx.GetPrefMapMode().GetMapUnit() != MapUnit::Map100thMM) + return; + + Size aPrefSize(rBmpEx.GetPrefSize()); + + if (aPrefSize.Width() && aPrefSize.Height() && mnWidth && mnHeight) + { + ImplOpenChunk(PNGCHUNK_pHYs); + sal_uInt32 nPrefSizeX = static_cast<sal_uInt32>( + 100000.0 / (static_cast<double>(aPrefSize.Width()) / mnWidth) + 0.5); + sal_uInt32 nPrefSizeY = static_cast<sal_uInt32>( + 100000.0 / (static_cast<double>(aPrefSize.Height()) / mnHeight) + 0.5); + ImplWriteChunk(nPrefSizeX); + ImplWriteChunk(nPrefSizeY); + ImplWriteChunk(sal_uInt8(1)); // nMapUnit + } +} + +void PNGWriterImpl::ImplWriteIDAT() +{ + mnDeflateInSize = mnBitsPerPixel; + + if (mpMaskAccess) + mnDeflateInSize += 8; + + mnBBP = (mnDeflateInSize + 7) >> 3; + + mnDeflateInSize = mnBBP * mnWidth + 1; + + mpDeflateInBuf.reset(new sal_uInt8[mnDeflateInSize]); + + if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times + { + mpPreviousScan.reset(new sal_uInt8[mnDeflateInSize]); + mpCurrentScan.reset(new sal_uInt8[mnDeflateInSize]); + ImplClearFirstScanline(); + } + mpZCodec.BeginCompression(mnCompLevel); + SvMemoryStream aOStm; + if (mnInterlaced == 0) + { + for (sal_uLong nY = 0; nY < mnHeight; nY++) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY)); + } + } + else + { + // interlace mode + sal_uLong nY; + for (nY = 0; nY < mnHeight; nY += 8) // pass 1 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 8)); + } + ImplClearFirstScanline(); + + for (nY = 0; nY < mnHeight; nY += 8) // pass 2 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 4, 8)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 5) // pass 3 + { + for (nY = 4; nY < mnHeight; nY += 8) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 4)); + } + ImplClearFirstScanline(); + } + + for (nY = 0; nY < mnHeight; nY += 4) // pass 4 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 2, 4)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 3) // pass 5 + { + for (nY = 2; nY < mnHeight; nY += 4) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 2)); + } + ImplClearFirstScanline(); + } + + for (nY = 0; nY < mnHeight; nY += 2) // pass 6 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 1, 2)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 2) // pass 7 + { + for (nY = 1; nY < mnHeight; nY += 2) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY)); + } + } + } + mpZCodec.EndCompression(); + + if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times + { + mpCurrentScan.reset(); + mpPreviousScan.reset(); + } + mpDeflateInBuf.reset(); + + sal_uInt32 nIDATSize = aOStm.Tell(); + sal_uInt32 nBytes, nBytesToWrite = nIDATSize; + while (nBytesToWrite) + { + nBytes = nBytesToWrite <= mnMaxChunkSize ? nBytesToWrite : mnMaxChunkSize; + ImplOpenChunk(PNGCHUNK_IDAT); + ImplWriteChunk( + const_cast<unsigned char*>(static_cast<unsigned char const*>(aOStm.GetData())) + + (nIDATSize - nBytesToWrite), + nBytes); + nBytesToWrite -= nBytes; + } +} + +// ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd +// appends to the currently used pass +// the complete size of scanline will be returned - in interlace mode zero is possible! + +sal_uLong PNGWriterImpl::ImplGetFilter(sal_uLong nY, sal_uLong nXStart, sal_uLong nXAdd) +{ + sal_uInt8* pDest; + + if (mnFilterType) + pDest = mpCurrentScan.get(); + else + pDest = mpDeflateInBuf.get(); + + if (nXStart < mnWidth) + { + *pDest++ = mnFilterType; // in this version the filter type is either 0 or 4 + + if (mpAccess + ->HasPalette()) // alphachannel is not allowed by pictures including palette entries + { + switch (mnBitsPerPixel) + { + case 1: + { + Scanline pScanline = mpAccess->GetScanline(nY); + sal_uLong nX, nXIndex; + for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++) + { + sal_uLong nShift = (nXIndex & 7) ^ 7; + if (nShift == 7) + *pDest = mpAccess->GetIndexFromData(pScanline, nX) << nShift; + else if (nShift == 0) + *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX) << nShift; + else + *pDest |= mpAccess->GetIndexFromData(pScanline, nX) << nShift; + } + if ((nXIndex & 7) != 0) + pDest++; // byte is not completely used, so the bufferpointer is to correct + } + break; + + case 4: + { + Scanline pScanline = mpAccess->GetScanline(nY); + sal_uLong nX, nXIndex; + for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++) + { + if (nXIndex & 1) + *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX); + else + *pDest = mpAccess->GetIndexFromData(pScanline, nX) << 4; + } + if (nXIndex & 1) + pDest++; + } + break; + + case 8: + { + Scanline pScanline = mpAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + *pDest++ = mpAccess->GetIndexFromData(pScanline, nX); + } + } + break; + + default: + mbStatus = false; + break; + } + } + else + { + if (mpMaskAccess) // mpMaskAccess != NULL -> alphachannel is to create + { + if (mbTrueAlpha) + { + Scanline pScanline = mpAccess->GetScanline(nY); + Scanline pScanlineMask = mpMaskAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + *pDest++ = 255 - mpMaskAccess->GetIndexFromData(pScanlineMask, nX); + } + } + else + { + const BitmapColor aTrans(mpMaskAccess->GetBestMatchingColor(COL_WHITE)); + Scanline pScanline = mpAccess->GetScanline(nY); + Scanline pScanlineMask = mpMaskAccess->GetScanline(nY); + + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + + if (mpMaskAccess->GetPixelFromData(pScanlineMask, nX) == aTrans) + *pDest++ = 0; + else + *pDest++ = 0xff; + } + } + } + else + { + Scanline pScanline = mpAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + } + } + } + } + // filter type4 ( PAETH ) will be used only for 24bit graphics + if (mnFilterType) + { + mnDeflateInSize = pDest - mpCurrentScan.get(); + pDest = mpDeflateInBuf.get(); + *pDest++ = 4; // filter type + + sal_uInt8* p1 = mpCurrentScan.get() + 1; // Current Pixel + sal_uInt8* p2 = p1 - mnBBP; // left pixel + sal_uInt8* p3 = mpPreviousScan.get(); // upper pixel + sal_uInt8* p4 = p3 - mnBBP; // upperleft Pixel; + + while (pDest < mpDeflateInBuf.get() + mnDeflateInSize) + { + sal_uLong nb = *p3++; + sal_uLong na, nc; + if (p2 >= mpCurrentScan.get() + 1) + { + na = *p2; + nc = *p4; + } + else + { + na = nc = 0; + } + + tools::Long np = na + nb - nc; + tools::Long npa = np - na; + tools::Long npb = np - nb; + tools::Long npc = np - nc; + + if (npa < 0) + npa = -npa; + if (npb < 0) + npb = -npb; + if (npc < 0) + npc = -npc; + + if (npa <= npb && npa <= npc) + *pDest++ = *p1++ - static_cast<sal_uInt8>(na); + else if (npb <= npc) + *pDest++ = *p1++ - static_cast<sal_uInt8>(nb); + else + *pDest++ = *p1++ - static_cast<sal_uInt8>(nc); + + p4++; + p2++; + } + for (tools::Long i = 0; i < static_cast<tools::Long>(mnDeflateInSize - 1); i++) + { + mpPreviousScan[i] = mpCurrentScan[i + 1]; + } + } + else + { + mnDeflateInSize = pDest - mpDeflateInBuf.get(); + } + return mnDeflateInSize; +} + +void PNGWriterImpl::ImplClearFirstScanline() +{ + if (mnFilterType) + memset(mpPreviousScan.get(), 0, mnDeflateInSize); +} + +void PNGWriterImpl::ImplOpenChunk(sal_uLong nChunkType) +{ + maChunkSeq.emplace_back(); + maChunkSeq.back().nType = nChunkType; +} + +void PNGWriterImpl::ImplWriteChunk(sal_uInt8 nSource) +{ + maChunkSeq.back().aData.push_back(nSource); +} + +void PNGWriterImpl::ImplWriteChunk(sal_uInt32 nSource) +{ + vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 24)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 16)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 8)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource)); +} + +void PNGWriterImpl::ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize) +{ + if (nDatSize) + { + vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); + sal_uInt32 nSize = rChunkData.aData.size(); + rChunkData.aData.resize(nSize + nDatSize); + memcpy(&rChunkData.aData[nSize], pSource, nDatSize); + } +} + +PNGWriter::PNGWriter(const BitmapEx& rBmpEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData) + : mpImpl(new vcl::PNGWriterImpl(rBmpEx, pFilterData)) +{ +} + +PNGWriter::~PNGWriter() {} + +bool PNGWriter::Write(SvStream& rStream) { return mpImpl->Write(rStream); } + +std::vector<vcl::PNGWriter::ChunkData>& PNGWriter::GetChunks() { return mpImpl->GetChunks(); } + +} // namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/svm/SvmConverter.cxx b/vcl/source/filter/svm/SvmConverter.cxx new file mode 100644 index 000000000..2e7ed8f35 --- /dev/null +++ b/vcl/source/filter/svm/SvmConverter.cxx @@ -0,0 +1,1290 @@ +/* -*- 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/log.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <o3tl/safeint.hxx> + +#include <vcl/TypeSerializer.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/filter/SvmReader.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include "SvmConverter.hxx" + +#include <boost/rational.hpp> +#include <algorithm> +#include <memory> +#include <stack> +#include <string.h> + +// Inlines +static void ImplReadRect( SvStream& rIStm, tools::Rectangle& rRect ) +{ + Point aTL; + Point aBR; + + TypeSerializer aSerializer(rIStm); + aSerializer.readPoint(aTL); + aSerializer.readPoint(aBR); + + rRect = tools::Rectangle( aTL, aBR ); +} + +static bool ImplReadPoly(SvStream& rIStm, tools::Polygon& rPoly) +{ + TypeSerializer aSerializer(rIStm); + + sal_Int32 nSize32(0); + rIStm.ReadInt32(nSize32); + sal_uInt16 nSize = nSize32; + + const size_t nMaxPossiblePoints = rIStm.remainingSize() / 2 * sizeof(sal_Int32); + if (nSize > nMaxPossiblePoints) + { + SAL_WARN("vcl.gdi", "svm record claims to have: " << nSize << " points, but only " << nMaxPossiblePoints << " possible"); + return false; + } + + rPoly = tools::Polygon(nSize); + + for (sal_uInt16 i = 0; i < nSize && rIStm.good(); ++i) + { + aSerializer.readPoint(rPoly[i]); + } + return rIStm.good(); +} + +static bool ImplReadPolyPoly(SvStream& rIStm, tools::PolyPolygon& rPolyPoly) +{ + bool bSuccess = true; + + tools::Polygon aPoly; + sal_Int32 nPolyCount32(0); + rIStm.ReadInt32(nPolyCount32); + sal_uInt16 nPolyCount = static_cast<sal_uInt16>(nPolyCount32); + + for (sal_uInt16 i = 0; i < nPolyCount && rIStm.good(); ++i) + { + if (!ImplReadPoly(rIStm, aPoly)) + { + bSuccess = false; + break; + } + rPolyPoly.Insert(aPoly); + } + + return bSuccess && rIStm.good(); +} + +static void ImplReadColor( SvStream& rIStm, Color& rColor ) +{ + sal_Int16 nVal(0); + + rIStm.ReadInt16( nVal ); rColor.SetRed( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) ); + rIStm.ReadInt16( nVal ); rColor.SetGreen( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) ); + rIStm.ReadInt16( nVal ); rColor.SetBlue( sal::static_int_cast<sal_uInt8>(static_cast<sal_uInt16>(nVal) >> 8) ); +} + +static bool ImplReadMapMode(SvStream& rIStm, MapMode& rMapMode) +{ + sal_Int16 nUnit(0); + rIStm.ReadInt16(nUnit); + + Point aOrg; + TypeSerializer aSerializer(rIStm); + aSerializer.readPoint(aOrg); + + sal_Int32 nXNum(0), nXDenom(0), nYNum(0), nYDenom(0); + rIStm.ReadInt32(nXNum).ReadInt32(nXDenom).ReadInt32(nYNum).ReadInt32(nYDenom); + + if (!rIStm.good() || nXDenom <= 0 || nYDenom <= 0 || nXNum <= 0 || nYNum <= 0) + { + SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode fraction"); + return false; + } + + if (nUnit < sal_Int16(MapUnit::Map100thMM) || nUnit > sal_Int16(MapUnit::LAST)) + { + SAL_WARN("vcl.gdi", "Parsing error: invalid mapmode"); + return false; + } + + rMapMode = MapMode(static_cast<MapUnit>(nUnit), aOrg, Fraction(nXNum, nXDenom), Fraction(nYNum, nYDenom)); + + return true; +} + +static void ImplReadUnicodeComment( sal_uInt32 nStrmPos, SvStream& rIStm, OUString& rString ) +{ + sal_uInt64 nOld = rIStm.Tell(); + if ( nStrmPos ) + { + sal_uInt16 nType; + sal_uInt32 nActionSize; + std::size_t nStringLen; + + rIStm.Seek( nStrmPos ); + rIStm .ReadUInt16( nType ) + .ReadUInt32( nActionSize ); + + nStringLen = (nActionSize - 4) >> 1; + + if ( nStringLen && ( nType == GDI_UNICODE_COMMENT ) ) + rString = read_uInt16s_ToOUString(rIStm, nStringLen); + } + rIStm.Seek( nOld ); +} + +static void ImplSkipActions(SvStream& rIStm, sal_uLong nSkipCount) +{ + sal_Int32 nActionSize; + sal_Int16 nType; + for (sal_uLong i = 0; i < nSkipCount; ++i) + { + rIStm.ReadInt16(nType).ReadInt32(nActionSize); + if (!rIStm.good() || nActionSize < 4) + break; + rIStm.SeekRel(nActionSize - 4); + } +} + +static void ImplReadExtendedPolyPolygonAction(SvStream& rIStm, tools::PolyPolygon& rPolyPoly) +{ + TypeSerializer aSerializer(rIStm); + + rPolyPoly.Clear(); + sal_uInt16 nPolygonCount(0); + rIStm.ReadUInt16( nPolygonCount ); + + if (!nPolygonCount) + return; + + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize; + if (nPolygonCount > nMaxRecords) + { + SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nPolygonCount << " claimed, truncating"); + nPolygonCount = nMaxRecords; + } + + for(sal_uInt16 a(0); a < nPolygonCount; a++) + { + sal_uInt16 nPointCount(0); + rIStm.ReadUInt16(nPointCount); + + const size_t nMinPolygonSize = sizeof(sal_Int32) * 2; + const size_t nMaxPolygons = rIStm.remainingSize() / nMinPolygonSize; + if (nPointCount > nMaxPolygons) + { + SAL_WARN("vcl.gdi", "Parsing error: " << nMaxPolygons << + " max possible entries, but " << nPointCount << " claimed, truncating"); + nPointCount = nMaxPolygons; + } + + tools::Polygon aCandidate(nPointCount); + + if (nPointCount) + { + for(sal_uInt16 b(0); b < nPointCount; b++) + { + aSerializer.readPoint(aCandidate[b]); + } + + sal_uInt8 bHasFlags(int(false)); + rIStm.ReadUChar( bHasFlags ); + + if(bHasFlags) + { + sal_uInt8 aPolyFlags(0); + + for(sal_uInt16 c(0); c < nPointCount; c++) + { + rIStm.ReadUChar( aPolyFlags ); + aCandidate.SetFlags(c, static_cast<PolyFlags>(aPolyFlags)); + } + } + } + + rPolyPoly.Insert(aCandidate); + } +} + +SVMConverter::SVMConverter( SvStream& rStm, GDIMetaFile& rMtf ) +{ + if( !rStm.GetError() ) + { + ImplConvertFromSVM1( rStm, rMtf ); + } +} + +namespace +{ + sal_Int32 SkipActions(sal_Int32 i, sal_Int32 nFollowingActionCount, sal_Int32 nActions) + { + sal_Int32 remainingActions = nActions - i; + if (nFollowingActionCount < 0) + nFollowingActionCount = remainingActions; + return std::min(remainingActions, nFollowingActionCount); + } + + void ClampRange(const OUString& rStr, sal_Int32& rIndex, sal_Int32& rLength, + std::vector<sal_Int32>* pDXAry = nullptr) + { + const sal_Int32 nStrLength = rStr.getLength(); + + if (rIndex < 0 || rIndex > nStrLength) + { + SAL_WARN("vcl.gdi", "inconsistent offset"); + rIndex = nStrLength; + } + + if (rLength < 0 || rLength > nStrLength - rIndex) + { + SAL_WARN("vcl.gdi", "inconsistent len"); + rLength = nStrLength - rIndex; + } + + if (pDXAry && pDXAry->size() > o3tl::make_unsigned(rLength)) + pDXAry->resize(rLength); + } +} + +#define LF_FACESIZE 32 + +void static lcl_error( SvStream& rIStm, const SvStreamEndian& nOldFormat, sal_uInt64 nPos) +{ + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + rIStm.SetEndian(nOldFormat); + rIStm.Seek(nPos); + return; +} +void SVMConverter::ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf ) +{ + const sal_uInt64 nPos = rIStm.Tell(); + const SvStreamEndian nOldFormat = rIStm.GetEndian(); + + rIStm.SetEndian( SvStreamEndian::LITTLE ); + + char aCode[ 5 ]; + Size aPrefSz; + + // read header + rIStm.ReadBytes(aCode, sizeof(aCode)); // Identifier + sal_Int16 nSize(0); + rIStm.ReadInt16( nSize ); // Size + sal_Int16 nVersion(0); + rIStm.ReadInt16( nVersion ); // Version + sal_Int32 nTmp32(0); + rIStm.ReadInt32( nTmp32 ); + if (nTmp32 < 0) + { + SAL_WARN("vcl.gdi", "svm: value for width should be positive"); + lcl_error(rIStm, nOldFormat, nPos); + return; + } + aPrefSz.setWidth( nTmp32 ); // PrefSize.Width() + rIStm.ReadInt32( nTmp32 ); + if (nTmp32 < 0) + { + SAL_WARN("vcl.gdi", "svm: value for height should be positive"); + lcl_error(rIStm, nOldFormat, nPos); + return; + } + aPrefSz.setHeight( nTmp32 ); // PrefSize.Height() + + // check header-magic and version + if( rIStm.GetError() + || ( nVersion != 200 ) + || ( memcmp( aCode, "SVGDI", sizeof( aCode ) ) != 0 ) ) + { + SAL_WARN("vcl.gdi", "svm: wrong check for header-magic and version"); + lcl_error(rIStm, nOldFormat, nPos); + return; + } + + LineInfo aLineInfo( LineStyle::NONE, 0 ); + std::stack<LineInfo, std::vector<LineInfo>> aLIStack; + ScopedVclPtrInstance< VirtualDevice > aFontVDev; + rtl_TextEncoding eActualCharSet = osl_getThreadTextEncoding(); + bool bFatLine = false; + + tools::Polygon aActionPoly; + tools::Rectangle aRect; + Point aPt, aPt1; + Size aSz; + Color aActionColor; + + sal_uInt32 nUnicodeCommentStreamPos = 0; + sal_Int32 nUnicodeCommentActionNumber = 0; + + rMtf.SetPrefSize(aPrefSz); + + MapMode aMapMode; + if (ImplReadMapMode(rIStm, aMapMode)) // MapMode + rMtf.SetPrefMapMode(aMapMode); + + sal_Int32 nActions(0); + rIStm.ReadInt32(nActions); // Action count + if (nActions < 0) + { + SAL_WARN("vcl.gdi", "svm claims negative action count (" << nActions << ")"); + nActions = 0; + } + + const size_t nMinActionSize = sizeof(sal_uInt16) + sizeof(sal_Int32); + const size_t nMaxPossibleActions = rIStm.remainingSize() / nMinActionSize; + if (o3tl::make_unsigned(nActions) > nMaxPossibleActions) + { + SAL_WARN("vcl.gdi", "svm claims more actions (" << nActions << ") than stream could provide, truncating"); + nActions = nMaxPossibleActions; + } + + size_t nLastPolygonAction(0); + + TypeSerializer aSerializer(rIStm); + + for (sal_Int32 i = 0; i < nActions && rIStm.good(); ++i) + { + sal_Int16 nType(0); + rIStm.ReadInt16(nType); + sal_Int32 nActBegin = rIStm.Tell(); + sal_Int32 nActionSize(0); + rIStm.ReadInt32(nActionSize); + + SAL_WARN_IF( ( nType > 33 ) && ( nType < 1024 ), "vcl.gdi", "Unknown GDIMetaAction while converting!" ); + + switch( nType ) + { + case GDI_PIXEL_ACTION: + { + aSerializer.readPoint(aPt); + ImplReadColor( rIStm, aActionColor ); + rMtf.AddAction( new MetaPixelAction( aPt, aActionColor ) ); + } + break; + + case GDI_POINT_ACTION: + { + aSerializer.readPoint(aPt); + rMtf.AddAction( new MetaPointAction( aPt ) ); + } + break; + + case GDI_LINE_ACTION: + { + aSerializer.readPoint(aPt); + aSerializer.readPoint(aPt1); + rMtf.AddAction( new MetaLineAction( aPt, aPt1, aLineInfo ) ); + } + break; + + case GDI_LINEJOIN_ACTION : + { + sal_Int16 nLineJoin(0); + rIStm.ReadInt16( nLineJoin ); + aLineInfo.SetLineJoin(static_cast<basegfx::B2DLineJoin>(nLineJoin)); + } + break; + + case GDI_LINECAP_ACTION : + { + sal_Int16 nLineCap(0); + rIStm.ReadInt16( nLineCap ); + aLineInfo.SetLineCap(static_cast<css::drawing::LineCap>(nLineCap)); + } + break; + + case GDI_LINEDASHDOT_ACTION : + { + sal_Int16 a(0); + sal_Int32 b(0); + + rIStm.ReadInt16( a ); aLineInfo.SetDashCount(a); + rIStm.ReadInt32( b ); aLineInfo.SetDashLen(b); + rIStm.ReadInt16( a ); aLineInfo.SetDotCount(a); + rIStm.ReadInt32( b ); aLineInfo.SetDotLen(b); + rIStm.ReadInt32( b ); aLineInfo.SetDistance(b); + + if(((aLineInfo.GetDashCount() && aLineInfo.GetDashLen()) + || (aLineInfo.GetDotCount() && aLineInfo.GetDotLen())) + && aLineInfo.GetDistance()) + { + aLineInfo.SetStyle(LineStyle::Dash); + } + } + break; + + case GDI_EXTENDEDPOLYGON_ACTION : + { + // read the tools::PolyPolygon in every case + tools::PolyPolygon aInputPolyPolygon; + ImplReadExtendedPolyPolygonAction(rIStm, aInputPolyPolygon); + + // now check if it can be set somewhere + if(nLastPolygonAction < rMtf.GetActionSize()) + { + MetaPolyLineAction* pPolyLineAction = dynamic_cast< MetaPolyLineAction* >(rMtf.GetAction(nLastPolygonAction)); + + if(pPolyLineAction) + { + // replace MetaPolyLineAction when we have a single polygon. Do not rely on the + // same point count; the originally written GDI_POLYLINE_ACTION may have been + // Subdivided for better quality for older usages + if(1 == aInputPolyPolygon.Count()) + { + rMtf.ReplaceAction( + new MetaPolyLineAction( + aInputPolyPolygon.GetObject(0), + pPolyLineAction->GetLineInfo()), + nLastPolygonAction); + } + } + else + { + MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast< MetaPolyPolygonAction* >(rMtf.GetAction(nLastPolygonAction)); + + if(pPolyPolygonAction) + { + // replace MetaPolyPolygonAction when we have a curved polygon. Do rely on the + // same sub-polygon count + if(pPolyPolygonAction->GetPolyPolygon().Count() == aInputPolyPolygon.Count()) + { + rMtf.ReplaceAction( + new MetaPolyPolygonAction( + aInputPolyPolygon), + nLastPolygonAction); + } + } + else + { + MetaPolygonAction* pPolygonAction = dynamic_cast< MetaPolygonAction* >(rMtf.GetAction(nLastPolygonAction)); + + if(pPolygonAction) + { + // replace MetaPolygonAction + if(1 == aInputPolyPolygon.Count()) + { + rMtf.ReplaceAction( + new MetaPolygonAction( + aInputPolyPolygon.GetObject(0)), + nLastPolygonAction); + } + } + } + } + } + } + break; + + case GDI_RECT_ACTION: + { + ImplReadRect( rIStm, aRect ); + sal_Int32 nTmp(0), nTmp1(0); + rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 ); + + if( nTmp || nTmp1 ) + rMtf.AddAction( new MetaRoundRectAction( aRect, nTmp, nTmp1 ) ); + else + { + rMtf.AddAction( new MetaRectAction( aRect ) ); + + if( bFatLine ) + rMtf.AddAction( new MetaPolyLineAction( aRect, aLineInfo ) ); + } + } + break; + + case GDI_ELLIPSE_ACTION: + { + ImplReadRect( rIStm, aRect ); + + if( bFatLine ) + { + const tools::Polygon aPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) ); + rMtf.AddAction( new MetaPolygonAction( aPoly ) ); + rMtf.AddAction( new MetaPopAction() ); + rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) ); + } + else + rMtf.AddAction( new MetaEllipseAction( aRect ) ); + } + break; + + case GDI_ARC_ACTION: + { + ImplReadRect( rIStm, aRect ); + aSerializer.readPoint(aPt); + aSerializer.readPoint(aPt1); + + if( bFatLine ) + { + const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Arc ); + + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) ); + rMtf.AddAction( new MetaPolygonAction( aPoly ) ); + rMtf.AddAction( new MetaPopAction() ); + rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) ); + } + else + rMtf.AddAction( new MetaArcAction( aRect, aPt, aPt1 ) ); + } + break; + + case GDI_PIE_ACTION: + { + ImplReadRect( rIStm, aRect ); + aSerializer.readPoint(aPt); + aSerializer.readPoint(aPt1); + + if( bFatLine ) + { + const tools::Polygon aPoly( aRect, aPt, aPt1, PolyStyle::Pie ); + + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) ); + rMtf.AddAction( new MetaPolygonAction( aPoly ) ); + rMtf.AddAction( new MetaPopAction() ); + rMtf.AddAction( new MetaPolyLineAction( aPoly, aLineInfo ) ); + } + else + rMtf.AddAction( new MetaPieAction( aRect, aPt, aPt1 ) ); + } + break; + + case GDI_INVERTRECT_ACTION: + case GDI_HIGHLIGHTRECT_ACTION: + { + ImplReadRect( rIStm, aRect ); + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::RASTEROP ) ); + rMtf.AddAction( new MetaRasterOpAction( RasterOp::Invert ) ); + rMtf.AddAction( new MetaRectAction( aRect ) ); + rMtf.AddAction( new MetaPopAction() ); + } + break; + + case GDI_POLYLINE_ACTION: + { + if (ImplReadPoly(rIStm, aActionPoly)) + { + nLastPolygonAction = rMtf.GetActionSize(); + + if( bFatLine ) + rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) ); + else + rMtf.AddAction( new MetaPolyLineAction( aActionPoly ) ); + } + } + break; + + case GDI_POLYGON_ACTION: + { + if (ImplReadPoly(rIStm, aActionPoly)) + { + if( bFatLine ) + { + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) ); + rMtf.AddAction( new MetaPolygonAction( aActionPoly ) ); + rMtf.AddAction( new MetaPopAction() ); + rMtf.AddAction( new MetaPolyLineAction( aActionPoly, aLineInfo ) ); + } + else + { + nLastPolygonAction = rMtf.GetActionSize(); + rMtf.AddAction( new MetaPolygonAction( aActionPoly ) ); + } + } + } + break; + + case GDI_POLYPOLYGON_ACTION: + { + tools::PolyPolygon aPolyPoly; + + if (ImplReadPolyPoly(rIStm, aPolyPoly)) + { + if( bFatLine ) + { + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) ); + rMtf.AddAction( new MetaLineColorAction( COL_TRANSPARENT, false ) ); + rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) ); + rMtf.AddAction( new MetaPopAction() ); + + for( sal_uInt16 nPoly = 0, nCount = aPolyPoly.Count(); nPoly < nCount; nPoly++ ) + rMtf.AddAction( new MetaPolyLineAction( aPolyPoly[ nPoly ], aLineInfo ) ); + } + else + { + nLastPolygonAction = rMtf.GetActionSize(); + rMtf.AddAction( new MetaPolyPolygonAction( aPolyPoly ) ); + } + } + } + break; + + case GDI_FONT_ACTION: + { + vcl::Font aFont; + char aName[LF_FACESIZE+1]; + + ImplReadColor( rIStm, aActionColor ); aFont.SetColor( aActionColor ); + ImplReadColor( rIStm, aActionColor ); aFont.SetFillColor( aActionColor ); + size_t nRet = rIStm.ReadBytes(aName, LF_FACESIZE); + aName[nRet] = 0; + aFont.SetFamilyName( OUString( aName, strlen(aName), rIStm.GetStreamCharSet() ) ); + + sal_Int32 nWidth(0), nHeight(0); + rIStm.ReadInt32(nWidth).ReadInt32(nHeight); + sal_Int16 nCharOrient(0), nLineOrient(0); + rIStm.ReadInt16(nCharOrient).ReadInt16(nLineOrient); + sal_Int16 nCharSet(0), nFamily(0), nPitch(0), nAlign(0), nWeight(0), nUnderline(0), nStrikeout(0); + rIStm.ReadInt16(nCharSet).ReadInt16(nFamily).ReadInt16(nPitch).ReadInt16(nAlign).ReadInt16(nWeight).ReadInt16(nUnderline).ReadInt16(nStrikeout); + bool bItalic(false), bOutline(false), bShadow(false), bTransparent(false); + rIStm.ReadCharAsBool(bItalic).ReadCharAsBool(bOutline).ReadCharAsBool(bShadow).ReadCharAsBool(bTransparent); + + aFont.SetFontSize( Size( nWidth, nHeight ) ); + aFont.SetCharSet( static_cast<rtl_TextEncoding>(nCharSet) ); + aFont.SetFamily( static_cast<FontFamily>(nFamily & SAL_MAX_ENUM) ); + aFont.SetPitch( static_cast<FontPitch>(nPitch & SAL_MAX_ENUM) ); + aFont.SetAlignment( static_cast<TextAlign>(nAlign & SAL_MAX_ENUM) ); + aFont.SetWeight( ( nWeight == 1 ) ? WEIGHT_LIGHT : ( nWeight == 2 ) ? WEIGHT_NORMAL : + ( nWeight == 3 ) ? WEIGHT_BOLD : WEIGHT_DONTKNOW ); + aFont.SetUnderline( static_cast<FontLineStyle>(nUnderline & SAL_MAX_ENUM) ); + aFont.SetStrikeout( static_cast<FontStrikeout>(nStrikeout & SAL_MAX_ENUM) ); + aFont.SetItalic( bItalic ? ITALIC_NORMAL : ITALIC_NONE ); + aFont.SetOutline( bOutline ); + aFont.SetShadow( bShadow ); + aFont.SetOrientation( Degree10(nLineOrient) ); + aFont.SetTransparent( bTransparent ); + + eActualCharSet = aFont.GetCharSet(); + if ( eActualCharSet == RTL_TEXTENCODING_DONTKNOW ) + eActualCharSet = osl_getThreadTextEncoding(); + + rMtf.AddAction( new MetaFontAction( aFont ) ); + rMtf.AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) ); + rMtf.AddAction( new MetaTextColorAction( aFont.GetColor() ) ); + rMtf.AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) ); + + // #106172# Track font relevant data in shadow VDev + aFontVDev->SetFont( aFont ); + } + break; + + case GDI_TEXT_ACTION: + { + sal_Int32 nIndex(0), nLen(0), nTmp(0); + aSerializer.readPoint(aPt); + rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ); + if (nTmp > 0) + { + OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp); + sal_uInt8 nTerminator = 0; + rIStm.ReadUChar( nTerminator ); + SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" ); + + OUString aStr(OStringToOUString(aByteStr, eActualCharSet)); + if ( nUnicodeCommentActionNumber == i ) + ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr ); + ClampRange(aStr, nIndex, nLen); + rMtf.AddAction( new MetaTextAction( aPt, aStr, nIndex, nLen ) ); + } + + if (nActionSize < 24) + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + else + rIStm.Seek(nActBegin + nActionSize); + } + break; + + case GDI_TEXTARRAY_ACTION: + { + sal_Int32 nIndex(0), nLen(0), nAryLen(0), nTmp(0); + aSerializer.readPoint(aPt); + rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nAryLen ); + if (nTmp > 0) + { + OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp); + sal_uInt8 nTerminator = 0; + rIStm.ReadUChar( nTerminator ); + SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" ); + + OUString aStr(OStringToOUString(aByteStr, eActualCharSet)); + + std::vector<sal_Int32> aDXAry; + if (nAryLen > 0) + { + const size_t nMinRecordSize = sizeof(sal_Int32); + const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize; + if (o3tl::make_unsigned(nAryLen) > nMaxRecords) + { + SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nAryLen << " claimed, truncating"); + nAryLen = nMaxRecords; + } + + sal_Int32 nStrLen( aStr.getLength() ); + + sal_Int32 nDXAryLen = std::max(nAryLen, nStrLen); + + if (nDXAryLen < nLen) + { + //MetaTextArrayAction ctor expects pDXAry to be >= nLen if set, so if this can't + //be achieved, don't read it, it's utterly broken. + SAL_WARN("vcl.gdi", "dxary too short, discarding completely"); + rIStm.SeekRel(sizeof(sal_Int32) * nDXAryLen); + nLen = 0; + nIndex = 0; + } + else + { + aDXAry.resize(nDXAryLen); + + for (sal_Int32 j = 0; j < nAryLen; ++j) + { + rIStm.ReadInt32( nTmp ); + aDXAry[ j ] = nTmp; + } + + // #106172# Add last DX array elem, if missing + if( nAryLen != nStrLen ) + { + if (nAryLen+1 == nStrLen && nIndex >= 0) + { + std::vector<sal_Int32> aTmpAry; + + aFontVDev->GetTextArray( aStr, &aTmpAry, nIndex, nLen ); + + if (aTmpAry.size() < o3tl::make_unsigned(nStrLen)) + SAL_WARN("vcl.gdi", "TextArray too short to recover missing element"); + else + { + // now, the difference between the + // last and the second last DX array + // is the advancement for the last + // glyph. Thus, to complete our meta + // action's DX array, just add that + // difference to last elem and store + // in very last. + if( nStrLen > 1 ) + aDXAry[ nStrLen-1 ] = aDXAry[ nStrLen-2 ] + aTmpAry[ nStrLen-1 ] - aTmpAry[ nStrLen-2 ]; + else + aDXAry[ nStrLen-1 ] = aTmpAry[ nStrLen-1 ]; // len=1: 0th position taken to be 0 + } + } +#ifdef DBG_UTIL + else + OSL_FAIL("More than one DX array element missing on SVM import"); +#endif + } + } + } + if ( nUnicodeCommentActionNumber == i ) + ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr ); + ClampRange(aStr, nIndex, nLen, &aDXAry); + rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, aDXAry, nIndex, nLen ) ); + } + + if (nActionSize < 24) + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + else + rIStm.Seek(nActBegin + nActionSize); + } + break; + + case GDI_STRETCHTEXT_ACTION: + { + sal_Int32 nIndex(0), nLen(0), nWidth(0), nTmp(0); + + aSerializer.readPoint(aPt); + rIStm.ReadInt32( nIndex ).ReadInt32( nLen ).ReadInt32( nTmp ).ReadInt32( nWidth ); + if (nTmp > 0) + { + OString aByteStr = read_uInt8s_ToOString(rIStm, nTmp); + sal_uInt8 nTerminator = 0; + rIStm.ReadUChar( nTerminator ); + SAL_WARN_IF( nTerminator != 0, "vcl.gdi", "expected string to be NULL terminated" ); + + OUString aStr(OStringToOUString(aByteStr, eActualCharSet)); + if ( nUnicodeCommentActionNumber == i ) + ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr ); + ClampRange(aStr, nIndex, nLen); + rMtf.AddAction( new MetaStretchTextAction( aPt, nWidth, aStr, nIndex, nLen ) ); + } + + if (nActionSize < 28) + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + else + rIStm.Seek(nActBegin + nActionSize); + } + break; + + case GDI_BITMAP_ACTION: + { + Bitmap aBmp; + + aSerializer.readPoint(aPt); + ReadDIB(aBmp, rIStm, true); + rMtf.AddAction( new MetaBmpAction( aPt, aBmp ) ); + } + break; + + case GDI_BITMAPSCALE_ACTION: + { + Bitmap aBmp; + + aSerializer.readPoint(aPt); + aSerializer.readSize(aSz); + ReadDIB(aBmp, rIStm, true); + rMtf.AddAction( new MetaBmpScaleAction( aPt, aSz, aBmp ) ); + } + break; + + case GDI_BITMAPSCALEPART_ACTION: + { + Bitmap aBmp; + Size aSz2; + + aSerializer.readPoint(aPt); + aSerializer.readSize(aSz); + aSerializer.readPoint(aPt1); + aSerializer.readSize(aSz2); + ReadDIB(aBmp, rIStm, true); + rMtf.AddAction( new MetaBmpScalePartAction( aPt, aSz, aPt1, aSz2, aBmp ) ); + } + break; + + case GDI_PEN_ACTION: + { + ImplReadColor( rIStm, aActionColor ); + + sal_Int32 nPenWidth(0); + sal_Int16 nPenStyle(0); + rIStm.ReadInt32( nPenWidth ).ReadInt16( nPenStyle ); + + aLineInfo.SetStyle( nPenStyle ? LineStyle::Solid : LineStyle::NONE ); + aLineInfo.SetWidth( nPenWidth ); + bFatLine = nPenStyle && !aLineInfo.IsDefault(); + + rMtf.AddAction( new MetaLineColorAction( aActionColor, nPenStyle != 0 ) ); + } + break; + + case GDI_FILLBRUSH_ACTION: + { + ImplReadColor( rIStm, aActionColor ); + rIStm.SeekRel( 6 ); + sal_Int16 nBrushStyle(0); + rIStm.ReadInt16( nBrushStyle ); + rMtf.AddAction( new MetaFillColorAction( aActionColor, nBrushStyle != 0 ) ); + rIStm.SeekRel( 2 ); + } + break; + + case GDI_MAPMODE_ACTION: + { + if (ImplReadMapMode(rIStm, aMapMode)) + { + rMtf.AddAction(new MetaMapModeAction(aMapMode)); + + // #106172# Track font relevant data in shadow VDev + aFontVDev->SetMapMode(aMapMode); + }; + } + break; + + case GDI_CLIPREGION_ACTION: + { + vcl::Region aRegion; + bool bClip = false; + + sal_Int16 nRegType(0); + sal_Int16 bIntersect(0); + rIStm.ReadInt16( nRegType ).ReadInt16( bIntersect ); + ImplReadRect( rIStm, aRect ); + + switch( nRegType ) + { + case 0: + break; + + case 1: + { + tools::Rectangle aRegRect; + + ImplReadRect( rIStm, aRegRect ); + aRegion = vcl::Region( aRegRect ); + bClip = true; + } + break; + + case 2: + { + if (ImplReadPoly(rIStm, aActionPoly)) + { + aRegion = vcl::Region( aActionPoly ); + bClip = true; + } + } + break; + + case 3: + { + bool bSuccess = true; + tools::PolyPolygon aPolyPoly; + sal_Int32 nPolyCount32(0); + rIStm.ReadInt32(nPolyCount32); + sal_uInt16 nPolyCount(nPolyCount32); + + for (sal_uInt16 j = 0; j < nPolyCount && rIStm.good(); ++j) + { + if (!ImplReadPoly(rIStm, aActionPoly)) + { + bSuccess = false; + break; + } + aPolyPoly.Insert(aActionPoly); + } + + if (bSuccess) + { + aRegion = vcl::Region( aPolyPoly ); + bClip = true; + } + } + break; + } + + if( bIntersect ) + aRegion.Intersect( aRect ); + + rMtf.AddAction( new MetaClipRegionAction( aRegion, bClip ) ); + } + break; + + case GDI_MOVECLIPREGION_ACTION: + { + sal_Int32 nTmp(0), nTmp1(0); + rIStm.ReadInt32( nTmp ).ReadInt32( nTmp1 ); + rMtf.AddAction( new MetaMoveClipRegionAction( nTmp, nTmp1 ) ); + } + break; + + case GDI_ISECTCLIPREGION_ACTION: + { + ImplReadRect( rIStm, aRect ); + rMtf.AddAction( new MetaISectRectClipRegionAction( aRect ) ); + } + break; + + case GDI_RASTEROP_ACTION: + { + RasterOp eRasterOp; + + sal_Int16 nRasterOp(0); + rIStm.ReadInt16( nRasterOp ); + + switch( nRasterOp ) + { + case 1: + eRasterOp = RasterOp::Invert; + break; + + case 4: + case 5: + eRasterOp = RasterOp::Xor; + break; + + default: + eRasterOp = RasterOp::OverPaint; + break; + } + + rMtf.AddAction( new MetaRasterOpAction( eRasterOp ) ); + } + break; + + case GDI_PUSH_ACTION: + { + aLIStack.push(aLineInfo); + rMtf.AddAction( new MetaPushAction( vcl::PushFlags::ALL ) ); + + // #106172# Track font relevant data in shadow VDev + aFontVDev->Push(); + } + break; + + case GDI_POP_ACTION: + { + + std::optional<LineInfo> xLineInfo; + if (!aLIStack.empty()) + { + xLineInfo = std::move(aLIStack.top()); + aLIStack.pop(); + } + + // restore line info + if (xLineInfo) + { + aLineInfo = *xLineInfo; + xLineInfo.reset(); + bFatLine = ( LineStyle::NONE != aLineInfo.GetStyle() ) && !aLineInfo.IsDefault(); + } + + rMtf.AddAction( new MetaPopAction() ); + + // #106172# Track font relevant data in shadow VDev + aFontVDev->Pop(); + } + break; + + case GDI_GRADIENT_ACTION: + { + ImplReadRect( rIStm, aRect ); + + sal_Int16 nStyle(0); + rIStm.ReadInt16( nStyle ); + + Color aStartCol, aEndCol; + ImplReadColor( rIStm, aStartCol ); + ImplReadColor( rIStm, aEndCol ); + + sal_Int16 nAngle(0), nBorder(0), nOfsX(0), nOfsY(0), nIntensityStart(0), nIntensityEnd(0); + rIStm.ReadInt16( nAngle ).ReadInt16( nBorder ).ReadInt16( nOfsX ).ReadInt16( nOfsY ).ReadInt16( nIntensityStart ).ReadInt16( nIntensityEnd ); + + Gradient aGrad( static_cast<GradientStyle>(nStyle), aStartCol, aEndCol ); + + aGrad.SetAngle( Degree10(nAngle) ); + aGrad.SetBorder( nBorder ); + aGrad.SetOfsX( nOfsX ); + aGrad.SetOfsY( nOfsY ); + aGrad.SetStartIntensity( nIntensityStart ); + aGrad.SetEndIntensity( nIntensityEnd ); + rMtf.AddAction( new MetaGradientAction( aRect, aGrad ) ); + } + break; + + case GDI_TRANSPARENT_COMMENT: + { + tools::PolyPolygon aPolyPoly; + sal_Int32 nFollowingActionCount(0); + sal_Int16 nTrans(0); + + ReadPolyPolygon( rIStm, aPolyPoly ); + rIStm.ReadInt16( nTrans ).ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaTransparentAction( aPolyPoly, nTrans ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_FLOATTRANSPARENT_COMMENT: + { + GDIMetaFile aMtf; + Point aPos; + Size aSize; + Gradient aGradient; + sal_Int32 nFollowingActionCount(0); + + SvmReader aReader( rIStm ); + aReader.Read( aMtf ); + aSerializer.readPoint(aPos); + aSerializer.readSize(aSize); + aSerializer.readGradient(aGradient); + rIStm.ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaFloatTransparentAction( aMtf, aPos, aSize, aGradient ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_HATCH_COMMENT: + { + tools::PolyPolygon aPolyPoly; + Hatch aHatch; + sal_Int32 nFollowingActionCount(0); + + ReadPolyPolygon( rIStm, aPolyPoly ); + ReadHatch( rIStm, aHatch ); + rIStm.ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaHatchAction( aPolyPoly, aHatch ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_REFPOINT_COMMENT: + { + Point aRefPoint; + bool bSet(false); + sal_Int32 nFollowingActionCount(0); + + aSerializer.readPoint(aRefPoint); + rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaRefPointAction( aRefPoint, bSet ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + + // #106172# Track font relevant data in shadow VDev + if( bSet ) + aFontVDev->SetRefPoint( aRefPoint ); + else + aFontVDev->SetRefPoint(); + } + break; + + case GDI_TEXTLINECOLOR_COMMENT: + { + Color aColor; + bool bSet(false); + sal_Int32 nFollowingActionCount(0); + + aSerializer.readColor(aColor); + rIStm.ReadCharAsBool( bSet ).ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaTextLineColorAction( aColor, bSet ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_TEXTLINE_COMMENT: + { + Point aStartPt; + sal_Int32 nWidth(0); + sal_uInt32 nStrikeout(0); + sal_uInt32 nUnderline(0); + sal_Int32 nFollowingActionCount(0); + + aSerializer.readPoint(aStartPt); + rIStm.ReadInt32(nWidth ).ReadUInt32(nStrikeout).ReadUInt32(nUnderline).ReadInt32(nFollowingActionCount); + ImplSkipActions(rIStm, nFollowingActionCount); + rMtf.AddAction( new MetaTextLineAction( aStartPt, nWidth, + static_cast<FontStrikeout>(nStrikeout & SAL_MAX_ENUM), + static_cast<FontLineStyle>(nUnderline & SAL_MAX_ENUM), + LINESTYLE_NONE ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_GRADIENTEX_COMMENT: + { + tools::PolyPolygon aPolyPoly; + Gradient aGradient; + sal_Int32 nFollowingActionCount(0); + + ReadPolyPolygon( rIStm, aPolyPoly ); + aSerializer.readGradient(aGradient); + rIStm.ReadInt32( nFollowingActionCount ); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction( new MetaGradientExAction( aPolyPoly, aGradient ) ); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_COMMENT_COMMENT: + { + std::vector<sal_uInt8> aData; + + OString aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); + sal_Int32 nValue(0); + sal_uInt32 nDataSize(0); + rIStm.ReadInt32(nValue).ReadUInt32(nDataSize); + + if (nDataSize) + { + const size_t nMaxPossibleData = rIStm.remainingSize(); + if (nDataSize > nMaxPossibleActions) + { + SAL_WARN("vcl.gdi", "svm record claims to have: " << nDataSize << " data, but only " << nMaxPossibleData << " possible"); + nDataSize = nMaxPossibleActions; + } + aData.resize(nDataSize); + nDataSize = rIStm.ReadBytes(aData.data(), nDataSize); + } + + sal_Int32 nFollowingActionCount(0); + rIStm.ReadInt32(nFollowingActionCount); + ImplSkipActions( rIStm, nFollowingActionCount ); + rMtf.AddAction(new MetaCommentAction(aComment, nValue, aData.data(), nDataSize)); + + i = SkipActions(i, nFollowingActionCount, nActions); + } + break; + + case GDI_UNICODE_COMMENT: + { + nUnicodeCommentActionNumber = i + 1; + nUnicodeCommentStreamPos = rIStm.Tell() - 6; + if (nActionSize < 4) + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + else + rIStm.SeekRel(nActionSize - 4); + } + break; + + default: + if (nActionSize < 4) + rIStm.SetError(SVSTREAM_FILEFORMAT_ERROR); + else + rIStm.SeekRel(nActionSize - 4); + break; + } + } + + rIStm.SetEndian( nOldFormat ); +} + +bool TestImportSVM(SvStream& rStream) +{ + GDIMetaFile aGDIMetaFile; + SvmReader aReader(rStream); + aReader.Read(aGDIMetaFile); + ScopedVclPtrInstance<VirtualDevice> aVDev; + aVDev->SetTextRenderModeForResolutionIndependentLayout(true); + try + { + aGDIMetaFile.Play(*aVDev); + } + catch (const boost::bad_rational&) + { + return false; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/svm/SvmConverter.hxx b/vcl/source/filter/svm/SvmConverter.hxx new file mode 100644 index 000000000..23185dc04 --- /dev/null +++ b/vcl/source/filter/svm/SvmConverter.hxx @@ -0,0 +1,92 @@ +/* -*- 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_VCL_INC_SVMCONVERTER_HXX +#define INCLUDED_VCL_INC_SVMCONVERTER_HXX + +#include <vcl/dllapi.h> +#include <vcl/gdimtf.hxx> + +#define GDI_PIXEL_ACTION 1 +#define GDI_POINT_ACTION 2 +#define GDI_LINE_ACTION 3 +#define GDI_RECT_ACTION 4 +#define GDI_ELLIPSE_ACTION 5 +#define GDI_ARC_ACTION 6 +#define GDI_PIE_ACTION 7 +#define GDI_INVERTRECT_ACTION 8 +#define GDI_HIGHLIGHTRECT_ACTION 9 +#define GDI_POLYLINE_ACTION 10 +#define GDI_POLYGON_ACTION 11 +#define GDI_POLYPOLYGON_ACTION 12 +#define GDI_TEXT_ACTION 13 +#define GDI_TEXTARRAY_ACTION 14 +#define GDI_STRETCHTEXT_ACTION 15 +#define GDI_BITMAP_ACTION 17 +#define GDI_BITMAPSCALE_ACTION 18 +#define GDI_PEN_ACTION 19 +#define GDI_FONT_ACTION 20 +#define GDI_FILLBRUSH_ACTION 22 +#define GDI_MAPMODE_ACTION 23 +#define GDI_CLIPREGION_ACTION 24 +#define GDI_RASTEROP_ACTION 25 +#define GDI_PUSH_ACTION 26 +#define GDI_POP_ACTION 27 +#define GDI_MOVECLIPREGION_ACTION 28 +#define GDI_ISECTCLIPREGION_ACTION 29 +#define GDI_BITMAPSCALEPART_ACTION 32 +#define GDI_GRADIENT_ACTION 33 + +#define GDI_TRANSPARENT_COMMENT 1024 +#define GDI_HATCH_COMMENT 1025 +#define GDI_REFPOINT_COMMENT 1026 +#define GDI_TEXTLINECOLOR_COMMENT 1027 +#define GDI_TEXTLINE_COMMENT 1028 +#define GDI_FLOATTRANSPARENT_COMMENT 1029 +#define GDI_GRADIENTEX_COMMENT 1030 +#define GDI_COMMENT_COMMENT 1031 +#define GDI_UNICODE_COMMENT 1032 + +#define GDI_LINEJOIN_ACTION 1033 +#define GDI_EXTENDEDPOLYGON_ACTION 1034 +#define GDI_LINEDASHDOT_ACTION 1035 + +#define GDI_LINECAP_ACTION 1036 + +/** + * Converts old SVGDI aka SVM1 format data to current VCLMTF aka SVM2 format metafile data. + */ +class SVMConverter +{ +private: + static void ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf ); + +public: + SVMConverter( SvStream& rIStm, GDIMetaFile& rMtf ); + +private: + SVMConverter( const SVMConverter& ) = delete; + SVMConverter& operator=( const SVMConverter& ) = delete; +}; + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportSVM(SvStream& rStream); + +#endif // INCLUDED_VCL_INC_SVMCONVERTER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/svm/SvmReader.cxx b/vcl/source/filter/svm/SvmReader.cxx new file mode 100644 index 000000000..f02451ea3 --- /dev/null +++ b/vcl/source/filter/svm/SvmReader.cxx @@ -0,0 +1,1438 @@ +/* -*- 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/log.hxx> +#include <osl/thread.h> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> + +#include <vcl/filter/SvmReader.hxx> +#include <vcl/TypeSerializer.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> + +#include "SvmConverter.hxx" + +namespace +{ +class DepthGuard +{ +private: + ImplMetaReadData& m_rData; + rtl_TextEncoding m_eOrigCharSet; + +public: + DepthGuard(ImplMetaReadData& rData, SvStream const& rIStm) + : m_rData(rData) + , m_eOrigCharSet(m_rData.meActualCharSet) + { + ++m_rData.mnParseDepth; + m_rData.meActualCharSet = rIStm.GetStreamCharSet(); + } + bool TooDeep() const { return m_rData.mnParseDepth > 1024; } + ~DepthGuard() + { + --m_rData.mnParseDepth; + m_rData.meActualCharSet = m_eOrigCharSet; + } +}; +} + +SvmReader::SvmReader(SvStream& rIStm) + : mrStream(rIStm) +{ +} + +SvStream& SvmReader::Read(GDIMetaFile& rMetaFile, ImplMetaReadData* pData) +{ + if (mrStream.GetError()) + { + SAL_WARN("vcl.gdi", "Stream error: " << mrStream.GetError()); + return mrStream; + } + + sal_uInt64 nStmPos = mrStream.Tell(); + SvStreamEndian nOldFormat = mrStream.GetEndian(); + + mrStream.SetEndian(SvStreamEndian::LITTLE); + + try + { + char aId[7]; + aId[0] = 0; + aId[6] = 0; + mrStream.ReadBytes(aId, 6); + if (mrStream.good() && !strcmp(aId, "VCLMTF")) + { + // new format + sal_uInt32 nStmCompressMode = 0; + sal_uInt32 nCount = 0; + std::unique_ptr<VersionCompatRead> pCompat(new VersionCompatRead(mrStream)); + + mrStream.ReadUInt32(nStmCompressMode); + TypeSerializer aSerializer(mrStream); + MapMode aMapMode; + aSerializer.readMapMode(aMapMode); + rMetaFile.SetPrefMapMode(aMapMode); + Size aSize; + aSerializer.readSize(aSize); + rMetaFile.SetPrefSize(aSize); + mrStream.ReadUInt32(nCount); + + pCompat.reset(); // destructor writes stuff into the header + + std::unique_ptr<ImplMetaReadData> xReadData; + if (!pData) + { + xReadData.reset(new ImplMetaReadData); + pData = xReadData.get(); + } + DepthGuard aDepthGuard(*pData, mrStream); + + if (aDepthGuard.TooDeep()) + throw std::runtime_error("too much recursion"); + + for (sal_uInt32 nAction = 0; (nAction < nCount) && !mrStream.eof(); nAction++) + { + rtl::Reference<MetaAction> pAction = MetaActionHandler(pData); + if (pAction) + { + if (pAction->GetType() == MetaActionType::COMMENT) + { + MetaCommentAction* pCommentAct + = static_cast<MetaCommentAction*>(pAction.get()); + + if (pCommentAct->GetComment() == "EMF_PLUS") + rMetaFile.UseCanvas(true); + } + rMetaFile.AddAction(pAction); + } + } + } + else + { + mrStream.Seek(nStmPos); + SVMConverter(mrStream, rMetaFile); + } + } + catch (...) + { + SAL_WARN("vcl", "GDIMetaFile exception during load"); + mrStream.SetError(SVSTREAM_FILEFORMAT_ERROR); + }; + + // check for errors + if (mrStream.GetError()) + { + rMetaFile.Clear(); + mrStream.Seek(nStmPos); + } + + mrStream.SetEndian(nOldFormat); + return mrStream; +} + +rtl::Reference<MetaAction> SvmReader::MetaActionHandler(ImplMetaReadData* pData) +{ + rtl::Reference<MetaAction> pAction; + sal_uInt16 nTmp = 0; + mrStream.ReadUInt16(nTmp); + MetaActionType nType = static_cast<MetaActionType>(nTmp); + + switch (nType) + { + case MetaActionType::NONE: + return DefaultHandler(); + case MetaActionType::PIXEL: + return PixelHandler(); + case MetaActionType::POINT: + return PointHandler(); + case MetaActionType::LINE: + return LineHandler(); + case MetaActionType::RECT: + return RectHandler(); + case MetaActionType::ROUNDRECT: + return RoundRectHandler(); + case MetaActionType::ELLIPSE: + return EllipseHandler(); + case MetaActionType::ARC: + return ArcHandler(); + case MetaActionType::PIE: + return PieHandler(); + case MetaActionType::CHORD: + return ChordHandler(); + case MetaActionType::POLYLINE: + return PolyLineHandler(); + case MetaActionType::POLYGON: + return PolygonHandler(); + case MetaActionType::POLYPOLYGON: + return PolyPolygonHandler(); + case MetaActionType::TEXT: + return TextHandler(pData); + case MetaActionType::TEXTARRAY: + return TextArrayHandler(pData); + case MetaActionType::STRETCHTEXT: + return StretchTextHandler(pData); + case MetaActionType::TEXTRECT: + return TextRectHandler(pData); + case MetaActionType::TEXTLINE: + return TextLineHandler(); + case MetaActionType::BMP: + return BmpHandler(); + case MetaActionType::BMPSCALE: + return BmpScaleHandler(); + case MetaActionType::BMPSCALEPART: + return BmpScalePartHandler(); + case MetaActionType::BMPEX: + return BmpExHandler(); + case MetaActionType::BMPEXSCALE: + return BmpExScaleHandler(); + case MetaActionType::BMPEXSCALEPART: + return BmpExScalePartHandler(); + case MetaActionType::MASK: + return MaskHandler(); + case MetaActionType::MASKSCALE: + return MaskScaleHandler(); + case MetaActionType::MASKSCALEPART: + return MaskScalePartHandler(); + case MetaActionType::GRADIENT: + return GradientHandler(); + case MetaActionType::GRADIENTEX: + return GradientExHandler(); + case MetaActionType::HATCH: + return HatchHandler(); + case MetaActionType::WALLPAPER: + return WallpaperHandler(); + case MetaActionType::CLIPREGION: + return ClipRegionHandler(); + case MetaActionType::ISECTRECTCLIPREGION: + return ISectRectClipRegionHandler(); + case MetaActionType::ISECTREGIONCLIPREGION: + return ISectRegionClipRegionHandler(); + case MetaActionType::MOVECLIPREGION: + return MoveClipRegionHandler(); + case MetaActionType::LINECOLOR: + return LineColorHandler(); + case MetaActionType::FILLCOLOR: + return FillColorHandler(); + case MetaActionType::TEXTCOLOR: + return TextColorHandler(); + case MetaActionType::TEXTFILLCOLOR: + return TextFillColorHandler(); + case MetaActionType::TEXTLINECOLOR: + return TextLineColorHandler(); + case MetaActionType::OVERLINECOLOR: + return OverlineColorHandler(); + case MetaActionType::TEXTALIGN: + return TextAlignHandler(); + case MetaActionType::MAPMODE: + return MapModeHandler(); + case MetaActionType::FONT: + return FontHandler(pData); + case MetaActionType::PUSH: + return PushHandler(); + case MetaActionType::POP: + return PopHandler(); + case MetaActionType::RASTEROP: + return RasterOpHandler(); + case MetaActionType::Transparent: + return TransparentHandler(); + case MetaActionType::FLOATTRANSPARENT: + return FloatTransparentHandler(pData); + case MetaActionType::EPS: + return EPSHandler(); + case MetaActionType::REFPOINT: + return RefPointHandler(); + case MetaActionType::COMMENT: + return CommentHandler(); + case MetaActionType::LAYOUTMODE: + return LayoutModeHandler(); + case MetaActionType::TEXTLANGUAGE: + return TextLanguageHandler(); + + default: + { + VersionCompatRead aCompat(mrStream); + } + break; + } + + return pAction; +} + +void SvmReader::ReadColor(Color& rColor) +{ + sal_uInt32 nTmp(0); + mrStream.ReadUInt32(nTmp); + rColor = ::Color(ColorTransparency, nTmp); +} + +rtl::Reference<MetaAction> SvmReader::LineColorHandler() +{ + rtl::Reference<MetaLineColorAction> pAction(new MetaLineColorAction); + + VersionCompatRead aCompat(mrStream); + Color aColor; + ReadColor(aColor); + bool aBool(false); + mrStream.ReadCharAsBool(aBool); + + pAction->SetSetting(aBool); + pAction->SetColor(aColor); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::FillColorHandler() +{ + rtl::Reference<MetaFillColorAction> pAction(new MetaFillColorAction); + + VersionCompatRead aCompat(mrStream); + + Color aColor; + ReadColor(aColor); + bool aBool(false); + mrStream.ReadCharAsBool(aBool); + + pAction->SetColor(aColor); + pAction->SetSetting(aBool); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::RectHandler() +{ + rtl::Reference<MetaRectAction> pAction(new MetaRectAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + pAction->SetRect(aRectangle); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PointHandler() +{ + rtl::Reference<MetaPointAction> pAction(new MetaPointAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPoint; + aSerializer.readPoint(aPoint); + pAction->SetPoint(aPoint); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PixelHandler() +{ + rtl::Reference<MetaPixelAction> pAction(new MetaPixelAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPoint; + aSerializer.readPoint(aPoint); + Color aColor; + ReadColor(aColor); + + pAction->SetPoint(aPoint); + pAction->SetColor(aColor); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::LineHandler() +{ + rtl::Reference<MetaLineAction> pAction(new MetaLineAction); + + VersionCompatRead aCompat(mrStream); + + // Version 1 + TypeSerializer aSerializer(mrStream); + Point aPoint; + Point aEndPoint; + aSerializer.readPoint(aPoint); + aSerializer.readPoint(aEndPoint); + + pAction->SetStartPoint(aPoint); + pAction->SetEndPoint(aEndPoint); + + // Version 2 + if (aCompat.GetVersion() >= 2) + { + LineInfo aLineInfo; + ReadLineInfo(mrStream, aLineInfo); + pAction->SetLineInfo(aLineInfo); + } + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::RoundRectHandler() +{ + rtl::Reference<MetaRoundRectAction> pAction(new MetaRoundRectAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + sal_uInt32 HorzRound(0); + sal_uInt32 VertRound(0); + mrStream.ReadUInt32(HorzRound).ReadUInt32(VertRound); + + pAction->SetRect(aRectangle); + pAction->SetHorzRound(HorzRound); + pAction->SetVertRound(VertRound); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::EllipseHandler() +{ + rtl::Reference<MetaEllipseAction> pAction(new MetaEllipseAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + + pAction->SetRect(aRectangle); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::ArcHandler() +{ + rtl::Reference<MetaArcAction> pAction(new MetaArcAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + Point aPoint; + aSerializer.readPoint(aPoint); + Point aEndPoint; + aSerializer.readPoint(aEndPoint); + + pAction->SetRect(aRectangle); + pAction->SetStartPoint(aPoint); + pAction->SetEndPoint(aEndPoint); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PieHandler() +{ + rtl::Reference<MetaPieAction> pAction(new MetaPieAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + Point aPoint; + aSerializer.readPoint(aPoint); + Point aEndPoint; + aSerializer.readPoint(aEndPoint); + + pAction->SetRect(aRectangle); + pAction->SetStartPoint(aPoint); + pAction->SetEndPoint(aEndPoint); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::ChordHandler() +{ + rtl::Reference<MetaChordAction> pAction(new MetaChordAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRectangle; + aSerializer.readRectangle(aRectangle); + Point aPoint; + aSerializer.readPoint(aPoint); + Point aEndPoint; + aSerializer.readPoint(aEndPoint); + + pAction->SetRect(aRectangle); + pAction->SetStartPoint(aPoint); + pAction->SetEndPoint(aEndPoint); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PolyLineHandler() +{ + rtl::Reference<MetaPolyLineAction> pAction(new MetaPolyLineAction); + + VersionCompatRead aCompat(mrStream); + + // Version 1 + tools::Polygon aPolygon; + ReadPolygon(mrStream, aPolygon); + + // Version 2 + if (aCompat.GetVersion() >= 2) + { + LineInfo aLineInfo; + ReadLineInfo(mrStream, aLineInfo); + pAction->SetLineInfo(aLineInfo); + } + if (aCompat.GetVersion() >= 3) + { + sal_uInt8 bHasPolyFlags(0); + mrStream.ReadUChar(bHasPolyFlags); + if (bHasPolyFlags) + aPolygon.Read(mrStream); + } + pAction->SetPolygon(aPolygon); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PolygonHandler() +{ + rtl::Reference<MetaPolygonAction> pAction(new MetaPolygonAction); + + VersionCompatRead aCompat(mrStream); + + tools::Polygon aPolygon; + ReadPolygon(mrStream, aPolygon); // Version 1 + + if (aCompat.GetVersion() >= 2) // Version 2 + { + sal_uInt8 bHasPolyFlags(0); + mrStream.ReadUChar(bHasPolyFlags); + if (bHasPolyFlags) + aPolygon.Read(mrStream); + } + + pAction->SetPolygon(aPolygon); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PolyPolygonHandler() +{ + rtl::Reference<MetaPolyPolygonAction> pAction(new MetaPolyPolygonAction); + + VersionCompatRead aCompat(mrStream); + tools::PolyPolygon aPolyPolygon; + ReadPolyPolygon(mrStream, aPolyPolygon); // Version 1 + + if (aCompat.GetVersion() < 2) // Version 2 + { + pAction->SetPolyPolygon(aPolyPolygon); + return pAction; + } + + sal_uInt16 nNumberOfComplexPolygons(0); + mrStream.ReadUInt16(nNumberOfComplexPolygons); + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = mrStream.remainingSize() / nMinRecordSize; + if (nNumberOfComplexPolygons > nMaxRecords) + { + SAL_WARN("vcl.gdi", "Parsing error: " << nMaxRecords << " max possible entries, but " + << nNumberOfComplexPolygons + << " claimed, truncating"); + nNumberOfComplexPolygons = nMaxRecords; + } + for (sal_uInt16 i = 0; i < nNumberOfComplexPolygons; ++i) + { + sal_uInt16 nIndex(0); + mrStream.ReadUInt16(nIndex); + tools::Polygon aPoly; + aPoly.Read(mrStream); + if (nIndex >= aPolyPolygon.Count()) + { + SAL_WARN("vcl.gdi", "svm contains polygon index " << nIndex + << " outside possible range " + << aPolyPolygon.Count()); + continue; + } + aPolyPolygon.Replace(aPoly, nIndex); + } + + pAction->SetPolyPolygon(aPolyPolygon); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextHandler(const ImplMetaReadData* pData) +{ + rtl::Reference<MetaTextAction> pAction(new MetaTextAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPoint; + aSerializer.readPoint(aPoint); + OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet); + sal_uInt16 nTmpIndex(0); + mrStream.ReadUInt16(nTmpIndex); + sal_uInt16 nTmpLen(0); + mrStream.ReadUInt16(nTmpLen); + + pAction->SetPoint(aPoint); + + if (aCompat.GetVersion() >= 2) // Version 2 + aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream); + + if (nTmpIndex > aStr.getLength()) + { + SAL_WARN("vcl.gdi", "inconsistent offset"); + nTmpIndex = aStr.getLength(); + } + + if (nTmpLen > aStr.getLength() - nTmpIndex) + { + SAL_WARN("vcl.gdi", "inconsistent len"); + nTmpLen = aStr.getLength() - nTmpIndex; + } + + pAction->SetIndex(nTmpIndex); + pAction->SetLen(nTmpLen); + + pAction->SetText(aStr); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextArrayHandler(const ImplMetaReadData* pData) +{ + rtl::Reference<MetaTextArrayAction> pAction(new MetaTextArrayAction); + + std::vector<sal_Int32> aArray; + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPoint; + aSerializer.readPoint(aPoint); + pAction->SetPoint(aPoint); + + OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet); + pAction->SetText(aStr); + + sal_uInt16 nTmpIndex(0); + mrStream.ReadUInt16(nTmpIndex); + + sal_uInt16 nTmpLen(0); + mrStream.ReadUInt16(nTmpLen); + + sal_Int32 nAryLen(0); + mrStream.ReadInt32(nAryLen); + + if (nTmpLen > aStr.getLength() - nTmpIndex) + { + SAL_WARN("vcl.gdi", "inconsistent offset and len"); + pAction->SetIndex(0); + pAction->SetLen(aStr.getLength()); + return pAction; + } + + pAction->SetIndex(nTmpIndex); + pAction->SetLen(nTmpLen); + + if (nAryLen) + { + // #i9762#, #106172# Ensure that DX array is at least mnLen entries long + if (nTmpLen >= nAryLen) + { + try + { + aArray.resize(nTmpLen); + sal_Int32 i; + sal_Int32 val(0); + for (i = 0; i < nAryLen; i++) + { + mrStream.ReadInt32(val); + aArray[i] = val; + } + // #106172# setup remainder + for (; i < nTmpLen; i++) + aArray[i] = 0; + } + catch (std::bad_alloc&) + { + } + } + else + { + return pAction; + } + } + + if (aCompat.GetVersion() >= 2) // Version 2 + { + aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream); + pAction->SetText(aStr); + + if (nTmpLen > aStr.getLength() - nTmpIndex) + { + SAL_WARN("vcl.gdi", "inconsistent offset and len"); + pAction->SetIndex(0); + pAction->SetLen(aStr.getLength()); + aArray.clear(); + } + } + + if (!aArray.empty()) + pAction->SetDXArray(std::move(aArray)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::StretchTextHandler(const ImplMetaReadData* pData) +{ + rtl::Reference<MetaStretchTextAction> pAction(new MetaStretchTextAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPoint; + aSerializer.readPoint(aPoint); + OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet); + sal_uInt32 nTmpWidth(0); + mrStream.ReadUInt32(nTmpWidth); + sal_uInt16 nTmpIndex(0); + mrStream.ReadUInt16(nTmpIndex); + sal_uInt16 nTmpLen(0); + mrStream.ReadUInt16(nTmpLen); + + pAction->SetPoint(aPoint); + pAction->SetWidth(nTmpWidth); + + if (aCompat.GetVersion() >= 2) // Version 2 + aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream); + + if (nTmpIndex > aStr.getLength()) + { + SAL_WARN("vcl.gdi", "inconsistent offset"); + nTmpIndex = aStr.getLength(); + } + + if (nTmpLen > aStr.getLength() - nTmpIndex) + { + SAL_WARN("vcl.gdi", "inconsistent len"); + nTmpLen = aStr.getLength() - nTmpIndex; + } + + pAction->SetIndex(nTmpIndex); + pAction->SetLen(nTmpLen); + + pAction->SetText(aStr); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextRectHandler(const ImplMetaReadData* pData) +{ + rtl::Reference<MetaTextRectAction> pAction(new MetaTextRectAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRect; + aSerializer.readRectangle(aRect); + OUString aStr = mrStream.ReadUniOrByteString(pData->meActualCharSet); + sal_uInt16 nTmp(0); + mrStream.ReadUInt16(nTmp); + + pAction->SetRect(aRect); + pAction->SetStyle(static_cast<DrawTextFlags>(nTmp)); + + if (aCompat.GetVersion() >= 2) // Version 2 + aStr = read_uInt16_lenPrefixed_uInt16s_ToOUString(mrStream); + + pAction->SetText(aStr); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextLineHandler() +{ + rtl::Reference<MetaTextLineAction> pAction(new MetaTextLineAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aPos; + aSerializer.readPoint(aPos); + sal_Int32 nTempWidth(0); + mrStream.ReadInt32(nTempWidth); + + pAction->SetStartPoint(aPos); + pAction->SetWidth(nTempWidth); + + sal_uInt32 nTempStrikeout(0); + mrStream.ReadUInt32(nTempStrikeout); + sal_uInt32 nTempUnderline(0); + mrStream.ReadUInt32(nTempUnderline); + + pAction->SetStrikeout(static_cast<FontStrikeout>(nTempStrikeout & SAL_MAX_ENUM)); + pAction->SetUnderline(static_cast<FontLineStyle>(nTempUnderline & SAL_MAX_ENUM)); + + if (aCompat.GetVersion() >= 2) + { + sal_uInt32 nTempOverline(0); + mrStream.ReadUInt32(nTempOverline); + pAction->SetOverline(static_cast<FontLineStyle>(nTempOverline & SAL_MAX_ENUM)); + } + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::BmpHandler() +{ + rtl::Reference<MetaBmpAction> pAction(new MetaBmpAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + pAction->SetBitmap(aBmp); + pAction->SetPoint(aPoint); + + return pAction; +} + +namespace +{ +void sanitizeNegativeSizeDimensions(Size& rSize) +{ + if (rSize.Width() < 0) + { + SAL_WARN("vcl.gdi", "sanitizeNegativeSizeDimensions: negative width"); + rSize.setWidth(0); + } + + if (rSize.Height() < 0) + { + SAL_WARN("vcl.gdi", "sanitizeNegativeSizeDimensions: negative height"); + rSize.setHeight(0); + } +} +} + +rtl::Reference<MetaAction> SvmReader::BmpScaleHandler() +{ + rtl::Reference<MetaBmpScaleAction> pAction(new MetaBmpScaleAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + Size aSz; + aSerializer.readSize(aSz); + sanitizeNegativeSizeDimensions(aSz); + + pAction->SetBitmap(aBmp); + pAction->SetPoint(aPoint); + pAction->SetSize(aSz); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::BmpScalePartHandler() +{ + rtl::Reference<MetaBmpScalePartAction> pAction(new MetaBmpScalePartAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + TypeSerializer aSerializer(mrStream); + Point aDestPoint; + aSerializer.readPoint(aDestPoint); + Size aDestSize; + aSerializer.readSize(aDestSize); + Point aSrcPoint; + aSerializer.readPoint(aSrcPoint); + Size aSrcSize; + aSerializer.readSize(aSrcSize); + + pAction->SetBitmap(aBmp); + pAction->SetDestPoint(aDestPoint); + pAction->SetDestSize(aDestSize); + pAction->SetSrcPoint(aSrcPoint); + pAction->SetSrcSize(aSrcSize); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::BmpExHandler() +{ + rtl::Reference<MetaBmpExAction> pAction(new MetaBmpExAction); + + VersionCompatRead aCompat(mrStream); + BitmapEx aBmpEx; + ReadDIBBitmapEx(aBmpEx, mrStream); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + pAction->SetPoint(aPoint); + pAction->SetBitmapEx(aBmpEx); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::BmpExScaleHandler() +{ + rtl::Reference<MetaBmpExScaleAction> pAction(new MetaBmpExScaleAction); + + VersionCompatRead aCompat(mrStream); + BitmapEx aBmpEx; + ReadDIBBitmapEx(aBmpEx, mrStream); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + Size aSize; + aSerializer.readSize(aSize); + sanitizeNegativeSizeDimensions(aSize); + + pAction->SetBitmapEx(aBmpEx); + pAction->SetPoint(aPoint); + pAction->SetSize(aSize); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::BmpExScalePartHandler() +{ + rtl::Reference<MetaBmpExScalePartAction> pAction(new MetaBmpExScalePartAction); + + VersionCompatRead aCompat(mrStream); + BitmapEx aBmpEx; + ReadDIBBitmapEx(aBmpEx, mrStream); + TypeSerializer aSerializer(mrStream); + Point aDstPoint; + aSerializer.readPoint(aDstPoint); + Size aDstSize; + aSerializer.readSize(aDstSize); + Point aSrcPoint; + aSerializer.readPoint(aSrcPoint); + Size aSrcSize; + aSerializer.readSize(aSrcSize); + + pAction->SetBitmapEx(aBmpEx); + pAction->SetDestPoint(aDstPoint); + pAction->SetDestSize(aDstSize); + pAction->SetSrcPoint(aSrcPoint); + pAction->SetSrcSize(aSrcSize); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::MaskHandler() +{ + rtl::Reference<MetaMaskAction> pAction(new MetaMaskAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + pAction->SetBitmap(aBmp); + pAction->SetPoint(aPoint); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::MaskScaleHandler() +{ + rtl::Reference<MetaMaskScaleAction> pAction(new MetaMaskScaleAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + Size aSize; + aSerializer.readSize(aSize); + + pAction->SetBitmap(aBmp); + pAction->SetPoint(aPoint); + pAction->SetSize(aSize); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::MaskScalePartHandler() +{ + rtl::Reference<MetaMaskScalePartAction> pAction(new MetaMaskScalePartAction); + + VersionCompatRead aCompat(mrStream); + Bitmap aBmp; + ReadDIB(aBmp, mrStream, true); + Color aColor; + ReadColor(aColor); + TypeSerializer aSerializer(mrStream); + Point aDstPt; + aSerializer.readPoint(aDstPt); + Size aDstSz; + aSerializer.readSize(aDstSz); + Point aSrcPt; + aSerializer.readPoint(aSrcPt); + Size aSrcSz; + aSerializer.readSize(aSrcSz); + + pAction->SetBitmap(aBmp); + pAction->SetColor(aColor); + pAction->SetDestPoint(aDstPt); + pAction->SetDestSize(aDstSz); + pAction->SetSrcPoint(aSrcPt); + pAction->SetSrcSize(aSrcSz); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::GradientHandler() +{ + rtl::Reference<MetaGradientAction> pAction(new MetaGradientAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + tools::Rectangle aRect; + aSerializer.readRectangle(aRect); + Gradient aGradient; + aSerializer.readGradient(aGradient); + + pAction->SetRect(aRect); + pAction->SetGradient(aGradient); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::GradientExHandler() +{ + rtl::Reference<MetaGradientExAction> pAction(new MetaGradientExAction); + + VersionCompatRead aCompat(mrStream); + tools::PolyPolygon aPolyPoly; + ReadPolyPolygon(mrStream, aPolyPoly); + TypeSerializer aSerializer(mrStream); + Gradient aGradient; + aSerializer.readGradient(aGradient); + + pAction->SetGradient(aGradient); + pAction->SetPolyPolygon(aPolyPoly); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::HatchHandler() +{ + rtl::Reference<MetaHatchAction> pAction(new MetaHatchAction); + + VersionCompatRead aCompat(mrStream); + tools::PolyPolygon aPolyPoly; + ReadPolyPolygon(mrStream, aPolyPoly); + Hatch aHatch; + ReadHatch(mrStream, aHatch); + + pAction->SetPolyPolygon(aPolyPoly); + pAction->SetHatch(aHatch); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::WallpaperHandler() +{ + rtl::Reference<MetaWallpaperAction> pAction(new MetaWallpaperAction); + + VersionCompatRead aCompat(mrStream); + Wallpaper aWallpaper; + ReadWallpaper(mrStream, aWallpaper); + + pAction->SetWallpaper(aWallpaper); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::ClipRegionHandler() +{ + rtl::Reference<MetaClipRegionAction> pAction(new MetaClipRegionAction); + + VersionCompatRead aCompat(mrStream); + vcl::Region aRegion; + ReadRegion(mrStream, aRegion); + bool aClip(false); + mrStream.ReadCharAsBool(aClip); + + pAction->SetRegion(aRegion); + pAction->SetClipping(aClip); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::ISectRectClipRegionHandler() +{ + rtl::Reference<MetaISectRectClipRegionAction> pAction(new MetaISectRectClipRegionAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + tools::Rectangle aRect; + aSerializer.readRectangle(aRect); + + pAction->SetRect(aRect); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::ISectRegionClipRegionHandler() +{ + rtl::Reference<MetaISectRegionClipRegionAction> pAction(new MetaISectRegionClipRegionAction); + + VersionCompatRead aCompat(mrStream); + vcl::Region aRegion; + ReadRegion(mrStream, aRegion); + pAction->SetRegion(aRegion); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::MoveClipRegionHandler() +{ + rtl::Reference<MetaMoveClipRegionAction> pAction(new MetaMoveClipRegionAction); + + VersionCompatRead aCompat(mrStream); + sal_Int32 nTmpHM(0), nTmpVM(0); + mrStream.ReadInt32(nTmpHM).ReadInt32(nTmpVM); + + pAction->SetHorzMove(nTmpHM); + pAction->SetVertMove(nTmpVM); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextColorHandler() +{ + rtl::Reference<MetaTextColorAction> pAction(new MetaTextColorAction); + + VersionCompatRead aCompat(mrStream); + Color aColor; + ReadColor(aColor); + + pAction->SetColor(aColor); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextFillColorHandler() +{ + rtl::Reference<MetaTextFillColorAction> pAction(new MetaTextFillColorAction); + + VersionCompatRead aCompat(mrStream); + Color aColor; + ReadColor(aColor); + bool bSet(false); + mrStream.ReadCharAsBool(bSet); + + pAction->SetColor(aColor); + pAction->SetSetting(bSet); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextLineColorHandler() +{ + rtl::Reference<MetaTextLineColorAction> pAction(new MetaTextLineColorAction); + + VersionCompatRead aCompat(mrStream); + Color aColor; + ReadColor(aColor); + bool bSet(false); + mrStream.ReadCharAsBool(bSet); + + pAction->SetColor(aColor); + pAction->SetSetting(bSet); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::OverlineColorHandler() +{ + rtl::Reference<MetaOverlineColorAction> pAction(new MetaOverlineColorAction); + + VersionCompatRead aCompat(mrStream); + Color aColor; + ReadColor(aColor); + bool bSet(false); + mrStream.ReadCharAsBool(bSet); + + pAction->SetColor(aColor); + pAction->SetSetting(bSet); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextAlignHandler() +{ + rtl::Reference<MetaTextAlignAction> pAction(new MetaTextAlignAction); + + VersionCompatRead aCompat(mrStream); + sal_uInt16 nTmp16(0); + mrStream.ReadUInt16(nTmp16); + + pAction->SetTextAlign(static_cast<TextAlign>(nTmp16)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::MapModeHandler() +{ + rtl::Reference<MetaMapModeAction> pAction(new MetaMapModeAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + MapMode aMapMode; + aSerializer.readMapMode(aMapMode); + + pAction->SetMapMode(aMapMode); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::FontHandler(ImplMetaReadData* pData) +{ + rtl::Reference<MetaFontAction> pAction(new MetaFontAction); + + VersionCompatRead aCompat(mrStream); + vcl::Font aFont; + ReadFont(mrStream, aFont); + pData->meActualCharSet = aFont.GetCharSet(); + if (pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW) + pData->meActualCharSet = osl_getThreadTextEncoding(); + + pAction->SetFont(aFont); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PushHandler() +{ + rtl::Reference<MetaPushAction> pAction(new MetaPushAction); + + VersionCompatRead aCompat(mrStream); + sal_uInt16 nTmp(0); + mrStream.ReadUInt16(nTmp); + + pAction->SetPushFlags(static_cast<vcl::PushFlags>(nTmp)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::PopHandler() +{ + rtl::Reference<MetaPopAction> pAction(new MetaPopAction); + + VersionCompatRead aCompat(mrStream); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::RasterOpHandler() +{ + rtl::Reference<MetaRasterOpAction> pAction(new MetaRasterOpAction); + + sal_uInt16 nTmp16(0); + + VersionCompatRead aCompat(mrStream); + mrStream.ReadUInt16(nTmp16); + + pAction->SetRasterOp(static_cast<RasterOp>(nTmp16)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TransparentHandler() +{ + rtl::Reference<MetaTransparentAction> pAction(new MetaTransparentAction); + + VersionCompatRead aCompat(mrStream); + tools::PolyPolygon aPolyPoly; + ReadPolyPolygon(mrStream, aPolyPoly); + sal_uInt16 nTransPercent(0); + mrStream.ReadUInt16(nTransPercent); + + pAction->SetPolyPolygon(aPolyPoly); + pAction->SetTransparence(nTransPercent); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::FloatTransparentHandler(ImplMetaReadData* pData) +{ + rtl::Reference<MetaFloatTransparentAction> pAction(new MetaFloatTransparentAction); + + VersionCompatRead aCompat(mrStream); + GDIMetaFile aMtf; + SvmReader aReader(mrStream); + aReader.Read(aMtf, pData); + TypeSerializer aSerializer(mrStream); + Point aPoint; + aSerializer.readPoint(aPoint); + + Size aSize; + aSerializer.readSize(aSize); + sanitizeNegativeSizeDimensions(aSize); + + Gradient aGradient; + aSerializer.readGradient(aGradient); + + pAction->SetGDIMetaFile(aMtf); + pAction->SetPoint(aPoint); + pAction->SetSize(aSize); + pAction->SetGradient(aGradient); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::EPSHandler() +{ + rtl::Reference<MetaEPSAction> pAction(new MetaEPSAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + GfxLink aGfxLink; + aSerializer.readGfxLink(aGfxLink); + Point aPoint; + aSerializer.readPoint(aPoint); + Size aSize; + aSerializer.readSize(aSize); + GDIMetaFile aSubst; + Read(aSubst); + + pAction->SetLink(aGfxLink); + pAction->SetPoint(aPoint); + pAction->SetSize(aSize); + pAction->SetSubstitute(aSubst); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::RefPointHandler() +{ + rtl::Reference<MetaRefPointAction> pAction(new MetaRefPointAction); + + VersionCompatRead aCompat(mrStream); + TypeSerializer aSerializer(mrStream); + + Point aRefPoint; + aSerializer.readPoint(aRefPoint); + bool bSet(false); + mrStream.ReadCharAsBool(bSet); + + pAction->SetRefPoint(aRefPoint); + pAction->SetSetting(bSet); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::CommentHandler() +{ + rtl::Reference<MetaCommentAction> pAction(new MetaCommentAction); + + VersionCompatRead aCompat(mrStream); + OString aComment; + aComment = read_uInt16_lenPrefixed_uInt8s_ToOString(mrStream); + sal_Int32 nValue(0); + sal_uInt32 nDataSize(0); + mrStream.ReadInt32(nValue).ReadUInt32(nDataSize); + + if (nDataSize > mrStream.remainingSize()) + { + SAL_WARN("vcl.gdi", "Parsing error: " << mrStream.remainingSize() << " available data, but " + << nDataSize << " claimed, truncating"); + nDataSize = mrStream.remainingSize(); + } + + SAL_INFO("vcl.gdi", "MetaCommentAction::Read " << aComment); + + std::unique_ptr<sal_uInt8[]> pData; + pData.reset(); + + if (nDataSize) + { + pData.reset(new sal_uInt8[nDataSize]); + mrStream.ReadBytes(pData.get(), nDataSize); + } + + pAction->SetComment(aComment); + pAction->SetDataSize(nDataSize); + pAction->SetValue(nValue); + pAction->SetData(pData.get(), nDataSize); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::LayoutModeHandler() +{ + rtl::Reference<MetaLayoutModeAction> pAction(new MetaLayoutModeAction); + + VersionCompatRead aCompat(mrStream); + sal_uInt32 tmp(0); + mrStream.ReadUInt32(tmp); + + pAction->SetLayoutMode(static_cast<vcl::text::ComplexTextLayoutFlags>(tmp)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::TextLanguageHandler() +{ + rtl::Reference<MetaTextLanguageAction> pAction(new MetaTextLanguageAction); + + VersionCompatRead aCompat(mrStream); + sal_uInt16 nTmp = 0; + mrStream.ReadUInt16(nTmp); + + pAction->SetTextLanguage(static_cast<LanguageType>(nTmp)); + + return pAction; +} + +rtl::Reference<MetaAction> SvmReader::DefaultHandler() +{ + return rtl::Reference<MetaAction>(new MetaAction); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/svm/SvmWriter.cxx b/vcl/source/filter/svm/SvmWriter.cxx new file mode 100644 index 000000000..118db1325 --- /dev/null +++ b/vcl/source/filter/svm/SvmWriter.cxx @@ -0,0 +1,1421 @@ +/* -*- 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 <vcl/filter/SvmWriter.hxx> +#include <vcl/TypeSerializer.hxx> +#include <vcl/dibtools.hxx> + +#include <tools/vcompat.hxx> + +#include <osl/thread.h> + +SvmWriter::SvmWriter(SvStream& rIStm) + : mrStream(rIStm) +{ +} + +void SvmWriter::WriteColor(::Color aColor) +{ + mrStream.WriteUInt32(static_cast<sal_uInt32>(aColor)); +} + +SvStream& SvmWriter::Write(const GDIMetaFile& rMetaFile) +{ + const SvStreamCompressFlags nStmCompressMode = mrStream.GetCompressMode(); + SvStreamEndian nOldFormat = mrStream.GetEndian(); + + mrStream.SetEndian(SvStreamEndian::LITTLE); + mrStream.WriteBytes("VCLMTF", 6); + + { + VersionCompatWrite aCompat(mrStream, 1); + + mrStream.WriteUInt32(static_cast<sal_uInt32>(nStmCompressMode)); + TypeSerializer aSerializer(mrStream); + aSerializer.writeMapMode(rMetaFile.GetPrefMapMode()); + aSerializer.writeSize(rMetaFile.GetPrefSize()); + mrStream.WriteUInt32(rMetaFile.GetActionSize()); + } // VersionCompatWrite dtor writes stuff into the header + + ImplMetaWriteData aWriteData; + + aWriteData.meActualCharSet = mrStream.GetStreamCharSet(); + + MetaAction* pAct = const_cast<GDIMetaFile&>(rMetaFile).FirstAction(); + while (pAct) + { + MetaActionHandler(pAct, &aWriteData); + pAct = const_cast<GDIMetaFile&>(rMetaFile).NextAction(); + } + + mrStream.SetEndian(nOldFormat); + + return mrStream; +} + +BitmapChecksum SvmWriter::GetChecksum(const GDIMetaFile& rMetaFile) +{ + SvMemoryStream aMemStm(65535, 65535); + ImplMetaWriteData aWriteData; + SVBT16 aBT16; + SVBT32 aBT32; + BitmapChecksumOctetArray aBCOA; + BitmapChecksum nCrc = 0; + + aWriteData.meActualCharSet = aMemStm.GetStreamCharSet(); + + for (size_t i = 0, nObjCount = rMetaFile.GetActionSize(); i < nObjCount; i++) + { + MetaAction* pAction = rMetaFile.GetAction(i); + + switch (pAction->GetType()) + { + case MetaActionType::BMP: + { + MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::BMPEX: + { + MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmapEx().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::MASK: + { + MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + + ShortToSVBT16(static_cast<sal_uInt16>(pAct->GetType()), aBT16); + nCrc = vcl_get_checksum(nCrc, aBT16, 2); + + BCToBCOA(pAct->GetBitmap().GetChecksum(), aBCOA); + nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + + UInt32ToSVBT32(sal_uInt32(pAct->GetColor()), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetDestSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().X(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcPoint().Y(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Width(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + + Int32ToSVBT32(pAct->GetSrcSize().Height(), aBT32); + nCrc = vcl_get_checksum(nCrc, aBT32, 4); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + nCrc = vcl_get_checksum(nCrc, pAct->GetLink().GetData(), + pAct->GetLink().GetDataSize()); + } + break; + + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction& rAct = static_cast<MetaClipRegionAction&>(*pAction); + const vcl::Region& rRegion = rAct.GetRegion(); + + if (rRegion.HasPolyPolygonOrB2DPolyPolygon()) + { + // It has shown that this is a possible bottleneck for checksum calculation. + // In worst case a very expensive RegionHandle representation gets created. + // In this case it's cheaper to use the PolyPolygon + const basegfx::B2DPolyPolygon aPolyPolygon(rRegion.GetAsB2DPolyPolygon()); + SVBT64 aSVBT64; + + for (auto const& rPolygon : aPolyPolygon) + { + const sal_uInt32 nPointCount(rPolygon.count()); + const bool bControl(rPolygon.areControlPointsUsed()); + + for (sal_uInt32 b(0); b < nPointCount; b++) + { + const basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(b)); + + DoubleToSVBT64(aPoint.getX(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + DoubleToSVBT64(aPoint.getY(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + + if (bControl) + { + if (rPolygon.isPrevControlPointUsed(b)) + { + const basegfx::B2DPoint aCtrl(rPolygon.getPrevControlPoint(b)); + + DoubleToSVBT64(aCtrl.getX(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + DoubleToSVBT64(aCtrl.getY(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + } + + if (rPolygon.isNextControlPointUsed(b)) + { + const basegfx::B2DPoint aCtrl(rPolygon.getNextControlPoint(b)); + + DoubleToSVBT64(aCtrl.getX(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + DoubleToSVBT64(aCtrl.getY(), aSVBT64); + nCrc = vcl_get_checksum(nCrc, aSVBT64, 8); + } + } + } + } + + sal_uInt8 tmp = static_cast<sal_uInt8>(rAct.IsClipping()); + nCrc = vcl_get_checksum(nCrc, &tmp, 1); + } + else + { + SvmWriter aWriter(aMemStm); + aWriter.MetaActionHandler(pAction, &aWriteData); + nCrc = vcl_get_checksum(nCrc, aMemStm.GetData(), aMemStm.Tell()); + aMemStm.Seek(0); + } + } + break; + + default: + { + SvmWriter aWriter(aMemStm); + aWriter.MetaActionHandler(pAction, &aWriteData); + nCrc = vcl_get_checksum(nCrc, aMemStm.GetData(), aMemStm.Tell()); + aMemStm.Seek(0); + } + break; + } + } + + return nCrc; +} + +void SvmWriter::MetaActionHandler(MetaAction* pAction, ImplMetaWriteData* pData) +{ + MetaActionType nType = pAction->GetType(); + + switch (nType) + { + case MetaActionType::NONE: + { + ActionHandler(pAction); + } + break; + + case MetaActionType::PIXEL: + { + auto* pMetaAction = static_cast<MetaPixelAction*>(pAction); + PixelHandler(pMetaAction); + } + break; + + case MetaActionType::POINT: + { + auto pMetaAction = static_cast<MetaPointAction*>(pAction); + PointHandler(pMetaAction); + } + break; + + case MetaActionType::LINE: + { + auto* pMetaAction = static_cast<MetaLineAction*>(pAction); + LineHandler(pMetaAction); + } + break; + + case MetaActionType::RECT: + { + auto* pMetaAction = static_cast<MetaRectAction*>(pAction); + RectHandler(pMetaAction); + } + break; + + case MetaActionType::ROUNDRECT: + { + auto* pMetaAction = static_cast<MetaRoundRectAction*>(pAction); + RoundRectHandler(pMetaAction); + } + break; + + case MetaActionType::ELLIPSE: + { + auto* pMetaAction = static_cast<MetaEllipseAction*>(pAction); + EllipseHandler(pMetaAction); + } + break; + + case MetaActionType::ARC: + { + auto* pMetaAction = static_cast<MetaArcAction*>(pAction); + ArcHandler(pMetaAction); + } + break; + + case MetaActionType::PIE: + { + auto* pMetaAction = static_cast<MetaPieAction*>(pAction); + PieHandler(pMetaAction); + } + break; + + case MetaActionType::CHORD: + { + auto* pMetaAction = static_cast<MetaChordAction*>(pAction); + ChordHandler(pMetaAction); + } + break; + + case MetaActionType::POLYLINE: + { + auto* pMetaAction = static_cast<MetaPolyLineAction*>(pAction); + PolyLineHandler(pMetaAction); + } + break; + + case MetaActionType::POLYGON: + { + auto* pMetaAction = static_cast<MetaPolygonAction*>(pAction); + PolygonHandler(pMetaAction); + } + break; + + case MetaActionType::POLYPOLYGON: + { + auto* pMetaAction = static_cast<MetaPolyPolygonAction*>(pAction); + PolyPolygonHandler(pMetaAction); + } + break; + + case MetaActionType::TEXT: + { + auto* pMetaAction = static_cast<MetaTextAction*>(pAction); + TextHandler(pMetaAction, pData); + } + break; + + case MetaActionType::TEXTARRAY: + { + auto* pMetaAction = static_cast<MetaTextArrayAction*>(pAction); + TextArrayHandler(pMetaAction, pData); + } + break; + + case MetaActionType::STRETCHTEXT: + { + auto* pMetaAction = static_cast<MetaStretchTextAction*>(pAction); + StretchTextHandler(pMetaAction, pData); + } + break; + + case MetaActionType::TEXTRECT: + { + auto* pMetaAction = static_cast<MetaTextRectAction*>(pAction); + TextRectHandler(pMetaAction, pData); + } + break; + + case MetaActionType::TEXTLINE: + { + auto* pMetaAction = static_cast<MetaTextLineAction*>(pAction); + TextLineHandler(pMetaAction); + } + break; + + case MetaActionType::BMP: + { + auto* pMetaAction = static_cast<MetaBmpAction*>(pAction); + BmpHandler(pMetaAction); + } + break; + + case MetaActionType::BMPSCALE: + { + auto* pMetaAction = static_cast<MetaBmpScaleAction*>(pAction); + BmpScaleHandler(pMetaAction); + } + break; + + case MetaActionType::BMPSCALEPART: + { + auto* pMetaAction = static_cast<MetaBmpScalePartAction*>(pAction); + BmpScalePartHandler(pMetaAction); + } + break; + + case MetaActionType::BMPEX: + { + auto* pMetaAction = static_cast<MetaBmpExAction*>(pAction); + BmpExHandler(pMetaAction); + } + break; + + case MetaActionType::BMPEXSCALE: + { + auto* pMetaAction = static_cast<MetaBmpExScaleAction*>(pAction); + BmpExScaleHandler(pMetaAction); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + auto* pMetaAction = static_cast<MetaBmpExScalePartAction*>(pAction); + BmpExScalePartHandler(pMetaAction); + } + break; + + case MetaActionType::MASK: + { + auto* pMetaAction = static_cast<MetaMaskAction*>(pAction); + MaskHandler(pMetaAction); + } + break; + + case MetaActionType::MASKSCALE: + { + auto* pMetaAction = static_cast<MetaMaskScaleAction*>(pAction); + MaskScaleHandler(pMetaAction); + } + break; + + case MetaActionType::MASKSCALEPART: + { + auto* pMetaAction = static_cast<MetaMaskScalePartAction*>(pAction); + MaskScalePartHandler(pMetaAction); + } + break; + + case MetaActionType::GRADIENT: + { + auto* pMetaAction = static_cast<MetaGradientAction*>(pAction); + GradientHandler(pMetaAction); + } + break; + + case MetaActionType::GRADIENTEX: + { + auto* pMetaAction = static_cast<MetaGradientExAction*>(pAction); + GradientExHandler(pMetaAction); + } + break; + + case MetaActionType::HATCH: + { + auto* pMetaAction = static_cast<MetaHatchAction*>(pAction); + HatchHandler(pMetaAction); + } + break; + + case MetaActionType::WALLPAPER: + { + auto* pMetaAction = static_cast<MetaWallpaperAction*>(pAction); + WallpaperHandler(pMetaAction); + } + break; + + case MetaActionType::CLIPREGION: + { + auto* pMetaAction = static_cast<MetaClipRegionAction*>(pAction); + ClipRegionHandler(pMetaAction); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + auto* pMetaAction = static_cast<MetaISectRectClipRegionAction*>(pAction); + ISectRectClipRegionHandler(pMetaAction); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + auto* pMetaAction = static_cast<MetaISectRegionClipRegionAction*>(pAction); + ISectRegionClipRegionHandler(pMetaAction); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + auto* pMetaAction = static_cast<MetaMoveClipRegionAction*>(pAction); + MoveClipRegionHandler(pMetaAction); + } + break; + + case MetaActionType::LINECOLOR: + { + auto* pMetaAction = static_cast<MetaLineColorAction*>(pAction); + LineColorHandler(pMetaAction); + } + break; + + case MetaActionType::FILLCOLOR: + { + auto* pMetaAction = static_cast<MetaFillColorAction*>(pAction); + FillColorHandler(pMetaAction); + } + break; + + case MetaActionType::TEXTCOLOR: + { + auto* pMetaAction = static_cast<MetaTextColorAction*>(pAction); + TextColorHandler(pMetaAction); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + auto* pMetaAction = static_cast<MetaTextFillColorAction*>(pAction); + TextFillColorHandler(pMetaAction); + } + break; + + case MetaActionType::TEXTLINECOLOR: + { + auto* pMetaAction = static_cast<MetaTextLineColorAction*>(pAction); + TextLineColorHandler(pMetaAction); + } + break; + + case MetaActionType::OVERLINECOLOR: + { + auto* pMetaAction = static_cast<MetaOverlineColorAction*>(pAction); + OverlineColorHandler(pMetaAction); + } + break; + + case MetaActionType::TEXTALIGN: + { + auto* pMetaAction = static_cast<MetaTextAlignAction*>(pAction); + TextAlignHandler(pMetaAction); + } + break; + + case MetaActionType::MAPMODE: + { + auto* pMetaAction = static_cast<MetaMapModeAction*>(pAction); + MapModeHandler(pMetaAction); + } + break; + + case MetaActionType::FONT: + { + auto* pMetaAction = static_cast<MetaFontAction*>(pAction); + FontHandler(pMetaAction, pData); + } + break; + + case MetaActionType::PUSH: + { + auto* pMetaAction = static_cast<MetaPushAction*>(pAction); + PushHandler(pMetaAction); + } + break; + + case MetaActionType::POP: + { + auto* pMetaAction = static_cast<MetaPopAction*>(pAction); + PopHandler(pMetaAction); + } + break; + + case MetaActionType::RASTEROP: + { + auto* pMetaAction = static_cast<MetaRasterOpAction*>(pAction); + RasterOpHandler(pMetaAction); + } + break; + + case MetaActionType::Transparent: + { + auto* pMetaAction = static_cast<MetaTransparentAction*>(pAction); + TransparentHandler(pMetaAction); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + auto* pMetaAction = static_cast<MetaFloatTransparentAction*>(pAction); + FloatTransparentHandler(pMetaAction); + } + break; + + case MetaActionType::EPS: + { + auto* pMetaAction = static_cast<MetaEPSAction*>(pAction); + EPSHandler(pMetaAction); + } + break; + + case MetaActionType::REFPOINT: + { + auto* pMetaAction = static_cast<MetaRefPointAction*>(pAction); + RefPointHandler(pMetaAction); + } + break; + + case MetaActionType::COMMENT: + { + auto* pMetaAction = static_cast<MetaCommentAction*>(pAction); + CommentHandler(pMetaAction); + } + break; + + case MetaActionType::LAYOUTMODE: + { + auto* pMetaAction = static_cast<MetaLayoutModeAction*>(pAction); + LayoutModeHandler(pMetaAction); + } + break; + + case MetaActionType::TEXTLANGUAGE: + { + auto* pMetaAction = static_cast<MetaTextLanguageAction*>(pAction); + TextLanguageHandler(pMetaAction); + } + break; + } +} + +void SvmWriter::ActionHandler(const MetaAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); +} + +void SvmWriter::PixelHandler(const MetaPixelAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + WriteColor(pAction->GetColor()); +} + +void SvmWriter::PointHandler(const MetaPointAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); +} + +void SvmWriter::LineHandler(const MetaLineAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + + // Version 1 + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetStartPoint()); + aSerializer.writePoint(pAction->GetEndPoint()); + // Version 2 + WriteLineInfo(mrStream, pAction->GetLineInfo()); +} + +void SvmWriter::RectHandler(const MetaRectAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); +} + +void SvmWriter::RoundRectHandler(const MetaRoundRectAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + mrStream.WriteUInt32(pAction->GetHorzRound()).WriteUInt32(pAction->GetVertRound()); +} + +void SvmWriter::EllipseHandler(const MetaEllipseAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); +} + +void SvmWriter::ArcHandler(const MetaArcAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + aSerializer.writePoint(pAction->GetStartPoint()); + aSerializer.writePoint(pAction->GetEndPoint()); +} + +void SvmWriter::PieHandler(const MetaPieAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + aSerializer.writePoint(pAction->GetStartPoint()); + aSerializer.writePoint(pAction->GetEndPoint()); +} + +void SvmWriter::ChordHandler(const MetaChordAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + aSerializer.writePoint(pAction->GetStartPoint()); + aSerializer.writePoint(pAction->GetEndPoint()); +} + +void SvmWriter::PolyLineHandler(const MetaPolyLineAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 3); + + tools::Polygon aSimplePoly; + pAction->GetPolygon().AdaptiveSubdivide(aSimplePoly); + + WritePolygon(mrStream, aSimplePoly); // Version 1 + WriteLineInfo(mrStream, pAction->GetLineInfo()); // Version 2 + + bool bHasPolyFlags = pAction->GetPolygon().HasFlags(); // Version 3 + mrStream.WriteBool(bHasPolyFlags); + if (bHasPolyFlags) + pAction->GetPolygon().Write(mrStream); +} + +void SvmWriter::PolygonHandler(const MetaPolygonAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + + tools::Polygon aSimplePoly; // Version 1 + pAction->GetPolygon().AdaptiveSubdivide(aSimplePoly); + WritePolygon(mrStream, aSimplePoly); + + bool bHasPolyFlags = pAction->GetPolygon().HasFlags(); // Version 2 + mrStream.WriteBool(bHasPolyFlags); + if (bHasPolyFlags) + pAction->GetPolygon().Write(mrStream); +} + +void SvmWriter::PolyPolygonHandler(const MetaPolyPolygonAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + + sal_uInt16 nNumberOfComplexPolygons = 0; + sal_uInt16 i, nPolyCount = pAction->GetPolyPolygon().Count(); + + tools::Polygon aSimplePoly; // Version 1 + mrStream.WriteUInt16(nPolyCount); + for (i = 0; i < nPolyCount; i++) + { + const tools::Polygon& rPoly = pAction->GetPolyPolygon().GetObject(i); + if (rPoly.HasFlags()) + nNumberOfComplexPolygons++; + rPoly.AdaptiveSubdivide(aSimplePoly); + WritePolygon(mrStream, aSimplePoly); + } + + mrStream.WriteUInt16(nNumberOfComplexPolygons); // Version 2 + for (i = 0; nNumberOfComplexPolygons && (i < nPolyCount); i++) + { + const tools::Polygon& rPoly = pAction->GetPolyPolygon().GetObject(i); + if (rPoly.HasFlags()) + { + mrStream.WriteUInt16(i); + rPoly.Write(mrStream); + + nNumberOfComplexPolygons--; + } + } +} + +void SvmWriter::TextHandler(const MetaTextAction* pAction, const ImplMetaWriteData* pData) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet); + mrStream.WriteUInt16(pAction->GetIndex()); + mrStream.WriteUInt16(pAction->GetLen()); + + write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2 +} + +void SvmWriter::TextArrayHandler(const MetaTextArrayAction* pAction, const ImplMetaWriteData* pData) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + const std::vector<sal_Int32>& rDXArray = pAction->GetDXArray(); + + const sal_Int32 nAryLen = !rDXArray.empty() ? pAction->GetLen() : 0; + + VersionCompatWrite aCompat(mrStream, 2); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet); + mrStream.WriteUInt16(pAction->GetIndex()); + mrStream.WriteUInt16(pAction->GetLen()); + mrStream.WriteInt32(nAryLen); + + for (sal_Int32 i = 0; i < nAryLen; ++i) + mrStream.WriteInt32(rDXArray[i]); + + write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2 +} + +void SvmWriter::StretchTextHandler(const MetaStretchTextAction* pAction, + const ImplMetaWriteData* pData) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet); + mrStream.WriteUInt32(pAction->GetWidth()); + mrStream.WriteUInt16(pAction->GetIndex()); + mrStream.WriteUInt16(pAction->GetLen()); + + write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2 +} + +void SvmWriter::TextRectHandler(const MetaTextRectAction* pAction, const ImplMetaWriteData* pData) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet); + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetStyle())); + + write_uInt16_lenPrefixed_uInt16s_FromOUString(mrStream, pAction->GetText()); // version 2 +} + +void SvmWriter::TextLineHandler(const MetaTextLineAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + + VersionCompatWrite aCompat(mrStream, 2); + + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetStartPoint()); + + mrStream.WriteInt32(pAction->GetWidth()); + mrStream.WriteUInt32(pAction->GetStrikeout()); + mrStream.WriteUInt32(pAction->GetUnderline()); + // new in version 2 + mrStream.WriteUInt32(pAction->GetOverline()); +} + +void SvmWriter::BmpHandler(const MetaBmpAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + } +} + +void SvmWriter::BmpScaleHandler(const MetaBmpScaleAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + aSerializer.writeSize(pAction->GetSize()); + } +} + +void SvmWriter::BmpScalePartHandler(const MetaBmpScalePartAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetDestPoint()); + aSerializer.writeSize(pAction->GetDestSize()); + aSerializer.writePoint(pAction->GetSrcPoint()); + aSerializer.writeSize(pAction->GetSrcSize()); + } +} + +void SvmWriter::BmpExHandler(const MetaBmpExAction* pAction) +{ + if (!pAction->GetBitmapEx().GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + } +} + +void SvmWriter::BmpExScaleHandler(const MetaBmpExScaleAction* pAction) +{ + if (!pAction->GetBitmapEx().GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + aSerializer.writeSize(pAction->GetSize()); + } +} + +void SvmWriter::BmpExScalePartHandler(const MetaBmpExScalePartAction* pAction) +{ + if (!pAction->GetBitmapEx().GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIBBitmapEx(pAction->GetBitmapEx(), mrStream); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetDestPoint()); + aSerializer.writeSize(pAction->GetDestSize()); + aSerializer.writePoint(pAction->GetSrcPoint()); + aSerializer.writeSize(pAction->GetSrcSize()); + } +} + +void SvmWriter::MaskHandler(const MetaMaskAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + } +} + +void SvmWriter::MaskScaleHandler(const MetaMaskScaleAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + aSerializer.writeSize(pAction->GetSize()); + } +} + +void SvmWriter::MaskScalePartHandler(const MetaMaskScalePartAction* pAction) +{ + if (!pAction->GetBitmap().IsEmpty()) + { + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteDIB(pAction->GetBitmap(), mrStream, false, true); + WriteColor(pAction->GetColor()); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetDestPoint()); + aSerializer.writeSize(pAction->GetDestSize()); + aSerializer.writePoint(pAction->GetSrcPoint()); + aSerializer.writeSize(pAction->GetSrcSize()); + } +} + +void SvmWriter::GradientHandler(const MetaGradientAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); + aSerializer.writeGradient(pAction->GetGradient()); +} + +void SvmWriter::GradientExHandler(const MetaGradientExAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + // #i105373# see comment at MetaTransparentAction::Write + tools::PolyPolygon aNoCurvePolyPolygon; + pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon); + + WritePolyPolygon(mrStream, aNoCurvePolyPolygon); + TypeSerializer aSerializer(mrStream); + aSerializer.writeGradient(pAction->GetGradient()); +} + +void SvmWriter::HatchHandler(const MetaHatchAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + // #i105373# see comment at MetaTransparentAction::Write + tools::PolyPolygon aNoCurvePolyPolygon; + pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon); + + WritePolyPolygon(mrStream, aNoCurvePolyPolygon); + WriteHatch(mrStream, pAction->GetHatch()); +} + +void SvmWriter::WallpaperHandler(const MetaWallpaperAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + WriteWallpaper(mrStream, pAction->GetWallpaper()); +} + +void SvmWriter::ClipRegionHandler(const MetaClipRegionAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteRegion(mrStream, pAction->GetRegion()); + mrStream.WriteBool(pAction->IsClipping()); +} + +void SvmWriter::ISectRectClipRegionHandler(const MetaISectRectClipRegionAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeRectangle(pAction->GetRect()); +} + +void SvmWriter::ISectRegionClipRegionHandler(const MetaISectRegionClipRegionAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteRegion(mrStream, pAction->GetRegion()); +} + +void SvmWriter::MoveClipRegionHandler(const MetaMoveClipRegionAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteInt32(pAction->GetHorzMove()).WriteInt32(pAction->GetVertMove()); +} + +void SvmWriter::LineColorHandler(const MetaLineColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::FillColorHandler(const MetaFillColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::TextColorHandler(const MetaTextColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); +} + +void SvmWriter::TextFillColorHandler(const MetaTextFillColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::TextLineColorHandler(const MetaTextLineColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::OverlineColorHandler(const MetaOverlineColorAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteColor(pAction->GetColor()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::TextAlignHandler(const MetaTextAlignAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetTextAlign())); +} + +void SvmWriter::MapModeHandler(const MetaMapModeAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + TypeSerializer aSerializer(mrStream); + aSerializer.writeMapMode(pAction->GetMapMode()); +} + +void SvmWriter::FontHandler(const MetaFontAction* pAction, ImplMetaWriteData* pData) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + WriteFont(mrStream, pAction->GetFont()); + pData->meActualCharSet = pAction->GetFont().GetCharSet(); + if (pData->meActualCharSet == RTL_TEXTENCODING_DONTKNOW) + pData->meActualCharSet = osl_getThreadTextEncoding(); +} + +void SvmWriter::PushHandler(const MetaPushAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetFlags())); +} + +void SvmWriter::PopHandler(const MetaPopAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); +} + +void SvmWriter::RasterOpHandler(const MetaRasterOpAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetRasterOp())); +} + +void SvmWriter::TransparentHandler(const MetaTransparentAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + // #i105373# The tools::PolyPolygon in this action may be a curve; this + // was ignored until now what is an error. To make older office + // versions work with MetaFiles, i opt for applying AdaptiveSubdivide + // to the PolyPolygon. + // The alternative would be to really write the curve information + // like in MetaPolyPolygonAction::Write (where someone extended it + // correctly, but not here :-( ). + // The golden solution would be to combine both, but i think it's + // not necessary; a good subdivision will be sufficient. + tools::PolyPolygon aNoCurvePolyPolygon; + pAction->GetPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon); + + WritePolyPolygon(mrStream, aNoCurvePolyPolygon); + mrStream.WriteUInt16(pAction->GetTransparence()); +} + +void SvmWriter::FloatTransparentHandler(const MetaFloatTransparentAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + SvmWriter aWriter(mrStream); + GDIMetaFile aMtf = pAction->GetGDIMetaFile(); + aWriter.Write(aMtf); + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetPoint()); + aSerializer.writeSize(pAction->GetSize()); + aSerializer.writeGradient(pAction->GetGradient()); +} + +void SvmWriter::EPSHandler(const MetaEPSAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + TypeSerializer aSerializer(mrStream); + aSerializer.writeGfxLink(pAction->GetLink()); + aSerializer.writePoint(pAction->GetPoint()); + aSerializer.writeSize(pAction->GetSize()); + + SvmWriter aWriter(mrStream); + GDIMetaFile aMtf = pAction->GetSubstitute(); + aWriter.Write(aMtf); +} + +void SvmWriter::RefPointHandler(const MetaRefPointAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + + TypeSerializer aSerializer(mrStream); + aSerializer.writePoint(pAction->GetRefPoint()); + mrStream.WriteBool(pAction->IsSetting()); +} + +void SvmWriter::CommentHandler(const MetaCommentAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + write_uInt16_lenPrefixed_uInt8s_FromOString(mrStream, pAction->GetComment()); + mrStream.WriteInt32(pAction->GetValue()).WriteUInt32(pAction->GetDataSize()); + + if (pAction->GetDataSize()) + mrStream.WriteBytes(pAction->GetData(), pAction->GetDataSize()); +} + +void SvmWriter::LayoutModeHandler(const MetaLayoutModeAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteUInt32(static_cast<sal_uInt32>(pAction->GetLayoutMode())); +} + +void SvmWriter::TextLanguageHandler(const MetaTextLanguageAction* pAction) +{ + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType())); + VersionCompatWrite aCompat(mrStream, 1); + mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetTextLanguage())); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/webp/reader.cxx b/vcl/source/filter/webp/reader.cxx new file mode 100644 index 000000000..3c0b1399c --- /dev/null +++ b/vcl/source/filter/webp/reader.cxx @@ -0,0 +1,318 @@ +/* -*- 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 <memory> +#include <vcl/graph.hxx> +#include <tools/stream.hxx> +#include <filter/WebpReader.hxx> +#include <bitmap/BitmapWriteAccess.hxx> +#include <salinst.hxx> +#include <sal/log.hxx> +#include <unotools/configmgr.hxx> +#include <svdata.hxx> +#include <comphelper/scopeguard.hxx> + +#include <webp/decode.h> + +static bool readWebpInfo(SvStream& stream, std::vector<uint8_t>& data, + WebPBitstreamFeatures& features) +{ + for (;;) + { + // Read 4096 (more) bytes. + size_t lastSize = data.size(); + data.resize(data.size() + 4096); + sal_Size nBytesRead = stream.ReadBytes(data.data() + lastSize, 4096); + if (nBytesRead <= 0) + return false; + data.resize(lastSize + nBytesRead); + int status = WebPGetFeatures(data.data(), data.size(), &features); + if (status == VP8_STATUS_OK) + break; + if (status == VP8_STATUS_NOT_ENOUGH_DATA) + continue; // Try again with 4096 more bytes read. + return false; + } + return true; +} + +static bool readWebp(SvStream& stream, Graphic& graphic) +{ + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) + { + SAL_WARN("vcl.filter.webp", "WebPInitDecoderConfig() failed"); + return false; + } + comphelper::ScopeGuard freeBuffer([&config]() { WebPFreeDecBuffer(&config.output); }); + std::vector<uint8_t> data; + if (!readWebpInfo(stream, data, config.input)) + return false; + // Here various parts of 'config' can be altered if wanted. + const int& width = config.input.width; + const int& height = config.input.height; + const int& has_alpha = config.input.has_alpha; + + if (width > SAL_MAX_INT32 / 8 || height > SAL_MAX_INT32 / 8) + return false; // avoid overflows later + + const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32(); + + Bitmap bitmap; + AlphaMask bitmapAlpha; + if (bSupportsBitmap32 && has_alpha) + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + } + else + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + if (has_alpha) + bitmapAlpha = AlphaMask(Size(width, height)); + } + + BitmapScopedWriteAccess access(bitmap); + if (!access) + return false; + // If data cannot be read directly into the bitmap, read data first to this buffer and then convert. + std::vector<uint8_t> tmpRgbaData; + enum class PixelMode + { + DirectRead, // read data directly to the bitmap + Split, // read to tmp buffer and split to rgb and alpha + SetPixel // read to tmp buffer and use setPixel() + }; + PixelMode pixelMode = PixelMode::SetPixel; + + config.output.width = width; + config.output.height = height; + config.output.is_external_memory = 1; + if (bSupportsBitmap32 && has_alpha) + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + // Our bitmap32 code expects premultiplied. + case ScanlineFormat::N32BitTcRgba: + config.output.colorspace = MODE_rgbA; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N32BitTcBgra: + config.output.colorspace = MODE_bgrA; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N32BitTcArgb: + config.output.colorspace = MODE_Argb; + pixelMode = PixelMode::DirectRead; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + else + { + if (has_alpha) + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + case ScanlineFormat::N24BitTcRgb: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::Split; + break; + case ScanlineFormat::N24BitTcBgr: + config.output.colorspace = MODE_BGRA; + pixelMode = PixelMode::Split; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + else + { + switch (RemoveScanline(access->GetScanlineFormat())) + { + case ScanlineFormat::N24BitTcRgb: + config.output.colorspace = MODE_RGB; + pixelMode = PixelMode::DirectRead; + break; + case ScanlineFormat::N24BitTcBgr: + config.output.colorspace = MODE_BGR; + pixelMode = PixelMode::DirectRead; + break; + default: + config.output.colorspace = MODE_RGBA; + pixelMode = PixelMode::SetPixel; + break; + } + } + } + if (pixelMode == PixelMode::DirectRead) + { + config.output.u.RGBA.rgba = access->GetBuffer(); + config.output.u.RGBA.stride = access->GetScanlineSize(); + config.output.u.RGBA.size = access->GetScanlineSize() * access->Height(); + } + else + { + tmpRgbaData.resize(width * height * 4); + config.output.u.RGBA.rgba = tmpRgbaData.data(); + config.output.u.RGBA.stride = width * 4; + config.output.u.RGBA.size = tmpRgbaData.size(); + } + + std::unique_ptr<WebPIDecoder, decltype(&WebPIDelete)> decoder(WebPIDecode(nullptr, 0, &config), + WebPIDelete); + + bool success = true; + for (;;) + { + // During first iteration, use data read while reading the header. + int status = WebPIAppend(decoder.get(), data.data(), data.size()); + if (status == VP8_STATUS_OK) + break; + if (status != VP8_STATUS_SUSPENDED) + { + // An error, still try to return at least a partially read bitmap, + // even if returning an error flag. + success = false; + break; + } + // If more data is needed, reading 4096 bytes more and repeat. + data.resize(4096); + sal_Size nBytesRead = stream.ReadBytes(data.data(), 4096); + if (nBytesRead <= 0) + { + // Truncated file, again try to return at least something. + success = false; + break; + } + data.resize(nBytesRead); + } + + switch (pixelMode) + { + case PixelMode::DirectRead: + { + // Adjust for IsBottomUp() if necessary. + if (access->IsBottomUp()) + { + std::vector<char> tmp; + const sal_uInt32 lineSize = access->GetScanlineSize(); + tmp.resize(lineSize); + for (tools::Long y = 0; y < access->Height() / 2; ++y) + { + tools::Long otherY = access->Height() - 1 - y; + memcpy(tmp.data(), access->GetScanline(y), lineSize); + memcpy(access->GetScanline(y), access->GetScanline(otherY), lineSize); + memcpy(access->GetScanline(otherY), tmp.data(), lineSize); + } + } + break; + } + case PixelMode::Split: + { + // Split to normal and alpha bitmaps. + AlphaScopedWriteAccess accessAlpha(bitmapAlpha); + for (tools::Long y = 0; y < access->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + unsigned char* dstB = access->GetScanline(y); + unsigned char* dstA = accessAlpha->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + memcpy(dstB, src, 3); + *dstA = 255 - *(src + 3); + src += 4; + dstB += 3; + dstA += 1; + } + } + break; + } + case PixelMode::SetPixel: + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + for (tools::Long x = 0; x < access->Width(); ++x) + { + sal_uInt8 r = src[0]; + sal_uInt8 g = src[1]; + sal_uInt8 b = src[2]; + sal_uInt8 a = src[3]; + access->SetPixel(y, x, Color(ColorAlpha, a, r, g, b)); + src += 4; + } + } + if (!bitmapAlpha.IsEmpty()) + { + AlphaScopedWriteAccess accessAlpha(bitmapAlpha); + for (tools::Long y = 0; y < accessAlpha->Height(); ++y) + { + const unsigned char* src = tmpRgbaData.data() + width * 4 * y; + for (tools::Long x = 0; x < accessAlpha->Width(); ++x) + { + sal_uInt8 a = src[3]; + accessAlpha->SetPixelIndex(y, x, 255 - a); + src += 4; + } + } + } + break; + } + } + + access.reset(); // Flush BitmapScopedWriteAccess. + if (bSupportsBitmap32 && has_alpha) + graphic = BitmapEx(bitmap); + else + { + if (has_alpha) + graphic = BitmapEx(bitmap, bitmapAlpha); + else + graphic = BitmapEx(bitmap); + } + return success; +} + +bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic) +{ + bool bRetValue = readWebp(rStream, rGraphic); + if (!bRetValue) + rStream.SetError(SVSTREAM_FILEFORMAT_ERROR); + return bRetValue; +} + +bool ReadWebpInfo(SvStream& stream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha) +{ + std::vector<uint8_t> data; + WebPBitstreamFeatures features; + if (!readWebpInfo(stream, data, features)) + return false; + pixelSize = Size(features.width, features.height); + bitsPerPixel = features.has_alpha ? 32 : 24; + hasAlpha = features.has_alpha; + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/webp/writer.cxx b/vcl/source/filter/webp/writer.cxx new file mode 100644 index 000000000..f29dad007 --- /dev/null +++ b/vcl/source/filter/webp/writer.cxx @@ -0,0 +1,205 @@ +/* -*- 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 <vcl/graph.hxx> +#include <tools/stream.hxx> +#include <filter/WebpWriter.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <comphelper/scopeguard.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <webp/encode.h> + +static int writerFunction(const uint8_t* data, size_t size, const WebPPicture* picture) +{ + SvStream* stream = static_cast<SvStream*>(picture->custom_ptr); + return stream->WriteBytes(data, size) == size ? 1 : 0; +} + +static WebPPreset presetToValue(std::u16string_view preset) +{ + if (o3tl::equalsIgnoreAsciiCase(preset, u"picture")) + return WEBP_PRESET_PICTURE; + if (o3tl::equalsIgnoreAsciiCase(preset, u"photo")) + return WEBP_PRESET_PHOTO; + if (o3tl::equalsIgnoreAsciiCase(preset, u"drawing")) + return WEBP_PRESET_DRAWING; + if (o3tl::equalsIgnoreAsciiCase(preset, u"icon")) + return WEBP_PRESET_ICON; + if (o3tl::equalsIgnoreAsciiCase(preset, u"text")) + return WEBP_PRESET_TEXT; + return WEBP_PRESET_DEFAULT; +} + +static bool writeWebp(SvStream& rStream, const BitmapEx& bitmapEx, bool lossless, + std::u16string_view preset, int quality) +{ + WebPConfig config; + if (!WebPConfigInit(&config)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigInit() failed"); + return false; + } + if (lossless) + { + if (!WebPConfigLosslessPreset(&config, 6)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigLosslessPreset() failed"); + return false; + } + } + else + { + if (!WebPConfigPreset(&config, presetToValue(preset), quality)) + { + SAL_WARN("vcl.filter.webp", "WebPConfigPreset() failed"); + return false; + } + } + // Here various parts of 'config' can be altered if wanted. + assert(WebPValidateConfig(&config)); + + const int width = bitmapEx.GetSizePixel().Width(); + const int height = bitmapEx.GetSizePixel().Height(); + + WebPPicture picture; + if (!WebPPictureInit(&picture)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureInit() failed"); + return false; + } + picture.width = width; + picture.height = height; + picture.use_argb = lossless ? 1 : 0; // libwebp recommends argb only for lossless + comphelper::ScopeGuard freePicture([&picture]() { WebPPictureFree(&picture); }); + + // Apparently libwebp needs the entire image data at once in WebPPicture, + // so allocate it and copy there. + Bitmap bitmap(bitmapEx.GetBitmap()); + AlphaMask bitmapAlpha; + if (bitmapEx.IsAlpha()) + bitmapAlpha = bitmapEx.GetAlpha(); + Bitmap::ScopedReadAccess access(bitmap); + AlphaMask::ScopedReadAccess accessAlpha(bitmapAlpha); + bool dataDone = false; + if (!access->IsBottomUp() && bitmapAlpha.IsEmpty()) + { + // Try to directly copy the bitmap data. + switch (access->GetScanlineFormat()) + { + case ScanlineFormat::N24BitTcRgb: + if (!WebPPictureImportRGB(&picture, access->GetBuffer(), access->GetScanlineSize())) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportRGB() failed"); + return false; + } + dataDone = true; + break; + case ScanlineFormat::N24BitTcBgr: + if (!WebPPictureImportBGR(&picture, access->GetBuffer(), access->GetScanlineSize())) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportBGR() failed"); + return false; + } + dataDone = true; + break; + // Our argb formats are premultiplied, so can't read directly using libwebp functions. + default: + break; + } + } + if (!dataDone) + { + // It would be simpler to convert the bitmap to 32bpp, but our 32bpp support is broken + // (it's unspecified whether it's premultiplied or not, for example). So handle this manually. + // Special handling for some common cases, generic otherwise. + if (!WebPPictureAlloc(&picture)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureAlloc() failed"); + return false; + } + std::vector<uint8_t> data; + const int bpp = 4; + data.resize(width * height * bpp); + if (!bitmapAlpha.IsEmpty()) + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + unsigned char* dst = data.data() + width * bpp * y; + const sal_uInt8* srcB = access->GetScanline(y); + const sal_uInt8* srcA = accessAlpha->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + BitmapColor color = access->GetPixelFromData(srcB, x); + BitmapColor transparency = accessAlpha->GetPixelFromData(srcA, x); + color.SetAlpha(255 - transparency.GetIndex()); + dst[0] = color.GetRed(); + dst[1] = color.GetGreen(); + dst[2] = color.GetBlue(); + dst[3] = color.GetAlpha(); + dst += bpp; + } + } + } + else + { + for (tools::Long y = 0; y < access->Height(); ++y) + { + unsigned char* dst = data.data() + width * bpp * y; + const sal_uInt8* src = access->GetScanline(y); + for (tools::Long x = 0; x < access->Width(); ++x) + { + Color color = access->GetPixelFromData(src, x); + dst[0] = color.GetRed(); + dst[1] = color.GetGreen(); + dst[2] = color.GetBlue(); + dst[3] = color.GetAlpha(); + dst += bpp; + } + } + } + // And now import from the temporary data. Use the import function rather + // than writing the data directly to avoid the need to write the data + // in the exact format WebPPicture wants (YUV, etc.). + if (!WebPPictureImportRGBA(&picture, data.data(), width * bpp)) + { + SAL_WARN("vcl.filter.webp", "WebPPictureImportRGBA() failed"); + return false; + } + } + + picture.writer = writerFunction; + picture.custom_ptr = &rStream; + return WebPEncode(&config, &picture); +} + +bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic, + FilterConfigItem* pFilterConfigItem) +{ + BitmapEx bitmap = rGraphic.GetBitmapEx(); + // If lossless, neither presets nor quality matter. + bool lossless = pFilterConfigItem->ReadBool("Lossless", true); + // Preset is WebPPreset values. + const OUString preset = pFilterConfigItem->ReadString("Preset", ""); + int quality = pFilterConfigItem->ReadInt32("Quality", 75); + return writeWebp(rStream, bitmap, lossless, preset, quality); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/emfwr.cxx b/vcl/source/filter/wmf/emfwr.cxx new file mode 100644 index 000000000..b2782847b --- /dev/null +++ b/vcl/source/filter/wmf/emfwr.cxx @@ -0,0 +1,1511 @@ +/* -*- 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 <sal/log.hxx> + +#include <algorithm> + +#include "emfwr.hxx" +#include <tools/helpers.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/metaact.hxx> +#include <memory> + +#define WIN_EMR_POLYGON 3 +#define WIN_EMR_POLYLINE 4 +#define WIN_EMR_POLYBEZIERTO 5 +#define WIN_EMR_POLYLINETO 6 +#define WIN_EMR_POLYPOLYGON 8 +#define WIN_EMR_SETWINDOWEXTEX 9 +#define WIN_EMR_SETWINDOWORGEX 10 +#define WIN_EMR_SETVIEWPORTEXTEX 11 +#define WIN_EMR_SETVIEWPORTORGEX 12 +#define WIN_EMR_EOF 14 +#define WIN_EMR_SETPIXELV 15 +#define WIN_EMR_SETMAPMODE 17 +#define WIN_EMR_SETBKMODE 18 +#define WIN_EMR_SETROP2 20 +#define WIN_EMR_SETTEXTALIGN 22 +#define WIN_EMR_SETTEXTCOLOR 24 +#define WIN_EMR_MOVETOEX 27 +#define WIN_EMR_INTERSECTCLIPRECT 30 +#define WIN_EMR_SAVEDC 33 +#define WIN_EMR_RESTOREDC 34 +#define WIN_EMR_SELECTOBJECT 37 +#define WIN_EMR_CREATEPEN 38 +#define WIN_EMR_CREATEBRUSHINDIRECT 39 +#define WIN_EMR_DELETEOBJECT 40 +#define WIN_EMR_ELLIPSE 42 +#define WIN_EMR_RECTANGLE 43 +#define WIN_EMR_ROUNDRECT 44 +#define WIN_EMR_LINETO 54 +#define WIN_EMR_BEGINPATH 59 +#define WIN_EMR_ENDPATH 60 +#define WIN_EMR_CLOSEFIGURE 61 +#define WIN_EMR_FILLPATH 62 +#define WIN_EMR_STROKEPATH 64 + +#define WIN_EMR_GDICOMMENT 70 +#define WIN_EMR_STRETCHDIBITS 81 +#define WIN_EMR_EXTCREATEFONTINDIRECTW 82 +#define WIN_EMR_EXTTEXTOUTW 84 + +#define WIN_SRCCOPY 0x00CC0020L +#define WIN_SRCPAINT 0x00EE0086L +#define WIN_SRCAND 0x008800C6L +#define WIN_SRCINVERT 0x00660046L +#define WIN_EMR_COMMENT_EMFPLUS 0x2B464D45L + +#define HANDLE_INVALID 0xffffffff +#define MAXHANDLES 65000 + +#define LINE_SELECT 0x00000001 +#define FILL_SELECT 0x00000002 +#define TEXT_SELECT 0x00000004 + +/* Text Alignment Options */ +#define TA_RIGHT 2 + +#define TA_TOP 0 +#define TA_BOTTOM 8 +#define TA_BASELINE 24 +#define TA_RTLREADING 256 + +#define MM_ANISOTROPIC 8 + +enum class EmfPlusRecordType +{ + Header = 0x4001, + EndOfFile = 0x4002, + GetDC = 0x4004, + FillPolygon = 0x400C, + SetAntiAliasMode = 0x401E, + SetInterpolationMode = 0x4021, + SetPixelOffsetMode = 0x4022, + SetCompositingQuality = 0x4024 +}; + +void EMFWriter::ImplBeginCommentRecord( sal_Int32 nCommentType ) +{ + ImplBeginRecord( WIN_EMR_GDICOMMENT ); + m_rStm.SeekRel( 4 ); + m_rStm.WriteInt32( nCommentType ); +} + +void EMFWriter::ImplEndCommentRecord() +{ + if( mbRecordOpen ) + { + sal_Int32 nActPos = m_rStm.Tell(); + m_rStm.Seek( mnRecordPos + 8 ); + m_rStm.WriteUInt32( nActPos - mnRecordPos - 0xc ); + m_rStm.Seek( nActPos ); + } + ImplEndRecord(); +} + +void EMFWriter::ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags ) +{ + SAL_WARN_IF( mbRecordPlusOpen, "vcl", "Another EMF+ record is already opened!" ); + + if( !mbRecordPlusOpen ) + { + mbRecordPlusOpen = true; + mnRecordPlusPos = m_rStm.Tell(); + + m_rStm.WriteUInt16( static_cast<sal_uInt16>(nType) ).WriteUInt16( nFlags ); + m_rStm.SeekRel( 8 ); + } +} + +void EMFWriter::ImplEndPlusRecord() +{ + SAL_WARN_IF( !mbRecordPlusOpen, "vcl", "EMF+ Record was not opened!" ); + + if( mbRecordPlusOpen ) + { + sal_Int32 nActPos = m_rStm.Tell(); + sal_Int32 nSize = nActPos - mnRecordPlusPos; + m_rStm.Seek( mnRecordPlusPos + 4 ); + m_rStm.WriteUInt32( nSize ) // Size + .WriteUInt32( nSize - 0xc ); // Data Size + m_rStm.Seek( nActPos ); + mbRecordPlusOpen = false; + } +} + +void EMFWriter::ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags ) +{ + ImplBeginPlusRecord( nType, nFlags ); + ImplEndPlusRecord(); +} + +void EMFWriter::WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog ) +{ + ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS ); + + sal_Int32 nDPIX = rMtfSizePix.Width()*25; + sal_Int32 nDivX = rMtfSizeLog.Width()/100; + if (nDivX) + nDPIX /= nDivX; // DPI X + + sal_Int32 nDPIY = rMtfSizePix.Height()*25; + sal_Int32 nDivY = rMtfSizeLog.Height()/100; + if (nDivY) + nDPIY /= nDivY; // DPI Y + + m_rStm.WriteInt16( sal_Int16(EmfPlusRecordType::Header) ); + m_rStm.WriteInt16( 0x01 ) // Flags - Dual Mode // TODO: Check this + .WriteInt32( 0x1C ) // Size + .WriteInt32( 0x10 ) // Data Size + .WriteInt32( 0xdbc01002 ) // (lower 12bits) 1-> v1 2-> v1.1 // TODO: Check this + .WriteInt32( 0x01 ) // Video display + .WriteInt32( nDPIX ) + .WriteInt32( nDPIY ); + ImplEndCommentRecord(); + + // Write more properties + ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS ); + ImplPlusRecord( EmfPlusRecordType::SetPixelOffsetMode, 0x0 ); + ImplPlusRecord( EmfPlusRecordType::SetAntiAliasMode, 0x09 ); // TODO: Check actual values for AntiAlias + ImplPlusRecord( EmfPlusRecordType::SetCompositingQuality, 0x0100 ); // Default Quality + ImplPlusRecord( EmfPlusRecordType::SetInterpolationMode, 0x00 ); // Default + ImplPlusRecord( EmfPlusRecordType::GetDC, 0x00 ); + ImplEndCommentRecord(); +} + +void EMFWriter::ImplWritePlusEOF() +{ + ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS ); + ImplPlusRecord( EmfPlusRecordType::EndOfFile, 0x0 ); + ImplEndCommentRecord(); +} + +void EMFWriter::ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans ) +{ + sal_uInt32 nAlpha = ((100-nTrans)*0xFF)/100; + sal_uInt32 nCol = rColor.GetBlue(); + + nCol |= static_cast<sal_uInt32>(rColor.GetGreen()) << 8; + nCol |= static_cast<sal_uInt32>(rColor.GetRed()) << 16; + nCol |= ( nAlpha << 24 ); + m_rStm.WriteUInt32( nCol ); +} + +void EMFWriter::ImplWritePlusPoint( const Point& rPoint ) +{ + // Convert to pixels + const Point aPoint(maVDev->LogicToPixel( rPoint, maDestMapMode )); + m_rStm.WriteUInt16( aPoint.X() ).WriteUInt16( aPoint.Y() ); +} + +void EMFWriter::ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans ) +{ + ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS ); + if( rPoly.GetSize() ) + { + ImplBeginPlusRecord( EmfPlusRecordType::FillPolygon, 0xC000 ); // Sets the color as well + ImplWritePlusColor( maVDev->GetFillColor(), nTrans ); + m_rStm.WriteUInt32( rPoly.GetSize() ); + for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ ) + ImplWritePlusPoint( rPoly[ i ] ); + ImplEndPlusRecord(); + } + ImplEndCommentRecord(); +} + +bool EMFWriter::WriteEMF(const GDIMetaFile& rMtf) +{ + const sal_uInt64 nHeaderPos = m_rStm.Tell(); + + maVDev->EnableOutput( false ); + maVDev->SetMapMode( rMtf.GetPrefMapMode() ); + // don't work with pixel as destination map mode -> higher resolution preferable + maDestMapMode.SetMapUnit( MapUnit::Map100thMM ); + mHandlesUsed = std::vector<bool>(MAXHANDLES, false); + mnHandleCount = mnRecordCount = mnRecordPos = mnRecordPlusPos = 0; + mbRecordOpen = mbRecordPlusOpen = false; + mbLineChanged = mbFillChanged = mbTextChanged = false; + mnLineHandle = mnFillHandle = mnTextHandle = HANDLE_INVALID; + mnHorTextAlign = 0; + + const Size aMtfSizePix( maVDev->LogicToPixel( rMtf.GetPrefSize(), rMtf.GetPrefMapMode() ) ); + const Size aMtfSizeLog( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) ); + + // seek over header + // use [MS-EMF 2.2.11] HeaderExtension2 Object, otherwise resulting EMF cannot be converted with GetWinMetaFileBits() + m_rStm.SeekRel( 108 ); + + // Write EMF+ Header + WriteEMFPlusHeader( aMtfSizePix, aMtfSizeLog ); + + // write initial values + + // set 100th mm map mode in EMF + ImplBeginRecord( WIN_EMR_SETMAPMODE ); + m_rStm.WriteInt32( MM_ANISOTROPIC ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SETVIEWPORTEXTEX ); + m_rStm.WriteInt32( maVDev->GetDPIX() ).WriteInt32( maVDev->GetDPIY() ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SETWINDOWEXTEX ); + m_rStm.WriteInt32( 2540 ).WriteInt32( 2540 ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SETVIEWPORTORGEX ); + m_rStm.WriteInt32( 0 ).WriteInt32( 0 ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SETWINDOWORGEX ); + m_rStm.WriteInt32( 0 ).WriteInt32( 0 ); + ImplEndRecord(); + + ImplWriteRasterOp( RasterOp::OverPaint ); + + ImplBeginRecord( WIN_EMR_SETBKMODE ); + m_rStm.WriteUInt32( 1 ); // TRANSPARENT + ImplEndRecord(); + + // write emf data + ImplWrite( rMtf ); + + ImplWritePlusEOF(); + + ImplBeginRecord( WIN_EMR_EOF ); + m_rStm.WriteUInt32( 0 ) // nPalEntries + .WriteUInt32( 0x10 ) // offPalEntries + .WriteUInt32( 0x14 ); // nSizeLast + ImplEndRecord(); + + // write header + const sal_uInt64 nEndPos = m_rStm.Tell(); m_rStm.Seek( nHeaderPos ); + + m_rStm.WriteUInt32( 0x00000001 ).WriteUInt32( 108 ) //use [MS-EMF 2.2.11] HeaderExtension2 Object + .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizePix.Width() - 1 ).WriteInt32( aMtfSizePix.Height() - 1 ) + .WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aMtfSizeLog.Width() - 1 ).WriteInt32( aMtfSizeLog.Height() - 1 ) + .WriteUInt32( 0x464d4520 ).WriteUInt32( 0x10000 ).WriteUInt32( nEndPos - nHeaderPos ) + .WriteUInt32( mnRecordCount ).WriteUInt16( mnHandleCount + 1 ).WriteUInt16( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ) + .WriteInt32( aMtfSizePix.Width() ).WriteInt32( aMtfSizePix.Height() ) + .WriteInt32( aMtfSizeLog.Width() / 100 ).WriteInt32( aMtfSizeLog.Height() / 100 ) + .WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ) + .WriteInt32( aMtfSizeLog.Width() * 10 ).WriteInt32( aMtfSizeLog.Height() * 10 ); //use [MS-EMF 2.2.11] HeaderExtension2 Object + + m_rStm.Seek( nEndPos ); + mHandlesUsed.clear(); + + return( m_rStm.GetError() == ERRCODE_NONE ); +} + +sal_uLong EMFWriter::ImplAcquireHandle() +{ + sal_uLong nHandle = HANDLE_INVALID; + + for( sal_uLong i = 0; i < mHandlesUsed.size() && ( HANDLE_INVALID == nHandle ); i++ ) + { + if( !mHandlesUsed[ i ] ) + { + mHandlesUsed[ i ] = true; + + if( ( nHandle = i ) == mnHandleCount ) + mnHandleCount++; + } + } + + SAL_WARN_IF( nHandle == HANDLE_INVALID, "vcl", "No more handles available" ); + return( nHandle != HANDLE_INVALID ? nHandle + 1 : HANDLE_INVALID ); +} + +void EMFWriter::ImplReleaseHandle( sal_uLong nHandle ) +{ + SAL_WARN_IF( !nHandle || ( nHandle >= mHandlesUsed.size() ), "vcl", "Handle out of range" ); + mHandlesUsed[ nHandle - 1 ] = false; +} + +void EMFWriter::ImplBeginRecord( sal_uInt32 nType ) +{ + SAL_WARN_IF( mbRecordOpen, "vcl", "Another record is already opened!" ); + + if( !mbRecordOpen ) + { + mbRecordOpen = true; + mnRecordPos = m_rStm.Tell(); + + m_rStm.WriteUInt32( nType ); + m_rStm.SeekRel( 4 ); + } +} + +void EMFWriter::ImplEndRecord() +{ + SAL_WARN_IF( !mbRecordOpen, "vcl", "Record was not opened!" ); + + if( !mbRecordOpen ) + return; + + sal_Int32 nFillBytes, nActPos = m_rStm.Tell(); + m_rStm.Seek( mnRecordPos + 4 ); + nFillBytes = nActPos - mnRecordPos; + nFillBytes += 3; // each record has to be dword aligned + nFillBytes ^= 3; + nFillBytes &= 3; + m_rStm.WriteUInt32( ( nActPos - mnRecordPos ) + nFillBytes ); + m_rStm.Seek( nActPos ); + while( nFillBytes-- ) + m_rStm.WriteUChar( 0 ); + mnRecordCount++; + mbRecordOpen = false; + +} + +bool EMFWriter::ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType ) +{ + if( rHandle != HANDLE_INVALID ) + { + sal_uInt32 nStockObject = 0x80000000; + + if( LINE_SELECT == nSelectType ) + nStockObject |= 0x00000007; + else if( FILL_SELECT == nSelectType ) + nStockObject |= 0x00000001; + else if( TEXT_SELECT == nSelectType ) + nStockObject |= 0x0000000a; + + // select stock object first + ImplBeginRecord( WIN_EMR_SELECTOBJECT ); + m_rStm.WriteUInt32( nStockObject ); + ImplEndRecord(); + + // destroy handle of created object + ImplBeginRecord( WIN_EMR_DELETEOBJECT ); + m_rStm.WriteUInt32( rHandle ); + ImplEndRecord(); + + // mark handle as free + ImplReleaseHandle( rHandle ); + } + + rHandle = ImplAcquireHandle(); + + return( HANDLE_INVALID != rHandle ); +} + +void EMFWriter::ImplCheckLineAttr() +{ + if( !(mbLineChanged && ImplPrepareHandleSelect( mnLineHandle, LINE_SELECT )) ) + return; + + sal_uInt32 nStyle = maVDev->IsLineColor() ? 0 : 5; + + ImplBeginRecord( WIN_EMR_CREATEPEN ); + m_rStm.WriteUInt32( mnLineHandle ).WriteUInt32( nStyle ).WriteUInt32( 0/*nWidth*/ ).WriteUInt32( 0/*nHeight*/ ); + ImplWriteColor( maVDev->GetLineColor() ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SELECTOBJECT ); + m_rStm.WriteUInt32( mnLineHandle ); + ImplEndRecord(); +} + +void EMFWriter::ImplCheckFillAttr() +{ + if( !(mbFillChanged && ImplPrepareHandleSelect( mnFillHandle, FILL_SELECT )) ) + return; + + sal_uInt32 nStyle = maVDev->IsFillColor() ? 0 : 1; + + ImplBeginRecord( WIN_EMR_CREATEBRUSHINDIRECT ); + m_rStm.WriteUInt32( mnFillHandle ).WriteUInt32( nStyle ); + ImplWriteColor( maVDev->GetFillColor() ); + m_rStm.WriteUInt32( 0/*nPatternStyle*/ ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SELECTOBJECT ); + m_rStm.WriteUInt32( mnFillHandle ); + ImplEndRecord(); +} + +void EMFWriter::ImplCheckTextAttr() +{ + if( !(mbTextChanged && ImplPrepareHandleSelect( mnTextHandle, TEXT_SELECT )) ) + return; + + const vcl::Font& rFont = maVDev->GetFont(); + const OUString& aFontName( rFont.GetFamilyName() ); + sal_Int32 nWeight; + sal_uInt16 i; + sal_uInt8 nPitchAndFamily; + + // tdf#127471 adapt nFontWidth from NormedFontScaling to + // Windows-like notation if used for text scaling + const tools::Long nFontHeight(rFont.GetFontSize().Height()); + tools::Long nFontWidth(rFont.GetFontSize().Width()); + +#ifndef _WIN32 + const bool bFontScaledHorizontally(nFontWidth != 0 && nFontWidth != nFontHeight); + + if(bFontScaledHorizontally) + { + // tdf#127471 nFontWidth is the non-Windows NormedFontScaling, need to convert to + // Windows-like notation with pre-multiplied AvgFontWidth since EMF/WMF are Windows + // specific formats. + const tools::Long nAverageFontWidth(rFont.GetOrCalculateAverageFontWidth()); + + if(nFontHeight > 0) + { + const double fScaleFactor(static_cast<double>(nAverageFontWidth) / static_cast<double>(nFontHeight)); + nFontWidth = static_cast<tools::Long>(static_cast<double>(nFontWidth) * fScaleFactor); + } + } +#endif + + ImplBeginRecord( WIN_EMR_EXTCREATEFONTINDIRECTW ); + m_rStm.WriteUInt32( mnTextHandle ); + ImplWriteExtent( -nFontHeight ); + ImplWriteExtent( nFontWidth ); + m_rStm.WriteInt32( rFont.GetOrientation().get() ).WriteInt32( rFont.GetOrientation().get() ); + + switch( rFont.GetWeight() ) + { + case WEIGHT_THIN: nWeight = 100; break; + case WEIGHT_ULTRALIGHT: nWeight = 200; break; + case WEIGHT_LIGHT: nWeight = 300; break; + case WEIGHT_SEMILIGHT: nWeight = 300; break; + case WEIGHT_NORMAL: nWeight = 400; break; + case WEIGHT_MEDIUM: nWeight = 500; break; + case WEIGHT_SEMIBOLD: nWeight = 600; break; + case WEIGHT_BOLD: nWeight = 700; break; + case WEIGHT_ULTRABOLD: nWeight = 800; break; + case WEIGHT_BLACK: nWeight = 900; break; + default: nWeight = 0; break; + } + + m_rStm.WriteInt32( nWeight ); + m_rStm.WriteUChar( ( ITALIC_NONE == rFont.GetItalic() ) ? 0 : 1 ); + m_rStm.WriteUChar( ( LINESTYLE_NONE == rFont.GetUnderline() ) ? 0 : 1 ); + m_rStm.WriteUChar( ( STRIKEOUT_NONE == rFont.GetStrikeout() ) ? 0 : 1 ); + m_rStm.WriteUChar( ( RTL_TEXTENCODING_SYMBOL == rFont.GetCharSet() ) ? 2 : 0 ); + m_rStm.WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ); + + switch( rFont.GetPitch() ) + { + case PITCH_FIXED: nPitchAndFamily = 0x01; break; + case PITCH_VARIABLE: nPitchAndFamily = 0x02; break; + default: nPitchAndFamily = 0x00; break; + } + + switch( rFont.GetFamilyType() ) + { + case FAMILY_DECORATIVE: nPitchAndFamily |= 0x50; break; + case FAMILY_MODERN: nPitchAndFamily |= 0x30; break; + case FAMILY_ROMAN: nPitchAndFamily |= 0x10; break; + case FAMILY_SCRIPT: nPitchAndFamily |= 0x40; break; + case FAMILY_SWISS: nPitchAndFamily |= 0x20; break; + default: break; + } + + m_rStm.WriteUChar( nPitchAndFamily ); + + for( i = 0; i < 32; i++ ) + m_rStm.WriteUInt16( ( i < aFontName.getLength() ) ? aFontName[ i ] : 0 ); + + // dummy elfFullName + for( i = 0; i < 64; i++ ) + m_rStm.WriteUInt16( 0 ); + + // dummy elfStyle + for( i = 0; i < 32; i++ ) + m_rStm.WriteUInt16( 0 ); + + // dummy elfVersion, elfStyleSize, elfMatch, elfReserved + m_rStm.WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ) ; + + // dummy elfVendorId + m_rStm.WriteUInt32( 0 ); + + // dummy elfCulture + m_rStm.WriteUInt32( 0 ); + + // dummy elfPanose + m_rStm.WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ); + + // fill record to get a record size divideable by 4 + m_rStm.WriteUInt16( 0 ); + + ImplEndRecord(); + + // TextAlign + sal_uInt32 nTextAlign; + + switch( rFont.GetAlignment() ) + { + case ALIGN_TOP: nTextAlign = TA_TOP; break; + case ALIGN_BOTTOM: nTextAlign = TA_BOTTOM; break; + default: nTextAlign = TA_BASELINE; break; + } + nTextAlign |= mnHorTextAlign; + + ImplBeginRecord( WIN_EMR_SETTEXTALIGN ); + m_rStm.WriteUInt32( nTextAlign ); + ImplEndRecord(); + + // Text color + ImplBeginRecord( WIN_EMR_SETTEXTCOLOR ); + ImplWriteColor( maVDev->GetTextColor() ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SELECTOBJECT ); + m_rStm.WriteUInt32( mnTextHandle ); + ImplEndRecord(); + +} + +void EMFWriter::ImplWriteColor( const Color& rColor ) +{ + sal_uInt32 nCol = rColor.GetRed(); + + nCol |= static_cast<sal_uInt32>(rColor.GetGreen()) << 8; + nCol |= static_cast<sal_uInt32>(rColor.GetBlue()) << 16; + + m_rStm.WriteUInt32( nCol ); +} + +void EMFWriter::ImplWriteRasterOp( RasterOp eRop ) +{ + sal_uInt32 nROP2; + + switch( eRop ) + { + case RasterOp::Invert: nROP2 = 6; break; + case RasterOp::Xor: nROP2 = 7; break; + default: nROP2 = 13;break; + } + + ImplBeginRecord( WIN_EMR_SETROP2 ); + m_rStm.WriteUInt32( nROP2 ); + ImplEndRecord(); +} + +void EMFWriter::ImplWriteExtent( tools::Long nExtent ) +{ + nExtent = OutputDevice::LogicToLogic( Size( nExtent, 0 ), maVDev->GetMapMode(), maDestMapMode ).Width(); + m_rStm.WriteInt32( nExtent ); +} + +void EMFWriter::ImplWritePoint( const Point& rPoint ) +{ + const Point aPoint( OutputDevice::LogicToLogic( rPoint, maVDev->GetMapMode(), maDestMapMode )); + m_rStm.WriteInt32( aPoint.X() ).WriteInt32( aPoint.Y() ); +} + +void EMFWriter::ImplWriteSize( const Size& rSize) +{ + const Size aSize( OutputDevice::LogicToLogic( rSize, maVDev->GetMapMode(), maDestMapMode )); + m_rStm.WriteInt32( aSize.Width() ).WriteInt32( aSize.Height() ); +} + +void EMFWriter::ImplWriteRect( const tools::Rectangle& rRect ) +{ + const tools::Rectangle aRect( OutputDevice::LogicToLogic ( rRect, maVDev->GetMapMode(), maDestMapMode )); + auto right = aRect.IsWidthEmpty() ? aRect.Left() : aRect.Right(); + auto bottom = aRect.IsHeightEmpty() ? aRect.Top() : aRect.Bottom(); + m_rStm + .WriteInt32( aRect.Left() ) + .WriteInt32( aRect.Top() ) + .WriteInt32( right ) + .WriteInt32( bottom ); +} + +void EMFWriter::ImplWritePolygonRecord( const tools::Polygon& rPoly, bool bClose ) +{ + if( !rPoly.GetSize() ) + return; + + if( rPoly.HasFlags() ) + ImplWritePath( rPoly, bClose ); + else + { + if( bClose ) + ImplCheckFillAttr(); + + ImplCheckLineAttr(); + + ImplBeginRecord( bClose ? WIN_EMR_POLYGON : WIN_EMR_POLYLINE ); + ImplWriteRect( rPoly.GetBoundRect() ); + m_rStm.WriteUInt32( rPoly.GetSize() ); + + for( sal_uInt16 i = 0; i < rPoly.GetSize(); i++ ) + ImplWritePoint( rPoly[ i ] ); + + ImplEndRecord(); + } +} + +void EMFWriter::ImplWritePolyPolygonRecord( const tools::PolyPolygon& rPolyPoly ) +{ + sal_uInt16 n, i, nPolyCount = rPolyPoly.Count(); + + if( !nPolyCount ) + return; + + if( 1 == nPolyCount ) + ImplWritePolygonRecord( rPolyPoly[ 0 ], true ); + else + { + bool bHasFlags = false; + sal_uInt32 nTotalPoints = 0; + + for( i = 0; i < nPolyCount; i++ ) + { + nTotalPoints += rPolyPoly[ i ].GetSize(); + if ( rPolyPoly[ i ].HasFlags() ) + bHasFlags = true; + } + if( nTotalPoints ) + { + if ( bHasFlags ) + ImplWritePath( rPolyPoly, true ); + else + { + ImplCheckFillAttr(); + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_POLYPOLYGON ); + ImplWriteRect( rPolyPoly.GetBoundRect() ); + m_rStm.WriteUInt32( nPolyCount ).WriteUInt32( nTotalPoints ); + + for( i = 0; i < nPolyCount; i++ ) + m_rStm.WriteUInt32( rPolyPoly[ i ].GetSize() ); + + for( i = 0; i < nPolyCount; i++ ) + { + const tools::Polygon& rPoly = rPolyPoly[ i ]; + + for( n = 0; n < rPoly.GetSize(); n++ ) + ImplWritePoint( rPoly[ n ] ); + } + ImplEndRecord(); + } + } + } +} + +void EMFWriter::ImplWritePath( const tools::PolyPolygon& rPolyPoly, bool bClosed ) +{ + if ( bClosed ) + ImplCheckFillAttr(); + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_BEGINPATH ); + ImplEndRecord(); + + sal_uInt16 i, n, o, nPolyCount = rPolyPoly.Count(); + for ( i = 0; i < nPolyCount; i++ ) + { + n = 0; + const tools::Polygon& rPoly = rPolyPoly[ i ]; + while ( n < rPoly.GetSize() ) + { + if( n == 0 ) + { + ImplBeginRecord( WIN_EMR_MOVETOEX ); + ImplWritePoint( rPoly[ 0 ] ); + ImplEndRecord(); + n++; + continue; + } + + sal_uInt16 nBezPoints = 0; + + while ( ( ( nBezPoints + n + 2 ) < rPoly.GetSize() ) && ( rPoly.GetFlags( nBezPoints + n ) == PolyFlags::Control ) ) + nBezPoints += 3; + + if ( nBezPoints ) + { + ImplBeginRecord( WIN_EMR_POLYBEZIERTO ); + tools::Polygon aNewPoly( nBezPoints + 1 ); + aNewPoly[ 0 ] = rPoly[ n - 1 ]; + for ( o = 0; o < nBezPoints; o++ ) + aNewPoly[ o + 1 ] = rPoly[ n + o ]; + ImplWriteRect( aNewPoly.GetBoundRect() ); + m_rStm.WriteUInt32( nBezPoints ); + for( o = 1; o < aNewPoly.GetSize(); o++ ) + ImplWritePoint( aNewPoly[ o ] ); + ImplEndRecord(); + n = n + nBezPoints; + } + else + { + sal_uInt16 nPoints = 1; + while( ( nPoints + n ) < rPoly.GetSize() && ( rPoly.GetFlags( nPoints + n ) != PolyFlags::Control ) ) + nPoints++; + + if ( nPoints > 1 ) + { + ImplBeginRecord( WIN_EMR_POLYLINETO ); + tools::Polygon aNewPoly( nPoints + 1 ); + aNewPoly[ 0 ] = rPoly[ n - 1]; + for ( o = 1; o <= nPoints; o++ ) + aNewPoly[ o ] = rPoly[ n - 1 + o ]; + ImplWriteRect( aNewPoly.GetBoundRect() ); + m_rStm.WriteUInt32( nPoints ); + for( o = 1; o < aNewPoly.GetSize(); o++ ) + ImplWritePoint( aNewPoly[ o ] ); + ImplEndRecord(); + } + else + { + ImplBeginRecord( WIN_EMR_LINETO ); + ImplWritePoint( rPoly[ n ] ); + ImplEndRecord(); + } + n = n + nPoints; + } + if ( bClosed && ( n == rPoly.GetSize() ) ) + { + ImplBeginRecord( WIN_EMR_CLOSEFIGURE ); + ImplEndRecord(); + } + } + } + ImplBeginRecord( WIN_EMR_ENDPATH ); + ImplEndRecord(); + ImplBeginRecord( bClosed ? WIN_EMR_FILLPATH : WIN_EMR_STROKEPATH ); + ImplWriteRect( rPolyPoly.GetBoundRect() ); + ImplEndRecord(); +} + +void EMFWriter::ImplWriteBmpRecord( const Bitmap& rBmp, const Point& rPt, + const Size& rSz, sal_uInt32 nROP ) +{ + if( rBmp.IsEmpty() ) + return; + + SvMemoryStream aMemStm( 65535, 65535 ); + const Size aBmpSizePixel( rBmp.GetSizePixel() ); + + ImplBeginRecord( WIN_EMR_STRETCHDIBITS ); + ImplWriteRect( tools::Rectangle( rPt, rSz ) ); + ImplWritePoint( rPt ); + m_rStm.WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( aBmpSizePixel.Width() ).WriteInt32( aBmpSizePixel.Height() ); + + // write offset positions and sizes later + const sal_uInt64 nOffPos = m_rStm.Tell(); + m_rStm.SeekRel( 16 ); + + m_rStm.WriteUInt32( 0 ).WriteInt32( ( RasterOp::Xor == maVDev->GetRasterOp() && WIN_SRCCOPY == nROP ) ? WIN_SRCINVERT : nROP ); + ImplWriteSize( rSz ); + + WriteDIB(rBmp, aMemStm, true, false); + + sal_uInt32 nDIBSize = aMemStm.Tell(), nHeaderSize, nCompression, nColsUsed, nPalCount, nImageSize; + sal_uInt16 nBitCount; + + // get DIB parameters + aMemStm.Seek( 0 ); + aMemStm.ReadUInt32( nHeaderSize ); + aMemStm.SeekRel( 10 ); + aMemStm.ReadUInt16( nBitCount ).ReadUInt32( nCompression ).ReadUInt32( nImageSize ); + aMemStm.SeekRel( 8 ); + aMemStm.ReadUInt32( nColsUsed ); + + if (nBitCount <= 8) + { + if (nColsUsed) + nPalCount = nColsUsed; + else + nPalCount = 1 << static_cast<sal_uInt32>(nBitCount); + } + else + { + if (nCompression == BITFIELDS) + nPalCount = 3; + else + nPalCount = 0; + } + + sal_uInt32 nPalSize = nPalCount * 4; + + m_rStm.WriteBytes( aMemStm.GetData(), nDIBSize ); + + const sal_uInt64 nEndPos = m_rStm.Tell(); + m_rStm.Seek( nOffPos ); + m_rStm.WriteUInt32( 80 ).WriteUInt32( nHeaderSize + nPalSize ); + m_rStm.WriteUInt32( 80 + nHeaderSize + nPalSize ).WriteUInt32( nImageSize ); + m_rStm.Seek( nEndPos ); + + ImplEndRecord(); + +} + +void EMFWriter::ImplWriteTextRecord( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_uInt32 nWidth ) +{ + sal_Int32 nLen = rText.getLength(), i; + + if( !nLen ) + return; + + sal_uInt32 nNormWidth; + std::vector<sal_Int32> aOwnArray; + o3tl::span<const sal_Int32> pDX; + + // get text sizes + if( !pDXArray.empty() ) + { + nNormWidth = maVDev->GetTextWidth( rText ); + pDX = pDXArray; + } + else + { + nNormWidth = maVDev->GetTextArray( rText, &aOwnArray ); + pDX = aOwnArray; + } + + if( nLen > 1 ) + { + nNormWidth = pDX[ nLen - 2 ] + maVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) ); + + if( nWidth && nNormWidth && ( nWidth != nNormWidth ) ) + { + if (!pDXArray.empty()) + { + aOwnArray.insert(aOwnArray.begin(), pDXArray.begin(), pDXArray.end()); + pDX = aOwnArray; + } + const double fFactor = static_cast<double>(nWidth) / nNormWidth; + + for( i = 0; i < ( nLen - 1 ); i++ ) + aOwnArray[ i ] = FRound( aOwnArray[ i ] * fFactor ); + } + } + + // write text record + ImplBeginRecord( WIN_EMR_EXTTEXTOUTW ); + + ImplWriteRect( tools::Rectangle( rPos, Size( nNormWidth, maVDev->GetTextHeight() ) ) ); + m_rStm.WriteUInt32( 1 ); + m_rStm.WriteInt32( 0 ).WriteInt32( 0 ); + ImplWritePoint( rPos ); + m_rStm.WriteUInt32( nLen ).WriteUInt32( 76 ).WriteUInt32( 2 ); + m_rStm.WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( 0 ).WriteInt32( 0 ); + m_rStm.WriteUInt32( 76 + ( nLen << 1 ) + ( (nLen & 1 ) ? 2 : 0 ) ); + + // write text + for( i = 0; i < nLen; i++ ) + m_rStm.WriteUInt16( rText[ i ] ); + + // padding word + if( nLen & 1 ) + m_rStm.WriteUInt16( 0 ); + + // write DX array + ImplWriteExtent( pDX[ 0 ] ); + + if( nLen > 1 ) + { + for( i = 1; i < ( nLen - 1 ); i++ ) + ImplWriteExtent( pDX[ i ] - pDX[ i - 1 ] ); + + ImplWriteExtent( pDX[ nLen - 2 ] / ( nLen - 1 ) ); + } + + ImplEndRecord(); + +} + +void EMFWriter::Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon) +{ + if(!rLinePolygon.count()) + return; + + basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon); + basegfx::B2DPolyPolygon aFillPolyPolygon; + + rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon); + + if(aLinePolyPolygon.count()) + { + for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon)) + { + ImplWritePolygonRecord( tools::Polygon(rB2DPolygon), false ); + } + } + + if(!aFillPolyPolygon.count()) + return; + + const Color aOldLineColor(maVDev->GetLineColor()); + const Color aOldFillColor(maVDev->GetFillColor()); + + maVDev->SetLineColor(); + maVDev->SetFillColor(aOldLineColor); + + for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon)) + { + ImplWritePolyPolygonRecord(tools::PolyPolygon( tools::Polygon(rB2DPolygon) )); + } + + maVDev->SetLineColor(aOldLineColor); + maVDev->SetFillColor(aOldFillColor); +} + +void EMFWriter::ImplWrite( const GDIMetaFile& rMtf ) +{ + for( size_t j = 0, nActionCount = rMtf.GetActionSize(); j < nActionCount; j++ ) + { + const MetaAction* pAction = rMtf.GetAction( j ); + const MetaActionType nType = pAction->GetType(); + + switch( nType ) + { + case MetaActionType::PIXEL: + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction); + + ImplCheckLineAttr(); + ImplBeginRecord( WIN_EMR_SETPIXELV ); + ImplWritePoint( pA->GetPoint() ); + ImplWriteColor( pA->GetColor() ); + ImplEndRecord(); + } + break; + + case MetaActionType::POINT: + { + if( maVDev->IsLineColor() ) + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction); + + ImplCheckLineAttr(); + ImplBeginRecord( WIN_EMR_SETPIXELV ); + ImplWritePoint( pA->GetPoint() ); + ImplWriteColor( maVDev->GetLineColor() ); + ImplEndRecord(); + } + } + break; + + case MetaActionType::LINE: + { + if( maVDev->IsLineColor() ) + { + const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction); + + if(pA->GetLineInfo().IsDefault()) + { + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_MOVETOEX ); + ImplWritePoint( pA->GetStartPoint() ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_LINETO ); + ImplWritePoint( pA->GetEndPoint() ); + ImplEndRecord(); + + ImplBeginRecord( WIN_EMR_SETPIXELV ); + ImplWritePoint( pA->GetEndPoint() ); + ImplWriteColor( maVDev->GetLineColor() ); + ImplEndRecord(); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + basegfx::B2DPolygon aPolygon; + aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y())); + aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y())); + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon); + } + } + } + break; + + case MetaActionType::RECT: + { + if( maVDev->IsLineColor() || maVDev->IsFillColor() ) + { + const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction); + + ImplCheckFillAttr(); + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_RECTANGLE ); + ImplWriteRect( pA->GetRect() ); + ImplEndRecord(); + } + } + break; + + case MetaActionType::ROUNDRECT: + { + if( maVDev->IsLineColor() || maVDev->IsFillColor() ) + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction); + + ImplCheckFillAttr(); + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_ROUNDRECT ); + ImplWriteRect( pA->GetRect() ); + ImplWriteSize( Size( pA->GetHorzRound(), pA->GetVertRound() ) ); + ImplEndRecord(); + } + } + break; + + case MetaActionType::ELLIPSE: + { + if( maVDev->IsLineColor() || maVDev->IsFillColor() ) + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction); + + ImplCheckFillAttr(); + ImplCheckLineAttr(); + + ImplBeginRecord( WIN_EMR_ELLIPSE ); + ImplWriteRect( pA->GetRect() ); + ImplEndRecord(); + } + } + break; + + case MetaActionType::ARC: + case MetaActionType::PIE: + case MetaActionType::CHORD: + case MetaActionType::POLYGON: + { + if( maVDev->IsLineColor() || maVDev->IsFillColor() ) + { + tools::Polygon aPoly; + + switch( nType ) + { + case MetaActionType::ARC: + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction); + aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc ); + } + break; + + case MetaActionType::PIE: + { + const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction); + aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie ); + } + break; + + case MetaActionType::CHORD: + { + const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction); + aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord ); + } + break; + + case MetaActionType::POLYGON: + aPoly = static_cast<const MetaPolygonAction*>(pAction)->GetPolygon(); + break; + default: break; + } + + ImplWritePolygonRecord( aPoly, nType != MetaActionType::ARC ); + } + } + break; + + case MetaActionType::POLYLINE: + { + if( maVDev->IsLineColor() ) + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction); + const tools::Polygon& rPoly = pA->GetPolygon(); + + if( rPoly.GetSize() ) + { + if(pA->GetLineInfo().IsDefault()) + { + ImplWritePolygonRecord( rPoly, false ); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon()); + } + } + } + } + break; + + case MetaActionType::POLYPOLYGON: + { + if( maVDev->IsLineColor() || maVDev->IsFillColor() ) + ImplWritePolyPolygonRecord( static_cast<const MetaPolyPolygonAction*>(pAction)->GetPolyPolygon() ); + } + break; + + case MetaActionType::GRADIENT: + { + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction); + GDIMetaFile aTmpMtf; + + Gradient aGradient = pA->GetGradient(); + aGradient.AddGradientActions( pA->GetRect(), aTmpMtf ); + ImplWrite( aTmpMtf ); + } + break; + + case MetaActionType::HATCH: + { + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction); + GDIMetaFile aTmpMtf; + + maVDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf ); + ImplWrite( aTmpMtf ); + } + break; + + case MetaActionType::Transparent: + { + const tools::PolyPolygon& rPolyPoly = static_cast<const MetaTransparentAction*>(pAction)->GetPolyPolygon(); + if( rPolyPoly.Count() ) + ImplWritePlusFillPolygonRecord( rPolyPoly[0], static_cast<const MetaTransparentAction*>(pAction)->GetTransparence() ); + ImplCheckFillAttr(); + ImplCheckLineAttr(); + ImplWritePolyPolygonRecord( rPolyPoly ); + + ImplBeginCommentRecord( WIN_EMR_COMMENT_EMFPLUS ); + ImplPlusRecord( EmfPlusRecordType::GetDC, 0x00 ); + ImplEndCommentRecord(); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); + + GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); + Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() ); + const Size aSrcSize( aTmpMtf.GetPrefSize() ); + const Point aDestPt( pA->GetPoint() ); + const Size aDestSize( pA->GetSize() ); + const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0; + const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0; + tools::Long nMoveX, nMoveY; + + if( fScaleX != 1.0 || fScaleY != 1.0 ) + { + aTmpMtf.Scale( fScaleX, fScaleY ); + aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) ); + aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) ); + } + + nMoveX = aDestPt.X() - aSrcPt.X(); + nMoveY = aDestPt.Y() - aSrcPt.Y(); + + if( nMoveX || nMoveY ) + aTmpMtf.Move( nMoveX, nMoveY ); + + ImplCheckFillAttr(); + ImplCheckLineAttr(); + ImplCheckTextAttr(); + ImplWrite( aTmpMtf ); + } + break; + + case MetaActionType::EPS: + { + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction); + const GDIMetaFile& aSubstitute( pA->GetSubstitute() ); + + for( size_t i = 0, nCount = aSubstitute.GetActionSize(); i < nCount; i++ ) + { + const MetaAction* pSubstAct = aSubstitute.GetAction( i ); + if( pSubstAct->GetType() == MetaActionType::BMPSCALE ) + { + maVDev->Push(); + ImplBeginRecord( WIN_EMR_SAVEDC ); + ImplEndRecord(); + + MapMode aMapMode( aSubstitute.GetPrefMapMode() ); + Size aOutSize( OutputDevice::LogicToLogic( pA->GetSize(), maVDev->GetMapMode(), aMapMode ) ); + aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) ); + aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) ); + aMapMode.SetOrigin( OutputDevice::LogicToLogic( pA->GetPoint(), maVDev->GetMapMode(), aMapMode ) ); + maVDev->SetMapMode( aMapMode ); + ImplWrite( aSubstitute ); + + maVDev->Pop(); + ImplBeginRecord( WIN_EMR_RESTOREDC ); + m_rStm.WriteInt32( -1 ); + ImplEndRecord(); + break; + } + } + } + break; + + case MetaActionType::BMP: + { + const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pAction); + ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), maVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ), WIN_SRCCOPY ); + } + break; + + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + ImplWriteBmpRecord( pA->GetBitmap(), pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction); + Bitmap aTmp( pA->GetBitmap() ); + + if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) ) + ImplWriteBmpRecord( aTmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY ); + } + break; + + case MetaActionType::BMPEX: + { + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pAction); + Bitmap aBmp( pA->GetBitmapEx().GetBitmap() ); + Bitmap aMsk( pA->GetBitmapEx().GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + ImplWriteBmpRecord( aMsk, pA->GetPoint(), maVDev->PixelToLogic( aMsk.GetSizePixel() ), WIN_SRCPAINT ); + ImplWriteBmpRecord( aBmp, pA->GetPoint(), maVDev->PixelToLogic( aBmp.GetSizePixel() ), WIN_SRCAND ); + } + else + ImplWriteBmpRecord( aBmp, pA->GetPoint(), aBmp.GetSizePixel(), WIN_SRCCOPY ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + Bitmap aBmp( pA->GetBitmapEx().GetBitmap() ); + Bitmap aMsk( pA->GetBitmapEx().GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + ImplWriteBmpRecord( aMsk, pA->GetPoint(), pA->GetSize(), WIN_SRCPAINT ); + ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCAND ); + } + else + ImplWriteBmpRecord( aBmp, pA->GetPoint(), pA->GetSize(), WIN_SRCCOPY ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction); + BitmapEx aBmpEx( pA->GetBitmapEx() ); + aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); + Bitmap aBmp( aBmpEx.GetBitmap() ); + Bitmap aMsk( aBmpEx.GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + ImplWriteBmpRecord( aMsk, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCPAINT ); + ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCAND ); + } + else + ImplWriteBmpRecord( aBmp, pA->GetDestPoint(), pA->GetDestSize(), WIN_SRCCOPY ); + } + break; + + case MetaActionType::TEXT: + { + const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction); + const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + + ImplCheckTextAttr(); + ImplWriteTextRecord( pA->GetPoint(), aText, {}, 0 ); + } + break; + + case MetaActionType::TEXTRECT: + { + const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction); + const OUString& aText( pA->GetText() ); + + ImplCheckTextAttr(); + ImplWriteTextRecord( pA->GetRect().TopLeft(), aText, {}, 0 ); + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction); + const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + + ImplCheckTextAttr(); + ImplWriteTextRecord( pA->GetPoint(), aText, pA->GetDXArray(), 0 ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction); + const OUString aText = pA->GetText().copy( pA->GetIndex(), std::min(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + + ImplCheckTextAttr(); + ImplWriteTextRecord( pA->GetPoint(), aText, {}, pA->GetWidth() ); + } + break; + + case MetaActionType::LINECOLOR: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + mbLineChanged = true; + } + break; + + case MetaActionType::FILLCOLOR: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + mbFillChanged = true; + } + break; + + case MetaActionType::TEXTCOLOR: + case MetaActionType::TEXTLINECOLOR: + case MetaActionType::TEXTFILLCOLOR: + case MetaActionType::TEXTALIGN: + case MetaActionType::FONT: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + mbTextChanged = true; + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + + ImplBeginRecord( WIN_EMR_INTERSECTCLIPRECT ); + ImplWriteRect( static_cast<const MetaISectRectClipRegionAction*>(pAction)->GetRect() ); + ImplEndRecord(); + } + break; + + case MetaActionType::CLIPREGION: + case MetaActionType::ISECTREGIONCLIPREGION: + case MetaActionType::MOVECLIPREGION: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + } + break; + + case MetaActionType::REFPOINT: + case MetaActionType::MAPMODE: + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + break; + + case MetaActionType::PUSH: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + + ImplBeginRecord( WIN_EMR_SAVEDC ); + ImplEndRecord(); + } + break; + + case MetaActionType::POP: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + + ImplBeginRecord( WIN_EMR_RESTOREDC ); + m_rStm.WriteInt32( -1 ); + ImplEndRecord(); + + ImplWriteRasterOp( maVDev->GetRasterOp() ); + mbLineChanged = mbFillChanged = mbTextChanged = true; + } + break; + + case MetaActionType::RASTEROP: + { + const_cast<MetaAction*>(pAction)->Execute( maVDev ); + ImplWriteRasterOp( static_cast<const MetaRasterOpAction*>(pAction)->GetRasterOp() ); + } + break; + + case MetaActionType::LAYOUTMODE: + { + vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pAction)->GetLayoutMode(); + mnHorTextAlign = 0; + if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) != vcl::text::ComplexTextLayoutFlags::Default) + { + mnHorTextAlign = TA_RIGHT | TA_RTLREADING; + } + if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight) != vcl::text::ComplexTextLayoutFlags::Default) + mnHorTextAlign |= TA_RIGHT; + else if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft) != vcl::text::ComplexTextLayoutFlags::Default) + mnHorTextAlign &= ~TA_RIGHT; + break; + } + + case MetaActionType::COMMENT: + { + MetaCommentAction const*const pCommentAction( + static_cast<MetaCommentAction const*>(pAction)); + if (pCommentAction->GetComment() == "EMF_PLUS") + { + ImplBeginCommentRecord(WIN_EMR_COMMENT_EMFPLUS); + m_rStm.WriteBytes(pCommentAction->GetData(), + pCommentAction->GetDataSize()); + ImplEndCommentRecord(); + } + } + break; + + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::WALLPAPER: + case MetaActionType::TEXTLINE: + case MetaActionType::GRADIENTEX: + // Explicitly ignored cases + break; + + default: + // TODO: Implement more cases as necessary. Let's not bother with a warning. + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/emfwr.hxx b/vcl/source/filter/wmf/emfwr.hxx new file mode 100644 index 000000000..502ae5d28 --- /dev/null +++ b/vcl/source/filter/wmf/emfwr.hxx @@ -0,0 +1,114 @@ +/* -*- 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_VCL_SOURCE_FILTER_WMF_EMFWR_HXX +#define INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX + +#include <vcl/gdimtf.hxx> +#include <vcl/virdev.hxx> + +class LineInfo; +namespace basegfx { class B2DPolygon; } +enum class EmfPlusRecordType; + +class EMFWriter +{ +private: + + ScopedVclPtr<VirtualDevice> maVDev; + MapMode maDestMapMode; + SvStream& m_rStm; + std::vector<bool> mHandlesUsed; + sal_uLong mnHandleCount; + sal_uLong mnRecordCount; + sal_uLong mnRecordPos; + sal_uLong mnRecordPlusPos; + bool mbRecordOpen; + bool mbRecordPlusOpen; + bool mbLineChanged; + sal_uInt32 mnLineHandle; + bool mbFillChanged; + sal_uInt32 mnFillHandle; + bool mbTextChanged; + sal_uInt32 mnTextHandle; + sal_uInt32 mnHorTextAlign; + + void ImplBeginRecord( sal_uInt32 nType ); + void ImplEndRecord(); + void ImplBeginPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags ); + void ImplEndPlusRecord(); + void ImplPlusRecord( EmfPlusRecordType nType, sal_uInt16 nFlags ); + void ImplBeginCommentRecord( sal_Int32 nCommentType ); + void ImplEndCommentRecord(); + + sal_uLong ImplAcquireHandle(); + void ImplReleaseHandle( sal_uLong nHandle ); + + bool ImplPrepareHandleSelect( sal_uInt32& rHandle, sal_uLong nSelectType ); + void ImplCheckLineAttr(); + void ImplCheckFillAttr(); + void ImplCheckTextAttr(); + + void ImplWriteColor( const Color& rColor ); + void ImplWriteRasterOp( RasterOp eRop ); + void ImplWriteExtent( tools::Long nExtent ); + void ImplWritePoint( const Point& rPoint ); + void ImplWriteSize( const Size& rSize); + void ImplWriteRect( const tools::Rectangle& rRect ); + void ImplWritePath( const tools::PolyPolygon& rPolyPoly, bool bClose ); + void ImplWritePolygonRecord( const tools::Polygon& rPoly, bool bClose ); + void ImplWritePolyPolygonRecord( const tools::PolyPolygon& rPolyPoly ); + void ImplWriteBmpRecord( const Bitmap& rBmp, const Point& rPt, const Size& rSz, sal_uInt32 nROP ); + void ImplWriteTextRecord( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_uInt32 nWidth ); + + void Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon); + void ImplWrite( const GDIMetaFile& rMtf ); + void WriteEMFPlusHeader( const Size &rMtfSizePix, const Size &rMtfSizeLog ); + void ImplWritePlusEOF(); + void ImplWritePlusFillPolygonRecord( const tools::Polygon& rPoly, sal_uInt32 nTrans ); + void ImplWritePlusColor( const Color& rColor, sal_uInt32 nTrans ); + void ImplWritePlusPoint( const Point& rPoint ); + +public: + + explicit EMFWriter(SvStream &rStream) + : maVDev( VclPtr<VirtualDevice>::Create() ) + , m_rStm(rStream) + , mnHandleCount(0) + , mnRecordCount(0) + , mnRecordPos(0) + , mnRecordPlusPos(0) + , mbRecordOpen(false) + , mbRecordPlusOpen(false) + , mbLineChanged(false) + , mnLineHandle(0) + , mbFillChanged(false) + , mnFillHandle(0) + , mbTextChanged(false) + , mnTextHandle(0) + , mnHorTextAlign(0) + { + } + + bool WriteEMF(const GDIMetaFile& rMtf); +}; + +#endif // INCLUDED_VCL_SOURCE_FILTER_WMF_EMFWR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/wmf.cxx b/vcl/source/filter/wmf/wmf.cxx new file mode 100644 index 000000000..bf91502b4 --- /dev/null +++ b/vcl/source/filter/wmf/wmf.cxx @@ -0,0 +1,134 @@ +/* -*- 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 "emfwr.hxx" +#include "wmfwr.hxx" +#include <vcl/wmf.hxx> +#include <vcl/gdimetafiletools.hxx> +#include <vcl/graph.hxx> + +using namespace com::sun::star; + +bool ReadWindowMetafile( SvStream& rStream, GDIMetaFile& rMTF ) +{ + // tdf#111484 Use new method to import Metafile. Take current StreamPos + // into account (used by SwWW8ImplReader::ReadGrafFile and by + // SwWw6ReadMetaStream, so do *not* ignore. OTOH XclImpDrawing::ReadWmf + // is nice enough to copy to an own MemStream to avoid that indirect + // parameter passing...) + const sal_uInt32 nStreamStart(rStream.Tell()); + const sal_uInt32 nStreamEnd(rStream.TellEnd()); + + if (nStreamStart >= nStreamEnd) + { + return false; + } + + // Read binary data to mem array + const sal_uInt32 nStreamLength(nStreamEnd - nStreamStart); + auto rData = std::make_unique<std::vector<sal_uInt8>>(nStreamLength); + rStream.ReadBytes(rData->data(), rData->size()); + BinaryDataContainer aDataContainer(std::move(rData)); + rStream.Seek(nStreamStart); + + if (rStream.good()) + { + // Throw into VectorGraphicData to get the import. Do not care + // too much for type, this will be checked there. Also no path + // needed, it is a temporary object + auto aVectorGraphicDataPtr = + std::make_shared<VectorGraphicData>(aDataContainer, VectorGraphicDataType::Emf); + + // create a Graphic and grep Metafile from it + const Graphic aGraphic(aVectorGraphicDataPtr); + + // get the Metafile from it, done + rMTF = aGraphic.GetGDIMetaFile(); + return true; + } + + return rStream.good(); +} + +bool ConvertGDIMetaFileToWMF( const GDIMetaFile & rMTF, SvStream & rTargetStream, + FilterConfigItem const * pConfigItem, bool bPlaceable) +{ + WMFWriter aWMFWriter; + GDIMetaFile aGdiMetaFile(rMTF); + + if(usesClipActions(aGdiMetaFile)) + { + // #i121267# It is necessary to prepare the metafile since the export does *not* support + // clip regions. This tooling method clips the geometry content of the metafile internally + // against its own clip regions, so that the export is safe to ignore clip regions + clipMetafileContentAgainstOwnRegions(aGdiMetaFile); + } + + bool bRet = aWMFWriter.WriteWMF(aGdiMetaFile, rTargetStream, pConfigItem, bPlaceable); + return bRet; +} + +bool ConvertGraphicToWMF(const Graphic& rGraphic, SvStream& rTargetStream, + FilterConfigItem const* pConfigItem, bool bPlaceable) +{ + GfxLink aLink = rGraphic.GetGfxLink(); + if (aLink.GetType() == GfxLinkType::NativeWmf && aLink.GetData() && aLink.GetDataSize()) + { + if(!aLink.IsEMF()) // If WMF, just write directly. + return rTargetStream.WriteBytes(aLink.GetData(), aLink.GetDataSize()) == aLink.GetDataSize(); + + // This may be an EMF+ file. In EmfReader::ReadEnhWMF() we normally drop non-EMF commands + // when reading EMF+, so converting that to WMF is better done by re-parsing with EMF+ disabled. + auto & rDataContainer = aLink.getDataContainer(); + auto aVectorGraphicData + = std::make_shared<VectorGraphicData>(rDataContainer, VectorGraphicDataType::Emf); + aVectorGraphicData->setEnableEMFPlus(false); + Graphic aGraphic(aVectorGraphicData); + bool bRet = ConvertGDIMetaFileToWMF(aGraphic.GetGDIMetaFile(), rTargetStream, pConfigItem, + bPlaceable); + return bRet; + } + + bool bRet = ConvertGDIMetaFileToWMF(rGraphic.GetGDIMetaFile(), rTargetStream, pConfigItem, + bPlaceable); + return bRet; +} + +bool ConvertGDIMetaFileToEMF(const GDIMetaFile & rMTF, SvStream & rTargetStream) +{ + EMFWriter aEMFWriter(rTargetStream); + GDIMetaFile aGdiMetaFile(rMTF); + + if(usesClipActions(aGdiMetaFile)) + { + // #i121267# It is necessary to prepare the metafile since the export does *not* support + // clip regions. This tooling method clips the geometry content of the metafile internally + // against its own clip regions, so that the export is safe to ignore clip regions + clipMetafileContentAgainstOwnRegions(aGdiMetaFile); + } + + return aEMFWriter.WriteEMF(aGdiMetaFile); +} + +bool WriteWindowMetafileBits( SvStream& rStream, const GDIMetaFile& rMTF ) +{ + return WMFWriter().WriteWMF( rMTF, rStream, nullptr, false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/wmfexternal.cxx b/vcl/source/filter/wmf/wmfexternal.cxx new file mode 100644 index 000000000..c5616beac --- /dev/null +++ b/vcl/source/filter/wmf/wmfexternal.cxx @@ -0,0 +1,74 @@ +/* -*- 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 <comphelper/propertyvalue.hxx> +#include <vcl/wmfexternal.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +// formally known as WMF_EXTERNALHEADER +WmfExternal::WmfExternal() + : xExt(0) + , yExt(0) + , mapMode(0) +{ +} + +css::uno::Sequence<css::beans::PropertyValue> WmfExternal::getSequence() const +{ + if (0 != xExt || 0 != yExt || 0 != mapMode) + { + return { comphelper::makePropertyValue("Width", static_cast<sal_Int16>(xExt)), + comphelper::makePropertyValue("Height", static_cast<sal_Int16>(yExt)), + comphelper::makePropertyValue("MapMode", static_cast<sal_Int16>(mapMode)) }; + } + + return {}; +} + +bool WmfExternal::setSequence(const css::uno::Sequence<css::beans::PropertyValue>& rSequence) +{ + bool bRetval(false); + + for (const auto& rPropVal : rSequence) + { + const OUString aName(rPropVal.Name); + + if (aName == "Width") + { + rPropVal.Value >>= xExt; + bRetval = true; + } + else if (aName == "Height") + { + rPropVal.Value >>= yExt; + bRetval = true; + } + else if (aName == "MapMode") + { + rPropVal.Value >>= mapMode; + bRetval = true; + } + } + + return bRetval; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/wmfwr.cxx b/vcl/source/filter/wmf/wmfwr.cxx new file mode 100644 index 000000000..22230ba46 --- /dev/null +++ b/vcl/source/filter/wmf/wmfwr.cxx @@ -0,0 +1,1904 @@ +/* -*- 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 <osl/diagnose.h> + +#include <algorithm> + +#include "wmfwr.hxx" +#include "emfwr.hxx" +#include <rtl/crc.h> +#include <rtl/tencinfo.h> +#include <tools/bigint.hxx> +#include <tools/helpers.hxx> +#include <tools/tenccvt.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <memory> +#include <vcl/fontcharmap.hxx> +#include <comphelper/sequenceashashmap.hxx> + +// MS Windows defines + +#define W_META_SETBKMODE 0x0102 +#define W_META_SETROP2 0x0104 +#define W_META_SETSTRETCHBLTMODE 0x0107 +#define W_META_SETTEXTCOLOR 0x0209 +#define W_META_SETWINDOWORG 0x020B +#define W_META_SETWINDOWEXT 0x020C +#define W_META_LINETO 0x0213 +#define W_META_MOVETO 0x0214 +#define W_META_INTERSECTCLIPRECT 0x0416 +#define W_META_ARC 0x0817 +#define W_META_ELLIPSE 0x0418 +#define W_META_PIE 0x081A +#define W_META_RECTANGLE 0x041B +#define W_META_ROUNDRECT 0x061C +#define W_META_SAVEDC 0x001E +#define W_META_SETPIXEL 0x041F +#define W_META_TEXTOUT 0x0521 +#define W_META_POLYGON 0x0324 +#define W_META_POLYLINE 0x0325 +#define W_META_ESCAPE 0x0626 +#define W_META_RESTOREDC 0x0127 +#define W_META_SELECTOBJECT 0x012D +#define W_META_SETTEXTALIGN 0x012E +#define W_META_CHORD 0x0830 +#define W_META_EXTTEXTOUT 0x0a32 +#define W_META_POLYPOLYGON 0x0538 +#define W_META_STRETCHDIB 0x0f43 +#define W_META_DELETEOBJECT 0x01f0 +#define W_META_CREATEPENINDIRECT 0x02FA +#define W_META_CREATEFONTINDIRECT 0x02FB +#define W_META_CREATEBRUSHINDIRECT 0x02FC + +#define W_TRANSPARENT 1 +#define W_OPAQUE 2 + +#define W_R2_NOT 6 +#define W_R2_XORPEN 7 +#define W_R2_COPYPEN 13 + +#define W_TA_NOUPDATECP 0x0000 +#define W_TA_LEFT 0x0000 +#define W_TA_RIGHT 0x0002 +#define W_TA_TOP 0x0000 +#define W_TA_BOTTOM 0x0008 +#define W_TA_BASELINE 0x0018 +#define W_TA_RTLREADING 0x0100 + +#define W_SRCCOPY 0x00CC0020L +#define W_SRCPAINT 0x00EE0086L +#define W_SRCAND 0x008800C6L +#define W_SRCINVERT 0x00660046L +#define W_DSTINVERT 0x00550009L + +#define W_PS_SOLID 0 +#define W_PS_DASH 1 +#define W_PS_DOT 2 +#define W_PS_DASHDOT 3 +#define W_PS_DASHDOTDOT 4 +#define W_PS_NULL 5 + +#define W_LF_FACESIZE 32 + +#define W_ANSI_CHARSET 0 + +#define W_DEFAULT_PITCH 0x00 +#define W_FIXED_PITCH 0x01 +#define W_VARIABLE_PITCH 0x02 + +#define W_FF_DONTCARE 0x00 +#define W_FF_ROMAN 0x10 +#define W_FF_SWISS 0x20 +#define W_FF_MODERN 0x30 +#define W_FF_SCRIPT 0x40 +#define W_FF_DECORATIVE 0x50 + +#define W_FW_DONTCARE 0 +#define W_FW_THIN 100 +#define W_FW_LIGHT 300 +#define W_FW_NORMAL 400 +#define W_FW_MEDIUM 500 +#define W_FW_SEMIBOLD 600 +#define W_FW_BOLD 700 +#define W_FW_ULTRALIGHT 200 +#define W_FW_ULTRABOLD 800 +#define W_FW_BLACK 900 + +#define W_BS_SOLID 0 +#define W_BS_HOLLOW 1 + +#define W_MFCOMMENT 15 + +#define PRIVATE_ESCAPE_UNICODE 2 + +WMFWriter::WMFWriter() + : bStatus(false) + , nLastPercent(0) + , pWMF(nullptr) + , pVirDev(nullptr) + , nMetafileHeaderPos(0) + , nMaxRecordSize(0) + , nActRecordPos(0) + , eSrcRasterOp(RasterOp::OverPaint) + , eSrcTextAlign(ALIGN_BASELINE) + , pAttrStack(nullptr) + , eSrcHorTextAlign(W_TA_LEFT) + , eDstROP2(RasterOp::OverPaint) + , eDstTextAlign(ALIGN_BASELINE) + , eDstHorTextAlign(W_TA_LEFT) + , bHandleAllocated{} + , nDstPenHandle(0) + , nDstFontHandle(0) + , nDstBrushHandle(0) + , nNumberOfActions(0) + , nNumberOfBitmaps(0) + , nWrittenActions(0) + , nWrittenBitmaps(0) + , nActBitmapPercent(0) + , bEmbedEMF(false) +{ +} + +void WMFWriter::MayCallback() +{ + if ( !xStatusIndicator.is() ) + return; + + sal_uLong nPercent; + + // we simply assume that 16386 actions match to a bitmap + // (normally a metafile either contains only actions or some bitmaps and + // almost no actions. In which case the ratio is less important) + + nPercent=((nWrittenBitmaps<<14)+(nActBitmapPercent<<14)/100+nWrittenActions) + *100 + /((nNumberOfBitmaps<<14)+nNumberOfActions); + + if ( nPercent >= nLastPercent + 3 ) + { + nLastPercent = nPercent; + if( nPercent <= 100 ) + xStatusIndicator->setValue( nPercent ); + } +} + +void WMFWriter::CountActionsAndBitmaps( const GDIMetaFile & rMTF ) +{ + size_t nAction, nActionCount; + + nActionCount = rMTF.GetActionSize(); + + for ( nAction=0; nAction < nActionCount; nAction++ ) + { + MetaAction* pMA = rMTF.GetAction( nAction ); + + switch( pMA->GetType() ) + { + case MetaActionType::BMP: + case MetaActionType::BMPSCALE: + case MetaActionType::BMPSCALEPART: + case MetaActionType::BMPEX: + case MetaActionType::BMPEXSCALE: + case MetaActionType::BMPEXSCALEPART: + nNumberOfBitmaps++; + break; + default: break; + } + nNumberOfActions++; + } +} + +void WMFWriter::WritePointXY(const Point & rPoint) +{ + Point aPt( OutputDevice::LogicToLogic(rPoint,aSrcMapMode,aTargetMapMode) ); + pWMF->WriteInt16( aPt.X() ).WriteInt16( aPt.Y() ); +} + +void WMFWriter::WritePointYX(const Point & rPoint) +{ + Point aPt( OutputDevice::LogicToLogic(rPoint,aSrcMapMode,aTargetMapMode) ); + pWMF->WriteInt16( aPt.Y() ).WriteInt16( aPt.X() ); +} + +sal_Int32 WMFWriter::ScaleWidth( sal_Int32 nDX ) +{ + Size aSz( OutputDevice::LogicToLogic(Size(nDX,0),aSrcMapMode,aTargetMapMode) ); + return aSz.Width(); +} + +void WMFWriter::WriteSize(const Size & rSize) +{ + Size aSz( OutputDevice::LogicToLogic(rSize,aSrcMapMode,aTargetMapMode) ); + pWMF->WriteInt16( aSz.Width() ).WriteInt16( aSz.Height() ); +} + +void WMFWriter::WriteHeightWidth(const Size & rSize) +{ + Size aSz( OutputDevice::LogicToLogic(rSize,aSrcMapMode,aTargetMapMode) ); + pWMF->WriteInt16( aSz.Height() ).WriteInt16( aSz.Width() ); +} + +void WMFWriter::WriteRectangle(const tools::Rectangle & rRect) +{ + WritePointYX(Point(rRect.Right()+1,rRect.Bottom()+1)); + WritePointYX(rRect.TopLeft()); +} + +void WMFWriter::WriteColor(const Color & rColor) +{ + pWMF->WriteUChar( rColor.GetRed() ).WriteUChar( rColor.GetGreen() ).WriteUChar( rColor.GetBlue() ).WriteUChar( 0 ); +} + +void WMFWriter::WriteRecordHeader(sal_uInt32 nSizeWords, sal_uInt16 nType) +{ + nActRecordPos=pWMF->Tell(); + if (nSizeWords>nMaxRecordSize) nMaxRecordSize=nSizeWords; + pWMF->WriteUInt32( nSizeWords ).WriteUInt16( nType ); +} + +void WMFWriter::UpdateRecordHeader() +{ + sal_uLong nPos; + sal_uInt32 nSize; + + nPos=pWMF->Tell(); nSize=nPos-nActRecordPos; + if ((nSize & 1)!=0) { + pWMF->WriteUChar( 0 ); + nPos++; nSize++; + } + nSize/=2; + if (nSize>nMaxRecordSize) nMaxRecordSize=nSize; + pWMF->Seek(nActRecordPos); + pWMF->WriteUInt32( nSize ); + pWMF->Seek(nPos); +} + +void WMFWriter::WMFRecord_Arc(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt) +{ + WriteRecordHeader(0x0000000b,W_META_ARC); + WritePointYX(rEndPt); + WritePointYX(rStartPt); + WriteRectangle(rRect); +} + +void WMFWriter::WMFRecord_Chord(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt) +{ + WriteRecordHeader(0x0000000b,W_META_CHORD); + WritePointYX(rEndPt); + WritePointYX(rStartPt); + WriteRectangle(rRect); +} + +void WMFWriter::WMFRecord_CreateBrushIndirect(const Color& rColor) +{ + WriteRecordHeader(0x00000007,W_META_CREATEBRUSHINDIRECT); + + if( rColor==COL_TRANSPARENT ) + pWMF->WriteUInt16( W_BS_HOLLOW ); + else + pWMF->WriteUInt16( W_BS_SOLID ); + + WriteColor( rColor ); + pWMF->WriteUInt16( 0 ); +} + +void WMFWriter::WMFRecord_CreateFontIndirect(const vcl::Font & rFont) +{ + sal_uInt16 nWeight,i; + sal_uInt8 nPitchFamily; + + WriteRecordHeader(0x00000000,W_META_CREATEFONTINDIRECT); + WriteHeightWidth(Size(rFont.GetFontSize().Width(),-rFont.GetFontSize().Height())); + pWMF->WriteInt16( rFont.GetOrientation().get() ).WriteInt16( rFont.GetOrientation().get() ); + + switch (rFont.GetWeight()) { + case WEIGHT_THIN: nWeight=W_FW_THIN; break; + case WEIGHT_ULTRALIGHT: nWeight=W_FW_ULTRALIGHT; break; + case WEIGHT_LIGHT: nWeight=W_FW_LIGHT; break; + case WEIGHT_SEMILIGHT: nWeight=W_FW_LIGHT; break; + case WEIGHT_NORMAL: nWeight=W_FW_NORMAL; break; + case WEIGHT_MEDIUM: nWeight=W_FW_MEDIUM; break; + case WEIGHT_SEMIBOLD: nWeight=W_FW_SEMIBOLD; break; + case WEIGHT_BOLD: nWeight=W_FW_BOLD; break; + case WEIGHT_ULTRABOLD: nWeight=W_FW_ULTRABOLD; break; + case WEIGHT_BLACK: nWeight=W_FW_BLACK; break; + default: nWeight=W_FW_DONTCARE; + } + pWMF->WriteUInt16( nWeight ); + + if (rFont.GetItalic()==ITALIC_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 ); + if (rFont.GetUnderline()==LINESTYLE_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 ); + if (rFont.GetStrikeout()==STRIKEOUT_NONE) pWMF->WriteUChar( 0 ); else pWMF->WriteUChar( 1 ); + + rtl_TextEncoding eFontNameEncoding = rFont.GetCharSet(); + sal_uInt8 nCharSet = rtl_getBestWindowsCharsetFromTextEncoding( eFontNameEncoding ); + if ( eFontNameEncoding == RTL_TEXTENCODING_SYMBOL ) + eFontNameEncoding = RTL_TEXTENCODING_MS_1252; + if ( nCharSet == 1 ) + nCharSet = W_ANSI_CHARSET; + pWMF->WriteUChar( nCharSet ); + + pWMF->WriteUChar( 0 ).WriteUChar( 0 ).WriteUChar( 0 ); + + switch (rFont.GetPitch()) { + case PITCH_FIXED: nPitchFamily=W_FIXED_PITCH; break; + case PITCH_VARIABLE: nPitchFamily=W_VARIABLE_PITCH; break; + default: nPitchFamily=W_DEFAULT_PITCH; + } + switch (rFont.GetFamilyType()) { + case FAMILY_DECORATIVE: nPitchFamily|=W_FF_DECORATIVE; break; + case FAMILY_MODERN: nPitchFamily|=W_FF_MODERN; break; + case FAMILY_ROMAN: nPitchFamily|=W_FF_ROMAN; break; + case FAMILY_SCRIPT: nPitchFamily|=W_FF_SCRIPT; break; + case FAMILY_SWISS: nPitchFamily|=W_FF_SWISS; break; + default: nPitchFamily|=W_FF_DONTCARE; + } + pWMF->WriteUChar( nPitchFamily ); + + OString aFontName(OUStringToOString(rFont.GetFamilyName(), eFontNameEncoding)); + for ( i = 0; i < W_LF_FACESIZE; i++ ) + { + char nChar = ( i < aFontName.getLength() ) ? aFontName[i] : 0; + pWMF->WriteChar( nChar ); + } + UpdateRecordHeader(); +} + +void WMFWriter::WMFRecord_CreatePenIndirect(const Color& rColor, const LineInfo& rLineInfo ) +{ + WriteRecordHeader(0x00000008,W_META_CREATEPENINDIRECT); + sal_uInt16 nStyle = rColor == COL_TRANSPARENT ? W_PS_NULL : W_PS_SOLID; + switch( rLineInfo.GetStyle() ) + { + case LineStyle::Dash : + { + if ( rLineInfo.GetDotCount() ) + { + if ( !rLineInfo.GetDashCount() ) + nStyle = W_PS_DOT; + else + { + if ( rLineInfo.GetDotCount() == 1 ) + nStyle = W_PS_DASHDOT; + else + nStyle = W_PS_DASHDOTDOT; + } + } + else + nStyle = W_PS_DASH; + } + break; + case LineStyle::NONE : + nStyle = W_PS_NULL; + break; + default: + break; + } + pWMF->WriteUInt16( nStyle ); + + WriteSize( Size( rLineInfo.GetWidth(), 0 ) ); + WriteColor( rColor ); +} + +void WMFWriter::WMFRecord_DeleteObject(sal_uInt16 nObjectHandle) +{ + WriteRecordHeader(0x00000004,W_META_DELETEOBJECT); + pWMF->WriteUInt16( nObjectHandle ); +} + +void WMFWriter::WMFRecord_Ellipse(const tools::Rectangle & rRect) +{ + WriteRecordHeader(0x00000007,W_META_ELLIPSE); + WriteRectangle(rRect); +} + +void WMFWriter::WMFRecord_Escape( sal_uInt32 nEsc, sal_uInt32 nLen, const sal_Int8* pData ) +{ +#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 + if ( nLen ) + nCheckSum = rtl_crc32( nCheckSum, pData, nLen ); + + WriteRecordHeader( 3 + 9 + ( ( nLen + 1 ) >> 1 ), W_META_ESCAPE ); + pWMF->WriteUInt16( W_MFCOMMENT ) + .WriteUInt16( nLen + 14 ) // we will always have a fourteen byte escape header: + .WriteUInt16( 0x4f4f ) // OO + .WriteUInt32( 0xa2c2a ) // evil magic number + .WriteUInt32( nCheckSum ) // crc32 checksum about nEsc & pData + .WriteUInt32( nEsc ); // escape number + pWMF->WriteBytes( pData, nLen ); + if ( nLen & 1 ) + pWMF->WriteUChar( 0 ); // pad byte +} + +/* if return value is true, then a complete unicode string and also a polygon replacement has been written, + so there is no more action necessary +*/ +bool WMFWriter::WMFRecord_Escape_Unicode( const Point& rPoint, const OUString& rUniStr, o3tl::span<const sal_Int32> pDXAry ) +{ + bool bEscapeUsed = false; + + sal_uInt32 i, nStringLen = rUniStr.getLength(); + if ( nStringLen ) + { + // first we will check if a comment is necessary + if ( aSrcFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) // symbol is always byte character, so there is no unicode loss + { + const sal_Unicode* pBuf = rUniStr.getStr(); + const rtl_TextEncoding aTextEncodingOrg = aSrcFont.GetCharSet(); + OString aByteStr(OUStringToOString(rUniStr, aTextEncodingOrg)); + OUString aUniStr2(OStringToOUString(aByteStr, aTextEncodingOrg)); + const sal_Unicode* pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion + for ( i = 0; i < nStringLen; i++ ) + { + if ( *pBuf++ != *pConversion++ ) + break; + } + + if ( i != nStringLen ) // after conversion the characters are not original, + { // try again, with determining a better charset from unicode char + pBuf = rUniStr.getStr(); + const sal_Unicode* pCheckChar = pBuf; + rtl_TextEncoding aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the first character + if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) { + aTextEncoding = aTextEncodingOrg; + } + for ( i = 1; i < nStringLen; i++) + { + if (aTextEncoding != aTextEncodingOrg) // found something + break; + pCheckChar++; + aTextEncoding = getBestMSEncodingByChar(*pCheckChar); // try the next character + if (aTextEncoding == RTL_TEXTENCODING_DONTKNOW) { + aTextEncoding = aTextEncodingOrg; + } + } + + aByteStr = OUStringToOString(rUniStr, aTextEncoding); + aUniStr2 = OStringToOUString(aByteStr, aTextEncoding); + pConversion = aUniStr2.getStr(); // this is the unicode array after bytestring <-> unistring conversion + for ( i = 0; i < nStringLen; i++ ) + { + if ( *pBuf++ != *pConversion++ ) + break; + } + if (i == nStringLen) + { + aSrcFont.SetCharSet (aTextEncoding); + SetAllAttr(); + } + } + + if ( ( i != nStringLen ) || IsStarSymbol( aSrcFont.GetFamilyName() ) ) // after conversion the characters are not original, so we + { // will store the unicode string and a polypoly replacement + Color aOldFillColor( aSrcFillColor ); + Color aOldLineColor( aSrcLineColor ); + aSrcLineInfo = LineInfo(); + aSrcFillColor = aSrcTextColor; + aSrcLineColor = COL_TRANSPARENT; + SetLineAndFillAttr(); + pVirDev->SetFont( aSrcFont ); + std::vector<tools::PolyPolygon> aPolyPolyVec; + if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniStr ) ) + { + sal_uInt32 nDXCount = !pDXAry.empty() ? nStringLen : 0; + sal_uInt32 nSkipActions = aPolyPolyVec.size(); + sal_Int32 nStrmLen = 8 + + + sizeof( nStringLen ) + ( nStringLen * 2 ) + + sizeof( nDXCount ) + ( nDXCount * 4 ) + + sizeof( nSkipActions ); + + SvMemoryStream aMemoryStream( nStrmLen ); + Point aPt( OutputDevice::LogicToLogic( rPoint, aSrcMapMode, aTargetMapMode ) ); + aMemoryStream.WriteInt32( aPt.X() ) + .WriteInt32( aPt.Y() ) + .WriteUInt32( nStringLen ); + for ( i = 0; i < nStringLen; i++ ) + aMemoryStream.WriteUInt16( rUniStr[ i ] ); + aMemoryStream.WriteUInt32( nDXCount ); + for ( i = 0; i < nDXCount; i++ ) + aMemoryStream.WriteInt32( pDXAry[ i ] ); + aMemoryStream.WriteUInt32( nSkipActions ); + WMFRecord_Escape( PRIVATE_ESCAPE_UNICODE, nStrmLen, static_cast<const sal_Int8*>(aMemoryStream.GetData()) ); + + for ( const auto& rPolyPoly : aPolyPolyVec ) + { + tools::PolyPolygon aPolyPoly( rPolyPoly ); + aPolyPoly.Move( rPoint.X(), rPoint.Y() ); + WMFRecord_PolyPolygon( aPolyPoly ); + } + aSrcFillColor = aOldFillColor; + aSrcLineColor = aOldLineColor; + bEscapeUsed = true; + } + } + } + } + return bEscapeUsed; +} + +void WMFWriter::WMFRecord_ExtTextOut( const Point& rPoint, + const OUString& rString, + o3tl::span<const sal_Int32> pDXAry ) +{ + sal_Int32 nOriginalTextLen = rString.getLength(); + + if ( (nOriginalTextLen <= 1) || pDXAry.empty() ) + { + WMFRecord_TextOut(rPoint, rString); + return; + } + rtl_TextEncoding eChrSet = aSrcFont.GetCharSet(); + OString aByteString(OUStringToOString(rString, eChrSet)); + TrueExtTextOut(rPoint, rString, aByteString, pDXAry); +} + +void WMFWriter::TrueExtTextOut( const Point& rPoint, const OUString& rString, + const OString& rByteString, o3tl::span<const sal_Int32> pDXAry ) +{ + WriteRecordHeader( 0, W_META_EXTTEXTOUT ); + WritePointYX( rPoint ); + sal_uInt16 nNewTextLen = static_cast<sal_uInt16>(rByteString.getLength()); + pWMF->WriteUInt16( nNewTextLen ).WriteUInt16( 0 ); + write_uInt8s_FromOString(*pWMF, rByteString, nNewTextLen); + if ( nNewTextLen & 1 ) + pWMF->WriteUChar( 0 ); + + sal_Int32 nOriginalTextLen = rString.getLength(); + std::unique_ptr<sal_Int16[]> pConvertedDXAry(new sal_Int16[ nOriginalTextLen ]); + sal_Int32 j = 0; + pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ 0 ] )); + for (sal_Int32 i = 1; i < ( nOriginalTextLen - 1 ); ++i) + pConvertedDXAry[ j++ ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ i ] - pDXAry[ i - 1 ] )); + pConvertedDXAry[ j ] = static_cast<sal_Int16>(ScaleWidth( pDXAry[ nOriginalTextLen - 2 ] / ( nOriginalTextLen - 1 ) )); + + for (sal_Int32 i = 0; i < nOriginalTextLen; ++i) + { + sal_Int16 nDx = pConvertedDXAry[ i ]; + pWMF->WriteInt16( nDx ); + if ( nOriginalTextLen < nNewTextLen ) + { + sal_Unicode nUniChar = rString[i]; + OString aTemp(&nUniChar, 1, aSrcFont.GetCharSet()); + j = aTemp.getLength(); + while ( --j > 0 ) + pWMF->WriteUInt16( 0 ); + } + } + pConvertedDXAry.reset(); + UpdateRecordHeader(); +} + +void WMFWriter::WMFRecord_LineTo(const Point & rPoint) +{ + WriteRecordHeader(0x00000005,W_META_LINETO); + WritePointYX(rPoint); +} + +void WMFWriter::WMFRecord_MoveTo(const Point & rPoint) +{ + WriteRecordHeader(0x00000005,W_META_MOVETO); + WritePointYX(rPoint); +} + +void WMFWriter::WMFRecord_Pie(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt) +{ + WriteRecordHeader(0x0000000b,W_META_PIE); + WritePointYX(rEndPt); + WritePointYX(rStartPt); + WriteRectangle(rRect); +} + +void WMFWriter::WMFRecord_Polygon(const tools::Polygon & rPoly) +{ + tools::Polygon aSimplePoly; + if ( rPoly.HasFlags() ) + rPoly.AdaptiveSubdivide( aSimplePoly ); + else + aSimplePoly = rPoly; + const sal_uInt16 nSize = aSimplePoly.GetSize(); + WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYGON); + pWMF->WriteUInt16( nSize ); + for (sal_uInt16 i=0; i<nSize; ++i) + WritePointXY(aSimplePoly.GetPoint(i)); +} + +void WMFWriter::WMFRecord_PolyLine(const tools::Polygon & rPoly) +{ + tools::Polygon aSimplePoly; + if ( rPoly.HasFlags() ) + rPoly.AdaptiveSubdivide( aSimplePoly ); + else + aSimplePoly = rPoly; + const sal_uInt16 nSize = aSimplePoly.GetSize(); + WriteRecordHeader(static_cast<sal_uInt32>(nSize)*2+4,W_META_POLYLINE); + pWMF->WriteUInt16( nSize ); + for (sal_uInt16 i=0; i<nSize; ++i) + WritePointXY(aSimplePoly.GetPoint(i)); +} + +void WMFWriter::WMFRecord_PolyPolygon(const tools::PolyPolygon & rPolyPoly) +{ + const tools::Polygon * pPoly; + sal_uInt16 nCount,nSize,i,j; + + nCount=rPolyPoly.Count(); + tools::PolyPolygon aSimplePolyPoly( rPolyPoly ); + for ( i = 0; i < nCount; i++ ) + { + if ( aSimplePolyPoly[ i ].HasFlags() ) + { + tools::Polygon aSimplePoly; + aSimplePolyPoly[ i ].AdaptiveSubdivide( aSimplePoly ); + aSimplePolyPoly[ i ] = aSimplePoly; + } + } + WriteRecordHeader(0,W_META_POLYPOLYGON); + pWMF->WriteUInt16( nCount ); + for (i=0; i<nCount; i++) pWMF->WriteUInt16( aSimplePolyPoly.GetObject(i).GetSize() ); + for (i=0; i<nCount; i++) { + pPoly=&(aSimplePolyPoly.GetObject(i)); + nSize=pPoly->GetSize(); + for (j=0; j<nSize; j++) WritePointXY(pPoly->GetPoint(j)); + } + UpdateRecordHeader(); +} + +void WMFWriter::WMFRecord_Rectangle(const tools::Rectangle & rRect) +{ + WriteRecordHeader( 0x00000007,W_META_RECTANGLE ); + WriteRectangle( rRect ); +} + +void WMFWriter::WMFRecord_RestoreDC() +{ + WriteRecordHeader(0x00000004,W_META_RESTOREDC); + pWMF->WriteInt16( -1 ); +} + +void WMFWriter::WMFRecord_RoundRect(const tools::Rectangle & rRect, tools::Long nHorzRound, tools::Long nVertRound) +{ + WriteRecordHeader(0x00000009,W_META_ROUNDRECT); + WriteHeightWidth(Size(nHorzRound,nVertRound)); + WriteRectangle(rRect); +} + +void WMFWriter::WMFRecord_SaveDC() +{ + WriteRecordHeader(0x00000003,W_META_SAVEDC); +} + +void WMFWriter::WMFRecord_SelectObject(sal_uInt16 nObjectHandle) +{ + WriteRecordHeader(0x00000004,W_META_SELECTOBJECT); + pWMF->WriteUInt16( nObjectHandle ); +} + +void WMFWriter::WMFRecord_SetBkMode(bool bTransparent) +{ + WriteRecordHeader(0x00000004,W_META_SETBKMODE); + if (bTransparent) pWMF->WriteUInt16( W_TRANSPARENT ); + else pWMF->WriteUInt16( W_OPAQUE ); +} + +void WMFWriter::WMFRecord_SetStretchBltMode() +{ + WriteRecordHeader( 0x00000004, W_META_SETSTRETCHBLTMODE ); + pWMF->WriteUInt16( 3 ); // STRETCH_DELETESCANS +} + +void WMFWriter::WMFRecord_SetPixel(const Point & rPoint, const Color & rColor) +{ + WriteRecordHeader(0x00000007,W_META_SETPIXEL); + WriteColor(rColor); + WritePointYX(rPoint); +} + +void WMFWriter::WMFRecord_SetROP2(RasterOp eROP) +{ + sal_uInt16 nROP2; + + switch (eROP) { + case RasterOp::Invert: nROP2=W_R2_NOT; break; + case RasterOp::Xor: nROP2=W_R2_XORPEN; break; + default: nROP2=W_R2_COPYPEN; + } + WriteRecordHeader(0x00000004,W_META_SETROP2); + pWMF->WriteUInt16( nROP2 ); +} + +void WMFWriter::WMFRecord_SetTextAlign(TextAlign eFontAlign, sal_uInt16 eHorTextAlign) +{ + sal_uInt16 nAlign; + + switch (eFontAlign) { + case ALIGN_TOP: nAlign=W_TA_TOP; break; + case ALIGN_BOTTOM: nAlign=W_TA_BOTTOM; break; + default: nAlign=W_TA_BASELINE; + } + nAlign|=eHorTextAlign; + nAlign|=W_TA_NOUPDATECP; + + WriteRecordHeader(0x00000004,W_META_SETTEXTALIGN); + pWMF->WriteUInt16( nAlign ); +} + +void WMFWriter::WMFRecord_SetTextColor(const Color & rColor) +{ + WriteRecordHeader(0x00000005,W_META_SETTEXTCOLOR); + WriteColor(rColor); +} + +void WMFWriter::WMFRecord_SetWindowExt(const Size & rSize) +{ + WriteRecordHeader(0x00000005,W_META_SETWINDOWEXT); + WriteHeightWidth(rSize); +} + +void WMFWriter::WMFRecord_SetWindowOrg(const Point & rPoint) +{ + WriteRecordHeader(0x00000005,W_META_SETWINDOWORG); + WritePointYX(rPoint); +} + +void WMFWriter::WMFRecord_StretchDIB( const Point & rPoint, const Size & rSize, + const Bitmap & rBitmap, sal_uInt32 nROP ) +{ + sal_uLong nPosAnf,nPosEnd; + + nActBitmapPercent=50; + MayCallback(); + + WriteRecordHeader(0x00000000,W_META_STRETCHDIB); + + // The sequence in the metafile should be: + // some parameters (length 22), then the bitmap without FILEHEADER. + // As *pWMF << rBitmap generates a FILEHEADER of size 14, + // we first write the bitmap at the right position + // and overwrite later the FILEHEADER with the parameters. + nPosAnf=pWMF->Tell(); // remember position, where parameters should be stored + pWMF->WriteInt32( 0 ).WriteInt32( 0 ); // replenish 8 bytes (these 8 bytes + + // 14 bytes superfluous FILEHEADER + // = 22 bytes parameter) + + // write bitmap + WriteDIB(rBitmap, *pWMF, false, true); + + // write the parameters: + nPosEnd=pWMF->Tell(); + pWMF->Seek(nPosAnf); + + // determine raster-op, if nothing was passed + if( !nROP ) + { + switch( eSrcRasterOp ) + { + case RasterOp::Invert: nROP = W_DSTINVERT; break; + case RasterOp::Xor: nROP = W_SRCINVERT; break; + default: nROP = W_SRCCOPY; + } + } + + pWMF->WriteUInt32( nROP ). + WriteInt16( 0 ). + WriteInt16( rBitmap.GetSizePixel().Height() ). + WriteInt16( rBitmap.GetSizePixel().Width() ). + WriteInt16( 0 ). + WriteInt16( 0 ); + + WriteHeightWidth(rSize); + WritePointYX(rPoint); + pWMF->Seek(nPosEnd); + + UpdateRecordHeader(); + + nWrittenBitmaps++; + nActBitmapPercent=0; +} + +void WMFWriter::WMFRecord_TextOut(const Point & rPoint, std::u16string_view rStr) +{ + rtl_TextEncoding eChrSet = aSrcFont.GetCharSet(); + OString aString(OUStringToOString(rStr, eChrSet)); + TrueTextOut(rPoint, aString); +} + +void WMFWriter::TrueTextOut(const Point & rPoint, const OString& rString) +{ + WriteRecordHeader(0,W_META_TEXTOUT); + + write_uInt16_lenPrefixed_uInt8s_FromOString(*pWMF, rString); + sal_Int32 nLen = rString.getLength(); + if ((nLen&1)!=0) pWMF->WriteUChar( 0 ); + WritePointYX(rPoint); + UpdateRecordHeader(); +} + +void WMFWriter::WMFRecord_IntersectClipRect( const tools::Rectangle& rRect ) +{ + WriteRecordHeader( 0x00000007, W_META_INTERSECTCLIPRECT ); + WriteRectangle(rRect); +} + +sal_uInt16 WMFWriter::AllocHandle() +{ + sal_uInt16 i; + + for (i=0; i<MAXOBJECTHANDLES; i++) { + if (!bHandleAllocated[i]) { + bHandleAllocated[i]=true; + return i; + } + } + bStatus=false; + return 0xffff; +} + +void WMFWriter::FreeHandle(sal_uInt16 nObjectHandle) +{ + if (nObjectHandle<MAXOBJECTHANDLES) bHandleAllocated[nObjectHandle]=false; +} + +void WMFWriter::CreateSelectDeletePen( const Color& rColor, const LineInfo& rLineInfo ) +{ + sal_uInt16 nOldHandle; + + nOldHandle=nDstPenHandle; + nDstPenHandle=AllocHandle(); + WMFRecord_CreatePenIndirect( rColor, rLineInfo ); + WMFRecord_SelectObject(nDstPenHandle); + if (nOldHandle<MAXOBJECTHANDLES) { + WMFRecord_DeleteObject(nOldHandle); + FreeHandle(nOldHandle); + } +} + +void WMFWriter::CreateSelectDeleteFont(const vcl::Font & rFont) +{ + sal_uInt16 nOldHandle; + + nOldHandle=nDstFontHandle; + nDstFontHandle=AllocHandle(); + WMFRecord_CreateFontIndirect(rFont); + WMFRecord_SelectObject(nDstFontHandle); + if (nOldHandle<MAXOBJECTHANDLES) { + WMFRecord_DeleteObject(nOldHandle); + FreeHandle(nOldHandle); + } +} + +void WMFWriter::CreateSelectDeleteBrush(const Color& rColor) +{ + sal_uInt16 nOldHandle; + + nOldHandle=nDstBrushHandle; + nDstBrushHandle=AllocHandle(); + WMFRecord_CreateBrushIndirect(rColor); + WMFRecord_SelectObject(nDstBrushHandle); + if (nOldHandle<MAXOBJECTHANDLES) { + WMFRecord_DeleteObject(nOldHandle); + FreeHandle(nOldHandle); + } +} + +void WMFWriter::SetLineAndFillAttr() +{ + if ( eDstROP2 != eSrcRasterOp ) + { + eDstROP2=eSrcRasterOp; + WMFRecord_SetROP2(eDstROP2); + } + if ( ( aDstLineColor != aSrcLineColor ) || ( aDstLineInfo != aSrcLineInfo ) ) + { + aDstLineColor = aSrcLineColor; + aDstLineInfo = aSrcLineInfo; + CreateSelectDeletePen( aDstLineColor, aDstLineInfo ); + } + if ( aDstFillColor != aSrcFillColor ) + { + aDstFillColor = aSrcFillColor; + CreateSelectDeleteBrush( aDstFillColor ); + } +} + +void WMFWriter::SetAllAttr() +{ + SetLineAndFillAttr(); + if ( aDstTextColor != aSrcTextColor ) + { + aDstTextColor = aSrcTextColor; + WMFRecord_SetTextColor(aDstTextColor); + } + if ( eDstTextAlign != eSrcTextAlign || eDstHorTextAlign != eSrcHorTextAlign ) + { + eDstTextAlign = eSrcTextAlign; + eDstHorTextAlign = eSrcHorTextAlign; + WMFRecord_SetTextAlign( eDstTextAlign, eDstHorTextAlign ); + } + if ( aDstFont == aSrcFont ) + return; + + pVirDev->SetFont(aSrcFont); + if ( aDstFont.GetFamilyName() != aSrcFont.GetFamilyName() ) + { + FontCharMapRef xFontCharMap; + if ( pVirDev->GetFontCharMap( xFontCharMap ) ) + { + if ( ( xFontCharMap->GetFirstChar() & 0xff00 ) == 0xf000 ) + aSrcFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + else if ( aSrcFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL ) + aSrcFont.SetCharSet( RTL_TEXTENCODING_MS_1252 ); + } + } + + aDstFont = aSrcFont; + CreateSelectDeleteFont(aDstFont); +} + +void WMFWriter::HandleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon) +{ + if(!rLinePolygon.count()) + return; + + basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon); + basegfx::B2DPolyPolygon aFillPolyPolygon; + + rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon); + + if(aLinePolyPolygon.count()) + { + aSrcLineInfo = rInfo; + SetLineAndFillAttr(); + + for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon)) + { + WMFRecord_PolyLine( tools::Polygon(rB2DPolygon) ); + } + } + + if(!aFillPolyPolygon.count()) + return; + + const Color aOldLineColor(aSrcLineColor); + const Color aOldFillColor(aSrcFillColor); + + aSrcLineColor = COL_TRANSPARENT; + aSrcFillColor = aOldLineColor; + SetLineAndFillAttr(); + + for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon)) + { + WMFRecord_Polygon( tools::Polygon(rB2DPolygon) ); + } + + aSrcLineColor = aOldLineColor; + aSrcFillColor = aOldFillColor; + SetLineAndFillAttr(); +} + +void WMFWriter::WriteRecords( const GDIMetaFile & rMTF ) +{ + if( !bStatus ) + return; + + size_t nACount = rMTF.GetActionSize(); + + WMFRecord_SetStretchBltMode(); + + for( size_t nA = 0; nA < nACount; nA++ ) + { + MetaAction* pMA = rMTF.GetAction( nA ); + + switch( pMA->GetType() ) + { + case MetaActionType::PIXEL: + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction *>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_SetPixel( pA->GetPoint(), pA->GetColor() ); + } + break; + + case MetaActionType::POINT: + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pMA); + const Point& rPt = pA->GetPoint(); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_MoveTo( rPt); + WMFRecord_LineTo( rPt ); + } + break; + + case MetaActionType::LINE: + { + const MetaLineAction* pA = static_cast<const MetaLineAction *>(pMA); + if(pA->GetLineInfo().IsDefault()) + { + aSrcLineInfo = pA->GetLineInfo(); + SetLineAndFillAttr(); + WMFRecord_MoveTo( pA->GetStartPoint() ); + WMFRecord_LineTo( pA->GetEndPoint() ); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + basegfx::B2DPolygon aPolygon; + aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y())); + aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y())); + HandleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon); + } + } + break; + + case MetaActionType::RECT: + { + const MetaRectAction* pA = static_cast<const MetaRectAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Rectangle( pA->GetRect() ); + } + break; + + case MetaActionType::ROUNDRECT: + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_RoundRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() ); + } + break; + + case MetaActionType::ELLIPSE: + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Ellipse( pA->GetRect() ); + } + break; + + case MetaActionType::ARC: + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Arc( pA->GetRect(),pA->GetStartPoint(),pA->GetEndPoint() ); + } + break; + + case MetaActionType::PIE: + { + const MetaPieAction* pA = static_cast<const MetaPieAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Pie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); + } + break; + + case MetaActionType::CHORD: + { + const MetaChordAction* pA = static_cast<const MetaChordAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Chord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); + } + break; + + case MetaActionType::POLYLINE: + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pMA); + const tools::Polygon& rPoly = pA->GetPolygon(); + + if( rPoly.GetSize() ) + { + if(pA->GetLineInfo().IsDefault()) + { + aSrcLineInfo = pA->GetLineInfo(); + SetLineAndFillAttr(); + WMFRecord_PolyLine( rPoly ); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + HandleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon()); + } + } + } + break; + + case MetaActionType::POLYGON: + { + const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Polygon( pA->GetPolygon() ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pMA); + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_PolyPolygon( pA->GetPolyPolygon() ); + } + break; + + case MetaActionType::TEXTRECT: + { + const MetaTextRectAction * pA = static_cast<const MetaTextRectAction*>(pMA); + OUString aTemp( pA->GetText() ); + aSrcLineInfo = LineInfo(); + SetAllAttr(); + + Point aPos( pA->GetRect().TopLeft() ); + if ( !WMFRecord_Escape_Unicode( aPos, aTemp, {} ) ) + WMFRecord_TextOut( aPos, aTemp ); + } + break; + + case MetaActionType::TEXT: + { + const MetaTextAction * pA = static_cast<const MetaTextAction*>(pMA); + OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + aSrcLineInfo = LineInfo(); + SetAllAttr(); + if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, {} ) ) + WMFRecord_TextOut( pA->GetPoint(), aTemp ); + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pMA); + + OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + aSrcLineInfo = LineInfo(); + SetAllAttr(); + if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, pA->GetDXArray() ) ) + WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, pA->GetDXArray() ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction *>(pMA); + OUString aTemp = pA->GetText().copy( pA->GetIndex(), std::min<sal_Int32>(pA->GetText().getLength() - pA->GetIndex(), pA->GetLen()) ); + + pVirDev->SetFont( aSrcFont ); + const sal_Int32 nLen = aTemp.getLength(); + std::vector<sal_Int32> aDXAry; + const sal_Int32 nNormSize = pVirDev->GetTextArray( aTemp, nLen ? &aDXAry : nullptr ); + if (nLen && nNormSize == 0) + { + OSL_FAIL("Impossible div by 0 action: MetaStretchTextAction!"); + } + else + { + for ( sal_Int32 i = 0; i < ( nLen - 1 ); i++ ) + aDXAry[ i ] = aDXAry[ i ] * static_cast<sal_Int32>(pA->GetWidth()) / nNormSize; + if ( ( nLen <= 1 ) || ( static_cast<sal_Int32>(pA->GetWidth()) == nNormSize ) ) + aDXAry.clear(); + aSrcLineInfo = LineInfo(); + SetAllAttr(); + if ( !WMFRecord_Escape_Unicode( pA->GetPoint(), aTemp, aDXAry ) ) + WMFRecord_ExtTextOut( pA->GetPoint(), aTemp, aDXAry ); + } + } + break; + + case MetaActionType::BMP: + { + const MetaBmpAction* pA = static_cast<const MetaBmpAction *>(pMA); + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetBitmap().GetSizePixel(), pA->GetBitmap() ); + } + break; + + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pMA); + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), pA->GetBitmap() ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pMA); + Bitmap aTmp( pA->GetBitmap() ); + + if( aTmp.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ) ) + WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aTmp ); + } + break; + + case MetaActionType::BMPEX: + { + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction *>(pMA); + Bitmap aBmp( pA->GetBitmapEx().GetBitmap() ); + Bitmap aMsk( pA->GetBitmapEx().GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + WMFRecord_StretchDIB( pA->GetPoint(), aMsk.GetSizePixel(), aBmp, W_SRCPAINT ); + WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp, W_SRCAND ); + } + else + WMFRecord_StretchDIB( pA->GetPoint(), aBmp.GetSizePixel(), aBmp ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pMA); + Bitmap aBmp( pA->GetBitmapEx().GetBitmap() ); + Bitmap aMsk( pA->GetBitmapEx().GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aMsk, W_SRCPAINT ); + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp, W_SRCAND ); + } + else + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), aBmp ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pMA); + BitmapEx aBmpEx( pA->GetBitmapEx() ); + aBmpEx.Crop( tools::Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); + Bitmap aBmp( aBmpEx.GetBitmap() ); + Bitmap aMsk( aBmpEx.GetAlpha() ); + + if( !aMsk.IsEmpty() ) + { + aBmp.Replace( aMsk, COL_WHITE ); + aMsk.Invert(); + WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aMsk, W_SRCPAINT ); + WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp, W_SRCAND ); + } + else + WMFRecord_StretchDIB( pA->GetDestPoint(), pA->GetDestSize(), aBmp ); + } + break; + + case MetaActionType::GRADIENT: + { + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pMA); + GDIMetaFile aTmpMtf; + + Gradient aGradient = pA->GetGradient(); + aGradient.AddGradientActions( pA->GetRect(), aTmpMtf ); + WriteRecords( aTmpMtf ); + } + break; + + case MetaActionType::HATCH: + { + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pMA); + GDIMetaFile aTmpMtf; + + pVirDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf ); + WriteRecords( aTmpMtf ); + } + break; + + case MetaActionType::WALLPAPER: + { + const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pMA); + const Color& rColor = pA->GetWallpaper().GetColor(); + const Color aOldLineColor( aSrcLineColor ); + const Color aOldFillColor( aSrcFillColor ); + + aSrcLineColor = rColor; + aSrcFillColor = rColor; + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_Rectangle( pA->GetRect() ); + aSrcLineColor = aOldLineColor; + aSrcFillColor = aOldFillColor; + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pMA); + WMFRecord_IntersectClipRect( pA->GetRect() ); + } + break; + + case MetaActionType::LINECOLOR: + { + const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pMA); + + if( pA->IsSetting() ) + aSrcLineColor = pA->GetColor(); + else + aSrcLineColor = COL_TRANSPARENT; + } + break; + + case MetaActionType::FILLCOLOR: + { + const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pMA); + + if( pA->IsSetting() ) + aSrcFillColor = pA->GetColor(); + else + aSrcFillColor = COL_TRANSPARENT; + } + break; + + case MetaActionType::TEXTCOLOR: + { + const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pMA); + aSrcTextColor = pA->GetColor(); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pMA); + if( pA->IsSetting() ) + aSrcFont.SetFillColor( pA->GetColor() ); + else + aSrcFont.SetFillColor( COL_TRANSPARENT ); + } + break; + + case MetaActionType::TEXTALIGN: + { + const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pMA); + eSrcTextAlign = pA->GetTextAlign(); + } + break; + + case MetaActionType::MAPMODE: + { + const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pMA); + + if (aSrcMapMode!=pA->GetMapMode()) + { + if( pA->GetMapMode().GetMapUnit() == MapUnit::MapRelative ) + { + const MapMode& aMM = pA->GetMapMode(); + Fraction aScaleX = aMM.GetScaleX(); + Fraction aScaleY = aMM.GetScaleY(); + + Point aOrigin = aSrcMapMode.GetOrigin(); + BigInt aX( aOrigin.X() ); + aX *= BigInt( aScaleX.GetDenominator() ); + if( aOrigin.X() >= 0 ) + if( aScaleX.GetNumerator() >= 0 ) + aX += BigInt( aScaleX.GetNumerator()/2 ); + else + aX -= BigInt( (aScaleX.GetNumerator()+1)/2 ); + else + if( aScaleX.GetNumerator() >= 0 ) + aX -= BigInt( (aScaleX.GetNumerator()-1)/2 ); + else + aX += BigInt( aScaleX.GetNumerator()/2 ); + aX /= BigInt( aScaleX.GetNumerator() ); + aOrigin.setX( static_cast<tools::Long>(aX) + aMM.GetOrigin().X() ); + BigInt aY( aOrigin.Y() ); + aY *= BigInt( aScaleY.GetDenominator() ); + if( aOrigin.Y() >= 0 ) + if( aScaleY.GetNumerator() >= 0 ) + aY += BigInt( aScaleY.GetNumerator()/2 ); + else + aY -= BigInt( (aScaleY.GetNumerator()+1)/2 ); + else + if( aScaleY.GetNumerator() >= 0 ) + aY -= BigInt( (aScaleY.GetNumerator()-1)/2 ); + else + aY += BigInt( aScaleY.GetNumerator()/2 ); + aY /= BigInt( aScaleY.GetNumerator() ); + aOrigin.setY( static_cast<tools::Long>(aY) + aMM.GetOrigin().Y() ); + aSrcMapMode.SetOrigin( aOrigin ); + + aScaleX *= aSrcMapMode.GetScaleX(); + aScaleY *= aSrcMapMode.GetScaleY(); + aSrcMapMode.SetScaleX( aScaleX ); + aSrcMapMode.SetScaleY( aScaleY ); + } + else + aSrcMapMode=pA->GetMapMode(); + } + } + break; + + case MetaActionType::FONT: + { + const MetaFontAction* pA = static_cast<const MetaFontAction*>(pMA); + aSrcFont = pA->GetFont(); + + if ( (aSrcFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW) + || (aSrcFont.GetCharSet() == RTL_TEXTENCODING_UNICODE) ) + { + aSrcFont.SetCharSet( RTL_TEXTENCODING_MS_1252 ); + } + eSrcTextAlign = aSrcFont.GetAlignment(); + aSrcTextColor = aSrcFont.GetColor(); + aSrcFont.SetAlignment( ALIGN_BASELINE ); + aSrcFont.SetColor( COL_WHITE ); + } + break; + + case MetaActionType::PUSH: + { + const MetaPushAction* pA = static_cast<const MetaPushAction*>(pMA); + + WMFWriterAttrStackMember* pAt = new WMFWriterAttrStackMember; + pAt->nFlags = pA->GetFlags(); + pAt->aClipRegion = aSrcClipRegion; + pAt->aLineColor=aSrcLineColor; + pAt->aFillColor=aSrcFillColor; + pAt->eRasterOp=eSrcRasterOp; + pAt->aFont=aSrcFont; + pAt->eTextAlign=eSrcTextAlign; + pAt->aTextColor=aSrcTextColor; + pAt->aMapMode=aSrcMapMode; + pAt->aLineInfo=aDstLineInfo; + pAt->pSucc=pAttrStack; + pAttrStack=pAt; + + SetAllAttr(); // update ( now all source attributes are equal to the destination attributes ) + WMFRecord_SaveDC(); + + } + break; + + case MetaActionType::POP: + { + WMFWriterAttrStackMember * pAt=pAttrStack; + + if( pAt ) + { + aDstLineInfo = pAt->aLineInfo; + aDstLineColor = pAt->aLineColor; + if ( pAt->nFlags & vcl::PushFlags::LINECOLOR ) + aSrcLineColor = pAt->aLineColor; + aDstFillColor = pAt->aFillColor; + if ( pAt->nFlags & vcl::PushFlags::FILLCOLOR ) + aSrcFillColor = pAt->aFillColor; + eDstROP2 = pAt->eRasterOp; + if ( pAt->nFlags & vcl::PushFlags::RASTEROP ) + eSrcRasterOp = pAt->eRasterOp; + aDstFont = pAt->aFont; + if ( pAt->nFlags & vcl::PushFlags::FONT ) + aSrcFont = pAt->aFont; + eDstTextAlign = pAt->eTextAlign; + if ( pAt->nFlags & ( vcl::PushFlags::FONT | vcl::PushFlags::TEXTALIGN ) ) + eSrcTextAlign = pAt->eTextAlign; + aDstTextColor = pAt->aTextColor; + if ( pAt->nFlags & ( vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR ) ) + aSrcTextColor = pAt->aTextColor; + if ( pAt->nFlags & vcl::PushFlags::MAPMODE ) + aSrcMapMode = pAt->aMapMode; + aDstClipRegion = pAt->aClipRegion; + if ( pAt->nFlags & vcl::PushFlags::CLIPREGION ) + aSrcClipRegion = pAt->aClipRegion; + + WMFRecord_RestoreDC(); + pAttrStack = pAt->pSucc; + delete pAt; + } + } + break; + + case MetaActionType::EPS : + { + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pMA); + const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() ); + + size_t nCount = aGDIMetaFile.GetActionSize(); + for ( size_t i = 0; i < nCount; i++ ) + { + const MetaAction* pMetaAct = aGDIMetaFile.GetAction( i ); + if ( pMetaAct->GetType() == MetaActionType::BMPSCALE ) + { + const MetaBmpScaleAction* pBmpScaleAction = static_cast<const MetaBmpScaleAction*>(pMetaAct); + WMFRecord_StretchDIB( pA->GetPoint(), pA->GetSize(), pBmpScaleAction->GetBitmap() ); + break; + } + } + } + break; + + case MetaActionType::RASTEROP: + { + const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pMA); + eSrcRasterOp=pA->GetRasterOp(); + } + break; + + case MetaActionType::Transparent: + { + aSrcLineInfo = LineInfo(); + SetLineAndFillAttr(); + WMFRecord_PolyPolygon( static_cast<const MetaTransparentAction*>(pMA)->GetPolyPolygon() ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pMA); + + GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); + Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() ); + const Size aSrcSize( aTmpMtf.GetPrefSize() ); + const Point aDestPt( pA->GetPoint() ); + const Size aDestSize( pA->GetSize() ); + const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0; + const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0; + tools::Long nMoveX, nMoveY; + + aSrcLineInfo = LineInfo(); + SetAllAttr(); + + if( fScaleX != 1.0 || fScaleY != 1.0 ) + { + aTmpMtf.Scale( fScaleX, fScaleY ); + aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) ); + aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) ); + } + + nMoveX = aDestPt.X() - aSrcPt.X(); + nMoveY = aDestPt.Y() - aSrcPt.Y(); + + if( nMoveX || nMoveY ) + aTmpMtf.Move( nMoveX, nMoveY ); + + WriteRecords( aTmpMtf ); + } + break; + + case MetaActionType::LAYOUTMODE: + { + vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<const MetaLayoutModeAction*>(pMA)->GetLayoutMode(); + eSrcHorTextAlign = 0; // TA_LEFT + if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) != vcl::text::ComplexTextLayoutFlags::Default) + { + eSrcHorTextAlign = W_TA_RIGHT | W_TA_RTLREADING; + } + if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight) != vcl::text::ComplexTextLayoutFlags::Default) + eSrcHorTextAlign |= W_TA_RIGHT; + else if ((nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft) != vcl::text::ComplexTextLayoutFlags::Default) + eSrcHorTextAlign &= ~W_TA_RIGHT; + break; + } + + case MetaActionType::CLIPREGION: + case MetaActionType::TEXTLANGUAGE: + case MetaActionType::COMMENT: + // Explicitly ignored cases + break; + + default: + // TODO: Implement more cases as necessary. Let's not bother with a warning. + break; + } + + nWrittenActions++; + MayCallback(); + + if (pWMF->GetError()) + bStatus=false; + + if(!bStatus) + break; + } +} + +void WMFWriter::WriteHeader( bool bPlaceable ) +{ + if( bPlaceable ) + { + sal_uInt16 nCheckSum, nValue; + Size aSize( OutputDevice::LogicToLogic(Size(1,1),MapMode(MapUnit::MapInch), aTargetMapMode) ); + sal_uInt16 nUnitsPerInch = static_cast<sal_uInt16>( ( aSize.Width() + aSize.Height() ) >> 1 ); + + nCheckSum=0; + nValue=0xcdd7; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x9ac6; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=static_cast<sal_uInt16>(aTargetSize.Width()); nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=static_cast<sal_uInt16>(aTargetSize.Height()); nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=nUnitsPerInch; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + nValue=0x0000; nCheckSum^=nValue; pWMF->WriteUInt16( nValue ); + pWMF->WriteUInt16( nCheckSum ); + } + + nMetafileHeaderPos=pWMF->Tell(); + pWMF->WriteUInt16( 0x0001 ) // type: file + .WriteUInt16( 0x0009 ) // header length in words + .WriteUInt16( 0x0300 ) // Version as BCD number + .WriteUInt32( 0x00000000 ) // file length (without 1st header), is later corrected by UpdateHeader() + .WriteUInt16( MAXOBJECTHANDLES ) // maximum number of simultaneous objects + .WriteUInt32( 0x00000000 ) // maximum record length, is later corrected by UpdateHeader() + .WriteUInt16( 0x0000 ); // reserved +} + +void WMFWriter::UpdateHeader() +{ + sal_uLong nPos; + sal_uInt32 nFileSize; + + nPos=pWMF->Tell(); // endposition = total size of file + nFileSize=nPos-nMetafileHeaderPos; // subtract size of 1st header + if ((nFileSize&1)!=0) { // if needed round to words + pWMF->WriteUChar( 0 ); + nPos++; + nFileSize++; + } + nFileSize>>=1; // convert to number of words + pWMF->Seek(nMetafileHeaderPos+6); // to filesize entry in second header + pWMF->WriteUInt32( nFileSize ); // rectify file size + pWMF->SeekRel(2); // to max-record-length-entry in second header + pWMF->WriteUInt32( nMaxRecordSize ); // and rectify + pWMF->Seek(nPos); +} + +bool WMFWriter::WriteWMF( const GDIMetaFile& rMTF, SvStream& rTargetStream, + FilterConfigItem const * pFConfigItem, bool bPlaceable ) +{ + WMFWriterAttrStackMember * pAt; + + bEmbedEMF = true; + bStatus=true; + pVirDev = VclPtr<VirtualDevice>::Create(); + + if (pFConfigItem) + { + xStatusIndicator = pFConfigItem->GetStatusIndicator(); + if ( xStatusIndicator.is() ) + { + xStatusIndicator->start( OUString(), 100 ); + } + + comphelper::SequenceAsHashMap aMap(pFConfigItem->GetFilterData()); + auto it = aMap.find("EmbedEMF"); + if (it != aMap.end()) + { + it->second >>= bEmbedEMF; + } + } + nLastPercent=0; + + pWMF=&rTargetStream; + pWMF->SetEndian(SvStreamEndian::LITTLE); + + nMaxRecordSize=0; + + aSrcMapMode=rMTF.GetPrefMapMode(); + + if( bPlaceable ) + { + aTargetMapMode = aSrcMapMode; + aTargetSize = rMTF.GetPrefSize(); + sal_uInt16 nTargetDivisor = CalcSaveTargetMapMode(aTargetMapMode, aTargetSize); + aTargetSize.setWidth( aTargetSize.Width() / nTargetDivisor ); + aTargetSize.setHeight( aTargetSize.Height() / nTargetDivisor ); + } + else + { + aTargetMapMode = MapMode( MapUnit::MapInch ); + + const tools::Long nUnit = pVirDev->LogicToPixel( Size( 1, 1 ), aTargetMapMode ).Width(); + const Fraction aFrac( 1, nUnit ); + + aTargetMapMode.SetScaleX( aFrac ); + aTargetMapMode.SetScaleY( aFrac ); + aTargetSize = OutputDevice::LogicToLogic( rMTF.GetPrefSize(), aSrcMapMode, aTargetMapMode ); + } + + pVirDev->SetMapMode( aTargetMapMode ); + + pAttrStack=nullptr; + + for (bool & rn : bHandleAllocated) + rn=false; + + nDstPenHandle=0xffff; + nDstFontHandle=0xffff; + nDstBrushHandle=0xffff; + + nNumberOfActions=0; + nNumberOfBitmaps=0; + nWrittenActions=0; + nWrittenBitmaps=0; + nActBitmapPercent=0; + + CountActionsAndBitmaps(rMTF); + + WriteHeader(bPlaceable); + if( bEmbedEMF ) + WriteEmbeddedEMF( rMTF ); + WMFRecord_SetWindowOrg(Point(0,0)); + WMFRecord_SetWindowExt(rMTF.GetPrefSize()); + WMFRecord_SetBkMode( true ); + + eDstROP2 = eSrcRasterOp = RasterOp::OverPaint; + WMFRecord_SetROP2(eDstROP2); + + aDstLineInfo = LineInfo(); + aDstLineColor = aSrcLineColor = COL_BLACK; + CreateSelectDeletePen( aDstLineColor, aDstLineInfo ); + + aDstFillColor = aSrcFillColor = COL_WHITE; + CreateSelectDeleteBrush( aDstFillColor ); + + aDstClipRegion = aSrcClipRegion = vcl::Region(); + + vcl::Font aFont; + aFont.SetCharSet( GetExtendedTextEncoding( RTL_TEXTENCODING_MS_1252 ) ); + aFont.SetColor( COL_WHITE ); + aFont.SetAlignment( ALIGN_BASELINE ); + aDstFont = aSrcFont = aFont; + CreateSelectDeleteFont(aDstFont); + + eDstTextAlign = eSrcTextAlign = ALIGN_BASELINE; + eDstHorTextAlign = eSrcHorTextAlign = W_TA_LEFT; + WMFRecord_SetTextAlign( eDstTextAlign, eDstHorTextAlign ); + + aDstTextColor = aSrcTextColor = COL_WHITE; + WMFRecord_SetTextColor(aDstTextColor); + + // Write records + WriteRecords(rMTF); + + WriteRecordHeader(0x00000003,0x0000); // end of file + UpdateHeader(); + + while(pAttrStack) + { + pAt=pAttrStack; + pAttrStack=pAt->pSucc; + delete pAt; + } + + pVirDev.disposeAndClear(); + + if ( xStatusIndicator.is() ) + xStatusIndicator->end(); + + return bStatus; +} + +sal_uInt16 WMFWriter::CalcSaveTargetMapMode(MapMode& rMapMode, + const Size& rPrefSize) +{ + Fraction aDivFrac(2, 1); + sal_uInt16 nDivisor = 1; + + Size aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode ); + + while( nDivisor <= 64 && (aSize.Width() > 32767 || aSize.Height() > 32767) ) + { + Fraction aFrac = rMapMode.GetScaleX(); + + aFrac *= aDivFrac; + rMapMode.SetScaleX(aFrac); + aFrac = rMapMode.GetScaleY(); + aFrac *= aDivFrac; + rMapMode.SetScaleY(aFrac); + nDivisor <<= 1; + aSize = OutputDevice::LogicToLogic( rPrefSize, aSrcMapMode, rMapMode ); + } + + return nDivisor; +} + +void WMFWriter::WriteEmbeddedEMF( const GDIMetaFile& rMTF ) +{ + SvMemoryStream aStream; + EMFWriter aEMFWriter(aStream); + + if( !aEMFWriter.WriteEMF( rMTF ) ) + return; + + sal_uInt64 const nTotalSize = aStream.Tell(); + if( nTotalSize > SAL_MAX_UINT32 ) + return; + aStream.Seek( 0 ); + sal_uInt32 nRemainingSize = static_cast< sal_uInt32 >( nTotalSize ); + sal_uInt32 nRecCounts = ( (nTotalSize - 1) / 0x2000 ) + 1; + sal_uInt16 nCheckSum = 0, nWord; + + sal_uInt32 nPos = 0; + + while( nPos + 1 < nTotalSize ) + { + aStream.ReadUInt16( nWord ); + nCheckSum ^= nWord; + nPos += 2; + } + + nCheckSum = static_cast< sal_uInt16 >( nCheckSum * -1 ); + + aStream.Seek( 0 ); + while( nRemainingSize > 0 ) + { + sal_uInt32 nCurSize; + if( nRemainingSize > 0x2000 ) + { + nCurSize = 0x2000; + nRemainingSize -= 0x2000; + } + else + { + nCurSize = nRemainingSize; + nRemainingSize = 0; + } + WriteEMFRecord( aStream, + nCurSize, + nRemainingSize, + nTotalSize, + nRecCounts, + nCheckSum ); + nCheckSum = 0; + } + +} + +void WMFWriter::WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize, sal_uInt32 nRemainingSize, + sal_uInt32 nTotalSize, sal_uInt32 nRecCounts, sal_uInt16 nCheckSum ) +{ + // according to http://msdn.microsoft.com/en-us/library/dd366152%28PROT.13%29.aspx + WriteRecordHeader( 0, W_META_ESCAPE ); + pWMF->WriteUInt16( W_MFCOMMENT ) // same as META_ESCAPE_ENHANCED_METAFILE + .WriteUInt16( nCurSize + 34 ) // we will always have a 34 byte escape header: + .WriteUInt32( 0x43464D57 ) // WMFC + .WriteUInt32( 0x00000001 ) // Comment type + .WriteUInt32( 0x00010000 ) // version + .WriteUInt16( nCheckSum ) // check sum + .WriteUInt32( 0 ) // flags = 0 + .WriteUInt32( nRecCounts ) // total number of records + .WriteUInt32( nCurSize ) // size of this record's data + .WriteUInt32( nRemainingSize ) // remaining size of data in following records, missing in MSDN documentation + .WriteUInt32( nTotalSize ); // total size of EMF stream + + pWMF->WriteBytes(static_cast<const char*>(rStream.GetData()) + rStream.Tell(), nCurSize); + rStream.SeekRel( nCurSize ); + UpdateRecordHeader(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/wmf/wmfwr.hxx b/vcl/source/filter/wmf/wmfwr.hxx new file mode 100644 index 000000000..7b0ce679e --- /dev/null +++ b/vcl/source/filter/wmf/wmfwr.hxx @@ -0,0 +1,205 @@ +/* -*- 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_VCL_SOURCE_FILTER_WMF_WMFWR_HXX +#define INCLUDED_VCL_SOURCE_FILTER_WMF_WMFWR_HXX + +#include <vcl/gdimtf.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/virdev.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <tools/stream.hxx> + +#define MAXOBJECTHANDLES 16 + +struct WMFWriterAttrStackMember +{ + struct WMFWriterAttrStackMember * pSucc; + Color aLineColor; + Color aFillColor; + Color aTextColor; + LineInfo aLineInfo; + TextAlign eTextAlign; + RasterOp eRasterOp; + vcl::Font aFont; + MapMode aMapMode; + vcl::Region aClipRegion; + vcl::PushFlags nFlags; +}; + +class StarSymbolToMSMultiFont; +class LineInfo; +namespace basegfx { class B2DPolygon; } + +class WMFWriter +{ +private: + + bool bStatus; + + sal_uLong nLastPercent; // with which number pCallback was called last time. + + css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator; + + SvStream* pWMF; + VclPtr<VirtualDevice> pVirDev; + MapMode aTargetMapMode; + Size aTargetSize; + + sal_uLong nMetafileHeaderPos; + sal_uInt32 nMaxRecordSize; // in words + sal_uLong nActRecordPos; + + // actual attribute in source metafile: + Color aSrcLineColor; + Color aSrcFillColor; + Color aSrcTextColor; + LineInfo aSrcLineInfo; + RasterOp eSrcRasterOp; + TextAlign eSrcTextAlign; + vcl::Font aSrcFont; + MapMode aSrcMapMode; + vcl::Region aSrcClipRegion; + WMFWriterAttrStackMember * pAttrStack; + + sal_uInt16 eSrcHorTextAlign; + + // actual attribute in destination metafile: + Color aDstLineColor; + Color aDstFillColor; + Color aDstTextColor; + LineInfo aDstLineInfo; + RasterOp eDstROP2; + TextAlign eDstTextAlign; + vcl::Font aDstFont; + + sal_uInt16 eDstHorTextAlign; + + vcl::Region aDstClipRegion; // ???: not taken into account at the moment + bool bHandleAllocated[MAXOBJECTHANDLES]; // which handles have been assigned + sal_uInt16 nDstPenHandle,nDstFontHandle,nDstBrushHandle; // which handles are owned by + // Selected-Objects + // 0xFFFF = none: + + // to prevent we have to compare all attributes at each operation: + + sal_uLong nNumberOfActions; // number of actions in the GDIMetafile + sal_uLong nNumberOfBitmaps; // number of bitmaps + sal_uLong nWrittenActions; // number of processed actions while writing the directory + sal_uLong nWrittenBitmaps; // number of bitmaps written + sal_uLong nActBitmapPercent; // percentage of next bitmap written. + + bool bEmbedEMF; // optionally embed EMF data into WMF + + void MayCallback(); + // this function calculates percentage using the above 5 parameters + // and triggers a callback if needed. Puts bStatus to FALSE if the + // users wants to abort. + + void CountActionsAndBitmaps(const GDIMetaFile & rMTF); + // Counts bitmaps and actions (nNumberOfActions and nNumberOfBitmaps should + // be initialised to 0 at start, as this method is recursive) + + void WritePointXY(const Point & rPoint); + void WritePointYX(const Point & rPoint); + sal_Int32 ScaleWidth( sal_Int32 nDX ); + void WriteSize(const Size & rSize); + void WriteHeightWidth(const Size & rSize); + void WriteRectangle(const tools::Rectangle & rRect); + void WriteColor(const Color & rColor); + + void WriteRecordHeader(sal_uInt32 nSizeWords, sal_uInt16 nType); + // nSizeWords is the size of the all records in number of words. + // If nSizeWords is unknown, then use 0 (see UpdateRecordHeader()) + + void UpdateRecordHeader(); + // returns the size of the record after writing the parameters, if + // nSizeWords was unknown upon calling WriteRecordHeader(..) + // if needed it inserts a BYTE 0 to make number of bytes even + + void WMFRecord_Arc(const tools::Rectangle& rRect, const Point& rStartPt, const Point& rEndPt); + void WMFRecord_Chord(const tools::Rectangle& rRect, const Point& rStartPt, const Point& rEndPt); + void WMFRecord_CreateBrushIndirect(const Color& rColor); + void WMFRecord_CreateFontIndirect(const vcl::Font& rFont); + void WMFRecord_CreatePenIndirect(const Color& rColor, const LineInfo& rLineInfo ); + void WMFRecord_DeleteObject(sal_uInt16 nObjectHandle); + void WMFRecord_Ellipse(const tools::Rectangle& rRect); + void WMFRecord_Escape( sal_uInt32 nEsc, sal_uInt32 nLen, const sal_Int8* pData ); + bool WMFRecord_Escape_Unicode( const Point& rPoint, const OUString& rStr, o3tl::span<const sal_Int32> pDXAry ); + void WMFRecord_ExtTextOut(const Point& rPoint, const OUString& rString, o3tl::span<const sal_Int32> pDXAry); + + void TrueExtTextOut(const Point& rPoint, const OUString& rString, + const OString& rByteString, o3tl::span<const sal_Int32> pDXAry); + void TrueTextOut(const Point& rPoint, const OString& rString); + void WMFRecord_LineTo(const Point & rPoint); + void WMFRecord_MoveTo(const Point & rPoint); + void WMFRecord_Pie(const tools::Rectangle & rRect, const Point & rStartPt, const Point & rEndPt); + void WMFRecord_Polygon(const tools::Polygon & rPoly); + void WMFRecord_PolyLine(const tools::Polygon & rPoly); + void WMFRecord_PolyPolygon(const tools::PolyPolygon & rPolyPoly); + void WMFRecord_Rectangle(const tools::Rectangle & rRect); + void WMFRecord_RestoreDC(); + void WMFRecord_RoundRect(const tools::Rectangle & rRect, tools::Long nHorzRound, tools::Long nVertRound); + void WMFRecord_SaveDC(); + void WMFRecord_SelectObject(sal_uInt16 nObjectHandle); + void WMFRecord_SetBkMode(bool bTransparent); + void WMFRecord_SetStretchBltMode(); + void WMFRecord_SetPixel(const Point & rPoint, const Color & rColor); + void WMFRecord_SetROP2(RasterOp eROP); + void WMFRecord_SetTextAlign(TextAlign eFontAlign, sal_uInt16 eHorTextAlign); + void WMFRecord_SetTextColor(const Color & rColor); + void WMFRecord_SetWindowExt(const Size & rSize); + void WMFRecord_SetWindowOrg(const Point & rPoint); + void WMFRecord_StretchDIB(const Point & rPoint, const Size & rSize, const Bitmap & rBitmap, sal_uInt32 nROP = 0 ); + void WMFRecord_TextOut(const Point & rPoint, std::u16string_view rString); + void WMFRecord_IntersectClipRect( const tools::Rectangle& rRect); + + sal_uInt16 AllocHandle(); + void FreeHandle(sal_uInt16 nObjectHandle); + void CreateSelectDeletePen( const Color& rColor, const LineInfo& rLineInfo ); + void CreateSelectDeleteFont(const vcl::Font & rFont); + void CreateSelectDeleteBrush(const Color& rColor); + + void SetLineAndFillAttr(); + void SetAllAttr(); + + void HandleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon); + void WriteRecords(const GDIMetaFile & rMTF); + + void WriteHeader(bool bPlaceable); + void UpdateHeader(); + + void WriteEmbeddedEMF( const GDIMetaFile& rMTF ); + void WriteEMFRecord( SvMemoryStream& rStream, sal_uInt32 nCurSize, + sal_uInt32 nRemainingSize, + sal_uInt32 nTotalSize, + sal_uInt32 nRecCounts, + sal_uInt16 nCheckSum ); + + sal_uInt16 CalcSaveTargetMapMode(MapMode& rMapMode, const Size& rPrefSize); + +public: + WMFWriter(); + bool WriteWMF(const GDIMetaFile & rMTF, SvStream & rTargetStream, FilterConfigItem const * pFilterConfigItem, bool bPlaceable); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |