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