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