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) staticconst 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;
}
staticbool 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 { returnfalse;
}
} 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 { returnfalse;
}
}
/////////////////////////////////////////////////////////////////////////////// // class SkPDFFont ///////////////////////////////////////////////////////////////////////////////
/* Resources are canonicalized and uniqueified by pointer so there has to be *someadditionalstateindicatingwhichsubsetofthefontisused.It *mustbemaintainedatthedocumentgranularity.
*/
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();
}
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;
}
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;
}
SkGlyphID lastGlyph = SkToU16(typeface.countGlyphs() - 1);
SkASSERT(glyph->getGlyphID() <= lastGlyph); // should be caught by SkPDFDevice::internalDrawText
/** 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 constchar* stretchNames[9] = { "UltraCondensed", "ExtraCondensed", "Condensed", "SemiCondensed", "Normal", "SemiExpanded", "Expanded", "ExtraExpanded", "UltraExpanded",
}; constchar* 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;
}
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);
// 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);
// 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,specifyingthefontboundingbox.Thisisthesmallest rectangleenclosingtheshapethatwouldresultifallofthe glyphsofthefontwereplacedwiththeiroriginscoincidentand
then filled." */
font.insertObject("FontBBox", SkPDFMakeArray(bbox.left(),
bbox.bottom(),
bbox.right(),
bbox.top()));
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.21Bemerkung:
(vorverarbeitet am 2026-08-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.