diff options
34 files changed, 5777 insertions, 2 deletions
diff --git a/Repository.mk b/Repository.mk index c3c3378ecdea..c148cf315d6c 100644 --- a/Repository.mk +++ b/Repository.mk @@ -282,6 +282,7 @@ $(eval $(call gb_Helper_register_plugins_for_install,OOOLIBS,calc, \ $(eval $(call gb_Helper_register_libraries_for_install,OOOLIBS,graphicfilter, \ svgfilter \ + flash \ wpftdraw \ graphicfilter \ )) diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 967264222b95..7d13a476a3f1 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -380,6 +380,7 @@ constexpr ExtensionMap aImpressExtensionMap[] = { "svg", u"impress_svg_Export"_ustr }, { "xhtml", u"XHTML Impress File"_ustr }, { "png", u"impress_png_Export"_ustr }, + { "swf", u"impress_flash_Export"_ustr }, { "bmp", u"impress_bmp_Export"_ustr }, { "gif", u"impress_gif_Export"_ustr }, { "tif", u"impress_tif_Export"_ustr }, diff --git a/filter/Configuration_filter.mk b/filter/Configuration_filter.mk index 29d503c0cc33..6407f162c906 100644 --- a/filter/Configuration_filter.mk +++ b/filter/Configuration_filter.mk @@ -702,6 +702,7 @@ $(eval $(call filter_Configuration_add_types,fcfg_langpack,fcfg_drawgraphics_typ eps_Encapsulated_PostScript \ gif_Graphics_Interchange \ graphic_HTML \ + graphic_SWF \ jpg_JPEG \ met_OS2_Metafile \ mov_MOV \ @@ -767,6 +768,7 @@ $(eval $(call filter_Configuration_add_filters,fcfg_langpack,fcfg_drawgraphics_f draw_emf_Export \ draw_emz_Export \ draw_eps_Export \ + draw_flash_Export \ draw_gif_Export \ draw_html_Export \ draw_jpg_Export \ @@ -787,6 +789,7 @@ $(eval $(call filter_Configuration_add_types,fcfg_langpack,fcfg_impressgraphics_ eps_Encapsulated_PostScript \ gif_Graphics_Interchange \ graphic_HTML \ + graphic_SWF \ impress_CGM_Computer_Graphics_Metafile \ jpg_JPEG \ met_OS2_Metafile \ @@ -812,6 +815,7 @@ $(eval $(call filter_Configuration_add_filters,fcfg_langpack,fcfg_impressgraphic impress_bmp_Export \ impress_emf_Export \ impress_eps_Export \ + impress_flash_Export \ impress_gif_Export \ impress_html_Export \ impress_jpg_Export \ diff --git a/filter/Library_flash.mk b/filter/Library_flash.mk new file mode 100644 index 000000000000..2ad3c5538198 --- /dev/null +++ b/filter/Library_flash.mk @@ -0,0 +1,56 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +$(eval $(call gb_Library_Library,flash)) + +$(eval $(call gb_Library_set_componentfile,flash,filter/source/flash/flash,services)) + +$(eval $(call gb_Library_use_sdk_api,flash)) + +$(eval $(call gb_Library_use_libraries,flash,\ + svt \ + vcl \ + utl \ + tl \ + i18nlangtag \ + comphelper \ + basegfx \ + cppuhelper \ + cppu \ + sal \ + salhelper \ +)) + +$(eval $(call gb_Library_use_externals,flash,\ + boost_headers \ + zlib \ +)) + +$(eval $(call gb_Library_add_exception_objects,flash,\ + filter/source/flash/impswfdialog \ + filter/source/flash/swfdialog \ + filter/source/flash/swfexporter \ + filter/source/flash/swffilter \ + filter/source/flash/swfuno \ + filter/source/flash/swfwriter \ + filter/source/flash/swfwriter1 \ + filter/source/flash/swfwriter2 \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/filter/Module_filter.mk b/filter/Module_filter.mk index c28c72705cec..23fec0d58a01 100644 --- a/filter/Module_filter.mk +++ b/filter/Module_filter.mk @@ -22,6 +22,7 @@ $(eval $(call gb_Module_add_targets,filter,\ Configuration_filter \ CustomTarget_svg \ Library_filterconfig \ + Library_flash \ Library_icg \ Library_msfilter \ Library_odfflatxml \ diff --git a/filter/UIConfig_filter.mk b/filter/UIConfig_filter.mk index 7d34128f56e0..34421120a05a 100644 --- a/filter/UIConfig_filter.mk +++ b/filter/UIConfig_filter.mk @@ -10,6 +10,7 @@ $(eval $(call gb_UIConfig_UIConfig,filter)) $(eval $(call gb_UIConfig_add_uifiles,filter,\ + filter/uiconfig/ui/impswfdialog \ filter/uiconfig/ui/pdfgeneralpage \ filter/uiconfig/ui/pdflinkspage \ filter/uiconfig/ui/pdfoptionsdialog \ diff --git a/filter/qa/unit/data/filter-dialogs-test.txt b/filter/qa/unit/data/filter-dialogs-test.txt index 22792ad28e6a..1f1715a194f8 100644 --- a/filter/qa/unit/data/filter-dialogs-test.txt +++ b/filter/qa/unit/data/filter-dialogs-test.txt @@ -44,6 +44,7 @@ filter/ui/pdflinkspage.ui filter/ui/pdfsignpage.ui filter/ui/xmlfiltertabpagegeneral.ui filter/ui/xmlfiltertabpagetransformation.ui +filter/ui/impswfdialog.ui filter/ui/testxmlfilter.ui filter/ui/warnpdfdialog.ui filter/ui/xmlfiltersettings.ui diff --git a/filter/source/config/cache/typedetection.cxx b/filter/source/config/cache/typedetection.cxx index 66706a027259..b45aee107e39 100644 --- a/filter/source/config/cache/typedetection.cxx +++ b/filter/source/config/cache/typedetection.cxx @@ -284,6 +284,7 @@ int getFlatTypeRank(std::u16string_view rType) // Export only "writer_layout_dump_xml", "writer_indexing_export", + "graphic_SWF", "graphic_HTML", // Internal use only diff --git a/filter/source/config/fragments/filters/draw_flash_Export.xcu b/filter/source/config/fragments/filters/draw_flash_Export.xcu new file mode 100644 index 000000000000..36e094c9bec9 --- /dev/null +++ b/filter/source/config/fragments/filters/draw_flash_Export.xcu @@ -0,0 +1,30 @@ +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . +--> + <node oor:name="draw_flash_Export" oor:op="replace"> + <prop oor:name="Flags"><value>EXPORT ALIEN 3RDPARTYFILTER</value></prop> + <prop oor:name="UIComponent"><value>com.sun.star.Impress.FlashExportDialog</value></prop> + <prop oor:name="FilterService"><value>com.sun.star.comp.Impress.FlashExportFilter</value></prop> + <prop oor:name="UserData"><value></value></prop> + <prop oor:name="UIName"> + <value xml:lang="en-US">Macromedia Flash (SWF)</value> + </prop> + <prop oor:name="FileFormatVersion"><value>0</value></prop> + <prop oor:name="Type"><value>graphic_SWF</value></prop> + <prop oor:name="TemplateName"/> + <prop oor:name="DocumentService"><value>com.sun.star.drawing.DrawingDocument</value></prop> + </node> diff --git a/filter/source/config/fragments/filters/impress_flash_Export.xcu b/filter/source/config/fragments/filters/impress_flash_Export.xcu new file mode 100644 index 000000000000..0cb961f7d287 --- /dev/null +++ b/filter/source/config/fragments/filters/impress_flash_Export.xcu @@ -0,0 +1,30 @@ +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . +--> + <node oor:name="impress_flash_Export" oor:op="replace"> + <prop oor:name="Flags"><value>EXPORT ALIEN 3RDPARTYFILTER</value></prop> + <prop oor:name="UIComponent"><value>com.sun.star.Impress.FlashExportDialog</value></prop> + <prop oor:name="FilterService"><value>com.sun.star.comp.Impress.FlashExportFilter</value></prop> + <prop oor:name="UserData"><value></value></prop> + <prop oor:name="UIName"> + <value xml:lang="en-US">Macromedia Flash (SWF)</value> + </prop> + <prop oor:name="FileFormatVersion"><value>0</value></prop> + <prop oor:name="Type"><value>graphic_SWF</value></prop> + <prop oor:name="TemplateName"/> + <prop oor:name="DocumentService"><value>com.sun.star.presentation.PresentationDocument</value></prop> + </node> diff --git a/filter/source/config/fragments/types/graphic_SWF.xcu b/filter/source/config/fragments/types/graphic_SWF.xcu new file mode 100644 index 000000000000..66f503d08623 --- /dev/null +++ b/filter/source/config/fragments/types/graphic_SWF.xcu @@ -0,0 +1,29 @@ +<!-- + * 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 . +--> + <node oor:name="graphic_SWF" oor:op="replace" > + <prop oor:name="DetectService"/> + <prop oor:name="URLPattern"/> + <prop oor:name="Extensions"><value>swf</value></prop> + <prop oor:name="MediaType"/> + <prop oor:name="Preferred"><value>false</value></prop> + <prop oor:name="PreferredFilter"/> + <prop oor:name="UIName"> + <value>Macromedia Flash (SWF)</value> + </prop> + <prop oor:name="ClipboardFormat"/> + </node> diff --git a/filter/source/flash/flash.component b/filter/source/flash/flash.component new file mode 100644 index 000000000000..879466efb301 --- /dev/null +++ b/filter/source/flash/flash.component @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + prefix="flash" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.Impress.FlashExportDialog"> + <service name="com.sun.star.Impress.FlashExportDialog"/> + </implementation> + <implementation name="com.sun.star.comp.Impress.FlashExportFilter"> + <service name="com.sun.star.document.ExportFilter"/> + </implementation> +</component> diff --git a/filter/source/flash/impswfdialog.cxx b/filter/source/flash/impswfdialog.cxx new file mode 100644 index 000000000000..5e50c2c4f1b6 --- /dev/null +++ b/filter/source/flash/impswfdialog.cxx @@ -0,0 +1,78 @@ +/* -*- 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 "impswfdialog.hxx" +#include <tools/solar.h> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + +ImpSWFDialog::ImpSWFDialog(weld::Window* pParent, Sequence<PropertyValue>& rFilterData) + : GenericDialogController(pParent, "filter/ui/impswfdialog.ui", "ImpSWFDialog") + , maConfigItem(u"Office.Common/Filter/Flash/Export/", &rFilterData) + , mxNumFldQuality(m_xBuilder->weld_spin_button("quality")) + , mxCheckExportAll(m_xBuilder->weld_check_button("exportall")) + , mxCheckExportBackgrounds(m_xBuilder->weld_check_button("exportbackgrounds")) + , mxCheckExportBackgroundObjects(m_xBuilder->weld_check_button("exportbackgroundobjects")) + , mxCheckExportSlideContents(m_xBuilder->weld_check_button("exportslidecontents")) + , mxCheckExportSound(m_xBuilder->weld_check_button("exportsound")) + , mxCheckExportOLEAsJPEG(m_xBuilder->weld_check_button("exportoleasjpeg")) + , mxCheckExportMultipleFiles(m_xBuilder->weld_check_button("exportmultiplefiles")) +{ + const sal_Int32 nCompressMode = maConfigItem.ReadInt32("CompressMode", 75); + mxNumFldQuality->set_value(nCompressMode); + + mxCheckExportAll->set_active(true); + mxCheckExportSlideContents->set_active(true); + mxCheckExportSound->set_active(true); + + mxCheckExportAll->connect_toggled(LINK(this, ImpSWFDialog, OnToggleCheckbox)); + + mxCheckExportBackgrounds->set_sensitive(false); + mxCheckExportBackgroundObjects->set_sensitive(false); + mxCheckExportSlideContents->set_sensitive(false); +} + +ImpSWFDialog::~ImpSWFDialog() {} + +Sequence<PropertyValue> ImpSWFDialog::GetFilterData() +{ + sal_Int32 nCompressMode = static_cast<sal_Int32>(mxNumFldQuality->get_value()); + maConfigItem.WriteInt32("CompressMode", nCompressMode); + maConfigItem.WriteBool("ExportAll", mxCheckExportAll->get_active()); + maConfigItem.WriteBool("ExportBackgrounds", mxCheckExportBackgrounds->get_active()); + maConfigItem.WriteBool("ExportBackgroundObjects", mxCheckExportBackgroundObjects->get_active()); + maConfigItem.WriteBool("ExportSlideContents", mxCheckExportSlideContents->get_active()); + maConfigItem.WriteBool("ExportSound", mxCheckExportSound->get_active()); + maConfigItem.WriteBool("ExportOLEAsJPEG", mxCheckExportOLEAsJPEG->get_active()); + maConfigItem.WriteBool("ExportMultipleFiles", mxCheckExportMultipleFiles->get_active()); + + Sequence<PropertyValue> aRet(maConfigItem.GetFilterData()); + + return aRet; +} + +IMPL_LINK_NOARG(ImpSWFDialog, OnToggleCheckbox, weld::Toggleable&, void) +{ + mxCheckExportBackgrounds->set_sensitive(!mxCheckExportBackgrounds->get_sensitive()); + mxCheckExportBackgroundObjects->set_sensitive(!mxCheckExportBackgroundObjects->get_sensitive()); + mxCheckExportSlideContents->set_sensitive(!mxCheckExportSlideContents->get_sensitive()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/impswfdialog.hxx b/filter/source/flash/impswfdialog.hxx new file mode 100644 index 000000000000..b48cbaf5d980 --- /dev/null +++ b/filter/source/flash/impswfdialog.hxx @@ -0,0 +1,53 @@ +/* -*- 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_FLASH_IMPSWFDIALOG_HXX +#define INCLUDED_FILTER_SOURCE_FLASH_IMPSWFDIALOG_HXX + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <vcl/FilterConfigItem.hxx> +#include <vcl/weld.hxx> + +class ImpSWFDialog : public weld::GenericDialogController +{ +private: + FilterConfigItem maConfigItem; + + std::unique_ptr<weld::SpinButton> mxNumFldQuality; + std::unique_ptr<weld::CheckButton> mxCheckExportAll; + std::unique_ptr<weld::CheckButton> mxCheckExportBackgrounds; + std::unique_ptr<weld::CheckButton> mxCheckExportBackgroundObjects; + std::unique_ptr<weld::CheckButton> mxCheckExportSlideContents; + std::unique_ptr<weld::CheckButton> mxCheckExportSound; + std::unique_ptr<weld::CheckButton> mxCheckExportOLEAsJPEG; + std::unique_ptr<weld::CheckButton> mxCheckExportMultipleFiles; + + DECL_LINK(OnToggleCheckbox, weld::Toggleable&, void); + +public: + ImpSWFDialog(weld::Window* pParent, css::uno::Sequence<css::beans::PropertyValue>& rFilterData); + virtual ~ImpSWFDialog() override; + + css::uno::Sequence<css::beans::PropertyValue> GetFilterData(); +}; + +#endif // INCLUDED_FILTER_SOURCE_FLASH_IMPSWFDIALOG_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfdialog.cxx b/filter/source/flash/swfdialog.cxx new file mode 100644 index 000000000000..11518d3f5f35 --- /dev/null +++ b/filter/source/flash/swfdialog.cxx @@ -0,0 +1,159 @@ +/* -*- 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 "swfdialog.hxx" +#include "swfuno.hxx" +#include "impswfdialog.hxx" +#include <comphelper/processfactory.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <com/sun/star/view/XRenderable.hpp> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::document; + +#define SERVICE_NAME "com.sun.star.Impress.FlashExportDialog" + +OUString SWFDialog_getImplementationName() { return SERVICE_NAME; } + +Sequence<OUString> SWFDialog_getSupportedServiceNames() +{ + Sequence<OUString> aRet{ SERVICE_NAME }; + return aRet; +} + +Reference<XInterface> SWFDialog_createInstance(const Reference<XMultiServiceFactory>& rSMgr) +{ + return static_cast<cppu::OWeakObject*>(new SWFDialog(comphelper::getComponentContext(rSMgr))); +} + +#undef SERVICE_NAME + +SWFDialog::SWFDialog(const Reference<XComponentContext>& rxContext) + : SWFDialog_Base(rxContext) +{ +} + +SWFDialog::~SWFDialog() {} + +OUString SAL_CALL SWFDialog::getImplementationName() { return SWFDialog_getImplementationName(); } + +Sequence<OUString> SAL_CALL SWFDialog::getSupportedServiceNames() +{ + return SWFDialog_getSupportedServiceNames(); +} + +std::unique_ptr<weld::DialogController> +SWFDialog::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) +{ + std::unique_ptr<weld::DialogController> xRet; + + if (mxSrcDoc.is()) + { + /* TODO: From the controller we may get information what page is visible and what shapes + are selected, if we optionally want to limit output to that + Any aSelection; + + try + { + Reference< XController > xController( Reference< XModel >( mxSrcDoc, UNO_QUERY )->getCurrentController() ); + + if( xController.is() ) + { + Reference< XSelectionSupplier > xView( xController, UNO_QUERY ); + + if( xView.is() ) + xView->getSelection() >>= aSelection; + } + } + catch( RuntimeException ) + { + } +*/ + xRet.reset(new ImpSWFDialog(Application::GetFrameWeld(rParent), maFilterData)); + } + + return xRet; +} + +void SWFDialog::executedDialog(sal_Int16 nExecutionResult) +{ + if (nExecutionResult && m_xDialog) + maFilterData = static_cast<ImpSWFDialog*>(m_xDialog.get())->GetFilterData(); + + destroyDialog(); +} + +Reference<XPropertySetInfo> SAL_CALL SWFDialog::getPropertySetInfo() +{ + Reference<XPropertySetInfo> xInfo(createPropertySetInfo(getInfoHelper())); + return xInfo; +} + +::cppu::IPropertyArrayHelper& SWFDialog::getInfoHelper() { return *getArrayHelper(); } + +::cppu::IPropertyArrayHelper* SWFDialog::createArrayHelper() const +{ + Sequence<Property> aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); +} + +Sequence<PropertyValue> SAL_CALL SWFDialog::getPropertyValues() +{ + sal_Int32 i, nCount; + + for (i = 0, nCount = maMediaDescriptor.getLength(); i < nCount; i++) + { + if (maMediaDescriptor[i].Name == "FilterData") + break; + } + + if (i == nCount) + maMediaDescriptor.realloc(++nCount); + + auto pMediaDescriptor = maMediaDescriptor.getArray(); + + pMediaDescriptor[i].Name = "FilterData"; + pMediaDescriptor[i].Value <<= maFilterData; + + return maMediaDescriptor; +} + +void SAL_CALL SWFDialog::setPropertyValues(const Sequence<PropertyValue>& rProps) +{ + maMediaDescriptor = rProps; + + for (sal_Int32 i = 0, nCount = maMediaDescriptor.getLength(); i < nCount; i++) + { + if (maMediaDescriptor[i].Name == "FilterData") + { + maMediaDescriptor[i].Value >>= maFilterData; + break; + } + } +} + +void SAL_CALL SWFDialog::setSourceDocument(const Reference<XComponent>& xDoc) { mxSrcDoc = xDoc; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfdialog.hxx b/filter/source/flash/swfdialog.hxx new file mode 100644 index 000000000000..04bea8cc2f32 --- /dev/null +++ b/filter/source/flash/swfdialog.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_FILTER_SOURCE_FLASH_SWFDIALOG_HXX +#define INCLUDED_FILTER_SOURCE_FLASH_SWFDIALOG_HXX + +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/document/XExporter.hpp> + +#include <comphelper/proparrhlp.hxx> +#include <svtools/genericunodialog.hxx> + +namespace vcl +{ +class Window; +} + +typedef cppu::ImplInheritanceHelper<::svt::OGenericUnoDialog, css::beans::XPropertyAccess, + css::document::XExporter> + SWFDialog_Base; +class SWFDialog final : public ::comphelper::OPropertyArrayUsageHelper<SWFDialog>, + public SWFDialog_Base +{ +private: + css::uno::Sequence<css::beans::PropertyValue> maMediaDescriptor; + css::uno::Sequence<css::beans::PropertyValue> maFilterData; + css::uno::Reference<css::lang::XComponent> mxSrcDoc; + + // OGenericUnoDialog + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + virtual std::unique_ptr<weld::DialogController> + createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + virtual void executedDialog(sal_Int16 nExecutionResult) override; + virtual css::uno::Reference<css::beans::XPropertySetInfo> + SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + virtual ::cppu::IPropertyArrayHelper* createArrayHelper() const override; + + // XPropertyAccess + using ::cppu::OPropertySetHelper::getPropertyValues; + using ::cppu::OPropertySetHelper::setPropertyValues; + virtual css::uno::Sequence<css::beans::PropertyValue> SAL_CALL getPropertyValues() override; + virtual void SAL_CALL + setPropertyValues(const css::uno::Sequence<css::beans::PropertyValue>& aProps) override; + + // XExporter + virtual void SAL_CALL + setSourceDocument(const css::uno::Reference<css::lang::XComponent>& xDoc) override; + +public: + explicit SWFDialog(const css::uno::Reference<css::uno::XComponentContext>& rxContext); + virtual ~SWFDialog() override; +}; + +#endif // INCLUDED_FILTER_SOURCE_FLASH_SWFDIALOG_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfexporter.cxx b/filter/source/flash/swfexporter.cxx new file mode 100644 index 000000000000..a0f1fd7207d2 --- /dev/null +++ b/filter/source/flash/swfexporter.cxx @@ -0,0 +1,729 @@ +/* -*- 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 <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> +#include <com/sun/star/drawing/XMasterPageTarget.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/task/XStatusIndicatorFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <vcl/gdimtf.hxx> +#include <unotools/tempfile.hxx> +#include <osl/diagnose.h> +#include <vcl/metaact.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/gdimetafiletools.hxx> +#include <memory> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/filter/SvmReader.hxx> + +#include "swfexporter.hxx" +#include "swfwriter.hxx" + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::task; +using namespace ::swf; + +using com::sun::star::io::XOutputStream; +using com::sun::star::beans::PropertyValue; +using com::sun::star::container::XIndexAccess; +using com::sun::star::beans::XPropertySet; +using com::sun::star::lang::XComponent; +using com::sun::star::lang::XServiceInfo; + +PageInfo::PageInfo() + : mnBackgroundID(0) + , mnObjectsID(0) + , mnForegroundID(0) +{ +} + +FlashExporter::FlashExporter(const Reference<XComponentContext>& rxContext, + + // #i56084# variables for selection export + const Reference<XShapes>& rxSelectedShapes, + const Reference<XDrawPage>& rxSelectedDrawPage, + + sal_Int32 nJPEGCompressMode, bool bExportOLEAsJPEG) + : mxContext(rxContext) + // #i56084# variables for selection export + , mxSelectedShapes(rxSelectedShapes) + , mxSelectedDrawPage(rxSelectedDrawPage) + , mbExportSelection(false) + + , mnDocWidth(0) + , mnDocHeight(0) + , mnJPEGcompressMode(nJPEGCompressMode) + , mbExportOLEAsJPEG(bExportOLEAsJPEG) + , mbPresentation(true) + , mnPageNumber(-1) +{ + if (mxSelectedDrawPage.is() && mxSelectedShapes.is() && mxSelectedShapes->getCount()) + { + // #i56084# determine export selection + mbExportSelection = true; + } +} + +FlashExporter::~FlashExporter() { Flush(); } + +void FlashExporter::Flush() +{ + mpWriter.reset(); + maPagesMap.clear(); +} + +const sal_uInt16 cBackgroundDepth = 2; +const sal_uInt16 cBackgroundObjectsDepth = 3; +const sal_uInt16 cPageObjectsDepth = 4; +const sal_uInt16 cWaitButtonDepth = 10; + +bool FlashExporter::exportAll(const Reference<XComponent>& xDoc, + Reference<XOutputStream> const& xOutputStream, + Reference<XStatusIndicator> const& xStatusIndicator) +{ + Reference<XServiceInfo> xDocServInfo(xDoc, UNO_QUERY); + if (xDocServInfo.is()) + mbPresentation + = xDocServInfo->supportsService("com.sun.star.presentation.PresentationDocument"); + + Reference<XDrawPagesSupplier> xDrawPagesSupplier(xDoc, UNO_QUERY); + if (!xDrawPagesSupplier.is()) + return false; + + Reference<XIndexAccess> xDrawPages = xDrawPagesSupplier->getDrawPages(); + if (!xDrawPages.is()) + return false; + + Reference<XDrawPage> xDrawPage; + + // #i56084# set xDrawPage directly when exporting selection + if (mbExportSelection) + { + xDrawPage = mxSelectedDrawPage; + } + else + { + xDrawPages->getByIndex(0) >>= xDrawPage; + } + + Reference<XPropertySet> xProp(xDrawPage, UNO_QUERY); + try + { + xProp->getPropertyValue("Width") >>= mnDocWidth; + xProp->getPropertyValue("Height") >>= mnDocHeight; + + sal_Int32 nOutputWidth = 14400; + sal_Int32 nOutputHeight = (nOutputWidth * mnDocHeight) / mnDocWidth; + mpWriter.reset( + new Writer(nOutputWidth, nOutputHeight, mnDocWidth, mnDocHeight, mnJPEGcompressMode)); + } + catch (const Exception&) + { + OSL_ASSERT(false); + return false; // no writer, no cookies + } + + // #i56084# nPageCount is 1 when exporting selection + const sal_Int32 nPageCount = mbExportSelection ? 1 : xDrawPages->getCount(); + + if (xStatusIndicator.is()) + { + xStatusIndicator->start("Macromedia Flash (SWF)", nPageCount); + } + + for (sal_Int32 nPage = 0; nPage < nPageCount; nPage++) + { + // #i56084# keep PageNumber? We could determine the PageNumber of the single to-be-exported page + // when exporting the selection, but this is only used for swf internal, so no need to do so (AFAIK) + mnPageNumber = nPage + 1; + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nPage); + + // #i56084# get current xDrawPage when not exporting selection; else already set above + if (!mbExportSelection) + { + xDrawPages->getByIndex(nPage) >>= xDrawPage; + } + + if (!xDrawPage.is()) + continue; + + Reference<XPropertySet> xPropSet(xDrawPage, UNO_QUERY); + if (mbPresentation) + { + bool bVisible = false; + xPropSet->getPropertyValue("Visible") >>= bVisible; + if (!bVisible) + continue; + } + + // #i56084# no background when exporting selection + if (!mbExportSelection) + { + exportBackgrounds(xDrawPage, nPage, false); + exportBackgrounds(xDrawPage, nPage, true); + } + + maPagesMap[nPage].mnForegroundID = mpWriter->startSprite(); + + // #i56084# directly export selection in export selection mode + if (mbExportSelection) + { + exportShapes(mxSelectedShapes, false, false); + } + else + { + exportDrawPageContents(xDrawPage, false, false); + } + + mpWriter->endSprite(); + + // AS: If the background is different than the previous slide, + // we have to remove the old one and place the new one. + if (nPage) + { + if (maPagesMap[nPage].mnBackgroundID != maPagesMap[nPage - 1].mnBackgroundID) + { + mpWriter->removeShape(cBackgroundDepth); + mpWriter->placeShape(maPagesMap[nPage].mnBackgroundID, cBackgroundDepth, 0, 0); + } + + if (maPagesMap[nPage].mnObjectsID != maPagesMap[nPage - 1].mnObjectsID) + { + mpWriter->removeShape(cBackgroundObjectsDepth); + mpWriter->placeShape(maPagesMap[nPage].mnObjectsID, cBackgroundObjectsDepth, 0, 0); + } + + // AS: Remove the Foreground of the previous slide. + mpWriter->removeShape(cPageObjectsDepth); + } + else + { + mpWriter->placeShape(maPagesMap[nPage].mnBackgroundID, cBackgroundDepth, 0, 0); + mpWriter->placeShape(maPagesMap[nPage].mnObjectsID, cBackgroundObjectsDepth, 0, 0); + } + + mpWriter->placeShape(maPagesMap[nPage].mnForegroundID, cPageObjectsDepth, 0, 0); + + mpWriter->waitOnClick(cWaitButtonDepth); + mpWriter->showFrame(); + } + + mpWriter->removeShape(cBackgroundDepth); + mpWriter->removeShape(cBackgroundObjectsDepth); + mpWriter->removeShape(cPageObjectsDepth); + mpWriter->gotoFrame(0); + mpWriter->showFrame(); + + mpWriter->storeTo(xOutputStream); + + return true; +} + +bool FlashExporter::exportSlides(const Reference<XDrawPage>& xDrawPage, + Reference<XOutputStream> const& xOutputStream) +{ + Reference<XPropertySet> xPropSet(xDrawPage, UNO_QUERY); + if (!xDrawPage.is() || !xPropSet.is()) + return false; + + try + { + if (!mpWriter) + { + xPropSet->getPropertyValue("Width") >>= mnDocWidth; + xPropSet->getPropertyValue("Height") >>= mnDocHeight; + + mpWriter.reset(new Writer(14400, 10800, mnDocWidth, mnDocHeight, mnJPEGcompressMode)); + } + + if (mbPresentation) + { + bool bVisible = false; + xPropSet->getPropertyValue("Visible") >>= bVisible; + if (!bVisible) + return false; + } + } + catch (const Exception&) + { + OSL_ASSERT(false); + } + + exportDrawPageContents(xDrawPage, true, false); + + mpWriter->storeTo(xOutputStream); + + return true; +} + +sal_uInt16 FlashExporter::exportBackgrounds(const Reference<XDrawPage>& xDrawPage, + Reference<XOutputStream> const& xOutputStream, + sal_uInt16 nPage, bool bExportObjects) +{ + Reference<XPropertySet> xPropSet(xDrawPage, UNO_QUERY); + if (!xDrawPage.is() || !xPropSet.is()) + return 0; + + if (!mpWriter) + { + xPropSet->getPropertyValue("Width") >>= mnDocWidth; + xPropSet->getPropertyValue("Height") >>= mnDocHeight; + + mpWriter.reset(new Writer(14400, 10800, mnDocWidth, mnDocHeight, mnJPEGcompressMode)); + } + + sal_uInt16 ret = exportBackgrounds(xDrawPage, nPage, bExportObjects); + + if (ret != nPage) + return ret; + + if (bExportObjects) + mpWriter->placeShape(maPagesMap[nPage].mnObjectsID, uInt16_(1), 0, 0); + else + mpWriter->placeShape(maPagesMap[nPage].mnBackgroundID, uInt16_(0), 0, 0); + + mpWriter->storeTo(xOutputStream); + + return nPage; +} + +sal_uInt16 FlashExporter::exportBackgrounds(Reference<XDrawPage> const& xDrawPage, sal_uInt16 nPage, + bool bExportObjects) +{ + Reference<XPropertySet> xPropSet(xDrawPage, UNO_QUERY); + if (!xDrawPage.is() || !xPropSet.is()) + return 0; + + bool bBackgroundVisible = true; + bool bBackgroundObjectsVisible = true; + + if (mbPresentation) + { + xPropSet->getPropertyValue("IsBackgroundVisible") >>= bBackgroundVisible; + xPropSet->getPropertyValue("IsBackgroundObjectsVisible") >>= bBackgroundObjectsVisible; + } + + if (bExportObjects) + { + if (bBackgroundObjectsVisible) + { + Reference<XMasterPageTarget> xMasterPageTarget(xDrawPage, UNO_QUERY); + if (!xMasterPageTarget.is()) + { + maPagesMap[nPage].mnObjectsID = 0xffff; + return 0xffff; + } + Reference<XDrawPage> aTemp = xMasterPageTarget->getMasterPage(); + sal_uInt16 ret = exportMasterPageObjects(nPage, aTemp); + if (ret != nPage) + return ret; + } + else + { + maPagesMap[nPage].mnObjectsID = 0xffff; + return 0xffff; + } + } + else + { + if (bBackgroundVisible) + { + sal_uInt16 ret = exportDrawPageBackground(nPage, xDrawPage); + + if (ret != nPage) + return ret; + } + else + { + maPagesMap[nPage].mnBackgroundID = 0xffff; + return 0xffff; + } + } + + return nPage; +} + +static sal_Int32 nPlaceDepth; +// AS: A Slide can have a private background or use its masterpage's background. +// We use the checksums on the metafiles to tell if backgrounds are the same and +// should be reused. The return value indicates which slide's background to use. +// If the return value != nPage, then there is no background (if == -1) or the +// background has already been exported. +sal_uInt16 FlashExporter::exportDrawPageBackground(sal_uInt16 nPage, + Reference<XDrawPage> const& xPage) +{ + sal_uInt16 rBackgroundID; + + GDIMetaFile aMtfPrivate, aMtfMaster; + Reference<XComponent> xComponent(xPage, UNO_QUERY); + + Reference<XMasterPageTarget> xMasterPageTarget(xPage, UNO_QUERY); + if (!xMasterPageTarget.is()) + return 0xffff; + + Reference<XDrawPage> xMasterPage = xMasterPageTarget->getMasterPage(); + if (!xMasterPage.is()) + return 0xffff; + + Reference<XComponent> xCompMaster(xMasterPage, UNO_QUERY); + + getMetaFile(xCompMaster, aMtfMaster, true); + getMetaFile(xComponent, aMtfPrivate, true); + + BitmapChecksum masterchecksum = SvmWriter::GetChecksum(aMtfMaster); + BitmapChecksum privatechecksum = SvmWriter::GetChecksum(aMtfPrivate); + + // AS: If the slide has its own background + if (privatechecksum) + { + ChecksumCache::iterator it = gPrivateCache.find(privatechecksum); + + // AS: and we've previously encountered this background, just return + // the previous index. + if (gPrivateCache.end() != it) + { + maPagesMap[nPage].mnBackgroundID = maPagesMap[it->second].mnBackgroundID; + return it->second; + } + else + { + // AS: Otherwise, cache this checksum. + gPrivateCache[privatechecksum] = nPage; + + rBackgroundID = mpWriter->defineShape(aMtfPrivate); + + maPagesMap[nPage].mnBackgroundID = rBackgroundID; + return nPage; + } + } + + // AS: Ok, no private background. Use the master page's. + // AS: Have we already exported this master page? + ChecksumCache::iterator it = gMasterCache.find(masterchecksum); + + if (gMasterCache.end() != it) + { + maPagesMap[nPage].mnBackgroundID = maPagesMap[it->second].mnBackgroundID; + + return it->second; // AS: Yes, so don't export it again. + } + + gMasterCache[masterchecksum] = nPage; + + rBackgroundID = mpWriter->defineShape(aMtfMaster); + + maPagesMap[nPage].mnBackgroundID = rBackgroundID; + + return nPage; +} + +sal_uInt16 FlashExporter::exportMasterPageObjects(sal_uInt16 nPage, + Reference<XDrawPage> const& xMasterPage) +{ + BitmapChecksum shapesum = ActionSummer(xMasterPage); + + ChecksumCache::iterator it = gObjectCache.find(shapesum); + + if (gObjectCache.end() != it) + { + maPagesMap[nPage].mnObjectsID = maPagesMap[it->second].mnObjectsID; + + return it->second; // AS: Yes, so don't export it again. + } + + gObjectCache[shapesum] = nPage; + + sal_uInt16 rObjectsID = mpWriter->startSprite(); + exportDrawPageContents(xMasterPage, false, true); + mpWriter->endSprite(); + + maPagesMap[nPage].mnObjectsID = rObjectsID; + + return nPage; +} + +/** export's the definition of the shapes inside this drawing page and adds the + shape infos to the current PageInfo */ +void FlashExporter::exportDrawPageContents(const Reference<XDrawPage>& xPage, bool bStream, + bool bMaster) +{ + exportShapes(xPage, bStream, bMaster); +} + +/** export's the definition of the shapes inside this XShapes container and adds the + shape infos to the current PageInfo */ +void FlashExporter::exportShapes(const Reference<XShapes>& xShapes, bool bStream, bool bMaster) +{ + OSL_ENSURE((xShapes->getCount() <= 0xffff), + "overflow in FlashExporter::exportDrawPageContents()"); + + sal_uInt16 nShapeCount + = static_cast<sal_uInt16>(std::min(xShapes->getCount(), sal_Int32(0xffff))); + sal_uInt16 nShape; + + Reference<XShape> xShape; + + for (nShape = 0; nShape < nShapeCount; nShape++) + { + xShapes->getByIndex(nShape) >>= xShape; + + if (xShape.is()) + { + Reference<XShapes> xShapes2(xShape, UNO_QUERY); + if (xShapes2.is() && xShape->getShapeType() == "com.sun.star.drawing.GroupShape") + // export the contents of group shapes, but we only ever stream at the top + // recursive level anyway, so pass false for streaming. + exportShapes(xShapes2, false, bMaster); + else + exportShape(xShape, bMaster); + } + + if (bStream) + mpWriter->showFrame(); + } +} + +/** export this shape definition and adds it's info to the current PageInfo */ +void FlashExporter::exportShape(const Reference<XShape>& xShape, bool bMaster) +{ + Reference<XPropertySet> xPropSet(xShape, UNO_QUERY); + if (!xPropSet.is()) + return; + + if (mbPresentation) + { + try + { + // skip empty presentation objects + bool bEmpty = false; + xPropSet->getPropertyValue("IsEmptyPresentationObject") >>= bEmpty; + if (bEmpty) + return; + + // don't export presentation placeholders on masterpage + // they can be non empty when user edits the default texts + if (bMaster) + { + OUString aShapeType(xShape->getShapeType()); + if (aShapeType == "com.sun.star.presentation.TitleTextShape" + || aShapeType == "com.sun.star.presentation.OutlinerShape" + || aShapeType == "com.sun.star.presentation.HeaderShape" + || aShapeType == "com.sun.star.presentation.FooterShape" + || aShapeType == "com.sun.star.presentation.SlideNumberShape" + || aShapeType == "com.sun.star.presentation.DateTimeShape") + return; + } + } + catch (const Exception&) + { + // TODO: If we are exporting a draw, this property is not available + } + } + + try + { + css::awt::Rectangle aBoundRect; + xPropSet->getPropertyValue("BoundRect") >>= aBoundRect; + + std::unique_ptr<ShapeInfo> pShapeInfo(new ShapeInfo()); + pShapeInfo->mnX = aBoundRect.X; + pShapeInfo->mnY = aBoundRect.Y; + pShapeInfo->mnWidth = aBoundRect.Width; + pShapeInfo->mnHeight = aBoundRect.Height; + + GDIMetaFile aMtf; + Reference<XComponent> xComponent(xShape, UNO_QUERY); + + bool bIsOleObject = xShape->getShapeType() == "com.sun.star.presentation.OLE2Shape" + || xShape->getShapeType() == "com.sun.star.drawing.OLE2Shape"; + + getMetaFile(xComponent, aMtf); + + // AS: If it's an OLE object, then export a JPEG if the user requested. + // In this case, we use the bounding rect info generated in the first getMetaFile + // call, and then clear the metafile and add a BMP action. This may be turned into + // a JPEG, depending on what gives the best compression. + if (bIsOleObject && mbExportOLEAsJPEG) + getMetaFile(xComponent, aMtf, false, true); + + sal_uInt16 nID; + BitmapChecksum checksum = SvmWriter::GetChecksum(aMtf); + + ChecksumCache::iterator it = gMetafileCache.find(checksum); + + if (gMetafileCache.end() != it) + nID = it->second; + else + { + nID = mpWriter->defineShape(aMtf); + gMetafileCache[checksum] = nID; + } + + if (!nID) + return; + + pShapeInfo->mnID = nID; + + // pPageInfo->addShape( pShapeInfo ); + + mpWriter->placeShape(pShapeInfo->mnID, uInt16_(nPlaceDepth++), pShapeInfo->mnX, + pShapeInfo->mnY); + } + catch (const Exception&) + { + OSL_ASSERT(false); + } +} + +bool FlashExporter::getMetaFile(Reference<XComponent> const& xComponent, GDIMetaFile& rMtf, + bool bOnlyBackground /* = false */, + bool bExportAsJPEG /* = false */) +{ + if (!mxGraphicExporter.is()) + mxGraphicExporter = GraphicExportFilter::create(mxContext); + + utl::TempFileNamed aFile; + aFile.EnableKillingFile(); + + Sequence<PropertyValue> aFilterData(bExportAsJPEG ? 3 : 2); + + auto pFilterData = aFilterData.getArray(); + + pFilterData[0].Name = "Version"; + pFilterData[0].Value <<= sal_Int32(6000); + pFilterData[1].Name = "PageNumber"; + pFilterData[1].Value <<= mnPageNumber; + + if (bExportAsJPEG) + { + pFilterData[2].Name = "Translucent"; + pFilterData[2].Value <<= true; + } + + Sequence<PropertyValue> aDescriptor(bOnlyBackground ? 4 : 3); + + auto pDescriptor = aDescriptor.getArray(); + + pDescriptor[0].Name = "FilterName"; + + // AS: If we've been asked to export as an image, then use the BMP filter. + // Otherwise, use SVM. This is useful for things that don't convert well as + // metafiles, like the occasional OLE object. + pDescriptor[0].Value <<= bExportAsJPEG ? OUString("PNG") : OUString("SVM"); + + pDescriptor[1].Name = "URL"; + pDescriptor[1].Value <<= aFile.GetURL(); + pDescriptor[2].Name = "FilterData"; + pDescriptor[2].Value <<= aFilterData; + if (bOnlyBackground) + { + pDescriptor[3].Name = "ExportOnlyBackground"; + pDescriptor[3].Value <<= bOnlyBackground; + } + mxGraphicExporter->setSourceDocument(xComponent); + mxGraphicExporter->filter(aDescriptor); + + if (bExportAsJPEG) + { + Graphic aGraphic; + GraphicFilter aFilter(false); + + aFilter.ImportGraphic(aGraphic, aFile.GetURL(), *aFile.GetStream(StreamMode::READ)); + BitmapEx rBitmapEx(aGraphic.GetBitmapEx().GetBitmap(), Color(255, 255, 255)); + + tools::Rectangle clipRect; + for (size_t i = 0, nCount = rMtf.GetActionSize(); i < nCount; i++) + { + const MetaAction* pAction = rMtf.GetAction(i); + if (pAction->GetType() == MetaActionType::ISECTRECTCLIPREGION) + { + const MetaISectRectClipRegionAction* pA + = static_cast<const MetaISectRectClipRegionAction*>(pAction); + clipRect = pA->GetRect(); + break; + } + } + MetaBmpExScaleAction* pmetaAct + = new MetaBmpExScaleAction(Point(clipRect.Left(), clipRect.Top()), + Size(clipRect.GetWidth(), clipRect.GetHeight()), rBitmapEx); + + rMtf.Clear(); + rMtf.AddAction(pmetaAct); + } + else + { + SvmReader aReader(*aFile.GetStream(StreamMode::READ)); + aReader.Read(rMtf); + + if (usesClipActions(rMtf)) + { + // #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(rMtf); + } + } + + return rMtf.GetActionSize() != 0; +} + +BitmapChecksum FlashExporter::ActionSummer(Reference<XShape> const& xShape) +{ + Reference<XShapes> xShapes(xShape, UNO_QUERY); + + if (xShapes.is()) + { + return ActionSummer(xShapes); + } + else + { + Reference<XComponent> xComponentShape(xShape, UNO_QUERY); + + GDIMetaFile aMtf; + getMetaFile(xComponentShape, aMtf); + + return SvmWriter::GetChecksum(aMtf); + } +} + +BitmapChecksum FlashExporter::ActionSummer(Reference<XShapes> const& xShapes) +{ + sal_uInt32 nShapeCount = xShapes->getCount(); + BitmapChecksum shapecount = 0; + + Reference<XShape> xShape2; + + for (sal_uInt32 nShape = 0; nShape < nShapeCount; nShape++) + { + xShapes->getByIndex(nShape) >>= xShape2; + + shapecount += ActionSummer(xShape2); + } + + return shapecount; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfexporter.hxx b/filter/source/flash/swfexporter.hxx new file mode 100644 index 000000000000..ffe17fbb8c76 --- /dev/null +++ b/filter/source/flash/swfexporter.hxx @@ -0,0 +1,148 @@ +/* -*- 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_FLASH_SWFEXPORTER_HXX +#define INCLUDED_FILTER_SOURCE_FLASH_SWFEXPORTER_HXX + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XGraphicExportFilter.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <vcl/checksum.hxx> + +#include <map> +#include <memory> + +typedef ::std::map<BitmapChecksum, sal_uInt16> ChecksumCache; + +class GDIMetaFile; + +namespace swf +{ +class Writer; + +class ShapeInfo +{ +public: + sal_uInt16 mnID; // the character id for the sprite definition of this shape + + sal_Int32 mnX; + sal_Int32 mnY; + + sal_Int32 mnWidth; + sal_Int32 mnHeight; + + ShapeInfo() + : mnID(0) + , mnX(0) + , mnY(0) + , mnWidth(0) + , mnHeight(0) + { + } +}; + +struct PageInfo +{ + sal_uInt16 mnBackgroundID; + sal_uInt16 mnObjectsID; + sal_uInt16 mnForegroundID; + + PageInfo(); +}; + +class FlashExporter +{ +public: + FlashExporter(const css::uno::Reference<css::uno::XComponentContext>& rxContext, + + // #i56084# variables for selection export + const css::uno::Reference<css::drawing::XShapes>& rxSelectedShapes, + const css::uno::Reference<css::drawing::XDrawPage>& rxSelectedDrawPage, + + sal_Int32 nJPEGCompressMode, bool bExportOLEAsJPEG); + ~FlashExporter(); + + void Flush(); + + bool exportAll(const css::uno::Reference<css::lang::XComponent>& xDoc, + css::uno::Reference<css::io::XOutputStream> const& xOutputStream, + css::uno::Reference<css::task::XStatusIndicator> const& xStatusIndicator); + bool exportSlides(const css::uno::Reference<css::drawing::XDrawPage>& xDrawPage, + css::uno::Reference<css::io::XOutputStream> const& xOutputStream); + sal_uInt16 exportBackgrounds(const css::uno::Reference<css::drawing::XDrawPage>& xDrawPage, + css::uno::Reference<css::io::XOutputStream> const& xOutputStream, + sal_uInt16 nPage, bool bExportObjects); + sal_uInt16 exportBackgrounds(css::uno::Reference<css::drawing::XDrawPage> const& xDrawPage, + sal_uInt16 nPage, bool bExportObjects); + + ChecksumCache gMasterCache; + ChecksumCache gPrivateCache; + ChecksumCache gObjectCache; + ChecksumCache gMetafileCache; + +private: + css::uno::Reference<css::uno::XComponentContext> mxContext; + + // #i56084# variables for selection export + const css::uno::Reference<css::drawing::XShapes> mxSelectedShapes; + const css::uno::Reference<css::drawing::XDrawPage> mxSelectedDrawPage; + bool mbExportSelection; + + css::uno::Reference<css::drawing::XGraphicExportFilter> mxGraphicExporter; + + ::std::map<sal_uInt32, PageInfo> maPagesMap; + + sal_uInt16 exportDrawPageBackground(sal_uInt16 nPage, + css::uno::Reference<css::drawing::XDrawPage> const& xPage); + sal_uInt16 + exportMasterPageObjects(sal_uInt16 nPage, + css::uno::Reference<css::drawing::XDrawPage> const& xMasterPage); + + void exportDrawPageContents(const css::uno::Reference<css::drawing::XDrawPage>& xPage, + bool bStream, bool bMaster); + void exportShapes(const css::uno::Reference<css::drawing::XShapes>& xShapes, bool bStream, + bool bMaster); + void exportShape(const css::uno::Reference<css::drawing::XShape>& xShape, bool bMaster); + + BitmapChecksum ActionSummer(css::uno::Reference<css::drawing::XShape> const& xShape); + BitmapChecksum ActionSummer(css::uno::Reference<css::drawing::XShapes> const& xShapes); + + bool getMetaFile(css::uno::Reference<css::lang::XComponent> const& xComponent, + GDIMetaFile& rMtf, bool bOnlyBackground = false, bool bExportAsJPEG = false); + + std::unique_ptr<Writer> mpWriter; + + sal_Int32 mnDocWidth; + sal_Int32 mnDocHeight; + + sal_Int32 mnJPEGcompressMode; + + bool mbExportOLEAsJPEG; + + bool mbPresentation; + + sal_Int32 mnPageNumber; +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swffilter.cxx b/filter/source/flash/swffilter.cxx new file mode 100644 index 000000000000..bdbaec86ce4a --- /dev/null +++ b/filter/source/flash/swffilter.cxx @@ -0,0 +1,518 @@ +/* -*- 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 <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <com/sun/star/document/XExporter.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/task/XStatusIndicatorFactory.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XOutputStream.hpp> + +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/processfactory.hxx> +#include <osl/file.hxx> + +#include "swfexporter.hxx" +#include "swfuno.hxx" + +#include <string.h> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::view; + +using ::com::sun::star::lang::XComponent; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::io::XOutputStream; +using ::com::sun::star::container::XIndexAccess; + +namespace swf +{ +namespace +{ +class OslOutputStreamWrapper : public ::cppu::WeakImplHelper<css::io::XOutputStream> +{ + osl::File maFile; + +public: + explicit OslOutputStreamWrapper(const OUString& rFileName) + : maFile(rFileName) + { + osl_removeFile(rFileName.pData); + (void)maFile.open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write); + } + + // css::io::XOutputStream + virtual void SAL_CALL writeBytes(const css::uno::Sequence<sal_Int8>& aData) override; + virtual void SAL_CALL flush() override; + virtual void SAL_CALL closeOutput() override; +}; +} + +void SAL_CALL OslOutputStreamWrapper::writeBytes(const css::uno::Sequence<sal_Int8>& aData) +{ + sal_uInt64 uBytesToWrite = aData.getLength(); + sal_uInt64 uBytesWritten = 0; + + sal_Int8 const* pBuffer = aData.getConstArray(); + + while (uBytesToWrite) + { + osl::File::RC eRC = maFile.write(pBuffer, uBytesToWrite, uBytesWritten); + + switch (eRC) + { + case osl::File::E_INVAL: // the format of the parameters was not valid + case osl::File::E_FBIG: // File too large + + case osl::File::E_AGAIN: // Operation would block + case osl::File::E_BADF: // Bad file + case osl::File::E_FAULT: // Bad address + case osl::File::E_INTR: // function call was interrupted + case osl::File::E_IO: // I/O error + case osl::File::E_NOLCK: // No record locks available + case osl::File::E_NOLINK: // Link has been severed + case osl::File::E_NOSPC: // No space left on device + case osl::File::E_NXIO: // No such device or address + throw css::io::IOException(); // TODO: Better error handling + default: + break; + } + + uBytesToWrite -= uBytesWritten; + pBuffer += uBytesWritten; + } +} + +void SAL_CALL OslOutputStreamWrapper::flush() {} + +void SAL_CALL OslOutputStreamWrapper::closeOutput() +{ + osl::File::RC eRC = maFile.close(); + + switch (eRC) + { + case osl::File::E_INVAL: // the format of the parameters was not valid + + case osl::File::E_BADF: // Bad file + case osl::File::E_INTR: // function call was interrupted + case osl::File::E_NOLINK: // Link has been severed + case osl::File::E_NOSPC: // No space left on device + case osl::File::E_IO: // I/O error + throw css::io::IOException(); // TODO: Better error handling + default: + break; + } +} + +namespace +{ +class FlashExportFilter + : public cppu::WeakImplHelper<css::document::XFilter, css::document::XExporter, + css::lang::XInitialization, css::lang::XServiceInfo> +{ + Reference<XComponent> mxDoc; + Reference<XComponentContext> mxContext; + Reference<XStatusIndicator> mxStatusIndicator; + + // #i56084# variables for selection export + Reference<XShapes> mxSelectedShapes; + Reference<XDrawPage> mxSelectedDrawPage; + bool mbExportSelection; + +public: + explicit FlashExportFilter(const Reference<XComponentContext>& rxContext); + + // XFilter + virtual sal_Bool SAL_CALL filter(const Sequence<PropertyValue>& aDescriptor) override; + + void ExportAsMultipleFiles(const Sequence<PropertyValue>& aDescriptor); + void ExportAsSingleFile(const Sequence<PropertyValue>& aDescriptor); + + virtual void SAL_CALL cancel() override; + + // XExporter + virtual void SAL_CALL setSourceDocument(const Reference<XComponent>& xDoc) override; + + // XInitialization + virtual void SAL_CALL initialize(const Sequence<Any>& aArguments) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +} + +FlashExportFilter::FlashExportFilter(const Reference<XComponentContext>& rxContext) + : mxDoc() + , mxContext(rxContext) + , mxStatusIndicator() + , mxSelectedShapes() + , mxSelectedDrawPage() + , mbExportSelection(false) +{ +} + +static OUString exportBackground(FlashExporter& aFlashExporter, + const Reference<XDrawPage>& xDrawPage, + const std::u16string_view& sPath, sal_uInt32 nPage, + const char* suffix) +{ + OUString filename + = "slide" + OUString::number(nPage + 1) + OUString::createFromAscii(suffix) + ".swf"; + OUString fullpath = OUString::Concat(sPath) + "/" + filename; + + // AS: If suffix is "o" then the last parameter is true (for exporting objects). + sal_uInt16 nCached + = aFlashExporter.exportBackgrounds(xDrawPage, new OslOutputStreamWrapper(fullpath), + sal::static_int_cast<sal_uInt16>(nPage), *suffix == 'o'); + aFlashExporter.Flush(); + + if (nCached != nPage) + { + osl_removeFile(fullpath.pData); + if (0xffff == nCached) + return "NULL"; + else + return "slide" + OUString::number(nCached + 1) + OUString::createFromAscii(suffix) + + ".swf"; + } + + return filename; +} + +template <typename TYPE> +static TYPE findPropertyValue(const Sequence<PropertyValue>& aPropertySequence, const char* name, + TYPE def) +{ + TYPE temp = TYPE(); + + sal_Int32 nLength = aPropertySequence.getLength(); + const PropertyValue* pValue = aPropertySequence.getConstArray(); + + for (sal_Int32 i = 0; i < nLength; i++) + { + if (pValue[i].Name.equalsAsciiL(name, strlen(name))) + { + pValue[i].Value >>= temp; + return temp; + } + } + + return def; +} + +sal_Bool SAL_CALL +FlashExportFilter::filter(const css::uno::Sequence<css::beans::PropertyValue>& aDescriptor) +{ + mxStatusIndicator = findPropertyValue<Reference<XStatusIndicator>>( + aDescriptor, "StatusIndicator", mxStatusIndicator); + + Sequence<PropertyValue> aFilterData; + aFilterData + = findPropertyValue<Sequence<PropertyValue>>(aDescriptor, "FilterData", aFilterData); + + // #i56084# check if selection shall be exported only; if yes, get the selected page and the selection itself + if (findPropertyValue<bool>(aDescriptor, "SelectionOnly", false)) + { + Reference<XDesktop2> xDesktop(Desktop::create(mxContext)); + + if (xDesktop.is()) + { + Reference<XFrame> xFrame(xDesktop->getCurrentFrame()); + + if (xFrame.is()) + { + Reference<XController> xController(xFrame->getController()); + + if (xController.is()) + { + Reference<XDrawView> xDrawView(xController, UNO_QUERY); + + if (xDrawView.is()) + { + mxSelectedDrawPage = xDrawView->getCurrentPage(); + } + + if (mxSelectedDrawPage.is()) + { + Reference<XSelectionSupplier> xSelection(xController, UNO_QUERY); + + if (xSelection.is()) + { + xSelection->getSelection() >>= mxSelectedShapes; + } + } + } + } + } + } + + if (mxSelectedDrawPage.is() && mxSelectedShapes.is() && mxSelectedShapes->getCount()) + { + // #i56084# to export selection we need the selected page and the selected shapes. + // There must be shapes selected, else fallback to regular export (export all) + mbExportSelection = true; + } + + // #i56084# no multiple files (suppress) when selection since selection can only export a single page + if (!mbExportSelection && findPropertyValue<bool>(aFilterData, "ExportMultipleFiles", false)) + { + ExportAsMultipleFiles(aDescriptor); + } + else + { + ExportAsSingleFile(aDescriptor); + } + + if (mxStatusIndicator.is()) + mxStatusIndicator->end(); + + return true; +} + +// AS: When exporting as multiple files, each background, object layer, and slide gets its own +// file. Additionally, a file called BackgroundConfig.txt is generated, indicating which +// background and objects (if any) go with each slide. The files are named slideNb.swf, +// slideNo.swf, and slideNp.swf, where N is the slide number, and b=background, o=objects, and +// p=slide contents. Note that under normal circumstances, there will be very few b and o files. + +// AS: HACK! Right now, I create a directory as a sibling to the swf file selected in the Export +// dialog. This directory is called presentation.sxi-swf-files. The name of the swf file selected +// in the Export dialog has no impact on this. All files created are placed in this directory. +void FlashExportFilter::ExportAsMultipleFiles(const Sequence<PropertyValue>& aDescriptor) +{ + Reference<XDrawPagesSupplier> xDrawPagesSupplier(mxDoc, UNO_QUERY); + if (!xDrawPagesSupplier.is()) + return; + + Reference<XIndexAccess> xDrawPages = xDrawPagesSupplier->getDrawPages(); + if (!xDrawPages.is()) + return; + + Reference<XDesktop2> rDesktop = Desktop::create(mxContext); + + Reference<XStorable> xStorable(rDesktop->getCurrentComponent(), UNO_QUERY); + if (!xStorable.is()) + return; + + Reference<XDrawPage> xDrawPage; + + Reference<XFrame> rFrame = rDesktop->getCurrentFrame(); + Reference<XDrawView> rDrawView(rFrame->getController(), UNO_QUERY); + + Reference<XDrawPage> rCurrentPage = rDrawView->getCurrentPage(); + + Sequence<PropertyValue> aFilterData; + + aFilterData + = findPropertyValue<Sequence<PropertyValue>>(aDescriptor, "FilterData", aFilterData); + + //AS: Do a bunch of path mangling to figure out where to put the files. + + OUString sOriginalPath = findPropertyValue<OUString>(aDescriptor, "URL", OUString()); + + // AS: sPath is the parent directory, where everything else exists (like the sxi, + // the -swf-files folder, the -audio files, etc. + sal_Int32 lastslash = sOriginalPath.lastIndexOf('/'); + OUString sPath(sOriginalPath.copy(0, lastslash)); + + OUString sPresentation(xStorable->getLocation()); + + lastslash = sPresentation.lastIndexOf('/') + 1; + sal_Int32 lastdot = sPresentation.lastIndexOf('.'); + + // AS: The name of the presentation, without 3 character extension. + OUString sPresentationName; + if (lastdot < 0) // fdo#71309 in case file has no name + sPresentationName = sPresentation.copy(lastslash); + else + sPresentationName = sPresentation.copy(lastslash, lastdot - lastslash); + + OUString fullpath, swfdirpath, backgroundfilename, objectsfilename; + + swfdirpath = sPath + "/" + sPresentationName + ".sxi-swf-files"; + + osl_createDirectory(swfdirpath.pData); + + fullpath = swfdirpath + "/backgroundconfig.txt"; + + oslFileHandle aBackgroundConfig(nullptr); + + // AS: Only export the background config if we're exporting all of the pages, otherwise we'll + // screw it up. + bool bExportAll = findPropertyValue<bool>(aFilterData, "ExportAll", true); + if (bExportAll) + { + osl_removeFile(fullpath.pData); + osl_openFile(fullpath.pData, &aBackgroundConfig, + osl_File_OpenFlag_Create | osl_File_OpenFlag_Write); + + sal_uInt64 bytesWritten; + osl_writeFile(aBackgroundConfig, "slides=", strlen("slides="), &bytesWritten); + } + + // TODO: check for errors + + FlashExporter aFlashExporter(mxContext, mxSelectedShapes, mxSelectedDrawPage, + findPropertyValue<sal_Int32>(aFilterData, "CompressMode", 75), + findPropertyValue<bool>(aFilterData, "ExportOLEAsJPEG", false)); + + const sal_Int32 nPageCount = xDrawPages->getCount(); + if (mxStatusIndicator.is()) + mxStatusIndicator->start("Saving :", nPageCount); + + for (sal_Int32 nPage = 0; nPage < nPageCount; nPage++) + { + if (mxStatusIndicator.is()) + mxStatusIndicator->setValue(nPage); + xDrawPages->getByIndex(nPage) >>= xDrawPage; + + // AS: If we're only exporting the current page, then skip the rest. + if (!bExportAll && xDrawPage != rCurrentPage) + continue; + + // AS: Export the background, the background objects, and then the slide contents. + if (bExportAll || findPropertyValue<bool>(aFilterData, "ExportBackgrounds", true)) + { + backgroundfilename + = exportBackground(aFlashExporter, xDrawPage, swfdirpath, nPage, "b"); + } + + if (bExportAll || findPropertyValue<bool>(aFilterData, "ExportBackgroundObjects", true)) + { + objectsfilename = exportBackground(aFlashExporter, xDrawPage, swfdirpath, nPage, "o"); + } + + if (bExportAll || findPropertyValue<bool>(aFilterData, "ExportSlideContents", true)) + { + fullpath = swfdirpath + "/slide" + OUString::number(nPage + 1) + "p.swf"; + + bool ret = aFlashExporter.exportSlides(xDrawPage, new OslOutputStreamWrapper(fullpath)); + aFlashExporter.Flush(); + + if (!ret) + osl_removeFile(fullpath.pData); + } + + // AS: Write out to the background config what backgrounds and objects this + // slide used. + if (bExportAll) + { + OUString temp = backgroundfilename + "|" + objectsfilename; + OString ASCIItemp(temp.getStr(), temp.getLength(), RTL_TEXTENCODING_ASCII_US); + + sal_uInt64 bytesWritten; + osl_writeFile(aBackgroundConfig, ASCIItemp.getStr(), ASCIItemp.getLength(), + &bytesWritten); + + if (nPage < nPageCount - 1) + osl_writeFile(aBackgroundConfig, "|", 1, &bytesWritten); + } + } + + if (bExportAll) + osl_closeFile(aBackgroundConfig); +} + +void FlashExportFilter::ExportAsSingleFile(const Sequence<PropertyValue>& aDescriptor) +{ + Reference<XOutputStream> xOutputStream + = findPropertyValue<Reference<XOutputStream>>(aDescriptor, "OutputStream", nullptr); + Sequence<PropertyValue> aFilterData; + + if (!xOutputStream.is()) + { + OSL_ASSERT(false); + return; + } + + FlashExporter aFlashExporter(mxContext, mxSelectedShapes, mxSelectedDrawPage, + findPropertyValue<sal_Int32>(aFilterData, "CompressMode", 75), + findPropertyValue<bool>(aFilterData, "ExportOLEAsJPEG", false)); + + aFlashExporter.exportAll(mxDoc, xOutputStream, mxStatusIndicator); +} + +void SAL_CALL FlashExportFilter::cancel() {} + +// XExporter +void SAL_CALL +FlashExportFilter::setSourceDocument(const css::uno::Reference<css::lang::XComponent>& xDoc) +{ + mxDoc = xDoc; +} + +// XInitialization +void SAL_CALL +FlashExportFilter::initialize(const css::uno::Sequence<css::uno::Any>& /* aArguments */) +{ +} + +OUString FlashExportFilter_getImplementationName() +{ + return "com.sun.star.comp.Impress.FlashExportFilter"; +} + +Sequence<OUString> FlashExportFilter_getSupportedServiceNames() +{ + Sequence<OUString> aRet{ "com.sun.star.document.ExportFilter" }; + return aRet; +} + +Reference<XInterface> FlashExportFilter_createInstance(const Reference<XMultiServiceFactory>& rSMgr) +{ + return static_cast<cppu::OWeakObject*>( + new FlashExportFilter(comphelper::getComponentContext(rSMgr))); +} + +// XServiceInfo +OUString SAL_CALL FlashExportFilter::getImplementationName() +{ + return FlashExportFilter_getImplementationName(); +} + +sal_Bool SAL_CALL FlashExportFilter::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL FlashExportFilter::getSupportedServiceNames() +{ + return FlashExportFilter_getSupportedServiceNames(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfuno.cxx b/filter/source/flash/swfuno.cxx new file mode 100644 index 000000000000..70532b38469f --- /dev/null +++ b/filter/source/flash/swfuno.cxx @@ -0,0 +1,69 @@ +/* -*- 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 <cppuhelper/factory.hxx> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include "swfuno.hxx" + +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::registry; + +using namespace ::swf; + +extern "C" { +SAL_DLLPUBLIC_EXPORT void* flash_component_getFactory(const char* pImplName, void* pServiceManager, + void* /* pRegistryKey */) +{ + void* pRet = nullptr; + + if (pServiceManager) + { + Reference<XSingleServiceFactory> xFactory; + + OUString implName = OUString::createFromAscii(pImplName); + if (implName == FlashExportFilter_getImplementationName()) + { + xFactory = createSingleFactory(static_cast<XMultiServiceFactory*>(pServiceManager), + OUString::createFromAscii(pImplName), + FlashExportFilter_createInstance, + FlashExportFilter_getSupportedServiceNames()); + } + else if (implName == SWFDialog_getImplementationName()) + { + xFactory = createSingleFactory(static_cast<XMultiServiceFactory*>(pServiceManager), + OUString::createFromAscii(pImplName), + SWFDialog_createInstance, + SWFDialog_getSupportedServiceNames()); + } + + if (xFactory.is()) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + } + + return pRet; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfuno.hxx b/filter/source/flash/swfuno.hxx new file mode 100644 index 000000000000..0db437675445 --- /dev/null +++ b/filter/source/flash/swfuno.hxx @@ -0,0 +1,73 @@ +/* -*- 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_FLASH_SWFUNO_HXX +#define INCLUDED_FILTER_SOURCE_FLASH_SWFUNO_HXX + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <rtl/ustring.hxx> + +namespace com +{ +namespace sun +{ +namespace star +{ +namespace lang +{ +class XMultiSerivceFactory; +} +namespace uno +{ +class XInterface; +} +} +} +} + +namespace swf +{ +/// @throws css::uno::RuntimeException +OUString FlashExportFilter_getImplementationName(); + +/// @throws css::uno::RuntimeException +css::uno::Sequence<OUString> FlashExportFilter_getSupportedServiceNames(); + +/// @throws css::uno::Exception +css::uno::Reference<css::uno::XInterface> +FlashExportFilter_createInstance(css::uno::Reference<css::lang::XMultiServiceFactory> const& rSMgr); +} + +/// @throws css::uno::RuntimeException +OUString SWFDialog_getImplementationName(); + +/// @throws css::uno::RuntimeException +css::uno::Sequence<OUString> SWFDialog_getSupportedServiceNames(); + +/// @throws css::uno::Exception +css::uno::Reference<css::uno::XInterface> +SWFDialog_createInstance(css::uno::Reference<css::lang::XMultiServiceFactory> const& rSMgr); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfwriter.cxx b/filter/source/flash/swfwriter.cxx new file mode 100644 index 000000000000..81edf16a2f89 --- /dev/null +++ b/filter/source/flash/swfwriter.cxx @@ -0,0 +1,401 @@ +/* -*- 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 "swfwriter.hxx" +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/debug.hxx> + +using namespace ::swf; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; + +static MapMode aTWIPSMode(MapUnit::MapTwip); +static MapMode a100thmmMode(MapUnit::Map100thMM); + +static sal_Int32 map100thmm(sal_Int32 n100thMM) +{ + Point aPoint(n100thMM, n100thMM); + sal_Int32 nX = OutputDevice::LogicToLogic(aPoint, a100thmmMode, aTWIPSMode).X(); + return nX; +} + +Writer::Writer(sal_Int32 nTWIPWidthOutput, sal_Int32 nTWIPHeightOutput, sal_Int32 nDocWidth, + sal_Int32 nDocHeight, sal_Int32 nJPEGcompressMode) + : mnDocWidth(map100thmm(nDocWidth)) + , mnDocHeight(map100thmm(nDocHeight)) + , mnDocXScale(static_cast<double>(nTWIPWidthOutput) / mnDocWidth) + , mnDocYScale(static_cast<double>(nTWIPHeightOutput) / mnDocHeight) + , mpClipPolyPolygon(nullptr) + , mnNextId(1) + , mnFrames(0) + , mnGlobalTransparency(0) + , mnJPEGCompressMode(nJPEGcompressMode) +{ + mpVDev->EnableOutput(false); + + maMovieTempFile.EnableKillingFile(); + maFontsTempFile.EnableKillingFile(); + + mpMovieStream = maMovieTempFile.GetStream(StreamMode::WRITE | StreamMode::TRUNC); + mpFontsStream = maFontsTempFile.GetStream(StreamMode::WRITE | StreamMode::TRUNC); + + // define an invisible button with the size of a page + tools::Rectangle aRect(0, 0, static_cast<long>(mnDocWidth * mnDocXScale), + static_cast<long>(mnDocHeight * mnDocYScale)); + tools::Polygon aPoly(aRect); + FillStyle aFill(COL_WHITE); + sal_uInt16 nWhiteBackgroundShapeId = defineShape(aPoly, aFill); + + ::basegfx::B2DHomMatrix m; // #i73264# + mnPageButtonId = createID(); + startTag(TAG_DEFINEBUTTON); + mpTag->addUI16(mnPageButtonId); // character id for button + + // button records + mpTag->addUI8(0x08); // only hit state + mpTag->addUI16(nWhiteBackgroundShapeId); // shape id of background rectangle + mpTag->addUI16(0); // depth for button DANGER! + mpTag->addMatrix(m); // identity matrix + mpTag->addUI8(0); // empty color transform + + // mpTag->addUI8( 0 ); // end of button records + + // action records + mpTag->addUI8(0x06); // ActionPlay + mpTag->addUI8(0); // end of action records + + endTag(); + + // place a shape that clips shapes depth 2-3 to document boundaries + // placeShape( mnWhiteBackgroundShapeId, 1, 0, 0, 4 ); +} + +Writer::~Writer() { mpVDev.disposeAndClear(); } + +static void ImplCopySvStreamToXOutputStream(SvStream& rIn, Reference<XOutputStream> const& xOut) +{ + sal_uInt32 nBufferSize = 64 * 1024; + + sal_uInt32 nSize = rIn.TellEnd(); + rIn.Seek(STREAM_SEEK_TO_BEGIN); + + Sequence<sal_Int8> aBuffer(std::min(nBufferSize, nSize)); + + while (nSize) + { + if (nSize < nBufferSize) + { + nBufferSize = nSize; + aBuffer.realloc(nSize); + } + + sal_uInt32 nRead = rIn.ReadBytes(aBuffer.getArray(), nBufferSize); + DBG_ASSERT(nRead == nBufferSize, "ImplCopySvStreamToXOutputStream failed!"); + xOut->writeBytes(aBuffer); + + if (nRead == 0) + break; + + nSize -= nRead; + } +} + +void Writer::storeTo(Reference<XOutputStream> const& xOutStream) +{ + for (auto& font : maFonts) + { + font->write(*mpFontsStream); + font.reset(); + } + maFonts.clear(); + + // Endtag + mpMovieStream->WriteUInt16(0); + + Tag aHeader(0xff); + + aHeader.addUI8('F'); + aHeader.addUI8('W'); + aHeader.addUI8('S'); + aHeader.addUI8(5); + + sal_uInt32 nSizePos = aHeader.Tell(); + + aHeader.WriteUInt32(0); + + tools::Rectangle aDocRect(0, 0, static_cast<long>(mnDocWidth * mnDocXScale), + static_cast<long>(mnDocHeight * mnDocYScale)); + + aHeader.addRect(aDocRect); + + // frame delay in 8.8 fixed number of frames per second + aHeader.addUI8(0); + aHeader.addUI8(12); + + aHeader.addUI16(uInt16_(mnFrames)); + + const sal_uInt32 nSize = aHeader.Tell() + mpFontsStream->Tell() + mpMovieStream->Tell(); + + aHeader.Seek(nSizePos); + aHeader.WriteUInt32(nSize); + + ImplCopySvStreamToXOutputStream(aHeader, xOutStream); + ImplCopySvStreamToXOutputStream(*mpFontsStream, xOutStream); + ImplCopySvStreamToXOutputStream(*mpMovieStream, xOutStream); +} + +sal_uInt16 Writer::startSprite() +{ + sal_uInt16 nShapeId = createID(); + mvSpriteStack.push(mpSprite.release()); + mpSprite.reset(new Sprite(nShapeId)); + return nShapeId; +} + +void Writer::endSprite() +{ + if (!mpSprite) + return; + + startTag(TAG_END); + endTag(); + + mpSprite->write(*mpMovieStream); + mpSprite.reset(); + + if (!mvSpriteStack.empty()) + { + mpSprite.reset(mvSpriteStack.top()); + mvSpriteStack.pop(); + } +} + +void Writer::placeShape(sal_uInt16 nID, sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y) +{ + startTag(TAG_PLACEOBJECT2); + + BitStream aBits; + + aBits.writeUB(sal_uInt32(0), 1); // Has Clip Actions? + aBits.writeUB(0, 1); // reserved + aBits.writeUB(sal_uInt32(0), 1); // has a name + aBits.writeUB(0, 1); // no ratio + aBits.writeUB(0, 1); // no color transform + aBits.writeUB(1, 1); // has a matrix + aBits.writeUB(1, 1); // places a character + aBits.writeUB(0, 1); // does not define a character to be moved + + mpTag->addBits(aBits); + mpTag->addUI16(nDepth); // depth + mpTag->addUI16(nID); // character Id + + // #i73264# + const basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix( + Int16_(static_cast<long>(map100thmm(x) * mnDocXScale)), + Int16_(static_cast<long>(map100thmm(y) * mnDocYScale)))); + mpTag->addMatrix(aMatrix); // transformation matrix + + endTag(); +} + +void Writer::removeShape(sal_uInt16 nDepth) +{ + startTag(TAG_REMOVEOBJECT2); + mpTag->addUI16(nDepth); // depth + endTag(); +} + +void Writer::startTag(sal_uInt8 nTagId) +{ + DBG_ASSERT(mpTag == nullptr, "Last tag was not ended"); + + mpTag.reset(new Tag(nTagId)); +} + +void Writer::endTag() +{ + sal_uInt8 nTag = mpTag->getTagId(); + + if (mpSprite + && ((nTag == TAG_END) || (nTag == TAG_SHOWFRAME) || (nTag == TAG_DOACTION) + || (nTag == TAG_STARTSOUND) || (nTag == TAG_PLACEOBJECT) || (nTag == TAG_PLACEOBJECT2) + || (nTag == TAG_REMOVEOBJECT2) || (nTag == TAG_FRAMELABEL))) + { + mpSprite->addTag(std::move(mpTag)); + } + else + { + mpTag->write(*mpMovieStream); + mpTag.reset(); + } +} + +void Writer::showFrame() +{ + startTag(TAG_SHOWFRAME); + endTag(); + + if (nullptr == mpSprite) + mnFrames++; +} + +sal_uInt16 Writer::defineShape(const GDIMetaFile& rMtf) +{ + mpVDev->SetMapMode(rMtf.GetPrefMapMode()); + Impl_writeActions(rMtf); + + sal_uInt16 nId = 0; + if (maShapeIds.empty()) + return nId; + + { + nId = startSprite(); + sal_uInt16 iDepth = 1; + for (auto const& shape : maShapeIds) + { + placeShape(shape, iDepth++, 0, 0); + } + endSprite(); + } + + maShapeIds.clear(); + + return nId; +} + +sal_uInt16 Writer::defineShape(const tools::Polygon& rPoly, const FillStyle& rFillStyle) +{ + const tools::PolyPolygon aPolyPoly(rPoly); + return defineShape(aPolyPoly, rFillStyle); +} + +sal_uInt16 Writer::defineShape(const tools::PolyPolygon& rPolyPoly, const FillStyle& rFillStyle) +{ + sal_uInt16 nShapeId = createID(); + + // start a DefineShape3 tag + startTag(TAG_DEFINESHAPE3); + + mpTag->addUI16(nShapeId); + mpTag->addRect(rPolyPoly.GetBoundRect()); + + // FILLSTYLEARRAY + mpTag->addUI8(1); // FillStyleCount + + // FILLSTYLE + rFillStyle.addTo(mpTag.get()); + + // LINESTYLEARRAY + mpTag->addUI8(0); // LineStyleCount + + // Number of fill and line index bits to 1 + mpTag->addUI8(0x11); + + BitStream aBits; + + const sal_uInt16 nCount = rPolyPoly.Count(); + sal_uInt16 i; + for (i = 0; i < nCount; i++) + { + const tools::Polygon& rPoly = rPolyPoly[i]; + if (rPoly.GetSize()) + Impl_addPolygon(aBits, rPoly, true); + } + + Impl_addEndShapeRecord(aBits); + + mpTag->addBits(aBits); + endTag(); + + return nShapeId; +} + +sal_uInt16 Writer::defineShape(const tools::PolyPolygon& rPolyPoly, sal_uInt16 nLineWidth, + const Color& rLineColor) +{ + sal_uInt16 nShapeId = createID(); + + // start a DefineShape3 tag + startTag(TAG_DEFINESHAPE3); + + mpTag->addUI16(nShapeId); + mpTag->addRect(rPolyPoly.GetBoundRect()); + + // FILLSTYLEARRAY + mpTag->addUI8(0); // FillStyleCount + + // LINESTYLEARRAY + mpTag->addUI8(1); // LineStyleCount + + // LINESTYLE + mpTag->addUI16(nLineWidth); // Width of line in twips + mpTag->addRGBA(rLineColor); // Color + + // Number of fill and line index bits to 1 + mpTag->addUI8(0x11); + + BitStream aBits; + + const sal_uInt16 nCount = rPolyPoly.Count(); + sal_uInt16 i; + for (i = 0; i < nCount; i++) + { + const tools::Polygon& rPoly = rPolyPoly[i]; + if (rPoly.GetSize()) + Impl_addPolygon(aBits, rPoly, false); + } + + Impl_addEndShapeRecord(aBits); + + mpTag->addBits(aBits); + endTag(); + + return nShapeId; +} + +void Writer::stop() +{ + startTag(TAG_DOACTION); + mpTag->addUI8(0x07); + mpTag->addUI8(0); + endTag(); +} + +void Writer::waitOnClick(sal_uInt16 nDepth) +{ + placeShape(uInt16_(mnPageButtonId), nDepth, 0, 0); + stop(); + showFrame(); + removeShape(nDepth); +} + +/** inserts a doaction tag with an ActionGotoFrame */ +void Writer::gotoFrame(sal_uInt16 nFrame) +{ + startTag(TAG_DOACTION); + mpTag->addUI8(0x81); + mpTag->addUI16(2); + mpTag->addUI16(nFrame); + mpTag->addUI8(0); + endTag(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfwriter.hxx b/filter/source/flash/swfwriter.hxx new file mode 100644 index 000000000000..3afef0862bdf --- /dev/null +++ b/filter/source/flash/swfwriter.hxx @@ -0,0 +1,421 @@ +/* -*- 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_FLASH_SWFWRITER_HXX +#define INCLUDED_FILTER_SOURCE_FLASH_SWFWRITER_HXX + +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <vcl/checksum.hxx> +#include <vcl/font.hxx> +#include <vcl/gradient.hxx> +#include <vcl/vclptr.hxx> +#include <unotools/tempfile.hxx> +#include <tools/color.hxx> +#include <tools/gen.hxx> +#include <tools/stream.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <osl/diagnose.h> +#include <vcl/kernarray.hxx> + +#include <vector> +#include <stack> +#include <map> + +class GDIMetaFile; +class BitmapEx; +class Gradient; +class SvtGraphicFill; +class SvtGraphicStroke; +class LineInfo; +namespace basegfx +{ +class B2DPolygon; +} +namespace tools +{ +class Polygon; +class PolyPolygon; +} + +inline sal_uInt16 uInt16_(sal_Int32 nValue) +{ + OSL_ENSURE((nValue >= 0) && (static_cast<sal_uInt32>(nValue) <= 0xffff), + "overflow while converting sal_Int32 to sal_uInt16"); + return static_cast<sal_uInt16>(nValue); +} + +inline sal_Int16 Int16_(sal_Int32 nValue) +{ + OSL_ENSURE((nValue >= -32768) && (nValue <= 32767), + "overflow while converting sal_Int32 to sal_Int16"); + return static_cast<sal_Int16>(nValue); +} + +class VirtualDevice; + +sal_uInt16 getMaxBitsSigned(sal_Int32 nValue); + +namespace swf +{ +const sal_uInt8 TAG_END = 0; +const sal_uInt8 TAG_SHOWFRAME = 1; + +const sal_uInt8 TAG_DEFINEBUTTON = 7; + +const sal_uInt8 TAG_BACKGROUNDCOLOR = 9; + +const sal_uInt8 TAG_DOACTION = 12; +const sal_uInt8 TAG_STARTSOUND = 15; + +const sal_uInt8 TAG_SOUNDSTREAMBLOCK = 19; +const sal_uInt8 TAG_SOUNDSTREAMHEAD = 18; +const sal_uInt8 TAG_SOUNDSTREAMHEAD2 = 45; + +const sal_uInt8 TAG_JPEGTABLES = 8; +const sal_uInt8 TAG_DEFINEBITS = 6; +const sal_uInt8 TAG_DEFINEBITSLOSSLESS = 20; +const sal_uInt8 TAG_DEFINEBITSJPEG2 = 21; +const sal_uInt8 TAG_DEFINEBITSJPEG3 = 35; +const sal_uInt8 TAG_DEFINEBITSLOSSLESS2 = 36; +const sal_uInt8 TAG_DEFINEEDITTEXT = 37; +const sal_uInt8 TAG_PLACEOBJECT = 4; +const sal_uInt8 TAG_PLACEOBJECT2 = 26; +const sal_uInt8 TAG_REMOVEOBJECT2 = 28; + +const sal_uInt8 TAG_DEFINEFONT = 10; +const sal_uInt8 TAG_DEFINETEXT = 11; +const sal_uInt8 TAG_DEFINESHAPE3 = 32; +const sal_uInt8 TAG_DEFINESPRITE = 39; + +const sal_uInt8 TAG_FRAMELABEL = 43; + +const sal_uInt8 TAG_HEADER = 0xff; + +/** converts a double to a 16.16 flash fixed value */ +sal_uInt32 getFixed(double fValue); + +typedef ::std::map<BitmapChecksum, sal_uInt16> ChecksumCache; + +/** container class to create bit structures */ +class BitStream +{ +public: + BitStream(); + + void writeUB(sal_uInt32 nValue, sal_uInt16 nBits); + void writeSB(sal_Int32 nValue, sal_uInt16 nBits); + void writeFB(sal_uInt32 nValue, sal_uInt16 nBits); + + void pad(); + void writeTo(SvStream& out); + + sal_uInt32 getOffset() const; + +private: + std::vector<sal_uInt8> maData; + sal_uInt8 mnBitPos; + sal_uInt8 mnCurrentByte; +}; + +/** this class collects all used glyphs for a given fonts and maps + characters to glyph ids. +*/ +class FlashFont +{ +public: + FlashFont(const vcl::Font& rFont, sal_uInt16 nId); + ~FlashFont(); + + sal_uInt16 getGlyph(sal_uInt16 nChar, VirtualDevice* pVDev); + + void write(SvStream& out); + + sal_uInt16 getID() const { return mnId; } + const vcl::Font& getFont() const { return maFont; } + +private: + const vcl::Font maFont; + std::map<sal_uInt16, sal_uInt16> maGlyphIndex; + sal_uInt16 mnNextIndex; + sal_uInt16 mnId; + BitStream maGlyphData; + std::vector<sal_uInt16> maGlyphOffsets; +}; + +/** this class helps creating flash tags */ +class Tag : public SvMemoryStream +{ +public: + explicit Tag(sal_uInt8 nTagId); + + sal_uInt8 getTagId() const { return mnTagId; } + + void write(SvStream& out); + + void addUI32(sal_uInt32 nValue); + void addUI16(sal_uInt16 nValue); + void addUI8(sal_uInt8 nValue); + void addBits(BitStream& rIn); + + void addRGBA(const Color& rColor); + void addRGB(const Color& rColor); + void addRect(const tools::Rectangle& rRect); + void addMatrix(const ::basegfx::B2DHomMatrix& rMatrix); // #i73264# + void addStream(SvStream& rIn); + + static void writeMatrix(SvStream& rOut, const ::basegfx::B2DHomMatrix& rMatrix); // #i73264# + static void writeRect(SvStream& rOut, const tools::Rectangle& rRect); + +private: + sal_uInt8 mnTagId; +}; + +/** this class helps to define flash sprites */ +class Sprite +{ +public: + explicit Sprite(sal_uInt16 nId); + ~Sprite(); + + void write(SvStream& out); + void addTag(std::unique_ptr<Tag> pNewTag); + +private: + std::vector<std::unique_ptr<Tag>> maTags; + sal_uInt16 mnId; + sal_uInt32 mnFrames; +}; + +/** this class stores a flash fill style for shapes */ +class FillStyle +{ +public: + enum FillStyleType + { + solid = 0x00, + linear_gradient = 0x10, + radial_gradient = 0x12, + tiled_bitmap = 0x40, + clipped_bitmap = 0x41 + }; + + /** this c'tor creates a solid fill style */ + explicit FillStyle(const Color& rSolidColor); + + /** this c'tor creates a linear or radial gradient fill style */ + FillStyle(const tools::Rectangle& rBoundRect, const Gradient& rGradient); + + /** this c'tor creates a tiled or clipped bitmap fill style */ + FillStyle(sal_uInt16 nBitmapId, bool bClipped, + const ::basegfx::B2DHomMatrix& rMatrix); // #i73264# + + void addTo(Tag* pTag) const; + +private: + void Impl_addGradient(Tag* pTag) const; + + FillStyleType meType; + ::basegfx::B2DHomMatrix maMatrix; // #i73264# + sal_uInt16 mnBitmapId; + Color maColor; + Gradient maGradient; + tools::Rectangle maBoundRect; +}; + +/** this class creates a flash movie from vcl geometry */ +class Writer +{ + friend class FlashFont; + +public: + /** creates a writer for a new flash movie. + nDocWidth and nDocHeight are the dimensions of the movie. + They must be in 100th/mm. + + An invisible shape with the size of the document is placed at depth 1 + and it clips all shapes on depth 2 and 3. + */ + Writer(sal_Int32 nTWIPWidthOutput, sal_Int32 nTWIPHeightOutput, sal_Int32 nDocWidth, + sal_Int32 nDocHeight, sal_Int32 nJPEGcompressMode); + ~Writer(); + + void storeTo(css::uno::Reference<css::io::XOutputStream> const& xOutStream); + + // geometry + void setClipping(const tools::PolyPolygon* pClipPolyPolygon); + + /** defines a flash shape from a filled polygon. + The coordinates must be in twips */ + sal_uInt16 defineShape(const tools::Polygon& rPoly, const FillStyle& rFillStyle); + + /** defines a flash shape from a filled polypolygon. + The coordinates must be in twips */ + sal_uInt16 defineShape(const tools::PolyPolygon& rPolyPoly, const FillStyle& rFillStyle); + + /** defines a flash shape from an outlined polypolygon. + The coordinates must be in twips */ + sal_uInt16 defineShape(const tools::PolyPolygon& rPolyPoly, sal_uInt16 nLineWidth, + const Color& rLineColor); + + /** defines a flash shape from a vcl metafile. + The mapmode of the metafile is used to map all coordinates to twips. + A character id of a flash sprite is returned that contains all geometry + from the metafile. + */ + sal_uInt16 defineShape(const GDIMetaFile& rMtf); + + /** defines a bitmap and returns its flash id. + */ + sal_uInt16 defineBitmap(const BitmapEx& bmpSource, sal_Int32 nJPEGQualityLevel); + + // control tags + + /** inserts a place shape tag into the movie stream or the current sprite */ + void placeShape(sal_uInt16 nID, sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y); + + /** inserts a remove shape tag into the movie stream or the current sprite */ + void removeShape(sal_uInt16 nDepth); + + /** inserts a show frame tag into the movie stream or the current sprite */ + void showFrame(); + + /** creates a new sprite and sets it as the current sprite for editing. + Only one sprite can be edited at one time */ + sal_uInt16 startSprite(); + + /** ends editing of the current sprites and adds it to the movie stream */ + void endSprite(); + + /** inserts a doaction tag with an ActionStop */ + void stop(); + + /** inserts a doaction tag with an ActionStop, place a button on depth nDepth that + continues playback on click */ + void waitOnClick(sal_uInt16 nDepth); + + /** inserts a doaction tag with an ActionGotoFrame */ + void gotoFrame(sal_uInt16 nFrame); + +private: + Point map(const Point& rPoint) const; + Size map(const Size& rSize) const; + void map(tools::PolyPolygon& rPolyPolygon) const; + sal_Int32 mapRelative(sal_Int32 n100thMM) const; + + void startTag(sal_uInt8 nTagId); + void endTag(); + sal_uInt16 createID() { return mnNextId++; } + + void Impl_writeBmp(sal_uInt16 nBitmapId, sal_uInt32 width, sal_uInt32 height, + sal_uInt8 const* pCompressed, sal_uInt32 compressed_size); + void Impl_writeImage(const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, + const Point& rSrcPt, const Size& rSrcSz, const tools::Rectangle& rClipRect, + bool bMap); + void Impl_writeJPEG(sal_uInt16 nBitmapId, const sal_uInt8* pJpgData, sal_uInt32 nJpgDataLength, + sal_uInt8 const* pCompressed, sal_uInt32 compressed_size); + void Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, + const basegfx::B2DPolygon& rLinePolygon); + void Impl_writeActions(const GDIMetaFile& rMtf); + void Impl_writePolygon(const tools::Polygon& rPoly, bool bFilled); + void Impl_writePolygon(const tools::Polygon& rPoly, bool bFilled, const Color& rFillColor, + const Color& rLineColor); + void Impl_writePolyPolygon(const tools::PolyPolygon& rPolyPoly, bool bFilled, + sal_uInt8 nTransparence = 0); + void Impl_writePolyPolygon(const tools::PolyPolygon& rPolyPoly, bool bFilled, + const Color& rFillColor, const Color& rLineColor); + void Impl_writeText(const Point& rPos, const OUString& rText, const KernArray* pDXArray, + long nWidth); + void Impl_writeText(const Point& rPos, const OUString& rText, const KernArray* pDXArray, + long nWidth, Color aTextColor); + void Impl_writeGradientEx(const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient); + void Impl_writeLine(const Point& rPt1, const Point& rPt2, const Color* pLineColor = nullptr); + void Impl_writeRect(const tools::Rectangle& rRect, long nRadX, long nRadY); + void Impl_writeEllipse(const Point& rCenter, long nRadX, long nRadY); + bool Impl_writeFilling(SvtGraphicFill const& rFilling); + bool Impl_writeStroke(SvtGraphicStroke const& rStroke); + + FlashFont& Impl_getFont(const vcl::Font& rFont); + + static void Impl_addPolygon(BitStream& rBits, const tools::Polygon& rPoly, bool bFilled); + + static void Impl_addShapeRecordChange(BitStream& rBits, sal_Int16 dx, sal_Int16 dy, + bool bFilled); + static void Impl_addStraightEdgeRecord(BitStream& rBits, sal_Int16 dx, sal_Int16 dy); + static void Impl_addCurvedEdgeRecord(BitStream& rBits, sal_Int16 control_dx, + sal_Int16 control_dy, sal_Int16 anchor_dx, + sal_Int16 anchor_dy); + static void Impl_addEndShapeRecord(BitStream& rBits); + + static void Impl_addStraightLine(BitStream& rBits, Point& rLastPoint, const double P2x, + const double P2y); + static void Impl_addQuadBezier(BitStream& rBits, Point& rLastPoint, const double P2x, + const double P2y, const double P3x, const double P3y); + static void Impl_quadBezierApprox(BitStream& rBits, Point& rLastPoint, const double d2, + const double P1x, const double P1y, const double P2x, + const double P2y, const double P3x, const double P3y, + const double P4x, const double P4y); + + css::uno::Reference<css::i18n::XBreakIterator> const& Impl_GetBreakIterator(); + +private: + css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator; + + std::vector<std::unique_ptr<FlashFont>> maFonts; + + sal_Int32 mnDocWidth; + sal_Int32 mnDocHeight; + + // AS: Scaling factor for output. + double mnDocXScale; + double mnDocYScale; + + sal_uInt16 mnPageButtonId; + + VclPtrInstance<VirtualDevice> mpVDev; + + const tools::PolyPolygon* mpClipPolyPolygon; + + /** holds the information of the objects defined in the movie stream + while executing defineShape + */ + std::vector<sal_uInt16> maShapeIds; + + std::unique_ptr<Tag> mpTag; + std::unique_ptr<Sprite> mpSprite; + std::stack<Sprite*> mvSpriteStack; + ChecksumCache mBitmapCache; + + sal_uInt16 mnNextId; + sal_uInt32 mnFrames; + + utl::TempFileNamed maMovieTempFile; + utl::TempFileNamed maFontsTempFile; + + SvStream* mpMovieStream; + SvStream* mpFontsStream; + + sal_uInt8 mnGlobalTransparency; + sal_Int32 mnJPEGCompressMode; +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfwriter1.cxx b/filter/source/flash/swfwriter1.cxx new file mode 100644 index 000000000000..93c797370ace --- /dev/null +++ b/filter/source/flash/swfwriter1.cxx @@ -0,0 +1,1960 @@ +/* -*- 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 <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <comphelper/processfactory.hxx> +#include "swfwriter.hxx" +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/graphictools.hxx> +#include <sal/log.hxx> +#include <tools/helpers.hxx> +#include <tools/debug.hxx> + +#include <zlib.h> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <memory> + +using namespace ::swf; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::beans; + +static MapMode aTWIPSMode(MapUnit::MapTwip); +static MapMode a100thmmMode(MapUnit::Map100thMM); + +Point Writer::map(const Point& rPoint) const +{ + const MapMode& aSourceMapMode = mpVDev->GetMapMode(); + + Point retPoint = mpVDev->LogicToLogic(rPoint, &aSourceMapMode, &aTWIPSMode); + + // AS: Produces a 'possible loss of data' warning that we can't fix without + // hurting code readability. + retPoint.setX(static_cast<long>(retPoint.X() * mnDocXScale)); + retPoint.setY(static_cast<long>(retPoint.Y() * mnDocYScale)); + + return retPoint; +} + +Size Writer::map(const Size& rSize) const +{ + const MapMode& aSourceMapMode = mpVDev->GetMapMode(); + + Size retSize = mpVDev->LogicToLogic(rSize, &aSourceMapMode, &aTWIPSMode); + + // AS: Produces a 'possible loss of data' warning that we can't fix without + // hurting code readability. + retSize.setWidth(static_cast<long>(retSize.Width() * mnDocXScale)); + retSize.setHeight(static_cast<long>(retSize.Height() * mnDocYScale)); + + return retSize; +} + +void Writer::map(tools::PolyPolygon& rPolyPolygon) const +{ + const sal_uInt16 nPolyCount = rPolyPolygon.Count(); + if (!nPolyCount) + return; + + sal_uInt16 nPoly, nPoint, nPointCount; + for (nPoly = 0; nPoly < nPolyCount; nPoly++) + { + tools::Polygon& rPoly = rPolyPolygon[nPoly]; + nPointCount = rPoly.GetSize(); + + for (nPoint = 0; nPoint < nPointCount; nPoint++) + { + rPoly[nPoint] = map(rPoly[nPoint]); + } + } +} + +sal_Int32 Writer::mapRelative(sal_Int32 n100thMM) const +{ + MapMode aSourceMapMode(mpVDev->GetMapMode()); + aSourceMapMode.SetOrigin(Point()); + + sal_Int32 nTwips + = mpVDev->LogicToLogic(Point(n100thMM, n100thMM), &aSourceMapMode, &aTWIPSMode).X(); + return nTwips; +} + +void Writer::Impl_addPolygon(BitStream& rBits, const tools::Polygon& rPoly, bool bFilled) +{ + Point aLastPoint(rPoly[0]); + + Impl_addShapeRecordChange(rBits, Int16_(aLastPoint.X()), Int16_(aLastPoint.Y()), bFilled); + + sal_uInt16 i = 0, nSize = rPoly.GetSize(); + + double d = 16.0f; + + // points + while ((i + 1) < nSize) + { + if ((i + 3) < nSize) + { + PolyFlags P1(rPoly.GetFlags(i)); + PolyFlags P4(rPoly.GetFlags(i + 3)); + + if ((PolyFlags::Normal == P1 || PolyFlags::Smooth == P1 || PolyFlags::Symmetric == P1) + && (PolyFlags::Control == rPoly.GetFlags(i + 1)) + && (PolyFlags::Control == rPoly.GetFlags(i + 2)) + && (PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 + || PolyFlags::Symmetric == P4)) + { + Impl_quadBezierApprox(rBits, aLastPoint, d * d, rPoly.GetPoint(i).X(), + rPoly.GetPoint(i).Y(), rPoly.GetPoint(i + 1).X(), + rPoly.GetPoint(i + 1).Y(), rPoly.GetPoint(i + 2).X(), + rPoly.GetPoint(i + 2).Y(), rPoly.GetPoint(i + 3).X(), + rPoly.GetPoint(i + 3).Y()); + i += 3; + continue; + } + } + + ++i; + + const Point aPolyPoint(rPoly[i]); + if (aPolyPoint != aLastPoint) + { + Impl_addStraightEdgeRecord(rBits, Int16_(aPolyPoint.X() - aLastPoint.X()), + Int16_(aPolyPoint.Y() - aLastPoint.Y())); + aLastPoint = aPolyPoint; + } + } + + if (bFilled && (rPoly[0] != rPoly[nSize - 1])) + { + const Point aPolyPoint(rPoly[0]); + if (aPolyPoint != aLastPoint) + { + Impl_addStraightEdgeRecord(rBits, Int16_(aPolyPoint.X() - aLastPoint.X()), + Int16_(aPolyPoint.Y() - aLastPoint.Y())); + } + } +} + +/** Exports a style change record with a move to (x,y) and depending on bFilled a line style 1 or fill style 1 +*/ +void Writer::Impl_addShapeRecordChange(BitStream& rBits, sal_Int16 dx, sal_Int16 dy, bool bFilled) +{ + rBits.writeUB(0, 1); // TypeFlag + rBits.writeUB(0, 1); // StateNewStyles + rBits.writeUB(sal_uInt32(!bFilled), 1); // StateLineStyle + rBits.writeUB(0, 1); // StateFillStyle0 + rBits.writeUB(bFilled ? 1 : 0, 1); // StateFillStyle1 + rBits.writeUB(1, 1); // StateMoveTo + + sal_uInt16 nMoveBits = std::max(getMaxBitsSigned(dx), getMaxBitsSigned(dy)); + + rBits.writeUB(nMoveBits, 5); // Number of bits per value + // TODO: Optimize horizontal and vertical lines + rBits.writeSB(dx, nMoveBits); // DeltaX + rBits.writeSB(dy, nMoveBits); // DeltaY + + rBits.writeUB(1, 1); // set FillStyle1 or LineStyle to 1 +} + +/** Exports a straight edge record +*/ +void Writer::Impl_addStraightEdgeRecord(BitStream& rBits, sal_Int16 dx, sal_Int16 dy) +{ + rBits.writeUB(1, 1); // TypeFlag + rBits.writeUB(1, 1); // StraightFlag + + sal_uInt16 nBits = std::max(getMaxBitsSigned(dx), getMaxBitsSigned(dy)); + + rBits.writeUB(nBits - 2, 4); // Number of bits per value + + if ((dx != 0) && (dy != 0)) + { + rBits.writeUB(1, 1); // GeneralLineFlag + rBits.writeSB(dx, nBits); // DeltaX + rBits.writeSB(dy, nBits); // DeltaY + } + else + { + rBits.writeUB(0, 1); + rBits.writeUB(sal_uInt32(dx == 0), 1); + if (dx == 0) + { + rBits.writeSB(dy, nBits); // DeltaY + } + else + { + rBits.writeSB(dx, nBits); // DeltaX + } + } +} + +/** Exports a curved edge record +*/ +void Writer::Impl_addCurvedEdgeRecord(BitStream& rBits, sal_Int16 control_dx, sal_Int16 control_dy, + sal_Int16 anchor_dx, sal_Int16 anchor_dy) +{ + rBits.writeUB(1, 1); // TypeFlag + rBits.writeUB(0, 1); // CurvedFlag + + sal_uInt8 nBits = static_cast<sal_uInt8>( + std::max(getMaxBitsSigned(control_dx), + std::max(getMaxBitsSigned(control_dy), + std::max(getMaxBitsSigned(anchor_dx), + std::max(getMaxBitsSigned(anchor_dy), sal_uInt16(3)))))); + + rBits.writeUB(nBits - 2, 4); // Number of bits per value + + rBits.writeSB(control_dx, nBits); // DeltaX + rBits.writeSB(control_dy, nBits); // DeltaY + rBits.writeSB(anchor_dx, nBits); // DeltaX + rBits.writeSB(anchor_dy, nBits); // DeltaY +} + +/** Exports an end shape record +*/ +void Writer::Impl_addEndShapeRecord(BitStream& rBits) { rBits.writeUB(0, 6); } + +void Writer::Impl_writePolygon(const tools::Polygon& rPoly, bool bFilled) +{ + tools::PolyPolygon aPolyPoly(rPoly); + Impl_writePolyPolygon(aPolyPoly, bFilled); +} + +void Writer::Impl_writePolygon(const tools::Polygon& rPoly, bool bFilled, const Color& rFillColor, + const Color& rLineColor) +{ + tools::PolyPolygon aPolyPoly(rPoly); + Impl_writePolyPolygon(aPolyPoly, bFilled, rFillColor, rLineColor); +} + +void Writer::Impl_writePolyPolygon(const tools::PolyPolygon& rPolyPoly, bool bFilled, + sal_uInt8 nTransparence /* = 0 */) +{ + Color aLineColor(mpVDev->GetLineColor()); + if (!aLineColor.IsTransparent()) + aLineColor.SetAlpha(255 - nTransparence); + Color aFillColor(mpVDev->GetFillColor()); + if (!aLineColor.IsTransparent()) + aFillColor.SetAlpha(255 - nTransparence); + Impl_writePolyPolygon(rPolyPoly, bFilled, aFillColor, aLineColor); +} + +void Writer::Impl_writePolyPolygon(const tools::PolyPolygon& rPolyPoly, bool bFilled, + const Color& rFillColor, const Color& rLineColor) +{ + tools::PolyPolygon aPolyPoly(rPolyPoly); + + if (!aPolyPoly.Count()) + return; + + map(aPolyPoly); + + if (mpClipPolyPolygon) + rPolyPoly.GetIntersection(*mpClipPolyPolygon, aPolyPoly); + + sal_uInt16 nID; + if (bFilled) + { + Color aFillColor(rFillColor); + if (0 != mnGlobalTransparency) + aFillColor.SetAlpha(255 - mnGlobalTransparency); + + FillStyle aStyle(aFillColor); + nID = defineShape(aPolyPoly, aStyle); + } + else + { + Color aLineColor(rLineColor); + if (0 != mnGlobalTransparency) + aLineColor.SetAlpha(255 - mnGlobalTransparency); + + nID = defineShape(aPolyPoly, 1, aLineColor); + } + maShapeIds.push_back(nID); +} + +/** A gradient is a transition from one color to another, rendered inside a given polypolygon */ +void Writer::Impl_writeGradientEx(const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient) +{ + if (!rPolyPoly.Count()) + return; + + tools::PolyPolygon aPolyPolygon(rPolyPoly); + map(aPolyPolygon); + + if ((rGradient.GetStyle() == css::awt::GradientStyle::GradientStyle_LINEAR + && rGradient.GetAngle() == 900_deg10) + || (rGradient.GetStyle() == css::awt::GradientStyle::GradientStyle_RADIAL)) + { + const tools::Rectangle aBoundRect(aPolyPolygon.GetBoundRect()); + + FillStyle aFillStyle(aBoundRect, rGradient); + + sal_uInt16 nShapeId = defineShape(aPolyPolygon, aFillStyle); + maShapeIds.push_back(nShapeId); + } + else + { + setClipping(&aPolyPolygon); + + // render the gradient filling to simple polygons + { + GDIMetaFile aTmpMtf; + rGradient.AddGradientActionsConst(aPolyPolygon.GetBoundRect(), aTmpMtf); + Impl_writeActions(aTmpMtf); + } + + setClipping(nullptr); + } +} + +void Writer::setClipping(const tools::PolyPolygon* pClipPolyPolygon) +{ + mpClipPolyPolygon = pClipPolyPolygon; +} + +// AS: Just comparing fonts straight up is too literal. There are some +// differences in font that actually require different glyphs to be defined, +// and some that don't. This function is meant to capture all the differences +// that we care about. +static bool compare_fonts_for_me(const vcl::Font& rFont1, const vcl::Font& rFont2) +{ + return rFont1.GetFamilyName() == rFont2.GetFamilyName() + && rFont1.GetWeight() == rFont2.GetWeight() && rFont1.GetItalic() == rFont2.GetItalic() + && rFont1.IsOutline() == rFont2.IsOutline() && rFont1.IsShadow() == rFont2.IsShadow() + && rFont1.GetRelief() == rFont2.GetRelief(); +} + +FlashFont& Writer::Impl_getFont(const vcl::Font& rFont) +{ + for (auto const& font : maFonts) + { + const vcl::Font tempFont = font->getFont(); + if (compare_fonts_for_me(tempFont, rFont)) + { + return *font; + } + } + + FlashFont* pFont = new FlashFont(rFont, createID()); + maFonts.emplace_back(pFont); + return *pFont; +} + +void Writer::Impl_writeText(const Point& rPos, const OUString& rText, const KernArray* pDXArray, + long nWidth) +{ + const FontMetric aMetric(mpVDev->GetFontMetric()); + + bool bTextSpecial + = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE); + + if (!bTextSpecial) + { + Impl_writeText(rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + } + else + { + if (aMetric.GetRelief() != FontRelief::NONE) + { + Color aReliefColor(COL_LIGHTGRAY); + Color aTextColor(mpVDev->GetTextColor()); + + if (aTextColor == COL_BLACK) + aTextColor = COL_WHITE; + + // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct + if (aTextColor == COL_WHITE) + aReliefColor = COL_BLACK; + + Point aPos(rPos); + Point aOffset(6, 6); + + if (aMetric.GetRelief() == FontRelief::Engraved) + { + aPos -= aOffset; + } + else + { + aPos += aOffset; + } + + Impl_writeText(aPos, rText, pDXArray, nWidth, aReliefColor); + Impl_writeText(rPos, rText, pDXArray, nWidth, aTextColor); + } + else + { + if (aMetric.IsShadow()) + { + long nOff = 1 + ((aMetric.GetLineHeight() - 24) / 24); + if (aMetric.IsOutline()) + nOff += 6; + + Color aTextColor(mpVDev->GetTextColor()); + Color aShadowColor(COL_BLACK); + + if ((aTextColor == COL_BLACK) || (aTextColor.GetLuminance() < 8)) + aShadowColor = COL_LIGHTGRAY; + + Point aPos(rPos); + aPos += Point(nOff, nOff); + Impl_writeText(aPos, rText, pDXArray, nWidth, aShadowColor); + + if (!aMetric.IsOutline()) + { + Impl_writeText(rPos, rText, pDXArray, nWidth, aTextColor); + } + } + + if (aMetric.IsOutline()) + { + Point aPos = rPos + Point(-6, -6); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(+6, +6); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(-6, +0); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(-6, +6); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(+0, +6); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(+0, -6); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(+6, -1); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + aPos = rPos + Point(+6, +0); + Impl_writeText(aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor()); + + Impl_writeText(rPos, rText, pDXArray, nWidth, COL_WHITE); + } + } + } +} + +void Writer::Impl_writeText(const Point& rPos, const OUString& rText, const KernArray* pDXArray, + long nWidth, Color aTextColor) +{ + sal_Int32 nLen = rText.getLength(); + + if (!nLen) + return; + + const bool bRTL = bool(mpVDev->GetLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiRtl); + + sal_Int16 nScriptType = ScriptType::LATIN; + Reference<XBreakIterator> xBI(Impl_GetBreakIterator()); + if (xBI.is()) + { + nScriptType = xBI->getScriptType(rText, 0); + } + + // if the text is either right to left or complex or asian, we + // ask the output device for a polygon representation. + // On complex and asian text, each unicode character can have + // different glyph representation, based on context. Also positioning + // is not trivial so we let the output device do it for us. + if (bRTL || (nScriptType != ScriptType::LATIN)) + { + // todo: optimize me as this will generate a huge amount of duplicate polygons + tools::PolyPolygon aPolyPoygon; + mpVDev->GetTextOutline(aPolyPoygon, rText, static_cast<sal_uInt16>(nLen), nWidth, + *pDXArray); + aPolyPoygon.Translate(rPos); + Impl_writePolyPolygon(aPolyPoygon, true, aTextColor, aTextColor); + } + else + { + Size aNormSize; + std::unique_ptr<KernArray> pOwnArray; + KernArray aDX; + + // get text sizes + if (pDXArray) + { + aNormSize = Size(mpVDev->GetTextWidth(rText), 0); + aDX = *pDXArray; + } + else + { + pOwnArray.reset(new KernArray()); + aNormSize = Size(mpVDev->GetTextArray(rText, pOwnArray.get()), 0); + aDX = *pOwnArray; + } + + if (nLen > 1) + { + aNormSize.setWidth(aDX[nLen - 2] + mpVDev->GetTextWidth(OUString(rText[nLen - 1]))); + + if (nWidth && aNormSize.Width() && (nWidth != aNormSize.Width())) + { + const double fFactor = static_cast<double>(nWidth) / aNormSize.Width(); + + for (sal_Int32 i = 0; i < (nLen - 1); i++) + aDX.set(i, FRound(aDX[i] * fFactor)); + } + } + + vcl::Font aOldFont(mpVDev->GetFont()); + Point aBaseLinePos(rPos); + + vcl::Font aFont(aOldFont); + Degree10 nOrientation = aFont.GetOrientation(); + aFont.SetOrientation(0_deg10); + aFont.SetUnderline(LINESTYLE_NONE); + aFont.SetStrikeout(STRIKEOUT_NONE); + mpVDev->SetFont(aFont); + + const FontMetric aMetric(mpVDev->GetFontMetric()); + + FlashFont& rFlashFont = Impl_getFont(aFont); + + // always adjust text position to match baseline alignment + switch (aOldFont.GetAlignment()) + { + case ALIGN_TOP: + aBaseLinePos.AdjustY(aMetric.GetAscent()); + break; + + case ALIGN_BOTTOM: + aBaseLinePos.AdjustY(-(aMetric.GetDescent())); + break; + + default: + break; + } + + // get mapped text position + const Point aPt(map(aBaseLinePos)); + + // write text element + +#if 0 // makes the calculated bound rect visible for debugging +{ + tools::Polygon aTmpPoly( aPoly ); + sal_uInt16 nID = FlashGeometryExporter::writePolygonShape( aMovieStream, aTmpPoly, false, COL_MAGENTA, COL_MAGENTA, mpClipPolyPolygon ); + ImplPlaceObject( nID ); +} +#endif + + // CL: This is still a hack until we figure out how to calculate a correct bound rect + // for rotated text + tools::Rectangle textBounds(0, 0, static_cast<long>(mnDocWidth * mnDocXScale), + static_cast<long>(mnDocHeight * mnDocYScale)); + double scale = 1.0; + + // scale width if we have a stretched text + if (0 != aFont.GetFontSize().Width()) + { + vcl::Font aTmpFont(aFont); + aTmpFont.SetAverageFontWidth(0); + mpVDev->SetFont(aTmpFont); + + const FontMetric aMetric2(mpVDev->GetFontMetric()); + mpVDev->SetFont(aFont); + + const long n1 = aFont.GetFontSize().Width(); + const long n2 = aMetric2.GetFontSize().Width(); + scale = static_cast<double>(n1) / static_cast<double>(n2); + } + + basegfx::B2DHomMatrix m(basegfx::utils::createRotateB2DHomMatrix(toRadians(nOrientation))); + m.translate(aPt.X() / scale, double(aPt.Y())); + m.scale(scale, scale); + + sal_Int16 nHeight = Int16_(map(Size(0, aFont.GetFontHeight())).Height()); + + startTag(TAG_DEFINETEXT); + + sal_uInt16 nTextId = createID(); + + mpTag->addUI16(nTextId); + mpTag->addRect(textBounds); + mpTag->addMatrix(m); + + sal_uInt8 nGlyphBits = 16; + sal_uInt8 nAdvanceBits = 16; + + mpTag->addUI8(nGlyphBits); + mpTag->addUI8(nAdvanceBits); + + // text style change record + mpTag->addUI8(0x8c); + mpTag->addUI16(rFlashFont.getID()); + mpTag->addRGB(aTextColor); + mpTag->addUI16(uInt16_(nHeight)); + + DBG_ASSERT(nLen <= 127, "TODO: handle text with more than 127 characters"); + + // Glyph record + mpTag->addUI8(static_cast<sal_uInt8>(nLen)); + + BitStream aBits; + + sal_Int32 nLastDX = 0; + sal_Int32 nAdvance; + for (sal_Int32 i = 0; i < nLen; i++) + { + if (i < (nLen - 1)) + { + nAdvance = aDX[i] - nLastDX; + nLastDX = aDX[i]; + } + else + { + nAdvance = 0; + } + + aBits.writeUB(rFlashFont.getGlyph(rText[i], mpVDev), nGlyphBits); + aBits.writeSB(Int16_(map(Size(static_cast<long>(nAdvance / scale), 0)).Width()), + nAdvanceBits); + } + + mpTag->addBits(aBits); + mpTag->addUI8(0); + + endTag(); + + maShapeIds.push_back(nTextId); + + // AS: Write strikeout and underline, if necessary. This code was originally taken from the SVG + // export facility, although the positioning had to be tweaked a little. I can't explain the + // numbers, but the flash lines up very well with the original OOo document. All of this should + // probably be converted to polygons as part of the meta file, though, as we don't handle any + // fancy lines (like dashes). + if ((aOldFont.GetStrikeout() != STRIKEOUT_NONE) + || (aOldFont.GetUnderline() != LINESTYLE_NONE)) + { + tools::Polygon aPoly(4); + const long nLineHeight = std::max<long>(FRound(aMetric.GetLineHeight() * 0.05), 1); + + if (aOldFont.GetStrikeout() != STRIKEOUT_NONE) + { + aPoly[0].setX(aBaseLinePos.X()); + aPoly[0].setY(aBaseLinePos.Y() - FRound(aMetric.GetAscent() * 0.26) - nLineHeight); + aPoly[1].setX(aPoly[0].X() + aNormSize.Width() - 1); + aPoly[1].setY(aPoly[0].Y()); + aPoly[2].setX(aPoly[1].X()); + aPoly[2].setY(aPoly[1].Y() + nLineHeight - 1); + aPoly[3].setX(aPoly[0].X()); + aPoly[3].setY(aPoly[2].Y()); + + Impl_writePolygon(aPoly, true, aTextColor, aTextColor); + } + + // AS: The factor of 1.5 on the nLineHeight is a magic number. I'm not sure why it works, + // but it looks good to me. + if (aOldFont.GetUnderline() != LINESTYLE_NONE) + { + aPoly[0].setX(aBaseLinePos.X()); + aPoly[0].setY(static_cast<long>(aBaseLinePos.Y() + 1.5 * nLineHeight)); + aPoly[1].setX(aPoly[0].X() + aNormSize.Width() - 1); + aPoly[1].setY(aPoly[0].Y()); + aPoly[2].setX(aPoly[1].X()); + aPoly[2].setY(aPoly[1].Y() + nLineHeight - 1); + aPoly[3].setX(aPoly[0].X()); + aPoly[3].setY(aPoly[2].Y()); + + Impl_writePolygon(aPoly, true, aTextColor, aTextColor); + } + } + + mpVDev->SetFont(aOldFont); + } +} + +sal_uInt16 Writer::defineBitmap(const BitmapEx& bmpSource, sal_Int32 nJPEGQualityLevel) +{ + BitmapChecksum bmpChecksum = bmpSource.GetChecksum(); + + ChecksumCache::iterator it = mBitmapCache.find(bmpChecksum); + + // AS: We already exported this bitmap, so just return its ID. + if (mBitmapCache.end() != it) + return it->second; + + sal_uInt16 nBitmapId = createID(); + mBitmapCache[bmpChecksum] = nBitmapId; + + // AS: OK, we have a good image, so now we decide whether or not to JPEG it or + // or Lossless compress it. + + // Figure out lossless size + std::vector<sal_uInt8> aImageData, aAlphaData; + + sal_uInt32 width = bmpSource.GetPrefSize().Width(); + sal_uInt32 height = bmpSource.GetPrefSize().Height(); + bmpSource.GetSplitData(aImageData, aAlphaData); + sal_uInt32 raw_size = width * height * 4; + uLongf compressed_size = raw_size + static_cast<sal_uInt32>(raw_size / 100) + 12; + std::unique_ptr<sal_uInt8[]> pCompressed(new sal_uInt8[compressed_size]); + + if (compress2(pCompressed.get(), &compressed_size, aImageData.data(), raw_size, + Z_BEST_COMPRESSION) + != Z_OK) + { + SAL_WARN("filter.flash", "compress2 failed!"); + ((void)0); + } + + // AS: SWF files let you provide an Alpha mask for JPEG images, but we have + // to ZLIB compress the alpha channel separately. + uLong alpha_compressed_size = 0; + std::unique_ptr<sal_uInt8[]> pAlphaCompressed; + if (bmpSource.IsAlpha()) + { + alpha_compressed_size + = uLongf(width * height + static_cast<sal_uInt32>(raw_size / 100) + 12); + pAlphaCompressed.reset(new sal_uInt8[compressed_size]); + + if (compress2(pAlphaCompressed.get(), &alpha_compressed_size, aAlphaData.data(), + width * height, Z_BEST_COMPRESSION) + != Z_OK) + { + SAL_WARN("filter.flash", "compress2 failed!"); + ((void)0); + } + } + + // clear these early for less peak memory usage + aImageData.resize(0); + aAlphaData.resize(0); + + // Figure out JPEG size + const sal_uInt8* pJpgData = nullptr; + sal_uInt32 nJpgDataLength = 0xffffffff; + + Graphic aGraphic(bmpSource); + SvMemoryStream aDstStm(65535, 65535); + + GraphicFilter aFilter; + + Sequence<PropertyValue> aFilterData(sal_Int32(nJPEGQualityLevel != -1)); + auto pFilterData = aFilterData.getArray(); + + if (nJPEGQualityLevel != -1) + { + pFilterData[0].Name = "Quality"; + pFilterData[0].Value <<= nJPEGQualityLevel; + } + + if (aFilter.ExportGraphic(aGraphic, std::u16string_view(), aDstStm, + aFilter.GetExportFormatNumberForShortName(JPG_SHORTNAME), + &aFilterData) + == ERRCODE_NONE) + { + pJpgData = static_cast<const sal_uInt8*>(aDstStm.GetData()); + nJpgDataLength = aDstStm.TellEnd(); + } + + // AS: Ok, now go ahead and use whichever is smaller. If JPEG is smaller, then + // we have to export as TAG_DEFINEBITSJPEG3 in the case that there is alpha + // channel data. + if (pJpgData && (nJpgDataLength + alpha_compressed_size < compressed_size)) + Impl_writeJPEG(nBitmapId, pJpgData, nJpgDataLength, pAlphaCompressed.get(), + alpha_compressed_size); + else + Impl_writeBmp(nBitmapId, width, height, pCompressed.get(), compressed_size); + + return nBitmapId; +} + +void Writer::Impl_writeImage(const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, + const Point& /* rSrcPt */, const Size& /* rSrcSz */, + const tools::Rectangle& rClipRect, bool bNeedToMapClipRect) +{ + if (rBmpEx.IsEmpty()) + return; + + BitmapEx bmpSource(rBmpEx); + + tools::Rectangle originalPixelRect(Point(), bmpSource.GetSizePixel()); + + Point srcPt(map(rPt)); + Size srcSize(map(rSz)); + tools::Rectangle destRect(srcPt, srcSize); + + // AS: Christian, my scaling factors are different than yours, and work better for me. + // However, I can't explain why exactly. I got some of this by trial and error. + double XScale = destRect.GetWidth() + ? static_cast<double>(originalPixelRect.GetWidth()) / destRect.GetWidth() + : 1.0; + double YScale = destRect.GetHeight() + ? static_cast<double>(originalPixelRect.GetHeight()) / destRect.GetHeight() + : 1.0; + + // AS: If rClipRect has a value set, then we need to crop the bmp appropriately. + // If a map event already occurred in the metafile, then we do not need to map + // the clip rect as it's already been done. + if (!rClipRect.IsEmpty()) + { + // AS: Christian, I also don't understand why bNeedToMapClipRect is necessary, but it + // works like a charm. Usually, the map event in the meta file does not cause the + // clipping rectangle to get mapped. However, sometimes there are multiple layers + // of mapping which eventually do cause the clipping rect to be mapped. + Size clipSize(bNeedToMapClipRect ? map(rClipRect.GetSize()) : rClipRect.GetSize()); + tools::Rectangle clipRect(Point(), clipSize); + destRect.Intersection(clipRect); + + tools::Rectangle cropRect(destRect); + + // AS: The bmp origin is always 0,0 so we have to adjust before we crop. + cropRect.Move(-srcPt.X(), -srcPt.Y()); + // AS: Rectangle has no scale function (?!) so I do it manually... + tools::Rectangle cropPixelRect(static_cast<long>(cropRect.Left() * XScale), + static_cast<long>(cropRect.Top() * YScale), + static_cast<long>(cropRect.Right() * XScale), + static_cast<long>(cropRect.Bottom() * YScale)); + + bmpSource.Crop(cropPixelRect); + } + + if (bmpSource.IsEmpty()) + return; + + // #105949# fix images that are under 16 pixels width or height by + // expanding them. Some swf players can't display such small + // bitmaps + const Size& rSizePixel = bmpSource.GetSizePixel(); + if ((rSizePixel.Width() < 16) || (rSizePixel.Height() < 16)) + { + const sal_uInt32 nDX = rSizePixel.Width() < 16 ? 16 - rSizePixel.Width() : 0; + const sal_uInt32 nDY = rSizePixel.Height() < 16 ? 16 - rSizePixel.Height() : 0; + bmpSource.Expand(nDX, nDY); + } + + sal_Int32 nJPEGQuality = mnJPEGCompressMode; + + Size szDestPixel = mpVDev->LogicToPixel(srcSize, aTWIPSMode); + + double pixXScale = originalPixelRect.GetWidth() + ? static_cast<double>(szDestPixel.Width()) / originalPixelRect.GetWidth() + : 1.0; + double pixYScale = originalPixelRect.GetHeight() ? static_cast<double>(szDestPixel.Height()) + / originalPixelRect.GetHeight() + : 1.0; + + // AS: If the image has been scaled down, then scale down the quality + // that we use for JPEG compression. + if (pixXScale < 1.0 && pixYScale < 1.0) + { + double qualityScale = (pixXScale + pixYScale) / 2; + + nJPEGQuality = static_cast<sal_Int32>(nJPEGQuality * qualityScale); + + if (nJPEGQuality < 10) + nJPEGQuality += 3; + } + + sal_uInt16 nBitmapId = defineBitmap(bmpSource, nJPEGQuality); + + tools::Polygon aPoly(destRect); + + // AS: Since images are being cropped now, no translation is normally necessary. + // However, some things like graphical bullet points still get translated. + ::basegfx::B2DHomMatrix m; // #i73264# + m.scale(1.0 / XScale, 1.0 / YScale); + if (destRect.Left() || destRect.Top()) + m.translate(destRect.Left(), destRect.Top()); + + FillStyle aFillStyle(nBitmapId, true, m); + + sal_uInt16 nShapeId = defineShape(aPoly, aFillStyle); + + maShapeIds.push_back(nShapeId); +} + +void Writer::Impl_writeBmp(sal_uInt16 nBitmapId, sal_uInt32 width, sal_uInt32 height, + sal_uInt8 const* pCompressed, sal_uInt32 compressed_size) +{ + startTag(TAG_DEFINEBITSLOSSLESS2); + + mpTag->addUI16(nBitmapId); + mpTag->addUI8(5); + mpTag->addUI16(uInt16_(width)); + mpTag->addUI16(uInt16_(height)); + + mpTag->WriteBytes(pCompressed, compressed_size); + + endTag(); +} + +void Writer::Impl_writeJPEG(sal_uInt16 nBitmapId, const sal_uInt8* pJpgData, + sal_uInt32 nJpgDataLength, sal_uInt8 const* pAlphaCompressed, + sal_uInt32 alpha_compressed_size) +{ + // AS: Go through the actual JPEG bits, separating out the + // header fields from the actual image fields. Fields are + // identified by 0xFFXX where XX is the field type. Both + // the header and the image need start and stop (D8 and D9), + // so that's why you see those written to both. I don't + // really know what the rest of these are, I got it to work + // kind of by trial and error and by comparing with known + // good SWF files. + sal_uInt8 cType = 0x01; + const sal_uInt8* pJpgSearch = pJpgData; + + int nLength = 0; + + SvMemoryStream EncodingTableStream; + SvMemoryStream ImageBitsStream; + for (; pJpgSearch < pJpgData + nJpgDataLength; pJpgSearch += nLength) + { +#ifdef DBG_UTIL + if (0xFF != *pJpgSearch) + { + OSL_FAIL("Expected JPEG marker."); + ((void)0); + } +#endif + + cType = *(pJpgSearch + 1); + + if (0xD8 == cType || 0xD9 == cType) + { + nLength = 2; + } + else if (0xDA == cType) + { + //AS: This is the actual image data, and runs to the + // end of the file (as best I know), minus 2 bytes + // for the closing 0xFFD9. + nLength = nJpgDataLength - (pJpgSearch - pJpgData) - 2; + } + else + { + // AS: Lengths are big endian. + + // Beware. pJpgSearch is not necessarily word-aligned, + // so we access it byte-wise. + + // AS: Add 2 to the length to include the 0xFFXX itself. + nLength = 2 + (pJpgSearch[2] << 8) + pJpgSearch[3]; + } + + // AS: I'm referring to libjpeg for a list of what these + // markers are. See jdmarker.c for a list. + // AS: I'm ignoring application specific markers 0xE1...0xEF + // and comments 0xFE. I don't know what + // 0xF0 or 0xFD are for, and they don't come up. + // Additionally, 0xDE and 0xDF aren't clear to me. + switch (cType) + { + case 0xD8: + case 0xD9: + EncodingTableStream.WriteBytes(pJpgSearch, nLength); + ImageBitsStream.WriteBytes(pJpgSearch, nLength); + break; + + case 0x01: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xC4: + EncodingTableStream.WriteBytes(pJpgSearch, nLength); + break; + + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC5: + case 0xC6: + case 0xC7: + // case 0xC8: Apparently reserved for JPEG extensions? + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xDA: + case 0xE0: + ImageBitsStream.WriteBytes(pJpgSearch, nLength); + break; + + default: + OSL_FAIL("JPEG marker I didn't handle!"); + } + } + + sal_uInt32 nEncodingTableSize = EncodingTableStream.TellEnd(); + EncodingTableStream.Seek(STREAM_SEEK_TO_BEGIN); + + sal_uInt32 nImageBitsSize = ImageBitsStream.TellEnd(); + ImageBitsStream.Seek(STREAM_SEEK_TO_BEGIN); + + // AS: If we need alpha support, use TAG_DEFINEBITSJPEG3. + if (alpha_compressed_size > 0) + { + startTag(TAG_DEFINEBITSJPEG3); + + mpTag->addUI16(nBitmapId); + + mpTag->addUI32(nEncodingTableSize + nImageBitsSize); + + mpTag->WriteBytes(EncodingTableStream.GetData(), nEncodingTableSize); + mpTag->WriteBytes(ImageBitsStream.GetData(), nImageBitsSize); + + mpTag->WriteBytes(pAlphaCompressed, alpha_compressed_size); + + endTag(); + } + else + { + startTag(TAG_DEFINEBITSJPEG2); + + mpTag->addUI16(nBitmapId); + + mpTag->WriteBytes(EncodingTableStream.GetData(), nEncodingTableSize); + mpTag->WriteBytes(ImageBitsStream.GetData(), nImageBitsSize); + + endTag(); + } +} + +void Writer::Impl_writeLine(const Point& rPt1, const Point& rPt2, const Color* pLineColor) +{ + Color aOldColor(mpVDev->GetLineColor()); + if (pLineColor) + mpVDev->SetLineColor(*pLineColor); + + const Point aPtAry[2] = { rPt1, rPt2 }; + tools::Polygon aPoly(2, aPtAry); + Impl_writePolygon(aPoly, false); + + mpVDev->SetLineColor(aOldColor); +} + +void Writer::Impl_writeRect(const tools::Rectangle& rRect, long nRadX, long nRadY) +{ + if ((rRect.Top() == rRect.Bottom()) || (rRect.Left() == rRect.Right())) + { + Color aColor(mpVDev->GetFillColor()); + Impl_writeLine(rRect.TopLeft(), rRect.BottomRight(), &aColor); + } + else + { + tools::Polygon aPoly(rRect, nRadX, nRadY); + Impl_writePolygon(aPoly, true); + } +} + +void Writer::Impl_writeEllipse(const Point& rCenter, long nRadX, long nRadY) +{ + tools::Polygon aPoly(rCenter, nRadX, nRadY); + Impl_writePolygon(aPoly, false); +} + +/** Writes the stroke defined by SvtGraphicStroke and returns true or it returns + false if it can't handle this stroke. +*/ +bool Writer::Impl_writeStroke(SvtGraphicStroke const& rStroke) +{ + tools::Polygon aPolygon; + rStroke.getPath(aPolygon); + tools::PolyPolygon aPolyPolygon(aPolygon); + + map(aPolyPolygon); + + // as log as not LINESTYLE2 and DefineShape4 is used (which + // added support for LineJoin), only round LineJoins are + // supported. Fallback to MetaActionType::POLYLINE and MetaActionType::LINE + if (SvtGraphicStroke::joinRound != rStroke.getJoinType()) + return false; + + tools::PolyPolygon aStartArrow; + rStroke.getStartArrow(aStartArrow); + if (0 != aStartArrow.Count()) + return false; // todo: Implement line ends + + tools::PolyPolygon aEndArrow; + rStroke.getEndArrow(aEndArrow); + if (0 != aEndArrow.Count()) + return false; // todo: Implement line ends + + SvtGraphicStroke::DashArray aDashArray; + rStroke.getDashArray(aDashArray); + if (!aDashArray.empty()) + return false; // todo: implement dashes + + Color aColor(mpVDev->GetLineColor()); + + if (0.0 != rStroke.getTransparency()) + aColor.SetAlpha(255 + - sal::static_int_cast<sal_uInt8>( + std::clamp(rStroke.getTransparency() * 0xff, 0.0, 255.0))); + + sal_uInt16 nShapeId = defineShape(aPolyPolygon, + sal::static_int_cast<sal_uInt16>(mapRelative( + static_cast<sal_Int32>(rStroke.getStrokeWidth()))), + aColor); + maShapeIds.push_back(nShapeId); + return true; +} + +/** Writes the filling defined by SvtGraphicFill and returns true or it returns + false if it can't handle this filling. +*/ +bool Writer::Impl_writeFilling(SvtGraphicFill const& rFilling) +{ + tools::PolyPolygon aPolyPolygon; + rFilling.getPath(aPolyPolygon); + + tools::Rectangle aOldRect(aPolyPolygon.GetBoundRect()); + + map(aPolyPolygon); + + tools::Rectangle aNewRect(aPolyPolygon.GetBoundRect()); + + switch (rFilling.getFillType()) + { + case SvtGraphicFill::fillSolid: + { + Color aColor(rFilling.getFillColor()); + + if (0.0 != rFilling.getTransparency()) + aColor.SetAlpha(255 + - sal::static_int_cast<sal_uInt8>( + std::clamp(rFilling.getTransparency() * 0xff, 0.0, 255.0))); + + FillStyle aFillStyle(aColor); + + sal_uInt16 nShapeId = defineShape(aPolyPolygon, aFillStyle); + maShapeIds.push_back(nShapeId); + } + break; + case SvtGraphicFill::fillGradient: + return false; + case SvtGraphicFill::fillHatch: + return false; + case SvtGraphicFill::fillTexture: + { + Graphic aGraphic; + rFilling.getGraphic(aGraphic); + + // CL->AS: Should we also scale down the quality here depending on image scale? + sal_uInt16 nBitmapId = defineBitmap(aGraphic.GetBitmapEx(), mnJPEGCompressMode); + + ::basegfx::B2DHomMatrix aMatrix; // #i73264# + + SvtGraphicFill::Transform aTransform; + + rFilling.getTransform(aTransform); + + sal_uInt16 a, b; + for (a = 0; a < 2; a++) + { + for (b = 0; b < 3; b++) + { + aMatrix.set(a, b, aTransform.matrix[a * 3 + b]); + } + } + aMatrix.set(2, 0, 0.0); + aMatrix.set(2, 1, 0.0); + aMatrix.set(2, 2, 1.0); + + // scale bitmap + double XScale = aOldRect.GetWidth() + ? static_cast<double>(aNewRect.GetWidth()) / aOldRect.GetWidth() + : 1.0; + double YScale = aOldRect.GetHeight() + ? static_cast<double>(aNewRect.GetHeight()) / aOldRect.GetHeight() + : 1.0; + + aMatrix.scale(XScale, YScale); + + FillStyle aFillStyle(nBitmapId, !rFilling.isTiling(), aMatrix); + + sal_uInt16 nShapeId = defineShape(aPolyPolygon, aFillStyle); + maShapeIds.push_back(nShapeId); + } + break; + } + return true; +} + +/* CL: The idea was to export page fields as text fields that get their + string from a variable set with actionscript by each page. This didn't + work out since the formatting is always wrong when text follows the + page number field since pages greater one may require more space than + page 1 +*/ +#if 0 +bool Writer::Impl_writePageField( Rectangle& rTextBounds ) +{ + startTag( TAG_DEFINEEDITTEXT ); + + sal_uInt16 nTextId = createID(); + + mpTag->addUI16( nTextId ); + mpTag->addRect( rTextBounds ); + + BitStream aBits; + aBits.writeUB( 1, 1 ); // HasText + aBits.writeUB( 0, 1 ); // WordWrap + aBits.writeUB( 0, 1 ); // MultiLine + aBits.writeUB( 0, 1 ); // Password + aBits.writeUB( 1, 1 ); // HasTextColor + aBits.writeUB( 0, 1 ); // HasMaxLength + aBits.writeUB( 0, 1 ); // HasFont + aBits.writeUB( 0, 1 ); // Reserved + aBits.writeUB( 0, 1 ); // AutoSize + aBits.writeUB( 0, 1 ); // HasLayout + aBits.writeUB( 1, 1 ); // NoSelect + aBits.writeUB( 1, 1 ); // Border + aBits.writeUB( 0, 1 ); // Reserved + aBits.writeUB( 0, 1 ); // HTML + aBits.writeUB( 0, 1 ); // UseOutlines + mpTag->addBits( aBits ); + + Color aCOL_BLACK ); + mpTag->addRGB( aColor ); + mpTag->addString( "PageNumber" ); + mpTag->addString( "XXX" ); + + endTag(); + + maShapeIds.push_back( nTextId ); + + return true; +} +#endif + +void Writer::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 (sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++) + { + const basegfx::B2DPolygon& aCandidate(aLinePolyPolygon.getB2DPolygon(a)); + Impl_writePolygon(tools::Polygon(aCandidate), false); + } + } + + if (!aFillPolyPolygon.count()) + return; + + const Color aOldLineColor(mpVDev->GetLineColor()); + const Color aOldFillColor(mpVDev->GetFillColor()); + + mpVDev->SetLineColor(); + mpVDev->SetFillColor(aOldLineColor); + + for (sal_uInt32 a(0); a < aFillPolyPolygon.count(); a++) + { + const tools::Polygon aPolygon(aFillPolyPolygon.getB2DPolygon(a)); + Impl_writePolyPolygon(tools::PolyPolygon(aPolygon), true); + } + + mpVDev->SetLineColor(aOldLineColor); + mpVDev->SetFillColor(aOldFillColor); +} + +void Writer::Impl_writeActions(const GDIMetaFile& rMtf) +{ + tools::Rectangle clipRect; + int bMap = 0; + for (size_t i = 0, nCount = rMtf.GetActionSize(); i < nCount; i++) + { + const MetaAction* pAction = rMtf.GetAction(i); + const MetaActionType nType = pAction->GetType(); + + switch (nType) + { + case MetaActionType::PIXEL: + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction); + + Impl_writeLine(pA->GetPoint(), pA->GetPoint(), &pA->GetColor()); + } + break; + + case MetaActionType::POINT: + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction); + + Impl_writeLine(pA->GetPoint(), pA->GetPoint()); + } + break; + + case MetaActionType::LINE: + { + const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction); + + if (pA->GetLineInfo().IsDefault()) + { + Impl_writeLine(pA->GetStartPoint(), 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())); + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon); + } + } + break; + + case MetaActionType::RECT: + { + Impl_writeRect(static_cast<const MetaRectAction*>(pAction)->GetRect(), 0, 0); + } + break; + + case MetaActionType::ROUNDRECT: + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction); + + Impl_writeRect(pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound()); + } + break; + + case MetaActionType::ELLIPSE: + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + Impl_writeEllipse(rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1); + } + break; + + case MetaActionType::ARC: + case MetaActionType::PIE: + case MetaActionType::CHORD: + case MetaActionType::POLYGON: + { + 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; + } + + if (aPoly.GetSize()) + { + Impl_writePolygon(aPoly, true); + } + } + break; + + case MetaActionType::POLYLINE: + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction); + const tools::Polygon& rPoly = pA->GetPolygon(); + + if (rPoly.GetSize()) + { + if (pA->GetLineInfo().IsDefault()) + { + Impl_writePolygon(rPoly, false); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon()); + } + } + } + break; + + case MetaActionType::POLYPOLYGON: + { + const MetaPolyPolygonAction* pA + = static_cast<const MetaPolyPolygonAction*>(pAction); + const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + if (rPolyPoly.Count()) + Impl_writePolyPolygon(rPolyPoly, true); + } + break; + + case MetaActionType::GRADIENT: + { + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction); + + tools::Polygon aPoly(pA->GetRect()); + tools::PolyPolygon aPolyPoly(aPoly); + Impl_writeGradientEx(aPolyPoly, pA->GetGradient()); + } + break; + + case MetaActionType::GRADIENTEX: + { + const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction); + Impl_writeGradientEx(pA->GetPolyPolygon(), pA->GetGradient()); + } + break; + + case MetaActionType::HATCH: + { + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction); + GDIMetaFile aTmpMtf; + + mpVDev->AddHatchActions(pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf); + Impl_writeActions(aTmpMtf); + } + break; + + case MetaActionType::Transparent: + { + const MetaTransparentAction* pA + = static_cast<const MetaTransparentAction*>(pAction); + const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + if (rPolyPoly.Count()) + { + // convert transparence from percent into 0x00 - 0xff + sal_uInt8 nTransparence = static_cast<sal_uInt8>( + std::clamp(pA->GetTransparence() * 2.55, 0.0, 255.0)); + Impl_writePolyPolygon(rPolyPoly, true, nTransparence); + } + } + 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; + 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); + + const Gradient& rGradient = pA->GetGradient(); + sal_uInt32 nLuminance + = (static_cast<sal_Int32>(rGradient.GetStartColor().GetLuminance()) + + static_cast<sal_Int32>(rGradient.GetEndColor().GetLuminance())) + >> 1; + + sal_uInt8 nOldGlobalTransparency = mnGlobalTransparency; + mnGlobalTransparency = static_cast<sal_uInt8>(std::clamp( + nLuminance, static_cast<sal_uInt32>(0), static_cast<sal_uInt32>(255))); + + mpVDev->Push(); + Impl_writeActions(aTmpMtf); + mpVDev->Pop(); + + mnGlobalTransparency = nOldGlobalTransparency; + } + break; + + case MetaActionType::EPS: + { + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction); + const GDIMetaFile& aGDIMetaFile(pA->GetSubstitute()); + bool bFound = false; + + for (size_t j = 0, nC = aGDIMetaFile.GetActionSize(); (j < nC) && !bFound; j++) + { + const MetaAction* pSubstAct = aGDIMetaFile.GetAction(j); + + if (pSubstAct->GetType() == MetaActionType::BMPSCALE) + { + bFound = true; + const MetaBmpScaleAction* pBmpScaleAction + = static_cast<const MetaBmpScaleAction*>(pSubstAct); + Impl_writeImage(BitmapEx(pBmpScaleAction->GetBitmap()), pA->GetPoint(), + pA->GetSize(), Point(), + pBmpScaleAction->GetBitmap().GetSizePixel(), clipRect, + 1 == bMap); + } + } + } + break; + + case MetaActionType::COMMENT: + { + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); + const sal_uInt8* pData = pA->GetData(); + + if (pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + const MetaGradientExAction* pGradAction = nullptr; + bool bDone = false; + + while (!bDone && (++i < nCount)) + { + pAction = rMtf.GetAction(i); + + 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"))) + { + bDone = true; + } + } + + if (pGradAction) + Impl_writeGradientEx(pGradAction->GetPolyPolygon(), + pGradAction->GetGradient()); + } + else if (pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") && pData) + { + // this comment encapsulates all high level information for a filling that caused + // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment. + + SvtGraphicFill aFilling; + SvMemoryStream aMemStm(const_cast<sal_uInt8*>(pData), pA->GetDataSize(), + StreamMode::READ); + + // read the fill info + ReadSvtGraphicFill(aMemStm, aFilling); + + // if impl_writeFilling can handle this high level filling, it returns true and we + // skip all meta actions until "XPATHFILL_SEQ_END" + if (Impl_writeFilling(aFilling)) + { + bool bDone = false; + + while (!bDone && (++i < nCount)) + { + pAction = rMtf.GetAction(i); + + if ((pAction->GetType() == MetaActionType::COMMENT) + && (static_cast<const MetaCommentAction*>(pAction) + ->GetComment() + .equalsIgnoreAsciiCase("XPATHFILL_SEQ_END"))) + { + bDone = true; + } + } + } + } + else if (pA->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN") && pData) + { + // this comment encapsulates all high level information for a filling that caused + // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment. + + SvtGraphicStroke aStroke; + SvMemoryStream aMemStm(const_cast<sal_uInt8*>(pData), pA->GetDataSize(), + StreamMode::READ); + + // read the fill info + ReadSvtGraphicStroke(aMemStm, aStroke); + + // if impl_writeStroke can handle this high level stroke, it returns true and we + // skip all meta actions until "XPATHSTROKE_SEQ_END" + if (Impl_writeStroke(aStroke)) + { + bool bDone = false; + + while (!bDone && (++i < nCount)) + { + pAction = rMtf.GetAction(i); + + if ((pAction->GetType() == MetaActionType::COMMENT) + && (static_cast<const MetaCommentAction*>(pAction) + ->GetComment() + .equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_END"))) + { + bDone = true; + } + } + } + } + } + break; + + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + + Impl_writeImage(BitmapEx(pA->GetBitmap()), pA->GetPoint(), pA->GetSize(), Point(), + pA->GetBitmap().GetSizePixel(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::BMP: + { + const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction); + Impl_writeImage(BitmapEx(pA->GetBitmap()), pA->GetPoint(), + mpVDev->PixelToLogic(pA->GetBitmap().GetSizePixel()), Point(), + pA->GetBitmap().GetSizePixel(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::BMPSCALEPART: + { + const MetaBmpScalePartAction* pA + = static_cast<const MetaBmpScalePartAction*>(pAction); + Impl_writeImage(BitmapEx(pA->GetBitmap()), pA->GetDestPoint(), pA->GetDestSize(), + pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::BMPEX: + { + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction); + Impl_writeImage(pA->GetBitmapEx(), pA->GetPoint(), + mpVDev->PixelToLogic(pA->GetBitmapEx().GetSizePixel()), Point(), + pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + Impl_writeImage(pA->GetBitmapEx(), pA->GetPoint(), pA->GetSize(), Point(), + pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + const MetaBmpExScalePartAction* pA + = static_cast<const MetaBmpExScalePartAction*>(pAction); + Impl_writeImage(pA->GetBitmapEx(), pA->GetDestPoint(), pA->GetDestSize(), + pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap); + } + break; + + case MetaActionType::TEXT: + { + const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction); + Impl_writeText(pA->GetPoint(), pA->GetText().copy(pA->GetIndex(), pA->GetLen()), + nullptr, 0); + } + break; + + case MetaActionType::TEXTRECT: + { + const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction); + Impl_writeText(pA->GetRect().TopLeft(), pA->GetText(), nullptr, 0); + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction); + Impl_writeText(pA->GetPoint(), pA->GetText().copy(pA->GetIndex(), pA->GetLen()), + &pA->GetDXArray(), 0); + } + break; + + case MetaActionType::STRETCHTEXT: + { + const MetaStretchTextAction* pA + = static_cast<const MetaStretchTextAction*>(pAction); + Impl_writeText(pA->GetPoint(), pA->GetText().copy(pA->GetIndex(), pA->GetLen()), + nullptr, pA->GetWidth()); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + const MetaISectRectClipRegionAction* pA + = static_cast<const MetaISectRectClipRegionAction*>(pAction); + clipRect = pA->GetRect(); + [[fallthrough]]; + } + case MetaActionType::CLIPREGION: + case MetaActionType::ISECTREGIONCLIPREGION: + case MetaActionType::MOVECLIPREGION: + { + const_cast<MetaAction*>(pAction)->Execute(mpVDev); + } + break; + + case MetaActionType::MAPMODE: + { + bMap++; + [[fallthrough]]; + } + case MetaActionType::REFPOINT: + case MetaActionType::LINECOLOR: + case MetaActionType::FILLCOLOR: + case MetaActionType::TEXTLINECOLOR: + case MetaActionType::TEXTFILLCOLOR: + case MetaActionType::TEXTCOLOR: + case MetaActionType::TEXTALIGN: + case MetaActionType::FONT: + case MetaActionType::PUSH: + case MetaActionType::POP: + case MetaActionType::LAYOUTMODE: + { + const_cast<MetaAction*>(pAction)->Execute(mpVDev); + } + break; + + case MetaActionType::RASTEROP: + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::WALLPAPER: + case MetaActionType::TEXTLINE: + { + // !!! >>> we don't want to support these actions + } + break; + + default: + break; + } + } +} + +void Writer::Impl_addStraightLine(BitStream& rBits, Point& rLastPoint, const double P2x, + const double P2y) +{ + Point aPoint(FRound(P2x), FRound(P2y)); + + Impl_addStraightEdgeRecord(rBits, Int16_(aPoint.X() - rLastPoint.X()), + Int16_(aPoint.Y() - rLastPoint.Y())); + rLastPoint = aPoint; +} + +void Writer::Impl_addQuadBezier(BitStream& rBits, Point& rLastPoint, const double P2x, + const double P2y, const double P3x, const double P3y) +{ + Point aControlPoint(FRound(P2x), FRound(P2y)); + Point aAnchorPoint(FRound(P3x), FRound(P3y)); + + Impl_addCurvedEdgeRecord(rBits, Int16_(aControlPoint.X() - rLastPoint.X()), + Int16_(aControlPoint.Y() - rLastPoint.Y()), + Int16_(aAnchorPoint.X() - aControlPoint.X()), + Int16_(aAnchorPoint.Y() - aControlPoint.Y())); + rLastPoint = aAnchorPoint; +} + +/* Approximate given cubic bezier curve by quadratic bezier segments */ +void Writer::Impl_quadBezierApprox(BitStream& rBits, Point& rLastPoint, const double d2, + const double P1x, const double P1y, const double P2x, + const double P2y, const double P3x, const double P3y, + const double P4x, const double P4y) +{ + // Check for degenerate case, where the given cubic bezier curve + // is already quadratic: P4 == 3P3 - 3P2 + P1 + if (P4x == 3.0 * P3x - 3.0 * P2x + P1x && P4y == 3.0 * P3y - 3.0 * P2y + P1y) + { + Impl_addQuadBezier(rBits, rLastPoint, 3.0 / 2.0 * P2x - 1.0 / 2.0 * P1x, + 3.0 / 2.0 * P2y - 1.0 / 2.0 * P1y, P4x, P4y); + } + else + { + // Create quadratic segment for given cubic: + // Start and end point must coincide, determine quadratic control + // point in such a way that it lies on the intersection of the + // tangents at start and end point, resp. Thus, both cubic and + // quadratic curve segments will match in 0th and 1st derivative + // at the start and end points + + // Intersection of P2P1 and P4P3 + // (P2y-P4y)(P3x-P4x)-(P2x-P4x)(P3y-P4y) + // lambda = ------------------------------------- + // (P1x-P2x)(P3y-P4y)-(P1y-P2y)(P3x-P4x) + // + // Intersection point IP is now + // IP = P2 + lambda(P1-P2) + // + const double nominator((P2y - P4y) * (P3x - P4x) - (P2x - P4x) * (P3y - P4y)); + const double denominator((P1x - P2x) * (P3y - P4y) - (P1y - P2y) * (P3x - P4x)); + const double lambda(nominator / denominator); + + const double IPx(P2x + lambda * (P1x - P2x)); + const double IPy(P2y + lambda * (P1y - P2y)); + + // Introduce some alias names: quadratic start point is P1, end + // point is P4, control point is IP + const double QP1x(P1x); + const double QP1y(P1y); + const double QP2x(IPx); + const double QP2y(IPy); + const double QP3x(P4x); + const double QP3y(P4y); + + // Adapted bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + // + // ||C(t) - Q(t)|| <= max ||c_j - q_j|| + // 0<=j<=n + // + // In this case, we don't need the distance from the cubic bezier + // to a straight line, but to a quadratic bezier. The c_j's are + // the cubic bezier's bernstein coefficients, the q_j's the + // quadratic bezier's. We have the c_j's given, the q_j's can be + // calculated from QPi like this (sorry, mixed index notation, we + // use [1,n], formulas use [0,n-1]): + // + // q_0 = QP1 = P1 + // q_1 = 1/3 QP1 + 2/3 QP2 + // q_2 = 2/3 QP2 + 1/3 QP3 + // q_3 = QP3 = P4 + // + // We can drop case 0 and 3, since there the curves coincide + // (distance is zero) + + // calculate argument of max for j=1 and j=2 + const double fJ1x(P2x - 1.0 / 3.0 * QP1x - 2.0 / 3.0 * QP2x); + const double fJ1y(P2y - 1.0 / 3.0 * QP1y - 2.0 / 3.0 * QP2y); + const double fJ2x(P3x - 2.0 / 3.0 * QP2x - 1.0 / 3.0 * QP3x); + const double fJ2y(P3y - 2.0 / 3.0 * QP2y - 1.0 / 3.0 * QP3y); + + // stop if distance from cubic curve is guaranteed to be bounded by d + // Should denominator be 0: then P1P2 and P3P4 are parallel (P1P2^T R[90,P3P4] = 0.0), + // meaning that either we have a straight line or an inflexion point (see else block below) + if (0.0 != denominator + && ::std::max(fJ1x * fJ1x + fJ1y * fJ1y, fJ2x * fJ2x + fJ2y * fJ2y) < d2) + { + // requested resolution reached. + // Add end points to output file. + // order is preserved, since this is so to say depth first traversal. + Impl_addQuadBezier(rBits, rLastPoint, QP2x, QP2y, QP3x, QP3y); + } + else + { + // Maybe subdivide further + + // This is for robustness reasons, since the line intersection + // method below gets instable if the curve gets closer to a + // straight line. If the given cubic bezier does not deviate by + // more than d/4 from a straight line, either: + // - take the line (that's what we do here) + // - express the line by a quadratic bezier + + // Perform bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + // + // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)|| + // 0<=j<=n + // + // What is calculated here is an upper bound to the distance from + // a line through b_0 and b_3 (P1 and P4 in our notation) and the + // curve. We can drop 0 and n from the running indices, since the + // argument of max becomes zero for those cases. + const double fJ1x2(P2x - P1x - 1.0 / 3.0 * (P4x - P1x)); + const double fJ1y2(P2y - P1y - 1.0 / 3.0 * (P4y - P1y)); + const double fJ2x2(P3x - P1x - 2.0 / 3.0 * (P4x - P1x)); + const double fJ2y2(P3y - P1y - 2.0 / 3.0 * (P4y - P1y)); + + // stop if distance from line is guaranteed to be bounded by d/4 + if (::std::max(fJ1x2 * fJ1x2 + fJ1y2 * fJ1y2, fJ2x2 * fJ2x2 + fJ2y2 * fJ2y2) + < d2 / 16.0) + { + // do not subdivide further, add straight line instead + Impl_addStraightLine(rBits, rLastPoint, P4x, P4y); + } + else + { + // deCasteljau bezier arc, split at t=0.5 + // Foley/vanDam, p. 508 + const double L1x(P1x), L1y(P1y); + const double L2x((P1x + P2x) * 0.5), L2y((P1y + P2y) * 0.5); + const double Hx((P2x + P3x) * 0.5), Hy((P2y + P3y) * 0.5); + const double L3x((L2x + Hx) * 0.5), L3y((L2y + Hy) * 0.5); + const double R4x(P4x), R4y(P4y); + const double R3x((P3x + P4x) * 0.5), R3y((P3y + P4y) * 0.5); + const double R2x((Hx + R3x) * 0.5), R2y((Hy + R3y) * 0.5); + const double R1x((L3x + R2x) * 0.5), R1y((L3y + R2y) * 0.5); + const double L4x(R1x), L4y(R1y); + + // subdivide further + Impl_quadBezierApprox(rBits, rLastPoint, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, + L4y); + Impl_quadBezierApprox(rBits, rLastPoint, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, + R4y); + } + } + } +} + +Reference<XBreakIterator> const& Writer::Impl_GetBreakIterator() +{ + if (!mxBreakIterator.is()) + { + Reference<XComponentContext> xContext(::comphelper::getProcessComponentContext()); + mxBreakIterator = BreakIterator::create(xContext); + } + return mxBreakIterator; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/flash/swfwriter2.cxx b/filter/source/flash/swfwriter2.cxx new file mode 100644 index 000000000000..050bdadc179d --- /dev/null +++ b/filter/source/flash/swfwriter2.cxx @@ -0,0 +1,575 @@ +/* -*- 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 "swfwriter.hxx" +#include <vcl/virdev.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/debug.hxx> + +#include <math.h> + +using namespace ::swf; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; + +static sal_uInt16 getMaxBitsUnsigned(sal_uInt32 nValue) +{ + sal_uInt16 nBits = 0; + + while (nValue) + { + nBits++; + nValue >>= 1; + } + + return nBits; +} + +sal_uInt16 getMaxBitsSigned(sal_Int32 nValue) +{ + if (nValue < 0) + nValue *= -1; + + return getMaxBitsUnsigned(static_cast<sal_uInt32>(nValue)) + 1; +} + +BitStream::BitStream() +{ + mnBitPos = 8; + mnCurrentByte = 0; +} + +void BitStream::writeUB(sal_uInt32 nValue, sal_uInt16 nBits) +{ + while (nBits != 0) + { + mnCurrentByte |= nValue << (32 - nBits) >> (32 - mnBitPos); + + if (nBits > mnBitPos) + { + nBits = nBits - mnBitPos; + mnBitPos = 0; + } + else + { + mnBitPos = sal::static_int_cast<sal_uInt8>(mnBitPos - nBits); + nBits = 0; + } + + if (0 == mnBitPos) + pad(); + } +} + +void BitStream::writeSB(sal_Int32 nValue, sal_uInt16 nBits) +{ + writeUB(static_cast<sal_uInt32>(nValue), nBits); +} + +void BitStream::writeFB(sal_uInt32 nValue, sal_uInt16 nBits) { writeUB(nValue, nBits); } + +void BitStream::pad() +{ + if (8 != mnBitPos) + { + maData.push_back(mnCurrentByte); + mnCurrentByte = 0; + mnBitPos = 8; + } +} + +void BitStream::writeTo(SvStream& out) +{ + pad(); + + for (auto const& data : maData) + { + out.WriteUChar(data); + } +} + +sal_uInt32 BitStream::getOffset() const { return maData.size(); } + +Tag::Tag(sal_uInt8 nTagId) { mnTagId = nTagId; } + +void Tag::write(SvStream& out) +{ + sal_uInt32 nSz = TellEnd(); + Seek(STREAM_SEEK_TO_BEGIN); + + if (mnTagId != 0xff) + { + bool bLarge = nSz > 62; + + sal_uInt16 nCode = (mnTagId << 6) | (bLarge ? 0x3f : uInt16_(nSz)); + + out.WriteUChar(nCode); + out.WriteUChar(nCode >> 8); + + if (bLarge) + { + sal_uInt32 nTmp = nSz; + + out.WriteUChar(nTmp); + nTmp >>= 8; + out.WriteUChar(nTmp); + nTmp >>= 8; + out.WriteUChar(nTmp); + nTmp >>= 8; + out.WriteUChar(nTmp); + } + } + + out.WriteBytes(GetData(), nSz); +} +#if 0 + + +void Tag::addI32( sal_Int32 nValue ) +{ + addUI32( static_cast<sal_uInt32>( nValue ) ); +} +#endif + +void Tag::addUI32(sal_uInt32 nValue) { WriteUInt32(nValue); } +#if 0 + + +void Tag::addI16( sal_Int16 nValue ) +{ + addUI16( static_cast<sal_uInt16>( nValue ) ); +} +#endif + +void Tag::addUI16(sal_uInt16 nValue) +{ + WriteUChar(nValue); + WriteUChar(nValue >> 8); +} + +void Tag::addUI8(sal_uInt8 nValue) { WriteUChar(nValue); } + +void Tag::addBits(BitStream& rIn) { rIn.writeTo(*this); } + +void Tag::addRGBA(const Color& rColor) +{ + addUI8(rColor.GetRed()); + addUI8(rColor.GetGreen()); + addUI8(rColor.GetBlue()); + addUI8(rColor.GetAlpha()); +} + +void Tag::addRGB(const Color& rColor) +{ + addUI8(rColor.GetRed()); + addUI8(rColor.GetGreen()); + addUI8(rColor.GetBlue()); +} + +void Tag::addRect(const tools::Rectangle& rRect) { writeRect(*this, rRect); } + +void Tag::writeRect(SvStream& rOut, const tools::Rectangle& rRect) +{ + BitStream aBits; + + sal_Int32 minX, minY, maxX, maxY; + + if (rRect.Left() < rRect.Right()) + { + minX = rRect.Left(); + maxX = rRect.Right(); + } + else + { + maxX = rRect.Left(); + minX = rRect.Right(); + } + + if (rRect.Top() < rRect.Bottom()) + { + minY = rRect.Top(); + maxY = rRect.Bottom(); + } + else + { + maxY = rRect.Top(); + minY = rRect.Bottom(); + } + + // AS: Figure out the maximum number of bits required to represent any of the + // rectangle coordinates. Since minX or minY could be negative, they could + // actually require more bits than maxX or maxY. + // AS: Christian, can they be negative, or is that a wasted check? + // CL: I think so, f.e. for shapes that have the top and/or left edge outside + // the page origin + sal_uInt8 nBits1 + = sal::static_int_cast<sal_uInt8>(std::max(getMaxBitsSigned(minX), getMaxBitsSigned(minY))); + sal_uInt8 nBits2 + = sal::static_int_cast<sal_uInt8>(std::max(getMaxBitsSigned(maxX), getMaxBitsSigned(maxY))); + sal_uInt8 nBitsMax = std::max(nBits1, nBits2); + + aBits.writeUB(nBitsMax, 5); + aBits.writeSB(minX, nBitsMax); + aBits.writeSB(maxX, nBitsMax); + aBits.writeSB(minY, nBitsMax); + aBits.writeSB(maxY, nBitsMax); + + aBits.writeTo(rOut); +} + +void Tag::addMatrix(const ::basegfx::B2DHomMatrix& rMatrix) // #i73264# +{ + writeMatrix(*this, rMatrix); +} + +void Tag::writeMatrix(SvStream& rOut, const ::basegfx::B2DHomMatrix& rMatrix) // #i73264# +{ + BitStream aBits; + + const bool bHasScale = rMatrix.get(0, 0) != 1.0 || rMatrix.get(1, 1) != 1.0; + + aBits.writeUB(int(bHasScale), 1); + + if (bHasScale) + { + sal_uInt8 nScaleBits = 31; + + aBits.writeUB(nScaleBits, 5); + aBits.writeFB(getFixed(rMatrix.get(0, 0)), nScaleBits); // Scale X + aBits.writeFB(getFixed(rMatrix.get(1, 1)), nScaleBits); // Scale Y + } + + const bool bHasRotate = rMatrix.get(0, 1) != 0.0 || rMatrix.get(1, 0) != 0.0; + + aBits.writeUB(int(bHasRotate), 1); + + if (bHasRotate) + { + sal_uInt8 nRotateBits = 31; + + aBits.writeUB(nRotateBits, 5); + aBits.writeFB(getFixed(rMatrix.get(0, 1)), nRotateBits); // RotateSkew0 + aBits.writeFB(getFixed(rMatrix.get(1, 0)), nRotateBits); // RotateSkew1 + } + + sal_uInt8 nTranslateBits = 16; + + aBits.writeUB(nTranslateBits, 5); + aBits.writeSB(static_cast<sal_Int16>(rMatrix.get(0, 2)), nTranslateBits); // Translate X + aBits.writeSB(static_cast<sal_Int16>(rMatrix.get(1, 2)), nTranslateBits); // Translate Y + + aBits.writeTo(rOut); +} + +void Tag::addStream(SvStream& rIn) { (*this).WriteStream(rIn); } + +Sprite::Sprite(sal_uInt16 nId) + : mnId(nId) + , mnFrames(0) +{ +} + +Sprite::~Sprite() {} + +void Sprite::write(SvStream& out) +{ + SvMemoryStream aTmp; + for (auto const& tag : maTags) + tag->write(aTmp); + + if (!mnFrames) + mnFrames = 1; + + aTmp.Seek(0); + + Tag aTag(TAG_DEFINESPRITE); + aTag.addUI16(mnId); + aTag.addUI16(uInt16_(mnFrames)); + aTag.addStream(aTmp); + aTag.write(out); +} + +void Sprite::addTag(std::unique_ptr<Tag> pNewTag) +{ + if (pNewTag->getTagId() == TAG_SHOWFRAME) + mnFrames++; + + maTags.push_back(std::move(pNewTag)); +} + +sal_uInt32 swf::getFixed(double fValue) +{ + sal_Int16 nUpper = static_cast<sal_Int16>(floor(fValue)); + sal_uInt16 nLower = static_cast<sal_uInt16>((fValue - floor(fValue)) * 0x10000); + + sal_uInt32 temp = static_cast<sal_Int32>(nUpper) << 16; + temp |= nLower; + + return temp; +} + +/** constructs a new flash font for the given VCL Font */ +FlashFont::FlashFont(const vcl::Font& rFont, sal_uInt16 nId) + : maFont(rFont) + , mnNextIndex(0) + , mnId(nId) +{ +} + +FlashFont::~FlashFont() {} + +/** gets the glyph id for the given character. The glyphs are created on demand */ +sal_uInt16 FlashFont::getGlyph(sal_uInt16 nChar, VirtualDevice* pVDev) +{ + // see if we already created a glyph for this character + std::map<sal_uInt16, sal_uInt16>::iterator aIter(maGlyphIndex.find(nChar)); + if (aIter != maGlyphIndex.end()) + { + return aIter->second; + } + + // if not, we create one now + + maGlyphIndex[nChar] = mnNextIndex; + + vcl::Font aOldFont(pVDev->GetFont()); + vcl::Font aNewFont(aOldFont); + aNewFont.SetAlignment(ALIGN_BASELINE); + pVDev->SetFont(aNewFont); + aOldFont.SetOrientation(0_deg10); + + // let the virtual device convert the character to polygons + tools::PolyPolygon aPolyPoly; + pVDev->GetTextOutline(aPolyPoly, OUString(sal_Unicode(nChar))); + + maGlyphOffsets.push_back(uInt16_(maGlyphData.getOffset())); + + // Number of fill and line index bits set to 1 + maGlyphData.writeUB(0x11, 8); + + const sal_uInt16 nCount = aPolyPoly.Count(); + sal_uInt16 i, n; + for (i = 0; i < nCount; i++) + { + tools::Polygon& rPoly = aPolyPoly[i]; + + const sal_uInt16 nSize = rPoly.GetSize(); + if (nSize) + { + // convert polygon to flash EM_SQUARE (1024x1024) + for (n = 0; n < nSize; n++) + { + Point aPoint(rPoly[n]); + aPoint.setX(static_cast<long>((double(aPoint.X()) * 1024.0) + / double(aOldFont.GetFontHeight()))); + aPoint.setY(static_cast<long>((double(aPoint.Y()) * 1024.0) + / double(aOldFont.GetFontHeight()))); + rPoly[n] = aPoint; + } + Writer::Impl_addPolygon(maGlyphData, rPoly, true); + } + } + Writer::Impl_addEndShapeRecord(maGlyphData); + + maGlyphData.pad(); + + pVDev->SetFont(aOldFont); + + return mnNextIndex++; +} + +void FlashFont::write(SvStream& out) +{ + Tag aTag(TAG_DEFINEFONT); + + aTag.addUI16(mnId); + + sal_uInt16 nGlyphs = uInt16_(maGlyphOffsets.size()); + sal_uInt16 nOffset = nGlyphs * sizeof(sal_uInt16); + + for (auto const& glyphOffset : maGlyphOffsets) + aTag.addUI16(nOffset + glyphOffset); + + aTag.addBits(maGlyphData); + + aTag.write(out); +} + +/** this c'tor creates a solid fill style */ +FillStyle::FillStyle(const Color& rSolidColor) + : meType(solid) + , mnBitmapId(0) + , maColor(rSolidColor) +{ +} + +/** this c'tor creates a tiled or clipped bitmap fill style */ +FillStyle::FillStyle(sal_uInt16 nBitmapId, bool bClipped, + const ::basegfx::B2DHomMatrix& rMatrix) // #i73264# + : meType(bClipped ? clipped_bitmap : tiled_bitmap) + , maMatrix(rMatrix) + , mnBitmapId(nBitmapId) +{ +} + +static FillStyle::FillStyleType Impl_getFillStyleType(const Gradient& rGradient) +{ + switch (rGradient.GetStyle()) + { + case css::awt::GradientStyle::GradientStyle_ELLIPTICAL: + case css::awt::GradientStyle::GradientStyle_RADIAL: + return FillStyle::radial_gradient; + case css::awt::GradientStyle::GradientStyle_AXIAL: + case css::awt::GradientStyle::GradientStyle_SQUARE: + case css::awt::GradientStyle::GradientStyle_RECT: + case css::awt::GradientStyle::GradientStyle_LINEAR: + default: + return FillStyle::linear_gradient; + } +} + +/** this c'tor creates a linear or radial gradient fill style */ +FillStyle::FillStyle(const tools::Rectangle& rBoundRect, const Gradient& rGradient) + : meType(Impl_getFillStyleType(rGradient)) + , mnBitmapId(0) + , maGradient(rGradient) + , maBoundRect(rBoundRect) +{ +} + +void FillStyle::addTo(Tag* pTag) const +{ + pTag->addUI8(sal::static_int_cast<sal_uInt8>(meType)); + switch (meType) + { + case solid: + pTag->addRGBA(maColor); + break; + case linear_gradient: + case radial_gradient: + Impl_addGradient(pTag); + break; + case tiled_bitmap: + case clipped_bitmap: + pTag->addUI16(mnBitmapId); + pTag->addMatrix(maMatrix); + break; + } +} + +namespace +{ +struct GradRecord +{ + sal_uInt8 mnRatio; + Color maColor; + + GradRecord(sal_uInt8 nRatio, const Color& rColor) + : mnRatio(nRatio) + , maColor(rColor) + { + } +}; +} + +// TODO: better emulation of our gradients +void FillStyle::Impl_addGradient(Tag* pTag) const +{ + std::vector<struct GradRecord> aGradientRecords; + basegfx::B2DHomMatrix m( + basegfx::utils::createRotateB2DHomMatrix(toRadians(maGradient.GetAngle() - 900_deg10))); + + switch (maGradient.GetStyle()) + { + case css::awt::GradientStyle::GradientStyle_ELLIPTICAL: + case css::awt::GradientStyle::GradientStyle_RADIAL: + { + aGradientRecords.emplace_back(0x00, maGradient.GetEndColor()); + aGradientRecords.emplace_back(0xff, maGradient.GetStartColor()); + + double tx = (maGradient.GetOfsX() * 32768.0) / 100.0; + double ty = (maGradient.GetOfsY() * 32768.0) / 100.0; + double scalex = static_cast<double>(maBoundRect.GetWidth()) / 32768.0; + double scaley = static_cast<double>(maBoundRect.GetHeight()) / 32768.0; + + m.scale(1.2, 1.2); + + if (scalex > scaley) + { + double scale_move = scaley / scalex; + + m.translate(tx, scale_move * ty); + + m.scale(scalex, scalex); + } + else + { + double scale_move = scalex / scaley; + + m.translate(scale_move * tx, ty); + + m.scale(scaley, scaley); + } + } + break; + case css::awt::GradientStyle::GradientStyle_AXIAL: + { + aGradientRecords.emplace_back(0x00, maGradient.GetEndColor()); + aGradientRecords.emplace_back(0x80, maGradient.GetStartColor()); + aGradientRecords.emplace_back(0xff, maGradient.GetEndColor()); + double scalex = static_cast<double>(maBoundRect.GetWidth()) / 32768.0; + double scaley = static_cast<double>(maBoundRect.GetHeight()) / 32768.0; + m.translate(32768.0 / 2.0, 32768.0 / 2.0); + m.scale(scalex, scaley); + } + break; + case css::awt::GradientStyle::GradientStyle_SQUARE: + case css::awt::GradientStyle::GradientStyle_RECT: + case css::awt::GradientStyle::GradientStyle_LINEAR: + { + aGradientRecords.emplace_back(0x00, maGradient.GetStartColor()); + aGradientRecords.emplace_back(0xff, maGradient.GetEndColor()); + double scalex = static_cast<double>(maBoundRect.GetWidth()) / 32768.0; + double scaley = static_cast<double>(maBoundRect.GetHeight()) / 32768.0; + + m.scale(scalex, scaley); + + m.translate(maBoundRect.GetWidth() / 2.0, maBoundRect.GetHeight() / 2.0); + } + break; + case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE: + break; + } + + m.translate(maBoundRect.Left(), maBoundRect.Top()); + + pTag->addMatrix(m); + + DBG_ASSERT(aGradientRecords.size() < 8, "Illegal FlashGradient!"); + + pTag->addUI8(static_cast<sal_uInt8>(aGradientRecords.size())); + + for (auto const& gradientRecord : aGradientRecords) + { + pTag->addUI8(gradientRecord.mnRatio); + pTag->addRGBA(gradientRecord.maColor); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/uiconfig/ui/impswfdialog.ui b/filter/uiconfig/ui/impswfdialog.ui new file mode 100644 index 000000000000..6617cd7128f2 --- /dev/null +++ b/filter/uiconfig/ui/impswfdialog.ui @@ -0,0 +1,277 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface domain="flt"> + <requires lib="gtk+" version="3.18"/> + <object class="GtkAdjustment" id="adjustmentquality"> + <property name="lower">1</property> + <property name="upper">100</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkDialog" id="ImpSWFDialog"> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="title" translatable="yes" context="impswfdialog|ImpSWFDialog">Flash (SWF) Options</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="default_width">0</property> + <property name="default_height">0</property> + <property name="type_hint">dialog</property> + <child> + <placeholder/> + </child> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="margin_top">5</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="ok"> + <property name="label">gtk-ok</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="help"> + <property name="label">gtk-help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="secondary">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="impswfdialog|label1">1: min. quality +100: max. quality</property> + <property name="use_underline">True</property> + <property name="justify">fill</property> + <property name="mnemonic_widget">quality</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="quality"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <property name="activates_default">True</property> + <property name="xalign">0.5</property> + <property name="input_purpose">digits</property> + <property name="adjustment">adjustmentquality</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="exportall"> + <property name="label" translatable="yes" context="impswfdialog|exportall">Export _all slides (uncheck to export current slide)</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkCheckButton" id="exportmultiplefiles"> + <property name="label" translatable="yes" context="impswfdialog|exportmultiplefiles">Export as _multiple files</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">20</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkCheckButton" id="exportbackgrounds"> + <property name="label" translatable="yes" context="impswfdialog|exportbackgrounds">Export _backgrounds</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="exportbackgroundobjects"> + <property name="label" translatable="yes" context="impswfdialog|exportbackgroundobjects">Export back_ground objects</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="exportslidecontents"> + <property name="label" translatable="yes" context="impswfdialog|exportslidecontents">Export _slide contents</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="exportsound"> + <property name="label" translatable="yes" context="impswfdialog|exportsound">Export _Verilogix Slide Annotations</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="exportoleasjpeg"> + <property name="label" translatable="yes" context="impswfdialog|exportoleasjpeg">Export OLE objects as _JPEG images</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">ok</action-widget> + <action-widget response="-6">cancel</action-widget> + <action-widget response="-11">help</action-widget> + </action-widgets> + </object> +</interface> diff --git a/include/sal/log-areas.dox b/include/sal/log-areas.dox index 2ee98e7985b7..29ee4ac8253f 100644 --- a/include/sal/log-areas.dox +++ b/include/sal/log-areas.dox @@ -228,6 +228,7 @@ certain functionality. @li @c filter.config @li @c filter.eps +@li @c filter.flash @li @c filter.hwp - Hangul word processor import @li @c filter.icgm @li @c filter.ms - escher import/export diff --git a/include/vcl/gdimetafiletools.hxx b/include/vcl/gdimetafiletools.hxx index 576ba41f372d..0d04759da428 100644 --- a/include/vcl/gdimetafiletools.hxx +++ b/include/vcl/gdimetafiletools.hxx @@ -34,11 +34,11 @@ class GDIMetaFile; // reimplement these im/exports to use primitives and not metafiles as base // information. -void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource); +VCL_DLLPUBLIC void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource); // Allow to check if a Metafile contains clipping or not -bool usesClipActions(const GDIMetaFile& rSource); +VCL_DLLPUBLIC bool usesClipActions(const GDIMetaFile& rSource); // hook to access metafile members in classes of modules above vcl. Currently // used in MetafilePrimitive2D to be able to access the local Metafile member diff --git a/include/vcl/gradient.hxx b/include/vcl/gradient.hxx index fdb8df0b9a4c..b03ad1f3c675 100644 --- a/include/vcl/gradient.hxx +++ b/include/vcl/gradient.hxx @@ -81,6 +81,7 @@ public: void GetBoundRect( const tools::Rectangle& rRect, tools::Rectangle &rBoundRect, Point& rCenter ) const; void AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile); + void AddGradientActionsConst(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const; Gradient& operator=( const Gradient& rGradient ); Gradient& operator=( Gradient&& rGradient ); diff --git a/scp2/source/graphicfilter/module_graphicfilter.ulf b/scp2/source/graphicfilter/module_graphicfilter.ulf index 6f4c286325ff..0598718d821a 100644 --- a/scp2/source/graphicfilter/module_graphicfilter.ulf +++ b/scp2/source/graphicfilter/module_graphicfilter.ulf @@ -99,3 +99,9 @@ en-US = "SVG Export" [STR_DESC_MODULE_OPTIONAL_GRFFLT_SVG] en-US = "SVG Export Filter" + +[STR_NAME_MODULE_OPTIONAL_GRFFLT_FLASH] +en-US = "Macromedia Flash (SWF)" + +[STR_DESC_MODULE_OPTIONAL_GRFFLT_FLASH] +en-US = "Macromedia Flash (SWF) Export Filter" diff --git a/sfx2/source/dialog/filtergrouping.cxx b/sfx2/source/dialog/filtergrouping.cxx index b6232e301e43..6a4718053441 100644 --- a/sfx2/source/dialog/filtergrouping.cxx +++ b/sfx2/source/dialog/filtergrouping.cxx @@ -969,6 +969,7 @@ namespace sfx2 sal_Int32 nHTMLIndex = -1; sal_Int32 nXHTMLIndex = -1; sal_Int32 nPDFIndex = -1; + sal_Int32 nFlashIndex = -1; OUString sUIName; OUString sExtensions; std::vector< ExportFilter > aImportantFilterGroup; @@ -1008,6 +1009,18 @@ namespace sfx2 aImportantFilterGroup.insert( aIter, aExportFilter ); nPDFIndex = 0; } + else if ( nFlashIndex == -1 && sTypeName == "graphic_SWF" ) + { + std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin(); + if ( nHTMLIndex != -1 ) + ++aIter; + if ( nXHTMLIndex != -1 ) + ++aIter; + if ( nPDFIndex != -1 ) + ++aIter; + aImportantFilterGroup.insert( aIter, aExportFilter ); + nFlashIndex = 0; + } else aFilterGroup.push_back( aExportFilter ); } diff --git a/solenv/inc/mime.types b/solenv/inc/mime.types index 9248e092f88f..e5afd9376a93 100644 --- a/solenv/inc/mime.types +++ b/solenv/inc/mime.types @@ -100,6 +100,7 @@ application/x-rad rad application/x-rpm rpm spm application/x-sh sh application/x-shar shar +application/x-shockwave-flash swf application/x-stuffit sit application/x-sv4cpio sv4cpio application/x-sv4crc sv4crc @@ -178,6 +179,8 @@ audio/vnd.rn-realaudio ra application/smil smi smil text/vnd.rn-realtext rt video/vnd.rn-realvideo rv +image/vnd.rn-realflash rf swf +application/x-shockwave-flash2-preview rf swf application/sdp sdp application/x-sdp sdp application/vnd.rn-realmedia rm diff --git a/sysui/desktop/apparmor/program.soffice.bin b/sysui/desktop/apparmor/program.soffice.bin index 42053db2abef..76d567eb7e8c 100644 --- a/sysui/desktop/apparmor/program.soffice.bin +++ b/sysui/desktop/apparmor/program.soffice.bin @@ -65,6 +65,8 @@ #Impress/Draw @{libreoffice_ext} += [pP][pP][tTsS]{,x,X} @{libreoffice_ext} += [pP][oO][tT]{,m,M} +#Flash +@{libreoffice_ext} += [sS][wW][fF] #Photoshop @{libreoffice_ext} += [pP][sS][dD] diff --git a/vcl/source/gdi/gradient.cxx b/vcl/source/gdi/gradient.cxx index 75a53a2a93a7..7de2f2b3b2f3 100644 --- a/vcl/source/gdi/gradient.cxx +++ b/vcl/source/gdi/gradient.cxx @@ -332,6 +332,37 @@ void Gradient::AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rM rMetaFile.AddAction(new MetaPopAction()); } +void Gradient::AddGradientActionsConst(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const +{ + tools::Rectangle aRect(rRect); + aRect.Normalize(); + + // do nothing if the rectangle is empty + if (aRect.IsEmpty()) + return; + + rMetaFile.AddAction(new MetaPushAction(vcl::PushFlags::ALL)); + rMetaFile.AddAction(new MetaISectRectClipRegionAction(aRect)); + rMetaFile.AddAction(new MetaLineColorAction(Color(), false)); + + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft(-1); + aRect.AdjustTop(-1); + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + + // we can't mutate the stepcount as that would lose us our const qualifier, so we need it to already be set... + assert(GetSteps()); + + if (GetStyle() == css::awt::GradientStyle_LINEAR || GetStyle() == css::awt::GradientStyle_AXIAL) + DrawLinearGradientToMetafile(aRect, rMetaFile); + else + DrawComplexGradientToMetafile(aRect, rMetaFile); + + rMetaFile.AddAction(new MetaPopAction()); +} + tools::Long Gradient::GetMetafileSteps(tools::Rectangle const& rRect) const { // calculate step count |