/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vcl { namespace { struct CreateNamedDest { OUString maDestName; MapMode maParaMapMode; PDFWriter::DestAreaType mnParaDestAreaType; tools::Rectangle maParaRect; sal_Int32 mnPage; }; struct CreateDest { MapMode maParaMapMode; PDFWriter::DestAreaType mnParaDestAreaType; tools::Rectangle maParaRect; sal_Int32 mnPage; }; struct CreateControlLink { sal_Int32 mnControlId; }; struct CreateLink { OUString maAltText; MapMode maParaMapMode; tools::Rectangle maParaRect; sal_Int32 mnPage; }; struct CreateScreen { OUString maAltText; OUString maMimeType; MapMode maParaMapMode; tools::Rectangle maParaRect; sal_Int32 mnPage; }; struct SetLinkDest { sal_Int32 mnLinkId; sal_Int32 mnDestId; }; struct SetLinkURL { OUString maLinkURL; sal_Int32 mnLinkId; }; struct SetScreenURL { OUString maScreenURL; sal_Int32 mnScreenId; }; struct SetScreenStream { OUString maScreenStream; sal_Int32 mnScreenId; }; struct RegisterDest { sal_Int32 mnDestId; }; struct CreateOutlineItem { OUString maText; sal_Int32 mnParent; sal_Int32 mnDestID; }; struct CreateNote { MapMode maParaMapMode; PDFNote maParaPDFNote; tools::Rectangle maParaRect; sal_Int32 mnPage; }; struct SetPageTransition { PDFWriter::PageTransition maParaPageTransition; sal_uInt32 mnMilliSec; sal_Int32 mnPage; }; struct EnsureStructureElement { sal_Int32 mnId; }; struct InitStructureElement { PDFWriter::StructElement mParaStructElement; OUString maAlias; sal_Int32 mnId; }; struct BeginStructureElement { sal_Int32 mnId; }; struct EndStructureElement{}; struct SetCurrentStructureElement { sal_Int32 mnStructId; }; struct SetStructureAttribute { PDFWriter::StructAttribute mParaStructAttribute; PDFWriter::StructAttributeValue mParaStructAttributeValue; }; struct SetStructureAttributeNumerical { PDFWriter::StructAttribute mParaStructAttribute; sal_Int32 mnId; }; struct SetStructureBoundingBox { tools::Rectangle mRect; }; struct SetStructureAnnotIds { ::std::vector annotIds; }; struct SetActualText { OUString maText; }; struct SetAlternateText { OUString maText; }; struct CreateControl { std::shared_ptr< PDFWriter::AnyWidget > mxControl; }; struct BeginGroup {}; struct EndGroupGfxLink { Graphic maGraphic; tools::Rectangle maOutputRect, maVisibleOutputRect; sal_Int32 mnTransparency; }; typedef std::variant GlobalActionData; typedef std::variant PageActionData; struct PDFExtOutDevDataSyncPage { sal_uInt32 nIdx; PageActionData eAct; }; struct PDFLinkDestination { tools::Rectangle mRect; MapMode mMapMode; sal_Int32 mPageNr; PDFWriter::DestAreaType mAreaType; }; } struct GlobalSyncData { std::deque< GlobalActionData > mActions; ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations; sal_Int32 GetMappedId(sal_Int32 nLinkId); /** the way this appears to work: (only) everything that increments mCurId at recording time must put an item into mParaIds at playback time, so that the mCurId becomes the eventual index into mParaIds. */ sal_Int32 mCurId; std::vector< sal_Int32 > mParaIds; std::map mSEMap; sal_Int32 mCurrentStructElement; std::vector< sal_Int32 > mStructParents; GlobalSyncData() : mCurId ( 0 ), mCurrentStructElement( 0 ) { mStructParents.push_back(0); // because PDFWriterImpl has a dummy root } void PlayGlobalActions( PDFWriter& rWriter ); }; sal_Int32 GlobalSyncData::GetMappedId(sal_Int32 nLinkId) { /* negative values are intentionally passed as invalid IDs * e.g. to create a new top level outline item */ if( nLinkId >= 0 ) { if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() ) nLinkId = mParaIds[ nLinkId ]; else nLinkId = -1; SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" ); } return nLinkId; } void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) { for (auto const& action : mActions) { if (std::holds_alternative(action)) { //i56629 const vcl::CreateNamedDest& rCreateNamedDest = std::get(action); rWriter.Push( PushFlags::MAPMODE ); rWriter.SetMapMode( rCreateNamedDest.maParaMapMode ); mParaIds.push_back( rWriter.CreateNamedDest( rCreateNamedDest.maDestName, rCreateNamedDest.maParaRect, rCreateNamedDest.mnPage, rCreateNamedDest.mnParaDestAreaType ) ); rWriter.Pop(); } else if (std::holds_alternative(action)) { const vcl::CreateDest& rCreateDest = std::get(action); rWriter.Push( PushFlags::MAPMODE ); rWriter.SetMapMode( rCreateDest.maParaMapMode ); mParaIds.push_back( rWriter.CreateDest( rCreateDest.maParaRect, rCreateDest.mnPage, rCreateDest.mnParaDestAreaType ) ); rWriter.Pop(); } else if (std::holds_alternative(action)) { const vcl::CreateControlLink& rCreateControlLink = std::get(action); // tdf#157397: this must be called *in order* with CreateLink etc. rWriter.SetLinkPropertyID(rCreateControlLink.mnControlId, sal_Int32(mParaIds.size())); mParaIds.push_back(rCreateControlLink.mnControlId); } else if (std::holds_alternative(action)) { const vcl::CreateLink& rCreateLink = std::get(action); rWriter.Push( PushFlags::MAPMODE ); rWriter.SetMapMode( rCreateLink.maParaMapMode ); mParaIds.push_back( rWriter.CreateLink(rCreateLink.maParaRect, rCreateLink.mnPage, rCreateLink.maAltText) ); // resolve LinkAnnotation structural attribute rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) ); rWriter.Pop(); } else if (std::holds_alternative(action)) { const vcl::CreateScreen& rCreateScreen = std::get(action); rWriter.Push(PushFlags::MAPMODE); rWriter.SetMapMode(rCreateScreen.maParaMapMode); mParaIds.push_back(rWriter.CreateScreen(rCreateScreen.maParaRect, rCreateScreen.mnPage, rCreateScreen.maAltText, rCreateScreen.maMimeType)); // resolve AnnotIds structural attribute rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1)); rWriter.Pop(); } else if (std::holds_alternative(action)) { const vcl::SetLinkDest& rSetLinkDest = std::get(action); sal_Int32 nLinkId = GetMappedId(rSetLinkDest.mnLinkId); sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId); rWriter.SetLinkDest( nLinkId, nDestId ); } else if (std::holds_alternative(action)) { const vcl::SetLinkURL& rSetLinkURL = std::get(action); sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId); rWriter.SetLinkURL( nLinkId, rSetLinkURL.maLinkURL ); } else if (std::holds_alternative(action)) { const vcl::SetScreenURL& rSetScreenURL = std::get(action); sal_Int32 nScreenId = GetMappedId(rSetScreenURL.mnScreenId); rWriter.SetScreenURL(nScreenId, rSetScreenURL.maScreenURL); } else if (std::holds_alternative(action)) { const vcl::SetScreenStream& rSetScreenStream = std::get(action); sal_Int32 nScreenId = GetMappedId(rSetScreenStream.mnScreenId); rWriter.SetScreenStream(nScreenId, rSetScreenStream.maScreenStream); } else if (std::holds_alternative(action)) { const vcl::RegisterDest& rRegisterDest = std::get(action); const sal_Int32 nDestId = rRegisterDest.mnDestId; OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(), "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" ); PDFLinkDestination& rDest = mFutureDestinations[ nDestId ]; rWriter.Push( PushFlags::MAPMODE ); rWriter.SetMapMode( rDest.mMapMode ); mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) ); rWriter.Pop(); } else if (std::holds_alternative(action)) { const vcl::CreateOutlineItem& rCreateOutlineItem = std::get(action); sal_Int32 nParent = GetMappedId(rCreateOutlineItem.mnParent); sal_Int32 nLinkId = GetMappedId(rCreateOutlineItem.mnDestID); mParaIds.push_back( rWriter.CreateOutlineItem( nParent, rCreateOutlineItem.maText, nLinkId ) ); } else if (std::holds_alternative(action)) { const vcl::CreateNote& rCreateNote = std::get(action); rWriter.Push( PushFlags::MAPMODE ); rWriter.SetMapMode( rCreateNote.maParaMapMode ); rWriter.CreateNote( rCreateNote.maParaRect, rCreateNote.maParaPDFNote, rCreateNote.mnPage ); } else if (std::holds_alternative(action)) { const vcl::SetPageTransition& rSetPageTransition = std::get(action); rWriter.SetPageTransition( rSetPageTransition.maParaPageTransition, rSetPageTransition.mnMilliSec, rSetPageTransition.mnPage ); } } } struct PageSyncData { std::deque< PDFExtOutDevDataSyncPage > mActions; Graphic mCurrentGraphic; GlobalSyncData* mpGlobalData; bool mbGroupIgnoreGDIMtfActions; explicit PageSyncData( GlobalSyncData* pGlobal ) : mbGroupIgnoreGDIMtfActions ( false ) { mpGlobalData = pGlobal; } void PushAction( const OutputDevice& rOutDev, PageActionData eAct ); bool PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData ); }; void PageSyncData::PushAction( const OutputDevice& rOutDev, PageActionData eAct ) { GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile(); SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" ); PDFExtOutDevDataSyncPage aSync; aSync.eAct = std::move(eAct); if ( pMtf ) aSync.nIdx = pMtf->GetActionSize(); else aSync.nIdx = 0x7fffffff; // sync not possible mActions.emplace_back( std::move(aSync) ); } bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData ) { bool bRet = false; if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) ) { bRet = true; PDFExtOutDevDataSyncPage aDataSync = std::move(mActions.front()); mActions.pop_front(); if (std::holds_alternative(aDataSync.eAct)) { #ifndef NDEBUG const vcl::EnsureStructureElement& rEnsureStructureElement = std::get(aDataSync.eAct); sal_Int32 const id = #endif rWriter.EnsureStructureElement(); assert(id == -1 || id == rEnsureStructureElement.mnId); // identity mapping } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::InitStructureElement& rInitStructureElement = std::get(aDataSync.eAct); rWriter.InitStructureElement(rInitStructureElement.mnId, rInitStructureElement.mParaStructElement, rInitStructureElement.maAlias); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::BeginStructureElement& rBeginStructureElement = std::get(aDataSync.eAct); rWriter.BeginStructureElement(rBeginStructureElement.mnId); } else if (std::holds_alternative(aDataSync.eAct)) { rWriter.EndStructureElement(); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetCurrentStructureElement& rSetCurrentStructureElement = std::get(aDataSync.eAct); rWriter.SetCurrentStructureElement(rSetCurrentStructureElement.mnStructId); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetStructureAttribute& rSetStructureAttribute = std::get(aDataSync.eAct); rWriter.SetStructureAttribute( rSetStructureAttribute.mParaStructAttribute, rSetStructureAttribute.mParaStructAttributeValue ); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetStructureAttributeNumerical& rSetStructureAttributeNumerical = std::get(aDataSync.eAct); rWriter.SetStructureAttributeNumerical( rSetStructureAttributeNumerical.mParaStructAttribute, rSetStructureAttributeNumerical.mnId ); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetStructureBoundingBox& rSetStructureBoundingBox = std::get(aDataSync.eAct); rWriter.SetStructureBoundingBox( rSetStructureBoundingBox.mRect ); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetStructureAnnotIds& rSetStructureAnnotIds = std::get(aDataSync.eAct); rWriter.SetStructureAnnotIds(rSetStructureAnnotIds.annotIds); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetActualText& rSetActualText = std::get(aDataSync.eAct); rWriter.SetActualText( rSetActualText.maText ); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::SetAlternateText& rSetAlternateText = std::get(aDataSync.eAct); rWriter.SetAlternateText( rSetAlternateText.maText ); } else if (std::holds_alternative(aDataSync.eAct)) { const vcl::CreateControl& rCreateControl = std::get(aDataSync.eAct); std::shared_ptr< PDFWriter::AnyWidget > pControl( rCreateControl.mxControl ); SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" ); if ( pControl ) { sal_Int32 const n = rWriter.CreateControl(*pControl); // resolve AnnotIds structural attribute ::std::vector const annotIds{ sal_Int32(mpGlobalData->mCurId) }; rWriter.SetStructureAnnotIds(annotIds); // tdf#157397: this must be called *in order* with CreateLink etc. mpGlobalData->mActions.push_back(CreateControlLink{n}); mpGlobalData->mCurId++; } } else if (std::holds_alternative(aDataSync.eAct)) { /* first determining if this BeginGroup is starting a GfxLink, by searching for an EndGroup or an EndGroupGfxLink */ mbGroupIgnoreGDIMtfActions = false; auto itStartingGfxLink = std::find_if(mActions.begin(), mActions.end(), [](const PDFExtOutDevDataSyncPage& rAction) { return std::holds_alternative(rAction.eAct); }); if ( itStartingGfxLink != mActions.end() ) { EndGroupGfxLink& rEndGroup = std::get(itStartingGfxLink->eAct); Graphic& rGraphic = rEndGroup.maGraphic; if ( rGraphic.IsGfxLink() ) { GfxLinkType eType = rGraphic.GetGfxLink().GetType(); if ( eType == GfxLinkType::NativeJpg ) { mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect); if ( !mbGroupIgnoreGDIMtfActions ) mCurrentGraphic = rGraphic; } else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf ) { if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect) ) mCurrentGraphic = rGraphic; } } } } else if (std::holds_alternative(aDataSync.eAct)) { EndGroupGfxLink& rEndGroup = std::get(aDataSync.eAct); tools::Rectangle aOutputRect, aVisibleOutputRect; Graphic aGraphic( rEndGroup.maGraphic ); sal_Int32 nTransparency = rEndGroup.mnTransparency; aOutputRect = rEndGroup.maOutputRect; aVisibleOutputRect = rEndGroup.maVisibleOutputRect; if ( mbGroupIgnoreGDIMtfActions ) { bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty(); GfxLink aGfxLink( aGraphic.GetGfxLink() ); if ( aGfxLink.GetType() == GfxLinkType::NativeJpg ) { if ( bClippingNeeded ) { rWriter.Push(); basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect( vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) ); rWriter.SetClipRegion( aRect); } AlphaMask aAlphaMask; if (nTransparency) { aAlphaMask = AlphaMask(aGraphic.GetSizePixel()); aAlphaMask.Erase(nTransparency); } SvMemoryStream aTmp; const sal_uInt8* pData = aGfxLink.GetData(); sal_uInt32 nBytes = aGfxLink.GetDataSize(); if( pData && nBytes ) { aTmp.WriteBytes( pData, nBytes ); // Look up the output rectangle from the previous // bitmap scale action if possible. This has the // correct position and size for images with a // custom translation (Writer header) or scaling // (Impress notes page). if (rCurGDIMtfAction > 0) { const MetaAction* pAction = rMtf.GetAction(rCurGDIMtfAction - 1); if (pAction && pAction->GetType() == MetaActionType::BMPSCALE) { const MetaBmpScaleAction* pA = static_cast(pAction); aOutputRect.SetPos(pA->GetPoint()); aOutputRect.SetSize(pA->GetSize()); } } auto ePixelFormat = aGraphic.GetBitmapEx().getPixelFormat(); rWriter.DrawJPGBitmap(aTmp, ePixelFormat > vcl::PixelFormat::N8_BPP, aGraphic.GetSizePixel(), aOutputRect, aAlphaMask, aGraphic); } if ( bClippingNeeded ) rWriter.Pop(); } mbGroupIgnoreGDIMtfActions = false; } mCurrentGraphic.Clear(); } } else if ( mbGroupIgnoreGDIMtfActions ) { rCurGDIMtfAction++; bRet = true; } return bRet; } PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) : mrOutDev ( rOutDev ), mbTaggedPDF ( false ), mbExportNotes ( true ), mbExportNotesInMargin ( false ), mbExportNotesPages ( false ), mbTransitionEffects ( true ), mbUseLosslessCompression( true ), mbReduceImageResolution ( false ), mbExportFormFields ( false ), mbExportBookmarks ( false ), mbExportHiddenSlides ( false ), mbSinglePageSheets ( false ), mbExportNDests ( false ), mnPage ( -1 ), mnCompressionQuality ( 90 ), mpGlobalSyncData ( new GlobalSyncData() ) { mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) ); } PDFExtOutDevData::~PDFExtOutDevData() { mpPageSyncData.reset(); mpGlobalSyncData.reset(); } const Graphic& PDFExtOutDevData::GetCurrentGraphic() const { return mpPageSyncData->mCurrentGraphic; } void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc ) { maDocLocale = rLoc; } void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage ) { mnPage = nPage; } void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression ) { mbUseLosslessCompression = bUseLosslessCompression; } void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality ) { mnCompressionQuality = nQuality; } void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution ) { mbReduceImageResolution = bReduceImageResolution; } void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes ) { mbExportNotes = bExportNotes; } void PDFExtOutDevData::SetIsExportNotesInMargin( const bool bExportNotesInMargin ) { mbExportNotesInMargin = bExportNotesInMargin; } void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages ) { mbExportNotesPages = bExportNotesPages; } void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF ) { mbTaggedPDF = bTaggedPDF; } void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects ) { mbTransitionEffects = bTransitionEffects; } void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields ) { mbExportFormFields = bExportFomtFields; } void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks ) { mbExportBookmarks = bExportBookmarks; } void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides ) { mbExportHiddenSlides = bExportHiddenSlides; } void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets ) { mbSinglePageSheets = bSinglePageSheets; } void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests ) { mbExportNDests = bExportNDests; } void PDFExtOutDevData::ResetSyncData(PDFWriter *const pWriter) { if (pWriter != nullptr) { // tdf#157182 HACK: all PDF actions on this page will be deleted; to have // matching SE IDs on the *next* page, replay EnsureStructureElement actions for (PDFExtOutDevDataSyncPage const& rAction : mpPageSyncData->mActions) { if (std::holds_alternative(rAction.eAct)) { pWriter->EnsureStructureElement(); } } } *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() ); } bool PDFExtOutDevData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf ) { return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this ); } void PDFExtOutDevData::PlayGlobalActions( PDFWriter& rWriter ) { mpGlobalSyncData->PlayGlobalActions( rWriter ); } /* global actions, synchronisation to the recorded metafile isn't needed, all actions will be played after the last page was recorded */ //--->i56629 sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr ) { mpGlobalSyncData->mActions.push_back( vcl::CreateNamedDest{ sDestName, mrOutDev.GetMapMode(), PDFWriter::DestAreaType::XYZ, rRect, nPageNr == -1 ? mnPage : nPageNr } ); return mpGlobalSyncData->mCurId++; } //<---i56629 sal_Int32 PDFExtOutDevData::RegisterDest() { const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++; mpGlobalSyncData->mActions.push_back( vcl::RegisterDest{ nLinkDestID } ); return nLinkDestID; } void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) { OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" ); PDFLinkDestination aLinkDestination; aLinkDestination.mRect = rRect; aLinkDestination.mMapMode = mrOutDev.GetMapMode(); aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr; aLinkDestination.mAreaType = eType; mpGlobalSyncData->mFutureDestinations[ nDestId ] = aLinkDestination; } sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) { mpGlobalSyncData->mActions.push_back( vcl::CreateDest{ mrOutDev.GetMapMode(), eType, rRect, nPageNr == -1 ? mnPage : nPageNr } ); return mpGlobalSyncData->mCurId++; } sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr) { mpGlobalSyncData->mActions.push_back( vcl::CreateLink{rAltText, mrOutDev.GetMapMode(), rRect, nPageNr == -1 ? mnPage : nPageNr } ); return mpGlobalSyncData->mCurId++; } sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, OUString const& rAltText, OUString const& rMimeType, sal_Int32 nPageNr, SdrObject const*const pObj) { mpGlobalSyncData->mActions.push_back(vcl::CreateScreen{ rAltText, rMimeType, mrOutDev.GetMapMode(), rRect, nPageNr }); auto const ret(mpGlobalSyncData->mCurId++); m_ScreenAnnotations[pObj].push_back(ret); return ret; } ::std::vector const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const { auto const it(m_ScreenAnnotations.find(pObj)); if (it == m_ScreenAnnotations.end()) { assert(false); // expected? } return it->second; } void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) { mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } ); } void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL ) { mpGlobalSyncData->mActions.push_back( vcl::SetLinkURL{ rURL, nLinkId } ); } void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL) { mpGlobalSyncData->mActions.push_back(vcl::SetScreenURL{ rURL, nScreenId }); } void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL) { mpGlobalSyncData->mActions.push_back(vcl::SetScreenStream{ rURL, nScreenId }); } sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID ) { if (nParent == -1) // Has no parent, it's a chapter / heading 1. maChapterNames.push_back(rText); mpGlobalSyncData->mActions.push_back( vcl::CreateOutlineItem{ rText, nParent, nDestID } ); return mpGlobalSyncData->mCurId++; } void PDFExtOutDevData::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) { mpGlobalSyncData->mActions.push_back( vcl::CreateNote{ mrOutDev.GetMapMode(), rNote, rRect, nPageNr == -1 ? mnPage : nPageNr } ); } void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec ) { mpGlobalSyncData->mActions.push_back( vcl::SetPageTransition{ eType, nMilliSec, mnPage } ); } /* local (page), actions have to be played synchronously to the actions of of the recorded metafile (created by each xRenderable->render()) */ sal_Int32 PDFExtOutDevData::EnsureStructureElement(void const*const key) { sal_Int32 id(-1); if (key != nullptr) { auto const it(mpGlobalSyncData->mSEMap.find(key)); if (it != mpGlobalSyncData->mSEMap.end()) { id = it->second; } } if (id == -1) { id = mpGlobalSyncData->mStructParents.size(); mpPageSyncData->PushAction(mrOutDev, vcl::EnsureStructureElement{ id }); mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement); if (key != nullptr) { mpGlobalSyncData->mSEMap.emplace(key, id); } } return id; } void PDFExtOutDevData::InitStructureElement(sal_Int32 const id, PDFWriter::StructElement const eType, const OUString& rAlias) { mpPageSyncData->PushAction(mrOutDev, vcl::InitStructureElement{ eType, rAlias, id }); // update parent: required for hell fly anchor frames in sw, so that on the actual // anchor frame EndStructureElement() resets mCurrentStructElement properly. mpGlobalSyncData->mStructParents[id] = mpGlobalSyncData->mCurrentStructElement; } void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id) { mpPageSyncData->PushAction( mrOutDev, vcl::BeginStructureElement{ id } ); mpGlobalSyncData->mCurrentStructElement = id; } sal_Int32 PDFExtOutDevData::WrapBeginStructureElement( PDFWriter::StructElement const eType, const OUString& rAlias) { sal_Int32 const id = EnsureStructureElement(nullptr); InitStructureElement(id, eType, rAlias); BeginStructureElement(id); return id; } void PDFExtOutDevData::EndStructureElement() { assert(mpGlobalSyncData->mCurrentStructElement != 0); // underflow? mpPageSyncData->PushAction( mrOutDev, vcl::EndStructureElement{} ); mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ]; } void PDFExtOutDevData::SetCurrentStructureElement(sal_Int32 const nStructId) { assert(o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size()); mpGlobalSyncData->mCurrentStructElement = nStructId; mpPageSyncData->PushAction( mrOutDev, vcl::SetCurrentStructureElement{ nStructId } ); } sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const { return mpGlobalSyncData->mCurrentStructElement; } void PDFExtOutDevData::SetStructureAttribute( PDFWriter::StructAttribute eAttr, PDFWriter::StructAttributeValue eVal ) { mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttribute{ eAttr, eVal } ); } void PDFExtOutDevData::SetStructureAttributeNumerical( PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) { mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttributeNumerical { eAttr, nValue } ); } void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect ) { mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureBoundingBox{ rRect } ); } void PDFExtOutDevData::SetStructureAnnotIds(::std::vector const& rAnnotIds) { mpPageSyncData->PushAction(mrOutDev, vcl::SetStructureAnnotIds{ rAnnotIds }); } void PDFExtOutDevData::SetActualText( const OUString& rText ) { mpPageSyncData->PushAction( mrOutDev, vcl::SetActualText{ rText } ); } void PDFExtOutDevData::SetAlternateText( const OUString& rText ) { mpPageSyncData->PushAction( mrOutDev, vcl::SetAlternateText{ rText } ); } void PDFExtOutDevData::CreateControl( const PDFWriter::AnyWidget& rControlType ) { std::shared_ptr< PDFWriter::AnyWidget > pClone( rControlType.Clone() ); mpPageSyncData->PushAction( mrOutDev, vcl::CreateControl{ pClone } ); } void PDFExtOutDevData::BeginGroup() { mpPageSyncData->PushAction( mrOutDev, vcl::BeginGroup{} ); } void PDFExtOutDevData::EndGroup( const Graphic& rGraphic, sal_uInt8 nTransparency, const tools::Rectangle& rOutputRect, const tools::Rectangle& rVisibleOutputRect ) { mpPageSyncData->PushAction( mrOutDev, vcl::EndGroupGfxLink{rGraphic, rOutputRect, rVisibleOutputRect, nTransparency} ); } // Avoids expensive de-compression and re-compression of large images. bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic, const tools::Rectangle & rOutputRect, const tools::Rectangle & rVisibleOutputRect ) const { assert(rGraphic.IsGfxLink() && (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg || rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng || rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf)); if (rOutputRect != rVisibleOutputRect) // rOutputRect is the crop rectangle, re-compress cropped image. return false; if (mbReduceImageResolution) // Reducing resolution was requested, implies that re-compressing is // wanted. return false; auto nSize = rGraphic.GetGfxLink().GetDataSize(); if (nSize == 0) return false; GfxLink aLink = rGraphic.GetGfxLink(); SvMemoryStream aMemoryStream(const_cast(aLink.GetData()), aLink.GetDataSize(), StreamMode::READ | StreamMode::WRITE); GraphicDescriptor aDescriptor(aMemoryStream, nullptr); if (aDescriptor.Detect(true) && aDescriptor.GetNumberOfImageComponents() == 4) // 4 means CMYK, which is not handled. return false; const Size aSize = rGraphic.GetSizePixel(); // small items better off as PNG anyway if ( aSize.Width() < 32 && aSize.Height() < 32 ) return false; if (GetIsLosslessCompression()) return !GetIsReduceImageResolution(); // FIXME: ideally we'd also pre-empt the DPI related scaling too. sal_Int32 nCurrentRatio = (100 * aSize.Width() * aSize.Height() * 4) / nSize; static const struct { sal_Int32 mnQuality; sal_Int32 mnRatio; } aRatios[] = { // minimum tolerable compression ratios { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 }, { 80, 1500 }, { 75, 1700 } }; sal_Int32 nTargetRatio = 10000; bool bIsTargetRatioReached = false; for (auto & rRatio : aRatios) { if ( mnCompressionQuality > rRatio.mnQuality ) { bIsTargetRatioReached = true; break; } nTargetRatio = rRatio.mnRatio; } return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */