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 <comphelper/diagnose_ex.hxx>
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 return ::Size( 0, 0 );
69 -aMetric
.GetDescent() );
72 throw css::uno::RuntimeException(
73 "tools::getBaselineOffset(): Unexpected TextAlign value" );
77 ::basegfx::B2DHomMatrix
& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
78 const VirtualDevice
& rVDev
)
80 // select size value in the middle of the available range,
81 // to have headroom both when map mode scales up, and when
83 const ::Size
aSizeLogic( 0x00010000L
,
86 const ::Size
aSizePixel( rVDev
.LogicToPixel( aSizeLogic
) );
88 o_rMatrix
= basegfx::utils::createScaleB2DHomMatrix(
89 aSizePixel
.Width() / static_cast<double>(aSizeLogic
.Width()),
90 aSizePixel
.Height() / static_cast<double>(aSizeLogic
.Height()) );
95 ::basegfx::B2DHomMatrix
& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix
& o_rMatrix
,
96 const VirtualDevice
& rVDev
)
99 calcLogic2PixelLinearTransform(o_rMatrix
, rVDev
);
101 // translate according to curr map mode/pref map mode offset
102 const ::Point aEmptyPoint
;
103 const ::Point
& rTranslatedPoint(
104 rVDev
.LogicToPixel( aEmptyPoint
));
106 o_rMatrix
.translate(rTranslatedPoint
.X(),
107 rTranslatedPoint
.Y());
112 bool modifyClip( rendering::RenderState
& o_rRenderState
,
113 const struct ::cppcanvas::internal::OutDevState
& rOutdevState
,
114 const CanvasSharedPtr
& rCanvas
,
115 const ::basegfx::B2DPoint
& rOffset
,
116 const ::basegfx::B2DVector
* pScaling
,
117 const double* pRotation
)
119 const bool bOffsetting( !rOffset
.equalZero() );
120 const bool bScaling( pScaling
&&
121 !rtl::math::approxEqual(pScaling
->getX(), 1.0) &&
122 !rtl::math::approxEqual(pScaling
->getY(), 1.0) );
123 const bool bRotation( pRotation
&&
126 if( !bOffsetting
&& !bScaling
&& !bRotation
)
127 return false; // nothing to do
129 if( rOutdevState
.clip
.count() )
131 // general polygon case
133 ::basegfx::B2DPolyPolygon
aLocalClip( rOutdevState
.clip
);
134 ::basegfx::B2DHomMatrix aTransform
;
137 aTransform
.translate( -rOffset
.getX(),
140 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
143 aTransform
.rotate( - *pRotation
);
145 aLocalClip
.transform( aTransform
);
147 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
148 rCanvas
->getUNOCanvas()->getDevice(),
153 else if( !rOutdevState
.clipRect
.IsEmpty() )
157 const ::tools::Rectangle
aLocalClipRect( rOutdevState
.clipRect
);
161 // rotation involved - convert to polygon first,
162 // then transform that
163 ::basegfx::B2DPolygon
aLocalClip(
164 ::basegfx::utils::createPolygonFromRect(
165 vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect
) ) );
166 ::basegfx::B2DHomMatrix aTransform
;
169 aTransform
.translate( -rOffset
.getX(),
172 aTransform
.scale( 1.0/pScaling
->getX(), 1.0/pScaling
->getY() );
174 aTransform
.rotate( - *pRotation
);
176 aLocalClip
.transform( aTransform
);
178 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
179 rCanvas
->getUNOCanvas()->getDevice(),
180 ::basegfx::B2DPolyPolygon( aLocalClip
) );
184 // scale and offset - do it on the fly, have to
185 // convert to float anyway.
186 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
187 rCanvas
->getUNOCanvas()->getDevice(),
188 ::basegfx::B2DPolyPolygon(
189 ::basegfx::utils::createPolygonFromRect(
190 ::basegfx::B2DRectangle(
191 (aLocalClipRect
.Left() - rOffset
.getX())/pScaling
->getX(),
192 (aLocalClipRect
.Top() - rOffset
.getY())/pScaling
->getY(),
193 (aLocalClipRect
.Right() - rOffset
.getX())/pScaling
->getX(),
194 (aLocalClipRect
.Bottom() - rOffset
.getY())/pScaling
->getY() ) ) ) );
198 // offset only - do it on the fly, have to convert
200 o_rRenderState
.Clip
= ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
201 rCanvas
->getUNOCanvas()->getDevice(),
202 ::basegfx::B2DPolyPolygon(
203 ::basegfx::utils::createPolygonFromRect(
204 ::basegfx::B2DRectangle( aLocalClipRect
.Left() - rOffset
.getX(),
205 aLocalClipRect
.Top() - rOffset
.getY(),
206 aLocalClipRect
.Right() - rOffset
.getX(),
207 aLocalClipRect
.Bottom() - rOffset
.getY() ) ) ) );
213 // empty clip, nothing to do
217 // create overline/underline/strikeout line info struct
218 TextLineInfo
createTextLineInfo( const ::VirtualDevice
& rVDev
,
219 const ::cppcanvas::internal::OutDevState
& rState
)
221 const bool bOldMode( rVDev
.IsMapModeEnabled() );
223 // #i68512# Force metric regeneration with mapmode enabled
224 // (prolly OutDev bug)
225 rVDev
.GetFontMetric();
227 // will restore map mode below
228 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( false );
230 const ::FontMetric aMetric
= rVDev
.GetFontMetric();
232 TextLineInfo
aTextInfo(
233 (aMetric
.GetDescent() + 2) / 4.0,
234 ((aMetric
.GetInternalLeading() + 1.5) / 3.0),
235 (aMetric
.GetInternalLeading() / 2.0) - aMetric
.GetAscent(),
236 aMetric
.GetDescent() / 2.0,
237 (aMetric
.GetInternalLeading() - aMetric
.GetAscent()) / 3.0,
238 rState
.textOverlineStyle
,
239 rState
.textUnderlineStyle
,
240 rState
.textStrikeoutStyle
);
242 const_cast< ::VirtualDevice
& >(rVDev
).EnableMapMode( bOldMode
);
249 void appendWaveline( ::basegfx::B2DPolyPolygon
& o_rPoly
,
250 const ::basegfx::B2DPoint
& rStartPos
,
251 const double nStartOffset
,
253 const double nHeight
,
256 const double x(rStartPos
.getX());
257 const double y(rStartPos
.getY() + nStartOffset
+ nHeight
);
258 double nWaveWidth
= nHeight
* 10.6 * 0.25;
259 // Offset for the double line.
260 double nOffset
= 0.0;
262 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
263 nOffset
= -nHeight
* 0.5;
267 basegfx::B2DPolygon aLine
;
268 aLine
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
269 aLine
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
271 o_rPoly
.append(::basegfx::utils::createWaveline(aLine
, nWaveWidth
, nWaveWidth
* 0.5));
273 if (nLineStyle
== LINESTYLE_DOUBLEWAVE
)
275 nOffset
= nHeight
* 1.2;
277 basegfx::B2DPolygon aLine2
;
278 aLine2
.append(basegfx::B2DPoint(x
, y
+ nOffset
));
279 aLine2
.append(basegfx::B2DPoint(x
+ nWidth
, y
+ nOffset
));
280 o_rPoly
.append(::basegfx::utils::createWaveline(aLine2
, nWaveWidth
, nWaveWidth
* 0.5));
284 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
285 const ::basegfx::B2DPoint
& rStartPos
,
291 const double x( rStartPos
.getX() );
292 const double y( rStartPos
.getY() );
295 ::basegfx::utils::createPolygonFromRect(
296 ::basegfx::B2DRectangle( x
+ nX1
, y
+ nY1
, x
+ nX2
, y
+ nY2
) ) );
299 void appendRect( ::basegfx::B2DPolyPolygon
& o_rPoly
,
306 ::basegfx::utils::createPolygonFromRect(
307 ::basegfx::B2DRectangle( nX1
, nY1
, nX2
, nY2
) ) );
310 bool appendDashes( ::basegfx::B2DPolyPolygon
& o_rPoly
,
313 const double nLineWidth
,
318 static const int aDottedArray
[] = { 1, 1, 0}; // DOTTED LINE
319 static const int aDotDashArray
[] = { 1, 1, 4, 1, 0}; // DASHDOT
320 static const int aDashDotDotArray
[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT
321 static const int aDashedArray
[] = { 5, 2, 0}; // DASHED LINE
322 static const int aLongDashArray
[] = { 7, 2, 0}; // LONGDASH
323 const int *pArray
= nullptr;
324 bool bIsBold
= false;
328 case LINESTYLE_BOLDDOTTED
:
331 case LINESTYLE_DOTTED
:
332 pArray
= aDottedArray
;
335 case LINESTYLE_BOLDDASH
:
339 pArray
= aDashedArray
;
342 case LINESTYLE_BOLDLONGDASH
:
345 case LINESTYLE_LONGDASH
:
346 pArray
= aLongDashArray
;
349 case LINESTYLE_BOLDDASHDOT
:
352 case LINESTYLE_DASHDOT
:
353 pArray
= aDotDashArray
;
355 case LINESTYLE_BOLDDASHDOTDOT
:
358 case LINESTYLE_DASHDOTDOT
:
359 pArray
= aDashDotDotArray
;
374 const double nEnd
= nX
+ nLineWidth
;
375 sal_Int32 nIndex
= 0;
381 if (pArray
[nIndex
] == 0)
384 const double nX2
= std::min(nEnd
, nX1
+ pArray
[nIndex
] * nLineHeight
);
387 appendRect(o_rPoly
, nX1
, nY
, nX2
, nY
+ nLineHeight
);
398 // create line actions for text such as underline and
400 void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
401 const ::basegfx::B2DPoint
& rStartPos
,
402 const double& rLineWidth
,
403 const TextLineInfo
& rTextLineInfo
)
405 switch( rTextLineInfo
.mnOverlineStyle
)
407 case LINESTYLE_NONE
: // nothing to do
408 case LINESTYLE_DONTKNOW
:
411 case LINESTYLE_DOUBLEWAVE
:
412 case LINESTYLE_SMALLWAVE
:
413 case LINESTYLE_BOLDWAVE
:
418 rTextLineInfo
.mnOverlineOffset
,
420 rTextLineInfo
.mnOverlineHeight
,
421 rTextLineInfo
.mnOverlineStyle
);
424 case LINESTYLE_SINGLE
:
429 rTextLineInfo
.mnOverlineOffset
,
431 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
438 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
,
440 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
);
443 case LINESTYLE_DOUBLE
:
448 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
* 2.0 ,
450 rTextLineInfo
.mnOverlineOffset
- rTextLineInfo
.mnOverlineHeight
);
456 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
,
458 rTextLineInfo
.mnOverlineOffset
+ rTextLineInfo
.mnOverlineHeight
* 2.0 );
465 rStartPos
.getY() + rTextLineInfo
.mnOverlineOffset
,
467 rTextLineInfo
.mnOverlineHeight
,
468 rTextLineInfo
.mnOverlineStyle
,
471 ENSURE_OR_THROW( false,
472 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" );
477 void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
478 const ::basegfx::B2DPoint
& rStartPos
,
479 const double& rLineWidth
,
480 const TextLineInfo
& rTextLineInfo
)
483 switch( rTextLineInfo
.mnUnderlineStyle
)
485 case LINESTYLE_NONE
: // nothing to do
486 case LINESTYLE_DONTKNOW
:
489 case LINESTYLE_DOUBLEWAVE
:
490 case LINESTYLE_SMALLWAVE
:
491 case LINESTYLE_BOLDWAVE
:
496 rTextLineInfo
.mnUnderlineOffset
,
498 rTextLineInfo
.mnLineHeight
,
499 rTextLineInfo
.mnUnderlineStyle
);
501 case LINESTYLE_SINGLE
:
506 rTextLineInfo
.mnUnderlineOffset
,
508 rTextLineInfo
.mnUnderlineOffset
+ rTextLineInfo
.mnLineHeight
);
516 rTextLineInfo
.mnUnderlineOffset
,
518 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
);
521 case LINESTYLE_DOUBLE
:
526 rTextLineInfo
.mnUnderlineOffset
- rTextLineInfo
.mnLineHeight
,
528 rTextLineInfo
.mnUnderlineOffset
);
534 rTextLineInfo
.mnUnderlineOffset
+ 2*rTextLineInfo
.mnLineHeight
,
536 rTextLineInfo
.mnUnderlineOffset
+ 3*rTextLineInfo
.mnLineHeight
);
543 rStartPos
.getY() + rTextLineInfo
.mnUnderlineOffset
,
545 rTextLineInfo
.mnLineHeight
,
546 rTextLineInfo
.mnUnderlineStyle
,
549 ENSURE_OR_THROW( false,
550 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" );
555 void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon
& rTextLinesPolyPoly
,
556 const ::basegfx::B2DPoint
& rStartPos
,
557 const double& rLineWidth
,
558 const TextLineInfo
& rTextLineInfo
)
560 switch( rTextLineInfo
.mnStrikeoutStyle
)
562 case STRIKEOUT_NONE
: // nothing to do
563 case STRIKEOUT_DONTKNOW
:
566 case STRIKEOUT_SLASH
: // TODO(Q1): we should handle this in the text layer
570 case STRIKEOUT_SINGLE
:
575 rTextLineInfo
.mnStrikeoutOffset
,
577 rTextLineInfo
.mnStrikeoutOffset
+ rTextLineInfo
.mnLineHeight
);
585 rTextLineInfo
.mnStrikeoutOffset
,
587 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
);
590 case STRIKEOUT_DOUBLE
:
595 rTextLineInfo
.mnStrikeoutOffset
- rTextLineInfo
.mnLineHeight
,
597 rTextLineInfo
.mnStrikeoutOffset
);
603 rTextLineInfo
.mnStrikeoutOffset
+ 2*rTextLineInfo
.mnLineHeight
,
605 rTextLineInfo
.mnStrikeoutOffset
+ 3*rTextLineInfo
.mnLineHeight
);
609 ENSURE_OR_THROW( false,
610 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" );
615 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const ::basegfx::B2DPoint
& rStartPos
,
616 const double& rLineWidth
,
617 const TextLineInfo
& rTextLineInfo
)
619 // fill the polypolygon with all text lines
620 ::basegfx::B2DPolyPolygon aTextLinesPolyPoly
;
622 createOverlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
623 createUnderlinePolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
624 createStrikeoutPolyPolygon(aTextLinesPolyPoly
, rStartPos
, rLineWidth
, rTextLineInfo
);
625 return aTextLinesPolyPoly
;
628 ::basegfx::B2DRange
calcDevicePixelBounds( const ::basegfx::B2DRange
& rBounds
,
629 const rendering::ViewState
& viewState
,
630 const rendering::RenderState
& renderState
)
632 ::basegfx::B2DHomMatrix aTransform
;
633 ::canvas::tools::mergeViewAndRenderTransform( aTransform
,
637 ::basegfx::B2DRange aTransformedBounds
;
638 return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds
,
643 // create line actions for text such as underline and
645 ::basegfx::B2DPolyPolygon
createTextLinesPolyPolygon( const double& rStartOffset
,
646 const double& rLineWidth
,
647 const TextLineInfo
& rTextLineInfo
)
649 return createTextLinesPolyPolygon(
650 ::basegfx::B2DPoint( rStartOffset
,
656 void createTextLinesPolyPolygon( const double& rStartOffset
,
657 const double& rLineWidth
,
658 const TextLineInfo
& rTextLineInfo
,
659 ::basegfx::B2DPolyPolygon
& rOverlinePolyPoly
,
660 ::basegfx::B2DPolyPolygon
& rUnderlinePolyPoly
,
661 ::basegfx::B2DPolyPolygon
& rStrikeoutPolyPoly
)
663 ::basegfx::B2DPoint
aStartPos(rStartOffset
, 0.0);
665 createOverlinePolyPolygon(rOverlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
666 createUnderlinePolyPolygon(rUnderlinePolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
667 createStrikeoutPolyPolygon(rStrikeoutPolyPoly
, aStartPos
, rLineWidth
, rTextLineInfo
);
671 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */