LibreOffice Module svx (master) 1
viewobjectcontact.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
33#include <svx/svdobj.hxx>
34#include <svx/svdomedia.hxx>
35#include <svx/svdmodel.hxx>
36#include <svx/svdpage.hxx>
37#include <svx/svdotext.hxx>
38#include <vcl/pdfwriter.hxx>
40
41using namespace com::sun::star;
42
43namespace {
44
45// animated extractor
46
47// Necessary to filter a sequence of animated primitives from
48// a sequence of primitives to find out if animated or not. The decision for
49// what to decompose is hard-coded and only done for knowingly animated primitives
50// to not decompose too deeply and unnecessarily. This implies that the list
51// which is view-specific needs to be expanded by hand when new animated objects
52// are added. This may eventually be changed to a dynamically configurable approach
53// if necessary.
54class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D
55{
56protected:
57 // the found animated primitives
59
60 // text animation allowed?
61 bool mbTextAnimationAllowed : 1;
62
63 // graphic animation allowed?
64 bool mbGraphicAnimationAllowed : 1;
65
66 // as tooling, the process() implementation takes over API handling and calls this
67 // virtual render method when the primitive implementation is BasePrimitive2D-based.
68 virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override;
69
70public:
71 AnimatedExtractingProcessor2D(
72 const drawinglayer::geometry::ViewInformation2D& rViewInformation,
73 bool bTextAnimationAllowed,
74 bool bGraphicAnimationAllowed);
75
76 // data access
77 const drawinglayer::primitive2d::Primitive2DContainer& getPrimitive2DSequence() const { return maPrimitive2DSequence; }
78 drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence() { return std::move(maPrimitive2DSequence); }
79};
80
81AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D(
82 const drawinglayer::geometry::ViewInformation2D& rViewInformation,
83 bool bTextAnimationAllowed,
84 bool bGraphicAnimationAllowed)
85: drawinglayer::processor2d::BaseProcessor2D(rViewInformation),
86 mbTextAnimationAllowed(bTextAnimationAllowed),
87 mbGraphicAnimationAllowed(bGraphicAnimationAllowed)
88{
89}
90
91void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate)
92{
93 // known implementation, access directly
94 switch(rCandidate.getPrimitive2DID())
95 {
96 // add and accept animated primitives directly, no need to decompose
100 {
102
103 if((rSwitchPrimitive.isTextAnimation() && mbTextAnimationAllowed)
104 || (rSwitchPrimitive.isGraphicAnimation() && mbGraphicAnimationAllowed))
105 {
107 maPrimitive2DSequence.push_back(xReference);
108 }
109 break;
110 }
111
112 // decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D
113 // which then produces the animation infos (all when used/needed)
116
117 // decompose SdrObjects with evtl. animated text
126
127 // #121194# With Graphic as Bitmap FillStyle, also check
128 // for primitives filled with animated graphics
132
133 // decompose evtl. animated text contained in MaskPrimitive2D
134 // or group primitives
137 {
138 process(rCandidate);
139 break;
140 }
141
142 default :
143 {
144 // nothing to do for the rest
145 break;
146 }
147 }
148}
149
150} // end of anonymous namespace
151
152namespace sdr::contact {
153
155: mrObjectContact(rObjectContact),
156 mrViewContact(rViewContact),
157 maGridOffset(0.0, 0.0),
158 mnActionChangedCount(0),
159 mbLazyInvalidate(false)
160{
161 // make the ViewContact remember me
163
164 // make the ObjectContact remember me
166}
167
169{
170 // if the object range is empty, then we have never had the primitive range change, so nothing to invalidate
171 if (!maObjectRange.isEmpty())
172 {
173 // invalidate in view
174 if(!getObjectRange().isEmpty())
175 {
177 }
178 }
179
180 // delete PrimitiveAnimation
181 mpPrimitiveAnimation.reset();
182
183 // take care of remembered ObjectContact. Remove from
184 // OC first. The VC removal (below) CAN trigger a StopGettingViewed()
185 // which (depending of its implementation) may destroy other OCs. This
186 // can trigger the deletion of the helper OC of a page visualising object
187 // which IS the OC of this object. Eventually StopGettingViewed() needs
188 // to get asynchron later
190
191 // take care of remembered ViewContact
193}
194
196{
198 {
200 basegfx::B2DRange aTempRange = GetViewContact().getRange(rViewInfo2D);
201 if (!aTempRange.isEmpty())
202 {
203 const_cast< ViewObjectContact* >(this)->maObjectRange = aTempRange;
204 }
205 else
206 {
207 // if range is not computed (new or LazyInvalidate objects), force it
208 const DisplayInfo aDisplayInfo;
210
211 if(!xSequence.empty())
212 {
213 const_cast< ViewObjectContact* >(this)->maObjectRange =
214 xSequence.getB2DRange(rViewInfo2D);
215 }
216 }
217 }
218
219 return maObjectRange;
220}
221
223{
224 // clear cached primitives
225 mxPrimitive2DSequence.clear();
227
229 return;
230
231 // set local flag
232 mbLazyInvalidate = true;
233
234 // force ObjectRange
236
237 if(!getObjectRange().isEmpty())
238 {
239 // invalidate current valid range
241
242 // reset gridOffset, it needs to be recalculated
243 if (GetObjectContact().supportsGridOffsets())
245 else
247 }
248
249 // register at OC for lazy invalidate
251}
252
254{
256 return;
257
258 // reset flag
259 mbLazyInvalidate = false;
260
261 // force ObjectRange
263
264 if(!getObjectRange().isEmpty())
265 {
266 // invalidate current valid range
268 }
269}
270
271// Take some action when new objects are inserted
273{
274 // force creation of the new VOC and trigger it's refresh, so it
275 // will take part in LazyInvalidate immediately
277
278 // forward action to ObjectContact
279 // const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact());
280 // GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange());
281}
282
284{
285 // remove old one
286 mpPrimitiveAnimation.reset();
287
288 // check for animated primitives
289 if(mxPrimitive2DSequence.empty())
290 return;
291
292 const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed());
293 const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed());
294
295 if(bTextAnimationAllowed || bGraphicAnimationAllowed)
296 {
297 AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(),
298 bTextAnimationAllowed, bGraphicAnimationAllowed);
299 aAnimatedExtractor.process(mxPrimitive2DSequence);
300
301 if(!aAnimatedExtractor.getPrimitive2DSequence().empty())
302 {
303 // derived primitiveList is animated, setup new PrimitiveAnimation
304 mpPrimitiveAnimation.reset( new sdr::animation::PrimitiveAnimation(*this, aAnimatedExtractor.extractPrimitive2DSequence()) );
305 }
306 }
307}
308
310{
311 // get the view-independent Primitive from the viewContact
314
315 if(!xRetval.empty())
316 {
317 // handle GluePoint
318 if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible())
319 {
320 const drawinglayer::primitive2d::Primitive2DContainer xGlue(GetViewContact().createGluePointPrimitive2DSequence());
321
322 if(!xGlue.empty())
323 {
324 xRetval.append(xGlue);
325 }
326 }
327
328 // handle ghosted
329 if(isPrimitiveGhosted(rDisplayInfo))
330 {
331 const basegfx::BColor aRGBWhite(1.0, 1.0, 1.0);
332 const basegfx::BColorModifierSharedPtr aBColorModifier =
333 std::make_shared<basegfx::BColorModifier_interpolate>(
334 aRGBWhite,
335 0.5);
338 std::move(xRetval),
339 aBColorModifier));
340
342 }
343 }
344
345 rVisitor.visit(xRetval);
346}
347
349{
351}
352
357{
358 SdrObject *const pSdrObj(mrViewContact.TryToGetSdrObject());
359
360 // Check if we need to embed to a StructureTagPrimitive2D, too. This
361 // was done at ImplRenderPaintProc::createRedirectedPrimitive2DSequence before
362 if (!rNewPrimitiveSequence.empty() && isExportPDFTags()
363 // ISO 14289-1:2014, Clause: 7.3
364 && (!pSdrObj || pSdrObj->getParentSdrObjectFromSdrObject() == nullptr))
365 {
366 if (nullptr != pSdrObj && !pSdrObj->IsDecorative())
367 {
369 const SdrInventor nInventor(pSdrObj->GetObjInventor());
370 const SdrObjKind nIdentifier(pSdrObj->GetObjIdentifier());
371 const bool bIsTextObj(nullptr != DynCastSdrTextObj(pSdrObj));
372
373 // Note: SwFlyDrawObj/SwVirtFlyDrawObj have SdrInventor::Swg - these
374 // are *not* handled here because not all of them are painted
375 // completely with primitives, so a tag here does not encapsulate them.
376 // The tag must be created by SwTaggedPDFHelper until this is fixed.
377 if ( nInventor == SdrInventor::Default )
378 {
379 if ( nIdentifier == SdrObjKind::Group )
380 eElement = vcl::PDFWriter::Figure;
381 else if (nIdentifier == SdrObjKind::Table)
382 eElement = vcl::PDFWriter::Table;
383 else if (nIdentifier == SdrObjKind::Media)
384 eElement = vcl::PDFWriter::Annot;
385 else if ( nIdentifier == SdrObjKind::TitleText )
386 eElement = vcl::PDFWriter::Heading;
387 else if ( nIdentifier == SdrObjKind::OutlineText )
388 eElement = vcl::PDFWriter::Division;
389 else if ( !bIsTextObj || !static_cast<const SdrTextObj&>(*pSdrObj).HasText() )
390 eElement = vcl::PDFWriter::Figure;
391 else
392 eElement = vcl::PDFWriter::Division;
393 }
394
396 {
397 SdrPage* pSdrPage(pSdrObj->getSdrPageFromSdrObject());
398
399 if(pSdrPage)
400 {
401 const bool bBackground(pSdrPage->IsMasterPage());
402 const bool bImage(SdrObjKind::Graphic == pSdrObj->GetObjIdentifier());
403 // note: there must be output device here, in PDF export
404 void const* pAnchorKey(nullptr);
405 if (auto const pUserCall = pSdrObj->GetUserCall())
406 {
407 pAnchorKey = pUserCall->GetPDFAnchorStructureElementKey(*pSdrObj);
408 }
409
410 ::std::vector<sal_Int32> annotIds;
411 if (eElement == vcl::PDFWriter::Annot
412 && !static_cast<SdrMediaObj*>(pSdrObj)->getURL().isEmpty())
413 {
414 auto const pPDFExtOutDevData(GetObjectContact().GetPDFExtOutDevData());
415 assert(pPDFExtOutDevData);
416 annotIds = pPDFExtOutDevData->GetScreenAnnotIds(pSdrObj);
417 }
418
421 eElement,
422 bBackground,
423 bImage,
424 std::move(rNewPrimitiveSequence),
425 pAnchorKey,
426 &annotIds));
427 rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer { xReference };
428 }
429 }
430 }
431 else
432 {
433 // page backgrounds etc should be tagged as artifacts:
436 // lies to force silly VclMetafileProcessor2D to emit NonStructElement
438 true,
439 true,
440 std::move(rNewPrimitiveSequence))
441 };
442 }
443 }
444}
445
447{
448 // only some of the top-level apps are any good at reliably invalidating us (e.g. writer is not)
450
451 if (nullptr != pSdrObj && pSdrObj->getSdrModelFromSdrObject().IsVOCInvalidationIsReliable())
452 {
453 if (!mxPrimitive2DSequence.empty())
455 }
456
457 // prepare new representation
459
460 // take care of redirectors and create new list
462
463 if(pRedirector)
464 {
465 pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo, xNewPrimitiveSequence);
466 }
467 else
468 {
469 createPrimitive2DSequence(rDisplayInfo, xNewPrimitiveSequence);
470 }
471
472 // check and eventually embed to GridOffset transform primitive (calc only)
473 if(!xNewPrimitiveSequence.empty() && GetObjectContact().supportsGridOffsets())
474 {
475 const basegfx::B2DVector& rGridOffset(getGridOffset());
476
477 if(0.0 != rGridOffset.getX() || 0.0 != rGridOffset.getY())
478 {
479 const basegfx::B2DHomMatrix aTranslateGridOffset(
481 rGridOffset));
484 aTranslateGridOffset,
485 std::move(xNewPrimitiveSequence)));
486 xNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer { aEmbed };
487 }
488 }
489
490 createStructureTag(xNewPrimitiveSequence);
491
492 // Local up-to-date checks. New list different from local one?
493 // This is the important point where it gets decided if the current or the new
494 // representation gets used. This is important for performance, since the
495 // current representation contains possible precious decompositions. That
496 // comparisons triggers exactly if something in the object visualization
497 // has changed.
498 // Note: That is the main reason for BasePrimitive2D::operator== at all. I
499 // have alternatively tried to invalidate the local representation on object
500 // change, but that is simply not reliable.
501 // Note2: I did that once in aw080, the lost CWS, and it worked well enough
502 // so that I could remove *all* operator== from all derivations of
503 // BasePrimitive2D, so it can be done again (with the needed resources)
504 if(mxPrimitive2DSequence != xNewPrimitiveSequence)
505 {
506 // has changed, copy content
507 const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = std::move(xNewPrimitiveSequence);
508
509 // check for animated stuff
510 const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations();
511
512 // always update object range when PrimitiveSequence changes
513 const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
514 const_cast< ViewObjectContact* >(this)->maObjectRange = mxPrimitive2DSequence.getB2DRange(rViewInformation2D);
515 }
516
517 // return current Primitive2DContainer
519}
520
521bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const
522{
523 // default: always visible
524 return true;
525}
526
528{
529 // default: standard check
530 return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive());
531}
532
534{
535 // check model-view visibility
536 if(!isPrimitiveVisible(rDisplayInfo))
537 return;
538
539 getPrimitive2DSequence(rDisplayInfo);
540 if(mxPrimitive2DSequence.empty())
541 return;
542
543 // get ranges
544 const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
545 // tdf#147164 cannot use maObjectRange here, it is unreliable
546 const basegfx::B2DRange aObjectRange(mxPrimitive2DSequence.getB2DRange(rViewInformation2D));
547 const basegfx::B2DRange& aViewRange(rViewInformation2D.getViewport());
548
549 // check geometrical visibility
550 bool bVisible = aViewRange.isEmpty() || aViewRange.overlaps(aObjectRange);
551 if(!bVisible)
552 return;
553
554 // temporarily take over the mxPrimitive2DSequence, in case it gets invalidated while we want to iterate over it
555 auto tmp = std::move(const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence);
556 int nPrevCount = mnActionChangedCount;
557
558 rVisitor.visit(tmp);
559
560 // if we received ActionChanged() calls while walking the primitives, then leave it empty, otherwise move it back
561 if (mnActionChangedCount == nPrevCount)
562 const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence = std::move(tmp);
563}
564
566{
567 const sal_uInt32 nSubHierarchyCount(GetViewContact().GetObjectCount());
568
569 for(sal_uInt32 a(0); a < nSubHierarchyCount; a++)
570 {
571 const ViewObjectContact& rCandidate(GetViewContact().GetViewContact(a).GetViewObjectContact(GetObjectContact()));
572
573 rCandidate.getPrimitive2DSequenceHierarchy(rDisplayInfo, rVisitor);
574 }
575}
576
577// Support getting a GridOffset per object and view for non-linear ViewToDevice
578// transformation (calc). On-demand created by delegating to the ObjectContact
579// (->View) that has then all needed information
581{
582 if(0.0 == maGridOffset.getX() && 0.0 == maGridOffset.getY() && GetObjectContact().supportsGridOffsets())
583 {
584 // create on-demand
586 }
587
588 return maGridOffset;
589}
590
592{
593 // reset buffered GridOffset itself
594 maGridOffset.setX(0.0);
595 maGridOffset.setY(0.0);
596
597 // also reset sequence to get a re-calculation when GridOffset changes
598 mxPrimitive2DSequence.clear();
600}
601
602}
603
604/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool IsVOCInvalidationIsReliable() const
Definition: svdmodel.hxx:614
Abstract DrawObject.
Definition: svdobj.hxx:260
SdrObjUserCall * GetUserCall() const
Definition: svdobj.hxx:837
virtual SdrInventor GetObjInventor() const
Definition: svdobj.cxx:621
SdrModel & getSdrModelFromSdrObject() const
Definition: svdobj.cxx:289
virtual SdrObjKind GetObjIdentifier() const
Definition: svdobj.cxx:626
SdrPage * getSdrPageFromSdrObject() const
Definition: svdobj.cxx:279
SdrObject * getParentSdrObjectFromSdrObject() const
Definition: svdobj.cxx:722
virtual bool IsDecorative() const
Definition: svdobj.cxx:900
A SdrPage contains exactly one SdrObjList and a description of the physical page dimensions (size / m...
Definition: svdpage.hxx:379
bool IsMasterPage() const
Definition: svdpage.hxx:464
virtual bool HasText() const override
Definition: svdotxat.cxx:420
bool isEmpty() const
bool overlaps(const Range2D &rRange) const
TYPE getX() const
void setY(TYPE fY)
TYPE getY() const
void setX(TYPE fX)
const basegfx::B2DRange & getViewport() const
virtual sal_uInt32 getPrimitive2DID() const=0
basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D &aViewInformation) const
void append(const Primitive2DReference &)
virtual void visit(const Primitive2DReference &)=0
virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D &rCandidate)
bool IsGhostedDrawModeActive() const
Definition: displayinfo.hxx:82
virtual void InvalidatePartOfView(const basegfx::B2DRange &rRange) const
void RemoveViewObjectContact(ViewObjectContact &rVOContact)
virtual void calculateGridOffsetForViewOjectContact(basegfx::B2DVector &rTarget, const ViewObjectContact &rClient) const
virtual bool isExportTaggedPDF() const
virtual void setLazyInvalidate(ViewObjectContact &rVOC)
const drawinglayer::geometry::ViewInformation2D & getViewInformation2D() const
ViewObjectContactRedirector * GetViewObjectContactRedirector() const
void AddViewObjectContact(ViewObjectContact &rVOContact)
virtual SdrObject * TryToGetSdrObject() const
void RemoveViewObjectContact(ViewObjectContact &rVOContact)
Definition: viewcontact.cxx:99
void AddViewObjectContact(ViewObjectContact &rVOContact)
Definition: viewcontact.cxx:93
ViewObjectContact & GetViewObjectContact(ObjectContact &rObjectContact)
Definition: viewcontact.cxx:65
virtual basegfx::B2DRange getRange(const drawinglayer::geometry::ViewInformation2D &rViewInfo2D) const
void getViewIndependentPrimitive2DContainer(drawinglayer::primitive2d::Primitive2DDecompositionVisitor &rVisitor) const
virtual void createRedirectedPrimitive2DSequence(const sdr::contact::ViewObjectContact &rOriginal, const sdr::contact::DisplayInfo &rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor &rVisitor)
const basegfx::B2DVector & getGridOffset() const
drawinglayer::primitive2d::Primitive2DContainer const & getPrimitive2DSequence(const DisplayInfo &rDisplayInfo) const
ViewContact & GetViewContact() const
ViewObjectContact(ObjectContact &rObjectContact, ViewContact &rViewContact)
virtual bool isPrimitiveVisible(const DisplayInfo &rDisplayInfo) const
drawinglayer::primitive2d::Primitive2DContainer mxPrimitive2DSequence
virtual bool isPrimitiveGhosted(const DisplayInfo &rDisplayInfo) const
ObjectContact & GetObjectContact() const
virtual bool isExportPDFTags() const
check if getPrimitive2DSequence() should create tag
void ActionChildInserted(ViewContact &rChild)
virtual void createPrimitive2DSequence(const DisplayInfo &rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor &rVisitor) const
void getPrimitive2DSequenceSubHierarchy(DisplayInfo &rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor &rVisitor) const
std::unique_ptr< sdr::animation::PrimitiveAnimation > mpPrimitiveAnimation
virtual void getPrimitive2DSequenceHierarchy(DisplayInfo &rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor &rVisitor) const
void createStructureTag(drawinglayer::primitive2d::Primitive2DContainer &rNewPrimitiveSequence) const
Check if we need to embed to a StructureTagPrimitive2D, too.
const basegfx::B2DRange & getObjectRange() const
#define PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D
#define PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D
#define PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D
#define PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
#define PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D
#define PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D
#define PRIMITIVE2D_ID_GROUPPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D
#define PRIMITIVE2D_ID_MASKPRIMITIVE2D
uno_Any a
B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
std::shared_ptr< BColorModifier > BColorModifierSharedPtr
SdrTextObj * DynCastSdrTextObj(SdrObject *pObj)
Definition: svdobj.cxx:3212
SdrInventor
Definition: svdobj.hxx:98
SdrObjKind
Definition: svdobjkind.hxx:25
@ Group
abstract object (SdrObject)
@ Media
custom shape
@ Table
media shape
@ Graphic
OutlineText, special text object for StarDraw.
@ OutlineText
TitleText, special text object for StarDraw.
@ TitleText
text object
#define PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D
#define PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D
bool bVisible