1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_canvas.hxx"
26 
27 #include <math.h>
28 
29 #include <canvas/debug.hxx>
30 #include <canvas/verbosetrace.hxx>
31 #include <tools/diagnose_ex.h>
32 
33 #include <vcl/metric.hxx>
34 #include <vcl/virdev.hxx>
35 
36 #ifdef WNT
37 #include <tools/prewin.h>
38 #include <windows.h>
39 #include <tools/postwin.h>
40 #ifdef max
41 #undef max
42 #endif
43 #ifdef min
44 #undef min
45 #endif
46 #endif
47 #include <vcl/sysdata.hxx>
48 
49 #include <basegfx/matrix/b2dhommatrix.hxx>
50 #include <basegfx/numeric/ftools.hxx>
51 
52 #include <boost/scoped_array.hpp>
53 
54 #include "cairo_textlayout.hxx"
55 #include "cairo_spritecanvas.hxx"
56 
57 #ifdef CAIRO_HAS_QUARTZ_SURFACE
58 # include "cairo_quartz_cairo.hxx"
59 #elif defined CAIRO_HAS_WIN32_SURFACE
60 # include "cairo_win32_cairo.hxx"
61 # include <cairo-win32.h>
62 #elif defined CAIRO_HAS_XLIB_SURFACE
63 # include "cairo_xlib_cairo.hxx"
64 # include <cairo-ft.h>
65 #else
66 # error Native API needed.
67 #endif
68 
69 using namespace ::cairo;
70 using namespace ::com::sun::star;
71 
72 namespace cairocanvas
73 {
74     namespace
75     {
76         void setupLayoutMode( OutputDevice& rOutDev,
77                               sal_Int8		nTextDirection )
78         {
79             // TODO(P3): avoid if already correctly set
80             sal_uLong nLayoutMode;
81             switch( nTextDirection )
82             {
83                 default:
84                     nLayoutMode = 0;
85                     break;
86                 case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
87                     nLayoutMode = TEXT_LAYOUT_BIDI_LTR;
88                     break;
89                 case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
90                     nLayoutMode = TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG;
91                     break;
92                 case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
93                     nLayoutMode = TEXT_LAYOUT_BIDI_RTL;
94                     break;
95                 case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
96                     nLayoutMode = TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG;
97                     break;
98             }
99 
100             // set calculated layout mode. Origin is always the left edge,
101             // as required at the API spec
102             rOutDev.SetLayoutMode( nLayoutMode | TEXT_LAYOUT_TEXTORIGIN_LEFT );
103         }
104 
105         bool compareFallbacks(const SystemGlyphData&rA, const SystemGlyphData &rB)
106         {
107             return rA.fallbacklevel < rB.fallbacklevel;
108         }
109     }
110 
111     TextLayout::TextLayout( const rendering::StringContext& 	aText,
112                             sal_Int8                        	nDirection,
113                             sal_Int64                       	/*nRandomSeed*/,
114                             const CanvasFont::Reference&      	rFont,
115 							const SurfaceProviderRef&			rRefDevice ) :
116         TextLayout_Base( m_aMutex ),
117         maText( aText ),
118         maLogicalAdvancements(),
119         mpFont( rFont ),
120         mpRefDevice( rRefDevice ),
121         mnTextDirection( nDirection )
122     {
123     }
124 
125     TextLayout::~TextLayout()
126     {
127     }
128 
129     void SAL_CALL TextLayout::disposing()
130     {
131         ::osl::MutexGuard aGuard( m_aMutex );
132 
133         mpFont.reset();
134         mpRefDevice.clear();
135     }
136 
137     // XTextLayout
138     uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes(  ) throw (uno::RuntimeException)
139     {
140         ::osl::MutexGuard aGuard( m_aMutex );
141 
142         // TODO
143         return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >();
144     }
145 
146     uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures(  ) throw (uno::RuntimeException)
147     {
148         ::osl::MutexGuard aGuard( m_aMutex );
149 
150         // TODO
151         return uno::Sequence< geometry::RealRectangle2D >();
152     }
153 
154     uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures(  ) throw (uno::RuntimeException)
155     {
156         ::osl::MutexGuard aGuard( m_aMutex );
157 
158         // TODO
159         return uno::Sequence< geometry::RealRectangle2D >();
160     }
161 
162     uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements(  ) throw (uno::RuntimeException)
163     {
164         ::osl::MutexGuard aGuard( m_aMutex );
165 
166         return maLogicalAdvancements;
167     }
168 
169     void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) throw (lang::IllegalArgumentException, uno::RuntimeException)
170     {
171         ::osl::MutexGuard aGuard( m_aMutex );
172 
173         if( aAdvancements.getLength() != maText.Length )
174         {
175             OSL_TRACE( "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" );
176             throw lang::IllegalArgumentException();
177         }
178 
179         maLogicalAdvancements = aAdvancements;
180     }
181 
182     geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds(  ) throw (uno::RuntimeException)
183     {
184         ::osl::MutexGuard aGuard( m_aMutex );
185 
186         OutputDevice* pOutDev = mpRefDevice->getOutputDevice();
187     	if( !pOutDev )
188             return geometry::RealRectangle2D();
189 
190         VirtualDevice aVDev( *pOutDev );
191         aVDev.SetFont( mpFont->getVCLFont() );
192 
193         // need metrics for Y offset, the XCanvas always renders
194         // relative to baseline
195         const ::FontMetric& aMetric( aVDev.GetFontMetric() );
196 
197         setupLayoutMode( aVDev, mnTextDirection );
198 
199         const sal_Int32 nAboveBaseline( -aMetric.GetIntLeading() - aMetric.GetAscent() );
200         const sal_Int32 nBelowBaseline( aMetric.GetDescent() );
201 
202         if( maLogicalAdvancements.getLength() )
203         {
204             return geometry::RealRectangle2D( 0, nAboveBaseline,
205                                               maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ],
206                                               nBelowBaseline );
207         }
208         else
209         {
210             return geometry::RealRectangle2D( 0, nAboveBaseline,
211                                               aVDev.GetTextWidth(
212                                                   maText.Text,
213                                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
214                                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ),
215                                               nBelowBaseline );
216         }
217     }
218 
219     double SAL_CALL TextLayout::justify( double /*nSize*/ ) throw (lang::IllegalArgumentException, uno::RuntimeException)
220     {
221         ::osl::MutexGuard aGuard( m_aMutex );
222 
223         // TODO
224         return 0.0;
225     }
226 
227     double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/,
228                                                  double /*nSize*/ ) throw (lang::IllegalArgumentException, uno::RuntimeException)
229     {
230         ::osl::MutexGuard aGuard( m_aMutex );
231 
232         // TODO
233         return 0.0;
234     }
235 
236     rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) throw (uno::RuntimeException)
237     {
238         ::osl::MutexGuard aGuard( m_aMutex );
239 
240         // TODO
241         return rendering::TextHit();
242     }
243 
244     rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/,
245                                                     sal_Bool /*bExcludeLigatures*/ ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException)
246     {
247         ::osl::MutexGuard aGuard( m_aMutex );
248 
249         // TODO
250         return rendering::Caret();
251     }
252 
253     sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/,
254                                                           sal_Int32 /*nCaretAdvancement*/,
255                                                           sal_Bool /*bExcludeLigatures*/ ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException)
256     {
257         ::osl::MutexGuard aGuard( m_aMutex );
258 
259         // TODO
260         return 0;
261     }
262 
263     uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/,
264                                                                                               sal_Int32 /*nEndIndex*/ ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException)
265     {
266         ::osl::MutexGuard aGuard( m_aMutex );
267 
268         // TODO
269         return uno::Reference< rendering::XPolyPolygon2D >();
270     }
271 
272     uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/,
273                                                                                                sal_Int32 /*nEndIndex*/ ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException)
274     {
275         ::osl::MutexGuard aGuard( m_aMutex );
276 
277         // TODO
278         return uno::Reference< rendering::XPolyPolygon2D >();
279     }
280 
281     double SAL_CALL TextLayout::getBaselineOffset(  ) throw (uno::RuntimeException)
282     {
283         ::osl::MutexGuard aGuard( m_aMutex );
284 
285         // TODO
286         return 0.0;
287     }
288 
289     sal_Int8 SAL_CALL TextLayout::getMainTextDirection(  ) throw (uno::RuntimeException)
290     {
291         ::osl::MutexGuard aGuard( m_aMutex );
292 
293         return mnTextDirection;
294     }
295 
296     uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont(  ) throw (uno::RuntimeException)
297     {
298         ::osl::MutexGuard aGuard( m_aMutex );
299 
300         return mpFont.getRef();
301     }
302 
303     rendering::StringContext SAL_CALL TextLayout::getText(  ) throw (uno::RuntimeException)
304     {
305         ::osl::MutexGuard aGuard( m_aMutex );
306 
307         return maText;
308     }
309 
310     void TextLayout::useFont( Cairo* pCairo )
311     {
312 	rendering::FontRequest aFontRequest = mpFont->getFontRequest();
313 	rendering::FontInfo aFontInfo = aFontRequest.FontDescription;
314 
315 	cairo_select_font_face( pCairo, ::rtl::OUStringToOString( aFontInfo.FamilyName, RTL_TEXTENCODING_UTF8 ), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
316 	cairo_set_font_size( pCairo, aFontRequest.CellSize );
317     }
318 
319   /** TextLayout:draw
320    *
321    * This function uses the "toy" api of the cairo library
322    *
323    **/
324     bool TextLayout::draw( Cairo* pCairo )
325     {
326         ::osl::MutexGuard aGuard( m_aMutex );
327 
328 		::rtl::OUString aSubText = maText.Text.copy( maText.StartPosition, maText.Length );
329 		::rtl::OString aUTF8String = ::rtl::OUStringToOString( aSubText, RTL_TEXTENCODING_UTF8 );
330 
331 		cairo_save( pCairo );
332 		/* move to 0, 0 as cairo_show_text advances current point and current point is not restored by cairo_restore.
333 		   before we were depending on unmodified current point which I believed was preserved by save/restore */
334 		cairo_move_to( pCairo, 0, 0 );
335 		useFont( pCairo );
336 		cairo_show_text( pCairo, aUTF8String.getStr() );
337 		cairo_restore( pCairo );
338 
339         return true;
340     }
341 
342 
343   /**
344    * TextLayout::isCairoRenderable
345    *
346    * Features currently not supported by Cairo (VCL rendering is used as fallback):
347    * - vertical glyphs
348    *
349    * @return true, if text/font can be rendered with cairo
350    **/
351     bool TextLayout::isCairoRenderable(SystemFontData aSysFontData) const
352     {
353 #if defined UNX && !defined QUARTZ
354         // is font usable?
355         if (!aSysFontData.nFontId) return false;
356 #endif
357 
358         // vertical glyph rendering is not supported in cairo for now
359         if (aSysFontData.bVerticalCharacterType) {
360             OSL_TRACE(":cairocanvas::TextLayout::isCairoRenderable(): ***************** VERTICAL CHARACTER STYLE!!! ****************");
361             return false;
362         }
363 
364         return true;
365     }
366 
367   /**
368    * TextLayout::draw
369    *
370    * Cairo-based text rendering. Draw text directly on the cairo surface with cairo fonts.
371    * Avoid using VCL VirtualDevices for that, bypassing VCL DrawText functions, when possible
372    *
373    * Note: some text effects are not rendered due to lacking generic canvas or cairo canvas
374    *       implementation. See issues 92657, 92658, 92659, 92660, 97529
375    *
376    * @return true, if successful
377    **/
378     bool TextLayout::draw( SurfaceSharedPtr&             pSurface,
379                            OutputDevice&                 rOutDev,
380                            const Point&                  rOutpos,
381                            const rendering::ViewState&   viewState,
382                            const rendering::RenderState& renderState ) const
383     {
384         ::osl::MutexGuard aGuard( m_aMutex );
385         SystemTextLayoutData aSysLayoutData;
386 #if (defined CAIRO_HAS_WIN32_SURFACE) && (OSL_DEBUG_LEVEL > 1)
387         LOGFONTW logfont;
388 #endif
389         setupLayoutMode( rOutDev, mnTextDirection );
390 
391         // TODO(P2): cache that
392         ::boost::scoped_array< sal_Int32 > aOffsets(new sal_Int32[maLogicalAdvancements.getLength()]);
393 
394         if( maLogicalAdvancements.getLength() )
395         {
396             setupTextOffsets( aOffsets.get(), maLogicalAdvancements, viewState, renderState );
397 
398             // TODO(F3): ensure correct length and termination for DX
399             // array (last entry _must_ contain the overall width)
400         }
401 
402         aSysLayoutData = rOutDev.GetSysTextLayoutData(rOutpos, maText.Text,
403                                                       ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
404                                                       ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length),
405                                                       maLogicalAdvancements.getLength() ? aOffsets.get() : NULL);
406 
407         // Sort them so that all glyphs on the same glyph fallback level are consecutive
408         std::sort(aSysLayoutData.rGlyphData.begin(), aSysLayoutData.rGlyphData.end(), compareFallbacks);
409         bool bCairoRenderable = true;
410 
411         //Pull all the fonts we need to render the text
412         typedef std::pair<SystemFontData,int> FontLevel;
413         typedef std::vector<FontLevel> FontLevelVector;
414         FontLevelVector aFontData;
415         SystemGlyphDataVector::const_iterator aIter=aSysLayoutData.rGlyphData.begin();
416         const SystemGlyphDataVector::const_iterator aEnd=aSysLayoutData.rGlyphData.end();
417         for( ; aIter != aEnd; ++aIter )
418         {
419             if( aFontData.empty() || aIter->fallbacklevel != aFontData.back().second )
420             {
421                 aFontData.push_back(FontLevel(rOutDev.GetSysFontData(aIter->fallbacklevel),
422                                               aIter->fallbacklevel));
423                 if( !isCairoRenderable(aFontData.back().first) )
424                 {
425                     bCairoRenderable = false;
426                     OSL_TRACE(":cairocanvas::TextLayout::draw(S,O,p,v,r): VCL FALLBACK %s%s%s%s - %s",
427                               maLogicalAdvancements.getLength() ? "ADV " : "",
428                               aFontData.back().first.bAntialias ? "AA " : "",
429                               aFontData.back().first.bFakeBold ? "FB " : "",
430                               aFontData.back().first.bFakeItalic ? "FI " : "",
431                               ::rtl::OUStringToOString( maText.Text.copy( maText.StartPosition, maText.Length ),
432                                                         RTL_TEXTENCODING_UTF8 ).getStr());
433                     break;
434                 }
435             }
436         }
437 
438         // The ::GetSysTextLayoutData(), i.e. layouting of text to glyphs can change the font being used.
439         // The fallback checks need to be done after final font is known.
440         if (!bCairoRenderable)    // VCL FALLBACKS
441         {
442             if (maLogicalAdvancements.getLength())        // VCL FALLBACK - with glyph advances
443             {
444                 rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets.get(),
445                                        ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
446                                        ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
447                 return true;
448             }
449             else                                               // VCL FALLBACK - without advances
450             {
451                 rOutDev.DrawText( rOutpos, maText.Text,
452                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
453                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
454                 return true;
455             }
456         }
457 
458         if (aSysLayoutData.rGlyphData.empty()) return false; //??? false?
459 
460         /**
461          * Setup platform independent glyph vector into cairo-based glyphs vector.
462          **/
463 
464         // Loop through the fonts used and render the matching glyphs for each
465         FontLevelVector::const_iterator aFontDataIter = aFontData.begin();
466         const FontLevelVector::const_iterator aFontDataEnd = aFontData.end();
467         for( ; aFontDataIter != aFontDataEnd; ++aFontDataIter )
468         {
469             const SystemFontData &rSysFontData = aFontDataIter->first;
470 
471             // setup glyphs
472             std::vector<cairo_glyph_t> cairo_glyphs;
473             cairo_glyphs.reserve( 256 );
474 
475             SystemGlyphDataVector::const_iterator aIter=aSysLayoutData.rGlyphData.begin();
476             const SystemGlyphDataVector::const_iterator aEnd=aSysLayoutData.rGlyphData.end();
477             for( ; aIter != aEnd; ++aIter )
478             {
479                 SystemGlyphData systemGlyph = *aIter;
480                 if( systemGlyph.fallbacklevel != aFontDataIter->second )
481                     continue;
482 
483                 cairo_glyph_t aGlyph;
484                 aGlyph.index = systemGlyph.index;
485     #ifdef CAIRO_HAS_WIN32_SURFACE
486                 // Cairo requires standard glyph indexes (ETO_GLYPH_INDEX), while vcl/win/* uses ucs4 chars.
487                 // Convert to standard indexes
488                 aGlyph.index = cairo::ucs4toindex((unsigned int) aGlyph.index, rSysFontData.hFont);
489     #endif
490                 aGlyph.x = systemGlyph.x;
491                 aGlyph.y = systemGlyph.y;
492                 cairo_glyphs.push_back(aGlyph);
493             }
494 
495             if (cairo_glyphs.empty())
496                 continue;
497 
498             /**
499              * Setup font
500              **/
501             cairo_font_face_t* font_face = NULL;
502 
503     #ifdef CAIRO_HAS_QUARTZ_SURFACE
504             // TODO: use cairo_quartz_font_face_create_for_cgfont(cgFont)
505             //       when CGFont (Mac OS X 10.5 API) is provided by the AQUA VCL backend.
506             font_face = cairo_quartz_font_face_create_for_atsu_font_id((ATSUFontID) rSysFontData.aATSUFontID);
507 
508     #elif defined CAIRO_HAS_WIN32_SURFACE
509       #if (OSL_DEBUG_LEVEL > 1)
510             GetObjectW( rSysFontData.hFont, sizeof(logfont), &logfont );
511       #endif
512             // Note: cairo library uses logfont fallbacks when lfEscapement, lfOrientation and lfWidth are not zero.
513             // VCL always has non-zero value for lfWidth
514             font_face = cairo_win32_font_face_create_for_hfont(rSysFontData.hFont);
515 
516     #elif defined CAIRO_HAS_XLIB_SURFACE
517             font_face = cairo_ft_font_face_create_for_ft_face((FT_Face)rSysFontData.nFontId,
518                                                               rSysFontData.nFontFlags);
519     #else
520     # error Native API needed.
521     #endif
522 
523             CairoSharedPtr pSCairo = pSurface->getCairo();
524 
525             cairo_set_font_face( pSCairo.get(), font_face);
526 
527             // create default font options. cairo_get_font_options() does not retrieve the surface defaults,
528             // only what has been set before with cairo_set_font_options()
529             cairo_font_options_t* options = cairo_font_options_create();
530             if (rSysFontData.bAntialias) {
531                 // CAIRO_ANTIALIAS_GRAY provides more similar result to VCL Canvas,
532                 // so we're not using CAIRO_ANTIALIAS_SUBPIXEL
533                 cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY);
534             }
535             cairo_set_font_options( pSCairo.get(), options);
536 
537             // Font color
538             Color mTextColor = rOutDev.GetTextColor();
539             cairo_set_source_rgb(pSCairo.get(),
540                                  mTextColor.GetRed()/255.0,
541                                  mTextColor.GetGreen()/255.0,
542                                  mTextColor.GetBlue()/255.0);
543 
544             // Font rotation and scaling
545             cairo_matrix_t m;
546             Font aFont = rOutDev.GetFont();
547             FontMetric aMetric( rOutDev.GetFontMetric(aFont) );
548             long nWidth = 0;
549 
550             // width calculation is deep magic and platform/font dependent.
551             // width == 0 means no scaling, and usually width == height means the same.
552             // Other values mean horizontal scaling (narrow or stretching)
553             // see issue #101566
554 
555             //proper scale calculation across platforms
556             if (aFont.GetWidth() == 0) {
557                 nWidth = aFont.GetHeight();
558             } else {
559                 // any scaling needs to be relative to the platform-dependent definition
560                 // of height of the font
561                 nWidth = aFont.GetWidth() * aFont.GetHeight() / aMetric.GetHeight();
562             }
563 
564             cairo_matrix_init_identity(&m);
565 
566             if (aSysLayoutData.orientation) cairo_matrix_rotate(&m, (3600 - aSysLayoutData.orientation) * M_PI / 1800.0);
567 
568             cairo_matrix_scale(&m, nWidth, aFont.GetHeight());
569 
570             //faux italics
571             if (rSysFontData.bFakeItalic) m.xy = -m.xx * 0x6000L / 0x10000L;
572 
573             cairo_set_font_matrix(pSCairo.get(), &m);
574 
575             OSL_TRACE("\r\n:cairocanvas::TextLayout::draw(S,O,p,v,r): Size:(%d,%d), W:%d->%d, Pos (%d,%d), G(%d,%d,%d) %s%s%s%s || Name:%s - %s",
576                       aFont.GetWidth(),
577                       aFont.GetHeight(),
578                       aMetric.GetWidth(),
579                       nWidth,
580                       (int) rOutpos.X(),
581                       (int) rOutpos.Y(),
582                       cairo_glyphs[0].index, cairo_glyphs[1].index, cairo_glyphs[2].index,
583                       maLogicalAdvancements.getLength() ? "ADV " : "",
584                       rSysFontData.bAntialias ? "AA " : "",
585                       rSysFontData.bFakeBold ? "FB " : "",
586                       rSysFontData.bFakeItalic ? "FI " : "",
587     #if (defined CAIRO_HAS_WIN32_SURFACE) && (OSL_DEBUG_LEVEL > 1)
588                       ::rtl::OUStringToOString( reinterpret_cast<const sal_Unicode*> (logfont.lfFaceName), RTL_TEXTENCODING_UTF8 ).getStr(),
589     #else
590                       ::rtl::OUStringToOString( aFont.GetName(), RTL_TEXTENCODING_UTF8 ).getStr(),
591     #endif
592                       ::rtl::OUStringToOString( maText.Text.copy( maText.StartPosition, maText.Length ),
593                                                 RTL_TEXTENCODING_UTF8 ).getStr()
594                 );
595 
596             cairo_show_glyphs(pSCairo.get(), &cairo_glyphs[0], cairo_glyphs.size());
597 
598             //faux bold
599             if (rSysFontData.bFakeBold) {
600                 double bold_dx = 0.5 * sqrt( 0.7 * aFont.GetHeight() );
601                 int total_steps = 2 * ((int) (bold_dx + 0.5));
602 
603                 // loop to draw the text for every half pixel of displacement
604                 for (int nSteps = 0; nSteps < total_steps; nSteps++) {
605                     for(int nGlyphIdx = 0; nGlyphIdx < (int) cairo_glyphs.size(); nGlyphIdx++) {
606                         cairo_glyphs[nGlyphIdx].x += bold_dx * nSteps / total_steps;
607                     }
608                     cairo_show_glyphs(pSCairo.get(), &cairo_glyphs[0], cairo_glyphs.size());
609                 }
610                 OSL_TRACE(":cairocanvas::TextLayout::draw(S,O,p,v,r): FAKEBOLD - dx:%d", (int) bold_dx);
611             }
612 
613             cairo_restore( pSCairo.get() );
614             cairo_font_face_destroy(font_face);
615         }
616         return true;
617     }
618 
619 
620     namespace
621     {
622         class OffsetTransformer
623         {
624         public:
625             OffsetTransformer( const ::basegfx::B2DHomMatrix& rMat ) :
626                 maMatrix( rMat )
627             {
628             }
629 
630             sal_Int32 operator()( const double& rOffset )
631             {
632                 // This is an optimization of the normal rMat*[x,0]
633                 // transformation of the advancement vector (in x
634                 // direction), followed by a length calculation of the
635                 // resulting vector: advancement' =
636                 // ||rMat*[x,0]||. Since advancements are vectors, we
637                 // can ignore translational components, thus if [x,0],
638                 // it follows that rMat*[x,0]=[x',0] holds. Thus, we
639                 // just have to calc the transformation of the x
640                 // component.
641 
642                 // TODO(F2): Handle non-horizontal advancements!
643                 return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset,
644 												maMatrix.get(1,0)*rOffset) );
645             }
646 
647         private:
648             ::basegfx::B2DHomMatrix maMatrix;
649         };
650     }
651 
652     void TextLayout::setupTextOffsets( sal_Int32*						outputOffsets,
653                                        const uno::Sequence< double >& 	inputOffsets,
654                                        const rendering::ViewState& 		viewState,
655                                        const rendering::RenderState& 	renderState		) const
656     {
657         ENSURE_OR_THROW( outputOffsets!=NULL,
658                           "TextLayout::setupTextOffsets offsets NULL" );
659 
660         ::basegfx::B2DHomMatrix aMatrix;
661 
662         ::canvas::tools::mergeViewAndRenderTransform(aMatrix,
663                                                      viewState,
664                                                      renderState);
665 
666         // fill integer offsets
667         ::std::transform( const_cast< uno::Sequence< double >& >(inputOffsets).getConstArray(),
668                           const_cast< uno::Sequence< double >& >(inputOffsets).getConstArray()+inputOffsets.getLength(),
669                           outputOffsets,
670                           OffsetTransformer( aMatrix ) );
671     }
672 
673 #define SERVICE_NAME "com.sun.star.rendering.TextLayout"
674 #define IMPLEMENTATION_NAME "CairoCanvas::TextLayout"
675 
676     ::rtl::OUString SAL_CALL TextLayout::getImplementationName() throw( uno::RuntimeException )
677     {
678         return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( IMPLEMENTATION_NAME ) );
679     }
680 
681     sal_Bool SAL_CALL TextLayout::supportsService( const ::rtl::OUString& ServiceName ) throw( uno::RuntimeException )
682     {
683         return ServiceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( SERVICE_NAME ) );
684     }
685 
686     uno::Sequence< ::rtl::OUString > SAL_CALL TextLayout::getSupportedServiceNames()  throw( uno::RuntimeException )
687     {
688         uno::Sequence< ::rtl::OUString > aRet(1);
689         aRet[0] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM ( SERVICE_NAME ) );
690 
691         return aRet;
692     }
693 }
694