/* * 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 "tools/fonts/TestSVGTypeface.h" #if defined(SK_ENABLE_SVG) #include "include/codec/SkEncodedImageFormat.h" #include "include/core/SkArc.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkData.h" #include "include/core/SkDrawable.h" #include "include/core/SkFontStyle.h" #include "include/core/SkImage.h" #include "include/core/SkImageInfo.h" #include "include/core/SkMatrix.h" #include "include/core/SkPath.h" #include "include/core/SkPathEffect.h" #include "include/core/SkPathUtils.h" #include "include/core/SkPixmap.h" #include "include/core/SkRRect.h" #include "include/core/SkSize.h" #include "include/core/SkStream.h" #include "include/core/SkSurface.h" #include "include/encode/SkPngEncoder.h" #include "include/pathops/SkPathOps.h" #include "include/private/base/SkTDArray.h" #include "include/private/base/SkTemplates.h" #include "include/utils/SkNoDrawCanvas.h" #include "modules/svg/include/SkSVGDOM.h" #include "modules/svg/include/SkSVGNode.h" #include "src/base/SkUtils.h" #include "src/core/SkAdvancedTypefaceMetrics.h" #include "src/core/SkFontDescriptor.h" #include "src/core/SkFontPriv.h" #include "src/core/SkGeometry.h" #include "src/core/SkGlyph.h" #include "src/core/SkMask.h" #include "src/core/SkPaintPriv.h" #include "src/core/SkPathPriv.h" #include "src/core/SkPointPriv.h" #include "src/core/SkScalerContext.h" #include "src/sfnt/SkOTUtils.h" #include "tools/Resources.h" #include using namespace skia_private; class SkDescriptor; TestSVGTypeface::TestSVGTypeface(const char* name, const SkFontStyle& style, int upem, const SkFontMetrics& fontMetrics, SkSpan data) : SkTypeface(style, false) , fName(name) , fUpem(upem) , fFontMetrics(fontMetrics) , fGlyphs(new Glyph[data.size()]) , fGlyphCount(data.size()) { for (size_t i = 0; i < data.size(); ++i) { const SkSVGTestTypefaceGlyphData& datum = data[i]; fCMap.set(datum.fUnicode, i); fGlyphs[i].fAdvance = datum.fAdvance; fGlyphs[i].fOrigin = datum.fOrigin; fGlyphs[i].fResourcePath = datum.fSvgResourcePath; } } template void TestSVGTypeface::Glyph::withSVG(Fn&& fn) const { SkAutoMutexExclusive lock(fSvgMutex); if (!fParsedSvg) { fParsedSvg = true; std::unique_ptr stream = GetResourceAsStream(fResourcePath); if (!stream) { return; } // We expressly *do not want* to set a SkFontMgr when parsing these SVGs. // 1) The SVGs we are processing have no tags in them. // 2) Trying to use ToolUtils::TestFontMgr() is a problem because the portable // SkFontMgr *calls* this function as it creates the typefaces. sk_sp svg = SkSVGDOM::MakeFromStream(*stream); if (!svg) { return; } if (svg->containerSize().isEmpty()) { return; } fSvg = std::move(svg); } if (fSvg) { fn(*fSvg); } } SkSize TestSVGTypeface::Glyph::size() const { SkSize size = SkSize::MakeEmpty(); this->withSVG([&](const SkSVGDOM& svg){ size = svg.containerSize(); }); return size; } void TestSVGTypeface::Glyph::render(SkCanvas* canvas) const { this->withSVG([&](const SkSVGDOM& svg){ svg.render(canvas); }); } TestSVGTypeface::~TestSVGTypeface() {} TestSVGTypeface::Glyph::Glyph() : fOrigin{0, 0}, fAdvance(0) {} TestSVGTypeface::Glyph::~Glyph() {} SkVector TestSVGTypeface::getAdvance(SkGlyphID glyphID) const { glyphID = glyphID < fGlyphCount ? glyphID : 0; return {fGlyphs[glyphID].fAdvance, 0}; } void TestSVGTypeface::getFontMetrics(SkFontMetrics* metrics) const { *metrics = fFontMetrics; } void TestSVGTypeface::onFilterRec(SkScalerContextRec* rec) const { rec->setHinting(SkFontHinting::kNone); } void TestSVGTypeface::getGlyphToUnicodeMap(SkUnichar* glyphToUnicode) const { SkDEBUGCODE(unsigned glyphCount = this->countGlyphs()); fCMap.foreach ([=](const SkUnichar& c, const SkGlyphID& g) { SkASSERT(g < glyphCount); glyphToUnicode[g] = c; }); } std::unique_ptr TestSVGTypeface::onGetAdvancedMetrics() const { std::unique_ptr info(new SkAdvancedTypefaceMetrics); info->fPostScriptName = fName; return info; } void TestSVGTypeface::onGetFontDescriptor(SkFontDescriptor* desc, bool* serialize) const { desc->setFamilyName(fName.c_str()); desc->setStyle(this->fontStyle()); *serialize = true; } void TestSVGTypeface::onCharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const { for (int i = 0; i < count; i++) { SkGlyphID* g = fCMap.find(uni[i]); glyphs[i] = g ? *g : 0; } } void TestSVGTypeface::onGetFamilyName(SkString* familyName) const { *familyName = fName; } bool TestSVGTypeface::onGetPostScriptName(SkString*) const { return false; } SkTypeface::LocalizedStrings* TestSVGTypeface::onCreateFamilyNameIterator() const { SkString familyName(fName); SkString language("und"); // undetermined return new SkOTUtils::LocalizedStrings_SingleName(familyName, language); } class SkTestSVGScalerContext : public SkScalerContext { public: SkTestSVGScalerContext(TestSVGTypeface& face, const SkScalerContextEffects& effects, const SkDescriptor* desc) : SkScalerContext(face, effects, desc) { fRec.getSingleMatrix(&fMatrix); SkScalar upem = this->getTestSVGTypeface()->fUpem; fMatrix.preScale(1.f / upem, 1.f / upem); } protected: TestSVGTypeface* getTestSVGTypeface() const { return static_cast(this->getTypeface()); } SkVector computeAdvance(SkGlyphID glyphID) { auto advance = this->getTestSVGTypeface()->getAdvance(glyphID); return fMatrix.mapXY(advance.fX, advance.fY); } GlyphMetrics generateMetrics(const SkGlyph& glyph, SkArenaAlloc*) override { SkGlyphID glyphID = glyph.getGlyphID(); glyphID = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0; GlyphMetrics mx(SkMask::kARGB32_Format); mx.neverRequestPath = true; mx.advance = this->computeAdvance(glyph.getGlyphID()); TestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID]; SkSize containerSize = glyphData.size(); 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); newBounds.roundOut(&mx.bounds); return mx; } void generateImage(const SkGlyph& glyph, void* imageBuffer) override { SkGlyphID glyphID = glyph.getGlyphID(); glyphID = glyphID < this->getTestSVGTypeface()->fGlyphCount ? glyphID : 0; SkBitmap bm; // TODO: this should be SkImageInfo::MakeS32 when that passes all the tests. bm.installPixels(SkImageInfo::MakeN32(glyph.width(), glyph.height(), kPremul_SkAlphaType), imageBuffer, glyph.rowBytes()); bm.eraseColor(0); TestSVGTypeface::Glyph& glyphData = this->getTestSVGTypeface()->fGlyphs[glyphID]; SkScalar dx = SkFixedToScalar(glyph.getSubXFixed()); SkScalar dy = SkFixedToScalar(glyph.getSubYFixed()); SkCanvas canvas(bm); canvas.translate(-glyph.left(), -glyph.top()); canvas.translate(dx, dy); canvas.concat(fMatrix); canvas.translate(glyphData.fOrigin.fX, -glyphData.fOrigin.fY); glyphData.render(&canvas); } bool generatePath(const SkGlyph& glyph, SkPath* path, bool* modified) override { // Should never get here since generateMetrics always sets the path to not exist. SK_ABORT("Path requested, but it should have been indicated that there isn't one."); path->reset(); return false; } struct SVGGlyphDrawable : public SkDrawable { SkTestSVGScalerContext* fSelf; SkGlyph fGlyph; SVGGlyphDrawable(SkTestSVGScalerContext* self, const SkGlyph& glyph) : fSelf(self), fGlyph(glyph) {} SkRect onGetBounds() override { return fGlyph.rect(); } size_t onApproximateBytesUsed() override { return sizeof(SVGGlyphDrawable); } void onDraw(SkCanvas* canvas) override { SkGlyphID glyphID = fGlyph.getGlyphID(); glyphID = glyphID < fSelf->getTestSVGTypeface()->fGlyphCount ? glyphID : 0; TestSVGTypeface::Glyph& glyphData = fSelf->getTestSVGTypeface()->fGlyphs[glyphID]; SkScalar dx = SkFixedToScalar(fGlyph.getSubXFixed()); SkScalar dy = SkFixedToScalar(fGlyph.getSubYFixed()); canvas->translate(dx, dy); canvas->concat(fSelf->fMatrix); canvas->translate(glyphData.fOrigin.fX, -glyphData.fOrigin.fY); glyphData.render(canvas); } }; sk_sp generateDrawable(const SkGlyph& glyph) override { return sk_sp(new SVGGlyphDrawable(this, glyph)); } void generateFontMetrics(SkFontMetrics* metrics) override { this->getTestSVGTypeface()->getFontMetrics(metrics); SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY()); } private: SkMatrix fMatrix; }; std::unique_ptr TestSVGTypeface::onCreateScalerContext( const SkScalerContextEffects& e, const SkDescriptor* desc) const { return std::make_unique(*const_cast(this), e, desc); } class DefaultTypeface : public TestSVGTypeface { using TestSVGTypeface::TestSVGTypeface; bool getPathOp(SkColor color, SkPathOp* op) const override { if ((SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3 > 0x20) { *op = SkPathOp::kDifference_SkPathOp; } else { *op = SkPathOp::kUnion_SkPathOp; } return true; } static constexpr SkTypeface::FactoryId FactoryId = SkSetFourByteTag('d','s','v','g'); static constexpr const char gHeaderString[] = "SkTestSVGTypefaceDefault01"; static constexpr const size_t kHeaderSize = sizeof(gHeaderString); std::unique_ptr onOpenStream(int* ttcIndex) const override { SkDynamicMemoryWStream wstream; wstream.write(gHeaderString, kHeaderSize); return wstream.detachAsStream(); } static sk_sp MakeFromStream(std::unique_ptr stream, const SkFontArguments&) { char header[kHeaderSize]; if (stream->read(header, kHeaderSize) != kHeaderSize || 0 != memcmp(header, gHeaderString, kHeaderSize)) { return nullptr; } return TestSVGTypeface::Default(); } void onGetFontDescriptor(SkFontDescriptor* desc, bool* serialize) const override { TestSVGTypeface::onGetFontDescriptor(desc, serialize); desc->setFactoryId(FactoryId); } public: struct Register { Register() { SkTypeface::Register(FactoryId, &MakeFromStream); } }; }; static DefaultTypeface::Register defaultTypefaceRegister; sk_sp TestSVGTypeface::Default() { // Recommended that the first four be .notdef, .null, CR, space constexpr const static SkSVGTestTypefaceGlyphData glyphs[] = { {"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}, // 😀 }; SkFontMetrics metrics; metrics.fFlags = SkFontMetrics::kUnderlineThicknessIsValid_Flag | SkFontMetrics::kUnderlinePositionIsValid_Flag | SkFontMetrics::kStrikeoutThicknessIsValid_Flag | SkFontMetrics::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_sp( new DefaultTypeface("Emoji", SkFontStyle::Normal(), 1000, metrics, glyphs)); } class PlanetTypeface : public TestSVGTypeface { using TestSVGTypeface::TestSVGTypeface; bool getPathOp(SkColor color, SkPathOp* op) const override { *op = SkPathOp::kUnion_SkPathOp; return true; } static constexpr SkTypeface::FactoryId FactoryId = SkSetFourByteTag('p','s','v','g'); static constexpr const char gHeaderString[] = "SkTestSVGTypefacePlanet01"; static constexpr const size_t kHeaderSize = sizeof(gHeaderString); std::unique_ptr onOpenStream(int* ttcIndex) const override { SkDynamicMemoryWStream wstream; wstream.write(gHeaderString, kHeaderSize); return wstream.detachAsStream(); } static sk_sp MakeFromStream(std::unique_ptr stream, const SkFontArguments&) { char header[kHeaderSize]; if (stream->read(header, kHeaderSize) != kHeaderSize || 0 != memcmp(header, gHeaderString, kHeaderSize)) { return nullptr; } return TestSVGTypeface::Planets(); } void onGetFontDescriptor(SkFontDescriptor* desc, bool* isLocal) const override { TestSVGTypeface::onGetFontDescriptor(desc, isLocal); desc->setFactoryId(FactoryId); } public: struct Register { Register() { SkTypeface::Register(FactoryId, &MakeFromStream); } }; }; static PlanetTypeface::Register planetTypefaceRegister; sk_sp TestSVGTypeface::Planets() { // Recommended that the first four be .notdef, .null, CR, space constexpr const static SkSVGTestTypefaceGlyphData glyphs[] = { {"fonts/svg/planets/pluto.svg", {0, 20}, 60, 0x0}, // .notdef {"fonts/svg/empty.svg", {0, 0}, 400, 0x0020}, // space {"fonts/svg/planets/mercury.svg", {0, 45}, 120, 0x263F}, // ☿ {"fonts/svg/planets/venus.svg", {0, 100}, 240, 0x2640}, // ♀ {"fonts/svg/planets/earth.svg", {0, 100}, 240, 0x2641}, // ♁ {"fonts/svg/planets/mars.svg", {0, 50}, 130, 0x2642}, // ♂ {"fonts/svg/planets/jupiter.svg", {0, 1000}, 2200, 0x2643}, // ♃ {"fonts/svg/planets/saturn.svg", {-300, 1500}, 2600, 0x2644}, // ♄ {"fonts/svg/planets/uranus.svg", {0, 375}, 790, 0x2645}, // ♅ {"fonts/svg/planets/neptune.svg", {0, 350}, 740, 0x2646}, // ♆ }; SkFontMetrics metrics; metrics.fFlags = SkFontMetrics::kUnderlineThicknessIsValid_Flag | SkFontMetrics::kUnderlinePositionIsValid_Flag | SkFontMetrics::kStrikeoutThicknessIsValid_Flag | SkFontMetrics::kStrikeoutPositionIsValid_Flag; metrics.fTop = -1500; metrics.fAscent = -200; metrics.fDescent = 50; metrics.fBottom = 1558; metrics.fLeading = 10; metrics.fAvgCharWidth = 200; metrics.fMaxCharWidth = 200; metrics.fXMin = -300; metrics.fXMax = 2566; metrics.fXHeight = 100; metrics.fCapHeight = 180; metrics.fUnderlineThickness = 8; metrics.fUnderlinePosition = 2; metrics.fStrikeoutThickness = 2; metrics.fStrikeoutPosition = -80; return sk_sp( new PlanetTypeface("Planets", SkFontStyle::Normal(), 200, metrics, glyphs)); } void TestSVGTypeface::exportTtxCommon(SkWStream* out, const char* type, const TArray* glyfInfo) const { int totalGlyphs = fGlyphCount; out->writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { out->writeText(" writeHexAsText(i, 4); out->writeText("\"/>\n"); } if (glyfInfo) { for (int i = 0; i < fGlyphCount; ++i) { for (int j = 0; j < (*glyfInfo)[i].fLayers.size(); ++j) { out->writeText(" writeHexAsText(i, 4); out->writeText("l"); out->writeHexAsText(j, 4); out->writeText("\"/>\n"); ++totalGlyphs; } } } out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(fUpem); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); // TODO: not recalculated for bitmap fonts? out->writeText(" writeScalarAsText(fFontMetrics.fXMin); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(-fFontMetrics.fBottom); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(fFontMetrics.fXMax); out->writeText("\"/>\n"); out->writeText(" 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(" write(macStyle, 8); out->writeText(" "); out->write(macStyle + 8, 8); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(-fFontMetrics.fAscent); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(-fFontMetrics.fDescent); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(fFontMetrics.fLeading); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(fFontMetrics.fXMax - fFontMetrics.fXMin); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); // Some of this table is going to be re-calculated, but we have to write it out anyway. out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(totalGlyphs); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(fFontMetrics.fAvgCharWidth); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(this->fontStyle().weight()); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(this->fontStyle().width()); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(fFontMetrics.fStrikeoutThickness); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(-fFontMetrics.fStrikeoutPosition); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \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(" write(fsSelection, 8); out->writeText(" "); out->write(fsSelection + 8, 8); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(-fFontMetrics.fAscent); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(-fFontMetrics.fDescent); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(fFontMetrics.fLeading); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(-fFontMetrics.fAscent); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(fFontMetrics.fDescent); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(fFontMetrics.fXHeight); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(fFontMetrics.fCapHeight); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { out->writeText(" 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 < fGlyphCount; ++i) { for (int j = 0; j < (*glyfInfo)[i].fLayers.size(); ++j) { out->writeText(" 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(" \n"); bool hasNonBMP = false; out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); fCMap.foreach ([&out, &hasNonBMP](const SkUnichar& c, const SkGlyphID& g) { if (0xFFFF < c) { hasNonBMP = true; return; } out->writeText(" writeHexAsText(c, 4); out->writeText("\" name=\"glyf"); out->writeHexAsText(g, 4); out->writeText("\"/>\n"); }); out->writeText(" \n"); if (hasNonBMP) { out->writeText( " \n"); fCMap.foreach ([&out](const SkUnichar& c, const SkGlyphID& g) { out->writeText(" writeHexAsText(c, 6); out->writeText("\" name=\"glyf"); out->writeHexAsText(g, 4); out->writeText("\"/>\n"); }); out->writeText(" \n"); } out->writeText(" \n"); out->writeText(" \n"); out->writeText( " \n"); out->writeText(" "); out->writeText(fName.c_str()); out->writeText(" "); out->writeText(type); out->writeText("\n"); out->writeText(" \n"); out->writeText( " \n"); out->writeText(" Regular\n"); out->writeText(" \n"); out->writeText( " \n"); out->writeText(" "); out->writeText(fName.c_str()); out->writeText("_"); out->writeText(type); out->writeText("\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeScalarAsText(fFontMetrics.fUnderlinePosition); out->writeText("\"/>\n"); out->writeText(" writeScalarAsText(fFontMetrics.fUnderlineThickness); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); } void TestSVGTypeface::exportTtxCbdt(SkWStream* out, SkSpan strikeSizes) const { SkPaint paint; SkFont font; font.setTypeface(sk_ref_sp(const_cast(this))); SkString name; this->getFamilyName(&name); // The CBDT/CBLC format is quite restrictive. Only write strikes which fully fit. STArray<8, int> goodStrikeSizes; for (size_t strikeIndex = 0; strikeIndex < strikeSizes.size(); ++strikeIndex) { font.setSize(strikeSizes[strikeIndex]); // CBLC limits SkFontMetrics fm; font.getMetrics(&fm); if (!SkTFitsIn((int)(-fm.fTop)) || !SkTFitsIn((int)(-fm.fBottom)) || !SkTFitsIn((int)(fm.fXMax - fm.fXMin))) { SkDebugf("Metrics too big cbdt font size %f for %s.\n", font.getSize(), name.c_str()); continue; } // CBDT limits auto exceedsCbdtLimits = [&]() { for (int i = 0; i < fGlyphCount; ++i) { SkGlyphID gid = i; SkScalar advance; SkRect bounds; font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr); SkIRect ibounds = bounds.roundOut(); if (!SkTFitsIn(ibounds.fLeft) || !SkTFitsIn(ibounds.fTop) || !SkTFitsIn(ibounds.width()) || !SkTFitsIn(ibounds.height()) || !SkTFitsIn((int)advance)) { return true; } } return false; }; if (exceedsCbdtLimits()) { SkDebugf("Glyphs too big cbdt font size %f for %s.\n", font.getSize(), name.c_str()); continue; } goodStrikeSizes.emplace_back(strikeSizes[strikeIndex]); } if (goodStrikeSizes.empty()) { SkDebugf("No strike size fit for cbdt font for %s.\n", name.c_str()); return; } out->writeText("\n"); out->writeText("\n"); this->exportTtxCommon(out, "CBDT"); out->writeText(" \n"); out->writeText("
\n"); for (int strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) { font.setSize(goodStrikeSizes[strikeIndex]); out->writeText(" writeDecAsText(strikeIndex); out->writeText("\">\n"); for (int i = 0; i < fGlyphCount; ++i) { SkGlyphID gid = i; SkScalar advance; SkRect bounds; font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr); SkIRect ibounds = bounds.roundOut(); if (ibounds.isEmpty()) { continue; } SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height()); sk_sp surface(SkSurfaces::Raster(image_info)); SkASSERT(surface); SkCanvas* canvas = surface->getCanvas(); canvas->clear(0); SkPixmap pix; surface->peekPixels(&pix); canvas->drawSimpleText(&gid, sizeof(gid), SkTextEncoding::kGlyphID, -bounds.fLeft, -bounds.fTop, font, paint); sk_sp image = surface->makeImageSnapshot(); sk_sp data = SkPngEncoder::Encode(nullptr, image.get(), {}); out->writeText(" writeHexAsText(i, 4); out->writeText("\">\n"); out->writeText(" \n"); out->writeText(" writeDecAsText(image->height()); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(image->width()); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(ibounds.fLeft); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(-ibounds.fTop); out->writeText("\"/>\n"); out->writeText(" writeDecAsText((int)advance); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" "); uint8_t const* bytes = data->bytes(); for (size_t j = 0; j < data->size(); ++j) { if ((j % 0x10) == 0x0) { out->writeText("\n "); } else if (((j - 1) % 0x4) == 0x3) { out->writeText(" "); } out->writeHexAsText(bytes[j], 2); } out->writeText("\n"); out->writeText(" \n"); out->writeText(" \n"); } out->writeText(" \n"); } out->writeText(" \n"); SkFontMetrics fm; out->writeText(" \n"); out->writeText("
\n"); for (int strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) { font.setSize(goodStrikeSizes[strikeIndex]); font.getMetrics(&fm); out->writeText(" writeDecAsText(strikeIndex); out->writeText("\">\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText((int)(-fm.fTop)); out->writeText("\"/>\n"); out->writeText(" writeDecAsText((int)(-fm.fBottom)); out->writeText("\"/>\n"); out->writeText(" writeDecAsText((int)(fm.fXMax - fm.fXMin)); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText((int)(-fm.fTop)); out->writeText("\"/>\n"); out->writeText(" writeDecAsText((int)(-fm.fBottom)); out->writeText("\"/>\n"); out->writeText(" writeDecAsText((int)(fm.fXMax - fm.fXMin)); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(goodStrikeSizes[strikeIndex]); out->writeText("\"/>\n"); out->writeText(" writeDecAsText(goodStrikeSizes[strikeIndex]); out->writeText("\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText( " \n"); for (int i = 0; i < fGlyphCount; ++i) { SkGlyphID gid = i; SkRect bounds; font.getBounds(&gid, 1, &bounds, nullptr); if (bounds.isEmpty()) { continue; } out->writeText(" writeHexAsText(i, 4); out->writeText("\"/>\n"); } out->writeText(" \n"); out->writeText(" \n"); } out->writeText(" \n"); out->writeText("\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 a degenerate line with points at the edge of the * bounding box of the glyph. * * See the SBIX slide in viewer for how positioning and bounds work. CoreText sbix is buggy in the * way it applies the glyf bbox values (only to one side). * The bbox in DWrite is ((0, 0),(png.width, png.height)) + originOffset * The bbox in FreeType is ((0, 0),(png.width, png.height)) + (lsb, bbox.yMin) + originOffset. * The bbox in CoreText is ((lsb, bbox.yMin), (lsb + bbox.xMax - bbox.xMin, bbox.yMax)) * In FreeType and DWrite the originOffsetX/Y apply to the bitmap and bounds. * In CoreText the originOffsetX/Y apply only to the bitmap (and not the bounds). * * The only way to create a compatibly positioned sbix bitmap glyph is to set * lsb = 0, bbox = ((0,0),png.size), originOffset = (0,0) and pad the png with transparent pixels. * This of course can only move the image up and to the right. * * To work with just CoreText and FreeType 2.12.0+ (DWrite having no offset) * lsb = x, bbox = ((0, y),(png.width, png.height + y)), originOffset = (0,0) * Which this does, since DWrite should be adding the lsb and bbox.yMin. */ void TestSVGTypeface::exportTtxSbix(SkWStream* out, SkSpan strikeSizes) const { out->writeText("\n"); out->writeText("\n"); this->exportTtxCommon(out, "sbix"); SkPaint paint; SkFont font; font.setTypeface(sk_ref_sp(const_cast(this))); out->writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { const TestSVGTypeface::Glyph& glyphData = this->fGlyphs[i]; SkSize containerSize = glyphData.size(); SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY, containerSize.fWidth, containerSize.fHeight); SkIRect ibounds = bounds.roundOut(); out->writeText(" writeHexAsText(i, 4); out->writeText("\" xMin=\""); out->writeDecAsText(/*ibounds.fLeft*/0); //hmtx::lsb already has this from common out->writeText("\" yMin=\""); out->writeDecAsText(-ibounds.fBottom); out->writeText("\" xMax=\""); out->writeDecAsText(ibounds.fRight - ibounds.fLeft); out->writeText("\" yMax=\""); out->writeDecAsText(-ibounds.fTop); out->writeText("\">\n"); out->writeText(" \n"); out->writeText(" writeDecAsText(/*ibounds.fLeft*/0); out->writeText("\" y=\""); out->writeDecAsText(-ibounds.fBottom); out->writeText("\" on=\"1\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(ibounds.fRight - ibounds.fLeft); out->writeText("\" y=\""); out->writeDecAsText(-ibounds.fTop); out->writeText("\" on=\"1\"/>\n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); } out->writeText(" \n"); // The loca table will be re-calculated, but if we don't write one we don't get one. out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" \n"); for (size_t strikeIndex = 0; strikeIndex < strikeSizes.size(); ++strikeIndex) { font.setSize(strikeSizes[strikeIndex]); out->writeText(" \n"); out->writeText(" writeDecAsText(strikeSizes[strikeIndex]); out->writeText("\"/>\n"); out->writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { SkGlyphID gid = i; SkScalar advance; SkRect bounds; font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr); SkIRect ibounds = bounds.roundOut(); if (ibounds.isEmpty()) { continue; } SkImageInfo image_info = SkImageInfo::MakeN32Premul(ibounds.width(), ibounds.height()); sk_sp surface(SkSurfaces::Raster(image_info)); SkASSERT(surface); SkCanvas* canvas = surface->getCanvas(); canvas->clear(0); SkPixmap pix; surface->peekPixels(&pix); canvas->drawSimpleText(&gid, sizeof(gid), SkTextEncoding::kGlyphID, -bounds.fLeft, -bounds.fTop, font, paint); sk_sp image = surface->makeImageSnapshot(); sk_sp data = SkPngEncoder::Encode(nullptr, image.get(), {}); out->writeText(" writeHexAsText(i, 4); // DirectWrite and CoreGraphics use positive values of originOffsetY to push the // image visually up (but from different origins). // FreeType used positive values to push the image down until 2.12.0. // However, in a bitmap only font there is little reason for these to not be zero. out->writeText("\" graphicType=\"png \" originOffsetX=\"0\" originOffsetY=\"0\">\n"); out->writeText(" "); uint8_t const* bytes = data->bytes(); for (size_t j = 0; j < data->size(); ++j) { if ((j % 0x10) == 0x0) { out->writeText("\n "); } else if (((j - 1) % 0x4) == 0x3) { out->writeText(" "); } out->writeHexAsText(bytes[j], 2); } out->writeText("\n"); out->writeText(" \n"); out->writeText(" \n"); } out->writeText(" \n"); } out->writeText(" \n"); out->writeText("\n"); } namespace { void convert_noninflect_cubic_to_quads(const SkPoint p[4], SkScalar toleranceSqd, TArray* 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, TArray* 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(); TArray qPts; SkAutoConicToQuads converter; const SkPoint* quadPts; for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { switch (verb) { case SkPathVerb::kMove: quadPath->moveTo(pts[0].fX, pts[0].fY); break; case SkPathVerb::kLine: quadPath->lineTo(pts[1].fX, pts[1].fY); break; case SkPathVerb::kQuad: quadPath->quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY); break; case SkPathVerb::kCubic: qPts.clear(); convertCubicToQuads(pts, SK_Scalar1, &qPts); for (int i = 0; i < qPts.size(); i += 3) { quadPath->quadTo( qPts[i + 1].fX, qPts[i + 1].fY, qPts[i + 2].fX, qPts[i + 2].fY); } break; case SkPathVerb::kConic: quadPts = converter.computeQuads(pts, *w, 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 SkPathVerb::kClose: quadPath->close(); break; } } } class SkCOLRCanvas : public SkNoDrawCanvas { public: SkCOLRCanvas(SkRect glyphBounds, const TestSVGTypeface& typeface, SkGlyphID glyphId, TestSVGTypeface::GlyfInfo* glyf, THashMap* colors, SkWStream* out) : SkNoDrawCanvas(glyphBounds.roundOut().width(), glyphBounds.roundOut().height()) , fBaselineOffset(glyphBounds.top()) , fTypeface(typeface) , fGlyphId(glyphId) , fGlyf(glyf) , fColors(colors) , fOut(out) , fLayerId(0) {} void writePoint(SkScalar x, SkScalar y, bool on) { fOut->writeText(" 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(" 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"); bool contourOpen = false; for (auto [verb, pts, w] : SkPathPriv::Iterate(quads)) { switch (verb) { case SkPathVerb::kMove: if (contourOpen) { fOut->writeText(" \n"); contourOpen = false; } break; case SkPathVerb::kLine: if (!contourOpen) { fOut->writeText(" \n"); this->writePoint(pts[0].fX, pts[0].fY, true); contourOpen = true; } this->writePoint(pts[1].fX, pts[1].fY, true); break; case SkPathVerb::kQuad: if (!contourOpen) { fOut->writeText(" \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 SkPathVerb::kClose: if (contourOpen) { fOut->writeText(" \n"); contourOpen = false; } break; default: SkDEBUGFAIL("bad verb"); return ibounds; } } if (contourOpen) { fOut->writeText(" \n"); } // Required to write out an instructions tag. fOut->writeText(" \n"); fOut->writeText(" \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, SkArc::Make(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 = skpathutils::FillPathWithPaint(path, paint, &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(); SkPathOp op; if (fTypeface.getPathOp(color, &op)) { fBasePath.add(path, op); } 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: SkScalar fBaselineOffset; const TestSVGTypeface& fTypeface; SkGlyphID fGlyphId; TestSVGTypeface::GlyfInfo* fGlyf; THashMap* fColors; SkWStream* const fOut; SkOpBuilder fBasePath; int fLayerId; }; } // namespace void TestSVGTypeface::exportTtxColr(SkWStream* out) const { out->writeText("\n"); out->writeText("\n"); THashMap colors; TArray glyfInfos(fGlyphCount); // Need to know all the glyphs up front for the common tables. SkDynamicMemoryWStream glyfOut; glyfOut.writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { const TestSVGTypeface::Glyph& glyphData = this->fGlyphs[i]; SkSize containerSize = glyphData.size(); SkRect bounds = SkRect::MakeXYWH(glyphData.fOrigin.fX, -glyphData.fOrigin.fY, containerSize.fWidth, containerSize.fHeight); SkCOLRCanvas canvas(bounds, *this, i, &glyfInfos.emplace_back(), &colors, &glyfOut); glyphData.render(&canvas); canvas.finishGlyph(); } glyfOut.writeText(" \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(" \n"); std::unique_ptr glyfStream = glyfOut.detachAsStream(); out->writeStream(glyfStream.get(), glyfStream->getLength()); out->writeText(" \n"); out->writeText(" \n"); for (int i = 0; i < fGlyphCount; ++i) { if (glyfInfos[i].fLayers.empty()) { continue; } if (glyfInfos[i].fBounds.isEmpty()) { SkDebugf("Glyph %d is empty but has layers.\n", i); } out->writeText(" writeHexAsText(i, 4); out->writeText("\">\n"); for (int j = 0; j < glyfInfos[i].fLayers.size(); ++j) { const int colorIndex = glyfInfos[i].fLayers[j].fLayerColorIndex; out->writeText(" writeDecAsText(colorIndex); out->writeText("\" name=\"glyf"); out->writeHexAsText(i, 4); out->writeText("l"); out->writeHexAsText(j, 4); out->writeText("\"/>\n"); } out->writeText(" \n"); } out->writeText(" \n"); // The colors must be written in order, the 'index' is ignored by ttx. AutoTMalloc colorsInOrder(colors.count()); colors.foreach ([&colorsInOrder](const SkColor& c, const int* i) { colorsInOrder[*i] = c; }); out->writeText(" \n"); out->writeText(" \n"); out->writeText(" writeDecAsText(colors.count()); out->writeText("\"/>\n"); out->writeText(" \n"); for (int i = 0; i < colors.count(); ++i) { SkColor c = colorsInOrder[i]; out->writeText(" 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(" \n"); out->writeText(" \n"); out->writeText("\n"); } #endif // SK_ENABLE_SVG