Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/outdev/text.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
#include <sal/config.h>
21
22
#include <sal/log.hxx>
23
#include <basegfx/matrix/b2dhommatrix.hxx>
24
#include <basegfx/matrix/b2dhommatrixtools.hxx>
25
#include <tools/lineend.hxx>
26
#include <tools/debug.hxx>
27
#include <comphelper/configuration.hxx>
28
29
#include <vcl/ctrl.hxx>
30
#include <vcl/metaact.hxx>
31
#include <vcl/metric.hxx>
32
#include <vcl/mnemonic.hxx>
33
#include <vcl/textrectinfo.hxx>
34
#include <vcl/virdev.hxx>
35
#include <vcl/sysdata.hxx>
36
37
#include <ImplLayoutArgs.hxx>
38
#include <ImplOutDevData.hxx>
39
#include <drawmode.hxx>
40
#include <salgdi.hxx>
41
#include <svdata.hxx>
42
#include <textlayout.hxx>
43
#include <textlineinfo.hxx>
44
#include <impglyphitem.hxx>
45
#include <TextLayoutCache.hxx>
46
#include <font/PhysicalFontFace.hxx>
47
48
#include <memory>
49
#include <optional>
50
51
1.06k
#define TEXT_DRAW_ELLIPSIS  (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
52
53
void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
54
11.7M
{
55
11.7M
    if( mpMetaFile )
56
1.11M
        mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) );
57
58
11.7M
    mnTextLayoutMode = nTextLayoutMode;
59
11.7M
}
60
61
void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage )
62
26.4M
{
63
26.4M
    if( mpMetaFile )
64
1.00M
        mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) );
65
66
26.4M
    meTextLanguage = eTextLanguage;
67
26.4M
}
68
69
void OutputDevice::ImplInitTextColor()
70
43.7k
{
71
43.7k
    DBG_TESTSOLARMUTEX();
72
73
43.7k
    if ( mbInitTextColor )
74
17.5k
    {
75
17.5k
        mpGraphics->SetTextColor( GetTextColor() );
76
17.5k
        mbInitTextColor = false;
77
17.5k
    }
78
43.7k
}
79
80
OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth,
81
                                        DrawTextFlags nStyle ) const
82
0
{
83
0
    vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this));
84
0
    return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle);
85
0
}
86
87
void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY,
88
                                     tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight )
89
2.20M
{
90
2.20M
    tools::Long nX = nDistX;
91
2.20M
    tools::Long nY = nDistY;
92
93
2.20M
    Degree10 nOrientation = mpFontInstance->mnOrientation;
94
2.20M
    if ( nOrientation )
95
1.73M
    {
96
        // Rotate rect without rounding problems for 90 degree rotations
97
1.73M
        if ( !(nOrientation % 900_deg10) )
98
6.43k
        {
99
6.43k
            if ( nOrientation == 900_deg10 )
100
6.43k
            {
101
6.43k
                tools::Long nTemp = nX;
102
6.43k
                nX = nY;
103
6.43k
                nY = -nTemp;
104
6.43k
                nTemp = nWidth;
105
6.43k
                nWidth = nHeight;
106
6.43k
                nHeight = nTemp;
107
6.43k
                nY -= nHeight;
108
6.43k
            }
109
3
            else if ( nOrientation == 1800_deg10 )
110
3
            {
111
3
                nX = -nX;
112
3
                nY = -nY;
113
3
                nX -= nWidth;
114
3
                nY -= nHeight;
115
3
            }
116
0
            else /* ( nOrientation == 2700 ) */
117
0
            {
118
0
                tools::Long nTemp = nX;
119
0
                nX = -nY;
120
0
                nY = nTemp;
121
0
                nTemp = nWidth;
122
0
                nWidth = nHeight;
123
0
                nHeight = nTemp;
124
0
                nX -= nWidth;
125
0
            }
126
6.43k
        }
127
1.72M
        else
128
1.72M
        {
129
1.72M
            nX += nBaseX;
130
1.72M
            nY += nBaseY;
131
            // inflate because polygons are drawn smaller
132
1.72M
            tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
133
1.72M
            tools::Polygon   aPoly( aRect );
134
1.72M
            aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
135
1.72M
            ImplDrawPolygon( aPoly );
136
1.72M
            return;
137
1.72M
        }
138
1.73M
    }
139
140
478k
    nX += nBaseX;
141
478k
    nY += nBaseY;
142
478k
    mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code
143
144
478k
}
145
146
void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout )
147
1.05k
{
148
1.05k
    const double nWidth = rSalLayout.GetTextWidth();
149
1.05k
    const basegfx::B2DPoint aBase = rSalLayout.DrawBase();
150
1.05k
    const tools::Long nX = aBase.getX();
151
1.05k
    const tools::Long nY = aBase.getY();
152
153
1.05k
    if ( mbLineColor || mbInitLineColor )
154
637
    {
155
637
        mpGraphics->SetLineColor();
156
637
        mbInitLineColor = true;
157
637
    }
158
1.05k
    mpGraphics->SetFillColor( GetTextFillColor() );
159
1.05k
    mbInitFillColor = true;
160
161
1.05k
    ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent),
162
1.05k
                      nWidth,
163
1.05k
                      mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent );
164
1.05k
}
165
166
tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const
167
0
{
168
0
    basegfx::B2DPoint aPoint = rSalLayout.GetDrawPosition();
169
0
    tools::Long nX = aPoint.getX();
170
0
    tools::Long nY = aPoint.getY();
171
172
0
    double nWidth = rSalLayout.GetTextWidth();
173
0
    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
174
175
0
    nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
176
177
0
    if ( mpFontInstance->mnOrientation )
178
0
    {
179
0
        tools::Long nBaseX = nX, nBaseY = nY;
180
0
        if ( !(mpFontInstance->mnOrientation % 900_deg10) )
181
0
        {
182
0
            tools::Long nX2 = nX+nWidth;
183
0
            tools::Long nY2 = nY+nHeight;
184
185
0
            Point aBasePt( nBaseX, nBaseY );
186
0
            aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation );
187
0
            aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation );
188
0
            nWidth = nX2-nX;
189
0
            nHeight = nY2-nY;
190
0
        }
191
0
        else
192
0
        {
193
            // inflate by +1+1 because polygons are drawn smaller
194
0
            tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
195
0
            tools::Polygon   aPoly( aRect );
196
0
            aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
197
0
            return aPoly.GetBoundRect();
198
0
        }
199
0
    }
200
201
0
    return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
202
0
}
203
204
bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout )
205
0
{
206
0
    tools::Long nX = rSalLayout.DrawBase().getX();
207
0
    tools::Long nY = rSalLayout.DrawBase().getY();
208
209
0
    tools::Rectangle aBoundRect;
210
0
    rSalLayout.DrawBase() = basegfx::B2DPoint( 0, 0 );
211
0
    rSalLayout.DrawOffset() = basegfx::B2DPoint{ 0, 0 };
212
0
    if (basegfx::B2DRectangle r; rSalLayout.GetBoundRect(r))
213
0
    {
214
0
        aBoundRect = SalLayout::BoundRect2Rectangle(r);
215
0
    }
216
0
    else
217
0
    {
218
        // guess vertical text extents if GetBoundRect failed
219
0
        double nRight = rSalLayout.GetTextWidth();
220
0
        tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
221
0
        tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
222
0
        aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop );
223
0
    }
224
225
    // cache virtual device for rotation
226
0
    if (!mpOutDevData->mpRotateDev)
227
0
        mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this);
228
0
    VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
229
230
    // size it accordingly
231
0
    if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
232
0
        return false;
233
234
0
    const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
235
0
    vcl::Font aFont( GetFont() );
236
0
    aFont.SetOrientation( 0_deg10 );
237
0
    aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) );
238
0
    pVDev->SetFont( aFont );
239
0
    pVDev->SetTextColor( COL_BLACK );
240
0
    pVDev->SetTextFillColor();
241
0
    if (!pVDev->InitFont())
242
0
        return false;
243
0
    pVDev->ImplInitTextColor();
244
245
    // draw text into upper left corner
246
0
    rSalLayout.DrawBase().adjustX(-aBoundRect.Left());
247
0
    rSalLayout.DrawBase().adjustY(-aBoundRect.Top());
248
0
    rSalLayout.DrawText( *pVDev->mpGraphics );
249
250
0
    Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
251
0
    if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) )
252
0
        return false;
253
254
    // calculate rotation offset
255
0
    tools::Polygon aPoly( aBoundRect );
256
0
    aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation );
257
0
    Point aPoint = aPoly.GetBoundRect().TopLeft();
258
0
    aPoint += Point( nX, nY );
259
260
    // mask output with text colored bitmap
261
0
    GDIMetaFile* pOldMetaFile = mpMetaFile;
262
0
    tools::Long nOldOffX = mnOutOffX;
263
0
    tools::Long nOldOffY = mnOutOffY;
264
0
    bool bOldMap = mbMap;
265
266
0
    mnOutOffX   = 0;
267
0
    mnOutOffY   = 0;
268
0
    mpMetaFile  = nullptr;
269
0
    EnableMapMode( false );
270
271
0
    DrawMask( aPoint, aBmp, GetTextColor() );
272
273
0
    EnableMapMode( bOldMap );
274
0
    mnOutOffX   = nOldOffX;
275
0
    mnOutOffY   = nOldOffY;
276
0
    mpMetaFile  = pOldMetaFile;
277
278
0
    return true;
279
0
}
280
281
void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout,
282
                                       bool bTextLines)
283
39.3k
{
284
39.3k
    if( mpFontInstance->mnOwnOrientation )
285
0
        if( ImplDrawRotateText( rSalLayout ) )
286
0
            return;
287
288
39.3k
    auto nOldX = rSalLayout.DrawBase().getX();
289
39.3k
    if( HasMirroredGraphics() )
290
0
    {
291
0
        tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
292
0
        auto x = rSalLayout.DrawBase().getX();
293
0
        rSalLayout.DrawBase().setX( w - 1 - x );
294
0
        if( !IsRTLEnabled() )
295
0
        {
296
0
            OutputDevice *pOutDevRef = this;
297
            // mirror this window back
298
0
            tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
299
0
            rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ;
300
0
        }
301
0
    }
302
39.3k
    else if( IsRTLEnabled() )
303
0
    {
304
0
        OutputDevice *pOutDevRef = this;
305
306
        // mirror this window back
307
0
        tools::Long devX = pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
308
0
        rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX );
309
0
    }
310
311
39.3k
    rSalLayout.DrawText( *mpGraphics );
312
39.3k
    rSalLayout.DrawBase().setX( nOldX );
313
314
39.3k
    if( bTextLines )
315
15.5k
        ImplDrawTextLines( rSalLayout,
316
15.5k
            maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
317
15.5k
            maFont.IsWordLineMode(), maFont.IsUnderlineAbove() );
318
319
    // emphasis marks
320
39.3k
    if( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
321
9.11k
        ImplDrawEmphasisMarks( rSalLayout );
322
39.3k
}
323
324
void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout )
325
5.96k
{
326
5.96k
    Color       aOldColor           = GetTextColor();
327
5.96k
    Color       aOldTextLineColor   = GetTextLineColor();
328
5.96k
    Color       aOldOverlineColor   = GetOverlineColor();
329
5.96k
    FontRelief  eRelief             = maFont.GetRelief();
330
331
5.96k
    basegfx::B2DPoint aOrigPos = rSalLayout.DrawBase();
332
5.96k
    if ( eRelief != FontRelief::NONE )
333
1.93k
    {
334
1.93k
        Color   aReliefColor( COL_LIGHTGRAY );
335
1.93k
        Color   aTextColor( aOldColor );
336
337
1.93k
        Color   aTextLineColor( aOldTextLineColor );
338
1.93k
        Color   aOverlineColor( aOldOverlineColor );
339
340
        // we don't have an automatic color, so black is always drawn on white
341
1.93k
        if ( aTextColor == COL_BLACK )
342
1.68k
            aTextColor = COL_WHITE;
343
1.93k
        if ( aTextLineColor == COL_BLACK )
344
6
            aTextLineColor = COL_WHITE;
345
1.93k
        if ( aOverlineColor == COL_BLACK )
346
56
            aOverlineColor = COL_WHITE;
347
348
        // relief-color is black for white text, in all other cases
349
        // we set this to LightGray
350
        // coverity[copy_paste_error: FALSE] - this is intentional
351
1.93k
        if ( aTextColor == COL_WHITE )
352
1.68k
            aReliefColor = COL_BLACK;
353
1.93k
        SetTextLineColor( aReliefColor );
354
1.93k
        SetOverlineColor( aReliefColor );
355
1.93k
        SetTextColor( aReliefColor );
356
1.93k
        ImplInitTextColor();
357
358
        // calculate offset - for high resolution printers the offset
359
        // should be greater so that the effect is visible
360
1.93k
        tools::Long nOff = 1;
361
1.93k
        nOff += mnDPIX/300;
362
363
1.93k
        if ( eRelief == FontRelief::Engraved )
364
588
            nOff = -nOff;
365
366
1.93k
        auto aPrevOffset = rSalLayout.DrawOffset();
367
1.93k
        rSalLayout.DrawOffset()
368
1.93k
            += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
369
1.93k
        ImplDrawTextDirect(rSalLayout, mbTextLines);
370
1.93k
        rSalLayout.DrawOffset() = aPrevOffset;
371
372
1.93k
        SetTextLineColor( aTextLineColor );
373
1.93k
        SetOverlineColor( aOverlineColor );
374
1.93k
        SetTextColor( aTextColor );
375
1.93k
        ImplInitTextColor();
376
1.93k
        ImplDrawTextDirect( rSalLayout, mbTextLines );
377
378
1.93k
        SetTextLineColor( aOldTextLineColor );
379
1.93k
        SetOverlineColor( aOldOverlineColor );
380
381
1.93k
        if ( aTextColor != aOldColor )
382
1.68k
        {
383
1.68k
            SetTextColor( aOldColor );
384
1.68k
            ImplInitTextColor();
385
1.68k
        }
386
1.93k
    }
387
4.03k
    else
388
4.03k
    {
389
4.03k
        if ( maFont.IsShadow() )
390
1.86k
        {
391
1.86k
            tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24);
392
1.86k
            if ( maFont.IsOutline() )
393
859
                nOff++;
394
1.86k
            SetTextLineColor();
395
1.86k
            SetOverlineColor();
396
1.86k
            if ( (GetTextColor() == COL_BLACK)
397
74
            ||   (GetTextColor().GetLuminance() < 8) )
398
1.81k
                SetTextColor( COL_LIGHTGRAY );
399
47
            else
400
47
                SetTextColor( COL_BLACK );
401
1.86k
            ImplInitTextColor();
402
1.86k
            rSalLayout.DrawBase() += basegfx::B2DPoint( nOff, nOff );
403
1.86k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
404
1.86k
            rSalLayout.DrawBase() -= basegfx::B2DPoint( nOff, nOff );
405
1.86k
            SetTextColor( aOldColor );
406
1.86k
            SetTextLineColor( aOldTextLineColor );
407
1.86k
            SetOverlineColor( aOldOverlineColor );
408
1.86k
            ImplInitTextColor();
409
410
1.86k
            if ( !maFont.IsOutline() )
411
1.00k
                ImplDrawTextDirect( rSalLayout, mbTextLines );
412
1.86k
        }
413
414
4.03k
        if ( maFont.IsOutline() )
415
3.02k
        {
416
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,-1);
417
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
418
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+1);
419
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
420
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+0);
421
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
422
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+1);
423
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
424
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,+1);
425
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
426
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,-1);
427
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
428
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,-1);
429
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
430
3.02k
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+0);
431
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
432
3.02k
            rSalLayout.DrawBase() = aOrigPos;
433
434
3.02k
            SetTextColor( COL_WHITE );
435
3.02k
            SetTextLineColor( COL_WHITE );
436
3.02k
            SetOverlineColor( COL_WHITE );
437
3.02k
            ImplInitTextColor();
438
3.02k
            ImplDrawTextDirect( rSalLayout, mbTextLines );
439
3.02k
            SetTextColor( aOldColor );
440
3.02k
            SetTextLineColor( aOldTextLineColor );
441
3.02k
            SetOverlineColor( aOldOverlineColor );
442
3.02k
            ImplInitTextColor();
443
3.02k
        }
444
4.03k
    }
445
5.96k
}
446
447
void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
448
11.5k
{
449
11.5k
    if( mbInitClipRegion )
450
2.23k
        InitClipRegion();
451
11.5k
    if( mbOutputClipped )
452
235
        return;
453
11.2k
    if( mbInitTextColor )
454
3.86k
        ImplInitTextColor();
455
456
11.2k
    rSalLayout.DrawBase() += basegfx::B2DPoint(mnTextOffX, mnTextOffY);
457
458
11.2k
    if( IsTextFillColor() )
459
1.05k
        ImplDrawTextBackground( rSalLayout );
460
461
11.2k
    if( mbTextSpecial )
462
5.96k
        ImplDrawSpecialText( rSalLayout );
463
5.31k
    else
464
5.31k
        ImplDrawTextDirect( rSalLayout, mbTextLines );
465
11.2k
}
466
467
void OutputDevice::SetTextColor( const Color& rColor )
468
11.5M
{
469
470
11.5M
    Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
471
472
11.5M
    if ( mpMetaFile )
473
881
        mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
474
475
11.5M
    if ( maTextColor != aColor )
476
10.0M
    {
477
10.0M
        maTextColor = aColor;
478
10.0M
        mbInitTextColor = true;
479
10.0M
    }
480
11.5M
}
481
482
void OutputDevice::SetTextFillColor()
483
613k
{
484
613k
    if ( mpMetaFile )
485
0
        mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );
486
487
613k
    if ( maFont.GetColor() != COL_TRANSPARENT ) {
488
625
        maFont.SetFillColor( COL_TRANSPARENT );
489
625
    }
490
613k
    if ( !maFont.IsTransparent() )
491
785
        maFont.SetTransparent( true );
492
613k
}
493
494
void OutputDevice::SetTextFillColor( const Color& rColor )
495
10.9k
{
496
10.9k
    Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
497
498
10.9k
    if ( mpMetaFile )
499
0
        mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
500
501
10.9k
    if ( maFont.GetFillColor() != aColor )
502
784
        maFont.SetFillColor( aColor );
503
10.9k
    if ( maFont.IsTransparent() != rColor.IsTransparent() )
504
1.09k
        maFont.SetTransparent( rColor.IsTransparent() );
505
10.9k
}
506
507
Color OutputDevice::GetTextFillColor() const
508
10.2k
{
509
10.2k
    if ( maFont.IsTransparent() )
510
9.15k
        return COL_TRANSPARENT;
511
1.09k
    else
512
1.09k
        return maFont.GetFillColor();
513
10.2k
}
514
515
void OutputDevice::SetTextAlign( TextAlign eAlign )
516
580k
{
517
580k
    if ( mpMetaFile )
518
0
        mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
519
520
580k
    if ( maFont.GetAlignment() != eAlign )
521
2.03k
    {
522
2.03k
        maFont.SetAlignment( eAlign );
523
2.03k
        mbNewFont = true;
524
2.03k
    }
525
580k
}
526
527
vcl::Region OutputDevice::GetOutputBoundsClipRegion() const
528
0
{
529
0
    return GetClipRegion();
530
0
}
531
532
const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE;
533
534
void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
535
                             sal_Int32 nIndex, sal_Int32 nLen,
536
                             std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
537
                             const SalLayoutGlyphs* pLayoutCache
538
                             )
539
255k
{
540
255k
    assert(!is_double_buffered_window());
541
542
255k
    if( (nLen < 0) || (nIndex + nLen > rStr.getLength()))
543
253k
    {
544
253k
        nLen = rStr.getLength() - nIndex;
545
253k
    }
546
547
255k
    if (mpOutDevData->mpRecordLayout)
548
0
    {
549
0
        pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
550
0
        pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
551
0
    }
552
553
#if OSL_DEBUG_LEVEL > 2
554
    SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")");
555
#endif
556
557
255k
    if ( mpMetaFile )
558
235k
        mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
559
255k
    if( pVector )
560
0
    {
561
0
        vcl::Region aClip(GetOutputBoundsClipRegion());
562
563
0
        if (mpOutDevData->mpRecordLayout)
564
0
        {
565
0
            mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() );
566
0
            aClip.Intersect( mpOutDevData->maRecordRect );
567
0
        }
568
0
        if( ! aClip.IsNull() )
569
0
        {
570
0
            std::vector< tools::Rectangle > aTmp;
571
0
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
572
573
0
            bool bInserted = false;
574
0
            for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
575
0
            {
576
0
                bool bAppend = false;
577
578
0
                if( aClip.Overlaps( *it ) )
579
0
                    bAppend = true;
580
0
                else if( rStr[ nIndex ] == ' ' && bInserted )
581
0
                {
582
0
                    std::vector< tools::Rectangle >::const_iterator next = it;
583
0
                    ++next;
584
0
                    if( next != aTmp.end() && aClip.Overlaps( *next ) )
585
0
                        bAppend = true;
586
0
                }
587
588
0
                if( bAppend )
589
0
                {
590
0
                    pVector->push_back( *it );
591
0
                    if( pDisplayText )
592
0
                        *pDisplayText += OUStringChar(rStr[ nIndex ]);
593
0
                    bInserted = true;
594
0
                }
595
0
            }
596
0
        }
597
0
        else
598
0
        {
599
0
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
600
0
            if( pDisplayText )
601
0
                *pDisplayText += rStr.subView( nIndex, nLen );
602
0
        }
603
0
    }
604
605
255k
    if ( !IsDeviceOutputNecessary() || pVector )
606
253k
        return;
607
608
1.75k
    if(mpFontInstance)
609
        // do not use cache with modified string
610
1.70k
        if(mpFontInstance->mpConversion)
611
0
            pLayoutCache = nullptr;
612
613
1.75k
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, {}, eDefaultLayout, nullptr, pLayoutCache);
614
1.75k
    if(pSalLayout)
615
1.75k
    {
616
1.75k
        ImplDrawText( *pSalLayout );
617
1.75k
    }
