LibreOffice Module canvas (master)  1
textlayout.cxx
Go to the documentation of this file.
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 <tools/diagnose_ex.h>
24 #include <tools/long.hxx>
28 #include <com/sun/star/rendering/CompositeOperation.hpp>
29 #include <com/sun/star/rendering/RenderState.hpp>
30 #include <com/sun/star/rendering/TextDirection.hpp>
31 #include <com/sun/star/rendering/ViewState.hpp>
32 #include <comphelper/sequence.hxx>
34 #include <vcl/metric.hxx>
35 #include <vcl/virdev.hxx>
36 
37 #include <canvas/canvastools.hxx>
38 
39 #include "textlayout.hxx"
40 
41 #include <memory>
42 
43 using namespace ::com::sun::star;
44 
45 namespace vclcanvas
46 {
47  namespace
48  {
49  void setupLayoutMode( OutputDevice& rOutDev,
50  sal_Int8 nTextDirection )
51  {
52  // TODO(P3): avoid if already correctly set
53  ComplexTextLayoutFlags nLayoutMode = ComplexTextLayoutFlags::Default;
54  switch( nTextDirection )
55  {
56  case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
57  break;
58  case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
59  nLayoutMode = ComplexTextLayoutFlags::BiDiStrong;
60  break;
61  case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
62  nLayoutMode = ComplexTextLayoutFlags::BiDiRtl;
63  break;
64  case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
65  nLayoutMode = ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::BiDiStrong;
66  break;
67  default:
68  break;
69  }
70 
71  // set calculated layout mode. Origin is always the left edge,
72  // as required at the API spec
73  rOutDev.SetLayoutMode( nLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
74  }
75  }
76 
77  TextLayout::TextLayout( const rendering::StringContext& aText,
78  sal_Int8 nDirection,
79  const CanvasFont::Reference& rFont,
80  const uno::Reference<rendering::XGraphicDevice>& xDevice,
81  const OutDevProviderSharedPtr& rOutDev ) :
83  maText( aText ),
84  mpFont( rFont ),
85  mxDevice( xDevice ),
86  mpOutDevProvider( rOutDev ),
87  mnTextDirection( nDirection )
88  {}
89 
90  void SAL_CALL TextLayout::disposing()
91  {
92  SolarMutexGuard aGuard;
93 
94  mpOutDevProvider.reset();
95  mxDevice.clear();
96  mpFont.clear();
97  }
98 
99  // XTextLayout
100  uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( )
101  {
102  SolarMutexGuard aGuard;
103 
104  OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
105  ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
106  pVDev->SetFont( mpFont->getVCLFont() );
107 
108  setupLayoutMode( *pVDev, mnTextDirection );
109 
110  const rendering::ViewState aViewState(
111  geometry::AffineMatrix2D(1,0,0, 0,1,0),
112  nullptr);
113 
114  rendering::RenderState aRenderState (
115  geometry::AffineMatrix2D(1,0,0,0,1,0),
116  nullptr,
117  uno::Sequence<double>(4),
118  rendering::CompositeOperation::SOURCE);
119 
120  std::unique_ptr< ::tools::Long []> aOffsets(new ::tools::Long[maLogicalAdvancements.getLength()]);
121  setupTextOffsets(aOffsets.get(), maLogicalAdvancements, aViewState, aRenderState);
122 
123  std::vector< uno::Reference< rendering::XPolyPolygon2D> > aOutlineSequence;
125  if (pVDev->GetTextOutlines(
126  aOutlines,
127  maText.Text,
128  maText.StartPosition,
129  maText.StartPosition,
130  maText.Length,
131  0,
132  aOffsets.get()))
133  {
134  aOutlineSequence.reserve(aOutlines.size());
135  sal_Int32 nIndex (0);
136  for (auto const& outline : aOutlines)
137  {
138  aOutlineSequence[nIndex++] = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
139  mxDevice,
140  outline);
141  }
142  }
143 
144  return comphelper::containerToSequence(aOutlineSequence);
145  }
146 
147  uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( )
148  {
149  SolarMutexGuard aGuard;
150 
151 
152  OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
153  ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
154  pVDev->SetFont( mpFont->getVCLFont() );
155 
156  setupLayoutMode( *pVDev, mnTextDirection );
157 
158  const rendering::ViewState aViewState(
159  geometry::AffineMatrix2D(1,0,0, 0,1,0),
160  nullptr);
161 
162  rendering::RenderState aRenderState (
163  geometry::AffineMatrix2D(1,0,0,0,1,0),
164  nullptr,
165  uno::Sequence<double>(4),
166  rendering::CompositeOperation::SOURCE);
167 
168  std::unique_ptr< ::tools::Long []> aOffsets(new ::tools::Long[maLogicalAdvancements.getLength()]);
169  setupTextOffsets(aOffsets.get(), maLogicalAdvancements, aViewState, aRenderState);
170 
171  std::vector< ::tools::Rectangle > aMetricVector;
172  uno::Sequence<geometry::RealRectangle2D> aBoundingBoxes;
173  if (pVDev->GetGlyphBoundRects(
174  Point(0,0),
175  maText.Text,
176  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
177  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length),
178  aMetricVector))
179  {
180  aBoundingBoxes.realloc(aMetricVector.size());
181  sal_Int32 nIndex (0);
182  for (auto const& metric : aMetricVector)
183  {
184  aBoundingBoxes[nIndex++] = geometry::RealRectangle2D(
185  metric.Left(),
186  metric.Top(),
187  metric.Right(),
188  metric.Bottom());
189  }
190  }
191  return aBoundingBoxes;
192  }
193 
194  uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( )
195  {
196  // TODO(F1)
197  return uno::Sequence< geometry::RealRectangle2D >();
198  }
199 
200  uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( )
201  {
202  SolarMutexGuard aGuard;
203 
204  return maLogicalAdvancements;
205  }
206 
207  void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements )
208  {
209  SolarMutexGuard aGuard;
210 
211  ENSURE_ARG_OR_THROW( aAdvancements.getLength() == maText.Length,
212  "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" );
213 
214  maLogicalAdvancements = aAdvancements;
215  }
216 
217  geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( )
218  {
219  SolarMutexGuard aGuard;
220 
221  if( !mpOutDevProvider )
222  return geometry::RealRectangle2D();
223 
224  OutputDevice& rOutDev = mpOutDevProvider->getOutDev();
225 
226  ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
227  pVDev->SetFont( mpFont->getVCLFont() );
228 
229  // need metrics for Y offset, the XCanvas always renders
230  // relative to baseline
231  const ::FontMetric& aMetric( pVDev->GetFontMetric() );
232 
233  setupLayoutMode( *pVDev, mnTextDirection );
234 
235  const sal_Int32 nAboveBaseline( -aMetric.GetAscent() );
236  const sal_Int32 nBelowBaseline( aMetric.GetDescent() );
237 
238  if( maLogicalAdvancements.hasElements() )
239  {
240  return geometry::RealRectangle2D( 0, nAboveBaseline,
241  maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ],
242  nBelowBaseline );
243  }
244  else
245  {
246  return geometry::RealRectangle2D( 0, nAboveBaseline,
247  pVDev->GetTextWidth(
248  maText.Text,
249  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
250  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ),
251  nBelowBaseline );
252  }
253  }
254 
255  double SAL_CALL TextLayout::justify( double )
256  {
257  // TODO(F1)
258  return 0.0;
259  }
260 
261  double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >&,
262  double )
263  {
264  // TODO(F1)
265  return 0.0;
266  }
267 
268  rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& )
269  {
270  // TODO(F1)
271  return rendering::TextHit();
272  }
273 
274  rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32, sal_Bool )
275  {
276  // TODO(F1)
277  return rendering::Caret();
278  }
279 
280  sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32, sal_Int32, sal_Bool )
281  {
282  // TODO(F1)
283  return 0;
284  }
285 
286  uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32, sal_Int32 )
287  {
288  // TODO(F1)
289  return uno::Reference< rendering::XPolyPolygon2D >();
290  }
291 
292  uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32, sal_Int32 )
293  {
294  // TODO(F1)
295  return uno::Reference< rendering::XPolyPolygon2D >();
296  }
297 
298  double SAL_CALL TextLayout::getBaselineOffset( )
299  {
300  // TODO(F1)
301  return 0.0;
302  }
303 
304  sal_Int8 SAL_CALL TextLayout::getMainTextDirection( )
305  {
306  SolarMutexGuard aGuard;
307 
308  return mnTextDirection;
309  }
310 
311  uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( )
312  {
313  SolarMutexGuard aGuard;
314 
315  return mpFont;
316  }
317 
318  rendering::StringContext SAL_CALL TextLayout::getText( )
319  {
320  SolarMutexGuard aGuard;
321 
322  return maText;
323  }
324 
325  void TextLayout::draw( OutputDevice& rOutDev,
326  const Point& rOutpos,
327  const rendering::ViewState& viewState,
328  const rendering::RenderState& renderState ) const
329  {
330  SolarMutexGuard aGuard;
331 
332  setupLayoutMode( rOutDev, mnTextDirection );
333 
334  if( maLogicalAdvancements.hasElements() )
335  {
336  // TODO(P2): cache that
337  std::unique_ptr< ::tools::Long []> aOffsets(new ::tools::Long[maLogicalAdvancements.getLength()]);
338  setupTextOffsets( aOffsets.get(), maLogicalAdvancements, viewState, renderState );
339 
340  // TODO(F3): ensure correct length and termination for DX
341  // array (last entry _must_ contain the overall width)
342 
343  rOutDev.DrawTextArray( rOutpos,
344  maText.Text,
345  aOffsets.get(),
346  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
347  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
348  }
349  else
350  {
351  rOutDev.DrawText( rOutpos,
352  maText.Text,
353  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
354  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
355  }
356  }
357 
358  namespace
359  {
360  class OffsetTransformer
361  {
362  public:
363  explicit OffsetTransformer( const ::basegfx::B2DHomMatrix& rMat ) :
364  maMatrix( rMat )
365  {
366  }
367 
368  sal_Int32 operator()( const double& rOffset )
369  {
370  // This is an optimization of the normal rMat*[x,0]
371  // transformation of the advancement vector (in x
372  // direction), followed by a length calculation of the
373  // resulting vector: advancement' =
374  // ||rMat*[x,0]||. Since advancements are vectors, we
375  // can ignore translational components, thus if [x,0],
376  // it follows that rMat*[x,0]=[x',0] holds. Thus, we
377  // just have to calc the transformation of the x
378  // component.
379 
380  // TODO(F2): Handle non-horizontal advancements!
381  return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset,
382  maMatrix.get(1,0)*rOffset) );
383  }
384 
385  private:
387  };
388  }
389 
390  void TextLayout::setupTextOffsets( ::tools::Long* outputOffsets,
391  const uno::Sequence< double >& inputOffsets,
392  const rendering::ViewState& viewState,
393  const rendering::RenderState& renderState ) const
394  {
395  ENSURE_OR_THROW( outputOffsets!=nullptr,
396  "TextLayout::setupTextOffsets offsets NULL" );
397 
398  ::basegfx::B2DHomMatrix aMatrix;
399 
401  viewState,
402  renderState);
403 
404  // fill integer offsets
405  std::transform( inputOffsets.begin(),
406  inputOffsets.end(),
407  outputOffsets,
408  OffsetTransformer( aMatrix ) );
409  }
410 
411  OUString SAL_CALL TextLayout::getImplementationName()
412  {
413  return "VCLCanvas::TextLayout";
414  }
415 
416  sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName )
417  {
418  return cppu::supportsService( this, ServiceName );
419  }
420 
421  uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames()
422  {
423  return { "com.sun.star.rendering.TextLayout" };
424  }
425 }
426 
427 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::shared_ptr< OutDevProvider > OutDevProviderSharedPtr
sal_Int32 nIndex
::basegfx::B2DHomMatrix & mergeViewAndRenderTransform(::basegfx::B2DHomMatrix &combinedTransform, const rendering::ViewState &viewState, const rendering::RenderState &renderState)
signed char sal_Int8
long Long
void SetLayoutMode(ComplexTextLayoutFlags nTextLayoutMode)
void DrawTextArray(const Point &rStartPt, const OUString &rStr, const tools::Long *pDXAry, sal_Int32 nIndex=0, sal_Int32 nLen=-1, SalLayoutFlags flags=SalLayoutFlags::NONE, const SalLayoutGlyphs *pLayoutCache=nullptr)
double get(sal_uInt16 nRow, sal_uInt16 nColumn) const
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
std::mutex m_aMutex
::cppu::WeakComponentImplHelper< css::rendering::XTextLayout, css::lang::XServiceInfo > TextLayout_Base
Definition: textlayout.hxx:38
ComplexTextLayoutFlags
TextLayout(const TextLayout &)=delete
make noncopyable
Target numeric_cast(Source arg)
Cast numeric value into another (numeric) data type.
unsigned char sal_Bool
::basegfx::B2DHomMatrix maMatrix
Definition: textlayout.cxx:386
#define ENSURE_ARG_OR_THROW(c, m)
#define ENSURE_OR_THROW(c, m)
Text maText
css::uno::Sequence< DstElementType > containerToSequence(const SrcType &i_Container)
rtl::Reference< CanvasFont > Reference
Definition: canvasfont.hxx:49
void DrawText(const Point &rStartPt, const OUString &rStr, sal_Int32 nIndex=0, sal_Int32 nLen=-1, std::vector< tools::Rectangle > *pVector=nullptr, OUString *pDisplayText=nullptr, const SalLayoutGlyphs *pLayoutCache=nullptr)
std::optional< vcl::Font > mpFont
::std::vector< B2DPolyPolygon > B2DPolyPolygonVector