Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include <config_graphite.h>
21 :
22 : #include <vector>
23 : #include <queue>
24 : #include <set>
25 :
26 : #include <prex.h>
27 : #include <X11/Xproto.h>
28 : #include <postx.h>
29 :
30 : #include "tools/debug.hxx"
31 :
32 : #include "basegfx/polygon/b2dpolygon.hxx"
33 : #include "basegfx/polygon/b2dpolypolygon.hxx"
34 : #include "basegfx/polygon/b2dpolypolygontools.hxx"
35 : #include "basegfx/polygon/b2dpolygontools.hxx"
36 : #include "basegfx/polygon/b2dpolygonclipper.hxx"
37 : #include "basegfx/polygon/b2dlinegeometry.hxx"
38 : #include "basegfx/matrix/b2dhommatrix.hxx"
39 : #include "basegfx/matrix/b2dhommatrixtools.hxx"
40 : #include "basegfx/polygon/b2dpolypolygoncutter.hxx"
41 : #include "basegfx/polygon/b2dtrapezoid.hxx"
42 :
43 : #include <vcl/jobdata.hxx>
44 : #include <vcl/virdev.hxx>
45 :
46 : #include "unx/salunx.h"
47 : #include "unx/saldata.hxx"
48 : #include "unx/saldisp.hxx"
49 : #include "unx/salgdi.h"
50 : #include "unx/salframe.h"
51 : #include "unx/salvd.h"
52 : #include "unx/x11/x11gdiimpl.h"
53 : #include <unx/x11/xlimits.hxx>
54 :
55 : #include "salgdiimpl.hxx"
56 : #include "unx/x11windowprovider.hxx"
57 : #include "textrender.hxx"
58 : #include "gdiimpl.hxx"
59 : #include "opengl/x11/gdiimpl.hxx"
60 : #include "x11cairotextrender.hxx"
61 : #include "openglx11cairotextrender.hxx"
62 :
63 : #include "generic/printergfx.hxx"
64 : #include "xrender_peer.hxx"
65 : #include "cairo_cairo.hxx"
66 : #include "cairo_xlib_cairo.hxx"
67 :
68 : #include <vcl/opengl/OpenGLHelper.hxx>
69 :
70 208 : X11SalGraphics::X11SalGraphics():
71 : m_pFrame(NULL),
72 : m_pVDev(NULL),
73 : m_pColormap(NULL),
74 : m_pDeleteColormap(NULL),
75 : hDrawable_(None),
76 : m_nXScreen( 0 ),
77 : m_pXRenderFormat(NULL),
78 : m_aXRenderPicture(0),
79 : pPaintRegion_(NULL),
80 : mpClipRegion(NULL),
81 : pFontGC_(NULL),
82 : nTextPixel_(0),
83 : hBrush_(None),
84 : bWindow_(false),
85 : bPrinter_(false),
86 : bVirDev_(false),
87 208 : bFontGC_(false)
88 : {
89 208 : if (OpenGLHelper::isVCLOpenGLEnabled())
90 : {
91 0 : mxImpl.reset(new X11OpenGLSalGraphicsImpl(*this));
92 0 : mxTextRenderImpl.reset((new OpenGLX11CairoTextRender(*this)));
93 : }
94 : else
95 : {
96 208 : mxTextRenderImpl.reset((new X11CairoTextRender(*this)));
97 208 : mxImpl.reset(new X11SalGraphicsImpl(*this));
98 : }
99 :
100 208 : }
101 :
102 392 : X11SalGraphics::~X11SalGraphics()
103 : {
104 196 : ReleaseFonts();
105 196 : freeResources();
106 196 : }
107 :
108 196 : void X11SalGraphics::freeResources()
109 : {
110 196 : Display *pDisplay = GetXDisplay();
111 :
112 : DBG_ASSERT( !pPaintRegion_, "pPaintRegion_" );
113 196 : if( mpClipRegion ) XDestroyRegion( mpClipRegion ), mpClipRegion = None;
114 :
115 196 : mxImpl->freeResources();
116 :
117 196 : if( hBrush_ ) XFreePixmap( pDisplay, hBrush_ ), hBrush_ = None;
118 196 : if( pFontGC_ ) XFreeGC( pDisplay, pFontGC_ ), pFontGC_ = None;
119 196 : if( m_pDeleteColormap )
120 9 : delete m_pDeleteColormap, m_pColormap = m_pDeleteColormap = NULL;
121 :
122 196 : if( m_aXRenderPicture )
123 0 : XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture ), m_aXRenderPicture = 0;
124 :
125 196 : bFontGC_ = false;
126 196 : }
127 :
128 3 : SalGraphicsImpl* X11SalGraphics::GetImpl() const
129 : {
130 3 : return mxImpl.get();
131 : }
132 :
133 413 : void X11SalGraphics::SetDrawable( Drawable aDrawable, SalX11Screen nXScreen )
134 : {
135 : // shortcut if nothing changed
136 413 : if( hDrawable_ == aDrawable )
137 413 : return;
138 :
139 : // free screen specific resources if needed
140 413 : if( nXScreen != m_nXScreen )
141 : {
142 0 : freeResources();
143 0 : m_pColormap = &vcl_sal::getSalDisplay(GetGenericData())->GetColormap( nXScreen );
144 0 : m_nXScreen = nXScreen;
145 : }
146 :
147 413 : hDrawable_ = aDrawable;
148 413 : SetXRenderFormat( NULL );
149 413 : if( m_aXRenderPicture )
150 : {
151 0 : XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture );
152 0 : m_aXRenderPicture = 0;
153 : }
154 :
155 : // TODO: moggi: FIXME nTextPixel_ = GetPixel( nTextColor_ );
156 : }
157 :
158 208 : void X11SalGraphics::Init( SalFrame *pFrame, Drawable aTarget,
159 : SalX11Screen nXScreen )
160 : {
161 208 : m_pColormap = &vcl_sal::getSalDisplay(GetGenericData())->GetColormap(nXScreen);
162 208 : m_nXScreen = nXScreen;
163 :
164 208 : m_pFrame = pFrame;
165 208 : m_pVDev = NULL;
166 :
167 208 : bWindow_ = true;
168 208 : bVirDev_ = false;
169 :
170 208 : SetDrawable( aTarget, nXScreen );
171 208 : mxImpl->Init();
172 208 : }
173 :
174 0 : void X11SalGraphics::DeInit()
175 : {
176 0 : SetDrawable( None, m_nXScreen );
177 0 : }
178 :
179 181 : void X11SalGraphics::SetClipRegion( GC pGC, Region pXReg ) const
180 : {
181 181 : Display *pDisplay = GetXDisplay();
182 :
183 181 : int n = 0;
184 : Region Regions[3];
185 :
186 181 : if( mpClipRegion )
187 4 : Regions[n++] = mpClipRegion;
188 :
189 181 : if( pXReg && !XEmptyRegion( pXReg ) )
190 0 : Regions[n++] = pXReg;
191 :
192 181 : if( 0 == n )
193 177 : XSetClipMask( pDisplay, pGC, None );
194 4 : else if( 1 == n )
195 4 : XSetRegion( pDisplay, pGC, Regions[0] );
196 : else
197 : {
198 0 : Region pTmpRegion = XCreateRegion();
199 0 : XIntersectRegion( Regions[0], Regions[1], pTmpRegion );
200 :
201 0 : XSetRegion( pDisplay, pGC, pTmpRegion );
202 0 : XDestroyRegion( pTmpRegion );
203 : }
204 181 : }
205 :
206 : // Calculate a dither-pixmap and make a brush of it
207 : #define P_DELTA 51
208 : #define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA))
209 :
210 0 : bool X11SalGraphics::GetDitherPixmap( SalColor nSalColor )
211 : {
212 : static const short nOrdDither8Bit[ 8 ][ 8 ] =
213 : {
214 : { 0, 38, 9, 48, 2, 40, 12, 50},
215 : {25, 12, 35, 22, 28, 15, 37, 24},
216 : { 6, 44, 3, 41, 8, 47, 5, 44},
217 : {32, 19, 28, 16, 34, 21, 31, 18},
218 : { 1, 40, 11, 49, 0, 39, 10, 48},
219 : {27, 14, 36, 24, 26, 13, 36, 23},
220 : { 8, 46, 4, 43, 7, 45, 4, 42},
221 : {33, 20, 30, 17, 32, 20, 29, 16}
222 : };
223 :
224 : // test for correct depth (8bit)
225 0 : if( GetColormap().GetVisual().GetDepth() != 8 )
226 0 : return false;
227 :
228 : char pBits[64];
229 0 : char *pBitsPtr = pBits;
230 :
231 : // Set the pallette-entries for the dithering tile
232 0 : sal_uInt8 nSalColorRed = SALCOLOR_RED ( nSalColor );
233 0 : sal_uInt8 nSalColorGreen = SALCOLOR_GREEN ( nSalColor );
234 0 : sal_uInt8 nSalColorBlue = SALCOLOR_BLUE ( nSalColor );
235 :
236 0 : for( int nY = 0; nY < 8; nY++ )
237 : {
238 0 : for( int nX = 0; nX < 8; nX++ )
239 : {
240 0 : short nMagic = nOrdDither8Bit[nY][nX];
241 0 : sal_uInt8 nR = P_DELTA * DMAP( nSalColorRed, nMagic );
242 0 : sal_uInt8 nG = P_DELTA * DMAP( nSalColorGreen, nMagic );
243 0 : sal_uInt8 nB = P_DELTA * DMAP( nSalColorBlue, nMagic );
244 :
245 0 : *pBitsPtr++ = GetColormap().GetPixel( MAKE_SALCOLOR( nR, nG, nB ) );
246 : }
247 : }
248 :
249 : // create the tile as ximage and an according pixmap -> caching
250 : XImage *pImage = XCreateImage( GetXDisplay(),
251 0 : GetColormap().GetXVisual(),
252 : 8,
253 : ZPixmap,
254 : 0, // offset
255 : pBits, // data
256 : 8, 8, // width & height
257 : 8, // bitmap_pad
258 0 : 0 ); // (default) bytes_per_line
259 :
260 0 : if( !hBrush_ )
261 0 : hBrush_ = limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 );
262 :
263 : // put the ximage to the pixmap
264 : XPutImage( GetXDisplay(),
265 : hBrush_,
266 : GetDisplay()->GetCopyGC( m_nXScreen ),
267 : pImage,
268 : 0, 0, // Source
269 : 0, 0, // Destination
270 0 : 8, 8 ); // width & height
271 :
272 : // destroy image-frame but not palette-data
273 0 : pImage->data = NULL;
274 0 : XDestroyImage( pImage );
275 :
276 0 : return true;
277 : }
278 :
279 16 : void X11SalGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) // const
280 : {
281 16 : const SalDisplay *pDisplay = GetDisplay();
282 16 : if (!pDisplay)
283 : {
284 : OSL_TRACE("Null display");
285 0 : rDPIX = rDPIY = 96;
286 16 : return;
287 : }
288 :
289 16 : Pair dpi = pDisplay->GetResolution();
290 16 : rDPIX = dpi.A();
291 16 : rDPIY = dpi.B();
292 :
293 16 : if ( rDPIY > 200 )
294 : {
295 0 : rDPIX = Divide( rDPIX * 200, rDPIY );
296 0 : rDPIY = 200;
297 : }
298 :
299 : // #i12705# equalize x- and y-resolution if they are close enough
300 16 : if( rDPIX != rDPIY )
301 : {
302 : // different x- and y- resolutions are usually artifacts of
303 : // a wrongly calculated screen size.
304 : #ifdef DEBUG
305 : printf("Forcing Resolution from %" SAL_PRIdINT32 "x%" SAL_PRIdINT32 " to %" SAL_PRIdINT32 "x%" SAL_PRIdINT32 "\n",
306 : rDPIX,rDPIY,rDPIY,rDPIY);
307 : #endif
308 16 : rDPIX = rDPIY; // y-resolution is more trustworthy
309 : }
310 : }
311 :
312 343 : sal_uInt16 X11SalGraphics::GetBitCount() const
313 : {
314 343 : return mxImpl->GetBitCount();
315 : }
316 :
317 0 : long X11SalGraphics::GetGraphicsWidth() const
318 : {
319 0 : return mxImpl->GetGraphicsWidth();
320 : }
321 :
322 0 : void X11SalGraphics::ResetClipRegion()
323 : {
324 0 : mxImpl->ResetClipRegion();
325 0 : }
326 :
327 3 : bool X11SalGraphics::setClipRegion( const vcl::Region& i_rClip )
328 : {
329 3 : return mxImpl->setClipRegion( i_rClip );
330 : }
331 :
332 182 : void X11SalGraphics::SetLineColor()
333 : {
334 182 : mxImpl->SetLineColor();
335 182 : }
336 :
337 0 : void X11SalGraphics::SetLineColor( SalColor nSalColor )
338 : {
339 0 : mxImpl->SetLineColor( nSalColor );
340 0 : }
341 :
342 0 : void X11SalGraphics::SetFillColor()
343 : {
344 0 : mxImpl->SetFillColor();
345 0 : }
346 :
347 177 : void X11SalGraphics::SetFillColor( SalColor nSalColor )
348 : {
349 177 : mxImpl->SetFillColor( nSalColor );
350 177 : }
351 :
352 0 : void X11SalGraphics::SetROPLineColor( SalROPColor nROPColor )
353 : {
354 0 : mxImpl->SetROPLineColor( nROPColor );
355 0 : }
356 :
357 0 : void X11SalGraphics::SetROPFillColor( SalROPColor nROPColor )
358 : {
359 0 : mxImpl->SetROPFillColor( nROPColor );
360 0 : }
361 :
362 1002 : void X11SalGraphics::SetXORMode( bool bSet, bool bInvertOnly )
363 : {
364 1002 : mxImpl->SetXORMode( bSet, bInvertOnly );
365 1002 : }
366 :
367 0 : void X11SalGraphics::drawPixel( long nX, long nY )
368 : {
369 0 : mxImpl->drawPixel( nX, nY );
370 0 : }
371 :
372 0 : void X11SalGraphics::drawPixel( long nX, long nY, SalColor nSalColor )
373 : {
374 0 : mxImpl->drawPixel( nX, nY, nSalColor );
375 0 : }
376 :
377 0 : void X11SalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
378 : {
379 0 : mxImpl->drawLine( nX1, nY1, nX2, nY2 );
380 0 : }
381 :
382 180 : void X11SalGraphics::drawRect( long nX, long nY, long nDX, long nDY )
383 : {
384 180 : mxImpl->drawRect( nX, nY, nDX, nDY );
385 180 : }
386 :
387 0 : void X11SalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry )
388 : {
389 0 : mxImpl->drawPolyLine( nPoints, pPtAry );
390 0 : }
391 :
392 2 : void X11SalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
393 : {
394 2 : mxImpl->drawPolygon( nPoints, pPtAry );
395 2 : }
396 :
397 0 : void X11SalGraphics::drawPolyPolygon( sal_uInt32 nPoly,
398 : const sal_uInt32 *pPoints,
399 : PCONSTSALPOINT *pPtAry )
400 : {
401 0 : mxImpl->drawPolyPolygon( nPoly, pPoints, pPtAry );
402 0 : }
403 :
404 0 : bool X11SalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry )
405 : {
406 0 : return mxImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry );
407 : }
408 :
409 0 : bool X11SalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry )
410 : {
411 0 : return mxImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry );
412 : }
413 :
414 0 : bool X11SalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoints, const sal_uInt32* pPoints,
415 : const SalPoint* const* pPtAry, const sal_uInt8* const* pFlgAry)
416 : {
417 0 : return mxImpl->drawPolyPolygonBezier( nPoints, pPoints, pPtAry, pFlgAry );
418 : }
419 :
420 0 : void X11SalGraphics::invert( sal_uInt32 nPoints,
421 : const SalPoint* pPtAry,
422 : SalInvert nFlags )
423 : {
424 0 : mxImpl->invert( nPoints, pPtAry, nFlags );
425 0 : }
426 :
427 0 : bool X11SalGraphics::drawEPS( long nX, long nY, long nWidth,
428 : long nHeight, void* pPtr, sal_uLong nSize )
429 : {
430 0 : return mxImpl->drawEPS( nX, nY, nWidth, nHeight, pPtr, nSize );
431 : }
432 :
433 3 : XRenderPictFormat* X11SalGraphics::GetXRenderFormat() const
434 : {
435 3 : if( m_pXRenderFormat == NULL )
436 1 : m_pXRenderFormat = XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual );
437 3 : return m_pXRenderFormat;
438 : }
439 :
440 2 : SystemGraphicsData X11SalGraphics::GetGraphicsData() const
441 : {
442 2 : SystemGraphicsData aRes;
443 :
444 2 : aRes.nSize = sizeof(aRes);
445 2 : aRes.pDisplay = GetXDisplay();
446 2 : aRes.hDrawable = hDrawable_;
447 2 : aRes.pVisual = GetVisual().visual;
448 2 : aRes.nScreen = m_nXScreen.getXScreen();
449 2 : aRes.nDepth = GetBitCount();
450 2 : aRes.aColormap = GetColormap().GetXColormap();
451 2 : aRes.pXRenderFormat = m_pXRenderFormat;
452 2 : return aRes;
453 : }
454 :
455 2 : bool X11SalGraphics::SupportsCairo() const
456 : {
457 2 : Display *pDisplay = GetXDisplay();
458 : int nDummy;
459 2 : return XQueryExtension(pDisplay, "RENDER", &nDummy, &nDummy, &nDummy);
460 : }
461 :
462 2 : cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
463 : {
464 2 : return cairo::SurfaceSharedPtr(new cairo::X11Surface(rSurface));
465 : }
466 :
467 : namespace
468 : {
469 2 : static cairo::X11SysData getSysData( const vcl::Window& rWindow )
470 : {
471 2 : const SystemEnvData* pSysData = cairo::GetSysData(&rWindow);
472 :
473 2 : if( !pSysData )
474 0 : return cairo::X11SysData();
475 : else
476 2 : return cairo::X11SysData(*pSysData);
477 : }
478 :
479 0 : static cairo::X11SysData getSysData( const VirtualDevice& rVirDev )
480 : {
481 0 : return cairo::X11SysData( rVirDev.GetSystemGfxData() );
482 : }
483 : }
484 :
485 2 : cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface( const OutputDevice& rRefDevice,
486 : int x, int y, int width, int height ) const
487 : {
488 2 : if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
489 : return cairo::SurfaceSharedPtr(new cairo::X11Surface(getSysData(static_cast<const vcl::Window&>(rRefDevice)),
490 2 : x,y,width,height));
491 0 : if( rRefDevice.GetOutDevType() == OUTDEV_VIRDEV )
492 : return cairo::SurfaceSharedPtr(new cairo::X11Surface(getSysData(static_cast<const VirtualDevice&>(rRefDevice)),
493 0 : x,y,width,height));
494 0 : return cairo::SurfaceSharedPtr();
495 : }
496 :
497 0 : cairo::SurfaceSharedPtr X11SalGraphics::CreateBitmapSurface( const OutputDevice& rRefDevice,
498 : const BitmapSystemData& rData,
499 : const Size& rSize ) const
500 : {
501 : SAL_INFO(
502 : "canvas.cairo",
503 : "requested size: " << rSize.Width() << " x " << rSize.Height()
504 : << " available size: " << rData.mnWidth << " x "
505 : << rData.mnHeight);
506 0 : if ( rData.mnWidth == rSize.Width() && rData.mnHeight == rSize.Height() )
507 : {
508 0 : if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
509 0 : return cairo::SurfaceSharedPtr(new cairo::X11Surface(getSysData(static_cast<const vcl::Window&>(rRefDevice)), rData ));
510 0 : else if( rRefDevice.GetOutDevType() == OUTDEV_VIRDEV )
511 0 : return cairo::SurfaceSharedPtr(new cairo::X11Surface(getSysData(static_cast<const VirtualDevice&>(rRefDevice)), rData ));
512 : }
513 :
514 0 : return cairo::SurfaceSharedPtr();
515 : }
516 :
517 0 : css::uno::Any X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const ::basegfx::B2ISize& /*rSize*/) const
518 : {
519 0 : cairo::X11Surface& rXlibSurface=dynamic_cast<cairo::X11Surface&>(*rSurface.get());
520 0 : css::uno::Sequence< css::uno::Any > args( 3 );
521 0 : args[0] = css::uno::Any( false ); // do not call XFreePixmap on it
522 0 : args[1] = css::uno::Any( rXlibSurface.getPixmap()->mhDrawable );
523 0 : args[2] = css::uno::Any( sal_Int32( rXlibSurface.getDepth() ) );
524 0 : return css::uno::Any(args);
525 : }
526 :
527 : // draw a poly-polygon
528 0 : bool X11SalGraphics::drawPolyPolygon( const ::basegfx::B2DPolyPolygon& rOrigPolyPoly, double fTransparency )
529 : {
530 0 : return mxImpl->drawPolyPolygon( rOrigPolyPoly, fTransparency );
531 : }
532 :
533 0 : bool X11SalGraphics::drawPolyLine(
534 : const ::basegfx::B2DPolygon& rPolygon,
535 : double fTransparency,
536 : const ::basegfx::B2DVector& rLineWidth,
537 : basegfx::B2DLineJoin eLineJoin,
538 : com::sun::star::drawing::LineCap eLineCap)
539 : {
540 0 : return mxImpl->drawPolyLine( rPolygon, fTransparency, rLineWidth,
541 0 : eLineJoin, eLineCap );
542 : }
543 :
544 0 : bool X11SalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient)
545 : {
546 0 : return mxImpl->drawGradient(rPoly, rGradient);
547 : }
548 :
549 0 : void X11SalGraphics::BeginPaint()
550 : {
551 0 : return mxImpl->beginPaint();
552 : }
553 :
554 0 : void X11SalGraphics::EndPaint()
555 : {
556 0 : return mxImpl->endPaint();
557 : }
558 :
559 0 : SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const
560 : {
561 0 : if (m_pFrame)
562 0 : return static_cast< SalGeometryProvider * >(m_pFrame);
563 : else
564 0 : return static_cast< SalGeometryProvider * >(m_pVDev);
565 9 : }
566 :
567 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|