1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <comphelper/fileformat.h>
24 #include <o3tl/make_shared.hxx>
25 #include <tools/fract.hxx>
26 #include <tools/vcompat.hxx>
27 #include <tools/urlobj.hxx>
28 #include <tools/stream.hxx>
29 #include <unotools/ucbhelper.hxx>
30 #include <unotools/ucbstreamhelper.hxx>
31 #include <unotools/tempfile.hxx>
33 #include <vcl/filter/SvmReader.hxx>
34 #include <vcl/filter/SvmWriter.hxx>
35 #include <vcl/outdev.hxx>
36 #include <vcl/graphicfilter.hxx>
37 #include <vcl/virdev.hxx>
38 #include <vcl/gfxlink.hxx>
39 #include <vcl/cvtgrf.hxx>
40 #include <vcl/graph.hxx>
41 #include <vcl/metaact.hxx>
42 #include <impgraph.hxx>
43 #include <com/sun/star/graphic/XPrimitive2D.hpp>
44 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
45 #include <vcl/dibtools.hxx>
48 #include <vcl/gdimetafiletools.hxx>
49 #include <vcl/TypeSerializer.hxx>
50 #include <vcl/pdfread.hxx>
51 #include <graphic/VectorGraphicLoader.hxx>
53 #define GRAPHIC_MTFTOBMP_MAXEXT 2048
54 #define GRAPHIC_STREAMBUFSIZE 8192UL
56 #define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' )
58 using namespace com::sun::star
;
64 utl::TempFileFast maTempFile
;
68 ImpSwapFile(OUString aOriginURL
)
69 : maOriginURL(std::move(aOriginURL
))
73 SvStream
* getStream() { return maTempFile
.GetStream(StreamMode::READWRITE
); }
74 OUString
const & getOriginURL() const { return maOriginURL
; }
77 SvStream
* ImpGraphic::getSwapFileStream() const
80 return mpSwapFile
->getStream();
84 ImpGraphic::ImpGraphic() :
85 meType ( GraphicType::NONE
),
88 mbDummyContext ( false ),
89 maLastUsed (std::chrono::high_resolution_clock::now()),
94 ImpGraphic::ImpGraphic(const ImpGraphic
& rImpGraphic
)
95 : maMetaFile(rImpGraphic
.maMetaFile
)
96 , maBitmapEx(rImpGraphic
.maBitmapEx
)
97 , maSwapInfo(rImpGraphic
.maSwapInfo
)
98 , mpContext(rImpGraphic
.mpContext
)
99 , mpSwapFile(rImpGraphic
.mpSwapFile
)
100 , mpGfxLink(rImpGraphic
.mpGfxLink
)
101 , meType(rImpGraphic
.meType
)
102 , mnSizeBytes(rImpGraphic
.mnSizeBytes
)
103 , mbSwapOut(rImpGraphic
.mbSwapOut
)
104 , mbDummyContext(rImpGraphic
.mbDummyContext
)
105 , maVectorGraphicData(rImpGraphic
.maVectorGraphicData
)
106 , maGraphicExternalLink(rImpGraphic
.maGraphicExternalLink
)
107 , maLastUsed (std::chrono::high_resolution_clock::now())
108 , mbPrepared (rImpGraphic
.mbPrepared
)
110 if( rImpGraphic
.mpAnimation
)
112 mpAnimation
= std::make_unique
<Animation
>( *rImpGraphic
.mpAnimation
);
113 maBitmapEx
= mpAnimation
->GetBitmapEx();
117 ImpGraphic::ImpGraphic(ImpGraphic
&& rImpGraphic
) noexcept
118 : maMetaFile(std::move(rImpGraphic
.maMetaFile
))
119 , maBitmapEx(std::move(rImpGraphic
.maBitmapEx
))
120 , maSwapInfo(std::move(rImpGraphic
.maSwapInfo
))
121 , mpAnimation(std::move(rImpGraphic
.mpAnimation
))
122 , mpContext(std::move(rImpGraphic
.mpContext
))
123 , mpSwapFile(std::move(rImpGraphic
.mpSwapFile
))
124 , mpGfxLink(std::move(rImpGraphic
.mpGfxLink
))
125 , meType(rImpGraphic
.meType
)
126 , mnSizeBytes(rImpGraphic
.mnSizeBytes
)
127 , mbSwapOut(rImpGraphic
.mbSwapOut
)
128 , mbDummyContext(rImpGraphic
.mbDummyContext
)
129 , maVectorGraphicData(std::move(rImpGraphic
.maVectorGraphicData
))
130 , maGraphicExternalLink(rImpGraphic
.maGraphicExternalLink
)
131 , maLastUsed (std::chrono::high_resolution_clock::now())
132 , mbPrepared (rImpGraphic
.mbPrepared
)
135 rImpGraphic
.mbDummyContext
= false;
138 ImpGraphic::ImpGraphic(std::shared_ptr
<GfxLink
> xGfxLink
, sal_Int32 nPageIndex
)
139 : mpGfxLink(std::move(xGfxLink
))
140 , meType(GraphicType::Bitmap
)
143 , mbDummyContext(false)
144 , maLastUsed (std::chrono::high_resolution_clock::now())
147 maSwapInfo
.mbIsTransparent
= true;
148 maSwapInfo
.mbIsAlpha
= true;
149 maSwapInfo
.mbIsEPS
= false;
150 maSwapInfo
.mbIsAnimated
= false;
151 maSwapInfo
.mnAnimationLoopCount
= 0;
152 maSwapInfo
.mnPageIndex
= nPageIndex
;
155 ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink
) :
156 meType ( GraphicType::Default
),
159 mbDummyContext ( false ),
160 maGraphicExternalLink(std::move(aGraphicExternalLink
)),
161 maLastUsed (std::chrono::high_resolution_clock::now()),
166 ImpGraphic::ImpGraphic( const BitmapEx
& rBitmapEx
) :
167 maBitmapEx ( rBitmapEx
),
168 meType ( !rBitmapEx
.IsEmpty() ? GraphicType::Bitmap
: GraphicType::NONE
),
171 mbDummyContext ( false ),
172 maLastUsed (std::chrono::high_resolution_clock::now()),
177 ImpGraphic::ImpGraphic(const std::shared_ptr
<VectorGraphicData
>& rVectorGraphicDataPtr
)
178 : meType( rVectorGraphicDataPtr
? GraphicType::Bitmap
: GraphicType::NONE
),
181 mbDummyContext ( false ),
182 maVectorGraphicData(rVectorGraphicDataPtr
),
183 maLastUsed (std::chrono::high_resolution_clock::now()),
188 ImpGraphic::ImpGraphic( const Animation
& rAnimation
) :
189 maBitmapEx ( rAnimation
.GetBitmapEx() ),
190 mpAnimation ( std::make_unique
<Animation
>( rAnimation
) ),
191 meType ( GraphicType::Bitmap
),
194 mbDummyContext ( false ),
195 maLastUsed (std::chrono::high_resolution_clock::now()),
200 ImpGraphic::ImpGraphic( const GDIMetaFile
& rMtf
) :
202 meType ( GraphicType::GdiMetafile
),
205 mbDummyContext ( false ),
206 maLastUsed (std::chrono::high_resolution_clock::now()),
211 ImpGraphic::~ImpGraphic()
213 vcl::graphic::Manager::get().unregisterGraphic(this);
216 ImpGraphic
& ImpGraphic::operator=( const ImpGraphic
& rImpGraphic
)
218 if( &rImpGraphic
!= this )
220 sal_Int64 aOldSizeBytes
= mnSizeBytes
;
222 maMetaFile
= rImpGraphic
.maMetaFile
;
223 meType
= rImpGraphic
.meType
;
224 mnSizeBytes
= rImpGraphic
.mnSizeBytes
;
226 maSwapInfo
= rImpGraphic
.maSwapInfo
;
227 mpContext
= rImpGraphic
.mpContext
;
228 mbDummyContext
= rImpGraphic
.mbDummyContext
;
229 maGraphicExternalLink
= rImpGraphic
.maGraphicExternalLink
;
233 if ( rImpGraphic
.mpAnimation
)
235 mpAnimation
= std::make_unique
<Animation
>( *rImpGraphic
.mpAnimation
);
236 maBitmapEx
= mpAnimation
->GetBitmapEx();
240 maBitmapEx
= rImpGraphic
.maBitmapEx
;
243 mbSwapOut
= rImpGraphic
.mbSwapOut
;
244 mpSwapFile
= rImpGraphic
.mpSwapFile
;
245 mbPrepared
= rImpGraphic
.mbPrepared
;
247 mpGfxLink
= rImpGraphic
.mpGfxLink
;
249 maVectorGraphicData
= rImpGraphic
.maVectorGraphicData
;
250 maLastUsed
= std::chrono::high_resolution_clock::now();
252 vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes
);
258 ImpGraphic
& ImpGraphic::operator=(ImpGraphic
&& rImpGraphic
)
260 sal_Int64 aOldSizeBytes
= mnSizeBytes
;
262 maMetaFile
= std::move(rImpGraphic
.maMetaFile
);
263 meType
= rImpGraphic
.meType
;
264 mnSizeBytes
= rImpGraphic
.mnSizeBytes
;
265 maSwapInfo
= std::move(rImpGraphic
.maSwapInfo
);
266 mpContext
= std::move(rImpGraphic
.mpContext
);
267 mbDummyContext
= rImpGraphic
.mbDummyContext
;
268 mpAnimation
= std::move(rImpGraphic
.mpAnimation
);
269 maBitmapEx
= std::move(rImpGraphic
.maBitmapEx
);
270 mbSwapOut
= rImpGraphic
.mbSwapOut
;
271 mpSwapFile
= std::move(rImpGraphic
.mpSwapFile
);
272 mpGfxLink
= std::move(rImpGraphic
.mpGfxLink
);
273 maVectorGraphicData
= std::move(rImpGraphic
.maVectorGraphicData
);
274 maGraphicExternalLink
= rImpGraphic
.maGraphicExternalLink
;
275 mbPrepared
= rImpGraphic
.mbPrepared
;
278 rImpGraphic
.mbDummyContext
= false;
279 maLastUsed
= std::chrono::high_resolution_clock::now();
281 vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes
);
286 bool ImpGraphic::operator==( const ImpGraphic
& rOther
) const
288 if( this == &rOther
)
291 if (mbPrepared
&& rOther
.mbPrepared
)
292 return (*mpGfxLink
== *rOther
.mpGfxLink
);
294 if (!isAvailable() || !rOther
.isAvailable())
297 if ( meType
!= rOther
.meType
)
303 case GraphicType::NONE
:
304 case GraphicType::Default
:
307 case GraphicType::GdiMetafile
:
308 return ( rOther
.maMetaFile
== maMetaFile
);
310 case GraphicType::Bitmap
:
312 if(maVectorGraphicData
)
314 if(maVectorGraphicData
== rOther
.maVectorGraphicData
)
319 else if(rOther
.maVectorGraphicData
)
322 bRet
= (*maVectorGraphicData
) == (*rOther
.maVectorGraphicData
);
325 else if( mpAnimation
)
327 if( rOther
.mpAnimation
&& ( *rOther
.mpAnimation
== *mpAnimation
) )
330 else if( !rOther
.mpAnimation
&& ( rOther
.maBitmapEx
== maBitmapEx
) )
341 const std::shared_ptr
<VectorGraphicData
>& ImpGraphic::getVectorGraphicData() const
345 return maVectorGraphicData
;
348 void ImpGraphic::createSwapInfo()
353 if (!maBitmapEx
.IsEmpty())
354 maSwapInfo
.maSizePixel
= maBitmapEx
.GetSizePixel();
356 maSwapInfo
.maSizePixel
= Size();
358 maSwapInfo
.maPrefMapMode
= getPrefMapMode();
359 maSwapInfo
.maPrefSize
= getPrefSize();
360 maSwapInfo
.mbIsAnimated
= isAnimated();
361 maSwapInfo
.mbIsEPS
= isEPS();
362 maSwapInfo
.mbIsTransparent
= isTransparent();
363 maSwapInfo
.mbIsAlpha
= isAlpha();
364 maSwapInfo
.mnAnimationLoopCount
= getAnimationLoopCount();
365 maSwapInfo
.mnPageIndex
= getPageNumber();
368 void ImpGraphic::clearGraphics()
373 maVectorGraphicData
.reset();
376 void ImpGraphic::setPrepared(bool bAnimated
, const Size
* pSizeHint
)
380 meType
= GraphicType::Bitmap
;
382 SvMemoryStream
aMemoryStream(const_cast<sal_uInt8
*>(mpGfxLink
->GetData()), mpGfxLink
->GetDataSize(), StreamMode::READ
| StreamMode::WRITE
);
386 maSwapInfo
.maPrefSize
= *pSizeHint
;
387 maSwapInfo
.maPrefMapMode
= MapMode(MapUnit::Map100thMM
);
390 GraphicDescriptor
aDescriptor(aMemoryStream
, nullptr);
391 if (aDescriptor
.Detect(true))
395 // If we have logic size, work with that, as later pixel -> logic
396 // conversion will work with the output device DPI, not the graphic
398 Size aLogSize
= aDescriptor
.GetSize_100TH_MM();
399 if (aDescriptor
.GetPreferredLogSize() && aDescriptor
.GetPreferredMapMode())
401 maSwapInfo
.maPrefSize
= *aDescriptor
.GetPreferredLogSize();
402 maSwapInfo
.maPrefMapMode
= *aDescriptor
.GetPreferredMapMode();
404 else if (aLogSize
.getWidth() && aLogSize
.getHeight())
406 maSwapInfo
.maPrefSize
= aLogSize
;
407 maSwapInfo
.maPrefMapMode
= MapMode(MapUnit::Map100thMM
);
411 maSwapInfo
.maPrefSize
= aDescriptor
.GetSizePixel();
412 maSwapInfo
.maPrefMapMode
= MapMode(MapUnit::MapPixel
);
416 maSwapInfo
.maSizePixel
= aDescriptor
.GetSizePixel();
417 maSwapInfo
.mbIsTransparent
= aDescriptor
.IsTransparent();
418 maSwapInfo
.mbIsAlpha
= aDescriptor
.IsAlpha();
420 maSwapInfo
.mbIsTransparent
= false;
421 maSwapInfo
.mbIsAlpha
= false;
424 maSwapInfo
.mnAnimationLoopCount
= 0;
425 maSwapInfo
.mbIsEPS
= false;
426 maSwapInfo
.mbIsAnimated
= bAnimated
;
428 if (maVectorGraphicData
)
429 maSwapInfo
.mnPageIndex
= maVectorGraphicData
->getPageIndex();
432 void ImpGraphic::clear()
440 meType
= GraphicType::NONE
;
441 sal_Int64 nOldSize
= mnSizeBytes
;
443 vcl::graphic::Manager::get().changeExisting(this, nOldSize
);
444 maGraphicExternalLink
.msURL
.clear();
447 void ImpGraphic::setDefaultType()
450 meType
= GraphicType::Default
;
453 bool ImpGraphic::isSupportedGraphic() const
455 return( meType
!= GraphicType::NONE
);
458 bool ImpGraphic::isTransparent() const
464 bRet
= maSwapInfo
.mbIsTransparent
;
466 else if (meType
== GraphicType::Bitmap
&& !maVectorGraphicData
)
468 bRet
= mpAnimation
? mpAnimation
->IsTransparent() : maBitmapEx
.IsAlpha();
474 bool ImpGraphic::isAlpha() const
480 bRet
= maSwapInfo
.mbIsAlpha
;
482 else if (maVectorGraphicData
)
486 else if (meType
== GraphicType::Bitmap
)
488 bRet
= (nullptr == mpAnimation
&& maBitmapEx
.IsAlpha());
494 bool ImpGraphic::isAnimated() const
496 return mbSwapOut
? maSwapInfo
.mbIsAnimated
: mpAnimation
!= nullptr;
499 bool ImpGraphic::isEPS() const
502 return maSwapInfo
.mbIsEPS
;
504 return( ( meType
== GraphicType::GdiMetafile
) &&
505 ( maMetaFile
.GetActionSize() > 0 ) &&
506 ( maMetaFile
.GetAction( 0 )->GetType() == MetaActionType::EPS
) );
509 bool ImpGraphic::isAvailable() const
511 return !mbPrepared
&& !mbSwapOut
;
514 bool ImpGraphic::makeAvailable()
516 return ensureAvailable();
519 BitmapEx
ImpGraphic::getVectorGraphicReplacement() const
521 BitmapEx aRet
= maVectorGraphicData
->getReplacement();
523 if (maExPrefSize
.getWidth() && maExPrefSize
.getHeight())
525 aRet
.SetPrefSize(maExPrefSize
);
531 Bitmap
ImpGraphic::getBitmap(const GraphicConversionParameters
& rParameters
) const
537 if( meType
== GraphicType::Bitmap
)
539 if(maVectorGraphicData
&& maBitmapEx
.IsEmpty())
541 // use maBitmapEx as local buffer for rendered svg
542 const_cast< ImpGraphic
* >(this)->maBitmapEx
= getVectorGraphicReplacement();
545 const BitmapEx
& rRetBmpEx
= ( mpAnimation
? mpAnimation
->GetBitmapEx() : maBitmapEx
);
547 aRetBmp
= rRetBmpEx
.GetBitmap( COL_WHITE
);
549 if(rParameters
.getSizePixel().Width() || rParameters
.getSizePixel().Height())
550 aRetBmp
.Scale(rParameters
.getSizePixel());
552 else if( ( meType
!= GraphicType::Default
) && isSupportedGraphic() )
554 if(maBitmapEx
.IsEmpty())
557 ScopedVclPtrInstance
< VirtualDevice
> aVDev
;
558 Size
aDrawSize(aVDev
->LogicToPixel(maMetaFile
.GetPrefSize(), maMetaFile
.GetPrefMapMode()));
560 if(rParameters
.getSizePixel().Width() && rParameters
.getSizePixel().Height())
562 // apply given size if exists
563 aDrawSize
= rParameters
.getSizePixel();
566 if(aDrawSize
.Width() && aDrawSize
.Height() && !rParameters
.getUnlimitedSize()
567 && (aDrawSize
.Width() > GRAPHIC_MTFTOBMP_MAXEXT
|| aDrawSize
.Height() > GRAPHIC_MTFTOBMP_MAXEXT
))
569 // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT
570 double fWH(static_cast<double>(aDrawSize
.Width()) / static_cast<double>(aDrawSize
.Height()));
574 aDrawSize
.setWidth(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT
* fWH
));
575 aDrawSize
.setHeight(GRAPHIC_MTFTOBMP_MAXEXT
);
579 aDrawSize
.setWidth(GRAPHIC_MTFTOBMP_MAXEXT
);
580 aDrawSize
.setHeight(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT
/ fWH
));
584 // calculate pixel size. Normally, it's the same as aDrawSize, but may
585 // need to be extended when hairlines are on the right or bottom edge
586 Size
aPixelSize(aDrawSize
);
588 if(GraphicType::GdiMetafile
== getType())
590 // tdf#126319 Removed correction based on hairline-at-the-extremes of
591 // the metafile. The task shows that this is no longer sufficient since
592 // less hairlines get used in general - what is good, but breaks that
593 // old fix. Anyways, hairlines are a left-over from non-AA times
594 // when it was not possible to paint lines taller than one pixel.
595 // This might need to be corrected further using primitives and
596 // the possibility to get better-quality ranges for correction. For
597 // now, always add that one pixel.
598 aPixelSize
.setWidth(aPixelSize
.getWidth() + 1);
599 aPixelSize
.setHeight(aPixelSize
.getHeight() + 1);
602 if(aVDev
->SetOutputSizePixel(aPixelSize
))
604 if(rParameters
.getAntiAliase())
606 aVDev
->SetAntialiasing(aVDev
->GetAntialiasing() | AntialiasingFlags::Enable
);
609 if(rParameters
.getSnapHorVerLines())
611 aVDev
->SetAntialiasing(aVDev
->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline
);
614 draw(*aVDev
, Point(), aDrawSize
);
616 // use maBitmapEx as local buffer for rendered metafile
617 const_cast< ImpGraphic
* >(this)->maBitmapEx
= aVDev
->GetBitmapEx( Point(), aVDev
->GetOutputSizePixel() );
621 aRetBmp
= maBitmapEx
.GetBitmap();
624 if( !aRetBmp
.IsEmpty() )
626 aRetBmp
.SetPrefMapMode(getPrefMapMode());
627 aRetBmp
.SetPrefSize(getPrefSize());
633 BitmapEx
ImpGraphic::getBitmapEx(const GraphicConversionParameters
& rParameters
) const
639 if( meType
== GraphicType::Bitmap
)
641 if(maVectorGraphicData
&& maBitmapEx
.IsEmpty())
643 // use maBitmapEx as local buffer for rendered svg
644 const_cast< ImpGraphic
* >(this)->maBitmapEx
= getVectorGraphicReplacement();
647 aRetBmpEx
= ( mpAnimation
? mpAnimation
->GetBitmapEx() : maBitmapEx
);
649 if(rParameters
.getSizePixel().Width() || rParameters
.getSizePixel().Height())
652 rParameters
.getSizePixel(),
656 else if( ( meType
!= GraphicType::Default
) && isSupportedGraphic() )
658 if(maBitmapEx
.IsEmpty())
660 const ImpGraphic
aMonoMask( maMetaFile
.GetMonochromeMtf( COL_BLACK
) );
662 // use maBitmapEx as local buffer for rendered metafile
663 const_cast< ImpGraphic
* >(this)->maBitmapEx
= BitmapEx(getBitmap(rParameters
), aMonoMask
.getBitmap(rParameters
));
666 aRetBmpEx
= maBitmapEx
;
672 Animation
ImpGraphic::getAnimation() const
674 Animation aAnimation
;
678 aAnimation
= *mpAnimation
;
683 const BitmapEx
& ImpGraphic::getBitmapExRef() const
689 const GDIMetaFile
& ImpGraphic::getGDIMetaFile() const
692 if (!maMetaFile
.GetActionSize()
693 && maVectorGraphicData
694 && (VectorGraphicDataType::Emf
== maVectorGraphicData
->getType()
695 || VectorGraphicDataType::Wmf
== maVectorGraphicData
->getType()))
697 // If we have a Emf/Wmf VectorGraphic object, we
698 // need a way to get the Metafile data out of the primitive
699 // representation. Use a strict virtual hook (MetafileAccessor)
700 // to access the MetafilePrimitive2D directly. Also see comments in
701 // XEmfParser about this.
702 const std::deque
< css::uno::Reference
< css::graphic::XPrimitive2D
> > aSequence(maVectorGraphicData
->getPrimitive2DSequence());
704 if (1 == aSequence
.size())
706 // try to cast to MetafileAccessor implementation
707 const css::uno::Reference
< css::graphic::XPrimitive2D
> xReference(aSequence
[0]);
708 auto pUnoPrimitive
= static_cast< const drawinglayer::primitive2d::UnoPrimitive2D
* >(xReference
.get());
711 const MetafileAccessor
* pMetafileAccessor
= dynamic_cast< const MetafileAccessor
* >(pUnoPrimitive
->getBasePrimitive2D().get());
713 if (pMetafileAccessor
)
715 // it is a MetafileAccessor implementation, get Metafile
716 pMetafileAccessor
->accessMetafile(const_cast< ImpGraphic
* >(this)->maMetaFile
);
722 if (GraphicType::Bitmap
== meType
&& !maMetaFile
.GetActionSize())
725 // Use the local maMetaFile as container for a metafile-representation
726 // of the bitmap graphic. This will be done only once, thus be buffered.
727 // I checked all usages of maMetaFile, it is only used when type is not
728 // GraphicType::Bitmap. In operator= it will get copied, thus buffering will
729 // survive copying (change this if not wanted)
730 ImpGraphic
* pThat
= const_cast< ImpGraphic
* >(this);
732 if(maVectorGraphicData
&& maBitmapEx
.IsEmpty())
734 // use maBitmapEx as local buffer for rendered svg
735 pThat
->maBitmapEx
= getVectorGraphicReplacement();
738 // #123983# directly create a metafile with the same PrefSize and PrefMapMode
739 // the bitmap has, this will be an always correct metafile
740 if(maBitmapEx
.IsAlpha())
742 pThat
->maMetaFile
.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx
.GetPrefSize(), maBitmapEx
));
746 pThat
->maMetaFile
.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx
.GetPrefSize(), maBitmapEx
.GetBitmap()));
749 pThat
->maMetaFile
.Stop();
750 pThat
->maMetaFile
.WindStart();
751 pThat
->maMetaFile
.SetPrefSize(maBitmapEx
.GetPrefSize());
752 pThat
->maMetaFile
.SetPrefMapMode(maBitmapEx
.GetPrefMapMode());
758 Size
ImpGraphic::getSizePixel() const
763 aSize
= maSwapInfo
.maSizePixel
;
765 aSize
= getBitmapEx(GraphicConversionParameters()).GetSizePixel();
770 Size
ImpGraphic::getPrefSize() const
776 aSize
= maSwapInfo
.maPrefSize
;
782 case GraphicType::Bitmap
:
784 if (maVectorGraphicData
&& maBitmapEx
.IsEmpty())
786 if (!maExPrefSize
.getWidth() || !maExPrefSize
.getHeight())
788 // svg not yet buffered in maBitmapEx, return size derived from range
789 const basegfx::B2DRange
& rRange
= maVectorGraphicData
->getRange();
792 // tdf#157680 scale down estimated size of embedded PDF
793 // For some unknown reason, the embedded PDF sizes
794 // are 20x larger than expected. This only occurs on
795 // macOS so possibly there is some special conversion
796 // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere
798 if (maVectorGraphicData
->getType() == VectorGraphicDataType::Pdf
)
799 aSize
= Size(basegfx::fround(rRange
.getWidth() / 20.0f
), basegfx::fround(rRange
.getHeight() / 20.0f
));
802 aSize
= Size(basegfx::fround(rRange
.getWidth()), basegfx::fround(rRange
.getHeight()));
806 aSize
= maExPrefSize
;
811 aSize
= maBitmapEx
.GetPrefSize();
813 if( !aSize
.Width() || !aSize
.Height() )
815 aSize
= maBitmapEx
.GetSizePixel();
821 case GraphicType::GdiMetafile
:
823 aSize
= maMetaFile
.GetPrefSize();
827 case GraphicType::NONE
:
828 case GraphicType::Default
:
836 void ImpGraphic::setValuesForPrefSize(const Size
& rPrefSize
)
840 case GraphicType::Bitmap
:
842 // used when importing a writer FlyFrame with SVG as graphic, added conversion
843 // to allow setting the PrefSize at the BitmapEx to hold it
844 if (maVectorGraphicData
&& maBitmapEx
.IsEmpty())
846 maExPrefSize
= rPrefSize
;
849 // #108077# Push through pref size to animation object,
850 // will be lost on copy otherwise
853 const_cast< BitmapEx
& >(mpAnimation
->GetBitmapEx()).SetPrefSize(rPrefSize
);
856 if (!maExPrefSize
.getWidth() || !maExPrefSize
.getHeight())
858 maBitmapEx
.SetPrefSize(rPrefSize
);
863 case GraphicType::GdiMetafile
:
865 if (isSupportedGraphic())
866 maMetaFile
.SetPrefSize(rPrefSize
);
870 case GraphicType::NONE
:
871 case GraphicType::Default
:
876 void ImpGraphic::setPrefSize(const Size
& rPrefSize
)
879 setValuesForPrefSize(rPrefSize
);
882 MapMode
ImpGraphic::getPrefMapMode() const
888 aMapMode
= maSwapInfo
.maPrefMapMode
;
894 case GraphicType::Bitmap
:
896 if (maVectorGraphicData
&& maBitmapEx
.IsEmpty())
898 // svg not yet buffered in maBitmapEx, return default PrefMapMode
899 aMapMode
= MapMode(MapUnit::Map100thMM
);
903 const Size
aSize(maBitmapEx
.GetPrefSize());
905 if (aSize
.Width() && aSize
.Height())
906 aMapMode
= maBitmapEx
.GetPrefMapMode();
911 case GraphicType::GdiMetafile
:
913 return maMetaFile
.GetPrefMapMode();
917 case GraphicType::NONE
:
918 case GraphicType::Default
:
926 void ImpGraphic::setValuesForPrefMapMod(const MapMode
& rPrefMapMode
)
930 case GraphicType::Bitmap
:
932 if (maVectorGraphicData
)
934 // ignore for Vector Graphic Data. If this is really used (except the grfcache)
935 // it can be extended by using maBitmapEx as buffer for getVectorGraphicReplacement()
939 // #108077# Push through pref mapmode to animation object,
940 // will be lost on copy otherwise
943 const_cast<BitmapEx
&>(mpAnimation
->GetBitmapEx()).SetPrefMapMode(rPrefMapMode
);
946 maBitmapEx
.SetPrefMapMode(rPrefMapMode
);
951 case GraphicType::GdiMetafile
:
953 maMetaFile
.SetPrefMapMode(rPrefMapMode
);
957 case GraphicType::NONE
:
958 case GraphicType::Default
:
963 void ImpGraphic::setPrefMapMode(const MapMode
& rPrefMapMode
)
966 setValuesForPrefMapMod(rPrefMapMode
);
969 sal_uLong
ImpGraphic::getSizeBytes() const
979 case GraphicType::Bitmap
:
981 if (maVectorGraphicData
)
983 std::pair
<VectorGraphicData::State
, size_t> aPair(maVectorGraphicData
->getSizeBytes());
984 if (VectorGraphicData::State::UNPARSED
== aPair
.first
)
986 return aPair
.second
; // don't cache it until Vector Graphic Data is parsed
988 mnSizeBytes
= aPair
.second
;
992 mnSizeBytes
= mpAnimation
? mpAnimation
->GetSizeBytes() : maBitmapEx
.GetSizeBytes();
997 case GraphicType::GdiMetafile
:
999 mnSizeBytes
= maMetaFile
.GetSizeBytes();
1003 case GraphicType::NONE
:
1004 case GraphicType::Default
:
1011 void ImpGraphic::draw(OutputDevice
& rOutDev
, const Point
& rDestPt
) const
1020 case GraphicType::Bitmap
:
1022 if (maVectorGraphicData
&& maBitmapEx
.IsEmpty())
1024 // use maBitmapEx as local buffer for rendered svg
1025 const_cast<ImpGraphic
*>(this)->maBitmapEx
= getVectorGraphicReplacement();
1030 mpAnimation
->Draw(rOutDev
, rDestPt
);
1034 maBitmapEx
.Draw(&rOutDev
, rDestPt
);
1039 case GraphicType::GdiMetafile
:
1041 draw(rOutDev
, rDestPt
, maMetaFile
.GetPrefSize());
1045 case GraphicType::Default
:
1046 case GraphicType::NONE
:
1051 void ImpGraphic::draw(OutputDevice
& rOutDev
,
1052 const Point
& rDestPt
, const Size
& rDestSize
) const
1061 case GraphicType::Bitmap
:
1063 if (maVectorGraphicData
&& maBitmapEx
.IsEmpty())
1065 // use maBitmapEx as local buffer for rendered svg
1066 const_cast<ImpGraphic
*>(this)->maBitmapEx
= getVectorGraphicReplacement();
1071 mpAnimation
->Draw(rOutDev
, rDestPt
, rDestSize
);
1075 maBitmapEx
.Draw(&rOutDev
, rDestPt
, rDestSize
);
1080 case GraphicType::GdiMetafile
:
1082 const_cast<ImpGraphic
*>(this)->maMetaFile
.WindStart();
1083 const_cast<ImpGraphic
*>(this)->maMetaFile
.Play(rOutDev
, rDestPt
, rDestSize
);
1084 const_cast<ImpGraphic
*>(this)->maMetaFile
.WindStart();
1088 case GraphicType::Default
:
1089 case GraphicType::NONE
:
1094 void ImpGraphic::startAnimation(OutputDevice
& rOutDev
, const Point
& rDestPt
,
1095 const Size
& rDestSize
, tools::Long nRendererId
,
1096 OutputDevice
* pFirstFrameOutDev
)
1100 if( isSupportedGraphic() && !isSwappedOut() && mpAnimation
)
1101 mpAnimation
->Start(rOutDev
, rDestPt
, rDestSize
, nRendererId
, pFirstFrameOutDev
);
1104 void ImpGraphic::stopAnimation( const OutputDevice
* pOutDev
, tools::Long nRendererId
)
1108 if( isSupportedGraphic() && !isSwappedOut() && mpAnimation
)
1109 mpAnimation
->Stop( pOutDev
, nRendererId
);
1112 void ImpGraphic::setAnimationNotifyHdl( const Link
<Animation
*,void>& rLink
)
1117 mpAnimation
->SetNotifyHdl( rLink
);
1120 Link
<Animation
*,void> ImpGraphic::getAnimationNotifyHdl() const
1122 Link
<Animation
*,void> aLink
;
1127 aLink
= mpAnimation
->GetNotifyHdl();
1132 sal_uInt32
ImpGraphic::getAnimationLoopCount() const
1135 return maSwapInfo
.mnAnimationLoopCount
;
1137 return mpAnimation
? mpAnimation
->GetLoopCount() : 0;
1140 void ImpGraphic::setContext( const std::shared_ptr
<GraphicReader
>& pReader
)
1142 mpContext
= pReader
;
1143 mbDummyContext
= false;
1146 bool ImpGraphic::swapInContent(SvStream
& rStream
)
1154 rStream
.ReadUInt32(nId
);
1157 if (SWAP_FORMAT_ID
!= nId
)
1159 SAL_WARN("vcl", "Incompatible swap file!");
1163 rStream
.ReadInt32(nType
);
1164 rStream
.ReadInt32(nLength
);
1166 meType
= static_cast<GraphicType
>(nType
);
1168 if (meType
== GraphicType::NONE
|| meType
== GraphicType::Default
)
1174 bRet
= swapInGraphic(rStream
);
1180 bool ImpGraphic::swapOutGraphic(SvStream
& rStream
)
1182 if (rStream
.GetError())
1189 rStream
.SetError(SVSTREAM_GENERALERROR
);
1195 case GraphicType::GdiMetafile
:
1197 if(!rStream
.GetError())
1199 SvmWriter
aWriter(rStream
);
1200 aWriter
.Write(maMetaFile
);
1205 case GraphicType::Bitmap
:
1207 if (maVectorGraphicData
)
1209 rStream
.WriteInt32(sal_Int32(GraphicContentType::Vector
));
1210 // stream out Vector Graphic defining data (length, byte array and evtl. path)
1211 // this is used e.g. in swapping out graphic data and in transporting it over UNO API
1212 // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
1213 // no problem to extend it; only used at runtime
1214 switch (maVectorGraphicData
->getType())
1216 case VectorGraphicDataType::Wmf
:
1218 rStream
.WriteUInt32(constWmfMagic
);
1221 case VectorGraphicDataType::Emf
:
1223 rStream
.WriteUInt32(constEmfMagic
);
1226 case VectorGraphicDataType::Svg
:
1228 rStream
.WriteUInt32(constSvgMagic
);
1231 case VectorGraphicDataType::Pdf
:
1233 rStream
.WriteUInt32(constPdfMagic
);
1238 rStream
.WriteUInt32(maVectorGraphicData
->getBinaryDataContainer().getSize());
1239 maVectorGraphicData
->getBinaryDataContainer().writeToStream(rStream
);
1241 else if (mpAnimation
)
1243 rStream
.WriteInt32(sal_Int32(GraphicContentType::Animation
));
1244 WriteAnimation(rStream
, *mpAnimation
);
1248 rStream
.WriteInt32(sal_Int32(GraphicContentType::Bitmap
));
1249 WriteDIBBitmapEx(maBitmapEx
, rStream
);
1254 case GraphicType::NONE
:
1255 case GraphicType::Default
:
1260 mpGfxLink
->getDataContainer().swapOut();
1265 bool ImpGraphic::swapOutContent(SvStream
& rStream
)
1271 if (meType
== GraphicType::NONE
|| meType
== GraphicType::Default
|| isSwappedOut())
1274 sal_uLong nDataFieldPos
;
1276 // Write the SWAP ID
1277 rStream
.WriteUInt32(SWAP_FORMAT_ID
);
1279 rStream
.WriteInt32(static_cast<sal_Int32
>(meType
));
1281 // data size is updated later
1282 nDataFieldPos
= rStream
.Tell();
1283 rStream
.WriteInt32(0);
1286 const sal_uInt64 nDataStart
= rStream
.Tell();
1288 swapOutGraphic(rStream
);
1290 if (!rStream
.GetError())
1292 // Write the written length th the header
1293 const sal_uInt64 nCurrentPosition
= rStream
.Tell();
1294 rStream
.Seek(nDataFieldPos
);
1295 rStream
.WriteInt32(nCurrentPosition
- nDataStart
);
1296 rStream
.Seek(nCurrentPosition
);
1303 bool ImpGraphic::swapOut()
1308 bool bResult
= false;
1310 sal_Int64 nByteSize
= getSizeBytes();
1312 // We have GfxLink so we have the source available
1313 if (mpGfxLink
&& mpGfxLink
->IsNative())
1319 // reset the swap file
1322 // mark as swapped out
1329 // Create a swap file
1330 auto pSwapFile
= o3tl::make_shared
<ImpSwapFile
>(getOriginURL());
1332 // Open a stream to write the swap file to
1334 SvStream
* pOutputStream
= pSwapFile
->getStream();
1340 pOutputStream
->SetVersion(SOFFICE_FILEFORMAT_50
);
1341 pOutputStream
->SetCompressMode(SvStreamCompressFlags::NATIVE
);
1342 pOutputStream
->SetBufferSize(GRAPHIC_STREAMBUFSIZE
);
1344 if (!pOutputStream
->GetError() && swapOutContent(*pOutputStream
))
1346 pOutputStream
->FlushBuffer();
1347 bResult
= !pOutputStream
->GetError();
1351 // Check if writing was successful
1354 // We have swapped out, so can clean memory and prepare swap info
1358 mpSwapFile
= std::move(pSwapFile
);
1365 // Signal to manager that we have swapped out
1366 vcl::graphic::Manager::get().swappedOut(this, nByteSize
);
1372 bool ImpGraphic::ensureAvailable() const
1374 auto pThis
= const_cast<ImpGraphic
*>(this);
1376 bool bResult
= true;
1379 bResult
= pThis
->swapIn();
1381 pThis
->maLastUsed
= std::chrono::high_resolution_clock::now();
1385 void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic
* pGraphic
)
1389 GraphicExternalLink aLink
= maGraphicExternalLink
;
1390 Size aPrefSize
= maSwapInfo
.maPrefSize
;
1391 MapMode aPrefMapMode
= maSwapInfo
.maPrefMapMode
;
1393 if (aPrefSize
.getWidth() && aPrefSize
.getHeight() && aPrefMapMode
== getPrefMapMode())
1395 // Use custom preferred size if it was set when the graphic was still unloaded.
1396 // Only set the size in case the unloaded and loaded unit matches.
1397 setPrefSize(aPrefSize
);
1399 maGraphicExternalLink
= aLink
;
1403 // Move over only graphic content
1404 mpAnimation
.reset();
1405 if (pGraphic
->mpAnimation
)
1407 mpAnimation
= std::make_unique
<Animation
>(*pGraphic
->mpAnimation
);
1408 maBitmapEx
= mpAnimation
->GetBitmapEx();
1412 maBitmapEx
= pGraphic
->maBitmapEx
;
1415 maMetaFile
= pGraphic
->maMetaFile
;
1416 maVectorGraphicData
= pGraphic
->maVectorGraphicData
;
1418 // Set to 0, to force recalculation
1422 restoreFromSwapInfo();
1428 void ImpGraphic::dumpState(rtl::OStringBuffer
&rState
)
1430 if (meType
== GraphicType::NONE
&& mnSizeBytes
== 0)
1431 return; // uninteresting.
1433 rState
.append("\n\t");
1436 rState
.append("swapped\t");
1438 rState
.append("loaded\t");
1440 rState
.append(static_cast<sal_Int32
>(meType
));
1441 rState
.append("\tsize:\t");
1442 rState
.append(static_cast<sal_Int64
>(mnSizeBytes
));
1443 rState
.append("\tgfxl:\t");
1444 rState
.append(static_cast<sal_Int64
>(mpGfxLink
? mpGfxLink
->getSizeBytes() : -1));
1445 rState
.append("\t");
1446 rState
.append(static_cast<sal_Int32
>(maSwapInfo
.maSizePixel
.Width()));
1448 rState
.append(static_cast<sal_Int32
>(maSwapInfo
.maSizePixel
.Height()));
1449 rState
.append("\t");
1450 rState
.append(static_cast<sal_Int32
>(maExPrefSize
.Width()));
1452 rState
.append(static_cast<sal_Int32
>(maExPrefSize
.Height()));
1455 void ImpGraphic::restoreFromSwapInfo()
1457 setValuesForPrefMapMod(maSwapInfo
.maPrefMapMode
);
1458 setValuesForPrefSize(maSwapInfo
.maPrefSize
);
1460 if (maVectorGraphicData
)
1462 maVectorGraphicData
->setPageIndex(maSwapInfo
.mnPageIndex
);
1469 std::optional
<VectorGraphicDataType
> lclConvertToVectorGraphicType(GfxLink
const & rLink
)
1471 switch(rLink
.GetType())
1473 case GfxLinkType::NativePdf
:
1474 return VectorGraphicDataType::Pdf
;
1476 case GfxLinkType::NativeWmf
:
1478 return VectorGraphicDataType::Emf
;
1480 return VectorGraphicDataType::Wmf
;
1482 case GfxLinkType::NativeSvg
:
1483 return VectorGraphicDataType::Svg
;
1488 return std::optional
<VectorGraphicDataType
>();
1493 bool ImpGraphic::swapIn()
1495 if (!isSwappedOut())
1498 bool bReturn
= false;
1503 if (!mpGfxLink
->LoadNative(aGraphic
))
1506 updateFromLoadedGraphic(aGraphic
.ImplGetImpGraphic());
1508 maLastUsed
= std::chrono::high_resolution_clock::now();
1511 else if (mpGfxLink
&& mpGfxLink
->IsNative())
1513 std::optional
<VectorGraphicDataType
> oType
= lclConvertToVectorGraphicType(*mpGfxLink
);
1516 maVectorGraphicData
= vcl::loadVectorGraphic(mpGfxLink
->getDataContainer(), *oType
);
1518 // Set to 0, to force recalculation
1522 restoreFromSwapInfo();
1529 if (!mpGfxLink
->LoadNative(aGraphic
))
1532 ImpGraphic
* pImpGraphic
= aGraphic
.ImplGetImpGraphic();
1533 if (meType
!= pImpGraphic
->meType
)
1536 updateFromLoadedGraphic(pImpGraphic
);
1539 maLastUsed
= std::chrono::high_resolution_clock::now();
1544 SvStream
* pStream
= nullptr;
1547 pStream
= mpSwapFile
->getStream();
1551 pStream
->SetVersion(SOFFICE_FILEFORMAT_50
);
1552 pStream
->SetCompressMode(SvStreamCompressFlags::NATIVE
);
1553 pStream
->SetBufferSize(GRAPHIC_STREAMBUFSIZE
);
1554 pStream
->Seek(STREAM_SEEK_TO_BEGIN
);
1556 bReturn
= swapInFromStream(*pStream
);
1558 restoreFromSwapInfo();
1560 setOriginURL(mpSwapFile
->getOriginURL());
1568 vcl::graphic::Manager::get().swappedIn(this, getSizeBytes());
1574 bool ImpGraphic::swapInFromStream(SvStream
& rStream
)
1578 if (rStream
.GetError())
1585 bRet
= swapInContent(rStream
);
1589 //throw away swapfile, etc.
1598 bool ImpGraphic::swapInGraphic(SvStream
& rStream
)
1600 bool bReturn
= false;
1602 if (rStream
.GetError())
1605 if (meType
== GraphicType::Bitmap
)
1607 sal_Int32 nContentType
= -1;
1608 rStream
.ReadInt32(nContentType
);
1609 if (nContentType
< 0)
1612 auto eContentType
= static_cast<GraphicContentType
>(nContentType
);
1614 switch (eContentType
)
1616 case GraphicContentType::Bitmap
:
1619 ReadDIBBitmapEx(aBitmapEx
, rStream
);
1620 if (!rStream
.GetError())
1622 maBitmapEx
= aBitmapEx
;
1628 case GraphicContentType::Animation
:
1630 auto pAnimation
= std::make_unique
<Animation
>();
1631 ReadAnimation(rStream
, *pAnimation
);
1632 if (!rStream
.GetError())
1634 mpAnimation
= std::move(pAnimation
);
1635 maBitmapEx
= mpAnimation
->GetBitmapEx();
1641 case GraphicContentType::Vector
:
1643 // try to stream in Svg defining data (length, byte array and evtl. path)
1644 // See below (operator<<) for more information
1646 rStream
.ReadUInt32(nMagic
);
1648 if (constSvgMagic
== nMagic
|| constWmfMagic
== nMagic
|| constEmfMagic
== nMagic
|| constPdfMagic
== nMagic
)
1650 sal_uInt32
nVectorGraphicDataSize(0);
1651 rStream
.ReadUInt32(nVectorGraphicDataSize
);
1653 if (nVectorGraphicDataSize
)
1655 BinaryDataContainer
aDataContainer(rStream
, nVectorGraphicDataSize
);
1657 if (rStream
.GetError())
1660 VectorGraphicDataType aDataType
;
1665 aDataType
= VectorGraphicDataType::Svg
;
1668 aDataType
= VectorGraphicDataType::Wmf
;
1671 aDataType
= VectorGraphicDataType::Emf
;
1674 aDataType
= VectorGraphicDataType::Pdf
;
1680 auto aVectorGraphicDataPtr
= std::make_shared
<VectorGraphicData
>(aDataContainer
, aDataType
);
1682 if (!rStream
.GetError())
1684 maVectorGraphicData
= aVectorGraphicDataPtr
;
1693 else if (meType
== GraphicType::GdiMetafile
)
1695 GDIMetaFile aMetaFile
;
1696 SvmReader
aReader(rStream
);
1697 aReader
.Read(aMetaFile
);
1698 if (!rStream
.GetError())
1700 maMetaFile
= aMetaFile
;
1707 void ImpGraphic::setGfxLink(const std::shared_ptr
<GfxLink
>& rGfxLink
)
1711 mpGfxLink
= rGfxLink
;
1714 const std::shared_ptr
<GfxLink
> & ImpGraphic::getSharedGfxLink() const
1719 GfxLink
ImpGraphic::getGfxLink() const
1723 return( mpGfxLink
? *mpGfxLink
: GfxLink() );
1726 bool ImpGraphic::isGfxLink() const
1728 return ( bool(mpGfxLink
) );
1731 BitmapChecksum
ImpGraphic::getChecksum() const
1733 if (mnChecksum
!= 0)
1740 case GraphicType::NONE
:
1741 case GraphicType::Default
:
1744 case GraphicType::Bitmap
:
1746 if (maVectorGraphicData
)
1747 mnChecksum
= maVectorGraphicData
->GetChecksum();
1748 else if (mpAnimation
)
1749 mnChecksum
= mpAnimation
->GetChecksum();
1751 mnChecksum
= maBitmapEx
.GetChecksum();
1755 case GraphicType::GdiMetafile
:
1757 mnChecksum
= SvmWriter::GetChecksum(maMetaFile
);
1764 sal_Int32
ImpGraphic::getPageNumber() const
1767 return maSwapInfo
.mnPageIndex
;
1769 if (maVectorGraphicData
)
1770 return maVectorGraphicData
->getPageIndex();
1773 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */