summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/pdf/SkPDFFont.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/pdf/SkPDFFont.cpp')
-rw-r--r--gfx/skia/skia/src/pdf/SkPDFFont.cpp724
1 files changed, 724 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/pdf/SkPDFFont.cpp b/gfx/skia/skia/src/pdf/SkPDFFont.cpp
new file mode 100644
index 0000000000..964c9aeb23
--- /dev/null
+++ b/gfx/skia/skia/src/pdf/SkPDFFont.cpp
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkBitmap.h"
+#include "include/core/SkData.h"
+#include "include/core/SkFont.h"
+#include "include/core/SkFontMetrics.h"
+#include "include/core/SkFontTypes.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkPath.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkString.h"
+#include "include/core/SkSurfaceProps.h"
+#include "include/core/SkTypes.h"
+#include "include/docs/SkPDFDocument.h"
+#include "include/private/SkBitmaskEnum.h"
+#include "include/private/base/SkTo.h"
+#include "src/base/SkUTF.h"
+#include "src/core/SkGlyph.h"
+#include "src/core/SkImagePriv.h"
+#include "src/core/SkMask.h"
+#include "src/core/SkScalerContext.h"
+#include "src/core/SkStrike.h"
+#include "src/core/SkStrikeSpec.h"
+#include "src/core/SkTHash.h"
+#include "src/pdf/SkPDFBitmap.h"
+#include "src/pdf/SkPDFDevice.h"
+#include "src/pdf/SkPDFDocumentPriv.h"
+#include "src/pdf/SkPDFFont.h"
+#include "src/pdf/SkPDFFormXObject.h"
+#include "src/pdf/SkPDFMakeCIDGlyphWidthsArray.h"
+#include "src/pdf/SkPDFMakeToUnicodeCmap.h"
+#include "src/pdf/SkPDFSubsetFont.h"
+#include "src/pdf/SkPDFType1Font.h"
+#include "src/pdf/SkPDFUtils.h"
+
+#include <limits.h>
+#include <initializer_list>
+#include <memory>
+#include <utility>
+
+void SkPDFFont::GetType1GlyphNames(const SkTypeface& face, SkString* dst) {
+ face.getPostScriptGlyphNames(dst);
+}
+
+namespace {
+// PDF's notion of symbolic vs non-symbolic is related to the character set, not
+// symbols vs. characters. Rarely is a font the right character set to call it
+// non-symbolic, so always call it symbolic. (PDF 1.4 spec, section 5.7.1)
+static const int32_t kPdfSymbolic = 4;
+static const SkFontTableTag kCOLRTableTag = SkSetFourByteTag('C', 'O', 'L', 'R');
+
+// scale from em-units to base-1000, returning as a SkScalar
+inline SkScalar from_font_units(SkScalar scaled, uint16_t emSize) {
+ return emSize == 1000 ? scaled : scaled * 1000 / emSize;
+}
+
+inline SkScalar scaleFromFontUnits(int16_t val, uint16_t emSize) {
+ return from_font_units(SkIntToScalar(val), emSize);
+}
+
+void setGlyphWidthAndBoundingBox(SkScalar width, SkIRect box,
+ SkDynamicMemoryWStream* content) {
+ // Specify width and bounding box for the glyph.
+ SkPDFUtils::AppendScalar(width, content);
+ content->writeText(" 0 ");
+ content->writeDecAsText(box.fLeft);
+ content->writeText(" ");
+ content->writeDecAsText(box.fTop);
+ content->writeText(" ");
+ content->writeDecAsText(box.fRight);
+ content->writeText(" ");
+ content->writeDecAsText(box.fBottom);
+ content->writeText(" d1\n");
+}
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// class SkPDFFont
+///////////////////////////////////////////////////////////////////////////////
+
+/* Resources are canonicalized and uniqueified by pointer so there has to be
+ * some additional state indicating which subset of the font is used. It
+ * must be maintained at the document granularity.
+ */
+
+SkPDFFont::~SkPDFFont() = default;
+
+SkPDFFont::SkPDFFont(SkPDFFont&&) = default;
+
+SkPDFFont& SkPDFFont::operator=(SkPDFFont&&) = default;
+
+static bool can_embed(const SkAdvancedTypefaceMetrics& metrics) {
+ return !SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag);
+}
+
+const SkAdvancedTypefaceMetrics* SkPDFFont::GetMetrics(const SkTypeface* typeface,
+ SkPDFDocument* canon) {
+ SkASSERT(typeface);
+ SkTypefaceID id = typeface->uniqueID();
+ if (std::unique_ptr<SkAdvancedTypefaceMetrics>* ptr = canon->fTypefaceMetrics.find(id)) {
+ return ptr->get(); // canon retains ownership.
+ }
+ int count = typeface->countGlyphs();
+ if (count <= 0 || count > 1 + SkTo<int>(UINT16_MAX)) {
+ // Cache nullptr to skip this check. Use SkSafeUnref().
+ canon->fTypefaceMetrics.set(id, nullptr);
+ return nullptr;
+ }
+ std::unique_ptr<SkAdvancedTypefaceMetrics> metrics = typeface->getAdvancedMetrics();
+ if (!metrics) {
+ metrics = std::make_unique<SkAdvancedTypefaceMetrics>();
+ }
+
+ if (0 == metrics->fStemV || 0 == metrics->fCapHeight) {
+ SkFont font;
+ font.setHinting(SkFontHinting::kNone);
+ font.setTypeface(sk_ref_sp(typeface));
+ font.setSize(1000); // glyph coordinate system
+ if (0 == metrics->fStemV) {
+ // Figure out a good guess for StemV - Min width of i, I, !, 1.
+ // This probably isn't very good with an italic font.
+ int16_t stemV = SHRT_MAX;
+ for (char c : {'i', 'I', '!', '1'}) {
+ uint16_t g = font.unicharToGlyph(c);
+ SkRect bounds;
+ font.getBounds(&g, 1, &bounds, nullptr);
+ stemV = std::min(stemV, SkToS16(SkScalarRoundToInt(bounds.width())));
+ }
+ metrics->fStemV = stemV;
+ }
+ if (0 == metrics->fCapHeight) {
+ // Figure out a good guess for CapHeight: average the height of M and X.
+ SkScalar capHeight = 0;
+ for (char c : {'M', 'X'}) {
+ uint16_t g = font.unicharToGlyph(c);
+ SkRect bounds;
+ font.getBounds(&g, 1, &bounds, nullptr);
+ capHeight += bounds.height();
+ }
+ metrics->fCapHeight = SkToS16(SkScalarRoundToInt(capHeight / 2));
+ }
+ }
+ // Fonts are always subset, so always prepend the subset tag.
+ metrics->fPostScriptName.prepend(canon->nextFontSubsetTag());
+ return canon->fTypefaceMetrics.set(id, std::move(metrics))->get();
+}
+
+const std::vector<SkUnichar>& SkPDFFont::GetUnicodeMap(const SkTypeface* typeface,
+ SkPDFDocument* canon) {
+ SkASSERT(typeface);
+ SkASSERT(canon);
+ SkTypefaceID id = typeface->uniqueID();
+ if (std::vector<SkUnichar>* ptr = canon->fToUnicodeMap.find(id)) {
+ return *ptr;
+ }
+ std::vector<SkUnichar> buffer(typeface->countGlyphs());
+ typeface->getGlyphToUnicodeMap(buffer.data());
+ return *canon->fToUnicodeMap.set(id, std::move(buffer));
+}
+
+SkAdvancedTypefaceMetrics::FontType SkPDFFont::FontType(const SkTypeface& typeface,
+ const SkAdvancedTypefaceMetrics& metrics) {
+ if (SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kVariable_FontFlag) ||
+ // PDF is actually interested in the encoding of the data, not just the logical format.
+ // If the TrueType is actually wOFF or wOF2 then it should not be directly embedded in PDF.
+ // For now export these as Type3 until the subsetter can handle table based fonts.
+ // See https://github.com/harfbuzz/harfbuzz/issues/3609 and
+ // https://skia-review.googlesource.com/c/skia/+/543485
+ SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kAltDataFormat_FontFlag) ||
+ SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag)) {
+ // force Type3 fallback.
+ return SkAdvancedTypefaceMetrics::kOther_Font;
+ }
+ if (typeface.getTableSize(kCOLRTableTag)) {
+ // https://bugs.chromium.org/p/skia/issues/detail?id=12650
+ // Don't embed COLRv0 / COLRv1 fonts, fall back to bitmaps.
+ return SkAdvancedTypefaceMetrics::kOther_Font;
+ }
+ return metrics.fType;
+}
+
+static SkGlyphID first_nonzero_glyph_for_single_byte_encoding(SkGlyphID gid) {
+ return gid != 0 ? gid - (gid - 1) % 255 : 1;
+}
+
+SkPDFFont* SkPDFFont::GetFontResource(SkPDFDocument* doc,
+ const SkGlyph* glyph,
+ SkTypeface* face) {
+ SkASSERT(doc);
+ SkASSERT(face); // All SkPDFDevice::internalDrawText ensures this.
+ const SkAdvancedTypefaceMetrics* fontMetrics = SkPDFFont::GetMetrics(face, doc);
+ SkASSERT(fontMetrics); // SkPDFDevice::internalDrawText ensures the typeface is good.
+ // GetMetrics only returns null to signify a bad typeface.
+ const SkAdvancedTypefaceMetrics& metrics = *fontMetrics;
+ SkAdvancedTypefaceMetrics::FontType type = SkPDFFont::FontType(*face, metrics);
+ if (!(glyph->isEmpty() || glyph->path())) {
+ type = SkAdvancedTypefaceMetrics::kOther_Font;
+ }
+ bool multibyte = SkPDFFont::IsMultiByte(type);
+ SkGlyphID subsetCode =
+ multibyte ? 0 : first_nonzero_glyph_for_single_byte_encoding(glyph->getGlyphID());
+ uint64_t typefaceID = (static_cast<uint64_t>(SkTypeface::UniqueID(face)) << 16) | subsetCode;
+
+ if (SkPDFFont* found = doc->fFontMap.find(typefaceID)) {
+ SkASSERT(multibyte == found->multiByteGlyphs());
+ return found;
+ }
+
+ sk_sp<SkTypeface> typeface(sk_ref_sp(face));
+ SkASSERT(typeface);
+
+ SkGlyphID lastGlyph = SkToU16(typeface->countGlyphs() - 1);
+
+ // should be caught by SkPDFDevice::internalDrawText
+ SkASSERT(glyph->getGlyphID() <= lastGlyph);
+
+ SkGlyphID firstNonZeroGlyph;
+ if (multibyte) {
+ firstNonZeroGlyph = 1;
+ } else {
+ firstNonZeroGlyph = subsetCode;
+ lastGlyph = SkToU16(std::min<int>((int)lastGlyph, 254 + (int)subsetCode));
+ }
+ auto ref = doc->reserveRef();
+ return doc->fFontMap.set(
+ typefaceID, SkPDFFont(std::move(typeface), firstNonZeroGlyph, lastGlyph, type, ref));
+}
+
+SkPDFFont::SkPDFFont(sk_sp<SkTypeface> typeface,
+ SkGlyphID firstGlyphID,
+ SkGlyphID lastGlyphID,
+ SkAdvancedTypefaceMetrics::FontType fontType,
+ SkPDFIndirectReference indirectReference)
+ : fTypeface(std::move(typeface))
+ , fGlyphUsage(firstGlyphID, lastGlyphID)
+ , fIndirectReference(indirectReference)
+ , fFontType(fontType)
+{
+ // Always include glyph 0
+ this->noteGlyphUsage(0);
+}
+
+void SkPDFFont::PopulateCommonFontDescriptor(SkPDFDict* descriptor,
+ const SkAdvancedTypefaceMetrics& metrics,
+ uint16_t emSize,
+ int16_t defaultWidth) {
+ descriptor->insertName("FontName", metrics.fPostScriptName);
+ descriptor->insertInt("Flags", (size_t)(metrics.fStyle | kPdfSymbolic));
+ descriptor->insertScalar("Ascent",
+ scaleFromFontUnits(metrics.fAscent, emSize));
+ descriptor->insertScalar("Descent",
+ scaleFromFontUnits(metrics.fDescent, emSize));
+ descriptor->insertScalar("StemV",
+ scaleFromFontUnits(metrics.fStemV, emSize));
+ descriptor->insertScalar("CapHeight",
+ scaleFromFontUnits(metrics.fCapHeight, emSize));
+ descriptor->insertInt("ItalicAngle", metrics.fItalicAngle);
+ descriptor->insertObject("FontBBox",
+ SkPDFMakeArray(scaleFromFontUnits(metrics.fBBox.left(), emSize),
+ scaleFromFontUnits(metrics.fBBox.bottom(), emSize),
+ scaleFromFontUnits(metrics.fBBox.right(), emSize),
+ scaleFromFontUnits(metrics.fBBox.top(), emSize)));
+ if (defaultWidth > 0) {
+ descriptor->insertScalar("MissingWidth",
+ scaleFromFontUnits(defaultWidth, emSize));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Type0Font
+///////////////////////////////////////////////////////////////////////////////
+
+// if possible, make no copy.
+static sk_sp<SkData> stream_to_data(std::unique_ptr<SkStreamAsset> stream) {
+ SkASSERT(stream);
+ (void)stream->rewind();
+ SkASSERT(stream->hasLength());
+ size_t size = stream->getLength();
+ if (const void* base = stream->getMemoryBase()) {
+ SkData::ReleaseProc proc =
+ [](const void*, void* ctx) { delete (SkStreamAsset*)ctx; };
+ return SkData::MakeWithProc(base, size, proc, stream.release());
+ }
+ return SkData::MakeFromStream(stream.get(), size);
+}
+
+static void emit_subset_type0(const SkPDFFont& font, SkPDFDocument* doc) {
+ const SkAdvancedTypefaceMetrics* metricsPtr =
+ SkPDFFont::GetMetrics(font.typeface(), doc);
+ SkASSERT(metricsPtr);
+ if (!metricsPtr) { return; }
+ const SkAdvancedTypefaceMetrics& metrics = *metricsPtr;
+ SkASSERT(can_embed(metrics));
+ SkAdvancedTypefaceMetrics::FontType type = font.getType();
+ SkTypeface* face = font.typeface();
+ SkASSERT(face);
+
+ auto descriptor = SkPDFMakeDict("FontDescriptor");
+ uint16_t emSize = SkToU16(font.typeface()->getUnitsPerEm());
+ SkPDFFont::PopulateCommonFontDescriptor(descriptor.get(), metrics, emSize, 0);
+
+ int ttcIndex;
+ std::unique_ptr<SkStreamAsset> fontAsset = face->openStream(&ttcIndex);
+ size_t fontSize = fontAsset ? fontAsset->getLength() : 0;
+ if (0 == fontSize) {
+ SkDebugf("Error: (SkTypeface)(%p)::openStream() returned "
+ "empty stream (%p) when identified as kType1CID_Font "
+ "or kTrueType_Font.\n", face, fontAsset.get());
+ } else {
+ switch (type) {
+ case SkAdvancedTypefaceMetrics::kTrueType_Font: {
+ if (!SkToBool(metrics.fFlags &
+ SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag)) {
+ SkASSERT(font.firstGlyphID() == 1);
+ sk_sp<SkData> subsetFontData = SkPDFSubsetFont(
+ stream_to_data(std::move(fontAsset)), font.glyphUsage(),
+ doc->metadata().fSubsetter,
+ metrics.fFontName.c_str(), ttcIndex);
+ if (subsetFontData) {
+ std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+ tmp->insertInt("Length1", SkToInt(subsetFontData->size()));
+ descriptor->insertRef(
+ "FontFile2",
+ SkPDFStreamOut(std::move(tmp),
+ SkMemoryStream::Make(std::move(subsetFontData)),
+ doc, SkPDFSteamCompressionEnabled::Yes));
+ break;
+ }
+ // If subsetting fails, fall back to original font data.
+ fontAsset = face->openStream(&ttcIndex);
+ SkASSERT(fontAsset);
+ SkASSERT(fontAsset->getLength() == fontSize);
+ if (!fontAsset || fontAsset->getLength() == 0) { break; }
+ }
+ std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+ tmp->insertInt("Length1", fontSize);
+ descriptor->insertRef("FontFile2",
+ SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+ doc, SkPDFSteamCompressionEnabled::Yes));
+ break;
+ }
+ case SkAdvancedTypefaceMetrics::kType1CID_Font: {
+ std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+ tmp->insertName("Subtype", "CIDFontType0C");
+ descriptor->insertRef("FontFile3",
+ SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+ doc, SkPDFSteamCompressionEnabled::Yes));
+ break;
+ }
+ default:
+ SkASSERT(false);
+ }
+ }
+
+ auto newCIDFont = SkPDFMakeDict("Font");
+ newCIDFont->insertRef("FontDescriptor", doc->emit(*descriptor));
+ newCIDFont->insertName("BaseFont", metrics.fPostScriptName);
+
+ switch (type) {
+ case SkAdvancedTypefaceMetrics::kType1CID_Font:
+ newCIDFont->insertName("Subtype", "CIDFontType0");
+ break;
+ case SkAdvancedTypefaceMetrics::kTrueType_Font:
+ newCIDFont->insertName("Subtype", "CIDFontType2");
+ newCIDFont->insertName("CIDToGIDMap", "Identity");
+ break;
+ default:
+ SkASSERT(false);
+ }
+ auto sysInfo = SkPDFMakeDict();
+ // These are actually ASCII strings.
+ sysInfo->insertByteString("Registry", "Adobe");
+ sysInfo->insertByteString("Ordering", "Identity");
+ sysInfo->insertInt("Supplement", 0);
+ newCIDFont->insertObject("CIDSystemInfo", std::move(sysInfo));
+
+ SkScalar defaultWidth = 0;
+ {
+ std::unique_ptr<SkPDFArray> widths = SkPDFMakeCIDGlyphWidthsArray(
+ *face, font.glyphUsage(), &defaultWidth);
+ if (widths && widths->size() > 0) {
+ newCIDFont->insertObject("W", std::move(widths));
+ }
+ newCIDFont->insertScalar("DW", defaultWidth);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ SkPDFDict fontDict("Font");
+ fontDict.insertName("Subtype", "Type0");
+ fontDict.insertName("BaseFont", metrics.fPostScriptName);
+ fontDict.insertName("Encoding", "Identity-H");
+ auto descendantFonts = SkPDFMakeArray();
+ descendantFonts->appendRef(doc->emit(*newCIDFont));
+ fontDict.insertObject("DescendantFonts", std::move(descendantFonts));
+
+ const std::vector<SkUnichar>& glyphToUnicode =
+ SkPDFFont::GetUnicodeMap(font.typeface(), doc);
+ SkASSERT(SkToSizeT(font.typeface()->countGlyphs()) == glyphToUnicode.size());
+ std::unique_ptr<SkStreamAsset> toUnicode =
+ SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+ &font.glyphUsage(),
+ font.multiByteGlyphs(),
+ font.firstGlyphID(),
+ font.lastGlyphID());
+ fontDict.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicode), doc));
+
+ doc->emit(fontDict, font.indirectReference());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PDFType3Font
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+// returns [0, first, first+1, ... last-1, last]
+struct SingleByteGlyphIdIterator {
+ SingleByteGlyphIdIterator(SkGlyphID first, SkGlyphID last)
+ : fFirst(first), fLast(last) {
+ SkASSERT(fFirst > 0);
+ SkASSERT(fLast >= first);
+ }
+ struct Iter {
+ void operator++() {
+ fCurrent = (0 == fCurrent) ? fFirst : fCurrent + 1;
+ }
+ // This is an input_iterator
+ SkGlyphID operator*() const { return (SkGlyphID)fCurrent; }
+ bool operator!=(const Iter& rhs) const {
+ return fCurrent != rhs.fCurrent;
+ }
+ Iter(SkGlyphID f, int c) : fFirst(f), fCurrent(c) {}
+ private:
+ const SkGlyphID fFirst;
+ int fCurrent; // must be int to make fLast+1 to fit
+ };
+ Iter begin() const { return Iter(fFirst, 0); }
+ Iter end() const { return Iter(fFirst, (int)fLast + 1); }
+private:
+ const SkGlyphID fFirst;
+ const SkGlyphID fLast;
+};
+} // namespace
+
+struct ImageAndOffset {
+ sk_sp<SkImage> fImage;
+ SkIPoint fOffset;
+};
+static ImageAndOffset to_image(SkGlyphID gid, SkBulkGlyphMetricsAndImages* smallGlyphs) {
+ const SkGlyph* glyph = smallGlyphs->glyph(SkPackedGlyphID{gid});
+ SkMask mask = glyph->mask();
+ if (!mask.fImage) {
+ return {nullptr, {0, 0}};
+ }
+ SkIRect bounds = mask.fBounds;
+ SkBitmap bm;
+ switch (mask.fFormat) {
+ case SkMask::kBW_Format:
+ bm.allocPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()));
+ for (int y = 0; y < bm.height(); ++y) {
+ for (int x8 = 0; x8 < bm.width(); x8 += 8) {
+ uint8_t v = *mask.getAddr1(x8 + bounds.x(), y + bounds.y());
+ int e = std::min(x8 + 8, bm.width());
+ for (int x = x8; x < e; ++x) {
+ *bm.getAddr8(x, y) = (v >> (x & 0x7)) & 0x1 ? 0xFF : 0x00;
+ }
+ }
+ }
+ bm.setImmutable();
+ return {bm.asImage(), {bounds.x(), bounds.y()}};
+ case SkMask::kA8_Format:
+ bm.installPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()),
+ mask.fImage, mask.fRowBytes);
+ return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode),
+ {bounds.x(), bounds.y()}};
+ case SkMask::kARGB32_Format:
+ bm.installPixels(SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()),
+ mask.fImage, mask.fRowBytes);
+ return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode),
+ {bounds.x(), bounds.y()}};
+ case SkMask::k3D_Format:
+ case SkMask::kLCD16_Format:
+ default:
+ SkASSERT(false);
+ return {nullptr, {0, 0}};
+ }
+}
+
+static SkPDFIndirectReference type3_descriptor(SkPDFDocument* doc,
+ const SkTypeface* typeface,
+ SkScalar xHeight) {
+ if (SkPDFIndirectReference* ptr = doc->fType3FontDescriptors.find(typeface->uniqueID())) {
+ return *ptr;
+ }
+
+ SkPDFDict descriptor("FontDescriptor");
+ int32_t fontDescriptorFlags = kPdfSymbolic;
+ if (const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, doc)) {
+ // Type3 FontDescriptor does not require all the same fields.
+ descriptor.insertName("FontName", metrics->fPostScriptName);
+ descriptor.insertInt("ItalicAngle", metrics->fItalicAngle);
+ fontDescriptorFlags |= (int32_t)metrics->fStyle;
+ // Adobe requests CapHeight, XHeight, and StemV be added
+ // to "greatly help our workflow downstream".
+ if (metrics->fCapHeight != 0) { descriptor.insertInt("CapHeight", metrics->fCapHeight); }
+ if (metrics->fStemV != 0) { descriptor.insertInt("StemV", metrics->fStemV); }
+ if (xHeight != 0) {
+ descriptor.insertScalar("XHeight", xHeight);
+ }
+ }
+ descriptor.insertInt("Flags", fontDescriptorFlags);
+ SkPDFIndirectReference ref = doc->emit(descriptor);
+ doc->fType3FontDescriptors.set(typeface->uniqueID(), ref);
+ return ref;
+}
+
+#ifdef SK_PDF_BITMAP_GLYPH_RASTER_SIZE
+static constexpr float kBitmapFontSize = SK_PDF_BITMAP_GLYPH_RASTER_SIZE;
+#else
+static constexpr float kBitmapFontSize = 64;
+#endif
+
+SkStrikeSpec make_small_strike(const SkTypeface& typeface) {
+ SkFont font(sk_ref_sp(&typeface), kBitmapFontSize);
+ font.setHinting(SkFontHinting::kNone);
+ font.setEdging(SkFont::Edging::kAlias);
+ return SkStrikeSpec::MakeMask(font,
+ SkPaint(),
+ SkSurfaceProps(0, kUnknown_SkPixelGeometry),
+ SkScalerContextFlags::kFakeGammaAndBoostContrast,
+ SkMatrix::I());
+}
+
+static void emit_subset_type3(const SkPDFFont& pdfFont, SkPDFDocument* doc) {
+ SkTypeface* typeface = pdfFont.typeface();
+ SkGlyphID firstGlyphID = pdfFont.firstGlyphID();
+ SkGlyphID lastGlyphID = pdfFont.lastGlyphID();
+ const SkPDFGlyphUse& subset = pdfFont.glyphUsage();
+ SkASSERT(lastGlyphID >= firstGlyphID);
+ // Remove unused glyphs at the end of the range.
+ // Keep the lastGlyphID >= firstGlyphID invariant true.
+ while (lastGlyphID > firstGlyphID && !subset.has(lastGlyphID)) {
+ --lastGlyphID;
+ }
+ int unitsPerEm;
+ SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(*typeface, &unitsPerEm);
+ auto strike = strikeSpec.findOrCreateStrike();
+ SkASSERT(strike);
+ SkScalar emSize = (SkScalar)unitsPerEm;
+ SkScalar xHeight = strike->getFontMetrics().fXHeight;
+ SkBulkGlyphMetricsAndPaths metricsAndPaths((sk_sp<SkStrike>(strike)));
+ SkBulkGlyphMetricsAndDrawables metricsAndDrawables(std::move(strike));
+
+ SkStrikeSpec strikeSpecSmall = kBitmapFontSize > 0 ? make_small_strike(*typeface)
+ : strikeSpec;
+
+ SkBulkGlyphMetricsAndImages smallGlyphs(strikeSpecSmall);
+ float bitmapScale = kBitmapFontSize > 0 ? emSize / kBitmapFontSize : 1.0f;
+
+ SkPDFDict font("Font");
+ font.insertName("Subtype", "Type3");
+ // Flip about the x-axis and scale by 1/emSize.
+ SkMatrix fontMatrix;
+ fontMatrix.setScale(SkScalarInvert(emSize), -SkScalarInvert(emSize));
+ font.insertObject("FontMatrix", SkPDFUtils::MatrixToArray(fontMatrix));
+
+ auto charProcs = SkPDFMakeDict();
+ auto encoding = SkPDFMakeDict("Encoding");
+
+ auto encDiffs = SkPDFMakeArray();
+ // length(firstGlyphID .. lastGlyphID) == lastGlyphID - firstGlyphID + 1
+ // plus 1 for glyph 0;
+ SkASSERT(firstGlyphID > 0);
+ SkASSERT(lastGlyphID >= firstGlyphID);
+ int glyphCount = lastGlyphID - firstGlyphID + 2;
+ // one other entry for the index of first glyph.
+ encDiffs->reserve(glyphCount + 1);
+ encDiffs->appendInt(0); // index of first glyph
+
+ auto widthArray = SkPDFMakeArray();
+ widthArray->reserve(glyphCount);
+
+ SkIRect bbox = SkIRect::MakeEmpty();
+
+ std::vector<std::pair<SkGlyphID, SkPDFIndirectReference>> imageGlyphs;
+ for (SkGlyphID gID : SingleByteGlyphIdIterator(firstGlyphID, lastGlyphID)) {
+ bool skipGlyph = gID != 0 && !subset.has(gID);
+ SkString characterName;
+ SkScalar advance = 0.0f;
+ SkIRect glyphBBox;
+ if (skipGlyph) {
+ characterName.set("g0");
+ } else {
+ characterName.printf("g%X", gID);
+ const SkGlyph* pathGlyph = metricsAndPaths.glyph(gID);
+ const SkGlyph* drawableGlyph = metricsAndDrawables.glyph(gID);
+ advance = pathGlyph->advanceX();
+ glyphBBox = pathGlyph->iRect();
+ bbox.join(glyphBBox);
+ const SkPath* path = pathGlyph->path();
+ SkDrawable* drawable = drawableGlyph->drawable();
+ SkDynamicMemoryWStream content;
+ if (drawable && !drawable->getBounds().isEmpty()) {
+ sk_sp<SkPDFDevice> glyphDevice = sk_make_sp<SkPDFDevice>(glyphBBox.size(), doc);
+ SkCanvas canvas(glyphDevice);
+ canvas.translate(-glyphBBox.fLeft, -glyphBBox.fTop);
+ canvas.drawDrawable(drawable);
+ SkPDFIndirectReference xobject = SkPDFMakeFormXObject(
+ doc, glyphDevice->content(),
+ SkPDFMakeArray(0, 0, glyphBBox.width(), glyphBBox.height()),
+ glyphDevice->makeResourceDict(),
+ SkMatrix::Translate(glyphBBox.fLeft, glyphBBox.fTop), nullptr);
+ imageGlyphs.emplace_back(gID, xobject);
+ SkPDFUtils::AppendScalar(drawableGlyph->advanceX(), &content);
+ content.writeText(" 0 d0\n1 0 0 1 0 0 cm\n/X");
+ content.write(characterName.c_str(), characterName.size());
+ content.writeText(" Do\n");
+ } else if (path && !path->isEmpty()) {
+ setGlyphWidthAndBoundingBox(pathGlyph->advanceX(), glyphBBox, &content);
+ SkPDFUtils::EmitPath(*path, SkPaint::kFill_Style, &content);
+ SkPDFUtils::PaintPath(SkPaint::kFill_Style, path->getFillType(), &content);
+ } else {
+ auto pimg = to_image(gID, &smallGlyphs);
+ if (!pimg.fImage) {
+ setGlyphWidthAndBoundingBox(pathGlyph->advanceX(), glyphBBox, &content);
+ } else {
+ using SkPDFUtils::AppendScalar;
+ imageGlyphs.emplace_back(gID, SkPDFSerializeImage(pimg.fImage.get(), doc));
+ AppendScalar(pathGlyph->advanceX(), &content);
+ content.writeText(" 0 d0\n");
+ AppendScalar(pimg.fImage->width() * bitmapScale, &content);
+ content.writeText(" 0 0 ");
+ AppendScalar(-pimg.fImage->height() * bitmapScale, &content);
+ content.writeText(" ");
+ AppendScalar(pimg.fOffset.x() * bitmapScale, &content);
+ content.writeText(" ");
+ AppendScalar((pimg.fImage->height() + pimg.fOffset.y()) * bitmapScale,
+ &content);
+ content.writeText(" cm\n/X");
+ content.write(characterName.c_str(), characterName.size());
+ content.writeText(" Do\n");
+ }
+ }
+ charProcs->insertRef(characterName, SkPDFStreamOut(nullptr,
+ content.detachAsStream(), doc));
+ }
+ encDiffs->appendName(std::move(characterName));
+ widthArray->appendScalar(advance);
+ }
+
+ if (!imageGlyphs.empty()) {
+ auto d0 = SkPDFMakeDict();
+ for (const auto& pair : imageGlyphs) {
+ d0->insertRef(SkStringPrintf("Xg%X", pair.first), pair.second);
+ }
+ auto d1 = SkPDFMakeDict();
+ d1->insertObject("XObject", std::move(d0));
+ font.insertObject("Resources", std::move(d1));
+ }
+
+ encoding->insertObject("Differences", std::move(encDiffs));
+ font.insertInt("FirstChar", 0);
+ font.insertInt("LastChar", lastGlyphID - firstGlyphID + 1);
+ /* FontBBox: "A rectangle expressed in the glyph coordinate
+ system, specifying the font bounding box. This is the smallest
+ rectangle enclosing the shape that would result if all of the
+ glyphs of the font were placed with their origins coincident and
+ then filled." */
+ font.insertObject("FontBBox", SkPDFMakeArray(bbox.left(),
+ bbox.bottom(),
+ bbox.right(),
+ bbox.top()));
+
+ font.insertName("CIDToGIDMap", "Identity");
+
+ const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, doc);
+ SkASSERT(glyphToUnicode.size() == SkToSizeT(typeface->countGlyphs()));
+ auto toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+ &subset,
+ false,
+ firstGlyphID,
+ lastGlyphID);
+ font.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicodeCmap), doc));
+ font.insertRef("FontDescriptor", type3_descriptor(doc, typeface, xHeight));
+ font.insertObject("Widths", std::move(widthArray));
+ font.insertObject("Encoding", std::move(encoding));
+ font.insertObject("CharProcs", std::move(charProcs));
+
+ doc->emit(font, pdfFont.indirectReference());
+}
+
+void SkPDFFont::emitSubset(SkPDFDocument* doc) const {
+ switch (fFontType) {
+ case SkAdvancedTypefaceMetrics::kType1CID_Font:
+ case SkAdvancedTypefaceMetrics::kTrueType_Font:
+ return emit_subset_type0(*this, doc);
+#ifndef SK_PDF_DO_NOT_SUPPORT_TYPE_1_FONTS
+ case SkAdvancedTypefaceMetrics::kType1_Font:
+ return SkPDFEmitType1Font(*this, doc);
+#endif
+ default:
+ return emit_subset_type3(*this, doc);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SkPDFFont::CanEmbedTypeface(SkTypeface* typeface, SkPDFDocument* doc) {
+ const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, doc);
+ return metrics && can_embed(*metrics);
+}
+