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 <sal/config.h>
23 #include <comphelper/diagnose_ex.hxx>
24 #include <basegfx/matrix/b2dhommatrix.hxx>
25 #include <basegfx/numeric/ftools.hxx>
26 #include <basegfx/utils/canvastools.hxx>
27 #include <com/sun/star/rendering/CompositeOperation.hpp>
28 #include <com/sun/star/rendering/RenderState.hpp>
29 #include <com/sun/star/rendering/TextDirection.hpp>
30 #include <com/sun/star/rendering/ViewState.hpp>
31 #include <comphelper/sequence.hxx>
32 #include <cppuhelper/supportsservice.hxx>
34 #include <vcl/kernarray.hxx>
35 #include <vcl/metric.hxx>
36 #include <vcl/virdev.hxx>
38 #include <canvas/canvastools.hxx>
40 #include "textlayout.hxx"
44 using namespace ::com::sun::star
;
50 void setupLayoutMode( OutputDevice
& rOutDev
,
51 sal_Int8 nTextDirection
)
53 // TODO(P3): avoid if already correctly set
54 vcl::text::ComplexTextLayoutFlags nLayoutMode
= vcl::text::ComplexTextLayoutFlags::Default
;
55 switch( nTextDirection
)
57 case rendering::TextDirection::WEAK_LEFT_TO_RIGHT
:
59 case rendering::TextDirection::STRONG_LEFT_TO_RIGHT
:
60 nLayoutMode
= vcl::text::ComplexTextLayoutFlags::BiDiStrong
;
62 case rendering::TextDirection::WEAK_RIGHT_TO_LEFT
:
63 nLayoutMode
= vcl::text::ComplexTextLayoutFlags::BiDiRtl
;
65 case rendering::TextDirection::STRONG_RIGHT_TO_LEFT
:
66 nLayoutMode
= vcl::text::ComplexTextLayoutFlags::BiDiRtl
| vcl::text::ComplexTextLayoutFlags::BiDiStrong
;
72 // set calculated layout mode. Origin is always the left edge,
73 // as required at the API spec
74 rOutDev
.SetLayoutMode( nLayoutMode
| vcl::text::ComplexTextLayoutFlags::TextOriginLeft
);
78 TextLayout::TextLayout( rendering::StringContext aText
,
80 CanvasFont::Reference rFont
,
81 uno::Reference
<rendering::XGraphicDevice
> xDevice
,
82 OutDevProviderSharedPtr xOutDev
) :
83 maText(std::move( aText
)),
84 mpFont(std::move( rFont
)),
85 mxDevice(std::move( xDevice
)),
86 mpOutDevProvider(std::move( xOutDev
)),
87 mnTextDirection( nDirection
)
90 void TextLayout::disposing(std::unique_lock
<std::mutex
>& rGuard
)
94 SolarMutexGuard aGuard
;
95 mpOutDevProvider
.reset();
103 uno::Sequence
< uno::Reference
< rendering::XPolyPolygon2D
> > SAL_CALL
TextLayout::queryTextShapes( )
105 SolarMutexGuard aGuard
;
107 OutputDevice
& rOutDev
= mpOutDevProvider
->getOutDev();
108 ScopedVclPtrInstance
< VirtualDevice
> pVDev( rOutDev
);
109 pVDev
->SetFont( mpFont
->getVCLFont() );
111 setupLayoutMode( *pVDev
, mnTextDirection
);
113 const rendering::ViewState
aViewState(
114 geometry::AffineMatrix2D(1,0,0, 0,1,0),
117 rendering::RenderState
aRenderState (
118 geometry::AffineMatrix2D(1,0,0,0,1,0),
120 uno::Sequence
<double>(4),
121 rendering::CompositeOperation::SOURCE
);
123 KernArray
aOffsets(setupTextOffsets(maLogicalAdvancements
, aViewState
, aRenderState
));
124 std::span
<const sal_Bool
> aKashidaArray(maKashidaPositions
.getArray(), maKashidaPositions
.getLength());
126 std::vector
< uno::Reference
< rendering::XPolyPolygon2D
> > aOutlineSequence
;
127 ::basegfx::B2DPolyPolygonVector aOutlines
;
128 if (pVDev
->GetTextOutlines(
131 maText
.StartPosition
,
132 maText
.StartPosition
,
138 aOutlineSequence
.reserve(aOutlines
.size());
139 sal_Int32
nIndex (0);
140 for (auto const& outline
: aOutlines
)
142 aOutlineSequence
[nIndex
++] = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
148 return comphelper::containerToSequence(aOutlineSequence
);
151 uno::Sequence
< geometry::RealRectangle2D
> SAL_CALL
TextLayout::queryInkMeasures( )
153 SolarMutexGuard aGuard
;
156 OutputDevice
& rOutDev
= mpOutDevProvider
->getOutDev();
157 ScopedVclPtrInstance
< VirtualDevice
> pVDev( rOutDev
);
158 pVDev
->SetFont( mpFont
->getVCLFont() );
160 setupLayoutMode( *pVDev
, mnTextDirection
);
162 const rendering::ViewState
aViewState(
163 geometry::AffineMatrix2D(1,0,0, 0,1,0),
166 rendering::RenderState
aRenderState (
167 geometry::AffineMatrix2D(1,0,0,0,1,0),
169 uno::Sequence
<double>(4),
170 rendering::CompositeOperation::SOURCE
);
172 std::vector
< ::tools::Rectangle
> aMetricVector
;
173 uno::Sequence
<geometry::RealRectangle2D
> aBoundingBoxes
;
174 if (pVDev
->GetGlyphBoundRects(
177 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.StartPosition
),
178 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.Length
),
181 aBoundingBoxes
.realloc(aMetricVector
.size());
182 auto pBoundingBoxes
= aBoundingBoxes
.getArray();
183 sal_Int32
nIndex (0);
184 for (auto const& metric
: aMetricVector
)
186 pBoundingBoxes
[nIndex
++] = geometry::RealRectangle2D(
193 return aBoundingBoxes
;
196 uno::Sequence
< geometry::RealRectangle2D
> SAL_CALL
TextLayout::queryMeasures( )
199 return uno::Sequence
< geometry::RealRectangle2D
>();
202 uno::Sequence
< double > SAL_CALL
TextLayout::queryLogicalAdvancements( )
204 SolarMutexGuard aGuard
;
206 return maLogicalAdvancements
;
209 void SAL_CALL
TextLayout::applyLogicalAdvancements( const uno::Sequence
< double >& aAdvancements
)
211 SolarMutexGuard aGuard
;
213 ENSURE_ARG_OR_THROW( aAdvancements
.getLength() == maText
.Length
,
214 "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" );
216 maLogicalAdvancements
= aAdvancements
;
219 uno::Sequence
< sal_Bool
> SAL_CALL
TextLayout::queryKashidaPositions( )
221 SolarMutexGuard aGuard
;
223 return maKashidaPositions
;
226 void SAL_CALL
TextLayout::applyKashidaPositions( const uno::Sequence
< sal_Bool
>& aPositions
)
228 SolarMutexGuard aGuard
;
230 ENSURE_ARG_OR_THROW( !aPositions
.hasElements() || aPositions
.getLength() == maText
.Length
,
231 "TextLayout::applyKashidaPositions(): mismatching number of positions" );
233 maKashidaPositions
= aPositions
;
236 geometry::RealRectangle2D SAL_CALL
TextLayout::queryTextBounds( )
238 SolarMutexGuard aGuard
;
240 if( !mpOutDevProvider
)
241 return geometry::RealRectangle2D();
243 OutputDevice
& rOutDev
= mpOutDevProvider
->getOutDev();
245 ScopedVclPtrInstance
< VirtualDevice
> pVDev( rOutDev
);
246 pVDev
->SetFont( mpFont
->getVCLFont() );
248 // need metrics for Y offset, the XCanvas always renders
249 // relative to baseline
250 const ::FontMetric
aMetric( pVDev
->GetFontMetric() );
252 setupLayoutMode( *pVDev
, mnTextDirection
);
254 const sal_Int32
nAboveBaseline( -aMetric
.GetAscent() );
255 const sal_Int32
nBelowBaseline( aMetric
.GetDescent() );
257 if( maLogicalAdvancements
.hasElements() )
259 return geometry::RealRectangle2D( 0, nAboveBaseline
,
260 maLogicalAdvancements
[ maLogicalAdvancements
.getLength()-1 ],
265 return geometry::RealRectangle2D( 0, nAboveBaseline
,
268 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.StartPosition
),
269 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.Length
) ),
274 double SAL_CALL
TextLayout::justify( double )
280 double SAL_CALL
TextLayout::combinedJustify( const uno::Sequence
< uno::Reference
< rendering::XTextLayout
> >&,
287 rendering::TextHit SAL_CALL
TextLayout::getTextHit( const geometry::RealPoint2D
& )
290 return rendering::TextHit();
293 rendering::Caret SAL_CALL
TextLayout::getCaret( sal_Int32
, sal_Bool
)
296 return rendering::Caret();
299 sal_Int32 SAL_CALL
TextLayout::getNextInsertionIndex( sal_Int32
, sal_Int32
, sal_Bool
)
305 uno::Reference
< rendering::XPolyPolygon2D
> SAL_CALL
TextLayout::queryVisualHighlighting( sal_Int32
, sal_Int32
)
308 return uno::Reference
< rendering::XPolyPolygon2D
>();
311 uno::Reference
< rendering::XPolyPolygon2D
> SAL_CALL
TextLayout::queryLogicalHighlighting( sal_Int32
, sal_Int32
)
314 return uno::Reference
< rendering::XPolyPolygon2D
>();
317 double SAL_CALL
TextLayout::getBaselineOffset( )
323 sal_Int8 SAL_CALL
TextLayout::getMainTextDirection( )
325 return mnTextDirection
;
328 uno::Reference
< rendering::XCanvasFont
> SAL_CALL
TextLayout::getFont( )
330 SolarMutexGuard aGuard
;
335 rendering::StringContext SAL_CALL
TextLayout::getText( )
340 void TextLayout::draw( OutputDevice
& rOutDev
,
341 const Point
& rOutpos
,
342 const rendering::ViewState
& viewState
,
343 const rendering::RenderState
& renderState
) const
345 SolarMutexGuard aGuard
;
349 // On Windows we get the wrong font width for fallback fonts unless we setup again here.
350 vcl::Font
aFont(rOutDev
.GetFont());
351 tools::setupFontWidth(mpFont
->getFontMatrix(), aFont
, rOutDev
);
352 rOutDev
.SetFont(aFont
);
355 setupLayoutMode( rOutDev
, mnTextDirection
);
357 if( maLogicalAdvancements
.hasElements() )
359 // TODO(P2): cache that
360 KernArray
aOffsets(setupTextOffsets(maLogicalAdvancements
, viewState
, renderState
));
361 std::span
<const sal_Bool
> aKashidaArray(maKashidaPositions
.getConstArray(), maKashidaPositions
.getLength());
363 // TODO(F3): ensure correct length and termination for DX
364 // array (last entry _must_ contain the overall width)
366 rOutDev
.DrawTextArray( rOutpos
,
370 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.StartPosition
),
371 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.Length
) );
375 rOutDev
.DrawText( rOutpos
,
377 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.StartPosition
),
378 ::canvas::tools::numeric_cast
<sal_uInt16
>(maText
.Length
) );
384 class OffsetTransformer
387 explicit OffsetTransformer( ::basegfx::B2DHomMatrix aMat
) :
388 maMatrix(std::move( aMat
))
392 sal_Int32
operator()( const double& rOffset
)
394 // This is an optimization of the normal rMat*[x,0]
395 // transformation of the advancement vector (in x
396 // direction), followed by a length calculation of the
397 // resulting vector: advancement' =
398 // ||rMat*[x,0]||. Since advancements are vectors, we
399 // can ignore translational components, thus if [x,0],
400 // it follows that rMat*[x,0]=[x',0] holds. Thus, we
401 // just have to calc the transformation of the x
404 // TODO(F2): Handle non-horizontal advancements!
405 return ::basegfx::fround( hypot(maMatrix
.get(0,0)*rOffset
,
406 maMatrix
.get(1,0)*rOffset
) );
410 ::basegfx::B2DHomMatrix maMatrix
;
414 KernArray
TextLayout::setupTextOffsets(
415 const uno::Sequence
< double >& inputOffsets
,
416 const rendering::ViewState
& viewState
,
417 const rendering::RenderState
& renderState
) const
419 ::basegfx::B2DHomMatrix aMatrix
;
421 ::canvas::tools::mergeViewAndRenderTransform(aMatrix
,
425 // fill integer offsets
426 KernArray outputOffsets
;
427 OffsetTransformer
aTransform(aMatrix
);
428 std::for_each(inputOffsets
.begin(), inputOffsets
.end(),
429 [&outputOffsets
, &aTransform
](double n
) {outputOffsets
.push_back(aTransform(n
)); } );
430 return outputOffsets
;
433 OUString SAL_CALL
TextLayout::getImplementationName()
435 return u
"VCLCanvas::TextLayout"_ustr
;
438 sal_Bool SAL_CALL
TextLayout::supportsService( const OUString
& ServiceName
)
440 return cppu::supportsService( this, ServiceName
);
443 uno::Sequence
< OUString
> SAL_CALL
TextLayout::getSupportedServiceNames()
445 return { u
"com.sun.star.rendering.TextLayout"_ustr
};
449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */