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 .
21 #include <sdr/overlay/overlaytools.hxx>
22 #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
27 #include <basegfx/polygon/b2dpolygontools.hxx>
28 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
29 #include <drawinglayer/geometry/viewinformation2d.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
32 #include <vcl/svapp.hxx>
33 #include <vcl/settings.hxx>
36 namespace drawinglayer
41 OverlayStaticRectanglePrimitive::OverlayStaticRectanglePrimitive(
42 const basegfx::B2DPoint
& rPosition
,
43 const basegfx::B2DSize
& rSize
,
44 const basegfx::BColor
& rStrokeColor
,
45 const basegfx::BColor
& rFillColor
,
48 : DiscreteMetricDependentPrimitive2D()
49 , maPosition(rPosition
)
51 , maStrokeColor(rStrokeColor
)
52 , maFillColor(rFillColor
)
53 , mfTransparence(fTransparence
)
54 , mfRotation(fRotation
)
57 void OverlayStaticRectanglePrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
59 Primitive2DContainer aPrimitive2DSequence
;
60 const double fHalfWidth
= maSize
.getX() * getDiscreteUnit() / 2.0;
61 const double fHalfHeight
= maSize
.getY() * getDiscreteUnit() / 2.0;
63 basegfx::B2DRange
aRange(
64 maPosition
.getX() - fHalfWidth
, maPosition
.getY() - fHalfHeight
,
65 maPosition
.getX() + fHalfWidth
, maPosition
.getY() + fHalfHeight
);
67 if (basegfx::fTools::more(getDiscreteUnit(), 0.0) && mfTransparence
<= 1.0)
69 basegfx::B2DPolygon
aPolygon(
70 basegfx::utils::createPolygonFromRect(aRange
));
72 // create filled primitive
73 basegfx::B2DPolyPolygon aPolyPolygon
;
74 aPolyPolygon
.append(aPolygon
);
76 const attribute::LineAttribute
aLineAttribute(maStrokeColor
, 1.0);
79 const Primitive2DReference
aStroke(
80 new PolyPolygonStrokePrimitive2D(aPolyPolygon
, aLineAttribute
));
82 // create fill primitive
83 const Primitive2DReference
aFill(
84 new PolyPolygonColorPrimitive2D(aPolyPolygon
, maFillColor
));
86 aPrimitive2DSequence
= Primitive2DContainer(2);
87 aPrimitive2DSequence
[0] = aFill
;
88 aPrimitive2DSequence
[1] = aStroke
;
90 // embed filled to transparency (if used)
91 if (mfTransparence
> 0.0)
93 const Primitive2DReference
aFillTransparent(
94 new UnifiedTransparencePrimitive2D(
98 aPrimitive2DSequence
= Primitive2DContainer
{ aFillTransparent
};
102 rContainer
.insert(rContainer
.end(), aPrimitive2DSequence
.begin(), aPrimitive2DSequence
.end());
105 bool OverlayStaticRectanglePrimitive::operator==(const BasePrimitive2D
& rPrimitive
) const
107 if (DiscreteMetricDependentPrimitive2D::operator==(rPrimitive
))
109 const OverlayStaticRectanglePrimitive
& rCompare
= static_cast<const OverlayStaticRectanglePrimitive
&>(rPrimitive
);
111 return (maPosition
== rCompare
.maPosition
112 && maSize
== rCompare
.maSize
113 && maStrokeColor
== rCompare
.maStrokeColor
114 && maFillColor
== rCompare
.maFillColor
115 && mfTransparence
== rCompare
.mfTransparence
116 && mfRotation
== rCompare
.mfRotation
);
122 ImplPrimitive2DIDBlock(OverlayStaticRectanglePrimitive
, PRIMITIVE2D_ID_OVERLAYRECTANGLEPRIMITIVE
)
124 }} // end of namespace drawinglayer::primitive2d
126 namespace drawinglayer
128 namespace primitive2d
130 OverlayBitmapExPrimitive::OverlayBitmapExPrimitive(
131 const BitmapEx
& rBitmapEx
,
132 const basegfx::B2DPoint
& rBasePosition
,
137 : DiscreteMetricDependentPrimitive2D(),
138 maBitmapEx(rBitmapEx
),
139 maBasePosition(rBasePosition
),
143 mfRotation(fRotation
)
146 void OverlayBitmapExPrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
148 const Size
aBitmapSize(getBitmapEx().GetSizePixel());
150 if(!aBitmapSize
.Width() || !aBitmapSize
.Height() || !basegfx::fTools::more(getDiscreteUnit(), 0.0))
153 // calculate back from internal bitmap's extreme coordinates (the edges)
154 // to logical coordinates. Only use a unified scaling value (getDiscreteUnit(),
155 // the prepared one which expresses how many logic units form a discrete unit)
156 // for this step. This primitive is to be displayed always unscaled (in it's pixel size)
157 // and unrotated, more like a marker
158 const double fLeft((0.0 - getCenterX()) * getDiscreteUnit());
159 const double fTop((0.0 - getCenterY()) * getDiscreteUnit());
160 const double fRight((aBitmapSize
.getWidth() - getCenterX()) * getDiscreteUnit());
161 const double fBottom((aBitmapSize
.getHeight() - getCenterY()) * getDiscreteUnit());
163 // create a BitmapPrimitive2D using those positions
164 basegfx::B2DHomMatrix aTransform
;
166 aTransform
.set(0, 0, fRight
- fLeft
);
167 aTransform
.set(1, 1, fBottom
- fTop
);
168 aTransform
.set(0, 2, fLeft
);
169 aTransform
.set(1, 2, fTop
);
171 // if shearX is used, apply it, too
172 if(!basegfx::fTools::equalZero(getShearX()))
174 aTransform
.shearX(getShearX());
177 // if rotation is used, apply it, too
178 if(!basegfx::fTools::equalZero(getRotation()))
180 aTransform
.rotate(getRotation());
184 aTransform
.translate(getBasePosition().getX(), getBasePosition().getY());
186 rContainer
.push_back(new BitmapPrimitive2D(getBitmapEx(), aTransform
));
189 bool OverlayBitmapExPrimitive::operator==( const BasePrimitive2D
& rPrimitive
) const
191 if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive
))
193 const OverlayBitmapExPrimitive
& rCompare
= static_cast< const OverlayBitmapExPrimitive
& >(rPrimitive
);
195 return (getBitmapEx() == rCompare
.getBitmapEx()
196 && getBasePosition() == rCompare
.getBasePosition()
197 && getCenterX() == rCompare
.getCenterX()
198 && getCenterY() == rCompare
.getCenterY()
199 && getShearX() == rCompare
.getShearX()
200 && getRotation() == rCompare
.getRotation());
206 ImplPrimitive2DIDBlock(OverlayBitmapExPrimitive
, PRIMITIVE2D_ID_OVERLAYBITMAPEXPRIMITIVE
)
208 } // end of namespace primitive2d
209 } // end of namespace drawinglayer
212 namespace drawinglayer
214 namespace primitive2d
216 OverlayCrosshairPrimitive::OverlayCrosshairPrimitive(
217 const basegfx::B2DPoint
& rBasePosition
,
218 const basegfx::BColor
& rRGBColorA
,
219 const basegfx::BColor
& rRGBColorB
,
220 double fDiscreteDashLength
)
221 : ViewportDependentPrimitive2D(),
222 maBasePosition(rBasePosition
),
223 maRGBColorA(rRGBColorA
),
224 maRGBColorB(rRGBColorB
),
225 mfDiscreteDashLength(fDiscreteDashLength
)
228 void OverlayCrosshairPrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
230 // use the prepared Viewport information accessible using getViewport()
232 if(!getViewport().isEmpty())
234 basegfx::B2DPolygon aPolygon
;
236 aPolygon
.append(basegfx::B2DPoint(getViewport().getMinX(), getBasePosition().getY()));
237 aPolygon
.append(basegfx::B2DPoint(getViewport().getMaxX(), getBasePosition().getY()));
239 rContainer
.push_back(
240 new PolygonMarkerPrimitive2D(
244 getDiscreteDashLength()));
247 aPolygon
.append(basegfx::B2DPoint(getBasePosition().getX(), getViewport().getMinY()));
248 aPolygon
.append(basegfx::B2DPoint(getBasePosition().getX(), getViewport().getMaxY()));
250 rContainer
.push_back(
251 new PolygonMarkerPrimitive2D(
255 getDiscreteDashLength()));
259 bool OverlayCrosshairPrimitive::operator==( const BasePrimitive2D
& rPrimitive
) const
261 if(ViewportDependentPrimitive2D::operator==(rPrimitive
))
263 const OverlayCrosshairPrimitive
& rCompare
= static_cast< const OverlayCrosshairPrimitive
& >(rPrimitive
);
265 return (getBasePosition() == rCompare
.getBasePosition()
266 && getRGBColorA() == rCompare
.getRGBColorA()
267 && getRGBColorB() == rCompare
.getRGBColorB()
268 && getDiscreteDashLength() == rCompare
.getDiscreteDashLength());
274 ImplPrimitive2DIDBlock(OverlayCrosshairPrimitive
, PRIMITIVE2D_ID_OVERLAYCROSSHAIRPRIMITIVE
)
276 } // end of namespace primitive2d
277 } // end of namespace drawinglayer
280 namespace drawinglayer
282 namespace primitive2d
284 OverlayRectanglePrimitive::OverlayRectanglePrimitive(
285 const basegfx::B2DRange
& rObjectRange
,
286 const basegfx::BColor
& rColor
,
287 double fTransparence
,
288 double fDiscreteGrow
,
289 double fDiscreteShrink
,
291 : DiscreteMetricDependentPrimitive2D(),
292 maObjectRange(rObjectRange
),
294 mfTransparence(fTransparence
),
295 mfDiscreteGrow(fDiscreteGrow
),
296 mfDiscreteShrink(fDiscreteShrink
),
297 mfRotation(fRotation
)
300 void OverlayRectanglePrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
302 Primitive2DContainer aRetval
;
303 basegfx::B2DRange
aInnerRange(getObjectRange());
305 if(!aInnerRange
.isEmpty() && basegfx::fTools::more(getDiscreteUnit(), 0.0) && getTransparence() <= 1.0)
307 basegfx::B2DRange
aOuterRange(getObjectRange());
309 // grow/shrink inner/outer polygons
310 aOuterRange
.grow(getDiscreteUnit() * getDiscreteGrow());
311 aInnerRange
.grow(getDiscreteUnit() * -getDiscreteShrink());
313 // convert to polygons
314 const double fFullGrow(getDiscreteGrow() + getDiscreteShrink());
315 const double fRelativeRadiusX(fFullGrow
/ aOuterRange
.getWidth());
316 const double fRelativeRadiusY(fFullGrow
/ aOuterRange
.getHeight());
317 basegfx::B2DPolygon
aOuterPolygon(
318 basegfx::utils::createPolygonFromRect(
322 basegfx::B2DPolygon
aInnerPolygon(
323 basegfx::utils::createPolygonFromRect(
326 // apply evtl. existing rotation
327 if(!basegfx::fTools::equalZero(getRotation()))
329 const basegfx::B2DHomMatrix
aTransform(basegfx::utils::createRotateAroundPoint(
330 getObjectRange().getMinX(), getObjectRange().getMinY(), getRotation()));
332 aOuterPolygon
.transform(aTransform
);
333 aInnerPolygon
.transform(aTransform
);
336 // create filled primitive
337 basegfx::B2DPolyPolygon aPolyPolygon
;
339 aPolyPolygon
.append(aOuterPolygon
);
340 aPolyPolygon
.append(aInnerPolygon
);
342 if(Application::GetSettings().GetStyleSettings().GetHighContrastMode())
344 // for high contrast, use hatch
345 const basegfx::BColor
aHighContrastLineColor(Application::GetSettings().GetStyleSettings().GetFontColor().getBColor());
346 const basegfx::BColor
aEmptyColor(0.0, 0.0, 0.0);
347 const double fHatchRotation(basegfx::deg2rad(45));
348 const double fDiscreteHatchDistance(3.0);
349 const drawinglayer::attribute::FillHatchAttribute
aFillHatchAttribute(
350 drawinglayer::attribute::HatchStyle::Single
,
351 fDiscreteHatchDistance
* getDiscreteUnit(),
352 fHatchRotation
- getRotation(),
353 aHighContrastLineColor
,
354 3, // same default as VCL, a minimum of three discrete units (pixels) offset
356 const Primitive2DReference
aHatch(
357 new PolyPolygonHatchPrimitive2D(
360 aFillHatchAttribute
));
362 aRetval
= Primitive2DContainer
{ aHatch
};
366 // create fill primitive
367 const Primitive2DReference
aFill(
368 new PolyPolygonColorPrimitive2D(
372 aRetval
= Primitive2DContainer
{ aFill
};
374 // embed filled to transparency (if used)
375 if(getTransparence() > 0.0)
377 const Primitive2DReference
aFillTransparent(
378 new UnifiedTransparencePrimitive2D(
382 aRetval
= Primitive2DContainer
{ aFillTransparent
};
387 rContainer
.insert(rContainer
.end(), aRetval
.begin(), aRetval
.end());
390 bool OverlayRectanglePrimitive::operator==( const BasePrimitive2D
& rPrimitive
) const
392 if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive
))
394 const OverlayRectanglePrimitive
& rCompare
= static_cast< const OverlayRectanglePrimitive
& >(rPrimitive
);
396 return (getObjectRange() == rCompare
.getObjectRange()
397 && getColor() == rCompare
.getColor()
398 && getTransparence() == rCompare
.getTransparence()
399 && getDiscreteGrow() == rCompare
.getDiscreteGrow()
400 && getDiscreteShrink() == rCompare
.getDiscreteShrink()
401 && getRotation() == rCompare
.getRotation());
407 ImplPrimitive2DIDBlock(OverlayRectanglePrimitive
, PRIMITIVE2D_ID_OVERLAYRECTANGLEPRIMITIVE
)
409 } // end of namespace primitive2d
410 } // end of namespace drawinglayer
413 namespace drawinglayer
415 namespace primitive2d
417 OverlayHelplineStripedPrimitive::OverlayHelplineStripedPrimitive(
418 const basegfx::B2DPoint
& rBasePosition
,
419 HelplineStyle eStyle
,
420 const basegfx::BColor
& rRGBColorA
,
421 const basegfx::BColor
& rRGBColorB
,
422 double fDiscreteDashLength
)
423 : ViewportDependentPrimitive2D(),
424 maBasePosition(rBasePosition
),
426 maRGBColorA(rRGBColorA
),
427 maRGBColorB(rRGBColorB
),
428 mfDiscreteDashLength(fDiscreteDashLength
)
431 void OverlayHelplineStripedPrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& rViewInformation
) const
433 // use the prepared Viewport information accessible using getViewport()
435 if(!getViewport().isEmpty())
439 case HELPLINESTYLE_VERTICAL
:
441 basegfx::B2DPolygon aLine
;
443 aLine
.append(basegfx::B2DPoint(getBasePosition().getX(), getViewport().getMinY()));
444 aLine
.append(basegfx::B2DPoint(getBasePosition().getX(), getViewport().getMaxY()));
446 rContainer
.push_back(
447 new PolygonMarkerPrimitive2D(
451 getDiscreteDashLength()));
455 case HELPLINESTYLE_HORIZONTAL
:
457 basegfx::B2DPolygon aLine
;
459 aLine
.append(basegfx::B2DPoint(getViewport().getMinX(), getBasePosition().getY()));
460 aLine
.append(basegfx::B2DPoint(getViewport().getMaxX(), getBasePosition().getY()));
462 rContainer
.push_back(
463 new PolygonMarkerPrimitive2D(
467 getDiscreteDashLength()));
471 default: // case HELPLINESTYLE_POINT :
473 const double fDiscreteUnit((rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)).getLength());
474 basegfx::B2DPolygon aLineA
, aLineB
;
476 aLineA
.append(basegfx::B2DPoint(getBasePosition().getX(), getBasePosition().getY() - fDiscreteUnit
));
477 aLineA
.append(basegfx::B2DPoint(getBasePosition().getX(), getBasePosition().getY() + fDiscreteUnit
));
479 rContainer
.push_back(
480 new PolygonMarkerPrimitive2D(
484 getDiscreteDashLength()));
486 aLineB
.append(basegfx::B2DPoint(getBasePosition().getX() - fDiscreteUnit
, getBasePosition().getY()));
487 aLineB
.append(basegfx::B2DPoint(getBasePosition().getX() + fDiscreteUnit
, getBasePosition().getY()));
489 rContainer
.push_back(
490 new PolygonMarkerPrimitive2D(
494 getDiscreteDashLength()));
502 bool OverlayHelplineStripedPrimitive::operator==( const BasePrimitive2D
& rPrimitive
) const
504 if(ViewportDependentPrimitive2D::operator==(rPrimitive
))
506 const OverlayHelplineStripedPrimitive
& rCompare
= static_cast< const OverlayHelplineStripedPrimitive
& >(rPrimitive
);
508 return (getBasePosition() == rCompare
.getBasePosition()
509 && getStyle() == rCompare
.getStyle()
510 && getRGBColorA() == rCompare
.getRGBColorA()
511 && getRGBColorB() == rCompare
.getRGBColorB()
512 && getDiscreteDashLength() == rCompare
.getDiscreteDashLength());
518 ImplPrimitive2DIDBlock(OverlayHelplineStripedPrimitive
, PRIMITIVE2D_ID_OVERLAYHELPLINESTRIPEDPRIMITIVE
)
520 } // end of namespace primitive2d
521 } // end of namespace drawinglayer
524 namespace drawinglayer
526 namespace primitive2d
528 OverlayRollingRectanglePrimitive::OverlayRollingRectanglePrimitive(
529 const basegfx::B2DRange
& aRollingRectangle
,
530 const basegfx::BColor
& rRGBColorA
,
531 const basegfx::BColor
& rRGBColorB
,
532 double fDiscreteDashLength
)
533 : ViewportDependentPrimitive2D(),
534 maRollingRectangle(aRollingRectangle
),
535 maRGBColorA(rRGBColorA
),
536 maRGBColorB(rRGBColorB
),
537 mfDiscreteDashLength(fDiscreteDashLength
)
540 void OverlayRollingRectanglePrimitive::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
542 // use the prepared Viewport information accessible using getViewport()
544 if(getViewport().isEmpty())
547 basegfx::B2DPolygon aLine
;
550 aLine
.append(basegfx::B2DPoint(getViewport().getMinX(), getRollingRectangle().getMinY()));
551 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getRollingRectangle().getMinY()));
552 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
555 aLine
.append(basegfx::B2DPoint(getViewport().getMinX(), getRollingRectangle().getMaxY()));
556 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getRollingRectangle().getMaxY()));
557 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
561 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getRollingRectangle().getMinY()));
562 aLine
.append(basegfx::B2DPoint(getViewport().getMaxX(), getRollingRectangle().getMinY()));
563 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
566 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getRollingRectangle().getMaxY()));
567 aLine
.append(basegfx::B2DPoint(getViewport().getMaxX(), getRollingRectangle().getMaxY()));
568 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
572 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getViewport().getMinY()));
573 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getRollingRectangle().getMinY()));
574 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
577 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getViewport().getMinY()));
578 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getRollingRectangle().getMinY()));
579 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
583 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getRollingRectangle().getMaxY()));
584 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMinX(), getViewport().getMaxY()));
585 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
588 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getRollingRectangle().getMaxY()));
589 aLine
.append(basegfx::B2DPoint(getRollingRectangle().getMaxX(), getViewport().getMaxY()));
590 rContainer
.push_back(new PolygonMarkerPrimitive2D(aLine
, getRGBColorA(), getRGBColorB(), getDiscreteDashLength()));
594 bool OverlayRollingRectanglePrimitive::operator==( const BasePrimitive2D
& rPrimitive
) const
596 if(ViewportDependentPrimitive2D::operator==(rPrimitive
))
598 const OverlayRollingRectanglePrimitive
& rCompare
= static_cast< const OverlayRollingRectanglePrimitive
& >(rPrimitive
);
600 return (getRollingRectangle() == rCompare
.getRollingRectangle()
601 && getRGBColorA() == rCompare
.getRGBColorA()
602 && getRGBColorB() == rCompare
.getRGBColorB()
603 && getDiscreteDashLength() == rCompare
.getDiscreteDashLength());
609 ImplPrimitive2DIDBlock(OverlayRollingRectanglePrimitive
, PRIMITIVE2D_ID_OVERLAYROLLINGRECTANGLEPRIMITIVE
)
611 } // end of namespace primitive2d
612 } // end of namespace drawinglayer
614 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */