aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/fonts/SkTestSVGTypeface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/fonts/SkTestSVGTypeface.cpp')
-rw-r--r--tools/fonts/SkTestSVGTypeface.cpp1300
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");
+}