Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/canvas/source/vcl/textlayout.cxx
Line
Count
Source
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
20
21
#include <sal/config.h>
22
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>
33
#include <utility>
34
#include <vcl/kernarray.hxx>
35
#include <vcl/metric.hxx>
36
#include <vcl/virdev.hxx>
37
38
#include <canvas/canvastools.hxx>
39
40
#include "textlayout.hxx"
41
42
#include <memory>
43
44
using namespace ::com::sun::star;
45
46
namespace vclcanvas
47
{
48
    namespace
49
    {
50
        void setupLayoutMode( OutputDevice& rOutDev,
51
                              sal_Int8      nTextDirection )
52
0
        {
53
            // TODO(P3): avoid if already correctly set
54
0
            vcl::text::ComplexTextLayoutFlags nLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
55
0
            switch( nTextDirection )
56
0
            {
57
0
                case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
58
0
                    break;
59
0
                case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
60
0
                    nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
61
0
                    break;
62
0
                case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
63
0
                    nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl;
64
0
                    break;
65
0
                case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
66
0
                    nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong;
67
0
                    break;
68
0
                default:
69
0
                    break;
70
0
            }
71
72
            // set calculated layout mode. Origin is always the left edge,
73
            // as required at the API spec
74
0
            rOutDev.SetLayoutMode( nLayoutMode | vcl::text::ComplexTextLayoutFlags::TextOriginLeft );
75
0
        }
76
    }
77
78
    TextLayout::TextLayout( rendering::StringContext                   aText,
79
                            sal_Int8                                   nDirection,
80
                            CanvasFont::Reference                      rFont,
81
                            uno::Reference<rendering::XGraphicDevice>  xDevice,
82
                            OutDevProviderSharedPtr                    xOutDev ) :
83
0
        maText(std::move( aText )),
84
0
        mpFont(std::move( rFont )),
85
0
        mxDevice(std::move( xDevice )),
86
0
        mpOutDevProvider(std::move( xOutDev )),
87
0
        mnTextDirection( nDirection )
88
0
    {}
Unexecuted instantiation: vclcanvas::TextLayout::TextLayout(com::sun::star::rendering::StringContext, signed char, rtl::Reference<vclcanvas::CanvasFont>, com::sun::star::uno::Reference<com::sun::star::rendering::XGraphicDevice>, std::__1::shared_ptr<vclcanvas::OutDevProvider>)
Unexecuted instantiation: vclcanvas::TextLayout::TextLayout(com::sun::star::rendering::StringContext, signed char, rtl::Reference<vclcanvas::CanvasFont>, com::sun::star::uno::Reference<com::sun::star::rendering::XGraphicDevice>, std::__1::shared_ptr<vclcanvas::OutDevProvider>)
89
90
    void TextLayout::disposing(std::unique_lock<std::mutex>& rGuard)
91
0
    {
92
0
        rGuard.unlock();
93
0
        {
94
0
            SolarMutexGuard aGuard;
95
0
            mpOutDevProvider.reset();
96
0
            mxDevice.clear();
97
0
            mpFont.clear();
98
0
        }
99
0
        rGuard.lock();
100
0
    }
101
102
    // XTextLayout
103
    uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes(  )
104
0
    {
105
0
        SolarMutexGuard aGuard;
106
107
0
        OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
108
0
        ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
109
0
        pVDev->SetFont( mpFont->getVCLFont() );
110
111
0
        setupLayoutMode( *pVDev, mnTextDirection );
112
113
0
        const rendering::ViewState aViewState(
114
0
            geometry::AffineMatrix2D(1,0,0, 0,1,0),
115
0
            nullptr);
116
117
0
        rendering::RenderState aRenderState (
118
0
            geometry::AffineMatrix2D(1,0,0,0,1,0),
119
0
            nullptr,
120
0
            uno::Sequence<double>(4),
121
0
            rendering::CompositeOperation::SOURCE);
122
123
0
        KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, aViewState, aRenderState));
124
0
        std::span<const sal_Bool> aKashidaArray(maKashidaPositions.getArray(), maKashidaPositions.getLength());
125
126
0
        std::vector< uno::Reference< rendering::XPolyPolygon2D> > aOutlineSequence;
127
0
        ::basegfx::B2DPolyPolygonVector aOutlines;
128
0
        if (pVDev->GetTextOutlines(
129
0
            aOutlines,
130
0
            maText.Text,
131
0
            maText.StartPosition,
132
0
            maText.StartPosition,
133
0
            maText.Length,
134
0
            0,
135
0
            aOffsets,
136
0
            aKashidaArray))
137
0
        {
138
0
            aOutlineSequence.reserve(aOutlines.size());
139
0
            sal_Int32 nIndex (0);
140
0
            for (auto const& outline : aOutlines)
141
0
            {
142
0
                aOutlineSequence[nIndex++] = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
143
0
                    mxDevice,
144
0
                    outline);
145
0
            }
146
0
        }
147
148
0
        return comphelper::containerToSequence(aOutlineSequence);
149
0
    }
150
151
    uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures(  )
152
0
    {
153
0
        SolarMutexGuard aGuard;
154
155
156
0
        OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
157
0
        ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
158
0
        pVDev->SetFont( mpFont->getVCLFont() );
159
160
0
        setupLayoutMode( *pVDev, mnTextDirection );
161
162
0
        const rendering::ViewState aViewState(
163
0
            geometry::AffineMatrix2D(1,0,0, 0,1,0),
164
0
            nullptr);
165
166
0
        rendering::RenderState aRenderState (
167
0
            geometry::AffineMatrix2D(1,0,0,0,1,0),
168
0
            nullptr,
169
0
            uno::Sequence<double>(4),
170
0
            rendering::CompositeOperation::SOURCE);
171
172
0
        std::vector< ::tools::Rectangle > aMetricVector;
173
0
        uno::Sequence<geometry::RealRectangle2D> aBoundingBoxes;
174
0
        if (pVDev->GetGlyphBoundRects(
175
0
            Point(0,0),
176
0
            maText.Text,
177
0
            ::canvastools::numeric_cast<sal_uInt16>(maText.StartPosition),
178
0
            ::canvastools::numeric_cast<sal_uInt16>(maText.Length),
179
0
            aMetricVector))
180
0
        {
181
0
            aBoundingBoxes.realloc(aMetricVector.size());
182
0
            auto pBoundingBoxes = aBoundingBoxes.getArray();
183
0
            sal_Int32 nIndex (0);
184
0
            for (auto const& metric : aMetricVector)
185
0
            {
186
0
                pBoundingBoxes[nIndex++] = geometry::RealRectangle2D(
187
0
                    metric.Left(),
188
0
                    metric.Top(),
189
0
                    metric.Right(),
190
0
                    metric.Bottom());
191
0
            }
192
0
        }
193
0
        return aBoundingBoxes;
194
0
    }
195
196
    uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures(  )
197
0
    {
198
        // TODO(F1)
199
0
        return uno::Sequence< geometry::RealRectangle2D >();
200
0
    }
201
202
    uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements(  )
203
0
    {
204
0
        SolarMutexGuard aGuard;
205
206
0
        return maLogicalAdvancements;
207
0
    }
208
209
    void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements )
210
0
    {
211
0
        SolarMutexGuard aGuard;
212
213
0
        ENSURE_ARG_OR_THROW( aAdvancements.getLength() == maText.Length,
214
0
                         "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" );
215
216
0
        maLogicalAdvancements = aAdvancements;
217
0
    }
218
219
    uno::Sequence< sal_Bool > SAL_CALL TextLayout::queryKashidaPositions(  )
220
0
    {
221
0
        SolarMutexGuard aGuard;
222
223
0
        return maKashidaPositions;
224
0
    }
