Coverage Report

Created: 2026-07-10 11:04

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