/*
|
* Copyright 2015 Google Inc.
|
*
|
* Use of this source code is governed by a BSD-style license that can be
|
* found in the LICENSE file.
|
*/
|
|
#ifndef SkFindAndPositionGlyph_DEFINED
|
#define SkFindAndPositionGlyph_DEFINED
|
|
#include "SkArenaAlloc.h"
|
#include "SkGlyph.h"
|
#include "SkMatrixPriv.h"
|
#include "SkPaint.h"
|
#include "SkStrike.h"
|
#include "SkTemplates.h"
|
#include "SkUTF.h"
|
#include <utility>
|
|
class SkFindAndPlaceGlyph {
|
public:
|
// ProcessPosText handles all cases for finding and positioning glyphs. It has a very large
|
// multiplicity. It figures out the glyph, position and rounding and pass those parameters to
|
// processOneGlyph.
|
//
|
// The routine processOneGlyph passed in by the client has the following signature:
|
// void f(const SkGlyph& glyph, SkPoint position, SkPoint rounding);
|
//
|
// * Sub-pixel positioning (2) - use sub-pixel positioning.
|
// * Text alignment (3) - text alignment with respect to the glyph's width.
|
// * Matrix type (3) - special cases for translation and X-coordinate scaling.
|
// * Components per position (2) - the positions vector can have a common Y with different
|
// Xs, or XY-pairs.
|
// * Axis Alignment (for sub-pixel positioning) (3) - when using sub-pixel positioning, round
|
// to a whole coordinate instead of using sub-pixel positioning.
|
// The number of variations is 108 for sub-pixel and 36 for full-pixel.
|
// This routine handles all of them using inline polymorphic variable (no heap allocation).
|
template<typename ProcessOneGlyph>
|
static void ProcessPosText(
|
const SkGlyphID[], int count,
|
SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
|
SkStrike* cache, ProcessOneGlyph&& processOneGlyph);
|
|
// The SubpixelAlignment function produces a suitable position for the glyph cache to
|
// produce the correct sub-pixel alignment. If a position is aligned with an axis a shortcut
|
// of 0 is used for the sub-pixel position.
|
static SkIPoint SubpixelAlignment(SkAxisAlignment axisAlignment, SkPoint position) {
|
|
if (!SkScalarsAreFinite(position.fX, position.fY)) {
|
return {0, 0};
|
}
|
|
// Only the fractional part of position.fX and position.fY matter, because the result of
|
// this function will just be passed to FixedToSub.
|
switch (axisAlignment) {
|
case kX_SkAxisAlignment:
|
return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding), 0};
|
case kY_SkAxisAlignment:
|
return {0, SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
|
case kNone_SkAxisAlignment:
|
return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding),
|
SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
|
}
|
SK_ABORT("Should not get here.");
|
return {0, 0};
|
}
|
|
// The SubpixelPositionRounding function returns a point suitable for rounding a sub-pixel
|
// positioned glyph.
|
static SkPoint SubpixelPositionRounding(SkAxisAlignment axisAlignment) {
|
switch (axisAlignment) {
|
case kX_SkAxisAlignment:
|
return {kSubpixelRounding, SK_ScalarHalf};
|
case kY_SkAxisAlignment:
|
return {SK_ScalarHalf, kSubpixelRounding};
|
case kNone_SkAxisAlignment:
|
return {kSubpixelRounding, kSubpixelRounding};
|
}
|
SK_ABORT("Should not get here.");
|
return {0.0f, 0.0f};
|
}
|
|
// MapperInterface given a point map it through the matrix. There are several shortcut
|
// variants.
|
// * TranslationMapper - assumes a translation only matrix.
|
// * XScaleMapper - assumes an X scaling and a translation.
|
// * GeneralMapper - Does all other matricies.
|
class MapperInterface {
|
public:
|
virtual ~MapperInterface() {}
|
|
virtual SkPoint map(SkPoint position) const = 0;
|
};
|
|
static MapperInterface* CreateMapper(const SkMatrix& matrix, const SkPoint& offset,
|
int scalarsPerPosition, SkArenaAlloc* arena) {
|
auto mtype = matrix.getType();
|
if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask) ||
|
scalarsPerPosition == 2) {
|
return arena->make<GeneralMapper>(matrix, offset);
|
}
|
|
if (mtype & SkMatrix::kScale_Mask) {
|
return arena->make<XScaleMapper>(matrix, offset);
|
}
|
|
return arena->make<TranslationMapper>(matrix, offset);
|
}
|
|
private:
|
// PositionReaderInterface reads a point from the pos vector.
|
// * HorizontalPositions - assumes a common Y for many X values.
|
// * ArbitraryPositions - a list of (X,Y) pairs.
|
class PositionReaderInterface {
|
public:
|
virtual ~PositionReaderInterface() { }
|
virtual SkPoint nextPoint() = 0;
|
};
|
|
class HorizontalPositions final : public PositionReaderInterface {
|
public:
|
explicit HorizontalPositions(const SkScalar* positions)
|
: fPositions(positions) { }
|
|
SkPoint nextPoint() override {
|
SkScalar x = *fPositions++;
|
return {x, 0};
|
}
|
|
private:
|
const SkScalar* fPositions;
|
};
|
|
class ArbitraryPositions final : public PositionReaderInterface {
|
public:
|
explicit ArbitraryPositions(const SkScalar* positions)
|
: fPositions(positions) { }
|
|
SkPoint nextPoint() override {
|
SkPoint to_return{fPositions[0], fPositions[1]};
|
fPositions += 2;
|
return to_return;
|
}
|
|
private:
|
const SkScalar* fPositions;
|
};
|
|
class TranslationMapper final : public MapperInterface {
|
public:
|
TranslationMapper(const SkMatrix& matrix, const SkPoint origin)
|
: fTranslate(matrix.mapXY(origin.fX, origin.fY)) { }
|
|
SkPoint map(SkPoint position) const override {
|
return position + fTranslate;
|
}
|
|
private:
|
const SkPoint fTranslate;
|
};
|
|
class XScaleMapper final : public MapperInterface {
|
public:
|
XScaleMapper(const SkMatrix& matrix, const SkPoint origin)
|
: fTranslate(matrix.mapXY(origin.fX, origin.fY)), fXScale(matrix.getScaleX()) { }
|
|
SkPoint map(SkPoint position) const override {
|
return {fXScale * position.fX + fTranslate.fX, fTranslate.fY};
|
}
|
|
private:
|
const SkPoint fTranslate;
|
const SkScalar fXScale;
|
};
|
|
// The caller must keep matrix alive while this class is used.
|
class GeneralMapper final : public MapperInterface {
|
public:
|
GeneralMapper(const SkMatrix& matrix, const SkPoint origin)
|
: fOrigin(origin), fMatrix(matrix), fMapProc(SkMatrixPriv::GetMapXYProc(matrix)) { }
|
|
SkPoint map(SkPoint position) const override {
|
SkPoint result;
|
fMapProc(fMatrix, position.fX + fOrigin.fX, position.fY + fOrigin.fY, &result);
|
return result;
|
}
|
|
private:
|
const SkPoint fOrigin;
|
const SkMatrix& fMatrix;
|
const SkMatrixPriv::MapXYProc fMapProc;
|
};
|
|
// The "call" to SkFixedToScalar is actually a macro. It's macros all the way down.
|
// Needs to be a macro because you can't have a const float unless you make it constexpr.
|
static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
|
|
// GlyphFindAndPlaceInterface given the text and position finds the correct glyph and does
|
// glyph specific position adjustment. The findAndPositionGlyph method takes text and
|
// position and calls processOneGlyph with the correct glyph, final position and rounding
|
// terms. The final position is not rounded yet and is the responsibility of processOneGlyph.
|
template<typename ProcessOneGlyph>
|
class GlyphFindAndPlaceInterface : SkNoncopyable {
|
public:
|
virtual ~GlyphFindAndPlaceInterface() { }
|
|
// findAndPositionGlyph calculates the position of the glyph, finds the glyph, and
|
// returns the position of where the next glyph will be using the glyph's advance. The
|
// returned position is used by drawText, but ignored by drawPosText.
|
// The compiler should prune all this calculation if the return value is not used.
|
//
|
// This should be a pure virtual, but some versions of GCC <= 4.8 have a bug that causes a
|
// compile error.
|
// See GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60277
|
virtual SkPoint findAndPositionGlyph(
|
SkGlyphID, SkPoint position,
|
ProcessOneGlyph&& processOneGlyph) {
|
SK_ABORT("Should never get here.");
|
return {0.0f, 0.0f};
|
}
|
};
|
|
// GlyphFindAndPlaceSubpixel handles finding and placing glyphs when sub-pixel positioning is
|
// requested. After it has found and placed the glyph it calls the templated function
|
// ProcessOneGlyph in order to actually perform an action.
|
template<typename ProcessOneGlyph, SkAxisAlignment kAxisAlignment>
|
class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
|
public:
|
explicit GlyphFindAndPlaceSubpixel(SkStrike* cache) : fCache(cache) {}
|
|
SkPoint findAndPositionGlyph(SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
|
// Find the glyph.
|
SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, position);
|
const SkGlyph& renderGlyph = fCache->getGlyphIDMetrics(glyphID, lookupPosition.fX, lookupPosition.fY);
|
|
// If the glyph has no width (no pixels) then don't bother processing it.
|
if (renderGlyph.fWidth > 0) {
|
processOneGlyph(renderGlyph, position,
|
SubpixelPositionRounding(kAxisAlignment));
|
}
|
return position + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX),
|
SkFloatToScalar(renderGlyph.fAdvanceY)};
|
}
|
|
private:
|
SkStrike* fCache;
|
};
|
|
// GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel
|
// positioning is requested.
|
template<typename ProcessOneGlyph>
|
class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
|
public:
|
explicit GlyphFindAndPlaceFullPixel(SkStrike* cache) : fCache(cache) {}
|
|
SkPoint findAndPositionGlyph(
|
SkGlyphID glyphID, SkPoint position,
|
ProcessOneGlyph&& processOneGlyph) override {
|
SkPoint finalPosition = position;
|
const SkGlyph& glyph = fCache->getGlyphIDMetrics(glyphID);
|
|
if (glyph.fWidth > 0) {
|
processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
|
}
|
return finalPosition + SkPoint{SkFloatToScalar(glyph.fAdvanceX),
|
SkFloatToScalar(glyph.fAdvanceY)};
|
}
|
|
private:
|
SkStrike* fCache;
|
};
|
|
template <typename ProcessOneGlyph>
|
static GlyphFindAndPlaceInterface<ProcessOneGlyph>* getSubpixel(
|
SkArenaAlloc* arena, SkAxisAlignment axisAlignment, SkStrike* cache)
|
{
|
switch (axisAlignment) {
|
case kX_SkAxisAlignment:
|
return arena->make<GlyphFindAndPlaceSubpixel<
|
ProcessOneGlyph, kX_SkAxisAlignment>>(cache);
|
case kNone_SkAxisAlignment:
|
return arena->make<GlyphFindAndPlaceSubpixel<
|
ProcessOneGlyph, kNone_SkAxisAlignment>>(cache);
|
case kY_SkAxisAlignment:
|
return arena->make<GlyphFindAndPlaceSubpixel<
|
ProcessOneGlyph, kY_SkAxisAlignment>>(cache);
|
}
|
SK_ABORT("Should never get here.");
|
return nullptr;
|
}
|
};
|
|
template<typename ProcessOneGlyph>
|
inline void SkFindAndPlaceGlyph::ProcessPosText(
|
const SkGlyphID glyphs[], int count,
|
SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
|
SkStrike* cache, ProcessOneGlyph&& processOneGlyph) {
|
|
SkAxisAlignment axisAlignment = cache->getScalerContext()->computeAxisAlignmentForHText();
|
uint32_t mtype = matrix.getType();
|
|
// Specialized code for handling the most common case for blink.
|
if (axisAlignment == kX_SkAxisAlignment
|
&& cache->isSubpixel()
|
&& mtype <= SkMatrix::kTranslate_Mask)
|
{
|
using Positioner =
|
GlyphFindAndPlaceSubpixel <
|
ProcessOneGlyph, kX_SkAxisAlignment>;
|
HorizontalPositions hPositions{pos};
|
ArbitraryPositions aPositions{pos};
|
PositionReaderInterface* positions = nullptr;
|
if (scalarsPerPosition == 2) {
|
positions = &aPositions;
|
} else {
|
positions = &hPositions;
|
}
|
TranslationMapper mapper{matrix, offset};
|
Positioner positioner(cache);
|
for (int i = 0; i < count; ++i) {
|
SkPoint mappedPoint = mapper.TranslationMapper::map(positions->nextPoint());
|
positioner.Positioner::findAndPositionGlyph(
|
glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
|
}
|
return;
|
}
|
|
SkSTArenaAlloc<120> arena;
|
|
PositionReaderInterface* positionReader = nullptr;
|
if (2 == scalarsPerPosition) {
|
positionReader = arena.make<ArbitraryPositions>(pos);
|
} else {
|
positionReader = arena.make<HorizontalPositions>(pos);
|
}
|
|
MapperInterface* mapper = CreateMapper(matrix, offset, scalarsPerPosition, &arena);
|
GlyphFindAndPlaceInterface<ProcessOneGlyph>* findAndPosition = nullptr;
|
if (cache->isSubpixel()) {
|
findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, cache);
|
} else {
|
findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(cache);
|
}
|
|
for (int i = 0; i < count; ++i) {
|
SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
|
findAndPosition->findAndPositionGlyph(
|
glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
|
}
|
}
|
|
#endif // SkFindAndPositionGlyph_DEFINED
|