diff options
-rw-r--r-- | BUILD.gn | 18 | ||||
-rw-r--r-- | gm/fontmgr.cpp | 7 | ||||
-rw-r--r-- | public.bzl | 6 | ||||
-rw-r--r-- | resources/fonts/cbdt.ttf | bin | 0 -> 18020 bytes | |||
-rw-r--r-- | resources/fonts/colr.ttf | bin | 0 -> 2996 bytes | |||
-rw-r--r-- | resources/fonts/sbix.ttf | bin | 0 -> 17956 bytes | |||
-rw-r--r-- | resources/fonts/svg/diamond.svg | 22 | ||||
-rw-r--r-- | resources/fonts/svg/empty.svg | 10 | ||||
-rw-r--r-- | resources/fonts/svg/notdef.svg | 29 | ||||
-rw-r--r-- | resources/fonts/svg/smile.svg | 44 | ||||
-rw-r--r-- | src/ports/SkFontHost_win.cpp | 7 | ||||
-rw-r--r-- | tools/fonts/SkTestFontMgr.cpp | 28 | ||||
-rw-r--r-- | tools/fonts/SkTestSVGTypeface.cpp | 1302 | ||||
-rw-r--r-- | tools/fonts/SkTestSVGTypeface.h | 132 | ||||
-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.cpp | 37 | ||||
-rw-r--r-- | tools/fonts/sk_tool_utils_font.cpp | 41 | ||||
-rw-r--r-- | tools/sk_tool_utils.cpp | 24 | ||||
-rw-r--r-- | tools/sk_tool_utils.h | 13 |
20 files changed, 1705 insertions, 54 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000000..bed498f07e --- /dev/null +++ b/resources/fonts/cbdt.ttf diff --git a/resources/fonts/colr.ttf b/resources/fonts/colr.ttf Binary files differnew file mode 100644 index 0000000000..42e2a08096 --- /dev/null +++ b/resources/fonts/colr.ttf diff --git a/resources/fonts/sbix.ttf b/resources/fonts/sbix.ttf Binary files differnew file mode 100644 index 0000000000..5eed8a917b --- /dev/null +++ b/resources/fonts/sbix.ttf 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 { |