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 <canvas/canvastools.hxx>
29 #include <tools/diagnose_ex.h>
30 
31 #include <vcl/virdev.hxx>
32 #include <vcl/metric.hxx>
33 #include <vcl/canvastools.hxx>
34 
35 #include <basegfx/polygon/b2dpolypolygon.hxx>
36 #include <basegfx/tools/canvastools.hxx>
37 
38 #include "cairo_canvasfont.hxx"
39 #include "cairo_textlayout.hxx"
40 #include "cairo_canvashelper.hxx"
41 
42 using namespace ::cairo;
43 using namespace ::com::sun::star;
44 
45 namespace cairocanvas
46 {
47 	enum ColorType
48 	{
49 		LINE_COLOR, FILL_COLOR, TEXT_COLOR, IGNORE_COLOR
50 	};
51 
createFont(const rendering::XCanvas *,const rendering::FontRequest & fontRequest,const uno::Sequence<beans::PropertyValue> & extraFontProperties,const geometry::Matrix2D & fontMatrix)52     uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* 					,
53                                                                        const rendering::FontRequest& 				fontRequest,
54                                                                        const uno::Sequence< beans::PropertyValue >& extraFontProperties,
55                                                                        const geometry::Matrix2D& 					fontMatrix )
56     {
57         return uno::Reference< rendering::XCanvasFont >( new CanvasFont( fontRequest, extraFontProperties, fontMatrix, mpSurfaceProvider ));
58     }
59 
queryAvailableFonts(const rendering::XCanvas *,const rendering::FontInfo &,const uno::Sequence<beans::PropertyValue> &)60     uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* 						,
61                                                                             const rendering::FontInfo& 						/*aFilter*/,
62                                                                             const uno::Sequence< beans::PropertyValue >& 	/*aFontProperties*/ )
63     {
64         // TODO
65         return uno::Sequence< rendering::FontInfo >();
66     }
67 
68 	static bool
setupFontTransform(::OutputDevice & rOutDev,::Point & o_rPoint,::Font & io_rVCLFont,const rendering::ViewState & rViewState,const rendering::RenderState & rRenderState)69 	setupFontTransform( ::OutputDevice&				    rOutDev,
70 						::Point&						o_rPoint,
71 						::Font& 						io_rVCLFont,
72 						const rendering::ViewState& 	rViewState,
73 						const rendering::RenderState& 	rRenderState )
74 	{
75 		::basegfx::B2DHomMatrix aMatrix;
76 
77 		::canvas::tools::mergeViewAndRenderTransform(aMatrix,
78 													 rViewState,
79 													 rRenderState);
80 
81 		::basegfx::B2DTuple aScale;
82 		::basegfx::B2DTuple aTranslate;
83 		double nRotate, nShearX;
84 
85 		aMatrix.decompose( aScale, aTranslate, nRotate, nShearX );
86 
87 		// query font metric _before_ tampering with width and height
88 		if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) )
89         {
90 			// retrieve true font width
91 			const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetWidth() );
92 
93 			const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) );
94 
95 			if( !nScaledFontWidth )
96 			{
97 				// scale is smaller than one pixel - disable text
98 				// output altogether
99 				return false;
100 			}
101 
102 			io_rVCLFont.SetWidth( nScaledFontWidth );
103 		}
104 
105 		if( !::rtl::math::approxEqual(aScale.getY(), 1.0) )
106         {
107 			const sal_Int32 nFontHeight( io_rVCLFont.GetHeight() );
108 			io_rVCLFont.SetHeight( ::basegfx::fround(nFontHeight * aScale.getY()) );
109 		}
110 
111 		io_rVCLFont.SetOrientation( static_cast< short >( ::basegfx::fround(-fmod(nRotate, 2*M_PI)*(1800.0/M_PI)) ) );
112 
113 		// TODO(F2): Missing functionality in VCL: shearing
114 		o_rPoint.X() = ::basegfx::fround(aTranslate.getX());
115 		o_rPoint.Y() = ::basegfx::fround(aTranslate.getY());
116 
117 		return true;
118 	}
119 
120     static int
setupOutDevState(OutputDevice & rOutDev,const rendering::XCanvas * pOwner,const rendering::ViewState & viewState,const rendering::RenderState & renderState,ColorType eColorType)121 	setupOutDevState( OutputDevice&                 rOutDev,
122                       const rendering::XCanvas*     pOwner,
123                       const rendering::ViewState& 	viewState,
124                       const rendering::RenderState& renderState,
125                       ColorType						eColorType )
126     {
127         ::canvas::tools::verifyInput( renderState,
128                                       BOOST_CURRENT_FUNCTION,
129                                       const_cast<rendering::XCanvas*>(pOwner), // only for refcount
130                                       2,
131                                       eColorType == IGNORE_COLOR ? 0 : 3 );
132 
133         int nTransparency(0);
134 
135         // TODO(P2): Don't change clipping all the time, maintain current clip
136         // state and change only when update is necessary
137 
138         // accumulate non-empty clips into one region
139         // ==========================================
140 
141         Region aClipRegion;
142 
143         if( viewState.Clip.is() )
144         {
145             ::basegfx::B2DPolyPolygon aClipPoly(
146                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(
147                     viewState.Clip) );
148 
149             if( aClipPoly.count() )
150             {
151                 // setup non-empty clipping
152                 ::basegfx::B2DHomMatrix aMatrix;
153                 aClipPoly.transform(
154                     ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix,
155                                                                     viewState.AffineTransform ) );
156 
157                 aClipRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );
158             }
159         }
160 
161         if( renderState.Clip.is() )
162         {
163             ::basegfx::B2DPolyPolygon aClipPoly(
164                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(
165                     renderState.Clip) );
166 
167             ::basegfx::B2DHomMatrix aMatrix;
168             aClipPoly.transform(
169                 ::canvas::tools::mergeViewAndRenderTransform( aMatrix,
170                                                               viewState,
171                                                               renderState ) );
172 
173             if( aClipPoly.count() )
174             {
175                 // setup non-empty clipping
176                 Region aRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );
177 
178                 if( aClipRegion.IsEmpty() )
179                     aClipRegion = aRegion;
180                 else
181                     aClipRegion.Intersect( aRegion );
182             }
183             else
184             {
185                 // clip polygon is empty
186                 aClipRegion.SetEmpty();
187             }
188         }
189 
190         // setup accumulated clip region. Note that setting an
191         // empty clip region denotes "clip everything" on the
192         // OutputDevice (which is why we translate that into
193         // SetClipRegion() here). When both view and render clip
194         // are empty, aClipRegion remains default-constructed,
195         // i.e. empty, too.
196         if( aClipRegion.IsEmpty() )
197         {
198             rOutDev.SetClipRegion();
199         }
200         else
201         {
202             rOutDev.SetClipRegion( aClipRegion );
203         }
204 
205         if( eColorType != IGNORE_COLOR )
206         {
207             Color aColor( COL_WHITE );
208 
209             if( renderState.DeviceColor.getLength() > 2 )
210             {
211                 aColor = ::vcl::unotools::stdColorSpaceSequenceToColor( renderState.DeviceColor );
212             }
213 
214             // extract alpha, and make color opaque
215             // afterwards. Otherwise, OutputDevice won't draw anything
216             nTransparency = aColor.GetTransparency();
217             aColor.SetTransparency(0);
218 
219             switch( eColorType )
220             {
221                 case LINE_COLOR:
222                     rOutDev.SetLineColor( aColor );
223                     rOutDev.SetFillColor();
224 
225                     break;
226 
227                 case FILL_COLOR:
228                     rOutDev.SetFillColor( aColor );
229                     rOutDev.SetLineColor();
230 
231                     break;
232 
233                 case TEXT_COLOR:
234                     rOutDev.SetTextColor( aColor );
235 
236                     break;
237 
238                 default:
239                     ENSURE_OR_THROW( false,
240                                       "CanvasHelper::setupOutDevState(): Unexpected color type");
241                     break;
242             }
243         }
244 
245         return nTransparency;
246     }
247 
setupTextOutput(OutputDevice & rOutDev,const rendering::XCanvas * pOwner,::Point & o_rOutPos,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const uno::Reference<rendering::XCanvasFont> & xFont)248     bool setupTextOutput( OutputDevice&                                     rOutDev,
249                           const rendering::XCanvas*                         pOwner,
250 						  ::Point&										    o_rOutPos,
251 						  const rendering::ViewState& 					    viewState,
252 						  const rendering::RenderState& 					renderState,
253 						  const uno::Reference< rendering::XCanvasFont >&	xFont	)
254     {
255         setupOutDevState( rOutDev, pOwner, viewState, renderState, TEXT_COLOR );
256 
257         ::Font aVCLFont;
258 
259         CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() );
260 
261         ENSURE_ARG_OR_THROW( pFont,
262                          "CanvasHelper::setupTextOutput(): Font not compatible with this canvas" );
263 
264         aVCLFont = pFont->getVCLFont();
265 
266         Color aColor( COL_BLACK );
267 
268         if( renderState.DeviceColor.getLength() > 2 )
269         {
270             aColor = ::vcl::unotools::stdColorSpaceSequenceToColor(renderState.DeviceColor );
271         }
272 
273         // setup font color
274         aVCLFont.SetColor( aColor );
275         aVCLFont.SetFillColor( aColor );
276 
277         // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here.
278         if( !setupFontTransform( rOutDev, o_rOutPos, aVCLFont, viewState, renderState ) )
279             return false;
280 
281         rOutDev.SetFont( aVCLFont );
282 
283 
284         return true;
285     }
286 
drawText(const rendering::XCanvas * pOwner,const rendering::StringContext & text,const uno::Reference<rendering::XCanvasFont> & xFont,const rendering::ViewState & viewState,const rendering::RenderState & renderState,sal_Int8 textDirection)287     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* 						pOwner,
288                                                                           const rendering::StringContext& 					text,
289                                                                           const uno::Reference< rendering::XCanvasFont >& 	xFont,
290                                                                           const rendering::ViewState& 						viewState,
291                                                                           const rendering::RenderState& 					renderState,
292                                                                           sal_Int8				 							textDirection )
293     {
294 #ifdef CAIRO_CANVAS_PERF_TRACE
295 		struct timespec aTimer;
296 		mxDevice->startPerfTrace( &aTimer );
297 #endif
298 
299         ENSURE_ARG_OR_THROW( xFont.is(),
300                          "CanvasHelper::drawText(): font is NULL");
301 
302 		if( !mpVirtualDevice )
303 			mpVirtualDevice = mpSurface->createVirtualDevice();
304 
305         if( mpVirtualDevice )
306 		{
307 #if defined CAIRO_HAS_WIN32_SURFACE
308             // FIXME: Some kind of work-araound...
309             cairo_rectangle (mpSurface->getCairo().get(), 0, 0, 0, 0);
310             cairo_fill(mpSurface->getCairo().get());
311 #endif
312             ::Point aOutpos;
313 			if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xFont ) )
314 				return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary
315 
316 				// change text direction and layout mode
317 			sal_uLong nLayoutMode(0);
318 			switch( textDirection )
319 				{
320 				case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
321 					nLayoutMode |= TEXT_LAYOUT_BIDI_LTR;
322 					// FALLTHROUGH intended
323 				case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
324 					nLayoutMode |= TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG;
325 					nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_LEFT;
326 					break;
327 
328 				case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
329 					nLayoutMode |= TEXT_LAYOUT_BIDI_RTL;
330 					// FALLTHROUGH intended
331 				case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
332 					nLayoutMode |= TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG;
333 					nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_RIGHT;
334 					break;
335 				}
336 
337 			// TODO(F2): alpha
338 			mpVirtualDevice->SetLayoutMode( nLayoutMode );
339 
340             OSL_TRACE(":cairocanvas::CanvasHelper::drawText(O,t,f,v,r,d): %s", ::rtl::OUStringToOString( text.Text.copy( text.StartPosition, text.Length ),
341                                                                                                          RTL_TEXTENCODING_UTF8 ).getStr());
342 
343             TextLayout* pTextLayout = new TextLayout(text, textDirection, 0, CanvasFont::Reference(dynamic_cast< CanvasFont* >( xFont.get() )), mpSurfaceProvider);
344             pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState );
345 		}
346 
347         return uno::Reference< rendering::XCachedPrimitive >(NULL);
348     }
349 
drawTextLayout(const rendering::XCanvas * pOwner,const uno::Reference<rendering::XTextLayout> & xLayoutedText,const rendering::ViewState & viewState,const rendering::RenderState & renderState)350     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* 						pOwner,
351                                                                                 const uno::Reference< rendering::XTextLayout >& xLayoutedText,
352                                                                                 const rendering::ViewState& 					viewState,
353                                                                                 const rendering::RenderState& 					renderState )
354     {
355         ENSURE_ARG_OR_THROW( xLayoutedText.is(),
356                          "CanvasHelper::drawTextLayout(): layout is NULL");
357 
358         TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() );
359 
360         if( pTextLayout )
361         {
362 			if( !mpVirtualDevice )
363 				mpVirtualDevice = mpSurface->createVirtualDevice();
364 
365             if( mpVirtualDevice )
366             {
367 #if defined CAIRO_HAS_WIN32_SURFACE
368                 // FIXME: Some kind of work-araound...
369                 cairo_rectangle( mpSurface->getCairo().get(), 0, 0, 0, 0);
370                 cairo_fill(mpSurface->getCairo().get());
371 #endif
372                 // TODO(T3): Race condition. We're taking the font
373                 // from xLayoutedText, and then calling draw() at it,
374                 // without exclusive access. Move setupTextOutput(),
375                 // e.g. to impltools?
376 
377                 ::Point aOutpos;
378                 if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xLayoutedText->getFont() ) )
379                     return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary
380 
381                 // TODO(F2): What about the offset scalings?
382                 pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState );
383             }
384         }
385         else
386         {
387             ENSURE_ARG_OR_THROW( false,
388                              "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" );
389         }
390 
391         return uno::Reference< rendering::XCachedPrimitive >(NULL);
392     }
393 
394 }
395