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