1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/outdev.hxx>
21 #include <vcl/print.hxx>
22 #include <tools/debug.hxx>
23 #include <tools/gen.hxx>
24 #include <tools/poly.hxx>
25 #include <unotools/charclass.hxx>
26 #include <editeng/unolingu.hxx>
27 #include <com/sun/star/i18n/KCharacterType.hpp>
28 #include <editeng/svxfont.hxx>
29 #include <editeng/escapementitem.hxx>
30 #include <sal/log.hxx>
36 eCaseMap
= SvxCaseMap::NotMapped
;
37 SetLanguage(LANGUAGE_SYSTEM
);
40 SvxFont::SvxFont( const vcl::Font
&rFont
)
45 eCaseMap
= SvxCaseMap::NotMapped
;
46 SetLanguage(LANGUAGE_SYSTEM
);
49 SvxFont::SvxFont( const SvxFont
&rFont
)
52 nKern
= rFont
.GetFixKerning();
53 nEsc
= rFont
.GetEscapement();
54 nPropr
= rFont
.GetPropr();
55 eCaseMap
= rFont
.GetCaseMap();
56 SetLanguage(rFont
.GetLanguage());
59 void SvxFont::DrawArrow( OutputDevice
&rOut
, const tools::Rectangle
& rRect
,
60 const Size
& rSize
, const Color
& rCol
, bool bLeft
)
62 long nLeft
= ( rRect
.Left() + rRect
.Right() - rSize
.Width() )/ 2;
63 long nRight
= nLeft
+ rSize
.Width();
64 long nMid
= ( rRect
.Top() + rRect
.Bottom() ) / 2;
65 long nTop
= nMid
- rSize
.Height() / 2;
66 long nBottom
= nTop
+ rSize
.Height();
67 if( nLeft
< rRect
.Left() )
70 nRight
= rRect
.Right();
72 if( nTop
< rRect
.Top() )
75 nBottom
= rRect
.Bottom();
78 Point
aTmp( bLeft
? nLeft
: nRight
, nMid
);
79 Point
aNxt( bLeft
? nRight
: nLeft
, nTop
);
80 aPoly
.Insert( 0, aTmp
);
81 aPoly
.Insert( 0, aNxt
);
83 aPoly
.Insert( 0, aNxt
);
84 aPoly
.Insert( 0, aTmp
);
85 Color aOldLineColor
= rOut
.GetLineColor();
86 Color aOldFillColor
= rOut
.GetFillColor();
87 rOut
.SetFillColor( rCol
);
88 rOut
.SetLineColor( COL_BLACK
);
89 rOut
.DrawPolygon( aPoly
);
90 rOut
.DrawLine( aTmp
, aNxt
);
91 rOut
.SetLineColor( aOldLineColor
);
92 rOut
.SetFillColor( aOldFillColor
);
96 OUString
SvxFont::CalcCaseMap(const OUString
&rTxt
) const
98 if (!IsCaseMap() || rTxt
.isEmpty())
101 // I still have to get the language
102 const LanguageType eLang
= LANGUAGE_DONTKNOW
== GetLanguage()
103 ? LANGUAGE_SYSTEM
: GetLanguage();
105 LanguageTag
aLanguageTag(eLang
);
106 CharClass
aCharClass( aLanguageTag
);
110 case SvxCaseMap::SmallCaps
:
111 case SvxCaseMap::Uppercase
:
113 aTxt
= aCharClass
.uppercase( aTxt
);
117 case SvxCaseMap::Lowercase
:
119 aTxt
= aCharClass
.lowercase( aTxt
);
122 case SvxCaseMap::Capitalize
:
124 // Every beginning of a word is capitalized, the rest of the word
125 // is taken over as is.
126 // Bug: if the attribute starts in the middle of the word.
129 for (sal_Int32 i
= 0; i
< aTxt
.getLength(); ++i
)
131 if( aTxt
[i
] == ' ' || aTxt
[i
] == '\t')
137 OUString
sTitle(aCharClass
.uppercase(OUString(aTxt
[i
])));
138 aTxt
= aTxt
.replaceAt(i
, 1, sTitle
);
147 SAL_WARN( "editeng", "SvxFont::CaseMapTxt: unknown casemap");
154 /*************************************************************************
155 * class SvxDoCapitals
156 * The virtual Method Do si called by SvxFont::DoOnCapitals alternately
157 * the uppercase and lowercase parts. The derivate of SvxDoCapitals fills
158 * this method with life.
159 *************************************************************************/
164 VclPtr
<OutputDevice
> pOut
;
165 const OUString
&rTxt
;
166 const sal_Int32 nIdx
;
167 const sal_Int32 nLen
;
170 SvxDoCapitals( OutputDevice
*_pOut
, const OUString
&_rTxt
,
171 const sal_Int32 _nIdx
, const sal_Int32 _nLen
)
172 : pOut(_pOut
), rTxt(_rTxt
), nIdx(_nIdx
), nLen(_nLen
)
175 virtual ~SvxDoCapitals() {}
177 virtual void DoSpace( const bool bDraw
);
178 virtual void SetSpace();
179 virtual void Do( const OUString
&rTxt
,
180 const sal_Int32 nIdx
, const sal_Int32 nLen
,
181 const bool bUpper
) = 0;
183 const OUString
&GetTxt() const { return rTxt
; }
184 sal_Int32
GetIdx() const { return nIdx
; }
185 sal_Int32
GetLen() const { return nLen
; }
188 void SvxDoCapitals::DoSpace( const bool /*bDraw*/ ) { }
190 void SvxDoCapitals::SetSpace() { }
192 /*************************************************************************
193 * SvxFont::DoOnCapitals() const
194 * Decomposes the String into uppercase and lowercase letters and then
195 * calls the method SvxDoCapitals::Do( ).
196 *************************************************************************/
198 void SvxFont::DoOnCapitals(SvxDoCapitals
&rDo
) const
200 const OUString
&rTxt
= rDo
.GetTxt();
201 const sal_Int32 nIdx
= rDo
.GetIdx();
202 const sal_Int32 nLen
= rDo
.GetLen();
204 const OUString
aTxt( CalcCaseMap( rTxt
) );
205 const sal_Int32 nTxtLen
= std::min( rTxt
.getLength(), nLen
);
207 sal_Int32 nOldPos
= nPos
;
209 // Test if string length differ between original and CaseMapped
210 bool bCaseMapLengthDiffers(aTxt
.getLength() != rTxt
.getLength());
212 const LanguageType eLang
= LANGUAGE_DONTKNOW
== GetLanguage()
213 ? LANGUAGE_SYSTEM
: GetLanguage();
215 LanguageTag
aLanguageTag(eLang
);
216 CharClass
aCharClass( aLanguageTag
);
217 OUString aCharString
;
219 while( nPos
< nTxtLen
)
221 // first in turn are the uppercase letters
223 // There are characters that are both upper- and lower-case L (eg blank)
224 // Such ambiguities lead to chaos, this is why these characters are
225 // allocated to the lowercase characters!
227 while( nPos
< nTxtLen
)
229 aCharString
= rTxt
.copy( nPos
+ nIdx
, 1 );
230 sal_Int32 nCharacterType
= aCharClass
.getCharacterType( aCharString
, 0 );
231 if ( nCharacterType
& css::i18n::KCharacterType::LOWER
)
233 if ( ! ( nCharacterType
& css::i18n::KCharacterType::UPPER
) )
237 if( nOldPos
!= nPos
)
239 if(bCaseMapLengthDiffers
)
241 // If strings differ work preparing the necessary snippet to address that
242 // potential difference
243 const OUString aSnippet
= rTxt
.copy(nIdx
+ nOldPos
, nPos
-nOldPos
);
244 OUString aNewText
= CalcCaseMap(aSnippet
);
246 rDo
.Do( aNewText
, 0, aNewText
.getLength(), true );
250 rDo
.Do( aTxt
, nIdx
+ nOldPos
, nPos
-nOldPos
, true );
255 // Now the lowercase are processed (without blanks)
256 while( nPos
< nTxtLen
)
258 sal_uInt32 nCharacterType
= aCharClass
.getCharacterType( aCharString
, 0 );
259 if ( nCharacterType
& css::i18n::KCharacterType::UPPER
)
261 if ( aCharString
== " " )
263 if( ++nPos
< nTxtLen
)
264 aCharString
= rTxt
.copy( nPos
+ nIdx
, 1 );
266 if( nOldPos
!= nPos
)
268 if(bCaseMapLengthDiffers
)
270 // If strings differ work preparing the necessary snippet to address that
271 // potential difference
272 const OUString aSnippet
= rTxt
.copy(nIdx
+ nOldPos
, nPos
- nOldPos
);
273 OUString aNewText
= CalcCaseMap(aSnippet
);
275 rDo
.Do( aNewText
, 0, aNewText
.getLength(), false );
279 rDo
.Do( aTxt
, nIdx
+ nOldPos
, nPos
-nOldPos
, false );
284 // Now the blanks are<processed
285 while( nPos
< nTxtLen
&& aCharString
== " " && ++nPos
< nTxtLen
)
286 aCharString
= rTxt
.copy( nPos
+ nIdx
, 1 );
288 if( nOldPos
!= nPos
)
290 rDo
.DoSpace( false );
292 if(bCaseMapLengthDiffers
)
294 // If strings differ work preparing the necessary snippet to address that
295 // potential difference
296 const OUString aSnippet
= rTxt
.copy(nIdx
+ nOldPos
, nPos
- nOldPos
);
297 OUString aNewText
= CalcCaseMap(aSnippet
);
299 rDo
.Do( aNewText
, 0, aNewText
.getLength(), false );
303 rDo
.Do( aTxt
, nIdx
+ nOldPos
, nPos
- nOldPos
, false );
314 void SvxFont::SetPhysFont( OutputDevice
*pOut
) const
316 const vcl::Font
& rCurrentFont
= pOut
->GetFont();
319 if ( !rCurrentFont
.IsSameInstance( *this ) )
320 pOut
->SetFont( *this );
324 Font
aNewFont( *this );
325 Size
aSize( aNewFont
.GetFontSize() );
326 aNewFont
.SetFontSize( Size( aSize
.Width() * nPropr
/ 100,
327 aSize
.Height() * nPropr
/ 100 ) );
328 if ( !rCurrentFont
.IsSameInstance( aNewFont
) )
329 pOut
->SetFont( aNewFont
);
334 vcl::Font
SvxFont::ChgPhysFont( OutputDevice
*pOut
) const
336 vcl::Font
aOldFont( pOut
->GetFont() );
342 Size
SvxFont::GetPhysTxtSize( const OutputDevice
*pOut
, const OUString
&rTxt
,
343 const sal_Int32 nIdx
, const sal_Int32 nLen
) const
345 if ( !IsCaseMap() && !IsKern() )
346 return Size( pOut
->GetTextWidth( rTxt
, nIdx
, nLen
),
347 pOut
->GetTextHeight() );
350 aTxtSize
.setHeight( pOut
->GetTextHeight() );
352 aTxtSize
.setWidth( pOut
->GetTextWidth( rTxt
, nIdx
, nLen
) );
355 const OUString aNewText
= CalcCaseMap(rTxt
);
356 bool bCaseMapLengthDiffers(aNewText
.getLength() != rTxt
.getLength());
359 if(bCaseMapLengthDiffers
)
361 // If strings differ work preparing the necessary snippet to address that
362 // potential difference
363 const OUString aSnippet
= rTxt
.copy(nIdx
, nLen
);
364 OUString _aNewText
= CalcCaseMap(aSnippet
);
365 nWidth
= pOut
->GetTextWidth( _aNewText
, 0, _aNewText
.getLength() );
369 nWidth
= pOut
->GetTextWidth( aNewText
, nIdx
, nLen
);
372 aTxtSize
.setWidth(nWidth
);
375 if( IsKern() && ( nLen
> 1 ) )
376 aTxtSize
.AdjustWidth( ( nLen
-1 ) * long( nKern
) );
381 Size
SvxFont::GetPhysTxtSize( const OutputDevice
*pOut
)
383 if ( !IsCaseMap() && !IsKern() )
384 return Size( pOut
->GetTextWidth( "" ), pOut
->GetTextHeight() );
387 aTxtSize
.setHeight( pOut
->GetTextHeight() );
389 aTxtSize
.setWidth( pOut
->GetTextWidth( "" ) );
391 aTxtSize
.setWidth( pOut
->GetTextWidth( CalcCaseMap( "" ) ) );
396 Size
SvxFont::QuickGetTextSize( const OutputDevice
*pOut
, const OUString
&rTxt
,
397 const sal_Int32 nIdx
, const sal_Int32 nLen
, long* pDXArray
) const
399 if ( !IsCaseMap() && !IsKern() )
400 return Size( pOut
->GetTextArray( rTxt
, pDXArray
, nIdx
, nLen
),
401 pOut
->GetTextHeight() );
404 aTxtSize
.setHeight( pOut
->GetTextHeight() );
406 aTxtSize
.setWidth( pOut
->GetTextArray( rTxt
, pDXArray
, nIdx
, nLen
) );
408 aTxtSize
.setWidth( pOut
->GetTextArray( CalcCaseMap( rTxt
),
409 pDXArray
, nIdx
, nLen
) );
411 if( IsKern() && ( nLen
> 1 ) )
413 aTxtSize
.AdjustWidth( ( nLen
-1 ) * long( nKern
) );
417 for ( sal_Int32 i
= 0; i
< nLen
; i
++ )
418 pDXArray
[i
] += ( (i
+1) * long( nKern
) );
419 // The last one is a nKern too big:
420 pDXArray
[nLen
-1] -= nKern
;
427 Size
SvxFont::GetTextSize( const OutputDevice
*pOut
, const OUString
&rTxt
,
428 const sal_Int32 nIdx
, const sal_Int32 nLen
) const
430 sal_Int32 nTmp
= nLen
;
431 if ( nTmp
== SAL_MAX_INT32
) // already initialized?
432 nTmp
= rTxt
.getLength();
433 Font
aOldFont( ChgPhysFont(const_cast<OutputDevice
*>(pOut
)) );
435 if( IsCapital() && !rTxt
.isEmpty() )
437 aTxtSize
= GetCapitalSize( pOut
, rTxt
, nIdx
, nTmp
);
439 else aTxtSize
= GetPhysTxtSize(pOut
,rTxt
,nIdx
,nTmp
);
440 const_cast<OutputDevice
*>(pOut
)->SetFont( aOldFont
);
445 void SvxFont::QuickDrawText( OutputDevice
*pOut
,
446 const Point
&rPos
, const OUString
&rTxt
,
447 const sal_Int32 nIdx
, const sal_Int32 nLen
, const long* pDXArray
) const
450 // Font has to be selected in OutputDevice...
451 if ( !IsCaseMap() && !IsCapital() && !IsKern() && !IsEsc() )
453 pOut
->DrawTextArray( rPos
, rTxt
, pDXArray
, nIdx
, nLen
);
461 long nDiff
= GetFontSize().Height();
466 aPos
.AdjustY( -nDiff
);
468 aPos
.AdjustX(nDiff
);
473 DBG_ASSERT( !pDXArray
, "DrawCapital not for TextArray!" );
474 DrawCapital( pOut
, aPos
, rTxt
, nIdx
, nLen
);
478 if ( IsKern() && !pDXArray
)
480 Size aSize
= GetPhysTxtSize( pOut
, rTxt
, nIdx
, nLen
);
483 pOut
->DrawStretchText( aPos
, aSize
.Width(), rTxt
, nIdx
, nLen
);
485 pOut
->DrawStretchText( aPos
, aSize
.Width(), CalcCaseMap( rTxt
), nIdx
, nLen
);
490 pOut
->DrawTextArray( aPos
, rTxt
, pDXArray
, nIdx
, nLen
);
492 pOut
->DrawTextArray( aPos
, CalcCaseMap( rTxt
), pDXArray
, nIdx
, nLen
);
498 void SvxFont::DrawPrev( OutputDevice
*pOut
, Printer
* pPrinter
,
499 const Point
&rPos
, const OUString
&rTxt
,
500 const sal_Int32 nIdx
, const sal_Int32 nLen
) const
502 if ( !nLen
|| rTxt
.isEmpty() )
504 sal_Int32 nTmp
= nLen
;
506 if ( nTmp
== SAL_MAX_INT32
) // already initialized?
507 nTmp
= rTxt
.getLength();
513 if( DFLT_ESC_AUTO_SUPER
== nEsc
)
515 else if( DFLT_ESC_AUTO_SUB
== nEsc
)
519 Size aSize
= GetFontSize();
520 aPos
.AdjustY( -(( nTmpEsc
* aSize
.Height() ) / 100) );
522 Font
aOldFont( ChgPhysFont( pOut
) );
523 Font
aOldPrnFont( ChgPhysFont( pPrinter
) );
526 DrawCapital( pOut
, aPos
, rTxt
, nIdx
, nTmp
);
529 Size aSize
= GetPhysTxtSize( pPrinter
, rTxt
, nIdx
, nTmp
);
532 pOut
->DrawStretchText( aPos
, aSize
.Width(), rTxt
, nIdx
, nTmp
);
535 const OUString aNewText
= CalcCaseMap(rTxt
);
536 bool bCaseMapLengthDiffers(aNewText
.getLength() != rTxt
.getLength());
538 if(bCaseMapLengthDiffers
)
540 // If strings differ work preparing the necessary snippet to address that
541 // potential difference
542 const OUString
aSnippet(rTxt
.copy( nIdx
, nTmp
));
543 OUString _aNewText
= CalcCaseMap(aSnippet
);
545 pOut
->DrawStretchText( aPos
, aSize
.Width(), _aNewText
, 0, _aNewText
.getLength() );
549 pOut
->DrawStretchText( aPos
, aSize
.Width(), CalcCaseMap( rTxt
), nIdx
, nTmp
);
553 pOut
->SetFont(aOldFont
);
554 pPrinter
->SetFont( aOldPrnFont
);
558 SvxFont
& SvxFont::operator=( const vcl::Font
& rFont
)
560 Font::operator=( rFont
);
564 SvxFont
& SvxFont::operator=( const SvxFont
& rFont
)
566 Font::operator=( rFont
);
567 eCaseMap
= rFont
.eCaseMap
;
569 nPropr
= rFont
.nPropr
;
574 class SvxDoGetCapitalSize
: public SvxDoCapitals
581 SvxDoGetCapitalSize( SvxFont
*_pFnt
, const OutputDevice
*_pOut
,
582 const OUString
&_rTxt
, const sal_Int32 _nIdx
,
583 const sal_Int32 _nLen
, const short _nKrn
)
584 : SvxDoCapitals( const_cast<OutputDevice
*>(_pOut
), _rTxt
, _nIdx
, _nLen
),
589 virtual void Do( const OUString
&rTxt
, const sal_Int32 nIdx
,
590 const sal_Int32 nLen
, const bool bUpper
) override
;
592 const Size
&GetSize() const { return aTxtSize
; };
595 void SvxDoGetCapitalSize::Do( const OUString
&_rTxt
, const sal_Int32 _nIdx
,
596 const sal_Int32 _nLen
, const bool bUpper
)
601 sal_uInt8 nProp
= pFont
->GetPropr();
602 pFont
->SetProprRel( SMALL_CAPS_PERCENTAGE
);
603 pFont
->SetPhysFont( pOut
);
604 aPartSize
.setWidth( pOut
->GetTextWidth( _rTxt
, _nIdx
, _nLen
) );
605 aPartSize
.setHeight( pOut
->GetTextHeight() );
606 aTxtSize
.setHeight( aPartSize
.Height() );
607 pFont
->SetPropr( nProp
);
608 pFont
->SetPhysFont( pOut
);
612 aPartSize
.setWidth( pOut
->GetTextWidth( _rTxt
, _nIdx
, _nLen
) );
613 aPartSize
.setHeight( pOut
->GetTextHeight() );
615 aTxtSize
.AdjustWidth(aPartSize
.Width() );
616 aTxtSize
.AdjustWidth( _nLen
* long( nKern
) );
619 Size
SvxFont::GetCapitalSize( const OutputDevice
*pOut
, const OUString
&rTxt
,
620 const sal_Int32 nIdx
, const sal_Int32 nLen
) const
623 SvxDoGetCapitalSize
aDo( const_cast<SvxFont
*>(this), pOut
, rTxt
, nIdx
, nLen
, nKern
);
625 Size
aTxtSize( aDo
.GetSize() );
628 if( !aTxtSize
.Height() )
630 aTxtSize
.setWidth( 0 );
631 aTxtSize
.setHeight( pOut
->GetTextHeight() );
636 class SvxDoDrawCapital
: public SvxDoCapitals
644 SvxDoDrawCapital( SvxFont
*pFnt
, OutputDevice
*_pOut
, const OUString
&_rTxt
,
645 const sal_Int32 _nIdx
, const sal_Int32 _nLen
,
646 const Point
&rPos
, const short nKrn
)
647 : SvxDoCapitals( _pOut
, _rTxt
, _nIdx
, _nLen
),
653 virtual void DoSpace( const bool bDraw
) override
;
654 virtual void SetSpace() override
;
655 virtual void Do( const OUString
&rTxt
, const sal_Int32 nIdx
,
656 const sal_Int32 nLen
, const bool bUpper
) override
;
659 void SvxDoDrawCapital::DoSpace( const bool bDraw
)
661 if ( bDraw
|| pFont
->IsWordLineMode() )
663 sal_uLong nDiff
= static_cast<sal_uLong
>(aPos
.X() - aSpacePos
.X());
666 bool bWordWise
= pFont
->IsWordLineMode();
667 bool bTrans
= pFont
->IsTransparent();
668 pFont
->SetWordLineMode( false );
669 pFont
->SetTransparent( true );
670 pFont
->SetPhysFont( pOut
);
671 pOut
->DrawStretchText( aSpacePos
, nDiff
, " ", 0, 2 );
672 pFont
->SetWordLineMode( bWordWise
);
673 pFont
->SetTransparent( bTrans
);
674 pFont
->SetPhysFont( pOut
);
679 void SvxDoDrawCapital::SetSpace()
681 if ( pFont
->IsWordLineMode() )
682 aSpacePos
.setX( aPos
.X() );
685 void SvxDoDrawCapital::Do( const OUString
&_rTxt
, const sal_Int32 _nIdx
,
686 const sal_Int32 _nLen
, const bool bUpper
)
691 // Set the desired font
692 FontLineStyle eUnder
= pFont
->GetUnderline();
693 FontStrikeout eStrike
= pFont
->GetStrikeout();
694 pFont
->SetUnderline( LINESTYLE_NONE
);
695 pFont
->SetStrikeout( STRIKEOUT_NONE
);
698 nProp
= pFont
->GetPropr();
699 pFont
->SetProprRel( SMALL_CAPS_PERCENTAGE
);
701 pFont
->SetPhysFont( pOut
);
703 aPartSize
.setWidth( pOut
->GetTextWidth( _rTxt
, _nIdx
, _nLen
) );
704 aPartSize
.setHeight( pOut
->GetTextHeight() );
705 long nWidth
= aPartSize
.Width();
708 aPos
.AdjustX(nKern
/2);
709 if ( _nLen
) nWidth
+= (_nLen
*long(nKern
));
711 pOut
->DrawStretchText(aPos
,nWidth
-nKern
,_rTxt
,_nIdx
,_nLen
);
714 pFont
->SetUnderline( eUnder
);
715 pFont
->SetStrikeout( eStrike
);
717 pFont
->SetPropr( nProp
);
718 pFont
->SetPhysFont( pOut
);
720 aPos
.AdjustX(nWidth
-(nKern
/2) );
723 /*************************************************************************
724 * SvxFont::DrawCapital() draws the uppercase letter.
725 *************************************************************************/
727 void SvxFont::DrawCapital( OutputDevice
*pOut
,
728 const Point
&rPos
, const OUString
&rTxt
,
729 const sal_Int32 nIdx
, const sal_Int32 nLen
) const
731 SvxDoDrawCapital
aDo( const_cast<SvxFont
*>(this),pOut
,rTxt
,nIdx
,nLen
,rPos
,nKern
);
735 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */