/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; namespace sd { struct RenderContext { SdrModel& mrModel; EEControlBits mnSavedControlBits; ScopedVclPtrInstance maVirtualDevice; RenderContext(unsigned char* pBuffer, SdrModel& rModel, SdrPage& rPage, Size const& rSlideSize) : mrModel(rModel) , maVirtualDevice(DeviceFormat::WITHOUT_ALPHA) { // Turn off spelling SdrOutliner& rOutliner = mrModel.GetDrawOutliner(); mnSavedControlBits = rOutliner.GetControlWord(); rOutliner.SetControlWord(mnSavedControlBits & ~EEControlBits::ONLINESPELLING); maVirtualDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); maVirtualDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(rSlideSize, Fraction(1.0), Point(), pBuffer); Size aPageSize(rPage.GetSize()); MapMode aMapMode(MapUnit::Map100thMM); const Fraction aFracX(rSlideSize.Width(), maVirtualDevice->LogicToPixel(aPageSize, aMapMode).Width()); aMapMode.SetScaleX(aFracX); const Fraction aFracY(rSlideSize.Height(), maVirtualDevice->LogicToPixel(aPageSize, aMapMode).Height()); aMapMode.SetScaleY(aFracY); maVirtualDevice->SetMapMode(aMapMode); } ~RenderContext() { // Restore spelling SdrOutliner& rOutliner = mrModel.GetDrawOutliner(); rOutliner.SetControlWord(mnSavedControlBits); } }; namespace { bool hasFields(SdrObject* pObject) { auto* pTextObject = dynamic_cast(pObject); if (!pTextObject) return false; OutlinerParaObject* pOutlinerParagraphObject = pTextObject->GetOutlinerParaObject(); if (pOutlinerParagraphObject) { const EditTextObject& rEditText = pOutlinerParagraphObject->GetTextObject(); if (rEditText.IsFieldObject()) return true; } return false; } /** VOC redirector to control which object should be rendered and which not */ class ObjectRedirector : public sdr::contact::ViewObjectContactRedirector { protected: RenderState& mrRenderState; public: ObjectRedirector(RenderState& rRenderState) : mrRenderState(rRenderState) { } virtual void createRedirectedPrimitive2DSequence( const sdr::contact::ViewObjectContact& rOriginal, const sdr::contact::DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override { // Generate single pass for background layer if (mrRenderState.meStage == RenderStage::Background) { mrRenderState.mbPassHasOutput = true; mrRenderState.mbSkipAllInThisPass = true; return; } if (mrRenderState.mbSkipAllInThisPass) return; SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); // Check if we are rendering an object that is valid to render (exists, and not empty) if (pObject == nullptr || pObject->IsEmptyPresObj()) return; SdrPage* pPage = pObject->getSdrPageFromSdrObject(); // Does the object have a page if (pPage == nullptr) return; // is the object visible and not hidden by any option const bool bVisible = pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true); if (!bVisible) return; // Check if we have already rendered the object if (mrRenderState.isObjectAlreadyRendered(pObject)) return; // Check if we are in correct stage if (mrRenderState.meStage == RenderStage::Master && !pPage->IsMasterPage()) { if (mrRenderState.mbFirstObjectInPass) { // if this is the first object - change from master to slide // means we are done with rendering of master layers mrRenderState.meStage = RenderStage::Slide; } else { // if not, we have to stop rendering all further objects mrRenderState.mbSkipAllInThisPass = true; return; } } if (mrRenderState.isObjectInAnimation(pObject)) { // Animated object has to be only one in the render mrRenderState.mbSkipAllInThisPass = true; // Animated object cannot be attached to the previous object if (!mrRenderState.mbFirstObjectInPass) return; } if (mrRenderState.meStage == RenderStage::Master && hasFields(pObject) && mrRenderState.mbStopRenderingWhenField && !mrRenderState.mbFirstObjectInPass) { mrRenderState.mbStopRenderingWhenField = false; mrRenderState.mbSkipAllInThisPass = true; return; } mrRenderState.mpCurrentTarget = pObject; // render the object sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence( rOriginal, rDisplayInfo, rVisitor); mrRenderState.mbFirstObjectInPass = false; mrRenderState.maObjectsDone.insert(pObject); mrRenderState.mbPassHasOutput = true; } }; bool hasEmptyMaster(SdrPage const& rPage) { if (!rPage.TRG_HasMasterPage()) return true; SdrPage& rMaster = rPage.TRG_GetMasterPage(); for (size_t i = 0; i < rMaster.GetObjCount(); i++) { auto pObject = rMaster.GetObj(i); if (!pObject->IsEmptyPresObj()) return false; } return true; } } // end anonymous namespace SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage& rPage) : mrPage(rPage) , mrModel(rPage.getSdrModelFromSdrPage()) { maRenderState.meStage = RenderStage::Background; setupAnimations(); } void SlideshowLayerRenderer::setupAnimations() { auto* pSdPage = dynamic_cast(&mrPage); if (!pSdPage) return; std::vector> aAnimationVector; anim::create_deep_vector(pSdPage->getAnimationNode(), aAnimationVector); for (uno::Reference const& rNode : aAnimationVector) { switch (rNode->getType()) { // filter out the most obvious case animations::AnimationNodeType::CUSTOM: case animations::AnimationNodeType::ANIMATE: case animations::AnimationNodeType::SET: case animations::AnimationNodeType::ANIMATEMOTION: case animations::AnimationNodeType::ANIMATECOLOR: case animations::AnimationNodeType::ANIMATETRANSFORM: case animations::AnimationNodeType::TRANSITIONFILTER: case animations::AnimationNodeType::ANIMATEPHYSICS: { uno::Reference xAnimate(rNode, uno::UNO_QUERY); if (xAnimate.is()) { uno::Any aAny = xAnimate->getTarget(); uno::Reference xShape; SvxShape* pShape = nullptr; SdrObject* pObject = nullptr; if ((aAny >>= xShape) && xShape.is()) { pShape = comphelper::getFromUnoTunnel(xShape); if (pShape) { pObject = pShape->GetSdrObject(); maRenderState.maInAnimation.insert(pObject); } } else // if target is not a shape { presentation::ParagraphTarget aParagraphTarget; if ((aAny >>= aParagraphTarget) && aParagraphTarget.Shape.is()) { //sal_Int32 nParagraph = aParagraphTarget.Paragraph; xShape = aParagraphTarget.Shape; pShape = comphelper::getFromUnoTunnel(xShape); if (pShape) { pObject = pShape->GetSdrObject(); maRenderState.maInAnimation.insert(pObject); } } } if (pObject) { bool bVisible; if (anim::getVisibilityProperty(xAnimate, bVisible)) { // if initial anim sets shape visible, set it // to invisible. If we're asked for the final // state, don't do anything obviously bVisible = !bVisible; maRenderState.maInitiallyVisible[pObject] = bVisible; } } } } } } } Size SlideshowLayerRenderer::calculateAndSetSizePixel(Size const& rDesiredSizePixel) { double fRatio = double(mrPage.GetHeight()) / mrPage.GetWidth(); Size aSize(rDesiredSizePixel.Width(), ::tools::Long(rDesiredSizePixel.Width() * fRatio)); maSlideSize = aSize; return maSlideSize; } void SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext) { SdrView aView(mrModel, rRenderContext.maVirtualDevice); aView.SetPageVisible(false); aView.SetPageShadowVisible(false); aView.SetPageBorderVisible(false); aView.SetBordVisible(false); aView.SetGridVisible(false); aView.SetHlplVisible(false); aView.SetGlueVisible(false); aView.setHideBackground(!maRenderState.includeBackground()); aView.ShowSdrPage(&mrPage); Size aPageSize(mrPage.GetSize()); Point aPoint; vcl::Region aRegion(::tools::Rectangle(aPoint, aPageSize)); ObjectRedirector aRedirector(maRenderState); aView.CompleteRedraw(rRenderContext.maVirtualDevice, aRegion, &aRedirector); } static void writeContentNode(::tools::JsonWriter& aJsonWriter) { auto aContentNode = aJsonWriter.startNode("content"); aJsonWriter.put("type", "%IMAGETYPE%"); aJsonWriter.put("checksum", "%IMAGECHECKSUM%"); } static void writeBoundingBox(::tools::JsonWriter& aJsonWriter, SdrObject* pObject) { auto aContentNode = aJsonWriter.startNode("bounds"); ::tools::Rectangle aRectmm100 = pObject->GetCurrentBoundRect(); ::tools::Rectangle aRect = o3tl::convert(aRectmm100, o3tl::Length::mm100, o3tl::Length::twip); aJsonWriter.put("x", aRect.getX()); aJsonWriter.put("y", aRect.getY()); aJsonWriter.put("width", aRect.GetWidth()); aJsonWriter.put("height", aRect.GetHeight()); } void SlideshowLayerRenderer::writeJSON(OString& rJsonMsg) { ::tools::JsonWriter aJsonWriter; aJsonWriter.put("group", maRenderState.stageString()); aJsonWriter.put("index", maRenderState.currentIndex()); aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage))); SdrObject* pObject = maRenderState.currentTarget(); bool bIsAnimated = maRenderState.isObjectInAnimation(pObject); if (bIsAnimated) { assert(pObject); aJsonWriter.put("type", "animated"); { auto aContentNode = aJsonWriter.startNode("content"); aJsonWriter.put("hash", RenderState::getObjectHash(pObject)); aJsonWriter.put("initVisible", maRenderState.isObjectInitiallyVisible(pObject)); aJsonWriter.put("type", "bitmap"); writeContentNode(aJsonWriter); writeBoundingBox(aJsonWriter, pObject); } } else { if (pObject && hasFields(pObject)) aJsonWriter.put("isField", true); // TODO: to be removed, implement properly aJsonWriter.put("type", "bitmap"); writeContentNode(aJsonWriter); } rJsonMsg = aJsonWriter.finishAndGetAsOString(); maRenderState.incrementIndex(); } bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg) { // Reset state maRenderState.resetPass(); RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize); createViewAndDraw(aRenderContext); // Check if we are done rendering all passes if (maRenderState.noMoreOutput()) return false; writeJSON(rJsonMsg); maRenderState.mnCurrentPass++; if (maRenderState.meStage == RenderStage::Background) maRenderState.meStage = RenderStage::Master; if (hasEmptyMaster(mrPage)) maRenderState.meStage = RenderStage::Slide; return true; } } // end of namespace sd /* vim:set shiftwidth=4 softtabstop=4 expandtab: */