618
1.75k
}
619
620
tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
621
     vcl::text::TextLayoutCache const*const pLayoutCache,
622
     SalLayoutGlyphs const*const pSalLayoutCache) const
623
21.3M
{
624
21.3M
    double nWidth = GetTextWidthDouble(rStr, nIndex, nLen, pLayoutCache, pSalLayoutCache);
625
21.3M
    return basegfx::fround<tools::Long>(nWidth);
626
21.3M
}
627
628
double OutputDevice::GetTextWidthDouble(const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
629
                                        vcl::text::TextLayoutCache const* const pLayoutCache,
630
                                        SalLayoutGlyphs const* const pSalLayoutCache) const
631
21.3M
{
632
21.3M
    return GetTextArray(rStr, nullptr, nIndex, nLen, false, pLayoutCache, pSalLayoutCache);
633
21.3M
}
634
635
tools::Long OutputDevice::GetTextHeight() const
636
29.0M
{
637
29.0M
    if (!InitFont())
638
181
        return 0;
639
640
29.0M
    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
641
642
29.0M
    if ( mbMap )
643
28.7M
        nHeight = ImplDevicePixelToLogicHeight( nHeight );
644
645
29.0M
    return nHeight;
646
29.0M
}
647
648
double OutputDevice::GetTextHeightDouble() const
649
0
{
650
0
    if (!InitFont())
651
0
        return 0;
652
653
0
    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
654
655
0
    return ImplDevicePixelToLogicHeightDouble(nHeight);
656
0
}
657
658
float OutputDevice::approximate_char_width() const
659
3
{
660
    //note pango uses "The quick brown fox jumps over the lazy dog." for english
661
    //and has a bunch of per-language strings which corresponds somewhat with
662
    //makeRepresentativeText in include/svtools/sampletext.hxx
663
3
    return GetTextWidth(u"aemnnxEM"_ustr) / 8.0;
664
3
}
665
666
float OutputDevice::approximate_digit_width() const
667
0
{
668
0
    return GetTextWidth(u"0123456789"_ustr) / 10.0;
669
0
}
670
671
void OutputDevice::DrawPartialTextArray(const Point& rStartPt, const OUString& rStr,
672
                                        KernArraySpan pDXArray,
673
                                        std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
674
                                        sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
675
                                        SalLayoutFlags flags, const SalLayoutGlyphs* pLayoutCache)
676
436k
{
677
436k
    assert(!is_double_buffered_window());
678
679
436k
    if (nLen < 0 || nIndex + nLen >= rStr.getLength())
680
15.7k
    {
681
15.7k
        nLen = rStr.getLength() - nIndex;
682
15.7k
    }
683
684
436k
    if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
685
11.9k
    {
686
11.9k
        nPartLen = rStr.getLength() - nPartIndex;
687
11.9k
    }
688
689
436k
    if (mpMetaFile)
690
434k
    {
691
434k
        mpMetaFile->AddAction(new MetaTextArrayAction(rStartPt, rStr, pDXArray, pKashidaArray,
692
434k
                                                      nPartIndex, nPartLen, nIndex, nLen));
693
434k
    }
694
695
436k
    if (!IsDeviceOutputNecessary())
696
434k
        return;
697
698
1.15k
    if (!mpGraphics && !AcquireGraphics())
699
0
        return;
700
701
1.15k
    assert(mpGraphics);
702
1.15k
    if (mbInitClipRegion)
703
81
        InitClipRegion();
704
705
1.15k
    if (mbOutputClipped)
706
157
        return;
707
708
    // Adding the UnclusteredGlyphs flag during layout enables per-glyph styling.
709
995
    std::unique_ptr<SalLayout> pSalLayout
710
995
        = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXArray, pKashidaArray,
711
995
                     flags | SalLayoutFlags::UnclusteredGlyphs, nullptr, pLayoutCache,
712
995
                     /*pivot cluster*/ nPartIndex,
713
995
                     /*min cluster*/ nPartIndex,
714
995
                     /*end cluster*/ nPartIndex + nPartLen);
715
716
995
    if (pSalLayout)
717
969
    {
718
969
        ImplDrawText(*pSalLayout);
719
969
    }
720
995
}
721
722
void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
723
                                  KernArraySpan pDXAry,
724
                                  std::span<const sal_Bool> pKashidaAry,
725
                                  sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
726
                                  const SalLayoutGlyphs* pSalLayoutCache )
727
31.7k
{
728
31.7k
    assert(!is_double_buffered_window());
729
730
31.7k
    if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
731
30.7k
    {
732
30.7k
        nLen = rStr.getLength() - nIndex;
733
30.7k
    }
734
31.7k
    if ( mpMetaFile )
735
25.1k
        mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) );
736
737
31.7k
    if ( !IsDeviceOutputNecessary() )
738
25.1k
        return;
739
6.62k
    if( !mpGraphics && !AcquireGraphics() )
740
0
        return;
741
6.62k
    assert(mpGraphics);
742
6.62k
    if( mbInitClipRegion )
743
1.29k
        InitClipRegion();
744
6.62k
    if( mbOutputClipped )
745
353
        return;
746
747
6.26k
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache);
748
6.26k
    if( pSalLayout )
749
6.16k
    {
750
6.16k
        ImplDrawText( *pSalLayout );
751
6.16k
    }
752
6.26k
}
753
754
double
755
OutputDevice::GetTextArray(const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex,
756
                           sal_Int32 nLen, bool bCaret,
757
                           vcl::text::TextLayoutCache const* const pLayoutCache,
758
                           SalLayoutGlyphs const* const pSalLayoutCache, std::optional<tools::Rectangle>* pBounds) const
759
30.1M
{
760
30.1M
    return GetPartialTextArray(rStr, pKernArray, nIndex, nLen, nIndex, nLen, bCaret, pLayoutCache,
761
30.1M
                               pSalLayoutCache, pBounds);
762
30.1M
}
763
764
double
765
OutputDevice::GetPartialTextArray(const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex,
766
                                  sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
767
                                  bool bCaret, const vcl::text::TextLayoutCache* pLayoutCache,
768
                                  const SalLayoutGlyphs* pSalLayoutCache, std::optional<tools::Rectangle>* pBounds) const
769
30.7M
{
770
30.7M
    if (nIndex >= rStr.getLength())
771
10.2M
    {
772
10.2M
        return {}; // TODO: this looks like a buggy caller?
773
10.2M
    }
774
775
20.5M
    if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
776
14.3M
    {
777
14.3M
        nLen = rStr.getLength() - nIndex;
778
14.3M
    }
779
780
20.5M
    if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
781
14.3M
    {
782
14.3M
        nPartLen = rStr.getLength() - nPartIndex;
783
14.3M
    }
784
785
20.5M
    KernArray* pDXAry = pKernArray;
786
787
    // do layout
788
20.5M
    std::unique_ptr<SalLayout> pSalLayout;
789
20.5M
    if (nIndex == nPartIndex && nLen == nPartLen)
790
20.1M
    {
791
20.1M
        pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
792
20.1M
                                pLayoutCache, pSalLayoutCache);
793
20.1M
    }
794
393k
    else
795
393k
    {
796
393k
        pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
797
393k
                                pLayoutCache, pSalLayoutCache,
798
393k
                                /*pivot cluster*/ nPartIndex,
799
393k
                                /*min cluster*/ nPartIndex,
800
393k
                                /*end cluster*/ nPartIndex + nPartLen);
801
393k
    }
802
803
20.5M
    if( !pSalLayout )
804
0
    {
805
        // The caller expects this to init the elements of pDXAry.
806
        // Adapting all the callers to check that GetTextArray succeeded seems
807
        // too much work.
808
        // Init here to 0 only in the (rare) error case, so that any missing
809
        // element init in the happy case will still be found by tools,
810
        // and hope that is sufficient.
811
0
        if (pDXAry)
812
0
        {
813
0
            pDXAry->resize(nPartLen);
814
0
            std::fill(pDXAry->begin(), pDXAry->end(), 0);
815
0
        }
816
817
0
        return {};
818
0
    }
819
820
20.5M
    std::vector<double> aDXPixelArray;
821
20.5M
    std::vector<double>* pDXPixelArray = nullptr;
822
20.5M
    if(pDXAry)
823
9.33M
    {
824
9.33M
        aDXPixelArray.resize(nPartLen);
825
9.33M
        pDXPixelArray = &aDXPixelArray;
826
9.33M
    }
827
828
20.5M
    double nWidth = 0.0;
829
830
    // Fall back to the unbounded DX array when there is no expanded layout context. This is
831
    // necessary for certain situations where characters are appended to the input string, such as
832
    // automatic ellipsis.
833
20.5M
    if (nIndex == nPartIndex && nLen == nPartLen)
834
20.1M
    {
835
20.1M
        nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
836
20.1M
    }
837
393k
    else
838
393k
    {
839
393k
        nWidth = pSalLayout->FillPartialDXArray(pDXPixelArray, bCaret ? rStr : OUString(),
840
393k
                                                nPartIndex - nIndex, nPartLen);
841
393k
    }
842
843
    // convert virtual char widths to virtual absolute positions
844
20.5M
    if( pDXPixelArray )
845
9.33M
    {
846
814M
        for (int i = 1; i < nPartLen; ++i)
847
804M
        {
848
804M
            (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
849
804M
        }
850
9.33M
    }
851
852
    // convert from font units to logical units
853
20.5M
    if (pDXPixelArray)
854
9.33M
    {
855
9.33M
        assert(pKernArray && "pDXPixelArray depends on pKernArray existing");
856
9.33M
        if (mbMap)
857
9.33M
        {
858
822M
            for (int i = 0; i < nPartLen; ++i)
859
813M
                (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidthDouble((*pDXPixelArray)[i]);
860
9.33M
        }
861
9.33M
    }
862
863
20.5M
    if (pDXAry)
864
9.33M
    {
865
9.33M
        pDXAry->resize(nPartLen);
866
823M
        for (int i = 0; i < nPartLen; ++i)
867
814M
            (*pDXAry)[i] = (*pDXPixelArray)[i];
868
9.33M
    }
869
870
20.5M
    if (pBounds)
871
1.28M
    {
872
1.28M
        basegfx::B2DRectangle stRect;
873
1.28M
        if (pSalLayout->GetBoundRect(stRect))
874
1.27M
        {
875
1.27M
            auto stRect2 = SalLayout::BoundRect2Rectangle(stRect);
876
1.27M
            *pBounds = ImplDevicePixelToLogic(stRect2);
877
1.27M
        }
878
1.28M
    }
879
880
20.5M
    return ImplDevicePixelToLogicWidthDouble(nWidth);
881
20.5M
}
882
883
void OutputDevice::GetCaretPositions( const OUString& rStr, KernArray& rCaretPos,
884
                                      sal_Int32 nIndex, sal_Int32 nLen,
885
                                      const SalLayoutGlyphs* pGlyphs ) const
886
0
{
887
888
0
    if( nIndex >= rStr.getLength() )
889
0
        return;
890
0
    if( nIndex+nLen >= rStr.getLength() )
891
0
        nLen = rStr.getLength() - nIndex;
892
893
0
    sal_Int32 nCaretPos = nLen * 2;
894
0
    rCaretPos.resize(nCaretPos);
895
896
    // do layout
897
0
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
898
0
                                                       eDefaultLayout, nullptr, pGlyphs);
899
0
    if( !pSalLayout )
900
0
    {
901
0
        std::fill(rCaretPos.begin(), rCaretPos.end(), -1);
902
0
        return;
903
0
    }
904
905
0
    std::vector<double> aCaretPixelPos;
906
0
    pSalLayout->GetCaretPositions(aCaretPixelPos, rStr);
907
908
    // fixup unknown caret positions
909
0
    int i;
910
0
    for (i = 0; i < nCaretPos; ++i)
911
0
        if (aCaretPixelPos[i] >= 0)
912
0
            break;
913
0
    double nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1;
914
0
    for (i = 0; i < nCaretPos; ++i)
915
0
    {
916
0
        if (aCaretPixelPos[i] >= 0)
917
0
            nXPos = aCaretPixelPos[i];
918
0
        else
919
0
            aCaretPixelPos[i] = nXPos;
920
0
    }
921
922
    // handle window mirroring
923
0
    if( IsRTLEnabled() )
924
0
    {
925
0
        double nWidth = pSalLayout->GetTextWidth();
926
0
        for (i = 0; i < nCaretPos; ++i)
927
0
            aCaretPixelPos[i] = nWidth - aCaretPixelPos[i] - 1;
928
0
    }
929
930
    // convert from font units to logical units
931
0
    if( mbMap )
932
0
    {
933
0
        for (i = 0; i < nCaretPos; ++i)
934
0
            aCaretPixelPos[i] = ImplDevicePixelToLogicWidthDouble(aCaretPixelPos[i]);
935
0
    }
936
937
0
    for (i = 0; i < nCaretPos; ++i)
938
0
        rCaretPos[i] = aCaretPixelPos[i];
939
0
}
940
941
void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 nWidth,
942
                                    const OUString& rStr,
