Coding Style Guidelines
These conventions have evolved over time. Some of the earlier code in both projects doesn’t strictly adhere to the guidelines. However, as the code evolves we hope to make the existing code conform to the guildelines.
Files
We use .cpp and .h as extensions for c++ source and header files.
Headers that aren’t meant for public consumption should be placed in src directories so that they aren’t in a client’s search path, or in include/private if they need to be used by public headers.
We prefer to minimize includes. If forward declaring a name in a header is sufficient then that is preferred to an include.
Forward declarations and file includes should be in alphabetical order.
No define before sktypes
Do not use #if/#ifdef before including “SkTypes.h” (directly or indirectly). Most things you’d #if on tend to not yet be decided until SkTypes.h.
We use 4 spaces, not tabs.
We use Unix style endlines (LF).
We prefer no trailing whitespace but aren’t very strict about it.
We wrap lines at 100 columns unless it is excessively ugly (use your judgement).
Naming
Most externally visible types and functions use an Sk- prefix to designate they’re part of Skia, but code in Ganesh uses Gr-. Nested types need not be prefixed.
class SkClass {
public:
class HelperClass {
...
};
};
Data fields in structs, classes, and unions that have methods begin with lower-case f and are then camel-capped, to distinguish those fields from other variables. Types that are predominantly meant for direct field access don’t need f-decoration.
struct GrCar {
float milesDriven;
Color color;
};
class GrMotorcyle {
public:
float getMilesDriven() const { return fMilesDriven; }
void setMilesDriven(float milesDriven) { fMilesDriven = milesDriven; }
Color getColor() const { return fColor; }
private:
float fMilesDriven;
Color fColor;
};
Global variables are similar but prefixed with g and camel-capped.
bool gLoggingEnabled;
Local variables and arguments are camel-capped with no initial cap.
int herdCats(const Array& cats) {
int numCats = cats.count();
}
Variables declared constexpr
or const
, and whose value is fixed for the
duration of the program, are named with a leading “k” and then camel-capped.
int drawPicture() {
constexpr SkISize kPictureSize = {100, 100};
constexpr float kZoom = 1.0f;
}
Enum values are also prefixed with k. Unscoped enum values are postfixed with an
underscore and singular name of the enum name. The enum itself should be
singular for exclusive values or plural for a bitfield. If a count is needed it
is k<singular enum name>Count
and not be a member of the enum (see example),
or a kLast member of the enum is fine too.
// Enum class does not need suffixes.
enum class SkPancakeType {
kBlueberry,
kPlain,
kChocolateChip,
};
// Enum should have a suffix after the enum name.
enum SkDonutType {
kGlazed_DonutType,
kSprinkles_DonutType,
kChocolate_DonutType,
kMaple_DonutType,
kLast_DonutType = kMaple_DonutType
};
static const SkDonutType kDonutTypeCount = kLast_DonutType + 1;
enum SkSausageIngredientBits {
kFennel_SausageIngredientBit = 0x1,
kBeef_SausageIngredientBit = 0x2
};
enum SkMatrixFlags {
kTranslate_MatrixFlag = 0x1,
kRotate_MatrixFlag = 0x2
};
Macros are all caps with underscores between words. Macros that have greater than file scope should be prefixed SK or GR.
Static non-class functions in implementation files are lower-case with underscores separating words:
static inline bool tastes_like_chicken(Food food) {
return kIceCream_Food != food;
}
Externed functions or static class functions are camel-capped with an initial cap:
bool SkIsOdd(int n);
class SkFoo {
public:
static int FooInstanceCount();
// Not static.
int barBaz();
};
Macros
Ganesh macros that are GL-specific should be prefixed GR_GL.
#define GR_GL_TEXTURE0 0xdeadbeef
Ganesh prefers that macros are always defined and the use of #if MACRO
rather
than #ifdef MACRO
.
#define GR_GO_SLOWER 0
...
#if GR_GO_SLOWER
Sleep(1000);
#endif
The rest of Skia tends to use #ifdef SK_MACRO
for boolean flags.
Braces
Open braces don’t get a newline. else
and else if
appear on same line as
opening and closing braces unless preprocessor conditional compilation
interferes. Braces are always used with if
, else
, while
, for
, and do
.
if (...) {
oneOrManyLines;
}
if (...) {
oneOrManyLines;
} else if (...) {
oneOrManyLines;
} else {
oneOrManyLines;
}
for (...) {
oneOrManyLines;
}
while (...) {
oneOrManyLines;
}
void function(...) {
oneOrManyLines;
}
if (!error) {
proceed_as_usual();
}
#if HANDLE_ERROR
else {
freak_out();
}
#endif
Flow Control
There is a space between flow control words and parentheses, and between parentheses and braces:
while (...) {
}
do {
} while (...);
switch (...) {
...
}
Cases and default in switch statements are indented from the switch.
switch (color) {
case kBlue:
...
break;
case kGreen:
...
break;
...
default:
...
break;
}
Fallthrough from one case to the next is annotated with [[fallthrough]]
.
However, when multiple case statements in a row are used, they do not need the
[[fallthrough]]
annotation.
switch (recipe) {
...
case kSmallCheesePizza_Recipe:
case kLargeCheesePizza_Recipe:
ingredients |= kCheese_Ingredient | kDough_Ingredient | kSauce_Ingredient;
break;
case kCheeseOmelette_Recipe:
ingredients |= kCheese_Ingredient;
[[fallthrough]]
case kPlainOmelette_Recipe:
ingredients |= (kEgg_Ingredient | kMilk_Ingredient);
break;
...
}
When a block is needed to declare variables within a case follow this pattern:
switch (filter) {
...
case kGaussian_Filter: {
Bitmap srcCopy = src->makeCopy();
...
} break;
...
};
Classes
Unless there is a need for forward declaring something, class declarations
should be ordered public
, protected
, private
. Each should be preceded by a
newline. Within each visibility section (public
, private
), fields should not
be intermixed with methods. It’s nice to keep all data fields together at the
end.
class SkFoo {
public:
...
protected:
...
private:
void barHelper(...);
...
SkBar fBar;
...
};
Virtual functions that are overridden in derived classes should use override, and the virtual keyword should be omitted.
void myVirtual() override {
}
If you call a method on a parent type that must stand out as specifically the
parent’s version of that method, such as Parent::method()
. The this->
that
would normally be required before a method call on the current object is not
necessary when using a scope qualifier.
class GrDillPickle : public GrPickle {
...
bool onTasty() const override {
return GrPickle::onTasty()
&& fFreshDill;
}
...
private:
bool fFreshDill;
};
Constructor initializers should be placed on the same line as the constructor, if they fit. Otherwise, each initializer should be on its own line, indented, with punctuation placed before the initializer.
GrDillPickle::GrDillPickle() : GrPickle(), fSize(kDefaultPickleSize) {}
GrDillPickle::GrDillPickle(float size, float crunchiness, const PickleOptions* options)
: GrPickle(options)
, fSize(size)
, fCrunchiness(crunchiness) {}
Constructors that take one argument should almost always be explicit, with exceptions made only for the (rare) automatic compatibility class.
class Foo {
explicit Foo(int x); // Good.
Foo(float y); // Spooky implicit conversion from float to Foo. No no no!
...
};
Method calls within method calls should be prefixed with dereference of the ‘this’ pointer. For example:
this->method();
A common pattern for virtual methods in Skia is to include a public non-virtual
(or final) method, paired with a private virtual method named “onMethodName”.
This ensures that the base-class method is always invoked and gives it control
over how the virtual method is used, rather than relying on each subclass to
call Parent::onMethodName()
. For example:
class SkSandwich {
public:
void assemble() {
// All sandwiches must have bread on the top and bottom.
this->addIngredient(kBread_Ingredient);
this->onAssemble();
this->addIngredient(kBread_Ingredient);
}
bool cook() {
return this->onCook();
}
private:
// All sandwiches must implement onAssemble.
virtual void onAssemble() = 0;
// Sandwiches can remain uncooked by default.
virtual bool onCook() { return true; }
};
class SkGrilledCheese : public SkSandwich {
private:
void onAssemble() override {
this->addIngredient(kCheese_Ingredient);
}
bool onCook() override {
return this->toastOnGriddle();
}
};
class SkPeanutButterAndJelly : public SkSandwich {
private:
void onAssemble() override {
this->addIngredient(kPeanutButter_Ingredient);
this->addIngredient(kGrapeJelly_Ingredient);
}
};
Integer Types
We follow the Google C++ guide for ints and are slowly making older code conform to this
(https://google.github.io/styleguide/cppguide.html#Integer_Types)
Summary: Use int
unless you have need a guarantee on the bit count, then use
stdint.h
types (int32_t
, etc). Assert that counts, etc are not negative
instead of using unsigned. Bitfields use uint32_t
unless they have to be made
shorter for packing or performance reasons.
Function Parameters
Mandatory constant object parameters are passed to functions as const references. Optional constant object parameters are passed to functions as const pointers. Mutable object parameters are passed to functions as pointers. We very rarely pass anything by non-const reference.
// src and paint are optional
void SkCanvas::drawBitmapRect(const SkBitmap& bitmap, const SkIRect* src,
const SkRect& dst, const SkPaint* paint = nullptr);
// metrics is mutable (it is changed by the method)
SkScalar SkPaint::getFontMetrics(FontMetric* metrics, SkScalar scale) const;
If function arguments or parameters do not all fit on one line, the overflowing parameters may be lined up with the first parameter on the next line
void drawBitmapRect(const SkBitmap& bitmap, const SkRect& dst,
const SkPaint* paint = nullptr) {
this->drawBitmapRectToRect(bitmap, nullptr, dst, paint,
kNone_DrawBitmapRectFlag);
}
or all parameters placed on the next line and indented eight spaces
void drawBitmapRect(
const SkBitmap& bitmap, const SkRect& dst, const SkPaint* paint = nullptr) {
this->drawBitmapRectToRect(
bitmap, nullptr, dst, paint, kNone_DrawBitmapRectFlag);
}
Python
Python code follows the Google Python Style Guide.
Folder Organiziation
Skia’s public API should live in the include
directory. Skia’s private headers and implementation
files should live in the src
directory. The modules
directory contains extra features that are
built on top of Skia (modules/skcms
being an exception) and can be used by clients.
Private utilities to test Skia live in tools
and can be used for reference but should not be used
by clients.
No header in include
should depend on files in other directories (modules/skcms
being an exception).
This is to prevent private symbols from leaking into client code via transitive dependencies.