skia2/samplecode/SampleSBIX.cpp
Ben Wagner faa9f72091 Add SampleSBIX to study sbix glyph translation.
What affects the placement of an sbix image? The cbox? The bounding box?
The lsb? The offsets? In which direction? What are the side effects?

Change-Id: I5b630c2117a26481733392bc1e95428d9a67fb34
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/519078
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
2022-03-25 14:43:23 +00:00

375 lines
16 KiB
C++

/*
* 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, SK_ARRAY_COUNT(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 < SK_ARRAY_COUNT(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(); )