943
                                    sal_Int32 nIndex, sal_Int32 nLen)
944
2.62k
{
945
2.62k
    assert(!is_double_buffered_window());
946
947
2.62k
    if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
948
2.56k
    {
949
2.56k
        nLen = rStr.getLength() - nIndex;
950
2.56k
    }
951
952
2.62k
    if ( mpMetaFile )
953
6
        mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
954
955
2.62k
    if ( !IsDeviceOutputNecessary() )
956
6
        return;
957
958
2.61k
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
959
2.61k
    if( pSalLayout )
960
2.61k
    {
961
2.61k
        ImplDrawText( *pSalLayout );
962
2.61k
    }
963
2.61k
}
964
965
vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
966
                                                    const sal_Int32 nMinIndex, const sal_Int32 nLen,
967
                                                    double nPixelWidth,
968
                                                    SalLayoutFlags nLayoutFlags,
969
         vcl::text::TextLayoutCache const*const pLayoutCache) const
970
31.7M
{
971
31.7M
    assert(nMinIndex >= 0);
972
31.7M
    assert(nLen >= 0);
973
974
    // get string length for calculating extents
975
31.7M
    sal_Int32 nEndIndex = rStr.getLength();
976
31.7M
    if( nMinIndex + nLen < nEndIndex )
977
12.3M
        nEndIndex = nMinIndex + nLen;
978
979
    // don't bother if there is nothing to do
980
31.7M
    if( nEndIndex < nMinIndex )
981
0
        nEndIndex = nMinIndex;
982
983
31.7M
    nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex );
984
985
31.7M
    if( !maFont.IsKerning() )
986
8.93M
        nLayoutFlags |= SalLayoutFlags::DisableKerning;
987
31.7M
    if( maFont.GetKerning() & FontKerning::Asian )
988
16.5k
        nLayoutFlags |= SalLayoutFlags::KerningAsian;
989
31.7M
    if( maFont.IsVertical() )
990
470k
        nLayoutFlags |= SalLayoutFlags::Vertical;
991
31.7M
    if( maFont.IsFixKerning() ||
992
31.6M
        ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) )
993
215k
        nLayoutFlags |= SalLayoutFlags::DisableLigatures;
994
995
31.7M
    if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
996
29.2M
    {
997
29.2M
        sal_Int32 nSubstringLen = nEndIndex - nMinIndex;
998
29.2M
        rStr = LocalizeDigitsInString(rStr, meTextLanguage, nMinIndex, nSubstringLen);
999
29.2M
        nEndIndex = nMinIndex + nSubstringLen;
1000
29.2M
    }
1001
1002
    // right align for RTL text, DRAWPOS_REVERSED, RTL window style
1003
31.7M
    bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl);
1004
31.7M
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft )
1005
107k
        bRightAlign = false;
1006
31.6M
    else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight )
1007
0
        bRightAlign = true;
1008
    // SSA: hack for western office, ie text get right aligned
1009
    //      for debugging purposes of mirrored UI
1010
31.7M
    bool bRTLWindow = IsRTLEnabled();
1011
31.7M
    bRightAlign ^= bRTLWindow;
1012
31.7M
    if( bRightAlign )
1013
1.08M
        nLayoutFlags |= SalLayoutFlags::RightAlign;
1014
1015
    // set layout options
1016
31.7M
    vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
1017
1018
31.7M
    Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10;
1019
31.7M
    aLayoutArgs.SetOrientation( nOrientation );
1020
1021
31.7M
    aLayoutArgs.SetLayoutWidth( nPixelWidth );
1022
1023
31.7M
    return aLayoutArgs;
1024
31.7M
}
1025
1026
SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr,
1027
                                                 const sal_Int32 nMinIndex,
1028
                                                 const sal_Int32 nEndIndex ) const
1029
33.0M
{
1030
33.0M
    SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE;
1031
33.0M
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl )
1032
1.19M
        nLayoutFlags |= SalLayoutFlags::BiDiRtl;
1033
33.0M
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong )
1034
10.2M
        nLayoutFlags |= SalLayoutFlags::BiDiStrong;
1035
22.7M
    else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) )
1036
22.6M
    {
1037
        // Disable Bidi if no RTL hint and only known LTR codes used.
1038
22.6M
        bool bAllLtr = true;
1039
374M
        for (sal_Int32 i = nMinIndex; i < nEndIndex; i++)
1040
359M
        {
1041
            // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
1042
            // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
1043
            //                  hopefully no RTL character will be encoded there.
1044
359M
            if (rStr[i] > 0x052F)
1045
7.93M
            {
1046
7.93M
                bAllLtr = false;
1047
7.93M
                break;
1048
7.93M
            }
1049
359M
        }
1050
22.6M
        if (bAllLtr)
1051
14.7M
            nLayoutFlags |= SalLayoutFlags::BiDiStrong;
1052
22.6M
    }
1053
33.0M
    return nLayoutFlags;
1054
33.0M
}
1055
1056
static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr;
1057
1058
static inline bool IsTrackingFontMappingUse()
1059
26.1M
{
1060
26.1M
    return fontMappingUseData != nullptr;
1061
26.1M
}
1062
1063
static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout)
1064
0
{
1065
0
    assert(fontMappingUseData);
1066
0
    OUString originalName = originalFont.GetStyleName().isEmpty()
1067
0
        ? originalFont.GetFamilyName()
1068
0
        : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName();
1069
0
    std::vector<OUString> usedFontNames;
1070
0
    SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks
1071
0
    int level = 0;
1072
0
    while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++))
1073
0
    {
1074
0
        const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace();
1075
0
        OUString name = face->GetStyleName().isEmpty()
1076
0
            ? face->GetFamilyName()
1077
0
            : face->GetFamilyName() + "/" + face->GetStyleName();
1078
0
        usedFontNames.push_back( name );
1079
0
    }
1080
0
    for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData )
1081
0
    {
1082
0
        if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames )
1083
0
        {
1084
0
            ++item.mCount;
1085
0
            return;
1086
0
        }
1087
0
    }
1088
0
    fontMappingUseData->push_back( { originalName, std::move(usedFontNames), 1 } );
1089
0
}
1090
1091
void OutputDevice::StartTrackingFontMappingUse()
1092
0
{
1093
0
    delete fontMappingUseData;
1094
0
    fontMappingUseData = new FontMappingUseData;
1095
0
}
1096
1097
OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
1098
0
{
1099
0
    if(!fontMappingUseData)
1100
0
        return {};
1101
0
    FontMappingUseData ret = std::move( *fontMappingUseData );
1102
0
    delete fontMappingUseData;
1103
0
    fontMappingUseData = nullptr;
1104
0
    return ret;
1105
0
}
1106
1107
std::unique_ptr<SalLayout> OutputDevice::ImplLayout(
1108
    const OUString& rOrigStr, sal_Int32 nMinIndex, sal_Int32 nLen, const Point& rLogicalPos,
1109
    tools::Long nLogicalWidth, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray,
1110
    SalLayoutFlags flags, vcl::text::TextLayoutCache const* pLayoutCache,
1111
    const SalLayoutGlyphs* pGlyphs, std::optional<sal_Int32> nDrawOriginCluster,
1112
    std::optional<sal_Int32> nDrawMinCharPos, std::optional<sal_Int32> nDrawEndCharPos) const
1113
31.6M
{
1114
31.6M
    if (pGlyphs && !pGlyphs->IsValid())
1115
77
    {
1116
77
        SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
1117
77
        pGlyphs = nullptr;
1118
77
    }
1119
#ifdef DBG_UTIL
1120
    if (pGlyphs)
1121
    {
1122
        for( int level = 0;; ++level )
1123
        {
1124
            SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level);
1125
            if(glyphsImpl == nullptr)
1126
                break;
1127
            // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly.
1128
            // If the glyphs have already been used, the AdjustLayout() call below might have
1129
            // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need
1130
            // fallback from the base layout, but then GenericSalLayout::LayoutText()
1131
            // would not know to call SetNeedFallback()).
1132
            assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly);
1133
        }
1134
    }
1135
#endif
1136
1137
31.6M
    if (!InitFont())
1138
126
        return nullptr;
1139
1140
    // check string index and length
1141
31.6M
    if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
1142
4.34M
    {
1143
4.34M
        const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
1144
4.34M
        if( nNewLen <= 0 )
1145
15
            return nullptr;
1146
4.34M
        nLen = nNewLen;
1147
4.34M
    }
1148
1149
31.6M
    OUString aStr = rOrigStr;
1150
1151
    // recode string if needed
1152
31.6M
    if( mpFontInstance->mpConversion ) {
1153
0
        mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() );
1154
0
        pLayoutCache = nullptr; // don't use cache with modified string!
1155
0
        pGlyphs = nullptr;
1156
0
    }
1157
1158
31.6M
    double nPixelWidth = nLogicalWidth;
1159
31.6M
    if( nLogicalWidth && mbMap )
1160
1.32k
    {
1161
        // convert from logical units to physical units
1162
1.32k
        nPixelWidth = ImplLogicWidthToDeviceSubPixel(nLogicalWidth);
1163
1.32k
    }
1164
1165
31.6M
    vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
1166
31.6M
            nPixelWidth, flags, pLayoutCache);
1167
1168
31.6M
    if (nDrawOriginCluster.has_value())
