Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / source / gdi / textlayout.cxx
blob5c0d0efa95958c92746aed1b9e0846cfaa8605ce
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <vcl/ctrl.hxx>
21 #include <vcl/outdev.hxx>
23 #include "fontinstance.hxx"
24 #include "textlayout.hxx"
26 #include <com/sun/star/i18n/ScriptDirection.hpp>
28 #include <tools/diagnose_ex.h>
29 #include <tools/fract.hxx>
31 #if OSL_DEBUG_LEVEL > 1
32 #include <rtl/strbuf.hxx>
33 #endif
35 #include <memory>
36 #include <iterator>
38 namespace vcl
41 DefaultTextLayout::~DefaultTextLayout()
45 long DefaultTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
47 return m_rTargetDevice.GetTextWidth( _rText, _nStartIndex, _nLength );
50 void DefaultTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex,
51 sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText )
53 m_rTargetDevice.DrawText( _rStartPoint, _rText, _nStartIndex, _nLength, _pVector, _pDisplayText );
56 void DefaultTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
57 sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
59 m_rTargetDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
62 sal_Int32 DefaultTextLayout::GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
64 return m_rTargetDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
67 bool DefaultTextLayout::DecomposeTextRectAction() const
69 return false;
72 class ReferenceDeviceTextLayout : public ITextLayout
74 public:
75 ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice );
76 virtual ~ReferenceDeviceTextLayout();
78 // ITextLayout
79 virtual long GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen ) const override;
80 virtual void DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText ) override;
81 virtual void GetCaretPositions( const OUString& _rText, long* _pCaretXArray, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const override;
82 virtual sal_Int32 GetTextBreak(const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength) const override;
83 virtual bool DecomposeTextRectAction() const override;
85 public:
86 // equivalents to the respective OutputDevice methods, which take the reference device into account
87 tools::Rectangle DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize );
88 tools::Rectangle GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize );
90 private:
91 long GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const;
93 OutputDevice& m_rTargetDevice;
94 OutputDevice& m_rReferenceDevice;
95 Font m_aUnzoomedPointFont;
96 const Fraction m_aZoom;
97 const bool m_bRTLEnabled;
99 tools::Rectangle m_aCompleteTextRect;
102 ReferenceDeviceTextLayout::ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice,
103 OutputDevice& _rReferenceDevice )
104 :m_rTargetDevice( _rTargetDevice )
105 ,m_rReferenceDevice( _rReferenceDevice )
106 ,m_aUnzoomedPointFont( _rControl.GetUnzoomedControlPointFont() )
107 ,m_aZoom( _rControl.GetZoom() )
108 ,m_bRTLEnabled( _rControl.IsRTLEnabled() )
110 m_rTargetDevice.Push( PushFlags::MAPMODE | PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
112 MapMode aTargetMapMode( m_rTargetDevice.GetMapMode() );
113 OSL_ENSURE( aTargetMapMode.GetOrigin() == Point(), "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: uhm, the code below won't work here ..." );
115 // normally, controls simulate "zoom" by "zooming" the font. This is responsible for (part of) the discrepancies
116 // between text in Writer and text in controls in Writer, though both have the same font.
117 // So, if we have a zoom set at the control, then we do not scale the font, but instead modify the map mode
118 // to accommodate for the zoom.
119 aTargetMapMode.SetScaleX( m_aZoom ); // TODO: shouldn't this be "current_scale * zoom"?
120 aTargetMapMode.SetScaleY( m_aZoom );
122 // also, use a higher-resolution map unit than "pixels", which should save us some rounding errors when
123 // translating coordinates between the reference device and the target device.
124 OSL_ENSURE( aTargetMapMode.GetMapUnit() == MapUnit::MapPixel,
125 "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: this class is not expected to work with such target devices!" );
126 // we *could* adjust all the code in this class to handle this case, but at the moment, it's not necessary
127 const MapUnit eTargetMapUnit = m_rReferenceDevice.GetMapMode().GetMapUnit();
128 aTargetMapMode.SetMapUnit( eTargetMapUnit );
129 OSL_ENSURE( aTargetMapMode.GetMapUnit() != MapUnit::MapPixel,
130 "ReferenceDeviceTextLayout::ReferenceDeviceTextLayout: a reference device which has map mode PIXEL?!" );
132 m_rTargetDevice.SetMapMode( aTargetMapMode );
134 // now that the Zoom is part of the map mode, reset the target device's font to the "unzoomed" version
135 Font aDrawFont( m_aUnzoomedPointFont );
136 aDrawFont.SetFontSize( OutputDevice::LogicToLogic( aDrawFont.GetFontSize(), MapUnit::MapPoint, eTargetMapUnit ) );
137 _rTargetDevice.SetFont( aDrawFont );
139 // transfer font to the reference device
140 m_rReferenceDevice.Push( PushFlags::FONT | PushFlags::TEXTLAYOUTMODE );
141 Font aRefFont( m_aUnzoomedPointFont );
142 aRefFont.SetFontSize( OutputDevice::LogicToLogic(
143 aRefFont.GetFontSize(), MapUnit::MapPoint, m_rReferenceDevice.GetMapMode().GetMapUnit() ) );
144 m_rReferenceDevice.SetFont( aRefFont );
147 ReferenceDeviceTextLayout::~ReferenceDeviceTextLayout()
149 m_rReferenceDevice.Pop();
150 m_rTargetDevice.Pop();
153 namespace
155 bool lcl_normalizeLength( const OUString& _rText, const sal_Int32 _nStartIndex, sal_Int32& _io_nLength )
157 sal_Int32 nTextLength = _rText.getLength();
158 if ( _nStartIndex > nTextLength )
159 return false;
160 if ( _nStartIndex + _io_nLength > nTextLength )
161 _io_nLength = nTextLength - _nStartIndex;
162 return true;
166 long ReferenceDeviceTextLayout::GetTextArray( const OUString& _rText, long* _pDXAry, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
168 if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
169 return 0;
171 // retrieve the character widths from the reference device
172 long nTextWidth = m_rReferenceDevice.GetTextArray( _rText, _pDXAry, _nStartIndex, _nLength );
173 #if OSL_DEBUG_LEVEL > 1
174 if ( _pDXAry )
176 OStringBuffer aTrace;
177 aTrace.append( "ReferenceDeviceTextLayout::GetTextArray( " );
178 aTrace.append( OUStringToOString( _rText, RTL_TEXTENCODING_UTF8 ) );
179 aTrace.append( " ): " );
180 aTrace.append( nTextWidth );
181 aTrace.append( " = ( " );
182 for ( sal_Int32 i=0; i<_nLength; )
184 aTrace.append( _pDXAry[i] );
185 if ( ++i < _nLength )
186 aTrace.append( ", " );
188 aTrace.append( ")" );
189 SAL_INFO( "vcl", aTrace.makeStringAndClear() );
191 #endif
192 return nTextWidth;
195 long ReferenceDeviceTextLayout::GetTextWidth( const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
197 return GetTextArray( _rText, nullptr, _nStartIndex, _nLength );
200 void ReferenceDeviceTextLayout::DrawText( const Point& _rStartPoint, const OUString& _rText, sal_Int32 _nStartIndex, sal_Int32 _nLength, MetricVector* _pVector, OUString* _pDisplayText )
202 if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
203 return;
205 if ( _pVector && _pDisplayText )
207 MetricVector aGlyphBounds;
208 m_rReferenceDevice.GetGlyphBoundRects( _rStartPoint, _rText, _nStartIndex, _nLength, aGlyphBounds );
209 ::std::copy(
210 aGlyphBounds.begin(), aGlyphBounds.end(),
211 ::std::insert_iterator< MetricVector > ( *_pVector, _pVector->end() ) );
212 *_pDisplayText += _rText.copy( _nStartIndex, _nLength );
213 return;
216 std::unique_ptr<long[]> pCharWidths(new long[ _nLength ]);
217 long nTextWidth = GetTextArray( _rText, pCharWidths.get(), _nStartIndex, _nLength );
218 m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, pCharWidths.get(), _nStartIndex, _nLength );
219 pCharWidths.reset();
221 m_aCompleteTextRect.Union( tools::Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
224 void ReferenceDeviceTextLayout::GetCaretPositions( const OUString& _rText, long* _pCaretXArray,
225 sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
227 if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
228 return;
230 // retrieve the caret positions from the reference device
231 m_rReferenceDevice.GetCaretPositions( _rText, _pCaretXArray, _nStartIndex, _nLength );
234 sal_Int32 ReferenceDeviceTextLayout::GetTextBreak( const OUString& _rText, long _nMaxTextWidth, sal_Int32 _nStartIndex, sal_Int32 _nLength ) const
236 if ( !lcl_normalizeLength( _rText, _nStartIndex, _nLength ) )
237 return 0;
239 return m_rReferenceDevice.GetTextBreak( _rText, _nMaxTextWidth, _nStartIndex, _nLength );
242 bool ReferenceDeviceTextLayout::DecomposeTextRectAction() const
244 return true;
247 tools::Rectangle ReferenceDeviceTextLayout::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
248 MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
250 if ( _rText.isEmpty() )
251 return tools::Rectangle();
253 // determine text layout mode from the RTL-ness of the control whose text we render
254 ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
255 m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
256 m_rTargetDevice.SetLayoutMode( nTextLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
258 // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
259 // our caller gives us the left border of the draw position, regardless of script type, text layout,
260 // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
261 // but passed pixel coordinates. So, adjust the rect.
262 tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
263 if (i_pDeviceSize)
265 //if i_pDeviceSize is passed in here, it was the original pre logic-to-pixel size of _rRect
266 SAL_WARN_IF(std::abs(_rRect.GetSize().Width() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Width()) > 1, "vcl", "DeviceSize width was expected to match Pixel width");
267 SAL_WARN_IF(std::abs(_rRect.GetSize().Height() - m_rTargetDevice.LogicToPixel(*i_pDeviceSize).Height()) > 1, "vcl", "DeviceSize height was expected to match Pixel height");
268 aRect.SetSize(*i_pDeviceSize);
271 m_aCompleteTextRect.SetEmpty();
272 m_rTargetDevice.DrawText( aRect, _rText, _nStyle, _pVector, _pDisplayText, this );
273 tools::Rectangle aTextRect = m_aCompleteTextRect;
275 if ( aTextRect.IsEmpty() && !aRect.IsEmpty() )
277 // this happens for instance if we're in a PaintToDevice call, where only a MetaFile is recorded,
278 // but no actual painting happens, so our "DrawText( Point, ... )" is never called
279 // In this case, calculate the rect from what OutputDevice::GetTextRect would give us. This has
280 // the disadvantage of less accuracy, compared with the approach to calculate the rect from the
281 // single "DrawText( Point, ... )" calls, since more intermediate arithmetic will translate
282 // from ref- to target-units.
283 aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
286 // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
287 // expects pixel coordinates
288 aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
290 // convert the metric vector
291 if ( _pVector )
293 for ( MetricVector::iterator charRect = _pVector->begin();
294 charRect != _pVector->end();
295 ++charRect
298 *charRect = m_rTargetDevice.LogicToPixel( *charRect );
302 return aTextRect;
305 tools::Rectangle ReferenceDeviceTextLayout::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize )
307 if ( _rText.isEmpty() )
308 return tools::Rectangle();
310 // determine text layout mode from the RTL-ness of the control whose text we render
311 ComplexTextLayoutFlags nTextLayoutMode = m_bRTLEnabled ? ComplexTextLayoutFlags::BiDiRtl : ComplexTextLayoutFlags::Default;
312 m_rReferenceDevice.SetLayoutMode( nTextLayoutMode );
313 m_rTargetDevice.SetLayoutMode( nTextLayoutMode | ComplexTextLayoutFlags::TextOriginLeft );
315 // ComplexTextLayoutFlags::TextOriginLeft is because when we do actually draw the text (in DrawText( Point, ... )), then
316 // our caller gives us the left border of the draw position, regardless of script type, text layout,
317 // and the like in our ctor, we set the map mode of the target device from pixel to twip, but our caller doesn't know this,
318 // but passed pixel coordinates. So, adjust the rect.
319 tools::Rectangle aRect( m_rTargetDevice.PixelToLogic( _rRect ) );
321 tools::Rectangle aTextRect = m_rTargetDevice.GetTextRect( aRect, _rText, _nStyle, nullptr, this );
323 //if o_pDeviceSize is available, stash the pre logic-to-pixel size in it
324 if (o_pDeviceSize)
326 *o_pDeviceSize = aTextRect.GetSize();
329 // similar to above, the text rect now contains TWIPs (or whatever unit the ref device has), but the caller
330 // expects pixel coordinates
331 aTextRect = m_rTargetDevice.LogicToPixel( aTextRect );
333 return aTextRect;
336 ControlTextRenderer::ControlTextRenderer( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice )
337 :m_pImpl( new ReferenceDeviceTextLayout( _rControl, _rTargetDevice, _rReferenceDevice ) )
341 ControlTextRenderer::~ControlTextRenderer()
345 tools::Rectangle ControlTextRenderer::DrawText( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle,
346 MetricVector* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize )
348 return m_pImpl->DrawText( _rRect, _rText, _nStyle, _pVector, _pDisplayText, i_pDeviceSize );
351 tools::Rectangle ControlTextRenderer::GetTextRect( const tools::Rectangle& _rRect, const OUString& _rText, DrawTextFlags _nStyle, Size* o_pDeviceSize = nullptr )
353 return m_pImpl->GetTextRect( _rRect, _rText, _nStyle, o_pDeviceSize );
356 } // namespace vcl
358 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */