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 .
21 #include <tools/diagnose_ex.h>
22 #include <com/sun/star/rendering/RenderState.hpp>
23 #include <com/sun/star/rendering/XCanvas.hpp>
24 #include <basegfx/numeric/ftools.hxx>
25 #include <basegfx/utils/canvastools.hxx>
26 #include <basegfx/polygon/b2dpolygontools.hxx>
27 #include <basegfx/polygon/b2dpolygon.hxx>
28 #include <basegfx/range/b2drectangle.hxx>
29 #include <basegfx/vector/b2dvector.hxx>
30 #include <canvas/canvastools.hxx>
31 #include <vcl/canvastools.hxx>
32 #include <vcl/gdimtf.hxx>
33 #include <vcl/metaact.hxx>
34 #include <vcl/virdev.hxx>
35 #include <vcl/metric.hxx>
36 #include <tools/poly.hxx>
37 #include "mtftools.hxx"
38 #include <outdevstate.hxx>
39 #include "polypolyaction.hxx"
40 #include <basegfx/matrix/b2dhommatrixtools.hxx>
43 using namespace ::com::sun::star
;
49 void initRenderState( rendering::RenderState
& renderState
,
50 const ::cppcanvas::internal::OutDevState
& outdevState
)
52 ::canvas::tools::initRenderState( renderState
);
53 ::canvas::tools::setRenderStateTransform( renderState
,
54 outdevState
.transform
);
55 renderState
.Clip
= outdevState
.xClipPoly
;
58 ::Size
getBaselineOffset( const ::cppcanvas::internal::OutDevState
& outdevState
,
59 const VirtualDevice
& rVDev
)
61 const ::FontMetric
& aMetric
= rVDev
.GetFontMetric();
63 // calc offset for text output, the XCanvas always renders
65 switch( outdevState
.textReferencePoint
)
69 aMetric
.GetInternalLeading() + aMetric
.GetAscent() );
72 ENSURE_OR_THROW( false,
73 "tools::getBaselineOffset(): Unexpected TextAlign value" );
74 // FALLTHROUGH intended (to calm compiler warning - case won't happen)
76 return ::Size( 0, 0 );
80 -aMetric
.GetDescent() );
85 ::basegfx::B2DHomMatrix
& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
86 const VirtualDevice
& rVDev
)
88 // select size value in the middle of the available range,
89 // to have headroom both when map mode scales up, and when
91 const ::Size
aSizeLogic( 0x00010000L
,
94 const ::Size
aSizePixel( rVDev
.LogicToPixel( aSizeLogic
) );
96 o_rMatrix
= basegfx::utils::createScaleB2DHomMatrix(
97 aSizePixel
.Width() / static_cast<double>(aSizeLogic
.Width()),
98 aSizePixel
.Height() / static_cast<double>(aSizeLogic
.Height()) );
103 ::basegfx::B2DHomMatrix
& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
104 const VirtualDevice
& rVDev
)
107 calcLogic2PixelLinearTransform(o_rMatrix
, rVDev
);
109 // translate according to curr map mode/pref map mode offset
110 const ::Point aEmptyPoint
;
111 const ::Point
& rTranslatedPoint(
112 rVDev
.LogicToPixel( aEmptyPoint
));
114 o_rMatrix
.translate(rTranslatedPoint
.X(),
115 rTranslatedPoint
.Y());
120 bool modifyClip( rendering::RenderState
& o_rRenderState
,
121 const struct ::cppcanvas::internal::OutDevState
& rOutdevState
,
122 const CanvasSharedPtr
& rCanvas
,
123 const ::basegfx::B2DPoint
& rOffset
,
124 const ::basegfx::B2DVector
* pScaling
,
125 const double* pRotation
)
127 const bool bOffsetting( !rOffset
.equalZero() );
128 const bool bScaling( pScaling
&&
129 !rtl::math::approxEqual(pScaling
->getX(), 1.0) &&
130 !rtl::math::approxEqual(pScaling
->getY(), 1.0) );
131 const bool bRotation( pRotation
&&
134 if( !bOffsetting
&& !bScaling
&& !bRotation
)
135 return false; // nothing to do
137 if( rOutdevState
.clip
.count() )
139 // general polygon case
141 ::basegfx::B2DPolyPolygon
aLocalClip( rOutdevState
.clip
);
142 ::basegfx::B2DHomMatrix aTransform
;
145 aTransform
.translate( -rOffset
.getX(),
148 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
151 aTransform
.rotate( - *pRotation
);
153 aLocalClip
.transform( aTransform
);
155 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
156 rCanvas
->getUNOCanvas()->getDevice(),
161 else if( !rOutdevState
.clipRect
.IsEmpty() )
165 const ::tools::Rectangle
aLocalClipRect( rOutdevState
.clipRect
);
169 // rotation involved - convert to polygon first,
170 // then transform that
171 ::basegfx::B2DPolygon
aLocalClip(
172 ::basegfx::utils::createPolygonFromRect(
173 vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect
) ) );
174 ::basegfx::B2DHomMatrix aTransform
;
177 aTransform
.translate( -rOffset
.getX(),
180 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
182 aTransform
.rotate( - *pRotation
);
184 aLocalClip
.transform( aTransform
);
186 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
187 rCanvas
->getUNOCanvas()->getDevice(),
188 ::basegfx::B2DPolyPolygon( aLocalClip
) );
192 // scale and offset - do it on the fly, have to
193 // convert to float anyway.
194 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
195 rCanvas
->getUNOCanvas()->getDevice(),
196 ::basegfx::B2DPolyPolygon(
197 ::basegfx::utils::createPolygonFromRect(
198 ::basegfx::B2DRectangle(
199 (aLocalClipRect
.Left() - rOffset
.getX())/pScaling
->getX(),
200 (aLocalClipRect
.Top() - rOffset
.getY())/pScaling
->getY(),
201 (aLocalClipRect
.Right() - rOffset
.getX())/pScaling
->getX(),
202 (aLocalClipRect
.Bottom() - rOffset
.getY())/pScaling
->getY() ) ) ) );
206 // offset only - do it on the fly, have to convert
208 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
209 rCanvas
->getUNOCanvas()->getDevice(),
210 ::basegfx::B2DPolyPolygon(
211 ::basegfx::utils::createPolygonFromRect(
212 ::basegfx::B2DRectangle( aLocalClipRect
.Left() - rOffset
.getX(),
213 aLocalClipRect
.Top() - rOffset
.getY(),
214 aLocalClipRect
.Right() - rOffset
.getX(),
215 aLocalClipRect
.Bottom() - rOffset
.getY() ) ) ) );
221 // empty clip, nothing to do
225 // create overline/underline/strikeout line info struct
226 TextLineInfo
createTextLineInfo( const ::VirtualDevice
& rVDev
,
227 const ::cppcanvas::internal::OutDevState
& rState
)
229 const bool bOldMode( rVDev
.IsMapModeEnabled() );
231 // #i68512# Force metric regeneration with mapmode enabled
232 // (prolly OutDev bug)
233 rVDev
.GetFontMetric();
235 // will restore map mode below
236 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( false );
238 const ::FontMetric aMetric
= rVDev
.GetFontMetric();
240 TextLineInfo
aTextInfo(
241 (aMetric
.GetDescent() + 2) / 4.0,
242 ((aMetric
.GetInternalLeading() + 1.5) / 3.0),
243 (aMetric
.GetInternalLeading() / 2.0) - aMetric
.GetAscent(),
244 aMetric
.GetDescent() / 2.0,
245 (aMetric
.GetInternalLeading() - aMetric
.GetAscent()) / 3.0,
246 rState
.textOverlineStyle
,
247 rState
.textUnderlineStyle
,
248 rState
.textStrikeoutStyle
);
250 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( bOldMode
);
257 void appendWaveline( ::basegfx::B2DPolyPolygon
& o_rPoly
,
258 const ::basegfx::B2DPoint
& rStartPos
,
259 const double nStartOffset
,
261 const double nHeight
,
264 const double x(rStartPos
.getX());
265 const double y(rStartPos
.getY() + nStartOffset
+ nHeight
);
266 double nWaveWidth
= nHeight
* 10.6 * 0.25;
267 // Offset for the double line.
268 double nOffset
= 0.0;
270 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
271 nOffset
= -nHeight
* 0.5;
275 basegfx::B2DPolygon aLine
;
276 aLine
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
277 aLine
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
279 o_rPoly
.append(::basegfx::utils::createWaveline(aLine
, nWaveWidth
, nWaveWidth
* 0.5));
281 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
283 nOffset
= nHeight
* 1.2;
285 basegfx::B2DPolygon aLine2
;
286 aLine2
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
287 aLine2
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
288 o_rPoly
.append(::basegfx::utils::createWaveline(aLine2
, nWaveWidth
, nWaveWidth
* 0.5));
292 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
293 const ::basegfx::B2DPoint
& rStartPos
,
299 const double x( rStartPos
.getX() );
300 const double y( rStartPos
.getY() );
303 ::basegfx::utils::createPolygonFromRect(
304 ::basegfx::B2DRectangle( x
+ nX1
, y
+ nY1
, x
+ nX2
, y
+ nY2
) ) );
307 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
314 ::basegfx::utils::createPolygonFromRect(
315 ::basegfx::B2DRectangle( nX1
, nY1
, nX2
, nY2
) ) );
318 bool appendDashes( ::basegfx::B2DPolyPolygon
& o_rPoly
,
321 const double nLineWidth
,
326 static const int aDottedArray
[] = { 1, 1, 0}; // DOTTED LINE
327 static const int aDotDashArray
[] = { 1, 1, 4, 1, 0}; // DASHDOT
328 static const int aDashDotDotArray
[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT
329 static const int aDashedArray
[] = { 5, 2, 0}; // DASHED LINE
330 static const int aLongDashArray
[] = { 7, 2, 0}; // LONGDASH
331 const int *pArray
= nullptr;
332 bool bIsBold
= false;
336 case LINESTYLE_BOLDDOTTED
:
339 case LINESTYLE_DOTTED
:
340 pArray
= aDottedArray
;
343 case LINESTYLE_BOLDDASH
:
347 pArray
= aDashedArray
;
350 case LINESTYLE_BOLDLONGDASH
:
353 case LINESTYLE_LONGDASH
:
354 pArray
= aLongDashArray
;
357 case LINESTYLE_BOLDDASHDOT
:
360 case LINESTYLE_DASHDOT
:
361 pArray
= aDotDashArray
;
363 case LINESTYLE_BOLDDASHDOTDOT
:
366 case LINESTYLE_DASHDOTDOT
:
367 pArray
= aDashDotDotArray
;
382 const double nEnd
= nX
+ nLineWidth
;
383 sal_Int32 nIndex
= 0;
389 if (pArray
[nIndex
] == 0)
392 const double nX2
= std::min(nEnd
, nX1
+ pArray
[nIndex
] * nLineHeight
);
395 appendRect(o_rPoly
, nX1
, nY
, nX2
, nY
+ nLineHeight
);
406 // create line actions for text such as underline and
408 void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
409 const ::basegfx::B2DPoint
& rStartPos
,
410 const double& rLineWidth
,
411 const TextLineInfo
& rTextLineInfo
)
413 switch( rTextLineInfo
.mnOverlineStyle
)
415 case LINESTYLE_NONE
: // nothing to do
416 case LINESTYLE_DONTKNOW
:
419 case LINESTYLE_DOUBLEWAVE
:
420 case LINESTYLE_SMALLWAVE
:
421 case LINESTYLE_BOLDWAVE
:
426 rTextLineInfo
.mnOverlineOffset
,
428 rTextLineInfo
.mnOverlineHeight
,
429 rTextLineInfo
.mnOverlineStyle
);
432 case LINESTYLE_SINGLE
:
437 rTextLineInfo
.mnOverlineOffset
,
439 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
446 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
,
448 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
451 case LINESTYLE_DOUBLE
:
456 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
* 2.0 ,
458 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
);
464 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
,
466 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
* 2.0 );
473 rStartPos
.getY() + rTextLineInfo
.mnOverlineOffset
,
475 rTextLineInfo
.mnOverlineHeight
,
476 rTextLineInfo
.mnOverlineStyle
,
479 ENSURE_OR_THROW( false,
480 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" );
485 void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
486 const ::basegfx::B2DPoint
& rStartPos
,
487 const double& rLineWidth
,
488 const TextLineInfo
& rTextLineInfo
)
491 switch( rTextLineInfo
.mnUnderlineStyle
)
493 case LINESTYLE_NONE
: // nothing to do
494 case LINESTYLE_DONTKNOW
:
497 case LINESTYLE_DOUBLEWAVE
:
498 case LINESTYLE_SMALLWAVE
:
499 case LINESTYLE_BOLDWAVE
:
504 rTextLineInfo
.mnUnderlineOffset
,
506 rTextLineInfo
.mnLineHeight
,
507 rTextLineInfo
.mnUnderlineStyle
);
509 case LINESTYLE_SINGLE
:
514 rTextLineInfo
.mnUnderlineOffset
,
516 rTextLineInfo
.mnUnderlineOffset
+ rTextLineInfo
.mnLineHeight
);
524 rTextLineInfo
.mnUnderlineOffset
,
526 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
);
529 case LINESTYLE_DOUBLE
:
534 rTextLineInfo
.mnUnderlineOffset
- rTextLineInfo
.mnLineHeight
,
536 rTextLineInfo
.mnUnderlineOffset
);
542 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
,
544 rTextLineInfo
.mnUnderlineOffset
+ 3*rTextLineInfo
.mnLineHeight
);
551 rStartPos
.getY() + rTextLineInfo
.mnUnderlineOffset
,
553 rTextLineInfo
.mnLineHeight
,
554 rTextLineInfo
.mnUnderlineStyle
,
557 ENSURE_OR_THROW( false,
558 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" );
563 void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
564 const ::basegfx::B2DPoint
& rStartPos
,
565 const double& rLineWidth
,
566 const TextLineInfo
& rTextLineInfo
)
568 switch( rTextLineInfo
.mnStrikeoutStyle
)
570 case STRIKEOUT_NONE
: // nothing to do
571 case STRIKEOUT_DONTKNOW
:
574 case STRIKEOUT_SLASH
: // TODO(Q1): we should handle this in the text layer
578 case STRIKEOUT_SINGLE
:
583 rTextLineInfo
.mnStrikeoutOffset
,
585 rTextLineInfo
.mnStrikeoutOffset
+ rTextLineInfo
.mnLineHeight
);
593 rTextLineInfo
.mnStrikeoutOffset
,
595 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
);
598 case STRIKEOUT_DOUBLE
:
603 rTextLineInfo
.mnStrikeoutOffset
- rTextLineInfo
.mnLineHeight
,
605 rTextLineInfo
.mnStrikeoutOffset
);
611 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
,
613 rTextLineInfo
.mnStrikeoutOffset
+ 3*rTextLineInfo
.mnLineHeight
);
617 ENSURE_OR_THROW( false,
618 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" );
623 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const ::basegfx::B2DPoint
& rStartPos
,
624 const double& rLineWidth
,
625 const TextLineInfo
& rTextLineInfo
)
627 // fill the polypolygon with all text lines
628 ::basegfx::B2DPolyPolygon aTextLinesPolyPoly
;
630 createOverlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
631 createUnderlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
632 createStrikeoutPolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
633 return aTextLinesPolyPoly
;
636 ::basegfx::B2DRange
calcDevicePixelBounds( const ::basegfx::B2DRange
& rBounds
,
637 const rendering::ViewState
& viewState
,
638 const rendering::RenderState
& renderState
)
640 ::basegfx::B2DHomMatrix aTransform
;
641 ::canvas::tools::mergeViewAndRenderTransform( aTransform
,
645 ::basegfx::B2DRange aTransformedBounds
;
646 return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds
,
651 // create line actions for text such as underline and
653 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const double& rStartOffset
,
654 const double& rLineWidth
,
655 const TextLineInfo
& rTextLineInfo
)
657 return createTextLinesPolyPolygon(
658 ::basegfx::B2DPoint( rStartOffset
,
664 void createTextLinesPolyPolygon( const double& rStartOffset
,
665 const double& rLineWidth
,
666 const TextLineInfo
& rTextLineInfo
,
667 ::basegfx::B2DPolyPolygon
& rOverlinePolyPoly
,
668 ::basegfx::B2DPolyPolygon
& rUnderlinePolyPoly
,
669 ::basegfx::B2DPolyPolygon
& rStrikeoutPolyPoly
)
671 ::basegfx::B2DPoint
aStartPos(rStartOffset
, 0.0);
673 createOverlinePolyPolygon(rOverlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
674 createUnderlinePolyPolygon(rUnderlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
675 createStrikeoutPolyPolygon(rStrikeoutPolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
680 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */