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/XCanvas.hpp>
23 #include <basegfx/utils/canvastools.hxx>
24 #include <basegfx/polygon/b2dpolygontools.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/range/b2drectangle.hxx>
27 #include <basegfx/vector/b2dvector.hxx>
28 #include <canvas/canvastools.hxx>
29 #include <rtl/math.hxx>
30 #include <vcl/canvastools.hxx>
31 #include <vcl/virdev.hxx>
32 #include <vcl/metric.hxx>
33 #include "mtftools.hxx"
34 #include <outdevstate.hxx>
35 #include <basegfx/matrix/b2dhommatrixtools.hxx>
38 using namespace ::com::sun::star
;
40 namespace cppcanvas::tools
42 void initRenderState( rendering::RenderState
& renderState
,
43 const ::cppcanvas::internal::OutDevState
& outdevState
)
45 ::canvas::tools::initRenderState( renderState
);
46 ::canvas::tools::setRenderStateTransform( renderState
,
47 outdevState
.transform
);
48 renderState
.Clip
= outdevState
.xClipPoly
;
51 ::Size
getBaselineOffset( const ::cppcanvas::internal::OutDevState
& outdevState
,
52 const VirtualDevice
& rVDev
)
54 const ::FontMetric
& aMetric
= rVDev
.GetFontMetric();
56 // calc offset for text output, the XCanvas always renders
58 switch( outdevState
.textReferencePoint
)
62 aMetric
.GetInternalLeading() + aMetric
.GetAscent() );
65 ENSURE_OR_THROW( false,
66 "tools::getBaselineOffset(): Unexpected TextAlign value" );
67 // FALLTHROUGH intended (to calm compiler warning - case won't happen)
69 return ::Size( 0, 0 );
73 -aMetric
.GetDescent() );
78 ::basegfx::B2DHomMatrix
& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
79 const VirtualDevice
& rVDev
)
81 // select size value in the middle of the available range,
82 // to have headroom both when map mode scales up, and when
84 const ::Size
aSizeLogic( 0x00010000L
,
87 const ::Size
aSizePixel( rVDev
.LogicToPixel( aSizeLogic
) );
89 o_rMatrix
= basegfx::utils::createScaleB2DHomMatrix(
90 aSizePixel
.Width() / static_cast<double>(aSizeLogic
.Width()),
91 aSizePixel
.Height() / static_cast<double>(aSizeLogic
.Height()) );
96 ::basegfx::B2DHomMatrix
& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
97 const VirtualDevice
& rVDev
)
100 calcLogic2PixelLinearTransform(o_rMatrix
, rVDev
);
102 // translate according to curr map mode/pref map mode offset
103 const ::Point aEmptyPoint
;
104 const ::Point
& rTranslatedPoint(
105 rVDev
.LogicToPixel( aEmptyPoint
));
107 o_rMatrix
.translate(rTranslatedPoint
.X(),
108 rTranslatedPoint
.Y());
113 bool modifyClip( rendering::RenderState
& o_rRenderState
,
114 const struct ::cppcanvas::internal::OutDevState
& rOutdevState
,
115 const CanvasSharedPtr
& rCanvas
,
116 const ::basegfx::B2DPoint
& rOffset
,
117 const ::basegfx::B2DVector
* pScaling
,
118 const double* pRotation
)
120 const bool bOffsetting( !rOffset
.equalZero() );
121 const bool bScaling( pScaling
&&
122 !rtl::math::approxEqual(pScaling
->getX(), 1.0) &&
123 !rtl::math::approxEqual(pScaling
->getY(), 1.0) );
124 const bool bRotation( pRotation
&&
127 if( !bOffsetting
&& !bScaling
&& !bRotation
)
128 return false; // nothing to do
130 if( rOutdevState
.clip
.count() )
132 // general polygon case
134 ::basegfx::B2DPolyPolygon
aLocalClip( rOutdevState
.clip
);
135 ::basegfx::B2DHomMatrix aTransform
;
138 aTransform
.translate( -rOffset
.getX(),
141 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
144 aTransform
.rotate( - *pRotation
);
146 aLocalClip
.transform( aTransform
);
148 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
149 rCanvas
->getUNOCanvas()->getDevice(),
154 else if( !rOutdevState
.clipRect
.IsEmpty() )
158 const ::tools::Rectangle
aLocalClipRect( rOutdevState
.clipRect
);
162 // rotation involved - convert to polygon first,
163 // then transform that
164 ::basegfx::B2DPolygon
aLocalClip(
165 ::basegfx::utils::createPolygonFromRect(
166 vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect
) ) );
167 ::basegfx::B2DHomMatrix aTransform
;
170 aTransform
.translate( -rOffset
.getX(),
173 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
175 aTransform
.rotate( - *pRotation
);
177 aLocalClip
.transform( aTransform
);
179 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
180 rCanvas
->getUNOCanvas()->getDevice(),
181 ::basegfx::B2DPolyPolygon( aLocalClip
) );
185 // scale and offset - do it on the fly, have to
186 // convert to float anyway.
187 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
188 rCanvas
->getUNOCanvas()->getDevice(),
189 ::basegfx::B2DPolyPolygon(
190 ::basegfx::utils::createPolygonFromRect(
191 ::basegfx::B2DRectangle(
192 (aLocalClipRect
.Left() - rOffset
.getX())/pScaling
->getX(),
193 (aLocalClipRect
.Top() - rOffset
.getY())/pScaling
->getY(),
194 (aLocalClipRect
.Right() - rOffset
.getX())/pScaling
->getX(),
195 (aLocalClipRect
.Bottom() - rOffset
.getY())/pScaling
->getY() ) ) ) );
199 // offset only - do it on the fly, have to convert
201 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
202 rCanvas
->getUNOCanvas()->getDevice(),
203 ::basegfx::B2DPolyPolygon(
204 ::basegfx::utils::createPolygonFromRect(
205 ::basegfx::B2DRectangle( aLocalClipRect
.Left() - rOffset
.getX(),
206 aLocalClipRect
.Top() - rOffset
.getY(),
207 aLocalClipRect
.Right() - rOffset
.getX(),
208 aLocalClipRect
.Bottom() - rOffset
.getY() ) ) ) );
214 // empty clip, nothing to do
218 // create overline/underline/strikeout line info struct
219 TextLineInfo
createTextLineInfo( const ::VirtualDevice
& rVDev
,
220 const ::cppcanvas::internal::OutDevState
& rState
)
222 const bool bOldMode( rVDev
.IsMapModeEnabled() );
224 // #i68512# Force metric regeneration with mapmode enabled
225 // (prolly OutDev bug)
226 rVDev
.GetFontMetric();
228 // will restore map mode below
229 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( false );
231 const ::FontMetric aMetric
= rVDev
.GetFontMetric();
233 TextLineInfo
aTextInfo(
234 (aMetric
.GetDescent() + 2) / 4.0,
235 ((aMetric
.GetInternalLeading() + 1.5) / 3.0),
236 (aMetric
.GetInternalLeading() / 2.0) - aMetric
.GetAscent(),
237 aMetric
.GetDescent() / 2.0,
238 (aMetric
.GetInternalLeading() - aMetric
.GetAscent()) / 3.0,
239 rState
.textOverlineStyle
,
240 rState
.textUnderlineStyle
,
241 rState
.textStrikeoutStyle
);
243 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( bOldMode
);
250 void appendWaveline( ::basegfx::B2DPolyPolygon
& o_rPoly
,
251 const ::basegfx::B2DPoint
& rStartPos
,
252 const double nStartOffset
,
254 const double nHeight
,
257 const double x(rStartPos
.getX());
258 const double y(rStartPos
.getY() + nStartOffset
+ nHeight
);
259 double nWaveWidth
= nHeight
* 10.6 * 0.25;
260 // Offset for the double line.
261 double nOffset
= 0.0;
263 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
264 nOffset
= -nHeight
* 0.5;
268 basegfx::B2DPolygon aLine
;
269 aLine
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
270 aLine
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
272 o_rPoly
.append(::basegfx::utils::createWaveline(aLine
, nWaveWidth
, nWaveWidth
* 0.5));
274 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
276 nOffset
= nHeight
* 1.2;
278 basegfx::B2DPolygon aLine2
;
279 aLine2
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
280 aLine2
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
281 o_rPoly
.append(::basegfx::utils::createWaveline(aLine2
, nWaveWidth
, nWaveWidth
* 0.5));
285 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
286 const ::basegfx::B2DPoint
& rStartPos
,
292 const double x( rStartPos
.getX() );
293 const double y( rStartPos
.getY() );
296 ::basegfx::utils::createPolygonFromRect(
297 ::basegfx::B2DRectangle( x
+ nX1
, y
+ nY1
, x
+ nX2
, y
+ nY2
) ) );
300 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
307 ::basegfx::utils::createPolygonFromRect(
308 ::basegfx::B2DRectangle( nX1
, nY1
, nX2
, nY2
) ) );
311 bool appendDashes( ::basegfx::B2DPolyPolygon
& o_rPoly
,
314 const double nLineWidth
,
319 static const int aDottedArray
[] = { 1, 1, 0}; // DOTTED LINE
320 static const int aDotDashArray
[] = { 1, 1, 4, 1, 0}; // DASHDOT
321 static const int aDashDotDotArray
[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT
322 static const int aDashedArray
[] = { 5, 2, 0}; // DASHED LINE
323 static const int aLongDashArray
[] = { 7, 2, 0}; // LONGDASH
324 const int *pArray
= nullptr;
325 bool bIsBold
= false;
329 case LINESTYLE_BOLDDOTTED
:
332 case LINESTYLE_DOTTED
:
333 pArray
= aDottedArray
;
336 case LINESTYLE_BOLDDASH
:
340 pArray
= aDashedArray
;
343 case LINESTYLE_BOLDLONGDASH
:
346 case LINESTYLE_LONGDASH
:
347 pArray
= aLongDashArray
;
350 case LINESTYLE_BOLDDASHDOT
:
353 case LINESTYLE_DASHDOT
:
354 pArray
= aDotDashArray
;
356 case LINESTYLE_BOLDDASHDOTDOT
:
359 case LINESTYLE_DASHDOTDOT
:
360 pArray
= aDashDotDotArray
;
375 const double nEnd
= nX
+ nLineWidth
;
376 sal_Int32 nIndex
= 0;
382 if (pArray
[nIndex
] == 0)
385 const double nX2
= std::min(nEnd
, nX1
+ pArray
[nIndex
] * nLineHeight
);
388 appendRect(o_rPoly
, nX1
, nY
, nX2
, nY
+ nLineHeight
);
399 // create line actions for text such as underline and
401 void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
402 const ::basegfx::B2DPoint
& rStartPos
,
403 const double& rLineWidth
,
404 const TextLineInfo
& rTextLineInfo
)
406 switch( rTextLineInfo
.mnOverlineStyle
)
408 case LINESTYLE_NONE
: // nothing to do
409 case LINESTYLE_DONTKNOW
:
412 case LINESTYLE_DOUBLEWAVE
:
413 case LINESTYLE_SMALLWAVE
:
414 case LINESTYLE_BOLDWAVE
:
419 rTextLineInfo
.mnOverlineOffset
,
421 rTextLineInfo
.mnOverlineHeight
,
422 rTextLineInfo
.mnOverlineStyle
);
425 case LINESTYLE_SINGLE
:
430 rTextLineInfo
.mnOverlineOffset
,
432 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
439 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
,
441 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
444 case LINESTYLE_DOUBLE
:
449 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
* 2.0 ,
451 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
);
457 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
,
459 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
* 2.0 );
466 rStartPos
.getY() + rTextLineInfo
.mnOverlineOffset
,
468 rTextLineInfo
.mnOverlineHeight
,
469 rTextLineInfo
.mnOverlineStyle
,
472 ENSURE_OR_THROW( false,
473 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" );
478 void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
479 const ::basegfx::B2DPoint
& rStartPos
,
480 const double& rLineWidth
,
481 const TextLineInfo
& rTextLineInfo
)
484 switch( rTextLineInfo
.mnUnderlineStyle
)
486 case LINESTYLE_NONE
: // nothing to do
487 case LINESTYLE_DONTKNOW
:
490 case LINESTYLE_DOUBLEWAVE
:
491 case LINESTYLE_SMALLWAVE
:
492 case LINESTYLE_BOLDWAVE
:
497 rTextLineInfo
.mnUnderlineOffset
,
499 rTextLineInfo
.mnLineHeight
,
500 rTextLineInfo
.mnUnderlineStyle
);
502 case LINESTYLE_SINGLE
:
507 rTextLineInfo
.mnUnderlineOffset
,
509 rTextLineInfo
.mnUnderlineOffset
+ rTextLineInfo
.mnLineHeight
);
517 rTextLineInfo
.mnUnderlineOffset
,
519 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
);
522 case LINESTYLE_DOUBLE
:
527 rTextLineInfo
.mnUnderlineOffset
- rTextLineInfo
.mnLineHeight
,
529 rTextLineInfo
.mnUnderlineOffset
);
535 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
,
537 rTextLineInfo
.mnUnderlineOffset
+ 3*rTextLineInfo
.mnLineHeight
);
544 rStartPos
.getY() + rTextLineInfo
.mnUnderlineOffset
,
546 rTextLineInfo
.mnLineHeight
,
547 rTextLineInfo
.mnUnderlineStyle
,
550 ENSURE_OR_THROW( false,
551 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" );
556 void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
557 const ::basegfx::B2DPoint
& rStartPos
,
558 const double& rLineWidth
,
559 const TextLineInfo
& rTextLineInfo
)
561 switch( rTextLineInfo
.mnStrikeoutStyle
)
563 case STRIKEOUT_NONE
: // nothing to do
564 case STRIKEOUT_DONTKNOW
:
567 case STRIKEOUT_SLASH
: // TODO(Q1): we should handle this in the text layer
571 case STRIKEOUT_SINGLE
:
576 rTextLineInfo
.mnStrikeoutOffset
,
578 rTextLineInfo
.mnStrikeoutOffset
+ rTextLineInfo
.mnLineHeight
);
586 rTextLineInfo
.mnStrikeoutOffset
,
588 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
);
591 case STRIKEOUT_DOUBLE
:
596 rTextLineInfo
.mnStrikeoutOffset
- rTextLineInfo
.mnLineHeight
,
598 rTextLineInfo
.mnStrikeoutOffset
);
604 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
,
606 rTextLineInfo
.mnStrikeoutOffset
+ 3*rTextLineInfo
.mnLineHeight
);
610 ENSURE_OR_THROW( false,
611 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" );
616 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const ::basegfx::B2DPoint
& rStartPos
,
617 const double& rLineWidth
,
618 const TextLineInfo
& rTextLineInfo
)
620 // fill the polypolygon with all text lines
621 ::basegfx::B2DPolyPolygon aTextLinesPolyPoly
;
623 createOverlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
624 createUnderlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
625 createStrikeoutPolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
626 return aTextLinesPolyPoly
;
629 ::basegfx::B2DRange
calcDevicePixelBounds( const ::basegfx::B2DRange
& rBounds
,
630 const rendering::ViewState
& viewState
,
631 const rendering::RenderState
& renderState
)
633 ::basegfx::B2DHomMatrix aTransform
;
634 ::canvas::tools::mergeViewAndRenderTransform( aTransform
,
638 ::basegfx::B2DRange aTransformedBounds
;
639 return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds
,
644 // create line actions for text such as underline and
646 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const double& rStartOffset
,
647 const double& rLineWidth
,
648 const TextLineInfo
& rTextLineInfo
)
650 return createTextLinesPolyPolygon(
651 ::basegfx::B2DPoint( rStartOffset
,
657 void createTextLinesPolyPolygon( const double& rStartOffset
,
658 const double& rLineWidth
,
659 const TextLineInfo
& rTextLineInfo
,
660 ::basegfx::B2DPolyPolygon
& rOverlinePolyPoly
,
661 ::basegfx::B2DPolyPolygon
& rUnderlinePolyPoly
,
662 ::basegfx::B2DPolyPolygon
& rStrikeoutPolyPoly
)
664 ::basegfx::B2DPoint
aStartPos(rStartOffset
, 0.0);
666 createOverlinePolyPolygon(rOverlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
667 createUnderlinePolyPolygon(rUnderlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
668 createStrikeoutPolyPolygon(rStrikeoutPolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
672 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */