diff options
Diffstat (limited to 'tools/fonts/SkTestSVGTypeface.cpp')
-rw-r--r-- | tools/fonts/SkTestSVGTypeface.cpp | 1300 |
1 files changed, 1300 insertions, 0 deletions
diff --git a/tools/fonts/SkTestSVGTypeface.cpp b/tools/fonts/SkTestSVGTypeface.cpp new file mode 100644 index 0000000000..715a28c5f0 --- /dev/null +++ b/tools/fonts/SkTestSVGTypeface.cpp @@ -0,0 +1,1300 @@ +/* + * 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(new Glyph[dataCount]) + , fGlyphCount(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); + fGlyphs[i].fAdvance = datum.fAdvance; + fGlyphs[i].fOrigin = datum.fOrigin; + if (!stream) { + continue; + } + sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(*stream.get()); + if (!svg) { + continue; + } + + const SkSize& sz = svg->containerSize(); + if (sz.isEmpty()) { + continue; + } + + fGlyphs[i].fSvg = std::move(svg); + } +} + +SkTestSVGTypeface::~SkTestSVGTypeface() {} + +SkTestSVGTypeface::Glyph::Glyph() : fOrigin{0,0}, fAdvance(0) {} +SkTestSVGTypeface::Glyph::~Glyph() {} + +void SkTestSVGTypeface::getAdvance(SkGlyph* glyph) const { + SkGlyphID glyphID = glyph->getGlyphID(); + glyphID = glyphID < fGlyphCount ? 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); + + SkTDArray<SkUnichar>& toUnicode = info->fGlyphToUnicode; + toUnicode.setCount(fGlyphCount); + 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()->fGlyphCount ? 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()->fGlyphCount ? 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) { + SkAutoExclusive lock(glyphData.fSvgMutex); + 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 = fGlyphCount; + out->writeText(" <GlyphOrder>\n"); + for (int i = 0; i < fGlyphCount; ++i) { + out->writeText(" <GlyphID name=\"glyf"); + out->writeHexAsText(i, 4); + out->writeText("\"/>\n"); + } + if (glyfInfo) { + for (int i = 0; i < fGlyphCount; ++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 < fGlyphCount; ++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 < fGlyphCount; ++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 < fGlyphCount; ++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 < fGlyphCount; ++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 a 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 < fGlyphCount; ++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 < fGlyphCount; ++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(fGlyphCount); + + // Need to know all the glyphs up front for the common tables. + SkDynamicMemoryWStream glyfOut; + glyfOut.writeText(" <glyf>\n"); + for (int i = 0; i < fGlyphCount; ++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 < fGlyphCount; ++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"); +} |