1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 // MARKER(update_precomp.py): autogen include statement, do not remove 29 #include "precompiled_canvas.hxx" 30 31 #include <canvas/debug.hxx> 32 #include <canvas/canvastools.hxx> 33 #include <tools/diagnose_ex.h> 34 35 #include <vcl/virdev.hxx> 36 #include <vcl/metric.hxx> 37 #include <vcl/canvastools.hxx> 38 39 #include <basegfx/polygon/b2dpolypolygon.hxx> 40 #include <basegfx/tools/canvastools.hxx> 41 42 #include "cairo_canvasfont.hxx" 43 #include "cairo_textlayout.hxx" 44 #include "cairo_canvashelper.hxx" 45 46 using namespace ::cairo; 47 using namespace ::com::sun::star; 48 49 namespace cairocanvas 50 { 51 enum ColorType 52 { 53 LINE_COLOR, FILL_COLOR, TEXT_COLOR, IGNORE_COLOR 54 }; 55 56 uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* , 57 const rendering::FontRequest& fontRequest, 58 const uno::Sequence< beans::PropertyValue >& extraFontProperties, 59 const geometry::Matrix2D& fontMatrix ) 60 { 61 return uno::Reference< rendering::XCanvasFont >( new CanvasFont( fontRequest, extraFontProperties, fontMatrix, mpSurfaceProvider )); 62 } 63 64 uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* , 65 const rendering::FontInfo& /*aFilter*/, 66 const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) 67 { 68 // TODO 69 return uno::Sequence< rendering::FontInfo >(); 70 } 71 72 static bool 73 setupFontTransform( ::OutputDevice& rOutDev, 74 ::Point& o_rPoint, 75 ::Font& io_rVCLFont, 76 const rendering::ViewState& rViewState, 77 const rendering::RenderState& rRenderState ) 78 { 79 ::basegfx::B2DHomMatrix aMatrix; 80 81 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, 82 rViewState, 83 rRenderState); 84 85 ::basegfx::B2DTuple aScale; 86 ::basegfx::B2DTuple aTranslate; 87 double nRotate, nShearX; 88 89 aMatrix.decompose( aScale, aTranslate, nRotate, nShearX ); 90 91 // query font metric _before_ tampering with width and height 92 if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) ) 93 { 94 // retrieve true font width 95 const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetWidth() ); 96 97 const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) ); 98 99 if( !nScaledFontWidth ) 100 { 101 // scale is smaller than one pixel - disable text 102 // output altogether 103 return false; 104 } 105 106 io_rVCLFont.SetWidth( nScaledFontWidth ); 107 } 108 109 if( !::rtl::math::approxEqual(aScale.getY(), 1.0) ) 110 { 111 const sal_Int32 nFontHeight( io_rVCLFont.GetHeight() ); 112 io_rVCLFont.SetHeight( ::basegfx::fround(nFontHeight * aScale.getY()) ); 113 } 114 115 io_rVCLFont.SetOrientation( static_cast< short >( ::basegfx::fround(-fmod(nRotate, 2*M_PI)*(1800.0/M_PI)) ) ); 116 117 // TODO(F2): Missing functionality in VCL: shearing 118 o_rPoint.X() = ::basegfx::fround(aTranslate.getX()); 119 o_rPoint.Y() = ::basegfx::fround(aTranslate.getY()); 120 121 return true; 122 } 123 124 static int 125 setupOutDevState( OutputDevice& rOutDev, 126 const rendering::XCanvas* pOwner, 127 const rendering::ViewState& viewState, 128 const rendering::RenderState& renderState, 129 ColorType eColorType ) 130 { 131 ::canvas::tools::verifyInput( renderState, 132 BOOST_CURRENT_FUNCTION, 133 const_cast<rendering::XCanvas*>(pOwner), // only for refcount 134 2, 135 eColorType == IGNORE_COLOR ? 0 : 3 ); 136 137 int nTransparency(0); 138 139 // TODO(P2): Don't change clipping all the time, maintain current clip 140 // state and change only when update is necessary 141 142 // accumulate non-empty clips into one region 143 // ========================================== 144 145 Region aClipRegion; 146 147 if( viewState.Clip.is() ) 148 { 149 ::basegfx::B2DPolyPolygon aClipPoly( 150 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( 151 viewState.Clip) ); 152 153 if( aClipPoly.count() ) 154 { 155 // setup non-empty clipping 156 ::basegfx::B2DHomMatrix aMatrix; 157 aClipPoly.transform( 158 ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix, 159 viewState.AffineTransform ) ); 160 161 aClipRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) ); 162 } 163 } 164 165 if( renderState.Clip.is() ) 166 { 167 ::basegfx::B2DPolyPolygon aClipPoly( 168 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( 169 renderState.Clip) ); 170 171 ::basegfx::B2DHomMatrix aMatrix; 172 aClipPoly.transform( 173 ::canvas::tools::mergeViewAndRenderTransform( aMatrix, 174 viewState, 175 renderState ) ); 176 177 if( aClipPoly.count() ) 178 { 179 // setup non-empty clipping 180 Region aRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) ); 181 182 if( aClipRegion.IsEmpty() ) 183 aClipRegion = aRegion; 184 else 185 aClipRegion.Intersect( aRegion ); 186 } 187 else 188 { 189 // clip polygon is empty 190 aClipRegion.SetEmpty(); 191 } 192 } 193 194 // setup accumulated clip region. Note that setting an 195 // empty clip region denotes "clip everything" on the 196 // OutputDevice (which is why we translate that into 197 // SetClipRegion() here). When both view and render clip 198 // are empty, aClipRegion remains default-constructed, 199 // i.e. empty, too. 200 if( aClipRegion.IsEmpty() ) 201 { 202 rOutDev.SetClipRegion(); 203 } 204 else 205 { 206 rOutDev.SetClipRegion( aClipRegion ); 207 } 208 209 if( eColorType != IGNORE_COLOR ) 210 { 211 Color aColor( COL_WHITE ); 212 213 if( renderState.DeviceColor.getLength() > 2 ) 214 { 215 aColor = ::vcl::unotools::stdColorSpaceSequenceToColor( renderState.DeviceColor ); 216 } 217 218 // extract alpha, and make color opaque 219 // afterwards. Otherwise, OutputDevice won't draw anything 220 nTransparency = aColor.GetTransparency(); 221 aColor.SetTransparency(0); 222 223 switch( eColorType ) 224 { 225 case LINE_COLOR: 226 rOutDev.SetLineColor( aColor ); 227 rOutDev.SetFillColor(); 228 229 break; 230 231 case FILL_COLOR: 232 rOutDev.SetFillColor( aColor ); 233 rOutDev.SetLineColor(); 234 235 break; 236 237 case TEXT_COLOR: 238 rOutDev.SetTextColor( aColor ); 239 240 break; 241 242 default: 243 ENSURE_OR_THROW( false, 244 "CanvasHelper::setupOutDevState(): Unexpected color type"); 245 break; 246 } 247 } 248 249 return nTransparency; 250 } 251 252 bool setupTextOutput( OutputDevice& rOutDev, 253 const rendering::XCanvas* pOwner, 254 ::Point& o_rOutPos, 255 const rendering::ViewState& viewState, 256 const rendering::RenderState& renderState, 257 const uno::Reference< rendering::XCanvasFont >& xFont ) 258 { 259 setupOutDevState( rOutDev, pOwner, viewState, renderState, TEXT_COLOR ); 260 261 ::Font aVCLFont; 262 263 CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() ); 264 265 ENSURE_ARG_OR_THROW( pFont, 266 "CanvasHelper::setupTextOutput(): Font not compatible with this canvas" ); 267 268 aVCLFont = pFont->getVCLFont(); 269 270 Color aColor( COL_BLACK ); 271 272 if( renderState.DeviceColor.getLength() > 2 ) 273 { 274 aColor = ::vcl::unotools::stdColorSpaceSequenceToColor(renderState.DeviceColor ); 275 } 276 277 // setup font color 278 aVCLFont.SetColor( aColor ); 279 aVCLFont.SetFillColor( aColor ); 280 281 // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here. 282 if( !setupFontTransform( rOutDev, o_rOutPos, aVCLFont, viewState, renderState ) ) 283 return false; 284 285 rOutDev.SetFont( aVCLFont ); 286 287 288 return true; 289 } 290 291 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* pOwner, 292 const rendering::StringContext& text, 293 const uno::Reference< rendering::XCanvasFont >& xFont, 294 const rendering::ViewState& viewState, 295 const rendering::RenderState& renderState, 296 sal_Int8 textDirection ) 297 { 298 #ifdef CAIRO_CANVAS_PERF_TRACE 299 struct timespec aTimer; 300 mxDevice->startPerfTrace( &aTimer ); 301 #endif 302 303 ENSURE_ARG_OR_THROW( xFont.is(), 304 "CanvasHelper::drawText(): font is NULL"); 305 306 if( !mpVirtualDevice ) 307 mpVirtualDevice = mpSurface->createVirtualDevice(); 308 309 if( mpVirtualDevice ) 310 { 311 #if defined CAIRO_HAS_WIN32_SURFACE 312 // FIXME: Some kind of work-araound... 313 cairo_rectangle (mpSurface->getCairo().get(), 0, 0, 0, 0); 314 cairo_fill(mpSurface->getCairo().get()); 315 #endif 316 ::Point aOutpos; 317 if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xFont ) ) 318 return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary 319 320 // change text direction and layout mode 321 sal_uLong nLayoutMode(0); 322 switch( textDirection ) 323 { 324 case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: 325 nLayoutMode |= TEXT_LAYOUT_BIDI_LTR; 326 // FALLTHROUGH intended 327 case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: 328 nLayoutMode |= TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG; 329 nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_LEFT; 330 break; 331 332 case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: 333 nLayoutMode |= TEXT_LAYOUT_BIDI_RTL; 334 // FALLTHROUGH intended 335 case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: 336 nLayoutMode |= TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG; 337 nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_RIGHT; 338 break; 339 } 340 341 // TODO(F2): alpha 342 mpVirtualDevice->SetLayoutMode( nLayoutMode ); 343 344 OSL_TRACE(":cairocanvas::CanvasHelper::drawText(O,t,f,v,r,d): %s", ::rtl::OUStringToOString( text.Text.copy( text.StartPosition, text.Length ), 345 RTL_TEXTENCODING_UTF8 ).getStr()); 346 347 TextLayout* pTextLayout = new TextLayout(text, textDirection, 0, CanvasFont::Reference(dynamic_cast< CanvasFont* >( xFont.get() )), mpSurfaceProvider); 348 pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState ); 349 } 350 351 return uno::Reference< rendering::XCachedPrimitive >(NULL); 352 } 353 354 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* pOwner, 355 const uno::Reference< rendering::XTextLayout >& xLayoutedText, 356 const rendering::ViewState& viewState, 357 const rendering::RenderState& renderState ) 358 { 359 ENSURE_ARG_OR_THROW( xLayoutedText.is(), 360 "CanvasHelper::drawTextLayout(): layout is NULL"); 361 362 TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() ); 363 364 if( pTextLayout ) 365 { 366 if( !mpVirtualDevice ) 367 mpVirtualDevice = mpSurface->createVirtualDevice(); 368 369 if( mpVirtualDevice ) 370 { 371 #if defined CAIRO_HAS_WIN32_SURFACE 372 // FIXME: Some kind of work-araound... 373 cairo_rectangle( mpSurface->getCairo().get(), 0, 0, 0, 0); 374 cairo_fill(mpSurface->getCairo().get()); 375 #endif 376 // TODO(T3): Race condition. We're taking the font 377 // from xLayoutedText, and then calling draw() at it, 378 // without exclusive access. Move setupTextOutput(), 379 // e.g. to impltools? 380 381 ::Point aOutpos; 382 if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xLayoutedText->getFont() ) ) 383 return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary 384 385 // TODO(F2): What about the offset scalings? 386 pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState ); 387 } 388 } 389 else 390 { 391 ENSURE_ARG_OR_THROW( false, 392 "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); 393 } 394 395 return uno::Reference< rendering::XCachedPrimitive >(NULL); 396 } 397 398 } 399