skia2/tests/TypefaceTest.cpp
Ben Wagner f9c7b28034 Add test for old font axis deserialization.
Minimized from fuzzer case (rr is amazing). Verified that this test
failed before the fix 712d3a57e "Cannot create SkFontData with no data."
and now succeeds with the fix.

Bug: oss-fuzz:26254
Change-Id: I985ba3ab61e5824d6a61ede04880692ecc69ce44
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/327916
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
2020-10-16 20:32:41 +00:00

421 lines
17 KiB
C++

/*
* Copyright 2013 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/SkData.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkStream.h"
#include "include/core/SkTypeface.h"
#include "include/ports/SkTypeface_win.h"
#include "include/private/SkFixed.h"
#include "src/core/SkAdvancedTypefaceMetrics.h"
#include "src/core/SkFontDescriptor.h"
#include "src/core/SkFontMgrPriv.h"
#include "src/core/SkFontPriv.h"
#include "src/core/SkTypefaceCache.h"
#include "src/sfnt/SkOTTable_OS_2.h"
#include "src/sfnt/SkSFNTHeader.h"
#include "src/utils/SkUTF.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include "tools/fonts/TestEmptyTypeface.h"
#include <memory>
static void TypefaceStyle_test(skiatest::Reporter* reporter,
uint16_t weight, uint16_t width, SkData* data)
{
sk_sp<SkData> dataCopy;
if (!data->unique()) {
dataCopy = SkData::MakeWithCopy(data->data(), data->size());
data = dataCopy.get();
}
SkSFNTHeader* sfntHeader = static_cast<SkSFNTHeader*>(data->writable_data());
SkSFNTHeader::TableDirectoryEntry* tableEntry =
SkTAfter<SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
SkSFNTHeader::TableDirectoryEntry* os2TableEntry = nullptr;
int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
for (int tableEntryIndex = 0; tableEntryIndex < numTables; ++tableEntryIndex) {
if (SkOTTableOS2::TAG == tableEntry[tableEntryIndex].tag) {
os2TableEntry = tableEntry + tableEntryIndex;
break;
}
}
SkASSERT_RELEASE(os2TableEntry);
size_t os2TableOffset = SkEndian_SwapBE32(os2TableEntry->offset);
SkOTTableOS2_V0* os2Table = SkTAddOffset<SkOTTableOS2_V0>(sfntHeader, os2TableOffset);
os2Table->usWeightClass.value = SkEndian_SwapBE16(weight);
using WidthType = SkOTTableOS2_V0::WidthClass::Value;
os2Table->usWidthClass.value = static_cast<WidthType>(SkEndian_SwapBE16(width));
sk_sp<SkTypeface> newTypeface(SkTypeface::MakeFromData(sk_ref_sp(data)));
if (!newTypeface) {
// Not all SkFontMgr can MakeFromStream().
return;
}
SkFontStyle newStyle = newTypeface->fontStyle();
//printf("%d, %f\n", weight, (newStyle.weight() - (float)0x7FFF) / (float)0x7FFF);
//printf("%d, %f\n", width , (newStyle.width() - (float)0x7F) / (float)0x7F);
//printf("%d, %d\n", weight, newStyle.weight());
//printf("%d, %d\n", width , newStyle.width());
// Some back-ends (CG, GDI, DW) support OS/2 version A which uses 0 - 10 (but all differently).
REPORTER_ASSERT(reporter,
newStyle.weight() == weight ||
(weight <= 10 && newStyle.weight() == 100 * weight) ||
(weight == 4 && newStyle.weight() == 350) || // GDI weirdness
(weight == 5 && newStyle.weight() == 400) || // GDI weirdness
(weight == 0 && newStyle.weight() == 1) || // DW weirdness
(weight == 1000 && newStyle.weight() == 999) // DW weirdness
);
// Some back-ends (GDI) don't support width, ensure these always report 'medium'.
REPORTER_ASSERT(reporter,
newStyle.width() == width ||
newStyle.width() == 5);
}
DEF_TEST(TypefaceStyle, reporter) {
std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("fonts/Em.ttf"));
if (!stream) {
REPORT_FAILURE(reporter, "fonts/Em.ttf", SkString("Cannot load resource"));
return;
}
sk_sp<SkData> data(SkData::MakeFromStream(stream.get(), stream->getLength()));
using SkFS = SkFontStyle;
for (int weight = SkFS::kInvisible_Weight; weight <= SkFS::kExtraBlack_Weight; ++weight) {
TypefaceStyle_test(reporter, weight, 5, data.get());
}
for (int width = SkFS::kUltraCondensed_Width; width <= SkFS::kUltraExpanded_Width; ++width) {
TypefaceStyle_test(reporter, 400, width, data.get());
}
}
DEF_TEST(TypefacePostScriptName, reporter) {
sk_sp<SkTypeface> typeface(MakeResourceAsTypeface("fonts/Em.ttf"));
if (!typeface) {
// Not all SkFontMgr can MakeFromStream().
return;
}
SkString postScriptName;
bool hasName = typeface->getPostScriptName(&postScriptName);
bool hasName2 = typeface->getPostScriptName(nullptr);
REPORTER_ASSERT(reporter, hasName == hasName2);
if (hasName) {
REPORTER_ASSERT(reporter, postScriptName == SkString("Em"));
}
}
DEF_TEST(TypefaceRoundTrip, reporter) {
sk_sp<SkTypeface> typeface(MakeResourceAsTypeface("fonts/7630.otf"));
if (!typeface) {
// Not all SkFontMgr can MakeFromStream().
return;
}
int fontIndex;
std::unique_ptr<SkStreamAsset> stream = typeface->openStream(&fontIndex);
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
sk_sp<SkTypeface> typeface2 = fm->makeFromStream(std::move(stream), fontIndex);
REPORTER_ASSERT(reporter, typeface2);
}
DEF_TEST(FontDescriptorNegativeVariationSerialize, reporter) {
SkFontDescriptor desc;
SkFontArguments::VariationPosition::Coordinate* variation = desc.setVariationCoordinates(1);
variation[0] = { 0, -1.0f };
SkDynamicMemoryWStream stream;
desc.serialize(&stream);
SkFontDescriptor descD;
SkFontDescriptor::Deserialize(stream.detachAsStream().get(), &descD);
if (descD.getVariationCoordinateCount() != 1) {
REPORT_FAILURE(reporter, "descD.getVariationCoordinateCount() != 1", SkString());
return;
}
REPORTER_ASSERT(reporter, descD.getVariation()[0].value == -1.0f);
};
DEF_TEST(FontDescriptorDeserializeOldFormat, reporter) {
// From ossfuzz:26254
const uint8_t old_serialized_desc[] = {
0x0, //style
0xff, 0xfb, 0x0, 0x0, 0x0, // kFontAxes
0x0, // coordinateCount
0xff, 0xff, 0x0, 0x0, 0x0, // kSentinel
0x0, // data length
};
SkMemoryStream stream(old_serialized_desc, sizeof(old_serialized_desc), false);
SkFontDescriptor desc;
if (!SkFontDescriptor::Deserialize(&stream, &desc)) {
REPORT_FAILURE(reporter, "!SkFontDescriptor::Deserialize(&stream, &desc)",
SkString("bytes should be recognized unless removing support"));
return;
}
// This call should not crash and should not return a valid SkFontData.
std::unique_ptr<SkFontData> data = desc.maybeAsSkFontData();
REPORTER_ASSERT(reporter, !data);
};
DEF_TEST(TypefaceAxes, reporter) {
std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
if (!distortable) {
REPORT_FAILURE(reporter, "distortable", SkString());
return;
}
constexpr int numberOfAxesInDistortable = 1;
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
// The position may be over specified. If there are multiple values for a given axis,
// ensure the last one since that's what css-fonts-4 requires.
const SkFontArguments::VariationPosition::Coordinate position[] = {
{ SkSetFourByteTag('w','g','h','t'), 1.618033988749895f },
{ SkSetFourByteTag('w','g','h','t'), SK_ScalarSqrt2 },
};
SkFontArguments params;
params.setVariationDesignPosition({position, SK_ARRAY_COUNT(position)});
// TODO: if axes are set and the back-end doesn't support them, should we create the typeface?
sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), params);
if (!typeface) {
return; // Not all SkFontMgr can makeFromStream().
}
int count = typeface->getVariationDesignPosition(nullptr, 0);
if (count == -1) {
return; // The number of axes is unknown.
}
REPORTER_ASSERT(reporter, count == numberOfAxesInDistortable);
// Variable font conservative bounds don't vary, so ensure they aren't reported.
REPORTER_ASSERT(reporter, typeface->getBounds().isEmpty());
SkFontArguments::VariationPosition::Coordinate positionRead[numberOfAxesInDistortable];
count = typeface->getVariationDesignPosition(positionRead, SK_ARRAY_COUNT(positionRead));
if (count == -1) {
return; // The position cannot be determined.
}
REPORTER_ASSERT(reporter, count == SK_ARRAY_COUNT(positionRead));
REPORTER_ASSERT(reporter, positionRead[0].axis == position[1].axis);
// Convert to fixed for "almost equal".
SkFixed fixedRead = SkScalarToFixed(positionRead[0].value);
SkFixed fixedOriginal = SkScalarToFixed(position[1].value);
REPORTER_ASSERT(reporter, SkTAbs(fixedRead - fixedOriginal) < 2 || // variation set correctly
SkTAbs(fixedRead - SK_Fixed1 ) < 2); // variation remained default
}
DEF_TEST(TypefaceVariationIndex, reporter) {
std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
if (!distortable) {
REPORT_FAILURE(reporter, "distortable", SkString());
return;
}
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
SkFontArguments params;
// The first named variation position in Distortable is 'Thin'.
params.setCollectionIndex(0x00010000);
sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), params);
if (!typeface) {
// FreeType is the only weird thing that supports this, Skia just needs to make sure if it
// gets one of these things make sense.
return;
}
int count = typeface->getVariationDesignPosition(nullptr, 0);
if (!(count == 1)) {
REPORT_FAILURE(reporter, "count == 1", SkString());
return;
}
SkFontArguments::VariationPosition::Coordinate positionRead[1];
count = typeface->getVariationDesignPosition(positionRead, SK_ARRAY_COUNT(positionRead));
if (count == -1) {
return;
}
if (!(count == 1)) {
REPORT_FAILURE(reporter, "count == 1", SkString());
return;
}
REPORTER_ASSERT(reporter, positionRead[0].axis == SkSetFourByteTag('w','g','h','t'));
REPORTER_ASSERT(reporter, positionRead[0].value == 0.5);
}
DEF_TEST(Typeface, reporter) {
sk_sp<SkTypeface> t1(SkTypeface::MakeFromName(nullptr, SkFontStyle()));
sk_sp<SkTypeface> t2(SkTypeface::MakeDefault());
REPORTER_ASSERT(reporter, SkTypeface::Equal(t1.get(), t2.get()));
REPORTER_ASSERT(reporter, SkTypeface::Equal(nullptr, t1.get()));
REPORTER_ASSERT(reporter, SkTypeface::Equal(nullptr, t2.get()));
REPORTER_ASSERT(reporter, SkTypeface::Equal(t1.get(), nullptr));
REPORTER_ASSERT(reporter, SkTypeface::Equal(t2.get(), nullptr));
}
DEF_TEST(TypefaceAxesParameters, reporter) {
std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
if (!distortable) {
REPORT_FAILURE(reporter, "distortable", SkString());
return;
}
constexpr int numberOfAxesInDistortable = 1;
constexpr SkScalar minAxisInDistortable = 0.5;
constexpr SkScalar defAxisInDistortable = 1;
constexpr SkScalar maxAxisInDistortable = 2;
constexpr bool axisIsHiddenInDistortable = true;
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
SkFontArguments params;
sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(distortable), params);
if (!typeface) {
return; // Not all SkFontMgr can makeFromStream().
}
SkFontParameters::Variation::Axis parameter[numberOfAxesInDistortable];
int count = typeface->getVariationDesignParameters(parameter, SK_ARRAY_COUNT(parameter));
if (count == -1) {
return;
}
REPORTER_ASSERT(reporter, count == SK_ARRAY_COUNT(parameter));
REPORTER_ASSERT(reporter, parameter[0].min == minAxisInDistortable);
REPORTER_ASSERT(reporter, parameter[0].def == defAxisInDistortable);
REPORTER_ASSERT(reporter, parameter[0].max == maxAxisInDistortable);
REPORTER_ASSERT(reporter, parameter[0].tag == SkSetFourByteTag('w','g','h','t'));
// This seems silly, but allows MSAN to ensure that isHidden is initialized.
// With GDI or before macOS 10.12, Win10, or FreeType 2.8.1 the API for hidden is missing.
REPORTER_ASSERT(reporter, parameter[0].isHidden() == axisIsHiddenInDistortable ||
parameter[0].isHidden() == false);
}
static bool count_proc(SkTypeface* face, void* ctx) {
int* count = static_cast<int*>(ctx);
*count = *count + 1;
return false;
}
static int count(skiatest::Reporter* reporter, const SkTypefaceCache& cache) {
int count = 0;
sk_sp<SkTypeface> none = cache.findByProcAndRef(count_proc, &count);
REPORTER_ASSERT(reporter, none == nullptr);
return count;
}
DEF_TEST(TypefaceCache, reporter) {
sk_sp<SkTypeface> t1(TestEmptyTypeface::Make());
{
SkTypefaceCache cache;
REPORTER_ASSERT(reporter, count(reporter, cache) == 0);
{
sk_sp<SkTypeface> t0(TestEmptyTypeface::Make());
cache.add(t0);
REPORTER_ASSERT(reporter, count(reporter, cache) == 1);
cache.add(t1);
REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
cache.purgeAll();
REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
}
REPORTER_ASSERT(reporter, count(reporter, cache) == 2);
cache.purgeAll();
REPORTER_ASSERT(reporter, count(reporter, cache) == 1);
}
REPORTER_ASSERT(reporter, t1->unique());
}
static void check_serialize_behaviors(sk_sp<SkTypeface> tf, bool isLocalData,
skiatest::Reporter* reporter) {
if (!tf) {
return;
}
auto data0 = tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
auto data1 = tf->serialize(SkTypeface::SerializeBehavior::kDontIncludeData);
auto data2 = tf->serialize(SkTypeface::SerializeBehavior::kIncludeDataIfLocal);
REPORTER_ASSERT(reporter, data0->size() >= data1->size());
if (isLocalData) {
REPORTER_ASSERT(reporter, data0->equals(data2.get()));
} else {
REPORTER_ASSERT(reporter, data1->equals(data2.get()));
}
}
DEF_TEST(Typeface_serialize, reporter) {
check_serialize_behaviors(SkTypeface::MakeDefault(), false, reporter);
check_serialize_behaviors(SkTypeface::MakeFromStream(
GetResourceAsStream("fonts/Distortable.ttf")),
true, reporter);
}
DEF_TEST(Typeface_glyph_to_char, reporter) {
SkFont font(ToolUtils::emoji_typeface(), 12);
SkASSERT(font.getTypeface());
char const * text = ToolUtils::emoji_sample_text();
size_t const textLen = strlen(text);
size_t const codepointCount = SkUTF::CountUTF8(text, textLen);
char const * const textEnd = text + textLen;
std::unique_ptr<SkUnichar[]> originalCodepoints(new SkUnichar[codepointCount]);
for (size_t i = 0; i < codepointCount; ++i) {
originalCodepoints[i] = SkUTF::NextUTF8(&text, textEnd);
}
std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[codepointCount]);
font.unicharsToGlyphs(originalCodepoints.get(), codepointCount, glyphs.get());
std::unique_ptr<SkUnichar[]> newCodepoints(new SkUnichar[codepointCount]);
SkFontPriv::GlyphsToUnichars(font, glyphs.get(), codepointCount, newCodepoints.get());
SkString familyName;
font.getTypeface()->getFamilyName(&familyName);
for (size_t i = 0; i < codepointCount; ++i) {
#if defined(SK_BUILD_FOR_WIN)
// GDI does not support character to glyph mapping outside BMP.
if (gSkFontMgr_DefaultFactory == &SkFontMgr_New_GDI &&
0xFFFF < originalCodepoints[i] && newCodepoints[i] == 0)
{
continue;
}
#endif
// If two codepoints map to the same glyph then this assert is not valid.
// However, the emoji test font should never have multiple characters map to the same glyph.
REPORTER_ASSERT(reporter, originalCodepoints[i] == newCodepoints[i],
"name:%s i:%zu original:%d new:%d glyph:%d", familyName.c_str(), i,
originalCodepoints[i], newCodepoints[i], glyphs[i]);
}
}
// This test makes sure the legacy typeface creation does not lose its specified
// style. See https://bugs.chromium.org/p/skia/issues/detail?id=8447 for more
// context.
DEF_TEST(LegacyMakeTypeface, reporter) {
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
sk_sp<SkTypeface> typeface1 = fm->legacyMakeTypeface(nullptr, SkFontStyle::Italic());
sk_sp<SkTypeface> typeface2 = fm->legacyMakeTypeface(nullptr, SkFontStyle::Bold());
sk_sp<SkTypeface> typeface3 = fm->legacyMakeTypeface(nullptr, SkFontStyle::BoldItalic());
REPORTER_ASSERT(reporter, typeface1->isItalic());
REPORTER_ASSERT(reporter, !typeface1->isBold());
REPORTER_ASSERT(reporter, !typeface2->isItalic());
REPORTER_ASSERT(reporter, typeface2->isBold());
REPORTER_ASSERT(reporter, typeface3->isItalic());
REPORTER_ASSERT(reporter, typeface3->isBold());
}