xref: /aoo42x/main/canvas/source/vcl/textlayout.cxx (revision 25ea7f45)
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