| /* |
| * 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 <utility> |
| |
| using namespace skia_private; |
| |
| class SkDescriptor; |
| |
| TestSVGTypeface::TestSVGTypeface(const char* name, |
| int upem, |
| const SkFontMetrics& fontMetrics, |
| SkSpan<const SkSVGTestTypefaceGlyphData> data, |
| const SkFontStyle& style) |
| : 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 <typename Fn> |
| void TestSVGTypeface::Glyph::withSVG(Fn&& fn) const { |
| SkAutoMutexExclusive lock(fSvgMutex); |
| |
| if (!fParsedSvg) { |
| fParsedSvg = true; |
| |
| std::unique_ptr<SkStreamAsset> 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 <text> 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<SkSVGDOM> 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<SkAdvancedTypefaceMetrics> TestSVGTypeface::onGetAdvancedMetrics() const { |
| std::unique_ptr<SkAdvancedTypefaceMetrics> 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(sk_sp<TestSVGTypeface> face, |
| const SkScalerContextEffects& effects, |
| const SkDescriptor* desc) |
| : SkScalerContext(std::move(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<TestSVGTypeface*>(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) 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<SkDrawable> generateDrawable(const SkGlyph& glyph) override { |
| return sk_sp<SVGGlyphDrawable>(new SVGGlyphDrawable(this, glyph)); |
| } |
| |
| void generateFontMetrics(SkFontMetrics* metrics) override { |
| this->getTestSVGTypeface()->getFontMetrics(metrics); |
| SkFontPriv::ScaleFontMetrics(metrics, fMatrix.getScaleY()); |
| } |
| |
| private: |
| SkMatrix fMatrix; |
| }; |
| |
| std::unique_ptr<SkScalerContext> TestSVGTypeface::onCreateScalerContext( |
| const SkScalerContextEffects& e, const SkDescriptor* desc) const |
| { |
| return std::make_unique<SkTestSVGScalerContext>( |
| sk_ref_sp(const_cast<TestSVGTypeface*>(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<SkStreamAsset> onOpenStream(int* ttcIndex) const override { |
| SkDynamicMemoryWStream wstream; |
| wstream.write(gHeaderString, kHeaderSize); |
| return wstream.detachAsStream(); |
| } |
| |
| static sk_sp<SkTypeface> MakeFromStream(std::unique_ptr<SkStreamAsset> 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> 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<TestSVGTypeface>( |
| new DefaultTypeface("Emoji", 1000, metrics, glyphs, SkFontStyle::Normal())); |
| } |
| |
| 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<SkStreamAsset> onOpenStream(int* ttcIndex) const override { |
| SkDynamicMemoryWStream wstream; |
| wstream.write(gHeaderString, kHeaderSize); |
| return wstream.detachAsStream(); |
| } |
| |
| static sk_sp<SkTypeface> MakeFromStream(std::unique_ptr<SkStreamAsset> 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> 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<TestSVGTypeface>( |
| new PlanetTypeface("Planets", 200, metrics, glyphs, SkFontStyle::Normal())); |
| } |
| |
| void TestSVGTypeface::exportTtxCommon(SkWStream* out, |
| const char* type, |
| const TArray<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.size(); ++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.size(); ++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 TestSVGTypeface::exportTtxCbdt(SkWStream* out, SkSpan<unsigned> strikeSizes) const { |
| SkPaint paint; |
| SkFont font; |
| font.setTypeface(sk_ref_sp(const_cast<TestSVGTypeface*>(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<int8_t>((int)(-fm.fTop)) || !SkTFitsIn<int8_t>((int)(-fm.fBottom)) || |
| !SkTFitsIn<uint8_t>((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<int8_t>(ibounds.fLeft) || !SkTFitsIn<int8_t>(ibounds.fTop) || |
| !SkTFitsIn<uint8_t>(ibounds.width()) || !SkTFitsIn<uint8_t>(ibounds.height()) || |
| !SkTFitsIn<uint8_t>((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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| out->writeText("<ttFont sfntVersion=\"\\x00\\x01\\x00\\x00\" ttLibVersion=\"3.19\">\n"); |
| this->exportTtxCommon(out, "CBDT"); |
| |
| out->writeText(" <CBDT>\n"); |
| out->writeText(" <header version=\"2.0\"/>\n"); |
| for (int strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) { |
| font.setSize(goodStrikeSizes[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; |
| 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<SkSurface> 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<SkImage> image = surface->makeImageSnapshot(); |
| sk_sp<SkData> data = SkPngEncoder::Encode(nullptr, image.get(), {}); |
| |
| 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(ibounds.fLeft); |
| out->writeText("\"/>\n"); |
| out->writeText(" <BearingY value=\""); |
| out->writeDecAsText(-ibounds.fTop); |
| out->writeText("\"/>\n"); |
| out->writeText(" <Advance value=\""); |
| out->writeDecAsText((int)advance); |
| out->writeText("\"/>\n"); |
| out->writeText(" </SmallGlyphMetrics>\n"); |
| out->writeText(" <rawimagedata>"); |
| 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(" </rawimagedata>\n"); |
| out->writeText(" </cbdt_bitmap_format_17>\n"); |
| } |
| out->writeText(" </strikedata>\n"); |
| } |
| out->writeText(" </CBDT>\n"); |
| |
| SkFontMetrics fm; |
| out->writeText(" <CBLC>\n"); |
| out->writeText(" <header version=\"2.0\"/>\n"); |
| for (int strikeIndex = 0; strikeIndex < goodStrikeSizes.size(); ++strikeIndex) { |
| font.setSize(goodStrikeSizes[strikeIndex]); |
| font.getMetrics(&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->writeDecAsText((int)(-fm.fTop)); |
| out->writeText("\"/>\n"); |
| out->writeText(" <descender value=\""); |
| out->writeDecAsText((int)(-fm.fBottom)); |
| out->writeText("\"/>\n"); |
| out->writeText(" <widthMax value=\""); |
| out->writeDecAsText((int)(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->writeDecAsText((int)(-fm.fTop)); |
| out->writeText("\"/>\n"); |
| out->writeText(" <descender value=\""); |
| out->writeDecAsText((int)(-fm.fBottom)); |
| out->writeText("\"/>\n"); |
| out->writeText(" <widthMax value=\""); |
| out->writeDecAsText((int)(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(goodStrikeSizes[strikeIndex]); |
| out->writeText("\"/>\n"); |
| out->writeText(" <ppemY value=\""); |
| out->writeDecAsText(goodStrikeSizes[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; |
| font.getBounds(&gid, 1, &bounds, nullptr); |
| 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 TestSVGTypeface::exportTtxSbix(SkWStream* out, SkSpan<unsigned> strikeSizes) 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; |
| SkFont font; |
| font.setTypeface(sk_ref_sp(const_cast<TestSVGTypeface*>(this))); |
| |
| out->writeText(" <glyf>\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(" <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"); |
| |
| out->writeText(" <sbix>\n"); |
| out->writeText(" <version value=\"1\"/>\n"); |
| out->writeText(" <flags value=\"00000000 00000001\"/>\n"); |
| for (size_t strikeIndex = 0; strikeIndex < strikeSizes.size(); ++strikeIndex) { |
| font.setSize(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; |
| 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<SkSurface> 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<SkImage> image = surface->makeImageSnapshot(); |
| sk_sp<SkData> data = SkPngEncoder::Encode(nullptr, image.get(), {}); |
| |
| // The originOffset values are difficult to use as DirectWrite and FreeType interpret |
| // the origin to be the initial glyph position on the baseline, but CoreGraphics |
| // interprets the origin to be the lower left of the cbox of the outline in the 'glyf' |
| // table. |
| //#define SK_SBIX_LIKE_FT |
| //#define SK_SBIX_LIKE_DW |
| out->writeText(" <glyph name=\"glyf"); |
| out->writeHexAsText(i, 4); |
| out->writeText("\" graphicType=\"png \" originOffsetX=\""); |
| #if defined(SK_SBIX_LIKE_FT) || defined(SK_SBIX_LIKE_DW) |
| out->writeDecAsText(bounds.fLeft); |
| #else |
| out->writeDecAsText(0); |
| #endif |
| // DirectWrite and CoreGraphics use positive values of originOffsetY to push the |
| // image visually up (but from different origins). |
| // FreeType uses positive values to push the image down. |
| out->writeText("\" originOffsetY=\""); |
| #if defined(SK_SBIX_LIKE_FT) |
| out->writeScalarAsText(bounds.fBottom); |
| #elif defined(SK_SBIX_LIKE_DW) |
| out->writeScalarAsText(-bounds.fBottom); |
| #else |
| out->writeDecAsText(0); |
| #endif |
| out->writeText("\">\n"); |
| |
| out->writeText(" <hexdata>"); |
| 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(" </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, |
| TArray<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, TArray<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(); |
| TArray<SkPoint, true> 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<SkColor, int>* 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(" <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"); |
| |
| bool contourOpen = false; |
| for (auto [verb, pts, w] : SkPathPriv::Iterate(quads)) { |
| switch (verb) { |
| case SkPathVerb::kMove: |
| if (contourOpen) { |
| fOut->writeText(" </contour>\n"); |
| contourOpen = false; |
| } |
| break; |
| case SkPathVerb::kLine: |
| 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 SkPathVerb::kQuad: |
| 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 SkPathVerb::kClose: |
| 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, 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<SkColor, int>* fColors; |
| SkWStream* const fOut; |
| SkOpBuilder fBasePath; |
| int fLayerId; |
| }; |
| |
| } // namespace |
| |
| void TestSVGTypeface::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"); |
| |
| THashMap<SkColor, int> colors; |
| TArray<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 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(" </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].fLayers.empty()) { |
| continue; |
| } |
| if (glyfInfos[i].fBounds.isEmpty()) { |
| SkDebugf("Glyph %d is empty but has layers.\n", i); |
| } |
| out->writeText(" <ColorGlyph name=\"glyf"); |
| out->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(" <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. |
| AutoTMalloc<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"); |
| } |
| #endif // SK_ENABLE_SVG |