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