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())) {
AutoSTMalloc<4, SkScalar> intervals;
SkPathEffectBase::DashInfo dashInfo(intervals, 4, 0); if (peb->asADash(&dashInfo) == SkPathEffectBase::DashType::kDash) { if (dashInfo.fCount > 4) {
intervals.realloc(dashInfo.fCount);
peb->asADash(&dashInfo);
} for (int32_t i = 0; i < dashInfo.fCount; ++i) {
dashInfo.fIntervals[i] *= fontToEMScale;
}
dashInfo.fPhase *= fontToEMScale;
paint.setPathEffect(
SkDashPathEffect::Make(dashInfo.fIntervals, dashInfo.fCount, dashInfo.fPhase));
} else { returnfalse;
}
}
/////////////////////////////////////////////////////////////////////////////// // 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.
*/
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();
}
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. // 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) || // 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, 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()));
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.