xref: /trunk/main/vcl/source/gdi/textlayout.cxx (revision 91144cd0085a7583d2099b982122deb2184ab956)
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_vcl.hxx"
26 #include <iterator>
27 
28 #include "vcl/ctrl.hxx"
29 #include "vcl/outdev.hxx"
30 
31 #include "outfont.hxx"
32 #include "textlayout.hxx"
33 
34 #include <com/sun/star/i18n/ScriptDirection.hpp>
35 
36 #include <tools/diagnose_ex.h>
37 
38 #if OSL_DEBUG_LEVEL > 1
39 #include <rtl/strbuf.hxx>
40 #endif
41 
42 //........................................................................
43 namespace vcl
44 {
45 //........................................................................
46 
47     using ::com::sun::star::uno::Reference;
48     using ::com::sun::star::uno::Exception;
49     namespace ScriptDirection = ::com::sun::star::i18n::ScriptDirection;
50 
51     //====================================================================
52     //= DefaultTextLayout
53     //====================================================================
54     //--------------------------------------------------------------------
~DefaultTextLayout()55     DefaultTextLayout::~DefaultTextLayout()
56     {
57     }
58 
59     //--------------------------------------------------------------------
GetTextWidth(const XubString & _rText,xub_StrLen _nStartIndex,xub_StrLen _nLength) const60     long DefaultTextLayout::GetTextWidth( const XubString& _rText, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
61     {
62         return m_rTargetDevice.GetTextWidth( _rText, _nStartIndex, _nLength );
63     }
64 
65     //--------------------------------------------------------------------
DrawText(const Point & _rStartPoint,const XubString & _rText,xub_StrLen _nStartIndex,xub_StrLen _nLength,MetricVector * _pVector,String * _pDisplayText)66     void DefaultTextLayout::DrawText( const Point& _rStartPoint, const XubString& _rText, xub_StrLen _nStartIndex,
67         xub_StrLen _nLength, MetricVector* _pVector, String* _pDisplayText )
68     {
69         m_rTargetDevice.DrawText( _rStartPoint, _rText, _nStartIndex, _nLength, _pVector, _pDisplayText );
70     }
71 
72     //--------------------------------------------------------------------
GetCaretPositions(const XubString & _rText,sal_Int32 * _pCaretXArray,xub_StrLen _nStartIndex,xub_StrLen _nLength) const73     bool DefaultTextLayout::GetCaretPositions( const XubString& _rText, sal_Int32* _pCaretXArray,
74         xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
75     {
76         return m_rTargetDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
77     }
78 
79     //--------------------------------------------------------------------
GetTextBreak(const XubString & _rText,long _nMaxTextWidth,xub_StrLen _nStartIndex,xub_StrLen _nLength) const80     xub_StrLen DefaultTextLayout::GetTextBreak( const XubString& _rText, long _nMaxTextWidth, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
81     {
82         return m_rTargetDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
83     }
84 
85     //--------------------------------------------------------------------
DecomposeTextRectAction() const86     bool DefaultTextLayout::DecomposeTextRectAction() const
87     {
88         return false;
89     }
90 
91     //====================================================================
92     //= ReferenceDeviceTextLayout
93     //====================================================================
94     class ReferenceDeviceTextLayout : public ITextLayout
95     {
96     public:
97         ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
98         virtual ~ReferenceDeviceTextLayout();
99 
100         // ITextLayout
101         virtual long        GetTextWidth( const XubString& rStr, xub_StrLen nIndex, xub_StrLen nLen ) const;
102         virtual void        DrawText( const Point& _rStartPoint, const XubString& _rText, xub_StrLen _nStartIndex, xub_StrLen _nLength, MetricVector* _pVector, String* _pDisplayText );
103         virtual bool        GetCaretPositions( const XubString& _rText, sal_Int32* _pCaretXArray, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const;
104         virtual xub_StrLen  GetTextBreak( const XubString& _rText, long _nMaxTextWidth, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const;
105         virtual bool        DecomposeTextRectAction() const;
106 
107     public:
108         // equivalents to the respective OutputDevice methods, which take the reference device into account
109         long        GetTextArray( const XubString& _rText, sal_Int32* _pDXAry, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const;
110         Rectangle   DrawText( const Rectangle& _rRect, const XubString& _rText, sal_uInt16 _nStyle, MetricVector* _pVector, String* _pDisplayText );
111 
112     protected:
onBeginDrawText()113         void onBeginDrawText()
114         {
115             m_aCompleteTextRect.SetEmpty();
116         }
onEndDrawText()117         Rectangle onEndDrawText()
118         {
119             return m_aCompleteTextRect;
120         }
121 
122     private:
123         OutputDevice&   m_rTargetDevice;
124         OutputDevice&   m_rReferenceDevice;
125         Font            m_aUnzoomedPointFont;
126         const Fraction  m_aZoom;
127         const bool      m_bRTLEnabled;
128 
129         Rectangle       m_aCompleteTextRect;
130     };
131 
132     //====================================================================
133     //= ControlTextRenderer
134     //====================================================================
ReferenceDeviceTextLayout(const Control & _rControl,OutputDevice & _rTargetDevice,OutputDevice & _rReferenceDevice)135     ReferenceDeviceTextLayout::ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice,
136         OutputDevice& _rReferenceDevice )
137         :m_rTargetDevice( _rTargetDevice )
138         ,m_rReferenceDevice( _rReferenceDevice )
139         ,m_aUnzoomedPointFont( _rControl.GetUnzoomedControlPointFont() )
140         ,m_aZoom( _rControl.GetZoom() )
141         ,m_bRTLEnabled( _rControl.IsRTLEnabled() )
142     {
143         m_rTargetDevice.Push( PUSH_MAPMODE | PUSH_FONT | PUSH_TEXTLAYOUTMODE );
144 
145         MapMode aTargetMapMode( m_rTargetDevice.GetMapMode() );
146         OSL_ENSURE( aTargetMapMode.GetOrigin() == Point(), "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: uhm, the code below won't work here ..." );
147 
148         // normally, controls simulate "zoom" by "zooming" the font. This is responsible for (part of) the discrepancies
149         // between text in Writer and text in controls in Writer, though both have the same font.
150         // So, if we have a zoom set at the control, then we do not scale the font, but instead modify the map mode
151         // to accommodate for the zoom.
152         aTargetMapMode.SetScaleX( m_aZoom );    // TODO: shouldn't this be "current_scale * zoom"?
153         aTargetMapMode.SetScaleY( m_aZoom );
154 
155         // also, use a higher-resolution map unit than "pixels", which should save us some rounding errors when
156         // translating coordinates between the reference device and the target device.
157         OSL_ENSURE( aTargetMapMode.GetMapUnit() == MAP_PIXEL,
158             "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: this class is not expected to work with such target devices!" );
159             // we *could* adjust all the code in this class to handle this case, but at the moment, it's not necessary
160         const MapUnit eTargetMapUnit = m_rReferenceDevice.GetMapMode().GetMapUnit();
161         aTargetMapMode.SetMapUnit( eTargetMapUnit );
162         OSL_ENSURE( aTargetMapMode.GetMapUnit() != MAP_PIXEL,
163             "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: a reference device which has map mode PIXEL?!" );
164 
165         m_rTargetDevice.SetMapMode( aTargetMapMode );
166 
167         // now that the Zoom is part of the map mode, reset the target device's font to the "unzoomed" version
168         Font aDrawFont( m_aUnzoomedPointFont );
169         aDrawFont.SetSize( m_rTargetDevice.LogicToLogic( aDrawFont.GetSize(), MAP_POINT, eTargetMapUnit ) );
170         _rTargetDevice.SetFont( aDrawFont );
171 
172         // transfer font to the reference device
173         m_rReferenceDevice.Push( PUSH_FONT | PUSH_TEXTLAYOUTMODE );
174         Font aRefFont( m_aUnzoomedPointFont );
175         aRefFont.SetSize( OutputDevice::LogicToLogic(
176             aRefFont.GetSize(), MAP_POINT, m_rReferenceDevice.GetMapMode().GetMapUnit() ) );
177         m_rReferenceDevice.SetFont( aRefFont );
178     }
179 
180     //--------------------------------------------------------------------
~ReferenceDeviceTextLayout()181     ReferenceDeviceTextLayout::~ReferenceDeviceTextLayout()
182     {
183         m_rReferenceDevice.Pop();
184         m_rTargetDevice.Pop();
185     }
186 
187     //--------------------------------------------------------------------
188     namespace
189     {
190         //................................................................
lcl_normalizeLength(const XubString & _rText,const xub_StrLen _nStartIndex,xub_StrLen & _io_nLength)191         bool lcl_normalizeLength( const XubString& _rText, const xub_StrLen _nStartIndex, xub_StrLen& _io_nLength )
192         {
193             xub_StrLen nTextLength = _rText.Len();
194             if ( _nStartIndex > nTextLength )
195                 return false;
196             if ( _nStartIndex + _io_nLength > nTextLength )
197                 _io_nLength = nTextLength - _nStartIndex;
198             return true;
199         }
200     }
201 
202     //--------------------------------------------------------------------
GetTextArray(const XubString & _rText,sal_Int32 * _pDXAry,xub_StrLen _nStartIndex,xub_StrLen _nLength) const203     long ReferenceDeviceTextLayout::GetTextArray( const XubString& _rText, sal_Int32* _pDXAry, xub_StrLen _nStartIndex,
204         xub_StrLen _nLength ) const
205     {
206         if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
207             return 0;
208 
209         // retrieve the character widths from the reference device
210         long nTextWidth = m_rReferenceDevice.GetTextArray( _rText, _pDXAry, _nStartIndex, _nLength );
211 #if OSL_DEBUG_LEVEL > 1
212         if ( _pDXAry )
213         {
214             ::rtl::OStringBuffer aTrace;
215             aTrace.append( "ReferenceDeviceTextLayout::GetTextArray( " );
216             aTrace.append( ::rtl::OUStringToOString( _rText, RTL_TEXTENCODING_UTF8 ) );
217             aTrace.append( " ): " );
218             aTrace.append( nTextWidth );
219             aTrace.append( " = ( " );
220             for ( size_t i=0; i<_nLength; )
221             {
222                 aTrace.append( _pDXAry[i] );
223                 if ( ++i < _nLength )
224                     aTrace.append( ", " );
225             }
226             aTrace.append( ")" );
227             OSL_TRACE( aTrace.makeStringAndClear().getStr() );
228         }
229 #endif
230         return nTextWidth;
231     }
232 
233     //--------------------------------------------------------------------
GetTextWidth(const XubString & _rText,xub_StrLen _nStartIndex,xub_StrLen _nLength) const234     long ReferenceDeviceTextLayout::GetTextWidth( const XubString& _rText, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
235     {
236         return GetTextArray( _rText, NULL, _nStartIndex, _nLength );
237     }
238 
239     //--------------------------------------------------------------------
DrawText(const Point & _rStartPoint,const XubString & _rText,xub_StrLen _nStartIndex,xub_StrLen _nLength,MetricVector * _pVector,String * _pDisplayText)240     void ReferenceDeviceTextLayout::DrawText( const Point& _rStartPoint, const XubString& _rText, xub_StrLen _nStartIndex, xub_StrLen _nLength, MetricVector* _pVector, String* _pDisplayText )
241     {
242         if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
243             return;
244 
245         if ( _pVector && _pDisplayText )
246         {
247             MetricVector aGlyphBounds;
248             m_rReferenceDevice.GetGlyphBoundRects( _rStartPoint, _rText, _nStartIndex, _nLength, _nStartIndex, aGlyphBounds );
249             ::std::copy(
250                 aGlyphBounds.begin(), aGlyphBounds.end(),
251                 ::std::insert_iterator< MetricVector > ( *_pVector, _pVector->end() ) );
252             _pDisplayText->Append( _rText.Copy( _nStartIndex, _nLength ) );
253             return;
254         }
255 
256         sal_Int32* pCharWidths = new sal_Int32[ _nLength ];
257         long nTextWidth = GetTextArray( _rText, pCharWidths, _nStartIndex, _nLength );
258         m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, pCharWidths, _nStartIndex, _nLength );
259         delete[] pCharWidths;
260 
261         m_aCompleteTextRect.Union( Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
262     }
263 
264     //--------------------------------------------------------------------
GetCaretPositions(const XubString & _rText,sal_Int32 * _pCaretXArray,xub_StrLen _nStartIndex,xub_StrLen _nLength) const265     bool ReferenceDeviceTextLayout::GetCaretPositions( const XubString& _rText, sal_Int32* _pCaretXArray,
266         xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
267     {
268         if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
269             return false;
270 
271         // retrieve the caret positions from the reference device
272         if ( !m_rReferenceDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength ) )
273             return false;
274 
275         return true;
276     }
277 
278     //--------------------------------------------------------------------
GetTextBreak(const XubString & _rText,long _nMaxTextWidth,xub_StrLen _nStartIndex,xub_StrLen _nLength) const279     xub_StrLen ReferenceDeviceTextLayout::GetTextBreak( const XubString& _rText, long _nMaxTextWidth, xub_StrLen _nStartIndex, xub_StrLen _nLength ) const
280     {
281         if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
282             return 0;
283 
284         return m_rReferenceDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
285     }
286 
287     //--------------------------------------------------------------------
DecomposeTextRectAction() const288     bool ReferenceDeviceTextLayout::DecomposeTextRectAction() const
289     {
290         return true;
291     }
292 
293     //--------------------------------------------------------------------
294     namespace
295     {
zoomBy(long _value,const Fraction & _zoom)296         long zoomBy( long _value, const Fraction& _zoom )
297         {
298             double n = (double)_value;
299             n *= (double)_zoom.GetNumerator();
300             n /= (double)_zoom.GetDenominator();
301             return (long)::rtl::math::round( n );
302         }
unzoomBy(long _value,const Fraction & _zoom)303         long unzoomBy( long _value, const Fraction& _zoom )
304         {
305             return zoomBy( _value, Fraction( _zoom.GetDenominator(), _zoom.GetNumerator() ) );
306         }
307     }
308 
309     //--------------------------------------------------------------------
DrawText(const Rectangle & _rRect,const XubString & _rText,sal_uInt16 _nStyle,MetricVector * _pVector,String * _pDisplayText)310     Rectangle ReferenceDeviceTextLayout::DrawText( const Rectangle& _rRect, const XubString& _rText, sal_uInt16 _nStyle, MetricVector* _pVector, String* _pDisplayText )
311     {
312         if ( !_rText.Len() )
313             return Rectangle();
314 
315         // determine text layout mode from the RTL-ness of the control whose text we render
316         sal_uLong nTextLayoutMode = m_bRTLEnabled ? TEXT_LAYOUT_BIDI_RTL : TEXT_LAYOUT_BIDI_LTR;
317         m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
318         m_rTargetDevice.SetLayoutMode( nTextLayoutMode | TEXT_LAYOUT_TEXTORIGIN_LEFT );
319             // TEXT_LAYOUT_TEXTORIGIN_LEFT is because when we do actually draw the text (in DrawText( Point, ... )), then
320             // our caller gives us the left border of the draw position, regardless of script type, text layout,
321             // and the like
322 
323         // in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
324         // but passed pixel coordinates. So, adjust the rect.
325         Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
326 
327         onBeginDrawText();
328         m_rTargetDevice.DrawText( aRect, _rText, _nStyle, _pVector, _pDisplayText, this );
329         Rectangle aTextRect = onEndDrawText();
330 
331         if ( aTextRect.IsEmpty() && !aRect.IsEmpty() )
332         {
333             // this happens for instance if we're in a PaintToDevice call, where only a MetaFile is recorded,
334             // but no actual painting happens, so our "DrawText( Point, ... )" is never called
335             // In this case, calculate the rect from what OutputDevice::GetTextRect would give us. This has
336             // the disadvantage of less accuracy, compared with the approach to calculate the rect from the
337             // single "DrawText( Point, ... )" calls, since more intermediate arithmetics will translate
338             // from ref- to target-units.
339             aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, NULL, this );
340         }
341 
342         // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
343         // expects pixel coordinates
344         aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
345 
346         // convert the metric vector
347         if ( _pVector )
348         {
349             for (   MetricVector::iterator charRect = _pVector->begin();
350                     charRect != _pVector->end();
351                     ++charRect
352                 )
353             {
354                 *charRect = m_rTargetDevice.LogicToPixel( *charRect );
355             }
356         }
357 
358         return aTextRect;
359     }
360 
361     //====================================================================
362     //= ControlTextRenderer
363     //====================================================================
364     //--------------------------------------------------------------------
ControlTextRenderer(const Control & _rControl,OutputDevice & _rTargetDevice,OutputDevice & _rReferenceDevice)365     ControlTextRenderer::ControlTextRenderer( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice )
366         :m_pImpl( new ReferenceDeviceTextLayout( _rControl, _rTargetDevice, _rReferenceDevice ) )
367     {
368     }
369 
370     //--------------------------------------------------------------------
~ControlTextRenderer()371     ControlTextRenderer::~ControlTextRenderer()
372     {
373     }
374 
375     //--------------------------------------------------------------------
DrawText(const Rectangle & _rRect,const XubString & _rText,sal_uInt16 _nStyle,MetricVector * _pVector,String * _pDisplayText)376     Rectangle ControlTextRenderer::DrawText( const Rectangle& _rRect, const XubString& _rText, sal_uInt16 _nStyle,
377         MetricVector* _pVector, String* _pDisplayText )
378     {
379         return m_pImpl->DrawText( _rRect, _rText, _nStyle, _pVector, _pDisplayText );
380     }
381 
382 //........................................................................
383 } // namespace vcl
384 //........................................................................
385