1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_slideshow.hxx"
26 
27 // must be first
28 #include <canvas/debug.hxx>
29 #include <tools/diagnose_ex.h>
30 #include <gdimtftools.hxx>
31 
32 #include <com/sun/star/document/XExporter.hpp>
33 #include <com/sun/star/document/XFilter.hpp>
34 #include <com/sun/star/graphic/XGraphic.hpp>
35 #include <com/sun/star/graphic/XGraphicRenderer.hpp>
36 #include <com/sun/star/drawing/XShape.hpp>
37 
38 #include <cppuhelper/basemutex.hxx>
39 #include <cppuhelper/compbase1.hxx>
40 
41 #include <comphelper/uno3.hxx>
42 #include <cppuhelper/implbase1.hxx>
43 
44 #include <tools/stream.hxx>
45 #include <vcl/svapp.hxx>
46 #include <vcl/canvastools.hxx>
47 #include <vcl/metaact.hxx>
48 #include <vcl/virdev.hxx>
49 #include <vcl/gdimtf.hxx>
50 #include <vcl/metaact.hxx>
51 #include <vcl/animate.hxx>
52 #include <vcl/graph.hxx>
53 
54 #include <unotools/streamwrap.hxx>
55 
56 #include "tools.hxx"
57 
58 using namespace ::com::sun::star;
59 
60 
61 // free support functions
62 // ======================
63 
64 namespace slideshow
65 {
66 namespace internal
67 {
68 // TODO(E2): Detect the case when svx/drawing layer is not
69 // in-process, or even not on the same machine, and
70 // fallback to metafile streaming!
71 
72 // For fixing #i48102#, have to be a _lot_ more selective
73 // on which metafiles to convert to bitmaps. The problem
74 // here is that we _always_ get the shape content as a
75 // metafile, even if we have a bitmap graphic shape. Thus,
76 // calling GetBitmapEx on such a Graphic (see below) will
77 // result in one poorly scaled bitmap into another,
78 // somewhat arbitrarily sized bitmap.
hasUnsupportedActions(const GDIMetaFile & rMtf)79 bool hasUnsupportedActions( const GDIMetaFile& rMtf )
80 {
81     // search metafile for RasterOp action
82     MetaAction* pCurrAct;
83 
84     // TODO(Q3): avoid const-cast
85     for( pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction();
86          pCurrAct;
87          pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction() )
88     {
89         switch( pCurrAct->GetType() )
90         {
91             case META_RASTEROP_ACTION:
92                 // overpaint is okay - that's the default, anyway
93                 if( ROP_OVERPAINT ==
94                     static_cast<MetaRasterOpAction*>(pCurrAct)->GetRasterOp() )
95                 {
96                     break;
97                 }
98                 // FALLTHROUGH intended
99             case META_MOVECLIPREGION_ACTION:
100                 // FALLTHROUGH intended
101             case META_REFPOINT_ACTION:
102                 // FALLTHROUGH intended
103             case META_WALLPAPER_ACTION:
104                 return true; // at least one unsupported
105                              // action encountered
106         }
107     }
108 
109     return false; // no unsupported action found
110 }
111 
112 namespace {
113 
114 typedef ::cppu::WeakComponentImplHelper1< graphic::XGraphicRenderer > DummyRenderer_Base;
115 
116 class DummyRenderer :
117         public DummyRenderer_Base,
118         public cppu::BaseMutex
119 {
120 public:
DummyRenderer()121     DummyRenderer() :
122         DummyRenderer_Base( m_aMutex ),
123         mxGraphic()
124         {
125         }
126 
127     //---  XGraphicRenderer  -----------------------------------
render(const uno::Reference<graphic::XGraphic> & rGraphic)128     virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) throw (uno::RuntimeException)
129         {
130             ::osl::MutexGuard aGuard( m_aMutex );
131             mxGraphic = rGraphic;
132         }
133 
134     /** Retrieve GDIMetaFile from renderer
135 
136         @param bForeignSource
137         When true, the source of the metafile might be a
138         foreign application. The metafile is checked
139         against unsupported content, and, if necessary,
140         returned as a pre-rendererd bitmap.
141     */
getMtf(bool bForeignSource) const142     GDIMetaFile getMtf( bool bForeignSource ) const
143     {
144         ::osl::MutexGuard aGuard( m_aMutex );
145 
146         Graphic aGraphic( mxGraphic );
147 
148         if( aGraphic.GetType() == GRAPHIC_BITMAP ||
149             (bForeignSource &&
150              hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) )
151         {
152             // wrap bitmap into GDIMetafile
153             GDIMetaFile 	aMtf;
154             ::Point			aEmptyPoint;
155 
156             ::BitmapEx		aBmpEx( aGraphic.GetBitmapEx() );
157 
158             aMtf.AddAction( new MetaBmpExAction( aEmptyPoint,
159                                                  aBmpEx ) );
160             aMtf.SetPrefSize( aBmpEx.GetPrefSize() );
161             aMtf.SetPrefMapMode( aBmpEx.GetPrefMapMode() );
162 
163             return aMtf;
164         }
165         else
166         {
167             return aGraphic.GetGDIMetaFile();
168         }
169     }
170 
171 private:
172     uno::Reference< graphic::XGraphic >	mxGraphic;
173 };
174 
175 } // anon namespace
176 
177 // Quick'n'dirty way: tunnel Graphic (only works for
178 // in-process slideshow, of course)
getMetaFile(const uno::Reference<lang::XComponent> & xSource,const uno::Reference<drawing::XDrawPage> & xContainingPage,GDIMetaFile & rMtf,int mtfLoadFlags,const uno::Reference<uno::XComponentContext> & rxContext)179 bool getMetaFile( const uno::Reference< lang::XComponent >& 	  xSource,
180                   const uno::Reference< drawing::XDrawPage >&     xContainingPage,
181                   GDIMetaFile&                                    rMtf,
182                   int                                             mtfLoadFlags,
183                   const uno::Reference< uno::XComponentContext >& rxContext )
184 {
185     ENSURE_OR_RETURN_FALSE( rxContext.is(),
186                        "getMetaFile(): Invalid context" );
187 
188     // create dummy XGraphicRenderer, which receives the
189     // generated XGraphic from the GraphicExporter
190 
191     // TODO(P3): Move creation of DummyRenderer out of the
192     // loop! Either by making it static, or transforming
193     // the whole thing here into a class.
194     DummyRenderer*						 		pRenderer( new DummyRenderer() );
195     uno::Reference< graphic::XGraphicRenderer > xRenderer( pRenderer );
196 
197     // -> stuff that into UnoGraphicExporter.
198     uno::Reference<lang::XMultiComponentFactory> xFactory(
199         rxContext->getServiceManager() );
200 
201     OSL_ENSURE( xFactory.is(), "### no UNO?!" );
202     if( !xFactory.is() )
203         return false;
204 
205     // creating the graphic exporter
206     uno::Reference< document::XExporter > xExporter(
207         xFactory->createInstanceWithContext(
208             OUSTR("com.sun.star.drawing.GraphicExportFilter"),
209             rxContext),
210         uno::UNO_QUERY );
211     uno::Reference< document::XFilter > xFilter( xExporter, uno::UNO_QUERY );
212 
213     OSL_ENSURE( xExporter.is() && xFilter.is(), "### no graphic exporter?!" );
214     if( !xExporter.is() || !xFilter.is() )
215         return false;
216 
217     uno::Sequence< beans::PropertyValue > aProps(3);
218     aProps[0].Name = OUSTR("FilterName");
219     aProps[0].Value <<= OUSTR("SVM");
220 
221     aProps[1].Name = OUSTR("GraphicRenderer");
222     aProps[1].Value <<= xRenderer;
223 
224     uno::Sequence< beans::PropertyValue > aFilterData(5);
225     aFilterData[0].Name = OUSTR("VerboseComments");
226     aFilterData[0].Value <<= ((mtfLoadFlags & MTF_LOAD_VERBOSE_COMMENTS) != 0);
227 
228     aFilterData[1].Name = OUSTR("ScrollText");
229     aFilterData[1].Value <<= ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0);
230 
231     aFilterData[2].Name = OUSTR("ExportOnlyBackground");
232     aFilterData[2].Value <<= ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0);
233 
234     aFilterData[3].Name = OUSTR("Version");
235     aFilterData[3].Value <<= static_cast<sal_Int32>( SOFFICE_FILEFORMAT_50 );
236 
237     aFilterData[4].Name = OUSTR("CurrentPage");
238     aFilterData[4].Value <<= uno::Reference< uno::XInterface >( xContainingPage,
239                                                                 uno::UNO_QUERY_THROW );
240 
241     aProps[2].Name = OUSTR("FilterData");
242     aProps[2].Value <<= aFilterData;
243 
244     xExporter->setSourceDocument( xSource );
245     if( !xFilter->filter( aProps ) )
246         return false;
247 
248     rMtf = pRenderer->getMtf( (mtfLoadFlags & MTF_LOAD_FOREIGN_SOURCE) != 0 );
249 
250     // pRenderer is automatically destroyed when xRenderer
251     // goes out of scope
252 
253     // TODO(E3): Error handling. Exporter might have
254     // generated nothing, a bitmap, threw an exception,
255     // whatever.
256     return true;
257 }
258 
removeTextActions(GDIMetaFile & rMtf)259 void removeTextActions( GDIMetaFile& rMtf )
260 {
261     // search metafile for text output
262     MetaAction* pCurrAct;
263 
264     int nActionIndex(0);
265     pCurrAct = rMtf.FirstAction();
266     while( pCurrAct )
267     {
268         switch( pCurrAct->GetType() )
269         {
270         case META_TEXTCOLOR_ACTION:
271         case META_TEXTFILLCOLOR_ACTION:
272         case META_TEXTLINECOLOR_ACTION:
273         case META_TEXTALIGN_ACTION:
274         case META_FONT_ACTION:
275         case META_LAYOUTMODE_ACTION:
276         case META_TEXT_ACTION:
277         case META_TEXTARRAY_ACTION:
278         case META_TEXTRECT_ACTION:
279         case META_STRETCHTEXT_ACTION:
280         case META_TEXTLINE_ACTION:
281         {
282             // remove every text-related actions
283             pCurrAct = rMtf.NextAction();
284 
285             rMtf.RemoveAction( nActionIndex );
286             break;
287         }
288 
289         default:
290             pCurrAct = rMtf.NextAction();
291             ++nActionIndex;
292             break;
293         }
294     }
295 }
296 
getNextActionOffset(MetaAction * pCurrAct)297 sal_Int32 getNextActionOffset( MetaAction * pCurrAct )
298 {
299     // Special handling for actions that represent
300     // more than one indexable action
301     // ===========================================
302 
303     switch (pCurrAct->GetType()) {
304     case META_TEXT_ACTION: {
305         MetaTextAction * pAct = static_cast<MetaTextAction *>(pCurrAct);
306         return (pAct->GetLen() == (sal_uInt16)STRING_LEN
307                 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen());
308     }
309     case META_TEXTARRAY_ACTION: {
310         MetaTextArrayAction * pAct =
311             static_cast<MetaTextArrayAction *>(pCurrAct);
312         return (pAct->GetLen() == (sal_uInt16)STRING_LEN
313                 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen());
314     }
315     case META_STRETCHTEXT_ACTION: {
316         MetaStretchTextAction * pAct =
317             static_cast<MetaStretchTextAction *>(pCurrAct);
318         return (pAct->GetLen() == (sal_uInt16)STRING_LEN
319                 ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen());
320     }
321     case META_FLOATTRANSPARENT_ACTION: {
322         MetaFloatTransparentAction * pAct =
323             static_cast<MetaFloatTransparentAction*>(pCurrAct);
324         // TODO(F2): Recurse into action metafile
325         // (though this is currently not used from the
326         // DrawingLayer - shape transparency gradients
327         // don't affect shape text)
328         return pAct->GetGDIMetaFile().GetActionCount();
329     }
330     default:
331         return 1;
332     }
333 }
334 
getAnimationFromGraphic(VectorOfMtfAnimationFrames & o_rFrames,::std::size_t & o_rLoopCount,CycleMode & o_eCycleMode,const Graphic & rGraphic)335 bool getAnimationFromGraphic( VectorOfMtfAnimationFrames&   o_rFrames,
336                               ::std::size_t&                o_rLoopCount,
337                               CycleMode&                    o_eCycleMode,
338                               const Graphic&                rGraphic )
339 {
340     o_rFrames.clear();
341 
342     if( !rGraphic.IsAnimated() )
343         return false;
344 
345     // some loop invariants
346     Animation 	aAnimation( rGraphic.GetAnimation() );
347     const Point aEmptyPoint;
348     const Size  aAnimSize( aAnimation.GetDisplaySizePixel() );
349 
350     // setup VDev, into which all bitmaps are painted (want to
351     // normalize animations to n bitmaps of same size. An Animation,
352     // though, can contain bitmaps of varying sizes and different
353     // update modes)
354     VirtualDevice aVDev;
355     aVDev.SetOutputSizePixel( aAnimSize );
356     aVDev.EnableMapMode( sal_False );
357 
358     // setup mask VDev (alpha VDev is currently rather slow)
359     VirtualDevice aVDevMask;
360     aVDevMask.SetOutputSizePixel( aAnimSize );
361     aVDevMask.EnableMapMode( sal_False );
362 
363     switch( aAnimation.GetCycleMode() )
364     {
365         case CYCLE_NOT:
366             o_rLoopCount = 1;
367             o_eCycleMode = CYCLE_LOOP;
368             break;
369 
370         case CYCLE_FALLBACK:
371             // FALLTHROUGH intended
372         case CYCLE_NORMAL:
373             o_rLoopCount = aAnimation.GetLoopCount();
374             o_eCycleMode = CYCLE_LOOP;
375             break;
376 
377         case CYCLE_REVERS:
378             // FALLTHROUGH intended
379         case CYCLE_REVERS_FALLBACK:
380             o_rLoopCount = aAnimation.GetLoopCount();
381             o_eCycleMode = CYCLE_PINGPONGLOOP;
382             break;
383 
384         default:
385             ENSURE_OR_RETURN_FALSE(false,
386                               "getAnimationFromGraphic(): Unexpected case" );
387             break;
388     }
389 
390     for( sal_uInt16 i=0, nCount=aAnimation.Count(); i<nCount; ++i )
391     {
392         const AnimationBitmap& rAnimBmp( aAnimation.Get(i) );
393         switch(rAnimBmp.eDisposal)
394         {
395             case DISPOSE_NOT:
396             {
397                 aVDev.DrawBitmapEx(rAnimBmp.aPosPix,
398                                    rAnimBmp.aBmpEx);
399                 Bitmap aMask = rAnimBmp.aBmpEx.GetMask();
400 
401                 if( aMask.IsEmpty() )
402                 {
403                     const Point aEmpty;
404                     const Rectangle aRect(aEmptyPoint,
405                                           aVDevMask.GetOutputSizePixel());
406                     const Wallpaper aWallpaper(COL_BLACK);
407                     aVDevMask.DrawWallpaper(aRect,
408                                             aWallpaper);
409                 }
410                 else
411                 {
412                     BitmapEx aTmpMask = BitmapEx(aMask,
413                                                  aMask);
414                     aVDevMask.DrawBitmapEx(rAnimBmp.aPosPix,
415                                            aTmpMask );
416                 }
417                 break;
418             }
419 
420             case DISPOSE_BACK:
421             {
422 				// #i70772# react on no mask
423 				const Bitmap aMask(rAnimBmp.aBmpEx.GetMask());
424 				const Bitmap aContent(rAnimBmp.aBmpEx.GetBitmap());
425 
426 				aVDevMask.Erase();
427 				aVDev.DrawBitmap(rAnimBmp.aPosPix, aContent);
428 
429 				if(aMask.IsEmpty())
430 				{
431 					const Rectangle aRect(rAnimBmp.aPosPix, aContent.GetSizePixel());
432 					aVDevMask.SetFillColor(COL_BLACK);
433 					aVDevMask.SetLineColor();
434 					aVDevMask.DrawRect(aRect);
435 				}
436 				else
437 				{
438 					aVDevMask.DrawBitmap(rAnimBmp.aPosPix, aMask);
439 				}
440 				break;
441             }
442 
443             case DISPOSE_FULL:
444             {
445                 aVDev.DrawBitmapEx(rAnimBmp.aPosPix,
446                                    rAnimBmp.aBmpEx);
447                 break;
448             }
449 
450             case DISPOSE_PREVIOUS :
451             {
452                 aVDev.DrawBitmapEx(rAnimBmp.aPosPix,
453                                    rAnimBmp.aBmpEx);
454                 aVDevMask.DrawBitmap(rAnimBmp.aPosPix,
455                                      rAnimBmp.aBmpEx.GetMask());
456                 break;
457             }
458         }
459 
460         // extract current aVDev content into a new animation
461         // frame
462         GDIMetaFileSharedPtr pMtf( new GDIMetaFile() );
463         pMtf->AddAction(
464             new MetaBmpExAction( aEmptyPoint,
465                                  BitmapEx(
466                                      aVDev.GetBitmap(
467                                          aEmptyPoint,
468                                          aAnimSize ),
469                                      aVDevMask.GetBitmap(
470                                          aEmptyPoint,
471                                          aAnimSize ))));
472 
473         // setup mtf dimensions and pref map mode (for
474         // simplicity, keep it all in pixel. the metafile
475         // renderer scales it down to (1, 1) box anyway)
476         pMtf->SetPrefMapMode( MapMode() );
477         pMtf->SetPrefSize( aAnimSize );
478 
479         // #115934#
480         // Take care of special value for MultiPage TIFFs. ATM these shall just
481         // show their first page for _quite_ some time.
482         sal_Int32 nWaitTime100thSeconds( rAnimBmp.nWait );
483         if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds )
484         {
485             // ATM the huge value would block the timer, so use a long
486             // time to show first page (whole day)
487             nWaitTime100thSeconds = 100 * 60 * 60 * 24;
488         }
489 
490         // There are animated GIFs with no WaitTime set. Take 0.1 sec, the
491         // same duration that is used by the edit view.
492         if( nWaitTime100thSeconds == 0 )
493             nWaitTime100thSeconds = 10;
494 
495         o_rFrames.push_back( MtfAnimationFrame( pMtf,
496                                                 nWaitTime100thSeconds / 100.0 ) );
497     }
498 
499     return !o_rFrames.empty();
500 }
501 
getRectanglesFromScrollMtf(::basegfx::B2DRectangle & o_rScrollRect,::basegfx::B2DRectangle & o_rPaintRect,const GDIMetaFileSharedPtr & rMtf)502 bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle&       o_rScrollRect,
503                                  ::basegfx::B2DRectangle&       o_rPaintRect,
504                                  const GDIMetaFileSharedPtr&    rMtf )
505 {
506     // extract bounds: scroll rect, paint rect
507     bool bScrollRectSet(false);
508     bool bPaintRectSet(false);
509 
510     for ( MetaAction * pCurrAct = rMtf->FirstAction();
511           pCurrAct != 0; pCurrAct = rMtf->NextAction() )
512     {
513         if (pCurrAct->GetType() == META_COMMENT_ACTION)
514         {
515             MetaCommentAction * pAct =
516                 static_cast<MetaCommentAction *>(pCurrAct);
517             // skip comment if not a special XTEXT comment
518             if (pAct->GetComment().CompareIgnoreCaseToAscii(
519                     RTL_CONSTASCII_STRINGPARAM("XTEXT") ) == COMPARE_EQUAL)
520             {
521                 if (pAct->GetComment().CompareIgnoreCaseToAscii(
522                         "XTEXT_SCROLLRECT" ) == COMPARE_EQUAL) {
523                     o_rScrollRect = ::vcl::unotools::b2DRectangleFromRectangle(
524                         *reinterpret_cast<Rectangle const *>(
525                             pAct->GetData() ) );
526 
527                     bScrollRectSet = true;
528                 }
529                 else if (pAct->GetComment().CompareIgnoreCaseToAscii(
530                              "XTEXT_PAINTRECT" ) == COMPARE_EQUAL) {
531                     o_rPaintRect = ::vcl::unotools::b2DRectangleFromRectangle(
532                         *reinterpret_cast<Rectangle const *>(
533                             pAct->GetData() ) );
534 
535                     bPaintRectSet = true;
536                 }
537             }
538         }
539     }
540 
541     return bScrollRectSet && bPaintRectSet;
542 }
543 
544 }
545 }
546 
547