LibreOffice Module canvas (master)  1
cairo_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 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <math.h>
24 #include <memory>
25 
26 #include <com/sun/star/rendering/TextDirection.hpp>
27 #include <canvas/canvastools.hxx>
31 #include <tools/diagnose_ex.h>
32 #include <vcl/metric.hxx>
33 #include <vcl/sysdata.hxx>
34 #include <vcl/virdev.hxx>
35 
36 
37 #include "cairo_textlayout.hxx"
38 
39 #include <cairo.h>
40 
41 #if defined CAIRO_HAS_FT_FONT
42 # include <cairo-ft.h>
43 #else
44 # error Native API needed.
45 #endif
46 
47 using namespace ::cairo;
48 using namespace ::com::sun::star;
49 
50 namespace cairocanvas
51 {
52  namespace
53  {
54  void setupLayoutMode( OutputDevice& rOutDev,
55  sal_Int8 nTextDirection )
56  {
57  // TODO(P3): avoid if already correctly set
58  ComplexTextLayoutFlags nLayoutMode = ComplexTextLayoutFlags::Default;
59  switch( nTextDirection )
60  {
61  case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
62  break;
63  case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
64  nLayoutMode = ComplexTextLayoutFlags::BiDiStrong;
65  break;
66  case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
67  nLayoutMode = ComplexTextLayoutFlags::BiDiRtl;
68  break;
69  case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
70  nLayoutMode = ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::BiDiStrong;
71  break;
72  default:
73  break;
74  }
75 
76  // set calculated layout mode. Origin is always the left edge,
77  // as required at the API spec
78  rOutDev.SetLayoutMode( nLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
79  }
80 
81  bool compareFallbacks(const SystemGlyphData&rA, const SystemGlyphData &rB)
82  {
83  return rA.fallbacklevel < rB.fallbacklevel;
84  }
85  }
86 
87  TextLayout::TextLayout( const rendering::StringContext& aText,
88  sal_Int8 nDirection,
89  sal_Int64 /*nRandomSeed*/,
90  const CanvasFont::Reference& rFont,
91  const SurfaceProviderRef& rRefDevice ) :
93  maText( aText ),
94  maLogicalAdvancements(),
95  mpFont( rFont ),
96  mpRefDevice( rRefDevice ),
97  mnTextDirection( nDirection )
98  {
99  }
100 
101  TextLayout::~TextLayout()
102  {
103  }
104 
105  void SAL_CALL TextLayout::disposing()
106  {
107  ::osl::MutexGuard aGuard( m_aMutex );
108 
109  mpFont.clear();
110  mpRefDevice.clear();
111  }
112 
113  // XTextLayout
114  uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( )
115  {
116  // TODO
117  return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >();
118  }
119 
120  uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( )
121  {
122  // TODO
123  return uno::Sequence< geometry::RealRectangle2D >();
124  }
125 
126  uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( )
127  {
128  // TODO
129  return uno::Sequence< geometry::RealRectangle2D >();
130  }
131 
132  uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( )
133  {
134  ::osl::MutexGuard aGuard( m_aMutex );
135 
136  return maLogicalAdvancements;
137  }
138 
139  void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements )
140  {
141  ::osl::MutexGuard aGuard( m_aMutex );
142 
143  if( aAdvancements.getLength() != maText.Length )
144  {
145  SAL_WARN("canvas.cairo", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" );
146  throw lang::IllegalArgumentException();
147  }
148 
149  maLogicalAdvancements = aAdvancements;
150  }
151 
152  geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( )
153  {
154  ::osl::MutexGuard aGuard( m_aMutex );
155 
156  OutputDevice* pOutDev = mpRefDevice->getOutputDevice();
157  if( !pOutDev )
158  return geometry::RealRectangle2D();
159 
160  ScopedVclPtrInstance< VirtualDevice > pVDev( *pOutDev );
161  pVDev->SetFont( mpFont->getVCLFont() );
162 
163  // need metrics for Y offset, the XCanvas always renders
164  // relative to baseline
165  const ::FontMetric& aMetric( pVDev->GetFontMetric() );
166 
167  setupLayoutMode( *pVDev, mnTextDirection );
168 
169  const sal_Int32 nAboveBaseline( -aMetric.GetAscent() );
170  const sal_Int32 nBelowBaseline( aMetric.GetDescent() );
171 
172  if( maLogicalAdvancements.hasElements() )
173  {
174  return geometry::RealRectangle2D( 0, nAboveBaseline,
175  maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ],
176  nBelowBaseline );
177  }
178  else
179  {
180  return geometry::RealRectangle2D( 0, nAboveBaseline,
181  pVDev->GetTextWidth(
182  maText.Text,
183  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
184  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ),
185  nBelowBaseline );
186  }
187  }
188 
189  double SAL_CALL TextLayout::justify( double /*nSize*/ )
190  {
191  // TODO
192  return 0.0;
193  }
194 
195  double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/,
196  double /*nSize*/ )
197  {
198  // TODO
199  return 0.0;
200  }
201 
202  rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ )
203  {
204  // TODO
205  return rendering::TextHit();
206  }
207 
208  rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/,
209  sal_Bool /*bExcludeLigatures*/ )
210  {
211  // TODO
212  return rendering::Caret();
213  }
214 
215  sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/,
216  sal_Int32 /*nCaretAdvancement*/,
217  sal_Bool /*bExcludeLigatures*/ )
218  {
219  // TODO
220  return 0;
221  }
222 
223  uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/,
224  sal_Int32 /*nEndIndex*/ )
225  {
226  // TODO
227  return uno::Reference< rendering::XPolyPolygon2D >();
228  }
229 
230  uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/,
231  sal_Int32 /*nEndIndex*/ )
232  {
233  // TODO
234  return uno::Reference< rendering::XPolyPolygon2D >();
235  }
236 
237  double SAL_CALL TextLayout::getBaselineOffset( )
238  {
239  // TODO
240  return 0.0;
241  }
242 
243  sal_Int8 SAL_CALL TextLayout::getMainTextDirection( )
244  {
245  ::osl::MutexGuard aGuard( m_aMutex );
246 
247  return mnTextDirection;
248  }
249 
250  uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( )
251  {
252  ::osl::MutexGuard aGuard( m_aMutex );
253 
254  return mpFont.get();
255  }
256 
257  rendering::StringContext SAL_CALL TextLayout::getText( )
258  {
259  ::osl::MutexGuard aGuard( m_aMutex );
260 
261  return maText;
262  }
263 
272  bool TextLayout::isCairoRenderable(SystemFontData aSysFontData) const
273  {
274 #if defined CAIRO_HAS_FT_FONT
275  // is font usable?
276  if (!aSysFontData.nFontId)
277  return false;
278 #endif
279 
280  // vertical glyph rendering is not supported in cairo for now
281  if (aSysFontData.bVerticalCharacterType)
282  {
283  SAL_WARN("canvas.cairo", ":cairocanvas::TextLayout::isCairoRenderable(): Vertical Character Style not supported");
284  return false;
285  }
286 
287  return true;
288  }
289 
290 
300  void TextLayout::draw( CairoSharedPtr const & pSCairo,
301  OutputDevice& rOutDev,
302  const Point& rOutpos,
303  const rendering::ViewState& viewState,
304  const rendering::RenderState& renderState ) const
305  {
306  ::osl::MutexGuard aGuard( m_aMutex );
307  SystemTextLayoutData aSysLayoutData;
308  setupLayoutMode( rOutDev, mnTextDirection );
309 
310  // TODO(P2): cache that
311  std::unique_ptr< long []> aOffsets(new long[maLogicalAdvancements.getLength()]);
312 
313  if( maLogicalAdvancements.hasElements() )
314  {
315  setupTextOffsets( aOffsets.get(), maLogicalAdvancements, viewState, renderState );
316 
317  // TODO(F3): ensure correct length and termination for DX
318  // array (last entry _must_ contain the overall width)
319  }
320 
321  aSysLayoutData = rOutDev.GetSysTextLayoutData(rOutpos, maText.Text,
322  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
323  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length),
324  maLogicalAdvancements.hasElements() ? aOffsets.get() : nullptr);
325 
326  // Sort them so that all glyphs on the same glyph fallback level are consecutive
327  std::sort(aSysLayoutData.rGlyphData.begin(), aSysLayoutData.rGlyphData.end(), compareFallbacks);
328  bool bCairoRenderable = true;
329 
330  //Pull all the fonts we need to render the text
331  typedef std::pair<SystemFontData,int> FontLevel;
332  std::vector<FontLevel> aFontData;
333  for (auto const& glyph : aSysLayoutData.rGlyphData)
334  {
335  if( aFontData.empty() || glyph.fallbacklevel != aFontData.back().second )
336  {
337  aFontData.emplace_back(rOutDev.GetSysFontData(glyph.fallbacklevel),
338  glyph.fallbacklevel);
339  if( !isCairoRenderable(aFontData.back().first) )
340  {
341  bCairoRenderable = false;
342  SAL_INFO("canvas.cairo", ":cairocanvas::TextLayout::draw(S,O,p,v,r): VCL FALLBACK " <<
343  (maLogicalAdvancements.hasElements() ? "ADV " : "") <<
344  (aFontData.back().first.bAntialias ? "AA " : "") <<
345  (aFontData.back().first.bFakeBold ? "FB " : "") <<
346  (aFontData.back().first.bFakeItalic ? "FI " : "") <<
347  " - " <<
348  maText.Text.copy( maText.StartPosition, maText.Length));
349  break;
350  }
351  }
352  }
353 
354  // The ::GetSysTextLayoutData(), i.e. layouting of text to glyphs can change the font being used.
355  // The fallback checks need to be done after final font is known.
356  if (!bCairoRenderable) // VCL FALLBACKS
357  {
358  if (maLogicalAdvancements.hasElements()) // VCL FALLBACK - with glyph advances
359  {
360  rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets.get(),
361  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
362  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
363  return;
364  }
365  else // VCL FALLBACK - without advances
366  {
367  rOutDev.DrawText( rOutpos, maText.Text,
368  ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
369  ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
370  return;
371  }
372  }
373 
374  if (aSysLayoutData.rGlyphData.empty())
375  return; //??? false?
376 
381  // Loop through the fonts used and render the matching glyphs for each
382  for (auto const& elemFontData : aFontData)
383  {
384  const SystemFontData &rSysFontData = elemFontData.first;
385 
386  // setup glyphs
387  std::vector<cairo_glyph_t> cairo_glyphs;
388  cairo_glyphs.reserve( 256 );
389 
390  for (auto const& systemGlyph : aSysLayoutData.rGlyphData)
391  {
392  if( systemGlyph.fallbacklevel != elemFontData.second )
393  continue;
394 
395  cairo_glyph_t aGlyph;
396  aGlyph.index = systemGlyph.index;
397  aGlyph.x = systemGlyph.x;
398  aGlyph.y = systemGlyph.y;
399  cairo_glyphs.push_back(aGlyph);
400  }
401 
402  if (cairo_glyphs.empty())
403  continue;
404 
405  const vcl::Font& aFont = rOutDev.GetFont();
406  long nWidth = aFont.GetAverageFontWidth();
407  long nHeight = aFont.GetFontHeight();
408  if (nWidth == 0)
409  nWidth = nHeight;
410  if (nWidth == 0 || nHeight == 0)
411  continue;
412 
416  cairo_font_face_t* font_face = nullptr;
417 
418 #if defined CAIRO_HAS_FT_FONT
419  font_face = cairo_ft_font_face_create_for_ft_face(static_cast<FT_Face>(rSysFontData.nFontId),
420  rSysFontData.nFontFlags);
421 #else
422 # error Native API needed.
423 #endif
424 
425  cairo_set_font_face( pSCairo.get(), font_face);
426 
427  // create default font options. cairo_get_font_options() does not retrieve the surface defaults,
428  // only what has been set before with cairo_set_font_options()
429  cairo_font_options_t* options = cairo_font_options_create();
430  if (rSysFontData.bAntialias)
431  {
432  // CAIRO_ANTIALIAS_GRAY provides more similar result to VCL Canvas,
433  // so we're not using CAIRO_ANTIALIAS_SUBPIXEL
434  cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY);
435  }
436  cairo_set_font_options( pSCairo.get(), options);
437 
438  // Font color
439  Color aTextColor = rOutDev.GetTextColor();
440  cairo_set_source_rgb(pSCairo.get(),
441  aTextColor.GetRed()/255.0,
442  aTextColor.GetGreen()/255.0,
443  aTextColor.GetBlue()/255.0);
444 
445  // Font rotation and scaling
446  cairo_matrix_t m;
447 
448  cairo_matrix_init_identity(&m);
449 
450  if (aSysLayoutData.orientation)
451  cairo_matrix_rotate(&m, (3600 - aSysLayoutData.orientation) * M_PI / 1800.0);
452 
453  cairo_matrix_scale(&m, nWidth, nHeight);
454 
455  //faux italics
456  if (rSysFontData.bFakeItalic)
457  m.xy = -m.xx * 0x6000 / 0x10000;
458 
459  cairo_set_font_matrix(pSCairo.get(), &m);
460 
461  SAL_INFO(
462  "canvas.cairo",
463  "Size:(" << aFont.GetAverageFontWidth() << "," << aFont.GetFontHeight()
464  << "), Pos (" << rOutpos.X() << "," << rOutpos.Y()
465  << "), G("
466  << (!cairo_glyphs.empty() ? cairo_glyphs[0].index : -1)
467  << ","
468  << (cairo_glyphs.size() > 1 ? cairo_glyphs[1].index : -1)
469  << ","
470  << (cairo_glyphs.size() > 2 ? cairo_glyphs[2].index : -1)
471  << ") " << (maLogicalAdvancements.hasElements() ? "ADV " : "")
472  << (rSysFontData.bAntialias ? "AA " : "")
473  << (rSysFontData.bFakeBold ? "FB " : "")
474  << (rSysFontData.bFakeItalic ? "FI " : "") << " || Name:"
475  << aFont.GetFamilyName() << " - "
476  << maText.Text.copy(maText.StartPosition, maText.Length));
477 
478  cairo_show_glyphs(pSCairo.get(), cairo_glyphs.data(), cairo_glyphs.size());
479 
480  //faux bold
481  if (rSysFontData.bFakeBold)
482  {
483  double bold_dx = 0.5 * sqrt( 0.7 * aFont.GetFontHeight() );
484  int total_steps = 1 * static_cast<int>(bold_dx + 0.5);
485 
486  // loop to draw the text for every half pixel of displacement
487  for (int nSteps = 0; nSteps < total_steps; nSteps++)
488  {
489  for(cairo_glyph_t & cairo_glyph : cairo_glyphs)
490  {
491  cairo_glyph.x += (bold_dx * nSteps / total_steps) / 4;
492  cairo_glyph.y -= (bold_dx * nSteps / total_steps) / 4;
493  }
494  cairo_show_glyphs(pSCairo.get(), cairo_glyphs.data(), cairo_glyphs.size());
495  }
496  SAL_INFO("canvas.cairo",":cairocanvas::TextLayout::draw(S,O,p,v,r): FAKEBOLD - dx:" << static_cast<int>(bold_dx));
497  }
498 
499  cairo_font_face_destroy(font_face);
500  cairo_font_options_destroy(options);
501  }
502  }
503 
504  namespace
505  {
506  class OffsetTransformer
507  {
508  public:
509  explicit OffsetTransformer( const ::basegfx::B2DHomMatrix& rMat ) :
510  maMatrix( rMat )
511  {
512  }
513 
514  sal_Int32 operator()( const double& rOffset )
515  {
516  // This is an optimization of the normal rMat*[x,0]
517  // transformation of the advancement vector (in x
518  // direction), followed by a length calculation of the
519  // resulting vector: advancement' =
520  // ||rMat*[x,0]||. Since advancements are vectors, we
521  // can ignore translational components, thus if [x,0],
522  // it follows that rMat*[x,0]=[x',0] holds. Thus, we
523  // just have to calc the transformation of the x
524  // component.
525 
526  // TODO(F2): Handle non-horizontal advancements!
527  return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset,
528  maMatrix.get(1,0)*rOffset) );
529  }
530 
531  private:
533  };
534  }
535 
536  void TextLayout::setupTextOffsets( long* outputOffsets,
537  const uno::Sequence< double >& inputOffsets,
538  const rendering::ViewState& viewState,
539  const rendering::RenderState& renderState ) const
540  {
541  ENSURE_OR_THROW( outputOffsets!=nullptr,
542  "TextLayout::setupTextOffsets offsets NULL" );
543 
544  ::basegfx::B2DHomMatrix aMatrix;
545 
547  viewState,
548  renderState);
549 
550  // fill integer offsets
551  std::transform( inputOffsets.begin(),
552  inputOffsets.end(),
553  outputOffsets,
554  OffsetTransformer( aMatrix ) );
555  }
556 
557  OUString SAL_CALL TextLayout::getImplementationName()
558  {
559  return "CairoCanvas::TextLayout";
560  }
561 
562  sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName )
563  {
564  return cppu::supportsService( this, ServiceName );
565  }
566 
567  uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames()
568  {
569  return { "com.sun.star.rendering.TextLayout" };
570  }
571 }
572 
573 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
const Color & GetTextColor() const
void DrawText(const Point &rStartPt, const OUString &rStr, sal_Int32 nIndex=0, sal_Int32 nLen=-1, MetricVector *pVector=nullptr, OUString *pDisplayText=nullptr, const SalLayoutGlyphs *pLayoutCache=nullptr)
const OUString & GetFamilyName() const
osl::Mutex m_aMutex
long GetFontHeight() const
TextLayout(const TextLayout &)=delete
make noncopyable
::basegfx::B2DHomMatrix & mergeViewAndRenderTransform(::basegfx::B2DHomMatrix &combinedTransform, const rendering::ViewState &viewState, const rendering::RenderState &renderState)
signed char sal_Int8
void SetLayoutMode(ComplexTextLayoutFlags nTextLayoutMode)
SystemGlyphDataVector rGlyphData
double get(sal_uInt16 nRow, sal_uInt16 nColumn) const
std::shared_ptr< cairo_t > CairoSharedPtr
rtl::Reference< CanvasFont > Reference
const vcl::Font & GetFont() const
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
::cppu::WeakComponentImplHelper< css::rendering::XTextLayout, css::lang::XServiceInfo > TextLayout_Base
void DrawTextArray(const Point &rStartPt, const OUString &rStr, const long *pDXAry, sal_Int32 nIndex=0, sal_Int32 nLen=-1, SalLayoutFlags flags=SalLayoutFlags::NONE, const SalLayoutGlyphs *pLayoutCache=nullptr)
ComplexTextLayoutFlags
SystemTextLayoutData GetSysTextLayoutData(const Point &rStartPt, const OUString &rStr, sal_Int32 nIndex, sal_Int32 nLen, const long *pDXAry) const
Target numeric_cast(Source arg)
Cast numeric value into another (numeric) data type.
unsigned char sal_Bool
SystemFontData GetSysFontData(int nFallbacklevel) const
std::unique_ptr< vcl::Font > mpFont
#define ENSURE_OR_THROW(c, m)
struct _cairo_font_options cairo_font_options_t
Text maText
#define SAL_INFO(area, stream)
::basegfx::B2DHomMatrix maMatrix
::rtl::Reference< SurfaceProvider > SurfaceProviderRef
#define SAL_WARN(area, stream)
tuple m
long GetAverageFontWidth() const