1169
829k
    {
1170
829k
        aLayoutArgs.mnDrawOriginCluster = *nDrawOriginCluster;
1171
829k
    }
1172
1173
31.6M
    if (nDrawMinCharPos.has_value())
1174
829k
    {
1175
829k
        aLayoutArgs.mnDrawMinCharPos = *nDrawMinCharPos;
1176
829k
    }
1177
1178
31.6M
    if (nDrawEndCharPos.has_value())
1179
829k
    {
1180
829k
        aLayoutArgs.mnDrawEndCharPos = *nDrawEndCharPos;
1181
829k
    }
1182
1183
31.6M
    double nEndGlyphCoord(0);
1184
31.6M
    if (!pDXArray.empty() || !pKashidaArray.empty())
1185
462k
    {
1186
        // The provided advance and kashida arrays are indexed relative to the first visible cluster
1187
462k
        auto nJustMinCluster = nDrawMinCharPos.value_or(nMinIndex);
1188
462k
        auto nJustLen = nLen;
1189
462k
        if (nDrawEndCharPos.has_value())
1190
434k
        {
1191
434k
            nJustLen = *nDrawEndCharPos - nJustMinCluster;
1192
434k
        }
1193
1194
462k
        JustificationData stJustification{ nJustMinCluster, nJustLen };
1195
1196
462k
        if (!pDXArray.empty() && mbMap)
1197
459k
        {
1198
            // convert from logical units to font units without rounding,
1199
            // keeping accuracy for lower levels
1200
10.1M
            for (int i = 0; i < nJustLen; ++i)
1201
9.72M
            {
1202
9.72M
                stJustification.SetTotalAdvance(
1203
9.72M
                    nJustMinCluster + i,
1204
9.72M
                    ImplLogicWidthToDeviceSubPixel(pDXArray[i]));
1205
9.72M
            }
1206
1207
459k
            nEndGlyphCoord = stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1);
1208
459k
        }
1209
2.91k
        else if (!pDXArray.empty())
1210
2.91k
        {
1211
53.2k
            for (int i = 0; i < nJustLen; ++i)
1212
50.3k
            {
1213
50.3k
                stJustification.SetTotalAdvance(nJustMinCluster + i, pDXArray[i]);
1214
50.3k
            }
1215
1216
2.91k
            nEndGlyphCoord
1217
2.91k
                = std::round(stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1));
1218
2.91k
        }
1219
1220
462k
        if (!pKashidaArray.empty())
1221
44
        {
1222
41.3k
            for (sal_Int32 i = 0; i < static_cast<sal_Int32>(pKashidaArray.size()); ++i)
1223
41.2k
            {
1224
41.2k
                stJustification.SetKashidaPosition(nJustMinCluster + i,
1225
41.2k
                                                   static_cast<bool>(pKashidaArray[i]));
1226
41.2k
            }
1227
44
        }
1228
1229
462k
        aLayoutArgs.SetJustificationData(std::move(stJustification));
1230
462k
    }
1231
1232
    // get matching layout object for base font
1233
31.6M
    std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
1234
1235
31.6M
    if (pSalLayout)
1236
31.6M
    {
1237
31.6M
        const bool bActivateSubpixelPositioning(IsMapModeEnabled() || isSubpixelPositioning());
1238
        // tdf#168002
1239
        // SubpixelPositioning was until now activated when *any* MapMode was set, but
1240
        // there is another case this is needed: When a TextSimplePortionPrimitive2D
1241
        // is rendered by a SDPR.
1242
        // In that case a TextLayouterDevice is used (to isolate all Text-related stuff
1243
        // that should not be at OutputDevice) combined with a 'empty' OutDev -> no
1244
        // MapMode used. It now gets SubpixelPositioning at it's OutDev to allow
1245
        // checking/usage here.
1246
        // The DXArray for Primitives (see that TextPrimitive) is defined in the
1247
        // Unit-Text_Coordinate-System, thus in (0..1) ranges. That allows to
1248
        // have the DXArray transformation-independent and thus re-usable and
1249
        // is used since the TextPrimitive was created.
1250
        // If there is a DXArray missing at the text Primitive (as is the case
1251
        // with SVG imported ones, but allowed in general) one gets automatically
1252
        // created during the SalLayout creation for rendering. Unfortunately there
1253
        // (see GenericSalLayout::LayoutText, usages of GetSubpixelPositioning) the
1254
        // coordinates get std::round'ed, so all up to that point correctly
1255
        // calculated metric information in that double-precision dependent coordinate
1256
        // space gets *shredded*.
1257
        // To avoid that, SubpixelPositioning  has to be activated. While this might
1258
        // be done in the future for all cases (SubpixelPositioning == true) for now
1259
        // just add this case with the Primitives to not break stuff.
1260
31.6M
        pSalLayout->SetSubpixelPositioning(bActivateSubpixelPositioning);
1261
31.6M
    }
1262
1263
    // layout text
1264
31.6M
    if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
1265
0
    {
1266
0
        pSalLayout.reset();
1267
0
    }
1268
1269
31.6M
    if( !pSalLayout )
1270
0
        return nullptr;
1271
1272
    // do glyph fallback if needed
1273
    // #105768# avoid fallback for very small font sizes
1274
31.6M
    if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
1275
0
        pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs);
1276
1277
31.6M
    if (flags & SalLayoutFlags::GlyphItemsOnly)
1278
        // Return glyph items only after fallback handling. Otherwise they may
1279
        // contain invalid glyph IDs.
1280
5.57M
        return pSalLayout;
1281
1282
    // position, justify, etc. the layout
1283
26.1M
    pSalLayout->AdjustLayout( aLayoutArgs );
1284
1285
    // default to on for pdf export, which uses SubPixelToLogic to convert back to
1286
    // the logical coord space, of if we are scaling/mapping
1287
26.1M
    if (mbMap || meOutDevType == OUTDEV_PDF)
1288
25.6M
        pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos);
1289
432k
    else
1290
432k
    {
1291
432k
        Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos);
1292
432k
        pSalLayout->DrawBase() = basegfx::B2DPoint(aDevicePos.X(), aDevicePos.Y());
1293
432k
    }
1294
1295
    // adjust to right alignment if necessary
1296
26.1M
    if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
1297
595k
    {
1298
595k
        double nRTLOffset;
1299
595k
        if (!pDXArray.empty())
1300
22.2k
            nRTLOffset = nEndGlyphCoord;
1301
573k
        else if( nPixelWidth )
1302
0
            nRTLOffset = nPixelWidth;
1303
573k
        else
1304
573k
            nRTLOffset = pSalLayout->GetTextWidth();
1305
595k
        pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
1306
595k
    }
1307
1308
26.1M
    if(IsTrackingFontMappingUse())
1309
0
        TrackFontMappingUse(GetFont(), pSalLayout.get());
1310
1311
26.1M
    return pSalLayout;
1312
31.6M
}
1313
1314
std::shared_ptr<const vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
1315
        OUString const& rString)
1316
145k
{
1317
145k
    return vcl::text::TextLayoutCache::Create(rString);
1318
145k
}
1319
1320
bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
1321
56.0k
{
1322
56.0k
    OUString aStr( rString );
1323
56.0k
    vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0);
1324
56.0k
    bool bRTL = false;
1325
56.0k
    int nCharPos = -1;
1326
56.0k
    if (!aArgs.GetNextPos(&nCharPos, &bRTL))
1327
23.4k
        return false;
1328
32.6k
    return (nCharPos != nIndex);
1329
56.0k
}
1330
1331
sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
1332
                                       sal_Int32 nIndex, sal_Int32 nLen,
1333
                                       tools::Long nCharExtra,
1334
         vcl::text::TextLayoutCache const*const pLayoutCache,
1335
         const SalLayoutGlyphs* pGlyphs) const
1336
623
{
1337
623
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
1338
623
            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
1339
623
    sal_Int32 nRetVal = -1;
1340
623
    if( pSalLayout )
1341
623
    {
1342
        // convert logical widths into layout units
1343
        // NOTE: be very careful to avoid rounding errors for nCharExtra case
1344
        // problem with rounding errors especially for small nCharExtras
1345
        // TODO: remove when layout units have subpixel granularity
1346
623
        tools::Long nSubPixelFactor = 1;
1347
623
        if (!mbMap)
1348
487
            nSubPixelFactor = 64;
1349
623
        double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
1350
623
        double nExtraPixelWidth = 0;
1351
623
        if( nCharExtra != 0 )
1352
0
            nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
1353
623
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
1354
623
    }
1355
1356
623
    return nRetVal;
1357
623
}
1358
1359
sal_Int32 OutputDevice::GetTextBreakArray(const OUString& rStr, tools::Long nTextWidth,
1360
                                          std::optional<sal_Unicode> nHyphenChar,
1361
                                          std::optional<sal_Int32*> pHyphenPos, sal_Int32 nIndex,
1362
                                          sal_Int32 nLen, tools::Long nCharExtra,
1363
                                          KernArraySpan aKernArray,
1364
                                          vcl::text::TextLayoutCache const* const pLayoutCache,
1365
                                          const SalLayoutGlyphs* pGlyphs) const
1366
745k
{
1367
745k
    if (pHyphenPos.has_value())
1368
0
    {
1369
0
        **pHyphenPos = -1;
1370
0
    }
1371
1372
745k
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(
1373
745k
        rStr, nIndex, nLen, Point(0, 0), 0, aKernArray, {}, eDefaultLayout, pLayoutCache, pGlyphs);
1374
745k
    sal_Int32 nRetVal = -1;
1375
745k
    if( pSalLayout )
1376
745k
    {
1377
        // convert logical widths into layout units
1378
        // NOTE: be very careful to avoid rounding errors for nCharExtra case
1379
        // problem with rounding errors especially for small nCharExtras
1380
        // TODO: remove when layout units have subpixel granularity
1381
745k
        tools::Long nSubPixelFactor = 1;
1382
745k
        if (!mbMap)
1383
0
            nSubPixelFactor = 64;
1384
1385
745k
        double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
1386
745k
        double nExtraPixelWidth = 0;
1387
745k
        if( nCharExtra != 0 )
1388
0
            nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
1389
1390
        // calculate un-hyphenated break position
1391
745k
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
1392
1393
        // calculate hyphenated break position
1394
745k
        if (nHyphenChar.has_value())
1395
0
        {
1396
0
            OUString aHyphenStr(*nHyphenChar);
1397
0
            std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout(aHyphenStr, 0, 1);
1398
0
            if (pHyphenLayout)
1399
0
            {
1400
                // calculate subpixel width of hyphenation character
1401
0
                double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
1402
1403
                // calculate hyphenated break position
1404
0
                nTextPixelWidth -= nHyphenPixelWidth;
1405
0
                if (nExtraPixelWidth > 0)
1406
0
                    nTextPixelWidth -= nExtraPixelWidth;
1407
1408
0
                if (pHyphenPos.has_value())
1409
0
                {
1410
0
                    **pHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth,
1411
0
                                                            nSubPixelFactor);
1412
1413
0
                    if (**pHyphenPos > nRetVal)
1414
0
                    {
1415
0
                        **pHyphenPos = nRetVal;
1416
0
                    }
1417
0
                }
1418
0
            }
1419
0
        }
1420
745k
    }
