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