faa9f72091
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>
375 lines
16 KiB
C++
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(); )
|