1421
1422
745k
    return nRetVal;
1423
745k
}
1424
1425
void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
1426
                                 const OUString& rOrigStr, DrawTextFlags nStyle,
1427
                                 std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
1428
                                 vcl::TextLayoutCommon& _rLayout )
1429
2.63k
{
1430
1431
2.63k
    Color aOldTextColor;
1432
2.63k
    Color aOldTextFillColor;
1433
2.63k
    bool  bRestoreFillColor = false;
1434
2.63k
    if ( (nStyle & DrawTextFlags::Disable) && ! pVector )
1435
407
    {
1436
407
        bool  bHighContrastBlack = false;
1437
407
        bool  bHighContrastWhite = false;
1438
407
        const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
1439
407
        if( rStyleSettings.GetHighContrastMode() )
1440
0
        {
1441
0
            Color aCol;
1442
0
            if( rTargetDevice.IsBackground() )
1443
0
                aCol = rTargetDevice.GetBackground().GetColor();
1444
0
            else
1445
                // best guess is the face color here
1446
                // but it may be totally wrong. the background color
1447
                // was typically already reset
1448
0
                aCol = rStyleSettings.GetFaceColor();
1449
1450
0
            bHighContrastBlack = aCol.IsDark();
1451
0
            bHighContrastWhite = aCol.IsBright();
1452
0
        }
1453
1454
407
        aOldTextColor = rTargetDevice.GetTextColor();
1455
407
        if ( rTargetDevice.IsTextFillColor() )
1456
4
        {
1457
4
            bRestoreFillColor = true;
1458
4
            aOldTextFillColor = rTargetDevice.GetTextFillColor();
1459
4
        }
1460
407
        if( bHighContrastBlack )
1461
0
            rTargetDevice.SetTextColor( COL_GREEN );
1462
407
        else if( bHighContrastWhite )
1463
0
            rTargetDevice.SetTextColor( COL_LIGHTGREEN );
1464
407
        else
1465
407
        {
1466
            // draw disabled text always without shadow
1467
            // as it fits better with native look
1468
407
            rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() );
1469
407
        }
1470
407
    }
1471
1472
2.63k
    tools::Long        nWidth          = rRect.GetWidth();
1473
2.63k
    tools::Long        nHeight         = rRect.GetHeight();
1474
1475
2.63k
    if (nWidth <= 0 || nHeight <= 0)
1476
1.11k
    {
1477
1.11k
        if (nStyle & DrawTextFlags::Clip)
1478
59
            return;
1479
1.05k
        static bool bFuzzing = comphelper::IsFuzzing();
1480
1.05k
        SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight);
1481
1.05k
        if (bFuzzing)
1482
1.05k
            return;
1483
1.05k
    }
1484
1485
1.52k
    Point       aPos            = rRect.TopLeft();
1486
1487
1.52k
    tools::Long        nTextHeight     = rTargetDevice.GetTextHeight();
1488
1.52k
    TextAlign   eAlign          = rTargetDevice.GetTextAlign();
1489
1.52k
    sal_Int32   nMnemonicPos    = -1;
1490
1491
1.52k
    OUString aStr = rOrigStr;
1492
1.52k
    if ( nStyle & DrawTextFlags::Mnemonic )
1493
483
        aStr = removeMnemonicFromString( aStr, nMnemonicPos );
1494
1495
1.52k
    const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
1496
1497
    // We treat multiline text differently
1498
1.52k
    if ( nStyle & DrawTextFlags::MultiLine )
1499
0
    {
1500
1501
0
        ImplMultiTextLineInfo   aMultiLineInfo;
1502
0
        sal_Int32               i;
1503
0
        sal_Int32               nFormatLines;
1504
1505
0
        if ( nTextHeight )
1506
0
        {
1507
0
            tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1508
0
            sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
1509
0
            OUString aLastLine;
1510
0
            nFormatLines = aMultiLineInfo.Count();
1511
0
            if (nLines <= 0)
1512
0
                nLines = 1;
1513
0
            if ( nFormatLines > nLines )
1514
0
            {
1515
0
                if ( nStyle & DrawTextFlags::EndEllipsis )
1516
0
                {
1517
                    // Create last line and shorten it
1518
0
                    nFormatLines = nLines-1;
1519
1520
0
                    ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
1521
0
                    aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
1522
                    // Replace all LineFeeds with Spaces
1523
0
                    OUStringBuffer aLastLineBuffer(aLastLine);
1524
0
                    sal_Int32 nLastLineLen = aLastLineBuffer.getLength();
1525
0
                    for ( i = 0; i < nLastLineLen; i++ )
1526
0
                    {
1527
0
                        if ( aLastLineBuffer[ i ] == '\n' )
1528
0
                            aLastLineBuffer[ i ] = ' ';
1529
0
                    }
1530
0
                    aLastLine = aLastLineBuffer.makeStringAndClear();
1531
0
                    aLastLine = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle);
1532
0
                    nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
1533
0
                    nStyle |= DrawTextFlags::Top;
1534
0
                }
1535
0
            }
1536
0
            else
1537
0
            {
1538
0
                if ( nMaxTextWidth <= nWidth )
1539
0
                    nStyle &= ~DrawTextFlags::Clip;
1540
0
            }
1541
1542
            // Do we need to clip the height?
1543
0
            if ( nFormatLines*nTextHeight > nHeight )
1544
0
                nStyle |= DrawTextFlags::Clip;
1545
1546
            // Set clipping
1547
0
            if ( nStyle & DrawTextFlags::Clip )
1548
0
            {
1549
0
                rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
1550
0
                rTargetDevice.IntersectClipRegion( rRect );
1551
0
            }
1552
1553
            // Vertical alignment
1554
0
            if ( nStyle & DrawTextFlags::Bottom )
1555
0
                aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
1556
0
            else if ( nStyle & DrawTextFlags::VCenter )
1557
0
                aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
1558
1559
            // Font alignment
1560
0
            if ( eAlign == ALIGN_BOTTOM )
1561
0
                aPos.AdjustY(nTextHeight );
1562
0
            else if ( eAlign == ALIGN_BASELINE )
1563
0
                aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
1564
1565
            // Output all lines except for the last one
1566
0
            for ( i = 0; i < nFormatLines; i++ )
1567
0
            {
1568
0
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
1569
0
                if ( nStyle & DrawTextFlags::Right )
1570
0
                    aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
1571
0
                else if ( nStyle & DrawTextFlags::Center )
1572
0
                    aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
1573
0
                sal_Int32 nIndex   = rLineInfo.GetIndex();
1574
0
                sal_Int32 nLineLen = rLineInfo.GetLen();
1575
0
                _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
1576
0
                if ( bDrawMnemonics )
1577
0
                {
1578
0
                    if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
1579
0
                    {
1580
0
                        tools::Long        nMnemonicX;
1581
0
                        tools::Long        nMnemonicY;
1582
1583
0
                        KernArray aDXArray;
1584
0
                        _rLayout.GetTextArray(aStr, &aDXArray, nIndex, nLineLen, true);
1585
0
                        sal_Int32 nPos = nMnemonicPos - nIndex;
1586
0
                        sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
1587
0
                        sal_Int32 lc_x2 = aDXArray[nPos];
1588
0
                        double nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));
1589
1590
0
                        Point       aTempPos = rTargetDevice.LogicToPixel( aPos );
1591
0
                        nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) );
1592
0
                        nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
1593
0
                        rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
1594
0
                    }
1595
0
                }
1596
0
                aPos.AdjustY(nTextHeight );
1597
0
                aPos.setX( rRect.Left() );
1598
0
            }
1599
1600
            // If there still is a last line, we output it left-aligned as the line would be clipped
1601
0
            if ( !aLastLine.isEmpty() )
1602
0
                _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText );
1603
1604
            // Reset clipping
1605
0
            if ( nStyle & DrawTextFlags::Clip )
1606
0
                rTargetDevice.Pop();
1607
0
        }
1608
0
    }
1609
1.52k
    else
1610
1.52k
    {
1611
1.52k
        tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
1612
1613
        // Clip text if needed
1614
1.52k
        if ( nTextWidth > nWidth )
1615
1.06k
        {
1616
1.06k
            if ( nStyle & TEXT_DRAW_ELLIPSIS )
1617
455
            {
1618
455
                aStr = _rLayout.GetEllipsisString(aStr, nWidth, nStyle);
1619
455
                nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
1620
455
                nStyle |= DrawTextFlags::Left;
1621
455
                nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() );
1622
455
            }
1623
1.06k
        }
1624
464
        else
1625
464
        {
1626
464
            if ( nTextHeight <= nHeight )
1627
219
                nStyle &= ~DrawTextFlags::Clip;
1628
464
        }
1629
1630
        // horizontal text alignment
1631
1.52k
        if ( nStyle & DrawTextFlags::Right )
1632
104
            aPos.AdjustX(nWidth-nTextWidth );
1633
1.42k
        else if ( nStyle & DrawTextFlags::Center )
1634
64
            aPos.AdjustX((nWidth-nTextWidth)/2 );
1635
1636
        // vertical font alignment
1637
1.52k
        if ( eAlign == ALIGN_BOTTOM )
1638
0
            aPos.AdjustY(nTextHeight );
1639
1.52k
        else if ( eAlign == ALIGN_BASELINE )
1640
33
            aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
1641
1642
1.52k
        if ( nStyle & DrawTextFlags::Bottom )
1643
460
            aPos.AdjustY(nHeight-nTextHeight );
1644
1.06k
        else if ( nStyle & DrawTextFlags::VCenter )
1645
176
            aPos.AdjustY((nHeight-nTextHeight)/2 );
1646
1647
1.52k
        tools::Long nMnemonicX = 0;
1648
1.52k
        tools::Long nMnemonicY = 0;
1649
1.52k
        double nMnemonicWidth = 0;
1650
1.52k
        if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength())
1651
295
        {
1652
295
            KernArray aDXArray;
1653
295
            _rLayout.GetTextArray(aStr, &aDXArray, 0, aStr.getLength(), true);
1654
295
            tools::Long lc_x1 = nMnemonicPos? aDXArray[nMnemonicPos - 1] : 0;
1655
295
            tools::Long lc_x2 = aDXArray[nMnemonicPos];
1656
295
            nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));
1657
1658
295
            Point aTempPos = rTargetDevice.LogicToPixel( aPos );
1659
295
            nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) );
1660
295
            nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
1661
295
        }
1662
1663
1.52k
        if ( nStyle & DrawTextFlags::Clip )
1664
344
        {
1665
344
            auto popIt = rTargetDevice.ScopedPush(vcl::PushFlags::CLIPREGION);
1666
344
            rTargetDevice.IntersectClipRegion( rRect );
1667
344
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
1668
344
            if ( bDrawMnemonics && nMnemonicPos != -1 )
1669
222
                rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
1670
344
        }
1671
1.18k
        else
1672
1.18k
        {
1673
1.18k
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
1674
1.18k
            if ( bDrawMnemonics && nMnemonicPos != -1 )
1675
120
                rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
1676
1.18k
        }
1677
1.52k
    }
1678
1679
1.52k
    if ( nStyle & DrawTextFlags::Disable && !pVector )
1680
343
    {
1681
343
        rTargetDevice.SetTextColor( aOldTextColor );
1682
343
        if ( bRestoreFillColor )
1683
4
            rTargetDevice.SetTextFillColor( aOldTextFillColor );
1684
343
    }
1685
1.52k
}
1686
1687
void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect,
1688
                                       const OUString&  rOrigStr,
1689
                                       DrawTextFlags    nStyle,
1690
                                       GDIMetaFile&     rMtf )
1691
0
{
1692
1693
0
    if ( rOrigStr.isEmpty() || rRect.IsEmpty() )
1694
0
        return;
1695
1696
    // we need a graphics
1697
0
    if( !mpGraphics && !AcquireGraphics() )
1698
0
        return;
1699
0
    assert(mpGraphics);
1700
0
    if( mbInitClipRegion )
1701
0
        InitClipRegion();
1702
1703
    // temporarily swap in passed mtf for action generation, and
1704
    // disable output generation.
1705
0
    const bool bOutputEnabled( IsOutputEnabled() );
1706
0
    GDIMetaFile* pMtf = mpMetaFile;
1707
1708
0
    mpMetaFile = &rMtf;
1709
0
    EnableOutput( false );
1710
1711
    // #i47157# Factored out to ImplDrawTextRect(), to be shared
1712
    // between us and DrawText()
1713
0
    vcl::DefaultTextLayout aLayout( *this );
1714
0
    ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout );
1715
1716
    // and restore again
1717
0
    EnableOutput( bOutputEnabled );
1718
0
    mpMetaFile = pMtf;
1719
0
}
1720
1721
void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle,
1722
                             std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
1723
                             vcl::TextLayoutCommon* _pTextLayout )
1724
4.90k
{
1725
4.90k
    assert(!is_double_buffered_window());
1726
1727
4.90k
    if (mpOutDevData->mpRecordLayout)
1728
0
    {
1729
0
        pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
1730
0
        pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
1731
0
    }
1732
1733
4.90k
    bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction();
1734
4.90k
    if ( mpMetaFile && !bDecomposeTextRectAction )
1735
818
        mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) );
1736
1737
4.90k
    if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() )
1738
1.93k
        return;
1739
1740
    // we need a graphics
1741
2.96k
    if( !mpGraphics && !AcquireGraphics() )
1742
0
        return;
1743
2.96k
    assert(mpGraphics);
1744
2.96k
    if( mbInitClipRegion )
1745
1.98k
        InitClipRegion();
1746
2.96k
    if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText)
1747
330
        return;
1748
1749
    // temporarily disable mtf action generation (ImplDrawText _does_
1750
    // create MetaActionType::TEXTs otherwise)
1751
2.63k
    GDIMetaFile* pMtf = mpMetaFile;
1752
2.63k
    if ( !bDecomposeTextRectAction )
1753
2.63k
        mpMetaFile = nullptr;
1754
1755
    // #i47157# Factored out to ImplDrawText(), to be used also
1756
    // from AddTextRectActions()
1757
2.63k
    vcl::DefaultTextLayout aDefaultLayout( *this );
1758
2.63k
    ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout );
1759
1760
    // and enable again
1761
2.63k
    mpMetaFile = pMtf;
1762
2.63k
}
1763
1764
tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
1765
                                     const OUString& rStr, DrawTextFlags nStyle,
1766
                                     TextRectInfo* pInfo,
1767
                                     const vcl::TextLayoutCommon* _pTextLayout ) const
1768
0
{
1769
1770
0
    tools::Rectangle           aRect = rRect;
1771
0
    sal_Int32           nLines;
1772
0
    tools::Long                nWidth = rRect.GetWidth();
1773
0
    tools::Long                nMaxWidth;
1774
0
    tools::Long                nTextHeight = GetTextHeight();
1775
1776
0
    OUString aStr = rStr;
1777
0
    if ( nStyle & DrawTextFlags::Mnemonic )
1778
0
        aStr = removeMnemonicFromString( aStr );
1779
1780
0
    if ( nStyle & DrawTextFlags::MultiLine )
1781
0
    {
1782
0
        ImplMultiTextLineInfo   aMultiLineInfo;
1783
0
        sal_Int32               nFormatLines;
1784
0
        sal_Int32               i;
1785
1786
0
        nMaxWidth = 0;
1787
0
        vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
1788
1789
0
        if (_pTextLayout)
1790
0
            const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1791
0
        else
1792
0
            aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1793
1794
0
        nFormatLines = aMultiLineInfo.Count();
1795
0
        if ( !nTextHeight )
1796
0
            nTextHeight = 1;
1797
0
        nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
1798
0
        if ( pInfo )
1799
0
            pInfo->mnLineCount = nFormatLines;
1800
0
        if ( !nLines )
1801
0
            nLines = 1;
1802
0
        if ( nFormatLines <= nLines )
1803
0
            nLines = nFormatLines;
1804
0
        else
1805
0
        {
1806
0
            if ( !(nStyle & DrawTextFlags::EndEllipsis) )
1807
0
                nLines = nFormatLines;
1808
0
            else
1809
0
            {
1810
0
                if ( pInfo )
1811
0
                    pInfo->mbEllipsis = true;
1812
0
                nMaxWidth = nWidth;
1813
0
            }
1814
0
        }
1815
0
        if ( pInfo )
1816
0
        {
1817
0
            bool bMaxWidth = nMaxWidth == 0;
1818
0
            pInfo->mnMaxWidth = 0;
1819
0
            for ( i = 0; i < nLines; i++ )
1820
0
            {
1821
0
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
1822
0
                if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) )
1823
0
                    nMaxWidth = rLineInfo.GetWidth();
1824
0
                if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth )
1825
0
                    pInfo->mnMaxWidth = rLineInfo.GetWidth();
1826
0
            }
1827
0
        }
1828
0
        else if ( !nMaxWidth )
1829
0
        {
1830
0
            for ( i = 0; i < nLines; i++ )
1831
0
            {
1832
0
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
1833
0
                if ( rLineInfo.GetWidth() > nMaxWidth )
1834
0
                    nMaxWidth = rLineInfo.GetWidth();
1835
0
            }
1836
0
        }
1837
0
    }
1838
0
    else
1839
0
    {
1840
0
        nLines      = 1;
1841
0
        nMaxWidth   = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );
1842
1843
0
        if ( pInfo )
1844
0
        {
1845
0
            pInfo->mnLineCount  = 1;
1846
0
            pInfo->mnMaxWidth   = nMaxWidth;
1847
0
        }
1848
1849
0
        if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
1850
0
        {
1851
0
            if ( pInfo )
1852
0
                pInfo->mbEllipsis = true;
1853
0
            nMaxWidth = nWidth;
1854
0
        }
1855
0
    }
1856
1857
0
    if ( nStyle & DrawTextFlags::Right )
1858
0
        aRect.SetLeft( aRect.Right()-nMaxWidth+1 );
1859
0
    else if ( nStyle & DrawTextFlags::Center )
1860
0
    {
1861
0
        aRect.AdjustLeft((nWidth-nMaxWidth)/2 );
1862
0
        aRect.SetRight( aRect.Left()+nMaxWidth-1 );
1863
0
    }
1864
0
    else
1865
0
        aRect.SetRight( aRect.Left()+nMaxWidth-1 );
1866
1867
0
    if ( nStyle & DrawTextFlags::Bottom )
1868
0
        aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 );
1869
0
    else if ( nStyle & DrawTextFlags::VCenter )
1870
0
    {
1871
0
        aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 );
1872
0
        aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
1873
0
    }
1874
0
    else
1875
0
        aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
1876
1877
    // #99188# get rid of rounding problems when using this rect later
1878
0
    if (nStyle & DrawTextFlags::Right)
1879
0
        aRect.AdjustLeft( -1 );
1880
0
    else
1881
0
        aRect.AdjustRight( 1 );
1882
1883
0
    if (maFont.GetOrientation() != 0_deg10)
1884
0
    {
1885
0
        tools::Polygon aRotatedPolygon(aRect);
1886
0
        aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation());
1887
0
        return aRotatedPolygon.GetBoundRect();
1888
0
    }
1889
1890
0
    return aRect;
1891
0
}
1892
1893
void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
1894
                                 const sal_Int32 nIndex, const sal_Int32 nLen,
1895
                                 DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
1896
                                 const SalLayoutGlyphs* pGlyphs )
1897
0
{
1898
0
    assert(!is_double_buffered_window());
1899
1900
0
    if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) )
1901
0
        return;
1902
1903
    // better get graphics here because ImplDrawMnemonicLine() will not
1904
    // we need a graphics
1905
0
    if( !mpGraphics && !AcquireGraphics() )
1906
0
        return;
1907
0
    assert(mpGraphics);
1908
0
    if( mbInitClipRegion )
1909
0
        InitClipRegion();
1910
0
    if ( mbOutputClipped )
1911
0
        return;
1912
1913
    // nIndex and nLen must go to mpAlphaVDev->DrawCtrlText unchanged
1914
0
    sal_Int32 nCorrectedIndex = nIndex;
1915
0
    sal_Int32 nCorrectedLen = nLen;
1916
0
    if ((nCorrectedLen < 0) || (nCorrectedIndex + nCorrectedLen >= rStr.getLength()))
1917
0
    {
1918
0
        nCorrectedLen = rStr.getLength() - nCorrectedIndex;
1919
0
    }
1920
0
    sal_Int32  nMnemonicPos = -1;
1921
1922
0
    tools::Long        nMnemonicX = 0;
1923
0
    tools::Long        nMnemonicY = 0;
1924
0
    tools::Long        nMnemonicWidth = 0;
1925
0
    const OUString aStr = removeMnemonicFromString(rStr, nMnemonicPos); // Strip mnemonics always
1926
0
    if (nMnemonicPos != -1)
1927
0
    {
1928
0
        if (nMnemonicPos < nCorrectedIndex)
1929
0
        {
1930
0
            --nCorrectedIndex;
1931
0
        }
1932
0
        else
1933
0
        {
1934
0
            if (nMnemonicPos < (nCorrectedIndex + nCorrectedLen))
1935
0
                --nCorrectedLen;
1936
0
        }
1937
0
        if (nStyle & DrawTextFlags::Mnemonic && !pVector
1938
0
            && !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics))
1939
0
        {
1940
0
            SAL_WARN_IF( nMnemonicPos >= (nCorrectedIndex+nCorrectedLen), "vcl", "Mnemonic underline marker after last character" );
1941
0
            bool bInvalidPos = false;
1942
1943
0
            if (nMnemonicPos >= nCorrectedLen)
1944
0
            {
1945
                // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
1946
                // due to some strange BiDi text editors
1947
                // -> place the underline behind the string to indicate a failure
1948
0
                bInvalidPos = true;
1949
0
                nMnemonicPos = nCorrectedLen - 1;
1950
0
            }
1951
1952
0
            KernArray aDXArray;
1953
0
            GetTextArray(aStr, &aDXArray, nCorrectedIndex, nCorrectedLen, true, nullptr, pGlyphs);
1954
0
            sal_Int32 nPos = nMnemonicPos - nCorrectedIndex;
1955
0
            sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
1956
0
            sal_Int32 lc_x2 = aDXArray[nPos];
1957
0
            nMnemonicWidth = std::abs(lc_x1 - lc_x2);
1958
1959
0
            Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() );
