aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn18
-rw-r--r--gm/fontmgr.cpp7
-rw-r--r--public.bzl6
-rw-r--r--resources/fonts/cbdt.ttfbin0 -> 18020 bytes
-rw-r--r--resources/fonts/colr.ttfbin0 -> 2996 bytes
-rw-r--r--resources/fonts/sbix.ttfbin0 -> 17956 bytes
-rw-r--r--resources/fonts/svg/diamond.svg22
-rw-r--r--resources/fonts/svg/empty.svg10
-rw-r--r--resources/fonts/svg/notdef.svg29
-rw-r--r--resources/fonts/svg/smile.svg44
-rw-r--r--src/ports/SkFontHost_win.cpp7
-rw-r--r--tools/fonts/SkTestFontMgr.cpp28
-rw-r--r--tools/fonts/SkTestSVGTypeface.cpp1302
-rw-r--r--tools/fonts/SkTestSVGTypeface.h132
-rw-r--r--tools/fonts/SkTestTypeface.cpp (renamed from tools/fonts/SkTestScalerContext.cpp)17
-rw-r--r--tools/fonts/SkTestTypeface.h (renamed from tools/fonts/SkTestScalerContext.h)22
-rw-r--r--tools/fonts/create_test_font_color.cpp37
-rw-r--r--tools/fonts/sk_tool_utils_font.cpp41
-rw-r--r--tools/sk_tool_utils.cpp24
-rw-r--r--tools/sk_tool_utils.h13
20 files changed, 1705 insertions, 54 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 1c49e51921..8ddd6bd0fe 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1281,7 +1281,11 @@ if (skia_enable_tools) {
"tools/debugger/SkObjectParser.cpp",
"tools/fonts/SkRandomScalerContext.cpp",
"tools/fonts/SkTestFontMgr.cpp",
- "tools/fonts/SkTestScalerContext.cpp",
+ "tools/fonts/SkTestFontMgr.h",
+ "tools/fonts/SkTestSVGTypeface.cpp",
+ "tools/fonts/SkTestSVGTypeface.h",
+ "tools/fonts/SkTestTypeface.cpp",
+ "tools/fonts/SkTestTypeface.h",
"tools/fonts/sk_tool_utils_font.cpp",
"tools/picture_utils.cpp",
"tools/random_parse_path.cpp",
@@ -1301,6 +1305,7 @@ if (skia_enable_tools) {
}
deps = [
":common_flags",
+ ":experimental_svg_model",
":flags",
"//third_party/libpng",
]
@@ -1671,6 +1676,17 @@ if (skia_enable_tools) {
]
}
+ test_app("create_test_font_color") {
+ sources = [
+ "tools/fonts/create_test_font_color.cpp",
+ ]
+ deps = [
+ ":flags",
+ ":skia",
+ ":tool_utils"
+ ]
+ }
+
test_app("get_images_from_skps") {
sources = [
"tools/get_images_from_skps.cpp",
diff --git a/gm/fontmgr.cpp b/gm/fontmgr.cpp
index a0b2f5b44a..58c569c300 100644
--- a/gm/fontmgr.cpp
+++ b/gm/fontmgr.cpp
@@ -9,6 +9,7 @@
#include "sk_tool_utils.h"
#include "SkCanvas.h"
#include "SkFontMgr.h"
+#include "SkPath.h"
#include "SkGraphics.h"
#include "SkTypeface.h"
@@ -224,6 +225,7 @@ public:
fontBounds.offset(x, y);
SkPaint boundsPaint(glyphPaint);
boundsPaint.setColor(boundsColor);
+ boundsPaint.setStyle(SkPaint::kStroke_Style);
canvas->drawRect(fontBounds, boundsPaint);
SkPaint::FontMetrics fm;
@@ -264,6 +266,10 @@ public:
}
SkGlyphID str[] = { left, right, top, bottom };
for (size_t i = 0; i < SK_ARRAY_COUNT(str); ++i) {
+ SkPath path;
+ glyphPaint.getTextPath(&str[i], sizeof(str[0]), x, y, &path);
+ SkPaint::Style style = path.isEmpty() ? SkPaint::kFill_Style : SkPaint::kStroke_Style;
+ glyphPaint.setStyle(style);
canvas->drawText(&str[i], sizeof(str[0]), x, y, glyphPaint);
}
}
@@ -282,7 +288,6 @@ protected:
paint.setAntiAlias(true);
paint.setSubpixelText(true);
paint.setTextSize(100);
- paint.setStyle(SkPaint::kStroke_Style);
paint.setTextScaleX(fScaleX);
paint.setTextSkewX(fSkewX);
diff --git a/public.bzl b/public.bzl
index fbd005bf0f..407ad1ce4c 100644
--- a/public.bzl
+++ b/public.bzl
@@ -457,8 +457,10 @@ DM_SRCS_ALL = struct(
"tools/fonts/SkRandomScalerContext.h",
"tools/fonts/SkTestFontMgr.cpp",
"tools/fonts/SkTestFontMgr.h",
- "tools/fonts/SkTestScalerContext.cpp",
- "tools/fonts/SkTestScalerContext.h",
+ "tools/fonts/SkTestSVGTypeface.cpp",
+ "tools/fonts/SkTestSVGTypeface.h",
+ "tools/fonts/SkTestTypeface.cpp",
+ "tools/fonts/SkTestTypeface.h",
"tools/fonts/sk_tool_utils_font.cpp",
"tools/fonts/test_font_monospace.inc",
"tools/fonts/test_font_sans_serif.inc",
diff --git a/resources/fonts/cbdt.ttf b/resources/fonts/cbdt.ttf
new file mode 100644
index 0000000000..bed498f07e
--- /dev/null
+++ b/resources/fonts/cbdt.ttf
Binary files differ
diff --git a/resources/fonts/colr.ttf b/resources/fonts/colr.ttf
new file mode 100644
index 0000000000..42e2a08096
--- /dev/null
+++ b/resources/fonts/colr.ttf
Binary files differ
diff --git a/resources/fonts/sbix.ttf b/resources/fonts/sbix.ttf
new file mode 100644
index 0000000000..5eed8a917b
--- /dev/null
+++ b/resources/fonts/sbix.ttf
Binary files differ
diff --git a/resources/fonts/svg/diamond.svg b/resources/fonts/svg/diamond.svg
new file mode 100644
index 0000000000..826cb749f3
--- /dev/null
+++ b/resources/fonts/svg/diamond.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- The default glyf will be much smaller than the colr glyf layers. -->
+<svg
+ version="1.1"
+ viewBox="0 0 158.75 211.66667"
+ height="800"
+ width="600">
+ <g
+ transform="translate(0,-85.333351)"
+ id="layer1">
+ <path
+ transform="matrix(1,0,0,1.3773044,0.80180858,-33.549844)"
+ d="m 79.11178,236.39392 c -5.497887,0 -73.0286729,-67.53078 -73.028673,-73.02867 -2e-7,-5.49789 67.530782,-73.028671 73.028669,-73.028672 5.497888,0 73.028674,67.530782 73.028674,73.028672 0,5.49789 -67.530782,73.02867 -73.02867,73.02867 z"
+ id="path5299"
+ style="fill:#ff2a2a;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="matrix(0.60436993,0,0,0.81236352,32.100808,58.741857)"
+ d="m 79.11178,236.39392 c -5.497887,0 -73.0286729,-67.53078 -73.028673,-73.02867 -2e-7,-5.49789 67.530782,-73.028671 73.028669,-73.028672 5.497888,0 73.028674,67.530782 73.028674,73.028672 0,5.49789 -67.530782,73.02867 -73.02867,73.02867 z"
+ id="path5299-5"
+ style="fill:none;stroke:#2b0000;stroke-width:14.19605827;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/resources/fonts/svg/empty.svg b/resources/fonts/svg/empty.svg
new file mode 100644
index 0000000000..d470cc5a4c
--- /dev/null
+++ b/resources/fonts/svg/empty.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Empty, so will not produce a glyf or bitmap entry. -->
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 0 0"
+ height="0"
+ width="0">
+</svg>
diff --git a/resources/fonts/svg/notdef.svg b/resources/fonts/svg/notdef.svg
new file mode 100644
index 0000000000..3fbb9a02ff
--- /dev/null
+++ b/resources/fonts/svg/notdef.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- In the colr format should be filled with the current paint. -->
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 158.75 211.66667"
+ height="800"
+ width="600">
+ <g
+ transform="translate(0,-85.333351)"
+ id="layer1">
+ <rect
+ y="97.374527"
+ x="4.3011618"
+ height="195.32433"
+ width="150.42305"
+ id="rect4639"
+ style="fill:none;stroke:#2b0000;stroke-width:6.61458349;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ id="path5186"
+ d="M 14.967094,109.37681 144.05828,283.63654"
+ style="fill:none;stroke:#2b0000;stroke-width:6.61458349;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ id="path5188"
+ d="M 146.19643,107.77319 14.699822,282.83473"
+ style="fill:none;stroke:#2b0000;stroke-width:6.61458349;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/resources/fonts/svg/smile.svg b/resources/fonts/svg/smile.svg
new file mode 100644
index 0000000000..41d26b5e29
--- /dev/null
+++ b/resources/fonts/svg/smile.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- In the colr format the eyes and mouth should be filled with the current paint. -->
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 211.66666 211.66667"
+ height="800"
+ width="800">
+ <g
+ transform="translate(0,-85.333317)"
+ id="layer1">
+ <circle
+ r="98.273819"
+ cy="192.76852"
+ cx="104.81271"
+ id="path10"
+ style="fill:#000000;stroke-width:0.3084828" />
+ <circle
+ r="90.782074"
+ cy="192.32785"
+ cx="104.37203"
+ id="path12"
+ style="fill:#ffcc00;stroke-width:0.3084828" />
+ <ellipse
+ ry="20.032738"
+ rx="20.410715"
+ cy="159.88458"
+ cx="74.574615"
+ id="path62"
+ style="fill:#2b0000;stroke-width:0.26458332" />
+ <ellipse
+ ry="20.032738"
+ rx="20.410715"
+ cy="158.13309"
+ cx="138.02272"
+ id="path62-7"
+ style="fill:#2b0000;stroke-width:0.26458332" />
+ <path
+ d="m 163.09897,220.67157 a 58.248119,31.234499 0 0 1 -26.87407,29.28648 58.248119,31.234499 0 0 1 -60.863188,0.45572 58.248119,31.234499 0 0 1 -28.388544,-28.87269 l 58.126712,2.01564 z"
+ id="path113"
+ style="fill:#2b0000;stroke-width:0.26458332" />
+ </g>
+</svg>
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index f0eb7b7d9b..abe1550a01 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -830,6 +830,13 @@ uint16_t SkScalerContext_GDI::generateCharToGlyph(SkUnichar utf32) {
HRZM(ScriptItemize(utf16, numWCHAR, maxItems, &sc, nullptr, si, &numItems),
"Could not itemize character.");
+ // Disable any attempt at shaping.
+ // Without this ScriptShape may return 0x80040200 (USP_E_SCRIPT_NOT_IN_FONT)
+ // when all that is desired here is a simple cmap lookup.
+ for (SCRIPT_ITEM& item : si) {
+ item.a.eScript = SCRIPT_UNDEFINED;
+ }
+
// Sometimes ScriptShape cannot find a glyph for a non-BMP and returns 2 space glyphs.
static const int maxGlyphs = 2;
SCRIPT_VISATTR vsa[maxGlyphs];
diff --git a/tools/fonts/SkTestFontMgr.cpp b/tools/fonts/SkTestFontMgr.cpp
index 3b5d1589f2..4513a2f8d1 100644
--- a/tools/fonts/SkTestFontMgr.cpp
+++ b/tools/fonts/SkTestFontMgr.cpp
@@ -8,6 +8,8 @@
#include "SkFontDescriptor.h"
#include "SkTestFontMgr.h"
#include "sk_tool_utils.h"
+#include "SkTestTypeface.h"
+#include "SkTestSVGTypeface.h"
namespace {
@@ -15,6 +17,28 @@ static constexpr const char* kFamilyNames[] = {
"Toy Liberation Sans",
"Toy Liberation Serif",
"Toy Liberation Mono",
+ "Emoji",
+};
+
+class JustOneTypefaceStyleSet final : public SkFontStyleSet {
+public:
+ explicit JustOneTypefaceStyleSet(sk_sp<SkTypeface> typeface) : fTypeface(std::move(typeface)) {}
+ int count() override { return 1; }
+
+ void getStyle(int index, SkFontStyle* style, SkString* name) override {
+ if (style) { *style = SkFontStyle::Normal(); }
+ if (name) { *name = "Normal"; }
+ }
+
+ SkTypeface* createTypeface(int index) override {
+ return SkRef(fTypeface.get());
+ }
+
+ SkTypeface* matchStyle(const SkFontStyle& pattern) override {
+ return this->matchStyleCSS3(pattern);
+ }
+private:
+ sk_sp<SkTypeface> fTypeface;
};
class FontStyleSet final : public SkFontStyleSet {
@@ -69,6 +93,7 @@ public:
fFamilies[0] = sk_make_sp<FontStyleSet>(0);
fFamilies[1] = sk_make_sp<FontStyleSet>(1);
fFamilies[2] = sk_make_sp<FontStyleSet>(2);
+ fFamilies[3] = sk_make_sp<JustOneTypefaceStyleSet>(SkTestSVGTypeface::Default());
}
int onCountFamilies() const override { return SK_ARRAY_COUNT(fFamilies); }
@@ -86,6 +111,7 @@ public:
if (strstr(familyName, "ans")) { return this->createStyleSet(0); }
if (strstr(familyName, "erif")) { return this->createStyleSet(1); }
if (strstr(familyName, "ono")) { return this->createStyleSet(2); }
+ if (strstr(familyName, "oji")) { return this->createStyleSet(3); }
}
return this->createStyleSet(0);
}
@@ -138,7 +164,7 @@ public:
}
private:
- sk_sp<FontStyleSet> fFamilies[3];
+ sk_sp<SkFontStyleSet> fFamilies[4];
};
}
diff --git a/tools/fonts/SkTestSVGTypeface.cpp b/tools/fonts/SkTestSVGTypeface.cpp
new file mode 100644
index 0000000000..a4e3e4975e
--- /dev/null
+++ b/tools/fonts/SkTestSVGTypeface.cpp
@@ -0,0 +1,1302 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Resources.h"
+#include "SkAdvancedTypefaceMetrics.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkData.h"
+#include "SkEncodedImageFormat.h"
+#include "SkFontDescriptor.h"
+#include "SkFontStyle.h"
+#include "SkGeometry.h"
+#include "SkGlyph.h"
+#include "SkImage.h"
+#include "SkImageInfo.h"
+#include "SkMask.h"
+#include "SkMatrix.h"
+#include "SkNoDrawCanvas.h"
+#include "SkOTUtils.h"
+#include "SkPaintPriv.h"
+#include "SkPath.h"
+#include "SkPathPriv.h"
+#include "SkPathEffect.h"
+#include "SkPathOps.h"
+#include "SkPixmap.h"
+#include "SkPointPriv.h"
+#include "SkRRect.h"
+#include "SkSVGDOM.h"
+#include "SkScalerContext.h"
+#include "SkSize.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+#include "SkTestSVGTypeface.h"
+#include "SkTDArray.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+#include <utility>
+
+class SkDescriptor;
+
+SkTestSVGTypeface::SkTestSVGTypeface(const char* name,
+ int upem,
+ const SkPaint::FontMetrics& fontMetrics,
+ const SkSVGTestTypefaceGlyphData* data, int dataCount,
+ const SkFontStyle& style)
+ : SkTypeface(style, false)
+ , fName(name)
+ , fUpem(upem)
+ , fFontMetrics(fontMetrics)
+ , fGlyphs(dataCount)
+{
+ for (int i = 0; i < dataCount; ++i) {
+ const SkSVGTestTypefaceGlyphData& datum = data[i];
+ std::unique_ptr<SkStreamAsset> stream = GetResourceAsStream(datum.fSvgResourcePath);
+ fCMap.set(datum.fUnicode, i);
+ if (!stream) {
+ fGlyphs.emplace_back(nullptr, datum);
+ continue;
+ }
+ sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(*stream.get());
+ if (!svg) {
+ fGlyphs.emplace_back(nullptr, datum);
+ continue;
+ }
+
+ const SkSize& sz = svg->containerSize();
+ if (sz.isEmpty()) {
+ fGlyphs.emplace_back(nullptr, datum);
+ continue;
+ }
+
+ fGlyphs.emplace_back(std::move(svg), datum);
+ }
+}
+
+SkTestSVGTypeface::~SkTestSVGTypeface() {}
+
+SkTestSVGTypeface::Glyph::Glyph(sk_sp<SkSVGDOM> svg, const SkSVGTestTypefaceGlyphData& data)
+ : fSvg(std::move(svg)), fOrigin(data.fOrigin), fAdvance(data.fAdvance) {}
+SkTestSVGTypeface::Glyph::~Glyph() {}
+
+void SkTestSVGTypeface::getAdvance(SkGlyph* glyph) const {
+ SkGlyphID glyphID = glyph->getGlyphID();
+ glyphID = glyphID < fGlyphs.count() ? glyphID : 0;
+
+ glyph->fAdvanceX = fGlyphs[glyphID].fAdvance;
+ glyph->fAdvanceY = 0;
+}
+
+void SkTestSVGTypeface::getFontMetrics(SkPaint::FontMetrics* metrics) const {
+ *metrics = fFontMetrics;
+}
+
+void SkTestSVGTypeface::getPath(SkGlyphID glyphID, SkPath* path) const {
+ path->reset();
+}
+
+void SkTestSVGTypeface::onFilterRec(SkScalerContextRec* rec) const {
+ rec->setHinting(SkPaint::kNo_Hinting);
+}
+
+std::unique_ptr<SkAdvancedTypefaceMetrics> SkTestSVGTypeface::onGetAdvancedMetrics() const {
+ std::unique_ptr<SkAdvancedTypefaceMetrics> info(new SkAdvancedTypefaceMetrics);
+ info->fFontName.set(fName);
+ int glyphCount = this->onCountGlyphs();
+
+ SkTDArray<SkUnichar>& toUnicode = info->fGlyphToUnicode;
+ toUnicode.setCount(glyphCount);
+ SkASSERT(glyphCount == SkToInt(fGlyphs.count()));
+ fCMap.foreach([&toUnicode](const SkUnichar& c, const SkGlyphID& g) {
+ toUnicode[g] = c;
+ });
+ return info;
+}
+
+void SkTestSVGTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const {
+ desc->setFamilyName(fName.c_str());
+ desc->setStyle(this->fontStyle());
+ *isLocal = false;
+}
+
+int SkTestSVGTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const {
+ auto utf8 = (const char*)chars;
+ auto utf16 = (const uint16_t*)chars;
+ auto utf32 = (const SkUnichar*)chars;
+
+ for (int i = 0; i < glyphCount; i++) {
+ SkUnichar ch;
+ switch (encoding) {
+ case kUTF8_Encoding: ch = SkUTF8_NextUnichar(&utf8 ); break;
+ case kUTF16_Encoding: ch = SkUTF16_NextUnichar(&utf16); break;
+ case kUTF32_Encoding: ch = *utf32++; break;
+ }
+ if (glyphs) {
+ SkGlyphID* g = fCMap.find(ch);
+ glyphs[i] = g ? *g : 0;
+ }
+ }
+ return glyphCount;
+}
+
+void SkTestSVGTypeface::onGetFamilyName(SkString* familyName) const {
+ *familyName = fName;
+}
+
+SkTypeface::LocalizedStrings* SkTestSVGTypeface::onCreateFamilyNameIterator() const {
+ SkString familyName(fName);
+ SkString language("und"); //undetermined
+ return new SkOTUtils::LocalizedStrings_SingleName(familyName, language);
+}
+
+class SkTestSVGScalerContext : public SkScalerContext {
+public:
+ SkTestSVGScalerContext(sk_sp<SkTestSVGTypeface> face, const SkScalerContextEffects& effects,
+ const SkDescriptor* desc)
+ : SkScalerContext(std::move(face), effects, desc)
+ {
+ fRec.getSingleMatrix(&fMatrix);
+ SkScalar upem = this->geTestSVGTypeface()->fUpem;
+ fMatrix.preScale(1.f/upem, 1.f/upem);
+ }
+
+protected:
+ SkTestSVGTypeface* geTestSVGTypeface() const {
+ return static_cast<SkTestSVGTypeface*>(this->getTypeface());
+ }
+
+ unsigned generateGlyphCount() override {
+ return this->geTestSVGTypeface()->onCountGlyphs();
+ }
+
+ uint16_t generateCharToGlyph(SkUnichar u) override {
+ uint16_t g;
+ (void) this->geTestSVGTypeface()->onCharsToGlyphs(&u, SkTypeface::kUTF32_Encoding, &g, 1);
+ return g;
+ }
+
+ void generateAdvance(SkGlyph* glyph) override {
+ this->geTestSVGTypeface()->getAdvance(glyph);
+
+ const SkVector advance = fMatrix.mapXY(SkFloatToScalar(glyph->fAdvanceX),
+ SkFloatToScalar(glyph->fAdvanceY));
+ glyph->fAdvanceX = SkScalarToFloat(advance.fX);
+ glyph->fAdvanceY = SkScalarToFloat(advance.fY);
+ }
+
+ void generateMetrics(SkGlyph* glyph) override {
+ SkGlyphID glyphID = glyph->getGlyphID();
+ glyphID = glyphID < this->geTestSVGTypeface()->fGlyphs.count() ? glyphID : 0;
+
+ glyph->zeroMetrics();
+ glyph->fMaskFormat = SkMask::kARGB32_Format;
+ this->generateAdvance(glyph);
+
+ SkTestSVGTypeface::Glyph& glyphData = this->geTestSVGTypeface()->fGlyphs[glyphID];
+ if (!glyphData.fSvg) {
+ return;
+ }
+
+ SkSize containerSize = glyphData.fSvg->containerSize();
+ SkRect newBounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
+ containerSize.fWidth, containerSize.fHeight);
+ fMatrix.mapRect(&newBounds);
+ SkScalar dx = SkFixedToScalar(glyph->getSubXFixed());
+ SkScalar dy = SkFixedToScalar(glyph->getSubYFixed());
+ newBounds.offset(dx, dy);
+
+ SkIRect ibounds;
+ newBounds.roundOut(&ibounds);
+ glyph->fLeft = ibounds.fLeft;
+ glyph->fTop = ibounds.fTop;
+ glyph->fWidth = ibounds.width();
+ glyph->fHeight = ibounds.height();
+ }
+
+ void generateImage(const SkGlyph& glyph) override {
+ SkGlyphID glyphID = glyph.getGlyphID();
+ glyphID = glyphID < this->geTestSVGTypeface()->fGlyphs.count() ? glyphID : 0;
+
+ SkBitmap bm;
+ // TODO: this should be SkImageInfo::MakeS32 when that passes all the tests.
+ bm.installPixels(SkImageInfo::MakeN32(glyph.fWidth, glyph.fHeight, kPremul_SkAlphaType),
+ glyph.fImage, glyph.rowBytes());
+ bm.eraseColor(0);
+
+ SkTestSVGTypeface::Glyph& glyphData = this->geTestSVGTypeface()->fGlyphs[glyphID];
+
+ SkScalar dx = SkFixedToScalar(glyph.getSubXFixed());
+ SkScalar dy = SkFixedToScalar(glyph.getSubYFixed());
+
+ SkCanvas canvas(bm);
+ canvas.translate(-glyph.fLeft, -glyph.fTop);
+ canvas.translate(dx, dy);
+ canvas.concat(fMatrix);
+ canvas.translate(glyphData.fOrigin.fX, -glyphData.fOrigin.fY);
+
+ if (glyphData.fSvg) {
+ glyphData.fSvg->render(&canvas);
+ }
+ }
+
+ void generatePath(SkGlyphID glyph, SkPath* path) override {
+ this->geTestSVGTypeface()->getPath(glyph, path);
+ path->transform(fMatrix);
+ }
+
+ void generateFontMetrics(SkPaint::FontMetrics* metrics) override {
+ this->geTestSVGTypeface()->getFontMetrics(metrics);
+ SkPaintPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY());
+ }
+
+private:
+ SkMatrix fMatrix;
+};
+
+SkScalerContext* SkTestSVGTypeface::onCreateScalerContext(
+ const SkScalerContextEffects& e, const SkDescriptor* desc) const
+{
+ return new SkTestSVGScalerContext(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)), e, desc);
+}
+
+// Recommended that the first four be .notdef, .null, CR, space
+constexpr const static SkSVGTestTypefaceGlyphData gGlyphs[] = {
+ {"fonts/svg/notdef.svg", {100,800}, 800, 0x0}, // .notdef
+ {"fonts/svg/empty.svg", {0,0}, 800, 0x0020}, // space
+ {"fonts/svg/diamond.svg", {100, 800}, 800, 0x2662}, // β™’
+ {"fonts/svg/smile.svg", {0,800}, 800, 0x1F600}, // πŸ˜€
+};
+
+sk_sp<SkTestSVGTypeface> SkTestSVGTypeface::Default() {
+ SkPaint::FontMetrics metrics;
+ metrics.fFlags = SkPaint::FontMetrics::kUnderlineThicknessIsValid_Flag |
+ SkPaint::FontMetrics::kUnderlinePositionIsValid_Flag |
+ SkPaint::FontMetrics::kStrikeoutThicknessIsValid_Flag |
+ SkPaint::FontMetrics::kStrikeoutPositionIsValid_Flag;
+ metrics.fTop = -800;
+ metrics.fAscent = -800;
+ metrics.fDescent = 200;
+ metrics.fBottom = 200;
+ metrics.fLeading = 100;
+ metrics.fAvgCharWidth = 1000;
+ metrics.fMaxCharWidth = 1000;
+ metrics.fXMin = 0;
+ metrics.fXMax = 1000;
+ metrics.fXHeight = 500;
+ metrics.fCapHeight = 700;
+ metrics.fUnderlineThickness = 40;
+ metrics.fUnderlinePosition = 20;
+ metrics.fStrikeoutThickness = 20;
+ metrics.fStrikeoutPosition = -400;
+ return sk_make_sp<SkTestSVGTypeface>("Emoji", 1000, metrics, gGlyphs, SK_ARRAY_COUNT(gGlyphs),
+ SkFontStyle::Normal());
+}
+
+void SkTestSVGTypeface::exportTtxCommon(SkWStream* out, const char* type,
+ const SkTArray<GlyfInfo>* glyfInfo) const
+{
+ int totalGlyphs = fGlyphs.count();
+ out->writeText(" <GlyphOrder>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ out->writeText(" <GlyphID name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\"/>\n");
+ }
+ if (glyfInfo) {
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
+ out->writeText(" <GlyphID name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("l");
+ out->writeHexAsText(j, 4);
+ out->writeText("\"/>\n");
+ ++totalGlyphs;
+ }
+ }
+ }
+ out->writeText(" </GlyphOrder>\n");
+
+ out->writeText(" <head>\n");
+ out->writeText(" <tableVersion value=\"1.0\"/>\n");
+ out->writeText(" <fontRevision value=\"1.0\"/>\n");
+ out->writeText(" <checkSumAdjustment value=\"0xa9c3274\"/>\n");
+ out->writeText(" <magicNumber value=\"0x5f0f3cf5\"/>\n");
+ out->writeText(" <flags value=\"00000000 00011011\"/>\n");
+ out->writeText(" <unitsPerEm value=\"");
+ out->writeDecAsText(fUpem);
+ out->writeText("\"/>\n");
+ out->writeText(" <created value=\"Thu Feb 15 12:55:49 2018\"/>\n");
+ out->writeText(" <modified value=\"Thu Feb 15 12:55:49 2018\"/>\n");
+ // TODO: not recalculated for bitmap fonts?
+ out->writeText(" <xMin value=\"");
+ out->writeScalarAsText(fFontMetrics.fXMin);
+ out->writeText("\"/>\n");
+ out->writeText(" <yMin value=\"");
+ out->writeScalarAsText(-fFontMetrics.fBottom);
+ out->writeText("\"/>\n");
+ out->writeText(" <xMax value=\"");
+ out->writeScalarAsText(fFontMetrics.fXMax);
+ out->writeText("\"/>\n");
+ out->writeText(" <yMax value=\"");
+ out->writeScalarAsText(-fFontMetrics.fTop);
+ out->writeText("\"/>\n");
+
+ char macStyle[16] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+ if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
+ macStyle[0xF - 0x0] = '1'; // Bold
+ }
+ switch (this->fontStyle().slant()) {
+ case SkFontStyle::kUpright_Slant:
+ break;
+ case SkFontStyle::kItalic_Slant:
+ macStyle[0xF - 0x1] = '1'; // Italic
+ break;
+ case SkFontStyle::kOblique_Slant:
+ macStyle[0xF - 0x1] = '1'; // Italic
+ break;
+ default:
+ SK_ABORT("Unknown slant.");
+ }
+ if (this->fontStyle().width() <= SkFontStyle::kCondensed_Width) {
+ macStyle[0xF - 0x5] = '1'; // Condensed
+ } else if (this->fontStyle().width() >= SkFontStyle::kExpanded_Width) {
+ macStyle[0xF - 0x6] = '1'; // Extended
+ }
+ out->writeText(" <macStyle value=\"");
+ out->write(macStyle, 8);
+ out->writeText(" ");
+ out->write(macStyle + 8, 8);
+ out->writeText("\"/>\n");
+ out->writeText(" <lowestRecPPEM value=\"8\"/>\n");
+ out->writeText(" <fontDirectionHint value=\"2\"/>\n");
+ out->writeText(" <indexToLocFormat value=\"0\"/>\n");
+ out->writeText(" <glyphDataFormat value=\"0\"/>\n");
+ out->writeText(" </head>\n");
+
+ out->writeText(" <hhea>\n");
+ out->writeText(" <tableVersion value=\"0x00010000\"/>\n");
+ out->writeText(" <ascent value=\"");
+ out->writeDecAsText(-fFontMetrics.fAscent);
+ out->writeText("\"/>\n");
+ out->writeText(" <descent value=\"");
+ out->writeDecAsText(-fFontMetrics.fDescent);
+ out->writeText("\"/>\n");
+ out->writeText(" <lineGap value=\"");
+ out->writeDecAsText(fFontMetrics.fLeading);
+ out->writeText("\"/>\n");
+ out->writeText(" <advanceWidthMax value=\"0\"/>\n");
+ out->writeText(" <minLeftSideBearing value=\"0\"/>\n");
+ out->writeText(" <minRightSideBearing value=\"0\"/>\n");
+ out->writeText(" <xMaxExtent value=\"");
+ out->writeScalarAsText(fFontMetrics.fXMax - fFontMetrics.fXMin);
+ out->writeText("\"/>\n");
+ out->writeText(" <caretSlopeRise value=\"1\"/>\n");
+ out->writeText(" <caretSlopeRun value=\"0\"/>\n");
+ out->writeText(" <caretOffset value=\"0\"/>\n");
+ out->writeText(" <reserved0 value=\"0\"/>\n");
+ out->writeText(" <reserved1 value=\"0\"/>\n");
+ out->writeText(" <reserved2 value=\"0\"/>\n");
+ out->writeText(" <reserved3 value=\"0\"/>\n");
+ out->writeText(" <metricDataFormat value=\"0\"/>\n");
+ out->writeText(" <numberOfHMetrics value=\"0\"/>\n");
+ out->writeText(" </hhea>\n");
+
+ // Some of this table is going to be re-calculated, but we have to write it out anyway.
+ out->writeText(" <maxp>\n");
+ out->writeText(" <tableVersion value=\"0x10000\"/>\n");
+ out->writeText(" <numGlyphs value=\"");
+ out->writeDecAsText(totalGlyphs);
+ out->writeText("\"/>\n");
+ out->writeText(" <maxPoints value=\"4\"/>\n");
+ out->writeText(" <maxContours value=\"1\"/>\n");
+ out->writeText(" <maxCompositePoints value=\"0\"/>\n");
+ out->writeText(" <maxCompositeContours value=\"0\"/>\n");
+ out->writeText(" <maxZones value=\"1\"/>\n");
+ out->writeText(" <maxTwilightPoints value=\"0\"/>\n");
+ out->writeText(" <maxStorage value=\"0\"/>\n");
+ out->writeText(" <maxFunctionDefs value=\"10\"/>\n");
+ out->writeText(" <maxInstructionDefs value=\"0\"/>\n");
+ out->writeText(" <maxStackElements value=\"512\"/>\n");
+ out->writeText(" <maxSizeOfInstructions value=\"24\"/>\n");
+ out->writeText(" <maxComponentElements value=\"0\"/>\n");
+ out->writeText(" <maxComponentDepth value=\"0\"/>\n");
+ out->writeText(" </maxp>\n");
+
+ out->writeText(" <OS_2>\n");
+ out->writeText(" <version value=\"4\"/>\n");
+ out->writeText(" <xAvgCharWidth value=\"");
+ out->writeScalarAsText(fFontMetrics.fAvgCharWidth);
+ out->writeText("\"/>\n");
+ out->writeText(" <usWeightClass value=\"");
+ out->writeDecAsText(this->fontStyle().weight());
+ out->writeText("\"/>\n");
+ out->writeText(" <usWidthClass value=\"");
+ out->writeDecAsText(this->fontStyle().width());
+ out->writeText("\"/>\n");
+ out->writeText(" <fsType value=\"00000000 00000000\"/>\n");
+ out->writeText(" <ySubscriptXSize value=\"665\"/>\n");
+ out->writeText(" <ySubscriptYSize value=\"716\"/>\n");
+ out->writeText(" <ySubscriptXOffset value=\"0\"/>\n");
+ out->writeText(" <ySubscriptYOffset value=\"143\"/>\n");
+ out->writeText(" <ySuperscriptXSize value=\"665\"/>\n");
+ out->writeText(" <ySuperscriptYSize value=\"716\"/>\n");
+ out->writeText(" <ySuperscriptXOffset value=\"0\"/>\n");
+ out->writeText(" <ySuperscriptYOffset value=\"491\"/>\n");
+ out->writeText(" <yStrikeoutSize value=\"");
+ out->writeScalarAsText(fFontMetrics.fStrikeoutThickness);
+ out->writeText("\"/>\n");
+ out->writeText(" <yStrikeoutPosition value=\"");
+ out->writeScalarAsText(-fFontMetrics.fStrikeoutPosition);
+ out->writeText("\"/>\n");
+ out->writeText(" <sFamilyClass value=\"0\"/>\n");
+ out->writeText(" <panose>\n");
+ out->writeText(" <bFamilyType value=\"0\"/>\n");
+ out->writeText(" <bSerifStyle value=\"0\"/>\n");
+ out->writeText(" <bWeight value=\"0\"/>\n");
+ out->writeText(" <bProportion value=\"0\"/>\n");
+ out->writeText(" <bContrast value=\"0\"/>\n");
+ out->writeText(" <bStrokeVariation value=\"0\"/>\n");
+ out->writeText(" <bArmStyle value=\"0\"/>\n");
+ out->writeText(" <bLetterForm value=\"0\"/>\n");
+ out->writeText(" <bMidline value=\"0\"/>\n");
+ out->writeText(" <bXHeight value=\"0\"/>\n");
+ out->writeText(" </panose>\n");
+ out->writeText(" <ulUnicodeRange1 value=\"00000000 00000000 00000000 00000001\"/>\n");
+ out->writeText(" <ulUnicodeRange2 value=\"00010000 00000000 00000000 00000000\"/>\n");
+ out->writeText(" <ulUnicodeRange3 value=\"00000000 00000000 00000000 00000000\"/>\n");
+ out->writeText(" <ulUnicodeRange4 value=\"00000000 00000000 00000000 00000000\"/>\n");
+ out->writeText(" <achVendID value=\"Skia\"/>\n");
+ char fsSelection[16] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+ fsSelection[0xF - 0x7] = '1'; // Use typo metrics
+ if (this->fontStyle().weight() >= SkFontStyle::Bold().weight()) {
+ fsSelection[0xF - 0x5] = '1'; // Bold
+ }
+ switch (this->fontStyle().slant()) {
+ case SkFontStyle::kUpright_Slant:
+ if (this->fontStyle().weight() < SkFontStyle::Bold().weight()) {
+ fsSelection[0xF - 0x6] = '1'; // Not bold or italic, is regular
+ }
+ break;
+ case SkFontStyle::kItalic_Slant:
+ fsSelection[0xF - 0x0] = '1'; // Italic
+ break;
+ case SkFontStyle::kOblique_Slant:
+ fsSelection[0xF - 0x0] = '1'; // Italic
+ fsSelection[0xF - 0x9] = '1'; // Oblique
+ break;
+ default:
+ SK_ABORT("Unknown slant.");
+ }
+ out->writeText(" <fsSelection value=\"");
+ out->write(fsSelection, 8);
+ out->writeText(" ");
+ out->write(fsSelection + 8, 8);
+ out->writeText("\"/>\n");
+ out->writeText(" <usFirstCharIndex value=\"0\"/>\n");
+ out->writeText(" <usLastCharIndex value=\"0\"/>\n");
+ out->writeText(" <sTypoAscender value=\"");
+ out->writeScalarAsText(-fFontMetrics.fAscent);
+ out->writeText("\"/>\n");
+ out->writeText(" <sTypoDescender value=\"");
+ out->writeScalarAsText(-fFontMetrics.fDescent);
+ out->writeText("\"/>\n");
+ out->writeText(" <sTypoLineGap value=\"");
+ out->writeScalarAsText(fFontMetrics.fLeading);
+ out->writeText("\"/>\n");
+ out->writeText(" <usWinAscent value=\"");
+ out->writeScalarAsText(-fFontMetrics.fAscent);
+ out->writeText("\"/>\n");
+ out->writeText(" <usWinDescent value=\"");
+ out->writeScalarAsText(fFontMetrics.fDescent);
+ out->writeText("\"/>\n");
+ out->writeText(" <ulCodePageRange1 value=\"00000000 00000000 00000000 00000000\"/>\n");
+ out->writeText(" <ulCodePageRange2 value=\"00000000 00000000 00000000 00000000\"/>\n");
+ out->writeText(" <sxHeight value=\"");
+ out->writeScalarAsText(fFontMetrics.fXHeight);
+ out->writeText("\"/>\n");
+ out->writeText(" <sCapHeight value=\"");
+ out->writeScalarAsText(fFontMetrics.fCapHeight);
+ out->writeText("\"/>\n");
+ out->writeText(" <usDefaultChar value=\"0\"/>\n");
+ out->writeText(" <usBreakChar value=\"32\"/>\n");
+ out->writeText(" <usMaxContext value=\"0\"/>\n");
+ out->writeText(" </OS_2>\n");
+
+ out->writeText(" <hmtx>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ out->writeText(" <mtx name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\" width=\"");
+ out->writeDecAsText(fGlyphs[i].fAdvance);
+ out->writeText("\" lsb=\"");
+ int lsb = fGlyphs[i].fOrigin.fX;
+ if (glyfInfo) {
+ lsb += (*glyfInfo)[i].fBounds.fLeft;
+ }
+ out->writeDecAsText(lsb);
+ out->writeText("\"/>\n");
+ }
+ if (glyfInfo) {
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ for (int j = 0; j < (*glyfInfo)[i].fLayers.count(); ++j) {
+ out->writeText(" <mtx name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("l");
+ out->writeHexAsText(j, 4);
+ out->writeText("\" width=\"");
+ out->writeDecAsText(fGlyphs[i].fAdvance);
+ out->writeText("\" lsb=\"");
+ int32_t lsb = fGlyphs[i].fOrigin.fX + (*glyfInfo)[i].fLayers[j].fBounds.fLeft;
+ out->writeDecAsText(lsb);
+ out->writeText("\"/>\n");
+ }
+ }
+ }
+ out->writeText(" </hmtx>\n");
+
+ bool hasNonBMP = false;
+ out->writeText(" <cmap>\n");
+ out->writeText(" <tableVersion version=\"0\"/>\n");
+ out->writeText(" <cmap_format_4 platformID=\"3\" platEncID=\"1\" language=\"0\">\n");
+ fCMap.foreach([&out, &hasNonBMP](const SkUnichar& c, const SkGlyphID& g) {
+ if (0xFFFF < c) {
+ hasNonBMP = true;
+ return;
+ }
+ out->writeText(" <map code=\"0x");
+ out->writeHexAsText(c, 4);
+ out->writeText("\" name=\"glyf");
+ out->writeHexAsText(g, 4);
+ out->writeText("\"/>\n");
+ });
+ out->writeText(" </cmap_format_4>\n");
+ if (hasNonBMP) {
+ out->writeText(" <cmap_format_12 platformID=\"3\" platEncID=\"10\" format=\"12\" reserved=\"0\" length=\"1\" language=\"0\" nGroups=\"0\">\n");
+ fCMap.foreach([&out](const SkUnichar& c, const SkGlyphID& g) {
+ out->writeText(" <map code=\"0x");
+ out->writeHexAsText(c, 6);
+ out->writeText("\" name=\"glyf");
+ out->writeHexAsText(g, 4);
+ out->writeText("\"/>\n");
+ });
+ out->writeText(" </cmap_format_12>\n");
+ }
+ out->writeText(" </cmap>\n");
+
+ out->writeText(" <name>\n");
+ out->writeText(" <namerecord nameID=\"1\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
+ out->writeText(" ");
+ out->writeText(fName.c_str());
+ out->writeText(" ");
+ out->writeText(type);
+ out->writeText("\n");
+ out->writeText(" </namerecord>\n");
+ out->writeText(" <namerecord nameID=\"2\" platformID=\"3\" platEncID=\"1\" langID=\"0x409\">\n");
+ out->writeText(" Regular\n");
+ out->writeText(" </namerecord>\n");
+ out->writeText(" </name>\n");
+
+ out->writeText(" <post>\n");
+ out->writeText(" <formatType value=\"3.0\"/>\n");
+ out->writeText(" <italicAngle value=\"0.0\"/>\n");
+ out->writeText(" <underlinePosition value=\"");
+ out->writeScalarAsText(fFontMetrics.fUnderlinePosition);
+ out->writeText("\"/>\n");
+ out->writeText(" <underlineThickness value=\"");
+ out->writeScalarAsText(fFontMetrics.fUnderlineThickness);
+ out->writeText("\"/>\n");
+ out->writeText(" <isFixedPitch value=\"0\"/>\n");
+ out->writeText(" <minMemType42 value=\"0\"/>\n");
+ out->writeText(" <maxMemType42 value=\"0\"/>\n");
+ out->writeText(" <minMemType1 value=\"0\"/>\n");
+ out->writeText(" <maxMemType1 value=\"0\"/>\n");
+ out->writeText(" </post>\n");
+}
+
+void SkTestSVGTypeface::exportTtxCbdt(SkWStream* out) const {
+ out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+ this->exportTtxCommon(out, "CBDT");
+
+ int strikeSizes[3] = { 16, 64, 128 };
+
+ SkPaint paint;
+ paint.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ out->writeText(" <CBDT>\n");
+ out->writeText(" <header version=\"2.0\"/>\n");
+ for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
+ paint.setTextSize(strikeSizes[strikeIndex]);
+ out->writeText(" <strikedata index=\"");
+ out->writeDecAsText(strikeIndex);
+ out->writeText("\">\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ SkGlyphID gid = i;
+ SkScalar advance;
+ SkRect bounds;
+ paint.getTextWidths(&gid, sizeof(gid), &advance, &bounds);
+ SkIRect ibounds = bounds.roundOut();
+ if (ibounds.isEmpty()) {
+ continue;
+ }
+ SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
+ sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
+ SkASSERT(surface);
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->clear(0);
+ SkPixmap pix;
+ surface->peekPixels(&pix);
+ canvas->drawText(&gid, sizeof(gid), -bounds.fLeft, -bounds.fTop, paint);
+ canvas->flush();
+ sk_sp<SkImage> image = surface->makeImageSnapshot();
+ sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+
+ out->writeText(" <cbdt_bitmap_format_17 name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\">\n");
+ out->writeText(" <SmallGlyphMetrics>\n");
+ out->writeText(" <height value=\"");
+ out->writeDecAsText(image->height());
+ out->writeText("\"/>\n");
+ out->writeText(" <width value=\"");
+ out->writeDecAsText(image->width());
+ out->writeText("\"/>\n");
+ out->writeText(" <BearingX value=\"");
+ out->writeDecAsText(bounds.fLeft);
+ out->writeText("\"/>\n");
+ out->writeText(" <BearingY value=\"");
+ out->writeScalarAsText(-bounds.fTop);
+ out->writeText("\"/>\n");
+ out->writeText(" <Advance value=\"");
+ out->writeScalarAsText(advance);
+ out->writeText("\"/>\n");
+ out->writeText(" </SmallGlyphMetrics>\n");
+ out->writeText(" <rawimagedata>");
+ uint8_t const * bytes = data->bytes();
+ for (size_t i = 0; i < data->size(); ++i) {
+ if ((i % 0x10) == 0x0) {
+ out->writeText("\n ");
+ } else if (((i - 1) % 0x4) == 0x3) {
+ out->writeText(" ");
+ }
+ out->writeHexAsText(bytes[i], 2);
+ }
+ out->writeText("\n");
+ out->writeText(" </rawimagedata>\n");
+ out->writeText(" </cbdt_bitmap_format_17>\n");
+ }
+ out->writeText(" </strikedata>\n");
+ }
+ out->writeText(" </CBDT>\n");
+
+ SkPaint::FontMetrics fm;
+ out->writeText(" <CBLC>\n");
+ out->writeText(" <header version=\"2.0\"/>\n");
+ for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
+ paint.setTextSize(strikeSizes[strikeIndex]);
+ paint.getFontMetrics(&fm);
+ out->writeText(" <strike index=\"");
+ out->writeDecAsText(strikeIndex);
+ out->writeText("\">\n");
+ out->writeText(" <bitmapSizeTable>\n");
+ out->writeText(" <sbitLineMetrics direction=\"hori\">\n");
+ out->writeText(" <ascender value=\"");
+ out->writeScalarAsText(-fm.fTop);
+ out->writeText("\"/>\n");
+ out->writeText(" <descender value=\"");
+ out->writeScalarAsText(-fm.fBottom);
+ out->writeText("\"/>\n");
+ out->writeText(" <widthMax value=\"");
+ out->writeScalarAsText(fm.fXMax - fm.fXMin);
+ out->writeText("\"/>\n");
+ out->writeText(" <caretSlopeNumerator value=\"0\"/>\n");
+ out->writeText(" <caretSlopeDenominator value=\"0\"/>\n");
+ out->writeText(" <caretOffset value=\"0\"/>\n");
+ out->writeText(" <minOriginSB value=\"0\"/>\n");
+ out->writeText(" <minAdvanceSB value=\"0\"/>\n");
+ out->writeText(" <maxBeforeBL value=\"0\"/>\n");
+ out->writeText(" <minAfterBL value=\"0\"/>\n");
+ out->writeText(" <pad1 value=\"0\"/>\n");
+ out->writeText(" <pad2 value=\"0\"/>\n");
+ out->writeText(" </sbitLineMetrics>\n");
+ out->writeText(" <sbitLineMetrics direction=\"vert\">\n");
+ out->writeText(" <ascender value=\"");
+ out->writeScalarAsText(-fm.fTop);
+ out->writeText("\"/>\n");
+ out->writeText(" <descender value=\"");
+ out->writeScalarAsText(-fm.fBottom);
+ out->writeText("\"/>\n");
+ out->writeText(" <widthMax value=\"");
+ out->writeScalarAsText(fm.fXMax - fm.fXMin);
+ out->writeText("\"/>\n");
+ out->writeText(" <caretSlopeNumerator value=\"0\"/>\n");
+ out->writeText(" <caretSlopeDenominator value=\"0\"/>\n");
+ out->writeText(" <caretOffset value=\"0\"/>\n");
+ out->writeText(" <minOriginSB value=\"0\"/>\n");
+ out->writeText(" <minAdvanceSB value=\"0\"/>\n");
+ out->writeText(" <maxBeforeBL value=\"0\"/>\n");
+ out->writeText(" <minAfterBL value=\"0\"/>\n");
+ out->writeText(" <pad1 value=\"0\"/>\n");
+ out->writeText(" <pad2 value=\"0\"/>\n");
+ out->writeText(" </sbitLineMetrics>\n");
+ out->writeText(" <colorRef value=\"0\"/>\n");
+ out->writeText(" <startGlyphIndex value=\"1\"/>\n");
+ out->writeText(" <endGlyphIndex value=\"1\"/>\n");
+ out->writeText(" <ppemX value=\"");
+ out->writeDecAsText(strikeSizes[strikeIndex]);
+ out->writeText("\"/>\n");
+ out->writeText(" <ppemY value=\"");
+ out->writeDecAsText(strikeSizes[strikeIndex]);
+ out->writeText("\"/>\n");
+ out->writeText(" <bitDepth value=\"32\"/>\n");
+ out->writeText(" <flags value=\"1\"/>\n");
+ out->writeText(" </bitmapSizeTable>\n");
+ out->writeText(" <eblc_index_sub_table_1 imageFormat=\"17\" firstGlyphIndex=\"1\" lastGlyphIndex=\"1\">\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ SkGlyphID gid = i;
+ SkRect bounds;
+ paint.getTextWidths(&gid, sizeof(gid), nullptr, &bounds);
+ if (bounds.isEmpty()) {
+ continue;
+ }
+ out->writeText(" <glyphLoc name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\"/>\n");
+ }
+ out->writeText(" </eblc_index_sub_table_1>\n");
+ out->writeText(" </strike>\n");
+ }
+ out->writeText(" </CBLC>\n");
+
+ out->writeText("</ttFont>\n");
+}
+
+/**
+ * UnitsPerEm is generally 1000 here. Versions of macOS older than 10.13
+ * have problems in CoreText determining the glyph bounds of bitmap glyphs
+ * with unitsPerEm set to 1024 or numbers not divisible by 100 when the
+ * contour is not closed. The bounds of sbix fonts on macOS appear to be those
+ * of the outline in the 'glyf' table. If this countour is closed it will be
+ * drawn, as the 'glyf' outline is to be drawn on top of any bitmap. (There is
+ * a bit which is supposed to control this, but it cannot be relied on.) So
+ * make the glyph contour ta degenerate line with points at the edge of the
+ * bounding box of the glyph.
+ */
+void SkTestSVGTypeface::exportTtxSbix(SkWStream* out) const {
+ out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+ this->exportTtxCommon(out, "sbix");
+
+ SkPaint paint;
+ paint.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ out->writeText(" <glyf>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ const SkTestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
+
+ SkSize containerSize = glyphData.fSvg ? glyphData.fSvg->containerSize()
+ : SkSize::MakeEmpty();
+ SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
+ containerSize.fWidth, containerSize.fHeight);
+ SkIRect ibounds = bounds.roundOut();
+ out->writeText(" <TTGlyph name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\" xMin=\"");
+ out->writeDecAsText(ibounds.fLeft);
+ out->writeText("\" yMin=\"");
+ out->writeDecAsText(-ibounds.fBottom);
+ out->writeText("\" xMax=\"");
+ out->writeDecAsText(ibounds.fRight);
+ out->writeText("\" yMax=\"");
+ out->writeDecAsText(-ibounds.fTop);
+ out->writeText("\">\n");
+ out->writeText(" <contour>\n");
+ out->writeText(" <pt x=\"");
+ out->writeDecAsText(ibounds.fLeft);
+ out->writeText("\" y=\"");
+ out->writeDecAsText(-ibounds.fBottom);
+ out->writeText("\" on=\"1\"/>\n");
+ out->writeText(" </contour>\n");
+ out->writeText(" <contour>\n");
+ out->writeText(" <pt x=\"");
+ out->writeDecAsText(ibounds.fRight);
+ out->writeText("\" y=\"");
+ out->writeDecAsText(-ibounds.fTop);
+ out->writeText("\" on=\"1\"/>\n");
+ out->writeText(" </contour>\n");
+ out->writeText(" <instructions/>\n");
+ out->writeText(" </TTGlyph>\n");
+ }
+ out->writeText(" </glyf>\n");
+
+ // The loca table will be re-calculated, but if we don't write one we don't get one.
+ out->writeText(" <loca/>\n");
+
+ int strikeSizes[3] = { 16, 64, 128 };
+
+ out->writeText(" <sbix>\n");
+ out->writeText(" <version value=\"1\"/>\n");
+ out->writeText(" <flags value=\"00000000 00000001\"/>\n");
+ for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
+ paint.setTextSize(strikeSizes[strikeIndex]);
+ out->writeText(" <strike>\n");
+ out->writeText(" <ppem value=\"");
+ out->writeDecAsText(strikeSizes[strikeIndex]);
+ out->writeText("\"/>\n");
+ out->writeText(" <resolution value=\"72\"/>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ SkGlyphID gid = i;
+ SkScalar advance;
+ SkRect bounds;
+ paint.getTextWidths(&gid, sizeof(gid), &advance, &bounds);
+ SkIRect ibounds = bounds.roundOut();
+ if (ibounds.isEmpty()) {
+ continue;
+ }
+ SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height());
+ sk_sp<SkSurface> surface(SkSurface::MakeRaster(image_info));
+ SkASSERT(surface);
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->clear(0);
+ SkPixmap pix;
+ surface->peekPixels(&pix);
+ canvas->drawText(&gid, sizeof(gid), -bounds.fLeft, -bounds.fTop, paint);
+ canvas->flush();
+ sk_sp<SkImage> image = surface->makeImageSnapshot();
+ sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+
+ out->writeText(" <glyph name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\" graphicType=\"png \" originOffsetX=\"");
+ out->writeDecAsText(bounds.fLeft);
+ out->writeText("\" originOffsetY=\"");
+ out->writeScalarAsText(bounds.fBottom);
+ out->writeText("\">\n");
+
+ out->writeText(" <hexdata>");
+ uint8_t const * bytes = data->bytes();
+ for (size_t i = 0; i < data->size(); ++i) {
+ if ((i % 0x10) == 0x0) {
+ out->writeText("\n ");
+ } else if (((i - 1) % 0x4) == 0x3) {
+ out->writeText(" ");
+ }
+ out->writeHexAsText(bytes[i], 2);
+ }
+ out->writeText("\n");
+ out->writeText(" </hexdata>\n");
+ out->writeText(" </glyph>\n");
+ }
+ out->writeText(" </strike>\n");
+ }
+ out->writeText(" </sbix>\n");
+ out->writeText("</ttFont>\n");
+}
+
+namespace {
+
+void convert_noninflect_cubic_to_quads(const SkPoint p[4],
+ SkScalar toleranceSqd,
+ SkTArray<SkPoint, true>* quads,
+ int sublevel = 0)
+{
+ // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
+ // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
+
+ SkVector ab = p[1] - p[0];
+ SkVector dc = p[2] - p[3];
+
+ if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) {
+ if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+ SkPoint* degQuad = quads->push_back_n(3);
+ degQuad[0] = p[0];
+ degQuad[1] = p[0];
+ degQuad[2] = p[3];
+ return;
+ }
+ ab = p[2] - p[0];
+ }
+ if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+ dc = p[1] - p[3];
+ }
+
+ static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
+ static const int kMaxSubdivs = 10;
+
+ ab.scale(kLengthScale);
+ dc.scale(kLengthScale);
+
+ // e0 and e1 are extrapolations along vectors ab and dc.
+ SkVector c0 = p[0];
+ c0 += ab;
+ SkVector c1 = p[3];
+ c1 += dc;
+
+ SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1);
+ if (dSqd < toleranceSqd) {
+ SkPoint cAvg = c0;
+ cAvg += c1;
+ cAvg.scale(SK_ScalarHalf);
+
+ SkPoint* pts = quads->push_back_n(3);
+ pts[0] = p[0];
+ pts[1] = cAvg;
+ pts[2] = p[3];
+ return;
+ }
+ SkPoint choppedPts[7];
+ SkChopCubicAtHalf(p, choppedPts);
+ convert_noninflect_cubic_to_quads(choppedPts + 0, toleranceSqd, quads, sublevel + 1);
+ convert_noninflect_cubic_to_quads(choppedPts + 3, toleranceSqd, quads, sublevel + 1);
+}
+
+void convertCubicToQuads(const SkPoint p[4], SkScalar tolScale, SkTArray<SkPoint, true>* quads) {
+ if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) {
+ return;
+ }
+ SkPoint chopped[10];
+ int count = SkChopCubicAtInflections(p, chopped);
+
+ const SkScalar tolSqd = SkScalarSquare(tolScale);
+
+ for (int i = 0; i < count; ++i) {
+ SkPoint* cubic = chopped + 3*i;
+ convert_noninflect_cubic_to_quads(cubic, tolSqd, quads);
+ }
+}
+
+void path_to_quads(const SkPath& path, SkPath* quadPath) {
+ quadPath->reset();
+ SkTArray<SkPoint, true> qPts;
+ SkAutoConicToQuads converter;
+ const SkPoint* quadPts;
+ SkPath::RawIter iter(path);
+ uint8_t verb;
+ SkPoint pts[4];
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ quadPath->moveTo(pts[0].fX, pts[0].fY);
+ break;
+ case SkPath::kLine_Verb:
+ quadPath->lineTo(pts[1].fX, pts[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ quadPath->quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ qPts.reset();
+ convertCubicToQuads(pts, SK_Scalar1, &qPts);
+ for (int i = 0; i < qPts.count(); i += 3) {
+ quadPath->quadTo(qPts[i+1].fX, qPts[i+1].fY, qPts[i+2].fX, qPts[i+2].fY);
+ }
+ break;
+ case SkPath::kConic_Verb:
+ quadPts = converter.computeQuads(pts, iter.conicWeight(), SK_Scalar1);
+ for (int i = 0; i < converter.countQuads(); ++i) {
+ quadPath->quadTo(quadPts[i*2+1].fX, quadPts[i*2+1].fY,
+ quadPts[i*2+2].fX, quadPts[i*2+2].fY);
+ }
+ break;
+ case SkPath::kClose_Verb:
+ quadPath->close();
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ }
+}
+
+class SkCOLRCanvas : public SkNoDrawCanvas {
+public:
+ SkCOLRCanvas(SkRect glyphBounds, SkGlyphID glyphId,
+ SkTestSVGTypeface::GlyfInfo* glyf, SkTHashMap<SkColor, int>* colors,
+ SkWStream* out)
+ : SkNoDrawCanvas(glyphBounds.roundOut().width(), glyphBounds.roundOut().height())
+ , fOut(out)
+ , fGlyphId(glyphId)
+ , fBaselineOffset(glyphBounds.top())
+ , fLayerId(0)
+ , fGlyf(glyf)
+ , fColors(colors)
+ { }
+
+ void writePoint(SkScalar x, SkScalar y, bool on) {
+ fOut->writeText(" <pt x=\"");
+ fOut->writeDecAsText(SkScalarRoundToInt(x));
+ fOut->writeText("\" y=\"");
+ fOut->writeDecAsText(SkScalarRoundToInt(y));
+ fOut->writeText("\" on=\"");
+ fOut->write8(on ? '1' : '0');
+ fOut->writeText("\"/>\n");
+ }
+ SkIRect writePath(const SkPath& path, bool layer) {
+ // Convert to quads.
+ SkPath quads;
+ path_to_quads(path, &quads);
+
+ SkRect bounds = quads.computeTightBounds();
+ SkIRect ibounds = bounds.roundOut();
+ // The bounds will be re-calculated anyway.
+ fOut->writeText(" <TTGlyph name=\"glyf");
+ fOut->writeHexAsText(fGlyphId, 4);
+ if (layer) {
+ fOut->writeText("l");
+ fOut->writeHexAsText(fLayerId, 4);
+ }
+ fOut->writeText("\" xMin=\"");
+ fOut->writeDecAsText(ibounds.fLeft);
+ fOut->writeText("\" yMin=\"");
+ fOut->writeDecAsText(ibounds.fTop);
+ fOut->writeText("\" xMax=\"");
+ fOut->writeDecAsText(ibounds.fRight);
+ fOut->writeText("\" yMax=\"");
+ fOut->writeDecAsText(ibounds.fBottom);
+ fOut->writeText("\">\n");
+
+ SkPath::RawIter iter(quads);
+ uint8_t verb;
+ SkPoint pts[4];
+ bool contourOpen = false;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (contourOpen) {
+ fOut->writeText(" </contour>\n");
+ contourOpen = false;
+ }
+ break;
+ case SkPath::kLine_Verb:
+ if (!contourOpen) {
+ fOut->writeText(" <contour>\n");
+ this->writePoint(pts[0].fX, pts[0].fY, true);
+ contourOpen = true;
+ }
+ this->writePoint(pts[1].fX, pts[1].fY, true);
+ break;
+ case SkPath::kQuad_Verb:
+ if (!contourOpen) {
+ fOut->writeText(" <contour>\n");
+ this->writePoint(pts[0].fX, pts[0].fY, true);
+ contourOpen = true;
+ }
+ this->writePoint(pts[1].fX, pts[1].fY, false);
+ this->writePoint(pts[2].fX, pts[2].fY, true);
+ break;
+ case SkPath::kClose_Verb:
+ if (contourOpen) {
+ fOut->writeText(" </contour>\n");
+ contourOpen = false;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return ibounds;
+ }
+ }
+ if (contourOpen) {
+ fOut->writeText(" </contour>\n");
+ }
+
+ // Required to write out an instructions tag.
+ fOut->writeText(" <instructions/>\n");
+ fOut->writeText(" </TTGlyph>\n");
+ return ibounds;
+ }
+
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ SkPath path;
+ path.addRect(rect);
+ this->drawPath(path, paint);
+ }
+
+ void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
+ SkPath path;
+ path.addOval(oval);
+ this->drawPath(path, paint);
+ }
+
+ void onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
+ const SkPaint& paint) override
+ {
+ SkPath path;
+ bool fillNoPathEffect = SkPaint::kFill_Style == paint.getStyle() && !paint.getPathEffect();
+ SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter,
+ fillNoPathEffect);
+ this->drawPath(path, paint);
+ }
+
+ void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
+ SkPath path;
+ path.addRRect(rrect);
+ this->drawPath(path, paint);
+ }
+
+ void onDrawPath(const SkPath& platonicPath, const SkPaint& originalPaint) override {
+ SkPaint paint = originalPaint;
+ SkPath path = platonicPath;
+
+ // Apply the path effect.
+ if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+ bool fill = paint.getFillPath(path, &path);
+
+ paint.setPathEffect(nullptr);
+ if (fill) {
+ paint.setStyle(SkPaint::kFill_Style);
+ } else {
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(0);
+ }
+ }
+
+ // Apply the matrix.
+ SkMatrix m = this->getTotalMatrix();
+ // If done to the canvas then everything would get clipped out.
+ m.postTranslate(0, fBaselineOffset); // put the baseline at 0
+ m.postScale(1, -1); // and flip it since OpenType is y-up.
+ path.transform(m);
+
+ // While creating the default glyf, union with dark colors and intersect with bright colors.
+ SkColor color = paint.getColor();
+ if ((SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3 > 0x20) {
+ fBasePath.add(path, SkPathOp::kDifference_SkPathOp);
+ } else {
+ fBasePath.add(path, SkPathOp::kUnion_SkPathOp);
+ }
+
+ SkIRect bounds = this->writePath(path, true);
+
+ // The CPAL table has the concept of a 'current color' which is index 0xFFFF.
+ // Mark any layer drawn in 'currentColor' as having this special index.
+ // The value of 'currentColor' here should a color which causes this layer to union into the
+ // default glyf.
+ constexpr SkColor currentColor = 0xFF2B0000;
+
+ int colorIndex;
+ if (color == currentColor) {
+ colorIndex = 0xFFFF;
+ } else {
+ int* colorIndexPtr = fColors->find(color);
+ if (colorIndexPtr) {
+ colorIndex = *colorIndexPtr;
+ } else {
+ colorIndex = fColors->count();
+ fColors->set(color, colorIndex);
+ }
+ }
+ fGlyf->fLayers.emplace_back(colorIndex, bounds);
+
+ ++fLayerId;
+ }
+
+ void finishGlyph() {
+ SkPath baseGlyph;
+ fBasePath.resolve(&baseGlyph);
+ fGlyf->fBounds = this->writePath(baseGlyph, false);
+ }
+
+private:
+ SkWStream * const fOut;
+ SkGlyphID fGlyphId;
+ SkScalar fBaselineOffset;
+ int fLayerId;
+ SkOpBuilder fBasePath;
+ SkTestSVGTypeface::GlyfInfo* fGlyf;
+ SkTHashMap<SkColor, int>* fColors;
+};
+
+} // namespace
+
+void SkTestSVGTypeface::exportTtxColr(SkWStream* out) const {
+ out->writeText("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n");
+
+ SkTHashMap<SkColor, int> colors;
+ SkTArray<GlyfInfo> glyfInfos(fGlyphs.count());
+
+ // Need to know all the glyphs up front for the common tables.
+ SkDynamicMemoryWStream glyfOut;
+ glyfOut.writeText(" <glyf>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ const SkTestSVGTypeface::Glyph& glyphData = this->fGlyphs[i];
+
+ SkSize containerSize = glyphData.fSvg ? glyphData.fSvg->containerSize()
+ : SkSize::MakeEmpty();
+ SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY,
+ containerSize.fWidth, containerSize.fHeight);
+ SkCOLRCanvas canvas(bounds, i, &glyfInfos.emplace_back(), &colors, &glyfOut);
+ if (glyphData.fSvg) {
+ glyphData.fSvg->render(&canvas);
+ }
+ canvas.finishGlyph();
+ }
+ glyfOut.writeText(" </glyf>\n");
+
+ this->exportTtxCommon(out, "COLR", &glyfInfos);
+
+ // The loca table will be re-calculated, but if we don't write one we don't get one.
+ out->writeText(" <loca/>\n");
+
+ std::unique_ptr<SkStreamAsset> glyfStream = glyfOut.detachAsStream();
+ out->writeStream(glyfStream.get(), glyfStream->getLength());
+
+ out->writeText(" <COLR>\n");
+ out->writeText(" <version value=\"0\"/>\n");
+ for (int i = 0; i < fGlyphs.count(); ++i) {
+ if (glyfInfos[i].fBounds.isEmpty() || glyfInfos[i].fLayers.empty()) {
+ continue;
+ }
+ out->writeText(" <ColorGlyph name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("\">\n");
+ for (int j = 0; j < glyfInfos[i].fLayers.count(); ++j) {
+ const int colorIndex = glyfInfos[i].fLayers[j].fLayerColorIndex;
+ out->writeText(" <layer colorID=\"");
+ out->writeDecAsText(colorIndex);
+ out->writeText("\" name=\"glyf");
+ out->writeHexAsText(i, 4);
+ out->writeText("l");
+ out->writeHexAsText(j, 4);
+ out->writeText("\"/>\n");
+ }
+ out->writeText(" </ColorGlyph>\n");
+ }
+ out->writeText(" </COLR>\n");
+
+ // The colors must be written in order, the 'index' is ignored by ttx.
+ SkAutoTMalloc<SkColor> colorsInOrder(colors.count());
+ colors.foreach([&colorsInOrder](const SkColor& c, const int* i) {
+ colorsInOrder[*i] = c;
+ });
+ out->writeText(" <CPAL>\n");
+ out->writeText(" <version value=\"0\"/>\n");
+ out->writeText(" <numPaletteEntries value=\"");
+ out->writeDecAsText(colors.count());
+ out->writeText("\"/>\n");
+ out->writeText(" <palette index=\"0\">\n");
+ for (int i = 0; i < colors.count(); ++i) {
+ SkColor c = colorsInOrder[i];
+ out->writeText(" <color index=\"");
+ out->writeDecAsText(i);
+ out->writeText("\" value=\"#");
+ out->writeHexAsText(SkColorGetR(c), 2);
+ out->writeHexAsText(SkColorGetG(c), 2);
+ out->writeHexAsText(SkColorGetB(c), 2);
+ out->writeHexAsText(SkColorGetA(c), 2);
+ out->writeText("\"/>\n");
+ }
+ out->writeText(" </palette>\n");
+ out->writeText(" </CPAL>\n");
+
+ out->writeText("</ttFont>\n");
+}
diff --git a/tools/fonts/SkTestSVGTypeface.h b/tools/fonts/SkTestSVGTypeface.h
new file mode 100644
index 0000000000..86c828511b
--- /dev/null
+++ b/tools/fonts/SkTestSVGTypeface.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTestSVGTypeface_DEFINED
+#define SkTestSVGTypeface_DEFINED
+
+#include "SkFontArguments.h"
+#include "SkPaint.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "SkTHash.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
+
+#include <memory>
+
+class SkDescriptor;
+class SkFontDescriptor;
+class SkFontStyle;
+class SkGlyph;
+class SkPath;
+class SkScalerContext;
+class SkStreamAsset;
+class SkSVGDOM;
+class SkWStream;
+struct SkAdvancedTypefaceMetrics;
+struct SkScalerContextEffects;
+struct SkScalerContextRec;
+
+struct SkSVGTestTypefaceGlyphData {
+ const char* fSvgResourcePath;
+ SkPoint fOrigin;
+ SkScalar fAdvance;
+ SkUnichar fUnicode; //TODO: this limits to 1:1
+};
+
+class SkTestSVGTypeface : public SkTypeface {
+public:
+ SkTestSVGTypeface(const char* name,
+ int upem,
+ const SkPaint::FontMetrics& metrics,
+ const SkSVGTestTypefaceGlyphData* data, int dataCount,
+ const SkFontStyle& style);
+ ~SkTestSVGTypeface() override;
+ void getAdvance(SkGlyph* glyph) const;
+ void getFontMetrics(SkPaint::FontMetrics* metrics) const;
+ void getPath(SkGlyphID glyph, SkPath* path) const;
+
+ static sk_sp<SkTestSVGTypeface> Default();
+ void exportTtxCbdt(SkWStream*) const;
+ void exportTtxSbix(SkWStream*) const;
+ void exportTtxColr(SkWStream*) const;
+
+ struct GlyfLayerInfo {
+ GlyfLayerInfo(int layerColorIndex, SkIRect bounds)
+ : fLayerColorIndex(layerColorIndex)
+ , fBounds(bounds) {}
+ int fLayerColorIndex;
+ SkIRect fBounds;
+ };
+ struct GlyfInfo {
+ GlyfInfo() : fBounds(SkIRect::MakeEmpty()) {}
+ SkIRect fBounds;
+ SkTArray<GlyfLayerInfo> fLayers;
+ };
+protected:
+ void exportTtxCommon(SkWStream*, const char* type, const SkTArray<GlyfInfo>* = nullptr) const;
+
+ SkScalerContext* onCreateScalerContext(const SkScalerContextEffects&,
+ const SkDescriptor* desc) const override;
+ void onFilterRec(SkScalerContextRec* rec) const override;
+ std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override;
+
+ SkStreamAsset* onOpenStream(int* ttcIndex) const override {
+ return nullptr;
+ }
+
+ void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override;
+
+ int onCharsToGlyphs(const void* chars, Encoding encoding,
+ uint16_t glyphs[], int glyphCount) const override;
+
+ int onCountGlyphs() const override {
+ return fGlyphs.count();
+ }
+
+ int onGetUPEM() const override {
+ return fUpem;
+ }
+
+ void onGetFamilyName(SkString* familyName) const override;
+ SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
+
+ int onGetVariationDesignPosition(SkFontArguments::VariationPosition::Coordinate coordinates[],
+ int coordinateCount) const override
+ {
+ return 0;
+ }
+
+ int onGetTableTags(SkFontTableTag tags[]) const override {
+ return 0;
+ }
+
+ size_t onGetTableData(SkFontTableTag tag, size_t offset,
+ size_t length, void* data) const override {
+ return 0;
+ }
+private:
+ struct Glyph {
+ Glyph(sk_sp<SkSVGDOM> svg, const SkSVGTestTypefaceGlyphData& data);
+ ~Glyph();
+ sk_sp<SkSVGDOM> fSvg;
+ SkPoint fOrigin;
+ SkScalar fAdvance;
+ };
+ SkString fName;
+ int fUpem;
+ const SkPaint::FontMetrics fFontMetrics;
+ SkTArray<Glyph> fGlyphs;
+ SkTHashMap<SkUnichar, SkGlyphID> fCMap;
+ friend class SkTestSVGScalerContext;
+};
+
+#endif
diff --git a/tools/fonts/SkTestScalerContext.cpp b/tools/fonts/SkTestTypeface.cpp
index f74b1bd57a..52751c9f59 100644
--- a/tools/fonts/SkTestScalerContext.cpp
+++ b/tools/fonts/SkTestTypeface.cpp
@@ -8,18 +8,25 @@
#include "SkAdvancedTypefaceMetrics.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
-#include "SkDescriptor.h"
#include "SkFontDescriptor.h"
#include "SkGlyph.h"
-#include "SkMakeUnique.h"
-#include "SkMask.h"
+#include "SkImageInfo.h"
+#include "SkMatrix.h"
#include "SkOTUtils.h"
#include "SkPaintPriv.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRect.h"
#include "SkScalerContext.h"
-#include "SkTestScalerContext.h"
-#include "SkTypefaceCache.h"
+#include "SkString.h"
+#include "SkTestTypeface.h"
+#include "SkTDArray.h"
#include "SkUtils.h"
+#include <utility>
+
+class SkDescriptor;
+
SkTestFont::SkTestFont(const SkTestFontData& fontData)
: INHERITED()
, fCharCodes(fontData.fCharCodes)
diff --git a/tools/fonts/SkTestScalerContext.h b/tools/fonts/SkTestTypeface.h
index e62210b2e3..b520fb6e5c 100644
--- a/tools/fonts/SkTestScalerContext.h
+++ b/tools/fonts/SkTestTypeface.h
@@ -5,17 +5,31 @@
* found in the LICENSE file.
*/
-#ifndef SkTestScalerContext_DEFINED
-#define SkTestScalerContext_DEFINED
+#ifndef SkTestTypeface_DEFINED
+#define SkTestTypeface_DEFINED
#include "SkFixed.h"
+#include "SkFontArguments.h"
+#include "SkFontStyle.h"
#include "SkPaint.h"
-#include "SkPath.h"
#include "SkRefCnt.h"
-#include "SkTDArray.h"
+#include "SkScalar.h"
#include "SkTypeface.h"
+#include "SkTypes.h"
+#include <memory>
+
+class SkDescriptor;
+class SkFontDescriptor;
+class SkGlyph;
+class SkPath;
+class SkScalerContext;
+class SkStreamAsset;
+class SkString;
class SkTestFont;
+struct SkAdvancedTypefaceMetrics;
+struct SkScalerContextEffects;
+struct SkScalerContextRec;
struct SkTestFontData {
const SkScalar* fPoints;
diff --git a/tools/fonts/create_test_font_color.cpp b/tools/fonts/create_test_font_color.cpp
new file mode 100644
index 0000000000..c3f2e08f3f
--- /dev/null
+++ b/tools/fonts/create_test_font_color.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// running create_test_font_color generates ./<cbdt|sbix|cpal>.ttx
+// which are read by fonttools ttx to produce native fonts.
+
+#include "SkCommandLineFlags.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTestSVGTypeface.h"
+
+int main(int argc, char** argv) {
+ SkCommandLineFlags::Parse(argc, argv);
+
+ sk_sp<SkTestSVGTypeface> typeface = SkTestSVGTypeface::Default();
+
+ SkFILEWStream cbdt("cbdt.ttx");
+ typeface->exportTtxCbdt(&cbdt);
+ cbdt.flush();
+ cbdt.fsync();
+
+ SkFILEWStream sbix("sbix.ttx");
+ typeface->exportTtxSbix(&sbix);
+ sbix.flush();
+ sbix.fsync();
+
+ SkFILEWStream colr("colr.ttx");
+ typeface->exportTtxColr(&colr);
+ colr.flush();
+ colr.fsync();
+
+ return 0;
+}
diff --git a/tools/fonts/sk_tool_utils_font.cpp b/tools/fonts/sk_tool_utils_font.cpp
index d2aac851bb..4d5b86aa0a 100644
--- a/tools/fonts/sk_tool_utils_font.cpp
+++ b/tools/fonts/sk_tool_utils_font.cpp
@@ -11,7 +11,7 @@
#include "SkFontStyle.h"
#include "SkMutex.h"
#include "SkOSFile.h"
-#include "SkTestScalerContext.h"
+#include "SkTestTypeface.h"
#include "SkUtils.h"
#include "sk_tool_utils.h"
@@ -70,42 +70,23 @@ sk_sp<SkTypeface> create_font(const char* name, SkFontStyle style) {
}
sk_sp<SkTypeface> emoji_typeface() {
+ const char* filename;
#if defined(SK_BUILD_FOR_WIN)
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
- const char *colorEmojiFontName = "Segoe UI Emoji";
- sk_sp<SkTypeface> typeface(fm->matchFamilyStyle(colorEmojiFontName, SkFontStyle()));
- if (typeface) {
- return typeface;
- }
- sk_sp<SkTypeface> fallback(fm->matchFamilyStyleCharacter(
- colorEmojiFontName, SkFontStyle(), nullptr /* bcp47 */, 0 /* bcp47Count */,
- 0x1f4b0 /* character: πŸ’° */));
- if (fallback) {
- return fallback;
- }
- // If we don't have Segoe UI Emoji and can't find a fallback, try Segoe UI Symbol.
- // Windows 7 does not have Segoe UI Emoji; Segoe UI Symbol has the (non - color) emoji.
- return SkTypeface::MakeFromName("Segoe UI Symbol", SkFontStyle());
-
+ filename = "fonts/colr.ttf";
#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
- return SkTypeface::MakeFromName("Apple Color Emoji", SkFontStyle());
-
+ filename = "fonts/sbix.ttf";
#else
- return MakeResourceAsTypeface("fonts/Funkster.ttf");
-
+ filename = "fonts/cbdt.ttf";
#endif
+ sk_sp<SkTypeface> typeface = MakeResourceAsTypeface(filename);
+ if (typeface) {
+ return typeface;
+ }
+ return SkTypeface::MakeFromName("Emoji", SkFontStyle());
}
const char* emoji_sample_text() {
-#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
- return "\xF0\x9F\x92\xB0" "\xF0\x9F\x8F\xA1" "\xF0\x9F\x8E\x85" // πŸ’°πŸ‘πŸŽ…
- "\xF0\x9F\x8D\xAA" "\xF0\x9F\x8D\x95" "\xF0\x9F\x9A\x80" // πŸͺπŸ•πŸš€
- "\xF0\x9F\x9A\xBB" "\xF0\x9F\x92\xA9" "\xF0\x9F\x93\xB7" // πŸš»πŸ’©πŸ“·
- "\xF0\x9F\x93\xA6" // πŸ“¦
- "\xF0\x9F\x87\xBA" "\xF0\x9F\x87\xB8" "\xF0\x9F\x87\xA6"; // πŸ‡ΊπŸ‡ΈπŸ‡¦
-#else
- return "Hamburgefons";
-#endif
+ return "\xF0\x9F\x98\x80" " " "\xE2\x99\xA2"; // πŸ˜€ β™’
}
static const char* platform_os_name() {
diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp
index 2eb8dba833..99b03a4391 100644
--- a/tools/sk_tool_utils.cpp
+++ b/tools/sk_tool_utils.cpp
@@ -5,19 +5,29 @@
* found in the LICENSE file.
*/
-#include "sk_tool_utils.h"
-
-#include "Resources.h"
#include "SkBitmap.h"
+#include "SkBlendMode.h"
#include "SkCanvas.h"
+#include "SkColorData.h"
+#include "SkColorPriv.h"
+#include "SkFloatingPoint.h"
#include "SkImage.h"
-#include "SkPixelRef.h"
+#include "SkMatrix.h"
#include "SkPM4f.h"
-#include "SkPoint3.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPixelRef.h"
+#include "SkPixmap.h"
+#include "SkPoint.h"
+#include "SkRRect.h"
#include "SkShader.h"
#include "SkSurface.h"
-#include "SkTestScalerContext.h"
#include "SkTextBlob.h"
+#include "sk_tool_utils.h"
+
+#include <cmath>
+#include <cstring>
+#include <memory>
namespace sk_tool_utils {
@@ -159,7 +169,7 @@ SkPath make_star(const SkRect& bounds, int numPts, int step) {
#pragma optimize("", off)
#endif
void make_big_path(SkPath& path) {
- #include "BigPathBench.inc"
+ #include "BigPathBench.inc" // IWYU pragma: keep
}
static float gaussian2d_value(int x, int y, float sigma) {
diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h
index 6c1adf8e26..30d195d479 100644
--- a/tools/sk_tool_utils.h
+++ b/tools/sk_tool_utils.h
@@ -9,26 +9,33 @@
#define sk_tool_utils_DEFINED
#include "SkColor.h"
+#include "SkData.h"
+#include "SkEncodedImageFormat.h"
+#include "SkFontStyle.h"
#include "SkImageEncoder.h"
#include "SkImageInfo.h"
#include "SkRandom.h"
+#include "SkRect.h"
#include "SkRefCnt.h"
+#include "SkScalar.h"
#include "SkStream.h"
+#include "SkTArray.h"
#include "SkTDArray.h"
-#include "SkTypeface.h"
+#include "SkTypes.h"
class SkBitmap;
class SkCanvas;
-class SkColorFilter;
+class SkFontStyle;
class SkImage;
class SkPaint;
class SkPath;
+class SkPixmap;
class SkRRect;
class SkShader;
class SkSurface;
class SkSurfaceProps;
-class SkTestFont;
class SkTextBlobBuilder;
+class SkTypeface;
namespace sk_tool_utils {