6fbd92db65
SkFontDescriptor and its serialization and usage are updated to better reflect how variations should be serialized. In addition this begins teasing apart SkFontData since it is now mostly an artifact of how the FreeType port works than anything else. This also removes SkTypeface::MakeFromFontData since it is no longer used and since SkFontData (which it takes as a parameter) was never public. SkFontMgr::makeFromFontData now only exists to support older skps and may be removed in the future. Change-Id: I266bd5e87de85788661cdf5c571592ea1f2ae669 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/307344 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Ben Wagner <bungeman@google.com>
380 lines
15 KiB
C++
380 lines
15 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(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(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);
|
|
|
|
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());
|
|
}
|