1960
0
            if( bInvalidPos )  // #106952#, place behind the (last) character
1961
0
                aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() );
1962
1963
0
            aTempPos += rPos;
1964
0
            aTempPos = LogicToPixel( aTempPos );
1965
0
            nMnemonicX = mnOutOffX + aTempPos.X();
1966
0
            nMnemonicY = mnOutOffY + aTempPos.Y();
1967
0
        }
1968
0
        else
1969
0
            nMnemonicPos = -1; // Reset - we don't show the mnemonic
1970
0
    }
1971
1972
0
    std::optional<Color> oOldTextColor;
1973
0
    std::optional<Color> oOldTextFillColor;
1974
0
    if ( nStyle & DrawTextFlags::Disable && ! pVector )
1975
0
    {
1976
0
        bool  bHighContrastBlack = false;
1977
0
        bool  bHighContrastWhite = false;
1978
0
        const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
1979
0
        if( rStyleSettings.GetHighContrastMode() )
1980
0
        {
1981
0
            if( IsBackground() )
1982
0
            {
1983
0
                Wallpaper aWall = GetBackground();
1984
0
                Color aCol = aWall.GetColor();
1985
0
                bHighContrastBlack = aCol.IsDark();
1986
0
                bHighContrastWhite = aCol.IsBright();
1987
0
            }
1988
0
        }
1989
1990
0
        oOldTextColor = GetTextColor();
1991
0
        if ( IsTextFillColor() )
1992
0
            oOldTextFillColor = GetTextFillColor();
1993
1994
0
        if( bHighContrastBlack )
1995
0
            SetTextColor( COL_GREEN );
1996
0
        else if( bHighContrastWhite )
1997
0
            SetTextColor( COL_LIGHTGREEN );
1998
0
        else
1999
0
            SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
2000
0
    }
2001
2002
0
    DrawText(rPos, aStr, nCorrectedIndex, nCorrectedLen, pVector, pDisplayText, pGlyphs);
2003
0
    if (nMnemonicPos != -1)
2004
0
        ImplDrawMnemonicLine(nMnemonicX, nMnemonicY, nMnemonicWidth);
2005
2006
0
    if (oOldTextColor)
2007
0
        SetTextColor( *oOldTextColor );
2008
0
    if (oOldTextFillColor)
2009
0
        SetTextFillColor(*oOldTextFillColor);
2010
0
}
2011
2012
tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
2013
0
{
2014
0
    sal_Int32 nLen = rStr.getLength();
2015
0
    sal_Int32 nIndex = 0;
2016
2017
0
    sal_Int32 nMnemonicPos;
2018
0
    OUString aStr = removeMnemonicFromString( rStr, nMnemonicPos );
2019
0
    if ( nMnemonicPos != -1 )
2020
0
    {
2021
0
        if ( nMnemonicPos < nIndex )
2022
0
            nIndex--;
2023
0
        else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
2024
0
            nLen--;
2025
0
    }
2026
0
    return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
2027
0
}
2028
2029
bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
2030
                                         const OUString& rStr, sal_Int32 nBase,
2031
                                         sal_Int32 nIndex, sal_Int32 nLen,
2032
                                         sal_uLong nLayoutWidth, KernArraySpan pDXAry,
2033
                                         std::span<const sal_Bool> pKashidaAry,
2034
                                         const SalLayoutGlyphs* pGlyphs ) const
2035
4.34M
{
2036
4.34M
    basegfx::B2DRectangle aRect;
2037
4.34M
    bool bRet = GetTextBoundRect(aRect, rStr, nBase, nIndex, nLen, nLayoutWidth, pDXAry,
2038
4.34M
                                 pKashidaAry, pGlyphs);
2039
4.34M
    rRect = SalLayout::BoundRect2Rectangle(aRect);
2040
4.34M
    return bRet;
2041
4.34M
}
2042
2043
bool OutputDevice::GetTextBoundRect(basegfx::B2DRectangle& rRect, const OUString& rStr,
2044
                                    sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen,
2045
                                    sal_uLong nLayoutWidth, KernArraySpan pDXAry,
2046
                                    std::span<const sal_Bool> pKashidaAry,
2047
                                    const SalLayoutGlyphs* pGlyphs) const
2048
4.34M
{
2049
4.34M
    bool bRet = false;
2050
4.34M
    rRect.reset();
2051
2052
4.34M
    std::unique_ptr<SalLayout> pSalLayout;
2053
4.34M
    const Point aPoint;
2054
    // calculate offset when nBase!=nIndex
2055
4.34M
    double nXOffset = 0;
2056
4.34M
    if( nBase != nIndex )
2057
0
    {
2058
0
        sal_Int32 nStart = std::min( nBase, nIndex );
2059
0
        sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
2060
0
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry );
2061
0
        if( pSalLayout )
2062
0
        {
2063
0
            nXOffset = pSalLayout->GetTextWidth();
2064
            // TODO: fix offset calculation for Bidi case
2065
0
            if( nBase < nIndex)
2066
0
                nXOffset = -nXOffset;
2067
0
        }
2068
0
    }
2069
2070
4.34M
    pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout,
2071
4.34M
                            nullptr, pGlyphs);
2072
4.34M
    if( pSalLayout )
2073
4.34M
    {
2074
4.34M
        basegfx::B2DRectangle aPixelRect;
2075
4.34M
        bRet = pSalLayout->GetBoundRect(aPixelRect);
2076
2077
4.34M
        if( bRet )
2078
4.33M
        {
2079
4.33M
            basegfx::B2DPoint aPos = pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
2080
4.33M
            aPixelRect.translate(mnTextOffX - aPos.getX(), mnTextOffY - aPos.getY());
2081
4.33M
            rRect = PixelToLogic( aPixelRect );
2082
4.33M
            if (mbMap)
2083
4.32M
            {
2084
4.32M
                rRect.translate(maMapRes.mnMapOfsX, maMapRes.mnMapOfsY);
2085
4.32M
            }
2086
4.33M
        }
2087
4.34M
    }
2088
2089
4.34M
    return bRet;
2090
4.34M
}
2091
2092
bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
2093
                                        const OUString& rStr, sal_Int32 nBase,
2094
                                        sal_Int32 nIndex, sal_Int32 nLen,
2095
                                        sal_uLong nLayoutWidth,
2096
                                        KernArraySpan pDXArray,
2097
                                        std::span<const sal_Bool> pKashidaArray ) const
2098
550
{
2099
550
    if (!InitFont())
2100
0
        return false;
2101
2102
550
    bool bRet = false;
2103
550
    rVector.clear();
2104
550
    if( nLen < 0 )
2105
0
    {
2106
0
        nLen = rStr.getLength() - nIndex;
2107
0
    }
2108
550
    rVector.reserve( nLen );
2109
2110
    // we want to get the Rectangle in logical units, so to
2111
    // avoid rounding errors we just size the font in logical units
2112
550
    bool bOldMap = mbMap;
2113
550
    if( bOldMap )
2114
0
    {
2115
0
        const_cast<OutputDevice&>(*this).mbMap = false;
2116
0
        const_cast<OutputDevice&>(*this).mbNewFont = true;
2117
0
    }
2118
2119
550
    std::unique_ptr<SalLayout> pSalLayout;
2120
2121
    // calculate offset when nBase!=nIndex
2122
550
    double nXOffset = 0;
2123
550
    if( nBase != nIndex )
2124
0
    {
2125
0
        sal_Int32 nStart = std::min( nBase, nIndex );
2126
0
        sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
2127
0
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray);
2128
0
        if( pSalLayout )
2129
0
        {
2130
0
            nXOffset = pSalLayout->GetTextWidth();
2131
0
            pSalLayout.reset();
2132
            // TODO: fix offset calculation for Bidi case
2133
0
            if( nBase > nIndex)
2134
0
                nXOffset = -nXOffset;
2135
0
        }
2136
0
    }
2137
2138
550
    pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray );
2139
550
    if( pSalLayout )
2140
550
    {
2141
550
        bRet = pSalLayout->GetOutline(rVector);
2142
550
        if( bRet )
2143
550
        {
2144
            // transform polygon to pixel units
2145
550
            basegfx::B2DHomMatrix aMatrix;
2146
2147
550
            if (nXOffset || mnTextOffX || mnTextOffY)
2148
0
            {
2149
0
                basegfx::B2DPoint aRotatedOfs(mnTextOffX, mnTextOffY);
2150
0
                aRotatedOfs -= pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
2151
0
                aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() );
2152
0
            }
2153
2154
550
            if( !aMatrix.isIdentity() )
2155
0
            {
2156
0
                for (auto & elem : rVector)
2157
0
                    elem.transform( aMatrix );
2158
0
            }
2159
550
        }
2160
2161
550
        pSalLayout.reset();
2162
550
    }
2163
2164
550
    if( bOldMap )
2165
0
    {
2166
        // restore original font size and map mode
2167
0
        const_cast<OutputDevice&>(*this).mbMap = bOldMap;
2168
0
        const_cast<OutputDevice&>(*this).mbNewFont = true;
2169
0
    }
2170
2171
550
    return bRet;
2172
550
}
2173
2174
bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
2175
                                        const OUString& rStr, sal_Int32 nBase,
2176
                                        sal_Int32 nIndex, sal_Int32 nLen,
2177
                                        sal_uLong nLayoutWidth, KernArraySpan pDXArray,
2178
                                        std::span<const sal_Bool> pKashidaArray ) const
2179
0
{
2180
0
    rResultVector.clear();
2181
2182
    // get the basegfx polypolygon vector
2183
0
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
2184
0
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
2185
0
                         nLayoutWidth, pDXArray, pKashidaArray ) )
2186
0
        return false;
2187
2188
    // convert to a tool polypolygon vector
2189
0
    rResultVector.reserve( aB2DPolyPolyVector.size() );
2190
0
    for (auto const& elem : aB2DPolyPolyVector)
2191
0
        rResultVector.emplace_back(elem); // #i76339#
2192
2193
0
    return true;
2194
0
}
2195
2196
bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const
2197
0
{
2198
0
    rPolyPoly.Clear();
2199
2200
    // get the basegfx polypolygon vector
2201
0
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
2202
0
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1,
2203
0
                         /*nLayoutWidth*/0, /*pDXArray*/{} ) )
2204
0
        return false;
2205
2206
    // convert and merge into a tool polypolygon
2207
0
    for (auto const& elem : aB2DPolyPolyVector)
2208
0
        for(auto const& rB2DPolygon : elem)
2209
0
            rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339#
2210
2211
0
    return true;
2212
0
}
2213
2214
void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled)
2215
0
{
2216
0
    if (nFlags & SystemTextColorFlags::Mono)
2217
0
    {
2218
0
        SetTextColor(COL_BLACK);
2219
0
    }
2220
0
    else
2221
0
    {
2222
0
        if (!bEnabled)
2223
0
        {
2224
0
            const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
2225
0
            SetTextColor(rStyleSettings.GetDisableColor());
2226
0
        }
2227
0
    }
2228
0
}
2229
2230
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */