| /* | |
| * 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 <array> | |
| #include "src/pdf/SkPDFFont.h" | |
| #include "include/codec/SkCodec.h" | |
| #include "include/core/SkAlphaType.h" | |
| #include "include/core/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkDrawable.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkFontMetrics.h" | |
| #include "include/core/SkFontStyle.h" | |
| #include "include/core/SkFontTypes.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMaskFilter.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathTypes.h" | |
| #include "include/core/SkPixmap.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkSpan.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "include/docs/SkPDFDocument.h" | |
| #include "include/effects/SkDashPathEffect.h" | |
| #include "include/private/SkDebug.h" | |
| #include "include/private/SkTPin.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "include/private/SkTo.h" | |
| #include "src/core/SkBitmaskEnum.h" | |
| #include "src/core/SkDescriptor.h" | |
| #include "src/core/SkDevice.h" | |
| #include "src/core/SkGlyph.h" | |
| #include "src/core/SkMask.h" | |
| #include "src/core/SkMaskFilterBase.h" | |
| #include "src/core/SkPathEffectBase.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/SkPDFFormXObject.h" | |
| #include "src/pdf/SkPDFGraphicState.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 <algorithm> | |
| #include <cstddef> | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| using namespace skia_private; | |
| 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; | |
| // 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 | |
| static bool scale_paint(SkPaint& paint, SkScalar fontToEMScale) { | |
| // What we really want here is a way ask the path effect or mask filter for a scaled | |
| // version of itself (if it is linearly scalable). | |
| if (SkMaskFilterBase* mfb = as_MFB(paint.getMaskFilter())) { | |
| SkMaskFilterBase::BlurRec blurRec; | |
| if (mfb->asABlur(&blurRec)) { | |
| // asABlur returns false if ignoring the CTM | |
| blurRec.fSigma *= fontToEMScale; | |
| paint.setMaskFilter(SkMaskFilter::MakeBlur(blurRec.fStyle, blurRec.fSigma, true)); | |
| } else { | |
| return false; | |
| } | |
| } | |
| if (SkPathEffectBase* peb = as_PEB(paint.getPathEffect())) { | |
| if (auto dashInfo = peb->asADash()) { | |
| SkSpan<const SkScalar> src = dashInfo->fIntervals; | |
| AutoSTArray<4, SkScalar> dst(src.size()); | |
| for (size_t i = 0; i < src.size(); ++i) { | |
| dst[i] = src[i] * fontToEMScale; | |
| } | |
| paint.setPathEffect(SkDashPathEffect::Make(dst, dashInfo->fPhase * fontToEMScale)); | |
| } else { | |
| return false; | |
| } | |
| } | |
| if (paint.getStyle() != SkPaint::kFill_Style && paint.getStrokeWidth() > 0) { | |
| paint.setStrokeWidth(paint.getStrokeWidth() * fontToEMScale); | |
| } | |
| return true; | |
| } | |
| SkPDFStrikeSpec::SkPDFStrikeSpec(SkStrikeSpec strikeSpec, SkScalar em) | |
| : fStrikeSpec(std::move(strikeSpec)) | |
| , fUnitsPerEM(em) | |
| {} | |
| sk_sp<SkPDFStrike> SkPDFStrike::Make(SkPDFDocument* doc, const SkFont& font, const SkPaint& paint) { | |
| #ifdef SK_PDF_BITMAP_GLYPH_RASTER_SIZE | |
| static constexpr float kBitmapFontSize = SK_PDF_BITMAP_GLYPH_RASTER_SIZE; | |
| #else | |
| static constexpr float kBitmapFontSize = 64; | |
| #endif | |
| SkScalar unitsPerEm = static_cast<SkScalar>(font.getTypeface()->getUnitsPerEm()); | |
| int glyphCount = font.getTypeface()->countGlyphs(); | |
| if (unitsPerEm <= 0 || glyphCount <= 0) { | |
| return nullptr; | |
| } | |
| SkFont canonFont(font); | |
| canonFont.setBaselineSnap(false); // canonicalize | |
| canonFont.setEdging(SkFont::Edging::kAntiAlias); // canonicalize | |
| canonFont.setEmbeddedBitmaps(false); // canonicalize | |
| //canonFont.setEmbolden(); // applied by scaler context, sets glyph path to modified | |
| canonFont.setForceAutoHinting(false); // canonicalize | |
| canonFont.setHinting(SkFontHinting::kNone); // canonicalize | |
| canonFont.setLinearMetrics(true); // canonicalize | |
| canonFont.setScaleX(1.0f); // original value applied by SkPDFDevice | |
| //canonFont.setSize(unitsPerEm); // canonicalize below, adjusted by SkPDFDevice | |
| canonFont.setSkewX(0.0f); // original value applied by SkPDFDevice | |
| canonFont.setSubpixel(false); // canonicalize | |
| //canonFont.setTypeface(); | |
| SkPaint pathPaint(paint); | |
| if (scale_paint(pathPaint, unitsPerEm / font.getSize())) { | |
| canonFont.setSize(unitsPerEm); | |
| } else { | |
| canonFont.setSize(font.getSize()); | |
| } | |
| SkScalar pathStrikeEM = canonFont.getSize(); | |
| SkStrikeSpec pathStrikeSpec = SkStrikeSpec::MakeWithNoDevice(canonFont, &pathPaint, | |
| SkScalerContextFlags::kNone); | |
| if (sk_sp<SkPDFStrike>* strike = doc->fStrikes.find(pathStrikeSpec.descriptor())) { | |
| return *strike; | |
| } | |
| if (kBitmapFontSize <= 0) { | |
| // old code path compatibility | |
| sk_sp<SkPDFStrike> strike(new SkPDFStrike(SkPDFStrikeSpec(pathStrikeSpec, pathStrikeEM), | |
| SkPDFStrikeSpec(pathStrikeSpec, pathStrikeEM), | |
| pathPaint.getMaskFilter(), doc)); | |
| doc->fStrikes.set(strike); | |
| return strike; | |
| } | |
| SkPaint imagePaint(paint); | |
| if (scale_paint(imagePaint, kBitmapFontSize / font.getSize())) { | |
| canonFont.setSize(kBitmapFontSize); | |
| } else { | |
| canonFont.setSize(font.getSize()); | |
| } | |
| SkScalar imageStrikeEM = canonFont.getSize(); | |
| SkStrikeSpec imageStrikeSpec = SkStrikeSpec::MakeWithNoDevice(canonFont, &imagePaint, | |
| SkScalerContextFlags::kNone); | |
| sk_sp<SkPDFStrike> strike(new SkPDFStrike(SkPDFStrikeSpec(pathStrikeSpec, pathStrikeEM), | |
| SkPDFStrikeSpec(imageStrikeSpec, imageStrikeEM), | |
| pathPaint.getMaskFilter(), doc)); | |
| doc->fStrikes.set(strike); | |
| return strike; | |
| } | |
| SkPDFStrike::SkPDFStrike(SkPDFStrikeSpec path, SkPDFStrikeSpec image, bool hasMaskFilter, | |
| SkPDFDocument* doc) | |
| : fPath(std::move(path)) | |
| , fImage(std::move(image)) | |
| , fHasMaskFilter(hasMaskFilter) | |
| , fDoc(doc) | |
| { | |
| SkASSERT(fDoc); | |
| } | |
| const SkDescriptor& SkPDFStrike::Traits::GetKey(const sk_sp<SkPDFStrike>& strike) { | |
| return strike->fPath.fStrikeSpec.descriptor(); | |
| } | |
| uint32_t SkPDFStrike::Traits::Hash(const SkDescriptor& descriptor) { | |
| return descriptor.getChecksum(); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // 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; | |
| static bool can_embed(const SkAdvancedTypefaceMetrics& metrics) { | |
| return !SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag); | |
| } | |
| static bool can_subset(const SkAdvancedTypefaceMetrics& metrics) { | |
| return !SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotSubsettable_FontFlag); | |
| } | |
| const SkAdvancedTypefaceMetrics* SkPDFFont::GetMetrics(const SkTypeface& typeface, | |
| SkPDFDocument* canon) { | |
| 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'}) { | |
| SkGlyphID g = font.unicharToGlyph(c); | |
| SkRect bounds; | |
| font.getBounds({&g, 1}, {&bounds, 1}, 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'}) { | |
| SkGlyphID g = font.unicharToGlyph(c); | |
| SkRect bounds; | |
| font.getBounds({&g, 1}, {&bounds, 1}, 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(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); | |
| return *canon->fToUnicodeMap.set(id, std::move(buffer)); | |
| } | |
| THashMap<SkGlyphID, SkString>& SkPDFFont::GetUnicodeMapEx(const SkTypeface& typeface, | |
| SkPDFDocument* canon) { | |
| SkASSERT(canon); | |
| SkTypefaceID id = typeface.uniqueID(); | |
| if (THashMap<SkGlyphID, SkString>* ptr = canon->fToUnicodeMapEx.find(id)) { | |
| return *ptr; | |
| } | |
| return *canon->fToUnicodeMapEx.set(id, THashMap<SkGlyphID, SkString>()); | |
| } | |
| SkAdvancedTypefaceMetrics::FontType SkPDFFont::FontType(const SkPDFStrike& pdfStrike, | |
| 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. | |
| // Export these as Type3 if the subsetter cannot handle table based fonts. | |
| (SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kAltDataFormat_FontFlag) | |
| && !SkPDFCanSubsetTableBasedFonts()) || | |
| SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag) || | |
| // Something like 45eeeddb00741493 and 7c86e7641b348ca7b0 to output OpenType should work, | |
| // but requires PDF 1.6 which is still not supported by all printers. One could fix this by | |
| // using bare CFF like 31a170226c22244cbd00497b67f6ae181f0f3e76 which is only PDF 1.3, | |
| // but this only works when the CFF CIDs == CFF index == GlyphID as PDF bare CFF prefers | |
| // CFF CIDs instead of GlyphIDs and Skia doesn't know the CIDs. | |
| metrics.fType == SkAdvancedTypefaceMetrics::kCFF_Font || | |
| pdfStrike.fHasMaskFilter) | |
| { | |
| // force Type3 fallback. | |
| 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* SkPDFStrike::getFontResource(const SkGlyph* glyph) { | |
| const SkTypeface& typeface = fPath.fStrikeSpec.typeface(); | |
| const SkAdvancedTypefaceMetrics* fontMetrics = SkPDFFont::GetMetrics(typeface, fDoc); | |
| SkASSERT(fontMetrics); // SkPDFDevice::internalDrawText ensures the typeface is good. | |
| // GetMetrics only returns null to signify a bad typeface. | |
| const SkAdvancedTypefaceMetrics& metrics = *fontMetrics; | |
| // Determine the FontType. | |
| // 1. Can the "original" font data be used directly | |
| // (simple OpenType, no non-default variations, not WOFF, etc). | |
| // 2. Is the glyph to be drawn unmodified from the font data | |
| // (no path effect, stroking, fake bolding, extra matrix, mask filter). | |
| // 3. Will PDF viewers draw this glyph the way we want | |
| // (at the moment this means an unmodified glyph path). | |
| SkAdvancedTypefaceMetrics::FontType type = SkPDFFont::FontType(*this, metrics); | |
| // Keep the type (and original data) if the glyph is empty or the glyph has an unmodified path. | |
| // Otherwise, fall back to Type3. | |
| if (!(glyph->isEmpty() || (glyph->path() && !glyph->pathIsModified()))) { | |
| type = SkAdvancedTypefaceMetrics::kOther_Font; | |
| } | |
| bool multibyte = SkPDFFont::IsMultiByte(type); | |
| SkGlyphID subsetCode = | |
| multibyte ? 0 : first_nonzero_glyph_for_single_byte_encoding(glyph->getGlyphID()); | |
| if (SkPDFFont* font = fFontMap.find(subsetCode)) { | |
| SkASSERT(multibyte == font->multiByteGlyphs()); | |
| return font; | |
| } | |
| SkGlyphID lastGlyph = SkToU16(typeface.countGlyphs() - 1); | |
| SkASSERT(glyph->getGlyphID() <= lastGlyph); // should be caught by SkPDFDevice::internalDrawText | |
| SkGlyphID firstNonZeroGlyph; | |
| if (multibyte) { | |
| firstNonZeroGlyph = 1; | |
| } else { | |
| firstNonZeroGlyph = subsetCode; | |
| lastGlyph = SkToU16(std::min<int>((int)lastGlyph, 254 + (int)subsetCode)); | |
| } | |
| auto ref = fDoc->reserveRef(); | |
| return fFontMap.set(subsetCode, SkPDFFont(this, firstNonZeroGlyph, lastGlyph, type, ref)); | |
| } | |
| SkPDFFont::SkPDFFont(const SkPDFStrike* strike, | |
| SkGlyphID firstGlyphID, | |
| SkGlyphID lastGlyphID, | |
| SkAdvancedTypefaceMetrics::FontType fontType, | |
| SkPDFIndirectReference indirectReference) | |
| : fStrike(strike) | |
| , 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 | |
| /////////////////////////////////////////////////////////////////////////////// | |
| static void emit_subset_type0(const SkPDFFont& font, SkPDFDocument* doc) { | |
| const SkTypeface& typeface = font.strike().fPath.fStrikeSpec.typeface(); | |
| const SkAdvancedTypefaceMetrics* metricsPtr = SkPDFFont::GetMetrics(typeface, doc); | |
| SkASSERT(metricsPtr); | |
| if (!metricsPtr) { | |
| return; | |
| } | |
| const SkAdvancedTypefaceMetrics& metrics = *metricsPtr; | |
| SkASSERT(can_embed(metrics)); | |
| SkAdvancedTypefaceMetrics::FontType type = font.getType(); | |
| auto descriptor = SkPDFMakeDict("FontDescriptor"); | |
| uint16_t emSize = SkToU16(SkScalarRoundToInt(font.strike().fPath.fUnitsPerEM)); | |
| SkPDFFont::PopulateCommonFontDescriptor(descriptor.get(), metrics, emSize, 0); | |
| int ttcIndex; | |
| std::unique_ptr<SkStreamAsset> fontAsset = typeface.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", &typeface, fontAsset.get()); | |
| } else if (type == SkAdvancedTypefaceMetrics::kTrueType_Font) { | |
| sk_sp<SkData> subsetFontData; | |
| if (can_subset(metrics)) { | |
| SkASSERT(font.firstGlyphID() == 1); | |
| subsetFontData = SkPDFSubsetFont(typeface, font.glyphUsage()); | |
| } | |
| std::unique_ptr<SkStreamAsset> subsetFontAsset; | |
| if (subsetFontData) { | |
| subsetFontAsset = SkMemoryStream::Make(std::move(subsetFontData)); | |
| } else { | |
| // If subsetting fails, fall back to original font data. | |
| subsetFontAsset = std::move(fontAsset); | |
| } | |
| std::unique_ptr<SkPDFDict> streamDict = SkPDFMakeDict(); | |
| streamDict->insertInt("Length1", subsetFontAsset->getLength()); | |
| descriptor->insertRef("FontFile2", | |
| SkPDFStreamOut(std::move(streamDict), std::move(subsetFontAsset), | |
| doc, SkPDFSteamCompressionEnabled::Yes)); | |
| } else if (type == SkAdvancedTypefaceMetrics::kType1CID_Font) { | |
| std::unique_ptr<SkPDFDict> streamDict = SkPDFMakeDict(); | |
| streamDict->insertName("Subtype", "CIDFontType0C"); | |
| descriptor->insertRef("FontFile3", | |
| SkPDFStreamOut(std::move(streamDict), std::move(fontAsset), | |
| doc, SkPDFSteamCompressionEnabled::Yes)); | |
| } else { | |
| 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)); | |
| // Unfortunately, poppler enforces DW (default width) must be an integer. | |
| int32_t defaultWidth = 0; | |
| { | |
| std::unique_ptr<SkPDFArray> widths = SkPDFMakeCIDGlyphWidthsArray( | |
| font.strike().fPath, font.glyphUsage(), &defaultWidth); | |
| if (widths && widths->size() > 0) { | |
| newCIDFont->insertObject("W", std::move(widths)); | |
| } | |
| newCIDFont->insertInt("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(typeface, doc); | |
| SkASSERT(SkToSizeT(typeface.countGlyphs()) == glyphToUnicode.size()); | |
| std::unique_ptr<SkStreamAsset> toUnicode = | |
| SkPDFMakeToUnicodeCmap(glyphToUnicode.data(), | |
| SkPDFFont::GetUnicodeMapEx(typeface, doc), | |
| &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: { | |
| // Make a gray image, used to smask a rectangle. | |
| // TODO: emit as MaskImage? | |
| const SkISize size = bounds.size(); | |
| bm.allocPixels(SkImageInfo::Make(size, kGray_8_SkColorType, kUnknown_SkAlphaType)); | |
| 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: | |
| case SkMask::k3D_Format: // just do the A8 part | |
| // Make a gray image, used to smask a rectangle. | |
| return {SkImages::RasterFromData( | |
| SkImageInfo::Make(bounds.size(), kGray_8_SkColorType, kUnknown_SkAlphaType), | |
| SkData::MakeWithCopy(mask.fImage, mask.computeImageSize()), | |
| mask.fRowBytes), | |
| {bounds.x(), bounds.y()}}; | |
| case SkMask::kARGB32_Format: | |
| // These will be drawn as images directly. | |
| return {SkImages::RasterFromData( | |
| SkImageInfo::MakeN32Premul(bounds.size()), | |
| SkData::MakeWithCopy(mask.fImage, mask.computeTotalImageSize()), | |
| mask.fRowBytes), | |
| {bounds.x(), bounds.y()}}; | |
| 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; | |
| /** PDF32000_2008: FontFamily should be used for Type3 fonts in Tagged PDF documents. */ | |
| SkString familyName; | |
| typeface.getFamilyName(&familyName); | |
| if (!familyName.isEmpty()) { | |
| descriptor.insertByteString("FontFamily", familyName); | |
| } | |
| /** PDF32000_2008: FontStretch should be used for Type3 fonts in Tagged PDF documents. */ | |
| static constexpr std::array<const char *, 9> stretchNames = { | |
| "UltraCondensed", | |
| "ExtraCondensed", | |
| "Condensed", | |
| "SemiCondensed", | |
| "Normal", | |
| "SemiExpanded", | |
| "Expanded", | |
| "ExtraExpanded", | |
| "UltraExpanded", | |
| }; | |
| const char* stretchName = stretchNames[typeface.fontStyle().width() - 1]; | |
| descriptor.insertName("FontStretch", stretchName); | |
| /** PDF32000_2008: FontWeight should be used for Type3 fonts in Tagged PDF documents. */ | |
| int weight = (typeface.fontStyle().weight() + 50) / 100; | |
| descriptor.insertInt("FontWeight", SkTPin(weight, 1, 9) * 100); | |
| 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; | |
| } | |
| static void emit_subset_type3(const SkPDFFont& pdfFont, SkPDFDocument* doc) { | |
| const SkPDFStrike& pdfStrike = pdfFont.strike(); | |
| 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; | |
| } | |
| SkScalar emSize = pdfStrike.fPath.fUnitsPerEM; | |
| sk_sp<SkStrike> strike = pdfStrike.fPath.fStrikeSpec.findOrCreateStrike(); | |
| SkASSERT(strike); | |
| SkScalar xHeight = strike->getFontMetrics().fXHeight; | |
| SkBulkGlyphMetricsAndPaths metricsAndPaths((sk_sp<SkStrike>(strike))); | |
| SkBulkGlyphMetricsAndDrawables metricsAndDrawables(std::move(strike)); | |
| SkBulkGlyphMetricsAndImages smallGlyphs(pdfFont.strike().fImage.fStrikeSpec); | |
| float bitmapScale = emSize / pdfStrike.fImage.fUnitsPerEM; | |
| 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::unique_ptr<SkPDFDict> xobjects = SkPDFMakeDict(); | |
| std::unique_ptr<SkPDFDict> graphicStates = SkPDFMakeDict(); | |
| for (SkGlyphID gID : SingleByteGlyphIdIterator(firstGlyphID, lastGlyphID)) { | |
| SkString characterName; | |
| SkScalar advance = 0.0f; | |
| if (gID != 0 && !subset.has(gID)) { | |
| characterName.set("g0"); | |
| advance = 0.0f; | |
| encDiffs->appendName(std::move(characterName)); | |
| widthArray->appendScalar(advance); | |
| continue; | |
| } | |
| const SkGlyph* pathGlyph = metricsAndPaths.glyph(gID); | |
| const SkGlyph* drawableGlyph = metricsAndDrawables.glyph(gID); | |
| characterName.printf("g%X", gID); | |
| advance = pathGlyph->advanceX(); | |
| encDiffs->appendName(characterName); | |
| widthArray->appendScalar(advance); | |
| SkIRect 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(), SkPDFParentTreeKey(), | |
| SkPDFMakeArray(0, 0, glyphBBox.width(), glyphBBox.height()), | |
| glyphDevice->makeResourceDict(), | |
| SkMatrix::Translate(glyphBBox.fLeft, glyphBBox.fTop), nullptr); | |
| xobjects->insertRef(SkStringPrintf("Xg%X", 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() && !pdfStrike.fHasMaskFilter) { | |
| setGlyphWidthAndBoundingBox(pathGlyph->advanceX(), glyphBBox, &content); | |
| SkPaint::Style style = pathGlyph->pathIsHairline() ? SkPaint::kStroke_Style | |
| : SkPaint::kFill_Style; | |
| using SkPDFUtils::EmptyPath, SkPDFUtils::EmptyVerb, SkPDFUtils::EmptyArea; | |
| const EmptyArea emptyArea = style == SkPaint::kFill_Style ? EmptyArea::Discard | |
| : EmptyArea::Preserve; | |
| if (SkPDFUtils::EmitPath(*path, EmptyPath::Discard, EmptyVerb::Discard, emptyArea, | |
| &content)) | |
| { | |
| SkPDFUtils::PaintPath(style, path->getFillType(), &content); | |
| } | |
| } else if (auto pimg = to_image(gID, &smallGlyphs); pimg.fImage) { | |
| using SkPDFUtils::AppendScalar; | |
| if (pimg.fImage->colorType() != kGray_8_SkColorType) { | |
| 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"); | |
| content.writeText("/X"); | |
| content.write(characterName.c_str(), characterName.size()); | |
| content.writeText(" Do\n"); | |
| SkPDFIndirectReference image = SkPDFSerializeImage(pimg.fImage.get(), doc, 101); | |
| xobjects->insertRef(SkStringPrintf("Xg%X", gID), image); | |
| } else { | |
| // TODO: For A1, put ImageMask on the PDF image and draw the image? | |
| // The A8 mask has been converted to a Gray image | |
| // This is a `d1` glyph (shaded with the current fill) | |
| const SkGlyph* smallGlyph = smallGlyphs.glyph(SkPackedGlyphID{gID}); | |
| SkRect smallBBox = smallGlyph->rect(); | |
| SkIRect smallIBox; | |
| SkMatrix::Scale(bitmapScale, bitmapScale).mapRect(smallBBox).roundOut(&smallIBox); | |
| bbox.join(smallIBox); | |
| setGlyphWidthAndBoundingBox(pathGlyph->advanceX(), smallIBox, &content); | |
| AppendScalar(bitmapScale, &content); | |
| content.writeText(" 0 0 "); | |
| AppendScalar(bitmapScale, &content); | |
| content.writeText(" "); | |
| AppendScalar(pimg.fOffset.x() * bitmapScale, &content); | |
| content.writeText(" "); | |
| AppendScalar(pimg.fOffset.y() * bitmapScale, &content); | |
| content.writeText(" cm\n"); | |
| // Convert Grey image to deferred jpeg image to emit as jpeg | |
| if (pdfStrike.fHasMaskFilter) { | |
| SkPDF::EncodeJpegCallback encodeJPEG = doc->metadata().jpegEncoder; | |
| SkPDF::DecodeJpegCallback decodeJPEG = doc->metadata().jpegDecoder; | |
| if (encodeJPEG && decodeJPEG) { | |
| SkImage* image = pimg.fImage.get(); | |
| SkPixmap pm; | |
| SkAssertResult(image->peekPixels(&pm)); | |
| SkDynamicMemoryWStream buffer; | |
| if (encodeJPEG(&buffer, pm, SK_PDF_MASK_QUALITY)) { | |
| std::unique_ptr<SkCodec> codec = | |
| decodeJPEG(buffer.detachAsData()); | |
| SkASSERT(codec); | |
| sk_sp<SkImage> jpegImage = SkCodecs::DeferredImage(std::move(codec)); | |
| SkASSERT(jpegImage); | |
| if (jpegImage) { | |
| pimg.fImage = jpegImage; | |
| } | |
| } | |
| } | |
| } | |
| // Draw image into a Form XObject | |
| const SkISize imageSize = pimg.fImage->dimensions(); | |
| sk_sp<SkPDFDevice> glyphDevice = sk_sp(new SkPDFDevice(imageSize, doc)); | |
| SkCanvas canvas(glyphDevice); | |
| canvas.drawImage(pimg.fImage, 0, 0); | |
| SkPDFIndirectReference sMask = SkPDFMakeFormXObject( | |
| doc, glyphDevice->content(), SkPDFParentTreeKey(), | |
| SkPDFMakeArray(0, 0, pimg.fImage->width(), pimg.fImage->height()), | |
| glyphDevice->makeResourceDict(), | |
| SkMatrix(), "DeviceGray"); | |
| // Use Form XObject as SMask (luminosity) on the graphics state | |
| SkPDFIndirectReference smaskGraphicState = SkPDFGraphicState::GetSMaskGraphicState( | |
| sMask, false, | |
| SkPDFGraphicState::kLuminosity_SMaskMode, doc); | |
| SkPDFUtils::ApplyGraphicState(smaskGraphicState.fValue, &content); | |
| // Draw a rectangle the size of the glyph (masked by SMask) | |
| SkPDFUtils::AppendRectangle(SkRect::Make(pimg.fImage->bounds()), &content); | |
| SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPathFillType::kWinding, &content); | |
| // Add glyph resources to font resource dict | |
| xobjects->insertRef(SkStringPrintf("Xg%X", gID), sMask); | |
| // TODO: name must match ApplyGraphicState | |
| graphicStates->insertRef(SkStringPrintf("G%d", smaskGraphicState.fValue), | |
| smaskGraphicState); | |
| } | |
| } else { | |
| setGlyphWidthAndBoundingBox(pathGlyph->advanceX(), glyphBBox, &content); | |
| } | |
| charProcs->insertRef(std::move(characterName), | |
| SkPDFStreamOut(nullptr, content.detachAsStream(), doc)); | |
| } | |
| if (xobjects->size() || graphicStates->size()) { | |
| auto resources = SkPDFMakeDict(); | |
| if (xobjects->size()) { | |
| resources->insertObject("XObject", std::move(xobjects)); | |
| } | |
| if (graphicStates->size()) { | |
| resources->insertObject("ExtGState", std::move(graphicStates)); | |
| } | |
| font.insertObject("Resources", std::move(resources)); | |
| } | |
| 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 SkTypeface& pathTypeface = pdfStrike.fPath.fStrikeSpec.typeface(); | |
| const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(pathTypeface, doc); | |
| SkASSERT(glyphToUnicode.size() == SkToSizeT(pathTypeface.countGlyphs())); | |
| auto toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(), | |
| SkPDFFont::GetUnicodeMapEx(pathTypeface, doc), | |
| &subset, | |
| false, | |
| firstGlyphID, | |
| lastGlyphID); | |
| font.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicodeCmap), doc)); | |
| font.insertRef("FontDescriptor", type3_descriptor(doc, pathTypeface, 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(const SkTypeface& typeface, SkPDFDocument* doc) { | |
| const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, doc); | |
| return metrics && can_embed(*metrics); | |
| } | |