225
226
    void SAL_CALL TextLayout::applyKashidaPositions( const uno::Sequence< sal_Bool >& aPositions )
227
0
    {
228
0
        SolarMutexGuard aGuard;
229
230
0
        ENSURE_ARG_OR_THROW( !aPositions.hasElements() || aPositions.getLength() == maText.Length,
231
0
                         "TextLayout::applyKashidaPositions(): mismatching number of positions" );
232
233
0
        maKashidaPositions = aPositions;
234
0
    }
235
236
    geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds(  )
237
0
    {
238
0
        SolarMutexGuard aGuard;
239
240
0
        if( !mpOutDevProvider )
241
0
            return geometry::RealRectangle2D();
242
243
0
        OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
244
245
0
        ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
246
0
        pVDev->SetFont( mpFont->getVCLFont() );
247
248
        // need metrics for Y offset, the XCanvas always renders
249
        // relative to baseline
250
0
        const ::FontMetric aMetric( pVDev->GetFontMetric() );
251
252
0
        setupLayoutMode( *pVDev, mnTextDirection );
253
254
0
        const sal_Int32 nAboveBaseline( -aMetric.GetAscent() );
255
0
        const sal_Int32 nBelowBaseline( aMetric.GetDescent() );
256
257
0
        if( maLogicalAdvancements.hasElements() )
258
0
        {
259
0
            return geometry::RealRectangle2D( 0, nAboveBaseline,
260
0
                                              maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ],
261
0
                                              nBelowBaseline );
262
0
        }
263
0
        else
264
0
        {
265
0
            return geometry::RealRectangle2D( 0, nAboveBaseline,
266
0
                                              pVDev->GetTextWidth(
267
0
                                                  maText.Text,
268
0
                                                  ::canvastools::numeric_cast<sal_uInt16>(maText.StartPosition),
269
0
                                                  ::canvastools::numeric_cast<sal_uInt16>(maText.Length) ),
270
0
                                              nBelowBaseline );
271
0
        }
272
0
    }
273
274
    double SAL_CALL TextLayout::justify( double )
275
0
    {
276
        // TODO(F1)
277
0
        return 0.0;
278
0
    }
279
280
    double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >&,
281
                                                 double )
282
0
    {
283
        // TODO(F1)
284
0
        return 0.0;
285
0
    }
286
287
    rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& )
288
0
    {
289
        // TODO(F1)
290
0
        return rendering::TextHit();
291
0
    }
292
293
    rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32, sal_Bool )
294
0
    {
295
        // TODO(F1)
296
0
        return rendering::Caret();
297
0
    }
298
299
    sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32, sal_Int32, sal_Bool )
300
0
    {
301
        // TODO(F1)
302
0
        return 0;
303
0
    }
304
305
    uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32, sal_Int32 )
306
0
    {
307
        // TODO(F1)
308
0
        return uno::Reference< rendering::XPolyPolygon2D >();
309
0
    }
310
311
    uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32, sal_Int32 )
312
0
    {
313
        // TODO(F1)
314
0
        return uno::Reference< rendering::XPolyPolygon2D >();
315
0
    }
316
317
    double SAL_CALL TextLayout::getBaselineOffset(  )
318
0
    {
319
        // TODO(F1)
320
0
        return 0.0;
321
0
    }
322
323
    sal_Int8 SAL_CALL TextLayout::getMainTextDirection(  )
324
0
    {
325
0
        return mnTextDirection;
326
0
    }
327
328
    uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont(  )
329
0
    {
330
0
        SolarMutexGuard aGuard;
331
332
0
        return mpFont;
333
0
    }
334
335
    rendering::StringContext SAL_CALL TextLayout::getText(  )
336
0
    {
337
0
        return maText;
338
0
    }
