/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: */ |