blob: 2486b92c7677a08c168e692d6236d8f4bcf6ba57 [file] [log] [blame]
/*
* Copyright 2022 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkGraphics.h"
#include "include/core/SkTime.h"
#include "include/core/SkTypeface.h"
#include "include/ports/SkFontMgr_empty.h"
#include "samplecode/Sample.h"
#include "src/sfnt/SkOTTable_glyf.h"
#include "src/sfnt/SkOTTable_head.h"
#include "src/sfnt/SkOTTable_hhea.h"
#include "src/sfnt/SkOTTable_hmtx.h"
#include "src/sfnt/SkOTTable_loca.h"
#include "src/sfnt/SkOTTable_maxp.h"
#include "src/sfnt/SkSFNTHeader.h"
#include "tools/Resources.h"
#include "tools/timer/TimeUtils.h"
namespace {
constexpr SkScalar DX = 100;
constexpr SkScalar DY = 300;
constexpr int kPointSize = 5;
constexpr SkScalar kFontSize = 200;
constexpr char kFontFile[] = "fonts/sbix_uncompressed_flags.ttf";
constexpr SkGlyphID kGlyphID = 2;
//constexpr char kFontFile[] = "fonts/HangingS.ttf";
//constexpr SkGlyphID kGlyphID = 4;
/**
* Return the closest int for the given float. Returns SK_MaxS32FitsInFloat for NaN.
*/
static inline int16_t sk_float_saturate2int16(float x) {
x = x < SK_MaxS16 ? x : SK_MaxS16;
x = x > SK_MinS16 ? x : SK_MinS16;
return (int16_t)x;
}
struct ShortCoordinate { bool negative; uint8_t magnitude; };
static inline ShortCoordinate sk_float_saturate2sm8(float x) {
bool negative = x < 0;
x = x < 255 ? x : 255;
x = x > -255 ? x : -255;
return ShortCoordinate{ negative, negative ? (uint8_t)-x : (uint8_t)x };
}
struct SBIXView : public Sample {
SkString name() override { return SkString("SBIX"); }
SkPoint fPts[12] = {
{0, 0}, // min
{0, 0}, // max
{0, 20}, // lsb
{0, 0}, // point
};
std::vector<sk_sp<SkFontMgr>> fFontMgr;
std::vector<SkFont> fFonts;
sk_sp<SkData> fSBIXData;
bool fInputChanged = false;
bool fDirty = true;
sk_sp<SkData> updateSBIXData(SkData* originalData, bool setPts) {
// Lots of unlikely to be aligned pointers in here, which is UB. Total hack.
sk_sp<SkData> dataCopy = SkData::MakeWithCopy(originalData->data(), originalData->size());
SkSFNTHeader* sfntHeader = static_cast<SkSFNTHeader*>(dataCopy->writable_data());
SkASSERT_RELEASE(memcmp(sfntHeader, originalData->data(), originalData->size()) == 0);
SkSFNTHeader::TableDirectoryEntry* tableEntry =
SkTAfter<SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
SkSFNTHeader::TableDirectoryEntry* glyfTableEntry = nullptr;
SkSFNTHeader::TableDirectoryEntry* headTableEntry = nullptr;
SkSFNTHeader::TableDirectoryEntry* hheaTableEntry = nullptr;
SkSFNTHeader::TableDirectoryEntry* hmtxTableEntry = nullptr;
SkSFNTHeader::TableDirectoryEntry* locaTableEntry = nullptr;
SkSFNTHeader::TableDirectoryEntry* maxpTableEntry = nullptr;
int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
for (int tableEntryIndex = 0; tableEntryIndex < numTables; ++tableEntryIndex) {
if (SkOTTableGlyph::TAG == tableEntry[tableEntryIndex].tag) {
glyfTableEntry = tableEntry + tableEntryIndex;
}
if (SkOTTableHead::TAG == tableEntry[tableEntryIndex].tag) {
headTableEntry = tableEntry + tableEntryIndex;
}
if (SkOTTableHorizontalHeader::TAG == tableEntry[tableEntryIndex].tag) {
hheaTableEntry = tableEntry + tableEntryIndex;
}
if (SkOTTableHorizontalMetrics::TAG == tableEntry[tableEntryIndex].tag) {
hmtxTableEntry = tableEntry + tableEntryIndex;
}
if (SkOTTableIndexToLocation::TAG == tableEntry[tableEntryIndex].tag) {
locaTableEntry = tableEntry + tableEntryIndex;
}
if (SkOTTableMaximumProfile::TAG == tableEntry[tableEntryIndex].tag) {
maxpTableEntry = tableEntry + tableEntryIndex;
}
}
SkASSERT_RELEASE(glyfTableEntry);
SkASSERT_RELEASE(headTableEntry);
SkASSERT_RELEASE(hheaTableEntry);
SkASSERT_RELEASE(hmtxTableEntry);
SkASSERT_RELEASE(locaTableEntry);
SkASSERT_RELEASE(maxpTableEntry);
size_t glyfTableOffset = SkEndian_SwapBE32(glyfTableEntry->offset);
SkOTTableGlyph* glyfTable =
SkTAddOffset<SkOTTableGlyph>(sfntHeader, glyfTableOffset);
size_t headTableOffset = SkEndian_SwapBE32(headTableEntry->offset);
SkOTTableHead* headTable =
SkTAddOffset<SkOTTableHead>(sfntHeader, headTableOffset);
size_t hheaTableOffset = SkEndian_SwapBE32(hheaTableEntry->offset);
SkOTTableHorizontalHeader* hheaTable =
SkTAddOffset<SkOTTableHorizontalHeader>(sfntHeader, hheaTableOffset);
size_t hmtxTableOffset = SkEndian_SwapBE32(hmtxTableEntry->offset);
SkOTTableHorizontalMetrics* hmtxTable =
SkTAddOffset<SkOTTableHorizontalMetrics>(sfntHeader, hmtxTableOffset);
size_t locaTableOffset = SkEndian_SwapBE32(locaTableEntry->offset);
SkOTTableIndexToLocation* locaTable =
SkTAddOffset<SkOTTableIndexToLocation>(sfntHeader, locaTableOffset);
size_t maxpTableOffset = SkEndian_SwapBE32(maxpTableEntry->offset);
SkOTTableMaximumProfile* maxpTable =
SkTAddOffset<SkOTTableMaximumProfile>(sfntHeader, maxpTableOffset);
SkASSERT_RELEASE(SkEndian_SwapBE32(maxpTable->version.version) == 0x00010000);
int numGlyphs = SkEndian_SwapBE16(maxpTable->version.tt.numGlyphs);
SkASSERT_RELEASE(kGlyphID < numGlyphs);
int emSize = SkEndian_SwapBE16(headTable->unitsPerEm);
SkScalar toEm = emSize / kFontSize;
SkOTTableGlyph::Iterator glyphIter(*glyfTable, *locaTable, headTable->indexToLocFormat);
glyphIter.advance(kGlyphID);
SkOTTableGlyphData* glyphData = glyphIter.next();
if (glyphData) {
if (setPts) {
fPts[0].set((int16_t)SkEndian_SwapBE16(glyphData->xMin) / toEm,
(int16_t)SkEndian_SwapBE16(glyphData->yMin) / -toEm);
fPts[1].set((int16_t)SkEndian_SwapBE16(glyphData->xMax) / toEm,
(int16_t)SkEndian_SwapBE16(glyphData->yMax) / -toEm);
} else {
glyphData->xMin = SkEndian_SwapBE16(sk_float_saturate2int16( fPts[0].x()*toEm));
glyphData->yMin = SkEndian_SwapBE16(sk_float_saturate2int16(-fPts[0].y()*toEm));
glyphData->xMax = SkEndian_SwapBE16(sk_float_saturate2int16( fPts[1].x()*toEm));
glyphData->yMax = SkEndian_SwapBE16(sk_float_saturate2int16(-fPts[1].y()*toEm));
}
int contourCount = SkEndian_SwapBE16(glyphData->numberOfContours);
if (contourCount > 0) {
SK_OT_USHORT* endPtsOfContours = SkTAfter<SK_OT_USHORT>(glyphData);
SK_OT_USHORT* numInstructions = SkTAfter<SK_OT_USHORT>(endPtsOfContours,
contourCount);
SK_OT_BYTE* instructions = SkTAfter<SK_OT_BYTE>(numInstructions);
SkOTTableGlyphData::Simple::Flags* flags =
SkTAfter<SkOTTableGlyphData::Simple::Flags>(
instructions, SkEndian_SwapBE16(*numInstructions));
int numResultPoints = SkEndian_SwapBE16(endPtsOfContours[contourCount-1]) + 1;
struct Coordinate {
SkOTTableGlyphData::Simple::Flags* flags;
size_t offsetToXDelta;
size_t xDeltaSize;
size_t offsetToYDelta;
size_t yDeltaSize;
};
std::vector<Coordinate> coordinates(numResultPoints);
size_t offsetToXDelta = 0;
size_t offsetToYDelta = 0;
SkOTTableGlyphData::Simple::Flags* currentFlags = flags;
for (int i = 0; i < numResultPoints; ++i) {
SkOTTableGlyphData::Simple::Flags* nextFlags;
int times = 1;
if (currentFlags->field.Repeat) {
SK_OT_BYTE* repeat = SkTAfter<SK_OT_BYTE>(currentFlags);
times += *repeat;
nextFlags = SkTAfter<SkOTTableGlyphData::Simple::Flags>(repeat);
} else {
nextFlags = SkTAfter<SkOTTableGlyphData::Simple::Flags>(currentFlags);
}
--i;
for (int time = 0; time < times; ++time) {
++i;
coordinates[i].flags = currentFlags;
coordinates[i].offsetToXDelta = offsetToXDelta;
coordinates[i].offsetToYDelta = offsetToYDelta;
if (currentFlags->field.xShortVector) {
offsetToXDelta += 1;
coordinates[i].xDeltaSize = 1;
} else if (currentFlags->field.xIsSame_xShortVectorPositive) {
offsetToXDelta += 0;
if (i == 0) {
coordinates[i].xDeltaSize = 0;
} else {
coordinates[i].xDeltaSize = coordinates[i-1].xDeltaSize;
}
} else {
offsetToXDelta += 2;
coordinates[i].xDeltaSize = 2;
}
if (currentFlags->field.yShortVector) {
offsetToYDelta += 1;
coordinates[i].yDeltaSize = 1;
} else if (currentFlags->field.yIsSame_yShortVectorPositive) {
offsetToYDelta += 0;
if (i == 0) {
coordinates[i].yDeltaSize = 0;
} else {
coordinates[i].yDeltaSize = coordinates[i-1].yDeltaSize;
}
} else {
offsetToYDelta += 2;
coordinates[i].yDeltaSize = 2;
}
}
currentFlags = nextFlags;
}
SK_OT_BYTE* xCoordinates = reinterpret_cast<SK_OT_BYTE*>(currentFlags);
SK_OT_BYTE* yCoordinates = xCoordinates + offsetToXDelta;
int pointIndex = 0;
if (coordinates[pointIndex].xDeltaSize == 0) {
// Zero delta relative to the origin. There is no data to modify.
SkDebugf("Failed to move point in X at all.\n");
} else if (coordinates[pointIndex].xDeltaSize == 1) {
ShortCoordinate x = sk_float_saturate2sm8(fPts[3].x()*toEm);
xCoordinates[coordinates[pointIndex].offsetToXDelta] = x.magnitude;
coordinates[pointIndex].flags->field.xIsSame_xShortVectorPositive = !x.negative;
} else {
*reinterpret_cast<SK_OT_SHORT*>(xCoordinates + coordinates[pointIndex].offsetToXDelta) =
SkEndian_SwapBE16(sk_float_saturate2int16(fPts[3].x()*toEm));
}
if (coordinates[pointIndex].yDeltaSize == 0) {
// Zero delta relative to the origin. There is no data to modify.
SkDebugf("Failed to move point in Y at all.\n");
} else if (coordinates[pointIndex].yDeltaSize == 1) {
ShortCoordinate y = sk_float_saturate2sm8(-fPts[3].y()*toEm);
yCoordinates[coordinates[pointIndex].offsetToYDelta] = y.magnitude;
coordinates[pointIndex].flags->field.yIsSame_yShortVectorPositive = !y.negative;
} else {
*reinterpret_cast<SK_OT_SHORT*>(yCoordinates + coordinates[pointIndex].offsetToYDelta) =
SkEndian_SwapBE16(sk_float_saturate2int16(-fPts[3].y()*toEm));
}
}
}
int numberOfFullMetrics = SkEndian_SwapBE16(hheaTable->numberOfHMetrics);
SkOTTableHorizontalMetrics::FullMetric* fullMetrics = hmtxTable->longHorMetric;
SK_OT_SHORT lsb = SkEndian_SwapBE16(sk_float_saturate2int16(fPts[2].x()*toEm));
if (kGlyphID < numberOfFullMetrics) {
if (setPts) {
fPts[2].fX = (int16_t)SkEndian_SwapBE16(fullMetrics[kGlyphID].lsb) / toEm;
} else {
fullMetrics[kGlyphID].lsb = lsb;
}
} else {
SkOTTableHorizontalMetrics::ShortMetric* shortMetrics =
SkTAfter<SkOTTableHorizontalMetrics::ShortMetric>(fullMetrics, numberOfFullMetrics);
int shortMetricIndex = kGlyphID - numberOfFullMetrics;
if (setPts) {
fPts[2].fX = (int16_t)SkEndian_SwapBE16(shortMetrics[shortMetricIndex].lsb) / toEm;
} else {
shortMetrics[shortMetricIndex].lsb = lsb;
}
}
headTable->flags.field.LeftSidebearingAtX0 = false;
return dataCopy;
}
void onOnceBeforeDraw() override {
fFontMgr.emplace_back(SkFontMgr::RefDefault());
//fFontMgr.emplace_back(SkFontMgr_New_Custom_Empty());
// GetResourceAsData may be backed by a read only file mapping.
// For sanity always make a copy.
fSBIXData = GetResourceAsData(kFontFile);
this->setBGColor(SK_ColorGRAY);
updateSBIXData(fSBIXData.get(), true);
}
void onDrawContent(SkCanvas* canvas) override {
canvas->translate(DX, DY);
SkPaint paint;
SkPoint position{0, 0};
SkPoint origin{0, 0};
if (fDirty) {
sk_sp<SkData> data(updateSBIXData(fSBIXData.get(), false));
fFonts.clear();
for (auto&& fontmgr : fFontMgr) {
fFonts.emplace_back(fontmgr->makeFromData(data), kFontSize);
}
fDirty = false;
}
for (auto&& font : fFonts) {
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(SK_ColorBLACK);
canvas->drawGlyphs(1, &kGlyphID, &position, origin, font, paint);
paint.setStrokeWidth(SkIntToScalar(kPointSize / 2));
paint.setStyle(SkPaint::kStroke_Style);
SkScalar advance;
SkRect rect;
font.getWidthsBounds(&kGlyphID, 1, &advance, &rect, &paint);
paint.setColor(SK_ColorRED);
canvas->drawRect(rect, paint);
paint.setColor(SK_ColorGREEN);
canvas->drawLine(0, 0, advance, 0, paint);
paint.setColor(SK_ColorRED);
canvas->drawPoint(0, 0, paint);
canvas->drawPoint(advance, 0, paint);
paint.setStrokeWidth(SkIntToScalar(kPointSize));
canvas->drawPoints(SkCanvas::kPoints_PointMode, std::size(fPts), fPts, paint);
canvas->translate(kFontSize, 0);
}
}
class PtClick : public Click {
public:
int fIndex;
PtClick(int index) : fIndex(index) {}
};
static bool hittest(const SkPoint& pt, SkScalar x, SkScalar y) {
return SkPoint::Length(pt.fX - x, pt.fY - y) < SkIntToScalar(kPointSize);
}
Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
x -= DX;
y -= DY;
for (size_t i = 0; i < std::size(fPts); i++) {
if (hittest(fPts[i], x, y)) {
return new PtClick((int)i);
}
}
return nullptr;
}
bool onClick(Click* click) override {
fPts[((PtClick*)click)->fIndex].set(click->fCurr.fX - DX, click->fCurr.fY - DY);
fDirty = true;
return true;
}
private:
using INHERITED = Sample;
};
} // namespace
DEF_SAMPLE( return new SBIXView(); )