339
340
    void TextLayout::draw( OutputDevice&                 rOutDev,
341
                           const Point&                  rOutpos,
342
                           const rendering::ViewState&   viewState,
343
                           const rendering::RenderState& renderState ) const
344
0
    {
345
0
        SolarMutexGuard aGuard;
346
347
#ifdef _WIN32
348
        // tdf#147999
349
        // On Windows we get the wrong font width for fallback fonts unless we setup again here.
350
        vcl::Font aFont(rOutDev.GetFont());
351
        vclcanvastools::setupFontWidth(mpFont->getFontMatrix(), aFont, rOutDev);
352
        rOutDev.SetFont(aFont);
353
#endif
354
355
0
        setupLayoutMode( rOutDev, mnTextDirection );
356
357
0
        if( maLogicalAdvancements.hasElements() )
358
0
        {
359
            // TODO(P2): cache that
360
0
            KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, viewState, renderState));
361
0
            std::span<const sal_Bool> aKashidaArray(maKashidaPositions.getConstArray(), maKashidaPositions.getLength());
362
363
            // TODO(F3): ensure correct length and termination for DX
364
            // array (last entry _must_ contain the overall width)
365
366
0
            rOutDev.DrawTextArray( rOutpos,
367
0
                                   maText.Text,
368
0
                                   aOffsets,
369
0
                                   aKashidaArray,
370
0
                                   ::canvastools::numeric_cast<sal_uInt16>(maText.StartPosition),
371
0
                                   ::canvastools::numeric_cast<sal_uInt16>(maText.Length) );
372
0
        }
373
0
        else
374
0
        {
375
0
            rOutDev.DrawText( rOutpos,
376
0
                              maText.Text,
377
0
                              ::canvastools::numeric_cast<sal_uInt16>(maText.StartPosition),
378
0
                              ::canvastools::numeric_cast<sal_uInt16>(maText.Length) );
379
0
        }
380
0
    }
381
382
    namespace
383
    {
384
        class OffsetTransformer
385
        {
386
        public:
387
            explicit OffsetTransformer( ::basegfx::B2DHomMatrix aMat ) :
388
0
                maMatrix(std::move( aMat ))
389
0
            {
390
0
            }
391
392
            sal_Int32 operator()( const double& rOffset )
393
0
            {
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
402
                // component.
403
404
                // TODO(F2): Handle non-horizontal advancements!
405
0
                return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset,
406
0
                                                maMatrix.get(1,0)*rOffset) );
407
0
            }
408
409
        private:
410
            ::basegfx::B2DHomMatrix maMatrix;
411
        };
412
    }
413
414
    KernArray TextLayout::setupTextOffsets(
415
                                       const uno::Sequence< double >&   inputOffsets,
416
                                       const rendering::ViewState&      viewState,
417
                                       const rendering::RenderState&    renderState     ) const
418
0
    {
419
0
        ::basegfx::B2DHomMatrix aMatrix;
420
421
0
        ::canvastools::mergeViewAndRenderTransform(aMatrix,
422
0
                                                     viewState,
423
0
                                                     renderState);
424
425
        // fill integer offsets
426
0
        KernArray outputOffsets;
427
0
        OffsetTransformer aTransform(aMatrix);
428
0
        std::for_each(inputOffsets.begin(), inputOffsets.end(),
429
0
                      [&outputOffsets, &aTransform](double n) {outputOffsets.push_back(aTransform(n)); } );
430
0
        return outputOffsets;
431
0
    }
432
433
    OUString SAL_CALL TextLayout::getImplementationName()
434
0
    {
435
0
        return u"VCLCanvas::TextLayout"_ustr;
436
0
    }
437
438
    sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName )
439
0
    {
440
0
        return cppu::supportsService( this, ServiceName );
441
0
    }
442
443
    uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames()
444
0
    {
445
0
        return { u"com.sun.star.rendering.TextLayout"_ustr };
446
0
    }
447
}
448
449
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */