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 <tools/diagnose_ex.h> 29 #include <canvas/canvastools.hxx> 30 31 #include <com/sun/star/rendering/CompositeOperation.hpp> 32 #include <com/sun/star/rendering/TextDirection.hpp> 33 34 #include <vcl/metric.hxx> 35 #include <vcl/virdev.hxx> 36 37 #include <basegfx/matrix/b2dhommatrix.hxx> 38 #include <basegfx/numeric/ftools.hxx> 39 #include <basegfx/tools/canvastools.hxx> 40 41 #include "impltools.hxx" 42 #include "textlayout.hxx" 43 44 #include <boost/scoped_array.hpp> 45 46 47 using namespace ::com::sun::star; 48 49 namespace vclcanvas 50 { 51 namespace 52 { setupLayoutMode(OutputDevice & rOutDev,sal_Int8 nTextDirection)53 void setupLayoutMode( OutputDevice& rOutDev, 54 sal_Int8 nTextDirection ) 55 { 56 // TODO(P3): avoid if already correctly set 57 sal_uIntPtr nLayoutMode; 58 switch( nTextDirection ) 59 { 60 default: 61 nLayoutMode = 0; 62 break; 63 case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: 64 nLayoutMode = TEXT_LAYOUT_BIDI_LTR; 65 break; 66 case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: 67 nLayoutMode = TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG; 68 break; 69 case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: 70 nLayoutMode = TEXT_LAYOUT_BIDI_RTL; 71 break; 72 case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: 73 nLayoutMode = TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG; 74 break; 75 } 76 77 // set calculated layout mode. Origin is always the left edge, 78 // as required at the API spec 79 rOutDev.SetLayoutMode( nLayoutMode | TEXT_LAYOUT_TEXTORIGIN_LEFT ); 80 } 81 } 82 TextLayout(const rendering::StringContext & aText,sal_Int8 nDirection,sal_Int64 nRandomSeed,const CanvasFont::Reference & rFont,const uno::Reference<rendering::XGraphicDevice> & xDevice,const OutDevProviderSharedPtr & rOutDev)83 TextLayout::TextLayout( const rendering::StringContext& aText, 84 sal_Int8 nDirection, 85 sal_Int64 nRandomSeed, 86 const CanvasFont::Reference& rFont, 87 const uno::Reference<rendering::XGraphicDevice>& xDevice, 88 const OutDevProviderSharedPtr& rOutDev ) : 89 TextLayout_Base( m_aMutex ), 90 maText( aText ), 91 maLogicalAdvancements(), 92 mpFont( rFont ), 93 mxDevice( xDevice ), 94 mpOutDevProvider( rOutDev ), 95 mnTextDirection( nDirection ) 96 { 97 (void)nRandomSeed; 98 } 99 disposing()100 void SAL_CALL TextLayout::disposing() 101 { 102 tools::LocalGuard aGuard; 103 104 mpOutDevProvider.reset(); 105 mxDevice.clear(); 106 mpFont.reset(); 107 } 108 109 // XTextLayout queryTextShapes()110 uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) throw (uno::RuntimeException) 111 { 112 tools::LocalGuard aGuard; 113 114 OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); 115 VirtualDevice aVDev( rOutDev ); 116 aVDev.SetFont( mpFont->getVCLFont() ); 117 118 setupLayoutMode( aVDev, mnTextDirection ); 119 120 const rendering::ViewState aViewState( 121 geometry::AffineMatrix2D(1,0,0, 0,1,0), 122 NULL); 123 124 rendering::RenderState aRenderState ( 125 geometry::AffineMatrix2D(1,0,0,0,1,0), 126 NULL, 127 uno::Sequence<double>(4), 128 rendering::CompositeOperation::SOURCE); 129 130 ::boost::scoped_array< sal_Int32 > aOffsets(new sal_Int32[maLogicalAdvancements.getLength()]); 131 setupTextOffsets(aOffsets.get(), maLogicalAdvancements, aViewState, aRenderState); 132 133 uno::Sequence< uno::Reference< rendering::XPolyPolygon2D> > aOutlineSequence; 134 ::basegfx::B2DPolyPolygonVector aOutlines; 135 if (aVDev.GetTextOutlines( 136 aOutlines, 137 maText.Text, 138 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 139 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 140 ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length), 141 sal_False, 142 0, 143 aOffsets.get())) 144 { 145 aOutlineSequence.realloc(aOutlines.size()); 146 sal_Int32 nIndex (0); 147 for (::basegfx::B2DPolyPolygonVector::const_iterator 148 iOutline(aOutlines.begin()), 149 iEnd(aOutlines.end()); 150 iOutline!=iEnd; 151 ++iOutline) 152 { 153 aOutlineSequence[nIndex++] = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( 154 mxDevice, 155 *iOutline); 156 } 157 } 158 159 return aOutlineSequence; 160 } 161 queryInkMeasures()162 uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) throw (uno::RuntimeException) 163 { 164 tools::LocalGuard aGuard; 165 166 167 OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); 168 VirtualDevice aVDev( rOutDev ); 169 aVDev.SetFont( mpFont->getVCLFont() ); 170 171 setupLayoutMode( aVDev, mnTextDirection ); 172 173 const rendering::ViewState aViewState( 174 geometry::AffineMatrix2D(1,0,0, 0,1,0), 175 NULL); 176 177 rendering::RenderState aRenderState ( 178 geometry::AffineMatrix2D(1,0,0,0,1,0), 179 NULL, 180 uno::Sequence<double>(4), 181 rendering::CompositeOperation::SOURCE); 182 183 ::boost::scoped_array< sal_Int32 > aOffsets(new sal_Int32[maLogicalAdvancements.getLength()]); 184 setupTextOffsets(aOffsets.get(), maLogicalAdvancements, aViewState, aRenderState); 185 186 MetricVector aMetricVector; 187 uno::Sequence<geometry::RealRectangle2D> aBoundingBoxes; 188 if (aVDev.GetGlyphBoundRects( 189 Point(0,0), 190 maText.Text, 191 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 192 ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length), 193 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 194 aMetricVector)) 195 { 196 aBoundingBoxes.realloc(aMetricVector.size()); 197 sal_Int32 nIndex (0); 198 for (MetricVector::const_iterator 199 iMetric(aMetricVector.begin()), 200 iEnd(aMetricVector.end()); 201 iMetric!=iEnd; 202 ++iMetric) 203 { 204 aBoundingBoxes[nIndex++] = geometry::RealRectangle2D( 205 iMetric->getX(), 206 iMetric->getY(), 207 iMetric->getX() + iMetric->getWidth(), 208 iMetric->getY() + iMetric->getHeight()); 209 } 210 } 211 return aBoundingBoxes; 212 } 213 queryMeasures()214 uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) throw (uno::RuntimeException) 215 { 216 tools::LocalGuard aGuard; 217 218 // TODO(F1) 219 return uno::Sequence< geometry::RealRectangle2D >(); 220 } 221 queryLogicalAdvancements()222 uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) throw (uno::RuntimeException) 223 { 224 tools::LocalGuard aGuard; 225 226 return maLogicalAdvancements; 227 } 228 applyLogicalAdvancements(const uno::Sequence<double> & aAdvancements)229 void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) throw (lang::IllegalArgumentException, uno::RuntimeException) 230 { 231 tools::LocalGuard aGuard; 232 233 ENSURE_ARG_OR_THROW( aAdvancements.getLength() == maText.Length, 234 "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); 235 236 maLogicalAdvancements = aAdvancements; 237 } 238 queryTextBounds()239 geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) throw (uno::RuntimeException) 240 { 241 tools::LocalGuard aGuard; 242 243 if( !mpOutDevProvider ) 244 return geometry::RealRectangle2D(); 245 246 OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); 247 248 VirtualDevice aVDev( rOutDev ); 249 aVDev.SetFont( mpFont->getVCLFont() ); 250 251 // need metrics for Y offset, the XCanvas always renders 252 // relative to baseline 253 const ::FontMetric& aMetric( aVDev.GetFontMetric() ); 254 255 setupLayoutMode( aVDev, mnTextDirection ); 256 257 const sal_Int32 nAboveBaseline( /*-aMetric.GetIntLeading()*/ - aMetric.GetAscent() ); 258 const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); 259 260 if( maLogicalAdvancements.getLength() ) 261 { 262 return geometry::RealRectangle2D( 0, nAboveBaseline, 263 maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ], 264 nBelowBaseline ); 265 } 266 else 267 { 268 return geometry::RealRectangle2D( 0, nAboveBaseline, 269 aVDev.GetTextWidth( 270 maText.Text, 271 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 272 ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ), 273 nBelowBaseline ); 274 } 275 } 276 justify(double nSize)277 double SAL_CALL TextLayout::justify( double nSize ) throw (lang::IllegalArgumentException, uno::RuntimeException) 278 { 279 tools::LocalGuard aGuard; 280 281 (void)nSize; 282 283 // TODO(F1) 284 return 0.0; 285 } 286 combinedJustify(const uno::Sequence<uno::Reference<rendering::XTextLayout>> & aNextLayouts,double nSize)287 double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& aNextLayouts, 288 double nSize ) throw (lang::IllegalArgumentException, uno::RuntimeException) 289 { 290 tools::LocalGuard aGuard; 291 292 (void)aNextLayouts; 293 (void)nSize; 294 295 // TODO(F1) 296 return 0.0; 297 } 298 getTextHit(const geometry::RealPoint2D & aHitPoint)299 rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& aHitPoint ) throw (uno::RuntimeException) 300 { 301 tools::LocalGuard aGuard; 302 303 (void)aHitPoint; 304 305 // TODO(F1) 306 return rendering::TextHit(); 307 } 308 getCaret(sal_Int32 nInsertionIndex,sal_Bool bExcludeLigatures)309 rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException) 310 { 311 tools::LocalGuard aGuard; 312 313 (void)nInsertionIndex; 314 (void)bExcludeLigatures; 315 316 // TODO(F1) 317 return rendering::Caret(); 318 } 319 getNextInsertionIndex(sal_Int32 nStartIndex,sal_Int32 nCaretAdvancement,sal_Bool bExcludeLigatures)320 sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException) 321 { 322 tools::LocalGuard aGuard; 323 324 (void)nStartIndex; 325 (void)nCaretAdvancement; 326 (void)bExcludeLigatures; 327 328 // TODO(F1) 329 return 0; 330 } 331 queryVisualHighlighting(sal_Int32 nStartIndex,sal_Int32 nEndIndex)332 uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException) 333 { 334 tools::LocalGuard aGuard; 335 336 (void)nStartIndex; 337 (void)nEndIndex; 338 339 // TODO(F1) 340 return uno::Reference< rendering::XPolyPolygon2D >(); 341 } 342 queryLogicalHighlighting(sal_Int32 nStartIndex,sal_Int32 nEndIndex)343 uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException) 344 { 345 tools::LocalGuard aGuard; 346 347 (void)nStartIndex; 348 (void)nEndIndex; 349 350 // TODO(F1) 351 return uno::Reference< rendering::XPolyPolygon2D >(); 352 } 353 getBaselineOffset()354 double SAL_CALL TextLayout::getBaselineOffset( ) throw (uno::RuntimeException) 355 { 356 tools::LocalGuard aGuard; 357 358 // TODO(F1) 359 return 0.0; 360 } 361 getMainTextDirection()362 sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) throw (uno::RuntimeException) 363 { 364 tools::LocalGuard aGuard; 365 366 return mnTextDirection; 367 } 368 getFont()369 uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) throw (uno::RuntimeException) 370 { 371 tools::LocalGuard aGuard; 372 373 return mpFont.getRef(); 374 } 375 getText()376 rendering::StringContext SAL_CALL TextLayout::getText( ) throw (uno::RuntimeException) 377 { 378 tools::LocalGuard aGuard; 379 380 return maText; 381 } 382 draw(OutputDevice & rOutDev,const Point & rOutpos,const rendering::ViewState & viewState,const rendering::RenderState & renderState) const383 bool TextLayout::draw( OutputDevice& rOutDev, 384 const Point& rOutpos, 385 const rendering::ViewState& viewState, 386 const rendering::RenderState& renderState ) const 387 { 388 tools::LocalGuard aGuard; 389 390 setupLayoutMode( rOutDev, mnTextDirection ); 391 392 if( maLogicalAdvancements.getLength() ) 393 { 394 // TODO(P2): cache that 395 ::boost::scoped_array< sal_Int32 > aOffsets(new sal_Int32[maLogicalAdvancements.getLength()]); 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 rOutDev.DrawTextArray( rOutpos, 402 maText.Text, 403 aOffsets.get(), 404 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 405 ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); 406 } 407 else 408 { 409 rOutDev.DrawText( rOutpos, 410 maText.Text, 411 ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), 412 ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); 413 } 414 415 return true; 416 } 417 418 namespace 419 { 420 class OffsetTransformer 421 { 422 public: OffsetTransformer(const::basegfx::B2DHomMatrix & rMat)423 OffsetTransformer( const ::basegfx::B2DHomMatrix& rMat ) : 424 maMatrix( rMat ) 425 { 426 } 427 operator ()(const double & rOffset)428 sal_Int32 operator()( const double& rOffset ) 429 { 430 // This is an optimization of the normal rMat*[x,0] 431 // transformation of the advancement vector (in x 432 // direction), followed by a length calculation of the 433 // resulting vector: advancement' = 434 // ||rMat*[x,0]||. Since advancements are vectors, we 435 // can ignore translational components, thus if [x,0], 436 // it follows that rMat*[x,0]=[x',0] holds. Thus, we 437 // just have to calc the transformation of the x 438 // component. 439 440 // TODO(F2): Handle non-horizontal advancements! 441 return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset, 442 maMatrix.get(1,0)*rOffset) ); 443 } 444 445 private: 446 ::basegfx::B2DHomMatrix maMatrix; 447 }; 448 } 449 setupTextOffsets(sal_Int32 * outputOffsets,const uno::Sequence<double> & inputOffsets,const rendering::ViewState & viewState,const rendering::RenderState & renderState) const450 void TextLayout::setupTextOffsets( sal_Int32* outputOffsets, 451 const uno::Sequence< double >& inputOffsets, 452 const rendering::ViewState& viewState, 453 const rendering::RenderState& renderState ) const 454 { 455 ENSURE_OR_THROW( outputOffsets!=NULL, 456 "TextLayout::setupTextOffsets offsets NULL" ); 457 458 ::basegfx::B2DHomMatrix aMatrix; 459 460 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, 461 viewState, 462 renderState); 463 464 // fill integer offsets 465 ::std::transform( const_cast< uno::Sequence< double >& >(inputOffsets).getConstArray(), 466 const_cast< uno::Sequence< double >& >(inputOffsets).getConstArray()+inputOffsets.getLength(), 467 outputOffsets, 468 OffsetTransformer( aMatrix ) ); 469 } 470 471 472 #define IMPLEMENTATION_NAME "VCLCanvas::TextLayout" 473 #define SERVICE_NAME "com.sun.star.rendering.TextLayout" 474 getImplementationName()475 ::rtl::OUString SAL_CALL TextLayout::getImplementationName() throw( uno::RuntimeException ) 476 { 477 return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( IMPLEMENTATION_NAME ) ); 478 } 479 supportsService(const::rtl::OUString & ServiceName)480 sal_Bool SAL_CALL TextLayout::supportsService( const ::rtl::OUString& ServiceName ) throw( uno::RuntimeException ) 481 { 482 return ServiceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( SERVICE_NAME ) ); 483 } 484 getSupportedServiceNames()485 uno::Sequence< ::rtl::OUString > SAL_CALL TextLayout::getSupportedServiceNames() throw( uno::RuntimeException ) 486 { 487 uno::Sequence< ::rtl::OUString > aRet(1); 488 aRet[0] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM ( SERVICE_NAME ) ); 489 490 return aRet; 491 } 492 } 493